Explorar o código

Merge pull request #428 from ZuluSCSI/feature/iomega-zip

Add Zip drive support
Alex Perez hai 1 ano
pai
achega
28de8ea70a

+ 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
platformio.ini

@@ -211,7 +211,7 @@ build_flags =
     -DROMDRIVE_OFFSET=${env:ZuluSCSI_Pico_DaynaPORT.program_flash_allocation}
 ; These take a large portion of the SRAM and can be adjusted
     -DLOGBUFSIZE=8192
-    -DPREFETCH_BUFFER_SIZE=5632
+    -DPREFETCH_BUFFER_SIZE=4608
     -DSCSI2SD_BUFFER_SIZE=57344
 ; This controls the depth of NETWORK_PACKET_MAX_SIZE (1520 bytes)
 ; For example a queue size of 10 would be 10 x 1520 = 15200 bytes

+ 13 - 0
src/ImageBackingStore.cpp

@@ -332,3 +332,16 @@ uint64_t ImageBackingStore::position()
         return 0;
     }
 }
+
+size_t ImageBackingStore::getFilename(char* buf, size_t buflen)
+{
+    if (m_fsfile.isOpen())
+    {
+        size_t name_length = m_fsfile.getName(buf, buflen);
+        if (name_length + 1 > buflen)
+            return 0;
+        else
+            return name_length;
+    }
+    return 0;
+}

+ 2 - 0
src/ImageBackingStore.h

@@ -100,6 +100,8 @@ public:
     // Result is only valid for regular files, not raw or flash access
     uint64_t position();
 
+    size_t getFilename(char* buf, size_t buflen);
+
 protected:
     bool m_israw;
     bool m_isrom;

+ 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.

+ 87 - 24
src/ZuluSCSI.cpp

@@ -310,6 +310,22 @@ bool createImage(const char *cmd_filename, char imgname[MAX_FILE_PATH + 1])
   return true;
 }
 
+static bool typeIsRemovable(S2S_CFG_TYPE type)
+{
+  switch (type)
+  {
+  case S2S_CFG_OPTICAL:
+  case S2S_CFG_MO:
+  case S2S_CFG_FLOPPY_14MB:
+  case S2S_CFG_ZIP100:
+  case S2S_CFG_REMOVABLE:
+  case S2S_CFG_SEQUENTIAL:
+    return true;
+  default:
+    return false;
+  }
+}
+
 // Iterate over the root path in the SD card looking for candidate image files.
 bool findHDDImages()
 {
@@ -336,6 +352,9 @@ bool findHDDImages()
   bool imageReady;
   bool foundImage = false;
   int usedDefaultId = 0;
+  uint8_t removable_count = 0;
+  uint8_t eject_btn_set = 0;
+  uint8_t last_removable_device = 255;
   while (1)
   {
     if (!file.openNext(&root, O_READ))
@@ -393,18 +412,19 @@ bool findHDDImages()
           name[MAX_FILE_PATH] = '\0';
         }
       }
-
+      bool use_prefix = false;
       bool is_hd = (tolower(name[0]) == 'h' && tolower(name[1]) == 'd');
       bool is_cd = (tolower(name[0]) == 'c' && tolower(name[1]) == 'd');
       bool is_fd = (tolower(name[0]) == 'f' && tolower(name[1]) == 'd');
       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 +444,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);
@@ -440,6 +453,7 @@ bool findHDDImages()
           if(tmp_id > -1 && tmp_id < 8)
           {
             id = tmp_id; // If valid id, set it, else use default
+            use_prefix = true;
           }
           else
           {
@@ -456,18 +470,6 @@ 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};
         strncpy(fullname, imgdir, MAX_FILE_PATH);
@@ -480,6 +482,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 +508,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
@@ -518,7 +529,7 @@ bool findHDDImages()
               logmsg("---- Using device preset: ", g_scsi_settings.getDevicePresetName(id));
           }
 
-          imageReady = scsiDiskOpenHDDImage(id, fullname, lun, blk, type);
+          imageReady = scsiDiskOpenHDDImage(id, fullname, lun, blk, type, use_prefix);
           if(imageReady)
           {
             foundImage = true;
@@ -562,11 +573,63 @@ bool findHDDImages()
               ", BlockSize: ", (int)cfg->bytesPerSector,
               ", Type: ", (int)cfg->deviceType,
               ", Quirks: ", (int)cfg->quirks,
-              ", Size: ", capacity_kB, "kB");
-      };
+              ", Size: ", capacity_kB, "kB",
+              typeIsRemovable((S2S_CFG_TYPE)cfg->deviceType) ? ", Removable" : ""
+              );
+       }
+    }
+  }
+  // count the removable drives and drive with eject enabled
+  for (uint8_t id; id < S2S_MAX_TARGETS; id++)
+  {
+    const S2S_TargetCfg* cfg = s2s_getConfigByIndex(id);
+    if (cfg  && (cfg->scsiId & S2S_CFG_TARGET_ENABLED ))
+    {
+       if (typeIsRemovable((S2S_CFG_TYPE)cfg->deviceType))
+        {
+          removable_count++;
+          last_removable_device = id;
+          if ( getEjectButton(id) !=0 )
+          {
+            eject_btn_set++;
+          }
+        }
     }
+  } 
+
+  if (removable_count == 1)
+  {
+    // If there is a removable device
+    if (eject_btn_set == 1)
+      logmsg("Eject set to device with ID: ", last_removable_device);
+    else if (eject_btn_set == 0)
+    {
+      logmsg("Found 1 removable device, to set an eject button see EjectButton in the, '", CONFIGFILE,"', or the http://zuluscsi.com/manual");
+    } 
   }
+  else if (removable_count > 1)
+  {
+
+    if (removable_count >= eject_btn_set && eject_btn_set > 0)
+    {
+      if (eject_btn_set == removable_count)
+        logmsg("Eject set on all removable devices:");
+      else
+        logmsg("Eject set on the following SCSI IDs:");
 
+      for (uint8_t id = 0; id < S2S_MAX_TARGETS; id++)
+      {
+        if( getEjectButton(id) != 0)
+        {
+          logmsg("-- SCSI ID: ", (int)id, " type: ", (int) s2s_getConfigById(id)->deviceType, " button mask: ", getEjectButton(id));
+        }
+      }
+    }
+    else
+    {
+      logmsg("Multiple removable devices, to set an eject button see EjectButton in the, '", CONFIGFILE,"', or the http://zuluscsi.com/manual");
+    }
+  }
   return foundImage;
 }
 

+ 3 - 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;
@@ -1934,6 +1888,7 @@ extern "C" int scsiCDRomCommand()
     int commandHandled = 1;
 
     uint8_t command = scsiDev.cdb[0];
+    // Start/stop command
     if (command == 0x1B)
     {
 #if ENABLE_AUDIO_OUTPUT

+ 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.23"
 #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

+ 225 - 15
src/ZuluSCSI_disk.cpp

@@ -264,7 +264,7 @@ static void scsiDiskSetImageConfig(uint8_t target_idx)
     memcpy(img.serial, devCfg->serial, sizeof(img.serial));
 }
 
-bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_lun, int blocksize, S2S_CFG_TYPE type)
+bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_lun, int blocksize, S2S_CFG_TYPE type, bool use_prefix)
 {
     image_config_t &img = g_DiskImages[target_idx];
     img.cuesheetfile.close();
@@ -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);
 
@@ -398,7 +407,8 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_lun, in
                 logmsg("---- No CUE sheet found at ", cuesheetname, ", using as plain binary image");
             }
         }
-
+        img.use_prefix = use_prefix;
+        img.file.getFilename(img.current_image, sizeof(img.current_image));
         return true;
     }
     else
@@ -432,7 +442,7 @@ bool scsiDiskFilenameValid(const char* name)
     if (extension)
     {
         const char *ignore_exts[] = {
-            ".rom_loaded", ".cue", ".txt", ".rtf", ".md", ".nfo", ".pdf", ".doc",
+            ".rom_loaded", ".cue", ".txt", ".rtf", ".md", ".nfo", ".pdf", ".doc", ".ini",
             NULL
         };
         const char *archive_exts[] = {
@@ -531,10 +541,67 @@ 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);
 }
 
+// Compares the prefix of both files and the scsi ID
+// cd3-name.iso and CD3-otherfile.bin matches, zp3.img or cd4-name.iso would not
+static bool compare_prefix(const char* name, const char* compare)
+{
+    if (strlen(name) >= 3 && strlen(compare) >= 3)
+    {
+        if (tolower(name[0]) == tolower(compare[0])
+            && tolower(name[1]) == tolower(compare[1])
+            && tolower(name[2]) == tolower(compare[2])
+        )
+        return true;
+    }
+    return false;
+}
+
+/***********************/
+/* 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);
+    }
+}
+
+
 // Finds filename with the lowest lexical order _after_ the given filename in
 // the given folder. If there is no file after the given one, or if there is
 // no current file, this will return the lowest filename encountered.
@@ -583,6 +650,8 @@ static int findNextImageAfter(image_config_t &img,
             continue;
         }
 
+        if (img.use_prefix && !compare_prefix(filename, buf)) continue;
+
         // keep track of the first item to allow wrapping
         // without having to iterate again
         if (first_name[0] == '\0' || strcasecmp(buf, first_name) < 0)
@@ -591,6 +660,7 @@ static int findNextImageAfter(image_config_t &img,
         }
 
         // discard if no selected name, or if candidate is before (or is) selected
+        // or prefix searching is enabled and file doesn't contain current prefix
         if (filename[0] == '\0' || strcasecmp(buf, filename) <= 0) continue;
 
         // if we got this far and the candidate is either 1) not set, or 2) is a
@@ -633,6 +703,10 @@ int scsiDiskGetNextImageName(image_config_t &img, char *buf, size_t buflen)
     // sanity check: is provided buffer is long enough to store a filename?
     assert(buflen >= MAX_FILE_PATH);
 
+    // find the next filename
+    char nextname[MAX_FILE_PATH];
+    int nextlen;
+
     if (img.image_directory)
     {
         // image directory was found during startup
@@ -661,6 +735,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;
@@ -674,8 +751,7 @@ int scsiDiskGetNextImageName(image_config_t &img, char *buf, size_t buflen)
         }
 
         // find the next filename
-        char nextname[MAX_FILE_PATH];
-        int nextlen = findNextImageAfter(img, dirname, img.current_image, nextname, sizeof(nextname));
+        nextlen = findNextImageAfter(img, dirname, img.current_image, nextname, sizeof(nextname));
 
         if (nextlen == 0)
         {
@@ -696,6 +772,26 @@ int scsiDiskGetNextImageName(image_config_t &img, char *buf, size_t buflen)
             return dirlen + nextlen;
         }
     }
+    else if (img.use_prefix)
+    {
+        nextlen = findNextImageAfter(img, "/", img.current_image, nextname, sizeof(nextname));
+        if (nextlen == 0)
+        {
+            logmsg("Next file with the same prefix as ", img.current_image," not found for ID", target_idx);
+        }
+        else if (buflen < nextlen + 1)
+        {
+            logmsg("Next file exceeds, '",nextname, "' exceed allowed length");
+        }
+        else
+        {
+            // construct a return value
+            strncpy(buf, nextname, buflen);
+            return nextlen;
+        }
+        img.image_index = -1;
+        return 0;
+    }
     else
     {
         img.image_index++;
@@ -722,7 +818,7 @@ int scsiDiskGetNextImageName(image_config_t &img, char *buf, size_t buflen)
         }
         else
         {
-            // images are not defined in config
+
             img.image_index = -1;
             return 0;
         }
@@ -740,12 +836,99 @@ void scsiDiskLoadConfig(int target_idx)
     img.image_index = IMAGE_INDEX_MAX;
     if (scsiDiskGetNextImageName(img, filename, sizeof(filename)))
     {
-        int blocksize = (img.deviceType == S2S_CFG_OPTICAL) ? 2048 : 512;
+        // set the default block size now that we know the device type
+        if (g_scsi_settings.getDevice(target_idx)->blockSize == 0)
+        {
+          g_scsi_settings.getDevice(target_idx)->blockSize = img.deviceType == S2S_CFG_OPTICAL ?  DEFAULT_BLOCKSIZE_OPTICAL : DEFAULT_BLOCKSIZE;
+        }
+        int blocksize = getBlockSize(filename, target_idx);
         logmsg("-- Opening '", filename, "' for id: ", target_idx);
-        scsiDiskOpenHDDImage(target_idx, filename, 0, blocksize, (S2S_CFG_TYPE) img.deviceType);
+        scsiDiskOpenHDDImage(target_idx, filename, 0, blocksize, (S2S_CFG_TYPE) img.deviceType, img.use_prefix);
     }
 }
 
+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;
+}
+
+uint8_t getEjectButton(uint8_t idx)
+{
+    return g_DiskImages[idx].ejectButton;
+}
+
+void setEjectButton(uint8_t idx, int8_t eject_button)
+{
+    g_DiskImages[idx].ejectButton = eject_button;
+    g_scsi_settings.getDevice(idx)->ejectButton = eject_button;
+}
+
+// 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, img.use_prefix);
+
+        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;
+                if (img.deviceType == S2S_CFG_OPTICAL) 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++)
@@ -771,7 +954,7 @@ static void diskEjectAction(uint8_t buttonId)
     for (uint8_t i = 0; i < S2S_MAX_TARGETS; i++)
     {
         image_config_t &img = g_DiskImages[i];
-        if (img.ejectButton == buttonId)
+        if (img.ejectButton & buttonId)
         {
             if (img.deviceType == S2S_CFG_OPTICAL)
             {
@@ -779,6 +962,16 @@ static void diskEjectAction(uint8_t buttonId)
                 logmsg("Eject button ", (int)buttonId, " pressed, passing to CD drive SCSI", (int)i);
                 cdromPerformEject(img);
             }
+            else if (img.deviceType == S2S_CFG_ZIP100 
+                    || img.deviceType == S2S_CFG_REMOVABLE 
+                    || img.deviceType == S2S_CFG_FLOPPY_14MB 
+                    || img.deviceType == S2S_CFG_MO
+                    || img.deviceType == S2S_CFG_SEQUENTIAL)
+            {
+                found = true;
+                logmsg("Eject button ", (int)buttonId, " pressed, passing to SCSI device", (int)i);
+                doPerformEject(img);
+            }
         }
     }
 
@@ -813,7 +1006,7 @@ uint8_t diskEjectButtonUpdate(bool immediate)
             uint8_t mask = 1;
             for (uint8_t i = 0; i < 8; i++)
             {
-                if (ejectors & mask) diskEjectAction(i + 1);
+                if (ejectors & mask) diskEjectAction(ejectors & mask);
                 mask = mask << 1;
             }
         }
@@ -1173,7 +1366,9 @@ int doTestUnitReady()
         {
             // We are now reporting to host that the drive is open.
             // Simulate a "close" for next time the host polls.
-            cdromCloseTray(img);
+            if (img.deviceType == S2S_CFG_OPTICAL) cdromCloseTray(img);
+            else doCloseTray(img);
+
         }
     }
     else if (unlikely(!(blockDev.state & DISK_PRESENT)))
@@ -1226,6 +1421,7 @@ static void doSeek(uint32_t lba)
     }
 }
 
+
 /********************************************/
 /* Transfer state for read / write commands */
 /********************************************/
@@ -1759,8 +1955,20 @@ int scsiDiskCommand()
         // Enable or disable media access operations.
         //int immed = scsiDev.cdb[1] & 1;
         int start = scsiDev.cdb[4] & 1;
-
-        if (start)
+        if ((scsiDev.cdb[4] & 2) || img.deviceType == S2S_CFG_ZIP100)
+        {
+            // Device load & eject
+            if (start)
+            {
+                doCloseTray(img);
+            }
+            else
+            {
+                // Eject and switch image
+                doPerformEject(img);
+            }
+        }
+        else if (start)
         {
             scsiDev.target->started = 1;
         }
@@ -1768,6 +1976,7 @@ int scsiDiskCommand()
         {
             scsiDev.target->started = 0;
         }
+
     }
     else if (likely(command == 0x08))
     {
@@ -1985,9 +2194,10 @@ void scsiDiskPoll()
         if (command == 0x12)
         {
             image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
-            if (img.deviceType == S2S_CFG_OPTICAL && img.reinsert_on_inquiry)
+            if (img.reinsert_on_inquiry)
             {
-                cdromCloseTray(img);
+                if (img.deviceType == S2S_CFG_OPTICAL) cdromCloseTray(img);
+                else doCloseTray(img);
             }
         }
     }

+ 16 - 1
src/ZuluSCSI_disk.h

@@ -63,6 +63,10 @@ struct image_config_t: public S2S_TargetCfg
 
     // True if there is a subdirectory of images for this target
     bool image_directory;
+
+    // True if the device type was determined by the drive prefix
+    bool use_prefix;
+
     // the name of the currently mounted image in a dynamic image directory
     char current_image[MAX_FILE_PATH];
 
@@ -106,7 +110,14 @@ void scsiDiskResetImages();
 // Close any files opened from SD card (prepare for remounting SD)
 void scsiDiskCloseSDCardImages();
 
-bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_lun, int blocksize, S2S_CFG_TYPE type = S2S_CFG_FIXED);
+// Get blocksize from filename or use device setting in ini file
+uint32_t getBlockSize(char *filename, uint8_t scsi_id);
+
+// Get and set the eject button bit flags
+uint8_t getEjectButton(uint8_t idx);
+void    setEjectButton(uint8_t idx, int8_t eject_button);
+
+bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_lun, int blocksize, S2S_CFG_TYPE type = S2S_CFG_FIXED, bool use_prefix = false);
 void scsiDiskLoadConfig(int target_idx);
 
 // Checks if a filename extension is appropriate for further processing as a disk image.
@@ -143,3 +154,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 "IomegaVendorCommand";
         case 0x08: return "Read6";
         case 0x0A: return "Write6";
         case 0x0B: return "Seek6";

+ 9 - 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])
@@ -260,6 +266,7 @@ static void readIniSCSIDeviceSetting(scsi_device_settings_t &cfg, const char *se
         memset(cfg.serial, 0, sizeof(cfg.serial));
         strncpy(cfg.serial, tmp, sizeof(cfg.serial));
     }
+
 }
 
 scsi_system_settings_t *ZuluSCSISettings::initSystem(const char *presetName)
@@ -311,6 +318,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;
 
 

+ 5 - 3
zuluscsi.ini

@@ -36,7 +36,7 @@
 #InitiatorMaxRetry = 5 #  number of retries on failed reads 0-255, default is 5
 #InitiatorImageHandling = 0 # 0: skip exisitng images, 1: create new image with incrementing suffix, 2: overwrite exising image
 
-#EnableCDAudio = 0 # Enable CD audio - an external I2S DAC on the v1.2 is required
+#EnableCDAudio = 0 # 1: Enable CD audio - an external I2S DAC on the v1.2 is required
 
 # Settings that can be specified either per-device or for all devices.
 
@@ -47,7 +47,8 @@
 #Product = "FIREBALL1"
 #Version = "1.0"
 #Serial = "0123456789ABCDEF"
-#Type = 0     # 0: Fixed, 1: Removable, 2: Optical, 3: Floppy, 4: Mag-optical, 5: Tape, 6: Network
+#Type = 0     # 0: Fixed, 1: Removable, 2: Optical, 3: Floppy, 4: Mag-optical, 
+              # 5: Tape,  6: Network,   7: Zip100
 #VendorExtensions = 0 # Bit flags for specific extensions per device type
 #  CDROM - 1: Plextor's d8h vendor command
 #TypeModifier = 0  # Affects only INQUIRY response
@@ -60,6 +61,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"
@@ -74,7 +76,7 @@
 #Product = "CD-ROM Drive"
 #Type = 2
 
-# If IMG0..IMG9 are specified, they are cycled after each CD eject command.
+# If IMG0..IMG9 are specified, they are cycled after each eject command.
 #IMG0 = FirstCD.iso
 #IMG1 = SecondCD.bin