Эх сурвалжийг харах

Merge branch 'main' into feature/toolbox

Moved scsiToolboxCBDLen code from Toolbox.cpp into vendor.c so
CDB vendor command lengths have visibility in the same file.
Morio 1 жил өмнө
parent
commit
630fd569c6
65 өөрчлөгдсөн 3842 нэмэгдсэн , 830 устгасан
  1. 2 2
      .github/workflows/firmware_build.yml
  2. 1 0
      .gitignore
  3. 0 42
      boards/ZuluSCSI_RP2040.json
  4. 57 0
      boards/zuluscsi_rp2040.json
  5. 6 2
      lib/SCSI2SD/include/scsi2sd.h
  6. 13 5
      lib/SCSI2SD/src/firmware/log.h
  7. 13 3
      lib/SCSI2SD/src/firmware/mode.c
  8. 70 20
      lib/SCSI2SD/src/firmware/network.c
  9. 7 0
      lib/SCSI2SD/src/firmware/network.h
  10. 29 3
      lib/SCSI2SD/src/firmware/scsi.c
  11. 7 0
      lib/SCSI2SD/src/firmware/scsi.h
  12. 0 1
      lib/SCSI2SD/src/firmware/toolbox.h
  13. 35 1
      lib/SCSI2SD/src/firmware/vendor.c
  14. 241 0
      lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform_msc.cpp
  15. 59 0
      lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform_msc.h
  16. 11 1
      lib/ZuluSCSI_platform_GD32F205/audio.cpp
  17. 3 2
      lib/ZuluSCSI_platform_GD32F205/usb_serial.cpp
  18. 26 0
      lib/ZuluSCSI_platform_GD32F205/usbd_conf.h
  19. 288 0
      lib/ZuluSCSI_platform_GD32F205/usbd_msc_bbb.c
  20. 101 0
      lib/ZuluSCSI_platform_GD32F205/usbd_msc_bbb.h
  21. 312 0
      lib/ZuluSCSI_platform_GD32F205/usbd_msc_core.c
  22. 59 0
      lib/ZuluSCSI_platform_GD32F205/usbd_msc_core.h
  23. 71 0
      lib/ZuluSCSI_platform_GD32F205/usbd_msc_data.c
  24. 50 0
      lib/ZuluSCSI_platform_GD32F205/usbd_msc_data.h
  25. 59 0
      lib/ZuluSCSI_platform_GD32F205/usbd_msc_mem.h
  26. 744 0
      lib/ZuluSCSI_platform_GD32F205/usbd_msc_scsi.c
  27. 51 0
      lib/ZuluSCSI_platform_GD32F205/usbd_msc_scsi.h
  28. 50 99
      lib/ZuluSCSI_platform_GD32F450/ZuluSCSI_platform.cpp
  29. 2 2
      lib/ZuluSCSI_platform_GD32F450/ZuluSCSI_platform.h
  30. 1 1
      lib/ZuluSCSI_platform_GD32F450/greenpak.cpp
  31. 8 3
      lib/ZuluSCSI_platform_GD32F450/scsiPhy.cpp
  32. 8 68
      lib/ZuluSCSI_platform_GD32F450/scsi_accel_dma.cpp
  33. 2 2
      lib/ZuluSCSI_platform_GD32F450/scsi_accel_greenpak.cpp
  34. 11 5
      lib/ZuluSCSI_platform_GD32F450/scsi_accel_sync.cpp
  35. 0 2
      lib/ZuluSCSI_platform_GD32F450/sd_card_sdio.cpp
  36. 274 0
      lib/ZuluSCSI_platform_GD32F450/sd_card_spi.cpp
  37. 51 197
      lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp
  38. 227 0
      lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_msc.cpp
  39. 40 0
      lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_msc.h
  40. 17 20
      lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_network.cpp
  41. 12 2
      lib/ZuluSCSI_platform_RP2040/audio.cpp
  42. 41 0
      lib/ZuluSCSI_platform_RP2040/process-linker-script.py
  43. 137 0
      lib/ZuluSCSI_platform_RP2040/program_flash.cpp
  44. 0 217
      lib/ZuluSCSI_platform_RP2040/rp2040-daynaport.ld
  45. 7 2
      lib/ZuluSCSI_platform_RP2040/rp2040-template.ld
  46. 1 6
      lib/ZuluSCSI_platform_RP2040/scsi_accel_target.cpp
  47. 4 1
      lib/ZuluSCSI_platform_RP2040/sd_card_sdio.cpp
  48. 68 19
      platformio.ini
  49. 0 7
      src/Toolbox.cpp
  50. 32 7
      src/ZuluSCSI.cpp
  51. 14 1
      src/ZuluSCSI_audio.h
  52. 165 24
      src/ZuluSCSI_cdrom.cpp
  53. 2 2
      src/ZuluSCSI_config.h
  54. 7 0
      src/ZuluSCSI_disk.cpp
  55. 160 42
      src/ZuluSCSI_initiator.cpp
  56. 4 0
      src/ZuluSCSI_initiator.h
  57. 4 2
      src/ZuluSCSI_log.cpp
  58. 2 0
      src/ZuluSCSI_log.h
  59. 17 13
      src/ZuluSCSI_log_trace.cpp
  60. 2 2
      src/ZuluSCSI_mode.cpp
  61. 103 0
      src/ZuluSCSI_msc.cpp
  62. 37 0
      src/ZuluSCSI_msc.h
  63. 10 1
      src/ZuluSCSI_settings.cpp
  64. 3 0
      src/ZuluSCSI_settings.h
  65. 4 1
      zuluscsi.ini

+ 2 - 2
.github/workflows/firmware_build.yml

@@ -14,7 +14,7 @@ jobs:
     
     steps:
       - name: Check out code from GitHub
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           path: ZuluSCSI
           fetch-depth: "0"
@@ -34,7 +34,7 @@ jobs:
           utils/rename_binaries.sh
 
       - name: Upload binaries into build artifacts
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           path: ZuluSCSI/distrib/*
           name: ZuluSCSI binaries

+ 1 - 0
.gitignore

@@ -1,2 +1,3 @@
 .pio
 .vscode
+.DS_Store

+ 0 - 42
boards/ZuluSCSI_RP2040.json

@@ -1,42 +0,0 @@
-{
-    "name": "ZuluSCSI RP2040",
-    "url": "https://github.com/ZuluSCSI/ZuluSCSI-firmware",
-    "vendor": "ZuluSCSI",
-    "build": {
-        "core": "arduino",
-        "cpu": "cortex-m0plus",
-        "extra_flags": "-DARDUINO_ARCH_RP2040",
-        "f_cpu": "133000000L",
-        "hwids": [
-        [
-            "0x2E8A",
-            "0x00C0"
-        ]
-        ],
-        "mcu": "rp2040",
-        "variant": "RASPBERRY_PI_PICO"
-    },
-    "debug": {
-        "jlink_device": "RP2040_M0_0",
-        "openocd_target": "rp2040.cfg",
-        "svd_path": "rp2040.svd"
-    },
-    "frameworks": [
-        "arduino"
-    ],
-    "upload": {
-        "maximum_ram_size": 270336,
-        "maximum_size": 2097152,
-        "require_upload_port": true,
-        "native_usb": true,
-        "use_1200bps_touch": true,
-        "wait_for_upload_port": false,
-        "protocol": "picotool",
-        "protocols": [
-        "cmsis-dap",
-        "jlink",
-        "raspberrypi-swd",
-        "picotool"
-        ]
-    }
-}

+ 57 - 0
boards/zuluscsi_rp2040.json

@@ -0,0 +1,57 @@
+{
+    "build": {
+      "arduino": {
+        "earlephilhower": {
+          "boot2_source": "boot2_w25q080_2_padded_checksum.S",
+          "usb_vid": "0x2E8A",
+          "usb_pid": "0xF00A"
+        }
+      },
+      "core": "earlephilhower",
+      "cpu": "cortex-m0plus",
+      "extra_flags": "-D ARDUINO_RHC_ZULUSCSI_RP2040 -DARDUINO_ARCH_RP2040 -DUSBD_MAX_POWER_MA=500",
+      "f_cpu": "125000000L",
+      "hwids": [
+        [
+          "0x2E8A",
+          "0x00C0"
+        ],
+        [
+          "0x2E8A",
+          "0xF00A"
+        ]
+      ],
+      "mcu": "rp2040",
+      "variant": "zuluscsi_rp2040"
+    },
+    "debug": {
+      "jlink_device": "RP2040_M0_0",
+      "openocd_target": "rp2040.cfg",
+      "svd_path": "rp2040.svd"
+    },
+    "frameworks": [
+      "arduino"
+    ],
+    "name": "ZuluSCSI",
+    "upload": {
+      "maximum_ram_size": 270336,
+      "maximum_size": 2097152,
+      "require_upload_port": true,
+      "native_usb": true,
+      "use_1200bps_touch": true,
+      "wait_for_upload_port": false,
+      "protocol": "picotool",
+      "protocols": [
+        "blackmagic",
+        "cmsis-dap",
+        "jlink",
+        "raspberrypi-swd",
+        "picotool",
+        "picoprobe",
+        "pico-debug"
+      ]
+    },
+    "url": "https://www.raspberrypi.org/products/raspberry-pi-pico/",
+    "vendor": "Rabbit Hole Computing"
+  }
+  

+ 6 - 2
lib/SCSI2SD/include/scsi2sd.h

@@ -89,7 +89,8 @@ typedef enum
 	S2S_CFG_QUIRKS_OMTI = 2,
 	S2S_CFG_QUIRKS_XEBEC = 4,
 	S2S_CFG_QUIRKS_VMS = 8,
-	S2S_CFG_QUIRKS_X68000 = 16
+	S2S_CFG_QUIRKS_X68000 = 16,
+	S2S_CFG_QUIRKS_EWSD = 32
 } S2S_CFG_QUIRKS;
 
 typedef enum
@@ -132,7 +133,10 @@ typedef struct __attribute__((packed))
 
 	uint16_t quirks; // S2S_CFG_QUIRKS
 
-	uint8_t reserved[64]; // Pad out to 128 bytes for main section.
+	// bit flags vendor extention for specific device types
+	uint32_t vendorExtensions;
+
+	uint8_t reserved[60]; // Pad out to 128 bytes for main section.
 } S2S_TargetCfg;
 
 typedef struct __attribute__((packed))

+ 13 - 5
lib/SCSI2SD/src/firmware/log.h

@@ -23,6 +23,7 @@
 extern "C" {
 #endif
 
+#ifdef NETWORK_DEBUG_LOGGING
 extern bool g_log_debug;
 
 extern void logmsg_buf(const unsigned char *buf, unsigned long size);
@@ -31,12 +32,19 @@ extern void logmsg_f(const char *format, ...);
 extern void dbgmsg_buf(const unsigned char *buf, unsigned long size);
 extern void dbgmsg_f(const char *format, ...);
 
-// check if debug is enabled before calling the logging function
-#define DBGMSG_BUF(buf, size) \
-	if (g_log_debug) {dbgmsg_buf(buf, size);}
+#define DBGMSG_BUF(buf, size) dbgmsg_buf(buf, size)
+#define DBGMSG_F(format, ...) dbgmsg_f(format, __VA_ARGS__);
+#define LOGMSG_BUF(buf, size) logmsg_buf(buf, size)
+#define LOGMSG_F(format, ...) logmsg_f(format, __VA_ARGS__);
 
-#define DBGMSG_F(format, ...) \
-	if (g_log_debug) {dbgmsg_f(format, __VA_ARGS__);}
+#else
+
+#define DBGMSG_BUF(buf, size) // Empty
+#define DBGMSG_F(format, ...) // Empty
+#define LOGMSG_BUF(buf, size) // Empty
+#define LOGMSG_F(format, ...) // Empty
+
+#endif // NETWORK_DEBUG_LOGGING
 
 #ifdef __cplusplus
 }

+ 13 - 3
lib/SCSI2SD/src/firmware/mode.c

@@ -1,6 +1,7 @@
 //	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
 //  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
 //
 //	This file is part of SCSI2SD.
 //
@@ -298,9 +299,18 @@ static void doModeSense(
 		break;
 
 	case S2S_CFG_OPTICAL:
-		mediumType = 0x02; // 120mm CDROM, data only.
-		deviceSpecificParam = 0;
-		density = 0x01; // User data only, 2048bytes per sector.
+		if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_APPLE)
+		{
+			mediumType = 0x00;
+			deviceSpecificParam = 0;
+			density = 0x00;
+		}
+		else
+		{
+			mediumType = 0x02; // 120mm CDROM, data only.
+			deviceSpecificParam = 0;
+			density = 0x01; // User data only, 2048bytes per sector.
+		}
 		break;
 
 	case S2S_CFG_SEQUENTIAL:

+ 70 - 20
lib/SCSI2SD/src/firmware/network.c

@@ -105,6 +105,12 @@ int scsiNetworkCommand()
 
 	DBGMSG_F("------ in scsiNetworkCommand with command 0x%02x (size %d)", command, size);
 
+	// Rather than duplicating code, this just diverts a 'fake' read request to make the gvpscsi.device happy on the Amiga
+	if ((scsiDev.cdb[0] == SCSI_NETWORK_WIFI_CMD) && (scsiDev.cdb[1] == SCSI_NETWORK_WIFI_CMD_ALTREAD)) {
+		// Redirect the command as a READ.
+		command = 0x08;
+	}
+
 	switch (command) {
 	case 0x08:
 		// read(6)
@@ -132,7 +138,7 @@ int scsiNetworkCommand()
 			}
 			else if (psize + 6 > size)
 			{
-				logmsg_f("%s: packet size too big (%d)", __func__, psize);
+				LOGMSG_F("%s: packet size too big (%d)", __func__, psize);
 				psize = size - 6;
 			}
 
@@ -157,26 +163,62 @@ int scsiNetworkCommand()
 
 			DBGMSG_BUF(scsiDev.data, scsiDev.dataLen);
 		}
+		// Patches around the weirdness on the Amiga SCSI devices
+		if ((scsiDev.cdb[0] == SCSI_NETWORK_WIFI_CMD) && (scsiDev.cdb[1] == SCSI_NETWORK_WIFI_CMD_ALTREAD)) {
+			scsiDev.data[2] = scsiDev.cdb[2];	// for me really
+			int extra = 0;
+			if (scsiDev.cdb[2] == AMIGASCSI_PATCH_24BYTE_BLOCKSIZE) {
+				if (scsiDev.dataLen<90) scsiDev.dataLen = 90;
+				int missing = (scsiDev.dataLen-90) % 24;
+				if (missing) {
+					scsiDev.dataLen += 24 - missing;
+					if (scsiDev.dataLen>NETWORK_PACKET_MAX_SIZE) {
+						extra = scsiDev.dataLen - NETWORK_PACKET_MAX_SIZE;
+						scsiDev.dataLen = NETWORK_PACKET_MAX_SIZE;
+					}
+				}
+				scsiEnterPhase(DATA_IN);
+				scsiWrite(scsiDev.data, scsiDev.dataLen);
+				while (!scsiIsWriteFinished(NULL))
+				{
+					platform_poll();
+				}
+				scsiFinishWrite();
+			} else {
+				extra = scsiDev.dataLen;     // F9 means send in ONE transaction
+				if (extra) scsiEnterPhase(DATA_IN);
+			}
 
-		// DaynaPort driver needs a delay between reading the initial packet size and the data so manually do two transfers
-		scsiEnterPhase(DATA_IN);
-		scsiWrite(scsiDev.data, 6);
-		while (!scsiIsWriteFinished(NULL))
-		{
-			platform_poll();
-		}
-		scsiFinishWrite();
-
-		if (scsiDev.dataLen > 6)
-		{
-			s2s_delay_us(80);
-
-			scsiWrite(scsiDev.data + 6, scsiDev.dataLen - 6);
+			if (extra) {
+				// Just write the extra data to make the padding work for such a large packet
+				scsiWrite(scsiDev.data, extra);
+				while (!scsiIsWriteFinished(NULL))
+				{
+					platform_poll();
+				}
+				scsiFinishWrite();
+			}
+		} else {
+			// DaynaPort driver needs a delay between reading the initial packet size and the data so manually do two transfers
+			scsiEnterPhase(DATA_IN);
+			scsiWrite(scsiDev.data, 6);
 			while (!scsiIsWriteFinished(NULL))
 			{
 				platform_poll();
 			}
 			scsiFinishWrite();
+
+			if (scsiDev.dataLen > 6)
+			{
+				s2s_delay_us(80);
+
+				scsiWrite(scsiDev.data + 6, scsiDev.dataLen - 6);
+				while (!scsiIsWriteFinished(NULL))
+				{
+					platform_poll();
+				}
+				scsiFinishWrite();
+			}
 		}
 
 		scsiDev.status = GOOD;
@@ -329,7 +371,7 @@ int scsiNetworkCommand()
 				int size = sizeof(struct wifi_network_entry) * nets;
 				if (size + 2 > sizeof(scsiDev.data))
 				{
-					logmsg_f("WARNING: wifi_network_list is bigger than scsiDev.data, truncating");
+					LOGMSG_F("WARNING: wifi_network_list is bigger than scsiDev.data, truncating");
 					size = sizeof(scsiDev.data) - 2;
 					size -= (size % (sizeof(struct wifi_network_entry)));
 				}
@@ -378,7 +420,7 @@ int scsiNetworkCommand()
 			struct wifi_join_request req = { 0 };
 
 			if (size != sizeof(req)) {
-				logmsg_f("wifi_join_request bad size (%zu != %zu), ignoring", size, sizeof(req));
+				LOGMSG_F("wifi_join_request bad size (%zu != %zu), ignoring", size, sizeof(req));
 				scsiDev.status = CHECK_CONDITION;
 				scsiDev.phase = STATUS;
 				break;
@@ -395,6 +437,14 @@ int scsiNetworkCommand()
 			scsiDev.phase = STATUS;
 			break;
 		}
+
+		case SCSI_NETWORK_WIFI_CMD_GETMACADDRESS:
+			// Update for the gvpscsi.device on the Amiga as it doesn't like 0x09 command being called! - NOTE this only sends 6 bytes back
+			memcpy(scsiDev.data, scsiDev.boardCfg.wifiMACAddress, sizeof(scsiDev.boardCfg.wifiMACAddress));
+			memset(scsiDev.data + sizeof(scsiDev.boardCfg.wifiMACAddress), 0, sizeof(scsiDev.data) - sizeof(scsiDev.boardCfg.wifiMACAddress));
+
+			scsiDev.dataLen = 6;
+			scsiDev.phase = DATA_IN;
 		}
 		break;
 
@@ -434,7 +484,7 @@ int scsiNetworkEnqueue(const uint8_t *buf, size_t len)
 	{
 		DBGMSG_F("%s: dropping packets in ring, write index caught up to read index", __func__);
 	}
-	
+
 	return 1;
 }
 
@@ -453,10 +503,10 @@ int scsiNetworkPurge(void)
 			scsiNetworkOutboundQueue.readIndex = 0;
 		else
 			scsiNetworkOutboundQueue.readIndex++;
-		
+
 		sent++;
 	}
 
 	return sent;
 }
-#endif // ZULUSCSI_NETWORK
+#endif // ZULUSCSI_NETWORK

+ 7 - 0
lib/SCSI2SD/src/firmware/network.h

@@ -30,6 +30,13 @@ extern "C" {
 #define SCSI_NETWORK_WIFI_CMD_INFO			0x04
 #define SCSI_NETWORK_WIFI_CMD_JOIN			0x05
 
+// Patches to make the DaynaPORT (or whats left of it) work on the Amiga - RobSmithDev
+#define SCSI_NETWORK_WIFI_CMD_ALTREAD       0x08   // gvpscsi.device on AMIGA doesnt like the standard version
+#define SCSI_NETWORK_WIFI_CMD_GETMACADDRESS 0x09   // gvpscsi.device on AMIGA doesnt like the standard version
+
+#define AMIGASCSI_PATCH_24BYTE_BLOCKSIZE 	0xA8   // In this mode, data written is rounded up to the nearest 24-byte boundary
+#define AMIGASCSI_PATCH_SINGLEWRITE_ONLY 	0xA9   // In this mode, data written is always ONLY as one single write command
+
 #ifndef NETWORK_PACKET_QUEUE_SIZE
 # define NETWORK_PACKET_QUEUE_SIZE   20		// must be <= 255
 #endif

+ 29 - 3
lib/SCSI2SD/src/firmware/scsi.c

@@ -1,6 +1,7 @@
 //	Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
 //	Copyright (c) 2023 joshua stein <jcs@jcs.org>
 //	Copyright (c) 2023 Andrea Ottaviani <andrea.ottaviani.69@gmail.com>
+//	Copyright (C) 2024 Rabbit Hole Computing LLC
 //
 //	This file is part of SCSI2SD.
 //
@@ -142,6 +143,11 @@ void process_Status()
 {
 	scsiEnterPhase(STATUS);
 
+	if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_EWSD)
+	{
+		s2s_delay_ms(1);
+	}
+
 	uint8_t message;
 
 	uint8_t control = scsiDev.cdb[scsiDev.cdbLen - 1];
@@ -299,6 +305,7 @@ static void process_Command()
 	memset(scsiDev.cdb + 6, 0, sizeof(scsiDev.cdb) - 6);
 	int parityError = 0;
 	scsiRead(scsiDev.cdb, 6, &parityError);
+	command = scsiDev.cdb[0];
 
 	group = scsiDev.cdb[0] >> 5;
 	scsiDev.cdbLen = CmdGroupBytes[group];
@@ -314,7 +321,7 @@ static void process_Command()
 	{
 		scsiRead(scsiDev.cdb + 6, scsiDev.cdbLen - 6, &parityError);
 	}
-	command = scsiDev.cdb[0];
+
 
 	// Prefer LUN's set by IDENTIFY messages for newer hosts.
 	if (scsiDev.lun < 0)
@@ -539,6 +546,12 @@ static void process_Command()
 			scsiDev.data[7] = 10; // additional length
 			scsiDev.data[12] = scsiDev.target->sense.asc >> 8;
 			scsiDev.data[13] = scsiDev.target->sense.asc;
+			if ((scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_EWSD))
+			{
+				/* EWSD seems not to want something behind additional length. (8 + 0x0e = 22) */
+				allocLength=22;
+				scsiDev.data[7] = 0x0e;
+			}
 		}
 
 		// Silently truncate results. SCSI-2 spec 8.2.14.
@@ -553,8 +566,15 @@ static void process_Command()
 	// on receiving the unit attention response on boot, thus
 	// triggering another unit attention condition.
 	else if (scsiDev.target->unitAttention &&
-		(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_UNIT_ATTENTION))
+		scsiDev.target->unitAttentionStop == 0 &&
+		((scsiDev.boardCfg.flags & S2S_CFG_ENABLE_UNIT_ATTENTION) ||
+		(scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_EWSD)))
 	{
+		/* EWSD requires unitAttention to be sent only once. */
+		if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_EWSD)
+		{
+			scsiDev.target->unitAttentionStop = 1;
+		}
 		scsiDev.target->sense.code = UNIT_ATTENTION;
 		scsiDev.target->sense.asc = scsiDev.target->unitAttention;
 
@@ -1300,7 +1320,13 @@ void scsiInit()
 		scsiDev.targets[i].reserverId = -1;
 		if (firstInit)
 		{
-			scsiDev.targets[i].unitAttention = POWER_ON_RESET;
+			if ((cfg->deviceType == S2S_CFG_MO) && (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_EWSD))
+			{
+				scsiDev.targets[i].unitAttention = POWER_ON_RESET_OR_BUS_DEVICE_RESET_OCCURRED;
+			} else
+			{
+				scsiDev.targets[i].unitAttention = POWER_ON_RESET;
+			}
 		}
 		else
 		{

+ 7 - 0
lib/SCSI2SD/src/firmware/scsi.h

@@ -102,6 +102,7 @@ typedef struct
 	ScsiSense sense;
 
 	uint16_t unitAttention; // Set to the sense qualifier key to be returned.
+	uint8_t unitAttentionStop; // Indicates if unit attention has to be stopped.
 
 	// Only let the reserved initiator talk to us.
 	// A 3rd party may be sending the RESERVE/RELEASE commands
@@ -182,6 +183,12 @@ typedef struct
 	int hostSpeedMeasured;
 } ScsiDevice;
 
+typedef enum
+{
+	VENDOR_EXTENSION_OPTICAL_PLEXTOR = 1 << 0,
+} VENDOR_EXTENSION_OPTICAL;
+
+
 extern ScsiDevice scsiDev;
 
 void process_Status(void);

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

@@ -22,7 +22,6 @@
 #define S2S_TOOLBOX_H
 #include <inttypes.h>
 int8_t scsiToolboxEnabled(void);
-void scsiToolboxCBDLen(uint8_t command, uint8_t *command_length);
 int scsiToolboxCommand(void);
 
 #endif

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

@@ -81,6 +81,21 @@ int scsiVendorCommand()
 		scsiDev.phase = DATA_OUT;
 		scsiDev.postDataOutHook = doWriteBuffer;
 	}
+	else if (command == 0xE0 && 
+		scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
+	{
+	  // RAM Diagnostic
+	  // XEBEC S1410 controller
+	  // http://bitsavers.informatik.uni-stuttgart.de/pdf/xebec/104524C_S1410Man_Aug83.pdf
+	  // Stub, return success
+	}
+	else if (command == 0xE4 && 
+		scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
+	{
+	  // Drive Diagnostic
+	  // XEBEC S1410 controller
+	  // Stub, return success
+	}   	
 	else if (scsiToolboxEnabled() && scsiToolboxCommand())
 	{
 		// already handled
@@ -95,8 +110,27 @@ int scsiVendorCommand()
 
 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())
 	{
-		scsiToolboxCBDLen(command, command_length);
+		// 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;
+		}
 	}
 }

+ 241 - 0
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform_msc.cpp

@@ -0,0 +1,241 @@
+/**
+ * Copyright (c) 2023-2024 zigzagjoe
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * 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/>.
+**/
+
+#ifdef PLATFORM_MASS_STORAGE
+
+#include <SdFat.h>
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_msc.h"
+#include "ZuluSCSI_settings.h"
+#include "usb_serial.h"
+
+/* gd32 USB code is all C linked */
+extern "C" {
+  #include <drv_usb_hw.h>
+  #include <usbd_core.h>
+  #include <drv_usbd_int.h>
+
+  #include "usb_conf.h"
+  #include "usbd_msc_core.h"
+  #include "usbd_msc_mem.h"
+  #include "usbd_msc_bbb.h"
+}
+
+
+/* local function prototypes ('static') */
+static int8_t storageInit(uint8_t Lun);
+static int8_t storageIsReady(uint8_t Lun);
+static int8_t storageIsWriteProtected(uint8_t Lun);
+static int8_t storageGetMaxLun(void);
+static int8_t storageRead(uint8_t Lun,
+                           uint8_t *buf,
+                           uint32_t BlkAddr,
+                           uint16_t BlkLen);
+static int8_t storageWrite(uint8_t Lun,
+                            uint8_t *buf,
+                            uint32_t BlkAddr,
+                            uint16_t BlkLen);
+
+usbd_mem_cb USBD_SD_fops = {
+    .mem_init      = storageInit,
+    .mem_ready     = storageIsReady,
+    .mem_protected = storageIsWriteProtected,
+    .mem_read      = storageRead,
+    .mem_write     = storageWrite,
+    .mem_maxlun    = storageGetMaxLun,
+
+    .mem_inquiry_data = {(uint8_t *)storageInquiryData},
+    .mem_block_size   = {SD_SECTOR_SIZE},
+    .mem_block_len    = {0}
+};
+
+usbd_mem_cb *usbd_mem_fops = &USBD_SD_fops;
+
+// shared with usb serial
+extern usb_core_driver cdc_acm;
+
+// external global SD variable
+extern SdFs SD;
+
+// private globals
+static bool unitReady = false;
+
+/* returns true if card reader mode should be entered. sd card is available. */
+bool platform_sense_msc() {
+
+  // kill usb serial.
+  usbd_disconnect (&cdc_acm);
+  // set the MSC storage size
+  usbd_mem_fops->mem_block_len[0] = SD.card()->sectorCount();
+  unitReady = true;
+  
+  // init the MSC class, uses ISR and other global routines from usb_serial.cpp
+  usbd_init(&cdc_acm, USB_CORE_ENUM_FS, &msc_desc, &msc_class);
+
+  logmsg("Waiting for USB enumeration to expose SD card as a mass storage device");
+
+  // wait for up to a second to be begin to be enumerated
+  uint32_t start = millis();
+  while ((uint32_t)(millis() - start) < CR_ENUM_TIMEOUT)
+    if (cdc_acm.dev.cur_status >= USBD_ADDRESSED)
+      return true;
+
+  //if not, disconnect MSC class...
+  usbd_disconnect (&cdc_acm);
+
+  // and bring serial back for later.
+  usb_serial_init();
+
+  return false;
+}
+
+/* perform MSC-specific init tasks */
+void platform_enter_msc() {
+  dbgmsg("USB MSC buffer size: ", (uint32_t) MSC_MEDIA_PACKET_SIZE);
+
+  // give the host a moment to finish enumerate and "load" media
+  uint32_t start = millis();
+  while ((USBD_CONFIGURED != cdc_acm.dev.cur_status) && ((uint32_t)(millis() - start) < CR_ENUM_TIMEOUT) ) 
+    delay(100);
+}
+
+/* return true while remaining in msc mode, and perform periodic tasks */
+bool platform_run_msc() {
+  usbd_msc_handler *msc = (usbd_msc_handler *)cdc_acm.dev.class_data[USBD_MSC_INTERFACE];
+
+  // stupid windows doesn't send start_stop_unit events if it is ejected via safely remove devices. 
+  // it just stops talking to the device so we don't know we've been ejected....
+  // other OSes always send the start_stop_unit, windows does too when ejected from explorer.
+  // so we watch for the OS suspending device and assume we're done in USB mode if so.
+  // this will also trigger if the host were to suspend usb device due to going to sleep
+  // however, I hope no sane OS would sleep mid transfer or with a dirty filesystem. 
+  // Note: Mac OS X apparently not sane.
+  uint8_t is_suspended = cdc_acm.dev.cur_status == (uint8_t)USBD_SUSPENDED;
+
+  return (! msc->scsi_disk_pop) && !is_suspended;
+}
+
+void platform_exit_msc() {
+  unitReady = false;
+
+  // disconnect msc....
+  usbd_disconnect (&cdc_acm);
+
+  // catch our breath....
+  delay(200);
+  
+  // ... and bring usb serial up
+  usb_serial_init();
+}
+
+/*!
+    \brief      initialize the storage medium
+    \param[in]  Lun: logical unit number
+    \param[out] none
+    \retval     status
+*/
+static int8_t storageInit(uint8_t Lun)
+{
+    return 0;
+}
+
+/*!
+    \brief      check whether the medium is ready
+    \param[in]  Lun: logical unit number
+    \param[out] none
+    \retval     status
+*/
+static int8_t storageIsReady(uint8_t Lun)
+{
+    return ! unitReady; // 0 = success / unit is ready
+}
+
+/*!
+    \brief      check whether the medium is write-protected
+    \param[in]  Lun: logical unit number
+    \param[out] none
+    \retval     status
+*/
+static int8_t storageIsWriteProtected(uint8_t Lun)
+{
+    return ! unitReady; // 0 = read/write
+}
+
+/*!
+    \brief      read data from the medium
+    \param[in]  Lun: logical unit number
+    \param[in]  buf: pointer to the buffer to save data
+    \param[in]  BlkAddr: address of 1st block to be read
+    \param[in]  BlkLen: number of blocks to be read
+    \param[out] none
+    \retval     status
+*/
+static int8_t storageRead(uint8_t Lun,
+                           uint8_t *buf,
+                           uint32_t BlkAddr,
+                           uint16_t BlkLen)
+{
+    // divide by sector size to convert address to LBA
+    bool rc = SD.card()->readSectors(BlkAddr/SD_SECTOR_SIZE, buf, BlkLen);
+
+    // only blink fast on reads; writes will override this
+    if (MSC_LEDMode == LED_SOLIDON)
+      MSC_LEDMode = LED_BLINK_FAST;
+      
+    return !rc;
+}
+
+/*!
+    \brief      write data to the medium
+    \param[in]  Lun: logical unit number
+    \param[in]  buf: pointer to the buffer to write
+    \param[in]  BlkAddr: address of 1st block to be written
+    \param[in]  BlkLen: number of blocks to be write
+    \param[out] none
+    \retval     status
+*/
+static int8_t storageWrite(uint8_t Lun,
+                            uint8_t *buf,
+                            uint32_t BlkAddr,
+                            uint16_t BlkLen)
+{
+    // divide by sector size to convert address to LBA
+    bool rc = SD.card()->writeSectors(BlkAddr/SD_SECTOR_SIZE, buf, BlkLen);
+
+    // always slow blink
+    MSC_LEDMode = LED_BLINK_SLOW;
+
+    return !rc;
+}
+
+/*!
+    \brief      get number of supported logical unit
+    \param[in]  none
+    \param[out] none
+    \retval     number of logical unit
+*/
+static int8_t storageGetMaxLun(void)
+{
+    return 0; // number of LUNs supported - 1
+}
+
+#endif // PLATFORM_MASS_STORAGE

+ 59 - 0
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform_msc.h

@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2023-2024 zigzagjoe
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * 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/>.
+**/
+
+#ifdef PLATFORM_MASS_STORAGE
+#pragma once
+
+#include "usbd_msc_mem.h"
+
+// private constants/enums
+#define SD_SECTOR_SIZE 512
+
+/* USB Mass storage Standard Inquiry Data */
+const uint8_t storageInquiryData[] = {
+    /* LUN 0 */
+    0x00,
+    0x80,
+    0x00,
+    0x01,
+    (USBD_STD_INQUIRY_LENGTH - 5U),
+    0x00,
+    0x00,
+    0x00,
+    'Z', 'u', 'l', 'u', 'S', 'C', 'S', 'I', /* Manufacturer : 8 bytes */
+    'G', 'D', '3', '2', 0, 0, 0, 0, /* Product      : 16 Bytes */
+    0, 0, 0, 0, 0, 0, 0, 0,
+    '1', '.', '0', '0',                     /* Version      : 4 Bytes */
+};
+
+/* return true if USB presence detected / eligble to enter CR mode */
+bool platform_sense_msc();
+
+/* perform MSC-specific init tasks */
+void platform_enter_msc();
+
+/* return true if we should remain in card reader mode. called in a loop. */
+bool platform_run_msc();
+
+/* perform any cleanup tasks for the MSC-specific functionality */
+void platform_exit_msc();
+
+#endif

+ 11 - 1
lib/ZuluSCSI_platform_GD32F205/audio.cpp

@@ -55,7 +55,8 @@ extern bool g_audio_stopped;
 
 
 // historical playback status information
-static audio_status_code audio_last_status[8] = {ASC_NO_STATUS};
+static audio_status_code audio_last_status[8] = {ASC_NO_STATUS, ASC_NO_STATUS, ASC_NO_STATUS, ASC_NO_STATUS,
+                                                 ASC_NO_STATUS, ASC_NO_STATUS, ASC_NO_STATUS, ASC_NO_STATUS};
 
 // volume information for targets
 static volatile uint16_t volumes[8] = {
@@ -410,5 +411,14 @@ void audio_set_channel(uint8_t id, uint16_t chn) {
     channels[id & 7] = chn;
 }
 
+uint64_t audio_get_file_position()
+{
+    return fpos;
+}
+
+void audio_set_file_position(uint32_t lba)
+{
+    fpos = 2352 * (uint64_t)lba;
+}
 
 #endif // ENABLE_AUDIO_OUTPUT

+ 3 - 2
lib/ZuluSCSI_platform_GD32F205/usb_serial.cpp

@@ -38,6 +38,7 @@ void usb_serial_init(void)
     // set USB full speed prescaler and turn on USB clock
     rcu_usbfs_trng_clock_config(RCU_CKUSB_CKPLL_DIV2_5);
     rcu_periph_clock_enable(RCU_USBFS);
+    
     usbd_init(&cdc_acm, USB_CORE_ENUM_FS, &gd32_cdc_desc, &gd32_cdc_class);
 
     // USB full speed Interrupt config
@@ -48,7 +49,8 @@ void usb_serial_init(void)
 
 bool usb_serial_ready(void)
 {
-    if (USBD_CONFIGURED == cdc_acm.dev.cur_status) 
+    // check that (our) serial is the currently active class
+    if ((USBD_CONFIGURED == cdc_acm.dev.cur_status) && (cdc_acm.dev.desc == &gd32_cdc_desc)) 
     {
         if (1U == gd32_cdc_acm_check_ready(&cdc_acm)) 
         {
@@ -73,7 +75,6 @@ void usb_udelay (const uint32_t usec)
     delay_us(usec);
 }
 
-
 void usb_mdelay (const uint32_t msec)
 {
     delay(msec);

+ 26 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_conf.h

@@ -59,4 +59,30 @@ OF SUCH DAMAGE.
 #define USB_CDC_DATA_PACKET_SIZE            64U   /* Endpoint IN & OUT Packet size */
 #define CDC_IN_FRAME_INTERVAL               5U   /* Number of frames between IN transfers */
 
+#ifdef PLATFORM_MASS_STORAGE
+    /* MSC  */
+
+    #define USBD_MSC_INTERFACE              0U
+
+    /* class layer parameter */
+    #define MSC_IN_EP                       EP1_IN
+    #define MSC_OUT_EP                      EP1_OUT
+
+    #define MSC_DATA_PACKET_SIZE            64U
+    /* recommend 4096 as a minimum - SD real sector size */
+    #define MSC_MEDIA_PACKET_SIZE           4096U
+
+    #define MEM_LUN_NUM                     1U
+
+#else
+    /* minimums to compile but not allocate storage */
+    #define USBD_MSC_INTERFACE              0xF
+    #define MEM_LUN_NUM                     0U
+    #define MSC_DATA_PACKET_SIZE            0U
+    #define MSC_MEDIA_PACKET_SIZE           0U
+    #define MSC_IN_EP                       0U
+    #define MSC_OUT_EP                      0U
+
+#endif
+
 #endif /* __USBD_CONF_H */

+ 288 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_bbb.c

@@ -0,0 +1,288 @@
+/*!
+    \file    usbd_msc_bbb.c
+    \brief   USB BBB(Bulk/Bulk/Bulk) protocol core functions
+    \note    BBB means Bulk-only transport protocol for USB MSC
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#include "usbd_enum.h"
+#include "usbd_msc_bbb.h"
+
+/* local function prototypes ('static') */
+static void msc_bbb_cbw_decode(usb_core_driver *udev);
+static void msc_bbb_data_send(usb_core_driver *udev, uint8_t *pbuf, uint32_t Len);
+static void msc_bbb_abort(usb_core_driver *udev);
+
+/*!
+    \brief      initialize the bbb process
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_init(usb_core_driver *udev)
+{
+    uint8_t lun_num;
+
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_state = BBB_IDLE;
+    msc->bbb_status = BBB_STATUS_NORMAL;
+
+    /* initialize the storage logic unit */
+    for(lun_num = 0U; lun_num < MEM_LUN_NUM; lun_num++) {
+        usbd_mem_fops->mem_init(lun_num);
+    }
+
+    /* flush the Rx FIFO */
+    usbd_fifo_flush(udev, MSC_OUT_EP);
+
+    /* flush the Tx FIFO */
+    usbd_fifo_flush(udev, MSC_IN_EP);
+
+    /* prepare endpoint to receive the first BBB CBW */
+    usbd_ep_recev(udev, MSC_OUT_EP, (uint8_t *)&msc->bbb_cbw, BBB_CBW_LENGTH);
+}
+
+/*!
+    \brief      reset the BBB machine
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_reset(usb_core_driver *udev)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_state = BBB_IDLE;
+    msc->bbb_status = BBB_STATUS_RECOVERY;
+
+    /* prepare endpoint to receive the first BBB command */
+    usbd_ep_recev(udev, MSC_OUT_EP, (uint8_t *)&msc->bbb_cbw, BBB_CBW_LENGTH);
+}
+
+/*!
+    \brief      deinitialize the BBB machine
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_deinit(usb_core_driver *udev)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_state = BBB_IDLE;
+}
+
+/*!
+    \brief      handle BBB data IN stage
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: endpoint number
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_data_in(usb_core_driver *udev, uint8_t ep_num)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    switch(msc->bbb_state) {
+    case BBB_DATA_IN:
+        if(scsi_process_cmd(udev, msc->bbb_cbw.bCBWLUN, &msc->bbb_cbw.CBWCB[0]) < 0) {
+            msc_bbb_csw_send(udev, CSW_CMD_FAILED);
+        }
+        break;
+
+    case BBB_SEND_DATA:
+    case BBB_LAST_DATA_IN:
+        msc_bbb_csw_send(udev, CSW_CMD_PASSED);
+        break;
+
+    default:
+        break;
+    }
+}
+
+/*!
+    \brief      handle BBB data OUT stage
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: endpoint number
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_data_out(usb_core_driver *udev, uint8_t ep_num)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    switch(msc->bbb_state) {
+    case BBB_IDLE:
+        msc_bbb_cbw_decode(udev);
+        break;
+
+    case BBB_DATA_OUT:
+        if(scsi_process_cmd(udev, msc->bbb_cbw.bCBWLUN, &msc->bbb_cbw.CBWCB[0]) < 0) {
+            msc_bbb_csw_send(udev, CSW_CMD_FAILED);
+        }
+        break;
+
+    default:
+        break;
+    }
+}
+
+/*!
+    \brief      send the CSW(command status wrapper)
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  csw_status: CSW status
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_csw_send(usb_core_driver *udev, uint8_t csw_status)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_csw.dCSWSignature = BBB_CSW_SIGNATURE;
+    msc->bbb_csw.bCSWStatus = csw_status;
+    msc->bbb_state = BBB_IDLE;
+
+    usbd_ep_send(udev, MSC_IN_EP, (uint8_t *)&msc->bbb_csw, BBB_CSW_LENGTH);
+
+    /* prepare endpoint to receive next command */
+    usbd_ep_recev(udev, MSC_OUT_EP, (uint8_t *)&msc->bbb_cbw, BBB_CBW_LENGTH);
+}
+
+/*!
+    \brief      complete the clear feature request
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: endpoint number
+    \param[out] none
+    \retval     none
+*/
+void msc_bbb_clrfeature(usb_core_driver *udev, uint8_t ep_num)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if(msc->bbb_status == BBB_STATUS_ERROR) { /* bad CBW signature */
+        usbd_ep_stall(udev, MSC_IN_EP);
+
+        msc->bbb_status = BBB_STATUS_NORMAL;
+    } else if(((ep_num & 0x80U) == 0x80U) && (msc->bbb_status != BBB_STATUS_RECOVERY)) {
+        msc_bbb_csw_send(udev, CSW_CMD_FAILED);
+    } else {
+
+    }
+}
+
+/*!
+    \brief      decode the CBW command and set the BBB state machine accordingly
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     none
+*/
+static void msc_bbb_cbw_decode(usb_core_driver *udev)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_csw.dCSWTag = msc->bbb_cbw.dCBWTag;
+    msc->bbb_csw.dCSWDataResidue = msc->bbb_cbw.dCBWDataTransferLength;
+
+    if((BBB_CBW_LENGTH != usbd_rxcount_get(udev, MSC_OUT_EP)) ||
+            (BBB_CBW_SIGNATURE != msc->bbb_cbw.dCBWSignature) ||
+            (msc->bbb_cbw.bCBWLUN > 1U) ||
+            (msc->bbb_cbw.bCBWCBLength < 1U) ||
+            (msc->bbb_cbw.bCBWCBLength > 16U)) {
+        /* illegal command handler */
+        scsi_sense_code(udev, msc->bbb_cbw.bCBWLUN, ILLEGAL_REQUEST, INVALID_CDB);
+
+        msc->bbb_status = BBB_STATUS_ERROR;
+
+        msc_bbb_abort(udev);
+    } else {
+        if(scsi_process_cmd(udev, msc->bbb_cbw.bCBWLUN, &msc->bbb_cbw.CBWCB[0]) < 0) {
+            msc_bbb_abort(udev);
+        } else if((BBB_DATA_IN != msc->bbb_state) &&
+                  (BBB_DATA_OUT != msc->bbb_state) &&
+                  (BBB_LAST_DATA_IN != msc->bbb_state)) { /* burst xfer handled internally */
+            if(msc->bbb_datalen > 0U) {
+                msc_bbb_data_send(udev, msc->bbb_data, msc->bbb_datalen);
+            } else if(0U == msc->bbb_datalen) {
+                msc_bbb_csw_send(udev, CSW_CMD_PASSED);
+            } else {
+
+            }
+        } else {
+
+        }
+    }
+}
+
+/*!
+    \brief      send the requested data
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  buf: pointer to data buffer
+    \param[in]  len: data length
+    \param[out] none
+    \retval     none
+*/
+static void msc_bbb_data_send(usb_core_driver *udev, uint8_t *buf, uint32_t len)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    len = USB_MIN(msc->bbb_cbw.dCBWDataTransferLength, len);
+
+    msc->bbb_csw.dCSWDataResidue -= len;
+    msc->bbb_csw.bCSWStatus = CSW_CMD_PASSED;
+    msc->bbb_state = BBB_SEND_DATA;
+
+    usbd_ep_send(udev, MSC_IN_EP, buf, len);
+}
+
+/*!
+    \brief      abort the current transfer
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     none
+*/
+static void msc_bbb_abort(usb_core_driver *udev)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if((0U == msc->bbb_cbw.bmCBWFlags) &&
+            (0U != msc->bbb_cbw.dCBWDataTransferLength) &&
+            (BBB_STATUS_NORMAL == msc->bbb_status)) {
+        usbd_ep_stall(udev, MSC_OUT_EP);
+    }
+
+    usbd_ep_stall(udev, MSC_IN_EP);
+
+    if(msc->bbb_status == BBB_STATUS_ERROR) {
+        usbd_ep_recev(udev, MSC_OUT_EP, (uint8_t *)&msc->bbb_cbw, BBB_CBW_LENGTH);
+    }
+}

+ 101 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_bbb.h

@@ -0,0 +1,101 @@
+/*!
+    \file    usbd_msc_bbb.h
+    \brief   the header file of the usbd_msc_bot.c file
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USBD_MSC_BBB_H
+#define __USBD_MSC_BBB_H
+
+#include "usbd_core.h"
+#include "msc_bbb.h"
+#include "usbd_msc_mem.h"
+#include "usbd_msc_scsi.h"
+
+/* MSC BBB state */
+enum msc_bbb_state {
+    BBB_IDLE = 0U,          /*!< idle state  */
+    BBB_DATA_OUT,           /*!< data OUT state */
+    BBB_DATA_IN,            /*!< data IN state */
+    BBB_LAST_DATA_IN,       /*!< last data IN state */
+    BBB_SEND_DATA           /*!< send immediate data state */
+};
+
+/* MSC BBB status */
+enum msc_bbb_status {
+    BBB_STATUS_NORMAL = 0U, /*!< normal status */
+    BBB_STATUS_RECOVERY,    /*!< recovery status*/
+    BBB_STATUS_ERROR        /*!< error status */
+};
+
+typedef struct {
+    uint8_t bbb_data[MSC_MEDIA_PACKET_SIZE];
+
+    uint8_t max_lun;
+    uint8_t bbb_state;
+    uint8_t bbb_status;
+
+    uint32_t bbb_datalen;
+
+    msc_bbb_cbw bbb_cbw;
+    msc_bbb_csw bbb_csw;
+
+    uint8_t scsi_sense_head;
+    uint8_t scsi_sense_tail;
+
+    uint32_t scsi_blk_size[MEM_LUN_NUM];
+    uint32_t scsi_blk_nbr[MEM_LUN_NUM];
+
+    uint32_t scsi_blk_addr;
+    uint32_t scsi_blk_len;
+    uint32_t scsi_disk_pop;
+
+    msc_scsi_sense scsi_sense[SENSE_LIST_DEEPTH];
+} usbd_msc_handler;
+
+/* function declarations */
+/* initialize the bbb process */
+void msc_bbb_init(usb_core_driver *udev);
+/* reset the BBB machine */
+void msc_bbb_reset(usb_core_driver *udev);
+/* deinitialize the BBB machine */
+void msc_bbb_deinit(usb_core_driver *udev);
+/* handle BBB data IN stage */
+void msc_bbb_data_in(usb_core_driver *udev, uint8_t ep_num);
+/* handle BBB data OUT stage */
+void msc_bbb_data_out(usb_core_driver *udev, uint8_t ep_num);
+/* send the CSW(command status wrapper) */
+void msc_bbb_csw_send(usb_core_driver *udev, uint8_t csw_status);
+/* complete the clear feature request */
+void msc_bbb_clrfeature(usb_core_driver *udev, uint8_t ep_num);
+
+#endif /* __USBD_MSC_BBB_H */

+ 312 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_core.c

@@ -0,0 +1,312 @@
+/*!
+    \file    usbd_msc_core.c
+    \brief   USB MSC device class core functions
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2020-12-10, V3.0.1, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#include "usbd_enum.h"
+#include "usbd_msc_bbb.h"
+#include "usbd_msc_core.h"
+#include <string.h>
+
+#define USBD_VID                    0x28E9U
+#define USBD_PID                    0x028FU
+
+/* local function prototypes ('static') */
+static uint8_t msc_core_init(usb_dev *udev, uint8_t config_index);
+static uint8_t msc_core_deinit(usb_dev *udev, uint8_t config_index);
+static uint8_t msc_core_req(usb_dev *udev, usb_req *req);
+static uint8_t msc_core_in(usb_dev *udev, uint8_t ep_num);
+static uint8_t msc_core_out(usb_dev *udev, uint8_t ep_num);
+
+usb_class_core msc_class = {
+    .init     = msc_core_init,
+    .deinit   = msc_core_deinit,
+
+    .req_proc = msc_core_req,
+
+    .data_in  = msc_core_in,
+    .data_out = msc_core_out
+};
+
+/* note: it should use the C99 standard when compiling the below codes */
+/* USB standard device descriptor */
+__ALIGN_BEGIN const usb_desc_dev msc_dev_desc __ALIGN_END = {
+    .header = {
+        .bLength           = USB_DEV_DESC_LEN,
+        .bDescriptorType   = USB_DESCTYPE_DEV
+    },
+    .bcdUSB                = 0x0200U,
+    .bDeviceClass          = 0x00U,
+    .bDeviceSubClass       = 0x00U,
+    .bDeviceProtocol       = 0x00U,
+    .bMaxPacketSize0       = USB_FS_EP0_MAX_LEN,
+    .idVendor              = USBD_VID,
+    .idProduct             = USBD_PID,
+    .bcdDevice             = 0x0100U,
+    .iManufacturer         = STR_IDX_MFC,
+    .iProduct              = STR_IDX_PRODUCT,
+    .iSerialNumber         = STR_IDX_SERIAL,
+    .bNumberConfigurations = USBD_CFG_MAX_NUM
+};
+
+/* USB device configuration descriptor */
+__ALIGN_BEGIN const usb_desc_config_set msc_config_desc __ALIGN_END = {
+    .config =
+    {
+        .header = {
+            .bLength         = sizeof(usb_desc_config),
+            .bDescriptorType = USB_DESCTYPE_CONFIG
+        },
+        .wTotalLength        = USB_MSC_CONFIG_DESC_SIZE,
+        .bNumInterfaces      = 0x01U,
+        .bConfigurationValue = 0x01U,
+        .iConfiguration      = 0x00U,
+        .bmAttributes        = 0x80U, // default is 0xC0 - self powered which is wrong
+        .bMaxPower           = 0x32U
+    },
+
+    .msc_itf =
+    {
+        .header = {
+            .bLength         = sizeof(usb_desc_itf),
+            .bDescriptorType = USB_DESCTYPE_ITF
+        },
+        .bInterfaceNumber    = 0x00U,
+        .bAlternateSetting   = 0x00U,
+        .bNumEndpoints       = 0x02U,
+        .bInterfaceClass     = USB_CLASS_MSC,
+        .bInterfaceSubClass  = USB_MSC_SUBCLASS_SCSI,
+        .bInterfaceProtocol  = USB_MSC_PROTOCOL_BBB,
+        .iInterface          = 0x00U
+    },
+
+    .msc_epin =
+    {
+        .header = {
+            .bLength         = sizeof(usb_desc_ep),
+            .bDescriptorType = USB_DESCTYPE_EP
+        },
+        .bEndpointAddress    = MSC_IN_EP,
+        .bmAttributes        = USB_EP_ATTR_BULK,
+        .wMaxPacketSize      = MSC_EPIN_SIZE,
+        .bInterval           = 0x00U
+    },
+
+    .msc_epout =
+    {
+        .header = {
+            .bLength         = sizeof(usb_desc_ep),
+            .bDescriptorType = USB_DESCTYPE_EP
+        },
+        .bEndpointAddress    = MSC_OUT_EP,
+        .bmAttributes        = USB_EP_ATTR_BULK,
+        .wMaxPacketSize      = MSC_EPOUT_SIZE,
+        .bInterval           = 0x00U
+    }
+};
+
+/* USB language ID descriptor */
+__ALIGN_BEGIN const usb_desc_LANGID usbd_language_id_desc __ALIGN_END = {
+    .header =
+    {
+        .bLength            = sizeof(usb_desc_LANGID),
+        .bDescriptorType    = USB_DESCTYPE_STR
+    },
+    .wLANGID                 = ENG_LANGID
+};
+
+/* USB manufacture string */
+static __ALIGN_BEGIN const usb_desc_str manufacturer_string __ALIGN_END = {
+    .header =
+    {
+        .bLength         = USB_STRING_LEN(10U),
+        .bDescriptorType = USB_DESCTYPE_STR,
+    },
+    .unicode_string = {'G', 'i', 'g', 'a', 'D', 'e', 'v', 'i', 'c', 'e'}
+};
+
+/* USB product string */
+static __ALIGN_BEGIN const usb_desc_str product_string __ALIGN_END = {
+    .header =
+    {
+        .bLength         = USB_STRING_LEN(12U),
+        .bDescriptorType = USB_DESCTYPE_STR,
+    },
+    .unicode_string = {'G', 'D', '3', '2', '-', 'U', 'S', 'B', '_', 'M', 'S', 'C'}
+};
+
+/* USBD serial string */
+static __ALIGN_BEGIN usb_desc_str serial_string __ALIGN_END = {
+    .header =
+    {
+        .bLength         = USB_STRING_LEN(12U),
+        .bDescriptorType = USB_DESCTYPE_STR,
+    }
+};
+
+/* USB string descriptor */
+void *const usbd_msc_strings[] = {
+    [STR_IDX_LANGID]  = (uint8_t *) &usbd_language_id_desc,
+    [STR_IDX_MFC]     = (uint8_t *) &manufacturer_string,
+    [STR_IDX_PRODUCT] = (uint8_t *) &product_string,
+    [STR_IDX_SERIAL]  = (uint8_t *) &serial_string
+};
+
+usb_desc msc_desc = {
+    .dev_desc    = (uint8_t *) &msc_dev_desc,
+    .config_desc = (uint8_t *) &msc_config_desc,
+    .strings     = usbd_msc_strings
+};
+
+static __ALIGN_BEGIN uint8_t usbd_msc_maxlun = 0U __ALIGN_END;
+
+/*!
+    \brief      initialize the MSC device
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  config_index: configuration index
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t msc_core_init(usb_dev *udev, uint8_t config_index)
+{
+    static __ALIGN_BEGIN usbd_msc_handler msc_handler __ALIGN_END;
+
+    memset((void *)&msc_handler, 0U, sizeof(usbd_msc_handler));
+
+    udev->dev.class_data[USBD_MSC_INTERFACE] = (void *)&msc_handler;
+
+    /* configure MSC Tx endpoint */
+    usbd_ep_setup(udev, &(msc_config_desc.msc_epin));
+
+    /* configure MSC Rx endpoint */
+    usbd_ep_setup(udev, &(msc_config_desc.msc_epout));
+
+    /* initialize the BBB layer */
+    msc_bbb_init(udev);
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      deinitialize the MSC device
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  config_index: configuration index
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t msc_core_deinit(usb_dev *udev, uint8_t config_index)
+{
+    /* clear MSC endpoints */
+    usbd_ep_clear(udev, MSC_IN_EP);
+    usbd_ep_clear(udev, MSC_OUT_EP);
+
+    /* deinitialize the BBB layer */
+    msc_bbb_deinit(udev);
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      handle the MSC class-specific and standard requests
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  req: device class-specific request
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t msc_core_req(usb_dev *udev, usb_req *req)
+{
+    usb_transc *transc = &udev->dev.transc_in[0];
+
+    switch(req->bRequest) {
+    case BBB_GET_MAX_LUN :
+        if((0U == req->wValue) &&
+                (1U == req->wLength) &&
+                (0x80U == (req->bmRequestType & 0x80U))) {
+            usbd_msc_maxlun = (uint8_t)usbd_mem_fops->mem_maxlun();
+
+            transc->xfer_buf = &usbd_msc_maxlun;
+            transc->remain_len = 1U;
+        } else {
+            return USBD_FAIL;
+        }
+        break;
+
+    case BBB_RESET :
+        if((0U == req->wValue) &&
+                (0U == req->wLength) &&
+                (0x80U != (req->bmRequestType & 0x80U))) {
+            msc_bbb_reset(udev);
+        } else {
+            return USBD_FAIL;
+        }
+        break;
+
+    case USB_CLEAR_FEATURE:
+        msc_bbb_clrfeature(udev, (uint8_t)req->wIndex);
+        break;
+
+    default:
+        return USBD_FAIL;
+    }
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      handle data in stage
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: the endpoint number
+    \param[out] none
+    \retval     none
+*/
+static uint8_t msc_core_in(usb_dev *udev, uint8_t ep_num)
+{
+    msc_bbb_data_in(udev, ep_num);
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      handle data out stage
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: the endpoint number
+    \param[out] none
+    \retval     none
+*/
+static uint8_t msc_core_out(usb_dev *udev, uint8_t ep_num)
+{
+    msc_bbb_data_out(udev, ep_num);
+
+    return USBD_OK;
+}

+ 59 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_core.h

@@ -0,0 +1,59 @@
+/*!
+    \file    usbd_msc_core.h
+    \brief   the header file of USB MSC device class core functions
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USBD_MSC_CORE_H
+#define __USBD_MSC_CORE_H
+
+#include "usbd_core.h"
+#include "usb_msc.h"
+
+#define USB_MSC_CONFIG_DESC_SIZE          32U
+
+#define MSC_EPIN_SIZE                     MSC_DATA_PACKET_SIZE
+#define MSC_EPOUT_SIZE                    MSC_DATA_PACKET_SIZE
+
+/* USB configuration descriptor structure */
+typedef struct {
+    usb_desc_config         config;
+
+    usb_desc_itf            msc_itf;
+    usb_desc_ep             msc_epin;
+    usb_desc_ep             msc_epout;
+} usb_desc_config_set;
+
+extern usb_desc msc_desc;
+extern usb_class_core msc_class;
+
+#endif /* __USBD_MSC_CORE_H */

+ 71 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_data.c

@@ -0,0 +1,71 @@
+/*!
+    \file    usbd_msc_data.c
+    \brief   USB MSC vital inquiry pages and sense data
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#include "usbd_msc_data.h"
+
+/* USB mass storage page 0 inquiry data */
+const uint8_t msc_page00_inquiry_data[] = {
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    (INQUIRY_PAGE00_LENGTH - 4U),
+    0x80U,
+    0x83U,
+};
+
+/* USB mass storage sense 6 data */
+const uint8_t msc_mode_sense6_data[] = {
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U
+};
+
+/* USB mass storage sense 10 data */
+const uint8_t msc_mode_sense10_data[] = {
+    0x00U,
+    0x06U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U,
+    0x00U
+};

+ 50 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_data.h

@@ -0,0 +1,50 @@
+/*!
+    \file    usbd_msc_data.h
+    \brief   the header file of the usbd_msc_data.c file
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USBD_MSC_DATA_H
+#define __USBD_MSC_DATA_H
+
+#include "usbd_conf.h"
+
+#define MODE_SENSE6_LENGTH                 8U
+#define MODE_SENSE10_LENGTH                8U
+#define INQUIRY_PAGE00_LENGTH              96U
+#define FORMAT_CAPACITIES_LENGTH           20U
+
+extern const uint8_t msc_page00_inquiry_data[];
+extern const uint8_t msc_mode_sense6_data[];
+extern const uint8_t msc_mode_sense10_data[];
+
+#endif /* __USBD_MSC_DATA_H */

+ 59 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_mem.h

@@ -0,0 +1,59 @@
+/*!
+    \file    usbd_msc_mem.h
+    \brief   header file for storage memory
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USBD_MSC_MEM_H
+#define __USBD_MSC_MEM_H
+
+#include "usbd_conf.h"
+
+#define USBD_STD_INQUIRY_LENGTH     36U
+
+typedef struct {
+    int8_t (*mem_init)(uint8_t lun);
+    int8_t (*mem_ready)(uint8_t lun);
+    int8_t (*mem_protected)(uint8_t lun);
+    int8_t (*mem_read)(uint8_t lun, uint8_t *buf, uint32_t block_addr, uint16_t block_len);
+    int8_t (*mem_write)(uint8_t lun, uint8_t *buf, uint32_t block_addr, uint16_t block_len);
+    int8_t (*mem_maxlun)(void);
+
+    uint8_t *mem_toc_data;
+    uint8_t *mem_inquiry_data[MEM_LUN_NUM];
+    uint32_t mem_block_size[MEM_LUN_NUM];
+    uint32_t mem_block_len[MEM_LUN_NUM];
+} usbd_mem_cb;
+
+extern usbd_mem_cb *usbd_mem_fops;
+
+#endif /* __USBD_MSC_MEM_H */

+ 744 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_scsi.c

@@ -0,0 +1,744 @@
+/*!
+    \file    usbd_msc_scsi.c
+    \brief   USB SCSI layer functions
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#include "usbd_enum.h"
+#include "usbd_msc_bbb.h"
+#include "usbd_msc_scsi.h"
+#include "usbd_msc_data.h"
+
+/* local function prototypes ('static') */
+static int8_t scsi_test_unit_ready(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_mode_select6(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_mode_select10(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_inquiry(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_read_format_capacity(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_read_capacity10(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_request_sense(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_mode_sense6(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_toc_cmd_read(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_mode_sense10(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_write10(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_read10(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static int8_t scsi_verify10(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+
+static int8_t scsi_process_read(usb_core_driver *udev, uint8_t lun);
+static int8_t scsi_process_write(usb_core_driver *udev, uint8_t lun);
+
+static inline int8_t scsi_check_address_range(usb_core_driver *udev, uint8_t lun, uint32_t blk_offset, uint16_t blk_nbr);
+static inline int8_t scsi_format_cmd(usb_core_driver *udev, uint8_t lun);
+static inline int8_t scsi_start_stop_unit(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+static inline int8_t scsi_allow_medium_removal(usb_core_driver *udev, uint8_t lun, uint8_t *params);
+
+/*!
+    \brief      process SCSI commands
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+int8_t scsi_process_cmd(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    switch(params[0]) {
+    case SCSI_TEST_UNIT_READY:
+        return scsi_test_unit_ready(udev, lun, params);
+
+    case SCSI_REQUEST_SENSE:
+        return scsi_request_sense(udev, lun, params);
+
+    case SCSI_INQUIRY:
+        return scsi_inquiry(udev, lun, params);
+
+    case SCSI_START_STOP_UNIT:
+        return scsi_start_stop_unit(udev, lun, params);
+
+    case SCSI_ALLOW_MEDIUM_REMOVAL:
+        return scsi_allow_medium_removal(udev, lun, params);
+
+    case SCSI_MODE_SENSE6:
+        return scsi_mode_sense6(udev, lun, params);
+
+    case SCSI_MODE_SENSE10:
+        return scsi_mode_sense10(udev, lun, params);
+
+    case SCSI_READ_FORMAT_CAPACITIES:
+        return scsi_read_format_capacity(udev, lun, params);
+
+    case SCSI_READ_CAPACITY10:
+        return scsi_read_capacity10(udev, lun, params);
+
+    case SCSI_READ10:
+        return scsi_read10(udev, lun, params);
+
+    case SCSI_WRITE10:
+        return scsi_write10(udev, lun, params);
+
+    case SCSI_VERIFY10:
+        return scsi_verify10(udev, lun, params);
+
+    case SCSI_FORMAT_UNIT:
+        return scsi_format_cmd(udev, lun);
+
+    case SCSI_READ_TOC_DATA:
+        return scsi_toc_cmd_read(udev, lun, params);
+
+    case SCSI_MODE_SELECT6:
+        return scsi_mode_select6(udev, lun, params);
+
+    case SCSI_MODE_SELECT10:
+        return scsi_mode_select10(udev, lun, params);
+
+    default:
+        scsi_sense_code(udev, lun, ILLEGAL_REQUEST, INVALID_CDB);
+        return -1;
+    }
+}
+
+/*!
+    \brief      load the last error code in the error list
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  skey: sense key
+    \param[in]  asc: additional sense key
+    \param[out] none
+    \retval     none
+*/
+void scsi_sense_code(usb_core_driver *udev, uint8_t lun, uint8_t skey, uint8_t asc)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->scsi_sense[msc->scsi_sense_tail].SenseKey = skey;
+    msc->scsi_sense[msc->scsi_sense_tail].ASC = asc << 8U;
+    msc->scsi_sense_tail++;
+
+    if(SENSE_LIST_DEEPTH == msc->scsi_sense_tail) {
+        msc->scsi_sense_tail = 0U;
+    }
+}
+
+/*!
+    \brief      process SCSI Test Unit Ready command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_test_unit_ready(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    /* case 9 : Hi > D0 */
+    if(0U != msc->bbb_cbw.dCBWDataTransferLength) {
+        scsi_sense_code(udev, msc->bbb_cbw.bCBWLUN, ILLEGAL_REQUEST, INVALID_CDB);
+
+        return -1;
+    }
+
+    if(0 != usbd_mem_fops->mem_ready(lun)) {
+        scsi_sense_code(udev, lun, NOT_READY, MEDIUM_NOT_PRESENT);
+
+        return -1;
+    }
+
+    msc->bbb_datalen = 0U;
+
+    return 0;
+}
+
+/*!
+    \brief      process Inquiry command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_mode_select6(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_datalen = 0U;
+
+    return 0;
+}
+
+/*!
+    \brief      process Inquiry command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_mode_select10(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_datalen = 0U;
+
+    return 0;
+}
+
+/*!
+    \brief      process Inquiry command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_inquiry(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint8_t *page = NULL;
+    uint16_t len = 0U;
+
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if(params[1] & 0x01U) {
+        /* Evpd is set */
+        page = (uint8_t *)msc_page00_inquiry_data;
+
+        len = INQUIRY_PAGE00_LENGTH;
+    } else {
+        page = (uint8_t *)usbd_mem_fops->mem_inquiry_data[lun];
+
+        len = (uint16_t)(page[4] + 5U);
+
+        if(params[4] <= len) {
+            len = params[4];
+        }
+    }
+
+    msc->bbb_datalen = len;
+
+    while(len) {
+        len--;
+        msc->bbb_data[len] = page[len];
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      process Read Capacity 10 command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_read_capacity10(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint32_t blk_num = usbd_mem_fops->mem_block_len[lun] - 1U;
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->scsi_blk_nbr[lun] = usbd_mem_fops->mem_block_len[lun];
+    msc->scsi_blk_size[lun] = usbd_mem_fops->mem_block_size[lun];
+
+    msc->bbb_data[0] = (uint8_t)(blk_num >> 24U);
+    msc->bbb_data[1] = (uint8_t)(blk_num >> 16U);
+    msc->bbb_data[2] = (uint8_t)(blk_num >> 8U);
+    msc->bbb_data[3] = (uint8_t)(blk_num);
+
+    msc->bbb_data[4] = (uint8_t)(msc->scsi_blk_size[lun] >> 24U);
+    msc->bbb_data[5] = (uint8_t)(msc->scsi_blk_size[lun] >> 16U);
+    msc->bbb_data[6] = (uint8_t)(msc->scsi_blk_size[lun] >> 8U);
+    msc->bbb_data[7] = (uint8_t)(msc->scsi_blk_size[lun]);
+
+    msc->bbb_datalen = 8U;
+
+    return 0;
+}
+
+/*!
+    \brief      process Read Format Capacity command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_read_format_capacity(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint16_t i = 0U;
+    uint32_t blk_size = usbd_mem_fops->mem_block_size[lun];
+    uint32_t blk_num = usbd_mem_fops->mem_block_len[lun];
+    uint32_t blk_nbr = blk_num - 1U;
+
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    for(i = 0U; i < 12U; i++) {
+        msc->bbb_data[i] = 0U;
+    }
+
+    msc->bbb_data[3] = 0x08U;
+    msc->bbb_data[4] = (uint8_t)(blk_nbr >> 24U);
+    msc->bbb_data[5] = (uint8_t)(blk_nbr >> 16U);
+    msc->bbb_data[6] = (uint8_t)(blk_nbr >>  8U);
+    msc->bbb_data[7] = (uint8_t)(blk_nbr);
+
+    msc->bbb_data[8] = 0x02U;
+    msc->bbb_data[9] = (uint8_t)(blk_size >> 16U);
+    msc->bbb_data[10] = (uint8_t)(blk_size >> 8U);
+    msc->bbb_data[11] = (uint8_t)(blk_size);
+
+    msc->bbb_datalen = 12U;
+
+    return 0;
+}
+
+/*!
+    \brief      process Mode Sense6 command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_mode_sense6(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint16_t len = 8U;
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_datalen = len;
+
+    while(len) {
+        len--;
+        msc->bbb_data[len] = msc_mode_sense6_data[len];
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      process Mode Sense10 command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_mode_sense10(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint16_t len = 8U;
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_datalen = len;
+
+    while(len) {
+        len--;
+        msc->bbb_data[len] = msc_mode_sense10_data[len];
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      process Request Sense command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_request_sense(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint8_t i = 0U;
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    for(i = 0U; i < REQUEST_SENSE_DATA_LEN; i++) {
+        msc->bbb_data[i] = 0U;
+    }
+
+    msc->bbb_data[0] = 0x70U;
+    msc->bbb_data[7] = REQUEST_SENSE_DATA_LEN - 6U;
+
+    if((msc->scsi_sense_head != msc->scsi_sense_tail)) {
+        msc->bbb_data[2] = msc->scsi_sense[msc->scsi_sense_head].SenseKey;
+        msc->bbb_data[12] = msc->scsi_sense[msc->scsi_sense_head].ASCQ;
+        msc->bbb_data[13] = msc->scsi_sense[msc->scsi_sense_head].ASC;
+        msc->scsi_sense_head++;
+
+        if(msc->scsi_sense_head == SENSE_LIST_DEEPTH) {
+            msc->scsi_sense_head = 0U;
+        }
+    }
+
+    msc->bbb_datalen = USB_MIN(REQUEST_SENSE_DATA_LEN, params[4]);
+
+    return 0;
+}
+
+
+/*
+byte 5 (index 4) of start stop unit 
+7  6  5  4  3  2  1  0
+[ PWRCON ]  R  F  E  S
+S = start
+E = loadeject
+*/
+
+#define STARTSTOPUNIT_START (1)
+#define STARTSTOPUNIT_LOADEJECT (2)
+
+/*!
+    \brief      process Start Stop Unit command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static inline int8_t scsi_start_stop_unit(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_datalen = 0U;
+
+    /* not part of GDS class! make sure to bring this over if updating USB class */
+    // if start = 0, load eject = 1,  host is advising of media eject (usually a device removal too)
+    if ((params[4] & STARTSTOPUNIT_LOADEJECT)) {
+        if (!(params[4] & STARTSTOPUNIT_START))
+            msc->scsi_disk_pop = 1U;
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      process Allow Medium Removal command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static inline int8_t scsi_allow_medium_removal(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    msc->bbb_datalen = 0U;
+
+    return 0;
+}
+
+/*!
+    \brief      process Read10 command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_read10(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if(msc->bbb_state == BBB_IDLE) {
+        /* direction is from device to host */
+        if(0x80U != (msc->bbb_cbw.bmCBWFlags & 0x80U)) {
+            scsi_sense_code(udev, msc->bbb_cbw.bCBWLUN, ILLEGAL_REQUEST, INVALID_CDB);
+
+            return -1;
+        }
+
+        if(0 != usbd_mem_fops->mem_ready(lun)) {
+            scsi_sense_code(udev, lun, NOT_READY, MEDIUM_NOT_PRESENT);
+
+            return -1;
+        }
+
+        msc->scsi_blk_addr = (params[2] << 24U) | (params[3] << 16U) | \
+                             (params[4] << 8U) |  params[5];
+
+        msc->scsi_blk_len = (params[7] << 8U) | params[8];
+
+        if(scsi_check_address_range(udev, lun, msc->scsi_blk_addr, (uint16_t)msc->scsi_blk_len) < 0) {
+            return -1; /* error */
+        }
+
+        msc->bbb_state = BBB_DATA_IN;
+
+        msc->scsi_blk_addr *= msc->scsi_blk_size[lun];
+        msc->scsi_blk_len  *= msc->scsi_blk_size[lun];
+
+        /* cases 4,5 : Hi <> Dn */
+        if(msc->bbb_cbw.dCBWDataTransferLength != msc->scsi_blk_len) {
+            scsi_sense_code(udev, msc->bbb_cbw.bCBWLUN, ILLEGAL_REQUEST, INVALID_CDB);
+
+            return -1;
+        }
+    }
+
+    msc->bbb_datalen = MSC_MEDIA_PACKET_SIZE;
+
+    return scsi_process_read(udev, lun);
+}
+
+/*!
+    \brief      process Write10 command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_write10(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if(BBB_IDLE == msc->bbb_state) {
+        /* case 8 : Hi <> Do */
+        if(0x80U == (msc->bbb_cbw.bmCBWFlags & 0x80U)) {
+            scsi_sense_code(udev, msc->bbb_cbw.bCBWLUN, ILLEGAL_REQUEST, INVALID_CDB);
+
+            return -1;
+        }
+
+        /* check whether media is ready */
+        if(0 != usbd_mem_fops->mem_ready(lun)) {
+            scsi_sense_code(udev, lun, NOT_READY, MEDIUM_NOT_PRESENT);
+
+            return -1;
+        }
+
+        /* check if media is write-protected */
+        if(0 != usbd_mem_fops->mem_protected(lun)) {
+            scsi_sense_code(udev, lun, NOT_READY, WRITE_PROTECTED);
+
+            return -1;
+        }
+
+        msc->scsi_blk_addr = (params[2] << 24U) | (params[3] << 16U) | \
+                             (params[4] << 8U) |  params[5];
+
+        msc->scsi_blk_len = (params[7] << 8U) | params[8];
+
+        /* check if LBA address is in the right range */
+        if(scsi_check_address_range(udev, lun, msc->scsi_blk_addr, (uint16_t)msc->scsi_blk_len) < 0) {
+            return -1; /* error */
+        }
+
+        msc->scsi_blk_addr *= msc->scsi_blk_size[lun];
+        msc->scsi_blk_len  *= msc->scsi_blk_size[lun];
+
+        /* cases 3,11,13 : Hn,Ho <> D0 */
+        if(msc->bbb_cbw.dCBWDataTransferLength != msc->scsi_blk_len) {
+            scsi_sense_code(udev, msc->bbb_cbw.bCBWLUN, ILLEGAL_REQUEST, INVALID_CDB);
+
+            return -1;
+        }
+
+        /* prepare endpoint to receive first data packet */
+        msc->bbb_state = BBB_DATA_OUT;
+
+        usbd_ep_recev(udev,
+                      MSC_OUT_EP,
+                      msc->bbb_data,
+                      USB_MIN(msc->scsi_blk_len, MSC_MEDIA_PACKET_SIZE));
+    } else { /* write process ongoing */
+        return scsi_process_write(udev, lun);
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      process Verify10 command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_verify10(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if(0x02U == (params[1] & 0x02U)) {
+        scsi_sense_code(udev, lun, ILLEGAL_REQUEST, INVALID_FIELED_IN_COMMAND);
+
+        return -1; /* error, verify mode not supported*/
+    }
+
+    if(scsi_check_address_range(udev, lun, msc->scsi_blk_addr, (uint16_t)msc->scsi_blk_len) < 0) {
+        return -1; /* error */
+    }
+
+    msc->bbb_datalen = 0U;
+
+    return 0;
+}
+
+/*!
+    \brief      check address range
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  blk_offset: block offset
+    \param[in]  blk_nbr: number of block to be processed
+    \param[out] none
+    \retval     status
+*/
+static inline int8_t scsi_check_address_range(usb_core_driver *udev, uint8_t lun, uint32_t blk_offset, uint16_t blk_nbr)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    if((blk_offset + blk_nbr) > msc->scsi_blk_nbr[lun]) {
+        scsi_sense_code(udev, lun, ILLEGAL_REQUEST, ADDRESS_OUT_OF_RANGE);
+
+        return -1;
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      handle read process
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_process_read(usb_core_driver *udev, uint8_t lun)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    uint32_t len = USB_MIN(msc->scsi_blk_len, MSC_MEDIA_PACKET_SIZE);
+
+    if(usbd_mem_fops->mem_read(lun,
+                               msc->bbb_data,
+                               msc->scsi_blk_addr,
+                               (uint16_t)(len / msc->scsi_blk_size[lun])) < 0) {
+        scsi_sense_code(udev, lun, HARDWARE_ERROR, UNRECOVERED_READ_ERROR);
+
+        return -1;
+    }
+
+    usbd_ep_send(udev, MSC_IN_EP, msc->bbb_data, len);
+
+    msc->scsi_blk_addr += len;
+    msc->scsi_blk_len  -= len;
+
+    /* case 6 : Hi = Di */
+    msc->bbb_csw.dCSWDataResidue -= len;
+
+    if(0U == msc->scsi_blk_len) {
+        msc->bbb_state = BBB_LAST_DATA_IN;
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      handle write process
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_process_write(usb_core_driver *udev, uint8_t lun)
+{
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    uint32_t len = USB_MIN(msc->scsi_blk_len, MSC_MEDIA_PACKET_SIZE);
+
+    if(usbd_mem_fops->mem_write(lun,
+                                msc->bbb_data,
+                                msc->scsi_blk_addr,
+                                (uint16_t)(len / msc->scsi_blk_size[lun])) < 0) {
+        scsi_sense_code(udev, lun, HARDWARE_ERROR, WRITE_FAULT);
+
+        return -1;
+    }
+
+    msc->scsi_blk_addr += len;
+    msc->scsi_blk_len  -= len;
+
+    /* case 12 : Ho = Do */
+    msc->bbb_csw.dCSWDataResidue -= len;
+
+    if(0U == msc->scsi_blk_len) {
+        msc_bbb_csw_send(udev, CSW_CMD_PASSED);
+    } else {
+        /* prepare endpoint to receive next packet */
+        usbd_ep_recev(udev,
+                      MSC_OUT_EP,
+                      msc->bbb_data,
+                      USB_MIN(msc->scsi_blk_len, MSC_MEDIA_PACKET_SIZE));
+    }
+
+    return 0;
+}
+
+/*!
+    \brief      process Format Unit command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[out] none
+    \retval     status
+*/
+static inline int8_t scsi_format_cmd(usb_core_driver *udev, uint8_t lun)
+{
+    return 0;
+}
+
+/*!
+    \brief      process Read_Toc command
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  lun: logical unit number
+    \param[in]  params: command parameters
+    \param[out] none
+    \retval     status
+*/
+static int8_t scsi_toc_cmd_read(usb_core_driver *udev, uint8_t lun, uint8_t *params)
+{
+    uint8_t *pPage;
+    uint16_t len;
+
+    usbd_msc_handler *msc = (usbd_msc_handler *)udev->dev.class_data[USBD_MSC_INTERFACE];
+
+    pPage = (uint8_t *)&usbd_mem_fops->mem_toc_data[lun * READ_TOC_CMD_LEN];
+    len = (uint16_t)pPage[1] + 2U;
+
+    msc->bbb_datalen = len;
+
+    while(len) {
+        len--;
+        msc->bbb_data[len] = pPage[len];
+    }
+
+    return 0;
+}

+ 51 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_msc_scsi.h

@@ -0,0 +1,51 @@
+/*!
+    \file    usbd_msc_scsi.h
+    \brief   the header file of the usbd_msc_scsi.c file
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2021-07-30, V3.1.0, firmware for GD32F20x
+*/
+
+/*
+    Copyright (c) 2021, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USBD_MSC_SCSI_H
+#define __USBD_MSC_SCSI_H
+
+#include "usbd_msc_data.h"
+#include "usbd_msc_bbb.h"
+#include "msc_scsi.h"
+
+#define SENSE_LIST_DEEPTH                           4U
+
+/* function declarations */
+/* process SCSI commands */
+int8_t scsi_process_cmd(usb_core_driver *udev, uint8_t lun, uint8_t *cmd);
+/* load the last error code in the error list */
+void scsi_sense_code(usb_core_driver *udev, uint8_t lun, uint8_t skey, uint8_t asc);
+
+#endif /* __USBD_MSC_SCSI_H */

+ 50 - 99
lib/ZuluSCSI_platform_GD32F450/ZuluSCSI_platform.cpp

@@ -133,17 +133,14 @@ void SysTick_Handle_PreEmptively()
 // Clock has already been initialized by system_gd32f20x.c
 void platform_init()
 {
-
     SystemCoreClockUpdate();
-    nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
+
     // Enable SysTick to drive millis()
+    // \todo not sure if this is needed
+    // nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
     g_millisecond_counter = 0;
     SysTick_Config(SystemCoreClock / 1000U);
-    nvic_irq_enable(SysTick_IRQn, 0x00U, 0x00U);
-    //NVIC_SetPriority(SysTick_IRQn, 0x00U);
-    //NVIC_EnableIRQ(SysTick_IRQn);
-
-    
+    NVIC_SetPriority(SysTick_IRQn, 0x00U);
 
     // Enable DWT counter to drive delay_ns()
     g_ns_to_cycles = ((uint64_t)SystemCoreClock << 32) / 1000000000;
@@ -152,11 +149,11 @@ void platform_init()
 
     // Enable debug output on SWO pin
     DBG_CTL0 |= DBG_CTL0_TRACE_IOEN;
-    //TODO figure out if this code needs to execute - TPI_ACPR == 99 at the if statement below
-    //if (TPI->ACPR == 0)
+    // if (TPI->ACPR == 0)
     {
         CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
-        TPI->ACPR = SystemCoreClock / 2000000 - 1; // 2 Mbps baudrate for SWO
+        TPI->ACPR = SystemCoreClock / 115200 - 1; // Serial speed baudrate for SWO
+        // TPI->ACPR = SystemCoreClock / 2000000 - 1; // 2 Mbps baudrate for SWO
         // TPI->ACPR = SystemCoreClock / 30000000 - 1; // 30 Mbps baudrate for SWO
         TPI->SPPR = 2;
         TPI->FFCR = 0x100; // TPIU packet framing disabled
@@ -218,31 +215,45 @@ void platform_init()
     gpio_mode_set(SCSI_TERM_EN_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SCSI_TERM_EN_PIN);
     gpio_output_options_set(SCSI_TERM_EN_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, SCSI_TERM_EN_PIN);
 
+#ifndef SD_USE_SDIO
+    // SD card pins using SPI
+    gpio_init(SD_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SD_CS_PIN);
+    gpio_init(SD_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, SD_CLK_PIN);
+    gpio_init(SD_PORT, GPIO_MODE_IPU, 0, SD_MISO_PIN);
+    gpio_init(SD_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, SD_MOSI_PIN);
+#else
     // SD card pins using SDIO
     gpio_mode_set(SD_SDIO_DATA_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SD_SDIO_D0 | SD_SDIO_D1 | SD_SDIO_D2 | SD_SDIO_D3);
-    gpio_output_options_set(SD_SDIO_DATA_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SD_SDIO_D0 | SD_SDIO_D1 | SD_SDIO_D2 | SD_SDIO_D3);
+    gpio_output_options_set(SD_SDIO_DATA_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SD_SDIO_D0 | SD_SDIO_D1 | SD_SDIO_D2 | SD_SDIO_D3);
     gpio_af_set(SD_SDIO_DATA_PORT, GPIO_AF_12, SD_SDIO_D0 | SD_SDIO_D1 | SD_SDIO_D2 | SD_SDIO_D3);
 
     gpio_mode_set(SD_SDIO_CLK_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SD_SDIO_CLK);
-    gpio_output_options_set(SD_SDIO_CLK_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SD_SDIO_CLK);
+    gpio_output_options_set(SD_SDIO_CLK_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SD_SDIO_CLK);
     gpio_af_set(SD_SDIO_CLK_PORT, GPIO_AF_12, SD_SDIO_CLK);
 
     gpio_mode_set(SD_SDIO_CMD_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SD_SDIO_CMD);
-    gpio_output_options_set(SD_SDIO_CMD_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SD_SDIO_CMD);
+    gpio_output_options_set(SD_SDIO_CMD_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SD_SDIO_CMD);
     gpio_af_set(SD_SDIO_CMD_PORT, GPIO_AF_12, SD_SDIO_CMD);
 
+#endif
+
+    // @TODO confirm dip switch 1 is not longer JTAG NJTRST
+    // Switch to SWD debug port (disable JTAG) to release PB4 as GPIO
+    //gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);   
+
     // DIP switches
     gpio_mode_set(DIP_PORT, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, DIPSW1_PIN | DIPSW2_PIN | DIPSW3_PIN);
 
+
     // LED pins
     gpio_bit_set(LED_PORT, LED_PINS);
     gpio_mode_set(LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, LED_PINS);
     gpio_output_options_set(LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, LED_PINS);
-
-    // SWO trace pin on PB3  
+ 
+    // SWO trace pin on PB3
     gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_3);
-    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3);
-    gpio_af_set(GPIOB, GPIO_AF_0, GPIO_PIN_3);  
+    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, GPIO_PIN_3);
+    gpio_af_set(GPIOB, GPIO_AF_0, GPIO_PIN_3);   
 }
 
 void platform_late_init()
@@ -250,8 +261,6 @@ void platform_late_init()
     logmsg("Platform: ", g_platform_name);
     logmsg("FW Version: ", g_log_firmwareversion);
     
-    
-
     if (gpio_input_bit_get(DIP_PORT, DIPSW3_PIN))
     {
         logmsg("DIPSW3 is ON: Enabling SCSI termination");
@@ -289,57 +298,6 @@ void platform_disable_led(void)
     logmsg("Disabling status LED");
 }
 
-/*****************************************/
-/* Supply voltage monitor                */
-/*****************************************/
-
-// Use ADC to implement supply voltage monitoring for the +3.0V rail.
-// This works by sampling the Vrefint, which has
-// a voltage of 1.2 V, allowing to calculate the VDD voltage.
-static void adc_poll()
-{
-#if PLATFORM_VDD_WARNING_LIMIT_mV > 0
-    static bool initialized = false;
-    static int lowest_vdd_seen = PLATFORM_VDD_WARNING_LIMIT_mV;
-
-    if (!initialized)
-    {
-        rcu_periph_clock_enable(RCU_ADC0);
-        adc_enable(ADC0);
-        adc_calibration_enable(ADC0);
-        adc_channel_16_to_18(ADC_TEMP_VREF_CHANNEL_SWITCH, ENABLE);
-        adc_inserted_channel_config(ADC0, 0, ADC_CHANNEL_17, ADC_SAMPLETIME_144);
-        //TODO can these be safely removed
-        /*
-        adc_external_trigger_source_config(ADC0, ADC_INSERTED_CHANNEL, ADC0_1_2_EXTTRIG_INSERTED_NONE);
-        adc_external_trigger_config(ADC0, ADC_INSERTED_CHANNEL, ENABLE);
-        */
-        adc_software_trigger_enable(ADC0, ADC_INSERTED_CHANNEL);
-        initialized = true;
-    }
-
-    // Read previous result and start new one
-    int adc_value = ADC_IDATA0(ADC0);
-    adc_software_trigger_enable(ADC0, ADC_INSERTED_CHANNEL);
-
-    // adc_value = 1200mV * 4096 / Vdd
-    // => Vdd = 1200mV * 4096 / adc_value
-    // To avoid wasting time on division, compare against
-    // limit directly.
-    const int limit = (1200 * 4096) / PLATFORM_VDD_WARNING_LIMIT_mV;
-    if (adc_value > limit)
-    {
-        // Warn once, and then again if we detect even a lower drop.
-        int vdd_mV = (1200 * 4096) / adc_value;
-        if (vdd_mV < lowest_vdd_seen)
-        {
-            logmsg("WARNING: Detected supply voltage drop to ", vdd_mV, "mV. Verify power supply is adequate.");
-            lowest_vdd_seen = vdd_mV - 50; // Small hysteresis to avoid excessive warnings
-        }
-    }
-#endif
-}
-
 /*****************************************/
 /* Debug logging and watchdog            */
 /*****************************************/
@@ -347,35 +305,28 @@ static void adc_poll()
 // Send log data to USB UART if USB is connected.
 // Data is retrieved from the shared log ring buffer and
 // this function sends as much as fits in USB CDC buffer.
-//
-// This is normally called by platform_reset_watchdog() in
-// the normal polling loop. If code hangs, the watchdog_callback()
-// also starts calling this after 2 seconds.
-// This ensures that log messages get passed even if code hangs,
-// but does not unnecessarily delay normal execution.
-static void usb_log_poll()
-{
-    static uint32_t logpos = 0;
-
-    if (usb_hs_ready())
-    {
-        // Retrieve pointer to log start and determine number of bytes available.
-        uint32_t available = 0;
-        const char *data = log_get_buffer(&logpos, &available);
-        // Limit to CDC packet size
-        uint32_t len = available;
-        if (len == 0) return;
-        if (len > USB_CDC_EP_IN_WORKING_SIZE) len = USB_CDC_EP_IN_WORKING_SIZE;
-
-        // Update log position by the actual number of bytes sent
-        // If USB CDC buffer is full, this may be 0
-        usb_hs_send((uint8_t*)data, len);
-        logpos -= available - len;
-    }
-}
-
-
 
+// \todo add serial logging for the F4
+// static void usb_log_poll()
+// {
+//     static uint32_t logpos = 0;
+
+//     if (usb_serial_ready())
+//     {
+//         // Retrieve pointer to log start and determine number of bytes available.
+//         uint32_t available = 0;
+//         const char *data = log_get_buffer(&logpos, &available);
+//         // Limit to CDC packet size
+//         uint32_t len = available;
+//         if (len == 0) return;
+//         if (len > USB_CDC_DATA_PACKET_SIZE) len = USB_CDC_DATA_PACKET_SIZE;
+
+//         // Update log position by the actual number of bytes sent
+//         // If USB CDC buffer is full, this may be 0
+//         usb_serial_send((uint8_t*)data, len);
+//         logpos -= available - len;
+//     }
+// }
 
 /*****************************************/
 /* Crash handlers                        */
@@ -554,7 +505,7 @@ void platform_reset_watchdog()
 void platform_poll()
 {
     // adc_poll();
-    usb_log_poll();
+    // usb_log_poll();
 }
 
 uint8_t platform_get_buttons()

+ 2 - 2
lib/ZuluSCSI_platform_GD32F450/ZuluSCSI_platform.h

@@ -38,7 +38,6 @@ extern "C" {
 
 extern const char *g_platform_name;
 
-
 #if defined(ZULUSCSI_V1_4)
 #   define PLATFORM_NAME "ZuluSCSI v1.4"
 #   define PLATFORM_REVISION "1.4"
@@ -77,7 +76,8 @@ static inline void delay_us(unsigned long us)
 // Approximate fast delay
 static inline void delay_100ns()
 {
-    asm volatile ("nop \n nop \n nop \n nop \n nop");
+//    asm volatile ("nop \n nop \n nop \n nop \n nop");
+   asm volatile ("nop \n nop \n nop \n nop \n nop");
 }
 
 // Initialize SPI and GPIO configuration

+ 1 - 1
lib/ZuluSCSI_platform_GD32F450/greenpak.cpp

@@ -63,7 +63,7 @@ static void greenpak_gpio_init()
     uint32_t greenpak_io = GREENPAK_PLD_IO1 | GREENPAK_PLD_IO2 | GREENPAK_PLD_IO3;
     gpio_bit_reset(SCSI_OUT_PORT, greenpak_io);
     gpio_mode_set(GREENPAK_PLD_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, greenpak_io);
-    gpio_output_options_set(GREENPAK_PLD_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, greenpak_io);
+    gpio_output_options_set(GREENPAK_PLD_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, greenpak_io);
     
 }
 

+ 8 - 3
lib/ZuluSCSI_platform_GD32F450/scsiPhy.cpp

@@ -153,7 +153,7 @@ static void selectPhyMode()
     int default_mode = PHY_MODE_BEST_AVAILABLE;
 
     // Read overriding setting from configuration file
-    int wanted_mode = getSystemSettings()->deviceType;
+    int wanted_mode = g_scsi_settings.getSystem()->phyMode;
 
     // Default: software GPIO bitbang, available on all revisions
     g_scsi_phy_mode = PHY_MODE_PIO;
@@ -602,6 +602,9 @@ void SCSI_SEL_IRQ (void)
 
 static void init_irqs()
 {
+    // Enable SYSCFG clock to set EXTI lines
+    rcu_periph_clock_enable(RCU_SYSCFG);
+
     // Falling edge of RST pin
     gpio_mode_set(SCSI_RST_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SCSI_RST_PIN);
     syscfg_exti_line_config(SCSI_RST_EXTI_SOURCE_PORT, SCSI_RST_EXTI_SOURCE_PIN);
@@ -610,14 +613,16 @@ static void init_irqs()
     NVIC_EnableIRQ(SCSI_RST_IRQn);
 
     // Rising edge of BSY pin
-    gpio_mode_set(SCSI_BSY_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SCSI_BSY_PIN);
+    // \todo unsure if this should be commented out
+    // gpio_mode_set(SCSI_BSY_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SCSI_BSY_PIN);
     syscfg_exti_line_config(SCSI_BSY_EXTI_SOURCE_PORT, SCSI_BSY_EXTI_SOURCE_PIN);
     exti_init(SCSI_BSY_EXTI, EXTI_INTERRUPT, EXTI_TRIG_RISING);
     NVIC_SetPriority(SCSI_BSY_IRQn, 1);
     NVIC_EnableIRQ(SCSI_BSY_IRQn);
 
     // Falling edge of SEL pin
-    gpio_mode_set(SCSI_SEL_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SCSI_SEL_PIN);
+    // \todo unsure if this should be commented out
+    // gpio_mode_set(SCSI_SEL_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SCSI_SEL_PIN);
     syscfg_exti_line_config(SCSI_SEL_EXTI_SOURCE_PORT, SCSI_SEL_EXTI_SOURCE_PIN);
     exti_init(SCSI_SEL_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
     NVIC_SetPriority(SCSI_SEL_IRQn, 1);

+ 8 - 68
lib/ZuluSCSI_platform_GD32F450/scsi_accel_dma.cpp

@@ -121,7 +121,7 @@ void scsi_accel_timer_dma_init()
     };
     dma_multi_data_mode_init(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, &timer_dma_config);
     dma_channel_subperipheral_select(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, SCSI_TIMER_DMACHB_SUB_PERIPH);
-    NVIC_SetPriority(SCSI_TIMER_DMACHB_IRQn, 2);
+    NVIC_SetPriority(SCSI_TIMER_DMACHB_IRQn, 128); // Priority = 128 to make sure this is lower priority independent of priority grouping
     NVIC_EnableIRQ(SCSI_TIMER_DMACHB_IRQn);
 
     g_scsi_dma.timer_buf = TIMER_SWEVG_UPG;
@@ -145,8 +145,9 @@ void scsi_accel_timer_dma_init()
     TIMER_CH1CV(SCSI_TIMER) = 1; // Copy data when ACK goes low
     TIMER_CH2CV(SCSI_TIMER) = 1; // REQ is low until ACK goes low
     TIMER_CH3CV(SCSI_TIMER) = 2; // Reset timer after ACK goes high & previous DMA is complete
+
     gpio_mode_set(SCSI_TIMER_IN_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SCSI_TIMER_IN_PIN);
-    gpio_af_set(SCSI_TIMER_IN_PORT, SCSI_TIMER_IN_AF, SCSI_TIMER_IN_PIN);   
+    gpio_af_set(SCSI_TIMER_IN_PORT, SCSI_TIMER_IN_AF, SCSI_TIMER_IN_PIN);    
     scsi_accel_dma_stopWrite();
 }
 
@@ -155,12 +156,6 @@ static void scsi_dma_gpio_config(bool enable)
 {
     if (enable)
     {
-        //gpio_init(SCSI_OUT_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, SCSI_OUT_REQ);
-
-        gpio_mode_set(SCSI_OUT_PORT, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, SCSI_OUT_REQ);
-        
-
-
         if (g_scsi_dma_use_greenpak)
         {
             GPIO_BC(SCSI_OUT_PORT) = GREENPAK_PLD_IO1;
@@ -168,9 +163,9 @@ static void scsi_dma_gpio_config(bool enable)
         }
         else
         {
+            gpio_mode_set(SCSI_OUT_PORT, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, SCSI_OUT_REQ);
             gpio_mode_set(SCSI_TIMER_OUT_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SCSI_TIMER_OUT_PIN);
-            // @TODO determine if the output should be set to 200MHZ instead of 50MHZ
-            gpio_output_options_set(SCSI_TIMER_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SCSI_TIMER_OUT_PIN);
+            gpio_output_options_set(SCSI_TIMER_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SCSI_TIMER_OUT_PIN);
             gpio_af_set(SCSI_TIMER_OUT_PORT, SCSI_TIMER_OUT_AF, SCSI_TIMER_OUT_PIN);
         }
     }
@@ -178,10 +173,10 @@ static void scsi_dma_gpio_config(bool enable)
     {
         GPIO_BC(SCSI_OUT_PORT) = GREENPAK_PLD_IO2;
         gpio_mode_set(SCSI_TIMER_OUT_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SCSI_TIMER_OUT_PIN);
-        gpio_output_options_set(SCSI_TIMER_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SCSI_TIMER_OUT_PIN);
+        gpio_output_options_set(SCSI_TIMER_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SCSI_TIMER_OUT_PIN);
         
         gpio_mode_set(SCSI_OUT_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SCSI_OUT_REQ);
-        gpio_output_options_set(SCSI_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SCSI_OUT_REQ);
+        gpio_output_options_set(SCSI_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SCSI_OUT_REQ);
     }
 }
 
@@ -292,13 +287,9 @@ static void stop_dma()
     greenpak_stop_dma();
 
     dma_channel_disable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA);
-    // DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA) &= ~DMA_CHXCTL_CHEN;
     dma_channel_disable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB);
-    // DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB) &= ~DMA_CHXCTL_CHEN;
     dma_interrupt_disable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_CHXCTL_FTFIE | DMA_CHXCTL_HTFIE);
-    // DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA) &= ~(DMA_CHXCTL_FTFIE | DMA_CHXCTL_HTFIE);
     dma_interrupt_disable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, DMA_CHXCTL_FTFIE);
-    //DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB) &= ~DMA_CHXCTL_FTFIE;
 
     // Wait for ACK of the last byte
     volatile int timeout = 10000;
@@ -333,12 +324,7 @@ static void check_dma_next_buffer()
 // Convert new data from application buffer to DMA buffer
 extern "C" void SCSI_TIMER_DMACHA_IRQ()
 {
-    // dbgmsg("DMA irq A, counts: ", DMA_CHCNT(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA), " ",
-    //             DMA_CHCNT(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB), " ",
-    //             TIMER_CNT(SCSI_TIMER));
-
- 
-   uint32_t intf0 = DMA_INTF0(SCSI_TIMER_DMA);
+    uint32_t intf0 = DMA_INTF0(SCSI_TIMER_DMA);
     uint32_t intf1 = DMA_INTF1(SCSI_TIMER_DMA);
 
     if (dma_interrupt_flag_get(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_FLAG_HTF))
@@ -413,15 +399,6 @@ extern "C" void SCSI_TIMER_DMACHB_IRQ()
                 __enable_irq();
             }
 
-            // Verify the first byte of the new data has been written to outputs
-            // It may have been updated after the DMA write occurred.
-/*  @TODO Is this still needed?
-            __disable_irq();
-            uint32_t first_data_idx = g_scsi_dma.dma_idx - (g_scsi_dma.bytes_dma - g_scsi_dma.scheduled_dma);
-            uint32_t first_data = g_scsi_dma.dma_buf[first_data_idx & DMA_BUF_MASK];
-            GPIO_BOP(SCSI_OUT_PORT) = first_data;
-            __enable_irq();
-*/
             // Update the total number of bytes available for DMA
             uint32_t dma_to_schedule = g_scsi_dma.bytes_app - g_scsi_dma.scheduled_dma;
             dma_channel_disable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB);
@@ -435,43 +412,6 @@ extern "C" void SCSI_TIMER_DMACHB_IRQ()
             stop_dma();
         }
     }
-
-
-
-    // dbgmsg("DMA irq B, counts: ", DMA_CHCNT(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA), " ",
-    //             DMA_CHCNT(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB), " ",
-    //             TIMER_CNT(SCSI_TIMER));
-    if (dma_interrupt_flag_get(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, DMA_FLAG_FTF))
-    {
-        dma_interrupt_flag_clear(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, DMA_FLAG_FTF);
-
-        if (g_scsi_dma.bytes_app > g_scsi_dma.scheduled_dma)
-        {
-            if (g_scsi_dma.dma_idx < g_scsi_dma.dma_fillto)
-            {
-                // Previous request didn't have a complete buffer worth of data.
-                // Refill the buffer and ensure that the first byte of the new data gets
-                // written to outputs.
-                __disable_irq();
-                refill_dmabuf();
-                __enable_irq();
-            }
-
-
-
-            // Update the total number of bytes available for DMA
-            uint32_t dma_to_schedule = g_scsi_dma.bytes_app - g_scsi_dma.scheduled_dma;
-            DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB) &= ~DMA_CHXCTL_CHEN;
-            DMA_CHCNT(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB) = dma_to_schedule;
-            DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB) |= DMA_CHXCTL_CHEN;
-            g_scsi_dma.scheduled_dma += dma_to_schedule;
-        }
-        else
-        {
-            // No more data available
-            stop_dma();
-        }
-    }
 }
 
 void scsi_accel_dma_startWrite(const uint8_t* data, uint32_t count, volatile int *resetFlag)

+ 2 - 2
lib/ZuluSCSI_platform_GD32F450/scsi_accel_greenpak.cpp

@@ -141,7 +141,7 @@ void scsi_accel_greenpak_send(const uint32_t *buf, uint32_t num_words, volatile
     // Disable external logic and set REQ pin as output
     GPIO_BC(SCSI_OUT_PORT) = GREENPAK_PLD_IO2;
     gpio_mode_set(SCSI_OUT_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE , SCSI_OUT_REQ);
-    gpio_output_options_set(SCSI_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SCSI_OUT_REQ);
+    gpio_output_options_set(SCSI_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SCSI_OUT_REQ);
 }
 
 /**********************************************/
@@ -381,7 +381,7 @@ void scsi_accel_greenpak_recv(uint32_t *buf, uint32_t num_words, volatile int *r
     // Disable external logic and set REQ pin as output
     GPIO_BC(SCSI_OUT_PORT) = GREENPAK_PLD_IO2;
     gpio_mode_set(SCSI_OUT_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SCSI_OUT_REQ);
-    gpio_output_options_set(SCSI_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SCSI_OUT_REQ);
+    gpio_output_options_set(SCSI_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SCSI_OUT_REQ);
     GPIO_BC(SCSI_OUT_PORT) = GREENPAK_PLD_IO3;
 }
 

+ 11 - 5
lib/ZuluSCSI_platform_GD32F450/scsi_accel_sync.cpp

@@ -111,11 +111,13 @@ void scsi_accel_sync_init()
     // };
     // dma_single_data_mode_init(SCSI_EXMC_DMA, SCSI_EXMC_DMACH, &exmc_dma_config);
     gpio_mode_set(SCSI_IN_ACK_EXMC_NWAIT_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SCSI_IN_ACK_EXMC_NWAIT_PIN);
+    gpio_output_options_set(SCSI_IN_ACK_EXMC_NWAIT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SCSI_IN_ACK_EXMC_NWAIT_PIN);
     gpio_af_set(SCSI_IN_ACK_EXMC_NWAIT_PORT, GPIO_AF_12, SCSI_IN_ACK_EXMC_NWAIT_PIN);
 
     // TIMER1 CH0 port and pin enable
     gpio_mode_set(SCSI_ACK_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SCSI_ACK_PIN);
     gpio_af_set(SCSI_ACK_PORT, GPIO_AF_1, SCSI_ACK_PIN);
+    
     // TIMER1 is used to count ACK pulses
     TIMER_CTL0(SCSI_SYNC_TIMER) = 0;
     TIMER_SMCFG(SCSI_SYNC_TIMER) = TIMER_SLAVE_MODE_EXTERNAL0 | TIMER_SMCFG_TRGSEL_CI0FE0;
@@ -126,6 +128,12 @@ void scsi_accel_sync_init()
 
 void scsi_accel_sync_recv(uint8_t *data, uint32_t count, int* parityError, volatile int *resetFlag)
 {
+    // Set SCSI data IN pins to external memory mode
+    gpio_mode_set(SCSI_IN_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SCSI_IN_MASK);
+    gpio_output_options_set(SCSI_IN_PORT, GPIO_PUPD_NONE, GPIO_OSPEED_200MHZ, SCSI_IN_MASK);
+    gpio_af_set(SCSI_IN_PORT, GPIO_AF_12, SCSI_IN_MASK);
+
+    
     // Enable EXMC to drive REQ from EXMC_NOE pin
     EXMC_SNCTL(EXMC_BANK0_NORSRAM_REGION0) |= EXMC_SNCTL_NRBKEN;
 
@@ -136,9 +144,8 @@ void scsi_accel_sync_recv(uint8_t *data, uint32_t count, int* parityError, volat
     uint32_t oldmode_gpio_omode = GPIO_OMODE(SCSI_OUT_REQ_EXMC_NOE_PORT);
     uint32_t oldmode_gpio_af = GPIO_AFSEL0(SCSI_OUT_REQ_EXMC_NOE_PORT);
 
-    // @TODO figure out ouput speed should be set to 200MHz
     gpio_af_set(SCSI_OUT_REQ_EXMC_NOE_PORT, GPIO_AF_12, SCSI_OUT_REQ_EXMC_NOE_PIN);
-    gpio_output_options_set(SCSI_OUT_REQ_EXMC_NOE_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SCSI_OUT_REQ_EXMC_NOE_PIN);
+    gpio_output_options_set(SCSI_OUT_REQ_EXMC_NOE_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SCSI_OUT_REQ_EXMC_NOE_PIN);
     gpio_mode_set(SCSI_OUT_REQ_EXMC_NOE_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SCSI_OUT_REQ_EXMC_NOE_PIN);
     
     while (count > 0)
@@ -149,11 +156,11 @@ void scsi_accel_sync_recv(uint8_t *data, uint32_t count, int* parityError, volat
         // dma_memory_address_config(SCSI_EXMC_DMA, SCSI_EXMC_DMACH, 0, (uint32_t)g_sync_dma_buf);
         // dma_transfer_number_config(SCSI_EXMC_DMA, SCSI_EXMC_DMACH, blocksize);
         // dma_channel_enable(SCSI_EXMC_DMA, SCSI_EXMC_DMACH);
-
         uint16_t *src = (uint16_t*)g_sync_dma_buf;
         uint8_t *dst = data;
         uint8_t *end = data + blocksize;
         uint32_t start = millis();
+
         while (dst < end)
         {
             // Read from EXMC and write to internal RAM
@@ -189,7 +196,6 @@ void scsi_accel_sync_recv(uint8_t *data, uint32_t count, int* parityError, volat
     GPIO_AFSEL0(SCSI_OUT_REQ_EXMC_NOE_PORT) = oldmode_gpio_af;
 
 
-
 }
 
 /********************************/
@@ -301,7 +307,7 @@ static void sync_send_100ns_15off(const uint8_t *buf, uint32_t num_bytes, volati
         "   subs  %[num_bytes], %[num_bytes], #16 \n"
         "   bmi     last_bytes_%= \n"
 
-       /* At each point make sure there is at most 15 bytes in flight */
+        /* At each point make sure there is at most 15 bytes in flight */
         "   ldr   %[data], [%[buf]], #4 \n"
         ASM_SEND_4BYTES_WAIT("26")
         ASM_DELAY2()

+ 0 - 2
lib/ZuluSCSI_platform_GD32F450/sd_card_sdio.cpp

@@ -25,8 +25,6 @@
 #include "ZuluSCSI_platform.h"
 
 #ifdef SD_USE_SDIO
-
-
 extern "C"
 {
 #include "gd32f4xx_sdio.h"

+ 274 - 0
lib/ZuluSCSI_platform_GD32F450/sd_card_spi.cpp

@@ -0,0 +1,274 @@
+// Driver and interface for accessing SD card in SPI mode
+// Used on ZuluSCSI v1.0.
+
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+#include "gd32f4xx_spi.h"
+#include "gd32f4xx_dma.h"
+#include <SdFat.h>
+
+#ifndef SD_USE_SDIO
+
+class GD32SPIDriver : public SdSpiBaseClass
+{
+public:
+    void begin(SdSpiConfig config) {
+        rcu_periph_clock_enable(RCU_SPI0);
+        rcu_periph_clock_enable(RCU_DMA0);
+
+        dma_parameter_struct rx_dma_config =
+        {
+            .periph_addr = (uint32_t)&SPI_DATA(SD_SPI),
+            .periph_width = DMA_PERIPHERAL_WIDTH_8BIT,
+            .memory_addr = 0, // Set before transfer
+            .memory_width = DMA_MEMORY_WIDTH_8BIT,
+            .number = 0, // Set before transfer
+            .priority = DMA_PRIORITY_ULTRA_HIGH,
+            .periph_inc = DMA_PERIPH_INCREASE_DISABLE,
+            .memory_inc = DMA_MEMORY_INCREASE_ENABLE,
+            .direction = DMA_PERIPHERAL_TO_MEMORY
+        };
+        dma_init(DMA0, SD_SPI_RX_DMA_CHANNEL, &rx_dma_config);
+
+        dma_parameter_struct tx_dma_config =
+        {
+            .periph_addr = (uint32_t)&SPI_DATA(SD_SPI),
+            .periph_width = DMA_PERIPHERAL_WIDTH_8BIT,
+            .memory_addr = 0, // Set before transfer
+            .memory_width = DMA_MEMORY_WIDTH_8BIT,
+            .number = 0, // Set before transfer
+            .priority = DMA_PRIORITY_HIGH,
+            .periph_inc = DMA_PERIPH_INCREASE_DISABLE,
+            .memory_inc = DMA_MEMORY_INCREASE_ENABLE,
+            .direction = DMA_MEMORY_TO_PERIPHERAL
+        };
+        dma_init(DMA0, SD_SPI_TX_DMA_CHANNEL, &tx_dma_config);
+    }
+        
+    void activate() {
+        spi_parameter_struct config = {
+            SPI_MASTER,
+            SPI_TRANSMODE_FULLDUPLEX,
+            SPI_FRAMESIZE_8BIT,
+            SPI_NSS_SOFT,
+            SPI_ENDIAN_MSB,
+            SPI_CK_PL_LOW_PH_1EDGE,
+            SPI_PSC_256
+        };
+
+        // Select closest available divider based on system frequency
+        int divider = (SystemCoreClock + m_sckfreq / 2) / m_sckfreq;
+        if (divider <= 2)
+            config.prescale = SPI_PSC_2;
+        else if (divider <= 4)
+            config.prescale = SPI_PSC_4;
+        else if (divider <= 8)
+            config.prescale = SPI_PSC_8;
+        else if (divider <= 16)
+            config.prescale = SPI_PSC_16;
+        else if (divider <= 32)
+            config.prescale = SPI_PSC_32;
+        else if (divider <= 64)
+            config.prescale = SPI_PSC_64;
+        else if (divider <= 128)
+            config.prescale = SPI_PSC_128;
+        else
+            config.prescale = SPI_PSC_256;
+
+        spi_init(SD_SPI, &config);
+        spi_enable(SD_SPI);
+    }
+    
+    void deactivate() {
+        spi_disable(SD_SPI);
+    }
+
+    void wait_idle() {
+        while (!(SPI_STAT(SD_SPI) & SPI_STAT_TBE));
+        while (SPI_STAT(SD_SPI) & SPI_STAT_TRANS);
+    }
+
+    // Single byte receive
+    uint8_t receive() {
+        // Wait for idle and clear RX buffer
+        wait_idle();
+        (void)SPI_DATA(SD_SPI);
+
+        // Send dummy byte and wait for receive
+        SPI_DATA(SD_SPI) = 0xFF;
+        while (!(SPI_STAT(SD_SPI) & SPI_STAT_RBNE));
+        return SPI_DATA(SD_SPI);
+    }
+
+    // Single byte send
+    void send(uint8_t data) {
+        SPI_DATA(SD_SPI) = data;
+        wait_idle();
+    }
+
+    // Multiple byte receive
+    uint8_t receive(uint8_t* buf, size_t count)
+    {
+        // Wait for idle and clear RX buffer
+        wait_idle();
+        (void)SPI_DATA(SD_SPI);
+
+        // Check if this is part of callback streaming request
+        bool stream = false;
+        if (m_stream_callback && buf == m_stream_buffer + m_stream_count)
+        {
+            stream = true;
+        }
+        else if (m_stream_callback)
+        {
+            dbgmsg("Stream buffer mismatch: ", (uint32_t)buf, " vs. ", (uint32_t)(m_stream_buffer + m_stream_count));
+        }
+
+        // Use DMA to stream dummy TX data and store RX data
+        uint8_t tx_data = 0xFF;
+        DMA_INTC(DMA0) = DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_RX_DMA_CHANNEL);
+        DMA_INTC(DMA0) = DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_TX_DMA_CHANNEL);
+        DMA_CHMADDR(DMA0, SD_SPI_RX_DMA_CHANNEL) = (uint32_t)buf;
+        DMA_CHMADDR(DMA0, SD_SPI_TX_DMA_CHANNEL) = (uint32_t)&tx_data;
+        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) &= ~DMA_CHXCTL_MNAGA; // No memory increment for TX
+        DMA_CHCNT(DMA0, SD_SPI_RX_DMA_CHANNEL) = count;
+        DMA_CHCNT(DMA0, SD_SPI_TX_DMA_CHANNEL) = count;
+        DMA_CHCTL(DMA0, SD_SPI_RX_DMA_CHANNEL) |= DMA_CHXCTL_CHEN;
+        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) |= DMA_CHXCTL_CHEN;
+
+        SPI_CTL1(SD_SPI) |= SPI_CTL1_DMAREN | SPI_CTL1_DMATEN;
+        
+        uint32_t start = millis();
+        while (!(DMA_INTF(DMA0) & DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_RX_DMA_CHANNEL)))
+        {
+            if (millis() - start > 500)
+            {
+                logmsg("ERROR: SPI DMA receive of ", (int)count, " bytes timeouted");
+                return 1;
+            }
+
+            if (stream)
+            {
+                uint32_t complete = m_stream_count + (count - DMA_CHCNT(DMA0, SD_SPI_RX_DMA_CHANNEL));
+                m_stream_callback(complete);
+            }
+        }
+
+        if (DMA_INTF(DMA0) & DMA_FLAG_ADD(DMA_FLAG_ERR, SD_SPI_RX_DMA_CHANNEL))
+        {
+            logmsg("ERROR: SPI DMA receive set DMA_FLAG_ERR");
+        }
+
+        SPI_CTL1(SD_SPI) &= ~(SPI_CTL1_DMAREN | SPI_CTL1_DMATEN);
+        DMA_CHCTL(DMA0, SD_SPI_RX_DMA_CHANNEL) &= ~DMA_CHXCTL_CHEN;
+        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) &= ~DMA_CHXCTL_CHEN;
+
+        if (stream)
+        {
+            m_stream_count += count;
+        }
+
+        return 0;
+    }
+
+    // Multiple byte send
+    void send(const uint8_t* buf, size_t count) {
+        // Check if this is part of callback streaming request
+        bool stream = false;
+        if (m_stream_callback && buf == m_stream_buffer + m_stream_count)
+        {
+            stream = true;
+        }
+        else if (m_stream_callback)
+        {
+            dbgmsg("Stream buffer mismatch: ", (uint32_t)buf, " vs. ", (uint32_t)(m_stream_buffer + m_stream_count));
+        }
+
+        // Use DMA to stream TX data
+        DMA_INTC(DMA0) = DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_TX_DMA_CHANNEL);
+        DMA_CHMADDR(DMA0, SD_SPI_TX_DMA_CHANNEL) = (uint32_t)buf;
+        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) |= DMA_CHXCTL_MNAGA; // Memory increment for TX
+        DMA_CHCNT(DMA0, SD_SPI_TX_DMA_CHANNEL) = count;
+        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) |= DMA_CHXCTL_CHEN;
+
+        SPI_CTL1(SD_SPI) |= SPI_CTL1_DMATEN;
+        
+        uint32_t start = millis();
+        while (!(DMA_INTF(DMA0) & DMA_FLAG_ADD(DMA_FLAG_FTF | DMA_FLAG_ERR, SD_SPI_TX_DMA_CHANNEL)))
+        {
+            if (millis() - start > 500)
+            {
+                logmsg("ERROR: SPI DMA transmit of ", (int)count, " bytes timeouted");
+                return;
+            }
+
+            if (stream)
+            {
+                uint32_t complete = m_stream_count + (count - DMA_CHCNT(DMA0, SD_SPI_TX_DMA_CHANNEL));
+                m_stream_callback(complete);
+            }
+        }
+
+        if (DMA_INTF(DMA0) & DMA_FLAG_ADD(DMA_FLAG_ERR, SD_SPI_TX_DMA_CHANNEL))
+        {
+            logmsg("ERROR: SPI DMA transmit set DMA_FLAG_ERR");
+        }
+
+        wait_idle();
+
+        SPI_CTL1(SD_SPI) &= ~(SPI_CTL1_DMAREN | SPI_CTL1_DMATEN);
+        DMA_CHCTL(DMA0, SD_SPI_TX_DMA_CHANNEL) &= ~DMA_CHXCTL_CHEN;
+
+        if (stream)
+        {
+            m_stream_count += count;
+        }
+    }
+
+    void setSckSpeed(uint32_t maxSck) {
+        m_sckfreq = maxSck;
+    }
+
+    void set_sd_callback(sd_callback_t func, const uint8_t *buffer)
+    {
+        m_stream_buffer = buffer;
+        m_stream_count = 0;
+        m_stream_callback = func;
+    }
+
+private:
+    uint32_t m_sckfreq;
+    const uint8_t *m_stream_buffer;
+    uint32_t m_stream_count;
+    sd_callback_t m_stream_callback;
+};
+
+void sdCsInit(SdCsPin_t pin)
+{
+}
+
+void sdCsWrite(SdCsPin_t pin, bool level)
+{
+    if (level)
+        GPIO_BOP(SD_PORT) = SD_CS_PIN;
+    else
+        GPIO_BC(SD_PORT) = SD_CS_PIN;
+}
+
+GD32SPIDriver g_sd_spi_port;
+SdSpiConfig g_sd_spi_config(0, DEDICATED_SPI, SD_SCK_MHZ(30), &g_sd_spi_port);
+
+void platform_set_sd_callback(sd_callback_t func, const uint8_t *buffer)
+{
+    g_sd_spi_port.set_sd_callback(func, buffer);    
+}
+
+// Check if a DMA request for SD card read has completed.
+// This is used to optimize the timing of data transfers on SCSI bus.
+bool check_sd_read_done()
+{
+    return (DMA_CHCTL(DMA0, SD_SPI_RX_DMA_CHANNEL) & DMA_CHXCTL_CHEN)
+        && (DMA_INTF(DMA0) & DMA_FLAG_ADD(DMA_FLAG_FTF, SD_SPI_RX_DMA_CHANNEL));
+}
+
+#endif

+ 51 - 197
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp

@@ -37,23 +37,14 @@
 #include <hardware/structs/usb.h>
 #include "scsi_accel_target.h"
 
-#ifdef __MBED__
-#  include <platform/mbed_error.h>
-#endif // __MBED__
-
-#ifndef __MBED__
 #ifndef PIO_FRAMEWORK_ARDUINO_NO_USB
 # include <SerialUSB.h>
 # include <class/cdc/cdc_device.h>
 #endif
-#else
-# include <USB/PluggableUSBSerial.h>
-#endif // __MBED__
 
-#ifndef ZULUSCSI_NETWORK
-#  include <multicore.h>
-#else
-#  include <pico/multicore.h> 
+#include <pico/multicore.h>
+
+#ifdef ZULUSCSI_NETWORK
 extern "C" {
 #  include <pico/cyw43_arch.h>
 } 
@@ -70,10 +61,6 @@ static bool g_scsi_initiator = false;
 static uint32_t g_flash_chip_size = 0;
 static bool g_uart_initialized = false;
 
-#ifdef __MBED__
-void mbed_error_hook(const mbed_error_ctx * error_context);
-#endif // __MBED__
-
 /***************/
 /* GPIO init   */
 /***************/
@@ -225,9 +212,6 @@ void platform_init()
     g_uart_initialized = true;
 #endif // DISABLE_SWO
 
-#ifdef __MBED__
-    mbed_set_error_hook(mbed_error_hook);
-#endif // __MBED__
     logmsg("Platform: ", g_platform_name);
     logmsg("FW Version: ", g_log_firmwareversion);
 
@@ -366,11 +350,10 @@ void platform_late_init()
         gpio_conf(SCSI_IN_ATN,    GPIO_FUNC_SIO, true, false, false, true, false);
         gpio_conf(SCSI_IN_RST,    GPIO_FUNC_SIO, true, false, false, true, false);
 
-#ifndef __MBED__
-# ifndef PIO_FRAMEWORK_ARDUINO_NO_USB
+#ifndef PIO_FRAMEWORK_ARDUINO_NO_USB
     Serial.begin();
-# endif
-#endif // __MBED__
+#endif
+
 
 #ifdef ENABLE_AUDIO_OUTPUT
         // one-time control setup for DMA channels and second core
@@ -424,7 +407,6 @@ extern uint32_t __StackTop;
 void platform_emergency_log_save()
 {
     platform_set_sd_callback(NULL, NULL);
-
     SD.begin(SD_CONFIG_CRASH);
     FsFile crashfile = SD.open(CRASHFILE, O_WRONLY | O_CREAT | O_TRUNC);
 
@@ -444,21 +426,31 @@ void platform_emergency_log_save()
     crashfile.close();
 }
 
-#ifdef __MBED__
-void mbed_error_hook(const mbed_error_ctx * error_context)
+
+static void usb_log_poll();
+
+__attribute__((noinline))
+void show_hardfault(uint32_t *sp)
 {
+    uint32_t pc = sp[6];
+    uint32_t lr = sp[5];
+
     logmsg("--------------");
     logmsg("CRASH!");
     logmsg("Platform: ", g_platform_name);
     logmsg("FW Version: ", g_log_firmwareversion);
-    logmsg("error_status: ", (uint32_t)error_context->error_status);
-    logmsg("error_address: ", error_context->error_address);
-    logmsg("error_value: ", error_context->error_value);
     logmsg("scsiDev.cdb: ", bytearray(scsiDev.cdb, 12));
     logmsg("scsiDev.phase: ", (int)scsiDev.phase);
-    scsi_accel_log_state();
+    logmsg("SP: ", (uint32_t)sp);
+    logmsg("PC: ", pc);
+    logmsg("LR: ", lr);
+    logmsg("R0: ", sp[0]);
+    logmsg("R1: ", sp[1]);
+    logmsg("R2: ", sp[2]);
+    logmsg("R3: ", sp[3]);
+
+    uint32_t *p = (uint32_t*)((uint32_t)sp & ~3);
 
-    uint32_t *p = (uint32_t*)((uint32_t)error_context->thread_current_sp & ~3);
     for (int i = 0; i < 8; i++)
     {
         if (p == &__StackTop) break; // End of stack
@@ -471,24 +463,33 @@ void mbed_error_hook(const mbed_error_ctx * error_context)
 
     while (1)
     {
+        usb_log_poll();
         // Flash the crash address on the LED
         // Short pulse means 0, long pulse means 1
-        int base_delay = 1000;
+        int base_delay = 500;
         for (int i = 31; i >= 0; i--)
         {
             LED_OFF();
-            for (int j = 0; j < base_delay; j++) delay_ns(100000);
+            for (int j = 0; j < base_delay; j++) busy_wait_ms(1);
 
-            int delay = (error_context->error_address & (1 << i)) ? (3 * base_delay) : base_delay;
+            int delay = (pc & (1 << i)) ? (3 * base_delay) : base_delay;
             LED_ON();
-            for (int j = 0; j < delay; j++) delay_ns(100000);
+            for (int j = 0; j < delay; j++) busy_wait_ms(1);
             LED_OFF();
         }
 
-        for (int j = 0; j < base_delay * 10; j++) delay_ns(100000);
+        for (int j = 0; j < base_delay * 10; j++) busy_wait_ms(1);
     }
 }
-#endif // __MBED__
+
+__attribute__((naked, interrupt))
+void isr_hardfault(void)
+{
+    // Copies stack pointer into first argument
+    asm("mrs r0, msp\n"
+        "bl show_hardfault": : : "r0");
+}
+
 
 /*****************************************/
 /* Debug logging and watchdog            */
@@ -505,9 +506,8 @@ void mbed_error_hook(const mbed_error_ctx * error_context)
 // but does not unnecessarily delay normal execution.
 static void usb_log_poll()
 {
+#ifndef PIO_FRAMEWORK_ARDUINO_NO_USB
     static uint32_t logpos = 0;
-#ifndef __MBED__
-# ifndef PIO_FRAMEWORK_ARDUINO_NO_USB
     if (Serial.availableForWrite())
     {
         // Retrieve pointer to log start and determine number of bytes available.
@@ -524,26 +524,7 @@ static void usb_log_poll()
         actual = Serial.write(data, len);
         logpos -= available - actual;
     }
-# endif // PIO_FRAMEWORK_ARDUINO_NO_USB
-#else
-    if (_SerialUSB.ready())
-    {
-        // Retrieve pointer to log start and determine number of bytes available.
-        uint32_t available = 0;
-        const char *data = log_get_buffer(&logpos, &available);
-
-        // Limit to CDC packet size
-        uint32_t len = available;
-        if (len == 0) return;
-        if (len > CDC_MAX_PACKET_SIZE) len = CDC_MAX_PACKET_SIZE;
-
-        // Update log position by the actual number of bytes sent
-        // If USB CDC buffer is full, this may be 0
-        uint32_t actual = 0;
-        _SerialUSB.send_nb((uint8_t*)data, len, &actual);
-        logpos -= available - actual;
-    }
-#endif // __MBED__
+#endif // PIO_FRAMEWORK_ARDUINO_NO_USB
 }
 
 
@@ -638,10 +619,11 @@ static void watchdog_callback(unsigned alarm_num)
             logmsg("scsiDev.phase: ", (int)scsiDev.phase);
             scsi_accel_log_state();
 
-            uint32_t *p = (uint32_t*)__get_PSP();
+            uint32_t *p =  (uint32_t*)__get_MSP();
+
             for (int i = 0; i < 8; i++)
             {
-                if (p == &__StackTop) break; // End of stack
+            if (p == &__StackTop) break; // End of stack
 
                 logmsg("STACK ", (uint32_t)p, ":    ", p[0], " ", p[1], " ", p[2], " ", p[3]);
                 p += 4;
@@ -654,14 +636,15 @@ static void watchdog_callback(unsigned alarm_num)
         if (g_watchdog_timeout <= 0)
         {
             logmsg("--------------");
-            logmsg("WATCHDOG TIMEOUT!");
+            logmsg("WATCHDOG TIMEOUT, already attempted bus reset, rebooting");
             logmsg("Platform: ", g_platform_name);
             logmsg("FW Version: ", g_log_firmwareversion);
             logmsg("GPIO states: out ", sio_hw->gpio_out, " oe ", sio_hw->gpio_oe, " in ", sio_hw->gpio_in);
             logmsg("scsiDev.cdb: ", bytearray(scsiDev.cdb, 12));
             logmsg("scsiDev.phase: ", (int)scsiDev.phase);
 
-            uint32_t *p = (uint32_t*)__get_PSP();
+            uint32_t *p =  (uint32_t*)__get_MSP();
+
             for (int i = 0; i < 8; i++)
             {
                 if (p == &__StackTop) break; // End of stack
@@ -678,7 +661,7 @@ static void watchdog_callback(unsigned alarm_num)
         }
     }
 
-    hardware_alarm_set_target(3, delayed_by_ms(get_absolute_time(), 1000));
+    hardware_alarm_set_target(alarm_num, delayed_by_ms(get_absolute_time(), 1000));
 }
 
 // This function can be used to periodically reset watchdog timer for crash handling.
@@ -687,7 +670,6 @@ void platform_reset_watchdog()
 {
     g_watchdog_timeout = WATCHDOG_CRASH_TIMEOUT;
 
-
     if (!g_watchdog_initialized)
     {
         int alarm_num = -1;
@@ -757,107 +739,7 @@ uint8_t platform_get_buttons()
     return buttons_debounced;
 }
 
-/*****************************************/
-/* Flash reprogramming from bootloader   */
-/*****************************************/
 
-#ifdef PLATFORM_BOOTLOADER_SIZE
-
-extern uint32_t __real_vectors_start;
-extern uint32_t __StackTop;
-static volatile void *g_bootloader_exit_req;
-
-__attribute__((section(".time_critical.platform_rewrite_flash_page")))
-bool platform_rewrite_flash_page(uint32_t offset, uint8_t buffer[PLATFORM_FLASH_PAGE_SIZE])
-{
-    if (offset == PLATFORM_BOOTLOADER_SIZE)
-    {
-        if (buffer[3] != 0x20 || buffer[7] != 0x10)
-        {
-            logmsg("Invalid firmware file, starts with: ", bytearray(buffer, 16));
-            return false;
-        }
-    }
-
-
-#ifdef __MBED__
-    if (NVIC_GetEnableIRQ(USBCTRL_IRQn))
-    {
-        logmsg("Disabling USB during firmware flashing");
-        NVIC_DisableIRQ(USBCTRL_IRQn);
-        usb_hw->main_ctrl = 0;
-    }
-#endif // __MBED__
-
-    dbgmsg("Writing flash at offset ", offset, " data ", bytearray(buffer, 4));
-    assert(offset % PLATFORM_FLASH_PAGE_SIZE == 0);
-    assert(offset >= PLATFORM_BOOTLOADER_SIZE);
-
-    // Avoid any mbed timer interrupts triggering during the flashing.
-    __disable_irq();
-
-    // For some reason any code executed after flashing crashes
-    // unless we disable the XIP cache.
-    // Not sure why this happens, as flash_range_program() is flushing
-    // the cache correctly.
-    // The cache is now enabled from bootloader start until it starts
-    // flashing, and again after reset to main firmware.
-    xip_ctrl_hw->ctrl = 0;
-
-    flash_range_erase(offset, PLATFORM_FLASH_PAGE_SIZE);
-    flash_range_program(offset, buffer, PLATFORM_FLASH_PAGE_SIZE);
-
-    uint32_t *buf32 = (uint32_t*)buffer;
-    uint32_t num_words = PLATFORM_FLASH_PAGE_SIZE / 4;
-    for (int i = 0; i < num_words; i++)
-    {
-        uint32_t expected = buf32[i];
-        uint32_t actual = *(volatile uint32_t*)(XIP_NOCACHE_BASE + offset + i * 4);
-
-        if (actual != expected)
-        {
-            logmsg("Flash verify failed at offset ", offset + i * 4, " got ", actual, " expected ", expected);
-            __enable_irq();
-            return false;
-        }
-    }
-
-    __enable_irq();
-
-    return true;
-}
-
-void platform_boot_to_main_firmware()
-{
-    // To ensure that the system state is reset properly, we perform
-    // a SYSRESETREQ and jump straight from the reset vector to main application.
-    g_bootloader_exit_req = &g_bootloader_exit_req;
-    SCB->AIRCR = 0x05FA0004;
-    while(1);
-}
-
-void btldr_reset_handler()
-{
-    uint32_t* application_base = &__real_vectors_start;
-    if (g_bootloader_exit_req == &g_bootloader_exit_req)
-    {
-        // Boot to main application
-        application_base = (uint32_t*)(XIP_BASE + PLATFORM_BOOTLOADER_SIZE);
-    }
-
-    SCB->VTOR = (uint32_t)application_base;
-    __asm__(
-        "msr msp, %0\n\t"
-        "bx %1" : : "r" (application_base[0]),
-                    "r" (application_base[1]) : "memory");
-}
-
-// Replace the reset handler when building the bootloader
-// The rp2040_btldr.ld places real vector table at an offset.
-__attribute__((section(".btldr_vectors")))
-const void * btldr_vectors[2] = {&__StackTop, (void*)&btldr_reset_handler};
-
-#endif // PLATFORM_BOOTLOADER_SIZE
 
 /************************************/
 /* ROM drive in extra flash space   */
@@ -865,8 +747,10 @@ const void * btldr_vectors[2] = {&__StackTop, (void*)&btldr_reset_handler};
 
 #ifdef PLATFORM_HAS_ROM_DRIVE
 
-// Reserve up to 352 kB for firmware.
-#define ROMDRIVE_OFFSET (352 * 1024)
+# ifndef ROMDRIVE_OFFSET
+    // Reserve up to 352 kB for firmware by default.
+    #define ROMDRIVE_OFFSET (352 * 1024)
+# endif
 
 uint32_t platform_get_romdrive_maxsize()
 {
@@ -1019,33 +903,3 @@ const uint16_t g_scsi_parity_check_lookup[512] __attribute__((aligned(1024), sec
 #undef X
 
 } /* extern "C" */
-
-
-#ifdef __MBED__
-/* Logging from mbed */
-
-static class LogTarget: public mbed::FileHandle {
-public:
-    virtual ssize_t read(void *buffer, size_t size) { return 0; }
-    virtual ssize_t write(const void *buffer, size_t size)
-    {
-        // A bit inefficient but mbed seems to write() one character
-        // at a time anyways.
-        for (int i = 0; i < size; i++)
-        {
-            char buf[2] = {((const char*)buffer)[i], 0};
-            log_raw(buf);
-        }
-        return size;
-    }
-
-    virtual off_t seek(off_t offset, int whence = SEEK_SET) { return offset; }
-    virtual int close() { return 0; }
-    virtual off_t size() { return 0; }
-} g_LogTarget;
-
-mbed::FileHandle *mbed::mbed_override_console(int fd)
-{
-    return &g_LogTarget;
-}
-#endif // __MBED__

+ 227 - 0
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_msc.cpp

@@ -0,0 +1,227 @@
+/**
+ * Copyright (c) 2023-2024 zigzagjoe
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * 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/>.
+**/
+
+/* platform specific MSC routines */
+#ifdef PLATFORM_MASS_STORAGE
+
+#include <SdFat.h>
+#include <device/usbd.h>
+#include <hardware/gpio.h>
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_msc.h"
+
+#include <class/msc/msc.h>
+#include <class/msc/msc_device.h>
+
+#if CFG_TUD_MSC_EP_BUFSIZE < SD_SECTOR_SIZE
+  #error "CFG_TUD_MSC_EP_BUFSIZE is too small! It needs to be at least 512 (SD_SECTOR_SIZE)"
+#endif
+
+// external global SD variable
+extern SdFs SD;
+static bool unitReady = false;
+
+/* return true if USB presence detected / eligble to enter CR mode */
+bool platform_sense_msc() {
+
+#ifdef ZULUSCSI_PICO
+  // check if we're USB powered, if not, exit immediately
+  // pin on the wireless module, see https://github.com/earlephilhower/arduino-pico/discussions/835
+  if (rp2040.isPicoW() && !digitalRead(34))
+    return false;
+
+  if (!rp2040.isPicoW() && !digitalRead(24))
+    return false;
+#endif
+
+  logmsg("Waiting for USB enumeration to enter Card Reader mode.");
+
+  // wait for up to a second to be enumerated
+  uint32_t start = millis();
+  while (!tud_connected() && ((uint32_t)(millis() - start) < CR_ENUM_TIMEOUT)) 
+    delay(100);
+
+  // tud_connected returns True if just got out of Bus Reset and received the very first data from host
+  // https://github.com/hathach/tinyusb/blob/master/src/device/usbd.h#L63
+  return tud_connected();
+}
+
+/* return true if we should remain in card reader mode and perform periodic tasks */
+bool platform_run_msc() {
+  return unitReady;
+}
+
+/* perform MSC class preinit tasks */
+void platform_enter_msc() {
+  dbgmsg("USB MSC buffer size: ", CFG_TUD_MSC_EP_BUFSIZE);
+  // MSC is ready for read/write
+  // we don't need any prep, but the var is requried as the MSC callbacks are always active
+  unitReady = true;
+}
+
+/* perform any cleanup tasks for the MSC-specific functionality */
+void platform_exit_msc() {
+  unitReady = false;
+}
+
+/* TinyUSB mass storage callbacks follow */
+
+// usb framework checks this func exists for mass storage config. no code needed.
+void __USBInstallMassStorage() { }
+
+// Invoked when received SCSI_CMD_INQUIRY
+// fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
+extern "C" void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8],
+                        uint8_t product_id[16], uint8_t product_rev[4]) {
+
+  // TODO: We could/should use strings from the platform, but they are too long
+  const char vid[] = "ZuluSCSI";
+  const char pid[] = "Pico"; 
+  const char rev[] = "1.0";
+
+  memcpy(vendor_id, vid, tu_min32(strlen(vid), 8));
+  memcpy(product_id, pid, tu_min32(strlen(pid), 16));
+  memcpy(product_rev, rev, tu_min32(strlen(rev), 4));
+}
+
+// max LUN supported
+// we only have the one SD card
+extern "C" uint8_t tud_msc_get_maxlun_cb(void) {
+  return 1; // number of LUNs supported
+}
+
+// return writable status
+// on platform supporting write protect switch, could do that here.
+// otherwise this is not actually needed
+extern "C" bool tud_msc_is_writable_cb (uint8_t lun)
+{
+  (void) lun;
+  return unitReady;
+}
+
+// see https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf pg 221
+extern "C" bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
+{
+  (void) lun;
+  (void) power_condition;
+
+  if (load_eject)  {
+    if (start) {
+      // load disk storage
+      // do nothing as we started "loaded"
+    } else {
+      unitReady = false;
+    }
+  }
+
+  return true;
+}
+
+// return true if we are ready to service reads/writes
+extern "C" bool tud_msc_test_unit_ready_cb(uint8_t lun) {
+  (void) lun;
+
+  return unitReady;
+}
+
+// return size in blocks and block size
+extern "C" void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count,
+                         uint16_t *block_size) {
+  (void) lun;
+
+  *block_count = unitReady ? (SD.card()->sectorCount()) : 0;
+  *block_size = SD_SECTOR_SIZE;
+}
+
+// Callback invoked when received an SCSI command not in built-in list (below) which have their own callbacks
+// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE, READ10 and WRITE10
+extern "C" int32_t tud_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer,
+                        uint16_t bufsize) {
+
+  const void *response = NULL;
+  uint16_t resplen = 0;
+
+  switch (scsi_cmd[0]) {
+  case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
+    // Host is about to read/write etc ... better not to disconnect disk
+    resplen = 0;
+    break;
+
+  default:
+    // Set Sense = Invalid Command Operation
+    tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);
+
+    // negative means error -> tinyusb could stall and/or response with failed status
+    resplen = -1;
+    break;
+  }
+
+  // return len must not larger than bufsize
+  if (resplen > bufsize) {
+    resplen = bufsize;
+  }
+
+  // copy response to stack's buffer if any
+  if (response && resplen) {
+    memcpy(buffer, response, resplen);
+  }
+
+  return resplen;
+}
+
+// Callback invoked when received READ10 command.
+// Copy disk's data to buffer (up to bufsize) and return number of copied bytes (must be multiple of block size)
+extern "C" int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, 
+                            void* buffer, uint32_t bufsize)
+{
+  (void) lun;
+
+  bool rc = SD.card()->readSectors(lba, (uint8_t*) buffer, bufsize/SD_SECTOR_SIZE);
+
+  // only blink fast on reads; writes will override this
+  if (MSC_LEDMode == LED_SOLIDON)
+    MSC_LEDMode = LED_BLINK_FAST;
+  
+  return rc ? bufsize : -1;
+}
+
+// Callback invoked when receive WRITE10 command.
+// Process data in buffer to disk's storage and return number of written bytes (must be multiple of block size)
+extern "C" int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset,
+                           uint8_t *buffer, uint32_t bufsize) {
+  (void) lun;
+
+  bool rc = SD.card()->writeSectors(lba, buffer, bufsize/SD_SECTOR_SIZE);
+
+  // always slow blink
+  MSC_LEDMode = LED_BLINK_SLOW;
+
+  return rc ? bufsize : -1;
+}
+
+// Callback invoked when WRITE10 command is completed (status received and accepted by host).
+// used to flush any pending cache to storage
+extern "C" void tud_msc_write10_complete_cb(uint8_t lun) {
+  (void) lun;
+}
+
+#endif

+ 40 - 0
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_msc.h

@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2023-2024 zigzagjoe
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * 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/>.
+**/
+
+#ifdef PLATFORM_MASS_STORAGE
+#pragma once
+
+// private constants/enums
+#define SD_SECTOR_SIZE 512
+
+/* return true if USB presence detected / eligble to enter CR mode */
+bool platform_sense_msc();
+
+/* perform MSC-specific init tasks */
+void platform_enter_msc();
+
+/* return true if we should remain in card reader mode. called in a loop. */
+bool platform_run_msc();
+
+/* perform any cleanup tasks for the MSC-specific functionality */
+void platform_exit_msc();
+
+#endif

+ 17 - 20
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_network.cpp

@@ -82,9 +82,8 @@ int platform_network_init(char *mac)
 		// retain Dayna vendor but use a device id specific to this board
 		pico_get_unique_board_id(&board_id);
 		if (g_log_debug)
-			logmsg_f("Unique board id: %02x %02x %02x %02x  %02x %02x %02x %02x",
-				board_id.id[0], board_id.id[1], board_id.id[2], board_id.id[3],
-				board_id.id[4], board_id.id[5], board_id.id[6], board_id.id[7]);
+			logmsg("Unique board id: ", board_id.id[0], " ", board_id.id[1], " ", board_id.id[2], " ", board_id.id[3], " ", 
+										board_id.id[4], " ", board_id.id[5], " ", board_id.id[6], " ", board_id.id[7]);
 
 		if (board_id.id[3] != 0 && board_id.id[4] != 0 && board_id.id[5] != 0)
 		{
@@ -101,11 +100,11 @@ int platform_network_init(char *mac)
 	cyw43_arch_enable_sta_mode();
 
 	cyw43_wifi_get_mac(&cyw43_state, CYW43_ITF_STA, read_mac);
-	logmsg_f("Wi-Fi MAC: %02X:%02X:%02X:%02X:%02X:%02X",
-		read_mac[0], read_mac[1], read_mac[2], read_mac[3], read_mac[4], read_mac[5]);
+	logmsg("Wi-Fi MAC: ", read_mac[0],":",read_mac[1], ":", read_mac[2], ":", read_mac[3], ":", read_mac[4], ":", read_mac[5]);
 	if (memcmp(mac, read_mac, sizeof(read_mac)) != 0)
-		logmsg_f("WARNING: Wi-Fi MAC is not what was requested (%02x:%02x:%02x:%02x:%02x:%02x), is libpico not compiled with CYW43_USE_OTP_MAC=0?",
-			mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+		logmsg("WARNING: Wi-Fi MAC is not what was requested (", 
+				(uint8_t)mac[0], ":", (uint8_t)mac[1], ":", (uint8_t)mac[2], ":", (uint8_t)mac[3], ":", (uint8_t)mac[4], ":", (uint8_t)mac[5],
+				"), is libpico not compiled with CYW43_USE_OTP_MAC=0?");
 
 	network_in_use = true;
 
@@ -117,7 +116,7 @@ void platform_network_add_multicast_address(uint8_t *mac)
 	int ret;
 
 	if ((ret = cyw43_wifi_update_multicast_filter(&cyw43_state, mac, true)) != 0)
-		logmsg_f("%s: cyw43_wifi_update_multicast_filter: %d", __func__, ret);
+		logmsg( __func__, ": cyw43_wifi_update_multicast_filter: ", ret);
 }
 
 bool platform_network_wifi_join(char *ssid, char *password)
@@ -129,18 +128,18 @@ bool platform_network_wifi_join(char *ssid, char *password)
 
 	if (password == NULL || password[0] == 0)
 	{
-		logmsg_f("Connecting to Wi-Fi SSID \"%s\" with no authentication", ssid);
+		logmsg("Connecting to Wi-Fi SSID \"", ssid, "\" with no authentication");
 		ret = cyw43_arch_wifi_connect_async(ssid, NULL, CYW43_AUTH_OPEN);
 	}
 	else
 	{
-		logmsg_f("Connecting to Wi-Fi SSID \"%s\" with WPA/WPA2 PSK", ssid);
+		logmsg("Connecting to Wi-Fi SSID \"", ssid, "\" with WPA/WPA2 PSK");
 		ret = cyw43_arch_wifi_connect_async(ssid, password, CYW43_AUTH_WPA2_MIXED_PSK);
 	}
 
 	if (ret != 0)
 	{
-		logmsg_f("Wi-Fi connection failed: %d", ret);
+		logmsg("Wi-Fi connection failed: ", ret);
 	}
 	else
 	{
@@ -168,7 +167,7 @@ int platform_network_send(uint8_t *buf, size_t len)
 {
 	int ret = cyw43_send_ethernet(&cyw43_state, 0, len, buf, 0);
 	if (ret != 0)
-		logmsg_f("cyw43_send_ethernet failed: %d", ret);
+		logmsg("cyw43_send_ethernet failed: ", ret);
 
 	return ret;
 }
@@ -257,11 +256,9 @@ void platform_network_wifi_dump_scan_list()
 		if (entry->ssid[0] == '\0')
 			break;
 			
-		logmsg_f("wifi[%d] = %s, channel %d, rssi %d, bssid %02x:%02x:%02x:%02x:%02x:%02x, flags %d",
-			i, entry->ssid, entry->channel, entry->rssi,
-			entry->bssid[0], entry->bssid[1], entry->bssid[2],
-			entry->bssid[3], entry->bssid[4], entry->bssid[5],
-			entry->flags);
+		logmsg("wifi[",i,"] = ",entry->ssid,", channel ",(int)entry->channel,", rssi ",(int)entry->rssi,
+				", bssid ",(uint8_t) entry->bssid[0],":",(uint8_t) entry->bssid[1],":",(uint8_t) entry->bssid[2],":",
+				(uint8_t) entry->bssid[3],":",(uint8_t) entry->bssid[4],":",(uint8_t) entry->bssid[5],", flags ", entry->flags);
 	}
 }
 
@@ -286,7 +283,7 @@ char * platform_network_wifi_ssid()
 	int ret = cyw43_ioctl(&cyw43_state, CYW43_IOCTL_GET_SSID, sizeof(ssid), (uint8_t *)&ssid, CYW43_ITF_STA);
 	if (ret)
 	{
-		logmsg_f("Failed getting Wi-Fi SSID: %d", ret);
+		logmsg("Failed getting Wi-Fi SSID: ", ret);
 		return NULL;
 	}
 
@@ -326,7 +323,7 @@ void cyw43_cb_process_ethernet(void *cb_data, int itf, size_t len, const uint8_t
 
 void cyw43_cb_tcpip_set_link_down(cyw43_t *self, int itf)
 {
-	logmsg_f("Disassociated from Wi-Fi SSID \"%s\"", self->ap_ssid);
+	logmsg("Disassociated from Wi-Fi SSID \"",  (char *)self->ap_ssid,"\"");
 }
 
 void cyw43_cb_tcpip_set_link_up(cyw43_t *self, int itf)
@@ -335,7 +332,7 @@ void cyw43_cb_tcpip_set_link_up(cyw43_t *self, int itf)
 
 	if (ssid)
 	{
-		logmsg_f("Successfully connected to Wi-Fi SSID \"%s\"", ssid);
+		logmsg("Successfully connected to Wi-Fi SSID \"",ssid,"\"");
 		// blink LED 3 times when connected
 		PICO_W_LED_OFF();
 		for (uint8_t i = 0; i < 3; i++)

+ 12 - 2
lib/ZuluSCSI_platform_RP2040/audio.cpp

@@ -138,8 +138,8 @@ static uint64_t fpos;
 static uint32_t fleft;
 
 // historical playback status information
-static audio_status_code audio_last_status[8] = {ASC_NO_STATUS};
-
+static audio_status_code audio_last_status[8] = {ASC_NO_STATUS, ASC_NO_STATUS, ASC_NO_STATUS, ASC_NO_STATUS,
+                                                 ASC_NO_STATUS, ASC_NO_STATUS, ASC_NO_STATUS, ASC_NO_STATUS};
 // volume information for targets
 static volatile uint16_t volumes[8] = {
     DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH,
@@ -585,4 +585,14 @@ void audio_set_channel(uint8_t id, uint16_t chn) {
     channels[id & 7] = chn;
 }
 
+uint64_t audio_get_file_position()
+{
+    return fpos;
+}
+
+void audio_set_file_position(uint32_t lba)
+{
+    fpos = 2352 * (uint64_t)lba;
+
+}
 #endif // ENABLE_AUDIO_OUTPUT

+ 41 - 0
lib/ZuluSCSI_platform_RP2040/process-linker-script.py

@@ -0,0 +1,41 @@
+# ZuluSCSI™ - Copyright (c) 2024 Rabbit Hole Computing™
+#
+# ZuluSCSI™ file is licensed under the GPL version 3 or any later version. 
+#
+# 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/>.
+from string import Template 
+Import ("env")
+
+template_file = 'lib/ZuluSCSI_platform_RP2040/rp2040-template.ld'
+linker_file = env.subst('$BUILD_DIR') + '/rp2040.ld'
+
+def process_template(source, target, env):
+    values = {
+        'program_size': env.GetProjectOption('program_flash_allocation'),
+        'project_name': env.subst('$PIOENV')
+        }
+    with open(template_file, 'r') as t:
+        src = Template(t.read())
+        result = src.substitute(values)
+
+    with open(linker_file, 'w') as linker_script:
+        linker_script.write(result)
+
+env.AddPreAction("${BUILD_DIR}/${PROGNAME}.elf",
+        env.VerboseAction(process_template, 
+        'Generating linker script: "' + linker_file + '" from : "' + template_file + '"'
+        )
+)

+ 137 - 0
lib/ZuluSCSI_platform_RP2040/program_flash.cpp

@@ -0,0 +1,137 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * 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/>.
+**/
+
+/**
+ * This has been removed from ZuluSCSI_platform.cpp so this code can be
+ * placed in SRAM while ZuluSCSI_platform.cpp.o can be placed in flash
+*/
+
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+#include <hardware/flash.h>
+#include <hardware/structs/xip_ctrl.h>
+#include <hardware/structs/usb.h>
+#ifndef PIO_FRAMEWORK_ARDUINO_NO_USB
+#include <SerialUSB.h>
+#include <class/cdc/cdc_device.h>
+#endif
+
+
+/*****************************************/
+/* Flash reprogramming from bootloader   */
+/*****************************************/
+
+#ifdef PLATFORM_BOOTLOADER_SIZE
+
+extern uint32_t __real_vectors_start;
+extern uint32_t __StackTop;
+static volatile void *g_bootloader_exit_req;
+
+__attribute__((section(".time_critical.platform_rewrite_flash_page")))
+bool platform_rewrite_flash_page(uint32_t offset, uint8_t buffer[PLATFORM_FLASH_PAGE_SIZE])
+{
+    if (offset == PLATFORM_BOOTLOADER_SIZE)
+    {
+        if (buffer[3] != 0x20 || buffer[7] != 0x10)
+        {
+            logmsg("Invalid firmware file, starts with: ", bytearray(buffer, 16));
+            return false;
+        }
+    }
+
+
+    if (NVIC_GetEnableIRQ(USBCTRL_IRQ_IRQn))
+    {
+        logmsg("Disabling USB during firmware flashing");
+        NVIC_DisableIRQ(USBCTRL_IRQ_IRQn);
+        usb_hw->main_ctrl = 0;
+    }
+
+    dbgmsg("Writing flash at offset ", offset, " data ", bytearray(buffer, 4));
+    assert(offset % PLATFORM_FLASH_PAGE_SIZE == 0);
+    assert(offset >= PLATFORM_BOOTLOADER_SIZE);
+
+    // Avoid any mbed timer interrupts triggering during the flashing.
+    __disable_irq();
+
+    // For some reason any code executed after flashing crashes
+    // unless we disable the XIP cache.
+    // Not sure why this happens, as flash_range_program() is flushing
+    // the cache correctly.
+    // The cache is now enabled from bootloader start until it starts
+    // flashing, and again after reset to main firmware.
+    xip_ctrl_hw->ctrl = 0;
+
+    flash_range_erase(offset, PLATFORM_FLASH_PAGE_SIZE);
+    flash_range_program(offset, buffer, PLATFORM_FLASH_PAGE_SIZE);
+
+    uint32_t *buf32 = (uint32_t*)buffer;
+    uint32_t num_words = PLATFORM_FLASH_PAGE_SIZE / 4;
+    for (int i = 0; i < num_words; i++)
+    {
+        uint32_t expected = buf32[i];
+        uint32_t actual = *(volatile uint32_t*)(XIP_NOCACHE_BASE + offset + i * 4);
+
+        if (actual != expected)
+        {
+            logmsg("Flash verify failed at offset ", offset + i * 4, " got ", actual, " expected ", expected);
+            __enable_irq();
+            return false;
+        }
+    }
+
+    __enable_irq();
+
+    return true;
+}
+
+
+void platform_boot_to_main_firmware()
+{
+    // To ensure that the system state is reset properly, we perform
+    // a SYSRESETREQ and jump straight from the reset vector to main application.
+    g_bootloader_exit_req = &g_bootloader_exit_req;
+    SCB->AIRCR = 0x05FA0004;
+    while(1);
+}
+
+void btldr_reset_handler()
+{
+    uint32_t* application_base = &__real_vectors_start;
+    if (g_bootloader_exit_req == &g_bootloader_exit_req)
+    {
+        // Boot to main application
+        application_base = (uint32_t*)(XIP_BASE + PLATFORM_BOOTLOADER_SIZE);
+    }
+
+    SCB->VTOR = (uint32_t)application_base;
+    __asm__(
+        "msr msp, %0\n\t"
+        "bx %1" : : "r" (application_base[0]),
+                    "r" (application_base[1]) : "memory");
+}
+
+// Replace the reset handler when building the bootloader
+// The rp2040_btldr.ld places real vector table at an offset.
+__attribute__((section(".btldr_vectors")))
+const void * btldr_vectors[2] = {&__StackTop, (void*)&btldr_reset_handler};
+
+#endif // PLATFORM_BOOTLOADER_SIZE

+ 0 - 217
lib/ZuluSCSI_platform_RP2040/rp2040-daynaport.ld

@@ -1,217 +0,0 @@
-/** 
- * ZuluSCSI™ - Copyright (c) 2023 Rabbit Hole Computing™
- * 
- * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
- * 
- * 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/>.
-**/
-
-MEMORY
-{
-    FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 576k
-    RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k  /* Leave space for pico-debug */
-    SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k
-    SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k
-}
-ENTRY(_entry_point)
-SECTIONS
-{
-    .flash_begin : {
-        __flash_binary_start = .;
-    } > FLASH
-    .boot2 : {
-        __boot2_start__ = .;
-        KEEP (*(.boot2))
-        __boot2_end__ = .;
-    } > FLASH
-    ASSERT(__boot2_end__ - __boot2_start__ == 256,
-        "ERROR: Pico second stage bootloader must be 256 bytes in size")
-    /* If ZuluSCSI SD card bootloader is included, it goes in first 128 kB */
-    
-    .text.bootloader : ALIGN(16) SUBALIGN(16)
-    {
-        KEEP(*(.text.btldr*))
-        . = ALIGN(131072);
-        CHECK_BOOTLOADER_SIZE = 1 / (. <= 131072);
-    } > FLASH
-
-    .text : {
-        __logical_binary_start = .;
-        __real_vectors_start = .;
-        KEEP (*(.vectors))
-        KEEP (*(.binary_info_header))
-        __binary_info_header_end = .;
-        KEEP (*(.reset))
-        KEEP (*(.init))
-        *(.fini)
-        *crtbegin.o(.ctors)
-        *crtbegin?.o(.ctors)
-        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
-        *(SORT(.ctors.*))
-        *(.ctors)
-        *crtbegin.o(.dtors)
-        *crtbegin?.o(.dtors)
-        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
-        *(SORT(.dtors.*))
-        *(.dtors)
-        *(.eh_frame*)
-        . = ALIGN(4);
-
-        /* Put only non-timecritical code in flash
-         * This includes e.g. floating point math routines.
-         */
-        *libm*:(.text .text*)
-        *libc*:(.text .text*)
-        *libgcc*:*df*(.text .text*)
-        *USB*(.text .text*)
-        *SPI*(.text .text*)
-        *Spi*(.text .text*)
-        *spi*(.text .text*)
-        *stdc*:(.text .text*)
-        *supc*:(.text .text*)
-        *nosys*:(.text .text*)
-        *libc*:*printf*(.text .text*)
-        *libc*:*toa*(.text .text*)
-        *libminIni.a:(.text .text*)
-        *libCUEParser.a:(.text .text*)
-        /* RP2040 breakpoints in RAM code don't always work very well
-         * because the boot routine tends to overwrite them.
-         * Uncommenting this line puts all code in flash.
-         */
-        /* *(.text .text*) */
-    } > FLASH
-    .rodata : {
-        . = ALIGN(4);
-        *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*)))
-        *(.rodata)
-        *(.rodata*)
-        . = ALIGN(4);
-    } > FLASH
-    .ARM.extab :
-    {
-        *(.ARM.extab* .gnu.linkonce.armextab.*)
-    } > FLASH
-    __exidx_start = .;
-    .ARM.exidx :
-    {
-        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
-    } > FLASH
-    __exidx_end = .;
-    . = ALIGN(4);
-    __binary_info_start = .;
-    .binary_info :
-    {
-        KEEP(*(.binary_info.keep.*))
-        *(.binary_info.*)
-    } > FLASH
-    __binary_info_end = .;
-    . = ALIGN(4);
-    __etext = .;
-   .ram_vector_table (COPY): {
-        *(.ram_vector_table)
-    } > RAM
-    .data : {
-        __data_start__ = .;
-        *(vtable)
-
-        /* Time critical code will go here to avoid external flash latency */
-        *(.time_critical*)
-        . = ALIGN(4);
-        *(.text)
-        *(.text*)
-
-        . = ALIGN(4);
-        *(.data*)
-        . = ALIGN(4);
-        *(.after_data.*)
-        . = ALIGN(4);
-        PROVIDE_HIDDEN (__mutex_array_start = .);
-        KEEP(*(SORT(.mutex_array.*)))
-        KEEP(*(.mutex_array))
-        PROVIDE_HIDDEN (__mutex_array_end = .);
-        . = ALIGN(4);
-        PROVIDE_HIDDEN (__preinit_array_start = .);
-        KEEP(*(SORT(.preinit_array.*)))
-        KEEP(*(.preinit_array))
-        PROVIDE_HIDDEN (__preinit_array_end = .);
-        . = ALIGN(4);
-        PROVIDE_HIDDEN (__init_array_start = .);
-        KEEP(*(SORT(.init_array.*)))
-        KEEP(*(.init_array))
-        PROVIDE_HIDDEN (__init_array_end = .);
-        . = ALIGN(4);
-        PROVIDE_HIDDEN (__fini_array_start = .);
-        *(SORT(.fini_array.*))
-        *(.fini_array)
-        PROVIDE_HIDDEN (__fini_array_end = .);
-        *(.jcr)
-        . = ALIGN(4);
-        __data_end__ = .;
-    } > RAM AT> FLASH
-    .uninitialized_data (COPY): {
-        . = ALIGN(4);
-        *(.uninitialized_data*)
-    } > RAM
-    .scratch_x : {
-        __scratch_x_start__ = .;
-        *(.scratch_x.*)
-        . = ALIGN(4);
-        __scratch_x_end__ = .;
-    } > SCRATCH_X AT > FLASH
-    __scratch_x_source__ = LOADADDR(.scratch_x);
-    .scratch_y : {
-        __scratch_y_start__ = .;
-        *(.scratch_y.*)
-        . = ALIGN(4);
-        __scratch_y_end__ = .;
-    } > SCRATCH_Y AT > FLASH
-    __scratch_y_source__ = LOADADDR(.scratch_y);
-    .bss : {
-        . = ALIGN(4);
-        __bss_start__ = .;
-        *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*)))
-        *(COMMON)
-        . = ALIGN(4);
-        __bss_end__ = .;
-    } > RAM
-    .heap (COPY):
-    {
-        __end__ = .;
-        PROVIDE(end = .);
-        *(.heap*)
-        . = ORIGIN(RAM) + LENGTH(RAM) - 0x400;
-        __HeapLimit = .;
-    } > RAM
-    .stack1_dummy (COPY):
-    {
-        *(.stack1*)
-    } > SCRATCH_X
-    .stack_dummy (COPY):
-    {
-        *(.stack*)
-    } > RAM
-    .flash_end : {
-        __flash_binary_end = .;
-    } > FLASH
-    __StackTop = ORIGIN(RAM) + LENGTH(RAM);
-    __StackLimit = __StackTop - 0x400;
-    __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X);
-    __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy);
-    __StackBottom = __StackTop - SIZEOF(.stack_dummy);
-    PROVIDE(__stack = __StackTop);
-    ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")
-    ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary")
-}

+ 7 - 2
lib/ZuluSCSI_platform_RP2040/rp2040.ld → lib/ZuluSCSI_platform_RP2040/rp2040-template.ld

@@ -21,7 +21,7 @@
 
 MEMORY
 {
-    FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 352k
+    FLASH(rx) : ORIGIN = 0x10000000, LENGTH = $program_size
     RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k  /* Leave space for pico-debug */
     SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k
     SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k
@@ -73,6 +73,11 @@ SECTIONS
         /* Put only non-timecritical code in flash
          * This includes e.g. floating point math routines.
          */
+        .pio/build/$project_name/src/ZuluSCSI_log.cpp.o(.text .text*)
+        .pio/build/$project_name/src/ZuluSCSI_log_trace.cpp.o(.text .text*)
+        .pio/build/$project_name/src/ZuluSCSI_settings.cpp.o(.text .text*)
+        .pio/build/$project_name/src/QuirksCheck.cpp.o(.text .text*)
+        *libZuluSCSI_platform_RP2040.a:ZuluSCSI_platform.cpp.o(.text .text*)
         *libm*:(.text .text*)
         *libc*:(.text .text*)
         *libgcc*:*df*(.text .text*)
@@ -86,6 +91,7 @@ SECTIONS
         *libc*:*printf*(.text .text*)
         *libc*:*toa*(.text .text*)
         *libminIni.a:(.text .text*)
+        *libCUEParser.a:(.text .text*)
 
         /* RP2040 breakpoints in RAM code don't always work very well
          * because the boot routine tends to overwrite them.
@@ -132,7 +138,6 @@ SECTIONS
         . = ALIGN(4);
         *(.text)
         *(.text*)
-
         . = ALIGN(4);
         *(.data*)
         . = ALIGN(4);

+ 1 - 6
lib/ZuluSCSI_platform_RP2040/scsi_accel_target.cpp

@@ -38,17 +38,12 @@
 #include <hardware/irq.h>
 #include <hardware/structs/iobank0.h>
 #include <hardware/sync.h>
+#include <pico/multicore.h>
 
 #ifdef ENABLE_AUDIO_OUTPUT
 #include <audio.h>
 #endif // ENABLE_AUDIO_OUTPUT
 
-#ifdef ZULUSCSI_NETWORK
-#include <pico/multicore.h>
-#else
-#include <multicore.h>
-#endif // ZULUSCSI_NETWORK
-
 #if defined(ZULUSCSI_PICO) || defined(ZULUSCSI_BS2)
 #include "scsi_accel_target_Pico.pio.h"
 #else

+ 4 - 1
lib/ZuluSCSI_platform_RP2040/sd_card_sdio.cpp

@@ -95,7 +95,10 @@ bool SdioCard::begin(SdioConfig sdioConfig)
     // Establish initial connection with the card
     for (int retries = 0; retries < 5; retries++)
     {
-        delayMicroseconds(1000);
+        // After a hard fault crash, delayMicroseconds hangs
+        // using busy_wait_us_32 instead
+        // delayMicroseconds(1000);
+        busy_wait_us_32(1000);
         reply = 0;
         rp2040_sdio_command_R1(CMD0, 0, NULL); // GO_IDLE_STATE
         status = rp2040_sdio_command_R1(CMD8, 0x1AA, &reply); // SEND_IF_COND

+ 68 - 19
platformio.ini

@@ -43,7 +43,7 @@ lib_deps =
 upload_protocol = stlink
 platform_packages = platformio/toolchain-gccarmnoneeabi@1.100301.220327
     framework-spl-gd32@https://github.com/CommunityGD32Cores/gd32-pio-spl-package.git
-debug_tool = cmsis-dap
+debug_tool = stlink
 extra_scripts = src/build_bootloader.py
 debug_build_flags = 
      -Os -Wall -Wno-sign-compare -ggdb -g3
@@ -55,6 +55,7 @@ build_flags =
      -DENABLE_DEDICATED_SPI=1
      -DPIO_USBFS_DEVICE_CDC
      -DZULUSCSI_V1_0
+     -DPLATFORM_MASS_STORAGE
 
 ; ZuluSCSI V1.0 mini hardware platform with GD32F205 CPU.
 [env:ZuluSCSIv1_0_mini]
@@ -68,6 +69,7 @@ build_flags =
      -DPIO_USBFS_DEVICE_CDC
      -DZULUSCSI_V1_0
      -DZULUSCSI_V1_0_mini
+     -DPLATFORM_MASS_STORAGE
 
 ; ZuluSCSI V1.1+ hardware platforms, this support v1.1, v1.1 ODE, and vl.2
 [env:ZuluSCSIv1_1_plus]
@@ -82,15 +84,24 @@ build_flags =
      -DHAS_SDIO_CLASS
      -DENABLE_AUDIO_OUTPUT
      -DZULUSCSI_V1_1_plus
+     -DPLATFORM_MASS_STORAGE
 
 ; ZuluSCSI RP2040 hardware platform, based on the Raspberry Pi foundation RP2040 microcontroller
 [env:ZuluSCSI_RP2040]
-platform = raspberrypi@1.9.0
+platform = https://github.com/maxgerhardt/platform-raspberrypi.git
+platform_packages =
+    framework-arduinopico@https://github.com/rabbitholecomputing/arduino-pico.git#v3.6.0-DaynaPORT
+board_build.core = earlephilhower
+board = zuluscsi_rp2040
 framework = arduino
-board = ZuluSCSI_RP2040
-extra_scripts = src/build_bootloader.py
-platform_packages = platformio/toolchain-gccarmnoneeabi@1.100301.220327
-board_build.ldscript = lib/ZuluSCSI_platform_RP2040/rp2040.ld
+; How much flash in bytes the bootloader and main app will be allocated
+; It is used as the starting point for a ROM image saved in flash
+; Changing this will cause issues with boards that already have a ROM drive in flash
+program_flash_allocation = 360448
+extra_scripts =
+    src/build_bootloader.py
+    lib/ZuluSCSI_platform_RP2040/process-linker-script.py
+board_build.ldscript = ${BUILD_DIR}/rp2040.ld
 ldscript_bootloader = lib/ZuluSCSI_platform_RP2040/rp2040_btldr.ld
 lib_deps =
     SdFat=https://github.com/rabbitholecomputing/SdFat#2.2.0-gpt
@@ -99,11 +110,11 @@ lib_deps =
     SCSI2SD
     CUEParser
 upload_protocol = cmsis-dap
+debug_tool = cmsis-dap
 debug_build_flags =
     -O2 -ggdb -g3
 ; The values can be adjusted down to get a debug build to fit in to SRAM
     -DLOGBUFSIZE=4096
-    ; -DPREFETCH_BUFFER_SIZE=2048
 build_flags =
     -O2 -Isrc -ggdb -g3
     -Wall -Wno-sign-compare -Wno-ignored-qualifiers
@@ -112,7 +123,14 @@ build_flags =
     -DENABLE_DEDICATED_SPI=1
     -DHAS_SDIO_CLASS
     -DUSE_ARDUINO=1
+    -DPICO_FLASH_SPI_CLKDIV=2
     -DZULUSCSI_V2_0
+    -DROMDRIVE_OFFSET=${env:ZuluSCSI_RP2040.program_flash_allocation}
+; build flags mirroring the framework-arduinopico#v3.6.0-DaynaPORT static library build
+    -DPICO_CYW43_ARCH_POLL=1
+	-DCYW43_LWIP=0
+	-DCYW43_USE_OTP_MAC=0
+    -DPLATFORM_MASS_STORAGE
 
 ; ZuluSCSI RP2040 hardware platform, as above, but with audio output support enabled
 [env:ZuluSCSI_RP2040_Audio]
@@ -135,6 +153,12 @@ build_flags =
     -DHAS_SDIO_CLASS
     -DUSE_ARDUINO=1
     -DZULUSCSI_PICO
+    -DROMDRIVE_OFFSET=${env:ZuluSCSI_RP2040.program_flash_allocation}
+; build flags mirroring the framework-arduinopico#v3.6.0-DaynaPORT static library build
+    -DPICO_CYW43_ARCH_POLL=1
+	-DCYW43_LWIP=0
+	-DCYW43_USE_OTP_MAC=0
+    -DPLATFORM_MASS_STORAGE
 
 ; Build for the ZuluSCSI Pico carrier board with a Pico-W
 ; for SCSI DaynaPORT emulation
@@ -145,18 +169,27 @@ platform_packages =
 framework = arduino
 board = rpipicow
 board_build.core = earlephilhower
-extra_scripts = src/build_bootloader.py
+; How much flash in bytes the bootloader and main app will be allocated
+; It is used as the starting point for a ROM image saved in flash
+program_flash_allocation = 589824
+extra_scripts =
+    src/build_bootloader.py
+    lib/ZuluSCSI_platform_RP2040/process-linker-script.py
 ldscript_bootloader = lib/ZuluSCSI_platform_RP2040/rp2040_btldr.ld
-board_build.ldscript = lib/ZuluSCSI_platform_RP2040/rp2040-daynaport.ld
+board_build.ldscript = ${BUILD_DIR}/rp2040.ld
 debug_tool = cmsis-dap
 debug_build_flags =
     -O2 -ggdb -g3
     -DLOGBUFSIZE=4096
     -DPREFETCH_BUFFER_SIZE=0
     -DSCSI2SD_BUFFER_SIZE=57344
-    ; This controls the depth of 2 x NETWORK_PACKET_MAX_SIZE (1520 bytes)
-    ; For example a queue size of 10 would be 10 x 2 x 1520 = 30400 bytes
+; This controls the depth NETWORK_PACKET_MAX_SIZE (1520 bytes)
+; For example a queue size of 10 would be 10 x 1520 = 30400 bytes
     -DNETWORK_PACKET_QUEUE_SIZE=10
+; This flag enables verbose logging of TCP/IP traffic and other information
+; it also takes up a bit of SRAM so it should be disabled with production code
+    -DNETWORK_DEBUG_LOGGING
+    -DPLATFORM_MASS_STORAGE
 
 lib_deps =
     SdFat=https://github.com/rabbitholecomputing/SdFat#2.2.0-gpt
@@ -175,18 +208,25 @@ build_flags =
     -DZULUSCSI_PICO
     -DZULUSCSI_NETWORK
     -DZULUSCSI_DAYNAPORT
+    -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=4096
+    -DPREFETCH_BUFFER_SIZE=4608
     -DSCSI2SD_BUFFER_SIZE=57344
-    ; This controls the depth of 2 x NETWORK_PACKET_MAX_SIZE (1520 bytes)
-    ; For example a queue size of 10 would be 10 x 2 x 1520 = 30400 bytes
-    -DNETWORK_PACKET_QUEUE_SIZE=18
+; 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
+    -DNETWORK_PACKET_QUEUE_SIZE=14
+    
+; This flag enables verbose logging of TCP/IP traffic and other information
+; it also takes up a bit of SRAM so it should be disabled with production code
+    ;-DNETWORK_DEBUG_LOGGING
+
 ; build flags mirroring the framework-arduinopico#v3.6.0-DaynaPORT static library build
     -DPICO_CYW43_ARCH_POLL=1
 	-DCYW43_LWIP=0
 	-DCYW43_USE_OTP_MAC=0
-    -DPIO_FRAMEWORK_ARDUINO_NO_USB
+ ;   -DPIO_FRAMEWORK_ARDUINO_NO_USB
+    -DPLATFORM_MASS_STORAGE
 
 ; Variant of RP2040 platform, based on Raspberry Pico board and a carrier PCB
 ; Differs in pinout from ZuluSCSI_RP2040 platform, but shares most of the code.
@@ -201,14 +241,23 @@ build_flags =
     -DHAS_SDIO_CLASS
     -DUSE_ARDUINO=1
     -DZULUSCSI_BS2
+    -DROMDRIVE_OFFSET=${env:ZuluSCSI_RP2040.program_flash_allocation}
+; build flags mirroring the framework-arduinopico#v3.6.0-DaynaPORT static library build
+    -DPICO_CYW43_ARCH_POLL=1
+	-DCYW43_LWIP=0
+	-DCYW43_USE_OTP_MAC=0
+    -DPLATFORM_MASS_STORAGE
 
-; ZuluSCSI F4 hardware platform with GD32F450ZET6 CPU.
-[env:ZuluSCSIv1_4]
+; ZuluSCSI VF4 hardware platform with GD32F450ZET6 CPU.
+[env:ZULUSCSIv1_4]
 platform = https://github.com/CommunityGD32Cores/platform-gd32.git
 board = genericGD32F450ZE
 board_build.mcu = gd32f450zet6
 board_build.core = gd32
 board_build.ldscript = lib/ZuluSCSI_platform_GD32F450/zuluscsi_gd32f450.ld
+lib_ignore = 
+    ZuluSCSI_platform_GD32F205
+    ZuluSCSI_platform_RP2040
 ldscript_bootloader = lib/ZuluSCSI_platform_GD32F450/zuluscsi_gd32f450_btldr.ld
 framework = spl
 lib_compat_mode = off
@@ -224,7 +273,7 @@ platform_packages =
     toolchain-gccarmnoneeabi@1.90201.191206
     framework-spl-gd32@https://github.com/CommunityGD32Cores/gd32-pio-spl-package.git
 extra_scripts = src/build_bootloader.py
-debug_tool = cmsis-dap
+debug_tool = stlink
 debug_build_flags = -Os -ggdb -g3
 build_flags = 
      -Os -Wall -Wno-sign-compare -ggdb -g3 -Isrc

+ 0 - 7
src/Toolbox.cpp

@@ -40,13 +40,6 @@ extern "C" int8_t scsiToolboxEnabled()
     return enabled == 1;
 }
 
-extern "C" void scsiToolboxCBDLen(uint8_t command, uint8_t* command_length)
-{
-  if (0xD0 <= command && command <= 0xDA)
-  {
-    *command_length = 10;
-  }
-}
 
 static bool toolboxFilenameValid(const char* name, bool isCD = false)
 {

+ 32 - 7
src/ZuluSCSI.cpp

@@ -9,6 +9,7 @@
  *  
  * This work incorporates work by following
  *  Copyright (c) 2023 joshua stein <jcs@jcs.org>
+ *  Copyright (c) 2023 zigzagjoe
  * 
  *  This file is free software: you may copy, redistribute and/or modify it  
  *  under the terms of the GNU General Public License as published by the  
@@ -56,6 +57,7 @@
 #include "ZuluSCSI_settings.h"
 #include "ZuluSCSI_disk.h"
 #include "ZuluSCSI_initiator.h"
+#include "ZuluSCSI_msc.h"
 #include "ROMDrive.h"
 
 SdFs SD;
@@ -459,10 +461,10 @@ bool findHDDImages()
         if (blksize)
         {
           int blktmp = strtoul(blksize + 1, NULL, 10);
-          if (blktmp == 256 || blktmp == 512 || blktmp == 1024 ||
-              blktmp == 2048 || blktmp == 4096 || blktmp == 8192)
+          if (8 <= blktmp && blktmp <= 64 * 1024)
           {
             blk = blktmp;
+            logmsg("-- Using custom block size, ",(int) blk," from filename: ", name);
           }
         }
 
@@ -719,10 +721,10 @@ static void reinitSCSI()
 #endif // ZULUSCSI_NETWORK
   
 }
-extern "C" void zuluscsi_setup(void)
+// Place all the setup code that requires the SD card to be initialized here
+// Which is pretty much everything after platform_init and and platform_late_init
+static void zuluscsi_setup_sd_card()
 {
-  platform_init();
-  platform_late_init();
 
   g_sdcard_present = mountSDCard();
 
@@ -782,8 +784,6 @@ extern "C" void zuluscsi_setup(void)
 
   }
 
-  logmsg("Initialization complete!");
-
   if (g_sdcard_present)
   {
     init_logfile();
@@ -797,6 +797,31 @@ extern "C" void zuluscsi_setup(void)
   LED_OFF();
 }
 
+extern "C" void zuluscsi_setup(void)
+{
+  platform_init();
+  platform_late_init();
+  zuluscsi_setup_sd_card();
+
+#ifdef PLATFORM_MASS_STORAGE
+  static bool check_mass_storage = true;
+  if (check_mass_storage && g_scsi_settings.getSystem()->enableUSBMassStorage)
+  {
+    check_mass_storage = false;
+    
+    // perform checks to see if a computer is attached and return true if we should enter MSC mode.
+    if (platform_sense_msc())
+    {
+      zuluscsi_msc_loop();
+      logmsg("Re-processing filenames and zuluscsi.ini config parameters");
+      zuluscsi_setup_sd_card();
+    }
+  }
+#endif
+
+  logmsg("Initialization complete!");
+}
+
 extern "C" void zuluscsi_main_loop(void)
 {
   static uint32_t sd_card_check_time = 0;

+ 14 - 1
src/ZuluSCSI_audio.h

@@ -145,4 +145,17 @@ uint16_t audio_get_channel(uint8_t id);
  * \param id    SCSI ID to set channel information for.
  * \param chn   The new channel information.
  */
-void audio_set_channel(uint8_t id, uint16_t chn);
+void audio_set_channel(uint8_t id, uint16_t chn);
+
+/**
+ * Gets the byte position in the audio image
+ * 
+ * \return byte position in the audio image
+*/
+uint64_t audio_get_file_position();
+
+/**
+ * Sets the playback position in the audio image via the lba
+ * 
+*/
+void audio_set_file_position(uint32_t lba);

+ 165 - 24
src/ZuluSCSI_cdrom.cpp

@@ -31,8 +31,10 @@
 #include "ZuluSCSI_cdrom.h"
 #include "ZuluSCSI_log.h"
 #include "ZuluSCSI_config.h"
+#include "ZuluSCSI_settings.h"
 #include <CUEParser.h>
 #include <assert.h>
+#include <minIni.h>
 #ifdef ENABLE_AUDIO_OUTPUT
 #include "ZuluSCSI_audio.h"
 #endif
@@ -41,6 +43,8 @@ extern "C" {
 #include <scsi.h>
 }
 
+static const uint16_t AUDIO_CD_SECTOR_LEN = 2352;
+
 /******************************************/
 /* Basic TOC generation without cue sheet */
 /******************************************/
@@ -218,6 +222,12 @@ static const uint8_t TrackInformation[] =
     0x00,   // 27: track size (LSB)
 };
 
+enum sector_type_t
+{
+     SECTOR_TYPE_VENDOR_PLEXTOR = 100,
+     SECTOR_TYPE_VENDOR_APPLE_300plus = 200
+};
+
 // Convert logical block address to CD-ROM time
 static void LBA2MSF(int32_t LBA, uint8_t* MSF, bool relative)
 {
@@ -1089,7 +1099,7 @@ void doGetConfiguration(uint8_t rt, uint16_t startFeature, uint16_t allocationLe
 #endif
 
     // finally, rewrite data length to match
-    uint32_t dlen = len - 8;
+    uint32_t dlen = len - 4;
     scsiDev.data[0] = dlen >> 24;
     scsiDev.data[1] = dlen >> 16;
     scsiDev.data[2] = dlen >> 8;
@@ -1300,9 +1310,9 @@ static void doGetEventStatusNotification(bool immed)
 
 void cdromGetAudioPlaybackStatus(uint8_t *status, uint32_t *current_lba, bool current_only)
 {
-    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
-
+    
 #ifdef ENABLE_AUDIO_OUTPUT
+    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
     if (status) {
         uint8_t target = img.scsiId & 7;
         if (current_only) {
@@ -1311,17 +1321,11 @@ void cdromGetAudioPlaybackStatus(uint8_t *status, uint32_t *current_lba, bool cu
             *status = (uint8_t) audio_get_status_code(target);
         }
     }
+    *current_lba = audio_get_file_position() / 2352;
 #else
     if (status) *status = 0; // audio status code for 'unsupported/invalid' and not-playing indicator
 #endif
-    if (current_lba)
-    {
-        if (img.file.isOpen()) {
-            *current_lba = img.file.position() / 2352;
-        } else {
-            *current_lba = 0;
-        }
-    }
+    
 }
 
 static void doPlayAudio(uint32_t lba, uint32_t length)
@@ -1338,8 +1342,10 @@ static void doPlayAudio(uint32_t lba, uint32_t length)
 
     // if transfer length is zero no audio playback happens.
     // don't treat as an error per SCSI-2; handle via short-circuit
+
     if (length == 0)
     {
+        audio_set_file_position(lba);
         scsiDev.status = 0;
         scsiDev.phase = STATUS;
         return;
@@ -1355,7 +1361,7 @@ static void doPlayAudio(uint32_t lba, uint32_t length)
         if (lba == 0xFFFFFFFF)
         {
             // request to start playback from 'current position'
-            lba = img.file.position() / 2352;
+            lba = audio_get_file_position() / AUDIO_CD_SECTOR_LEN;
         }
 
         uint64_t offset = trackinfo.file_offset
@@ -1413,7 +1419,7 @@ static void doPlayAudio(uint32_t lba, uint32_t length)
 static void doPauseResumeAudio(bool resume)
 {
 #ifdef ENABLE_AUDIO_OUTPUT
-    logmsg("------ CD-ROM ", resume ? "resume" : "pause", " audio playback");
+    dbgmsg("------ CD-ROM ", resume ? "resume" : "pause", " audio playback");
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
     uint8_t target_id = img.scsiId & 7;
 
@@ -1454,7 +1460,7 @@ static void doMechanismStatus(uint16_t allocation_length)
     uint8_t *buf = scsiDev.data;
 
     uint8_t status;
-    uint32_t lba;
+    uint32_t lba = 0;
     cdromGetAudioPlaybackStatus(&status, &lba, true);
 
     *buf++ = 0x00; // No fault state
@@ -1503,12 +1509,46 @@ static void doReadCD(uint32_t lba, uint32_t length, uint8_t sector_type,
     getTrackFromLBA(parser, lba, &trackinfo);
 
     // Figure out the data offset in the file
-    uint64_t offset = trackinfo.file_offset + trackinfo.sector_length * (lba - trackinfo.track_start);
-    dbgmsg("------ Read CD: ", (int)length, " sectors starting at ", (int)lba,
-           ", track number ", trackinfo.track_number, ", sector size ", (int)trackinfo.sector_length,
-           ", main channel ", main_channel, ", sub channel ", sub_channel,
-           ", data offset in file ", (int)offset);
+    uint64_t offset;
+    if (sector_type == SECTOR_TYPE_VENDOR_PLEXTOR &&
+         g_scsi_settings.getDevice(img.scsiId & 0x7)->vendorExtensions & VENDOR_EXTENSION_OPTICAL_PLEXTOR)
+    {
+        // This overrides values so doReadCD can be used with the
+        // Vendor specific Plextor 0xD8 command to read raw CD data
+        if (lba < 2)
+        {
+            logmsg("WARNING: Plextor CD-ROM vendor extension 0xd8 lba start below 2 is not implemented. LBA is ", lba);
+            scsiDev.status = CHECK_CONDITION;
+            scsiDev.target->sense.code = ILLEGAL_REQUEST;
+            scsiDev.target->sense.asc = LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+            scsiDev.phase = STATUS;
+            return;
+        }
 
+        trackinfo.sector_length = AUDIO_CD_SECTOR_LEN;
+        trackinfo.track_mode = CUETrack_AUDIO;
+        offset = (uint64_t)(lba - 2) * trackinfo.sector_length;
+        dbgmsg("------ Read CD Vendor Plextor (0xd8): ", (int)length, " sectors starting at ", (int)lba -2,
+               ", sector size ", (int) AUDIO_CD_SECTOR_LEN,
+               ", data offset in file ", (int)offset);
+    }
+    if (sector_type == SECTOR_TYPE_VENDOR_APPLE_300plus)
+    {
+        trackinfo.sector_length = AUDIO_CD_SECTOR_LEN;
+        trackinfo.track_mode = CUETrack_AUDIO;
+        offset = (uint64_t)(lba) * trackinfo.sector_length;
+        dbgmsg("------ Read CD Vendor Apple CDROM 300 plus (0xd8): ", (int)length, " sectors starting at ", (int)lba,
+               ", sector size ", (int) AUDIO_CD_SECTOR_LEN,
+               ", data offset in file ", (int)offset);
+    }
+    else
+    {
+        offset = trackinfo.file_offset + trackinfo.sector_length * (lba - trackinfo.track_start);
+        dbgmsg("------ Read CD: ", (int)length, " sectors starting at ", (int)lba,
+            ", track number ", trackinfo.track_number, ", sector size ", (int)trackinfo.sector_length,
+            ", main channel ", main_channel, ", sub channel ", sub_channel,
+            ", data offset in file ", (int)offset);
+    }
     // Ensure read is not out of range of the image
     uint64_t readend = offset + trackinfo.sector_length * length;
     if (readend > img.file.size())
@@ -1534,6 +1574,15 @@ static void doReadCD(uint32_t lba, uint32_t length, uint8_t sector_type,
         {
             sector_type_ok = true;
         }
+        else if (sector_type == SECTOR_TYPE_VENDOR_PLEXTOR && 
+            g_scsi_settings.getDevice(img.scsiId & 0x7)->vendorExtensions & VENDOR_EXTENSION_OPTICAL_PLEXTOR)
+        {
+            sector_type_ok = true;
+        }
+        else if (sector_type == SECTOR_TYPE_VENDOR_APPLE_300plus )
+        {
+            sector_type_ok = true;
+        }
 
         if (!sector_type_ok)
         {
@@ -1561,7 +1610,7 @@ static void doReadCD(uint32_t lba, uint32_t length, uint8_t sector_type,
     else if (trackinfo.track_mode == CUETrack_AUDIO)
     {
         // Transfer whole 2352 byte audio sectors from file to host
-        sector_length = 2352;
+        sector_length = AUDIO_CD_SECTOR_LEN;
     }
     else if (trackinfo.track_mode == CUETrack_MODE1_2048 && main_channel == 0x10)
     {
@@ -1584,7 +1633,7 @@ static void doReadCD(uint32_t lba, uint32_t length, uint8_t sector_type,
     else if (trackinfo.track_mode == CUETrack_MODE1_2352 && (main_channel & 0xB8) == 0xB8)
     {
         // Transfer whole 2352 byte data sector with ECC to host
-        sector_length = 2352;
+        sector_length = AUDIO_CD_SECTOR_LEN;
     }
     else
     {
@@ -1720,7 +1769,7 @@ static void doReadSubchannel(bool time, bool subq, uint8_t parameter, uint8_t tr
     if (parameter == 0x01)
     {
         uint8_t audiostatus;
-        uint32_t lba;
+        uint32_t lba = 0;
         cdromGetAudioPlaybackStatus(&audiostatus, &lba, false);
         dbgmsg("------ Get audio playback position: status ", (int)audiostatus, " lba ", (int)lba);
 
@@ -1851,6 +1900,16 @@ static bool doReadCapacity(uint32_t lba, uint8_t pmi)
     return true;
 }
 
+static void doReadPlextorD8(uint32_t lba, uint32_t length)
+{
+    doReadCD(lba, length, SECTOR_TYPE_VENDOR_PLEXTOR, true, false, false );
+}
+
+static void doAppleD8(uint32_t lba, uint32_t blocks)
+{
+    doReadCD(lba, blocks, SECTOR_TYPE_VENDOR_APPLE_300plus, true, false, false );
+}
+
 /**************************************/
 /* CD-ROM command dispatching         */
 /**************************************/
@@ -2047,8 +2106,9 @@ extern "C" int scsiCDRomCommand()
                 && scsiDev.cdb[5] == 0xFF)
         {
             // request to start playback from 'current position'
-            image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
-            lba = img.file.position() / 2352;
+#ifdef ENABLE_AUDIO_OUTPUT
+            lba = audio_get_file_position() / AUDIO_CD_SECTOR_LEN;
+#endif
         }
 
         uint32_t length = end - lba;
@@ -2153,6 +2213,42 @@ extern "C" int scsiCDRomCommand()
 
         doReadCD(lba, blocks, 0, 0x10, 0, true);
     }
+    else if (unlikely(scsiDev.target->cfg->vendorExtensions & VENDOR_EXTENSION_OPTICAL_PLEXTOR) && 
+            command == 0xD8)
+    {
+        uint8_t lun = scsiDev.cdb[1] & 0x7;
+        uint8_t subcode = scsiDev.cdb[10];
+        if (lun != 0)
+        {
+            logmsg("ERROR: Plextor vendor 0xD8 command only supports LUN 0. Given ", (int)lun);
+            scsiDev.status = CHECK_CONDITION;
+            scsiDev.target->sense.code = ILLEGAL_REQUEST;
+            scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+            scsiDev.phase = STATUS;
+        }
+        else if (subcode != 0)
+        {
+            logmsg("ERROR: Plextor vendor 0xD8 command subcodes not supported. Given ", subcode);
+            scsiDev.status = CHECK_CONDITION;
+            scsiDev.target->sense.code = ILLEGAL_REQUEST;
+            scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+            scsiDev.phase = STATUS;
+        }
+        else
+        {
+            uint32_t lba =
+                (((uint32_t) scsiDev.cdb[2]) << 24) +
+                (((uint32_t) scsiDev.cdb[3]) << 16) +
+                (((uint32_t) scsiDev.cdb[4]) << 8) +
+                scsiDev.cdb[5];
+            uint32_t blocks =
+                (((uint32_t) scsiDev.cdb[6]) << 24) +
+                (((uint32_t) scsiDev.cdb[7]) << 16) +
+                (((uint32_t) scsiDev.cdb[8]) << 8) +
+                scsiDev.cdb[9];
+            doReadPlextorD8(lba, blocks);
+        }
+    }
     else if (command == 0x4E)
     {
         // STOP PLAY/SCAN
@@ -2193,6 +2289,51 @@ extern "C" int scsiCDRomCommand()
         // Byte 5: 'F' in hex
         commandHandled = 0;
     }
+    else if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_APPLE
+            && command == 0xD8)
+    {
+        // vendor-specific command for Apple 300 plus
+        // plays CD audio over the SCSI bus
+        uint32_t lba =
+            (((uint32_t) scsiDev.cdb[2]) << 24) +
+            (((uint32_t) scsiDev.cdb[3]) << 16) +
+            (((uint32_t) scsiDev.cdb[4]) << 8) +
+            scsiDev.cdb[5];
+        uint32_t blocks =
+            (((uint32_t) scsiDev.cdb[6]) << 24) +
+            (((uint32_t) scsiDev.cdb[7]) << 16) +
+            (((uint32_t) scsiDev.cdb[8]) << 8) +
+            scsiDev.cdb[9];
+        uint8_t sub_sector_type = scsiDev.cdb[10];
+        if (sub_sector_type != 0)
+        {
+            dbgmsg("For Apple CD-ROM 0xD8 command, only 2352 sector length supported (type 0), got subsector type: ", sub_sector_type);
+        }
+        doAppleD8(lba, blocks);
+    }
+    else if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_APPLE
+            && command == 0xD9)
+    {
+        // vendor-specific command for Apple 300 plus
+        // plays CD audio over the SCSI bus using MSF
+
+        uint8_t m = scsiDev.cdb[3];
+        uint8_t s = scsiDev.cdb[4];
+        uint8_t f = scsiDev.cdb[5];
+        uint32_t lba = MSF2LBA(m, s, f, false);
+
+        m = scsiDev.cdb[7];
+        s = scsiDev.cdb[8];
+        f = scsiDev.cdb[9];
+        uint32_t blocks = MSF2LBA(m, s, f, false) - lba;
+        uint8_t sub_sector_type = scsiDev.cdb[10];
+        if (sub_sector_type != 0)
+        {
+            dbgmsg("For Apple CD-ROM 0xD9 command, only 2352 sector length supported (type 0), got subsector type: ", sub_sector_type);
+        }
+
+        doAppleD8(lba, blocks);
+    }
     else
     {
         commandHandled = 0;

+ 2 - 2
src/ZuluSCSI_config.h

@@ -28,7 +28,7 @@
 #include <ZuluSCSI_platform.h>
 
 // Use variables for version number
-#define FW_VER_NUM      "23.12.14"
+#define FW_VER_NUM      "24.03.19"
 #define FW_VER_SUFFIX   "dev"
 #define ZULU_FW_VERSION FW_VER_NUM "-" FW_VER_SUFFIX
 
@@ -89,7 +89,7 @@
 // Default SCSI drive information when Apple quirks are enabled
 #define APPLE_DRIVEINFO_FIXED     {"CDC",      "ZuluSCSI HDD",      PLATFORM_REVISION, "1.0"}
 #define APPLE_DRIVEINFO_REMOVABLE {"IOMEGA",   "BETA230",           PLATFORM_REVISION, "2.02"}
-#define APPLE_DRIVEINFO_OPTICAL   {"MATSHITA", "CD-ROM CR-8004A",   PLATFORM_REVISION, "2.0a"}
+#define APPLE_DRIVEINFO_OPTICAL   {"MATSHITA", "CD-ROM CR-8004",    PLATFORM_REVISION, "1.1f"}
 #define APPLE_DRIVEINFO_FLOPPY    {"IOMEGA",     "Io20S         *F", "PP33", ""}
 #define APPLE_DRIVEINFO_MAGOPT    {"MOST",     "RMD-5200",          PLATFORM_REVISION, "1.0"}
 #define APPLE_DRIVEINFO_NETWORK   {"Dayna",    "SCSI/Link",       "2.0f", ""}

+ 7 - 0
src/ZuluSCSI_disk.cpp

@@ -250,6 +250,8 @@ static void scsiDiskSetImageConfig(uint8_t target_idx)
     img.reinsert_on_inquiry = devCfg->reinsertOnInquiry;
     img.reinsert_after_eject = devCfg->reinsertAfterEject;
     img.ejectButton = devCfg->ejectButton;
+    img.vendorExtensions = devCfg->vendorExtensions;
+
 #ifdef ENABLE_AUDIO_OUTPUT
     uint16_t vol = devCfg->vol;
     // Set volume on both channels
@@ -704,6 +706,7 @@ int scsiDiskGetNextImageName(image_config_t &img, char *buf, size_t buflen)
         int ret = ini_gets(section, key, "", buf, buflen, CONFIGFILE);
         if (buf[0] != '\0')
         {
+            img.deviceType = g_scsi_settings.getDevice(target_idx)->deviceType;
             return ret;
         }
         else if (img.image_index > 0)
@@ -1112,6 +1115,10 @@ static void doReadCapacity()
     {
         uint32_t highestBlock = capacity - 1;
 
+	if (pmi && scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_EWSD)
+	{
+		highestBlock = 0x00001053;
+	}
         scsiDev.data[0] = highestBlock >> 24;
         scsiDev.data[1] = highestBlock >> 16;
         scsiDev.data[2] = highestBlock >> 8;

+ 160 - 42
src/ZuluSCSI_initiator.cpp

@@ -81,11 +81,19 @@ static struct {
     uint32_t sectorcount_all;
     uint32_t sectors_done;
     uint32_t max_sector_per_transfer;
+    uint32_t bad_sector_count;
+    uint8_t ansi_version;
+    uint8_t max_retry_count;
+    uint8_t device_type;
 
     // Retry information for sector reads.
     // If a large read fails, retry is done sector-by-sector.
     int retrycount;
     uint32_t failposition;
+    bool eject_when_done;
+    bool removable;
+
+    uint32_t removable_count[8];
 
     FsFile target_file;
 } g_initiator_state;
@@ -103,6 +111,12 @@ void scsiInitiatorInit()
         logmsg("InitiatorID set to illegal value in, ", CONFIGFILE, ", defaulting to 7");
         g_initiator_state.initiator_id = 7;
     }
+    else
+    {
+        logmsg("InitiatorID set to ID ", g_initiator_state.initiator_id);
+    }
+    g_initiator_state.max_retry_count = ini_getl("SCSI", "InitiatorMaxRetry", 5, CONFIGFILE);
+
     // treat initiator id as already imaged drive so it gets skipped
     g_initiator_state.drives_imaged = 1 << g_initiator_state.initiator_id;
 
@@ -114,7 +128,12 @@ void scsiInitiatorInit()
     g_initiator_state.retrycount = 0;
     g_initiator_state.failposition = 0;
     g_initiator_state.max_sector_per_transfer = 512;
-
+    g_initiator_state.ansi_version = 0;
+    g_initiator_state.bad_sector_count = 0;
+    g_initiator_state.device_type = SCSI_DEVICE_TYPE_DIRECT_ACCESS;
+    g_initiator_state.removable = false;
+    g_initiator_state.eject_when_done = false;
+    memset(g_initiator_state.removable_count, 0, sizeof(g_initiator_state.removable_count));
 
 }
 
@@ -150,6 +169,30 @@ void delay_with_poll(uint32_t ms)
     }
 }
 
+static int scsiTypeToIniType(int scsi_type, bool removable)
+{
+    int ini_type = -1;
+    switch (scsi_type)
+    {
+        case SCSI_DEVICE_TYPE_DIRECT_ACCESS:
+            ini_type = removable ? S2S_CFG_REMOVABLE : S2S_CFG_FIXED;
+            break;
+        case 1:
+            ini_type = -1; // S2S_CFG_SEQUENTIAL
+            break;
+        case SCSI_DEVICE_TYPE_CD:
+            ini_type = S2S_CFG_OPTICAL;
+            break;
+        case SCSI_DEVICE_TYPE_MO:
+            ini_type = S2S_CFG_MO;
+            break;
+        default:
+            ini_type = -1;
+            break;
+    }
+    return ini_type;
+}
+
 // High level logic of the initiator mode
 void scsiInitiatorMainLoop()
 {
@@ -166,14 +209,14 @@ void scsiInitiatorMainLoop()
         g_initiator_state.sectors_done = 0;
         g_initiator_state.retrycount = 0;
         g_initiator_state.max_sector_per_transfer = 512;
+        g_initiator_state.bad_sector_count = 0;
+        g_initiator_state.eject_when_done = false;
 
         if (!(g_initiator_state.drives_imaged & (1 << g_initiator_state.target_id)))
         {
             delay_with_poll(1000);
 
-            uint8_t inquiry_data[36];
-	    char vendor[9], product[17], revision[5];
-	    int type;
+            uint8_t inquiry_data[36] = {0};
 
             LED_ON();
             bool startstopok =
@@ -188,14 +231,6 @@ void scsiInitiatorMainLoop()
             bool inquiryok = startstopok &&
                 scsiInquiry(g_initiator_state.target_id, inquiry_data);
 
-	    memcpy(vendor, &inquiry_data[8], 8);
-	    vendor[8]=0;
-	    memcpy(product, &inquiry_data[16], 16);
-	    product[16]=0;
-	    memcpy(revision, &inquiry_data[32], 4);
-	    revision[4]=0;
-	    type=inquiry_data[0]&0x1f;
-
             LED_OFF();
 
             uint64_t total_bytes = 0;
@@ -205,12 +240,6 @@ void scsiInitiatorMainLoop()
                     " capacity ", (int)g_initiator_state.sectorcount,
                     " sectors x ", (int)g_initiator_state.sectorsize, " bytes");
 
-		logmsg("[SCSI", g_initiator_state.target_id,"]");
-		logmsg("  Vendor = \"", vendor,"\"");
-		logmsg("  Product = \"", product,"\"");
-		logmsg("  Version = \"", revision,"\"");
-		logmsg("  Type = ", type);
-
                 g_initiator_state.sectorcount_all = g_initiator_state.sectorcount;
 
                 total_bytes = (uint64_t)g_initiator_state.sectorcount * g_initiator_state.sectorsize;
@@ -235,29 +264,88 @@ void scsiInitiatorMainLoop()
             }
             else
             {
+#ifndef ZULUSCSI_NETWORK
                 dbgmsg("Failed to connect to SCSI ID ", g_initiator_state.target_id);
+#endif
                 g_initiator_state.sectorsize = 0;
                 g_initiator_state.sectorcount = g_initiator_state.sectorcount_all = 0;
             }
 
             char filename_base[12];
             strncpy(filename_base, "HD00_imaged", sizeof(filename_base));
-            const char *filename_extention = ".hda";
+            const char *filename_extension = ".hda";
+
             if (inquiryok)
             {
-                if ((inquiry_data[0] & 0x1F) == 5)
+                char vendor[9], product[17], revision[5];
+                g_initiator_state.device_type=inquiry_data[0] & 0x1f;
+                g_initiator_state.ansi_version = inquiry_data[2] & 0x7;
+                g_initiator_state.removable = !!(inquiry_data[1] & 0x80);
+                g_initiator_state.eject_when_done = g_initiator_state.removable;
+                memcpy(vendor, &inquiry_data[8], 8);
+                vendor[8]=0;
+                memcpy(product, &inquiry_data[16], 16);
+                product[16]=0;
+                memcpy(revision, &inquiry_data[32], 4);
+                revision[4]=0;
+
+                if(g_initiator_state.ansi_version < 0x02)
+                {
+                    // this is a SCSI-1 drive, use READ6 and 256 bytes to be safe.
+                    g_initiator_state.max_sector_per_transfer = 256;
+                }
+                int ini_type = scsiTypeToIniType(g_initiator_state.device_type, g_initiator_state.removable);
+                logmsg("SCSI Version ", (int) g_initiator_state.ansi_version);
+                logmsg("[SCSI", g_initiator_state.target_id,"]");
+                logmsg("  Vendor = \"", vendor,"\"");
+                logmsg("  Product = \"", product,"\"");
+                logmsg("  Version = \"", revision,"\"");
+                if (ini_type == -1)
+                    logmsg("Type = Not Supported, trying direct access");
+                else
+                    logmsg("  Type = ", ini_type);
+
+                if (g_initiator_state.device_type == SCSI_DEVICE_TYPE_CD)
                 {
                     strncpy(filename_base, "CD00_imaged", sizeof(filename_base));
-                    filename_extention = ".iso";
+                    filename_extension = ".iso";
+                }
+                else if (g_initiator_state.device_type == SCSI_DEVICE_TYPE_MO)
+                {
+                    strncpy(filename_base, "MO00_imaged", sizeof(filename_base));
+                    filename_extension = ".img";
+                }
+                else if (g_initiator_state.device_type != SCSI_DEVICE_TYPE_DIRECT_ACCESS)
+                {
+                    logmsg("Unhandled scsi device type: ", g_initiator_state.device_type, ". Handling it as Direct Access Device.");
+                    g_initiator_state.device_type = SCSI_DEVICE_TYPE_DIRECT_ACCESS;
+                }
+
+                if (g_initiator_state.device_type == SCSI_DEVICE_TYPE_DIRECT_ACCESS && g_initiator_state.removable)
+                {
+                    strncpy(filename_base, "RM00_imaged", sizeof(filename_base));
+                    filename_extension = ".img";
                 }
             }
 
+            if (g_initiator_state.eject_when_done && g_initiator_state.removable_count[g_initiator_state.target_id] == 0)
+            {
+                g_initiator_state.removable_count[g_initiator_state.target_id] = 1;
+            }
+
             if (g_initiator_state.sectorcount > 0)
             {
                 char filename[32] = {0};
                 filename_base[2] += g_initiator_state.target_id;
-                strncpy(filename, filename_base, sizeof(filename) - 1);
-                strncat(filename, filename_extention, sizeof(filename) - 1);
+                if (g_initiator_state.eject_when_done)
+                {
+                    auto removable_count = g_initiator_state.removable_count[g_initiator_state.target_id];
+                    snprintf(filename, sizeof(filename), "%s(%lu)%s",filename_base, removable_count, filename_extension);
+                }
+                else
+                {
+                    snprintf(filename, sizeof(filename), "%s%s", filename_base, filename_extension);
+                }
                 static int handling = -1;
                 if (handling == -1)
                 {
@@ -291,12 +379,16 @@ void scsiInitiatorMainLoop()
                             return;
                         }
                         char filename_copy[6] = {0};
-                        snprintf(filename_copy, sizeof(filename_copy), "_%03lu", i);
-
-                        strncpy(filename, filename_base, sizeof(filename) - 1);
-                        strncat(filename, filename_copy, sizeof(filename) - 1);
-                        strncat(filename, filename_extention, sizeof(filename) - 1);
-
+                        if (g_initiator_state.eject_when_done)
+                        {
+                            auto removable_count = g_initiator_state.removable_count[g_initiator_state.target_id];
+                            snprintf(filename, sizeof(filename), "%s(%lu)-%03lu%s", filename_base, removable_count, i, filename_extension);
+                        }
+                        else
+                        {
+                            snprintf(filename, sizeof(filename), "%s-%03lu%s", filename_base, i, filename_extension);
+                        }
+                        snprintf(filename_copy, sizeof(filename_copy), "-%03lu", i);
                         if (SD.exists(filename))
                             continue;
                         break;
@@ -333,7 +425,7 @@ void scsiInitiatorMainLoop()
                     return;
                 }
 
-                g_initiator_state.target_file = SD.open(filename, O_RDWR | O_CREAT | O_TRUNC);
+                g_initiator_state.target_file = SD.open(filename, O_WRONLY | O_CREAT | O_TRUNC);
                 if (!g_initiator_state.target_file.isOpen())
                 {
                     logmsg("Failed to open file for writing: ", filename);
@@ -368,7 +460,17 @@ void scsiInitiatorMainLoop()
                 logmsg("Please reformat the SD card with exFAT format to image this drive fully");
             }
 
-            g_initiator_state.drives_imaged |= (1 << g_initiator_state.target_id);
+            if(g_initiator_state.bad_sector_count != 0)
+            {
+                logmsg("NOTE: There were ",  (int) g_initiator_state.bad_sector_count, " bad sectors that could not be read off this drive.");
+            }
+
+            if (!g_initiator_state.eject_when_done)
+            {
+                logmsg("Marking SCSI ID, ", g_initiator_state.target_id, ", as imaged, wont ask it again.");
+                g_initiator_state.drives_imaged |= (1 << g_initiator_state.target_id);
+            }
+
             g_initiator_state.imaging = false;
             g_initiator_state.target_file.close();
             return;
@@ -394,11 +496,12 @@ void scsiInitiatorMainLoop()
         {
             logmsg("Failed to transfer ", numtoread, " sectors starting at ", (int)g_initiator_state.sectors_done);
 
-            if (g_initiator_state.retrycount < 5)
+            if (g_initiator_state.retrycount < g_initiator_state.max_retry_count)
             {
-                logmsg("Retrying.. ", g_initiator_state.retrycount, "/5");
+                logmsg("Retrying.. ", g_initiator_state.retrycount + 1, "/", (int) g_initiator_state.max_retry_count);
                 delay_with_poll(200);
-                scsiHostPhyReset();
+                // This reset causes some drives to hang and seems to have no effect if left off.
+                // scsiHostPhyReset();
                 delay_with_poll(200);
 
                 g_initiator_state.retrycount++;
@@ -415,6 +518,7 @@ void scsiInitiatorMainLoop()
                 logmsg("Retry limit exceeded, skipping one sector");
                 g_initiator_state.retrycount = 0;
                 g_initiator_state.sectors_done++;
+                g_initiator_state.bad_sector_count++;
                 g_initiator_state.target_file.seek((uint64_t)g_initiator_state.sectors_done * g_initiator_state.sectorsize);
             }
         }
@@ -427,7 +531,8 @@ void scsiInitiatorMainLoop()
             int speed_kbps = numtoread * g_initiator_state.sectorsize / (millis() - time_start);
             logmsg("SCSI read succeeded, sectors done: ",
                   (int)g_initiator_state.sectors_done, " / ", (int)g_initiator_state.sectorcount,
-                  " speed ", speed_kbps, " kB/s");
+                  " speed ", speed_kbps, " kB/s - ", 
+                  (int)(100 * g_initiator_state.sectors_done / g_initiator_state.sectorcount), "%");
         }
     }
 }
@@ -445,7 +550,9 @@ int scsiInitiatorRunCommand(int target_id,
 
     if (!scsiHostPhySelect(target_id, g_initiator_state.initiator_id))
     {
+#ifndef ZULUSCSI_NETWORK
         dbgmsg("------ Target ", target_id, " did not respond");
+#endif
         scsiHostPhyRelease();
         return -1;
     }
@@ -509,7 +616,9 @@ int scsiInitiatorRunCommand(int target_id,
             uint8_t tmp = -1;
             scsiHostRead(&tmp, 1);
             status = tmp;
+#ifndef ZULUSCSI_NETWORK
             dbgmsg("------ STATUS: ", tmp);
+#endif
         }
     }
 
@@ -568,9 +677,9 @@ bool scsiRequestSense(int target_id, uint8_t *sense_key)
                                          response, sizeof(response),
                                          NULL, 0);
 
-    logmsg("RequestSense response: ", bytearray(response, 18));
+    dbgmsg("RequestSense response: ", bytearray(response, 18));
 
-    *sense_key = response[2];
+    *sense_key = response[2] % 0xF;
     return status == 0;
 }
 
@@ -585,6 +694,15 @@ bool scsiStartStopUnit(int target_id, bool start)
         command[4] |= 1; // Start
         command[1] = 0;  // Immediate
     }
+    else // stop
+    {
+        if(g_initiator_state.eject_when_done)
+        {
+            logmsg("Ejecting media on SCSI ID: ", target_id);
+            g_initiator_state.removable_count[g_initiator_state.target_id]++;
+            command[4] = 0b00000010; // eject(6), stop(7).
+        }
+    }
 
     int status = scsiInitiatorRunCommand(target_id,
                                          command, sizeof(command),
@@ -595,7 +713,7 @@ bool scsiStartStopUnit(int target_id, bool start)
     {
         uint8_t sense_key;
         scsiRequestSense(target_id, &sense_key);
-        logmsg("START STOP UNIT on target ", target_id, " failed, sense key ", sense_key);
+        dbgmsg("START STOP UNIT on target ", target_id, " failed, sense key ", sense_key);
     }
 
     return status == 0;
@@ -640,18 +758,18 @@ bool scsiTestUnitReady(int target_id)
             if (sense_key == 6)
             {
                 uint8_t inquiry[36];
-                logmsg("Target ", target_id, " reports UNIT_ATTENTION, running INQUIRY");
+                dbgmsg("Target ", target_id, " reports UNIT_ATTENTION, running INQUIRY");
                 scsiInquiry(target_id, inquiry);
             }
             else if (sense_key == 2)
             {
-                logmsg("Target ", target_id, " reports NOT_READY, running STARTSTOPUNIT");
+                dbgmsg("Target ", target_id, " reports NOT_READY, running STARTSTOPUNIT");
                 scsiStartStopUnit(target_id, true);
             }
         }
         else
         {
-            logmsg("Target ", target_id, " TEST UNIT READY response: ", status);
+            dbgmsg("Target ", target_id, " TEST UNIT READY response: ", status);
         }
     }
 
@@ -769,7 +887,7 @@ bool scsiInitiatorReadDataToFile(int target_id, uint32_t start_sector, uint32_t
 
     // Read6 command supports 21 bit LBA - max of 0x1FFFFF
     // ref: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf pg 134
-    if (start_sector < 0x1FFFFF && sectorcount <= 256)
+    if (g_initiator_state.ansi_version < 0x02 || (start_sector < 0x1FFFFF && sectorcount <= 256))
     {
         // Use READ6 command for compatibility with old SCSI1 drives
         uint8_t command[6] = {0x08,

+ 4 - 0
src/ZuluSCSI_initiator.h

@@ -26,6 +26,10 @@
 #include <stdint.h>
 #include <stdlib.h>
 
+#define SCSI_DEVICE_TYPE_CD 0x5
+#define SCSI_DEVICE_TYPE_MO 0x7
+#define SCSI_DEVICE_TYPE_DIRECT_ACCESS 0x0
+
 void scsiInitiatorInit();
 
 void scsiInitiatorMainLoop();

+ 4 - 2
src/ZuluSCSI_log.cpp

@@ -29,7 +29,7 @@
 
 const char *g_log_firmwareversion = ZULU_FW_VERSION " " __DATE__ " " __TIME__;
 
-bool g_log_debug = true;
+bool g_log_debug = false;
 
 // This memory buffer can be read by debugger and is also saved to zululog.txt
 #define LOGBUFMASK (LOGBUFSIZE - 1)
@@ -199,6 +199,8 @@ const char *log_get_buffer(uint32_t *startpos, uint32_t *available)
     return result;
 }
 
+
+#ifdef NETWORK_DEBUG_LOGGING
 // TODO write directly global log buffer to save some memory
 static char shared_log_buf[1500 * 3];
 
@@ -267,5 +269,5 @@ void dbgmsg_buf(const unsigned char *buf, unsigned long size)
         return;
     log_hex_buf(buf, size, true);
 }
-
+#endif // NETWORK_DEBUG_LOGGING
 

+ 2 - 0
src/ZuluSCSI_log.h

@@ -102,6 +102,7 @@ inline void dbgmsg(Params... params)
     }
 }
 
+#ifdef NETWORK_DEBUG_LOGGING
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -120,4 +121,5 @@ void dbgmsg_f(const char *format, ...);
 
 #ifdef __cplusplus
 }
+#endif
 #endif

+ 17 - 13
src/ZuluSCSI_log_trace.cpp

@@ -100,6 +100,10 @@ static const char *getCommandName(uint8_t cmd)
         case 0xA8: return "Read12";
         case 0xC0: return "OMTI-5204 DefineFlexibleDiskFormat";
         case 0xC2: return "OMTI-5204 AssignDiskParameters";
+        case 0xD8: return "Vendor 0xD8 Command";
+        case 0xD9: return "Vendor 0xD9 Command";
+        case 0xE0: return "Xebec RAM Diagnostic";
+        case 0xE4: return "Xebec Drive Diagnostic";              
         default:   return "Unknown";
     }
 }
@@ -175,9 +179,9 @@ static void printNewPhase(int phase, bool initiator = false)
             if (!initiator && scsiDev.target->syncOffset > 0)
                 dbgmsg("---- DATA_OUT, syncOffset ", (int)scsiDev.target->syncOffset,
                                     " syncPeriod ", (int)scsiDev.target->syncPeriod);
-	    // log Xebec vendor commands data
-	    else if (scsiDev.cdb[0] == 0x0C || scsiDev.cdb[0] == 0x0F)
-		    g_LogData = true;
+            // log Xebec vendor commands data
+            else if (scsiDev.cdb[0] == 0x0C || scsiDev.cdb[0] == 0x0F)
+                g_LogData = true;
             else
                 dbgmsg("---- DATA_OUT");
             break;
@@ -210,17 +214,17 @@ void scsiLogPhaseChange(int new_phase)
         {
             dbgmsg("---- Total IN: ", g_InByteCount, " OUT: ", g_OutByteCount, " CHECKSUM: ", (int)g_DataChecksum);
         }
-	// log Xebec vendor command
+        // log Xebec vendor command
         if (old_phase == DATA_OUT && scsiDev.cdb[0] == 0x0C && g_OutByteCount == 8)
-	{
-		int cylinders = ((uint16_t)scsiDev.data[0] << 8) + scsiDev.data[1];
-		int heads = scsiDev.data[2];
-		int reducedWrite = ((uint16_t)scsiDev.data[3] << 8) + scsiDev.data[4];
-		int writePrecomp = ((uint16_t)scsiDev.data[5] << 8) + scsiDev.data[6];
-		int eccBurst = scsiDev.data[7];
-		dbgmsg("---- Xebec Initialize Drive Characteristics: cylinders=", cylinders, " heads=", heads,
-				" reducedWrite=", reducedWrite, " writePrecomp=", writePrecomp, " eccBurst=", eccBurst);
-	}
+        {
+            int cylinders = ((uint16_t)scsiDev.data[0] << 8) + scsiDev.data[1];
+            int heads = scsiDev.data[2];
+            int reducedWrite = ((uint16_t)scsiDev.data[3] << 8) + scsiDev.data[4];
+            int writePrecomp = ((uint16_t)scsiDev.data[5] << 8) + scsiDev.data[6];
+            int eccBurst = scsiDev.data[7];
+            dbgmsg("---- Xebec Initialize Drive Characteristics: cylinders=", cylinders, " heads=", heads,
+                    " reducedWrite=", reducedWrite, " writePrecomp=", writePrecomp, " eccBurst=", eccBurst);
+        }
         g_InByteCount = g_OutByteCount = 0;
         g_DataChecksum = 0;
 

+ 2 - 2
src/ZuluSCSI_mode.cpp

@@ -53,8 +53,8 @@ static const uint8_t CDROMAudioControlParametersPage[] =
 0x04, // 'Immed' bit set, 'SOTC' bit not set
 0x00, // reserved
 0x00, // reserved
-0x80, // 1 LBAs/sec multip
-0x00, 0x4B, // 75 LBAs/sec
+0x00, // reserved was //  0x80, // 1 LBAs/sec multip
+0x00, 0x00, // obsolete was // 75 LBAs/sec 
 0x01, 0xFF, // output port 0 active, max volume
 0x02, 0xFF, // output port 1 active, max volume
 0x00, 0x00, // output port 2 inactive

+ 103 - 0
src/ZuluSCSI_msc.cpp

@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2023,2024 zigzagjoe
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * 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/>.
+**/
+
+#ifdef PLATFORM_MASS_STORAGE
+
+#include <SdFat.h>
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_msc.h"
+
+// external global SD variable
+extern SdFs SD;
+
+// public globals
+volatile MSC_LEDState MSC_LEDMode;
+
+// card reader operation loop
+// assumption that SD card was enumerated and is working
+void zuluscsi_msc_loop() {
+
+  // turn LED on to indicate entering card reader mode.
+  LED_ON();
+  
+  logmsg("Entering USB Mass storage mode. Eject the USB disk to exit.");
+
+  platform_enter_msc();
+  
+  uint32_t sd_card_check_time = 0;
+  uint16_t syncCounter = 0;
+        
+  // steady state operation / indication loop
+  // led remains steady on
+  while(platform_run_msc()) {
+    platform_reset_watchdog(); // also sends log to USB serial
+
+    if ((uint32_t)(millis() - sd_card_check_time) > 5000) {
+      sd_card_check_time = millis();
+      uint32_t ocr;
+      if (!SD.card()->readOCR(&ocr)) {
+        if (!SD.card()->readOCR(&ocr)) {
+          logmsg("SD card presence check failed! Card unexpectedly removed?");
+          break;
+        }
+      }
+    }
+ 
+    // blink LED according to access type
+    switch (MSC_LEDMode) {
+      case LED_BLINK_FAST:
+        LED_OFF();
+        delay(30);
+        break;
+      case LED_BLINK_SLOW:
+        delay(30);
+        LED_OFF();
+        delay(100);
+        syncCounter = 1;
+        break;
+      default:
+        // sync sd card ~ 500ms after writes stop
+        if (syncCounter && (++syncCounter > 8)) {
+          syncCounter = 0;
+          SD.card()->syncDevice();
+        }
+    }
+
+    // LED always on in card reader mode
+    MSC_LEDMode = LED_SOLIDON;
+	  LED_ON(); 
+    delay(30);
+  }
+
+  // turn the LED off to indicate exiting MSC
+  LED_OFF();
+  
+  logmsg("USB Mass Storage mode exited: resuming standard functionality.");
+  platform_exit_msc();
+  
+  SD.card()->syncDevice();
+
+  // leave the LED off for a moment, before any blinks from the main firmware occur
+  delay(1000);
+}
+
+#endif

+ 37 - 0
src/ZuluSCSI_msc.h

@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2023-2024 zigzagjoe
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * 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/>.
+**/
+
+#ifdef PLATFORM_MASS_STORAGE
+#pragma once
+
+// include platform-specific defines
+#include "ZuluSCSI_platform_msc.h"
+
+// wait up to this long during init sequence for USB enumeration to enter card reader
+#define CR_ENUM_TIMEOUT 1000
+
+enum  MSC_LEDState { LED_SOLIDON = 0, LED_BLINK_FAST, LED_BLINK_SLOW };
+extern volatile MSC_LEDState MSC_LEDMode;
+
+// run cardreader main loop (blocking)
+void zuluscsi_msc_loop();
+
+#endif

+ 10 - 1
src/ZuluSCSI_settings.cpp

@@ -161,6 +161,8 @@ void ZuluSCSISettings::setDefaultDriveInfo(uint8_t scsiId, const char *presetNam
     if (m_devPreset[scsiId] == DEV_PRESET_NONE)
     {
         cfgDev.deviceType = type;
+        cfgDev.deviceType = ini_getl(section, "Type", cfgDev.deviceType, CONFIGFILE);
+        
         if (cfgSys.quirks == S2S_CFG_QUIRKS_APPLE)
         {
             // Use default drive IDs that are recognized by Apple machines
@@ -207,7 +209,6 @@ void ZuluSCSISettings::setDefaultDriveInfo(uint8_t scsiId, const char *presetNam
 // Read device settings
 static void readIniSCSIDeviceSetting(scsi_device_settings_t &cfg, const char *section)
 {
-    cfg.deviceType = ini_getl(section, "Type", cfg.deviceType, CONFIGFILE);
     cfg.deviceTypeModifier = ini_getl(section, "TypeModifier", cfg.deviceTypeModifier, CONFIGFILE);
     cfg.sectorsPerTrack = ini_getl(section, "SectorsPerTrack", cfg.sectorsPerTrack, CONFIGFILE);
     cfg.headsPerCylinder = ini_getl(section, "HeadsPerCylinder", cfg.headsPerCylinder, CONFIGFILE);
@@ -225,6 +226,8 @@ static void readIniSCSIDeviceSetting(scsi_device_settings_t &cfg, const char *se
     cfg.sectorSDBegin = ini_getl(section, "SectorSDBegin", cfg.sectorSDBegin, CONFIGFILE);
     cfg.sectorSDEnd = ini_getl(section, "SectorSDEnd", cfg.sectorSDEnd, CONFIGFILE);
 
+    cfg.vendorExtensions = ini_getl(section, "VendorExtensions", cfg.vendorExtensions, CONFIGFILE);
+
     char tmp[32];
     ini_gets(section, "Vendor", "", tmp, sizeof(tmp), CONFIGFILE);
     if (tmp[0])
@@ -286,6 +289,7 @@ scsi_system_settings_t *ZuluSCSISettings::initSystem(const char *presetName)
     cfgSys.enableParity = true;
     cfgSys.useFATAllocSize = false;
     cfgSys.enableCDAudio = false;
+    cfgSys.enableUSBMassStorage = false;
     
     // setting set for all or specific devices
     cfgDev.deviceType = S2S_CFG_NOT_SET;
@@ -305,6 +309,8 @@ scsi_system_settings_t *ZuluSCSISettings::initSystem(const char *presetName)
     cfgDev.sectorSDBegin = 0;
     cfgDev.sectorSDEnd = 0;
 
+    cfgDev.vendorExtensions = 0;
+
     // System-specific defaults
 
     if (strequals(systemPresetName[SYS_PRESET_NONE], presetName))
@@ -375,6 +381,9 @@ scsi_system_settings_t *ZuluSCSISettings::initSystem(const char *presetName)
     cfgSys.enableParity =  ini_getbool("SCSI", "EnableParity", cfgSys.enableParity, CONFIGFILE);
     cfgSys.useFATAllocSize = ini_getbool("SCSI", "UseFATAllocSize", cfgSys.useFATAllocSize, CONFIGFILE);
     cfgSys.enableCDAudio = ini_getbool("SCSI", "EnableCDAudio", cfgSys.enableCDAudio, CONFIGFILE);
+
+    cfgSys.enableUSBMassStorage = ini_getbool("SCSI", "EnableUSBMassStorage", cfgSys.enableUSBMassStorage, CONFIGFILE);
+    
     return &cfgSys;
 }
 

+ 3 - 0
src/ZuluSCSI_settings.h

@@ -65,6 +65,7 @@ typedef struct __attribute__((__packed__)) scsi_system_settings_t
     bool enableParity;
     bool useFATAllocSize;
     bool enableCDAudio;
+    bool enableUSBMassStorage;
 } scsi_system_settings_t;
 
 // This struct should only have new setting added to the end
@@ -93,6 +94,8 @@ typedef struct __attribute__((__packed__)) scsi_device_settings_t
 
     uint32_t sectorSDBegin;
     uint32_t sectorSDEnd;
+
+    uint32_t vendorExtensions;
 } scsi_device_settings_t;
 
 

+ 4 - 1
zuluscsi.ini

@@ -17,7 +17,7 @@
 # The PhyMode parameter has no effect on ZuluSCSI RP2040-based platforms, as there is only one PHY mode.
 
 # Settings that can be needed for compatibility with some hosts
-#Quirks = 0   # 0: Standard, 1: Apple, 2: OMTI, 4: Xebec, 8: VMS, 16: X68000
+#Quirks = 0   # 0: Standard, 1: Apple, 2: OMTI, 4: Xebec, 8: VMS, 16: X68000, 32: EWSD
 #EnableUnitAttention = 0 # Post UNIT_ATTENTION status on power-on or SD card hotplug
 #EnableSCSI2 = 1 # Enable faster speeds of SCSI2
 #EnableSelLatch = 0 # For Philips P2000C and other devices that release SEL signal before BSY
@@ -33,6 +33,7 @@
 
 #Initiator settings
 #InitiatorID = 7 # SCSI ID, 0-7, when the device is in initiator mode, default is 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
@@ -47,6 +48,8 @@
 #Version = "1.0"
 #Serial = "0123456789ABCDEF"
 #Type = 0     # 0: Fixed, 1: Removable, 2: Optical, 3: Floppy, 4: Mag-optical, 5: Tape, 6: Network
+#VendorExtensions = 0 # Bit flags for specific extensions per device type
+#  CDROM - 1: Plextor's d8h vendor command
 #TypeModifier = 0  # Affects only INQUIRY response
 #SectorsPerTrack = 63
 #HeadsPerCylinder = 255