文章目录
前言
USB(通用串行总线)作为现代计算机和嵌入式设备中广泛使用的数据传输协议,提供了高效、稳定的通信方式。本文将重点探讨 USB 数据接收的工作机制、关键流程及其在 Linux 驱动中的实现。
USB 数据传输 4 种模式
USB 采用主从架构(Host-Device),所有数据传输均由主机(Host) 发起,设备(Device) 被动响应。USB 数据传输分为四种模式:
- 控制传输(Control Transfer):用于设备初始化和控制命令的交换,如枚举(Enumeration)。
- 中断传输(Interrupt Transfer):用于低延迟、小数据量的传输,如键盘、鼠标。
- 批量传输(Bulk Transfer):用于大数据量的可靠传输,如 U 盘、网卡。
- 同步传输(Isochronous Transfer):用于对时延敏感的数据流,如音视频传输。
无论哪种传输模式,USB 接收的核心逻辑都是相似的——主机向设备请求数据,设备返回数据,主机确认接收。
USB 数据传输 3 种数据包
USB 传输的基本单元是数据包,分为三类:
- 令牌包(Token Packet):指示数据传输的方向(IN 或 OUT)和目标地址。
- 数据包(Data Packet):包含实际的数据内容。
- 握手包(Handshake Packet):用于确认数据包的接收情况,如 ACK(确认)、NAK(未就绪)等。
USB 接收流程
USB 设备的数据接收通常是主机请求数据后,设备返回相应数据包的过程。完整的数据接收流程如下:
- 主机发送 IN 令牌:主机请求设备发送数据。
- 设备响应数据包:如果有数据可供传输,设备发送 DATA 包,否则返回 NAK。
- 主机确认:若数据正确接收,主机返回 ACK,否则可能请求重传。
- 循环提交新的接收请求:USB 设备通常需要不断提交新的接收请求以持续接收数据。
示例:USB 键盘的接收过程
- 主机周期性发送 IN 请求。
- 键盘设备响应 DATA 包(包含按键信息)。
- 主机解析数据包,将按键信息传递到操作系统。
USB 驱动中的接收处理
以 usbnet.c 为例,当 USB 设备接收到数据包后,内核会调用 rx_complete 回调函数。
static void usbnet_rx_complete(struct urb *urb) {
struct sk_buff *skb = urb->context;
if (urb->status == 0) {
netif_rx(skb); // 将数据包提交给网络协议栈
}
usbnet_rx_submit(dev); // 重新提交接收请求,继续监听数据
}
netif_rx(skb):将接收到的数据包交给网络协议栈处理。
usbnet_rx_submit(dev):重新提交接收请求,确保持续接收数据。
循环调用(rx_submit <-> rx_complete)
- rx_submit 提交一个新的接收请求。
- 当数据到达时,rx_complete 处理数据,并再次调用 rx_submit 继续监听。
- 这个过程形成了一个 数据接收的循环机制,保证数据流不中断。
所以,USB 的数据传输机制本质上是基于主机的轮询(Polling),而非设备主动触发的中断。
为何采用轮询?
- 避免总线冲突:USB 支持多达 127 个设备,轮询机制防止多个设备同时发送数据。
- 联想到 TTCAN
- 当 CAN 节点数目过多时,仲裁冲突显著增加,改用时间轮询的数据传输机制可以有效改善这个问题。
- 电源管理:主机可以控制设备的唤醒和休眠状态,节省能耗。
- 确定性延迟:轮询周期固定,适合实时性要求高的设备(如音频设备)。
对比传统中断机制
- 硬件中断:设备通过中断线(IRQ)直接通知CPU,立即抢占处理(如键盘的PS/2接口)。
- USB“中断”:实为高频轮询,延迟取决于主机轮询间隔(例如1ms),无法做到真正的即时响应。
USB 数据同步(DATA0 / DATA1)
USB 采用 DATA0 / DATA1 标志位进行数据同步:
- 设备发送的数据包会交替使用 DATA0 和 DATA1。
- 主机检查数据包的标志位,如果接收数据重复,会丢弃该数据包,避免数据重复。
主机接收设备发送的完整流程
- 主机发送 IN 令牌包(IN Token Packet)
主机向设备请求数据,表示它准备接收数据。 - 设备发送数据包(DATA0 / DATA1)
设备附带一个 DATA0 / DATA1 标志位,确保数据包的唯一性。 - 主机检查数据并发送响应(ACK / NAK / STALL)
ACK(Acknowledgment):如果数据正确接收,主机返回 ACK,设备继续发送下一个数据包。
NAK(Negative Acknowledgment):如果主机暂时无法处理数据,返回 NAK,设备稍后重试发送。
STALL:如果设备遇到严重错误,主机可能需要重新初始化通信。
主机发送设备接收数据的完整流程
- 主机发送 OUT 令牌包(OUT Token Packet)
告诉设备接下来要发送数据。
此时,设备不会立即回应,主机直接进入下一步。设备仍然可以在数据包后进行确认。 - 主机发送数据包(DATA0 / DATA1)
主机附带一个 DATA0 或 DATA1 标志位,防止数据重复。 - 设备确认(ACK / NAK / STALL)
ACK(Acknowledgment):数据正确接收,设备通知主机可以发送下一个数据包。
NAK(Negative Acknowledgment):设备暂时无法处理,主机稍后重试。
STALL:设备遇到严重错误,主机可能需要重新初始化传输。
如果数据包丢失或损坏怎么办?
- 如果 OUT 令牌包丢失(未到达设备)
设备不会收到数据,自然不会返回 ACK,主机会超时并重新发送。 - 如果数据包(DATA0 / DATA1)丢失或损坏(例如 CRC 校验失败)
设备不会发送 ACK,主机检测到超时后会重发该数据包(相同的 DATA0 / DATA1 标志位)。(解决丢包问题)
设备通过 检查 DATA0 / DATA1 标志位,避免重复处理数据。 - 如果设备的 ACK 丢失了(主机没收到 ACK)
主机会误以为设备没有收到数据,因此会重发相同的 OUT 令牌和数据包。
设备检查 DATA0 / DATA1,如果是重复数据,则丢弃,并重新发送 ACK。(解决接收重复包问题)
USB 错误处理
USB 采用 CRC(循环冗余校验) 检测数据包是否损坏:
- 如果主机检测到数据包错误,不发送 ACK,设备会自动重发。
- 如果主机长时间未收到正确数据,可能会触发超时机制并中断传输。
USB 接收性能优化
在高吞吐量应用(如 USB 网卡、U 盘)中,提高 USB 接收性能很重要,下面是几种方法:
- 使用多 URB 进行并行接收
通过预提交多个 URB,减少等待时间,提高数据吞吐量。 - 增加缓冲区(Buffer)
适当增大接收缓冲区,减少 CPU 频繁处理小包的开销。 - 采用 DMA 传输
许多高性能 USB 控制器支持 DMA,可减少 CPU 负担,提高传输效率。