从零写OS内核|345:USB 协议——从 HID 到高速设备
摘要:USB 几乎是所有外设的”通用串行总线”,但很多人只知道”插上就能用”。本篇文章从物理层拓扑、传输类型、xHCI 控制器、到 lsusb 实战,带你彻底理解 USB 的设计哲学与 Linux 实战技能。wandos 目前无 USB 协议栈实现,相关代码仅在 Linux 环境有效。
1. USB 拓扑:星型结构不只是画着好看
USB 采用星型拓扑(Star Topology),所有设备通过 Hub 层层连接到一个根集线器(Root Hub),再接到 host 控制器。看似是树形,实际上对软件来说更像单根总线——所有设备共享同一个 host 控制器带宽。
┌──────────────────┐
│ USB Host │
│ (xHCI Controller)│
└────────┬─────────┘
│ USB 3.x Type-C
┌────────▼─────────┐
│ Root Hub │
└──┬──┬──┬─────┬──┘
│ │ │ │
┌────┘ │ │ └────┐
│ │ │ │
┌───▼──┐ ┌──▼─▼┐ ┌────▼────┐
│ Hub │ │Keyboard│ │ Mouse │
│ │ │ HID │ │ HID │
└──────┘ └─────┘ └─────────┘为什么这样设计?USB 的核心设计目标之一是简单性——设备不需要关心总线仲裁,host 负责轮询所有设备。每个设备都有唯一地址(1-127),通过 4 种传输类型来满足不同带宽需求。
2. 四种传输类型:选对”车道”很重要
USB 提供了 4 种传输类型,工程师必须理解它们的特性才能写出正确的固件或驱动。
| 传输类型 | 方向 | 带宽 | 延迟 | 典型设备 |
|---|---|---|---|---|
| Control | 双向 | 低 | 低 | 所有设备(枚举用) |
| Interrupt | 单向 | 低 | 确定 | 键盘、鼠标 |
| Bulk | 单向 | 可变 | 不确定 | U盘、打印机 |
| Isochronous | 单向 | 固定 | 确定 | 摄像头、音频 |
2.1 Control Transfer:枚举是它的主场
Control 传输是 USB 的”握手协议”。每个设备必须支持,至少要有两个端点:EP0 IN 和 EP0 OUT。它分 3 个阶段:
Setup Stage → Data Stage(optional) → Status Stage
8 bytes 0/8/16/32/64 bytes 0字节(方向反转)DESCRIPTOR REQUEST、SET_ADDRESS、SET_CONFIGURATION 这些枚举过程全部走 EP0 的 Control Transfer。
2.2 Interrupt Transfer:键盘鼠标的最爱
Interrupt 端点不是硬件中断,而是轮询——host 每 1-125ms(高速/超高速)或 10ms(全速)查询一次设备有没有数据。键盘鼠标用这个,延迟可预测,不会丢包(带重试)。
Host Device
│─── IN token (PING) ──────────────►│
│◄── NAK (无数据) ─────────────────│ (设备暂无数据)
│ │
│ (经过 1-125ms) │
│ │
│─── IN token (PING) ──────────────►│
│◄── DATA0/1 + 数据 ───────────────│ (有按键事件)2.3 Bulk Transfer:U盘的大动脉
Bulk 端点只有在总线空闲时才能使用,不保证延迟但保证数据正确性(带 CRC16 + 重试)。一个大文件写入 U 盘,带宽可能在 0 到满速之间波动,因为它和其他 Bulk 传输竞争。
2.4 Isochronous Transfer:实时性高于一切
摄像头和音频需要恒定带宽和确定延迟,但允许丢包(不需要重试)。Isochronous 传输没有握手,不带 CRC,错了就错了,下一帧赶紧跟上。
3. xHCI:USB 3.0 控制器
USB 主机控制器经历三代:
- UHCI(USB 1.1):Intel 规范,较少用
- OHCI(USB 1.1):Compaq 规范,常用于嵌入式
- EHCI(USB 2.0):统一支持 USB 1.1 + 2.0
- xHCI(USB 3.x):当前主流,支持 USB 3.0/3.1/3.2/USB4
xHCI 的核心数据结构是 Transfer Ring(传输环)——一个环形缓冲区,每个条目是一个 TRB(Transfer Request Block)。
Transfer Ring (环形)
┌──────────────────────────────────┐
│ TRB[0] │ TRB[1] │ TRB[2] │ TRB[3] │→→→(回到TRB[0])
└───┬────┴───┬────┴───┬────┘
│ │ │
Event Data Command
Ring Transfer RingxHCI 支持 Command Ring(host → controller 命令)和 Event Ring(controller → host 中断通知),真正的异步多队列设计。
wandos 目前无 USB 协议栈实现,xHCI 初始化代码仅存在于 Linux 分支。
4. Linux 实战:lsusb 家族
4.1 lsusb:列出所有 USB 设备
$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 046d:c52b Logitech, Inc. Unifying Receiver
Bus 001 Device 002: ID 046d:c534 Logitech, Inc. USB Receiver
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hubBus 001 是 USB 2.0 (EHCI),Bus 002 是 USB 3.0 (xHCI)。
4.2 lsusb -v:详细模式
$ lsusb -v -d 046d:c52b 2>/dev/null | head -60
Bus 001 Device 003: ID 046d:c52b Logitech, Inc. Unifying Receiver
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x046d Logitech, Inc.
idProduct 0xc52b Unifying Receiver
bcdDevice 12.01
iManufacturer 1 Logitech
iProduct 2 USB Receiver
iSerial 0
bNumConfigurations 14.3 usb-devices:脚本化版本
$ usb-devices | grep -A 10 "Logitech"
T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 3 Spd=12 MxCh= 0
D: Ver= 2.00 Cls=00(>ifc) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
P: Vendor=046d ProdID=c52b Rev=12.01
S: Manufacturer=Logitech
S: Product=USB Receiver
C: #Ifs= 2 Cfg#= 1 Atr=a0 MxPwr= 98mA
I: If#=0x0 Alt= 0 #EPs= 1 ECls=03(HID) Sub=01 Prot=02 Driver=usbhid
I: If#=0x1 Alt= 0 #EPs= 1 ECls=03(HID) Prot=00 Driver=usbhid这里可以看到:Logitech Unifying Receiver 是一个 HID 设备,有 2 个接口(HID Interface + 一个额外接口),驱动是 usbhid。
4.4 查看 USB 树状结构
$ lsusb -t
/: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M
/: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/11p, 480M
/: Bus 01.Port 2: Dev 2, If 0, Class=HID, Driver=usbhid, 1.5M
/: Bus 01.Port 3: Dev 3, If 0, Class=HID, Driver=usbhid, 1.5M-t 参数显示树状结构,同时显示每个设备的速率:5000M = USB 3.2 Gen2x1,480M = USB 2.0,1.5M = Low Speed(鼠标键盘)。
4.5 usbhid-dump:看 HID 报告
$ sudo usbhid-dump -d 046d:c52b 2>/dev/null | head -20
001:003:046d:c52b:4 report 2 size 8 offset 1
0000: 20 04 ff 00 00 00 00 00
0008: 00 00 00 00 00 00 00 00这就是 USB HID 的原始报告数据,HID 报告描述符告诉 host 这些字节代表什么意思(键盘按键、鼠标移动方向、滚轮等)。
5. USB 枚举过程:设备是怎么被”发现”的
当一个 USB 设备插入,host 控制器会执行一系列标准操作:
1. 复位信号 → 设备进入默认地址(0)
2. GET_DESCRIPTOR(device) → 读设备描述符(包含bcdUSB, idVendor, idProduct)
3. SET_ADDRESS → 给设备分配1-127的唯一地址
4. GET_DESCRIPTOR(configuration) → 读配置描述符
5. SET_CONFIGURATION(n) → 选择配置(设备可能支持多种配置)
6. 读取接口描述符、端点描述符
7. 根据接口类型,加载对应驱动(hid driver / storage driver / cdc driver...)枚举顺序是 USB 协议最核心的部分,理解了这个,才能理解为什么某些设备插上后不会立即工作(驱动加载失败)。
6. wandos 对比:无 USB 协议栈
wandos 目前无 USB 协议栈实现。USB 的复杂度远超串口——它需要完整的主机控制器驱动、Hub 驱动、HID 驱动、Storage 驱动分层。wandos 当前的外设模型是内存映射 I/O 直连,不经过 USB 协议层。对于想研究 USB 初始化的同学,建议从 Linux 的 drivers/usb/core/hcd.c(主机控制器驱动核心)和 drivers/usb/core/hub.c(Hub 驱动)入手。
7. 深入学习路径
├── 协议层:USB 2.0 Specification (Chapter 9 - Device Framework)
├── 控制器:xHCI Specification (Intel)
├── Linux内核:drivers/usb/core/(枚举、hub、urb)
├── HID:drivers/hid/usbhid/hid-core.c
└── 工具:usbutils (lsusb源码), wireshark USBdissector公众号底部:
📢 关注公众号【内核栈】,获取更多 OS/内核干货
💡 本文属于「从零写OS内核」系列,欢迎回顾:
- 篇 344:《时钟与复位》
- 篇 343:《总线与地址空间》
- 篇 342:《晶体管基础》
🔗 回复「USB」获取 USB 协议规范 PDF
作者:内核栈 | 原创出品
本文永久链接:https://kernelstack.dev/345-usb-protocol
评论