Linux-USB驱动笔记(四)--协议解析
1、前言
USB基础知识在前三篇笔记中。
2、 USB协议解析
USB 协议中有很多的基础概念,我们来总结一下吧。
之前在基础知识中,我们介绍过设备,配置,接口,端点等概念,它在Linux中有4个对应的结构体来表示。同时也有对应的结构体来描述USB设备。
内核版本:4.20.12
- USB整体框架如下:
USB驱动分为主机侧和设备侧,主机侧和设备侧的USB控制器分别称为主机控制器(Host Controller)和USB设备控制器(UDC)。USB核心层向上下提供编程接口,维护整个系统的USB信息,完成热插拔控制,数据传输控制。
- 主机侧:
从上图看,我们需要实现两个驱动,USB主机控制器驱动和USB设备驱动。
USB主机控制器驱动:控制插入的USB设备
USB设备驱动:控制具体USB设备和主机如何通信
- 设备侧:
设备侧也需要实现两部分驱动,UDC驱动和Gadget Function驱动。
UDC驱动:控制USB设备和主机的通信
Gadget Function驱动:控制USB设备功能的实现
2.1、USB 描述符
顾名思义,USB 描述符就是用来描述 USB 信息的,描述符就是一串按照一定规则构建的字符串,USB 设备使用描述符来向主机报告自己的相关属性信息,常用的描述符如下表所示:
描述符类型 | 名字 | 值 |
---|---|---|
Device Descriptor | 设备描述符 | 1 |
Configuration Descriptor | 配置描述符 | 2 |
String Descriptor | 字符串描述符 | 3 |
Interface Descriptor | 接口字符串 | 4 |
Endpoint Descriptor | 端点描述符 | 5 |
USB设备代码如下:
//表示一个USB设备
struct usb_device {
int devnum; //设备号,USB总线上的地址
char devpath[16]; //消息中使用的设备ID字符串,例如/port/...
u32 route; //树拓扑(16进制字符串),用于xHCI
enum usb_device_state state; //设备状态
enum usb_device_speed speed; //设备速率 high/full/low
unsigned int rx_lanes; //正在使用的RX线路数
unsigned int tx_lanes; //正在使用的TX线路数
//为了让高速USB设备兼容低速设备
//通过使用TT(Transaction Translator)--高速USB中的转换电路
struct usb_tt *tt; //事务转换信息,用于低速/全速设备,高速HUB
int ttport; //tt hub上的设备口
unsigned int toggle[2]; //每个端点使用一个bit表示输入输出状态
//([0] = 输入, [1] = 输出)
struct usb_device *parent; //父hub,如果是roothub,就是NULL
struct usb_bus *bus; //设备所在的总线
struct usb_host_endpoint ep0; //端点0数据 (默认用来控制)
struct device dev; //设备
struct usb_device_descriptor descriptor; //usb设备描述符
struct usb_host_bos *bos; //USB设备BOS描述符集
struct usb_host_config *config; //所有的设备配置
struct usb_host_config *actconfig; //当前激活的配置
struct usb_host_endpoint *ep_in[16]; //输入端点数组
struct usb_host_endpoint *ep_out[16];//输出端点数组
char **rawdescriptors; //每个配置的源描述符
unsigned short bus_mA; //总线提供的电流
u8 portnum; //父port号
u8 level; //USB HUB上一级的数量
unsigned can_submit:1; //URB可以被提交
unsigned persist_enabled:1;
unsigned have_langid:1;
unsigned authorized:1;
unsigned authenticated:1;
unsigned wusb:1;
unsigned lpm_capable:1;
unsigned usb2_hw_lpm_capable:1;
unsigned usb2_hw_lpm_besl_capable:1;
unsigned usb2_hw_lpm_enabled:1;
unsigned usb2_hw_lpm_allowed:1;
unsigned usb3_lpm_u1_enabled:1;
unsigned usb3_lpm_u2_enabled:1;
int string_langid;
/* static strings from the device */
char *product; //产品描述字符串
char *manufacturer; //厂家描述字符串
char *serial; //序列号字符串
struct list_head filelist; //该设备打开的usb文件系统文件
int maxchild; //hub可以连接的最大port数
u32 quirks;
atomic_t urbnum; //整个设备URB提交总数
unsigned long active_duration; //激活的总时间
#ifdef CONFIG_PM //电源管理
unsigned long connect_time;
unsigned do_remote_wakeup:1;
unsigned reset_resume:1;
unsigned port_is_suspended:1;
#endif
struct wusb_dev *wusb_dev;
int slot_id;
enum usb_device_removable removable;
struct usb2_lpm_parameters l1_params;
struct usb3_lpm_parameters u1_params;
struct usb3_lpm_parameters u2_params;
unsigned lpm_disable_count;
u16 hub_delay;
};
为了更清晰的了解设备,配置,接口,端点等结构体信息,我在Ubuntu上插入一个U盘,然后使用lsusb -v去查看该usb设备的设备描述符等信息。
2.1.1、设备描述符
设备描述符用于描述 USB 设备的一般信息,USB 设备只有一个设备描述符。设备描述符里面记录了设备的 USB 版本号、设备类型、VID(厂商 ID)、PID(产品 ID)、设备序列号等。设备描述符结构如下表所示:
偏移 | 域 | 大小(B) | 值类型 | 描述 |
---|---|---|---|---|
0 | bLength | 1 | 数字 | 此设备描述符长度,18 个字节 |
1 | bDescriptorType | 1 | 常量 | 描述符类型,为 0X01 |
2 | bcdUSB | 2 | BCD 码 | USB 版本号 |
4 | bDeviceClass | 1 | 类 | 设备类 |
5 | bDeviceSubClass | 1 | 子类 | 设备子类 |
6 | bDevicePortocol | 1 | 协议 | 设备协议 |
7 | bMaxPacketSize0 | 1 | 数字 | 端点 0 的最大包长度 |
8 | idVendor | 2 | ID | 厂商 ID |
10 | idProduct | 2 | ID | 产品 ID |
12 | bcdDevice | 2 | BCD 码 | 设备版本号 |
14 | iManufacturer | 1 | 索引 | 厂商信息字符串描述符索引值 |
15 | iProduct | 1 | 索引 | 产品信息字符串描述符索引值 |
16 | iSerialNumber | 1 | 索引 | 产品序列号字符串描述符索引值 |
17 | bNumConfigurations | 1 | 索引 | 可能的配置描述符数目 |
具体代码如下:
//\include\uapi\linux\usb\ch9.h
struct usb_device_descriptor {
__u8 bLength; //描述符长度
__u8 bDescriptorType; //描述符类型
__le16 bcdUSB; //USB总线版本号
__u8 bDeviceClass; //由USB-IF分配的设备类代码
__u8 bDeviceSubClass; //由USB-IF分配的USB子类代码
__u8 bDeviceProtocol; //由USB-IF分配协议代码
__u8 bMaxPacketSize0; //端点0最大包大小,只有8、16、32或64有效。
__le16 idVendor; //厂商ID
__le16 idProduct; //产品ID
__le16 bcdDevice; //设备出厂编号
__u8 iManufacturer; //描述厂商的字符串描述符索引
__u8 iProduct; //描述产品的字符串描述符索引
__u8 iSerialNumber; //描述产品序列号的字符串描述符索引
__u8 bNumConfigurations; //配置描述符数量
} __attribute__ ((packed));
2.1.2、配置描述符
设备描述符的 bNumConfigurations 域定义了一个 USB 设备的配置描述符数量,一个 USB设备至少有一个配置描述符。配置描述符描述了设备可提供的接口(Interface)数量、配置编号、供电信息等,配置描述符结构如下表所示:
偏移 | 域 | 大小(B) | 值类型 | 描述 |
---|---|---|---|---|
0 | bLength | 1 | 数字 | 此配置描述符长度,9 个字节 |
1 | bDescriptorType | 1 | 常量 | 配置描述符类型,为 0X02 |
2 | wTotalLength | 2 | 数字 | 整个配置信息总长度(包括配置、接口、端点、设备类和厂家定义的描述 |
4 | bNumInterfaces | 1 | 数字 | 此配置所支持的接口数 |
5 | bConfigurationValue | 1 | 数字 | 该配置的值,一个设备支持多种配置,通过配置值来区分不同的配置。 |
6 | bConfiguration | 1 | 数字 | 描述此配置的字符串描述索引 |
7 | bmAttributes | 1 | 数字 | 该设备的属性:D7:保留D6:自给电源D5:远程唤醒D4:0:保留 |
8 | bMaxPower | 1 | 数字 | 此配置下所需的总线电流(单位 2mA) |
具体代码如下:
struct usb_config_descriptor {
__u8 bLength; //描述符长度
__u8 bDescriptorType; //描述符类型
__le16 wTotalLength; //配置所返回的所有数据的大小
__u8 bNumInterfaces; //配置支持的接口数量
__u8 bConfigurationValue; //当使用SetConfiguration和GetConfiguration请求时所指定的配置索引值
__u8 iConfiguration; //描述配置的字符串描述符索引
__u8 bmAttributes; //供电模式选择
__u8 bMaxPower; //最大功耗
} __attribute__ ((packed));
2.1.3、接口描述符
配置描述符中指定了该配置下的接口数量,配置可以提供一个或多个接口,接口描述符用于描述接口属性。接口描述符中一般记录接口编号、接口对应的端点数量、接口所述的类等,接口描述符结构如下表所示:
偏移 | 域 | 大小(B) | 值类型 | 描述 |
---|---|---|---|---|
0 | bLength | 1 | 数字 | 此接口描述符长度,9 个字节 |
1 | bDescriptorType | 1 | 常量 | 描述符类型,为 0X04 |
2 | bInterfaceNumber | 1 | 数字 | 当前接口编号,从 0 开始 |
3 | bAlternateSetting | 1 | 数字 | 当前接口备用编号 |
4 | bNumEndpoints | 1 | 数字 | 当前接口的端点数量 |
5 | bInterfaceClass | 1 | 类 | 当前接口所属的类 |
6 | bInterfaceSubClass | 1 | 子类 | 当前接口所属的子类 |
7 | bInterfaceProtocol | 1 | 协议 | 当前接口所使用的协议 |
8 | iInterface | 1 | 索引 | 当前接口字符串的索引值 |
具体代码如下:
struct usb_interface_descriptor {
__u8 bLength; //描述符长度
__u8 bDescriptorType; //描述符类型
__u8 bInterfaceNumber; //接口编号
__u8 bAlternateSetting;//备用的接口描述符编号
__u8 bNumEndpoints; //端点数量,不包括端点0
__u8 bInterfaceClass; //接口类型
__u8 bInterfaceSubClass;//接口子类型
__u8 bInterfaceProtocol;//接口协议
__u8 iInterface; //描述该接口的字符串描述符索引
} __attribute__ ((packed));
2.1.4、端点描述符
接口描述符定义了其端点数量,端点是设备与主机之间进行数据传输的逻辑接口,除了端点 0 是双向端口,其他的端口都是单向的。端点描述符描述了树传输类型、方向、数据包大小、端点号等信息,端点描述符结构如下表所示:
偏移 | 域 | 大小(B) | 值类型 | 描述 |
---|---|---|---|---|
0 | bLength | 1 | 数字 | 此端点描述符长度,7 个字节 |
1 | bDescriptorType | 1 | 常量 | 描述符类型,为 0X05 |
2 | bEndpointAddress | 1 | 数字 | 端点地址和方向:bit3:0:端点号 bit6:4:保留,为零。bit7:方向,0 输出端点(主机到设备),1 输入端点(设备到主机) |
3 | bmAttributes | 1 | 数字 | 端点属性,bit1:0 表示传输类型:00:控制传输01:同步传输10:批量传输11:中断传输其他位保留 |
4 | wMaxPacketSize | 2 | 数字 | 端点能发送或接收的最大数据包长度 |
6 | bInterval | 1 | 子类 | 端点数据传输中周期时间间隙值,此域对于批量传输和控制传输无效,同步传输的话此域必须为 1ms,中断传输此域可以设置 1ms~255ms。 |
具体代码如下:
struct usb_endpoint_descriptor {
__u8 bLength; //描述符长度
__u8 bDescriptorType; //描述符类型
__u8 bEndpointAddress; //端点地址, bit7是方向(0为输出,1为输入),bit0~3为端点号
__u8 bmAttributes; //端点属性
__le16 wMaxPacketSize; //端点所支持最大数据包的长度
__u8 bInterval; //端点数据传输的访问时间间隔
/* 下面两个只用在Audio端点 */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8 bRefresh; //刷新
__u8 bSynchAddress; //同步地址
} __attribute__ ((packed));
2.1.5、字符串描述符
字符串描述符是可选的,字符串描述符用于描述一些方便人们阅读的信息,比如制造商、设备名称啥的。如果一个设备没有字符串描述符,那么其他描述符中和字符串有关的索引值都必须为 0,字符串描述符结构如下表所示:
偏移 | 域 | 大小(B) | 值类型 | 描述 |
---|---|---|---|---|
0 | bLength | 1 | N+2 | 此字符串描述符长度 |
1 | bDescriptorType | 1 | 常量 | 字符串描述符类型,为 0X03 |
2 | wLANGID[0] | 2 | 数字 | 语言标识码 0 |
… | … | 2 | 数字 | … |
N | wLANGID[x] | 2 | 数字 | 语言标识码 x |
wLANGID[0]~wLANGID[x] 指 明 了 设 备 支 持 的 语 言,具 体 含 义 要 查 阅 文 档《 USB_LANGIDs.pdf 》
主机会再次根据自己所需的语言向设备请求字符串描述符,这次会主机会指明要得到的字符串索引值和语言。设备返回 Unicode 编码的字符串描述符,结构如表所示:
偏移 | 域 | 大小(B) | 值类型 | 描述 |
---|---|---|---|---|
0 | bLength | 1 | N+2 | 此字符串描述符长度 |
1 | bDescriptorType | 1 | 常量 | 字符串描述符类型,为 0X03 |
2 | bString | N | 数字 | Unicode 编码的字符串 |
具体代码如下:
struct usb_string_descriptor {
__u8 bLength; //描述符长度
__u8 bDescriptorType; //描述符类型
__le16 wData[1]; /* UTF-16LE encoded */
} __attribute__ ((packed));