USB的全称是Universal Serial Bus,通用串行总线。它的出现主要是为了简化我的计算机与外围设备的链接,增长易用性。USB支持热插拔,而且是即插即用的,另外,它还具备很强的可扩展性,传输速度也很快,这些特性使支持USB接口的电子设备更易用、更大众化。java
本文将从USB协议、枚举流程、host和device驱动等各方面,全面介绍Linux USB模块的工做原理和代码流程,下面就请随我一块儿,遨游多姿多彩而又复杂严谨的USB世界吧~linux
塔顶为USB主控制器和根集线器(Root Hub),下面接USB集线器(Hub),集线器将一个USB口扩展为多个USB口,USB2.0规定集线器的层数最多为6层,理论上一个USB主控制器最多可接127个设备,由于协议规定USB设备具备一个7 bit的地址(取值范围为0~127,而地址0是保留给未初始化的设备使用的)。android
USB采用差分信号传输,使用的是如上图所示的NRZI编码方式:数据为0时,电平翻转;数据为1时,电平不翻转。若是出现6个连续的数据1,则插入一个数据0,强制电平翻转,以便时钟同步。上面的一条线表示的是原始数据序列,下面的一条线表示的是通过NRZI编码后的数据序列。数据结构
USB总线上的传输数据是以包为基本单位的,包格式如上图所示。根据PID的不一样,USB协议中规定的包类型有令牌包、数据包、握手包和特殊包等。框架
USB芯片(硬件)会完成CRC校验、位填充、PID识别、数据包切换、握手等协议处理。函数
USB传输是主从模式,主机负责发起数据传输过程,从机负责应答。编码
USB传输使用小端结构(Little-Endian),一个字节在USB总线上的传输前后顺序为:b0 b1 b2 …b7 (与I2C相反,I2C是大端结构)。spa
数据传输方向均以主机为参考3d
好比启动USB传输的令牌包名称code
IN令牌包 用来通知设备返回一个数据包
数据包的传输方向:主机←从机( IN )
OUT令牌包 用来通知设备将要输出一个数据包
数据包的传输方向:主机→从机( OUT )
针对不一样的数据传输场景,USB分为四种数据传输模式,这四种传输模式分别由不一样的包(packet)组成,而且有不一样的数据处理策略。每种数据传输模式的流程示意图以及应用场景以下:
用于枚举过程,要保证数据传输过程的数据完整性。
用于数据量大、对实时性要求不高的场合,如U盘。
用于数据量小的场合,保证查询频率,如鼠标、键盘。
用于数据量大、同时对实时性要求较高的场合,如音视频。
不保证数据完整性,没有ACK/NAK应答包,不进行数据重传。
一个USB设备一般有一个或多个配置,但在同一时刻只能有一个配置;
一个配置一般有一个或多个接口;
一个接口一般有一个或多个端点;
驱动是绑定到USB接口上的,而不是整个USB设备。
枚举过程当中,device将各类描述符返回给host。
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设备功能的实现。
对应上述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));
USB枚举其实是host检测到device插入后,经过发送各类标准请求,请device返回各类USB描述符的过程。USB枚举的示意图以下:
上述说起的USB标准请求的结构以下:
上述说起的USB标准请求的结构以下:
用Bus Hound抓取的枚举过程数据流,device侧USB配置(功能组合)为mtp+adb
数据示意图以下:
根据上面所讲的结构框图和代码流程图,结合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的描述符:
在"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
USB请求块(USB Request Block,URB)是USB设备驱动中用来描述与USB设备通讯所用的基本载体和核心数据结构。
一个URB用来向一个特定USB设备的特定USB端点发送数据或接收数据。设备中的每一个端点都处理一个URB队列。
URB的处理流程:
在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事件:
如上图所示,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
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