基于嵌入式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文件里的定义如下:
[!--empirenews.page--]
该函数的形参对应的就是总线两条链表里的设备和驱动。当总线上有新设备和驱动时,这个函数就会被调用。
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设备驱动程序的开发。