linux usb驱动中的urb详解

转自:https://blog.csdn.net/lemontree1945/article/details/78852728

linux 内核中的 USB 代码和所有的 USB 设备通讯使用称为 urb 的东西( USB request block). 这个请求块用 struct urb 结构描述并且可在 include/linux/usb.h 中找到. 

 

    一个urb 用来发送或接受数据到或者从一个特定 USB 设备上的特定的 USB 端点, 以一种异步的方式.一个 USB 设备驱动可能分配许多 urb 给一个端点或者可能重用单个 urb 给多个不同的端点, 根据驱动的需要. 设备中的每个端点都处理一个 urb 队列, 以至于多个 urb 可被发送到相同的端点, 在队列清空之前. 一个 urb 的典型生命循环如下:

  • 被一个 USB 设备驱动创建.

  • 安排给一个特定 USB 设备的特定端点.

  • 提交给 USB 核心, 被 USB 设备驱动.

  • 提交给特定设备的被 USB 核心指定的 USB 主机控制器驱动, .

  • 被 USB 主机控制器处理, 它做一个 USB 传送到设备.

  • 当 urb 完成, USB 主机控制器驱动通知 USB 设备驱动.

    urb 也可被提交这个 urb 的驱动在任何时间取消, 或者被 USB 核心如果设备被从系统中移出. urb 被动态创建并且包含一个内部引用计数, 使它们在这个 urb 的最后一个用户释放它时被自动释放.

1、URB结构体分析    

    struct urb代码如下:

复制代码
 1 struct urb {
 2     
 3     struct kref kref;        
 4     void *hcpriv;            
 5     atomic_t use_count;        
 6     atomic_t reject;        
 7     int unlinked;            
 8 
 9     
10     struct list_head urb_list;    
12     struct list_head anchor_list;    
13     struct usb_anchor *anchor;
14     struct usb_device *dev;     
15     struct usb_host_endpoint *ep;    
16     unsigned int pipe;        
17     int status;            
18     unsigned int transfer_flags;    
19     void *transfer_buffer;        
20     dma_addr_t transfer_dma;    
21     struct usb_sg_request *sg;    
22     int num_sgs;            
23     u32 transfer_buffer_length;    
24     u32 actual_length;        
25     unsigned char *setup_packet;    
26     dma_addr_t setup_dma;        
27     int start_frame;        
28     int number_of_packets;        
29     int interval;            
31     int error_count;        
32     void *context;            
33     usb_complete_t complete;    
34     struct usb_iso_packet_descriptor iso_frame_desc[0];
35                     
36 };
复制代码
14     struct usb_device *dev;     

    指向这个urb要发送的struct usb_device 的指针,在urb发送到usb核心之前,这个变量必须被usb驱动初始化。 

16     unsigned int pipe;        
可以称之为管道,也可以称为端点消息,是给这个urb发送到的特定struct usb_device,同理,在urb发送到usb核心之前,这个变量必须被usb驱动初始化。 
为了设置这个pipe,驱动有一些初始化函数,这里要注意,每个pipe只可能是其中一种类型、、、
复制代码
unsigned int usb_sndctrlpipe(struct usb_device *dev, unsigned int endpoint)
//指定一个控制 OUT 端点给特定的带有特定端点号的 USB 设备.

unsigned int usb_rcvctrlpipe(struct usb_device *dev, unsigned int endpoint)
//指定一个控制 IN 端点给带有特定端点号的特定 USB 设备.

unsigned int usb_sndbulkpipe(struct usb_device *dev, unsigned int endpoint)
//指定一个块 OUT 端点给带有特定端点号的特定 USB 设备

unsigned int usb_rcvbulkpipe(struct usb_device *dev, unsigned int endpoint)
//指定一个块 IN 端点给带有特定端点号的特定 USB 设备

unsigned int usb_sndintpipe(struct usb_device *dev, unsigned int endpoint)
//指定一个中断 OUT 端点给带有特定端点号的特定 USB 设备

unsigned int usb_rcvintpipe(struct usb_device *dev, unsigned int endpoint)
//指定一个中断 IN 端点给带有特定端点号的特定 USB 设备

unsigned int usb_sndisocpipe(struct usb_device *dev, unsigned int endpoint)
//指定一个同步 OUT 端点给带有特定端点号的特定 USB 设备

unsigned int usb_rcvisocpipe(struct usb_device *dev, unsigned int endpoint)
//指定一个同步 IN 端点给带有特定端点号的特定 USB 设备
复制代码
18     unsigned int transfer_flags;    
这个变量可被设置为不同的位值,根据这个位,usb驱动可以设置urb传输的状态,具体可用值详见ldd3.
19     void *transfer_buffer;        
指向缓冲区的指针,这个指针可以是OUT urb 或者是In urb。主机控制器为了正确使用这个缓冲区,必须使用kmalloc调用来创建它,而不是堆栈或者静态数据区。对于控制端点,这个缓冲是给发送的数据。
20     dma_addr_t transfer_dma;    
用来使用DMA传送数据到usb设备的缓冲。
23     u32 transfer_buffer_length;    
缓冲区的长度,由于urb只会使用buffer或者dma其中一种来传输数据,所以这个长度可以被它们共用。如果这是0,表示没有传送缓冲被usb核心所使用。
25     unsigned char *setup_packet;    
指向一个控制urb的setup报文的指针,在位于正常传送缓冲的数据之前被传送,这个变量只对控制urb有效。
26     dma_addr_t setup_dma;        
给控制 urb 的 setupt 报文的 DMA 缓冲. 在位于正常传送缓冲的数据之前被传送. 这个变量只对控制 urb 有效.
24     u32 actual_length;        
当urb完成处理后,这个变量被设置为数据的真实长度,或者由这个urb(OUT)发送,或者由这个urb(IN)接受。对于IN urb,这个值必须被用来替代taansfer_buffer_length,因为接收的数据可能比整个缓冲区的大小小。
17     int status;            
当这个 urb 被结束, 或者开始由 USB 核心处理, 这个变量被设置为 urb 的当前状态. 一个 USB 驱动可安全存取这个变量的唯一时间是在 urb 完成处理者函数中(在"CompletingUrbs: 完成回调处理者"一节中描述). 这个限制是阻止竞争情况, 发生在这个 urb 被 USB 核心处理当中. 对于同步 urb, 在这个变量中的一个成功的值(0)只指示是否这个 urb 已被去链. 为获得在同步 urb 上的详细状态, 应当检查 iso_frame_desc 变量.
这个变量的有效值包括:
复制代码
0
//这个 urb 传送是成功的.

-ENOENT
//这个 urb 被对 usb_kill_urb 的调用停止.

-ECONNRESET
//urb 被对 usb_unlink_urb 的调用去链, 并且 transfer_flags 变量被设置为 URB_ASYNC_UNLINK.

-EINPROGRESS

-EPROTO

-EILSEQ
//在这个 urb 传送中有一个 CRC 不匹配.

-EPIPE

-ECOMM
//在传送中数据接收快于能被写入系统内存. 这个错误值只对 IN urb.

-ENOSR

-EOVERFLOW

-EREMOTEIO

-ENODEV
//这个 USB 设备现在从系统中消失.

-EXDEV

-EINVAL

-ESHUTDOWN

复制代码
33     usb_complete_t complete;    
指向完成处理者函数的指针, 它被 USB 核心调用当这个 urb 被完全传送或者当 urb 发生一个错误. 在这个函数中, USB 驱动可检查这个 urb, 释放它, 或者重新提交它给另一次传送.
32     void *context;            
指向数据点的指针, 它可被 USB 驱动设置. 它可在完成处理者中使用当 urb 被返回到驱动. 可能是在回调函数使用的数据。
27     int start_frame;        
设置或者返回同步传送要使用的初始帧号。
29     int interval;            
只对同步 urb 有效, 并且指定这个 urb 要处理的同步传送缓冲的编号. 在这个 urb 发送给 USB 核心之前,这个值必须被 USB 驱动设置给同步 urb .
31     int error_count;        
被 USB 核心设置, 只给同步 urb 在它们完成之后. 它指定报告任何类型错误的同步传送的号码.
34     struct usb_iso_packet_descriptor iso_frame_desc[0];      
只对同步 urb 有效. 这个变量是组成这个 urb 的一个 struct usb_iso_packet_descriptor 结构数组. 这个结构允许单个 urb 来一次定义多个同步传送. 它也用来收集每个单独传送的传送状态
2、URB创建和销毁

struct urb 结构在驱动中必须不能被静态创建, 或者在另一个结构中, 因为这可能破坏 USB 核心给 urb 使用的引用计数方法. 它必须使用对 usb_alloc_urb 函数的调用而被创建. 这个函数有这个原型:

struct urb *usb_alloc_urb(int iso_packets, int mem_flags);

第一个参数, iso_packet, 是这个 urb 应当包含的同步报文的数目. 如果你不想创建一个同步 urb, 这个变量应当被设置为 0. 第 2 个参数, mem_flags, 是和传递给 kmalloc 函数调用来从内核分配内存的相同的标志类型(见"flags 参数"一节, 第 8 章, 关于这些标志的细节). 如果这个函数在分配足够内存给这个 urb 成功, 一个指向 urb 的指针被返回给调用者. 如果返回值是 NULL, 某个错误在 USB 核心中发生了, 并且驱动需要正确地清理.

在创建了一个 urb 之后, 它必须被正确初始化在它可被 USB 核心使用之前. 如何初始化不同类型 urb 见下一节

为了告诉 USB 核心驱动用完这个 urb, 驱动必须调用 usb_free_urb 函数. 这个函数只有一个参数:

void usb_free_urb(struct urb *urb);

参数是一个指向你要释放的 struct urb 的指针. 在这个函数被调用之后, urb 结构消失, 驱动不能再存取它.

   3、中断 urb

函数 usb_fill_int_urb 是一个帮忙函数, 来正确初始化一个urb 来发送给 USB 设备的一个中断端点:

void usb_fill_int_urb(struct urb *urb, struct usb_device *dev,
 unsigned int pipe, void *transfer_buffer,
 int buffer_length, usb_complete_t complete,
 void *context, int interval);

这个函数包含许多参数:

struct urb *urb

指向要被初始化的 urb 的指针.

struct usb_device *dev

这个 urb 要发送到的 USB 设备.

unsigned int pipe

这个 urb 要被发送到的 USB 设备的特定端点. 这个值被创建, 使用前面提过的 usb_sndintpipe 或者 usb_rcvintpipe 函数.

void *transfer_buffer

指向缓冲的指针, 从那里外出的数据被获取或者进入数据被接受. 注意这不能是一个静态的缓冲并且必须使用 kmalloc 调用来创建.

int buffer_length

缓冲的长度, 被 transfer_buffer 指针指向.

usb_complete_t complete

指针, 指向当这个 urb 完成时被调用的完成处理者.

void *context

指向数据块的指针, 它被添加到这个 urb 结构为以后被完成处理者函数获取.

int interval

这个 urb 应当被调度的间隔. 见之前的 struct urb 结构的描述, 来找到这个值的正确单位.

 

 

4、块urb

块 urb 被初始化非常象中断 urb. 做这个的函数是 usb_fill_bulk_urb, 它看来如此:

 
void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev,
 unsigned int pipe, void *transfer_buffer,
 int buffer_length, usb_complete_t complete,
 void *context);

这个函数参数和 usb_fill_int_urb 函数的都相同. 但是, 没有 interval 参数因为 bulk urb 没有间隔值. 请注意这个 unsiged int pipe 变量必须被初始化用对 usb_sndbulkpipe 或者 usb_rcvbulkpipe 函数的调用.

usb_fill_int_urb 函数不设置 urb 中的 transfer_flags 变量, 因此任何对这个成员的修改不得不由这个驱动自己完成.

5、控制urb

控制 urb 被初始化几乎和 块 urb 相同的方式, 使用对函数 usb_fill_control_urb 的调用:

 
void usb_fill_control_urb(struct urb *urb, struct usb_device *dev,
 unsigned int pipe, unsigned char *setup_packet,
 void *transfer_buffer, int buffer_length,
 usb_complete_t complete, void *context);

函数参数和 usb_fill_bulk_urb 函数都相同, 除了有个新参数, unsigned char *setup_packet, 它必须指向要发送给端点的 setup 报文数据. 还有, unsigned int pipe 变量必须被初始化, 使用对 usb_sndctrlpipe 或者 usb_rcvictrlpipe 函数的调用.

usb_fill_control_urb 函数不设置 transfer_flags 变量在 urb 中, 因此任何对这个成员的修改必须游驱动自己完成. 大部分驱动不使用这个函数, 因为使用在"USB 传送不用 urb"一节中介绍的同步 API 调用更简单.

6、同步urb
不幸的是, 同步 urb 没有一个象中断, 控制, 和块 urb 的初始化函数. 因此它们必须在驱动中"手动"初始化, 在它们可被提交给 USB 核心之前. 下面是一个如何正确初始化这类 urb 的例子. 它是从 konicawc.c 内核驱动中取得的, 它位于主内核源码树的 drivers/usb/media 目录.
复制代码
urb->dev = dev;
urb->context = uvd;
urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp-1);
urb->interval = 1;
urb->transfer_flags = URB_ISO_ASAP;
urb->transfer_buffer = cam->sts_buf[i];
urb->complete = konicawc_isoc_irq;
urb->number_of_packets = FRAMES_PER_DESC;
urb->transfer_buffer_length = FRAMES_PER_DESC;
for (j=0; j < FRAMES_PER_DESC; j++) {

urb->iso_frame_desc[j].offset = j;
urb->iso_frame_desc[j].length = 1;
}

复制代码

  7、提交 urb

 

一旦 urb 被正确地创建,并且被 USB 驱动初始化, 它已准备好被提交给 USB 核心来发送出到 USB 设备. 这通过调用函数usb_submit_urb 实现:

 

int usb_submit_urb(struct urb *urb, int mem_flags);

 

urb 参数是一个指向 urb 的指针, 它要被发送到设备. mem_flags 参数等同于传递给 kmalloc 调用的同样的参数, 并且用来告诉 USB 核心如何及时分配任何内存缓冲在这个时间.

 

在 urb 被成功提交给 USB 核心之后, 应当从不试图存取 urb 结构的任何成员直到完成函数被调用.

 

因为函数 usb_submit_urb 可被在任何时候被调用(包括从一个中断上下文), mem_flags 变量的指定必须正确.根据 usb_submit_urb 被调用的时间,只有 3 个有效值可用:

 

GFP_ATOMIC
只要满足以下条件,就应当使用此值:
1.调用者处于一个 urb 结束处理例程,中断处理例程,底半部,tasklet或者一个定时器回调函数.
2.调用者持有自旋锁或者读写锁. 注意如果正持有一个信号量, 这个值不必要.
3.current->state 不是 TASK_RUNNING. 除非驱动已自己改变 current 状态,否则状态应该一直是 TASK_RUNNING .
 
GFP_NOIO
    驱动处于块 I/O 处理过程中. 它还应当用在所有的存储类型的错误处理过程中.
 
GFP_KERNEL
    所有不属于之前提到的其他情况

  8、完成 urb: 完成回调处理者

如果对 usb_submit_urb 的调用成功, 驱动将传递对 urb 的控制给 USB 核心, 这个函数返回 0; 否则, 一个负错误值被返回. 如果函数调用成功, urb 的完成处理者(就是complete指定的的函数)被确切地调用一次, 当 urb 被完成. 当这个函数被调用, USB 核心完成这个 urb, 并且对它的控制现在返回给设备驱动.

只有 3 个方法, 一个urb 可被结束并且使完成函数被调用:

  • urb 被成功发送给设备, 并且设备返回正确的确认. 对于一个 OUT urb, 数据被成功发送, 对于一个 IN urb, 请求的数据被成功收到. 如果发生这个, urb 中的状态变量被设置为 0.

  • 一些错误连续发生, 当发送或者接受数据从设备中. 被 urb 结构中的 status 变量中的错误值所记录.

  • 这个 urb 被从 USB 核心去链. 这发生在要么当驱动告知 USB 核心取消一个已提交的 urb 通过调用 usb_unlink_urb 或者 usb_kill_urb, 要么当设备从系统中去除, 以及一个 urb 已经被提交给它

 

9、取消 urb

为停止一个已经提交给 USB 核心的 urb, 函数 usb_kill_urb 或者 usb_unlink_urb 应当被调用:

int usb_kill_urb(struct urb *urb); 
int usb_unlink_urb(struct urb *urb);

The urb parameter for both of these functions is a pointer to the urb that is to be canceled.

 

当函数是 usb_kill_urb, 这个 urb 的生命循环就停止了. 这个函数常常在设备从系统去除时被使用, 在去连接回调中.

对一些驱动, 应当用 usb_unlink_urb 函数来告知 USB 核心去停止 urb. 这个函数在返回到调用者之前不等待这个 urb 完全停止. 这对于在中断处理或者持有一个自旋锁时停止 urb 时是有用的, 因为等待一个 urb 完全停止需要 USB 核心有能力使调用进程睡眠. 为了正确工作这个函数要求 URB_ASYNC_UNLINK 标志值被设置在正被要求停止的 urb 中.

 

至此,关于urb的结构,功能,提交和取消全部介绍完毕,对于一般的USB驱动,应该是足够用了,本文大多数内容引用自ldd3,有少部分我做了修改,方便自己查阅,同时分享自己的学习心得。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
linux系统下USB键盘驱动源码+使用文档说明 如何编写Linux下的USB键盘驱动 1. 指定USB键盘驱动所需的头文件: #include /*内核头文件,含有内核一些常用函数的原型定义*/ #include /*定义内存分配的一些函数*/ #include /*模块编译必须的头文件*/ #include /*输入设备相关函数的头文件*/ #include /*linux初始化模块函数定义*/ #include /*USB设备相关函数定义*/ 2. 定义键盘码表数组: /*使用第一套键盘扫描码表:A-1E;B-30;C-2E…*/ static unsigned char usb_kbd_keycode[256] = { 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106, 105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190, 191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113, 115,114, 0, 0, 0,121, 0, 89, 93,124, 92, 94, 95, 0, 0, 0, 122,123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113, 150,158,159,128,136,177,178,176,142,152,173,140 }; 3. 编写设备ID表: static struct usb_device_id usb_kbd_id_table [] = { { USB_INTERFACE_INFO(3, 1, 1) },/*3,1,1分别表示接口类,接口子类,接口协议;3,1,1为键盘接口类;鼠标为3,1,2*/ { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, usb_kbd_id_table);/*指定设备ID表*/ 4. 定义USB键盘结构体: struct usb_kbd { struct input_dev *dev; /*定义一个输入设备*/ struct usb_device *usbdev;/*定义一个usb设备*/ unsigned char old[8]; /*按键离开时所用之数据缓冲区*/ struct urb *irq/*usb键盘之断请求块*/, *led/*usb键盘之指示灯请求块*/; unsigned char newleds;/*目标指定灯状态*/ char name[128];/*存放厂商名字及产品名字*/ char phys[64]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值