• 带你遨游USB世界


    1、什么是USB

    USB的全称是Universal Serial Bus,通用串行总线。它的出现主要是为了简化个人计算机与外围设备的连接,增加易用性。USB支持热插拔,并且是即插即用的,另外,它还具有很强的可扩展性,传输速度也很快,这些特性使支持USB接口的电子设备更易用、更大众化。

    本文将从USB协议、枚举流程、host和device驱动等各方面,全面介绍Linux USB模块的工作原理和代码流程,下面就请随我一起,遨游多姿多彩而又复杂严谨的USB世界吧~

    2、USB传输基础知识介绍

    2.1、USB金字塔型拓扑结构

    2.1.1、USB协议基础

    image

    塔顶为USB主控制器和根集线器(Root Hub),下面接USB集线器(Hub),集线器将一个USB口扩展为多个USB口,USB2.0规定集线器的层数最多为6层,理论上一个USB主控制器最多可接127个设备,因为协议规定USB设备具有一个7 bit的地址(取值范围为0~127,而地址0是保留给未初始化的设备使用的)。

    2.1.2、NRZI编码

    image

    USB采用差分信号传输,使用的是如上图所示的NRZI编码方式:数据为0时,电平翻转;数据为1时,电平不翻转。如果出现6个连续的数据1,则插入一个数据0,强制电平翻转,以便时钟同步。上面的一条线表示的是原始数据序列,下面的一条线表示的是经过NRZI编码后的数据序列。

    2.1.3、包(packet)格式

    image

    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

    image

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

    1. 批量传输—— Bulk Transfers

    image

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

    1. 中断传输—— Interrupt Transfers

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

    1. 同步传输(等时传输)—— Isochronous Transfers

    image

    用于数据量大、同时对实时性要求较高的场合,如音视频。

    不保证数据完整性,没有ACK/NAK应答包,不进行数据重传。

    2.1.6、USB设备结构及描述符

    image

    一个USB设备通常有一个或多个配置,但在同一时刻只能有一个配置;

    一个配置通常有一个或多个接口;

    一个接口通常有一个或多个端点;

    驱动是绑定到USB接口上的,而不是整个USB设备。

    枚举过程中,device将各种描述符返回给host。

    2.2、Linux USB驱动总体结构

    image

    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枚举的示意图如下:

    image

    3.2、USB标准请求的结构

    上述提及的USB标准请求的结构如下:

    image

    3.2、USB标准请求的结构

    上述提及的USB标准请求的结构如下:

    image

    3.3、枚举过程数据流抓取

    用Bus Hound抓取的枚举过程数据流,device侧USB配置(功能组合)为mtp+adb

    数据示意图如下:

    image

    4、USB gadget驱动分析

    4.1、USB gadget功能框架

    image

    4.2、USB gadget驱动代码流程图

    image

    4.3、MTP interface开机启动流程代码分析

    根据上面所讲的结构框图和代码流程图,结合MTP interface的实际运行流程,分析如下:

    1)系统开机时,kernel启动init进程启动zygote启动孵化出SystemServer进程USB Service等一系列Service启动UsbManager启动UsbDeviceManager启动。

    image

    2)UsbDeviceManager.java

    image

    3)init.qcom.usb.rc

    usb属性配置文件

    image

    4)android.c

    接收属性节点的值;向framework发送usb状态改变的uevent

    image

    5)f_mtp.c

    mtp驱动文件

    映射到文件节点/dev/mtp_usb :

    image

    配置mtp interface的描述符:

    image

    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

    image

    2)kerneldriversusbgadgetf_mtp.c

    image

    5、USB host驱动分析

    5.1、URB

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

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

    URB的处理流程:

    image

    5.2、鼠标驱动

    在Linux kernel中,drivershidusbhidhiddev.c和drivershidusbhidusbmouse.c两个驱动文件均可以支持USB鼠标,具体使用哪个驱动,取决于kernel的编译配置。下面我们就以drivershidusbhidusbmouse.c这个驱动文件为例,分析USB鼠标的驱动代码流程。

    USB鼠标遵循USB HID(Human Interface Device)规范。

    在probe中探测设备是否符合HID规范,并且创建和初始化URB:

    image

    image

    在usb_mouse_open函数中提交URB:

    image

    执行回调函数,向user space上报input事件:

    image

    image

    5.3、U盘驱动

    5.3.1、U盘驱动框架

    image

    如上图所示,USB Device Driver识别到U盘设备后,还需要将U盘模拟为SCSI(小型计算机系统接口)设备,才能与User Space进行数据传输。相关代码路径如下:

    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:

    image

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

    systemvoldNetlinkHandler.cpp
    

    image

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

    image

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

    systemvoldmain.cpp

    image

    配置文件路径:

    deviceqcommsm_xxxfstab.qcom
    

    image

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

    systemvoldDirectVolume.cpp

    image

  • 相关阅读:
    2020.4.13 机器学习相关数学基础
    2020.3.30 机器学习概述
    12.18语法制导的语义翻译
    12.11算符优先分析
    12.4自下而上语法分析
    11.27实验二 递归下降语法分析
    11.20LL(1)文法的判断,递归下降分析程序
    11.13消除左递归
    4.K均值算法--应用
    3.K均值算法
  • 原文地址:https://www.cnblogs.com/linhaostudy/p/13335320.html
Copyright © 2020-2023  润新知