irpas技术客

Linux之USB分析_夜暝_linux usb

未知 2424

一、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描述符:

设备描述符配置描述符接口描述符端点描述符字符串描述符(可选项)

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

关于字符串描述符:在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种不同的传输类型:

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

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

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

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

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

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

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

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的下行端口号个数。 B—Band width Alloc:该总线分配得到的带宽Int:中断请求数ISO:同步传输请求数,USB有四大传输,中断、控制、批量和同步。 D–Device Descriptor 设备描述符。 Ver:设备USB版本号。Cls:设备的类(hub的类是9),sub:设备的子类Prot:设备的协议MxPS:default 端点的最大packet sizeCfgs: 配置的个数;USB里共有四大描述符,它们是设备描述符、端点描述符、接口描述符和配置描述符。 P—设备信息 Vendor: 厂商ID,Linuxfoundation的ID是1d6b,http://www.linux-usb.org/usb.idsRev: 校订版本号

S—Manufacturer

S—产品

S—序列号

C*—配置描述信息

#Ifs:接口的数量,Atr:属性MxPwr:最大功耗,USB设备供电有两种方式,self-powered和bus-powered两种方式,驱动代码会判断设备标志寄存器是否过流的。最大500mA。 I–描述接口的接口描述符 If#:接口号Alt:接口属性#EPs:接口具有的端点数量,端点零必须存在,在USB设备addressed之前,会使用该端口配置设备。Cls:接口的类Sub:接口的子类Prot:接口的协议Driver:驱动的名称。 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; }


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Linux #USB #分为主从两大体系一般而言