一、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 activatedMicrophone activatedCamera 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 ", 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 ", 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 ", 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 ", 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 ", __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 ", __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 ", __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 "); 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 ", 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 ", i, num_configs, plural(num_configs)); } else { i = -1; dev_warn(&udev->dev, "no configuration chosen from %d choice%s ", 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?? "); /*首先,根据选择好的配置号找到相应的配置,在这里要注意了, dev->config[]数组中的配置并不是按照配置的序号来存放的,而是按照遍历到顺序来排序的.因为有些设备在发送配置描述符的时候,并不是按照配置序号来发送的,例如,配置2可能在第一次GET_CONFIGURATION就被发送了,而配置1可能是在第二次GET_CONFIGURATION才能发送. 取得配置描述信息之后,要对它进行有效性判断,注意一下本段代码的最后几行代码:usb2.0 spec上规定,0号配置是无效配置,但是可能有些厂商的设备并末按照这一约定,所以在linux中,遇到这种情况只是打印出警告信息,然后尝试使用这一配置.*/ n = nintf = 0; if (cp) { //接口总数 nintf = cp->desc.bNumInterfaces; //在这里, 注要是为new_interfaces分配空间,要这意的是, new_interfaces是一个二级指针,它的最终指向是struct usb_interface结构.特别的,如果总电流数要小于配置所需电流,则打印出警告消息.实际上,这种情况在usb_choose_configuration()中已经进行了过滤. new_interfaces = kmalloc(nintf * sizeof(*new_interfaces), GFP_KERNEL); ... ... for (; n < nintf; ++n) { new_interfaces[n] = kzalloc( sizeof(struct usb_interface), GFP_KERNEL); ... ... } //如果总电源小于所需电流,打印警告信息 i = dev->bus_mA - cp->desc.bMaxPower * 2; ... ... } //要对设备进行配置了,先唤醒它 ret = usb_autoresume_device(dev); if (ret) goto free_interfaces; //不是处于ADDRESS状态,先清除设备的状态 if (dev->state != USB_STATE_ADDRESS) usb_disable_device(dev, 1); /* Skip ep0 */ //确定我们有足够带宽提供这个配置 ret = usb_hcd_alloc_bandwidth(dev, cp, NULL, NULL); ... ... //发送控制消息,选取配置 ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_CONFIGURATION, 0, configuration, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); ... ... } //dev->actconfig存放的是当前设备选取的配置 dev->actconfig = cp; ... ... //将状态设为CONFIGURED usb_set_device_state(dev, USB_STATE_CONFIGURED); /*接下来,就要对设备进行配置了,首先,将设备唤醒.只有在ADDRESS状态才能转入到CONFIG状态.(SUSPEND状态除外). 所以,如果设备当前不是处于ADDRESS状态,就需要将设备的状态初始化 接着,发送SET_CONFIGURATION的Control消息给设备,用来选择配置最后,将dev->actconfig指向选定的配置,将设备状态设为CONFIG*/ //遍历所有的接口 for (i = 0; i < nintf; ++i) { struct usb_interface_cache *intfc; struct usb_interface *intf; struct usb_host_interface *alt; /*之前初始化的new_interfaces在这里终于要派上用场了.初始化各接口,从上面的初始化过程中,我们可以看出: Intf->altsetting,表示接口的各种设置 Intf->num_altsetting:表示接口的设置数目 Intf->intf_assoc:接口的关联接口(定义于minor usb 2.0 spec) Intf->cur_altsetting:接口的当前设置.*/ cp->interface[i] = intf = new_interfaces[i]; intfc = cp->intf_cache[i]; intf->altsetting = intfc->altsetting; intf->num_altsetting = intfc->num_altsetting; //是否关联的接口描述符,定义在minor usb 2.0 spec中 intf->intf_assoc = find_iad(dev, cp, i); kref_get(&intfc->ref); //选择0号设置 alt = usb_altnum_to_altsetting(intf, 0); //如果0号设置不存在,选排在第一个设置 if (!alt) alt = &intf->altsetting[0]; //当前的配置 intf->cur_altsetting = alt; //用来启用接口,也就是启用接口中的每一个endpoint. usb_enable_interface(dev, intf); //注意这个地方对intf内嵌的struct devcie结构赋值,它的type被赋值为了usb_if_device_type.bus还是usb_bus_type.可能你已经反应过来了,要和这个device匹配的设备是interface的驱动. intf->dev.parent = &dev->dev; intf->dev.driver = NULL; intf->dev.bus = &usb_bus_type; intf->dev.type = &usb_if_device_type; intf->dev.dma_mask = dev->dev.dma_mask; device_initialize(&intf->dev);//device 初始化 mark_quiesced(intf); /* device的命名: dev指的是这个接口所属的usb_dev,结合我们之前在UHCI中关于usb设备命名方式的描述.可得出它的命令方式如下: USB总线号-设备路径:配置号.接口号. 例如,在我的虚拟机上:/sys/bus/usb/devices 1-0:1.0 usb1 可以得知,系统只有一个usb control. 1-0:1.0:表示,第一个usb control下的root hub的1号配置的0号接口. */ sprintf(&intf->dev.bus_id[0], "%d-%s:%d.%d", dev->bus->busnum, dev->devpath, configuration, alt->desc.bInterfaceNumber); } kfree(new_interfaces); if (cp->string == NULL) cp->string = usb_cache_string(dev, cp->desc.iConfiguration); //注册每一个接口? for (i = 0; i < nintf; ++i) { struct usb_interface *intf = cp->interface[i]; dev_dbg(&dev->dev, "adding %s (config #%d, interface %d) ", intf->dev.bus_id, configuration, intf->cur_altsetting->desc.bInterfaceNumber); ret = device_add(&intf->dev);//增加device if (ret != 0) { dev_err(&dev->dev, "device_add(%s) --> %d ", intf->dev.bus_id, ret); continue; } usb_create_sysfs_intf_files(intf); } //使设备suspend usb_autosuspend_device(dev); return 0;}
最后,注册intf内嵌的device结构.设备配置完成了,为了省电,可以将设备置为SUSPEND状态.
到此为止usb_generic_driver凭借自己的博爱的胸襟将所有设备的各个接口添加到了linux的设备模型中。
usb设备首先以设备的身份与usb_generic_driver匹配,成功之后,会分裂出接口,当对接口调用device_add()后,会引起接口和接口驱动的匹配,这个匹配还是用usb_bus_type.mach()函数。因为接口的device->bus=& usb_bus_type, 这跟usb设备是一样的,所以,都会调用到usb_bus_type.mach(),但设备和接口的处理流程是不一样的(前面已经分析过)。
usb_hub_init:
int usb_hub_init(void){ if (usb_register(&hub_driver) < 0) { printk(KERN_ERR "%s: can't register hub driver ", usbcore_name); return -1; } khubd_task = kthread_run(hub_thread, NULL, "khubd"); if (!IS_ERR(khubd_task)) return 0; /* Fall through if kernel_thread failed */ usb_deregister(&hub_driver); printk(KERN_ERR "%s: can't start khubd ", usbcore_name); return -1;}
这个函数主要有两个功能:
在系统初始化的时候在usb_init函数中调用usb_hub_init函数,就进入了hub的初始化。
对于usb_register()可以看作是usb设备中的接口驱动,而usb_register_device_driver()是一个单纯的USB设备驱动。
在usb_hub_init函数中完成了注册hub驱动,并且利用函数kthread_run创建一个内核线程。该线程用来管理监视hub的状态,所有的情况都通过该线程来报告。
当加载主控器的时候,在自身的platform驱动的probe函数里,调用usb_add_hcd->register_root_hub向usb总线注册root hub设备, usb总线match成功后,由usb_generic_driver驱动的probe函数,配置interface设备,然后向usb总线注册interface, usb总线再一次match, 不过这次是匹配了interface,通过ID值和hub驱动配置,因此调用hub驱动的probe函数(hub_probe),hub_probe函数中调用hub_configure函数来配置hub,在这个函数中主要是利用函数usb_alloc_urb函数来分配一个urb,利用usb_fill_int_urb来初始化这个urb结构,包括hub的中断服务程序hub_irq的,查询的周期等。
每当有设备连接到USB接口时,USB总线在查询hub状态信息的时候会触发hub的中断服务程序hub_irq,在该函数中利用kick_khubd将hub结构通过event_list添加到khubd的队列hub_event_list,然后唤醒khubd。进入hub_events函数,该函数用来处理khubd事件队列,从khubd的hub_event_list中的每个usb_hub数据结构。该函数中首先判断hub是否出错,然后通过一个for循环来检测每个端口的状态信息。利用usb_port_status获取端口信息,如果发生变化就调用hub_port_connect_change函数来配置端口等。
static void hub_events(void){ ... ... while (1) { //如果hub_event_list为空,退出 spin_lock_irq(&hub_event_lock); if (list_empty(&hub_event_list)) { spin_unlock_irq(&hub_event_lock); break; } //取hub_event_list中的后一个元素,并将其断链 tmp = hub_event_list.next; list_del_init(tmp); //根据tmp获取hub hub = list_entry(tmp, struct usb_hub, event_list); //增加hub计数 kref_get(&hub->kref); //解锁 spin_unlock_irq(&hub_event_lock); hdev = hub->hdev; hub_dev = hub->intfdev; intf = to_usb_interface(hub_dev); ... ... usb_lock_device(hdev); //如果hub断开了,继续hub_event_list中的下一个 if (unlikely(hub->disconnected)) goto loop; //设备没有连接上 if (hdev->state == USB_STATE_NOTATTACHED) { hub->error = -ENODEV; //将下面的子设备全部disable hub_pre_reset(intf); goto loop; } /* 自动恢复 */ ret = usb_autopm_get_interface(intf); if (ret) { dev_dbg(hub_dev, "Can't autoresume: %d ", ret); goto loop; } //hub 暂停 if (hub->quiescing) goto loop_autopm; //hub 有错误发生? if (hub->error) { dev_dbg (hub_dev, "resetting for error %d ", hub->error); ret = usb_reset_composite_device(hdev, intf); if (ret) { dev_dbg (hub_dev, "error resetting hub: %d ", ret); goto loop_autopm; } hub->nerrors = 0; hub->error = 0; } /*首先,从hub_event_list摘下第一个元素,根据我们之前在接口驱动probe过程的kick_khubd()函数分析中,有将hub-> event_list添加到hub_event_list.因此,就可以顺藤摸瓜找到hub,再根据hub结构,找到接口结构和所属的usb 设备结构. 然后,进行第一个重要的判断.如果hub被断开了,则,断开hub下面所连接的所有端口,这是在hub_pre_reset()中完成的. 最后,进行第二个重要的判断,如果hub发生了错误,则reset它下面的所有端口,这是在usb_reset_composite_device()中完成的.*/ //在这里,它遍历hub上的每一个端口,如果端口的连接会生了改变(connect_change等于1)的情况,就会调用hub_port_connect_change() for (i = 1; i <= hub->descriptor->bNbrPorts; i++) { //检测端口是否忙 if (test_bit(i, hub->busy_bits)) continue; //change_bits会在hub 第一次初始化时被赋值。而event_bits则在hub_irq中改变 connect_change = test_bit(i, hub->change_bits); //如果都没有改变,继续测试下一个端口。 if (!test_and_clear_bit(i, hub->event_bits) && !connect_change && !hub->activating) continue; //Get_Port_Status:取得端口状态. //会取得port的改变值和状态值 ret = hub_port_status(hub, i, &portstatus, &portchange); if (ret < 0) continue; //在struct usb_dev中,有一个struct usb_device *children[USB_MAXCHILDREN]的成员,它是表示对应端口序号上所连接的usb设备. //如果对应端口没有在设备树上,且端口显示已经连接上 //将connect_change置为1 if (hub->activating && !hdev->children[i-1] && (portstatus & USB_PORT_STAT_CONNECTION)) connect_change = 1; //端口的连接状态发生了改变.需要发送Clear_Feature if (portchange & USB_PORT_STAT_C_CONNECTION) { clear_port_feature(hdev, i, USB_PORT_FEAT_C_CONNECTION); connect_change = 1; } //端口的状态从enable 变为了disable if (portchange & USB_PORT_STAT_C_ENABLE) { if (!connect_change) dev_dbg (hub_dev, "port %d enable change, " "status %08x ", i, portstatus); clear_port_feature(hdev, i, USB_PORT_FEAT_C_ENABLE); //端口已经被停止了,且端口已经被连在设备树中. //需要重启一下此端口 if (!(portstatus & USB_PORT_STAT_ENABLE) && !connect_change && hdev->children[i-1]) { dev_err (hub_dev, "port %i " "disabled by hub (EMI?), " "re-enabling... ", i); connect_change = 1; } } //Resume完成 if (portchange & USB_PORT_STAT_C_SUSPEND) { clear_port_feature(hdev, i, USB_PORT_FEAT_C_SUSPEND); //如果端口连接了设备,就将设备唤醒 if (hdev->children[i-1]) { ret = remote_wakeup(hdev-> children[i-1]); if (ret < 0) connect_change = 1; } //如果端口没有连接设备,就将端口禁用 else { ret = -ENODEV; hub_port_disable(hub, i, 1); } dev_dbg (hub_dev, "resume on port %d, status %d ", i, ret); } //有过流保护,需要对hub power on if (portchange & USB_PORT_STAT_C_OVERCURRENT) { dev_err (hub_dev, "over-current change on port %d ", i); clear_port_feature(hdev, i, USB_PORT_FEAT_C_OVER_CURRENT); hub_power_on(hub); } //Reset状态已经完成了 if (portchange & USB_PORT_STAT_C_RESET) { dev_dbg (hub_dev, "reset change on port %d ", i); clear_port_feature(hdev, i, USB_PORT_FEAT_C_RESET); } /*什么情况下, hub_port_connect_change才会被设为1. 1:端口在hub->change_bits中被置位.搜索整个代码树,发生在设置hub->change_bits的地方,只有在hub_port_logical_disconnect()中手动将端口禁用,会将对应位置1. 2:hub上没有这个设备树上没有这个端口上的设备.但显示端口已经连上了设备 3:hub这个端口上的连接发生了改变,从端口有设备连接变为无设备连接,或者从无设备连接变为有设备连接. 4:hub的端口变为了disable,此时这个端口上连接了设备,但被显示该端口已经变禁用,需要将connect_change设为1. 5:端口状态从SUSPEND变成了RESUME,远程唤醒端口上的设备失败,就需要将connect_change设为1. 另外hub_port_connect_change()函数我们放在后面再来讨论*/ if (connect_change) hub_port_connect_change(hub, i, portstatus, portchange); } //对HUB的处理 //如果hub状态末变化,不需要做任何处理 if (test_and_clear_bit(0, hub->event_bits) == 0) ; /* do nothing */ //Get_hub_status 失败? else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0) dev_err (hub_dev, "get_hub_status failed "); else { //这里是对应hub 状态发生了改变,且Get_hub_status正常返回的情况 //如果hub的本地电源供电发生了改变 if (hubchange & HUB_CHANGE_LOCAL_POWER) { dev_dbg (hub_dev, "power change "); clear_hub_feature(hdev, C_HUB_LOCAL_POWER); //如果是本地电源供电 if (hubstatus & HUB_STATUS_LOCAL_POWER) /* FIXME: Is this always true? */ hub->limited_power = 1; //如果本电源不供电 else hub->limited_power = 0; } //如果hub 发生过电源保护,需要对hub power on if (hubchange & HUB_CHANGE_OVERCURRENT) { dev_dbg (hub_dev, "overcurrent change "); msleep(500); /* Cool down */ clear_hub_feature(hdev, C_HUB_OVER_CURRENT); hub_power_on(hub); } } hub->activating = 0; /* If this is a root hub, tell the HCD it's okay to * re-enable port-change interrupts now. */ if (!hdev->parent && !hub->busy_bits[0]) usb_enable_root_hub_irq(hdev->bus); loop_autopm: /* Allow autosuspend if we're not going to run again */ if (list_empty(&hub->event_list)) usb_autopm_enable(intf);loop: usb_unlock_device(hdev); kref_put(&hub->kref, hub_release); } /* end while (1) */}
hub_port_connect_change()函数分析:
static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange){ ... ... //hub led if (hub->has_indicators) { set_port_led(hub, port1, HUB_LED_AUTO); hub->indicator[port1-1] = INDICATOR_AUTO; } //忽略掉CONFIG_USB_OTG的处理#ifdef CONFIG_USB_OTG /* during HNP, don't repeat the debounce */ if (hdev->bus->is_b_host) portchange &= ~(USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE);#endif //尝试唤醒一个存在的设备 udev = hdev->children[port1-1]; if ((portstatus & USB_PORT_STAT_CONNECTION) && udev && udev->state != USB_STATE_NOTATTACHED) { usb_lock_device(udev); if (portstatus & USB_PORT_STAT_ENABLE) { status = 0; /* Nothing to do */#ifdef CONFIG_USB_SUSPEND } else if (udev->state == USB_STATE_SUSPENDED && udev->persist_enabled) { /* For a suspended device, treat this as a * remote wakeup event. */ status = usb_remote_wakeup(udev);#endif } else { status = -ENODEV; /* Don't resuscitate */ } usb_unlock_device(udev); if (status == 0) { clear_bit(port1, hub->change_bits); return; } } //如果对应端口已经有设备连接,先将其断开 if (udev) usb_disconnect(&hdev->children[port1-1]); //接下来,将hub->change_bits的对应位清掉,该位是在函数hub_port_logical_disconnect()中被置的,在这里将其清除,免得下次在进入hub_events()的时候,再次检测到这个位发生改变. clear_bit(port1, hub->change_bits); //如果发生物理断开或者连接状态改变,我们可能忘记移除设备 if (!(portstatus & USB_PORT_STAT_CONNECTION) || (portchange & USB_PORT_STAT_C_CONNECTION)) clear_bit(port1, hub->removed_bits); //连接发生改变 //连接反弹的处理,实际上就是除抖动 if (portchange & (USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE)) { //如果该端口的连接发生改变(从有连接到无接接,或者从无连接到有连接),就有一个除抖动的过程,usb2.0 spec上规定,除抖动的时间为100ms. //在函数里,定义的测试时间是1500ms.如果在这个时间内,端口还末处于稳定状态,就会返回-ETIMEDOUT //如果已经处于稳定状态了,就会返回稳定状态下的portstatus status = hub_port_debounce(hub, port1); if (status < 0) { if (printk_ratelimit()) dev_err(hub_dev, "connect-debounce failed, " "port %d disabled ", port1); portstatus &= ~USB_PORT_STAT_CONNECTION; } else { portstatus = status; } } //如果接口上没有连接了,可以直接退出了 if (!(portstatus & USB_PORT_STAT_CONNECTION) || test_bit(port1, hub->removed_bits)) { /*经过去抖后,端口稳定的处于断开连接状态.说明端口已经没有设备了.然后,再判断hub是否有电源开关((wHubCharacteristics & HUB_CHAR_LPSM) < 2),portstatus 的 USB_PORT_FEAT_POWER位是否被设置,如果没有被设置,则说明该端口断电了. 如果hub有电源开关,且端口没有上电,则需要发送POWER的Set_Feature来为之上电*/ if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2 && !(portstatus & USB_PORT_STAT_POWER)) set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); //如果端口依然处理enable状态,就会跳转到标号done处,就端口disalbe. if (portstatus & USB_PORT_STAT_ENABLE) goto done; return; } /*如果端口隐定处于连接状态,那就需要连接端口下的设备了.首先看到的是一个for循环,是用来配置设备的两种方式.我们知道,在配置设备的时候,首先要去取设备的描述符,这个过程是在ep0上完成的.而这个ep0支持的最大传输出数据又是在设备描述符的bMaxPacketSize0中所 定义的.因此就对应有两种处理方式: 第一种是传输8个字节,取得描述符的前面一部份,从而就可以取得bMaxPacketSize0.此后再reset设备,再根据这个bMaxPacketSize0的长度去取它的设备描述符. 第二种是一次传输64字节,取得设备描述符的bMaxPacketSize0字段*/ for (i = 0; i < SET_CONFIG_TRIES; i++) { //为探测到的usb设备(包括普通hub,u盘等)分配并初始化udev // 在为root hub分配struct usb_dev的时候,它的第一个参数,也就是它的父结点是为NULL. /*我们来观察一下它在sysfs中的命名方式 在没有插入U盘之前:/sys/bus/usb/devices 1-0:1.0 usb1 插入U盘之后: 1-0:1.0 1-1 1-1:1.0 usb1 增加的两个目是: 1-1和1-1:1.0 1-1:1.0 :只有这样的目录,表示该U盘只有一个接口,当前选取的是第0号设置项.*/ udev = usb_alloc_dev(hdev, hdev->bus, port1); if (!udev) { dev_err (hub_dev, "couldn't allocate port %d usb_device ", port1); goto done; } //置为USB_STATE_POWERED状态 usb_set_device_state(udev, USB_STATE_POWERED); udev->bus_mA = hub->mA_per_port; udev->level = hdev->level + 1; udev->wusb = hub_is_wusb(hub); /* * USB 3.0 devices are reset automatically before the connect * port status change appears, and the root hub port status * shows the correct speed. We also get port change * notifications for USB 3.0 devices from the USB 3.0 portion of * an external USB 3.0 hub, but this isn't handled correctly yet * FIXME. */ if (!(hcd->driver->flags & HCD_USB3)) udev->speed = USB_SPEED_UNKNOWN; else if ((hdev->parent == NULL) && (portstatus & USB_PORT_STAT_SUPER_SPEED)) udev->speed = USB_SPEED_SUPER; else udev->speed = USB_SPEED_UNKNOWN; /*为设备指定一个地址,是到所属的usb bus的bus->devmap中找到没有使用的那一位,先进行两次新的策略(i=0和=1时),如果不行就再进行两次旧的策略(i=2和i=3时).所有这一切只有一个目的,就是为了获得设备的描述符, 设置了udev->tt、udev->ttport和udev->ep 0.desc.wMaxPacketSize,设置udev->status= USB_STATE_ADDRESS。*/ choose_address(udev); if (udev->devnum <= 0) { status = -ENOTCONN; /* Don't retry */ goto loop; } //hub_port_init()对这个usb_dev结构进行一系的初始化,在这个函数中会处理:Get_Description,Set_address.等操作 status = hub_port_init(hub, udev, port1, i); if (status < 0) goto loop; usb_detect_quirks(udev); if (udev->quirks & USB_QUIRK_DELAY_INIT) msleep(1000); /* consecutive bus-powered hubs aren't reliable; they can * violate the voltage drop budget. if the new child has * a "powered" LED, users should notice we didn't enable it * (without reading syslog), even without per-port LEDs * on the parent. */ if (udev->descriptor.bDeviceClass == USB_CLASS_HUB && udev->bus_mA <= 100) { u16 devstat; status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstat); if (status < 2) { dev_dbg(&udev->dev, "get status %d ? ", status); goto loop_disable; } le16_to_cpus(&devstat); if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) { dev_err(&udev->dev, "can't connect bus-powered hub " "to this port "); if (hub->has_indicators) { hub->indicator[port1-1] = INDICATOR_AMBER_BLINK; schedule_delayed_work (&hub->leds, 0); } status = -ENOTCONN; /* Don't retry */ goto loop_disable; } } /* check for devices running slower than they could */ if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200 && udev->speed == USB_SPEED_FULL && highspeed_hubs != 0) check_highspeed (hub, udev, port1); /* Store the parent's children[] pointer. At this point * udev becomes globally accessible, although presumably * no one will look at it until hdev is unlocked. */ status = 0; // 将分配的struct usb_dev结构跟他的父结构关联起来,也就是说添加到它的父结构的usb_dev-> children[]数组. spin_lock_irq(&device_state_lock); if (hdev->state == USB_STATE_NOTATTACHED) status = -ENOTCONN; else hdev->children[port1-1] = udev; spin_unlock_irq(&device_state_lock); if (!status) { /*usb_configure_device(): 得到设备的描述符(包括设备描述符、配置描述符、接口描述符等),分析以上描述符信息,提取出配置、接口等,并赋值给udev结构里相应的字段。 device_add() :将usb设备注册到系统里,这个动作将触发驱动的匹配,由于这是个usb设备,所以万能usb驱动usb_generic_driver会匹配上,从而generic_probe会得到执行,从上面可以看出来,这一次hub_events()调用是由于主控制器初始化调用了 hub_probe,从而引发hub_events调用。那root hub初始化完成以后hub_events会如何触发呢?答案是通过中断!而这个中断的服务函数就是hub_irq,也即是说,凡是真正的有端口变化事件发生,hub_irq就会被调用,而hub_irq()最终会调用kick_khubd(), 触发hub的event_list,于是再次调用hub_events().*/ status = usb_new_device(udev); if (status) { spin_lock_irq(&device_state_lock); hdev->children[port1-1] = NULL; spin_unlock_irq(&device_state_lock); } } if (status) goto loop_disable; status = hub_power_remaining(hub); if (status) dev_dbg(hub_dev, "%dmA power budget left ", status); return;loop_disable: hub_port_disable(hub, port1, 1);loop: usb_ep0_reinit(udev); release_address(udev); hub_free_dev(udev); usb_put_dev(udev); if ((status == -ENOTCONN) || (status == -ENOTSUPP)) break; } if (hub->hdev->parent || !hcd->driver->port_handed_over || !(hcd->driver->port_handed_over)(hcd, port1)) dev_err(hub_dev, "unable to enumerate USB device on port %d ", port1);// Done标号是对应上述处理失败的处理,它禁用掉该端口(因为该端口没有连接设备或者是端口上的设备配置失败),如果是root hub,且USB控制器器驱动中又定义了relinquish_port.调用它.done: hub_port_disable(hub, port1, 1); if (hcd->driver->relinquish_port && !hub->hdev->parent) hcd->driver->relinquish_port(hcd, port1);}
参考了很多大神的分析,非常感谢!
评论
查看更多