从零写OS内核|345:USB 协议——从 HID 到高速设备

摘要:USB 几乎是所有外设的”通用串行总线”,但很多人只知道”插上就能用”。本篇文章从物理层拓扑、传输类型、xHCI 控制器、到 lsusb 实战,带你彻底理解 USB 的设计哲学与 Linux 实战技能。wandos 目前无 USB 协议栈实现,相关代码仅在 Linux 环境有效。


1. USB 拓扑:星型结构不只是画着好看

USB 采用星型拓扑(Star Topology),所有设备通过 Hub 层层连接到一个根集线器(Root Hub),再接到 host 控制器。看似是树形,实际上对软件来说更像单根总线——所有设备共享同一个 host 控制器带宽。

Bash
                    ┌──────────────────┐
                    │   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 个阶段:

Bash
Setup Stage → Data Stage(optional) → Status Stage
   8 bytes     0/8/16/32/64 bytes    0字节(方向反转)

DESCRIPTOR REQUESTSET_ADDRESSSET_CONFIGURATION 这些枚举过程全部走 EP0 的 Control Transfer。

2.2 Interrupt Transfer:键盘鼠标的最爱

Interrupt 端点不是硬件中断,而是轮询——host 每 1-125ms(高速/超高速)或 10ms(全速)查询一次设备有没有数据。键盘鼠标用这个,延迟可预测,不会丢包(带重试)。

Bash
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)

Bash
                 Transfer Ring (环形)
        ┌──────────────────────────────────┐
        │ TRB[0] │ TRB[1] │ TRB[2] │ TRB[3] │→→→(回到TRB[0])
        └───┬────┴───┬────┴───┬────┘
            │        │        │
         Event      Data     Command
         Ring       Transfer  Ring

xHCI 支持 Command Ring(host → controller 命令)和 Event Ring(controller → host 中断通知),真正的异步多队列设计。

wandos 目前无 USB 协议栈实现,xHCI 初始化代码仅存在于 Linux 分支。


4. Linux 实战:lsusb 家族

4.1 lsusb:列出所有 USB 设备

Bash
$ 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 hub

Bus 001 是 USB 2.0 (EHCI),Bus 002 是 USB 3.0 (xHCI)。

4.2 lsusb -v:详细模式

Bash
$ 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      1

4.3 usb-devices:脚本化版本

Bash
$ 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 树状结构

Bash
$ 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 报告

Bash
$ 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 控制器会执行一系列标准操作:

Bash
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. 深入学习路径

Bash
├── 协议层: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

最后修改: 2024年12月14日

作者

评论

发表评论

您的邮箱地址不会被公开。