Sfoglia il codice sorgente

Merge remote-tracking branch 'zuul/main'

Eric Helgeson 3 anni fa
parent
commit
f0d14ed4e7

+ 2 - 2
.github/workflows/firmware_build.yml

@@ -11,7 +11,7 @@ jobs:
 
     steps:
       - name: Check out code from GitHub
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
         with:
           path: BlueSCSI
           fetch-depth: "0"
@@ -31,7 +31,7 @@ jobs:
           utils/rename_binaries.sh
 
       - name: Upload binaries into build artifacts
-        uses: actions/upload-artifact@v2
+        uses: actions/upload-artifact@v3
         with:
           path: BlueSCSI/distrib/*
           name: BlueSCSI binaries

+ 4 - 0
bluescsi.ini

@@ -14,6 +14,10 @@ EnableSelLatch = 0 # For Philips P2000C and other devices that release SEL signa
 MapLunsToIDs = 0 # For Philips P2000C simulate multiple LUNs
 MaxSyncSpeed = 10 # Set to 5 or 10 to enable synchronous SCSI mode, 0 to disable
 
+# ROM settings
+#DisableROMDrive = 1 # Disable the ROM drive if it has been loaded to flash
+#ROMDriveSCSIID = 7 # Override ROM drive's SCSI ID
+
 # Settings that can be specified either per-device or for all devices.
 #Vendor = "QUANTUM"
 #Product = "FIREBALL1"

+ 79 - 1
lib/BlueSCSI_platform_RP2040/BlueSCSI_platform.cpp

@@ -15,9 +15,11 @@ extern "C" {
 // As of 2022-09-13, the platformio RP2040 core is missing cplusplus guard on flash.h
 // For that reason this has to be inside the extern "C" here.
 #include <hardware/flash.h>
+#include "rp2040_flash_do_cmd.h"
 
 const char *g_bluescsiplatform_name = PLATFORM_NAME;
 static bool g_scsi_initiator = false;
+static uint32_t g_flash_chip_size = 0;
 
 void mbed_error_hook(const mbed_error_ctx * error_context);
 
@@ -65,6 +67,8 @@ void bluescsiplatform_init()
     mbed_set_error_hook(mbed_error_hook);
 
     //bluelog("DIP switch settings: debug log ", (int)dbglog, ", termination ", (int)termination);
+    bluelog("Platform: ", g_bluescsiplatform_name);
+    bluelog("FW Version: ", g_bluelog_firmwareversion);
 
     g_bluelog_debug = false; // Debug logging can be handled with a debug firmware, very easy to reflash
 
@@ -77,6 +81,13 @@ void bluescsiplatform_init()
     //     bluelog("NOTE: SCSI termination is disabled");
     // }
 
+    // Get flash chip size
+    uint8_t cmd_read_jedec_id[4] = {0x9f, 0, 0, 0};
+    uint8_t response_jedec[4] = {0};
+    flash_do_cmd(cmd_read_jedec_id, response_jedec, 4);
+    g_flash_chip_size = (1 << response_jedec[3]);
+    bluelog("Flash chip size: ", (int)(g_flash_chip_size / 1024), " kB");
+
     // SD card pins
     // Card is used in SDIO mode for main program, and in SPI mode for crash handler & bootloader.
     //        pin             function       pup   pdown  out    state fast
@@ -144,7 +155,7 @@ void bluescsiplatform_late_init()
     else
     {
         g_scsi_initiator = false;
-        bluelog("SCSI target mode selected by DIP switch, acting as an SCSI disk");
+        bluelog("SCSI target/disk mode selected by DIP switch, acting as a SCSI disk");
     }
 
     /* Initialize SCSI pins to required modes.
@@ -461,6 +472,73 @@ const void * btldr_vectors[2] = {&__StackTop, (void*)&btldr_reset_handler};
 
 #endif
 
+/************************************/
+/* ROM drive in extra flash space   */
+/************************************/
+
+#ifdef PLATFORM_HAS_ROM_DRIVE
+
+// Reserve up to 352 kB for firmware.
+#define ROMDRIVE_OFFSET (352 * 1024)
+
+uint32_t azplatform_get_romdrive_maxsize()
+{
+    if (g_flash_chip_size >= ROMDRIVE_OFFSET)
+    {
+        return g_flash_chip_size - ROMDRIVE_OFFSET;
+    }
+    else
+    {
+        // Failed to read flash chip size, default to 2 MB
+        return 2048 * 1024 - ROMDRIVE_OFFSET;
+    }
+}
+
+bool azplatform_read_romdrive(uint8_t *dest, uint32_t start, uint32_t count)
+{
+    xip_ctrl_hw->stream_ctr = 0;
+
+    while (!(xip_ctrl_hw->stat & XIP_STAT_FIFO_EMPTY))
+    {
+        (void) xip_ctrl_hw->stream_fifo;
+    }
+
+    xip_ctrl_hw->stream_addr = start + ROMDRIVE_OFFSET;
+    xip_ctrl_hw->stream_ctr = count / 4;
+
+    // Transfer happens in multiples of 4 bytes
+    assert(start < azplatform_get_romdrive_maxsize());
+    assert((count & 3) == 0);
+    assert((((uint32_t)dest) & 3) == 0);
+
+    uint32_t *dest32 = (uint32_t*)dest;
+    uint32_t words_remain = count / 4;
+    while (words_remain > 0)
+    {
+        if (!(xip_ctrl_hw->stat & XIP_STAT_FIFO_EMPTY))
+        {
+            *dest32++ = xip_ctrl_hw->stream_fifo;
+            words_remain--;
+        }
+    }
+
+    return true;
+}
+
+bool azplatform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t count)
+{
+    assert(start < azplatform_get_romdrive_maxsize());
+    assert((count % AZPLATFORM_ROMDRIVE_PAGE_SIZE) == 0);
+
+    __disable_irq();
+    flash_range_erase(start + ROMDRIVE_OFFSET, count);
+    flash_range_program(start + ROMDRIVE_OFFSET, data, count);
+    __enable_irq();
+    return true;
+}
+
+#endif
+
 /**********************************************/
 /* Mapping from data bytes to GPIO BOP values */
 /**********************************************/

+ 14 - 0
lib/BlueSCSI_platform_RP2040/BlueSCSI_platform.h

@@ -74,6 +74,20 @@ bool bluescsiplatform_rewrite_flash_page(uint32_t offset, uint8_t buffer[BLUESCS
 void bluescsiplatform_boot_to_main_firmware();
 #endif
 
+// ROM drive in the unused external flash area
+#ifndef RP2040_DISABLE_ROMDRIVE
+#define PLATFORM_HAS_ROM_DRIVE 1
+// Check maximum available space for ROM drive in bytes
+uint32_t azplatform_get_romdrive_maxsize();
+
+// Read ROM drive area
+bool azplatform_read_romdrive(uint8_t *dest, uint32_t start, uint32_t count);
+
+// Reprogram ROM drive area
+#define AZPLATFORM_ROMDRIVE_PAGE_SIZE 4096
+bool azplatform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t count);
+#endif
+
 // Below are GPIO access definitions that are used from scsiPhy.cpp.
 
 // Write a single SCSI pin.

+ 1 - 1
lib/BlueSCSI_platform_RP2040/rp2040.ld

@@ -1,6 +1,6 @@
 MEMORY
 {
-    FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k
+    FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 352k
     RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 240k  /* Leave space for pico-debug */
     SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k
     SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k

+ 78 - 0
lib/BlueSCSI_platform_RP2040/rp2040_flash_do_cmd.h

@@ -0,0 +1,78 @@
+// As of 2022-11-16, the raspberrypi platformio package ships with old version
+// of pico-sdk. This version lacks the flash_do_cmd() function in flash.h header.
+// This file backports the function from new versions.
+
+#pragma once
+
+#ifndef RP2040_HAS_FLASH_DO_CMD
+
+#include <hardware/flash.h>
+#include "pico/bootrom.h"
+#include "hardware/structs/ssi.h"
+#include "hardware/structs/ioqspi.h"
+
+#define BOOT2_SIZE_WORDS 64
+
+static uint32_t boot2_copyout[BOOT2_SIZE_WORDS];
+static bool boot2_copyout_valid = false;
+
+static void __no_inline_not_in_flash_func(flash_init_boot2_copyout)(void) {
+    if (boot2_copyout_valid)
+        return;
+    for (int i = 0; i < BOOT2_SIZE_WORDS; ++i)
+        boot2_copyout[i] = ((uint32_t *)XIP_BASE)[i];
+    __asm__ volatile ("" : : : "memory");
+    boot2_copyout_valid = true;
+}
+
+static void __no_inline_not_in_flash_func(flash_enable_xip_via_boot2)(void) {
+    ((void (*)(void))((char*)boot2_copyout+1))();
+}
+
+// Bitbanging the chip select using IO overrides, in case RAM-resident IRQs
+// are still running, and the FIFO bottoms out. (the bootrom does the same)
+static void __no_inline_not_in_flash_func(flash_cs_force)(bool high) {
+    uint32_t field_val = high ?
+        IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH :
+        IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW;
+    hw_write_masked(&ioqspi_hw->io[1].ctrl,
+        field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB,
+        IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS
+    );
+}
+
+static void __no_inline_not_in_flash_func(flash_do_cmd)(const uint8_t *txbuf, uint8_t *rxbuf, size_t count) {
+    void (*connect_internal_flash)(void) = (void(*)(void))rom_func_lookup(rom_table_code('I', 'F'));
+    void (*flash_exit_xip)(void) = (void(*)(void))rom_func_lookup(rom_table_code('E', 'X'));
+    void (*flash_flush_cache)(void) = (void(*)(void))rom_func_lookup(rom_table_code('F', 'C'));
+    assert(connect_internal_flash && flash_exit_xip && flash_flush_cache);
+    flash_init_boot2_copyout();
+    __asm__ volatile ("" : : : "memory");
+    connect_internal_flash();
+    flash_exit_xip();
+
+    flash_cs_force(0);
+    size_t tx_remaining = count;
+    size_t rx_remaining = count;
+    // We may be interrupted -- don't want FIFO to overflow if we're distracted.
+    const size_t max_in_flight = 16 - 2;
+    while (tx_remaining || rx_remaining) {
+        uint32_t flags = ssi_hw->sr;
+        bool can_put = !!(flags & SSI_SR_TFNF_BITS);
+        bool can_get = !!(flags & SSI_SR_RFNE_BITS);
+        if (can_put && tx_remaining && rx_remaining - tx_remaining < max_in_flight) {
+            ssi_hw->dr0 = *txbuf++;
+            --tx_remaining;
+        }
+        if (can_get && rx_remaining) {
+            *rxbuf++ = (uint8_t)ssi_hw->dr0;
+            --rx_remaining;
+        }
+    }
+    flash_cs_force(1);
+
+    flash_flush_cache();
+    flash_enable_xip_via_boot2();
+}
+
+#endif

+ 7 - 4
lib/BlueSCSI_platform_RP2040/rp2040_sdio.cpp

@@ -181,10 +181,13 @@ sdio_status_t rp2040_sdio_command_R1(uint8_t command, uint32_t arg, uint32_t *re
     {
         if ((uint32_t)(millis() - start) > 2)
         {
-            bluedbg("Timeout waiting for response in rp2040_sdio_command_R1(", (int)command, "), ",
-                  "PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_CMD_SM) - (int)g_sdio.pio_cmd_clk_offset,
-                  " RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM),
-                  " TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_CMD_SM));
+            if (command != 8) // Don't log for missing SD card
+            {
+                bluedbg("Timeout waiting for response in rp2040_sdio_command_R1(", (int)command, "), ",
+                    "PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_CMD_SM) - (int)g_sdio.pio_cmd_clk_offset,
+                    " RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM),
+                    " TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_CMD_SM));
+            }
 
             // Reset the state machine program
             pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM);

+ 13 - 1
lib/BlueSCSI_platform_RP2040/scsiPhy.cpp

@@ -7,6 +7,7 @@
 #include "BlueSCSI_log_trace.h"
 #include "BlueSCSI_config.h"
 #include "scsi_accel_rp2040.h"
+#include "hardware/structs/iobank0.h"
 
 #include <scsi2sd.h>
 extern "C" {
@@ -255,6 +256,15 @@ void scsiEnterBusFree(void)
     } \
   }
 
+// In synchronous mode the ACK pulse can be very short, so use edge IRQ to detect it.
+#define CHECK_EDGE(pin) \
+    ((iobank0_hw->intr[pin / 8] >> (4 * (pin % 8))) & GPIO_IRQ_EDGE_FALL)
+
+#define SCSI_WAIT_ACTIVE_EDGE(pin) \
+  if (!CHECK_EDGE(SCSI_IN_ ## pin)) { \
+    while(!SCSI_IN(pin) && !CHECK_EDGE(SCSI_IN_ ## pin) && !scsiDev.resetFlag); \
+  }
+
 #define SCSI_WAIT_INACTIVE(pin) \
   if (SCSI_IN(pin)) { \
     if (SCSI_IN(pin)) { \
@@ -263,12 +273,14 @@ void scsiEnterBusFree(void)
   }
 
 // Write one byte to SCSI host using the handshake mechanism
+// This is suitable for both asynchronous and synchronous communication.
 static inline void scsiWriteOneByte(uint8_t value)
 {
     SCSI_OUT_DATA(value);
     delay_100ns(); // DB setup time before REQ
+    gpio_acknowledge_irq(SCSI_IN_ACK, GPIO_IRQ_EDGE_FALL);
     SCSI_OUT(REQ, 1);
-    SCSI_WAIT_ACTIVE(ACK);
+    SCSI_WAIT_ACTIVE_EDGE(ACK);
     SCSI_RELEASE_DATA_REQ();
     SCSI_WAIT_INACTIVE(ACK);
 }

+ 1 - 1
lib/BlueSCSI_platform_RP2040/sd_card_sdio.cpp

@@ -87,7 +87,7 @@ bool SdioCard::begin(SdioConfig sdioConfig)
 
     if (reply != 0x1AA || status != SDIO_OK)
     {
-        bluedbg("SDIO not responding to CMD8 SEND_IF_COND, status ", (int)status, " reply ", reply);
+        // bluedbg("SDIO not responding to CMD8 SEND_IF_COND, status ", (int)status, " reply ", reply);
         return false;
     }
 

+ 1 - 0
platformio.ini

@@ -25,3 +25,4 @@ build_flags =
     -DENABLE_DEDICATED_SPI=1
     -DHAS_SDIO_CLASS
     -DUSE_ARDUINO=1
+    -DZULUSCSI_V2_0

+ 110 - 39
src/BlueSCSI.cpp

@@ -29,6 +29,8 @@
 
 SdFs SD;
 FsFile g_logfile;
+static bool g_romdrive_active;
+static bool g_sdcard_present;
 
 /************************************/
 /* Status reporting by blinking led */
@@ -72,7 +74,7 @@ void save_logfile(bool always = false)
   static uint32_t prev_log_save = 0;
   uint32_t loglen = bluelog_get_buffer_len();
 
-  if (loglen != prev_log_len)
+  if (loglen != prev_log_len && g_sdcard_present)
   {
     // When debug is off, save log at most every LOG_SAVE_INTERVAL_MS
     // When debug is on, save after every SCSI command.
@@ -199,8 +201,8 @@ bool findHDDImages()
         {
           const char *archive_exts[] = {
             ".tar", ".tgz", ".gz", ".bz2", ".tbz2", ".xz", ".zst", ".z",
-            ".zip", ".zipx", ".rar", ".lzh", ".7z", ".s7z", ".arj",
-            ".dmg",
+            ".zip", ".zipx", ".rar", ".lzh", ".lha", ".lzo", ".lz4", ".arj",
+            ".dmg", ".hqx", ".cpt", ".7z", ".s7z",
             NULL
           };
 
@@ -220,6 +222,18 @@ bool findHDDImages()
           continue;
         }
 
+        // Check if the image should be loaded to microcontroller flash ROM drive
+        bool is_romdrive = false;
+        if (extension && strcasecmp(extension, ".rom") == 0)
+        {
+          is_romdrive = true;
+        }
+        else if (extension && strcasecmp(extension, ".rom_loaded") == 0)
+        {
+          // Already loaded ROM drive, ignore the image
+          continue;
+        }
+
         // Defaults for Hard Disks
         int id  = 1; // 0 and 3 are common in Macs for physical HD and CD, so avoid them.
         int lun = 0;
@@ -274,26 +288,35 @@ bool findHDDImages()
         strcat(fullname, name);
 
         // Check whether this SCSI ID has been configured yet
-        const S2S_TargetCfg* cfg = s2s_getConfigByIndex(id);
-        if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))
+        if (s2s_getConfigById(id))
         {
           bluelog("-- Ignoring ", fullname, ", SCSI ID ", id, " is already in use!");
           continue;
         }
 
+        // Type mapping based on filename.
+        // If type is FIXED, the type can still be overridden in .ini file.
+        S2S_CFG_TYPE type = S2S_CFG_FIXED;
+        if (is_cd) type = S2S_CFG_OPTICAL;
+        if (is_fd) type = S2S_CFG_FLOPPY_14MB;
+        if (is_mo) type = S2S_CFG_MO;
+        if (is_re) type = S2S_CFG_REMOVEABLE;
+        if (is_tp) type = S2S_CFG_SEQUENTIAL;
+
         // Open the image file
-        if(id < NUM_SCSIID && lun < NUM_SCSILUN) {
+        if (id < NUM_SCSIID && is_romdrive)
+        {
+          bluelog("-- Loading ROM drive from ", fullname, " for id:", id);
+          imageReady = scsiDiskProgramRomDrive(fullname, id, blk, type);
+          
+          if (imageReady)
+          {
+            foundImage = true;
+          }
+        }
+        else if(id < NUM_SCSIID && lun < NUM_SCSILUN) {
           bluelog("-- Opening ", fullname, " for id:", id, " lun:", lun);
 
-          // Type mapping based on filename.
-          // If type is FIXED, the type can still be overridden in .ini file.
-          S2S_CFG_TYPE type = S2S_CFG_FIXED;
-          if (is_cd) type = S2S_CFG_OPTICAL;
-          if (is_fd) type = S2S_CFG_FLOPPY_14MB;
-          if (is_mo) type = S2S_CFG_MO;
-          if (is_re) type = S2S_CFG_REMOVEABLE;
-          if (is_tp) type = S2S_CFG_SEQUENTIAL;
-
           imageReady = scsiDiskOpenHDDImage(id, fullname, id, lun, blk, type);
           if(imageReady)
           {
@@ -315,6 +338,8 @@ bool findHDDImages()
   }
   root.close();
 
+  g_romdrive_active = scsiDiskActivateRomDrive();
+
   // Print SCSI drive map
   for (int i = 0; i < NUM_SCSIID; i++)
   {
@@ -422,37 +447,62 @@ static void reinitSCSI()
 
 extern "C" void bluescsi_setup(void)
 {
+// The RP2040 must log FW Version in azplatform_init to avoid hanging  
+#ifndef BLUESCSI_V2_0  
+  bluelog("Platform: ", g_bluescsiplatform_name);
+  bluelog("FW Version: ", g_bluelog_firmwareversion);
+#endif
+
   bluescsiplatform_init();
   bluescsiplatform_late_init();
 
-  if(!mountSDCard())
+  g_sdcard_present = mountSDCard();
+
+  if(!g_sdcard_present)
   {
     bluelog("SD card init failed, sdErrorCode: ", (int)SD.sdErrorCode(),
            " sdErrorData: ", (int)SD.sdErrorData());
+    
+    blinkStatus(BLINK_ERROR_NO_SD_CARD);
+
+    if (scsiDiskCheckRomDrive())
+    {
+      reinitSCSI();
+      if (g_romdrive_active)
+      {
+        bluelog("Enabled ROM drive without SD card");
+        return;
+      }
+    }
 
     do
     {
       blinkStatus(BLINK_ERROR_NO_SD_CARD);
       delay(1000);
       bluescsiplatform_reset_watchdog();
-    } while (!mountSDCard());
+      g_sdcard_present = mountSDCard();
+    } while (!g_sdcard_present);
     bluelog("SD card init succeeded after retry");
   }
 
-  if (SD.clusterCount() == 0)
+  if (g_sdcard_present)
   {
-    bluelog("SD card without filesystem!");
-  }
-
-  print_sd_info();
+    if (SD.clusterCount() == 0)
+    {
+      bluelog("SD card without filesystem!");
+    }
 
-  reinitSCSI();
+    print_sd_info();
+  
+    reinitSCSI();
+  }
 
   bluelog("Initialization complete!");
-  bluelog("Platform: ", g_bluescsiplatform_name);
-  bluelog("FW Version: ", g_bluelog_firmwareversion);
 
-  init_logfile();
+  if (g_sdcard_present)
+  {
+    init_logfile();
+  }
 }
 
 extern "C" void bluescsi_main_loop(void)
@@ -481,29 +531,50 @@ extern "C" void bluescsi_main_loop(void)
     }
   }
 
-  // Check SD card status for hotplug
-  if (scsiDev.phase == BUS_FREE &&
-      (uint32_t)(millis() - sd_card_check_time) > 5000)
+  if (g_sdcard_present)
   {
-    sd_card_check_time = millis();
-    uint32_t ocr;
-    if (!SD.card()->readOCR(&ocr))
+    // Check SD card status for hotplug
+    if (scsiDev.phase == BUS_FREE &&
+        (uint32_t)(millis() - sd_card_check_time) > 5000)
     {
+      sd_card_check_time = millis();
+      uint32_t ocr;
       if (!SD.card()->readOCR(&ocr))
       {
-        bluelog("SD card removed, trying to reinit");
-        do
+        if (!SD.card()->readOCR(&ocr))
         {
-          blinkStatus(BLINK_ERROR_NO_SD_CARD);
-          delay(1000);
-          bluescsiplatform_reset_watchdog();
-        } while (!mountSDCard());
+          g_sdcard_present = false;
+          bluelog("SD card removed, trying to reinit");
+        }
+      }
+    }
+  }
+
+  if (!g_sdcard_present)
+  {
+    // Try to remount SD card
+    do 
+    {
+      g_sdcard_present = mountSDCard();
+
+      if (g_sdcard_present)
+      {
         bluelog("SD card reinit succeeded");
         print_sd_info();
 
         reinitSCSI();
         init_logfile();
       }
-    }
+      else if (!g_romdrive_active)
+      {
+        blinkStatus(BLINK_ERROR_NO_SD_CARD);
+        delay(1000);
+        bluescsiplatform_reset_watchdog();
+      }
+    } while (!g_sdcard_present && !g_romdrive_active);
+  }
+  else
+  {
+    
   }
 }

+ 1 - 1
src/BlueSCSI_config.h

@@ -6,7 +6,7 @@
 #include <BlueSCSI_platform.h>
 
 // Use variables for version number
-#define FW_VER_NUM      "1.1.3"
+#define FW_VER_NUM      "1.1.4"
 #define FW_VER_SUFFIX   "devel"
 #define BLUESCSI_FW_VERSION FW_VER_NUM "-" FW_VER_SUFFIX
 

+ 276 - 10
src/BlueSCSI_disk.cpp

@@ -18,6 +18,7 @@
 extern "C" {
 #include <scsi2sd_time.h>
 #include <sd.h>
+#include <mode.h>
 }
 
 #ifndef PLATFORM_MAX_SCSI_SPEED
@@ -43,9 +44,176 @@ extern "C" {
 #define PLATFORM_OPTIMAL_LAST_SD_WRITE_SIZE 512
 #endif
 
+#ifndef PLATFORM_HAS_ROM_DRIVE
+// Dummy defines for platforms without ROM drive support
+#define AZPLATFORM_ROMDRIVE_PAGE_SIZE 1024
+uint32_t azplatform_get_romdrive_maxsize() { return 0; }
+bool azplatform_read_romdrive(uint8_t *dest, uint32_t start, uint32_t count) { return false; }
+bool azplatform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t count) { return false; }
+#endif
+
 // SD card sector size is always 512 bytes
 #define SD_SECTOR_SIZE 512
 
+/************************************************/
+/* ROM drive support (in microcontroller flash) */
+/************************************************/
+
+struct romdrive_hdr_t {
+    char magic[8]; // "ROMDRIVE"
+    int scsi_id;
+    uint32_t imagesize;
+    uint32_t blocksize;
+    S2S_CFG_TYPE drivetype;
+    uint32_t reserved[32];
+};
+
+// Check if the romdrive is present
+static bool check_romdrive(romdrive_hdr_t *hdr)
+{
+    if (!azplatform_read_romdrive((uint8_t*)hdr, 0, sizeof(romdrive_hdr_t)))
+    {
+        return false;
+    }
+
+    if (memcmp(hdr->magic, "ROMDRIVE", 8) != 0)
+    {
+        return false;
+    }
+
+    if (hdr->imagesize <= 0 || hdr->scsi_id < 0 || hdr->scsi_id > 8)
+    {
+        return false;
+    }
+
+    return true;
+}
+
+// Load an image file to romdrive
+bool scsiDiskProgramRomDrive(const char *filename, int scsi_id, int blocksize, S2S_CFG_TYPE type)
+{
+    FsFile file = SD.open(filename, O_RDONLY);
+    if (!file.isOpen())
+    {
+        bluelog("---- Failed to open: ", filename);
+        return false;
+    }
+
+    uint64_t filesize = file.size();
+    uint32_t maxsize = azplatform_get_romdrive_maxsize() - AZPLATFORM_ROMDRIVE_PAGE_SIZE;
+
+    bluelog("---- SCSI ID: ", scsi_id, " blocksize ", blocksize, " type ", (int)type);
+    bluelog("---- ROM drive maximum size is ", (int)maxsize,
+          " bytes, image file is ", (int)filesize, " bytes");
+    
+    if (filesize > maxsize)
+    {
+        bluelog("---- Image size exceeds ROM space, not loading");
+        file.close();
+        return false;
+    }
+
+    romdrive_hdr_t hdr = {};
+    memcpy(hdr.magic, "ROMDRIVE", 8);
+    hdr.scsi_id = scsi_id;
+    hdr.imagesize = filesize;
+    hdr.blocksize = blocksize;
+    hdr.drivetype = type;
+
+    // Program the drive metadata header
+    if (!azplatform_write_romdrive((const uint8_t*)&hdr, 0, AZPLATFORM_ROMDRIVE_PAGE_SIZE))
+    {
+        bluelog("---- Failed to program ROM drive header");
+        file.close();
+        return false;
+    }
+    
+    // Program the drive contents
+    uint32_t pages = (filesize + AZPLATFORM_ROMDRIVE_PAGE_SIZE - 1) / AZPLATFORM_ROMDRIVE_PAGE_SIZE;
+    for (uint32_t i = 0; i < pages; i++)
+    {
+        if (i % 2)
+            LED_ON();
+        else
+            LED_OFF();
+
+        if (file.read(scsiDev.data, AZPLATFORM_ROMDRIVE_PAGE_SIZE) <= 0 ||
+            !azplatform_write_romdrive(scsiDev.data, (i + 1) * AZPLATFORM_ROMDRIVE_PAGE_SIZE, AZPLATFORM_ROMDRIVE_PAGE_SIZE))
+        {
+            bluelog("---- Failed to program ROM drive page ", (int)i);
+            file.close();
+            return false;
+        }
+    }
+
+    LED_OFF();
+
+    file.close();
+
+    char newname[MAX_FILE_PATH * 2] = "";
+    strlcat(newname, filename, sizeof(newname));
+    strlcat(newname, "_loaded", sizeof(newname));
+    SD.rename(filename, newname);
+    bluelog("---- ROM drive programming successful, image file renamed to ", newname);
+
+    return true;
+}
+
+bool scsiDiskCheckRomDrive()
+{
+    romdrive_hdr_t hdr = {};
+    return check_romdrive(&hdr);
+}
+
+// Check if rom drive exists and activate it
+bool scsiDiskActivateRomDrive()
+{
+    uint32_t maxsize = azplatform_get_romdrive_maxsize() - AZPLATFORM_ROMDRIVE_PAGE_SIZE;
+    bluelog("-- Platform supports ROM drive up to ", (int)(maxsize / 1024), " kB");
+
+    romdrive_hdr_t hdr = {};
+    if (!check_romdrive(&hdr))
+    {
+        bluelog("---- ROM drive image not detected");
+        return false;
+    }
+
+    if (ini_getbool("SCSI", "DisableROMDrive", 0, CONFIGFILE))
+    {
+        bluelog("---- ROM drive disabled in ini file, not enabling");
+        return false;
+    }
+
+    long rom_scsi_id = ini_getl("SCSI", "ROMDriveSCSIID", -1, CONFIGFILE);
+    if (rom_scsi_id >= 0 && rom_scsi_id <= 7)
+    {
+        hdr.scsi_id = rom_scsi_id;
+        bluelog("---- ROM drive SCSI id overriden in ini file, changed to ", (int)hdr.scsi_id);
+    }
+
+    if (s2s_getConfigById(hdr.scsi_id))
+    {
+        bluelog("---- ROM drive SCSI id ", (int)hdr.scsi_id, " is already in use, not enabling");
+        return false;
+    }
+
+
+
+    bluelog("---- Activating ROM drive, SCSI id ", (int)hdr.scsi_id, " size ", (int)(hdr.imagesize / 1024), " kB");
+    bool status = scsiDiskOpenHDDImage(hdr.scsi_id, "ROM:", hdr.scsi_id, 0, hdr.blocksize, hdr.drivetype);
+
+    if (!status)
+    {
+        bluelog("---- ROM drive activation failed");
+        return false;
+    }
+    else
+    {
+        return true;
+    }
+}
+
+
 /***********************/
 /* Backing image files */
 /***********************/
@@ -58,17 +226,21 @@ SdDevice sdDev = {2, 256 * 1024 * 1024 * 2}; /* For SCSI2SD */
 //
 // Raw access is activated by using filename like "RAW:0:12345"
 // where the numbers are the first and last sector.
+//
+// If the platform supports a ROM drive, it is activated by using
+// filename "ROM:".
 class ImageBackingStore
 {
 public:
     ImageBackingStore()
     {
         m_israw = false;
+        m_isrom = false;
         m_blockdev = nullptr;
         m_bgnsector = m_endsector = m_cursector = 0;
     }
 
-    ImageBackingStore(const char *filename): ImageBackingStore()
+    ImageBackingStore(const char *filename, uint32_t scsi_block_size): ImageBackingStore()
     {
         if (strncasecmp(filename, "RAW:", 4) == 0)
         {
@@ -82,6 +254,12 @@ public:
                 return;
             }
 
+            if ((scsi_block_size % SD_SECTOR_SIZE) != 0)
+            {
+                bluelog("SCSI block size ", (int)scsi_block_size, " is not supported for RAW partitions (must be divisible by 512 bytes)");
+                return;
+            }
+
             m_israw = true;
             m_blockdev = SD.card();
 
@@ -92,13 +270,25 @@ public:
                 m_endsector = sectorCount - 1;
             }
         }
+        else if (strncasecmp(filename, "ROM:", 4) == 0)
+        {
+            if (!check_romdrive(&m_romhdr))
+            {
+                m_romhdr.imagesize = 0;
+            }
+            else
+            {
+                m_isrom = true;
+            }
+        }
         else
         {
             m_fsfile = SD.open(filename, O_RDWR);
 
             uint32_t sectorcount = m_fsfile.size() / SD_SECTOR_SIZE;
             uint32_t begin = 0, end = 0;
-            if (m_fsfile.contiguousRange(&begin, &end) && end >= begin + sectorcount)
+            if (m_fsfile.contiguousRange(&begin, &end) && end >= begin + sectorcount
+                && (scsi_block_size % SD_SECTOR_SIZE) == 0)
             {
                 // Convert to raw mapping, this avoids some unnecessary
                 // access overhead in SdFat library.
@@ -126,7 +316,26 @@ public:
         }
     }
 
-    bool isOpen() { return m_israw ? !!m_blockdev : m_fsfile.isOpen(); }
+    bool isWritable()
+    {
+        return !m_isrom;
+    }
+
+    bool isRom()
+    {
+        return m_isrom;
+    }
+
+    bool isOpen()
+    {
+        if (m_israw)
+            return (m_blockdev != NULL);
+        else if (m_isrom)
+            return (m_romhdr.imagesize > 0);
+        else
+            return m_fsfile.isOpen();
+    }
+
     bool close()
     {
         if (m_israw)
@@ -134,6 +343,11 @@ public:
             m_blockdev = nullptr;
             return true;
         }
+        else if (m_isrom)
+        {
+            m_romhdr.imagesize = 0;
+            return true;
+        }
         else
         {
             return m_fsfile.close();
@@ -146,6 +360,10 @@ public:
         {
             return (uint64_t)(m_endsector - m_bgnsector + 1) * SD_SECTOR_SIZE;
         }
+        else if (m_isrom)
+        {
+            return m_romhdr.imagesize;
+        }
         else
         {
             return m_fsfile.size();
@@ -160,6 +378,12 @@ public:
             *endSector = m_endsector;
             return true;
         }
+        else if (m_isrom)
+        {
+            *bgnSector = 0;
+            *endSector = 0;
+            return true;
+        }
         else
         {
             return m_fsfile.contiguousRange(bgnSector, endSector);
@@ -168,13 +392,20 @@ public:
 
     bool seek(uint64_t pos)
     {
-        if (m_israw && m_blockdev)
+        if (m_israw)
         {
             uint32_t sectornum = pos / SD_SECTOR_SIZE;
             assert((uint64_t)sectornum * SD_SECTOR_SIZE == pos);
             m_cursector = m_bgnsector + sectornum;
             return (m_cursector <= m_endsector);
         }
+        else if (m_isrom)
+        {
+            uint32_t sectornum = pos / SD_SECTOR_SIZE;
+            assert((uint64_t)sectornum * SD_SECTOR_SIZE == pos);
+            m_cursector = sectornum;
+            return m_cursector * SD_SECTOR_SIZE < m_romhdr.imagesize;
+        }
         else
         {
             return m_fsfile.seek(pos);
@@ -197,6 +428,21 @@ public:
                 return -1;
             }
         }
+        else if (m_isrom)
+        {
+            uint32_t sectorcount = count / SD_SECTOR_SIZE;
+            assert((uint64_t)sectorcount * SD_SECTOR_SIZE == count);
+            uint32_t start = m_cursector * SD_SECTOR_SIZE + AZPLATFORM_ROMDRIVE_PAGE_SIZE;
+            if (azplatform_read_romdrive((uint8_t*)buf, start, count))
+            {
+                m_cursector += sectorcount;
+                return count;
+            }
+            else
+            {
+                return -1;
+            }
+        }
         else
         {
             return m_fsfile.read(buf, count);
@@ -219,6 +465,11 @@ public:
                 return 0;
             }
         }
+        else if (m_isrom)
+        {
+            bluelog("ERROR: attempted to write to ROM drive");
+            return 0;
+        }
         else
         {
             return m_fsfile.write(buf, count);
@@ -227,7 +478,7 @@ public:
 
     void flush()
     {
-        if (!m_israw)
+        if (!m_israw && !m_isrom)
         {
             m_fsfile.flush();
         }
@@ -235,6 +486,8 @@ public:
 
 private:
     bool m_israw;
+    bool m_isrom;
+    romdrive_hdr_t m_romhdr;
     FsFile m_fsfile;
     SdCard *m_blockdev;
     uint32_t m_bgnsector;
@@ -422,7 +675,7 @@ static void setDefaultDriveInfo(int target_idx)
 bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int scsi_lun, int blocksize, S2S_CFG_TYPE type)
 {
     image_config_t &img = g_DiskImages[target_idx];
-    img.file = ImageBackingStore(filename);
+    img.file = ImageBackingStore(filename, blocksize);
 
     if (img.file.isOpen())
     {
@@ -439,7 +692,11 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int
         }
 
         uint32_t sector_begin = 0, sector_end = 0;
-        if (img.file.contiguousRange(&sector_begin, &sector_end))
+        if (img.file.isRom())
+        {
+            // ROM is always contiguous, no need to log
+        }
+        else if (img.file.contiguousRange(&sector_begin, &sector_end))
         {
             bluelog("---- Image file is contiguous, SD card sectors ", (int)sector_begin, " to ", (int)sector_end);
         }
@@ -716,7 +973,8 @@ const S2S_TargetCfg* s2s_getConfigById(int scsiId)
     for (i = 0; i < S2S_MAX_TARGETS; ++i)
     {
         const S2S_TargetCfg* tgt = s2s_getConfigByIndex(i);
-        if ((tgt->scsiId & S2S_CFG_TARGET_ID_BITS) == scsiId)
+        if ((tgt->scsiId & S2S_CFG_TARGET_ID_BITS) == scsiId &&
+            (tgt->scsiId & S2S_CFG_TARGET_ENABLED))
         {
             return tgt;
         }
@@ -1044,10 +1302,11 @@ static void doWrite(uint32_t lba, uint32_t blocks)
     bluedbg("------ Write ", (int)blocks, "x", (int)bytesPerSector, " starting at ", (int)lba);
 
     if (unlikely(blockDev.state & DISK_WP) ||
-        unlikely(scsiDev.target->cfg->deviceType == S2S_CFG_OPTICAL))
+        unlikely(scsiDev.target->cfg->deviceType == S2S_CFG_OPTICAL) ||
+        unlikely(!img.file.isWritable()))
 
     {
-        bluelog("WARNING: Host attempted write to CD-ROM");
+        bluelog("WARNING: Host attempted write to read-only drive ID ", (int)(img.scsiId & S2S_CFG_TARGET_ID_BITS));
         scsiDev.status = CHECK_CONDITION;
         scsiDev.target->sense.code = ILLEGAL_REQUEST;
         scsiDev.target->sense.asc = WRITE_PROTECTED;
@@ -1672,6 +1931,13 @@ int scsiDiskCommand()
 
         scsiDev.phase = DATA_IN;
     }
+    else if (img.file.isRom())
+    {
+        // Special handling for ROM drive to make SCSI2SD code report it as read-only
+        blockDev.state |= DISK_WP;
+        commandHandled = scsiModeCommand();
+        blockDev.state &= ~DISK_WP;
+    }
     else
     {
         commandHandled = 0;

+ 7 - 0
src/BlueSCSI_disk.h

@@ -17,5 +17,12 @@ void scsiDiskResetImages();
 bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int scsi_lun, int blocksize, S2S_CFG_TYPE type = S2S_CFG_FIXED);
 void scsiDiskLoadConfig(int target_idx);
 
+// Program ROM drive and rename image file
+bool scsiDiskProgramRomDrive(const char *filename, int scsi_id, int blocksize, S2S_CFG_TYPE type);
+
+// Check if there is ROM drive configured in microcontroller flash
+bool scsiDiskCheckRomDrive();
+bool scsiDiskActivateRomDrive();
+
 // Returns true if there is at least one image active
 bool scsiDiskCheckAnyImagesConfigured();

+ 0 - 1
utils/run_gdb_rp2040.sh

@@ -1,6 +1,5 @@
 #!/bin/bash
 
-
 arm-none-eabi-gdb \
        -iex 'target extended /dev/ttyACM0' \
        -iex 'mon s' -iex 'att 1' \