Răsfoiți Sursa

Merge pull request #406 from ZuluSCSI/feature/toolbox

Implement the SCSI side of a SCSI control and data transfer mechanism
Alex Perez 1 an în urmă
părinte
comite
e0db115ce4

+ 1 - 0
lib/SCSI2SD/src/firmware/disk.h

@@ -54,5 +54,6 @@ void scsiDiskInit(void);
 void scsiDiskReset(void);
 void scsiDiskPoll(void);
 int scsiDiskCommand(void);
+int doTestUnitReady();
 
 #endif

+ 17 - 0
lib/SCSI2SD/src/firmware/mode.c

@@ -23,6 +23,7 @@
 #include "disk.h"
 #include "inquiry.h"
 #include "ZuluSCSI_mode.h"
+#include "toolbox.h"
 
 #include <string.h>
 
@@ -247,6 +248,15 @@ static const uint8_t AppleVendorPage[] =
 'A','P','P','L','E',' ','C','O','M','P','U','T','E','R',',',' ','I','N','C',' ',' ',' '
 };
 
+static const uint8_t ToolboxVendorPage[] =
+{
+0x31, // Page code
+42,   // Page length
+'Z','u','l','u','S','C','S','I',' ','i','s',' ','G','P','L','v','3',' ','F','T','W',
+' ','R','a','b','b','i','t','H','o','l','e','C','o','m','p','u','t','i','n','g',0x00
+};
+
+
 static void pageIn(int pc, int dataIdx, const uint8_t* pageData, int pageLen)
 {
 	memcpy(&scsiDev.data[dataIdx], pageData, pageLen);
@@ -535,6 +545,13 @@ static void doModeSense(
 		idx += sizeof(AppleVendorPage);
 	}
 
+	if (scsiToolboxEnabled() && (pageCode == 0x31 || pageCode == 0x3F))
+	{
+		pageFound = 1;
+		pageIn(pc, idx, ToolboxVendorPage, sizeof(ToolboxVendorPage));
+		idx += sizeof(ToolboxVendorPage);
+	}
+
 	if (pageCode == 0x38) // Don't send unless requested
 	{
 		pageFound = 1;

+ 23 - 18
lib/SCSI2SD/src/firmware/scsi.c

@@ -34,8 +34,8 @@
 #include "tape.h"
 #include "mo.h"
 #include "vendor.h"
-
 #include <string.h>
+#include "toolbox.h"
 
 // Global SCSI device state.
 ScsiDevice scsiDev S2S_DMA_ALIGN;
@@ -293,7 +293,7 @@ static void process_DataOut()
 	}
 }
 
-static const uint8_t CmdGroupBytes[8] = {6, 10, 10, 6, 6, 12, 6, 6};
+static const uint8_t CmdGroupBytes[8] = {6, 10, 10, 6, 16, 12, 6, 6};
 static void process_Command()
 {
 	int group;
@@ -309,21 +309,8 @@ static void process_Command()
 
 	group = scsiDev.cdb[0] >> 5;
 	scsiDev.cdbLen = CmdGroupBytes[group];
-
-	if (scsiDev.target->cfg->deviceType == S2S_CFG_OPTICAL)
-	{
-		if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_APPLE && (command == 0xD8 || command == 0xD9))
-		{
-			scsiDev.cdbLen =  12;
-		}
-		// Plextor CD-ROM vendor extensions 0xD8
-		if (unlikely(scsiDev.target->cfg->vendorExtensions & VENDOR_EXTENSION_OPTICAL_PLEXTOR))
-
-			if (command == 0xD8)
-			{
-				scsiDev.cdbLen =  12;
-			}
-	}
+	scsiVendorCommandSetLen(scsiDev.cdb[0], &scsiDev.cdbLen);
+	
 	if (parityError &&
 		(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
 	{
@@ -645,7 +632,25 @@ static void process_Command()
 	{
 		scsiReadBuffer();
 	}
-	else if (!scsiModeCommand() && !scsiVendorCommand())
+	else if (scsiModeCommand())
+	{
+		// handled
+	}
+	else if (scsiVendorCommand())
+	{
+		// handled
+	}
+	else if (unlikely(command == 0x00))
+    {
+        // TEST UNIT READY
+        doTestUnitReady();
+    }
+    else if (unlikely(!doTestUnitReady()))
+    {
+		// This should be last as it can override other commands 
+        // Status and sense codes already set by doTestUnitReady
+    }
+	else
 	{
 		scsiDev.target->sense.code = ILLEGAL_REQUEST;
 		scsiDev.target->sense.asc = INVALID_COMMAND_OPERATION_CODE;

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

@@ -146,7 +146,7 @@ typedef struct
 	int savedDataPtr; // Index into data, initially 0.
 	int dataLen;
 
-	uint8_t cdb[12]; // command descriptor block
+	uint8_t cdb[16]; // command descriptor block
 	uint8_t cdbLen; // 6, 10, or 12 byte message.
 	int8_t lun; // Target lun, set by IDENTIFY message.
 	uint8_t discPriv; // Disconnect priviledge.

+ 27 - 0
lib/SCSI2SD/src/firmware/toolbox.h

@@ -0,0 +1,27 @@
+/** 
+ * Copyright (C) 2023 Eric Helgeson
+ * Copyright (C) 2024 Rabbit Hole Computing
+ * 
+ * This file is originally part of BlueSCSI adopted for ZuluSCSI
+ * 
+ * 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/>.
+**/
+
+#ifndef S2S_TOOLBOX_H
+#define S2S_TOOLBOX_H
+#include <inttypes.h>
+int8_t scsiToolboxEnabled(void);
+int scsiToolboxCommand(void);
+
+#endif

+ 31 - 0
lib/SCSI2SD/src/firmware/vendor.c

@@ -18,6 +18,7 @@
 #include "scsi.h"
 #include "vendor.h"
 #include "diagnostic.h"
+#include "toolbox.h"
 
 // Callback after the DATA OUT phase is complete.
 static void doAssignDiskParameters(void)
@@ -95,6 +96,10 @@ int scsiVendorCommand()
 	  // XEBEC S1410 controller
 	  // Stub, return success
 	}   	
+	else if (scsiToolboxEnabled() && scsiToolboxCommand())
+	{
+		// already handled
+	}
 	else
 	{
 		commandHandled = 0;
@@ -103,3 +108,29 @@ int scsiVendorCommand()
 	return commandHandled;
 }
 
+void scsiVendorCommandSetLen(uint8_t command, uint8_t* command_length)
+{
+	if (scsiDev.target->cfg->deviceType == S2S_CFG_OPTICAL)
+	{
+		// Apple CD-ROM with CD audio over the SCSI bus
+		if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_APPLE && (command == 0xD8 || command == 0xD9))
+		{
+			scsiDev.cdbLen =  12;
+		}
+		// Plextor CD-ROM vendor extensions 0xD8
+		if (unlikely(scsiDev.target->cfg->vendorExtensions & VENDOR_EXTENSION_OPTICAL_PLEXTOR) && command == 0xD8)
+		{
+			scsiDev.cdbLen =  12;
+		}
+	}
+
+	if (scsiToolboxEnabled())
+	{
+		// Conflicts with Apple CD-ROM audio over SCSI bus and Plextor CD-ROM D8 extension
+		// Will override those commands if enabled
+		if (0xD0 <= command && command <= 0xDA)
+		{
+			*command_length = 10;
+		}
+	}
+}

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

@@ -18,5 +18,6 @@
 #define S2S_VENDOR_H
 
 int scsiVendorCommand(void);
-
+// Set the command length for applicable vendor commands
+void scsiVendorCommandSetLen(uint8_t command, uint8_t* command_length);
 #endif

+ 1 - 6
lib/ZuluSCSI_platform_RP2040/scsi_accel_target.cpp

@@ -902,12 +902,7 @@ void scsi_accel_rp2040_init()
         dma_channel_claim(SCSI_DMA_CH_D);
         g_channels_claimed = true;
     }
-
-#ifndef ZULUSCSI_NETWORK
-    // Load PIO programs
-    pio_clear_instruction_memory(SCSI_DMA_PIO);
-#endif // ZULUSCSI_NETWORK
-
+    
     // Parity lookup generator
     g_scsi_dma.pio_offset_parity = pio_add_program(SCSI_DMA_PIO, &scsi_parity_program);
     g_scsi_dma.pio_cfg_parity = scsi_parity_program_get_default_config(g_scsi_dma.pio_offset_parity);

+ 1 - 0
platformio.ini

@@ -109,6 +109,7 @@ lib_deps =
     ZuluSCSI_platform_RP2040
     SCSI2SD
     CUEParser
+upload_protocol = cmsis-dap
 debug_tool = cmsis-dap
 debug_build_flags =
     -O2 -ggdb -g3

+ 412 - 0
src/Toolbox.cpp

@@ -0,0 +1,412 @@
+/** 
+ * Copyright (C) 2023 Eric Helgeson
+ * Copyright (C) 2024 Rabbit Hole Computing
+ * This file is originally part of BlueSCSI adopted for ZuluSCSI
+ * 
+ * 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 "Toolbox.h"
+#include "ZuluSCSI_disk.h"
+#include "ZuluSCSI_cdrom.h"
+#include "ZuluSCSI_log.h"
+#include <minIni.h>
+#include <SdFat.h>
+extern "C" {
+#include <toolbox.h>
+#include <scsi2sd_time.h>
+#include <sd.h>
+#include <mode.h>
+}
+
+
+extern "C" int8_t scsiToolboxEnabled()
+{
+    static int8_t enabled = -1;
+    if (enabled == -1)
+    {
+        enabled = ini_getbool("SCSI", "EnableToolbox", 0, CONFIGFILE);
+    }
+    return enabled == 1;
+}
+
+
+static bool toolboxFilenameValid(const char* name, bool isCD = false)
+{
+    if(strlen(name) == 0)
+    {
+        dbgmsg("toolbox: Ignoring filename empty file name");
+        return false;
+    }
+    if (isCD)
+    {
+      return scsiDiskFilenameValid(name);
+    }
+    return true;
+}
+
+static void doCountFiles(const char * dir_name, bool isCD = false)
+{
+    FsFile dir;
+    FsFile file;
+    char name[MAX_FILE_PATH] = {0};
+    dir.open(dir_name);
+    dir.rewindDirectory();
+    uint8_t file_count = 0;
+    while (file.openNext(&dir, O_RDONLY))
+    {
+        if(file.getError() > 0)
+        {
+            file.close();
+            break;
+        }
+        bool isDir = file.isDirectory();
+        file.getName(name, MAX_FILE_PATH);
+        file.close();
+        if (isCD && isDir)
+            continue;
+        // only count valid files.
+        if(toolboxFilenameValid(name, isCD))
+        {
+            file_count = file_count + 1;
+            if(file_count > 100) {
+                scsiDev.status = CHECK_CONDITION;
+                scsiDev.target->sense.code = ILLEGAL_REQUEST;
+                scsiDev.target->sense.asc = OPEN_RETRO_SCSI_TOO_MANY_FILES;
+                scsiDev.phase = STATUS;
+                dir.close();
+                return;
+            }
+        }
+  }
+  scsiDev.data[0] = file_count;
+  scsiDev.dataLen = sizeof(file_count);
+  scsiDev.phase = DATA_IN;
+}
+
+static void onListFiles(const char * dir_name, bool isCD = false) {
+    FsFile dir;
+    FsFile file;
+
+    memset(scsiDev.data, 0, 4096);
+    int ENTRY_SIZE = 40;
+    char name[MAX_FILE_PATH] = {0};
+    dir.open(dir_name);
+    dir.rewindDirectory();
+    uint8_t index = 0;
+    uint8_t file_entry[40] = {0};
+    while (file.openNext(&dir, O_RDONLY))
+    {   
+        uint8_t isDir = file.isDirectory() ? 0x00 : 0x01;
+        int len = file.getName(name, MAX_FILE_PATH);
+        if (len > MAX_MAC_PATH)
+            name[MAX_MAC_PATH] = 0x0;
+        uint64_t size = file.fileSize();
+        file.close();
+        if (!toolboxFilenameValid(name, isCD))
+            continue;
+        if (isCD && isDir == 0x00)
+            continue;
+        file_entry[0] = index;
+        file_entry[1] = isDir;
+        int c = 0; // Index of char in name[]
+
+        for(int i = 2; i < (MAX_MAC_PATH + 1 + 2); i++) {   // bytes 2 - 34
+            file_entry[i] = name[c++];
+        }
+        file_entry[35] = 0; //(size >> 32) & 0xff;
+        file_entry[36] = (size >> 24) & 0xff;
+        file_entry[37] = (size >> 16) & 0xff;
+        file_entry[38] = (size >> 8) & 0xff;
+        file_entry[39] = (size) & 0xff;
+        memcpy(&(scsiDev.data[ENTRY_SIZE * index]), file_entry, ENTRY_SIZE);
+        index = index + 1;
+    }
+    dir.close();
+
+    scsiDev.dataLen = 4096;
+    scsiDev.phase = DATA_IN;
+}
+
+static FsFile get_file_from_index(uint8_t index, const char * dir_name, bool isCD = false)
+{
+  FsFile dir;
+  FsFile file_test;
+  char name[MAX_FILE_PATH] = {0};
+
+  dir.open(dir_name);
+  dir.rewindDirectory(); // Back to the top
+  int count = 0;
+  while (file_test.openNext(&dir, O_RDONLY))
+  {
+    
+    // If error there is no next file to open.
+    if(file_test.getError() > 0) {
+      file_test.close();
+      break;
+    }
+    file_test.getName(name, MAX_FILE_PATH);
+    if (isCD && file_test.isDirectory())
+    {
+        file_test.close();
+        continue;
+    }
+    if(!toolboxFilenameValid(name, isCD))
+    {
+        file_test.close();
+        continue;
+    }
+    if (count == index)
+    {
+      dir.close();
+      return file_test;
+    }
+    else
+    {
+      file_test.close();
+    }
+    count++;
+  }
+  file_test.close();
+  dir.close();
+  return file_test;
+}
+
+// Devices that are active on this SCSI device.
+static void onListDevices()
+{
+  for (int i = 0; i < NUM_SCSIID; i++)
+  {
+    const S2S_TargetCfg* cfg = s2s_getConfigById(i);
+    if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))
+    {
+        scsiDev.data[i] = (int)cfg->deviceType; // 2 == cd
+    }
+    else
+    {
+        scsiDev.data[i] = 0xFF; // not enabled target.
+    }
+  }
+  scsiDev.dataLen = NUM_SCSIID;
+}
+
+static void onSetNextCD(const char * img_dir)
+{
+    char name[MAX_FILE_PATH] = {0};
+    char full_path[MAX_FILE_PATH * 2] = {0};
+    uint8_t file_index = scsiDev.cdb[1];
+    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+    FsFile next_cd = get_file_from_index(file_index, img_dir, true);
+    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);
+}
+
+FsFile gFile; // global so we can keep it open while transfering.
+void onGetFile10(char * dir_name) {
+    uint8_t index = scsiDev.cdb[1];
+
+    uint32_t offset = ((uint32_t)scsiDev.cdb[2] << 24) | ((uint32_t)scsiDev.cdb[3] << 16) | ((uint32_t)scsiDev.cdb[4] << 8) | scsiDev.cdb[5];
+
+    if (offset == 0) // first time, open the file.
+    {
+        gFile = get_file_from_index(index, dir_name);
+        if(!gFile.isDirectory() && !gFile.isReadable())
+        {
+            scsiDev.status = CHECK_CONDITION;
+            scsiDev.target->sense.code = ILLEGAL_REQUEST;
+            //SCSI_ASC_INVALID_FIELD_IN_CDB
+            scsiDev.phase = STATUS;
+            return;
+        }
+    }
+
+    uint32_t file_total = gFile.size();
+    memset(scsiDev.data, 0, 4096);
+    gFile.seekSet(offset * 4096);
+    int bytes_read = gFile.read(scsiDev.data, 4096);
+    if(offset * 4096 >= file_total) // transfer done, close.
+    {
+        gFile.close();
+    }
+    scsiDev.dataLen = bytes_read;
+    scsiDev.phase = DATA_IN;
+}
+
+/*
+  Prepares a file for receving. The file name is null terminated in the scsi data.
+*/
+static void onSendFilePrep(char * dir_name)
+{
+    char file_name[32+1];
+    memset(file_name, '\0', 32+1);
+    scsiEnterPhase(DATA_OUT);
+    for (int i = 0; i < 32+1; ++i)
+    {
+        file_name[i] = scsiReadByte();
+    }
+    SD.chdir(dir_name);
+    gFile.open(file_name, FILE_WRITE);
+    SD.chdir("/");
+    if(gFile.isOpen() && gFile.isWritable())
+    {
+        gFile.rewind();
+        gFile.sync();
+        // do i need to manually set phase to status here?
+        return;
+    } else {
+        gFile.close();
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        //SCSI_ASC_INVALID_FIELD_IN_CDB
+        scsiDev.phase = STATUS;
+    }
+}
+
+static void onSendFileEnd(void)
+{
+    gFile.sync();
+    gFile.close();
+    scsiDev.phase = STATUS;
+}
+
+static void onSendFile10(void)
+{
+    if(!gFile.isOpen() || !gFile.isWritable())
+    {
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        //SCSI_ASC_INVALID_FIELD_IN_CDB
+        scsiDev.phase = STATUS;
+    }
+
+    // Number of bytes sent this request, 1..512.
+    uint16_t bytes_sent = ((uint16_t)scsiDev.cdb[1] << 8)  | scsiDev.cdb[2];
+    // 512 byte offset of where to put these bytes.
+    uint32_t offset     = ((uint32_t)scsiDev.cdb[3] << 16) | ((uint32_t)scsiDev.cdb[4] << 8) | scsiDev.cdb[5];
+    uint16_t buf_size   = 512;
+    uint8_t buf[512];
+
+    // Check if last block of file, and not the only bock in file.
+    if(bytes_sent < buf_size)
+    {
+        buf_size = bytes_sent;
+    }
+
+    scsiEnterPhase(DATA_OUT);
+    scsiRead(buf, bytes_sent, NULL);
+    gFile.seekCur(offset * 512);
+    gFile.write(buf, buf_size);
+    if(gFile.getWriteError())
+    {
+        gFile.clearWriteError();
+        gFile.close();
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+    }
+    //scsiDev.phase = STATUS;
+}
+static void onToggleDebug()
+{
+    if(scsiDev.cdb[1] == 0) // 0 == Set Debug, 1 == Get Debug State
+    {
+        g_log_debug = scsiDev.cdb[2];
+        logmsg("Set debug logs to: ", g_log_debug);
+        scsiDev.phase = STATUS;
+    }
+    else
+    {
+        logmsg("Debug currently set to: ", g_log_debug);
+        scsiDev.data[0] = g_log_debug ? 0x1 : 0x0;
+        scsiDev.dataLen = 1;
+        scsiDev.phase = DATA_IN;
+    }
+}
+
+static int getToolBoxSharedDir(char * dir_name)
+{
+  return ini_gets("SCSI", "ToolBoxSharedDir", "/shared", dir_name, MAX_FILE_PATH, CONFIGFILE);
+}
+
+extern "C" int scsiToolboxCommand()
+{
+    int commandHandled = 1;
+    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+    uint8_t command = scsiDev.cdb[0];
+
+    if (unlikely(command == TOOLBOX_COUNT_FILES))
+    {
+        char img_dir[MAX_FILE_PATH];
+        getToolBoxSharedDir(img_dir);
+        doCountFiles(img_dir);
+    }
+    else if (unlikely(command == TOOLBOX_LIST_FILES))
+    {
+        char img_dir[MAX_FILE_PATH];
+        getToolBoxSharedDir(img_dir);
+        onListFiles(img_dir);
+    }
+    else if (unlikely(command == TOOLBOX_GET_FILE))
+    {
+        char img_dir[MAX_FILE_PATH];
+        getToolBoxSharedDir(img_dir);
+        onGetFile10(img_dir);
+    }
+    else if (unlikely(command == TOOLBOX_SEND_FILE_PREP))
+    {
+        char img_dir[MAX_FILE_PATH];
+        getToolBoxSharedDir(img_dir);
+        onSendFilePrep(img_dir);
+    }
+    else if (unlikely(command == TOOLBOX_SEND_FILE_10))
+    {
+        onSendFile10();
+    }
+    else if (unlikely(command == TOOLBOX_SEND_FILE_END))
+    {
+        onSendFileEnd();
+    }
+    else if(unlikely(command == TOOLBOX_TOGGLE_DEBUG))
+    {
+        onToggleDebug();
+    }
+    else if(unlikely(command == TOOLBOX_LIST_CDS))
+    {
+        char img_dir[4];
+        snprintf(img_dir, sizeof(img_dir), CD_IMG_DIR, (int)img.scsiId & S2S_CFG_TARGET_ID_BITS);
+        onListFiles(img_dir, true);
+    }
+    else if(unlikely(command == TOOLBOX_SET_NEXT_CD))
+    {
+        char img_dir[4];
+        snprintf(img_dir, sizeof(img_dir), CD_IMG_DIR, (int)img.scsiId & S2S_CFG_TARGET_ID_BITS);
+        onSetNextCD(img_dir);
+    }
+    else if(unlikely(command == TOOLBOX_LIST_DEVICES))
+    {
+        onListDevices();
+    }
+    else if (unlikely(command == TOOLBOX_COUNT_CDS))
+    {
+        char img_dir[4];
+        snprintf(img_dir, sizeof(img_dir), CD_IMG_DIR, (int)img.scsiId & S2S_CFG_TARGET_ID_BITS);
+        doCountFiles(img_dir, true);
+    }
+    else
+    {
+        commandHandled = 0;
+    }
+
+    return commandHandled;
+}

+ 37 - 0
src/Toolbox.h

@@ -0,0 +1,37 @@
+/** 
+ * Copyright (C) 2023 Eric Helgeson
+ * 
+ * This file is part of BlueSCSI
+ * 
+ * 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/>.
+**/
+
+#pragma once
+
+#define MAX_MAC_PATH 32
+#define CD_IMG_DIR "CD%d"
+
+
+#define TOOLBOX_LIST_FILES     0xD0
+#define TOOLBOX_GET_FILE       0xD1
+#define TOOLBOX_COUNT_FILES    0xD2
+#define TOOLBOX_SEND_FILE_PREP 0xD3
+#define TOOLBOX_SEND_FILE_10   0xD4
+#define TOOLBOX_SEND_FILE_END  0xD5
+#define TOOLBOX_TOGGLE_DEBUG   0xD6
+#define TOOLBOX_LIST_CDS       0xD7
+#define TOOLBOX_SET_NEXT_CD    0xD8
+#define TOOLBOX_LIST_DEVICES   0xD9
+#define TOOLBOX_COUNT_CDS      0xDA
+#define OPEN_RETRO_SCSI_TOO_MANY_FILES 0x0001

+ 16 - 3
src/ZuluSCSI_cdrom.cpp

@@ -1218,12 +1218,20 @@ 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)
+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.
-    char filename[MAX_FILE_PATH];
+    
     int target_idx = img.scsiId & 7;
-    scsiDiskGetNextImageName(img, filename, sizeof(filename));
+    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)
@@ -1240,6 +1248,11 @@ bool cdromSwitchNextImage(image_config_t &img)
 
         if (status)
         {
+            if (next_filename != nullptr)
+            {
+                img.ejected = false;
+                img.cdrom_events = 2; // New Media
+            }
             return true;
         }
     }

+ 1 - 1
src/ZuluSCSI_cdrom.h

@@ -22,7 +22,7 @@ void cdromPerformEject(image_config_t &img);
 void cdromReinsertFirstImage(image_config_t &img);
 
 // Switch to next CD-ROM image if multiple have been configured
-bool cdromSwitchNextImage(image_config_t &img);
+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

+ 2 - 11
src/ZuluSCSI_disk.cpp

@@ -1144,8 +1144,8 @@ static void doReadCapacity()
 /*************************/
 /* TestUnitReady command */
 /*************************/
-
-static int doTestUnitReady()
+extern "C"
+int doTestUnitReady()
 {
     int ready = 1;
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
@@ -1765,15 +1765,6 @@ int scsiDiskCommand()
             scsiDev.target->started = 0;
         }
     }
-    else if (unlikely(command == 0x00))
-    {
-        // TEST UNIT READY
-        doTestUnitReady();
-    }
-    else if (unlikely(!doTestUnitReady()))
-    {
-        // Status and sense codes already set by doTestUnitReady
-    }
     else if (likely(command == 0x08))
     {
         // READ(6)

+ 1 - 1
src/ZuluSCSI_disk.h

@@ -142,4 +142,4 @@ void scsiDiskStartRead(uint32_t lba, uint32_t blocks);
 void scsiDiskStartWrite(uint32_t lba, uint32_t blocks);
 
 // Returns true if there is at least one network device active
-bool scsiDiskCheckAnyNetworkDevicesConfigured();
+bool scsiDiskCheckAnyNetworkDevicesConfigured();