usb总线驱动学习总结

发布时间 2023-12-10 11:55:16作者: 小小的番茄

一、概述

二、usb总线硬件原理

三、usb总线通信协议

四、uboot基于DM框架的usb驱动代码流程

 

一、概述

  本文旨在对usb总线驱动的学习做一个总结;

  先描述usb总线的硬件结构及工作原理;

  然后描述usb总线通信协议规范的主要内容,搞清楚usb主机和usb设备是如何基于包进行通信的;

  最后,基于usb总线的硬件原理和usb通信协议,描述uboot下面基于DM驱动框架的usb驱动软件是如何实现的,先描述usb控制器驱动初始化,usb hub的扫描配置,然后扫描usb hub的port上连接的usb设备,最后,以dm9601 usb网卡驱动为例,说明如何通过usb总线访问dm9601内部的寄存器,如何通过usb总线与dm9601进行数据收发。

二、usb总线硬件原理

  介绍usb总线的硬件结构,整体框架、组成部件(usb控制器、hub、usb设备)、总线拓扑结构。

2.1 usb总线拓扑结构

  下图是usb总线的拓扑结构,由usb控制器、hub以及各类usb设备构成一种树型结构,其中usb控制器内部有且仅有一个root hub。

  下图是hub的框图,hub是用来连接usb设备的,一般hub有多个port,每个port可连接一个usb设备,也可连接另一个hub继续扩展。

2.2 usb控制器结构框图

  下图是一个usb控制器的结构框图,控制器内部包括处理usb协议相关的组件、寄存器、数据包缓存、root hub、以及用于连接外部差分信号线的USB PHY;

2.3 usb设备结构框图(DM9601)

  如下是usb设备dm9601网卡的结构框图,除了网卡相关的mac和phy组件外,还包括一个usb控制接口;

三、usb总线通信协议

  usb通信协议规定了usb设备和usb总线通信的规范,所有厂商的usb设备都遵守这些规范,因此所有厂商的usb设备都可以基于usb通信协议进行通信;

  对于usb设备,usb通信协议规定,每个usb设备都需要有设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符(可选)、这些描述符内容结构固定,描述了设备的属性和具备的功能;usb主机通过这些描述符对设备进行类别识别、配置,并匹配对应的驱动程序,比如在usb设备枚举时,通过产商ID匹配驱动程序;

  对于usb主机和usb设备的通信,usb协议规定了usb主机和usb设备之间如何实现通信:

  usb主机和设备的通信使用主从模式,数据读写只能由主机发起,比如usb设备不能主动发数据给主机,必须由主机给usb设备发读命令,然后usb设备再将数据发给主机;

  usb主机和设备的信息传输基于包实现,包由多个字段构成,usb协议定义了4种类型的包:令牌包、数据包、握手包、特殊包;usb主机和设备的每次通信都要互相收发多个包实现,比如usb主机要从usb设备读一个数据:首先发一个令牌包告知设备要读数据、然后设备将数据通过数据包发给主机、主机收到数据后发送握手包给设备确认收到数据;

  需要说明的是,usb设备功能的控制由端点实现,每个usb设备都有多个端点,各端点分别控制不同的功能;对于usb主机来说,可以通过读取端点描述符知晓对应端点的属性,每个端点有各自的端点号,包的地址字段包含要访问的端点号,usb主机通过端点号访问端点,从而实现和usb设备的通信。

3.1 usb设备的描述符

  usb协议为usb设备定义了一套描述设备功能和属性的有固定结构的描述符,包括设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符;

3.1.1 设备描述符

  每个usb设备只有一个设备描述符,描述了设备的一般信息,包括制造商标识号ID、产品序列号、所属设备类号、默认端点的最大包长度、配置描述符的个数:

 1 struct usb_device_descriptor {
 2     u8 bLength;
 3     u8 bDescriptorType;    /* 0x01 */
 4     u16 bcdUSB;
 5     u8 bDeviceClass;
 6     u8 bDeviceSubClass;
 7     u8 bDeviceProtocol;
 8     u8 bMaxPacketSize0;
 9     u16 idVendor;
10     u16 idProduct;
11     u16 bcdDevice;
12     u8 iManufacturer;
13     u8 iProduct;
14     u8 iSerialNumber;
15     u8 bNumConfigurations;
16 } __attribute__ ((packed));

3.1.2 配置描述符

  每个usb设备可能有多个配置描述符,内容主要包括usb设备的供电方式、最大耗电量、此配置支持的接口个数等:

 1 struct usb_configuration_descriptor {
 2     u8 bLength;
 3     u8 bDescriptorType;    /* 0x2 */
 4     u16 wTotalLength;
 5     u8 bNumInterfaces;
 6     u8 bConfigurationValue;
 7     u8 iConfiguration;
 8     u8 bmAttributes;
 9     u8 bMaxPower;
10 } __attribute__ ((packed));

3.1.3 接口描述符

  一个配置可能包含多个接口,一个接口表示一个设备具备的某一个功能,多个接口表示一个设备具备多个功能,我们编写的usb设备驱动程序是服务于一个接口的,也就是一个接口对应一个usb设备驱动程序:

 1 struct usb_interface_descriptor {
 2     u8 bLength;
 3     u8 bDescriptorType;    /* 0x04 */
 4     u8 bInterfaceNumber;
 5     u8 bAlternateSetting;
 6     u8 bNumEndpoints;
 7     u8 bInterfaceClass;
 8     u8 bInterfaceSubClass;
 9     u8 bInterfaceProtocol;
10     u8 iInterface;
11 } __attribute__ ((packed));

3.1.4 端点描述符

  端点是usb主机和设备间通信的逻辑接口,每个设备都有一个端点0,在设备还没有分配地址时,主机通过这个接口和设备通信,一般功能端点都是单向的,也就是只能被主机写或者只能被主机读,但是这个端点0是双向的,并且没有端点描述符;端点描述符描述了数据的传输类型(控制传输、中断传输、批量传输、同步传输)、传输方向、数据包大小和端点号:

1 struct usb_endpoint_descriptor {
2     u8 bLength;
3     u8 bDescriptorType;    /* 0x5 */
4     u8 bEndpointAddress;
5     u8 bmAttributes;
6     u16 wMaxPacketSize;
7     u8 bInterval;
8 } __attribute__ ((packed));

3.1.5 字符串描述符

  字符串描述符是一个可选信息,描述了如制造商、设备名称、序列号等信息:

1 struct usb_string_descriptor {
2     u8 bLength;
3     u8 bDescriptorType;    /* 0x03 */
4     u16 wData[0];
5 } __attribute__ ((packed));

3.2 usb主机和usb设备的通信

3.2.1 主机和设备通信流程

  usb主机和设备的通信通过主机访问设备的端点实现,通信方式基于4种类型的包(令牌包、数据包、握手包、特殊包),包是USB信息传输的基本单元,包由5个字段组成:同步字段(SYNC)、包标识符字段(PID)、数据字段(数据字段由地址(设备地址和端点号)、frame number、Data构成)、CRC字段、结尾字段(EOP);

  对于不同场景的通信任务,usb协议定义了4种传输方式:控制传输、中断传输、批量传输、同步传输,对应的,每个端点的传输类型都属于其中一种;

  控制传输(双向):主要传输对设备的控制指令、设备状态查询及确认命令,用于连接时的设备枚举,不允许出错或丢失;

  中断传输(单向):仅输入主机的少量数据,如鼠标;

  批量传输(单向、双向):大批量数据的传输,实时性不强,但是要求不能出错,若出错应重新传输,用于U盘、打印机、数码相机等;

  同步传输(单向、双向):连续的固定速率数据的传输,要求低延时传输,应开辟较大缓冲区,并确保低误码率。

  因此,usb主机和usb设备的通信就是通过这4种传输方式实现。

  每次传输都需要主机和设备互相收发多个不同的包实现,为了描述这个过程,还需要引入事务这个概念,setup事务、输入事务、输出事务:

  setup事务:主机用来向设备发送控制命令,

    1、主机向设备发送令牌信息包,告知设备主机要写数据;

    2、主机将命令通过数据包发给设备;

    3、设备向主机发送握手包确认收到数据;

  输入事务:主机用来从设备读取数据,

    1、主机向设备发送令牌信息包,告知设备主机要读数据;

    2、设备向主机发送数据包;

    3、主机向设备发送握手包确认收到数据;

  输出事务:主机用来向设备发送数据,

    1、主机向设备发送令牌信息包,告知设备主机要写数据;

    2、主机向设备发送数据包;

    3、设备向主机发送握手包确认收到数据;

  比如,在设备枚举阶段,主机读取设备的配置描述符,需要使用控制传输访问设备的控制端点0,需要使用到3种事务,步骤如下:

 1 /*
 2  * 设备描述符读取步骤
 3  * 使用控制传输读
 4 */
 5 1、设置事务(setup stage)
 6     主机向设备写入获取设备描述符的命令,这个事务由3个数据包完成
 7     令牌包(setup:OUT):告知设备主机要写数据
 8     数据包(主机->设备):使用设备地址0向端点0写入获取描述符的命令
 9         8字节命令字段:
10             1byte:bmRequestType
11             1byte:bRequest
12             2byte:wvalue
13             2byte:wIndex
14             2byte:wLength
15     握手包:设备向主机回复ACK
16 
17 2、输入事务(data stage)
18     主机向设备发送IN方向的令牌包,设备将设备描述符发给主机,然后主机回复握手包给设备
19     令牌包(setup:IN):向设备告知要读数据
20     数据包(设备->主机):设备将描述符发给主机
21     握手包:主机向设备回复ACK
22 
23 3、输出事务(status stage)
24     状态信息步骤,因为描述符有18字节,上面只读了16字节,所以设备所处的状态是等到主机读剩余2字节,但是主机不需要读这2字节,
25     因此发送空的写数据事务,用来复位设备的状态,以告知设备主机已经接收完数据
26     令牌包(setup:OUT):向设备告知要写数据
27     数据包(主机->设备):发送包,但是数据内容为空
28     握手包:设备向主机回复ACK

3.2.2 usb请求命令

  usb协议定义了11种请求命令,命令由8个字节构成,命令封装在数据包的data字段发给usb设备,这8个字节命令格式为:

1 8字节命令字段:
2             1byte:bmRequestType
3             1byte:bRequest
4             2byte:wvalue
5             2byte:wIndex
6             2byte:wLength

  11种命令分别是:

  GET_STATUS、CLEAR_FEATURE、SET_FEATURE、SET_ADDRESS、GET_DESCRIPTOR、SET_DESCRIPTOR、GET_CONFIGURATION、SET_CONFIGURATION、GET_INTERFACE、SET_INTERFACE、SYNCH_FRAME;

  厂商可以选择性支持部分命令,如下是dm9601支持的usb标准命令:

  厂商除了支持标准usb命令,还可以定义自己的usb命令,如下是2条dm9601自定义的用于读写内部寄存器的命令:

四、usb驱动流程

  本节总结uboot基于DM代码框架的usb驱动流程,大致内容如下:

  设置usb控制器;

  扫描usb控制器下挂的设备,usb hub;

  设置usb hub;

  扫描usb hub的所有port;

  枚举port连接的usb设备;

  调用usb设备驱动的probe函数,也就是dm9601 usb网卡驱动的probe函数;

4.1 设置usb控制器

  这个阶段会设置usb phy的寄存器、设置usb控制器的寄存器,让usb控制器工作起来,可以发包扫描usb设备。

 

4.2 枚举usb root hub

  首先,usb控制器使用设备地址0访问root hub的端点0,读取root hub的设备描述符,然后设置root hub的设备地址(每次枚举到一个设备,这个地址就增加1,也就是下一个枚举到的设备地址比root hub加1),然后选择一个默认配置,接着读取厂商、制造、序列号的字符串描述符;接着,设备驱动使用读取到的root hub的厂商ID匹配hub的驱动程序,从而调用相关的probe函数,对root hub进行相关设置;在相关probe函数里面会将hub所有的port都上电,接着会读取port的状态看该port是否有连接usb设备,如果有连接usb设备,就对这个usb设备进行枚举。

  读取root hub的厂商ID、设置root hub的设备地址,用厂商ID查找hub的设备驱动程序,调用驱动程序的probe函数:

  Root hub的前半部分probe没有实质性操作,只是一些数据结构的初始化:

  Root hub的后半部分probe函数,执行uclass的uclass_post_probe_device函数,这个函数里面会下发控制消息给root hub,获取hub描述符,然后将root hub的每个port上电,然后读取port的状态判断是否连接usb设备,对连接的usb设备进行枚举:

  对port上连接的usb设备进行枚举,和枚举root hub一样,首先会读取usb设备的设备描述符,设置usb设备的设备地址,然后用读到的厂商ID查找usb设备的驱动程序,接着调用该驱动程序的probe函数,这里是dm9601网卡:

  Dm9601网卡驱动的probe函数,这个probe函数里面会解析端点信息保存到全局变量以便后面使用、设置接口、操作dm9601的寄存器进行网卡相关功能的操作:复位网卡、设置mac地址、读器件ID、power up phy等;

 

参考博客:

https://www.usbzh.com/article/detail-243.html

https://blog.csdn.net/snaking616/article/details/85759974

https://blog.csdn.net/qq_41483419/article/details/129009273

https://blog.csdn.net/gy794627991/article/details/123424120

https://blog.csdn.net/weixin_38878510/article/details/109028877

https://blog.csdn.net/ooonebook/article/details/53234020