瀏覽代碼

Merge pull request #75 from ZuluSCSI/rp2040_initiator

RP2040 initiator mode support
Alex Perez 3 年之前
父節點
當前提交
4e09cda87f

+ 22 - 0
README.md

@@ -76,6 +76,28 @@ For ZuluSCSI V1.1, the DIP switch settings are as follows:
 
 
 ZuluSCSI Mini has no DIP switches, so all optional configuration parameters must be defined in zuluscsi.ini
 ZuluSCSI Mini has no DIP switches, so all optional configuration parameters must be defined in zuluscsi.ini
 
 
+ZuluSCSI RP2040 DIP switch settings are:
+- INITIATOR: Enable SCSI initiator mode for imaging SCSI drives
+- DEBUG LOG: Enable verbose debug log (saved to `Zululog.txt`)
+- TERMINATION: Enable SCSI termination
+- BOOTLOADER: Enable built-in USB bootloader, this DIP switch MUST remain off during normal operation.
+
+SCSI initiator mode
+-------------------
+The RP2040 model supports SCSI initiator mode for reading SCSI drives.
+When enabled by the DIP switch, ZuluSCSI RP2040 will scan for SCSI drives on the bus and copy the data as `HDxx_imaged.hda` to the SD card.
+
+LED indications in initiator mode:
+
+- Short blink once a second: idle, searching for SCSI drives
+- Slow blink every 5 seconds: copying data. The blink acts as a progress bar: first it is short and becomes longer when data copying progresses.
+
+The firmware retries reads up to 5 times and attempts to skip any sectors that have problems.
+Any read errors are logged into `zululog.txt`.
+
+Depending on hardware setup, you may need to mount diode `D205` and jumper `JP201` to supply `TERMPWR` to the SCSI bus.
+This is necessary if the drives do not supply their own SCSI terminator power.
+
 Project structure
 Project structure
 -----------------
 -----------------
 - **src/ZuluSCSI.cpp**: Main portable SCSI implementation.
 - **src/ZuluSCSI.cpp**: Main portable SCSI implementation.

+ 7 - 6
lib/ZuluSCSI_platform_GD32F205/sd_card_sdio.cpp

@@ -222,7 +222,7 @@ static void sdio_callback(uint32_t complete)
     }
     }
 }
 }
 
 
-static sdio_callback_t get_stream_callback(const uint8_t *buf, uint32_t count)
+static sdio_callback_t get_stream_callback(const uint8_t *buf, uint32_t count, const char *accesstype, uint32_t sector)
 {
 {
     m_stream_count_start = m_stream_count;
     m_stream_count_start = m_stream_count;
 
 
@@ -235,7 +235,8 @@ static sdio_callback_t get_stream_callback(const uint8_t *buf, uint32_t count)
         }
         }
         else
         else
         {
         {
-            azdbg("Stream buffer mismatch: ", (uint32_t)buf, " vs. ", (uint32_t)(m_stream_buffer + m_stream_count));
+            azdbg("SD card ", accesstype, "(", (int)sector,
+                  ") slow transfer, buffer", (uint32_t)buf, " vs. ", (uint32_t)(m_stream_buffer + m_stream_count));
             return NULL;
             return NULL;
         }
         }
     }
     }
@@ -247,19 +248,19 @@ static sdio_callback_t get_stream_callback(const uint8_t *buf, uint32_t count)
 bool SdioCard::writeSector(uint32_t sector, const uint8_t* src)
 bool SdioCard::writeSector(uint32_t sector, const uint8_t* src)
 {
 {
     return checkReturnOk(sd_block_write((uint32_t*)src, (uint64_t)sector * 512, 512,
     return checkReturnOk(sd_block_write((uint32_t*)src, (uint64_t)sector * 512, 512,
-        get_stream_callback(src, 512)));
+        get_stream_callback(src, 512, "writeSector", sector)));
 }
 }
 
 
 bool SdioCard::writeSectors(uint32_t sector, const uint8_t* src, size_t n)
 bool SdioCard::writeSectors(uint32_t sector, const uint8_t* src, size_t n)
 {
 {
     return checkReturnOk(sd_multiblocks_write((uint32_t*)src, (uint64_t)sector * 512, 512, n,
     return checkReturnOk(sd_multiblocks_write((uint32_t*)src, (uint64_t)sector * 512, 512, n,
-        get_stream_callback(src, n * 512)));
+        get_stream_callback(src, n * 512, "writeSectors", sector)));
 }
 }
 
 
 bool SdioCard::readSector(uint32_t sector, uint8_t* dst)
 bool SdioCard::readSector(uint32_t sector, uint8_t* dst)
 {
 {
     return checkReturnOk(sd_block_read((uint32_t*)dst, (uint64_t)sector * 512, 512,
     return checkReturnOk(sd_block_read((uint32_t*)dst, (uint64_t)sector * 512, 512,
-        get_stream_callback(dst, 512)));
+        get_stream_callback(dst, 512, "readSector", sector)));
 }
 }
 
 
 bool SdioCard::readSectors(uint32_t sector, uint8_t* dst, size_t n)
 bool SdioCard::readSectors(uint32_t sector, uint8_t* dst, size_t n)
@@ -279,7 +280,7 @@ bool SdioCard::readSectors(uint32_t sector, uint8_t* dst, size_t n)
     }
     }
 
 
     return checkReturnOk(sd_multiblocks_read((uint32_t*)dst, (uint64_t)sector * 512, 512, n,
     return checkReturnOk(sd_multiblocks_read((uint32_t*)dst, (uint64_t)sector * 512, 512, n,
-        get_stream_callback(dst, n * 512)));
+        get_stream_callback(dst, n * 512, "readSectors", sector)));
 }
 }
 
 
 // Check if a DMA request for SD card read has completed.
 // Check if a DMA request for SD card read has completed.

+ 146 - 52
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp

@@ -17,6 +17,7 @@ extern "C" {
 #include <hardware/flash.h>
 #include <hardware/flash.h>
 
 
 const char *g_azplatform_name = PLATFORM_NAME;
 const char *g_azplatform_name = PLATFORM_NAME;
+static bool g_scsi_initiator = false;
 
 
 void mbed_error_hook(const mbed_error_ctx * error_context);
 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
     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 dbglog = !gpio_get(DIP_DBGLOG);
     bool termination = !gpio_get(DIP_TERM);
     bool termination = !gpio_get(DIP_TERM);
 
 
@@ -65,12 +65,7 @@ void azplatform_init()
     uart_init(uart0, 1000000);
     uart_init(uart0, 1000000);
     mbed_set_error_hook(mbed_error_hook);
     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;
     g_azlog_debug = dbglog;
     
     
@@ -83,7 +78,76 @@ void azplatform_init()
         azlog("NOTE: SCSI termination is disabled");
         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.
      * 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_DB7,    GPIO_FUNC_SIO, true, false, false, true, true);
     gpio_conf(SCSI_IO_DBP,    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;
 }
 }
 
 
 /*****************************************/
 /*****************************************/
@@ -243,15 +307,45 @@ static void watchdog_callback(unsigned alarm_num)
 
 
     if (g_watchdog_timeout <= WATCHDOG_CRASH_TIMEOUT - WATCHDOG_BUS_RESET_TIMEOUT)
     if (g_watchdog_timeout <= WATCHDOG_CRASH_TIMEOUT - WATCHDOG_BUS_RESET_TIMEOUT)
     {
     {
-        if (!scsiDev.resetFlag)
+        if (!scsiDev.resetFlag || !g_scsiHostPhyReset)
         {
         {
+            azlog("--------------");
             azlog("WATCHDOG TIMEOUT, attempting bus reset");
             azlog("WATCHDOG TIMEOUT, attempting bus reset");
+            azlog("GPIO states: out ", sio_hw->gpio_out, " oe ", sio_hw->gpio_oe, " in ", sio_hw->gpio_in);
+
+            uint32_t *p = (uint32_t*)__get_PSP();
+            for (int i = 0; i < 8; i++)
+            {
+                if (p == &__StackTop) break; // End of stack
+
+                azlog("STACK ", (uint32_t)p, ":    ", p[0], " ", p[1], " ", p[2], " ", p[3]);
+                p += 4;
+            }
+
             scsiDev.resetFlag = 1;
             scsiDev.resetFlag = 1;
+            g_scsiHostPhyReset = true;
         }
         }
 
 
         if (g_watchdog_timeout <= 0)
         if (g_watchdog_timeout <= 0)
         {
         {
-            assert(false);
+            azlog("--------------");
+            azlog("WATCHDOG TIMEOUT!");
+            azlog("Platform: ", g_azplatform_name);
+            azlog("FW Version: ", g_azlog_firmwareversion);
+            azlog("GPIO states: out ", sio_hw->gpio_out, " oe ", sio_hw->gpio_oe, " in ", sio_hw->gpio_in);
+
+            uint32_t *p = (uint32_t*)__get_PSP();
+            for (int i = 0; i < 8; i++)
+            {
+                if (p == &__StackTop) break; // End of stack
+
+                azlog("STACK ", (uint32_t)p, ":    ", p[0], " ", p[1], " ", p[2], " ", p[3]);
+                p += 4;
+            }
+
+            azplatform_emergency_log_save();
+
+            azplatform_boot_to_main_firmware();
         }
         }
     }
     }
 
 

+ 17 - 3
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.h

@@ -5,6 +5,7 @@
 #include <stdint.h>
 #include <stdint.h>
 #include <Arduino.h>
 #include <Arduino.h>
 #include "ZuluSCSI_platform_gpio.h"
 #include "ZuluSCSI_platform_gpio.h"
+#include "scsiHostPhy.h"
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
@@ -16,9 +17,10 @@ extern const char *g_azplatform_name;
 #define PLATFORM_REVISION "2.0"
 #define PLATFORM_REVISION "2.0"
 #define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_SYNC_10
 #define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_SYNC_10
 #define PLATFORM_OPTIMAL_MIN_SD_WRITE_SIZE 4096
 #define PLATFORM_OPTIMAL_MIN_SD_WRITE_SIZE 4096
-#define PLATFORM_OPTIMAL_MAX_SD_WRITE_SIZE 65536
+#define PLATFORM_OPTIMAL_MAX_SD_WRITE_SIZE 32768
 #define PLATFORM_OPTIMAL_LAST_SD_WRITE_SIZE 8192
 #define PLATFORM_OPTIMAL_LAST_SD_WRITE_SIZE 8192
 #define SD_USE_SDIO 1
 #define SD_USE_SDIO 1
+#define PLATFORM_HAS_INITIATOR_MODE 1
 
 
 // NOTE: The driver supports synchronous speeds higher than 10MB/s, but this
 // NOTE: The driver supports synchronous speeds higher than 10MB/s, but this
 // has not been tested due to lack of fast enough SCSI adapter.
 // has not been tested due to lack of fast enough SCSI adapter.
@@ -43,7 +45,7 @@ static inline void delay_ns(unsigned long ns)
 // Approximate fast delay
 // Approximate fast delay
 static inline void delay_100ns()
 static inline void delay_100ns()
 {
 {
-    asm volatile ("nop \n nop \n nop \n nop \n nop");
+    asm volatile ("nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop");
 }
 }
 
 
 // Initialize SD card and GPIO configuration
 // Initialize SD card and GPIO configuration
@@ -52,6 +54,9 @@ void azplatform_init();
 // Initialization for main application, not used for bootloader
 // Initialization for main application, not used for bootloader
 void azplatform_late_init();
 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
 // Setup soft watchdog if supported
 void azplatform_reset_watchdog();
 void azplatform_reset_watchdog();
 
 
@@ -81,6 +86,15 @@ void azplatform_boot_to_main_firmware();
 #define SCSI_IN(pin) \
 #define SCSI_IN(pin) \
     ((sio_hw->gpio_in & (1 << (SCSI_IN_ ## pin))) ? 0 : 1)
     ((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
 // Enable driving of shared control pins
 #define SCSI_ENABLE_CONTROL_OUT() \
 #define SCSI_ENABLE_CONTROL_OUT() \
     (sio_hw->gpio_oe_set = (1 << SCSI_OUT_CD) | \
     (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)
                        (1 << SCSI_OUT_SEL)
 
 
 // Read SCSI data bus
 // Read SCSI data bus
-#define SCSI_IN_DATA(data) \
+#define SCSI_IN_DATA() \
     (~sio_hw->gpio_in & SCSI_IO_DATA_MASK) >> SCSI_IO_SHIFT
     (~sio_hw->gpio_in & SCSI_IO_DATA_MASK) >> SCSI_IO_SHIFT
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus

+ 10 - 0
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_gpio.h

@@ -38,6 +38,16 @@
 #define SCSI_IN_BSY  13
 #define SCSI_IN_BSY  13
 #define SCSI_IN_RST  27
 #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
 // Status LED pins
 #define LED_PIN      25
 #define LED_PIN      25
 #define LED_ON()     sio_hw->gpio_set = 1 << LED_PIN
 #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*:*printf*(.text .text*)
         *libc*:*toa*(.text .text*)
         *libc*:*toa*(.text .text*)
         *libminIni.a:(.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
     } > FLASH
     .rodata : {
     .rodata : {
         . = ALIGN(4);
         . = ALIGN(4);

+ 238 - 0
lib/ZuluSCSI_platform_RP2040/scsiHostPhy.cpp

@@ -0,0 +1,238 @@
+#include "scsiHostPhy.h"
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_log_trace.h"
+#include "scsi_accel_host.h"
+#include <assert.h>
+
+#include <scsi2sd.h>
+extern "C" {
+#include <scsi.h>
+}
+
+volatile int g_scsiHostPhyReset;
+
+// Release bus and pulse RST signal, initialize PHY to host mode.
+void scsiHostPhyReset(void)
+{
+    SCSI_RELEASE_OUTPUTS();
+    SCSI_ENABLE_INITIATOR();
+
+    scsi_accel_host_init();
+
+    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 < 2500; wait++)
+    {
+        delayMicroseconds(100);
+        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_RELEASE_DATA_REQ();
+    SCSI_OUT(BSY, 1);
+    SCSI_OUT(SEL, 0);
+    return true;
+}
+
+// Read the current communication phase as signaled by the target
+int scsiHostPhyGetPhase()
+{
+    static absolute_time_t last_online_time;
+
+    if (g_scsiHostPhyReset)
+    {
+        // Reset request from watchdog timer
+        scsiHostPhyRelease();
+        return BUS_FREE;
+    }
+
+    int phase = 0;
+    bool req_in = SCSI_IN(REQ);
+    if (SCSI_IN(CD)) phase |= __scsiphase_cd;
+    if (SCSI_IN(IO)) phase |= __scsiphase_io;
+    if (SCSI_IN(MSG)) phase |= __scsiphase_msg;
+
+    if (phase == 0 && absolute_time_diff_us(last_online_time, get_absolute_time()) > 100)
+    {
+        // Disable OUT_BSY for a short time to see if the target is still on line
+        SCSI_OUT(BSY, 0);
+        delayMicroseconds(1);
+
+        if (!SCSI_IN(BSY))
+        {
+            scsiLogInitiatorPhaseChange(BUS_FREE);
+            return BUS_FREE;
+        }
+
+        // Still online, re-enable OUT_BSY to enable IO buffers
+        SCSI_OUT(BSY, 1);
+        last_online_time = get_absolute_time();
+    }
+    else if (phase != 0)
+    {
+        last_online_time = get_absolute_time();
+    }
+
+    if (!req_in)
+    {
+        // Don't act on phase changes until target asserts request signal.
+        // This filters out any spurious changes on control signals.
+        return BUS_BUSY;
+    }
+    else
+    {
+        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)
+{
+    int parityError = 0;
+
+    if ((count & 1) == 0)
+    {
+        // Even number of bytes, use accelerated routine
+        scsi_accel_host_read(data, count, &parityError, &g_scsiHostPhyReset);
+    }
+    else
+    {
+        for (uint32_t i = 0; i < count; i++)
+        {
+            if (g_scsiHostPhyReset) return false;
+
+            data[i] = scsiHostReadOneByte(&parityError);
+        }
+    }
+
+    if (parityError || g_scsiHostPhyReset)
+    {
+        return false;
+    }
+    else
+    {
+        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 int 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();

+ 131 - 0
lib/ZuluSCSI_platform_RP2040/scsi_accel_host.cpp

@@ -0,0 +1,131 @@
+// Accelerated SCSI subroutines for SCSI initiator/host side communication
+
+#include "scsi_accel_host.h"
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+#include "scsi_accel_host.pio.h"
+#include <hardware/pio.h>
+#include <hardware/dma.h>
+#include <hardware/irq.h>
+#include <hardware/structs/iobank0.h>
+#include <hardware/sync.h>
+
+#define SCSI_PIO pio0
+#define SCSI_SM 0
+
+static struct {
+    // PIO configurations
+    uint32_t pio_offset_async_read;
+    pio_sm_config pio_cfg_async_read;
+} g_scsi_host;
+
+enum scsidma_state_t { SCSIHOST_IDLE = 0,
+                       SCSIHOST_READ };
+static volatile scsidma_state_t g_scsi_host_state;
+
+static void scsi_accel_host_config_gpio()
+{
+    if (g_scsi_host_state == SCSIHOST_IDLE)
+    {
+        iobank0_hw->io[SCSI_IO_DB0].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB1].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB2].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB3].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB4].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB5].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB6].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB7].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DBP].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_OUT_ACK].ctrl = GPIO_FUNC_SIO;
+    }
+    else if (g_scsi_host_state == SCSIHOST_READ)
+    {
+        // Data bus and REQ as input, ACK pin as output
+        pio_sm_set_pins(SCSI_PIO, SCSI_SM, 0x7FF);
+        pio_sm_set_consecutive_pindirs(SCSI_PIO, SCSI_SM, 0, 10, false);
+        pio_sm_set_consecutive_pindirs(SCSI_PIO, SCSI_SM, 10, 1, true);
+
+        iobank0_hw->io[SCSI_IO_DB0].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB1].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB2].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB3].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB4].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB5].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB6].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DB7].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_IO_DBP].ctrl  = GPIO_FUNC_SIO;
+        iobank0_hw->io[SCSI_OUT_ACK].ctrl = GPIO_FUNC_PIO0;
+    }
+}
+
+void scsi_accel_host_read(uint8_t *buf, uint32_t count, int *parityError, volatile int *resetFlag)
+{
+    // Currently this method just reads from the PIO RX fifo directly in software loop.
+    // The SD card access is parallelized using DMA, so there is limited benefit from using DMA here.
+    g_scsi_host_state = SCSIHOST_READ;
+
+    pio_sm_init(SCSI_PIO, SCSI_SM, g_scsi_host.pio_offset_async_read, &g_scsi_host.pio_cfg_async_read);
+    scsi_accel_host_config_gpio();
+    pio_sm_set_enabled(SCSI_PIO, SCSI_SM, true);
+
+    // Set the number of bytes to read, must be divisible by 2.
+    assert((count & 1) == 0);
+    pio_sm_put(SCSI_PIO, SCSI_SM, count - 1);
+
+    // Read results from PIO RX FIFO
+    uint8_t *dst = buf;
+    uint8_t *end = buf + count;
+    uint32_t paritycheck = 0;
+    while (dst < end)
+    {
+        if (*resetFlag)
+        {
+            break;
+        }
+
+        uint32_t available = pio_sm_get_rx_fifo_level(SCSI_PIO, SCSI_SM);
+
+        while (available > 0)
+        {
+            available--;
+            uint32_t word = pio_sm_get(SCSI_PIO, SCSI_SM);
+            paritycheck ^= word;
+            word = ~word;
+            *dst++ = word & 0xFF;
+            *dst++ = word >> 16;
+        }
+    }
+
+    // Check parity errors in whole block
+    // This doesn't detect if there is even number of parity errors in block.
+    uint8_t byte0 = ~(paritycheck & 0xFF);
+    uint8_t byte1 = ~(paritycheck >> 16);
+    if (paritycheck != ((g_scsi_parity_lookup[byte1] << 16) | g_scsi_parity_lookup[byte0]))
+    {
+        azlog("Parity error in scsi_accel_host_read(): ", paritycheck);
+        *parityError = 1;
+    }
+
+    g_scsi_host_state = SCSIHOST_IDLE;
+    SCSI_RELEASE_DATA_REQ();
+    scsi_accel_host_config_gpio();
+    pio_sm_set_enabled(SCSI_PIO, SCSI_SM, false);
+}
+
+
+void scsi_accel_host_init()
+{
+    g_scsi_host_state = SCSIHOST_IDLE;
+    scsi_accel_host_config_gpio();
+
+    // Load PIO programs
+    pio_clear_instruction_memory(SCSI_PIO);
+
+    // Asynchronous / synchronous SCSI read
+    g_scsi_host.pio_offset_async_read = pio_add_program(SCSI_PIO, &scsi_host_async_read_program);
+    g_scsi_host.pio_cfg_async_read = scsi_host_async_read_program_get_default_config(g_scsi_host.pio_offset_async_read);
+    sm_config_set_in_pins(&g_scsi_host.pio_cfg_async_read, SCSI_IO_DB0);
+    sm_config_set_sideset_pins(&g_scsi_host.pio_cfg_async_read, SCSI_OUT_ACK);
+    sm_config_set_out_shift(&g_scsi_host.pio_cfg_async_read, true, false, 32);
+    sm_config_set_in_shift(&g_scsi_host.pio_cfg_async_read, true, true, 32);
+}

+ 11 - 0
lib/ZuluSCSI_platform_RP2040/scsi_accel_host.h

@@ -0,0 +1,11 @@
+// Accelerated SCSI subroutines for SCSI initiator/host side communication
+
+#pragma once
+
+#include <stdint.h>
+
+void scsi_accel_host_init();
+
+// Read data from SCSI bus.
+// Number of bytes to read must be divisible by two.
+void scsi_accel_host_read(uint8_t *buf, uint32_t count, int *parityError, volatile int *resetFlag);

+ 26 - 0
lib/ZuluSCSI_platform_RP2040/scsi_accel_host.pio

@@ -0,0 +1,26 @@
+; RP2040 PIO program for accelerating SCSI initiator / host function
+; Run "pioasm scsi_accel_host.pio scsi_accel_host.pio.h" to regenerate the C header from this.
+; GPIO mapping:
+; - 0-7: DB0-DB7
+; -   8: DBP
+; Side set is ACK pin
+
+.define REQ 9
+.define ACK 10
+
+; Read from SCSI bus using asynchronous handshake.
+; Data is returned as 16-bit words that contain the 8 data bits + 1 parity bit.
+; Number of bytes to receive minus 1 should be written to TX fifo.
+; Number of bytes to receive must be divisible by 2.
+.program scsi_host_async_read
+    .side_set 1
+
+    pull block                  side 1  ; Get number of bytes to receive
+    mov x, osr                  side 1  ; Store to counter X
+
+start:
+    wait 0 gpio REQ             side 1  ; Wait for REQ low
+    in pins, 9                  side 0  ; Assert ACK, read GPIO
+    in null, 7                  side 0  ; Padding bits
+    wait 1 gpio REQ             side 0  ; Wait for REQ high
+    jmp x-- start               side 1  ; Deassert ACK, decrement byte count and jump to start

+ 44 - 0
lib/ZuluSCSI_platform_RP2040/scsi_accel_host.pio.h

@@ -0,0 +1,44 @@
+// -------------------------------------------------- //
+// This file is autogenerated by pioasm; do not edit! //
+// -------------------------------------------------- //
+
+#pragma once
+
+#if !PICO_NO_HARDWARE
+#include "hardware/pio.h"
+#endif
+
+// -------------------- //
+// scsi_host_async_read //
+// -------------------- //
+
+#define scsi_host_async_read_wrap_target 0
+#define scsi_host_async_read_wrap 6
+
+static const uint16_t scsi_host_async_read_program_instructions[] = {
+            //     .wrap_target
+    0x90a0, //  0: pull   block           side 1     
+    0xb027, //  1: mov    x, osr          side 1     
+    0x3009, //  2: wait   0 gpio, 9       side 1     
+    0x4009, //  3: in     pins, 9         side 0     
+    0x4067, //  4: in     null, 7         side 0     
+    0x2089, //  5: wait   1 gpio, 9       side 0     
+    0x1042, //  6: jmp    x--, 2          side 1     
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program scsi_host_async_read_program = {
+    .instructions = scsi_host_async_read_program_instructions,
+    .length = 7,
+    .origin = -1,
+};
+
+static inline pio_sm_config scsi_host_async_read_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + scsi_host_async_read_wrap_target, offset + scsi_host_async_read_wrap);
+    sm_config_set_sideset(&c, 1, false, false);
+    return c;
+}
+#endif
+

+ 7 - 6
lib/ZuluSCSI_platform_RP2040/sd_card_sdio.cpp

@@ -40,7 +40,7 @@ void azplatform_set_sd_callback(sd_callback_t func, const uint8_t *buffer)
     m_stream_count_start = 0;
     m_stream_count_start = 0;
 }
 }
 
 
-static sd_callback_t get_stream_callback(const uint8_t *buf, uint32_t count)
+static sd_callback_t get_stream_callback(const uint8_t *buf, uint32_t count, const char *accesstype, uint32_t sector)
 {
 {
     m_stream_count_start = m_stream_count;
     m_stream_count_start = m_stream_count;
 
 
@@ -53,7 +53,8 @@ static sd_callback_t get_stream_callback(const uint8_t *buf, uint32_t count)
         }
         }
         else
         else
         {
         {
-            azdbg("Stream buffer mismatch: ", (uint32_t)buf, " vs. ", (uint32_t)(m_stream_buffer + m_stream_count));
+            azdbg("SD card ", accesstype, "(", (int)sector,
+                  ") slow transfer, buffer", (uint32_t)buf, " vs. ", (uint32_t)(m_stream_buffer + m_stream_count));
             return NULL;
             return NULL;
         }
         }
     }
     }
@@ -307,7 +308,7 @@ bool SdioCard::writeSector(uint32_t sector, const uint8_t* src)
     }
     }
 
 
     // If possible, report transfer status to application through callback.
     // If possible, report transfer status to application through callback.
-    sd_callback_t callback = get_stream_callback(src, 512);
+    sd_callback_t callback = get_stream_callback(src, 512, "writeSector", sector);
 
 
     uint32_t reply;
     uint32_t reply;
     if (!checkReturnOk(rp2040_sdio_command_R1(16, 512, &reply)) || // SET_BLOCKLEN
     if (!checkReturnOk(rp2040_sdio_command_R1(16, 512, &reply)) || // SET_BLOCKLEN
@@ -350,7 +351,7 @@ bool SdioCard::writeSectors(uint32_t sector, const uint8_t* src, size_t n)
         return true;
         return true;
     }
     }
 
 
-    sd_callback_t callback = get_stream_callback(src, n * 512);
+    sd_callback_t callback = get_stream_callback(src, n * 512, "writeSectors", sector);
 
 
     uint32_t reply;
     uint32_t reply;
     if (!checkReturnOk(rp2040_sdio_command_R1(16, 512, &reply)) || // SET_BLOCKLEN
     if (!checkReturnOk(rp2040_sdio_command_R1(16, 512, &reply)) || // SET_BLOCKLEN
@@ -393,7 +394,7 @@ bool SdioCard::readSector(uint32_t sector, uint8_t* dst)
         dst = (uint8_t*)g_sdio_dma_buf;
         dst = (uint8_t*)g_sdio_dma_buf;
     }
     }
 
 
-    sd_callback_t callback = get_stream_callback(dst, 512);
+    sd_callback_t callback = get_stream_callback(dst, 512, "readSector", sector);
 
 
     uint32_t reply;
     uint32_t reply;
     if (!checkReturnOk(rp2040_sdio_command_R1(16, 512, &reply)) || // SET_BLOCKLEN
     if (!checkReturnOk(rp2040_sdio_command_R1(16, 512, &reply)) || // SET_BLOCKLEN
@@ -441,7 +442,7 @@ bool SdioCard::readSectors(uint32_t sector, uint8_t* dst, size_t n)
         return true;
         return true;
     }
     }
 
 
-    sd_callback_t callback = get_stream_callback(dst, n * 512);
+    sd_callback_t callback = get_stream_callback(dst, n * 512, "readSectors", sector);
 
 
     uint32_t reply;
     uint32_t reply;
     if (!checkReturnOk(rp2040_sdio_command_R1(16, 512, &reply)) || // SET_BLOCKLEN
     if (!checkReturnOk(rp2040_sdio_command_R1(16, 512, &reply)) || // SET_BLOCKLEN

+ 38 - 11
src/ZuluSCSI.cpp

@@ -50,6 +50,7 @@
 #include "ZuluSCSI_log.h"
 #include "ZuluSCSI_log.h"
 #include "ZuluSCSI_log_trace.h"
 #include "ZuluSCSI_log_trace.h"
 #include "ZuluSCSI_disk.h"
 #include "ZuluSCSI_disk.h"
+#include "ZuluSCSI_initiator.h"
 
 
 SdFs SD;
 SdFs SD;
 FsFile g_logfile;
 FsFile g_logfile;
@@ -373,11 +374,6 @@ void readSCSIDeviceConfig()
   {
   {
     scsiDiskLoadConfig(i);
     scsiDiskLoadConfig(i);
   }
   }
-  
-  if (ini_getbool("SCSI", "Debug", 0, CONFIGFILE))
-  {
-    g_azlog_debug = true;
-  }
 }
 }
 
 
 /*********************************/
 /*********************************/
@@ -386,6 +382,26 @@ void readSCSIDeviceConfig()
 
 
 static void reinitSCSI()
 static void reinitSCSI()
 {
 {
+  if (ini_getbool("SCSI", "Debug", 0, CONFIGFILE))
+  {
+    g_azlog_debug = true;
+  }
+
+#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();
   scsiDiskResetImages();
   readSCSIDeviceConfig();
   readSCSIDeviceConfig();
   findHDDImages();
   findHDDImages();
@@ -454,15 +470,26 @@ extern "C" void zuluscsi_main_loop(void)
   static uint32_t sd_card_check_time = 0;
   static uint32_t sd_card_check_time = 0;
 
 
   azplatform_reset_watchdog();
   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();
     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
   // Check SD card status for hotplug
   if (scsiDev.phase == BUS_FREE &&
   if (scsiDev.phase == BUS_FREE &&

+ 2 - 0
src/ZuluSCSI_config.h

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

+ 505 - 0
src/ZuluSCSI_initiator.cpp

@@ -0,0 +1,505 @@
+/*
+ *  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 "SdFat.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 struct {
+    // Bitmap of all drives that have been imaged
+    uint32_t drives_imaged;
+
+    // Is imaging a drive in progress, or are we scanning?
+    bool imaging;
+
+    // Information about currently selected drive
+    int target_id;
+    uint32_t sectorsize;
+    uint32_t sectorcount;
+    uint32_t sectors_done;
+    int retrycount;
+
+    FsFile target_file;
+} g_initiator_state;
+
+extern SdFs SD;
+
+// Initialization of initiator mode
+void scsiInitiatorInit()
+{
+    scsiHostPhyReset();
+
+    g_initiator_state.drives_imaged = 0;
+    g_initiator_state.imaging = false;
+    g_initiator_state.target_id = -1;
+    g_initiator_state.sectorsize = 0;
+    g_initiator_state.sectorcount = 0;
+    g_initiator_state.sectors_done = 0;
+    g_initiator_state.retrycount = 0;
+}
+
+// High level logic of the initiator mode
+void scsiInitiatorMainLoop()
+{
+    if (!g_initiator_state.imaging)
+    {
+        // Scan for SCSI drives one at a time
+        g_initiator_state.target_id = (g_initiator_state.target_id + 1) % 8;
+        g_initiator_state.sectors_done = 0;
+        g_initiator_state.retrycount = 0;
+
+        if (!(g_initiator_state.drives_imaged & (1 << g_initiator_state.target_id)))
+        {
+            delay(1000);
+            LED_ON();
+            bool readcapok = scsiInitiatorReadCapacity(g_initiator_state.target_id, &g_initiator_state.sectorcount, &g_initiator_state.sectorsize);
+            LED_OFF();
+
+            if (readcapok)
+            {
+                azlog("SCSI id ", g_initiator_state.target_id,
+                    " capacity ", (int)g_initiator_state.sectorcount,
+                    " sectors x ", (int)g_initiator_state.sectorsize, " bytes");
+
+                char filename[] = "HD00_imaged.hda";
+                filename[2] += g_initiator_state.target_id;
+
+                SD.remove(filename);
+                g_initiator_state.target_file = SD.open(filename, O_RDWR | O_CREAT | O_TRUNC);
+                if (!g_initiator_state.target_file.isOpen())
+                {
+                    azlog("Failed to open file for writing: ", filename);
+                    return;
+                }
+
+                azlog("Starting to copy drive data to ", filename);
+                g_initiator_state.target_file.preAllocate((uint64_t)g_initiator_state.sectorcount * g_initiator_state.sectorsize);
+                g_initiator_state.imaging = true;
+            }
+        }
+    }
+    else
+    {
+        // Copy sectors from SCSI drive to file
+        if (g_initiator_state.sectors_done >= g_initiator_state.sectorcount)
+        {
+            azlog("Finished imaging drive with id ", g_initiator_state.target_id);
+            LED_OFF();
+            g_initiator_state.drives_imaged |= (1 << g_initiator_state.target_id);
+            g_initiator_state.imaging = false;
+            g_initiator_state.target_file.close();
+            return;
+        }
+
+        // Update status indicator, the led blinks every 5 seconds and is on the longer the more data has been transferred
+        uint32_t time_start = millis();
+        int phase = (time_start % 5000);
+        int duty = g_initiator_state.sectors_done * 5000 / g_initiator_state.sectorcount;
+        if (duty < 100) duty = 100;
+        if (phase <= duty)
+        {
+            LED_ON();
+        }
+        else
+        {
+            LED_OFF();
+        }
+
+        // How many sectors to read in one batch?
+        int numtoread = g_initiator_state.sectorcount - g_initiator_state.sectors_done;
+        if (numtoread > 512) numtoread = 512;
+
+        // Retry sector-by-sector
+        if (g_initiator_state.retrycount > 1)
+            numtoread = 1;
+
+        bool status = scsiInitiatorReadDataToFile(g_initiator_state.target_id,
+            g_initiator_state.sectors_done, numtoread, g_initiator_state.sectorsize,
+            g_initiator_state.target_file);
+
+        if (!status)
+        {
+            azlog("Failed to transfer ", numtoread, " sectors starting at ", (int)g_initiator_state.sectors_done);
+
+            if (g_initiator_state.retrycount < 5)
+            {
+                azlog("Retrying.. ", g_initiator_state.retrycount, "/5");
+                delay(200);
+                scsiHostPhyReset();
+                delay(200);
+
+                g_initiator_state.retrycount++;
+                g_initiator_state.target_file.seek((uint64_t)g_initiator_state.sectors_done * g_initiator_state.sectorsize);
+            }
+            else
+            {
+                azlog("Retry limit exceeded, skipping one sector");
+                g_initiator_state.retrycount = 0;
+                g_initiator_state.sectors_done++;
+                g_initiator_state.target_file.seek((uint64_t)g_initiator_state.sectors_done * g_initiator_state.sectorsize);
+            }
+        }
+        else
+        {
+            g_initiator_state.retrycount = 0;
+            g_initiator_state.sectors_done += numtoread;
+            g_initiator_state.target_file.flush();
+
+            int speed_kbps = numtoread * g_initiator_state.sectorsize / (millis() - time_start);
+            azlog("SCSI read succeeded, sectors done: ",
+                  (int)g_initiator_state.sectors_done, " / ", (int)g_initiator_state.sectorcount,
+                  " speed ", speed_kbps, " kB/s");
+        }
+    }
+}
+
+/*************************************
+ * 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,
+                            bool returnDataPhase)
+{
+    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 (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)
+        {
+            if (returnDataPhase) return 0;
+            if (bufInLen == 0)
+            {
+                azlog("DATA_IN phase but no data to receive!");
+                status = -3;
+                break;
+            }
+
+            if (!scsiHostRead(bufIn, bufInLen))
+            {
+                azlog("scsiHostRead failed, was writing ", bytearray(bufOut, bufOutLen));
+                status = -2;
+                break;
+            }
+        }
+        else if (phase == DATA_OUT)
+        {
+            if (returnDataPhase) return 0;
+            if (bufOutLen == 0)
+            {
+                azlog("DATA_OUT phase but no data to send!");
+                status = -3;
+                break;
+            }
+
+            if (!scsiHostWrite(bufOut, bufOutLen))
+            {
+                azlog("scsiHostWrite failed, was writing ", bytearray(bufOut, bufOutLen));
+                status = -2;
+                break;
+            }
+        }
+        else if (phase == STATUS)
+        {
+            uint8_t tmp = 0;
+            scsiHostRead(&tmp, 1);
+            status = tmp;
+            azdbg("------ STATUS: ", tmp);
+        }
+    }
+
+    scsiHostPhyRelease();
+
+    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);
+        
+        *sectorcount += 1; // SCSI reports last sector address
+
+        *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;
+    }
+} 
+
+// This uses callbacks to run SD and SCSI transfers in parallel
+static struct {
+    uint32_t bytes_sd; // Number of bytes that have been transferred on SD card side
+    uint32_t bytes_sd_scheduled; // Number of bytes scheduled for transfer on SD card side
+    uint32_t bytes_scsi; // Number of bytes that have been scheduled for transfer on SCSI side
+    uint32_t bytes_scsi_done; // Number of bytes that have been transferred on SCSI side
+    
+    uint32_t bytes_per_sector;
+    bool all_ok;
+} g_initiator_transfer;
+
+static void initiatorReadSDCallback(uint32_t bytes_complete)
+{
+    if (g_initiator_transfer.bytes_scsi_done < g_initiator_transfer.bytes_scsi)
+    {
+        // How many bytes remaining in the transfer?
+        uint32_t remain = g_initiator_transfer.bytes_scsi - g_initiator_transfer.bytes_scsi_done;
+        uint32_t len = remain;
+
+        // Limit maximum amount of data transferred at one go, to give enough callbacks to SD driver.
+        // Select the limit based on total bytes in the transfer.
+        // Transfer size is reduced towards the end of transfer to reduce the dead time between
+        // end of SCSI transfer and the SD write completing.
+        uint32_t limit = g_initiator_transfer.bytes_scsi / 8;
+        uint32_t bytesPerSector = g_initiator_transfer.bytes_per_sector;
+        if (limit < PLATFORM_OPTIMAL_MIN_SD_WRITE_SIZE) limit = PLATFORM_OPTIMAL_MIN_SD_WRITE_SIZE;
+        if (limit > PLATFORM_OPTIMAL_MAX_SD_WRITE_SIZE) limit = PLATFORM_OPTIMAL_MAX_SD_WRITE_SIZE;
+        if (limit > len) limit = PLATFORM_OPTIMAL_LAST_SD_WRITE_SIZE;
+        if (limit < bytesPerSector) limit = bytesPerSector;
+
+        if (len > limit)
+        {
+            len = limit;
+        }
+
+        // Split read so that it doesn't wrap around buffer edge
+        uint32_t bufsize = sizeof(scsiDev.data);
+        uint32_t start = (g_initiator_transfer.bytes_scsi_done % bufsize);
+        if (start + len > bufsize)
+            len = bufsize - start;
+
+        // Don't overwrite data that has not yet been written to SD card
+        uint32_t sd_ready_cnt = g_initiator_transfer.bytes_sd + bytes_complete;
+        if (g_initiator_transfer.bytes_scsi_done + len > sd_ready_cnt + bufsize)
+            len = sd_ready_cnt + bufsize - g_initiator_transfer.bytes_scsi_done;
+        
+        if (sd_ready_cnt == g_initiator_transfer.bytes_sd_scheduled &&
+            g_initiator_transfer.bytes_sd_scheduled + bytesPerSector <= g_initiator_transfer.bytes_scsi_done)
+        {
+            // Current SD transfer is complete, it is better we return now and offer a chance for the next
+            // transfer to begin.
+            return;
+        }
+
+        // Keep transfers a multiple of sector size.
+        if (remain >= bytesPerSector && len % bytesPerSector != 0)
+        {
+            len -= len % bytesPerSector;
+        }
+
+        if (len == 0)
+            return;
+
+        // azdbg("SCSI read ", (int)start, " + ", (int)len, ", sd ready cnt ", (int)sd_ready_cnt, " ", (int)bytes_complete, ", scsi done ", (int)g_initiator_transfer.bytes_scsi_done);
+        if (!scsiHostRead(&scsiDev.data[start], len))
+        {
+            azlog("Read failed at byte ", (int)g_initiator_transfer.bytes_scsi_done);
+            g_initiator_transfer.all_ok = false;
+        }
+        g_initiator_transfer.bytes_scsi_done += len;
+    }
+}
+
+static void scsiInitiatorWriteDataToSd(FsFile &file, bool use_callback)
+{
+    // Figure out longest continuous block in buffer
+    uint32_t bufsize = sizeof(scsiDev.data);
+    uint32_t start = g_initiator_transfer.bytes_sd % bufsize;
+    uint32_t len = g_initiator_transfer.bytes_scsi_done - g_initiator_transfer.bytes_sd;
+    if (start + len > bufsize) len = bufsize - start;
+
+    // Try to do writes in multiple of 512 bytes
+    // This allows better performance for SD card access.
+    if (len >= 512) len &= ~511;
+
+    // Start writing to SD card and simultaneously reading more from SCSI bus
+    uint8_t *buf = &scsiDev.data[start];
+    // azdbg("SD write ", (int)start, " + ", (int)len);
+
+    if (use_callback)
+    {
+        azplatform_set_sd_callback(&initiatorReadSDCallback, buf);
+    }
+
+    g_initiator_transfer.bytes_sd_scheduled = g_initiator_transfer.bytes_sd + len;
+    if (file.write(buf, len) != len)
+    {
+        azlog("scsiInitiatorReadDataToFile: SD card write failed");
+        g_initiator_transfer.all_ok = false;
+    }
+    azplatform_set_sd_callback(NULL, NULL);
+    g_initiator_transfer.bytes_sd += len;
+}
+
+bool scsiInitiatorReadDataToFile(int target_id, uint32_t start_sector, uint32_t sectorcount, uint32_t sectorsize,
+                                 FsFile &file)
+{
+    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
+    };
+
+    // Start executing command, return in data phase
+    int status = scsiInitiatorRunCommand(target_id, command, sizeof(command), NULL, 0, NULL, 0, true);
+
+    if (status != 0)
+    {
+        azlog("scsiInitiatorReadDataToFile: Issuing command failed: ", status);
+        scsiHostPhyRelease();
+        return false;
+    }
+
+    SCSI_PHASE phase;
+
+    g_initiator_transfer.bytes_scsi = sectorcount * sectorsize;
+    g_initiator_transfer.bytes_per_sector = sectorsize;
+    g_initiator_transfer.bytes_sd = 0;
+    g_initiator_transfer.bytes_sd_scheduled = 0;
+    g_initiator_transfer.bytes_scsi_done = 0;
+    g_initiator_transfer.all_ok = true;
+
+    while (true)
+    {
+        phase = (SCSI_PHASE)scsiHostPhyGetPhase();
+        if (phase != DATA_IN && phase != BUS_BUSY)
+        {
+            break;
+        }
+
+        // Read next block from SCSI bus if buffer empty
+        if (g_initiator_transfer.bytes_sd == g_initiator_transfer.bytes_scsi_done)
+        {
+            initiatorReadSDCallback(0);
+        }
+        else
+        {
+            // Write data to SD card and simultaneously read more from SCSI
+            scsiInitiatorWriteDataToSd(file, true);
+        }
+    }
+
+    // Write any remaining buffered data
+    while (g_initiator_transfer.bytes_sd < g_initiator_transfer.bytes_scsi_done)
+    {
+        scsiInitiatorWriteDataToSd(file, false);
+    }
+
+    if (g_initiator_transfer.bytes_sd != g_initiator_transfer.bytes_scsi)
+    {
+        azlog("SCSI read from sector ", (int)start_sector, " was incomplete: expected ",
+             (int)g_initiator_transfer.bytes_scsi, " got ", (int)g_initiator_transfer.bytes_sd, " bytes");
+        g_initiator_transfer.all_ok = false;
+    }
+
+    while ((phase = (SCSI_PHASE)scsiHostPhyGetPhase()) != BUS_FREE)
+    {
+        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 == STATUS)
+        {
+            uint8_t tmp = 0;
+            scsiHostRead(&tmp, 1);
+            status = tmp;
+            azdbg("------ STATUS: ", tmp);
+        }
+    }
+
+    scsiHostPhyRelease();
+
+    return status == 0 && g_initiator_transfer.all_ok;
+}
+
+
+#endif

+ 25 - 0
src/ZuluSCSI_initiator.h

@@ -0,0 +1,25 @@
+// 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,
+                            bool returnDataPhase = false);
+
+// Execute READ CAPACITY command
+bool scsiInitiatorReadCapacity(int target_id, uint32_t *sectorcount, uint32_t *sectorsize);
+
+// Read a block of data from SCSI device and write to file on SD card
+class FsFile;
+bool scsiInitiatorReadDataToFile(int target_id, uint32_t start_sector, uint32_t sectorcount, uint32_t sectorsize,
+                                 FsFile &file);

+ 35 - 6
src/ZuluSCSI_log_trace.cpp

@@ -10,6 +10,7 @@ extern "C" {
 }
 }
 
 
 static bool g_LogData = false;
 static bool g_LogData = false;
+static bool g_LogInitiatorCommand = false;
 static int g_InByteCount = 0;
 static int g_InByteCount = 0;
 static int g_OutByteCount = 0;
 static int g_OutByteCount = 0;
 static uint16_t g_DataChecksum = 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_LogData = false;
+    g_LogInitiatorCommand = false;
     if (!g_azlog_debug)
     if (!g_azlog_debug)
     {
     {
         return;
         return;
@@ -83,7 +85,10 @@ static void printNewPhase(int phase)
             break;
             break;
         
         
         case SELECTION:
         case SELECTION:
-            azdbg("---- SELECTION: ", (int)(*SCSI_STS_SELECTED & 7));
+            if (initiator)
+                azdbg("---- SELECTION");
+            else
+                azdbg("---- SELECTION: ", (int)(*SCSI_STS_SELECTED & 7));
             break;
             break;
         
         
         case RESELECTION:
         case RESELECTION:
@@ -91,7 +96,12 @@ static void printNewPhase(int phase)
             break;
             break;
         
         
         case STATUS:
         case STATUS:
-            if (scsiDev.status == GOOD)
+            if (initiator)
+            {
+                azdbg("---- STATUS");
+                g_LogData = true;
+            }
+            else if (scsiDev.status == GOOD)
             {
             {
                 azdbg("---- STATUS: 0 GOOD");
                 azdbg("---- STATUS: 0 GOOD");
             }
             }
@@ -106,11 +116,12 @@ static void printNewPhase(int phase)
             break;
             break;
         
         
         case COMMAND:
         case COMMAND:
+            g_LogInitiatorCommand = initiator;
             g_LogData = true;
             g_LogData = true;
             break;
             break;
         
         
         case DATA_IN:
         case DATA_IN:
-            if (scsiDev.target->syncOffset > 0)
+            if (!initiator && scsiDev.target->syncOffset > 0)
                 azdbg("---- DATA_IN, syncOffset ", (int)scsiDev.target->syncOffset,
                 azdbg("---- DATA_IN, syncOffset ", (int)scsiDev.target->syncOffset,
                                    " syncPeriod ", (int)scsiDev.target->syncPeriod);
                                    " syncPeriod ", (int)scsiDev.target->syncPeriod);
             else
             else
@@ -118,7 +129,7 @@ static void printNewPhase(int phase)
             break;
             break;
         
         
         case DATA_OUT:
         case DATA_OUT:
-            if (scsiDev.target->syncOffset > 0)
+            if (!initiator && scsiDev.target->syncOffset > 0)
                 azdbg("---- DATA_OUT, syncOffset ", (int)scsiDev.target->syncOffset,
                 azdbg("---- DATA_OUT, syncOffset ", (int)scsiDev.target->syncOffset,
                                     " syncPeriod ", (int)scsiDev.target->syncPeriod);
                                     " syncPeriod ", (int)scsiDev.target->syncPeriod);
             else
             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)
 void scsiLogDataIn(const uint8_t *buf, uint32_t length)
 {
 {
     if (g_LogData)
     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)
 void scsiLogDataOut(const uint8_t *buf, uint32_t length)
 {
 {
-    if (buf == scsiDev.cdb)
+    if (buf == scsiDev.cdb || g_LogInitiatorCommand)
     {
     {
         azdbg("---- COMMAND: ", getCommandName(buf[0]));
         azdbg("---- COMMAND: ", getCommandName(buf[0]));
     }
     }

+ 1 - 0
src/ZuluSCSI_log_trace.h

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