一、USB协议基础知识
前序:USB概念概述
USB1.0版本速度1.5Mbps(低速USB) USB1.1版本速度12Mbps(全速USB) USB2.0版本速度480Mbps(高速USB)。
USB 分为主从两大体系,一般而言, PC 中的 USB 系统就是作主,而一般的 USB 鼠标, U 盘则是典型的 USB 从系统。
USB主控制器这一块,我们至少要开发出 USB 的主控制器与从控制器,鼠标是低速设备,所需的是最简单的一类从控制器。主控制器则复杂得多,因为太过于复杂了,所以就形成了一些标准。在一个复杂的系统中,标准的好处就是可以让开发者把精力集中在自己负责的一块中来,只需要向外界提供最标准的接口,而免于陷于技术的汪洋大海中。
USB 主控制器主要有 1.1 时代的 OHCI 和 UHCI , 2.0 时代的 EHCI ,这些标准规定了主控制器的功能和接口(寄存器的序列及功能),对我们驱动工程师而言,这样的好处就是只要你的驱动符合标某一标准,你就能轻而易举的驱动所有这个标准的主控制器。要想把主控制器驱动起来,本来是一件很难的事情,估计全球的 IT 工程师没几个能有这样的水平,但有了标准,我们就可以轻松的占有这几个高水平的 IT 工程师的劳动成果。
主控制器和驱动有了,我们还需要 USB 协议栈,这就是整个 USB 系统的软件部分的核心(有的资料中直接把其称为 USB 核心), USB 协议栈一方面向使用 USB 总线的设备驱动提供操作 USB 总线的 API ,另一方面则管理上层驱动传下来的的数据流,按 USB 主控制器的要求放在控制器驱动规定的位置, USB 主控制器会调度这些数据。
我们这里用到了调度这个词, USB 主控制器的调度其实和火车的调度 CPU 的调度有相似之处,物理上的通路只有一条,但 USB 中规定的逻辑上的通路却有许多条,有时一个设备就会占用几条逻辑通道,而 USB 系统中又会有多个设备同时运行。这就好像是只有一条铁路线,但来来往往的火车却有许多, USB 主控制器的作用就是调度这些火车,而 USB 协议栈的作用则向上层的 USB 设备驱动提供不同的车次。
有了以上的这些模块,才能为 USB 鼠标设计驱动,这一点上 ps/2 鼠标的驱动和 USB 鼠标的驱动结构基本一样,只不过我们的数据通路是 USB 总线。
USB 系统甚至把设备驱动都给标准化了,只要是支持 USB 的主机,就可以支持任何一个厂商的 USB 鼠标,任何一个厂商的 U 盘,只要是被 USB 系统包函的设备,只要这些设备支持相应的标准,就无需重新设计驱动而直接使用。
下是简单的列出了 USB 设备类型,理想的情况 USB 系统要对这些设备作完整的支持,设备也必须符合 USB 规范中的要求。
1 - audio :表示一个音频设 备。 |
|
2 - communication device :通讯设备,如电话, moden 等等。 |
|
3 - HID :人机交互设备,如键盘,鼠标等。 |
|
6 - image 图象设备,如扫描仪,摄像头等,有时数码相 机也可归到这一类。 |
|
7 -打印机类。如单向,双向打印机等。 |
|
8 - mass storage 海量存储类。所有带有一定存储功能的都可以归到这一类。如数码相机大多数都归这一类。 |
|
9 - hub 类。 |
|
11 - chip card/smart card 。 |
|
13 -- Content Security |
|
14 -- Video ( Interface ) |
|
15 -- Personal Healthcare |
|
220 -- Diagnostic Device |
|
224 -- Wireless Controller ( Interface ) |
|
239 -- Miscellaneous |
|
254 -- Application Specific ( Interface ) |
|
255 - vendor specific. 厂家的自定义类,主要用于一些特殊的设备。如接口转接卡等。 |
随着 USB 技术的发展, USB 系统中的一些不足也逐渐被承认, OTG 就是这种情况下的主要产物。
现在市面上有些设备(比如一些 MP4 )即能插上电脑当 U 盘使,也能被 U 盘插上读取 U 盘。这样的设备在 USB 系统中是作主还是作从呢?
这就是 OTG(On-The-Go), 即可以作主也可以作从,传说中的雌雄同体。这主要是为嵌入式设备准备的,因为 USB 是一种主从系统,不能支持点对点平等的传输数据, OTG 正是在这种需求下产生的, OTG 不仅支持控制器的主从切换,在一定层度上,也支持相同设备之间的数据交换。
1、USB的传输线结构
一条USB的传输线分别由地线、电源线、D+、D-四条线构成,D+和D-是差分输入线(抗干扰),它使用的是3.3V的电压,而电源线和地线可向设备提供5V电压,最大电流为500MA。OTG 的做法就是增来一个 ID pin 来判断设备是接入设备的是主还是从。vbus 主要是供电, D+/D- 则是用来传输数据,就是我们前面所讲的主设备和从设备间唯一的一条铁路。
|
信号线名称 |
颜色 |
1 |
Vbus |
红 |
2 |
D- |
白 |
3 |
D+ |
绿 |
4 |
GNU |
黑 |
shell (金属壳) |
屏敝层 |
|
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主机控制器属于南桥芯片的一部分,通过PCI总线和处理器通信。USB主机控制器分为UHCI(英特尔提出)、OHCI(康柏和微软提出)、 EHCI。其中OHCI驱动程序用来为非PC系统上以及带有SiS和ALi芯片组的PC主办上的USB芯片提供支持。UHCI驱动程序多用来为大多数其他PC主板(包括Intel和Via)上的USB芯片提供支持。ENCI兼容OHCI和UHCI。UHCI的硬件线路比OHCI简单,所以成本较低,但需要较复杂的驱动程序,CPU负荷稍重。主机控制器驱动程序完成的功能主要包括:解析和维护URB,根据不同的端点进行分类缓存URB;负责不同USB传输类型的调度工作;负责USB数据的实际传输工作;实现虚拟跟HUB的功能。
4、USB设备的构成
USB设备的构成包括了配置,接口和端点。
1. 设备通常具有一个或者更多个配置
2. 配置经常具有一个或者更多个接口
3. 接口通常具有一个或者更多个设置
4. 接口没有或者具有一个以上的端点
需要注意的是,驱动是绑定到USB接口上,而不是整个设备。
5、主控制怎么正确访问各种不同的USB设备
每一个USB设备接入PC时,USB总线驱动程序都会使用默认的地址0(仅未分配地址的设备可以使用)跟USB设备通信,然后给它分配一个编号,接在USB总线上的每一个USB设备都有自己的编号(地址),PC机想访问某个USB设备时,发出的命令都含有对应的编号(地址)就可以了。
USB总线驱动程序获取USB设置信息。USB设备里都会有一个叫 EEPROM的东东,它就是用来存储设备本身信息的。它与Flash虽说都是要电擦除的,但它可以按字节擦除,Flash只能一次擦除一个 block。
6、usb-firmware简易框架
usb firmware主要工作是满足usb 协议所定义的标准请求(usb协议第9章第4节),不同的firmware因为硬件不同而操作有所不同,但目的都是完成主控制器对设备的标准请求,大致框图如下:
7、USB传输事务
USB通信最基本的形式是通过一个名为端点(endpoint)的东西。它是真实存在的。
端点只能往一个方向传送数据(端点0除外,端点0使用message管道,它既可以IN又可以OUT),或者IN,或者OUT。除了端点0,低速设备只能有2个端点,高速设备也只能有15个IN端点和15个OUT端点。
主机和端点之间的数据传输是通过管道。
端点只有在device上才有,协议说端点代表在主机和设备端点之间移动数据的能力。
USB通信都是由host端发起的。
首先明确一点USB协议规定所有的数据传输都必须由主机发起。所以这个传输的一般格式:令牌包(表明传输的类型),数据包(实际传输的数据),握手包(数据的正确性)。首先是由主机控制器发出令牌包,然后主机/设备发送数据包,甚至可以没有,最后设备/主机发送握手包,这么一个过程就叫做一个USB传输事务。一个USB传输事务就实现了一次从主机和设备间的通讯。USB的事务有:OUT、IN、SETUP事务。
令牌包:可分为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)
8、USB协议的四种传输类型
因为usb支持的设备实在是太多,而且不同的设备对于传输数据各有各的要求和这就导致了我们需要不同的传输方式。USB支持4种传输方式:控制传输;批量传输;中断传输;实(等)时传输。
控制传输:首先发送 Setup 传输事务,然后IN/OUT传输事务,最后是 STATUS transaction,向主机汇报前面SETUP 和 IN/OUT阶段的结果。控制传输主要用于向设备发送配置信息、获取设备信息、发送命令道设备,或者获取设备的状态报告。控制传输一般发送的数据量较小,当USB设备插入时,USB核心使用端点0对设备进行配置,另外,端口0与其他端点不一样,端点0可以双向传输。
批量传输:由OUT事务和IN事务构成,用于大容量数据传输,没有固定的传输速率,也不占用带宽,当总线忙时,USB会优先进行其他类型的数据传输,而暂时停止批量转输。批量传输通常用在数据量大、对数据实时性要求不高的场合,例如USB打印机、扫描仪、大容量存储设备、U盘等。
中断传输:由OUT事务和IN事务构成,中断传输就是中断端点以一个固定的速度来传输较少的数据,USB键盘和鼠标就是使用这个传输方式。这里说的中断和硬件上下文中的中断不一样,它不是设备主动发送一个中断请求,而是主机控制器在保证不大于某个时间间隔内安排一次传输。中断传输对时间要求比较严格,所以可以用中断传输来不断地检测某个设备,当条件满足后再使用批量传输传输大量的数据。
等时传输:由OUT事务和IN事务构成,有两个特殊地方,第一,在同步传输的IN和OUT事务中是没有握手阶段;第二,在数据包阶段所有的数据包都为DATA0 。等时传输同样可以传输大批量数据,但是对数据是否到达没有保证,它对实时性的要求很高,例如音频、视频等设备(USB摄像头,USB话筒)。
这4种传输方式由4个事务组成:
IN事务:IN事务为host输入服务,当host需要从设备获得数据的时候,就需要IN事务。
OUT事务:OUT事务为host输出服务,当host需要输出数据到设备的时候,就需要OUT事务。
SETUP事务:SETUP事务为host控制服务,当host希望传输一些USB规范的默认操作的时候就需要使用setup事务。
SOF事务:这个用于帧同步。
然后这4种事务又由3类包(token包,handshake包,data包)组成,每类又分几种:
in包:in包用于指明当前的事务为in类型的。
out包: out包用于指明当前事务为out类型的。
setup包: setup包指明当前事务为setup类型的。
sof包: sof包指明当前事务为setup类型的。
ack包:ack握手包指明当前的事务的数据包传输是成功的。
nak包:nak握手包指明当前设备忙,不能处理数据包,请主机稍后再次发送。
stall包:stall握手包指明当前设备不能接受或者传输数据,表示一个严重的错误。
data0包:该数据包的类型为0。
data1包:该数据包的类型为1。
下图是一个USB鼠标插入Linux系统时完整的枚举过程,一共发生了11次传输,每次传输包括几个事务,每个事务又包括几个包,每个包包括几个域。
这里有一个概念需要注意,这里的中断传输与硬件中断那个中断是不一样的,这个中断传输实际是靠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包。
9、USB设备被识别的过程
当USB设备插上主机时,主机就通过一系列的动作来对设备进行枚举配置。
1、接入态(Attached):设备接入主机后,主机通过检测信号线上的电平变化来发现设备的接入;
2、供电态(Powered):就是给设备供电,分为设备接入时的默认供电值,配置阶段后的供电值(按数据中要求的最大值,可通过编程设置)
3、缺省态(Default):USB在被配置之前,通过缺省地址0与主机进行通信;
4、地址态(Address):经过了配置,USB设备被复位后,就可以按主机分配给它的唯一地址来与主机通信,这种状态就是地址态;
5、配置态(Configured):通过各种标准的USB请求命令来获取设备的各种信息,并对设备的某此信息进行改变或设置。
6、挂起态(Suspended):总线供电设备在3ms内没有总线动作,即USB总线处于空闲状态的话,该设备就要自动进入挂起状态,在进入挂起状态后,总的电流功耗不超过280UA。
10、标准的USB设备请求命令
USB设备请求命令是在控制传输的第一个阶段:setup事务传输的数据传输阶段发送给设备的。
标准USB设备请求命令共有11个,大小都是8个字节,具有相同的结构,由5 个字段构成。通过标准USB准设备请求,我们可以获取存储在设备EEPROM里面的信息;知道设备有哪些的设置或功能;获得设备的运行状态;改变设备的配置等。
标准USB准设备请求 = bmRequestType(1) + bRequest(2) + wvalue(2) + wIndex(2) + wLength(2)
bmRequestType:
[7 bit]= 0主机到设备; 1设备到主机
[6-5 bit]= 00标准请求命令; 01类请求命令; 10用户定义命令; 11保留
[4-0 bit]= 00000 接收者为设备; 00001 接收者为接口; 00010 接收者为端点; 00011 接收者为其他接收者; 其他 其他值保留
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 阶段的长度。
二、Linux USB系统架构
这个是USB系统的拓扑图,4个部分构成:USB主机控制器,根集线器,集线器,设备。其中Root Hub与USB主机控制器是绑定在一起的。
在机箱的尾部面板上,物理上存在一,二或四个USB端口。端口可以用来连接一个普通设备或者一个hub,hub是一个USB设备,可以用来扩展连接USB设备的端口数量。最大连接USB设备数量是减去连在总线上的hub数量(如果有50个hub,那么最多77(=127-50)个设备能够连接),剩下的就是能够连接USB设备的数量。Hub总是高速的,如果一个hub是自供电的,那么任何设备都能够附着到上面。但是如果hub是总线供电的,那么仅仅低供电(最大100mA)设备能够附着到上面,一个总线供电的hub不应该连接到另一个总线供电的hub-你应该在总线供电和自供电间交替.
通常情况下主机控制器的物理端口由一个虚拟的root hub脸管理。这个hub是有主机控制器(host controller)的设备驱动虚拟的,用来统一管理总线拓扑,因此USB子系统的驱动能够用同样的方法管理每个端口。
USB通信都是由host端发起的。USB设备驱动程序分配并初始化一个URB发给USB Core,USB Core改一改,发给USB主机控制器驱动,USB主机控制器驱动把它解析成包,在总线上进行传送。
USB Core是由内核实现的,其实也就是把host control driver里的功能更集中的向上抽象了一层,它是用来对最上层的USB设备驱动屏蔽掉host control的不同。
USB通信最基本的形式是通过一个名为端点(endpoint)的东西。它是真实存在的。端点只能往一个方向传送数据(端点0除外,端点0使用message管道,它既可以IN又可以OUT),或者IN,或者OUT(前面已经介绍过)。除了端点0,低速设备只能有2个端点,高速设备也只能有15个IN端点和15个OUT端点。主机和端点之间的数据传输是通过管道。端点只有在device上才有,协议说端点代表在主机和设备端点之间移动数据的能力。
Linux系统下的usb部分分为四个部门或者叫做四大家族,他们是host控制器驱动、hub驱动、usb core、设备类驱动,他们共同配合着完成了对usb设备的访问操作。
枚举和设备描述符
每当一个USB设备附着到总线上,它将会被USB子系统枚举.也就是分配唯一的设备号(1-127)然后读取设备描述符.描述符是一个包含关于设备的信息和属性的数据结构.USB标准定义了一个描述符层次结构,下图所示:
标准描述符
设备描述符: 描述USB设备的大概信息,其中包括适用于设备的全局信息,所有设备的配置。一个USB设备只有一个设备描述符。
配置描述符: 描述了特定的设备配置信息。一个USB设备可以有一或多个配置描述符。每个配置有一个或多个接口(interface),并且每个接口有零或多个端点(endpoint)。一个端点在一个单独的配置下,是不和其他的接口共享的,但是一个单独的接口对于同一个端点能够有几种可选的配置。端点可以没有限制的在一部分不同的配置下的接口间共享。配置仅仅能够通过标准的控制传输set_configuration来激活。不同的配置能够用来全局配置信息,例如供电消耗。
接口描述符: 描述了一个配置内的特定接口。一个配置提供一个或多个接口,每个接口带有零个或多个端点描述符描述了在配置内的唯一配置。一个可以包含可选的配置的接口使得配置好的端点和/或他们的特性能够多种多样。默认的接口设置总是设置为零。可替换的设置能够在标准控制传输的set_interface来选择一个。例如一个多功能设备带有话筒的摄像头,可以有三种可用的配置来改变分配在总线上的带宽。
Camera activated |
Microphone activated |
Camera and microphone activated |
端点描述符: 包含主机用来决定每个端点带宽的信息。一个端点象征一个USB设备的逻辑数据源或接收端(logic data source or sink)。端点零是用来所有的控制传输并且该端点没有设备描述符。USB spec交替使用pipe和endpoint术语。
字符串描述符: 是可选项,提供了unicode编码的额外的可读信息。他们可以是厂商和设备名称或序列号。
设备类型
标准的设备和接口描述符包含有关分类的内容:class, sub-class和protocol。这些字段主机可以用来设备或接口和驱动联系。依赖于分类说明是如何指定的?对于class字段和接口描述符的合法字段是由USB Device Working Group来定义的。
在Class Specification中将设备或接口分组归类并指定特性,这样就使得主机开发软件能够基于这个类别进行管理多种多样的实现。这样的主机软件通过设备中的描述信息将操作方法绑定到指定的设备。一个类别规格作为所有的该类别的设备或接口的最小操作框架服务。(PS:也就是说,所有该类别的设备或接口,都是以类别规格定义为接口框架。)
人机接口设备
HID分类,主要是包含人们控制计算机系统的设备。典型的HID分类设备包含:
键盘和鼠标设备例如:标准的鼠标设备,追踪球,游戏手柄。
前端面板控制 例如:旋钮,开关,按键,滚动器。
可能在电话设备,远端控制VCR,游戏或模拟设备上存在控制器。
再了解一下USB驱动框架:
USB总线和USB设备使用软件进行抽象描述起来是非常复杂的,一方面是协议使然,一方面也是因为它们使用太广泛了,抽象时考虑很太多情况。幸运的是,内核开发者们抽象出来的内核USB 子系统把很多复杂性都隐藏了。
针对上面这幅图,为了理解什么是USB子系统,我们要做以下说明:
a) USB 驱动都是夸kernel子系统的,因为最终USB设备是要通过BLCOCK 或CHAR设备的方式呈现给我们的,所以USB Driver之上还有一层。
b) USB driver利用USB Core提供的API来简单优雅的完成驱动工作,这里USB Core抽象了复杂的USB协议。
c) 主机控制器驱动位于USB软件的最下层,提供主机控制器硬件的抽象,隐藏硬件的细节,在主机控制器之下是物理的USB及所有与之连接的USB设备。主机控制器驱动只和USB Core进行关联,USB Core将用户的请求映射到相关的主机控制器驱动,从而使用户无需去访问主机控制器。
d) USB Core和USB主机控制器驱动就构成了我们的USB子系统,USB Core负责实现一些核心的功能,例如协议之类,提供一个用于访问和控制USB硬件的接口,使设备驱动不用去考虑系统当前使用哪种主机控制器。自从有了USB子系统,写USB驱动的时候,只需要调用USB Core export的接口,就几乎能完成所有工作。
e) USB总线将USB设备和USB驱动关联起来。
USB子系统初始化
usb初始化函数定义在内核源码(2.6.37)drivers/usb/core/usb.c:
/* * Init */ static int __init usb_init(void) { int retval; if (nousb) { pr_info("%s: USB support disabled\\n", usbcore_name); return 0; } retval = usb_debugfs_init(); if (retval) goto out; 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; retval = usb_major_init(); if (retval) goto major_init_failed; retval = usb_register(&usbfs_driver); if (retval) goto driver_register_failed; retval = usb_devio_init(); if (retval) goto usb_devio_init_failed; retval = usbfs_init(); if (retval) goto fs_init_failed; retval = usb_hub_init(); if (retval) goto hub_init_failed; retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE); if (!retval) goto out; usb_hub_cleanup(); hub_init_failed: usbfs_cleanup(); fs_init_failed: usb_devio_cleanup(); usb_devio_init_failed: usb_deregister(&usbfs_driver); driver_register_failed: usb_major_cleanup(); major_init_failed: bus_unregister_notifier(&usb_bus_type, &usb_bus_nb); bus_notifier_failed: bus_unregister(&usb_bus_type); bus_register_failed: usb_debugfs_cleanup(); out: return retval; } subsys_initcall(usb_init);
usb_debugfs_init():
DebugFS,顾名思义,是一种用于内核调试的虚拟文件系统,内核开发者通过debugfs和用户空间交换数据。类似的虚拟文件系统还有procfs和sysfs等,这几种虚拟文件系统都并不实际存储在硬盘上,而是Linux内核运行起来后,执行mount -t debugfs none /media/mmcblk0p2/ 才建立起来。在/media/mmcblk0p2/目录下创建usb目录并在下面创建devices文件。
当我们执行cat devices会调用usbfs_devices_fops->read(usb_device_read)函数去搜寻usb_bus_list链表下的usb设备信息,也就是所有总线下的设备。
bus_register:
是将usb总线注册到系统中,总线可是linux设备模型中的领导者,不管是多大的领导,也是领导,如PCI、USB、I2C,即使他们在物理上有从属关系,但是在模型的世界里,都是总线,拥有一样的待遇,所以任何一个子系统只要管理自己的设备和驱动,就需要向内核注册一个总线,注册报到。
bus_register_notifier:
大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。
通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。所以对于通知链表来说有一个通知方与一个接收方。在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。其实和系统调用signal的思想差不多。
bus_register->BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier),已经初始化了usb_bus_type->p->bus_notifier通过blocking_notifier_chain_register函数注册到通知链表。
那什么时候usb总线收到通知呢?
当总线发现新的设备调用device_add->blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_ADD_DEVICE, dev)
当总线卸载设备时调用device_del->blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_DEL_DEVICE, dev);
则调用usb_bus_nb的回调成员函数notifier_call(usb_bus_notify),函数定义如下:
/* * Notifications of device and interface registration */ 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)//usb 设备 (void) usb_create_sysfs_dev_files(to_usb_device(dev)); //创建descriptors文件 else if (dev->type == &usb_if_device_type) //usb接口 (void) usb_create_sysfs_intf_files( to_usb_interface(dev));//创建interface文件 break; case BUS_NOTIFY_DEL_DEVICE: if (dev->type == &usb_device_type)//usb设备 usb_remove_sysfs_dev_files(to_usb_device(dev));//删除descriptors文件 else if (dev->type == &usb_if_device_type)//usb接口 usb_remove_sysfs_intf_files(to_usb_interface(dev));//删除interface文件 break; } return 0; }
usb_major_init:注册字符设备,主设备号180。
usb_register(&usbfs_driver):
struct usb_driver usbfs_driver = { .name = "usbfs", .probe = driver_probe, .disconnect = driver_disconnect, .suspend = driver_suspend, .resume = driver_resume, };
usb_register->usb_register_driver():
/** * usb_register_driver - register a USB interface driver * @new_driver: USB operations for the interface driver * @owner: module owner of this driver. * @mod_name: module name string * * Registers a USB interface driver with the USB core. The list of * unattached interfaces will be rescanned whenever a new driver is * added, allowing the new driver to attach to any recognized interfaces. * Returns a negative error code on failure and 0 on success. * * NOTE: if you want your driver to use the USB major number, you must call * usb_register_dev() to enable that functionality. This function no longer * takes care of that. */ int usb_register_driver(struct usb_driver *new_driver, struct module *owner, const char *mod_name) { int retval = 0; if (usb_disabled()) return -ENODEV; new_driver->drvwrap.for_devices = 0; new_driver->drvwrap.driver.name = (char *) new_driver->name; new_driver->drvwrap.driver.bus = &usb_bus_type; new_driver->drvwrap.driver.probe = usb_probe_interface; new_driver->drvwrap.driver.remove = usb_unbind_interface; new_driver->drvwrap.driver.owner = owner; new_driver->drvwrap.driver.mod_name = mod_name; spin_lock_init(&new_driver->dynids.lock); INIT_LIST_HEAD(&new_driver->dynids.list); retval = driver_register(&new_driver->drvwrap.driver); if (retval) goto out; usbfs_update_special(); retval = usb_create_newid_file(new_driver); if (retval) goto out_newid; retval = usb_create_removeid_file(new_driver); if (retval) goto out_removeid; pr_info("%s: registered new interface driver %s\\n", usbcore_name, new_driver->name); out: return retval; out_removeid: usb_remove_newid_file(new_driver); out_newid: driver_unregister(&new_driver->drvwrap.driver); printk(KERN_ERR "%s: error %d registering interface " " driver %s\\n", usbcore_name, retval, new_driver->name); goto out; } EXPORT_SYMBOL_GPL(usb_register_driver);
其余功能如下:
1> driver_register实现。后面会详细分析。
2> usbfs_update_special(): 跟usb文件系统相关,看下面的usbfs_init分析。
3> usb_create_newid_file(): 创建newid属性文件,在/sys/bus/usb/drivers/usbfs/下面可以看到此文件。根据传入的ID值,增加一个新的动态usb设备到驱动(这里是usbfs),引起驱动重新探测所有的设备。
4> usb_create_removeid_file():创建removeid属性文件,在/sys/bus/usb/drivers/usbfs/下面可以看到此文件。根据传入的ID值,删除驱动(这里是usbfs)里的一个usb设备。
5> 输出信息:usbcore: registered new interface driver usbfs
现在分析driver_register功能:
1> 首先判断,些驱动所属bus的subsys_private结构有没有初始化。如果没有,报bug信息。
2> 判断需要注册的driver和driver所属的bus是否都有probe, remove, shutdown函数。如有,打印kernel warning信息。
3> 判断此driver已经在driver所属的bus上面注册过了。如果注册过了,打印错误信息,并返回。
4> 调用bus_add_driver来注册driver。
5> 调用driver_add_groups来添加组属性。
最后对bus_add_driver进行分析。
/** * bus_add_driver - Add a driver to the bus. * @drv: driver. */ int bus_add_driver(struct device_driver *drv) { struct bus_type *bus; struct driver_private *priv; int error = 0; bus = bus_get(drv->bus); if (!bus) return -EINVAL; pr_debug("bus: \'%s\': add driver %s\\n", bus->name, drv->name); priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) { error = -ENOMEM; goto out_put_bus; } klist_init(&priv->klist_devices, NULL, NULL); priv->driver = drv; drv->p = priv; priv->kobj.kset = bus->p->drivers_kset; error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, "%s", drv->name); if (error) goto out_unregister; if (drv->bus->p->drivers_autoprobe) { error = driver_attach(drv); if (error) goto out_unregister; } klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); module_add_driver(drv->owner, drv); error = driver_create_file(drv, &driver_attr_uevent); if (error) { printk(KERN_ERR "%s: uevent attr (%s) failed\\n", __func__, drv->name); } error = driver_add_attrs(bus, drv); if (error) { /* How the hell do we get out of this pickle? Give up */ printk(KERN_ERR "%s: driver_add_attrs(%s) failed\\n", __func__, drv->name); } if (!drv->suppress_bind_attrs) { error = add_bind_files(drv); if (error) { /* Ditto */ printk(KERN_ERR "%s: add_bind_files(%s) failed\\n", __func__, drv->name); } } kobject_uevent(&priv->kobj, KOBJ_ADD); return 0; out_unregister: kobject_put(&priv->kobj); kfree(drv->p); drv->p = NULL; out_put_bus: bus_put(bus); return error; }
其功能是向bus中添加一个driver。
1> bus_get():bus的计数加1;
2> kzalloc,分配driver_private内存空间。
3> 初始化此driver的klist_devices链表。
4> kobject_init_and_add():在/sys/bus/usb/drivers/下面创建usbfs文件夹。
5> 如果总线支持drivers_autoprobe,调用driver_attach。(USB 总线支持)
6> driver_create_file: 在/sys/bus/usb/drivers/usbfs下面创建uevent属性文件。
7> driver_add_attrs():将总线的属性也加到/sys/bus/usb/drivers/usbfs
8> add_bind_files():在/sys/bus/usb/drivers/usbfs创建bind和unbind属性文件。
9> kobject_uevent():发送一个KOBJ_ADD的事件。
在/sys/bus/usb/drivers/usbfs下面的文件:
bind module new_id remove_id uevent unbind
usb_devio_init:注册字符设备,主设备189。
usbfs_init:
int __init usbfs_init(void) { int retval; retval = register_filesystem(&usb_fs_type); if (retval) return retval; usb_register_notify(&usbfs_nb); /* create mount point for usbfs */ usbdir = proc_mkdir("bus/usb", NULL); return 0; }
函数功能:
1> register_filesystem注册usbfs文件系统,当应用程序执行mount命令的时候,挂载文件系统到相应的目录。
2> usb_register_notify函数注册到内核通知链表,当收到其他子系统通知,调用notifier_call回调函数usbfs_notify:
static int usbfs_notify(struct notifier_block *self, unsigned long action, void *dev) { switch (action) { case USB_DEVICE_ADD: usbfs_add_device(dev);//在bus号创建的目录下,根据设备号创建设备文件 break; case USB_DEVICE_REMOVE: usbfs_remove_device(dev);//删除bus号创建的目录下的设备文件 break; case USB_BUS_ADD: usbfs_add_bus(dev);//根据bus号创建目录 break; case USB_BUS_REMOVE: usbfs_remove_bus(dev);//删除bus号创建的目录 } usbfs_update_special();//更新文件系统节点 usbfs_conn_disc_event(); return NOTIFY_OK; }
static BLOCKING_NOTIFIER_HEAD(usb_notifier_list);usb_notifier_list通知链表初始化
usb_register_notify->blocking_notifier_chain_register(&usb_notifier_list, nb):向usb_notifier_list通知链表注册
blocking_notifier_call_chain(&usb_notifier_list, USB_DEVICE_ADD, udev):通知有usb设备增加
blocking_notifier_call_chain(&usb_notifier_list,USB_DEVICE_REMOVE, udev):通知有usb设备移除
blocking_notifier_call_chain(&usb_notifier_list, USB_BUS_ADD, ubus):通知有usb总线增加
blocking_notifier_call_chain(&usb_notifier_list, USB_BUS_REMOVE, ubus):通知有usb总线移除
3> proc_mkdir在/proc/bus/目录下创建usb目录。
usb_register_device_driver:
在了解usb_generic_driver驱动前,先分析usb总线的match函数:
static int usb_device_match(struct device *dev, struct device_driver *drv) { /* devices and interfaces are handled separately */ if (is_usb_device(dev)) { /* interface drivers never match devices */ if (!is_usb_device_driver(drv)) return 0; /* TODO: Add real matching code */ return 1; } else if (is_usb_interface(dev)) { struct usb_interface *intf; struct usb_driver *usb_drv; const struct usb_device_id *id; /* device drivers never match interfaces */ if (is_usb_device_driver(drv)) return 0; intf = to_usb_interface(dev); usb_drv = to_usb_driver(drv); id = usb_match_id(intf, usb_drv->id_table); if (id) return 1; id = usb_match_dynamic_id(intf, usb_drv); if (id) return 1; } return 0; }
函数中我们分成两类判断:
is_usb_device(),根据设备类型dev->type == &usb_device_type 来判断是否是usb设备,然后在通过for_devices(usb_register_device_driver函数注册的时候设置为1) 判断驱动是否是usb设备设备驱动,如果成功,则设备和设备驱动匹配,调用相应的驱动的probe函数(因为usb总线没有probe成员函数)。
is_usb_interface(),根据设备类型dev->type == &usb_if_device_type 来判断是否是接口,然后在通过for_devices(usb_register函数注册的时候设置为0) 判断驱动是否是接口驱动,如果是接口驱动(所以调用usb_register都是注册的接口驱动,因为一个设备可以有多个接口,每个接口必须独立驱动),接着usb_match_id这个函数就是用来判断这个接口是否在id table中得到了match,一旦得到,就进入了具体接口驱动的probe函数了。。
到这里我们不禁要思索驱动找到了注册的地方,那设备来自哪里?这里也有两个函数要分析:
usb_alloc_dev():dev->dev.type = &usb_device_type,这里就表示了是usb设备,这个函数主要有两个地方调用。一个就是usb_init->usb_hub_init->hub_thread->hub_events->hub_port_connect_change,这个会在下面进行详细的分析;另外一个musb_probe->musb_init_controller->usb_add_hcd,DM8168芯片注册主控器的时候用到(或者其他芯片主控器注册)。
usb_set_configuration(): intf->dev.type = &usb_if_device_type,这里就表示了是接口。
这里我们知道usb_register 和 usb_register_device_driver,一个是设备驱动的注册,一个是接口驱动的注册,match的时候通过for_devices来区分。接口指的就是一种具体的功能。
上面我们提过每种类型的总线都有一套自己的驱动函数,看来在usb的世界里更特殊一些,usb总线下的设备驱动有一套,接口驱动也有一套:usb_probe_interface。
不管是设备还是接口都是挂在总线上的,一个总线只有一个match函数,usb_device_match。
在这个usb的match函数里,首先是对usb设备的match,设备的match很简单的,只要是个usb设备就认为match了,因为现在进来的usb设备统统都认为是usb_generic_driver的,都和他match。上面我们提到过这个,所有的usb设备首先都会经过筛选这一关,处理之后,才有重生的机会。接口就不一样了,如果进来的dev不是设备,就认为是个接口,然后判断drv是否为接口驱动,如果是,那么就继续判断,这个判断机制就是usb特有的了:Id。每个接口驱动注册的时候都会有一个id 的,加到了id table表中。
看了上面分析,usb match函数中涉及到的设备和接口驱动两条判断路线,在usb的世界里,真正的驱动是针对接口的,针对设备的其实是刚开始没有配置之前,一个通用的usb设备驱动,用来处理所有的usb设备,将其进入配置态,获取该配置下的各种接口,并将接口作为一种特殊的usb设备(接口设备)添加到设备模型中。
下面我们分析usb_generic_driver:
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设备(只有设备先被注册之后才会分析接口,才会注册接口) 被探测并被注册到系统后(用device_add),会调用usb_bus_type.mach()(只要是usb设备,都会跟usb_generic_driver匹配上),之后会调用usb_probe_device(),从而引发usb_generic_driver的 probe()调用,也就是generic_probe函数。
下面将会对generic_probe函数进行分析:
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); if (err) { dev_err(&udev->dev, "can\'t set config #%d, error %d\\n", c, err); /* This need not be fatal. The user can try to * set other configurations. */ } } } usb_notify_add_device(udev); return 0; }
usb_generic_driver中的generic_probe函数,这个函数是一个usb设备的第一个匹配的driver。Generic通用,只要是个usb设备就得先跟他来一段,usb设备驱动界的老大。他的probe干啥了呢?很简单!找个合适的配置,配置一下。从此usb设备就进入配置的时代了。(前期的工作谁做的呢,到这都已经设置完地址了,当然是hub了,hub发现设备后,会进行前期的枚举过程,获得配置,最终调用device_add将该usb设备添加到总线上。这个过程可以专门来一大段,是hub的主要工作,所以需要把hub单独作为一个家族来对待,人家可是走在第一线的默默无闻的工作者,默默的将设备枚举完成后,将这个设备添加到usb总线上,多伟大)。
注意:设备setconfig时参数只能为0或者合理的配置值,0就代表不配置,仍然是寻址态。不过有些设备就是拿配置0作为配置值得。
usb_choose_configuration从设备可能的众多配置(udev->descriptor.bNumConfigurations)选择一个合适的配置(struct usb_host_config),并返回该配置的索引值。
//为usb device选择一个合适的配置 int usb_choose_configuration(struct usb_device *udev) { int i; int num_configs; int insufficient_power = 0; struct usb_host_config *c, *best; best = NULL; //udev->config,其实是一个数组,存放设备的配置.usb_dev->config[m]-> interface[n]表示第m个配置的第n个接口的intercace结构.(m,n不是配置序号和接口序号). c = udev->config; //config项数 num_configs = udev->descriptor.bNumConfigurations; //遍历所有配置项 for (i = 0; i < num_configs; (i++, c++)) { struct usb_interface_descriptor *desc = NULL; //配置项的接口数目 //取配置项的第一个接口 if (c->desc.bNumInterfaces > 0) desc = &c->intf_cache[0]->altsetting->desc; ... ... //电源不足.配置描述符中的电力是所需电力的1/2 if (c->desc.bMaxPower * 2 > udev->bus_mA) { insufficient_power++; continue; } //非标准Ethernet-over-USB协议 if (i == 0 && num_configs > 1 && desc && (is_rndis(desc) || is_activesync(desc))){ ... ... } //选择一个不是USB_CLASS_VENDOR_SPEC的配置 else if (udev->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC && (!desc || desc->bInterfaceClass != USB_CLASS_VENDOR_SPEC)) { best = c; break; } /*如果所有剩下的配置是特殊的vendor,选择第一个*/ else if (!best) best = c; } ... ... //如果选择好了配置,返回配置的序号,否则,返回-1 if (best) { i = best->desc.bConfigurationValue; dev_info(&udev->dev, "configuration #%d chosen from %d choice%s\\n", i, num_configs, plural(num_configs)); } else { i = -1; dev_warn(&udev->dev, "no configuration chosen from %d choice%s\\n", num_configs, plural(num_configs)); } return i; }
例如:我机器上的的 usb 驱动加载时,输出:usb 1-1: configuration #1 chosen from 3 choices
表示:此设备有3个配置,而驱动最终选择了索引号为1的配置,至于选择策略是怎样的,请看usb_choose_configuration()函数。
generic_probe函数中的usb_set_configuration函数里有很重要的动作,不是简单的设置个配置,当我们选择了某一个配置后,需要将这个配置的所有接口取出来,初始化接口作为驱动对应的一种”设备”的参数,如总线类型、设备类型等,调用device_add将该接口设备添加到设备模型中。
int usb_set_configuration(struct usb_device *dev, int configuration) { ... ... if (cp && configuration == 0) dev_warn(&dev->dev, "config 0 descriptor??\\n"); /*首先,根据选择好的配置号找到相应的配置,在这里要注意了, dev->config[]数组中的配置并不是按照配置的序号来存放的,而是按照遍历到顺序来排序的.因为有些设备在发送配置描述符的时候,并不是按照配置序号来发送的,例如,配置2可能在第一次GET_CONFIGURATION就被发送了,而配置1可能是在第二次GET_CONFIGURATION才能发送. 取得配置描述信息之后,要对它进行有效性判断,注意一下本段代码的最后几行代码:usb2.0 spec上规定,0号配置是无效配置,但是可能有些厂商的设备并末按照这一约定,所以在linux中,遇到这种情况以上是关于Linux usb子系统:子系统架构的主要内容,如果未能解决你的问题,请参考以下文章
Linux笔记(固定USB摄像头硬件端口,绑定前后置摄像头)
Android 逆向Linux 文件权限 ( Linux 权限简介 | 系统权限 | 用户权限 | 匿名用户权限 | 读 | 写 | 执行 | 更改组 | 更改用户 | 粘滞 )(代码片段