vxworks usb tcd实现

本文仅讨论在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设备中存在该引脚 ↩︎

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值