USB 协议基础:从物理连线到设备识别
大家好,欢迎来到《USB Gadget 驱动实现》的第一章。今天我们聊聊最基础的东西——USB 协议。我知道很多朋友喜欢直接上手写驱动代码,但说实话,USB 这玩意儿要是底层不懂,调试起来会非常痛苦,我自己就在这上面栽过不少跟头。嗯,我们先花点时间,把地基打牢。
1.1 USB 总线架构:主从分明,层次清晰
USB 总线是一个主从架构。说白了,就是只有一个“老大”——主机(Host),其他全是“小弟”——设备(Device)。所有数据交换都由主机发起,设备只能被动响应。我年轻时有个误解,以为设备能主动“喊”主机,结果调试了半天发现根本发不出去,数据全堵在缓冲区里。后来才明白,USB 的规则就是这样,设备永远不能主动开始一次传输。
从逻辑上看,USB 系统可以拆成三层:
- 主机端:包含 USB 主机控制器(Host Controller)和根集线器(Root Hub)。主机控制器负责生成总线上的所有传输事务,根集线器提供物理端口。
- 物理连接:就是线缆和 Hub。Hub 用来扩展端口,让一个主机口能接多个设备。
- 设备端:包括 Hub 和功能设备(比如 U 盘、鼠标、或者我们关心的 Gadget)。
设备内部的结构也很有意思。一个 USB 设备可以包含多个配置(Configuration),一个配置下面有多个接口(Interface),一个接口又包含多个端点(Endpoint)。端点,才是真正传输数据的管道。而一个或多个接口组合在一起,就构成了一个功能(Function)。举个例子,一个摄像头设备,可能有一个视频接口和一个音频接口,它们共享一个配置。
| 层级 | 描述 | 我的理解 |
|---|---|---|
| 设备(Device) | 代表一个物理设备 | 设备地址的拥有者 |
| 配置(Configuration) | 设备的供电模式与功能组合 | 设备启动时选一个 |
| 接口(Interface) | 实现一项具体功能 | 驱动通常匹配接口 |
| 端点(Endpoint) | 数据收发的管道 | 0号端点必须存在 |
理解这个层次结构对我们后面写 Gadget 驱动至关重要。因为你要告诉主机,你的 Gadget 长什么样,就是通过一层层描述符来定义的。描述符的顺序,必须严格遵循“设备 -> 配置 -> 接口 -> 端点”这种层级。我曾见到过同事把接口类和端点描述符的顺序搞反了,结果设备怎么都枚举不成功。
1.2 传输类型:四种“快递”服务
端点可以配置不同的传输类型。USB 定义了四种,你可以把它们想象成四种快递服务:
- 控制传输(Control Transfers):这是最核心的,用来配置和获取设备信息。每个设备在枚举阶段,都靠它来完成身份确认。它是双向的,有特定的握手流程。我个人习惯称它为“管理通道”。
- 批量传输(Bulk Transfers):适合传输大量数据,对时间要求不高,但对准确性要求很高的场景。比如 U 盘、打印机。它保证数据无误送达,但可能延迟。我曾经做过一个高速数据采集的 Gadget,代码层面批量传输的逻辑是最简单的。
- 中断传输(Interrupt Transfers):名字叫“中断”,但本质上还是主机轮询。只是主机保证会定期来“询问”设备,适合键盘、鼠标这类需要及时响应的设备。延迟有上限。
- 等时传输(Isochronous Transfers):这是对时间要求最苛刻的。数据必须按照预定速率发送或接收。如果某次传输失败了,它不会重传,因为下一帧数据已经来了。适合音频、视频流。嗯,这里要特别注意,等时传输不保证数据正确性,数据错了一位它也不管。
1.3 枚举过程:一次完整的自我介绍
枚举过程,是 USB 设备最重要的成人礼。一个设备插上去,主机是怎么一步步认识它的?简单说,就是一个一问一答的过程,所有问答都通过 端点 0 进行控制传输。
流程我总结成这八步:
- 设备连接:主机通过根 Hub 或外接 Hub 检测到端口电平变化(D+或D-线被拉高),知道有设备接入。
- Hub 汇报:Hub 告诉主机,某个端口状态改变了。
- 主机查询:主机查询 Hub,具体是哪个端口,发生了什么变化。
- 端口复位:主机向这个端口发送复位信号。这时设备收到复位,会把设备地址复位到 0,准备接收主机指令。
- 获取设备描述符:主机使用默认地址 0,向设备发送“获取设备描述符”请求。设备返回前 8 个字节。这步非常重要!主要是获取 bMaxPacketSize0,即端点 0 的最大包长。为什么只收前 8 个?因为这时候主机还不知道设备端点 0 能传多少字节,前 8 字节是最小约定。
- 重新复位并分配地址:主机知道端点 0 的大小后,会再次复位设备,然后发送“设置地址”请求,给设备分配一个唯一的地址(比如 1、2、3...)。设备收到后必须应答并启用新地址。
- 完整获取描述符:主机再用新地址,完整地获取设备描述符(18字节)、配置描述符、接口描述符、端点描述符等。这一步就是收集齐所有配置信息,好为设备选择合适的驱动程序。
- 配置设备:主机加载驱动程序后,会发送“设置配置”请求。设备收到后,真正进入工作状态,可以开始正常数据传输了。
这个过程非常标准。我自己在开发 Gadget 驱动时,最常用来定位问题的方法,就是在主机端用 lsusb -v 或 USB 分析仪抓包,看枚举在哪个步骤失败了。绝大多数问题都出在第 5 步“获取设备描述符”和第 7 步“完整获取描述符”中,描述符的数据填错了。
好了,为了让大家对上面这些内容有个整体印象,我画了一张图,把 USB 的架构、传输类型和枚举过程串起来。
这张图把今天的核心概念都串起来了。左边是 USB 总线的“主-从”架构,右边是设备内部的“配置-接口-端点”层级。中间部分列出了四种传输类型,下方是完整的 8 步枚举流程。你可以把它当成一份速查指南,后面几章我们会不断回到这些概念上。