Pārlūkot izejas kodu

RP2040: Basics of initiator mode support.

So far just scans for drives on the bus.
Petteri Aimonen 3 gadi atpakaļ
vecāks
revīzija
2558fe38a5

+ 115 - 50
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp

@@ -17,6 +17,7 @@ extern "C" {
 #include <hardware/flash.h>
 
 const char *g_azplatform_name = PLATFORM_NAME;
+static bool g_scsi_initiator = false;
 
 void mbed_error_hook(const mbed_error_ctx * error_context);
 
@@ -56,7 +57,6 @@ void azplatform_init()
 
     delay(10); // 10 ms delay to let pull-ups do their work
 
-    bool initiator = !gpio_get(DIP_INITIATOR);
     bool dbglog = !gpio_get(DIP_DBGLOG);
     bool termination = !gpio_get(DIP_TERM);
 
@@ -65,12 +65,7 @@ void azplatform_init()
     uart_init(uart0, 1000000);
     mbed_set_error_hook(mbed_error_hook);
 
-    azlog("DIP switch settings: initiator ", (int)initiator, ", debug log ", (int)dbglog, ", termination ", (int)termination);
-
-    if (initiator)
-    {
-        azlog("ERROR: SCSI initiator mode is not implemented yet, turn DIP switch off for proper operation!");
-    }
+    azlog("DIP switch settings: debug log ", (int)dbglog, ", termination ", (int)termination);
 
     g_azlog_debug = dbglog;
     
@@ -83,7 +78,76 @@ void azplatform_init()
         azlog("NOTE: SCSI termination is disabled");
     }
 
-    /* Initialize SCSI and SD card pins to required modes.
+    // SD card pins
+    // Card is used in SDIO mode for main program, and in SPI mode for crash handler & bootloader.
+    //        pin             function       pup   pdown  out    state fast
+    gpio_conf(SD_SPI_SCK,     GPIO_FUNC_SPI, true, false, true,  true, true);
+    gpio_conf(SD_SPI_MOSI,    GPIO_FUNC_SPI, true, false, true,  true, true);
+    gpio_conf(SD_SPI_MISO,    GPIO_FUNC_SPI, true, false, false, true, true);
+    gpio_conf(SD_SPI_CS,      GPIO_FUNC_SIO, true, false, true,  true, true);
+    gpio_conf(SDIO_D1,        GPIO_FUNC_SIO, true, false, false, true, true);
+    gpio_conf(SDIO_D2,        GPIO_FUNC_SIO, true, false, false, true, true);
+
+    // LED pin
+    gpio_conf(LED_PIN,        GPIO_FUNC_SIO, false,false, true,  false, false);
+
+    // I2C pins
+    //        pin             function       pup   pdown  out    state fast
+    gpio_conf(GPIO_I2C_SCL,   GPIO_FUNC_I2C, true,false, false,  true, true);
+    gpio_conf(GPIO_I2C_SDA,   GPIO_FUNC_I2C, true,false, false,  true, true);
+}
+
+static bool read_initiator_dip_switch()
+{
+    /* Revision 2022d hardware has problems reading initiator DIP switch setting.
+     * The 74LVT245 hold current is keeping the GPIO_ACK state too strongly.
+     * Detect this condition by toggling the pin up and down and seeing if it sticks.
+     */
+
+    // Strong output high, then pulldown
+    //        pin             function       pup   pdown   out    state  fast
+    gpio_conf(DIP_INITIATOR,  GPIO_FUNC_SIO, false, false, true,  true,  false);
+    gpio_conf(DIP_INITIATOR,  GPIO_FUNC_SIO, false, true,  false, true,  false);
+    delay(1);
+    bool initiator_state1 = gpio_get(DIP_INITIATOR);
+    
+    // Strong output low, then pullup
+    //        pin             function       pup   pdown   out    state  fast
+    gpio_conf(DIP_INITIATOR,  GPIO_FUNC_SIO, false, false, true,  false, false);
+    gpio_conf(DIP_INITIATOR,  GPIO_FUNC_SIO, true,  false, false, false, false);
+    delay(1);
+    bool initiator_state2 = gpio_get(DIP_INITIATOR);
+
+    if (initiator_state1 == initiator_state2)
+    {
+        // Ok, was able to read the state directly
+        return !initiator_state1;
+    }
+
+    // Enable OUT_BSY for a short time.
+    // If in target mode, this will force GPIO_ACK high.
+    gpio_put(SCSI_OUT_BSY, 0);
+    delay_100ns();
+    gpio_put(SCSI_OUT_BSY, 1);
+
+    return !gpio_get(DIP_INITIATOR);
+}
+
+// late_init() only runs in main application, SCSI not needed in bootloader
+void azplatform_late_init()
+{
+    if (read_initiator_dip_switch())
+    {
+        g_scsi_initiator = true;
+        azlog("SCSI initiator mode selected by DIP switch, expecting SCSI disks on the bus");
+    }
+    else
+    {
+        g_scsi_initiator = false;
+        azlog("SCSI target mode selected by DIP switch, acting as an SCSI disk");
+    }
+
+    /* Initialize SCSI pins to required modes.
      * SCSI pins should be inactive / input at this point.
      */
 
@@ -100,57 +164,57 @@ void azplatform_init()
     gpio_conf(SCSI_IO_DB7,    GPIO_FUNC_SIO, true, false, false, true, true);
     gpio_conf(SCSI_IO_DBP,    GPIO_FUNC_SIO, true, false, false, true, true);
 
-    // SCSI control outputs
-    //        pin             function       pup   pdown  out    state fast
-    gpio_conf(SCSI_OUT_IO,    GPIO_FUNC_SIO, false,false, true,  true, true);
-    gpio_conf(SCSI_OUT_MSG,   GPIO_FUNC_SIO, false,false, true,  true, true);
-
-    // REQ pin is switched between PIO and SIO, pull-up makes sure no glitches
-    gpio_conf(SCSI_OUT_REQ,   GPIO_FUNC_SIO, true ,false, true,  true, true);
-
-    // Shared pins are changed to input / output depending on communication phase
-    gpio_conf(SCSI_IN_SEL,    GPIO_FUNC_SIO, true, false, false, true, true);
-    if (SCSI_OUT_CD != SCSI_IN_SEL)
+    if (!g_scsi_initiator)
     {
-        gpio_conf(SCSI_OUT_CD,    GPIO_FUNC_SIO, false,false, true,  true, true);
-    }
+        // Act as SCSI device / target
 
-    gpio_conf(SCSI_IN_BSY,    GPIO_FUNC_SIO, true, false, false, true, true);
-    if (SCSI_OUT_MSG != SCSI_IN_BSY)
-    {
-        gpio_conf(SCSI_OUT_MSG,    GPIO_FUNC_SIO, false,false, true,  true, true);
-    }
+        // SCSI control outputs
+        //        pin             function       pup   pdown  out    state fast
+        gpio_conf(SCSI_OUT_IO,    GPIO_FUNC_SIO, false,false, true,  true, true);
+        gpio_conf(SCSI_OUT_MSG,   GPIO_FUNC_SIO, false,false, true,  true, true);
 
-    // SCSI control inputs
-    //        pin             function       pup   pdown  out    state fast
-    gpio_conf(SCSI_IN_ACK,    GPIO_FUNC_SIO, true, false, false, true, false);
-    gpio_conf(SCSI_IN_ATN,    GPIO_FUNC_SIO, true, false, false, true, false);
-    gpio_conf(SCSI_IN_RST,    GPIO_FUNC_SIO, true, false, false, true, false);
+        // REQ pin is switched between PIO and SIO, pull-up makes sure no glitches
+        gpio_conf(SCSI_OUT_REQ,   GPIO_FUNC_SIO, true ,false, true,  true, true);
 
-    // SD card pins
-    // Card is used in SDIO mode for main program, and in SPI mode for crash handler & bootloader.
-    //        pin             function       pup   pdown  out    state fast
-    gpio_conf(SD_SPI_SCK,     GPIO_FUNC_SPI, true, false, true,  true, true);
-    gpio_conf(SD_SPI_MOSI,    GPIO_FUNC_SPI, true, false, true,  true, true);
-    gpio_conf(SD_SPI_MISO,    GPIO_FUNC_SPI, true, false, false, true, true);
-    gpio_conf(SD_SPI_CS,      GPIO_FUNC_SIO, true, false, true,  true, true);
-    gpio_conf(SDIO_D1,        GPIO_FUNC_SIO, true, false, false, true, true);
-    gpio_conf(SDIO_D2,        GPIO_FUNC_SIO, true, false, false, true, true);
+        // Shared pins are changed to input / output depending on communication phase
+        gpio_conf(SCSI_IN_SEL,    GPIO_FUNC_SIO, true, false, false, true, true);
+        if (SCSI_OUT_CD != SCSI_IN_SEL)
+        {
+            gpio_conf(SCSI_OUT_CD,    GPIO_FUNC_SIO, false,false, true,  true, true);
+        }
 
-    // LED pin
-    gpio_conf(LED_PIN,        GPIO_FUNC_SIO, false,false, true,  false, false);
+        gpio_conf(SCSI_IN_BSY,    GPIO_FUNC_SIO, true, false, false, true, true);
+        if (SCSI_OUT_MSG != SCSI_IN_BSY)
+        {
+            gpio_conf(SCSI_OUT_MSG,    GPIO_FUNC_SIO, false,false, true,  true, true);
+        }
 
-    // I2C pins
-    //        pin             function       pup   pdown  out    state fast
-    gpio_conf(GPIO_I2C_SCL,   GPIO_FUNC_I2C, true,false, false,  true, true);
-    gpio_conf(GPIO_I2C_SDA,   GPIO_FUNC_I2C, true,false, false,  true, true);
+        // SCSI control inputs
+        //        pin             function       pup   pdown  out    state fast
+        gpio_conf(SCSI_IN_ACK,    GPIO_FUNC_SIO, true, false, false, true, false);
+        gpio_conf(SCSI_IN_ATN,    GPIO_FUNC_SIO, true, false, false, true, false);
+        gpio_conf(SCSI_IN_RST,    GPIO_FUNC_SIO, true, false, false, true, false);
+    }
+    else
+    {
+        // Act as SCSI initiator
+
+        //        pin             function       pup   pdown  out    state fast
+        gpio_conf(SCSI_IN_IO,     GPIO_FUNC_SIO, true ,false, false, true, false);
+        gpio_conf(SCSI_IN_MSG,    GPIO_FUNC_SIO, true ,false, false, true, false);
+        gpio_conf(SCSI_IN_CD,     GPIO_FUNC_SIO, true ,false, false, true, false);
+        gpio_conf(SCSI_IN_REQ,    GPIO_FUNC_SIO, true ,false, false, true, false);
+        gpio_conf(SCSI_IN_BSY,    GPIO_FUNC_SIO, true, false, false, true, false);
+        gpio_conf(SCSI_IN_RST,    GPIO_FUNC_SIO, true, false, false, true, false);
+        gpio_conf(SCSI_OUT_SEL,   GPIO_FUNC_SIO, false,false, true,  true, true);
+        gpio_conf(SCSI_OUT_ACK,   GPIO_FUNC_SIO, false,false, true,  true, true);
+        gpio_conf(SCSI_OUT_ATN,   GPIO_FUNC_SIO, false,false, true,  true, true);
+    }
 }
 
-void azplatform_late_init()
+bool azplatform_is_initiator_mode_enabled()
 {
-    /* This function can usually be left empty.
-     * It can be used for initialization code that should not run in bootloader.
-     */
+    return g_scsi_initiator;
 }
 
 /*****************************************/
@@ -247,6 +311,7 @@ static void watchdog_callback(unsigned alarm_num)
         {
             azlog("WATCHDOG TIMEOUT, attempting bus reset");
             scsiDev.resetFlag = 1;
+            g_scsiHostPhyReset = true;
         }
 
         if (g_watchdog_timeout <= 0)

+ 15 - 1
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.h

@@ -5,6 +5,7 @@
 #include <stdint.h>
 #include <Arduino.h>
 #include "ZuluSCSI_platform_gpio.h"
+#include "scsiHostPhy.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -19,6 +20,7 @@ extern const char *g_azplatform_name;
 #define PLATFORM_OPTIMAL_MAX_SD_WRITE_SIZE 65536
 #define PLATFORM_OPTIMAL_LAST_SD_WRITE_SIZE 8192
 #define SD_USE_SDIO 1
+#define PLATFORM_HAS_INITIATOR_MODE 1
 
 // NOTE: The driver supports synchronous speeds higher than 10MB/s, but this
 // has not been tested due to lack of fast enough SCSI adapter.
@@ -52,6 +54,9 @@ void azplatform_init();
 // Initialization for main application, not used for bootloader
 void azplatform_late_init();
 
+// Query whether initiator mode is enabled on targets with PLATFORM_HAS_INITIATOR_MODE
+bool azplatform_is_initiator_mode_enabled();
+
 // Setup soft watchdog if supported
 void azplatform_reset_watchdog();
 
@@ -81,6 +86,15 @@ void azplatform_boot_to_main_firmware();
 #define SCSI_IN(pin) \
     ((sio_hw->gpio_in & (1 << (SCSI_IN_ ## pin))) ? 0 : 1)
 
+// Set pin directions for initiator vs. target mode
+#define SCSI_ENABLE_INITIATOR() \
+    (sio_hw->gpio_oe_set = (1 << SCSI_OUT_ACK) | \
+                           (1 << SCSI_OUT_ATN)), \
+    (sio_hw->gpio_oe_clr = (1 << SCSI_IN_IO) | \
+                           (1 << SCSI_IN_CD) | \
+                           (1 << SCSI_IN_MSG) | \
+                           (1 << SCSI_IN_REQ))
+
 // Enable driving of shared control pins
 #define SCSI_ENABLE_CONTROL_OUT() \
     (sio_hw->gpio_oe_set = (1 << SCSI_OUT_CD) | \
@@ -117,7 +131,7 @@ extern const uint32_t g_scsi_parity_lookup[256];
                        (1 << SCSI_OUT_SEL)
 
 // Read SCSI data bus
-#define SCSI_IN_DATA(data) \
+#define SCSI_IN_DATA() \
     (~sio_hw->gpio_in & SCSI_IO_DATA_MASK) >> SCSI_IO_SHIFT
 
 #ifdef __cplusplus

+ 10 - 0
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_gpio.h

@@ -38,6 +38,16 @@
 #define SCSI_IN_BSY  13
 #define SCSI_IN_RST  27
 
+// Status line outputs for initiator mode
+#define SCSI_OUT_ACK  10
+#define SCSI_OUT_ATN  29
+
+// Status line inputs for initiator mode
+#define SCSI_IN_IO    12
+#define SCSI_IN_CD    11
+#define SCSI_IN_MSG   13
+#define SCSI_IN_REQ   9
+
 // Status LED pins
 #define LED_PIN      25
 #define LED_ON()     sio_hw->gpio_set = 1 << LED_PIN

+ 6 - 0
lib/ZuluSCSI_platform_RP2040/rp2040.ld

@@ -65,6 +65,12 @@ SECTIONS
         *libc*:*printf*(.text .text*)
         *libc*:*toa*(.text .text*)
         *libminIni.a:(.text .text*)
+
+        /* RP2040 breakpoints in RAM code don't always work very well
+         * because the boot routine tends to overwrite them.
+         * Uncommenting this line puts all code in flash.
+         */
+        /* *(.text .text*) */
     } > FLASH
     .rodata : {
         . = ALIGN(4);

+ 191 - 0
lib/ZuluSCSI_platform_RP2040/scsiHostPhy.cpp

@@ -0,0 +1,191 @@
+#include "scsiHostPhy.h"
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_log_trace.h"
+#include <assert.h>
+
+#include <scsi2sd.h>
+extern "C" {
+#include <scsi.h>
+}
+
+volatile bool g_scsiHostPhyReset;
+
+// Release bus and pulse RST signal, initialize PHY to host mode.
+void scsiHostPhyReset(void)
+{
+    SCSI_RELEASE_OUTPUTS();
+    SCSI_ENABLE_INITIATOR();
+
+    SCSI_OUT(RST, 1);
+    delay(2);
+    SCSI_OUT(RST, 0);
+    delay(250);
+    g_scsiHostPhyReset = false;
+}
+
+// Select a device, id 0-7.
+// Returns true if the target answers to selection request.
+bool scsiHostPhySelect(int target_id)
+{
+    SCSI_RELEASE_OUTPUTS();
+
+    // We can't write individual data bus bits, so use a bit modified
+    // arbitration scheme. We always yield to any other initiator on
+    // the bus.
+    scsiLogInitiatorPhaseChange(BUS_BUSY);
+    SCSI_OUT(BSY, 1);
+    for (int wait = 0; wait < 10; wait++)
+    {
+        delayMicroseconds(1);
+
+        if (SCSI_IN_DATA() != 0)
+        {
+            azdbg("scsiHostPhySelect: bus is busy");
+            scsiLogInitiatorPhaseChange(BUS_FREE);
+            SCSI_RELEASE_OUTPUTS();
+            return false;
+        }
+    }
+
+    // Selection phase
+    scsiLogInitiatorPhaseChange(SELECTION);
+    azdbg("------ SELECTING ", target_id);
+    SCSI_OUT(SEL, 1);
+    delayMicroseconds(5);
+    SCSI_OUT_DATA(1 << target_id);
+    delayMicroseconds(5);
+    SCSI_OUT(BSY, 0);
+
+    // Wait for target to respond
+    for (int wait = 0; wait < 250; wait++)
+    {
+        delayMicroseconds(1000);
+        if (SCSI_IN(BSY))
+        {
+            break;
+        }
+    }
+
+    if (!SCSI_IN(BSY))
+    {
+        // No response
+        SCSI_RELEASE_OUTPUTS();
+        return false;
+    }
+
+    // We need to assert OUT_BSY to enable IO buffer U105 to read status signals.
+    SCSI_OUT(BSY, 1);
+    SCSI_OUT(SEL, 0);
+    return true;
+}
+
+// Read the current communication phase as signaled by the target
+int scsiHostPhyGetPhase()
+{
+    // Disable OUT_BSY for a short time to see if the target is still on line
+    SCSI_OUT(BSY, 0);
+    delay_100ns();
+
+    if (!SCSI_IN(BSY))
+    {
+        scsiLogInitiatorPhaseChange(BUS_FREE);
+        return BUS_FREE;
+    }
+
+    // Re-enable OUT_BSY to read signal states
+    SCSI_OUT(BSY, 1);
+    delay_100ns();
+
+    int phase = 0;
+    if (SCSI_IN(CD)) phase |= __scsiphase_cd;
+    if (SCSI_IN(IO)) phase |= __scsiphase_io;
+    if (SCSI_IN(MSG)) phase |= __scsiphase_msg;
+
+    scsiLogInitiatorPhaseChange(phase);
+    return phase;
+}
+
+bool scsiHostRequestWaiting()
+{
+    return SCSI_IN(REQ);
+}
+
+// Blocking data transfer
+#define SCSIHOST_WAIT_ACTIVE(pin) \
+  if (!SCSI_IN(pin)) { \
+    if (!SCSI_IN(pin)) { \
+      while(!SCSI_IN(pin) && !g_scsiHostPhyReset); \
+    } \
+  }
+
+#define SCSIHOST_WAIT_INACTIVE(pin) \
+  if (SCSI_IN(pin)) { \
+    if (SCSI_IN(pin)) { \
+      while(SCSI_IN(pin) && !g_scsiHostPhyReset); \
+    } \
+  }
+
+// Write one byte to SCSI target using the handshake mechanism
+static inline void scsiHostWriteOneByte(uint8_t value)
+{
+    SCSIHOST_WAIT_ACTIVE(REQ);
+    SCSI_OUT_DATA(value);
+    delay_100ns(); // DB setup time before ACK
+    SCSI_OUT(ACK, 1);
+    SCSIHOST_WAIT_INACTIVE(REQ);
+    SCSI_RELEASE_DATA_REQ();
+    SCSI_OUT(ACK, 0);
+}
+
+// Read one byte from SCSI target using the handshake mechanism.
+static inline uint8_t scsiHostReadOneByte(int* parityError)
+{
+    SCSIHOST_WAIT_ACTIVE(REQ);
+    uint16_t r = SCSI_IN_DATA();
+    SCSI_OUT(ACK, 1);
+    SCSIHOST_WAIT_INACTIVE(REQ);
+    SCSI_OUT(ACK, 0);
+
+    if (parityError && r != (g_scsi_parity_lookup[r & 0xFF] ^ SCSI_IO_DATA_MASK))
+    {
+        azlog("Parity error in scsiReadOneByte(): ", (uint32_t)r);
+        *parityError = 1;
+    }
+
+    return (uint8_t)r;
+}
+
+bool scsiHostWrite(const uint8_t *data, uint32_t count)
+{
+    scsiLogDataOut(data, count);
+
+    for (uint32_t i = 0; i < count; i++)
+    {
+        if (g_scsiHostPhyReset) return false;
+
+        scsiHostWriteOneByte(data[i]);
+    }
+
+    return true;
+}
+
+bool scsiHostRead(uint8_t *data, uint32_t count)
+{
+    for (uint32_t i = 0; i < count; i++)
+    {
+        if (g_scsiHostPhyReset) return false;
+
+        data[i] = scsiHostReadOneByte(NULL);
+    }
+
+    scsiLogDataIn(data, count);
+    return true;
+}
+
+// Release all bus signals
+void scsiHostPhyRelease()
+{
+    scsiLogInitiatorPhaseChange(BUS_FREE);
+    SCSI_RELEASE_OUTPUTS();
+}

+ 31 - 0
lib/ZuluSCSI_platform_RP2040/scsiHostPhy.h

@@ -0,0 +1,31 @@
+// Host side SCSI physical interface.
+// Used in initiator to interface to an SCSI drive.
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+// Request to stop activity and reset the bus
+extern volatile bool g_scsiHostPhyReset;
+
+// Release bus and pulse RST signal, initialize PHY to host mode.
+void scsiHostPhyReset(void);
+
+// Select a device, id 0-7.
+// Returns true if the target answers to selection request.
+bool scsiHostPhySelect(int target_id);
+
+// Read the current communication phase as signaled by the target
+// Matches SCSI_PHASE enumeration from scsi.h.
+int scsiHostPhyGetPhase();
+
+// Returns true if the device has asserted REQ signal, i.e. data waiting
+bool scsiHostRequestWaiting();
+
+// Blocking data transfer
+bool scsiHostWrite(const uint8_t *data, uint32_t count);
+bool scsiHostRead(uint8_t *data, uint32_t count);
+
+// Release all bus signals
+void scsiHostPhyRelease();

+ 33 - 6
src/ZuluSCSI.cpp

@@ -50,6 +50,7 @@
 #include "ZuluSCSI_log.h"
 #include "ZuluSCSI_log_trace.h"
 #include "ZuluSCSI_disk.h"
+#include "ZuluSCSI_initiator.h"
 
 SdFs SD;
 FsFile g_logfile;
@@ -386,6 +387,21 @@ void readSCSIDeviceConfig()
 
 static void reinitSCSI()
 {
+#ifdef PLATFORM_HAS_INITIATOR_MODE
+  if (azplatform_is_initiator_mode_enabled())
+  {
+    // Initialize scsiDev to zero values even though it is not used
+    scsiInit();
+
+    // Initializer initiator mode state machine
+    scsiInitiatorInit();
+
+    blinkStatus(BLINK_STATUS_OK);
+
+    return;
+  }
+#endif
+
   scsiDiskResetImages();
   readSCSIDeviceConfig();
   findHDDImages();
@@ -454,15 +470,26 @@ extern "C" void zuluscsi_main_loop(void)
   static uint32_t sd_card_check_time = 0;
 
   azplatform_reset_watchdog();
-  scsiPoll();
-  scsiDiskPoll();
-  scsiLogPhaseChange(scsiDev.phase);
-
-  // Save log periodically during status phase if there are new messages.
-  if (scsiDev.phase == STATUS)
+  
+#ifdef PLATFORM_HAS_INITIATOR_MODE
+  if (azplatform_is_initiator_mode_enabled())
   {
+    scsiInitiatorMainLoop();
     save_logfile();
   }
+  else
+#endif
+  {
+    scsiPoll();
+    scsiDiskPoll();
+    scsiLogPhaseChange(scsiDev.phase);
+
+    // Save log periodically during status phase if there are new messages.
+    if (scsiDev.phase == STATUS)
+    {
+      save_logfile();
+    }
+  }
 
   // Check SD card status for hotplug
   if (scsiDev.phase == BUS_FREE &&

+ 2 - 0
src/ZuluSCSI_config.h

@@ -3,6 +3,8 @@
 
 #pragma once
 
+#include <ZuluSCSI_platform.h>
+
 // Use variables for version number
 #define FW_VER_NUM      "1.0.9"
 #define FW_VER_SUFFIX   "devel"

+ 164 - 0
src/ZuluSCSI_initiator.cpp

@@ -0,0 +1,164 @@
+/*
+ *  ZuluSCSI
+ *  Copyright (c) 2022 Rabbit Hole Computing
+ * 
+ * Main program for initiator mode.
+ */
+
+#include "ZuluSCSI_config.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_log_trace.h"
+#include "ZuluSCSI_initiator.h"
+#include <ZuluSCSI_platform.h>
+
+#include <scsi2sd.h>
+extern "C" {
+#include <scsi.h>
+}
+
+#ifndef PLATFORM_HAS_INITIATOR_MODE
+
+void scsiInitiatorInit()
+{
+}
+
+void scsiInitiatorMainLoop()
+{
+}
+
+int scsiInitiatorRunCommand(const uint8_t *command, size_t cmdlen,
+                            uint8_t *bufIn, size_t bufInLen,
+                            const uint8_t *bufOut, size_t bufOutLen)
+{
+    return -1;
+}
+
+bool scsiInitiatorReadCapacity(int target_id, uint32_t *sectorcount, uint32_t *sectorsize)
+{
+    return false;
+}
+
+#else
+
+/*************************************
+ * High level initiator mode logic   *
+ *************************************/
+
+static uint32_t g_initiator_drives_imaged;
+static int g_initiator_next_id;
+
+// Initialization of initiator mode
+void scsiInitiatorInit()
+{
+    scsiHostPhyReset();
+
+    g_initiator_drives_imaged = 0;
+    g_initiator_next_id = 0;
+}
+
+// High level logic of the initiator mode
+void scsiInitiatorMainLoop()
+{
+    // Scan for SCSI drives one at a time
+    g_initiator_next_id = (g_initiator_next_id + 1) % 8;
+
+    uint32_t sectorcount, sectorsize;
+    if (scsiInitiatorReadCapacity(g_initiator_next_id, &sectorcount, &sectorsize))
+    {
+        azlog("SCSI id ", g_initiator_next_id, " capacity ", (int)sectorcount, " sectors x ", (int)sectorsize, " bytes");
+    }
+
+    delay(1000);
+}
+
+/*************************************
+ * Low level command implementations *
+ *************************************/
+
+int scsiInitiatorRunCommand(int target_id,
+                            const uint8_t *command, size_t cmdLen,
+                            uint8_t *bufIn, size_t bufInLen,
+                            const uint8_t *bufOut, size_t bufOutLen)
+{
+    if (!scsiHostPhySelect(target_id))
+    {
+        azdbg("------ Target ", target_id, " did not respond");
+        scsiHostPhyRelease();
+        return -1;
+    }
+
+    SCSI_PHASE phase;
+    int status = -1;
+    while ((phase = (SCSI_PHASE)scsiHostPhyGetPhase()) != BUS_FREE)
+    {
+        if (!scsiHostRequestWaiting())
+        {
+            // Wait for target to assert REQ before dealing with the new phase.
+            // This way we don't react to any spurious status signal changes.
+        }
+        else if (phase == MESSAGE_IN)
+        {
+            uint8_t dummy = 0;
+            scsiHostRead(&dummy, 1);
+        }
+        else if (phase == MESSAGE_OUT)
+        {
+            uint8_t identify_msg = 0x80;
+            scsiHostWrite(&identify_msg, 1);
+        }
+        else if (phase == COMMAND)
+        {
+            scsiHostWrite(command, cmdLen);
+        }
+        else if (phase == DATA_IN)
+        {
+            scsiHostRead(bufIn, bufInLen);
+        }
+        else if (phase == DATA_OUT)
+        {
+            scsiHostWrite(bufOut, bufOutLen);
+        }
+        else if (phase == STATUS)
+        {
+            uint8_t tmp = 0;
+            scsiHostRead(&tmp, 1);
+            status = tmp;
+            azdbg("------ STATUS: ", tmp);
+        }
+    }
+
+    return status;
+}
+
+bool scsiInitiatorReadCapacity(int target_id, uint32_t *sectorcount, uint32_t *sectorsize)
+{
+    uint8_t command[10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    uint8_t response[8] = {0};
+    int status = scsiInitiatorRunCommand(target_id,
+                                         command, sizeof(command),
+                                         response, sizeof(response),
+                                         NULL, 0);
+    
+    if (status == 0)
+    {
+        *sectorcount = ((uint32_t)response[0] << 24)
+                    | ((uint32_t)response[1] << 16)
+                    | ((uint32_t)response[2] <<  8)
+                    | ((uint32_t)response[3] <<  0);
+        
+        *sectorsize = ((uint32_t)response[4] << 24)
+                    | ((uint32_t)response[5] << 16)
+                    | ((uint32_t)response[6] <<  8)
+                    | ((uint32_t)response[7] <<  0);
+
+        return true;
+    }
+    else
+    {
+        *sectorcount = *sectorsize = 0;
+        return false;
+    }
+} 
+
+
+#endif

+ 19 - 0
src/ZuluSCSI_initiator.h

@@ -0,0 +1,19 @@
+// Main state machine for SCSI initiator mode
+
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+
+void scsiInitiatorInit();
+
+void scsiInitiatorMainLoop();
+
+// Select target and execute SCSI command
+int scsiInitiatorRunCommand(int target_id,
+                            const uint8_t *command, size_t cmdLen,
+                            uint8_t *bufIn, size_t bufInLen,
+                            const uint8_t *bufOut, size_t bufOutLen);
+
+// Execute READ CAPACITY command
+bool scsiInitiatorReadCapacity(int target_id, uint32_t *sectorcount, uint32_t *sectorsize);

+ 35 - 6
src/ZuluSCSI_log_trace.cpp

@@ -10,6 +10,7 @@ extern "C" {
 }
 
 static bool g_LogData = false;
+static bool g_LogInitiatorCommand = false;
 static int g_InByteCount = 0;
 static int g_OutByteCount = 0;
 static uint16_t g_DataChecksum = 0;
@@ -60,9 +61,10 @@ static const char *getCommandName(uint8_t cmd)
     }
 }
 
-static void printNewPhase(int phase)
+static void printNewPhase(int phase, bool initiator = false)
 {
     g_LogData = false;
+    g_LogInitiatorCommand = false;
     if (!g_azlog_debug)
     {
         return;
@@ -83,7 +85,10 @@ static void printNewPhase(int phase)
             break;
         
         case SELECTION:
-            azdbg("---- SELECTION: ", (int)(*SCSI_STS_SELECTED & 7));
+            if (initiator)
+                azdbg("---- SELECTION");
+            else
+                azdbg("---- SELECTION: ", (int)(*SCSI_STS_SELECTED & 7));
             break;
         
         case RESELECTION:
@@ -91,7 +96,12 @@ static void printNewPhase(int phase)
             break;
         
         case STATUS:
-            if (scsiDev.status == GOOD)
+            if (initiator)
+            {
+                azdbg("---- STATUS");
+                g_LogData = true;
+            }
+            else if (scsiDev.status == GOOD)
             {
                 azdbg("---- STATUS: 0 GOOD");
             }
@@ -106,11 +116,12 @@ static void printNewPhase(int phase)
             break;
         
         case COMMAND:
+            g_LogInitiatorCommand = initiator;
             g_LogData = true;
             break;
         
         case DATA_IN:
-            if (scsiDev.target->syncOffset > 0)
+            if (!initiator && scsiDev.target->syncOffset > 0)
                 azdbg("---- DATA_IN, syncOffset ", (int)scsiDev.target->syncOffset,
                                    " syncPeriod ", (int)scsiDev.target->syncPeriod);
             else
@@ -118,7 +129,7 @@ static void printNewPhase(int phase)
             break;
         
         case DATA_OUT:
-            if (scsiDev.target->syncOffset > 0)
+            if (!initiator && scsiDev.target->syncOffset > 0)
                 azdbg("---- DATA_OUT, syncOffset ", (int)scsiDev.target->syncOffset,
                                     " syncPeriod ", (int)scsiDev.target->syncPeriod);
             else
@@ -177,6 +188,24 @@ void scsiLogPhaseChange(int new_phase)
     }
 }
 
+void scsiLogInitiatorPhaseChange(int new_phase)
+{
+    static int old_phase = BUS_FREE;
+
+    if (new_phase != old_phase)
+    {
+        if (old_phase == DATA_IN || old_phase == DATA_OUT)
+        {
+            azdbg("---- Total IN: ", g_InByteCount, " OUT: ", g_OutByteCount, " CHECKSUM: ", (int)g_DataChecksum);
+        }
+        g_InByteCount = g_OutByteCount = 0;
+        g_DataChecksum = 0;
+
+        printNewPhase(new_phase, true);
+        old_phase = new_phase;
+    }
+}
+
 void scsiLogDataIn(const uint8_t *buf, uint32_t length)
 {
     if (g_LogData)
@@ -199,7 +228,7 @@ void scsiLogDataIn(const uint8_t *buf, uint32_t length)
 
 void scsiLogDataOut(const uint8_t *buf, uint32_t length)
 {
-    if (buf == scsiDev.cdb)
+    if (buf == scsiDev.cdb || g_LogInitiatorCommand)
     {
         azdbg("---- COMMAND: ", getCommandName(buf[0]));
     }

+ 1 - 0
src/ZuluSCSI_log_trace.h

@@ -6,5 +6,6 @@
 
 // Called from scsiPhy.cpp
 void scsiLogPhaseChange(int new_phase);
+void scsiLogInitiatorPhaseChange(int new_phase);
 void scsiLogDataIn(const uint8_t *buf, uint32_t length);
 void scsiLogDataOut(const uint8_t *buf, uint32_t length);