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

你有没有想过:插一个鼠标,系统怎么就知道这是个鼠标而不是键盘? 为什么 U 盘插上去就能用,但老式打印机却要装驱动?USB 看似简单,背后的协议栈却深不见底。

本文从 USB 的树形拓扑讲起,拆解传输类型、描述符、xHCI 控制器,并给出可复制的 Linux 调试命令。读完你会发现——USB 不只是”插上去就能用”,它是一套完整的外设通信标准。


一、USB 的物理拓扑:为什么它是树形结构

USB(Universal Serial Bus)设计上是一个树形拓扑,通过 Hub 扩展,每个节点是一个设备或 Hub。

Bash
                      ┌─────────────┐
                      │   USB Host   │  ← 只有 1 个 host(PC 端)
                      │  (控制器)   │
                      └──────┬──────┘
                             │ roothub
              ┌──────────────┼──────────────┐
              │              │              │
         ┌────▼───┐     ┌────▼───┐     ┌────▼────┐
         │ HubPort│     │HubPort │     │   Hub   │
         │   1   │     │   2   │     │ (总线)  │
         └────┬───┘     └────┬───┘     └───┬────┘
              │              │            │
         ┌────▼───┐     ┌────▼───┐   ┌────▼────┐
         │  键盘  │     │  鼠标  │   │   Hub   │
         │        │     │        │   │(下行×4) │
         └────────┘     └────────┘   └───┬────┘
                                        │
                                   ┌────▼────┐
                                   │  摄像头  │
                                   └─────────┘

关键约束:

  • 每个 USB Host 最多接 127 个设备(包括 Hub)
  • Hub 每个端口是一个独立事务(transaction),Hub 本身也算一个设备
  • 全速(12Mbps)/高速(480Mbps)/超高速(5Gbps) 是物理层差异,协议层统一

二、四种传输类型:USB 的”路”分几种

USB 为了满足不同场景的需求,定义了四种传输类型——就像公路分快车道、慢车道、应急车道

Bash
┌──────────────┬────────────┬──────────┬────────────┬──────────────────┐
│ 传输类型      │ 方向       │ 实时性   │ 带宽        │ 典型设备          │
├──────────────┼────────────┼──────────┼────────────┼──────────────────┤
│ Control      │ 双向        │ 无保证   │ 低(1ms帧) │ 枚举、配置        │
│ Interrupt    │ 双向        │ 保证     │ 低          │ 键盘、鼠标        │
│ Bulk         │ 单向        │ 无保证   │ 剩余带宽    │ U 盘、打印机      │
│ Isochronous   │ 单向        │ 保证     │ 高带宽      │ 摄像头、音频      │
└──────────────┴────────────┴──────────┴────────────┴──────────────────┘

为什么键盘用 Interrupt 而不是 Bulk?
键盘要求”按下去立刻响应”,Interrupt 每 10ms 最多 1 次轮询,保证延迟。Bulk 会被队列挤压,不适合实时输入。


三、USB 描述符:设备是怎么自述的

每个 USB 设备在插入时会向 Host 报告自己的”简历”——这就是描述符(Descriptor)。它有四层嵌套:

Bash
设备描述符(1 个设备只有 1 个)
  └── 配置描述符(1 个设备可以有多个配置,但同时只有 1 个生效)
        └── 接口描述符(1 个配置有多个接口,代表功能分组)
              └── 端点描述符(每个接口有多个端点,端点 0 用于控制传输)

实际例子:插一个 U 盘后发生了什么

Bash
# 1. 插入 U 盘,设备插入 Hub 端口
# 2. Hub 检测到设备插入,发中断通知主机
# 3. 主机发 GetDescriptor(DEVICE),读设备描述符
#    → 获取:Vendor ID (VID) / Product ID (PID) / 设备类
# 4. 主机发 GetDescriptor(CONFIGURATION),读配置描述符
#    → 获取:配置值、接口数、供电方式
# 5. 主机加载对应驱动(如 usb-storage)
# 6. 驱动通过 Bulk 传输读写 U 盘数据

用 Linux 查看 U 盘的描述符信息:

Bash
$ lsusb
Bus 002 Device 003: ID 0951:1666 Kingston Technology DataTraveler 3.0

$ lsusb -v -d 0951:1666
Bus 002 Device 003: ID 0951:1666 Kingston Technology DataTraveler 3.0
  Device Descriptor:
    bcdUSB:             0x0300 (USB 3.0)
    idVendor:           0x0951 Kingston Technology
    idProduct:          0x1666 DataTraveler 3.0
    bNumConfigurations: 1
    Configuration Descriptor:
      bNumInterfaces:   1
      bConfigurationValue: 1
      MaxPower:          200mA
      Interface Descriptor:
        bInterfaceClass: 0x08 (Mass Storage)
        bInterfaceSubClass: 0x06 (SCSI)
        bInterfaceProtocol: 0x50 (BBB)
        Endpoint Descriptor:
          bEndpointAddress: 0x81 (IN, EP1)
          bmAttributes:      0x02 (Bulk)
          wMaxPacketSize:   1024

四、xHCI 控制器:USB 的”交通调度中心”

USB 控制器芯片负责把 USB 总线和 PCIe 总线桥接起来。目前主流控制器有三种:

控制器 全称 对应 USB 版本 特点
UHCI Universal Host Controller Interface USB 1.1 Intel 创建,硬件简单,软件复杂
OHCI Open Host Controller Interface USB 1.1 康柏创建,硬件复杂,软件简单
EHCI Enhanced Host Controller Interface USB 2.0 高速控制器
xHCI eXtensible Host Controller Interface USB 3.x/4 最新,支持 USB 3.0+,支持虚拟化

xHCI 的内部结构:

Bash
 PCIe 总线
    │
    ▼
┌────────────┐
│ xHCI MMIO │  ← 寄存器映射(BAR 地址)
│  (MMIO)   │
└─────┬──────┘
      │
      ▼
┌────────────┐
│  HC (Host │
│  Controller)│
└─────┬──────┘
      │ 多个 port
  ┌───┼────────────────────┐
  │   │                    │
┌─▼─┐┌─▼─┐            ┌─▼──┐
│Port││Port│            │Port│
│  1 ││  2 │  ...       │  N │
└────┘└────┘            └────┘
  │    │                 │
  │    │              [设备]
  └───┴─── [Hub] ───────┘

Linux 查看控制器类型:

Bash
$ lspci -nnk | grep -i usb
00:14.0 USB controller [0c03]: Intel Corporation Tiger Lake-H USB 3.2 Gen 2x2 XHCI (20d5)
        Subsystem: Intel Corporation Device 9a13
        Kernel driver in use: xhci_hcd
# 注意 driver 是 xhci_hcd → 这是 xHCI 控制器

$ ls /sys/bus/pci/drivers/xhci_hcd/
bind  Module  new_id  parameters  uevent  unbind  0000:00:14.0

五、实战:Linux USB 调试命令

Bash
# 查看系统所有 USB 设备树
lsusb
lsusb -t          # 以树形展示拓扑(包含速度信息)
lsusb -v          # 详细描述符

# 查看特定设备
lsusb -d 0951:1666

# 监控 USB 事件(实时插拔)
udevadm monitor --subsystem-match=usb

# 查看设备在内核的模块绑定
ls /sys/bus/usb-serial/drivers/
# 挂载 U 盘后的块设备
lsblk | grep sd
dmesg | grep -i usb | tail -20
# 查看 USB 设备的具体端点信息
cat /sys/bus/usb/devices/*/idVendor

强制重新加载 USB 存储驱动:

Bash
modprobe usb-storage
# 或查看具体块设备
dmesg | grep "SCSI command"

六、wandos 的 USB 支持

wandos 目前没有 USB 协议栈实现。 wandos 的 I/O 模型以文件系统为核心(VFS/ext2),对硬件外设的抽象层尚未建立。

如果你想在 wandos 上支持 USB,需要:

  1. arch/x86/core/ 下实现 EHCI/xHCI MMIO 寄存器访问
  2. 实现 USB Hub 检测和设备枚举
  3. 为 HID 设备实现输入子系统

这一块是 wandos 的 roadmap 缺口,欢迎 contribution。


公众号 footer

相关阅读:

  • 《从零写OS内核 | 总线与地址空间——CPU 是怎么找到每个设备的》(343)
  • 《从零写OS内核 | I2C 总线——嵌入式系统的血管》(348)

动手环节:
运行 lsusb -t,观察你电脑上有多少 USB 设备,哪几个是高速(480M)或超高速(5G)?

仓库地址:
github.com/golang12306/os-kernel-from-scratch
对应 Demo:demos/usb/(概念验证代码)


关注公众号「AI不着急」,回复”资料”获取内核学习路线图。

最后修改: 2024年6月15日

作者

评论

发表评论

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