Chinaunix首页 | 论坛 | 博客
  • 博客访问: 737729
  • 博文数量: 192
  • 博客积分: 2653
  • 博客等级: 少校
  • 技术积分: 1623
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-31 18:25
个人简介

How tough life is, how strong you should be!

文章分类

全部博文(192)

文章存档

2015年(1)

2014年(2)

2013年(37)

2012年(152)

分类:

2012-09-10 10:11:40

原文地址:USB内核代码分析 作者:Tom_Chou

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]
这个元素指用字节指定了传输缓冲区的大小。对于中断和控制传输,缓冲区必须小于或等于相关端点数据包的最大值。数据包的大小可以在端点描述符中的wMaxPacketSize找到。

块传输比wMaxPacketSize大,wMaxPacketSize会自动的被分成更小的块。

.complete [optional input parameter]
以上的USB子系统处理请求是异步的。这个元素允许为调用者指定一处理函数,这个处理函数在请求完成后被调用。这个处理的目的是尽快完成调用者指定的请求,因为调用不受主机硬件中断处理器控制的。这意味着请求代码的其它约束都被中断控制器记录。

.context [optional input parameter]






.transfer_flags [optional input parameter and return value]
在处理传输请求过程时可以指定多个传输标志来改变处理动作。

.USB_DISABLE_SPD
这个标志禁止短小的数据包。如果一个upstream请求传输比端点最大数据包少的数据时就会发生短小的数据包。

USB_NO_FSBR
USB_ISO_ASAP
当安排同步请求时这个标志将告诉主机控制器尽快启动传输。如果没有指定USB_ISO_ASAP,就必须指定启动结构。如果同步传输不是必须与当前的结构数量同步,则推荐使用这个标志。当前的结构数量是11bit,它每毫秒就增加。

USB_ASYNC_UNLINK
取消URB时有可能是同步或异步的。用这个标志接通异步unlinking。

USB_TIMEOUT_KILLED
这个标志只在主机控制器标记URB由于超时被杀掉时设置。URB状态反映引起超时的错误。

USB_QUEUE_BULK
这个标志是用来允许块传输加入队列。一般情况下对于指定设备的一个端点只有一个块传输能加入队列。


.next [optional input parameter]
使用next指针可以在一条链中链接几个URBs。它允许你发送一个USB传输序列到USB内核。这个链必须被NULL指针终止或最后一个URB必须首先连接。这允许重复安排多个URBs传输连续的数据流。

.status [return value]
这个标志代表着一个正在进行或已经完成的请求的状态。在成功发送一个请求到USB内核后状态为-EINPROGRESS。成功完成一个请求后为0。

.actual_length [return value]
完成请求后这个元素记载着传输的字节数。保留的URB元素指定为传输类型。

.Bulk Transfers
不要指定额外的参数。

Control Transfers
setup_packet [mandatory input parameter]
第一步是启动数据包的downstream 传输。这个元素指向包含启动数据的缓冲区。这个缓冲区必须作为非页式的相邻物理内存块来分配(用void *kmalloc(size_t, GFP_KERNEL)来实现)。

Interrupt Transfers
start_frame [return value]
interval [mandatory input parameter]
这个元素为中断传输指定以毫秒为间隔。1<=interval<=255。指定0ms间隔会引起一个短暂的中断(不会自动重新安排)。你可以为中断端点在端点描述符中的bInterval找到中断间隔。

Isochronous Transfers
start_frame [input parameter or return value]
这个元素指定同步传输安排的第一个结构数。设置start_frame允许与端点进行同步传输。如果设置了USB_ISO_ASAP标志这个元素就会指示同步传输安排的第一个结构数。

number_of_packets [mandatory input parameter]
同步传输请求被当作一系列请求信号发送给USB内核。一个单独的请求信号传输一个数据包到一个指定的端点(管道)。这个元素设定传输的数据包数量。

error_count [return value]
在请求完成后(URB status is!=-EINPEOGRESS)这个元素记载了错误数据包的数量。Iso_frame_desc结构中有传输请求信号的详细情况。

timeout [input parameter] 在jiffies中超时可以指定自动的从主机控制器的时间表中删除一个URB。发生超时就会设置USB_TIMEOUT_KILLED。实际的传输状态代表着引起超时的USB状态。


iso_frame_desc [mandatory input parameter]
在每个同步URB的末尾这些结构为每个单独的请求数据包填充参数。

offset [mandatory input parameter]
为每个单独的请求指定偏移地址到transfer_buffer。

length [mandatory input parameter]
为每个单独的数据包指定数据缓冲区大小。如果length设置为0 USB结构会被跳过而且不会初始化传输。这个选项可被用来同步数据流。

actual_length [return value]
返回请求传输的实际字节数。

status [return value]
返回这个请求的状态。
URB函数
USB内核有四个处理URBs的函数。
purb_t usb_alloc_urb(int iso_packets);
要用到URB结构时调用此函数。ios_packets是用来在启动同步传输时在URB结构末尾指定iso_frame_desc的数量。如果成功则返回的指向URB结构的指针为0,否则返回NULL指针。


void usb_free_urb (purb_t purb);
用usb_alloc_urb释放内存时就调用此函数。

int usb_submit_urb(purb_t purb);
这个函数异步的发送一个传输请求给USB内核。purb指向以前的已初始化的URB结构。成功则返回0,否则返回对应的错误代码。这个函数返回的不会是块而且能够在无需等待的情况下为不同的端点安排多个URBs。在同步端点,还可以为一个端点安排多个URBs。这是USB协议错误处理和重试机制的限制。

int usb_unlink_urb(purb_t purb);
这个函数用来在请求完成之前取消操作。purb指向以前提交的URB结构。依赖于传输标志USB_ASYNC_UNLINK可以同步或异步的调用这个函数。同步的调用函数需要等待1ms而且不能在中断或完成的处理中调用。成功则返回0。异步调用则立刻返回。如果函数成功的启动了返回值为-EINPROGRESS。在函数完成以后当调用usb_unlink_urb时要用到完全的处理过程。URB的状态由-ENOENT(同步调用)或-ECONNRESET(异步调用)表示
usb_unlink_urb也用在终止一个中断传输URB。

URB宏
以下的宏用于初始化不同的传输类型:
FILL_CONTROL_URB(purb, dev, pipe, setup_packet, transfer_buffer, transfer_buffer_length, complete, context);
FILL_BULK_URB(purb, dev, pipe, transfer_buffer, transfer_buffer_length, complete, context);
FILL_INT_URB(purb, dev, pipe, transfer_buffer, transfer_buffer_length, complete, context, interval);
FILL_CONTROL_URB_TO();
FILL_BULK_URB_TO();

兼容性
USB内核包含多个较高级的函数,这些函数是为了与旧APIs兼容。有些函数仍然用来阻塞控制或块传输。
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, __u16 size, int timeout);
发送一个阻塞标准控制请求。在jiffies必须指定超时。成功则返回一正数,该正数代表传输的字节,否则返回错误代码。

int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, unsigned long *actual_length, int timeout);
发送一个阻塞标准块控制。actual_length是一个可选的指针,它指向的变量代表这个请求传输的实际字节数。jiffies中必须要指定超时。
阅读(1055) | 评论(0) | 转发(0) |
0

上一篇:USB驱动分析(三)

下一篇:两道c语言题目

给主人留下些什么吧!~~