1. 枚举是什么?
一旦获悉有新设备连接上来,主机就会发送一系列的请求(Resqusts)给设备所挂载到的hub,再由hub建立起一条连接主机(Host)和设备(Device)之间的通信通道。然后主机以控制传输(Control Transfer)的方式,通过端点0(Endpoint 0)对设备发送各种请求,设备收到主机发来的请求后回复相应的信息,进行枚举(Enumerate)操作。所有的USB设备必须支持标准请求(StandardRequests),控制传输方式(Control Transfer)和端点0(Endpoint 0)。
在讲解枚举之前,先大概说说USB的一种传输模式——控制传输。这种传输在USB中是非常重要的,它要保证数据的正确性,在设备的枚举过程中都是使用控制传输的。控制传输分为三个阶段:①建立阶段。②数据阶段。③确认阶段。
建立(setup)阶段:都是由USB主机发起,它是一个setup数据包,里面包含一些数据请求的命令以及一些数据。如果建立阶段是输入请求,那么数据阶段就要输入数据;如果建立阶段是输出请求,那么数据阶段就要输出数据。如果在数据阶段,即便不需要传送数据,也要发一个0长度的数据包。数据阶段过后就是确认阶段。确认阶段刚好跟数据阶段相反,如果是输入请求,则它是一个输出数据包;如果是输出请求,则它是一个输入数据包。确认阶段用来确认数据的正确传输。
1.1 通信传输流
1.2 设备状态图
1.3 状态详解
1) 连接(Attached)
设备可以连接到USB或者从USB上拔出.USB设备从总线上拨出后的状态在规范没定义,只说明一旦USB连到总线要求的操作以及属性.
2) 上电(Powered)
USB设备的电源可来自外部电源,也可从USB接口的集线器而来。电源来自外部电源的USB设备被称作自给电源式的(self-powered)。尽管自给电源式的USB设备可能在连接上USB接口以前可能已经带电,但它们直到连线上USB接口后才能被看作是加电状态(Powered state)。而这时候VBUS已经对设备产生作用了.
一个设备可能有既支持自给电源的,同时也支持总线电源式的配置。有一些支持其中的一种,而另一些设备配置可能只有在自给电源下才能被使用。设备对电源支持的能力是通过配置描述表(configuration descriptor)来反映的。当前的电源供给形式被作为设备状态的一部分被反映出来。设备可在任何时候改变它们的供电来源,比如说:从自给式向总线式改变,如果一个配置同时支持两种模式,那此状态的最大电源需求就是指设备在两种模式下从VBUS上获取电能的最大值。设备必须以此最大电源作为参照,而究竟处于何状态是不考虑的。如果有一配置仅支持一种电源模式,那么电源模式的改变会使得设备失去当前配置与地址,返回加电状态。如果一个设备是自给电源式,并且当前配置需要大于100mA电流,那么如果此设备转到了总线电源式,它必须返回地址状态(Address state)。自给电源式集线器使用VBUS来为集线控制器(Hub controller)提供电源,因而可以仍然保持配置状态(Configured state),尽管自给电源停止提供电源。
3)默认状态(Default)
设备上电后,它不响应任何总线处理,直到总线接收到复位信号为止.接收到复位信号后,用默认的地址可以对设备寻址.
当用复位过程完成后,USB设备在正确的速度下操作(即低速/全速/高速).低速和全速的数据选择由设备的终端电阻决定.能进行高速操作的设备决定它是否在复位的过程的一部分执行高速操作.
能进行高速操作的设备在全速的电气环境中操作时,必须能以全速成功复位.设备成功复位后,设备必须成功响应设备和配置描述符请求,并且返回适当的信息.当在全速下工作时,设备可能或者不能支持预定义的功能.
4) 地址(Address)
所有的USB设备在加电复位以后都使用缺省地址。每一设备在连接或复位后由主机分配一个唯一的地址。当USB设备处于挂起状态时,它保持这个地址不变。
USB设备只对缺省通道(Pipe)请求发生响应,而不管设备是否已经被分配地址或在使用缺省地址。
5) 配置状态( Configured )
在USB设备正常工作以前,设备必须被正确配置。从设备的角度来看,配置包括一个将非零值写入设备配置寄存器的操作。配置一个设备或改变一个可变的设备设置会使得与这个相关接口的终端结点的所有的状态与配置值被设成缺省值。这包括将正在使用(date toggle)的结点(end point)的 (Date toggle)被设置成DATA0。
6) 挂起状态
为节省电源,USB设备在探测不到总线传输时自动进入中止状态。当中止时,USB设备保持本身的内部状态,包括它的地址及配置。
所有的设备在一段特定的时间内探测不到总线活动时必须进入中止态。不管设备是被分配了非缺省的地址或者是被配置了,已经连接的设备必须在任何加电的时刻随时准备中止。总线活动的中止可能是因为主机本身进入了中止状态。另外,USB设备必须在所连接的集线器端口失效时进入中止态。这就是所指的选择性中止(Selective suspend)。
USB设备在总线活动来到时结束中止态。USB设备也可以远程唤醒的电流信号来请求主机退出中止态或选择性中止态。具体设备具有的远程唤醒的能力是可选的,也就是说,如果一个设备有远程唤醒的能力,此设备必须能让主机控制此能力的有效与否。当设备复位时,远程唤醒能力必须被禁止。
2. 枚举步骤
USB协议定义了设备的6种状态,仅在枚举过程中,设备就经历了4个状态的迁移:上电状态(Powered),默认状态(Default),地址状态(Address)和配置状态(Configured)(其他两种是连接状态和挂起状态(Suspend))。
2.1 用户把USB设备插入USB端口或给系统启动时设备上电
这里指的USB端口指的是主机下的根hub或主机下行端口上的hub端口。Hub给端口供电,连接着的设备处于上电状态。此时,USB设备处于加电状态,它所连接的端口是无效的。
2.2 Hub监测它各个端口数据线上(D+/D-)的电压
在hub端,数据线D+和D-都有一个阻值在14.25k到24.8k的下拉电阻Rpd,而在设备端,D+(全速,高速)和D-(低速)上有一个1.5k的上拉电阻Rpu。当设备插入到hub端口时,有上拉电阻的一根数据线被拉高到幅值的90%的电压(大致是3V)。hub检测到它的一根数据线是高电平,就认为是有设备插入,并能根据是D+还是D-被拉高来判断到底是什么设备(全速/低速)插入端口(全速、高速设备的区分在我将来的文章中描述)。如下图。
USB全速/高速设备上电连接
检测到设备后,hub继续给设备供电,但并不急于与设备进行USB传输。
USB接口定义如下图所示:
2.3 Host了解连接的设备
每个hub利用它自己的中断端点向主机报告它的各个端口的状态(对于这个过程,设备是看不到的,也不必关心),报告的内容只是hub端口的设备连接/断开的事件。如果有连接/断开事件发生,那么host会发送一个 Get_Port_Status请求(request)给hub以了解此次状态改变的确切含义。Get_Port_Status等请求属于所有hub都要求支持的hub类标准请求(standard hub-class requests)。
2.4 Hub检测所插入的设备是高速还是低速设备
hub通过检测USB总线空闲(Idle)时差分线的高低电压来判断所连接设备的速度类型,当host发来Get_Port_Status请求时,hub就可以将此设备的速度类型信息回复给host。USB 2.0规范要求速度检测要先于复位(Reset)操作。
2.5 hub复位设备
主机一旦得知新设备已连上以后,它至少等待100ms以使得插入操作的完成以及设备电源稳定工作。然后主机控制器就向hub发出一个 Set_Port_Feature请求让hub复位其管理的端口(刚才设备插上的端口)。hub通过驱动数据线到复位状态(D+和D-全为低电平 ),并持续至少10ms。当然,hub不会把这样的复位信号发送给其他已有设备连接的端口,所以其他连在该hub上的设备自然看不到复位信号,不受影响。
2.6 Host检测所连接的全速设备是否是支持高速模式
因为根据USB 2.0协议,高速(High Speed)设备在初始时是默认全速(Full Speed )状态运行,所以对于一个支持USB 2.0的高速hub,当它发现它的端口连接的是一个全速设备时,会进行高速检测,看看目前这个设备是否还支持高速传输,如果是,那就切到高速信号模式,否则就一直在全速状态下工作。
同样的,从设备的角度来看,如果是一个高速设备,在刚连接bub或上电时只能用全速信号模式运行(根据USB 2.0协议,高速设备必须向下兼容USB 1.1的全速模式)。随后hub会进行高速检测,之后这个设备才会切换到高速模式下工作。假如所连接的hub不支持USB 2.0,即不是高速hub,不能进行高速检测,设备将一直以全速工作。
2.7 Hub建立设备和主机之间的信息通道
主机不停地向hub发送Get_Port_Status请求,以查询设备是否复位成功。Hub返回的报告信息中有专门的一位用来标志设备的复位状态。
当hub撤销了复位信号,设备就处于默认/空闲状态(Default state),准备接收主机发来的请求。设备和主机之间的通信通过控制传输,默认地址0,端点号0进行。此时,设备能从总线上得到的最大电流是100mA。(所有的USB设备在总线复位后其地址都为0,这样主机就可以跟那些刚刚插入的设备通过地址0通信。)
2.8 主机发送Get_Descriptor请求获取默认管道的最大包长度
默认管道(Default Pipe)在设备一端来看就是端点0。主机此时发送的请求是默认地址0,端点0,虽然所有未分配地址的设备都是通过地址0来获取主机发来的请求,但由于枚举过程不是多个设备并行处理,而是一次枚举一个设备的方式进行,所以不会发生多个设备同时响应主机发来的请求。
设备描述符的第8字节代表设备端点0的最大包大小。虽然说设备所返回的设备描述符(Device Descriptor)长度只有18字节,但系统也不在乎,此时,描述符的长度信息对它来说是最重要的,其他的瞄一眼就过了。当完成第一次的控制传输后,也就是完成控制传输的状态阶段,系统会要求hub对设备进行再一次的复位操作(USB规范里面可没这要求)。再次复位的目的是使设备进入一个确定的状态。
2.9 主机给设备分配一个地址
主机控制器通过Set_Address请求向设备分配一个唯一的地址。在完成这次传输之后,设备进入地址状态(Address state),之后就启用新地址继续与主机通信。这个地址对于设备来说是终生制的,设备在,地址在;设备消失(被拔出,复位,系统重启),地址被收回。同一个设备当再次被枚举后得到的地址不一定是上次那个了。
2.10 主机获取设备的信息
主机发送 Get_Descriptor请求到新地址读取设备描述符,这次主机发送Get_Descriptor请求可算是诚心,它会认真解析设备描述符的内容。设备描述符内信息包括端点0的最大包长度,设备所支持的配置(Configuration)个数,设备类型,VID(Vendor ID,由USB-IF分配), PID(Product ID,由厂商自己定制)等信息。Get_Descriptor请求(Device type)和设备描述符(已抹去VID,PID等信息)见下图:
标准Get_Descriptor请求
设备描述符(Device Descriptor)
之后主机发送Get_Descriptor请求,读取配置描述符(Configuration Descriptor),字符串等,逐一了解设备更详细的信息。事实上,对于配置描述符的标准请求中,有时wLength一项会大于实际配置描述符的长度(9字节),比如255。这样的效果便是:主机发送了一个Get_Descriptor_Configuration 的请求,设备会把接口描述符,端点描述符等后续描述符一并回给主机,主机则根据描述符头部的标志判断送上来的具体是何种描述符。
接下来,主机就会获取配置描述符。配置描述符总共为9字节。主机在获取到配置描述符后,根据里面的配置集合总长度,再获取配置集合。配置集合包括配置描述符,接口描述符,端点描符等等。
如果有字符串描述符的话,还要获取字符串描述符。另外HID设备还有HID描述符等。
2.11 主机给设备挂载驱动(复合设备除外)
主机通过解析描述符后对设备有了足够的了解,会选择一个最合适的驱动给设备。 然后tell the world(announce_device)说明设备已经找到了,最后调用设备模型提供的接口device_add将设备添加到 usb 总线的设备列表里,然后 usb总线会遍历驱动列表里的每个驱动,调用自己的 match(usb_device_match) 函数看它们和你的设备或接口是否匹配,匹配的话调用device_bind_driver函数,现在就将控制权交到设备驱动了。
对于复合设备,通常应该是不同的接口(Interface)配置给不同的驱动,因此,需要等到当设备被配置并把接口使能后才可以把驱动挂载上去。
设备-配置-接口-端点关系见下图:
USB 设备-配置-接口-端点关系
实际情况没有上述关系复杂。一般来说,一个设备就一个配置,一个接口,如果设备是多功能符合设备,则有多个接口。端点一般都有好几个,比如Mass Storage设备一般就有两个端点(控制端点0除外)。
2.12 设备驱动选择一个配置
驱动(注意,这里是驱动,之后的事情都是有驱动来接管负责与设备的通信)根据前面设备回复的信息,发送Set_Configuration请求来正式确定选择设备的哪个配置(Configuration)作为工作配置(对于大多数设备来说,一般只有一个配置被定义)。至此,设备处于配置状态(Configured),当然,设备也应该使能它的各个接口(Interface)。
对于复合设备,主机会在这个时候根据设备接口信息,给它们挂载驱动。
3. 控制传输
1) bmRequestType(向谁请求)
0=主机至设备;1=设备至主机
D6..5:命令类型
D4..0:接受者类型
2=端点;3=其它
4..31 保留
USB说明定义了一系列所有设备必须支持的标准请求。这些请求见下面的表<Standard Device Requests>。另外,一个设备类可定义更多的请求。设备厂商也可定义设备支持的请求.
请求可被导引到设备,设备接口,或某一个设备端结点(endpoint)上。这个请求域也指定了接收者。当指定的是接口或端结点(endpoint)时,wIndex域指出那个接口或端节点。
此域用来传送当前请求的参数,随请求不同而变。
4) wIndex域
当bmRequestType的Recipient字段为接口或端点时,wIndex域用来表明是哪一个接口或端结。
5) wLength域
这个域表明第二阶段的数据传输长度。传输方向由bmRequstType域的Direction位指出。wLength域为0则表明无数据传输。在输入请求下,设备返回的数据长度不应多于wLength,但可以少于。在输出请求下,wLength指出主机发出的确切数据量。如果主机发送多于wLength的数据,设备做出的响应是无定义的。
3.1 标准设备请求
3.2 标准请求码(bRequest的值)
3.3 描述符类型
3.4 GET_DESCRIPTOR(读取描述符)
wIndex域:
所有的设备必须提供一个设备描述符并且至少一个配置描述符,如果一个设备不支持一个请求的描述符,则返回请求错误。
•缺省状态:此请求合法。
•地址状态:此请求合法。
•配置状态:此请求合法。
3.5 GET_INTERFACE(取得接口)
一些USB设备有接口设置互斥的配置。这个请求允许主机确定当前选定的备用设置。如果wValue或者wLength的值与上面指定的不一致,那么设备的行为没有定义;如果指定的接口不存在,那么设备将用请求错误响应。
•默认状态:当设备处于默认状态时接收到这个请求,设备的行为没 有定义
•地址状态:设备给出请求错误
•配置状态:当设备处于配置状态时,这是一个有效的请求
1. USB协议
1.1 USB主机系统
在USB主机系统中,通过根集线器与外部USB从机设备相连的处理芯片,称为USB主机控制器。USB主机控制器包含硬件、软件和固件一部分。
1.2 USB设备系统
USB设备按功能分为两部分:集线器(Hub)和功能部件。从下图可知,主机通过根集线器连接到各种外围设备(集线器和功能部件)。
1.3 主机和设备之间通信模型
主机与设备之间的通信模型
上图展示了USB主机和USB设备之间的数据传输过程。在设备端,USB设备将非USB格式的数据进行打包处理,转换成USB格式的数据包,然后传递到链路层,经过硬件处理、传递到物理层,由物理层通过PHY以数据流的形式传输到主机。
USB主机在USB设备和USB主机之间发起的传输过程,稳为事务。每次事务以2到3个数据包的形式进行USB总线传输。每个数据包包含2到3个步骤:
1) USB主机控制器向USB设备发出命令
2) USB控制器和USB设备之间传递读写请求,其方向取决于第一部分的命令是读还是写
3) 握手信号。
USB主机控制器向USB设备发送事务类型请求,通过分组标识符来进行识别。
1.4 USB分组标识
主机和设备之间进行操作,通过分组标识(PID)来进行传输。数据包传输格式一般由:PID、数据/控制信息、CRC校验码组成。
常见的PID主要包括令牌、数据、握手等类型组成。PID码以特定的方式组成,如下表所示:
PID分组码是数据传输流程中的重要元素。无论硬件还是软件,都要对PID分组码进行分析,从而做出正确响应。USB主机和设备严格按照PID分组码信息进行信息交互。
1.5 数据包传输模式
当USB设备连接到集线器,集线器状态将发生相应的变化,并将状态变化信息传递给USB主机。USB主机通过根集线器向USB设备发送命令,获取USB设备的各种信息,包含USB设备传输类型、ID号、Product、USB速度等信息。
USB主机和USB设备之间的数据传输共有四种类型:控制传输、批量传输、中断传输和同频传输。与之对应,USB主机和USB设备之间有四种事务:控制事务、批量事务、中断事务和同步事务。
1.5.1 批量(Bulk)传输
作用:主要用于非实时性传输,数据包较大而延时要求较低。
特点:数据传输准备即可,采用批量传输模式的USB从机设备,如U盘
数据传输分三个阶段:
a) 令牌阶段:主机发送请求,USB设备依据请求PID来判断IN或OUT传输
b) 数据传输阶段:依据令牌阶段的IN或OUT传输,来决定数据传输为DATA0或DATA1来进行数据传输
c) 握手阶段:接收信息的一方发送ACK信号以表示接收成功;若为NAK,表示发送失败;STALL表示不可预知的错误
1.5.2 控制(Control)传输
作用:USB传输过程必须支持的传输模式。USB主机为了获取设备描述符、ID、Product等信息,向USB设备发送相应的PID命令。
特点:唯一可以进行IN/OUT传输的传输模式。
数据宽度:控制传输方式可以以8、16、32或64字节的数据进行传输,这取决于设备的传输速度。
USB主机和设备之间必须支持控制传输,通过端点0进行数据传输。控制传输分为令牌、数据传输和握手阶段。
1.5.3 中断传输事务
作用:按照一定时刻轮询设备是否有中断传输请求
特点:查询频率取决于端点的模式结构,从1到255ms不等
中断传输主要用于实时性要求非常高的从机设备,如键盘操纵杆和Mouse等
传输过程也分为令牌阶段、数据传输和握手阶段
1.6 USB描述符
USB协议中共定义了以下四种描述符:
1) 设备描述符
2) 配置描述符
3) 接口描述符
4) 端点描述符
其关系如下图所示:
1.6.1 设备描述符
每个USB设备都有一个唯一的设备描述符,如下表所示:
1.6.2 配置描述符
每个USB设备都有默认的配置描述符,支持至少一个接口,每个配置描述符如下表:
1.6.3 接口描述符
设备应至少支持一个接口,如:块传输数据接口,部分设备可能支持其它的接口。复合设备可以支持额外接口,以支持音频和视频功能。标准中并没有定义此类接口。接口可能有多个可选设置,主机将会检查每个可选的设置。
1.6.4 端点描述符
每个设备至少支持控制端点0。USB设备应该支持三类端点:控制端点、输入端点和输出端点。
2. OTG协议
OTG设备采用Mini-AB插座,相对于传统的USB数据线,Mini-AB接口多了一根数据线ID,ID线是否接入将Mini-AB接口分为Mini-A和Mini-B接口两种类型。在OTG设备之间数据连接的过程中,通过OTG数据线Mini-A和Mini-B接口来确定OTG设备的主从:接入Mini-A接口的设备默认为A设备(主机设备);接入Mini-B接口的设备,默认为B设备(从设备)。
A设备和B设备无需交换电缆接口,即可通过主机交换协议(HNP)实现A、B设备之间的角色互换。同时,为了节省电源,OTG允许总线空闲时A设备判断电源。此时,若B设备希望使用总线,可以通过会话请求协议(SRP)请求A设备提供电源。
2.1 HNP(主机交换)协议
当Mini-A接口接入A设备并确定A设备为主机时;若B设备希望成为主机,则A设备向B设备发送SetFeature命令,允许B设备进行主机交换。B设备检测到总线挂起5ms后,即挂起D+并启动HNP,使总线处于SE0状态。此时A设备检测到总线处于SE0状态,即认为B设备发起主机交换,A设备进行响应。待B设备发现D+线为高电平而D-线为低电平(J状态),表示A设备识别了B设备的HNP请求。B设备开始总线复位并具有总线控制权,主机交换协议完成。
2.2 SRP(会话请求)协议
对于主机,要求能响应会话请求;对于设备,仅要求能够发起SRP协议。OTG设备,不仅要求发起SRP,而且还能响应SRP请求。
SRP分为数据线脉冲调制和电压脉冲调两种方式,B设备发起SRP必须满足以下两个条件:
1) B设备检测到A设备低于其有效的电压阈值,同时B设备低于有效的电压阈值。
2) B设备必须检测到D+和D-数据线至少在2ms的时间内低于有效阈值,即处于SE0状态。
数据线脉冲调制会话请求:B设备必须等到满足以上两个条件后,将数据线接入上拉电阻一定的时间,以备A设备过滤数据线上的瞬间电压。与此同时,B设备上拉D+以便于在全速模式下进行初始化操作。A设备在检测到D+变为高电平或D-变为低电平时产生SRP指示信号。
Vbus脉冲调制会话请求:B设备同样需等待满足上述两个初始化条件,然后B设备通过对电容充电以提高总线电压,待达到总线上的电压阈值,唤醒A设备。在充电过程中,一定要保证充电的电压峰值在一定的范围以避免烧坏A设备。
3. USB驱动架构
USB驱动架构如下图所示:
3.1 USB主机端驱动
USB核心(USBD)是整个USB驱动的核心部分,从上图可知,一方面USBD对接收到USB主机控制器的数据进行处理,并传递给上层的设备端驱动软件;同时也接收来自上层的非USB格式数据流,进行相应的数据处理后传递给USB主机控制器驱动。
USB数据传输都以URB(USB Request Block)请求、URB生成、URB递交、URB释放为主线。从上图可知,当加载控制器驱动之后,注册根据集线器,hub和hcd驱动成为一个整体。接着,主机通过控制传输获取设备的控制描述符等信息,接着详述整个控制传输的流程。usb_submit_urb依据是否连接到根集线器来决定调用urb_enqueue或rh_urb_enqueue函数。
USB从设备通过集线器或根集线器连接到USB主机上。比如:主机通过根集线器与外界进行数据交互,根集线器通过探测数据线状态的变化来通知USB主机是否有USB外围设备接入。
在主机端控制器驱动加载的过程中,注册了根集线器,然后匹配了相应的hub驱动程序,同时完成了对Hub的轮询函数和状态处理函数的设置。这样,一旦hub集线器的状态发生变化,就会产生相应的中断,主机端控制器就会执行相应的中断处理函数,下图为hub驱动程序的流程图。
USB Core中的usb_init()函数中完成了对hub线程(khubd,在usb_hub_init函数中真正地创建)的创建,然后完成相应设备的探测。主机端控制器驱动进行探测时,将hub驱动和主机端控制器驱动结合在一起,相互之间完成调用。 相对于大容量存储设备与主机之间通过控制/批量传输,集线器与主机之间通过中断/控制方式完成数据交互。
3.2 USB设备端驱动
从上图可知,设备端驱动包含两部分:
1) 底层设备控制器驱动
2) 上层大容量存储类驱动
3.2.1 设备控制器驱动
USB设备控制器驱动主要实现Gadget API定义的函数和中断服务函数,可按功能划分为:API函数实现模块和中断处理模块。
API函数主要实现Gadget API定义的函数功能,如结构体usb_ep_ops和usb_gadget_ops中的函数、usb_gadget_register_driver函数。这些函数是供Gadget Driver调用。
中断处理模块主要处理设备控制器产生的各种中断,包括端点中断、复位、挂起等中断。
上图为设备端控制器基本架构,主要完成了Gadget驱动和控制器驱动绑定、usb_gadget_register_driver注册。
3.3 OTG驱动
OS_FS: 文件系统
USBD: USB核心
HCD: 主机控制器驱动
UDC: 设备端控制器驱动
OTG设备支持HNP和SRP协议。OTG设备通过USB OTG电缆连接到一起,其中接Mini-A接口的设备为A设备,默认为主机端,Mini-B接口的设备默认为B设备。当A、B设备完成数据交互之后,A、B设备之间的USB OTG电缆进入挂起状态,如下图所示:
当B设备写入b_bus_req,向A设备发起HNP请求。待A设备响应之后,A设备发送a_set_b_hnp_en,B设备响应之后即进入主机状态,同时发送请求使用A设备set_device,这样A、B设备完成主从交换。
4. USB 传输流程
4.1 USB初始化过程
USB驱动作为一个系统,集成了众多的驱动模块,注册过程非常复杂。从USB系统的角度来说,USB主机驱动主要包含:
1) USB核驱动
2) 主机控制器驱动
3) 集线器驱动
驱动的加载执行流程如下图所示:
USB初始化过程
4.1.1 USB Core的初始化
USB驱动从USB子系统的初始化开始,USB子系统的初始化在文件driver/usb/core/usb.c
- subsys_initcall(usb_init);
- module_exit(usb_exit);
subsys_initcall()是一个宏,可以理解为module_init()。由于此部分代码非常重要,开发者把它看作一个子系统,而不仅仅是一个模块。USB Core这个模块代表的不是某一个设备,而是所有USB设备赖以生存的模块。在Linux中,像这样一个类别的设备驱动被归结为一个子系统。subsys_initcall(usb_init)告诉我们,usb_init才是真正的初始化函数,而usb_exit将是整个USB子系统结束时的清理函数。
4.1.2 主机控制器的初始化及驱动执行(以EHCI为例)
module_init(otg_init); 模块注册
static init __init otg_init(void);
platform_driver_register(); 平台注册
static int __init otg_probe(struct platform_device *pdev); 探测处理函数
reg = platform_get_resource(pdev, IORESOURCE_MEM, 0); 获取寄存器信息
data = platform_get_resource(pdev,IORESOURCE_MEM, 1); 获取内存信息
irq = platform_get_irq(pdev,0); 获取中断号
usb_create_hcd(&otg_hc_driver, &pdev->dev, pdev->dev.bus_id);
分配和初始化HCD结构体。对设备数据空间进行分配,初始化计数器、总线、定时器、hcd结构体各成员值。
ret = usb_add_hcd(hcd,irq,SA_INTERRUPT);
完成HCD结构体的初始化和注册。申请buffer,注册总线、分配设备端内存空间,向中断向量表中申请中断,注册根集线器,对根集线器状态进行轮询。
4.1.3 注册集线器
register_root_hub(hcd);
在USB系统驱动加载的过程中,创建了集线器的线程(khubd),并且一直查询相应的线程事务。HCD驱动中,将集线器作为一个设备添加到主机控制器驱动中,然后进行集线器端口的初始化。在USB主机看来,根集线器本身也是USB主机的设备。USB主机驱动加载完成之后,即开始注册根集线器,并且作为一个设备加载到主机驱动之中。
USB主机和USB设备之间进行数据交互,USB设备本身并没有总线控制权,U盘被动地接收USB主机发送过来的信息并做出响应。USB主机控制器与根集线器构成了主机系统,然后外接其它的USB设备。
为了更好地探测到根集线器的状态变化,USB主机控制器驱动增加了状态轮询函数,以一定的时间间隔轮询根集线器状态是否发生变化。一旦根集线器状态发生变化,主机控制器就会产生相应的响应。
USB主机和USB设备之间的数据传输以URB(USB Request Block)的形式进行。
4.2 URB传输过程
USB初始化过程中,无论是主机控制器驱动还是根集线器驱动,都是通过URB传输获取设备信息。
4.2.1 申请URB
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
为urb分配内存并执行初始化。
4.2.2 初始化URB
初始化具体的urb包
- static inline void usb_fill_bulk_urb(struct urb *urb,
- struct usb_device *dev,
- unsigned int pipe,
- void *transfer_buffer,
- int buffer_length,
- usb_complete_t complete_fn,
- void *context)
- static inline void usb_fill_control_urb(struct urb *urb,
- struct usb_device *dev,
- unsigned int pipe,
- unsigned char *setup_packet,
- void *transfer_buffer,
- int buffer_length,
- usb_complete_t complete_fn,
- void *context)
- static inline void usb_fill_int_urb(struct urb *urb,
- struct usb_device *dev,
- unsigned int pipe,
- void *transfer_buffer,
- int buffer_length,
- usb_complete_t complete_fn,
- void *context,
- int interval)
不同的传输模式下,驱动为之申请不同的URB。其中,Linux内核只支持同步传输外的三种传输事件,ISO事务需要手工进行初始化工作。控制传输事务、批量传输事务、中断传输事务API如上所示。
三种事务传输模式下的URB初始化函数有很多相似之处,主要参数含义如下:
• urb: 事务传输中的urb
• dev: 事务传输的目的设备
• pipe: USB主机与USB设备之间数据传输的通道
• transfer_buffer: 发送数据所申请的内存缓冲区首地址
• length: 发送数据缓冲区的长度
• context: complete函数的上下文
• complete_fn: 调用完成函数
• usb_fill_control_urb()的setup_packet: 即将被发送的设备数据包
• usb_fill_int_urb()的interval: 中断传输中两个URB调度的时间间隔
4.2.3 提交URB
URB初始化完成之后,USBD开始通过usb_start_wait_urb()提交urb请求(它调用usb_submit_urb来真正的发送URB请求),添加completition函数。
接下来,从message.c传到主机控制器(hcd.c),开始真正的usb_hcd_submit_urb()。此时,根据是否为根集线器,进入不同的工作队列。
usb_start_wait_urb->
usb_submit_urb->
usb_hcd_submit_urb
a) root_hub传输
若为root hub,将调用rh_urb_enqueue(),共有两种传输事务(控制传输和中断传输)
- static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb)
- {
- if (usb_endpoint_xfer_int(&urb->ep->desc)) // 中断传输
- return rh_queue_status (hcd, urb);
- if (usb_endpoint_xfer_control(&urb->ep->desc)) // 控制传输
- return rh_call_control (hcd, urb);
- return -EINVAL;
- }
b) 非root_hub传输
对于非常root_hub传输,它调用:
status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
c) 批量传输
root_hub本身没有批量传输流程,按照控制传输流程,控制传输最终要通过switch语句跳转到Bulk-Only传输流程中。