USB总线驱动及鼠标驱动实例

转自:https://blog.csdn.net/liangzc1124/article/details/119333357、https://www.cnblogs.com/lifexy/p/7631900.html

1、Windows USB设备驱动

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

    以windows为例,插上一个没有USB驱动的USB设备,会提示你安装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驱动整体框架

  • image-20210728213402190

  • 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等。

2、USB总线驱动分析

2.1 USB描述符的层次及定义
  • 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配置描述符

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 这个成员结构体里.

struct usb_interface { 
    /* 包含所有可用于该接口的可选设置的接口结构数组。每个 struct usb_host_interface 包含一套端点配置(即struct usb_host_endpoint结构所定义的端点配置。这些接口结构没有特别的顺序。*/
    struct usb_host_interface *altsetting;
    
    /* 指向altsetting内部的指针,表示当前激活的接口配置*/
    struct usb_host_interface *cur_altsetting; 
    
    unsigned num_altsetting; /* 可选设置的数量*/

    /* If there is an interface association descriptor then it will list the associated interfaces */ 
    struct usb_interface_assoc_descriptor *intf_assoc;
    
	/* 如果绑定到这个接口的 USB 驱动使用 USB 主设备号, 这个变量包含由 USB 核心分配给接口的次设备号. 这只在一个成功的调用 usb_register_dev后才有效。*/ 
    int minor; 
    ... ...
}

cur_altsetting成员的结构体是usb_host_interface,如下:

struct usb_host_interface {
    struct usb_interface_descriptor desc;   //当前被激活的接口描述符
    struct usb_host_endpoint *endpoint;   /* 这个接口的所有端点结构体的联合数组*/
    char *string;                 /* 接口描述字符串 */
    unsigned char *extra;           /* 额外的描述符 */
    int extralen;
};

2.1.4 端点描述符

struct usb_endpoint_descriptor {
    __u8  bLength;                      //描述符的长度

    __u8  bDescriptorType;              //描述符类型的编号

    __u8  bEndpointAddress;             //端点编号,比如端点1,就是1

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

比如端点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总线驱动如何识别设备

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

img

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

img

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

2.2.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.2.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.2.3 继续搜索kick_khubd,发现被hub_irq()函数中调用

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

2.2.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

hub_irq
	kick_khubd // 唤醒hub_thread线程
		hub_thread
			hub_events // 处理USB设备插入事件
				hub_port_connect_change
				
					udev = usb_alloc_dev(hdev, hdev->bus, port1);
								dev->dev.bus = &usb_bus_type;
				
					choose_address(udev); // 给新设备分配编号(地址)										
					hub_port_init   // usb 1-1: new full speed USB device using s3c2410-ohci and address 3
						
						hub_set_address  // 把编号(地址)告诉USB设备
						
						usb_get_device_descriptor(udev, 8); // 获取设备描述符
						retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
						
						usb_new_device(udev)   
							err = usb_get_configuration(udev); // 把所有的描述符都读出来,并解析
							usb_parse_configuration
							
							device_add  // 把device放入usb_bus_type的dev链表, 
							            // 从usb_bus_type的driver链表里取出usb_driver,
							            // 把usb_interface和usb_driver的id_table比较
							            // 如果能匹配,调用usb_driver的probe

2.2.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结构体
}
  • 其中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.2.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.2.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);  //重新获取设备描述符信息
    	... ...
    }
    
     
     
      • 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.2.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.2.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.2.10 USB驱动的id_table该如何定义

      id_table的结构体为usb_device_id,如下所示:

      参考/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设备(鼠标)驱动编写

        • 达到的目的:鼠标左键=按键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,释放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 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");
        
        

        3.2 编写.id_table

        • 该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.nNumEndpoints != 1)		//除了端点0之外,鼠标的端点数如果不是1的话,则返回错误
                return -ENODEV;
            
        	endpoint = &interface->endpoint[0].desc;	//得到第一个非零endpoint的描述符
            
            if (!usb_endpoint_is_int_in(endpoint))		//如果不是中断输入型端点的话,返回错误
                return -ENODEV;
        
        	/* a. 分配一个input_dev */
        	uk_dev = input_allocate_device();	//在文件开头先定义输入设备(input_dev)指针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)
        	*/
        	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主机周期性(j间隔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鼠标,观察串口输出
        • 将鼠标当作键盘,输入ls+回车命令
        # 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
        
        
        • 5
          点赞
        • 6
          收藏
          觉得还不错? 一键收藏
        • 0
          评论

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

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

        请填写红包祝福语或标题

        红包个数最小为10个

        红包金额最低5元

        当前余额3.43前往充值 >
        需支付:10.00
        成就一亿技术人!
        领取后你会自动成为博主和红包主的粉丝 规则
        hope_wisdom
        发出的红包
        实付
        使用余额支付
        点击重新获取
        扫码支付
        钱包余额 0

        抵扣说明:

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

        余额充值