Linux USB 驱动开发(三)—— 编写USB 驱动程序

    前面学习了USB驱动的一些基础概念与重要的数据结构,那么究竟如何编写一个USB 驱动程序呢?编写与一个USB设备驱动程序的方法和其余总线驱动方式相似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否安装了硬件。固然,这些制造商和设备标识须要咱们编写进USB 驱动程序中。linux

  USB 驱动程序依然遵循设备模型 —— 总线、设备、驱动。和I2C 总线设备驱动编写同样,全部的USB驱动程序都必须建立的主要结构体是 struct usb_driver,它们向USB 核心代码描述了USB 驱动程序。但这是个外壳,只是实现设备和总线的挂接,具体的USB 设备是什么样的,如何实现的,好比一个字符设备,咱们还需填写相应的文件操做接口 ,下面咱们从外到里进行剖析,学习如何搭建这样的一个USB驱动外壳框架:数组


1、注册USB驱动程序数据结构

  Linux的设备驱动,特别是这种hotplug的USB设备驱动,会被编译成模块,而后在须要时挂在到内核。因此USB驱动和注册与正常的模块注册、卸载是同样的,下面是USB驱动的注册与卸载:
static int __init usb_skel_init(void)   
{   
     int result;   
     /* register this driver with the USB subsystem */   
     result = usb_register(&skel_driver);   
     if (result)   
         err("usb_register failed. Error number %d", result);   
  
     return result;   
}   
  
static void __exit usb_skel_exit(void)   
{   
     /* deregister this driver with the USB subsystem */   
     usb_deregister(&skel_driver);   
}   
  
module_init (usb_skel_init);   
module_exit (usb_skel_exit);   
MODULE_LICENSE("GPL");

      USB设备驱动的模块加载函数通用的方法是在I2C设备驱动的模块加载函数中使用usb_register(struct *usb_driver)函数添加usb_driver的工做,而在模块卸载函数中利用usb_deregister(struct *usb_driver)作相反的工做。 对比I2C设备驱动中的 i2c_add_driver(&i2c_driver)i2c_del_driver(&i2c_driver)并发

    struct usb_driver是USB设备驱动,咱们须要实现其成员函数:框架

static struct usb_driver skel_driver = {   
     .owner = THIS_MODULE,    
     .name = "skeleton",  
     .id_table = skel_table,       
     .probe = skel_probe,     
     .disconnect = skel_disconnect,     
};
从代码看来,usb_driver须要初始化五个字段:

模块的全部者 THIS_MODULE
模块的名字  skeleton
probe函数   skel_probe
disconnect函数skel_disconnect

id_table异步

    最重要的固然是probe函数与disconnect函数,这个在后面详细介绍,先谈一下id_table:ide

    id_table 是struct usb_device_id 类型,包含了一列该驱动程序能够支持的全部不一样类型的USB设备。若是没有设置该变量,USB驱动程序中的探测回调该函数将不会被调用。对比I2C中struct i2c_device_id *id_table,一个驱动程序能够对应多个设备,i2c 示例:函数

static const struct i2c_device_id mpu6050_id[] = {    
    { "mpu6050", 0},    
    {}    
};

    usb子系统经过设备的production ID和vendor ID的组合或者设备的class、subclass跟protocol的组合来识别设备,并调用相关的驱动程序做处理。咱们能够看看这个id_table究竟是什么东西:学习

static struct usb_device_id skel_table [] = {     
     { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },     
     { }                    /* Terminating entry */     
};     
    
MODULE_DEVICE_TABLE (usb, skel_table);

   MODULE_DEVICE_TABLE的第一个参数是 设备的类型,若是是USB设备,那天然是usb。后面一个参数是 设备表这个设备表的最后一个元素是空的,用于标识结束。代码定义了USB_SKEL_VENDOR_ID是0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是说,当有一个设备接到集线器时,usb子系统就会检查这个设备的vendor ID和product ID,若是它们的值是0xfff0时,那么子系统就会调用这个skeleton模块做为设备的驱动。

   当USB设备接到USB控制器接口时,usb_core就检测该设备的一些信息,例如生产厂商ID和产品的ID,或者是设备所属的class、subclass跟protocol,以便肯定应该调用哪个驱动处理该设备
  
 this

     咱们下面所要作的就是对probe函数与disconnect函数的填充了,可是在对probe函数与disconnect函数填充以前,有必要先学习三个重要的数据结构,这在咱们后面probe函数与disconnect函数中有很大的做用:

2、USB驱动程序中重要数据结构

一、usb-skeleton

       usb-skeleton 是一个局部结构体,用于与端点进行通讯。下面先看一下Linux内核源码中的一个usb-skeleton(就是usb驱动的骨架咯),其定义的设备结构体就叫作usb-skel:

struct usb_skel {   
     struct usb_device *udev;                 /* the usb device for this device */   
     struct usb_interface  *interface;            /* the interface for this device */   
     struct semaphore limit_sem;         /* limiting the number of writes in progress */   
     unsigned char *bulk_in_buffer;     /* the buffer to receive data */   
     size_t         bulk_in_size;                  /* the size of the receive buffer */   
     __u8          bulk_in_endpointAddr;        /* the address of the bulk in endpoint */   
     __u8          bulk_out_endpointAddr;      /* the address of the bulk out endpoint */   
     struct kref   kref;   
};

他拥有:

描述usb设备的结构体udev
一个接口interface
用于并发访问控制的semaphore(信号量) limit_sem
用于接收数据的缓冲bulk_in_buffer
用于接收数据的缓冲尺寸bulk_in_size
批量输入端口地址bulk_in_endpointAddr
批量输出端口地址bulk_out_endpointAddr

内核使用的引用计数器


     从开发人员的角度看,每个usb设备有若干个配置(configuration)组成,每一个配置又能够有多个接口(interface)(我理解就是USB设备的一项功能),每一个接口又有多个设置,而接口自己可能没有端点或者多个端点(end point)

二、USB 接口数据结构 struct usb_interface

struct usb_interface  
{           
         struct usb_host_interface *altsetting;   
         struct usb_host_interface *cur_altsetting;        
         unsigned num_altsetting;           
         int minor;                        
         enum usb_interface_condition condition;           
         unsigned is_active:1;             
         unsigned needs_remote_wakeup:1;    
         struct device dev;                
         struct device *usb_dev;           
         int pm_usage_cnt;                 
};

       在逻辑上,一个USB设备的功能划分是经过接口来完成的。好比说一个USB扬声器,可能会包括有两个接口:一个用于键盘控制,另一个用于音频流传输。而事实上,这种设备须要用到不一样的两个驱动程序来操做,一个控制键盘,一个控制音频流。但也有例外,好比蓝牙设备,要求有两个接口,第一用于ACL跟EVENT的传输,另一个用于SCO链路,但二者经过一个驱动控制。在Linux上,接口使用struct usb_interface来描述,如下是该结构体中比较重要的字段:

a -- struct usb_host_interface *altsetting(注意不是usb_interface)

       其实据我理解,他应该是每一个接口的设置,虽然名字上有点奇怪。该字段是一个设置的数组(一个接口能够有多个设置),每一个usb_host_interface都包含一套由struct usb_host_endpoint定义的端点配置。但这些配置次序是不定的。

b -- struct usb_host_interface *cur_altsetting

       当前活动的设置,指向altsetting数组中的一个

struct usb_host_interface数据结构:

struct usb_host_interface   
{  
         struct usb_interface_descriptor desc;//usb描述符,主要有四种usb描述符,设备描述符,配置描述符,接口描述符和端点描述符,协议里规定一个usb设备是必须支持这四大描述符的。  
                                 //usb描述符放在usb设备的eeprom里边  
         /* array of desc.bNumEndpoint endpoints associated with this 
          * interface setting. these will be in no particular order. 
          */  
         struct usb_host_endpoint *endpoint;//这个设置所使用的端点  
  
         char *string;           /* iInterface string, if present */  
         unsigned char *extra;   /* Extra descriptors */关于额外描述符  
         int extralen;  
};

c -- unsigned num_altstting

      可选设置的数量,即altsetting所指数组的元素个数

d -- int minor

     当捆绑到该接口的USB驱动程序使用USB主设备号时,USB core分配的次设备号。仅在成功调用usb_register_dev以后才有效。  


三、USB 端点 struct usb_host_endpoint

      Linux中用struct usb_host_endpoint 来描述USB端点

struct usb_host_endpoint   
{  
         struct usb_endpoint_descriptor desc;  
         struct list_head                urb_list;//端点要处理的urb队列.urb是usb通讯的主角,设备中的每一个端点均可以处理一个urb队列.要想和你的usb通讯,就得建立一个urb,而且为它赋好值,  
                                   //交给我们的usb core,它会找到合适的host controller,从而进行具体的数据传输  
         void                            *hcpriv;//这是提供给HCD(host controller driver)用的  
         struct ep_device                *ep_dev;        /* For sysfs info */  
  
         unsigned char *extra;   /* Extra descriptors */  
         int extralen;  
};

     每一个usb_host_endpoint中包含一个struct usb_endpoint_descriptor结构体,当中包含该端点的信息以及设备自定义的各类信息,这些信息包括:

a -- bEndpointAddress(b for byte)

       8位端点地址,其地址还隐藏了端点方向的信息(以前说过,端点是单向的),能够用掩码USB_DIR_OUT和USB_DIR_IN来肯定。

b -- bmAttributes

       端点的类型,结合USB_ENDPOINT_XFERTYPE_MASK能够肯定端点是USB_ENDPOINT_XFER_ISOC(等时)、USB_ENDPOINT_XFER_BULK(批量)仍是USB_ENDPOINT_XFER_INT(中断)。

c -- wMaxPacketSize

       端点一次处理的最大字节数。发送的BULK包能够大于这个数值,但会被分割传送。

d -- bInterval

       若是端点是中断类型,该值是端点的间隔设置,以毫秒为单位


3、探测和断开函数分析

        USB驱动程序指定了两个USB核心在适当时间调用的函数。

一、探测函数

        当一个设备被安装而USB核心认为该驱动程序应该处理时,探测函数被调用;

        探测函数应该检查传递给他的设备信息肯定驱动程序是否真的适合该设备。当驱动程序由于某种缘由不该控制设备时,断开函数被调用,它能够作一些清洁的工做。

      系统会传递给探测函数的信息是什么呢?一个usb_interface * 跟一个struct usb_device_id *做为参数。他们分别是该USB设备的接口描述(通常会是该设备的第0号接口,该接口的默认设置也是第0号设置)跟它的设备ID描述(包括Vendor ID、Production ID等)。

       USB驱动程序应该初始化任何可能用于控制USB设备的局部结构体,它还应该把所需的任何设备相关信息保存到局部结构体中。例如,USB驱动程序一般须要探测设备对的端点地址和缓冲区大小,由于须要他们才能和端点通讯

       下面具体分析探测函数作了哪些事情:

a -- 探测设备的端点地址、缓冲区大小,初始化任何可能用于控制USB设备的数据结构

       下面是一个实例代码,他们探测批量类型的IN和OUT端点,把相关信息保存到一个局部设备结构体中

/* set up the endpoint information */   
     /* use only the first bulk-in and bulk-out endpoints */   
     iface_desc = interface->cur_altsetting;   
     for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {   
         endpoint = &iface_desc->endpoint[i].desc; 
  
         if ( !dev->bulk_in_endpointAddr &&   
                ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&   
             ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {   
             /* we found a bulk in endpoint */   
              buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);   
              dev->bulk_in_size = buffer_size;   
              dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;   
              dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);   
              if (!dev->bulk_in_buffer) {   
                  err("Could not allocate bulk_in_buffer");   
                   goto error;                 
              }
         }   
  
         if (!dev->bulk_out_endpointAddr &&   
            ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)= =USB_DIR_OUT) &&   
               ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)= = USB_ENDPOINT_XFER_BULK)) {   
              /* we found a bulk out endpoint */   
              dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;   
         }   
     }   
  
     if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {   
         err("Could not find both bulk-in and bulk-out endpoints");   
         goto error;   
     }

具体流程以下:   

     该代码块首先循环访问该接口中存在的每个端点,赋予该端点结构体的局部指针以使稍后的访问更加容易

for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {   
         endpoint = &iface_desc->endpoint[i].desc;

         而后,咱们有了一个端点,而尚未发现批量IN类型的端点时,查看该端点的方向是否为IN。这能够经过检查位掩码 USB_DIR_IN 是否包含在bEndpointAddress 端点变量中来肯定。若是是的话,咱们测定该端点类型是否批量,这首先经过USB_ENDPOINT_XFERTYPE_MASK 位掩码来取bmAttributes变量的值,而后检查它是否和USB_ENDPOINT_XFER_BULK 的值匹配来完成

if ( !dev->bulk_in_endpointAddr &&   
                ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&   
             ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {

       若是这些都经过了,驱动程序就知道它已经发现了正确的端点类型,能够把该端点相关的信息保存到一个局部结构体中,就是咱们前面的usb_skel ,以便稍后使用它和端点进行通讯

/* we found a bulk in endpoint */   
              buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);   
              dev->bulk_in_size = buffer_size;   
              dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;   
              dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);   
              if (!dev->bulk_in_buffer) {   
                  err("Could not allocate bulk_in_buffer");   
                   goto error;               
	      }


b -- 把已经初始化数据结构的指针保存到接口设备中

      接下来的工做是向系统注册一些之后会用的的信息。首先咱们来讲明一下usb_set_intfdata()他向内核注册一个data,这个data的结构能够是任意的,这段程序向内核注册了一个usb_skel结构,就是咱们刚刚看到的被初始化的那个,这个data能够在之后用usb_get_intfdata来获得

usb_set_intfdata(interface, dev);


c -- 注册USB设备

       若是USB驱动程序没有和处理设备与用户交互(例如输入、tty、视频等)的另外一种类型的子系统相关联,驱动程序可使用USB主设备号,以便在用户空间使用传统的字符驱动程序接口。若是要这样作,USB驱动程序必须在探测函数中调用 usb_resgister_dev 函数来把设备注册到USB核心。只要该函数被调用,就要确保设备和驱动陈旭都处于能够处理用户访问设备的要求的恰当状态

retval = usb_register_dev(interface, &skel_class);

skel_class结构。这个结构又是什么?咱们就来看看这究竟是个什么东西:

static struct usb_class_driver skel_class = {   
     .name =       "skel%d",   
     .fops =       &skel_fops,   
     .minor_base = USB_SKEL_MINOR_BASE,   
};

    它实际上是一个系统定义的结构,里面包含了一名字、一个文件操做结构体还有一个次设备号的基准值。事实上它才是定义真正完成对设备IO操做的函数。因此他的核心内容应该是skel_fops。

   由于usb设备能够有多个interface,每一个interface所定义的IO操做可能不同,因此向系统注册的usb_class_driver要求注册到某一个interface,而不是device,所以,usb_register_dev的第一个参数才是interface,而第二个参数就是某一个usb_class_driver。

   一般状况下,linux系统用主设备号来识别某类设备的驱动程序,用次设备号管理识别具体的设备,驱动程序能够依照次设备号来区分不一样的设备,因此,这里的次设备好实际上是用来管理不一样的interface的,但因为这个范例只有一个interface,在代码上没法求证这个猜测。

static struct file_operations skel_fops = {   
     .owner = THIS_MODULE,   
     .read =       skel_read,   
     .write =   skel_write,   
     .open =       skel_open,   
     .release =    skel_release,   
};


二、断开函数

      当设备被拔出集线器时,usb子系统会自动地调用disconnect,他作的事情很少,最重要的是注销class_driver(交还次设备号)和interface的data:

dev = usb_get_intfdata(interface);  
usb_set_intfdata(interface, NULL);  
  
/* give back our minor */  
usb_deregister_dev(interface, &skel_class);


4、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
{
	/* 私有的:只能由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一次可定义多个等时传输时,描述各个等时传输 */
};


一、建立和注销 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 指针


二、初始化 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;
}


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