IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> Linux之USB分析 -> 正文阅读

[嵌入式]Linux之USB分析

一、USB概念概述

USB1.0版本速度1.5Mbps(低速USB) USB1.1版本速度12Mbps(全速USB) USB2.0版本速度480Mbps(高速USB)。
在这里插入图片描述
USB 分为主从两大体系,一般而言, PC 中的 USB 系统就是作主,而一般的 USB 鼠标, U 盘则是典型的 USB 从系统。

下是简单的列出了 USB 设备类型,理想的情况 USB 系统要对这些设备作完整的支持,设备也必须符合 USB 规范中的要求。
在这里插入图片描述
随着 USB 技术的发展, USB 系统中的一些不足也逐渐被承认, OTG 就是这种情况下的主要产物,OTG(On-The-Go), 即可以作主也可以作从,传说中的雌雄同体。这主要是为嵌入式设备准备的,因为 USB 是一种主从系统,不能支持点对点平等的传输数据, OTG 正是在这种需求下产生的, OTG 不仅支持控制器的主从切换,在一定层度上,也支持相同设备之间的数据交换

1.USB传输线及供电

一条USB的传输线分别由地线、电源线、D+、D-四条线构成,D+和D-是差分输入线(抗干扰),它使用的是3.3V的电压,而电源线和地线可向设备提供5V电压,最大电流为500MA。OTG 的做法就是增来一个 ID pin 来判断设备是接入设备的是主还是从(ID高则必为主,低则根据协议判断主从)。vbus 主要是供电, D+/D- 则是用来传输数据,就是我们前面所讲的主设备和从设备间唯一的一条铁路
在这里插入图片描述
在这里插入图片描述
USB设备有两种供电方式
? 自供电设备:设备从外部电源获取工作电压
? 总线供电设备:设备从VBUS(5v) 取电

对总线供电设备,区分低功耗和高功耗USB设备
? 低功耗总线供电设备:最大功耗不超过100mA
? 高功耗总线供电设备: 枚举时最大功耗不超过100mA,枚举完成配置结束后功耗不超过500mA

设备在枚举过程中,通过设备的配置描述符向主机报告它的供电配置(自供电/总线供电)以及它的功耗要求

2.USB可以热插拔的硬件原理

USB主机是如何检测到设备的插入的呢?首先,在USB集线器的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。而在USB设备端,在D+或者D-上接了1.5K欧姆上拉电阻。对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。这样,当设备插入到集线器时,由1.5K的上拉电阻和15K的下拉电阻分压,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到高速模式的。在高速模式下,是电流传输模式,这时将D+上的上拉电阻断开。
在这里插入图片描述

3.USB设备的构成

USB设备的构成包括了配置,接口和端点。
在这里插入图片描述
需要注意的是,驱动是绑定到USB接口上,而不是整个设备。
在这里插入图片描述
1.设备通常具有一个或者更多个配置
2.配置经常具有一个或者更多个接口
3.接口通常具有一个或者更多个设置
4.接口没有或者具有一个以上的端点

配置由接口组成,每个设备都有不同级别的配置信息;
接口由多个端点组成,代表一个基本的功能;
端点是USB设备中的唯一可寻址部分,可以理解为USB设备或主机上的一个数据缓冲区。
配置和设置的理解:一个手机可以有多重配置,比如可以作为电话,可以接在PC上当成一个U盘,这两种情况就属于不同的配置。再来看设置,一个手机作为电话已经确定了,但是通话场景(室外模式,会议模式等等)可以改变,每种场景就可以算一个设置。

例如:一个USB播放器带有音频,视频功能,还有旋钮和按钮。
配置1:音频(接口) + 旋钮(接口)
配置2:音频(接口) + 视频(接口) + 旋钮(接口)
配置3:视频(接口) + 旋钮(接口)

每一个接口均需要一个驱动程序。每个USB设备有一个唯一的地址,这个地址是在设备连上主机时,由主机分配的,而设备中的每个端点在设备内部有唯一的端点号,这个端点号是在设计设备时给定的。每个端点都是一个简单的连接点,是单向的。

端点0是一个特殊的端点,用于设备枚举和对设备进行一些基本的控制功能。除了端点0,其余的端点在设备配置之前不能与主机通信,只有向主机报告这些端点的特性并被确认后才能被激活。

例如:
USB总线,类似于高速公路;
收发的数据,类似于汽车;
USB端点,类似于高速公路收费站的入口或出口。

a.USB描述符

当USB设备插入到主机系统中,主机系统会自动检测USB设备的相关信息,就是通过USB描述符来实现的。

标准的USB设备有五种USB描述符:

  1. 设备描述符
  2. 配置描述符
  3. 接口描述符
  4. 端点描述符
  5. 字符串描述符(可选项)

一个设备只有一个设备描述符,而一个设备描述符可以包含多个配置描述符,而一个配置描述符可以包含多个接口描述符,一个接口使用了几个端点,就有几个端点描述符。

关于字符串描述符:在USB中,字符串描述符是可选的,也就是属于可有可无的角色,USB并没有强制规定必须有,但是一般产品是有的,至少能说明生产厂家、产品信息等等,如果设备没有字符串描述符,那么在设备描述符、配置描述符、接口描述符等处的字符串索引值必须为0,要不然在枚举过程中,USB主机会尝试去获取字符串描述符,而刚好你又没有,那么枚举就会失败,所以必须指定为0

b.硬件构成

在这里插入图片描述
高速模块一般分为控制器Controller和PHY两部分,Controller大多为数字逻辑实现,PHY通常为模拟逻辑实现。

USB芯片也分为Controller部分和PHY部分。Controller部分主要实现USB的协议和控制。内部逻辑主要有MAC层、CSR层和FIFO控制层,还有其他低功耗管理之类层次。MAC实现按USB协议进行数据包打包和解包,并把数据按照UTMI总线格式发送给PHY(USB3.0为PIPE)。CSR层进行寄存器控制,软件对USB芯片的控制就是通过CSR寄存器,这部分和CPU进行交互访问,主要作为Slave通过AXI或者AHB进行交互。FIFO控制层主要是和DDR进行数据交互,控制USB从DDR搬运数据的通道,主要作为Master通过AXI/AHB进行交互。PHY部分功能主要实现并转串的功能,把UTMI或者PIPE口的并行数据转换成串行数据,再通过差分数据线输出到芯片外部。

USB芯片内部实现的功能就是接受软件的控制,进而从内存搬运数据并按照USB协议进行数据打包,并串转换后输出到芯片外部。或者从芯片外部接收差分数据信号,串并转换后进行数据解包并写到内存里。

4.USB传输事务

USB通信最基本的形式是通过一个名为端点(endpoint)的东西。它是真实存在的。

端点只能往一个方向传送数据(端点0除外,端点0使用message管道,它既可以IN又可以OUT),或者IN,或者OUT。除了端点0,低速设备只能有2个端点,高速设备也只能有15个IN端点和15个OUT端点。

主机和端点之间的数据传输是通过管道。端点只有在device上才有,协议说端点代表在主机和设备端点之间移动数据的能力。USB通信都是由host端发起的。

在这里插入图片描述
首先明确一点USB协议规定所有的数据传输都必须由主机发起。所以这个传输的一般格式:令牌包(表明传输的类型),数据包(实际传输的数据),握手包(数据的正确性)。首先是由主机控制器发出令牌包,然后主机/设备发送数据包,甚至可以没有,最后设备/主机发送握手包,这么一个过程就叫做一个USB传输事务。一个USB传输事务就实现了一次从主机和设备间的通讯。

a.四种传输类型

端点有4中不同的类型:控制,批量,等时,中断。对应USB的4种不同的传输类型:

  1. 控制传输:适用于小量的,对传输时间和速率没有要求的设备。如USB设备配置信息。
  2. 批量传输:适用于类似打印机,扫描仪等传输量大,但对传输时间和速度无要求的设备。
  3. 等时传输:适用于大量的,速率恒定,具有周期性的数据,对实时性有要求的,比如音视频。
  4. 中断传输:适用于非大量,但具有周期性的数据,比如鼠标键盘。当USB宿主要求设备传输数据时,中断端点会以一个固定的数率传输数据。鼠标,键盘以及游戏手柄等。此种中断和经常说的硬件中断是不同的,此种中断会以固定的时间间隔来查询USB设备。

b.四种事务类型

一次传输由一个或多个事务构成。

  1. IN:IN事务为host输入服务,当host需要从设备获得数据的时候,就需要IN事务。
  2. OUT:OUT事务为host输出服务,当host需要输出数据到设备的时候,就需要OUT事务。
  3. SETUP:SETUP事务为host控制服务,当host希望传输一些USB规范的默认操作的时候就需要使用setup事务。
  4. SOF:这个用于帧同步

c.四种包(package)类型

一个事务由一个或多个包构成,包可分为令牌包(setup),数据包(data),握手包(ACK)和特殊包

  1. 令牌包:可分为OUT包、IN包、SetUp包和帧起始包,OUT包就是说明接下来的数据包的方向时从主机到设备。
  • SYNC + PID + ADDR + ENDP + CRC5 :(同步) + (IN/OUT/SetUp) + (设备地址)+(设备端点) + (校验)
  1. 数据包:里面包含的就是我们实际要传输的东西。分为DATA0包和DATA1包,当USB发送数据的时候,当一次发送的数据长度大于相应端点的容量时,就需要把数据包分为好几个包,分批发送,DATA0包和DATA1包交替发送,即如果第一个数据包是 DATA0,那第二个数据包就是DATA1。
  • SYNC + PID + DATA0/1 + CRC5:(同步) + (DATA0/1) + (数据) + (校验)。
  • 但也有例外情况,在同步传输中(四类传输类型中之一),所有的数据包都是为DATA0,格式如下: SYNC + PID + 0~1023字节 + CRC16:(同步) + (DATA0) + (数据) + (校验)。
  1. 握手包:发送方发送了数据,接受方收应答。
  • SYNC+PID:(同步)+(HandShake)

d.域

一个包由多个域构成,域可分为同步域(SYNC),标识域(PID),地址域(ADDR),端点域(ENDP),帧号域(FRAM),数据域(DATA),校验域(CRC)。
下图是一个USB鼠标插入Linux系统时完整的枚举过程
在这里插入图片描述
这里有一个概念需要注意,这里的中断传输与硬件中断那个中断是不一样的,这个中断传输实际是靠USB host control轮询usb device来实现的,而USB host control对于CPU则是基于中断的机制。

拿USB鼠标为例,USB host control对USB鼠标不断请求,这个请求的间隔是很短的,在USB spec Table 9-13端点描述符中的bInterval域中指定的,当鼠标发生过了事件之后,鼠标会发送数据回host,这时USB host control中断通知CPU,于是usb_mouse_irq被调用,在usb_mouse_irq里,就可以读取鼠标发回来的数据,当读完之后,驱动再次调用usb_submit_urb发出请求,就这么一直重复下去,一个usb鼠标的驱动也就完成了。

下面是USB鼠标中断传输图,可以看到USB host control向usb device发送了IN包,没有数据的时候device回复的是NAK,有数据的时候才向host control发送DATA包。
在这里插入图片描述

5.USB设备被识别的过程

当USB设备插上主机时,主机就通过一系列的动作来对设备进行枚举配置。

1、接入态(Attached):设备接入主机后,主机通过检测信号线上的电平变化来发现设备的接入;
  2、供电态(Powered):就是给设备供电,分为设备接入时的默认供电值,配置阶段后的供电值(按数据中要求的最大值,可通过编程设置)
  3、缺省态(Default):USB在被配置之前,通过缺省地址0与主机进行通信;
  4、地址态(Address):经过了配置,USB设备被复位后,就可以按主机分配给它的唯一地址来与主机通信,这种状态就是地址态;
  5、配置态(Configured):通过各种标准的USB请求命令来获取设备的各种信息,并对设备的某此信息进行改变或设置。
  6、挂起态(Suspended):总线供电设备在3ms内没有总线动作,即USB总线处于空闲状态的话,该设备就要自动进入挂起状态,在进入挂起状态后,总的电流功耗不超过280UA。

6.标准的USB设备请求命令

USB设备请求命令是在控制传输的第一个阶段:setup事务传输的数据传输阶段发送给设备的。

标准USB设备请求命令共有11个,大小都是8个字节,具有相同的结构,由5 个字段构成。通过标准USB准设备请求,我们可以获取存储在设备EEPROM里面的信息;知道设备有哪些的设置或功能;获得设备的运行状态;改变设备的配置等。

标准USB准设备请求 = bmRequestType(1) + bRequest(1) + wvalue(2) + wIndex(2) + wLength(2)
在这里插入图片描述

a.bmRequestType

[7 bit]= 0主机到设备; 1设备到主机
[6-5 bit]= 00标准请求命令; 01类请求命令; 10用户定义命令; 11保留
[4-0 bit]= 00000 接收者为设备; 00001 接收者为接口; 00010 接收者为端点; 00011 接收者为其他接收者; 其他 其他值保留

b.bRequest

0) 0 GET_STATUS:用来返回特定接收者的状态
1) 1 CLEAR_FEATURE:用来清除或禁止接收者的某些特性
2) 3 SET_FEATURE:用来启用或激活命令接收者的某些特性
3) 5 SET_ADDRESS:用来给设备分配地址
4) 6 GET_DEscriptOR:用于主机获取设备的特定描述符
5) 7 SET_DEscriptOR:修改设备中有关的描述符,或者增加新的描述符
6) 8 GET_CONFIGURATION:用于主机获取设备当前设备的配置值、
7) 9 SET_CONFIGURATION:用于主机指示设备采用的要求的配置
8) 10 GET_INTERFACE:用于获取当前某个接口描述符编号
9) 11 SET_INTERFACE:用于主机要求设备用某个描述符来描述接口
10) 12 SYNCH_FRAME:用于设备设置和报告一个端点的同步

wvalue: 这个字段是 request 的参数,request 不同,wValue就不同。
wIndex:wIndex,也是request 的参数,bRequestType指明 request 针对的是设备上的某个接口或端点的时候,wIndex 就用来指明是哪个接口或端点。
wLength:控制传输中 DATA transaction 阶段的长度。

二、 USB关键数据结构分析

在这里插入图片描述

1. USB设备结构体

在内核中使用数据结构 struct usb_device来描述整个USB设备

struct usb_device {
    int     devnum;  // 设备号,是在USB总线的地址
    char    devpath[16];  // 用于消息的设备ID字符串
    enum usb_device_state   state;  // 设备状态:已配置、未连接等等
    enum usb_device_speed   speed;  // 设备速度:高速、全速、低速或错误

    struct usb_tt   *tt;  // 事务转换,用于高速设备像低速设备或反过来的数据交互。从USB 2.0开始,全速/低速设备被隔离成树。一种是从USB 1.1主机控制器(OHCI、UHCI等)发展而来。另一种类型是在使用“事务转换器”(TTs)连接到全/低速设备时从高速集线器发展而来的
    int     ttport;  // 位于tt HUB的设备口

    unsigned int toggle[2];  // 每个端点的占一位,表明端点的方向([0] = IN, [1] = OUT)

    struct usb_device *parent;  // 上一级HUB
    struct usb_bus *bus;  
    struct usb_host_endpoint ep0;  // 端点0数据

    struct device dev;  // 一般的设备接口数据结构

    struct usb_device_descriptor descriptor;  // USB设备描述符
    struct usb_host_config *config;  // 设备的所支持的配置,结构体里包含了配置描述符

    struct usb_host_config *actconfig;  // 被激活的设备配置
    struct usb_host_endpoint *ep_in[16];  // 输入端点数组
    struct usb_host_endpoint *ep_out[16];  // 输出端点数组

    unsigned short bus_mA;  // 可使用的总线电流
    u8 portnum;  // 父端口号
    u8 level;  // USB HUB的层数

    unsigned can_submit:1;  // URB可被提交标志
    unsigned persist_enabled:1;  // USB_PERSIST使能标志
    unsigned have_langid:1;  // string_langid存在标志
    unsigned authorized:1;  // 经授权的
    unsigned authenticated:1;  // 认证
    unsigned wusb:1;  // 无线USB标志
    int string_langid;

    /* static strings from the device 设备的静态字符串 */
    char *product;
    char *manufacturer;
    char *serial;

    struct list_head filelist;  // 此设备打开的usbfs文件

#if defined(CONFIG_USB_DEVICEFS)
    struct dentry *usbfs_dentry;    /* 设备的usbfs入口 */
#endif

    int maxchild;  // (若为HUB)接口数

    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;  // (如果为无线USB)连接到WUSB特定的数据结构
};

2. USB四大描述符

在USB描述符中,从上到下分为四个层次:USB设备描述符、USB配置描述符、USB接口描述符、USB端点描述符。

? 一个USB设备只有一个设备描述符
? 一个设置描述符可以有多个配置描述符(注:配置同一时刻只能有一个生效,但可以切换)
? 一个配置描述符可以有多个接口描述符(比如声卡驱动,就有两个接口:录音接口和播放接口)
? 一个接口描述符可以有多个端点描述符

详细关系如下图所示
在这里插入图片描述

a. USB设备描述符(usb_device_descriptor)

/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
    __u8  bLength;   // 本描述符的大小(18字节)
    __u8  bDescriptorType;  // 描述符的类型,USB_DT_DEVICE
    __le16 bcdUSB;  // 指明usb的版本,比如usb2.0
__u8  bDeviceClass;  // 类(由USB官方分配),有如下定义,接口描述符中的类也用这些定义
 
    __u8  bDeviceSubClass;  // 子类(由USB官方分配)
    __u8  bDeviceProtocol;  // 指定协议(由USB官方分配)
    __u8  bMaxPacketSize0;  // 端点0对应的最大包大小(有效大小为8,16,32,64)
    __le16 idVendor;  // 厂家id
    __le16 idProduct;  // 产品id
    __le16 bcdDevice;  // 设备的发布号
    __u8  iManufacturer;  // 字符串描述符中厂家ID的索引
    __u8  iProduct;  // 字符串描述符中产品ID的索引
    __u8  iSerialNumber;  // 字符串描述符中设备序列号的索引
    __u8  bNumConfigurations;  // 配置描述符的个数,表示有多少个配置描述符
} __attribute__ ((packed));

linux中类的定义如下
在这里插入图片描述

b. USB配置描述符(usb_config_descriptor)

struct usb_config_descriptor {
    __u8  bLength;  // 描述符的长度
    __u8  bDescriptorType;  // USB_DT_CONFIG
    __le16 wTotalLength;  // 配置 所返回的所有数据的大小,配置描述符,通常将一个配置以及它所包含的接口,接口所包含的端点所有的描述符一次性都获取到,wTotalLength 就是它们全部的长度
    __u8  bNumInterfaces;  // 配置 所支持的接口个数, 表示有多少个接口描述符
    __u8  bConfigurationValue;  // Set_Configuration命令需要的参数值
    __u8  iConfiguration;  // 描述该配置的字符串的索引值
    __u8  bmAttributes;  // 供电模式的选择
    __u8  bMaxPower;  // 设备从总线提取的最大电流
} __attribute__ ((packed));

c. USB接口描述符(usb_interface_descriptor)

USB接口只处理一种USB逻辑连接。一个USB接口代表一个逻辑上的设备,比如声卡驱动,就有两个接口:录音接口和播放接口。这可以在windows系统中看出,有时插入一个USB设备后,系统会识别出多个设备,并安装相应多个的驱动

struct usb_interface_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;  // USB_DT_INTERFACE
    __u8  bInterfaceNumber;  // 接口的编号
    __u8  bAlternateSetting;  // 备用的接口描述符编号,提供不同质量的服务参数
    __u8  bNumEndpoints;  // 要使用的端点个数(不包括端点0), 表示有多少个端点描述符,比如鼠标就只有一个端点
    __u8  bInterfaceClass;  // 接口类型,与驱动的id_table匹配用
    __u8  bInterfaceSubClass;  // 接口子类型
    __u8  bInterfaceProtocol;  // 接口所遵循的协议
    __u8  iInterface;  // 描述该接口的字符串索引值
} __attribute__ ((packed));

d. USB端点描述符(usb_endpoint_descriptor)

struct usb_endpoint_descriptor {
    __u8  bLength; 
    __u8  bDescriptorType;  // USB_DT_ENDPOINT
    __u8  bEndpointAddress;  // 端点地址:0~3位为端点号,第7位为传输方向
    __u8  bmAttributes;  // 端点属性 bit 0-1 00控制 01 同步 02批量 03 中断
__le16 wMaxPacketSize;  // 一个端点的最大包大小(注意这个值为16bit大小,不同于端点0最大只能是64字节) 端点可以一次处理的最大字节数。驱动可以发送比这个值大的数据量到端点, 但是当真正传送到设备时,数据会被分为 wMaxPakcetSize 大小的块。对于高速设备, 通过使用高位部分几个额外位,可用来支持端点的高带宽模式

__u8  bInterval;  // 间隔时间,
// 轮询数据断端点的时间间隔
// 批量传送的端点,以及控制传送的端点,此域忽略
// 对于中断传输的端点,此域的范围为1~255

    /* NOTE:  these two are _only_ in audio endpoints. */
    /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
    __u8  bRefresh;
    __u8  bSynchAddress;
} __attribute__ ((packed));

3. USB接口结构体

USB 端点被绑为接口,USB接口只处理一种USB逻辑连接。一个USB接口代表一个基本功能,每个USB驱动控制一个接口。所以一个物理上的硬件设备可能需要 一个以上的驱动程序。实际上在linux中写的USB驱动大多都是接口驱动,复杂的USB设备驱动已经由usbcore完成了

一个接口可能有多个设置(一个接口多种功能),也就是这些接口所包含的端点凑起来可能有多种功能

struct usb_interface {
    /* 包含所有可用于该接口的可选设置的接口结构数组。每个 struct usb_host_interface 包含一套端点配置(即struct usb_host_endpoint结构所定义的端点配置。这些接口结构没有特别的顺序。 */
    struct usb_host_interface *altsetting; // 这个结构体里包含了接口描述符

    struct usb_host_interface *cur_altsetting;  /* 表示当前激活的接口配置 */
    unsigned num_altsetting;    /* 可选设置的数量 */

    /* 如果有接口关联描述符,那么它将列出关联的接口 */
    struct usb_interface_assoc_descriptor *intf_assoc;

int minor;          /* 如果绑定到这个接口的 USB 驱动使用 USB 主设备号, 这个变量包含由 USB 核心分配给接口的次设备号. 这只在一个成功的调用 usb_register_dev后才有效 */
/* 以下的数据在我们写的驱动中基本不用考虑,系统会自动设置 */
    enum usb_interface_condition condition;     /* state of binding */
    unsigned sysfs_files_created:1; /* the sysfs attributes exist */
    unsigned ep_devs_created:1; /* endpoint "devices" exist */
    unsigned unregistering:1;   /* unregistration is in progress */
    unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */
    unsigned needs_altsetting0:1;   /* switch to altsetting 0 is pending */
    unsigned needs_binding:1;   /* needs delayed unbind/rebind */
    unsigned resetting_device:1;    /* true: bandwidth alloc after reset */
    unsigned authorized:1;      /* used for interface authorization */

    struct device dev;      /* interface specific device info */
    struct device *usb_dev;
    atomic_t pm_usage_cnt;      /* usage counter for autosuspend */
    struct work_struct reset_ws;    /* for resets in atomic context */
};

4. USB端点结构体

USB 通讯的最基本形式是通过一个称为端点的东西。一个USB端点只能向一个方向传输数据(从主机到设备(称为输出端点)或者从设备到主机(称为输入端点))。端点可被看作一个单向的管道(端点0除外)

struct usb_host_endpoint {
    struct usb_endpoint_descriptor      desc; // 端点描述符

    struct list_head        urb_list;  // 本端口对应的urb队列
    void                *hcpriv;
    struct ep_device        *ep_dev;    /* For sysfs info */

    unsigned char *extra;   /* Extra descriptors */
    int extralen;
    int enabled;  // 使能的话urb才能被提交到此端口
    int streams;
};

5. URB(USB Request Block,USB请求块)

a. 结构

USB 请求块是USB 设备驱动中用来描述与USB 设备通信所用的基本载体和核心数据结构,是usb主机与设备通信的电波

struct urb {
    /* 私有的:只能由USB 核心和主机控制器访问的字段 */
    struct kref kref;       /* urb 引用计数 */
    void *hcpriv;           /* 主机控制器私有数据 */
    atomic_t use_count;     /* 并发传输计数 */
    atomic_t reject;        /* 传输将失败 */
    int unlinked;           /* 连接失败代码 */

    /* 公共的:可以被驱动使用的字段 */
    struct list_head urb_list;  /* 链表头 */
    struct list_head anchor_list;   /* the URB may be anchored */
    struct usb_anchor *anchor;
    struct usb_device *dev;     /* 指向这个 urb 要发送的目标 struct usb_device 的指针,这个变量必须在这个 urb 被发送到 USB 核心之前被 USB 驱动初始化 */
    struct usb_host_endpoint *ep;   /* (internal) pointer to endpoint */
    unsigned int pipe;      /* 管道信息 */
    unsigned int stream_id;     /* (in) stream ID */
    int status;         /* (return) URB 的当前状态 */
unsigned int transfer_flags;    /* (in) URB_SHORT_NOT_OK | ...*/

    void *transfer_buffer;      /* 指向用于发送数据到设备(OUT urb)或者从设备接收数据(IN urb)的缓冲区指针。为了主机控制器驱动正确访问这个缓冲, 它必须使用 kmalloc 调用来创建, 不是在堆栈或者静态内存中。 对控制端点, 这个缓冲区用于数据中转 */
    dma_addr_t transfer_dma;    /* (in) 用来以DMA 方式向设备传输数据的缓冲区 */
    struct scatterlist *sg;     /* (in) scatter gather buffer list */
    int num_mapped_sgs;     /* (internal) mapped sg entries */
int num_sgs;            /* (in) number of entries in the sg list */

u32 transfer_buffer_length; /* transfer_buffer 或者 transfer_dma 变量指向的缓冲区大小。如果这是 0, 传送缓冲没有被 USB 核心所使用。对于一个 OUT 端点, 如果这个端点大小比这个变量指定的值小, 对这个 USB 设备的传输将被分成更小的块,以正确地传送数据。这种大的传送以连续的 USB 帧进行。在一个 urb 中提交一个大块数据, 让 USB 主机控制器去划分为更小的块, 比以连续地顺序发送小缓冲的速度快得多 */

u32 actual_length;      /*当这个 urb 完成后, 该变量被设置为这个 urb (对于 OUT urb)发送或(对于 IN urb)接受数据的真实长度.对于 IN urb, 必须是用此变量而非 transfer_buffer_length , 因为接收的数据可能比整个缓冲小 */

    unsigned char *setup_packet;    /* (in) 指向控制URB 的设置数据包的指针 (control only) */
    dma_addr_t setup_dma;       /* (in) 控制URB 的设置数据包的DMA 缓冲区 */
    int start_frame;        /* (modify) 等时传输中用于设置或返回初始帧 (ISO) */
    int number_of_packets;      /* (in) 等时传输中等时缓冲区数量 */
    int interval;           /* (modify) URB 被轮询到的时间间隔(对中断和等时urb 有效)(INT/ISO) */
    int error_count;        /* (return) 等时传输错误数量 */
    void *context;          /* (in) context for completion */
    usb_complete_t complete;    /* (in) 当 urb 被完全传送或发生错误,它将被 USB 核心调用. 此函数检查这个 urb, 并决定释放它或重新提交给另一个传输中 */
    struct usb_iso_packet_descriptor iso_frame_desc[0]; /* (in) 单个URB 一次可定义多个等时传输时,描述各个等时传输ISO ONLY */
};

b. 流程

urb 以一种异步的方式同一个特定USB设备的特定端点发送或接受数据。一个 USB 设备驱动可根据驱动的需要,分配多个 urb 给一个端点或重用单个 urb 给多个不同的端点。设备中的每个端点都处理一个 URB队列, 所以多个 urb 可在队列清空之前被发送到相同的端点
在队列清空之前. 一个 URB的典型生命循环如下:

  1. 被一个 USB驱动(接口)创建.
  • 申请:usb_alloc_urb
  • 释放:usb_free_urb
  1. 安排给一个特定 USB 设备的特定端点(目标USB设备的指定端点)
  • 中断urb:调用usb_fill_int_urb,此时urb绑定了对应设备,pipe指向对应的端点
  • 批量urb:使用usb_fill_bulk_urb()函数来初始化urb
  • 控制urb:使用usb_fill_control_urb()函数来初始化urb
  • 等时urb:手工初始化
  1. 被 USB 设备驱动提交给 USB 核心,
  • 在完成创建和初始化后,urb 便可以提交给USB 核心,通过usb_submit_urb()函数来完成
  • 如果usb_submit_urb()调用成功,即URB 的控制权被移交给USB core。
  1. USB core提交该URB到USB主控制器驱动程序.
  2. USB主控制器驱动程序根据URB描述的信息,来访问USB设备.
  3. 以上操作完成后,USB主机控制器驱动通知 USB 设备驱动.

注:第4和第5步,由USB 核心和主机控制器完成,不受USB 设备驱动的控制

urb 也可被提交它的驱动在任何时间取消;如果设备被移除,urb 可以被USB核心取消。urb 被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被自动释放,如下3 种情况,urb 将结束,urb 完成函数将被调用。

1、 urb 被成功发送给设备,并且设备返回正确的确认。如果urb→status 为0,意味着对于一个输出urb,数据被成功发送;对于一个输入urb,请求的数据被成功收到。
2、 如果发送数据到设备或从设备接收数据时发生了错误,urb→status 将记录错误值.
3、 urb 被从USB 核心“去除连接”,这发生在驱动通过usb_unlink_urb()或usb_kill_urb()函数取消urb,或urb 虽已提交,而USB 设备被拔出的情况下
在这里插入图片描述

三、 USB驱动架构简述

在这里插入图片描述
USB主机驱动程序由3部分组成:USB主机控制器驱动(HCD)、USB核心驱动(USBD)和不同种类的USB设备类驱动,如下所示。其中HCD和USBD被称为协议软件或者协议栈,这两部分共同处理与协议相关的操作
在这里插入图片描述

在Linux USB子系统中,HCD是直接和硬件进行交互的软件模块,是USB协议栈的最底层部分,是USB主机控制器硬件和数据传输的一种抽象。

HCD向上仅对USB总线驱动程序服务,HCD提供了一个软件接口,即HCDI,使得各种USB主机控制器的硬件特性都被软件化,并受USB总线驱动程序的调用和管理。HCD向下则直接管理和检测主控制器硬件的各种行为。HCD提供的功能主要有:主机控制器硬件初始化;为USBD层提供相应的接口函数;提供根HUB(ROOT HUB)设备配置、控制功能;完成4种类型的数据传输等。

USBD部分是整个USB主机驱动的核心,主要实现的功能有:USB总线管理;USB总线设备管理、USB总线带宽管理、USB的4种类型数据传输、USB HUB驱动、为USB设备驱动提供相关接口、提供应用程序访问USB系统的文件接口等。其中USB HUB作为一类特殊的USB设备,其驱动程序被包含在USBD层。

在嵌入式Linux系统中,已经包含HCD模块和USB核心驱动USBD,不需要用户重新编写,用户仅仅需要完成USB设备类驱动即可

全流程如下图
在这里插入图片描述

四、 USB CORE

1. USB子系统初始化

在这里插入图片描述

a. usb_debugfs

在这里插入图片描述

如上图,其输出的意义如下:

  1. T—topology,表示的是拓扑结构上的意思。
  • Bus:是其所在的usb总线号,一个总线号会对应一个rootHub,并且一个总线号对应的设备总数<=127,这是倒不是因为电气特性限制,而是因为USB规范中规定用7bit寻址设备,第八个bit用于标识数据流向。00就是0号总线。
  • Lev:该设备所在层,这个Lev信息看图最明显了。
  • Prnt:parent Devicenumber父设备的ID号,rootHUb没有父设备,该值等于零,其它的设备的父设备一定指向一个hub。
  • port:该设备连接的端口号,这里指的端口号是下行端口号,并且一个hub通常下行端口号有多个,上行端口号只有一个。
  • Cnt:这个Lev上设备的总数,hub也会计数在内,hub也是usb设备,其是主机控制器和usb设备通信的桥梁。
  • Dev:是设备号,按顺序排列的,一个总线上最多挂127个;可以有多个总线。
  • spd:设备的速率,12M(1.1)、480M(2.0)等。
  • MxCh:最多挂接的子设备个数,这个数值通常对应于HuB的下行端口号个数。
  1. B—Band width
  • Alloc:该总线分配得到的带宽
  • Int:中断请求数
  • ISO:同步传输请求数,USB有四大传输,中断、控制、批量和同步。
  1. D–Device Descriptor 设备描述符。
  • Ver:设备USB版本号。
  • Cls:设备的类(hub的类是9),
  • sub:设备的子类
  • Prot:设备的协议
  • MxPS:default 端点的最大packet size
  • Cfgs: 配置的个数;USB里共有四大描述符,它们是设备描述符、端点描述符、接口描述符和配置描述符。
  1. P—设备信息
  • Vendor: 厂商ID,Linuxfoundation的ID是1d6b,http://www.linux-usb.org/usb.ids
  • Rev: 校订版本号
  1. S—Manufacturer

  2. S—产品

  3. S—序列号

  4. C*—配置描述信息

  • #Ifs:接口的数量,
  • Atr:属性
  • MxPwr:最大功耗,USB设备供电有两种方式,self-powered和bus-powered两种方式,驱动代码会判断设备标志寄存器是否过流的。最大500mA。
  1. I–描述接口的接口描述符
  • If#:接口号
  • Alt:接口属性
  • #EPs:接口具有的端点数量,端点零必须存在,在USB设备addressed之前,会使用该端口配置设备。
  • Cls:接口的类
  • Sub:接口的子类
  • Prot:接口的协议
  • Driver:驱动的名称。
  1. E—端点描述符
  • Ad(s):端点地址,括号的s为I或者O表示该端点是输入还是输出端点。
  • Atr(sss):端点的属性,sss是端点的类型,对应上述的四大传输类型。
  • MxPS:端点具有的最大传输包
  • Ivl:传输间的间隔。

b. USB总线注册与通知

retval = bus_register(&usb_bus_type);
    if (retval)
        goto bus_register_failed;
    retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);
    if (retval)
        goto bus_notifier_failed;

struct bus_type usb_bus_type = {
    .name =     "usb",
    .match =    usb_device_match,
    .uevent =   usb_uevent,
};

Usb总线注册后,通过usb_device_match将驱动与设备进行匹配,匹配如果通过,则调用驱动的probe进行注册,其具体注册流程为:
driver_register-> bus_add_driver-> driver_attach-> __driver_attach-> driver_match_device(bus->match)-> driver_probe_device-> really_probe(dev->bus->probe)

注册通知则是通过以下函数实现,可以看出,如果一个驱动与设备匹配完成后并成功注册,则notify实现了将该设备注册进sysfs中,并且区分是具体的设备还是设备下的接口。当设备卸载时,从sysfs中对其删除

static struct notifier_block usb_bus_nb = {
    .notifier_call = usb_bus_notify,
};
static int usb_bus_notify(struct notifier_block *nb, unsigned long action,
        void *data)
{
    struct device *dev = data;

    switch (action) {
    case BUS_NOTIFY_ADD_DEVICE:
        if (dev->type == &usb_device_type)
            (void) usb_create_sysfs_dev_files(to_usb_device(dev));
        else if (dev->type == &usb_if_device_type)
            usb_create_sysfs_intf_files(to_usb_interface(dev));
        break;

    case BUS_NOTIFY_DEL_DEVICE:
        if (dev->type == &usb_device_type)
            usb_remove_sysfs_dev_files(to_usb_device(dev));
        else if (dev->type == &usb_if_device_type)
            usb_remove_sysfs_intf_files(to_usb_interface(dev));
        break;
    }
    return 0;
}

c. USB驱动注册

在USB子系统初始化过程中,一共注册了3个驱动,后面随着启动又注册了几个usb驱动,可以看出,设备驱动只有一个,剩下的全是接口驱动
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如上图,在USB子系统初始化阶段就注册的三个驱动分别为usbfs,hub和usb设备驱动
如下图为USB子系统注册驱动的关键函数,一般的接口驱动最终都会调用usb_probe_interface 匹配完成后在调用具体驱动的probe进行进一步的匹配和初始化
在这里插入图片描述
在这里插入图片描述

2. USB设备通用驱动(usb core)

a. 通用流程

struct usb_device_driver usb_generic_driver = {
    .name = "usb",
    .probe = generic_probe,
    .disconnect = generic_disconnect,
#ifdef  CONFIG_PM
    .suspend = generic_suspend,
    .resume = generic_resume,
#endif
    .supports_autosuspend = 1,
};

该驱动由usb子系统初始化时注册,通常情况下,任何一个USB实体设备在与驱动匹配时都会与该驱动匹配,该驱动是接口驱动的接口,获取USB配置并进行配置,详情如下(部分删减)
其中获取配置的环节为:register_root_hub -> usb_get_device_descriptor + usb_new_device -> usb_enumerate_device(设备枚举) -> usb_get_configuration 获取配置

static int generic_probe(struct usb_device *udev)
{
    int err, c;
    // 选择并设置配置。这将向驱动程序核心注册接口,并允许接口驱动程序绑定到它们.
    if (udev->authorized == 0) //设备未被授权使用
        dev_err(&udev->dev, "Device is not authorized for usage\n");
    else {
        c = usb_choose_configuration(udev);
        if (c >= 0) {
            err = usb_set_configuration(udev, c);
        }
    }
    usb_notify_add_device(udev); //usbfs中新增usb设备
    return 0;
}

在设备枚举过程中,会获取到usb设备配置(通过一系列复杂的usb通信流程),然后在通用驱动中选择一个合适的配置(根据总线所能支持的最大电流来选择一个),随后进入配置阶段

int usb_set_configuration(struct usb_device *dev, int configuration)
{
    int i, ret;
    struct usb_host_config *cp = NULL;
    struct usb_interface **new_interfaces = NULL;
    struct usb_hcd *hcd = bus_to_hcd(dev->bus);
    int n, nintf;

    if (dev->authorized == 0 || configuration == -1)
        configuration = 0; //未授权或者没配置的直接pass
    else {
        for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
            if (dev->config[i].desc.bConfigurationValue ==
                    configuration) {
                cp = &dev->config[i]; //获取到对应配置,配置可能有好几种,这里选择总线所能支持的一个最佳的方案
                break;
            }
        }
    }
    if ((!cp && configuration != 0))
        return -EINVAL;

    /* USB规范称配置0表示未配置。但是,如果设备包含编号为0的配置,我们将接受它作为正确配置的状态。如果确实要取消设备配置,请使用-1.*/
    if (cp && configuration == 0)
        dev_warn(&dev->dev, "config 0 descriptor??\n");

	/* 对配置下有的接口数量进行内存申请 */
    n = nintf = 0;
    if (cp) {
        nintf = cp->desc.bNumInterfaces;
        new_interfaces = kmalloc(nintf * sizeof(*new_interfaces),
                GFP_NOIO);
        if (!new_interfaces)
            return -ENOMEM;

        for (; n < nintf; ++n) {
            new_interfaces[n] = kzalloc(
                    sizeof(struct usb_interface),
                    GFP_NOIO);
            if (!new_interfaces[n]) {
                ret = -ENOMEM;
free_interfaces:
                while (--n >= 0)
                    kfree(new_interfaces[n]);
                kfree(new_interfaces);
                return ret;
            }
        }

        i = dev->bus_mA - usb_get_max_power(dev, cp);
        if (i < 0)
            dev_warn(&dev->dev, "new config #%d exceeds power "
                    "limit by %dmA\n",
                    configuration, -i);
    }

    /* 唤醒设备,以便向其发送设置配置请求 */
    ret = usb_autoresume_device(dev);
    if (ret)
        goto free_interfaces;

    /* 如果已配置,请先清除旧状态。摆脱旧接口意味着解除其驱动程序的绑定.*/
    if (dev->state != USB_STATE_ADDRESS)
        usb_disable_device(dev, 1); /* Skip ep0 */
	中间省略部分对接口的配置过程

    /* 到这里接口都已经配置完成,可以对接口进行驱动注册了.*/
    for (i = 0; i < nintf; ++i) {
        struct usb_interface *intf = cp->interface[i];

        dev_info(&dev->dev,
            "adding %s (config #%d, interface %d)\n",
            dev_name(&intf->dev), configuration,
            intf->cur_altsetting->desc.bInterfaceNumber);
        device_enable_async_suspend(&intf->dev);
        ret = device_add(&intf->dev); //这里对接口驱动进行匹配
        if (ret != 0) {
            dev_err(&dev->dev, "device_add(%s) --> %d\n",
                dev_name(&intf->dev), ret);
            continue;
        }
        create_intf_ep_devs(intf); //对接口下的每个端点在进行设备驱动注册
    }

    usb_autosuspend_device(dev);
    return 0;
}

b. 驱动匹配(接口)

在设备注册过程中会经由总线__device_attach -> __device_attach_driver -> driver_match_device最终会执行drv->bus->match(dev, drv),实际上执行了usb_device_match
可以看出usb设备驱动分成两大类:设备驱动和接口驱动(一般来说,usb驱动大都是接口驱动,一个usb接口对应一个驱动,但是从大的整体上看,usb设备也是要有驱动的,使用一般就是usb的通用驱动,还有要注意接口驱动和设备驱动驱动结构体都是不同的)

static int usb_device_match(struct device *dev, struct device_driver *drv)
{
    /* 设备和接口是分开处理的 */
    if (is_usb_device(dev)) {
        /* 如果dev是“设备”,而驱动是接口驱动,则返回0,就是没匹配到 */
        if (!is_usb_device_driver(drv))
            return 0;
        return 1;

    } else if (is_usb_interface(dev)) {
        struct usb_interface *intf;
        struct usb_driver *usb_drv;
        const struct usb_device_id *id;
        
        /* 如果dev是“接口”,而驱动是设备驱动,则返回0,也是没匹配到 */
        if (is_usb_device_driver(drv))
            return 0;

        intf = to_usb_interface(dev); //dev转成接口
        usb_drv = to_usb_driver(drv); //drv转成“接口驱动”因为设备驱动和接口驱动的结构是不同的

        id = usb_match_id(intf, usb_drv->id_table); //id_table进行匹配
        if (id)
            return 1; //匹配到了返回1
            
        id = usb_match_dynamic_id(intf, usb_drv);
        if (id)
            return 1;
    }

    return 0;
}

USB设备驱动,使用usb_device_driver,USB接口驱动,使用usb_driver
对于SOC上的USB,当驱动加载时,会先有USB设备驱动匹配,在有USB驱动(接口)匹配
对于大多数USB设备,作为USB设备的驱动都是通用usb_generic_driver,接口驱动各不相同。
在匹配过程中会有一个结构体(由驱动去决定)起到关键作用

struct usb_device_id {
    /* 要与哪些字段匹配? */
    __u16       match_flags;  //最为关键,这个标志决定了以什么去进行匹配
 
    /* 用于特定产品匹配;范围包括如下 */
    __u16       idVendor;  //供应商
    __u16       idProduct; //产品ID
    __u16       bcdDevice_lo;
    __u16       bcdDevice_hi;

    /* 用于设备类匹配 */
    __u8        bDeviceClass; //设备类型
    __u8        bDeviceSubClass;  //设备子类型
    __u8        bDeviceProtocol;  //协议

    /* 用于接口类匹配 */
    __u8        bInterfaceClass; 
    __u8        bInterfaceSubClass;  
    __u8        bInterfaceProtocol;

    /* 用于供应商特定的接口匹配 */
    __u8        bInterfaceNumber;

    /* not matched against */
    kernel_ulong_t  driver_info
        __attribute__((aligned(sizeof(kernel_ulong_t))));
};

比如hub进行匹配时有如下图,这样直接将hub驱动与设备就匹配到了
在这里插入图片描述
在这里插入图片描述

3. hub驱动(usb core)

a. 驱动注册

在这里插入图片描述
USB子系统注册的3个最初的驱动之一,usb主机会自动匹配并注册成roothub,流程继设备通用驱动的接口驱动匹配,匹配成功后即进入hub_probe,下面来具体分析一下整个驱动,驱动接口如下

static struct usb_driver hub_driver = {
    .name =     "hub",
    .probe =    hub_probe, //hub探测接口,重要
    .disconnect =   hub_disconnect,
    .suspend =  hub_suspend, //hub挂起,涉及到PM电源管理驱动控制
    .resume =   hub_resume,
    .reset_resume = hub_reset_resume, //通过对hub复位来引发复位
    .pre_reset =    hub_pre_reset, //与PM电源管理系统相关
    .post_reset =   hub_post_reset, //与PM电源管理系统相关
    .unlocked_ioctl = hub_ioctl, //hub id表,重要
    .id_table = hub_id_table,
    .supports_autosuspend = 1,
};

根据id_table,只要match_flag符合如下3种就是hub类,就能顺利匹配上了

static const struct usb_device_id hub_id_table[] = {
    { .match_flags = USB_DEVICE_ID_MATCH_VENDOR
            | USB_DEVICE_ID_MATCH_INT_CLASS,
      .idVendor = USB_VENDOR_GENESYS_LOGIC,
      .bInterfaceClass = USB_CLASS_HUB,
      .driver_info = HUB_QUIRK_CHECK_PORT_AUTOSUSPEND},
    { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,
      .bDeviceClass = USB_CLASS_HUB},
    { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
      .bInterfaceClass = USB_CLASS_HUB},
    { }                     /* Terminating entry */
};

在USB子系统初始化过程中调用usb_hub_init即开始了hub的注册,实际上就做了两件事情:驱动注册和开启一个工作队列

int usb_hub_init(void)
{
    usb_register(&hub_driver)
    ……
    hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);
}

b. hub激活过程

i. hub_probe

static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
    struct usb_host_interface *desc;
    struct usb_endpoint_descriptor *endpoint;
    struct usb_device *hdev;
    struct usb_hub *hub;
  
    desc = intf->cur_altsetting; // 获取当前激活的接口
hdev = interface_to_usbdev(intf);
省略部分

	/* hub的最大深度为6,即hub接hub接hub最多接6次,roothub深度为0,即除开roothub最多在接6个 */
    if (hdev->level == MAX_TOPO_LEVEL) {
        return -E2BIG;
    }

    endpoint = &desc->endpoint[0].desc; //获取端点0描述符

    /* 如果端点属性没有中断能力很明显有问题 */
    if (!usb_endpoint_is_int_in(endpoint))
        goto descriptor_error;

    /* We found a hub */
    dev_info(&intf->dev, "USB hub found\n");

    hub = kzalloc(sizeof(*hub), GFP_KERNEL);
    if (!hub)
        return -ENOMEM;

    kref_init(&hub->kref);
    hub->intfdev = &intf->dev;
    hub->hdev = hdev;
    INIT_DELAYED_WORK(&hub->leds, led_work);
    INIT_DELAYED_WORK(&hub->init_work, NULL);
    INIT_WORK(&hub->events, hub_event); /* hub枚举的核心工作队列 */
    省略部分
	/* 该函数主要进行各种缓存的申请,执行get_hub_descriptor并通过usb通讯获取hub设备的描述符,然后根据描述符进行进一步的配置,最后启动hub */
    if (hub_configure(hub, endpoint) >= 0) 
        return 0;

    hub_disconnect(intf);
    return -ENODEV;
}

ii. hub_configure

static int hub_configure(struct usb_hub *hub,
    struct usb_endpoint_descriptor *endpoint)
{
	……
    /* 获取hub的描述符*/
    ret = get_hub_descriptor(hdev, hub->descriptor);
    if (ret < 0) {
        message = "can't read hub descriptor";
        goto fail;
    }
	/* 获取hub能接几个 */
    maxchild = hub->descriptor->bNbrPorts;
    dev_info(hub_dev, "%d port%s detected\n", maxchild,
            (maxchild == 1) ? "" : "s");

/* 略部分参数设置 */

/* 管道创建 */
    pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress);
    maxp = usb_maxpacket(hdev, pipe, usb_pipeout(pipe));

    if (maxp > sizeof(*hub->buffer))
        maxp = sizeof(*hub->buffer);

    hub->urb = usb_alloc_urb(0, GFP_KERNEL); /* 申请urb */

    usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
        hub, endpoint->bInterval); /* urb初始化成中断方式,该irq最终会唤醒一个工作队列也就是hub_event */

    mutex_lock(&usb_port_peer_mutex);
    for (i = 0; i < maxchild; i++) {
        ret = usb_hub_create_port_device(hub, i + 1);
        if (ret < 0) {
            dev_err(hub->intfdev,
                "couldn't create port%d device.\n", i + 1);
            break;
        }
    }
    hdev->maxchild = i;

    mutex_unlock(&usb_port_peer_mutex);

    /* Update the HCD's internal representation of this hub before hub_wq
     * starts getting port status changes for devices under the hub.
     */
    if (hcd->driver->update_hub_device) {
        ret = hcd->driver->update_hub_device(hcd, hdev,
                &hub->tt, GFP_KERNEL);
        if (ret < 0) {
            message = "can't update HCD hub info";
            goto fail;
        }
    }

    usb_hub_adjust_deviceremovable(hdev, hub->descriptor);

	/* 在该函数中,会调用usb_submit_urb 提交urb,然后 kick_hub_wq (hub);
 */
    hub_activate(hub, HUB_INIT); //激活hub
}

iii. kick_hub_wq

最终调用在一开始创建的工作队列,而工作队列里要做的事情就是 hub->events 也就是 hub_event这个函数

static void kick_hub_wq(struct usb_hub *hub)
{
	…… 
    kref_get(&hub->kref);
    if (queue_work(hub_wq, &hub->events))
        return;
	……
    kref_put(&hub->kref, hub_release);
}

c. hub枚举过程

i. hub_irq

USB枚举由HUB激活(首次或者重启)或者hub_irq两种方式激活,由hub激活在激活过程已经分析过,接下来分析hub_irq这条路线,从程序上分析出:该urb触发后调用hub_event工作队列,然后该urb又被该函数本身提交,即该urb循环利用

static void hub_irq(struct urb *urb)
{
    switch (status) {
    case -ENOENT:       /* synchronous unlink */
    case -ECONNRESET:   /* async unlink */
    case -ESHUTDOWN:    /* hardware going away */
        return;

    default:        /* presumably an error */
        /* 运行出现其他错误类型10次,如果超过10次就重启 */
        dev_dbg(hub->intfdev, "transfer --> %d\n", status);
        if ((++hub->nerrors < 10) || hub->error)
            goto resubmit;
        hub->error = status;
        /* FALL THROUGH */

    /* let hub_wq handle things */
    case 0:         /* we got data:  port status changed */
        bits = 0;
        for (i = 0; i < urb->actual_length; ++i)
            bits |= ((unsigned long) ((*hub->buffer)[i]))
                    << (i*8); /* 根据位表获取具体是hub的那个端口状态变了 */
        hub->event_bits[0] = bits; /* 将该表给到hub_event里 */
        break;
    }

    hub->nerrors = 0;

    /* Something happened, let hub_wq figure it out */
    kick_hub_wq(hub); /* 唤醒工作队列,执行hub_event */

resubmit:
    if (hub->quiescing)
        return;
	/* 该urb重新循环利用 */
    status = usb_submit_urb(hub->urb, GFP_ATOMIC);
    if (status != 0 && status != -ENODEV && status != -EPERM)
        dev_err(hub->intfdev, "resubmit --> %d\n", status);
}

ii. hub_event

在这里插入图片描述

static void hub_event(struct work_struct *work)
{
	……
/* deal with port status changes */
/* 重1开始,处理每一个hub的port情况(bit0代表hub本身) */
    for (i = 1; i <= hdev->maxchild; i++) {
        struct usb_port *port_dev = hub->ports[i - 1];

        if (test_bit(i, hub->event_bits)
                || test_bit(i, hub->change_bits)
                || test_bit(i, hub->wakeup_bits)) {
……
            port_event(hub, i);
……
        }
    }
    /* deal with hub status changes 处理hub本身有两种变化:电源改变或者过电流,具体略 */
}

iii. port_event之后到新设备识别

函数 port_event对需要改变的port进行处理,并根据协议想该端口进行通讯,最后调用函数 hub_port_connect_change
在这里插入图片描述

static void port_event(struct usb_hub *hub, int port1)
        __must_hold(&port_dev->status_lock)
{
	……
 
    if (portchange & USB_PORT_STAT_C_CONNECTION) {
        usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION);
		/* 创建管道发送数据 */
        connect_change = 1;
}
其他portchange略
	……
    if (connect_change)
        hub_port_connect_change(hub, port1, portstatus, portchange);
}
	然后 hub_port_connect_change 在调用hub_port_connect
static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
        u16 portchange)
{
    /* 断开此端口下的所有现有设备 */
    usb_disconnect(&port_dev->child);

/* 省略中间的去抖判断和状态标志的一些变化 */
    udev = usb_alloc_dev(hdev, hdev->bus, port1); // 申请新的usbdev
// 新的设备继承hub的一些属性

    choose_devnum(udev); // 给该设备分配一个号 最大127
    status = hub_port_init(hub, udev, port1, i); /* 获取设备描述符 */

    /* 如果是个hub额外进行电源的管理,如果供电能力不行则直接跳出 */
    port_dev->child = udev; 

    /* Run it through the hoops (find a driver, etc) */
status = usb_new_device(udev); //新设备的注册
……
}

iv. usb_new_device新设备注册

USB子系统注册了bus对应的probe接口即usb_probe_interface
在这里插入图片描述

int usb_new_device(struct usb_device *udev)
{
	……
    err = usb_enumerate_device(udev); /* 读取配置描述符并选择一个合适的 */
    ……

    /* 设备注册 */
err = device_add(&udev->dev);
流程如下
device_add –> bus_probe_device -> device_initial_probe -> __device_attach -> __device_attach_driver -> driver_probe_device -> really_probe -> dev->bus->probe(即usb_probe_interface) -> driver->probe(到这里根据id_table匹配的设备去注册对应设备驱动)
	
	……
(void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev);
……
}

d. hub中的“irq”的产生

对于hub_irq这个“中断”,是注册在中断urb中的,当该urb完成时调用,也即是说和urb的流程一一相关,而在hub驱动中调用usb_submit_urb后,urb就归于主机和usbcore去控制了,所以需要深入了解一下

i. urb在hcd中实际是定时器轮询

usb_submit_urb – > usb_hcd_submit_urb

int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
{
	……
    usb_get_urb(urb);
    atomic_inc(&urb->use_count);
    atomic_inc(&urb->dev->urbnum);
    usbmon_urb_submit(&hcd->self, urb); //开抓包时候有用

    if (is_root_hub(urb->dev)) {
        status = rh_urb_enqueue(hcd, urb); //这里先只分析roothub
    } else {
        ……
    }
    return status;
}

rh_urb_enqueue -> rh_queue_status

static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb)
{
    ……
    hcd->status_urb = urb; /* urb给到hcd */
    urb->hcpriv = hcd;  /* indicate it's queued */
    if (!hcd->uses_new_polling)
        mod_timer(&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
	/* 开了个定时器,执行rh_timer,也就是执行rh_timer_func -> usb_hcd_poll_rh_status */
 
    /* If a status change has already occurred, report it ASAP */
    else if (HCD_POLL_PENDING(hcd))
        mod_timer(&hcd->rh_timer, jiffies);
    retval = 0;
 	……
    return retval;
}

void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
{
    struct urb  *urb;
    int     length;
    unsigned long   flags;
    char        buffer[6];  /* Any root hubs with > 31 ports? */

	/* 获取hub的端口状态值,下面详解 */
    length = hcd->driver->hub_status_data(hcd, buffer);
    if (length > 0) {

        /* try to complete the status urb */
        spin_lock_irqsave(&hcd_root_hub_lock, flags);
        urb = hcd->status_urb;
        if (urb) {
            clear_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
            hcd->status_urb = NULL;
            urb->actual_length = length;
			/* 将获取的端口状态位表拷贝给urb */
            memcpy(urb->transfer_buffer, buffer, length);
            usb_hcd_unlink_urb_from_ep(hcd, urb);
			/* urb执行,在该函数中最终调用 urb->complete(urb) */
            usb_hcd_giveback_urb(hcd, urb, 0);
        } else {
            length = 0;
            set_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
        }
        spin_unlock_irqrestore(&hcd_root_hub_lock, flags);
    }

    /* 重开定时器,即不停的去执行 */
    mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
}

也就是说,对于roothub的hub_irq实际上就是一个定时器,去不停的轮询prot的状态,而端口状态由函数hcd->driver->hub_status_data(hcd, buffer)获取

ii. 实际的状态值是中断产生

在dwc2(USB架构中的主机控制器驱动)中有如下函数

static struct hc_driver dwc2_hc_driver = {
	……
	.irq = _dwc2_hcd_irq,
	……
    .hub_status_data = _dwc2_hcd_hub_status_data,
 	……
};

static int _dwc2_hcd_hub_status_data(struct usb_hcd *hcd, char *buf)
{
    struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);

    buf[0] = dwc2_hcd_is_status_changed(hsotg, 1) << 1; /* 第0位用于表示hub本身的状态 */
    return buf[0] != 0;
}
static int dwc2_hcd_is_status_changed(struct dwc2_hsotg *hsotg, int port)
{
    int retval;

    if (port != 1)
        return -EINVAL;

    retval = (hsotg->flags.b.port_connect_status_change ||
          hsotg->flags.b.port_reset_change ||
          hsotg->flags.b.port_enable_change ||
          hsotg->flags.b.port_suspend_change ||
          hsotg->flags.b.port_over_current_change);
    return retval;
}

以端口状态被改变为例

void dwc2_hcd_connect(struct dwc2_hsotg *hsotg)
{
    if (hsotg->lx_state != DWC2_L0)
        usb_hcd_resume_root_hub(hsotg->priv);

    hsotg->flags.b.port_connect_status_change = 1;
    hsotg->flags.b.port_connect_status = 1;
}

而dwc2_hcd_connect由dwc2_port_intr调用,而该函数的调用路径为:
_dwc2_hcd_irq -> dwc2_handle_hcd_intr –> dwc2_port_intr(dwc2中详解)
即最终被注册成了一个实际上的中断

static void dwc2_port_intr(struct dwc2_hsotg *hsotg)
{
    u32 hprt0;
    u32 hprt0_modify;

    dev_vdbg(hsotg->dev, "--Port Interrupt--\n");
    hprt0 = dwc2_readl(hsotg->regs + HPRT0);
    hprt0_modify = hprt0;
    hprt0_modify &= ~(HPRT0_ENA | HPRT0_CONNDET | HPRT0_ENACHG |
              HPRT0_OVRCURRCHG);
    if (hprt0 & HPRT0_CONNDET) {
        dwc2_writel(hprt0_modify | HPRT0_CONNDET, hsotg->regs + HPRT0);

        dev_vdbg(hsotg->dev,
             "--Port Interrupt HPRT0=0x%08x Port Connect Detected--\n",
             hprt0);
        dwc2_hcd_connect(hsotg);
    }
	……
}

也就是说,roothub识别的最终流程为:实际物理设备的中断调用改变了prot的状态标志,由主机控制器的定时器轮询到然后将该状态的位表返回给hub,然后hub调用工作队列执行hub_event在根据位表去判断具体那个prot发生了改变然后进行进一步的操作。

4. HCD

a. usb_add_hcd

Hcd负责urb的一些控制,具体不去详细深入,在上面hub产生irq已经有过部分分析,这里对usbcore中的hcd初始化分析一下

int usb_add_hcd(struct usb_hcd *hcd,
        unsigned int irqnum, unsigned long irqflags)
{
    int retval;
    struct usb_device *rhdev;

    if (IS_ENABLED(CONFIG_GENERIC_PHY) && !hcd->phy) {
        struct phy *phy = phy_get(hcd->self.controller, "usb");
		……
        retval = phy_init(phy);
		……
        retval = phy_power_on(phy);
		……
        hcd->phy = phy;
        hcd->remove_phy = 1;
    }
	……

    retval = usb_register_bus(&hcd->self);
	/* HCD注册的一定是rootbub */
    rhdev = usb_alloc_dev(NULL, &hcd->self, 0);
    ……
hcd->self.root_hub = rhdev;
……
    if (hcd->driver->reset) {
        retval = hcd->driver->reset(hcd);
		……
    }
……
/* hcd中断申请,注册usb_hcd_irq */
    retval = usb_hcd_request_irqs(hcd, irqnum, irqflags);

 	……
retval = hcd->driver->start(hcd);
……

    /* starting here, usbcore will pay attention to this root hub */
retval = register_root_hub(hcd);
……
    if (hcd->uses_new_polling && HCD_POLL_RH(hcd))
        usb_hcd_poll_rh_status(hcd); //直接开启一次轮询操作

    return retval;
}

b. register_root_hub

static int register_root_hub(struct usb_hcd *hcd)
{
    struct device *parent_dev = hcd->self.controller;
    struct usb_device *usb_dev = hcd->self.root_hub;
    const int devnum = 1;
    int retval;

    usb_dev->devnum = devnum;
    usb_dev->bus->devnum_next = devnum + 1;
	……
usb_dev->ep0.desc.wMaxPacketSize = cpu_to_le16(64); /* roothub的ep0直接设置成64长度 */
/* 获取描述符 */
retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE);

	……
retval = usb_new_device (usb_dev); // 注册新usb设备
……
}

五、 主机控制器驱动(以dwc2进行分析)

1. 驱动注册

驱动匹配设备树(涉及到真的硬件了)以及match_table,之后进行probe

static struct platform_driver dwc2_platform_driver = {
    .driver = {
        .name = dwc2_driver_name,
        .of_match_table = dwc2_of_match_table,
        .pm = &dwc2_dev_pm_ops,
    },
    .probe = dwc2_driver_probe,
    .remove = dwc2_driver_remove,
    .shutdown = dwc2_driver_shutdown,
};

probe函数中主要进行了硬件信息获取与初始化,中断设置以及模式设置,下面来具体分析一下

static int dwc2_driver_probe(struct platform_device *dev)
{
 	…… 
    res = platform_get_resource(dev, IORESOURCE_MEM, 0);
    hsotg->regs = devm_ioremap_resource(&dev->dev, res);
	/* 从设备树获取资源,然后对其寄存器地址进行iomap */

retval = dwc2_lowlevel_hw_init(hsotg);
/* 从设备树获取usb2-phy和clk */

    hsotg->core_params = devm_kzalloc(&dev->dev,
                sizeof(*hsotg->core_params), GFP_KERNEL);
    dwc2_set_all_params(hsotg->core_params, -1);

hsotg->irq = platform_get_irq(dev, 0);
/* 从设备树获取中断 */
    retval = devm_request_irq(hsotg->dev, hsotg->irq,
                  dwc2_handle_common_intr, IRQF_SHARED,
                  dev_name(hsotg->dev), hsotg); 
/* 通用中断注册 */

retval = dwc2_lowlevel_hw_enable(hsotg);
/* 硬件资源使能,即clk和usbphy使能 */

retval = dwc2_get_dr_mode(hsotg);
/* 获取usb模式:主,从,otg */

retval = dwc2_core_reset_and_force_dr_mode(hsotg);
/* 从这里开始涉及到usb硬件寄存器的读取 */

    /* Detect config values from hardware */
    retval = dwc2_get_hwparams(hsotg);
    /* 通过读取usb寄存器获取相应的配置信息 */

    /* Validate parameter values */
dwc2_set_parameters(hsotg, params);
/* 进行配置 */

    dwc2_force_dr_mode(hsotg);

    if (hsotg->dr_mode != USB_DR_MODE_HOST) {
        retval = dwc2_gadget_init(hsotg, hsotg->irq);
        /* 不是usb主则初始化gadget */
        hsotg->gadget_enabled = 1;
    }

    if (hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) {
        retval = dwc2_hcd_init(hsotg, hsotg->irq);
       /* 不是从机则初始化hcd:USB主机控制器HCD(Host Controller Device) */
        hsotg->hcd_enabled = 1;
    }

    platform_set_drvdata(dev, hsotg); /* 把hsotg这个数据放入dev的私有data中 */

    dwc2_debugfs_init(hsotg);

    /* Gadget code manages lowlevel hw on its own */
    if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)
        dwc2_lowlevel_hw_disable(hsotg);
	/* 如果确认为从机,则clk和usbphy停掉 */

    return 0;

error:
    dwc2_lowlevel_hw_disable(hsotg);
    return retval;
}

2. 注册成主机

在这里插入图片描述
可以分析出其根基于dwc2_hcd_init开始,重点分析该函数

int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq)
{
  	hcd = usb_create_hcd(&dwc2_hc_driver, hsotg->dev, 
dev_name(hsotg->dev)); /* 创建并初始化HCD结构,并赋值到driver->hcd_priv_size */

    dwc2_disable_global_interrupts(hsotg);

    /* Initialize the DWC_otg core, and select the Phy type */
    retval = dwc2_core_init(hsotg, true); /* 初始化DWC_otg控制器寄存器,并为设备模式或主机模式操作做核心准备,这里基本都是些寄存器的读取和配置*/

    /* Create new workqueue and init work */
    hsotg->wq_otg = alloc_ordered_workqueue("dwc2", 0);
    INIT_WORK(&hsotg->wf_otg, dwc2_conn_id_status_change); /* 该工作队列用于检测otg下id引脚状态是否发生改变,以改变自身主从状态 */

    setup_timer(&hsotg->wkp_timer, dwc2_wakeup_detected,
            (unsigned long)hsotg); /* 起定时器检测hcd是否挂起,如果挂起了则唤醒并最终调用usb_hcd_resume_root_hub,然后起工作队列queue_work(pm_wq, &hcd->wakeup_work) */

    /* Initialize hsotg start work */
    INIT_DELAYED_WORK(&hsotg->start_work, dwc2_hcd_start_func); /* 当状态切换时才会使用该工作队列,常态下hcd启动由下面的usb_add_hcd完成 */
    /* Initialize port reset work */
    INIT_DELAYED_WORK(&hsotg->reset_work, dwc2_hcd_reset_func); /* 当状态切换时才会使用该工作队列 */

    if (!IS_ERR_OR_NULL(hsotg->uphy))
        otg_set_host(hsotg->uphy->otg, &hcd->self);

    /* 完成常规HCD初始化并启动HCD。此函数分配DMA缓冲池,注册USB总线,请求IRQ线路,并调用hcd_start方法 */
    retval = usb_add_hcd(hcd, irq, IRQF_SHARED);

    device_wakeup_enable(hcd->self.controller);
    dwc2_enable_global_interrupts(hsotg);

    return 0;
}
int usb_add_hcd(struct usb_hcd *hcd,
        unsigned int irqnum, unsigned long irqflags)
{
    int retval;
    struct usb_device *rhdev;

    if (IS_ENABLED(CONFIG_GENERIC_PHY) && !hcd->phy) {
        struct phy *phy = phy_get(hcd->self.controller, "usb");
        retval = phy_init(phy);
        retval = phy_power_on(phy);
        hcd->phy = phy;
    }

    dev_info(hcd->self.controller, "%s\n", hcd->product_desc);

retval = usb_register_bus(&hcd->self); /* USB设备注册到总线上 */
    rhdev = usb_alloc_dev(NULL, &hcd->self, 0); /* 申请usb_device并和总线绑定 */
    
    mutex_lock(&usb_port_peer_mutex);
    hcd->self.root_hub = rhdev;
    mutex_unlock(&usb_port_peer_mutex);

    retval = usb_hcd_request_irqs(hcd, irqnum, irqflags);  /* 申请中断并绑定 usb_hcd_irq-> hcd->driver->irq(hcd),也就是说实际调用_dwc2_hcd_irq */

    hcd->state = HC_STATE_RUNNING;
    retval = hcd->driver->start(hcd); /* 调用_dwc2_hcd_start */

    /* starting here, usbcore will pay attention to this root hub */
    retval = register_root_hub(hcd); /* 在这里进行root hub 的最终注册 */

    return retval;
}


3. 中断

USB通用中断,也就是主从都可能触发的中断

/*
 * Common interrupt handler
 *
 * 常见的中断是在主机和设备模式下发生的中断.
 * This handler handles the following interrupts:
 * - Mode Mismatch Interrupt//模式不匹配中断
 * - OTG Interrupt
 * - Connector ID Status Change Interrupt//连接器ID状态更改中断
 * - Disconnect Interrupt
 * - Session Request Interrupt//会话请求中断
 * - Resume / Remote Wakeup Detected Interrupt//恢复/远程唤醒检测到中断
 * - Suspend Interrupt//暂停中断
 */
irqreturn_t dwc2_handle_common_intr(int irq, void *dev)
{
    struct dwc2_hsotg *hsotg = dev;
    u32 gintsts;
    irqreturn_t retval = IRQ_NONE;

    spin_lock(&hsotg->lock);

    if (!dwc2_is_controller_alive(hsotg)) {
        dev_warn(hsotg->dev, "Controller is dead\n");
        goto out;
    } /* 通过读取硬件寄存器判断设备是否存在 */

gintsts = dwc2_read_common_intr(hsotg);
/* 直接读寄存器获取中断状态 */
    if (gintsts & ~GINTSTS_PRTINT)
        retval = IRQ_HANDLED;

    if (gintsts & GINTSTS_MODEMIS)
        dwc2_handle_mode_mismatch_intr(hsotg);
	/* 模式不匹配警告的中断 */
    if (gintsts & GINTSTS_OTGINT)
        dwc2_handle_otg_intr(hsotg);
	/* 处理OTG中断。它读取OTG中断寄存器(GOTGINT)以确定发生了什么中断,深入进去可以发现其中还涉及到多个中断,不具体分析 */
    if (gintsts & GINTSTS_CONIDSTSCHNG)
        dwc2_handle_conn_id_status_change_intr(hsotg);
	/* 读取OTG中断寄存器(GOTCTL)以确定这是设备到主机模式转换还是主机到设备模式转换。仅当连接/拔下PHY连接器上的电缆时,才会发生这种情况。即该中断实现otg下的模式转换,H8中显然用不到,因为usb模式已经由硬件定死了 */
    if (gintsts & GINTSTS_DISCONNINT)
        dwc2_handle_disconnect_intr(hsotg);
	/* 检测到断连后的处理,这里主要是主机,调用dwc2_hcd_disconnect这个函数 */
    if (gintsts & GINTSTS_SESSREQINT)
        dwc2_handle_session_req_intr(hsotg);
    if (gintsts & GINTSTS_WKUPINT)
        dwc2_handle_wakeup_detected_intr(hsotg);
    if (gintsts & GINTSTS_USBSUSP)
        dwc2_handle_usb_suspend_intr(hsotg);

    if (gintsts & GINTSTS_PRTINT) {
        /*
         * The port interrupt occurs while in device mode with HPRT0
         * Port Enable/Disable
         */
        if (dwc2_is_device_mode(hsotg)) {
            dev_dbg(hsotg->dev,
                " --Port interrupt received in Device mode--\n");
            dwc2_handle_usb_port_intr(hsotg);
            retval = IRQ_HANDLED;
        }
    }

out:
    spin_unlock(&hsotg->lock);
    return retval;
}

主机(HCD)中断,HCD驱动由dwc2_hcd_init进行注册。具体的中断注册路线为:dwc2_hcd_init(hsotg, hsotg->irq)(注:这是hsotg->irq已经有通用中断了) –> usb_add_hcd(hcd, irq, IRQF_SHARED) -> usb_hcd_request_irqs(hcd, irqnum, irqflags) -> request_irq(irqnum, &usb_hcd_irq, irqflags,hcd->irq_descr, hcd)-> hcd->driver->irq(hcd)

static struct hc_driver dwc2_hc_driver = {
	其他全部省略
    .irq = _dwc2_hcd_irq,(由最后一步hcd->driver->irq(hcd)调用)
};
static irqreturn_t _dwc2_hcd_irq(struct usb_hcd *hcd)
{
    struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
    return dwc2_handle_hcd_intr(hsotg);
}
irqreturn_t dwc2_handle_hcd_intr(struct dwc2_hsotg *hsotg)
{
    u32 gintsts, dbg_gintsts;
    irqreturn_t retval = IRQ_NONE;

    if (!dwc2_is_controller_alive(hsotg)) {
        dev_warn(hsotg->dev, "Controller is dead\n");
        return retval;
    }

    spin_lock(&hsotg->lock);

    /* Check if HOST Mode */
    if (dwc2_is_host_mode(hsotg)) {
        gintsts = dwc2_read_core_intr(hsotg);
        if (!gintsts) {
            spin_unlock(&hsotg->lock);
            return retval;
        }
        retval = IRQ_HANDLED;
        if (gintsts & GINTSTS_SOF)
            dwc2_sof_intr(hsotg);  // start-of-frame interrupt开始帧中断
        if (gintsts & GINTSTS_RXFLVL)
            dwc2_rx_fifo_level_intr(hsotg); // 处理Rx FIFO电平中断,这表明Rx FIFO中至少有一个数据包。
        if (gintsts & GINTSTS_NPTXFEMP)
            dwc2_np_tx_fifo_empty_intr(hsotg); //当非周期性Tx FIFO为半空时,会发生此中断。可以将更多数据包写入FIFO进行输出传输。
        if (gintsts & GINTSTS_PRTINT)
            dwc2_port_intr(hsotg); /* 有多种情况可导致端口中断。此函数确定发生了哪些中断条件,并对其进行适当处理。包括3种:检测到端口连接,端口使能改变(Port Enable Changed) ,端口过电流变化 */
        if (gintsts & GINTSTS_HCHINT)
           dwc2_hc_intr(hsotg); /* 此中断表示一个或多个主机通道有挂起的中断。有多种情况会导致每个主机通道中断。此函数确定每个主机通道中断发生的条件,并对其进行适当处理。 */
        if (gintsts & GINTSTS_PTXFEMP)
            dwc2_perio_tx_fifo_empty_intr(hsotg);
    }
    spin_unlock(&hsotg->lock);
    return retval;
}

六、 接口驱动

1. usb-skeleton

a. 设备与驱动匹配

usb-skeleton.c是USB Host端代码的一个骨架,如果想要编写自己的Host端 bulk传输的代码,可以参考这个部分的代码进行编写,至于其他 isoc的传输方式,可能还需要参考其他的驱动代码进行编写
使用module_usb_driver注册HOST端驱动,声明匹配的gadget驱动列表(主要依赖VID与PID),完善相关的探测、断开连接等函数

static struct usb_driver skel_driver = {
    .name =     "skeleton",
    .probe =    skel_probe,
    .disconnect =   skel_disconnect,
    .suspend =  skel_suspend,
    .resume =   skel_resume,
    .pre_reset =    skel_pre_reset,
    .post_reset =   skel_post_reset,
    .id_table = skel_table, /* 通过厂家id和产品id进行匹配 */
    .supports_autosuspend = 1,
};

/* Define these values to match your devices */
#define USB_SKEL_VENDOR_ID  0xfff0 /* 修改成对应的设备的id就能匹配上 */
#define USB_SKEL_PRODUCT_ID 0xfff0

/* table of devices that work with this driver */
static const struct usb_device_id skel_table[] = {
    { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
    { }                 /* Terminating entry */
};


b. probe

关键数据如下
在这里插入图片描述
遍历接口的当前配置的所有端点,找到bulk in端点地址并申请urb传输描述符

static int skel_probe(struct usb_interface *interface,
              const struct usb_device_id *id)
{
    struct usb_skel *dev;
    struct usb_host_interface *iface_desc;
    struct usb_endpoint_descriptor *endpoint;
    size_t buffer_size;

    /* 申请内存 */
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
 
    kref_init(&dev->kref); /* 引用计数 */
    sema_init(&dev->limit_sem, WRITES_IN_FLIGHT); /* 信号量被设置成8,也就是说可以同时写8次 */
    mutex_init(&dev->io_mutex);
    spin_lock_init(&dev->err_lock);
    init_usb_anchor(&dev->submitted);
    init_waitqueue_head(&dev->bulk_in_wait); /* 用于读取的等待队列 */

    dev->udev = usb_get_dev(interface_to_usbdev(interface));
    dev->interface = interface;

    /* set up the endpoint information */
    /* use only the first bulk-in and bulk-out endpoints */
    iface_desc = interface->cur_altsetting; /* 获取接口配置 */
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
	/* 遍历端点描述符,实际上这里只用符合条件的第一个批量输入和输出 */
        endpoint = &iface_desc->endpoint[i].desc;
		/* 批量输入端点地址为0,且该端点是输入端点 */
        if (!dev->bulk_in_endpointAddr &&
            usb_endpoint_is_bulk_in(endpoint)) {
            /* we found a bulk in endpoint */
            buffer_size = usb_endpoint_maxp(endpoint); /* 获取端点buf大小 */
            dev->bulk_in_size = buffer_size;
            dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; /* 保存该端点地址 */
            dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); /* 为该端点申请缓存 */
            if (!dev->bulk_in_buffer)
                goto error;
            dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); /* 申请urb */
            if (!dev->bulk_in_urb)
                goto error;
        }

        if (!dev->bulk_out_endpointAddr &&
            usb_endpoint_is_bulk_out(endpoint)) {
            /* we found a bulk out endpoint */
            dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
        }
}
/* 都找完了,还有端点是空的,说明有问题 */
    if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
        dev_err(&interface->dev,
            "Could not find both bulk-in and bulk-out endpoints\n");
        goto error;
    }

    /* save our data pointer in this interface device */
    usb_set_intfdata(interface, dev); /* dev给到接口结构体 */
	 
    /* we can register the device now, as it is ready */
    retval = usb_register_dev(interface, &skel_class); /* usb设备注册,这个skel_class中包含了fops的相关结构,如上图 */
    if (retval) {
        /* something prevented us from registering this driver */
        dev_err(&interface->dev,
            "Not able to get a minor for this device.\n");
        usb_set_intfdata(interface, NULL);
        goto error;
    }

    /* let the user know what node this device is now attached to */
    dev_info(&interface->dev,
         "USB Skeleton device now attached to USBSkel-%d",
         interface->minor);
    return 0;

error:
    if (dev)
        /* this frees allocated memory */
        kref_put(&dev->kref, skel_delete);
    return retval;
}

在这里插入图片描述

c. write

可以看出该write每次写入大小有限制的,如果超过一定长度,需要应用层去尝试多次写入,在填充urb是,write_back在写完触发时,释放信号量

static ssize_t skel_write(struct file *file, const char *user_buffer,
              size_t count, loff_t *ppos)
{
    struct usb_skel *dev;
    int retval = 0;
    struct urb *urb = NULL;
    char *buf = NULL;
    size_t writesize = min(count, (size_t)MAX_TRANSFER); /* 写入大小是由限制的,取小的那一个 #define MAX_TRANSFER   (PAGE_SIZE - 512) */

    dev = file->private_data;

    /* cnt=0就是没有要写的 */
    if (count == 0)
        goto exit;

    /*
     * 通过信号量限制URB数量,最多8个
     */
    if (!(file->f_flags & O_NONBLOCK)) {
        if (down_interruptible(&dev->limit_sem)) {
            retval = -ERESTARTSYS;
            goto exit;
        }
    } else {
        if (down_trylock(&dev->limit_sem)) {
            retval = -EAGAIN;
            goto exit;
        }
    }

    spin_lock_irq(&dev->err_lock);
    retval = dev->errors;
    if (retval < 0) {
        /* any error is reported once */
        dev->errors = 0;
        /* to preserve notifications about reset */
        retval = (retval == -EPIPE) ? retval : -EIO;
    }
    spin_unlock_irq(&dev->err_lock);
    if (retval < 0)
        goto error;

    /* 申请urb */
    urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!urb) {
        retval = -ENOMEM;
        goto error;
    }
	/* 创建数据发送的buf */
    buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL,
                 &urb->transfer_dma);
    if (!buf) {
        retval = -ENOMEM;
        goto error;
    }
	
    if (copy_from_user(buf, user_buffer, writesize)) {
        retval = -EFAULT;
        goto error;
    }

    /* this lock makes sure we don't submit URBs to gone devices */
    mutex_lock(&dev->io_mutex);
    if (!dev->interface) {      /* disconnect() was called */
        mutex_unlock(&dev->io_mutex);
        retval = -ENODEV;
        goto error;
    }

    /* 填充urb */
    usb_fill_bulk_urb(urb, dev->udev,
              usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
              buf, writesize, skel_write_bulk_callback, dev);
    urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
    usb_anchor_urb(urb, &dev->submitted);

    /* 提交,然后数据发送 */
    retval = usb_submit_urb(urb, GFP_KERNEL);
    mutex_unlock(&dev->io_mutex);
    if (retval) {
        dev_err(&dev->interface->dev,
            "%s - failed submitting write urb, error %d\n",
            __func__, retval);
        goto error_unanchor;
    }

    /*
     * 释放我们对这个urb的引用,USB核心最终将完全释放它
     */
    usb_free_urb(urb);

    return writesize;

error_unanchor:
    usb_unanchor_urb(urb);
error:
    if (urb) {
        usb_free_coherent(dev->udev, writesize, buf, urb->transfer_dma);
        usb_free_urb(urb);
    }
    up(&dev->limit_sem);

exit:
    return retval;
}

d. read

读取函数与写类似,read_back时将读取的数据长度赋值给in_filled,也是如果读取超过了端点的最大长度,一次读的只能是端点大小,需要应用层进行多次读取操作

static ssize_t skel_read(struct file *file, char *buffer, size_t count,
             loff_t *ppos)
{
    struct usb_skel *dev;
    int rv;
    bool ongoing_io;

    dev = file->private_data;

    /* 没有urb或者要读的为0,直接返回 */
    if (!dev->bulk_in_urb || !count)
        return 0;

    /* no concurrent readers */
    rv = mutex_lock_interruptible(&dev->io_mutex);
    if (rv < 0)
        return rv;

    if (!dev->interface) {      /* disconnect() was called */
        rv = -ENODEV;
        goto exit;
    }

    /* if IO is under way, we must not touch things */
retry:
    spin_lock_irq(&dev->err_lock);
    ongoing_io = dev->ongoing_read;
    spin_unlock_irq(&dev->err_lock);
	/* 在读io操作时会被置位,也就是说如果正在读,则有以下两种情况 */
    if (ongoing_io) {
        /* 不阻塞,直接返回 */
        if (file->f_flags & O_NONBLOCK) {
            rv = -EAGAIN;
            goto exit;
        }
        /*
         * IO可能需要很长时间,因此需要在可中断状态下等待
         */
		/* 否则等待read完成,会给等待队列唤醒的信号 */
        rv = wait_event_interruptible(dev->bulk_in_wait, (!dev->ongoing_read));
        if (rv < 0)
            goto exit;
    }

    /* errors must be reported */
    rv = dev->errors;
    if (rv < 0) {
        /* any error is reported once */
        dev->errors = 0;
        /* to preserve notifications about reset */
        rv = (rv == -EPIPE) ? rv : -EIO;
        /* report it */
        goto exit;
    }

    /*
     * 如果已经有了填充,说明上一次或者刚才已经将数据读到了buf中
     */
    if (dev->bulk_in_filled) {
        /* 计算有多少可读 */
        size_t available = dev->bulk_in_filled - dev->bulk_in_copied;
        size_t chunk = min(available, count);

        if (!available) {
            /*
             * 全被读过了,即copy=fill,在重读一次
             */
            rv = skel_do_read_io(dev, count);
            if (rv < 0)
                goto exit;
            else
                goto retry;
        }
        /*
         * 将实际能拷贝的数据放进userbuf
         */
        if (copy_to_user(buffer,
                 dev->bulk_in_buffer + dev->bulk_in_copied,
                 chunk))
            rv = -EFAULT;
        else
            rv = chunk; /* 实际读出来的数据长度 */

        dev->bulk_in_copied += chunk;

        /*
         * 如果实际上读出来的小于要读的,再次启动io读取操作
         */
        if (available < count)
            skel_do_read_io(dev, count - chunk);
    } else {
        /* 缓存里没数据,直接读 */
        rv = skel_do_read_io(dev, count);
        if (rv < 0)
            goto exit;
        else
            goto retry;
    }
exit:
    mutex_unlock(&dev->io_mutex);
    return rv;
}

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-03-13 21:59:21  更:2022-03-13 22:00:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 6:20:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码