本文将对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. linux usb gadget驱动详解(一)

    由于PC的推广,USB(通用串行总线)是我们最熟知的通信总线规范之一,其他的还有诸如以太网.PCIE总线和RS232串口等.这里我们主要讨论USB. USB是一个主从通信架构,但只能一主多从.其中us ...

  2. linux usb gadget驱动详解(二)

    在上篇<linux usb gadget驱动详解(一)>中,我们了解到gadget的测试方法,但在最后,我们留下一个问题,就是怎样使用新的方法进行usb gadget驱动测试. 我们发现l ...

  3. linux usb gadget驱动详解(四)

    现从msg_bind()函数(drivers/usb/gadget/legacy/mass_storage.c)开始讲起. U盘的gadget驱动比较复杂,因为它包含几部分,包括gadget驱动.U盘 ...

  4. linux usb gadget驱动详解(五)

    现从fsg_bind()讲起. //不失一般性,删掉错误处理和configfs相关代码 static int fsg_bind(struct usb_configuration *c, struct ...

  5. Linux usb设备驱动详解

    1.Linux usb设备驱动框架 USB是通用串行总线的总称,Linux内核几乎支持所有的usb设备,包括键盘,鼠标,打印机,modem,扫描仪.Linux的usb驱动分为主机驱动与gadget驱动 ...

  6. linux usb键盘驱动详解

    1.首先我们通过上节的代码中修改,来打印下键盘驱动的数据到底是怎样的 先来回忆下,我们之前写的鼠标驱动的id_table是这样: 所以我们要修改id_table,使这个驱动为键盘的驱动,如下图所示: ...

  7. linux usb ga驱动详解,Linux设备驱动之内存映射

    1. 内存映射 所谓的内存映射就是把物理内存映射到进程的地址空间之内,这些应用程序就可以直接使用输入输出的地址空间,从而提高读写的效率.Linux提供了mmap()函数,用来映射物理内存. 在驱动程序 ...

  8. Linux字符设备驱动详解七(“插件“设备树实现RGB灯驱动)

    文章目录 系列文章目录 前言 正文 Device Tree Overlays:"插件"设备树 传统设备树 "插件"设备树 使用前提 案例说明 设备树:foo.d ...

  9. Linux字符设备驱动详解四(使用自属的xbus驱动总线)

    文章目录 系列文章目录 前言 驱动目录 正文 驱动总线 总线管理 总线注册 设备注册 驱动注册 代码示例 总结 系列文章目录 Linux字符设备驱动详解 Linux字符设备驱动详解二(使用设备驱动模型 ...

最新文章

  1. nginx.pid failed (2: The system cannot find the file specified
  2. 安卓Activity界面切换添加动画特效
  3. C#中多线程和定时器是不是有冲突?
  4. 新增房源服务实现之AutoGenerator使用以及创建pojo对象
  5. 【Java语法】比较两个字符串是否相等
  6. 配置树莓派linux的内核和编译并将镜像拷贝至树莓派
  7. 基于图嵌入的兵棋联合作战态势实体知识表示学习方法
  8. CRITICAL_SECTION 学习
  9. 0x06 MySQL 单表查询
  10. pandas 学习(四)—— 数据处理(清洗)、缺失值的处理
  11. php索引数组转键数组,PHP-Codeigniter:如何从指定索引转换数组值?
  12. 苹果电脑取色器怎么用?Mac取色器——为你的设计提提速
  13. Splice Beatmaker for Mac(音乐节拍工具)
  14. 苹果Mac AI 智能图像降噪工具:Topaz DeNoise AI
  15. Qt之QSS(白色靓丽)
  16. 价值1000元的微信二维码活码管理系统网站源码分享
  17. C++游戏编程教程(一)
  18. mysql evict_SpringBoot+Mybatis+MySQL实现读写分离
  19. Flex布局-实现网上商城-个人中心页面
  20. git apply 打补丁

热门文章

  1. 宠辱不惊,闲看庭前花开花落
  2. 灵感专题-蓝色系创意网页设计欣赏
  3. Avue中实现多选删除功能
  4. 什么是码点与代码单元
  5. 岁月(胡军、于和伟)
  6. Stata分享:一个在线-Stata-教程网站
  7. 一览众山小:专栏总结和我们过往经验分享
  8. 百度站长平台使用教程:HTTPS认证
  9. Android 根据目标宽度,将bitmap等比缩放。
  10. 系统,网络,硬件,安全