Linux驱动框架——USB驱动
通用串行总线USB(Universal serial bus)通常用于外围设备与主机之间的连接,USB使用树型结构,主机作为根,集线器作为节点,外围设备作为树叶。
Linux当前支持几乎所有USB类设备(标准类型的设备,如键盘,鼠标,调制解调器,打印机和扬声器),以及越来越多的特定于供应商的设备(如USB到串行转换器,数码相机,以太网设备和MP3播放器等)。
也有一部分USB设备是Linux不支持的,这些设备一般都是由供应商单独开发协议,大部分会开放协议给驱动人员开发使用。
由于不同的通信协议都需要自定义创建新的驱动程序,因此Linux开发者开发了一套USB驱动通用框架,它是源码中的drivers/usb/usb-skeleton.c。
USB设备很复杂(http://www.usb.org)。幸运的是Linux内核提供了一个叫做USB core的驱动子系统处理大多数的复杂情况。如下图,USB Core向上提供给驱动接口,让驱动可以简单的对USB主机控制器进行控制操作,从而对USB硬件设备进行控制操作。
USB设备基础知识
下面介绍Linux中USB设备的组织结构。
端点Endpoint
USB的通过端点来进行通信,USB的端点类似于单向管道,它的通信是单工的。USB的端点一共有以下四种类型:
控制传输
控制传输主要用于向设备发送配置信息、获取设备信息、发送命令道设备,或者获取设备的状态报告。控制传输一般发送的数据量较小,当USB设备插入时,USB核心使用端点0对设备进行配置,另外,端口0与其他端点不一样,端点0可以双向传输。
中断传输
中断传输就是中断端点以一个固定的速度来传输较少的数据,USB键盘和鼠标就是使用这个传输方式。这里说的中断和硬件上下文中的中断不一样,它不是设备主动发送一个中断请求,而是主机控制器在保证不大于某个时间间隔内安排一次传输。中断传输对时间要求比较严格,所以可以用中断传输来不断地检测某个设备,当条件满足后再使用批量传输传输大量的数据。
批量传输
批量传输通常用在数据量大、对数据实时性要求不高的场合,例如USB打印机、扫描仪、大容量存储设备、U盘等。
等时传输
等时传输同样可以传输大批量数据,但是对数据是否到达没有保证,它对实时性的要求很高,例如音频、视频等设备。
端点的数据结构
内核中,使用usb_host_endpoint来描述USB端点,这个结构体中包含了一个保存端点信息的结构体usb_endpoint_descriptor,驱动一般会关心其中保存的端点地址bEndpointAddress、端点类型bmAttributes、端点处理的最大字节数wMaxPacketSize、以及如果是中断类型端点的中断时间bInterval。
接口Interface
由上面的USB设备结构图我们可以看到,USB设备的端点包含在接口Interface中。USB接口用于每个USB接口仅控制着一种类型的USB连接,例如鼠标、键盘或者音频流。大多数的USB设备存在多个接口,因为每一个接口代表一个基础功能,因此每一个接口都需要一个驱动程序驱动。
USB接口在内核中使用usb_interface结构体描述,这个结构体一般由USB core传递给USB驱动,由USB驱动进行控制。比较重要的成员变量有:struct usb_host_interface *altsetting控制字段、unsigned num_altsetting控制数量、struct usb_host_interface *cur_altsetting指向控制选项的指针、int minor次设备号。
配置Configurations
由上面的USB设备结构图我们可以看到,USB设备的接口包含在配置Configurations中。 一个USB设备可以有多种配置,但是每一个时间点只能启用生效一个配置。
Linux内核中使用usb_host_config结构体描述USB设备的配置。一般来说驱动无需对配置进行读写操作。
USB驱动框架源码分析
其实稍微阅读一下源码就会发现,USB驱动框架和之前研讨过的帧缓冲驱动框架非常相似。都是一个文件用来组装加载驱动框架的内核模块usb-skeleton.c(帧缓冲是fbmem.c),一个文件用来描述驱动框架相关的数据结构以及驱动注册方法等usb.h(帧缓冲是fb.h)。
因此,先初步断定,Linux中的驱动框架都是一个套路,所以按照以往的经验,先从usb-skeleton.c这个驱动框架的模块入手分析。既然是Linux模块,当然要从入口处先分析了!下面审计模块初始函数usb_skel_init。
static int __init usb_skel_init(void)
{
int result;
/* register this driver with the USB subsystem */
//
result = usb_register(&skel_driver);
if (result)
err("usb_register failed. Error number %d", result);
return result;
}
和猜想没错, 驱动模块加载必然是先注册驱动结构体,这个注册的驱动结构体usb_driver在模块中被初始化写好了name、probe探测函数、disconnect断开函数、id_table表(用来告诉内核该驱动支持的设备)。
static struct usb_driver skel_driver =