Bladeren bron

USB MSC initiator mode

When SD card is not present and initiator mode DIP switch
is active, expose any connected SCSI drives through USB.
Petteri Aimonen 11 maanden geleden
bovenliggende
commit
ef083810fc

+ 15 - 9
lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_msc.cpp

@@ -28,12 +28,12 @@
 #include "ZuluSCSI_platform.h"
 #include "ZuluSCSI_log.h"
 #include "ZuluSCSI_msc.h"
+#include "ZuluSCSI_msc_initiator.h"
 #include "ZuluSCSI_config.h"
 #include "ZuluSCSI_settings.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
@@ -111,6 +111,8 @@ void __USBInstallMassStorage() { }
 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]) {
 
+  if (g_msc_initiator) return init_msc_inquiry_cb(lun, vendor_id, product_id, product_rev);
+
   const char vid[] = "ZuluSCSI";
   const char pid[] = PLATFORM_PID; 
   const char rev[] = "1.0";
@@ -123,6 +125,8 @@ extern "C" void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8],
 // max LUN supported
 // we only have the one SD card
 extern "C" uint8_t tud_msc_get_maxlun_cb(void) {
+  if (g_msc_initiator) return init_msc_get_maxlun_cb();
+
   return 1; // number of LUNs supported
 }
 
@@ -131,6 +135,8 @@ extern "C" uint8_t tud_msc_get_maxlun_cb(void) {
 // otherwise this is not actually needed
 extern "C" bool tud_msc_is_writable_cb (uint8_t lun)
 {
+  if (g_msc_initiator) return init_msc_is_writable_cb(lun);
+
   (void) lun;
   return unitReady;
 }
@@ -138,8 +144,7 @@ extern "C" bool tud_msc_is_writable_cb (uint8_t lun)
 // 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 (g_msc_initiator) return init_msc_start_stop_cb(lun, power_condition, start, load_eject);
 
   if (load_eject)  {
     if (start) {
@@ -155,7 +160,7 @@ extern "C" bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool
 
 // return true if we are ready to service reads/writes
 extern "C" bool tud_msc_test_unit_ready_cb(uint8_t lun) {
-  (void) lun;
+  if (g_msc_initiator) return init_msc_test_unit_ready_cb(lun);
 
   return unitReady;
 }
@@ -163,7 +168,7 @@ extern "C" bool tud_msc_test_unit_ready_cb(uint8_t lun) {
 // 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;
+  if (g_msc_initiator) return init_msc_capacity_cb(lun, block_count, block_size);
 
   *block_count = unitReady ? (SD.card()->sectorCount()) : 0;
   *block_size = SD_SECTOR_SIZE;
@@ -173,6 +178,7 @@ extern "C" void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count,
 // - 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) {
+  if (g_msc_initiator) return init_msc_scsi_cb(lun, scsi_cmd, buffer, bufsize);
 
   const void *response = NULL;
   uint16_t resplen = 0;
@@ -210,7 +216,7 @@ extern "C" int32_t tud_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void
 extern "C" int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, 
                             void* buffer, uint32_t bufsize)
 {
-  (void) lun;
+  if (g_msc_initiator) return init_msc_read10_cb(lun, lba, offset, buffer, bufsize);
 
   bool rc = SD.card()->readSectors(lba, (uint8_t*) buffer, bufsize/SD_SECTOR_SIZE);
 
@@ -225,7 +231,7 @@ extern "C" int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset,
 // 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;
+  if (g_msc_initiator) return init_msc_read10_cb(lun, lba, offset, buffer, bufsize);
 
   bool rc = SD.card()->writeSectors(lba, buffer, bufsize/SD_SECTOR_SIZE);
 
@@ -238,7 +244,7 @@ extern "C" int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset
 // 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;
+  if (g_msc_initiator) return init_msc_write10_complete_cb(lun);
 }
 
-#endif
+#endif

+ 33 - 11
src/ZuluSCSI.cpp

@@ -58,6 +58,7 @@
 #include "ZuluSCSI_settings.h"
 #include "ZuluSCSI_disk.h"
 #include "ZuluSCSI_initiator.h"
+#include "ZuluSCSI_msc_initiator.h"
 #include "ZuluSCSI_msc.h"
 #include "ROMDrive.h"
 
@@ -65,7 +66,7 @@ SdFs SD;
 FsFile g_logfile;
 bool g_rawdrive_active;
 static bool g_romdrive_active;
-static bool g_sdcard_present;
+bool g_sdcard_present;
 
 #ifndef SD_SPEED_CLASS_WARN_BELOW
 #define SD_SPEED_CLASS_WARN_BELOW 10
@@ -955,7 +956,7 @@ static void firmware_update()
 
 // 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()
+static void zuluscsi_setup_sd_card(bool wait_for_card = true)
 {
 
   g_sdcard_present = mountSDCard();
@@ -980,11 +981,18 @@ static void zuluscsi_setup_sd_card()
       blinkStatus(BLINK_ERROR_NO_SD_CARD);
       platform_reset_watchdog();
       g_sdcard_present = mountSDCard();
-    } while (!g_sdcard_present);
+    } while (!g_sdcard_present && wait_for_card);
     blink_cancel();
     LED_OFF();
 
-    logmsg("SD card init succeeded after retry");
+    if (g_sdcard_present)
+    {
+      logmsg("SD card init succeeded after retry");
+    }
+    else
+    {
+      logmsg("Continuing without SD card");
+    }
   }
 
   firmware_update();
@@ -1062,12 +1070,18 @@ extern "C" void zuluscsi_setup(void)
 {
   platform_init();
   platform_late_init();
-  zuluscsi_setup_sd_card();
+
+  bool is_initiator = false;
+#ifdef PLATFORM_HAS_INITIATOR_MODE
+  is_initiator = platform_is_initiator_mode_enabled();
+#endif
+
+  zuluscsi_setup_sd_card(!is_initiator);
 
 #ifdef PLATFORM_MASS_STORAGE
   static bool check_mass_storage = true;
-  if ((check_mass_storage && g_scsi_settings.getSystem()->enableUSBMassStorage)
-      || platform_rebooted_into_mass_storage())
+  if (((check_mass_storage && g_scsi_settings.getSystem()->enableUSBMassStorage)
+      || platform_rebooted_into_mass_storage()) && !is_initiator)
   {
     check_mass_storage = false;
 
@@ -1089,6 +1103,11 @@ extern "C" void zuluscsi_main_loop(void)
   static uint32_t sd_card_check_time = 0;
   static uint32_t last_request_time = 0;
 
+  bool is_initiator = false;
+#ifdef PLATFORM_HAS_INITIATOR_MODE
+  is_initiator = platform_is_initiator_mode_enabled();
+#endif
+
   platform_reset_watchdog();
   platform_poll();
   diskEjectButtonUpdate(true);
@@ -1099,7 +1118,7 @@ extern "C" void zuluscsi_main_loop(void)
 #endif // ZULUSCSI_NETWORK
 
 #ifdef PLATFORM_HAS_INITIATOR_MODE
-  if (platform_is_initiator_mode_enabled())
+  if (is_initiator)
   {
     scsiInitiatorMainLoop();
     save_logfile();
@@ -1128,7 +1147,7 @@ extern "C" void zuluscsi_main_loop(void)
   {
     // Check SD card status for hotplug
     if (scsiDev.phase == BUS_FREE &&
-        (uint32_t)(millis() - sd_card_check_time) > 5000)
+        (uint32_t)(millis() - sd_card_check_time) > SDCARD_POLL_INTERVAL)
     {
       sd_card_check_time = millis();
       uint32_t ocr;
@@ -1143,8 +1162,11 @@ extern "C" void zuluscsi_main_loop(void)
     }
   }
 
-  if (!g_sdcard_present)
+  if (!g_sdcard_present && (uint32_t)(millis() - sd_card_check_time) > SDCARD_POLL_INTERVAL
+      && !g_msc_initiator)
   {
+    sd_card_check_time = millis();
+
     // Try to remount SD card
     do
     {
@@ -1166,6 +1188,6 @@ extern "C" void zuluscsi_main_loop(void)
         platform_reset_watchdog();
         platform_poll();
       }
-    } while (!g_sdcard_present && !g_romdrive_active);
+    } while (!g_sdcard_present && !g_romdrive_active && !is_initiator);
   }
 }

+ 3 - 0
src/ZuluSCSI_config.h

@@ -54,6 +54,9 @@
 #endif
 #define LOG_SAVE_INTERVAL_MS 1000
 
+// How often to check for SD card presence
+#define SDCARD_POLL_INTERVAL 5000
+
 // Watchdog timeout
 // Watchdog will first issue a bus reset and if that does not help, crashdump.
 #define WATCHDOG_BUS_RESET_TIMEOUT 15000

+ 35 - 0
src/ZuluSCSI_initiator.cpp

@@ -28,6 +28,8 @@
 #include "ZuluSCSI_log.h"
 #include "ZuluSCSI_log_trace.h"
 #include "ZuluSCSI_initiator.h"
+#include "ZuluSCSI_msc_initiator.h"
+#include "ZuluSCSI_msc.h"
 #include <ZuluSCSI_platform.h>
 #include <minIni.h>
 #include "SdFat.h"
@@ -61,6 +63,9 @@ bool scsiInitiatorReadCapacity(int target_id, uint32_t *sectorcount, uint32_t *s
 
 #else
 
+// From ZuluSCSI.cpp
+extern bool g_sdcard_present;
+
 /*************************************
  * High level initiator mode logic   *
  *************************************/
@@ -137,6 +142,11 @@ void scsiInitiatorInit()
 
 }
 
+int scsiInitiatorGetOwnID()
+{
+    return g_initiator_state.initiator_id;
+}
+
 // Update progress bar LED during transfers
 static void scsiInitiatorUpdateLed()
 {
@@ -202,6 +212,31 @@ void scsiInitiatorMainLoop()
         scsiHostPhyReset();
     }
 
+#ifdef PLATFORM_MASS_STORAGE
+    if (g_msc_initiator)
+    {
+        poll_msc_initiator();
+        platform_run_msc();
+        return;
+    }
+    else
+    {
+        if (!g_sdcard_present || ini_getbool("SCSI", "InitiatorMSC", false, CONFIGFILE))
+        {
+            logmsg("Entering USB MSC initiator mode");
+            platform_enter_msc();
+            setup_msc_initiator();
+            return;
+        }
+    }
+#endif
+
+    if (!g_sdcard_present)
+    {
+        // Wait for SD card
+        return;
+    }
+
     if (!g_initiator_state.imaging)
     {
         // Scan for SCSI drives one at a time

+ 3 - 0
src/ZuluSCSI_initiator.h

@@ -34,6 +34,9 @@ void scsiInitiatorInit();
 
 void scsiInitiatorMainLoop();
 
+// Get the SCSI ID used by the initiator itself
+int scsiInitiatorGetOwnID();
+
 // Select target and execute SCSI command
 int scsiInitiatorRunCommand(int target_id,
                             const uint8_t *command, size_t cmdLen,

+ 353 - 0
src/ZuluSCSI_msc_initiator.cpp

@@ -0,0 +1,353 @@
+/* Initiator mode USB Mass Storage Class connection.
+ * This file binds platform-specific MSC routines to the initiator mode
+ * SCSI bus interface. The call structure is modeled after TinyUSB, but
+ * should be usable with other USB libraries.
+ *
+ * ZuluSCSI™ - Copyright (c) 2023 Rabbit Hole Computing™
+ *
+ * This file is licensed under the GPL version 3 or any later version. 
+ * It is derived from cdrom.c in SCSI2SD V6
+ *
+ * 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/>.
+ */
+
+
+#include "ZuluSCSI_config.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_log_trace.h"
+#include "ZuluSCSI_initiator.h"
+#include <ZuluSCSI_platform.h>
+#include <minIni.h>
+#include "SdFat.h"
+
+bool g_msc_initiator;
+
+#ifndef PLATFORM_HAS_INITIATOR_MODE
+
+bool setup_msc_initiator() { return false; }
+void poll_msc_initiator() {}
+
+void init_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) {}
+uint8_t init_msc_get_maxlun_cb(void) { return 0; }
+bool init_msc_is_writable_cb (uint8_t lun) { return false; }
+bool init_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { return false; }
+bool init_msc_test_unit_ready_cb(uint8_t lun) { return false; }
+void init_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) {}
+int32_t init_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer, uint16_t bufsize) {return -1;}
+int32_t init_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) {return -1;}
+int32_t init_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) { return -1;}
+void init_msc_write10_complete_cb(uint8_t lun) {}
+
+#else
+
+// If there are multiple SCSI devices connected, they are mapped into LUNs for host.
+static struct {
+    int target_id;
+    uint32_t sectorsize;
+    uint32_t sectorcount;
+} g_msc_initiator_targets[NUM_SCSIID];
+static int g_msc_initiator_target_count;
+
+static void scan_targets()
+{
+    int initiator_id = scsiInitiatorGetOwnID();
+    uint8_t inquiry_data[36] = {0};
+    g_msc_initiator_target_count = 0;
+    for (int target_id = 0; target_id < NUM_SCSIID; target_id++)
+    {
+        if (target_id == initiator_id) continue;
+
+        if (scsiTestUnitReady(target_id))
+        {
+            uint32_t sectorcount, sectorsize;
+
+            bool inquiryok =
+                scsiStartStopUnit(target_id, true) &&
+                scsiInquiry(target_id, inquiry_data) &&
+                scsiInitiatorReadCapacity(target_id, &sectorcount, &sectorsize);
+
+            char vendor_id[9] = {0};
+            char product_id[17] = {0};
+            memcpy(vendor_id, &inquiry_data[8], 8);
+            memcpy(product_id, &inquiry_data[16], 16);
+
+            if (inquiryok)
+            {
+                logmsg("Found SCSI drive with ID ", target_id, ": ", vendor_id, " ", product_id);
+                g_msc_initiator_targets[g_msc_initiator_target_count].target_id = target_id;
+                g_msc_initiator_targets[g_msc_initiator_target_count].sectorcount = sectorcount;
+                g_msc_initiator_targets[g_msc_initiator_target_count].sectorsize = sectorsize;
+                g_msc_initiator_target_count++;
+            }
+            else
+            {
+                logmsg("Detected SCSI device with ID ", target_id, ", but failed to get inquiry response, skipping");
+            }
+        }
+    }
+}
+
+bool setup_msc_initiator()
+{
+    logmsg("SCSI Initiator: activating USB MSC mode");
+    g_msc_initiator = true;
+
+    scsiInitiatorInit();
+
+    // Scan for targets
+    scan_targets();
+
+    logmsg("SCSI Initiator: found " , g_msc_initiator_target_count, " SCSI drives");
+    return g_msc_initiator_target_count > 0;
+}
+
+void poll_msc_initiator()
+{
+    if (g_msc_initiator_target_count == 0)
+    {
+        // Scan for targets until we find one
+        scan_targets();
+    }
+}
+
+static int get_target(uint8_t lun)
+{
+    if (lun >= g_msc_initiator_target_count)
+    {
+        logmsg("Host requested access to non-existing lun ", (int)lun);
+        return 0;
+    }
+    else
+    {
+        return g_msc_initiator_targets[lun].target_id;
+    }
+}
+
+void init_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
+{
+    int target = get_target(lun);
+    uint8_t response[36] = {0};
+    bool status = scsiInquiry(target, response);
+    if (!status)
+    {
+        logmsg("SCSI Inquiry to target ", target, " failed");
+    }
+
+    memcpy(vendor_id, &response[8], 8);
+    memcpy(product_id, &response[16], 16);
+    memcpy(product_rev, &response[32], 4);
+}
+
+uint8_t init_msc_get_maxlun_cb(void)
+{
+    return g_msc_initiator_target_count;
+}
+
+bool init_msc_is_writable_cb (uint8_t lun)
+{
+    int target = get_target(lun);
+    uint8_t command[6] = {0x1A, 0x08, 0, 0, 4, 0}; // MODE SENSE(6)
+    uint8_t response[4] = {0};
+    scsiInitiatorRunCommand(target, command, 6, response, 4, NULL, 0);
+    return (response[2] & 0x80) == 0; // Check write protected bit
+}
+
+bool init_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
+{
+    int target = get_target(lun);
+    uint8_t command[6] = {0x1B, 0x1, 0, 0, 0, 0};
+    uint8_t response[4] = {0};
+    
+    if (start)
+    {
+        command[4] |= 1; // Start
+        command[1] = 0;  // Immediate
+    }
+
+    if (load_eject)
+    {
+        command[4] |= 2;
+    }
+
+    command[4] |= power_condition << 4;
+
+    int status = scsiInitiatorRunCommand(target,
+                                         command, sizeof(command),
+                                         response, sizeof(response),
+                                         NULL, 0);
+
+    if (status == 2)
+    {
+        uint8_t sense_key;
+        scsiRequestSense(target, &sense_key);
+        logmsg("START STOP UNIT on target ", target, " failed, sense key ", sense_key);
+    }
+
+    return status == 0;
+}
+
+bool init_msc_test_unit_ready_cb(uint8_t lun)
+{
+    return scsiTestUnitReady(get_target(lun));
+}
+
+void init_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size)
+{
+    uint32_t sectorcount = 0;
+    uint32_t sectorsize = 0;
+    scsiInitiatorReadCapacity(get_target(lun), &sectorcount, &sectorsize);
+    *block_count = sectorcount;
+    *block_size = sectorsize;
+}
+
+int32_t init_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer, uint16_t bufsize)
+{
+    // NOTE: the TinyUSB API around free-form commands is not very good,
+    // this function could need improvement.
+    
+    // Figure out command length
+    static const uint8_t CmdGroupBytes[8] = {6, 10, 10, 6, 16, 12, 6, 6}; // From SCSI2SD
+    int cmdlen = CmdGroupBytes[scsi_cmd[0] >> 5];
+
+    int target = get_target(lun);
+    int status = scsiInitiatorRunCommand(target,
+                                         scsi_cmd, cmdlen,
+                                         NULL, 0,
+                                         (const uint8_t*)buffer, bufsize);
+
+    return status;
+}
+
+int32_t init_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
+{
+    int status = -1;
+
+    int target_id = get_target(lun);
+    int sectorsize = g_msc_initiator_targets[lun].sectorsize;
+    uint32_t start_sector = lba;
+    uint32_t sectorcount = bufsize / sectorsize;
+
+    if (sectorcount == 0)
+    {
+        // Not enough buffer left for a full sector
+        return 0;
+    }
+
+    // Read6 command supports 21 bit LBA - max of 0x1FFFFF
+    // ref: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf pg 134
+    if (start_sector < 0x1FFFFF && sectorcount <= 256)
+    {
+        // Use READ6 command for compatibility with old SCSI1 drives
+        uint8_t command[6] = {0x08,
+            (uint8_t)(start_sector >> 16),
+            (uint8_t)(start_sector >> 8),
+            (uint8_t)start_sector,
+            (uint8_t)sectorcount,
+            0x00
+        };
+
+        status = scsiInitiatorRunCommand(target_id, command, sizeof(command), (uint8_t*)buffer, bufsize, NULL, 0);
+    }
+    else
+    {
+        // Use READ10 command for larger number of blocks
+        uint8_t command[10] = {0x28, 0x00,
+            (uint8_t)(start_sector >> 24), (uint8_t)(start_sector >> 16),
+            (uint8_t)(start_sector >> 8), (uint8_t)start_sector,
+            0x00,
+            (uint8_t)(sectorcount >> 8), (uint8_t)(sectorcount),
+            0x00
+        };
+
+        status = scsiInitiatorRunCommand(target_id, command, sizeof(command), (uint8_t*)buffer, bufsize, NULL, 0);
+    }
+
+
+    if (status != 0)
+    {
+        uint8_t sense_key;
+        scsiRequestSense(target_id, &sense_key);
+
+        logmsg("SCSI Initiator read failed: ", status, " sense key ", sense_key);
+        return -1;
+    }
+
+    return sectorcount * sectorsize;
+}
+
+int32_t init_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
+{
+    int status = -1;
+
+    int target_id = get_target(lun);
+    int sectorsize = g_msc_initiator_targets[lun].sectorsize;
+    uint32_t start_sector = lba;
+    uint32_t sectorcount = bufsize / sectorsize;
+
+    if (sectorcount == 0)
+    {
+        // Not a complete sector
+        return 0;
+    }
+
+    // Write6 command supports 21 bit LBA - max of 0x1FFFFF
+    if (start_sector < 0x1FFFFF && sectorcount <= 256)
+    {
+        // Use WRITE6 command for compatibility with old SCSI1 drives
+        uint8_t command[6] = {0x0A,
+            (uint8_t)(start_sector >> 16),
+            (uint8_t)(start_sector >> 8),
+            (uint8_t)start_sector,
+            (uint8_t)sectorcount,
+            0x00
+        };
+
+        status = scsiInitiatorRunCommand(target_id, command, sizeof(command), NULL, 0, buffer, bufsize);
+    }
+    else
+    {
+        // Use READ10 command for larger number of blocks
+        uint8_t command[10] = {0x2A, 0x00,
+            (uint8_t)(start_sector >> 24), (uint8_t)(start_sector >> 16),
+            (uint8_t)(start_sector >> 8), (uint8_t)start_sector,
+            0x00,
+            (uint8_t)(sectorcount >> 8), (uint8_t)(sectorcount),
+            0x00
+        };
+
+        status = scsiInitiatorRunCommand(target_id, command, sizeof(command), NULL, 0, buffer, bufsize);
+    }
+
+
+    if (status != 0)
+    {
+        uint8_t sense_key;
+        scsiRequestSense(target_id, &sense_key);
+
+        logmsg("SCSI Initiator write failed: ", status, " sense key ", sense_key);
+        return -1;
+    }
+
+    return sectorcount * sectorsize;
+}
+
+void init_msc_write10_complete_cb(uint8_t lun)
+{
+    (void)lun;
+}
+
+
+#endif

+ 46 - 0
src/ZuluSCSI_msc_initiator.h

@@ -0,0 +1,46 @@
+/* Initiator mode USB Mass Storage Class connection.
+ * This file binds platform-specific MSC routines to the initiator mode
+ * SCSI bus interface. The call structure is modeled after TinyUSB, but
+ * should be usable with other USB libraries.
+ *
+ * ZuluSCSI™ - Copyright (c) 2023 Rabbit Hole Computing™
+ *
+ * This file is licensed under the GPL version 3 or any later version. 
+ * It is derived from cdrom.c in SCSI2SD V6
+ *
+ * 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/>.
+ */
+
+#pragma once
+#include <stdint.h>
+
+// When true, initiator MSC mode is enabled.
+extern bool g_msc_initiator;
+bool setup_msc_initiator();
+void poll_msc_initiator();
+
+void init_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]);
+uint8_t init_msc_get_maxlun_cb(void);
+bool init_msc_is_writable_cb (uint8_t lun);
+bool init_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject);
+bool init_msc_test_unit_ready_cb(uint8_t lun);
+void init_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size);
+int32_t init_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer, uint16_t bufsize);
+int32_t init_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize);
+int32_t init_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize);
+void init_msc_write10_complete_cb(uint8_t lun);
+
+

+ 1 - 0
zuluscsi.ini

@@ -44,6 +44,7 @@
 #InitiatorID = 7 # SCSI ID, 0-7, when the device is in initiator mode, default is 7
 #InitiatorMaxRetry = 5 #  number of retries on failed reads 0-255, default is 5
 #InitiatorImageHandling = 0 # 0: skip existing images, 1: create new image with incrementing suffix, 2: overwrite existing image
+#InitiatorMSC = 0 # Force USB MSC mode for initiator. By default enabled only if SD card is not inserted.
 
 #EnableCDAudio = 0 # 1: Enable CD audio - an external I2S DAC on the v1.2 is required