Browse Source

Improve performance with streaming transfers.

Data is streamed directly between SD card and SCSI for lower latency and higher throughput.

Clock source is now internal 8 MHz oscillator multiplied to 120 MHz.
Petteri Aimonen 3 years ago
parent
commit
53e81f23f2

+ 147 - 5
lib/AzulSCSI_platform_GD32F205/AzulSCSI_platform.cpp

@@ -21,6 +21,22 @@ void delay(unsigned long ms)
     while ((uint32_t)(g_millisecond_counter - start) < ms);
 }
 
+void delay_ns(unsigned long ns)
+{
+    int cycles = (ns * SysTick->LOAD) / 1024 / 1024;
+    cycles -= 10; // Call overhead
+    if (cycles > 0)
+    {
+        int end = (int)SysTick->VAL - cycles;
+        if (end < 0)
+        {
+            end += SysTick->LOAD;
+            while (SysTick->VAL < cycles);
+        }
+        while (SysTick->VAL > end);
+    }
+}
+
 void SysTick_Handler(void)
 {
     g_millisecond_counter++;
@@ -199,19 +215,19 @@ void show_hardfault(uint32_t *sp)
     {
         // Flash the crash address on the LED
         // Short pulse means 0, long pulse means 1
-        int base_delay = 500000;
+        int base_delay = 1000;
         for (int i = 31; i >= 0; i--)
         {
             LED_OFF();
-            for (int j = 0; j < base_delay; j++) delay_100ns();
+            for (int j = 0; j < base_delay; j++) delay_ns(100000);
             
             int delay = (pc & (1 << i)) ? (3 * base_delay) : base_delay;
             LED_ON();
-            for (int j = 0; j < delay; j++) delay_100ns();
+            for (int j = 0; j < delay; j++) delay_ns(100000);
             LED_OFF();
         }
 
-        for (int j = 0; j < base_delay * 10; j++) delay_100ns();
+        for (int j = 0; j < base_delay * 10; j++) delay_ns(100000);
     }
 }
 
@@ -250,6 +266,22 @@ void UsageFault_Handler(void)
 /* 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); \
+    } \
+  }
+
 #define SD_SPI SPI0
 class GD32SPIDriver : public SdSpiBaseClass
 {
@@ -317,6 +349,13 @@ public:
         wait_idle();
         (void)SPI_DATA(SD_SPI);
 
+        if (buf == m_stream_buffer)
+        {
+            // Stream data directly to SCSI bus
+            return stream_receive(count);
+        }
+        
+        // Store data to buffer
         for (size_t i = 0; i < count; i++)
         {
             while (!(SPI_STAT(SD_SPI) & SPI_STAT_TBE));
@@ -328,12 +367,67 @@ public:
         return 0;
     }
 
+    // Stream data directly to SCSI bus
+    uint8_t stream_receive(size_t count)
+    {         
+        // Handle first byte
+        SPI_DATA(SD_SPI) = 0xFF;
+        while (!(SPI_STAT(SD_SPI) & SPI_STAT_RBNE));
+        uint8_t data = SPI_DATA(SD_SPI);
+        SCSI_OUT_DATA(data);
+        SPI_DATA(SD_SPI) = 0xFF;
+        SCSI_WAIT_INACTIVE(ACK);
+        SCSI_OUT(REQ, 1);
+
+        // Handle main payload
+        for (size_t i = 1; i < count - 1; i++)
+        {
+            // Wait that host confirms previous reception
+            SCSI_WAIT_ACTIVE(ACK);
+            SCSI_OUT(REQ, 0);
+
+            // Wait for received byte
+            while (!(SPI_STAT(SD_SPI) & SPI_STAT_RBNE));
+            data = SPI_DATA(SD_SPI);
+
+            // Stream byte to SCSI
+            SCSI_OUT_DATA(data);
+            
+            // Start SPI transfer for next byte
+            SPI_DATA(SD_SPI) = 0xFF;
+
+            SCSI_WAIT_INACTIVE(ACK); // This takes long enough to fullfill the 100 ns setup time.
+            SCSI_OUT(REQ, 1);
+        }
+
+        // Handle last byte
+        while (!(SPI_STAT(SD_SPI) & SPI_STAT_RBNE));
+        data = SPI_DATA(SD_SPI);
+        SCSI_OUT_DATA(data);
+        delay_100ns(); // DB hold time before REQ (DTC-510B)
+        SCSI_WAIT_INACTIVE(ACK);
+        SCSI_OUT(REQ, 1);
+        SCSI_WAIT_ACTIVE(ACK);
+        SCSI_RELEASE_DATA_REQ();
+        SCSI_WAIT_INACTIVE(ACK);
+
+        m_stream_status = true;
+        m_stream_buffer = NULL;
+        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)
+        {
+            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];
@@ -341,12 +435,49 @@ public:
         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 = true;
+        m_stream_buffer = NULL;
+    }
+
     void setSckSpeed(uint32_t maxSck) {
         m_sckfreq = maxSck;
     }
 
+    void prepare_stream(uint8_t *buffer)
+    {
+        m_stream_buffer = buffer;
+        m_stream_status = false;
+    }
+
+    bool finish_stream()
+    {
+        bool result = m_stream_status;
+        m_stream_status = false;
+        m_stream_buffer = NULL;
+        return result;
+    }
+
 private:
     uint32_t m_sckfreq;
+    uint8_t *m_stream_buffer;
+    bool m_stream_status;
 };
 
 void sdCsInit(SdCsPin_t pin)
@@ -364,6 +495,16 @@ void sdCsWrite(SdCsPin_t pin, bool level)
 GD32SPIDriver g_sd_spi_port;
 SdSpiConfig g_sd_spi_config(0, DEDICATED_SPI, SD_SCK_MHZ(25), &g_sd_spi_port);
 
+void azplatform_prepare_stream(uint8_t *buffer)
+{
+    g_sd_spi_port.prepare_stream(buffer);
+}
+
+bool azplatform_finish_stream()
+{
+    return g_sd_spi_port.finish_stream();
+}
+
 /**********************************************/
 /* Mapping from data bytes to GPIO BOP values */
 /**********************************************/
@@ -378,7 +519,8 @@ SdSpiConfig g_sd_spi_config(0, DEDICATED_SPI, SD_SCK_MHZ(25), &g_sd_spi_port);
     ((n & 0x20) ? (SCSI_OUT_DB5 << 16) : SCSI_OUT_DB5) | \
     ((n & 0x40) ? (SCSI_OUT_DB6 << 16) : SCSI_OUT_DB6) | \
     ((n & 0x80) ? (SCSI_OUT_DB7 << 16) : SCSI_OUT_DB7) | \
-    (PARITY(n)  ? (SCSI_OUT_DBP << 16) : SCSI_OUT_DBP) \
+    (PARITY(n)  ? (SCSI_OUT_DBP << 16) : SCSI_OUT_DBP) | \
+    (SCSI_OUT_REQ) \
 )
     
 const uint32_t g_scsi_out_byte_to_bop[256] =

+ 18 - 8
lib/AzulSCSI_platform_GD32F205/AzulSCSI_platform.h

@@ -109,13 +109,13 @@ unsigned long millis();
 void delay(unsigned long ms);
 
 // Precise nanosecond delays
+// Works in interrupt context also, max delay 500 000 ns, min delay about 500 ns
+void delay_ns(unsigned long ns);
+
+// Approximate fast delay
 static inline void delay_100ns()
 {
-#if HXTAL_VALUE==8000000
-    asm("nop"); asm("nop"); asm("nop");
-#else
     asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop");
-#endif
 }
 
 // Initialize SPI and GPIO configuration
@@ -128,6 +128,16 @@ void azplatform_set_rst_callback(void (*callback)());
 // 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.
+// If this returns false, the caller should do the SCSI write themselves.
+// Usually that happens if the read goes through SdFs cache.
+bool azplatform_finish_stream();
+
 // Write a single SCSI pin.
 // Example use: SCSI_OUT(ATN, 1) sets SCSI_ATN to low (active) state.
 #define SCSI_OUT(pin, state) \
@@ -138,14 +148,14 @@ void azplatform_emergency_log_save();
 #define SCSI_IN(pin) \
     ((GPIO_ISTAT(SCSI_ ## pin ## _PORT) & (SCSI_ ## pin ## _PIN)) ? 0 : 1)
 
-// Write SCSI data bus
+// Write SCSI data bus, also sets REQ to inactive.
 extern const uint32_t g_scsi_out_byte_to_bop[256];
 #define SCSI_OUT_DATA(data) \
     GPIO_BOP(SCSI_OUT_PORT) = g_scsi_out_byte_to_bop[(uint8_t)(data)]
 
-// Release SCSI data bus
-#define SCSI_RELEASE_DATA() \
-    GPIO_BOP(SCSI_OUT_PORT) = SCSI_OUT_DATA_MASK
+// Release SCSI data bus and REQ signal
+#define SCSI_RELEASE_DATA_REQ() \
+    GPIO_BOP(SCSI_OUT_PORT) = SCSI_OUT_DATA_MASK | SCSI_OUT_REQ
 
 // Release all SCSI outputs
 #define SCSI_RELEASE_OUTPUTS() \

+ 1 - 1
platformio.ini

@@ -19,7 +19,7 @@ platform_packages =
 build_flags = 
      -Os -Wall -Wno-sign-compare
      -ggdb -g3 -Isrc
-     -DHXTAL_VALUE=8000000
+     -D__SYSTEM_CLOCK_120M_PLL_IRC8M=120000000
      -DSPI_DRIVER_SELECT=3
      -DSD_CHIP_SELECT_MODE=2
      -DENABLE_DEDICATED_SPI=1

+ 58 - 13
src/AzulSCSI.cpp

@@ -73,7 +73,7 @@ void blinkStatus(int count)
 /* Global state for SCSI code    */
 /*********************************/
 
-static volatile bool g_busreset = false;
+volatile bool g_busreset = false;
 static uint8_t g_sensekey = 0;
 uint8_t   g_scsi_id_mask;    // Mask list of responding SCSI IDs
 uint8_t   g_scsi_id;         // Currently responding SCSI-ID
@@ -296,16 +296,31 @@ void findHDDImages()
 #define active   1
 #define inactive 0
 
+#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); \
+    } \
+  }
+
 /*
  * Read by handshake.
  */
 inline uint8_t readHandshake(void)
 {
   SCSI_OUT(REQ,active);
-  while (!SCSI_IN(ACK)) { if(g_busreset) return 0; }
+  SCSI_WAIT_ACTIVE(ACK);
+  delay_100ns(); // ACK.Fall to DB output delay 100ns(MAX)  (DTC-510B)
   uint8_t r = SCSI_IN_DATA();
-  SCSI_OUT(REQ,inactive);
-  while (SCSI_IN(ACK)) { if(g_busreset) return 0; }
+  SCSI_OUT(REQ, inactive);
+  SCSI_WAIT_INACTIVE(ACK);
   return r;  
 }
 
@@ -315,13 +330,11 @@ inline uint8_t readHandshake(void)
 inline void writeHandshake(uint8_t d)
 {
   SCSI_OUT_DATA(d);
-  SCSI_OUT(REQ,inactive);
-  delay_100ns(); // ACK.Fall to DB output delay 100ns(MAX)  (DTC-510B)
+  delay_100ns(); // DB hold time before REQ (DTC-510B)
   SCSI_OUT(REQ, active);
-  while(!g_busreset && !SCSI_IN(ACK));
-  SCSI_OUT(REQ, inactive); // ACK.Fall to REQ.Raise max delay 500ns(typ.) (DTC-510B)
-  SCSI_RELEASE_DATA(); // REQ.Raise to DB hold time 0ns
-  while(!g_busreset && SCSI_IN(ACK));
+  SCSI_WAIT_ACTIVE(ACK);
+  SCSI_RELEASE_DATA_REQ(); // Release data and REQ
+  SCSI_WAIT_INACTIVE(ACK);
 }
 
 /*
@@ -334,9 +347,7 @@ void writeDataPhase(int len, const uint8_t* p)
   SCSI_OUT(CD ,inactive);
   SCSI_OUT(IO ,  active);
   for (int i = 0; i < len; i++) {
-    if(g_busreset) {
-      return;
-    }
+    if (g_busreset) break;
     writeHandshake(p[i]);
   }
 }
@@ -358,8 +369,22 @@ void writeDataPhase_FromSD(uint32_t adds, uint32_t len)
 
   for(uint32_t i = 0; i < len; i++)
   {
+#if STREAM_SD_TRANSFERS
+    azplatform_prepare_stream(buf);
+    g_currentimg->m_file.read(buf, g_currentimg->m_blocksize);
+
+    if (g_busreset) return;
+
+    if (!azplatform_finish_stream())
+    {
+      // Streaming did not happen, send data now
+      azdbg("Streaming from SD failed, using fallback");
+      writeDataPhase(g_currentimg->m_blocksize, buf);
+    }
+#else
     g_currentimg->m_file.read(buf, g_currentimg->m_blocksize);
     writeDataPhase(g_currentimg->m_blocksize, buf);
+#endif
   }
 }
 
@@ -396,9 +421,27 @@ void readDataPhase_ToSD(uint32_t adds, uint32_t len)
 
   for (uint32_t i = 0; i < len; i++)
   {
+#if STREAM_SD_TRANSFERS
+  azplatform_prepare_stream(buf);
+  g_currentimg->m_file.write(buf, g_currentimg->m_blocksize);
+
+  if (g_busreset) return;
+
+  if (!azplatform_finish_stream())
+  {
+    // Streaming did not happen, rewrite
+    azdbg("Streaming to SD failed, using fallback");
+
+    g_currentimg->m_file.seek(pos + i * g_currentimg->m_blocksize);
+
     readDataPhase(g_currentimg->m_blocksize, buf);
     g_currentimg->m_file.write(buf, g_currentimg->m_blocksize);
   }
+#else
+    readDataPhase(g_currentimg->m_blocksize, buf);
+    g_currentimg->m_file.write(buf, g_currentimg->m_blocksize);
+#endif
+  }
   g_currentimg->m_file.flush();
 }
 
@@ -664,6 +707,8 @@ int readSCSICommand(uint8_t cmd[12])
   SCSI_OUT(CD ,  active);
   SCSI_OUT(IO ,inactive);
 
+  delay_ns(500);
+
   cmd[0] = readHandshake();
   if (g_busreset) return 0;
 

+ 3 - 0
src/AzulSCSI_config.h

@@ -27,3 +27,6 @@
 #define DEFAULT_VENDOR "QUANTUM "
 #define DEFAULT_PRODUCT "FIREBALL1       "
 #define DEFAULT_VERSION "1.0 "
+
+// Use streaming SCSI and SD transfers for higher performance
+#define STREAM_SD_TRANSFERS 1