一、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 size
- Cfgs: 配置的个数;USB里共有四大描述符,它们是设备描述符、端点描述符、接口描述符和配置描述符。
- P—设备信息
- Vendor: 厂商ID,Linuxfoundation的ID是1d6b,http://www.linux-usb.org/usb.ids
- Rev: 校订版本号
-
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;
}