Sfoglia il codice sorgente

Merge pull request #104 from ZuluSCSI/rp2040_rom_drive

RP2040 ROM drive support
Alex Perez 3 anni fa
parent
commit
a83f3cbdcb

+ 16 - 0
README.md

@@ -98,6 +98,22 @@ Any read errors are logged into `zululog.txt`.
 Depending on hardware setup, you may need to mount diode `D205` and jumper `JP201` to supply `TERMPWR` to the SCSI bus.
 This is necessary if the drives do not supply their own SCSI terminator power.
 
+ROM drive in microcontroller flash
+----------------------------------
+The RP2040 model supports storing up to 1660kB image as a read-only drive in the
+flash chip on the PCB itself. This can be used as e.g. a boot floppy that is available
+even without SD card.
+
+To initialize a ROM drive, name your image file as e.g. `HD0.rom`.
+The drive type, SCSI ID and blocksize can be set in the filename the same way as for normal images.
+On first boot, the LED will blink rapidly while the image is being loaded into flash memory.
+Once loading is complete, the file is renamed to `HD0.rom_loaded` and the data is accessed from flash instead.
+
+The status and maximum size of ROM drive are reported in `zululog.txt`.
+To disable a previously programmed ROM drive, create empty file called `HD0.rom`.
+If there is a `.bin` file with the same ID as the programmed ROM drive, it overrides the ROM drive.
+There can be at most one ROM drive enabled at a time.
+
 Project structure
 -----------------
 - **src/ZuluSCSI.cpp**: Main portable SCSI implementation.

+ 23 - 8
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_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_azplatform_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);
 
@@ -78,6 +80,13 @@ void azplatform_init()
         azlog("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]);
+    azlog("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
@@ -463,15 +472,20 @@ const void * btldr_vectors[2] = {&__StackTop, (void*)&btldr_reset_handler};
 
 #ifdef PLATFORM_HAS_ROM_DRIVE
 
-// Reserve up to 512 kB for firmware.
-#define ROMDRIVE_OFFSET (512 * 1024)
-
-// TODO: Actually read the flash chip size instead of assuming 2 MB.
-static uint32_t g_romdrive_max_size = 2048 * 1024 - ROMDRIVE_OFFSET;
+// Reserve up to 384 kB for firmware.
+#define ROMDRIVE_OFFSET (384 * 1024)
 
 uint32_t azplatform_get_romdrive_maxsize()
 {
-    return g_romdrive_max_size;
+    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)
@@ -487,7 +501,7 @@ bool azplatform_read_romdrive(uint8_t *dest, uint32_t start, uint32_t count)
     xip_ctrl_hw->stream_ctr = count / 4;
 
     // Transfer happens in multiples of 4 bytes
-    assert(start < g_romdrive_max_size);
+    assert(start < azplatform_get_romdrive_maxsize());
     assert((count & 3) == 0);
     assert((((uint32_t)dest) & 3) == 0);
 
@@ -507,8 +521,9 @@ bool azplatform_read_romdrive(uint8_t *dest, uint32_t start, uint32_t count)
 
 bool azplatform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t count)
 {
-    assert(start < g_romdrive_max_size);
+    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);

+ 1 - 1
lib/ZuluSCSI_platform_RP2040/rp2040.ld

@@ -1,6 +1,6 @@
 MEMORY
 {
-    FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 512k
+    FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 384k
     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/ZuluSCSI_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/ZuluSCSI_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)
         {
-            azdbg("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
+            {
+                azdbg("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);

+ 1 - 1
lib/ZuluSCSI_platform_RP2040/sd_card_sdio.cpp

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

+ 69 - 26
src/ZuluSCSI.cpp

@@ -54,6 +54,8 @@
 
 SdFs SD;
 FsFile g_logfile;
+static bool g_romdrive_active;
+static bool g_sdcard_present;
 
 /************************************/
 /* Status reporting by blinking led */
@@ -95,7 +97,7 @@ void save_logfile(bool always = false)
   static uint32_t prev_log_save = 0;
   uint32_t loglen = azlog_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.
@@ -314,8 +316,7 @@ 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))
         {
           azlog("-- Ignoring ", fullname, ", SCSI ID ", id, " is already in use!");
           continue;
@@ -365,7 +366,7 @@ bool findHDDImages()
   }
   root.close();
 
-  scsiDiskActivateRomDrive();
+  g_romdrive_active = scsiDiskActivateRomDrive();
 
   // Print SCSI drive map
   for (int i = 0; i < NUM_SCSIID; i++)
@@ -477,34 +478,55 @@ extern "C" void zuluscsi_setup(void)
   azplatform_init();
   azplatform_late_init();
 
-  if(!mountSDCard())
+  g_sdcard_present = mountSDCard();
+
+  if(!g_sdcard_present)
   {
     azlog("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)
+      {
+        azlog("Enabled ROM drive without SD card");
+        return;
+      }
+    }
+
     do
     {
       blinkStatus(BLINK_ERROR_NO_SD_CARD);
       delay(1000);
       azplatform_reset_watchdog();
-    } while (!mountSDCard());
+      g_sdcard_present = mountSDCard();
+    } while (!g_sdcard_present);
     azlog("SD card init succeeded after retry");
   }
 
-  if (SD.clusterCount() == 0)
+  if (g_sdcard_present)
   {
-    azlog("SD card without filesystem!");
-  }
+    if (SD.clusterCount() == 0)
+    {
+      azlog("SD card without filesystem!");
+    }
 
-  print_sd_info();
+    print_sd_info();
   
-  reinitSCSI();
+    reinitSCSI();
+  }
 
-  azlog("Initialization complete!");
   azlog("Platform: ", g_azplatform_name);
   azlog("FW Version: ", g_azlog_firmwareversion);
+  azlog("Initialization complete!");
 
-  init_logfile();
+  if (g_sdcard_present)
+  {
+    init_logfile();
+  }
 }
 
 extern "C" void zuluscsi_main_loop(void)
@@ -533,29 +555,50 @@ extern "C" void zuluscsi_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))
       {
-        azlog("SD card removed, trying to reinit");
-        do
+        if (!SD.card()->readOCR(&ocr))
         {
-          blinkStatus(BLINK_ERROR_NO_SD_CARD);
-          delay(1000);
-          azplatform_reset_watchdog();
-        } while (!mountSDCard());
+          g_sdcard_present = false;
+          azlog("SD card removed, trying to reinit");
+        }
+      }
+    }
+  }
+
+  if (!g_sdcard_present)
+  {
+    // Try to remount SD card
+    do 
+    {
+      g_sdcard_present = mountSDCard();
+
+      if (g_sdcard_present)
+      {
         azlog("SD card reinit succeeded");
         print_sd_info();
 
         reinitSCSI();
         init_logfile();
       }
-    }
+      else if (!g_romdrive_active)
+      {
+        blinkStatus(BLINK_ERROR_NO_SD_CARD);
+        delay(1000);
+        azplatform_reset_watchdog();
+      }
+    } while (!g_sdcard_present && !g_romdrive_active);
+  }
+  else
+  {
+    
   }
 }

+ 42 - 5
src/ZuluSCSI_disk.cpp

@@ -18,6 +18,7 @@
 extern "C" {
 #include <scsi2sd_time.h>
 #include <sd.h>
+#include <mode.h>
 }
 
 #ifndef PLATFORM_MAX_SCSI_SPEED
@@ -131,7 +132,12 @@ bool scsiDiskProgramRomDrive(const char *filename, int scsi_id, int blocksize, S
     uint32_t pages = (filesize + AZPLATFORM_ROMDRIVE_PAGE_SIZE - 1) / AZPLATFORM_ROMDRIVE_PAGE_SIZE;
     for (uint32_t i = 0; i < pages; i++)
     {
-        if (!file.read(scsiDev.data, AZPLATFORM_ROMDRIVE_PAGE_SIZE) ||
+        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))
         {
             azlog("---- Failed to program ROM drive page ", (int)i);
@@ -140,6 +146,8 @@ bool scsiDiskProgramRomDrive(const char *filename, int scsi_id, int blocksize, S
         }
     }
 
+    LED_OFF();
+
     file.close();
 
     char newname[MAX_FILE_PATH * 2] = "";
@@ -151,6 +159,12 @@ bool scsiDiskProgramRomDrive(const char *filename, int scsi_id, int blocksize, S
     return true;
 }
 
+bool scsiDiskCheckRomDrive()
+{
+    romdrive_hdr_t hdr = {};
+    return check_romdrive(&hdr);
+}
+
 // Check if rom drive exists and activate it
 bool scsiDiskActivateRomDrive()
 {
@@ -287,6 +301,16 @@ public:
         }
     }
 
+    bool isWritable()
+    {
+        return !m_isrom;
+    }
+
+    bool isRom()
+    {
+        return m_isrom;
+    }
+
     bool isOpen()
     {
         if (m_israw)
@@ -652,7 +676,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) && sector_end != 0)
+        if (img.file.isRom())
+        {
+            // ROM is always contiguous, no need to log
+        }
+        else if (img.file.contiguousRange(&sector_begin, &sector_end))
         {
             azlog("---- Image file is contiguous, SD card sectors ", (int)sector_begin, " to ", (int)sector_end);
         }
@@ -919,7 +947,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;
         }
@@ -1247,10 +1276,11 @@ static void doWrite(uint32_t lba, uint32_t blocks)
     azdbg("------ 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()))
 
     {
-        azlog("WARNING: Host attempted write to CD-ROM");
+        azlog("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;
@@ -1875,6 +1905,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;

+ 1 - 0
src/ZuluSCSI_disk.h

@@ -21,6 +21,7 @@ void scsiDiskLoadConfig(int target_idx);
 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