3. USB接口基础:USB协议简介、USB控制器初始化、USB设备枚举流程
做机顶盒WiFi驱动,USB接口是绕不开的坎。说实话,现在市面上九成以上的外置WiFi模块都是走USB接口的。你想想看,USB协议虽然复杂,但我们搞驱动开发,真正需要吃透的就三块:协议基础、控制器初始化、设备枚举流程。今天我就把这三点掰开了讲清楚。
3.1 USB协议简介——别被那些术语吓到
USB协议,说白了就是一套主从通信的规矩。主机(Host)说了算,设备(Device)乖乖听话。我刚开始接触USB时,看到那些描述符、端点、管道之类的术语,头都大了。后来做多了才发现,核心就几个概念。
3.1.1 物理层与电气特性
USB使用差分信号传输,D+和D-两根线。速度不同,电气特性也不一样:
| USB版本 | 速率 | 信号编码 | 线缆最大长度 |
|---|---|---|---|
| USB 1.1 | 12 Mbps(全速) | NRZI | 5米 |
| USB 2.0 | 480 Mbps(高速) | NRZI + 位填充 | 5米 |
| USB 3.0 | 5 Gbps | 8b/10b | 3米 |
机顶盒WiFi模块大多是USB 2.0高速设备。嗯,这里要注意:高速设备在D+线上会接一个1.5kΩ上拉电阻,全速设备则是在D-线上。这个细节在调试时很有用,我曾经因为上拉电阻焊错了位置,折腾了两天才找到原因。
3.1.2 协议分层结构
USB协议分三层,从上到下是:
- 功能层:设备具体干什么,比如WiFi模块收发数据
- 设备层:管理设备状态、配置、接口
- 总线接口层:处理包传输、事务、传输类型
做驱动开发,我们最常打交道的是设备层。你得理解设备描述符、配置描述符、接口描述符、端点描述符这四件套。我习惯把它们比作一本书:描述符是目录,配置是章节,接口是段落,端点是句子。
3.1.3 传输类型
USB定义了四种传输类型,WiFi驱动里全用得上:
| 传输类型 | 特点 | WiFi驱动中的应用 |
|---|---|---|
| 控制传输 | 可靠,双向,有保证 | 设备枚举、命令下发 |
| 批量传输 | 可靠,但延迟不确定 | 大数据收发(TCP数据) |
| 中断传输 | 有保证的延迟,但数据量小 | WiFi事件通知、扫描结果 |
| 等时传输 | 实时性高,但可能丢数据 | 音频视频流(WiFi模块很少用) |
我个人习惯把批量传输用在数据通道上,中断传输用在事件通知上。控制传输嘛,初始化时用得多,跑起来后就很少碰了。
3.2 USB控制器初始化——让硬件准备好
USB控制器,在机顶盒里通常是集成在SoC中的。初始化流程各家芯片略有不同,但大框架是一样的。
3.2.1 控制器硬件初始化
以常见的EHCI(USB 2.0)控制器为例,初始化步骤大致如下:
// USB控制器初始化伪代码
void usb_hcd_init(struct usb_hcd *hcd)
{
// 1. 使能时钟和电源
clk_enable(hcd->clk);
regulator_enable(hcd->vbus_reg);
// 2. 复位控制器
writel(USBCMD_HCRESET, hcd->regs + USBCMD);
mdelay(10); // 等待复位完成
// 3. 设置控制器模式为主机模式
writel(USBMODE_CM_HOST, hcd->regs + USBMODE);
// 4. 配置中断
writel(USBINTR_ALL, hcd->regs + USBINTR);
// 5. 启动控制器
writel(USBCMD_RUN_STOP, hcd->regs + USBCMD);
// 6. 等待控制器就绪
while (!(readl(hcd->regs + USBSTS) & USBSTS_HCH))
cpu_relax();
}
这段代码看着简单,但坑不少。我遇到过最典型的问题是:复位后不等够时间就直接操作寄存器,结果控制器根本没准备好。还有一次是电源时序不对,先给了时钟再给电源,控制器直接罢工。
3.2.2 根Hub初始化
USB控制器内部集成了一个根Hub(Root Hub)。初始化完控制器,还得把根Hub搞起来:
- 注册根Hub设备
- 配置端口属性(比如端口数量、供电能力)
- 使能端口状态变化检测中断
根Hub初始化完成后,控制器就等着设备插上来了。一旦检测到D+/D-上的电平变化,就会触发端口状态中断,然后开始枚举流程。
3.3 USB设备枚举流程——设备是怎么被认出来的
枚举,就是主机发现并配置设备的过程。这个过程很有意思,我当年第一次跟踪枚举流程时,感觉就像在看一场精心编排的舞蹈。
3.3.1 枚举的七个步骤
标准的USB枚举流程分七步:
- 设备连接:设备插入,D+或D-被拉高,主机检测到端口状态变化
- 复位设备:主机向端口发送复位信号,设备地址重置为0
- 获取设备描述符:主机发控制请求,获取前8字节(含最大包长度)
- 再次复位:根据设备速度重新复位(高速设备需要这一步)
- 设置地址:主机分配唯一地址给设备
- 获取完整描述符:获取设备、配置、接口、端点等全部描述符
- 选择配置:主机选择一个配置,设备进入配置状态
你想想看,从设备插上到能正常工作,也就几百毫秒的事。但这里面每一步都可能出问题。
3.3.2 枚举过程中的关键交互
枚举的核心是控制传输。主机通过默认端点(端点0)和设备通信。我画个简化的交互流程:
主机 --> 设备: GET_DESCRIPTOR (设备描述符, 8字节)
设备 --> 主机: 返回前8字节 (含bMaxPacketSize0)
主机 --> 设备: SET_ADDRESS (地址=1)
设备 --> 主机: 状态阶段完成
主机 --> 设备: GET_DESCRIPTOR (设备描述符, 完整18字节)
设备 --> 主机: 返回完整设备描述符
主机 --> 设备: GET_DESCRIPTOR (配置描述符)
设备 --> 主机: 返回配置描述符 + 接口描述符 + 端点描述符
主机 --> 设备: SET_CONFIGURATION (配置值=1)
设备 --> 主机: 状态阶段完成
3.3.3 枚举失败常见原因
做WiFi驱动调试,枚举失败是最常见的问题。我总结了几种典型情况:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 设备连接后无中断 | VBUS没电、上拉电阻问题 | 量D+/D-电压,检查电源 |
| 获取描述符超时 | 地址冲突、设备未复位 | 抓USB总线数据,看复位时序 |
| 设置地址后设备无响应 | 地址分配错误、设备固件bug | 换一个已知正常的设备对比测试 |
| 配置描述符解析失败 | 描述符长度不对、格式错误 | 用USB分析仪抓包,逐字节检查 |
说实话,枚举问题八成出在硬件上。我建议你先用示波器看看D+/D-的波形,确认设备确实发出了信号,再往软件层面排查。
3.4 实战经验:WiFi模块枚举的特殊之处
WiFi模块作为USB设备,枚举时有个特点:它通常需要加载固件后才能完成枚举。也就是说,模块刚插上时可能只是一个「空壳」,主机枚举到某个阶段后,需要先下载固件到模块里,然后模块复位,重新枚举一次。
我遇到过最头疼的情况是:模块第一次枚举成功了,固件也下载进去了,但第二次枚举时设备地址变了,导致上层应用找不到设备。后来发现是驱动里没有处理好设备移除和重新添加的事件。
dev_info(dev, "Enumeration step %d: %s\n", step,
status == 0 ? "OK" : "FAIL");
有了这些日志,定位问题会快很多。
嗯,USB接口基础就讲到这里。下一章我们会深入WiFi模块的USB通信细节,包括批量传输的urb管理、中断传输的异步通知机制。到时候再聊。