Procházet zdrojové kódy

Adding Zip drive support

This adds Iomega Zip Drive 100 support.
Code adapted from here https://github.com/BlueSCSI/BlueSCSI-v2/compare/v2024.04.02...v2024.05.21
Still has some issues with cycling media.

Co-authored-by: jokker <jokker@gmail.com>
Co-authored-by: Eric Helgeson <erichelgeson@gmail.com>
Morio před 1 rokem
rodič
revize
9dbeed43d2

+ 1 - 0
lib/SCSI2SD/include/scsi2sd.h

@@ -78,6 +78,7 @@ typedef enum
 	S2S_CFG_MO = 4,
 	S2S_CFG_SEQUENTIAL = 5,
 	S2S_CFG_NETWORK = 6,
+	S2S_CFG_ZIP100 = 7,
 	S2S_CFG_NOT_SET = 255
 
 } S2S_CFG_TYPE;

+ 1 - 2
lib/SCSI2SD/src/firmware/geometry.h

@@ -18,8 +18,7 @@
 #define GEOMETRY_H
 
 #include "config.h"
-// TODO #include "sd.h"
-#define SD_SECTOR_SIZE 512
+#include "sd.h"
 
 typedef enum
 {

+ 35 - 3
lib/SCSI2SD/src/firmware/inquiry.c

@@ -1,6 +1,7 @@
 //	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
 //	Copyright (C) 2019 Landon Rodgers  <g.landon.rodgers@gmail.com>
 //	Copyright (c) 2023 joshua stein <jcs@jcs.org>
+//	Copyright (c) 2024 Eric Helgeson <erichelgeson@gmail.com>
 //
 //	This file is part of SCSI2SD.
 //
@@ -23,6 +24,7 @@
 #include "scsi.h"
 #include "config.h"
 #include "inquiry.h"
+#include "ZuluSCSI_config.h"
 
 #include <string.h>
 
@@ -83,6 +85,17 @@ static const uint8_t AscImpOperatingDefinition[] =
 'S','C','S','I','-','2'
 };
 
+static const uint8_t IomegaVendorInquiry[] =
+{
+'0', '8', '/', '2', '0', '/', '9', '6', 0x0, 0x0, 0x0, 0x0,
+0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+'(', 'c', ')', ' ', 'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', 'I', 'O',
+'M', 'E', 'G', 'A', ' ', '1', '9', '9', '5', ' '
+};
+
+
 void s2s_scsiInquiry()
 {
 	uint8_t evpd = scsiDev.cdb[1] & 1; // enable vital product data.
@@ -127,8 +140,8 @@ void s2s_scsiInquiry()
 	{
 		memcpy(scsiDev.data, UnitSerialNumber, sizeof(UnitSerialNumber));
 		scsiDev.dataLen = sizeof(UnitSerialNumber);
-        const S2S_TargetCfg* config = scsiDev.target->cfg;
-        memcpy(&scsiDev.data[4], config->serial, sizeof(config->serial));
+		const S2S_TargetCfg* config = scsiDev.target->cfg;
+		memcpy(&scsiDev.data[4], config->serial, sizeof(config->serial));
 		scsiDev.phase = DATA_IN;
 	}
 	else if (pageCode == 0x81)
@@ -203,6 +216,7 @@ void s2s_scsiInquiry()
 
 		case S2S_CFG_FLOPPY_14MB:
 		case S2S_CFG_REMOVABLE:
+		case S2S_CFG_ZIP100:
 			scsiDev.data[1] |= 0x80; // Removable bit.
 			break;
 
@@ -227,6 +241,7 @@ uint32_t s2s_getStandardInquiry(
 	const S2S_TargetCfg* cfg, uint8_t* out, uint32_t maxlen
 	)
 {
+	uint32_t size = 0;
 	uint32_t buflen = sizeof(StandardResponse);
 	if (buflen > maxlen) buflen = maxlen;
 
@@ -245,10 +260,26 @@ uint32_t s2s_getStandardInquiry(
 	memcpy(&out[8], cfg->vendor, sizeof(cfg->vendor));
 	memcpy(&out[16], cfg->prodId, sizeof(cfg->prodId));
 	memcpy(&out[32], cfg->revision, sizeof(cfg->revision));
-	return sizeof(StandardResponse) +
+	size =  sizeof(StandardResponse) +
 		sizeof(cfg->vendor) +
 		sizeof(cfg->prodId) +
 		sizeof(cfg->revision);
+
+	if(cfg->deviceType == S2S_CFG_ZIP100)
+	{
+		memcpy(&out[size], IomegaVendorInquiry, sizeof(IomegaVendorInquiry));
+		size += sizeof(IomegaVendorInquiry);
+		out[7] = 0x00; // Disable sync and linked commands
+		out[4] = 0x75; // 117 length
+	}
+	// Iomega already has a vendor inquiry
+	if(cfg->deviceType != S2S_CFG_NETWORK && cfg->deviceType != S2S_CFG_ZIP100) {
+		memcpy(&out[size], INQUIRY_NAME, sizeof(INQUIRY_NAME));
+		size += sizeof(INQUIRY_NAME);
+		out[size] = TOOLBOX_API;
+		size += 1;
+	}
+	return size;
 }
 
 uint8_t getDeviceTypeQualifier()
@@ -270,6 +301,7 @@ uint8_t getDeviceTypeQualifier()
 
 	case S2S_CFG_FLOPPY_14MB:
 	case S2S_CFG_REMOVABLE:
+	case S2S_CFG_ZIP100:
 		return 0;
 		break;
 

+ 24 - 1
lib/SCSI2SD/src/firmware/mode.c

@@ -2,7 +2,7 @@
 //  Copyright (C) 2014 Doug Brown <doug@downtowndougbrown.com>
 //  Copyright (C) 2019 Landon Rodgers <g.landon.rodgers@gmail.com>
 //	Copyright (C) 2024 Rabbit Hole Computing LLC
-//
+//	Copyright (C) 2024 jokker <jokker@gmail.com>
 //	This file is part of SCSI2SD.
 //
 //	SCSI2SD is free software: you can redistribute it and/or modify
@@ -256,6 +256,19 @@ static const uint8_t ToolboxVendorPage[] =
 ' ','R','a','b','b','i','t','H','o','l','e','C','o','m','p','u','t','i','n','g',0x00
 };
 
+static const uint8_t IomegaZip100VendorPage[] =
+{
+	0x2f, // Page Code
+	4, // Page Length
+	0x5c, 0xf, 0xff, 0xf
+};
+
+static const uint8_t IomegaZip250VendorPage[] =
+{
+	0x2f, // Page Code
+	4, // Page Length
+	0x5c, 0xf, 0x3c, 0xf
+};
 
 static void pageIn(int pc, int dataIdx, const uint8_t* pageData, int pageLen)
 {
@@ -284,6 +297,7 @@ static void doModeSense(
 	{
 	case S2S_CFG_FIXED:
 	case S2S_CFG_REMOVABLE:
+	case S2S_CFG_ZIP100:
 		mediumType = 0; // We should support various floppy types here!
 		// Contains cache bits (0) and a Write-Protect bit.
 		deviceSpecificParam =
@@ -522,6 +536,15 @@ static void doModeSense(
 	idx += modeSenseCDDevicePage(pc, idx, pageCode, &pageFound);
 	idx += modeSenseCDAudioControlPage(pc, idx, pageCode, &pageFound);
 
+	
+	if ((scsiDev.target->cfg->deviceType == S2S_CFG_ZIP100) &&
+		(pageCode == 0x2f || pageCode == 0x3f))
+	{
+		pageFound = 1;
+		pageIn(pc, idx, IomegaZip100VendorPage, sizeof(IomegaZip100VendorPage));
+		idx += sizeof(IomegaZip100VendorPage);
+	}
+
 	if ((scsiDev.target->cfg->deviceType == S2S_CFG_SEQUENTIAL) &&
 		(pageCode == 0x10 || pageCode == 0x3F))
 	{

+ 1 - 1
lib/SCSI2SD/src/firmware/scsi.c

@@ -584,7 +584,7 @@ static void process_Command()
 
 		enter_Status(CHECK_CONDITION);
 	}
-	else if (scsiDev.lun)
+	else if (scsiDev.lun && (command < 0xD0))
 	{
 		scsiDev.target->sense.code = ILLEGAL_REQUEST;
 		scsiDev.target->sense.asc = LOGICAL_UNIT_NOT_SUPPORTED;

+ 76 - 1
lib/SCSI2SD/src/firmware/vendor.c

@@ -1,4 +1,5 @@
 //	Copyright (C) 2016 Michael McMaster <michael@codesrc.com>
+//	Copyright (C) 2024 Jokker <jokker@gmail.com>
 //
 //	This file is part of SCSI2SD.
 //
@@ -19,6 +20,7 @@
 #include "vendor.h"
 #include "diagnostic.h"
 #include "toolbox.h"
+#include <string.h>
 
 // Callback after the DATA OUT phase is complete.
 static void doAssignDiskParameters(void)
@@ -47,7 +49,80 @@ int scsiVendorCommand()
 
 	uint8_t command = scsiDev.cdb[0];
 
-	if (command == 0xC0)
+	// iomega sense command
+	if (command == 0x06 && scsiDev.target->cfg->deviceType == S2S_CFG_ZIP100)
+	{
+		int subcommand = scsiDev.cdb[2];
+		uint8_t alloc_length = scsiDev.cdb[4];
+		scsiDev.phase = DATA_IN;
+
+		// byte 0 is the page
+		scsiDev.data[0] = subcommand;
+
+		if (subcommand == 0x1)
+		{
+			// page is 86 bytes in length
+			scsiDev.dataLen = alloc_length < 0x58 ? alloc_length : 0x58;
+			memset(&scsiDev.data[1], 0xff, scsiDev.dataLen);
+			// byte 1 is the page length minus pagecode and length
+			scsiDev.data[1] = scsiDev.dataLen - 2;
+
+			scsiDev.data[2] = 1;
+			scsiDev.data[3] = 0;
+			scsiDev.data[4] = 0;
+			scsiDev.data[5] = 0;
+			scsiDev.data[6] = 0x5;
+			scsiDev.data[7] = 0xdc;
+			scsiDev.data[8] = 0x6;
+			scsiDev.data[9] = 0xc;
+			scsiDev.data[10] = 0x5;
+			scsiDev.data[11] = 0xdc;
+			scsiDev.data[12] = 0x6;
+			scsiDev.data[13] = 0xc;
+			scsiDev.data[14] = 0;
+		}
+		else if (subcommand == 0x2) {
+			// page is 61 bytes in length
+			scsiDev.dataLen = alloc_length < 0x3f ? alloc_length : 0x3f;
+			memset(&scsiDev.data[1], 0, scsiDev.dataLen);
+			// byte 1 is the page length minus pagecode and length
+			scsiDev.data[1] = scsiDev.dataLen - 2;
+
+			scsiDev.data[3] = 2;
+			scsiDev.data[6] = 0x2;
+			scsiDev.data[7] = 0xff;
+			scsiDev.data[8] = 0xff;
+			// this has something to do with the format/disk life
+			// currently this makes it 100%
+			scsiDev.data[14] = 0x7e;
+			scsiDev.data[18] = 0x7e;
+
+			// byte 21 is the read/write/password settings
+			// 5 = password for R/W
+			// 3 = password for W
+			// 2 = RO
+			// 0 = RW
+			scsiDev.data[20] = 0;
+
+			// set a serial number ABCDEFGHIJKLMNO
+			// starts at byte 25 and is 15 bytes long
+			for(int i = 0; i < 20; i++) {
+				scsiDev.data[25 + i] = i + 0x41;
+			}
+
+			scsiDev.data[0x3e] = 1;
+		}
+		else
+		{
+			// anything else is an illegal command
+			scsiDev.status = CHECK_CONDITION;
+			scsiDev.target->sense.code = ILLEGAL_REQUEST;
+			scsiDev.target->sense.asc = LOGICAL_UNIT_NOT_SUPPORTED;
+			scsiDev.phase = STATUS;
+		}
+
+	}
+	else if (command == 0xC0)
 	{
 		// Define flexible disk format
 		// OMTI-5204 controller

+ 1 - 1
src/Toolbox.cpp

@@ -232,7 +232,7 @@ static void onSetNextCD(const char * img_dir)
     next_cd.getName(name, sizeof(name));
     next_cd.close();
     snprintf(full_path, (MAX_FILE_PATH * 2), "%s/%s", img_dir, name);
-    cdromSwitchNextImage(img, full_path);
+    switchNextImage(img, full_path);
 }
 
 FsFile gFile; // global so we can keep it open while transfering.

+ 12 - 19
src/ZuluSCSI.cpp

@@ -400,11 +400,12 @@ bool findHDDImages()
       bool is_mo = (tolower(name[0]) == 'm' && tolower(name[1]) == 'o');
       bool is_re = (tolower(name[0]) == 'r' && tolower(name[1]) == 'e');
       bool is_tp = (tolower(name[0]) == 't' && tolower(name[1]) == 'p');
+      bool is_zp = (tolower(name[0]) == 'z' && tolower(name[1]) == 'p');
 #ifdef ZULUSCSI_NETWORK
       bool is_ne = (tolower(name[0]) == 'n' && tolower(name[1]) == 'e');
 #endif // ZULUSCSI_NETWORK
 
-      if (is_hd || is_cd || is_fd || is_mo || is_re || is_tp
+      if (is_hd || is_cd || is_fd || is_mo || is_re || is_tp || is_zp
 #ifdef ZULUSCSI_NETWORK
         || is_ne
 #endif // ZULUSCSI_NETWORK
@@ -424,13 +425,6 @@ bool findHDDImages()
         // 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;
-        int blk = 512;
-
-        if (is_cd)
-        {
-          // Use 2048 as the default sector size for CD-ROMs
-          blk = DEFAULT_BLOCKSIZE_OPTICAL;
-        }
 
         // Parse SCSI device ID
         int file_name_length = strlen(name);
@@ -456,17 +450,7 @@ bool findHDDImages()
           }
         }
 
-        // Parse block size (HD00_NNNN)
-        const char *blksize = strchr(name, '_');
-        if (blksize)
-        {
-          int blktmp = strtoul(blksize + 1, NULL, 10);
-          if (8 <= blktmp && blktmp <= 64 * 1024)
-          {
-            blk = blktmp;
-            logmsg("-- Using custom block size, ",(int) blk," from filename: ", name);
-          }
-        }
+
 
         // Add the directory name to get the full file path
         char fullname[MAX_FILE_PATH * 2 + 2] = {0};
@@ -480,6 +464,14 @@ bool findHDDImages()
           logmsg("-- Ignoring ", fullname, ", SCSI ID ", id, " is already in use!");
           continue;
         }
+
+        // set the default block size now that we know the device type 
+        if (g_scsi_settings.getDevice(id)->blockSize == 0)
+        {
+          g_scsi_settings.getDevice(id)->blockSize = is_cd ?  DEFAULT_BLOCKSIZE_OPTICAL : DEFAULT_BLOCKSIZE;
+        }
+        int blk = getBlockSize(name, id);
+
 #ifdef ZULUSCSI_NETWORK
         if (is_ne && !platform_network_supported())
         {
@@ -498,6 +490,7 @@ bool findHDDImages()
 #endif // ZULUSCSI_NETWORK
         if (is_re) type = S2S_CFG_REMOVABLE;
         if (is_tp) type = S2S_CFG_SEQUENTIAL;
+        if (is_zp) type = S2S_CFG_ZIP100;
 
         g_scsi_settings.initDevice(id & 7, type);
         // Open the image file

+ 2 - 48
src/ZuluSCSI_cdrom.cpp

@@ -1190,7 +1190,7 @@ void cdromPerformEject(image_config_t &img)
         dbgmsg("------ CDROM open tray on ID ", (int)target);
         img.ejected = true;
         img.cdrom_events = 3; // Media removal
-        cdromSwitchNextImage(img); // Switch media for next time
+        switchNextImage(img); // Switch media for next time
     }
     else
     {
@@ -1208,7 +1208,7 @@ void cdromReinsertFirstImage(image_config_t &img)
         dbgmsg("---- Restarting from first CD-ROM image for ID ", (int)target);
         img.image_index = -1;
         img.current_image[0] = '\0';
-        cdromSwitchNextImage(img);
+        switchNextImage(img);
     }
     else if (img.ejected)
     {
@@ -1217,52 +1217,6 @@ void cdromReinsertFirstImage(image_config_t &img)
     }
 }
 
-// Check if we have multiple CD-ROM images to cycle when drive is ejected.
-bool cdromSwitchNextImage(image_config_t &img, const char* next_filename)
-{
-    // Check if we have a next image to load, so that drive is closed next time the host asks.
-    
-    int target_idx = img.scsiId & 7;
-    char filename[MAX_FILE_PATH];
-    if (next_filename == nullptr)
-    {
-        scsiDiskGetNextImageName(img, filename, sizeof(filename));
-    }
-    else
-    {
-        strncpy(filename, next_filename, MAX_FILE_PATH);
-    }
-
-#ifdef ENABLE_AUDIO_OUTPUT
-    // if in progress for this device, terminate audio playback immediately (Annex C)
-    audio_stop(target_idx);
-    // Reset position tracking for the new image
-    audio_get_status_code(target_idx); // trash audio status code
-#endif
-
-    if (filename[0] != '\0')
-    {
-        logmsg("Switching to next CD-ROM image for ", target_idx, ": ", filename);
-        img.file.close();
-        bool status = scsiDiskOpenHDDImage(target_idx, filename, 0, 2048, S2S_CFG_OPTICAL);
-
-        if (status)
-        {
-            if (next_filename != nullptr)
-            {
-                // present the drive as ejected until the host queries it again,
-                // to make sure host properly detects the media change
-                img.ejected = true;
-                img.reinsert_after_eject = true;
-                img.cdrom_events = 2; // New Media
-            }
-            return true;
-        }
-    }
-
-    return false;
-}
-
 static void doGetEventStatusNotification(bool immed)
 {
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;

+ 0 - 3
src/ZuluSCSI_cdrom.h

@@ -21,9 +21,6 @@ void cdromPerformEject(image_config_t &img);
 // Reinsert ejected CD-ROM and restart from first image
 void cdromReinsertFirstImage(image_config_t &img);
 
-// Switch to next CD-ROM image if multiple have been configured
-bool cdromSwitchNextImage(image_config_t &img,  const char* next_filename = nullptr);
-
 // Check if the currently loaded cue sheet for the image can be parsed
 // and print warnings about unsupported track types
 bool cdromValidateCueSheet(image_config_t &img);

+ 15 - 0
src/ZuluSCSI_config.h

@@ -31,6 +31,8 @@
 #define FW_VER_NUM      "24.05.17"
 #define FW_VER_SUFFIX   "release"
 #define ZULU_FW_VERSION FW_VER_NUM "-" FW_VER_SUFFIX
+#define INQUIRY_NAME  PLATFORM_NAME " v" ZULU_FW_VERSION
+#define TOOLBOX_API 0
 
 // Configuration and log file paths
 #define CONFIGFILE  "zuluscsi.ini"
@@ -83,6 +85,9 @@
 #define DRIVEINFO_NETWORK   {"Dayna",    "SCSI/Link",       "2.0f", ""}
 #define DRIVEINFO_TAPE      {"ZULUSCSI", "TAPE",      PLATFORM_REVISION, ""}
 
+// Default block size
+#define DEFAULT_BLOCKSIZE 512
+
 // Default optical drive blocksize
 #define DEFAULT_BLOCKSIZE_OPTICAL 2048
 
@@ -95,6 +100,11 @@
 #define APPLE_DRIVEINFO_NETWORK   {"Dayna",    "SCSI/Link",       "2.0f", ""}
 #define APPLE_DRIVEINFO_TAPE      {"ZULUSCSI", "APPLE_TAPE",        PLATFORM_REVISION, ""}
 
+// Default Iomega ZIP drive information
+#define IOMEGA_DRIVEINFO_ZIP100     {"IOMEGA", "ZIP 100", "D.13", ""}
+#define IOMEGA_DRIVEINFO_ZIP250     {"IOMEGA", "ZIP 250", "42.S", ""}
+#define IOMEGA_DRIVEINFO_JAZ        {"iomega", "jaz", "", ""}
+
 // Default delay for SCSI phases.
 // Can be adjusted in ini file
 #define DEFAULT_SCSI_DELAY_US 10
@@ -108,3 +118,8 @@
 // Masks for buttons
 #define EJECT_BTN_MASK (1|2)
 #define USER_BTN_MASK  (4)
+
+
+// Zip disk  media sizes
+#define ZIP100_DISK_SIZE    100663296 // bytes
+#define ZIP250_DISK_SIZE    250640384 // bytes

+ 139 - 0
src/ZuluSCSI_disk.cpp

@@ -354,6 +354,15 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_lun, in
             logmsg("---- Configuring as tape drive");
             img.deviceType = S2S_CFG_SEQUENTIAL;
         }
+                else if (type == S2S_CFG_ZIP100)
+        {
+            logmsg("---- Configuration as Iomega Zip100");
+            img.deviceType = S2S_CFG_ZIP100;
+            if(img.file.size() != ZIP100_DISK_SIZE)
+            {
+                logmsg("---- Zip 100 disk (", (int)img.file.size(), " bytes) is not exactly ", ZIP100_DISK_SIZE, " bytes, may not work correctly");
+            }
+        }
 
         quirksCheck(&img);
 
@@ -531,6 +540,11 @@ static void scsiDiskSetConfig(int target_idx)
         strcpy(tmp, "FD0");
         tmp[2] += target_idx;
         scsiDiskCheckDir(tmp, target_idx, &img, S2S_CFG_FLOPPY_14MB, "floppy");
+
+        strcpy(tmp, "ZP0");
+        tmp[2] += target_idx;
+        scsiDiskCheckDir(tmp, target_idx, &img, S2S_CFG_ZIP100, "Iomega Zip 100");
+
     }
     g_scsi_settings.initDevice(target_idx, (S2S_CFG_TYPE)img.deviceType);
 }
@@ -661,6 +675,9 @@ int scsiDiskGetNextImageName(image_config_t &img, char *buf, size_t buflen)
                 case S2S_CFG_FLOPPY_14MB:
                     strcpy(dirname, "FD0");
                 break;
+                case S2S_CFG_ZIP100:
+                    strcpy(dirname, "ZP0");
+                break;
                 default:
                     dbgmsg("No matching device type for default directory found");
                     return 0;
@@ -746,6 +763,76 @@ void scsiDiskLoadConfig(int target_idx)
     }
 }
 
+uint32_t getBlockSize(char *filename, uint8_t scsi_id)
+{
+    // Parse block size (HD00_NNNN)
+    uint32_t block_size = g_scsi_settings.getDevice(scsi_id)->blockSize;
+    const char *blksizestr = strchr(filename, '_');
+    if (blksizestr)
+    {
+        int blktmp = strtoul(blksizestr + 1, NULL, 10);
+        if (8 <= blktmp && blktmp <= 64 * 1024)
+        {
+        block_size = blktmp;
+        logmsg("-- Using custom block size, ",(int) block_size," from filename: ", filename);
+        }
+    }
+    return block_size;
+}
+// Check if we have multiple drive images to cycle when drive is ejected.
+bool switchNextImage(image_config_t &img, const char* next_filename)
+{
+    // Check if we have a next image to load, so that drive is closed next time the host asks.
+    
+    int target_idx = img.scsiId & 7;
+    char filename[MAX_FILE_PATH];
+    if (next_filename == nullptr)
+    {
+        scsiDiskGetNextImageName(img, filename, sizeof(filename));
+    }
+    else
+    {
+        strncpy(filename, next_filename, MAX_FILE_PATH);
+    }
+
+#ifdef ENABLE_AUDIO_OUTPUT
+    // if in progress for this device, terminate audio playback immediately (Annex C)
+    audio_stop(target_idx);
+    // Reset position tracking for the new image
+    audio_get_status_code(target_idx); // trash audio status code
+#endif
+
+    if (filename[0] != '\0')
+    {
+        logmsg("Switching to next image for id ", target_idx, ": ", filename);
+        img.file.close();
+
+        // set default blocksize for CDs
+        int block_size = getBlockSize(filename, target_idx);
+        bool status = scsiDiskOpenHDDImage(target_idx, filename, 0, block_size, (S2S_CFG_TYPE) img.deviceType);
+
+        if (status)
+        {
+            if (next_filename != nullptr && img.deviceType == S2S_CFG_OPTICAL)
+            {
+                // present the drive as ejected until the host queries it again,
+                // to make sure host properly detects the media change
+                img.ejected = true;
+                img.reinsert_after_eject = true;
+                img.cdrom_events = 2; // New Media
+            }
+            return true;
+        }
+    }
+    else
+    {
+        logmsg("Switch to next image failed because target filename was empty.");
+    }
+
+    return false;
+}
+
+
 bool scsiDiskCheckAnyImagesConfigured()
 {
     for (int i = 0; i < S2S_MAX_TARGETS; i++)
@@ -1226,6 +1313,43 @@ static void doSeek(uint32_t lba)
     }
 }
 
+
+/***********************/
+/* Start/stop commands */
+/***********************/
+static void doCloseTray(image_config_t &img)
+{
+    if (img.ejected)
+    {
+        uint8_t target = img.scsiId & 7;
+        dbgmsg("------ Device close tray on ID ", (int)target);
+        img.ejected = false;
+
+        if (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_UNIT_ATTENTION)
+        {
+            dbgmsg("------ Posting UNIT ATTENTION after medium change");
+            scsiDev.targets[target].unitAttention = NOT_READY_TO_READY_TRANSITION_MEDIUM_MAY_HAVE_CHANGED;
+        }
+    }
+}
+
+ 
+// Eject and switch image
+static void doPerformEject(image_config_t &img)
+{
+    uint8_t target = img.scsiId & 7;
+    if (!img.ejected)
+    {
+        dbgmsg("------ Device open tray on ID ", (int)target);
+        img.ejected = true;
+        switchNextImage(img); // Switch media for next time
+    }
+    else
+    {
+        doCloseTray(img);
+    }
+}
+
 /********************************************/
 /* Transfer state for read / write commands */
 /********************************************/
@@ -1768,6 +1892,21 @@ int scsiDiskCommand()
         {
             scsiDev.target->started = 0;
         }
+
+        if ((scsiDev.cdb[4] & 2))
+        {
+            // Device load & eject
+            int start = scsiDev.cdb[4] & 1;
+            if (start)
+            {
+                doCloseTray(img);
+            }
+            else
+            {
+                // Eject and switch image
+                doPerformEject(img);
+            }
+        }
     }
     else if (likely(command == 0x08))
     {

+ 7 - 0
src/ZuluSCSI_disk.h

@@ -106,6 +106,9 @@ void scsiDiskResetImages();
 // Close any files opened from SD card (prepare for remounting SD)
 void scsiDiskCloseSDCardImages();
 
+// Get blocksize from filename or use device setting in ini file
+uint32_t getBlockSize(char *filename, uint8_t scsi_id);
+
 bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_lun, int blocksize, S2S_CFG_TYPE type = S2S_CFG_FIXED);
 void scsiDiskLoadConfig(int target_idx);
 
@@ -143,3 +146,7 @@ void scsiDiskStartWrite(uint32_t lba, uint32_t blocks);
 
 // Returns true if there is at least one network device active
 bool scsiDiskCheckAnyNetworkDevicesConfigured();
+
+
+// Switch to next Drive image if multiple have been configured
+bool switchNextImage(image_config_t &img, const char* next_filename = nullptr);

+ 1 - 0
src/ZuluSCSI_log_trace.cpp

@@ -46,6 +46,7 @@ static const char *getCommandName(uint8_t cmd)
         case 0x03: return "RequestSense";
         case 0x04: return "FormatUnit";
         case 0x05: return "ReadBlockLimits";
+        case 0x06: return "IomegaVendroCommand";
         case 0x08: return "Read6";
         case 0x0A: return "Write6";
         case 0x0B: return "Seek6";

+ 8 - 0
src/ZuluSCSI_settings.cpp

@@ -120,6 +120,8 @@ void ZuluSCSISettings::setDefaultDriveInfo(uint8_t scsiId, const char *presetNam
     static const char *apl_driveinfo_network[4]   = APPLE_DRIVEINFO_NETWORK;
     static const char *apl_driveinfo_tape[4]      = APPLE_DRIVEINFO_TAPE;
 
+    static const char *iomega_driveinfo_removeable[4] = IOMEGA_DRIVEINFO_ZIP100;
+    
     const char **driveinfo = NULL;
     bool known_preset = false;
     scsi_system_settings_t& cfgSys = m_sys;
@@ -175,6 +177,7 @@ void ZuluSCSISettings::setDefaultDriveInfo(uint8_t scsiId, const char *presetNam
                 case S2S_CFG_MO:            driveinfo = apl_driveinfo_magopt; break;
                 case S2S_CFG_NETWORK:       driveinfo = apl_driveinfo_network; break;
                 case S2S_CFG_SEQUENTIAL:    driveinfo = apl_driveinfo_tape; break;
+                case S2S_CFG_ZIP100:        driveinfo = iomega_driveinfo_removeable; break;
                 default:                    driveinfo = apl_driveinfo_fixed; break;
             }
         }
@@ -190,6 +193,7 @@ void ZuluSCSISettings::setDefaultDriveInfo(uint8_t scsiId, const char *presetNam
                 case S2S_CFG_MO:            driveinfo = driveinfo_magopt; break;
                 case S2S_CFG_NETWORK:       driveinfo = driveinfo_network; break;
                 case S2S_CFG_SEQUENTIAL:    driveinfo = driveinfo_tape; break;
+                case S2S_CFG_ZIP100:        driveinfo = iomega_driveinfo_removeable; break;
                 default:                    driveinfo = driveinfo_fixed; break;
             }
         }
@@ -228,6 +232,8 @@ static void readIniSCSIDeviceSetting(scsi_device_settings_t &cfg, const char *se
 
     cfg.vendorExtensions = ini_getl(section, "VendorExtensions", cfg.vendorExtensions, CONFIGFILE);
 
+    cfg.blockSize = ini_getl(section, "BlockSize", cfg.blockSize, CONFIGFILE);
+
     char tmp[32];
     ini_gets(section, "Vendor", "", tmp, sizeof(tmp), CONFIGFILE);
     if (tmp[0])
@@ -311,6 +317,8 @@ scsi_system_settings_t *ZuluSCSISettings::initSystem(const char *presetName)
 
     cfgDev.vendorExtensions = 0;
 
+    cfgDev.blockSize = 0;
+
     // System-specific defaults
 
     if (strequals(systemPresetName[SYS_PRESET_NONE], presetName))

+ 2 - 0
src/ZuluSCSI_settings.h

@@ -96,6 +96,8 @@ typedef struct __attribute__((__packed__)) scsi_device_settings_t
     uint32_t sectorSDEnd;
 
     uint32_t vendorExtensions;
+
+    uint32_t blockSize;
 } scsi_device_settings_t;
 
 

+ 1 - 0
zuluscsi.ini

@@ -60,6 +60,7 @@
 #EjectButton = 0 # Enable eject by button 1 or 2, or set 0 to disable
 #CDAVolume = 63 # Change CD Audio default volume. Maximum 255.
 #DisableMacSanityCheck = 0 # Disable sanity warnings for Mac disk drives. Default is 0 - enable checks
+#BlockSize = 0 # Set the drive's blocksize, defaults to 2048 for CDs and 512 for all other drives
 
 # SCSI DaynaPORT settings
 #WiFiSSID = "Wifi SSID string"