起因

系列里 EHCI 篇讲了"怎么驱动主机控制器",键鼠篇讲了"怎么解析报表"——中间那层"USB 协议本身怎么运作"一直是背景板。这层的性格全写在出身上:九十年代中期 Intel、微软、康柏这批厂商要终结"装机开机箱拨跳线"的日子,定下的却是一条 Host 主导的主从总线——设备永远被动应答、时间切固定帧,后文的规矩几乎都能从这两条推出来。这篇把它拉到前台单独讲清:设备类是什么、四种传输类型各管什么、Host 与 Device 谁说话、鼠标的坐标和键盘的键值到底是怎么一步步传回来的、U 盘的读写又是一套什么机制。这层想通了,EHCI 和键鼠两篇的代码为什么那么写、U 盘驱动为什么长那个结构,自己就能对上号。

一、USB 的世界模型:Host 主导一切

USB 是主从式总线,规矩只有一条:Host(主机侧)发起一切通信,Device(设备侧)永远只应答。设备不会"主动通知"主机任何事情——所谓"鼠标动了,通知电脑",真相是 Host 按固定节奏发问,设备有新数据就答,没有就回 NAK(我不说话但也没事)。

  • 我们的工控板是固定的 Host 角色;键鼠、U 盘都是 Device
  • Host/Device 是硬件角色不是软件姿态:OTG(On-The-Go)规范允许一个口在两种角色间切换(手机既插电脑当 U 盘、又插 U 盘读文件),工控场景用不上,但概念要分清——角色决定了谁发 token
  • 时间被切成帧:全速 1ms 一帧、高速 125µs 一微帧。Host 在每帧里排班:哪些端点轮到了、控制传输进行到哪个阶段——EHCI 的周期列表和异步列表就是这张排班表的硬件化身

把"轮询而非中断"这个模型想通,USB 就懂了一半:键盘鼠标走的中断传输(Interrupt Transfer),名字里的"中断"指的是保证延迟上限,不是设备能产生中断

二、四种传输类型

USB 按数据的要求分四种传输,端点生下来就注定属于其中一种:

类型 特点 保证 用在哪
控制传输 三阶段事务,走端点 0 一定传(重试) 枚举、配置、类协议命令——管理通道
中断传输 Host 按承诺间隔轮询 延迟上限(间隔内必被问到) 键盘、鼠标
批量传输 占用空闲带宽,尽量传 可靠(CRC+重试),不保证时间 U 盘读写、打印机
等时传输 固定带宽预留 带宽,不重传 音频、摄像头

控制传输的三阶段(SETUP → DATA → STATUS)是 USB 一切"配置动作"的统一格式:8 字节请求包说清"要干嘛",数据阶段可选,状态阶段收尾——枚举的 SET_ADDRESS、HID 的 SET_PROTOCOL、U 盘的批量命令都骑在这条通道上。批量传输没有延迟保证但保证正确,U 盘读写大数据全靠它。等时传输我们用不上,不展开。

四种传输都靠握手包收口:ACK 收好、NAK 这轮没有、STALL 出问题了。STALL 要记牢——设备用它说两句话:控制传输里说"这条命令我不支持"(比如向只读盘发 WRITE),批量端点上说"我出故障了,先别理我"。STALL 之后端点挂起,必须走端点 0 发 CLEAR_FEATURE(ENDPOINT_HALT) 清掉才能继续;U 盘驱动写坏一块盘、设备失电重连,撞上的都是它,恢复路径写不写直接决定驱动健不健壮。

三、设备类:同一类设备说同一种话

USB 用**类码(Class Code)**给设备分方言,Host 按类写驱动,一份驱动通吃所有厂商的同类设备:

类码 我们系统的角色
0x03 HID(人机接口设备) 键盘、鼠标
0x08 Mass Storage U 盘、移动硬盘
0x09 Hub 集线器(TT 方案里机内那个高速 Hub)
0x02 CDC 虚拟串口(调试备用通道);4G 模块用的 NCM 也属 CDC 家族
0xFF 厂商自定义 完全私有协议(不走类驱动的那种设备)

类信息挂在接口描述符上(设备级类声明已少见),U 盘的典型配置:接口类 0x08、子类 0x06(SCSI 透明命令集)、协议 0x50(Bulk-Only Transport)——这三个数字就是"这是一只标准 U 盘"的出生证明。

四、鼠标怎么把坐标传回来

端点配置:EP0(控制,走配置和 SET_PROTOCOL)+ 一个中断 IN 端点(bInterval = 8ms 之类)。数据通路按时间轴展开:

  1. Host 每 8ms 对这个端点发一次 IN token(EHCI 周期列表排好的班)
  2. 鼠标上次报表之后动过 → 回一个报表包;没动过 → 回 NAK,这一轮作废不占线
  3. 报表落在 Host 给的缓冲区,驱动被"传输完成"唤醒

报表内容(Boot 协议,3 字节起步):

1
2
3
4
byte0: 按键位图  bit0左键 bit1右键 bit2中键
byte1: dx   int8  相对位移 X(-127~127)
byte2: dy   int8  相对位移 Y
byte3: 滚轮(可选)

三个关键认知:

  • 相对坐标:鼠标报的是"这 8ms 里动了多少",不是"光标该在哪"。绝对位置由主机累积维护——这样手在桌面上无限移动、光标却停在屏幕里;触摸屏/数字化仪才用绝对坐标(另一套报表格式)
  • int8 的补码:向左向上是负值,快速甩动一轮报表能跳几十像素,驱动解析时 as i8 转换不能丢(键鼠篇踩过的坑在此有源头)
  • 报表是最新状态快照不是增量流水——同一轮询周期内多次移动会被合并成最后一个位移,这是"轮询模型"的天然行为

五、键盘怎么传数据

同样的中断 IN 通道,报表换成 8 字节:

1
2
3
byte0:  修饰键位图 bit0左Ctrl bit1左Shift bit2左Alt bit3左GUI bit4~7右侧对应
byte1:  保留
byte2~7: 当前按着的键的 Usage 码数组,0=空槽

和鼠标同样的三个关键认知,再加两条:

  • 数组快照语义:报表回答的是"现在按着谁",不是"谁刚按下"。按下/弹起事件要靠前后两帧对比:上帧没有这帧有 = 按下,反之 = 弹起——这是键盘驱动核心逻辑就一个双层循环的原因
  • 只有 6 个槽:同时按超过 6 键,设备回 Usage 0x01(rollover),全帧作废——经典折衷,游戏玩家骂的"键位冲突"源头就在这。修饰键不占槽(在 byte0 位图里),所以 Ctrl+Shift+5 键随便组合
  • 长按不会连发:设备按住不放报表内容就不变;“自动重复"是主机软件(UI 层定时器)自己实现的。SET_IDLE 是让设备连"无变化的重复报表"都省了,总线更清净
  • Usage 码不是 ASCII(0x04 才是 a,0x1E 是 1),键盘布局(US/欧洲)到字符的映射表在主机侧——所以换语言布局不用换键盘

六、U 盘:三层协议叠出来的块设备

U 盘的读写比键鼠多一层,整体是三层洋葱

1
2
3
4
应用层要读第 N 块
    ↓ SCSI 命令层:READ(10),带 LBA 和块数
    ↓ MSC 类协议层:SCSI 命令装进 CBW,数据按方向批量传,结果装进 CSW
    ↓ USB 传输层:控制传输(配置期)+ 批量传输(数据期)

端点配置:EP0 + Bulk OUT(命令、写出数据)+ Bulk IN(读回数据、状态)。

每次读写是一个固定三段式事务(Bulk-Only Transport):

第一段:CBW(命令块包,31 字节),Host 通过 Bulk OUT 发出:

字段 偏移 内容
dCBWSignature 0 0x43425355("USBC"
dCBWTag 4 本次事务的编号,CSW 里原样带回用于配对
dCBWDataTransferLength 8 数据阶段总字节数
bmCBWFlags 12 bit7 = 0 写出 / 1 读回
bCBWLUN 13 逻辑单元号(多卡读卡器区分槽位用)
bCBWCBLength 14 SCSI 命令长度
CB 15 SCSI 命令块(16 字节区)

第二段:数据阶段,按 CBW 声明的方向和长度走批量传输(读盘 = Bulk IN,写盘 = Bulk OUT)。

第三段:CSW(命令状态包,13 字节),设备通过 Bulk IN 回:

字段 偏移 内容
dCSWSignature 0 0x53425355("USBS"
dCSWTag 4 对应 CBW 的 tag
dCSWDataResidue 8 实际比声明的少传了多少
bCSWStatus 12 0 成功;1 失败(要跟 REQUEST SENSE 问原因);2 相位错误(复位重来)

SCSI 子集一只 U 盘只需要几个命令:INQUIRY(0x12) 设备识别、READ CAPACITY(0x25) 问容量、READ(10)(0x28)/WRITE(10)(0x2A) 读写、REQUEST SENSE(0x03) 失败后问细节。tag 配对 + 状态机三段式是 MSC 驱动的全部骨架:发 CBW → 搬数据 → 收 CSW → 核对 tag → 下一事务。

七、回到我们的系统

把这篇映射回系列代码:EHCI 篇实现的是第一节那张"排班表”(周期列表=中断传输的班次,异步列表=控制/批量的通道);键鼠篇解析的正是第四、五节的报表格式;U 盘驱动篇把第六节的 CBW/CSW 状态机 + SCSI 命令子集落成了 NVMe 篇定义的 BlockDevice——exFAT 文件系统没改一行,同时服务 NVMe 盘和 U 盘。这条链走完,分层的好处不用再讲:传输层搬运、类协议说话、块设备抽象收口。

总结

USB 的心智模型一句话:Host 排班、设备应答,一切"事件"都是轮询出来的。四种传输按需求分工——控制管配置、中断保延迟、批量保正确、等时保带宽。设备类让一份驱动通吃同类设备:键鼠(HID)靠中断传输传状态快照(鼠标是 int8 相对位移、键盘是 6 槽 Usage 数组,都要前后帧对比出事件);U 盘(MSC)靠批量传输跑 CBW→数据→CSW 三段式,里面包着 SCSI 命令。把这篇和 EHCI、键鼠两篇叠起来读,USB 栈从控制器到报表的每一层就都透明了。

参考链接