首页 » 技术分享 » linux usb gadget驱动详解(三)

linux usb gadget驱动详解(三)

 

         本文将对linux4.4.19版本usb gadget源码进行简单分析。鉴于前文反复测试U盘设备驱动,现从linux-4.4.19/drivers/usb/gadget/legacy/mass_storage.c开始分析。

        目的是了解U盘设备驱动的工作原理,为啥它能让PC识别成“可移动磁盘”,以及它可以像市面上的U盘一样能读写文件。最后介绍内核gadget框架提供的api,助力实现自定义的USB设备驱动。

#mass_storage.c line:242
/****************************** Some noise ******************************/

static struct usb_composite_driver msg_driver = {
	.name		= "g_mass_storage",
	.dev		= &msg_device_desc,
	.max_speed	= USB_SPEED_SUPER,
	.needs_serial	= 1,
	.strings	= dev_strings,
	.bind		= msg_bind,
	.unbind		= msg_unbind,
};

MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_AUTHOR("Michal Nazarewicz");
MODULE_LICENSE("GPL");

static int __init msg_init(void)
{
	return usb_composite_probe(&msg_driver);
}
module_init(msg_init);

static void msg_cleanup(void)
{
	if (test_and_clear_bit(0, &msg_registered))
		usb_composite_unregister(&msg_driver);
}
module_exit(msg_cleanup);

从上面代码的module_init(msg_init);我们知道,驱动入口在static int __init msg_init(void);其中__init修饰表明这个函数仅在初始化期间使用,在模块被装载之后,它占用的资源就会释放掉。msg_init函数只是糖衣炮弹,真正干活的是usb_composite_probe,这个函数有大来头,在composite.c中实现:

#composite.c line:2198
int usb_composite_probe(struct usb_composite_driver *driver)
{
	struct usb_gadget_driver *gadget_driver;

	if (!driver || !driver->dev || !driver->bind)
		return -EINVAL;

	if (!driver->name)
		driver->name = "composite";

	driver->gadget_driver = composite_driver_template;
	gadget_driver = &driver->gadget_driver;

	gadget_driver->function =  (char *) driver->name;
	gadget_driver->driver.name = driver->name;
	gadget_driver->max_speed = driver->max_speed;

	return usb_gadget_probe_driver(gadget_driver);
}
EXPORT_SYMBOL_GPL(usb_composite_probe);

        我们先看回来msg_init()函数,它用到了struct usb_composite_driver结构体对象的一个实例——msg_driver:

struct usb_composite_driver {
	const char				*name;
	const struct usb_device_descriptor	*dev;
	struct usb_gadget_strings		**strings;
	enum usb_device_speed			max_speed;
	unsigned		needs_serial:1;

	int			(*bind)(struct usb_composite_dev *cdev);
	int			(*unbind)(struct usb_composite_dev *);

	void			(*disconnect)(struct usb_composite_dev *);

	/* global suspend hooks */
	void			(*suspend)(struct usb_composite_dev *);
	void			(*resume)(struct usb_composite_dev *);
	struct usb_gadget_driver		gadget_driver;
};

static struct usb_composite_driver msg_driver = {
	.name		= "g_mass_storage",
	.dev		= &msg_device_desc,
	.max_speed	= USB_SPEED_SUPER,
	.needs_serial	= 1,
	.strings	= dev_strings,
	.bind		= msg_bind,
	.unbind		= msg_unbind,
};

这里我们定义了U盘驱动的“名称name”、“设备描述符dev”、“设备的速度”、“字符串描述符”、以及注册bind和unbind回调函数,这些参数和回调函数,最终会在libcomposite.ko驱动(composite.c)中回调。

其中每一个USB设备均有一个设备描述符,设备描述符的字段及其意义参考USB2.0规范的第九章(linux内核定义了ch9.h头文件专门来描述规范里描述的结构体和宏定义):

static struct usb_device_descriptor msg_device_desc = {
	.bLength =		sizeof msg_device_desc,
	.bDescriptorType =	USB_DT_DEVICE,

	.bcdUSB =		cpu_to_le16(0x0200),
	.bDeviceClass =		USB_CLASS_PER_INTERFACE,

	/* Vendor and product id can be overridden by module parameters.  */
	.idVendor =		cpu_to_le16(FSG_VENDOR_ID),
	.idProduct =		cpu_to_le16(FSG_PRODUCT_ID),
	.bNumConfigurations =	1,
};

上述的bNumConfigurations字段值为1,代表该U盘设备驱动只有一个配置(描述符)。

现在回到刚刚的usb_composite_probe()函数,我们先来分析这个函数内部究竟做了什么内容。

我们发现usb_composite_probe主要是填充好struct usb_composite_driver结构体里面的成员gadget_driver:

      struct usb_gadget_driver        gadget_driver;

值得注意的是,它在这里被赋值为composite_driver_template实例了,这个函数最后把重要的工作交给了return usb_gadget_probe_driver(gadget_driver);

#linux-4.4.19/drivers/usb/gadget/udc/udc-core.c
int usb_gadget_probe_driver(struct usb_gadget_driver *driver)
{
	struct usb_udc		*udc = NULL;
	int			ret;

	if (!driver || !driver->bind || !driver->setup)
		return -EINVAL;

	mutex_lock(&udc_lock);
	list_for_each_entry(udc, &udc_list, list) {
		/* For now we take the first one */
		if (!udc->driver)
			goto found;
	}

	pr_debug("couldn't find an available UDC\n");
	mutex_unlock(&udc_lock);
	return -ENODEV;
found:
	ret = udc_bind_to_driver(udc, driver);
	mutex_unlock(&udc_lock);
	return ret;
}
EXPORT_SYMBOL_GPL(usb_gadget_probe_driver);

        从上面usb_gadget_probe_driver()函数的代码片段可知,它在遍历udc_list链表,找到一个udc(usb设备控制器驱动描述结构体)没有对应驱动的实例,然后通过udc_bind_to_driver()将udc驱动与上述的composite_driver_template模板实例绑定在一起,即能通过udc找回composite_driver_template实例,也能通过composite_driver_template实例找到绑定它的udc驱动。内核很喜欢使用container_of()宏找来找去,container_of()作用是根据某对象成员的指针返回该对象的指针,有点父类找派生类的意思,作为内核面向对象编程的工具,container_of宏用得很频繁!!既然有udc队列的遍历取出,必然有udc队列的添加,每注册一个udc驱动(usb_add_gadget_udc())就会把udc结构体添加到udc队列的尾部。

        我们再来看udc_bind_to_driver做了什么:

#linux-4.4.19/drivers/usb/gadget/udc/udc-core.c
/* udc_lock must be held */
static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver)
{
	int ret;

	dev_dbg(&udc->dev, "registering UDC driver [%s]\n",
			driver->function);

	udc->driver = driver;
	udc->dev.driver = &driver->driver;
	udc->gadget->dev.driver = &driver->driver;

	ret = driver->bind(udc->gadget, driver);
	if (ret)
		goto err1;

	/* If OTG, the otg core starts the UDC when needed */
	mutex_unlock(&udc_lock);
	udc->is_otg = !usb_otg_register_gadget(udc->gadget, &otg_gadget_intf);
	mutex_lock(&udc_lock);
	if (!udc->is_otg) {
		ret = usb_gadget_udc_start(udc);
		if (ret) {
			driver->unbind(udc->gadget);
			goto err1;
		}
		usb_udc_connect_control(udc);
	}

	kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
	return 0;
err1:
	if (ret != -EISNAM)
		dev_err(&udc->dev, "failed to start %s: %d\n",
			udc->driver->function, ret);
	udc->driver = NULL;
	udc->dev.driver = NULL;
	udc->gadget->dev.driver = NULL;
	return ret;
}

        上述代码片段说明udc与driver绑定其实就是一个“它们的结构体成员互相赋值”的过程:

    udc->driver = driver;
    udc->dev.driver = &driver->driver;
    udc->gadget->dev.driver = &driver->driver;

这样我包含你你包含我。最后回调了driver的bind函数,这个bind回调函数在composite_driver_template实例中赋值。最后,调用usb_otg_register_gadget()将gadget注册到otg core去,然后开启udc功能(usb_gadget_udc_start(udc))。对应BBB板用到的TI芯片am3358来说otg 用到了Mentor Graphics公司的musb ip核。usb_gadget_udc_start(udc)事实上是回调了drivers/usb/musb/musb_gadget.c(对应musb_hdrc.ko驱动)的musb_gadget_start()函数进行操控otg硬件寄存器。udc驱动我们这里不作讨论!

当然了,如果USB控制器不支持otg,kernel也没有加入CONFIG_USB_OTG的话,usb_otg_register_gadget()其实是空函数,返回不支持(return -ENOTSUPP;)。

下面看下composite_bind()

static int composite_bind(struct usb_gadget *gadget,
		struct usb_gadget_driver *gdriver)
{
	struct usb_composite_dev	*cdev;
	struct usb_composite_driver	*composite = to_cdriver(gdriver);
	int				status = -ENOMEM;

	cdev = kzalloc(sizeof *cdev, GFP_KERNEL);
	if (!cdev)
		return status;

	spin_lock_init(&cdev->lock);
	cdev->gadget = gadget;
	set_gadget_data(gadget, cdev);
	INIT_LIST_HEAD(&cdev->configs);
	INIT_LIST_HEAD(&cdev->gstrings);

	status = composite_dev_prepare(composite, cdev);
	if (status)
		goto fail;

	/* composite gadget needs to assign strings for whole device (like
	 * serial number), register function drivers, potentially update
	 * power state and consumption, etc
	 */
	status = composite->bind(cdev);
	if (status < 0)
		goto fail;

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

	update_unchanged_dev_desc(&cdev->desc, composite->dev);

	/* has userspace failed to provide a serial number? */
	if (composite->needs_serial && !cdev->desc.iSerialNumber)
		WARNING(cdev, "userspace failed to provide iSerialNumber\n");

	INFO(cdev, "%s ready\n", composite->name);
	return 0;

fail:
	__composite_unbind(gadget, false);
	return status;
}

其中,composite_dev_prepare()主要是初始化ep0端,申请了ep0的请求队列(struct usb_request *req)、创建sysfs文件系统的属性文件、最后填充struct usb_request        *req的complete完成函数。我们知道ep0是设备控制传输用到的端点,尤其在设备枚举、属性配置上起关键作用,所以我们要首先初始化。

int composite_dev_prepare(struct usb_composite_driver *composite,
		struct usb_composite_dev *cdev)
{
	struct usb_gadget *gadget = cdev->gadget;
	int ret = -ENOMEM;

	/* preallocate control response and buffer */
	cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL);
	if (!cdev->req)
		return -ENOMEM;

	cdev->req->buf = kmalloc(USB_COMP_EP0_BUFSIZ, GFP_KERNEL);
	if (!cdev->req->buf)
		goto fail;

	ret = device_create_file(&gadget->dev, &dev_attr_suspended);
	if (ret)
		goto fail_dev;

	cdev->req->complete = composite_setup_complete;
	cdev->req->context = cdev;
	gadget->ep0->driver_data = cdev;

	cdev->driver = composite;

	/*
	 * As per USB compliance update, a device that is actively drawing
	 * more than 100mA from USB must report itself as bus-powered in
	 * the GetStatus(DEVICE) call.
	 */
	if (CONFIG_USB_GADGET_VBUS_DRAW <= USB_SELF_POWER_VBUS_MAX_DRAW)
		usb_gadget_set_selfpowered(gadget);

	/* interface and string IDs start at zero via kzalloc.
	 * we force endpoints to start unassigned; few controller
	 * drivers will zero ep->driver_data.
	 */
	usb_ep_autoconfig_reset(gadget);
	return 0;
fail_dev:
	kfree(cdev->req->buf);
fail:
	usb_ep_free_request(gadget->ep0, cdev->req);
	cdev->req = NULL;
	return ret;
}

紧跟着,status = composite->bind(cdev);实际上是回调mass_storage.c的int msg_bind(struct usb_composite_dev *cdev),而cdev在composite_bind()函数开头通过kzalloc创建,并把gadget驱动等填充好struct usb_composite_dev结构体对象。msg_bind(这个函数下一节再讲,这个函数比较复杂 。而update_unchanged_dev_desc()主要用于填充新的设备描述符信息,因为调用了msg_bind()后信息可能会有改变。

这就完成了U盘设备驱动的初始化(注册)了,但U盘为什么能工作起来,这里貌似没有体现出来,下篇文章将从msg_bind()说起。

 

        说个题外话,除了可以使用int usb_gadget_probe_driver(struct usb_gadget_driver *driver)来注册外,还可以使用int usb_udc_attach_driver(const char *name, struct usb_gadget_driver *driver);该函数有个额外参数name,可以指定用哪个usb口来初始化gadget驱动,譬如am3358含有两个usb控制器USB0和USB1,我们可以使用ret = usb_udc_attach_driver("musb-hdrc.0.auto", gadget_driver);来指定将gadget驱动挂到USB0上,这样该U盘 gadget设备就可以固定在USB0这个usb口上模拟出一个u盘了,USB1口则可以作它用。"musb-hdrc.0.auto"这个名字似曾相识吧?对,在上篇文章中使用configfs来启动U盘时,通过echo "musb-hdrc.1.auto" > UDC,底层就是调用了这个usb_udc_attach_driver()函数,来指定使用USB0。

        其中,“ ls /sys/class/udc/ 可以列举出当前系统注册了多少个UDC(usb设备控制器)。譬如,在am3358的kernel里那两个usb的UDC实例分别被命名为musb-hdrc.0.auto和musb-hdrc.1.auto。其他芯片的有自己的命名,一样可以通过/sys/class/udc/查看。

 

转载自原文链接, 如需删除请联系管理员。

原文链接:linux usb gadget驱动详解(三),转载请注明来源!

1