1、什么是USB
USB的全称是Universal Serial Bus,通用串行总线。它的出现主要是为了简化个人计算机与外围设备的连接,增加易用性。USB支持热插拔,并且是即插即用的,另外,它还具有很强的可扩展性,传输速度也很快,这些特性使支持USB接口的电子设备更易用、更大众化。
本文将从USB协议、枚举流程、host和device驱动等各方面,全面介绍Linux USB模块的工作原理和代码流程,下面就请随我一起,遨游多姿多彩而又复杂严谨的USB世界吧~
2、USB传输基础知识介绍
2.1、USB金字塔型拓扑结构
2.1.1、USB协议基础
塔顶为USB主控制器和根集线器(Root Hub),下面接USB集线器(Hub),集线器将一个USB口扩展为多个USB口,USB2.0规定集线器的层数最多为6层,理论上一个USB主控制器最多可接127个设备,因为协议规定USB设备具有一个7 bit的地址(取值范围为0~127,而地址0是保留给未初始化的设备使用的)。
2.1.2、NRZI编码
USB采用差分信号传输,使用的是如上图所示的NRZI编码方式:数据为0时,电平翻转;数据为1时,电平不翻转。如果出现6个连续的数据1,则插入一个数据0,强制电平翻转,以便时钟同步。上面的一条线表示的是原始数据序列,下面的一条线表示的是经过NRZI编码后的数据序列。
2.1.3、包(packet)格式
USB总线上的传输数据是以包为基本单位的,包格式如上图所示。根据PID的不同,USB协议中规定的包类型有令牌包、数据包、握手包和特殊包等。
USB芯片(硬件)会完成CRC校验、位填充、PID识别、数据包切换、握手等协议处理。
2.1.4、USB数据传输规范和约定
-
USB传输是主从模式,主机负责发起数据传输过程,从机负责应答。
-
USB传输使用小端结构(Little-Endian),一个字节在USB总线上的传输先后顺序为:b0 b1 b2 …b7 (与I2C相反,I2C是大端结构)。
-
数据传输方向均以主机为参考
比如启动USB传输的令牌包名称
IN令牌包 用来通知设备返回一个数据包
数据包的传输方向:主机←从机( IN )
OUT令牌包 用来通知设备将要输出一个数据包
数据包的传输方向:主机→从机( OUT )
2.1.5、四种传输模式
针对不同的数据传输场景,USB分为四种数据传输模式,这四种传输模式分别由不同的包(packet)组成,并且有不同的数据处理策略。每种数据传输模式的流程示意图以及应用场景如下:
- 控制传输—— Control Transfers
用于枚举过程,要保证数据传输过程的数据完整性。
- 批量传输—— Bulk Transfers
用于数据量大、对实时性要求不高的场合,如U盘。
- 中断传输—— Interrupt Transfers
- 同步传输(等时传输)—— Isochronous Transfers
用于数据量大、同时对实时性要求较高的场合,如音视频。
不保证数据完整性,没有ACK/NAK应答包,不进行数据重传。
2.1.6、USB设备结构及描述符
一个USB设备通常有一个或多个配置,但在同一时刻只能有一个配置;
一个配置通常有一个或多个接口;
一个接口通常有一个或多个端点;
驱动是绑定到USB接口上的,而不是整个USB设备。
枚举过程中,device将各种描述符返回给host。
2.2、Linux USB驱动总体结构
Linux USB驱动总体结构图
从Host侧看,在Linux驱动中,处于USB驱动最底层的是USB主机控制器硬件,在其上运行的是USB主机控制器驱动,在主机控制器上的为USB核心层,再上层为USB设备驱动层(插入主机上的U盘、鼠标、USB转串口等设备驱动)。主机控制器驱动负责识别和控制插入其中的USB设备,USB设备驱动控制USB设备如何与主机通信,USB Core则负责USB驱动管理和协议处理的主要工作。
从Device侧看,UDC驱动程序直接访问硬件,控制USB设备和主机间的底层通信。Gadget API是UDC驱动程序回调函数的包装。Gadget Driver具体控制USB设备功能的实现。
2.3、USB描述符
对应上述USB设备的构成,USB采用描述符来描述USB设备的属性,在USB协议的第九章(chaper 9)中,有对USB描述符的详细说明,在Linux驱动的以下文件中,定义了USB描述符的结构体,文件名直接命名为ch9.h。
设备描述符结构体
/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
__u8 bLength; //该描述符结构体大小(18字节)
__u8 bDescriptorType; //描述符类型(本结构体中固定为0x01)
__le16 bcdUSB; //USB 版本号
__u8 bDeviceClass; //设备类代码(由USB官方分配)
__u8 bDeviceSubClass; //子类代码(由USB官方分配)
__u8 bDeviceProtocol; //设备协议代码(由USB官方分配)
__u8 bMaxPacketSize0; //端点0的最大包大小(有效大小为8,16,32,64)
__le16 idVendor; //生产厂商编号(由USB官方分配)
__le16 idProduct; //产品编号(制造厂商分配)
__le16 bcdDevice; //设备出厂编号
__u8 iManufacturer; //设备厂商字符串索引
__u8 iProduct; //产品描述字符串索引
__u8 iSerialNumber; //设备序列号字符串索引
__u8 bNumConfigurations; //当前速度下能支持的配置数量
} __attribute__ ((packed));
配置描述符结构体
struct usb_config_descriptor {
__u8 bLength; //该描述符结构体大小
__u8 bDescriptorType; //描述符类型(本结构体中固定为0x02)
__le16 wTotalLength; //此配置返回的所有数据大小
__u8 bNumInterfaces; //此配置的接口数量
__u8 bConfigurationValue; //Set_Configuration 命令所需要的参数值
__u8 iConfiguration; //描述该配置的字符串的索引值
__u8 bmAttributes; //供电模式的选择
__u8 bMaxPower; //设备从总线提取的最大电流
} __attribute__ ((packed));
接口描述符结构体
struct usb_interface_descriptor {
__u8 bLength; //该描述符结构大小
__u8 bDescriptorType; //接口描述符的类型编号(0x04)
__u8 bInterfaceNumber; //接口描述符的类型编号(0x04)
__u8 bAlternateSetting; //接口描述符的类型编号(0x04)
__u8 bNumEndpoints; //该接口使用的端点数,不包括端点0
__u8 bInterfaceClass; //接口类型
__u8 bInterfaceSubClass; //接口子类型
__u8 bInterfaceProtocol; //接口遵循的协议
__u8 iInterface; //描述该接口的字符串索引值
} __attribute__ ((packed));
端点描述符结构体
struct usb_endpoint_descriptor {
__u8 bLength; //端点描述符字节数大小(7个字节)
__u8 bDescriptorType; //端点描述符类型编号(0x05)
__u8 bEndpointAddress; //端点地址及输入输出属性
__u8 bmAttributes; //端点的传输类型属性
__le16 wMaxPacketSize; //端点收、发的最大包大小
__u8 bInterval; //主机查询端点的时间间隔
/* NOTE: these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8 bRefresh; //声卡用到的变量
__u8 bSynchAddress;
} __attribute__ ((packed));
3、USB枚举
3.1、枚举示意图
USB枚举实际上是host检测到device插入后,通过发送各种标准请求,请device返回各种USB描述符的过程。USB枚举的示意图如下:
3.2、USB标准请求的结构
上述提及的USB标准请求的结构如下:
3.2、USB标准请求的结构
上述提及的USB标准请求的结构如下:
3.3、枚举过程数据流抓取
用Bus Hound抓取的枚举过程数据流,device侧USB配置(功能组合)为mtp+adb
数据示意图如下:
4、USB gadget驱动分析
4.1、USB gadget功能框架
4.2、USB gadget驱动代码流程图
4.3、MTP interface开机启动流程代码分析
根据上面所讲的结构框图和代码流程图,结合MTP interface的实际运行流程,分析如下:
1)系统开机时,kernel启动init进程启动zygote启动孵化出SystemServer进程USB Service等一系列Service启动UsbManager启动UsbDeviceManager启动。
2)UsbDeviceManager.java
3)init.qcom.usb.rc
usb属性配置文件
4)android.c
接收属性节点的值;向framework发送usb状态改变的uevent
5)f_mtp.c
mtp驱动文件
映射到文件节点/dev/mtp_usb :
配置mtp interface的描述符:
4.4 MTP传输启动流程代码分析
在"PC和Android设备建立MTP连接"后,UsbManager向MtpReceiver发送广播,接着MtpReceiver会启动MtpService,MtpService会启动MtpServer(Java层),MtpServer(Java)层会调用底层的JNI函数。在JNI中,会打开MTP文件节点"/dev/mtp_usb",然后调用MtpServer对象的run()方法不断的从中读取消息并进行处理。
1)frameworks\av\media\mtp\MtpServer.cpp
2)kernel\drivers\usb\gadget\f_mtp.c
5、USB host驱动分析
5.1、URB
USB请求块(USB Request Block,URB)是USB设备驱动中用来描述与USB设备通信所用的基本载体和核心数据结构。
一个URB用来向一个特定USB设备的特定USB端点发送数据或接收数据。设备中的每个端点都处理一个URB队列。
URB的处理流程:
5.2、鼠标驱动
在Linux kernel中,drivers\hid\usbhid\hiddev.c和drivers\hid\usbhid\usbmouse.c两个驱动文件均可以支持USB鼠标,具体使用哪个驱动,取决于kernel的编译配置。下面我们就以drivers\hid\usbhid\usbmouse.c这个驱动文件为例,分析USB鼠标的驱动代码流程。
USB鼠标遵循USB HID(Human Interface Device)规范。
在probe中探测设备是否符合HID规范,并且创建和初始化URB:
在usb_mouse_open函数中提交URB:
执行回调函数,向user space上报input事件:
5.3、U盘驱动
5.3.1、U盘驱动框架
如上图所示,USB Device Driver识别到U盘设备后,还需要将U盘模拟为SCSI(小型计算机系统接口)设备,才能与User Space进行数据传输。相关代码路径如下:
drivers\usb\storage\unusual_devs.h //添加非常规设备的参数
drivers\usb\storage\usb.c //USB Device Driver
drivers\usb\storage\scsiglue.c //SCSI Driver
5.3.2、U盘mount流程
Linux Kernel将U盘模拟为SCSI设备后,会向vold(volume deamon)发送如下格式的Uevent:
vold的NetlinkManager接收到uevent消息后,只处理SUBSYSTEM=block的消息:
system\vold\NetlinkHandler.cpp
并按以下流程完成U盘的mount:
其中vold的process_config函数会根据配置文件配置VM对象:
system\vold\main.cpp
配置文件路径:
device\qcom\msm_xxx\fstab.qcom
最后,vold的handlePartitionAdded函数识别并mount设备的所有分区:
system\vold\DirectVolume.cpp