带你遨游USB世界

2020-07-21 09:47:46 浏览数 (1)

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数据传输规范和约定
  1. USB传输是主从模式,主机负责发起数据传输过程,从机负责应答。
  2. USB传输使用小端结构(Little-Endian),一个字节在USB总线上的传输先后顺序为:b0 b1 b2 …b7 (与I2C相反,I2C是大端结构)。
  3. 数据传输方向均以主机为参考

比如启动USB传输的令牌包名称

IN令牌包 用来通知设备返回一个数据包

数据包的传输方向:主机←从机( IN )

OUT令牌包 用来通知设备将要输出一个数据包

数据包的传输方向:主机→从机( OUT )

2.1.5、四种传输模式

针对不同的数据传输场景,USB分为四种数据传输模式,这四种传输模式分别由不同的包(packet)组成,并且有不同的数据处理策略。每种数据传输模式的流程示意图以及应用场景如下:

  1. 控制传输—— Control Transfers

用于枚举过程,要保证数据传输过程的数据完整性。

  1. 批量传输—— Bulk Transfers

用于数据量大、对实时性要求不高的场合,如U盘。

  1. 中断传输—— Interrupt Transfers

用于数据量小的场合,保证查询频率,如鼠标、键盘。

  1. 同步传输(等时传输)—— 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。

设备描述符结构体

代码语言:javascript复制
/* 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));

配置描述符结构体

代码语言:javascript复制
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));

接口描述符结构体

代码语言:javascript复制
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));

端点描述符结构体

代码语言:javascript复制
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)frameworksavmediamtpMtpServer.cpp

2)kerneldriversusbgadgetf_mtp.c

5、USB host驱动分析

5.1、URB

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

一个URB用来向一个特定USB设备的特定USB端点发送数据或接收数据。设备中的每个端点都处理一个URB队列。

URB的处理流程:

5.2、鼠标驱动

在Linux kernel中,drivershidusbhidhiddev.c和drivershidusbhidusbmouse.c两个驱动文件均可以支持USB鼠标,具体使用哪个驱动,取决于kernel的编译配置。下面我们就以drivershidusbhidusbmouse.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进行数据传输。相关代码路径如下:

代码语言:javascript复制
driversusbstorageunusual_devs.h //添加非常规设备的参数

driversusbstorageusb.c          //USB Device Driver

driversusbstoragescsiglue.c      //SCSI Driver
5.3.2、U盘mount流程

Linux Kernel将U盘模拟为SCSI设备后,会向vold(volume deamon)发送如下格式的Uevent:

vold的NetlinkManager接收到uevent消息后,只处理SUBSYSTEM=block的消息:

代码语言:javascript复制
systemvoldNetlinkHandler.cpp

并按以下流程完成U盘的mount:

其中vold的process_config函数会根据配置文件配置VM对象:

systemvoldmain.cpp

配置文件路径:

代码语言:javascript复制
deviceqcommsm_xxxfstab.qcom

最后,vold的handlePartitionAdded函数识别并mount设备的所有分区:

systemvoldDirectVolume.cpp

0 人点赞