Linux USB 基础概念与协议简介

一月 12, 2025 更新: 二月 25, 2025 [网络与安全] #Linux #obsidian #协议与标准

Linux USB 基础概念与协议简介

引言

  因需将搭载Linux系统的开发板作为USB从设备(虚拟串口)使用,故在利用Linux 内核中Gadget驱动设备之前,普及USB的基础概念显得十分必要。此外,USB设备驱动开发过程中,也因借助BUS Hound工具对USB设备进行抓包处理,分析USB设备的接收到的协议帧。

基础概念

  1. USB是主从结构:   a. 所有的USB传输,都是从USB主机这方发起;   b. USB设备没有"主动"通知USB主机的能力。 例子:USB鼠标滑动一下立刻产生数据,但是它没有能力通知PC机来读数据,只能被动地等得PC机来读。

  1. USB四种传输类型:   a. 控制传输:可靠,时间有保证,比如:USB设备的识别过程   b. 批量传输: 可靠, 时间没有保证, 比如:U盘   c. 中断传输:可靠,实时,比如:USB鼠标   d. 实时传输:不可靠,实时,比如:USB摄像头

  1. USB传输的对象:端点(endpoint)   a. 我们说"读U盘"、“写U盘”,可以细化为:把数据写到U盘的端点1,从U盘的端点2里读出数据   b. 除了端点0外,每一个端点只支持一个方向的数据传输;   c. 端点0用于控制传输,既能输出也能输入;   d. 每一个端点都有传输类型,传输方向;   e. 端点是USB设备通信的基本单位,所有通信都是从端点发起的。

  1. 程序里说的输入(IN)、输出(OUT), “都是” 基于USB主机的立场说的。 例子:比如鼠标的数据是从鼠标传到PC机, 对应的端点称为"输入端点"

  1. 按USB协议栈的层次划分:   a. 一个Host可能有一个或者多个Device;   b. 一个Device可能有一个或者多个Interface;   c. 一个Interface可能有一个或者多个Endpoint。

  1. 主要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));

  1. 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 抓取数据帧的界面:

在这里插入图片描述