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 系统的核心,负责管理数据传输、设备控制、事件处理等。Linux 支持多种 USB 主机控制器,例如:
-
USB 设备(Devices):
- USB 设备是通过 USB 总线连接到计算机的外部硬件设备。设备通过 USB 总线与主机进行数据交换。设备可以是多种类型:
- USB 存储设备:如 U 盘、外部硬盘。
- USB 输入设备:如键盘、鼠标、触摸屏。
- USB 音频设备:如麦克风、扬声器。
- USB 视频设备:如网络摄像头、视频采集卡。
- 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 ID
和 Product 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
用于处理设备添加或删除事件。它会监听内核的设备事件(如add
或remove
),并根据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)
- libusb:
libusb
是一个用户空间库,用于通过 USB 接口与设备进行通信。它提供了一个跨平台的 API,允许用户空间程序与 USB 设备交互,不需要编写内核代码。
2. USB 子系统 (USB Subsystem)
- usbcore:
usbcore
是 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 系统中的设备管理涉及到插拔事件的处理、设备的识别与分类、驱动的加载与卸载。
- 电源管理涉及到设备的挂起、唤醒、节能等策略,通常与
usbcore
及usb_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-storage
、usbhid
、usb-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
+----------------------+
| 设备可用 |
| (用户空间访问) |
+----------------------+