ソースを参照

Merge pull request #397 from ZuluSCSI/msc-pico-gd32

Unified MSC for RP2040 and GD32F205 platforms
John Morio Sakaguchi 1 年間 前
コミット
7ae0f62005

+ 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 storageInit(uint8_t Lun);
+static int8_t storageIsReady(uint8_t Lun);
+static int8_t storageIsWriteProtected(uint8_t Lun);
+static int8_t storageGetMaxLun(void);
+static int8_t storageRead(uint8_t Lun,
+                           uint8_t *buf,
+                           uint32_t BlkAddr,
+                           uint16_t BlkLen);
+static int8_t storageWrite(uint8_t Lun,
+                            uint8_t *buf,
+                            uint32_t BlkAddr,
+                            uint16_t BlkLen);
+
+usbd_mem_cb USBD_SD_fops = {
+    .mem_init      = storageInit,
+    .mem_ready     = storageIsReady,
+    .mem_protected = storageIsWriteProtected,
+    .mem_read      = storageRead,
+    .mem_write     = storageWrite,
+    .mem_maxlun    = storageGetMaxLun,
+
+    .mem_inquiry_data = {(uint8_t *)storageInquiryData},
+    .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_sense_msc() {
+
+  // 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_enter_msc() {
+  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_run_msc() {
+  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_exit_msc() {
+  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 storageInit(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 storageIsReady(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 storageIsWriteProtected(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 storageRead(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 storageWrite(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 storageGetMaxLun(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 storageInquiryData[] = {
+    /* 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_sense_msc();
+
+/* perform MSC-specific init tasks */
+void platform_enter_msc();
+
+/* return true if we should remain in card reader mode. called in a loop. */
+bool platform_run_msc();
+
+/* perform any cleanup tasks for the MSC-specific functionality */
+void platform_exit_msc();
+
+#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 */

+ 227 - 0
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_msc.cpp

@@ -0,0 +1,227 @@
+/**
+ * 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 <class/msc/msc.h>
+#include <class/msc/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_sense_msc() {
+
+#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_run_msc() {
+  return unitReady;
+}
+
+/* perform MSC class preinit tasks */
+void platform_enter_msc() {
+  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_exit_msc() {
+  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;
+  
+  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_sense_msc();
+
+/* perform MSC-specific init tasks */
+void platform_enter_msc();
+
+/* return true if we should remain in card reader mode. called in a loop. */
+bool platform_run_msc();
+
+/* perform any cleanup tasks for the MSC-specific functionality */
+void platform_exit_msc();
+
+#endif

+ 10 - 4
platformio.ini

@@ -55,6 +55,7 @@ build_flags =
      -DENABLE_DEDICATED_SPI=1
      -DPIO_USBFS_DEVICE_CDC
      -DZULUSCSI_V1_0
+     -DPLATFORM_MASS_STORAGE
 
 ; ZuluSCSI V1.0 mini hardware platform with GD32F205 CPU.
 [env:ZuluSCSIv1_0_mini]
@@ -68,6 +69,7 @@ build_flags =
      -DPIO_USBFS_DEVICE_CDC
      -DZULUSCSI_V1_0
      -DZULUSCSI_V1_0_mini
+     -DPLATFORM_MASS_STORAGE
 
 ; ZuluSCSI V1.1+ hardware platforms, this support v1.1, v1.1 ODE, and vl.2
 [env:ZuluSCSIv1_1_plus]
@@ -82,6 +84,7 @@ build_flags =
      -DHAS_SDIO_CLASS
      -DENABLE_AUDIO_OUTPUT
      -DZULUSCSI_V1_1_plus
+     -DPLATFORM_MASS_STORAGE
 
 ; ZuluSCSI RP2040 hardware platform, based on the Raspberry Pi foundation RP2040 microcontroller
 [env:ZuluSCSI_RP2040]
@@ -126,7 +129,7 @@ build_flags =
     -DPICO_CYW43_ARCH_POLL=1
 	-DCYW43_LWIP=0
 	-DCYW43_USE_OTP_MAC=0
-
+    -DPLATFORM_MASS_STORAGE
 
 ; ZuluSCSI RP2040 hardware platform, as above, but with audio output support enabled
 [env:ZuluSCSI_RP2040_Audio]
@@ -154,6 +157,7 @@ build_flags =
     -DPICO_CYW43_ARCH_POLL=1
 	-DCYW43_LWIP=0
 	-DCYW43_USE_OTP_MAC=0
+    -DPLATFORM_MASS_STORAGE
 
 ; Build for the ZuluSCSI Pico carrier board with a Pico-W
 ; for SCSI DaynaPORT emulation
@@ -184,6 +188,7 @@ debug_build_flags =
 ; This flag enables verbose logging of TCP/IP traffic and other information
 ; it also takes up a bit of SRAM so it should be disabled with production code
     -DNETWORK_DEBUG_LOGGING
+    -DPLATFORM_MASS_STORAGE
 
 lib_deps =
     SdFat=https://github.com/rabbitholecomputing/SdFat#2.2.0-gpt
@@ -209,7 +214,7 @@ build_flags =
     -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=18
+    -DNETWORK_PACKET_QUEUE_SIZE=14
     
 ; This flag enables verbose logging of TCP/IP traffic and other information
 ; it also takes up a bit of SRAM so it should be disabled with production code
@@ -219,7 +224,8 @@ build_flags =
     -DPICO_CYW43_ARCH_POLL=1
 	-DCYW43_LWIP=0
 	-DCYW43_USE_OTP_MAC=0
-    -DPIO_FRAMEWORK_ARDUINO_NO_USB
+ ;   -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.
@@ -239,7 +245,7 @@ build_flags =
     -DPICO_CYW43_ARCH_POLL=1
 	-DCYW43_LWIP=0
 	-DCYW43_USE_OTP_MAC=0
-
+    -DPLATFORM_MASS_STORAGE
     
 ; ZuluSCSI F4 hardware platform with GD32F450ZET6 CPU.
 [env:ZuluSCSIv1_4]

+ 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_sense_msc())
+    {
+      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_enter_msc();
+  
+  uint32_t sd_card_check_time = 0;
+  uint16_t syncCounter = 0;
+        
+  // steady state operation / indication loop
+  // led remains steady on
+  while(platform_run_msc()) {
+    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_exit_msc();
+  
+  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