USB系列之三:从你的U盘里读出更多的内容
U盘是我们最常使用的一种USB设备,本文继续使用DOSUSB做驱动,试图以读取扇区的方式读取你的U盘。本文可能涉及的协议可能会比较多。
一、了解你的U盘
首先我们用上一篇文章介绍的程序usbview.exe去看一下你的U盘,我在本文中用于测试的U盘情况如下:
Device Descriptor: (设备描述符) USB Address: Length: Descriptor Type: 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: Product Index: Serial Number Index: Number of Configuration: String Descriptor: (字符串描述符) Manufacturer: Alcor Micro Product: Mass Storage Device Configuration Descriptor: (配置描述符) Length: Descriptor Type: Total Length: Number of Interfaces: Configuration Value: Configuration Index: Attributes: Bus Powered Max Power: 50mA Interface Descriptor: (接口描述符) Length: Descriptor Type: Interface Number: Alternate Setting: Number of Endpoints: 2 Interface Class: 8 Mass Storage Device Interface Sub Class: 6 Interface Protocol: 80 Interface Index: Endpoint Descriptor: (端点描述符) Length: Descriptor Type: Endpoint Address: OUT endpoint Attributes: Bulk Max Packet Size: Interval: Endpoint Descriptor: (端点描述符) Length: Descriptor Type: Endpoint Address: IN endpoint Attributes: Bulk Max Packet Size: Interval:
各种描述符的含义在以前的文章中介绍过了,或者去翻阅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盘读更多的规范,如下:
- Universal Serial Bus Mass Storage Class Specification Overview
下载地址:http://www.usb.org/developers/docs/devclass_docs/Mass_Storage_Specification_Overview_v1.4_2-19-2010.pdf - Universal Serial Bus Mass Storage Class -- Bulk-Only Transport
下载地址:http://www.usb.org/developers/docs/devclass_docs/usbmassbulk_10.pdf - SCSI Primary Commands - 3(SPC-3)
下载地址:http://blog.hengch.com/specification/SCSI_Primary_Commands-3.pdf - SCSI Block Commands - 2(SBC-2)
下载地址:ftp://ftp.t10.org/t10/drafts/sbc2/sbc2r16.pdf
2009-01-13 ftp://ftp.t10.org/t10/drafts/sbc2/sbc2r16.pdf
2009-01-13 ftp://ftp.lab.unb.br/pub/docs/t10/t10/drafts/sbc2/sbc2r16.pdf
2009-01-13 ftp://ftp.u-aizu.ac.jp/pub/doc/scsi/t10.org/t10/drafts/sbc2/sbc2r16.pdf
2009-01-13 ftp://moscoso.org/pub/docs/t10/t10/drafts/sbc2/sbc2r16.pdf
2009-01-13 ftp://www.moscoso.org/pub/docs/t10/t10/drafts/sbc2/sbc2r16.pdf
2009-01-13 ftp://lab.unb.br/pub/docs/t10/t10/drafts/sbc2/sbc2r16.pdf
2009-01-13 ftp://164.41.45.4/pub/docs/t10/t10/drafts/sbc2/sbc2r16.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.hengch.com/source/reader.rar
各种概念在前面已经介绍过了,程序无非就是实现这些概念,几乎所有的代码都是围绕着填写数据结构和显示返回结果的,所以代码本身并不难,更重要的是理解数据结构中个字段的含义,这可能不得不阅读一些规范,我想我不可能比规范说的更严谨更完整。要注意的是,你使用的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.
USB系列之三:从你的U盘里读出更多的内容的更多相关文章
- USB系列之六:基于DOSUSB的简单U盘驱动程序
首先要说明的是,该驱动程序仅实现了部分块设备的功能,如果作为成品软件使用,会感觉性能比较差,而且有些功能(比如FORMAT)是不能完成的,发表此驱动程序的目的旨在说明USB的编程原理以及DOS下驱动程 ...
- USB系列之八:透过ASPI执行SCSI命令
在<USB系列之七>里我们介绍了ASPI的规范,并对一系列ASPI的命令做了测试,其中的02号命令是执行SCSI命令,我们专门在这篇文章中介绍,在<USB系列七>中,我们已经了 ...
- USB系列之七:ASPI介绍及命令测试
在以前的一篇博文<关于构建DOS下编程平台的总结>中曾经介绍了一种在DOS下驱动U盘的方法,我们大致回顾一下.在config.sys中加入两个驱动程序,就可以驱动U盘:device = a ...
- USB系列之五:用汇编实现的一些USB功能
前面的USB系列一至四,实现了我们需要的一些USB功能,但都是用C语言的32位代码,之后我们插进了三篇关于DOS下设备驱动程序的文章,我们现在应该清楚,当我们要在DOS下写一个U盘的驱动时,最好使用汇 ...
- USB系列之四:向U盘上写数据
在<USB系列之三>中,我们实现了一系列的SCSI命令,在这个系列中,我们要实现向U盘上写扇区的命令,所以,本文相对比较容易,更多地是给出一个实现的源程序. 在<USB系列之三> ...
- USB系列之九:基于ASPI的U盘驱动程序
USB系列之七和之八介绍了ASPI,并通过一些实例说明了基于ASPI的编程方法,本文使用前两篇文章介绍的知识以及以前介绍的有关DOS驱动程序下驱动程序的内容实际完成一个简单的基于ASPI的U盘驱动程序 ...
- .Neter玩转Linux系列之三:Linux下的分区讲解
基础篇 .Neter玩转Linux系列之一:初识Linux .Neter玩转Linux系列之二:Linux下的文件目录及文件目录的权限 .Neter玩转Linux系列之三:Linux下的分区讲解 .N ...
- DataSnap 2009 系列之三 (生命周期篇)
DataSnap 2009 系列之三 (生命周期篇) DataSnap 2009的服务器对象的生命周期依赖于DSServerClass组件的设置 当DSServer启动时从DSServerClass组 ...
- Sql Server来龙去脉系列之三 查询过程跟踪
我们在读写数据库文件时,当文件被读.写或者出现错误时,这些过程活动都会触发一些运行时事件.从一个用户角度来看,有些时候会关注这些事件,特别是我们调试.审核.服务维护.例如,当数据库错误出现.列数据被更 ...
随机推荐
- Azure Storage Client Library 重试策略建议
有关如何配置 Azure Storage Library 重试策略的信息,可参阅 Gaurav Mantri 撰写的一篇不错的文章<SCL 2.0 – 实施重试策略>.但很难找到关于使用何 ...
- 通过java类文件识别JDK编译版本
类文件中第5,6,7,8四个字节是jDK版本号信息,其中5,6为小版本号:7,8为大版本号. 大版本号对应JDK版本如下: JDK版本号 7,8字节 JDK8 52(0x34) JDK7 51(0x3 ...
- HashMap和Hashtable的差别
1. HashMap 与 Hashtable继承自不同的类 1) HashMap 继承自AbstractMap,而AbstractMap实现了Map接口 2) Hashtable 继承自Dict ...
- 【LeetCode练习题】Remove Duplicates from Sorted List II
Remove Duplicates from Sorted List II Given a sorted linked list, delete all nodes that have duplica ...
- 《Algorithms 4th Edition》读书笔记——3.1 符号表(Elementary Symbol Tables)-Ⅰ
3.1符号表 符号表最主要的目的就是将一个键和一个值联系起来.用例能够将一个键值对插入符号表并希望在之后能够从符号表的所有键值对中按照键值姐找到对应的值.要实现符号表,我们首先要定义其背后的数据结构, ...
- [置顶] Objective-C ,ios,iphone开发基础:自定义控件:Eg: UIButton
第一步:新建一个工程,在 .h文件中坐如下声明: #import <UIKit/UIKit.h> @interface MyButtonViewController : UIViewCon ...
- 第十四个目标(dp + 树状数组 + 线段树)
Problem 2236 第十四个目标 Accept: 17 Submit: 35 Time Limit: 1000 mSec Memory Limit : 32768 KB Probl ...
- Linux下redis的安装及用法
1.下载源代码包redis-2.8.21.tar.gz,并将其上传到指定文件夹/urs/src,然后对其进行解压: [root@Slave1pc src]# tar -xvf redis-2.8.21 ...
- [置顶] 【cocos2d-x入门实战】微信飞机大战之十二:分数的本地存储
转载请表明地址:http://blog.csdn.net/jackystudio/article/details/12036237 作为一个单机游戏,连分数存储的的功能都没有,让它怎么在单机游戏圈里混 ...
- nginx 搭建rtmp流媒体所用资源
Linux: 1.nginx安装包,自行下载. 2.nginx_mod_h264_streaming-2.2.7下载 3.nginx-rtmp-module-master下载 Windows: ngi ...