============= 本系列参考 =============
《圈圈教你玩 USB》,《Linux 那些事儿之我是 USB》
协议文档: https://www.usb.org/document-library/usb-20-specification usb_20_20190524/usb_20.PDF
====================================
前言:
我们先不一上来讲 USB 大而全的协议规范文档, 会让人退而却步, 只要有协议, 在数据传输上波形就有规律可循, 翻译成数据, 也先不管 USB1.1/2.0 等版本, 因为最终的传输单元是一样的
一. 最基本传输单位 -- 包(packet)
1. 电气信号:
a. 采用 D+/D - 差分信号传输, LSB 在前, NRZI 编码也就是 0 反转, 1 不反转, 遇到连续 6 个 1 强插一个 0
b. 低速 Lowspeed 1.5Mb/s, 全速 Fullspeed 12Mb/s, 高速 Highspeed 480Mb/s, USB1.1 支持 L/F USB2.0 支持 L/F/H USB3.0 也支持 L/F/H 同时支持 OTG 功能
c. OTG(on the go) 就是多了根 ID 线, 用于判断主控器作 Host 还是 device
d. L/F S 采用电压传输(3.3v), HS 采用电流传输(等效电阻后示波器显示 400mv)
e. 传输方向以 Host 为准, 即 IN 表示 device 数据到 Host, OUT 表示 Host 数据到 device
f. 插入上电波形分析(下面单独抽出来分析)
2. packet 格式
SYNC 同步域 + PID 域 + 数据域 + CRC + EOP
a. 同步域: L/FS 固定 00000001, HS 前面 31 个 0 后一个 1
b. PID 占一个字节, 高 4bit 是低 4bit 的反码, 用于校验 PID 本身, 而 PID[3:0] 表示该 packet 的类型(协议文档 8.3.1):
打 * 表示 USB1.1 不支持的, 而 USB2.0 全支持, 这里要特别注意令牌包, 任何事务传输, 必须先发个令牌包说明意图, 至于后面是否需要数据包还是握手吧取决事务类型(下面会说)
c. 数据域是可选的, 取决 PID 是不是数据包(DATA0/1/2/M)
d. CRC 也是可选的, PID 自校验, 所以只对数据域校验, 若数据域没有那 CRC 也没有, 令牌包的数据域采用 CRC5 校验, 数据包的数据域采用 CRC16 校验
e. EOP 结束包, 对于 L/FS 是两个数据位宽的 SE0 信号(D+/D - 都是 0), 对于 HS 使用故意位填充表示(待具体解释)
f. 空闲状态, 在 SYNC 同步域前和 EOP 后 总线上处于空闲状态, L/FS 是一根高电平一根低电平(也就是 J 或 K 状态, 后面会讲), HS 是 SE0 表示空闲状态
g. SYNC 同步域, EOP,CRC 是硬件发射器自动添加和硬件接收器自动解析的, 软件看到的只有 PID 域和数据域
2. packet 类型
根据 PID[3:0]可以将包的类型分成 4 类
a. 令牌包: 一次 USB 传输必须首发令牌包, 告知意图, 同时后面数据表示跟哪个设备及端点通信, 这点很重要, 设想一下一个 Host 接了很多外设, Host 发出的信号会到达所有 hub 和普通外设, 如何避免串扰呢?
那就是总线某一时刻只有一个外设与 Host 通信, 外设硬件接口只响应令牌包, 因为令牌包的数据域表示设备的地址和端点地址, 外设可以解析是否和自己匹配, 如果是则响应(使能硬件接收数据), 以及后续的数据包交互, 如果不是
就不响应, 当然后续的数据包也会被外设硬件屏蔽, 不理会总线信号, 除非一段时间后又检测到令牌包, 再次进行地址匹配, 符合才使能硬件接收总线上的信号
IN OUT SETUP 包的数据域包含 7bit 设备地址和 4bit 端点地址, 所以一个 Host 能够最多接 127 个设备(0 是外设刚插入时的默认地址, 握手后必须赋值非 0, 不然下一个设备也是 0 就冲突了), 一个设备端点最多只能 16 个(端点 0 是必须的, 所以其他最多 15 个)
SOF(帧起始包) 相当于心跳包, 让所有外设知道 Host 还在活动(哪怕 Host 不是跟该设备通信但起码知道跟其他设备通信), L/FS 每隔 1ms 发一次, 每发一次 11bit 帧号加 1, HS 把 1ms 分割 8 份即每隔 125us 发一次, 但这 8 份里面的 11bit 帧号是相同的
这个心跳包主要用于休眠唤醒用的, 当 Host 没有发 SOF 超过 3ms 时 (一般是 Host 自己进入休眠或者想外设休眠), 外设设置自己进入低功耗状态(如果支持), 然后进入监听模式如果检测到总线有信号变化(只要跟睡眠前不一样) 立即唤醒,
可能是 Host 要召唤设备了, 当然设备也可以唤醒 Host, Host 进入休眠也会设置监听总线状态, 外设被人为唤醒改变总线信号接着唤醒 Host
b. 数据包: 这没啥好说的就是 PID 表明自己是数据包(DATA0 还是 DATA1 主要用于 确保对方收到), 后面就是字节数据了, 这里需要注意就是没有告知这个数据包到底多少个数据, 所以我猜想外设接收 PID 域后, 每接收一个字节 counter 计数器加 1
直到 EOP, 然后减 2 CRC16 校验值就是数据量, 接着对 FIFO 数据 CRC16 和最后两个字节对比, 不一致就产生数据错误中断, 一致就产生数据成功中断并将数据量填充 RX counter 寄存器
c. 握手包: 告知对方状态, 比如 Host 发送 IN 令牌包, 接着设备发送数据包, 然后 Host 接收完发送 ACK 握手包告知设备成功接收
不用数据域!
d. 特殊包主要用于高速, 比如上面 Host 发完 IN 令牌包后, 设备应该要发数据包的, 但设备还没准备好数据, 导致 Host 等待超时, Host 可以再次发 IN 包让设备进入发送数据, Host 切换等待接收数据状态,
这里有两个小问题, 一是设备数据未准备好, 却没有有效方式告知 Host, 只能啥都不做靠超时告知, 浪费 Host 时间, 二是 IN 包让设备进入发送数据模式, 设备有数据早发了还等你吹, 还让外设进入发送模式影响准备数据
而 PING 特殊包就是当第一次超时后, Host 不发 IN 包改发 PING 包询问设备准备好没, 设备若准备好了回复 ACK 握手包, 接着 Host 再发 IN 包, 如果还没准备好就发 NAK 告知, Host 就知道设备还没准备好而不用死等超时,
其他几个读者可自行查阅
总结: 总线是一个一个 packet 传输的, 且信号达到所有外设, 当发送 SYNC 域所有外设接收并调整时钟采样点做好同步, 接着解析 PID 域, 如果是非令牌包就不理会(只有已被选中的外设才理会), 如果是令牌包就解析后面地址是否和自己匹配,
不匹配继续不会理, 匹配的使能硬件接收数据功能, 并根据 PID 是 IN OUT SETUP SOF 再细分, 如果是 OUT, 产生 OUT 中断, 软件应该清空使能 FIFO 准备接收数据, 如果是 IN, 产生 IN 中断, 软件要填充好即将发的数据然后使能端点发送,
如果是 SETUP 包(Host 会接着发 DATA0 数据包数据域包含 8 个字节的标准请求), 设备要清空特殊 FIFO 并做好接受下一个数据, 接受完才产生 SETUP 中断, 软件就解析 FIFO 里的 8byte 标准请求, 然后准备数据, 比如是获取设备描述符请求
那软件得准备好设备描述符缓存并 ACK(必须 ACK 不能 NAK)回复, 然后 Host 会发 IN 包, 接着设备 IN 中断将刚才准备好的设备描述符缓存丢到端点 0 发出去!
如果是 SOF 包, 设备会重置时间计数器, 当 3ms 内没有新的 SOF 包, 就会产生中断, 设备知道总线现在是空闲状态, 可以自行决定是否休眠
二, 事务 -- 四种传输类型
一个个 packet 只是一盘散沙, 通过组织起来作为一个有效传输我们称之为事务, 所以一个事务起码包含:
一个令牌包, 通过地址选中具体外设
可选的数据包, 如果是 IN/OUT/SETUP 包那后续有数据包, 如果是 SOF 则数据包和握手吧都没有
可选的握手包, 像视频聊天这种实时传输不需要 ACK 应该, 丢了就丢了, 省下带宽不如用来发数据
因此, 根据具体的使用场景, 事务可以分成四种传输类型:
1. 批量传输(Bulk transfers )
一个批量事务包含三个阶段, 令牌包阶段 + 数据包阶段 + 握手包阶段, 其中数据包阶段可以发一个或多个数据包
以 Beagle USB 480 逻辑分析仪抓 U 盘上电时序时为例, 期间 Host(PC 机)会读取 U 盘数据(bluk 传输), 我们可以猜测应该发一个读取 U 盘根目录命令, 然后读取扇区信息, 如下:
一个读取扇区信息命令分别为 Command + Data + Status, Command 是一个写操作, 往设备发送数据告知想干嘛, 然后就是读数据, 最后检查状态, 可以看到这些操作都由三个 packet 构成 IN/OUT 令牌包 + 数据包 + 握手包
因为 U 盘每次操作只能 512byte/block, 所以想读取多个扇区只能分多次 IN 操作(传输最大字节数端点描述符有说明)
2. 中断传输(Interrupt transfers )
一个中断事务跟批量事务类似, 不同在于传输量比较少, 且希望 Host 每隔一段时间来访问设备(不是靠硬件中断告知系统, 而是端点描述符有个时间间隔变量, 告知 Host 最好小于这个时间间隔来访问设备), 像鼠标键盘都是这类传输模式,
以 Beagle USB 480 逻辑分析仪抓键盘为例:
这里可以看出三点, 一是 Host 每间隔 x 时间就发起一次读取键盘数据操作(还是老样子 IN 包 + DATA0 包 + ACK 包); 二是如果我没敲键盘, 则设备 NAK 告知 Host 没有数据; 三是间隔时间约 72/10 344/44 = 8ms
查看键盘端点描述符 bInterval=1, 根据 datasheet 代表 1ms, 即键盘希望 Host 每隔 1ms 读取一次数据, 但采不采纳在于 Host 端
3. 等时传输(Isochronous transfers )
等时事务跟前两种也差不多, 不同在于对时间敏感, 对数据准确性不关心, 所以不需要握手包, 主要用于音频, 视频类设备
4. 控制传输(Control transfers )
控制传输稍微复杂一点, 上面三个一个传输就是一个事务, 但控制传输有三个状态, 每个状态对应一个事务, 所以需要三次事务
三次过程分别为:
建立过程: SETUP 令牌包 + DATA0 数据包(标准请求就在这) + ACK 握手包(设备必须返回 ACK, 不能 NAK 如果设备连这个都不能保证的话就别玩了)
数据过程: 可选, 如上面是获取设备描述符这里就是 IN 令牌包 + DATA1 数据包 + ACK 握手包; 如果是设置地址请求, 地址在请求内部了, 不需要数据过程
状态过程: 上面的数据过程必须是同一个方向的, 如果方向改变, 则就是状态过程, 如果没有数据过程, 则这个数据包就是状态过程不管哪个方向
以 Beagle USB 480 逻辑分析仪抓 U 盘为例:
从捕捉的数据可看到, 建立过程的数据包包含着标准请求 80 06 00 01 00 00 12 00 (小端排序) , 前面的 C3 是 PID, 后面 E0 F4 是 CRC16, 可以通过 http://www.ip33.com/crc.html 验证
- 80 06 0100 0000 0012
- struct usb_ctrlrequest {
- __u8 bRequestType; //0x80
- __u8 bRequest; //0x06
- __le16 wValue; //0x100
- __le16 wIndex; //0
- __le16 wLength; //0x12
- } __attribute__ ((packed));
具体请参考协议文档 9-4
上面 log 还有个有趣的现象: 状态过程发送 1 字节 0x00 数据包, U 盘竟然返回 NAK, 不知为何, 由于是高速模式下, 所以 Host 接下来会发 PING 包探测 U 盘是否 ready, 直到 U 盘回复 ACK 才再次发送 OUT 包, 如果是 L/FS 则继续发 OUT 包直到接收 ACK
剩余数据的解析将在下一篇博文讲解!
来源: https://www.cnblogs.com/vedic/p/10951851.html