笔者来简单介绍一下SCSI得协议命令
1、SCSI协议认识
- SCSI:Small Computer System Interface,用于计算机外部设备得接口标准,定义了与外部设备得一套协议。
- SCSI标准协议族支持很多钟SCSI设备,像盘,打印机,扫描仪等等)。这个标准定义了一个支持所有SCSI设备得设备模型。其他SCS命令标准扩展了通用得SCSI设备模型,以满足适配特定得SCIS设备。
- SCSI用CDB来描述命令协议,CDB:command description block,主要有6字节、10字节、12字节、16字节命令协议。通常第一个字节用于描述命令码、操作码。
- lun:logci unit number,逻辑单元号,指逻辑设备ID,通过该ID可以寻找SCSI下面挂载的设备。
2、SCSI协议命令
首先介绍一下CDB命令格式以及参数得意义,然后介绍各自SCSI命令。
2.1 、CDB命令格式
比如6字节命令。
- OPERATION CODE:操作码
- LOGICAL BLOCK ADDRESS:逻辑块地址,以块为单位的地址,例如块大小为4K,那么1的地址就是0x1000,2就是0x2000,
- TRANSFER LENGTH / PARAMETER LIST LENGTH/ ALLOCATION LENGTH:传输长度/参数长度/分配长度,传输的长度也是以块为单位的,块的大小,总共地址的大小,可以通过read Capcacity来读取回来。
- CONTROL:控制type,所有的命令都有。
可以看到6字节 的块地址只占2个Byte,length 只占1个Byte,所以其寻址的范围是有限的。
例如:Block_Size = 0x1000 - Block_addr_end = 0x10000 * 0x1000 = 256M B。
- 一次读的数据长度也有限制, 0x100*0x1000 =1MB。
对于大容量存储的设备来说,明显不够,所以后面由推出了10Byte、12Byte、16Byte、32Byte的命令格式。
由上图来看,明显是增加了logic block address 的位数以及length的位数,可以访问的地址范围更大, - address:4Byte,end = 16TB,还是以Block Size=4K来算。
- length:4Byte,
2.2 、CDB具体命令
2.2.1 Test_Unit_Ready
用来询问设备是否处于ready状态,是否可以正常接收媒介数据访问命令并正确处理。
- 注意其不是一个请求自我测试的命令
- 可以通过start unit命令让其处于ready状态。
- 如果处于不可操作的状态,则会返回NOT Ready的sense key。
命令直接填个00就可以,比较简单。
如果是ready状态,device不回复SCSI的数据。从下面代码中可以看到,如果是异常会,返回NOT_Ready的Sense key。
static int8_t SCSI_TestUnitReady(uint8_t lun, uint8_t *params)
{
/* case 9 : Hi > D0 */
if (MSC_BOT_cbw.dDataLength != 0)
{
SCSI_SenseCode(MSC_BOT_cbw.bLUN,
ILLEGAL_REQUEST,
INVALID_CDB);
return -1;
}
if(USBD_STORAGE_fops->IsReady(lun) !=0 )
{
SCSI_SenseCode(lun,
NOT_READY,
MEDIUM_NOT_PRESENT);
return -1;
}
MSC_BOT_DataLen = 0;
re
2.2.2 Inquiry
- Length:可以填35,获取标准的厂商设备信息查询
- 其他字段都可以填0
响应格式: - Device Type:存储设备类型
- length:返回的数据长度
- verdor : 厂商信息
- product:产品信息
- product version:版本信息
一般标志的设备信息,都默认36字节,如果多个设备,通过lun去偏移。
//USB Mass storage 标准查询数据(每个lun占36字节)
const int8_t STORAGE_Inquirydata[] = {
/* LUN 0 */
0x00,
0x80,
0x02,
0x02,
(USBD_STD_INQUIRY_LENGTH - 4),
0x00,
0x00,
0x00,
/* Vendor Identification */
'A', 'L', 'I', 'E', 'N', 'T', 'E', 'K', ' ',//9字节
/* Product Identification */
'S', 'P', 'I', ' ', 'F', 'l', 'a', 's', 'h',//15字节
' ','D', 'i', 's', 'k', ' ',
/* Product Revision Level */
'1', '.', '0', ' ', //4字节
/* LUN 1 */
0x00,
0x80,
0x02,
0x02,
(USBD_STD_INQUIRY_LENGTH - 4),
0x00,
0x00,
0x00,
/* Vendor Identification */
'A', 'L', 'I', 'E', 'N', 'T', 'E', 'K',' ', //9字节
/* Product Identification */
'N', 'A', 'N', 'D', ' ', 'F', 'l', 'a', 's', 'h',//15字节
' ','D', 'i', 's', 'k',
/* Product Revision Level */
'1', '.', '0' ,' ', //4字节
/* LUN 2 */
0x00,
0x80,
0x02,
0x02,
(USBD_STD_INQUIRY_LENGTH - 4),
0x00,
0x00,
0x00,
/* Vendor Identification */
'A', 'L', 'I', 'E', 'N', 'T', 'E', 'K',' ', //9字节
/* Product Identification */
'S', 'D', ' ', 'F', 'l', 'a', 's', 'h', ' ',//15字节
'D', 'i', 's', 'k', ' ', ' ',
/* Product Revision Level */
'1', '.', '0' ,' ', //4字节
};
static int8_t SCSI_Inquiry(uint8_t lun, uint8_t *params)
{
uint8_t* pPage;
uint16_t len;
if (params[1] & 0x01)/*Evpd is set*/
{
pPage = (uint8_t *)MSC_Page00_Inquiry_Data;
len = LENGTH_INQUIRY_PAGE00;
}
else
{
pPage = (uint8_t *)STORAGE_Inquirydata[lun * USBD_STD_INQUIRY_LENGTH];
len = pPage[4] + 5;
if (params[4] <= len)
{
len = params[4];
}
}
MSC_BOT_DataLen = len;
while (len)
{
len--;
MSC_BOT_Data[len] = pPage[len];
}
r
2.2.3 Read
read10命令
主要关注的 address、length,其他填0,可以读device中的数据。
2.2.4 Write
write10命令。
主要关注的 address、length,其他填0,可以向device写数据。
2.2.5 ReadCapacity
read10 命令:获取device设备的容量以及块大小。
PMI以及 LBA均填0则行。
响应值:
returned logical block address:返回的最大的逻辑块的地址
由下面代码可以看出,返回的就是逻辑块的size和逻辑块的最大地址。
int8_t STORAGE_GetCapacity (uint8_t lun, uint32_t *block_num, uint32_t *block_size)
{
switch(lun)
{
case 0://SPI FLASH
*block_size=512;
*block_num=1024*1024*25/512; //SPI FLASH的前面25M字节,文件系统用
break;
case 1://NAND FLASH
*block_size=512;
*block_num=nand_dev.valid_blocknum*nand_dev.block_pagenum*nand_dev.page_mainsize/512;
break;
case 2://SD卡
*block_size=512;
*block_num=SDCardInfo.CardCapacity/512;
break;
}
return 0;
}
static int8_t SCSI_ReadCapacity10(uint8_t lun, uint8_t *params)
{
if(USBD_STORAGE_fops->GetCapacity(lun, &SCSI_blk_nbr[lun], &SCSI_blk_size) != 0)
{
SCSI_SenseCode(lun,
NOT_READY,
MEDIUM_NOT_PRESENT);
return -1;
}
else
{
MSC_BOT_Data[0] = (uint8_t)((SCSI_blk_nbr[lun] - 1) >> 24);
MSC_BOT_Data[1] = (uint8_t)((SCSI_blk_nbr[lun] - 1) >> 16);
MSC_BOT_Data[2] = (uint8_t)((SCSI_blk_nbr[lun] - 1) >> 8);
MSC_BOT_Data[3] = (uint8_t)(SCSI_blk_nbr[lun] - 1);
MSC_BOT_Data[4] = (uint8_t)(SCSI_blk_size >> 24);
MSC_BOT_Data[5] = (uint8_t)(SCSI_blk_size >> 16);
MSC_BOT_Data[6] = (uint8_t)(SCSI_blk_size >> 8);
MSC_BOT_Data[7] = (uint8_t)(SCSI_blk_size);
MSC_BOT_DataLen = 8;
return 0;
}
}
2.2.6 Request SenseCode
用来请求sense data,sense data则是记录通信过程中的错误信息。
DESC Bit:用来决定返回的响应值是哪种格式。
主要关注三个值即可。
SENSE KEY
ADDITIONAL SENSE CODE
ADDITIONAL SENSE CODE QUALIFIER
可以依次理解为:主错误码、子错误码和子子错误码的意思。
如果DESC置1,则是下面这张响应格式
如果是0,则是固定的格式
3、SCSI协议命令测试
3.1 Test_Unit_Ready
主机询问是否TestReady,从机什么都不回复就正常,如果有异常,csw 会返回failed,从而通过sense key返回错误状态。
如果data length不等于0,则会返回无效请求的错误信息。
此时csw 会返回failed的status=1,然后就会通过sense data来知道是什么错误信息。
sense key = 0x5,无效的请求
ASC= 0x20,无效的CDB命令。
正常Test unit ready命令,data len = 0,但是上面的cbw data transfer length =8,就会引发device回复错误,看上面代码。
3.2 ReadCapacity
最大的地址=0xC800,50K个块数据
块size = 0x200,512Byte
3.3 Write
地址=0x010000 ,长度为512。
3.4 Read
块size = 0x200,所以read的len=0x1,则data length 需要设置512,否则就会报错。
然后通过write写进去的数据,可以通过read读出来。
3.5 Inquiry Data
需要设置读取的数据长度是多少,返回的数据就是多少,如果是0,肯呢个会出现异常。
4、BusHound 抓包说明
1、勾选Devices,就可以抓对应设备的USB协议包
2、选择Setting
- 可以选择需要抓的包的类型,比如SCSI Command,USB control transfer命令,
- 也可以修改左上角的抓包限制,改最大,避免抓包的长度太小,没有记录到需要的数据。
- 左下角可以选择触发暂停,比如下图中出现无效的命令时暂停,
3、选择Capature,点击start,就可以看到抓包的数据。
4、点击Devices,双击设备,可以出现Bus Commander,可以用来发送USB命令或者SCSI命令,方便调试协议。