首页 > 代码库 > 利用mass storage class 做免驱动usb设备.

利用mass storage class 做免驱动usb设备.

  当需要使用usb bulk传输,想让设备像串口通讯那样和PC主机通信, 通常需要自己做一个PC端的驱动,比较麻烦.

  为避免在pc上编写usb设备驱动的麻烦,可以将设备做成mass storage 类的设备,使用通用的驱动.

  在通讯之前设备端需要先做两件事:

  1,实现mass storage 类的描述符和类请求.

  2,实现必要的SCSI命令,让PC认为该设备已正常运作.

 

我利用修改linux中的gadget zero设备做了一个简单的设备. 如果是在裸机程序下面做,应该也差不多,直接拿芯片厂商BSP中的USB样例程序修改即可.

  

  

  

1实现mass storage 类的描述符和类请求.

mass storage

在linux中对应代码:

1)设备描述符

static struct usb_device_descriptor device_desc = {	.bLength =		sizeof device_desc,	.bDescriptorType =	USB_DT_DEVICE,	.bcdUSB =		cpu_to_le16(0x0200),//	.bDeviceClass =		USB_CLASS_VENDOR_SPEC,	.bDeviceClass =		USB_CLASS_PER_INTERFACE,	.idVendor =		cpu_to_le16(DRIVER_VENDOR_NUM),	.idProduct =		cpu_to_le16(DRIVER_PRODUCT_NUM),	.bNumConfigurations =	1,};

 设备描述符没什么特殊的,因为PC端usb驱动是与设备的接口对应的,与mass storage class对应的是接口描述符

2)接口描述符

/* SCSI device types */#define TYPE_DISK	0x00#define TYPE_CDROM	0x05/* USB protocol value = http://www.mamicode.com/the transport method */>

 符合usb mass storage 类规范。对应下表

    

使用SCSI命令集,协议实现是Bulk-Only 传输。

3)实现一个mass storage 类的请求

	case USB_BULK_GET_MAX_LUN_REQUEST:		printk("USB_BULK_GET_MAX_LUN_REQUEST\n");		if (ctrl->bRequestType !=		    (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE))			break;		*(u8 *) req->buf = 0;		/* Respond with data/status */		req->length = min((u16)1, w_length);		value = http://www.mamicode.com/usb_ep_queue(f->config->cdev->gadget->ep0, req, GFP_ATOMIC);"source/sinkc response, err %d\n",					value);		return(value);

 简单返回了一个0。

在linux中,linux把一些诸如获取描述符之类的请求集中在了一起放在了composite.c 中,不同设备类请求放在各自个f_xxx.c中各自的接口的xxx_setup函数中。

 

当实现了以上描述符和类请求之后,把嵌入式设备接上电脑,windows就会在设备管理器中列出usb mass storage设备。不过有一个黄色感叹号。

根据usb抓包情况来看是,电脑上面驱动发送SCSI命令数次不成功之后,会重新枚举过程,数次不正常之后就会认为该设备不正常。

 

2)必要的SCSI命令

大概要处理mass storage pc端驱动发过来的一下命令

#define SC_INQUIRY   0x12

#define SC_TEST_UNIT_READY  0x00

#define SC_READ_CAPACITY  0x25
#define SC_READ_FORMAT_CAPACITIES 0x23

前两条应该是必须的,后两条我也给加上了,去掉行不行,没有测试。

这些命令即可以放到linux gadget driver中也可以放到应用层程序中处理. 我是放到了应用层.

处理的流程基本是:

接收SCSI命令----->处理SCSI命令----->返回状态

基本是按照SCSI协议进行

CBW:Command Block Wrapper   命令块数据包

CSW:Command Status Wrapper  命令执行状态

按照CBW和CSW格式定义结构体:

struct ms_cbw_struct{	u32 dCBWSignature;	u32 dCBWTag;	u32 dCBWDataTransferLength;	u8 bmCBWFlags;	u8 bCBWLUN;	u8 bCBWCBLength;	u8 CBWCB[SCSI_CMD_MAX_LEN];};struct ms_csw_struct{	u32 dCSWSignature;	u32 dCSWTag;	u32 dCSWDataResidue;	u8 bCSWStatus;};

 

以SC_INQUIRY   命令为例

当我程序收到 0x12 命令,我就要按照scsi协议中该命令的规范来处理,我需要返回下面表格格式的数据给主机

 

.

第一个字节后5位决定了主机识别成一个cdrom或是可移动盘或其他类型的设备.

 RMB表示是否是一个可以移除设备.

Additional length (n-4)  需要填写.

其他的可根据需要填写.

 

之后返回状态CSW:

dCSWSignature固定为0x53425355,

dCSWTag 与CBW发过来的相同,

dCSWDataResidue等于CBW中要得长度和实际长度的差值.

bCSWStatus 表示命令成功或失败, 0表示成功,其他值表示失败.

 

另外这条命令

#define SC_TEST_UNIT_READY  0x00

是主机会在空闲时间不停发送的. 并且一开始连上主机,如果这条命令返回的CSW 成功,那么主机会使用READ_FORMAT_CAPACITIES 命令查询格式化的容量,read(10)读文件系统的信息. 如果得不到正确信息windows就会跳出对话框问你要不要格式化等等.

由于现在我并非真的需要做一个U盘之类的设备,所以0x00 命令,我CSW直接返回1. 这样当你点击该设备的盘符,就会提示说没有设备插入. 这对我没有影响,我只是用mass storage这个壳来进行通信的. 只是骗过mass storage的标准驱动而已. 

 

现在我就可以通过自定义的SCSI命令或者改写标准的命令来进行通信了.