USB驱动总结

2012.8.21 星期二


USB驱动总结 


这几天看了下2.6内核的usb驱动,现将关键知识点罗列如下,主要参考书目为ldd3.


usb设备是一个非常复杂的东西,而为了规避这些复杂的协议内容,linux内核提供了一个称为USB核心的子系统来处理大部分的复杂性。
USB设备的构成包括配置、接口、端点。配置包含接口,接口包含端点。
USB通信最基本的形式是通过端点(endpoint),且端点只能是一个方向传输(单向通道),从主机到设备为OUT端点,从设备到主机为IN端点。


端点


USB端点分四种不同的类型
控制端点,用于配置设备,每个USB设备都有一个名为“端点0”的控制端点,10年的时候使用单片机配合USB传输芯片1581进行批量传送的时候,使用过控制端点配置设备。
中断端点,这种端点是USB键盘和鼠标所使用的主要传输方式。每当USB宿主要求设备传输数据时,中断端点就以一个固定的速率来传送少量的数据。下面要说明的USB鼠标驱动使用的就是这种类型的端点。
批量端点, 以前用过,可以参考之前编写的USB批量传输代码(E:\usb相关\USB资料) 一般用于打印机存储设备和网络设备上。
等时端点,用于音频和视频设备。


内核使用struct usb_host_endpoint-->struct usb_endpoint_descriptor包含USB端点信息。其中的字段:
bEndpointAddress 特定端点USB地址(同时包含端点方向,结合位掩码USB_DIR_OUT,USB_DIR_IN使用)。
bmAttributes 端点类型,结合USB_ENDPOINT_XFER_ISOC(等时)、USB_ENDPOINT_XERF_BULK(批量)、USB_ENDPOINT_XERF_INT(中断)使用。
wMaxPacketSize 端点一次可以处理的最大字节数。驱动程序可以发送数量大于此值的数据到端点,但是在实际传输到设备的时候,数据将被分割成wMaxPacketSize大小的块。
bInterval 如果端点是中断类型,该值是端点的间隔设置,也就是端点中断请求间隔时间,单位是ms。


接口


内核使用struct usb_interface结构体来描述USB接口,每个usb驱动程序控制一个接口


配置


linux使用struct usb_host_config来描述usb配置。使用struct usb_device来描述整个usb设备。usb设备驱动程序通常需要把一个struct usb_interface结构体数据转化成为一个struct usb_device结构体,使用interface_to_usbdev函数。


USB urb
linux内核 USB 代码通过urb和所有USB设备通信,urb由struct urb描述,urb被用来以一种异步的方式往/从特定usb设备上的特定usb端点发送/接收数据。
0一个典型的urb生命周期为(重要):
 1、由usb设备驱动程序创建。
 2、分配一个特定的usb设备的特定端点。
 3、由usb设备驱动程序递交给usb核心。
 4、由usb核心递交到特定设备的特定usb主控制器驱动程序。
 5、由usb主控制器驱动程序处理,它对设备进行usb传送。
 6、当urb结束,usb主控制器驱动程序通知usb设备驱动程序。
struct urb需要关心的字段
struct usb_device *dev   urb要发送给usb核心的目标(所以在配置的说明里需要将struct usb_interface结构体数据转化成为一个struct usb_device结构体),该变量在发送之前必须被驱动程序初始化。
unsigned int pipe  urb所要发送的usb_device的端点信息,发送前也必须要进行初始化。可以通过一些函数指定usb设备的指定端点号端点类型(函数详见ldd3 page 333)
unsigned int tansfer_flags 用到了再说明。
void *transfer_buffer 发送/接收数据缓冲区指针,必须使用kmalloc创建
void *transfer_buffer_length        transfer_buffer指向的缓冲区大小。
unsigned char *setup_packet  指向控制urb的设置数据包指针,它在传输缓冲区中的数据之前被传送,该变量只对控制urb有效。
void *context 指向一个被usb驱动程序设置的数据块。它可以在结束处理例程中当urb被返回到驱动程序时使用,用于装载回调函数。
int actual_length 当urb结束后,该变量被设置为实际发送/接收数据的实际长度。
int status 当urb结束之后,urb的当前状态。
int interval urb被轮询的时间间隔。只针对中断或者等时urb
其他具体看ldd3 page337




为了思路清晰,现在以usb鼠标驱动为例对usb设备驱动进行说明(为了紧跟时代步伐,代码采用linux3.0.1的内核代码usbmouse.c)。
USB 总线引出两个重要的链表!
一个 USB 总线引出两个重要的链表,一个为 USB 设备链表,一个为 USB 驱动链表。设备链表包含各种系统中的 USB 设备以及这些设备的所有接口,驱动链表包含 USB 设备驱动程序(usb device driver)和 USB 驱动程序(usb driver)。


USB 设备驱动程序(usb device driver)和 USB 驱动程序(usb driver)的区别是什么?
USB 设备驱动程序包含 USB 设备的一些通用特性,将与所有 USB 设备相匹配。在 USB core 定义了:struct usb_device_driver usb_generic_driver。usb_generic_driver 是 USB 子系统中唯一的一个设备驱动程序对象。而 USB 驱动程序则是与接口相匹配,接口是一个完成特定功能的端点的集合。


设备是如何添加到设备链表上去的?
在设备插入 USB 控制器之后,USB core 即会将设备在系统中注册,添加到 USB 设备链表上去。


USB 设备驱动程序(usb device driver)是如何添加到驱动链表上去的?
在系统启动注册 USB core 时,USB 设备驱动程序即将被注册,也就添加到驱动链表上去了。


接口是如何添加到设备链表上去的?
在 USB 设备驱动程序和 USB 设备的匹配之后,USB core 会对设备进行配置,分析设备的结构之后会将设备所有接口都添加到设备链表上去。比如鼠标设备中有一个接口,USB core 对鼠标设备配置后,会将这个接口添加到设备链表上去。


USB 驱动程序(usb driver)是如何添加到驱动链表上去的?
在每个 USB 驱动程序的被注册时,USB 驱动程序即会添加到驱动链表上去。比如鼠标驱动程序,usb_mouse_init 函数将通过 usb_register(&usb_mouse_driver) 将鼠标驱动程序注册到 USB core 中,然后就添加到驱动链表中去了。其中 usb_mouse_driver 是描述鼠标驱动程序的结构体。


已配置状态(configured status)之后话
当鼠标的设备、接口都添加到设备链表,并且鼠标驱动程序也添加到驱动链表上去了,系统就进入一种叫做已配置(configured)的状态。要达到已配置状态,将经历复杂的过程,USB core 为 USB 设备奉献着无怨无悔。在这个过程中,系统将会建立起该设备的的设备、配置、接口、设置、端点的描述信息,它们分别被 usb_device、usb_configuration、usb_interface、usb_host_interface、usb_host_endpoint 结构体描述。
设备达到已配置状态后,首先当然就要进行 USB 驱动程序和相应接口的配对,对于鼠标设备来说则是鼠标驱动程序和鼠标中的接口的配对。USB core 会调用 usb_device_match 函数,通过比较设备中的接口信息和 USB 驱动程序中的 id_table,来初步决定该 USB 驱动程序是不是跟相应接口相匹配。通过这一道关卡后,USB core 会认为这个设备应该由这个驱动程序负责。
然而,仅仅这一步是不够的,接着,将会调用 USB 驱动程序中的 probe 函数对相应接口进行进一步检查。如果该驱动程序确实适合设备接口,对设备做一些初始化工作,分配 urb 准备数据传输。
当鼠标设备在用户空间打开时,将提交 probe 函数构建的 urb 请求块,urb 将开始为传送数据而忙碌了。urb 请求块就像一个装东西的“袋子”,USB 驱动程序把“空袋子”提交给 USB core,然后再交给主控制器,主控制器把数据放入这个“袋子”后再将装满数据的“袋子”通过 USB core 交还给 USB 驱动程序,这样一次数据传输就完成了。


为了阅读方便,以程序运行流程进行说明。
module_init(usb_mouse_init);
static int __init usb_mouse_init(void)
{
int retval = usb_register(&usb_mouse_driver); //将usb驱动注册进usb core中,添加到驱动链表中。
if (retval == 0)
printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
DRIVER_DESC "\n");
return retval;
}
static struct usb_device_id usb_mouse_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};
/*  
  usb_device_id 结构体用于表示该驱动程序所支持的设备,USB_INTERFACE_INFO 可以用来匹配特定类型的接口,  这个宏的参数意思为 (类别, 子类别, 协议)。  USB_INTERFACE_CLASS_HID 表示是一种 HID (Human Interface Device),即人机交互设备类别;  USB_INTERFACE_SUBCLASS_BOOT 是子类别,表示是一种 boot 阶段使用的 HID;   USB_INTERFACE_PROTOCOL_MOUSE 表示是鼠标设备,遵循鼠标的协议。  
 */  


MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);


static struct usb_driver usb_mouse_driver = {
.name = "usbmouse", //指向驱动程序名字的指针,如果该驱动程序运行在内核中,可以在sysfs的/sys/bus/usb/drivers/下面找到它。
.probe = usb_mouse_probe,//探测函数指针,当usb核心认为认为它有一个struct usb_interface可以由该驱动程序处理时,该函数被调用。
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,//该表包含了一列该驱动程序可以支持的所有不同类型的USB设备。如果不设置该变量,usb驱动程序中的探测回调函数不会被调用。USB core 会调用 usb_device_match 函数,通过比较设备中的接口信息和 USB 驱动程序中的 id_table,来初步决定该 USB 驱动程序是不是跟相应接口相匹配
};






usb core通过比较设备中的接口信息和 USB 驱动程序中的 id_table,来初步决定该 USB 驱动程序是不是跟相应接口相匹配,如果确认匹配,则调用如下函数。
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);//这个应该是由于历史以及习惯原因,传入的usb_interface结构体需要转换成usb_device结构体,以便后面使用。
struct usb_host_interface *interface;  //usb_host_interface 是用于描述接口设置的结构体,内嵌在接口结构体 usb_interface 中。
struct usb_endpoint_descriptor *endpoint; //usb_endpoint_descriptor 是端点描述符结构体,内嵌在端点结构体 usb_host_endpoint 中,而端点结构体内嵌在接口设置结构体中。  
struct usb_mouse *mouse; 
struct input_dev *input_dev; //输入结构体,用于输入子系统
int pipe, maxp; 
int error = -ENOMEM;


interface = intf->cur_altsetting;//cur_altsetting是指向altsetting数组内部的指针,表示该接口的当前活动设置;altsetting是一个usb_host_interface 接口结构体数组,包含了所有可能用于该接口的可选设置,每个usb_host_interface结构体包含一套由struct usb_host_endpoint结构体定义的端点配置。这句的意思就是取出接口的端点配置信息。


if (interface->desc.bNumEndpoints != 1) //鼠标仅有一个端点,不满足此要求的设备报错
return -ENODEV;

endpoint = &interface->endpoint[0].desc;
if (!usb_endpoint_is_int_in(endpoint))鼠标仅有一个 interrupt 类型的 in 端点,不满足此要求的设备报错
return -ENODEV;

    //返回对应端点能够传输的最大的数据包,鼠标的返回的最大数据包为4个字节,数据包具体内容在 urb回调函数中有详细说明。  
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);   //将pipe设置为中断IN端点
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));


mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);//为鼠标设备结构体分配内核空间
input_dev = input_allocate_device();//创建输入设备结构体
if (!mouse || !input_dev)
goto fail1;
 /*  
     申请内存空间用于数据传输,data 为指向该空间的地址,data_dma 则是这块内存空间的 dma 映射,   即这块内存空间对应的 dma 地址。在使用 dma 传输的情况下,则使用 data_dma 指向的 dma 区域,  否则使用 data 指向的普通内存区域进行传输。  GFP_ATOMIC 表示不等待,GFP_KERNEL 是普通的优先级,可以睡眠等待,由于鼠标使用中断传输方式,  不允许睡眠状态,data 又是周期性获取鼠标事件的存储区,因此使用 GFP_ATOMIC 优先级,如果不能 分配到内存则立即返回 0。  
     */  
mouse->data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &mouse->data_dma);
if (!mouse->data)
goto fail1;
 /*  
     * 为 urb 结构体申请内存空间,第一个参数表示等时传输时需要传送包的数量,其它传输方式则为0。  
     * 申请的内存将通过下面即将见到的 usb_fill_int_urb 函数进行填充。   
     */  
mouse->irq = usb_alloc_urb(0, GFP_KERNEL);//这里就是前面所说urb周期的第一步。
if (!mouse->irq)
goto fail2;
/* 填充 usb 设备结构体和输入设备结构体,所以前面需要将usb_interface结构体转化为usb_device结构体 */  
mouse->usbdev = dev;
mouse->dev = input_dev;
/* 获取鼠标设备的名称 */  
if (dev->manufacturer)
strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));


if (dev->product) {
if (dev->manufacturer)
strlcat(mouse->name, " ", sizeof(mouse->name));
strlcat(mouse->name, dev->product, sizeof(mouse->name));
}


if (!strlen(mouse->name))
snprintf(mouse->name, sizeof(mouse->name),
"USB HIDBP Mouse %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
 /*  
   填充鼠标设备结构体中的节点名。usb_make_path 用来获取 USB 设备在 Sysfs 中的路径,格式为:usb-usb 总线号-路径名。  
     */  
usb_make_path(dev, mouse->phys, sizeof(mouse->phys));
strlcat(mouse->phys, "/input0", sizeof(mouse->phys));
 /* 将鼠标设备的名称赋给鼠标设备内嵌的输入子系统结构体 */  
input_dev->name = mouse->name;
 /* 将鼠标设备的设备节点名赋给鼠标设备内嵌的输入子系统结构体 */  
input_dev->phys = mouse->phys;
 /*  
   input_dev 中的 input_id 结构体,用来存储厂商、设备类型和设备的编号,这个函数是将设备描述符中的编号赋给内嵌的输入子系统结构体  
     */  
usb_to_input_id(dev, &input_dev->id);


input_dev->dev.parent = &intf->dev;//?????????????????
/* evbit 用来描述事件,EV_KEY 是按键事件,EV_REL 是相对坐标事件 */  
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
 /* keybit 表示键值,包括左键、右键和中键 */  
input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
 /* keybit 表示键值,包括左键、右键和中键 */  
input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
 /* 有的鼠标还有其它按键 */ 
input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |
BIT_MASK(BTN_EXTRA);
 /* 中键滚轮的滚动值 */  
input_dev->relbit[0] |= BIT_MASK(REL_WHEEL);
/*将input_dev->p->driver_data赋值为mouse,应该是input_dev私有数据项表示当前输入设备种类*/
input_set_drvdata(input_dev, mouse);
 /* 填充输入设备打开函数指针 */  
input_dev->open = usb_mouse_open;
/* 填充输入设备关闭函数指针 */  
input_dev->close = usb_mouse_close;
 /*  
     * 填充构建 urb,将刚才填充好的 mouse 结构体的数据填充进 urb 结构体中,在 open 中递交 urb。当 urb 包含一个即将传输的 DMA 缓冲区时应该设置 URB_NO_TRANSFER_DMA_MAP。USB核心使用transfer_dma变量所指向的缓冲区,而不是transfer_buffer变量所指向的。URB_NO_SETUP_DMA_MAP 用于 Setup 包,URB_NO_TRANSFER_DMA_MAP 用于所有 Data 包。  
     */  
usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,   (maxp > 8 ? 8 : maxp), usb_mouse_irq, mouse, endpoint->bInterval);/*这里就是上面所说的urb生命周期的第二步:分配一个特定的usb设备的特定端点。 
mouse->irq是我们之前创建的urb结构,此结构在这个函数里被初始化;
dev是之前通过转化usb_interface结构体的usb_device结构体,是该urb所发送的目标usb设备;
pipe为该urb所发送目标usb设备的特定端点(前面已经将其设置为中断in端点),可以通过一系列函数来设定此端点的类型(ldd3 page333)。该变量必须在urb发送到usb核心之前被初始化
mouse->data  在前面进行了内核空间的申请,用于保存发送/接受数据的缓冲区指针。
(maxp > 8 ? 8 : maxp)指缓冲区的大小,端点规定的最大数据包不能超过8个字节
usb_mouse_irq 指向当该urb结束后调用的结束处理程序,input设备的报点程序在这个里面
mouse 指向一个小数据块,该块被添加到urb结构中以便进行结束处理例程后面的查找,在本驱动中,回调函数需要使用。
endpoint->bInterval 该urb应该被调度的时间间隔*/
mouse->irq->transfer_dma = mouse->data_dma; //使用dma缓冲区进行传输
mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
   
error = input_register_device(mouse->dev);//向系统注册输入设备
if (error)
goto fail3;


/*  
 一般在 probe 函数中,都需要将设备相关信息保存在一个 usb_interface 结构体中,以便以后通过usb_get_intfdata 获取使用。这里鼠标设备结构体信息将保存在 intf 接口结构体内嵌的设备结构体中的 driver_data 数据成员中,即 intf->dev->dirver_data = mouse。  
     */  
usb_set_intfdata(intf, mouse);
return 0;


fail3:
usb_free_urb(mouse->irq);
fail2:
usb_free_coherent(dev, 8, mouse->data, mouse->data_dma);
fail1:
input_free_device(input_dev);
kfree(mouse);
return error;
}








/*  
  打开鼠标设备时,开始提交在 probe 函数中构建的 urb,进入 urb 周期。  
 */


static int usb_mouse_open(struct input_dev *dev)
{
struct usb_mouse *mouse = input_get_drvdata(dev);//probe函数里使用input_set_drvdata(input_dev, mouse);将mouse赋值给了input_dev的p-drvdata,这次是将其取回,真他妈绕。


mouse->irq->dev = mouse->usbdev;
if (usb_submit_urb(mouse->irq, GFP_KERNEL)) //这里进行urb生命周期的第3步将urb提交给usb核心。到这里urb生命周期在设备驱动里的任务就结束了,剩下的任务交给usb核心去做了。
return -EIO;


return 0;
}




/*  
  urb 回调函数,在完成提交 urb 后,urb 回调函数将被调用,此函数作为 usb_fill_int_urb 函数的形参,为构建的 urb 制定的回调函数。  
 */  


static void usb_mouse_irq(struct urb *urb)
{
/* urb 中的 context 指针用于为 USB 驱动程序保存一些数据。比如在这个回调函数的形参没有传递在 probe中为 mouse 结构体分配的那块内存的地址指针,而又需要用到那块内存区域中的数据,context 指针则帮了大忙了!在填充 urb 时将 context 指针指向 mouse 结构体数据区,在这又创建一个局部 mouse 指针指向在 probe函数中为 mouse 申请的那块内存,那块内存保存着非常重要数据。当 urb 通过 USB core 提交给 hc 之后,如果结果正常,mouse->data 指向的内存区域将保存着鼠标的按键和移动坐标信息,系统则依靠这些信息对鼠标的行为作出反应。mouse 中内嵌的 dev 指针,指向 input_dev 所属于的内存区域*/  
struct usb_mouse *mouse = urb->context;//这里体现了在usb_fill_int_urb函数中将context函数赋值为mouse的意义。
signed char *data = mouse->data;//数据缓冲区,probe函数中定义
struct input_dev *dev = mouse->dev;
int status;//urb当前状态
 /*  
      status 值为 0 表示 urb 成功返回,直接跳出循环把鼠标事件报告给输入子系统。ECONNRESET 出错信息表示 urb 被 usb_unlink_urb 函数给 unlink 了,ENOENT 出错信息表示 urb 被 usb_kill_urb 函数给 kill 了。usb_kill_urb 表示彻底结束 urb 的生命周期,而 usb_unlink_urb 则是停止 urb,这个函数不等 urb 完全终止就会返回给回调函数。这在运行中断处理程序时或者等待某自旋锁时非常有用,在这两种情况下是不能睡眠的,而等待一个 urb 完全停止很可能会出现睡眠的情况。ESHUTDOWN 这种错误表示 USB 主控制器驱动程序发生了严重的错误,或者提交完 urb 的一瞬间设备被拔出。  遇见除了以上三种错误以外的错误,将申请重传 urb。  
        */  
switch (urb->status) {//对urb状态进行判断
case 0: /* success */
break;
case -ECONNRESET: /* unlink */
case -ENOENT:
case -ESHUTDOWN:
return;
/* -EPIPE:  should clear the halt */
default: /* error */
goto resubmit;
}


/*  
  向输入子系统汇报鼠标事件情况,以便作出反应。  data 数组的第0个字节:bit 0、1、2、3、4分别代表左、右、中、SIDE、EXTRA键的按下情况; data 数组的第1个字节:表示鼠标的水平位移; data 数组的第2个字节:表示鼠标的垂直位移; data 数组的第3个字节:REL_WHEEL位移。  
     */  


input_report_key(dev, BTN_LEFT,   data[0] & 0x01);
input_report_key(dev, BTN_RIGHT,  data[0] & 0x02);
input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);
input_report_key(dev, BTN_SIDE,   data[0] & 0x08);
input_report_key(dev, BTN_EXTRA,  data[0] & 0x10);


input_report_rel(dev, REL_X,     data[1]);
input_report_rel(dev, REL_Y,     data[2]);
input_report_rel(dev, REL_WHEEL, data[3]);
  /*  
     这里是用于事件同步。上面几行是一次完整的鼠标事件,包括按键信息、绝对坐标信息和滚轮信息,输入子系统正是通过这个同步信号来在多个完整事件报告中区分每一次完整事件报告。示意如下:按键信息 坐标位移信息 滚轮信息 EV_SYC | 按键信息 坐标位移信息 滚轮信息 EV_SYC ...  
     */ 
input_sync(dev);

 /*  
      系统需要周期性不断地获取鼠标的事件信息,因此在 urb 回调函数的末尾再次提交 urb 请求块,这样又会调用新的回调函数,周而复始。在回调函数中提交 urb 一定只能是 GFP_ATOMIC 优先级的,因为 urb 回调函数运行于中断上下文中,在提交 urb 过程中可能会需要申请内存、保持信号量,这些操作或许会导致 USB core 睡眠,一切导致睡眠的行为都是不允许的。  
     */  


resubmit:
status = usb_submit_urb (urb, GFP_ATOMIC);
if (status)
err ("can't resubmit intr, %s-%s/input0, status %d",
mouse->usbdev->bus->bus_name,
mouse->usbdev->devpath, status);
}




结束
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值