zoukankan      html  css  js  c++  java
  • Linux USB驱动层次分析

    1、USB驱动层次简介

    Linux内核中USB驱动程序分为两类:USB主机控制器驱动程序(Host Controller Driver)、USB设备驱动程序(USB device drivers),它们在内核中的USB驱动的层次关系,如下图所示:

    由上图可以看出,内核中的USB驱动层次可以分为三层。USB主机控制器驱动位于USB驱动层次最底层,直接作用于UBS主机控制器硬件之上,在主机控制器上的为USB核心,再上层为USB设备驱动层。

    对于以上几部分的功能,我们可以大致进行以下概括:

    • USB主机控制器驱动程序提供访问USB设备的接口,它是一个“数据通道”,至于这些数据有什么作用,这要靠上层的USB设备驱动程序来解释。
    • USB设备驱动程序使用下层的驱动提供的接口来访问USB设备,不需要关心主机控制器和设备如何进行通信。
    • USB核心负责USB驱动管理和协议处理的主要工作,其功能包括:通过定义一些数据结构、宏和功能函数,向上为设备驱动提供编程接口,向下为USB主机控制器驱动提供编程接口;维护整个系统的USB设备信息;完成设备热插拔控制、总线数据传输等。

    2、USB主机控制器驱动

    USB主机控制器有OHCI(Open Host Controller Interface)、UHCI(Universal Host Controller Interface)、EHCI(Enhanced Host Controller Interface)、xHCI(eXtensible Host Controller Interface)等类型,这部分驱动程序由内核实现。

    我们可以根据目标板硬件的信息,通过make menuconfig配置内核支持那种USB主机控制器。USB主机控制器驱动程序实现的功能如下:

    • 1、识别插入的USB设备
    • 2、查找并安装对应的驱动程序
    • 3、提供了USB读写函数

    下面我们将围绕以上的几点对USB主机控制器驱动程序进行分析

    2.1 识别插入的USB设备

    识别插入的USB设备也称为设备的枚举过程,这一过程就是从设备读取各种描述符,从而知道设备是什么样的设备,如何进行通信等。USB设备插拔检测是基于硬件机制实现的,当USB插入,信号线D+或D-就会被拉高,主机控制器就会产生一个hub_irq中断,控制器调用hub的探测函数,来解析设备信息。

    识别过程如下所示:

    ① hub_events是在hub_thread线程中调用的hub事件处理函数,每次执行一次hub事件,hub_thread就会进入休眠等待。当usb port上状态发现变化时,hub_thread线程被唤醒,hub事件处理函数被执行

    ② hub事件处理函数遍历hub中所有的port,对各个port进行查看,判断port是否发生了变化,如果产生变化就调用hub_port_connect_change进行处理。hub_port_connect_change分为两个部分,第一部分主要是确认是否有新设备插入,第二部分是如果有新设备插入后,分配设备资源,并进行枚举操作

    ③ 通过usb_alloc_dev为新的usb设备分配设备资源,并进行一些列的初始化,将usb设置为USB_STATE_ATTACHED,表示设备已经连接

    ④ choose_address为新设备分配地址,每次的地址编号是在已分配的地址连续加的,若编号大于127并且没有找到可以地址编号,将继续从头开始收索

    ⑤ 通过hub_port_init对usb设备进行reset,并设置usb设备的地址,获取usb设备的设备描述符

    ⑥ 通过hub_set_address设置usb设备地址,设置地址为④中分配的地址,此后usb主机控制器不在使用默认地址0,而是使用这个地址与usb设备进行通信

    ⑦ 通过usb_get_device_descriptor获取usb设备的描述符

    ⑧ usb_new_device主要是获取usb设备信息,将usb设备添加到usb总线上去

    ⑨ 调用usb_parse_configuration解析获取得到的信息,将包含配置,相关接口,端点等的这些信息,补全到之前申请的usb_host_config结构里

    ⑩ 将usb设备添加到usb总线,并匹配支持的usb设备驱动程序

    2.2 查找并安装对应驱动程序

    识别插入的usb设备后,调用usb_new_device创建新设备,最终调用到了device_add函数。我们知道device_add函数是内核中“总线-设备-驱动”模型中的创建设备的函数,在device_add函数中会将device挂载到总线上,并依次寻找总线上的挂载的驱动模型,调用总线的.match函数进行匹配。如果匹配成功,将调用驱动模型中的probe函数。匹配过程如下图所示:

    usb总线usb_bus_type中的match函数

    static int usb_device_match(struct device *dev, struct device_driver *drv)
    {
        /* devices and interfaces are handled separately */
        if (is_usb_device(dev)) {
    
            /* interface drivers never match devices */
            if (!is_usb_device_driver(drv))
                return 0;
    
            /* TODO: Add real matching code */
            return 1;
    
        } else {
            struct usb_interface *intf;
            struct usb_driver *usb_drv;
            const struct usb_device_id *id;
    
            /* device drivers never match interfaces */
            if (is_usb_device_driver(drv))
                return 0;
    
            intf = to_usb_interface(dev);
            usb_drv = to_usb_driver(drv);
    
            id = usb_match_id(intf, usb_drv->id_table);
            if (id)
                return 1;
    
            id = usb_match_dynamic_id(intf, usb_drv);
            if (id)
                return 1;
        }
    
        return 0;
    }

    match函数中将usb设备和usb设备接口的驱动匹配分离开,如果设备过来了,走到了设备这条路,然后要判断下驱动是不是设备驱动,是不是针对整个设备的,如果不是的话,设备找不到对应的驱动,匹配不成功,就直接返回了。如果usb设备接口来了,走到了接口这条路,然后看匹配的是不是usb设备的驱动程序,如果是直接返回匹配不成功,否则继续进行匹配,接口驱动程序的匹配是通过id_table进行匹配的

    3、USB设备驱动

    USB主机驱动程序提供了USB设备的插拔识别和枚举操作,以及访问USB设备的接口,它是一个“数据通道”,完成了数据的传输。至于这些数据有什么作用,这要靠USB设备驱动程序来解释,USB设备驱动程序实现对主机控制器驱动获取的数据的进行加工使用

    3.1 支持设备配置

    每个USB设备被挂载后,会自动寻找与之匹配的驱动程序,但每个设备有不同的配置,每个配置又有不同的接口,每个接口可能实现不同的逻辑功能,不同的逻辑功能又需要匹配不同的驱动。根据每个设备的接口逻辑,编写驱动程序这就是驱动开发者需要完成的工作。那么如何知道什么样的设备驱动匹配什么样的设备呢?这就是通过注册的设备驱动中的usb_driver结构体中的id_table来完成的

    struct usb_device_id {
        /* which fields to match against? */
        __u16        match_flags;
    
        /* Used for product specific matches; range is inclusive */
        __u16        idVendor;
        __u16        idProduct;
        __u16        bcdDevice_lo;
        __u16        bcdDevice_hi;
    
        /* Used for device class matches */
        __u8        bDeviceClass;
        __u8        bDeviceSubClass;
        __u8        bDeviceProtocol;
    
        /* Used for interface class matches */
        __u8        bInterfaceClass;
        __u8        bInterfaceSubClass;
        __u8        bInterfaceProtocol;
    
        /* not matched against */
        kernel_ulong_t    driver_info;
    };

    .match_flags:说明使用那种匹配方式

    内核中提供了几个宏来初始化这个结构体

    USB_DEVICE(vendor, product) 创建一个 struct usb_device_id,可用来只匹配特定供应商和产品 ID 值,对于需要特定驱动的 USB 设备,这是非常普遍用的。

    USB_DEVICE_VER(vendor, product, lo, hi) 创建一个 struct usb_device_id,用来在一个版本范围中只匹配特定供应商和产品 ID 值。

    USB_DEVICE_INFO(class, subclass, protocol) 创建一个 struct usb_device_id,可用来只匹配一个特定类的 USB 设备。

    USB_INTERFACE_INFO(class, subclass, protocol) 创建一个 struct usb_device_id,可用来只匹配一个特定类的 USB 接口。

    3.2 请求usb数据传输实现

    设备驱动程序和主机控制器之间数据传输是通过urb(usb request block)来描述的,usb总线就像一条高速公路,货物、人流之类的可以看成是系统与设备交互的数据,而urb就可以看成是总线这条高速线路上汽车司机运输的订单。它不仅告诉了这辆汽车要运送什么东西,目的地是什么,要运输的东西有多大,还告诉了汽车司机运送到目的地要找告诉谁去接收货物。

    1)urb需要在设备驱动程序进行分配和初始化,一般通过调用usb_alloc_urb()函数进行动态分配

    2)分配完成urb结构后,还需对urb中的信息进行初始化,比如传输源地址(根据设备地址和端点信息确定),目的地址(分配存放传输数据内存)和传输数据的长度,以及传输完成后的处理函数等等。不同的传输方式有不同的urb填充函数,如下所示:

      控制传输:usb_fill_control_urb

      批量传输:usb_fill_bulk_urb

        中断传输:usb_fill_int_urb

    3)以上两步完成,就可以调用usb_submit_urb()函数提交urb信息。如果usb_submit_urb()调用成功,即urb 的控制权被移交给USB 核心,该函数返回0

    提交的urb由USB 核心指定的USB 主机控制器驱动,被USB 主机控制器处理,进行一次到USB 设备的传送。当传输完成将调用urb_fill_xxx_urb中指定的传输完成的处理函数

    通过urb就实现了设备驱动和usb设备之前的数据的进行访问过程,后面我们将开始编写usb驱动程序,加深这部分的理解

  • 相关阅读:
    Leetcode 257. 二叉树的所有路径
    Leetcode 1306. 跳跃游戏 III
    Leetcode 编程中好用的一些C++用法
    Leetcode 96. 不同的二叉搜索树
    Leetcode 892. 三维形体的表面积
    Leetcode 219. 存在重复元素 II
    Leetcode 5281. 使结果不超过阈值的最小除数
    springboot多租户设计
    MAC电脑修改Terminal以及vim高亮显示
    基于springboot搭建的web系统架构
  • 原文地址:https://www.cnblogs.com/053179hu/p/13909219.html
Copyright © 2011-2022 走看看