Linux 驱动开发之USB设备分析4

Linux 驱动开发之USB设备分析4(基于Linux6.6)---USB热插拔介绍

一、USB基础概念

1.1、硬件知识

在最初的标准里,USB接头有4条线:电源,D-,D+,地线。我们暂且把这样的叫做标准的USB接头吧。后来OTG出现了,又增加了miniUSB接头。而miniUSB接头则有5条线,多了一条ID线,用来标识身份用的。标准USB口只有A型和B型。其中每一型又分为插头和插座,例如A型插头,A型插座等。我们平常电脑上用的那种插座叫做A型USB插座,而相应的插头,叫做A型插头,例如U盘上那种。而像打印机上面那个插座,则是B型插座(比较四方的,没电脑上面那种扁),相应的插头,就是B型插头。也许你见过一头方一头扁的USB延长线,没错了,扁的那头就叫做A型插头,而方的那头,就叫做B型插头,而相应的被插的那两个插座,就分别是A型插座和B型插座了。A型插头是插不进B型插座的,反之亦然。

miniUSB也分为A型,B型,但增加了一个AB型。既然它叫做miniUSB,那么当然它就是很小的了,主要是给便携式设备用的,例如MP3、手机、数码相机等。USB是一主多从结构,即一个时刻只能有一台主机。像PC机就是一个主机,其它的只能是设备,因而两个设备之间是无法直接进行通信的。而USB OTG(on the go)的出现,则解决了这个矛盾:一个设备可以在某种场合下,改变身份,以主机的形式出现。因而就出现了AB型的miniUSB插座,不管是A型miniUSB插头,还是B型miniUSB插头,都可以插进去,而靠里面多出的那条ID线来识别它的身份:是主机还是从机。这样两个USB设备就可以直接连接起来,进行数据传送了。 像我们MP3上用的那中miniUSB插座,就是B型的miniUSB插座(注意,有一类miniUSB插座,似乎不是USB规范里面的,因为miniUSB接头应该有5条线,而这种插座只有4条线)。由于USB是支持热插拔的,因此它在接头的设计上也有相应的措施。USB插头的地引脚和电源引脚比较长,而两个数据引脚则比较短,这样在插入到插座中时,首先接通电源和地,然后再接通两个数据线。这样就可以保证电源在数据线之前接通,防止闩锁发生。至于USB电缆,通常我们不怎么关心,买现成的就行了,除非你是生产USB线缆的。在全速模式下需要使用带屏蔽的双绞电缆线,而低速模式模式则可以不用屏蔽和双绞。此外,USB协议规定,USB低速电缆长度不得超过3米,而全速电缆长度不得超过5米。这是因为线缆传输有延迟,要保证能够正确响应,就不能延迟太多。USB标准规定了里面信号线的颜色,其中Vbus为红色,D-为白色,D+为绿色,GND为黑色。

1.2、集线器把USB设备的连接报告给USB主控制器

      首先,在USB集线器的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。而在USB设备端,在D+或者D-上接了1.5K欧姆上拉电阻。对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。这样,当设备插入到集线器时,由1.5K的上拉电阻和15K的下拉电阻分压,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到高速模式的。在高速模式下,是电流传输模式,这时将D+上的上拉电阻断开。

1.3、USB 硬件架构

USB 架构可以分为以下几个重要部分:

  • 主机控制器(Host Controller)

    • 主机控制器是 USB 系统的核心,负责管理数据传输、设备控制、事件处理等。Linux 支持多种 USB 主机控制器,例如:
      • UHCI(Universal Host Controller Interface):较老的控制器,通常用于早期的 USB 1.x 系统。
      • OHCI(Open Host Controller Interface):另一种较旧的 USB 1.x 控制器。
      • EHCI(Enhanced Host Controller Interface):用于 USB 2.0 设备的控制器。
      • xHCI(eXtensible Host Controller Interface):用于支持 USB 3.x 和更高版本设备的控制器。
  • USB 设备(Devices)

    • USB 设备是通过 USB 总线连接到计算机的外部硬件设备。设备通过 USB 总线与主机进行数据交换。设备可以是多种类型:
      • USB 存储设备:如 U 盘、外部硬盘。
      • USB 输入设备:如键盘、鼠标、触摸屏。
      • USB 音频设备:如麦克风、扬声器。
      • USB 视频设备:如网络摄像头、视频采集卡。
      • USB 打印机:打印机等输出设备。
  • USB 集线器(Hub)

    • USB 集线器(Hub)是允许多个 USB 设备连接到计算机的设备。它可以扩展 USB 接口的数量,分配带宽以及管理多个设备的通信。
  • USB 总线(Bus)

    • USB 总线是主机控制器与设备之间的数据传输通道。USB 总线是全双工的,可以同时进行数据发送和接收。每个 USB 设备都有一个唯一的地址,USB 主机控制器管理这些设备的连接和通信。

1.4、USB 设备的通信

USB 的数据传输是通过一种叫做“数据包”(Packet)的方式进行的。每个数据包都包含了数据和控制信息,传输过程由主机控制器和 USB 设备共同协调。

USB 的通信协议分为多个层次,主要包括:

  • 物理层(Physical Layer):负责电气信号的传输。
  • 链路层(Link Layer):定义数据传输的格式、速率、纠错机制等。
  • 传输层(Transport Layer):负责设备之间的数据传输,定义了不同类型的传输(控制、批量、中断、同步)。
  • 会话层(Session Layer):管理设备的连接状态,进行设备的识别、配置等操作。

传输类型

USB 支持以下几种不同的数据传输方式:

  • 控制传输(Control Transfer)

    • 用于设备的初始化、配置、命令控制等小数据量的通信。
    • 如设备的请求、状态查询等操作。
  • 批量传输(Bulk Transfer)

    • 用于大数据量的传输,通常用于存储设备(例如 U 盘、硬盘等)。
    • 传输速率较高,但传输是非实时的。
  • 中断传输(Interrupt Transfer)

    • 用于实时性要求较高的传输,如键盘、鼠标等输入设备的事件。
    • 有固定的传输时间间隔。
  • 同步传输(Isochronous Transfer)

    • 用于需要保证固定带宽、低延迟的实时数据传输,如音频、视频等设备。
    • 支持实时流数据传输,确保数据传输的时间一致性。

1.5、USB 设备的识别与配置

当一个 USB 设备连接到主机时,Linux 内核会根据设备的 Vendor IDProduct ID(即设备的唯一标识符)来识别设备,并加载相应的驱动程序。

设备描述符(Device Descriptor)

USB 设备通过设备描述符(Device Descriptor)向主机报告自身的信息。设备描述符中包含了设备的 Vendor ID、Product ID、设备类、子类、协议等信息。根据这些信息,主机可以识别设备并加载合适的驱动。

  • 设备描述符(Device Descriptor):包含设备的基本信息。
  • 配置描述符(Configuration Descriptor):描述设备的配置,包括设备的功能、接口、端点等。
  • 接口描述符(Interface Descriptor):描述设备的功能接口,可能有多个接口。
  • 端点描述符(Endpoint Descriptor):描述设备数据传输的端点(数据流的来源或目的地)。

设备驱动

Linux 提供了对 USB 设备的驱动支持。当设备连接时,USB 子系统会根据设备的描述符信息来匹配合适的驱动。常见的 USB 设备驱动包括:

  • USB 存储驱动:如 usb-storage 驱动,用于支持 U 盘、外部硬盘等存储设备。
  • USB 网络驱动:如 cdc_ether 驱动,用于支持 USB 网络适配器。
  • USB 输入设备驱动:如 usbhid 驱动,用于支持 USB 键盘、鼠标等设备。
  • USB 音频驱动:如 usb-audio 驱动,用于支持 USB 音频设备。

二、热插拔

1. USB 热插拔工作原理

USB 热插拔的关键在于系统能在设备插入或拔出时,动态地检测、识别、配置并加载适当的驱动程序。具体的工作过程包括以下几个步骤:

  • 设备插入:当用户插入 USB 设备时,物理层(硬件)会通过 USB 总线传递信号到主机控制器。Linux 内核通过检测到的硬件中断(IRQ)来识别这一事件。

  • 设备检测:内核的 USB 子系统接收到设备插入的信号后,会扫描该设备的描述符(Device Descriptor)以获取设备的身份信息,如厂商 ID、产品 ID、设备类型等。

  • 驱动匹配:内核根据设备的描述符与已加载的 USB 驱动程序进行匹配。匹配成功后,相应的驱动程序会被加载,设备也就准备好了可以使用。

  • 设备配置:设备的配置过程通常包括分配系统资源、设置接口、配置端点等。对于一些设备(如存储设备),内核还可能挂载该设备。

  • 设备使用:一旦设备配置完成,用户可以开始使用设备,例如读取 U 盘中的数据、使用 USB 键盘输入等。

  • 设备拔出:当设备被拔出时,Linux 会检测到 USB 总线上的信号变化,并进行相应的清理操作。系统会卸载设备驱动程序并释放资源,确保不会出现数据丢失或系统崩溃。

2. USB 热插拔的关键组件

  • 内核(Kernel):Linux 内核提供了支持 USB 热插拔的底层机制。USB 子系统负责设备的识别、驱动的加载、以及设备的资源分配。

  • udev(用户空间设备管理器)udev 是一个用户空间的设备管理器,它用于动态管理设备节点和设备事件。udev 会监听内核发出的设备添加或删除事件,并执行相应的规则来创建或删除设备节点。例如,当插入一个 USB 存储设备时,udev 会自动在 /dev 目录下创建相应的设备文件(如 /dev/sda)。

  • USB 主机控制器驱动:主机控制器是与 USB 总线通信的硬件部分。不同版本的 USB(如 USB 2.0、USB 3.0、USB 3.1)会使用不同的控制器,并由相应的驱动程序进行支持。

  • 设备驱动程序:USB 设备的驱动程序负责与设备进行通信,处理数据传输等。Linux 提供了对各种 USB 设备的驱动支持,如 USB 存储、USB 网络设备、USB 输入设备等。

3. 热插拔事件处理

USB 热插拔事件在 Linux 系统中是通过以下几个主要机制进行处理的:

  • 内核日志(dmesg:当插入或拔出 USB 设备时,内核会输出相关的日志信息。这些信息包括设备的连接、断开、驱动加载等状态。用户可以使用 dmesg 命令查看日志,以便诊断 USB 设备的问题。

    例如,插入 USB 设备时可能会看到类似的日志输出:

  • [ 1234.567890] usb 2-1: new high-speed USB device number 3 using xhci_hcd
    [ 1234.789012] usb 2-1: New USB device found, idVendor=0781, idProduct=5567, bcdDevice= 1.00
    [ 1234.789019] usb 2-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
    
  • udev 规则udev 用于处理设备添加或删除事件。它会监听内核的设备事件(如 addremove),并根据 udev 规则执行相应的动作(如创建设备文件、设置权限等)。

    例如,当插入一个 USB 存储设备时,udev 会在 /dev 目录下创建一个设备文件 /dev/sdb。可以使用 udevadm 工具来查询或调试 udev 规则。

  • udevadm info --name=/dev/sdb --query=all
    

4. 热插拔支持的设备类型

Linux 支持多种类型的 USB 设备的热插拔,包括但不限于以下几种:

  • USB 存储设备:如 U 盘、外部硬盘、SSD、闪存卡等。

  • USB 输入设备:如键盘、鼠标、游戏手柄、触摸屏等。

  • USB 音频设备:如 USB 音频接口、麦克风、耳机等。

  • USB 网络设备:如 USB 网络适配器、USB 无线网卡等。

  • USB 打印机:如 USB 打印机、扫描仪等外设。

  • USB 摄像头:如 USB 网络摄像头、USB 视频采集卡等。

三、Linux 下USB热插拔处理

3.1、 Linux下USB HUB的驱动的实现和分析

在系统初始化的时候在usb_init函数中调用usb_hub_init函数,就进入了hub的初始化

在usb_hub_init函数中完成了注册hub驱动,并且利用函数kthread_run创建一个内核线程。该线程用来管理监视hub的状态,所有的情况都通过该线程来报告。

USB设备是热插拔,这就和PCI设备不同,PCI设备是在系统启动的时候都固定了,因此PCI设备只需要初始化进行枚举就可以了,采用递归算法即可。而USB设备需要热插拔,因此在hub_probe函数中调用hub_configure函数来配置hub,在这个函数中主要是利用函数usb_alloc_urb函数来分配一个urb,利用usb_fill_int_urb来初始化这个urb结构,包括hub的中断服务程序hub_irq的,查询的周期等。

每当有设备连接到USB接口时,USB总线在查询hub状态信息的时候会触发hub的中断服务程序hub_irq,在该函数中利用kick_khubd将hub结构通过event_list添加到khubd的队列hub_event_list,然后唤醒khubd。进入hub_events函数,该函数用来处理khubd事件队列,从khubd的hub_event_list中的每个usb_hub数据结构。该函数中首先判断hub是否出错,然后通过一个for循环来检测每个端口的状态信息。利用usb_port_status获取端口信息,如果发生变化就调用hub_port_connect_change函数来配置端口等。

3.2、软件层次分析

先讲讲USB热插拔事件的处理工作。---Khubd守护进程。

-Khubd守护进程它是一个守护进程,来检查usb port的事件通知HCD和usb core,然后做相应的处理。

驱动目录drivers/usb/*
usb/serial  usb 串行设备驱动 (例如usb 3G卡、蓝牙等)
usb/storage  usb 大储量磁盘驱动(u盘)  
usb/host usb host usb主机控制器驱动(嵌入式otg:dwc_otg)
usb/core   usb 核心一些处理代码,所有的驱动相关处理都在这里,也都注册到它里面。
usb/usb-skeleton.c 经典的usb客户驱动框架。

...
下面贴出USB的整体驱动框架:

USB 驱动框架架构

+--------------------------------------------------+
|                用户空间 (User Space)             |
+--------------------------------------------------+
                |                         ^
                v                         |
+-------------------------+        +------------------+
|    libusb (用户空间库)    | <----> | 设备驱动程序接口  |
+-------------------------+        +------------------+
                |                         ^
                v                         |
+--------------------------------------------------+
|           USB 子系统 (USB Subsystem)             |
|  (usbcore, 驱动核心模块)                        |
+--------------------------------------------------+
                |                         ^
                v                         |
+-------------------------+        +-------------------+
|   USB 控制器驱动        | <----> |    USB HCD 驱动   |
|  (USB Host Controller   |        |  (Host Controller |
|   Driver)               |        |  Driver)          |
+-------------------------+        +-------------------+
                |                         ^
                v                         |
+--------------------------------------------------+
|   USB 设备驱动程序 (USB Device Drivers)         |
|  (针对不同类型设备的驱动,接口驱动等)           |
+--------------------------------------------------+

1. 用户空间 (User Space)

  • libusblibusb 是一个用户空间库,用于通过 USB 接口与设备进行通信。它提供了一个跨平台的 API,允许用户空间程序与 USB 设备交互,不需要编写内核代码。

2. USB 子系统 (USB Subsystem)

  • usbcoreusbcore 是 Linux 内核中的 USB 驱动核心,负责 USB 设备的注册、连接管理和 USB 设备的发现。它为上层驱动提供了一些接口和功能,如设备枚举、设备识别、设备管理等。
  • 设备驱动程序接口 (Driver Model):Linux 内核的驱动模型(如 struct usb_driver)允许 USB 设备与 USB 驱动程序关联,处理设备的插拔、设备文件的创建等功能。

3. USB 主机控制器驱动 (USB Host Controller Driver, HCD)

  • HCD 驱动:负责与 USB 主机控制器进行通信。HCD 提供了一些硬件抽象层功能,管理 USB 总线的数据传输和主机控制器的操作。常见的 HCD 包括:
    • EHCI:用于支持 USB 2.0 的主机控制器驱动。
    • UHCI/OHCI:用于支持 USB 1.x 的主机控制器驱动。
    • XHCI:用于支持 USB 3.x 的主机控制器驱动。

4. USB 设备驱动程序 (USB Device Drivers)

  • USB 设备驱动:这是针对不同 USB 设备的驱动程序,通常按照设备的类型和协议编写。根据设备的不同,驱动程序可以是:
    • USB 存储设备驱动:如支持 USB 闪存、硬盘等存储设备。
    • USB 网卡驱动:如支持 USB 网络适配器的驱动程序。
    • USB 音频设备驱动:如支持 USB 音频设备(如麦克风、耳机等)。
    • USB HID 驱动:支持 USB 输入设备(如键盘、鼠标等)。

5. 控制器层 (Host Controller)

  • USB 控制器驱动:与 USB 主机硬件交互的底层驱动程序,负责直接控制 USB 端口的电气信号、数据传输等。它是 USB 主机控制器的一部分,通常由硬件厂商提供并支持不同版本的 USB 规范(如 USB 2.0、USB 3.x)。

6. 设备管理和电源管理

  • USB 系统中的设备管理涉及到插拔事件的处理、设备的识别与分类、驱动的加载与卸载。
  • 电源管理涉及到设备的挂起、唤醒、节能等策略,通常与 usbcoreusb_device 相关。

主要分析khub的工作原理: 硬件层次是hub的工作,如何和host及其设备间通信及相应事件

drivers/usb/core/hub.c 

int usb_hub_init(void)
{
	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;

	/* Fall through if kernel_thread failed */
	usb_deregister(&hub_driver);
	pr_err("%s: can't allocate workqueue for usb hub\n", usbcore_name);

	return -1;
}

只关心alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);然后我们看alloc_workqueue函数。

kernel/workqueue.c 


__printf(1, 4)
struct workqueue_struct *alloc_workqueue(const char *fmt,
					 unsigned int flags,
					 int max_active, ...)
{
	size_t tbl_size = 0;
	va_list args;
	struct workqueue_struct *wq;
	struct pool_workqueue *pwq;

	/*
	 * Unbound && max_active == 1 used to imply ordered, which is no
	 * longer the case on NUMA machines due to per-node pools.  While
	 * alloc_ordered_workqueue() is the right way to create an ordered
	 * workqueue, keep the previous behavior to avoid subtle breakages
	 * on NUMA.
	 */
	if ((flags & WQ_UNBOUND) && max_active == 1)
		flags |= __WQ_ORDERED;

	/* see the comment above the definition of WQ_POWER_EFFICIENT */
	if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)
		flags |= WQ_UNBOUND;

	/* allocate wq and format name */
	if (flags & WQ_UNBOUND)
		tbl_size = nr_node_ids * sizeof(wq->numa_pwq_tbl[0]);

	wq = kzalloc(sizeof(*wq) + tbl_size, GFP_KERNEL);
	if (!wq)
		return NULL;

	if (flags & WQ_UNBOUND) {
		wq->unbound_attrs = alloc_workqueue_attrs();
		if (!wq->unbound_attrs)
			goto err_free_wq;
	}

	va_start(args, max_active);
	vsnprintf(wq->name, sizeof(wq->name), fmt, args);
	va_end(args);

	max_active = max_active ?: WQ_DFL_ACTIVE;
	max_active = wq_clamp_max_active(max_active, flags, wq->name);

	/* init wq */
	wq->flags = flags;
	wq->saved_max_active = max_active;
	mutex_init(&wq->mutex);
	atomic_set(&wq->nr_pwqs_to_flush, 0);
	INIT_LIST_HEAD(&wq->pwqs);
	INIT_LIST_HEAD(&wq->flusher_queue);
	INIT_LIST_HEAD(&wq->flusher_overflow);
	INIT_LIST_HEAD(&wq->maydays);

	wq_init_lockdep(wq);
	INIT_LIST_HEAD(&wq->list);

	if (alloc_and_link_pwqs(wq) < 0)
		goto err_unreg_lockdep;

	if (wq_online && init_rescuer(wq) < 0)
		goto err_destroy;

	if ((wq->flags & WQ_SYSFS) && workqueue_sysfs_register(wq))
		goto err_destroy;

	/*
	 * wq_pool_mutex protects global freeze state and workqueues list.
	 * Grab it, adjust max_active and add the new @wq to workqueues
	 * list.
	 */
	mutex_lock(&wq_pool_mutex);

	mutex_lock(&wq->mutex);
	for_each_pwq(pwq, wq)
		pwq_adjust_max_active(pwq);
	mutex_unlock(&wq->mutex);

	list_add_tail_rcu(&wq->list, &workqueues);

	mutex_unlock(&wq_pool_mutex);

	return wq;

err_unreg_lockdep:
	wq_unregister_lockdep(wq);
	wq_free_lockdep(wq);
err_free_wq:
	free_workqueue_attrs(wq->unbound_attrs);
	kfree(wq);
	return NULL;
err_destroy:
	destroy_workqueue(wq);
	return NULL;
}
EXPORT_SYMBOL_GPL(alloc_workqueue);
  • 0:这是工作队列的其他参数,表示工作队列的大小为默认值(通常是动态分配的),没有指定具体的线程数。

7. 作用

调用 alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0) 会创建一个名为 "usb_hub_wq" 的工作队列,该队列是一个 可冻结的 工作队列,通常在以下情况下用于 USB hub 相关的任务:

  • USB hub 设备的异步操作:对于 USB hub,特别是涉及多个 USB 设备的操作,很多任务是异步的,需要延迟执行。比如处理 USB 设备的插拔事件、设备的状态监控、检测 USB 端口的变化等,这些操作可以通过工作队列来异步处理,避免在内核中直接进行可能导致阻塞的操作。

  • 避免阻塞内核主线程:通过使用工作队列,可以将处理这些任务的操作从内核主线程(如硬中断或软中断)中分离出来,从而避免阻塞重要的内核任务,保持系统的响应性。

  • 节省系统资源:在需要执行某些低优先级任务时,可以将它们放入工作队列,延迟执行。这样在系统负载较轻时才处理这些任务,从而平衡系统资源。

  • 支持设备休眠/挂起:由于使用了 WQ_FREEZABLE 标志,这意味着如果系统或设备需要进入低功耗模式(比如待机或挂起),这个工作队列中的任务会被暂停,直到系统恢复正常工作。这对于管理 USB 设备的电源管理非常重要。

8.典型应用场景

在 USB hub 驱动中,工作队列通常用于处理一些需要延迟或在特定条件下执行的任务。例如:

  • 在 USB hub 上检测到新的设备插入时,可能会创建一个工作任务来处理设备的初始化,这个任务会推迟到工作队列中异步执行。
  • 在 USB hub 设备的状态发生变化时,可能会创建工作任务来更新设备状态或执行其他操作。
  • USB hub 需要处理设备的断开事件时,可能会通过工作队列来处理设备的移除操作。

四、USB的枚举过程

       内核辅助线程khubd用来监视与该集线器连接的所有端口,通常情况下,该线程处于休眠状态,当集线器驱动程序检测到USB端口状态变化后,该内核线程立马唤醒

      USB的枚举过程:USB的枚举过程是热插拔USB设备的起始步骤,该过程中,主机控制器获取设备的相关信息并配置好设备,集线器驱动程序负责该枚举过程

枚举过程主要分如下几步:

1. 设备连接和主机检测

  • 当 USB 设备插入计算机的 USB 端口时,USB 主机控制器(Host Controller)会检测到新设备的插入,并触发一个硬件中断。
  • 主机控制器将发出一个 USB 插拔事件,该事件会通知内核进行进一步处理。

2. 设备初始化和设备树

  • 内核收到插拔事件后,调用 USB 子系统的代码(usbcore),开始处理该事件。内核首先通过 设备树(Device Tree)或 PCI 总线(如果是内建主机控制器)将 USB 主机控制器与 USB 设备进行关联。
  • 内核会为新插入的设备分配一个唯一的设备标识符(Device ID),并通过设备编号来管理 USB 总线上的所有设备。

3. USB 设备描述符获取

  • 内核通过 USB 控制传输 请求设备的 描述符(Descriptor)。这些描述符包含了设备的基本信息,例如设备类型、设备版本、厂商 ID、产品 ID、类代码、协议等。描述符有多个类型:
    • 设备描述符:包含设备的基本信息(如厂商、产品、版本等)。
    • 配置描述符:描述设备的配置(包括接口、端点等)。
    • 接口描述符:描述设备的接口信息。
    • 端点描述符:描述设备的数据传输端点信息(如输入/输出端点)。

这一步是通过 控制传输 的方式进行的,USB 主机控制器会将设备描述符传回。

4. 设备地址分配

  • 在获取设备描述符之后,主机分配一个 唯一地址(USB 地址)给新连接的设备。USB 设备地址通常是一个 7 位数值,从 1 开始递增,最多可分配到 127
  • 这个设备地址是在 设备枚举的早期阶段 由主机控制器分配的,以便后续对设备进行识别和管理。

5. 配置设备

  • 当设备描述符被成功读取并且设备地址已分配后,内核会根据设备提供的配置描述符来选择一个合适的配置。这一过程被称为 设备配置
  • 在 USB 设备可以有多个配置(每个配置可能有不同的功能),主机会选择一个默认配置,通常是第一个配置。如果设备有多个接口和端点,内核会相应地分配资源。

设备配置过程可能涉及选择接口,选择传输端点,配置数据传输协议等。每个接口可以包含多个端点(例如输入端点和输出端点)。

6. 加载适配驱动

  • 在设备成功配置后,内核根据设备的 类代码(Device Class)来判断该设备需要哪个驱动程序。驱动程序可能已经预先加载,或者在设备连接时按需加载。
  • 驱动程序通过设备的 接口描述符端点描述符 来初始化设备的通讯协议,设置设备的功能。
  • 如果设备的驱动程序未预先加载,内核将会触发模块加载器(modprobe)来自动加载相应的设备驱动模块。

驱动程序通常是通过内核中的 USB 设备驱动程序框架(如 usb-storageusbhidusb-net 等)提供的。内核会根据设备的描述符匹配并绑定到相应的驱动程序。

7. 设备就绪

  • 一旦驱动程序加载完成并与设备成功通信,设备就被认为已准备好,可以供用户空间程序访问了。
  • 内核会创建相应的 设备节点(如 /dev/ttyUSB0/dev/sda/dev/input/event0 等),用户空间程序可以通过这些节点与 USB 设备进行交互。

此时,USB 设备已经完全初始化并处于可用状态,用户可以通过标准的文件操作接口访问设备,或者通过应用程序与设备进行数据交互。

Linux USB 枚举过程 的简化流程图:

+----------------------+
|  设备连接到主机       |
|  (USB 插拔事件触发)   |
+----------------------+
           |
           v
+----------------------+
|  主机控制器检测到新设备 |
|  (硬件中断触发)       |
+----------------------+
           |
           v
+----------------------+
|  内核收到插拔事件     |
|  (调用 usbcore 处理) |
+----------------------+
           |
           v
+----------------------+
|  主机控制器分配设备地址|
|  (如地址 1 到 127)    |
+----------------------+
           |
           v
+----------------------+
|  发送控制传输请求     |
|  (请求设备描述符)     |
+----------------------+
           |
           v
+----------------------+
|  获取设备描述符       |
|  (设备 ID, 类代码等)  |
+----------------------+
           |
           v
+----------------------+
|  发送控制传输请求     |
|  (请求配置描述符)     |
+----------------------+
           |
           v
+----------------------+
|  获取配置描述符       |
|  (配置, 接口, 端点)   |
+----------------------+
           |
           v
+----------------------+
|  配置设备             |
|  (选择配置, 接口等)   |
+----------------------+
           |
           v
+----------------------+
|  驱动程序匹配和加载   |
|  (根据类代码选择驱动) |
+----------------------+
           |
           v
+----------------------+
|  设备初始化完成       |
|  (设备节点创建)       |
+----------------------+
           |
           v
+----------------------+
|  设备可用             |
|  (用户空间访问)       |
+----------------------+
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值