USB系列之三:從你的U盤里讀出更多的內容
U盤是我們最常使用的一種USB設備,本文繼續使用DOSUSB做驅動,試圖以讀取扇區的方式讀取你的U盤。
本文可能涉及的協議可能會比較多。
一、了解你的U盤
首先我們用上一篇文章介紹的程序usbview.exe去看一下你的U盤,我在本文中用于測試的U盤情況如下:
Device Descriptor: (設備描述符)
USB Address: 1
Length: 18
Descriptor Type: 1
USB Specification nr.: 0x0110
Calss Code: Class code specified by interface
Subclass Code: 0x00
Protocol Code: 0x00
MAX Packet Size: 0x08
Vendor ID: 0x058f
Product ID: 0x9321
Device Code: 0x0100
Manufacture Index: 1
Product Index: 2
Serial Number Index: 0
Number of Configuration: 1
String Descriptor: (字符串描述符)
Manufacturer: Alcor Micro
Product: Mass Storage Device
Configuration Descriptor: (配置描述符)
Length: 9
Descriptor Type: 2
Total Length: 32
Number of Interfaces: 1
Configuration Value: 1
Configuration Index: 0
Attributes: Bus Powered
Max Power: 50mA
Interface Descriptor: (接口描述符)
Length: 9
Descriptor Type: 4
Interface Number: 0
Alternate Setting: 0
Number of Endpoints: 2
Interface Class: Mass Storage Device
Interface Sub Class: 6
Interface Protocol: 80
Interface Index: 0
Endpoint Descriptor: (端點描述符)
Length: 7
Descriptor Type: 5
Endpoint Address: 1 OUT endpoint
Attributes: Bulk
Max Packet Size: 64
Interval: 0
Endpoint Descriptor: (端點描述符)
Length: 7
Descriptor Type: 5
Endpoint Address: 2 IN endpoint
Attributes: Bulk
Max Packet Size: 64
Interval: 0
各種描述符的含義在以前的文章中介紹過了,或者去翻閱USB的specification,這里就不多說了,我們從接口描述符開始就一些關鍵點進行一下說明。
首先看接口描述符,Interface Class = 8,表明是Mass Storage Device;Sub Class =
6,表明執行SCSI命令;Interface Protocol = 0x80,表明支持Bulk傳輸;另外,Number of Endpoints
= 2,表明有兩個端點。
兩個端點描述符要注意的是,Endpoint Address = 1的是OUT端點,Endpoint
Address =
2的是IN端點,有些可能會不一樣;有些U盤可能還會有第三個端點,比如支持中斷傳輸的U盤還會有一個Interrupt端點,不過這都沒有關系。
我大概看了我手頭有的5個U盤,都支持批量傳輸,且支持SCSI命令,所以,這可能是一個比較典型的例子,我們就以它為例。
二、CBW(Command Block Wrapper)和CSW(Command Status Wrapper)
在《USB系列之一》中,我們安裝了一個DOSUSB,在《USB系列之二》中,我們利用USBDOS讀取了所有的描述表,掌握這些內容需要了解USB協議1.1(USB Specification Revision 1.1)即可,當然還要了解USBDOS,不過這個比較簡單。
在系列一和系列二中,我們已經對DOSUSB的一個數據結構URB有所了解,本文中還要大量用到,我們還接觸了一個結構叫device_request,這個結構是在USB協議中定義的,用于向設備發送命令(Request),本文也會用到。
與前面不同的是,前面的兩個系列可以針對任何USB設備,比如U盤、攝像頭、打印機等,而本文將只針對我們經常使用的USB設備----U盤,如果你打算嘗試本文所介紹的內容,請準備好一個U盤,什么樣子的都行,或者是一個USB讀卡器,不過要記得插一張卡進去,實際上本文所載范例就是使用一個USB的CF卡讀卡器完成的,不用擔心損害你的U盤中的數據,本文不會對U盤進行任何寫操作,僅僅做一些讀操作。
這個系列中我們需要針對U盤讀更多的規范,如下:
(2017年3月15日注:下面四個鏈接已經修復)
Universal Serial Bus Mass Storage Class Specification Overview
下載地址:http://blog.whowin.net/specification/usb_msc_overview_1.2.pdfUniversal Serial Bus Mass Storage Class -- Bulk-Only Transport
下載地址:http://blog.whowin.net/specification/usbmassbulk_10.pdfSCSI Primary Commands - 3(SPC-3)
下載地址:http://blog.whowin.net/specification/SCSI_Primary_Commands-3.pdfSCSI Block Commands - 2(SBC-2)
下載地址:http://blog.whowin.net/specification/SCSI_Block_Command-2.pdf
不用為規范發愁,實際上,前兩個規范都很短,其中第一個對實際編程沒有什么作用,但最好看一下;第二個規范連目錄一共22頁,其中13頁以前的內容可以跳過(很多和USB Specification中相同),第三個規范主要看第6章,第四個規范主要看第5章,后兩個規范在編程時需要經常翻閱,以便了解你正在實現的SCSI命令的具體格式和參數。
本節我們主要介紹兩個新的數據結構,這兩個結構都是在第二個規范中定義的。
第一個數據結構叫CBW(Command Block Wrapper)
這個結構將承載具體的與設備有關的命令發送到設備上去,這個結構分成兩部分,第一部分從byte[0]--byte[14]共15個字節,第而部分從byte[15]--byte[30]共16個字節,整個數據結構為31個字節。規范中并沒有定義第二部分的內容,這是因為第二部分承載的具體的命令,既與命令集(SCSI命令集)有關,也與具體的命令有關,我們使用SCSI命令集,所以后16個字節的內容在前面提到的后面兩個規范中有定義。
比如我們要向設備發出一個SCSI命令INQUIRY(我們姑且先不要管命令的含義),那么這個命令的結構在SPC-3的第142頁有定義,如下:

對于SCSI INQUIRY這條命令而言,CBW的第二部分的定義就是上面的這六個字節,不同的命令,定義也會不同。
好,我們回到CSW的結構上來,根據規范,dCBWSignature的值必須是0X43425355,其實就是USBC這幾個字母倒過來,這是因為CBW的字符順序是little endian(這個東東在以前有關網絡編程的文章中介紹過),而我們PC機中的字符順序是big endian,所以要顛倒一下,總之寫dCBWSignature = 0X43425355就OK了;dCBWTag僅僅是一個標志,你可以填任何值,這里要先說一下CSW(Command Status Wrapper),我們每發出一個命令,設備都會返回一個CSW(這個東東下面很快就要介紹了),以說明命令的執行狀態,這個結構中也有Signature和Tag這兩個字段,其中Tag字段和發出命令時CBW中的Tag字段相同,這樣就可以區分這個CSW是和那個CBW對應的了,至于Signature,下面再說。
下一個字段是dCBWDataTransferLength,表示的是當這個命令發出后,我們希望設備返回數據的字符數或者我們要向設備傳輸的字符數,本文僅涉及從設備返回數據,不涉及向設備傳輸數據;舉例來說:我們發送INQIURY命令到設備,按照SPC-3第144頁的說明,該命令返回的數據至少為36個字節,所以,此時這個字節應該填36;再如:我們讀取U盤的一個扇區,如果扇區的長度是512個字節,那么這個字段就要填512。
再下來是bmCBWFlags字段,這個字段只有bit 7有意義,為0表示要向設備傳輸數據,為1表示要從設備獲得數據。
bCBWLUN字段總是填0,因為絕大多數的U盤都不支持多LUN(Logical Unit Number),只有一個邏輯單元自然好嗎就是0了。
bCBWCBLength字段是只CBW第二部分的長度,像前面舉例的INQUIRY命令,長度為6個字節,則這個字段就應該填6,再如:READ(10)命令的長度是10個字節(SBC-2第42頁有定義),這個字段當然要填10了。
第二個要說的數據結構是CSW,當host向device發送一個CBW后,接著就可以從device收到數據(或者發數據到device),當接受完所需的的數據后,就可以從device獲得一個CSW(Command Status Wrapper),CSW的結構如下:
前面說過,在CBW中的dCBWSignature的值恒為:0x43425355,得到的CSW中的dCSWSignature的值為:0x53425355,dCSWTag與dCBWTag中的一致。
在得到的CSW中,恒定有13個字節,bCSWStatus的定義如下:
三、發送命令和接收數據
我們知道USB協議中定義了三種傳輸方式,控制傳輸、批量傳輸、中斷傳輸和實時傳輸,在《USB系列二》中我們一直都在使用控制傳輸,我們應該比較熟悉了,本文中將涉及批量傳輸。
我們在使用控制傳輸時,我們設置好URB啟動傳輸事務,相應的結果將返回到制定得buffer中,批量傳輸沒有那么簡單,批量傳輸分為輸出事務和輸入事務,我們應該注意到,前面在看U盤的描述表時,在端點這一級有兩個端點,一個叫OUT端點,一個叫IN端點,當我們啟動一個輸出事務時,一定要發送給OUT端點,當我們啟動一個輸入事務時,一定要發送到輸入端點。下面我們簡單描述一下如何啟動批量傳輸事務。
在使用控制傳輸時,我們應該閱讀過DOSUSB的說明,并且對URB結構比較熟悉,URB中有一個字段叫transation_type,當這個值為0x2d時為控制傳輸;當為0x69時為批量傳輸的IN事務;當為0xe1時為批量傳輸的OUT事務;當我們啟動一個傳輸時,一定要正確地設置這個值。
我們以一個具體的例子來說明如何啟動一個傳輸,我們以SCSI INQUIRY命令為了,關于這個命令的定義在SPC-3的第142頁--157頁有說明,篇幅很長,但絕大多數篇幅用來解釋返回數據的含義,我們可以暫時不去理會。首先我們要填寫CBW結構,CBW結構的第一部分的填寫前面已經說的很明白了,第二部分的定義在SPC-3的第142頁,共有6個字節,我們要按照定義填寫好,實際上只要填兩個字段,一個是OPERATION CODE = 0X12,第二個就是ALLOCATION CODE = 36,表示需要返回36個字節的內容;CBW填好后,我們開始填寫URB,首先把CBW的偏移和段地址放到URB的buffer_off和buffer_seg中,把transation_type=0xe1,表示一個輸出事務,注意把end_point字段一定要放OUT endpoint的地址,從前面的描述符表中看,應該是1(2是IN endpoint的地址,你的機器可能不同),其它字段的填法在《USB系列二》中已經介紹過了,填完以后調用DOSUSB,這樣,一個承載著INQUIRY命令的輸出事務就發送到由URB中dev_add和end_point兩個字段指定的端點上去了。
接下來我們要接收device返回的執行INQUIRY命令的結果,這要啟動一個輸入事務,相對容易一些,只要填寫URB就可以了,把transation_type=0x69,把end_point填上OUT endpoint的地址,本例中為2,buffer_off和buffer_seg指向緩沖區buffer,把buffer_length和actual_length均填為64,因為前面端點描述符表中寫明包的最大長度為64,其它字段按常規填寫,調用DOSUSB,在buffer中就可以得到返回的內容,按照SPC-3中對返回內容的解釋即可了解設備的一些情況。
接收晚數據后,不要忘了接收CSW,方法也是啟動一個輸入事務,與接收數據完全相同,然后根據CSW的結構解釋其含義。至此一個命令執行完畢。
四、范例
在本文的范例中,我們實現了如下內容:
實現了Bulk-Only Mass Storage Reset
實現了Get Max LUN
實現了SCSI INQUIRY Command
實現了SCSI READ CAPACITY (10) Command
實現了SCSI REQUEST SENSE Command
實現了SCSI TEST UNIT READY Command
實現了SCSI READ (10) Command
最后的一個命令,我將從你的U盤上讀出一個扇區。
最前面的兩個命令,請翻閱《Universal Serial Bus Mass Storage Class - Bulk-Only Transport》第7頁;INQUIRY、REQUEST SENSE、TEST UNIT READY三個命令請翻閱SPC-3的第142、221和232頁;READ CAPACITY(10)和READ(10)命令,請翻閱SBC-2的第42和44頁。
源代碼請在下面網址下載:
http://blog.whowin.net/source/reader.rar(2017年3月15日注:這個鏈接已經修復)
各種概念在前面已經介紹過了,程序無非就是實現這些概念,幾乎所有的代碼都是圍繞著填寫數據結構和顯示返回結果的,所以代碼本身并不難,更重要的是理解數據結構中個字段的含義,這可能不得不閱讀一些規范,我想我不可能比規范說的更嚴謹更完整。要注意的是,你使用的U盤不可能和我的完全一致,一般情況下有可能有變化的是:設備地址devAddr、輸出端點地址outEndpoint和輸入端點地址inEndpoint,所以在編譯程序之前一定要使用《USB系列之二》中的方法仔細查看一下你的U盤的各種描述符表,如果這些值和我的U盤不同,請在主程序開始的地方,更改這幾個變量;另外,在主程序6th step中,scsiRead10(0),傳遞給scsiRead10的參數為0,含義是從LBA(Logical Block Address)為0的地方讀取一個扇區,如果你向讀取其它扇區,可以更改這個值,其最大值我們在實現 READ CAPACITY時已經讀出了,可以參考;此外,注意CBW的字符順序是little endian,所以我們在填寫LBA和讀取最大LBA時都做了相應的轉換。
好了,應該沒有什么了!
Enjoy it.
http://hengch.blog.163.com/blog/static/10780067200851283245935/
*博客內容為網友個人發布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。















