zoukankan      html  css  js  c++  java
  • 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读写函数。

    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盘后,终端输出的信息。

    拔出u盘后

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

    找到driversusbcorehub.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类似,具体的定义如下:

    其中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的定义如下:

    紧接着来看看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函数。

  • 相关阅读:
    .NET创建WebService服务简单的例子
    SVN服务器搭建
    Visual Studio 2017 系统发布部署服务器教程
    C#进阶--WebApi异常处理机制
    利用iis虚拟目录实现文件服务器功能(分布式存储)
    jQuery 选择器
    jQuery 简介
    从「闭包」到 思考人生
    跨域
    ajax 和 mock 数据
  • 原文地址:https://www.cnblogs.com/beijiqie1104/p/11731123.html
Copyright © 2011-2022 走看看