查看: 862|回复: 1

[经验] 嵌入式技术:Linux驱动USB必须了解的四个描述符

[复制链接]
  • TA的每日心情
    开心
    2019-11-4 13:48
  • 签到天数: 14 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 2020-5-9 09:56:19 | 显示全部楼层 |阅读模式
    分享到:
    嵌入式Linux下的USB设备驱动技术
    Linux以其稳定、高效、易定制、硬件支持广泛、源代码开放等特点,已在嵌入式领域迅速崛起,被国际上许多大型的跨国企业用作嵌入式产品的系统平台。

    USB是Universal Serial Bus (通用串行总线)的缩写,是1995年由microsoft、Compaq、IBM等公司联合制定的一种新的PC串行通信协议。它是一种快速、灵活的总线接口。与其它通信接口相比较,USB接口的最大特点是易于使用,这也是USB的主要设计目标。USB的成功得益于在USB标准中除定义了通信的物理层和电器层标准外。还定义了一套相对完整的软件协议堆栈。这使得多数USB设备都很容易在各种平台上工作。作为一种高速总线接口,USB适用于多种设备(如数码相机、MP3播放器、高速数据采集设备等)。另外,USB接口还支持热插拔,而且所有的配置过程都由系统自动完成,无须用户干预。

    1 Linux下的USB设备驱动

    在Linux内核的不断升级过程中,驱动程序的结构相对稳定。由于USB设备也是外围设备的一种,因此,它的驱动程序结构与普通设备的驱动程序相同。Linux系统的设备分为字符设备(CharDevice)和块设备(BlockDevice)。字符设备支持面向块字符的I/O操作,它不通过系统的快速缓存,而只支持顺序存取。块设备则支持面向块的I/O操作,所有块设备的I/O操作都通过在内核地址空间的I/O缓冲区进行,可以支持几乎任意长度和任意位置上的I/O请求。块设备与字符设备还有一点不同,就是块设备必须能够随机存取(RandomAccess),字符设备则没有这个要求。典型的字符设备包括鼠标、键盘、串行口等,而块设备主要包括硬盘软盘设备、CD-Rom等。由于USB设备主要都是通过快速串行通讯来读写数据,因此一般都可作为字符设备来进行处理。

    2 Linux下的USB core

    2.1 Linux中USB core与USB的结构关系

    Linux操作系统中有一个叫做“USB core”的子系统,可提供支持USB设备驱动程序的API和USB主机控制器的驱动程序。同时提供有许多数据结构、宏定义和功能函数来对硬件或设备进行支持。在Linux下编写USB设备的驱动程序时,从严格意义上讲,就是使用这些USB core的子系统所定义的数据结构、宏和函数来编写数据的处理功能。在Linux下,core、host controller和driver三者之间的关系如图1所示。



    2.2 USB core的初始化

    USB core从USB子系统的初始化开始。USB子系统的初始化则在文件drivers/usb/core/usb.c里。其代码如下:

      subsys_initcall(usb_init);
      module_exit(usb_exit);

    代码中的subsys_initcall是一个宏,相当于module_init,只不过因为这部分代码是核心,开发者通常把它看作一个子系统,而不仅仅是一个模块。因为USB core模块代表的不是某一个设备,而是所有USB设备赖以生存的模块。因此,在Linux中,像这样把一个类别的设备驱动归结为一个子系统(比如PCI子系统、scsi子系统等)。基本上,drivers/目录下面第一层的每个目录都可算作一个子系统,因为它们代表了一类设备。一般地,usb_init是真正的初始化函数,而usb_exit()则是整个USB子系统结束时的清理函数:



    函数usb_init主要完成初始化和注册设备。

    2.3 USB里的设备模型

    Linux里一个很重要的概念是设备模型。对于驱动来说,设备的概念就是总线和与其相连的各种设备。在内核里,总线、设备、驱动也就是bus、device、driver是设备模型很重要的三个概念,它们都有自己专属的结构。在include/linux/devide.h里的定义为:

    struct bus_type {……};
    struct device {……);
    struct device_driver {……};

    每次出现一个设备都要向总线注册,每次出现一个驱动,也要向总线注册。系统初始化时,应扫描连接许多设备,并为每一个设备建立一个struct device的变量。每一次都应有一个驱动程序,并要准备一个struct device_driver结构的变量。还要把这些变量加入相应的链表(如把device插入devices链表,driver插入drivers链表)。这样,通过总线就能找到每一个设备和每一个驱动。然而,假如计算机里只有设备却没有对应的驱动,那么设备将无法工作。反过来,倘若只有驱动却没有设备,驱动也起不了任何作用。对于USB设备,它可以在计算机启动以后再插入或者拔出计算机。由于device可以在任何时刻出现,而driver也可以在任何时刻被加载,所以,每当一个struct device诞生时,它就会去BUS的drivers链表中寻找自己的另一半。如果找到了匹配的设备,就调用device_bind_driver,并绑定好。

    Linux设备模型中的总线落实在USB子系统里就是usb_bus_type,它在usb_init函数中可用retval=bus_register(&usb_bus_type)语句注册,而在driver.c文件里的定义如下:



    该函数的形参对应的就是总线两条链表里的设备和驱动。当总线上有新设备和驱动时,这个函数就会被调用。

    3 USB驱动程序的描述符

    一个设备可以有多个接口,一个接口可代表一个功能,因此,每个接口都对应着一个驱动。例如一个USB设备有两种功能,一个键盘,上面还带一个扬声器,这就是两个接口,就需要两个驱动程序,一个是键盘驱动程序,一个是音频流驱动程序。

    一个驱动程序是否支持一个设备,要通过读取设备的描述符来判断。那么,什么是USB的描述符呢?USB的描述符是一个带有预定义格式的数据结构,里面保存有USB设备的各种属性和相关信息,可以通过向设备请求获得它们的描述符内容来深刻了解和感知一个USB设备。主要有四种USB描述符,分别为:接口描述符、端点描述符、设备描述符和配置描述符。

    协议规定:一个USB设备必须支持这四大描述符,还有些描述符不是必须包含的,有些特殊设备用来描述设备的不同特性,但这四大描述符是一个都不能少的。USB设备里有一个eeprom,可用来存储设备本身信息,设备的描述符就存储在这里。

    上述四个描述符分别放在了include/linux/usb.h文件中的struct usb_host_inteRFace、structusb_host_endpoint、struct usb_device、struetusb_host_config里,而描述符结构体本身定义在include/linux/usb/ch9.h里.并分别用struct usb_interface_descriptor、struct usb_host_endpoint、structusb_device_descriptor和struct usb_config_descriptor来表示。描述符结构体的定义应完全按照USB协议对描述符的规定来定义。

    4 USB接口驱动

    4.1 接口结构

    平时编写的USB驱动通常指的是写USB接口的驱动,一个接口对应一个接口驱动程序,需要以一个struct usb_driver结构的对象为中心,并以设备的接口提供的功能为基础,来进行USB驱动程序的编写。struct usb_driver结构体一般定义在include/linux/usb.h文件里。具体如下:

    struct usb_driver{
    const char*name;
    int(*probe)  (struct usb_interface*intf,const
    struct usb_device_jd*id);
    void(*disconnect)  (struct usb_interface*intf);
    int(*ioctl)  (struct usb_interface*intf,unsigned
    int code,void*buf);
    int  (*suspend)  (struct usb_interface*intf,
    pm_message_t message);
    int(*resume)  (struct usb_interface*intf);
    void(*pre_reset)  (struct usb_interface*intf);
    void(*post_reset)(struct usb_interface*intf);
    const struct usb_device_id*id_table;
    struct usb_dynids dynids;
    struct usbdrv_wrap drvwrap;
    unsigned int no_dynamic_id:1;
    unsigned int supports_autosuspend:1;
    };

    Name为驱动程序的名字,对应于/sys/bus/usb/drivers/下面的子目录名称。它只是彼此区别的一个代号,这里的名字在所有的USB驱动中必须是唯一的。probe用来看看这个USB驱动是否愿意接受某个接口的函数。Disconnect函数将在接口失去联系或使用rmmod卸载驱动将它和接口强行分开时被调用。Ioctl函数则用在驱动通过usbfs和用户空间进行交流时使用。Suspend、esume分别在设备被挂起和唤醒时使用。pre_reset、post_reset分别在设备将要复位(reset)和已经复位后使用。id_table的变量可用来判断是否支持某个设备接口。Dynids是支持动态id的。实际上,即使驱动已经加载了,也可以添加新的id给它。drvwrap是给USB core区分设备驱动和接口驱动用的。no_dynamic_id可以用来禁止动态id。supports_autosuspend可对autosuspend提供支持,如果设置为0,则不再允许绑定到这个驱动的接口autosuspend。

    接口驱动

    当insmod或modprobe驱动的时候,经过一个曲折的过程,就会调用相应USB驱动里的xxx_init函数,进而去调用usb_register (),以将相应的USB驱动提交给设备模型,添加到USB总线的驱动链表里。当rmmod驱动时,同样,在经过一个曲折的过程之后,再调用相应驱动里的xxx_cleanup函数,进而调用usb_deregister ()将相应的USB驱动从USB总线的驱动链表里删除。

    5 结束语

    本文介绍了Linux下USB core的工作原理,同时介绍了驱动USB必须了解的四个描述符。此外,还介绍了Linux下usb接口驱动的工作原理。本文介绍的方法能适应于Linux下各种不同的USB设备驱动程序的开发。

    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-11-18 22:41 , Processed in 0.123237 second(s), 17 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.