0


USB基础知识总结

1.USB基本概念介绍

USB (Universal Serial Bus,通用串行总线)是1995年英特尔和微软等公司联合倡导发起的一种新的 PC 串行通信协议。它基于通用连接技术,实现外设的简单快速连接,达到方便用户、降低成本、扩展 PC 连接外设范围的目的。其最大特点是支持热插拔和即插即用。最多可串接下127 个外设,它可以向低压设备提供5 伏电源,同时可以减少 PC 机 I/O 接口数量。USB出现之前,计算机领域中的接口太多太繁杂,USB出现之后减少了接口的种类,总的来说就是设计出了一个万能的接口,各种外设都能用同一种接口,所以才冠以“通用(是Universal)”为名。

1.1 USB常用术语

在进行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。

1.2 USB协议版本

  • USB 1.0(low speed),传输速率最大为1.5Mbps
  • USB 1.1(full speed),传输速率最大为12Mbps
  • USB 2.0(high speed),传输速率最大480Mbps
  • USB 3.0(super speed),传输速率最大5Gbps

在这里插入图片描述

1.3 USB 的电气特性

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- 的双绞线传输,它们各自使用半双工的差分信号并协同工作,以抵消长导线的电磁干扰。

1.4 USB 硬件接口

在这里插入图片描述
在这里插入图片描述

1.5 USB 的特点

  • 可以热插拔,即插即用。
  • 携带方便。USB 设备大多以“小、轻、薄”见长,对用户来说,随身携带大量数据时,很方便。当然 USB 硬盘是首要之选了。
  • 标准统一。大家常见的是 IDE 接口的硬盘,串口的鼠标键盘,并口的打印机扫描仪,可是有了 USB 之后,这些应用外设统统可以用同样的标准与个人电脑连接,这时就有了 USB 硬盘、USB 鼠标、USB 打印机等等。
  • 可以连接多个设备。USB 在个人电脑上往往具有多个接口,可以同时连接几个设备,如果接上一个有四个端口的 USB HUB 时,就可以再连上;四个 USB 设备,以此类推,尽可以连下去,将你家的设备都同时连在一台个人电脑上而不会有任何问题(注:最高可连接至 127 个设备)。

1.6 USB 的拓朴结构

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上的设备)连接。

1.7 USB 总线的总体结构

整个 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 或应用之间都是一个点对点的连接。

1.8 USB 传输方式

USB endpiont有四种类型,分别对应了不同的数据传输方式,分别为control transfers控制传输、interrupt transfers中断传输、Bluk Data transfers批量传输、Isochronous Data Tranfers等时传输。

  • 控制传输:控制传输是双向传输,数据量通常比较小,通常用于配置设备,获取设备信息,发送命令到设备。
  • 批量传输:主要应用在数据大量传输,同时又没有带宽和间隔时间要求的情况下,进行可靠传输。比如:U盘拷贝数据。
  • 中断传输:中断传输主要用于定时查询设备是否有中断数据要传输,设备的端点模式器的结构决定了它的查询频率,从1到255ms之间。这种传输方式典型的应用在少量的、分散的、不可预测数据的传输,比如,键盘和鼠标就属于这一类型。中断传输是单向的并且对于host来说只有输入的方式。
  • 等时传输:等时传输提供了确定的带宽和间隔时间,它被用于时间严格并具有较强容错性的流数据传输,或者用于要求恒定的数据传输率的即时应用中。比如:USB摄像头。

1.9 USB的数据传输方式

USB总线是串行总线(跟串口一样),数据是一位一位地在数据线上进行传输的。LSB在前,最低位先发出,接下来是次低位,最后才是最高位(MSB)。

1.10 USB设备开发流程

  1. 首先确定USB系统的开发类型,是USB主机、USB从机还是USB OTG。
  2. 如果确定是USB设备,必须确定设备类型:HID、UDIO、CDC、HUB、IMAGE等。
  3. 查找相关设备手册,确定其描述符。
  4. 完成描述符后,编写USB枚举程序,观察是否枚举成功,如果枚举成功了,此设备开发已经完成大部分。
  5. 编写应用程序,在枚举成功后,主要是进行数据处理,编写应用程序。在这里插入图片描述

2. 如何区分USB2.0与USB3.0

电脑上的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”。
在这里插入图片描述

3. USB2.0通信协议简介(包-事务-传输)

3.1 包(Package)

包是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校验。

3.1.1 令牌包(Token Package)

令牌包是第一个数据包,表示一次传输即将开始。共有4种令牌包:SETUP令牌包、IN令牌包、OUT令牌包、SOF令牌包。其中SETUP、IN、OUT令牌包的结构是一样的,共有6个域,格式如下:
在这里插入图片描述
SETUP令牌包
在这里插入图片描述
IN令牌包
在这里插入图片描述
OUT令牌包
在这里插入图片描述
SOF令牌包
帧起始包(Start Of Frame),在USB的拓扑结构中,主机每隔一段时间就向总线上广播SOF包,所有的连到总线上的全速设备和高速设备都能收到SOF包。
SOF令牌包结构相对其余的三个令牌包来说,有点特殊,只有5个域。
在这里插入图片描述
说明:

  1. 设备枚举阶段使用的是控制传输,控制传输的建立事务(建立过程)使用的是SETUP令牌包(只有控制传输才会有SETUP令牌包),设备的端点0是专门用于控制传输的,所以包目标端点ENDP为0,包目标地址ADDR可以是0或者非0值[1~127]。
  2. 设备使用地址0时,CRC5的值是0x08;设置地址阶段过后,CRC5的值根据具体的地址值来进行计算,CRC5的计算由USB控制器自动完成。
  3. SETUP令牌包中包含了紧跟在其后面的数据包的目标地址和包目标端点信息。
  4. 4个令牌包中,只有SOF令牌包之后不跟随数据传输。
  5. 对于低速/全速设备,SYNC为:0000 0001;而对于高速设备,SYNC中有31个0,最后一个位是1。

3.1.2 数据包(DATAx)

USB1.1协议中,只有两种数据包:DATA0 和 DATA1。USB2.0新增了两个数据包:DATA2和MDATA分别用于高速分裂事务(split)事务和高速带宽同步传输中,DATA0和DATA1是需要关注的重点。
DATA0数据包结构(DATA0数据包的PID是0xc3):
在这里插入图片描述
DATA1数据包结构(DATA1数据包的PID是0xd2):
在这里插入图片描述
说明:
对于控制传输

  1. 建立事务的数据过程使用的是DATA0数据包(SETUP事务的数据过程只能使用DATA0),DATA0数据包是8字节的请求数据。
  2. IN事务或者OUT事务的数据过程使用的是DATA1数据包,是实际要交互的数据。
  3. 端点0传输的数据包就在DATA0和DATA1之间进行切换,这是实现设备栈时,为什么配置USB控制芯片端点的toggle功能的原因。
  4. 数据包是跟随在令牌包后面的,数据包传输的目标地址和端点信息已经在令牌中指明。

3.1.3 握手包(ACK)

握手包只有三个域,结构如下:
在这里插入图片描述
说明:

  1. 握手包跟随在令牌包或者数据包之后,组成一次完整的事务。
  2. 握手包可以由设备发出也可以由主机发出。
  3. 握手包的方向和数据包的方向是相反的。

3.2 事务(Transaction)

一个事务通常由两个或者三个包来组成:令牌包+数据包+握手包
1)令牌包:启动一个事务,总是由主机发出。
2)数据包:用于传输数据,可以从主机发到设备,也可以从设备发到主机,数据包的方向由令牌包来指定。
3)握手包:握手包的发送者就是数据包的接收者。所以说,握手包的方向和数据包的方向是相反的。当数据被正确接收后,接收者就发出握手包。

3.2.1 事务的分类

事务总是以令牌包开始,所以令牌包的类型决定事务的类型, 因此事务可分为:SETUP事务、IN事务、OUT事务。

3.3 传输(Transfer)

USB总共有四种传输类型:批量传输(bulk)、中断传输(interrupt)、等时传输(isochronous)、控制传输(control)。不同的传输类型都有各自的数据流模型。
在设备枚举阶段,设备的主机之间的数据交互使用的是控制传输,首先了解控制传输,对于其他类型的传输及其数据流模型,后面介绍不同的类设备开发的时候,再逐一研究。

3.3.1 控制传输

控制传输是四种传输类型中最复杂的一种,控制传输过程分为三个阶段:第一个阶段是建立阶段;第二个阶段是可选的数据阶段;第三个过程是状态阶段。这三个阶段也称为建立过程、数据过程、状态过程。
建立过程就是一个建立事务,事务里面的第一个包是SETUP令牌包,其次是DATA0数据包(8字节的请求数据),最后是握手包(只能是ACK握手包)。数据阶段是可选的,也就是说控制传输可以没有数据过程(SETUP包的数据域会指定为0),数据过程又分为:控制读传输,数据事务是输入的;控制写传输,数据事务是输出的(读/写是相对主机来说的)。而且数据过程的第一个数据包必须是DATA1数据包。

3.4 包、事务、传输三者之间的关系

包构成事务,事务构成传输。即一个传输由多个事务构成,一个事务又由多个包构成。
事务的类型由包的类型来决定,传输的类型就看具体实现功能时所用的传输。

3.4.1 关系图

以控制传输,主机请求设备的描述符数据为例,关系图如下:
在这里插入图片描述
说明:

  1. 控制传输一般用来实现一条USB协议定义的请求
  2. SETUP事务用来将一条请求命令发送到设备
  3. IN事务用于USB设备向主机返回主机所请求的数据
  4. OUT事务使用握手机制对之前的数据事务的正常结束进行确认

4. USB芯片

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接口之间做数字信号与模拟信号的转换。

5. USB设备的插入检测机制

首先,在USB集线器(Hub)的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。而在USB设备端,在D+或者D-上接了1.5K欧姆上拉电阻,对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。这样,当设备插入到集线器时,由1.5K的上拉电阻和15K的下拉电阻分压,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到高速模式的。在高速模式下,是电流传输模式,这时将D+上的上拉电阻断开。

5.1 识别低速和全速设备

主机的D+和D-都接有15K下拉电阻;全速USB设备的数据线D+接有1.5K的上拉电阻,一旦接入主机,主机的D+被拉高;低速USB设备的数据线D-接有1.5K的上拉电阻,一旦接入主机,主机的D-会被拉高。因此,主机就可以根据检测到自己的D+为高还是D-为高,从而判断接入的设备是一个全速还是低速设备。硬件结构图如下:
在这里插入图片描述
在这里插入图片描述

5.2 识别高速设备

刚开始时,高速设备以全速模式连接到主机,D+有上拉电阻。主机检测到全速设备连接上之后,对设备进行复位,USB设备收到复位信号,主动发起高速模式的握手协议进行速度识别。接下来,就取决于主机端了,如果主机的USB控制器支持高速模式传输,则主机会与该高速设备交互完成高速模式握手协议,之后,两者都工作在高速模式下;如果主机不支持高速模式传输(如果主机上没有EHCI类型的控制器,像比较老旧的电脑,并且是XP系统的),那么握手协议就会失败,设备端也不会切换到高速模式,之后两者都工作在全速模式下。
如果是一个全速设备接到高速主机,设备端没法发起高速握手协议,所以,最终设备和主机都会工作在全速模式下;主机具有多种控制器类型,能工作在全速/高速模式下。
全速和高速控制器,都是DP上拉,低速才是DM上拉(配置寄存器的时候,需要注意的地方)。

6. 请求

请求和描述符一样,有标准的设备请求,而对于不同类的设备,又有自己特定的请求。先介绍标准的设备请求,对于特殊的请求,介绍每个不同的类设备时,再做解析。

6.1 标准设备请求的数据结构

USB协议中规定,标准请求的长度为8个字节。在设备枚举过程中,Host会下发一系列的标准请求,设备端需要去解析这些标准请求(SETUP事务),并作出正确响应,设备才能成功枚举。成功枚举之后,才能调用相关接口进行数据通信。
8字节的标准请求结构如下:
在这里插入图片描述
每个域的解析如下表:
在这里插入图片描述
对于标准的请求,D6~D5 = 00,USB协议规定了11个标准请求,请求码(bRequest)如下表。
在这里插入图片描述
下面重点介绍三个标准请求。

6.1.1 GET_DESCRIPTOR请求

获取描述符请求。该请求由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或者协议分析仪上的数据需要注意的地方。

6.1.2 SET_ADDRESS请求

主机在收到第一个数据包(设备描述符数据包),解析无误后,接下来就进入设置地址阶段。
设置地址请求也是一个USB标准设备请求。这是一个分配地址的过程(主机给设备分配一个地址)。既然是标准请求,那么它也是有8个字节的数据。指定的地址包含在wValue字段中
主机从地址为0的设备获取设备描述符,一旦第一次成功获取到设备描述符之后,主机就会立刻发送设置地址的请求,减少设备使用公共地址0的时间(每个设备插入,都先被复位,默认的地址为0,也就是0地址是所有的USB设备的初始化地址,即可以理解为公共地址)。
设置地址请求的结构如下:
在这里插入图片描述说明:
(1) 设置地址的请求过程,是没有数据的,所以,数据长度就是0。
(2) 索引也用不着(请求字符串描述符,索引才用的上),所以也为0。
实例
主机下发的数据包为: 00 05 1D 00 00 00 00 00
解析: 数据方向【主机—>设备】 设置地址请求 地址数据为0x001D=29

6.1.3 SET_CONFIGURATION请求

设置配置的请求下发后,设备端的USB控制器进入配置状态。设备端根据配置值,使用对应的配置,USB设备才能正常工作。
设置配置的结构如下:
在这里插入图片描述
说明:
(1) 在设置配置请求中,wValue的第一字节(低字节)为配置的值。当该值与某配置描述符中的配置编号一致时(相等时),表示选中的是该配置,接下来设备就使用这个配置。
实例
一个USB设备可以有很多个配置。bConfigurationValue就是每个配置的标识!主机请求设置配置的时候,会下发一个配置值,如果某个配置的bConfigurationValue和主机请求的配置值相匹配,就表示该配置被激活,USB设备就使用这个配置(由主机决定,设备使用哪个配置)。
设置配置请求是一个输出请求,根据所请求的配置值,使能相应的端点。设备收到之后,返回一个0长度的状态数据包。设备收到非0的配置值之后,才会使能非0端点。否则会禁用非零端点。

7. USB设备的枚举过程

USB主机在检测到USB设备插入后,就要对设备进行枚举了。为什么要枚举呢?枚举就是从设备读取一些信息,包含USB设备传输类型、ID号、Product、USB速度等信息,这样主机就可以根据这些信息来加载合适的驱动程序。调试USB设备,很重要的一点就是USB的枚举过程,只要枚举成功了,那么就已经成功大半了。
在说枚举之前,先大概说一下USB的一种传输模式——控制传输。这种传输在USB中是非常重要的,它要保证数据的正确性,在设备的枚举过程中都是使用控制传输。控制传输分为三个过程:①建立过程(Setup)。②可选的数据过程。③状态过程。建立(Setup)过程都是由USB主机发起,它开始于一个Setup令牌包,后面紧跟一个DATA0包。如果是控制输入传输,那么数据过程就是输入数据;如果是控制输出传输,那么数据过程是输出数据。如果在设置过程中,指定了数据长度为0,则没有数据过程。数据过程之后是状态过程。状态过程刚好与数据过程的数据传输方向相反:如果是控制输入传输,则状态过程是一个输出数据包;如果是控制输出传输,则状态过程是一个输入数据包。状态阶段用来确认所有的数据都已经正确传输。

7.1 枚举的详细过程

首先,USB主机检测到USB设备插入后,就会先对设备复位。设备复位后,设备就处于默认/空闲状态(Default state),准备接收主机发来的请求。USB主机就会对地址为0的设备发送获取设备描述符的标准请求。所有的USB设备在总线复位后其地址都为0,这样主机就可以跟那些刚刚插入的设备通过地址0通信(主机此时发送的请求是默认地址0,端点0,虽然所有未分配地址的设备都是通过地址0来获取主机发来的请求,但由于枚举过程不是多个设备并行处理,而是一次枚举一个设备的方式进行,所以不会发生多个设备同时响应主机发来的请求)。主机在建立阶段发出获取设备描述符的输入请求,设备收到该请求后,在数据过程将设备描述符返回给主机。主机在成功获取到一个数据包的设备描述符后并且确认没有什么错误后,(注意:有些USB设备的端点0大小不足18字节(但至少具有8字节),而标准的设备描述有18字节,在这种情况下,USB设备只能暂时按最大包将部分设备描述符返回,而主机在成功获取到前面一部分描述符后,就不会再请求剩下的设备描述符部分,而是进入设置地址阶段),就会返回一个0长度的状态数据包给设备。
然后主机再对设备复位一下(再次复位的目的是使设备进入一个确定的状态),接下来就会进入到设置地址阶段。这时USB主机控制器通过发出一个设置地址(set_address)的请求(建立过程,设置地址无数据过程),地址包含在建立包中,具体的地址USB主机会负责管理,它会分配一个唯一的地址给新的设备。USB设备在收到地址后,返回0长度的状态包,主机收到0长度的状态包之后,会返回一个ACK给设备。设备在收到这个ACK之后,就可以启用新的地址了。这个地址对于设备来说是终生制的,设备在,地址在;设备消失(被拔出,复位,系统重启),地址被收回。同一个设备当再次被枚举后得到的地址不一定是上次那个了。
在这里插入图片描述

接下来主机发送 Get_Descriptor请求到新地址读取设备描述符,这次主机发送Get_Descriptor请求可算是诚心,它会认真解析设备描述符的内容。设备描述符内信息包括端点0的最大包长度,设备所支持的配置(Configuration)个数,设备类型,VID(Vendor ID,由USB-IF分配), PID(Product ID,由厂商自己定制)等信息。

  • 要仔细分析,主机的每一次标准请求,接收者是谁(设备、接口、端点)。
  • 枚举过程,使用的传输方式都是控制传输方式。
  • 枚举过程,一些必须的请求肯定会有,比如:获取设备描述符---->设置地址---->USB复位总线----->获取配置描述符------>获取配置描述符集合---->获取各种类型的字符串描述符—>设置配置。
  • 要明确各种描述符的意义和其在程序中的定义,以及数据如何返回给主机。明确收到请求,并响应的整个数据流通过程。

7.2 主机给设备挂载驱动(复合设备除外)

主机通过解析描述符后对设备有了足够的了解,会选择一个最合适的驱动给设备。 然后tell the world(announce_device)说明设备已经找到了,最后调用设备模型提供的接口device_add将设备添加到 usb 总线的设备列表里,然后 usb总线会遍历驱动列表里的每个驱动,调用自己的 match(usb_device_match) 函数看它们和你的设备或接口是否匹配,匹配的话调用device_bind_driver函数,现在就将控制权交到设备驱动了。
对于复合设备,通常应该是不同的接口(Interface)配置给不同的驱动,因此,需要等到当设备被配置并把接口使能后才可以把驱动挂载上去。
设备-配置-接口-端点关系见下图:
在这里插入图片描述
USB枚举的具体流程:
在这里插入图片描述
当守护程序第一次运行或usb port上状态发生变化,守护进程被唤醒都会运行hub_events函数,这个函数在usb系统中处于核心位置,usb的枚举过程就是由它完成。

staticvoidhub_events(void){structlist_head*tmp;structusb_device*hdev;structusb_interface*intf;structusb_hub*hub;structdevice*hub_dev;
    u16 hubstatus;
    u16 hubchange;
    u16 portstatus;
    u16 portchange;int i, ret;int connect_change, wakeup_change;/*
     *  We restart the list every time to avoid a deadlock with
     * deleting hubs downstream from this one. This should be
     * safe since we delete the hub from the event list.
     * Not the most efficient, but avoids deadlocks.
     */while(1){/* Grab the first entry at the beginning of the list */spin_lock_irq(&hub_event_lock);if(list_empty(&hub_event_list)){spin_unlock_irq(&hub_event_lock);break;}

        tmp = hub_event_list.next;list_del_init(tmp);

        hub =list_entry(tmp,structusb_hub, event_list);kref_get(&hub->kref);/* make sure hdev is not freed before accessing it */if(hub->disconnected){//判断hub是否连接spin_unlock_irq(&hub_event_lock);goto hub_disconnected;}else{usb_get_dev(hub->hdev);}spin_unlock_irq(&hub_event_lock);

        hdev = hub->hdev;
        hub_dev = hub->intfdev;
        intf =to_usb_interface(hub_dev);dev_dbg(hub_dev,"state %d ports %d chg %04x evt %04x\n",
                hdev->state, hub->descriptor
                    ? hub->descriptor->bNbrPorts
                    :0,/* NOTE: expects max 15 ports... */(u16) hub->change_bits[0],(u16) hub->event_bits[0]);/* Lock the device, then check to see if we were
         * disconnected while waiting for the lock to succeed. */usb_lock_device(hdev);if(unlikely(hub->disconnected))goto loop_disconnected;/* If the hub has died, clean up after it */if(hdev->state == USB_STATE_NOTATTACHED){
            hub->error =-ENODEV;hub_quiesce(hub, HUB_DISCONNECT);goto loop;}/* Autoresume */
        ret =usb_autopm_get_interface(intf);if(ret){dev_dbg(hub_dev,"Can't autoresume: %d\n", ret);goto loop;}/* If this is an inactive hub, do nothing */if(hub->quiescing)goto loop_autopm;if(hub->error){dev_dbg(hub_dev,"resetting for error %d\n",
                hub->error);

            ret =usb_reset_device(hdev);if(ret){dev_dbg(hub_dev,"error resetting hub: %d\n", ret);goto loop_autopm;}

            hub->nerrors =0;
            hub->error =0;}/* deal with port status changes *///遍历hub中的所有port,对各个port的状态进行查看,判断port是否发生了状态变化for(i =1; i <= hub->descriptor->bNbrPorts; i++){if(test_bit(i, hub->busy_bits))continue;
            connect_change =test_bit(i, hub->change_bits);
            wakeup_change =test_and_clear_bit(i, hub->wakeup_bits);if(!test_and_clear_bit(i, hub->event_bits)&&!connect_change &&!wakeup_change)continue;

            ret =hub_port_status(hub, i,&portstatus,&portchange);if(ret <0)continue;if(portchange & USB_PORT_STAT_C_CONNECTION){usb_clear_port_feature(hdev, i,
                    USB_PORT_FEAT_C_CONNECTION);
                connect_change =1;}if(portchange & USB_PORT_STAT_C_ENABLE){if(!connect_change)dev_dbg(hub_dev,"port %d enable change, ""status %08x\n",
                        i, portstatus);usb_clear_port_feature(hdev, i,
                    USB_PORT_FEAT_C_ENABLE);/*
                 * EM interference sometimes causes badly
                 * shielded USB devices to be shutdown by
                 * the hub, this hack enables them again.
                 * Works at least with mouse driver. 
                 */if(!(portstatus & USB_PORT_STAT_ENABLE)&&!connect_change
                    && hub->ports[i -1]->child){dev_err(hub_dev,"port %i ""disabled by hub (EMI?), ""re-enabling...\n",
                        i);
                    connect_change =1;}}if(hub_handle_remote_wakeup(hub, i,
                        portstatus, portchange))
                connect_change =1;if(portchange & USB_PORT_STAT_C_OVERCURRENT){
                u16 status =0;
                u16 unused;dev_dbg(hub_dev,"over-current change on port ""%d\n", i);usb_clear_port_feature(hdev, i,
                    USB_PORT_FEAT_C_OVER_CURRENT);msleep(100);/* Cool down */hub_power_on(hub, true);hub_port_status(hub, i,&status,&unused);if(status & USB_PORT_STAT_OVERCURRENT)dev_err(hub_dev,"over-current ""condition on port %d\n", i);}if(portchange & USB_PORT_STAT_C_RESET){dev_dbg(hub_dev,"reset change on port %d\n",
                    i);usb_clear_port_feature(hdev, i,
                    USB_PORT_FEAT_C_RESET);}if((portchange & USB_PORT_STAT_C_BH_RESET)&&hub_is_superspeed(hub->hdev)){dev_dbg(hub_dev,"warm reset change on port %d\n",
                    i);usb_clear_port_feature(hdev, i,
                    USB_PORT_FEAT_C_BH_PORT_RESET);}if(portchange & USB_PORT_STAT_C_LINK_STATE){usb_clear_port_feature(hub->hdev, i,
                        USB_PORT_FEAT_C_PORT_LINK_STATE);}if(portchange & USB_PORT_STAT_C_CONFIG_ERROR){dev_warn(hub_dev,"config error on port %d\n",
                    i);usb_clear_port_feature(hub->hdev, i,
                        USB_PORT_FEAT_C_PORT_CONFIG_ERROR);}/* Warm reset a USB3 protocol port if it's in
             * SS.Inactive state.
             */if(hub_port_warm_reset_required(hub, portstatus)){int status;structusb_device*udev =
                    hub->ports[i -1]->child;dev_dbg(hub_dev,"warm reset port %d\n", i);if(!udev ||!(portstatus & USB_PORT_STAT_CONNECTION)||
                    udev->state == USB_STATE_NOTATTACHED){
                    status =hub_port_reset(hub, i,NULL, HUB_BH_RESET_TIME,
                            true);if(status <0)hub_port_disable(hub, i,1);}else{usb_lock_device(udev);
                    status =usb_reset_device(udev);usb_unlock_device(udev);
                    connect_change =0;}}if(connect_change)//如果port状态发生变化,则调用hub_port_connect_changehub_port_connect_change(hub, i,
                        portstatus, portchange);}/* end for i *//* deal with hub status changes */if(test_and_clear_bit(0, hub->event_bits)==0);/* do nothing */elseif(hub_hub_status(hub,&hubstatus,&hubchange)<0)dev_err(hub_dev,"get_hub_status failed\n");else{if(hubchange & HUB_CHANGE_LOCAL_POWER){dev_dbg(hub_dev,"power change\n");clear_hub_feature(hdev, C_HUB_LOCAL_POWER);if(hubstatus & HUB_STATUS_LOCAL_POWER)/* FIXME: Is this always true? */
                    hub->limited_power =1;else
                    hub->limited_power =0;}if(hubchange & HUB_CHANGE_OVERCURRENT){
                u16 status =0;
                u16 unused;dev_dbg(hub_dev,"over-current change\n");clear_hub_feature(hdev, C_HUB_OVER_CURRENT);msleep(500);/* Cool down */hub_power_on(hub, true);hub_hub_status(hub,&status,&unused);if(status & HUB_STATUS_OVERCURRENT)dev_err(hub_dev,"over-current ""condition\n");}}

 loop_autopm:/* Balance the usb_autopm_get_interface() above */usb_autopm_put_interface_no_suspend(intf);
 loop:/* Balance the usb_autopm_get_interface_no_resume() in
         * kick_khubd() and allow autosuspend.
         */usb_autopm_put_interface(intf);
 loop_disconnected:usb_unlock_device(hdev);usb_put_dev(hdev);
 hub_disconnected:kref_put(&hub->kref, hub_release);}/* end while (1) */}

hub_events本身是一个死循环,只要条件满足它便会一直执行。
首先判断hub_event_list是否为空,如果为空,则跳出循环,hub_events运行结束,会进入休眠状态;如果不为空,则从hub_event_list列表中取出某一项,并把它从hub_event_list中删除,通过list_entry来获取event_list所对应的hub,并增加hub使用计数;然后做了一些逻辑判断,判断hub是否连接,hub上是否有错误,如果有错误就重启hub,如果没有错误,接下来就对hub 上的每个port进行扫描,判断各个port是否发生状态变化;
接着遍历hub中的所有port,对各个port的状态进行查看,判断port是否发生了状态变化,如果产生了变化则调用hub_port_connect_change进行处理;
在hub结构中存在多个标志位,用来表示port一些状态:

  1. 如果usb的第i个port处于resume或reset状态,则hub中busy_bits中第i个位置1;如果busy_bits中第i个位置1,则退过当前port;
  2. event_bits中第0位用来表示hub本身是否产生local power status change和是否产生过流,其它的各位用来表示hub下各个port状态是否发生变化,这些状态包括: ConnectStatusChange 连接状态发生变化,PortEnableStatusChange端口使能状态发生变化,PortSuspendStatusChange端口从挂起到恢复完成,PortOverCurrentIndicatorChange端口过流状态发生变化,PortResetStatusChange端口reset10ms置位;
  3. remove_bits用来表示hub下各个port是否有设备连接,如果置位表示没有设备连接或设备已经断开;
  4. change_bits用来表示hub下各个端口逻辑状态是否发生变化,它是在hub初始化的时候赋值的,这些状态主要有:1.原先port上没有设备,现在检测到有设备连接;2.原先port上有设备,由于控制器不正常关闭或禁止usb port等原图使得该设备处于NOATTACHED态,则需要断开设备;

在遍历port时,首先对这些标志进行判断,如果没有产生变化则跳过当前port,否则读取当前port的status,判断出产生状态变化的原因;
如果发生变化port的连接状态发生变化,清除相应的端口状态,设置connect_change变量为1;
如果port的使能状态发生变化,清除相应的状态标志,如果是由于EMI电磁干扰引起的状态变化,则设置connect_change变量为1;
……
经过一系列列port判断,如果发现port的连接状态发生变化或由于EMI导致连接使能发生变化,即connect_change=1,则调用hub_port_connect_change。

/* Handle physical or logical connection change events.
 * This routine is called when:
 *  a port connection-change occurs;
 *  a port enable-change occurs (often caused by EMI);
 *  usb_reset_and_verify_device() encounters changed descriptors (as from
 *      a firmware download)
 * caller already locked the hub
 */staticvoidhub_port_connect_change(structusb_hub*hub,int port1,
                    u16 portstatus, u16 portchange){structusb_device*hdev = hub->hdev;structdevice*hub_dev = hub->intfdev;structusb_hcd*hcd =bus_to_hcd(hdev->bus);unsigned wHubCharacteristics =le16_to_cpu(hub->descriptor->wHubCharacteristics);structusb_device*udev;int status, i;unsigned unit_load;dev_dbg(hub_dev,"port %d, status %04x, change %04x, %s\n",
        port1, portstatus, portchange,portspeed(hub, portstatus));if(hub->has_indicators){//如果当前的hub支持indicator指示灯,则设备指示灯为HUB_LED_AUTOset_port_led(hub, port1, HUB_LED_AUTO);
        hub->indicator[port1-1]= INDICATOR_AUTO;}#ifdefCONFIG_USB_OTG/* during HNP, don't repeat the debounce */if(hdev->bus->is_b_host)
        portchange &=~(USB_PORT_STAT_C_CONNECTION |
                USB_PORT_STAT_C_ENABLE);#endif/* Try to resuscitate an existing device */
    udev = hub->ports[port1 -1]->child;if((portstatus & USB_PORT_STAT_CONNECTION)&& udev &&
            udev->state != USB_STATE_NOTATTACHED){usb_lock_device(udev);if(portstatus & USB_PORT_STAT_ENABLE){
            status =0;/* Nothing to do */#ifdefCONFIG_PM_RUNTIME}elseif(udev->state == USB_STATE_SUSPENDED &&
                udev->persist_enabled){/* For a suspended device, treat this as a
             * remote wakeup event.
             */
            status =usb_remote_wakeup(udev);#endif}else{
            status =-ENODEV;/* Don't resuscitate */}usb_unlock_device(udev);if(status ==0){//清除当前port的change_bits标志clear_bit(port1, hub->change_bits);return;}}/* Disconnect any existing devices under this port */if(udev){if(hcd->phy &&!hdev->parent &&!(portstatus & USB_PORT_STAT_CONNECTION))usb_phy_notify_disconnect(hcd->phy, udev->speed);usb_disconnect(&hub->ports[port1 -1]->child);}clear_bit(port1, hub->change_bits);/* We can forget about a "removed" device when there's a physical
     * disconnect or the connect status changes.
     */if(!(portstatus & USB_PORT_STAT_CONNECTION)||(portchange & USB_PORT_STAT_C_CONNECTION))clear_bit(port1, hub->removed_bits);if(portchange &(USB_PORT_STAT_C_CONNECTION |
                USB_PORT_STAT_C_ENABLE)){
        status =hub_port_debounce_be_stable(hub, port1);if(status <0){if(status !=-ENODEV &&printk_ratelimit())dev_err(hub_dev,"connect-debounce failed, ""port %d disabled\n", port1);
            portstatus &=~USB_PORT_STAT_CONNECTION;}else{
            portstatus = status;}}/* Return now if debouncing failed or nothing is connected or
     * the device was "removed".
     */if(!(portstatus & USB_PORT_STAT_CONNECTION)||test_bit(port1, hub->removed_bits)){/* maybe switch power back on (e.g. root hub was reset) */if((wHubCharacteristics & HUB_CHAR_LPSM)<2&&!port_is_power_on(hub, portstatus))set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);if(portstatus & USB_PORT_STAT_ENABLE)goto done;return;}if(hub_is_superspeed(hub->hdev))
        unit_load =150;else
        unit_load =100;

    status =0;for(i =0; i < SET_CONFIG_TRIES; i++){/* reallocate for each attempt, since references
         * to the previous one can escape in various ways
         * 通过usb_alloc_dev为新的USB设备申请资源,并进行一些初始化,
         * 将usb设置为USB_STATE_ATTACHED,表示设备已经连接
         */
        udev =usb_alloc_dev(hdev, hdev->bus, port1);if(!udev){dev_err(hub_dev,"couldn't allocate port %d usb_device\n",
                port1);goto done;}usb_set_device_state(udev, USB_STATE_POWERED);//设置usb设备为USB_STATE_POWERED态
        udev->bus_mA = hub->mA_per_port;//设置usb设备可以从port上获取的电流量
        udev->level = hdev->level +1;,//设置usb设备的拓扑层级
        udev->wusb =hub_is_wusb(hub);/* Only USB 3.0 devices are connected to SuperSpeed hubs. *///如果hub支持超速,则设置usb设备的速度为超速,否则设置usb设备速度 为unknowif(hub_is_superspeed(hub->hdev))
            udev->speed = USB_SPEED_SUPER;else
            udev->speed = USB_SPEED_UNKNOWN;choose_devnum(udev);//从usb总线中找到一个usb地址if(udev->devnum <=0){
            status =-ENOTCONN;/* Don't retry */goto loop;}/* reset (non-USB 3.0 devices) and get descriptor */
        status =hub_port_init(hub, udev, port1, i);if(status <0)goto loop;usb_detect_quirks(udev);if(udev->quirks & USB_QUIRK_DELAY_INIT)msleep(1000);/* consecutive bus-powered hubs aren't reliable; they can
         * violate the voltage drop budget.  if the new child has
         * a "powered" LED, users should notice we didn't enable it
         * (without reading syslog), even without per-port LEDs
         * on the parent.
         *///如果当前的usb设备是一个hub,它由hub供电if(udev->descriptor.bDeviceClass == USB_CLASS_HUB
                && udev->bus_mA <= unit_load){
            u16 devstat;

            status =usb_get_status(udev, USB_RECIP_DEVICE,0,&devstat);if(status <2){dev_dbg(&udev->dev,"get status %d ?\n", status);goto loop_disable;}le16_to_cpus(&devstat);if((devstat &(1<< USB_DEVICE_SELF_POWERED))==0){dev_err(&udev->dev,"can't connect bus-powered hub ""to this port\n");if(hub->has_indicators){
                    hub->indicator[port1-1]=
                        INDICATOR_AMBER_BLINK;schedule_delayed_work(&hub->leds,0);}
                status =-ENOTCONN;/* Don't retry */goto loop_disable;}}/* check for devices running slower than they could *///如果usb设备支持高速运行,而现在却工作在全速,它就会通过点亮绿色指示灯来指示这种错误if(le16_to_cpu(udev->descriptor.bcdUSB)>=0x0200&& udev->speed == USB_SPEED_FULL
                && highspeed_hubs !=0)check_highspeed(hub, udev, port1);/* Store the parent's children[] pointer.  At this point
         * udev becomes globally accessible, although presumably
         * no one will look at it until hdev is unlocked.
         */
        status =0;/* We mustn't add new devices if the parent hub has
         * been disconnected; we would race with the
         * recursively_mark_NOTATTACHED() routine.
         */spin_lock_irq(&device_state_lock);if(hdev->state == USB_STATE_NOTATTACHED)
            status =-ENOTCONN;else
            hub->ports[port1 -1]->child = udev;spin_unlock_irq(&device_state_lock);/* Run it through the hoops (find a driver, etc) */if(!status){
            status =usb_new_device(udev);//通过usb_new_device去获取usb设备的配置信息,将它注册到系统中if(status){spin_lock_irq(&device_state_lock);
                hub->ports[port1 -1]->child =NULL;spin_unlock_irq(&device_state_lock);}}if(status)goto loop_disable;

        status =hub_power_remaining(hub);if(status)dev_dbg(hub_dev,"%dmA power budget left\n", status);return;

loop_disable:hub_port_disable(hub, port1,1);
loop:usb_ep0_reinit(udev);release_devnum(udev);hub_free_dev(udev);usb_put_dev(udev);if((status ==-ENOTCONN)||(status ==-ENOTSUPP))break;}if(hub->hdev->parent ||!hcd->driver->port_handed_over ||!(hcd->driver->port_handed_over)(hcd, port1)){if(status !=-ENOTCONN && status !=-ENODEV)dev_err(hub_dev,"unable to enumerate USB device on port %d\n",
                    port1);}

done:hub_port_disable(hub, port1,1);if(hcd->driver->relinquish_port &&!hub->hdev->parent)
        hcd->driver->relinquish_port(hcd, port1);}

hub_port_connect_change分为两部分:
第一部分主要是:确认是否有新设备插入;
第二部分主要是:确认port口上有新设备插入后,分配设备资源,并进行枚举操作;
对于一些已经连接的设备,进行一些类似防抖动处理,处理机制:每隔25ms去读取port的status和change状态,查看port连接状态是否稳定,如果port处于稳定状态大于100ms,则认为当前port上的设备已经稳定,这种处理机制最长时间为1.5s;如果port处于稳定状态的时间小于100ms,则认为连接是不稳定的,则跳出当前port,去执行下一个port;
对于一个新插入的设备,并已经确定它的存在,则接下来会为这个usb设备进行枚举。
一条usb总线下总共可以有128个设备,usb总线通过位图的形式来管理usb设备的地址,choose_devnum是从usb 总线中找到一个usb地址,usb地址在1和128之间;
通过hub_port_init对usb设备进行reset,并设置usb设备的地址,获取usb设备的设备描述符,这个函数相对比较重要,所以对它进行了进一步分析:

/* Reset device, (re)assign address, get device descriptor.
 * Device connection must be stable, no more debouncing needed.
 * Returns device in USB_STATE_ADDRESS, except on error.
 *
 * If this is called for an already-existing device (as part of
 * usb_reset_and_verify_device), the caller must own the device lock.  For a
 * newly detected device that is not accessible through any global
 * pointers, it's not necessary to lock the device.
 */staticinthub_port_init(structusb_hub*hub,structusb_device*udev,int port1,int retry_counter){staticDEFINE_MUTEX(usb_address0_mutex);structusb_device*hdev = hub->hdev;structusb_hcd*hcd =bus_to_hcd(hdev->bus);int         i, j, retval;unsigned        delay = HUB_SHORT_RESET_TIME;enumusb_device_speed   oldspeed = udev->speed;constchar*speed;int         devnum = udev->devnum;/* root hub ports have a slightly longer reset period
     * (from USB 2.0 spec, section 7.1.7.5)
     * 设置用于读取port状态的时间间隔,root hub需要50ms,普通的hub需要10ms,
     * 对于低速的usb设备需要200ms
     */if(!hdev->parent){
        delay = HUB_ROOT_RESET_TIME;if(port1 == hdev->bus->otg_port)
            hdev->bus->b_hnp_enable =0;}/* Some low speed devices have problems with the quick delay, so *//*  be a bit pessimistic with those devices. RHbug #23670 */if(oldspeed == USB_SPEED_LOW)
        delay = HUB_LONG_RESET_TIME;mutex_lock(&usb_address0_mutex);/* Reset the device; full speed may morph to high speed *//* FIXME a USB 2.0 device may morph into SuperSpeed on reset. *///通过hub_port_reset来reset设备
    retval =hub_port_reset(hub, port1, udev, delay, false);if(retval <0)/* error or disconnect */goto fail;/* success, speed is known */

    retval =-ENODEV;if(oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed){dev_dbg(&udev->dev,"device reset changed speed!\n");goto fail;}
    oldspeed = udev->speed;/* USB 2.0 section 5.5.3 talks about ep0 maxpacket ...
     * it's fixed size except for full speed devices.
     * For Wireless USB devices, ep0 max packet is always 512 (tho
     * reported as 0xff in the device descriptor). WUSB1.0[4.8.1].
     * 根据usb设备的速度来初始化endpont0的最大发送数据长度,
     * 对于无线usb其maxpacketsize为512字节,对于高速和全速为64字节,而低速为8个字节
     */switch(udev->speed){case USB_SPEED_SUPER:case USB_SPEED_WIRELESS:/* fixed at 512 */
        udev->ep0.desc.wMaxPacketSize =cpu_to_le16(512);break;case USB_SPEED_HIGH:/* fixed at 64 */
        udev->ep0.desc.wMaxPacketSize =cpu_to_le16(64);break;case USB_SPEED_FULL:/* 8, 16, 32, or 64 *//* to determine the ep0 maxpacket size, try to read
         * the device descriptor to get bMaxPacketSize0 and
         * then correct our initial guess.
         */
        udev->ep0.desc.wMaxPacketSize =cpu_to_le16(64);break;case USB_SPEED_LOW:/* fixed at 8 */
        udev->ep0.desc.wMaxPacketSize =cpu_to_le16(8);break;default:goto fail;}if(udev->speed == USB_SPEED_WIRELESS)
        speed ="variable speed Wireless";else
        speed =usb_speed_string(udev->speed);if(udev->speed != USB_SPEED_SUPER)dev_info(&udev->dev,"%s %s USB device number %d using %s\n",(udev->config)?"reset":"new", speed,
                devnum, udev->bus->controller->driver->name);/* Set up TT records, if needed  *///如果hub为高速,而usb设备为低速或全速,则在它们之间需要有一个速度转换设备ttif(hdev->tt){
        udev->tt = hdev->tt;
        udev->ttport = hdev->ttport;}elseif(udev->speed != USB_SPEED_HIGH
            && hdev->speed == USB_SPEED_HIGH){if(!hub->tt.hub){dev_err(&udev->dev,"parent hub has no TT\n");
            retval =-EINVAL;goto fail;}
        udev->tt =&hub->tt;
        udev->ttport = port1;}/* Why interleave GET_DESCRIPTOR and SET_ADDRESS this way?
     * Because device hardware and firmware is sometimes buggy in
     * this area, and this is how Linux has done it for ages.
     * Change it cautiously.
     *
     * NOTE:  If USE_NEW_SCHEME() is true we will start by issuing
     * a 64-byte GET_DESCRIPTOR request.  This is what Windows does,
     * so it may help with some non-standards-compliant devices.
     * Otherwise we start with SET_ADDRESS and then try to read the
     * first 8 bytes of the device descriptor to get the ep0 maxpacket
     * value.
     */for(i =0; i < GET_DESCRIPTOR_TRIES;(++i,msleep(100))){if(USE_NEW_SCHEME(retry_counter)&&!(hcd->driver->flags & HCD_USB3)&&!((hcd->driver->flags & HCD_RT_OLD_ENUM)&&!hdev->parent)){structusb_device_descriptor*buf;
            ushort idvendor;int r =0;#defineGET_DESCRIPTOR_BUFSIZE64
            buf =kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);if(!buf){
                retval =-ENOMEM;continue;}/* Retry on all errors; some devices are flakey.
             * 255 is for WUSB devices, we actually need to use
             * 512 (WUSB1.0[4.8.1]).
             */for(j =0; j <3;++j){
                buf->bMaxPacketSize0 =0;
                r =usb_control_msg(udev,usb_rcvaddr0pipe(),
                    USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
                    USB_DT_DEVICE <<8,0,
                    buf, GET_DESCRIPTOR_BUFSIZE,
                    initial_descriptor_timeout);switch(buf->bMaxPacketSize0){case8:case16:case32:case64:case255:if(buf->bDescriptorType ==
                            USB_DT_DEVICE){
                        r =0;break;}/* FALL THROUGH */default:if(r ==0)
                        r =-EPROTO;break;}if(r ==0)break;}
            udev->descriptor.bMaxPacketSize0 =
                    buf->bMaxPacketSize0;
            idvendor =le16_to_cpu(buf->idVendor);kfree(buf);/*
             * If it is a HSET Test device, we don't issue a
             * second reset which results in failure due to
             * speed change.
             */if(idvendor !=0x1a0a){
                retval =hub_port_reset(hub, port1, udev,
                             delay, false);if(retval <0)/* error or disconnect */goto fail;if(oldspeed != udev->speed){dev_dbg(&udev->dev,"device reset changed speed!\n");
                    retval =-ENODEV;goto fail;}}if(r){if(r !=-ENODEV)dev_err(&udev->dev,"device descriptor read/64, error %d\n",
                            r);
                retval =-EMSGSIZE;continue;}#undefGET_DESCRIPTOR_BUFSIZE}/*
         * If device is WUSB, we already assigned an
         * unauthorized address in the Connect Ack sequence;
         * authorization will assign the final address.
         */if(udev->wusb ==0){for(j =0; j < SET_ADDRESS_TRIES;++j){
                retval =hub_set_address(udev, devnum);if(retval >=0)break;msleep(200);}if(retval <0){if(retval !=-ENODEV)dev_err(&udev->dev,"device not accepting address %d, error %d\n",
                            devnum, retval);goto fail;}if(udev->speed == USB_SPEED_SUPER){
                devnum = udev->devnum;dev_info(&udev->dev,"%s SuperSpeed USB device number %d using %s\n",(udev->config)?"reset":"new",
                        devnum, udev->bus->controller->driver->name);}/* cope with hardware quirkiness:
             *  - let SET_ADDRESS settle, some device hardware wants it
             *  - read ep0 maxpacket even for high and low speed,
             */msleep(10);if(USE_NEW_SCHEME(retry_counter)&&!(hcd->driver->flags & HCD_USB3)&&!((hcd->driver->flags & HCD_RT_OLD_ENUM)&&!hdev->parent))break;}

        retval =usb_get_device_descriptor(udev,8);if(retval <8){if(retval !=-ENODEV)dev_err(&udev->dev,"device descriptor read/8, error %d\n",
                    retval);if(retval >=0)
                retval =-EMSGSIZE;}else{
            retval =0;break;}}if(retval)goto fail;if(hcd->phy &&!hdev->parent)usb_phy_notify_connect(hcd->phy, udev->speed);/*
     * Some superspeed devices have finished the link training process
     * and attached to a superspeed hub port, but the device descriptor
     * got from those devices show they aren't superspeed devices. Warm
     * reset the port attached by the devices can fix them.
     */if((udev->speed == USB_SPEED_SUPER)&&(le16_to_cpu(udev->descriptor.bcdUSB)<0x0300)){dev_err(&udev->dev,"got a wrong device descriptor, ""warm reset device\n");hub_port_reset(hub, port1, udev,
                HUB_BH_RESET_TIME, true);
        retval =-EINVAL;goto fail;}if(udev->descriptor.bMaxPacketSize0 ==0xff||
            udev->speed == USB_SPEED_SUPER)
        i =512;else
        i = udev->descriptor.bMaxPacketSize0;if(usb_endpoint_maxp(&udev->ep0.desc)!= i){if(udev->speed == USB_SPEED_LOW ||!(i ==8|| i ==16|| i ==32|| i ==64)){dev_err(&udev->dev,"Invalid ep0 maxpacket: %d\n", i);
            retval =-EMSGSIZE;goto fail;}if(udev->speed == USB_SPEED_FULL)dev_dbg(&udev->dev,"ep0 maxpacket = %d\n", i);elsedev_warn(&udev->dev,"Using ep0 maxpacket: %d\n", i);
        udev->ep0.desc.wMaxPacketSize =cpu_to_le16(i);usb_ep0_reinit(udev);}//根据maxpacketsize去获取完整的设备描述符
    retval =usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);if(retval <(signed)sizeof(udev->descriptor)){if(retval !=-ENODEV)dev_err(&udev->dev,"device descriptor read/all, error %d\n",
                    retval);if(retval >=0)
            retval =-ENOMSG;goto fail;}if(udev->wusb ==0&&le16_to_cpu(udev->descriptor.bcdUSB)>=0x0201){
        retval =usb_get_bos_descriptor(udev);if(!retval){
            udev->lpm_capable =usb_device_supports_lpm(udev);usb_set_lpm_parameters(udev);}}

    retval =0;/* notify HCD that we have a device connected and addressed */if(hcd->driver->update_device)
        hcd->driver->update_device(hcd, udev);
fail:if(retval){hub_port_disable(hub, port1,0);update_devnum(udev, devnum);/* for disconnect processing */}mutex_unlock(&usb_address0_mutex);return retval;}

hub_port_init首先对port进行reset,复位成功后设置usb设备的速度和端口0最大发送数据长度,设置usb设备的地址,并获取usb设备的设备描述符;
通过hub_port_reset来reset设备,reset机制为:通过set_port_feature来传输USB_PORT_FEAT_RESET指令,设置成功后循环延时由之前确定的时间间隔后去读取port的status和change状态,要确保usb设备在reset后还能正常存在,如reset还能正常,则通过port的status状态位来确定usb设备的速度,循环延时总时间为500ms,而usb设备reset次数为5次。
根据usb设备的速度来初始化endpont0的最大发送数据长度,对于无线usb其maxpacketsize为512字节,对于高速和全速为64字节,而低速为8个字节;
如果hub为高速,而usb设备为低速或全速,则在它们之间需要有一个速度转换设备tt;
通过获取usb设备的设备描述符来得到usb设备的endpoint0的最大发送数据长度,设备描述符实际上有18个字节,而endpoint0的maxpacketsize在设备描述符里的第8个字节位,所以只要获取设备描述符的前8个字节数据就可以了,但有些USB设备它并不支持只获取8个字节这样不完整的usb请求,为解决这种问题usb驱动里采用了两种策略去获取maxpacketsize,一种是windows作法,它直接向usb设备请求64字节数据,对于一些maxpacketsize为32或64的,它可以直接一次性把18个字节的设备描述符传输回来,而对于像low speed的设备它的maxpacketsize只有8个字节,就需要进行多次发送; 而Linux作法是先设置usb设备的地址,然后再发送8个字节数据请求,从返回的8个字节里获取endpoint0的maxpacketsize;

/**
 * usb_new_device - perform initial device setup (usbcore-internal)
 * @udev: newly addressed device (in ADDRESS state)
 *
 * This is called with devices which have been detected but not fully
 * enumerated.  The device descriptor is available, but not descriptors
 * for any device configuration.  The caller must have locked either
 * the parent hub (if udev is a normal device) or else the
 * usb_bus_list_lock (if udev is a root hub).  The parent's pointer to
 * udev has already been installed, but udev is not yet visible through
 * sysfs or other filesystem code.
 *
 * It will return if the device is configured properly or not.  Zero if
 * the interface was registered with the driver core; else a negative
 * errno value.
 *
 * This call is synchronous, and may not be used in an interrupt context.
 *
 * Only the hub driver or root-hub registrar should ever call this.
 */intusb_new_device(structusb_device*udev){int err;if(udev->parent){/* Initialize non-root-hub device wakeup to disabled;
         * device (un)configuration controls wakeup capable
         * sysfs power/wakeup controls wakeup enabled/disabled
         */device_init_wakeup(&udev->dev,0);}/* Tell the runtime-PM framework the device is active */pm_runtime_set_active(&udev->dev);pm_runtime_get_noresume(&udev->dev);pm_runtime_use_autosuspend(&udev->dev);pm_runtime_enable(&udev->dev);/* By default, forbid autosuspend for all devices.  It will be
     * allowed for hubs during binding.
     */usb_disable_autosuspend(udev);

    err =usb_enumerate_device(udev);/* Read descriptors */if(err <0)goto fail;dev_dbg(&udev->dev,"udev %d, busnum %d, minor = %d\n",
            udev->devnum, udev->bus->busnum,(((udev->bus->busnum-1)*128)+(udev->devnum-1)));/* export the usbdev device-node for libusb */
    udev->dev.devt =MKDEV(USB_DEVICE_MAJOR,(((udev->bus->busnum-1)*128)+(udev->devnum-1)));/* Tell the world! */announce_device(udev);if(udev->serial)add_device_randomness(udev->serial,strlen(udev->serial));if(udev->product)add_device_randomness(udev->product,strlen(udev->product));if(udev->manufacturer)add_device_randomness(udev->manufacturer,strlen(udev->manufacturer));device_enable_async_suspend(&udev->dev);/*
     * check whether the hub marks this port as non-removable. Do it
     * now so that platform-specific data can override it in
     * device_add()
     */if(udev->parent)set_usb_port_removable(udev);/* Register the device.  The device driver is responsible
     * for configuring the device and invoking the add-device
     * notifier chain (used by usbfs and possibly others).
     */
    err =device_add(&udev->dev);if(err){dev_err(&udev->dev,"can't device_add, error %d\n", err);goto fail;}/* Create link files between child device and usb port device. */if(udev->parent){structusb_hub*hub =usb_hub_to_struct_hub(udev->parent);structusb_port*port_dev;if(!hub)goto fail;

        port_dev = hub->ports[udev->portnum -1];if(!port_dev)goto fail;

        err =sysfs_create_link(&udev->dev.kobj,&port_dev->dev.kobj,"port");if(err)goto fail;

        err =sysfs_create_link(&port_dev->dev.kobj,&udev->dev.kobj,"device");if(err){sysfs_remove_link(&udev->dev.kobj,"port");goto fail;}pm_runtime_get_sync(&port_dev->dev);}(void)usb_create_ep_devs(&udev->dev,&udev->ep0, udev);usb_mark_last_busy(udev);pm_runtime_put_sync_autosuspend(&udev->dev);return err;

fail:usb_set_device_state(udev, USB_STATE_NOTATTACHED);pm_runtime_disable(&udev->dev);pm_runtime_set_suspended(&udev->dev);return err;}

usb_new_device主是要获取usb设备的配置信息,然后将usb设备添加到系统中,这里usb_enumerate_device相对比较重要,就是它完成了配置信息获取及分配;

/**
 * usb_enumerate_device - Read device configs/intfs/otg (usbcore-internal)
 * @udev: newly addressed device (in ADDRESS state)
 *
 * This is only called by usb_new_device() and usb_authorize_device()
 * and FIXME -- all comments that apply to them apply here wrt to
 * environment.
 *
 * If the device is WUSB and not authorized, we don't attempt to read
 * the string descriptors, as they will be errored out by the device
 * until it has been authorized.
 */staticintusb_enumerate_device(structusb_device*udev){int err;if(udev->config ==NULL){
        err =usb_get_configuration(udev);if(err <0){if(err !=-ENODEV)dev_err(&udev->dev,"can't read configurations, error %d\n",
                        err);return err;}}/* read the standard strings and cache them if present */
    udev->product =usb_cache_string(udev, udev->descriptor.iProduct);
    udev->manufacturer =usb_cache_string(udev,
                          udev->descriptor.iManufacturer);
    udev->serial =usb_cache_string(udev, udev->descriptor.iSerialNumber);

    err =usb_enumerate_device_otg(udev);if(err <0)return err;usb_detect_interface_quirks(udev);return0;}

通过usb_get_configuration获取和分配usb设备的配置,接口,端口信息。

/*
 * Get the USB config descriptors, cache and parse'em
 *
 * hub-only!! ... and only in reset path, or usb_new_device()
 * (used by real hubs and virtual root hubs)
 */intusb_get_configuration(structusb_device*dev){structdevice*ddev =&dev->dev;int ncfg = dev->descriptor.bNumConfigurations;int result =0;unsignedint cfgno, length;unsignedchar*bigbuffer;structusb_config_descriptor*desc;

    cfgno =0;
    result =-ENOMEM;//如果usb设备的配置个数大于最大允许配置数8,则发出警告,并把设备的配置个数改成最大允许配置数8if(ncfg > USB_MAXCONFIG){dev_warn(ddev,"too many configurations: %d, ""using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
        dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;}//如果usb设备没有配置,则不允许,直接返回错误信息if(ncfg <1){dev_err(ddev,"no configurations\n");return-EINVAL;}//根据配置数,申请usb配置结构usb_host_config,并为用于存放配置结构的指针数据申请空间
    length = ncfg *sizeof(structusb_host_config);
    dev->config =kzalloc(length, GFP_KERNEL);if(!dev->config)goto err2;

    length = ncfg *sizeof(char*);
    dev->rawdescriptors =kzalloc(length, GFP_KERNEL);if(!dev->rawdescriptors)goto err2;//申请用于暂时存放配置描述符的结构空间
    desc =kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL);if(!desc)goto err2;

    result =0;//依次获取usb设备的配置信息for(; cfgno < ncfg; cfgno++){/* We grab just the first descriptor so we know how long
         * the whole configuration is */
        result =usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
            desc, USB_DT_CONFIG_SIZE);if(result <0){dev_err(ddev,"unable to read config index %d ""descriptor/%s: %d\n", cfgno,"start", result);if(result !=-EPIPE)goto err;dev_err(ddev,"chopping to %d config(s)\n", cfgno);
            dev->descriptor.bNumConfigurations = cfgno;break;}elseif(result <4){dev_err(ddev,"config index %d descriptor too short ""(expected %i, got %i)\n", cfgno,
                USB_DT_CONFIG_SIZE, result);
            result =-EINVAL;goto err;}
        length =max((int)le16_to_cpu(desc->wTotalLength),
            USB_DT_CONFIG_SIZE);//得到wTotallLength/* Now that we know the length, get the whole thing *///根据得到的wTotallLength,申请用于存放这些信息的内存空间
        bigbuffer =kmalloc(length, GFP_KERNEL);if(!bigbuffer){
            result =-ENOMEM;goto err;}if(dev->quirks & USB_QUIRK_DELAY_INIT)msleep(100);//根据得到的wTotallLength,去获取完整的配置,接口,端口等描述符信息,把这些信息存放到bigbuffer里
        result =usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
            bigbuffer, length);if(result <0){dev_err(ddev,"unable to read config index %d ""descriptor/%s\n", cfgno,"all");kfree(bigbuffer);goto err;}if(result < length){dev_warn(ddev,"config index %d descriptor too short ""(expected %i, got %i)\n", cfgno, length, result);
            length = result;}//把用于存放配置等信息的地址保存在之前申请的rawdescriptors里
        dev->rawdescriptors[cfgno]= bigbuffer;//通过usb_parse_configuration函数,把获得的配置等信息按照类型进行分类 
        result =usb_parse_configuration(dev, cfgno,&dev->config[cfgno], bigbuffer, length);if(result <0){++cfgno;goto err;}}
    result =0;

err:kfree(desc);
    dev->descriptor.bNumConfigurations = cfgno;
err2:if(result ==-ENOMEM)dev_err(ddev,"out of memory\n");return result;}

这个函数主要分成二部分,第一部分是向usb device侧获取设备的配置,接口,端口信息。首先,它为这些信息申请存放空间,然后像之前获取设备描述符一样,先发送一个9 个字节的请求,用来获取配置,接口,端口等描述符的总长度,最后根据得到的总长度去得到完成 的设备配置,接口,端口信息;第二部分是把获取到的这个信息按照类别分别进行分类,并存到相应的结构中;

通过usb_parse_configuration函数,把获得的配置等信息按照类型进行分类。

staticintusb_parse_configuration(structusb_device*dev,int cfgidx,structusb_host_config*config,unsignedchar*buffer,int size){structdevice*ddev =&dev->dev;unsignedchar*buffer0 = buffer;int cfgno;int nintf, nintf_orig;int i, j, n;structusb_interface_cache*intfc;unsignedchar*buffer2;int size2;structusb_descriptor_header*header;int len, retval;
    u8 inums[USB_MAXINTERFACES], nalts[USB_MAXINTERFACES];unsigned iad_num =0;memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE);if(config->desc.bDescriptorType != USB_DT_CONFIG ||
        config->desc.bLength < USB_DT_CONFIG_SIZE ||
        config->desc.bLength > size){dev_err(ddev,"invalid descriptor for config index %d: ""type = 0x%X, length = %d\n", cfgidx,
            config->desc.bDescriptorType, config->desc.bLength);return-EINVAL;}
    cfgno = config->desc.bConfigurationValue;

    buffer += config->desc.bLength;
    size -= config->desc.bLength;

    nintf = nintf_orig = config->desc.bNumInterfaces;if(nintf > USB_MAXINTERFACES){dev_warn(ddev,"config %d has too many interfaces: %d, ""using maximum allowed: %d\n",
            cfgno, nintf, USB_MAXINTERFACES);
        nintf = USB_MAXINTERFACES;}/* Go through the descriptors, checking their length and counting the
     * number of altsettings for each interface */
    n =0;for((buffer2 = buffer, size2 = size);
          size2 >0;(buffer2 += header->bLength, size2 -= header->bLength)){if(size2 <sizeof(structusb_descriptor_header)){dev_warn(ddev,"config %d descriptor has %d excess ""byte%s, ignoring\n",
                cfgno, size2,plural(size2));break;}

        header =(structusb_descriptor_header*) buffer2;if((header->bLength > size2)||(header->bLength <2)){dev_warn(ddev,"config %d has an invalid descriptor ""of length %d, skipping remainder of the config\n",
                cfgno, header->bLength);break;}if(header->bDescriptorType == USB_DT_INTERFACE){structusb_interface_descriptor*d;int inum;

            d =(structusb_interface_descriptor*) header;if(d->bLength < USB_DT_INTERFACE_SIZE){dev_warn(ddev,"config %d has an invalid ""interface descriptor of length %d, ""skipping\n", cfgno, d->bLength);continue;}

            inum = d->bInterfaceNumber;if((dev->quirks & USB_QUIRK_HONOR_BNUMINTERFACES)&&
                n >= nintf_orig){dev_warn(ddev,"config %d has more interface ""descriptors, than it declares in ""bNumInterfaces, ignoring interface ""number: %d\n", cfgno, inum);continue;}if(inum >= nintf_orig)dev_warn(ddev,"config %d has an invalid ""interface number: %d but max is %d\n",
                    cfgno, inum, nintf_orig -1);/* Have we already encountered this interface?
             * Count its altsettings */for(i =0; i < n;++i){if(inums[i]== inum)break;}if(i < n){if(nalts[i]<255)++nalts[i];}elseif(n < USB_MAXINTERFACES){
                inums[n]= inum;
                nalts[n]=1;++n;}}elseif(header->bDescriptorType ==
                USB_DT_INTERFACE_ASSOCIATION){if(iad_num == USB_MAXIADS){dev_warn(ddev,"found more Interface ""Association Descriptors ""than allocated for in ""configuration %d\n", cfgno);}else{
                config->intf_assoc[iad_num]=(structusb_interface_assoc_descriptor*)header;
                iad_num++;}}elseif(header->bDescriptorType == USB_DT_DEVICE ||
                header->bDescriptorType == USB_DT_CONFIG)dev_warn(ddev,"config %d contains an unexpected ""descriptor of type 0x%X, skipping\n",
                cfgno, header->bDescriptorType);}/* for ((buffer2 = buffer, size2 = size); ...) */
    size = buffer2 - buffer;
    config->desc.wTotalLength =cpu_to_le16(buffer2 - buffer0);if(n != nintf)dev_warn(ddev,"config %d has %d interface%s, different from ""the descriptor's value: %d\n",
            cfgno, n,plural(n), nintf_orig);elseif(n ==0)dev_warn(ddev,"config %d has no interfaces?\n", cfgno);
    config->desc.bNumInterfaces = nintf = n;/* Check for missing interface numbers */for(i =0; i < nintf;++i){for(j =0; j < nintf;++j){if(inums[j]== i)break;}if(j >= nintf)dev_warn(ddev,"config %d has no interface number ""%d\n", cfgno, i);}/* Allocate the usb_interface_caches and altsetting arrays */for(i =0; i < nintf;++i){
        j = nalts[i];if(j > USB_MAXALTSETTING){dev_warn(ddev,"too many alternate settings for ""config %d interface %d: %d, ""using maximum allowed: %d\n",
                cfgno, inums[i], j, USB_MAXALTSETTING);
            nalts[i]= j = USB_MAXALTSETTING;}

        len =sizeof(*intfc)+sizeof(structusb_host_interface)* j;
        config->intf_cache[i]= intfc =kzalloc(len, GFP_KERNEL);if(!intfc)return-ENOMEM;kref_init(&intfc->ref);}/* FIXME: parse the BOS descriptor *//* Skip over any Class Specific or Vendor Specific descriptors;
     * find the first interface descriptor */
    config->extra = buffer;
    i =find_next_descriptor(buffer, size, USB_DT_INTERFACE,
        USB_DT_INTERFACE,&n);
    config->extralen = i;if(n >0)dev_dbg(ddev,"skipped %d descriptor%s after %s\n",
            n,plural(n),"configuration");
    buffer += i;
    size -= i;/* Parse all the interface/altsetting descriptors */while(size >0){
        retval =usb_parse_interface(ddev, cfgno, config,
            buffer, size, inums, nalts);if(retval <0)return retval;

        buffer += retval;
        size -= retval;}/* Check for missing altsettings */for(i =0; i < nintf;++i){
        intfc = config->intf_cache[i];for(j =0; j < intfc->num_altsetting;++j){for(n =0; n < intfc->num_altsetting;++n){if(intfc->altsetting[n].desc.
                    bAlternateSetting == j)break;}if(n >= intfc->num_altsetting)dev_warn(ddev,"config %d interface %d has no ""altsetting %d\n", cfgno, inums[i], j);}}return0;}

在这里插入图片描述
到这里为止,usb的设备枚举基本上已经完成 ,接下来系统会根据各接口的类型为它分配接口驱动,这里所说的接口驱动就是驱动工程师所需要去完成 的关于usb设备功能使用程序。当然,如果当前port上接的是一个hub,则它会调用hub_probe对hub进行配置。

8. Linux USB驱动架构

Linux内核支持两种主要类型的USB驱动程序:主机(host)系统上的驱动程序和设备(device)上的驱动程序。主机的USB控制器驱动程序控制插入其中的USB设备,主机的USB设备驱动程序控制该设备如何作为一个USB从设备和主机通信,由于“USB设备驱动程序”(USB devices drivers)容易混淆,USB开发者创建了术语“USB器件驱动程序”(USB gadget drivers)来描述控制连接到计算机的USB设备的功能实现。
接下来会讲解从主机侧看到的USB主机控制器驱动和设备驱动,以及从设备侧看到的设备控制器和Gadget驱动。

8.1 Linux USB驱动层次

8.1.1 主机侧和设备侧USB驱动

主机侧和设备侧的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实现上述功能。
编写gadget的关键是在于了解udc、gadget、composite三者之间的联系和架构层次,在实际应用中gadget是不需要我们去编写的,需要我们自己去编写的是composite层,以及地对udc层的修改。composite英文意思是复合的意思,编写usb gadget层设备驱动都整合到一起,通过统一的函数usb_composite_register注册。功能各异,杂七杂八,所以称为复合层。
USB驱动整体框架:
在这里插入图片描述

8.1.2 设备、配置、接口、端点

在USB 设备的逻辑组织中,包含设备、配置、接口和端点4 个层次,每个USB 设备都提供了不同级别的配置信息,可以包含一个或多个配置,不同的配置使设备表现出不同的功能组合,配置由多个接口组成,接口由多个端点组成,代表一个基本的功能,是USB 设备驱动程序控制的对象,如下图是USB 设备、配置、接口和端点之间的关系。
端点是 USB最基本的形式,每一个USB设备接口在主机看来就是一个端点的集合。主机只能通过端点与设备进行通信,以使用设备的功能。在USB系统中每一个端点都有唯一的地址,每个端点都有一定的属性,其中包括传输方式、总线访问频率、带宽、端点号和数据包的最大容量等。一个USB端点只能在一个 方向上承载数据,从主机到设备(称为输出端点)或者从设备到主机(称为输入端点),因此端点可以看成一个单向的管道。端点0通常是控制端点,用于设备初始化参数等。只要设备连接到USB上并且上电,端点 0就可被访问,端点1、2等一般用作数据端点,存放主机与设备间往来的数据。
在这里插入图片描述

  1. 设备通常有一个或多个配置;
  2. 配置通常有一个或多个接口;
  3. 接口有0个或多个端点;

USB是个通用的总线,端口都是统一的。但是USB设备却各种各样,例如USB鼠标,USB键盘,U盘等等,那么USB主机是如何识别出不同的设备的呢?这就要依赖于描述符了。USB的描述符主要有设备描述符,配置描述符,接口描述符,端点描述符,字符串描述符,HID描述符,报告描述符等等。
一个USB设备有一个设备描述符,设备描述符里面决定了该设备有多少种配置,每种配置描对应着配置描述符;而在配置描述符中又定义了该配置里面有多少个接口,每个接口有对应的接口描述符;在接口描述符里面又定义了该接口有多少个端点,每个端点对应一个端点描述符;端点描述符定义了端点的大小,类型等等。由此我们可以看出,USB的描述符之间的关系是一层一层的,最上一层是设备描述符,下面是配置描述符,再下面是接口描述符,再下面是端点描述符。在获取描述符时,先获取设备描述符,然后再获取配置描述符,根据配置描述符中的配置集合长度,依次将配置描述符、接口描述符、端点描述符一起一次读回。其中可能还会有获取设备序列号,厂商字符串,产品字符串等。

8.1.2.1 设备描述符

每个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。

8.1.2.2 配置描述符

一个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)。

事实上,对于配置描述符的标准请求中,有时wLength一项会大于实际配置描述符的长度(9字节),比如255。这样的效果便是:主机发送了一个Get_Descriptor_Configuration 的请求,设备会把接口描述符,端点描述符等后续描述符一并回给主机,主机则根据描述符头部的标志判断送上来的具体是何种描述符。

8.1.2.3 接口描述符

配置描述符中包含了一个或多个接口描述符,这里的“接口”并不是指物理存在的接口,在这里把它称之为“功能”更易理解些,例如一个设备既有录音的功能又有扬声器的功能,则这个设备至少就有两个“接口”。
接口描述符不能单独返回,要附着配置描述符后一并返回(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,则表示没有字符串。

8.1.2.4 端点描述符

端点是设备与主机之间进行数据传输的逻辑接口,除配置使用的端点0(控制端点,一般一个设备只有一个控制端点)为双向端口外,其它均为单向。端点描述符描述了数据的传输类型、传输方向、数据包大小和端点号(也可称为端点地址)等。端点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:表示端点查询的时间。对于中断端点以及同步传输,表示端点的轮询时间间隔,而对于块传输,该字段没有意义(介绍到具体的类设备时,该字段会作为重点解析的字段)。

8.1.2.5 字符串描述符

主机在获得配置描述符集合之后,会下发获得语言ID的请求(索引值为0),以及获得字符串描述符的请求。在USB协议中,字符串描述符是可选的。在设备描述符中,申请了三个非0的索引值:
1:是厂商字符串的索引值
2:是产品字符串的索引值
3:是产品序列号的索引值
主机通过索引值来获取对应的字符串数据。索引值为0则表示获取的是语言ID字符串。字符串描述符的结构很简单,如下:
首先是语言ID描述符的结构:
在这里插入图片描述
字符串描述符(产品+厂商+产品序列号)的结构:
在这里插入图片描述
说明:
1)语言ID,只使用美式英语的一种,即0x0409。
2)bString字段使用的是UNICODE编码的字符串,使用两个字节来表示一个字符。
3)如果设备描述符中的iManufacturer、iProduct、iSerialNumber都设置为0的话,主机就不会下发对设备字符串描述符的请求(调试),所以说,开发过程中,字符串描述符并不是必须的。但是,针对产品的研发,它又是必须的。

8.1.2.6 各个描述符之间的关系

1)一个设备,只有一个设备描述符。
2)一个设备描述符可以包含多个配置描述符。
3)一个配置描述符可以包含多个接口描述符。
4)一个接口描述符可以包含多个端点描述符。
描述符之间的包含关系如下图(站在集合的角度去理解):
在这里插入图片描述

8.2 USB主机控制器驱动

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主机控制器包括:

  1. UHCI: Universal Host Controller Interface (通用主机控制接口, USB1.0/1.1),与OHCI不兼容。UHCI在软件驱动层面就需要做的比较多,需要做得比较复杂。
  2. OHCI: Open Host Controller Interface (开放主机控制接口,USB1.0/1.1) ,不仅仅是针对USB,还支持其他的接口,比如支持Apple的火线(Firewire,IEEE 1394)接口。与UHCI相比,OHCI的硬件复杂,很多事情都交给硬件来做,所以实现对应的软件驱动的任务,就相对较简单。主要用于非x86的USB,如扩展卡、嵌入式开发板的USB主控。
  3. EHCI: Enhanced Host Controller Interface (用于USB2.0高速设备的“增强主机控制接口”),是Intel主导的USB2.0的接口标准。EHCI仅提供USB2.0的高速功能,而依靠UHCI或OHCI来提供对全速(full-speed)或低速(low-speed)设备的支持。
  4. 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主机控制器驱动让主机控制器工作起来,发挥它的潜力。 让控制器发数据、收数据 。主机控制器主要包含以下几步:

  1. 按照主机控制器的要求组织结构体
  2. 将结构体在合适的时间、放在合适的地方
  3. trigger
  4. 等待完成信号

8.2.1 主机控制器驱动

在Linux内核中,用usb_hcd结构体描述USB主机控制器驱动,它包含USB主机控制器的“家务”信息、硬件资源、状态描述和用于操作主机控制器的hc_driver等。

structusb_hcd{/* 管理“家务” */structusb_bus self;constchar*product_desc;/* 产品/厂商字符串 */char irq_descr[24];/* 驱动 + 总线 # */structtimer_list rh_timer;/* 根Hub轮询 */structurb*status_urb;/* 目前的状态urb *//* 硬件信息/状态 */conststructhc_driver*driver;/* 硬件特定的钩子函数 *//* 需要维护的标志 */unsignedlong flags;#defineHCD_FLAG_HW_ACCESSIBLE0x00000001#defineHCD_FLAG_SAW_IRQ0x00000002unsigned 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 = 无限制 */#defineHCD_BUFFER_POOLS4structdma_pool*pool[HCD_BUFFER_POOLS];int state;#define__ACTIVE                0x01#define__SUSPEND               0x04#define__TRANSIENT             0x80#defineHC_STATE_HALT0#defineHC_STATE_RUNNING(_ _ACTIVE)#defineHC_STATE_QUIESCING(_ _SUSPEND|_ _TRANSIENT|_ _ACTIVE)#defineHC_STATE_RESUMING(_ _SUSPEND|_ _TRANSIENT)#defineHC_STATE_SUSPENDED(_ _SUSPEND)#defineHC_IS_RUNNING(state)((state)& _ _ACTIVE)#defineHC_IS_SUSPENDED(state)((state)& _ _SUSPEND)/* 主机控制器驱动的私有数据 */unsignedlong hcd_priv[0]
                           _ _attribute_ _((aligned(sizeof(unsignedlong))));};

usb_hcd结构体中的hc_driver成员非常重要,它包含具体的用于操作主机控制器的钩子函数,即“hw-specific hooks”

structhc_driver{constchar*description;/* "ehci-hcd" 等 */constchar*product_desc;/* 产品/厂商字符串 */size_t hcd_priv_size;/* 私有数据的大小 *//* 中断处理函数 */irqreturn_t(*irq)(structusb_hcd*hcd,structpt_regs*regs);int flags;#defineHCD_MEMORY0x0001/* HC寄存器使用的内存和I/O */#defineHCD_USB110x0010/* USB 1.1 */#defineHCD_USB20x0020/* USB 2.0 *//* 被调用以初始化HCD和根Hub */int(*reset)(structusb_hcd*hcd);int(*start)(structusb_hcd*hcd);/* 挂起Hub后,进入D3(etc)前被调用 */int(*suspend)(structusb_hcd*hcd,pm_message_t message);/* 在进入D0(etc)后,恢复Hub前调用 */int(*resume)(structusb_hcd*hcd);/* 使HCD停止写内存和进行I/O操作 */void(*stop)(structusb_hcd*hcd);/* 返回目前的帧数 */int(*get_frame_number)(structusb_hcd*hcd);/* 管理I/O请求和设备状态 */int(*urb_enqueue)(structusb_hcd*hcd,structusb_host_endpoint*ep,structurb*urb,gfp_t mem_flags);int(*urb_dequeue)(structusb_hcd*hcd,structurb*urb);/* 释放endpoint资源 */void(*endpoint_disable)(structusb_hcd*hcd,structusb_host_endpoint*ep);/* 根Hub支持 */int(*hub_status_data)(structusb_hcd*hcd,char*buf);int(*hub_control)(structusb_hcd*hcd, u16 typeReq, u16 wValue, u16 wIndex,char*buf, u16 wLength);int(*bus_suspend)(structusb_hcd*);int(*bus_resume)(structusb_hcd*);int(*start_port_reset)(structusb_hcd*,unsigned port_num);void(*hub_irq_enable)(structusb_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)。

8.2.2 EHCI主机控制器

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目录即可。

structehci_hcd{/* one per controller *//* timing support */enumehci_hrtimer_event        next_hrtimer_event;unsigned                enabled_hrtimer_events;ktime_t                        hr_timeouts[EHCI_HRTIMER_NUM_EVENTS];structhrtimer                hrtimer;int                        PSS_poll_count;int                        ASS_poll_count;int                        died_poll_count;/* glue to PCI and HCD framework */structehci_caps __iomem *caps;structehci_regs __iomem *regs;structehci_dbg_port __iomem *debug;

        __u32                        hcs_params;/* cached register copy */spinlock_t                lock;enumehci_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;structehci_qh*qh_scan_next;/* async schedule support */structehci_qh*async;structehci_qh*dummy;/* For AMD quirk use */structlist_head        async_unlink;structlist_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 */#defineDEFAULT_I_TDPS1024/* some HCs can do less */unsigned                periodic_size;
        __hc32                        *periodic;/* hw periodic table */dma_addr_t                periodic_dma;structlist_head        intr_qh_list;unsigned                i_thresh;/* uframes HC might cache */union ehci_shadow        *pshadow;/* mirror hw periodic table */structlist_head        intr_unlink_wait;structlist_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 */structlist_head        cached_itd_list;structehci_itd*last_itd_to_free;structlist_head        cached_sitd_list;structehci_sitd*last_sitd_to_free;/* per root hub port */unsignedlong                reset_done[EHCI_MAX_ROOT_PORTS];//记录已经reset的port/* bit vectors (one bit per port) */unsignedlong                bus_suspended;/* which ports were
                        already suspended at the start of a bus suspend */unsignedlong                companion_ports;/* which ports are
                        dedicated to the companion controller */unsignedlong                owned_ports;/* which ports are
                        owned by the companion during a bus suspend */unsignedlong                port_c_suspend;/* which ports have
                        the change-suspend feature turned on */unsignedlong                suspended_ports;/* which ports are
                        suspended */unsignedlong                resuming_ports;/* which ports have
                        started to resume *//* per-HC memory pools (could be per-bus, but ...) */structdma_pool*qh_pool;/* qh per active urb */structdma_pool*qtd_pool;/* one or more per qh */structdma_pool*itd_pool;/* itd per iso urb */structdma_pool*sitd_pool;/* sitd per split iso urb */unsigned                random_frame;unsignedlong                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 */#defineOHCI_CTRL_HCFS(3<<6)#defineOHCI_USB_OPER(2<<6)#defineOHCI_USB_SUSPEND(3<<6)#defineOHCI_HCCTRL_OFFSET0x4#defineOHCI_HCCTRL_LEN0x4
        __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 */#ifdefEHCI_STATSstructehci_stats        stats;#defineCOUNT(x)((x)++)#else#defineCOUNT(x)#endif/* debug files */#ifdefCONFIG_DYNAMIC_DEBUGstructdentry*debug_dir;#endif/* bandwidth usage */#defineEHCI_BANDWIDTH_SIZE64#defineEHCI_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 */structlist_head        tt_list;/* platform-specific data -- must come last */unsignedlong                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。

staticconststructhc_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即可。

8.3 USB设备驱动

8.3.1 USB设备驱动的整体结构

这里所说的USB设备驱动是从USB主机侧的角度看,怎么访问被插入的USB设备,而不是指USB设备内部本身运行的固件程序。Linux系统实现了几类通用的USB设备驱动(也称客户驱动),划分为如下几个 设备类:

  • 音频设备类;
  • 通信设备类;
  • HID(人机接口)设备类;
  • 显示设备类;
  • 海量存储设备类;
  • 电源设备类;
  • 打印设备类;
  • 集线器设备类; 一般通用的Linux设备(如U盘、USB鼠标 、USB键盘等)都不需要工程师再编写驱动,而工程师需要编写的是特定厂商、特定芯片的驱动,而且往往也可以参考已经在内核中提供的驱动模板。 在Linux内核中使用usb_driver结构体描述一个USB设备驱动。
structusb_driver{constchar*name;int(*probe)(structusb_interface*intf,conststructusb_device_id*id);void(*disconnect)(structusb_interface*intf);int(*unlocked_ioctl)(structusb_interface*intf,unsignedint code,void*buf);int(*suspend)(structusb_interface*intf,pm_message_t message);int(*resume)(structusb_interface*intf);int(*reset_resume)(structusb_interface*intf);int(*pre_reset)(structusb_interface*intf);int(*post_reset)(structusb_interface*intf);conststructusb_device_id*id_table;structusb_dynids dynids;structusbdrv_wrap drvwrap;unsignedint no_dynamic_id:1;unsignedint supports_autosuspend:1;unsignedint disable_hub_initiated_lpm:1;unsignedint 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设备连接和断开的作用。

8.3.2 USB请求块URB

8.3.2.1 URB结构体

USB请求块(USB Request Block,URB)是USB设备驱动中用来描述与USB设备通信所用的基本载体和核心数据结构。

structurb{/* 私有的:只能由USB 核心和主机控制器访问的字段 */structkref kref;/*urb 引用计数 */void*hcpriv;/* 主机控制器私有数据 */atomic_t use_count;/* 并发传输计数 */
      u8 reject;/* 传输将失败*/int     unlink;/* unlink 错误码 *//* 公共的: 可以被驱动使用的字段 */structlist_head urb_list;/* 链表头*/structusb_anchor*anchor;structusb_device*dev;/* 关联的USB 设备 */structusb_host_endpoint*ep;unsignedint     pipe;/* 管道信息 */int     status;/* URB 的当前状态 */unsignedint     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 结束后,发送或接收数据的实际长度 */unsignedchar*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 一次可定义多个等时传输时,描述各个等时传输 */structusb_iso_packet_descriptor iso_frame_desc[0];};

8.3.2.2 URB处理流程

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);

8.3.2.3 简单的批量与控制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()函数掌握足够的信息,以判断和等待该调用的结束。

8.3.3 探测和断开函数

在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);

8.3.4 USB骨架程序

在Linux内核的开源代码中的drivers/usb/usb-skeleton.c文件为我们提供了一个最基本的USB驱动程序,即USB骨架程序。尽管具体的USB设备驱动程序千差万别,但是其骨架万变不离其宗。
首先看USB骨架程序的usb_driver结构体,定义如下:

staticstructusb_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[],代码如下:

staticconststructusb_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 中:

#defineusb_register(driver)\usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)voidusb_deregister(structusb_driver*driver){pr_info("%s: deregistering interface driver %s\n",
                        usbcore_name, driver->name);usb_remove_newid_files(driver);driver_unregister(&driver->drvwrap.driver);usb_free_dynids(driver);}EXPORT_SYMBOL_GPL(usb_deregister);#definemodule_driver(__driver, __register, __unregister,...)\staticint __init __driver##_init(void)\{\return__register(&(__driver),##__VA_ARGS__);\}\module_init(__driver##_init);\staticvoid __exit __driver##_exit(void)\{\__unregister(&(__driver),##__VA_ARGS__);\}\module_exit(__driver##_exit);#definemodule_usb_driver(__usb_driver)\module_driver(__usb_driver, usb_register,\usb_deregister)

USB的骨架程序的模块加载代码如下:

staticstructusb_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设备,代码清单如下:

staticintskel_probe(structusb_interface*interface,conststructusb_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 endpoints\n");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);
        ……
        return0;
        ……
}

usb_skel 结构体可以被看作一个私有数据结构,代码定义如下:

/* Structure to hold all of our device specific stuff */structusb_skel{structusb_device*udev;/* the usb device for this device */structusb_interface*interface;/* the interface for this device */structsemaphore        limit_sem;/* limiting the number of writes in progress */structusb_anchor        submitted;/* in case we need to retract our submissions */structurb*bulk_in_urb;/* the urb to read data with */unsignedchar*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 */structkref                kref;structmutex                io_mutex;/* synchronize I/O with disconnect */wait_queue_head_t        bulk_in_wait;/* to wait for an ongoing read */};

断开函数会完成与probe() 相反的工作,设置接口数据为NULL,注销USB设备,代码如下:

staticvoidskel_disconnect(structusb_interface*interface){structusb_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字符设备的另一个组成部分。代码如下:

staticconststructfile_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,代码如下:

staticintskel_open(structinode*inode,structfile*file){structusb_skel*dev;structusb_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()),代码如下:

staticssize_tskel_write(structfile*file,constchar*user_buffer,size_t count,loff_t*ppos){structusb_skel*dev;int retval =0;structurb*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的判断,代码如下:

staticvoidskel_write_bulk_callback(structurb*urb){structusb_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: %d\n",__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);}

8.3.5 实例:USB键盘驱动

在Linux系统中,键盘被认定为标准输入设备,对于USB键盘,驱动主要由两部分组成:usb_driver(USB外设驱动)的成员函数和输入设备驱动的input_event获取和报告。
USB键盘设备驱动的模块加载和卸载函数,分别注册和注销对应于USB键盘的usb_driver结构体usb_kbd_driver,如下代码清单为模块加载与卸载函数以及usb_driver结构体的定义。

staticstructusb_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);staticstructusb_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的初始化,并设置接口的私有数据,如下代码清单所示。

staticintusb_kbd_probe(structusb_interface*iface,conststructusb_device_id*id){structusb_device*dev =interface_to_usbdev(iface);structusb_host_interface*interface;structusb_endpoint_descriptor*endpoint;structusb_kbd*kbd;structinput_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(structusb_kbd), GFP_KERNEL);// 分配内存
     input_dev =input_allocate_device();// 动态初始化input_devif(!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);return0;
    
fail2:usb_kbd_free_mem(dev, kbd);
fail1:input_free_device(input_dev);kfree(kbd);return error;}

usb_driver的断开函数,设置接口私有数据为NULL、终止已提交的URB、注销输入设备,如下代码清单所示。

staticvoidusb_kbd_disconnect(structusb_interface*intf){structusb_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()报告同步事件,如下代码清单所示。

staticvoidusb_kbd_irq(structurb*urb){structusb_kbd*kbd = urb->context;int i;switch(urb->status){case0:/* 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);elsehid_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);elsehid_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。

8.4 USB UDC与Gadget驱动

8.4.1 UDC(USB设备控制器)和Gadget(器件)驱动的基本概念

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接口暴露给应用层,以便在应用层实现用户空间的驱动。

8.4.2 关键数据结构与API

USB器件控制器驱动,需要关心几个核心的数据结构,包括描述一个USB器件控制器的usb_gadget、UDC操作usb_gadget_ops、描述一个端点的usb_ep以及描述端点操作的usb_ep_ops结构体等。UDC驱动围绕这些数据结构及其成员函数展开,下面列出这些关键的数据结构,路径:include/linux/usb/gadget.h。

structusb_gadget{structwork_struct              work;/* readonly to gadget driver */conststructusb_gadget_ops*ops;structusb_ep*ep0;structlist_head                ep_list;/* of usb_ep */enumusb_device_speed           speed;enumusb_device_speed           max_speed;enumusb_device_state           state;constchar*name;structdevice                   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;};structusb_ep{void*driver_data;constchar*name;conststructusb_ep_ops*ops;structlist_head        ep_list;unsigned                maxpacket:16;unsigned                maxpacket_limit:16;unsigned                max_streams:16;unsigned                mult:2;unsigned                maxburst:5;
        u8                      address;conststructusb_endpoint_descriptor*desc;conststructusb_ss_ep_comp_descriptor*comp_desc;enumep_type            ep_type;
        u8                      ep_num;
        u8                      ep_intr_num;
        bool                    endless;};structusb_gadget_ops{int(*get_frame)(structusb_gadget*);int(*wakeup)(structusb_gadget*);int(*func_wakeup)(structusb_gadget*,int interface_id);int(*set_selfpowered)(structusb_gadget*,int is_selfpowered);int(*vbus_session)(structusb_gadget*,int is_active);int(*vbus_draw)(structusb_gadget*,unsigned mA);int(*pullup)(structusb_gadget*,int is_on);int(*restart)(structusb_gadget*);int(*ioctl)(structusb_gadget*,unsigned code,unsignedlong param);void(*get_config_params)(structusb_dcd_config_params*);int(*udc_start)(structusb_gadget*,structusb_gadget_driver*);int(*udc_stop)(structusb_gadget*,structusb_gadget_driver*);};structusb_ep_ops{int(*enable)(structusb_ep*ep,conststructusb_endpoint_descriptor*desc);int(*disable)(structusb_ep*ep);structusb_request*(*alloc_request)(structusb_ep*ep,gfp_t gfp_flags);void(*free_request)(structusb_ep*ep,structusb_request*req);int(*queue)(structusb_ep*ep,structusb_request*req,gfp_t gfp_flags);int(*dequeue)(structusb_ep*ep,structusb_request*req);int(*set_halt)(structusb_ep*ep,int value);int(*set_wedge)(structusb_ep*ep);int(*fifo_status)(structusb_ep*ep);void(*fifo_flush)(structusb_ep*ep);int(*gsi_ep_op)(structusb_ep*ep,void*op_data,enumgsi_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中,其形式如代码清单如下所示。

structusb_function{constchar*name;int                             intf_id;structusb_gadget_strings**strings;structusb_descriptor_header**fs_descriptors;structusb_descriptor_header**hs_descriptors;structusb_descriptor_header**ss_descriptors;structusb_configuration*config;structusb_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)(structusb_configuration*,structusb_function*);void(*unbind)(structusb_configuration*,structusb_function*);void(*free_func)(structusb_function*f);structmodule*mod;/* runtime state management */int(*set_alt)(structusb_function*,unsigned interface,unsigned alt);int(*get_alt)(structusb_function*,unsigned interface);void(*disable)(structusb_function*);int(*setup)(structusb_function*,conststructusb_ctrlrequest*);void(*suspend)(structusb_function*);void(*resume)(structusb_function*);/* USB 3.0 additions */int(*get_status)(structusb_function*);int(*func_suspend)(structusb_function*, u8 suspend_opt);unsigned                func_is_suspended:1;unsigned                func_wakeup_allowed:1;unsigned                func_wakeup_pending:1;/* private: *//* internals */structlist_head                list;DECLARE_BITMAP(endpoints,32);conststructusb_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结构体的定义如下。

structusb_request{void*buf;unsigned                length;dma_addr_t              dma;structscatterlist*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)(structusb_ep*ep,structusb_request*req);void*context;structlist_head        list;int                     status;unsigned                actual;unsigned                udc_priv;};

在include/linux/usb/gadget.h文件中,还封装了一些常用的API,以供Gadget Function驱动调用,从而便于它们操作端点。
USB Gadget driver对象。

structusb_gadget_driver{char*function;//驱动名称enumusb_device_speed speed;//USB设备速度类型int(*bind)(structusb_gadget*);//将驱动和设备绑定,一般在驱动注册时调用void(*unbind)(structusb_gadget*);//卸载驱动时调用,rmmod时调用int(*setup)(structusb_gadget*,conststructusb_ctrlrequest*);//处理ep0的控制请求,在中断中调用,不能睡眠void(*disconnect)(structusb_gadget*);//可能在中断中调用不能睡眠void(*suspend)(structusb_gadget*);//电源管理模式相关,设备挂起void(*resume)(structusb_gadget*);//电源管理模式相关,设备恢复/* FIXME support safe rmmod */structdevice_driver driver;//内核设备管理使用};

字符串结构。

structusb_gadget_strings{
    u16 language;/* 0x0409 for en-us */structusb_string*strings;};structusb_string{
    u8 id;//索引constchar*s;};

UDC驱动程序需要实现的上层调用接口
int usb_gadget_register_driver(struct usb_gadget_driver *driver);
int usb_gadget_unregister_driver(struct usb_gadget_driver *driver);

8.4.3 UDC驱动程序

UDC层主要数据结构,以S3C2410为例,在driver/usb/gadget/s3c2410_udc.c和s3c2410_udc.h文件中。下面的结构基本上每个UDC驱动程序都会实现,但具体实现的细节又不太相同。但万变不离其宗,宗就是上面介绍的基本gadget驱动数据结构,基本上UDC驱动程序自己实现的数据结构都是都这些基本数据结构的二次封装。
a. 设备结构

structs3c2410_udc{spinlock_t lock;structs3c2410_ep ep[S3C2410_ENDPOINTS];int address;structusb_gadget gadget;structusb_gadget_driver*driver;structs3c2410_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;structdentry*regs_info;};

程序中对这个结构的初始化:

staticstructs3c2410_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. 端点结构

structs3c2410_ep{structlist_head queue;unsignedlong last_io;/* jiffies timestamp */structusb_gadget*gadget;structs3c2410_udc*dev;conststructusb_endpoint_descriptor*desc;structusb_ep ep;//封装的struct usb_ep结构
    u8 num;unsignedshort fifo_size;
    u8 bEndpointAddress;
    u8 bmAttributes;unsigned halted :1;unsigned already_seen :1;unsigned setup_stage :1;};

对usb_ep结构进行封装,并有一个queue队列来对该端口上的request进行排队。
c. Request结构

structs3c2410_request{structlist_head queue;/* ep's requests */structusb_request req;};

对usb_request进行封装,queue变量进行队列排队。
1. UDC驱动是作为platform driver向platform子系统注册的,因此UDC驱动首先就需要实现struct platform_driver结构中的函数成员:

structplatform_driver{int(*probe)(structplatform_device*);//驱动和设备绑定int(*remove)(structplatform_device*);//支持热插拔的设备移除void(*shutdown)(structplatform_device*);//设备关闭int(*suspend)(structplatform_device*,pm_message_t state);//电源管理相关,挂起设备int(*resume)(structplatform_device*);//电源管理相关,恢复设备structdevice_driver driver;structplatform_device_id*id_table;//驱动和设备匹配信息};

在下面的源码分析中以s3c2410_udc.c文件为例:

staticstructplatform_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函数实现资源的释放。

staticints3c2410_udc_probe(structplatform_device*pdev){structs3c2410_udc*udc =&memory;//s3c2410的UDC设备,在其中对usb_gadget设备对象和端点等进行了初始化structdevice*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 failed\n");}dev_dbg(dev,"probe ok\n");return0;

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设备和驱动的一些初始化工作,并申请驱动所需的资源,若端口区间、中断号等,并将中断函数和中断号绑定。这个中断处理函数非常重要,对这个设备的所有操作都将从中断函数入口。

staticints3c2410_udc_remove(structplatform_device*pdev){structs3c2410_udc*udc =platform_get_drvdata(pdev);//获取UDC设备对象,在probe函数中绑定的unsignedint 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 ok\n",__func__);return0;}

可以看出,remove函数基本上是probe函数的逆操作,将probe函数中申请的资源释放掉。
2. UDC驱动程序还需要为上层实现usb_gadget_register_driver和usb_gadget_unregister_driver两个gadget driver注册接口,这两个函数将实现gadget driver和udc driver绑定。

intusb_gadget_register_driver(structusb_gadget_driver*driver){structs3c2410_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 %d\n",driver->bind, driver->setup, driver->speed);return-EINVAL;}//支持卸载的话,还需要实现unbind函数#ifdefined(MODULE)if(!driver->unbind){printk(KERN_ERR "Invalid driver: no unbind method\n");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() : %d\n",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);return0;
register_error:
         udc->driver =NULL;
         udc->gadget.dev.driver =NULL;return retval;}/*gadget 驱动注销函数*/intusb_gadget_unregister_driver(structusb_gadget_driver*driver){structs3c2410_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);return0;}

3. 中断函数
中断处理函数是UDC驱动层的核心函数,由于UDC是从设备,主机端是控制端,所有的操作都是主机端发起的,所以中断处理函数是UDC驱动层的核心函数。

staticirqreturn_ts3c2410_udc_irq(int dummy,void*_dev){structs3c2410_udc*dev = _dev;//UDC设备对象int usb_status;int usbd_status;int pwr_reg;int ep0csr;int i;
      u32 idx;unsignedlong 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=%02x\n", 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 %x\n", 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 resume\n");/* 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 suspend\n");/* 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 irq\n");/* 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 irq\n", 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的处理函数,一个是其他端点的处理函数。

staticvoids3c2410_udc_handle_ep0(structs3c2410_udc*dev){
       u32 ep0csr;structs3c2410_ep*ep =&dev->ep[0];structs3c2410_request*req;structusb_ctrlrequest crq;if(list_empty(&ep->queue))
              req =NULL;else
              req =list_entry(ep->queue.next,structs3c2410_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 %s\n", 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
其他端点的处理函数,主要是数据发送和接收
*/staticvoids3c2410_udc_handle_ep(structs3c2410_ep*ep){structs3c2410_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,structs3c2410_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 %d\n", idx, ep_csr1, req ?1:0);if(ep_csr1 & S3C2410_UDC_ICSR1_SENTSTL){dprintk(DEBUG_VERBOSE,"st\n");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:%02x\n", 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);}}}

端点操作函数集:端点的基本操作函数

staticconststructusb_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这个函数,因为上层主要和这个函数打交道,接收或发送数据都需要对应的端点队列提交请求

staticints3c2410_udc_queue(structusb_ep*_ep,structusb_request*_req,gfp_t gfp_flags){structs3c2410_request*req =to_s3c2410_req(_req);structs3c2410_ep*ep =to_s3c2410_ep(_ep);structs3c2410_udc*dev;
          u32 ep_csr =0;int fifo_count =0;unsignedlong flags;if(unlikely(!_ep ||(!ep->desc && ep->ep.name != ep0name))){dprintk(DEBUG_NORMAL,"%s: invalid args\n",__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 X\n",__func__);else{dprintk(DEBUG_NORMAL,"%s: 0 %01d %01d %01d\n",__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 %d\n",__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 */){//端点0switch(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;}}elseif((ep->bEndpointAddress & USB_DIR_IN)!=0&&(!(ep_csr&S3C2410_UDC_OCSR1_PKTRDY))&&s3c2410_udc_write_fifo(ep, req)){
                           req =NULL;}elseif((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 ok\n",__func__);return0;}

UDC驱动中还有一个重要函数,在数据传输或接收完成,即一个请求完成之后,会调用这个请求的完成函数来通知上层驱动。

staticvoids3c2410_udc_done(structs3c2410_ep*ep,structs3c2410_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设备驱动层主要需要做以下几个工作:

  1. 对usb_gadget、usb_ep、usb_request三个标准数据结构进行封装,根据自己UDC的一些设备特性,设计对应的自己的数据结构;
  2. 实现platform_driver数据结构中的函数,将UDC设备驱动向platform系统进行注册;
  3. 实现usb_gadget_ops函数集,这些函数主要是操作UDC设备的一些特性(针对设备);
  4. 实现usb_ep_ops函数集,这些函数主要是操作端点的功能,如请求分配和提交等(针对端点);
  5. 实现UDC设备的中断处理函数,这个函数基本上就是UDC设备驱动的核心;
  6. 实现上层功能驱动注册接口函数: int usb_gadget_register_driver(struct usb_gadget_driver *driver) int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)

8.4.4 USB Gadget FUNC API

Gadget设备层
这一层是可选的,介于UDC驱动层和Gadget功能层之间。主要源码在composite.c和composite.h文件中,设备层其实和硬件无关,主要实现一些通用性的代码,减少gadget功能层的代码重复工作。Gadget设备层其中承上启下的作用,联系Gadget功能层和UDC驱动层。
将composite源码独立出来,还为复合设备的实现提供了一个通用性的框架。复合设备是指在一个配置描述符中支持多个功能,或者支持多个配置的设备中,每个配置都有一个不同的功能。如一个设备同时支持网络和存储,一个设备同时支持键盘和鼠标功能等。
Gadget设备层的主要数据结构:
1.Function

structusb_function{//描述一个配置的一个功能constchar*name;//功能名称structusb_gadget_strings**strings;//string数组,通过bind中分配的id访问structusb_descriptor_header**descriptors;//全速和低速的描述符表,用于bind中分配的接口描述符和string描述符structusb_descriptor_header**hs_descriptors;//高速描述符表structusb_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)(structusb_configuration*,structusb_function*);/*unbind的逆操作*/void(*unbind)(structusb_configuration*,structusb_function*);/* runtime state management *//*重新配置altsetting,*/int(*set_alt)(structusb_function*,unsigned interface,unsigned alt);/*获取当前altsetting*/int(*get_alt)(structusb_function*,unsigned interface);/*关闭功能*/void(*disable)(structusb_function*);/*接口相关的控制处理*/int(*setup)(structusb_function*,conststructusb_ctrlrequest*);/*电源管理相关的挂起和恢复功能*/void(*suspend)(structusb_function*);void(*resume)(structusb_function*);/* private: *//* internals */structlist_head list;DECLARE_BITMAP(endpoints,32);};

2.Config

structusb_configuration{//表示一个Gadget配置constchar*label;//配置名称structusb_gadget_strings**strings;//字符串表conststructusb_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)(structusb_configuration*);void(*unbind)(structusb_configuration*);/*处理驱动框架不能处理的配置控制请求*/int(*setup)(structusb_configuration*,conststructusb_ctrlrequest*);/* fields in the config descriptor *//*用来赋值配置描述符*/
       u8 bConfigurationValue;
       u8 iConfiguration;
       u8 bmAttributes;
       u8 bMaxPower;/*和composite设备关联,在usb_add_config函数中设置*/structusb_composite_dev*cdev;/* private: *//* internals */structlist_head list;structlist_head functions;//功能链表
       u8 next_interface_id;unsigned highspeed:1;unsigned fullspeed:1;structusb_function*interface[MAX_CONFIG_INTERFACES];};

3. Driver

structusb_composite_driver{constchar*name;//驱动名称conststructusb_device_descriptor*dev;//设备描述符structusb_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)(structusb_composite_dev*);int(*unbind)(structusb_composite_dev*);/* global suspend hooks */void(*suspend)(structusb_composite_dev*);void(*resume)(structusb_composite_dev*);};

4. Dev

structusb_composite_dev{//表示一个composite设备structusb_gadget*gadget;//关联的gadgetstructusb_request*req;//用于控制响应,提前分配的unsigned bufsiz;//req中提前分配的buffer长度structusb_configuration*config;//当前配置/* private: *//* internals */structusb_device_descriptor desc;structlist_head configs;//配置链表structusb_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;};

8.4.5 Gadget FUNC 驱动

Gadget功能层完成USB设备的具体功能,其表现的形式各不相同,如键盘、鼠标、存储和网卡等等。功能层不仅涉及到Gadget驱动相关的内容,还涉及到其功能相关的内核子系统。如存储还涉及到内核存储子系统,网卡还涉及到网络驱动子系统。因此,Gadget功能的代码非常复杂。这里以zero.c为例,这个模块只是简单地将接收的数据回显回去。
一、数据结构
首先需要实现usb_composite_driver函数集:

staticstructusb_composite_driver zero_driver ={.name ="zero",.dev =&device_desc,.strings = dev_strings,.bind = zero_bind,.unbind = zero_unbind,.suspend = zero_suspend,.resume = zero_resume,};

二、主要函数
这个模块的实现就是这么简单:

staticint __init init(void){returnusb_composite_register(&zero_driver);}module_init(init);staticvoid __exit cleanup(void){usb_composite_unregister(&zero_driver);}

Bind函数是功能层需要实现与设备层关联的重要函数:

staticint __init zero_bind(structusb_composite_dev*cdev){int gcnum;structusb_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,(unsignedlong) 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);}return0;}/*增加数据简单回显功能*/int __init loopback_add(structusb_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;}returnusb_add_config(cdev,&loopback_driver);//增加一个配置}/*loopback配置*/staticstructusb_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函数,来分配这个配置所需要的资源。
structf_loopback{structusb_function function;structusb_ep*in_ep;structusb_ep*out_ep;};staticint __init loopback_bind_config(structusb_configuration*c){structf_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函数:

staticint __init  loopback_bind(structusb_configuration*c,structusb_function*f){structusb_composite_dev*cdev = c->cdev;structf_loopback*loop =func_to_loop(f);int id;/* allocate interface ID(s) */
             id =usb_interface_id(c, f);//分配一个接口idif(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 %s\n", 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/%s\n",gadget_is_dualspeed(c->cdev->gadget)?"dual":"full",f->name, loop->in_ep->name, loop->out_ep->name);return0;}

功能的实现
Loopback_set_alt函数将在设备层的setup函数中被调用,控制通信设置接口。

staticintloopback_set_alt(structusb_function*f,unsigned intf,unsigned alt){structf_loopback*loop =func_to_loop(f);structusb_composite_dev*cdev = f->config->cdev;/* we know alt is zero */if(loop->in_ep->driver_data)disable_loopback(loop);returnenable_loopback(cdev, loop);//开启功能}staticintenable_loopback(structusb_composite_dev*cdev,structf_loopback*loop){int result =0;conststructusb_endpoint_descriptor*src,*sink;structusb_ep*ep;structusb_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 --> %d\n", ep->name, result);}else{usb_ep_disable(ep);
                      ep->driver_data =NULL;
                      result =-ENOMEM;goto fail0;}}DBG(cdev,"%s enabled\n", loop->function.name);return result;}/*接收到数据之后,将调用这个完成函数*/staticvoidloopback_complete(structusb_ep*ep,structusb_request*req){structf_loopback*loop = ep->driver_data;structusb_composite_dev*cdev = loop->function.config->cdev;int status = req->status;switch(status){case0:/* 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: %d\n", 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/%d\n", 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;}}

8.5 USB OTG

OTG是On-The-Go的缩写。其设计的初衷是为了两个“外设”在没有PC(Host)的情况下,也可以通过USB进行数据传输。可以理解为,拥有OTG功能的USB设备(OTG设备)既可以做host,也可以做peripheral。

8.6 总结

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驱动交互。

9. Linux USB热插拔

9.1 硬件知识(USB插座和插头)

在最初的标准里,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线缆并没有遵循标准,所以大家在使用时要小心,用表测量一下比较可靠。

9.2 集线器把USB设备的连接报告给USB主控制器

首先,在USB集线器的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。而在USB设备端,在D+或者D-上接了1.5K欧姆上拉电阻。对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。这样,当设备插入到集线器时,由1.5K的上拉电阻和15K的下拉电阻分压,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器,这样就检测到设备的插入了。USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到高速模式的。在高速模式下,是电流传输模式,这时将D+上的上拉电阻断开。

9.3 热插拔

热插拔(hot-plugging或Hot Swap)即带电插拔,热插拔功能就是允许用户在不关闭系统,不切断电源的情况下取出和更换损坏的硬盘、电源或板卡等部件,从而提高了系统对灾难的及时恢复能力、扩展性和灵活性等。

9.3.1 热插拔好处

系统中加入热插拔的好处包括:

  • 在系统开机情况下将损坏的模块移除,还可以在开机情况下做更新或扩容而不影响系统操作。
  • 由于热插拔零件的可靠度提升,还可以将它们用做断电器,而且因为热插拔能够自动恢复,有很多热插拔芯片为系统提供线路供电情况的信号,以便系统做故障分析,因此减少了成本。

9.4 Linux 下USB热插拔处理

9.4.1 Linux下USB HUB的驱动的实现和分析:

在系统初始化的时候在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函数来配置端口等。

9.4.2 软件层次分析

这里我们先讲讲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 ]
intusb_hub_init(void){if(usb_register(&hub_driver)<0){printk(KERN_ERR "%s: can't register hub driver\n",
                        usbcore_name);return-1;}
 
        khubd_task =kthread_run(hub_thread,NULL,"khubd");if(!IS_ERR(khubd_task))return0;/* Fall through if kernel_thread failed */usb_deregister(&hub_driver);printk(KERN_ERR "%s: can't start khubd\n", usbcore_name);return-1;}

这里我们只关心kthread_run(hub_thread, NULL, “khubd”); 然后我们看hub_thread函数。

staticinthub_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 exiting\n", usbcore_name);return0;}

这里我们看到了hub_events()函数。然后设置运行状态,如果有事件就加入hub_event_list。自此khubd运行起来了。
在这里插入图片描述
在这里插入图片描述
通过流程图我们可以清晰的明白,当usb设备插入usb接口后,khubd运行,它检测到port状态的变化,调用hub_port_connect_change(),如果是新设备那么usb_allco_dev,然后调用usb_new_device来进行配置使usb设备可以正常工作。


本文转载自: https://blog.csdn.net/qq_41483419/article/details/128788133
版权归原作者 庐州拎壶冲 所有, 如有侵权,请联系我们删除。

“USB基础知识总结”的评论:

还没有评论