Linux下的USB子系统
在Linux系统中有一个名为“The USB Core”子系统,它有特殊的API支持USB设备和控制器。它的作用是通过定义一组数据结构,宏和函数来抽象所有硬件或设备依赖的部分。
USB内核包含所有USB设备和控制器的最外层的驱动。这些函数可以被划分到上层或下层API。下图所示的就是针对于USB设备驱动和另外一个主机控制器的API。下层集成在USB设备驱动层,因为主机控制器驱动的开发已经完成。
USB Core API Layers
USB设备驱动程序结构
USB设备驱动在子系统中注册和注销。一个驱动程序必须注册2个入口点和它的名字。对于特殊的USB设备(它不适合在其它子系统中注册)一个驱动程序可以注册一对文件操作和一个次设备号。在这种情况下指定的次设备号和随后的15个数字被分配给驱动程序。这样就可以使一个驱动程序支持16个相似的USB设备。所有USB设备的主设备号是180。
数据结构:
所有USB相关函数或数据结构都遵循同样的命名规则而且都是以usb_开始。
name:模块名字
probe:prob函数的入口
disconnect:disconnect函数的入口
driver_list:子系统初始化时为{NULL,NULL}
fops:驱动程序文件操作的链表
minor:分配给这个设备的次设备号(其值是16的倍数)
serialize:
ioctl:
id_table:
结构入口
USB驱动程序结构添加两个入口到普通的设备驱动:
void *probe(struct usb_device *dev, unsigned int interface, const struct usb_device_id *id_table);在有新的设备接入到总线上时就会调用这个入口函数。然后,驱动程序要为新设备创建一个内部数据结构。
dev声明了特定的设备正文,它的内容指向所有USB描述符。interface声明了特定的接口号。如果一个USB驱动程序想把自己与一个具体设备和接口绑定在一起,它就必须返回一个指针。这个指针通常指向设备驱动程序的正文结构。
探查是通过检查vendor和产品的定义或分类以及子集的定义来实现的。如果它们匹配则接口号就与驱动程序支持的比较。因为设备的特性多不相同所以当探查完成后分类是基于有必要解析一些USB描述符。
Probe函数:
void disconnect(struct usb_device *dev, void *drv_context);当设备与这个函数断开时就调用这个函数。
dev声明了特定的设备正文而且driver_context返回一个指针指向当前注册的probe函数的driver_context。返回后从USB结构的disconnect函数释放了所有与设备有关的数据结构。所以,特别是usb设备不能再使用usb_device结构
disconnect函数:
结构函数
int usb_register(struct usb_driver *drv);
这个函数用来在子系统中注册一个新的USB设备。drv指向一个已完全初始化的usb_driver结构。成功返回0,否则返回一个错误值。
void usb_deregister(struct usb_driver *drv);
这个函数用来注销一个已在子系统中注册的USB设备。
void usb_driver_claim_interface(struct usb_driver *driver, struct usb_interface *iface, void *drv_context);
在探查过程中USB设备驱动程序在设备上声明多于一个接口时就使用这个函数。driver指向一个完全初始化的usb_driver结构。iface指向usb_config_descriptor中的usb_interface结构,usb_config_descriptor在usb_device(在probe函数中给出)结构中可访问。drv_context指向设备驱动程序的正文结构。
int usb_interface_claimed(struct usb_interface *iface);
如果另一设备驱动已声明特定的接口则这个函数是用来检查的。如果接口没有被其它的驱动程序声明就返回0。
void usb_driver_release_interface(struct usb_driver *driver, struct usb_interface *iface);
如果一个驱动程序要释放以前声明的接口就要调用这个函数。在disconnect函数中你不一定非要释放在probe函数中声明的接口。
const struct usb_device_id *usb_match_id( struct usb_device *dev, struct usb_interface *interface, const struct usb_device_id *id);
配置USB设备
API包括一系列的函数用来选择或查询描述符,配置和选择设备设定。所有的标准操作都通过控制设备传输来完成。
描述符数据结构
Linux USB子系统通过在子系统特定结构中扩展或嵌入标准USB描述符来描述层次结构。这个结构帮助存储指向选择配置和接口的指针。
这些结构的原理直到它们对于后来的API有必要调用时才详细解释。
struct usb_device{
...
struct usb_config_descriptor *actconfig;/* the active configuration */
...
struct usb_device_descriptor descriptor;/* Descriptor */
struct usb_config_descriptor *config; /* All of the configs */
}
usb_device结构是所有USB特定描述符的根。有时为了配置设备或适当地启动传输请求有必要在驱动程序中解析描述符。
.可以像这样来访问所有可用的配置描述符:
for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
struct usb_config_descriptor *cfg = &dev->config[i];
...
}
.像这样访问所有可用的特定配置的接口描述符:
for (j = 0; j < cfg->bNumInterfaces; j++) {
struct usb_interface *ifp = &cfg->interface[j];
...
}
用dev?actconfig指针开始解析激活的配置。
.访问所有特定接口的可选的设置:
for (k = 0; k < ifp->num_altsetting; k++) {
struct usb_interface_descriptor *as = &ifp->altsetting[k];
...
}
可选的设定可以通过*as = &ifp->altsetting[ifp->act_altsetting]来访问
.访问所有特定可选的设定端点描述符:
for(l = 0; l < as->bNumEndpoints; l++) {
struct usb_endpoint_descriptor *ep=&as->endpoint[k];
...
}
标准设备请求
有一组函数用来查询或设置特殊配置或改变设置。这些常用的函数启动标准设备请求(控制指定设备的传输):
.int usb_set_configuration(struct usb_device *dev, int configuration);
用这个函数激活特定配置。
0<=configurationdescriptor.bNumConfigurations。
设备与总线相连后0是缺省值。
.int usb_set_interface(struct usb_device *dev, int interface, int alternate);
这个函数激活指定接口的设置。
0<=interface 0<=alternate
.int usb_get_device_descriptor(struct usb_device *dev);
这个函数从指定的设备中重读全部的描述符树。当一个设备连接到总线上或一个USB描述符发生改变就自动调用这个函数。
.int usb_get_descriptor(struct usb_device *dev,unsigned char desctype,unsigned char descindex, void *buf, int size);
单个的USB描述符可以从设备中作为原始数据读入。这个函数可被用来解析扩展的或指定的描述符。
.int usb_get_string(struct usb_device *dev,unsigned short langid,unsigned char index, void *buf, int size);
如果一设备,配置或接口描述符涉及到一串索引值,则这个函数可被用来遍历描述符。依照规范USB strings被作为Unicode来编码。如果成功就返回0,否则返回错误代码。
.int usb_string(struct usb_device *dev, int index, char *buf, size_t size);
这个函数通过把Unicode串转换成ASCII串来简化usb_get_string。
.int usb_get_status(struct usb_device *dev, int type, int target, void *data);
.int usb_clear_halt(struct usb_device *dev, int pipe);
如果一个端点被停止,就调用这个函数来清除STALL。STALL表明一个函数不能发送或接收数据,或者是不支持控制管道请求。endpoint定义了一个管道。
.int usb_get_protocol(struct usb_device *dev, int ifnum);
.int usb_set_protocol(struct usb_device *dev, int protocol, int ifnum);
.int usb_get_report(struct usb_device *dev, unsigned char type, unsigned char id, int ifnum, void *buf, int size);
.int usb_set_idle(struct usb_device *dev, int ifnum, int duration, int report_id);
USB传输
用于传输的数据结构和宏
Linux USB子系统只用一个数据传输结构名为USB Request Block(URB)。这个结构包含了用于启动任何USB传输类型的所有参数。所有的传输请求都被异步的发送给USB core而且通过回调函数告知回调完成。
URB 结构:
标记了“>”的是输入参数,M表示强制性的而且O表示可选择的。标记“<”的是返回值。标记了“T”的是临时参数(输入、输出)。所有非普通元素都用三列标记了它们代表控制,中断和同步传输。“X”表示这个元素与传输类型相关。
dev [mandatory input parameter]
这个元素指向usb_device结构
pipe [mandatory input parameter]
管道元素被用来对端点号和特征进行编码。以下的几个宏用来创建一个适当的管道值:
pipe=usb_sndctrlpipe(dev,endpoint)
pipe=usb_rcvctrlpipe(dev,endpoint)
为downstream或upstream控制与一个给定的端点进行传输创建一个管道。dev指向usb_device结构。endpoint通常为0。
pipe=usb_sndbulkpipe(dev,endpoint)
pipe=usb_rcvbulkpipe(dev,endpoint)
为downstream或upstream控制与一个给定的端点进行块传输创建一个管道。
1<=endpoint<=15(依赖于活动的端点描述符)
pipe=usb_sndintpipe(dev,endpoint)
pipe=usb_rcvintpipe(dev,endpoint)
为downstream或upstream控制与一个给定的端点进行中断传输创建一个管道。
1<=endpoint<=15(依赖于活动的端点描述符)
.transfer_buffer [mandatory input parameter]
这个元素指向传输缓冲区,该缓冲区包含了与设备进行传输的数据。这个缓冲区必须作为一个非页式的临近物理内存块来分配(用void *kmalloc(size_t,GFP_KERNEL)来实现)。
.transfer_buffer_length [mandatory input parameter]
这个元素指用字节指定了传输缓冲区的大