當須要使用usb bulk傳輸,想讓設備像串口通信那樣和PC主機通訊, 一般須要本身作一個PC端的驅動,比較麻煩.linux
爲避免在pc上編寫usb設備驅動的麻煩,能夠將設備作成mass storage 類的設備,使用通用的驅動.windows
在通信以前設備端須要先作兩件事:app
1,實現mass storage 類的描述符和類請求.函數
2,實現必要的SCSI命令,讓PC認爲該設備已正常運做.測試
我利用修改linux中的gadget zero設備作了一個簡單的設備. 若是是在裸機程序下面作,應該也差很少,直接拿芯片廠商BSP中的USB樣例程序修改便可.spa
1實現mass storage 類的描述符和類請求.blog
mass storage接口
在linux中對應代碼:ip
1)設備描述符ci
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 = the transport method */ #define USB_PR_CBI 0x00 /* Control/Bulk/Interrupt */ #define USB_PR_CB 0x01 /* Control/Bulk w/o interrupt */ #define USB_PR_BULK 0x50 /* Bulk-only */ /* USB subclass value = the protocol encapsulation */ #define USB_SC_RBC 0x01 /* Reduced Block Commands (flash) */ #define USB_SC_8020 0x02 /* SFF-8020i, MMC-2, ATAPI (CD-ROM) */ #define USB_SC_QIC 0x03 /* QIC-157 (tape) */ #define USB_SC_UFI 0x04 /* UFI (floppy) */ #define USB_SC_8070 0x05 /* SFF-8070i (removable) */ #define USB_SC_SCSI 0x06 /* Transparent SCSI */ /* Bulk-only class specific requests */ #define USB_BULK_RESET_REQUEST 0xff #define USB_BULK_GET_MAX_LUN_REQUEST 0xfe static struct usb_interface_descriptor source_sink_intf = { .bLength = sizeof source_sink_intf, .bDescriptorType = USB_DT_INTERFACE, .bNumEndpoints = 2, // .bInterfaceClass = USB_CLASS_VENDOR_SPEC, .bInterfaceClass = USB_CLASS_MASS_STORAGE, .bInterfaceSubClass = USB_SC_SCSI, .bInterfaceProtocol = USB_PR_BULK, /* .iInterface = DYNAMIC */ };
符合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 = usb_ep_queue(f->config->cdev->gadget->ep0, req, GFP_ATOMIC); if (value < 0) ERROR(f->config->cdev, "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命令或者改寫標準的命令來進行通訊了.