Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cd209ee5 authored by Elson Roy Serrao's avatar Elson Roy Serrao
Browse files

usb: gadget: Add qti_usb gadget driver



This change adds qti_gadget driver for early USB enumeration.
This is a snapshot of the driver from msm-4.14 as of commit
26acafd0651("usb: gadget: qti: Add support for MBIM OS descriptor
handling").

Change-Id: Ie6d3a6584d396132827949a30608d1efb2408e77
Signed-off-by: default avatarElson Roy Serrao <eserrao@codeaurora.org>
parent 629f8312
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -3000,6 +3000,7 @@ static int gsi_bind(struct usb_configuration *c, struct usb_function *f)
	struct gsi_function_bind_info info = {0};
	struct f_gsi *gsi = func_to_gsi(f);
	struct rndis_params *params;
	struct usb_os_desc *descs[1];
	struct gsi_opts *opts;
	int status;
	__u8  class;
@@ -3202,6 +3203,14 @@ static int gsi_bind(struct usb_configuration *c, struct usb_function *f)
			f->os_desc_n = 1;
			f->os_desc_table[0].os_desc = &opts->os_desc;
			f->os_desc_table[0].if_id = gsi->data_id;
			opts->os_desc.ext_compat_id = opts->ext_compat_id;
			descs[0] = &opts->os_desc;
			snprintf(sub_compatible_id, sizeof(sub_compatible_id),
					"%u", c->bConfigurationValue);
			memcpy(descs[0]->ext_compat_id, compatible_id,
					strlen(compatible_id));
			memcpy(descs[0]->ext_compat_id + 8, sub_compatible_id,
					strlen(sub_compatible_id));
		}
		break;
	case IPA_USB_RMNET:
+3 −0
Original line number Diff line number Diff line
@@ -88,6 +88,9 @@
/* ID for Microsoft OS String */
#define GSI_MBIM_OS_STRING_ID 0xEE

static char compatible_id[256] = "ALTRCFG";
static char sub_compatible_id[8];

#define EVT_NONE			0
#define EVT_UNINITIALIZED		1
#define EVT_INITIALIZED			2
+10 −0
Original line number Diff line number Diff line
@@ -350,6 +350,16 @@ config USB_G_PRINTER
	  For more information, see Documentation/usb/gadget_printer.rst
	  which includes sample code for accessing the device file.

config USB_G_QTI
	tristate "QTI composite gadget"
	select USB_LIBCOMPOSITE
	help
	  This gadget provides support for multi-configuration
	  composite device.
	  Functions can be specified via devicetree properties,
	  along with PID and PID details which can also also be
	  updated using kernel commandline using module parameters.

if TTY

config USB_CDC_COMPOSITE
+2 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ g_webcam-y := webcam.o
g_ncm-y				:= ncm.o
g_acm_ms-y			:= acm_ms.o
g_tcm_usb_gadget-y		:= tcm_usb_gadget.o
g_qti_gadget-y			:= qti_gadget.o

obj-$(CONFIG_USB_ZERO)		+= g_zero.o
obj-$(CONFIG_USB_AUDIO)		+= g_audio.o
@@ -44,3 +45,4 @@ obj-$(CONFIG_USB_G_NCM) += g_ncm.o
obj-$(CONFIG_USB_G_ACM_MS)	+= g_acm_ms.o
obj-$(CONFIG_USB_GADGET_TARGET)	+= tcm_usb_gadget.o
obj-$(CONFIG_USB_RAW_GADGET)	+= raw_gadget.o
obj-$(CONFIG_USB_G_QTI)		+= g_qti_gadget.o
+677 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
 */

#include <linux/module.h>
#include <linux/property.h>
#include <linux/usb/composite.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/kernel.h>

struct qti_usb_function {
	struct usb_function_instance *fi;
	struct usb_function *f;

	struct list_head list;
};

#define MAX_FUNC_NAME_LEN	48
#define MAX_CFG_NAME_LEN       128

#define QW_SIGN_LEN		14

char qw_sign[QW_SIGN_LEN] = "MSFT100";

struct qti_usb_config {
	struct usb_configuration c;

	/* List of functions bound to this config */
	struct list_head func_list;
	/* List of qti_usb_functions bound to this config */
	struct list_head qti_funcs;
};

struct qti_usb_gadget {
	struct usb_composite_dev cdev;
	struct usb_composite_driver composite;

	const char *composition_funcs;
	bool enabled;
	struct device *dev;
	struct device_node *cfg_node;
};

static char manufacturer_string[256] = "Qualcomm Technologies, Inc";
module_param_string(manufacturer, manufacturer_string,
		    sizeof(manufacturer_string), 0644);
MODULE_PARM_DESC(quirks, "String representing name of manufacturer");

static char product_string[256] = "USB_device!_SN:12345";
module_param_string(product, product_string,
		    sizeof(product_string), 0644);
MODULE_PARM_DESC(quirks, "String representing product string");

static char serialno_string[256] = "12345";

static char usb_pid_string[256];
module_param_string(usb_pid, usb_pid_string, sizeof(usb_pid_string), 0644);
MODULE_PARM_DESC(quirks, "String representing product id");

/* String Table */
static struct usb_string strings_dev[] = {
	[USB_GADGET_MANUFACTURER_IDX].s = manufacturer_string,
	[USB_GADGET_PRODUCT_IDX].s = product_string,
	[USB_GADGET_SERIAL_IDX].s = serialno_string,
	{  }			/* end of list */
};

static struct usb_gadget_strings stringtab_dev = {
	.language	= 0x0409,	/* en-us */
	.strings	= strings_dev,
};

static struct usb_gadget_strings *dev_strings[] = {
	&stringtab_dev,
	NULL,
};

static void qti_configs_remove_funcs(struct qti_usb_gadget *qg)
{
	struct usb_configuration	*c;

	list_for_each_entry(c, &qg->cdev.configs, list) {
		struct qti_usb_config *cfg;
		struct usb_function *f, *tmp;

		cfg = container_of(c, struct qti_usb_config, c);

		list_for_each_entry_safe_reverse(f, tmp, &c->functions, list) {

			list_move(&f->list, &cfg->func_list);
			if (f->unbind) {
				dev_dbg(&qg->cdev.gadget->dev,
					"unbind function '%s'/%pK\n",
					     f->name, f);
				f->unbind(c, f);
			}
		}
		c->fullspeed = 0;
		c->highspeed = 0;
		c->superspeed = 0;
		c->superspeed_plus = 0;
		c->next_interface_id = 0;
		memset(c->interface, 0, sizeof(c->interface));
	}
}

static int qti_composite_bind(struct usb_gadget *gadget,
		struct usb_gadget_driver *gdriver)
{
	struct usb_composite_driver *composite = to_cdriver(gdriver);
	struct qti_usb_gadget *qg = container_of(composite,
				     struct qti_usb_gadget, composite);
	struct usb_composite_dev *cdev = &qg->cdev;
	struct usb_configuration *c;
	struct usb_string *s;
	int ret = -EINVAL;

	cdev->gadget = gadget;
	set_gadget_data(gadget, cdev);
	spin_lock_init(&qg->cdev.lock);

	ret = composite_dev_prepare(composite, cdev);
	if (ret)
		return ret;

	if (list_empty(&cdev->configs)) {
		pr_err("No configurations found in %s.\n", composite->name);
		ret = -EINVAL;
		goto composite_cleanup;
	}

	list_for_each_entry(c, &cdev->configs, list) {
		struct qti_usb_config *qcfg;

		qcfg = container_of(c, struct qti_usb_config, c);
		if (list_empty(&qcfg->func_list)) {
			pr_err("Config %s/%d of %s doesn't have a function.\n",
			      c->label, c->bConfigurationValue,
			      qg->composite.name);
			goto composite_cleanup;
		}
	}

	s = usb_gstrings_attach(cdev, dev_strings, USB_GADGET_FIRST_AVAIL_IDX);
	if (IS_ERR(s)) {
		ret = PTR_ERR(s);
		goto composite_cleanup;
	}

	cdev->desc.iManufacturer = s[USB_GADGET_MANUFACTURER_IDX].id;
	cdev->desc.iProduct = s[USB_GADGET_PRODUCT_IDX].id;
	cdev->desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id;

	/* Go through all configs, attach all functions */
	list_for_each_entry(c, &qg->cdev.configs, list) {
		struct qti_usb_config *qcfg;
		struct usb_function *f, *tmp;

		qcfg = container_of(c, struct qti_usb_config, c);

		list_for_each_entry_safe(f, tmp, &qcfg->func_list, list) {
			list_del(&f->list);
			ret = usb_add_function(c, f);
			if (ret) {
				list_add(&f->list, &qcfg->func_list);
				goto remove_funcs;
			}
		}
		usb_ep_autoconfig_reset(cdev->gadget);
	}

	if (cdev->use_os_string) {
		ret = composite_os_desc_req_prepare(cdev, gadget->ep0);
		if (ret)
			goto remove_funcs;
	}

	usb_ep_autoconfig_reset(cdev->gadget);

	return 0;

remove_funcs:
	qti_configs_remove_funcs(qg);
composite_cleanup:
	composite_dev_cleanup(cdev);
	return ret;
}

static void qti_composite_unbind(struct usb_gadget *gadget)
{
	struct usb_composite_dev *cdev;
	struct qti_usb_gadget *qg;

	cdev = get_gadget_data(gadget);
	qg = container_of(cdev, struct qti_usb_gadget, cdev);

	qti_configs_remove_funcs(qg);
	composite_dev_cleanup(cdev);
	usb_ep_autoconfig_reset(cdev->gadget);
	cdev->gadget = NULL;
	set_gadget_data(gadget, NULL);
}

static const struct usb_gadget_driver qti_gadget_driver = {
	.bind           = qti_composite_bind,
	.unbind         = qti_composite_unbind,
	.setup          = composite_setup,
	.reset          = composite_disconnect,
	.disconnect     = composite_disconnect,
	.suspend	= composite_suspend,
	.resume		= composite_resume,

	.max_speed	= USB_SPEED_SUPER_PLUS,
	.driver = {
		.owner          = THIS_MODULE,
		.name		= "qti-gadget",
	},
};

static void qti_usb_funcs_free(struct qti_usb_config *qcfg)
{
	struct usb_function *f, *tmp;
	struct qti_usb_function *qf, *qf_tmp;

	list_for_each_entry_safe(f, tmp, &qcfg->func_list, list) {
		list_del(&f->list);
		usb_put_function(f);

		/* find corresponding function_instance and free it */
		list_for_each_entry_safe(qf, qf_tmp, &qcfg->qti_funcs, list) {
			if (qf->f == f) {
				list_del(&qf->list);
				usb_put_function_instance(qf->fi);
				kfree(qf);
				break;
			}
		}
	}
}

static void qti_cleanup_configs_funcs(struct qti_usb_gadget *qg)
{
	struct usb_configuration *c, *c_tmp;

	list_for_each_entry_safe(c, c_tmp, &qg->cdev.configs, list) {
		struct qti_usb_config *qcfg;

		qcfg = container_of(c, struct qti_usb_config, c);
		WARN_ON(!list_empty(&qcfg->c.functions));

		qti_usb_funcs_free(qcfg);

		list_del(&qcfg->c.list);
		kfree(qcfg->c.label);
		kfree(qcfg);
	}
}

static int qti_usb_func_alloc(struct qti_usb_config *qcfg,
				   const char *name)
{
	struct qti_usb_function *qf;
	struct usb_function_instance *fi;
	struct usb_composite_dev *cdev = qcfg->c.cdev;
	struct usb_function *f;
	char buf[MAX_FUNC_NAME_LEN];
	char *func_name;
	char *instance_name;
	int ret;

	ret = scnprintf(buf, MAX_FUNC_NAME_LEN, "%s", name);
	if (ret >= MAX_FUNC_NAME_LEN)
		return -ENAMETOOLONG;

	func_name = buf;
	instance_name = strnchr(func_name, MAX_FUNC_NAME_LEN, '.');
	if (!instance_name) {
		pr_err("Can't find . in <func>.<instance>:%s\n", buf);
		return -EINVAL;
	}
	*instance_name = '\0';
	instance_name++;

	qf = kzalloc(sizeof(*qf), GFP_KERNEL);
	if (!qf)
		return -ENOMEM;

	fi = usb_get_function_instance(func_name);
	if (IS_ERR(fi)) {
		kfree(qf);
		return PTR_ERR(fi);
	}
	qf->fi = fi;

	if (fi->set_inst_name) {
		ret = fi->set_inst_name(fi, instance_name);
		if (ret) {
			kfree(qf);
			usb_put_function_instance(fi);
			return ret;
		}
	}

	f = usb_get_function(fi);
	if (IS_ERR(f)) {
		kfree(qf);
		usb_put_function_instance(fi);
		return PTR_ERR(f);
	}
	qf->f = f;
	list_add_tail(&qf->list, &qcfg->qti_funcs);

	/* stash the function until we bind it to the gadget */
	list_add_tail(&f->list, &qcfg->func_list);

	if (!strcmp(instance_name, "mbim")) {
		cdev->os_desc_config = &qcfg->c;
		cdev->use_os_string = true;
		cdev->b_vendor_code = 0xA5;
		memcpy(cdev->qw_sign, qw_sign, QW_SIGN_LEN);
	}

	return 0;
}

static int qti_usb_funcs_alloc(struct qti_usb_config *qcfg,
				const char *funcs)
{
	char buf[MAX_CFG_NAME_LEN];
	char *fn_name, *next_fn;
	int ret = 0;

	ret = scnprintf(buf, MAX_CFG_NAME_LEN, "%s", funcs);
	if (ret >= MAX_CFG_NAME_LEN)
		return -ENAMETOOLONG;

	fn_name = buf;
	while (fn_name) {
		next_fn = strnchr(fn_name, MAX_CFG_NAME_LEN, ',');
		if (next_fn)
			*next_fn++ = '\0';

		ret = qti_usb_func_alloc(qcfg, fn_name);
		if (ret) {
			qti_usb_funcs_free(qcfg);
			break;
		}

		fn_name = next_fn;
	}

	return ret;
}

static int qti_usb_config_add(struct qti_usb_gadget *gadget,
				  const char *name, u8 num)
{
	struct qti_usb_config *qcfg;
	int ret = 0, val = 0;

	qcfg = kzalloc(sizeof(*qcfg), GFP_KERNEL);
	if (!qcfg)
		return -ENOMEM;

	qcfg->c.label = kstrdup(name, GFP_KERNEL);
	if (!qcfg->c.label) {
		ret = -ENOMEM;
		goto free_cfg;
	}
	qcfg->c.bConfigurationValue = num;
	qcfg->c.bmAttributes = USB_CONFIG_ATT_ONE;
	qcfg->c.MaxPower = CONFIG_USB_GADGET_VBUS_DRAW;

	ret = of_property_read_u32(gadget->cfg_node, "qcom,bmAttributes", &val);
	if (!ret)
		qcfg->c.bmAttributes = (u8)val;

	INIT_LIST_HEAD(&qcfg->func_list);
	INIT_LIST_HEAD(&qcfg->qti_funcs);

	ret = usb_add_config_only(&gadget->cdev, &qcfg->c);
	if (ret)
		goto free_label;

	ret = qti_usb_funcs_alloc(qcfg, name);
	if (ret)
		goto cfg_del;

	return ret;

cfg_del:
	list_del(&qcfg->c.list);
free_label:
	kfree(qcfg->c.label);
free_cfg:
	kfree(qcfg);
	return ret;

}

static int qti_usb_configs_make(struct qti_usb_gadget *gadget,
				  const char *cfgs)
{
	char buf[MAX_CFG_NAME_LEN];
	char *cfg_name, *next_cfg;
	int ret = 0;
	u8 num = 1;

	ret = scnprintf(buf, MAX_CFG_NAME_LEN, "%s", cfgs);
	if (ret >= MAX_CFG_NAME_LEN)
		return -ENAMETOOLONG;

	cfg_name = buf;
	while (cfg_name) {
		next_cfg = strnchr(cfg_name, MAX_CFG_NAME_LEN, '|');
		if (next_cfg)
			*next_cfg++ = '\0';

		ret = qti_usb_config_add(gadget, cfg_name, num);
		if (ret)
			break;

		cfg_name = next_cfg;
		num++;
	}

	return ret;
}

static int qti_gadget_register(struct qti_usb_gadget *qg)
{
	int ret;

	if (qg->enabled)
		return -EINVAL;

	ret = qti_usb_configs_make(qg, qg->composition_funcs);
	if (ret)
		return ret;

	qg->cdev.desc.bLength = USB_DT_DEVICE_SIZE;
	qg->cdev.desc.bDescriptorType = USB_DT_DEVICE;
	qg->cdev.desc.bcdDevice = cpu_to_le16(get_default_bcdDevice());

	qg->composite.gadget_driver = qti_gadget_driver;
	qg->composite.max_speed = qti_gadget_driver.max_speed;

	qg->composite.gadget_driver.function = kstrdup("qti-gadget",
							GFP_KERNEL);
	qg->composite.name = qg->composite.gadget_driver.function;

	if (!qg->composite.gadget_driver.function) {
		ret = -ENOMEM;
		goto free_configs;
	}

	ret = usb_gadget_probe_driver(&qg->composite.gadget_driver);
	if (ret)
		goto free_name;

	qg->enabled = true;

	return 0;

free_name:
	kfree(qg->composite.gadget_driver.function);
free_configs:
	qti_cleanup_configs_funcs(qg);

	return ret;
}

static void qti_gadget_unregister(struct qti_usb_gadget *qg)
{
	if (!qg->enabled)
		return;

	usb_gadget_unregister_driver(&qg->composite.gadget_driver);
	kfree(qg->composite.gadget_driver.function);
	qti_cleanup_configs_funcs(qg);

	qg->enabled = false;
}

static int qti_gadget_get_properties(struct qti_usb_gadget *gadget)
{
	struct device *dev = gadget->dev;
	struct device_node *child = NULL;
	int ret = 0, val = 0, pid = 0;
	char *start = NULL, *end = NULL;

	ret = device_property_read_u32(dev, "qcom,vid", &val);
	if (ret) {
		dev_err(dev, "USB gadget idVendor not specified\n");
		return ret;
	}
	gadget->cdev.desc.idVendor = (u16)val;

	ret = device_property_read_u32(dev, "qcom,class", &val);
	if (!ret)
		gadget->cdev.desc.bDeviceClass = (u8)val;

	ret = device_property_read_u32(dev, "qcom,subclass", &val);
	if (!ret)
		gadget->cdev.desc.bDeviceSubClass = (u8)val;

	ret = device_property_read_u32(dev, "qcom,protocol", &val);
	if (!ret)
		gadget->cdev.desc.bDeviceProtocol = (u8)val;

	/* Check if pid passed via cmdline which takes precedence */
	if (usb_pid_string[0] != 0) {
		ret = kstrtoint(usb_pid_string, 16, &val);
		if (ret)
			return ret;
	} else {
		ret = device_property_read_u32(dev, "qcom,default-pid", &val);
		if (ret) {
			dev_dbg(dev, "USB gadget default-pid not specified\n");
			return ret;
		}
	}

	pid = val;

	/* Extract serial # from kernel cmdline */
	start = strnstr(saved_command_line, "androidboot.serialno=",
			strlen(saved_command_line));
	if (start) {
		/* If serial # is at the end of the kernel cmdline
		 * it will be terminated by NULL character
		 */
		end = strchrnul(start, ' ');
		start += strlen("androidboot.serialno=");
		strlcpy(serialno_string, start, (end - start)+1);
	}

	/* Product string contains a space but in kernel
	 * cmdline it will be passed with a special character
	 * '!' instead of space to maintain the design of
	 * cmdline parameters
	 */
	strreplace(product_string, '!', ' ');

	/* Go through all the child nodes and find matching pid */
	while ((child = of_get_next_child(dev->of_node, child)) != NULL) {
		of_property_read_u32(child, "qcom,pid", &val);
		if (val == pid) {
			of_property_read_string(child, "qcom,composition",
					&gadget->composition_funcs);

			gadget->cfg_node = child;
			break;
		}
	}

	/* Check if couldn't find a matching composition */
	if (gadget->composition_funcs == NULL)
		return -EINVAL;

	/* bail out if ffs is specified and let userspace handle it */
	if (strnstr(gadget->composition_funcs, "ffs.",
			strlen(gadget->composition_funcs))) {
		dev_err(dev, "user should enable ffs\n");
		return -EINVAL;
	}

	gadget->cdev.desc.idProduct = (u16)pid;

	return 0;
}

static ssize_t enabled_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct qti_usb_gadget *qg = dev_get_drvdata(dev);

	return scnprintf(buf, PAGE_SIZE, "%c\n",
			qg->enabled ? 'Y' : 'N');
}

static ssize_t enabled_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct qti_usb_gadget *qg = dev_get_drvdata(dev);
	bool enable;
	int ret;

	ret = strtobool(buf, &enable);
	if (ret)
		return ret;

	if (enable)
		qti_gadget_register(qg);
	else
		qti_gadget_unregister(qg);

	return count;
}
static DEVICE_ATTR_RW(enabled);

static int qti_gadget_probe(struct platform_device *pdev)
{
	int ret;
	struct device *dev = &pdev->dev;
	struct qti_usb_gadget *gadget;

	gadget = devm_kzalloc(dev, sizeof(*gadget), GFP_KERNEL);
	if (!gadget)
		return -ENOMEM;

	platform_set_drvdata(pdev, gadget);
	gadget->dev = dev;
	INIT_LIST_HEAD(&gadget->cdev.configs);
	INIT_LIST_HEAD(&gadget->cdev.gstrings);

	ret = qti_gadget_get_properties(gadget);
	if (ret)
		return ret;

	ret = qti_gadget_register(gadget);
	if (ret)
		return ret;

	device_create_file(&pdev->dev, &dev_attr_enabled);

	return 0;
}

static int qti_gadget_remove(struct platform_device *pdev)
{
	struct qti_usb_gadget *qg = platform_get_drvdata(pdev);

	device_remove_file(&pdev->dev, &dev_attr_enabled);
	qti_gadget_unregister(qg);

	return 0;
}

static const struct of_device_id qti_gadget_dt_match[] = {
	{
		.compatible = "qcom,usb-gadget",
	},
	{ },
};
MODULE_DEVICE_TABLE(of, qti_gadget_dt_match);

static struct platform_driver qti_gadget_platform_driver = {
	.driver = {
		.name = "qti_usb_gadget",
		.of_match_table = qti_gadget_dt_match,
	},
	.probe = qti_gadget_probe,
	.remove = qti_gadget_remove,
};

static int __init gadget_qti_init(void)
{
	int ret;

	ret = platform_driver_register(&qti_gadget_platform_driver);
	if (ret) {
		pr_err("%s: Failed to register qti gadget platform driver\n",
			__func__);
	}

	return ret;
}
module_init(gadget_qti_init);

static void __exit gadget_qti_exit(void)
{
	platform_driver_unregister(&qti_gadget_platform_driver);
}
module_exit(gadget_qti_exit);
Loading