USB (Universal Serial Bus;通用串行总线)是1995年英特尔和微软等公司联合倡导发起的一种新的 PC 串行通信协议。它基于通用连接技术;实现外设的简单快速连接;达到方便用户、降低成本、扩展 PC 连接外设范围的目的。其最大特点是支持热插拔和即插即用。最多可串接下127 个外设;它可以向低压设备提供5 伏电源;同时可以减少 PC 机 I/O 接口数量。USB出现之前;计算机领域中的接口太多太繁杂;USB出现之后减少了接口的种类;总的来说就是设计出了一个万能的接口;各种外设都能用同一种接口;所以才冠以“通用;是Universal;”为名。
在进行USB系统开发之前;有必要了解USB开发中可能遇到的一些常用术语;USB系统开发分为USB主机开发和USB设备开发。在一个USB系统中;某一个时刻只有一个USB主机;其余均为USB设备;但是为了让一个USB系统既有USB主机功能;又有USB从及功能;便出现了USB OTG。因此USB开发主要包括USB主机、USB设备、USB OTG系统开发。下面就一些常用书进行介绍;
USB Host(USB主机);在任何一个USB系统中只有一个USB主机;主机就是USB总线中作主设备角色的设备, 负责管理USB总线中的数据传输及端口管理。USB和主机系统的接口称为主机控制器。比如一个U盘(USB大容量储存设备)和PC通讯, PC在这里就是USB Host。
USB Device(USB设备);USB主机的下行设备;在USB总线中作从设备角色的设备;为系统提供具体功能;USB主机最多可以支持127个USB设备。
USB OTG;OTG就是On The Go;正在进行中的意思。
USB设备;USB设备按功能分为两部分;集线器(Hub)和功能部件。
逻辑设备;逻辑设备就是一系列端点的组合;逻辑设备与主机之间的通信发生在一个主机的缓冲区和设备的一个端点之间。
USB Hub;USB集线器;:USB Hub可以将一个USB口转换为多个;扩展USB主机所能连接设备的数量;USB Host带有Root Hub;第一个USB设备是一个根集线器(Root_hub)它控制连接到其上的整个USB总线;该控制器是连接PCI总线和USB总线的桥;也是该总线上的第一个USB设备;USB Hub对于上游而言是一个USB Device, 对于下游而言扮演USB Host, 所以USB设备本身不知道自己连接在Hub还是Root Hub上。
PIPE;usb通信的最基本形式是通过USB设备里的endpoint;而主机和endpoit之间的数据传输就是通过pipe。
端点;主机与设备之间通信的目的或来源。端点是有方向的;主机到从机成为out端点;从机到主机成为in端点。控制端点可以双向传输数据;而其他端点只能在但方向上传输数据。主机和设备的通信最终作用于设备的各个端点上;是主机和设备间通信流的一个 逻辑终端;每个USB设备有一个唯一的地址;由主机分配;而设备中的每个端点在设备内部有唯一的端点号。这个端点号是在设计设备时给定的。每个设备必须有端点0;用于设备枚举和对设备进行一些基本的控制功能;除了端点0;其余的端点在设备配置之前不能与主机通信;只有向主机报告这些端点的特性并被确认后才能被激活;端点位于USB系统内部;是一个可寻址的FIFO空间;类似于高速公路收费口的入口或出口;一个端点地对应一个方向。
管道通信方式;pipe中的数据通信方式有两种;一种是stream一种是message。message要求进出进出方向必须要求同一个管道;默认就使用ep0作为message管道。
传输方式;USB endpiont有四种类型;分别对应了不同的数据传输方式;分别为control transfers控制传输、interrupt transfers中断传输、Bluk Data transfers批量传输、Isochronous Data Tranfers等时传输;控制传输通常用于配置设备;获取设备信息;发送命令到设备。
接口;一个逻辑设备可能包含若干个接口;每个接口包含1个或多个端点。每个接口表示一种功能。一个接口对应一个驱动程序。例如usb扬声器就包含一个键盘接口和一个音频流接口。
Class协议;USB协议中除了定义一些通用软硬件电气特性;还包含各种各样的class协议;用来为不同的功能定义各自的标准接口和具体总线上的数据交互内容和格式。例如u盘的Mass storage class、通用数据交换CDC class。
USB 1.0/1.1(low/full speed);传输速率最大为12Mbps
USB 2.0(high speed);传输速率最大480Mbps
USB 3.0(super speed);传输速率最大5Gbps
USB 连接器包含四条线;2 条用于电源供电( VBUS 和 GND);2 条用于 USB 数据传输D; ;USB数据正信号线;USB Data Positive;即USB-DP线;简写为D;;和 D-;USB数据负信号线;USB Data Minus; 即USB-DM线;简写为D-;。VBUS 提供 5V 电源;电流可达 500mA。D; 和 D- 为双向信号线;信号传输速率为 12Mbps (每位 83ns)。D; 和 D- 信号电平为 3.3V 。
USB OTG 接口中有 5 条线; 2 条用来传送数据;D; 、D-;; 1 条是电源线(VBUS); 1 条则是接地线(GND)、1 条是 ID 线。
USB信号使用分别标记为D;和D- 的双绞线传输;它们各自使用半双工的差分信号并协同工作;以抵消长导线的电磁干扰。
可以热插拔;即插即用。
携带方便。USB 设备大多以“小、轻、薄”见长;对用户来说;随身携带大量数据时;很方便。当然 USB 硬盘是首要之选了。
标准统一。大家常见的是 IDE 接口的硬盘;串口的鼠标键盘;并口的打印机扫描仪;可是有了 USB 之后;这些应用外设统统可以用同样的标准与个人电脑连接;这时就有了 USB 硬盘、USB 鼠标、USB 打印机等等。
可以连接多个设备。USB 在个人电脑上往往具有多个接口;可以同时连接几个设备;如果接上一个有四个端口的 USB HUB 时;就可以再连上;四个 USB 设备;以此类推;尽可以连下去;将你家的设备都同时连在一台个人电脑上而不会有任何问题(注;最高可连接至 127 个设备)。
USB 的总线结构是采用阶梯式星形;tiered star;的拓扑;topology;结构;如下图所示。每一个星形的中心是集线器;而每一个设备可以通过集线器上的接口来加以连接。从图中可以看到 USB 的设备包含了两种类型;USB 集线器与 USB 设备。位于最顶端的就是Host;主机端;。从 Host 的联机往下连接至 Hub;集线器;;再由集线器按阶梯式以一层或一阶的方式往下扩展出去;连接在下一层的设备或另一个集线器上。事实上;集线器也可视为一种设备。而其中最大层数为 6 层;(包括最后一级设备后共7层);。每一个星形的外接点的数目可加以变化;一般集线器具有 2、4 或 7 个接口。
USB 的拓扑体系由 3 种元素组成 ;主机(Root Hub 与 USB 主机控制器是绑定在一起的)、Hub 和设备。在 PC 平台上的 USB 中;PC 就是主机和根 Hub;用户可以将设备和下级 Hub 与之连接。而这些附加的 Hub 又可以连接更下一级的 Hub 和设备;从而构成了星形结构。
图中的 Hub 是一类特殊的 USB 设备;它是一组 USB 的连接点;主机中有一个被嵌入的 Hub 叫根 Hub(root Hub)。主机通过根 Hub 提供若干个连接点。为了防止环状连接;采用星形连接来体现层次性。
USB架构中; hub负责检测设备的连接和断开;利用其中断IN端点(Interrupt IN Endpoint)来向主机;Host;报告。在系统启动时;主机轮询它的根hub;Root Hub;的状态看是否有设备;包括子hub和子hub上的设备;连接。
整个 USB 总线可以分为 3 个部分进行描述;USB 连接、USB 设备、USB 主机。
USB 主机;
在 USB 总线中只有一个主机。USB 总线与计算机主机系统的接口部分就是主机控制器;它可被看做一个硬件、固件和软件的结合体。主机系统中集成了一个根 hub 来提供一个或多个连接点。
USB 设备;
首先 USB 设备可被分为两大类;hub 类;提供附加 USB 接入点的设备;和功能 设备类;为系统实现某些功能的设备;如 ISDN 适配器、数字游戏杆等;。
按照功能;USB 设备又可分为很多类;如;音频、人机交互、显示、通信、电源、打印机、海量存储、物理反馈等设备。每个 USB 设备都必须提供自鉴定信息和通用的设置
USB 设备都有一个标准的USB接口;它的作用为;解释 USB 协议;对标准 USB 操作的响应;如挂起和设置等;提供设备的一些描述信息。
在实际的设计应用中;USB 设备的接口有自已的特点。USB 接口的正确设计与设备的性能紧密相关;在 USB接口设计之前必须要对设备的功能、指标进行详细的分析。
连接在 USB 接口上的设备通过基于令牌和主机控制的协议来共同享用整个 USB 带宽。在其它设备正常工作的前提下;USB 允许某设备连接、设置、运行和断开连接。
USB 连接;
USB 连接是指 USB 主机和 USB 设备的通信方式与方法;包括;总线拓扑;USB主机和设备之间的连接方式;;层内关系;USB总线每一层中的任务;;数据流模式;数据在USB总线上的流动方式;;USB 调度; USB 提供一个共享的服从调度的互连;
USB 设备是通过 USB 总线连接到 USB 主机上的。USB总线上的物理连接是一个分层的星形拓扑。处于每个星形拓扑中央的是 hub(USB 集线器)。在主机和一个hub或者一个应用之间以及在 hub 和其它 hub 或应用之间都是一个点对点的连接。
USB endpiont有四种类型;分别对应了不同的数据传输方式;分别为control transfers控制传输、interrupt transfers中断传输、Bluk Data transfers批量传输、Isochronous Data Tranfers等时传输
控制传输通常用于配置设备;获取设备信息;发送命令到设备。
USB总线是串行总线;跟串口一样;;数据是一位一位地在数据线上进行传输的。LSB在前;最低位先发出;接下来是次低位;最后才是最高位;MSB;。
首先确定USB系统的开发类型;是USB主机、USB从机还是USB OTG。
如果确定是USB设备;必须确定设备类型;HID、UDIO、CDC、HUB、IMAGE等。
查找相关设备手册;确定其描述符。
完成描述符后;编写USB枚举程序;观察是否枚举成功;如果枚举成功了;此设备开发已经完成大部分。
编写应用程序;在枚举成功后;主要是进行数据处理;编写应用程序。
电脑上的USB接口是3.0还是2.0可以通过三个方法区分;颜色区分法、触片法区分法、标识区分法。
1、USB3.0与USB2.0外观区别;观察USB;本身;的插口和电脑上USB插口;中间的塑料片颜色;USB3.0——蓝色;USB2.0——黑色或者白色。
2、不能通过颜色区分;也可以看接口针脚数。USB3.0相较于USB2.0多了几个针脚;在Type-A接口上;接口的里面多了5个针脚;Type-B接口则在接口上方多了一块。
USB3.0采用的是两排共9个针脚的设计。
3、标识区分法;根据在插口旁边的符号来区分;如下图。USB3.0的“SS”代表着“SuperSpeed”。
包是USB传输的基本单元;USB协议规定了三种类型的包;令牌(Token)包、数据(DATAx)包、握手(Ack)包。其中;令牌包只能是从主机发出。数据包和握手包可由主机发出;也可是设备发出。
包由各个不同的域组成;所有的包都以同步域SYNC开始;以包结束信号EOP作为结束。包的结构如下;
不同类型的包;组成位域是不相同的;主要有这几个域;
(1) 包标识符(PID)
包标识符主要表明当前的包是属于哪种类型的包。包标识符总共有8个位;其中USB协议使用的只有4个位[3:0];另外的4个位[7:4]是对应的前面的4个位的取反。各种类型包的PID位域以及说明如下表;
说明;
4. USB是串行总线;使用串行方式进行传输;;数据是一位接着一位在总线上进行传输的;并且使用的是LSB在前的传输方式;也就是最低位先传输;接着是次低位;最后是最高位。以传输SETUP包的PID为例;总线上的数据应该是;1011 0010;所以使用协议分析仪抓到的数据就是0xB4。
5. 表格中列出的是常见的数据包;还有一些特殊用途包;没有做过多的学习。以及随着USB协议更新;可能还会有一些新的包类型;具体情况需要参考官方文档。
(2) 包目标地址(ADDR)
是主机分配给设备的一个非0地址。USB主机发送的包会在总线上进行广播;所有接在总线上的设备根据自己的设备地址对令牌包以及跟在令牌包后面的数据包进行过滤。包目标地址只有7个位;共2的7次方=128个地址;;即地址范围是;0127。0地址是主机分配地址给设备之前;设备的默认地址;除去0地址不算;设备的通信地址就是1127。也就是说;一个USB主机最多只能管理127个USB设备;理论上;一个Host;最多能接入127个Device;再多接入一个;地址就重合了;。
(3) 包目标端点(ENDP)
USB设备和USB主机之间是通过端点来发送数据的。因此在总线上传输的每一个数据包都要指定;发送数据包的目标端点;数据包要发到哪一个设备的哪一个端点上;。
(4) 数据域
数据长度的范围;0~1024字节;不同的传输类型在不同模式下的数据域长度都不相同。该值与传输类型以及端点支持的最大包长、数据对象都有关系;比如主机请求获取不同的设备描述符;指定的长度是不同的;。
(5) 循环冗余校验码(CRC)
USB协议规定;只有令牌包和数据包有循环冗余校验码;令牌包使用5位的CRC5;数据包使用的是16位的CRC16校验。
令牌包是第一个数据包;表示一次传输即将开始。共有4种令牌包;SETUP令牌包、IN令牌包、OUT令牌包、SOF令牌包。其中SETUP、IN、OUT令牌包的结构是一样的;共有6个域;格式如下;
SETUP令牌包
IN令牌包
OUT令牌包
SOF令牌包
帧起始包(Start Of Frame);在USB的拓扑结构中;主机每隔一段时间就向总线上广播SOF包;所有的连到总线上的全速设备和高速设备都能收到SOF包。
SOF令牌包结构相对其余的三个令牌包来说;有点特殊;只有5个域。
说明;
设备枚举阶段使用的是控制传输;控制传输的建立事务;建立过程;使用的是SETUP令牌包;只有控制传输才会有SETUP令牌包;;设备的端点0是专门用于控制传输的;所以包目标端点ENDP为0;包目标地址ADDR可以是0或者非0值[1~127]。
设备使用地址0时;CRC5的值是0x08;设置地址阶段过后;CRC5的值根据具体的地址值来进行计算;CRC5的计算由USB控制器自动完成。
SETUP令牌包中包含了紧跟在其后面的数据包的目标地址和包目标端点信息。
4个令牌包中;只有SOF令牌包之后不跟随数据传输。
对于低速/全速设备;SYNC为;0000 0001;而对于高速设备;SYNC中有31个0;最后一个位是1。
USB1.1协议中;只有两种数据包;DATA0 和 DATA1。USB2.0新增了两个数据包;DATA2和MDATA分别用于高速分裂事务;split;事务和高速带宽同步传输中;DATA0和DATA1是需要关注的重点。
DATA0数据包结构;DATA0数据包的PID是0xc3;;
DATA1数据包结构;DATA1数据包的PID是0xd2;;
说明;
对于控制传输
建立事务的数据过程使用的是DATA0数据包(SETUP事务的数据过程只能使用DATA0);DATA0数据包是8字节的请求数据。
IN事务或者OUT事务的数据过程使用的是DATA1数据包;是实际要交互的数据。
端点0传输的数据包就在DATA0和DATA1之间进行切换;这是实现设备栈时;为什么配置USB控制芯片端点的toggle功能的原因。
数据包是跟随在令牌包后面的;数据包传输的目标地址和端点信息已经在令牌中指明。
握手包只有三个域;结构如下;
说明;
握手包跟随在令牌包或者数据包之后;组成一次完整的事务。
握手包可以由设备发出也可以由主机发出。
握手包的方向和数据包的方向是相反的。
一个事务通常由两个或者三个包来组成;令牌包;数据包;握手包
1;令牌包;启动一个事务;总是由主机发出。
2;数据包;用于传输数据;可以从主机发到设备;也可以从设备发到主机;数据包的方向由令牌包来指定。
3;握手包;握手包的发送者就是数据包的接收者。所以说;握手包的方向和数据包的方向是相反的。当数据被正确接收后;接收者就发出握手包。
事务总是以令牌包开始;所以令牌包的类型决定事务的类型; 因此事务可分为;SETUP事务、IN事务、OUT事务。
USB总共有四种传输类型;批量传输(bulk)、中断传输(interrupt)、等时传输(isochronous)、控制传输(control)。不同的传输类型都有各自的数据流模型。
在设备枚举阶段;设备的主机之间的数据交互使用的是控制传输;首先了解控制传输;对于其他类型的传输及其数据流模型;后面介绍不同的类设备开发的时候;再逐一研究。
控制传输是四种传输类型中最复杂的一种;控制传输过程分为三个阶段;第一个阶段是建立阶段;第二个阶段是可选的数据阶段;第三个过程是状态阶段。这三个阶段也称为建立过程、数据过程、状态过程。
建立过程就是一个建立事务;事务里面的第一个包是SETUP令牌包;其次是DATA0数据包;8字节的请求数据;;最后是握手包;只能是ACK握手包;。数据阶段是可选的;也就是说控制传输可以没有数据过程;SETUP包的数据域会指定为0;;数据过程又分为;控制读传输;数据事务是输入的;控制写传输;数据事务是输出的;读/写是相对主机来说的;。而且数据过程的第一个数据包必须是DATA1数据包。
包构成事务;事务构成传输。即一个传输由多个事务构成;一个事务又由多个包构成。
事务的类型由包的类型来决定;传输的类型就看具体实现功能时所用的传输。
以控制传输;主机请求设备的描述符数据为例;关系图如下;
说明;
控制传输一般用来实现一条USB协议定义的请求
SETUP事务用来将一条请求命令发送到设备
IN事务用于USB设备向主机返回主机所请求的数据
OUT事务使用握手机制对之前的数据事务的正常结束进行确认
USB芯片分为Controller部分和PHY;Physical Layer;PHY负责底层信号转换;部分。Controller部分主要实现USB的协议和控制。内部逻辑主要有MAC层、CSR层和FIFO控制层;还有其他低功耗管理之类层次。MAC实现按USB协议进行数据包打包和解包;并把数据按照UTMI总线格式发送给PHY;USB3.0为PIPE;。CSR层进行寄存器控制;软件对USB芯片的控制就是通过CSR寄存器;这部分主要作为Slave通过AXI或者AHB与CPU进行交互。FIFO控制层主要是和DDR进行数据交互;控制USB从DDR搬运数据的通道;主要作为Master通过AXI/AHB进行交互。PHY部分功能主要实现并转串的功能;把UTMI或者PIPE口的并行数据转换成串行数据;再通过差分数据线输出到芯片外部。
USB芯片内部实现的功能就是接受软件的控制;进而从内存搬运数据并按照USB协议进行数据打包;并串转换后输出到芯片外部。或者从芯片外部接收差分数据信号;串并转换后进行数据解包并写到内存里。
USB控制器接受的是数字信号;USB PHY负责;在USB控制器与USB接口之间做数字信号与模拟信号的转换。
首先;在USB集线器;Hub;的每个下游端口的D;和D-上;分别接了一个15K欧姆的下拉电阻到地。这样;在集线器的端口悬空时;就被这两个下拉电阻拉到了低电平。而在USB设备端;在D;或者D-上接了1.5K欧姆上拉电阻;对于全速和高速设备;上拉电阻是接在D;上;而低速设备则是上拉电阻接在D-上。这样;当设备插入到集线器时;由1.5K的上拉电阻和15K的下拉电阻分压;结果就将差分数据线中的一条拉高了。集线器检测到这个状态后;它就报告给USB主控制器;或者通过它上一层的集线器报告给USB主控制器;;这样就检测到设备的插入了。USB高速设备先是被识别为全速设备;然后通过HOST和DEVICE两者之间的确认;再切换到高速模式的。在高速模式下;是电流传输模式;这时将D;上的上拉电阻断开。
主机的D;和D-都接有15K下拉电阻;全速USB设备的数据线D;接有1.5K的上拉电阻;一旦接入主机;主机的D;被拉高;低速USB设备的数据线D-接有1.5K的上拉电阻;一旦接入主机;主机的D-会被拉高。因此;主机就可以根据检测到自己的D;为高还是D-为高;从而判断接入的设备是一个全速还是低速设备。硬件结构图如下;
刚开始时;高速设备以全速模式连接到主机;D;有上拉电阻。主机检测到全速设备连接上之后;对设备进行复位;USB设备收到复位信号;主动发起高速模式的握手协议进行速度识别。接下来;就取决于主机端了;如果主机的USB控制器支持高速模式传输;则主机会与该高速设备交互完成高速模式握手协议;之后;两者都工作在高速模式下;如果主机不支持高速模式传输;如果主机上没有EHCI类型的控制器;像比较老旧的电脑;并且是XP系统的;;那么握手协议就会失败;设备端也不会切换到高速模式;之后两者都工作在全速模式下。
如果是一个全速设备接到高速主机;设备端没法发起高速握手协议;所以;最终设备和主机都会工作在全速模式下;主机具有多种控制器类型;能工作在全速/高速模式下。
全速和高速控制器;都是DP上拉;低速才是DM上拉;配置寄存器的时候;需要注意的地方;。
请求和描述符一样;有标准的设备请求;而对于不同类的设备;又有自己特定的请求。先介绍标准的设备请求;对于特殊的请求;介绍每个不同的类设备时;再做解析。
USB协议中规定;标准请求的长度为8个字节。在设备枚举过程中;Host会下发一系列的标准请求;设备端需要去解析这些标准请求;SETUP事务;;并作出正确响应;设备才能成功枚举。成功枚举之后;才能调用相关接口进行数据通信。
8字节的标准请求结构如下;
每个域的解析如下表;
对于标准的请求;D6~D5 = 00;USB协议规定了11个标准请求;请求码(bRequest)如下表。
下面重点介绍三个标准请求。
获取描述符请求。该请求由Host发出;设备端解析请求;并返回指定描述符数据到Host;是设备枚举过程中用的最多的一个请求。
获取设备描述符请求结构如下;
对每个域的解析如下
1;wValue有两个字节;低字节表示;索引号。即同一类描述符里面的哪一个描述符。
例1;字符串描述符可分为厂商字符串、产品字符串、产品序列号字符串;对应的索引值分别为1、2、3。如果索引值为0;则表示获取的是语音ID字符串。
例2;假如一个设备有多种配置;定义了多个配置描述符;;那么索引号就是配置描述符的ID号(bConfigurationValue)。
wValue的高字节表示描述符的类型;USB协议规定不同的类型由一个唯一的码值来表示;如下表;
2;wIndex
这个域是专门为获取字符串描述符而设置的。当主机请求获取其他描述符时;这个域的值一定是0;当主机请求获取那三个字符串描述符时;这个域是字符串的语言ID号;一般用的是美式英语;值固定为0x0409;该值就是主机获取字符串描述符语言ID的时候;设备返回的值。所以;主机获取设备字符串描述符的时候;顺序一般都是;假如在设备描述符中设置字符串描述符的索引值为非0;;
A. 最先请求的是字符串描述符的语言ID
B. 请求厂商字符串
C. 请求产品字符串
D. 请求产品序列号字符串
3;wLength
数据过程;要求设备返回的数据长度。设备端返回的数据可以小于这个值;比如枚举过程中;Host(win7)会一次性请求长度为0xFF的配置描述符。
注;
(1) 如果设备工作在全速模式(Full-Speed Mode)下;那么主机端获取标准描述符;只有三个标准请求;A.获取设备描述符的请求;B.获取配置描述符的请求;C.获取字符串描述符的请求。对于接口描述符和端点描述符;是在主机端请求配置描述符集合的时候;一并返回。
(2) USB协议规定;总线的传输方式是串行方式;并且是LSB在前;……;MSB在最后。这是分析BusHound或者协议分析仪上的数据需要注意的地方。
主机在收到第一个数据包;设备描述符数据包;;解析无误后;接下来就进入设置地址阶段。
设置地址请求也是一个USB标准设备请求。这是一个分配地址的过程;主机给设备分配一个地址;。既然是标准请求;那么它也是有8个字节的数据。指定的地址包含在wValue字段中
主机从地址为0的设备获取设备描述符;一旦第一次成功获取到设备描述符之后;主机就会立刻发送设置地址的请求;减少设备使用公共地址0的时间;每个设备插入;都先被复位;默认的地址为0;也就是0地址是所有的USB设备的初始化地址;即可以理解为公共地址;。
设置地址请求的结构如下;
说明;
(1) 设置地址的请求过程;是没有数据的;所以;数据长度就是0。
(2) 索引也用不着;请求字符串描述符;索引才用的上;;所以也为0。
实例
主机下发的数据包为; 00 05 1D 00 00 00 00 00
解析; 数据方向【主机—>设备】 设置地址请求 地址数据为0x001D=29
设置配置的请求下发后;设备端的USB控制器进入配置状态。设备端根据配置值;使用对应的配置;USB设备才能正常工作。
设置配置的结构如下;
说明;
(1) 在设置配置请求中;wValue的第一字节;低字节;为配置的值。当该值与某配置描述符中的配置编号一致时;相等时;;表示选中的是该配置;接下来设备就使用这个配置。
实例
一个USB设备可以有很多个配置。bConfigurationValue就是每个配置的标识;主机请求设置配置的时候;会下发一个配置值;如果某个配置的bConfigurationValue和主机请求的配置值相匹配;就表示该配置被激活;USB设备就使用这个配置(由主机决定;设备使用哪个配置)。
设置配置请求是一个输出请求;根据所请求的配置值;使能相应的端点。设备收到之后;返回一个0长度的状态数据包。设备收到非0的配置值之后;才会使能非0端点。否则会禁用非零端点。
USB主机在检测到USB设备插入后;就要对设备进行枚举了。为什么要枚举呢?枚举就是从设备读取一些信息;包含USB设备传输类型、ID号、Product、USB速度等信息;这样主机就可以根据这些信息来加载合适的驱动程序。调试USB设备;很重要的一点就是USB的枚举过程;只要枚举成功了;那么就已经成功大半了。
在说枚举之前;先大概说一下USB的一种传输模式——控制传输。这种传输在USB中是非常重要的;它要保证数据的正确性;在设备的枚举过程中都是使用控制传输。控制传输分为三个过程;①建立过程;Setup;。②可选的数据过程。③状态过程。建立;Setup;过程都是由USB主机发起;它开始于一个Setup令牌包;后面紧跟一个DATA0包。如果是控制输入传输;那么数据过程就是输入数据;如果是控制输出传输;那么数据过程是输出数据。如果在设置过程中;指定了数据长度为0;则没有数据过程。数据过程之后是状态过程。状态过程刚好与数据过程的数据传输方向相反;如果是控制输入传输;则状态过程是一个输出数据包;如果是控制输出传输;则状态过程是一个输入数据包。状态阶段用来确认所有的数据都已经正确传输。
首先;USB主机检测到USB设备插入后;就会先对设备复位。设备复位后;USB主机就会对地址为0的设备发送获取设备描述符的标准请求。所有的USB设备在总线复位后其地址都为0;这样主机就可以跟那些刚刚插入的设备通过地址0通信。主机在建立阶段发出获取设备描述符的输入请求;设备收到该请求后;在数据过程将设备描述符返回给主机。主机在成功获取到一个数据包的设备描述符后并且确认没有什么错误后;;注意;有些USB设备的端点0大小不足18字节;但至少具有8字节;;而标准的设备描述有18字节;在这种情况下;USB设备只能暂时按最大包将部分设备描述符返回;而主机在成功获取到前面一部分描述符后;就不会再请求剩下的设备描述符部分;而是进入设置地址阶段;;就会返回一个0长度的状态数据包给设备。
然后主机再对设备复位一下;接下来就会进入到设置地址阶段。这时USB主机发出一个设置地址的请求;建立过程;设置地址无数据过程;;地址包含在建立包中;具体的地址USB主机会负责管理;它会分配一个唯一的地址给新的设备。USB设备在收到地址后;返回0长度的状态包;主机收到0长度的状态包之后;会返回一个ACK给设备。设备在收到这个ACK之后;就可以启用新的地址了。这样设备就分配到了一个唯一的设备地址;以后主机就通过它来进行访问该设备。然后主机再次获取设备描述符;这次跟第一次可能有点不一样;这次需要获取完全部的18个字节的设备描述符。当然;如果你的端点0缓冲大于18字节的话;那就跟第一次的情形一样了。
接下来;主机就会获取配置描述符。配置描述符总共为9字节;为什么还要再读一次甚至多次设备描述符?设备刚接入时,只读一次设备描述符,可以申请读18字节;也可以申请更多的,但是只要检测到一个输入包;就够了;而不管端点0多大;即使申请的长度未够,也切换到设置地址阶段。设置地址后;再根据刚刚获取到的端点0大小来读取完整的设备描述符。通常每一次读设备描述符的操作是从不同的设备驱动中发出来的;设备驱动互相调用时不会传递很多信息;新的驱动获得控制权后自然要读一下设备描述符以确认这是它能处理的设备。;。主机在获取到配置描述符后;根据里面的配置集合总长度;再获取配置集合。配置集合包括配置描述符;接口描述符;端点描符等等。如果有字符串描述符的话;还要获取字符串描述符。另外HID设备还有HID描述符等。
要仔细分析;主机的每一次标准请求;接收者是谁;设备、接口、端点;。
枚举过程;使用的传输方式都是控制传输方式。
枚举过程;一些必须的请求肯定会有;比如;获取设备描述符---->设置地址---->USB复位总线----->获取配置描述符------>获取配置描述符集合---->获取各种类型的字符串描述符—>设置配置。
要明确各种描述符的意义和其在程序中的定义;以及数据如何返回给主机。明确收到请求;并响应的整个数据流通过程。
Linux内核支持两种主要类型的USB驱动程序;主机(host)系统上的驱动程序和设备(device)上的驱动程序。主机的USB控制器驱动程序控制插入其中的USB设备;主机的USB设备驱动程序控制该设备如何作为一个USB从设备和主机通信;由于“USB设备驱动程序”(USB devices drivers)容易混淆;USB开发者创建了术语“USB器件驱动程序”(USB gadget drivers)来描述控制连接到计算机的USB设备的功能实现。
接下来会讲解从主机侧看到的USB主机控制器驱动和设备驱动;以及从设备侧看到的设备控制器和Gadget驱动。
主机侧和设备侧的USB控制器分别称为主机控制器(Host Controller)和USB设备控制器(UDC)每条总线上只有一个主机控制器;负责协调主机和设备的通信;设备不能主动向主机发送任何信息。如下图在Linux系统中;USB驱动可以从两个角度去观察;主机侧和设备侧。从主机侧去看;在Linux系统中;处于USB驱动最底层的是USB主机控制器硬件;在其上运行的是USB主机控制器驱动;在主机控制器上的为USB核心层(只要是依赖总线的子系统;都有这样几个层次;host、core、device;拿USB子系统来说;就有USB主控制器、USB核心、USB子设备);再向上为USB设备驱动层;插在主机上的U盘、鼠标、USB转串口等设备驱动;。因此在主机侧要实现USB主机控制器驱动和USB设备驱动;主机的USB控制器驱动程序控制插入其中的USB设备;主机的USB设备驱动程序控制该设备如何作为一个USB从设备和主机通信;USB核心通过定义一些数据结构、宏和功能函数向上为设备驱动提供编程接口;向下为USB主机控制器提供编程接口;维护整个系统USB设备信息;完成设备热插拔、总线数据传输控制等。
USB设备侧驱动程序分为三个层次;USB设备控制器(UDC)、Gadget Function API以及Gadget Function驱动程序。UDC驱动程序直接访问硬件;控制USB设备和主机间的底层通信;向上层提供与硬件相关操作的回调函数;Gadget Function API是UDC驱动程序回调函数的简单包装;Gadget Function驱动程序具体控制USB设备功能的实现;使设备表现出“网络连接”、“打印机”或“USB Mass Storage”等特性;它使用Gadget Function API控制UDC实现上述功能。
在USB 设备的逻辑组织中;包含设备、配置、接口和端点4 个层次;每个USB 设备都提供了不同级别的配置信息;可以包含一个或多个配置;不同的配置使设备表现出不同的功能组合;配置由多个接口组成;接口由多个端点组成;代表一个基本的功能;是USB 设备驱动程序控制的对象;如下图是USB 设备、配置、接口和端点之间的关系。
端点是 USB最基本的形式;每一个USB设备接口在主机看来就是一个端点的集合。主机只能通过端点与设备进行通信;以使用设备的功能。在USB系统中每一个端点都有唯一的地址;每个端点都有一定的属性;其中包括传输方式、总线访问频率、带宽、端点号和数据包的最大容量等。一个USB端点只能在一个 方向上承载数据;从主机到设备;称为输出端点;或者从设备到主机;称为输入端点;;因此端点可以看成一个单向的管道。端点0通常是控制端点;用于设备初始化参数等。只要设备连接到USB上并且上电;端点 0就可被访问;端点1、2等一般用作数据端点;存放主机与设备间往来的数据。
设备通常有一个或多个配置;
配置通常有一个或多个接口;
接口有0个或多个端点;
USB是个通用的总线;端口都是统一的。但是USB设备却各种各样;例如USB鼠标;USB键盘;U盘等等;那么USB主机是如何识别出不同的设备的呢?这就要依赖于描述符了。USB的描述符主要有设备描述符;配置描述符;接口描述符;端点描述符;字符串描述符;HID描述符;报告描述符等等。
一个USB设备有一个设备描述符;设备描述符里面决定了该设备有多少种配置;每种配置描对应着配置描述符;而在配置描述符中又定义了该配置里面有多少个接口;每个接口有对应的接口描述符;在接口描述符里面又定义了该接口有多少个端点;每个端点对应一个端点描述符;端点描述符定义了端点的大小;类型等等。由此我们可以看出;USB的描述符之间的关系是一层一层的;最上一层是设备描述符;下面是配置描述符;再下面是接口描述符;再下面是端点描述符。在获取描述符时;先获取设备描述符;然后再获取配置描述符;根据配置描述符中的配置集合长度;依次将配置描述符、接口描述符、端点描述符一起一次读回。其中可能还会有获取设备序列号;厂商字符串;产品字符串等。
每个USB设备都必须并且只有一个设备描述符(在程序中定义好设备描述符)。USB协议对设备描述符的定义如下;
说明;
1;bcdUSB是该设备所使用的USB协议版本号;长度2字节。比如可以取2.0或者1.1等版本号。需要特别注意的是;协议规定使用BCD码来表示版本号;比如;USB2.0协议就是0x0200;USB1.1协议就是0x0110。对照USB协议分析仪来看的时候;要注意;USB协议中使用的是小端结构;也就是低字节在前。比如说;USB2.0协议拆分成两个字节就是0x00 0x02。
2;bDeviceClass是设备所使用的类代码;XX类接口描述符码;。常用的类如下(根据协议;进行C宏定义);
//HID设备类接口描述符码
#define HID_CLASS 0x03
//音频类接口描述符码
#define Audio_CLASS 0x01
//视频类接口描述符码
#define Vedio_CLASS 0x0E
//大容量设备类接口描述符码
MASS_STORAGE_CLASS 0x08
//杂项类或者混合类接口描述符码
#define MISC_CLASS 0xEF
//厂商自定义的设备类接口描述符码
#define CUSTOM_CLASS 0xFF
//特定应用类接口描述符码
#define DFU_DEVICE_CLASS 0xFE
3;bDeviceSubClass设备所使用的子类代码。当子类代码不为0也不是0xFF时;子类代码就得根据协议来进行赋值。当类代码为0的时候;子类代码也必须为0。
4;bDeviceProtocol是设备使用的协议。协议代码由USB协议规定。
5;端点0的最大包长;取值可以是:8、16、32、64字节。注意;对应的十六进制数分别就是;0x08、0x10、0x20、0x40(分析源码和协议分析仪里面的数据;注意进行转换)。
6;关于厂商ID (2Byte);在开发中;可以随意设定一个值。真正做产品;要使用公司的ID(向USB协会申请);避免侵权。对于插入的设备;主机是依靠厂商ID号、产品ID号、产品序列号来安装驱动的。
7;产品ID是生产厂商自己定义的;比较自由。
8;bcdDevice设备版本号。同一个产品;升级之后(比如固件修改;新增功能);可以通过修改设备版本号来进行区别。
9;iManufacturer是描述厂商字符串的索引值。如果设为0;则表示该USB设备没有厂商字符串。主机单独获取厂商字符串的时候;下发的标准请求数据包中;wValue域的第一个字节【低字节】就是厂商字符串的索引值;而高字节就是描述符的类型(字符串描述符0x03)。
厂商字符串就是一串普通的字符串;在设备描述符中;有三个非0的索引值;厂商字符串的索引值为1;产品字符串的索引值为2;产品序列号字符串的索引为3。设备在收到主机的字符串描述符请求之后;根据索引值;将对应的字符串数据返回给主机。
所以;如果解析到主机字符串描述符请求的数据包;如果wValue=0x0301;则表示主机请求获得厂商字符串。
10;iProduct是描述产品的字符串的索引值。同样的;如果设置为0;则表示该USB设备没有产品字符串。第一次插上设备时;提示发现新硬件;并显示设备的名称;其实这里显示的信息就是从产品字符串中获取的。实验:可通过修改产品字符串;再编译固件烧录;插入设备;就可以看到提示信息了。
同理;当主机请求产品字符串的时候;wValue这个域的值应该是;0x0302
11;iSerialNumber是设备的序列号字符串的索引值。同样的;如果设置为0;则表示该USB设备没有设备序列号。最好一个产品指定一个唯一的序列号;因为有可能主机会结合产品序列号和VID、PID来进行设备的区分和加载对应的驱动。同理;当主机请求序列号字符串的时候;wValue这个域的值应该是;0x0303。
一个USB设置至少有一个配置描述符;标准配置描述符结构如下;
说明;
1;wTotalLength是整个配置描述符集合的总长度;配置描述符集合就包括;配置描述符自身的长度、接口描述符、端点描述符、类特殊描述符等。
2;bNumInterfaces是该设备所支持的接口数量。通常来说;功能单一的设备就只有一个接口;比如鼠标、键盘;;而复合设备则具有多个接口;比如音频设备;一般是HID ; UAC;。
3;bConfigurationValue;一个USB设备可以有很多个配置。bConfigurationValue就是每个配置的标识;ID;;进行设置配置这个请求的时候;主机会发送一个配置值;如果某个配置的bConfigurationValue和主机请求的配置值相匹配;就表示该配置被激活;USB设备就使用这个配置。(主机决定;设备使用哪个配置)
4;iConfiguration为0;则表示没有字符串来描述该配置描述符。
5;bmAttributes;大小为一字节;不同的位;表示不同的特性。
6;bMaxPower;大小为1字节。表示设备从总线获取的最大电流量;单位是2mA。比如;如果需要200mA的电流;那么该字节的值就是100;100 x 2mA = 200mA;。
接口描述符不能单独返回;要附着配置描述符后一并返回;Host请求配置描述符集合后;Device返回一堆数据;。
说明;
1;bInterfaceNumber;当一个配置有多个接口时;每个接口的编号都不相同;从0开始递增对一个配置的接口进行编号。这里需要注意;对照一下;配置描述符里面;支持多少个接口;这个域;bNumInterfaces;。
2;bAlternateSetting:备用端口号;也是从0开始。一般很少用到该字段;开发UAC就必须用到这个字段;;设置为0即可。Alternate;备用。
3;bNumEndpoints;是该接口使用的端点数;不包括0端点。如果该字段设置为0;那么表示没有非0端点。
4;bInterfaceClass、bInterfaceSubClass、bInterfaceProtocol;分别是接口所使用的类、子类、和协议;对应的代码(编号)由USB协议来定义。跟设备描述符中的意义很相像;设备描述符也有类似的三个域 ;如果要开发自己的上位机;则这三个字段都设置为0xFF;。
5;iInterface;如果设置为0;则表示没有字符串。
端点描述符不能单独返回;要附着配置描述符后一并返回;Host请求配置描述符集合后;Device返回一堆数据;。
说明;
1;bEndpointAddress;大小1字节;端点的地址。
最高位;也就是第7位D7;表示该端点的传输方向。1表示输入(像Input的第一个字母) 0表示输出(像Output的第一个字母)
D6~D4;保留位;默认为0。
D3~D0;才是端点号
2;bmAttributes是端点的属性。
最低两位D1~D0;表示该端点的传输类型。0为控制传输;1为等时传输;2为批量传输;3为中断传输。两个位;也就四种情况;00 01 10 11
如果是非等时传输;控制传输、批量传输、中断传输中的一种;;那么D7~D2都为0
如果该端点时等时传输;则;D3D2表示同步的类型;0为无同步、1为异步、2为适配、3为同步。D5D4表示用途;0为数据端点、1为反馈端点、2为暗含反馈的数据端点、3是保留值。D7~D6;保留;设为0。
3;wMaxPacketSize;大小2字节;表示该端点每次传输的最大包长;单位;字节;。关于端点的最大包长;需要注意两点;其一是USB协议规定的最大包长上限;其二是需要注意结合实际开发场合来规定端点最大包长;比如;开发UAC的时候;端点最大包长要根据采样率、通道数等来计算并设置;否则PC端就不能正确进行重采样;声音就会失真;。
4;bInterval;表示端点查询的时间。对于中断端点以及同步传输;表示端点的轮询时间间隔;而对于块传输;该字段没有意义;介绍到具体的类设备时;该字段会作为重点解析的字段;。
主机在获得配置描述符集合之后;会下发获得语言ID的请求;索引值为0;;以及获得字符串描述符的请求。在USB协议中;字符串描述符是可选的。在设备描述符中;申请了三个非0的索引值;
1;是厂商字符串的索引值
2;是产品字符串的索引值
3;是产品序列号的索引值
主机通过索引值来获取对应的字符串数据。索引值为0则表示获取的是语言ID字符串。字符串描述符的结构很简单;如下;
首先是语言ID描述符的结构;
字符串描述符(产品;厂商;产品序列号)的结构;
说明:
1;语言ID;只使用美式英语的一种;即0x0409。
2;bString字段使用的是UNICODE编码的字符串;使用两个字节来表示一个字符。
3;如果设备描述符中的iManufacturer、iProduct、iSerialNumber都设置为0的话;主机就不会下发对设备字符串描述符的请求;调试;;所以说;开发过程中;字符串描述符并不是必须的。但是;针对产品的研发;它又是必须的。
1;一个设备;只有一个设备描述符。
2;一个设备描述符可以包含多个配置描述符。
3;一个配置描述符可以包含多个接口描述符。
4;一个接口描述符可以包含多个端点描述符。
描述符之间的包含关系如下图;站在集合的角度去理解;;
USB 设备和主机的接口就是host controller;一个主机可以支持多个host controller;比如分别属于不同厂商的。那么USB host controller 本身是做什么的? controller(控制器);用于控制。控制什么? 控制所有的USB设备的通信。
CPU把要做的事情分配给主机控制器;然后自己想干什么就干什么去;主机控制器替他去完成剩下的事情;事情办完了再通知CPU。否则让CPU去盯着每一个设备做每一件事情;那是不现实的。
控制器的主要工作是什么? 把数扔出去;把数拿回来。绝对不应该偷偷加工数据。主机控制器控制总线上包的传输; 使用1ms或125us的帧。在每帧的开始时;主机控制器产生一个帧开始包(SOF: Start of Frame)。SOF包用于同步帧的开始和跟踪帧的数目。包在帧中被传输;或由Host到Device(out事务);或由Device到Host(in事务)。传输总是由Host发起(轮询传输);因此每条USB总线只能有一个Host。每个包的传输都有一个状态阶段同(同步传输除外);数据接收者可以在其中返回ACK(应答接收);NAK(重试);STALL(错误条件)或什么也没有(混乱数据阶段;设备不可用或已经断开)。
USB主机控制器包括;
UHCI: Universal Host Controller Interface (通用主机控制接口, USB1.0/1.1);与OHCI不兼容。UHCI在软件驱动层面就需要做的比较多;需要做得比较复杂。
OHCI: Open Host Controller Interface (开放主机控制接口,USB1.0/1.1) ;不仅仅是针对USB;还支持其他的接口;比如支持Apple的火线;Firewire;IEEE 1394;接口。与UHCI相比;OHCI的硬件复杂;很多事情都交给硬件来做;所以实现对应的软件驱动的任务;就相对较简单。主要用于非x86的USB;如扩展卡、嵌入式开发板的USB主控。
EHCI: Enhanced Host Controller Interface (用于USB2.0高速设备的“增强主机控制接口”);是Intel主导的USB2.0的接口标准。EHCI仅提供USB2.0的高速功能;而依靠UHCI或OHCI来提供对全速;full-speed;或低速;low-speed;设备的支持。
xHCI;eXtensible Host Controller Interface;是最新USB3.0的接口标准;它在速度、节能、虚拟化等方面都比前面3中有了较大的提高。xHCI支持所有种类速度的USB设备(USB 3.0的 SuperSpeed; USB 2.0 Low-、Full-、and High-speed; USB 1.1 Low- and Full-speed)。xHCI的目的是为了替换前面3种类型。
USB主机控制器驱动让主机控制器工作起来;发挥它的潜力。 让控制器发数据、收数据 。主机控制器主要包含以下几步;
按照主机控制器的要求组织结构体
将结构体在合适的时间、放在合适的地方
trigger
等待完成信号
在Linux内核中;用usb_hcd结构体描述USB主机控制器驱动;它包含USB主机控制器的“家务”信息、硬件资源、状态描述和用于操作主机控制器的hc_driver等。
struct usb_hcd { /* 管理“家务” */ struct usb_bus self; const char *product_desc; /* 产品/厂商字符串 */ char irq_descr[24]; /* 驱动 ; 总线 # */ struct timer_list rh_timer; /* 根Hub轮询 */ struct urb *status_urb; /* 目前的状态urb */ /* 硬件信息/状态 */ const struct hc_driver *driver; /* 硬件特定的钩子函数 */ /* 需要维护的标志 */ unsigned long flags; #define HCD_FLAG_HW_ACCESSIBLE 0x00000001 #define HCD_FLAG_SAW_IRQ 0x00000002 unsigned rh_registered: 1; /* 根Hub注册? */ /* 下一个标志的采用只是“权益之计”;当所有HCDs支持新的根Hub轮询机制后将移除 */ unsigned uses_new_polling: 1; unsigned poll_rh: 1; /* 轮询根Hub状态? */ unsigned poll_pending: 1; /* 状态已经改变? */ int irq; /* 被分配的irq */ void _ _iomem *regs; /* 设备内存和I/O */ u64 rsrc_start; /* 内存和I/O资源开始位置 */ u64 rsrc_len; /* 内存和I/O资源长度 */ unsigned power_budget; /* mA, 0 = 无限制 */ #define HCD_BUFFER_POOLS 4 struct dma_pool *pool[HCD_BUFFER_POOLS]; int state; #define _ _ACTIVE 0x01 #define _ _SUSPEND 0x04 #define _ _TRANSIENT 0x80 #define HC_STATE_HALT 0 #define HC_STATE_RUNNING (_ _ACTIVE) #define HC_STATE_QUIESCING (_ _SUSPEND|_ _TRANSIENT|_ _ACTIVE) #define HC_STATE_RESUMING (_ _SUSPEND|_ _TRANSIENT) #define HC_STATE_SUSPENDED (_ _SUSPEND) #define HC_IS_RUNNING(state) ((state) & _ _ACTIVE) #define HC_IS_SUSPENDED(state) ((state) & _ _SUSPEND) /* 主机控制器驱动的私有数据 */ unsigned long hcd_priv[0] _ _attribute_ _((aligned(sizeof(unsigned long)))); };
usb_hcd结构体中的hc_driver成员非常重要;它包含具体的用于操作主机控制器的钩子函数;即“hw-specific hooks”
struct hc_driver { const char *description; /* ;ehci-hcd; 等 */ const char *product_desc; /* 产品/厂商字符串 */ size_t hcd_priv_size; /* 私有数据的大小 */ /* 中断处理函数 */ irqreturn_t(*irq)(struct usb_hcd *hcd, struct pt_regs *regs); int flags; #define HCD_MEMORY 0x0001 /* HC寄存器使用的内存和I/O */ #define HCD_USB11 0x0010 /* USB 1.1 */ #define HCD_USB2 0x0020 /* USB 2.0 */ /* 被调用以初始化HCD和根Hub */ int(*reset)(struct usb_hcd *hcd); int(*start)(struct usb_hcd *hcd); /* 挂起Hub后;进入D3(etc)前被调用 */ int(*suspend)(struct usb_hcd *hcd, pm_message_t message); /* 在进入D0(etc)后;恢复Hub前调用 */ int(*resume)(struct usb_hcd *hcd); /* 使HCD停止写内存和进行I/O操作 */ void(*stop)(struct usb_hcd *hcd); /* 返回目前的帧数 */ int(*get_frame_number)(struct usb_hcd *hcd); /* 管理I/O请求和设备状态 */ int(*urb_enqueue)(struct usb_hcd *hcd, struct usb_host_endpoint *ep, struct urb *urb, gfp_t mem_flags); int(*urb_dequeue)(struct usb_hcd *hcd, struct urb *urb); /* 释放endpoint资源 */ void(*endpoint_disable)(struct usb_hcd *hcd, struct usb_host_endpoint *ep); /* 根Hub支持 */ int(*hub_status_data)(struct usb_hcd *hcd, char *buf); int(*hub_control)(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength); int(*bus_suspend)(struct usb_hcd*); int(*bus_resume)(struct usb_hcd*); int(*start_port_reset)(struct usb_hcd *, unsigned port_num); void(*hub_irq_enable)(struct usb_hcd*); };
在Linux内核中;使用如下函数来创建HCD:
struct usb_hcd *usb_creat_hcd(const struct hc_driver *driver, struct device *dev, char *bus_name) ;
使用如下函数来增加和移除HCD:
int usb_add_hcd(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irflags);
void usb_remove_hcd(struct usb_hcd *hcd);
urb_enqueue()函数非常关键;实际上;上层通过usb_submit_urb()提交一个usb请求后;该函数调用usb_hcd_submit_urb();并最终调用至usb_hcd的driver成员;hc_driver类型的urb_enqueue;。
EHCI主机控制器驱动;负责将usb_core发来的URB传输请求转化成HC可识别的格式并启动HC传输,直至完成传输。整个usb subsystem只有该驱动直接操作硬件寄存器。该层还支持其他不同的host controller driver;比如UHCI;OHCI等。
EHCI UCD驱动属于HCD驱动的实例;它定义了一个ehci_hcd结构体;通常作为usb_hcd结构体的私有数据;hcd_priv;;这个结构体的定义位于drivers/usb/host/ehci.h。usb host controller driver 只需关注host目录;结合core目录即可。
struct ehci_hcd { /* one per controller */ /* timing support */ enum ehci_hrtimer_event next_hrtimer_event; unsigned enabled_hrtimer_events; ktime_t hr_timeouts[EHCI_HRTIMER_NUM_EVENTS]; struct hrtimer hrtimer; int PSS_poll_count; int ASS_poll_count; int died_poll_count; /* glue to PCI and HCD Framework */ struct ehci_caps __iomem *caps; struct ehci_regs __iomem *regs; struct ehci_dbg_port __iomem *debug; __u32 hcs_params; /* cached register copy */ spinlock_t lock; enum ehci_rh_state rh_state; /* general schedule support */ bool scanning:1; bool need_rescan:1; bool intr_unlinking:1; bool iaa_in_progress:1; bool async_unlinking:1; bool shutdown:1; struct ehci_qh *qh_scan_next; /* async schedule support */ struct ehci_qh *async; struct ehci_qh *dummy; /* For AMD quirk use */ struct list_head async_unlink; struct list_head async_idle; unsigned async_unlink_cycle; unsigned async_count; /* async activity count */ __hc32 old_current; /* Test for QH becoming */ __hc32 old_token; /* inactive during unlink */ /* periodic schedule support */ #define DEFAULT_I_TDPS 1024 /* some HCs can do less */ unsigned periodic_size; __hc32 *periodic; /* hw periodic table */ dma_addr_t periodic_dma; struct list_head intr_qh_list; unsigned i_thresh; /* uframes HC might cache */ union ehci_shadow *pshadow; /* mirror hw periodic table */ struct list_head intr_unlink_wait; struct list_head intr_unlink; unsigned intr_unlink_wait_cycle; unsigned intr_unlink_cycle; unsigned now_frame; /* frame from HC hardware */ unsigned last_iso_frame; /* last frame scanned for iso */ unsigned intr_count; /* intr activity count */ unsigned isoc_count; /* isoc activity count */ unsigned periodic_count; /* periodic activity count */ unsigned uframe_periodic_max; /* max periodic time per uframe */ /* list of itds & sitds completed while now_frame was still active */ struct list_head cached_itd_list; struct ehci_itd *last_itd_to_free; struct list_head cached_sitd_list; struct ehci_sitd *last_sitd_to_free; /* per root hub port */ unsigned long reset_done[EHCI_MAX_ROOT_PORTS];//记录已经reset的port /* bit vectors (one bit per port) */ unsigned long bus_suspended; /* which ports were already suspended at the start of a bus suspend */ unsigned long companion_ports; /* which ports are dedicated to the companion controller */ unsigned long owned_ports; /* which ports are owned by the companion during a bus suspend */ unsigned long port_c_suspend; /* which ports have the change-suspend feature turned on */ unsigned long suspended_ports; /* which ports are suspended */ unsigned long resuming_ports; /* which ports have started to resume */ /* per-HC memory pools (could be per-bus, but ...) */ struct dma_pool *qh_pool; /* qh per active urb */ struct dma_pool *qtd_pool; /* one or more per qh */ struct dma_pool *itd_pool; /* itd per iso urb */ struct dma_pool *sitd_pool; /* sitd per split iso urb */ unsigned random_frame; unsigned long next_statechange; ktime_t last_periodic_enable; u32 command; /* SILICON QUIRKS */ unsigned no_selective_suspend:1; unsigned has_fsl_port_bug:1; /* FreeScale */ unsigned has_fsl_hs_errata:1; /* Freescale HS quirk */ unsigned big_endian_mmio:1; unsigned big_endian_desc:1; unsigned big_endian_capbase:1; unsigned has_amcc_usb23:1; unsigned need_io_watchdog:1; unsigned amd_pll_fix:1; unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/ unsigned has_synopsys_hc_bug:1; /* Synopsys HC */ unsigned frame_index_bug:1; /* MosChip (AKA NetMos) */ unsigned need_oc_pp_cycle:1; /* MPC834X port power */ unsigned imx28_write_fix:1; /* For Freescale i.MX28 */ /* required for usb32 quirk */ #define OHCI_CTRL_HCFS (3 << 6) #define OHCI_USB_OPER (2 << 6) #define OHCI_USB_SUSPEND (3 << 6) #define OHCI_HCCTRL_OFFSET 0x4 #define OHCI_HCCTRL_LEN 0x4 __hc32 *ohci_hcctrl_reg; unsigned has_hostpc:1; unsigned has_tdi_phy_lpm:1; unsigned has_ppcd:1; /* support per-port change bits */ u8 sbrn; /* packed release number */ /* irq statistics */ #ifdef EHCI_STATS struct ehci_stats stats; # define COUNT(x) ((x);;) #else # define COUNT(x) #endif /* debug files */ #ifdef CONFIG_DYNAMIC_DEBUG struct dentry *debug_dir; #endif /* bandwidth usage */ #define EHCI_BANDWIDTH_SIZE 64 #define EHCI_BANDWIDTH_FRAMES (EHCI_BANDWIDTH_SIZE >> 3) u8 bandwidth[EHCI_BANDWIDTH_SIZE]; /* us allocated per uframe */ u8 tt_budget[EHCI_BANDWIDTH_SIZE]; /* us budgeted per uframe */ struct list_head tt_list; /* platform-specific data -- must come last */ unsigned long priv[0] __aligned(sizeof(s64)); };
使用如下内联函数可实现usb_hcd和ehci_hcd的相互转换;
struct ehci_hcd *hcd_to_ehci(struct usb_hcd *hcd);
struct usb_hcd *ehci_to_usb(const struct ehci_hcd *ehci);
从usb_hcd得到ehci_hcd只是获得了私有数据;而从ehci_hcd得到usb_hcd则是通过container_of从结构体成员获得结构体指针。
使用如下函数可初始化EHCI主机控制器;
static int ehci_init(struct usb_hcd *hcd);
如下函数可分别用于开启、停止及复位EHCI控制器;
static int ehci_run(struct usb_hcd *hcd);
static void ehci_stop(struct usb_hcd *hcd);
static int ehci_reset(struct ehci_hcd *hcd);
上述函数在drivers/usb/host/ehci-hcd.c文件中被填充给了一个hc_driver结构体的实例ehci_hc_driver。
static const struct hc_driver ehci_hc_driver = { ... .reset = ehci_reset, .start = ehci_run, .stop = ehci_stop, .shutdown = ehci_shutdown, }
drivers/usb/host/ehci-hcd.c实现了绝大多数的EHCI主机驱动工作;调用
void ehci_init_driver(struct hc_driver *drv, const struct ehci_driver_overrides *over); 初始化hc_driver即可。
这里所说的USB设备驱动是从USB主机侧的角度看;怎么访问被插入的USB设备;而不是指USB设备内部本身运行的固件程序。Linux系统实现了几类通用的USB设备驱动;也称客户驱动;;划分为如下几个 设备类;
音频设备类;
通信设备类;
HID;人机接口;设备类;
显示设备类;
海量存储设备类;
电源设备类;
打印设备类;
集线器设备类;
一般通用的Linux设备;如U盘、USB鼠标 、USB键盘等;都不需要工程师再编写驱动;而工程师需要编写的是特定厂商、特定芯片的驱动;而且往往也可以参考已经在内核中提供的驱动模板。
在Linux内核中使用usb_driver结构体描述一个USB设备驱动。
struct usb_driver {const char *name; int (*probe) (struct usb_interface *intf, const struct usb_device_id *id); void (*disconnect) (struct usb_interface *intf); int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code, void *buf); int (*suspend) (struct usb_interface *intf, pm_message_t message); int (*resume) (struct usb_interface *intf); int (*reset_resume)(struct usb_interface *intf); int (*pre_reset)(struct usb_interface *intf); int (*post_reset)(struct usb_interface *intf); const struct usb_device_id *id_table; struct usb_dynids dynids; struct usbdrv_wrap drvwrap; unsigned int no_dynamic_id:1; unsigned int supports_autosuspend:1; unsigned int disable_hub_initiated_lpm:1; unsigned int soft_unbind:1; };
在编写新的USB设备驱动时;主要应该完成的工作是probe()和disconnect()函数;即探测和断开函数。他们分别在设备被插入和拔出的时候被调用;用于初始化和释放软硬件资源;对usb_driver的注册与注销可通过下面两个函数完成。
int usb_register(struct usb_driver *new_driver);
void usb_deregister(struct usb_driver *driver);
usb_driver本身只是有找到USB设备、管理USB设备连接和断开的作用。
USB请求块;USB Request Block;URB;是USB设备驱动中用来描述与USB设备通信所用的基本载体和核心数据结构。
struct urb { /* 私有的;只能由USB 核心和主机控制器访问的字段 */ struct kref kref; /*urb 引用计数 */ void *hcpriv; /* 主机控制器私有数据 */ atomic_t use_count; /* 并发传输计数 */ u8 reject; /* 传输将失败*/ int unlink; /* unlink 错误码 */ /* 公共的; 可以被驱动使用的字段 */ struct list_head urb_list; /* 链表头*/ struct usb_anchor *anchor; struct usb_device *dev; /* 关联的USB 设备 */ struct usb_host_endpoint *ep; unsigned int pipe; /* 管道信息 */ int status; /* URB 的当前状态 */ unsigned int transfer_flags; /* URB_SHORT_NOT_OK | ...*/ void *transfer_buffer; /* 发送数据到设备或从设备接收数据的缓冲区 */ dma_addr_t transfer_dma; /*用来以DMA 方式向设备传输数据的缓冲区 */ int transfer_buffer_length;/*transfer_buffer 或transfer_dma 指向缓冲区的大小 */ int actual_length; /* URB 结束后;发送或接收数据的实际长度 */ unsigned char *setup_packet; /* 指向控制URB 的设置数据包的指针*/ dma_addr_t setup_dma; /*控制URB 的设置数据包的DMA 缓冲区*/ int start_frame; /*等时传输中用于设置或返回初始帧*/ int number_of_packets; /*等时传输中等时缓冲区数量 */ int interval; /* URB 被轮询到的时间间隔;对中断和等时urb 有效; */ int error_count; /* 等时传输错误数量 */ void *context; /* completion 函数上下文 */ usb_complete_t complete; /* 当URB 被完全传输或发生错误时;被调用 */ /*单个URB 一次可定义多个等时传输时;描述各个等时传输 */ struct usb_iso_packet_descriptor iso_frame_desc[0]; };
USB 设备中的每个端点都处理一个urb 队列;在队列被清空之前;一个urb 的典型生命周期如下。
1;被一个USB设备驱动创建(创建URB)。
创建urb 结构体的函数为;
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
iso_packets 是这个urb 应当包含的等时数据包的数目;若为0 表示不创建等时数据包。
mem_flags 参数是分配内存的标志;和kmalloc()函数的分配标志参数含义相同。如果分配成功;该函数返回一个urb 结构体指针;否则返回0。
urb 结构体在驱动中不能静态创建;因为这可能破坏USB 核心给urb 使用的引用计数方法。
usb_alloc_urb()的“反函数”为;
void usb_free_urb(struct urb *urb);
该函数用于释放由usb_alloc_urb()分配的urb 结构体。
2;初始化;被安排给一个特定的USB设备的特定端点(填充URB)。
对于中断urb;使用usb_fill_int_urb()函数来初始化urb;如下所示;
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, void *context, int interval);
urb 参数指向要被初始化的urb 的指针;dev 指向这个urb 要被发送到的USB 设备;pipe 是这个urb 要被发送到的USB 设备的特定端点;transfer_buffer 是指向发送数据或接收数据的缓冲区的指针;和urb 一样;它也不能是静态缓冲区;必须使用kmalloc()来分配;buffer_length 是transfer_buffer 指针所指向缓冲区的大小;complete 指针指向当这个 urb 完成时被调用的完成处理函数;context 是完成处理函数的“上下文”;interval 是这个urb 应当被调度的间隔。
3) 被USB设备驱动提交给USB核心(提交URB)。
在完成创建和初始化urb 后;urb 便可以提交给USB 核心;通过usb_submit_urb()函数来完成;如下所示;
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
urb 参数是指向urb 的指针;mem_flags 参数与传递给kmalloc()函数参数的意义相同;它用于告知USB 核心如何在此时分配内存缓冲区。在提交urb 到USB 核心后;直到完成函数被调用之前;不要访问urb 中的任何成员。
usb_submit_urb()在原子上下文和进程上下文中都可以被调用;mem_flags 变量需根据调用环境进行相应的设置;如下所示。
GFP_ATOMIC;在中断处理函数、底半部、tasklet、定时器处理函数以及urb 完成函数中;在调用者持有自旋锁或者读写锁时以及当驱动将current→state 修改为非 TASK_RUNNING 时;应使用此标志。
GFP_NOIO;在存储设备的块I/O 和错误处理路径中;应使用此标志;
GFP_KERNEL;如果没有任何理由使用GFP_ATOMIC 和GFP_NOIO;就使用GFP_KERNEL。
如果usb_submit_urb()调用成功;即urb 的控制权被移交给USB 核心;该函数返回0;否则;返回错误号。
4;提交由USB核心指定的USB主机控制器驱动。
5;被USB主机控制器处理;进行一次到USB设备的传送。
第4;5;步由USB核心和USB主机控制器完成;不受USB设备驱动的控制。
6;当URB完成;USB主机控制器驱动通知USB设备驱动(处理URB)。
7;URB的取消。
如果想取消之前提交的URB;可以用usb_unlink_urb来实现;
int usb_unlink_urb(struct urb *urb);
用前面的方式提交urb或取消urb时;程序不会阻塞;属于异步方式。除了异步方式外;usb还可用同步方式来提交和取消urb。有时USB驱动程序只是从USB设备上接收或向USB设备发送一些简单的数据;这时候;没有必要将urb创建、初始化、提交、完成处理的整个流程走一遍;而可以使用两个更简单的函数;如下所示。
(1) usb_bulk_msg()函数
usb_bulk_msg()函数创建一个USB批量urb 并将它发送到特定设备;这个函数是同步的;它一直等待urb完成后才返回。usb_bulk_msg()函数的原型为;
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,void *data, int len, int *actual_length,
int timeout);
usb_dev参数为批量消息要发送的USB 设备的指针;pipe为批量消息要发送到的USB设备的端点;data参数为指向要发送或接收的数据缓冲区的指针;len参数为data参数所指向的缓冲区的长度;actual_length用于返回实际发送或接收的字节数;timeout是发送超时;以jiffies为单位;0意味着永远等待。
如果函数调用成功;返回0;否则;返回1个负的错误值。
(2) usb_control_msg()函数
usb_control_msg()函数与usb_bulk_msg()函数类似;不过它提供驱动发送和结束USB控制信息而非批量信息的能力;该函数的原型为;
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
__u8 requesttype, __u16 value, __u16 index, void *data, __u16 size, int timeout);
dev指向控制消息发往的USB设备;pipe是控制消息要发往的USB设备的端点;request是这个控制消息的USB请求值;requesttype是这个控制消息的USB请求类型;value是这个控制消息的USB消息值;index是这个控制消息的USB消息索引值;data指向要发送或接收的数据缓冲区;size是data参数所指向的缓冲区的大小;timeout是发送超时;以jiffies为单位;0意味着永远等待。
参数request、requesttype、value和index与USB规范中定义的USB控制消息直接对应。
如果函数调用成功;该函数返回发送到设备或从设备接收到的字节数;否则;返回一个负的错误值。
对usb_bulk_msg()和usb_control_msg()函数的使用要特别慎重;由于它们是同步的;因此不能在中断上下文和持有自旋锁的情况下使用。而且;该函数也不能被任何其他函数取消;因此;务必要使得驱动程序的disconnect()函数掌握足够的信息;以判断和等待该调用的结束。
在USB 设备驱动usb_driver 结构体的探测函数probe()中;应该完成如下工作;
探测设备的端点地址、缓冲区大小;初始化任何可能用于控制USB设备的数据结构。
把已初始化的数据结构的指针保存到接口设备中。
注册USB设备。
如果是简单的字符设备;可调用usb_register_dev();这个函数原型为;
int usb_register_dev(struct usb_interface *intf, struct usb_class_driver *class_driver);
在USB 设备驱动usb_driver 结构体的disconnect()函数中;应该完成如下工作;
释放所有为设备分配的资源。
设置接口设备的数据指针为NULL。
注销USB设备。
如果是简单的字符设备;可调用usb_register_dev()的“反函数”;这个函数原型为;
int usb_deregister_dev(struct usb_interface *intf, struct usb_class_driver *class_driver);
在Linux内核的开源代码中的drivers/usb/usb-skeleton.c文件为我们提供了一个最基本的USB驱动程序;即USB骨架程序。尽管具体的USB设备驱动程序千差万别;但是其骨架万变不离其宗。
首先看USB骨架程序的usb_driver结构体;定义如下;
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, .supports_autosuspend = 1, };
上面代码中.id_table = skel_table定义了该驱动程序支持的设备列表数组skel_table[];代码如下;
static const struct usb_device_id skel_table[] = {{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, skel_table);
MODULE_DEVICE_TABLE 宏定义作用详解->MODULE_DEVICE_TABLE
usb_driver的注册与注销发生在USB骨架程序的模块加载与卸载函数内;分别调用了usb_register()和usb_deregister()函数。
这两个函数使用快捷宏 module_usb_driver 实现;该宏在 include/linux/usb.h 中;
#define usb_register(driver) usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME) void usb_deregister(struct usb_driver *driver) {pr_info(%s: deregistering interface driver %sn;, usbcore_name, driver->name); usb_remove_newid_files(driver); driver_unregister(&driver->drvwrap.driver); usb_free_dynids(driver); } EXPORT_SYMBOL_GPL(usb_deregister); #define module_driver(__driver, __register, __unregister, ...) static int __init __driver##_init(void) { return __register(&(__driver) , ##__VA_ARGS__); } module_init(__driver##_init); static void __exit __driver##_exit(void) { __unregister(&(__driver) , ##__VA_ARGS__); } module_exit(__driver##_exit); #define module_usb_driver(__usb_driver) module_driver(__usb_driver, usb_register, usb_deregister)
USB的骨架程序的模块加载代码如下;
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, .supports_autosuspend = 1, }; module_usb_driver(skel_driver);
下面开始讲解USB骨架程序中的probe()函数;在usb_driver的usb_probe()成员函数中;根据 usb_interface 成员寻找第一个批量输入和批量输出端点;并将端点地址、缓冲区等信息存入为USB骨架程序定义的 usb_skel 结构中并将 usb_skel 实例指针传入 usb_set_intfdata() 中以作为USB接口的私有数据;最后注册USB设备;代码清单如下;
static int skel_probe(struct usb_interface *interface, const struct usb_device_id *id) {…… /* allocate memory for our device state and initialize it */ dev = kzalloc(sizeof(*dev), GFP_KERNEL); …… 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; if (!dev->bulk_in_endpointAddr && usb_endpoint_is_bulk_in(endpoint)) {/* we found a bulk in endpoint */ buffer_size = usb_endpoint_maxp(endpoint); 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); 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 endpointsn;); goto error; } /* save our data pointer in this interface device */ usb_set_intfdata(interface, dev); /* we can register the device now, as it is ready */ retval = usb_register_dev(interface, &skel_class); …… return 0; …… }
usb_skel 结构体可以被看作一个私有数据结构;代码定义如下;
/* Structure to hold all of our device specific stuff */ struct usb_skel {struct usb_device *udev; /* the usb device for this device */ struct usb_interface *interface; /* the interface for this device */ struct semaphore limit_sem; /* limiting the number of writes in progress */ struct usb_anchor submitted; /* in case we need to retract our submissions */ struct urb *bulk_in_urb; /* the urb to read data with */ unsigned char *bulk_in_buffer; /* the buffer to receive data */ size_t bulk_in_size; /* the size of the receive buffer */ size_t bulk_in_filled; /* number of bytes in the buffer */ size_t bulk_in_copied; /* already copied to user space */ __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */ __u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */ int errors; /* the last request tanked */ bool ongoing_read; /* a read is going on */ spinlock_t err_lock; /* lock for errors */ struct kref kref; struct mutex io_mutex; /* synchronize I/O with disconnect */ wait_queue_head_t bulk_in_wait; /* to wait for an ongoing read */ };
断开函数会完成与probe() 相反的工作;设置接口数据为NULL;注销USB设备;代码如下;
static void skel_disconnect(struct usb_interface *interface) {struct usb_skel *dev; int minor = interface->minor; dev = usb_get_intfdata(interface); usb_set_intfdata(interface, NULL); /* give back our minor */ usb_deregister_dev(interface, &skel_class); /* prevent more I/O from starting */ mutex_lock(&dev->io_mutex); dev->interface = NULL; mutex_unlock(&dev->io_mutex); usb_kill_anchored_urbs(&dev->submitted); /* decrement our usage count */ kref_put(&dev->kref, skel_delete); dev_info(&interface->dev, ;USB Skeleton #%d now disconnected;, minor); }
usb_probe()函数中的 usb_register_dev(interface, &skel_class)中的第二个参数包含字符设备的 file_operation 结构体指针;而这个结构体的成员也是USB字符设备的另一个组成部分。代码如下;
static const struct file_operations skel_fops = {.owner = THIS_MODULE, .read = skel_read, .write = skel_write, .open = skel_open, .release = skel_release, .flush = skel_flush, .llseek = noop_llseek, };
由于只是一个象征性的骨架架构;open()成员函数的实现非常简单;它根据 usb_driver 和次设备号通过 usb_find_interface() 获得USB接口;之后通过 usb_get_intfdata()获得接口的私有数据并赋予 file->private_data,代码如下;
static int skel_open(struct inode *inode, struct file *file) {struct usb_skel *dev; struct usb_interface *interface; int subminor; int retval = 0; subminor = iminor(inode); interface = usb_find_interface(&skel_driver, subminor); …… dev = usb_get_intfdata(interface); …… retval = usb_autopm_get_interface(interface); if (retval) goto exit; /* increment our usage count for the device */ kref_get(&dev->kref); /* save our object in the file;s private structure */ file->private_data = dev; exit: return retval; }
由于skel_open()中并未申请资源;所以skel_release()函数只需要减少一些引用计数即可。
接下来要分析的是读写函数;前面已经提到;在访问USB设备的时候;贯穿其中的“中枢神经”是URB结构体。
在skel_write()函数中进行的关于URB的操作即进行了URB的分配;调用usb_alloc_urb();;初始化(调用usb_fill_bluk_urb())和提交;调用usb_submit_urb();;代码如下;
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); dev = file->private_data; …… spin_lock_irq(&dev->err_lock); retval = dev->errors; …… spin_unlock_irq(&dev->err_lock); …… /* create a urb, and a buffer for it, and copy the data to the urb */ urb = usb_alloc_urb(0, GFP_KERNEL); …… buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL, &urb->transfer_dma); …… 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); …… /* initialize the urb properly */ 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); /* send the data out the bulk port */ retval = usb_submit_urb(urb, GFP_KERNEL); mutex_unlock(&dev->io_mutex); …… usb_free_urb(urb); return writesize; …… }
在写函数中发起的URB结束后;在 usb_fill_bulk_urb() 函数中填入的完成函数 skel_write_bulk_callback() 将会被调用;进行urb->status的判断;代码如下;
static void skel_write_bulk_callback(struct urb *urb) {struct usb_skel *dev; dev = urb->context; /* sync/async unlink faults aren;t errors */ if (urb->status) {if (!(urb->status == -ENOENT || urb->status == -ECONNRESET || urb->status == -ESHUTDOWN)) dev_err(&dev->interface->dev, %s - nonzero write bulk status received: %dn;, __func__, urb->status); spin_lock(&dev->err_lock); dev->errors = urb->status; spin_unlock(&dev->err_lock); } /* free up our allocated buffer */ usb_free_coherent(urb->dev, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma); up(&dev->limit_sem); }
在Linux系统中;键盘被认定为标准输入设备;对于USB键盘;驱动主要由两部分组成;usb_driver;USB外设驱动;的成员函数和输入设备驱动的input_event获取和报告。
USB键盘设备驱动的模块加载和卸载函数;分别注册和注销对应于USB键盘的usb_driver结构体usb_kbd_driver;如下代码清单为模块加载与卸载函数以及usb_driver结构体的定义。
static struct usb_device_id usb_kbd_id_table [] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_KEYBOARD) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, usb_kbd_id_table); static struct usb_driver usb_kbd_driver = { .name = ;usbkbd;, .probe = usb_kbd_probe, .disconnect = usb_kbd_disconnect, .id_table = usb_kbd_id_table, }; module_usb_driver(usb_kbd_driver);
在usb_driver的probe()函数中;将进行输入设备的初始化和注册;USB键盘要使用的中断URB和控制URB的初始化;并设置接口的私有数据;如下代码清单所示。
static int usb_kbd_probe(struct usb_interface *iface, const struct usb_device_id *id) { struct usb_device *dev = interface_to_usbdev(iface); struct usb_host_interface *interface; struct usb_endpoint_descriptor *endpoint; struct usb_kbd *kbd; struct input_dev *input_dev; // 输入设备 int i, pipe, maxp; int error = -ENOMEM; interface = iface->cur_altsetting; if (interface->desc.bNumEndpoints != 1) return -ENODEV; endpoint = &interface->endpoint[0].desc; if (!usb_endpoint_is_int_in(endpoint)) return -ENODEV; pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); // 分配内存 input_dev = input_allocate_device(); // 动态初始化input_dev if (!kbd || !input_dev) goto fail1; /* 分配内存 */ if (usb_kbd_alloc_mem(dev, kbd)) goto fail2; kbd->usbdev = dev; kbd->dev = input_dev; spin_lock_init(&kbd->leds_lock); if (dev->manufacturer) strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name)); if (dev->product) { if (dev->manufacturer) strlcat(kbd->name, ; ;, sizeof(kbd->name)); strlcat(kbd->name, dev->product, sizeof(kbd->name)); } if (!strlen(kbd->name)) snprintf(kbd->name, sizeof(kbd->name), ;USB HIDBP Keyboard %04x:%04x;, le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); usb_make_path(dev, kbd->phys, sizeof(kbd->phys)); strlcat(kbd->phys, ;/input0;, sizeof(kbd->phys)); /* 输入设备初始化 */ input_dev->name = kbd->name; input_dev->phys = kbd->phys; usb_to_input_id(dev, &input_dev->id); input_dev->dev.parent = &iface->dev; input_set_drvdata(input_dev, kbd); input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) | BIT_MASK(EV_REP); input_dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) | BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) | BIT_MASK(LED_KANA); for (i = 0; i < 255; i;;) set_bit(usb_kbd_keycode[i], input_dev->keybit); clear_bit(0, input_dev->keybit); input_dev->event = usb_kbd_event; input_dev->open = usb_kbd_open; input_dev->close = usb_kbd_close; /* 中断urb初始化 */ usb_fill_int_urb(kbd->irq, dev, pipe, kbd->new, (maxp > 8 ? 8 : maxp), usb_kbd_irq, kbd, endpoint->bInterval); kbd->irq->transfer_dma = kbd->new_dma; kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE; kbd->cr->bRequest = 0x09; kbd->cr->wValue = cpu_to_le16(0x200); kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber); kbd->cr->wLength = cpu_to_le16(1); /* 控制urb初始化 */ usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0), (void *) kbd->cr, kbd->leds, 1, usb_kbd_led, kbd); kbd->led->transfer_dma = kbd->leds_dma; kbd->led->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* 注册输入设备 */ error = input_register_device(kbd->dev); if (error) goto fail2; usb_set_intfdata(iface, kbd); /* 设置接口私有数据 */ device_set_wakeup_enable(&dev->dev, 1); return 0; fail2: usb_kbd_free_mem(dev, kbd); fail1: input_free_device(input_dev); kfree(kbd); return error; }
usb_driver的断开函数;设置接口私有数据为NULL、终止已提交的URB、注销输入设备;如下代码清单所示。
static void usb_kbd_disconnect(struct usb_interface *intf) { struct usb_kbd *kbd = usb_get_intfdata (intf); usb_set_intfdata(intf, NULL); /* 设置接口私有数据为NULL */ if (kbd) { usb_kill_urb(kbd->irq); /* 终止已提交的URB */ input_unregister_device(kbd->dev); /* 注销输入设备 */ usb_kill_urb(kbd->led); /* 终止已提交的URB */ usb_kbd_free_mem(interface_to_usbdev(intf), kbd); /*释放内存*/ kfree(kbd); } }
键盘主要依赖于中断传输模式;在键盘中断URB的完成函数usb_kbd_irq()中;通过input_report_key()报告按键事件;通过input_sync()报告同步事件;如下代码清单所示。
static void usb_kbd_irq(struct urb *urb) { struct usb_kbd *kbd = urb->context; int i; switch (urb->status) { case 0: /* success */ break; case -ECONNRESET: /* unlink */ case -ENOENT: case -ESHUTDOWN: return; /* -EPIPE: should clear the halt */ default: /* error */ goto resubmit; } /* 报告按键事件 */ for (i = 0; i < 8; i;;) input_report_key(kbd->dev, usb_kbd_keycode[i ; 224], (kbd->new[0] >> i) & 1); for (i = 2; i < 8; i;;) { if (kbd->old[i] > 3 && memscan(kbd->new ; 2, kbd->old[i], 6) == kbd->new ; 8) { if (usb_kbd_keycode[kbd->old[i]]) input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0); else hid_info(urb->dev, ;Unknown key (scancode %#x) released.n;, kbd->old[i]); } if (kbd->new[i] > 3 && memscan(kbd->old ; 2, kbd->new[i], 6) == kbd->old ; 8) { if (usb_kbd_keycode[kbd->new[i]]) input_report_key(kbd->dev, usb_kbd_keycode[kbd->new[i]], 1); else hid_info(urb->dev, ;Unknown key (scancode %#x) pressed.n;, kbd->new[i]); } } /* 报告同步事件 */ input_sync(kbd->dev); memcpy(kbd->old, kbd->new, 8); resubmit: i = usb_submit_urb (urb, GFP_ATOMIC); if (i) hid_err(urb->dev, ;can;t resubmit intr, %s-%s/input0, status %d;, kbd->usbdev->bus->bus_name, kbd->usbdev->devpath, i); }
从USB键盘驱动实例中;进一步看到usb_driver本身只是起一个挂接总线的作用;具体设备类型的驱动仍然是工作的主体;例如键盘就是input、USB串口就是tty;只是在设备底层进行硬件访问时;调用的都是与URB;USB请求块;相关的接口;这套USB核心层API——URB的存在使得无须关心底层USB主机控制器的具体细节;因此;USB设备驱动变得与平台无关;同样的驱动可适用于不同的SoC。
USB设备控制器驱动指的是作为其他USB主机控制器外设的USB硬件设备上底层硬件控制器的驱动;该硬件和驱动负责将一个USB设备依附于一个USB主机控制器上;例如;当某运行Linux系统的手机作为PC的U盘时;手机中的底层USB控制器行使USB设备控制器的功能;这时运行在底层的是UDC驱动;手机要成为U盘;在UDC驱动之上仍然需要另外一个驱动;对于USB大容量存储器而言;这个驱动为File Storage驱动;称为Function驱动。USB设备驱动调用USB核心的API;具体驱动与SoC无关;Function驱动调用通用的Gadget Function API;具体Function驱动也与SoC无关。
为了与主机端设备驱动的USB Device Driver概念进行区分;将在外围器件中运行的驱动程序称为USB Gadget Driver。其中;Host端的设备驱动程序是master;设备端gadget driver是slave或者function driver。
Gadget Driver和USB Host端驱动程序类似;都是使用请求队列来对I/O包进行缓冲;这些请求可以被提交和取消。同时也是通过bind和unbind将driver与device建立关系。
UDC驱动和Function驱动都位于内核的drivers/usb/gadget目录;如drivers/usb/gadget/udc下面的at91_udc.c、omap_udc.c、s3c2410_udc.c等是对应SoC平台上的UDC驱动;drivers/usb/gadget/function目录下的f_serial.c、f_mass_storage.c、f_rndis.c等文件实现了一些Gadget功能;重要的Function驱动如下所示。
Ethernet over USB;该驱动模拟以太网网口;支持多种运行方式——CDC Ethernet;实现标准Communications Device Class ;Ethernet Model;协议;、CDC Subset以及RNDIS;微软公司对CDC Ethernet的变种实现;。
File-Backed Storage Gadget;最常见的U盘功能实现。
Serial Gadget;包括Generic Serial实现;只需要Bulk-in/Bulk-out端点;ep0;和CDC ACM规范实现。
Gadget MIDI;音乐设备数字接口;;暴露ALSA MIDI接口。
USB Video Class Gadget驱动;让Linux系统成为另外一个系统的USB视频采集源。
另外;drivers/usb/gadget源代码还实现一个Gadget文件系统;GadgetFS;;将Gadget API接口暴露给应用层;以便在应用层实现用户空间的驱动。
USB器件控制器驱动;需要关心几个核心的数据结构;包括描述一个USB器件控制器的usb_gadget、UDC操作usb_gadget_ops、描述一个端点的usb_ep以及描述端点操作的usb_ep_ops结构体等。UDC驱动围绕这些数据结构及其成员函数展开;下面列出这些关键的数据结构;路径;include/linux/usb/gadget.h。
struct usb_gadget {struct work_struct work; /* readonly to gadget driver */ const struct usb_gadget_ops *ops; struct usb_ep *ep0; struct list_head ep_list; /* of usb_ep */ enum usb_device_speed speed; enum usb_device_speed max_speed; enum usb_device_state state; const char *name; struct device dev; unsigned out_epnum; unsigned in_epnum; unsigned sg_supported:1; unsigned is_otg:1; unsigned is_a_peripheral:1; unsigned b_hnp_enable:1; unsigned a_hnp_support:1; unsigned a_alt_hnp_support:1; unsigned quirk_ep_out_aligned_size:1; bool remote_wakeup; u32 xfer_isr_count; u8 usb_core_id; bool l1_supported; bool bam2bam_func_enabled; u32 extra_buf_alloc; int interrupt_num; }; struct usb_ep {void *driver_data; const char *name; const struct usb_ep_ops *ops; struct list_head ep_list; unsigned maxpacket:16; unsigned maxpacket_limit:16; unsigned max_streams:16; unsigned mult:2; unsigned maxburst:5; u8 address; const struct usb_endpoint_descriptor *desc; const struct usb_ss_ep_comp_descriptor *comp_desc; enum ep_type ep_type; u8 ep_num; u8 ep_intr_num; bool endless; }; struct usb_gadget_ops {int (*get_frame)(struct usb_gadget *); int (*wakeup)(struct usb_gadget *); int (*func_wakeup)(struct usb_gadget *, int interface_id); int (*set_selfpowered) (struct usb_gadget *, int is_selfpowered); int (*vbus_session) (struct usb_gadget *, int is_active); int (*vbus_draw) (struct usb_gadget *, unsigned mA); int (*pullup) (struct usb_gadget *, int is_on); int (*restart)(struct usb_gadget *); int (*ioctl)(struct usb_gadget *, unsigned code, unsigned long param); void (*get_config_params)(struct usb_dcd_config_params *); int (*udc_start)(struct usb_gadget *, struct usb_gadget_driver *); int (*udc_stop)(struct usb_gadget *, struct usb_gadget_driver *); }; struct usb_ep_ops {int (*enable) (struct usb_ep *ep, const struct usb_endpoint_descriptor *desc); int (*disable) (struct usb_ep *ep); struct usb_request *(*alloc_request) (struct usb_ep *ep, gfp_t gfp_flags); void (*free_request) (struct usb_ep *ep, struct usb_request *req); int (*queue) (struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags); int (*dequeue) (struct usb_ep *ep, struct usb_request *req); int (*set_halt) (struct usb_ep *ep, int value); int (*set_wedge) (struct usb_ep *ep); int (*fifo_status) (struct usb_ep *ep); void (*fifo_flush) (struct usb_ep *ep); int (*gsi_ep_op)(struct usb_ep *ep, void *op_data, enum gsi_ep_op op); };
在具体的UDC驱动中;需要封装usb_gadget和每个端点usb_ep;实现usb_gadget的usb_gadget_ops并实现端点的usb_ep_ops;完成usb_request。这些事情搞定后;注册一个UDC;它是通过usb_add_gadget_udc()API来进行的;其原型为;
include/linux/usb/gadget.h
int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget);
注册UDC;USB设备控制器;之前;需要先把usb_gadget这个结构体的ep_list;端点链表;填充好;并填充好usb_gadget的usb_gadget_ops以及每个端点的usb_gadget_ops。
而Gadget的Function;需要自己填充usb_interface_descriptor、usb_endpoint_descriptor;合成一些
usb_descriptor_header;并实现usb_function结构体的成员函数。usb_function结构体定义于
include/linux/usb/composite.h中;其形式如代码清单如下所示。
struct usb_function {const char *name; int intf_id; struct usb_gadget_strings **strings; struct usb_descriptor_header **fs_descriptors; struct usb_descriptor_header **hs_descriptors; struct usb_descriptor_header **ss_descriptors; struct usb_configuration *config; struct usb_os_desc_table *os_desc_table; unsigned os_desc_n; /* REVISIT: bind() functions can be marked __init, which * makes trouble for section mismatch analysis. See if * we can;t restructure things to avoid mismatching. * Related: unbind() may kfree() but bind() won;t... */ /* configuration management: bind/unbind */ int (*bind)(struct usb_configuration *, struct usb_function *); void (*unbind)(struct usb_configuration *, struct usb_function *); void (*free_func)(struct usb_function *f); struct module *mod; /* runtime state management */ int (*set_alt)(struct usb_function *, unsigned interface, unsigned alt); int (*get_alt)(struct usb_function *, unsigned interface); void (*disable)(struct usb_function *); int (*setup)(struct usb_function *, const struct usb_ctrlrequest *); void (*suspend)(struct usb_function *); void (*resume)(struct usb_function *); /* USB 3.0 additions */ int (*get_status)(struct usb_function *); int (*func_suspend)(struct usb_function *, u8 suspend_opt); unsigned func_is_suspended:1; unsigned func_wakeup_allowed:1; unsigned func_wakeup_pending:1; /* private: */ /* internals */ struct list_head list; DECLARE_BITMAP(endpoints, 32); const struct usb_function_instance *fi; };
fs_descriptors是全速和低速的描述符表;hs_descriptors是高速描述符表;ss_descriptors是超高速描述符表。bind;;完成在Gadget注册时获取I/O缓冲、端点等资源。
在usb_function的成员函数以及各种描述符准备好后;内核通过usb_function_register;;API来完成Gadget Function的注册;该API的原型为;
include/linux/usb/composite.h
int usb_function_register(struct usb_function_driver *newf);
在Gadget驱动中;用usb_request结构体来描述一次传输请求;描述一个I/O请求;;这个结构体的地位类似于USB主机侧的URB。usb_request结构体的定义如下。
struct usb_request {void *buf; unsigned length; dma_addr_t dma; struct scatterlist *sg; unsigned num_sgs; unsigned num_mapped_sgs; unsigned stream_id:16; unsigned no_interrupt:1; unsigned zero:1; unsigned short_not_ok:1; unsigned dma_pre_mapped:1; void (*complete)(struct usb_ep *ep, struct usb_request *req); void *context; struct list_head list; int status; unsigned actual; unsigned udc_priv; };
在include/linux/usb/gadget.h文件中;还封装了一些常用的API;以供Gadget Function驱动调用;从而便于它们操作端点。
USB Gadget driver对象。
struct usb_gadget_driver {char *function; //驱动名称 enum usb_device_speed speed; //USB设备速度类型 int (*bind)(struct usb_gadget *); //将驱动和设备绑定;一般在驱动注册时调用 void (*unbind)(struct usb_gadget *);//卸载驱动时调用;rmmod时调用 int (*setup)(struct usb_gadget *, const struct usb_ctrlrequest *); //处理ep0的控制请求;在中断中调用;不能睡眠 void (*disconnect)(struct usb_gadget *); //可能在中断中调用不能睡眠 void (*suspend)(struct usb_gadget *); //电源管理模式相关;设备挂起 void (*resume)(struct usb_gadget *);//电源管理模式相关;设备恢复 /* FIXME support safe rmmod */ struct device_driver driver; //内核设备管理使用 };
字符串结构。
struct usb_gadget_strings {u16 language; /* 0x0409 for en-us */ struct usb_string *strings; }; struct usb_string {u8 id; //索引 const char *s; };
UDC驱动程序需要实现的上层调用接口
int usb_gadget_register_driver(struct usb_gadget_driver *driver);
int usb_gadget_unregister_driver(struct usb_gadget_driver *driver);
UDC层主要数据结构;以S3C2410为例;在driver/usb/gadget/s3c2410_udc.c和s3c2410_udc.h文件中。下面的结构基本上每个UDC驱动程序都会实现;但具体实现的细节又不太相同。但万变不离其宗;宗就是上面介绍的基本gadget驱动数据结构;基本上UDC驱动程序自己实现的数据结构都是都这些基本数据结构的二次封装。
a; 设备结构
struct s3c2410_udc { spinlock_t lock; struct s3c2410_ep ep[S3C2410_ENDPOINTS]; int address; struct usb_gadget gadget; struct usb_gadget_driver *driver; struct s3c2410_request fifo_req; u8 fifo_buf[EP_FIFO_SIZE]; u16 devstatus; u32 port_status; int ep0state; unsigned got_irq : 1; unsigned req_std : 1; unsigned req_config : 1; unsigned req_pending : 1; u8 vbus; struct dentry *regs_info; };
程序中对这个结构的初始化;
static struct s3c2410_udc memory = {.gadget = { .ops = &s3c2410_ops, .ep0 = &memory.ep[0].ep, .name = gadget_name, .dev = {.init_name = ;gadget;, }, }, /* control endpoint */ .ep[0] = { //struct s3c2410_ep .num = 0, .ep = {//struct usb_ep .name = ep0name, .ops = &s3c2410_ep_ops, .maxpacket = EP0_FIFO_SIZE, }, .dev = &memory, }, /* first group of endpoints */ .ep[1] = { .num = 1, .ep = {.name = ;ep1-bulk;, .ops = &s3c2410_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .fifo_size = EP_FIFO_SIZE, .bEndpointAddress = 1, .bmAttributes = USB_ENDPOINT_XFER_BULK, }, .ep[2] = { .num = 2, .ep = { .name = ;ep2-bulk;, .ops = &s3c2410_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .fifo_size = EP_FIFO_SIZE, .bEndpointAddress = 2, .bmAttributes = USB_ENDPOINT_XFER_BULK, }, .ep[3] = {.num = 3, .ep = { .name = ;ep3-bulk;, .ops = &s3c2410_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .fifo_size = EP_FIFO_SIZE, .bEndpointAddress = 3, .bmAttributes = USB_ENDPOINT_XFER_BULK, }, .ep[4] = { .num = 4, .ep = { .name = ;ep4-bulk;, .ops = &s3c2410_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .fifo_size = EP_FIFO_SIZE, .bEndpointAddress = 4, .bmAttributes = USB_ENDPOINT_XFER_BULK, } };
不同的UDC;自定义的数据结构不同。但一般都有这样一个数据结构来表示UDC设备;对usb_gadget设备对象进行封装;并包含设备的所有端点。
b; 端点结构
struct s3c2410_ep {struct list_head queue; unsigned long last_io; /* jiffies timestamp */ struct usb_gadget *gadget; struct s3c2410_udc *dev; const struct usb_endpoint_descriptor *desc; struct usb_ep ep; //封装的struct usb_ep结构 u8 num; unsigned short fifo_size; u8 bEndpointAddress; u8 bmAttributes; unsigned halted : 1; unsigned already_seen : 1; unsigned setup_stage : 1; };
对usb_ep结构进行封装;并有一个queue队列来对该端口上的request进行排队。
c; Request结构
struct s3c2410_request { struct list_head queue; /* ep;s requests */ struct usb_request req; };
对usb_request进行封装;queue变量进行队列排队。
1; UDC驱动是作为platform driver向platform子系统注册的;因此UDC驱动首先就需要实现struct platform_driver结构中的函数成员;
struct platform_driver { int (*probe)(struct platform_device *); //驱动和设备绑定 int (*remove)(struct platform_device *); //支持热插拔的设备移除 void (*shutdown)(struct platform_device *); //设备关闭 int (*suspend)(struct platform_device *, pm_message_t state); //电源管理相关;挂起设备 int (*resume)(struct platform_device *); //电源管理相关;恢复设备 struct device_driver driver; struct platform_device_id *id_table; //驱动和设备匹配信息 };
在下面的源码分析中以s3c2410_udc.c文件为例;
static struct platform_driver udc_driver_2410 = { .driver = { .name = ;s3c2410-usbgadget;, .owner = THIS_MODULE, }, .probe = s3c2410_udc_probe, .remove = s3c2410_udc_remove, .suspend = s3c2410_udc_suspend, .resume = s3c2410_udc_resume, };
其中以s3c2410_udc_probe和s3c2410_udc_remove函数最为重要;s3c2410_udc_probe函数实现驱动和设备的匹配绑定;并分配资源;而s3c2410_udc_remove函数实现资源的释放。
static int s3c2410_udc_probe(struct platform_device *pdev) { struct s3c2410_udc *udc = &memory; //s3c2410的UDC设备;在其中对usb_gadget设备对象和端点等进行了初始化 struct device *dev = &pdev->dev; int retval; int irq; //获取总线时钟并使能 usb_bus_clock = clk_get(NULL, ;usb-bus-gadget;); clk_enable(usb_bus_clock); //获取设备时钟并使能 udc_clock = clk_get(NULL, ;usb-device;); clk_enable(udc_clock); mdelay(10); spin_lock_init (&udc->lock); //初始化设备的自旋锁 udc_info = pdev->dev.platform_data; rsrc_start = S3C2410_PA_USBDEV; //s3c2410 UDC端口起始地址 rsrc_len = S3C24XX_SZ_USBDEV; //s3c2410端口地址长度 if (!request_mem_region(rsrc_start, rsrc_len, gadget_name)) //申请端口资源 return -EBUSY; base_addr = ioremap(rsrc_start, rsrc_len); //端口映射 if (!base_addr) { retval = -ENOMEM; goto err_mem; } device_initialize(&udc->gadget.dev); //初始化device设备对象 udc->gadget.dev.parent = &pdev->dev; //当前UDC设备的父设备对象是platform_device udc->gadget.dev.dma_mask = pdev->dev.dma_mask; the_controller = udc; platform_set_drvdata(pdev, udc); //驱动和设备绑定;在platform_device结构中保存udc设备对象 /*重新初始化设备*/ s3c2410_udc_disable(udc); s3c2410_udc_reinit(udc); /* irq setup after old hardware state is cleaned up */ /*申请中断;并绑定中断函数;中断函数是UDC功能驱动的入口函数*/ retval = request_irq(IRQ_USBD, s3c2410_udc_irq, IRQF_DISABLED, gadget_name, udc); if (udc_info && udc_info->vbus_pin > 0) { retval = gpio_request(udc_info->vbus_pin, ;udc vbus;); irq = gpio_to_irq(udc_info->vbus_pin); retval = request_irq(irq, s3c2410_udc_vbus_irq, IRQF_DISABLED | IRQF_TRIGGER_RISING| IRQF_TRIGGER_FALLING | IRQF_SHARED,gadget_name, udc); } else { udc->vbus = 1; } if (s3c2410_udc_debugfs_root) { //创建虚拟文件debugfs udc->regs_info = debugfs_create_file(;registers;, S_IRUGO, s3c2410_udc_debugfs_root,udc, &s3c2410_udc_debugfs_fops); if (!udc->regs_info) dev_warn(dev, ;debugfs file creation failedn;); } dev_dbg(dev, ;probe okn;); return 0; err_gpio_claim: if (udc_info && udc_info->vbus_pin > 0) gpio_free(udc_info->vbus_pin); err_int: free_irq(IRQ_USBD, udc); err_map: iounmap(base_addr); err_mem: release_mem_region(rsrc_start, rsrc_len); return retval; }
从s3c2410_udc_probe函数可以看出;probe函数主要完成的就是将platform_device设备对象和UDC设备对象建立关系;UDC设备和驱动的一些初始化工作;并申请驱动所需的资源;若端口区间、中断号等;并将中断函数和中断号绑定。这个中断处理函数非常重要;对这个设备的所有操作都将从中断函数入口。
static int s3c2410_udc_remove(struct platform_device *pdev) { struct s3c2410_udc *udc = platform_get_drvdata(pdev); //获取UDC设备对象;在probe函数中绑定的 unsigned int irq; if (udc->driver) //设备的驱动usb_gadget_driver对象;说明设备正在使用 return -EBUSY; debugfs_remove(udc->regs_info); //移除debugfs文件系统中建立的文件 if (udc_info && udc_info->vbus_pin > 0) { irq = gpio_to_irq(udc_info->vbus_pin); free_irq(irq, udc); } free_irq(IRQ_USBD, udc); //释放中断 /*释放端口资源*/ iounmap(base_addr); release_mem_region(rsrc_start, rsrc_len); /*解除绑定*/ platform_set_drvdata(pdev, NULL); /*释放时钟*/ if (!IS_ERR(udc_clock) && udc_clock != NULL) {clk_disable(udc_clock); clk_put(udc_clock); udc_clock = NULL; } if (!IS_ERR(usb_bus_clock) && usb_bus_clock != NULL) {clk_disable(usb_bus_clock); clk_put(usb_bus_clock); usb_bus_clock = NULL; } dev_dbg(&pdev->dev, %s: remove okn;, __func__); return 0; }
可以看出;remove函数基本上是probe函数的逆操作;将probe函数中申请的资源释放掉。
2; UDC驱动程序还需要为上层实现usb_gadget_register_driver和usb_gadget_unregister_driver两个gadget driver注册接口;这两个函数将实现gadget driver和udc driver绑定。
int usb_gadget_register_driver(struct usb_gadget_driver *driver) { struct s3c2410_udc *udc = the_controller; //UDC设备对象 int retval; /* Sanity checks */ if (!udc) return -ENODEV; /*UDC设备只能绑定一个gadget driver对象*/ if (udc->driver) return -EBUSY; /*检查gadget driver是否实现了绑定函数、setup函数。同时低速设备也不支持gadget driver*/ if (!driver->bind || !driver->setup|| driver->speed < USB_SPEED_FULL) { printk(KERN_ERR ;Invalid driver: bind %p setup %p speed %dn;,driver->bind, driver->setup, driver->speed); return -EINVAL; } //支持卸载的话;还需要实现unbind函数 #if defined(MODULE) if (!driver->unbind) { printk(KERN_ERR ;Invalid driver: no unbind methodn;); return -EINVAL; } #endif /* Hook the driver */ /*将gadget driver和udc设备绑定*/ udc->driver = driver; udc->gadget.dev.driver = &driver->driver; /* Bind the driver */ if ((retval = device_add(&udc->gadget.dev)) != 0) { //完成gadget设备在内核中的注册 printk(KERN_ERR ;Error in device_add() : %dn;,retval); goto register_error; } if ((retval = driver->bind (&udc->gadget)) != 0) {//gadget驱动绑定函数 device_del(&udc->gadget.dev); goto register_error; } /* Enable udc */ //使能UDC设备 s3c2410_udc_enable(udc); return 0; register_error: udc->driver = NULL; udc->gadget.dev.driver = NULL; return retval; } /*gadget 驱动注销函数*/ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) { struct s3c2410_udc *udc = the_controller; if (!udc) return -ENODEV; /*驱动必须和注册时的驱动是一致的;同时实现了unbind函数*/ if (!driver || driver != udc->driver || !driver->unbind) return -EINVAL; dprintk(DEBUG_NORMAL,;usb_gadget_register_driver() %s;n;, driver->driver.name); /*调用gadget driver实现的unbind函数*/ driver->unbind(&udc->gadget); device_del(&udc->gadget.dev); //和register函数中的device_add对应 udc->driver = NULL;//这个很重要;这里说明gadget驱动注销之后;才能移除udc设备 /* Disable udc */ /*关闭设备*/ s3c2410_udc_disable(udc); return 0; }
3; 中断函数
中断处理函数是UDC驱动层的核心函数;由于UDC是从设备;主机端是控制端;所有的操作都是主机端发起的;所以中断处理函数是UDC驱动层的核心函数。
static irqreturn_t s3c2410_udc_irq(int dummy, void *_dev) { struct s3c2410_udc *dev = _dev; //UDC设备对象 int usb_status; int usbd_status; int pwr_reg; int ep0csr; int i; u32 idx; unsigned long flags; spin_lock_irqsave(&dev->lock, flags); /* Driver connected ? */ if (!dev->driver) { //还没有和驱动绑定;清除中断 /* Clear interrupts */ udc_write(udc_read(S3C2410_UDC_USB_INT_REG), S3C2410_UDC_USB_INT_REG); udc_write(udc_read(S3C2410_UDC_EP_INT_REG),S3C2410_UDC_EP_INT_REG); } /* Save index */ idx = udc_read(S3C2410_UDC_INDEX_REG); //这是哪个端点产生中断的索引号 /* Read status registers */ usb_status = udc_read(S3C2410_UDC_USB_INT_REG); //UDC设备状态 usbd_status = udc_read(S3C2410_UDC_EP_INT_REG); //UDC产生中断的端点状态 pwr_reg = udc_read(S3C2410_UDC_PWR_REG); udc_writeb(base_addr, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); ep0csr = udc_read(S3C2410_UDC_IN_CSR1_REG); dprintk(DEBUG_NORMAL, ;usbs=%02x, usbds=%02x, pwr=%02x ep0csr=%02xn;, usb_status, usbd_status, pwr_reg, ep0csr); /* 开始中断的实际处理;这里的中断只有两种类型; 1. UDC设备中断; 重置、挂起和恢复 2. 端点中断 */ /* UDC设备RESET操作处理 */ if (usb_status & S3C2410_UDC_USBINT_RESET) { //Reset中断 /* two kind of reset : * - reset start -> pwr reg = 8 * - reset end -> pwr reg = 0 **/ dprintk(DEBUG_NORMAL, ;USB reset csr %x pwr %xn;, ep0csr, pwr_reg); dev->gadget.speed = USB_SPEED_UNKNOWN; udc_write(0x00, S3C2410_UDC_INDEX_REG); udc_write((dev->ep[0].ep.maxpacket & 0x7ff) >> 3, S3C2410_UDC_MAXP_REG); dev->address = 0; dev->ep0state = EP0_IDLE; dev->gadget.speed = USB_SPEED_FULL; /* clear interrupt */ udc_write(S3C2410_UDC_USBINT_RESET, S3C2410_UDC_USB_INT_REG); udc_write(idx, S3C2410_UDC_INDEX_REG); spin_unlock_irqrestore(&dev->lock, flags); return IRQ_HANDLED; } /* UDC设备RESUME操作处理 */ if (usb_status & S3C2410_UDC_USBINT_RESUME) { //Resume中断 dprintk(DEBUG_NORMAL, ;USB resumen;); /* clear interrupt */ udc_write(S3C2410_UDC_USBINT_RESUME, S3C2410_UDC_USB_INT_REG); if (dev->gadget.speed != USB_SPEED_UNKNOWN && dev->driver&& dev->driver->resume)//调用resume函数 dev->driver->resume(&dev->gadget); } /* UDC设备SUSPEND操作处理 */ if (usb_status & S3C2410_UDC_USBINT_SUSPEND) { //Suspend中断 dprintk(DEBUG_NORMAL, ;USB suspendn;); /* clear interrupt */ udc_write(S3C2410_UDC_USBINT_SUSPEND, S3C2410_UDC_USB_INT_REG); if (dev->gadget.speed != USB_SPEED_UNKNOWN && dev->driver&& dev->driver->suspend)//调用suspend函数 dev->driver->suspend(&dev->gadget); dev->ep0state = EP0_IDLE; } /* 下面就是端点中断得处理 */ /* 首先是控制端点(端点0)的中断处理*/ /* check on ep0csr != 0 is not a good idea as clearing in_pkt_ready * generate an interrupt */ if (usbd_status & S3C2410_UDC_INT_EP0) { //端点0的中断 dprintk(DEBUG_VERBOSE, ;USB ep0 irqn;); /* Clear the interrupt bit by setting it to 1 */ udc_write(S3C2410_UDC_INT_EP0, S3C2410_UDC_EP_INT_REG); s3c2410_udc_handle_ep0(dev); //处理端点0 } /* 其他端点;就是数据传输的处理*/ for (i = 1; i < S3C2410_ENDPOINTS; i;;) { //遍历所有端点;找出中断的端点 u32 tmp = 1 << i; if (usbd_status & tmp) { dprintk(DEBUG_VERBOSE, ;USB ep%d irqn;, i); /* Clear the interrupt bit by setting it to 1 */ udc_write(tmp, S3C2410_UDC_EP_INT_REG); s3c2410_udc_handle_ep(&dev->ep[i]); //处理对应端点 } } dprintk(DEBUG_VERBOSE, ;irq: %d s3c2410_udc_done.n;, IRQ_USBD); /* Restore old index */ udc_write(idx, S3C2410_UDC_INDEX_REG); spin_unlock_irqrestore(&dev->lock, flags); return IRQ_HANDLED; }
4. 端点操作函数
端点操作函数是UDC驱动的基础;因为大部分的动作其实都是和端点相关的;如数据传输等。首先来看中断函数中涉及的两个函数;一个是端点0的处理函数;一个是其他端点的处理函数。
static void s3c2410_udc_handle_ep0(struct s3c2410_udc *dev) { u32 ep0csr; struct s3c2410_ep *ep = &dev->ep[0]; struct s3c2410_request *req; struct usb_ctrlrequest crq; if (list_empty(&ep->queue)) req = NULL; else req = list_entry(ep->queue.next, struct s3c2410_request, queue); /* We make the assumption that S3C2410_UDC_IN_CSR1_REG equal to * S3C2410_UDC_EP0_CSR_REG when index is zero */ udc_write(0, S3C2410_UDC_INDEX_REG); ep0csr = udc_read(S3C2410_UDC_IN_CSR1_REG); dprintk(DEBUG_NORMAL, ;ep0csr %x ep0state %sn;, ep0csr, ep0states[dev->ep0state]); /* clear stall status */ if (ep0csr & S3C2410_UDC_EP0_CSR_SENTSTL) { //清除STALL状态 s3c2410_udc_nuke(dev, ep, -EPIPE); dprintk(DEBUG_NORMAL, ;... clear SENT_STALL ...n;); s3c2410_udc_clear_ep0_sst(base_addr); dev->ep0state = EP0_IDLE; return; } /* clear setup end */ if (ep0csr & S3C2410_UDC_EP0_CSR_SE) { dprintk(DEBUG_NORMAL, ;... serviced SETUP_END ...n;); s3c2410_udc_nuke(dev, ep, 0); s3c2410_udc_clear_ep0_se(base_addr); dev->ep0state = EP0_IDLE; } /*端点0的状态处理*/ switch (dev->ep0state) { case EP0_IDLE: s3c2410_udc_handle_ep0_idle(dev, ep, &crq, ep0csr); //在这个函数中会调用上层提供的gadget_driver中实现的setup函数来处理控制数据包 break; case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR etc */ //向主机发送数据 dprintk(DEBUG_NORMAL, ;EP0_IN_DATA_PHASE ... what now?n;); if (!(ep0csr & S3C2410_UDC_EP0_CSR_IPKRDY) && req) { s3c2410_udc_write_fifo(ep, req); //写UDC FIFO } break; case EP0_OUT_DATA_PHASE: /* SET_DESCRIPTOR etc */ //从主机接收数据 dprintk(DEBUG_NORMAL, ;EP0_OUT_DATA_PHASE ... what now?n;); if ((ep0csr & S3C2410_UDC_EP0_CSR_OPKRDY) && req ) { s3c2410_udc_read_fifo(ep,req); } break; case EP0_END_XFER: dprintk(DEBUG_NORMAL, ;EP0_END_XFER ... what now?n;); dev->ep0state = EP0_IDLE; break; case EP0_STALL: dprintk(DEBUG_NORMAL, ;EP0_STALL ... what now?n;); dev->ep0state = EP0_IDLE; break; } }
/* * handle_ep - Manage I/O endpoints 其他端点的处理函数;主要是数据发送和接收 */ static void s3c2410_udc_handle_ep(struct s3c2410_ep *ep) { struct s3c2410_request *req; int is_in = ep->bEndpointAddress & USB_DIR_IN; u32 ep_csr1; u32 idx; if (likely (!list_empty(&ep->queue))) //取出申请 req = list_entry(ep->queue.next, struct s3c2410_request, queue); else req = NULL; idx = ep->bEndpointAddress & 0x7F; //端点地址 if (is_in) { //向主机发送数据 udc_write(idx, S3C2410_UDC_INDEX_REG); ep_csr1 = udc_read(S3C2410_UDC_IN_CSR1_REG); dprintk(DEBUG_VERBOSE, ;ep%01d write csr:%02x %dn;, idx, ep_csr1, req ? 1 : 0); if (ep_csr1 & S3C2410_UDC_ICSR1_SENTSTL) { dprintk(DEBUG_VERBOSE, ;stn;); udc_write(idx, S3C2410_UDC_INDEX_REG); udc_write(ep_csr1 & ~S3C2410_UDC_ICSR1_SENTSTL, S3C2410_UDC_IN_CSR1_REG); return; } if (!(ep_csr1 & S3C2410_UDC_ICSR1_PKTRDY) && req) { s3c2410_udc_write_fifo(ep,req); } } else //从主机接收数据 { udc_write(idx, S3C2410_UDC_INDEX_REG); ep_csr1 = udc_read(S3C2410_UDC_OUT_CSR1_REG); dprintk(DEBUG_VERBOSE, ;ep%01d rd csr:%02xn;, idx, ep_csr1); if (ep_csr1 & S3C2410_UDC_OCSR1_SENTSTL) { udc_write(idx, S3C2410_UDC_INDEX_REG); udc_write(ep_csr1 & ~S3C2410_UDC_OCSR1_SENTSTL, S3C2410_UDC_OUT_CSR1_REG); return; } if ((ep_csr1 & S3C2410_UDC_OCSR1_PKTRDY) && req) { s3c2410_udc_read_fifo(ep,req); } } }
端点操作函数集;端点的基本操作函数
static const struct usb_ep_ops s3c2410_ep_ops = {.enable = s3c2410_udc_ep_enable, //端点使能 .disable = s3c2410_udc_ep_disable, //关闭端点 .alloc_request = s3c2410_udc_alloc_request, //分配一个请求 .free_request = s3c2410_udc_free_request, //释放请求 .queue = s3c2410_udc_queue, //向端点提交一个请求 .dequeue = s3c2410_udc_dequeue, //从端点请求队列中删除一个请求 .set_halt = s3c2410_udc_set_halt, };
主要分析queue这个函数;因为上层主要和这个函数打交道;接收或发送数据都需要对应的端点队列提交请求
static int s3c2410_udc_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) { struct s3c2410_request *req = to_s3c2410_req(_req); struct s3c2410_ep *ep = to_s3c2410_ep(_ep); struct s3c2410_udc *dev; u32 ep_csr = 0; int fifo_count = 0; unsigned long flags; if (unlikely (!_ep || (!ep->desc && ep->ep.name != ep0name))) { dprintk(DEBUG_NORMAL, %s: invalid argsn;, __func__); return -EINVAL; } dev = ep->dev; if (unlikely (!dev->driver|| dev->gadget.speed == USB_SPEED_UNKNOWN)) { return -ESHUTDOWN; } local_irq_save (flags); if (unlikely(!_req || !_req->complete || !_req->buf || !list_empty(&req->queue))) { if (!_req) dprintk(DEBUG_NORMAL, %s: 1 X X Xn;, __func__); else {dprintk(DEBUG_NORMAL, %s: 0 %01d %01d %01dn;, __func__, !_req->complete,!_req->buf, !list_empty(&req->queue)); } local_irq_restore(flags); return -EINVAL; } _req->status = -EINPROGRESS; _req->actual = 0; dprintk(DEBUG_VERBOSE, %s: ep%x len %dn;, __func__, ep->bEndpointAddress, _req->length); if (ep->bEndpointAddress) { //设置端点号 udc_write(ep->bEndpointAddress & 0x7F, S3C2410_UDC_INDEX_REG); ep_csr = udc_read((ep->bEndpointAddress & USB_DIR_IN) ? S3C2410_UDC_IN_CSR1_REG: S3C2410_UDC_OUT_CSR1_REG); fifo_count = s3c2410_udc_fifo_count_out(); } else {udc_write(0, S3C2410_UDC_INDEX_REG); ep_csr = udc_read(S3C2410_UDC_IN_CSR1_REG); fifo_count = s3c2410_udc_fifo_count_out(); } /* kickstart this i/o queue? */ if (list_empty(&ep->queue) && !ep->halted) { //该端点队列为空;且没有关闭;则直接完成申请 if (ep->bEndpointAddress == 0 /* ep0 */) { //端点0 switch (dev->ep0state) { case EP0_IN_DATA_PHASE: if (!(ep_csr&S3C2410_UDC_EP0_CSR_IPKRDY) &&s3c2410_udc_write_fifo(ep,req)) { dev->ep0state = EP0_IDLE; req = NULL; } break; case EP0_OUT_DATA_PHASE: if ((!_req->length)|| ((ep_csr & S3C2410_UDC_OCSR1_PKTRDY)&& s3c2410_udc_read_fifo(ep,req))) { dev->ep0state = EP0_IDLE; req = NULL; } break; default: local_irq_restore(flags); return -EL2HLT; } } else if ((ep->bEndpointAddress & USB_DIR_IN) != 0 && (!(ep_csr&S3C2410_UDC_OCSR1_PKTRDY)) &&s3c2410_udc_write_fifo(ep, req)) { req = NULL; } else if ((ep_csr & S3C2410_UDC_OCSR1_PKTRDY)&& fifo_count&& s3c2410_udc_read_fifo(ep, req)) { req = NULL; } } /* pio or dma irq handler advances the queue. */ if (likely (req != 0)) list_add_tail(&req->queue, &ep->queue); //加入队列;等待中断处理 local_irq_restore(flags); dprintk(DEBUG_VERBOSE, %s okn;, __func__); return 0; }
UDC驱动中还有一个重要函数;在数据传输或接收完成;即一个请求完成之后;会调用这个请求的完成函数来通知上层驱动。
static void s3c2410_udc_done(struct s3c2410_ep *ep, struct s3c2410_request *req, int status) { unsigned halted = ep->halted; list_del_init(&req->queue); //将这个请求从端点请求队列中删除 if (likely (req->req.status == -EINPROGRESS)) req->req.status = status; //返回完成状态 else status = req->req.status; ep->halted = 1; req->req.complete(&ep->ep, &req->req); ep->halted = halted; }
总结;
UDC设备驱动层的源码就分析得差不多了;其他很多函数都是操作寄存器;与UDC设备密切相关;但总的来说完成的功能都是一致的。可以发现;在UDC设备驱动层主要需要做以下几个工作;
对usb_gadget、usb_ep、usb_request三个标准数据结构进行封装;根据自己UDC的一些设备特性;设计对应的自己的数据结构;
实现platform_driver数据结构中的函数;将UDC设备驱动向platform系统进行注册;
实现usb_gadget_ops函数集;这些函数主要是操作UDC设备的一些特性;针对设备;;
实现usb_ep_ops函数集;这些函数主要是操作端点的功能;如请求分配和提交等;针对端点;;
实现UDC设备的中断处理函数;这个函数基本上就是UDC设备驱动的核心;
实现上层功能驱动注册接口函数;
int usb_gadget_register_driver(struct usb_gadget_driver *driver)
int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
Gadget设备层
这一层是可选的;介于UDC驱动层和Gadget功能层之间。主要源码在composite.c和composite.h文件中;设备层其实和硬件无关;主要实现一些通用性的代码;减少gadget功能层的代码重复工作。Gadget设备层其中承上启下的作用;联系Gadget功能层和UDC驱动层。
将composite源码独立出来;还为复合设备的实现提供了一个通用性的框架。复合设备是指在一个配置描述符中支持多个功能;或者支持多个配置的设备中;每个配置都有一个不同的功能。如一个设备同时支持网络和存储;一个设备同时支持键盘和鼠标功能等。
Gadget设备层的主要数据结构;
1;Function
struct usb_function { //描述一个配置的一个功能 const char *name; //功能名称 struct usb_gadget_strings **strings; //string数组;通过bind中分配的id访问 struct usb_descriptor_header **descriptors; //全速和低速的描述符表;用于bind中分配的接口描述符和string描述符 struct usb_descriptor_header **hs_descriptors; //高速描述符表 struct usb_configuration *config;//调用usb_add_function()函数赋值;是这个功能关联的配置描述符 /* REVISIT: bind() functions can be marked __init, which * makes trouble for section mismatch analysis. See if * we can;t restructure things to avoid mismatching. * Related: unbind() may kfree() but bind() won;t... */ /* configuration management: bind/unbind */ /*在Gadget注册时;分配资源*/ int (*bind)(struct usb_configuration *, struct usb_function *); /*unbind的逆操作*/ void (*unbind)(struct usb_configuration *, struct usb_function *); /* runtime state management */ /*重新配置altsetting;*/ int (*set_alt)(struct usb_function *,unsigned interface, unsigned alt); /*获取当前altsetting*/ int (*get_alt)(struct usb_function *, unsigned interface); /*关闭功能*/ void (*disable)(struct usb_function *); /*接口相关的控制处理*/ int (*setup)(struct usb_function *, const struct usb_ctrlrequest *); /*电源管理相关的挂起和恢复功能*/ void (*suspend)(struct usb_function *); void (*resume)(struct usb_function *); /* private: */ /* internals */ struct list_head list; DECLARE_BITMAP(endpoints, 32); };
2;Config
struct usb_configuration { //表示一个Gadget配置 const char *label; //配置名称 struct usb_gadget_strings **strings; //字符串表 const struct usb_descriptor_header **descriptors; //功能描述符表 /* REVISIT: bind() functions can be marked __init, which * makes trouble for section mismatch analysis. See if * we can;t restructure things to avoid mismatching... */ /* configuration management: bind/unbind */ /*在usb_add_config函数中调用;分配资源等*/ int (*bind)(struct usb_configuration *); void (*unbind)(struct usb_configuration *); /*处理驱动框架不能处理的配置控制请求*/ int (*setup)(struct usb_configuration *, const struct usb_ctrlrequest *); /* fields in the config descriptor */ /*用来赋值配置描述符*/ u8 bConfigurationValue; u8 iConfiguration; u8 bmAttributes; u8 bMaxPower; /*和composite设备关联;在usb_add_config函数中设置*/ struct usb_composite_dev *cdev; /* private: */ /* internals */ struct list_head list; struct list_head functions; //功能链表 u8 next_interface_id; unsigned highspeed:1; unsigned fullspeed:1; struct usb_function *interface[MAX_CONFIG_INTERFACES]; };
3; Driver
struct usb_composite_driver {const char *name; //驱动名称 const struct usb_device_descriptor *dev; //设备描述符 struct usb_gadget_strings **strings; //字符串表 /* REVISIT: bind() functions can be marked __init, which * makes trouble for section mismatch analysis. See if * we can;t restructure things to avoid mismatching... */ int (*bind)(struct usb_composite_dev *); int (*unbind)(struct usb_composite_dev *); /* global suspend hooks */ void (*suspend)(struct usb_composite_dev *); void (*resume)(struct usb_composite_dev *); };
4; Dev
struct usb_composite_dev { //表示一个composite设备 struct usb_gadget *gadget; //关联的gadget struct usb_request *req; //用于控制响应;提前分配的 unsigned bufsiz; //req中提前分配的buffer长度 struct usb_configuration *config; //当前配置 /* private: */ /* internals */ struct usb_device_descriptor desc; struct list_head configs; //配置链表 struct usb_composite_driver *driver; u8 next_string_id; /* the gadget driver won;t enable the data pullup * while the deactivation count is nonzero. */ unsigned deactivations; /* protects at least deactivation count */ spinlock_t lock; };
Gadget功能层完成USB设备的具体功能;其表现的形式各不相同;如键盘、鼠标、存储和网卡等等。功能层不仅涉及到Gadget驱动相关的内容;还涉及到其功能相关的内核子系统。如存储还涉及到内核存储子系统;网卡还涉及到网络驱动子系统。因此;Gadget功能的代码非常复杂。这里以zero.c为例;这个模块只是简单地将接收的数据回显回去。
一、数据结构
首先需要实现usb_composite_driver函数集;
static struct usb_composite_driver zero_driver = { .name = ;zero;, .dev = &device_desc, .strings = dev_strings, .bind = zero_bind, .unbind = zero_unbind, .suspend = zero_suspend, .resume = zero_resume, };
二、主要函数
这个模块的实现就是这么简单;
static int __init init(void) { return usb_composite_register(&zero_driver); } module_init(init); static void __exit cleanup(void) { usb_composite_unregister(&zero_driver); }
Bind函数是功能层需要实现与设备层关联的重要函数;
static int __init zero_bind(struct usb_composite_dev *cdev) { int gcnum; struct usb_gadget *gadget = cdev->gadget; //Gadget设备 int id; /* Allocate string descriptor numbers ... note that string * contents can be overridden by the composite_dev glue. */ /*分配字符串描述符的id;并赋值给设备描述符中字符串索引*/ id = usb_string_id(cdev); strings_dev[STRING_MANUFACTURER_IDX].id = id; device_desc.iManufacturer = id; id = usb_string_id(cdev); i strings_dev[STRING_PRODUCT_IDX].id = id; device_desc.iProduct = id; id = usb_string_id(cdev); strings_dev[STRING_SERIAL_IDX].id = id; device_desc.iSerialNumber = id; /*设置挂起后;设备自动恢复的定时器*/ setup_timer(&autoresume_timer, zero_autoresume, (unsigned long) cdev); /*核心代码;实现功能*/ if (loopdefault) { loopback_add(cdev, autoresume != 0); //数据简单回显功能 if (!gadget_is_sh(gadget)) sourcesink_add(cdev, autoresume != 0); } else { sourcesink_add(cdev, autoresume != 0); if (!gadget_is_sh(gadget)) loopback_add(cdev, autoresume != 0); } /*初始化设备描述符*/ gcnum = usb_gadget_controller_number(gadget); if (gcnum >= 0) device_desc.bcdDevice = cpu_to_le16(0x0200 ; gcnum); else { device_desc.bcdDevice = cpu_to_le16(0x9999); } return 0; } /*增加数据简单回显功能*/ int __init loopback_add(struct usb_composite_dev *cdev, bool autoresume) { int id; /*获取字符串描述符id索引*/ id = usb_string_id(cdev); strings_loopback[0].id = id; loopback_intf.iInterface = id; loopback_driver.iConfiguration = id; /* support autoresume for remote wakeup testing */ if (autoresume) sourcesink_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; /* support OTG systems */ if (gadget_is_otg(cdev->gadget)) {loopback_driver.descriptors = otg_desc; loopback_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; } return usb_add_config(cdev, &loopback_driver); //增加一个配置 } /*loopback配置*/ static struct usb_configuration loopback_driver = { .label = ;loopback;, .strings = loopback_strings, .bind = loopback_bind_config, .bConfigurationValue = 2, .bmAttributes = USB_CONFIG_ATT_SELFPOWER, /* .iConfiguration = DYNAMIC */ }; 将增加配置的usb_add_config函数中会调用其bind函数;即loopback_bind_config函数;来分配这个配置所需要的资源。 struct f_loopback {struct usb_function function; struct usb_ep *in_ep; struct usb_ep *out_ep; }; static int __init loopback_bind_config(struct usb_configuration *c) { struct f_loopback *loop; int status; loop = kzalloc(sizeof *loop, GFP_KERNEL); //分配一个loop结构 if (!loop) return -ENOMEM; /*初始化一个功能*/ loop->function.name = ;loopback;; loop->function.descriptors = fs_loopback_descs; loop->function.bind = loopback_bind; loop->function.unbind = loopback_unbind; loop->function.set_alt = loopback_set_alt; loop->function.disable = loopback_disable; status = usb_add_function(c, &loop->function); //加入这个功能 if (status) kfree(loop); return status; }
在usb_add_function函数中;又会调用这个功能的bind函数;即loopback_bind函数;
static int __init loopback_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct f_loopback *loop = func_to_loop(f); int id; /* allocate interface ID(s) */ id = usb_interface_id(c, f); //分配一个接口id if (id < 0) return id; loopback_intf.bInterfaceNumber = id; /* allocate endpoints */ /*返回一个输入端点*/ loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_source_desc); if (!loop->in_ep) { autoconf_fail: ERROR(cdev, %s: can;t autoconfigure on %sn;, f->name, cdev->gadget->name); return -ENODEV; } loop->in_ep->driver_data = cdev; /* claim */ /*返回一个输出端点;返回合适的端点*/ loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc); if (!loop->out_ep) goto autoconf_fail; loop->out_ep->driver_data = cdev; /* claim */ /* support high speed hardware */ if (gadget_is_dualspeed(c->cdev->gadget)) { hs_loop_source_desc.bEndpointAddress = fs_loop_source_desc.bEndpointAddress; hs_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress; f->hs_descriptors = hs_loopback_descs; } DBG(cdev, %s speed %s: IN/%s, OUT/%sn;, gadget_is_dualspeed(c->cdev->gadget) ? ;dual; : ;full;,f->name, loop->in_ep->name, loop->out_ep->name); return 0; }
功能的实现
Loopback_set_alt函数将在设备层的setup函数中被调用;控制通信设置接口。
static int loopback_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct f_loopback *loop = func_to_loop(f); struct usb_composite_dev *cdev = f->config->cdev; /* we know alt is zero */ if (loop->in_ep->driver_data) disable_loopback(loop); return enable_loopback(cdev, loop); //开启功能 } static int enable_loopback(struct usb_composite_dev *cdev, struct f_loopback *loop) { int result = 0; const struct usb_endpoint_descriptor *src, *sink; struct usb_ep *ep; struct usb_request *req; unsigned i; /*选择端点描述符*/ src = ep_choose(cdev->gadget, &hs_loop_source_desc, &fs_loop_source_desc); sink = ep_choose(cdev->gadget, &hs_loop_sink_desc, &fs_loop_sink_desc); /* one endpoint writes data back IN to the host */ /*输入输出端点使能*/ ep = loop->in_ep; result = usb_ep_enable(ep, src); if (result < 0) return result; ep->driver_data = loop; /* one endpoint just reads OUT packets */ ep = loop->out_ep; result = usb_ep_enable(ep, sink); if (result < 0) { fail0: ep = loop->in_ep; usb_ep_disable(ep); ep->driver_data = NULL; return result; } ep->driver_data = loop; /* allocate a bunch of read buffers and queue them all at once. * we buffer at most ;qlen; transfers; fewer if any need more * than ;buflen; bytes each. */ /*qlen=32;分配32个请求;将这个请求放入输出端点队列;等待接收数据*/ for (i = 0; i < qlen && result == 0; i;;) { req = alloc_ep_req(ep); if (req) { req->complete = loopback_complete; result = usb_ep_queue(ep, req, GFP_ATOMIC); if (result) ERROR(cdev, %s queue req --> %dn;, ep->name, result); } else { usb_ep_disable(ep); ep->driver_data = NULL; result = -ENOMEM; goto fail0; } } DBG(cdev, %s enabledn;, loop->function.name); return result; } /*接收到数据之后;将调用这个完成函数*/ static void loopback_complete(struct usb_ep *ep, struct usb_request *req) { struct f_loopback *loop = ep->driver_data; struct usb_composite_dev *cdev = loop->function.config->cdev; int status = req->status; switch (status) { case 0: /* normal completion? */ if (ep == loop->out_ep) { //将接收到的数据放入输入端点;返回给主机 /* loop this OUT packet back IN to the host */ req->zero = (req->actual < req->length); req->length = req->actual; status = usb_ep_queue(loop->in_ep, req, GFP_ATOMIC); if (status == 0) return; /* ;should never get here; */ ERROR(cdev, ;can;t loop %s to %s: %dn;, ep->name, loop->in_ep->name,status); } /* queue the buffer for some later OUT packet */ req->length = buflen; //将输入端点完成的申请;重新放入输出队列;等待接收新的数据 status = usb_ep_queue(loop->out_ep, req, GFP_ATOMIC); if (status == 0) return; /* ;should never get here; */ /* FALLTHROUGH */ default: ERROR(cdev, %s loop complete --> %d, %d/%dn;, ep->name, status, req->actual, req->length); /* FALLTHROUGH */ /* NOTE: since this driver doesn;t maintain an explicit record * of requests it submitted (just maintains qlen count), we * rely on the hardware driver to clean up on disconnect or * endpoint disable. */ case -ECONNABORTED: /* hardware forced ep reset */ case -ECONNRESET: /* request dequeued */ case -ESHUTDOWN: /* disconnect from host */ free_ep_req(ep, req); return; } }
OTG是On-The-Go的缩写。其设计的初衷是为了两个“外设”在没有PC;Host;的情况下;也可以通过USB进行数据传输。可以理解为;拥有OTG功能的USB设备;OTG设备;既可以做host;也可以做peripheral。
USB驱动分为USB主机驱动和USB设备驱动;如果系统的USB主机控制器符合OHCI;Open Host Controller Interface;等标准;那主机驱动的绝大部分工作都可以沿用通用的代码。对于一个USB设备;USB设备至少具备两重身份;首先USB设备是“USB”的;其次USB设备是“自己”的。USB设备是“USB”的;指USB设备挂接在USB总线上;其必须完成usb_driver的初始化和注册;USB设备是“自己”的;意味着本身可能是一个字符设备、tty设备、网络设备等;在USB设备驱动中也必须实现符合相应框架的代码。
USB设备驱动的自身设备驱动部分的读写等操作流程有其特殊性;以URB;USB请求块;来贯穿始终;一个URB的生命周期通常包含创建、初始化、提交和被USB核心及USB主机传递、完成后回调函数被调用的过程;在URB被驱动提交后;也可以被取消。
在UDC;USB设备控制器;和Gadget Function;UDC驱动之上的;侧;UDC关心底层的硬件操作;而Function;File Storage;驱动则只是利用通用的API;并通过usb_request与底层UDC驱动交互。
在最初的标准里;USB接头有4条线;电源;D-,D;,地线。我们暂且把这样的叫做标准的USB接头吧。后来OTG出现了;又增加了miniUSB接头。而miniUSB接头则有5条线;多了一条ID线,用来标识身份用的。标准USB口只有A型和B型。其中每一型又分为插头和插座;例如A型插头;A型插座等。我们平常电脑上用的那种插座叫做A型USB插座;而相应的插头;叫做A型插头;例如U盘上那种。而像打印机上面那个插座;则是B型插座(比较四方的;没电脑上面那种扁);相应的插头;就是B型插头。也许你见过一头方一头扁的USB延长线;没错了;扁的那头就叫做A型插头;而方的那头;就叫做B型插头;而相应的被插的那两个插座;就分别是A型插座和B型插座了。A型插头是插不进B型插座的;反之亦然。
miniUSB也分为A型;B型;但增加了一个AB型。既然它叫做miniUSB,那么当然它就是很小的了;主要是给便携式设备用的;例如MP3、手机、数码相机等。USB是一主多从结构;即一个时刻只能有一台主机。像PC机就是一个主机;其它的只能是设备;因而两个设备之间是无法直接进行通信的。而USB OTG(on the go)的出现;则解决了这个矛盾;一个设备可以在某种场合下;改变身份;以主机的形式出现。因而就出现了AB型的miniUSB插座;不管是A型miniUSB插头;还是B型miniUSB插头;都可以插进去;而靠里面多出的那条ID线来识别它的身份;是主机还是从机。这样两个USB设备就可以直接连接起来;进行数据传送了。 像我们MP3上用的那中miniUSB插座;就是B型的miniUSB插座。由于USB是支持热插拔的;因此它在接头的设计上也有相应的措施。USB插头的地引脚和电源引脚比较长;而两个数据引脚则比较短;这样在插入到插座中时;首先接通电源和地;然后再接通两个数据线。这样就可以保证电源在数据线之前接通;防止闩锁发生。至于USB电缆;通常我们不怎么关心;买现成的就行了;除非你是生产USB线缆的。在全速模式下需要使用带屏蔽的双绞电缆线;而低速模式则可以不用屏蔽和双绞。此外;USB协议规定;USB低速电缆长度不得超过3米;而全速电缆长度不得超过5米。这是因为线缆传输有延迟;要保证能够正确响应;就不能延迟太多。USB标准规定了里面信号线的颜色;其中Vbus为红色;D-为白色;D;为绿色;GND为黑色。然而;我见过很多USB线缆并没有遵循标准;所以大家在使用时要小心;用表测量一下比较可靠。
首先;在USB集线器的每个下游端口的D;和D-上;分别接了一个15K欧姆的下拉电阻到地。这样;在集线器的端口悬空时;就被这两个下拉电阻拉到了低电平。而在USB设备端;在D;或者D-上接了1.5K欧姆上拉电阻。对于全速和高速设备;上拉电阻是接在D;上;而低速设备则是上拉电阻接在D-上。这样;当设备插入到集线器时;由1.5K的上拉电阻和15K的下拉电阻分压;结果就将差分数据线中的一条拉高了。集线器检测到这个状态后;它就报告给USB主控制器;这样就检测到设备的插入了。USB高速设备先是被识别为全速设备;然后通过HOST和DEVICE两者之间的确认;再切换到高速模式的。在高速模式下;是电流传输模式;这时将D;上的上拉电阻断开。
热插拔;hot-plugging或Hot Swap;即带电插拔;热插拔功能就是允许用户在不关闭系统;不切断电源的情况下取出和更换损坏的硬盘、电源或板卡等部件;从而提高了系统对灾难的及时恢复能力、扩展性和灵活性等。
系统中加入热插拔的好处包括;
在系统开机情况下将损坏的模块移除;还可以在开机情况下做更新或扩容而不影响系统操作。
由于热插拔零件的可靠度提升;还可以将它们用做断电器;而且因为热插拔能够自动恢复;有很多热插拔芯片为系统提供线路供电情况的信号;以便系统做故障分析;因此减少了成本。
在系统初始化的时候在usb_init函数中调用usb_hub_init函数;就进入了hub的初始化。在usb_hub_init函数中完成了注册hub驱动;并且利用函数kthread_run创建一个内核线程。该线程用来管理监视hub的状态;所有的情况都通过该线程来报告。
USB设备是热插拔;这就和PCI设备不同;PCI设备是在系统启动的时候都固定了;因此PCI设备只需要初始化进行枚举就可以了;采用递归算法即可。而USB设备需要热插拔;因此在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函数来配置端口等。
这里我们先讲讲USB热插拔事件的处理工作。—Khubd守护进程
Khubd是HUB的守护进程;用来检查usb port的事件通知HCD和usb core;然后做相应的处理。
在hub驱动安装OK后;系统会调用kthread_run(hub_thread, NULL, “khubd”)来启动守护进程的;khubd就是守护进程名称;这个进程几乎是个死循环;只有在执行kthread_should_stop时才会退出;而进程也不是时时都在执行的;当hub没有设备插入时;进程属于睡眠状态;只有当有设备插入时;才会唤醒进程;进行处理。
驱动目录drivers/usb/*
usb/serial usb 串行设备驱动 ;例如usb 3G卡、蓝牙等;
usb/storage usb 大储量磁盘驱动;u盘;
usb/host usb host usb主机控制器驱动;嵌入式otg;dwc_otg;
usb/core usb 核心一些处理代码;所有的驱动相关处理都在这里;也都注册到它里面。
usb/usb-skeleton.c 经典的usb客户驱动框架;可以参考。
当然还有其他这里不再说明;下面贴出USB的整体驱动框架;
这里我们主要分析khub的工作原理; 硬件层次是hub的工作;如何和host及其设备间通信及相应事件
[usb/core/hub.c ]
int usb_hub_init(void) {if (usb_register(&hub_driver) < 0) {printk(KERN_ERR %s: can;t register hub drivern;, 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 khubdn;, usbcore_name); return -1; }
这里我们只关心kthread_run(hub_thread, NULL, “khubd”); 然后我们看hub_thread函数。
static int hub_thread(void *__unused) {do {hub_events(); wait_event_interruptible(khubd_wait, !list_empty(&hub_event_list) || kthread_should_stop()); try_to_freeze(); } while (!kthread_should_stop() || !list_empty(&hub_event_list)); pr_debug(%s: khubd exitingn;, usbcore_name); return 0; }
这里我们看到了hub_events;;函数。然后设置运行状态;如果有事件就加入hub_event_list。自此khubd运行起来了。
通过流程图我们可以清晰的明白;当usb设备插入usb接口后;khubd运行;它检测到port状态的变化;调用hub_port_connect_change();如果是新设备那么usb_allco_dev;然后调用usb_new_device来进行配置使usb设备可以正常工作。