一文入门USB设备的驱动编写方法

摘要:本文介绍了USB设备驱动相关的基本知识结构,和编写驱动的基本步骤和流程。最后通过编写一个USB鼠标的驱动实力,讲述了简单字符型USB输入设备驱动的具体编写步骤,并给予了测试方法。文末附有完整程序代码和Makefile。

1、几个常见疑惑?

  • 为什么一插上就会有提示信息?

    是因为windows自带了USB总线驱动程序;

  • 那USB总线驱动程序是干嘛用的?

    • 识别USB设备;
    • 给USB设备找到并安装对应的驱动程序;
    • 提供USB的读写函数。

    首先,新接入的USB设备的默认地址(编号)为0,再未分配新编号前,PC主机使用0地址和它通信。然后USB总线驱动程序都会给它分配一个地址(编号)。PC机想访问USB总线上某个USB设备时,发出的命令都含有对应的地址(编号)。

  • USB的结构是怎样的?

    • USB是一种主从结构。主机叫做Host,从机叫做Device,所有的USB传输,都是从USB主机这方发起;USB设备没有“主动”通知USB主机的能力。
    • 例如:USB鼠标滑动一下立刻产生数据,但是它还没有能力通过OC机来读数据,只能被动地等待PC机来读。
  • USB可以热拔插的硬件原理

    • 在USB集线器(hub)的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。
    • 而在USB设备端,在D+或者D-上接了1.5k欧姆上拉电阻,对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。
    • 这样,当设备插入到集线器时,由1.5k的上拉电阻和15k的下拉电阻分呀,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。
    • USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到告诉模式的。在高速模式下,是电流传输模式,这时将D+上的上拉电阻断开。
  • USB的4大传输类型

    • 控制传输
      • 是每一个USB设备必须支持的,通常用来获取设备描述符、设置设备的状态等等。一个USB设备从插入到最后的拔出这个过程一定会产生控制传输(即便这个USB设备不能被这个系统支持)。
    • 中断传输
      • 支持中断传输的典型设备有USB鼠标、USB键盘等等。中断传输不是说我的设备真正发出一个中断,然后主机会来读取数据。它其实是一种轮询的方式来完成数据的通信。USB设备会在设备驱动程序中设置一个参数叫做interval,它是endpoint的一个成员。interval是间隔时间的意思,表示我这个设备希望主机多长时间来轮询自己,只要这个值确定了之后,我主机就会周期性来查看有没有数据需要处理。
    • 批量处理
      • 支持批量传输最典型的设备就是U盘,它进行大数量的数据传输,能够保证数据的准确性,但是时间不是固定的。
    • 实时传输
      • USB摄像头就是实时传输设备的典型代表,它同样进行大量数据的传输,数据的准确性无法保证,但是对传输延迟非常敏感,也就是说对实时性要求比较高
  • USB端点

    • 每个USB设备与主机会有若干个通信的“端点”。每个端点都有个端点号,除了端点0外,每一个端点只能工作在一种传输类型(控制传输、中断传输、批量传输、实时传输)下,一个传输方向。
    • 传输方向都是基于USB主机的立场说的,比如:鼠标的数据是从鼠标传到PC机,对应的端点称为“中断输入端点”。
    • 端点0是设备的默认控制端点,既能输出也能输入,主要用于USB设备的识别过程。
  • USB主机控制器类型

    要想成为一个USB主机,硬件上就必须要有USB主机控制器,USB主机控制器又分为4种接口:

    • OHCI(Open Host Controller Inerface):微软主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),OHCI接口的硬件简单,软件复杂。
    • UHCI(Universal Host Controller Interface):Intel主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),而UHCI接口的软件简单,硬件复杂。
    • EHCI(Enhace Host Controller Interface):高速USB2.0(480Mbps)
    • xHCI(eXtensible Host Controller Interface):USB3.0(5.0Gbps),采用了9针脚设针,同时也支持USB2.0、1.1等。
  • USB驱动整体框架

    USB系统驱动框架

    USB设备驱动框架

2、USB总线驱动分析

一个 USB 设备是一个非常复杂的事物, 如同在官方的 USB 文档(可从 http://www.usb.org 中得到)中描述的. 幸运的是, Linux 提供了一个子系统 称为 USB 核, 来处理大部分复杂的工作. 《图USB设备驱动框架》 显示了 USB 设备如何包含配置接口端点,,以及 USB 驱动如何绑定到 USB 接口, 而不是整个 USB 设备.

  • 端点:它是USB 通讯的最基本形式,且是一个单向通讯管道,要么从主机到设备(称为输出端点),要么从设备到主机(称为输入端 点)。
    • 端点类型 :一个USB 端点可是 4 种不同类型的一种, 它来描述数据如何被传送。
      • CONTROL(控制端点):通常用作配置、 获取USB设备的信息或状态. 每个 USB 设备都有一个被称"端点0"的控制端点, 它被 USB 核用来在USB设备插入时配置设备。
      • INTERRUPT(中断端点):在每次 USB 主机请求设备数据时, 以固定的速率传送小量的数据。例如鼠标、键盘等就是使用该类型。
      • BULK(块端点) :一般用于需要传送不能有任何数据丢失的大量数据的设备,例如打印机、存储器和网络设备等。
      • ISOCHRONOUS (同步端点):一般用于需要传送可以有部分数据丢失的大量数据的设备,例如音视频实时数据收集设备等。
    • 控制和块端点采用异步数据传送,USB 协议保证前者数据传送的实时性,而后者不保证。
    • 中断和同步端点是周期性的(在固定的时间内连续传送数据),USB 协议保证前者数据传送的实时性,而后者不保证。

2.1 USB描述符的层次及定义

image-20210731131536533

  • USB设备描述符(usb_device_descriptor)
    • USB配置描述符(usb_config_descriptor)
      • USB接口描述符(usb_interface_descriptor)
        • USB端点描述符(usb_endpoint_descriptor)

一个设备描述符可以有多个配置描述符;
一个配置描述符可以有多个接口描述符(比如声卡驱动就有两个接口:录音接口和播放接口)
一个接口描述符可以有多个端点描述符;

2.1.1 USB设备描述符(usb_device_descriptor)

USB设备描述符结构体如下所示:

struct usb_device_descriptor {
     __u8  bLength;					//本描述符的size
     __u8  bDescriptorType;         //描述符的类型,这里是设备描述符DEVICE
     __u16 bcdUSB;                  //指明usb的版本,比如usb2.0
     __u8  bDeviceClass;            //类
     __u8  bDeviceSubClass;         //子类
     __u8  bDeviceProtocol;         //指定协议
     __u8  bMaxPacketSize0;         //端点0对应的最大包大小
     __u16 idVendor;                //厂家ID
     __u16 idProduct;               //产品ID
     __u16 bcdDevice;               //设备的发布号
     __u8  iManufacturer;           //字符串描述符中厂家ID的索引
     __u8  iProduct;                //字符串描述符中产品ID的索引
     __u8  iSerialNumber;           //字符串描述符中设备序列号的索引
     __u8  bNumConfigurations;      //配置描述符的个数,表示有多少个配置描述符
} __attribute__ ((packed));
	USB设备描述符位于USB设备结构体usb_device中的成员descriptor中。同样地,配置、接口、端点描述符也是位于USB配置、接口、端点结构体中,不过这3个对于我们写驱动的不是很常用。
  • usb_device结构体如下所示:

    struct usb_device {
       int devnum;							//设备号,是在USB总线上的地址
       char devpath [16];        		//用于消息的设备ID字符串
       enum usb_device_state state;			//设备状态:已配置、未连接等等
       enum usb_device_speed speed;			//设备速度:高速、全速、低速或错误
        
       struct usb_tt *tt;        		//处理传输者信息;用于低速、全速设备和高速HUB
       int ttport;							//位于tt HUB的设备口
        
       unsigned int toggle[2];				//每个端点占一位,表明端点的方向([0] = IN, [1] = OUT)  
       struct usb_device *parent;			//上一级HUB指针
       struct usb_bus *bus;       		//总线指针
       struct usb_host_endpoint ep0; 		//端点0的数据
       struct device dev;					//通用的设备接口数据结构
       
       struct usb_device_descriptor descriptor;		//USB设备描述符,
       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;             	//每个配置的raw描述符
        
       unsigned short bus_mA;						//可使用的总线电流
    
       u8 portnum;               	//父端口号
       u8 level;                	//USB HUB的层数
      
       unsigned can_submit:1;         //URB可被提交标志
       unsigned discon_suspended:1;			//暂停时断开标志
       unsigned persist_enabled:1;       //USB_PERSIST使能标志
       unsigned have_langid:1;         //string_langid存在标志
       unsigned authorized:1; 
       unsigned authenticated:1;
       unsigned wusb:1;             //无线USB标志
       int string_langid;             //字符串语言ID
      
       /* static strings from the device */ //设备的静态字符串
       char *product;               //产品名
       char *manufacturer;            //厂商名
       char *serial;                //产品串号
      
       struct list_head filelist;         //此设备打开的usbfs文件
    #ifdef CONFIG_USB_DEVICE_CLASS
       struct device *usb_classdev;    //用户空间访问的为usbfs设备创建的USB类设备
    #endif
        
    #ifdef CONFIG_USB_DEVICEFS
       struct dentry *usbfs_dentry;        //设备的usbfs入口
    #endif
      
       int maxchild;                       //(若为HUB)接口数
       struct usb_device *children[USB_MAXCHILDREN];	//连接在这个HUB上的子设备
       int pm_usage_cnt;                 	//自动挂起的使用计数
       u32 quirks; 
       atomic_t urbnum;                   //这个设备所提交的URB计数
      
       unsigned long active_duration;         	//激活后使用计时
    
    #ifdef CONFIG_PM						 //电源管理相关
       struct delayed_work autosuspend;		//自动挂起的延时
       struct work_struct autoresume;		//(中断的)自动唤醒需求
       struct mutex pm_mutex;				//PM的互斥锁 
      
       unsigned long last_busy;				//最后使用的时间
       int autosuspend_delay; 
       unsigned long connect_time;			//第一次连接的时间
      
       unsigned auto_pm:1;					//自动挂起/唤醒
       unsigned do_remote_wakeup:1;			//远程唤醒
       unsigned reset_resume:1;				//使用复位替代唤醒
       unsigned autosuspend_disabled:1;		//挂起关闭
       unsigned autoresume_disabled:1;		//唤醒关闭
       unsigned skip_sys_resume:1;			//跳过下个系统唤醒
    #endif
       struct wusb_dev *wusb_dev;			//(如果为无线USB)连接到WUSB特定的数据结构
    };
    

2.1.2 USB配置描述符

一个 USB 设备可有多个配置并且可能在它们 之间转换以便改变设备的状态,但 一个配置只能在一个时间点上被使能。linux使用结构 struct usb_host_config描述 USB 配置,USB 设备驱动通常不会需要读写这些结构。

struct usb_config_descriptor {   
	__u8  bLength;                   //描述符的长度
    __u8  bDescriptorType;           //描述符类型的编号

	__le16 wTotalLength;               //配置所返回的所有数据的大小
	__u8  bNumInterfaces;              //配置所支持的接口个数, 表示有多少个接口描述符
	__u8  bConfigurationValue;         //Set_Configuration命令需要的参数值
	__u8  iConfiguration;              //描述该配置的字符串的索引值
	__u8  bmAttributes;                //供电模式的选择
	__u8  bMaxPower;                   //设备从总线提取的最大电流
} __attribute__ ((packed));

2.1.3 接口描述符(逻辑设备)

USB接口只处理一种USB逻辑连接。一个USB接口代表一个逻辑上的设备,比如声卡驱动就有两个接口:录音接口和播放接口。这可以在windows系统中看出,有时插入一个USB设备后,系统会识别出多个设备,并安装相应多个的驱动。

struct usb_interface_descriptor {  
      __u8  bLength;                    //描述符的长度
      __u8  bDescriptorType;            //描述符类型的编号

      __u8  bInterfaceNumber;           //接口的编号
      __u8  bAlternateSetting;          //备用的接口描述符编号,提供不同质量的服务参数.
      __u8  bNumEndpoints;              //要使用的端点个数(不包括端点0), 表示有多少个端点描述符,比如鼠标就只有一个端点
      __u8  bInterfaceClass;            //接口类型,与驱动的id_table对应
      __u8  bInterfaceSubClass;         //接口子类型
      __u8  bInterfaceProtocol;         //接口所遵循的协议
      __u8  iInterface;                 //描述该接口的字符串索引值
 } __attribute__ ((packed)

它位于usb_interface->cur_altsetting->desc 这个成员结构体里,

  • usb_interface结构体如下所示:

    struct usb_interface { 
        struct usb_host_interface *altsetting;
        struct usb_host_interface *cur_altsetting; 
        unsigned num_altsetting;
        int minor; 
        ...
    }
    
    • altsetting:指向一个包含所有可用于该接口的可选设置的结构数组。每个 struct usb_host_interface 包含一套端点配置(即struct usb_host_endpoint结构所定义的端点配置)。这些接口结构没有特别的顺序。

    • num_altsetting:由 altsetting 指针指向的预设置的数量。

    • cur_altsetting:指向数组 altsetting 的一个指针, 表示这个接口当前被激活的设置.

    • **minor **:如果绑定到这个接口的 USB 驱动使用 USB 主设备号, 这个变量包含由 USB 核心分配给接口的次设备号. 这只在一个成功的调用 usb_register_dev后才有效。

    • …:编写驱动不需要关心的其他成员

    • 结构体usb_host_interface的定义如下:

      struct usb_host_interface {
          struct usb_interface_descriptor desc;   //当前被激活的接口描述符
          struct usb_host_endpoint *endpoint;   /* 这个接口的所有端点结构体的联合数组*/
          char *string;                 /* 接口描述字符串 */
          unsigned char *extra;           /* 额外的描述符 */
          int extralen;
      };
      
  • 一个 USB 设备驱动通常需要从给定的 struct usb_interface 结构得到 struct usb_device 结构,为此,linux提供有interface_to_usbdev()函数来完成此目的。

2.1.4 端点描述符

  • USB 端点在内核中使用结构 struct usb_host_endpoint 来描述,但真实的端点信息(所有的USB设备特定的数据)却保存在该结构体指向的称为 struct usb_endpoint_descriptor中。

    struct usb_endpoint_descriptor {
        __u8  bLength;                      //描述符的长度
        __u8  bDescriptorType;              //描述符类型的编号
        __u8  bEndpointAddress;             //端点地址(编号)
        __u8  bmAttributes;                 //端点属性, 比如中断传输类型,输入类型
        __le16 wMaxPacketSize;              //一个端点的最大包大小
        __u8  bInterval;                //间隔时间,用在中断传输上,比如间隔时间查询鼠标的数据
    
        /* NOTE:  these two are _only_ in audio endpoints. */
        /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
        __u8  bRefresh;
        __u8  bSynchAddress;
    
    } __attribute__ ((packed));
    
    • bEndpointAddress:端点地址,其中一位反映了端口方向,可利用位掩码 USB_DIR_OUT 或 USB_DIR_IN获知。
    • bmAttributes :端点类型。可利用位掩码 USB_ENDPOINT_XFERTYPE_MASK 与其相与来判断端点是 USB_ENDPOINT_XFER_ISOC, USB_ENDPOINT_XFER_BULKUSB_ENDPOINT_XFER_INT中的一种。
    • wMaxPacketSize :这个端点可一次处理的最大字节数。当真正进行数据传输时时,如果驱动传送比这个值大的数据时, 数据会被分成若干个大小为 wMaxPakcetSize 的块. 对于高速设备, 这个成员可用来支持高带宽模式,即通过在这个值的高位部分使用几个额外位( 细节见 USB 规范)。
  • bInterval :如果这个端点是中断类型的, 这个值是为这个端点设置的间隔(单位毫秒)。

  • endpoint的结构体为usb_host_endpoint,如下所示:

    • 假设端点0就位于usb_interface->cur_altsetting->desc->endpoint[0].desc
    struct usb_host_endpoint {
          struct usb_endpoint_descriptor desc; 			//端点描述符
          struct usb_ss_ep_comp_descriptor ss_ep_comp;  //超快速端点描述符
          struct list_head urb_list; 					//本端口对应的urb链表
          void *hcpriv;
          struct ep_device *ep_dev; 					/* For sysfs info */
    
        unsigned char *extra; 							/* Extra descriptors */
        int extralen;
        int enabled;									//使能的话urb才能被提交到此端口
    

};





## 2.2 USB在sysfs中的表示

由于单个 USB 物理设备的复杂性, 设备在 sysfs 中的表示也非常复杂. 物理  USB 设备(通过 struct usb_device 表示)和单个 USB 接口(由 struct  usb_interface 表示)都作为单个设备出现在 sysfs . (这是因为这 2 个结构都 包含一个 struct device 结构)。

### 2.2.1 内核如何标识 USB 设备?

- 第一个 USB 设备是一个**根集线器**. 这也是 USB 控制器, 常常包含在一个 PCI 设 备中. 控制器的命名是由于它控制整个连接到它上面的 USB 总线. 控制器是一 个 PCI 总线和 USB 总线之间的桥, 同时是总线上的第一个设备。

-  USB 核为所有的根集线器分配了一个唯一的号。例如usb1、usb2等等。可包含在单个系统中的根集线器的数目没有限制。

-  USB设备在 sysfs 文件系统中的命名方法:**root_hub-hub_port:config.interface**。即采用根集线器的`序号`作为它的名子的第一个数字紧跟`-` 字符和设备插入的`端口号`,再跟`:` 字符和设备的`配置序号`紧跟`.` 字符和`接口序号`,例如`2-1:1.0`表示连接在系统第二个USB根集线器的第一个端口下的第一个配置中的0号接口。

- 随着设备在 USB 树中进一步拓展,设备名也将越来越长。对一个 2 层的树, 设备 名看来象:  **root_hub-hub_port-hub_port:config.interface**

- 一个USB鼠标在sysfs中的目录结构为:

```c
/sys/devices/pci0000:00/0000:00:09.0/usb2/2-1 			// 设备的usb_device结构
|-- 2-1:1.0 											// 鼠标驱动被绑定到的接口
| |-- bAlternateSetting 
| |-- bInterfaceClass 
| |-- bInterfaceNumber 
| |-- bInterfaceProtocol 
| |-- bInterfaceSubClass 
| |-- bNumEndpoints 
| |-- detach_state 
| |-- iInterface 
| `-- power 
| `-- state 
|-- bConfigurationValue 
|-- bDeviceClass 
|-- bDeviceProtocol 
|-- bDeviceSubClass 
|-- bMaxPower 
|-- bNumConfigurations 
|-- bNumInterfaces 
|-- bcdDevice 
|-- bmAttributes 
|-- detach_state 
|-- devnum 
|-- idProduct 
|-- idVendor 
|-- maxchild 
|-- power 
| `-- state 
|-- speed 
`-- version 
  • 所有的 USB 特定信息可直接从 sysfs 获得(例如, idVendor, idProduct, 和 bMaxPower 信息)
  • 可写文件bConfigrationValue,可被改写以改变激活的正被使用的 USB 配置。
  • sysfs只显示USB接口层面的信息,任何USB设备可能的配置及其端点信息可在 usbfs 文件系统中找到,其在内核中的目录为/proc/bus/usb/devices
  • usbfs 文件系统允许在用户空间直接操作它里面的USB设备,所以使许多内核驱动被可以移出到易维护和调试的用户空间。例如USB 扫描器驱动就在包含在用户空间的 SANE 库程序中

2.3 urb结构体

  • linux 内核中的 USB核和所有的 USB 设备使用请求块( USB request block)进行通讯,这个请求块用 struct urb 结构描述并且可在 include/linux/usb.h 中找到。

  • 一个 urb 用来从一个特定 USB 设备上的特定的 USB 端 点, 以一种异步的方式发送或接受数据。它非常像被用在文件系统异步 I/O 代码中的kiocb 结构,或用在网络代码中的 struct skbuff结构。

  • 根据驱动的需要,一个 USB 设备驱动可能分配多个 urb 给一个端点,或者可能重用单个 urb 给多个不同的端点。设备中的每个端点都维护一个 urb 队列,多个 urb 可被发送到相同的端点。

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

    1. 被一个 USB 设备驱动创建.
    2. 安排给一个特定 USB 设备的特定端点.
    3. 被 USB 设备驱动提交给 USB 核心 .
    4. 被 USB 核心指定的 USB 主机控制器驱动提交给特定设备 .
    5. 被 USB 主机控制器处理, 它做一个 USB 传送到设备.
    6. USB 主机控制器驱动通知 USB 设备驱动本次 urb 完成.
  • 提交 urb 的驱动可在任何时间取消提交, USB 核心也可在设备被拔出时取消它提交的urb。

  • urb 被动态创建并且包含一个内部引用计数,只有计数值为0时才会释放该urb。

  • urb 的使用主要是为了获得最高可能的数据传送速度,但若没有速度要求,例如只是想发送单独的块或者控制消息, 并且不关心数据吞吐率时,就不必使用urb了。也就是说USB传送可以不用urb。

2.3.1 urb 结构中和 USB 设备驱动有关的成员

  • struct usb_device *dev :指向 urb 要发送到的USB设备。在 urb 被发送到 USB 核之前,它必须 被 USB 驱动初始化。

  • unsigned int pipe:指向 urb 要发送到的USB设备的端点。在 urb 被发送到 USB 核之前,它必须 被 USB 驱动初始化。

    • 为了便于初始化上述两个变量,linux内核提供了如下初始化函数:
    //1.给带有特定端点号endpoint的特定USB设备dev指定一个控制OUT端点.
    unsigned int usb_sndctrlpipe(struct usb_device *dev, unsigned int endpoint);
    
    //2.给带有特定端点号endpoint的特定USB设备dev指定一个控制IN端点.
    unsigned int usb_rcvctrlpipe(struct usb_device *dev, unsigned int endpoint);
    
    //3.给带有特定端点号endpoint的特定USB设备dev指定一个块OUT端点.
    unsigned int usb_sndbulkpipe(struct usb_device *dev, unsigned int endpoint);
    
    //4.给带有特定端点号endpoint的特定USB设备dev指定一个块IN端点.
    unsigned int usb_rcvbulkpipe(struct usb_device *dev, unsigned int endpoint);
    
    //5.给带有特定端点号endpoint的特定USB设备dev指定一个中断OUT端点.
    unsigned int usb_sndintpipe(struct usb_device *dev, unsigned int endpoint);
    
    //6.给带有特定端点号endpoint的特定USB设备dev指定一个中断IN端点.
    unsigned int usb_rcvintpipe(struct usb_device *dev, unsigned int endpoint);
    
    //7.给带有特定端点号endpoint的特定USB设备dev指定一个同步OUT端点.
    unsigned int usb_sndisocpipe(struct usb_device *dev, unsigned int endpoint);
    
    //8.给带有特定端点号endpoint的特定USB设备dev指定一个同步IN端点.
    unsigned int usb_rcvisocpipe(struct usb_device *dev, unsigned int endpoint); 
    
  • unsigned int transfer_flags :根据 USB 驱动想这个 urb 发生什 么,这个变量可被设置为以下不同的位值:

    1. URB_SHORT_NOT_OK:当置位, 它指出任何在某IN端点上可能发生的短读, 应当被 USB 核 心当作一个错误。这个值只对从 USB 设备读的 urb 有用, 不是写 urbs。
    2. **URB_ISO_ASAP **:对于同步 urb, 只要设置了该位,如果驱动想调度这个 urb,只要带宽允许,且在此点设置这个 urb 中的 start_frame 变 量即可;如果这个位没有被置位,如果没有在那个时刻启动,驱动必须指定 start_frame 值并且必须能够正确恢复。
    3. **URB_NO_TRANSFER_DMA_MAP **:当 urb 包含一个要被发送的 DMA 缓冲时,应当被置位。此时USB 核心使用这 个被 transfer_dma 变量指向的缓冲, 不是被 transfer_buffer 变量指向的缓冲.
    4. **URB_NO_SETUP_DMA_MAP **:当 urb 已经建立了一个 DMA 缓冲时,如果它被置位, USB 核心使用这个被 setup_dma 变量而 不是 setup_packet 变量指向的缓冲.
    5. URB_ASYNC_UNLINK:如果置位, 对 usb_unlink_urb 的调用几乎立刻返回, 并 且这个 urb 在后面被解除连接. 否则, 这个函数等待直到 urb 完全被 去链并且在返回前结束. 小心使用这个位, 因为它可有非常难于调试的 同步问题.
    6. URB_NO_FSBR:只有 UHCI USB 主机控制器驱动使用, 并且告诉它不要试图做 Front Side Bus Reclamation 逻辑. 这个位通常应当不设置。
    7. URB_ZERO_PACKET:如果置位,当数据需要对齐到一个端点报文边界时,一个块 OUT urb 可以通过发送不包含数据的短报文而结束。
    8. URB_NO_INTERRUPT:如果置位, 当 urb 结束时硬件可能不产生一个中断。这个位只在多个排队到相同端点的 urb 时使用,USB 核心函数使用这个 为了做 DMA 缓冲传送。
  • void *transfer_buffer :指向用在发送数据到设备(对一个 OUT urb)或者从设备中获取数据(对于 一个 IN urb)的缓冲的指针. 对主机控制器而言,这个缓冲必须使用kmalloc 来创建, 而不是在栈或者静态分配。对控制端点, 这个缓冲是给发送的数据阶段。

  • dma_addr_t transfer_dma:用来使用 DMA 传送数据到 USB 设备的缓冲。

  • int transfer_buffer_length:缓冲区(transfer_buffer 或者 transfer_dma)的长度。0表示没有传送缓冲被 USB 核使用。对于一个 OUT 端点而言,在一个 urb 中提交一个大块数据(大于端点一次可传送的最大数据量),使 USB 主机控制器去划分为更小的块去传送,比以连续的顺序发送小的缓冲块要快。

  • unsigned char *setup_packet :指向给一个控制 urb 的 setup 报文的指针. 它在位于传送缓冲中的数 据之前被传送. 这个变量只对控制 urb 有效.

  • dma_addr_t setup_dma:给控制 urb 的 setupt 报文的 DMA 缓冲. 在位于正常传送缓冲的数据 之前被传送. 这个变量只对控制 urb 有效.

  • usb_complete_t complete :当一个 urb 被完全传送或者当 urb 发生一个错误时, USB 核心调用该变量指向的完成处理者函数(类似于回调函数),在这个函数中, USB 驱动可检查这个 urb, 要么释放它, 要么重新提交另一次传送给它。

    • typedef void (*usb_complete_t)(struct urb *, struct pt_regs *);
  • void *context:指向数据点的指针, 它可被 USB 驱动设置。当 urb 返回到驱动时,它可在完成处理者中使用。

  • int actual_length:当urb 被完成时, 这个变量被设置为已传送数据的真实长度。特别对于 IN urb, 这个必须被用来替代 transfer_buffer_length 变量, 因为接收的数据可能比缓冲区小.

  • int status :urb 的当前状态。 USB 驱动可在 urb 完成后调用处理者函数中安全的存取该值。对于同步 urb, 状态成功(0)只代表这个 urb 是否已被去链. 为获得同步 urb 的详细状态, 应当检查 iso_frame_desc 变量。status的值包含以下可能的取值:

    1. 0: urb 传送成功;

    2. -ENOENT:这个 urb 被对 usb_kill_urb 的调用停止;

    3. -ECONNRESET:urb 被对 usb_unlink_urb 的调用去链, 并且 transfer_flags 变量被 设置为 URB_ASYNC_UNLINK;

    4. **-EINPROGRESS **:这个 urb 仍然在被 USB 主机控制器处理中;

    5. -EPROTO:一个 bitstuff 错误在传送中发生,或者硬件没有及时收到响应帧。

    6. -EILSEQ:这个 urb 传送中有一个 CRC 不匹配;

    7. -EPIPE:这个端点现在被停止. 如果这个包含的端点不是一个控制端点, 这个错 误可被清除通过一个对函数 usb_clear_halt 的调用.

    8. -ECOMM :在传送中数据接收快于能被写入系统内存. 这个错误值只对 IN urb.

    9. -ENOSR :在传送中数据不能及时从系统内存中获取, 以便可跟上请求的 USB 数据速率. 这个错误只对 OUT urb.

    10. -EOVERFLOW : 这个 urb 发生一个"babble"错误.(端点接受的数据多于端点的特定最大报文大小).

    11. -EREMOTEIO :只发生在当 URB_SHORT_NOT_OK 标志被设置在 urb 的 transfer_flags 变量, 并且意味着 urb 请求的完整数量的数据没有收到.

    12. -ENODEV :系统中没有这个 USB 设备(设备被拔出).

    13. -EXDEV :只对同步 urb 发生, 并且意味着传送只部分完成. 为了决定传送什么, 驱动必须看单独的帧状态.

    14. -EINVAL :这个 urb 发生了非常坏的事情. 例如一个参数在 urb 结构中被不正确地设置了, 或者如果 在提交这个 urb 给 USB 核心的 usb_submit_urb 调用中, 有一个不正 确的函数参数.

    15. -ESHUTDOWN:这个 urb 被提交给的设备已掉线

      通常, 错误值 -EPROTO, -EILSEQ, 和 -EOVERFLOW 指示设备的硬件问题。

  • int start_frame :设置或返回同步传送要使用的初始帧号.

  • int interval : urb被轮询的间隔,这只对中断或者同步 urb 有效。这个值的单位依据设备速度而不同,对于低速和高速的设备, 单位是帧(等同于毫秒),对于单位是宏帧的设备, 它等同于 1/8 微秒。 在这个 urb 被发送到 USB 核心之 前,这个值必须先被 USB 驱动设置给同步或者中断 urb。

  • int number_of_packets :只对同步 urb 有效, 并且指定这个 urb 要处理的同步传送缓冲的编号. 在这个 urb 发送给 USB 核心之前,这个值必须先被 USB 驱动设置给同步 urb。

  • int error_count :在同步 urb 完成之后,被 USB 核心设置。它指定同步传送发生的错误次数.

  • struct usb_iso_packet_descriptor iso_frame_desc[0] :只对同步 urb 有效. 这个变量是组成这个 urb 的一个 struct usb_iso_packet_descriptor 结构数组. 这个结构允许单个 urb 来一次 定义多个同步传送. 它也用来收集每个单独传送的传送状态. 结构 usb_iso_packet_descriptor 由下列成员组成:

    • unsigned int offset :报文数据所在的传送缓冲中的偏移(第一个字节从 0 开始).
    • unsigned int length :这个报文的传送缓冲的长度.
    • unsigned int actual_length :接收到给这个同步报文的传送缓冲的数据长度.
    • unsigned int status :这个报文的单独同步传送的状态. 它可采用同样的返回值如同主 struct urb 结构的状态变量.

2.3.2 urb的创建和销毁

  • 为了不破坏 USB 核心用于统计 urb 引用计数的函数,struct urb 结构在驱动中必须使用kmalloc动态创建。为此,Linux内核专门提供了分配和回收函数:
    • struct urb *usb_alloc_urb(int iso_packets, int mem_flags);
      • iso_packet:这个 urb 包含的同步报文的数目。若是非同步urb,取值为0.
      • mem_flags:传递给 kmalloc 函数调用来从内核分配内存的相同的标志类型。
    • void usb_free_urb(struct urb *urb);
      • urb:被释放的 struct urb 的指针。
  • 在 urb 被成功分配后, 一个 DMA 缓冲也应当被创建
    • void *usb_buffer_alloc(struct usb_device *, int count, int mem_flags, &urb->transfer_dma);

2.3.3 urb的初始化

  • 初始化为中断urb

    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,\
                          void *context, int interval); 
    
    • struct urb *urb :指向要被初始化的 urb 的指针.
    • struct usb_device *dev: 这个 urb 要发送到的 USB 设备.
    • unsigned int pipe: 这个 urb 要被发送到的 USB 设备的特定端点. 使用前面提过的 usb_sndintpipe() 或者 usb_rcvintpipe() 函数创建.
    • void *transfer_buffer: 指向缓冲的指针. 注意这必须使用 kmalloc 调用来创建缓冲,而不能是一个静态的缓冲.
    • int buffer_length: 被 transfer_buffer 指针指向的缓冲的长度.
    • usb_complete_t complete: 指向当这个 urb 完成时被调用的"完成处理函数".
    • void *context: 指向数据块的指针, 为以后被完成处理者函数获取.
    • int interval: 这个 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,\
                           void *context); 
    
    • 参数涵义同中断urb,因为bulk urb没有间隔值,故没有interval参数;
    • pipe 变量必须用 usb_sndbulkpipe 或者 usb_rcvbulkpipe 函数的调用初始化;

由于以上函数都不设置 urb 中的 transfer_flags 变量, 因此任何对这个成员的修改不得不由驱动自己完成

  • 初始化为控制urb

    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, void *context); 
    
    • 大部分参数涵义同块urb,除了unsigned char *setup_packet,它必须指向要发送给端点的 setup 报文数据。
    • pipe 变量必须用 usb_sndctrlpipe 或者 usb_rcvictrlpipe 函数的调用初始化。
    • 大部分驱动不使用这个函数, 因为不用 urb同步 API 调用更简单。
  • 初始化为同步urb

    • 同步 urb 没有像中断, 控制, 和块 urb 那样的初始化函数,在被提交给 USB 核心之前,必须在驱动中"手动"初始化。下面是一 个如何正确初始化这类 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; 
      } 
      

2.3.4 urb的提交

一旦 urb 被正确地创建,并且被 USB 驱动初始化, 它已准备好被提交给 USB 核心来发送出到 USB 设备。以下函数完成该功能:

int usb_submit_urb(struct urb *urb, int mem_flags);		//成功返回0,失败返回负值错误码
  • urb:被提交的urb指针
  • mem_flags:告诉 USB 核心如何分配内存缓冲,等同于传递给 kmalloc 调用的同样的参数;因为函数 usb_submit_urb 可被在任何时候被调用(包括从一个中断上下文), mem_flags 变量的指定必须正确,他有以下取值:
    • GFP_ATOMIC:只要满足以下任一条件,就需要使用该宏
      • 调用者处于一个 urb 完成处理者函数, 一个中断, 一个中断后半部, 一个tasklet, 或者一个时钟回调函数中。
      • 调用者持有一个自旋锁或者读写锁;
      • current->state 不是 TASK_RUNNING;
    • GFP_NOIO:如果驱动在块 I/O 补丁中或所有存储类型的错误处理补丁中。
    • GFP_KERNEL:当用在所有其他的情况中。

2.3.5 urb的回调函数

当 urb 符合以下任一情况时,在初始化时指定的回调函数complete将被调用一次:

  1. urb 被成功发送给设备, 并且设备返回正确的确认信号。此时urb的状态变量被设置为0
  2. urb在数据传送中连续发生错误,urb传送被系统停止,urb的状态变量被设置为对应的错误值
  3. urb 被从 USB 核心去链(驱动通过调用 usb_unlink_urb 或者 usb_kill_urb函数取消一个已提交的urb,或者urb将要被传送到的设备已从系统中被去除)。
  • void (*usb_complete_t)(struct urb *, struct pt_regs *);

    • 回调函数做的第一件事是检查 urb 的状态来决定这个 urb 是否成功完成。注意:错误值, -ENOENT, -ECONNRESET, 和 -ESHUTDOWN 不是真正的传送错误, 只是报告伴随成功传送的情况.
    • 接着这个回调释放安排给这个 urb 传送的已分配的缓冲.
    • 注意,urb 回调是在中断上下文运行, 因此它不应当做任何内存分配, 持有任何锁, 或者任何可导致进程睡眠的事情。
    • 当需要从回调中提交 urb, 使用 GFP_ATOMIC 标志来告知 USB 核心不要睡眠。
  • 一个回调函数的例子:

    static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs) 
    { 
         /* sync/async unlink faults aren't errors */ 
         if (urb->status && 
             !(urb->status == -ENOENT || 
             urb->status == -ECONNRESET || 
             urb->status == -ESHUTDOWN))
         { 
        	dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status); 
         } 
         /* free up our allocated buffer */ 
         usb_buffer_free(urb->dev, urb->transfer_buffer_length,\
                         urb->transfer_buffer, urb->transfer_dma); 
    }
    

2.3.6 urb的取消

当需要停止一个已经提交给 USB 核心的 urb时,可采用以下函数:

  • int usb_kill_urb(struct urb *urb);——常用在urb要送达的设备从系统被去除时使用,常用在去连接的回调函数中。
  • int usb_unlink_urb(struct urb *urb); ——该函数是非阻塞的,可用在中断处理或者持有一个自旋锁时停止 urb,但这个函数要求urb的 URB_ASYNC_UNLINK 标志值被设置。

2.4 无 urb 的 USB 传送

当需要进行大量数据传送时,选择urb是对的。但若仅是传送一些简单的数据,我们有一些方便的函数可以达到此目的,而不必创建繁琐的urb。

2.4.1 usb_bulk_msg

  • 该函数自动创建一个 USB 块 urb 并且发送它到特定的设备, 等待发送完成后返回到调用者。

    int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,\
                     void *data, int len, int *actual_length,\
                     int timeout);
    
    • usb_dev:指向发往的 USB 设备;
    • pipe:要发送到的 USB 设备的特定端点,使用usb_sndbulkpipe 或者 usb_rcvbulkpipe 函数创建pipe的值。
    • data:指向要发往设备的数据的指针或者是存储接收来自设备的数据的缓存区地址
    • len:data 参数指向的缓冲的长度
    • actual_length:指向函数放置真实字节数的指针
    • timeout:等待超时时间(滴答数),若为0,则永远等待直至消息传输结束。
    • 返回值:成功返回0,且actual_length 参数包含被传送 或从消息中获取的字节数,否则返回负值错误码。
  • 应用举例:

    /* 例子展示了一个简单的从一个 IN 端点的块读. 如果读取成功, 数据接着被拷贝到用户空间 */ 
    retval = usb_bulk_msg(dev->udev,\
                          usb_rcvbulkpipe(dev->udev,dev->bulk_in_endpointAddr),\
                          dev->bulk_in_buffer,\
                          min(dev->bulk_in_size, count),\
                          &count, HZ*10); 
    
    /* if the read was successful, copy the data to user space */ 
    if (!retval) 
    { 
     	if (copy_to_user(buffer, dev->bulk_in_buffer, count)) 
     		retval = -EFAULT; 
     	else 
     		retval = count; 
    }
    
  • 注意事项

    1. 不能从中断上下文中或者在持有自旋锁期间调用usb_bulk_msg,因为该函数会导致睡眠。
    2. 函数不能被任何其他函数取消;
    3. 要确认驱动的disconnect函数能预留足够多的时间(在它自己被卸载之前)去等待该函数的完成。

2.4.2 usb_control_msg

  • 除了允许驱动发送结束 USB 控制信息外,它和usb_bulk_msg功能一样。

    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:要发送到的 USB 设备的特定端点;
    • request:控制消息的 USB 请求值;
    • requesttype:控制消息的 USB 请求类型;
    • value:控制消息的 USB 消息值;
    • index:控制消息的 USB 消息索引值;以上4个参数见USB规范第9章。
    • data:指向要发往设备的数据的指针或者是存储接收来自设备的数据的缓存区地址;
    • size:data 参数指向的缓冲的大小;
    • timeout:等待超时时间(滴答数),若为0,则永远等待直至消息传输结束。
    • 返回值:成功,返回被传送 或从消息中获取的字节数,否则返回负值错误码。
  • 注意事项

    1. 不能从中断上下文中或者在持有自旋锁期间调用usb_bulk_msg,因为该函数会导致睡眠。
    2. 函数不能被任何其他函数取消;
    3. 要确认驱动的disconnect函数能预留足够多的时间(在它自己被卸载之前)去等待该函数的完成。

2.4.3 USB设备信息获取函数

  • 前提条件:这些函 数不能从中断上下文或者持有自旋锁时调用.

  • 当USB 驱动想从usb_device 结构中, 获取任何还没有在 usb_device 和 usb_interface 结构中出现的设备描述符时,可调用以下函数:

  • int usb_get_descriptor(struct usb_device *dev, unsigned char type,\
                           unsigned char index, void *buf, int size);
    
    • dev:指向应当从中获取描述符的 USB 设备;

    • type :描述符类型,取值是下列类型之一

    USB_DT_DEVICE				USB_DT_CONFIG 				 USB_DT_INTERFACE 	
    USB_DT_ENDPOINT 			USB_DT_DEVICE_QUALIFIER		 USB_DT_STRING	 
    USB_DT_OTHER_SPEED_CONFIG 	USB_DT_INTERFACE_POWER 		 USB_DT_OTG 
    USB_DT_DEBUG 				USB_DT_INTERFACE_ASSOCIATION USB_DT_CS_DEVICE 
    USB_DT_CS_CONFIG 			USB_DT_CS_STRING			 USB_DT_CS_INTERFACE 
    USB_DT_CS_ENDPOINT			 
    
    • index:希望从设备获取的描述符的数目;

    • buf:存储描述符信息的缓冲区指针;

    • size:由 buf 变量指向的内存的大小

  • usb_get_descripter 调用的一项最普遍的用法是从 USB 设备获取一个字符串,所以linux又专门为这个功能提供了两个函数:

    1. usb_get_string

      int usb_get_string(struct usb_device *dev, unsigned short langid,\
                         unsigned char index, void *buf, int size); 
      
      • 成功时返回字符串字节数,失败时返回负值错误码;
      • 成功时,buf将会收到一个以 UTF-16LE 格式编码的字符串(Unicode, 16 位每字符, 小端字节序)
    2. usb_string

      int usb_string(struct usb_device *dev, unsigned short langid,\
                     void *buf, int size); 
      
      • 参数同上,其中langid是结构体usb_device_descriptor中的某一项

      • 但它buf中存放的是一个已经转化为 ISO 8859-1格式编码的字符串,而该格式是USB设备的字符串典型格式。

      • 使用举例:

        static int af9015_probe(struct usb_interface *intf,\
                                const struct usb_device_id *id)
        {
            struct usb_device *udev = interface_to_usbdev(intf);
            char manufacturer[sizeof("ITE Technologies, Inc.")];
        
            memset(manufacturer, 0, sizeof(manufacturer));
            usb_string(udev, udev->descriptor.iManufacturer,\
                       manufacturer, sizeof(manufacturer));
            /*
             * There is two devices having same ID but different chipset. One uses
             * AF9015 and the other IT9135 chipset. Only difference seen on lsusb
             * is iManufacturer string.
             *
             * idVendor           0x0ccd TerraTec Electronic GmbH
             * idProduct          0x0099
             * bcdDevice            2.00
             * iManufacturer           1 Afatech
             * iProduct                2 DVB-T 2
             *
             * idVendor           0x0ccd TerraTec Electronic GmbH
             * idProduct          0x0099
             * bcdDevice            2.00
             * iManufacturer           1 ITE Technologies, Inc.
             * iProduct                2 DVB-T TV Stick
             */
            if ((le16_to_cpu(udev->descriptor.idVendor) == USB_VID_TERRATEC) &&\
                    (le16_to_cpu(udev->descriptor.idProduct) == 0x0099)) 
            {
                if (!strcmp("ITE Technologies, Inc.", manufacturer)) 
                {
                    dev_dbg(&udev->dev, "%s: rejecting device\n", __func__);
                    return -ENODEV;
                }
            }
        
            return dvb_usbv2_probe(intf, id);
        }
        

2.5 注册一个 USB 驱动

  • 所有 USB 驱动必须创建的主要结构是 struct usb_driver. 这个结构必须被 USB 驱动填充,通常只有 5 个成员需要被初始化:
static struct usb_driver skel_driver = { 
 .owner = THIS_MODULE, 
 .name = "skeleton", 
 .id_table = skel_table, 
 .probe = skel_probe, 
 .disconnect = skel_disconnect, 
};
  1. struct module *owner:指向这个驱动的模块拥有者,USB 核心使用它正确地对这 个 USB 驱动进行引用计数。一般设置为THIS_MODULE宏.

  2. const char *name:指向驱动名。它在内核 USB 驱动中必须是唯一的,并且通常和驱动的模块名相同。当驱动被加载到内核中时,它出现在/sys/bus/usb/drivers/ 之下。

  3. const struct usb_device_id *id_table :包含这个驱动可接受的所有不同类型 USB 设备的列表. 如果这个变量没被设置, USB 驱动中的probe函数不会被调用。如果你想你的驱动可被系统中每个 USB 设备调用, 你只需创建一个只设置了 driver_info 成员的入口项:

    static struct usb_device_id usb_ids[] = {
        {.driver_info = 42},
        {} 
    }; 
    
  4. int (*probe) (struct usb_interface *intf, const struct usb_device_id *id) :指向 USB 驱动中的探测函数,当USB核心发现系统中有一个USB设备(struct usb_interface)可被该驱动驱动时,它将调用probe指向的函数。成功返回0,失败返回负值错误码。

  5. void (*disconnect) (struct usb_interface *intf):指向去连接函数。当 struct usb_interface 已被从系统中清除或者当驱动被从 USB 核心卸载时调用。

  • 除了以上5个成员外,还有以下几个不常用的成员项:

    • int (*ioctl) (struct usb_interface *intf, unsigned int code, void *buf):只有 USB 集线器驱动使用这个 ioctl,在用户空间对一个关联到 USB 设备的usbfs文件系统做IO系统调用时,会触发该iocctl被调用。
    • int (*suspend) (struct usb_interface *intf, u32 state): 当设备要被 USB 核心悬挂时被调 用
    • int (*resume) (struct usb_interface *intf) :当设备正被 USB 核心恢复时被调 用。
  • 一般在 USB 驱动的模块初始化代码中注册 struct usb_driver 到 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; 
    } 
    module_init(usb_skel_init);
    
  • 一般在 USB 驱动的模块卸载代码中将 struct usb_driver 从USB 核心注销,此后任何当前绑定到这 个驱动的 USB 接口被去连接, 并且去连接函数被调用。使用的函数模板为:

    static void __exit usb_skel_exit(void) 
    { 
         /* deregister this driver with the USB subsystem */ 
         usb_deregister(&skel_driver); 
    } 
    module_exit(usb_skel_exit);
    

2.6 探测和去连接函数

  • 在usb_driver结构中,描述了两个USB 核心在合 适的时候调用的函数:

    1. 探测函数probe:当设备被安装时, USB 核心认为这个驱动可以处理该设备时,便会调用驱动的探测函数,此时,探测函数应当对传递给它的设备信息进行检查, 并且决定驱动是否真正合适那个设备.
    2. 去连接函数disconnect:当驱动应当不再控制设备时被调用做一些清理工作。
  • 需要注意的是,以上2个函数都是在USB 集线器内核线程上下文中被调用,因此它们睡眠是合法的。但是, 为了保持 USB 探测时间为最小,建议大部分工作应当在设备被用户打开时完成。

  • 在探测函数回调中, USB 驱动应当初始化任何它将来可能使用来管理 USB 设备的本地结构, 因为在此时做这些通常更容易。例如,驱动经常需要获取USB设备的端点地址和缓冲大小等信息。一个探测 BULK 类型的 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;
        //判断端点的方向是否是IN,端点类型是否是块
         if (!dev->bulk_in_endpointAddr &&\
             (endpoint->bEndpointAddress & USB_DIR_IN) &&\
             ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)	== USB_ENDPOINT_XFER_BULK))
         {   /* we found a bulk in endpoint */ 
             //保存关于端点的信息到本地结构中, 驱动后来将需要这些信息和它通讯
             buffer_size = 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; 
         	 } 
    	 } 
         //判断端点的方向是否是OUT,端点类型是否是块
     	 if (!dev->bulk_out_endpointAddr &&\
             !(endpoint->bEndpointAddress & 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; 
    } 
    
  • 在设备的生命周期后期,USB 驱动需要获取一个和该设备关联的usb_interface数据结构,为此Linux提供了如下函数:

    /* save our data pointer in this interface device */ 
    usb_set_intfdata(struct usb_interface *interface, void *dev); 
    
    • 该函数接受一个指向任何数据类型的指针,并且保存它到 struct usb_interface 结构,并为后面使用。
  • 为了获取上面保存的数据,可以调用如下函数:

    void *usb_get_intfdata(struct usb_interface *interface); 
    
    • 在 USB 驱动的 open 函数和在去连接函数中,该函数经常被使用。
  • 正是因为使用上面两个函数, USB 驱动不需要为系统中所有当前的设备保持一个静态指针数组来保存单个设备结构。这种对设备信息的非直接使得USB驱动支持无限数目的设备。

  • 一个使用usb_get_intfdata的例子如下:

    struct usb_skel *dev; 
    struct usb_interface *interface; 
    int subminor; 
    int retval = 0;
    subminor = iminor(inode); 
    interface = usb_find_interface(&skel_driver, subminor); 
    if (!interface) 
    { 
         err ("%s - error, can't find device for minor %d", __FUNCTION__, subminor); 
         retval = -ENODEV; 
         goto exit; 
    } 
    dev = usb_get_intfdata(interface); 
    if (!dev) 
    { 
         retval = -ENODEV; 
         goto exit; 
    }
    
  • 如果 USB 驱动没有和另一种处理用户和设备交互的子系统(例如 input, tty, video, 等待)关联,,为了使用传统的和用户空间之间的交互,驱动可使用 USB 主设备号来创建一个字符驱动接口。为此, USB 驱动必须在探测函数中调用 usb_register_dev 函 数。

    int usb_register_dev(struct usb_interface *, struct usb_class_driver *); 
    
    • usb_class_driver包含了许多参数:

      • char *name sysfs:用来描述设备的名子。用在 devfs 文件系统中。 如果设备号需要显示在名子中, 字符 %d 应当在名子串中。例如要创建usb/foo1 和 sysfs 类名 foo1, 名子串应当设置为 usb/foo%d
      • struct file_operations *fops:驱动已定义用来注册为字符设备.
      • mode_t mode:若要在 devfs 文件系统中要被创建的模式,否则不使用。这个变量的典型设置是值 S_IRUSR 和 值 S_IWUSR 的结合。
      • int minor_base:驱动的起始次设备号。 只有 16 个设备被允许在任何时刻和这个驱动关联, 除非 CONFIG_USB_DYNAMIC_MINORS 配置选项被打开。此时可忽略这个变量,以后这个设备的所有的次设备号被以先来先服务的方式分配。建议打开了这个选项的系统,使用 udev 来关联系统中的设备节点。
      /* we can register the device now, as it is ready */ 
      retval = usb_register_dev(  interface, &skel_class); 
      if (retval) 
      { 
           /* something prevented us from registering this driver */ 
           err("Not able to get a minor for this device."); 
           usb_set_intfdata(interface, NULL); 
           goto error; 
      }
      
  • 如果 usb_register_dev 已被在探测函数中调用来分配一个 USB 设备的次设备号,当 USB 设备断开, 所有的关联到这个设备的资源应当被清除(将次设备号返还给 USB 核心),Linux提供了一个函数来完成此任务:

    usb_deregister_dev(struct usb_interface *, struct usb_class_driver *); 
    
  • 当然,在去连接函数中,还需要从接口获取之前调用 usb_set_intfdata 所设置的 数据. 接着设置数据指针在 struct us_interface 结构为 NULL 来阻止在不正 确存取数据中的任何进一步的错误:

    static void skel_disconnect(struct usb_interface *interface) 
    { 
         struct usb_skel *dev; 
         int minor = interface->minor; 
        
         /* prevent skel_open() from racing skel_disconnect( ) */ 
         lock_kernel(); 
         dev = usb_get_intfdata(interface); 
         usb_set_intfdata(interface, NULL); 
        
         /* give back our minor */ 
         usb_deregister_dev(interface, &skel_class); 
         unlock_kernel(); /* decrement our usage count */ 
         kref_put(&dev->kref, skel_delete); 
         info("USB Skeleton #%d now disconnected", minor); 
    }
    
    • 在 disconnect 函数被调用后,所有的当前在被传送的 urb 可被 USB 核心自动取消, 因此驱动不必明确为这些 urb 调用 usb_kill_urb。若此后驱动还像该设备提交urb时,将返回-EPIPE错误。

2.7 涉及usb的结构和函数

  • #include <linux/usb.h> :所有和 USB 相关的头文件. 它必须被所有的 USB 设备驱动包含

  • struct usb_driver:描述 USB 驱动的结构;

  • struct usb_device_id: 描述这个驱动支持的 USB 设备的结构.

  • int usb_register(struct usb_driver *d) : 用来从 USB 核心注册和注销一个 USB 驱动的函数.

  • struct usb_device *interface_to_usbdev(struct usb_interface *intf): 从 struct usb_interface 获取控制 struct usb_device *.

  • struct usb_device:控制完整 USB 设备的结构.

  • struct usb_interface:主 USB 设备结构, 用来和 USB 核心通讯的所有的 USB 驱动。

  • void usb_set_intfdata(struct usb_interface *intf, void *data):设置在 struct usb_interface 中的私有数据指针部分的函数.

  • void *usb_get_intfdata(struct usb_interface *intf) :获取在 struct usb_interface 中的私有数据指针部分的函数.

  • struct usb_class_driver:描述 USB 驱动的一个结构, 这个驱动要使用 USB 主设备号来和用户空间程序通讯.

  • int usb_register_dev(struct usb_interface *intf, struct usb_class_driver *class_driver):用来注册一个特定 struct usb_interface * 结构到 struct usb_class_driver 结构的函数.

  • void usb_deregister_dev(struct usb_interface *intf, struct usb_class_driver *class_driver):用来注销一个特定 struct usb_interface * 结构到 struct usb_class_driver 结构的函数.

  • struct urb:描述一个 USB 数据传输的结构.

  • struct urb *usb_alloc_urb(int iso_packets, int mem_flags):用来创建一个 struct urb*的函数

  • void usb_free_urb(struct urb *urb):用来销毁一个 struct urb*的函数.

  • 用来启动或停止一个 USB 数据传输的函数

    int usb_submit_urb(struct urb *urb, int mem_flags); 
    int usb_kill_urb(struct urb *urb); 
    int usb_unlink_urb(struct urb *urb);
    
  • 用来在被提交给 USB 核心之前初始化一个 struct urb 的函数

    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,\
                          void *context, int interval)
        
    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,\
                           void *context)
        
    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, void *context)
    
  • 用来发送和接受 USB 数据的函数(不使用 urb).

    int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,\
                     void *data, int len, int  *actual_length, int timeout);
    
    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);  
    

2.8 USB总线驱动如何识别设备

由于内核自带了USB驱动,所以我们先插入一个USB键盘到开发板上看打印信息发现以下字段:

img

如下图,找到第一段话是位于drivers/usb/core/hub.c的第2186行:

img

这个hub其实就是我们的USB主机控制器的集线器,用来管理多个USB接口

2.8.1 drivers/usb/core/hub.c的第2186行位于hub_port_init()函数里

它又是被谁调用的,如下图所示,我们搜索到它是通过hub_thread()函数调用的

img

hub_thread()函数如下:

static int hub_thread(void *__unused)
{

do {
       hub_events();       //执行一次hub事件函数
       wait_event_interruptible(khubd_wait,!list_empty(&hub_event_list) ||kthread_should_stop());                              //(1).每次执行一次hub事件,都会进入一次等待事件中断函数
       try_to_freeze();            
} while (!kthread_should_stop() || !list_empty(&hub_event_list));

pr_debug("%s: khubd exiting\n", usbcore_name);
return 0;
}

从上面函数中得到, 要想执行hub_events(),都要等待khubd_wait这个中断唤醒才行。

2.8.2 搜索”khubd_wait”,看看是被谁唤醒

找到该中断在kick_khubd()函数中唤醒,代码如下:

static void kick_khubd(struct usb_hub *hub)
{
       unsigned long       flags;
       to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;
 
       spin_lock_irqsave(&hub_event_lock, flags);
       if (list_empty(&hub->event_list)) {
              list_add_tail(&hub->event_list, &hub_event_list);
              wake_up(&khubd_wait);                     //唤醒khubd_wait这个中断
       }

       spin_unlock_irqrestore(&hub_event_lock, flags);
}

2.8.3 继续搜索kick_khubd,发现被hub_irq()函数中调用

显然,就是当USB设备插入后,D+或D-就会被拉高,然后USB主机控制器就会产生一个hub_irq中断.

2.8.4 分析hub_port_connect_change()函数如何连接端口

static void hub_port_connect_change(struct usb_hub *hub, int port1,u16 portstatus, u16 portchange)
{ 
  ... ...
  udev = usb_alloc_dev(hdev, hdev->bus, port1);     //(1)注册一个usb_device,然后会放在usb总线上

  usb_set_device_state(udev, USB_STATE_POWERED); //设置注册的USB设备的状态标志
  ... ...

  choose_address(udev);                              //(2)给新的设备分配一个地址编号
   status = hub_port_init(hub, udev, port1, i);      //(3)初始化端口,与USB设备建立连接
  ... ...

  status = usb_new_device(udev);                 //(4)创建USB设备,与USB设备驱动连接
  ... ...
}

所以最终流程图如下:

img

2.8.5 进入hub_port_connect_change()->usb_alloc_dev(),看它是怎么设置usb_device

usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
{
    struct usb_device *dev;
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);   //分配一个usb_device设备结构体

       ... ...
    device_initialize(&dev->dev);   //初始化usb_device
    dev->dev.bus = &usb_bus_type;   //(1)设置usb_device的成员device->bus等于usb_bus总线

    dev->dev.type = &usb_device_type;    //设置usb_device的成员device->type等于usb_device_type
                                   
        ... ...
 
    return dev;                         //返回一个usb_device结构体
}
  • 在第17行上,设置device成员,主要是用来后面8.2小节,注册usb总线的device表上.

  • 其中usb_bus_type是一个全局变量, 它和我们之前学的platform平台总线相似,属于USB总线, 是Linux中bus的一种.

  • 如下图所示,每当创建一个USB设备,或者USB设备驱动时,USB总线都会调用match成员来匹配一次,使USB设备和USB设备驱动联系起来.

img

usb_bus_type结构体如下:

struct bus_type usb_bus_type = {
    .name =		"usb",			//总线名称,存在/sys/bus下
    .match = 	usb_device_match, //匹配函数,匹配成功就会调用usb_driver驱动的probe函数成员
    .uevent =   usb_uevent,     //事件函数
    .suspend =  usb_suspend,   //休眠函数
    .resume =   usb_resume,    //唤醒函数
};  

2.8.6 进入hub_port_connect_change()->choose_address(),看它是怎么分配地址编号的

static void choose_address(struct usb_device *udev)
{
    int devnum;
	struct usb_bus    *bus = udev->bus;
    devnum = find_next_zero_bit(bus->devmap.devicemap, 128,bus->devnum_next);
    //在bus->devnum_next~128区间中,循环查找下一个非0(没有设备)的编号

    if (devnum >= 128)                 //若编号大于等于128,说明没有找到空余的地址编号,从头开始找
    	devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);
    bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);  //设置下次寻址的区间+1
    if (devnum < 128) {
        set_bit(devnum, bus->devmap.devicemap);      //设置位
        udev->devnum = devnum;                 
    }
}

从上面代码中分析到每次的地址编号是连续加的,USB接口最大能接127个设备,我们连续插拔两次USB键盘,也可以看出,如下图所示:

img

2.8.7 再看hub_port_connect_change()->hub_port_init()函数是如何来实现连接USB设备的

static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,int retry_counter)
{
    ... ...
    for (j = 0; j < SET_ADDRESS_TRIES; ++j){
        retval = hub_set_address(udev);     //(1)设置地址,告诉USB设备新的地址编号
        if (retval >= 0)
            break;
        msleep(200);
    }
	retval = usb_get_device_descriptor(udev, 8);   //(2)获得USB设备描述符前8个字节
	... ...
    retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);  //重新获取设备描述符信息
	... ...
}
  • 上面第6行中,hub_set_address()函数主要是用来告诉USB设备新的地址编号,hub_set_address()函数如下:
static int hub_set_address(struct usb_device *udev)
{
       int retval;
       ... ...
       retval = usb_control_msg(udev, usb_sndaddr0pipe(),USB_REQ_SET_ADDRESS,0, udev->devnum, 0,NULL, 0, USB_CTRL_SET_TIMEOUT);                                                //(1.1)等待传输完成
    if (retval == 0) {              //设置新的地址,传输完成,返回0
              usb_set_device_state(udev, USB_STATE_ADDRESS);  //设置状态标志
              ep0_reinit(udev);
       }
return retval;
}
  • usb_control_msg()函数就是用来让USB主机控制器把一个控制报文发给USB设备,如果传输完成就返回0.其中参数udev表示目标设备;使用的管道为usb_sndaddr0pipe(),也就是默认的地址0加上控制端点号0; USB_REQ_SET_ADDRESS表示命令码,既设置地址; udev->devnum表示要设置目标设备的设备号;允许等待传输完成的时间为5秒,因为USB_CTRL_SET_TIMEOUT定义为5000。
  • 上面第12行中,usb_get_device_descriptor()函数主要是获取目标设备描述符前8个字节,为什么先只开始读取8个字节?是因为开始时还不知道对方所支持的信包容量,这8个字节是每个设备都有的,后面再根据设备的数据,通过usb_get_device_descriptor()重读一次目标设备的设备描述结构.

其中USB设备描述符结构体如下所示:

struct usb_device_descriptor {
     __u8  bLength;                          //本描述符的size
     __u8  bDescriptorType;                //描述符的类型,这里是设备描述符DEVICE
     __u16 bcdUSB;                           //指明usb的版本,比如usb2.0
     __u8  bDeviceClass;                    //类
     __u8  bDeviceSubClass;                 //子类
     __u8  bDeviceProtocol;                  //指定协议
     __u8  bMaxPacketSize0;                 //端点0对应的最大包大小
     __u16 idVendor;                         //厂家ID
     __u16 idProduct;                        //产品ID
     __u16 bcdDevice;                        //设备的发布号
     __u8  iManufacturer;                    //字符串描述符中厂家ID的索引
     __u8  iProduct;                         //字符串描述符中产品ID的索引
     __u8  iSerialNumber;                   //字符串描述符中设备序列号的索引
     __u8  bNumConfigurations;               //可能的配置的数目
} __attribute__ ((packed));

2.8.8 再看hub_port_connect_change()->usb_new_device()函数是如何来创建USB设备的

int usb_new_device(struct usb_device *udev)
{
   ... ...
   err = usb_get_configuration(udev);           //(1)获取配置描述块
  ... ...
  err = device_add(&udev->dev);     // (2)把device放入bus的dev链表中,并寻找对应的设备驱动
}
  1. 其中usb_get_configuration()函数如下,就是获取各个配置
int   usb_get_configuration(struct usb_device *dev)
{
	... ...
      /* USB_MAXCONFIG 定义为8,表示设备描述块下有最多不能超过8个配置描述块 */
      /*ncfg表示 设备描述块下 有多少个配置描述块 */
    if (ncfg > USB_MAXCONFIG) {
        dev_warn(ddev, "too many configurations: %d, "
                      "using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
        dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
    }
    ... ...
    
    for (cfgno = 0; cfgno < ncfg; cfgno++){   //for循环,从USB设备里依次读入所有配置描述块
          //每次先读取USB_DT_CONFIG_SIZE个字节,也就是9个字节,暂放到buffer中
          result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,buffer, USB_DT_CONFIG_SIZE);
                                  
          ... ...

          //通过wTotalLength,知道实际数据大小
          length = max((int) le16_to_cpu(desc->wTotalLength),USB_DT_CONFIG_SIZE);         

          bigbuffer = kmalloc(length, GFP_KERNEL);  //然后再来分配足够大的空间
          ... ...

          //在调用一次usb_get_descriptor,把整个配置描述块读出来,放到bigbuffer中
          result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,bigbuffer, length);
                                 
          ... ...

          //再将bigbuffer地址放在rawdescriptors所指的指针数组中
          dev->rawdescriptors[cfgno] = bigbuffer;   

          result = usb_parse_configuration(&dev->dev, cfgno,&dev->config[cfgno],

        bigbuffer, length);         //最后在解析每个配置块
    }
  ... ...
}
  1. 其中device_add ()函数如下
int   usb_get_configuration(struct usb_device *dev)
{
    dev = get_device(dev);         //使dev等于usb_device下的device成员
    ... ...
    if ((error = bus_add_device(dev))) // 把这个设备添加到dev->bus的device表中
    	goto BusError;
 	... ...
	bus_attach_device(dev);           //来匹配对应的驱动程序
 	... ...
}

当bus_attach_device()函数匹配成功,就会调用驱动的probe函数

2.8.9 再看usb_bus_type这个的成员usb_device_match函数是如何匹配的

img

usb_device_match函数如下所示:

static int usb_device_match(struct device *dev, struct device_driver *drv)
{
    if (is_usb_device(dev)) {                       //判断是不是USB设备
    	if (!is_usb_device_driver(drv))
        	return 0;
     	return 1;
    }
    else
    {       //否则就是USB驱动或者USB设备的接口
        struct usb_interface *intf;
        struct usb_driver *usb_drv;
        const struct usb_device_id *id;           

        if (is_usb_device_driver(drv))   //如果是USB驱动,就不需要匹配,直接return
        	return 0; 

        intf = to_usb_interface(dev);               //获取USB设备的接口
        usb_drv = to_usb_driver(drv);                    //获取USB驱动
        id = usb_match_id(intf, usb_drv->id_table);  //匹配USB驱动的成员id_table
        if (id)
	        return 1;

        id = usb_match_dynamic_id(intf, usb_drv);
        if (id)
    	    return 1;
   }
   return 0;
}

显然就是匹配USB驱动的id_table

2.8.10 USB驱动的id_table该如何定义

id_table的结构体为struct usb_device_id,提供了这个驱动支持的 USB 设备列表。当特定设备被插入系统时,这个列表被 USB 核心用来决定给设备哪个驱动, 并且通过热插拔脚本来 决定哪个驱动自动加载。其定义如下所示:

struct usb_device_id {
        
       __u16             match_flags;   
    	 //匹配哪种USB类型?这是一个位成员, 由include/linux/mod_devicetable.h文件中指定
         //USB_DEVICE_ID_MATCH_INT_INFO : 用于匹配设备的接口描述符的3个成员
         //USB_DEVICE_ID_MATCH_DEV_INFO: 用于匹配设备描述符的3个成员
         //USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION: 用于匹配特定的USB设备的4个成员
         //USB_DEVICE_ID_MATCH_DEVICE:用于匹配特定的USB设备的2个成员(idVendor和idProduct)

 
       /* 描述该驱动可匹配的特定的USB设备 */
       __u16             idVendor;              //厂家ID
       __u16             idProduct;             //产品ID
       __u16             bcdDevice_lo;        	//设备的低版本号(以 BCD 方式编码)
       __u16             bcdDevice_hi;        	//设备的高版本号(以 BCD 方式编码)

       /*以下3个就是用于比较“设备描述符”的*/
       __u8        bDeviceClass;                //设备类
       __u8        bDeviceSubClass;             //设备子类
       __u8        bDeviceProtocol;             //设备协议

       /* 以下3个就是用于比较设备的“接口描述符”的 */
       __u8        bInterfaceClass;             //接口类型
       __u8        bInterfaceSubClass;          //接口子类型
       __u8        bInterfaceProtocol;          //接口所遵循的协议

       /* 这个值不用来匹配,用来在probe函数中区分不同的设备.  */
       kernel_ulong_t       driver_info;		
};
  • 有几个宏可用来初始化这个结构:

    • USB_DEVICE(vendor, product) :用来只匹配特定供应商和产品 ID 值.
    • USB_DEVICE_VER(vendor, product, lo, hi) :用来在一个版本范围中只匹配特定供 应商和产品 ID 值.
    • USB_DEVICE_INFO(class, subclass, protocol) :用来只匹配一个特定类的 USB 设 备.
    • USB_INTERFACE_INFO(class, subclass, protocol) :用来只匹配一个特定类的 USB 接口.
  • 对于一个简单的只控制来自一个供应商的单一 USB 设备, struct usb_device_id 表可定义如:

    /* table of devices that work with this driver */ 
    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); 
    

参考/drivers/hid/usbhid/usbmouse.c(内核自带的USB鼠标驱动),看它是如何使用的:

img

发现,它是通过**USB_INTERFACE_INFO()**这个宏定义的.该宏如下所示:

#define USB_INTERFACE_INFO(cl,sc,pr) \
      .match_flags = USB_DEVICE_ID_MATCH_INT_INFO,  \    //设置id_table的.match_flags成员
      .bInterfaceClass = (cl), .bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
                                                         //设置id_table的3个成员,用于与匹配USB设备的3个成员

然后将上图里的usb_mouse_id_table []里的3个值代入宏**USB_INTERFACE_INFO(cl,sc,pr)**中:

.bInterfaceClass =USB_INTERFACE_CLASS_HID;  
   //设置匹配USB的接口类型为HID类, 因为USB_INTERFACE_CLASS_HID=0x03
   //HID类是属于人机交互的设备,比如:USB键盘,USB鼠标,USB触摸板,USB游戏操作杆都要填入0X03

.bInterfaceSubClass =USB_INTERFACE_SUBCLASS_BOOT;  
   //设置匹配USB的接口子类型为启动设备

.bInterfaceProtocol=USB_INTERFACE_PROTOCOL_MOUSE;
  //设置匹配USB的接口协议为USB鼠标的协议,等于2
  //当.bInterfaceProtocol=1也就是USB_INTERFACE_PROTOCOL_KEYBOARD时,表示USB键盘的协议

如下图,我们也可以通过windows上也可以找到鼠标的协议号,也是2:

img

其中VID:表示厂家(vendor)ID

PID:表示产品(Product) ID

总结:当我们插上USB设备时,系统就会获取USB设备的设备、配置、接口、端点的数据并创建新设备,所以我们的驱动就需要写id_table来匹配该USB设备。

3、USB设备(鼠标)驱动编写

讲了这么多,大家一定蒙圈了,现在以一个具体例子来具体分析一个USB驱动的编写方法!

  • 预期目标:鼠标左键=按键L,鼠标右键=按键S,鼠标中键=回车键

  • .probe函数的结构

    • 1 分配input_dev
    • 2 设置
    • 3 注册
    • 硬件操作
      • 使用USB总线驱动函数的读写函数来收发数据
  • 怎么写USB设备驱动程序

    • 1 分配/设置usb_driver结构体
      • .name
      • .id_table
      • .probe
      • .disconnect
    • 2 注册
  • 代码编写步骤如下

0 定义全局变量 :usb_driver结构体、input_dev指针结构体 、虚拟地址缓存区、DMA地址缓存区

1 在入口函数中

  1. 通过usb_register()函数注册usb_driver结构体

2 在usb_driver的probe函数中

  1. 分配一个input_dev结构体

  2. 设置input_dev支持L、S、回车、3个按键事件

  3. 注册input_dev结构体

  4. 设置USB数据传输:

->4.1)通过usb_rcvintpipe()创建一个接收中断类型的端点管道pipe,用于端点和数据缓冲区之间的连接

->4.2)通过usb_buffer_alloc()申请USB缓冲区

->4.3)申请并初始化urb结构体(urb:用来传输数据)

->4.4) 因为我们2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址

->4.5)使用usb_submit_urb()提交urb

3 在鼠标中断函数中

1)判断缓存区数据是否改变,若改变则上传鼠标事件

2)使用usb_submit_urb()提交urb

4 在usb_driver的disconnect函数中

1)通过usb_kill_urb()杀掉提交到内核中的urb

2)释放urb

3)释放USB缓存区

4注销input_device

5 在出口函数中

1)通过usb_deregister ()函数注销usb_driver结构体

3.1 先写驱动主框架

/*
 * drivers\hid\usbhid\usbmouse.c
 */

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>

/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
	.name		= "usbmouse_as_key_",
	.probe		= usbmouse_as_key_probe,
	.disconnect	= usbmouse_as_key_disconnect,
	.id_table	= usbmouse_as_key_id_table,
};

static int __init usbmouse_as_key_init(void)
{
	/* 2. 注册 */
	usb_register(&usbmouse_as_key_driver);
	return 0;
}

static void __exit usbmouse_as_key_exit(void)
{
	usb_deregister(&usbmouse_as_key_driver);	
}

module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);

MODULE_LICENSE("GPL");

3.2 编写.id_table(描述本驱动适配的USB类型)

  • 该USB设备驱动匹配的是接口描述符中的HID类 & BOOT子类 & MOUSE协议的设备。
static struct usb_device_id usbmouse_as_key_id_table[] = {
	{USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID,\
    	USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE)
    },
	//如果需要匹配USB设备的厂家和产品ID,可以添加{USB_DEVICE(0x1234,0x5678)},
	{ }	/* Terminating entry */
};

3.3 编写.probe函数

  • 开始的时候可以仅在该函数中打印一条语句,待整个架构测试通过后再逐步添加 。
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	struct usb_device *dev = interface_to_usbdev(intf);	//获得usb设备信息
    
    /* 以下语句可以用来打印USB设备的厂家及产品id信息
     *printk("Found usbmouse!\n");
     *printk("bcdUSB = %x\n",dev->descriptor.bcdUSB);    
     *printk("VID	 = 0X%x\n",dev->descriptor.idVendor);   
     *printk("PID	 = 0X%x\n",dev->descriptor.idProduct);   
    */
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
	int pipe;    
  
	interface = intf->cur_altsetting;
    if (interface->desc.bNumEndpoints != 1)		//除了端点0之外,鼠标的端点数如果不是1的话,则返回错误
        return -ENODEV;
    
	endpoint = &interface->endpoint[0].desc;	//得到第一个非零endpoint的描述符
    
    if (!usb_endpoint_is_int_in(endpoint))		//如果不是中断输入型端点的话,返回错误,更多内嵌函数见<linux/usb.h>
        return -ENODEV;

	/* a. 分配一个input_dev */
	uk_dev = input_allocate_device();	//在文件开头先定义输入设备指针uk_dev
	
	/* b. 设置 */
	/* b.1 能产生哪类事件 */
	set_bit(EV_KEY, uk_dev->evbit);	//能产生按键类事件
	set_bit(EV_REP, uk_dev->evbit);	//能产生按键的重复类事件
	
	/* b.2 能产生哪些按键 */
	set_bit(KEY_L, uk_dev->keybit);
	set_bit(KEY_S, uk_dev->keybit);
	set_bit(KEY_ENTER, uk_dev->keybit);
	
	/* c. 注册 */
	input_register_device(uk_dev);
	
	/* d. 硬件相关操作 */
	/* 数据传输“三要素”: 源,目的,长度 */
    
	/* 1源: USB设备的某个端点 */
	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

	/* 2长度: 要在文件开头定义一个长度变量,int len*/
	len = endpoint->wMaxPacketSize;
	
    /*
	 * 3目的: (1.在文件开头定义一个字符串指针,char * usb_buf) 
	 * 		  (2.在文件开头定义一个usb_buf的物理地址,dma_addr_t * usb_buf_phys)
	 * 注意:在linux2.6.34及之前的代码中还可以使用usb_buffer_alloc 和 usb_buffer_free 
	 *      来分配和释放缓存,但在之后的内核中这两个函数已不在使用了,可以用usb_alloc_coherent 和
     *      usb_free_coherent代替。原因是使用DMA传输数据时,由linux保证内存和硬件cache的一致性。
     *      这也是为何新函数名字中含有coherent的原因。
	 */
	usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);

	/* 使用"3要素" */
    
	/* 1分配usb request block */
	uk_urb = usb_alloc_urb(0, GFP_KERNEL);	//先要在文件开头定义一个urb指针,即urb * uk_urb
	
    /* 2使用之前的三要素设置urb" */
	usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
	uk_urb->transfer_dma = usb_buf_phys;	
	uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	/* 3使用URB */
	usb_submit_urb(uk_urb, GFP_KERNEL);
	
	return 0;
}

3.4 编写USB中断函数

USB主机周期性(间隔时间为endpoint->bInterval)的从设备获得数据,并存入到usb buffer中,同时USB主机控制器会向CPU产生中断,并调用usb中断处理函数:

static void usbmouse_as_key_irq(struct urb *urb)
{   
//为了调试,先打印usb_buf中的数据
#if 0	
	int i;
	static int cnt = 0;
	printk("data cnt %d: ", ++cnt);
    //usb_buf[]里存放的是鼠标按键值、x坐标、y坐标和滚轮的值
	for (i = 0; i < len; i++)
	{
		printk("%02x ", usb_buf[i]);
	}
	printk("\n");
#endif

//上报事件(USB总线驱动是不知道usb数据含义的,只有在USB设备驱动中加以解析)
#if 1
	/* USB鼠标数据含义
	 * data[0]: bit0-左键(1-按下, 0-松开)
	 *          bit1-右键(1-按下, 0-松开)
	 *          bit2-中键(1-按下, 0-松开)
	 */
   	static unsigned char pre_val;
	if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))	//如果上次数据的Bit0不等于现在的值
	{
		/* 左键发生了变化 */
		input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
		input_sync(uk_dev);
	}

	if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
	{
		/* 右键发生了变化 */
		input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
		input_sync(uk_dev);
	}

	if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
	{
		/* 中键发生了变化 */
		input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
		input_sync(uk_dev);
	}
	
	pre_val = usb_buf[0];
#endif
    
	/* 重新提交urb */
	usb_submit_urb(uk_urb, GFP_KERNEL);
}

3.5 编写.disconnect函数

  • 开始的时候可以仅在该函数中打印一条语句,待整个架构测试通过后再逐步添加。 。
static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
	struct usb_device *dev = interface_to_usbdev(intf);

	//printk("disconnect usbmouse!\n");
	usb_kill_urb(uk_urb);	//杀死urb
	usb_free_urb(uk_urb);	//释放urb

	usb_free_coherent(dev, len, usb_buf, usb_buf_phys);	//释放usb buffer
	input_unregister_device(uk_dev);
	input_free_device(uk_dev);
}

4、测试

4.1 去掉内核鼠标驱动支持

cd linux3.4.2
make menuconfig

->Device Drivers
	->HID Devices
		-> <>USB Human Interface Device (full HID) support
		
make uImage

4.2 使用新内核启动开发板

tftp 30000000 uImage	//将新的内核复制到tftp共享目录后,下载到开发板内核
//nfs 30000000 主机IP:/nfs_root/uImage	//或者使用nfs命令下载新内核

bootm 30000000	//从内核的30000000地址处启动内核

4.3 安装自己编译的USB设备驱动

mount -t nfs -o nolock  主机IP:/nfs_root /mnt		//挂接网络文件系统
insmod usbmouse_as_key.ko	//内核启动后,安装自己编译的USB设备驱动

4.4 测试

  • 在开发板上接入、拔出USB鼠标,观察串口输出
  • 将鼠标当作键盘,依次点击左键(等价于符号l)、右键(等价于符号s)、中键(等价于符号Enter)
# ls /dev/event*			//观察当前系统有没有其它event设备
ls:/dev/event*:No such file or directory

接上USB鼠标
usb 1-1:configuration #1 chosen from 1 choice
input:Unspecified device as /class/input/input0

# ls /dev/event*
/dev/event0			//对应我们刚接上去的鼠标

# cat /dev/tty1 	//将鼠标作为键盘,观察tty1口输出
l(按下左键)s(按下右键)
(按下回车)

llllll(按住左键)

5、整体代码

  • usbmouse_as_key.c

/*
 * drivers\hid\usbhid\usbmouse.c
 */

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>

static struct input_dev *uk_dev;//鼠标作为输入设备
static char *usb_buf;			//使用虚拟地址的usb buffer
static dma_addr_t usb_buf_phys;	//使用物理地址的DMA缓冲区
static int len;					//usb buffer 的大小
static struct urb *uk_urb;		//usb请求块

static struct usb_device_id usbmouse_as_key_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	//{USB_DEVICE(0x1234,0x5678)},
	{ }	/* Terminating entry */
};

static void usbmouse_as_key_irq(struct urb *urb)
{
	static unsigned char pre_val;
#if 0	
	int i;
	static int cnt = 0;
	printk("data cnt %d: ", ++cnt);
	for (i = 0; i < len; i++)
	{
		printk("%02x ", usb_buf[i]);
	}
	printk("\n");
#endif
	/* USB鼠标数据含义
	 * data[0]: bit0-左键, 1-按下, 0-松开
	 *          bit1-右键, 1-按下, 0-松开
	 *          bit2-中键, 1-按下, 0-松开 
	 *
     */
	if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))
	{
		/* 左键发生了变化 */
		input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
		input_sync(uk_dev);
	}

	if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
	{
		/* 右键发生了变化 */
		input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
		input_sync(uk_dev);
	}

	if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
	{
		/* 中键发生了变化 */
		input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
		input_sync(uk_dev);
	}
	
	pre_val = usb_buf[0];

	/* 重新提交urb */
	usb_submit_urb(uk_urb, GFP_KERNEL);
}

static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	struct usb_device *dev = interface_to_usbdev(intf);
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
	int pipe;
	
	interface = intf->cur_altsetting;
	endpoint = &interface->endpoint[0].desc;

	/* a. 分配一个input_dev */
	uk_dev = input_allocate_device();
	
	/* b. 设置 */
	/* b.1 能产生哪类事件 */
	set_bit(EV_KEY, uk_dev->evbit);
	set_bit(EV_REP, uk_dev->evbit);
	
	/* b.2 能产生哪些事件 */
	set_bit(KEY_L, uk_dev->keybit);
	set_bit(KEY_S, uk_dev->keybit);
	set_bit(KEY_ENTER, uk_dev->keybit);
	
	/* c. 注册 */
	input_register_device(uk_dev);
	
	/* d. 硬件相关操作 */
	/* 数据传输3要素: 源,目的,长度 */
	/* 源: USB设备的某个端点 */
	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

	/* 长度: */
	len = endpoint->wMaxPacketSize;

	/* 目的: */
	usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);

	/* 使用"3要素" */
	/* 分配usb request block */
	uk_urb = usb_alloc_urb(0, GFP_KERNEL);
	/* 使用"3要素设置urb" */
	usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
	uk_urb->transfer_dma = usb_buf_phys;
	uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	/* 使用URB */
	usb_submit_urb(uk_urb, GFP_KERNEL);
	
	return 0;
}

static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
	struct usb_device *dev = interface_to_usbdev(intf);

	//printk("disconnect usbmouse!\n");
	usb_kill_urb(uk_urb);
	usb_free_urb(uk_urb);

	usb_free_coherent(dev, len, usb_buf, usb_buf_phys);
	input_unregister_device(uk_dev);
	input_free_device(uk_dev);
}

/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
	.name		= "usbmouse_as_key_",
	.probe		= usbmouse_as_key_probe,
	.disconnect	= usbmouse_as_key_disconnect,
	.id_table	= usbmouse_as_key_id_table,
};


static int usbmouse_as_key_init(void)
{
	/* 2. 注册 */
	usb_register(&usbmouse_as_key_driver);
	return 0;
}

static void usbmouse_as_key_exit(void)
{
	usb_deregister(&usbmouse_as_key_driver);	
}

module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);

MODULE_LICENSE("GPL");
  • Makefile文件
KERN_DIR = /home/leon/linux-3.4.2

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= usbmouse_as_key.o
  • 16
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
当然,我很乐意帮助您入门Docker!Docker是一个开源的容器化平台,它可以帮助您打包、发布和运行应用程序。以下是一些关键概念和步骤来帮助您入门Docker: 1. 安装Docker:首先,您需要在您的机器上安装Docker。Docker提供了适用于不同操作系统(如Windows、Mac和Linux)的安装程序。您可以在Docker官方网站上找到相应的安装指南。 2. Docker镜像:Docker镜像是一个轻量级的独立软件包,其中包含了运行应用程序所需的所有内容(包括代码、运行时环境、库和依赖项等)。您可以通过构建自己的镜像或者从Docker Hub等镜像仓库中获取现有的镜像。 3. Docker容器:Docker容器是从Docker镜像创建的运行实例。容器可以独立运行,并且具有自己的文件系统、网络和进程空间。您可以使用Docker命令来创建、启动、停止和删除容器。 4. Dockerfile:Dockerfile是一个文本文件,其中包含了一系列的指令,用于构建Docker镜像。通过编写Dockerfile,您可以定义应用程序的所需环境、依赖项和配置等信息,并将其打包到镜像中。 5. Docker Compose:Docker Compose是一个用于定义和运行多个Docker容器的工具。通过编写一个YAML格式的配置文件,您可以定义多个服务(每个服务对应一个容器),并指定它们之间的关联和依赖关系。 以上是Docker的一些基本概念和步骤,希望对您入门Docker有所帮助。如果您有更具体的问题或者需要进一步了解,请随时提问!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值