本文将对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驱动详解(三),转载请注明来源!