本文仅讨论在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文件
系统架构
软件层面主要分为三层:
- 驱动层:主要负责硬件设备的初始化,配置,中断具体处理,状态获取,数据读写等,是与硬件直接接触的一层
- 硬件抽象层:主要负责将硬件设备的状态,中断,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) ¶ms,
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
至此,启动阶段的初始化基本完成
中断处理调用流程
如图所示,可以看出函数usbHalIsr及usbTcdxxxEvalExec是两个很重要的函数,中断状态初步获取完成后会传递给usbHalInterruptThread任务,线程化。后续处理在该任务中进行,诸如断开、复位、唤醒、EP中断处理、挂起等,功能类层会将要处理的消息类型及回调函数指针等打包为一个ERP结构体,放在链表上,等硬件抽象层收发数据时,从链表上取出,进行对应的处理
功能类层负责usb枚举过程中的诸如获取设备描述符、设置设备地址等USB标准协议处理。枚举完成后,转入SCSI协议后,功能类层也负责SCSI协议的处理
USB基本知识
引脚定义
名称 | 描述 | |
---|---|---|
1 | Vbus | 电源正5v |
2 | D- | 数据- |
3 | D+ | 数据+ |
4 | ID | OTG识别:接地,A型,Host;悬空,B型,Device1 |
5 | GND | 地 |
OTG设备的Host及Device识别,是通过连接线将Host端的ID引脚接地,Device端的ID引脚悬空实现的
这里将OTG控制器强制配置成Device设备,不存在ID识别过程
USB连接模型
主从结构
USB总线上所有的数据传输都由主机主动发起,而设备只是被动的负责应答。例如,在读数据时,USB主机先发出读命令,设备收到该命令后,才返回数据。
USB拓扑结构图
通信流模型
端点:USB设备端概念,是真实的物理设备。分为控制端点和数据端点,控制端点即0地址端点,每个设备都必须包含一个控制端点,双向传输,其主要功能使主机实现对设备的配置如设备描述符,配置描述符和字符串描述的获取和对设备的配置(如设备地址的设置等)。数据端点用于数据的传输
管道:USB主机和设备使用管道进行数据通讯。管道是USB主机在软件层面上的一个抽象.管道可以理解为USB主机端对USB端点的软件抽象,所以它包括USB设备端点的所有信息。
方向是相对主机来说的,IN为主机的输入方向,OUT为主机的输出方向
USB传输
包Packet
usb总线上传输的数据都是以包为基本单位的。一个包可以被分成不同的域,不同类型的包,有相同特点,以SOP开始,同步域SYNC,紧跟一个包标识符PID,最终以包结束符EOP结束
根据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
分为四种传输类型,批量传输、中断传输、同步传输、控制传输;同步传输、批量传输每次需要一个或多个事务,中断传输每次需要一个事务
控制传输:主机在枚举设备时使用该传输
同步传输:使用同步事务,适用于数据量大、对实时性要求高的场合,如音频、视频设备
批量传输:使用批量事务,适用于数据量大、对实时性要求不高的场合,如打印机、大容量存储设备
中断传输:使用中断事务,适用于周期性、低频率,允许有限延迟,可以保证主机查询频率,在小于要求的时间间隔内安排一次传输,如鼠标设备
枚举
概念
枚举就是从设备读取一些信息,知道设备是什么样的设备,如何进行通信,这样主机就可以根据这些信息来加载合适的驱动程序
枚举过程
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.
1B | 1B | 2B | 2B | 2B |
---|---|---|---|---|
bmRequestType | bReqest | wValue | wIndex | wLength |
bmRequestType字段(1字节
D7,下一步数据的传输方向
0 = 主机到设备(SET请求)
1 = 设备到主机(GET请求)D6-D5,命令的类型
00 = 标准请求命令
01 = 类请求命令(UVC规范中的请求属于类请求命令)
10 = 用户定义的请求命令
11 = 保留D4-D0,命令接受者的类型
00000 = 接受者为设备
00001 = 接收者为接口
00010 = 接受者为端点
00011 = 其它
4…31 = 保留
其他值保留。
bReqest字段(1字节
bRequest Value GET_STATUS 0 CLEAR_FEATURE 1 SET_FEATURE 3 SET_ADDRESS 5 GET_DESCRIPTOR 6 SET_DESCRIPTOR 7 GET_CONFIGURATION 8 SET_CONFIGURATION 9 GET_INTERFACE 10 SET_INTERFACE 11 SYNCH_FRAME 12 SET_ENCRYPTION 13 (USB2.0以后) GET_ENCRYPTION 14 SET_HANDSHAKE 15 GET_HANDSHAKE 16 SET_CONNECTION 17 SET_SECURITY_DATA 18 GET_SECURITY_DATA 19 SET_WUSB_DATA 20 LOOPBACKDATAWRITE 21 LOOPBACK_DATA_READ 22 SET_INTERFACE_DS 23 SET_SEL 48 SET_ISOCH_DELAY 49
NOTE:完整版标准请求格式可以查看USB2.0标准
USB描述符
设备描述符是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命令集
-
dCBWSignature:帮助指明该数据报为CBW的信号标记。这个字段的值为0x43425355(小端),表示这是一个CBW。
-
dCBWTag:主机发送的命令块标签。设备应在相关CBW的dCSWTag字段中将这个字段的内容返回给主机。dCSWTag将CSW与对应的CBW联系起来。
-
dCBWDataTransferLength:主机要求在执行CBW命令期间,在批量输入或批量输出端点传输数据字节数。如果该字段为0,则设备和主机不应该在CBW和相关的CSW中间传输数据,设备应该忽略bmCBWFlags中方向位的值。注意,这个字段指明的是跟在CBW之后数据包的长度。
-
bmCBWFlags:本字段的位定义如下:
位7:方向。0 = 从主机到设备的DataOut,1 = 从设备到主机的DataIn;
位6:废弃的,主机应该将该位设置为0;
位5-0:保留,主机应该将该位设置为0; -
bCBWLUN:命令块发送的设备逻辑单元号(LUN)。对于支持多个LUN的设备,主机应该将该字段设置为命令块寻址的LUN。否则应该设置为0。对于U盘主机系统来说,因为U盘都不支持多个LUN,因此该字段应该设置为0。
-
CBWCB:设备将执行的命令块
CSW
CSW (Command Status Wrapper)命令状态包,13个字节
-
dCSWSignature:帮助指明该数据包为CSW的信号标记,这个字段的值为0x53425355(小端),表示这是一个CSW。
-
dCSWTag:设备应将这个字段设置为接收到的相应CBW的dCBWTag字段值。
-
dCSTDataResidue:对于DataOut,设备应在这个字段报告dCBWDataTransferLength字段规定的要求数量与设备实际处理的数据量之差。对于DataIn,设备应在这个字段报告dCBWDataTransferLength字段规定的要求数量与设备实际发送的数据量之差。dCSWResidue的值不会超过dCBWDataTransferLength发送的值。
-
bCSWStatus:表示命令执行是否成功。0 = 执行成功,非0表示失败,如下表:
值 00h 命令通过 01h 命令失败 02h 状态错误 03-04h 保留(废弃) 05-FFh 保留
这部分处理主要对应vxworks源码中的usbTargRbcLib.c
、usbTargRbcCmd.c
文件
遇到的问题
主要列举几个遇到的问题
-
枚举阶段,数据包时间延迟的要求
枚举阶段不包含数据的标准设备请求超时时间很短,有资料说要在50ms内完成,非vxBus的TCD实现方式过于繁琐,接收和发送数据需要在多层之间反复调用,极大的浪费了时间。因此这块在调试时,精简了过程。vxBus下的TCD改变了这种多层架构,驱动可以直接获取ERP,框架限定的较少,给了驱动很大的灵活性,枚举阶段应该会更高效。
-
vxworks6.9 usbTargRbcCmd.c中对SCSI_WRITE10(0x2A)命令的一次性写入的64k大小限制
该问题是在主机写入超过64k大小的文件时,vxworks只接收处理64k数据到数据缓冲区中,不再进行接收,造成主机写入超时。
解决办法,修改usbTargRbcCmd.c中在大于64k时,先接收64k,写入硬盘,再次启动接收,写入硬盘,直至接收完成。即在bulkOutErpCallbackData()函数中重复添加接收处理ERP。
-
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
设备在Bus1上,设备号为5
cat /sys/kernel/debug/usb/usbmon/1u
开始监测
参考文档
- usbmon数据抓取及其分析
- 基于OHCI的USB主机 —— UFI命令 USB Mass Storage Class Bulk-Only Transport协议介绍
- 基于STM32F103的USB学习笔记35 - Mass Storage之SCSI命令
- USB协议学习:USB设备的枚举过程
- 基于OHCI的USB主机 —— UFI命令 USB Mass Storage Class Bulk-Only Transport协议介绍
- 基于STM32F103的USB学习笔记35 - Mass Storage之SCSI命令
- USB协议学习:USB设备的枚举过程
- 描述符详细介绍
只有OTG设备中存在该引脚 ↩︎