Linux USB开发整理和随笔

目录

1 概述

2 硬件原理基础

2.1 USB发展

2.2 USB的拓扑

2.3 硬件接口

2.4 USB总线协议

2.4.1 通信过程

2.4.2 概念关系

2.4.3 管道PIPE

2.4.4 传输

2.4.5 事务

2.4.6 包结构与类型

2.4.6.1 令牌包

2.4.6.2 数据包

2.4.6.3 握手包

2.5 描述符

2.5.1 设备描述符

2.5.2 配置描述符

2.5.3 接口描述符

2.5.4 端点描述符

2.5.5 字符串描述符

2.6 request请求

2.6.1 请求格式

2.6.2 标准请求

2.6.2.1 清除特性Clear Feature

2.6.2.2 获得配置符(Get Configuration )

2.6.2.3 获得描述符(GetDescriptor)

2.6.2.4 设置地址(SetAddress)

2.6.2.5 设置配置(SetConfiguration)

2.6.2.6 获得接口(GetInterface)

2.7 枚举过程

2.7.1 usb检查设备

2.7.1.1 插入检测

2.7.1.2 速率版本检测

2.7.2 枚举②

3 软件

3.1 usb device 

3.1.1 usb的创建与注册流程

3.1.1.1 调用流程

3.1.1.2 代码流程分析

3.2.1 枚举与数据通信过程

3.3.1 使用configfs配置usb设备

3.3.1.1 创建gadgets

3.3.1.2 创建配置

3.3.1.3 创建functions

3.3.1.4 将configuration和function绑定

3.3.1.5 使能gadget

3.4.1 Configfs usb源码分析

3.2 usb host

3.2.1 创建一个usb设备

3.2.1.1 流程概览

3.2.1.2 uvc代码流程介绍

3.2.2 usb host设备识别流程

4 工具


1 概述

        本文主要介绍USB一些基础硬件理论原理和Linux下软件框架分析。其中硬件理论部分的介绍大多摘自网络(笔者是个整理者,有些已备注引用,如有侵犯请联系笔者删除),主要针对影响理解源码和基本的流程而介绍。软件源码介绍部分是笔者分析源码而来。

2 硬件原理基础

2.1 USB发展

        USB发展历史过程版本有USB1.0,USB1.1,USB2.0和USB3.0(3.x系列),各个版本支持的速率以及控制器如下,其中USB4.0截至发稿未发布。

        USB 体系包括 USB host(主机)、USB device(设备)以及物理连接(USB interconnect)三个部分。其中,设备(USB device)又分为USB function和USB Hub。USB interconnect是USB设备连接到主机并与之通信的方式。

2.2 USB的拓扑

        如下是从协议手册里截图下来的拓扑图,USB 上的设备通过分层的星形拓扑物理连接到主机,中心是 USB 主机。HOST-ROOT HUB 为起点,最多支持 7 层(Tier),也就是说任何一个 USB 系统中 最多可以允许 5 USB HUB 级联。一个复合设备(Compound Device)将同时占据两层或更多的层。 以确保即使处于最低层的 USB 设备也能在 USB 规范定义的最大可容忍传播延迟内进行通信。拓扑中的连续层通过集线器连接集线器可以菊花链连接在一起,以支持总共 127 个 USB 设备

        任何 USB 系统中只有一个主机,并且与设备的所有通信都仅由主机发起。

2.3 硬件接口

        VBUS线为所有连接的设备提供 4.4 至 5.25 V 的恒定电源电压

        GND线为设备提供地电压参考。

        D+ 和 D- 是在 3.3V 下工作的数据线,使用带位填充的非归零反转 (NRZI) 编码的差分传输。

2.4 USB总线协议

2.4.1 通信过程

        一次完整的USB通信过程分为三个过程:请求过程(令牌包)、数据过程(数据包)和状态过程(握手包),没有数据要传输时,跳过数据过程。

        USB是轮询总线,USB主机与设备之间的数据交换都是由主机发起的,设备端能被动的响应。USB数据传入或传出 USB 设备中的端点.

        USB 主机中的客户端将数据存储在缓冲区中,USB主机没有端点的概念。

        USB Host 和外围 USB Device 有不同的层,如下图所示。各层之间的连接是每个水平层之间的逻辑主机-设备接口。在逻辑连接之间使用USB Pipes传输数据。

2.4.2 概念关系

        USB数据收发过程涉及的概念关系可用如下图大致描述:主机与从机建立管道(PIPE)通信后,可进行多个传输(transfer),每个传输可包含多个事务(Transaction),每个事务可有令牌包(token)、数据包(Data)和握手(handshake)/状态包(Status)两个或三个包组成。

        其中,事务可分为分别有IN、OUT 和 SETUP 三大事务,跟令牌包一样根据令牌包的标识可分为IN令牌包、OUT令牌包和SETUP令牌包;数据包根据ID可分为DATA0和DATA1;握手包可分为ACK、NAK、STALL、NYET包。

2.4.3 管道PIPE

        管道分为两种类型:消息管道和流管道。消息管道具有已定义的 USB 格式并受主机控制,消息管道允许数据双向流动并且仅支持控制传输,通常用端点0完成。流管道没有定义的 USB 格式,可以由主机或设备控制,数据流具有预定义的方向,即IN或OUT。

2.4.4 传输

        一个传输有多个事务组成,一个事务由2到3个包组成。

        USB协议定义了四种传输类型:批量(大容量数据)传输(Bulk Transfers)、中断传输(Interrupt Transfers)、控制传输(Control Transfers)、等时(同步)传输(Isochronous Transfers)。

               批量(大容量数据)传输(Bulk Transfers):

        非周期性,突发大容量数据的通信,数据可以占用任意带宽,并容忍延迟 。如USB打印机、扫描仪、大容量储存设备等 ,低速 USB 设备不支持批量传输,高速批量端点的最大包长度为 512,全速批量端点的最大包长度可以为 8、16、32、64。批量传输在访问 USB 总线时,相对其他传输类型具有最低的优先级,USB HOST 总是优先安排其他类型的传输,当总线带宽有富余时才安排批量传输。高速的批量端点必须支持PING 操作,向主机报告端点的状态,NYET 表示否定应答,没有准备好接收下一个数据包,ACK 表示肯定应答,已经准备好接收下一个数据包。

        中断传输(Interrupt Transfers): 

        周期性,低频率允许有限延迟的通信如人机接口设备(HID)中的鼠标、键盘、轨迹球等中断传输是一种保证查询频率的传输。中断端点在端点描述符中要报告它的查询间隔,主机会保证在小于这个时间间隔的范围内安排一次传输。中断传输是一种轮询的传输方式,是一种单向的传输,HOST通过固定的间隔对中断端点进行查询,若有数据传输或可以接收数据则返回数据或发送数据,否则返回NAK,表示尚未准备好。中断传输的延迟有保证,但并非实时传输,它是一种延迟有限的可靠传输,支持错误重传。 

        等时(同步)传输(Isochronous Transfers):

        周期性 持续性的传输,用于传输与时效相关的信息,并且在数据中保存时间戳的信息 ,如音频视频设备等时(同步)传输用在数据量大、对实时性要求高的场合,如音频设备,视频设备等,这些设备对数据的延迟很敏感。对于音频或视频设备数据的100%正确性要求不高,少量的数据错误是可以容忍的,主要是保证数据不能停顿,所以等时传输是不保证数据100%正确的。当数据错误时,不再重传操作。因此等时传输没有应答包,数据是否正确,由数据的CRC校验来确认。

        控制传输(Control Transfers): 

        非周期性,突发用于命令和状态的传输控制传输可分为三个过程:建立过程、数据过程(可选)、状态过程。每个USB设备都必须有控制端点,支持控制传输来进行命令和状态的传输。USB主机驱动将通过控制传输与USB设备的控制端点通信,完成USB设备的枚举和配置。控制传输是双向的传输,必须有IN和OUT两个方向上的特定端点号的控制端点来完成两个方向上的控制传输控制传输是一种可靠的双向传输,一次控制传输可分为三个阶段。第一阶段为从HOST到Device的SETUP事务传输,这个阶段指定了此次控制传输的请求类型;第二阶段为数据阶段,也有些请求没有数据阶段;第三阶段为状态阶段,通过一次IN/OUT 传输表明请求是否成功完成。

2.4.5 事务

        一个事务通常由令牌包(token)、数据包(Data)和握手(handshake)两个或三个包组成。SB事务根据功能可以分为3种:

        Setup事务:表示USB主机向设备发送控制命令,由令牌包实现。

        IN事务:表示USB主机从设备读取数据的过程,由数据包实现。

        OUT事务:表示USB主机向设备发送数据的过程,由握手包实现。

        USB的事务都是由主机发起的,一个完整的事务可以分为3个阶段(Phase):

        令牌阶段(Token Phase): 由主机指明传输数据的类型。

        数据阶段(Data Phase): 用于传输数据。

        响应阶段(Acknowledge Phase): 用来判断数据是否发送成功。

        所以,一个基本的事务,需要涉及多个包:令牌包、数据包、握手包。

2.4.6 包结构与类型

        USB包格式如下,一个包被分为不同域,域是 USB 数据最小的单位,由若干位组成(多少位由具体的域决 定)。不同类型的包所包含的域是不一样的,都要以同步域 SYNC 开始 ,紧跟一个包标识符PID,最终以包结束符 EOP 来结束这个包。

同步域(SYNC)

        所有的 USB 包都由 SYNC 开始,高速包的 SYNC 宽度为 32bit(31 位 0+1 位 1),全速 /低速包的 SYNC 段度为 8bit(7 位 0+1 位 1)。实际接收到的 SYNC 长度由于 USB HUB的关系,可能会小于该值。同步域用于告诉 USB 串行接口引擎数据即将开始传输,另一个作用是用于本地时钟与输入同步。

标识域PID

        PID 是用来标识一个包的类型的。它共有 8 位,只使用 4 位(PID0 ~ PID3),另外 4 位 是 PID0 ~ PID3 的取反,用来校验 PID。PID 规定了四类包:令牌包、数据包、握手包和特殊包。同类的包又各分为具体的四种包。

        PID取值含义如下,提取Token,OUT的值为0x87,IN为0x96,SETUP为0xB4,SOFT为0x5A。

地址域(ADDR)

        地址共占 11 位,其中低 7 位是设备地址,高 4 位是端点地址。

        a) 地址域:七位地址,代表了设备在主机上的地址,地址 000 0000 被命名为零地址,是任何设备第一次连接到主机时,在被主机配置、枚举前的默认地址,由此可以知道为什么一个 USB 主机只能接 127 个设备。

        b) 端点域(ENDP):四位,由此可知一个 USB 设备端点数量最大为 16 个。

帧号

        占 11 位,主机每发出一个帧,帧号都会自加 1,当帧号达到 0x7FF 时,将归零重新开始计数。帧号域最大容量 0x800,对于同步传输有重要意义。

数据 

        根据传输类型的不同,数据域的数据长度从 0 到 1024 字节不等。

CRC

        对令牌包和数据包中非 PID 域进行校验的一种方法,CRC 校验在通讯中应用很泛,是 一种很好的校验方法,CRC 码的除法是模 2 运算,不同于 10 进制中的除法。

  1. Token CRCs:对于令牌(Token)使用 5 位 CRC,涵盖了 IN,SETUP 和 OUT 令牌 的 ADDR 和 ENDP 字段或 SOF 令牌的时间戳字段。 PING 和 SPLIT 特殊令牌也包括一个 5 位 CRC 字段。

        b) Data CRCs:数据 CRC 是应用在数据包的数据字段上的 16 位多项式,使用 16 位 CRC。

2.4.6.1 令牌包

        令牌包分为三种,IN令牌包这种类型的令牌包用于向设备请求数据,并且在事务中数据包从设备发送到主机。OUT令牌包此类令牌包用于通知设备主机已准备好向设备发送数据。SETUP令牌包:此类令牌包是在设备设置和配置期间发出的。

        令牌包的package content里没有帧号和数据,只含包PID和地址域,地址域有两个地址:7 位设备地址、4 位端点 ID。

SOF令牌包(帧首包)

        Start-of-(micro)Frame,是一个特殊的包。开始帧(SOF)数据包由主机以全速总 线每 1.00ms±0.0005ms 和高速总线 125us±0.0625us 的标称速率发出。SOF 数据包由一个 PID 指示数据包类型,后面跟着一个 11 位帧数字段,如下图所示。SOF 令牌包括仅用于令 牌的事务处理,其以对应于每个帧的开始的精确时间间隔来分配 SOF 标记和伴随帧号。所有高速和全速功能(包括集线器)都会收到 SOF 数据包。SOF 令牌不会导致任何接收函数生成返回数据包;因此,SOF 交付给任何给定的功能不能得到保证。

        包数据只包含:PID + 帧号 + CRC5;

2.4.6.2 数据包

        数据包携带主机控制器请求或发送的有效载荷,它由数据包PID、有效载荷数据和CRC16字段组成,用于检查收到的数据包的完整性。数据包支持两种类型的数据包 ID:DATA0、DATA1、DATA2和MDATA。数据包分为 DATA0 包和 DATA1 包。当 USB 发送数据的时候,如果一次发送的数据长度大于相应端点的容量时,就需要把数据包分为好几个包分批发送,DATA0 包和 DATA1 包交替发送,即如果第一个数据包是 DATA0,那第二个数据包就是 DATA1。但也有例外情况,在同步传输中(四类传输类型中之一),所有的数据包都是为 DATA0。格式如下:

        SYNC + PID + 0~1024 字节 + CRC16

        注意:低速设备允许的最大数据有效载荷大小为 8 个字节。 全速设备的最大数据有效

        载荷大小为 1023.高速设备的最大数据有效载荷大小为 1024 个字节。

2.4.6.3 握手包

        握手包包括 ACK、NAK、STALL 以及 NYET 四种,其中

ACK表示肯定的应答成功的数据传输。对于 IN 事务,将由 host 发出;对于 OUT、SETUP和 PING 事务,将由 device 发出。

        NAK表示否定的应答失败的数据传输,要求重新传输。在数据阶段,对于IN 事务,它将由device发出;在握手阶段,对于OUT和PING事务,它也将由device发出;host从不发送NAK包。

        STALL表示功能错误或端点被设置了STALL属性。

        NYET表示尚未准备好,要求等待。

        握手包是结构最为简单的包,格式如下:SYNC + PID。

2.5 描述符

        USB使用一系列层级的描述符 (Descriptors) 来向主机描述自身信息,USB描述主要包括Device Descriptors(设备描述)、Configuration Descriptors(配置描述)、Interface Descriptors( 接口描述)、Endpoint Descriptors(端点描述)、String Descriptors(字符串描述)。

        如下图大致描述了描述符之间的关系:

        描述定义在kernel内核的include/uapi/linux/usb/ch9.h。Usb3.0协议定义在ch9章节。

2.5.1 设备描述符

        描述USB设备的大概信息,其中包括适用于设备的全局信息,所有设备的配置。一个USB设备只有一个设备描述符。

/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
    __u8  bLength; // 18 字节
    __u8  bDescriptorType; // 0x01

    __le16 bcdUSB; // 设备所依从的 USB 版本号
    __u8  bDeviceClass; // 设备类型
    __u8  bDeviceSubClass; // 设备子类型
    __u8  bDeviceProtocol; // 设备协议
    __u8  bMaxPacketSize0; // ep0 的最大包长度,有效值为 8,6,32,64
    __le16 idVendor; // 厂商号
    __le16 idProduct; // 产品号
    __le16 bcdDevice; // 设备版本号
    __u8  iManufacturer; // 产商字名称
    __u8  iProduct; // 产品名称
    __u8  iSerialNumber; // 序列号
    __u8  bNumConfigurations; // 配置描述符的个数
} __attribute__ ((packed));
#define USB_DT_DEVICE_SIZE      18

2.5.2 配置描述符

        描述了特定的设备配置信息。一个USB设备可以有一或多个配置描述符。每个配置有一个或多个接口(interface), 并且每个接口有零或多个端点(endpoint)。一个端点在一个单独的配置下,是不和其他的接口共享的,但是一个单 独的接口对于同一个端点能够有几种可选的配置。端点可以没有限制的在一部分不同的配置下的接口间共享。配置 仅仅能够通过标准的控制传输set_configuration来激活。不同的配置能够用来全局配置信息,例如供电消耗。

/* USB_DT_CONFIG: Configuration descriptor information.
 *
 * USB_DT_OTHER_SPEED_CONFIG is the same descriptor, except that the
 * descriptor type is different.  Highspeed-capable devices can look
 * different depending on what speed they're currently running.  Only
 * devices with a USB_DT_DEVICE_QUALIFIER have any OTHER_SPEED_CONFIG
 * descriptors.
 */
struct usb_config_descriptor {
    __u8  bLength; // 9
    __u8  bDescriptorType; // 0x02

    __le16 wTotalLength; // 返回数据的总长度
    __u8  bNumInterfaces; // 接口描述符的个数
    __u8  bConfigurationValue; // 当前配置描述符的值 (用来选择该配置)
    __u8  iConfiguration; // 该配置的字符串信息 (在字符串描述符中的索引)
    __u8  bmAttributes; // 属性信息
    __u8  bMaxPower; // 最大功耗,以 2mA 为单位
} __attribute__ ((packed));
#define USB_DT_CONFIG_SIZE      9

2.5.3 接口描述符

        描述了一个配置内的特定接口。一个配置提供一个或多个接口,每个接口带有零个或多个端点描述符描述了在配置 内的唯一配置。一个可以包含可选的配置的接口使得配置好的端点和/或他们的特性能够多种多样。默认的接口设置 总是设置为零。可替换的设置能够在标准控制传输的set_interface来选择一个。例如一个多功能设备带有话筒的摄 像头,可以有三种可用的配置来改变分配在总线上的带宽。

/* USB_DT_INTERFACE: Interface descriptor */
struct usb_interface_descriptor {
    __u8  bLength;
    __u8  bDescriptorType; // 0x04

    __u8  bInterfaceNumber; // 接口序号
    __u8  bAlternateSetting;
    __u8  bNumEndpoints;//端点数量
    __u8  bInterfaceClass;
    __u8  bInterfaceSubClass;
    __u8  bInterfaceProtocol;
    __u8  iInterface; // 接口的字符串描述,同上
} __attribute__ ((packed));
#define USB_DT_INTERFACE_SIZE       9

2.5.4 端点描述符

        包含主机用来决定每个端点带宽的信息。一个端点象征一个USB设备的逻辑数据源或接收端(logic data source or sink)。端点零是用来所有的控制传输并且该端点没有设备描述符。USB spec交替使用pipe和endpoint术语。

/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
    __u8  bLength;
    __u8  bDescriptorType; // 0x05

/*
bEndpointAddress 8位数据分别代表:
Bit 0-3: 端点号
Bit 4-6: 保留,值为0
Bit 7: 数据方向,0 为 OUT,1 为 IN
*/
__u8  bEndpointAddress; // 端点地址

/*
bmAttributes 8位数据分别代表:
Bit 0-1: 传输类型
00: Control
01: Isochronous
10: Bulk
11: Interrupt
Bit 2-7: 对非 Isochronous 端点来说是保留位,对 Isochronous 端点而言表示 Synchronisation Type 和 Usage Type,每种端点类型对应一种传输类型。
*/
__u8  bmAttributes; // 端点属性

    __le16 wMaxPacketSize; // 该端点收发的最大包大小
    __u8  bInterval; // 轮询间隔,只对 Isochronous 和 interrupt 传输类型的端点有效 (见下)
    /* NOTE:  these two are _only_ in audio endpoints. */
    /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
    __u8  bRefresh;
    __u8  bSynchAddress;
} __attribute__ ((packed));
#define USB_DT_ENDPOINT_SIZE        7
#define USB_DT_ENDPOINT_AUDIO_SIZE  9   /* Audio extension */

2.5.5 字符串描述符

        是可选项,提供了unicode编码的额外的可读信息。他们可以是厂商和设备名称或序列号。

/* USB_DT_STRING: String descriptor */
struct usb_string_descriptor {
    __u8  bLength;
    __u8  bDescriptorType; // 0x03

    __le16 wData[1];        /* UTF-16LE encoded */
} __attribute__ ((packed));
/* note that "string" zero is special, it holds language codes that
 * the device supports, not Unicode characters.
 */

2.6 request请求

2.6.1 请求格式

        所有 USB 设备都会响应设备默认控制管道上来自主机的请求。这些请求使用控制转移

进行。请求和请求的参数在Setup数据包中发送到设备。主机负责建立下表中列出的字段值。

每个 SETUP 包都有八个字节。

struct usb_ctrlrequest {
    __u8 bRequestType; // 对应 USB 协议中的 bmRequestType,包含请求的方向、类型和指定接受者
    __u8 bRequest; // 决定所要执行的请求
    __le16 wValue; // 请求参数
    __le16 wIndex; // 同上
    __le16 wLength; // 如果请求包含 Data Stage,则指定数据的长度
} __attribute__ ((packed));

        请求通过控制传输的 SETUP 事务的 DATA0 包发送出去。请求分为标准请求(Standard Request)和类特定请求(Class-Specified Request),USB 规范定义了如下标准请求码:

        如下给出了描述符类型

        示例

bmRequestType

        该位图字段标识特定请求的特征。特别是,该字段标识控制传输第二阶段中的数据传输方向。如wLength字段为零,则表示方向位的状态将被忽略,表示没有数据阶段。USB 规范定义了所有设备必须支持的一系列标准请求。这些在下一节的表中列举。另外,设备类可以定义额外的请求。设备供应商也可以定义设备支持的请求。可以将请求指向设备,设备上的接口或设备上的特定端点。该字段还指定了请求的预期收件人。当指定接口或端点时,Index 字段标识接口或端点。

bRequest

        该字段指定了特定的请求。bmRequestType 字段中的 Type 位修改此字段的 含义。本规范仅在位重置为零时指定 bRequest 字段的值。

wValue

        该字段的内容根据要求而有所不同。它用于将参数传递给设备,特定于请求。

wIndex

        该字段的内容根据要求而有所不同。 它用于将参数传递给设备,特定于请 求。wIndex 字段通常用于指定端点或接口的请求中。

2.6.2 标准请求

        如下图是所有标准请求列表,USB 设备必须响应标准的设备请求,即使设备尚未分配地址或尚未配置。Feature selectors 用于启用或设置特定功能,如设备,接口或端点特有的远程唤醒功能。

2.6.2.1 清除特性Clear Feature

        wValue 中的功能选择器值必须适合接收者。当接收者是设备时,只能使用设备功能选

择器值;当接收者是接口时,只有接口特征选择器值可以被使用,并且当接收者是端点时,可以仅使用端点特征选择器值。引用无法设置或不存在的功能的 SetFeature()请求会导致在请求的状态阶段返回 STALL。如果 wLength 非零,那么设备的行为没有被指定。 如果指定了不存在的端点或接口,则设备会响应请求错误。

Default state

        处于默认状态时,设备必须能够接受 SetFeature(TEST_MODETEST_SELECTOR)请求。未指定设备处于默认状态时其他 SetFeature 请求的设备行为。

Address state

        如果指定了端点 0 以外的接口或端点,则设备会响应请求错误。

Configured state

        这是一个有效的请求。

2.6.2.2 获得配置符(Get Configuration

        如果返回值为零,则说明设备未配置。如果 wValuewIndex wLength 不符合上述规定,则未指定设备行为。

Default state

        当设备处于默认状态时收到该请求,则设备行为是未指定的。

Address state

        必须返回 0.

Configured state

        必须返回当前配置的非零值 bConfigurationValue

2.6.2.3 获得描述符(GetDescriptor)

        该请求中的 wValue 的高字节表示要取的设备描述符,低字节表示描述符的索引值,描述的类型见下表。wIndex 的值为 0 或语言 ID;当要取的值为字符串描述符时,该域的值为语言 ID;当为其它的描述符时,该域为 0。wLength 表示要返回的数据长度,如果 SETUP 阶段的地址使用的是预设地址 0,(ENDP 字段为 0),这时 wLength 值会大于实际的描述的值。这是为什么? 原因是用户预设的地址 0 来取得设备描述符时,不管多少个字节,用户最多只能取前 8 个字节,即在控制传输过程只有一次数据阶段。

        Descriptor type域取值:

2.6.2.4 设置地址(SetAddress)

        该请求为即将连入的设备设置地址。

        请求实际上可能会导致最多三个阶段:

        在第一阶段,设置数据包被发送到设备。

        在可选的第二阶段,数据在主机和设备之间传输。在最后阶段,状态在主机和设备之间传输。数据和状态传输的方向取决于主机是向设备发送数据还是设备正在向主机发送数据。 状态阶段转移始终与数据阶段相反。如果没有数据阶段,则状态阶段从设备到主机。最初的 SETUP 数据包之后的阶段采用与安装数据包相同的设备地址。直到此请求的状态阶段成功完成后,USB 设备才会更改其设备地址。

        请注意,这是此请求和所有其他请求之间的区别。对于所有其他请求,指示的操作必须在状态阶段之前完成,如果指定的设备地址大于 127,或者 wIndex wLength 非零,则未指定设备的行为。

Default state:如果指定的地址非零,则设备应进入地址状态;否则,设备保持默认状 态(这不是错误状态)。

Address state:如果指定的地址为零,则设备将进入默认状态;否则,设备保持在地址 状态,但使用新指定的地址。

Configured state:设备处于 Configured 状态时收到此请求时的设备行为是未指定的。

2.6.2.5 设置配置(SetConfiguration)

        wValue 字段的低字节指定了所需的配置。该配置值必须为零或匹配配置描述符中的配 置值。如果配置值为零,则设备处于其地址状态。wValue 字段的高字节保留。如果 wIndex

        wLength 或 wValue 的高字节不为零,则此请求的行为未指定。

Default state:在设备处于默认状态时收到此请求时的设备行为未指定。

Address state:如果指定的配置值为零,则设备保持在地址状态。 如果指定的配置值与配置描述符中的配置值相匹配,则选择该配置并且设备进入配置状态。 否则,设备会响 应请求错误。

Configured state:如果指定的配置值为零,则设备进入地址状态。 如果指定的配置值 与配置描述符中的配置值相匹配,则选择该配置并且设备保持配置状态。 否则,设备会响

应请求错误。

2.6.2.6 获得接口(GetInterface)

        该请求返回指定接口的选定备用设置。

        某些 USB 设备的配置具有互斥设置的接口。该请求允许主机确定当前选择的备用设置。 如果 wValue wLength 不符合上述规定,则设备行为未指定。如果指定的接口不存在,那么设备将响应请求错误。

Default state:当设备处于默认状态时收到该请求,则设备行为是未指定的。

Address state:设备给出请求错误响应

Configured state:请求是有效的

2.6.2.7 获得状态(Get State)

此请求返回指定接收者的状态。

        bmRequestType 字段的 Recipient bits 指定了所需的接收者。返回的数据是指定接收者的当前状态。如果 wValue wLength 不符合上面的规定,或者如果 wIndex 对于设备状态请求非零,则未指定设备的行为。如果指定的接口或端点不存在,那么设备将响应请求错误。

Default state:当设备处于默认状态时收到该请求,则设备行为是未指定的。

Address state:如果指定了端点 0 以外的端点或者是接口,则设备会响应请求错误。

Configured state:如果指定的端点或者接口不存在,设备会响应请求错误。

2.7 枚举过程

2.7.1 usb检查设备

2.7.1.1 插入检测

USB的插入检测机制:USB端口的D+和D-均用一个15k的电阻接地,当无设备接入时,均处于低电平;在设备端在D+(表示高速设备或者全速设备)或者D-(表示低速设备)接了一个1.5k的上拉电阻到+3.3v,一旦将设备接入,USB端口的D+或者D-其中一个被拉高为3v,系统识别到外部设备接入。注意高速设备首先会被识别为全速设备,然后再通过集线器和设备二者的确认最后切换到高速模式下。

2.7.1.2 速率版本检测

(1)低速和全速设备识别

        低速(Low Speed)和全速(Full Speed)设备区分方法比较简单:在设备端有一个1.5k的上拉电阻,当设备插入hub或上电(固定线缆的USB设备)时,有上拉电阻的那根数据线就会被拉高,hub根据D+/D-上的电平判断所挂载的是全速设备还是低速设备。

低速设备1.5K上拉电阻位于D-

全速设备1.5K上拉电阻位于D+

(2)高速设备识别①

        对于高速设备的识别就显得稍微复杂些,高速设备初始是以一个全速设备的身份出现,和全速设备一样,D+线上有一个1.5k的上拉电阻。USB2.0的hub把它当作一个全速设备,之后,hub和设备通过一系列握手信号确认双方的身份。

hub检测到有设备插入/上电时,向主机通报,主机发送Set_Port_Feature请求让hub复位新插入的设备。设备复位操作是hub通过驱动数据线到复位状态SE0(Single-ended 0,即D+和D-全为低电平),并持续至少10ms。

高速设备看到复位信号后,通过内部的电流源向D-线持续灌大小为17.78mA电流。因为此时高速设备的1.5k上拉电阻还未撤销,在hub端,全速/低速驱动器形成一个阻抗为45欧姆(Ohm)的终端电阻,2电阻并联后仍是45欧姆左右的阻抗,所以在hub端看到一个约800mV的电压(45欧姆*17.78mA),这就是Chirp K信号。Chirp K信号的持续时间是1ms~7ms。

在hub端,虽然下达了复位信号,并一直驱动着SE0,但USB2.0的高速接收器一直在检测Chirp K信号,如果没有Chirp K信号看到,就继续复位操作,直到复位结束,之后就在全速模式下操作。如果只是一个全速的hub,不支持高速操作,那么该hub不理会设备发送的Chirp K信号,之后设备也不会切换到高速模式。

设备发送的Chirp K信号结束后100us内,hub必须开始回复一连串的KJKJKJ….序列,向设备表明这是一个USB2.0的hub。这里的KJ序列是连续的,中间不能间断,而且每个K或J的持续时间在40us~60us之间。KJ序列停止后的100~500us内结束复位操作。hub发送Chirp KJ序列的方式和设备一样,通过电流源向差分数据线交替灌17.78mA的电流实现。

再回到设备端来。设备检测到6个hub发出的Chirp信号后(3对KJ序列),它必须在500us内切换到高速模式。切换动作有:

1) 断开1.5k的上拉电阻。

2) 连接D+/D-上的高速终端电阻(high-speed termination),实际上就是全速/低速差分驱动器。

3) 进入默认的高速状态。

执行1,2两步后,USB信号线上看到的现象就发生变化了:hub发送出来的Chirp KJ序列幅值降到了原先的一半,400mV。这是因为设备端挂载新的终端电阻后,配上原先hub端的终端电阻,并联后的阻抗是22.5欧姆。400mV就是由17.78mA*22.5Ohm得来。以后高速操作的信号幅值就是400mV而不像全速/低速那样的3V。

至此,高速设备与USB2.0 hub握手完毕,进行后续的480Mbps高速信号通信.

①https://blog.csdn.net/flydream0/article/details/71512852

2.7.2 枚举②

USB设备枚举一般会经过插入、供电、初始化、分配地址,配置,获取设备描述符、获取配置描述符、获取字符串描述符和配置设备这么几个过程。

USB设备的枚举过程如下:

(1)USB设备插入USB接口后,主机检测D+/D-线上的电压,确认有设备连接,USB集线器通过中断IN通道,向主机报告有USB设备连接。

(2)主机接到通知后,通过集线器设备类请求GetPortStatus获取更多的信息。然后主机等待100ms等待设备稳定,然后发送集线器设备类请求SetPortStatus,对USB设备进行复位,复位后USB设备的地址为0,这样主机就可以使用地址0与USB设备进行通信,复位后的设备可以从USB总线上获取小于100mA的电流,用于使用默认地址对管道0控制事务响应。

主机向地址为0(即刚插入的USB设备)的设备端点0(默认端点)发送获取设备描述符的标准请求GetDescriptor。

(3)USB设备收到请求后,将其预设的设备描述符返回给主机。

(4)主机收到设备描述符后,返回一个0长度的数据确认包。

(5)主机对设备再次复位,复位后主机对地址为0的设备端点0发送一个设置地址SetAddress请求(新的设备地址在数据包中)。

(6)主机发送请求状态返回,设备返回0长度的状态数据包。

(7)主机收到状态数据包后,发送应答包ACK给设备,设备收到ACK后,启用新的设备地址。

(8)主机再次使新的地址获取设备描述符GetDescriptor,设备返回地址描述符。

(9)主机获取第一次配置描述符有前18个字节,设备返回配置描述符的前18个字节,其数据包中含有配置描述符的总长度。

(10)主机根据配置描述符的总长度再次获取配置描述符,设备返回全总的配置描述符。

如果还有字符串描述符,系统还会获取字符串描述符。像HID设备还有报告描述符,它也需要单独获取。

②https://www.usbzh.com/article/detail-110.html

3 软件

        Linux内核中,usb源码实现了usb host和usb device的代码,usb host代码主要集中在core和host中,控制器实现了dwc2和dwc3驱动代码,兼容host和device。Usb device的实现主要在gadget下,其他的有usb控制器驱动例如wusb等。

Host与device的源码层次关系如下,host与device之间都是通过usb控制器通信,Linux host实现了usb核心层,向上提供给驱动用户注册设备的接口,usb host设备驱动例如uvc、cdc、hid等,向下有几个协议类型的usb协议接口驱动(硬件篇已介绍过)。Device最上层通常访问设备的驱动,例如serial一般上层是tty框架,该层向下调用gadget提供的function接口,往下则是复合设备接口composite,下层是ucd用户控制设备,一般有dw3/dw2等,ucd层将直接访问控制的硬件接口。

3.1 usb device 

以usb cdc设备分析usb device设备注册与创建过程,枚举与configfs使用过程。

3.1.1 usb的创建与注册流程

3.1.1.1 调用流程

如下图是一个cdc设备的调用栈流程

3.1.1.2 代码流程分析

注册function:

drivers/usb/gadget/function/f_serial.c

DECLARE_USB_FUNCTION_INIT(gser, gser_alloc_inst, gser_alloc);
#define DECLARE_USB_FUNCTION_INIT(_name, _inst_alloc, _func_alloc)  \
    DECLARE_USB_FUNCTION(_name, _inst_alloc, _func_alloc)       \
    static int __init _name ## mod_init(void)           \
    {                               \
        return usb_function_register(&_name ## usb_func);   \
    }                               \
    static void __exit _name ## mod_exit(void)          \
    {                               \
        usb_function_unregister(&_name ## usb_func);        \
    }                               \
    module_init(_name ## mod_init);                 \
    module_exit(_name ## mod_exit)

加入func_list链表drivers/usb/gadget/functions.c

int usb_function_register(struct usb_function_driver *newf)
{
    struct usb_function_driver *fd;
   ...
    list_for_each_entry(fd, &func_list, list) {
        if (!strcmp(fd->name, newf->name))
            goto out;
    }
    ret = 0;
    list_add_tail(&newf->list, &func_list);
out:
...
    return ret;
}

上层设备和udc的注册与调用栈

drivers/usb/gadget/legacy/serial.c

module_init(init);
init
=>usb_composite_probe (drivers/usb/gadget/composite.c)
==>usb_gadget_probe_driver (drivers/usb/gadget/udc/core.c)
===>udc_bind_to_driver
====>ret = driver->bind(udc->gadget, driver);-->gs_bind(drivers/usb/gadget/legacy/serial.c)
=====>usb_gadget_udc_start:udc->gadget->ops->udc_start
======>usb_udc_connect_control => usb_gadget_connect => gadget->ops->pullup

前面调用driver->bind回调将调用drivers/usb/gadget/legacy/serial.c下的gs_bind,gs_bind主要回调上层操作集的回调,初始化对更上一层的框架,这里初始化了tty_driver.

gs_bind
=>usb_get_function_instance 
==>try_get_usb_function_instance->fd->alloc_inst:gser_alloc_inst(drivers/usb/gadget/function/f_serial.c)
 ==>usb_get_function --> fi->fd->alloc_func(fi):gser_alloc(drivers/usb/gadget/function/f_serial.c)

另外一个回调是由udc用户设备控制器注册时提供,调用栈如下:

module_platform_driver(dwc3_driver); (drivers/usb/dwc3/core.c)
dwc3_probe
 =>dwc3_core_init_mode
  ==>dwc3_gadget_init : USB_DR_MODE_OTG ->dts: usb_otg (drivers/usb/dwc3/gadget.c)
   ==>usb_add_gadget_udc: dwc->gadget.ops = &dwc3_gadget_ops; (drivers/usb/gadget/udc/core.c)
    ==>usb_add_gadget_udc_release
list_add_tail(&udc->list, &udc_list);

最终调用到drivers/usb/gadget/udc/core.c里dwc3_gadget_ops的udc_start

udc->gadget->ops->udc_start --> dwc3_gadget_ops
=>dwc3_gadget_start (drivers/usb/dwc3/gadget.c)
 request_threaded_irq-->dwc3_interrupt-->dwc3_thread_interrupt

3.2.1 枚举与数据通信过程

初始化过程为枚举和通信过程初始化了很多对象内存数据,这些数据在枚举和通信过程都会被调用使用。

前面初始化过程中,调用了dwc3_gadget_start ,该函数会注册一个端点中断。

static int dwc3_gadget_start(struct usb_gadget *g,
        struct usb_gadget_driver *driver)
{
...
    ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt,
            IRQF_SHARED, "dwc3", dwc->ev_buf);
...
}

分成了中断上下部,上部主要处理中断事件的数据:

dwc3_interrupt
=>dwc3_check_event_buf
    count = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(0));
    count &= DWC3_GEVNTCOUNT_MASK;
    if (!count)
        return IRQ_NONE;
    evt->count = count;
    evt->flags |= DWC3_EVENT_PENDING;

下半部用线程处理,调用栈如下

dwc3_thread_interrupt
=>dwc3_process_event_buf
==>dwc3_process_event_entry
===>dwc3_process_event_entry
====>dwc3_endpoint_interrupt
   =====>dwc3_ep0_interrupt(drivers/usb/dwc3/ep0.c)
======>dwc3_ep0_xfer_complete

端点0数据包类型处理,有setup、data和status三种,作为device,需要依host下发数据包回复不同的数据。枚举阶段大部分都由host发出setup包,device相应,例如获取设备信息、描述符信息等。

static void dwc3_ep0_xfer_complete(struct dwc3 *dwc,
            const struct dwc3_event_depevt *event)
{
    struct dwc3_ep      *dep = dwc->eps[event->endpoint_number];
...
    switch (dwc->ep0state) {
    case EP0_SETUP_PHASE:
        dwc3_ep0_inspect_setup(dwc, event);
        break;
    case EP0_DATA_PHASE:
        dwc3_ep0_complete_data(dwc, event);
        break;
    case EP0_STATUS_PHASE:
        dwc3_ep0_complete_status(dwc, event);
        break;
    ...}}

Setup的处理:

Setup处理提供了标准和其他处理,最终都是端点数据的处理过程,dwc3提供了自定义的处理,以dwc3_ep0_delegate_req为例子

static void dwc3_ep0_inspect_setup(struct dwc3 *dwc,
        const struct dwc3_event_depevt *event)
{
    struct usb_ctrlrequest *ctrl = dwc->ctrl_req;
    ...
    if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
        ret = dwc3_ep0_std_request(dwc, ctrl);//标准请求走的接口,gadget框架提供的接口
    else
        ret = dwc3_ep0_delegate_req(dwc, ctrl);//设备自定义提供的接口
  ....
out:
    if (ret < 0)
        dwc3_ep0_stall_and_restart(dwc);
}

dwc3_ep0_delegate_req最终回调了dwc3控制器驱动注册的usb_gadget_driver: dwc->gadget_driver->setup,该接口在前面初始化流程调用udc_start初始化的

static int dwc3_gadget_start(struct usb_gadget *g,
        struct usb_gadget_driver *driver)
{
...
dwc->gadget_driver = driver;
...
}

即调用传进来的driver中的setup,根据前初始化流程的分析,该函数在设备层注册进来的drivers/usb/gadget/composite.c

int usb_composite_probe(struct usb_composite_driver *driver)
{
    struct usb_gadget_driver *gadget_driver;
...
    driver->gadget_driver = composite_driver_template;
    ...
    return usb_gadget_probe_driver(gadget_driver);
}
static const struct usb_gadget_driver composite_driver_template = {
    .bind       = composite_bind,
    .unbind     = composite_unbind,
    .setup      = composite_setup,
    .reset      = composite_disconnect,
    .disconnect = composite_disconnect,
    .suspend    = composite_suspend,
    .resume     = composite_resume,
    .driver = {
        .owner      = THIS_MODULE,
    },
};

即调用到composite_setup,该函数是对host请求进行处理,有

USB_REQ_GET_DESCRIPTOR
USB_REQ_SET_CONFIGURATION
USB_REQ_GET_CONFIGURATION
USB_REQ_SET_INTERFACE
USB_REQ_GET_INTERFACE
USB_REQ_GET_STATUS
USB_REQ_CLEAR_FEATURE|USB_REQ_SET_FEATURE

以USB_REQ_GET_DESCRIPTOR分析为例:

int
composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
{
...
case USB_REQ_GET_DESCRIPTOR:
        if (ctrl->bRequestType != USB_DIR_IN)
            goto unknown;
switch (w_value >> 8) {
        case USB_DT_DEVICE:
          cdev->desc.bNumConfigurations =count_configs(cdev, USB_DT_DEVICE);//获取配置数量,每个设备都有一个配置
            cdev->desc.bMaxPacketSize0 =cdev->gadget->ep0->maxpacket;//端点的最大传输
            if (gadget_is_superspeed(gadget)) {//是否支持高速
                if (gadget->speed >= USB_SPEED_SUPER) {
                    cdev->desc.bcdUSB = cpu_to_le16(0x0300);
                    cdev->desc.bMaxPacketSize0 = 9;
                } else {
                    cdev->desc.bcdUSB = cpu_to_le16(0x0210);
                }
            } else {
                cdev->desc.bcdUSB = cpu_to_le16(0x0200);
            }
            value = min(w_length, (u16) sizeof cdev->desc);
            memcpy(req->buf, &cdev->desc, value);
            break;
...
    if (value >= 0 && value != USB_GADGET_DELAYED_STATUS) {
        ...
        value = composite_ep0_queue(cdev, req, GFP_ATOMIC);//发送
        ....
    }
...
}

其他请求流程基本一样。

3.3.1 使用configfs配置usb设备

Linux下的usb框架也支持使用configfs来支持用户通过文件系统配置的方式使能设备包含复合设备。需要Linux内核打开对应的宏来支持。

CONFIG_USB_SUPPORT
CONFIG_USB
CONFIG_USB_DWC3
CONFIG_USB_LIBCOMPOSITE
CONFIG_USB_GADGET
CONFIGFS_FS

Usb configfs配置步骤与例子

以创建一个uac2设备配置为例子。

3.3.1.1 创建gadgets

# usb_gadget依赖于libcomposite模块

 modprobe libcomposite

# 挂载config文件系统

 mount -t configfs none /sys/kernel/config

# 创建g1目录,实例化一个新的gadget模板

mkdir -m 0770 /sys/kernel/config/usb_gadget/g1

# 设置产品的VendorID、ProductID及USB规范版本号

echo 0x2207 > /sys/kernel/config/usb_gadget/g1/idVendor

echo 0x0019 > /sys/kernel/config/usb_gadget/g1/idProduct

# 设备版本号USB 1.1: 0x0110 # USB 2.0: 0x0200, USB 2.1: 0x0210, USB 2.5: 0x0250 # USB 3.0: 0x0300, USB 3.1: 0x0310, USB 3.2: 0x0320

 echo 0x0200 > /sys/kernel/config/usb_gadget/g1/bcdDevice

#创建语言配置目录,指定serialnumbermanufacturerproduct等字符串所使用的语言,这里使用英语0x409,其他语言代表文件夹名称例如:0x407:代表德语。0x40c:代表法语。0x411:代表日语。0x412:代表韩语。0x416:代表葡萄牙语

echo "Setting English strings" 

mkdir /sys/kernel/config/usb_gadget/g2/strings/0x409   -m 0770

echo "rockchip"  > /sys/kernel/config/usb_gadget/g2/strings/0x409/manufacturer

echo "rk3xxx"  > /sys/kernel/config/usb_gadget/g2/strings/0x409/product

echo "0123456789ABCDEF" > /sys/kernel/config/usb_gadget/g1/strings/0x409/serialnumber

3.3.1.2 创建配置

每个gadget都包含若干个配置(configurations),他们的命令规则如下:

$ mkdir configs/<name>.<number>

where <name> can be any string which is legal in a filesystem and the <number> is the configuration's number, e.g.:

$ mkdir configs/c.1

每个配置还需要配置strings,所以需要创建对应的语言文件夹

$ mkdir configs/c.1/strings/0x409

然后对该配置进行命名

$ echo <configuration> > configs/c.1/strings/0x409/configuration

有时也可以配置一些属性

$ echo 120 > configs/c.1/MaxPower

实例:

# Creating Config echo "Creating Config" 

mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/configs/c.1

mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409

echo "uac2" > /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409/configuration

echo 500 > /sys/kernel/config/usb_gadget/g1/configs/c.1/MaxPower

3.3.1.3 创建functions

每个configuration都能对应有function来配置设备,配置设备需要创建functions文件夹

$ mkdir functions/<name>.<instance name>

例如 $ mkdir functions/ncm.usb0

实例:

mkdir /sys/kernel/config/usb_gadget/g1/functions/uac2.0

3.3.1.4 将configuration和function绑定

$ ln -s functions/<name>.<instance name> configs/<name>.<number>

e.g.:

$ ln -s functions/ncm.usb0 configs/c.1

实例:

ln -s /sys/kernel/config/usb_gadget/g1/functions/uac2.0 /sys/kernel/config/usb_gadget/g1/configs/c.1

3.3.1.5 使能gadget

echo <udc name> > UDC, udc name在路径/sys/class/udc/*能找到,标识usb设备控制器。

实例:

echo fe800000.dwc3 > /sys/kernel/config/usb_gadget/g1/UDC

/sys/kernel/config/usb_gadget/g1文件目录最后的文件展示

├── bcdDevice
├── bcdUSB
├── bDeviceClass
├── bDeviceProtocol
├── bDeviceSubClass
├── bMaxPacketSize0
├── configs
│   └── c.1
│       ├── bmAttributes
│       ├── MaxPower
│       ├── strings
│       │   └── 0x409
│       │       └── configuration
│       └── uac2.0 -> ../../../../usb_gadget/g1/functions/uac2.0
├── functions
│   └── uac2.0
│       ├── c_chmask      # 录音通道掩码,默认0x3
│       ├── c_srate       # 录音采样率,默认64000
│       ├── c_ssize       # 录音一帧数据所占bit位,默认16bit
│       ├── p_chmask      # 播放通道掩码,默认0x3
│       ├── p_srate       # 播放采样率,默认48000
│       ├── p_ssize       # 播放一帧数据所占bit位,默认16bit
│       └── req_number    # 分配usb请求数量,默认2个
├── idProduct
├── idVendor
├── os_desc
│   ├── b_vendor_code
│   ├── qw_sign
│   └── use
├── strings
│   └── 0x409
│       ├── manufacturer
│       ├── product
│       └── serialnumber
└── UDC    # 用于设置绑定USB控制器的名称

3.4.1 Configfs usb源码分析

Usb的configfs源码实现在drivers/usb/gadget/configfs.c,前面软件框图已经提及了configfs在usb框架中的作用位置,以前面分析的serial为例,如果内核不使能

CONFIG_USB_G_SERIAL

即drivers/usb/gadget/legacy/serial.c文件不被使用来做为上层设备注册,此时可以通过configfs方式来配置使能cdc设备。

代码初始化先创建注册了configfs子系统对象:

static struct configfs_group_operations gadgets_ops = {
    .make_group     = &gadgets_make,
    .drop_item      = &gadgets_drop,
};
static struct config_item_type gadgets_type = {
    .ct_group_ops   = &gadgets_ops,
    .ct_owner       = THIS_MODULE,
};
static struct configfs_subsystem gadget_subsys = {
    .su_group = {
        .cg_item = {
            .ci_namebuf = "usb_gadget",
            .ci_type = &gadgets_type,
        },
    },
    .su_mutex = __MUTEX_INITIALIZER(gadget_subsys.su_mutex),
};
static int __init gadget_cfs_init(void)
{
    int ret;
    config_group_init(&gadget_subsys.su_group);
    ret = configfs_register_subsystem(&gadget_subsys);
...
    return ret;
}
module_init(gadget_cfs_init);

当configfs usb被挂在时,将在/sys/kernel/config下生成usb_gadget的文件夹,并调用gadgets_make,该函数主要实在group下创建不同的item,生成functions、configs、strings、os_desc文件夹,并对其他控制参数进行初始化。

static struct config_group *gadgets_make(
        struct config_group *group,
        const char *name)
{
struct gadget_info *gi;

    gi = kzalloc(sizeof(*gi), GFP_KERNEL);
    if (!gi)
        return ERR_PTR(-ENOMEM);
    config_group_init_type_name(&gi->group, name, &gadget_root_type);
    config_group_init_type_name(&gi->functions_group, "functions",&functions_type);
    configfs_add_default_group(&gi->functions_group, &gi->group);
    config_group_init_type_name(&gi->configs_group, "configs",&config_desc_type);
    configfs_add_default_group(&gi->configs_group, &gi->group);
    config_group_init_type_name(&gi->strings_group, "strings",&gadget_strings_strings_type);
    configfs_add_default_group(&gi->strings_group, &gi->group);
    config_group_init_type_name(&gi->os_desc_group, "os_desc",&os_desc_type);
    configfs_add_default_group(&gi->os_desc_group, &gi->group);
...
gi->composite.gadget_driver = configfs_driver_template;//跟之前分析的driver注册一样
...
}

上述每个item的注册,分别绑定了不同回调,上层在对应文件夹操作时,不同回调将被调用,其中gadget_root_type分别注册了不同的文件,用户将访configfs文件实现的回调show和stroe。

CONFIGFS_ATTR(gadget_dev_desc_, bDeviceClass);
CONFIGFS_ATTR(gadget_dev_desc_, bDeviceSubClass);
CONFIGFS_ATTR(gadget_dev_desc_, bDeviceProtocol);
CONFIGFS_ATTR(gadget_dev_desc_, bMaxPacketSize0);
CONFIGFS_ATTR(gadget_dev_desc_, idVendor);
CONFIGFS_ATTR(gadget_dev_desc_, idProduct);
CONFIGFS_ATTR(gadget_dev_desc_, bcdDevice);
CONFIGFS_ATTR(gadget_dev_desc_, bcdUSB);
CONFIGFS_ATTR(gadget_dev_desc_, UDC);
static struct configfs_attribute *gadget_root_attrs[] = {
    &gadget_dev_desc_attr_bDeviceClass,
    &gadget_dev_desc_attr_bDeviceSubClass,
    &gadget_dev_desc_attr_bDeviceProtocol,
    &gadget_dev_desc_attr_bMaxPacketSize0,
    &gadget_dev_desc_attr_idVendor,
    &gadget_dev_desc_attr_idProduct,
    &gadget_dev_desc_attr_bcdDevice,
    &gadget_dev_desc_attr_bcdUSB,
    &gadget_dev_desc_attr_UDC,
    NULL,
};
static struct config_item_type gadget_root_type = {
    .ct_item_ops    = &gadget_root_item_ops,
    .ct_attrs   = gadget_root_attrs,
    .ct_owner   = THIS_MODULE,
};

CONFIGFS_ATTR的定义:

#define CONFIGFS_ATTR(_pfx, _name)          \
static struct configfs_attribute _pfx##attr_##_name = { \
    .ca_name    = __stringify(_name),       \
    .ca_mode    = S_IRUGO | S_IWUSR,        \
    .ca_owner   = THIS_MODULE,          \
    .show       = _pfx##_name##_show,       \
    .store      = _pfx##_name##_store,      \
}

即此时用户也要实现show和store的回调,从上述的定义上看,当configfs usb被挂载时,将在gadget下生成bDeviceClass、bDeviceSubClass....bcdUSB和UDC这些文件,只要用户访问这些文件,store或者show回调将被调用,已触发配置生效的回调为例子:

上面使用configfs触发生效的配置是将控制器名称写入udc

echo fe800000.dwc3 > /sys/kernel/config/usb_gadget/g1/UDC

此时源码回调的是gadget_dev_desc_UDC_store,该函数最重要的是进行设备的注册,跟前面以drivers/usb/gadget/legacy/serial.c源码进行注册效果一样:

static ssize_t gadget_dev_desc_UDC_store(struct config_item *item,
        const char *page, size_t len)
{
    struct gadget_info *gi = to_gadget_info(item);
    ...
    if (!strlen(name) || strcmp(name, "none") == 0) {//设置none关闭
       ...
    } else {
        ...
        ret = usb_udc_attach_driver(name, &gi->composite.gadget_driver);//绑定driver到框架中
       ...
        gi->udc_name = name;
    }
  ...
}

drivers/usb/gadget/udc/core.c

int usb_udc_attach_driver(const char *name, struct usb_gadget_driver *driver)
{
    struct usb_udc *udc = NULL;
   ...
list_for_each_entry(udc, &udc_list, list) {
//从udc_list里找出设备名称一致的udc控制器对象,再进行下一步绑定,这里name就是用户配置UDC的名称
        ret = strcmp(name, dev_name(&udc->dev));
        if (!ret)
            break;
    }
    if (ret) {
        ret = -ENODEV;
        goto out;
    }
    if (udc->driver) {//该udc的driver需要没被绑定过,例如前面如果用源码方式注册设备,则configfs不生效。
        ret = -EBUSY;
        goto out;
    }
    ret = udc_bind_to_driver(udc, driver);//将udc和driver绑定,并进行前面初始化过程一样的初始化。
...}

多控制器/设备的配置

前面进行configfs挂载后,在gadget下创建了一个g1实例usb设备,芯片如果支持多个usb控制器,此时根据usb控制器再创建g2,g3,configfs将重复上面分析的过程,进行对应的控制器绑定driver过程。

# 创建g1目录,实例化一个新的gadget模板

mkdir -m 0770 /sys/kernel/config/usb_gadget/g1

对于单个控制器,支持复合设备例如uac+uvc,或者uac+cdc+uvc等,用户在configs文件夹配创建c1配置

# Creating Config echo "Creating Config" 

mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/configs/c.1

Usb configfs源码在configs创建文件夹时绑定make_group回调如下:

static struct configfs_group_operations config_desc_ops = {
    .make_group     = &config_desc_make,
    .drop_item      = &config_desc_drop,
};
static struct config_item_type config_desc_type = {
    .ct_group_ops   = &config_desc_ops,
    .ct_owner       = THIS_MODULE,
};

该回调主要进行config的注册,同时在该文件夹下增加了一个item,string文件夹。

static struct config_group *config_desc_make(struct config_group *group,const char *name)
{
    struct gadget_info *gi;
    struct config_usb_cfg *cfg;
    ...
    gi = container_of(group, struct gadget_info, configs_group);
  ....
    config_group_init_type_name(&cfg->group, name,&gadget_config_type);
    config_group_init_type_name(&cfg->strings_group, "strings",&gadget_config_name_strings_type);
    configfs_add_default_group(&cfg->strings_group, &cfg->group);
    ret = usb_add_config_only(&gi->cdev, &cfg->c);//增加一个设备配置,该配置被维护在cdev中,后续同个控制的配置都在该cdev下
  ...
}

上面configs的生成被放到了cdev链表中,前面分析的host获取config数量也是从cdev中取,即有多少个设备config,将上送给host对应数量的值。

int usb_add_config_only(struct usb_composite_dev *cdev,
        struct usb_configuration *config)
{
    struct usb_configuration *c;
...
    /* Prevent duplicate configuration identifiers */
    list_for_each_entry(c, &cdev->configs, list) {//确保配置在链表中的需要不一样,否则认为该配置已被注册
        if (c->bConfigurationValue == config->bConfigurationValue)
            return -EBUSY;
    }
    config->cdev = cdev;
    list_add_tail(&config->list, &cdev->configs);//加入cdev维护的链表
...    return 0;
}

上述只分支了udc和config,还有其他参数文件的处理过程逻辑一样,篇幅较长笔者不再进行分析。

这里需要提下不同设备有不同的配置,例如uac有关于音频参数的配置,在uac源码中生成drivers/usb/gadget/function/f_uac2.c:

UAC_ATTRIBUTE(p_chmask);
UAC_ATTRIBUTE(p_ssize);
UAC_ATTRIBUTE(p_feature_unit);
UAC_ATTRIBUTE(c_chmask);
UAC_ATTRIBUTE(c_ssize);
UAC_ATTRIBUTE(c_feature_unit);
UAC_ATTRIBUTE(req_number);
UAC_RATE_ATTRIBUTE(p_srate);
UAC_RATE_ATTRIBUTE(c_srate);
static struct configfs_attribute *f_uac2_attrs[] = {
    &f_uac_opts_attr_p_chmask,
    &f_uac_opts_attr_p_srate,
    &f_uac_opts_attr_p_ssize,
    &f_uac_opts_attr_p_feature_unit,
    &f_uac_opts_attr_c_chmask,
    &f_uac_opts_attr_c_srate,
    &f_uac_opts_attr_c_ssize,
    &f_uac_opts_attr_c_feature_unit,
    &f_uac_opts_attr_req_number,
    NULL,
};

这些参数在初始化增加一个function时配置

DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc);
afunc_alloc_inst
    config_group_init_type_name(&opts->func_inst.group, "",&f_uac2_func_type);

3.2 usb host

对驱动用户来说,实现usb host设备,只需要开发usb设备驱动层,控制器等由芯片厂商或者usb ip厂商提供,如下图是usb作为host的分层关系。

3.2.1 创建一个usb设备

3.2.1.1 流程概览

对驱动开发者来说,想创建一个usb设备,只需要开发usb设备驱动层,添加usb设备驱动程序,注册driver到usb core,usb core在device接入时将根据用户提供的设备id来匹配是否是用户驱动。

分析drivers/media/usb/uvc/uvc_driver.c uvc驱动来分析创建一个usb设备过程。uvc总体模块关系如下:

uvc_driver.c实现的是uvc应用驱动这一层,该层向上注册进了v4l2框架,上层应用可通过v4l2访问到usb。下层则是前面介绍过的usb core和usb host控制器驱动,最后访问到硬件。usb设备驱动注册进v4l2时,将被模拟成一个v4l2设备,对于uvc则是v4l2 output类型设备。

3.2.1.2 uvc代码流程介绍

        usb设备注册使用usb_register,提供struct usb_device_id 并实现必要成员,这里比较关键的是id_table,当接入的device枚举到的设备id和定义的(match_flags、idVendor、idProduct)一样,该驱动的probe函数将被调用。如下代码,uvc_ids结构体实现了很多id,当host检查的device接入在枚举过程中匹配到任意id,uvc_probe函数将被调用。

static struct usb_device_id uvc_ids[] = {
    /* LogiLink Wireless Webcam */
    { .match_flags      = USB_DEVICE_ID_MATCH_DEVICE
                | USB_DEVICE_ID_MATCH_INT_INFO,
      .idVendor     = 0x0416,
      .idProduct        = 0xa91a,
      .bInterfaceClass  = USB_CLASS_VIDEO,
      .bInterfaceSubClass   = 1,
      .bInterfaceProtocol   = 0,
      .driver_info      = (kernel_ulong_t)&uvc_quirk_probe_minmax },
....
};
struct uvc_driver uvc_driver = {
    .driver = {
        .name       = "uvcvideo",
        .probe      = uvc_probe,
        .disconnect = uvc_disconnect,
        .suspend    = uvc_suspend,
        .resume     = uvc_resume,
        .reset_resume   = uvc_reset_resume,
        .id_table   = uvc_ids,
        .supports_autosuspend = 1,
    },
};
static int __init uvc_init(void)
{
    int ret;
    uvc_debugfs_init();
    ret = usb_register(&uvc_driver.driver);
    if (ret < 0) {
        uvc_debugfs_cleanup();
        return ret;
    }
    printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");
    return 0;
}
module_init(uvc_init);

uvc_probe函数主要进行设备初始化,包括v4l2、uvc等。

static int uvc_probe(struct usb_interface *intf,
             const struct usb_device_id *id)
{
...
    /* Parse the Video Class control descriptor. */
    if (uvc_parse_control(dev) < 0) {
        uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC "
            "descriptors.\n");
        goto error;
    }
...
//v4l2 注册
    if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)
        goto error;
    /* Initialize controls. */
    if (uvc_ctrl_init_device(dev) < 0)
        goto error;
...
    /* Register video device nodes. */
    if (uvc_register_chains(dev) < 0)
        goto error;
...
}

device在接入后触发的Probe主要完成了v4l2的初始化,device后续发送数据将CPU将通过中断来通知,uvc采用bulk方式传输,接收device的数据流程如下:

注册的vb2_ops里,当上层用户开启流时,uvc_start_streaming将被调用

static struct vb2_ops uvc_queue_qops = {
   ...
    .start_streaming = uvc_start_streaming,
    .stop_streaming = uvc_stop_streaming,
};

uvc_start_streaming
=>uvc_start_streaming
==>uvc_init_video
===>uvc_init_video_bulk
        usb_fill_bulk_urb(urb, stream->dev->udev, pipe, uvc_urb->buffer,
                  size, uvc_video_complete, uvc_urb);

使用usb_fill_bulk_urb注册接收数据处理函数uvc_video_complete,在uvc_video_complete里处理接收的数据。

static void uvc_video_complete(struct urb *urb)
{
    ...
    buf = uvc_queue_get_current_buffer(queue);
    /* Re-initialise the URB async work. */
    uvc_urb->async_operations = 0;
    if (vb2_qmeta) {
        spin_lock_irqsave(&qmeta->irqlock, flags);
        if (!list_empty(&qmeta->irqqueue))
            buf_meta = list_first_entry(&qmeta->irqqueue,struct uvc_buffer, queue);
        spin_unlock_irqrestore(&qmeta->irqlock, flags);
    }
    /*
     * Process the URB headers, and optionally queue expensive memcpy tasks
     * to be deferred to a work queue.
     */
    stream->decode(uvc_urb, buf, buf_meta);
    /* If no async work is needed, resubmit the URB immediately. */
    if (!uvc_urb->async_operations) {
        ret = usb_submit_urb(uvc_urb->urb, GFP_ATOMIC);
       ...
        return;
    }
    INIT_WORK(&uvc_urb->work, uvc_video_copy_data_work);
    queue_work(stream->async_wq, &uvc_urb->work);
}

3.2.2 usb host设备识别流程

当设备接入时,cpu通过usb hub中断通知用户处理,usb hub中断处理函数如下:

usb_hub_init
=>hub_probe
   ==>hub_configure
    usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
        hub, endpoint->bInterval);

hub_irq即是usb收到的端点数据是进入的中断

hub_irq
=>kick_hub_wq
queue_work(hub_wq, &hub->events)

唤醒工作队列

hub_event
=>port_event
==>hub_port_connect_change
===>hub_port_connect
usb_alloc_dev
=====>hub_port_init
hub_set_address
        =====>usb_new_device
   =======>device_add

4 工具

USB Device Viewer

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/936032.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一键找出图像中物体的角点

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

考研数学【线性代数基础box(数二)】

本文是对数学二线性代数基础进行总结&#xff0c;一些及极其简单的被省略了&#xff0c;代数的概念稀碎&#xff0c;不如高数关联性高&#xff0c;所以本文仅供参考&#xff0c;做题请从中筛选&#xff01; 本文为初稿&#xff0c;后面会根据刷题和自己的理解继续更新 第一章…

警惕!手动调整服务器时间可能引发的系统灾难

警惕&#xff01;手动调整服务器时间可能引发的系统灾难 1. 鉴权机制1.1 基于时间戳的签名验证1.2 基于会话的认证机制&#xff08;JWT、TOTP&#xff09; 2. 雪花算法生成 ID 的影响2.1 时间戳回拨导致 ID 冲突2.2 ID 顺序被打乱 3. 日志记录与审计3.1 日志顺序错误3.2 审计日…

【Linux】Systemtap在CentsOS9上测试成功了

在Ubuntu上测试没有成功&#xff0c;先看看运行成功的效果吧&#xff1a; 看到运行的效果&#xff0c;可以安心些&#xff0c;哈哈 指导操作来源于这里&#xff1a;SystemTap 主要来源于这里&#xff1a; https://sourceware.org/systemtap/SystemTap_Beginners_Guide/using-s…

【EthIf-03】 EthernetInterface软件栈的文件组织结构

上图为《AUTOSAR_SWS_EthernetInterface【v2.2.0 】》给出的EthernetInterface软件栈的文件组织结构,本文主要关注arccore代码中已存在的文件的功能和作用,不知道的小伙伴可以查看🔗EthIf的文件结构中的src和inc目录下的文件有哪些: 1. 文件结构 1.1 EthIf_Cbk.h 头文…

【LeetCode刷题之路】622.设计循环队列

LeetCode刷题记录 &#x1f310; 我的博客主页&#xff1a;iiiiiankor&#x1f3af; 如果你觉得我的内容对你有帮助&#xff0c;不妨点个赞&#x1f44d;、留个评论✍&#xff0c;或者收藏⭐&#xff0c;让我们一起进步&#xff01;&#x1f4dd; 专栏系列&#xff1a;LeetCode…

基于windows环境使用nvm安装多版本nodejs

目录 前言 一、卸载node 二、nvm是什么&#xff1f; 三、nvm安装 1.官网下载 nvm 包 2. 安装nvm-setup.exe 3. 配置路径和下载镜像 4. 检查安装是否完成 四、 使用nvm安装node 五、修改npm默认镜像源为淘宝镜像 六、环境变量配置 1. 新建目录 2. 设置环境变量 七…

Neo4j+Neovis+Vue3:前端连接数据库渲染

Neovis&#xff08;github&#xff09;&#xff1a;https://github.com/neo4j-contrib/neovis.js Neovis配置文档&#xff1a;neovis.js (neo4j-contrib.github.io) 一、安装Neo4j 参考文章&#xff1a;neo4j下载安装配置步骤-CSDN博客 二、Neovis使用 1.npm引入 ?npm ins…

《宇宙机器人》提示错误弹窗“找不到d3dx9_43.dll”是什么原因?“d3dx9_43.dll缺失”怎么解决?

电脑游戏运行时常见问题解析&#xff1a;《宇宙机器人》提示“找不到d3dx9_43.dll”的解决之道 TGA2024落幕&#xff0c;年度最佳游戏——《宇宙机器人》&#xff0c;作为一名在软件开发领域深耕多年的从业者&#xff0c;我深知电脑游戏在运行过程中可能会遇到的各种挑战&…

Cesium 限制相机倾斜角(pitch)滑动范围

1.效果 2.思路 在项目开发的时候&#xff0c;有一个需求是限制相机倾斜角&#xff0c;也就是鼠标中键调整视图俯角时&#xff0c;不能过大&#xff0c;一般 pitch 角度范围在 0 至 -90之间&#xff0c;-90刚好为正俯视。 在网上查阅了很多资料&#xff0c;发现并没有一个合适的…

28. Three.js案例-创建圆角矩形并进行拉伸

28. Three.js案例-创建圆角矩形并进行拉伸 实现效果 知识点 WebGLRenderer (WebGL渲染器) WebGLRenderer 是 Three.js 中用于渲染 3D 场景的主要渲染器。 构造器 WebGLRenderer( parameters : Object ) 参数类型描述parametersObject渲染器的配置参数&#xff0c;可选。 …

transformer学习笔记-自注意力机制(2)

经过上一篇transformer学习笔记-自注意力机制&#xff08;1&#xff09;原理学习&#xff0c;这一篇对其中的几个关键知识点代码演示&#xff1a; 1、整体qkv注意力计算 先来个最简单未经变换的QKV处理&#xff1a; import torch Q torch.tensor([[3.0, 3.0,0.0],[0.5, 4…

基于米尔全志T527开发板的OpenCV进行手势识别方案

本文将介绍基于米尔电子MYD-LT527开发板&#xff08;米尔基于全志T527开发板&#xff09;的OpenCV手势识别方案测试。 摘自优秀创作者-小火苗 米尔基于全志T527开发板 一、软件环境安装 1.安装OpenCV sudo apt-get install libopencv-dev python3-opencv 2.安装pip sudo apt…

arXiv-2024 | VLM-GroNav: 基于物理对齐映射视觉语言模型的户外环境机器人导航

作者&#xff1a; Mohamed Elnoor, Kasun Weerakoon, Gershom Seneviratne, Ruiqi Xian, Tianrui Guan, Mohamed Khalid M Jaffar, Vignesh Rajagopal, and Dinesh Manocha单位&#xff1a;马里兰大学学院公园分校原文链接&#xff1a;VLM-GroNav: Robot Navigation Using Phys…

Typora 修改默认的高亮颜色

shift F12 参考 怎么给typora添加颜色&#xff1f;

基于阿里云Ubuntu22.04 64位服务器Java及MySql环境配置命令记录

基于阿里云Ubuntu22.04 64位服务器Java及MySql环境配置命令记录 Java 23 离线环境配置MySql 环境配置MySQL常用命令 Java 23 离线环境配置 下载 Ubuntu环境下 Java 23 离线包 链接: java Downloads. 在Linux环境下创建一个安装目录 mkdir -p /usr/local/java将下载好的jdk压缩…

【树莓派4B】MindSpore lite 部署demo

一个demo&#xff0c;mindspore lite 部署在树莓派4B ubuntu22.04中&#xff0c;为后续操作开个门&#xff01; 环境 开发环境&#xff1a;wsl-ubuntu22.04分发版部署环境&#xff1a;树莓派4B&#xff0c;操作系统为ubuntu22.04mindspore lite版本&#xff1a;mindspore-li…

RK3576 Android14,内存大于4G时UVC应用无法申请内存

最近有个项目需要将Linux虚拟成UVC摄像头&#xff0c;开发过程中遇到一个奇怪的事情&#xff0c;通过V4l2框架接口申请内存时&#xff0c;相同的板子&#xff0c;只是内存一个4G一个8G。4G的内存可以申请成功&#xff0c;8G就不行。提示“内存不足” 内存更大反而内存不足&…

HarmonyOS-高级(四)

文章目录 应用开发安全应用DFX能力介绍HiLog使用指导HiAppEvent &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;HarmonyOS专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年12月11日11点18分 应用开发安全 应用隐私保护 隐私声明弹窗的作…

函数与结构体(入门6)

【深基7.例1】距离函数 #include <iostream> #include <iomanip> #include <cmath> using namespace std; int main() {double x1, x2, x3, y1, y2, y3;cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3;double d1 pow(pow(…