Linux之USB子系统(一):usb总线设备模型

USB子系统

USB体系介绍

​USB( Universal Serial Bus )是一种支持热拔插的高速串行传输总线,其设计目标是替代串行、并行等各种低速总线,以一种单一类型的总线连接各种不同的设备。当前,USB已几乎可以支持所有连接到PC的设备,并从最初的USB1.0、USB1.1、USB2.0发展到USB3.0,USB3.1和USB3.2,USB2.0的高速模式(High-Speed)最高已经到了480Mbps,USB3.0的Super-Speed模式达到5Gbps,最新的USB3.2更是可以达到20Gbps。USB被设计成为向下兼容的模式,当有全速(USB1.1)或者低速(USB1.0)连接到高速(USB2.0)主机时,主机依然可以支持,不过速度只能是当前设备的USB版本的速度。一条 USB 总线上,可达到的最高传输速度等级由该总线上最慢的设备决定。

​USB 体系包括USB主机控制器、USB设备以及USB连接三个部分,并采用分层的拓扑来连接所有USB设备,如下图所示:
在这里插入图片描述

  • USB主机控制器:控制所有的USB设备的通信。通常,计算机的CPU并不直接和USB设备打交道,而是和控制器打交道,CPU通过控制器发送指令给设备,接受数据也是通过控制器实现。
  • Hub:计算机并不只有一个USB接口,允许同一时刻使用多个USB设备,这些接口实际上就是所谓的Hub口。通常一个USB控制器会集成一个Hub,这个Hub被称作Root Hub,其他的Hub可以连接到它这里,然后延伸出去,外界别的设备。当然也可以不用Hub,USB设备直接接到Root Hub上。
  • USB设备:设备包括USB功能设备和USB Hub,一个主机控制器最多可以同时支持128个地址,地址0作为默认地址,只有在设备枚举期间使用,而不能分配给任何一个设备,因此一个USB Host最多可以同时支持127个地址,如果一个设备占用一个地址,那么最多可以支持127个设备。
  • 复合设备(Compound Device):所谓的复合设备其实就是把多个功能设备通过内置的USB Hub组合而成的设备,比如带录音话筒的USB摄像头等。
  • USB连接:指连接USB 设备和主机(或Hub)的四线电缆。电缆中包括VBUS(电源线)、GND(地线)和D+、D-两根信号线。主机对连接的USB设备提供电源供其使用,而每个USB设备也能够有自己的电源,如下图所示:

在这里插入图片描述

USB协议简述

​USB总线是一种轮询式的总线,采用轮询的广播机制传输数据,协议规定所有的传输都由主机发起,由主机控制初始化所有的数据传输,并且任何时刻整个USB体系内仅允许一个数据包的传输。USB通信最基本的形式是通过USB设备中的一个叫Endpoint(端点)的东西,而主机和端点之间的数据传输是通过Pipe(管道)。

​一个USB逻辑设备就是一系列端点的集合,它与主机之间的通信发生在主机上的一个缓冲区和设备上的一个端点之间,通过管道来传输数据。也就是说,管道的一端是主机上的一个缓冲区,一端是设备上的端点。
在这里插入图片描述

  • 数据采用“令牌包-数据包-握手包”传输,在令牌包中指定数据包去向或者来源的设备地址和端点,从而保证只有一个设备对广播的数据包/令牌包做出响应。数据包是USB 总线上数据传输的最小单位,包括 SYNC、数据及 EOP 三个部分。其中数据的格式针对不同的包有不同的格式,但都以 8 位的 PID 开始。PID 指定了数据包的类型(共 16 种)。令牌包即指 PID 为 IN/OUT/SETUP 的包。握手包表示了传输数据的成功与否。

  • EP:端点是USB设备中的可以进行数据收发的最小单元,支持单向或者双向的数据传输。**端点具有方向且确定,要么是in、要么是out,端点0除外,协议中规定,所有的USB设备必须具有端点0,既是in端点也作为out端点,usb系统中利用端点0来实现控制设备。**设备支持端点的数量是有限制的,除默认端点外低速设备最多支持 2 组端点(2 个输入,2 个输出),高速和全速设备最多支持 15 组端点。

  • 接口:usb逻辑设备,usb端点被捆绑为接口,一个接口代表一个基本功能。有的设备具有多个接口,像usb扬声器就包括一个键盘接口和一个音频流接口。一个接口要对应一个驱动程序,usb扬声器在Linux里就需要两个不同的驱动程序。

  • Pipe:主机和设备之间数据传输的模型,共有两种类型的管道,无格式的流管道(Stream Pipe)和有格式的信息管道(Message Pipe)。任何USB设备一旦上电就存在一个信息管道,即默认的控制管道,连接主机和默认端点,USB主机通过该管道来获取设备的描述、配置、状态,并对设备进行配置。

  • 传输类型:USB体系定义了四种类型的传输,如下:

    • 控制传输:主要用于在设备连接时对设备进行枚举以及其他因设备而已的特定操作。
    • 中断传输:用于对延迟要求严格、小量数据的可靠传输,如键盘、游戏手柄等。
    • 批量传输:用于对延迟要求宽松,大量数据的可靠传输,如U盘等 。
    • 同步传输:用于对可靠性要求不高的实时数据传输,如摄像头、 USB 音响等。

    注:中断传输并不意味在传输过程中,设备会先中断HOST,继而通知 HOST 启动传输。中断传输也是HOST发起的传输,采用轮询的方式询问设备是否有数据发送,若有则进行数据传输。

  • 描述符:usb描述符分为设备描述符、配置描述符、接口描述符和端点描述符。USB协议规定每个usb接口的设备必须支持这四种描述符, 这些描述符存放在usb设备的EEPROM中,在usb设备枚举时,主机控制器会获取设备上的这些描述符,以给这个设备安装对应的驱动。

    四种描述符的关系:一个USB设备只有一个设备描述符。设备描述符里决定了该设备有多少种配置,每种配置都有一个配置描述符;而在每个配置描述符中又定义了该配置有多少个接口,每个接口都有一个接口描述符;在接口描述符里又定义了该接口有多少个端点,每个端点都有一个端点描述符;端点描述符定义了端点的大小、类型等。
    在这里插入图片描述

Q&A

  • 现象:把一个未知的usb设备接到PC,如USB转串口线

    1. 设备管理器下端口中会弹出一个黄色感叹号的”cp2102 usb to uart bridge controller“。
    2. 跳出一个对话框,提示你安装驱动程序或者自动进行驱动安装。
  • Q1:既然还没有"驱动程序",为何能知道是usb转串口设备呢?

    A1:windows里已经有了USB的总线驱动程序,接入USB设备后,"总线驱动程序"通过EP0获取设备描述符后,知道你是一个usb to uart设备,提示你安装的是”设备驱动程序“。

    USB总线驱动程序负责识别USB设备识别,给USB设备找到对应的驱动程序。

  • Q2:USB设备种类非常多,为什么一接入电脑,就能识别出来?

    A2:USB主机控制器和USB设备都遵循USB协议,USB总线驱动程序会发出某些命令想获取设备信息(描述符),USB设备必须返回"描述符"给主机。

  • Q3:PC机上接有非常多的USB设备,怎么分辨它们?

    A3:每一个USB设备接入主机时,USB总线驱动程序都会给它分配一个编号,接在USB总线上的每一个USB设备都有自己的编号(地址),主机想访问某个USB设备时,发出的命令都含有对应的编号(地址)。

  • Q4:USB设备刚接入PC时,还没有编号,那么PC怎么把"分配的编号"告诉它?

    A4:新接入的USB设备的默认编号是0,在未分配新编号前,主机使用0编号和它通信。

  • Q5:为什么一接入USB设备,PC机就能发现它?

    A5:主机的USB口内部,D-和D+接有15K的下拉电阻,未接USB设备时为低电平。USB设备的USB口内部,D-或D+接有1.5K的上拉电阻。设备一接入主机,就会把主机USB口的D-或D+拉高,从硬件的角度通知主机有新设备接入。

USB驱动程序框架

在这里插入图片描述

  • USB主机控制器类型:

    • OHCI: 是支持USB1.1的标准, 相较UHCI,OHCI的硬件复杂,硬件做的事情更多,所以实现对应的软件驱动的任务就相对较简单。主要用于非x86的USB,如扩展卡、嵌入式开发板的USB主控。
    • UHCI: 是Intel主导的对USB1.0、1.1的接口标准,与OHCI不兼容。UHCI的软件驱动的任务重,需要做得比较复杂,但可以使用较便宜、较简单的硬件的USB控制器。
    • EHCI: 是Intel主导的USB2.0的接口标准。EHCI仅提供USB2.0的高速功能,而依靠UHCI或OHCI来提供对全速(full-speed)或低速(low-speed)设备的支持。
    • XHCI:最新的USB3.0接口标准, 在速度、节能、虚拟化等方面都比前面3中有了较大的提高,支持所有种类速度的USB设备,xHCI的目的是为了替换OHCI/UHCI/EHCI。
  • USB总线驱动程序的作用:

    • 枚举USB设备:获取设备描述、地址分配、获取配置描述、设备配置。

      描述符的信息见include\linux\usb\ch9.h,或参照USB SPEC的9.5章节

    • 匹配设备驱动

    • 提供USB读写函数(不知道数据含义)

  • USB设备驱动程序:具体功能接口的驱动程序,知道数据含义,实现功能

USB总线驱动程序

usb总线设备驱动模型

​USB子系统在内核中是Linux平台总线设备驱动模型,总入口在linux-4.19.148\drivers\usb\core\usb.c中,内核初始化do_initcalls会调用到subsys_initcall,初始化usb子系统。
在这里插入图片描述

usb_init

模型中的总线落实在USB子系统里就是usb_bus_type,它在usb_init中通过bus_register进行注册。usb_init中当然还有sysfs、hub的初始化,这个放到后面另开章节在讲!

/* linux-4.19.148\drivers\usb\core\usb.c */
static int __init usb_init(void)
{
    int retval;
    //注册总线
	retval = bus_register(&usb_bus_type);
	//...
	//注册通用驱动
	retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
	if (!retval)
		goto out;
	//...
out:
	return retval;
}

subsys_initcall(usb_init);

/* linux-4.19.148\drivers\usb\core\driver.c */
struct bus_type usb_bus_type = {
	.name =		"usb",
	.match =	usb_device_match,
	.uevent =	usb_uevent,
	.need_parent_lock =	true,
};

/* linux-4.19.148\drivers\usb\core\generic.c */
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,
};

usb_device_match

bus的name是usb,设备和驱动之间的匹配函数是usb_device_match,match函数中的两个参数对应的就是总线两条链表里的设备和驱动。总线上有新设备或新的驱动添加时,都会调用到match函数,如果指定的驱动程序能够处理指定的设备,也就匹配成功,函数返回1。usb_device_match中有两个分支,一个是给USB设备走,一个给USB接口走。

​设备可以有多个接口,每个接口代表一个功能,每个接口对应着一个驱动。Linux设备模型中的device落实在USB子系统中,成了两个结构:一个是struct usb_device,一个是usb_interface。一个USB键盘,上面带有一个扬声器,因此有两个接口,那肯定得要有两个驱动程序,一个是键盘驱动程序,一个是音频驱动程序。

/* linux-4.19.148\drivers\usb\core\driver.c */
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 if (is_usb_interface(dev)) {
		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;
}

usb_register_device_driver

注册usb设备驱动。

usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
{
    /* 表示该driver是usb_device_driver */
	usb_generic_driver->drvwrap.for_devices = 1;
	usb_generic_driver->drvwrap.driver.name = usb_generic_driver->name;
    /* 绑定bus为usb_bus_type */
	usb_generic_driver->drvwrap.driver.bus = &usb_bus_type;
    /* probe函数为usb_probe_device */
	usb_generic_driver->drvwrap.driver.probe = usb_probe_device;
	usb_generic_driver->drvwrap.driver.owner = owner;
	
    /* 注册平台驱动 */
	retval = driver_register(&usb_generic_driver->drvwrap.driver);
	return retval;
}

usb设备枚举

usb_init在内核启动后已将完成了usb子系统的初始化,若不接任何usb设备,usb子系统的使命就完成了。只有在接入设备后,usb子系统才会进入到设备枚举的流程。

USB2.0 SPEC中介绍到,当设备连接到主机时,按照以下顺序进行枚举

  1. 连接了设备的 HUB 在 HOST 查询其状态改变端点时返回对应的bitmap,告知 HOST 某个 PORT 状态发生了改变。

  2. 主机向 HUB 查询该 PORT 的状态,得知有设备连接,并知道了该设备的基本特性。

  3. 主机等待(至少 100ms)设备上电稳定,然后向 HUB 发送请求,复位并使能该 PORT。

  4. HUB 执行 PORT 复位操作,复位完成后该 PORT 就使能了。现在设备进入到 defalut 状态,可以从 Vbus 获取不超过 100mA 的电流。主机可以通过 0 地址与其通讯。

  5. 主机通过 0 地址向该设备发送 get_device_descriptor 标准请求,获取设备的描述符。

  6. 主机再次向 HUB 发送请求,复位该 PORT。

  7. 主机通过标准请求 set_address 给设备分配地址。

  8. 主机通过新地址向设备发送 get_device_descriptor 标准请求,获取设备的描述符。

  9. 主机通过新地址向设备发送其他 get_configuration 请求,获取设备的配置描述符。

  10. 根据配置信息,主机选择合适配置,通过 set_configuration 请求对设备而进行配置。这时设备方可正常使用。

当把USB设备接到设备上,可以看到输出信息:

[root@dvrdvs ] # [   33.587882] usb 3-1: new low-speed USB device number 2 using xhci-hcd

拔掉USB设备,输出信息如下:

[root@dvrdvs ] # [   48.614777] usb 3-1: USB disconnect, device number 2

再次接上设备:

[root@dvrdvs ] # [ 3173.397887] usb 3-1: new low-speed USB device number 3 using xhci-hcd

接下来就根据打印信息分析usb总线驱动程序是如何枚举usb设备的

/* linux-4.19.148\drivers\usb\core\hub.c */
static int hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
		int retry_counter)
{
    //...
    if (udev->speed < USB_SPEED_SUPER)
		dev_info(&udev->dev,
				"%s %s USB device number %d using %s\n",
				(udev->config) ? "reset" : "new", speed,
				devnum, driver_name);
    //...
    if (udev->speed >= USB_SPEED_SUPER) {
        devnum = udev->devnum;
        dev_info(&udev->dev,
                 "%s SuperSpeed%s%s USB device number %d using %s\n",
                 (udev->config) ? "reset" : "new",
                 (udev->speed == USB_SPEED_SUPER_PLUS) ?
                 "Plus Gen 2" : " Gen 1",
                 (udev->rx_lanes == 2 && udev->tx_lanes == 2) ?
                 "x2" : "",
                 devnum, driver_name);
	}
    //...
}

void usb_disconnect(struct usb_device **pdev)
{
    //...
	usb_set_device_state(udev, USB_STATE_NOTATTACHED);
	dev_info(&udev->dev, "USB disconnect, device number %d\n",
			udev->devnum);
    //...
}

static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
		u16 portchange)
{
	struct usb_device *udev = port_dev->child;
	/* Disconnect any existing devices under this port */
	if (udev) {
		/* 若udev存在,说明是设备下线动作 */
		usb_disconnect(&port_dev->child);
	}
    /* ... */
    /* 设备上线动作 */
	/* reset (non-USB 3.0 devices) and get descriptor */
    usb_lock_port(port_dev);
    status = hub_port_init(hub, udev, port1, i);
    usb_unlock_port(port_dev);
    /* ... */
}
int usb_hub_init(void)
    hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);

hub_probe
    INIT_WORK(&hub->events, hub_event);

hub_irq(struct urb *urb)
    /* Something happened, let hub_wq figure it out */
	kick_hub_wq(hub);
		queue_work(hub_wq, &hub->events)
            hub_event
                port_event
                    hub_port_connect_change  /* port口状态改变 */
                        hub_port_connect
            				/* 分配一个usb_device */
                            udev = usb_alloc_dev(hdev, hdev->bus, port1);
							/* 给新设备分配编号 */
                            choose_devnum(udev); 
							/* port初始化 */
                            hub_port_init(hub, udev, port1, i)
                            /* 新设备初始化 */
                            usb_new_device(udev)                  

usb_alloc_dev

struct usb_device *usb_alloc_dev(struct usb_device *parent,
				 struct usb_bus *bus, unsigned port1)
    struct usb_device *dev;
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	device_initialize(&dev->dev);
	/* usb_device中的device的bus为usb_bus_type */
	dev->dev.bus = &usb_bus_type;
	/* usb_device中的device的bus为usb_device_type */
	dev->dev.type = &usb_device_type;
	//...

hub_port_init

port初始化

hub_port_init
	/* 4.PORT 复位操作 */
    hub_port_reset
   	/* 5. 发送get_device_descriptor标准请求,获取设备描述符 */
#define GET_DESCRIPTOR_BUFSIZE	64              
    usb_control_msg(udev, usb_rcvaddr0pipe(),
					USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
					USB_DT_DEVICE << 8, 0,
					buf, GET_DESCRIPTOR_BUFSIZE,
					initial_descriptor_timeout); 
	/* 6. 主机再次向 HUB 发送请求,复位该 PORT */
	hub_port_reset
    /* 7. 主机通过标准请求set_address给设备设置分配的地址 */
    hub_set_address(udev, devnum);
	/* 8. 主机通过新地址向设备发送get_device_descriptor标准请求,获取设备描述符 */
	retval = usb_get_device_descriptor(udev, 8);
	retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);

usb_new_device

新设备初始化

usb_new_device
    /* 继续设备枚举流程 */
	usb_enumerate_device(udev);
		/* 9. 主机通过新地址向设备发送get_configuration请求,获取设备的配置描述符 */
		usb_get_configuration(udev);
			/* 解析配置 */
			usb_parse_configuration
                /* 解析接口 */
               	usb_parse_interface
                	/* 解析端点 */
                	usb_parse_endpoint
	/* 向总线注册设备 */
	device_add(&udev->dev);

usb设备match

usb_new_devicedevice_add(&udev->dev),此时usb子系统bus总线上添加了一个usb_device,进入到总线设备驱动模型的match函数中

问:dev在device_add中添加到总线,drv在哪里注册?

答:usb_initusb_register_device_driver(&usb_generic_driver, THIS_MODULE)

static int usb_device_match(struct device *dev, struct device_driver *drv)
	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; 
    }


/* dev在usb_alloc_dev中指定了type为usb_device_type,所以走得是match函数中的usb设备分支 */
static inline int is_usb_device(const struct device *dev)
{
	return dev->type == &usb_device_type;
}

/* 此时总线上的dirver为usb_generic_driver->drvwrap.driver,其for_devices成员在usb_register_device_driver指定为1 */
static inline int is_usb_device_driver(struct device_driver *drv)
{
	return container_of(drv, struct usbdrv_wrap, driver)->
			for_devices;
}

dev和drv匹配,执行drv中的probe函数,usb_register_device_driver中指定probe函数为usb_probe_device,最终执行到usb_generic_driver的probe成员generic_probe

static int usb_probe_device(struct device *dev)
    struct usb_device_driver *udriver = to_usb_device_driver(dev->driver);
	struct usb_device *udev = to_usb_device(dev);
    error = udriver->probe(udev);
static int generic_probe(struct usb_device *udev)
    c = usb_choose_configuration(udev);
	/* 10.根据配置信息,主机选择合适配置,通过 set_configuration 请求对设备而进行配置 */
	usb_set_configuration(udev, c);
		/* 分配需要的usb接口 */
		struct usb_interface **new_interfaces = NULL;
		nintf = cp->desc.bNumInterfaces;
		new_interfaces = kmalloc_array(nintf, sizeof(*new_interfaces),
					       GFP_NOIO);
		for (i = 0; i < nintf; ++i)
            struct usb_interface *intf;
			/* 指定接口的设备device总线类型为usb_bus_type */
			intf->dev.bus = &usb_bus_type;
			/* 指定接口的设备device类型为usb_if_device_type */
			intf->dev.type = &usb_if_device_type;
		/* 设备配置 */
		ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
			      USB_REQ_SET_CONFIGURATION, 0, configuration, 0,
			      NULL, 0, USB_CTRL_SET_TIMEOUT);
		for (i = 0; i < nintf; ++i)
            /* 向总线注册设备 */
			device_add(&intf->dev);

usb接口match

generic_probedevice_add(&intf->dev)向总线注册了接口的设备,此时再次进入到总线设备驱动模型的match函数中

static int usb_device_match(struct device *dev, struct device_driver *drv)
	else if (is_usb_interface(dev)){
        struct usb_interface *intf;
		struct usb_driver *usb_drv;
		const struct usb_device_id *id;

		/* device drivers never match interfaces,若drv是is_usb_device_driver,返回0 */
		if (is_usb_device_driver(drv))
			return 0;

		intf = to_usb_interface(dev);
		usb_drv = to_usb_driver(drv);
		
        /* 匹配id_table后,match函数执行成功,继而执行drv的probe函数 */
		id = usb_match_id(intf, usb_drv->id_table);
		if (id)
			return 1;
    }

/* generic_probe中指定设备的type为usb_if_device_type,所以走得是match函数中的usb接口分支 */
static inline int is_usb_interface(const struct device *dev)
{
	return dev->type == &usb_if_device_type;
}

/* 上面有提到设备可以有多个接口,每个接口代表一个功能,每个接口对应着一个驱动,这边的usb_drv就是我们要编写的功能接口驱动,例如键盘驱动、鼠标驱动、音频驱动等 */
#define	to_usb_driver(d) container_of(d, struct usb_driver, drvwrap.driver)

接口驱动的注册,参考内核中的usb鼠标的驱动例子

/* linux-4.19.148\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,
};

/* 注册usb_driver */
module_usb_driver(usb_mouse_driver);

/* linux-4.19.148\include\linux\usb.h */
#define module_usb_driver(__usb_driver) \
	module_driver(__usb_driver, usb_register, \
		       usb_deregister)

#define usb_register(driver) \
	usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)

/* linux-4.19.148\drivers\usb\core\driver.c */
int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
			const char *mod_name)
{
    /* 表示该驱动不是usb_device_driver */
    new_driver->drvwrap.for_devices = 0;
	new_driver->drvwrap.driver.name = new_driver->name;
    /* 绑定bus为usb_bus_type */
	new_driver->drvwrap.driver.bus = &usb_bus_type;
    /* probe函数为usb_probe_interface */
	new_driver->drvwrap.driver.probe = usb_probe_interface;
    /* remove函数为usb_unbind_interface */
    new_driver->drvwrap.driver.remove = usb_unbind_interface;
	spin_lock_init(&new_driver->dynids.lock);
	INIT_LIST_HEAD(&new_driver->dynids.list);
	/* 注册平台驱动 */
	retval = driver_register(&new_driver->drvwrap.driver);
    return retval;
}

dev和drv匹配,执行drv中的probe函数,usb_register_driver中指定probe函数为usb_probe_interface,最终执行到usb_mouse_driver的probe成员usb_mouse_probe

static int usb_probe_interface(struct device *dev)
    	struct usb_driver *driver = to_usb_driver(dev->driver);
		struct usb_interface *intf = to_usb_interface(dev);
    	id = usb_match_dynamic_id(intf, driver);
		if (!id)
            id = usb_match_id(intf, driver->id_table);
    	error = driver->probe(intf, id);

USB设备驱动程序

​我们已经通过总线驱动程序获取到了usb设备的描述符信息,接下来就是在设备驱动程序中通过总线驱动的接口进行usb通信,实现usb鼠标或usb键盘等对应的设备功能。我们编写程序一般就是编写usb设备驱动程序。

​这边说的usb设备驱动程序其实指的是接口驱动程序,设备可以有多个接口,每个接口代表一个功能,每个接口对应着一个驱动。usb设备驱动程序如何写,大致流程如下:

  • 分配/设置usb_driver结构体
    • id_table,匹配device和driver
    • probe,设备接入时执行到该函数,进行usb通信(urb-usb请求块),实现对应的功能
    • disconnect,设备拔出时执行该函数
  • 注册
    • module_usb_driver

参考内核中的usb鼠标驱动程序(Linux-4.9.88\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,
};

module_usb_driver(usb_mouse_driver);

module_usb_driver

见上面usb接口match章节

/* linux-4.19.148\include\linux\usb.h */
#define module_usb_driver(__usb_driver) \
	module_driver(__usb_driver, usb_register, \
		       usb_deregister)

id_table

usb_device_match函数中会执行到usb_match_id,匹配条件就是设备驱动程序中的id_table。usbmouse驱动的匹配条件为:只要usb设备的接口描述符(设备枚举过程中已拿到描述符信息)中的类是USB_INTERFACE_CLASS_HID,子类是USB_INTERFACE_SUBCLASS_BOOT,协议是USB_INTERFACE_PROTOCOL_MOUSE则匹配成功。当然匹配条件也可以是其他信息,可参考Linux-4.9.88\include\linux\usb.h中的宏定义,如只想匹配某个设备,可以设置条件是设备描述符中的idVendor(厂商标识)和idProduct(产品标识),可以使用宏USB_DEVICE(vend, prod)

static const struct usb_device_id usb_mouse_id_table[] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	{ }	/* Terminating entry */
};

#define USB_INTERFACE_INFO(cl, sc, pr) \
	.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \
	.bInterfaceClass = (cl), \
	.bInterfaceSubClass = (sc), \
	.bInterfaceProtocol = (pr)

probe

设备和驱动匹配后,最终走到接口驱动的probe函数,在该函数内会进行urb请求,有关usb设备的数据处理在这里实现。接口驱动中往往会引入其他子系统,如usb鼠标设备驱动中一定会有input子系统的内容。

数据传输的3要素:源、目的和长度

  • 源:usb设备的某个端点ep,对应到传输就是管道Pipe,由端点创建。不同传输类型、方向的端点创建的管道也不一样。
/* linux-4.19.148\include\linux\usb.h */
#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)
  • 长度:数据包的最大长度,从设备描述符中获取
static inline __u16 usb_maxpacket(struct usb_device *udev, int pipe, int is_out)
  • 目的:申请数据内存,usb子系统一般都是用到了dma,所以申请的是一致性dma,当然也可以直接使用kmalloc出来的内存,这个在usb_alloc_coherent适配了。
void *usb_alloc_coherent(struct usb_device *dev, size_t size, gfp_t mem_flags, dma_addr_t *dma)
  • 数据传输,使用到usb请求块(urb)
/* 分配urb */
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
/* 初始化urb */
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)
/*
 * urb - 要初始化的urb指针
 * dev - usb_device,当前urb的usb设备指针
 * pipe - 当前urb传输的管道
 * transfer_buffer - 当前urb传输的数据起始地址
 * buffer_length - 当前urb传输的数据长度
 * complete_fn - 当前urb处理完后调用的回调函数,在回调函数里进行数据处理
 * context - 回调函数用到的私有数据内容
 * interval - 在中断传输中表示主机控制器轮询的时间间隔
 */
 
/* 不同传输类型会有不同的urb初始化函数 */
usb_fill_control_urb
usb_fill_bulk_urb
usb_fill_int_urb
/* 注:对于等时传输,urb里是可以指定多次传输的,需要对每次传输都进行初始化,这样就无法写出一个类似其他三种传输的通用初始化接口  */
    
/* 提交urb。发出一个异步的传输请求,完成后将调用回调函数complete_fn。 */    
int usb_submit_urb(struct urb *urb, gfp_t mem_flags)

具体来看看usbmouse.c中的probe是怎么做的:

static int usb_mouse_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;
    struct input_dev *input_dev;
	struct usb_mouse *mouse; /* usbmouse驱动内部数据结构 */
   	int pipe, maxp;
    int error = -ENOMEM;

    /* 获取该接口中当前使用的配置 */
  	interface = intf->cur_altsetting;
    /* 获取端点,注这边获得到的端点不含端点0 */
	endpoint = &interface->endpoint[0].desc;
	
    /* 创建管道,usb鼠标使用的是中断传输,对host来说方向是输入,所以使用usb_rcvintpipe,不同类型的管道创建函数还有usb_sndctrlpipe、 usb_rcvctrlpipe等,见linux-4.19.148\include\linux\usb.h*/
    pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
    /* 获取数据包的最大长度 */
	maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
    
    mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);
    /* 分配一个input_dev */
    input_dev = input_allocate_device();
	
    /* 申请数据内存,usb传输使用到一致性dma */
    mouse->data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &mouse->data_dma);
    
    /* 分配usb request block,usb请求块 */
    mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
    
    /* input子系统的内容,设置能够产生按键类事件、相对位移类事件 */
    mouse->dev = input_dev;
    input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
	input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
		BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
	input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
	input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |
		BIT_MASK(BTN_EXTRA);
	input_dev->relbit[0] |= BIT_MASK(REL_WHEEL);
    input_set_drvdata(input_dev, mouse);
    input_dev->open = usb_mouse_open;
	input_dev->close = usb_mouse_close;
    
    /* 初始化中断传输类型的urb */
    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;
    
    /* 注册input设备 */
    error = input_register_device(mouse->dev);
    
    return 0;
}

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

	mouse->irq->dev = mouse->usbdev;
    /* 提交urb,发出个异步的传输请求,完成后将调用到回调函数 */
	if (usb_submit_urb(mouse->irq, GFP_KERNEL))
		return -EIO;

	return 0;
}

static void usb_mouse_close(struct input_dev *dev)
{
	struct usb_mouse *mouse = input_get_drvdata(dev);
	/* 杀掉urb */
	usb_kill_urb(mouse->irq);
}

static void usb_mouse_irq(struct urb *urb)
{
	struct usb_mouse *mouse = urb->context;
	signed char *data = mouse->data;
	struct input_dev *dev = mouse->dev;

    /* 上报按键类事件 */
	input_report_key(dev, BTN_LEFT,   data[0] & 0x01);
	input_report_key(dev, BTN_RIGHT,  data[0] & 0x02);
	input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);
	input_report_key(dev, BTN_SIDE,   data[0] & 0x08);
	input_report_key(dev, BTN_EXTRA,  data[0] & 0x10);
	/* 上报相对位移类事件 */
	input_report_rel(dev, REL_X,     data[1]);
	input_report_rel(dev, REL_Y,     data[2]);
	input_report_rel(dev, REL_WHEEL, data[3]);

	input_sync(dev);
resubmit:
    /* 重新提交urb,继续让主机控制器进行查询 */
	status = usb_submit_urb (urb, GFP_ATOMIC);
}

disconnect

设备掉线后,会执行到linux-4.19.148\drivers\usb\core\hub.c中的usb_disconnect函数,函数中会通过device_del删除总线上的设备,此时总线驱动模型会调用到driver的remove函数,module_usb_driver调用到usb_register_driver,指定了remove函数为usb_unbind_interface,最终调用到__usb_driver的disconnect函数。

/* linux-4.19.148\include\linux\usb.h */
#define module_usb_driver(__usb_driver) \
	module_driver(__usb_driver, usb_register, \
		       usb_deregister)
		       
#define usb_register(driver) \
	usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)

/* linux-4.19.148\drivers\usb\core\driver.c */
int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
			const char *mod_name)
{
    new_driver->drvwrap.driver.remove = usb_unbind_interface;
}

static int usb_unbind_interface(struct device *dev)
{
	struct usb_driver *driver = to_usb_driver(dev->driver);
    struct usb_interface *intf = to_usb_interface(dev);
	driver->disconnect(intf); 
}

具体来看看usbmouse.c中的disconnect做了什么:

static void usb_mouse_disconnect(struct usb_interface *intf)
{
	struct usb_mouse *mouse = usb_get_intfdata (intf);

	usb_set_intfdata(intf, NULL);
	if (mouse) {
        /* 杀掉urb */
		usb_kill_urb(mouse->irq);
        /* 注销input_dev */
		input_unregister_device(mouse->dev);
        /* 释放urb */
		usb_free_urb(mouse->irq);
        /* 释放usb传输的内存 */
		usb_free_coherent(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma);
		/* 释放私有结构体 */
        kfree(mouse);
	}
}

版权声明:本文为m0_57705123原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。