Procházet zdrojové kódy

Add template for porting to new platforms

Petteri Aimonen před 3 roky
rodič
revize
9a607f7a0c

+ 4 - 2
README.md

@@ -83,6 +83,8 @@ Project structure
 - **lib/SdFat_NoArduino**: Modified version of [SdFat](https://github.com/greiman/SdFat) library for use without Arduino core.
 - **utils/run_gdb.sh**: Helper script for debugging with st-link adapter. Displays SWO log directly in console.
 
+To port the code to a new platform, see README in [lib/AzulSCSI_platform_template](lib/AzulSCSI_platform_template) folder.
+
 Building
 --------
 This codebase uses [PlatformIO](https://platformio.org/).
@@ -107,8 +109,8 @@ Main program structure:
 
 Major changes from BlueSCSI and SCSI2SD include:
 
-* Separation of platform-specific functionality to separate file to ease porting.
+* Separation of platform-specific functionality to separate directory 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.
+* Simultaneous transfer between SD card and SCSI for improved performance.

+ 8 - 0
greenpak/README.md

@@ -0,0 +1,8 @@
+GreenPAK design files
+=====================
+
+This folder contains design files for `SLG46824` programmable logic device.
+It is optionally used on AzulSCSI V1.1 to speed up access to SCSI bus.
+
+What this logic does is implement the `REQ` / `ACK` handshake in hardware.
+The CPU only has to write new data to the GPIO, and the external logic will toggle `REQ` signal quickly.

+ 108 - 0
lib/AzulSCSI_platform_template/AzulSCSI_platform.cpp

@@ -0,0 +1,108 @@
+#include "AzulSCSI_platform.h"
+#include "AzulSCSI_log.h"
+#include "AzulSCSI_config.h"
+#include <SdFat.h>
+#include <scsi.h>
+#include <assert.h>
+
+extern "C" {
+
+const char *g_azplatform_name = PLATFORM_NAME;
+
+/***************/
+/* GPIO init   */
+/***************/
+
+void azplatform_init()
+{
+    /* Initialize SCSI and SD card pins to required modes.
+     * SCSI pins should be inactive / input at this point.
+     */
+}
+
+void azplatform_late_init()
+{
+    /* This function can usually be left empty.
+     * It can be used for initialization code that should not run in bootloader.
+     */
+}
+
+/*****************************************/
+/* Debug logging and watchdor            */
+/*****************************************/
+
+// This function is called for every log message.
+// It can e.g. write the log to serial port in real time.
+// It can also be left empty to use only the debug log file on SD card.
+void azplatform_log(const char *s)
+{
+}
+
+// This function can be used to periodically reset watchdog timer for crash handling.
+// It can also be left empty if the platform does not use a watchdog timer.
+void azplatform_reset_watchdog()
+{
+}
+
+/**********************************************/
+/* Mapping from data bytes to GPIO BOP values */
+/**********************************************/
+
+/* A lookup table is the fastest way to calculate parity and convert the IO pin mapping for
+ * data bus. The method below uses the BOP register of GD32, this is called BSRR on STM32.
+ * If there are no other pins on the same port, you can also use direct writes to the GPIO.
+ */
+
+#define PARITY(n) ((1 ^ (n) ^ ((n)>>1) ^ ((n)>>2) ^ ((n)>>3) ^ ((n)>>4) ^ ((n)>>5) ^ ((n)>>6) ^ ((n)>>7)) & 1)
+#define X(n) (\
+    ((n & 0x01) ? (SCSI_OUT_DB0 << 16) : SCSI_OUT_DB0) | \
+    ((n & 0x02) ? (SCSI_OUT_DB1 << 16) : SCSI_OUT_DB1) | \
+    ((n & 0x04) ? (SCSI_OUT_DB2 << 16) : SCSI_OUT_DB2) | \
+    ((n & 0x08) ? (SCSI_OUT_DB3 << 16) : SCSI_OUT_DB3) | \
+    ((n & 0x10) ? (SCSI_OUT_DB4 << 16) : SCSI_OUT_DB4) | \
+    ((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) | \
+    (SCSI_OUT_REQ) \
+)
+
+const uint32_t g_scsi_out_byte_to_bop[256] =
+{
+    X(0x00), X(0x01), X(0x02), X(0x03), X(0x04), X(0x05), X(0x06), X(0x07), X(0x08), X(0x09), X(0x0a), X(0x0b), X(0x0c), X(0x0d), X(0x0e), X(0x0f),
+    X(0x10), X(0x11), X(0x12), X(0x13), X(0x14), X(0x15), X(0x16), X(0x17), X(0x18), X(0x19), X(0x1a), X(0x1b), X(0x1c), X(0x1d), X(0x1e), X(0x1f),
+    X(0x20), X(0x21), X(0x22), X(0x23), X(0x24), X(0x25), X(0x26), X(0x27), X(0x28), X(0x29), X(0x2a), X(0x2b), X(0x2c), X(0x2d), X(0x2e), X(0x2f),
+    X(0x30), X(0x31), X(0x32), X(0x33), X(0x34), X(0x35), X(0x36), X(0x37), X(0x38), X(0x39), X(0x3a), X(0x3b), X(0x3c), X(0x3d), X(0x3e), X(0x3f),
+    X(0x40), X(0x41), X(0x42), X(0x43), X(0x44), X(0x45), X(0x46), X(0x47), X(0x48), X(0x49), X(0x4a), X(0x4b), X(0x4c), X(0x4d), X(0x4e), X(0x4f),
+    X(0x50), X(0x51), X(0x52), X(0x53), X(0x54), X(0x55), X(0x56), X(0x57), X(0x58), X(0x59), X(0x5a), X(0x5b), X(0x5c), X(0x5d), X(0x5e), X(0x5f),
+    X(0x60), X(0x61), X(0x62), X(0x63), X(0x64), X(0x65), X(0x66), X(0x67), X(0x68), X(0x69), X(0x6a), X(0x6b), X(0x6c), X(0x6d), X(0x6e), X(0x6f),
+    X(0x70), X(0x71), X(0x72), X(0x73), X(0x74), X(0x75), X(0x76), X(0x77), X(0x78), X(0x79), X(0x7a), X(0x7b), X(0x7c), X(0x7d), X(0x7e), X(0x7f),
+    X(0x80), X(0x81), X(0x82), X(0x83), X(0x84), X(0x85), X(0x86), X(0x87), X(0x88), X(0x89), X(0x8a), X(0x8b), X(0x8c), X(0x8d), X(0x8e), X(0x8f),
+    X(0x90), X(0x91), X(0x92), X(0x93), X(0x94), X(0x95), X(0x96), X(0x97), X(0x98), X(0x99), X(0x9a), X(0x9b), X(0x9c), X(0x9d), X(0x9e), X(0x9f),
+    X(0xa0), X(0xa1), X(0xa2), X(0xa3), X(0xa4), X(0xa5), X(0xa6), X(0xa7), X(0xa8), X(0xa9), X(0xaa), X(0xab), X(0xac), X(0xad), X(0xae), X(0xaf),
+    X(0xb0), X(0xb1), X(0xb2), X(0xb3), X(0xb4), X(0xb5), X(0xb6), X(0xb7), X(0xb8), X(0xb9), X(0xba), X(0xbb), X(0xbc), X(0xbd), X(0xbe), X(0xbf),
+    X(0xc0), X(0xc1), X(0xc2), X(0xc3), X(0xc4), X(0xc5), X(0xc6), X(0xc7), X(0xc8), X(0xc9), X(0xca), X(0xcb), X(0xcc), X(0xcd), X(0xce), X(0xcf),
+    X(0xd0), X(0xd1), X(0xd2), X(0xd3), X(0xd4), X(0xd5), X(0xd6), X(0xd7), X(0xd8), X(0xd9), X(0xda), X(0xdb), X(0xdc), X(0xdd), X(0xde), X(0xdf),
+    X(0xe0), X(0xe1), X(0xe2), X(0xe3), X(0xe4), X(0xe5), X(0xe6), X(0xe7), X(0xe8), X(0xe9), X(0xea), X(0xeb), X(0xec), X(0xed), X(0xee), X(0xef),
+    X(0xf0), X(0xf1), X(0xf2), X(0xf3), X(0xf4), X(0xf5), X(0xf6), X(0xf7), X(0xf8), X(0xf9), X(0xfa), X(0xfb), X(0xfc), X(0xfd), X(0xfe), X(0xff)
+};
+
+#undef X
+
+} /* extern "C" */
+
+/* The SdFat library is used for SD card access.
+ * You can set the configuration here.
+ * Refer to SdFat examples for usage on various CPUs.
+ */
+SdSpiConfig g_sd_spi_config(0, DEDICATED_SPI, SD_SCK_MHZ(25));
+
+void azplatform_set_sd_callback(sd_callback_t func, const uint8_t *buffer)
+{
+    /* This function can be left empty.
+     * If the platform supports DMA for SD card transfers, this function
+     * can be used to set a callback that is invoked while waiting for DMA
+     * to finish. In that way the SD card and SCSI transfers can execute
+     * simultaneously.
+     */
+}

+ 103 - 0
lib/AzulSCSI_platform_template/AzulSCSI_platform.h

@@ -0,0 +1,103 @@
+// Platform-specific definitions for AzulSCSI.
+//
+// This file is example platform definition that can easily be
+// customized for a different board / CPU.
+
+#pragma once
+
+/* Add any platform-specific includes you need here */
+#include <stdint.h>
+#include <Arduino.h>
+#include "AzulSCSI_platform_gpio.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* These are used in debug output and default SCSI strings */
+extern const char *g_azplatform_name;
+#define PLATFORM_NAME "Example"
+#define PLATFORM_REVISION "1.0"
+
+// Debug logging function, can be used to print to e.g. serial port.
+// May get called from interrupt handlers.
+void azplatform_log(const char *s);
+
+// Timing and delay functions.
+// Arduino platform already provides these
+unsigned long millis(void);
+void delay(unsigned long ms);
+
+// Short delays, can be called from interrupt mode
+static inline void delay_ns(unsigned long ns)
+{
+    delayMicroseconds(ns / 1000);
+}
+
+// Approximate fast delay
+static inline void delay_100ns()
+{
+    asm volatile ("nop \n nop \n nop \n nop \n nop");
+}
+
+// Initialize SD card and GPIO configuration
+void azplatform_init();
+
+// Initialization for main application, not used for bootloader
+void azplatform_late_init();
+
+// Setup soft watchdog if supported
+void azplatform_reset_watchdog();
+
+// 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);
+
+// Below are GPIO access definitions that are used from scsiPhy.cpp.
+// The definitions shown will work for STM32 style devices, other platforms
+// will need adaptations.
+
+// Write a single SCSI pin.
+// Example use: SCSI_OUT(ATN, 1) sets SCSI_ATN to low (active) state.
+#define SCSI_OUT(pin, state) \
+    (SCSI_OUT_ ## pin ## _PORT)->BSRR = (SCSI_OUT_ ## pin ## _PIN) << (state ? 16 : 0)
+
+// Read a single SCSI pin.
+// Example use: SCSI_IN(ATN), returns 1 for active low state.
+#define SCSI_IN(pin) \
+    (((SCSI_ ## pin ## _PORT)->IDR & (SCSI_ ## pin ## _PIN)) ? 0 : 1)
+
+// 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) \
+    (SCSI_OUT_PORT)->BSRR = g_scsi_out_byte_to_bop[(uint8_t)(data)]
+
+// Release SCSI data bus and REQ signal
+#define SCSI_RELEASE_DATA_REQ() \
+    (SCSI_OUT_PORT)->BSRR = SCSI_OUT_DATA_MASK | SCSI_OUT_REQ
+
+// Release all SCSI outputs
+#define SCSI_RELEASE_OUTPUTS() \
+    (SCSI_OUT_PORT)->BSRR = SCSI_OUT_DATA_MASK | SCSI_OUT_REQ, \
+    (SCSI_OUT_IO_PORT)->BSRR  = SCSI_OUT_IO_PIN, \
+    (SCSI_OUT_CD_PORT)->BSRR  = SCSI_OUT_CD_PIN, \
+    (SCSI_OUT_SEL_PORT)->BSRR = SCSI_OUT_SEL_PIN, \
+    (SCSI_OUT_MSG_PORT)->BSRR = SCSI_OUT_MSG_PIN, \
+    (SCSI_OUT_RST_PORT)->BSRR = SCSI_OUT_RST_PIN, \
+    (SCSI_OUT_BSY_PORT)->BSRR = SCSI_OUT_BSY_PIN
+
+// Read SCSI data bus
+#define SCSI_IN_DATA(data) \
+    (((~(SCSI_IN_PORT->IDR)) & SCSI_IN_MASK) >> SCSI_IN_SHIFT)
+
+#ifdef __cplusplus
+}
+
+// SD card driver for SdFat
+class SdSpiConfig;
+extern SdSpiConfig g_sd_spi_config;
+#define SD_CONFIG g_sd_spi_config
+#define SD_CONFIG_CRASH g_sd_spi_config
+
+#endif

+ 67 - 0
lib/AzulSCSI_platform_template/AzulSCSI_platform_gpio.h

@@ -0,0 +1,67 @@
+// Example GPIO definitions for AzulSCSI platform
+
+#pragma once
+
+// SCSI data output port.
+// The output data is written using BSRR mechanism, so all data pins must be on same GPIO port.
+// The output pins are open-drain in hardware, using separate buffer chips for driving.
+#define SCSI_OUT_PORT GPIOD
+#define SCSI_OUT_DB0  GPIO_PIN_0
+#define SCSI_OUT_DB1  GPIO_PIN_1
+#define SCSI_OUT_DB2  GPIO_PIN_2
+#define SCSI_OUT_DB3  GPIO_PIN_3
+#define SCSI_OUT_DB4  GPIO_PIN_4
+#define SCSI_OUT_DB5  GPIO_PIN_5
+#define SCSI_OUT_DB6  GPIO_PIN_6
+#define SCSI_OUT_DB7  GPIO_PIN_7
+#define SCSI_OUT_DBP  GPIO_PIN_8
+#define SCSI_OUT_REQ  GPIO_PIN_9
+#define SCSI_OUT_DATA_MASK (SCSI_OUT_DB0 | SCSI_OUT_DB1 | SCSI_OUT_DB2 | SCSI_OUT_DB3 | SCSI_OUT_DB4 | SCSI_OUT_DB5 | SCSI_OUT_DB6 | SCSI_OUT_DB7 | SCSI_OUT_DBP)
+
+// SCSI input data port (can be same as output port)
+#define SCSI_IN_PORT  GPIOE
+#define SCSI_IN_DB0   GPIO_PIN_0
+#define SCSI_IN_DB1   GPIO_PIN_1
+#define SCSI_IN_DB2   GPIO_PIN_2
+#define SCSI_IN_DB3   GPIO_PIN_3
+#define SCSI_IN_DB4   GPIO_PIN_4
+#define SCSI_IN_DB5   GPIO_PIN_5
+#define SCSI_IN_DB6   GPIO_PIN_6
+#define SCSI_IN_DB7   GPIO_PIN_7
+#define SCSI_IN_DBP   GPIO_PIN_8
+#define SCSI_IN_MASK  (SCSI_IN_DB7|SCSI_IN_DB6|SCSI_IN_DB5|SCSI_IN_DB4|SCSI_IN_DB3|SCSI_IN_DB2|SCSI_IN_DB1|SCSI_IN_DB0|SCSI_IN_DBP)
+#define SCSI_IN_SHIFT 8
+
+// SCSI output status lines
+#define SCSI_OUT_IO_PORT  GPIOD
+#define SCSI_OUT_IO_PIN   GPIO_PIN_10
+#define SCSI_OUT_CD_PORT  GPIOD
+#define SCSI_OUT_CD_PIN   GPIO_PIN_11
+#define SCSI_OUT_SEL_PORT GPIOD
+#define SCSI_OUT_SEL_PIN  GPIO_PIN_12
+#define SCSI_OUT_MSG_PORT GPIOD
+#define SCSI_OUT_MSG_PIN  GPIO_PIN_13
+#define SCSI_OUT_RST_PORT GPIOD
+#define SCSI_OUT_RST_PIN  GPIO_PIN_14
+#define SCSI_OUT_BSY_PORT GPIOD
+#define SCSI_OUT_BSY_PIN  GPIO_PIN_15
+#define SCSI_OUT_REQ_PORT SCSI_OUT_PORT
+#define SCSI_OUT_REQ_PIN  SCSI_OUT_REQ
+
+// SCSI input status signals (can be same as output port)
+#define SCSI_SEL_PORT GPIOD
+#define SCSI_SEL_PIN  GPIO_PIN_12
+#define SCSI_ACK_PORT GPIOE
+#define SCSI_ACK_PIN  GPIO_PIN_0
+#define SCSI_ATN_PORT GPIOE
+#define SCSI_ATN_PIN  GPIO_PIN_1
+#define SCSI_BSY_PORT GPIOE
+#define SCSI_BSY_PIN  GPIO_PIN_2
+#define SCSI_RST_PORT GPIOE
+#define SCSI_RST_PIN  GPIO_PIN_3
+
+// Status LED pins
+#define LED_PORT     GPIOE
+#define LED_PIN      GPIO_PIN_4
+#define LED_ON()     LED_PORT->BSRR = LED_PIN
+#define LED_OFF()    LED_PORT->BRR = LED_PIN

+ 57 - 0
lib/AzulSCSI_platform_template/README.md

@@ -0,0 +1,57 @@
+Porting AzulSCSI firmware to new platforms
+==========================================
+
+The AzulSCSI firmware is designed to be portable to a wide variety of platforms.
+This directory contains an example platform definition that can serve as a base for
+porting efforts.
+
+Creating a new platform definition
+----------------------------------
+
+The bare minimum to support a new platform is to:
+
+1. Make a copy of the `AzulSCSI_platform_template` folder to a new name, e.g. `AzulSCSI_platform_MyCustomHardware`
+2. Make a copy of the `[env:template]` section to a new name, e.g. `[env:MyCustomHardware]`
+3. Edit `AzulSCSI_platform_gpio.h` to match the pinout of your platform.
+4. Edit `AzulSCSI_platform.h` for the hardware access functions implemented in your platform.
+5. Edit `scsiPhy.cpp` to enable the `RST` and `BSY` interrupts.
+
+Required IO capabilities
+------------------------
+
+The minimum IO capabilities for AzulSCSI firmware are:
+
+* Bidirectional access to SCSI data bus: `DB0`-`DB7`, `DBP`
+* Bidirectional access to SCSI signal `BSY`, with rising edge interrupt.
+* Bidirectional access to SCSI signal `RST`, with falling edge interrupt.
+* Output access to SCSI signals `REQ`, `IO`, `CD`, `MSG`
+* Input access to SCSI signals `SEL`, `ACK`, `ATN`
+* Access to SD card, using either SDIO or SPI bus.
+
+RAM usage
+---------
+
+By default the AzulSCSI firmware uses large buffers for best performance.
+The total RAM usage in default configuration is about 100 kB.
+Minimum possible RAM usage is about 10 kB.
+
+To reduce the RAM usage, following settings can be given in `platformio.ini` for the platform:
+
+* `LOGBUFSIZE`: Default 16384, minimum 512 bytes
+* `PREFETCH_BUFFER_SIZE`: Default 8192, minimum 0 bytes
+* `MAX_SECTOR_SIZE`: Default 8192, minimum 512 bytes
+* `SCSI2SD_BUFFER_SIZE`: Default `MAX_SECTOR_SIZE * 8`, minimum `MAX_SECTOR_SIZE * 2`
+
+Enabling parallel transfers
+---------------------------
+
+Access performance is improved a lot if SCSI access can occur in parallel with SD card access.
+To implement this, one or both of them must be able to execute transfers in background using hardware DMA.
+On most platforms this is possible for SD card access.
+The SCSI handshake mechanism is harder to implement using DMA.
+
+To implement parallelism with SD card DMA, implement `azplatform_set_sd_callback(func, buffer)`.
+It sets a callback function which should be called by the SD card driver to report how many bytes have
+been transferred to/from `buffer` so far. The SD card driver should call this function in a loop while
+it is waiting for SD card transfer to finish. The code in `AzulSCSI_disk.cpp` will implement the callback
+that will transfer the data to SCSI bus during the wait.

+ 5 - 0
lib/AzulSCSI_platform_template/bsp.h

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

+ 289 - 0
lib/AzulSCSI_platform_template/scsiPhy.cpp

@@ -0,0 +1,289 @@
+// Implements the low level interface to SCSI bus
+// Partially derived from scsiPhy.c from SCSI2SD-V6
+
+#include "scsiPhy.h"
+#include "AzulSCSI_platform.h"
+#include "AzulSCSI_log.h"
+#include "AzulSCSI_log_trace.h"
+#include "AzulSCSI_config.h"
+
+#include <scsi2sd.h>
+extern "C" {
+#include <scsi.h>
+#include <time.h>
+}
+
+/***********************/
+/* 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;
+
+void scsi_bsy_deassert_interrupt()
+{
+    if (SCSI_IN(SEL) && !SCSI_IN(BSY))
+    {
+        // Check if any of the targets we simulate is selected
+        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;
+        }
+
+        // selFlag is required for Philips P2000C which releases it after 600ns
+        // without waiting for BSY.
+        // Also required for some early Mac Plus roms
+        scsiDev.selFlag = *SCSI_STS_SELECTED;
+    }
+}
+
+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()
+{
+    // Glitch filtering
+    bool rst1 = SCSI_IN(RST);
+    delay_ns(500);
+    bool rst2 = SCSI_IN(RST);
+
+    if (rst1 && rst2)
+    {
+        azdbg("BUS RESET");
+        scsiDev.resetFlag = 1;
+    }
+}
+
+// This function is called to initialize the phy code.
+// It is called after power-on and after SCSI bus reset.
+extern "C" void scsiPhyReset(void)
+{
+    SCSI_RELEASE_OUTPUTS();
+    g_scsi_sts_selection = 0;
+    g_scsi_ctrl_bsy = 0;
+
+    /* Implement here code to enable two interrupts:
+     * scsi_bsy_deassert_interrupt() on rising edge of BSY pin
+     * scsi_rst_assert_interrupt() on falling edge of RST pin
+     */
+}
+
+/************************/
+/* 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)
+    {
+        int oldphase = g_scsi_phase;
+        g_scsi_phase = (SCSI_PHASE)phase;
+        scsiLogPhaseChange(phase);
+
+        if (phase < 0)
+        {
+            // Other communication on bus or reset state
+            SCSI_RELEASE_OUTPUTS();
+            return 0;
+        }
+        else
+        {
+            SCSI_OUT(MSG, phase & __scsiphase_msg);
+            SCSI_OUT(CD,  phase & __scsiphase_cd);
+            SCSI_OUT(IO,  phase & __scsiphase_io);
+
+            int delayNs = 400; // Bus settle delay
+            if ((oldphase & __scsiphase_io) != (phase & __scsiphase_io))
+            {
+                delayNs += 400; // Data release delay
+            }
+
+            if (scsiDev.compatMode < COMPAT_SCSI2)
+            {
+                // EMU EMAX needs 100uS ! 10uS is not enough.
+                delayNs += 100000;
+            }
+
+            return delayNs;
+        }
+    }
+    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); \
+    } \
+  }
+
+// Write one byte to SCSI host using the handshake mechanism
+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();
+    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)
+{
+    scsiLogDataIn(data, count);
+    for (uint32_t i = 0; i < count; i++)
+    {
+        if (scsiDev.resetFlag) break;
+        scsiWriteOneByte(data[i]);
+    }
+}
+
+extern "C" void scsiStartWrite(const uint8_t* data, uint32_t count)
+{
+    // If the platform supports DMA for either SD card access or for SCSI bus,
+    // this function can be used to execute SD card transfers in parallel with
+    // SCSI transfers. This usually doubles the transfer speed.
+    //
+    // For simplicity, this example only implements blocking writes.
+    scsiWrite(data, count);
+}
+
+extern "C" bool scsiIsWriteFinished(const uint8_t *data)
+{
+    // Asynchronous writes are not implemented in this example.
+    return true;
+}
+
+extern "C" void scsiFinishWrite()
+{
+    // Asynchronous writes are not implemented in this example.
+}
+
+/*********************/
+/* Receive from host */
+/*********************/
+
+// Read one byte from SCSI host using the handshake mechanism.
+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);
+}

+ 67 - 0
lib/AzulSCSI_platform_template/scsiPhy.h

@@ -0,0 +1,67 @@
+// 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);
+
+// Blocking data transfer
+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);
+
+// Non-blocking data transfer.
+// Depending on platform support the start() function may block.
+// The start function can be called multiple times, it may internally
+// either combine transfers or block until previous transfer completes.
+void scsiStartWrite(const uint8_t* data, uint32_t count);
+void scsiFinishWrite();
+
+// Query whether the data at pointer has already been read, i.e. buffer can be reused.
+// If data is NULL, checks if all writes have completed.
+bool scsiIsWriteFinished(const uint8_t *data);
+
+
+#define s2s_getScsiRateKBs() 0
+
+#ifdef __cplusplus
+}
+#endif

+ 13 - 0
lib/AzulSCSI_platform_template/time.h

@@ -0,0 +1,13 @@
+// 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)

+ 22 - 0
platformio.ini

@@ -1,5 +1,26 @@
 ; PlatformIO Project Configuration File https://docs.platformio.org/page/projectconf.html
 
+[platformio]
+default_envs = AzulSCSIv1_0, AzulSCSIv1_1
+
+; Example platform to serve as a base for porting efforts
+[env:template]
+platform = ststm32
+framework = arduino
+board = bluepill_f103c8
+build_flags =
+    -Os -Isrc
+    -DLOGBUFSIZE=512
+    -DPREFETCH_BUFFER_SIZE=0
+    -DMAX_SECTOR_SIZE=2048
+    -DSCSI2SD_BUFFER_SIZE=4096
+lib_deps =
+    SdFat=https://github.com/greiman/SdFat
+    minIni
+    AzulSCSI_platform_template
+    SCSI2SD
+
+; AzulSCSI V1.0 hardware platform with GD32F205 CPU.
 [env:AzulSCSIv1_0]
 platform = https://github.com/CommunityGD32Cores/platform-gd32.git
 board = genericGD32F205VC
@@ -27,6 +48,7 @@ build_flags =
      -DENABLE_DEDICATED_SPI=1
      -DAZULSCSI_V1_0
 
+; AzulSCSI V1.1 hardware platform, similar to V1.0 but with improved performance.
 [env:AzulSCSIv1_1]
 extends = env:AzulSCSIv1_0
 build_flags =