vxworks usb tcd实现

本文详细讨论了在VxWorks6.9环境下,针对非vxBus的USBTargetControllerDriver(TCD)支持,特别是以massstorage设备为例,分析了驱动架构和实现过程。文中介绍了开发环境、源码组织结构、系统架构,以及驱动初始化和中断处理调用流程。此外,还涵盖了USB的基本知识,如引脚定义、枚举过程、通信模型、USB传输类型和标准请求。最后,文章提到了在开发过程中遇到的问题以及解决方案,并给出了USB监控工具usbmon的使用说明。

本文仅讨论在vxworks6.9下的usb tcd支持(非vxBus),以mass storage设备举例,主要以分析软件架构及实现为主,不详细描述驱动移植过程

开发环境

软件环境
vxworks6.9
vscode

硬件环境使用synopsys otg控制器配置为强制从模式,mass storage使用RAM DISK进行模拟

源码组织架构

target/config/comps/vxWorks/40/usb.cdf中修改FOLDER_USB_PERIPHERAL_CONTROLLERS

Folder FOLDER_USB_PERIPHERAL_CONTROLLERS {
    NAME            USB Peripheral Controllers
    SYNOPSIS        USB Target Controllers
    _CHILDREN       FOLDER_USB_GEN1_TARGET
    CHILDREN        INCLUDE_PDIUSBD12                       \
                    INCLUDE_PHILIPS1582                     \
                    INCLUDE_NET2280                         \
                    INCLUDE_FSL_TCD							\
                    INCLUDE_SYNOPSYS_TCD
}

仿照INCLUDE_PHILIPS1582添加INCLUDE_SYNOPSYS_TCD

Component INCLUDE_SYNOPSYS_TCD {
    NAME            synopsys
    SYNOPSIS        Target Controller Driver for synopsys
    MODULES         usbTcdSynopsysInitExit.o
    REQUIRES        INCLUDE_USB_TARG
    PROTOTYPE       void sysSynopsysPciDisconnect (void);
    INIT_RTN        sysSynopsysPciDisconnect ();
    _INIT_ORDER     usrRoot
    INIT_AFTER      usrRtpInit
}

注意同时删除INCLUDE_PHILIPS1582中的INIT_BEFORE选项,否则在下面包含Targ Mass Storage Emulator Init组件时会包含philips1582驱动 ps:不知是否版本发布的时候忘记删除QaQ

kernel configuration

在USB GEN1 Target->USB Peripheral Controllers下出现一个新的选项synopsys

包含synopsys,Targ Mass Storage Emulator Init, Mass Storage Emulator ,以及RAM DISK组件

在target/src/drv/usb/target中新建文件:

usbTcdSynopsysDeviceControl.c

usbTcdSynopsysEndpoint.c

usbTcdSynopsysInitExit.c

usbTcdSynopsysInterrupt.c

usbTcdSynopsysUtil.c

修改 Makefile文件OBJS=后添加usbTcdSynopsysInitExit.o

在对应的target/h/drv/usb/target中新建.h文件

系统架构

image-20230308103947197

软件层面主要分为三层:

  • 驱动层:主要负责硬件设备的初始化,配置,中断具体处理,状态获取,数据读写等,是与硬件直接接触的一层
  • 硬件抽象层:主要负责将硬件设备的状态,中断,endpoint的读写等操作统一化处理,并对接上层功能类层
  • 功能类层:根据选择的类型,实现诸如大容量存储设备、键盘、鼠标、打印机等多种设备类型

以mass storage设备为例,具体程序调用关系如下:

初始化:

usrRoot->
	sysxxxPciDisConnect()	//实现控制器的初始化配置
    usbTargMsInit()			//mass storage设备初始化
    	usbTargMsBlockDevCreate() //如果是基于RAMDISK的实现,创建RAMDISK设备,如果不是(例如sata),可以尝试在该函数中修改,获取device_t变量,替代RAMDISK设备
    	usbTargRbcCmdDataUpdate() //获取硬盘设备的块大小、块数量等参数
    	g_rbcSync = semBCreate(SEM_Q_FIFO,SEM_EMPTY);//创建设备同步信号量,该信号量在后面用于硬盘设备读写完成时的回调使用,用来表示硬盘设备读写完成
		msInitxxx()	//完成usbHal的初始化,创建"tUsbHal"任务,挂载中断ISR
            usbTargTcdAttach (usbTcdxxxEvalExec, (pVOID) &params,
                          callbackTable, callbackParam, &msTargChannel)
            /*params变量保存io基地址和中断号,callbackTable指向Ms回调函数表usbTargMsCallbackTable,具体成员如下
            LOCAL USB_TARG_CALLBACK_TABLE usbTargMsCallbackTable = 
            {
            mngmtFunc,                  
            featureClear,              
            featureSet,                 
            configurationGet,         
            configurationSet,           
            descriptorGet,   //host获取slave设备描述信息,比较重要          
            NULL,                      
            interfaceGet,               
            interfaceSet,               
            statusGet,                 
            addressSet,      //host设置slave地址,比较重要        
            NULL,                      
            vendorSpecific             
            };
            usbTcdxxxEvalExec作为驱动与硬件抽象层之间的唯一接口函数,所有驱动调用皆通过该函数实现*/

usbTargTcdAttach这个函数很重要,通过调用usbHalTcdAttach函数,完成三个具体操作:

  • 一是创建tUsbHal任务,任务函数usbHalInterruptThread,易见,这个任务主要是硬件抽象层用来处理底层驱动中断的。
  • 二是通过TCD_FNC_ATTACH方法,通过底层驱动接口函数usbTcdxxxEvalExec调用底层驱动的usbTcdxxxFncAttach实现。包括底层设备结构体的初始化,底层硬件中断ISR挂接到中断向量表上,这个中断ISR usbTcdxxxIsr与底层系统相关,由驱动负责实现,是硬件中断的入口函数。usbTcdxxxFncAttach还需要完成OTG寄存器的配置,完成中断屏蔽寄存器配置
  • 三是通过usbTargMsCallbackTable中的mngmtFunc回调函数通知上层,完成上层Attach

至此,启动阶段的初始化基本完成

中断处理调用流程

image-20230315095123424

如图所示,可以看出函数usbHalIsr及usbTcdxxxEvalExec是两个很重要的函数,中断状态初步获取完成后会传递给usbHalInterruptThread任务,线程化。后续处理在该任务中进行,诸如断开、复位、唤醒、EP中断处理、挂起等,功能类层会将要处理的消息类型及回调函数指针等打包为一个ERP结构体,放在链表上,等硬件抽象层收发数据时,从链表上取出,进行对应的处理

功能类层负责usb枚举过程中的诸如获取设备描述符、设置设备地址等USB标准协议处理。枚举完成后,转入SCSI协议后,功能类层也负责SCSI协议的处理

USB基本知识

引脚定义

名称描述
1Vbus电源正5v
2D-数据-
3D+数据+
4IDOTG识别:接地,A型,Host;悬空,B型,Device1
5GND

OTG设备的Host及Device识别,是通过连接线将Host端的ID引脚接地,Device端的ID引脚悬空实现的

这里将OTG控制器强制配置成Device设备,不存在ID识别过程

USB连接模型

主从结构

USB总线上所有的数据传输都由主机主动发起,而设备只是被动的负责应答。例如,在读数据时,USB主机先发出读命令,设备收到该命令后,才返回数据。

USB拓扑结构图

USB拓扑结构

通信流模型

端点:USB设备端概念,是真实的物理设备。分为控制端点和数据端点,控制端点即0地址端点,每个设备都必须包含一个控制端点,双向传输,其主要功能使主机实现对设备的配置如设备描述符,配置描述符和字符串描述的获取和对设备的配置(如设备地址的设置等)。数据端点用于数据的传输

管道:USB主机和设备使用管道进行数据通讯。管道是USB主机在软件层面上的一个抽象.管道可以理解为USB主机端对USB端点的软件抽象,所以它包括USB设备端点的所有信息。

方向是相对主机来说的,IN为主机的输入方向,OUT为主机的输出方向
modem

USB传输

image-20230315143552925

包Packet

usb总线上传输的数据都是以包为基本单位的。一个包可以被分成不同的域,不同类型的包,有相同特点,以SOP开始,同步域SYNC,紧跟一个包标识符PID,最终以包结束符EOP结束

image-20230315140238317

根据PID不同,分为四大类,每类下面又分为具体的几个

PID类型PID名称包种类
Token令牌OUT/IN/SETUP/SOF令牌包、SOF包
Data数据DATA0/DATA1/DATA2/MDATA数据包
Handshake握手ACK/NAK/STALL/NYET握手包
Special特殊PRE/ERR/SPLIT/PING
事务Transaction

分为三种事务,setup事务,主要向设备发送控制命令;IN事务,主要从设备读取数据;OUT事务,主要向设备发送数据。事务由2-3个包组成,setup事务、IN事务、OUT事务由令牌包Token、数据Data packet、握手Handshake握手包组成

传输Transfer

分为四种传输类型,批量传输、中断传输、同步传输、控制传输;同步传输、批量传输每次需要一个或多个事务,中断传输每次需要一个事务

控制传输:主机在枚举设备时使用该传输

同步传输:使用同步事务,适用于数据量大、对实时性要求高的场合,如音频、视频设备

批量传输:使用批量事务,适用于数据量大、对实时性要求不高的场合,如打印机、大容量存储设备

中断传输:使用中断事务,适用于周期性、低频率,允许有限延迟,可以保证主机查询频率,在小于要求的时间间隔内安排一次传输,如鼠标设备

image-20230315153221546

枚举

概念

枚举就是从设备读取一些信息,知道设备是什么样的设备,如何进行通信,这样主机就可以根据这些信息来加载合适的驱动程序

enum

枚举过程

1. 主机集线器检测到新设备
主机集线器监视着每个端口的信号电压,当有新设备接入时便可觉察

2. 主机发送GET_STATUS请求
每个集线器用中断传输来报告在集线器上的事件。当主机知道了这个事件,它给集线器发送一个GET_STATUS请求来了解更多的消息。返回的消息告诉主机一个设备是什么时候连接的。

3. 主机发送SET_FEATURE请求,集线器重启端口
当主机知道有一个新的设备时,主机给集线器发送一个SET_FEATURE请求,请求集线器来重启端口。集线器使得设备的USB数据线处于重启(Reset)状态至少10ms。以使从设备的设备地址置0

4. 集线器在设备和主机之间建立一个信号通路
主机发送一个GET_STATUS请求来验证设备是否激起重启状态。返回的数据有一位表示设备仍然处于重启状态。当集线器释放了重启状态,设备就处于默认状态了,设备已经准备好通过Endpoint 0 的默认流程响应控制传输,即设备现在使用默认地址0与主机通信。

5. 集线器检测设备速度
集线器通过测定哪根信号线(D+或D-)在空闲时有更高的电压来检测设备是低速设备还是全速设备。

6. 主机发送GET_DESCRIPTOR命令,获取最大数据包长度
PC 向address 0发送USB协议规定的GET_DESCRIPTOR命令获取设备描述符,以取得控制管道所支持的最大数据包长度,并在有限的时间内等待USB设备的响应。该长度包含在设备描述符的bMaxPacketSize0字段中,其地址偏移量为7,所以这时主机只需读取该描述符的前8个字节。注意,主机一次只能枚举一个USB设备,所以同一时刻只能有一个USB设备使用缺省地址0。
端点0的最大包长度与设备的模式有关,高速模式最大包长为64字节,全速模式为8/16/32/64可选,低速为8字节。第一次通信的时候,主机并不知道端点0的最大包长度,所以先通过GET_DESCRIPTOR命令获取设备描述符中的第8个字节,然后进行复位。

7. 复位

8. 主机分配一个新的地址给设备
主机通过发送一个SET_ADDRESS请求来分配一个唯一的地址给设备。设备读取这个请求,返回一个确认,并保存新的地址。从此开始所有通信都使用这个新地址。

9. 主机重新发送GET_DESCRIPTOR命令读取完整设备描述符
主机向新地址重新发送GET_DESCRIPTOR命令,此次读取其设备描述符的全部字段,以了解该设备的总体信息,如VID,PID。

10. 主机发送GET_DESCRIPTOR命令,获取完整配置信息
主机向设备循环发送GET_DESCRIPTOR命令,要求USB设备回答,以读取全部配置信息。

11. 主机发送GET_DESCRIPTOR命令,获得字符描述符(unicode)
描述字符集包括了产商、产品描述、型号

12. 主机展示新设备信息
此时主机将会弹出窗口,展示发现新设备的信息,产商、产品描述、型号

13. PC判断能否提供该类USB的驱动
根据设备描述符和配置描述符应答,PC判断是否能够提供USB的驱动

14. 主机发送SET_CONFIGURATION(x)命令,请求为设备选择一个配置
加载了USB设备驱动以后,主机发送SET_CONFIGURATION(x)命令请求为该设备选择一个合适的配置(x代表非0的配置值)。如果配置成功,USB设备进入“配置”状态,并可以和客户软件进行数据传输。

usbmon抓包
9800000224fc21c0 3982859413 S Ci:4:000:0 s 80 06 0100 0000 0040 64 < //获取设备描述符,为了获取控制管道最大长度
9800000224fc21c0 3982859637 C Ci:4:000:0 0 18 = 12010002 00000040 81070000 00000102 0301
//向集线器发送指令,使从设备复位
9800000224fc21c0 3982859643 S Co:4:001:0 s 23 03 0004 0001 0000 0
9800000224fc21c0 3982859649 C Co:4:001:0 0 0
9800000224fc21c0 3982929727 S Ci:4:001:0 s a3 00 0000 0001 0004 4 <
9800000224fc21c0 3982929886 C Ci:4:001:0 0 4 = 03051000
9800000224fc21c0 3982929890 S Co:4:001:0 s 23 01 0014 0001 0000 0
9800000224fc21c0 3982929895 C Co:4:001:0 0 0

9800000224fc1380 3982988324 S Co:4:000:0 s 00 05 000f 0000 0000 0 //SET_ADDRESS请求,设置从设备地址为0xf
9800000224fc1380 3982988520 C Co:4:000:0 0 0
9800000224fc2a00 3983015639 S Ci:4:015:0 s 80 06 0100 0000 0012 18 < //获取设备描述符,为了获取完整设备描述符
9800000224fc2a00 3983015773 C Ci:4:015:0 0 18 = 12010002 00000040 81070000 00000102 0301
9800000224fc2a00 3983015793 S Ci:4:015:0 s 80 06 0200 0000 0009 9 < //获取配置描述符
9800000224fc2a00 3983015895 C Ci:4:015:0 0 9 = 09022000 010100e0 00
9800000224fc2a00 3983015901 S Ci:4:015:0 s 80 06 0200 0000 0020 32 < //获取配置描述符,接口描述符、端点描述符
9800000224fc2a00 3983016021 C Ci:4:015:0 0 32 = 09022000 010100e0 00090400 00020806 50000705 81020002 00070501 02000200
9800000224fc3a80 3983016037 S Ci:4:015:0 s 80 06 0300 0000 00ff 255 < //获取字符串描述符
9800000224fc3a80 3983016145 C Ci:4:015:0 0 4 = 04030904
9800000224fc3a80 3983016152 S Ci:4:015:0 s 80 06 0302 0409 00ff 255 <//获取字符串描述符
9800000224fc3a80 3983016269 C Ci:4:015:0 0 52 = 34035500 53004200 20006d00 61007300 73002000 73007400 6f007200 61006700
9800000224fc3a80 3983016277 S Ci:4:015:0 s 80 06 0301 0409 00ff 255 <//获取字符串描述符
9800000224fc3a80 3983016394 C Ci:4:015:0 0 38 = 26035700 69006e00 64002000 52006900 76006500 72002000 53007900 73007400
9800000224fc3a80 3983016401 S Ci:4:015:0 s 80 06 0303 0409 00ff 255 <//获取字符串描述符
9800000224fc3a80 3983016519 C Ci:4:015:0 0 26 = 1a033000 31003200 33003400 35003600 37003800 39003000 3100
9800000224fc33c0 3983016712 S Co:4:015:0 s 00 09 0001 0000 0000 0 //发送SET_CONFIGURATION,选择配置
9800000224fc33c0 3983016896 C Co:4:015:0 0 0
//枚举完成
9800000224fc2ac0 3984031290 S Ci:4:015:0 s a1 fe 0000 0000 0001 1 < //get max lun
9800000224fc2ac0 3984031509 C Ci:4:015:0 0 1 = 00
9800000224fc2ac0 3984031694 S Bo:4:015:1 -150 31 = 55534243 01000000 24000000 80000612 00000024 00000000 00000000 000000
9800000224fc2ac0 3984031743 C Bo:4:015:1 0 31 >
98000001f3c51140 3984031755 S Bi:4:015:1 -150 36 <
98000001f3c51140 3984031868 C Bi:4:015:1 0 36 = 00800002 5b000000 57525320 20202020 55534220 4d617373 2053746f 72616765
9800000224fc2ac0 3984031876 S Bi:4:015:1 -150 13 <
9800000224fc2ac0 3984031992 C Bi:4:015:1 0 13 = 55534253 01000000 00000000 00

USB标准请求

usb定义了8个字节的标准请求,USB的标准请求的数据传输方式都是控制传输方式,所以使用的端点是设备的默认端点0。USB这8个字节的的控制请求不包括传输过程中的数据

标准请求格式

USB标准请求的个字节分为5个部分,其变量分别定义为: 1字节的bmRequestType,1 字节的bReqest,2字节的wValue,2字节的wIndex和2字节的wLength.

1B1B2B2B2B
bmRequestTypebReqestwValuewIndexwLength

bmRequestType字段(1字节

D7,下一步数据的传输方向
0 = 主机到设备(SET请求)
1 = 设备到主机(GET请求)

D6-D5,命令的类型
00 = 标准请求命令
01 = 类请求命令(UVC规范中的请求属于类请求命令)
10 = 用户定义的请求命令
11 = 保留

D4-D0,命令接受者的类型
00000 = 接受者为设备
00001 = 接收者为接口
00010 = 接受者为端点
00011 = 其它
4…31 = 保留
其他值保留。

bReqest字段(1字节

bRequestValue
GET_STATUS0
CLEAR_FEATURE1
SET_FEATURE3
SET_ADDRESS5
GET_DESCRIPTOR6
SET_DESCRIPTOR7
GET_CONFIGURATION8
SET_CONFIGURATION9
GET_INTERFACE10
SET_INTERFACE11
SYNCH_FRAME12
SET_ENCRYPTION13 (USB2.0以后)
GET_ENCRYPTION14
SET_HANDSHAKE15
GET_HANDSHAKE16
SET_CONNECTION17
SET_SECURITY_DATA18
GET_SECURITY_DATA19
SET_WUSB_DATA20
LOOPBACKDATAWRITE21
LOOPBACK_DATA_READ22
SET_INTERFACE_DS23
SET_SEL48
SET_ISOCH_DELAY49

NOTE:完整版标准请求格式可以查看USB2.0标准

USB描述符

image-20230316091816458

设备描述符是USB设备的第一个描述符,每个USB设备都得具有设备描述符,且只能拥有一个。一个设备描述符下可以有多个配置描述符,但是同一时刻只能有一种配置描述符有效,多数设备只有一个配置描述符。一个配置描述符下可以有多个接口,接口对应不同的功能。一个接口描述符下可以有多个端点描述符。比如一个设备既有录音功能又有扬声器功能,则这个设备至少有两个接口。

USB设备描述符

设备描述符是USB设备的第一个描述符,每个USB设备都得具有设备描述符,且只能拥有一个。

typedef struct usb_device_descr
    {
    UINT8 length;                   /* 描述符长度 */
    UINT8 descriptorType;           /* 描述符类型,设备描述符为0x01 */
    UINT16 bcdUsb;                  /* USB协议版本 */
    UINT8 deviceClass;              /* 类代码 */
    UINT8 deviceSubClass;           /* 子类代码 */
    UINT8 deviceProtocol;           /* 设备使用的协议 */
    UINT8 maxPacketSize0;           /* 端点0最大包长 */
    UINT16 vendor;                  /* 厂商ID */
    UINT16 product;                 /* 产品ID */
    UINT16 bcdDevice;               /* 设备版本 */
    UINT8 manufacturerIndex;        /* 描述厂商字符串的索引 */
    UINT8 productIndex;             /* 描述产品字符串的索引 */
    UINT8 serialNumberIndex;        /* 描述产品序列号字符串的索引 */
    UINT8 numConfigurations;        /* 当前速率下的配置数量 */
    } WRS_PACK_ALIGN(4) USB_DEVICE_DESCR, *pUSB_DEVICE_DESCR;

分析usbmon抓包数据

12	01					0002 			000000	  		40 		8107 0000 0000	  010203  		01
长度 类型(设备描述符) USB版本(2.0) 一般都为0  端点0最大包长 厂商/产品/设备版本 字符串索引 当前速率配置数
USB配置描述符
typedef struct usb_config_descr
    {
    UINT8 length;               /* 描述符长度 */
    UINT8 descriptorType;       /* 描述符类型,配置描述符为0x02 */
    UINT16 totalLength;         /* 包含此配置返回的端点描述符、接口描述符、端点描述符的长度 */
    UINT8 numInterfaces;        /* 该配置下的接口数 */
    UINT8 configurationValue;   /* 作为Set Configuration的一个参数选择配置值 */
    UINT8 configurationIndex;   /* 描述该配置的字符串的索引值 */
    UINT8 attributes;           /* 供电模式选择 */
    UINT8 maxPower;             /* 设备从总线提取的最大电流 */
    } WRS_PACK_ALIGN(4) USB_CONFIG_DESCR, *pUSB_CONFIG_DESCR;
USB配置描述符
typedef struct usb_interface_descr
    {
    UINT8 length;                   /* 描述符长度 */
    UINT8 descriptorType;           /* 描述符类型,配置描述符为0x04 */
    UINT8 interfaceNumber;          /* 接口序号 */
    UINT8 alternateSetting;         /* 备用的接口描述符编号 */
    UINT8 numEndpoints;             /* 该接口使用端点数,不包括端点0 */
    UINT8 interfaceClass;           /* 接口类型 */
    UINT8 interfaceSubClass;        /* 接口子类型  */
    UINT8 interfaceProtocol;        /* 接口所遵循的协议 */
    UINT8 interfaceIndex;           /* 描述该接口的字符串索引值 */
    } USB_INTERFACE_DESCR, *pUSB_INTERFACE_DESCR;

其中接口类型、接口子类型、接口所遵循的协议这几个地段很重要

常用接口类型分配:
AUDIO=1;COMMUNICATION=2;HID=3;MSC=8
Class = 3 (HID)的情况下,
*SubClass = 1 (BOOT) = 0 (no-BOOT) *
Protocol = 1 (KeyBoard) = 0(Mouse)

1表示是个启动设备,一般对PC才有意义,表示bios启动时能识别并使用该设备;否则只能在OS启动后才
能用

USB端点描述符
typedef struct usb_endpoint_descr
    {
    UINT8 length;                   /* 描述符长度 */
    UINT8 descriptorType;           /* 描述符类型,端点描述符为0x05 */
    UINT8 endpointAddress;          /* 端点地址及输入输出属性 */
    UINT8 attributes;               /* 端点传输类型属性,控制、同步、批量、中断 */
    UINT16 maxPacketSize;           /* 端点最大包大小 */
    UINT8 interval;                 /* 主机查询端点的时间间隔,仅中断传输有效 */
    } WRS_PACK_ALIGN(1) USB_ENDPOINT_DESCR, *pUSB_ENDPOINT_DESCR;

分析usbmon抓包数据

9800000224fc2a00 3983015793 S Ci:4:015:0 s 80 06 0200 0000 0009 9 < //获取配置描述符
9800000224fc2a00 3983015895 C Ci:4:015:0 0 9 = 09022000 010100e0 00
9800000224fc2a00 3983015901 S Ci:4:015:0 s 80 06 0200 0000 0020 32 < //获取配置描述符,接口描述符、端点描述符
9800000224fc2a00 3983016021 C Ci:4:015:0 0 32 = 09022000 010100e0 00090400 00020806 50000705 81020002 00070501 02000200

第一次获取时,因主机不知道配置描述符后面所跟的接口描述符、端点描述符长度,所以只获取9个字节的配置描述符,再通过配置描述符里的totalLength字段获取长度,再次获取

09	02					2000 		01	    01    00       e0   00  
长度 类型(配置描述符) 长度	 接口数  配置号 字符串索引 供电  最大电流
09  04 						00/00			02		  08  06   50    00  
长度 类型(接口描述符) 接口序号/备用编号  使用端点数  MSC SCSI BOT   字符串索引
07  05 					81	   02		0002 		00 
长度 类型(端点描述符) IN:1  批量   最大包长512  
07	05					01 		02		0002		00
长度 类型(端点描述符) OUT:1  批量   最大包长512    
USB字符串描述符
typedef struct usb_string_descr
    {
    UINT8 length;               /* 描述符长度  */
    UINT8 descriptorType;       /* 描述符类型,字符串描述符为0x03 */
    UINT8 string [1];           /* Unicode编码字符串 */
    } USB_STRING_DESCR, *pUSB_STRING_DESCR;

USB语言ID描述符

typedef struct usb_language_descr
    {
    UINT8 length;               /* 描述符长度 */
    UINT8 descriptorType;       /* 描述符类型,字符串描述符为0x03 */
    UINT16 langId [1];          /* 语言ID编号,一般为0409,表示美式英语 */
    } USB_LANGUAGE_DESCR, *pUSB_LANGUAGE_DESCR;

分析usbmon抓包数据

9800000224fc3a80 3983016037 S Ci:4:015:0 s 80 06 0300 0000 00ff 255 < //获取字符串描述符,索引00
9800000224fc3a80 3983016145 C Ci:4:015:0 0 4 = 04030904
9800000224fc3a80 3983016152 S Ci:4:015:0 s 80 06 0302 0409 00ff 255 <//获取字符串描述符,索引02,对应设备描述符里的产品字符串索引
9800000224fc3a80 3983016269 C Ci:4:015:0 0 52 = 
3403	5500 53004200 20006d00 61007300 73002000 73007400 6f007200 61006700
		"USB mass storage emulator"
9800000224fc3a80 3983016277 S Ci:4:015:0 s 80 06 0301 0409 00ff 255 <//获取字符串描述符,索引01,对应设备描述符里的厂商字符串索引
9800000224fc3a80 3983016394 C Ci:4:015:0 0 38 = 
2603	5700 69006e00 64002000 52006900 76006500 72002000 53007900 73007400
    	"Wind River Systems" 
9800000224fc3a80 3983016401 S Ci:4:015:0 s 80 06 0303 0409 00ff 255 <//获取字符串描述符,索引03,对应设备描述符里的产品序列号字符串索引
9800000224fc3a80 3983016519 C Ci:4:015:0 0 26 = 
1a03	3000 31003200 33003400 35003600 37003800 39003000 3100
    	"012345678901"
USB设置配置
9800000224fc33c0 3983016712 S Co:4:015:0 s 00 09 0001 0000 0000 0 //发送SET_CONFIGURATION,选择配置

至此,枚举完成,已经成功了一半

GetMaxLUN

可以看到抓包log,设置配置后面还有一个请求,该请求不是标准请求命令,属于类请求命令,与具体的类相关,这里属于BOT协议的命令

9800000224fc2ac0 3984031290 S Ci:4:015:0 s a1 fe 0000 0000 0001 1 < //get max lun
9800000224fc2ac0 3984031509 C Ci:4:015:0 0 1 = 00

该条命令是获取设备所支持的最大逻辑单元号,U盘设备一般只有一个逻辑单元,返回0

这部分处理主要对应vxworks源码中的usbTargMsLib.c文件

到此为止,上面的通信我理解为总线接口,下面的通信开始通过数据端点进行,而协议也转为BOT协议

SCSI

小型计算机系统接口(SCSI,Small Computer System Interface)是一种用于计算机及其周边设备之间(硬盘、软驱、光驱、打印机、扫描仪等)系统级接口的独立处理器标准。

从上面的内容可以看出,SCSI并不是跟USB绑定的协议,任何在Host上数据总线传输的数据,都可以通过SCSI协议来实现指定功能。

SCSI命令集详细不再赘述,可以查阅https://blog.csdn.net/weiaipan1314/article/details/112801882

BOT

BOT全称是Bulk-Only Transpot,指的是整个传输过程只使用USB的批量传输(Bulk Transers)。

BOT是基于USB数据包传输的基础上,为了配合SCSI协议,专门为大容量存储设备量身打造,在USB和SCSI中间又添加了一层协议,关于BOT的的协议文档是usbmassbulk_10

BOT主要有三种类型的数据:CBW、CSW和普通数据。

通信过程为:主机发送CBW,普通数据(可以没有),CSW

CBW

CBW (Command Block Wrapper)命令块包,31个字节,命令遵从接口描述符中的bInterfaceSubClass所指定的命令集,在这里为SCSI命令集

cbw

  1. dCBWSignature:帮助指明该数据报为CBW的信号标记。这个字段的值为0x43425355(小端),表示这是一个CBW。

  2. dCBWTag:主机发送的命令块标签。设备应在相关CBW的dCSWTag字段中将这个字段的内容返回给主机。dCSWTag将CSW与对应的CBW联系起来。

  3. dCBWDataTransferLength:主机要求在执行CBW命令期间,在批量输入或批量输出端点传输数据字节数。如果该字段为0,则设备和主机不应该在CBW和相关的CSW中间传输数据,设备应该忽略bmCBWFlags中方向位的值。注意,这个字段指明的是跟在CBW之后数据包的长度。

  4. bmCBWFlags:本字段的位定义如下:
    位7:方向。0 = 从主机到设备的DataOut,1 = 从设备到主机的DataIn;
    位6:废弃的,主机应该将该位设置为0;
    位5-0:保留,主机应该将该位设置为0;

  5. bCBWLUN:命令块发送的设备逻辑单元号(LUN)。对于支持多个LUN的设备,主机应该将该字段设置为命令块寻址的LUN。否则应该设置为0。对于U盘主机系统来说,因为U盘都不支持多个LUN,因此该字段应该设置为0。

  6. CBWCB:设备将执行的命令块

CSW

CSW (Command Status Wrapper)命令状态包,13个字节

CSW

  1. dCSWSignature:帮助指明该数据包为CSW的信号标记,这个字段的值为0x53425355(小端),表示这是一个CSW。

  2. dCSWTag:设备应将这个字段设置为接收到的相应CBW的dCBWTag字段值。

  3. dCSTDataResidue:对于DataOut,设备应在这个字段报告dCBWDataTransferLength字段规定的要求数量与设备实际处理的数据量之差。对于DataIn,设备应在这个字段报告dCBWDataTransferLength字段规定的要求数量与设备实际发送的数据量之差。dCSWResidue的值不会超过dCBWDataTransferLength发送的值。

  4. bCSWStatus:表示命令执行是否成功。0 = 执行成功,非0表示失败,如下表:

    00h命令通过
    01h命令失败
    02h状态错误
    03-04h保留(废弃)
    05-FFh保留

这部分处理主要对应vxworks源码中的usbTargRbcLib.cusbTargRbcCmd.c文件

遇到的问题

主要列举几个遇到的问题

  1. 枚举阶段,数据包时间延迟的要求

    枚举阶段不包含数据的标准设备请求超时时间很短,有资料说要在50ms内完成,非vxBus的TCD实现方式过于繁琐,接收和发送数据需要在多层之间反复调用,极大的浪费了时间。因此这块在调试时,精简了过程。vxBus下的TCD改变了这种多层架构,驱动可以直接获取ERP,框架限定的较少,给了驱动很大的灵活性,枚举阶段应该会更高效。

  2. vxworks6.9 usbTargRbcCmd.c中对SCSI_WRITE10(0x2A)命令的一次性写入的64k大小限制

    该问题是在主机写入超过64k大小的文件时,vxworks只接收处理64k数据到数据缓冲区中,不再进行接收,造成主机写入超时。

    解决办法,修改usbTargRbcCmd.c中在大于64k时,先接收64k,写入硬盘,再次启动接收,写入硬盘,直至接收完成。即在bulkOutErpCallbackData()函数中重复添加接收处理ERP。

  3. vxworks6.9 usbTargRbcCmd.c中对windows下弹出设备的bug

    该问题导致windows不能弹出设备,与usbTargRbcCmd.c文件中对RBC_CMD_PRVENTALLOWMEDIUMREMOVAL(0x1E)、RBC_CMD_TESTUNITREADY(0x00)以及对RBC_CMD_STARTSTOPUNIT(0x1B)的命令处理有关。看vxBus下的相关源码处理已经解决了该问题,所以尽量使用vxBus进行开发

usbmon使用

usbmon是一个linux内置的usb抓包工具,本质是一个内核模块,相较USB逻辑分析仪来说,显示格式及可读性并不友好,但是胜在不花钱。windows下推荐bushound。

1. 安装usbmon模块

modprobe usbmon
ls /sys/module/usbmon

发现该目录下有以下内容:0s、0u、1s、1t、1u、2s、2t、2u,其中1代表 bus1,2代表 bus2,0代表所有 usb 总线。

2. 监测数据

lsusb 

img

设备在Bus1上,设备号为5

cat /sys/kernel/debug/usb/usbmon/1u

开始监测

image-20230315170156375

参考文档

  1. usbmon数据抓取及其分析
  2. 基于OHCI的USB主机 —— UFI命令 USB Mass Storage Class Bulk-Only Transport协议介绍
  3. 基于STM32F103的USB学习笔记35 - Mass Storage之SCSI命令
  4. USB协议学习:USB设备的枚举过程
  5. 基于OHCI的USB主机 —— UFI命令 USB Mass Storage Class Bulk-Only Transport协议介绍
  6. 基于STM32F103的USB学习笔记35 - Mass Storage之SCSI命令
  7. USB协议学习:USB设备的枚举过程
  8. 描述符详细介绍

  1. 只有OTG设备中存在该引脚 ↩︎

目录 3 DMA驱动 1 3.1 简介 1 3.2 概要 1 3.3 VxBus驱动方法 1 3.3.1 {vxbDmaResourceGet}( ) 1 3.3.2 {vxbDmaResourceRelease}( ) 2 3.3.3 {vxbDmaResDedicatedGet}( ) 2 3.4 头文件 2 3.5 BSP配置 3 3.6 可用的工具函数 3 3.7 初始化 3 3.8 DMA系统结构和函数 3 3.8.1 (*dmaRead)( ) 3 3.8.2 (*dmaReadAndWait)( ) 4 3.8.3 (*dmaWrite)( ) 4 3.8.4 (*dmaWriteAndWait)( ) 4 3.8.5 (*dmaCancel)( ) 5 3.8.6 (*dmaPause)( ) 5 3.8.7 (*dmaResume)( ) 5 3.8.8 (*dmaStatus)( ) 5 3.9 调试 5 4 中断控制器驱动 5 4.1 介绍 6 4.2 概要 6 4.3 VxBus驱动方法 7 4.3.1 基本方法 7 4.3.2 动态向量方法 8 4.3.3 多处理器方法 9 4.4 头文件 9 4.5 BSP配置 10 4.5.1 中断输入表 10 4.5.2 动态向量表 11 4.5.3 CPU路由表 12 4.5.4 中断优先级 12 4.5.5 交差路由表 13 4.6 现有的工具函数 14 4.6.1 intCtlrHwConfGet( ) 14 4.6.2 intCtlrISRAdd( ) 14 4.6.3 intCtlrISRDisable( ) 14 4.6.4 intCtlrISREnable( ) 15 4.6.5 intCtlrISRRemove( ) 15 4.6.6 intCtlrPinFind( ) 15 4.6.7 intCtlrTableArgGet( ) 15 4.6.8 intCtlrTableFlagsGet( ) 15 4.6.9 intCtlrTableIsrGet( ) 15 4.6.10 intCtlrHwConfShow( ) 15 4.6.11 intCtlrTableCreate( ) 15 4.6.12 intCtlrTableFlagsSet( ) 15 4.1.13 intCtlrTableUserSet( ) 15 4.6.14 VXB_INTCTLR_ISR_CALL( ) 15 4.6.15 VXB_INTCTLR_PINENTRY_ENABLED( ) 16 4.6.16 VXB_INTCTLR_PINENTRY_ALLOCATED( ) 16 4.6.17 调度函数 16 4.7 初始化 16 4.8 中断控制器术语和层次 17 4.9 中断优先级 17 4.10 ISR调度 18 4.11 管理动态中断向量 20 4.12 中断输入的内部特征 22 4.13 VxWorks SMP 多处理器问题 22 4.14 调试 22 5 多功能驱动 23 5.1 介绍 23 5.2 概述 23 5.3 VxBus驱动方法 23 5.4 头文件 23 5.5 BSP配置 23 5.6 可用的工具函数 24 5.7 初始化 24 5.8 设备互联 24 5.8.1 交互寄存器 24 5.8.2 共享资源 25 5.8.3 其它交互 25 5.9 子设备的逻辑位置 25 5.10 调试 25 6 网卡驱动 25 6.1 介绍 25 6.1.1 术语 25 6.1.2 网络概述 26 6.2 网络接口驱动程序 27 6.2.1 网络接口驱动概述 27 6.2.2 网络接口驱动程序VxBus驱动方法 28 6.2.3 网络接口驱动程序头文件 33 6.2.4 网络接口驱动程序BSP配置 34 6.2.5 网络接口驱动程序可用的工具程序 34 6.2.6 网络接口驱动程序初始化 42 6.2.7 MUX:连接到网络代码 42 6.2.8 jobQueueLib:延迟中断处理 43 6.2.9 使用Ipcom_pkt包 43 6.2.10 netBufLib:用M_BLKs传输数据 46 6.2.11 协议对驱动程序的影响 48 6.2.12 其它的网络接口驱动问题 48 6.2.13 网络接口驱动程序的调试 48 6.3 PHY驱动程序 56 6.3.1 PHY驱动概述 56 6.3.2 PHY驱动程序的VxBus驱动方法 58 6.3.3 PHY驱动程序头文件 60 6.3.4 PHY驱动的BSP配置 60 6.3.5 PHY驱动程序拥有的工具程序 60 6.3.6 PHY驱动的初始化 62 6.3.7 PHY驱动的调试 62 6.4 无线以太网驱动 62 6.5 层次END驱动 62 7 Non-Volatile RAM驱动 63 7.1 介绍 63 7.2 Non-Volatile RAM 驱动 63 7.2.1 NVRAM驱动概述 63 7.2.2 针对NVRAM驱动的VxBus驱动方法 63 7.2.3 头文件 64 7.2.4 NVRAM驱动的BSP配置 64 7.2.5 NVRAM驱动的工具程序 65 7.2.6 NVRAM驱动的初始化 65 7.2.7 NVRAM块大小 65 7.2.8 栈NVRAM实例 66 7.2.9 调试NVRAM驱动 66 7.3 TureFFS Flash文件系统支持 66 7.3.1 TrueFFS概述 66 7.3.2 TrueFFS驱动开发流程 67 8 资源驱动 90 8.1 介绍 90 8.2 概要 90 8.3 VxBus驱动方法 91 8.4 头文件 91 8.5 BSP配置 91 8.6 可以的工具函数 91 8.7 初始化 91 8.8 调试 91 10 存储器驱动 92 10.1 介绍 92 10.2 概要 92 10.3 VxBus驱动方法 92 10.4 头文件 92 10.5 BSP配置 92 10.6 可用的工具程序 93 10.7 初始化 93 10.8 VxWorks文件系统关联接口 93 10.8.1 设备创建 93 10.8.2 处理 95 10.8.3 事件报告 95 10.9 写一个新的存储器驱动 96 10.10 调试 97 12 USB驱动 97 12.1 介绍 97 12.2 风河USB概要 98 12.2.1 USB主机栈驱动 98 12.2.2 USB外设栈驱动 98 12.3 主机控制器和根集线器类驱动 98 12.3.1 VxBus驱动方法 98 12.3.2 头文件 99 12.3.3 BSP配置 99 12.3.4 可用的工具函数 101 12.3.5 初始化 101 12.3.6 调试 101 13 其它驱动类 103 13.1 介绍 103 13.2 概要 103 13.3 VxBus驱动方法 103 13.4 头文件 104 13.5 BSP配置 104 13.6 可以的工具函数 104 13.7 初始化 104 13.8 调试 104
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值