USB 基础概念与协议简介
引言
因需将搭载Linux系统的开发板作为USB从设备(虚拟串口)使用,故在利用Linux 内核中Gadget驱动设备之前,普及USB的基础概念显得十分必要。此外,USB设备驱动开发过程中,也因借助BUS Hound工具对USB设备进行抓包处理,分析USB设备的接收到的协议帧。
基础概念
1. USB是主从结构:
a. 所有的USB传输,都是从USB主机这方发起;
b. USB设备没有"主动"通知USB主机的能力。
例子:USB鼠标滑动一下立刻产生数据,但是它没有能力通知PC机来读数据,只能被动地等得PC机来读。
2. USB四种传输类型:
a. 控制传输:可靠,时间有保证,比如:USB设备的识别过程
b. 批量传输: 可靠, 时间没有保证, 比如:U盘
c. 中断传输:可靠,实时,比如:USB鼠标
d. 实时传输:不可靠,实时,比如:USB摄像头
3. USB传输的对象:端点(endpoint)
a. 我们说"读U盘"、“写U盘”,可以细化为:把数据写到U盘的端点1,从U盘的端点2里读出数据
b. 除了端点0外,每一个端点只支持一个方向的数据传输;
c. 端点0用于控制传输,既能输出也能输入;
d. 每一个端点都有传输类型,传输方向;
e. 端点是USB设备通信的基本单位,所有通信都是从端点发起的。
4. 程序里说的输入(IN)、输出(OUT), “都是” 基于USB主机的立场说的。
例子:比如鼠标的数据是从鼠标传到PC机, 对应的端点称为"输入端点"
5. 按USB协议栈的层次划分:
a. 一个Host可能有一个或者多个Device;
b. 一个Device可能有一个或者多个Interface;
c. 一个Interface可能有一个或者多个Endpoint。
6. 主要USB 控制器标准:
a. OHCI(Open Host Controller Interface)
b. UHCI(Universal Host Controller Interface)
c. EHCI( Enhanced Host Controller Interface)
d. xHCI(eXtensible Host Controller Interface),xHCI 支持所有速度种类的 USB 设备,xHCI 出现的目的就是为了替换前面三个。
协议简介
1. USB驱动开发常见的五大结构体:
①、设备描述符用于描述 USB 设备的一般信息,USB 设备只有一个设备描述符。设备描述符里面记录了设备的 USB 版本号、设备类型、VID(厂商 ID)、PID(产品 ID)、设备序列号等。
struct usb_device_descriptor {
__u8 bLength; // 此设备描述符长度,18 个字节
__u8 bDescriptorType; // 描述符类型,为 0X01
__u16 bcdUSB; // USB 版本号,BCD 码
__u8 bDeviceClass; // 设备类
__u8 bDeviceSubClass; // 设备子类
__u8 bDeviceProtocol; // 设备协议
__u8 bMaxPacketSize0; // 端点 0 的最大包长度
__u16 idVendor; // 厂商 ID
__u16 idProduct; // 产品 ID
__u16 bcdDevice; // 设备版本号
__u8 iManufacturer; // 厂商信息字符串描述符索引值
__u8 iProduct; // 产品信息字符串描述符索引值
__u8 iSerialNumber; // 产品序列号字符串描述符索引值
__u8 bNumConfigurations; // 可能的配置描述符数目
} __attribute__ ((packed));
②、设备描述符的 bNumConfigurations 域定义了一个 USB 设备的配置描述符数量,一个 USB设备至少有一个配置描述符。配置描述符描述了设备可提供的接口(Interface)数量、配置编号、供电信息等。
struct usb_config_descriptor {
__u8 bLength; // 此配置描述符长度,9 个字节
__u8 bDescriptorType; // 配置描述符类型,为 0X02
__le16 wTotalLength; // 整个配置信息总长度(包括配置、接口、端点、设备类和厂家定义的描述符)
__u8 bNumInterfaces; // 此配置所支持的接口数
__u8 bConfigurationValue; // 该配置的值,一个设备支持多种配置,通过配置值来区分不同的配置。
__u8 iConfiguration; // 描述此配置的字符串描述索引
__u8 bmAttributes; // 该设备的属性: D7:保留 D6:自给电源 D5:远程唤醒 D4:0:保留
__u8 bMaxPower; // 此配置下所需的总线电流(单位 2mA)
} __attribute__ ((packed));
③、字符串描述符是可选的,字符串描述符用于描述一些方便人们阅读的信息,比如制造商、设备名称啥的。如果一个设备没有字符串描述符,那么其他描述符中和字符串有关的索引值都必须为 0。
struct usb_string_descriptor {
__u8 bLength; // 此字符串描述符长度
__u8 bDescriptorType; // 字符串描述符类型,为 0X03
__le16 wData[1]; /* UTF-16LE encoded */
} __attribute__ ((packed));
④、配置描述符中指定了该配置下的接口数量,配置可以提供一个或多个接口,接口描述符用于描述接口属性。接口描述符中一般记录接口编号、接口对应的端点数量、接口所述的类等。
struct usb_interface_descriptor {
__u8 bLength; // 此接口描述符长度,9 个字节
__u8 bDescriptorType; // 描述符类型,为 0X04
__u8 bInterfaceNumber; // 当前接口编号,从 0 开始
__u8 bAlternateSetting; // 当前接口备用编号
__u8 bNumEndpoints; // 当前接口的端点数量
__u8 bInterfaceClass; // 当前接口所属的类
__u8 bInterfaceSubClass; // 当前接口所属的子类
__u8 bInterfaceProtocol; // 当前接口所使用的协议
__u8 iInterface; // 当前接口字符串的索引值
} __attribute__ ((packed));
⑤、接口描述符定义了其端点数量,端点是设备与主机之间进行数据传输的逻辑接口,除了端点 0 是双向端口,其他的端口都是单向的。端点描述符描述了树传输类型、方向、数据包大小、端点号等信息。
struct usb_endpoint_descriptor {
__u8 bLength; // 此端点描述符长度,7 个字节
__u8 bDescriptorType; // 描述符类型,为 0X05
__u8 bEndpointAddress; // 端点地址和方向: bit3:0:端点号 bit6:4:保留,为零。 bit7:方向,0 输出端点(主机到设备),1 输入端点(设备到主机)
__u8 bmAttributes; // 端点属性,bit1:0 表示传输类型: 00:控制传输 01:同步传输 10:批量传输 11:中断传输 其他位保留
__le16 wMaxPacketSize; // 端点能发送或接收的最大数据包长度
__u8 bInterval; // 端点数据传输中周期时间间隙值,此域对于批量传输和控制传输无效,同步传输的话此域必须为 1ms,中断传输此域可以设置 1ms~255ms。
/* NOTE: these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8 bRefresh;
__u8 bSynchAddress;
} __attribute__ ((packed));
2. USB 枚举:
当 USB 设备与 USB 主机连接以后主机就会对 USB 设备进行枚举,通过枚举来获取设备的描述符信息,主机得到这些信息以后就知道该加载什么样的驱动、如何进行通信等。USB 枚举过程如下:
①、第一回合,当 USB 主机检测到 USB 设备插入以后机会发出总线复位信号来复位设备。USB 设备复位完成以后地址为 0,主机向地址 0 的端点 0 发送数据,请求设备的描述符。设备得到请求以后就会按照主机的要求将设备描述符发送给主机,主机得到设备发送过来的设备描述符以后,如果确认无误就会向设备返回一个确认数据包(ACK)。
②、第二回合,主机再次复位设备,进入地址设置阶段。 主机向地址 0 的端点 0 发送设置地址请求数据包,新的设备地址就包含在这个数据包中,因此没有数据过程。设备进入状态过程,等待主机请求状态返回,收到以后设备就会向主机发送一个 0 字节状态数据包,表明设备已经设置好地址了,主机收到这个 0 字节状态数据包以后会返回一个确认包(ACK)。设备收到主机发送的 ACK 包以后就会使用这个新的设备地址,至此设备就得到了一个唯一的地址。
③、第三回合,主机向新的设备地址端点 0 发送请求设备描述符数据包,这一次主机要获取整个设备描述符,一共是 18 个字节。
④、和第③步类似,接下来依次获取配置描述符、配置集合、字符串描述符等等。
USB 抓包工具(BUS Hound)
一个便利的USB抓帧工具,能让我们在USB驱动开发过程中事半功倍。以下为BUS Hound 抓取数据帧的界面: