USB_scsi 之旅

时间:2021-12-29 10:58:26

  现在总结一下scsi,scsi协议有很多,所以只总结这次在usb mass storage里面用到的协议,主要包括inquiry,format , read write等等命令。

下面会一个一个总结。

U盘需要处理的命令如下:

1:inquiry:设备的一个描述,告诉host你的设备是什么,名字叫什么,用的什么协议,这里用的SCSI协议—SPC2

2:READ FORMAT CAPACITIES:读格式容量(The READ FORMAT CAPACITIES command allows the host to request a list of the possible capacities that can be formatted on the currently installed medium.)

3:READ CAPACITY:读取容量信息

4:READ(10):回发在逻辑单元的数据,既回发MBR(Main Boot Record)主引导扇区

5:SENSE6:目的在于获得设备内部很多潜在的信息,其中包括了是否设置了写保 (The MODE SENSE(6) command (see table 62) provides a means for a device server to report parameters to an application client. It is a complementary command to the MODE SELECT(6) command. Device servers that implement the MODE SENSE(6) command shall also implement the MODE SELECT(6) command.)

6:WRITE(10):host向slave发生数据并写在u盘存储器里面。

7:TEST UNIT READY:检查U盘准备好没有

  • 1:inquiry
// The standard INQUIRY data shall contain at least 36 bytes
// This is the reduced structure for Mass Storage Devices

typedef struct
{
  cyg_uint8 peripheral;        // Device Type
  cyg_uint8 rmb;               // Removable Media Bit
  cyg_uint8 version;           // Version Field
  cyg_uint8 resp_data_format;  // Response Data Format
  cyg_uint8 additional_len;    // Additional Length
  cyg_uint8 sccstp;            // SCC Supported (include embedded storage array)
  cyg_uint8 bque;              // Basic Queuing
  cyg_uint8 cmdque;            // Command Queuing
  cyg_uint8 vendor_id[];
  cyg_uint8 product_id[];
  cyg_uint8 product_rev[];
} msd_scsi_inq_resp;

这个结构体就是为inquiry准备的数据包,这个命令是usb mass storage第一个接收到的命令

USB_scsi 之旅

  在这里只需要关注两个字段OPERATION CODE,ALLOCATION LENGTH。其它的还没用到,每个命令都有一个OPERATION CODE用来标志这个命令是什么命令,毕竟我们不会看数字就知道什么意思,电脑也不会看文字就是要做什么,是吧。现在这里是12,我们查一查SPC-2协议,就知道OPERATION CODE为12就知道这次host需要slave做什么操作。

  既然知道了这个操作,那么咱们就应该回复一下,毕竟来而不往非礼也。那咱们得准备一下礼物送给host,不然这个门打不开,马上就关起来了,就好像现在这个社会,有钱有权什么都可以,这个社会让我们这些刚毕业的怎么生存啊,咱学习嵌入式就是为了多赚点money。下面就是一个能够回复Host的inquiry包。

const msd_scsi_inq_resp inrq_resp = {
  USBS_SCSI_DIRECT_ACCESS_BLOCK_DEVICE, // Direct Access Block device 0x00
  USBS_SCSI_REMOVABLE_DEVICE,          // Device is removable
  0x04,                                // Comply with SPC-2
  0x02,                                // Standard format
  0x20,
  0x00,
  0x00,
  0x00,
  {'R','i','s','e','t','e','k',' '},
  {'M','a','s','s',' ','S','t','o','r','a','g','e',' ',' ',' ',' '},
  {'},
};

USB_scsi 之旅

这个图下面还有数据端,但是在这里就只回了一个最小的数据包回去,那就总结这些字段就好了吧。

第0个字节:有这两个PERIPHERAL QUALIFIER,PERIPHERAL DEVICE TYPE东东需要关注,PERIPHERAL QUALIFIER这里三位全为0,PERIPHERAL DEVICE TYPE的值都为0,表明意思是Direct-access device。

第一个字节:只要关注RMB表明这个设备可不可以移除,原文为:A removable medium (RMB) bit of zero indicates that the medium is not removable. A RMB bit of one indicates that the medium is removable.

第二个字节:VERSION表明数据交流使用的协议版本是SPC-2,其值为0x04,

第三个字节:An AERC bit of one indicates that the processor device is capable of accepting asynchronous event reports. An AERC bit of zero indicates that the processor device does not support asynchronous event reports;

RESPONSE DATA FORMAT:A RESPONSE DATA FORMAT field value of two indicates that the data shall be in the format specified in this standard.

这里写为2就好,整个字节的值就是为0x02。

第四个字节:ADDITIONAL LENGTH表明这个值附加了多少数据,Response is 0x20 + 4 bytes,表明这个请求包有多长,这里为36,前面有四个数据下面有32个刚好36个数据。

第五,第六,第七个字节都填0,想知道为什么的话,看看手册吧,不然这么总结就成了在翻译协议了。

第八字节—第十五字节:VENDOR IDENTIFICATION,在8个字节里面,随便写一些字符,写法如上面就可以了。而PRODUCT IDENTIFICATION和PRODUCT REVISION LEVEL都是想填什么就填什么,只要没有操作范围。一定要注意范围哦。

static inline cyg_int32 usb_msd_scsi_handle_inquiry( usb_msd * msd )
{
      cyg_int32 tx_bytes = sizeof(msd_scsi_inq_resp);
      DBG("USB Slave MSD: SCSI inquiry\n"); 

      // Copy over to message buffer
      memcpy( msd->buffer, (cyg_uint8 *) &inrq_resp, tx_bytes );
      usb_msd_tx_send(msd , tx_bytes);
      return tx_bytes;
}

通过端点2就把数据发给host,第一步就完成了。

  • 2:READ FORMAT CAPACITIES

下表就是READ FORMATCAPACITIES Command的命令表,这个命令也只需要关注两个字节,Operation Code (23h) and Logical Unit Number。Logical Unit Number(LUN),前面说过这个LUN,这里就为一个LUN。下面准备回复的数据。

USB_scsi 之旅

这个usb_msd_scsi_read_format_capacities函数就是用来准备回复host数据的函数并且发送给host。还是按照协议回复,上图。

USB_scsi 之旅

第一个需要准备的是Capacity List Header,然后是Current/Maximum Capacity Header等等。

USB_scsi 之旅

又是一个图,这图图还真多,看看图,开心了吧,只用关注一个字节就好了,手册上说这个字段写0x08就好。

USB_scsi 之旅

接下来是Current/Maximum Capacity Descriptor,0~3字节用来描述这个存储设备有多少块block,第四个字节Descriptor Code的值为0x02表示Formatted Media,5~7字节用来描述每块有多大,这次写的存储器每块的大小为512。下面的字段咱们偷偷懒就不回了,其实这样是很正确的。

static inline cyg_int32 usb_msd_scsi_read_format_capacities( usb_msd * msd )
{
      cyg_uint8  BulkBuf[] ={
      [ ] = 0x00,
      [ ] = 0x00,
      [ ] = 0x00,
      [ ] = 0x08,
      [ ] = (MSC_BlockCount >> ) & 0xFF,
      [ ] = (MSC_BlockCount >> ) & 0xFF,
      [ ] = (MSC_BlockCount >> ) & 0xFF,
      [ ] = (MSC_BlockCount >> ) & 0xFF,
      [ ] = 0x02,
      [ ] = (MSC_BlockSize >> ) & 0xFF,
      [] = (MSC_BlockSize >> ) & 0xFF,
      [] = (MSC_BlockSize >> ) & 0xFF,
      }; 

      memset(msd->buffer ,  , );
      memcpy(msd->buffer , BulkBuf, );
      usb_msd_tx_send(msd , );      

      ;
}
  • 3:READ CAPACITY

这里回复存储容量,但是这里和上面的format还有点不明白,以后再处理,现在先这么总结先。现在在上图,图解直观。

USB_scsi 之旅

这个命令关注五个东东,Operation Code (25h),Logical Unit Number,RelAdr,Logical Block Address,PMI。其实待会有惊喜

Operation Code (25h),Logical Unit Number这两个就不说了,已经说过了,Logical Block Address(逻辑块地址)应该怎么设置呢,手册上有这么一句话,看了应该很是高兴。RelAdr: This bit should be set to zero. Logical Block Address should be set to zero.

PMI: This bit should be set to zero.贴了三句E文,看了后,是不是很开心,不用在烦恼剩下的描述了,全是0。既然协议都说是0那么就不管了。命令是不管了,但是偶们还是要给host回复点东西吧,送礼了哦,不然host会嚷嚷你去睡觉。

USB_scsi 之旅

这个回复和format差不多,还比它简单,只是注意现在在Block Count那里减1,我想应该是0的问题,如果0是第一个数据,那么你自然要减一个下来。

static inline cyg_int32 usb_msd_scsi_handle_capacity( usb_msd * msd )
 {

       DBG("usb_msd_scsi_handle_capacity \n");
      cyg_uint8  length = ;
      cyg_uint8  BulkBuf[ ] ={
        [ ] = ((MSC_BlockCount - ) >> ) & 0xFF,
        [ ] = ((MSC_BlockCount - ) >> ) & 0xFF,
        [ ] = ((MSC_BlockCount - ) >> ) & 0xFF,
        [ ] = ((MSC_BlockCount - ) >> ) & 0xFF,
        [ ] = (MSC_BlockSize >> ) & 0xFF,
        [ ] = (MSC_BlockSize >> ) & 0xFF,
        [ ] = (MSC_BlockSize >> ) & 0xFF,
        [ ] = (MSC_BlockSize >> ) & 0xFF,
      };   
      memset(msd->buffer ,  , );
      memcpy(msd->buffer , BulkBuf, length);
      usb_msd_tx_send(msd , length);

      ;
 }
  • 4:READ(10)

现在直奔主题了,上图

USB_scsi 之旅

这个命令就是用来读取存储器里面存储的数据,包括MBR等数据。现在挑不熟悉的总结了,熟悉的就不说了。

咋一看都是比较熟悉的呢,那几个不熟悉的,在协议上说全是0。这里还有一个关注一下,Logical Block Address前面说的是些0,但是这里不一样了,host必须给出存储器的块,slave才知道提交那块的数据。usb_msd_scsi_rwsetup

就是用来解析CBW包里面包含的块和要求回复数据的长度。Transfer Length (MSB)和Transfer Length (LSB)存储了数据的长度。

static inline void usb_msd_scsi_MemoryRead (usb_msd * msd) {

      cyg_uint8 len;
      DBG("usb_msd_scsi_MemoryRead \n");  

      if (Length > MSC_MAX_PACKET) {
             len = MSC_MAX_PACKET;
      } else {
             len= Length;
      }

      if ((Offset + len) > MSC_MemorySize) {
             len = MSC_MemorySize - Offset;
             BulkStage = MSC_BS_DATA_IN_LAST_STALL;
      }

      usb_scsi_send(&DiskImage[Offset] , len);
      Offset += len;
      Length -= len;
      msd->csw.data_residue -= len;

      ) {
             BulkStage = MSC_BS_DATA_IN_LAST;
      }
      if (BulkStage != MSC_BS_DATA_IN) {
            msd->csw.status = CSW_CMD_PASSED;
      }
}

static inline cyg_int32 usb_msd_scsi_handle_read(usb_msd * msd)
{
  DBG("usb_msd_scsi_handle_read \n");
      if(usb_msd_scsi_rwsetup(msd))
      {
             ) {

                    BulkStage = MSC_BS_DATA_IN;
                    usb_msd_scsi_MemoryRead(msd);               

                    } else {                                         

                           stm32_usb_set_rxep_status (msd->tx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
                                  |CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);

                           msd->csw.status = CSW_PHASE_ERROR;
                           usb_msd_csw_send();
                    }
      }
      ;
}
  • 5:SENSE6 

这里需要知道SENSE6命令的操作码是0x1A,其他的都不怎么重要了,至少在这个实验里面。那应该怎么回复数据呢,如下图所示

USB_scsi 之旅

MODE DATA LENGTH表示接下来有几个字段,这里自然是三个,接下来的三个字段都得换一个手册了,从SPC-2 JUMP到SSC-3。

在SSC-3里面有这么一个图图,就是用来解析第三个字段的。表示是不是只读。当WP为0表示可读可写,反之只读。其他的就写0了。要是在其他的情况,比如不是usb mass storage那么还得研究协议哦。

USB_scsi 之旅

static inline cyg_int32 usb_msd_scsi_handle_sense(usb_msd * msd)
{
      DBG("usb_msd_scsi_handle_sense \n");

      cyg_uint8  length = ;

      cyg_uint8  BulkBuf[ ] ={
      [ ] = 0x03,
      [ ] = 0x00,
      [ ] = 0x80,
      [ ] = 0x00,
      };   

      memset(msd->buffer ,  , );
      memcpy(msd->buffer , BulkBuf, length);
      usb_msd_tx_send(msd , length);

      ;
}
  • 6:WRITE(10)

这个命令和read差不了多少,只是一个向host提交数据,一个是host向salve写数据。数据包不同就在操作码。

static inline void usb_msd_scsi_MemoryWrite (usb_msd * msd) {

      //cyg_int32 n;

      DBG("usb_msd_scsi_MemoryWrite \n");

      if ((Offset + bulk_length) > MSC_MemorySize) {
             bulk_length = MSC_MemorySize - Offset;
             BulkStage = MSC_BS_CSW;
             stm32_usb_set_rxep_status (msd->rx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
                    |CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
      }

      // for (n = 0; n < bulk_length; n++) {
      //Memory[Offset + n] = msd->buffer[n];
      // }

      Offset += bulk_length;
      Length -= bulk_length;

      msd->csw.data_residue -= bulk_length;

      ) || (BulkStage == MSC_BS_CSW)) {
             msd->csw.status= CSW_CMD_PASSED;
             usb_msd_csw_send();
      }
  }

static inline void usb_msd_scsi_MemoryVerify (usb_msd * msd) {

#if 0
      cyg_int32 reg_val , n;

      DBG("usb_msd_scsi_MemoryVerify \n");

      if ((Offset + bulk_length) > MSC_MemorySize) {

             bulk_length = MSC_MemorySize - Offset;
             BulkStage = MSC_BS_CSW;
             stm32_usb_set_rxep_status (msd->rx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL|CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
      }

      ; n < bulk_length; n++) {
             if (Memory[Offset + n] != msd->buffer[n]) {
                    MemOK = __FALSE;
                    break;
             }
      }

      Offset += bulk_length;
      Length -= bulk_length;

      msd->csw.data_residue -= bulk_length;

      ) || (BulkStage == MSC_BS_CSW)) {
             msd->csw.status= (MemOK) ? CSW_CMD_PASSED : CSW_CMD_FAILED;
             usb_msd_csw_send();
      }
#endif
}

BOOL usb_msd_scsi_rwsetup (usb_msd * msd) {

      cyg_uint32 n;
  DBG("usb_msd_scsi_rwsetup \n");       

      n = (msd->cbw->cb.data[]<< ) |
                    (msd->cbw->cb.data[] << ) |
                    (msd->cbw->cb.data[] << ) |
                    (msd->cbw->cb.data[] << );

      Offset = n * MSC_BlockSize;

      ]) {
             case USBS_SCSI_READ_10:
             case USBS_SCSI_WRITE_10:
             case USBS_SCSI_VERIFY_10:
                    n = (msd->cbw->cb.data[] << ) |
                                  (msd->cbw->cb.data[] << );
             break;

             case USBS_SCSI_READ_12:
             case USBS_SCSI_WRITE_12:
                    n = (msd->cbw->cb.data[] << ) |
                                  (msd->cbw->cb.data[] << ) |
                                  (msd->cbw->cb.data[] << ) |
                                 (msd->cbw->cb.data[] << );
             break;
      }

      Length = n * MSC_BlockSize; 

      ) {
             msd->csw.status = CSW_CMD_FAILED;
             usb_msd_csw_send();
             return (__FALSE);
      }

      if (msd->cbw->data_transfert_len != Length) {
             ) {
                    stm32_usb_set_rxep_status (msd->tx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
                           |CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
             } else {
                    stm32_usb_set_rxep_status (msd->rx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
                           |CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
             }

      msd->csw.status = CSW_CMD_FAILED;
      usb_msd_csw_send();

      return (__FALSE);
   }
 return (__TRUE);
}
  • 7:TEST UNIT READY

这个命令没什么好说的,只是看看磁盘准备好没,咱们也不用向host进贡了,高兴一下吧。

static inline cyg_int32 usb_msd_scsi_handle_test_unit_ready(usb_msd * msd)
{
      DBG("usb_msd_scsi_handle_test_unit_ready \n");

      ) { 

             ) {
                    stm32_usb_set_rxep_status (msd->tx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
                           |CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);

             } else {
                    stm32_usb_set_rxep_status (msd->rx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
                           |CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
             }
      }

      msd->csw.status = CSW_CMD_PASSED;
      usb_msd_csw_send();

   ;
} 

static inline cyg_int32 usb_msd_scsi_handle_write(usb_msd * msd)
{

      DBG("usb_msd_scsi_handle_write \n");

      if (usb_msd_scsi_rwsetup(msd)) {
             ) {

                    BulkStage = MSC_BS_DATA_OUT;
                    usb_scsi_setup_status(msd->rx_ep_num);   

             } else {
                    stm32_usb_set_rxep_status (msd->tx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
                           |CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);

                    msd->csw.status = CSW_PHASE_ERROR;
                    usb_msd_csw_send();
             }
      }

  ;
}

static inline cyg_int32 usb_msd_scsi_handle_verify(usb_msd * msd )
{
     DBG("usb_msd_scsi_handle_verify \n");
     //usb_msd_scsi_MemoryVerify(msd);
     ;
} 

cyg_int32 usb_msd_scsi_handle_cmd( usb_msd * msd , cyg_uint8 *ctrlep_msg_buffer )
{
  cyg_uint32 ret;

  bulk_length = strlen((;
  msd->cbw =(usb_msd_cbw *)ctrlep_msg_buffer;
  cyg_uint8 cmd = msd->cbw->cb.data[];
  usb_scsi_command = cmd;
  msd->csw.tag             = msd->cbw->tag; //csw tag
  msd->csw.data_residue = msd->cbw->data_transfert_len; //csw length

  DBG("usb_msd_scsi_handle_cmd cmd = 0x%x msd->csw.data_residue = %d\n" , cmd , msd->csw.data_residue); 

  switch( cmd )
  {
    case USBS_SCSI_INQUIRY:
       ret = usb_msd_scsi_handle_inquiry(msd);
       break;

    case USBS_SCSI_READ_CAPACITY:
       ret = usb_msd_scsi_handle_capacity( msd );
       break;

    case USBS_SCSI_READ_10:
       ret = usb_msd_scsi_handle_read(msd );
       break;

    case USBS_SCSI_WRITE_10:
       ret = usb_msd_scsi_handle_write( msd );

       break;

    case USBS_SCSI_REQUEST_SENSE:
       //ret = usb_msd_scsi_handle_req_sense( msd);
       break;

    case USBS_SCSI_SENSE_6:
       ret = usb_msd_scsi_handle_sense( msd );
       break;

    case USBS_SCSI_TEST_UNIT_READY:
       ret = usb_msd_scsi_handle_test_unit_ready( msd );
       break;

    case USBS_SCSI_VERIFY_10:
       ret = usb_msd_scsi_handle_verify( msd );
       break;

    case USBS_SCSI_READ_FORMAT_CAPACITIES:
       ret = usb_msd_scsi_read_format_capacities( msd );
       break;

    case USBS_SCSI_PREVENT_ALLOW_REMOVAL:
       //ret = usb_msd_scsi_handle_removal( msd );
       break;

    default:
       // Use for all commands not implemented, not
       // supported
             DBG("USB Slave MSD: SCSI illegal request %x \n", cmd );
             msd->csw.status = USBS_MSD_CSW_STATUS_FAILED;
            ret = ;
             break;
   }

  return ret;

}

void usb_msd_scsi_bulk_out(cyg_uint8 *ctrlep_msg_buffer , cyg_uint32 length)
{

      cyg_uint32 reg_val;

      DBG("usb_msd_scsi_bulk_out length = %d \n" , length);

      memset(msd.buffer ,  , );
      bulk_length = length;
      memcpy(msd.buffer , ctrlep_msg_buffer, bulk_length); 

      switch (BulkStage) {
             case MSC_BS_CBW:
                    usb_msd_scsi_handle_cmd(&msd , ctrlep_msg_buffer);
                    break;

             case MSC_BS_DATA_OUT:
                    switch (usb_scsi_command) {
                           case USBS_SCSI_WRITE_10:
                           case USBS_SCSI_WRITE_12:
                                  usb_msd_scsi_MemoryWrite(&msd );
                                  usb_scsi_setup_status(msd.rx_ep_num);
                                  break;

                           case USBS_SCSI_VERIFY_10:
                                  //usb_msd_scsi_MemoryVerify(&msd);
                                  break;
                           }
                    break;

             case MSC_BS_CSW:
                    break;

             default:
                    HAL_READ_UINT32 (CYGHWR_HAL_STM32_USB + CYGHWR_HAL_STM32_USB_EPXR (msd.rx_ep_num), reg_val);
                    stm32_usb_set_rxep_status (msd.rx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
                                         |CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
                    msd.csw.status = CSW_PHASE_ERROR;
                    usb_msd_csw_send();
                    break;
      }
 }

void usb_msd_scsi_bulk_in()
{

  DBG("usb_msd_scsi_bulk_in \n");

      switch (BulkStage) {
             case MSC_BS_DATA_IN:
                    ]) {
                    case USBS_SCSI_READ_10:
                           usb_msd_scsi_MemoryRead(&msd );
                           break;
                    }
                    break;

             case MSC_BS_DATA_IN_LAST:
                    usb_msd_csw_send();
                    break;

             case MSC_BS_DATA_IN_LAST_STALL:
                    usb_msd_csw_send();
                    break;

             case MSC_BS_CSW:
                    BulkStage = MSC_BS_CBW;
                    break;
      }

}

终于写完了肩膀都累了。第一次做usb协议,写的不好的见谅,有错希望给予指正。如果有STM32开发USB的可以看看 http://fpgamcu.gotoip55.com/forum.php?mod=viewthread&tid=37&pid=96&page=1&extra=#pid96pid96

这个论坛可以找本人!