Quellcode durchsuchen

Refactor ImageBackingStore and ROMDrive to separate files.

Makes ZuluSCSI_disk.cpp easier to maintain.
No functional changes.
Petteri Aimonen vor 2 Jahren
Ursprung
Commit
3bcba34f67
6 geänderte Dateien mit 590 neuen und 424 gelöschten Zeilen
  1. 3 2
      src/BlueSCSI.cpp
  2. 10 422
      src/BlueSCSI_disk.cpp
  3. 283 0
      src/ImageBackingStore.cpp
  4. 84 0
      src/ImageBackingStore.h
  5. 178 0
      src/ROMDrive.cpp
  6. 32 0
      src/ROMDrive.h

+ 3 - 2
src/BlueSCSI.cpp

@@ -57,6 +57,7 @@
 #include "BlueSCSI_log_trace.h"
 #include "BlueSCSI_disk.h"
 #include "BlueSCSI_initiator.h"
+#include "ROMDrive.h"
 
 SdFs SD;
 FsFile g_logfile;
@@ -281,7 +282,7 @@ bool findHDDImages()
 
       if(strcasecmp(name, "CLEAR_ROM") == 0)
       {
-        scsiDiskClearRomDrive();
+        romDriveClear();
         continue;
       }
 
@@ -575,7 +576,7 @@ extern "C" void bluescsi_setup(void)
     log("SD card init failed, sdErrorCode: ", (int)SD.sdErrorCode(),
            " sdErrorData: ", (int)SD.sdErrorData());
 
-    if (scsiDiskCheckRomDrive())
+    if (romDriveCheckPresent())
     {
       reinitSCSI();
       if (g_romdrive_active)

+ 10 - 422
src/BlueSCSI_disk.cpp

@@ -10,6 +10,8 @@
 #include "BlueSCSI_log.h"
 #include "BlueSCSI_config.h"
 #include "BlueSCSI_presets.h"
+#include "ImageBackingStore.h"
+#include "ROMDrive.h"
 #include <minIni.h>
 #include <string.h>
 #include <strings.h>
@@ -56,14 +58,6 @@ extern "C" {
 #endif
 #endif
 
-#ifndef PLATFORM_HAS_ROM_DRIVE
-// Dummy defines for platforms without ROM drive support
-#define PLATFORM_ROMDRIVE_PAGE_SIZE 1024
-uint32_t platform_get_romdrive_maxsize() { return 0; }
-bool platform_read_romdrive(uint8_t *dest, uint32_t start, uint32_t count) { return false; }
-bool platform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t count) { return false; }
-#endif
-
 #ifndef PLATFORM_SCSIPHY_HAS_NONBLOCKING_READ
 // For platforms that do not have non-blocking read from SCSI bus
 void scsiStartRead(uint8_t* data, uint32_t count, int *parityError)
@@ -80,153 +74,24 @@ bool scsiIsReadFinished(const uint8_t *data)
 }
 #endif
 
-// SD card sector size is always 512 bytes
-extern SdFs SD;
-#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 (!platform_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;
-}
-
-// Clear the drive metadata header
-bool scsiDiskClearRomDrive()
-{
-    romdrive_hdr_t hdr = {0x0};
-
-    if (!platform_write_romdrive((const uint8_t*)&hdr, 0, PLATFORM_ROMDRIVE_PAGE_SIZE))
-    {
-        log("-- Failed to clear ROM drive");
-        return false;
-    }
-    log("-- Cleared ROM drive");
-    SD.remove("CLEAR_ROM");
-    return true;
-}
-
-// Load an image file to romdrive
-bool scsiDiskProgramRomDrive(const char *filename, int scsi_id, int blocksize, S2S_CFG_TYPE type)
-{
-#ifndef PLATFORM_HAS_ROM_DRIVE
-    log("---- Platform does not support ROM drive");
-    return false;
-#endif
-
-    FsFile file = SD.open(filename, O_RDONLY);
-    if (!file.isOpen())
-    {
-        log("---- Failed to open: ", filename);
-        return false;
-    }
-
-    uint64_t filesize = file.size();
-    uint32_t maxsize = platform_get_romdrive_maxsize() - PLATFORM_ROMDRIVE_PAGE_SIZE;
-
-    log("---- SCSI ID: ", scsi_id, " blocksize ", blocksize, " type ", (int)type);
-    log("---- ROM drive maximum size is ", (int)maxsize,
-          " bytes, image file is ", (int)filesize, " bytes");
-    
-    if (filesize > maxsize)
-    {
-        log("---- 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 (!platform_write_romdrive((const uint8_t*)&hdr, 0, PLATFORM_ROMDRIVE_PAGE_SIZE))
-    {
-        log("---- Failed to program ROM drive header");
-        file.close();
-        return false;
-    }
-    
-    // Program the drive contents
-    uint32_t pages = (filesize + PLATFORM_ROMDRIVE_PAGE_SIZE - 1) / PLATFORM_ROMDRIVE_PAGE_SIZE;
-    for (uint32_t i = 0; i < pages; i++)
-    {
-        if (i % 2)
-            LED_ON();
-        else
-            LED_OFF();
-
-        if (file.read(scsiDev.data, PLATFORM_ROMDRIVE_PAGE_SIZE) <= 0 ||
-            !platform_write_romdrive(scsiDev.data, (i + 1) * PLATFORM_ROMDRIVE_PAGE_SIZE, PLATFORM_ROMDRIVE_PAGE_SIZE))
-        {
-            log("---- 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);
-    log("---- 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()
 {
 #ifndef PLATFORM_HAS_ROM_DRIVE
     return false;
-#endif
+#else
     log("");
     log("=== ROM Drive ===");
+
     uint32_t maxsize = platform_get_romdrive_maxsize() - PLATFORM_ROMDRIVE_PAGE_SIZE;
     log("Platform supports ROM drive up to ", (int)(maxsize / 1024), " kB");
 
     romdrive_hdr_t hdr = {};
-    if (!check_romdrive(&hdr))
+    if (!romDriveCheckPresent(&hdr))
     {
         log("---- ROM drive image not detected");
         return false;
@@ -255,6 +120,7 @@ bool scsiDiskActivateRomDrive()
         return false;
     }
 
+    log("---- 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)
@@ -267,311 +133,33 @@ bool scsiDiskActivateRomDrive()
         log("---- Activated ROM drive, SCSI id ", (int)hdr.scsi_id, " size ", (int)(hdr.imagesize / 1024), " kB");
         return true;
     }
+
+#endif
 }
 
 
 /***********************/
-/* Backing image files */
+/* Image configuration */
 /***********************/
 
 extern SdFs SD;
 SdDevice sdDev = {2, 256 * 1024 * 1024 * 2}; /* For SCSI2SD */
 
-// This class wraps SdFat library FsFile to allow access
-// through either FAT filesystem or as a raw sector range.
-//
-// 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_isreadonly_attr = false;
-        m_blockdev = nullptr;
-        m_bgnsector = m_endsector = m_cursector = 0;
-    }
-
-    ImageBackingStore(const char *filename, uint32_t scsi_block_size, S2S_CFG_TYPE type): ImageBackingStore()
-    {
-        if (strncasecmp(filename, "RAW:", 4) == 0)
-        {
-            char *endptr, *endptr2;
-            m_bgnsector = strtoul(filename + 4, &endptr, 0);
-            m_endsector = strtoul(endptr + 1, &endptr2, 0);
-
-            if (*endptr != ':' || *endptr2 != '\0')
-            {
-                log("Invalid format for raw filename: ", filename);
-                return;
-            }
-
-            if ((scsi_block_size % SD_SECTOR_SIZE) != 0)
-            {
-                log("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();
-
-            uint32_t sectorCount = SD.card()->sectorCount();
-            if (m_endsector >= sectorCount)
-            {
-                log("Limiting RAW image mapping to SD card sector count: ", (int)sectorCount);
-                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_isreadonly_attr = !!(FS_ATTRIB_READ_ONLY & SD.attrib(filename));
-            if (m_isreadonly_attr)
-            {
-                m_fsfile = SD.open(filename, O_RDONLY);
-                log("---- Image file is read-only, writes disabled");
-            }
-            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
-                && (scsi_block_size % SD_SECTOR_SIZE) == 0)
-            {
-                // Convert to raw mapping, this avoids some unnecessary
-                // access overhead in SdFat library.
-                m_israw = true;
-                m_blockdev = SD.card();
-                m_bgnsector = begin;
-                m_endsector = begin + sectorcount - 1;
-                m_fsfile.close();
-            }
-        }
-    }
-
-    bool isWritable()
-    {
-        return !(m_isrom || m_isreadonly_attr);
-    }
-
-    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)
-        {
-            m_blockdev = nullptr;
-            return true;
-        }
-        else if (m_isrom)
-        {
-            m_romhdr.imagesize = 0;
-            return true;
-        }
-        else
-        {
-            return m_fsfile.close();
-        }
-    }
-
-    uint64_t size()
-    {
-        if (m_israw && m_blockdev)
-        {
-            return (uint64_t)(m_endsector - m_bgnsector + 1) * SD_SECTOR_SIZE;
-        }
-        else if (m_isrom)
-        {
-            return m_romhdr.imagesize;
-        }
-        else
-        {
-            return m_fsfile.size();
-        }
-    }
-
-    bool contiguousRange(uint32_t* bgnSector, uint32_t* endSector)
-    {
-        if (m_israw && m_blockdev)
-        {
-            *bgnSector = m_bgnsector;
-            *endSector = m_endsector;
-            return true;
-        }
-        else if (m_isrom)
-        {
-            *bgnSector = 0;
-            *endSector = 0;
-            return true;
-        }
-        else
-        {
-            return m_fsfile.contiguousRange(bgnSector, endSector);
-        }
-    }
-
-    bool seek(uint64_t pos)
-    {
-        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);
-        }
-    }
-
-    int read(void* buf, size_t count)
-    {
-        if (m_israw && m_blockdev)
-        {
-            uint32_t sectorcount = count / SD_SECTOR_SIZE;
-            assert((uint64_t)sectorcount * SD_SECTOR_SIZE == count);
-            if (m_blockdev->readSectors(m_cursector, (uint8_t*)buf, sectorcount))
-            {
-                m_cursector += sectorcount;
-                return count;
-            }
-            else
-            {
-                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 + PLATFORM_ROMDRIVE_PAGE_SIZE;
-            if (platform_read_romdrive((uint8_t*)buf, start, count))
-            {
-                m_cursector += sectorcount;
-                return count;
-            }
-            else
-            {
-                return -1;
-            }
-        }
-        else
-        {
-            return m_fsfile.read(buf, count);
-        }
-    }
-
-    size_t write(const void* buf, size_t count)
-    {
-        if (m_israw && m_blockdev)
-        {
-            uint32_t sectorcount = count / SD_SECTOR_SIZE;
-            assert((uint64_t)sectorcount * SD_SECTOR_SIZE == count);
-            if (m_blockdev->writeSectors(m_cursector, (const uint8_t*)buf, sectorcount))
-            {
-                m_cursector += sectorcount;
-                return count;
-            }
-            else
-            {
-                return 0;
-            }
-        }
-        else if (m_isrom)
-        {
-            log("ERROR: attempted to write to ROM drive");
-            return 0;
-        }
-        else  if (m_isreadonly_attr)
-        {
-            log("ERROR: attempted to write to a read only image");
-            return 0;
-        }
-        else
-        {
-            return m_fsfile.write(buf, count);
-        }
-    }
-
-    void flush()
-    {
-        if (!m_israw && !m_isrom && !m_isreadonly_attr)
-        {
-            m_fsfile.flush();
-        }
-    }
-
-private:
-    bool m_israw;
-    bool m_isrom;
-    bool m_isreadonly_attr;
-    romdrive_hdr_t m_romhdr;
-    FsFile m_fsfile;
-    SdCard *m_blockdev;
-    uint32_t m_bgnsector;
-    uint32_t m_endsector;
-    uint32_t m_cursector;
-};
-
 struct image_config_t: public S2S_TargetCfg
 {
     ImageBackingStore file;
-
     // For CD-ROM drive ejection
     bool ejected;
     uint8_t cdrom_events;
     bool reinsert_on_inquiry;
-
     // Index of image, for when image on-the-fly switching is used for CD drives
     int image_index;
-
     // Right-align vendor / product type strings (for Apple)
     // Standard SCSI uses left alignment
     // This field uses -1 for default when field is not set in .ini
     int rightAlignStrings;
-
     // Maximum amount of bytes to prefetch
     int prefetchbytes;
-
     // Warning about geometry settings
     bool geometrywarningprinted;
 };
@@ -733,7 +321,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, blocksize, type);
+    img.file = ImageBackingStore(filename, blocksize);
 
     if (img.file.isOpen())
     {

+ 283 - 0
src/ImageBackingStore.cpp

@@ -0,0 +1,283 @@
+/**
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ *
+ * This file is licensed under the GPL version 3 or any later version. 
+ * It is derived from disk.c in SCSI2SD V6
+ *
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program 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. 
+ *
+ * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#include "ImageBackingStore.h"
+#include <SdFat.h>
+#include <BlueSCSI_platform.h>
+#include "BlueSCSI_log.h"
+#include "BlueSCSI_config.h"
+#include <minIni.h>
+#include <strings.h>
+#include <string.h>
+#include <assert.h>
+
+ImageBackingStore::ImageBackingStore()
+{
+    m_israw = false;
+    m_isrom = false;
+    m_isreadonly_attr = false;
+    m_blockdev = nullptr;
+    m_bgnsector = m_endsector = m_cursector = 0;
+}
+
+ImageBackingStore::ImageBackingStore(const char *filename, uint32_t scsi_block_size): ImageBackingStore()
+{
+    if (strncasecmp(filename, "RAW:", 4) == 0)
+    {
+        char *endptr, *endptr2;
+        m_bgnsector = strtoul(filename + 4, &endptr, 0);
+        m_endsector = strtoul(endptr + 1, &endptr2, 0);
+
+        if (*endptr != ':' || *endptr2 != '\0')
+        {
+            log("Invalid format for raw filename: ", filename);
+            return;
+        }
+
+        if ((scsi_block_size % SD_SECTOR_SIZE) != 0)
+        {
+            log("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();
+
+        uint32_t sectorCount = SD.card()->sectorCount();
+        if (m_endsector >= sectorCount)
+        {
+            log("Limiting RAW image mapping to SD card sector count: ", (int)sectorCount);
+            m_endsector = sectorCount - 1;
+        }
+    }
+    else if (strncasecmp(filename, "ROM:", 4) == 0)
+    {
+        if (!romDriveCheckPresent(&m_romhdr))
+        {
+            m_romhdr.imagesize = 0;
+        }
+        else
+        {
+            m_isrom = true;
+        }
+    }
+    else
+    {
+        m_isreadonly_attr = !!(FS_ATTRIB_READ_ONLY & SD.attrib(filename));
+        if (m_isreadonly_attr)
+        {
+            m_fsfile = SD.open(filename, O_RDONLY);
+            log("---- Image file is read-only, writes disabled");
+        }
+        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
+            && (scsi_block_size % SD_SECTOR_SIZE) == 0)
+        {
+            // Convert to raw mapping, this avoids some unnecessary
+            // access overhead in SdFat library.
+            m_israw = true;
+            m_blockdev = SD.card();
+            m_bgnsector = begin;
+            m_endsector = begin + sectorcount - 1;
+            m_fsfile.close();
+        }
+    }
+}
+
+bool ImageBackingStore::isOpen()
+{
+    if (m_israw)
+        return (m_blockdev != NULL);
+    else if (m_isrom)
+        return (m_romhdr.imagesize > 0);
+    else
+        return m_fsfile.isOpen();
+}
+
+bool ImageBackingStore::isWritable()
+{
+    return !(m_isrom && m_isreadonly_attr);
+}
+
+bool ImageBackingStore::isRom()
+{
+    return m_isrom;
+}
+
+bool ImageBackingStore::close()
+{
+    if (m_israw)
+    {
+        m_blockdev = nullptr;
+        return true;
+    }
+    else if (m_isrom)
+    {
+        m_romhdr.imagesize = 0;
+        return true;
+    }
+    else
+    {
+        return m_fsfile.close();
+    }
+}
+
+uint64_t ImageBackingStore::size()
+{
+    if (m_israw && m_blockdev)
+    {
+        return (uint64_t)(m_endsector - m_bgnsector + 1) * SD_SECTOR_SIZE;
+    }
+    else if (m_isrom)
+    {
+        return m_romhdr.imagesize;
+    }
+    else
+    {
+        return m_fsfile.size();
+    }
+}
+
+bool ImageBackingStore::contiguousRange(uint32_t* bgnSector, uint32_t* endSector)
+{
+    if (m_israw && m_blockdev)
+    {
+        *bgnSector = m_bgnsector;
+        *endSector = m_endsector;
+        return true;
+    }
+    else if (m_isrom)
+    {
+        *bgnSector = 0;
+        *endSector = 0;
+        return true;
+    }
+    else
+    {
+        return m_fsfile.contiguousRange(bgnSector, endSector);
+    }
+}
+
+bool ImageBackingStore::seek(uint64_t pos)
+{
+    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);
+    }
+}
+
+ssize_t ImageBackingStore::read(void* buf, size_t count)
+{
+    if (m_israw && m_blockdev)
+    {
+        uint32_t sectorcount = count / SD_SECTOR_SIZE;
+        assert((uint64_t)sectorcount * SD_SECTOR_SIZE == count);
+        if (m_blockdev->readSectors(m_cursector, (uint8_t*)buf, sectorcount))
+        {
+            m_cursector += sectorcount;
+            return count;
+        }
+        else
+        {
+            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;
+        if (romDriveRead((uint8_t*)buf, start, count))
+        {
+            m_cursector += sectorcount;
+            return count;
+        }
+        else
+        {
+            return -1;
+        }
+    }
+    else
+    {
+        return m_fsfile.read(buf, count);
+    }
+}
+
+ssize_t ImageBackingStore::write(const void* buf, size_t count)
+{
+    if (m_israw && m_blockdev)
+    {
+        uint32_t sectorcount = count / SD_SECTOR_SIZE;
+        assert((uint64_t)sectorcount * SD_SECTOR_SIZE == count);
+        if (m_blockdev->writeSectors(m_cursector, (const uint8_t*)buf, sectorcount))
+        {
+            m_cursector += sectorcount;
+            return count;
+        }
+        else
+        {
+            return 0;
+        }
+    }
+    else if (m_isrom)
+    {
+        log("ERROR: attempted to write to ROM drive");
+        return 0;
+    }
+    else  if (m_isreadonly_attr)
+    {
+        log("ERROR: attempted to write to a read only image");
+        return 0;
+    }
+    else
+    {
+        return m_fsfile.write(buf, count);
+    }
+}
+
+void ImageBackingStore::flush()
+{
+    if (!m_israw && !m_isrom && !m_isreadonly_attr)
+    {
+        m_fsfile.flush();
+    }
+}

+ 84 - 0
src/ImageBackingStore.h

@@ -0,0 +1,84 @@
+/* Access layer to image files associated with a SCSI device.
+ * Currently supported image storage modes:
+ *
+ * - Files on SD card
+ * - Raw SD card partitions
+ * - Microcontroller flash ROM drive
+ */
+
+#pragma once
+#include <stdint.h>
+#include <unistd.h>
+#include <SdFat.h>
+#include "ROMDrive.h"
+
+extern "C" {
+#include <scsi.h>
+}
+
+// SD card sector size is always 512 bytes
+extern SdFs SD;
+#define SD_SECTOR_SIZE 512
+
+// This class wraps SdFat library FsFile to allow access
+// through either FAT filesystem or as a raw sector range.
+//
+// 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:
+    // Empty image, cannot be accessed
+    ImageBackingStore();
+
+    // Parse image file parameters from filename.
+    // Special filename formats:
+    //    RAW:start:end
+    //    ROM:
+    ImageBackingStore(const char *filename, uint32_t scsi_block_size);
+
+    // Can the image be read?
+    bool isOpen();
+
+    // Can the image be written?
+    bool isWritable();
+
+    // Is this internal ROM drive in microcontroller flash?
+    bool isRom();
+
+    // Close the image so that .isOpen() will return false.
+    bool close();
+
+    // Return image size in bytes
+    uint64_t size();
+
+    // Check if the image sector range is contiguous, and the image is on
+    // SD card, return the sector numbers.
+    bool contiguousRange(uint32_t* bgnSector, uint32_t* endSector);
+
+    // Set current position for following read/write operations
+    bool seek(uint64_t pos);
+
+    // Read data from the image file, returns number of bytes read, or negative on error.
+    ssize_t read(void* buf, size_t count);
+
+    // Write data to image file, returns number of bytes written, or negative on error.
+    ssize_t write(const void* buf, size_t count);
+
+    // Flush any pending changes to filesystem
+    void flush();
+
+protected:
+    bool m_israw;
+    bool m_isrom;
+    bool m_isreadonly_attr;
+    romdrive_hdr_t m_romhdr;
+    FsFile m_fsfile;
+    SdCard *m_blockdev;
+    uint32_t m_bgnsector;
+    uint32_t m_endsector;
+    uint32_t m_cursector;
+};

+ 178 - 0
src/ROMDrive.cpp

@@ -0,0 +1,178 @@
+/**
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * Function romDriveClear() Copyright (c) 2023 Eric Helgeson
+ *
+ * This file is licensed under the GPL version 3 or any later version. 
+ * It is derived from disk.c in SCSI2SD V6
+ *
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program 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. 
+ *
+ * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#include "ROMDrive.h"
+#include <SdFat.h>
+#include <BlueSCSI_platform.h>
+#include "BlueSCSI_log.h"
+#include "BlueSCSI_config.h"
+#include <strings.h>
+#include <string.h>
+
+extern "C" {
+#include <scsi.h>
+}
+
+extern SdFs SD;
+
+#ifndef PLATFORM_HAS_ROM_DRIVE
+
+bool romDriveCheckPresent(romdrive_hdr_t *hdr)
+{
+    return false;
+}
+
+bool romDriveClear()
+{
+    log("---- Platform does not support ROM drive");
+    return false;
+}
+
+bool scsiDiskProgramRomDrive(const char *filename, int scsi_id, int blocksize, S2S_CFG_TYPE type)
+{
+    log("---- Platform does not support ROM drive");
+    return false;
+}
+
+bool romDriveRead(uint8_t *buf, uint32_t start, uint32_t count)
+{
+    return false;
+}
+
+#else
+
+// Check if the romdrive is present
+bool romDriveCheckPresent(romdrive_hdr_t *hdr)
+{
+    romdrive_hdr_t tmp;
+    if (!hdr) hdr = &tmp;
+
+    if (!platform_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;
+}
+
+// Clear the drive metadata header
+bool romDriveClear()
+{
+    romdrive_hdr_t hdr = {0x0};
+
+    if (!platform_write_romdrive((const uint8_t*)&hdr, 0, PLATFORM_ROMDRIVE_PAGE_SIZE))
+    {
+        log("-- Failed to clear ROM drive");
+        return false;
+    }
+    log("-- Cleared ROM drive");
+    SD.remove("CLEAR_ROM");
+    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())
+    {
+        log("---- Failed to open: ", filename);
+        return false;
+    }
+
+    uint64_t filesize = file.size();
+    uint32_t maxsize = platform_get_romdrive_maxsize() - PLATFORM_ROMDRIVE_PAGE_SIZE;
+
+    log("---- SCSI ID: ", scsi_id, " blocksize ", blocksize, " type ", (int)type);
+    log("---- ROM drive maximum size is ", (int)maxsize,
+          " bytes, image file is ", (int)filesize, " bytes");
+
+    if (filesize > maxsize)
+    {
+        log("---- 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 (!platform_write_romdrive((const uint8_t*)&hdr, 0, PLATFORM_ROMDRIVE_PAGE_SIZE))
+    {
+        log("---- Failed to program ROM drive header");
+        file.close();
+        return false;
+    }
+
+    // Program the drive contents
+    uint32_t pages = (filesize + PLATFORM_ROMDRIVE_PAGE_SIZE - 1) / PLATFORM_ROMDRIVE_PAGE_SIZE;
+    for (uint32_t i = 0; i < pages; i++)
+    {
+        if (i % 2)
+            LED_ON();
+        else
+            LED_OFF();
+
+        if (file.read(scsiDev.data, PLATFORM_ROMDRIVE_PAGE_SIZE) <= 0 ||
+            !platform_write_romdrive(scsiDev.data, (i + 1) * PLATFORM_ROMDRIVE_PAGE_SIZE, PLATFORM_ROMDRIVE_PAGE_SIZE))
+        {
+            log("---- 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);
+    log("---- ROM drive programming successful, image file renamed to ", newname);
+
+    return true;
+}
+
+bool romDriveRead(uint8_t *buf, uint32_t start, uint32_t count)
+{
+    return platform_read_romdrive(buf, start + PLATFORM_ROMDRIVE_PAGE_SIZE, count);
+}
+
+#endif

+ 32 - 0
src/ROMDrive.h

@@ -0,0 +1,32 @@
+/* Access layer to microcontroller internal flash ROM drive storage.
+ * Can store a small disk image.
+ */
+
+#pragma once
+#include <stdint.h>
+#include <unistd.h>
+#include <scsi2sd.h>
+
+// Header used for storing the rom drive parameters in 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];
+};
+
+// Return true if ROM drive is found.
+// If hdr is not NULL, it will receive the ROM drive header information.
+// If flash is empty, returns false.
+bool romDriveCheckPresent(romdrive_hdr_t *hdr = nullptr);
+
+// Clear any existing ROM drive, returning flash to empty state
+bool romDriveClear();
+
+// Program ROM drive image to flash
+bool romDriveProgram(const char *filename, int scsi_id, int blocksize, S2S_CFG_TYPE type);
+
+// Read data from rom drive main data area
+bool romDriveRead(uint8_t *buf, uint32_t start, uint32_t count);