linux usb驱动详解

来源:互联网 发布:云主机绑定域名 编辑:程序博客网 时间:2024/05/23 00:00
1 数据结构说明
1.1
struct usb_device_id {
        /* which fields to match against? */
        __u16                match_flags;


        /* Used for product specific matches; range is inclusive */
        __u16                idVendor;
        __u16                idProduct;
        __u16                bcdDevice_lo;
        __u16                bcdDevice_hi;


        /* Used for device class matches */
        __u8                bDeviceClass;
        __u8                bDeviceSubClass;
        __u8                bDeviceProtocol;


        /* Used for interface class matches */
        __u8                bInterfaceClass;
        __u8                bInterfaceSubClass;
        __u8                bInterfaceProtocol;


        /* Used for vendor-specific interface matches */
        __u8                bInterfaceNumber;


        /* not matched against */
        kernel_ulong_t        driver_info
                __attribute__((aligned(sizeof(kernel_ulong_t))));
};
成员变量说明:
match_flags :需要匹配的类型(可以多个或起来),数据定义如下:
        #define USB_DEVICE_ID_MATCH_VENDOR                0x0001 //匹配PID
        #define USB_DEVICE_ID_MATCH_PRODUCT                0x0002 //匹配VID
        #define USB_DEVICE_ID_MATCH_DEV_LO                0x0004 //匹配产品编号低位
        #define USB_DEVICE_ID_MATCH_DEV_HI                0x0008 //匹配产品编号高位
        #define USB_DEVICE_ID_MATCH_DEV_CLASS                0x0010 //匹配设备描述符的class
        #define USB_DEVICE_ID_MATCH_DEV_SUBCLASS        0x0020 //匹配设备描述符的subclass
        #define USB_DEVICE_ID_MATCH_DEV_PROTOCOL        0x0040 //匹配设备描述符的protocol
        #define USB_DEVICE_ID_MATCH_INT_CLASS                0x0080 //匹配接口描述符的class
        #define USB_DEVICE_ID_MATCH_INT_SUBCLASS        0x0100 //匹配接口描述符的subclass
        #define USB_DEVICE_ID_MATCH_INT_PROTOCOL        0x0200 //匹配接口描述符的protocol
        #define USB_DEVICE_ID_MATCH_INT_NUMBER                0x0400 //匹配接口的数量
driver_info :用来保存用户的私有数据


1.2 
struct usb_driver {
        const char *name;


        int (*probe) (struct usb_interface *intf,
                      const struct usb_device_id *id);


        void (*disconnect) (struct usb_interface *intf);


        int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code,
                        void *buf);


        int (*suspend) (struct usb_interface *intf, pm_message_t message);
        int (*resume) (struct usb_interface *intf);
        int (*reset_resume)(struct usb_interface *intf);


        int (*pre_reset)(struct usb_interface *intf);
        int (*post_reset)(struct usb_interface *intf);


        const struct usb_device_id *id_table;


        struct usb_dynids dynids;
        struct usbdrv_wrap drvwrap;
        unsigned int no_dynamic_id:1;
        unsigned int supports_autosuspend:1;
        unsigned int disable_hub_initiated_lpm:1;
        unsigned int soft_unbind:1;
};
name:独一无二的设备的名字
probe:回调函数指针,在USB设备插入,且符合struct usb_device_id 定义的匹配条件是被调用。这是如果匹配成功返回0,
disconnect:回调函数指针,当设备拔出时被调用
unlocked_ioctl:
suspend : 设备休眠的时候被调用
resume:设备唤醒的时候被调用
reset_resume:设备在休眠的时候已经reset,唤醒的时候调用
pre_reset:在调用usb_reset_device()的时候这个回调函数被调用,这个函数在reset前调用,该函数必须等到没有有效的URBs才能返回。
post_reset:在调用usb_reset_device()的时候这个回调函数被调用,这个函数在reset完成后调用
id_table:需要匹配的设备信息
dynids:用来保持动态添加的设备列表
drvwrap:Driver-model core structure wrapper
no_dynamic_id:如果设置为1,内核将不允许添加动态设备列表
supports_autosuspend:如果设置为0 内核将不允许设备自动休眠,不知道为什么这个参数设置为0,有时会造成设备访问被拒绝
soft_unbind:如果设置为1 ,在设备拔出前,不允许禁用端点,杀掉URBs
disable_hub_initiated_lpm:
注意:这个结构必须要实现name、probe、disconnect、id_table,其他成员可以根据情况选择是否实现


1.3
struct usb_class_driver {
        char *name;
        char *(*devnode)(struct device *dev, umode_t *mode);
        const struct file_operations *fops;
        int minor_base;
};
name:设备节点的名称,也就是在dev目录下的设备,可以直接Open这个设备进行读写
devnode:??
fops:设备文件操作结构
minor_base:次设备号的基准值,


1.4
struct urb {
        /* private: usb core and host controller only fields in the urb */
        struct kref kref;                /* reference count of the URB */
        void *hcpriv;                        /* private data for host controller */
        atomic_t use_count;                /* concurrent submissions counter */
        atomic_t reject;                /* submissions will fail */
        int unlinked;                        /* unlink error code */


        /* 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;                /* (in) pointer to associated device */
        struct usb_host_endpoint *ep;        /* (internal) pointer to endpoint */
        unsigned int pipe;                /* (in) pipe information */
        unsigned int stream_id;                /* (in) stream ID */
        int status;                        /* (return) non-ISO status */
        unsigned int transfer_flags;        /* (in) URB_SHORT_NOT_OK | ...*/
        void *transfer_buffer;                /* (in) associated data buffer */
        dma_addr_t transfer_dma;        /* (in) dma addr for transfer_buffer */
        struct scatterlist *sg;                /* (in) scatter gather buffer list */
        int num_mapped_sgs;                /* (internal) mapped sg entries */
        int num_sgs;                        /* (in) number of entries in the sg list */
        u32 transfer_buffer_length;        /* (in) data buffer length */
        u32 actual_length;                /* (return) actual transfer length */
        unsigned char *setup_packet;        /* (in) setup packet (control only) */
        dma_addr_t setup_dma;                /* (in) dma addr for setup_packet */
        int start_frame;                /* (modify) start frame (ISO) */
        int number_of_packets;                /* (in) number of ISO packets */
        int interval;                        /* (modify) transfer interval
                                         * (INT/ISO) */
        int error_count;                /* (return) number of ISO errors */
        void *context;                        /* (in) context for completion */
        usb_complete_t complete;        /* (in) completion routine */
        struct usb_iso_packet_descriptor iso_frame_desc[0];
                                        /* (in) ISO ONLY */
};
以下的成员变量是私有的,只能由usb核心和主机控制器访问的字段 
        kref : urb引用计数 ,当计数引用为0时,系统释放urb
        hcpriv : 主机控制器私有数据 
        use_count :   并发传输计数  
        reject : 传输将失败


以下的成员变量是公共的,可以被驱动使用的字段 
        urb_list :链表头
        dev :关联的usb设备 
        pipe :端点消息,这个变量必须在这个 urb 被发送到 USB 核心之前被 USB 驱动初始化.
               要设置这个结构的成员, 驱动可以使用下面的函数来设置. 注意每个端点只可以是一个类型.
                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 设备 
        status : urb的当前状态 
                0        urb 传送是成功的.
                -ENOENT  这个 urb 被 usb_kill_urb调用停止.
                -ECONNRESET urb 被usb_unlink_urb 调用去链, 并且 transfer_flags 变量被设置为 URB_ASYNC_UNLINK.
                -EINPROGRESS  这个 urb 仍然在被 USB 主机控制器处理中. 如果你的驱动曾见到这个值, 它是一个你的驱动中的 bug.
                -EPROTO       一个 bitstuff 错误在传送中发生. 硬件没有及时收到响应帧.
                 -EILSEQ       在这个 urb 传送中 CRC 不匹配.
                -EPIPE        这个端点现在被停止. 如果这个包含的端点不是一个控制端点, 这个错误可被清除通过一个对函数 usb_clear_halt 的调用.
                -ECOMM        在传送中数据接收快于能被写入系统内存. 这个错误值只对 IN urb.
                -ENOSR        在传送中数据不能从系统内存中获取得足够快, 以便可跟上请求的 USB 数据速率. 这个错误只对 OUT urb.
                -EOVERFLOW    这个 urb 发生一个"babble"错误. 一个"babble"错误发生当端点接受数据多于端点的特定最大报文大小.
                -EREMOTEIO    只发生在当 URB_SHORT_NOT_OK 标志被设置在 urb 的 transfer_flags 变量, 并且意味着 urb 请求的完整数量的数据没有收到.
                -ENODEV       这个 USB 设备现在从系统中消失.
                -EXDEV        只对同步 urb 发生, 并且意味着传送只部分完成. 为了决定传送什么, 驱动必须看单独的帧状态.
                -EINVAL       这个 urb 发生了非常坏的事情. USB 内核文档描述了这个值意味着什么:ISO 疯了, 如果发生这个: 退出并回家.
                      它也可发生, 如果一个参数在 urb 结构中被不正确地设置了, 或者如果在提交这个 urb 给 USB 核心的 usb_submit_urb 调用中, 有一个不正确的函数参数.
                -ESHUTDOWN    这个 USB 主机控制器驱动有严重的错误; 它现在已被禁止, 或者设备和系统去掉连接, 并且这个urb 在设备被去除后被提交. 它也可发生当这个设备的配置改变, 而这个 urb 被提交给设备.
                通常, 错误值 -EPROTO, -EILSEQ, 和 -EOVERFLOW 指示设备的硬件问题, 设备固件, 或者连接设备到计算机的线缆.
                
        transfer_flags:这个变量可被设置为不同位值, 根据这个 USB 驱动想这个 urb 发生什么. 可用的值如下:
                URB_SHORT_NOT_OK          当置位, 它指出任何在一个 IN 端点上可能发生的短读, 应当被 USB 核心当作一个错误. 这个值只对从 USB 设备读的 urb 有用, 不是写 urbs.
                URB_ISO_ASAP              如果这个 urb 是同步的, 这个位可被置位如果驱动想这个 urb 被调度, 只要带宽允许它这样, 并且在此点设置这个 urb 中的 start_frame 变量. 如果对于同步 urb 这个位没有被置位, 驱动必须指定 start_frame 值并且必须能够正确恢复, 如果没有在那个时刻启动. 见下面的章节关于同步 urb 更多的消息.
                URB_NO_TRANSFER_DMA_MAP   应当被置位, 当 urb 包含一个要被发送的 DMA 缓冲. USB 核心使用这个被 transfer_dma 变量指向的缓冲, 不是被 transfer_buffer 变量指向的缓冲.
                URB_NO_SETUP_DMA_MAP      象 URB_NO_TRANSFER_DMA_MAP 位, 这个位用来控制有一个 DMA 缓冲已经建立的 urb. 如果它被置位, USB 核心使用这个被 setup_dma 变量而不是 setup_packet 变量指向的缓冲.
                URB_ASYNC_UNLINK          如果置位, 给这个 urb 的对 usb_unlink_urb 的调用几乎立刻返回, 并且这个 urb 在后面被解除连接. 否则, 这个函数等待直到 urb 完全被去链并且在返回前结束. 小心使用这个位, 因为它可有非常难于调试的同步问题.
                URB_NO_FSBR               只有 UHCI USB 主机控制器驱动使用, 并且告诉它不要试图做 Front Side Bus Reclamation 逻辑. 这个位通常应当不设置, 因为有 UHCI 主机控制器的机器创建了许多 CPU 负担, 并且 PCI 总线被等待设置了这个位的 urb 所饱和.
                URB_ZERO_PACKET           如果置位, 一个块 OUT urb 通过发送不包含数据的短报文而结束, 当数据对齐到一个端点报文边界. 这被一些坏掉的 USB 设备所需要(例如一些 USB 到 IR 的设备) 为了正确的工作..
                URB_NO_INTERRUPT          如果置位, 硬件当 urb 结束时可能不产生一个中断. 这个位应当小心使用并且只在排队多个到相同端点的 urb 时使用. USB 核心函数使用这个为了做 DMA 缓冲传送.
        transfer_buffer : 发送数据到设备或从设备接收数据的缓冲区,这个缓冲区它必须被使用 kmalloc来创建, 不是在堆栈或者静态地址,所以不能定义一个大数组来作为缓冲区.
        transfer_dma : 用来以dma方式向设备传输数据的缓冲区 
        transfer_buffer_length : transfer_buffer或transfer_dma 指向缓冲区的大小 
        actual_length : urb结束后,发送或接收数据的实际长度 
        setup_packet : 指向控制urb的设置数据包的指针
        setup_dma :控制urb的设置数据包的dma缓冲区
        start_frame :等时传输中用于设置或返回初始帧
        number_of_packets :只对同步 urb 有效, 并且指定这个 urb 要处理的同步传送缓冲的编号. 这个值必须被 USB 驱动设置给同步 urb, 在这个 urb 发送给 USB 核心之前.
        interval : urb被轮询到的时间间隔(对中断和等时urb有效) 
        error_count : 等时传输错误数量 
        context :completion函数上下文 ,用来保存用户数据的指针
        complete : 回调函数指针,当urb被完全传输或发生错误时,被调用 
                usb_complete_t 类型定义 :typedef void (*usb_complete_t)(struct urb *, struct pt_regs *);
        iso_frame_desc[0] : 单个urb一次可定义多个等时传输时,描述各个等时传输 
        
        注意:urb 不能静态创建, 或者在另一个结构中, 必须使用 usb_alloc_urb 函数来创建,使用usb_free_urb来释放 。
        
2 函数说明


        int usb_register_driver(struct usb_driver *new_driver, struct module *owner,        const char *mod_name)
        函数功能:注册一个USB驱动
        参数说明:new_driver :struct usb_driver的结构指针 
                  owner      :模块的所有者名
                  mod_name   :模块名称
        返回值:0 成功 非0 失败
        说明 :这个函数通常在驱动的__init函数中调用注册一个USB驱动。为简化调用的参数,我们通常直接用 usb_register这个宏
               usb_register定义如下
               #define usb_register(driver)         usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)        
         
               
        void usb_deregister(struct usb_driver * driver);
        函数功能:注销一个USB驱动,这个函数通常在驱动的__exit函数中调用
        参数说明:driver : struct usb_driver的结构指针 
        返回值:无
        
        
        int usb_register_dev(struct usb_interface *intf, struct usb_class_driver *class_driver)
        函数功能:注册一个USB设备,注册成功后可以在/dev目录下看到相应的设备节点,这个函数通常是在struct usb_driver中定义的probe回调函数中被调用
        参数说明:intf :USB接口描述符
                  class_driver : struct usb_class_driver 这个结构将指定设备的名称和  file_operations结构
        返回值:0成功 非0 失败 
        
        void usb_deregister_dev(struct usb_interface *intf, struct usb_class_driver *class_driver)
        函数功能: 注销一个USB设备,这个函数通常是在struct usb_driver中定义的disconnect回调函数中被调用
        参数说明:intf :USB接口描述符
                  class_driver : struct usb_class_driver 这个结构将指定设备的名称和  file_operations结构
        返回值: 无
        
        struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
        函数功能:动态分配一个urb,并初始化
        参数说明:iso_packets : usb_iso_packet_descriptor的个数,这个参数只对同步传输有效。对于控制、中断、块传输,这个参数必须为0
                  mem_flags : 和kmalloc 函数调用来从内核分配内存的标志类型相同
        返回值:成功返回分配的urb的指针,失败返回NULL
        
        void usb_free_urb(struct urb *urb)
        函数功能:释放usb_alloc_urb分配的urb
        参数说明:urb : 要释放的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_fn, void *context)
        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)
        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)
        函数功能:用来填充用于块传输、中断传输、控制传输的urb
        参数说明:Urb:是要初始化的urb
                  Dev:表示消息要被发送到的USB设备
                  Pipe:表示消息被发送到的端点
                  transfer_buffer:表示发送或接收数据的缓冲区
                  length:就是transfer_buffer所表示的缓冲区大小
                  context:完成处理函数的上下文
                  complete_fn:传输完了之后的回调函数.
                  interval:中断传输被调度的间隔
                  setup_packet:将被发送到端点的设置数据包
        返回值:无
        
        int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
        函数功能:用来递交urb,该函数以非阻塞的方式发送,也就是这个函数将不等发送完成就返回。
        参数说明:urb :指向urb的指针
                  mem_flags :可以有下面三种取值
                              GFP_ATOMIC:在中断处理函数、底半部、tasklet、定时器处理函数以及urb完成函数中,在调用者持有自旋锁或者读写锁时以及当驱动将current->state修改为非 TASK_ RUNNING时,应使用此标志。
                                GFP_NOIO:在存储设备的块I/O和错误处理路径中,应使用此标志;
                              GFP_KERNEL:如果没有任何理由使用GFP_ATOMIC和GFP_NOIO,就使用GFP_ KERNEL。
        返回值:0成功 非0 失败
        
        void usb_kill_urb(struct urb *urb)
        函数功能:取消一个urb传输,并且等待他结束
        参数说明:要取消的urb的指针
        返回值:无
        
        int usb_unlink_urb(struct urb *urb)
        函数功能:停止 urb传输. 这个函数 不等待这个 urb 完全停止就返回
        参数说明:要取消的urb的指针
        返回值:无
        
        int usb_control_msg(struct usb_device *dev, unsigned int pipe, u8 request, u8 requesttype, u16 value, u16 index, void *data, u16 size, int timeout)
        函数功能:以阻塞的方式进行控制传输,直到超时或urb传输完成函数才返回
        参数说明:Dev:表示消息要被发送到的USB设备
                  Pipe:表示消息被发送到的端点
                  request:命令的序号(其实就是命令)
                  requesttype:D7=0主机到设备, =1设备到主机;
                            D6D5 =00标准请求命令, 01 类请求命令,10用户定义的命令,11保留值
                            D4D3D2D1D0= 0表示接收者为设备,1表示接收者为接口,2表示接收者为端点,3表示接收者为其他
              value:2个字节,用来传送当前请求的参数,随请求不同而变。
              index:索引字段同样是2个字节,描述的是接口号 
              data :数据缓冲区的指针
              size:数据缓冲区的长度
              timeout:超时时间   
    返回值:0成功,非0 失败           
        
        int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout)        
        int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout)
        函数功能:以阻塞的方式进行中断或者块传输,直到超时或urb传输完成函数才返回
        参数说明:Dev:表示消息要被发送到的USB设备
                  Pipe:表示消息被发送到的端点             
              data :数据缓冲区的指针
              len :数据缓冲区的长度
              actual_length :实际发送或者接收的数据长度
              timeout:超时时间   
    返回值:0成功,非0 失败  
    
    void usb_anchor_urb(struct urb *urb, struct usb_anchor *anchor)
    函数功能:当urb在处理的时候锁定该urb ,urb被使用时是不能被删除的。在删除一个urb时需要调用usb_wait_anchor_empty_timeout来等待urb传输完全结束
    参数说明:urb:要锁定的urb
              anchor:   
        返回值:无
        
        void usb_unanchor_urb(struct urb *urb) 
        函数功能:解锁一个urb
    参数说明:urb:要解锁的urb 
        返回值:无
        
        int usb_wait_anchor_empty_timeout(struct usb_anchor *anchor,unsigned int timeout)
        函数功能:等待urb传输完全结束
    参数说明:urb:要等待的urb
              anchor: 
              timeout :超时设置  
        返回值:0传输完成,非0 超时
        
        void usb_kill_anchored_urbs(struct usb_anchor *anchor)
        函数功能:撤销一个urb传输
        参数说明:anchor:
        
        
3注册一个USB驱动的流程
        1 定义一个 struct usb_device_id的结构。
        2 定义一个 struct usb_driver,并实现probe和disconnect函数
        3 在模块的init函数中调用usb_register函数注册一个驱动
        4 定义一个usb_class_driver 
        5 在probe函数中调用usb_register_dev注册一个USB设备,这样在dev目录下就可以看到我们的USB设备了
        
下面是示例代码:
。。。。
static const struct file_operations usbdev_fops = {
        .owner =        THIS_MODULE,
        .read =                usbdev_read,
        .write =        usbdev_write,
        .open =                usbdev_open,
        .release =        usbdev_release,
        .flush =        usbdev_flush,
        .llseek =        noop_llseek,
};
static struct usb_class_driver usbdev_class = {
        .name =                "USBTestDevice%d",//这里一次插上多个USB时候,将在dev目录下看到USBTestDevice0、USBTestDevice1 ....
        .fops =                &usbdev_fops,
        .minor_base =        USB_DEV_MINOR_BASE,
};
static int usbdev_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
        ......
        retval = usb_register_dev(interface, &usbdev_class);
        ......
}
static void usbdev_disconnect(struct usb_interface *interface)
{
        ................
        usb_deregister_dev(interface, &usbdev_class);
        ................
}
static const struct usb_device_id usbdev_table[] = {
        { USB_DEVICE(USB_MATCH_VENDOR_ID, USB_MATCH_PRODUCT_ID) },
        { USB_DEVICE(USB_MATCH_VENDOR_ID, USB_MATCH_PRODUCT_ID_1) },
        { }                                        /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, usbdev_table);
static struct usb_driver usbdev_driver = {
        .name =                "usbtest",
        .probe =        usbdev_probe,
        .disconnect =        usbdev_disconnect,
        .id_table =        usbdev_table,
};
static int __init usbdev_init(void)
{
         
        return usb_register(&usbdev_driver);
         
}
module_init(usbdev_init);
static void __exit usbdev_exit(void)
{
        usb_deregister(&usbdev_driver);
}
module_exit(usbdev_exit);




4 urb的操作流程:
  1 调用usb_alloc_urb分配一个urb
  2 调用kmalloc分配缓冲区
  3 调用usb_fill_XXX_urb初始化urb
  4 调用usb_submit_urb提交
  5 在回调函数(complete)中处理,接收的数据,判断是否还要继续接收,如果还要接收,重复第三步。
    如果是发送数据,判断是否还有数据要发送,填充数据
  6 如果传输完成调用usb_free_urb释放urb,释放缓冲区
  
  下面是示例代码
  //回调函数
static void async_ctrl_callback(struct urb *urb)
{
        struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context;
        int status = urb->status;


        if (status < 0)
        {
                printk("error\n");
        }
                 
        kfree(req);
        kfree(urb->transfer_buffer);
        usb_free_urb(urb);
}
        struct urb *urb;
        struct usb_ctrlrequest *req;
        void *data;
        urb = usb_alloc_urb(0, GFP_KERNEL);
        req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC);
        /*根据协议设置reg*/
        req->bRequestType = RequestType;
        req->bRequest = Request;
        req->wValue = Value;
        req->wIndex = Index;
        req->wLength = Length;
        data = kmalloc(Length, GFP_KERNEL);
         
        usb_fill_control_urb(urb, dev, usb_sndctrlpipe(dev, 0),reg, data, Length,  usb_ctrl_complete, req);
        usb_submit_urb(urb, GFP_ATOMIC);
        
        对于其他的传输,方法差不多。
        最后需要注意的是中断传输接收,不需要每次调用usb_submit_urb,调用一次usb_submit_urb之后,系统会按照指定的间隔,不断接收数据的。
        
5 下面是完成的驱动的示例代码




#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kref.h>
#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/mutex.h>
#include <linux/moduleparam.h>
#define BUF_PER_PAGE 10240
#define MAX_PAGE 5
   
typedef struct
{
        volatile int iReadIndex;
        volatile int iWriteIndex;
        unsigned char *PackgeBuf[MAX_PAGE];//[BUF_PER_PAGE];
        int iLen[MAX_PAGE];
}cachebuf  ;


 
/* Define these values to match your devices */
#define USB_MATCH_VENDOR_ID            0x1234
#define USB_MATCH_PRODUCT_ID        0x5678
 
/* table of devices that work with this driver */
static const struct usb_device_id usbdev_table[] = {
        { USB_DEVICE(USB_MATCH_VENDOR_ID, USB_MATCH_PRODUCT_ID) },
        { }                                        /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, usbdev_table);




/* Get a minor range for your devices from the usb maintainer */
#define USB_DEV_MINOR_BASE        192


/* our private defines. if this grows any larger, use your own .h file */
#define MAX_TRANSFER                (PAGE_SIZE - 512)
/* MAX_TRANSFER is chosen so that the VM is not stressed by
   allocations > PAGE_SIZE and the number of packets in a page
   is an integer 512 is the largest possible packet on EHCI */
#define WRITES_IN_FLIGHT        8
/* arbitrarily chosen */


/* Structure to hold all of our device specific stuff */
struct usb_devinfo {
        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 */
        struct usb_anchor        submitted;                /* in case we need to retract our submissions */
        struct urb                *bulk_in_urb;                /* the urb to read data with */
        unsigned char           *bulk_in_buffer;        /* the buffer to receive data */
        size_t                        bulk_in_size;                /* the size of the receive buffer */
        size_t                        bulk_in_filled;                /* number of bytes in the buffer */
        size_t                        bulk_in_copied;                /* already copied to user space */
        __u8                        bulk_in_endpointAddr;        /* the address of the bulk in endpoint */
        __u8                        bulk_out_endpointAddr;        /* the address of the bulk out endpoint */
        int                        errors;                        /* the last request tanked */


        spinlock_t                err_lock;                /* lock for errors */
        struct kref                kref;
        struct mutex                io_mutex;                /* synchronize I/O with disconnect */


        cachebuf UsbBuffer; 
        wait_queue_head_t   wqReadOK; /* buf is not empty*/
        struct work_struct work;
        struct mutex                read_mutex;
        int isOpen;
};
 


static struct usb_driver usbdev_driver;
static void usbdev_emptyurb(struct usb_devinfo *dev);
static int devusb_do_read_io(struct usb_devinfo *dev, size_t count);
 
static void work_func(struct work_struct *work)
{
        struct usb_devinfo *dev = container_of(work, struct usb_devinfo, work);
         
        if(dev->isOpen==0)
        {
                return ;
        }
        
        
        if(dev->bulk_in_filled>0)
        {
                int iWriteIndex = dev->UsbBuffer.iWriteIndex;
                mutex_lock(&dev->read_mutex); 
                //memcpy(dev->UsbBuffer.PackgeBuf[iWriteIndex],dev->bulk_in_buffer  ,dev->bulk_in_filled);
                 
                dev->UsbBuffer.iLen[iWriteIndex] = dev->bulk_in_filled;
                mutex_unlock(&dev->read_mutex); 
                iWriteIndex++;
                if(iWriteIndex>=MAX_PAGE)
                {
                        iWriteIndex = 0;
                }
                while(iWriteIndex ==dev->UsbBuffer.iReadIndex)
                {
                        if(iRetry==0)
                                printk(KERN_ALERT"usbdev_overflow(%d %d) \n",dev->UsbBuffer.iReadIndex,dev->UsbBuffer.iWriteIndex);
                         
                         
                }
                mutex_lock(&dev->read_mutex); 
                dev->UsbBuffer.iWriteIndex = iWriteIndex;
                mutex_unlock(&dev->read_mutex); 
                dev->bulk_in_filled = 0;
                //printk("write w=%d r=%d \n",dev->UsbBuffer.iWriteIndex,dev->UsbBuffer.iReadIndex);
                
                devusb_do_read_io(dev,BUF_PER_PAGE);
                wake_up_interruptible(&dev->wqReadOK);         
        }
         
         
}
static void usbdev_delete(struct kref *kref)
{
        struct usb_devinfo *dev = container_of(kref, struct usb_devinfo, kref);
        int i; 


        usb_free_urb(dev->bulk_in_urb);
        usb_put_dev(dev->udev);
        kfree(dev->bulk_in_buffer);
        for(i=0;i<MAX_PAGE;i++)
        {
                kfree(dev->UsbBuffer.PackgeBuf[i]);
        }
        kfree(dev);
        
        
}


static int usbdev_open(struct inode *inode, struct file *file)
{
        struct usb_devinfo *dev;
        struct usb_interface *interface;
        int subminor;
        int retval = 0;
        printk(KERN_ALERT"usbdev_open \n");
        subminor = iminor(inode);


        interface = usb_find_interface(&usbdev_driver, subminor);
        if (!interface) {
                pr_err("%s - error, can't find device for minor %d\n",
                        __func__, subminor);
                retval = -ENODEV;
                goto exit;
        }


        dev = usb_get_intfdata(interface);
        if (!dev) {
                retval = -ENODEV;
                goto exit;
        }


        retval = usb_autopm_get_interface(interface);
        if (retval)
                goto exit;


        /* increment our usage count for the device */
        kref_get(&dev->kref);
         
        
        /* save our object in the file's private structure */
        file->private_data = dev;
        dev->UsbBuffer.iReadIndex=dev->UsbBuffer.iWriteIndex=0;
        devusb_do_read_io(dev,BUF_PER_PAGE);
        dev->isOpen = 1 ;
exit:
        return retval;
}


static int usbdev_release(struct inode *inode, struct file *file)
{
        struct usb_devinfo *dev;
         
        dev = file->private_data;
        if (dev == NULL)
                return -ENODEV;


        /* allow the device to be autosuspended */
        mutex_lock(&dev->io_mutex);
        usbdev_emptyurb(dev);
        if (dev->interface)
                usb_autopm_put_interface(dev->interface);
        mutex_unlock(&dev->io_mutex);
        dev->isOpen = 0 ; 
        wake_up_interruptible(&dev->wqReadOK);         
        /* decrement the count on our device */
        kref_put(&dev->kref, usbdev_delete);
        return 0;
}


static int usbdev_flush(struct file *file, fl_owner_t id)
{
        struct usb_devinfo *dev;
        int res;


        dev = file->private_data;
        if (dev == NULL)
                return -ENODEV;


        /* wait for io to stop */
        mutex_lock(&dev->io_mutex);
        usbdev_emptyurb(dev);


        /* read out errors, leave subsequent opens a clean slate */
        spin_lock_irq(&dev->err_lock);
        res = dev->errors ? (dev->errors == -EPIPE ? -EPIPE : -EIO) : 0;
        dev->errors = 0;
        spin_unlock_irq(&dev->err_lock);


        mutex_unlock(&dev->io_mutex);


        return res;
}


static void usbdev_read_callback(struct urb *urb)
{
        struct usb_devinfo *dev;


        dev = urb->context;
        //printk(KERN_ALERT"usbdev_read_callback\n");
        spin_lock(&dev->err_lock);
        /* sync/async unlink faults aren't errors */
        if (urb->status) {
                if (!(urb->status == -ENOENT ||
                    urb->status == -ECONNRESET ||
                    urb->status == -ESHUTDOWN))
                        dev_err(&dev->interface->dev,
                                "%s - nonzero write bulk status received: %d\n",
                                __func__, urb->status);


                dev->errors = urb->status;
        } else {
                dev->bulk_in_filled = urb->actual_length;
                if(dev->isOpen==1)
                {
                        schedule_work(&(dev->work));
                }
                 
                
        }


        spin_unlock(&dev->err_lock);
        


}


static int devusb_do_read_io(struct usb_devinfo *dev, size_t count)
{
        int rv;
 
        /* prepare a read */
        usb_fill_bulk_urb(dev->bulk_in_urb,
                        dev->udev,
                        usb_rcvbulkpipe(dev->udev,
                                dev->bulk_in_endpointAddr),
                        dev->UsbBuffer.PackgeBuf[dev->UsbBuffer.iWriteIndex],//dev->bulk_in_buffer,//
                        min(dev->bulk_in_size, count),
                        usbdev_read_callback,
                        dev);
 
        /* tell everybody to leave the URB alone */
        //spin_lock_irq(&dev->err_lock);


        //spin_unlock_irq(&dev->err_lock);


        /* submit bulk in urb, which means no data to deliver */
        dev->bulk_in_filled = 0;
        dev->bulk_in_copied = 0;


        /* do it */
        rv = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL);
        if (rv < 0) {
                //dev_err(&dev->interface->dev,
                //        "%s - failed submitting read urb, error %d\n",
                //        __func__, rv);
                rv = (rv == -ENOMEM) ? rv : -EIO;
                spin_lock_irq(&dev->err_lock);


                spin_unlock_irq(&dev->err_lock);
        }
        //printk(KERN_ALERT"devusb_do_read_io\n");
        return rv;
}


static ssize_t usbdev_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
{
        struct usb_devinfo *dev;
         
         
        int copyed=0;
        dev = file->private_data;
        if(dev->isOpen != 1 )
        {
                //printk(KERN_ALERT"close read err\n");
                return -1;
        }
        if ( !count)
                return 0;
        if(dev->UsbBuffer.iReadIndex==dev->UsbBuffer.iWriteIndex)
        {
                if (file->f_flags & O_NONBLOCK) 
                {
                        return copyed;
                }
                else
                {
                        
                        wait_event_interruptible(dev->wqReadOK, (dev->UsbBuffer.iReadIndex!=dev->UsbBuffer.iWriteIndex)||(dev->isOpen != 1 ) );
                }
        }
        if(dev->isOpen != 1 )
        {
                //printk(KERN_ALERT"close read err1\n");
                return -1;
        } 
        //下一步加锁
        mutex_lock(&dev->read_mutex); 
        while( dev->UsbBuffer.iReadIndex!=dev->UsbBuffer.iWriteIndex )
        {
                int iLen = dev->UsbBuffer.iLen[dev->UsbBuffer.iReadIndex];
                if( count>=iLen )
                {
                         
                         
                        if(copy_to_user(buffer+copyed, &dev->UsbBuffer.PackgeBuf[dev->UsbBuffer.iReadIndex][0],         iLen)!=0)
                        {
                                copyed = -EFAULT;
                                break;
                        }
                        dev->UsbBuffer.iReadIndex++;
                        if(dev->UsbBuffer.iReadIndex>=MAX_PAGE)
                        {
                                dev->UsbBuffer.iReadIndex = 0;
                        }
                        count-=iLen;
                        copyed+=iLen;
                        //printk("read r=%d w=%d\n",dev->UsbBuffer.iReadIndex,dev->UsbBuffer.iWriteIndex );
                        if(iLen<BUF_PER_PAGE)
                        {
                                break; 
                                
                        }
                         
                         
                }
                else
                {
                        break;
                }
                
        }
        mutex_unlock(&dev->read_mutex); 
        return copyed;
}


static void usbdev_write_callback(struct urb *urb)
{
        struct usb_devinfo *dev;


        dev = urb->context;


        /* sync/async unlink faults aren't errors */
        if (urb->status) {
                if (!(urb->status == -ENOENT ||
                    urb->status == -ECONNRESET ||
                    urb->status == -ESHUTDOWN))
                        dev_err(&dev->interface->dev,
                                "%s - nonzero write bulk status received: %d\n",
                                __func__, urb->status);


                spin_lock(&dev->err_lock);
                dev->errors = urb->status;
                spin_unlock(&dev->err_lock);
        }


        /* free up our allocated buffer */
        usb_free_coherent(urb->dev, urb->transfer_buffer_length,
                          urb->transfer_buffer, urb->transfer_dma);
        up(&dev->limit_sem);
}


static ssize_t usbdev_write(struct file *file, const char *user_buffer,
                          size_t count, loff_t *ppos)
{
        struct usb_devinfo *dev;
        int retval = 0;
        struct urb *urb = NULL;
        char *buf = NULL;
        size_t writesize = min(count, (size_t)MAX_TRANSFER);


        dev = file->private_data;
        if(dev->isOpen != 1 )
        {
                return -1;
        }
        /* verify that we actually have some data to write */
        if (count == 0)
                goto exit;


        /*
         * limit the number of URBs in flight to stop a user from using up all
         * RAM
         */
        if (!(file->f_flags & O_NONBLOCK)) {
                int ret = down_timeout(&dev->limit_sem,10);
                if (ret!=0) {
                        retval = -ERESTARTSYS;
                        goto exit;
                }
        } else {
                if (down_trylock(&dev->limit_sem)) {
                        retval = -EAGAIN;
                        goto exit;
                }
        }


        spin_lock_irq(&dev->err_lock);
        retval = dev->errors;
        if (retval < 0) {
                /* any error is reported once */
                dev->errors = 0;
                /* to preserve notifications about reset */
                retval = (retval == -EPIPE) ? retval : -EIO;
        }
        spin_unlock_irq(&dev->err_lock);
        if (retval < 0)
                goto error;


        /* create a urb, and a buffer for it, and copy the data to the urb */
        urb = usb_alloc_urb(0, GFP_KERNEL);
        if (!urb) {
                retval = -ENOMEM;
                goto error;
        }


        buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL,
                                 &urb->transfer_dma);
        if (!buf) {
                retval = -ENOMEM;
                goto error;
        }


        if (copy_from_user(buf, user_buffer, writesize)) {
                retval = -EFAULT;
                goto error;
        }


        /* this lock makes sure we don't submit URBs to gone devices */
        mutex_lock(&dev->io_mutex);
        if (!dev->interface) {                /* disconnect() was called */
                mutex_unlock(&dev->io_mutex);
                retval = -ENODEV;
                goto error;
        }


        /* initialize the urb properly */
        usb_fill_bulk_urb(urb, dev->udev,
                          usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
                          buf, writesize, usbdev_write_callback, dev);
        urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
        usb_anchor_urb(urb, &dev->submitted);


        /* send the data out the bulk port */
        retval = usb_submit_urb(urb, GFP_KERNEL); 
        mutex_unlock(&dev->io_mutex);
        if (retval) {
                dev_err(&dev->interface->dev,
                        "%s - failed submitting write urb, error %d\n",
                        __func__, retval);
                goto error_unanchor;
        }


        /*
         * release our reference to this urb, the USB core will eventually free
         * it entirely
         */
        usb_free_urb(urb);
         


        return writesize;


error_unanchor:
        usb_unanchor_urb(urb);
error:
        if (urb) {
                usb_free_coherent(dev->udev, writesize, buf, urb->transfer_dma);
                usb_free_urb(urb);
        }
        up(&dev->limit_sem);


exit:
        return retval;
}


static const struct file_operations usbdev_fops = {
        .owner =        THIS_MODULE,
        .read =                usbdev_read,
        .write =        usbdev_write,
        .open =                usbdev_open,
        .release =        usbdev_release,
        .flush =        usbdev_flush,
        .llseek =        noop_llseek,
};


/*
 * usb class driver info in order to get a minor number from the usb core,
 * and to have the device registered with the driver core
 */
static struct usb_class_driver usbdev_class = {
        .name =                "USBDev%d",
        .fops =                &usbdev_fops,
        .minor_base =        USB_DEV_MINOR_BASE,
};
static struct usb_class_driver usbdevother_class = {
        .name =                "USBOther%d",
        .fops =                &usbdev_fops,
        .minor_base =        USB_DEV_MINOR_BASE,
};
static int usbdev_probe(struct usb_interface *interface,
                      const struct usb_device_id *id)
{
        struct usb_devinfo *dev;
        struct usb_host_interface *iface_desc;
        struct usb_endpoint_descriptor *endpoint;
        size_t buffer_size;
        int i;
        int retval = -ENOMEM;
        int iInterfaceType=0;
        if(interface->cur_altsetting->desc.bInterfaceClass==0xff)
        {
                if(interface->cur_altsetting->desc.bInterfaceSubClass==0xff)
                {
                        iInterfaceType=1;
                }
                else
                {
                        iInterfaceType=2;
                }
        }
        if(iInterfaceType==0)
        {
                return ;
        } 
         
        /* allocate memory for our device state and initialize it */
        dev = kzalloc(sizeof(*dev), GFP_KERNEL);
        if (!dev) {
                dev_err(&interface->dev, "Out of memory\n");
                goto error;
        }
        kref_init(&dev->kref);
        sema_init(&dev->limit_sem, WRITES_IN_FLIGHT);
        mutex_init(&dev->io_mutex);
        spin_lock_init(&dev->err_lock);
        init_usb_anchor(&dev->submitted);




        dev->udev = usb_get_dev(interface_to_usbdev(interface));
        dev->interface = interface;


        /* 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 &&
                    usb_endpoint_is_bulk_in(endpoint)) {
                        /* we found a bulk in endpoint */
                        buffer_size = usb_endpoint_maxp(endpoint);
                        dev->bulk_in_size = BUF_PER_PAGE;//buffer_size*100;
                        dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
                        dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL);
                        if (!dev->bulk_in_buffer) {
                                dev_err(&interface->dev,
                                        "Could not allocate bulk_in_buffer\n");
                                goto error;
                        }
                        dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL);
                        if (!dev->bulk_in_urb) {
                                dev_err(&interface->dev,
                                        "Could not allocate bulk_in_urb\n");
                                goto error;
                        }
                }


                if (!dev->bulk_out_endpointAddr &&
                    usb_endpoint_is_bulk_out(endpoint)) {
                        /* we found a bulk out endpoint */
                        dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
                }
        }
        if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
                dev_err(&interface->dev,
                        "Could not find both bulk-in and bulk-out endpoints\n");
                goto error;
        }


        /* save our data pointer in this interface device */
        usb_set_intfdata(interface, dev);


        /* we can register the device now, as it is ready */
        if(iInterfaceType==1)
        {
                retval = usb_register_dev(interface, &usbdev_class);
        }
        else
        {
                retval = usb_register_dev(interface, &usbdevother_class);
        }
        if (retval) {
                /* something prevented us from registering this driver */
                dev_err(&interface->dev,
                        "Not able to get a minor for this device.\n");
                usb_set_intfdata(interface, NULL);
                goto error;
        }


        memset(&dev->UsbBuffer,0,sizeof(cachebuf));
         init_waitqueue_head(&dev->wqReadOK);
         INIT_WORK(&(dev->work), work_func);
         mutex_init(&dev->read_mutex);
         dev->isOpen = 0 ;
         for(i=0;i< MAX_PAGE;i++)
         {
                 dev->UsbBuffer.PackgeBuf[i] = kmalloc(dev->bulk_in_size, GFP_KERNEL);
         }
        /* let the user know what node this device is now attached to */
        dev_info(&interface->dev,
                 "USB Skeleton device now attached to USBSkel-%d",
                 interface->minor);
         
        return 0;


error:
        if (dev)
                /* this frees allocated memory */
                kref_put(&dev->kref, usbdev_delete);
        return retval;
}


static void usbdev_disconnect(struct usb_interface *interface)
{
        struct usb_devinfo *dev;
        int minor = interface->minor;
        int iInterfaceType=0;
         
        if(interface->cur_altsetting->desc.bInterfaceClass==0xff)
        {
                if(interface->cur_altsetting->desc.bInterfaceSubClass==0xff)
                {
                        iInterfaceType=1;
                }
                else
                {
                        iInterfaceType=2;
                }
        }
        if(iInterfaceType==0)
        {
                return ;
        }
        
        dev = usb_get_intfdata(interface);
        dev->isOpen = 0 ;
         wake_up_interruptible(&dev->wqReadOK);         
        usb_set_intfdata(interface, NULL);


        /* give back our minor */
        if(iInterfaceType==1)
        {
                usb_deregister_dev(interface, &usbdev_class);
        }
        else
        {
                usb_deregister_dev(interface, &usbdevother_class);
        }


        /* prevent more I/O from starting */
        mutex_lock(&dev->io_mutex);
        dev->interface = NULL;
        mutex_unlock(&dev->io_mutex);


        usb_kill_anchored_urbs(&dev->submitted);


        /* decrement our usage count */
        kref_put(&dev->kref, usbdev_delete);
         
        dev_info(&interface->dev, "USB  #%d now disconnected", minor);
}


static void usbdev_emptyurb(struct usb_devinfo *dev)
{
        int time;


        time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000);
        if (!time)
                usb_kill_anchored_urbs(&dev->submitted);
        usb_kill_urb(dev->bulk_in_urb);
}


static int usbdev_suspend(struct usb_interface *intf, pm_message_t message)
{
        struct usb_devinfo *dev = usb_get_intfdata(intf);


        if (!dev)
                return 0;
        usbdev_emptyurb(dev);
        return 0;
}


static int usbdev_resume(struct usb_interface *intf)
{
        return 0;
}


static int usbdev_pre_reset(struct usb_interface *intf)
{
        struct usb_devinfo *dev = usb_get_intfdata(intf);


        mutex_lock(&dev->io_mutex);
        usbdev_emptyurb(dev);


        return 0;
}


static int usbdev_post_reset(struct usb_interface *intf)
{
        struct usb_devinfo *dev = usb_get_intfdata(intf);


        /* we are sure no URBs are active - no locking needed */
        dev->errors = -EPIPE;
        mutex_unlock(&dev->io_mutex);


        return 0;
}


static struct usb_driver usbdev_driver = {
        .name =                "usbtest",
        .probe =        usbdev_probe,
        .disconnect =        usbdev_disconnect,
        .suspend =        usbdev_suspend,
        .resume =        usbdev_resume,
        .pre_reset =        usbdev_pre_reset,
        .post_reset =        usbdev_post_reset,
        .id_table =        usbdev_table,
        .supports_autosuspend = 1,
};


 
static int __init usbdev_init(void)
{
         
                return usb_register(&usbdev_driver);
        
}
module_init(usbdev_init);
static void __exit usbdev_exit(void)
{
        usb_deregister(&usbdev_driver);
}
module_exit(usbdev_exit);
MODULE_LICENSE("GPL");

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 酷派手机出现无命令怎么办 华为荣耀4x卡怎么办 华为手机图案解锁忘了怎么办 xp电脑读不起u盘怎么办 在外国玩王者卡怎么办 华为p7忘记解锁密码怎么办 华为荣耀4x存储空间不足怎么办 红米4a内存不够怎么办 华为h60开不了机怎么办 华为荣耀4c内存不足怎么办 华为4c运行内存不足怎么办 华为手机总是显示内存不足怎么办 华为荣耀4x畅玩版内存不足怎么办 三星手机忘了解锁密码怎么办 荣耀8密码忘了怎么办 华为4x开不了机怎么办 华为指纹和密码解锁解不开怎么办 华为荣耀5x死机怎么办 华为荣耀开不了机怎么办 荣耀10开不了机怎么办 乐视pro3变砖了怎么办 手机升级后开不了机怎么办 华为g750开不了机怎么办 手机变砖怎么办插电没反应 变砖手机不通电怎么办 小米手机充电口坏了怎么办 小米2s尾插坏了怎么办 小米手机充电插口坏了怎么办 一条网线上两个亚马逊账号怎么办 加拿大28输20万怎么办 买家账户被亚马逊关闭余额怎么办 京东自营物流慢怎么办 京东退货不给退怎么办 刚付款不想要了怎么办 淘宝卖家拒绝退货退款怎么办 投诉不成立卖家怎么办 淘宝卖家被买家投诉卖假货怎么办 天猫三天未发货怎么办 天猫申请换货卖家不处理怎么办 天猫新疆不发货怎么办 天猫商城少发货怎么办