Linux USB驱动源代码分析
+ -

LINUX&USB驱动urb

2024-03-08 15 0
原文转自:https://www.cnblogs.com/wen123456/p/14212660.html

USB规范定义了USB的四种数据传输模式,但他们都是依赖于一个叫的URB的东西,全称USB Request BLOCK.
作为一名Windows驱动开发人员,其URB中通过其IRP结构体PIO_STACK_LOCATION的一个指针指向其内存的。一般的代码如下:

    PURB Urb;
    PIO_STACK_LOCATION IoStack;

    IoStack = IoGetCurrentIrpStackLocation(Irp);
    Urb = (PURB)IoStack->Parameters.Others.Argument1;

在Linux&USB驱动中,也其对应的URB结构体:

struct urb
{
    /* 私有的:只能由usb核心和主机控制器访问的字段 */
    struct kref kref; /*urb引用计数 */
    spinlock_t lock; /* urb锁 */
    void *hcpriv; /* 主机控制器私有数据 */
    int bandwidth; /* int/iso请求的带宽 */
    atomic_t use_count; /* 并发传输计数 */
    u8 reject; /* 传输将失败*/

    /* 公共的: 可以被驱动使用的字段 */
    struct list_head urb_list; /* 链表头*/
    struct usb_device *dev; /* 关联的usb设备 */
    unsigned int pipe; /* 管道信息 */
    int status; /* urb的当前状态 */
    unsigned int transfer_flags; /* urb_short_not_ok | ...*/
    void *transfer_buffer; /* 发送数据到设备或从设备接收数据的缓冲区 */
    dma_addr_t transfer_dma; /*用来以dma方式向设备传输数据的缓冲区 */
    int transfer_buffer_length;/*transfer_buffer或transfer_dma 指向缓冲区的大小 */
    int actual_length; /* urb结束后,发送或接收数据的实际长度 */
    unsigned char *setup_packet; /* 指向控制urb的设置数据包的指针*/
    dma_addr_t setup_dma; /*控制urb的设置数据包的dma缓冲区*/
    int start_frame; /*等时传输中用于设置或返回初始帧*/
    int number_of_packets; /*等时传输中等时缓冲区数据 */
    int interval; /* urb被轮询到的时间间隔(对中断和等时urb有效) */
    int error_count;  /* 等时传输错误数量 */
    void *context; /* completion函数上下文 */
    usb_complete_t complete; /* 当urb被完全传输或发生错误时,被调用 */
    struct usb_iso_packet_descriptor iso_frame_desc[0];
    /*单个urb一次可定义多个等时传输时,描述各个等时传输 */
};

一个 USB 设备驱动可根据驱动的需要,分配多个 urb 给一个端点或重用单个 urb 给多个不同的端点。设备中的每个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点。

一个 urb 的典型生命循环如下:

(1)被创建;
(2)被分配给一个特定 USB 设备的特定端点;
(3)被提交给 USB 核心;
(4)被 USB 核心提交给特定设备的特定 USB 主机控制器驱动;
(5)被 USB 主机控制器驱动处理, 并传送到设备;
(6)以上操作完成后,USB主机控制器驱动通知 USB 设备驱动。

urb 也可被提交它的驱动在任何时间取消;如果设备被移除,urb 可以被USB核心取消。urb 被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被自动释放。

申请urb内存结构和释放

urb本质是一片内存,只是urb结构体描述了其内存的数据布局。所以其内存的申请和常用的内核内存申请没有多大的区别。linux为了方便,封装了urb结构体的申请和释放。这些内存都是在堆上的。

申请urb内存

urb内存使用函数usb_alloc_urb来实现,其原型为:

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

示例代码如下:

urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!urb) {
        retval = -ENOMEM;
        goto error;
    }

释放urb内存

如果驱动已经对 urb 使用完毕, 必须调用 usb_free_urb 函数,释放urb。函数原型:

void usb_free_urb(struct urb *urb);
//struct urb *urb : 要释放的 struct urb 指针

urb初始化

urb至少包含了4种传输方式,其中控制传输方式又有很多种类型。

/*中断 urb 初始化函数*/
static inline 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_fn,
                 void *context,
                 int interval);

/*批量 urb 初始化函数*/
static inline 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_fn,
                 void *context);

/*控制 urb 初始化函数*/
static inline 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_fn,
                    void *context);

//struct urb *urb :指向要被初始化的 urb 的指针
//struct usb_device *dev :指向 urb 要发送到的 USB 设备.
//unsigned int pipe : urb 要被发送到的 USB 设备的特定端点. 必须使用前面提过的 usb_******pipe 函数创建
//void *transfer_buffer :指向外发数据或接收数据的缓冲区的指针.注意:不能是静态缓冲,必须使用 kmalloc 来创建.
//int buffer_length :transfer_buffer 指针指向的缓冲区的大小
//usb_complete_t complete :指向 urb 结束处理例程函数指针
//void *context :指向一个小数据块的指针, 被添加到 urb 结构中,以便被结束处理例程函数获取使用.
//int interval :中断 urb 被调度的间隔.
//函数不设置 urb 中的 transfer_flags 变量, 因此对这个成员的修改必须由驱动手动完成

/*等时 urb 没有初始化函数,必须手动初始化,以下为一个例子*/
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;
}

提交urb

一旦 urb 被正确地创建并初始化, 它就可以提交给 USB 核心以发送出到 USB 设备. 这通过调用函数 usb_submit_urb 实现:

 int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
//struct urb *urb :指向被提交的 urb 的指针 
//gfp_t mem_flags :使用传递给 kmalloc 调用同样的参数, 用来告诉 USB 核心如何及时分配内存缓冲

/*因为函数 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 
所有不属于之前提到的其他情况
*/

在 urb 被成功提交给 USB 核心之后, 直到结束处理例程函数被调用前,都不能访问 urb 结构的任何成员.

urb结束处理例程

如果 usb_submit_urb 被成功调用, 并把对 urb 的控制权传递给 USB 核心, 函数返回 0; 否则返回一个负的错误代码. 如果函数调用成功, 当 urb 被结束的时候结束处理例程会被调用一次.当这个函数被调用时, USB 核心就完成了这个urb, 并将它的控制权返回给设备驱动.

只有 3 种结束urb并调用结束处理例程的情况:

(1)urb 被成功发送给设备, 且设备返回正确的确认.如果这样, urb 中的status变量被设置为 0.
(2)发生错误, 错误值记录在 urb 结构中的 status 变量.
(3)urb 从 USB 核心unlink. 这发生在要么当驱动通过调用 usb_unlink_urb 或者 usb_kill_urb告知 USB 核心取消一个已提交的 urb,或者在一个 urb 已经被提交给它时设备从系统中去除.

取消 urb

使用以下函数停止一个已经提交给 USB 核心的 urb:

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

如果调用usb_kill_urb函数,则 urb 的生命周期将被终止. 这通常在设备从系统移除时,在断开回调函数(disconnect callback)中调用.

对一些驱动, 应当调用 usb_unlink_urb 函数来使 USB 核心停止 urb. 这个函数不会等待 urb 完全停止才返回. 这对于在中断处理例程中或者持有一个自旋锁时去停止 urb 是很有用的, 因为等待一个 urb 完全停止需要 USB 核心有使调用进程休眠的能力(wait_event()函数).

0 篇笔记 写笔记

关注公众号
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!