一,前言

这一篇学习分析USB设备驱动程序,主要涉及到USB驱动基本概念,USB版本对比、USB主从结构、USB设备的传输类型、USB设备描述符关系、USB的数据传输对象;详细分析了USB总线驱动框架,USB Core、USB HCD、USB总线-设备-驱动模型;最后总结了USB驱动开发中的一般流程并模拟实现一个USB设备驱动程序。

二,USB子系统
2.1 USB驱动基础概念
2.1.1 USB版本

USB1.0版本,USB LS(Low Speed低速),速度1.5Mbps。

USB1.1版本,USB FS(Full Speed全速),速度12Mbps。

USB2.0版本,USB HS(High Speed高速),速度480Mbps。

USB3.0版本,USB SS(Super Speed超速),速度5Gbps。也称为 USB 3.1 Gen 1 或 USB 3.2 Gen 1

USB3.1版本,USB SS(Super Speed超速),速度10Gbps。也称为 USB 3.2 Gen 2

USB 3.2 是对 USB 3.0 和 USB 3.1 标准的重新命名
USB3.2 版本,USB SS(Super Speed超速),速度20Gbps。

USB 3.2 Gen 1:也称为 USB 3.0 或 USB 3.1 Gen 1,传输速度为 5Gbps。
USB 3.2 Gen 2:也称为 USB 3.1 Gen 2,传输速度为 10Gbps。
USB 3.2 Gen 2x2:这是最新的版本,传输速度可达 20Gbp

在这里插入图片描述

2.1.2 USB主从结构

USB分为USB 主机(USB Host)和USB device(USB设备)。比如USB鼠标插入到电脑,电脑就是USB Host,鼠标就是USB device。所有的USB传输,都是从USB主机这方发起的;USB设备没有"主动"通知USB主机的能力。

2.1.3 USB的传输类型

在 USB 通信中,控制传输(Control Transfer)是一个非常常见且重要的传输类型,主要用于配置设备、查询设备状态以及发送和接收命令。控制传输有三种主要形式:控制写传输(Control Write Transfer)、控制读传输(Control Read Transfer)和无数据控制传输(No Data Control Transfer)。这些传输类型在不同的场景下使用,具体如下:

  1. 控制写传输 (Control Write Transfer)

    场景:当主机(Host)需要向设备(Device)发送配置信息或命令时使用控制写传输。例如:
    向设备发送设置命令或配置参数,例如设置音频设备的音量。
    向设备发送特定的请求来改变设备的状态,例如设置设备的工作模式。
    示例:主机通过控制写传输向设备发送一组特定的数据,如设置 LED 灯的颜色或改变设备的通信速率。

  2. 控制读传输 (Control Read Transfer)

    场景:当主机需要从设备获取信息时使用控制读传输。这通常用于查询设备状态或获取设备描述符等信息。例如:
    主机请求设备返回设备描述符,以获取设备的基本信息(如厂商 ID、产品 ID 等)。
    主机请求设备的状态或配置信息,例如查询设备当前的电池电量。
    示例:主机通过控制读传输从设备读取某个传感器的当前读数或获取设备的固件版本信息。

  3. 无数据控制传输 (No Data Control Transfer)

    场景:当主机仅发送一个控制请求,但不需要传输任何数据时使用无数据控制传输。这种情况常见于简单的命令或状态请求中。例如:
    主机请求设备执行某个操作,但不需要传输额外的数据或接收设备的响应数据。
    用于简单的请求,如设备复位、设备清除特定状态或发出一些简单的指令。
    示例:主机发送一个复位请求给设备,然后设备复位后进入默认状态。

这三种控制传输类型通常用于 USB 设备的初始化、配置和状态管理,是确保 USB 设备和主机之间能够正确通信的基础。

2.1.4 USB设备描述符
在这里插入图片描述

我们第一个学习要点就是USB描述符,所谓描述符其实就是C语言里面的结构体或者数组,数组包含的信息说明当前的设备具有哪些特征。USB描述符有设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符,HID设备有HID描述符、报告描述符和物理描述符。我们先学会每个描述符的细节,我会写的非常详细且简洁,后面在学习USB枚举的时候会通过抓包和波形来学习每一个描述符在总线上的作用,并且会介绍每一个描述符是在什么时候以哪种方式在总线上进行传输的,今天主要是学习USB设备描述符的组成。

设备描述符是USB主机枚举USB设备申请的第1个描述符,每个设备有且仅有一个设备描述符,也就是大家以后看到的任何的USB设备都只有一个设备描述符数组,设备描述符的长度是18字节,下面我们来学习设备描述符数据结构,看看USB设备具有哪些特征,它的结构我们可以通过下面的表格来逐一了解。
2.1.4.1 设备描述符

/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
	__u8  bLength; //该结构体大小
	__u8  bDescriptorType; //描述符类型 (此处应为0x01,即设备描述符)
 
	__le16 bcdUSB; //usb版本号 200 -> USB2.0
	__u8  bDeviceClass; //设备类 
	__u8  bDeviceSubClass; //设备类子类
	__u8  bDeviceProtocol; //设备协议,以上三点都是USB官方定义
	__u8  bMaxPacketSize0; //端点0最大包大小 (只能为8,16,32,64)
	__le16 idVendor; //厂家id
	__le16 idProduct; //产品id
	__le16 bcdDevice; //设备出厂编号
	__u8  iManufacturer; //描述厂商信息的字符串描述符的索引值
	__u8  iProduct; //描述产品信息的字串描述符的索引值
	__u8  iSerialNumber; //描述设备序列号信息的字串描述符的索引值 
	__u8  bNumConfigurations; //可能的配置描述符的数目
} __attribute__ ((packed));

举例,设备描述符代码实现如下:

#define 	USB_DEVICE_DESCRIPTOR_TYPE              	0x01		// 设备描述符类别
	#define 	USB_OTG_MAX_EP0_SIZE                 		64			// 端点0支持的最大包长
	#define 	USBD_VID                        			0x0483		// 厂商ID		
	#define 	USBD_PID                       		 		0xDF11		// 产品ID
	#define  	USBD_IDX_MFC_STR                            0x01 		// 厂商字符串的索引
	#define  	USBD_IDX_PRODUCT_STR                        0x02		// 产品字符串的索引
	#define  	USBD_IDX_SERIAL_STR                         0x03		// 产品序列号字符串的索引
	#define 	USBD_CFG_MAX_NUM                			1 			// 设备的配置数	
	/* USB Standard Device Descriptor */
	uint8_t USBD_DeviceDesc[USB_SIZ_DEVICE_DESC] =
	  {
	    0x12,                       /*bLength */
	    USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType*/
	    0x00,                       /*bcdUSB */
	    0x02,
	    0x00,                       /*bDeviceClass*/
	    0x00,                       /*bDeviceSubClass*/
	    0x00,                       /*bDeviceProtocol*/
	    USB_OTG_MAX_EP0_SIZE,      /*bMaxPacketSize*/
	    LOBYTE(USBD_VID),           /*idVendor low*/
	    HIBYTE(USBD_VID),           /*idVendor high*/
	    LOBYTE(USBD_PID),           /*idVendor low*/
	    HIBYTE(USBD_PID),           /*idVendor high*/
	    0x00,                       /*bcdDevice rel. 2.00*/
	    0x02,
	    USBD_IDX_MFC_STR,           /*Index of manufacturer  string*/
	    USBD_IDX_PRODUCT_STR,       /*Index of product string*/
	    USBD_IDX_SERIAL_STR,        /*Index of serial number string*/
	    USBD_CFG_MAX_NUM            /*bNumConfigurations*/
	  } ; /* USB_DeviceDescriptor */

下面来介绍设备描述符每一个内容的详细含义(所有的描述符都是小端格式,先低后高)

(1)bLength

描述符长度(18字节,十六进制为0x12),就是标志描述符数据结构的长度。

(2)bDescriptorType

bDescriptorType代表了本描述符的类型,设备描述符为0x01。所有的描述符类型表示如下图,大家以后也可以速查:
在这里插入图片描述
(3)bcdUSB

USB协议版本,表示形式0xJJMN版本JJ.M.N(JJ - 主要版本号,M - 次要版本号,N - 次要版本)

例子:如果是USB2.0,写成:0200H;如果是USB1.1,写成:0110H 如果是USB3.11,写成:0311H

(4)bDeviceClass、bDeviceSubClass、bDeviceProtocol

bDeviceClass、bDeviceSubClass、bDeviceProtocol分别代表设备类型,子类型,设备使用的协议,USB-IF区分设备类分了三个等级(类-子类-协议码)其中,类包含人机交互类、图像类、无线类、音频类等等,子类比如音频类的音频控制、音频流等等,协议比如人机接口类中的鼠标、键盘、触摸屏等,为何会有这么多USB的Class分类,子分类,设备协议。我们要知道,USB协议设计的目的,就是为实现通用,用单一的USB接口取代之前种类繁多的各种其他接口。而为了取代其他各种接口,那意味着就要实现各种设备所对应的各种功能。如下图显示USB设备的各种类别,USB设备类信息更详细内容可进入https://www.usb.org/defined-class-codes查看。
在这里插入图片描述(5)bMaxPackeSize0

就是端点一次最大传多少个字节。USB协议里有规定,端点0最低8字节,端点的最大传输大小和USB速度等级以及传输类型有关,控制传输一般使用端点0,低速最大8字节,全速和高速最大传输64字节,如下图:

(6)idVender

厂商ID,就是个2字节的编号,由USB协议分配,厂商申请时需要交费。自己随便写的话,仅限于学习、测试的情况下,不能用做产品。

(7)idProduct

产品ID,厂家自己随便定义。

(8)bcdDevice

产品版本号,厂家自己随便定义。

(9)iManufacturer

描述厂商的字符串的索引,为0则表示没有,后面详细讲解。

(10)iProduct

描述产品的字符串的索引,为0则表示没有,后面详细讲解。

(11)iSerialNumber

产品序列号字符串的索引,为0则表示没有,后面详细讲解。

(12)bNumConfigurations

指示设备由多少个配置,前面提到过,一个USB可能有多个配置,一般USB产品都是1个配置。

2.1.4.2 配置描述符

struct usb_config_descriptor {
	__u8  bLength; //该结构体大小
	__u8  bDescriptorType;//描述符类型(本结构体中固定为0x02)  
 
	__le16 wTotalLength; //该配置下,信息的总长度(包括配置,接口,端点和设备类及厂商定义的描述符)
	__u8  bNumInterfaces; //接口的个数
	__u8  bConfigurationValue; //Set_Configuration命令所需要的参数值,用来选定此配置
	__u8  iConfiguration; //描述该配置的字符串描述的索引值 
	__u8  bmAttributes;//供电模式的选择  
	__u8  bMaxPower;//设备从总线提取的最大电流
} __attribute__ ((packed));

2.1.4.3 接口描述符

配置描述符中包含了一个或多个接口描述符,这里的“接口”并不是指物理存在的接口,在这里把它称之为“功能”更易理解些,例如一个设备既有录音的功能又有扬声器的功能,则这个设备至少就有两个“接口”。

struct usb_interface_descriptor {
	__u8  bLength;		//该结构体大小
	__u8  bDescriptorType;//接口描述符的类型编号(0x04)
 
	__u8  bInterfaceNumber;  //该接口的编号  
	__u8  bAlternateSetting; //备用的接口描述符编号  
	__u8  bNumEndpoints; //该接口使用的端点数,不包括端点0  
	__u8  bInterfaceClass; //接口类
	__u8  bInterfaceSubClass; //子类
	__u8  bInterfaceProtocol; //协议
	__u8  iInterface;//描述此接口的字串描述表的索引值  
} __attribute__ ((packed));

2.1.4.4 端点描述符

端点是设备与主机之间进行数据传输的逻辑接口,除配置使用的端点0(控制端点,一般一个设备只有一个控制端点)为双向端口外,其它均为单向。端点描述符描述了数据的传输类型、传输方向、数据包大小和端点号(也可称为端点地址)等。

除了描述符中描述的端点外,每个设备必须要有一个默认的控制型端点,地址为0,它的数据传输为双向,而且没有专门的描述符,只是在设备描述符中定义了它的最大包长度。主机通过此端点向设备发送命令,获得设备的各种描述符的信息,并通过它来配置设备。

/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
	__u8  bLength;        //端点描述符字节数大小(7个字节)
	__u8  bDescriptorType;//端点描述符类型编号(0x05) 
 
	__u8  bEndpointAddress; //此描述表所描述的端点的地址、方向 : 
                            // bit3~bit0:端点号,bit6~bit4:保留,
                            // bit7:方向,如果是控制端点则忽略,0-输出端点(主机到设备)1-输入端点(设备到主机)
	__u8  bmAttributes; // 端点特性,bit1~bit0 表示传输类型,其他位保留
                        // 00-控制传输  01-实时传输   10-批量传输 11-中断传输
	__le16 wMaxPacketSize;  //端点收、发的最大包大小
	__u8  bInterval; // 中断传输模式中主机查询端点的时间间隔。
                     // 对于实时传输的端点此域必需为1,表示周期为1ms。对于中断传输的端点此域值的范围为1ms到255ms
 
	/* NOTE:  these two are _only_ in audio endpoints. */
	/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
	__u8  bRefresh;
	__u8  bSynchAddress;
} __attribute__ ((packed));

2.1.4.5 字符串描述符

struct usb_string_descriptor {
	__u8  bLength;  // 此描述表的字节数(bString域的数值N+2)
	__u8  bDescriptorType; // 字串描述表类型(此处应为0x03)

	__le16 wData[1];		/* UTF-16LE encoded */  
} __attribute__ ((packed));

2.2 USB子系统框架
如下图所示,Linux内核中USB主机体系结构由五部分组成,分别为Application Software、USB Class Driver、USB Core((USB Driver)、USB Host Controller Driver、USB Host Controller。应用程序处于用户空间,通过系统调用访问Class Driver,从而间接的访问USB设备,如主机端的应用程序aplay、arecord可以访问USB音频设备。Class Driver是某一类设备驱动,不同类设备会匹配不同的Class Driver,如USB音频设备会匹配Audio驱动,USB存储设备会匹配Mass Storage驱动,鼠标和键盘会匹配HID驱动。USB Core(USB Driver)是内核设计的一个抽象层,目的是将Class Driver和USB Host Controller Driver分隔开,使两者都依赖一个稳定的中间层;USB Core(USB Driver)向上提供通信接口,向下统一管理USB设备,同时完成USB设备和USB Class Driver的匹配工作。USB Host Controller目前有4种不同的硬件级接口标准,分别为OHCI、UHCI、EHCI、xHCI,OHCI和UHCI实现了USB1.1,EHCI实现了USB2.0,xHCI实现了USB3.2,不同的接口标准都有对应的USB Host Controller Driver,如xHCI对应于xhci-hcd驱动,向下兼容OHCI和EHCI。最底层是USB Host Controller硬件。下面将分别介绍Linux内核中USB主机体系结构USB Class Driver、USB Core(USB Driver)、USB Host Controller Driver四个部分。
在这里插入图片描述

三,USB总线驱动程序

3.1 USB Core

初始化内核USB总线及提供USB相关API,为设备驱动和HCD的交互提供桥梁。

Linux启动阶段,通过subsys_initcall会完成USB Core的加载

在./drivers/usb/core/usb.c里实现了初始化,源代码如下,

static int __init usb_init(void)
{
  
    retval = bus_register(&usb_bus_type); //注册USB总线,也就是USB子系统
   
    retval = usb_hub_init(); //usb hub初始化 
   
    retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE); //注册一个USB设备(不是接口)驱动程序
   
}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/jiangchaobing_2017/article/details/142660572

3.1.1 注册 USB 总线

USB是基于总线-驱动-设备模型的框架,其初始化阶段一个重点任务就是完成USB总线的创建。usb_bus_type提供了驱动和设备匹配的匹配函数,后面注册设备和驱动时会调用到。

struct bus_type usb_bus_type = {  
    .name = "usb",  //总线名   
    .match =    usb_device_match,   //匹配方法,应该用于usb驱动id_table的匹配   
    .uevent =   usb_uevent, //事件处理   
};  
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/jiangchaobing_2017/article/details/142660572

3.1.2 注册USB接口驱动

一个设备可以有多个接口,每个接口对应着不同的功能。

// 在usb总线注册USB接口驱动,该驱动被放在usb总线的驱动链表中。
retval = usb_register(&usbfs_driver);
if (retval)
    goto driver_register_failed;

struct usb_driver usbfs_driver = {
	.name =		"usbfs",
	.probe =	driver_probe,
	.disconnect =	driver_disconnect,
};

3.1.3 初始化USB Hub

初始化一个USB设备集线器,用来检测USB设备的连接和断开。
[usb/core/hub.c ]

int usb_hub_init(void)
{
    if (usb_register(&hub_driver) < 0) { //注册usb HUB设备
        printk(KERN_ERR "%s: can't register hub driver\n",
            usbcore_name);
        return -1;
    }

    /*
     * The workqueue needs to be freezable to avoid interfering with
     * USB-PERSIST port handover. Otherwise it might see that a full-speed
     * device was gone before the EHCI controller had handed its port
     * over to the companion full-speed controller.
     */
    /* 工作队列需要是可冻结的,以避免干扰usb持续的端口切换。
    否则它可能会看到全速设备在EHCI控制器把端口交给全速控制器之前就消失了 */
    hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);  //创建工作队列
    if (hub_wq)
        return 0;
}

static struct usb_driver hub_driver = {
	.name                 =	    "hub",
	.probe                =	    hub_probe,
	.disconnect           =	    hub_disconnect,
	.suspend              =	    hub_suspend,
	.resume               =	    hub_resume,
	.reset_resume         =		hub_reset_resume,
	.pre_reset            =		hub_pre_reset,
	.post_reset           =		hub_post_reset,
	.unlocked_ioctl       = 	hub_ioctl,
	.id_table             =		hub_id_table,
	.supports_autosuspend =		1,
};
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/jiangchaobing_2017/article/details/142885341
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/jiangchaobing_2017/article/details/142885341

hub_probe 在后面向USB总线注册一个和hub驱动匹配的hub设备时会被调用。

hub_probe ->    
    hub_configure ->
        usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
                hub, endpoint->bInterval);
        // usb_fill_int_urb 接口创建了一个中断类型的 USB请求控制块
            hub_irq -> 
                kick_khubd(hub) ->
                    wake_up(&khubd_wait);

3.1.4 注册USB设备驱动

// 在usb总线注册USB设备驱动,该驱动被放在usb总线的驱动链表中。
retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
if (!retval)
    goto out;

struct usb_device_driver usb_generic_driver = {
	.name =	"usb",
	.probe = generic_probe,
	.disconnect = generic_disconnect,
#ifdef	CONFIG_PM
	.suspend = generic_suspend,
	.resume = generic_resume,
#endif
	.supports_autosuspend = 1,
};

3.1.5 usb_register 和 usb_register_device_driver

usb_register 注册一个USB接口驱动,一个设备可以有多个接口,一个接口表示一种功能。比如USB声卡设备,有两个接口,一个播放接口,一个录音接口。

include/linux/usb.h
static inline int usb_register(struct usb_driver *driver)
{
	return usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME);
}

drivers/usb/core/driver.c
int usb_register_device_driver(struct usb_device_driver *new_udriver,struct module *owner)  

{  
    int retval = 0;  
    if (usb_disabled())  
        return -ENODEV;  
    new_udriver->drvwrap.for_devices = 1;    //usb设备   
    new_udriver->drvwrap.driver.name = (char *) new_udriver->name;    //设备名   
    new_udriver->drvwrap.driver.bus = &usb_bus_type; //总线类型   
    new_udriver->drvwrap.driver.probe = usb_probe_device;    //probe方法   
    new_udriver->drvwrap.driver.remove = usb_unbind_device;  //remove方法   
    new_udriver->drvwrap.driver.owner = owner;   //模块所有者   
    retval = driver_register(&new_udriver->drvwrap.driver);  //注册设备驱动   
    if (!retval) {  
        pr_info("%s: registered new device driver %s\n",usbcore_name, new_udriver->name);  
        usbfs_update_special();  
    } else {  
        printk(KERN_ERR "%s: error %d registering device driver %s\n",usbcore_name, retval, new_udriver->name);  
    }  
    return retval;  
}  
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/jiangchaobing_2017/article/details/142660572

usb_register_device_driver 注册一个通用USB设备驱动,而不是USB接口驱动。

3.1.6 总结

USB core注册了一个USB总线,并向USB总线中注册了三个驱动,分别是USB接口驱动、HUB驱动、USB设备驱动。其中在注册HUB驱动前创建了一个hub_thread线程,用来处理hub上USB设备事件,比如插入和拔出;在HUB驱动的probe函数中,创建了一个urb并为其注册了一个中断处理函数hub_irq,用来唤醒hub_thread线程来处理USB设备事件。

3.2 USB主机控制器驱动(HCD)

USB HCD注册在平台总线上。用来处理主机控制器的初始化以及数据的传输,并监测外部设备插入、拔出,完成设备枚举。
3.2.1 USB主机控制器驱动的probe函数

ohci_hcd_s3c2410_drv_probe -> 
    usb_hcd_s3c2410_probe ->
        usb_add_hcd -> 
            rhdev = usb_alloc_dev
            hcd->self.root_hub = rhdev
            register_root_hub -> 
                usb_new_device ->
                    device_add ->     
                        bus_attach_device ->
                            device_attach -> 
                                bus_for_each_drv -> // 从usb总线的的驱动链表中,取出每一项驱动进行匹配
                                    __device_attach ->
                                        driver_probe_device ->
                                            // 此总线类型为USB总线,其存在match函数,即调用usb_device_match进行匹配
                                            if (drv->bus->match && !drv->bus->match(dev, drv)) 
                                                driver_probe_device-> // 在此函数中匹配成功的话,就会去调用驱动的probe函数
                                                    really_probe->
                                                        drv->probe(dev)

3.2.2 usb_device_match

static inline int is_usb_device(const struct device *dev)
{
	return dev->type == &usb_device_type;
}

/* Do the same for device drivers and interface drivers. */

static inline int is_usb_device_driver(struct device_driver *drv)
{
    // struct device_driver 中 struct usbdrv_wrap 中的for_devices变量为1,则为USB设备驱动
    // 上节USB Core中向USB总线注册的USB设备驱动中有将该变量设置为1(new_udriver->drvwrap.for_devices = 1;)
	return container_of(drv, struct usbdrv_wrap, driver)->
			for_devices;
}

static int usb_device_match(struct device *dev, struct device_driver *drv)
{
	// USB设备 和 USB接口“设备”分开处理 
	/* devices and interfaces are handled separately */
	if (is_usb_device(dev)) {
		// 处理USB设备
		/* interface drivers never match devices */
		if (!is_usb_device_driver(drv))
			return 0;

		/* TODO: Add real matching code */
		return 1;

	} else {
        // 处理USB接口设备
		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;
}

probe 向USB总线注册一个root hub 设备,从usb总线的的驱动链表中,取出每一项驱动进行匹配。在USB Core中已经向总线注册了三个驱动(USB设备驱动、USB接口驱动、USB hub驱动),根据条件匹配到USB设备驱动,则去调用USB设备驱动的probe函数。

3.2.3 USB设备驱动的probe函数

generic_probe(struct usb_device *udev) -> // 从上分析流程知udev为USB root hub设备 
    usb_set_configuration ->
        device_add ->  // 创建USB接口设备,USB root hub接口设备被创建

总结下USB设备和驱动匹配的全过程,
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
USB设备各个配置的详细信息在USB core里的漫漫旅途中已经被获取并存放在相关的几个成员里。

usb_generic_driver得到了USB设备的详细信息,然后把准备好的接口送给设备模型,Linux设备模型将接口添加到设备链表里,然后去轮询USB总线另外一条驱动链表,针对每个找到的驱动去调用USB总线的match函数,完成匹配。

四,USB设备驱动 – USB鼠标

用于和枚举到的USB设备进行绑定,完成特定的功能。
比如USB鼠标设备,驱动开发主要是这一块代码的coding。
4.1 注册一个USB接口驱动

向USB总线注册一个USB接口驱动

// linux-2.6.22.6/drivers/hid/usbhid/usbmouse.c

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

static int __init usb_mouse_init(void)
{
	int retval = usb_register(&usb_mouse_driver);
	if (retval == 0)
		info(DRIVER_VERSION ":" DRIVER_DESC);
	return retval;
}

4.2 USB接口设备的创建

当一个USB 鼠标设备插入后,主机USB控制器检测到后,触发USB设备集线器中的"中断"处理函数hub_irq。在hub_irq中会获取USB鼠标设备的设备描述符,根据设备描述符创建USB接口设备,从而和这边的USB接口驱动匹配,调用其probe函数,通过USB总线驱动程序(USB Core和USB HCD)和USB鼠标设备建立联系,进而操作(读写控制)该设备。

hub_irq
	kick_khubd // 唤醒hub_thread线程
		hub_thread
			hub_events // 处理USB设备插入事件
				hub_port_connect_change
				
					udev = usb_alloc_dev(hdev, hdev->bus, port1);
								dev->dev.bus = &usb_bus_type;
				
					choose_address(udev); // 给新设备分配编号(地址)										
					hub_port_init   // usb 1-1: new full speed USB device using s3c2410-ohci and address 3
						
						hub_set_address  // 把编号(地址)告诉USB设备
						
						usb_get_device_descriptor(udev, 8); // 获取设备描述符
						retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
						
						usb_new_device(udev)   
							err = usb_get_configuration(udev); // 把所有的描述符都读出来,并解析
							usb_parse_configuration
							
							device_add  // 把device放入usb_bus_type的dev链表, 
							            // 从usb_bus_type的driver链表里取出usb_driver,
							            // 把usb_interface和usb_driver的id_table比较
							            // 如果能匹配,调用usb_driver的probe

4.3 USB接口驱动和USB接口设备的匹配

USB设备插入后根据获取到的设备描述符所创建的USB 接口设备和开发的USB接口驱动匹配:
对于设备:
将获取到的USB设备描述符信息保存在其id_table中。
对于驱动:
驱动的id_table中存放,期望该驱动适用的USB设备。

// linux-2.6.22.6/drivers/hid/usbhid/usbmouse.c

static struct usb_device_id usb_mouse_id_table [] = {  
    /*
    匹配 HID 设备
    USB 设备中有一大类就是 HID 设备,即 Human Interface Devices,人机接口设备。
    这类设备包括鼠标、键盘等,主要用于人与计算机进行交互。 
    它是 USB 协议最早支持的一种设备类。 
    HID 设备可以作为低速、全速、高速设备用。
    由于 HID 设备要求用户输入能得到及时响应,故其传输方式通常采用中断方式。
    */
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	{ }	/* Terminating entry */
};

匹配成功后调用该驱动的probe函数,具体的过程和前面分析的差不多。接下来就是在probe函数中,和USB总线驱动程序建立联系,以达到操作USB 鼠标设备的目的。

4.4 创建数据传输管道

根据数据传输类型,有几个接口可供调用

/* Create various pipes... */
// 控制传输
#define usb_sndctrlpipe(dev,endpoint)	\
	((PIPE_CONTROL << 30) | __create_pipe(dev,endpoint))
#define usb_rcvctrlpipe(dev,endpoint)	\
	((PIPE_CONTROL << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN)
// 实时传输
#define usb_sndisocpipe(dev,endpoint)	\
	((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev,endpoint))
#define usb_rcvisocpipe(dev,endpoint)	\
	((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN)
// 批量传输
#define usb_sndbulkpipe(dev,endpoint)	\
	((PIPE_BULK << 30) | __create_pipe(dev,endpoint))
#define usb_rcvbulkpipe(dev,endpoint)	\
	((PIPE_BULK << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN)
// 中断传输
#define usb_sndintpipe(dev,endpoint)	\
	((PIPE_INTERRUPT << 30) | __create_pipe(dev,endpoint))
#define usb_rcvintpipe(dev,endpoint)	\
	((PIPE_INTERRUPT << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN)

对于USB 鼠标设备,使用中断传输方式

// linux-2.6.22.6/drivers/hid/usbhid/usbmouse.c

struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
struct usb_mouse *mouse;
struct input_dev *input_dev;
int pipe, maxp;
int error = -ENOMEM;

interface = intf->cur_altsetting;

if (interface->desc.bNumEndpoints != 1)
    return -ENODEV;

endpoint = &interface->endpoint[0].desc;
if (!usb_endpoint_is_int_in(endpoint))
    return -ENODEV;

// 端点是USB设备数据传输对象
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));

4.5 分配urb

urb(USB Request Block)是Linux内核中USB驱动实现上的一个数据结构,用于组织每一次的USB设备驱动的数据传输请求。

mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
if (!mouse->irq)
    goto fail2;

4.6 urb数据结构初始化

根据传输类型,有几个接口可供调用。

// 控制
static inline void usb_fill_control_urb(struct urb *urb,
					struct usb_device *dev,
					unsigned int pipe,
					unsigned char *setup_packet,
					void *transfer_buffer,
					int buffer_length,
					usb_complete_t complete_fn,
					void *context)
// 中断
static inline void usb_fill_int_urb(struct urb *urb,
				    struct usb_device *dev,
				    unsigned int pipe,
				    void *transfer_buffer,
				    int buffer_length,
				    usb_complete_t complete_fn,
				    void *context,
				    int interval)
// 批量

static inline void usb_fill_bulk_urb(struct urb *urb,
				     struct usb_device *dev,
				     unsigned int pipe,
				     void *transfer_buffer,
				     int buffer_length,
				     usb_complete_t complete_fn,
				     void *context)
    
// 实时   
// 实时urb 没有和中断、控制、批量urb 类似的初始化函数,因此它们在提交到USB核心之前,需要在驱动程序中手动的初始化    

对于USB鼠标设备,采用中断传输方式

usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,
			 (maxp > 8 ? 8 : maxp),
			 usb_mouse_irq, mouse, endpoint->bInterval);
mouse->irq->transfer_dma = mouse->data_dma;
mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

4.7 提交USB请求块

调用usb_submit_urb接口以获取USB设备数据。

// linux-2.6.22.6/drivers/hid/usbhid/usbmouse.c

static int usb_mouse_open(struct input_dev *dev)
{
	struct usb_mouse *mouse = input_get_drvdata(dev);

	mouse->irq->dev = mouse->usbdev;
	if (usb_submit_urb(mouse->irq, GFP_KERNEL))
		return -EIO;

	return 0;
}

该USB鼠标设备驱动还涉及输入子系统的内容,之前已经分析过,不在此再次记录。
4.8 总结

USB驱动开发,针对某一个USB设备的某个功能(接口)构建的驱动程序。USB驱动并不直接和USB设备进行数据交互,而是通过USB总线驱动程序(USB Core和USB HCD)来操作USB设备的。一般构建USB设备驱动的流程为:

根据期望适用的USB设备信息构建一个id_table。
根据需要的数据传输类型,调用相应的接口创建数据传输管道。
分配一个urb(USB请求块)。
根据需要的数据传输类型,调用相应的接口进行urb数据结构初始化。
提交urb

五,实现一个USB设备驱动程序

static struct input_dev *uk_dev;
static char *usb_buf;
static dma_addr_t usb_buf_phys;
static int len;
static struct urb *uk_urb;

static struct usb_device_id usbmouse_as_key_id_table [] = {
	// 匹配HID,鼠标设备
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	//{USB_DEVICE(0x1234,0x5678)},
	{ }	/* Terminating entry */
};

static void usbmouse_as_key_irq(struct urb *urb)
{
	static unsigned char pre_val;

	/* USB鼠标数据含义
	 * data[0]: bit0-左键, 1-按下, 0-松开
	 *          bit1-右键, 1-按下, 0-松开
	 *          bit2-中键, 1-按下, 0-松开 
	 *
     */

	if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))
	{
		/* 左键发生了变化 */
		input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
		input_sync(uk_dev);
	}

	if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
	{
		/* 右键发生了变化 */
		input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
		input_sync(uk_dev);
	}

	if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
	{
		/* 中键发生了变化 */
		input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
		input_sync(uk_dev);
	}
	
	pre_val = usb_buf[0];

	/* 重新提交urb */
	usb_submit_urb(uk_urb, GFP_KERNEL);
}

static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	struct usb_device *dev = interface_to_usbdev(intf);
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
	int pipe;
	
	interface = intf->cur_altsetting;
	endpoint = &interface->endpoint[0].desc;

	/* a. 分配一个input_dev */
	uk_dev = input_allocate_device();
	
	/* b. 设置 */
	/* b.1 能产生哪类事件 */
	set_bit(EV_KEY, uk_dev->evbit);
	set_bit(EV_REP, uk_dev->evbit);
	
	/* b.2 能产生哪些事件 */
	set_bit(KEY_L, uk_dev->keybit);
	set_bit(KEY_S, uk_dev->keybit);
	set_bit(KEY_ENTER, uk_dev->keybit);
	
	/* c. 注册 */
	input_register_device(uk_dev);
	
	/* d. 硬件相关操作 */
	/* 数据传输3要素: 源,目的,长度 */
	/* 源: USB设备的某个端点 */

	// 创建数据传输管道
	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

	/* 长度: */
	len = endpoint->wMaxPacketSize;

	/* 目的: */
	usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);

	/* 使用"3要素" */
	/* 分配usb request block */
	uk_urb = usb_alloc_urb(0, GFP_KERNEL);
	/* 使用"3要素设置urb" */
	// 初始化urb数据结构
	usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
	uk_urb->transfer_dma = usb_buf_phys;
	uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	/* 使用URB */
	// 提交urb,以读取usb设备数据。
	usb_submit_urb(uk_urb, GFP_KERNEL);
	
	return 0;
}

static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
	struct usb_device *dev = interface_to_usbdev(intf);

	//printk("disconnect usbmouse!\n");
	usb_kill_urb(uk_urb);
	usb_free_urb(uk_urb);

	usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
	input_unregister_device(uk_dev);
	input_free_device(uk_dev);
}

/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
	.name		= "usbmouse_as_key_",
	.probe		= usbmouse_as_key_probe,
	.disconnect	= usbmouse_as_key_disconnect,
	.id_table	= usbmouse_as_key_id_table,
};


static int usbmouse_as_key_init(void)
{
	/* 2. 注册 */
	usb_register(&usbmouse_as_key_driver);
	return 0;
}

static void usbmouse_as_key_exit(void)
{
	usb_deregister(&usbmouse_as_key_driver);	
}

module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);

MODULE_LICENSE("GPL");
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐