1.概述

通用串行总线(USB)用于连接主机和外围设备。USB总线采用拓扑结构,USB主机和USB设备的连接构成了一颗树,树的结点为USB节点或USB集线器(HUB),USB集线器(HUB)用于扩展设备接口,一个集线器(HUB)可接多个USB设备或多个集线器。主机侧的USB节点为根节点,所有子节点都连接在根节点集线器(ROOT HUB)上,根节点由USB主机控制器(USB Host Controller)控制,设备侧的节点为子节点,由USB设备控制器(USB Device Controller)控制。在USB总线中,只能有一个USB主机控制器(根节点),可以有多个USB设备控制器(子节点)。USB主机负责协调主机和设备之间的通讯,USB设备不能主动向主机发送任何数据。

2.USB规范

USB1.1协议包括OHCI(Open Host Controller Interface Specification)和UHCI(Universal Host Controller Interface Specification)规范。UHCI对硬件的要求较低,但驱动程序开发复杂,CPU处理负担较高,OHCI则使用硬件实现了较多的功能,对软件的要求降低,减轻了CPU的处理负担。USB2.0增加了EHCI(Enhanced Host Controller Interface),为USB 2.0主机高速数据传输控制器的软硬件设计提供了统一的接口标准,大大简化了USB 2.0的主机设计,提高了软件的可移植性。EHCI本身并不支持全速和低速设备,为了兼容USB 1.1,USB 2.0的主机控制器由EHCI和CHC(Companion Host Controller)两部分组成,CHC由OHCI和UHCI组成。xHCI是由Intel公司开发的可扩展主机控制器接口,主要面向USB3.0,同时也支持USB 2.0及以下设备。

3.USB驱动基础

USB设备包括设备、配置、接口和端点这四个层次,如下图所示。设备中包含若干个配置,配置中包含若干个接口,接口中包含若干个端点。


3.1.端点

端点是USB最基本的通信形式,只能往一个方向传输数据,从主机到设备(输出设备)或者从设备到主机(称为输入端点),端点可以看作是单向的管道。每个端点都有唯一的地址和对应的属性,地址由设备地址和端点号给出,属性包括传输方向、总线访问频率、带宽、端点号和数据包的最大容量等。端点0通常为控制端点,用于设备的初始化。USB端点有四种不同的类型,分别具有不同的数据传递方式。
(1)控制
控制端点用于配置设备、获取设备信息、发送命令到设备、获取设备的状态。每个USB设备都有端点0的控制端点,当USB设备插入到USB主机拓扑网络中时,USB主机就通过端点0与USB设备通信,对USB设备进行配置,便于后续的数据传输。USB协议保证控制端点有足够的带宽。控制端点的数据传输方式为控制传输,控制传输可靠,时间有保证,但传输的数据量不大。如USB设备的识别过程就采用的时控制传输。
(2)中断
当USB主机请求USB设备传输数据时,中断端点以一个固定的速率传送少量的数据。中断端点的数据传输方式为中断传输,数据传输可靠,实时性高,这里的中断并不是USB设备产生中断,而是USB主机每隔一个固定的时间主动查询USB设备是否有数据要传输,以轮询的方式提高实时性。如USB鼠标采用的是中断传输。
(3)批量
批量端点用于传输大量数据,这些端点一次可以保存更多的数据。USB协议不保证这些数据传输可以在特定的时间内完成,但保证数据的准确性。如果总线上的空间不足以发送整个批量包,则将数据拆分为多个包传输。批量传输数据可靠,但实时性较低。如USB硬盘、打印机等设备就采用的是批量传输方式。
(4)等时
等时端点也可以传输大量数据,但数据的可靠性无法保证。采用等时传输的USB设备更加注重保持一个恒定的数据传输速度,对数据的可靠性要求不高。如USB摄像头就使用的是等时传输方式。
Linux内核使用struct usb_endpoint_descriptor结构体描述端点。

    [include/uapi/linux/usb/ch9.h]struct usb_endpoint_descriptor {__u8  bLength;  // 端点描述符长度__u8  bDescriptorType;  // 端点描述符类型 // 端点地址,低四位是端点号,最高位表示数据传输方向,0为输出,1为输入__u8  bEndpointAddress;  __u8  bmAttributes;  // 端点类型,为0表示控制,1表示等时,2表示批量,3表示中断__le16 wMaxPacketSize;  // 本端点接收或发送数据包的最大字节数// 中断传输轮询周期,批量传输忽略,等时传输为1,中断传输范围为1-255__u8  bInterval;  __u8  bRefresh;__u8  bSynchAddress;// __attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,// 按照实际占用字节数进行对齐,是GCC特有的语法} __attribute__ ((packed));

Linux内核使用struct usb_host_endpoint描述主机侧的端点。

    [include/linux/usb.h]struct usb_host_endpoint {struct usb_endpoint_descriptor desc;  // 端点描述符struct usb_ss_ep_comp_descriptor ss_ep_comp;struct list_head        urb_list;  // USB请求块链表节点,由USB核心层管理void               *hcpriv;  // 主机控制器使用,通常用于硬件DMA队列头struct ep_device        *ep_dev;    /* For sysfs info */unsigned char *extra;   /* Extra descriptors */int extralen;  // extra的字节数int enabled;  // URBs may be submitted to this endpointint streams;  // number of USB-3 streams allocated on the endpoint};

3.2.接口

USB接口代表了一个USB设备的基本功能,USB接口由多个USB端口组成。USB接口只处理一种USB逻辑连接,例如鼠标、键盘或者音频流。一些USB设备具有多个接口,例如USB扬声器可以包括两个接口:USB按键和USB音频流。
Linux内核使用struct usb_interface_descriptor描述端点。

    [include/uapi/linux/usb/ch9.h]struct usb_interface_descriptor {__u8  bLength;  // 描述符长度__u8  bDescriptorType;  // 描述符类型__u8  bInterfaceNumber;  // 接口的编号__u8  bAlternateSetting;  // 备用的接口描述符编号__u8  bNumEndpoints;  // 接口使用的端点数量,不包括端点0__u8  bInterfaceClass;  // 接口类型__u8  bInterfaceSubClass;  // 接口子类型__u8  bInterfaceProtocol;  // 接口所遵循的协议__u8  iInterface;  // 描述该接口的字符串索引值} __attribute__ ((packed));

Linux内核使用struct usb_host_interface描述主机侧的接口。

    [include/linux/usb.h]struct usb_host_interface {struct usb_interface_descriptor  desc;  // 接口描述符int extralen;  // extra的字节数unsigned char *extra;   /* Extra descriptors */struct usb_host_endpoint *endpoint;  // 主机侧的端点char *string;        /* 接口字符串 */};

3.3.配置

USB配置由一个或多个USB接口组成,每个配置具有一个或多个基本功能。Linux使用struct usb_config_descriptor描述配置。

    [include/uapi/linux/usb/ch9.h]struct usb_config_descriptor {__u8  bLength;  // 描述符长度__u8  bDescriptorType;  // 描述符类型编号__le16 wTotalLength;  // 配置返回的数据长度__u8  bNumInterfaces;  // 配置所支持的接口数量__u8  bConfigurationValue;  // Set_Configuration命令所需的参数值__u8  iConfiguration;  // 描述该配置字符串的索引值__u8  bmAttributes;  // 供电模式选择__u8  bMaxPower;  // 设备从总线提取的最大电流} __attribute__ ((packed));

Linux内核使用struct usb_host_config描述主机侧的配置。

    [include/linux/usb.h]#define USB_MAXINTERFACES   32#define USB_MAXIADS       (USB_MAXINTERFACES/2)struct usb_host_config {struct usb_config_descriptor   desc;  // 配置描述符char *string;        // 配置字符串// 配置的接口中关联的描述符struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS];/* 配置关联的接口 */struct usb_interface *interface[USB_MAXINTERFACES];// 接口的可用信息struct usb_interface_cache *intf_cache[USB_MAXINTERFACES];unsigned char *extra;   /* Extra descriptors */int extralen;  // extra字节数};

3.4.设备

USB设备有一个或多个配置组成。USB设备可以在这些配置之间切换,以改变设备的状态。Linux使用struct usb_device_descriptor描述设备。

    [include/uapi/linux/usb/ch9.h]struct usb_device_descriptor {__u8  bLength;  // 描述符长度__u8  bDescriptorType;  // 描述符类型编号__le16 bcdUSB;  // USB版本号__u8  bDeviceClass;  // USB分配的设备类code__u8  bDeviceSubClass;  // USB分配的子类code__u8  bDeviceProtocol;  // USB分配的协议code__u8  bMaxPacketSize0;  // 端点0最大包大小__le16 idVendor;  // 厂商编号__le16 idProduct;  // 产品编号__le16 bcdDevice;  // 设备出场编号__u8  iManufacturer;  // 描述厂商字符串的索引__u8  iProduct;  // 描述产品字符串的索引__u8  iSerialNumber;  // 描述设备序列号字符串的索引__u8  bNumConfigurations;  // 可能的配置数量} __attribute__ ((packed));

4.Linux USB驱动程序

在Linux系统中,USB驱动可以从两个角度观察,一个角度是主机侧,另一个角度是设备侧。主机测,处于USB驱动底层的是USB主机控制器硬件,在其上运行的是USB主机控制器驱动,再往上是USB核心层,最上层是USB设备驱动层(插入主机上的U盘、鼠标、键盘等设备驱动)。因此在主机侧,需要实现USB主机控制器驱动和USB设备驱动,前者用于控制USB主机控制器和插入USB总线的USB设备之间的通信,后者描述主机应该怎么和插入的USB设备通信。USB核心完成驱动管理和协议处理的主要工作,向上为主机侧USB设备驱动提供统一的编程接口,向下为USB主机控制器驱动提供统一的编程接口。设备侧驱动程序分为UDC驱动程序、Gadget Function API和Gadget Function驱动程序。UDC驱动程序直接访问硬件,控制USB设备控制器与USB主机控制器通信,向上提供与硬件交互的接口。Gadget Function API封装了UDC驱动程序向上提供的接口。Gadget Function驱动程序实现具体的USB设备功能。Linux内核支持的USB设备类包括USB打印设备、通信类设备、HID设备类、存储设备类、语音设备类等。驱动工程师一般需要实现USB设备驱动,USB主机控制器驱动通常由芯片厂家实现。

4.1.USB设备驱动

Linux USB核心层使用struct usb_driver结构体表示一个USB设备驱动,编写USB设备驱动时,主要实现probedisconnect函数,分别用于初始化和释放软硬件资源,设备和驱动匹配成功后,probe函数被调用,设备断开时disconnect函数被调用。使用宏usb_register注册USB设备驱动程序,使用函数usb_deregister注销USB设备驱动程序,还可以使用宏module_usb_driver注册USB驱动程序,其同时完成注册、注销、module_initmodule_exit功能。在注册USB驱动程序时,还需要定义MODULE_DEVICE_TABLE宏,将设备驱动匹配表导出到用户空间,常用于设备热插拔时进行设备识别。USB协议支持设备的热拔插。
这里需要注意的时USB只是一种总线,而连接到总线上的USB设备可以是字符设备、tty设备、块设备、输入设备等。usb_driver结构体处理了设备和USB总线相关的工作,至于设备的功能,需要根据具体的设备类型来编写具体的设备驱动。因此USB设备驱动包含了其作为总线上挂接设备的驱动和所属设备类型的驱动。这和platform_driveri2c_driver等类似,usb_driver起到桥梁的作用,即在xxx_driver结构体的probe函数中注册具体的设备,如注册字符、tty等设备,在disconnect函数中注销字符、tty设备。

    [include/linux/usb.h]struct usb_driver {const char *name;  // 驱动名称// probe函数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;  // 描述了USB驱动支持的USB设备列表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;};// 注册USB设备驱动程序,driver为struct usb_driver结构体指针#define usb_register(driver) usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)#define module_usb_driver(__usb_driver) module_driver(__usb_driver, usb_register, usb_deregister)// 注销USB设备驱动程序void usb_deregister(struct usb_driver *driver)[include/linux/device.h]// 简介的注册宏定义,同时完成初始化和卸载功能#define module_driver(__driver, __register, __unregister, ...) \static int __init __driver##_init(void) \{ \return __register(&(__driver) , ##__VA_ARGS__); \} \module_init(__driver##_init); \static void __exit __driver##_exit(void) \{ \__unregister(&(__driver) , ##__VA_ARGS__); \} \module_exit(__driver##_exit);

id_table描述了USB驱动支持的USB设备列表,其指向了struct usb_device_id类型的数组。struct usb_device_id包含了USB设备的制造商ID、产品ID、产品版本、设备类、接口类等信息及其要匹配标志成员match_flags(表明要与那些成员匹配)。match_flags的取值从下面的宏定义中选取。

    [include/linux/mod_devicetable.h]#define USB_DEVICE_ID_MATCH_VENDOR      0x0001  // 按供应商ID匹配#define USB_DEVICE_ID_MATCH_PRODUCT      0x0002  // 按产品ID匹配#define USB_DEVICE_ID_MATCH_DEV_LO        0x0004#define USB_DEVICE_ID_MATCH_DEV_HI        0x0008#define USB_DEVICE_ID_MATCH_DEV_CLASS     0x0010 // 按设备类型匹配#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS   0x0020 // 按设备子类型匹配#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL  0x0040 // 按设备协议匹配#define USB_DEVICE_ID_MATCH_INT_CLASS      0x0080 // 按接口类型匹配#define USB_DEVICE_ID_MATCH_INT_SUBCLASS   0x0100 // 按接口子类型匹配#define USB_DEVICE_ID_MATCH_INT_PROTOCOL  0x0200 // 按接口协议匹配#define USB_DEVICE_ID_MATCH_INT_NUMBER     0x0400struct usb_device_id {__u16       match_flags;  // 匹配方式__u16      idVendor;  // 供应商ID,由USB协会分配__u16        idProduct;  // 产品ID,供应商自己分配__u16     bcdDevice_lo;  // 产品版本号的最小值__u16        bcdDevice_hi;  // 产品版本号的最大值__u8     bDeviceClass;  // 设备类型__u8      bDeviceSubClass;  // 设备子类型__u8      bDeviceProtocol;  // 设备协议__u8       bInterfaceClass;  // 接口类__u8        bInterfaceSubClass;  // 接口子类__u8        bInterfaceProtocol;  // 接口协议__u8        bInterfaceNumber;/* not matched against */kernel_ulong_t    driver_info__attribute__((aligned(sizeof(kernel_ulong_t))));};

也可使用下面的宏定义来初始化usb_device_idUSB_DEVICE宏根据制造商ID和产品ID生成一个usb_device_id结构体实列,在数组中增加该元素将意味着该驱动可支持与制造商ID、产品ID相匹配的设备。USB_DEVICE_VER宏根据制造商ID、产品ID、产品版本号范围(在最大值与最小值之间)生成一个usb_device_id结构体的实列,在数组中增加该元素将意味着该驱动可支持与制造商ID、产品ID、lo~hi产品版本号范围内的产品匹配。USB_DEVICE_INFO宏用于创建一个匹配指定设备类型的usb_device_id结构体实列。USB_INTERFACE_INFO宏创建一个匹配指定接口类型的usb_device_id结构体实列。创建完usb_device_id结构体实列后,还需要使用MODULE_DEVICE_TABLE宏将usb_device_id导出到用户空间。

    [include/linux/usb.h]// 匹配方式的组合,用于设置struct usb_device_id的match_flags标志#define USB_DEVICE_ID_MATCH_DEVICE \(USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT)#define USB_DEVICE_ID_MATCH_DEV_RANGE \(USB_DEVICE_ID_MATCH_DEV_LO | USB_DEVICE_ID_MATCH_DEV_HI)#define USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION \(USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_RANGE)#define USB_DEVICE_ID_MATCH_DEV_INFO \(USB_DEVICE_ID_MATCH_DEV_CLASS | \USB_DEVICE_ID_MATCH_DEV_SUBCLASS | \USB_DEVICE_ID_MATCH_DEV_PROTOCOL)#define USB_DEVICE_ID_MATCH_INT_INFO \(USB_DEVICE_ID_MATCH_INT_CLASS | \USB_DEVICE_ID_MATCH_INT_SUBCLASS | \USB_DEVICE_ID_MATCH_INT_PROTOCOL)#define USB_DEVICE(vend, prod) \.match_flags = USB_DEVICE_ID_MATCH_DEVICE, \.idVendor = (vend), \  // 制造商ID.idProduct = (prod)    // 产品ID#define USB_DEVICE_VER(vend, prod, lo, hi) \.match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, \.idVendor = (vend), \     // 制造商ID.idProduct = (prod), \    // 产品ID.bcdDevice_lo = (lo), \   // 产品版本号范围的最小值.bcdDevice_hi = (hi)      // 产品版本号范围的最大值#define USB_DEVICE_INFO(cl, sc, pr) \.match_flags = USB_DEVICE_ID_MATCH_DEV_INFO, \.bDeviceClass = (cl), \     // 设备类型 .bDeviceSubClass = (sc), \  // 设备子类.bDeviceProtocol = (pr)     // 设备协议#define USB_INTERFACE_INFO(cl, sc, pr) \.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \.bInterfaceClass = (cl), \     // 接口类型.bInterfaceSubClass = (sc), \  // 接口子类.bInterfaceProtocol = (pr)     // 接口协议......

下面是定义USB设备属性的例子,当USB核心检测到某个插入设备的属性和某个驱动程序的属性一致时,这个驱动程序的probe()函数就被调用,当拔掉设备或者卸载驱动后,USB核心层就执行disconnect()函数,将设备和驱动断开。

    // 先定义一个usb_device_id数组,使用USB_DEVICE初始化内部的元素,然后使用// MODULE_DEVICE_TABLE将usb_device_id导出到用户空间,便于热插拔时进行设备识别static struct usb_device_id id_table[] = {{USB_DEVICE(VEND_TD, PRODUCT_ID)},{ },  // 最后一个元素必须是空}MODULE_DEVICE_TABLE(usb, id_table);

4.2.USB设备

USB核心层使用struct usb_device结构来表示一个USB设备。当USB控制器检测到设备连接时,就会分配一个struct usb_device结构体,然后注册到总线设备列表中,然后匹配对应的驱动。

    [include/linux/usb.h]struct usb_device {int      devnum;  // 该设备在总线上的序号char      devpath[16];  // USB拓扑路径u32     route;enum usb_device_state state;  // 状态enum usb_device_speed  speed;  // 速度struct usb_tt  *tt;  // 事务转换(如高速接口兼容低速设备)int     ttport;;struct usb_device *parent;struct usb_bus *bus;struct usb_host_endpoint ep0;  // 端点0struct device dev;struct usb_device_descriptor descriptor;  // 设备描述符struct usb_host_bos *bos;struct usb_host_config *config;struct usb_host_config *actconfig;struct usb_host_endpoint *ep_in[16];  // 输入端口struct usb_host_endpoint *ep_out[16];  // 输出端口char **rawdescriptors;  // GET_DESCRIPTOR命令返回的描述符原始字符串unsigned short bus_mA;  // 总线电流u8 portnum;  // HUB端口号u8 level;  // USB设备树层级......};

4.3.USB主机控制器驱动

在Linux内核中,使用struct usb_hcd描述USB主机控制区驱动,包含了主机控制器的‘家务’信息、硬件资源、状态描述和用于控制主机控制器的hc_driver等。struct usb_hcdhc_driver成员非常重要,包含了操作主机控制器的所有方法。

    [include/linux/usb/hcd.h]struct usb_hcd {struct usb_bus      self;       /* hcd is-a bus */struct kref       kref;       /* reference counter */const char       *product_desc;  /* 厂商字符串 */int          speed;      /* 主机控制器根HUB的速度char         irq_descr[24];  /* driver + bus # */struct timer_list  rh_timer;   /* drives root-hub polling */struct urb     *status_urb;    /* the current status urb */// USB主机控制器操作函数集合const struct hc_driver *driver;    /* hw-specific hooks */// OTG和某些USB控制器需要和PHY交互struct usb_phy        *usb_phy;struct phy     *phy;unsigned long      flags;unsigned int      irq;        /* irq allocated */void __iomem     *regs;      /* device memory/io */resource_size_t       rsrc_start; /* memory/io resource start */resource_size_t       rsrc_len;   /* memory/io resource length */unsigned     power_budget;   /* in mA, 0 = no limit */struct giveback_urb_bh  high_prio_bh;struct giveback_urb_bh  low_prio_bh;struct mutex     *bandwidth_mutex;struct usb_hcd     *shared_hcd;struct usb_hcd      *primary_hcd;#define HCD_BUFFER_POOLS   4struct dma_pool        *pool[HCD_BUFFER_POOLS];int         state;unsigned long hcd_priv[0]  // 私有数据__attribute__ ((aligned(sizeof(s64))));};

urb_enqueue函数非常关键,上层通过usb_submit_urb提交一个USB请求后,该函数内部通过调用usb_hcd_submit_urb函数,最终调用到urb_enqueue

    [include/linux/usb/hcd.h]struct hc_driver {const char    *description;   /* "ehci-hcd" etc */const char    *product_desc;  /* product/vendor string */size_t       hcd_priv_size;  /* size of private data *//* irq handler */irqreturn_t  (*irq) (struct usb_hcd *hcd);int    flags;/* called to init HCD and root hub */int  (*reset) (struct usb_hcd *hcd);int  (*start) (struct usb_hcd *hcd);int  (*pci_suspend)(struct usb_hcd *hcd, bool do_wakeup);int (*pci_resume)(struct usb_hcd *hcd, bool hibernated);void    (*stop) (struct usb_hcd *hcd);/* shutdown HCD */void(*shutdown) (struct usb_hcd *hcd);/* return current frame number */int  (*get_frame_number) (struct usb_hcd *hcd);/* manage i/o requests, device state */int    (*urb_enqueue)(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags);int   (*urb_dequeue)(struct usb_hcd *hcd, struct urb *urb, int status);int    (*map_urb_for_dma)(struct usb_hcd *hcd, struct urb *urb,gfp_t mem_flags);void(*unmap_urb_for_dma)(struct usb_hcd *hcd, struct urb *urb);void(*endpoint_disable)(struct usb_hcd *hcd,struct usb_host_endpoint *ep);void(*endpoint_reset)(struct usb_hcd *hcd,struct usb_host_endpoint *ep);int   (*hub_status_data) (struct usb_hcd *hcd, char *buf);int (*hub_control) (struct usb_hcd *hcd,u16 typeReq, u16 wValue, u16 wIndex,char *buf, u16 wLength);int (*bus_suspend)(struct usb_hcd *);int    (*bus_resume)(struct usb_hcd *);......};

在Linux内核中,使用usb_create_hcd函数创建主机控制器,使用usb_add_hcdusb_remove_hcd函数注册、注销主机控制器。

    [include/linux/usb/hcd.h]// 创建主机控制器struct usb_hcd结构体并初始化。driver为struct hc_driver结构体指针,dev为主机// 控制器的设备结构体,保存在hcd->self.controller中。bus_name为总线名称,保存在hcd->self.bus_name。struct usb_hcd *usb_create_hcd(const struct hc_driver *driver,struct device *dev, const char *bus_name)// 初始化并注册主机控制器struct usb_hcd结构体。hcd为struct usb_hcd结构体指针。irqnum为中断号。// irqflags为中断标志int usb_add_hcd(struct usb_hcd *hcd,unsigned int irqnum, unsigned long irqflags)// 注销主机控制器struct usb_hcd结构体。hcd为struct usb_hcd结构体指针。void usb_remove_hcd(struct usb_hcd *hcd)

4.4.USB请求块URB

USB请求块(USB Request Block,URB)是USB设备驱动中用来描述与USB设备通信的基本载体和核心数据结构,使用urb结构体描述,类似与网络设备驱动中的sk_buff结构体。

4.4.1.URB数据结构

struct urb结构体由两部分组成,第一部分是私有的,只能被usb核心层或主机控制器使用,第二部分是公有的,可被usb设备驱动程序使用。

    [include/linux/usb.h]struct urb {// 以下私有:usb核心层或主机控制器用struct kref kref;       /* URB引用计数 */void *hcpriv;          /* 主机控制器私有数据 */atomic_t use_count;      /* 并发提交个数 */atomic_t reject;        /* 提交失败的数量 */int unlinked;          /* unlink error code */// 以下公用:可被驱动使用的成员struct list_head urb_list;       /* urb组成的链表 */struct list_head anchor_list; /* the URB may be anchored */struct usb_anchor *anchor;struct usb_device *dev;          /* 关联的设备 */struct usb_host_endpoint *ep;    /* (internal) pointer to endpoint */pipe用来查找端点队列,队列的特性定义在端点描述符中。pipe的各位定义如下:   bit7-0:bit7数据传输方向,0 = Host-to-Device [Out],1 = Device-to-Host [In]bit8-14:USB设备地址(编号),bit positions known to uhci-hcdbit15-18:端点地址(编号),... bit positions known to uhci-hcd,可找到对应的端点bit30-31:pipe的类型,00 = isochronous, 01 = interrupt, 10 = control, 11 = bulk)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 intervalint 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];  // 等时数据包};

使用usb_sndctrlpipeusb_rcvctrlpipe宏获取控制传输的发送和接收pipe,使用usb_sndisocpipeusb_rcvisocpipe宏获取等时传输的发送和接收pipe,使用usb_sndbulkpipeusb_rcvbulkpipe宏获取批量传输的发送和接收pipe,使用usb_sndintpipeusb_rcvintpipe宏获取中断传输的发送和接收pipe。

    #define USB_DIR_OUT          0       /* 数据传输方向 to device */#define USB_DIR_IN            0x80        /* 数据传输方向 to host */#define PIPE_ISOCHRONOUS        0  // pipe类型#define PIPE_INTERRUPT          1#define PIPE_CONTROL           2#define PIPE_BULK          3static inline unsigned int __create_pipe(struct usb_device *dev, unsigned int endpoint){return (dev->devnum << 8) | (endpoint << 15);}#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)

4.4.2.urb的操作函数

(1)分配和释放URB
使用usb_alloc_urb分配一个urb结构体,使用usb_free_urb释放一个urb结构体。参数iso_packets表示分配等时数据包的数量,若为0则不分配,mem_flags为分配内存的标志,调用kmalloc时使用。urb不宜静态创建,因为这可能破环USB核心层使用的URB引用计数。

    [include/linux/usb.h]// 成功返回urb的指针,否则返回NULLstruct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)void usb_free_urb(struct urb *urb)

(2)初始化URB
urb结构体在使用之前要和USB设备端点对应起来,需要根据数据传递方式进行初始化。
中断传输方式(端点)使用usb_fill_int_urb初始化urb结构体,参数pipe比较重要,根据pipe可以知道对应的USB设备、端点、数据传输方向及数据传输类型。对应中断传输,可使用usb_sndintpipeusb_rcvintpipe宏来获取pipe。

    [include/linux/usb.h]// urb-urb结构体的指针// dev-urb关联的usb设备结构体指针// pipe-端点的管道,// transfer_buffer-传输缓冲区指针,和urb一样,不能是静态缓冲区,不能使用kmalloc分配,//     必须使用专门的函数usb_alloc_coherent分配// buffer_length-传输缓冲区transfer_buffer的长度// complete_fn-传输完成的回调函数// context-complete_fn函数的上下文// interval-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)[include/linux/usb.h]// 批量传输方式(端点)使用usb_fill_bulk_urb初始化urb结构体// pipe-可使用usb_sndbulkpipe和usb_rcvbulkpipe宏来获取。批量传输没有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)[include/linux/usb.h]// 控制传输方式(端点)使用usb_fill_control_urb初始化urb结构体// pipe-可使用usb_sndctrlpipe和usb_rcvctrlpipe宏来获取// setup_packet-控制传输和批量传输相比,多了一个setup_packet参数,setup_packet指向设置数据包的缓冲区。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)

等时传输方式没有现成的初始化函数用来初始化urb结构体。需要手动初始化并设置URB_ISO_ASSP标志。
(3)异步提交URB
urb结构体分配和初始化完成后,就可以使用usb_submit_urb函数将请求异步的提交到USB核心层了,提交成功返回0,否则返回小于0的错误代码,请求完成后回调函数complete将被调用。urb指向要提交的urb结构体,mem_flagskmalloc函数分配内存时的标志。在将URB提交到USB核心层后,直到完成函数被调用之前,不要操作urb结构体中的任何成员。usb_submit_urb在原子上下文和进程上下文中都可以调用,mem_flags参数需要根据调用环境进行设置。如下所示:
GFP_ATOMIC:在原子上下文环境中使用,如中断处理函数、中断下半本部分、tasklet、定时器处理函数、USR完成函数complete、持有自旋锁或读写锁时将current->stat设置为非TASK_RUNNING
GFP_NOIO:在存储设备的块I/O和错误处理路径中使用。
GFP_KERNEL:没有使用GFP_ATOMICGFP_NOIO的理由,就使用此标志。
GFP_NOFS:没有使用过。

    [include/linux/usb.h]// 异步提交请求,complete回调函数被调用表明请求已完成int usb_submit_urb(struct urb *urb, gfp_t mem_flags)

(4)同步提交URB(信息)
下面三个接口可以同步提交URB(信息),常用于提交一些简单的信息,所以省略了创建和初始化urb结构体的步骤,在请求处理完后或者超时时间到才返回,提交完成返回0,否则返回小于0的错误码。这三个接口都不能用在硬件中断、软件中断及持有自旋锁的上下文环境(!in_interrupt ())中。usb_control_msg用于提交控制传输的请求,usb_interrupt_msg用于传输中断类型的请求,usb_bulk_msg用于传输批量类型的请求。

    [include/linux/usb.h]// dev:USB设备结构体指针,pipe:pipe值,用于找到发送的USB设备端点,request:USB信息请求值,// requesttype:USB信息请求类型,value:USB消息值,index:USB消息索引值,data:发送或接收缓冲区指针,// size:发送或接收缓冲区的大小,timeout:等待消息完成的时间,单位为毫秒,0为一直等待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)// dev:USB设备结构体指针,pipe:pipe值,用于找到发送的USB设备端点,data:发送或接收缓冲区指针,// len:缓冲区中发送的字节数,actual_length:保存实际传输的字节数,// timeout:等待消息完成的时间,单位为毫秒,0为一直等待int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe,void *data, int len, int *actual_length, int timeout)// dev:USB设备结构体指针,pipe:pipe值,用于找到发送的USB设备端点,data:发送或接收缓冲区指针,// len:缓冲区中发送的字节数,actual_length:保存实际传输的字节数,// timeout:等待消息完成的时间,单位为毫秒,0为一直等待int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,void *data, int len, int *actual_length, int timeout)

(5)取消URB
usb_kill_urbusb_kill_urb可以取消一个urb,前者是异步取消,不等待取消成功就返回,若取消的urb正在执行,则执行完成后取消,后者是同步的,直到取消成功才返回。

    [include/linux/usb.h]void usb_unlink_urb(struct urb *urb)void usb_kill_urb(struct urb *urb)

(6)URB传输数据缓冲区的分配和释放
初始化USB请求块URB的参数transfer_buffer不能静态定义和使用kmalloc等,必须使用usb_alloc_coherent函数分配。因为USB数据内部传输用到了DMA,为了保证数据一致性,则必须要使用和DMA相关的内存分配的函数,usb_alloc_coherent函数内部调用了dma_alloc_coherent。此函数分配的内存是uncache的。使用usb_free_coherent释放分配的内存。

    [include/linux/usb.h]// dev:USB设备结构体// size:缓冲区长度// mem_flags:内存分配标志// dma:分配的内存对应的物理地址void *usb_alloc_coherent(struct usb_device *dev, size_t size, gfp_t mem_flags,dma_addr_t *dma)void usb_free_coherent(struct usb_device *dev, size_t size, void *addr,dma_addr_t dma)

5.实验

首先取消内核中的usb mouse驱动支持,路径如下,取消USB HID transport layer的驱动支持。

Device Drivers > HID support > USB HID support > <> USB HID transport layer

使用insmod usb_mouse.ko,插入USB鼠标,系统会打印鼠标信息,同时在dev目录下生成/dev/input/event4设备节点。使用hexdump /dev/input/event4命令,此时移动鼠标,会看到输出的数据。

6.USB鼠标驱动源码

 /*===========================usb_mouse.h================================*/#ifndef USB_MOUSE_H#define USB_MOUSE_H#include <linux/usb.h>#include <uapi/linux/usb/ch9.h>#include <linux/usb/input.h>#include <linux/input.h>struct usb_mouse{struct usb_host_interface *intf;struct usb_device* dev;struct usb_endpoint_descriptor* endpoint;struct urb* irq_urb;struct input_dev* input;int maxlen;unsigned int pipe;char* data;  // 虚拟地址dma_addr_t data_dma;  // 物理地址char name[128];char phys[64];};
#endif // USB_MOUSE_H/*===========================usb_mouse.c================================*/#include "usb_mouse.h"#include <linux/kernel.h>#include <linux/slab.h>#include <linux/module.h>#include <linux/init.h>#include <linux/hid.h>#include <linux/device.h>// 参考usbmouse.c文件,路径:usbmouse.c  drivers/hid/usbhid/usbmouse.c   struct usb_mouse* usb_mouse_dev = NULL;static int usb_mouse_open(struct input_dev *dev){int ret = 0;struct usb_mouse* usb_mouse = input_get_drvdata(dev);usb_mouse->irq_urb->dev = usb_mouse->dev;if ((ret = usb_submit_urb(usb_mouse->irq_urb, GFP_KERNEL)) < 0) {dev_err(&usb_mouse->dev->dev, "usb_submit_urb failed, errno %d\n", ret);return -EIO;}return 0;}static void usb_mouse_close(struct input_dev *dev){struct usb_mouse* usb_mouse = input_get_drvdata(dev);usb_kill_urb(usb_mouse->irq_urb);}static void usb_mouse_irq(struct urb *urb){struct usb_mouse* usb_mouse = urb->context;char* data = usb_mouse->data;struct input_dev* dev = usb_mouse->input;int status, i;for (i = 0; i < usb_mouse->maxlen; i++) {printk("%02x ", data[i]);}printk("\n");// 有标准的完成函数,不需要判断usb的状态/*switch (urb->status) {  // 获取urb提交结果case 0:           // success break;case -ECONNRESET:  // unlink case -ENOENT:case -ESHUTDOWN:return;// -EPIPE:  should clear the halt default:        // error goto resubmit;}*/input_report_key(dev, BTN_LEFT,   data[0] & 0x01);input_report_key(dev, BTN_RIGHT,  data[0] & 0x02);input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);input_report_key(dev, BTN_SIDE,   data[0] & 0x08);input_report_key(dev, BTN_EXTRA,  data[0] & 0x10);input_report_rel(dev, REL_X,     data[1]);input_report_rel(dev, REL_Y,     data[2]);input_report_rel(dev, REL_WHEEL, data[3]);input_sync(dev);//resubmit:status = usb_submit_urb(urb, GFP_ATOMIC);if (status)dev_err(&usb_mouse->dev->dev,"can't resubmit intr, %s-%s/input0, status %d\n",usb_mouse->dev->bus->bus_name,usb_mouse->dev->devpath, status);}static int usb_mouse_probe(struct usb_interface* intf, const struct usb_device_id* id){int error = -ENOMEM;struct usb_device* usbdev;usbdev = interface_to_usbdev(intf);usb_mouse_dev = kmalloc(sizeof(struct usb_mouse), GFP_KERNEL);if (NULL == usb_mouse_dev) {dev_err(&usbdev->dev, "usb_mouse_dev kmalloc failed\n");return error; }memset(usb_mouse_dev, 0, sizeof(struct usb_mouse));usb_mouse_dev->dev = usbdev;dev_info(&usbdev->dev, "bcdUSB = 0x%x\n", usb_mouse_dev->dev->descriptor.bcdUSB);  // USB版本号dev_info(&usbdev->dev, "idVendor = 0x%x\n", usb_mouse_dev->dev->descriptor.idVendor);  // 厂商编号dev_info(&usbdev->dev, "idProduct = 0x%x\n", usb_mouse_dev->dev->descriptor.idProduct);  // 设备出场编号usb_mouse_dev->intf = intf->cur_altsetting;// 检查此端点数量是否为1(除端点0)if (usb_mouse_dev->intf->desc.bNumEndpoints != 1) {error = -ENODEV;dev_err(&usbdev->dev, "bNumEndpoints error, bNumEndpoints %u\n", usb_mouse_dev->intf->desc.bNumEndpoints);goto free_usb_mouse;}// 获取主机侧与鼠标通信的端点usb_mouse_dev->endpoint = &usb_mouse_dev->intf->endpoint[0].desc;// 判断是否是中断输入端点if (!usb_endpoint_is_int_in(usb_mouse_dev->endpoint)) {error = -ENODEV;dev_err(&usbdev->dev, "endpoint isn't int in\n");goto free_usb_mouse;}// 获取pipe,根据pipe可以知道对应的USB设备、端点、数据传输方向及数据传输类型usb_mouse_dev->pipe = usb_rcvintpipe(usb_mouse_dev->dev, usb_mouse_dev->endpoint->bEndpointAddress);// 获取本端点接收或发送数据包的最大字节数usb_mouse_dev->maxlen = usb_maxpacket(usb_mouse_dev->dev, usb_mouse_dev->pipe, usb_pipeout(usb_mouse_dev->pipe));dev_info(&usbdev->dev, "wMaxPacketSize 0x%x\n", usb_mouse_dev->maxlen);// 分配usb数据传输的缓冲区,不能使用kmalloc分配usb_mouse_dev->data = usb_alloc_coherent(usb_mouse_dev->dev, usb_mouse_dev->maxlen, GFP_ATOMIC, &usb_mouse_dev->data_dma);if (NULL == usb_mouse_dev->data) {    dev_err(&usbdev->dev, "usb mouse alloc usb coherent buffer error\n");  goto free_usb_mouse;}// 分配一个usb请求块usb_mouse_dev->irq_urb = usb_alloc_urb(0, GFP_KERNEL);if (NULL == usb_mouse_dev->irq_urb) {dev_err(&usbdev->dev, "usb mouse alloc usb urb error\n");  goto free_usb_coherent;}// 填充usb请求块usb_fill_int_urb(usb_mouse_dev->irq_urb, usb_mouse_dev->dev, usb_mouse_dev->pipe, usb_mouse_dev->data, usb_mouse_dev->maxlen,// usb_mouse_dev保存到struct urb的context变量中usb_mouse_irq, usb_mouse_dev, usb_mouse_dev->endpoint->bInterval);dev_info(&usbdev->dev, "bInterval 0x%x\n", usb_mouse_dev->endpoint->bInterval);usb_mouse_dev->irq_urb->transfer_dma = usb_mouse_dev->data_dma;usb_mouse_dev->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;usb_set_intfdata(intf, usb_mouse_dev);usb_mouse_dev->input = input_allocate_device();if (NULL == usb_mouse_dev->input) {dev_err(&usbdev->dev, "usb mouse allocate input device error\n");goto free_usb_urb;}if (usb_mouse_dev->dev->manufacturer)strlcpy(usb_mouse_dev->name, usb_mouse_dev->dev->manufacturer, sizeof(usb_mouse_dev->name));if (usb_mouse_dev->dev->product) {if (usb_mouse_dev->dev->manufacturer)strlcat(usb_mouse_dev->name, " ", sizeof(usb_mouse_dev->name));strlcat(usb_mouse_dev->name, usb_mouse_dev->dev->product, sizeof(usb_mouse_dev->name));}if (!strlen(usb_mouse_dev->name))snprintf(usb_mouse_dev->name, sizeof(usb_mouse_dev->name),"USB HIDBP Mouse %04x:%04x",le16_to_cpu(usb_mouse_dev->dev->descriptor.idVendor),le16_to_cpu(usb_mouse_dev->dev->descriptor.idProduct));usb_make_path(usb_mouse_dev->dev, usb_mouse_dev->phys, sizeof(usb_mouse_dev->phys));strlcat(usb_mouse_dev->phys, "/input0", sizeof(usb_mouse_dev->phys));usb_mouse_dev->input->name = usb_mouse_dev->name;usb_mouse_dev->input->phys = usb_mouse_dev->phys;dev_info(&usbdev->dev, "name = %s\n", usb_mouse_dev->name);dev_info(&usbdev->dev, "phys = %s\n", usb_mouse_dev->phys);usb_to_input_id(usb_mouse_dev->dev, &usb_mouse_dev->input->id);usb_mouse_dev->input->dev.parent = &intf->dev;usb_mouse_dev->input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);usb_mouse_dev->input->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);usb_mouse_dev->input->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);usb_mouse_dev->input->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |BIT_MASK(BTN_EXTRA);usb_mouse_dev->input->relbit[0] |= BIT_MASK(REL_WHEEL);input_set_drvdata(usb_mouse_dev->input, usb_mouse_dev);usb_mouse_dev->input->open = usb_mouse_open;usb_mouse_dev->input->close = usb_mouse_close;error = input_register_device(usb_mouse_dev->input);if (error < 0) {dev_err(&usbdev->dev, "usb mouse register input device error, errno %d\n", error);goto free_input_device;}#if 0  // 测试用// 提交urbif (error = usb_submit_urb(usb_mouse_dev->irq_urb, GFP_ATOMIC)) {dev_err(&usbdev->dev, "usb_submit_urb failed, errno %d\n", error);return error;        }#endifreturn 0;free_input_device:input_free_device(usb_mouse_dev->input);free_usb_urb:usb_free_urb(usb_mouse_dev->irq_urb);free_usb_coherent:usb_free_coherent(usb_mouse_dev->dev, usb_mouse_dev->maxlen, usb_mouse_dev->data, usb_mouse_dev->data_dma);free_usb_mouse:kfree(usb_mouse_dev);usb_mouse_dev = NULL;return error;}static void usb_mouse_disconnect(struct usb_interface *intf){dev_info(&usb_mouse_dev->dev->dev, "usb_mouse_disconnect\n");usb_kill_urb(usb_mouse_dev->irq_urb);input_unregister_device(usb_mouse_dev->input);input_free_device(usb_mouse_dev->input);usb_free_urb(usb_mouse_dev->irq_urb);usb_free_coherent(usb_mouse_dev->dev, usb_mouse_dev->maxlen, usb_mouse_dev->data, usb_mouse_dev->data_dma);kfree(usb_mouse_dev);usb_mouse_dev = NULL;}static struct usb_device_id usb_mouse_id_table [] = {{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,USB_INTERFACE_PROTOCOL_MOUSE) },{ }    /* Terminating entry */};MODULE_DEVICE_TABLE(usb, usb_mouse_id_table);static struct usb_driver usb_mouse_driver = {.name       = "usb_mouse",.probe     = usb_mouse_probe,.disconnect  = usb_mouse_disconnect,.id_table   = usb_mouse_id_table,};#if 0static int __init usb_mouse_init(void){int ret = usb_register(&usb_mouse_driver);if (ret < 0)pr_err("usb mouse register failed, errno: %d\n", ret);return ret;}static void __exit usb_mouse_exit(void){usb_deregister(&usb_mouse_driver);}module_init(usb_mouse_init);module_exit(usb_mouse_exit);#elsemodule_driver(usb_mouse_driver, usb_register, usb_deregister);#endif // 1MODULE_LICENSE("GPL");MODULE_AUTHOR("liyang.plus@foxmail.com");

Linux USB驱动-鼠标驱动相关推荐

  1. Linux USB 3.0驱动分析—UAC驱动分析

    转自 https://www.cnblogs.com/wen123456/p/14281917.html 因为项目里面有USB音频外设,所以需要分析一下UAC驱动. USB Audio Class,U ...

  2. linux USB大容量设备驱动入门之读取U盘容量

    主要参考了以下资料: usbmassbulk_10.pdf , usbmass-ufi10.pdf, SCSI Commands Reference Manual,spc-3.pdf 以下驱动模块通过 ...

  3. linux usb有线网卡驱动_有线网卡Linux驱动安装小记

    一直使用无线网,竟然没发现我的有线网卡Atheros AR8162竟然没有驱动,在此小记: ubuntu 12.04 以及 基于ubuntu的mint 13 maya 都有这个问题 在linux下查看 ...

  4. hid设备驱动linux,linux usb hid设备驱动(3)

    本文分析了蓝牙bluez协议栈中HID协议的实现. 1. 基本概念 HID协议用于人机输入设备.Bluez中关于HID的实现代码在其根目录下的input目录.蓝牙规范中包含关于HID的profile, ...

  5. 【正点原子Linux连载】第六十七章 Linux USB驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  6. Linux USB 驱动实验

    目录 USB 接口简介 什么是USB? USB 电气特性 USB 拓扑结构 什么是USB OTG? I.MX6ULL USB 接口简介 硬件原理图分析 USB HUB 原理图分析 V2.4 版本以前底 ...

  7. 从零开始学USB(二十六、usb鼠标驱动驱动实例分析[1]简介)

    这个驱动是在上一节的最简单的usb驱动基础上增加了输入子系统和usb包的获取和请求. 首先先给出一个稍微简化了一下的usb的鼠标驱动,这里只做了鼠标的三个按键.为了方便验证,这里用鼠标的左键,右键和中 ...

  8. linux选择usb功能,USB gadget设备驱动解析(1)——功能体验

    利用Linux USB gadget设备驱动可以实现一些比较有意思的功能,举两个例子: 1.一个嵌入式产品中的某个存储设备,或是一个存储设备的某个分区,可以作为一个U盘被PC:设别,从而非常方便的完成 ...

  9. USB gadget设备驱动解析

    利用Linux USB gadget设备驱动可以实现一些比较有意思的功能,举两个例子: 1.一个嵌入式产品中的某个存储设备,或是一个存储设备的某个分区,可以作为一个U盘被PC:设别,从而非常方便的完成 ...

  10. Linux USB 驱动开发实例(二)—— USB 鼠标驱动注解及测试

    参考2.6.14版本中的driver/usb/input/usbmouse.c.鼠标驱动可分为几个部分:驱动加载部分.probe部分.open部分.urb回调函数处理部分. 一.驱动加载部分 [cpp ...

最新文章

  1. XHTML标准下的100%高度问题
  2. python零基础难学吗-如何从零开始学习Python,零基础学python难吗
  3. Error assembling WAR: webxml attribute is required (or pre-existing WEB-INF/web.xml
  4. 我想做个MP3,要怎么入手?
  5. 双电容单相电机接线图解_单相电机要用电容,三相电机为何不需要?
  6. freeswitch 用户配置详解_FreeSwitch安装配置记录-阿里云开发者社区
  7. java动态数组储存敌机_如何使用参数通过graphql将动态数组字符串存储为neo4j中的节点属性?...
  8. linux CP命令覆盖不提示方法
  9. c51单片机音乐盒c语言,毕业论文-基于AT89C51单片机的音乐盒设计(C程序).doc
  10. Ajaxsubmit表单提交
  11. 林老师话说天南地北  我的学生在美国西雅图微软总部
  12. 思科路由器 密码设置和恢复
  13. 向工程腐化开炮 | proguard治理
  14. 论文结构及其内容简介
  15. Predicting mRNA Abundance Directly from Genomic Sequence Using Deep Convolutional Neural Networks
  16. php dingo和jwt,Laravel5.5+dingo+JWT 开发后台 API
  17. webstorm2020.2.3下载安装教程
  18. 基于51单片机1602温度显示时钟
  19. Deepin开发环境搭建
  20. 三星14纳米EUV DDR5 DRAM量产;Amazfit推出三款智能手表;Whale帷幄获5000万美元融资 | 全球TMT...

热门文章

  1. FFmpeg —— 屏幕录像机
  2. 网络工程师面试真题——ACL与IP Prefix的区别
  3. QT控件之QComboBox(下拉框相关)
  4. android studio设置SVN忽略
  5. 希捷硬盘校准日志分析
  6. ssm mysql项目实战_ssm项目实战_ssm项目实战教程_ssm项目实战视频教程 _课课家
  7. Netty实战《RPC调用》
  8. kms地址大全_kms激活服务器地址(常用)和自己搭建KMS服务器教程
  9. Linux 4G/5G 驱动移植、实践测试记录
  10. sap apo 生产排程软件的架构和设计分析