天天看点

十四、Linux驱动之USB驱动分析1. 基本概念2. USB驱动分析

1. 基本概念

1.1 简介

    USB是英文"Universal Serial Bus"的缩写,意为"通用串行总线"。USB最初是为了替代许多不同的低速总线(包括并行、串行和键盘连接)而设计的,它以单一类型的总线连接各种不同的类型的设备。USB的发展已经超越了这些低速的连接方式,它现在可以支持几乎所有可以连接到PC上的设备。最新的USB规范修订了理论上高达480Mbps的高速连接。

    USB的驱动可以分为3类:SoC的USB控制器的驱动,主机端USB设备的驱动,设备上的USB Gadget驱动,通常,对于USB这种标准化的设备,内核已经将主机控制器的驱动编写好了,设备上的Gadget驱动通常只运行固件程序而不是基于Linux, 所以驱动工程师的主要工作就是编写主机端的USB设备驱动。

1.2 热拔插

    当一个USB设备插入PC机,PC机怎么知道有设备插入? 如下图所示,USB接口只有4条线:VCC(5V),GND,D-,D+。 PC机的USB插孔的D-和D+数据线均连接15K欧姆的下拉电阻。而USB设备端的D-或D+数据线连接1.5K欧姆的上拉电阻。当设备插入PC机的时候,会将PC机的D-或D+端的电压拉高,当PC机在D-或D+端检测到高电平时,就知道有设备插入了。如果是PC机D-端被拉高,接入的则是USB低速设备;如果是PC机D+端被拉高,接入的则是USB全速或高速设备,具体是全速设备还是高速设备,会由PC机和USB设备发包握手确定。

十四、Linux驱动之USB驱动分析1. 基本概念2. USB驱动分析
十四、Linux驱动之USB驱动分析1. 基本概念2. USB驱动分析

                       USB低速设备硬件接线图                                                         USB全速(高速)设备硬件接线图            

1.3 传输类型

    控制传输(control):是每一个USB设备必须支持的,通常用来获取设备描述符、设置设备的状态等等。一个USB设备从插入到最后的拔出这个过程一定会产生控制传输(即便这个USB设备不能被这个系统支持)。

    中断传输(interrupt):支持中断传输的典型设备有USB鼠标、 USB键盘等等。中断传输不是说我的设备真正发出一个中断,然后主机会来读取数据。它其实是一种轮询的方式来完成数据的通信。USB设备会在设备驱动程序中设置一个参数叫做interval,它是endpoint的一个成员。 interval是间隔时间的意思,表示我这个设备希望主机多长时间来轮询自己,只要这个值确定了之后,我主机就会周期性的来查看有没有数据需要处理。

    批量传输(bulk):支持批量传输最典型的设备就是U盘,它进行大数量的数据传输,能够保证数据的准确性,但是时间不是固定的。

    实时传输(isochronous):USB摄像头就是实时传输设备的典型代表,它同样进行大数量的数据传输,数据的准确性无法保证,但是对传输延迟非常敏感,也就是说对实时性要求比较高。

1.4 USB设备逻辑结构

    为了更好地描述USB设备的特征,USB提出了设备架构的概念。从这个角度来看,可以认为USB设备是由一些配置、接口和端点组成(设备通常有一个或多个配置,配置通常有一个或多个接口,接口通常有一个或多个设置,接口有零或多个端点)。其中,配置和接口是对USB设备功能的抽象,实际的数据传输由端点来完成。在使用USB设备前,必须指明其采用的配置和接口。这个步骤一般是在设备接入主机时设备进行枚举时完成的。

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

1.5 USB协议

    USB2.0协议中文版

2. USB驱动分析

2.1 USB驱动程序框架

USB驱动程序框架图如下:

十四、Linux驱动之USB驱动分析1. 基本概念2. USB驱动分析

USB总线驱动程序的作用:

    1. 识别USB设备

        1.1 分配地址

        1.2 并告诉USB设备(set address)

        1.3 发出命令获取描述符(描述符的信息可以在include\linux\usb\Ch9.h看到  (Ch9是指USB规范的第九章))

    2. 查找并安装对应的设备驱动程序

    3. 提供USB读写函数

    USB总线上的所有通信都是由主机发起的,所以本质上,USB都是采用轮询的方式进行的。USB总线会使用轮询的方式不断检测总线上是否有设备接入,如果有设备接入相应的D+D-就会有电平变化。然后总线就会按照USB规定的协议与设备进行通信,设备将存储在自身的设备信息依次交给主机,主机将这些信息组织起来。上报到内核,内核中的USB子系统再去匹配相应的驱动。

2.2 USB驱动分析

    当我们开发板启动内核(内核配置好了usbmouse相关驱动)后,开发板接上一个USB鼠标,串口端输入如下信息:

十四、Linux驱动之USB驱动分析1. 基本概念2. USB驱动分析

    从第一行输出信息的“USB device using”定位到内核的drivers/usb/core/hub.c文件的hub_port_init()函数中

dev_info (&udev->dev,"%s %s speed %sUSB device using %s and address %d\n",
		  (udev->config) ? "reset" : "new", speed, type,
		  udev->bus->controller->driver->name, udev->devnum);
           

    这个hub其实就是我们的USB主机控制器的集线器,用来管理多个USB接口。继续在内核中搜索hub_port_init()函数被谁调用,最终调用层次如下:

    hub_thread()    //hub线程函数

        hub_events()    //hub事件函数

            hub_port_connect_change()    //hub端口连接函数

                hub_port_init()    //hub端口初始化函数

    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()); 
                             //每次执行一次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这个中断唤醒才行,查找内核调用层次如下:

        hub_irq()   

            kick_khubd()

                wake_up(&khubd_wait);    //唤醒hub_thread()函数中陷入的休眠

    hub_irq()函数在usb_fill_int_urb()函数的第三个参数传入。当usb port上状态发生变化(比如接入usb设备),USB主机控制器就会产生一个hub_irq中断,hub_irq()中又调用了kick_khubd()函数,在该函数里调用了wake_up()函数唤醒hub_thread()函数中陷入的休眠,后面就会进行一系列的枚举、注册等过程。接下来详细介绍唤醒hub_thread()函数后所做的工作。

2.2.1 hub_events()函数

唤醒hub_thread()函数后会再次执行do while循环,进入hub_events(),hub_events()函数的部分代码如下:

static void hub_events(void)
{
   	... ...
	while (1) {

		/*
		 * 做了一些逻辑判断,判断hub是否连接,hub上是否有错误,
		 * 如果有错误就重启hub,如果没有错误,接下来就对hub上的
		 * 每个port进行扫描,判断各个port是否发生状态变化,如果
		 * 产生了变化则调用hub_port_connect_change进行处理;
		 */
		... ...
		
		for (i = 1; i <= hub->descriptor->bNbrPorts; i++) {
			... ...
				
			if (connect_change)
				hub_port_connect_change(hub, i,
						portstatus, portchange);
		}
		... ...
	    } 
}
           

    该函数主要工作是遍历hub中的所有port,对各个port的状态进行查看,判断port是否发生了状态变化(这些状态主要有:1.原先port上没有设备,现在检测到有设备连接;2.原先port上有设备,由于控制器不正常关闭或禁止usb port等原图使得该设备处于NOATTACHED态),如果发现port的连接状态发生变化或由于EMI导致连接使能发生变化,即connect_change=1,则调用hub_port_connect_change()函数。

2.2.2 hub_port_connect_change()函数

    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);          //分配设置一个usb_device结构体

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

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

  status = usb_new_device(udev);                     //创建USB设备,注册到系统
  ... ...
}
           

    hub_port_connect_change()函数主要调用以下4个函数:

(1) usb_alloc_dev()函数:

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;    //设置usb_device的成员device->bus等于usb_bus总线

       dev->dev.type = &usb_device_type;    //设置usb_device的成员device->type等于usb_device_type                    
       ... ...
       return dev;    //返回一个usb_device结构体
}
           

(2) 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;                 
    }
}
           

    这里分配了地址编号,内核启动时会进来一次,编号1被使用了,所以接上一个USB鼠标时打印出的地址编号为2,下次分配的地址编号递增,直到127再从1开始找没有使用的地址编号。拔插两次USB鼠标输出如下图:

十四、Linux驱动之USB驱动分析1. 基本概念2. USB驱动分析

(3) hub_port_init()函数:

static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,int retry_counter)
{
    ... ...
    dev_info (&udev->dev,
        "%s %s speed %sUSB device using %s and address %d\n",
        (udev->config) ? "reset" : "new", speed, type,
        udev->bus->controller->driver->name, udev->devnum);    //对应前面打印的USB设备信息
    ... ...
    for (j = 0; j < SET_ADDRESS_TRIES; ++j) 
    {
       retval = hub_set_address(udev);     //设置地址,告诉USB设备新的地址编号

       if (retval >= 0)
                break;
       msleep(200);
    }
    retval = usb_get_device_descriptor(udev, 8);    //获得USB设备描述符前8个字节
    ... ...

    retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);    //重新获取设备描述符信息
    ... ...
}
           

    获取描述符时还不知道端点0一次性传输的包大小是多少,可以通过先获得描述符的第8个字节得到端点0一次性传输的包大小,后面再以该包大小重读一次目标设备的设备描述符。这里提到了设备描述符,该数据结构如下:

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

(4) usb_new_device()函数:

int usb_new_device(struct usb_device *udev)
{
    int err;
    ... ...
    err = usb_get_configuration(udev);    //获取并解析配置信息
    ... ...
    err = device_add(&udev->dev);    //把device放入bus的dev链表中,并寻找对应的设备驱动
}
           

    usb_get_configuration()函数里获取得到的信息里它包含了多种信息:配置,相关联接口,接口,端口等,再将这些信息把它补全到之前申请的usb_host_config结构里,把各类信息放到相应的结构中。最后调用device_add()函数,使用设备模型机制,将该USB设备注册到内核中去。

2.2.3 device_add()函数

    device_add()函数部分代码如下:

int device_add(struct 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);           //来匹配USB总线上的usb_drv
    ... ...
}
           

    与之前章节讲解的platform总线相同,bus_attach_device会调用到该设备总线类型里的成员.match匹配函数,与该总线上的所有的usb_drv进行匹配,对于本节的USB驱动则是usb_bus_type,该数据结构如下:

struct bus_type usb_bus_type = {
	.name =		"usb",
	.match =	usb_device_match,    //匹配函数
	.uevent =	usb_uevent,
	.suspend =	usb_suspend,
	.resume =	usb_resume,
};
           

    匹配函数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_match_id()函数最终又调用到了usb_match_device()函数,部分代码如下:

/* 返回0匹配成功,返回1匹配失败 */
int usb_match_device(struct usb_device *dev, const struct usb_device_id *id)
{
        ... ...

	if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) &&
	    (id->bDeviceClass != dev->descriptor.bDeviceClass))
		return 0;

	if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) &&
	    (id->bDeviceSubClass!= dev->descriptor.bDeviceSubClass))
		return 0;

	if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) &&
	    (id->bDeviceProtocol != dev->descriptor.bDeviceProtocol))
		return 0;

	return 1;
}
           

    即匹配前面提到的设备描述符里的三个成员:

struct usb_device_descriptor {
... ...
 __u8  bDeviceClass;                    //类
 __u8  bDeviceSubClass;                 //子类
 __u8  bDeviceProtocol;                  //指定协议
... ...
} __attribute__ ((packed));
           

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

/*
#define USB_INTERFACE_INFO(cl,sc,pr) \
	.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, .bInterfaceClass = (cl), \
	.bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
*/

static struct usb_device_id usb_mouse_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	{ }	/* Terminating entry */
};

static struct usb_driver usb_mouse_driver = {
	.name		= "usbmouse",
	.probe		= usb_mouse_probe,
	.disconnect	= usb_mouse_disconnect,
	.id_table	= usb_mouse_id_table,
};
           

所以可以看出对于USB驱动device_add的作用:

    1. 把device放入usb_bus_type的dev链表

    2. 从usb_bus_type的driver链表里取出usb_driver

    3. 把usb_interface和usb_driver的id_table比较

    4. 如果能匹配,调用usb_driver的probe

2.2.4 总结

整体框架图如下:

十四、Linux驱动之USB驱动分析1. 基本概念2. USB驱动分析

    USB总线驱动程序在我们接入USB设备的时候会帮我们构造一个新的usb_device注册到总线里面来。我们插上任意的USB设备,就发现内核打印出一些信息,说明USB总线驱动程序、USB设备程序已经做好,后面只需要编写USB驱动程序,即下图中的usb_driver 。(因此,以后写USB驱动程序,写的就是USB接口描述符方面的)

十四、Linux驱动之USB驱动分析1. 基本概念2. USB驱动分析

继续阅读