【Linux驱动】USB协议

🐱作者:一只大喵咪1201
🐱专栏:《Linux驱动》
🔥格言:你只管努力,剩下的交给时间!
图

😼认识USB

当我们把一个USB设备,比如U盘插入刀电脑中以后,就会弹出发现xxx设备,还会跳出一个对话框,提示我们安装驱动程序。这里有几个问题需要弄明白:

  1. 为什么一接入USB设备,电脑就能发现它?

图
如上图所示,USB接口有四根线,在PC的USB内部,这四根线的D+和D-各接一个15K的下拉电阻,在USB设备没有插入时,这两根线处于低电平状态。

在USB设备内部,D+或者D-接了一个1.5K的上拉电阻,当USB设备插入以后,PC和USB设备的这四根线就相连了。

  • 此时D+或者D-就变成了高电平。
  • USB控制器检测到电平变化以后就知道有USB设备插入了。
  1. USB设备插到电脑上去,插入的是PC的什么模块?

图
如上图所示是USB总线的拓扑图,USB设备插入电脑以后,是插入到PC的USB控制器,这是一个内嵌的root hub

  • USB Host:它跟处理器CPU相连,处理器通过USB Host跟各类USB设备通信,USB Host中集成有一个root hub。
  • USB Device:这分为两类设备:
    • Hub:用来扩展USB接口,就像是扩展坞
    • Function:就是普通的USB设备,比如U盘、声卡等

可以看到,每一级Hub既可以连接一个USB设备,也可以连接一个Hub,但是Hub的连接是有限制的,就像扩展坞不能无限制的在扩展坞上继续接扩展坞。

  • 包括USB Host在内的RootHub,最多有6级Hub。
  • 第7层就必须接USB设备了,不能再接Hub了。
  1. 既然还没有USB的驱动程序,为何能知道这是一个U盘,从而让我们安装U盘的驱动程序呢?

在PC的系统中,存在很多USB设备的驱动程序,在没有相应USB设的时候没有安装,只是放在那里。

  • 接入USB设备后,USB总线驱动程序能识别到这个USB设备。
  • 识别到以后再提示我们安装存放在系统中的对应USB设备的驱动程序。
  1. USB设备种类非常多,为什么一接入电脑,就能识别出来它的种类?

PC和USB设备都遵循USB协议的规范,在USB设备插入PC以后,PC会向USB设备发出你是什么设备的询问,USB设备就会回答我是xxx,并且询问和回答的格式是固定的。

  • 这个过程就是PC在获取USB设备的描述符信息。
  1. 电脑上一般都会插着很多USB设备,怎么分辨它们?
  • 每一个USB设备在刚插入PC时,默认的地址编号是0,PC就和地址编号0进行通信。
  • PC会再给不同的USB设备分配一个唯一的地址编号,之后就用分配的这个地址编号和USB设备之间进行通信。

软件框架:
图
如上图所示操作USB设备的软件框架:

  • 应用层可以通过USB设备驱动,去使用USB控制器驱动中的方法去控制芯片中的USB控制器,进而去操作USB设备。
  • 应用层也可以跳过USB设备驱动,直接去使用USB控制器驱动中的方法去控制芯片中的USB控制器,进而去操作USB设备。

🙀USB的电气信号

图
如上图所示USB设备和USB的Hub直接的电路图,USB设备和Hub之间的D+和D-两根线直接相连:

  • 如果USB设备的D+接1.5K的上拉电阻,那么USB设备插入以后,Hub端的D+就会成为高电平,从而表明这是一个全速或者高速的USB设备。
  • 如果USB设备的D-接1.5K的上拉电阻,那么USB设备插入以后,Hub端的D-就会成为高电平,从而表明这是一个低速的USB设备。
  • USB设备不会同时具备高速和低速,但是可以同时具备者高速和全速或者是低速和全速。
  • USB连接线有4条:5V、D+、D-、GND。
  • 数据线D+、D-,只能表示4种状态。

USB协议中,很巧妙地使用D+和D-这两条线路实现了空闲(Idle)开始(SOP)传输数据(Data)结束(EOP) 等功能。

低速和全速信号电平

这里本喵仅介绍低速和全速信号电平,高速的自己类推即可:

图
如上图所示是USB2.0中的低速和全速信号电平表:

  • Differential “1”:这是差分信号1,D+线高电平,D-线低电平。
  • Differential “2”:这是差分信号2,D+线低电平,D-线高电平。
  • SE0:D+线和D-线都是低电平。
  • SE1:D+线和D-线都是高电平。

以上就是两根线对应的四种状态,所有信号的都是由这四种状态来表示的:

  • J状态:低速设备对应差分信号0,全速设备对应差分信号1。
  • K状态:低速设备对应差分信号1,全速设备对应差分信号0。
  • 空闲状态(Idle):低速和全速设备对应的都是J状态。

图
如上图所示,对于高速设备:

  • 它先作为全速设备被识别出来,然后再被识别为高速设备。
  • 识别出全速设备后,Hub端口发出SE0复位信号。
  • USB设备检测到SE0信号号,就会回应一个信号来表明自己是否是高速设备。

工作于高速模式时,D+的上拉电阻是断开的,所以对于工作于高速模式的USB设备,无法通过D+的引脚电平变化监测到它已经断开。

  • 工作于高速模式的设备,D+、D-两边有45欧姆的下拉电阻,用来消除反射信号。

图
如上图所示Hub内部电路图:

  • 当断开高速设备后,Hub发出信号,得到的反射信号无法衰减,Hub监测到这些信号后就知道高速设备已经断开。

低速和全速的数据信号

图
如上图所示USB协议数据格式:

  1. SOP:Start Of Packet,也就是起始信号。

图
如上图所示,起初D+和D-两根线是处于J状态的,当这两根线变成K状态以后,表明Hub发起USB传输。

  1. SYNC:同步信号。
  • 对于串口协议,需要通信双方约定好波特率,一方按照这个速度去改变TX数据线上的电平,另一方按照这个速度去读取RX线上的电平,从而进行通信。
  • 对于I2C和SPI协议,通信双方之间有一根时钟线,时钟线每产生一个脉冲,发送方改变一次SDA或者MOSI线上的电平,与此同时,接收方读取对应线上的电平,从而进行通信。

无论是是同步通信还是异步通信,都需要双方知道改变数据线上电平的速率。

  • USB协议中的同步信号就是Hub和USB设备在进行通信速率的交流。

图
如上图所示,SYNC格式为3对KJ信号外加2个K信号。

  • USB设备根据SYNC信号速率,自行识别并且记录下这个速度,这一次传输就按照这个速度去读取或者改变D+和D-线上的电平。
  • 这样一来,通信双方就完成了通信速率的交流。
  1. Packet Content:包内容。

后面会详细讲解USB数据包的内容。

  1. EOP:End Of Packet,也就是结束信号。

图
如上图所示EOP信号时序:

  • 由数据的发送方先设置D+和D-为SE0状态,并持续2位的时间。
  • 然后再设置位J状态,并持续1位的时间。
  • 最后将D+和D-变为高阻态,此时D+和D-进入Idle空闲状态。

NRZI与位填充

图
如上图所示每一个比特位的电平信号:

  • 对于串口或者I2C等协议,低电平用0表示,高电平用1表示。
  • 对于USB协议,对于数据0,保持J或者K状态不变,对于数据1,反转波形,由J状态变成K状态或者由K状态变成J状态。
  • USB协议采用的是反向不为零编码方式来传输每一位数据。

但是此时有一个问题:

  • 如果传输非常多位的0,D+和D-不断在J状态和K状态之间切换,接收方识别到一次切换就知道这是一个0,并且可以调整频率,同步接收。
  • 但是如果传输的是多位的1,D+和D-会保持J状态或者K状态不变,即使在同步信号中双方统一了传输速率,但是由于晶振频率等原因也会出现误差。

比如传输100个1时,如果接收方稍有偏差,就可能认为接收到了99位1、101位1。对于这种情况,USB采用了Bit-Stuffing位填充的处理方式:

  • 在连续发送6个1后面会插入1个0,强制翻转发送信号,从而让接收方调整频率,同步接收。
  • 而接收方在接收时只要接收到连续的6个1后,直接将后面的0舍弃即可得到正确的数据。

😼USB协议

图
如上图,一个USB物理设备里面可能有多个逻辑设备,Host可以外接多个逻辑设备。

  • 比如我们的USB摄像头,它既有视频流功能,也有音频流功能。
  • 视频流和音频流这两个功能在Host看来,这就是两个逻辑设备。

Host操作的是不同的逻辑设备,即使这些逻辑设备同属于一个物理设备。

🙀包格式

图
如上图所示USB数据格式,SOP,SYNC,以及EOP,前面在电气信号中本喵已经介绍了,还剩下最重要的Packet Content 包内容

  • 一个完整的USB数据包就是由以上几部分组成的。
  • SOP,SYNC等四部分被称为一个一个的域。

PID域:

图
Packet Content中的PID就是令牌,PID有八位,用来表明这是一个什么类型的包:

  • 前4位表示PID,也就是表明数据包的类型。
  • 后4位是对应位的取反,接收方发现后4位不是前4位的取反的话,就认为发生了错误。

图
如上图所示,包的类型分为四类,PID的取值有16种:

  1. 令牌包(Token):四位中的低两位是01B,表明这是一个令牌包,又分为四种令牌包:
  • OUT:设置设备将要输出数据。
  • IN:通知设备将要读取数据。
  • SOF:通知设备这是一个帧起始包。
  • STEUP:通知设备将要开始一个控制传输。
  1. 数据包(Data):四位中的低两位是11B,表明这是一个数据包,又分为四种数据包:
  • 有DATA0、DATA1等四种类型的数据包。
  1. 握手包(Handshake):四位中的低两位是10B,表明这是一个握手包,又分为四种握手包:
  • ACK:确认应答。
  • NAK:不确认应答。
  • STALL:挂起应答。
  • NYET:未准备好应答。
  1. 特殊包(Special):四位中的低两位是00B,表明这是一个特殊类的包,又分为四种:
  • 一般情况下用不到这类包,本喵不做讲解。

令牌包

地址域:

图
如上图所示,USB设备的地址有7位,地址用来标识唯一的USB设备。

图
如上图所示,USB设备的端点号有4位:

  • 端点号可以理解为USB设备中的寄存器。

7位USB地址和4位端点总共11位,组成地址域,也被叫做帧号域。

校验域:

这里是一个CRC校验码,用来校对通信的数据是否正确。

完整令牌包:

图
如上图所示是一个完整令牌包的格式:

  • SOP启动传输后,SYNC同步通信速率。
  • 在Packet Content里的PID表明这是一个输出还是输入等类型的令牌包。
  • 令牌包后面必然跟的是USB设备的地址和端点号,表明要和访问的地址。
  • 然后是一个CRC校验码。
  • 最后是一个EOP结束信号。

数据包

Host使用OUT、IN、SETUP等令牌包来通知设备:我要传输数据了,真正的数据是通过数据包进行传输的。

Host和设备都会维护自己的数据包切换机制,当数据包成功发送或者接收时,数据包类型要进行切换。

当检测到对方使用的数据包类型不对时,USB系统认为发生了错误:

  • Host发送DATA0给设备,设备返回ACK表示成功接收,设备期待下一个数据是DATA1
  • 但是Host没有接收到ACK,Host认为数据没有发送成功,Host继续使用DATA0发送上一次的数据
  • 设备再次接收到DATA0数据包,它就知道:哦,这是重传的数据包。

图
如上图所示是完整的数据包:

  • SOP和SYNC和令牌包一样。
  • PID中的值是DATA0或者DATA1,表明这是一个数据包。
  • 接下来就是真正要传输的数据了。
  • 后的CRC和EOP也是和令牌包一样的意义。

握手包

  • ACK:数据接收方用来回复发送方,表示正确接收到了数据并且有足够的空间保存数据。
  • NAK:Host发送数据给设备时,设备可以回应NAK表示"我还没准备好,没办法接收数据";Host想读取设备的数据时,设备可以回复NAK表示"我没有数据给你"。
  • STALL:表示发生了错误,比如设备无法执行这个请求(不支持该端点等待)、端点已经挂起。设备返回STALL后,需要主机进行干预才能解除STALL状态。
  • NYET:仅适用于高速设备。Host可以发出PING包用来确认设备有数据,设备可以回应NYET表示"还没呢"。Hub也可以回应NYET表示低速/全速传输还没完结。

TU
如上图所示是完整的握手包:

  • SOP和SYNC作用和前面一样。
  • PID中是ACK或NAK等应答信号。
  • 没有数据域或者帧域,也没有CRC校验码。
  • EOP和前面一样。

😼传输类型

USB传输的基本单位是包(Packet),包的类型由PID表示。一个单纯的包,是无法传输完整的数据。

图

如上图所示,完整的数据传输,需要涉及多个包:令牌包、数据包、握手包,这个完整的数据传输过程,被称为事务(Transaction)。

有些事务需要握手包,有些事务不需要握手包,有些事务可以传输很大的数据,有些事务只能传输小量数据,所以事务又分为四类:

  • 批量事务:用来传输大量的数据,数据的正确性有保证,时效没有保证。
  • 中断事务:用来传输周期性的、小量的数据,数据的正确性和时效都有保证。
  • 实时事务:用来传输实时数据,数据的正确性没有保证,时效有保证。
  • 建立事务:跟批量事务类似,只不过令牌包是SETUP令牌包。
  • 传输和事务其实是不一样的,但是在USB手册的中文翻译版中很多地方都将其归为了一类。

有四类传输(Transfer):

  • 批量传输:就是使用批量事务实现数据传输,比如U盘。
  • 中断传输:就是使用中断事务实现数据传输,比如鼠标。
  • 实时传输:就是使用实时事务实现数据传输,比如摄像头。
  • 控制传输:由建立事务、批量事务组成,所有的USB设备都必须支持控制传输,用于 识别/枚举 USB设备。
  • 对于批量传输、中断传输、实时传输,它们分别由一个事务组成,不再细分为若干个过程。
  • 但是控制传输由多个事务组成,这些事务分别处于3个过程:建立过程(stage)、数据过程(stage)、状态过程(stage)。每一个过程都是一个或者多个事务。

所以在USB协议中:

  • bit组成域(Field)
  • 域组成包(Packet)
  • 包组成事务(Transaction)
  • 事务组成传输(Transfer)

一个完整事务涉及到的三个阶段:

  • 令牌阶段(Token phase):由令牌包实现
  • 数据阶段(Data phase):由数据包实现
  • 握手阶段(Handshake phase):由握手包实现

🙀批量传输

图
如上图所示USB手册中的批量传输架构图,各个矩形框就对应一个完整的包:

  • 批量传输用批量事务来实现,用于传输大量的数据,数据的正确性有保证,时效没有保证。
  • 批量事务由3个阶段(phase)组成:令牌阶段、数据阶段、握手阶段。每个阶段都是一个完整的包,含有SOP、SYNC、PID、EOP。
  • 在这里,批量传输就等于批量事务。

图
如上图所示是一次正确的批量输入事务。

图
如上图所示是一次正确的批量输出事务。

🙀中断传输

图
如上图所示是USB手册的中断传输架构,一个矩形框就对应一个完整的包:

  • 中断传输用中断事务来实现,用于传输小量的、周期性的数据,数据的正确性和时效都有保证。
  • 中断事务由3个阶段(phase)组成:令牌阶段、数据阶段、握手阶段。每个阶段都是一个完整的包,含有SOP、SYNC、PID、EOP。
  • 相比于批量传输来说,中断传输中没有PING事务,而且握手包中也少了一个NYET包。

中断事务跟批量事务非常类似,Host使用它来周期性地读数据、写数据,以鼠标为例:

  • 我们需要及时获得鼠标的数据,不及时的话你会感觉鼠标很迟钝。

  • 但是USB协议中并没有中断功能,它使用周期性的读、写来实现及时性。具体过程如下:

    • Host每隔n毫秒发出一个IN令牌包。
    • 鼠标有数据的话,发出DATA0或DATA1数据包给Host;鼠标没有数据的话,发出NAK给Host。

中断事务的优先级比批量事务更高,它要求实时性,而批量事务不要求实时性。

🙀实时传输

图
如上图所示是实时传输的框架,一个矩形框对应一个包:

  • 实时传输用使用事务来实现,用于传输实时数据,对数据的正确性没有要求。
  • 实时事务由2个阶段(phase)组成:令牌阶段、数据阶段。每个阶段都是一个完整的包,含有SOP、SYNC、PID、EOP。
  • 实时事务不需要握手阶段,也就是不需要应答信号。

比如为了传输摄像头的实时数据,偶尔的数据错误是可以忍受的,大不了出现短暂的花屏。如果为了解决花屏而重传数据,那就会导致后续画面被推迟,实时性无法得到保证。

实时事务跟中断事务非常类似,Host也会周期性的发起实时事务,主要区别在于:

  • 实时事务不要求准确性,没有握手阶段
  • 实时事务传输的数据量比较大,中断事务传输的数据量比较小

🙀控制传输

图
如上图所示是USB手册中的控制传输框架,一个矩形框对应一个事务,而不是一个

在使用批量传输时,使用IN令牌包或OUT令牌包表示数据传输方向。控制传输的令牌包永远是SETUP:

  • 发出SETUP令牌包后,还要发出DATA0数据包。
  • 根据数据阶段的内容来确定后续是读数据,还是写数据。这个过程称为建立事务(SETUP Transaction)。

图

如上图所示是建立事务对应的框图。

但是控制传输由多个事务组成,这些事务分别处于3个过程:建立过程(stage)、数据过程(stage)、状态过程(stage)。

  • 建立过程(stage),使用SETUP事务:Host发出SETUP令牌包、DATA0数据包、得到ACK握手包。
  • 数据过程(stage),使用一个或者多个批量事务
    • 对于输出:Host发出OUT令牌包,发出DATA0、DATA1数据包、得到ACK握手包
    • 对于输入:Host发出IN令牌包,读到DATA0、DATA1数据包、发出ACK握手包
  • 状态过程(stage),使用批量事务:
    • 对于输出:Host发出IN令牌包,读到DATA1数据包,发出ACK握手包。
    • 对于输入:Host发出OUT令牌包,发出DATA1数据包,等待ACK握手包。
    • 状态过程的批量事务中的数据阶段,数据大小是0,只是为了获得状态。

😼设备描述符

对于一个USB设备,它可以多种配置(Configuration)。比如4G上网卡就有2种配置:

  • U盘、上网卡。第1次把4G上网卡插入电脑时,它是一个U盘,可以安装里面的驱动程序。
  • 装好程序后,把它再次插入电脑,它就是一个上网卡。
  • 驱动程序可以选择让它工作于哪种配置,同一时间只能有一种配置。大多数的USB设备只有一种配置。
    图

如上图所示,在Linux中使用lsusb -v查看USB设备的描述符信息,一个USB设备:

  • 只有一个设备描述符:用来表示设备的ID、它有多少个配置、它的端点0一次最大能传输多少字节数据。
  • 可能有多个配置描述符:用来表示它有多少个接口、供电方式、最大电流。
  • 一个配置描述符下面,可能有多个接口描述符:用来表示它是哪类接口、有几个设置(Setting)、有几个端点。
  • 一个接口描述符下面,可能有多个端点描述符:用来表示端点号、方向(IN/OUT)、类型(批量/中断/同步)。

图
如上图所示是描述符的组成关系。

  • 描述符信息是USB设备中内嵌的,当USB设备插入以后,Hub会发起控制传输来获取描述符信息。
  • 每一个接口就是一个逻辑设备,Hub会将其认为是一个设备。

在setup事务中:

  • SETUP令牌包:用来通知设备,要开始控制传输了。
  • DATA0数据包:它含有固定的格式,用来告诉设备是读还是写、读什么、写什么。

图
如上图所示,Host通过DATA0数据包发送8字节数据给设备,它的格式如上。

图
数据阶段中的8个字节的常用值如上图所示,USB设备根据这8个字节就可以知道Host想要控制什么。

  • bRequest这列中的值是一个个宏,表示着不同的意义。

图
如上图所示是这些宏的取值。

🙀枚举过程

图

如上图所示是USB设备插入以后的设备状态变化过程,也就是枚举过程:

  • 插入以后,Hub识别出USB设备,进行相应的驱动配置。
  • 再获取默认设备描述符,此时是和端点0进行通信。
  • 再给USB设备设置新的地址,之后使用新地址进行通信。
  • 再次获取设备描述符,以及接口配置描述符。
  • 最后再设置USB设备的配置,即使只有一个配置也要设置。

下面本喵使用一个USB数据捕获工具来看看USB设备的枚举过程,它可以详细的显示出USB数据。
图
如上图所示,是插入USB设备以后,第一次获取设备描述符:

  • 在建立过程中,先出建立事务,在数据阶段发送一个8字节的数据表明要GET_DESCRIPTOR,也就是获取设备描述符。
  • 在数据过程中,先发出一个读取数据的批量事务来获得设备描述符,但是此时USB设备还没有准备好,给出一个NAK应答。
    • Host再次发起一个读取数据的批量事务,这次得到了USB设备发来的18个字节的设备描述符。
  • 在状态过程中,发出一个批量事务,但是数据阶段中的数据是0字节,表明这是一个状态过程。
  • 这个控制传输中,都是在和USB设备的ADDR0默认地址和ENDP0进行通信。

图
如上图所示,这是Host给USB设备设置新的地址:

  • 在建立过程中,先发起建立事务,在数据阶段发出SET_ADDRESS,表明要设置地址,并且将要设置的新地址也在这8个字节中发送过程。
  • 在状态过程中,读取USB设备的应答,数据节点的字节数为0,确定地址是否设置成功。
  • 这个过程中并没有数据阶段,因为设置的新地址在建立过程中的8个字节里就发送了过去。

图
如上图所示,是Host根据设置的新地址获取设备描述符:

  • 在建立过程中,使用新地址1和ENDP0向USB设备获取设备描述符。
  • 在数据过程中,读取USB设备的设备描述符,大小是18个自己。
  • 在状态过程中,发送字节数为0的批量事务表示收到设备描述符。
  • 在建立过程的STEP事务中,数据阶段发送的8字节数据就是前面本喵列举的表中的数据组合。

😼总结

并没有涉及到Linux驱动,仅仅是讲解了USB协议的电平信号,协议格式,传输类型,以及它的枚举过程。

相关推荐

  1. LinuxUSB驱动框架-USB鼠标驱动源码分析(5)

    2024-05-03 11:32:03       29 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-05-03 11:32:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-05-03 11:32:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-05-03 11:32:03       87 阅读
  4. Python语言-面向对象

    2024-05-03 11:32:03       96 阅读

热门阅读

  1. 力扣67 二进制求和 C语言

    2024-05-03 11:32:03       35 阅读
  2. Vue入门到关门之第三方框架elementui

    2024-05-03 11:32:03       31 阅读
  3. 什么是oneflow

    2024-05-03 11:32:03       41 阅读
  4. 70.爬楼梯

    2024-05-03 11:32:03       37 阅读
  5. Bug优先级定义

    2024-05-03 11:32:03       34 阅读
  6. windows下安装Chronograf的具体步骤

    2024-05-03 11:32:03       37 阅读
  7. 【产品经理修炼之道】- 需求分析和实现

    2024-05-03 11:32:03       33 阅读
  8. LeetCode——滑动窗口

    2024-05-03 11:32:03       34 阅读
  9. centos 中使用 kubekey 安装 k8s v1.22.12 支持 GPU 调用

    2024-05-03 11:32:03       35 阅读
  10. Django框架之模型层

    2024-05-03 11:32:03       25 阅读