Linux下USB Core的工作原理及设备驱动技术

时间:2022-12-05 16:09:15
Linux下USB Core的工作原理及设备驱动技术

Linux下USB Core的工作原理及设备驱动技术


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所示。

Linux下USB Core的工作原理及设备驱动技术

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子系统结束时的清理函数:
Linux下USB Core的工作原理及设备驱动技术

函数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文件里的定义如下:
Linux下USB Core的工作原理及设备驱动技术

该函数的形参对应的就是总线两条链表里的设备和驱动。当总线上有新设备和驱动时,这个函数就会被调用。
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设备驱动程序的开发。