ソースを参照

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 ヶ月 前
コミット
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