linux usb 驱动详解

USB 设备驱动代码通过urb和所有的 USB 设备通讯。urb用 struct urb 结构描述(include/linux/usb.h )。
 urb
以一种异步的方式同一个特定USB设备的特定端点发送或接受数据。一个 USB 设备驱动可根据驱动的需要,分配多个 urb 给一个端点或重用单个
urb 给多个不同的端点。设备中的每个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点。

 一个 urb 的典型生命循环如下:
 (1)被创建;
 (2)被分配给一个特定 USB 设备的特定端点;
 (3)被提交给 USB 核心;
 (4)被 USB 核心提交给特定设备的特定 USB 主机控制器驱动;
 (5)被 USB 主机控制器驱动处理, 并传送到设备;
 (6)以上操作完成后,USB主机控制器驱动通知 USB 设备驱动。
 
 urb 也可被提交它的驱动在任何时间取消;如果设备被移除,urb 可以被USB核心取消。urb 被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被自动释放。

struct urb
 

struct urb {
    /* private: usb core and host controller only fields in the urb */
    struct kref kref;        /* URB引用计数 */
    void *hcpriv;            /* host控制器的私有数据 */
    atomic_t use_count;        /* 当前提交计数 */
    atomic_t reject;        /* 提交失败计数 */
    int unlinked;            /* 连接失败代码 */

/* public: documented fields in the urb that can be used by drivers */
    struct list_head urb_list;    /* list head for use by the urb's
                     * current owner */
    struct list_head anchor_list;    /* the URB may be anchored */
    struct usb_anchor *anchor;
    struct usb_device *dev;     /* 指向这个 urb 要发送的目标 struct usb_device 的指针,这个变量必须在这个 urb 被发送到 USB 核心之前被 USB 驱动初始化.*/
    struct usb_host_endpoint *ep;    /* (internal) pointer to endpoint */
    unsigned int pipe;        /* 这个 urb 所要发送到的特定struct usb_device的端点消息,这个变量必须在这个 urb 被发送到 USB 核心之前被 USB 驱动初始化.必须由下面的函数生成*/
    int status;            /*
当 urb开始由 USB 核心处理或处理结束, 这个变量被设置为 urb 的当前状态. USB 驱动可安全访问这个变量的唯一时间是在 urb
结束处理例程函数中. 这个限制是为防止竞态. 对于等时 urb, 在这个变量中成功值(0)只表示这个 urb 是否已被去链. 为获得等时
urb 的详细状态, 应当检查 iso_frame_desc 变量. */
    unsigned int transfer_flags;    /* 传输设置*/
    void *transfer_buffer;        /* 指向用于发送数据到设备(OUT urb)或者从设备接收数据(IN urb)的缓冲区指针。为了主机控制器驱动正确访问这个缓冲, 它必须使用 kmalloc 调用来创建, 不是在堆栈或者静态内存中。 对控制端点, 这个缓冲区用于数据中转*/
    dma_addr_t transfer_dma;    /* 用于以 DMA 方式传送数据到 USB 设备的缓冲区*/
    int transfer_buffer_length;    /*
transfer_buffer 或者 transfer_dma 变量指向的缓冲区大小。如果这是 0, 传送缓冲没有被 USB
核心所使用。对于一个 OUT 端点, 如果这个端点大小比这个变量指定的值小, 对这个 USB
设备的传输将被分成更小的块,以正确地传送数据。这种大的传送以连续的 USB 帧进行。在一个 urb 中提交一个大块数据, 并且使 USB
主机控制器去划分为更小的块, 比以连续地顺序发送小缓冲的速度快得多*/
    int actual_length;        /*
当这个 urb 完成后, 该变量被设置为这个 urb (对于 OUT urb)发送或(对于 IN urb)接受数据的真实长度.对于 IN
urb, 必须是用此变量而非 transfer_buffer_length , 因为接收的数据可能比整个缓冲小*/
    unsigned char *setup_packet;    /* 指向控制urb的设置数据包指针.它在传送缓冲中的数据之前被传送(用于控制 urb)*/
    dma_addr_t setup_dma;        /* 控制 urb 用于设置数据包的 DMA 缓冲区地址,它在传送普通缓冲区中的数据之前被传送(用于控制 urb)*/
    int start_frame;        /* 设置或返回初始的帧数量(用于等时urb) */
    int number_of_packets;        /* 指定urb所处理的等时传输缓冲区的数量(用于等时urb,在urb被发送到USB核心前,必须设置) */
    int interval;            /*urb
被轮询的时间间隔. 仅对中断或等时 urb 有效. 这个值的单位依据设备速度而不同. 对于低速和高速的设备, 单位是帧, 它等同于毫秒.
对于其他设备, 单位是微帧, 等同于 1/8 毫秒. 在 urb被发送到 USB 核心之前,此值必须设置.*/
    int error_count;        /* 等时urb的错误计数,由USB核心设置 */
    void *context;            /* 指向一个可以被USB驱动模块设置的数据块. 当 urb 被返回到驱动时,可在结束处理例程中使用. */
    usb_complete_t complete;    /* 结束处理例程函数指针, 当 urb 被完全传送或发生错误,它将被 USB 核心调用. 此函数检查这个 urb, 并决定释放它或重新提交给另一个传输中*/
    struct usb_iso_packet_descriptor iso_frame_desc[0];
                    /* (仅用于等时urb)usb_iso_packet_descriptor结构体允许单个urb一次定义许多等时传输,它用于收集每个单独的传输状态*/
    };

struct usb_iso_packet_descriptor {
    unsigned int offset;        /* 该数据包的数据在传输缓冲区中的偏移量(第一个字节为0) */
    unsigned int length;        /* 该数据包的传输缓冲区大小 */
    unsigned int actual_length;    /* 等时数据包接收到传输缓冲区中的数据长度 */
    int status;            /* 该数据包的单个等时传输状态。它可以把相同的返回值作为主struct urb 结构体的状态变量 */
};

typedef void (*usb_complete_t)(struct urb *);

上述结构体中unsigned int pipe;的生成函数(define):

    static inline unsigned int __create_pipe(struct usb_device *dev,
        unsigned int endpoint)
{
    return (dev->devnum << 8) | (endpoint << 15);
}

/* Create various pipes... */
#define usb_sndctrlpipe(dev,endpoint)    \
    ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint))
#define usb_rcvctrlpipe(dev,endpoint)    \
    ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndisocpipe(dev,endpoint)    \
    ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint))
#define usb_rcvisocpipe(dev,endpoint)    \
    ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndbulkpipe(dev,endpoint)    \
    ((PIPE_BULK << 30) | __create_pipe(dev, endpoint))
#define usb_rcvbulkpipe(dev,endpoint)    \
    ((PIPE_BULK << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndintpipe(dev,endpoint)    \
    ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint))
#define usb_rcvintpipe(dev,endpoint)    \
    ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
//snd:OUT rcv:IN  ctrl:控制  isoc:等时  bulk:批量 int:中断

上述结构体中unsigned int transfer_flags;的值域:

/*
 * urb->transfer_flags:
 *
 * Note: URB_DIR_IN/OUT is automatically set in usb_submit_urb().
 */
#define URB_SHORT_NOT_OK    0x0001    /* 置位时,任何在 IN 端点上发生的简短读取, 被 USB 核心当作错误. 仅对从 USB 设备读取的 urb 有用 */
#define URB_ISO_ASAP        0x0002    /* 若为等时 urb , 驱动想调度这个 urb 时,可置位该位, 只要带宽允许且想在此时设置 urb 中的 start_frame 变量. 若没有置位,则驱动必须指定 start_frame 值,且传输如果不能在当

时启动的话,必须能够正确恢复 */
#define URB_NO_TRANSFER_DMA_MAP    0x0004    /* 当 urb 包含要被发送的 DMA 缓冲时,应被置位.USB 核心使用就会使用 transfer_dma 变量指向的缓冲, 而不是被 transfer_buffer 变量指向的缓冲. */
#define URB_NO_SETUP_DMA_MAP    0x0008    /* 和 URB_NO_TRANSFER_DMA_MAP 类似, 这个位用来控制 DMA 缓冲已经建立的 urb. 如果它被置位, USB 核心使用 setup_dma 变量而不是 setup_packet 变量指向的缓冲. */
#define URB_NO_FSBR        0x0020    /*
仅 UHCI USB 主机控制器驱动使用, 并且告诉它不要试图使用前端总线回收( Front Side Bus Reclamation)
逻辑. 这个位通常应不设置, 因为有 UHCI 主机控制器的机器会增加 CPU 负担, 且PCI 总线会忙于等待设置了这个位的 urb */
#define URB_ZERO_PACKET        0x0040    /* 如果置位, 批量 OUT urb 通过发送不包含数据的短包来结束, 这时数据对齐到一个端点数据包边界. 这被一些掉线的 USB 设备需要该位才能正确工作 */
#define URB_NO_INTERRUPT    0x0080    /* 如果置位, 当 urb 结束时硬件可能不产生一个中断. 该位应当小心使用并且只在多个 urb 排队到相同端点时才使用. USB 核心函数使用该位进行 DMA 缓冲传送. */
#define URB_FREE_BUFFER        0x0100    /* Free transfer buffer with the URB */

#define URB_DIR_IN        0x0200    /* Transfer from device to host */
#define URB_DIR_OUT        0
#define URB_DIR_MASK        URB_DIR_IN

上述结构体中int status;的常用值(in include/asm-generic/errno.h  and errno_base.h) :

// 0     表示 urb 传送成功*/

//以下各个定义在使用时为负值
#define    ENOENT         2    /* urb 被 usb_kill_urb 停止 */
#define    ECONNRESET    104    /* urb 被 usb_unlink_urb 去链, 且 transfer_flags 被设为 URB_ASYNC_UNLINK */
#define    EINPROGRESS    115    /* urb 仍在 USB 主机控制器处理 */
#define    EPROTO        71    /* urb 发生错误: 在传送中发生bitstuff 错误或硬件没有及时收到响应帧 */
#define    EILSEQ        84    /* urb 传送中出现 CRC 较验错 */
#define    EPIPE        32    /* 端点被停止. 若此端点不是控制端点, 则这个错误可通过函数 usb_clear_halt 清除 */
#define    ECOMM        70    /* 数据传输时的接收速度快于写入系统内存的速度. 此错误仅出现在 IN urb */
#define    ENOSR        63    /* 从系统内存中获取数据的速度赶不上USB 数据传送速度,此错误仅出现在 OUT urb. */
#define    EOVERFLOW    75    /* urb 发生"babble"(串扰)错误:端点接受的数据大于端点的最大数据包大小 */
#define    EREMOTEIO    181    /* 当 urb 的 transfer_flags 变量的 URB_SHORT_NOT_OK 标志被设置, urb 请求的数据没有完整地收到 */
#define    ENODEV        19    /* USB 设备从系统中拔出 */
#define    EXDEV        18    /* 仅发生在等时 urb 中, 表示传送部分完成. 为了确定所传输的内容, 驱动必须看单独的帧状态. */
#define    EINVAL        22    /* 如果urb的一个参数设置错误或在提交 urb 给 USB 核心的 usb_submit_urb 调用中, 有不正确的参数,则可能发生次错误 */
#define    ESHUTDOWN    108    /* USB 主机控制器驱动有严重错误,它已被禁止, 或者设备从系统中拔出。且这个urb 在设备被移除后被提交. 它也可能发生在 urb 被提交给设备时,设备的配置已被改变*/


创建和注销 urb

struct urb 结构不能静态创建,必须使用 usb_alloc_urb 函数创建. 函数原型:

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
//int iso_packets : urb 包含等时数据包的数目。如果不使用等时urb,则为0
//gfp_t mem_flags : 与传递给 kmalloc 函数调用来从内核分配内存的标志类型相同

//返回值          : 如果成功分配足够内存给 urb , 返回值为指向 urb 的指针. 如果返回值是 NULL, 则在 USB 核心中发生了错误, 且驱动需要进行适当清理

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

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

根据内核源码,可以通过自己kmalloc一个空间来创建urb,然后必须使用

void usb_init_urb(struct urb *urb);

进行初始化后才可以继续使用。

其实usb_alloc_urb函数就是这样实现的,所以我当然不推荐这种自找麻烦的做法。

初始化 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);

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);

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;
}

其实那三个初始化函数只是简单的包装,是inline函数。所以其实和等时的urb手动初始化没什么大的区别。



提交 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()函数).

linux usb 驱动详解的更多相关文章

  1. 13.Linux键盘驱动 (详解)

    版权声明:本文为博主原创文章,未经博主允许不得转载. 在上一节分析输入子系统内的intput_handler软件处理部分后,接下来我们开始写input_dev驱动 本节目标: 实现键盘驱动,让开发板的 ...

  2. usb驱动---linux ACM驱动详解ACA【转】

    转自:http://blog.chinaunix.net/uid-9185047-id-3404684.html DTE提供或接收数据,连接到网络中的用户端机器,主要是计算机和终端设备.与此相对地,在 ...

  3. Linux设备驱动详解 宋宝华 硬件基础

    处理器 存储器 接口与总线 I2C时序 SPI总线时序 以太网

  4. Linux USB驱动

    linux usb 驱动详解 一 http://blog.163.com/cl2006ky@126/blog/static/87195173201131245557340/ USB设备驱动开发-USB ...

  5. Linux /dev目录详解和Linux系统各个目录的作用

    Linux /dev目录详解(转http://blog.csdn.net/maopig/article/details/7195048) 在linux下,/dev目录是很重要的,各种设备都在下面.下面 ...

  6. Linux串口编程详解(转)

    串口本身,标准和硬件 † 串口是计算机上的串行通讯的物理接口.计算机历史上,串口曾经被广泛用于连接计算机和终端设备和各种外部设备.虽然以太网接口和USB接口也是以一个串行流进行数据传送的,但是串口连接 ...

  7. 25.Linux-Nor Flash驱动(详解)

    1.nor硬件介绍: 从原理图中我们能看到NOR FLASH有地址线,有数据线,它和我们的SDRAM接口相似,能直接读取数据,但是不能像SDRAM直接写入数据,需要有命令才行 1.1其中我们2440的 ...

  8. 16.Linux-LCD驱动(详解)

    在上一节LCD层次分析中,得出写个LCD驱动入口函数,需要以下4步: 1) 分配一个fb_info结构体: framebuffer_alloc(); 2) 设置fb_info 3) 设置硬件相关的操作 ...

  9. 16.Linux-LCD驱动(详解)【转】

    转自:https://www.cnblogs.com/lifexy/p/7604011.html 在上一节LCD层次分析中,得出写个LCD驱动入口函数,需要以下4步: 1) 分配一个fb_info结构 ...

随机推荐

  1. MongoDB GUI管理工具Mongo VUE

    一.前言 现在越来越多的公司开始采用非关系数据库了,并且很多公司的面试都要求面试 者有MongoDB的使用经验,至于非关系数据库与关系型数据库之间的区别大家可以自行百度.但是作为程序员的我们,既然大部 ...

  2. Java异步调用Future对象

    Future类存在于JDK的concurrent包中,主要用途是接收Java的异步线程计算返回的结果. 个人理解的使用场景大概如下: 有两个任务A和B,A任务中仅仅需要使用B任务计算成果,有两种方法实 ...

  3. Horner规则

    霍纳(Horner)规则是采用最少的乘法运算策略,求多项式 A(x) = a[n]x^n + a[n-1]x^(n-1) + ... + a[1]x^1 + a[0]x^0 在x处的值. 该规则为 A ...

  4. Linux NFS服务器搭建

    1.NFS:NFS(Network File System)即网络文件系统,是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源.   在NFS的应用中,本地N ...

  5. poj 3320 Jessica's Reading Problem(尺取法)

    Description Jessica's a very lovely girl wooed by lots of boys. Recently she has a problem. The fina ...

  6. PHP foreach()跳出本次或当前循环与终止循环方法

    PHPforeach()跳出本次或当前循环与终止循环方法 PHP中用foreach()循环中,想要在循环的时候,当满足某个条件时,想 $arr = array('a','b','c','d','e') ...

  7. WPF动画

    System.Windows.Media.Animation 这个命名空间中,包含了三种动画类:线性插值动画类(17个).关键帧动画(22个).路径动画(3个). 线性插值动画类(17个)如下: By ...

  8. DoTween学习笔记(二) UGUI结合使用(实现一些简单效果)

    UGUI官方实例中是使用Animation来控制UI的移动,放大缩小动画等等, Animation来控制UI的动画工作量实在是太多了, 所以我们一般使用itween,DoTween. 来控制动画, 这 ...

  9. [Immutable.js] Transforming Immutable Data with Reduce

    Immutable.js iterables offer the reduce() method, a powerful and often misunderstood functional oper ...

  10. MongoDB学习笔记04

    创建索引使用ensureIndex方法,对于同一个集合,同样的索引只需要创建一次,反复创建是徒劳的. 对某个键的索引会加速对该键的查询,然而,对于其它查询可能没有帮助,即便是查询中包含了被索引的键.实 ...