1、USB驱动层次简介

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

linux内核usb架构 linux 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的探测函数,来解析设备信息。

识别过程如下所示:

linux内核usb架构 linux usb驱动架构_设备驱动程序_02

① 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函数。匹配过程如下图所示:

linux内核usb架构 linux usb驱动架构_linux内核usb架构_03

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驱动程序,加深这部分的理解