7 Linux usb驱动框架分析

现象:将USB设备接入PCPC右下角上会弹出"发现xx新设备",例如"发现andriod phone"PC上没有安装该设备的驱动程序,则会弹出对话框提示"安装驱动程序"

1:没有安装设备的驱动程序之前,为什么PC还能发现andriod phone设备呢?

1windows系统中已经安装了USB"总线驱动程序",是"总线驱动程序"发现了新的设备,而提示我们安装的是"设备驱动程序"

2USB设备种类繁多,为何一接入电脑,就可以识别出来?

2PCUSB设备都要遵循一些规范。

比如:USB设备插入PC机后,PC会发出"你是什么"

     USB设备回答"我是xxx",且回答的语言必须是中文。

     USB总线驱动程序会发出某些命令获取设备的信息(描述符)。

     USB设备必须返回"描述符"PC

3PC机上接有很多的USB设备,如何区分这些设备?

     USB接口上有四条线:5VGNDD+D-

3:每一个USB设备在接入到总线上后,USB驱动程序都会给它分配一个固定的编号。

接在USB总线上的USB设备都拥有属于自己的编号(地址),PC发送含有对应USB设备的编号(地址)的命令进行访问。

4:新接入的USB设备还没有编号,PC如何将总线"分配的编号"告诉对应的USB设备?

4:新接入的USB设备默认的编号是0,在未分配新的编号前,PC机使用0编号和设备进行通信。

5:为什么设备一插入PC机,就能被PC机发现了?

5PC机的USB口内部,D+D-都接有15k的下拉电阻,未接入USB设备时为低电平;

     USB设备的USB口内部,D+或者D-接有1.5k的上拉电阻,他一接入PC,就会把PCUSB口的D+或者D-拉高,从而通过硬件通知PC有新的USB 设备接入。

USB概念简介

  1. USB是主从结构

    所有的USB传输,都是从USB主机这方发起,USB设备方不能主动发起通信。

  2. USB的传输类型

    a、控制传输:可靠的,时间有保证,比如:USB设备的识别过程

    b、批量传输:可靠,不实时,时间没有保证,比如:U

    c、中断传输(不断的查询实现实时性):可靠,实时,比如:USB鼠标

    d、实时传输:不可靠,实时,比如:USB摄像头

  3. USB传输的对象:端点(endpoint

    端点0用于控制传输,既能输出也能输入。

    除了端口0外,每一个端点只支持一个方向的数据传输。 

  4. 每一个端点都有传输类型,传输方向。 
  5. 术语或者程序中的"输入""输出"都是针对USB主机的立场说的。 

linux内核中USB架构

如下图所示,linux内核中USB驱动采用一种分层架构,由USB总线驱动和USB设备驱动构成。其中,USB总线驱动程序的作用主要包括:a 、识别USB设备;b、查找并安装对应的设备驱动程序;c、提供USB读写函数。

7 Linux usb驱动框架分析

OHCIOpen Host Controller Interface):由CompaqMicrosoftNational Semiconductor创立,支持USB1.1的标准,硬件复杂,软件相对简单,主要用于非x86USB,如扩展卡、嵌入式开发板的USB主控。

UHCIUniversal Host Controller Interface):Intel主导的USB1.0USB1.1的接口标准,与OHCI不兼容,其硬件较为简单,软件则比较复杂。

EHCIEnhanced Host Controller Interface),是Intel主导的USB2.0的接口标准。提供USB2.0的高速功能。

xHCIeXtensible Host Controller Interface),是最新最火的USB3.0的接口标准,它在速度、节能、虚拟化等方面都比前面3种有了较大的提高。xHCI支持所有种类速度的USB设备(USB 3.0 SuperSpeedUSB 2.0 Low-Full-and High-speedUSB 1.1 Low- and Full-speed)。

USB驱动程序框架分析

往开发板上插入一个U盘,借助系统打印出的信息,分析Linux内核系统中USB总线驱动程序是如何运行的。

插上u盘后,终端输出的信息。

7 Linux usb驱动框架分析

拔出u盘后

7 Linux usb驱动框架分析

选择"usb 1-1: new full speed USB device using s3c2410-ohci and address 2"中部分信息,使用grep命令在内核中进行查找,搜索结果如下。

7 Linux usb驱动框架分析

找到drivers\usb\core\hub.c文件,发现是在hub_port_init函数中打印的上述信息。继续搜索,最后列出函数的调用关系如下:

hub_irq

kick_khubd

->wake_up(&khubd_wait); 唤醒khubd_wait

hub_thread

hub_events

wait_event_interruptible (khubd_wait,..) 等待khubd_wait事件

hub_port_connect_change

usb_alloc_dev

    dev->dev.bus = &usb_bus_type;

    …

    choose_address 为新的USB设备分配一个编号(地址)

    hub_port_init

        -> hub_port_reset

        -> hub_set_address 为新的USB设备设置分配的地址编号

        -> usb_get_device_descriptor 获取USB的设备描述符

    usb_new_device

                    usb_get_configuration 获取并解析USB设备的配置描述符

                    device_add 将新的dev放入到usb_bus_ typedev链表中,

                             再从usb_bus_ typedriver链表中取出drv进行一一比较,

                             若匹配成功,则可以调用drvprobe函数。

有关USB设备描述符,请参考:https://www.cnblogs.com/beijiqie1104/p/11725775.html

进入hub_port_connect_change函数。

  1. static void hub_port_connect_change(struct usb_hub *hub, int port1,  
  2.                     u16 portstatus, u16 portchange)  
  3. {  
  4.     struct usb_device *hdev = hub->hdev;  
  5.     struct device *hub_dev = hub->intfdev;  
  6.     ...  
  7.     udev = usb_alloc_dev(hdev, hdev->bus, port1);  
  8.     ...  
  9.     choose_address(udev);  
  10.     ...  
  11.     status = hub_port_init(hub, udev, port1, i);  
  12.     ...  
  13.     status = usb_new_device(udev);  
  14.     ...  
  15. }  

代码第7行,申请一个usb_dev,并将其挂接到usb_bus上。

代码第9行,为usb_dev分配一个地址编号。

代码第11行,初始化hub port端口。

代码第13行,创建一个新的usb device

查看usb_alloc_dev函数。

  1. struct usb_device *  
  2. usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)  
  3. {  
  4.     struct usb_device *dev;  
  5.     
  6.     dev = kzalloc(sizeof(*dev), GFP_KERNEL);  //申请usb_dev结构体空间
  7.     ...  
  8.     
  9.     device_initialize(&dev->dev);      
  10.     dev->dev.bus = &usb_bus_type;              //将设备的总线指向usb_bus_type
  11.     dev->dev.type = &usb_device_type;          //设备的类型为usb_device_type
  12.     ...  
  13.     
  14.     return dev;  
  15. }  

代码中第6行,分配了一个usb_dev结构体。

代码第10行,将结构体usb_devdev.bus指向usb_bus_type。其中usb_bus_typelinux内核中bus_type的一种,与之前的平台总线platform_bus_type类似,具体的定义如下:

7 Linux usb驱动框架分析

其中usb_device_match函数用于实现devicedrvier的匹配。

  1. static int usb_device_match(struct device *dev, struct device_driver *drv)  
  2. {  
  3.     /* devices and interfaces are handled separately */  
  4.     if (is_usb_device(dev)) {   //是否是usb设备
  5.     
  6.         /* interface drivers never match devices */  
  7.         if (!is_usb_device_driver(drv))  
  8.             return 0;  
  9.     
  10.         /* TODO: Add real matching code */  
  11.         return 1;  
  12.     
  13.     } else {                          //USB的接口或者驱动drv
  14.         struct usb_interface *intf;  
  15.         struct usb_driver *usb_drv;  
  16.         const struct usb_device_id *id;  
  17.     
  18.         /* device drivers never match interfaces */  
  19.         if (is_usb_device_driver(drv))  
  20.             return 0;  
  21.     
  22.         intf = to_usb_interface(dev);  //获取usb的接口
  23.         usb_drv = to_usb_driver(drv);  //获取usb的驱动
  24.     
  25.         id = usb_match_id(intf, usb_drv->id_table);  //匹配usb驱动的id
  26.         if (id)  
  27.             return 1;  
  28.     
  29.         id = usb_match_dynamic_id(intf, usb_drv);  
  30.         if (id)  
  31.             return 1;  
  32.     }  
  33.     
  34.     return 0;  
  35. }  

代码第11行,将dev.type指向usb_device_type。其中usb_device_type的定义如下:

7 Linux usb驱动框架分析

紧接着来看看hub_port_connect_change函数中的choose_address函数。

  1. static void choose_address(struct usb_device *udev)  
  2. {  
  3.     int     devnum;  
  4.     struct usb_bus  *bus = udev->bus;  
  5.     
  6.     /* If khubd ever becomes multithreaded, this will need a lock */  
  7.     
  8.     /* Try to allocate the next devnum beginning at bus->devnum_next. */  
  9.     devnum = find_next_zero_bit(bus->devmap.devicemap, 128,  
  10.             bus->devnum_next);  
  11.     if (devnum >= 128)  
  12.         devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);  
  13.     
  14.     bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);  
  15.     
  16.     if (devnum < 128) {  
  17.         set_bit(devnum, bus->devmap.devicemap);  
  18.         udev->devnum = devnum;  
  19.     }  
  20. }  

代码第9行,在bus->devnum_next128之间,查找一个非0的编号。

代码第11行,当编号大于等于128的时候,从1开头处开始查找。

代码第14行,设置下次查找的起始位置。

hub_port_init函数

  1. static int  
  2. hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,  
  3.         int retry_counter)  
  4. {  
  5.     ...  
  6.     buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);  
  7.     ...  
  8.     retval = hub_port_reset(hub, port1, udev, delay);  
  9.     ...  
  10.     retval = hub_set_address(udev);  
  11.     ...  
  12.     retval = usb_get_device_descriptor(udev, 8);  
  13.     ...  
  14.     retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);  
  15.     ...  
  16. }  

代码第10行,设置usb device的地址编号。

代码第12行,获取设备描述符的前8个字节,这8个字节是USB设备描述符的固定字节数,先将8个字节数据读出后,在解析后续需要再重新读取的字节数。

代码第14行,再次获取完整的设备描述符信息。

usb_new_device函数如下:

  1. int usb_new_device(struct usb_device *udev)  
  2. {  
  3.     ...  
  4.     err = usb_get_configuration(udev);  //获取USB设备的配置描述符
  5.    ...  
  6.     err = device_add(&udev->dev);          //将设备加入到usb device设备链表中
  7.    ...  
  8. }  

代码第4行,获取设备的配置描述符,其中usb_get_configuration函数定义如下:

  1. int usb_get_configuration(struct usb_device *dev)  
  2. {  
  3.     ...  
  4.     int ncfg = dev->descriptor.bNumConfigurations; //获取设备配置描述符的个数
  5.     ...  
  6.     for (cfgno = 0; cfgno < ncfg; cfgno++) {          //循环
  7.     ...  
  8.     result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, 
  9.             buffer, USB_DT_CONFIG_SIZE);  //获取配置描述符的前九个字节 
  10.     ...  
  11.     length = max((int) le16_to_cpu(desc->wTotalLength),  
  12.             USB_DT_CONFIG_SIZE);  //获取设备配置描述符的长度
  13.     ...  
  14.     result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,  
  15.             bigbuffer, length);  //取出完整的配置描述符
  16.     ...  
  17.     result = usb_parse_configuration(&dev->dev, cfgno,  
  18.             &dev->config[cfgno], bigbuffer, length);  //解析配置描述符
  19.    ...  
  20.    }  
  21. }  

接着来看看device_add函数。

  1. int device_add(struct device *dev)  
  2. {  
  3.     ...  
  4.     dev = get_device(dev);   //获取设备
  5.     ...  
  6.     if ((error = bus_add_device(dev)))  //将设备添加到总线上的device链表中
  7.     ...  
  8.     bus_attach_device(dev);  
  9.     ...  
  10. }  

总结:

linux内核中USB的驱动分为USB总线驱动和USB设备驱动两部分。系统中hub_thread在没有USB连接的时候,处于睡眠状态,一旦主机控制器检测到USB设备插入的时候,会产生hub_irq中断,唤醒hub_thread线程。然后会创建新的USB设备,分配地址编号,获取设备的描述符信息,将其放入到设备链表中,并和drv链表中的id_table进行匹配,若匹配成功则调用drvprobe函数。框架中已经将总线驱动完成,我们需要做的是编写usb的设备驱动,重点就是probe函数。

相关推荐