Linux:主机USB设备驱动简析

文章目录

  • 1. 前言
  • 2. 分析背景
  • 3. USB 总线硬件拓扑
  • 4. USB 协议栈概览
    • 4.1 Linux USB 子系统概览
    • 4.2 USB外设(如U盘)固件基础
  • 5. Linux USB 子系统初始化
  • 6. Linux USB 主机控制器(HCD) 驱动
    • 6.1 USB 主机控制器驱动初始化
    • 6.2 USB 主机控制器设备对象注册和驱动加载
  • 7. Linux USB 设备驱动加载过程
    • 7.1 `HUB 类设备` 驱动加载过程
    • 7.2 `非 HUB 类设备` 驱动加载过程
      • 7.2.1 按 VID & PID 匹配驱动
      • 7.2.2 按 interface class 匹配驱动
      • 7.2.2 其它情形
  • 8. 参考资料

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 分析背景

本文基于 linux-4.14.132 内核代码进行分析。

3. USB 总线硬件拓扑

在这里插入图片描述
其中:

1. Host 就是 USB 主机控制器(HCD),就是常说的 OHCI,UHCI,EHCI,xHCI , 内核用数据结构 struct usb_hcd 来描述。
2. RootHub 是集成到 USB 主机控制器(HCD) Host 的根集线器,根集线器上的端口用于连接USB设备:Hub或Func。
3. Func 表示USB设备,如U盘等。

一个 USB 主机控制器(HCD) 上最多可连接 127 个设备(HUB或Func),每个设备在枚举完成后会分配1个地址,地址区间为1~127,特殊地址0用于在设备枚举期间和主机控制器通信。

4. USB 协议栈概览

4.1 Linux USB 子系统概览

         USB 主机(如带 Linux 系统的设备)                     USB Slave 设备
 ----------------------------------------         ------------------------------
|          USB设备驱动                   |       |                              |
|       	    ^                        |       |                              |
|               |                        |       |                              |
|               v                        |       |                              |
|            USB Core                    |       |                              |
|       	    ^                        |       |                              |
|               |                        |       |  如U盘、USB鼠标键盘 驱动固件   |
|               v                        |       |                              |
| USB主机控制器驱动(OHCI/UHCI/EHCI/xHCI)  |       |                              |
|       	    ^                        |       |                              |
|               |                        |       |                              |
|               v                        |       |                              |
|         USB主机控制器                   |       |                              |
 ----------------------------------------         ------------------------------
                ^                                                       ^
				|                                                       |
				|                         USB 总线                      |
                 -------------------------------------------------------

4.2 USB外设(如U盘)固件基础

一个 USB 外设,它包含一系列的描述符,这些描述符用于定义 USB 外设的功能特性和行为逻辑。描述符包含以下类型:

设备描述符:每个设备有且仅有1个设备描述符。它定义设备的 PID & VID,包含的配置描述符的个数信息。
配置描述符:设备支持的接口数等信息。
接口描述符:接口包含的端点数,支持的协议类别、子类别,协议类型等信息。
端点描述符:端点是USB通信的基本单位。端点描述符包含端点类别、缓冲大小、地址等信息。
......

由于 USB 协议栈的内容过于庞大,也不是本文的重点,本篇将不做展开,感兴趣的童鞋可以参考 USB 官网 相关资料。

5. Linux USB 子系统初始化

/* drivers/usb/core/usb.c */

static int __init usb_init(void)
{
	int retval;
	
	...
	
	/*
	 * USB 调试系统初始化:
	 * . 创建目录 /sys/kernel/debug/usb 
	 * . 创建文件 /sys/kernel/debug/usb/devices
	 */
	retval = usb_debugfs_init();
	if (retval)
		goto out;

	...
	retval = bus_register(&usb_bus_type); /* 注册 USB 总线类型 */
	if (retval)
		goto bus_register_failed;

	/* 
	 * USB 总线 notifier 注册:
	 * 在 usb device 或 interface 注册、注销时,增加、移除相关的 sysfs 目录树。
	 */
	retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);
	if (retval)
		goto bus_notifier_failed;
	...
	retval = usb_hub_init(); /* 注册 USB HUB 驱动 */
	if (retval)
		goto hub_init_failed;
	/* 注册 USB device 通用驱动 */
	retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
	if (!retval)
		goto out;
	
	...
bus_notifier_failed:
	bus_unregister(&usb_bus_type);
bus_register_failed:
	...
	usb_debugfs_cleanup()	
out:
	return retval;
}

subsys_initcall(usb_init);
/* drivers/usb/core/hub.c */

static const struct usb_device_id hub_id_table[] = {
    { .match_flags = USB_DEVICE_ID_MATCH_VENDOR
			| USB_DEVICE_ID_MATCH_INT_CLASS,
      .idVendor = USB_VENDOR_GENESYS_LOGIC,
      .bInterfaceClass = USB_CLASS_HUB,
      .driver_info = HUB_QUIRK_CHECK_PORT_AUTOSUSPEND},
    { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,
      .bDeviceClass = USB_CLASS_HUB},
    { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
      .bInterfaceClass = USB_CLASS_HUB},
    { }						/* Terminating entry */
};

MODULE_DEVICE_TABLE(usb, hub_id_table);

static struct usb_driver hub_driver = { /* USB HUB 驱动 */
	.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,
};

int usb_hub_init(void)
{
	/* 注册 USB HUB 驱动 */
	if (usb_register(&hub_driver) < 0) {
		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.
	 */
	hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);
	if (hub_wq)
		return 0;

	...
}

USB 设备通用驱动 usb_generic_driver 注册:

/* drivers/usb/core/driver.c */

int usb_register_device_driver(struct usb_device_driver *new_udriver,
		struct module *owner)
{
	int retval = 0;
	
	new_udriver->drvwrap.for_devices = 1; /* 标记为 device 级别的驱动 */
	new_udriver->drvwrap.driver.name = new_udriver->name;
	new_udriver->drvwrap.driver.bus = &usb_bus_type;
	new_udriver->drvwrap.driver.probe = usb_probe_device;
	new_udriver->drvwrap.driver.remove = usb_unbind_device;
	new_udriver->drvwrap.driver.owner = owner;

	retval = driver_register(&new_udriver->drvwrap.driver);
	...
	
	return retval;
}
/* include/linux/usb.h */

/* use a define to avoid include chaining to get THIS_MODULE & friends */
#define usb_register(driver) \
	usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)

/* drivers/usb/core/driver.c */
int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
			const char *mod_name)
{
	int retval = 0;

	...
	
	new_driver->drvwrap.for_devices = 0; /* 标记为 interface 级别驱动 */
	new_driver->drvwrap.driver.name = new_driver->name;
	new_driver->drvwrap.driver.bus = &usb_bus_type;
	new_driver->drvwrap.driver.probe = usb_probe_interface;
	new_driver->drvwrap.driver.remove = usb_unbind_interface;
	new_driver->drvwrap.driver.owner = owner;
	new_driver->drvwrap.driver.mod_name = mod_name;
	spin_lock_init(&new_driver->dynids.lock);
	INIT_LIST_HEAD(&new_driver->dynids.list);

	retval = driver_register(&new_driver->drvwrap.driver);
	...

	return retval;
}

上面的代码主要完成了以下3项工作:

1. USB 总线类型注册
2. USB HUB 驱动注册
3. USB 设备通用驱动注册

6. Linux USB 主机控制器(HCD) 驱动

我们以支持 USB 2.0 的 EHCI USB 主机控制器 驱动为例进行阐述,而其它 OHCI,UHCI,xHCI 的主机控制器驱动,读者可自行阅读相关代码进行分析。

6.1 USB 主机控制器驱动初始化

static int __init ehci_platform_init(void)
{
	/* ehci-platform: EHCI generic platform driver */
	pr_info("%s: " DRIVER_DESC "\n", hcd_name);

	/* 初始化 EHCI 主机控制器驱动 */
	ehci_init_driver(&ehci_platform_hc_driver, &platform_overrides);
	/* 注册 EHCI 主机控制器 platform 驱动 */
	return platform_driver_register(&ehci_platform_driver);
}
/* drivers/usb/host/ehci-hcd.c */

/* EHCI 主机控制器驱动 */
static const struct hc_driver ehci_hc_driver = {
	.description =		hcd_name,
	.product_desc =		"EHCI Host Controller",
	...

	/*
	 * generic hardware linkage
	 */
	.irq =			ehci_irq,
	.flags =		HCD_MEMORY | HCD_USB2 | HCD_BH,
	.hcd_priv_size =	sizeof(struct ehci_hcd),
	
	/*
	 * basic lifecycle operations
	 */
	.reset =		ehci_setup,
	.start =		ehci_run,
	.stop =			ehci_stop,
	.shutdown =		ehci_shutdown,

	/*
	 * managing i/o requests and associated device resources
	 */
	.urb_enqueue =		ehci_urb_enqueue,
	.urb_dequeue =		ehci_urb_dequeue,
	.endpoint_disable =	ehci_endpoint_disable,
	.endpoint_reset =	ehci_endpoint_reset,
	.clear_tt_buffer_complete =	ehci_clear_tt_buffer_complete,

	/*
	 * scheduling support
	 */
	.get_frame_number =	ehci_get_frame,

	/*
	 * root hub support
	 */
	.hub_status_data =	ehci_hub_status_data,
	.hub_control =		ehci_hub_control,
	.bus_suspend =		ehci_bus_suspend,
	.bus_resume =		ehci_bus_resume,
	.relinquish_port =	ehci_relinquish_port,
	.port_handed_over =	ehci_port_handed_over,

	/*
	 * device support
	 */
	.free_dev =		ehci_remove_device,
};

void ehci_init_driver(struct hc_driver *drv,
		const struct ehci_driver_overrides *over)
{
	/* Copy the generic table to drv and then apply the overrides */
	*drv = ehci_hc_driver;

	if (over) {
		drv->hcd_priv_size += over->extra_priv_size;
		if (over->reset)
			drv->reset = over->reset;
		if (over->port_power)
			drv->port_power = over->port_power;
	}
}

USB 主机控制器(HCD)驱动 用数据结构 struct hc_driver 抽象。

6.2 USB 主机控制器设备对象注册和驱动加载

看一下全志 H3 平台 EHCI 的 DTS 配置:

ehci0: usb@01c1a000 {
	compatible = "allwinner,sun8i-h3-ehci", "generic-ehci";
	reg = <0x01c1a000 0x100>;
	interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&ccu CLK_BUS_EHCI0>, <&ccu CLK_BUS_OHCI0>;
	resets = <&ccu RST_BUS_EHCI0>, <&ccu RST_BUS_OHCI0>;
	status = "okay";
};

在系统启动期间,会为该 EHCI 主机控制器 创建一个 platform_device 对象,然后在注册 EHCI 主机控制器的 platform_driver 驱动时,触发 EHCI 主机控制器 platform 设备驱动的加载:

/* drivers/usb/host/echi-platform.c */

static const struct of_device_id vt8500_ehci_ids[] = {
	...
	{ .compatible = "generic-ehci", },
	...
	{}
};
MODULE_DEVICE_TABLE(of, vt8500_ehci_ids);

static struct platform_driver ehci_platform_driver = {
	.id_table	= ehci_platform_table,
	.probe		= ehci_platform_probe,
	.remove		= ehci_platform_remove,
	.shutdown	= usb_hcd_platform_shutdown,
	.driver		= {
		.name	= "ehci-platform",
		.pm	= &ehci_platform_pm_ops,
		.of_match_table = vt8500_ehci_ids,
		.acpi_match_table = ACPI_PTR(ehci_acpi_match),
	}
};

/* 加载 ECHI 的 platform_driver 驱动 */
static int ehci_platform_probe(struct platform_device *dev)
{
	struct usb_hcd *hcd;
	...
	int err, irq, phy_num, clk = 0, rst;

	...
	
	/* 
	 * 解析中断配置: 
	 * interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
	 */
	irq = platform_get_irq(dev, 0);

	...
	/* 创建 USB ECHI 主机控制器(HCD)对象 */
	hcd = usb_create_hcd(&ehci_platform_hc_driver, &dev->dev,
			     dev_name(&dev->dev));
	...

	...
	/* 注册 USB ECHI 主机控制器对象 */
	err = usb_add_hcd(hcd, irq, IRQF_SHARED);

	...
	platform_set_drvdata(dev, hcd);

	return err;
}
/* drivers/usb/core/hcd.c */

struct usb_hcd *usb_create_hcd(const struct hc_driver *driver,
		struct device *dev, const char *bus_name)
{
	return __usb_create_hcd(driver, dev, dev, bus_name, NULL);
}

struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver,
		struct device *sysdev, struct device *dev, const char *bus_name,
		struct usb_hcd *primary_hcd)
{
	struct usb_hcd *hcd;

	hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL);
	...
	
	...
	hcd->self.controller = dev; /* EHCI 的 platform_device */
	...
	hcd->self.bus_name = bus_name;

	init_timer(&hcd->rh_timer);
	hcd->rh_timer.function = rh_timer_func;
	hcd->rh_timer.data = (unsigned long) hcd;
#ifdef CONFIG_PM
	INIT_WORK(&hcd->wakeup_work, hcd_resume_work);
#endif

	hcd->driver = driver; /* 设定 EHCI 主机控制器的驱动: ehci_platform_hc_driver */
	hcd->speed = driver->flags & HCD_MASK;
	hcd->product_desc = (driver->product_desc) ? driver->product_desc :
			"USB Host Controller";
	return hcd;
}

int usb_add_hcd(struct usb_hcd *hcd,
		unsigned int irqnum, unsigned long irqflags)
{
	int retval;
	struct usb_device *rhdev;

	...
	/* ehci-platform 1c1a000.usb: EHCI Host Controller */
	dev_info(hcd->self.controller, "%s\n", hcd->product_desc/*ehci_hc_driver.product_desc*/);

	...
	retval = hcd_buffer_create(hcd);
	...

	retval = usb_register_bus(&hcd->self);
	...
	
	...
	/* 创建 ECHI 主机控制器 ROOT HUB 设备对象 */
	rhdev = usb_alloc_dev(NULL, &hcd->self, 0);
	...
	hcd->self.root_hub = rhdev;
	...

	switch (hcd->speed) {
	...
	case HCD_USB2:
		rhdev->speed = USB_SPEED_HIGH;
		break;
	...
	}

	...

	if (hcd->driver->reset) {
		/* 配置 EHCI 主机控制器 */
		retval = hcd->driver->reset(hcd); /* ehci_setup() */
		...
	}

	/* initialize tasklets */
	init_giveback_urb_bh(&hcd->high_prio_bh);
	init_giveback_urb_bh(&hcd->low_prio_bh);

	...
	if (usb_hcd_is_primary_hcd(hcd) && irqnum) {
		/* 注册 ECHI 主机控制器中断处理接口 ehci_irq() */
		retval = usb_hcd_request_irqs(hcd, irqnum, irqflags);
		...
	}

	hcd->state = HC_STATE_RUNNING;
	/* 启动 EHCI 主机控制器 */
	retval = hcd->driver->start(hcd); /* ehci_run() */

	/* starting here, usbcore will pay attention to this root hub */
	/* ECHI 主机控制器 ROOT HUB 设备对象注册 和 驱动加载 */
	retval = register_root_hub(hcd);

	...
	
	return retval;
}

static int usb_register_bus(struct usb_bus *bus)
{
	...
	busnum = idr_alloc(&usb_bus_idr, bus, 1, USB_MAXBUS, GFP_KERNEL);
	bus->busnum = busnum;
	...

	usb_notify_add_bus(bus);

	/* ehci-platform 1c1a000.usb: new USB bus registered, assigned bus number 1 */
	dev_info (bus->controller, "new USB bus registered, assigned bus "
		  "number %d\n", bus->busnum);
	return 0;
}

/* 创建 USB 设备对象 (usb_device) */
struct usb_device *usb_alloc_dev(struct usb_device *parent,
				 struct usb_bus *bus, unsigned port1) // drivers/usb/core/usb.c
{
	struct usb_device *dev;

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	...

	device_initialize(&dev->dev);
	dev->dev.bus = &usb_bus_type;
	dev->dev.type = &usb_device_type;
	dev->dev.groups = usb_device_groups;

	...
	dev->state = USB_STATE_ATTACHED;
	...

	dev->portnum = port1;
	dev->bus = bus;
	dev->parent = parent;
	...

	return dev;
}

static int usb_hcd_request_irqs(struct usb_hcd *hcd,
		unsigned int irqnum, unsigned long irqflags)
{
	if (hcd->driver->irq) {
		...
		retval = request_irq(irqnum, &usb_hcd_irq, irqflags,
				hcd->irq_descr, hcd);
		...
		hcd->irq = irqnum;
		...
	} else {
		...
	}
	return 0;
}

来看 EHCI 主机控制器 ROOT HUB 设备对象注册和驱动加载的细节:

/* drivers/usb/core/hcd.c */

static int register_root_hub(struct usb_hcd *hcd)
{
	struct device *parent_dev = hcd->self.controller;
	struct usb_device *usb_dev = hcd->self.root_hub;
	const int devnum = 1;
	int retval;

	usb_dev->devnum = devnum;
	usb_dev->bus->devnum_next = devnum + 1;
	...
	usb_set_device_state(usb_dev, USB_STATE_ADDRESS);

	usb_dev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
	retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE);
	...

	retval = usb_new_device (usb_dev);
	...

	return retval;
}
/* drivers/usb/core/hub.c */

int usb_new_device(struct usb_device *udev)
{
	...
	err = usb_enumerate_device(udev);	/* Read descriptors */
	...

	/* Tell the world! */
	/*
	 * usb usb1: New USB device found, idVendor=1d6b, idProduct=0002
	 * usb usb1: New USB device strings: Mfr=3, Product=2, SerialNumber=1
	 * usb usb1: Product: EHCI Host Controller
	 * usb usb1: Manufacturer: Linux 4.14.111 ehci_hcd
	 * usb usb1: SerialNumber: 1c1a000.usb
	*/
	announce_device(udev);

	/* 
	 * 注册 ECHI 控制器的 ROOT HUB 设备到 driver core ,
	 * 这将触发 HUB 驱动 hub_driver 加载: 即触发 hub_probe() .
	 */
	err = device_add(&udev->dev);
	...

	(void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev);
	...
	return err;
}

/* ECHI 主机控制器的 ROOT HUB 驱动加载 */
static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	struct usb_hub *hub;
	
	...
	/* We found a hub */
	dev_info(&intf->dev, "USB hub found\n"); /* hub 1-0:1.0: USB hub found */

	/* 创建 USB HUB 对象 */
	hub = kzalloc(sizeof(*hub), GFP_KERNEL);
	...

	...
	hub->intfdev = &intf->dev;
	hub->hdev = hdev;
	INIT_DELAYED_WORK(&hub->leds, led_work);
	INIT_DELAYED_WORK(&hub->init_work, NULL);
	INIT_WORK(&hub->events, hub_event); /* 设置用来处理 HUB 上 USB 设备枚举过程的 work */
	...

	...
	if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) /* 配置 HUB */
		return 0;

	...
}

static int hub_configure(struct usb_hub *hub,
	struct usb_endpoint_descriptor *endpoint)
{
	hub->buffer = kmalloc(sizeof(*hub->buffer), GFP_KERNEL);
	...

	hub->status = kmalloc(sizeof(*hub->status), GFP_KERNEL);
	...

	hub->descriptor = kzalloc(sizeof(*hub->descriptor), GFP_KERNEL);
	...
	
	/* 
	 * 读取 HUB 设备 @hdev 的 【HUB 描述符】 到 @hub->descriptor , 
	 * 搞清楚 HUB 上有几个 USB 连接端口。
	 */ 
	ret = get_hub_descriptor(hdev, hub->descriptor);
	...

	maxchild = hub->descriptor->bNbrPorts;
	/* 
	 * 如: hub 1-0:1.0: 1 port detected
	 * 这日志表明,hub 1-0:1.0 上,物理上只有1个接口(1 port)
	 *
	 * hub 1-0:1.0: 6 ports detected
	 * hub 2-0:1.0: 2 ports detected
	 * hub 2-2:1.0: 7 ports detected
	 */
	dev_info(hub_dev, "%d port%s detected\n", maxchild,
			(maxchild == 1) ? "" : "s");

	/* 为 HUB 上的 USB 端口创建对象  */
	hub->ports = kzalloc(maxchild * sizeof(struct usb_port *), GFP_KERNEL);
	...

	...
	hub->urb = usb_alloc_urb(0, GFP_KERNEL); /* 分配用于 HUB 中断处理的 URB 对象 */

	/* 初始化用于 HUB 中断处理的 URB 对象: 中断 URB complete 处理回调设为 hub_irq() */
	usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
		hub, endpoint->bInterval);

	...
	for (i = 0; i < maxchild; i++) { /* 为 HUB 上的 usb 端口创建设备对象,并注册到 driver core */
		ret = usb_hub_create_port_device(hub, i + 1);
		...
	}
	hdev->maxchild = i;
	...

	hub_activate(hub, HUB_INIT);
	return 0;
}

static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
{
	...
	/* Scan all ports that need attention */
	kick_hub_wq(hub);
	...
}

static void kick_hub_wq(struct usb_hub *hub)
{
	if (hub->disconnected || work_pending(&hub->events))
		return;

	...
	if (queue_work(hub_wq, &hub->events)) /* 触发 hub_event() */
		return;
	...
}

从上面的代码看到,USB 主机控制器 用数据结构 struct usb_hcd 描述;USB HUB 设备 用数据结构 struct usb_hub 描述;USB HUB 上的端口 用数据结构 struct usb_port 描述;USB 端口上挂接的设备 用数据结构 struct usb_device 描述。

7. Linux USB 设备驱动加载过程

当设备插入到 USB 端口时,会产生中断信号,然后内核调用 HUB 的中断处理接口 hub_irq() 来处理中断信号:

/* drivers/usb/core/hub.c */

/* completion function, fires on port status changes and various faults */
static void hub_irq(struct urb *urb)
{
	hub->nerrors = 0;

	/* Something happened, let hub_wq figure it out */
	kick_hub_wq(hub); /* 调度 work: 触发 hub_event() 进一步处理设备插入事件 */
	
	...
}

static void hub_event(struct work_struct *work)
{
	struct usb_device *hdev;
	struct usb_interface *intf;
	struct usb_hub *hub;
	struct device *hub_dev;
	u16 hubstatus;
	u16 hubchange;
	int i, ret;

	hub = container_of(work, struct usb_hub, events);
	hdev = hub->hdev;
	hub_dev = hub->intfdev;
	intf = to_usb_interface(hub_dev);
	
	...

	/* deal with port status changes */
	for (i = 1; i <= hdev->maxchild; i++) {
		struct usb_port *port_dev = hub->ports[i - 1]; /* HUB 的第 @i 个端口 */

		if (test_bit(i, hub->event_bits)
				|| test_bit(i, hub->change_bits)/* 端口上发生状态变化: 设备插入、拔出 */
				|| test_bit(i, hub->wakeup_bits)) {
			...
			port_event(hub, i); /* 处理端口上的事件 */
			...
		}
	}
	
	...
}

static void port_event(struct usb_hub *hub, int port1)
		__must_hold(&port_dev->status_lock)
{
	int connect_change;

	connect_change = test_bit(port1, hub->change_bits);
	clear_bit(port1, hub->event_bits);
	clear_bit(port1, hub->wakeup_bits);

	if (hub_port_status(hub, port1, &portstatus, &portchange) < 0)
		return;
	
	...

	if (connect_change)
		hub_port_connect_change(hub, port1, portstatus, portchange);
}

static void hub_port_connect_change(struct usb_hub *hub, int port1,
					u16 portstatus, u16 portchange)
		__must_hold(&port_dev->status_lock
{
	...
	clear_bit(port1, hub->change_bits);

	...

	usb_unlock_port(port_dev);
	hub_port_connect(hub, port1, portstatus, portchange);
	usb_lock_port(port_dev);
}

static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
		u16 portchange)
{
	...
	status = 0;
	for (i = 0; i < SET_CONFIG_TRIES; i++) {
		udev = usb_alloc_dev(hdev, hdev->bus, port1);
		...

		/* 复位设备, 为设备分配地址, 获取设备描述符(usb_device_descriptor) */
		...
		status = hub_port_init(hub, udev, port1, i); 
		...

		/* Run it through the hoops (find a driver, etc) */
		if (!status) {
			...
			status = usb_new_device(udev); /* 匹配设备到驱动,然后加载设备驱动 */
			...
		}
	}
}

int usb_new_device(struct usb_device *udev)
{
	...
	/* 读取设备 配置描述符、字符串描述符、OTG 描述符、 quirk 配置 */
	err = usb_enumerate_device(udev);	/* Read descriptors */
	...

	...
	/* Tell the world! */
	announce_device(udev); /* usb usb2: New USB device found, idVendor=1d6b, idProduct=0002 */

	...
	err = device_add(&udev->dev); /* 添加设备到 driver core,将触发 USB 设备和驱动配对 */
	...

	...
	return err;
}

USB 设备驱动的匹配和加载概要流程如下:

device_add()
	bus_add_device(dev)
	bus_probe_device(dev)
		device_initial_probe(dev)
			__device_attach(dev, true)
				bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver)
					driver_match_device(drv, dev) /* 配对 USB 设备 和 驱动 */
					driver_probe_device(drv, dev) /* 加载 USB 设备驱动 */

这里我们假定 USB 设备驱动USB 设备 先注册。对于 USB 设备 比其 设备驱动 先注册的情形,匹配和加载过程是类似的,这里不再赘述。
USB 设备驱动匹配加载过程,对于不同类别的设备,过程上存在着一定差异,下面取几个典型的类别设备驱动匹配加载过程进行分析。

7.1 HUB 类设备 驱动加载过程

前面在讲述 EHCI 主机控制器集成的 ROOT HUB 设备驱动加载过程中,没有描述 HUB 驱动和设备的匹配细节,我们将在本小节展开。对于非 ROOT HUBROOT HUB 设备的驱动加载过程,它们同样是通过 usb_new_device() 接口来加载驱动:

usb_new_device(udev)
	device_add(&udev->dev)
		...
		driver_match_device(drv, dev) /* 配对 USB 设备 和 驱动 */
		driver_probe_device(drv, dev) /* 加载 USB 设备驱动 */

下面就来看看 HUB 类设备驱动加载的细节 :

/* drivers/base/base.h */

static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)
{
	/* 此处 drv->bus->match = usb_bus_type.match = usb_device_match() */
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
/* drivers/usb/core/driver.c */

static int usb_device_match(struct device *dev, struct device_driver *drv)
{
	/* devices and interfaces are handled separately */
	/* devices and interfaces are handled separately */
	if (is_usb_device(dev)) {

		/* interface drivers never match devices */
		if (!is_usb_device_driver(drv)) /* 不是 device 级别的驱动,表示不匹配 */
			return 0;

		/* TODO: Add real matching code */
		return 1; /* 新插入的设备总是走这里: 先匹配到 usb_generic_driver */

	} else if (is_usb_interface(dev)) {
		/* interface 设备驱动匹配,在后面详述 */
		...
	}

	return 0;
}

所有新发现的 USB 设备(HUB 也是 USB 设备),首先都会先匹配到 USB 设备通用驱动 usb_generic_driver ,然后进入该驱动的 usb_probe_device() 接口:

driver_probe_device(drv, dev)
	really_probe(dev, drv)
		drv->probe(dev) = usb_probe_device()
/* drivers/usb/core/driver.c */

static int usb_probe_device(struct device *dev)
{
	int error = 0;

	...
	
	if (!error)
		error = udriver->probe(udev); /* generic_probe() */
	return error;
}
/* drivers/usb/core/generic.c */

static int generic_probe(struct usb_device *udev)
{
	int err, c;
	
	...
	if (udev->authorized == 0)
		...
	else {
		c = usb_choose_configuration(udev);
		if (c >= 0) {
			err = usb_set_configuration(udev, c);
			...
		}
	}
	...

	return 0;
}
/* drivers/usb/core/message.c */

int usb_set_configuration(struct usb_device *dev, int configuration)
{
	...
	/* Allocate memory for new interfaces before doing anything else,
	 * so that if we run out then nothing will have changed. */
	n = nintf = 0;
	if (cp) {
		/* 为 USB 设备 的 所有 interface 创建对象 */
		nintf = cp->desc.bNumInterfaces;
		new_interfaces = kmalloc(nintf * sizeof(*new_interfaces), GFP_NOIO);
		...

		for (; n < nintf; ++n) {
			new_interfaces[n] = kzalloc(sizeof(struct usb_interface), GFP_NOIO);
			...
		}

		...
	}

	...

	/* 初始化 USB 设备的 所有 interface 设备 */
	for (i = 0; i < nintf; ++i) {
		struct usb_interface_cache *intfc;
		struct usb_interface *intf;
		struct usb_host_interface *alt;

		cp->interface[i] = intf = new_interfaces[i];
		...

		alt = usb_altnum_to_altsetting(intf, 0);

		...
		intf->cur_altsetting = alt;
		usb_enable_interface(dev, intf, true);
		intf->dev.parent = &dev->dev;
		intf->dev.driver = NULL;
		intf->dev.bus = &usb_bus_type;
		intf->dev.type = &usb_if_device_type;
		...
		device_initialize(&intf->dev);
		...
	}
	kfree(new_interfaces);

	/* 发送 SET_CONFIGURATION 请求 */
	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
			      USB_REQ_SET_CONFIGURATION, 0, configuration, 0,
			      NULL, 0, USB_CTRL_SET_TIMEOUT);
	...
	dev->actconfig = cp;

	...
	usb_set_device_state(dev, USB_STATE_CONFIGURED);

	...

	for (i = 0; i < nintf; ++i) {
		struct usb_interface *intf = cp->interface[i];

		...
		device_enable_async_suspend(&intf->dev);
		/* 
		 * 添加 USB interface 设备到 driver core , 
		 * 触发 USB interface 设备驱动匹配流程 。
		 */
		ret = device_add(&intf->dev);
		...
		create_intf_ep_devs(intf);
	}

	usb_autosuspend_device(dev);
	return 0;
}
device_add(&udev->dev)
		...
		driver_match_device(drv, dev) /* 配对 USB 设备 和 驱动 */
		driver_probe_device(drv, dev) /* 加载 USB 设备驱动 */

static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)
{
	/* 此处 drv->bus->match = usb_bus_type.match = usb_device_match() */
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

static int usb_device_match(struct device *dev, struct device_driver *drv)
{
	/* devices and interfaces are handled separately */
	if (is_usb_device(dev)) {
		/* USB interface 设备驱动匹配不走这里 */
		...
	} else if (is_usb_interface(dev)) { /* USB interface 设备驱动匹配走这里 */
		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)) /* hub_driver 在这里成立 */
			return 0;

		intf = to_usb_interface(dev);
		usb_drv = to_usb_driver(drv); /* hub_driver */

		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 外设(如 U盘、USB 键鼠)读取到的 设备描述符interface 描述符 信息,匹配到 hub_driverusb_device_id 表后,将加载 HUB 类设备的驱动:

driver_probe_device(drv, dev) /* 加载 USB 设备驱动 */
	really_probe(dev, drv)
		drv->probe(dev) = usb_probe_interface()
/* drivers/usb/core/driver.c */

/* called from driver core with dev locked */
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);
	struct usb_device *udev = interface_to_usbdev(intf);
	const struct usb_device_id *id;
	int error = -ENODEV;
	...

	...

	error = driver->probe(intf, id); /* 进入 HUB 驱动入口: hub_probe() */
	...
	
	....
	return error;
}

到此,HUB 类设备(包括 ROOT HUB非 ROOT HUB)驱动的加载过程完成。

7.2 非 HUB 类设备 驱动加载过程

7.2.1 按 VID & PID 匹配驱动

如 RealTek 的 R8152 USB 网卡,看它的驱动定义:

/* drivers/net/usb/r8152.c */

#define REALTEK_USB_DEVICE(vend, prod)	\
	.match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
		       USB_DEVICE_ID_MATCH_INT_CLASS, \
	.idVendor = (vend), \
	.idProduct = (prod), \
	.bInterfaceClass = USB_CLASS_VENDOR_SPEC \
}, \
{ \
	.match_flags = USB_DEVICE_ID_MATCH_INT_INFO | \
		       USB_DEVICE_ID_MATCH_DEVICE, \
	.idVendor = (vend), \
	.idProduct = (prod), \
	.bInterfaceClass = USB_CLASS_COMM, \
	.bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, \
	.bInterfaceProtocol = USB_CDC_PROTO_NONE

/* table of devices that work with this driver */
static const struct usb_device_id rtl8152_table[] = {
	...
	{REALTEK_USB_DEVICE(VENDOR_ID_REALTEK, 0x8152)},
	...
	{}
};

MODULE_DEVICE_TABLE(usb, rtl8152_table);

static struct usb_driver rtl8152_driver = {
	.name =		MODULENAME,
	.id_table =	rtl8152_table,
	.probe =	rtl8152_probe,
	.disconnect =	rtl8152_disconnect,
	.suspend =	rtl8152_suspend,
	.resume =	rtl8152_resume,
	.reset_resume =	rtl8152_reset_resume,
	.pre_reset =	rtl8152_pre_reset,
	.post_reset =	rtl8152_post_reset,
	.supports_autosuspend = 1,
	.disable_hub_initiated_lpm = 1,
};

module_usb_driver(rtl8152_driver);

usb_device_match() 匹配过程中,读取到 R8152 网卡设备描述符的 VID & PID 信息,匹配到驱动的 rtl8152_driver 的 ID 匹配表 rtl8152_table[] ,则加载 R8152 驱动 rtl8152_driver ,进入 rtl8152_probe() 执行。

7.2.2 按 interface class 匹配驱动

还有的 USB 设备,它们驱动的匹配表不指定 VID & PID,而是指定 interface class 信息。像这种情形,驱动的匹配是通过读取设备的 interface 描述符,然后提取其中的 interface class 信息进行匹配。

/* drivers/usb/class/usblp.c */

static const struct usb_device_id usblp_ids[] = {
	{ USB_DEVICE_INFO(USB_CLASS_PRINTER, 1, 1) },
	...
	{ }						/* Terminating entry */
};

MODULE_DEVICE_TABLE(usb, usblp_ids);

static struct usb_driver usblp_driver = {
	.name =		"usblp",
	.probe =	usblp_probe,
	.disconnect =	usblp_disconnect,
	.suspend =	usblp_suspend,
	.resume =	usblp_resume,
	.id_table =	usblp_ids,
	.supports_autosuspend =	1,
};

module_usb_driver(usblp_driver);

这是一个 USB 打印机类别设备的通用驱动,当从打印机的 interface 描述符 提取到的 class 信息,与驱动匹配表的 class 信息 USB_CLASS_PRINTER 匹配时,将加载驱动,进入驱动接口 usblp_probe() 执行。

7.2.2 其它情形

还有更多其它驱动匹配的情形,细节呈现在 usb_device_match() 中,函数的逻辑不复杂,不在此处展开,感兴趣的读者可以自行阅读代码研究。

8. 参考资料

《Universal Serial Bus Specification》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/3004.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【亲测搭建成功】模拟无网络情况下安装K8S集群和相关组件

目录标题 前言准备工作:k8s集群:先构思网络拓扑图划分网络资源服务器开始搭建服务器操作系统初始化1.关闭防火墙2. 关闭selinux3. 修改网卡配置5.系统模块配置nacos 高可用mysql双主+双从rockemq 集群nginx高可用(双主)Redis 双主、双从minio分布式文件存储前言 最近项目上…

vulnhub Noob渗透笔记

靶机下载地址:https://www.vulnhub.com/entry/noob-1,746/ kali ip 信息收集 依旧我们先使用nmap扫描确定一下靶机ip nmap -sP 192.168.20.0/24发现靶机ip 扫描开放端口 nmap -A -p 1-65535 192.168.20.129 开放21 80 55077端口 先尝试使用匿名账号登录ftp,账户anonym…

linux系统运维面试题大全(137道题)

linux系统运维面试题大全 1、 如何看当前Linux系统有几颗物理CPU和每颗CPU的核数&#xff1f; 查看物理cup&#xff1a; cat /proc/cpuinfo|grep -c ‘physical id’ 查看每颗cup核数 cat /proc/cpuinfo|grep -c ‘processor’ 2、查看系统负载有两个常用的命令&#xff0c;…

STM32 ADC+定时器+DMA+FFT

本次实现的功能为单片机DAC输出一个正弦波&#xff0c;然后ADC定时采样用DMA输出&#xff0c;最后对DAC输出的波形进行FFT。单片机STM32F103ZET6内部时钟一、配置ADCADC端口为PA1&#xff0c;采用DMA输出&#xff0c;定时器3触发定时器时钟64M&#xff0c;分频后为102.4KHzADC采…

Scrapy的callback进入不了回调方法

一、前言 有的时候&#xff0c;Scrapy的callback方法直接被略过了&#xff0c;不去执行其中的回调方法&#xff0c;可能排查好久都排查不出来&#xff0c;我来教大家集中解决方法。 yield Request(urlurl, callbackself.parse_detail, cb_kwargs{item: item})二、解决方法 1…

基于QEMU-aarch64学习UEFI(EDK2)-1环境搭建

基于QEMU-aarch64学习UEFI(EDK2)-1环境搭建 文章目录基于QEMU-aarch64学习UEFI(EDK2)-1环境搭建一、环境搭建1、虚拟机Ubuntu系统安装2、docker镜像导入3、下载EDK2源码4、容器创建和代码编译4.1 容器创建4.2 代码编译5、运行QEMU_EFI.fd6、VSCODE配置7、日常工作8、不同项目的…

数据结构——二叉树与堆

作者&#xff1a;几冬雪来 时间&#xff1a; 内容&#xff1a;二叉树与堆内容讲解 目录 前言&#xff1a; 1.完全二叉树的存储&#xff1a; 2.堆的实现&#xff1a; 1.创建文件&#xff1a; 2.定义结构体&#xff1a; 3.初始化结构体&#xff1a; 4.扩容空间与扩容…

学习黑客十余年,如何成为一名高级的安全工程师?

1. 前言 说实话&#xff0c;一直到现在&#xff0c;我都认为绝大多数看我这篇文章的读者最后终究会放弃&#xff0c;原因很简单&#xff0c;自学终究是一种适合于极少数人的学习方法&#xff0c;而且非常非常慢&#xff0c;在这个过程中的变数过大&#xff0c;稍有不慎&#…

win32api之文件系统管理(七)

什么是文件系统 文件系统是一种用于管理计算机存储设备上文件和目录的机制。文件系统为文件和目录分配磁盘空间&#xff0c;管理文件和目录的存储和检索&#xff0c;以及提供对它们的访问和共享&#xff0c;以下是常见的两种文件系统&#xff1a; NTFSFAT32磁盘分区容量2T32G…

C/C++之while(do-while)详细讲解

目录 while循环有两个重要组成部分&#xff1a; while 是一个预测试循环 无限循环 do-while 循环 while循环有两个重要组成部分&#xff1a; 进行 true 值或 false 值判断的表达式&#xff1b;只要表达式为 true 就重复执行的语句或块&#xff1b;图 1 显示了 while 循环的…

GIS开源库GEOS库学习教程(一):编译及示例代码

1、介绍 GEOS库是一个集合形状的拓扑关系操作实用库&#xff0c;简单得说&#xff0c;就是判断两个几何形状之间关系和对两个几何形状进行操作以形成新的几何形状的库。GEOS是仿照JTS库做的&#xff0c;是JTS的C实现。下面是JTS Topology Suite (JTS) 拓扑运算函数库的介绍&…

Android 自定义View 之 Mac地址输入框

Mac地址输入框前言正文一、什么是View?二、什么是自定义View三、自定义View① 构造方法② XML样式③ 测量④ 绘制1. 绘制方框2. 绘制文字⑤ 输入1. 键盘布局2. 键盘接口3. 键盘弹窗4. 显示键盘5. 处理输入四、使用自定义View五、源码前言 在日常工作开发中&#xff0c;我们时长…

Ubuntu下载、配置、安装和编译opencv

1 安装相关依赖安装opencv前&#xff0c;需要先准备好编译器、相关依赖sudo apt-get install gcc g cmake vim sudo apt-get install build-essential libgtk2.0-dev libavcodec-dev libavformat-dev libjpeg-dev libswscale-dev libtiff5-dev sudo apt-get install libgtk2.0-…

MySQL数据库实现主从同步

安装MySQL数据库8.0.32 前言 今天来学习数据库主从同步的原理及过程&#xff0c;数据库主要是用来存储WEB数据&#xff0c;在企业当中是极为重要的&#xff0c;下面一起来看下。 1.1 数据库做主从的目的 MySQL主从复制在中小企业&#xff0c;大型企业中广泛使用&#xff0c…

类和对象 - 下

本文已收录至《C语言》专栏&#xff01; 作者&#xff1a;ARMCSKGT 目录 前言 正文 初始化列表 成员变量的定义与初始化 初始化列表的使用 变量定义顺序 explicit关键字 隐式类型转换 自定义类型隐式转换 explicit 限制转换 关于static static声明类成员 友元 友…

重构类关系-Push Down Method函数下移四

重构类关系-Push Down Method函数下移四 1.函数下移 1.1.使用场景 超类中的某个函数只与部分&#xff08;而非全部&#xff09;子类有关。将这个函数移到相关的那些子类去。 Push Down Method (328)与Pull Up Method (322)恰恰相反。当我有必要把某些行为从超类移至特定的子…

maven的profiles功能介绍、maven自定义插件plugin

profiles maven配置文件的profiles功能类似于springboot的spring.profiles.active配置&#xff0c;指定不同的环境来读取相应的配置内容。 <profiles>标签的子标签可以包含<repository> <plugin> <dependencies> <distributionManagement>等。 …

go语言gin框架学习

让框架去做http解包封包等&#xff0c;让我们的精力用在应用层开发 MVC模式 M: model&#xff0c;操作数据库gorm view 视图 处理模板页面 contoller 控制器 路由 逻辑函数 解决gin相关代码飘红的问题 记得启用gomodule go env -w GO111MODULEon然后到相应目录下执行 go mod i…

Karl Guttag:论相机对焦技术在AR/VR中的沿用

近期&#xff0c;AR/VR光学专家Karl Guttag介绍了两家在CES 2023展出光学传感技术的公司&#xff1a;poLight和CML&#xff08;剑桥机电一体化&#xff09;。​同时介绍两家公司的原因&#xff0c;是因为他们提供了实现AR/VR“光学微动”&#xff08;Optics Micromovement&…

5.多线程学习

作者&#xff1a;爱塔居 专栏&#xff1a;JavaEE 作者简介&#xff1a;大三学生&#xff0c;喜欢总结与分享~ 文章目录 目录 文章目录 章节回顾 一、wait 和notify 二、设计模式 2.1 单例模式 章节回顾 线程安全 1.一个线程不安全的案例&#xff08;两个线程各自自增5w次&…