Эх сурвалжийг харах

Unified MSC for RP2040 and GD32G205 platforms

zigzagjoe 1 жил өмнө
parent
commit
0e4bfc8645

+ 241 - 0
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform_msc.cpp

@@ -0,0 +1,241 @@
+/**
+ * Copyright (c) 2023-2024 zigzagjoe
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#ifdef PLATFORM_MASS_STORAGE
+
+#include <SdFat.h>
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_msc.h"
+#include "ZuluSCSI_settings.h"
+#include "usb_serial.h"
+
+/* gd32 USB code is all C linked */
+extern "C" {
+  #include <drv_usb_hw.h>
+  #include <usbd_core.h>
+  #include <drv_usbd_int.h>
+
+  #include "usb_conf.h"
+  #include "usbd_msc_core.h"
+  #include "usbd_msc_mem.h"
+  #include "usbd_msc_bbb.h"
+}
+
+
+/* local function prototypes ('static') */
+static int8_t STORAGE_Init(uint8_t Lun);
+static int8_t STORAGE_IsReady(uint8_t Lun);
+static int8_t STORAGE_IsWriteProtected(uint8_t Lun);
+static int8_t STORAGE_GetMaxLun(void);
+static int8_t STORAGE_Read(uint8_t Lun,
+                           uint8_t *buf,
+                           uint32_t BlkAddr,
+                           uint16_t BlkLen);
+static int8_t STORAGE_Write(uint8_t Lun,
+                            uint8_t *buf,
+                            uint32_t BlkAddr,
+                            uint16_t BlkLen);
+
+usbd_mem_cb USBD_SD_fops = {
+    .mem_init      = STORAGE_Init,
+    .mem_ready     = STORAGE_IsReady,
+    .mem_protected = STORAGE_IsWriteProtected,
+    .mem_read      = STORAGE_Read,
+    .mem_write     = STORAGE_Write,
+    .mem_maxlun    = STORAGE_GetMaxLun,
+
+    .mem_inquiry_data = {(uint8_t *)STORAGE_InquiryData},
+    .mem_block_size   = {SD_SECTOR_SIZE},
+    .mem_block_len    = {0}
+};
+
+usbd_mem_cb *usbd_mem_fops = &USBD_SD_fops;
+
+// shared with usb serial
+extern usb_core_driver cdc_acm;
+
+// external global SD variable
+extern SdFs SD;
+
+// private globals
+static bool unitReady = false;
+
+/* returns true if card reader mode should be entered. sd card is available. */
+bool platform_senseMSC() {
+
+  // kill usb serial.
+  usbd_disconnect (&cdc_acm);
+  // set the MSC storage size
+  usbd_mem_fops->mem_block_len[0] = SD.card()->sectorCount();
+  unitReady = true;
+  
+  // init the MSC class, uses ISR and other global routines from usb_serial.cpp
+  usbd_init(&cdc_acm, USB_CORE_ENUM_FS, &msc_desc, &msc_class);
+
+  logmsg("Waiting for USB enumeration to expose SD card as a mass storage device");
+
+  // wait for up to a second to be begin to be enumerated
+  uint32_t start = millis();
+  while ((uint32_t)(millis() - start) < CR_ENUM_TIMEOUT)
+    if (cdc_acm.dev.cur_status >= USBD_ADDRESSED)
+      return true;
+
+  //if not, disconnect MSC class...
+  usbd_disconnect (&cdc_acm);
+
+  // and bring serial back for later.
+  usb_serial_init();
+
+  return false;
+}
+
+/* perform MSC-specific init tasks */
+void platform_enterMSC() {
+  dbgmsg("USB MSC buffer size: ", (uint32_t) MSC_MEDIA_PACKET_SIZE);
+
+  // give the host a moment to finish enumerate and "load" media
+  uint32_t start = millis();
+  while ((USBD_CONFIGURED != cdc_acm.dev.cur_status) && ((uint32_t)(millis() - start) < CR_ENUM_TIMEOUT) ) 
+    delay(100);
+}
+
+/* return true while remaining in msc mode, and perform periodic tasks */
+bool platform_runMSC() {
+  usbd_msc_handler *msc = (usbd_msc_handler *)cdc_acm.dev.class_data[USBD_MSC_INTERFACE];
+
+  // stupid windows doesn't send start_stop_unit events if it is ejected via safely remove devices. 
+  // it just stops talking to the device so we don't know we've been ejected....
+  // other OSes always send the start_stop_unit, windows does too when ejected from explorer.
+  // so we watch for the OS suspending device and assume we're done in USB mode if so.
+  // this will also trigger if the host were to suspend usb device due to going to sleep
+  // however, I hope no sane OS would sleep mid transfer or with a dirty filesystem. 
+  // Note: Mac OS X apparently not sane.
+  uint8_t is_suspended = cdc_acm.dev.cur_status == (uint8_t)USBD_SUSPENDED;
+
+  return (! msc->scsi_disk_pop) && !is_suspended;
+}
+
+void platform_exitMSC() {
+  unitReady = false;
+
+  // disconnect msc....
+  usbd_disconnect (&cdc_acm);
+
+  // catch our breath....
+  delay(200);
+  
+  // ... and bring usb serial up
+  usb_serial_init();
+}
+
+/*!
+    \brief      initialize the storage medium
+    \param[in]  Lun: logical unit number
+    \param[out] none
+    \retval     status
+*/
+static int8_t STORAGE_Init(uint8_t Lun)
+{
+    return 0;
+}
+
+/*!
+    \brief      check whether the medium is ready
+    \param[in]  Lun: logical unit number
+    \param[out] none
+    \retval     status
+*/
+static int8_t STORAGE_IsReady(uint8_t Lun)
+{
+    return ! unitReady; // 0 = success / unit is ready
+}
+
+/*!
+    \brief      check whether the medium is write-protected
+    \param[in]  Lun: logical unit number
+    \param[out] none
+    \retval     status
+*/
+static int8_t STORAGE_IsWriteProtected(uint8_t Lun)
+{
+    return ! unitReady; // 0 = read/write
+}
+
+/*!
+    \brief      read data from the medium
+    \param[in]  Lun: logical unit number
+    \param[in]  buf: pointer to the buffer to save data
+    \param[in]  BlkAddr: address of 1st block to be read
+    \param[in]  BlkLen: number of blocks to be read
+    \param[out] none
+    \retval     status
+*/
+static int8_t STORAGE_Read(uint8_t Lun,
+                           uint8_t *buf,
+                           uint32_t BlkAddr,
+                           uint16_t BlkLen)
+{
+    // divide by sector size to convert address to LBA
+    bool rc = SD.card()->readSectors(BlkAddr/SD_SECTOR_SIZE, buf, BlkLen);
+
+    // only blink fast on reads; writes will override this
+    if (MSC_LEDMode == LED_SOLIDON)
+      MSC_LEDMode = LED_BLINK_FAST;
+      
+    return !rc;
+}
+
+/*!
+    \brief      write data to the medium
+    \param[in]  Lun: logical unit number
+    \param[in]  buf: pointer to the buffer to write
+    \param[in]  BlkAddr: address of 1st block to be written
+    \param[in]  BlkLen: number of blocks to be write
+    \param[out] none
+    \retval     status
+*/
+static int8_t STORAGE_Write(uint8_t Lun,
+                            uint8_t *buf,
+                            uint32_t BlkAddr,
+                            uint16_t BlkLen)
+{
+    // divide by sector size to convert address to LBA
+    bool rc = SD.card()->writeSectors(BlkAddr/SD_SECTOR_SIZE, buf, BlkLen);
+
+    // always slow blink
+    MSC_LEDMode = LED_BLINK_SLOW;
+
+    return !rc;
+}
+
+/*!
+    \brief      get number of supported logical unit
+    \param[in]  none
+    \param[out] none
+    \retval     number of logical unit
+*/
+static int8_t STORAGE_GetMaxLun(void)
+{
+    return 0; // number of LUNs supported - 1
+}
+
+#endif // PLATFORM_MASS_STORAGE

+ 59 - 0
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform_msc.h

@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2023-2024 zigzagjoe
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#ifdef PLATFORM_MASS_STORAGE
+#pragma once
+
+#include "usbd_msc_mem.h"
+
+// private constants/enums
+#define SD_SECTOR_SIZE 512
+
+/* USB Mass storage Standard Inquiry Data */
+const uint8_t STORAGE_InquiryData[] = {
+    /* LUN 0 */
+    0x00,
+    0x80,
+    0x00,
+    0x01,
+    (USBD_STD_INQUIRY_LENGTH - 5U),
+    0x00,
+    0x00,
+    0x00,
+    'Z', 'u', 'l', 'u', 'S', 'C', 'S', 'I', /* Manufacturer : 8 bytes */
+    'G', 'D', '3', '2', 0, 0, 0, 0, /* Product      : 16 Bytes */
+    0, 0, 0, 0, 0, 0, 0, 0,
+    '1', '.', '0', '0',                     /* Version      : 4 Bytes */
+};
+
+/* return true if USB presence detected / eligble to enter CR mode */
+bool platform_senseMSC();
+
+/* perform MSC-specific init tasks */
+void platform_enterMSC();
+
+/* return true if we should remain in card reader mode. called in a loop. */
+bool platform_runMSC();
+
+/* perform any cleanup tasks for the MSC-specific functionality */
+void platform_exitMSC();
+
+#endif

+ 3 - 2
lib/ZuluSCSI_platform_GD32F205/usb_serial.cpp

@@ -38,6 +38,7 @@ void usb_serial_init(void)
     // set USB full speed prescaler and turn on USB clock
     rcu_usbfs_trng_clock_config(RCU_CKUSB_CKPLL_DIV2_5);
     rcu_periph_clock_enable(RCU_USBFS);
+    
     usbd_init(&cdc_acm, USB_CORE_ENUM_FS, &gd32_cdc_desc, &gd32_cdc_class);
 
     // USB full speed Interrupt config
@@ -48,7 +49,8 @@ void usb_serial_init(void)
 
 bool usb_serial_ready(void)
 {
-    if (USBD_CONFIGURED == cdc_acm.dev.cur_status) 
+    // check that (our) serial is the currently active class
+    if ((USBD_CONFIGURED == cdc_acm.dev.cur_status) && (cdc_acm.dev.desc == &gd32_cdc_desc)) 
     {
         if (1U == gd32_cdc_acm_check_ready(&cdc_acm)) 
         {
@@ -73,7 +75,6 @@ void usb_udelay (const uint32_t usec)
     delay_us(usec);
 }
 
-
 void usb_mdelay (const uint32_t msec)
 {
     delay(msec);

+ 26 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_conf.h

@@ -59,4 +59,30 @@ OF SUCH DAMAGE.
 #define USB_CDC_DATA_PACKET_SIZE            64U   /* Endpoint IN & OUT Packet size */
 #define CDC_IN_FRAME_INTERVAL               5U   /* Number of frames between IN transfers */
 
+#ifdef PLATFORM_MASS_STORAGE
+    /* MSC  */
+
+    #define USBD_MSC_INTERFACE              0U
+
+    /* class layer parameter */
+    #define MSC_IN_EP                       EP1_IN
+    #define MSC_OUT_EP                      EP1_OUT
+
+    #define MSC_DATA_PACKET_SIZE            64U
+    /* recommend 4096 as a minimum - SD real sector size */
+    #define MSC_MEDIA_PACKET_SIZE           4096U
+
+    #define MEM_LUN_NUM                     1U
+
+#else
+    /* minimums to compile but not allocate storage */
+    #define USBD_MSC_INTERFACE              0xF
+    #define MEM_LUN_NUM                     0U
+    #define MSC_DATA_PACKET_SIZE            0U
+    #define MSC_MEDIA_PACKET_SIZE           0U
+    #define MSC_IN_EP                       0U
+    #define MSC_OUT_EP                      0U
+
+#endif
+
 #endif /* __USBD_CONF_H */

+ 288 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_bbb.c

@@ -0,0 +1,288 @@
+/*!
+    \file    usbd_msc_bbb.c
+    \brief   USB BBB(Bulk/Bulk/Bulk) protocol core functions
+    \note    BBB means Bulk-only transport protocol for USB MSC
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#include "usbd_enum.h"
+#include "usbd_msc_bbb.h"
+
+/* local function prototypes ('static') */
+static void msc_bbb_cbw_decode(usb_core_driver *udev);
+static void msc_bbb_data_send(usb_core_driver *udev, uint8_t *pbuf, uint32_t Len);
+static void msc_bbb_abort(usb_core_driver *udev);
+
+/*!
+    \brief      initialize the bbb process
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_init(usb_core_driver *udev)
+{
+    uint8_t lun_num;
+
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_state = BBB_IDLE;
+    msc->bbb_status = BBB_STATUS_NORMAL;
+
+    /* initialize the storage logic unit */
+    for(lun_num = 0U; lun_num < MEM_LUN_NUM; lun_num++) {
+        usbd_mem_fops->mem_init(lun_num);
+    }
+
+    /* flush the Rx FIFO */
+    usbd_fifo_flush(udev, MSC_OUT_EP);
+
+    /* flush the Tx FIFO */
+    usbd_fifo_flush(udev, MSC_IN_EP);
+
+    /* prepare endpoint to receive the first BBB CBW */
+    usbd_ep_recev(udev, MSC_OUT_EP, (uint8_t *)&msc->bbb_cbw, BBB_CBW_LENGTH);
+}
+
+/*!
+    \brief      reset the BBB machine
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_reset(usb_core_driver *udev)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_state = BBB_IDLE;
+    msc->bbb_status = BBB_STATUS_RECOVERY;
+
+    /* prepare endpoint to receive the first BBB command */
+    usbd_ep_recev(udev, MSC_OUT_EP, (uint8_t *)&msc->bbb_cbw, BBB_CBW_LENGTH);
+}
+
+/*!
+    \brief      deinitialize the BBB machine
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_deinit(usb_core_driver *udev)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_state = BBB_IDLE;
+}
+
+/*!
+    \brief      handle BBB data IN stage
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: endpoint number
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_data_in(usb_core_driver *udev, uint8_t ep_num)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    switch(msc->bbb_state) {
+    case BBB_DATA_IN:
+        if(scsi_process_cmd(udev, msc->bbb_cbw.bCBWLUN, &msc->bbb_cbw.CBWCB[0]) < 0) {
+            msc_bbb_csw_send(udev, CSW_CMD_FAILED);
+        }
+        break;
+
+    case BBB_SEND_DATA:
+    case BBB_LAST_DATA_IN:
+        msc_bbb_csw_send(udev, CSW_CMD_PASSED);
+        break;
+
+    default:
+        break;
+    }
+}
+
+/*!
+    \brief      handle BBB data OUT stage
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: endpoint number
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_data_out(usb_core_driver *udev, uint8_t ep_num)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    switch(msc->bbb_state) {
+    case BBB_IDLE:
+        msc_bbb_cbw_decode(udev);
+        break;
+
+    case BBB_DATA_OUT:
+        if(scsi_process_cmd(udev, msc->bbb_cbw.bCBWLUN, &msc->bbb_cbw.CBWCB[0]) < 0) {
+            msc_bbb_csw_send(udev, CSW_CMD_FAILED);
+        }
+        break;
+
+    default:
+        break;
+    }
+}
+
+/*!
+    \brief      send the CSW(command status wrapper)
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  csw_status: CSW status
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_csw_send(usb_core_driver *udev, uint8_t csw_status)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_csw.dCSWSignature = BBB_CSW_SIGNATURE;
+    msc->bbb_csw.bCSWStatus = csw_status;
+    msc->bbb_state = BBB_IDLE;
+
+    usbd_ep_send(udev, MSC_IN_EP, (uint8_t *)&msc->bbb_csw, BBB_CSW_LENGTH);
+
+    /* prepare endpoint to receive next command */
+    usbd_ep_recev(udev, MSC_OUT_EP, (uint8_t *)&msc->bbb_cbw, BBB_CBW_LENGTH);
+}
+
+/*!
+    \brief      complete the clear feature request
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: endpoint number
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_clrfeature(usb_core_driver *udev, uint8_t ep_num)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if(msc->bbb_status == BBB_STATUS_ERROR) { /* bad CBW signature */
+        usbd_ep_stall(udev, MSC_IN_EP);
+
+        msc->bbb_status = BBB_STATUS_NORMAL;
+    } else if(((ep_num & 0x80U) == 0x80U) && (msc->bbb_status != BBB_STATUS_RECOVERY)) {
+        msc_bbb_csw_send(udev, CSW_CMD_FAILED);
+    } else {
+
+    }
+}
+
+/*!
+    \brief      decode the CBW command and set the BBB state machine accordingly
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     none
+*/
+static void msc_bbb_cbw_decode(usb_core_driver *udev)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_csw.dCSWTag = msc->bbb_cbw.dCBWTag;
+    msc->bbb_csw.dCSWDataResidue = msc->bbb_cbw.dCBWDataTransferLength;
+
+    if((BBB_CBW_LENGTH != usbd_rxcount_get(udev, MSC_OUT_EP)) ||
+            (BBB_CBW_SIGNATURE != msc->bbb_cbw.dCBWSignature) ||
+            (msc->bbb_cbw.bCBWLUN > 1U) ||
+            (msc->bbb_cbw.bCBWCBLength < 1U) ||
+            (msc->bbb_cbw.bCBWCBLength > 16U)) {
+        /* illegal command handler */
+        scsi_sense_code(udev, msc->bbb_cbw.bCBWLUN, ILLEGAL_REQUEST, INVALID_CDB);
+
+        msc->bbb_status = BBB_STATUS_ERROR;
+
+        msc_bbb_abort(udev);
+    } else {
+        if(scsi_process_cmd(udev, msc->bbb_cbw.bCBWLUN, &msc->bbb_cbw.CBWCB[0]) < 0) {
+            msc_bbb_abort(udev);
+        } else if((BBB_DATA_IN != msc->bbb_state) &&
+                  (BBB_DATA_OUT != msc->bbb_state) &&
+                  (BBB_LAST_DATA_IN != msc->bbb_state)) { /* burst xfer handled internally */
+            if(msc->bbb_datalen > 0U) {
+                msc_bbb_data_send(udev, msc->bbb_data, msc->bbb_datalen);
+            } else if(0U == msc->bbb_datalen) {
+                msc_bbb_csw_send(udev, CSW_CMD_PASSED);
+            } else {
+
+            }
+        } else {
+
+        }
+    }
+}
+
+/*!
+    \brief      send the requested data
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  buf: pointer to data buffer
+    \param[in]  len: data length
+    \param[out] none
+    \retval     none
+*/
+static void msc_bbb_data_send(usb_core_driver *udev, uint8_t *buf, uint32_t len)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    len = USB_MIN(msc->bbb_cbw.dCBWDataTransferLength, len);
+
+    msc->bbb_csw.dCSWDataResidue -= len;
+    msc->bbb_csw.bCSWStatus = CSW_CMD_PASSED;
+    msc->bbb_state = BBB_SEND_DATA;
+
+    usbd_ep_send(udev, MSC_IN_EP, buf, len);
+}
+
+/*!
+    \brief      abort the current transfer
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     none
+*/
+static void msc_bbb_abort(usb_core_driver *udev)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if((0U == msc->bbb_cbw.bmCBWFlags) &&
+            (0U != msc->bbb_cbw.dCBWDataTransferLength) &&
+            (BBB_STATUS_NORMAL == msc->bbb_status)) {
+        usbd_ep_stall(udev, MSC_OUT_EP);
+    }
+
+    usbd_ep_stall(udev, MSC_IN_EP);
+
+    if(msc->bbb_status == BBB_STATUS_ERROR) {
+        usbd_ep_recev(udev, MSC_OUT_EP, (uint8_t *)&msc->bbb_cbw, BBB_CBW_LENGTH);
+    }
+}

+ 101 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_bbb.h

@@ -0,0 +1,101 @@
+/*!
+    \file    usbd_msc_bbb.h
+    \brief   the header file of the usbd_msc_bot.c file
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USBD_MSC_BBB_H
+#define __USBD_MSC_BBB_H
+
+#include "usbd_core.h"
+#include "msc_bbb.h"
+#include "usbd_msc_mem.h"
+#include "usbd_msc_scsi.h"
+
+/* MSC BBB state */
+enum msc_bbb_state {
+    BBB_IDLE = 0U,          /*!< idle state  */
+    BBB_DATA_OUT,           /*!< data OUT state */
+    BBB_DATA_IN,            /*!< data IN state */
+    BBB_LAST_DATA_IN,       /*!< last data IN state */
+    BBB_SEND_DATA           /*!< send immediate data state */
+};
+
+/* MSC BBB status */
+enum msc_bbb_status {
+    BBB_STATUS_NORMAL = 0U, /*!< normal status */
+    BBB_STATUS_RECOVERY,    /*!< recovery status*/
+    BBB_STATUS_ERROR        /*!< error status */
+};
+
+typedef struct {
+    uint8_t bbb_data[MSC_MEDIA_PACKET_SIZE];
+
+    uint8_t max_lun;
+    uint8_t bbb_state;
+    uint8_t bbb_status;
+
+    uint32_t bbb_datalen;
+
+    msc_bbb_cbw bbb_cbw;
+    msc_bbb_csw bbb_csw;
+
+    uint8_t scsi_sense_head;
+    uint8_t scsi_sense_tail;
+
+    uint32_t scsi_blk_size[MEM_LUN_NUM];
+    uint32_t scsi_blk_nbr[MEM_LUN_NUM];
+
+    uint32_t scsi_blk_addr;
+    uint32_t scsi_blk_len;
+    uint32_t scsi_disk_pop;
+
+    msc_scsi_sense scsi_sense[SENSE_LIST_DEEPTH];
+} usbd_msc_handler;
+
+/* function declarations */
+/* initialize the bbb process */
+void msc_bbb_init(usb_core_driver *udev);
+/* reset the BBB machine */
+void msc_bbb_reset(usb_core_driver *udev);
+/* deinitialize the BBB machine */
+void msc_bbb_deinit(usb_core_driver *udev);
+/* handle BBB data IN stage */
+void msc_bbb_data_in(usb_core_driver *udev, uint8_t ep_num);
+/* handle BBB data OUT stage */
+void msc_bbb_data_out(usb_core_driver *udev, uint8_t ep_num);
+/* send the CSW(command status wrapper) */
+void msc_bbb_csw_send(usb_core_driver *udev, uint8_t csw_status);
+/* complete the clear feature request */
+void msc_bbb_clrfeature(usb_core_driver *udev, uint8_t ep_num);
+
+#endif /* __USBD_MSC_BBB_H */

+ 312 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_core.c

@@ -0,0 +1,312 @@
+/*!
+    \file    usbd_msc_core.c
+    \brief   USB MSC device class core functions
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2020-12-10, V3.0.1, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#include "usbd_enum.h"
+#include "usbd_msc_bbb.h"
+#include "usbd_msc_core.h"
+#include <string.h>
+
+#define USBD_VID                    0x28E9U
+#define USBD_PID                    0x028FU
+
+/* local function prototypes ('static') */
+static uint8_t msc_core_init(usb_dev *udev, uint8_t config_index);
+static uint8_t msc_core_deinit(usb_dev *udev, uint8_t config_index);
+static uint8_t msc_core_req(usb_dev *udev, usb_req *req);
+static uint8_t msc_core_in(usb_dev *udev, uint8_t ep_num);
+static uint8_t msc_core_out(usb_dev *udev, uint8_t ep_num);
+
+usb_class_core msc_class = {
+    .init     = msc_core_init,
+    .deinit   = msc_core_deinit,
+
+    .req_proc = msc_core_req,
+
+    .data_in  = msc_core_in,
+    .data_out = msc_core_out
+};
+
+/* note: it should use the C99 standard when compiling the below codes */
+/* USB standard device descriptor */
+__ALIGN_BEGIN const usb_desc_dev msc_dev_desc __ALIGN_END = {
+    .header = {
+        .bLength           = USB_DEV_DESC_LEN,
+        .bDescriptorType   = USB_DESCTYPE_DEV
+    },
+    .bcdUSB                = 0x0200U,
+    .bDeviceClass          = 0x00U,
+    .bDeviceSubClass       = 0x00U,
+    .bDeviceProtocol       = 0x00U,
+    .bMaxPacketSize0       = USB_FS_EP0_MAX_LEN,
+    .idVendor              = USBD_VID,
+    .idProduct             = USBD_PID,
+    .bcdDevice             = 0x0100U,
+    .iManufacturer         = STR_IDX_MFC,
+    .iProduct              = STR_IDX_PRODUCT,
+    .iSerialNumber         = STR_IDX_SERIAL,
+    .bNumberConfigurations = USBD_CFG_MAX_NUM
+};
+
+/* USB device configuration descriptor */
+__ALIGN_BEGIN const usb_desc_config_set msc_config_desc __ALIGN_END = {
+    .config =
+    {
+        .header = {
+            .bLength         = sizeof(usb_desc_config),
+            .bDescriptorType = USB_DESCTYPE_CONFIG
+        },
+        .wTotalLength        = USB_MSC_CONFIG_DESC_SIZE,
+        .bNumInterfaces      = 0x01U,
+        .bConfigurationValue = 0x01U,
+        .iConfiguration      = 0x00U,
+        .bmAttributes        = 0x80U, // default is 0xC0 - self powered which is wrong
+        .bMaxPower           = 0x32U
+    },
+
+    .msc_itf =
+    {
+        .header = {
+            .bLength         = sizeof(usb_desc_itf),
+            .bDescriptorType = USB_DESCTYPE_ITF
+        },
+        .bInterfaceNumber    = 0x00U,
+        .bAlternateSetting   = 0x00U,
+        .bNumEndpoints       = 0x02U,
+        .bInterfaceClass     = USB_CLASS_MSC,
+        .bInterfaceSubClass  = USB_MSC_SUBCLASS_SCSI,
+        .bInterfaceProtocol  = USB_MSC_PROTOCOL_BBB,
+        .iInterface          = 0x00U
+    },
+
+    .msc_epin =
+    {
+        .header = {
+            .bLength         = sizeof(usb_desc_ep),
+            .bDescriptorType = USB_DESCTYPE_EP
+        },
+        .bEndpointAddress    = MSC_IN_EP,
+        .bmAttributes        = USB_EP_ATTR_BULK,
+        .wMaxPacketSize      = MSC_EPIN_SIZE,
+        .bInterval           = 0x00U
+    },
+
+    .msc_epout =
+    {
+        .header = {
+            .bLength         = sizeof(usb_desc_ep),
+            .bDescriptorType = USB_DESCTYPE_EP
+        },
+        .bEndpointAddress    = MSC_OUT_EP,
+        .bmAttributes        = USB_EP_ATTR_BULK,
+        .wMaxPacketSize      = MSC_EPOUT_SIZE,
+        .bInterval           = 0x00U
+    }
+};
+
+/* USB language ID descriptor */
+__ALIGN_BEGIN const usb_desc_LANGID usbd_language_id_desc __ALIGN_END = {
+    .header =
+    {
+        .bLength            = sizeof(usb_desc_LANGID),
+        .bDescriptorType    = USB_DESCTYPE_STR
+    },
+    .wLANGID                 = ENG_LANGID
+};
+
+/* USB manufacture string */
+static __ALIGN_BEGIN const usb_desc_str manufacturer_string __ALIGN_END = {
+    .header =
+    {
+        .bLength         = USB_STRING_LEN(10U),
+        .bDescriptorType = USB_DESCTYPE_STR,
+    },
+    .unicode_string = {'G', 'i', 'g', 'a', 'D', 'e', 'v', 'i', 'c', 'e'}
+};
+
+/* USB product string */
+static __ALIGN_BEGIN const usb_desc_str product_string __ALIGN_END = {
+    .header =
+    {
+        .bLength         = USB_STRING_LEN(12U),
+        .bDescriptorType = USB_DESCTYPE_STR,
+    },
+    .unicode_string = {'G', 'D', '3', '2', '-', 'U', 'S', 'B', '_', 'M', 'S', 'C'}
+};
+
+/* USBD serial string */
+static __ALIGN_BEGIN usb_desc_str serial_string __ALIGN_END = {
+    .header =
+    {
+        .bLength         = USB_STRING_LEN(12U),
+        .bDescriptorType = USB_DESCTYPE_STR,
+    }
+};
+
+/* USB string descriptor */
+void *const usbd_msc_strings[] = {
+    [STR_IDX_LANGID]  = (uint8_t *) &usbd_language_id_desc,
+    [STR_IDX_MFC]     = (uint8_t *) &manufacturer_string,
+    [STR_IDX_PRODUCT] = (uint8_t *) &product_string,
+    [STR_IDX_SERIAL]  = (uint8_t *) &serial_string
+};
+
+usb_desc msc_desc = {
+    .dev_desc    = (uint8_t *) &msc_dev_desc,
+    .config_desc = (uint8_t *) &msc_config_desc,
+    .strings     = usbd_msc_strings
+};
+
+static __ALIGN_BEGIN uint8_t usbd_msc_maxlun = 0U __ALIGN_END;
+
+/*!
+    \brief      initialize the MSC device
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  config_index: configuration index
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t msc_core_init(usb_dev *udev, uint8_t config_index)
+{
+    static __ALIGN_BEGIN usbd_msc_handler msc_handler __ALIGN_END;
+
+    memset((void *)&msc_handler, 0U, sizeof(usbd_msc_handler));
+
+    udev->dev.class_data[USBD_MSC_INTERFACE] = (void *)&msc_handler;
+
+    /* configure MSC Tx endpoint */
+    usbd_ep_setup(udev, &(msc_config_desc.msc_epin));
+
+    /* configure MSC Rx endpoint */
+    usbd_ep_setup(udev, &(msc_config_desc.msc_epout));
+
+    /* initialize the BBB layer */
+    msc_bbb_init(udev);
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      deinitialize the MSC device
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  config_index: configuration index
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t msc_core_deinit(usb_dev *udev, uint8_t config_index)
+{
+    /* clear MSC endpoints */
+    usbd_ep_clear(udev, MSC_IN_EP);
+    usbd_ep_clear(udev, MSC_OUT_EP);
+
+    /* deinitialize the BBB layer */
+    msc_bbb_deinit(udev);
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      handle the MSC class-specific and standard requests
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  req: device class-specific request
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t msc_core_req(usb_dev *udev, usb_req *req)
+{
+    usb_transc *transc = &udev->dev.transc_in[0];
+
+    switch(req->bRequest) {
+    case BBB_GET_MAX_LUN :
+        if((0U == req->wValue) &&
+                (1U == req->wLength) &&
+                (0x80U == (req->bmRequestType & 0x80U))) {
+            usbd_msc_maxlun = (uint8_t)usbd_mem_fops->mem_maxlun();
+
+            transc->xfer_buf = &usbd_msc_maxlun;
+            transc->remain_len = 1U;
+        } else {
+            return USBD_FAIL;
+        }
+        break;
+
+    case BBB_RESET :
+        if((0U == req->wValue) &&
+                (0U == req->wLength) &&
+                (0x80U != (req->bmRequestType & 0x80U))) {
+            msc_bbb_reset(udev);
+        } else {
+            return USBD_FAIL;
+        }
+        break;
+
+    case USB_CLEAR_FEATURE:
+        msc_bbb_clrfeature(udev, (uint8_t)req->wIndex);
+        break;
+
+    default:
+        return USBD_FAIL;
+    }
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      handle data in stage
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: the endpoint number
+    \param[out] none
+    \retval     none
+*/
+static uint8_t msc_core_in(usb_dev *udev, uint8_t ep_num)
+{
+    msc_bbb_data_in(udev, ep_num);
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      handle data out stage
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: the endpoint number
+    \param[out] none
+    \retval     none
+*/
+static uint8_t msc_core_out(usb_dev *udev, uint8_t ep_num)
+{
+    msc_bbb_data_out(udev, ep_num);
+
+    return USBD_OK;
+}

+ 59 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_core.h

@@ -0,0 +1,59 @@
+/*!
+    \file    usbd_msc_core.h
+    \brief   the header file of USB MSC device class core functions
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USBD_MSC_CORE_H
+#define __USBD_MSC_CORE_H
+
+#include "usbd_core.h"
+#include "usb_msc.h"
+
+#define USB_MSC_CONFIG_DESC_SIZE          32U
+
+#define MSC_EPIN_SIZE                     MSC_DATA_PACKET_SIZE
+#define MSC_EPOUT_SIZE                    MSC_DATA_PACKET_SIZE
+
+/* USB configuration descriptor structure */
+typedef struct {
+    usb_desc_config         config;
+
+    usb_desc_itf            msc_itf;
+    usb_desc_ep             msc_epin;
+    usb_desc_ep             msc_epout;
+} usb_desc_config_set;
+
+extern usb_desc msc_desc;
+extern usb_class_core msc_class;
+
+#endif /* __USBD_MSC_CORE_H */

+ 71 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_data.c

@@ -0,0 +1,71 @@
+/*!
+    \file    usbd_msc_data.c
+    \brief   USB MSC vital inquiry pages and sense data
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#include "usbd_msc_data.h"
+
+/* USB mass storage page 0 inquiry data */
+const uint8_t msc_page00_inquiry_data[] = {
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    (INQUIRY_PAGE00_LENGTH - 4U),
+    0x80U,
+    0x83U,
+};
+
+/* USB mass storage sense 6 data */
+const uint8_t msc_mode_sense6_data[] = {
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U
+};
+
+/* USB mass storage sense 10 data */
+const uint8_t msc_mode_sense10_data[] = {
+    0x00U,
+    0x06U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U
+};

+ 50 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_data.h

@@ -0,0 +1,50 @@
+/*!
+    \file    usbd_msc_data.h
+    \brief   the header file of the usbd_msc_data.c file
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USBD_MSC_DATA_H
+#define __USBD_MSC_DATA_H
+
+#include "usbd_conf.h"
+
+#define MODE_SENSE6_LENGTH                 8U
+#define MODE_SENSE10_LENGTH                8U
+#define INQUIRY_PAGE00_LENGTH              96U
+#define FORMAT_CAPACITIES_LENGTH           20U
+
+extern const uint8_t msc_page00_inquiry_data[];
+extern const uint8_t msc_mode_sense6_data[];
+extern const uint8_t msc_mode_sense10_data[];
+
+#endif /* __USBD_MSC_DATA_H */

+ 59 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_mem.h

@@ -0,0 +1,59 @@
+/*!
+    \file    usbd_msc_mem.h
+    \brief   header file for storage memory
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USBD_MSC_MEM_H
+#define __USBD_MSC_MEM_H
+
+#include "usbd_conf.h"
+
+#define USBD_STD_INQUIRY_LENGTH     36U
+
+typedef struct {
+    int8_t (*mem_init)(uint8_t lun);
+    int8_t (*mem_ready)(uint8_t lun);
+    int8_t (*mem_protected)(uint8_t lun);
+    int8_t (*mem_read)(uint8_t lun, uint8_t *buf, uint32_t block_addr, uint16_t block_len);
+    int8_t (*mem_write)(uint8_t lun, uint8_t *buf, uint32_t block_addr, uint16_t block_len);
+    int8_t (*mem_maxlun)(void);
+
+    uint8_t *mem_toc_data;
+    uint8_t *mem_inquiry_data[MEM_LUN_NUM];
+    uint32_t mem_block_size[MEM_LUN_NUM];
+    uint32_t mem_block_len[MEM_LUN_NUM];
+} usbd_mem_cb;
+
+extern usbd_mem_cb *usbd_mem_fops;
+
+#endif /* __USBD_MSC_MEM_H */

+ 744 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_scsi.c

@@ -0,0 +1,744 @@
+/*!
+    \file    usbd_msc_scsi.c
+    \brief   USB SCSI layer functions
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#include "usbd_enum.h"
+#include "usbd_msc_bbb.h"
+#include "usbd_msc_scsi.h"
+#include "usbd_msc_data.h"
+
+/* local function prototypes ('static') */
+static int8_t scsi_test_unit_ready(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_mode_select6(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_mode_select10(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_inquiry(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_read_format_capacity(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_read_capacity10(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_request_sense(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_mode_sense6(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_toc_cmd_read(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_mode_sense10(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_write10(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_read10(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_verify10(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+
+static int8_t scsi_process_read(usb_core_driver *udev, uint8_t lun);
+static int8_t scsi_process_write(usb_core_driver *udev, uint8_t lun);
+
+static inline int8_t scsi_check_address_range(usb_core_driver *udev, uint8_t lun, uint32_t blk_offset, uint16_t blk_nbr);
+static inline int8_t scsi_format_cmd(usb_core_driver *udev, uint8_t lun);
+static inline int8_t scsi_start_stop_unit(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static inline int8_t scsi_allow_medium_removal(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+
+/*!
+    \brief      process SCSI commands
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+int8_t scsi_process_cmd(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    switch(params[0]) {
+    case SCSI_TEST_UNIT_READY:
+        return scsi_test_unit_ready(udev, lun, params);
+
+    case SCSI_REQUEST_SENSE:
+        return scsi_request_sense(udev, lun, params);
+
+    case SCSI_INQUIRY:
+        return scsi_inquiry(udev, lun, params);
+
+    case SCSI_START_STOP_UNIT:
+        return scsi_start_stop_unit(udev, lun, params);
+
+    case SCSI_ALLOW_MEDIUM_REMOVAL:
+        return scsi_allow_medium_removal(udev, lun, params);
+
+    case SCSI_MODE_SENSE6:
+        return scsi_mode_sense6(udev, lun, params);
+
+    case SCSI_MODE_SENSE10:
+        return scsi_mode_sense10(udev, lun, params);
+
+    case SCSI_READ_FORMAT_CAPACITIES:
+        return scsi_read_format_capacity(udev, lun, params);
+
+    case SCSI_READ_CAPACITY10:
+        return scsi_read_capacity10(udev, lun, params);
+
+    case SCSI_READ10:
+        return scsi_read10(udev, lun, params);
+
+    case SCSI_WRITE10:
+        return scsi_write10(udev, lun, params);
+
+    case SCSI_VERIFY10:
+        return scsi_verify10(udev, lun, params);
+
+    case SCSI_FORMAT_UNIT:
+        return scsi_format_cmd(udev, lun);
+
+    case SCSI_READ_TOC_DATA:
+        return scsi_toc_cmd_read(udev, lun, params);
+
+    case SCSI_MODE_SELECT6:
+        return scsi_mode_select6(udev, lun, params);
+
+    case SCSI_MODE_SELECT10:
+        return scsi_mode_select10(udev, lun, params);
+
+    default:
+        scsi_sense_code(udev, lun, ILLEGAL_REQUEST, INVALID_CDB);
+        return -1;
+    }
+}
+
+/*!
+    \brief      load the last error code in the error list
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  skey: sense key
+    \param[in]  asc: additional sense key
+    \param[out] none
+    \retval     none
+*/
+void scsi_sense_code(usb_core_driver *udev, uint8_t lun, uint8_t skey, uint8_t asc)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->scsi_sense[msc->scsi_sense_tail].SenseKey = skey;
+    msc->scsi_sense[msc->scsi_sense_tail].ASC = asc << 8U;
+    msc->scsi_sense_tail++;
+
+    if(SENSE_LIST_DEEPTH == msc->scsi_sense_tail) {
+        msc->scsi_sense_tail = 0U;
+    }
+}
+
+/*!
+    \brief      process SCSI Test Unit Ready command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_test_unit_ready(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    /* case 9 : Hi > D0 */
+    if(0U != msc->bbb_cbw.dCBWDataTransferLength) {
+        scsi_sense_code(udev, msc->bbb_cbw.bCBWLUN, ILLEGAL_REQUEST, INVALID_CDB);
+
+        return -1;
+    }
+
+    if(0 != usbd_mem_fops->mem_ready(lun)) {
+        scsi_sense_code(udev, lun, NOT_READY, MEDIUM_NOT_PRESENT);
+
+        return -1;
+    }
+
+    msc->bbb_datalen = 0U;
+
+    return 0;
+}
+
+/*!
+    \brief      process Inquiry command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_mode_select6(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_datalen = 0U;
+
+    return 0;
+}
+
+/*!
+    \brief      process Inquiry command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_mode_select10(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_datalen = 0U;
+
+    return 0;
+}
+
+/*!
+    \brief      process Inquiry command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_inquiry(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint8_t *page = NULL;
+    uint16_t len = 0U;
+
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if(params[1] & 0x01U) {
+        /* Evpd is set */
+        page = (uint8_t *)msc_page00_inquiry_data;
+
+        len = INQUIRY_PAGE00_LENGTH;
+    } else {
+        page = (uint8_t *)usbd_mem_fops->mem_inquiry_data[lun];
+
+        len = (uint16_t)(page[4] + 5U);
+
+        if(params[4] <= len) {
+            len = params[4];
+        }
+    }
+
+    msc->bbb_datalen = len;
+
+    while(len) {
+        len--;
+        msc->bbb_data[len] = page[len];
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      process Read Capacity 10 command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_read_capacity10(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint32_t blk_num = usbd_mem_fops->mem_block_len[lun] - 1U;
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->scsi_blk_nbr[lun] = usbd_mem_fops->mem_block_len[lun];
+    msc->scsi_blk_size[lun] = usbd_mem_fops->mem_block_size[lun];
+
+    msc->bbb_data[0] = (uint8_t)(blk_num >> 24U);
+    msc->bbb_data[1] = (uint8_t)(blk_num >> 16U);
+    msc->bbb_data[2] = (uint8_t)(blk_num >> 8U);
+    msc->bbb_data[3] = (uint8_t)(blk_num);
+
+    msc->bbb_data[4] = (uint8_t)(msc->scsi_blk_size[lun] >> 24U);
+    msc->bbb_data[5] = (uint8_t)(msc->scsi_blk_size[lun] >> 16U);
+    msc->bbb_data[6] = (uint8_t)(msc->scsi_blk_size[lun] >> 8U);
+    msc->bbb_data[7] = (uint8_t)(msc->scsi_blk_size[lun]);
+
+    msc->bbb_datalen = 8U;
+
+    return 0;
+}
+
+/*!
+    \brief      process Read Format Capacity command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_read_format_capacity(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint16_t i = 0U;
+    uint32_t blk_size = usbd_mem_fops->mem_block_size[lun];
+    uint32_t blk_num = usbd_mem_fops->mem_block_len[lun];
+    uint32_t blk_nbr = blk_num - 1U;
+
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    for(i = 0U; i < 12U; i++) {
+        msc->bbb_data[i] = 0U;
+    }
+
+    msc->bbb_data[3] = 0x08U;
+    msc->bbb_data[4] = (uint8_t)(blk_nbr >> 24U);
+    msc->bbb_data[5] = (uint8_t)(blk_nbr >> 16U);
+    msc->bbb_data[6] = (uint8_t)(blk_nbr >>  8U);
+    msc->bbb_data[7] = (uint8_t)(blk_nbr);
+
+    msc->bbb_data[8] = 0x02U;
+    msc->bbb_data[9] = (uint8_t)(blk_size >> 16U);
+    msc->bbb_data[10] = (uint8_t)(blk_size >> 8U);
+    msc->bbb_data[11] = (uint8_t)(blk_size);
+
+    msc->bbb_datalen = 12U;
+
+    return 0;
+}
+
+/*!
+    \brief      process Mode Sense6 command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_mode_sense6(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint16_t len = 8U;
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_datalen = len;
+
+    while(len) {
+        len--;
+        msc->bbb_data[len] = msc_mode_sense6_data[len];
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      process Mode Sense10 command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_mode_sense10(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint16_t len = 8U;
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_datalen = len;
+
+    while(len) {
+        len--;
+        msc->bbb_data[len] = msc_mode_sense10_data[len];
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      process Request Sense command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_request_sense(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint8_t i = 0U;
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    for(i = 0U; i < REQUEST_SENSE_DATA_LEN; i++) {
+        msc->bbb_data[i] = 0U;
+    }
+
+    msc->bbb_data[0] = 0x70U;
+    msc->bbb_data[7] = REQUEST_SENSE_DATA_LEN - 6U;
+
+    if((msc->scsi_sense_head != msc->scsi_sense_tail)) {
+        msc->bbb_data[2] = msc->scsi_sense[msc->scsi_sense_head].SenseKey;
+        msc->bbb_data[12] = msc->scsi_sense[msc->scsi_sense_head].ASCQ;
+        msc->bbb_data[13] = msc->scsi_sense[msc->scsi_sense_head].ASC;
+        msc->scsi_sense_head++;
+
+        if(msc->scsi_sense_head == SENSE_LIST_DEEPTH) {
+            msc->scsi_sense_head = 0U;
+        }
+    }
+
+    msc->bbb_datalen = USB_MIN(REQUEST_SENSE_DATA_LEN, params[4]);
+
+    return 0;
+}
+
+
+/*
+byte 5 (index 4) of start stop unit 
+7  6  5  4  3  2  1  0
+[ PWRCON ]  R  F  E  S
+S = start
+E = loadeject
+*/
+
+#define STARTSTOPUNIT_START (1)
+#define STARTSTOPUNIT_LOADEJECT (2)
+
+/*!
+    \brief      process Start Stop Unit command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static inline int8_t scsi_start_stop_unit(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_datalen = 0U;
+
+    /* not part of GDS class! make sure to bring this over if updating USB class */
+    // if start = 0, load eject = 1,  host is advising of media eject (usually a device removal too)
+    if ((params[4] & STARTSTOPUNIT_LOADEJECT)) {
+        if (!(params[4] & STARTSTOPUNIT_START))
+            msc->scsi_disk_pop = 1U;
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      process Allow Medium Removal command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static inline int8_t scsi_allow_medium_removal(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_datalen = 0U;
+
+    return 0;
+}
+
+/*!
+    \brief      process Read10 command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_read10(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if(msc->bbb_state == BBB_IDLE) {
+        /* direction is from device to host */
+        if(0x80U != (msc->bbb_cbw.bmCBWFlags & 0x80U)) {
+            scsi_sense_code(udev, msc->bbb_cbw.bCBWLUN, ILLEGAL_REQUEST, INVALID_CDB);
+
+            return -1;
+        }
+
+        if(0 != usbd_mem_fops->mem_ready(lun)) {
+            scsi_sense_code(udev, lun, NOT_READY, MEDIUM_NOT_PRESENT);
+
+            return -1;
+        }
+
+        msc->scsi_blk_addr = (params[2] << 24U) | (params[3] << 16U) | \
+                             (params[4] << 8U) |  params[5];
+
+        msc->scsi_blk_len = (params[7] << 8U) | params[8];
+
+        if(scsi_check_address_range(udev, lun, msc->scsi_blk_addr, (uint16_t)msc->scsi_blk_len) < 0) {
+            return -1; /* error */
+        }
+
+        msc->bbb_state = BBB_DATA_IN;
+
+        msc->scsi_blk_addr *= msc->scsi_blk_size[lun];
+        msc->scsi_blk_len  *= msc->scsi_blk_size[lun];
+
+        /* cases 4,5 : Hi <> Dn */
+        if(msc->bbb_cbw.dCBWDataTransferLength != msc->scsi_blk_len) {
+            scsi_sense_code(udev, msc->bbb_cbw.bCBWLUN, ILLEGAL_REQUEST, INVALID_CDB);
+
+            return -1;
+        }
+    }
+
+    msc->bbb_datalen = MSC_MEDIA_PACKET_SIZE;
+
+    return scsi_process_read(udev, lun);
+}
+
+/*!
+    \brief      process Write10 command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_write10(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if(BBB_IDLE == msc->bbb_state) {
+        /* case 8 : Hi <> Do */
+        if(0x80U == (msc->bbb_cbw.bmCBWFlags & 0x80U)) {
+            scsi_sense_code(udev, msc->bbb_cbw.bCBWLUN, ILLEGAL_REQUEST, INVALID_CDB);
+
+            return -1;
+        }
+
+        /* check whether media is ready */
+        if(0 != usbd_mem_fops->mem_ready(lun)) {
+            scsi_sense_code(udev, lun, NOT_READY, MEDIUM_NOT_PRESENT);
+
+            return -1;
+        }
+
+        /* check if media is write-protected */
+        if(0 != usbd_mem_fops->mem_protected(lun)) {
+            scsi_sense_code(udev, lun, NOT_READY, WRITE_PROTECTED);
+
+            return -1;
+        }
+
+        msc->scsi_blk_addr = (params[2] << 24U) | (params[3] << 16U) | \
+                             (params[4] << 8U) |  params[5];
+
+        msc->scsi_blk_len = (params[7] << 8U) | params[8];
+
+        /* check if LBA address is in the right range */
+        if(scsi_check_address_range(udev, lun, msc->scsi_blk_addr, (uint16_t)msc->scsi_blk_len) < 0) {
+            return -1; /* error */
+        }
+
+        msc->scsi_blk_addr *= msc->scsi_blk_size[lun];
+        msc->scsi_blk_len  *= msc->scsi_blk_size[lun];
+
+        /* cases 3,11,13 : Hn,Ho <> D0 */
+        if(msc->bbb_cbw.dCBWDataTransferLength != msc->scsi_blk_len) {
+            scsi_sense_code(udev, msc->bbb_cbw.bCBWLUN, ILLEGAL_REQUEST, INVALID_CDB);
+
+            return -1;
+        }
+
+        /* prepare endpoint to receive first data packet */
+        msc->bbb_state = BBB_DATA_OUT;
+
+        usbd_ep_recev(udev,
+                      MSC_OUT_EP,
+                      msc->bbb_data,
+                      USB_MIN(msc->scsi_blk_len, MSC_MEDIA_PACKET_SIZE));
+    } else { /* write process ongoing */
+        return scsi_process_write(udev, lun);
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      process Verify10 command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_verify10(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if(0x02U == (params[1] & 0x02U)) {
+        scsi_sense_code(udev, lun, ILLEGAL_REQUEST, INVALID_FIELED_IN_COMMAND);
+
+        return -1; /* error, verify mode not supported*/
+    }
+
+    if(scsi_check_address_range(udev, lun, msc->scsi_blk_addr, (uint16_t)msc->scsi_blk_len) < 0) {
+        return -1; /* error */
+    }
+
+    msc->bbb_datalen = 0U;
+
+    return 0;
+}
+
+/*!
+    \brief      check address range
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  blk_offset: block offset
+    \param[in]  blk_nbr: number of block to be processed
+    \param[out] none
+    \retval     status
+*/
+static inline int8_t scsi_check_address_range(usb_core_driver *udev, uint8_t lun, uint32_t blk_offset, uint16_t blk_nbr)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if((blk_offset + blk_nbr) > msc->scsi_blk_nbr[lun]) {
+        scsi_sense_code(udev, lun, ILLEGAL_REQUEST, ADDRESS_OUT_OF_RANGE);
+
+        return -1;
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      handle read process
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_process_read(usb_core_driver *udev, uint8_t lun)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    uint32_t len = USB_MIN(msc->scsi_blk_len, MSC_MEDIA_PACKET_SIZE);
+
+    if(usbd_mem_fops->mem_read(lun,
+                               msc->bbb_data,
+                               msc->scsi_blk_addr,
+                               (uint16_t)(len / msc->scsi_blk_size[lun])) < 0) {
+        scsi_sense_code(udev, lun, HARDWARE_ERROR, UNRECOVERED_READ_ERROR);
+
+        return -1;
+    }
+
+    usbd_ep_send(udev, MSC_IN_EP, msc->bbb_data, len);
+
+    msc->scsi_blk_addr += len;
+    msc->scsi_blk_len  -= len;
+
+    /* case 6 : Hi = Di */
+    msc->bbb_csw.dCSWDataResidue -= len;
+
+    if(0U == msc->scsi_blk_len) {
+        msc->bbb_state = BBB_LAST_DATA_IN;
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      handle write process
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_process_write(usb_core_driver *udev, uint8_t lun)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    uint32_t len = USB_MIN(msc->scsi_blk_len, MSC_MEDIA_PACKET_SIZE);
+
+    if(usbd_mem_fops->mem_write(lun,
+                                msc->bbb_data,
+                                msc->scsi_blk_addr,
+                                (uint16_t)(len / msc->scsi_blk_size[lun])) < 0) {
+        scsi_sense_code(udev, lun, HARDWARE_ERROR, WRITE_FAULT);
+
+        return -1;
+    }
+
+    msc->scsi_blk_addr += len;
+    msc->scsi_blk_len  -= len;
+
+    /* case 12 : Ho = Do */
+    msc->bbb_csw.dCSWDataResidue -= len;
+
+    if(0U == msc->scsi_blk_len) {
+        msc_bbb_csw_send(udev, CSW_CMD_PASSED);
+    } else {
+        /* prepare endpoint to receive next packet */
+        usbd_ep_recev(udev,
+                      MSC_OUT_EP,
+                      msc->bbb_data,
+                      USB_MIN(msc->scsi_blk_len, MSC_MEDIA_PACKET_SIZE));
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      process Format Unit command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[out] none
+    \retval     status
+*/
+static inline int8_t scsi_format_cmd(usb_core_driver *udev, uint8_t lun)
+{
+    return 0;
+}
+
+/*!
+    \brief      process Read_Toc command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_toc_cmd_read(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint8_t *pPage;
+    uint16_t len;
+
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    pPage = (uint8_t *)&usbd_mem_fops->mem_toc_data[lun * READ_TOC_CMD_LEN];
+    len = (uint16_t)pPage[1] + 2U;
+
+    msc->bbb_datalen = len;
+
+    while(len) {
+        len--;
+        msc->bbb_data[len] = pPage[len];
+    }
+
+    return 0;
+}

+ 51 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_scsi.h

@@ -0,0 +1,51 @@
+/*!
+    \file    usbd_msc_scsi.h
+    \brief   the header file of the usbd_msc_scsi.c file
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USBD_MSC_SCSI_H
+#define __USBD_MSC_SCSI_H
+
+#include "usbd_msc_data.h"
+#include "usbd_msc_bbb.h"
+#include "msc_scsi.h"
+
+#define SENSE_LIST_DEEPTH                           4U
+
+/* function declarations */
+/* process SCSI commands */
+int8_t scsi_process_cmd(usb_core_driver *udev, uint8_t lun, uint8_t *cmd);
+/* load the last error code in the error list */
+void scsi_sense_code(usb_core_driver *udev, uint8_t lun, uint8_t skey, uint8_t asc);
+
+#endif /* __USBD_MSC_SCSI_H */

+ 236 - 0
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_msc.cpp

@@ -0,0 +1,236 @@
+/**
+ * Copyright (c) 2023-2024 zigzagjoe
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+/* platform specific MSC routines */
+#ifdef PLATFORM_MASS_STORAGE
+
+#include <SdFat.h>
+#include <device/usbd.h>
+#include <hardware/gpio.h>
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_msc.h"
+
+#include "msc.h"
+#include "msc_device.h"
+
+#if CFG_TUD_MSC_EP_BUFSIZE < SD_SECTOR_SIZE
+  #error "CFG_TUD_MSC_EP_BUFSIZE is too small! It needs to be at least 512 (SD_SECTOR_SIZE)"
+#endif
+
+// external global SD variable
+extern SdFs SD;
+static bool unitReady = false;
+
+/* return true if USB presence detected / eligble to enter CR mode */
+bool platform_senseMSC() {
+
+#ifdef ZULUSCSI_PICO
+  // check if we're USB powered, if not, exit immediately
+  // pin on the wireless module, see https://github.com/earlephilhower/arduino-pico/discussions/835
+  if (rp2040.isPicoW() && !digitalRead(34))
+    return false;
+
+  if (!rp2040.isPicoW() && !digitalRead(24))
+    return false;
+#endif
+
+  logmsg("Waiting for USB enumeration to enter Card Reader mode.");
+
+  // wait for up to a second to be enumerated
+  uint32_t start = millis();
+  while (!tud_connected() && ((uint32_t)(millis() - start) < CR_ENUM_TIMEOUT)) 
+    delay(100);
+
+  // tud_connected returns True if just got out of Bus Reset and received the very first data from host
+  // https://github.com/hathach/tinyusb/blob/master/src/device/usbd.h#L63
+  return tud_connected();
+}
+
+/* return true if we should remain in card reader mode and perform periodic tasks */
+bool platform_runMSC() {
+  return unitReady;
+}
+
+/* perform MSC class preinit tasks */
+void platform_enterMSC() {
+  dbgmsg("USB MSC buffer size: ", CFG_TUD_MSC_EP_BUFSIZE);
+  // MSC is ready for read/write
+  // we don't need any prep, but the var is requried as the MSC callbacks are always active
+  unitReady = true;
+}
+
+/* perform any cleanup tasks for the MSC-specific functionality */
+void platform_exitMSC() {
+  unitReady = false;
+}
+
+/* TinyUSB mass storage callbacks follow */
+
+// usb framework checks this func exists for mass storage config. no code needed.
+void __USBInstallMassStorage() { }
+
+// Invoked when received SCSI_CMD_INQUIRY
+// fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
+extern "C" void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8],
+                        uint8_t product_id[16], uint8_t product_rev[4]) {
+
+  // TODO: We could/should use strings from the platform, but they are too long
+  const char vid[] = "ZuluSCSI";
+  const char pid[] = "Pico"; 
+  const char rev[] = "1.0";
+
+  memcpy(vendor_id, vid, tu_min32(strlen(vid), 8));
+  memcpy(product_id, pid, tu_min32(strlen(pid), 16));
+  memcpy(product_rev, rev, tu_min32(strlen(rev), 4));
+}
+
+// max LUN supported
+// we only have the one SD card
+extern "C" uint8_t tud_msc_get_maxlun_cb(void) {
+  return 1; // number of LUNs supported
+}
+
+// return writable status
+// on platform supporting write protect switch, could do that here.
+// otherwise this is not actually needed
+extern "C" bool tud_msc_is_writable_cb (uint8_t lun)
+{
+  (void) lun;
+  return unitReady;
+}
+
+// see https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf pg 221
+extern "C" bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
+{
+  (void) lun;
+  (void) power_condition;
+
+  if (load_eject)  {
+    if (start) {
+      // load disk storage
+      // do nothing as we started "loaded"
+    } else {
+      unitReady = false;
+    }
+  }
+
+  return true;
+}
+
+// return true if we are ready to service reads/writes
+extern "C" bool tud_msc_test_unit_ready_cb(uint8_t lun) {
+  (void) lun;
+
+  return unitReady;
+}
+
+// return size in blocks and block size
+extern "C" void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count,
+                         uint16_t *block_size) {
+  (void) lun;
+
+  *block_count = unitReady ? (SD.card()->sectorCount()) : 0;
+  *block_size = SD_SECTOR_SIZE;
+}
+
+// Callback invoked when received an SCSI command not in built-in list (below) which have their own callbacks
+// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE, READ10 and WRITE10
+extern "C" int32_t tud_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer,
+                        uint16_t bufsize) {
+
+  const void *response = NULL;
+  uint16_t resplen = 0;
+
+  switch (scsi_cmd[0]) {
+  case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
+    // Host is about to read/write etc ... better not to disconnect disk
+    resplen = 0;
+    break;
+
+  default:
+    // Set Sense = Invalid Command Operation
+    tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);
+
+    // negative means error -> tinyusb could stall and/or response with failed status
+    resplen = -1;
+    break;
+  }
+
+  // return len must not larger than bufsize
+  if (resplen > bufsize) {
+    resplen = bufsize;
+  }
+
+  // copy response to stack's buffer if any
+  if (response && resplen) {
+    memcpy(buffer, response, resplen);
+  }
+
+  return resplen;
+}
+
+// Callback invoked when received READ10 command.
+// Copy disk's data to buffer (up to bufsize) and return number of copied bytes (must be multiple of block size)
+extern "C" int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, 
+                            void* buffer, uint32_t bufsize)
+{
+  (void) lun;
+
+  bool rc = SD.card()->readSectors(lba, (uint8_t*) buffer, bufsize/SD_SECTOR_SIZE);
+
+  // only blink fast on reads; writes will override this
+  if (MSC_LEDMode == LED_SOLIDON)
+    MSC_LEDMode = LED_BLINK_FAST;
+    
+ /*
+ // debug check: only needed if changed the TUD MSC buffer, but did not recompile tinyusb  code
+ if (bufsize < SD_SECTOR_SIZE)
+    logmsg ("ERROR: USB MSC CFG_TUD_MSC_EP_BUFSIZE (",bufsize,") is smaller than SD card sector size! Card reader will not work.");
+
+  if (bufsize % SD_SECTOR_SIZE)
+    logmsg ("ERROR: USB MSC CFG_TUD_MSC_EP_BUFSIZE (",bufsize,") is not a multiple of SD card sector size! Card reader will not work.");   
+  */
+
+  return rc ? bufsize : -1;
+}
+
+// Callback invoked when receive WRITE10 command.
+// Process data in buffer to disk's storage and return number of written bytes (must be multiple of block size)
+extern "C" int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset,
+                           uint8_t *buffer, uint32_t bufsize) {
+  (void) lun;
+
+  bool rc = SD.card()->writeSectors(lba, buffer, bufsize/SD_SECTOR_SIZE);
+
+  // always slow blink
+  MSC_LEDMode = LED_BLINK_SLOW;
+
+  return rc ? bufsize : -1;
+}
+
+// Callback invoked when WRITE10 command is completed (status received and accepted by host).
+// used to flush any pending cache to storage
+extern "C" void tud_msc_write10_complete_cb(uint8_t lun) {
+  (void) lun;
+}
+
+#endif

+ 40 - 0
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_msc.h

@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2023-2024 zigzagjoe
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#ifdef PLATFORM_MASS_STORAGE
+#pragma once
+
+// private constants/enums
+#define SD_SECTOR_SIZE 512
+
+/* return true if USB presence detected / eligble to enter CR mode */
+bool platform_senseMSC();
+
+/* perform MSC-specific init tasks */
+void platform_enterMSC();
+
+/* return true if we should remain in card reader mode. called in a loop. */
+bool platform_runMSC();
+
+/* perform any cleanup tasks for the MSC-specific functionality */
+void platform_exitMSC();
+
+#endif

+ 382 - 0
lib/ZuluSCSI_platform_RP2040/msc.h

@@ -0,0 +1,382 @@
+/* 
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef _TUSB_MSC_H_
+#define _TUSB_MSC_H_
+
+#include "common/tusb_common.h"
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Mass Storage Class Constant
+//--------------------------------------------------------------------+
+/// MassStorage Subclass
+typedef enum
+{
+  MSC_SUBCLASS_RBC = 1 , ///< Reduced Block Commands (RBC) T10 Project 1240-D
+  MSC_SUBCLASS_SFF_MMC , ///< SFF-8020i, MMC-2 (ATAPI). Typically used by a CD/DVD device
+  MSC_SUBCLASS_QIC     , ///< QIC-157. Typically used by a tape device
+  MSC_SUBCLASS_UFI     , ///< UFI. Typically used by Floppy Disk Drive (FDD) device
+  MSC_SUBCLASS_SFF     , ///< SFF-8070i. Can be used by Floppy Disk Drive (FDD) device
+  MSC_SUBCLASS_SCSI      ///< SCSI transparent command set
+}msc_subclass_type_t;
+
+enum {
+  MSC_CBW_SIGNATURE = 0x43425355, ///< Constant value of 43425355h (little endian)
+  MSC_CSW_SIGNATURE = 0x53425355  ///< Constant value of 53425355h (little endian)
+};
+
+/// \brief MassStorage Protocol.
+/// \details CBI only approved to use with full-speed floopy disk & should not used with highspeed or device other than floopy
+typedef enum
+{
+  MSC_PROTOCOL_CBI              = 0 ,  ///< Control/Bulk/Interrupt protocol (with command completion interrupt)
+  MSC_PROTOCOL_CBI_NO_INTERRUPT = 1 ,  ///< Control/Bulk/Interrupt protocol (without command completion interrupt)
+  MSC_PROTOCOL_BOT              = 0x50 ///< Bulk-Only Transport
+}msc_protocol_type_t;
+
+/// MassStorage Class-Specific Control Request
+typedef enum
+{
+  MSC_REQ_GET_MAX_LUN = 254, ///< The Get Max LUN device request is used to determine the number of logical units supported by the device. Logical Unit Numbers on the device shall be numbered contiguously starting from LUN 0 to a maximum LUN of 15
+  MSC_REQ_RESET       = 255  ///< This request is used to reset the mass storage device and its associated interface. This class-specific request shall ready the device for the next CBW from the host.
+}msc_request_type_t;
+
+/// \brief Command Block Status Values
+/// \details Indicates the success or failure of the command. The device shall set this byte to zero if the command completed
+/// successfully. A non-zero value shall indicate a failure during command execution according to the following
+typedef enum
+{
+  MSC_CSW_STATUS_PASSED = 0 , ///< MSC_CSW_STATUS_PASSED
+  MSC_CSW_STATUS_FAILED     , ///< MSC_CSW_STATUS_FAILED
+  MSC_CSW_STATUS_PHASE_ERROR  ///< MSC_CSW_STATUS_PHASE_ERROR
+}msc_csw_status_t;
+
+/// Command Block Wrapper
+typedef struct TU_ATTR_PACKED
+{
+  uint32_t signature;   ///< Signature that helps identify this data packet as a CBW. The signature field shall contain the value 43425355h (little endian), indicating a CBW.
+  uint32_t tag;         ///< Tag sent by the host. The device shall echo the contents of this field back to the host in the dCSWTagfield of the associated CSW. The dCSWTagpositively associates a CSW with the corresponding CBW.
+  uint32_t total_bytes; ///< The number of bytes of data that the host expects to transfer on the Bulk-In or Bulk-Out endpoint (as indicated by the Direction bit) during the execution of this command. If this field is zero, the device and the host shall transfer no data between the CBW and the associated CSW, and the device shall ignore the value of the Direction bit in bmCBWFlags.
+  uint8_t dir;          ///< Bit 7 of this field define transfer direction \n - 0 : Data-Out from host to the device. \n - 1 : Data-In from the device to the host.
+  uint8_t lun;          ///< The device Logical Unit Number (LUN) to which the command block is being sent. For devices that support multiple LUNs, the host shall place into this field the LUN to which this command block is addressed. Otherwise, the host shall set this field to zero.
+  uint8_t cmd_len;      ///< The valid length of the CBWCBin bytes. This defines the valid length of the command block. The only legal values are 1 through 16
+  uint8_t command[16];  ///< The command block to be executed by the device. The device shall interpret the first cmd_len bytes in this field as a command block
+}msc_cbw_t;
+
+TU_VERIFY_STATIC(sizeof(msc_cbw_t) == 31, "size is not correct");
+
+/// Command Status Wrapper
+typedef struct TU_ATTR_PACKED
+{
+  uint32_t signature    ; ///< Signature that helps identify this data packet as a CSW. The signature field shall contain the value 53425355h (little endian), indicating CSW.
+  uint32_t tag          ; ///< The device shall set this field to the value received in the dCBWTag of the associated CBW.
+  uint32_t data_residue ; ///< For Data-Out the device shall report in the dCSWDataResiduethe difference between the amount of data expected as stated in the dCBWDataTransferLength, and the actual amount of data processed by the device. For Data-In the device shall report in the dCSWDataResiduethe difference between the amount of data expected as stated in the dCBWDataTransferLengthand the actual amount of relevant data sent by the device
+  uint8_t  status       ; ///< indicates the success or failure of the command. Values from \ref msc_csw_status_t
+}msc_csw_t;
+
+TU_VERIFY_STATIC(sizeof(msc_csw_t) == 13, "size is not correct");
+
+//--------------------------------------------------------------------+
+// SCSI Constant
+//--------------------------------------------------------------------+
+
+/// SCSI Command Operation Code
+typedef enum
+{
+  SCSI_CMD_TEST_UNIT_READY              = 0x00, ///< The SCSI Test Unit Ready command is used to determine if a device is ready to transfer data (read/write), i.e. if a disk has spun up, if a tape is loaded and ready etc. The device does not perform a self-test operation.
+  SCSI_CMD_INQUIRY                      = 0x12, ///< The SCSI Inquiry command is used to obtain basic information from a target device.
+  SCSI_CMD_MODE_SELECT_6                = 0x15, ///<  provides a means for the application client to specify medium, logical unit, or peripheral device parameters to the device server. Device servers that implement the MODE SELECT(6) command shall also implement the MODE SENSE(6) command. Application clients should issue MODE SENSE(6) prior to each MODE SELECT(6) to determine supported mode pages, page lengths, and other parameters.
+  SCSI_CMD_MODE_SENSE_6                 = 0x1A, ///< 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.
+  SCSI_CMD_START_STOP_UNIT              = 0x1B,
+  SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E,
+  SCSI_CMD_READ_CAPACITY_10             = 0x25, ///< The SCSI Read Capacity command is used to obtain data capacity information from a target device.
+  SCSI_CMD_REQUEST_SENSE                = 0x03, ///< The SCSI Request Sense command is part of the SCSI computer protocol standard. This command is used to obtain sense data -- status/error information -- from a target device.
+  SCSI_CMD_READ_FORMAT_CAPACITY         = 0x23, ///< The command allows the Host to request a list of the possible format capacities for an installed writable media. This command also has the capability to report the writable capacity for a media when it is installed
+  SCSI_CMD_READ_10                      = 0x28, ///< The READ (10) command requests that the device server read the specified logical block(s) and transfer them to the data-in buffer.
+  SCSI_CMD_WRITE_10                     = 0x2A, ///< The WRITE (10) command requests thatthe device server transfer the specified logical block(s) from the data-out buffer and write them.
+}scsi_cmd_type_t;
+
+/// SCSI Sense Key
+typedef enum
+{
+  SCSI_SENSE_NONE            = 0x00, ///< no specific Sense Key. This would be the case for a successful command
+  SCSI_SENSE_RECOVERED_ERROR = 0x01, ///< ndicates the last command completed successfully with some recovery action performed by the disc drive.
+  SCSI_SENSE_NOT_READY       = 0x02, ///< Indicates the logical unit addressed cannot be accessed.
+  SCSI_SENSE_MEDIUM_ERROR    = 0x03, ///< Indicates the command terminated with a non-recovered error condition.
+  SCSI_SENSE_HARDWARE_ERROR  = 0x04, ///< Indicates the disc drive detected a nonrecoverable hardware failure while performing the command or during a self test.
+  SCSI_SENSE_ILLEGAL_REQUEST = 0x05, ///< Indicates an illegal parameter in the command descriptor block or in the additional parameters
+  SCSI_SENSE_UNIT_ATTENTION  = 0x06, ///< Indicates the disc drive may have been reset.
+  SCSI_SENSE_DATA_PROTECT    = 0x07, ///< Indicates that a command that reads or writes the medium was attempted on a block that is protected from this operation. The read or write operation is not performed.
+  SCSI_SENSE_FIRMWARE_ERROR  = 0x08, ///< Vendor specific sense key.
+  SCSI_SENSE_ABORTED_COMMAND = 0x0b, ///< Indicates the disc drive aborted the command.
+  SCSI_SENSE_EQUAL           = 0x0c, ///< Indicates a SEARCH DATA command has satisfied an equal comparison.
+  SCSI_SENSE_VOLUME_OVERFLOW = 0x0d, ///< Indicates a buffered peripheral device has reached the end of medium partition and data remains in the buffer that has not been written to the medium.
+  SCSI_SENSE_MISCOMPARE      = 0x0e  ///< ndicates that the source data did not match the data read from the medium.
+}scsi_sense_key_type_t;
+
+//--------------------------------------------------------------------+
+// SCSI Primary Command (SPC-4)
+//--------------------------------------------------------------------+
+
+/// SCSI Test Unit Ready Command
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t cmd_code    ; ///< SCSI OpCode for \ref SCSI_CMD_TEST_UNIT_READY
+  uint8_t lun         ; ///< Logical Unit
+  uint8_t reserved[3] ;
+  uint8_t control     ;
+} scsi_test_unit_ready_t;
+
+TU_VERIFY_STATIC(sizeof(scsi_test_unit_ready_t) == 6, "size is not correct");
+
+/// SCSI Inquiry Command
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t cmd_code     ; ///< SCSI OpCode for \ref SCSI_CMD_INQUIRY
+  uint8_t reserved1    ;
+  uint8_t page_code    ;
+  uint8_t reserved2    ;
+  uint8_t alloc_length ; ///< specifies the maximum number of bytes that USB host has allocated in the Data-In Buffer. An allocation length of zero specifies that no data shall be transferred.
+  uint8_t control      ;
+} scsi_inquiry_t, scsi_request_sense_t;
+
+TU_VERIFY_STATIC(sizeof(scsi_inquiry_t) == 6, "size is not correct");
+
+/// SCSI Inquiry Response Data
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t peripheral_device_type     : 5;
+  uint8_t peripheral_qualifier       : 3;
+
+  uint8_t                            : 7;
+  uint8_t is_removable               : 1;
+
+  uint8_t version;
+
+  uint8_t response_data_format       : 4;
+  uint8_t hierarchical_support       : 1;
+  uint8_t normal_aca                 : 1;
+  uint8_t                            : 2;
+
+  uint8_t additional_length;
+
+  uint8_t protect                    : 1;
+  uint8_t                            : 2;
+  uint8_t third_party_copy           : 1;
+  uint8_t target_port_group_support  : 2;
+  uint8_t access_control_coordinator : 1;
+  uint8_t scc_support                : 1;
+
+  uint8_t addr16                     : 1;
+  uint8_t                            : 3;
+  uint8_t multi_port                 : 1;
+  uint8_t                            : 1; // vendor specific
+  uint8_t enclosure_service          : 1;
+  uint8_t                            : 1;
+
+  uint8_t                            : 1; // vendor specific
+  uint8_t cmd_que                    : 1;
+  uint8_t                            : 2;
+  uint8_t sync                       : 1;
+  uint8_t wbus16                     : 1;
+  uint8_t                            : 2;
+
+  uint8_t vendor_id[8]  ; ///< 8 bytes of ASCII data identifying the vendor of the product.
+  uint8_t product_id[16]; ///< 16 bytes of ASCII data defined by the vendor.
+  uint8_t product_rev[4]; ///< 4 bytes of ASCII data defined by the vendor.
+} scsi_inquiry_resp_t;
+
+TU_VERIFY_STATIC(sizeof(scsi_inquiry_resp_t) == 36, "size is not correct");
+
+
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t response_code : 7; ///< 70h - current errors, Fixed Format 71h - deferred errors, Fixed Format
+  uint8_t valid         : 1;
+
+  uint8_t reserved;
+
+  uint8_t sense_key     : 4;
+  uint8_t               : 1;
+  uint8_t ili           : 1; ///< Incorrect length indicator
+  uint8_t end_of_medium : 1;
+  uint8_t filemark      : 1;
+
+  uint32_t information;
+  uint8_t  add_sense_len;
+  uint32_t command_specific_info;
+  uint8_t  add_sense_code;
+  uint8_t  add_sense_qualifier;
+  uint8_t  field_replaceable_unit_code;
+
+  uint8_t  sense_key_specific[3]; ///< sense key specific valid bit is bit 7 of key[0], aka MSB in Big Endian layout
+
+} scsi_sense_fixed_resp_t;
+
+TU_VERIFY_STATIC(sizeof(scsi_sense_fixed_resp_t) == 18, "size is not correct");
+
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t cmd_code     ; ///< SCSI OpCode for \ref SCSI_CMD_MODE_SENSE_6
+
+  uint8_t : 3;
+  uint8_t disable_block_descriptor : 1;
+  uint8_t : 4;
+
+  uint8_t page_code : 6;
+  uint8_t page_control : 2;
+
+  uint8_t subpage_code;
+  uint8_t alloc_length;
+  uint8_t control;
+} scsi_mode_sense6_t;
+
+TU_VERIFY_STATIC( sizeof(scsi_mode_sense6_t) == 6, "size is not correct");
+
+// This is only a Mode parameter header(6).
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t data_len;
+  uint8_t medium_type;
+
+  uint8_t reserved : 7;
+  bool write_protected : 1;
+
+  uint8_t block_descriptor_len;
+} scsi_mode_sense6_resp_t;
+
+TU_VERIFY_STATIC( sizeof(scsi_mode_sense6_resp_t) == 4, "size is not correct");
+
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t cmd_code; ///< SCSI OpCode for \ref SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL
+  uint8_t reserved[3];
+  uint8_t prohibit_removal;
+  uint8_t control;
+} scsi_prevent_allow_medium_removal_t;
+
+TU_VERIFY_STATIC( sizeof(scsi_prevent_allow_medium_removal_t) == 6, "size is not correct");
+
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t cmd_code;
+
+  uint8_t immded : 1;
+  uint8_t        : 7;
+
+  uint8_t TU_RESERVED;
+
+  uint8_t power_condition_mod : 4;
+  uint8_t                     : 4;
+
+  uint8_t start           : 1;
+  uint8_t load_eject      : 1;
+  uint8_t no_flush        : 1;
+  uint8_t                 : 1;
+  uint8_t power_condition : 4;
+
+  uint8_t control;
+} scsi_start_stop_unit_t;
+
+TU_VERIFY_STATIC( sizeof(scsi_start_stop_unit_t) == 6, "size is not correct");
+
+//--------------------------------------------------------------------+
+// SCSI MMC
+//--------------------------------------------------------------------+
+/// SCSI Read Format Capacity: Write Capacity
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t cmd_code;
+  uint8_t reserved[6];
+  uint16_t alloc_length;
+  uint8_t control;
+} scsi_read_format_capacity_t;
+
+TU_VERIFY_STATIC( sizeof(scsi_read_format_capacity_t) == 10, "size is not correct");
+
+typedef struct TU_ATTR_PACKED{
+  uint8_t reserved[3];
+  uint8_t list_length; /// must be 8*n, length in bytes of formattable capacity descriptor followed it.
+
+  uint32_t block_num; /// Number of Logical Blocks
+  uint8_t  descriptor_type; // 00: reserved, 01 unformatted media , 10 Formatted media, 11 No media present
+
+  uint8_t  reserved2;
+  uint16_t block_size_u16;
+
+} scsi_read_format_capacity_data_t;
+
+TU_VERIFY_STATIC( sizeof(scsi_read_format_capacity_data_t) == 12, "size is not correct");
+
+//--------------------------------------------------------------------+
+// SCSI Block Command (SBC-3)
+// NOTE: All data in SCSI command are in Big Endian
+//--------------------------------------------------------------------+
+
+/// SCSI Read Capacity 10 Command: Read Capacity
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t  cmd_code                 ; ///< SCSI OpCode for \ref SCSI_CMD_READ_CAPACITY_10
+  uint8_t  reserved1                ;
+  uint32_t lba                      ; ///< The first Logical Block Address (LBA) accessed by this command
+  uint16_t reserved2                ;
+  uint8_t  partial_medium_indicator ;
+  uint8_t  control                  ;
+} scsi_read_capacity10_t;
+
+TU_VERIFY_STATIC(sizeof(scsi_read_capacity10_t) == 10, "size is not correct");
+
+/// SCSI Read Capacity 10 Response Data
+typedef struct {
+  uint32_t last_lba   ; ///< The last Logical Block Address of the device
+  uint32_t block_size ; ///< Block size in bytes
+} scsi_read_capacity10_resp_t;
+
+TU_VERIFY_STATIC(sizeof(scsi_read_capacity10_resp_t) == 8, "size is not correct");
+
+/// SCSI Read 10 Command
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t  cmd_code    ; ///< SCSI OpCode
+  uint8_t  reserved    ; // has LUN according to wiki
+  uint32_t lba         ; ///< The first Logical Block Address (LBA) accessed by this command
+  uint8_t  reserved2   ;
+  uint16_t block_count ; ///< Number of Blocks used by this command
+  uint8_t  control     ;
+} scsi_read10_t, scsi_write10_t;
+
+TU_VERIFY_STATIC(sizeof(scsi_read10_t) == 10, "size is not correct");
+TU_VERIFY_STATIC(sizeof(scsi_write10_t) == 10, "size is not correct");
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* _TUSB_MSC_H_ */

+ 952 - 0
lib/ZuluSCSI_platform_RP2040/msc_device.c

@@ -0,0 +1,952 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if (CFG_TUD_ENABLED && CFG_TUD_MSC)
+
+#include "device/dcd.h"         // for faking dcd_event_xfer_complete
+#include "device/usbd.h"
+#include "device/usbd_pvt.h"
+
+#include "msc_device.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+
+// Can be selectively disabled to reduce logging when troubleshooting other driver
+#define MSC_DEBUG   2
+
+enum
+{
+  MSC_STAGE_CMD  = 0,
+  MSC_STAGE_DATA,
+  MSC_STAGE_STATUS,
+  MSC_STAGE_STATUS_SENT,
+  MSC_STAGE_NEED_RESET,
+};
+
+typedef struct
+{
+  // TODO optimize alignment
+  CFG_TUSB_MEM_ALIGN msc_cbw_t cbw;
+  CFG_TUSB_MEM_ALIGN msc_csw_t csw;
+
+  uint8_t  itf_num;
+  uint8_t  ep_in;
+  uint8_t  ep_out;
+
+  // Bulk Only Transfer (BOT) Protocol
+  uint8_t  stage;
+  uint32_t total_len;   // byte to be transferred, can be smaller than total_bytes in cbw
+  uint32_t xferred_len; // numbered of bytes transferred so far in the Data Stage
+
+  // Sense Response Data
+  uint8_t sense_key;
+  uint8_t add_sense_code;
+  uint8_t add_sense_qualifier;
+}mscd_interface_t;
+
+CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static mscd_interface_t _mscd_itf;
+CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint8_t _mscd_buf[CFG_TUD_MSC_EP_BUFSIZE];
+
+//--------------------------------------------------------------------+
+// INTERNAL OBJECT & FUNCTION DECLARATION
+//--------------------------------------------------------------------+
+static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_t* buffer, uint32_t bufsize);
+static void proc_read10_cmd(uint8_t rhport, mscd_interface_t* p_msc);
+
+static void proc_write10_cmd(uint8_t rhport, mscd_interface_t* p_msc);
+static void proc_write10_new_data(uint8_t rhport, mscd_interface_t* p_msc, uint32_t xferred_bytes);
+
+TU_ATTR_ALWAYS_INLINE static inline bool is_data_in(uint8_t dir)
+{
+  return tu_bit_test(dir, 7);
+}
+
+static inline bool send_csw(uint8_t rhport, mscd_interface_t* p_msc)
+{
+  // Data residue is always = host expect - actual transferred
+  p_msc->csw.data_residue = p_msc->cbw.total_bytes - p_msc->xferred_len;
+
+  p_msc->stage = MSC_STAGE_STATUS_SENT;
+  return usbd_edpt_xfer(rhport, p_msc->ep_in , (uint8_t*) &p_msc->csw, sizeof(msc_csw_t));
+}
+
+static inline bool prepare_cbw(uint8_t rhport, mscd_interface_t* p_msc)
+{
+  p_msc->stage = MSC_STAGE_CMD;
+  return usbd_edpt_xfer(rhport, p_msc->ep_out, (uint8_t*) &p_msc->cbw, sizeof(msc_cbw_t));
+}
+
+static void fail_scsi_op(uint8_t rhport, mscd_interface_t* p_msc, uint8_t status)
+{
+  msc_cbw_t const * p_cbw = &p_msc->cbw;
+  msc_csw_t       * p_csw = &p_msc->csw;
+
+  p_csw->status       = status;
+  p_csw->data_residue = p_msc->cbw.total_bytes - p_msc->xferred_len;
+  p_msc->stage        = MSC_STAGE_STATUS;
+
+  // failed but sense key is not set: default to Illegal Request
+  if ( p_msc->sense_key == 0 ) tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);
+
+  // If there is data stage and not yet complete, stall it
+  if ( p_cbw->total_bytes && p_csw->data_residue )
+  {
+    if ( is_data_in(p_cbw->dir) )
+    {
+      usbd_edpt_stall(rhport, p_msc->ep_in);
+    }
+    else
+    {
+      usbd_edpt_stall(rhport, p_msc->ep_out);
+    }
+  }
+}
+
+static inline uint32_t rdwr10_get_lba(uint8_t const command[])
+{
+  // use offsetof to avoid pointer to the odd/unaligned address
+  uint32_t const lba = tu_unaligned_read32(command + offsetof(scsi_write10_t, lba));
+
+  // lba is in Big Endian
+  return tu_ntohl(lba);
+}
+
+static inline uint16_t rdwr10_get_blockcount(msc_cbw_t const* cbw)
+{
+  uint16_t const block_count = tu_unaligned_read16(cbw->command + offsetof(scsi_write10_t, block_count));
+  return tu_ntohs(block_count);
+}
+
+static inline uint16_t rdwr10_get_blocksize(msc_cbw_t const* cbw)
+{
+  // first extract block count in the command
+  uint16_t const block_count = rdwr10_get_blockcount(cbw);
+
+  // invalid block count
+  if (block_count == 0) return 0;
+
+  return (uint16_t) (cbw->total_bytes / block_count);
+}
+
+uint8_t rdwr10_validate_cmd(msc_cbw_t const* cbw)
+{
+  uint8_t status = MSC_CSW_STATUS_PASSED;
+  uint16_t const block_count = rdwr10_get_blockcount(cbw);
+
+  if ( cbw->total_bytes == 0 )
+  {
+    if ( block_count )
+    {
+      TU_LOG(MSC_DEBUG, "  SCSI case 2 (Hn < Di) or case 3 (Hn < Do) \r\n");
+      status = MSC_CSW_STATUS_PHASE_ERROR;
+    }else
+    {
+      // no data transfer, only exist in complaint test suite
+    }
+  }else
+  {
+    if ( SCSI_CMD_READ_10 == cbw->command[0] && !is_data_in(cbw->dir) )
+    {
+      TU_LOG(MSC_DEBUG, "  SCSI case 10 (Ho <> Di)\r\n");
+      status = MSC_CSW_STATUS_PHASE_ERROR;
+    }
+    else if ( SCSI_CMD_WRITE_10 == cbw->command[0] && is_data_in(cbw->dir) )
+    {
+      TU_LOG(MSC_DEBUG, "  SCSI case 8 (Hi <> Do)\r\n");
+      status = MSC_CSW_STATUS_PHASE_ERROR;
+    }
+    else if ( 0 == block_count )
+    {
+      TU_LOG(MSC_DEBUG, "  SCSI case 4 Hi > Dn (READ10) or case 9 Ho > Dn (WRITE10) \r\n");
+      status =  MSC_CSW_STATUS_FAILED;
+    }
+    else if ( cbw->total_bytes / block_count == 0 )
+    {
+      TU_LOG(MSC_DEBUG, " Computed block size = 0. SCSI case 7 Hi < Di (READ10) or case 13 Ho < Do (WRIT10)\r\n");
+      status = MSC_CSW_STATUS_PHASE_ERROR;
+    }
+  }
+
+  return status;
+}
+
+//--------------------------------------------------------------------+
+// Debug
+//--------------------------------------------------------------------+
+#if CFG_TUSB_DEBUG >= 2
+
+TU_ATTR_UNUSED static tu_lookup_entry_t const _msc_scsi_cmd_lookup[] =
+{
+  { .key = SCSI_CMD_TEST_UNIT_READY              , .data = "Test Unit Ready" },
+  { .key = SCSI_CMD_INQUIRY                      , .data = "Inquiry" },
+  { .key = SCSI_CMD_MODE_SELECT_6                , .data = "Mode_Select 6" },
+  { .key = SCSI_CMD_MODE_SENSE_6                 , .data = "Mode_Sense 6" },
+  { .key = SCSI_CMD_START_STOP_UNIT              , .data = "Start Stop Unit" },
+  { .key = SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL , .data = "Prevent/Allow Medium Removal" },
+  { .key = SCSI_CMD_READ_CAPACITY_10             , .data = "Read Capacity10" },
+  { .key = SCSI_CMD_REQUEST_SENSE                , .data = "Request Sense" },
+  { .key = SCSI_CMD_READ_FORMAT_CAPACITY         , .data = "Read Format Capacity" },
+  { .key = SCSI_CMD_READ_10                      , .data = "Read10" },
+  { .key = SCSI_CMD_WRITE_10                     , .data = "Write10" }
+};
+
+TU_ATTR_UNUSED static tu_lookup_table_t const _msc_scsi_cmd_table =
+{
+  .count = TU_ARRAY_SIZE(_msc_scsi_cmd_lookup),
+  .items = _msc_scsi_cmd_lookup
+};
+
+#endif
+
+//--------------------------------------------------------------------+
+// APPLICATION API
+//--------------------------------------------------------------------+
+bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, uint8_t add_sense_qualifier)
+{
+  (void) lun;
+
+  _mscd_itf.sense_key           = sense_key;
+  _mscd_itf.add_sense_code      = add_sense_code;
+  _mscd_itf.add_sense_qualifier = add_sense_qualifier;
+
+  return true;
+}
+
+static inline void set_sense_medium_not_present(uint8_t lun)
+{
+  // default sense is NOT READY, MEDIUM NOT PRESENT
+  tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3A, 0x00);
+}
+
+//--------------------------------------------------------------------+
+// USBD Driver API
+//--------------------------------------------------------------------+
+void mscd_init(void)
+{
+  tu_memclr(&_mscd_itf, sizeof(mscd_interface_t));
+}
+
+void mscd_reset(uint8_t rhport)
+{
+  (void) rhport;
+  tu_memclr(&_mscd_itf, sizeof(mscd_interface_t));
+}
+
+uint16_t mscd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len)
+{
+  // only support SCSI's BOT protocol
+  TU_VERIFY(TUSB_CLASS_MSC    == itf_desc->bInterfaceClass &&
+            MSC_SUBCLASS_SCSI == itf_desc->bInterfaceSubClass &&
+            MSC_PROTOCOL_BOT  == itf_desc->bInterfaceProtocol, 0);
+
+  // msc driver length is fixed
+  uint16_t const drv_len = sizeof(tusb_desc_interface_t) + 2*sizeof(tusb_desc_endpoint_t);
+
+  // Max length must be at least 1 interface + 2 endpoints
+  TU_ASSERT(max_len >= drv_len, 0);
+
+  mscd_interface_t * p_msc = &_mscd_itf;
+  p_msc->itf_num = itf_desc->bInterfaceNumber;
+
+  // Open endpoint pair
+  TU_ASSERT( usbd_open_edpt_pair(rhport, tu_desc_next(itf_desc), 2, TUSB_XFER_BULK, &p_msc->ep_out, &p_msc->ep_in), 0 );
+
+  // Prepare for Command Block Wrapper
+  TU_ASSERT( prepare_cbw(rhport, p_msc), drv_len);
+
+  return drv_len;
+}
+
+static void proc_bot_reset(mscd_interface_t* p_msc)
+{
+  p_msc->stage       = MSC_STAGE_CMD;
+  p_msc->total_len   = 0;
+  p_msc->xferred_len = 0;
+
+  p_msc->sense_key           = 0;
+  p_msc->add_sense_code      = 0;
+  p_msc->add_sense_qualifier = 0;
+}
+
+// Invoked when a control transfer occurred on an interface of this class
+// Driver response accordingly to the request and the transfer stage (setup/data/ack)
+// return false to stall control endpoint (e.g unsupported request)
+bool mscd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request)
+{
+  // nothing to do with DATA & ACK stage
+  if (stage != CONTROL_STAGE_SETUP) return true;
+
+  mscd_interface_t* p_msc = &_mscd_itf;
+
+  // Clear Endpoint Feature (stall) for recovery
+  if ( TUSB_REQ_TYPE_STANDARD     == request->bmRequestType_bit.type      &&
+       TUSB_REQ_RCPT_ENDPOINT     == request->bmRequestType_bit.recipient &&
+       TUSB_REQ_CLEAR_FEATURE     == request->bRequest                    &&
+       TUSB_REQ_FEATURE_EDPT_HALT == request->wValue )
+  {
+    uint8_t const ep_addr = tu_u16_low(request->wIndex);
+
+    if ( p_msc->stage == MSC_STAGE_NEED_RESET )
+    {
+      // reset recovery is required to recover from this stage
+      // Clear Stall request cannot resolve this -> continue to stall endpoint
+      usbd_edpt_stall(rhport, ep_addr);
+    }
+    else
+    {
+      if ( ep_addr == p_msc->ep_in )
+      {
+        if ( p_msc->stage == MSC_STAGE_STATUS )
+        {
+          // resume sending SCSI status if we are in this stage previously before stalled
+          TU_ASSERT( send_csw(rhport, p_msc) );
+        }
+      }
+      else if ( ep_addr == p_msc->ep_out )
+      {
+        if ( p_msc->stage == MSC_STAGE_CMD )
+        {
+          // part of reset recovery (probably due to invalid CBW) -> prepare for new command
+          // Note: skip if already queued previously
+          if ( usbd_edpt_ready(rhport, p_msc->ep_out) )
+          {
+            TU_ASSERT( prepare_cbw(rhport, p_msc) );
+          }
+        }
+      }
+    }
+
+    return true;
+  }
+
+  // From this point only handle class request only
+  TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS);
+
+  switch ( request->bRequest )
+  {
+    case MSC_REQ_RESET:
+      TU_LOG(MSC_DEBUG, "  MSC BOT Reset\r\n");
+      TU_VERIFY(request->wValue == 0 && request->wLength == 0);
+
+      // driver state reset
+      proc_bot_reset(p_msc);
+
+      tud_control_status(rhport, request);
+    break;
+
+    case MSC_REQ_GET_MAX_LUN:
+    {
+      TU_LOG(MSC_DEBUG, "  MSC Get Max Lun\r\n");
+      TU_VERIFY(request->wValue == 0 && request->wLength == 1);
+
+      uint8_t maxlun = 1;
+      if (tud_msc_get_maxlun_cb) maxlun = tud_msc_get_maxlun_cb();
+      TU_VERIFY(maxlun);
+
+      // MAX LUN is minus 1 by specs
+      maxlun--;
+
+      tud_control_xfer(rhport, request, &maxlun, 1);
+    }
+    break;
+
+    default: return false; // stall unsupported request
+  }
+
+  return true;
+}
+
+bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes)
+{
+  (void) event;
+
+  mscd_interface_t* p_msc = &_mscd_itf;
+  msc_cbw_t const * p_cbw = &p_msc->cbw;
+  msc_csw_t       * p_csw = &p_msc->csw;
+
+  switch (p_msc->stage)
+  {
+    case MSC_STAGE_CMD:
+      //------------- new CBW received -------------//
+      // Complete IN while waiting for CMD is usually Status of previous SCSI op, ignore it
+      if(ep_addr != p_msc->ep_out) return true;
+
+      if ( !(xferred_bytes == sizeof(msc_cbw_t) && p_cbw->signature == MSC_CBW_SIGNATURE) )
+      {
+        TU_LOG(MSC_DEBUG, "  SCSI CBW is not valid\r\n");
+
+        // BOT 6.6.1 If CBW is not valid stall both endpoints until reset recovery
+        p_msc->stage = MSC_STAGE_NEED_RESET;
+
+        // invalid CBW stall both endpoints
+        usbd_edpt_stall(rhport, p_msc->ep_in);
+        usbd_edpt_stall(rhport, p_msc->ep_out);
+
+        return false;
+      }
+
+      TU_LOG(MSC_DEBUG, "  SCSI Command [Lun%u]: %s\r\n", p_cbw->lun, tu_lookup_find(&_msc_scsi_cmd_table, p_cbw->command[0]));
+      //TU_LOG_MEM(MSC_DEBUG, p_cbw, xferred_bytes, 2);
+
+      p_csw->signature    = MSC_CSW_SIGNATURE;
+      p_csw->tag          = p_cbw->tag;
+      p_csw->data_residue = 0;
+      p_csw->status       = MSC_CSW_STATUS_PASSED;
+
+      /*------------- Parse command and prepare DATA -------------*/
+      p_msc->stage = MSC_STAGE_DATA;
+      p_msc->total_len = p_cbw->total_bytes;
+      p_msc->xferred_len = 0;
+
+      // Read10 or Write10
+      if ( (SCSI_CMD_READ_10 == p_cbw->command[0]) || (SCSI_CMD_WRITE_10 == p_cbw->command[0]) )
+      {
+        uint8_t const status = rdwr10_validate_cmd(p_cbw);
+
+        if ( status != MSC_CSW_STATUS_PASSED)
+        {
+          fail_scsi_op(rhport, p_msc, status);
+        }else if ( p_cbw->total_bytes )
+        {
+          if (SCSI_CMD_READ_10 == p_cbw->command[0])
+          {
+            proc_read10_cmd(rhport, p_msc);
+          }else
+          {
+            proc_write10_cmd(rhport, p_msc);
+          }
+        }else
+        {
+          // no data transfer, only exist in complaint test suite
+          p_msc->stage = MSC_STAGE_STATUS;
+        }
+      }
+      else
+      {
+        // For other SCSI commands
+        // 1. OUT : queue transfer (invoke app callback after done)
+        // 2. IN & Zero: Process if is built-in, else Invoke app callback. Skip DATA if zero length
+        if ( (p_cbw->total_bytes > 0 ) && !is_data_in(p_cbw->dir) )
+        {
+          if (p_cbw->total_bytes > sizeof(_mscd_buf))
+          {
+            TU_LOG(MSC_DEBUG, "  SCSI reject non READ10/WRITE10 with large data\r\n");
+            fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+          }else
+          {
+            // Didn't check for case 9 (Ho > Dn), which requires examining scsi command first
+            // but it is OK to just receive data then responded with failed status
+            TU_ASSERT( usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_buf, (uint16_t) p_msc->total_len) );
+          }
+        }else
+        {
+          // First process if it is a built-in commands
+          int32_t resplen = proc_builtin_scsi(p_cbw->lun, p_cbw->command, _mscd_buf, sizeof(_mscd_buf));
+
+          // Invoke user callback if not built-in
+          if ( (resplen < 0) && (p_msc->sense_key == 0) )
+          {
+            resplen = tud_msc_scsi_cb(p_cbw->lun, p_cbw->command, _mscd_buf, (uint16_t) p_msc->total_len);
+          }
+
+          if ( resplen < 0 )
+          {
+            // unsupported command
+            TU_LOG(MSC_DEBUG, "  SCSI unsupported or failed command\r\n");
+            fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+          }
+          else if (resplen == 0)
+          {
+            if (p_cbw->total_bytes)
+            {
+              // 6.7 The 13 Cases: case 4 (Hi > Dn)
+              // TU_LOG(MSC_DEBUG, "  SCSI case 4 (Hi > Dn): %lu\r\n", p_cbw->total_bytes);
+              fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+            }else
+            {
+              // case 1 Hn = Dn: all good
+              p_msc->stage = MSC_STAGE_STATUS;
+            }
+          }
+          else
+          {
+            if ( p_cbw->total_bytes == 0 )
+            {
+              // 6.7 The 13 Cases: case 2 (Hn < Di)
+              // TU_LOG(MSC_DEBUG, "  SCSI case 2 (Hn < Di): %lu\r\n", p_cbw->total_bytes);
+              fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+            }else
+            {
+              // cannot return more than host expect
+              p_msc->total_len = tu_min32((uint32_t) resplen, p_cbw->total_bytes);
+              TU_ASSERT( usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_buf, (uint16_t) p_msc->total_len) );
+            }
+          }
+        }
+      }
+    break;
+
+    case MSC_STAGE_DATA:
+      TU_LOG(MSC_DEBUG, "  SCSI Data [Lun%u]\r\n", p_cbw->lun);
+      //TU_LOG_MEM(MSC_DEBUG, _mscd_buf, xferred_bytes, 2);
+
+      if (SCSI_CMD_READ_10 == p_cbw->command[0])
+      {
+        p_msc->xferred_len += xferred_bytes;
+
+        if ( p_msc->xferred_len >= p_msc->total_len )
+        {
+          // Data Stage is complete
+          p_msc->stage = MSC_STAGE_STATUS;
+        }else
+        {
+          proc_read10_cmd(rhport, p_msc);
+        }
+      }
+      else if (SCSI_CMD_WRITE_10 == p_cbw->command[0])
+      {
+        proc_write10_new_data(rhport, p_msc, xferred_bytes);
+      }
+      else
+      {
+        p_msc->xferred_len += xferred_bytes;
+
+        // OUT transfer, invoke callback if needed
+        if ( !is_data_in(p_cbw->dir) )
+        {
+          int32_t cb_result = tud_msc_scsi_cb(p_cbw->lun, p_cbw->command, _mscd_buf, (uint16_t) p_msc->total_len);
+
+          if ( cb_result < 0 )
+          {
+            // unsupported command
+            TU_LOG(MSC_DEBUG, "  SCSI unsupported command\r\n");
+            fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+          }else
+          {
+            // TODO haven't implement this scenario any further yet
+          }
+        }
+
+        if ( p_msc->xferred_len >= p_msc->total_len )
+        {
+          // Data Stage is complete
+          p_msc->stage = MSC_STAGE_STATUS;
+        }
+        else
+        {
+          // This scenario with command that take more than one transfer is already rejected at Command stage
+          TU_BREAKPOINT();
+        }
+      }
+    break;
+
+    case MSC_STAGE_STATUS:
+      // processed immediately after this switch, supposedly to be empty
+    break;
+
+    case MSC_STAGE_STATUS_SENT:
+      // Wait for the Status phase to complete
+      if( (ep_addr == p_msc->ep_in) && (xferred_bytes == sizeof(msc_csw_t)) )
+      {
+        TU_LOG(MSC_DEBUG, "  SCSI Status [Lun%u] = %u\r\n", p_cbw->lun, p_csw->status);
+        // TU_LOG_MEM(MSC_DEBUG, p_csw, xferred_bytes, 2);
+
+        // Invoke complete callback if defined
+        // Note: There is racing issue with samd51 + qspi flash testing with arduino
+        // if complete_cb() is invoked after queuing the status.
+        switch(p_cbw->command[0])
+        {
+          case SCSI_CMD_READ_10:
+            if ( tud_msc_read10_complete_cb ) tud_msc_read10_complete_cb(p_cbw->lun);
+          break;
+
+          case SCSI_CMD_WRITE_10:
+            if ( tud_msc_write10_complete_cb ) tud_msc_write10_complete_cb(p_cbw->lun);
+          break;
+
+          default:
+            if ( tud_msc_scsi_complete_cb ) tud_msc_scsi_complete_cb(p_cbw->lun, p_cbw->command);
+          break;
+        }
+
+        TU_ASSERT( prepare_cbw(rhport, p_msc) );
+      }else
+      {
+        // Any xfer ended here is consider unknown error, ignore it
+        TU_LOG1("  Warning expect SCSI Status but received unknown data\r\n");
+      }
+    break;
+
+    default : break;
+  }
+
+  if ( p_msc->stage == MSC_STAGE_STATUS )
+  {
+    // skip status if epin is currently stalled, will do it when received Clear Stall request
+    if ( !usbd_edpt_stalled(rhport,  p_msc->ep_in) )
+    {
+      if ( (p_cbw->total_bytes > p_msc->xferred_len) && is_data_in(p_cbw->dir) )
+      {
+        // 6.7 The 13 Cases: case 5 (Hi > Di): STALL before status
+        // TU_LOG(MSC_DEBUG, "  SCSI case 5 (Hi > Di): %lu > %lu\r\n", p_cbw->total_bytes, p_msc->xferred_len);
+        usbd_edpt_stall(rhport, p_msc->ep_in);
+      }else
+      {
+        TU_ASSERT( send_csw(rhport, p_msc) );
+      }
+    }
+
+    #if TU_CHECK_MCU(OPT_MCU_CXD56)
+    // WORKAROUND: cxd56 has its own nuttx usb stack which does not forward Set/ClearFeature(Endpoint) to DCD.
+    // There is no way for us to know when EP is un-stall, therefore we will unconditionally un-stall here and
+    // hope everything will work
+    if ( usbd_edpt_stalled(rhport, p_msc->ep_in) )
+    {
+      usbd_edpt_clear_stall(rhport, p_msc->ep_in);
+      send_csw(rhport, p_msc);
+    }
+    #endif
+  }
+
+  return true;
+}
+
+/*------------------------------------------------------------------*/
+/* SCSI Command Process
+ *------------------------------------------------------------------*/
+
+// return response's length (copied to buffer). Negative if it is not an built-in command or indicate Failed status (CSW)
+// In case of a failed status, sense key must be set for reason of failure
+static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_t* buffer, uint32_t bufsize)
+{
+  (void) bufsize; // TODO refractor later
+  int32_t resplen;
+
+  mscd_interface_t* p_msc = &_mscd_itf;
+
+  switch ( scsi_cmd[0] )
+  {
+    case SCSI_CMD_TEST_UNIT_READY:
+      resplen = 0;
+      if ( !tud_msc_test_unit_ready_cb(lun) )
+      {
+        // Failed status response
+        resplen = - 1;
+
+        // set default sense if not set by callback
+        if ( p_msc->sense_key == 0 ) set_sense_medium_not_present(lun);
+      }
+    break;
+
+    case SCSI_CMD_START_STOP_UNIT:
+      resplen = 0;
+
+      if (tud_msc_start_stop_cb)
+      {
+        scsi_start_stop_unit_t const * start_stop = (scsi_start_stop_unit_t const *) scsi_cmd;
+        if ( !tud_msc_start_stop_cb(lun, start_stop->power_condition, start_stop->start, start_stop->load_eject) )
+        {
+          // Failed status response
+          resplen = - 1;
+
+          // set default sense if not set by callback
+          if ( p_msc->sense_key == 0 ) set_sense_medium_not_present(lun);
+        }
+      }
+    break;
+
+    case SCSI_CMD_READ_CAPACITY_10:
+    {
+      uint32_t block_count;
+      uint32_t block_size;
+      uint16_t block_size_u16;
+
+      tud_msc_capacity_cb(lun, &block_count, &block_size_u16);
+      block_size = (uint32_t) block_size_u16;
+
+      // Invalid block size/count from callback, possibly unit is not ready
+      // stall this request, set sense key to NOT READY
+      if (block_count == 0 || block_size == 0)
+      {
+        resplen = -1;
+
+        // set default sense if not set by callback
+        if ( p_msc->sense_key == 0 ) set_sense_medium_not_present(lun);
+      }else
+      {
+        scsi_read_capacity10_resp_t read_capa10;
+
+        read_capa10.last_lba   = tu_htonl(block_count-1);
+        read_capa10.block_size = tu_htonl(block_size);
+
+        resplen = sizeof(read_capa10);
+        memcpy(buffer, &read_capa10, (size_t) resplen);
+      }
+    }
+    break;
+
+    case SCSI_CMD_READ_FORMAT_CAPACITY:
+    {
+      scsi_read_format_capacity_data_t read_fmt_capa =
+      {
+          .list_length     = 8,
+          .block_num       = 0,
+          .descriptor_type = 2, // formatted media
+          .block_size_u16  = 0
+      };
+
+      uint32_t block_count;
+      uint16_t block_size;
+
+      tud_msc_capacity_cb(lun, &block_count, &block_size);
+
+      // Invalid block size/count from callback, possibly unit is not ready
+      // stall this request, set sense key to NOT READY
+      if (block_count == 0 || block_size == 0)
+      {
+        resplen = -1;
+
+        // set default sense if not set by callback
+        if ( p_msc->sense_key == 0 ) set_sense_medium_not_present(lun);
+      }else
+      {
+        read_fmt_capa.block_num = tu_htonl(block_count);
+        read_fmt_capa.block_size_u16 = tu_htons(block_size);
+
+        resplen = sizeof(read_fmt_capa);
+        memcpy(buffer, &read_fmt_capa, (size_t) resplen);
+      }
+    }
+    break;
+
+    case SCSI_CMD_INQUIRY:
+    {
+      scsi_inquiry_resp_t inquiry_rsp =
+      {
+          .is_removable         = 1,
+          .version              = 2,
+          .response_data_format = 2,
+          .additional_length    = sizeof(scsi_inquiry_resp_t) - 5,
+      };
+
+      // vendor_id, product_id, product_rev is space padded string
+      memset(inquiry_rsp.vendor_id  , ' ', sizeof(inquiry_rsp.vendor_id));
+      memset(inquiry_rsp.product_id , ' ', sizeof(inquiry_rsp.product_id));
+      memset(inquiry_rsp.product_rev, ' ', sizeof(inquiry_rsp.product_rev));
+
+      tud_msc_inquiry_cb(lun, inquiry_rsp.vendor_id, inquiry_rsp.product_id, inquiry_rsp.product_rev);
+
+      resplen = sizeof(inquiry_rsp);
+      memcpy(buffer, &inquiry_rsp, (size_t) resplen);
+    }
+    break;
+
+    case SCSI_CMD_MODE_SENSE_6:
+    {
+      scsi_mode_sense6_resp_t mode_resp =
+      {
+          .data_len             = 3,
+          .medium_type          = 0,
+          .write_protected      = false,
+          .reserved             = 0,
+          .block_descriptor_len = 0  // no block descriptor are included
+      };
+
+      bool writable = true;
+      if ( tud_msc_is_writable_cb )
+      {
+        writable = tud_msc_is_writable_cb(lun);
+      }
+
+      mode_resp.write_protected = !writable;
+
+      resplen = sizeof(mode_resp);
+      memcpy(buffer, &mode_resp, (size_t) resplen);
+    }
+    break;
+
+    case SCSI_CMD_REQUEST_SENSE:
+    {
+      scsi_sense_fixed_resp_t sense_rsp =
+      {
+          .response_code = 0x70, // current, fixed format
+          .valid         = 1
+      };
+
+      sense_rsp.add_sense_len       = sizeof(scsi_sense_fixed_resp_t) - 8;
+      sense_rsp.sense_key           = (uint8_t) (p_msc->sense_key & 0x0F);
+      sense_rsp.add_sense_code      = p_msc->add_sense_code;
+      sense_rsp.add_sense_qualifier = p_msc->add_sense_qualifier;
+
+      resplen = sizeof(sense_rsp);
+      memcpy(buffer, &sense_rsp, (size_t) resplen);
+
+      // request sense callback could overwrite the sense data
+      if (tud_msc_request_sense_cb)
+      {
+        resplen = tud_msc_request_sense_cb(lun, buffer, (uint16_t) bufsize);
+      }
+
+      // Clear sense data after copy
+      tud_msc_set_sense(lun, 0, 0, 0);
+    }
+    break;
+
+    default: resplen = -1; break;
+  }
+
+  return resplen;
+}
+
+static void proc_read10_cmd(uint8_t rhport, mscd_interface_t* p_msc)
+{
+  msc_cbw_t const * p_cbw = &p_msc->cbw;
+
+  // block size already verified not zero
+  uint16_t const block_sz = rdwr10_get_blocksize(p_cbw);
+
+  // Adjust lba with transferred bytes
+  uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz);
+
+  // remaining bytes capped at class buffer
+  int32_t nbytes = (int32_t) tu_min32(sizeof(_mscd_buf), p_cbw->total_bytes-p_msc->xferred_len);
+
+  // Application can consume smaller bytes
+  uint32_t const offset = p_msc->xferred_len % block_sz;
+  nbytes = tud_msc_read10_cb(p_cbw->lun, lba, offset, _mscd_buf, (uint32_t) nbytes);
+
+  if ( nbytes < 0 )
+  {
+    // negative means error -> endpoint is stalled & status in CSW set to failed
+    TU_LOG(MSC_DEBUG, "  tud_msc_read10_cb() return -1\r\n");
+
+    // set sense
+    set_sense_medium_not_present(p_cbw->lun);
+
+    fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+  }
+  else if ( nbytes == 0 )
+  {
+    // zero means not ready -> simulate an transfer complete so that this driver callback will fired again
+    dcd_event_xfer_complete(rhport, p_msc->ep_in, 0, XFER_RESULT_SUCCESS, false);
+  }
+  else
+  {
+    TU_ASSERT( usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_buf, (uint16_t) nbytes), );
+  }
+}
+
+static void proc_write10_cmd(uint8_t rhport, mscd_interface_t* p_msc)
+{
+  msc_cbw_t const * p_cbw = &p_msc->cbw;
+  bool writable = true;
+
+  if ( tud_msc_is_writable_cb )
+  {
+    writable = tud_msc_is_writable_cb(p_cbw->lun);
+  }
+
+  if ( !writable )
+  {
+    // Not writable, complete this SCSI op with error
+    // Sense = Write protected
+    tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_DATA_PROTECT, 0x27, 0x00);
+    fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+    return;
+  }
+
+  // remaining bytes capped at class buffer
+  uint16_t nbytes = (uint16_t) tu_min32(sizeof(_mscd_buf), p_cbw->total_bytes-p_msc->xferred_len);
+
+  // Write10 callback will be called later when usb transfer complete
+  TU_ASSERT( usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_buf, nbytes), );
+}
+
+// process new data arrived from WRITE10
+static void proc_write10_new_data(uint8_t rhport, mscd_interface_t* p_msc, uint32_t xferred_bytes)
+{
+  msc_cbw_t const * p_cbw = &p_msc->cbw;
+
+  // block size already verified not zero
+  uint16_t const block_sz = rdwr10_get_blocksize(p_cbw);
+
+  // Adjust lba with transferred bytes
+  uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz);
+
+  // Invoke callback to consume new data
+  uint32_t const offset = p_msc->xferred_len % block_sz;
+  int32_t nbytes = tud_msc_write10_cb(p_cbw->lun, lba, offset, _mscd_buf, xferred_bytes);
+
+  if ( nbytes < 0 )
+  {
+    // negative means error -> failed this scsi op
+    TU_LOG(MSC_DEBUG, "  tud_msc_write10_cb() return -1\r\n");
+
+    // update actual byte before failed
+    p_msc->xferred_len += xferred_bytes;
+
+    // Set sense
+    set_sense_medium_not_present(p_cbw->lun);
+
+    fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+  }else
+  {
+    // Application consume less than what we got (including zero)
+    if ( (uint32_t) nbytes < xferred_bytes )
+    {
+      uint32_t const left_over = xferred_bytes - (uint32_t) nbytes;
+      if ( nbytes > 0 )
+      {
+        p_msc->xferred_len += (uint16_t) nbytes;
+        memmove(_mscd_buf, _mscd_buf+nbytes, left_over);
+      }
+
+      // simulate an transfer complete with adjusted parameters --> callback will be invoked with adjusted parameter
+      dcd_event_xfer_complete(rhport, p_msc->ep_out, left_over, XFER_RESULT_SUCCESS, false);
+    }
+    else
+    {
+      // Application consume all bytes in our buffer
+      p_msc->xferred_len += xferred_bytes;
+
+      if ( p_msc->xferred_len >= p_msc->total_len )
+      {
+        // Data Stage is complete
+        p_msc->stage = MSC_STAGE_STATUS;
+      }else
+      {
+        // prepare to receive more data from host
+        proc_write10_cmd(rhport, p_msc);
+      }
+    }
+  }
+}
+
+#endif

+ 162 - 0
lib/ZuluSCSI_platform_RP2040/msc_device.h

@@ -0,0 +1,162 @@
+/* 
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef _TUSB_MSC_DEVICE_H_
+#define _TUSB_MSC_DEVICE_H_
+
+#include "common/tusb_common.h"
+#include "msc.h"
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Class Driver Configuration
+//--------------------------------------------------------------------+
+
+#if !defined(CFG_TUD_MSC_EP_BUFSIZE) & defined(CFG_TUD_MSC_BUFSIZE)
+  // TODO warn user to use new name later on
+  // #warning CFG_TUD_MSC_BUFSIZE is renamed to CFG_TUD_MSC_EP_BUFSIZE, please update to use the new name
+  #define CFG_TUD_MSC_EP_BUFSIZE  CFG_TUD_MSC_BUFSIZE
+#endif
+
+#ifndef CFG_TUD_MSC_EP_BUFSIZE
+  #error CFG_TUD_MSC_EP_BUFSIZE must be defined, value of a block size should work well, the more the better
+#endif
+
+TU_VERIFY_STATIC(CFG_TUD_MSC_EP_BUFSIZE < UINT16_MAX, "Size is not correct");
+
+//--------------------------------------------------------------------+
+// Application API
+//--------------------------------------------------------------------+
+
+// Set SCSI sense response
+bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, uint8_t add_sense_qualifier);
+
+//--------------------------------------------------------------------+
+// Application Callbacks (WEAK is optional)
+//--------------------------------------------------------------------+
+
+// Invoked when received SCSI READ10 command
+// - Address = lba * BLOCK_SIZE + offset
+//   - offset is only needed if CFG_TUD_MSC_EP_BUFSIZE is smaller than BLOCK_SIZE.
+//
+// - Application fill the buffer (up to bufsize) with address contents and return number of read byte. If
+//   - read < bufsize : These bytes are transferred first and callback invoked again for remaining data.
+//
+//   - read == 0      : Indicate application is not ready yet e.g disk I/O busy.
+//                      Callback invoked again with the same parameters later on.
+//
+//   - read < 0       : Indicate application error e.g invalid address. This request will be STALLed
+//                      and return failed status in command status wrapper phase.
+int32_t tud_msc_read10_cb (uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize);
+
+// Invoked when received SCSI WRITE10 command
+// - Address = lba * BLOCK_SIZE + offset
+//   - offset is only needed if CFG_TUD_MSC_EP_BUFSIZE is smaller than BLOCK_SIZE.
+//
+// - Application write data from buffer to address contents (up to bufsize) and return number of written byte. If
+//   - write < bufsize : callback invoked again with remaining data later on.
+//
+//   - write == 0      : Indicate application is not ready yet e.g disk I/O busy.
+//                       Callback invoked again with the same parameters later on.
+//
+//   - write < 0       : Indicate application error e.g invalid address. This request will be STALLed
+//                       and return failed status in command status wrapper phase.
+//
+// TODO change buffer to const uint8_t*
+int32_t tud_msc_write10_cb (uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize);
+
+// Invoked when received SCSI_CMD_INQUIRY
+// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
+void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]);
+
+// Invoked when received Test Unit Ready command.
+// return true allowing host to read/write this LUN e.g SD card inserted
+bool tud_msc_test_unit_ready_cb(uint8_t lun);
+
+// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size
+// Application update block count and block size
+void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size);
+
+/**
+ * Invoked when received an SCSI command not in built-in list below.
+ * - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, TEST_UNIT_READY, START_STOP_UNIT, MODE_SENSE6, REQUEST_SENSE
+ * - READ10 and WRITE10 has their own callbacks
+ *
+ * \param[in]   lun         Logical unit number
+ * \param[in]   scsi_cmd    SCSI command contents which application must examine to response accordingly
+ * \param[out]  buffer      Buffer for SCSI Data Stage.
+ *                            - For INPUT: application must fill this with response.
+ *                            - For OUTPUT it holds the Data from host
+ * \param[in]   bufsize     Buffer's length.
+ *
+ * \return      Actual bytes processed, can be zero for no-data command.
+ * \retval      negative    Indicate error e.g unsupported command, tinyusb will \b STALL the corresponding
+ *                          endpoint and return failed status in command status wrapper phase.
+ */
+int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize);
+
+/*------------- Optional callbacks -------------*/
+
+// Invoked when received GET_MAX_LUN request, required for multiple LUNs implementation
+TU_ATTR_WEAK uint8_t tud_msc_get_maxlun_cb(void);
+
+// Invoked when received Start Stop Unit command
+// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage
+// - Start = 1 : active mode, if load_eject = 1 : load disk storage
+TU_ATTR_WEAK bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject);
+
+// Invoked when received REQUEST_SENSE
+TU_ATTR_WEAK int32_t tud_msc_request_sense_cb(uint8_t lun, void* buffer, uint16_t bufsize);
+
+// Invoked when Read10 command is complete
+TU_ATTR_WEAK void tud_msc_read10_complete_cb(uint8_t lun);
+
+// Invoke when Write10 command is complete, can be used to flush flash caching
+TU_ATTR_WEAK void tud_msc_write10_complete_cb(uint8_t lun);
+
+// Invoked when command in tud_msc_scsi_cb is complete
+TU_ATTR_WEAK void tud_msc_scsi_complete_cb(uint8_t lun, uint8_t const scsi_cmd[16]);
+
+// Invoked to check if device is writable as part of SCSI WRITE10
+TU_ATTR_WEAK bool tud_msc_is_writable_cb(uint8_t lun);
+
+//--------------------------------------------------------------------+
+// Internal Class Driver API
+//--------------------------------------------------------------------+
+void     mscd_init            (void);
+void     mscd_reset           (uint8_t rhport);
+uint16_t mscd_open            (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len);
+bool     mscd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * p_request);
+bool     mscd_xfer_cb         (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* _TUSB_MSC_DEVICE_H_ */

+ 98 - 0
lib/ZuluSCSI_platform_RP2040/tusb_config.h

@@ -0,0 +1,98 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef _TUSB_CONFIG_H_
+#define _TUSB_CONFIG_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+//--------------------------------------------------------------------
+// COMMON CONFIGURATION
+//--------------------------------------------------------------------
+
+#ifndef CFG_TUSB_MCU
+ #define CFG_TUSB_MCU             OPT_MCU_RP2040
+#endif
+
+#define CFG_TUSB_RHPORT0_MODE     OPT_MODE_DEVICE
+#define CFG_TUSB_OS               OPT_OS_PICO
+
+// CFG_TUSB_DEBUG is defined by compiler in DEBUG build
+#ifndef CFG_TUSB_DEBUG
+#define CFG_TUSB_DEBUG           0
+#endif
+
+/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
+ * Tinyusb use follows macros to declare transferring memory so that they can be put
+ * into those specific section.
+ * e.g
+ * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
+ * - CFG_TUSB_MEM_ALIGN   : __attribute__ ((aligned(4)))
+ */
+#ifndef CFG_TUSB_MEM_SECTION
+#define CFG_TUSB_MEM_SECTION
+#endif
+
+#ifndef CFG_TUSB_MEM_ALIGN
+#define CFG_TUSB_MEM_ALIGN          __attribute__ ((aligned(4)))
+#endif
+
+//--------------------------------------------------------------------
+// DEVICE CONFIGURATION
+//--------------------------------------------------------------------
+
+#ifndef CFG_TUD_ENDPOINT0_SIZE
+#define CFG_TUD_ENDPOINT0_SIZE    64
+#endif
+
+//------------- CLASS -------------//
+#define CFG_TUD_HID              (2)
+#define CFG_TUD_CDC              (1)
+#define CFG_TUD_MSC              (1)
+#define CFG_TUD_MIDI             (0)
+#define CFG_TUD_VENDOR           (0)
+
+#define CFG_TUD_CDC_RX_BUFSIZE  (256)
+#define CFG_TUD_CDC_TX_BUFSIZE  (256)
+
+
+#ifndef PLATFORM_MASS_STORAGE
+// standard value.
+    #define CFG_TUD_MSC_EP_BUFSIZE  (64)
+#else
+    // 4096 is a SD sector size and provides decent all around performance. min 512.
+    #define CFG_TUD_MSC_EP_BUFSIZE  (1024*4)
+#endif
+
+// HID buffer size Should be sufficient to hold ID (if any) + Data
+#define CFG_TUD_HID_EP_BUFSIZE  (64)
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* _TUSB_CONFIG_H_ */

+ 72 - 1
platformio.ini

@@ -1,7 +1,7 @@
 ; PlatformIO Project Configuration File https://docs.platformio.org/page/projectconf.html
 
 [platformio]
-default_envs = ZuluSCSIv1_0, ZuluSCSIv1_0_mini, ZuluSCSIv1_1_plus, ZuluSCSI_RP2040, ZuluSCSI_RP2040_Audio, ZuluSCSI_Pico, ZuluSCSI_Pico_DaynaPORT, ZuluSCSI_BS2
+default_envs = ZuluSCSIv1_0, ZuluSCSIv1_0_mini, ZuluSCSIv1_1_plus, ZuluSCSIv1_1_plus_MSC, ZuluSCSI_RP2040, ZuluSCSI_RP2040_Audio, ZuluSCSI_Pico, ZuluSCSI_Pico_DaynaPORT, ZuluSCSI_Pico_DaynaPORT_MSC, ZuluSCSI_RP2040_MSC, ZuluSCSI_BS2
 
 ; Example platform to serve as a base for porting efforts
 [env:template]
@@ -83,6 +83,12 @@ build_flags =
      -DENABLE_AUDIO_OUTPUT
      -DZULUSCSI_V1_1_plus
 
+[env:ZuluSCSIv1_1_plus_MSC]
+extends = env:ZuluSCSIv1_1_plus
+build_flags = 
+     ${env:ZuluSCSIv1_1_plus.build_flags}
+    -DPLATFORM_MASS_STORAGE
+
 ; ZuluSCSI RP2040 hardware platform, based on the Raspberry Pi foundation RP2040 microcontroller
 [env:ZuluSCSI_RP2040]
 platform = raspberrypi@1.9.0
@@ -128,6 +134,12 @@ build_flags =
     -DENABLE_AUDIO_OUTPUT
     -DLOGBUFSIZE=8192
 
+[env:ZuluSCSI_RP2040_MSC]
+extends = env:ZuluSCSI_RP2040
+build_flags =
+    ${env:ZuluSCSI_RP2040.build_flags}
+    -DPLATFORM_MASS_STORAGE
+
 ; Variant of RP2040 platform, based on Raspberry Pico board and a carrier PCB
 ; Part of the ZuluSCSI_RP2040 platform, but with different pins.
 [env:ZuluSCSI_Pico]
@@ -200,6 +212,65 @@ build_flags =
 	-DCYW43_USE_OTP_MAC=0
     -DPIO_FRAMEWORK_ARDUINO_NO_USB
 
+; Build for the ZuluSCSI Pico carrier board with a Pico-W
+; for SCSI DaynaPORT emulation
+[env:ZuluSCSI_Pico_DaynaPORT_MSC]
+platform = https://github.com/maxgerhardt/platform-raspberrypi.git
+platform_packages =
+    framework-arduinopico@https://github.com/rabbitholecomputing/arduino-pico.git#v3.6.0-DaynaPORT
+framework = arduino
+board = rpipicow
+board_build.core = earlephilhower
+; How much flash in bytes the bootloader and main app will be allocated
+; It is used as the starting point for a ROM image save in flash
+program_flash_allocation = 589824
+extra_scripts =
+    src/build_bootloader.py
+    lib/ZuluSCSI_platform_RP2040/process-linker-script.py
+ldscript_bootloader = lib/ZuluSCSI_platform_RP2040/rp2040_btldr.ld
+board_build.ldscript = ${BUILD_DIR}/rp2040.ld
+debug_tool = cmsis-dap
+debug_build_flags =
+    -O2 -ggdb -g3
+    -DLOGBUFSIZE=4096
+    -DPREFETCH_BUFFER_SIZE=0
+    -DSCSI2SD_BUFFER_SIZE=57344
+    ; This controls the depth NETWORK_PACKET_MAX_SIZE (1520 bytes)
+    ; For example a queue size of 10 would be 10 x 1520 = 30400 bytes
+    -DNETWORK_PACKET_QUEUE_SIZE=10
+
+lib_deps =
+    SdFat=https://github.com/rabbitholecomputing/SdFat#2.2.0-gpt
+    minIni
+    ZuluSCSI_platform_RP2040
+    SCSI2SD
+    CUEParser
+build_flags =
+    -O2 -Isrc
+    -Wall -Wno-sign-compare -Wno-ignored-qualifiers
+    -DSPI_DRIVER_SELECT=3
+    -DSD_CHIP_SELECT_MODE=2
+    -DENABLE_DEDICATED_SPI=1
+    -DHAS_SDIO_CLASS
+    -DUSE_ARDUINO=1
+    -DZULUSCSI_PICO
+    -DZULUSCSI_NETWORK
+    -DZULUSCSI_DAYNAPORT
+    -DROMDRIVE_OFFSET=${env:ZuluSCSI_Pico_DaynaPORT.program_flash_allocation}
+; These take a large portion of the SRAM and can be adjusted
+    -DLOGBUFSIZE=8192
+    -DPREFETCH_BUFFER_SIZE=4608
+    -DSCSI2SD_BUFFER_SIZE=57344
+    ; This controls the depth of NETWORK_PACKET_MAX_SIZE (1520 bytes)
+    ; For example a queue size of 10 would be 10 x 1520 = 15200 bytes
+    -DNETWORK_PACKET_QUEUE_SIZE=12
+; build flags mirroring the framework-arduinopico#v3.6.0-DaynaPORT static library build
+    -DPICO_CYW43_ARCH_POLL=1
+	-DCYW43_LWIP=0
+	-DCYW43_USE_OTP_MAC=0
+;    -DPIO_FRAMEWORK_ARDUINO_NO_USB
+    -DPLATFORM_MASS_STORAGE
+
 ; Variant of RP2040 platform, based on Raspberry Pico board and a carrier PCB
 ; Differs in pinout from ZuluSCSI_RP2040 platform, but shares most of the code.
 [env:ZuluSCSI_BS2]

+ 30 - 5
src/ZuluSCSI.cpp

@@ -9,6 +9,7 @@
  *  
  * This work incorporates work by following
  *  Copyright (c) 2023 joshua stein <jcs@jcs.org>
+ *  Copyright (c) 2023 zigzagjoe
  * 
  *  This file is free software: you may copy, redistribute and/or modify it  
  *  under the terms of the GNU General Public License as published by the  
@@ -56,6 +57,7 @@
 #include "ZuluSCSI_settings.h"
 #include "ZuluSCSI_disk.h"
 #include "ZuluSCSI_initiator.h"
+#include "ZuluSCSI_msc.h"
 #include "ROMDrive.h"
 
 SdFs SD;
@@ -719,10 +721,10 @@ static void reinitSCSI()
 #endif // ZULUSCSI_NETWORK
   
 }
-extern "C" void zuluscsi_setup(void)
+// Place all the setup code that requires the SD card to be initialized here
+// Which is pretty much everything after platform_init and and platform_late_init
+static void zuluscsi_setup_sd_card()
 {
-  platform_init();
-  platform_late_init();
 
   g_sdcard_present = mountSDCard();
 
@@ -782,8 +784,6 @@ extern "C" void zuluscsi_setup(void)
 
   }
 
-  logmsg("Initialization complete!");
-
   if (g_sdcard_present)
   {
     init_logfile();
@@ -797,6 +797,31 @@ extern "C" void zuluscsi_setup(void)
   LED_OFF();
 }
 
+extern "C" void zuluscsi_setup(void)
+{
+  platform_init();
+  platform_late_init();
+  zuluscsi_setup_sd_card();
+
+#ifdef PLATFORM_MASS_STORAGE
+  static bool check_mass_storage = true;
+  if (check_mass_storage && g_scsi_settings.getSystem()->enableUSBMassStorage)
+  {
+    check_mass_storage = false;
+    
+    // perform checks to see if a computer is attached and return true if we should enter MSC mode.
+    if (platform_senseMSC())
+    {
+      zuluscsi_MSC_loop();
+      logmsg("Re-processing filenames and zuluscsi.ini config parameters");
+      zuluscsi_setup_sd_card();
+    }
+  }
+#endif
+
+  logmsg("Initialization complete!");
+}
+
 extern "C" void zuluscsi_main_loop(void)
 {
   static uint32_t sd_card_check_time = 0;

+ 103 - 0
src/ZuluSCSI_msc.cpp

@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2023,2024 zigzagjoe
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#ifdef PLATFORM_MASS_STORAGE
+
+#include <SdFat.h>
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_msc.h"
+
+// external global SD variable
+extern SdFs SD;
+
+// public globals
+volatile MSC_LEDState MSC_LEDMode;
+
+// card reader operation loop
+// assumption that SD card was enumerated and is working
+void zuluscsi_MSC_loop() {
+
+  // turn LED on to indicate entering card reader mode.
+  LED_ON();
+  
+  logmsg("Entering USB Mass storage mode. Eject the USB disk to exit.");
+
+  platform_enterMSC();
+  
+  uint32_t sd_card_check_time = 0;
+  uint16_t syncCounter = 0;
+        
+  // steady state operation / indication loop
+  // led remains steady on
+  while(platform_runMSC()) {
+    platform_reset_watchdog(); // also sends log to USB serial
+
+    if ((uint32_t)(millis() - sd_card_check_time) > 5000) {
+      sd_card_check_time = millis();
+      uint32_t ocr;
+      if (!SD.card()->readOCR(&ocr)) {
+        if (!SD.card()->readOCR(&ocr)) {
+          logmsg("SD card presence check failed! Card unexpectedly removed?");
+          break;
+        }
+      }
+    }
+ 
+    // blink LED according to access type
+    switch (MSC_LEDMode) {
+      case LED_BLINK_FAST:
+        LED_OFF();
+        delay(30);
+        break;
+      case LED_BLINK_SLOW:
+        delay(30);
+        LED_OFF();
+        delay(100);
+        syncCounter = 1;
+        break;
+      default:
+        // sync sd card ~ 500ms after writes stop
+        if (syncCounter && (++syncCounter > 8)) {
+          syncCounter = 0;
+          SD.card()->syncDevice();
+        }
+    }
+
+    // LED always on in card reader mode
+    MSC_LEDMode = LED_SOLIDON;
+	  LED_ON(); 
+    delay(30);
+  }
+
+  // turn the LED off to indicate exiting MSC
+  LED_OFF();
+  
+  logmsg("USB Mass Storage mode exited: resuming standard functionality.");
+  platform_exitMSC();
+  
+  SD.card()->syncDevice();
+
+  // leave the LED off for a moment, before any blinks from the main firmware occur
+  delay(1000);
+}
+
+#endif

+ 37 - 0
src/ZuluSCSI_msc.h

@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2023-2024 zigzagjoe
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#ifdef PLATFORM_MASS_STORAGE
+#pragma once
+
+// include platform-specific defines
+#include "ZuluSCSI_platform_msc.h"
+
+// wait up to this long during init sequence for USB enumeration to enter card reader
+#define CR_ENUM_TIMEOUT 1000
+
+enum  MSC_LEDState { LED_SOLIDON = 0, LED_BLINK_FAST, LED_BLINK_SLOW };
+extern volatile MSC_LEDState MSC_LEDMode;
+
+// run cardreader main loop (blocking)
+void zuluscsi_MSC_loop();
+
+#endif

+ 4 - 0
src/ZuluSCSI_settings.cpp

@@ -289,6 +289,7 @@ scsi_system_settings_t *ZuluSCSISettings::initSystem(const char *presetName)
     cfgSys.enableParity = true;
     cfgSys.useFATAllocSize = false;
     cfgSys.enableCDAudio = false;
+    cfgSys.enableUSBMassStorage = false;
     
     // setting set for all or specific devices
     cfgDev.deviceType = S2S_CFG_NOT_SET;
@@ -380,6 +381,9 @@ scsi_system_settings_t *ZuluSCSISettings::initSystem(const char *presetName)
     cfgSys.enableParity =  ini_getbool("SCSI", "EnableParity", cfgSys.enableParity, CONFIGFILE);
     cfgSys.useFATAllocSize = ini_getbool("SCSI", "UseFATAllocSize", cfgSys.useFATAllocSize, CONFIGFILE);
     cfgSys.enableCDAudio = ini_getbool("SCSI", "EnableCDAudio", cfgSys.enableCDAudio, CONFIGFILE);
+
+    cfgSys.enableUSBMassStorage = ini_getbool("SCSI", "EnableUSBMassStorage", cfgSys.enableUSBMassStorage, CONFIGFILE);
+    
     return &cfgSys;
 }
 

+ 1 - 0
src/ZuluSCSI_settings.h

@@ -65,6 +65,7 @@ typedef struct __attribute__((__packed__)) scsi_system_settings_t
     bool enableParity;
     bool useFATAllocSize;
     bool enableCDAudio;
+    bool enableUSBMassStorage;
 } scsi_system_settings_t;
 
 // This struct should only have new setting added to the end