Bladeren bron

Use SCSI2SD codebase for SCSI command implementations.

Seems to be more standards-compliant than BlueSCSI.
SCSI2SD code is mostly unmodified and updates from upstream can
be merged in the future.
Petteri Aimonen 3 jaren geleden
bovenliggende
commit
ecd47fec37

+ 1 - 0
.github/workflows/firmware_build.yml

@@ -45,6 +45,7 @@ jobs:
       - name: Upload to latest release
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        if: github.ref == 'refs/heads/main'
         run: |
           cd AzulSCSI
           git tag -d latest

+ 27 - 18
README.md

@@ -25,23 +25,23 @@ In crashes the firmware will also attempt to save information into `azulerr.txt`
 Configuration file
 ------------------
 Optional configuration can be stored in `azulscsi.ini`.
+If image file is found but configuration is missing, a default configuration is used.
+
+A single AzulSCSI device can represent multiple devices on the SCSI bus.
+The configuration sections are numbered `SCSI0` to `SCSI7` and correspond to images `HD00.hda` to `HD70.hda`.
+
 Example format for config file:
 
     [SCSI]
+    # Settings that apply to all devices
+    Debug = 0   # Same effect as DIPSW2
+
+    [SCSI0]
     Vendor = "QUANTUM "
     Product = "FIREBALL1       "
     Version = "1.0 "
-    Quirks = 0   # 0: Standard, 1: Sharp, 2: NEC PC98
-    Debug = 0   # Same effect as DIPSW2
-
-    # Default timings that work with most devices
-    ARBITRATION_DELAY_US = 10
-    SELECTION_DELAY_US   = 10
-    COMMAND_DELAY_US     = 10
-    DATA_DELAY_US        = 10
-    STATUS_DELAY_US      = 10
-    MESSAGE_DELAY_US     = 10
-    REQ_TYPE_SETUP_NS    = 500
+    Serial = "0123456789ABCDEF"
+    Quirks = 0   # 0: Standard, 1: Apple, 2: OMTI, 4: Xebec, 8: VMS
 
 Performance
 -----------
@@ -96,12 +96,21 @@ To build run the command:
 Origins and License
 -------------------
 
-This firmware is derived from [BlueSCSI](https://github.com/erichelgeson/BlueSCSI), which in turn is derived from [ArdSCSIno-stm32](https://github.com/ztto/ArdSCSino-stm32). The firmware is available under GPL 3 license.
+This firmware is derived from two sources, both under GPL 3 license:
+
+* [SCSI2SD V6](http://www.codesrc.com/mediawiki/index.php/SCSI2SD)
+* [BlueSCSI](https://github.com/erichelgeson/BlueSCSI), which in turn is derived from [ArdSCSIno-stm32](https://github.com/ztto/ArdSCSino-stm32).
+
+Main program structure:
+
+* SCSI command implementations are from SCSI2SD.
+* SCSI physical layer code is mostly custom, with some inspiration from BlueSCSI.
+* Image file access is derived from BlueSCSI.
 
-Major changes from BlueSCSI include:
+Major changes from BlueSCSI and SCSI2SD include:
 
-- Separation of platform-specific functionality to separate file to ease porting.
-- Ported to GD32F205.
-- Removal of Arduino core dependency, as it was not currently available for GD32F205.
-- Buffered log functions.
-- Direct streaming between SD card and SCSI for slightly improved performance.
+* Separation of platform-specific functionality to separate file to ease porting.
+* Ported to GD32F205.
+* Removal of Arduino core dependency, as it was not currently available for GD32F205.
+* Buffered log functions.
+* Direct streaming between SD card and SCSI for slightly improved performance.

+ 18 - 423
lib/AzulSCSI_platform_GD32F205/AzulSCSI_platform.cpp

@@ -1,7 +1,5 @@
 #include "AzulSCSI_platform.h"
-#include "gd32f20x_spi.h"
 #include "gd32f20x_sdio.h"
-#include "gd32f20x_dma.h"
 #include "AzulSCSI_log.h"
 #include "AzulSCSI_config.h"
 #include <SdFat.h>
@@ -10,6 +8,10 @@ extern "C" {
 
 const char *g_azplatform_name = PLATFORM_NAME;
 
+/*************************/
+/* Timing functions      */
+/*************************/
+
 static volatile uint32_t g_millisecond_counter;
 static volatile uint32_t g_watchdog_timeout;
 static uint32_t g_ns_to_cycles; // Q0.32 fixed point format
@@ -60,16 +62,9 @@ void SysTick_Handler(void)
         "b SysTick_Handler_inner": : : "r0");
 }
 
-// Writes log data to the PB3 SWO pin
-void azplatform_log(const char *s)
-{
-    while (*s)
-    {
-        // Write to SWO pin
-        while (ITM->PORT[0].u32 == 0);
-        ITM->PORT[0].u8 = *s++;
-    }
-}
+/***************/
+/* GPIO init   */
+/***************/
 
 // Initialize SPI and GPIO configuration
 // Clock has already been initialized by system_gd32f20x.c
@@ -105,6 +100,7 @@ void azplatform_init()
     ITM->TER = 0xFFFFFFFF; // Enable all stimulus ports
 
     // Enable needed clocks for GPIO
+    rcu_periph_clock_enable(RCU_AF);
     rcu_periph_clock_enable(RCU_GPIOA);
     rcu_periph_clock_enable(RCU_GPIOB);
     rcu_periph_clock_enable(RCU_GPIOC);
@@ -177,35 +173,23 @@ void azplatform_init()
     }
 }
 
-static void (*g_rst_callback)();
+/*****************************************/
+/* Crash handlers                        */
+/*****************************************/
 
-void azplatform_set_rst_callback(void (*callback)())
-{
-    g_rst_callback = callback;
-    gpio_exti_source_select(SCSI_RST_EXTI_SOURCE_PORT, SCSI_RST_EXTI_SOURCE_PIN);
-    exti_init(SCSI_RST_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
-    NVIC_SetPriority(SCSI_RST_IRQn, 0x00U);
-    NVIC_EnableIRQ(SCSI_RST_IRQn);
-}
+extern SdFs SD;
 
-void SCSI_RST_IRQ (void)
+// Writes log data to the PB3 SWO pin
+void azplatform_log(const char *s)
 {
-    if (exti_interrupt_flag_get(SCSI_RST_EXTI))
+    while (*s)
     {
-        exti_interrupt_flag_clear(SCSI_RST_EXTI);
-        if (g_rst_callback)
-        {
-            g_rst_callback();
-        }
+        // Write to SWO pin
+        while (ITM->PORT[0].u32 == 0);
+        ITM->PORT[0].u8 = *s++;
     }
 }
 
-/*****************************************/
-/* Crash handlers                        */
-/*****************************************/
-
-extern SdFs SD;
-
 void azplatform_emergency_log_save()
 {
     FsFile crashfile = SD.open(CRASHFILE, O_WRONLY | O_CREAT | O_TRUNC);
@@ -324,395 +308,6 @@ void azplatform_reset_watchdog(int timeout_ms)
     g_watchdog_timeout = timeout_ms;
 }
 
-/*****************************************/
-/* Driver for GD32 SPI for SdFat library */
-/*****************************************/
-
-extern volatile bool g_busreset;
-
-#define SCSI_WAIT_ACTIVE(pin) \
-  if (!SCSI_IN(pin)) { \
-    if (!SCSI_IN(pin)) { \
-      while(!SCSI_IN(pin) && !g_busreset); \
-    } \
-  }
-
-#define SCSI_WAIT_INACTIVE(pin) \
-  if (SCSI_IN(pin)) { \
-    if (SCSI_IN(pin)) { \
-      while(SCSI_IN(pin) && !g_busreset); \
-    } \
-  }
-
-// Optimized ASM blocks for the SCSI communication subroutine
-
-// Take 8 bits from d and format them for writing
-// d is name of data operand, b is bit offset, x is unique label
-#define ASM_LOAD_DATA(d, b, x) \
-"    load_data1_" x "_%=: \n" \
-"        ubfx    %[tmp1], %[" d "], #" b ", #8 \n" \
-"        ldr     %[tmp1], [%[byte_lookup], %[tmp1], lsl #2] \n"
-
-// Write data to SCSI port and set REQ high
-#define ASM_SEND_DATA(x) \
-"    send_data" x "_%=: \n" \
-"        str     %[tmp1], [%[out_port_bop]] \n"
-
-// Wait for ACK to be high, set REQ low, wait ACK low
-#define ASM_HANDSHAKE(x) \
-"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
-"        str     %[tmp2], [%[req_pin_bb]] \n" \
-"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
-"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
-"        str     %[tmp2], [%[req_pin_bb]] \n" \
-"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
-"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
-"        str     %[tmp2], [%[req_pin_bb]] \n" \
-"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
-"    wait_ack_inactive" x "_%=: \n" \
-"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
-"        str     %[tmp2], [%[req_pin_bb]] \n" \
-"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
-"        b.n     wait_ack_inactive" x "_%= \n" \
-"    req_is_low_now" x "_%=: \n" \
-"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
-"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
-"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
-"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
-"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
-"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
-"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
-"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
-"    wait_ack_active" x "_%=: \n" \
-"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
-"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
-"        b.n     wait_ack_active" x "_%= \n" \
-"    over_ack_active" x "_%=: \n" \
-
-// Send bytes to SCSI bus using the asynchronous handshake mechanism
-// Takes 4 bytes at a time for sending from buf.
-// Returns the next buffer pointer.
-static inline uint32_t *scsi_send_words_async(uint32_t *buf, uint32_t num_words)
-{
-    volatile uint32_t *out_port_bop = (volatile uint32_t*)&GPIO_BOP(SCSI_OUT_PORT);
-    const uint32_t *byte_lookup = g_scsi_out_byte_to_bop;
-    uint32_t ack_pin_bb = PERIPH_BB_BASE + (((uint32_t)&GPIO_ISTAT(SCSI_ACK_PORT)) - APB1_BUS_BASE) * 32 + 12 * 4;
-    uint32_t req_pin_bb = PERIPH_BB_BASE + (((uint32_t)out_port_bop) - APB1_BUS_BASE) * 32 + (9 + 16) * 4;
-    register uint32_t tmp1 = 0;
-    register uint32_t tmp2 = 0;
-    register uint32_t data = 0;
-
-    asm volatile (
-    "   ldr      %[data], [%[buf]], #4 \n" \
-        ASM_LOAD_DATA("data", "0", "first")
-
-    "inner_loop_%=: \n" \
-        ASM_SEND_DATA("0")
-        ASM_LOAD_DATA("data", "8", "8")
-        ASM_HANDSHAKE("0")
-        
-        ASM_SEND_DATA("8")
-        ASM_LOAD_DATA("data", "16", "16")
-        ASM_HANDSHAKE("8")
-
-        ASM_SEND_DATA("16")
-        ASM_LOAD_DATA("data", "24", "24")
-        ASM_HANDSHAKE("16")
-
-        ASM_SEND_DATA("24")
-    "   ldr      %[data], [%[buf]], #4 \n" \
-        ASM_LOAD_DATA("data", "0", "0")
-        ASM_HANDSHAKE("24")
-
-    "   subs     %[num_words], %[num_words], #1 \n" \
-    "   bne     inner_loop_%= \n"
-    : /* Output */ [tmp1] "+l" (tmp1), [tmp2] "+l" (tmp2), [data] "+r" (data),
-                   [buf] "+r" (buf), [num_words] "+r" (num_words)
-    : /* Input */ [ack_pin_bb] "r" (ack_pin_bb),
-                  [req_pin_bb] "r" (req_pin_bb),
-                  [out_port_bop] "r"(out_port_bop),
-                  [byte_lookup] "r" (byte_lookup)
-    : /* Clobber */ );
-
-    return buf - 1;
-}
-
-class GD32SPIDriver : public SdSpiBaseClass
-{
-public:
-    void begin(SdSpiConfig config) {
-        rcu_periph_clock_enable(RCU_SPI0);
-        rcu_periph_clock_enable(RCU_DMA0);
-
-        dma_parameter_struct rx_dma_config =
-        {
-            .periph_addr = (uint32_t)&SPI_DATA(SD_SPI),
-            .periph_width = DMA_PERIPHERAL_WIDTH_8BIT,
-            .memory_addr = 0, // Set before transfer
-            .memory_width = DMA_MEMORY_WIDTH_8BIT,
-            .number = 0, // Set before transfer
-            .priority = DMA_PRIORITY_ULTRA_HIGH,
-            .periph_inc = DMA_PERIPH_INCREASE_DISABLE,
-            .memory_inc = DMA_MEMORY_INCREASE_ENABLE,
-            .direction = DMA_PERIPHERAL_TO_MEMORY
-        };
-        dma_init(DMA0, SD_SPI_RX_DMA_CHANNEL, &rx_dma_config);
-
-        dma_parameter_struct tx_dma_config =
-        {
-            .periph_addr = (uint32_t)&SPI_DATA(SD_SPI),
-            .periph_width = DMA_PERIPHERAL_WIDTH_8BIT,
-            .memory_addr = 0, // Set before transfer
-            .memory_width = DMA_MEMORY_WIDTH_8BIT,
-            .number = 0, // Set before transfer
-            .priority = DMA_PRIORITY_HIGH,
-            .periph_inc = DMA_PERIPH_INCREASE_DISABLE,
-            .memory_inc = DMA_MEMORY_INCREASE_ENABLE,
-            .direction = DMA_MEMORY_TO_PERIPHERAL
-        };
-        dma_init(DMA0, SD_SPI_TX_DMA_CHANNEL, &tx_dma_config);
-    }
-        
-    void activate() {
-        spi_parameter_struct config = {
-            SPI_MASTER,
-            SPI_TRANSMODE_FULLDUPLEX,
-            SPI_FRAMESIZE_8BIT,
-            SPI_NSS_SOFT,
-            SPI_ENDIAN_MSB,
-            SPI_CK_PL_LOW_PH_1EDGE,
-            SPI_PSC_256
-        };
-
-        // Select closest available divider based on system frequency
-        int divider = (SystemCoreClock + m_sckfreq / 2) / m_sckfreq;
-        if (divider <= 2)
-            config.prescale = SPI_PSC_2;
-        else if (divider <= 4)
-            config.prescale = SPI_PSC_4;
-        else if (divider <= 8)
-            config.prescale = SPI_PSC_8;
-        else if (divider <= 16)
-            config.prescale = SPI_PSC_16;
-        else if (divider <= 32)
-            config.prescale = SPI_PSC_32;
-        else if (divider <= 64)
-            config.prescale = SPI_PSC_64;
-        else if (divider <= 128)
-            config.prescale = SPI_PSC_128;
-        else
-            config.prescale = SPI_PSC_256;
-
-        spi_init(SD_SPI, &config);
-        spi_enable(SD_SPI);
-    }
-    
-    void deactivate() {
-        spi_disable(SD_SPI);
-    }
-
-    void wait_idle() {
-        while (!(SPI_STAT(SD_SPI) & SPI_STAT_TBE));
-        while (SPI_STAT(SD_SPI) & SPI_STAT_TRANS);
-    }
-
-    uint8_t receive() {
-        // Wait for idle and clear RX buffer
-        wait_idle();
-        (void)SPI_DATA(SD_SPI);
-
-        // Send dummy byte and wait for receive
-        SPI_DATA(SD_SPI) = 0xFF;
-        while (!(SPI_STAT(SD_SPI) & SPI_STAT_RBNE));
-        return SPI_DATA(SD_SPI);
-    }
-
-    uint8_t receive(uint8_t* buf, size_t count) {
-        // Wait for idle and clear RX buffer
-        wait_idle();
-        (void)SPI_DATA(SD_SPI);
-
-        if (buf == m_stream_buffer + m_stream_status)
-        {
-            // Stream data directly to SCSI bus
-            return stream_receive(buf, count);
-        }
-        
-        // Stream to memory
-        
-        // Use DMA to stream dummy TX data and store RX data
-        uint8_t tx_data = 0xFF;
-        DMA_INTC(DMA0) = DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_RX_DMA_CHANNEL);
-        DMA_INTC(DMA0) = DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_TX_DMA_CHANNEL);
-        DMA_CHMADDR(DMA0, SD_SPI_RX_DMA_CHANNEL) = (uint32_t)buf;
-        DMA_CHMADDR(DMA0, SD_SPI_TX_DMA_CHANNEL) = (uint32_t)&tx_data;
-        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) &= ~DMA_CHXCTL_MNAGA; // No memory increment for TX
-        DMA_CHCNT(DMA0, SD_SPI_RX_DMA_CHANNEL) = count;
-        DMA_CHCNT(DMA0, SD_SPI_TX_DMA_CHANNEL) = count;
-        DMA_CHCTL(DMA0, SD_SPI_RX_DMA_CHANNEL) |= DMA_CHXCTL_CHEN;
-        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) |= DMA_CHXCTL_CHEN;
-
-        SPI_CTL1(SD_SPI) |= SPI_CTL1_DMAREN | SPI_CTL1_DMATEN;
-        
-        uint32_t start = millis();
-        while (!(DMA_INTF(DMA0) & DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_RX_DMA_CHANNEL)))
-        {
-            if (millis() - start > 500)
-            {
-                azlog("ERROR: SPI DMA receive of ", (int)count, " bytes timeouted");
-                return 1;
-            }
-        }
-
-        if (DMA_INTF(DMA0) & DMA_FLAG_ADD(DMA_FLAG_ERR, SD_SPI_RX_DMA_CHANNEL))
-        {
-            azlog("ERROR: SPI DMA receive set DMA_FLAG_ERR");
-        }
-
-        SPI_CTL1(SD_SPI) &= ~(SPI_CTL1_DMAREN | SPI_CTL1_DMATEN);
-        DMA_CHCTL(DMA0, SD_SPI_RX_DMA_CHANNEL) &= ~DMA_CHXCTL_CHEN;
-        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) &= ~DMA_CHXCTL_CHEN;
-
-        return 0;
-    }
-
-    // Stream data directly to SCSI bus
-    uint8_t stream_receive(uint8_t *buf, size_t count)
-    {
-        uint8_t tx_data = 0xFF;
-        DMA_INTC(DMA0) = DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_RX_DMA_CHANNEL);
-        DMA_INTC(DMA0) = DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_TX_DMA_CHANNEL);
-        DMA_CHMADDR(DMA0, SD_SPI_RX_DMA_CHANNEL) = (uint32_t)buf;
-        DMA_CHMADDR(DMA0, SD_SPI_TX_DMA_CHANNEL) = (uint32_t)&tx_data;
-        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) &= ~DMA_CHXCTL_MNAGA; // No memory increment for TX
-        DMA_CHCNT(DMA0, SD_SPI_RX_DMA_CHANNEL) = count;
-        DMA_CHCNT(DMA0, SD_SPI_TX_DMA_CHANNEL) = count;
-        DMA_CHCTL(DMA0, SD_SPI_RX_DMA_CHANNEL) |= DMA_CHXCTL_CHEN;
-        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) |= DMA_CHXCTL_CHEN;
-
-        SPI_CTL1(SD_SPI) |= SPI_CTL1_DMAREN | SPI_CTL1_DMATEN;
-        
-        // DMA transfer is now running, we can start sending received bytes to SCSI
-        uint32_t *word_ptr = (uint32_t*)buf;
-        uint32_t *end_ptr = word_ptr + (count / 4);
-        while (word_ptr < end_ptr)
-        {
-            uint32_t words_available = (count - DMA_CHCNT(DMA0, SD_SPI_RX_DMA_CHANNEL)) / 4;
-            words_available -= (word_ptr - (uint32_t*)buf);
-            if (words_available > 0)
-            {
-                if (word_ptr + words_available > end_ptr)
-                {
-                    words_available = end_ptr - word_ptr;
-                }
-
-                word_ptr = scsi_send_words_async(word_ptr, words_available);
-            }
-        }
-        
-        SCSI_RELEASE_DATA_REQ();
-
-        if (DMA_INTF(DMA0) & DMA_FLAG_ADD(DMA_FLAG_ERR, SD_SPI_RX_DMA_CHANNEL))
-        {
-            azlog("ERROR: SPI DMA receive set DMA_FLAG_ERR");
-        }
-
-        SPI_CTL1(SD_SPI) &= ~(SPI_CTL1_DMAREN | SPI_CTL1_DMATEN);
-        DMA_CHCTL(DMA0, SD_SPI_RX_DMA_CHANNEL) &= ~DMA_CHXCTL_CHEN;
-        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) &= ~DMA_CHXCTL_CHEN;
-
-        m_stream_status += count;
-        return 0;
-    }
-
-    void send(uint8_t data) {
-        SPI_DATA(SD_SPI) = data;
-        wait_idle();
-    }
-
-    void send(const uint8_t* buf, size_t count) {
-        if (buf == m_stream_buffer + m_stream_status)
-        {
-            stream_send(count);
-            return;
-        }
-
-        for (size_t i = 0; i < count; i++) {
-            while (!(SPI_STAT(SD_SPI) & SPI_STAT_TBE));
-            SPI_DATA(SD_SPI) = buf[i];
-        }
-        wait_idle();
-    }
-
-    // Stream data directly from SCSI bus
-    void stream_send(size_t count)
-    {
-        for (size_t i = 0; i < count; i++) {
-            SCSI_OUT(REQ, 1);
-            SCSI_WAIT_ACTIVE(ACK);
-            delay_100ns(); // ACK.Fall to DB output delay 100ns(MAX)  (DTC-510B)
-            uint8_t data = SCSI_IN_DATA();
-            SCSI_OUT(REQ, 0);
-
-            while (!(SPI_STAT(SD_SPI) & SPI_STAT_TBE));
-            SPI_DATA(SD_SPI) = data;
-
-            SCSI_WAIT_INACTIVE(ACK);
-        }
-        wait_idle();
-
-        m_stream_status += count;
-    }
-
-    void setSckSpeed(uint32_t maxSck) {
-        m_sckfreq = maxSck;
-    }
-
-    void prepare_stream(uint8_t *buffer)
-    {
-        m_stream_buffer = buffer;
-        m_stream_status = 0;
-    }
-
-    size_t finish_stream()
-    {
-        size_t result = m_stream_status;
-        m_stream_status = 0;
-        m_stream_buffer = NULL;
-        return result;
-    }
-
-private:
-    uint32_t m_sckfreq;
-    uint8_t *m_stream_buffer;
-    size_t m_stream_status; // Number of bytes transferred so far
-};
-
-void sdCsInit(SdCsPin_t pin)
-{
-}
-
-void sdCsWrite(SdCsPin_t pin, bool level)
-{
-    if (level)
-        GPIO_BOP(SD_PORT) = SD_CS_PIN;
-    else
-        GPIO_BC(SD_PORT) = SD_CS_PIN;
-}
-
-GD32SPIDriver g_sd_spi_port;
-SdSpiConfig g_sd_spi_config(0, DEDICATED_SPI, SD_SCK_MHZ(30), &g_sd_spi_port);
-
-void azplatform_prepare_stream(uint8_t *buffer)
-{
-    g_sd_spi_port.prepare_stream(buffer);
-}
-
-size_t azplatform_finish_stream()
-{
-    return g_sd_spi_port.finish_stream();
-}
-
 /**********************************************/
 /* Mapping from data bytes to GPIO BOP values */
 /**********************************************/

+ 5 - 12
lib/AzulSCSI_platform_GD32F205/AzulSCSI_platform.h

@@ -31,7 +31,7 @@ void azplatform_log(const char *s);
 
 // Minimal millis() implementation as GD32F205 does not
 // have an Arduino core yet.
-unsigned long millis();
+unsigned long millis(void);
 void delay(unsigned long ms);
 
 // Precise nanosecond delays
@@ -55,9 +55,6 @@ static inline void delay_100ns()
 // Initialize SPI and GPIO configuration
 void azplatform_init();
 
-// Set callback for when SCSI_RST pin goes low
-void azplatform_set_rst_callback(void (*callback)());
-
 // Setup soft watchdog
 void azplatform_reset_watchdog(int timeout_ms);
 
@@ -65,14 +62,10 @@ void azplatform_reset_watchdog(int timeout_ms);
 // This can be used in crash handlers.
 void azplatform_emergency_log_save();
 
-// Direct streaming between SCSI and SD card
-// If the SD card driver receives a read request to buffer, it will directly send the data to SCSI bus.
-// If the SD card driver receives a write request from buffer, it will directly get the data from SCSI.
-void azplatform_prepare_stream(uint8_t *buffer);
-
-// Get status of latest streaming operation.
-// Returns number of bytes transferred.
-size_t azplatform_finish_stream();
+// Set callback that will be called during data transfer to/from SD card.
+// This can be used to implement simultaneous transfer to SCSI bus.
+typedef void (*sd_callback_t)(uint32_t bytes_complete);
+void azplatform_set_sd_callback(sd_callback_t func, const uint8_t *buffer);
 
 // Write a single SCSI pin.
 // Example use: SCSI_OUT(ATN, 1) sets SCSI_ATN to low (active) state.

+ 9 - 2
lib/AzulSCSI_platform_GD32F205/AzulSCSI_v1_0_gpio.h

@@ -49,8 +49,6 @@
 #define SCSI_OUT_REQ_PIN  SCSI_OUT_REQ
 
 // SCSI input status signals
-#define SCSI_BSY_PORT GPIOB
-#define SCSI_BSY_PIN  GPIO_PIN_10
 #define SCSI_SEL_PORT GPIOB
 #define SCSI_SEL_PIN  GPIO_PIN_11
 #define SCSI_ACK_PORT GPIOB
@@ -65,6 +63,15 @@
 #define SCSI_ATN_PIN  GPIO_PIN_6
 #endif
 
+// BSY pin uses EXTI interrupt
+#define SCSI_BSY_PORT GPIOB
+#define SCSI_BSY_PIN  GPIO_PIN_10
+#define SCSI_BSY_EXTI EXTI_10
+#define SCSI_BSY_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB
+#define SCSI_BSY_EXTI_SOURCE_PIN  GPIO_PIN_SOURCE_10
+#define SCSI_BSY_IRQ  EXTI10_15_IRQHandler
+#define SCSI_BSY_IRQn EXTI10_15_IRQn
+
 // RST pin uses EXTI interrupt
 #define SCSI_RST_PORT GPIOB
 #define SCSI_RST_PIN  GPIO_PIN_13

+ 5 - 0
lib/AzulSCSI_platform_GD32F205/bsp.h

@@ -0,0 +1,5 @@
+// Dummy file for SCSI2SD.
+
+#pragma once
+
+#define S2S_DMA_ALIGN

+ 295 - 0
lib/AzulSCSI_platform_GD32F205/scsiPhy.cpp

@@ -0,0 +1,295 @@
+// Implements the low level interface to SCSI bus
+// Partially derived from scsiPhy.c from SCSI2SD-V6
+
+#include "scsiPhy.h"
+#include "AzulSCSI_platform.h"
+#include "scsi_accel_asm.h"
+#include "AzulSCSI_log.h"
+#include "AzulSCSI_log_trace.h"
+
+#include <scsi2sd.h>
+extern "C" {
+#include <scsi.h>
+#include <time.h>
+}
+
+static void init_irqs();
+
+/***********************/
+/* SCSI status signals */
+/***********************/
+
+extern "C" bool scsiStatusATN()
+{
+    return SCSI_IN(ATN);
+}
+
+extern "C" bool scsiStatusBSY()
+{
+    return SCSI_IN(BSY);
+}
+
+/************************/
+/* SCSI selection logic */
+/************************/
+
+volatile uint8_t g_scsi_sts_selection;
+volatile uint8_t g_scsi_ctrl_bsy;
+
+static void scsi_bsy_deassert_interrupt()
+{
+    if (SCSI_IN(SEL) && !SCSI_IN(BSY))
+    {
+        uint8_t sel_bits = SCSI_IN_DATA();
+        int sel_id = -1;
+        for (int i = 0; i < S2S_MAX_TARGETS; i++)
+        {
+            if (scsiDev.targets[i].targetId <= 7 && scsiDev.targets[i].cfg)
+            {
+                if (sel_bits & (1 << scsiDev.targets[i].targetId))
+                {
+                    sel_id = scsiDev.targets[i].targetId;
+                    break;
+                }
+            }
+        }
+
+        if (sel_id >= 0)
+        {
+            uint8_t atn_flag = SCSI_IN(ATN) ? SCSI_STS_SELECTION_ATN : 0;
+            g_scsi_sts_selection = SCSI_STS_SELECTION_SUCCEEDED | atn_flag | sel_id;
+        }
+    }
+}
+
+extern "C" bool scsiStatusSEL()
+{
+    if (g_scsi_ctrl_bsy)
+    {
+        // We don't have direct register access to BSY bit like SCSI2SD scsi.c expects.
+        // Instead update the state here.
+        // Releasing happens with bus release.
+        g_scsi_ctrl_bsy = 0;
+        SCSI_OUT(BSY, 1);
+    }
+
+    return SCSI_IN(SEL);
+}
+
+/************************/
+/* SCSI bus reset logic */
+/************************/
+
+static void scsi_rst_assert_interrupt()
+{
+    bool rst1 = SCSI_IN(RST);
+    delay_ns(500);
+    bool rst2 = SCSI_IN(RST);
+
+    if (rst1 && rst2)
+    {
+        scsiDev.resetFlag = 1;
+    }
+}
+
+extern "C" void scsiPhyReset(void)
+{
+    SCSI_RELEASE_OUTPUTS();
+    g_scsi_sts_selection = 0;
+    g_scsi_ctrl_bsy = 0;
+    init_irqs();
+}
+
+/************************/
+/* SCSI bus phase logic */
+/************************/
+
+static SCSI_PHASE g_scsi_phase;
+
+extern "C" void scsiEnterPhase(int phase)
+{
+    int delay = scsiEnterPhaseImmediate(phase);
+    if (delay > 0)
+    {
+        s2s_delay_ns(delay);
+    }
+}
+
+// Change state and return nanosecond delay to wait
+extern "C" uint32_t scsiEnterPhaseImmediate(int phase)
+{
+    // ANSI INCITS 362-2002 SPI-3 10.7.1:
+    // Phase changes are not allowed while REQ or ACK is asserted.
+    while (likely(!scsiDev.resetFlag) && SCSI_IN(ACK)) {}
+
+    if (phase != g_scsi_phase)
+    {
+        if (phase < 0)
+        {
+            // Other communication on bus or reset state
+            SCSI_RELEASE_OUTPUTS();
+        }
+        else
+        {
+            SCSI_OUT(MSG, phase & __scsiphase_msg);
+            SCSI_OUT(CD,  phase & __scsiphase_cd);
+            SCSI_OUT(IO,  phase & __scsiphase_io);
+        }
+
+        scsiLogPhaseChange(phase);
+        g_scsi_phase = (SCSI_PHASE)phase;
+
+        // Hardcoded 100 us delay for now.
+        return 100000;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+// Release all signals
+void scsiEnterBusFree(void)
+{
+    g_scsi_phase = BUS_FREE;
+    g_scsi_sts_selection = 0;
+    g_scsi_ctrl_bsy = 0;
+    scsiDev.cdbLen = 0;
+    
+    SCSI_RELEASE_OUTPUTS();
+}
+
+/********************/
+/* Transmit to host */
+/********************/
+
+#define SCSI_WAIT_ACTIVE(pin) \
+  if (!SCSI_IN(pin)) { \
+    if (!SCSI_IN(pin)) { \
+      while(!SCSI_IN(pin) && !scsiDev.resetFlag); \
+    } \
+  }
+
+#define SCSI_WAIT_INACTIVE(pin) \
+  if (SCSI_IN(pin)) { \
+    if (SCSI_IN(pin)) { \
+      while(SCSI_IN(pin) && !scsiDev.resetFlag); \
+    } \
+  }
+
+static inline void scsiWriteOneByte(uint8_t value)
+{
+    SCSI_OUT_DATA(value);
+    delay_100ns(); // DB setup time before REQ
+    SCSI_OUT(REQ, 1);
+    SCSI_WAIT_ACTIVE(ACK);
+    SCSI_RELEASE_DATA_REQ(); // Release data and REQ
+    SCSI_WAIT_INACTIVE(ACK);
+}
+
+extern "C" void scsiWriteByte(uint8_t value)
+{
+    scsiLogDataIn(&value, 1);
+    scsiWriteOneByte(value);
+}
+
+extern "C" void scsiWrite(const uint8_t* data, uint32_t count)
+{
+    uint32_t count_words = count / 4;
+    scsiLogDataIn(data, count);
+    if (count_words * 4 == count)
+    {
+        // Use accelerated subroutine
+        scsi_accel_asm_send((const uint32_t*)data, count_words, &scsiDev.resetFlag);
+    }
+    else
+    {
+        for (uint32_t i = 0; i < count; i++)
+        {
+            if (scsiDev.resetFlag) break;
+            scsiWriteOneByte(data[i]);
+        }
+    }
+}
+
+/*********************/
+/* Receive from host */
+/*********************/
+
+static inline uint8_t scsiReadOneByte(void)
+{
+    SCSI_OUT(REQ, 1);
+    SCSI_WAIT_ACTIVE(ACK);
+    delay_100ns();
+    uint8_t r = SCSI_IN_DATA();
+    SCSI_OUT(REQ, 0);
+    SCSI_WAIT_INACTIVE(ACK);
+
+    return r;
+}
+
+extern "C" uint8_t scsiReadByte(void)
+{
+    uint8_t r = scsiReadOneByte();
+    scsiLogDataOut(&r, 1);
+    return r;
+}
+
+extern "C" void scsiRead(uint8_t* data, uint32_t count, int* parityError)
+{
+    *parityError = 0;
+
+    for (uint32_t i = 0; i < count; i++)
+    {
+        if (scsiDev.resetFlag) break;
+
+        data[i] = scsiReadOneByte();
+    }
+
+    scsiLogDataOut(data, count);
+}
+
+/**********************/
+/* Interrupt handlers */
+/**********************/
+
+extern "C"
+void SCSI_RST_IRQ (void)
+{
+    if (exti_interrupt_flag_get(SCSI_RST_EXTI))
+    {
+        exti_interrupt_flag_clear(SCSI_RST_EXTI);
+        scsi_rst_assert_interrupt();
+    }
+
+    if (exti_interrupt_flag_get(SCSI_BSY_EXTI))
+    {
+        exti_interrupt_flag_clear(SCSI_BSY_EXTI);
+        scsi_bsy_deassert_interrupt();
+    }
+}
+
+#if SCSI_RST_IRQn != SCSI_BSY_IRQn
+extern "C"
+void SCSI_BSY_IRQ (void)
+{
+    SCSI_RST_IRQ();
+}
+#endif
+
+static void init_irqs()
+{
+    // Falling edge of RST pin
+    gpio_exti_source_select(SCSI_RST_EXTI_SOURCE_PORT, SCSI_RST_EXTI_SOURCE_PIN);
+    exti_init(SCSI_RST_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
+    NVIC_SetPriority(SCSI_RST_IRQn, 1);
+    NVIC_EnableIRQ(SCSI_RST_IRQn);
+
+    // Rising edge of BSY pin
+    gpio_exti_source_select(SCSI_BSY_EXTI_SOURCE_PORT, SCSI_BSY_EXTI_SOURCE_PIN);
+    exti_init(SCSI_BSY_EXTI, EXTI_INTERRUPT, EXTI_TRIG_RISING);
+    NVIC_SetPriority(SCSI_BSY_IRQn, 1);
+    NVIC_EnableIRQ(SCSI_BSY_IRQn);
+}
+
+

+ 57 - 0
lib/AzulSCSI_platform_GD32F205/scsiPhy.h

@@ -0,0 +1,57 @@
+// Interface to SCSI physical interface.
+// This file is derived from scsiPhy.h in SCSI2SD-V6.
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Read SCSI status signals
+bool scsiStatusATN();
+bool scsiStatusBSY();
+bool scsiStatusSEL();
+
+// Parity not yet implemented
+#define scsiParityError() 0
+
+// Get SCSI selection status.
+// This is latched by interrupt when BSY is deasserted while SEL is asserted.
+// Lowest 3 bits are the selected target id.
+// Highest bits are status information.
+#define SCSI_STS_SELECTION_SUCCEEDED 0x40
+#define SCSI_STS_SELECTION_ATN 0x80
+extern volatile uint8_t g_scsi_sts_selection;
+#define SCSI_STS_SELECTED (&g_scsi_sts_selection)
+extern volatile uint8_t g_scsi_ctrl_bsy;
+#define SCSI_CTRL_BSY (&g_scsi_ctrl_bsy)
+
+// Called when SCSI RST signal has been asserted, should release bus.
+void scsiPhyReset(void);
+
+// Change MSG / CD / IO signal states and wait for necessary transition time.
+// Phase argument is one of SCSI_PHASE enum values.
+void scsiEnterPhase(int phase);
+
+// Change state and return nanosecond delay to wait
+uint32_t scsiEnterPhaseImmediate(int phase);
+
+// Release all signals
+void scsiEnterBusFree(void);
+
+//void scsiSetDataCount(uint32_t count);
+//int scsiFifoReady(void);
+
+void scsiWrite(const uint8_t* data, uint32_t count);
+void scsiRead(uint8_t* data, uint32_t count, int* parityError);
+void scsiWriteByte(uint8_t value);
+uint8_t scsiReadByte(void);
+
+#define s2s_getScsiRateKBs() 0
+
+#ifdef __cplusplus
+}
+#endif

+ 100 - 0
lib/AzulSCSI_platform_GD32F205/scsi_accel_asm.cpp

@@ -0,0 +1,100 @@
+#include "scsi_accel_asm.h"
+#include "AzulSCSI_platform.h"
+
+// Optimized ASM blocks for the SCSI communication subroutine
+
+// Take 8 bits from d and format them for writing
+// d is name of data operand, b is bit offset, x is unique label
+#define ASM_LOAD_DATA(d, b, x) \
+"    load_data1_" x "_%=: \n" \
+"        ubfx    %[tmp1], %[" d "], #" b ", #8 \n" \
+"        ldr     %[tmp1], [%[byte_lookup], %[tmp1], lsl #2] \n"
+
+// Write data to SCSI port and set REQ high
+#define ASM_SEND_DATA(x) \
+"    send_data" x "_%=: \n" \
+"        str     %[tmp1], [%[out_port_bop]] \n"
+
+// Wait for ACK to be high, set REQ low, wait ACK low
+#define ASM_HANDSHAKE(x) \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        str     %[tmp2], [%[req_pin_bb]] \n" \
+"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        str     %[tmp2], [%[req_pin_bb]] \n" \
+"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        str     %[tmp2], [%[req_pin_bb]] \n" \
+"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
+"    wait_ack_inactive" x "_%=: \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        str     %[tmp2], [%[req_pin_bb]] \n" \
+"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
+"        ldr     %[tmp2], [%[reset_flag]] \n" \
+"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
+"        b.n     wait_ack_inactive" x "_%= \n" \
+"    req_is_low_now" x "_%=: \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
+"    wait_ack_active" x "_%=: \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
+"        ldr     %[tmp2], [%[reset_flag]] \n" \
+"        cbnz    %[tmp2], over_ack_active" x "_%= \n" \
+"        b.n     wait_ack_active" x "_%= \n" \
+"    over_ack_active" x "_%=: \n" \
+
+// Send bytes to SCSI bus using the asynchronous handshake mechanism
+// Takes 4 bytes at a time for sending from buf.
+// Returns the next buffer pointer.
+void scsi_accel_asm_send(const uint32_t *buf, uint32_t num_words, volatile int *resetFlag)
+{
+    volatile uint32_t *out_port_bop = (volatile uint32_t*)&GPIO_BOP(SCSI_OUT_PORT);
+    const uint32_t *byte_lookup = g_scsi_out_byte_to_bop;
+    uint32_t ack_pin_bb = PERIPH_BB_BASE + (((uint32_t)&GPIO_ISTAT(SCSI_ACK_PORT)) - APB1_BUS_BASE) * 32 + 12 * 4;
+    uint32_t req_pin_bb = PERIPH_BB_BASE + (((uint32_t)out_port_bop) - APB1_BUS_BASE) * 32 + (9 + 16) * 4;
+    register uint32_t tmp1 = 0;
+    register uint32_t tmp2 = 0;
+    register uint32_t data = 0;
+
+    asm volatile (
+    "   ldr      %[data], [%[buf]], #4 \n" \
+        ASM_LOAD_DATA("data", "0", "first")
+
+    "inner_loop_%=: \n" \
+        ASM_SEND_DATA("0")
+        ASM_LOAD_DATA("data", "8", "8")
+        ASM_HANDSHAKE("0")
+        
+        ASM_SEND_DATA("8")
+        ASM_LOAD_DATA("data", "16", "16")
+        ASM_HANDSHAKE("8")
+
+        ASM_SEND_DATA("16")
+        ASM_LOAD_DATA("data", "24", "24")
+        ASM_HANDSHAKE("16")
+
+        ASM_SEND_DATA("24")
+    "   ldr      %[data], [%[buf]], #4 \n" \
+        ASM_LOAD_DATA("data", "0", "0")
+        ASM_HANDSHAKE("24")
+
+    "   subs     %[num_words], %[num_words], #1 \n" \
+    "   bne     inner_loop_%= \n"
+    : /* Output */ [tmp1] "+l" (tmp1), [tmp2] "+l" (tmp2), [data] "+r" (data),
+                   [buf] "+r" (buf), [num_words] "+r" (num_words)
+    : /* Input */ [ack_pin_bb] "r" (ack_pin_bb),
+                  [req_pin_bb] "r" (req_pin_bb),
+                  [out_port_bop] "r"(out_port_bop),
+                  [byte_lookup] "r" (byte_lookup),
+                  [reset_flag] "r" (resetFlag)
+    : /* Clobber */ );
+
+    SCSI_RELEASE_DATA_REQ();
+}

+ 7 - 0
lib/AzulSCSI_platform_GD32F205/scsi_accel_asm.h

@@ -0,0 +1,7 @@
+// SCSI subroutines using hand-optimized assembler
+
+#pragma once
+
+#include <stdint.h>
+
+void scsi_accel_asm_send(const uint32_t *buf, uint32_t num_words, volatile int *resetFlag);

+ 241 - 0
lib/AzulSCSI_platform_GD32F205/sd_card_spi.cpp

@@ -0,0 +1,241 @@
+// Driver and interface for accessing SD card in SPI mode
+// Used on AzulSCSI v1.0.
+
+#include "AzulSCSI_platform.h"
+#include "AzulSCSI_log.h"
+#include "gd32f20x_spi.h"
+#include "gd32f20x_dma.h"
+#include <SdFat.h>
+
+class GD32SPIDriver : public SdSpiBaseClass
+{
+public:
+    void begin(SdSpiConfig config) {
+        rcu_periph_clock_enable(RCU_SPI0);
+        rcu_periph_clock_enable(RCU_DMA0);
+
+        dma_parameter_struct rx_dma_config =
+        {
+            .periph_addr = (uint32_t)&SPI_DATA(SD_SPI),
+            .periph_width = DMA_PERIPHERAL_WIDTH_8BIT,
+            .memory_addr = 0, // Set before transfer
+            .memory_width = DMA_MEMORY_WIDTH_8BIT,
+            .number = 0, // Set before transfer
+            .priority = DMA_PRIORITY_ULTRA_HIGH,
+            .periph_inc = DMA_PERIPH_INCREASE_DISABLE,
+            .memory_inc = DMA_MEMORY_INCREASE_ENABLE,
+            .direction = DMA_PERIPHERAL_TO_MEMORY
+        };
+        dma_init(DMA0, SD_SPI_RX_DMA_CHANNEL, &rx_dma_config);
+
+        dma_parameter_struct tx_dma_config =
+        {
+            .periph_addr = (uint32_t)&SPI_DATA(SD_SPI),
+            .periph_width = DMA_PERIPHERAL_WIDTH_8BIT,
+            .memory_addr = 0, // Set before transfer
+            .memory_width = DMA_MEMORY_WIDTH_8BIT,
+            .number = 0, // Set before transfer
+            .priority = DMA_PRIORITY_HIGH,
+            .periph_inc = DMA_PERIPH_INCREASE_DISABLE,
+            .memory_inc = DMA_MEMORY_INCREASE_ENABLE,
+            .direction = DMA_MEMORY_TO_PERIPHERAL
+        };
+        dma_init(DMA0, SD_SPI_TX_DMA_CHANNEL, &tx_dma_config);
+    }
+        
+    void activate() {
+        spi_parameter_struct config = {
+            SPI_MASTER,
+            SPI_TRANSMODE_FULLDUPLEX,
+            SPI_FRAMESIZE_8BIT,
+            SPI_NSS_SOFT,
+            SPI_ENDIAN_MSB,
+            SPI_CK_PL_LOW_PH_1EDGE,
+            SPI_PSC_256
+        };
+
+        // Select closest available divider based on system frequency
+        int divider = (SystemCoreClock + m_sckfreq / 2) / m_sckfreq;
+        if (divider <= 2)
+            config.prescale = SPI_PSC_2;
+        else if (divider <= 4)
+            config.prescale = SPI_PSC_4;
+        else if (divider <= 8)
+            config.prescale = SPI_PSC_8;
+        else if (divider <= 16)
+            config.prescale = SPI_PSC_16;
+        else if (divider <= 32)
+            config.prescale = SPI_PSC_32;
+        else if (divider <= 64)
+            config.prescale = SPI_PSC_64;
+        else if (divider <= 128)
+            config.prescale = SPI_PSC_128;
+        else
+            config.prescale = SPI_PSC_256;
+
+        spi_init(SD_SPI, &config);
+        spi_enable(SD_SPI);
+    }
+    
+    void deactivate() {
+        spi_disable(SD_SPI);
+    }
+
+    void wait_idle() {
+        while (!(SPI_STAT(SD_SPI) & SPI_STAT_TBE));
+        while (SPI_STAT(SD_SPI) & SPI_STAT_TRANS);
+    }
+
+    // Single byte receive
+    uint8_t receive() {
+        // Wait for idle and clear RX buffer
+        wait_idle();
+        (void)SPI_DATA(SD_SPI);
+
+        // Send dummy byte and wait for receive
+        SPI_DATA(SD_SPI) = 0xFF;
+        while (!(SPI_STAT(SD_SPI) & SPI_STAT_RBNE));
+        return SPI_DATA(SD_SPI);
+    }
+
+    // Single byte send
+    void send(uint8_t data) {
+        SPI_DATA(SD_SPI) = data;
+        wait_idle();
+    }
+
+    // Multiple byte receive
+    uint8_t receive(uint8_t* buf, size_t count)
+    {
+        // Wait for idle and clear RX buffer
+        wait_idle();
+        (void)SPI_DATA(SD_SPI);
+
+        // Check if this is part of callback streaming request
+        bool stream = false;
+        if (m_stream_callback && buf == m_stream_buffer + m_stream_count)
+        {
+            stream = true;
+            m_stream_count += count;
+        }
+
+        // Use DMA to stream dummy TX data and store RX data
+        uint8_t tx_data = 0xFF;
+        DMA_INTC(DMA0) = DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_RX_DMA_CHANNEL);
+        DMA_INTC(DMA0) = DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_TX_DMA_CHANNEL);
+        DMA_CHMADDR(DMA0, SD_SPI_RX_DMA_CHANNEL) = (uint32_t)buf;
+        DMA_CHMADDR(DMA0, SD_SPI_TX_DMA_CHANNEL) = (uint32_t)&tx_data;
+        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) &= ~DMA_CHXCTL_MNAGA; // No memory increment for TX
+        DMA_CHCNT(DMA0, SD_SPI_RX_DMA_CHANNEL) = count;
+        DMA_CHCNT(DMA0, SD_SPI_TX_DMA_CHANNEL) = count;
+        DMA_CHCTL(DMA0, SD_SPI_RX_DMA_CHANNEL) |= DMA_CHXCTL_CHEN;
+        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) |= DMA_CHXCTL_CHEN;
+
+        SPI_CTL1(SD_SPI) |= SPI_CTL1_DMAREN | SPI_CTL1_DMATEN;
+        
+        uint32_t start = millis();
+        while (!(DMA_INTF(DMA0) & DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_RX_DMA_CHANNEL)))
+        {
+            if (millis() - start > 500)
+            {
+                azlog("ERROR: SPI DMA receive of ", (int)count, " bytes timeouted");
+                return 1;
+            }
+
+            if (stream)
+            {
+                uint32_t complete = (count - DMA_CHCNT(DMA0, SD_SPI_RX_DMA_CHANNEL));
+                m_stream_callback(complete);
+            }
+        }
+
+        if (DMA_INTF(DMA0) & DMA_FLAG_ADD(DMA_FLAG_ERR, SD_SPI_RX_DMA_CHANNEL))
+        {
+            azlog("ERROR: SPI DMA receive set DMA_FLAG_ERR");
+        }
+
+        SPI_CTL1(SD_SPI) &= ~(SPI_CTL1_DMAREN | SPI_CTL1_DMATEN);
+        DMA_CHCTL(DMA0, SD_SPI_RX_DMA_CHANNEL) &= ~DMA_CHXCTL_CHEN;
+        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) &= ~DMA_CHXCTL_CHEN;
+
+        return 0;
+    }
+
+    // Multiple byte send
+    void send(const uint8_t* buf, size_t count) {
+        // Check if this is part of callback streaming request
+        bool stream = false;
+        if (m_stream_callback && buf == m_stream_buffer + m_stream_count)
+        {
+            stream = true;
+            m_stream_count += count;
+        }
+
+        // Use DMA to stream TX data
+        DMA_INTC(DMA0) = DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_TX_DMA_CHANNEL);
+        DMA_CHMADDR(DMA0, SD_SPI_TX_DMA_CHANNEL) = (uint32_t)buf;
+        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) |= DMA_CHXCTL_MNAGA; // Memory increment for TX
+        DMA_CHCNT(DMA0, SD_SPI_TX_DMA_CHANNEL) = count;
+        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) |= DMA_CHXCTL_CHEN;
+
+        SPI_CTL1(SD_SPI) |= SPI_CTL1_DMATEN;
+        
+        uint32_t start = millis();
+        while (!(DMA_INTF(DMA0) & DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_TX_DMA_CHANNEL)))
+        {
+            if (millis() - start > 500)
+            {
+                azlog("ERROR: SPI DMA transmit of ", (int)count, " bytes timeouted");
+                return;
+            }
+
+            if (stream)
+            {
+                uint32_t complete = (count - DMA_CHCNT(DMA0, SD_SPI_TX_DMA_CHANNEL));
+                m_stream_callback(complete);
+            }
+        }
+
+        if (DMA_INTF(DMA0) & DMA_FLAG_ADD(DMA_FLAG_ERR, SD_SPI_TX_DMA_CHANNEL))
+        {
+            azlog("ERROR: SPI DMA transmit set DMA_FLAG_ERR");
+        }
+
+        wait_idle();
+
+        SPI_CTL1(SD_SPI) &= ~(SPI_CTL1_DMAREN | SPI_CTL1_DMATEN);
+        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) &= ~DMA_CHXCTL_CHEN;
+    }
+
+    void setSckSpeed(uint32_t maxSck) {
+        m_sckfreq = maxSck;
+    }
+
+    void set_sd_callback(sd_callback_t func, const uint8_t *buffer)
+    {
+        m_stream_buffer = buffer;
+        m_stream_count = 0;
+        m_stream_callback = func;
+    }
+
+private:
+    uint32_t m_sckfreq;
+    const uint8_t *m_stream_buffer;
+    uint32_t m_stream_count;
+    sd_callback_t m_stream_callback;
+};
+
+void sdCsInit(SdCsPin_t pin)
+{
+}
+
+void sdCsWrite(SdCsPin_t pin, bool level)
+{
+    if (level)
+        GPIO_BOP(SD_PORT) = SD_CS_PIN;
+    else
+        GPIO_BC(SD_PORT) = SD_CS_PIN;
+}
+
+GD32SPIDriver g_sd_spi_port;
+SdSpiConfig g_sd_spi_config(0, DEDICATED_SPI, SD_SCK_MHZ(30), &g_sd_spi_port);

+ 4 - 0
lib/AzulSCSI_platform_GD32F205/sd_card_spi.h

@@ -0,0 +1,4 @@
+
+
+#pragma once
+

+ 14 - 0
lib/AzulSCSI_platform_GD32F205/time.h

@@ -0,0 +1,14 @@
+// Timing functions for SCSI2SD.
+// This file is derived from time.h in SCSI2SD-V6.
+
+#pragma once
+
+#include <stdint.h>
+#include "AzulSCSI_platform.h"
+
+#define s2s_getTime_ms() millis()
+#define s2s_elapsedTime_ms(since) ((uint32_t)(millis() - (since)))
+#define s2s_delay_ms(x) delay_ns(x * 1000000)
+#define s2s_delay_us(x) delay_ns(x * 1000)
+#define s2s_delay_ns(x) delay_ns(x)
+

+ 30 - 30
lib/SCSI2SD/src/firmware/config.h

@@ -1,30 +1,30 @@
-//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
-//
-//	This file is part of SCSI2SD.
-//
-//	SCSI2SD is free software: you can redistribute it and/or modify
-//	it under the terms of the GNU General Public License as published by
-//	the Free Software Foundation, either version 3 of the License, or
-//	(at your option) any later version.
-//
-//	SCSI2SD is distributed in the hope that it will be useful,
-//	but WITHOUT ANY WARRANTY; without even the implied warranty of
-//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//	GNU General Public License for more details.
-//
-//	You should have received a copy of the GNU General Public License
-//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
-#ifndef S2S_Config_H
-#define S2S_Config_H
-
-#include "scsi2sd.h"
-
-void s2s_configInit(S2S_BoardCfg* config);
-void s2s_debugInit(void);
-void s2s_configPoll(void);
-void s2s_configSave(int scsiId, uint16_t byesPerSector);
-
-const S2S_TargetCfg* s2s_getConfigByIndex(int index);
-const S2S_TargetCfg* s2s_getConfigById(int scsiId);
-
-#endif
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef S2S_Config_H
+#define S2S_Config_H
+
+#include "scsi2sd.h"
+
+void s2s_configInit(S2S_BoardCfg* config);
+void s2s_debugInit(void);
+void s2s_configPoll(void);
+void s2s_configSave(int scsiId, uint16_t byesPerSector);
+
+const S2S_TargetCfg* s2s_getConfigByIndex(int index);
+const S2S_TargetCfg* s2s_getConfigById(int scsiId);
+
+#endif

+ 1 - 0
platformio.ini

@@ -11,6 +11,7 @@ lib_deps =
     SdFat_NoArduino
     minIni
     AzulSCSI_platform_GD32F205
+    SCSI2SD
 upload_protocol = stlink
 platform_packages = 
     toolchain-gccarmnoneeabi@1.60301.0

File diff suppressed because it is too large
+ 41 - 1002
src/AzulSCSI.cpp


+ 2 - 1
src/AzulSCSI_config.h

@@ -25,13 +25,14 @@
 
 // SCSI config
 #define NUM_SCSIID  7          // Maximum number of supported SCSI-IDs (The minimum is 0)
-#define NUM_SCSILUN 2          // Maximum number of LUNs supported     (The minimum is 0)
+#define NUM_SCSILUN 1          // Maximum number of LUNs supported     (Currently has to be 1)
 #define READ_PARITY_CHECK 0    // Perform read parity check (unverified)
 
 // Default SCSI drive information (can be overridden in INI file)
 #define DEFAULT_VENDOR "QUANTUM "
 #define DEFAULT_PRODUCT "FIREBALL1       "
 #define DEFAULT_VERSION "1.0 "
+#define DEFAULT_SERIAL  "0123456789ABCDEF"
 
 // Default delay for SCSI phases.
 // Can be adjusted in ini file

+ 822 - 0
src/AzulSCSI_disk.cpp

@@ -0,0 +1,822 @@
+// This file implements the main SCSI disk emulation and data streaming.
+// It is derived from disk.c in SCSI2SD V6.
+//
+//    Licensed under GPL v3.
+//    Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//    Copyright (C) 2014 Doug Brown <doug@downtowndougbrown.com>
+//    Copyright (C) 2022 Rabbit Hole Computing
+
+#include "AzulSCSI_disk.h"
+#include "AzulSCSI_log.h"
+#include <minIni.h>
+#include <string.h>
+#include <SdFat.h>
+
+extern "C" {
+#include <time.h>
+#include <sd.h>
+}
+
+/***********************/
+/* Backing image files */
+/***********************/
+
+extern SdFs SD;
+SdDevice sdDev = {2, 256 * 1024 * 1024 * 2}; /* For SCSI2SD */
+
+struct image_config_t: public S2S_TargetCfg
+{
+    FsFile file;    
+};
+
+static image_config_t g_DiskImages[S2S_MAX_TARGETS];
+
+bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int scsi_lun, int blocksize)
+{
+    image_config_t &img = g_DiskImages[target_idx];
+    img.file = SD.open(filename, O_RDWR);
+
+    if (img.file.isOpen())
+    {
+        img.bytesPerSector = blocksize;
+        img.scsiSectors = img.file.size() / blocksize;
+        img.scsiId = scsi_id | S2S_CFG_TARGET_ENABLED;
+        img.sdSectorStart = 0;
+        
+        if (img.scsiSectors == 0)
+        {
+            azlog("---- Error: image file ", filename, " is empty");
+            img.file.close();
+            return false;
+        }
+
+        if (img.file.contiguousRange(NULL, NULL))
+        {
+            azlog("---- Image file is contiguous.");
+        }
+        else
+        {
+            azlog("---- WARNING: file ", filename, " is not contiguous. This will increase read latency.");
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+void scsiDiskLoadConfig(int target_idx)
+{
+    image_config_t &img = g_DiskImages[target_idx];
+    char section[6] = "SCSI0";
+    section[4] = '0' + target_idx;
+
+    img.deviceType = ini_getl(section, "Type", S2S_CFG_FIXED, CONFIGFILE);
+    img.deviceTypeModifier = ini_getl(section, "TypeModifier", 0, CONFIGFILE);
+    img.sectorsPerTrack = ini_getl(section, "SectorsPerTrack", 18, CONFIGFILE);
+    img.headsPerCylinder = ini_getl(section, "HeadsPerCylinder", 255, CONFIGFILE);
+    img.quirks = ini_getl(section, "Quirks", S2S_CFG_QUIRKS_NONE, CONFIGFILE);
+    
+    char tmp[32];
+    memset(tmp, 0, sizeof(tmp));
+    ini_gets(section, "Vendor", DEFAULT_VENDOR, tmp, sizeof(tmp), CONFIGFILE);
+    memcpy(img.vendor, tmp, 8);
+
+    memset(tmp, 0, sizeof(tmp));
+    ini_gets(section, "Product", DEFAULT_PRODUCT, tmp, sizeof(tmp), CONFIGFILE);
+    memcpy(img.prodId, tmp, 16);
+
+    memset(tmp, 0, sizeof(tmp));
+    ini_gets(section, "Version", DEFAULT_VERSION, tmp, sizeof(tmp), CONFIGFILE);
+    memcpy(img.revision, tmp, 4);
+    
+    memset(tmp, 0, sizeof(tmp));
+    ini_gets(section, "Serial", DEFAULT_SERIAL, tmp, sizeof(tmp), CONFIGFILE);
+    memcpy(img.serial, tmp, 16);
+}
+
+/*******************************/
+/* Config handling for SCSI2SD */
+/*******************************/
+
+extern "C"
+void s2s_configInit(S2S_BoardCfg* config)
+{
+    memset(config, 0, sizeof(S2S_BoardCfg));
+    memcpy(config->magic, "BCFG", 4);
+    config->flags = ini_getl("SCSI", "Flags", 0, CONFIGFILE);
+    config->startupDelay = 0;
+    config->selectionDelay = ini_getl("SCSI", "SelectionDelay", 255, CONFIGFILE);
+    config->flags6 = ini_getl("SCSI", "Flags6", 0, CONFIGFILE);
+    config->scsiSpeed = ini_getl("SCSI", "SCSISpeed", S2S_CFG_SPEED_ASYNC_50, CONFIGFILE);
+}
+
+extern "C"
+void s2s_debugInit(void)
+{
+}
+
+extern "C"
+void s2s_configPoll(void)
+{
+}
+
+extern "C"
+void s2s_configSave(int scsiId, uint16_t byesPerSector)
+{
+    // Modification of config over SCSI bus is not implemented.
+}
+
+extern "C"
+const S2S_TargetCfg* s2s_getConfigByIndex(int index)
+{
+    if (index < 0 || index >= S2S_MAX_TARGETS)
+    {
+        return NULL;
+    }
+    else
+    {
+        return &g_DiskImages[index];
+    }
+}
+
+extern "C"
+const S2S_TargetCfg* s2s_getConfigById(int scsiId)
+{
+    int i;
+    for (i = 0; i < S2S_MAX_TARGETS; ++i)
+    {
+        const S2S_TargetCfg* tgt = s2s_getConfigByIndex(i);
+        if ((tgt->scsiId & S2S_CFG_TARGET_ID_BITS) == scsiId)
+        {
+            return tgt;
+        }
+    }
+    return NULL;
+}
+
+/**********************/
+/* FormatUnit command */
+/**********************/
+
+// Callback once all data has been read in the data out phase.
+static void doFormatUnitComplete(void)
+{
+    scsiDev.phase = STATUS;
+}
+
+static void doFormatUnitSkipData(int bytes)
+{
+    // We may not have enough memory to store the initialisation pattern and
+    // defect list data.  Since we're not making use of it yet anyway, just
+    // discard the bytes.
+    scsiEnterPhase(DATA_OUT);
+    int i;
+    for (i = 0; i < bytes; ++i)
+    {
+        scsiReadByte();
+    }
+}
+
+// Callback from the data out phase.
+static void doFormatUnitPatternHeader(void)
+{
+    int defectLength =
+        ((((uint16_t)scsiDev.data[2])) << 8) +
+            scsiDev.data[3];
+
+    int patternLength =
+        ((((uint16_t)scsiDev.data[4 + 2])) << 8) +
+        scsiDev.data[4 + 3];
+
+        doFormatUnitSkipData(defectLength + patternLength);
+        doFormatUnitComplete();
+}
+
+// Callback from the data out phase.
+static void doFormatUnitHeader(void)
+{
+    int IP = (scsiDev.data[1] & 0x08) ? 1 : 0;
+    int DSP = (scsiDev.data[1] & 0x04) ? 1 : 0;
+
+    if (! DSP) // disable save parameters
+    {
+        // Save the "MODE SELECT savable parameters"
+        s2s_configSave(
+            scsiDev.target->targetId,
+            scsiDev.target->liveCfg.bytesPerSector);
+    }
+
+    if (IP)
+    {
+        // We need to read the initialisation pattern header first.
+        scsiDev.dataLen += 4;
+        scsiDev.phase = DATA_OUT;
+        scsiDev.postDataOutHook = doFormatUnitPatternHeader;
+    }
+    else
+    {
+        // Read the defect list data
+        int defectLength =
+            ((((uint16_t)scsiDev.data[2])) << 8) +
+            scsiDev.data[3];
+        doFormatUnitSkipData(defectLength);
+        doFormatUnitComplete();
+    }
+}
+
+/************************/
+/* ReadCapacity command */
+/************************/
+
+static void doReadCapacity()
+{
+    uint32_t lba = (((uint32_t) scsiDev.cdb[2]) << 24) +
+        (((uint32_t) scsiDev.cdb[3]) << 16) +
+        (((uint32_t) scsiDev.cdb[4]) << 8) +
+        scsiDev.cdb[5];
+    int pmi = scsiDev.cdb[8] & 1;
+
+    uint32_t capacity = getScsiCapacity(
+        scsiDev.target->cfg->sdSectorStart,
+        scsiDev.target->liveCfg.bytesPerSector,
+        scsiDev.target->cfg->scsiSectors);
+
+    if (!pmi && lba)
+    {
+        // error.
+        // We don't do anything with the "partial medium indicator", and
+        // assume that delays are constant across each block. But the spec
+        // says we must return this error if pmi is specified incorrectly.
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+        scsiDev.phase = STATUS;
+    }
+    else if (capacity > 0)
+    {
+        uint32_t highestBlock = capacity - 1;
+
+        scsiDev.data[0] = highestBlock >> 24;
+        scsiDev.data[1] = highestBlock >> 16;
+        scsiDev.data[2] = highestBlock >> 8;
+        scsiDev.data[3] = highestBlock;
+
+        uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
+        scsiDev.data[4] = bytesPerSector >> 24;
+        scsiDev.data[5] = bytesPerSector >> 16;
+        scsiDev.data[6] = bytesPerSector >> 8;
+        scsiDev.data[7] = bytesPerSector;
+        scsiDev.dataLen = 8;
+        scsiDev.phase = DATA_IN;
+    }
+    else
+    {
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = NOT_READY;
+        scsiDev.target->sense.asc = MEDIUM_NOT_PRESENT;
+        scsiDev.phase = STATUS;
+    }
+}
+
+/*************************/
+/* TestUnitReady command */
+/*************************/
+
+static int doTestUnitReady()
+{
+    int ready = 1;
+    if (likely(blockDev.state == (DISK_PRESENT | DISK_INITIALISED) &&
+		scsiDev.target->started))
+    {
+        // nothing to do.
+    }
+    else if (unlikely(!scsiDev.target->started))
+    {
+        ready = 0;
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = NOT_READY;
+        scsiDev.target->sense.asc = LOGICAL_UNIT_NOT_READY_INITIALIZING_COMMAND_REQUIRED;
+        scsiDev.phase = STATUS;
+    }
+    else if (unlikely(!(blockDev.state & DISK_PRESENT)))
+    {
+        ready = 0;
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = NOT_READY;
+        scsiDev.target->sense.asc = MEDIUM_NOT_PRESENT;
+        scsiDev.phase = STATUS;
+    }
+    else if (unlikely(!(blockDev.state & DISK_INITIALISED)))
+    {
+        ready = 0;
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = NOT_READY;
+        scsiDev.target->sense.asc = LOGICAL_UNIT_NOT_READY_CAUSE_NOT_REPORTABLE;
+        scsiDev.phase = STATUS;
+    }
+    return ready;
+}
+
+/****************/
+/* Seek command */
+/****************/
+
+static void doSeek(uint32_t lba)
+{
+    if (lba >=
+        getScsiCapacity(
+            scsiDev.target->cfg->sdSectorStart,
+            scsiDev.target->liveCfg.bytesPerSector,
+            scsiDev.target->cfg->scsiSectors)
+        )
+    {
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        scsiDev.target->sense.asc = LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+        scsiDev.phase = STATUS;
+    }
+    else
+    {
+        if (unlikely(scsiDev.target->cfg->deviceType == S2S_CFG_FLOPPY_14MB) ||
+            scsiDev.compatMode < COMPAT_SCSI2)
+        {
+            s2s_delay_ms(10);
+        }
+        else
+        {
+            s2s_delay_us(10);
+        }
+    }
+}
+
+/********************************************/
+/* Transfer state for read / write commands */
+/********************************************/
+
+BlockDevice blockDev;
+Transfer transfer;
+
+/*****************/
+/* Write command */
+/*****************/
+
+static void doWrite(uint32_t lba, uint32_t blocks)
+{
+    if (unlikely(scsiDev.target->cfg->deviceType == S2S_CFG_FLOPPY_14MB)) {
+        // Floppies are supposed to be slow. Some systems can't handle a floppy
+        // without an access time
+        s2s_delay_ms(10);
+    }
+
+    uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
+
+    if (unlikely(blockDev.state & DISK_WP) ||
+        unlikely(scsiDev.target->cfg->deviceType == S2S_CFG_OPTICAL))
+
+    {
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        scsiDev.target->sense.asc = WRITE_PROTECTED;
+        scsiDev.phase = STATUS;
+    }
+    else if (unlikely(((uint64_t) lba) + blocks >
+        getScsiCapacity(
+            scsiDev.target->cfg->sdSectorStart,
+            bytesPerSector,
+            scsiDev.target->cfg->scsiSectors
+            )
+        ))
+    {
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        scsiDev.target->sense.asc = LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+        scsiDev.phase = STATUS;
+    }
+    else
+    {
+        transfer.multiBlock = true;
+        transfer.lba = lba;
+        transfer.blocks = blocks;
+        transfer.currentBlock = 0;
+        scsiDev.phase = DATA_OUT;
+        scsiDev.dataLen = bytesPerSector;
+        scsiDev.dataPtr = bytesPerSector;
+
+        image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+        if (!img.file.seek(transfer.lba * img.bytesPerSector))
+        {
+            azlog("Seek to ", transfer.lba, " failed for ", scsiDev.target->targetId);
+            scsiDev.status = CHECK_CONDITION;
+            scsiDev.target->sense.code = MEDIUM_ERROR;
+            scsiDev.target->sense.asc = NO_SEEK_COMPLETE;
+            scsiDev.phase = STATUS;
+        }
+    }
+}
+
+void diskDataOut_callback(uint32_t bytes_complete)
+{
+    if (scsiDev.dataPtr < scsiDev.dataLen)
+    {
+        // DMA is now writing to SD card.
+        // We can use this time to transfer next block from SCSI bus.
+        uint32_t len = scsiDev.dataLen - scsiDev.dataPtr;
+        if (len > 512) len = 512;
+        
+        int parityError = 0;
+        scsiRead(scsiDev.data + scsiDev.dataPtr, len, &parityError);
+        scsiDev.dataPtr += len;
+
+        if (parityError)
+        {
+            scsiDev.status = CHECK_CONDITION;
+            scsiDev.target->sense.code = ABORTED_COMMAND;
+            scsiDev.target->sense.asc = SCSI_PARITY_ERROR;
+            scsiDev.phase = STATUS;
+        }
+    }
+}
+
+void diskDataOut()
+{
+    scsiEnterPhase(DATA_OUT);
+
+    // Figure out how many blocks we can fit in buffer
+    uint32_t blockcount = (transfer.blocks - transfer.currentBlock);
+    uint32_t maxblocks = sizeof(scsiDev.data) / scsiDev.target->liveCfg.bytesPerSector;
+    if (blockcount > maxblocks) blockcount = maxblocks;
+    uint32_t transferlen = blockcount * scsiDev.target->liveCfg.bytesPerSector;
+
+    // Read first block from SCSI bus
+    scsiDev.dataLen = transferlen;
+    scsiDev.dataPtr = 0;
+    diskDataOut_callback(0);
+    
+    // Start writing blocks to SD card.
+    // The callback will simultaneously read the next block from SCSI bus.
+    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+    uint32_t written = 0;
+    while (written < transferlen)
+    {
+        uint8_t *buf = scsiDev.data + written;
+        uint32_t buflen = scsiDev.dataPtr - written;
+        azplatform_set_sd_callback(&diskDataOut_callback, buf);
+        if (img.file.write(buf, buflen) != buflen)
+        {
+            azlog("SD card write failed: ", SD.sdErrorCode());
+            scsiDev.status = CHECK_CONDITION;
+            scsiDev.target->sense.code = MEDIUM_ERROR;
+            scsiDev.target->sense.asc = WRITE_ERROR_AUTO_REALLOCATION_FAILED;
+            scsiDev.phase = STATUS;
+        }
+
+        written += buflen;
+    }
+
+    azplatform_set_sd_callback(NULL, NULL);
+
+    transfer.currentBlock += blockcount;
+}
+
+/*****************/
+/* Read command */
+/*****************/
+
+static void doRead(uint32_t lba, uint32_t blocks)
+{
+    if (unlikely(scsiDev.target->cfg->deviceType == S2S_CFG_FLOPPY_14MB)) {
+        // Floppies are supposed to be slow. Some systems can't handle a floppy
+        // without an access time
+        s2s_delay_ms(10);
+    }
+
+    uint32_t capacity = getScsiCapacity(
+        scsiDev.target->cfg->sdSectorStart,
+        scsiDev.target->liveCfg.bytesPerSector,
+        scsiDev.target->cfg->scsiSectors);
+    if (unlikely(((uint64_t) lba) + blocks > capacity))
+    {
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        scsiDev.target->sense.asc = LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+        scsiDev.phase = STATUS;
+    }
+    else
+    {
+        transfer.multiBlock = 1;
+        transfer.lba = lba;
+        transfer.blocks = blocks;
+        transfer.currentBlock = 0;
+        scsiDev.phase = DATA_IN;
+        scsiDev.dataLen = 0;
+        scsiDev.dataPtr = 0;
+
+        image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+        if (!img.file.seek(transfer.lba * img.bytesPerSector))
+        {
+            azlog("Seek to ", transfer.lba, " failed for ", scsiDev.target->targetId);
+            scsiDev.status = CHECK_CONDITION;
+            scsiDev.target->sense.code = MEDIUM_ERROR;
+            scsiDev.target->sense.asc = NO_SEEK_COMPLETE;
+            scsiDev.phase = STATUS;
+        }
+    }
+}
+
+void diskDataIn_callback(uint32_t bytes_complete)
+{
+    // For best performance, do writes in blocks of 4 or more bytes
+    bytes_complete &= ~3;
+
+    if (bytes_complete > scsiDev.dataPtr)
+    {
+        // DMA is reading from SD card, bytes_complete bytes have already been read.
+        // Send them to SCSI bus now.
+        
+        uint32_t len = bytes_complete - scsiDev.dataPtr;
+        scsiWrite(scsiDev.data + scsiDev.dataPtr, len);
+        scsiDev.dataPtr += len;
+    }
+}
+
+static void diskDataIn()
+{
+    scsiEnterPhase(DATA_IN);
+
+    // Figure out how many blocks we can fit in buffer
+    uint32_t blockcount = (transfer.blocks - transfer.currentBlock);
+    uint32_t maxblocks = sizeof(scsiDev.data) / scsiDev.target->liveCfg.bytesPerSector;
+    if (blockcount > maxblocks) blockcount = maxblocks;
+    uint32_t transferlen = blockcount * scsiDev.target->liveCfg.bytesPerSector;
+
+    // Start reading from SD card.
+    // The callback will write to SCSI bus.
+    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+    azplatform_set_sd_callback(&diskDataIn_callback, scsiDev.data);
+
+    if (img.file.read(scsiDev.data, transferlen) != transferlen)
+    {
+        azlog("SD card read failed: ", SD.sdErrorCode());
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = MEDIUM_ERROR;
+        scsiDev.target->sense.asc = UNRECOVERED_READ_ERROR;
+        scsiDev.phase = STATUS;
+    }
+
+    azplatform_set_sd_callback(NULL, NULL);
+    transfer.currentBlock += blockcount;
+}
+
+
+/********************/
+/* Command dispatch */
+/********************/
+
+// Handle direct-access scsi device commands
+extern "C"
+int scsiDiskCommand()
+{
+    int commandHandled = 1;
+
+    uint8_t command = scsiDev.cdb[0];
+    if (unlikely(command == 0x1B))
+    {
+        // START STOP UNIT
+        // Enable or disable media access operations.
+        //int immed = scsiDev.cdb[1] & 1;
+        int start = scsiDev.cdb[4] & 1;
+	    int loadEject = scsiDev.cdb[4] & 2;
+	
+        if (loadEject)
+        {
+            // Ignore load/eject requests. We can't do that.
+        }
+        else if (start)
+        {
+            scsiDev.target->started = 1;
+            blockDev.state = DISK_INITIALISED;
+        }
+        else
+        {
+            scsiDev.target->started = 0;
+        }
+    }
+    else if (unlikely(command == 0x00))
+    {
+        // TEST UNIT READY
+        doTestUnitReady();
+    }
+    else if (unlikely(!doTestUnitReady()))
+    {
+        // Status and sense codes already set by doTestUnitReady
+    }
+    else if (likely(command == 0x08))
+    {
+        // READ(6)
+        uint32_t lba =
+            (((uint32_t) scsiDev.cdb[1] & 0x1F) << 16) +
+            (((uint32_t) scsiDev.cdb[2]) << 8) +
+            scsiDev.cdb[3];
+        uint32_t blocks = scsiDev.cdb[4];
+        if (unlikely(blocks == 0)) blocks = 256;
+        doRead(lba, blocks);
+    }
+    else if (likely(command == 0x28))
+    {
+        // READ(10)
+        // Ignore all cache control bits - we don't support a memory cache.
+
+        uint32_t lba =
+            (((uint32_t) scsiDev.cdb[2]) << 24) +
+            (((uint32_t) scsiDev.cdb[3]) << 16) +
+            (((uint32_t) scsiDev.cdb[4]) << 8) +
+            scsiDev.cdb[5];
+        uint32_t blocks =
+            (((uint32_t) scsiDev.cdb[7]) << 8) +
+            scsiDev.cdb[8];
+
+        doRead(lba, blocks);
+    }
+    else if (likely(command == 0x0A))
+    {
+        // WRITE(6)
+        uint32_t lba =
+            (((uint32_t) scsiDev.cdb[1] & 0x1F) << 16) +
+            (((uint32_t) scsiDev.cdb[2]) << 8) +
+            scsiDev.cdb[3];
+        uint32_t blocks = scsiDev.cdb[4];
+        if (unlikely(blocks == 0)) blocks = 256;
+        doWrite(lba, blocks);
+    }
+    else if (likely(command == 0x2A) || // WRITE(10)
+        unlikely(command == 0x2E)) // WRITE AND VERIFY
+    {
+        // Ignore all cache control bits - we don't support a memory cache.
+        // Don't bother verifying either. The SD card likely stores ECC
+        // along with each flash row.
+
+        uint32_t lba =
+            (((uint32_t) scsiDev.cdb[2]) << 24) +
+            (((uint32_t) scsiDev.cdb[3]) << 16) +
+            (((uint32_t) scsiDev.cdb[4]) << 8) +
+            scsiDev.cdb[5];
+        uint32_t blocks =
+            (((uint32_t) scsiDev.cdb[7]) << 8) +
+            scsiDev.cdb[8];
+
+        doWrite(lba, blocks);
+    }
+    else if (unlikely(command == 0x04))
+    {
+        // FORMAT UNIT
+        // We don't really do any formatting, but we need to read the correct
+        // number of bytes in the DATA_OUT phase to make the SCSI host happy.
+
+        int fmtData = (scsiDev.cdb[1] & 0x10) ? 1 : 0;
+        if (fmtData)
+        {
+            // We need to read the parameter list, but we don't know how
+            // big it is yet. Start with the header.
+            scsiDev.dataLen = 4;
+            scsiDev.phase = DATA_OUT;
+            scsiDev.postDataOutHook = doFormatUnitHeader;
+        }
+        else
+        {
+            // No data to read, we're already finished!
+        }
+    }
+    else if (unlikely(command == 0x25))
+    {
+        // READ CAPACITY
+        doReadCapacity();
+    }
+    else if (unlikely(command == 0x0B))
+    {
+        // SEEK(6)
+        uint32_t lba =
+            (((uint32_t) scsiDev.cdb[1] & 0x1F) << 16) +
+            (((uint32_t) scsiDev.cdb[2]) << 8) +
+            scsiDev.cdb[3];
+
+        doSeek(lba);
+    }
+
+    else if (unlikely(command == 0x2B))
+    {
+        // SEEK(10)
+        uint32_t lba =
+            (((uint32_t) scsiDev.cdb[2]) << 24) +
+            (((uint32_t) scsiDev.cdb[3]) << 16) +
+            (((uint32_t) scsiDev.cdb[4]) << 8) +
+            scsiDev.cdb[5];
+
+        doSeek(lba);
+    }
+    else if (unlikely(command == 0x36))
+    {
+        // LOCK UNLOCK CACHE
+        // We don't have a cache to lock data into. do nothing.
+    }
+    else if (unlikely(command == 0x34))
+    {
+        // PRE-FETCH.
+        // We don't have a cache to pre-fetch into. do nothing.
+    }
+    else if (unlikely(command == 0x1E))
+    {
+        // PREVENT ALLOW MEDIUM REMOVAL
+        // Not much we can do to prevent the user removing the SD card.
+        // do nothing.
+    }
+    else if (unlikely(command == 0x01))
+    {
+        // REZERO UNIT
+        // Set the lun to a vendor-specific state. Ignore.
+    }
+    else if (unlikely(command == 0x35))
+    {
+        // SYNCHRONIZE CACHE
+        // We don't have a cache. do nothing.
+    }
+    else if (unlikely(command == 0x2F))
+    {
+        // VERIFY
+        // TODO: When they supply data to verify, we should read the data and
+        // verify it. If they don't supply any data, just say success.
+        if ((scsiDev.cdb[1] & 0x02) == 0)
+        {
+            // They are asking us to do a medium verification with no data
+            // comparison. Assume success, do nothing.
+        }
+        else
+        {
+            // TODO. This means they are supplying data to verify against.
+            // Technically we should probably grab the data and compare it.
+            scsiDev.status = CHECK_CONDITION;
+            scsiDev.target->sense.code = ILLEGAL_REQUEST;
+            scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+            scsiDev.phase = STATUS;
+        }
+    }
+    else if (unlikely(command == 0x37))
+    {
+        // READ DEFECT DATA
+        uint32_t allocLength = (((uint16_t)scsiDev.cdb[7]) << 8) |
+            scsiDev.cdb[8];
+
+        scsiDev.data[0] = 0;
+        scsiDev.data[1] = scsiDev.cdb[1];
+        scsiDev.data[2] = 0;
+        scsiDev.data[3] = 0;
+        scsiDev.dataLen = 4;
+
+        if (scsiDev.dataLen > allocLength)
+        {
+            scsiDev.dataLen = allocLength;
+        }
+
+        scsiDev.phase = DATA_IN;
+    }
+    else
+    {
+        commandHandled = 0;
+    }
+
+    return commandHandled;
+}
+
+extern "C"
+void scsiDiskPoll()
+{
+    if (scsiDev.phase == DATA_IN &&
+        transfer.currentBlock != transfer.blocks)
+    {
+        diskDataIn();
+     }
+    else if (scsiDev.phase == DATA_OUT &&
+        transfer.currentBlock != transfer.blocks)
+    {
+        diskDataOut();
+    }
+}
+
+extern "C"
+void scsiDiskReset()
+{
+    scsiDev.dataPtr = 0;
+    scsiDev.savedDataPtr = 0;
+    scsiDev.dataLen = 0;
+    // transfer.lba = 0; // Needed in Request Sense to determine failure
+    transfer.blocks = 0;
+    transfer.currentBlock = 0;
+    transfer.multiBlock = 0;
+}
+
+extern "C"
+void scsiDiskInit()
+{
+    scsiDiskReset();
+}
+

+ 17 - 0
src/AzulSCSI_disk.h

@@ -0,0 +1,17 @@
+// SCSI disk access routines
+// Implements both SCSI2SD V6 disk.h functions and some extra.
+
+#pragma once
+
+#include <stdint.h>
+#include <scsi2sd.h>
+#include <scsiPhy.h>
+
+extern "C" {
+#include <disk.h>
+#include <config.h>
+#include <scsi.h>
+}
+
+bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int scsi_lun, int blocksize);
+void scsiDiskLoadConfig(int target_idx);

+ 138 - 0
src/AzulSCSI_log_trace.cpp

@@ -0,0 +1,138 @@
+// SCSI trace logging
+
+#include "AzulSCSI_log_trace.h"
+#include "AzulSCSI_log.h"
+#include <scsi2sd.h>
+
+extern "C" {
+#include <scsi.h>
+#include <scsiPhy.h>
+}
+
+static bool g_LogData = false;
+
+static const char *getCommandName(uint8_t cmd)
+{
+    if (cmd == 0x00) return "TestUnitReady";
+    if (cmd == 0x1A) return "ModeSense";
+    if (cmd == 0x5A) return "ModeSense10";
+    if (cmd == 0x0A) return "Write6";
+    if (cmd == 0x2A) return "Write10";
+    if (cmd == 0x08) return "Read6";
+    if (cmd == 0x28) return "Read10";
+    if (cmd == 0x12) return "Inquiry";
+    if (cmd == 0x25) return "ReadCapacity";
+    return "";
+}
+
+static void printCommand()
+{
+    uint8_t cmd = scsiDev.cdb[0];
+    const char *cmdname = getCommandName(cmd);
+
+    azdbg("---- COMMAND: ", cmdname, " ", bytearray(scsiDev.cdb, scsiDev.cdbLen));
+}
+
+static void printNewPhase(int phase)
+{
+    g_LogData = true; //false;
+    if (!g_azlog_debug)
+    {
+        return;
+    }
+
+    switch(phase)
+    {
+        case BUS_FREE:
+            azdbg("-- BUS_FREE");
+            break;
+        
+        case BUS_BUSY:
+            azdbg("-- BUS_BUSY");
+            break;
+        
+        case ARBITRATION:
+            azdbg("---- ARBITRATION");
+            break;
+        
+        case SELECTION:
+            azdbg("---- SELECTION: ", (int)(*SCSI_STS_SELECTED & 7));
+            break;
+        
+        case RESELECTION:
+            azdbg("---- RESELECTION");
+            break;
+        
+        case STATUS:
+            if (scsiDev.status == GOOD)
+            {
+                azdbg("---- STATUS: 0 GOOD");
+            }
+            else if (scsiDev.status == CHECK_CONDITION && scsiDev.target)
+            {
+                azdbg("---- STATUS: 2 CHECK_CONDITION, sense ", (uint32_t)scsiDev.target->sense.asc);
+            }
+            else
+            {
+                azdbg("---- STATUS: ", (int)scsiDev.status);
+            }
+            break;
+        
+        case COMMAND:
+            break;
+        
+        case DATA_IN:
+            azdbg("---- DATA_IN");
+            break;
+        
+        case DATA_OUT:
+            azdbg("---- DATA_OUT");
+            break;
+        
+        case MESSAGE_IN:
+            azdbg("---- MESSAGE_IN");
+            g_LogData = true;
+            break;
+        
+        case MESSAGE_OUT:
+            azdbg("---- MESSAGE_OUT");
+            g_LogData = true;
+            break;
+        
+        default:
+            azdbg("---- PHASE: ", phase);
+            break;
+    }
+}
+
+void scsiLogPhaseChange(int new_phase)
+{
+    static int old_phase = BUS_FREE;
+
+    if (new_phase != old_phase)
+    {
+        if (old_phase == COMMAND && scsiDev.cdbLen > 0)
+        {
+            printCommand();
+        }
+
+        printNewPhase(new_phase);
+        old_phase = new_phase;
+    }
+}
+
+void scsiLogDataIn(const uint8_t *buf, uint32_t length)
+{
+    if (g_LogData)
+    {
+        azdbg("------ IN: ", bytearray(buf, length));
+    }
+}
+
+void scsiLogDataOut(const uint8_t *buf, uint32_t length)
+{
+    if (g_LogData)
+    {
+        azdbg("------ OUT: ", bytearray(buf, length));
+    }
+}

+ 10 - 0
src/AzulSCSI_log_trace.h

@@ -0,0 +1,10 @@
+// SCSI trace logging
+
+#pragma once
+
+#include <stdint.h>
+
+// Called from scsiPhy.cpp
+void scsiLogPhaseChange(int new_phase);
+void scsiLogDataIn(const uint8_t *buf, uint32_t length);
+void scsiLogDataOut(const uint8_t *buf, uint32_t length);

Some files were not shown because too many files changed in this diff