Переглянути джерело

Added the DaynaPORT code and it works

I took the great code from Joshua Stein on his branch on
the BlueSCSIv2 codebase and massaged it to work with the
ZuluSCSI Pico. He's credited on all the files he touched.
Morio 2 роки тому
батько
коміт
6ed0e955d4

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

@@ -14,6 +14,10 @@
 //
 //	You should have received a copy of the GNU General Public License
 //	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+//
+// This work incorporates work from the following
+//  Copyright (c) 2023 joshua stein <jcs@jcs.org>
+
 #ifndef scsi2sd_h
 #define scsi2sd_h
 
@@ -73,7 +77,8 @@ typedef enum
 	S2S_CFG_OPTICAL,
 	S2S_CFG_FLOPPY_14MB,
 	S2S_CFG_MO,
-	S2S_CFG_SEQUENTIAL
+	S2S_CFG_SEQUENTIAL,
+	S2S_CFG_NETWORK,
 
 } S2S_CFG_TYPE;
 
@@ -139,7 +144,11 @@ typedef struct __attribute__((packed))
 
 	uint8_t scsiSpeed;
 
-	uint8_t reserved[119]; // Pad out to 128 bytes
+	char wifiMACAddress[6];
+	char wifiSSID[32];
+	char wifiPassword[63];
+
+	uint8_t reserved[18]; // Pad out to 128 bytes
 } S2S_BoardCfg;
 
 typedef enum

+ 5 - 0
lib/SCSI2SD/src/firmware/config.h

@@ -14,10 +14,15 @@
 //
 //	You should have received a copy of the GNU General Public License
 //	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+//
+// This work incorporates work from the following
+//  Copyright (c) 2023 joshua stein <jcs@jcs.org>
+
 #ifndef S2S_Config_H
 #define S2S_Config_H
 
 #include "scsi2sd.h"
+#include "log.h"
 
 void s2s_configInit(S2S_BoardCfg* config);
 void s2s_debugInit(void);

+ 13 - 0
lib/SCSI2SD/src/firmware/inquiry.c

@@ -15,6 +15,9 @@
 //
 //	You should have received a copy of the GNU General Public License
 //	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+//
+// This work incorporates work from the following
+//  Copyright (c) 2023 joshua stein <jcs@jcs.org>
 
 #include "scsi.h"
 #include "config.h"
@@ -201,6 +204,11 @@ void s2s_scsiInquiry()
 		case S2S_CFG_REMOVEABLE:
 			scsiDev.data[1] |= 0x80; // Removable bit.
 			break;
+
+		case S2S_CFG_NETWORK:
+			scsiDev.data[2] = 0x01;  // Page code.
+			break;
+		
 		default:
 			// Accept defaults for a fixed disk.
 			break;
@@ -264,6 +272,11 @@ uint8_t getDeviceTypeQualifier()
 		return 0;
 		break;
 
+	case S2S_CFG_NETWORK:
+		// processor device
+		return 0x03;
+		break;
+
 	default:
 		// Accept defaults for a fixed disk.
 		return 0;

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

@@ -0,0 +1,27 @@
+//	Copyright (C) 2023 joshua stein <jcs@jcs.org>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD 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.
+//
+//	SCSI2SD 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void logmsg_buf(const unsigned char *buf, unsigned long size);
+extern void logmsg_f(const char *format, ...);
+
+#ifdef __cplusplus
+}
+#endif

+ 487 - 0
lib/SCSI2SD/src/firmware/network.c

@@ -0,0 +1,487 @@
+/*
+ * Copyright (c) 2023 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "scsi.h"
+#include "scsi2sd_time.h"
+#include "scsiPhy.h"
+#include "config.h"
+#include "network.h"
+
+extern bool g_log_debug;
+
+static bool scsiNetworkEnabled = false;
+
+static uint8_t scsiNetworkPacketsInbound[NETWORK_PACKET_QUEUE_SIZE][NETWORK_PACKET_MAX_SIZE];
+static uint16_t scsiNetworkPacketInboundSizes[NETWORK_PACKET_QUEUE_SIZE];
+static uint8_t scsiNetworkPacketInboundWriteIndex = 0;
+static uint8_t scsiNetworkPacketInboundReadIndex = 0;
+
+static uint8_t scsiNetworkPacketsOutbound[NETWORK_PACKET_QUEUE_SIZE][NETWORK_PACKET_MAX_SIZE];
+static uint16_t scsiNetworkPacketOutboundSizes[NETWORK_PACKET_QUEUE_SIZE];
+static uint8_t scsiNetworkPacketOutboundWriteIndex = 0;
+static uint8_t scsiNetworkPacketOutboundReadIndex = 0;
+
+struct __attribute__((packed)) wifi_network_entry wifi_network_list[WIFI_NETWORK_LIST_ENTRY_COUNT] = { 0 };
+
+static const uint32_t crc32_tab[] = {
+	0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+	0xe963a535, 0x9e6495a3,	0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+	0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+	0xf3b97148, 0x84be41de,	0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+	0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,	0x14015c4f, 0x63066cd9,
+	0xfa0f3d63, 0x8d080df5,	0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+	0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,	0x35b5a8fa, 0x42b2986c,
+	0xdbbbc9d6, 0xacbcf940,	0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+	0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+	0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+	0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,	0x76dc4190, 0x01db7106,
+	0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+	0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+	0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+	0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+	0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+	0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+	0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+	0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+	0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+	0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+	0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+	0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+	0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+	0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+	0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+	0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+	0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+	0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+	0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+	0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+	0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+	0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+	0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+	0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+	0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+	0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+	0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+	0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+	0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+	0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+	0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+	0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+uint32_t crc32(const void *buf, size_t size)
+{
+	const uint8_t *p = buf;
+	uint32_t crc;
+
+	crc = ~0U;
+	while (size--)
+		crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
+	return crc ^ ~0U;
+}
+
+int scsiNetworkCommand()
+{
+	int handled = 1;
+	int off = 0;
+	int parityError = 0;
+	long psize;
+	uint32_t size = scsiDev.cdb[4] + (scsiDev.cdb[3] << 8);
+	uint8_t command = scsiDev.cdb[0];
+	uint8_t cont = (scsiDev.cdb[5] == 0x80);
+
+	if (g_log_debug)
+	{
+		logmsg_f("in scsiNetworkCommand with command 0x%02x (size %d)", command, size);
+	}
+
+	switch (command) {
+	case 0x08:
+		// read(6)
+		if (unlikely(size == 1))
+		{
+			scsiDev.status = CHECK_CONDITION;
+			scsiDev.phase = STATUS;
+			break;
+		}
+
+		if (scsiNetworkPacketInboundReadIndex == scsiNetworkPacketInboundWriteIndex)
+		{
+			// nothing available
+			memset(scsiDev.data, 0, 6);
+			scsiDev.dataLen = 6;
+		}
+		else
+		{
+			psize = scsiNetworkPacketInboundSizes[scsiNetworkPacketInboundReadIndex];
+
+			// pad smaller packets
+			if (psize < 64)
+			{
+				psize = 64;
+			}
+			else if (psize + 6 > size)
+			{
+				logmsg_f("%s: packet size too big (%d)", __func__, psize);
+				psize = size - 6;
+			}
+
+			if (g_log_debug)
+			{
+				logmsg_f("%s: sending packet[%d] to host of size %zu + 6", __func__, scsiNetworkPacketInboundReadIndex, psize);
+			}
+
+			scsiDev.dataLen = psize + 6; // 2-byte length + 4-byte flag + packet
+			memcpy(scsiDev.data + 6, scsiNetworkPacketsInbound[scsiNetworkPacketInboundReadIndex], psize);
+			scsiDev.data[0] = (psize >> 8) & 0xff;
+			scsiDev.data[1] = psize & 0xff;
+
+			if (scsiNetworkPacketInboundReadIndex == NETWORK_PACKET_QUEUE_SIZE - 1)
+				scsiNetworkPacketInboundReadIndex = 0;
+			else
+				scsiNetworkPacketInboundReadIndex++;
+
+			// flags
+			scsiDev.data[2] = 0;
+			scsiDev.data[3] = 0;
+			scsiDev.data[4] = 0;
+			// more data to read?
+			scsiDev.data[5] = (scsiNetworkPacketInboundReadIndex == scsiNetworkPacketInboundWriteIndex ? 0 : 0x10);
+
+			if (g_log_debug)
+			{
+				logmsg_buf(scsiDev.data, scsiDev.dataLen);
+			}
+		}
+
+		// 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;
+		scsiDev.phase = STATUS;
+		break;
+
+	case 0x09:
+		// read mac address and stats
+		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));
+
+		// three 32-bit counters expected to follow, just return zero for all
+		scsiDev.dataLen = 18;
+		scsiDev.phase = DATA_IN;
+		break;
+
+	case 0x0a:
+		// write(6)
+		off = 0;
+		if (cont)
+		{
+			size += 8;
+		}
+
+		memset(scsiDev.data, 0, sizeof(scsiDev.data));
+
+		scsiEnterPhase(DATA_OUT);
+		parityError = 0;
+		scsiRead(scsiDev.data, size, &parityError);
+
+		if (g_log_debug)
+		{
+			logmsg_f("%s: read packet from host of size %zu - %d (parity error %d)", __func__, size, (cont ? 4 : 0), parityError);
+			logmsg_buf(scsiDev.data, size);
+		}
+
+		if (cont)
+		{
+			size = (scsiDev.data[0] << 8) | scsiDev.data[1];
+			off = 4;
+		}
+
+		memcpy(&scsiNetworkPacketsOutbound[scsiNetworkPacketOutboundWriteIndex], scsiDev.data + off, size);
+		scsiNetworkPacketOutboundSizes[scsiNetworkPacketOutboundWriteIndex] = size;
+
+		if (scsiNetworkPacketOutboundWriteIndex == NETWORK_PACKET_QUEUE_SIZE - 1)
+			scsiNetworkPacketOutboundWriteIndex = 0;
+		else
+			scsiNetworkPacketOutboundWriteIndex++;
+
+		scsiDev.status = GOOD;
+		scsiDev.phase = STATUS;
+		break;
+
+	case 0x0c:
+		// set interface mode (ignored)
+		//broadcasts = (scsiDev.cdb[4] == 0x04);
+		break;
+
+	case 0x0d:
+		// add multicast addr to network filter
+		memset(scsiDev.data, 0, sizeof(scsiDev.data));
+		scsiEnterPhase(DATA_OUT);
+		parityError = 0;
+		scsiRead(scsiDev.data, size, &parityError);
+
+		if (g_log_debug)
+		{
+			logmsg_f("%s: adding multicast address %02x:%02x:%02x:%02x:%02x:%02x", __func__,
+			  scsiDev.data[0], scsiDev.data[1], scsiDev.data[2], scsiDev.data[3], scsiDev.data[4], scsiDev.data[5]);
+		}
+
+		platform_network_add_multicast_address(scsiDev.data);
+
+		scsiDev.status = GOOD;
+		scsiDev.phase = STATUS;
+		break;
+
+	case 0x0e:
+		// toggle interface
+		if (scsiDev.cdb[5] & 0x80)
+		{
+			if (g_log_debug)
+			{
+				logmsg_f("%s: enable interface", __func__);
+			}
+			scsiNetworkEnabled = true;
+			scsiNetworkPacketInboundWriteIndex = 0;
+			scsiNetworkPacketInboundReadIndex = 0;
+			scsiNetworkPacketOutboundWriteIndex = 0;
+			scsiNetworkPacketOutboundReadIndex = 0;
+		}
+		else
+		{
+			if (g_log_debug)
+			{
+				logmsg_f("%s: disable interface", __func__);
+			}
+			scsiNetworkEnabled = false;
+		}
+		break;
+
+	case 0x1a:
+		// mode sense (ignored)
+		break;
+
+	case 0x40:
+		// set MAC (ignored)
+		scsiDev.dataLen = 6;
+		scsiDev.phase = DATA_OUT;
+		break;
+
+	case 0x80:
+		// set mode (ignored)
+		break;
+	
+	// custom wifi commands all using the same opcode, with a sub-command in cdb[2]
+	case SCSI_NETWORK_WIFI_CMD:
+		if (g_log_debug)
+		{
+			logmsg_f("in scsiNetworkCommand with wi-fi command 0x%02x (size %d)", scsiDev.cdb[2], size);
+		}
+
+		switch (scsiDev.cdb[2]) {
+		case SCSI_NETWORK_WIFI_CMD_SCAN:
+			// initiate wi-fi scan
+			scsiDev.dataLen = 1;
+			int ret = platform_network_wifi_start_scan();
+			scsiDev.data[0] = (ret < 0 ? ret : 1);
+			scsiDev.phase = DATA_IN;
+			break;
+
+		case SCSI_NETWORK_WIFI_CMD_COMPLETE:
+			// check for wi-fi scan completion
+			scsiDev.dataLen = 1;
+			scsiDev.data[0] = (platform_network_wifi_scan_finished() ? 1 : 0);
+			scsiDev.phase = DATA_IN;
+			break;
+
+		case SCSI_NETWORK_WIFI_CMD_SCAN_RESULTS:
+			// return wi-fi scan results
+			if (!platform_network_wifi_scan_finished())
+			{
+				scsiDev.status = CHECK_CONDITION;
+				scsiDev.phase = STATUS;
+				break;
+			}
+
+			int nets = 0;
+			for (int i = 0; i < WIFI_NETWORK_LIST_ENTRY_COUNT; i++)
+			{
+				if (wifi_network_list[i].ssid[0] == '\0')
+					break;
+				nets++;
+			}
+
+			if (nets) {
+				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");
+					size = sizeof(scsiDev.data) - 2;
+					size -= (size % (sizeof(struct wifi_network_entry)));
+				}
+				scsiDev.data[0] = (size >> 8) & 0xff;
+				scsiDev.data[1] = size & 0xff;
+				memcpy(scsiDev.data + 2, wifi_network_list, size);
+				scsiDev.dataLen = size + 2;
+			}
+			else
+			{
+				scsiDev.data[0] = 0;
+				scsiDev.data[1] = 0;
+				scsiDev.dataLen = 2;
+			}
+
+			scsiDev.phase = DATA_IN;
+			break;
+
+		case SCSI_NETWORK_WIFI_CMD_INFO: {
+			// return current wi-fi information
+			struct wifi_network_entry wifi_cur = { 0 };
+			int size = sizeof(wifi_cur);
+
+			char *ssid = platform_network_wifi_ssid();
+			if (ssid != NULL)
+				strlcpy(wifi_cur.ssid, ssid, sizeof(wifi_cur.ssid));
+
+			char *bssid = platform_network_wifi_bssid();
+			if (bssid != NULL)
+				memcpy(wifi_cur.bssid, bssid, sizeof(wifi_cur.bssid));
+
+			wifi_cur.rssi = platform_network_wifi_rssi();
+
+			wifi_cur.channel = platform_network_wifi_channel();
+
+			scsiDev.data[0] = (size >> 8) & 0xff;
+			scsiDev.data[1] = size & 0xff;
+			memcpy(scsiDev.data + 2, (char *)&wifi_cur, size);
+			scsiDev.dataLen = size + 2;
+			scsiDev.phase = DATA_IN;
+			break;
+		}
+
+		case SCSI_NETWORK_WIFI_CMD_JOIN: {
+			// set current wi-fi network
+			struct wifi_join_request req = { 0 };
+
+			if (size != sizeof(req)) {
+				logmsg_f("wifi_join_request bad size (%zu != %zu), ignoring", size, sizeof(req));
+				scsiDev.status = CHECK_CONDITION;
+				scsiDev.phase = STATUS;
+				break;
+			}
+
+			scsiEnterPhase(DATA_OUT);
+			parityError = 0;
+			scsiRead((uint8_t *)&req, sizeof(req), &parityError);
+
+			if (g_log_debug)
+			{
+				logmsg_f("%s: read join request from host:", __func__);
+				logmsg_buf(scsiDev.data, size);
+			}
+
+			platform_network_wifi_join(req.ssid, req.key);
+
+			scsiDev.status = GOOD;
+			scsiDev.phase = STATUS;
+			break;
+		}
+		}
+		break;
+
+	default:
+		handled = 0;
+	}
+
+	return handled;
+}
+
+int scsiNetworkEnqueue(const uint8_t *buf, size_t len)
+{
+	if (!scsiNetworkEnabled)
+		return 0;
+
+	if (len + 4 > sizeof(scsiNetworkPacketsInbound[0]))
+	{
+		if (g_log_debug)
+			logmsg_f("%s: dropping incoming network packet, too large (%zu > %zu)", __func__, len, sizeof(scsiNetworkPacketsInbound[0]));
+		return 0;
+	}
+
+	memcpy(scsiNetworkPacketsInbound[scsiNetworkPacketInboundWriteIndex], buf, len);
+	uint32_t crc = crc32(buf, len);
+	scsiNetworkPacketsInbound[scsiNetworkPacketInboundWriteIndex][len] = crc & 0xff;
+	scsiNetworkPacketsInbound[scsiNetworkPacketInboundWriteIndex][len + 1] = (crc >> 8) & 0xff;
+	scsiNetworkPacketsInbound[scsiNetworkPacketInboundWriteIndex][len + 2] = (crc >> 16) & 0xff;
+	scsiNetworkPacketsInbound[scsiNetworkPacketInboundWriteIndex][len + 3] = (crc >> 24) & 0xff;
+
+	scsiNetworkPacketInboundSizes[scsiNetworkPacketInboundWriteIndex] = len + 4;
+
+	if (scsiNetworkPacketInboundWriteIndex == NETWORK_PACKET_QUEUE_SIZE - 1)
+		scsiNetworkPacketInboundWriteIndex = 0;
+	else
+		scsiNetworkPacketInboundWriteIndex++;
+
+	if (scsiNetworkPacketInboundWriteIndex == scsiNetworkPacketInboundReadIndex)
+	{
+		if (g_log_debug)
+			logmsg_f("%s: dropping packets in ring, write index caught up to read index", __func__);
+	}
+	
+	return 1;
+}
+
+int scsiNetworkPurge(void)
+{
+	int sent = 0;
+
+	if (!scsiNetworkEnabled)
+		return 0;
+
+	while (scsiNetworkPacketOutboundReadIndex != scsiNetworkPacketOutboundWriteIndex)
+	{
+		platform_network_send(scsiNetworkPacketsOutbound[scsiNetworkPacketOutboundReadIndex], scsiNetworkPacketOutboundSizes[scsiNetworkPacketOutboundReadIndex]);
+
+		if (scsiNetworkPacketOutboundReadIndex == NETWORK_PACKET_QUEUE_SIZE - 1)
+			scsiNetworkPacketOutboundReadIndex = 0;
+		else
+			scsiNetworkPacketOutboundReadIndex++;
+		
+		sent++;
+	}
+
+	return sent;
+}

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

@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2023 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef NETWORK_H
+#define NETWORK_H
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SCSI_NETWORK_WIFI_CMD				0x1c	// cdb opcode
+#define SCSI_NETWORK_WIFI_CMD_SCAN			0x01	// cdb[2]
+#define SCSI_NETWORK_WIFI_CMD_COMPLETE		0x02
+#define SCSI_NETWORK_WIFI_CMD_SCAN_RESULTS	0x03
+#define SCSI_NETWORK_WIFI_CMD_INFO			0x04
+#define SCSI_NETWORK_WIFI_CMD_JOIN			0x05
+
+#define NETWORK_PACKET_QUEUE_SIZE   20		// must be <= 255
+#define NETWORK_PACKET_MAX_SIZE     1520
+
+struct __attribute__((packed)) wifi_network_entry {
+	char ssid[64];
+	char bssid[6];
+	int8_t rssi;
+	uint8_t channel;
+	uint8_t flags;
+	uint8_t _padding;
+#define WIFI_NETWORK_FLAGS_AUTH 0x1
+};
+#define WIFI_NETWORK_LIST_ENTRY_COUNT 10
+extern struct wifi_network_entry wifi_network_list[WIFI_NETWORK_LIST_ENTRY_COUNT];
+
+struct __attribute__((packed)) wifi_join_request {
+	char ssid[64];
+	char key[64];
+	uint8_t channel;
+	uint8_t _padding;
+};
+
+int scsiNetworkCommand(void);
+int scsiNetworkEnqueue(const uint8_t *buf, size_t len);
+int scsiNetworkPurge(void);
+
+extern int platform_network_send(uint8_t *buf, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

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

@@ -14,6 +14,9 @@
 //
 //	You should have received a copy of the GNU General Public License
 //	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+//
+// This work incorporates work from the following
+//  Copyright (c) 2023 joshua stein <jcs@jcs.org>
 
 #include "scsi.h"
 #include "scsiPhy.h"
@@ -27,6 +30,7 @@
 #include "bsp.h"
 #include "cdrom.h"
 //#include "debug.h"
+#include "network.h"
 #include "tape.h"
 #include "mo.h"
 #include "vendor.h"
@@ -518,7 +522,8 @@ static void process_Command()
 	// write commands. Will fall-through to generic disk handling.
 	else if (((cfg->deviceType == S2S_CFG_OPTICAL) && scsiCDRomCommand()) ||
 		((cfg->deviceType == S2S_CFG_SEQUENTIAL) && scsiTapeCommand()) ||
-		((cfg->deviceType == S2S_CFG_MO) && scsiMOCommand()))
+		((cfg->deviceType == S2S_CFG_MO) && scsiMOCommand()) ||
+		((cfg->deviceType == S2S_CFG_NETWORK && scsiNetworkCommand())))
 	{
 		// Already handled.
 	}

+ 70 - 25
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp

@@ -34,12 +34,21 @@
 #include <hardware/flash.h>
 #include <hardware/structs/xip_ctrl.h>
 #include <hardware/structs/usb.h>
-#include <platform/mbed_error.h>
-#include <multicore.h>
-#include <USB/PluggableUSBSerial.h>
-#include "audio.h"
 #include "scsi_accel_target.h"
 
+#ifdef __MBED__
+#  include <platform/mbed_error.h>
+#  include <multicore.h>
+#  include <USB/PluggableUSBSerial.h>
+#  include "audio.h"
+#else
+#  include <pico/multicore.h> 
+extern "C" {
+#  include <pico/cyw43_arch.h>
+} 
+#endif // __MBED__
+
+
 extern "C" {
 
 const char *g_platform_name = PLATFORM_NAME;
@@ -47,7 +56,9 @@ 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   */
@@ -102,7 +113,7 @@ static void reclock_for_audio() {
     // reset UART for the new clock speed
     uart_init(uart0, 1000000);
 }
-#endif
+#endif  // ENABLE_AUDIO_OUT
 
 void platform_init()
 {
@@ -130,17 +141,18 @@ void platform_init()
     bool termination = !gpio_get(DIP_TERM);
 #else
     delay(10);
-#endif
+#endif // HAS_DIP_SWITCHES
 
 #ifndef DISABLE_SWO
     /* Initialize logging to SWO pin (UART0) */
     gpio_conf(SWO_PIN,        GPIO_FUNC_UART,false,false, true,  false, true);
     uart_init(uart0, 1000000);
     g_uart_initialized = true;
-#endif
+#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);
 
@@ -159,13 +171,13 @@ void platform_init()
 #else
     g_log_debug = false;
     logmsg ("SCSI termination is handled by a hardware jumper");
-#endif
+#endif  // HAS_DIP_SWITCHES
 
 #ifdef ENABLE_AUDIO_OUTPUT
     logmsg("SP/DIF audio to expansion header enabled");
     logmsg("-- Overclocking to 135.428571MHz");
     reclock_for_audio();
-#endif
+#endif // ENABLE_AUDIO_OUTPUT
 
     // Get flash chip size
     uint8_t cmd_read_jedec_id[4] = {0x9f, 0, 0, 0};
@@ -195,13 +207,13 @@ void platform_init()
     //        pin             function       pup   pdown  out    state fast
     gpio_conf(GPIO_I2C_SCL,   GPIO_FUNC_I2C, true,false, false,  true, true);
     gpio_conf(GPIO_I2C_SDA,   GPIO_FUNC_I2C, true,false, false,  true, true);
-#endif
+#endif  // GPIO_I2C_SDA
 #else
     //        pin             function       pup   pdown  out    state fast
     gpio_conf(GPIO_EXP_AUDIO, GPIO_FUNC_SPI, true,false, false,  true, true);
     gpio_conf(GPIO_EXP_SPARE, GPIO_FUNC_SIO, true,false, false,  true, false);
     // configuration of corresponding SPI unit occurs in audio_setup()
-#endif
+#endif  // ENABLE_AUDIO_OUTPUT
 }
 
 #ifdef HAS_DIP_SWITCHES
@@ -240,7 +252,7 @@ static bool read_initiator_dip_switch()
 
     return !gpio_get(DIP_INITIATOR);
 }
-#endif
+#endif // HAS_DIP_SWITCHES
 
 // late_init() only runs in main application, SCSI not needed in bootloader
 void platform_late_init()
@@ -260,7 +272,7 @@ void platform_late_init()
 #else
     g_scsi_initiator = false;
     logmsg("SCSI target/disk mode, acting as a SCSI disk");
-#endif
+#endif // defined(HAS_DIP_SWITCHES) && defined(PLATFORM_HAS_INITIATOR_MODE)
 
     /* Initialize SCSI pins to required modes.
      * SCSI pins should be inactive / input at this point.
@@ -313,7 +325,7 @@ void platform_late_init()
 #ifdef ENABLE_AUDIO_OUTPUT
         // one-time control setup for DMA channels and second core
         audio_setup();
-#endif
+#endif // ENABLE_AUDIO_OUTPUT
     }
     else
     {
@@ -332,7 +344,7 @@ void platform_late_init()
         gpio_conf(SCSI_OUT_SEL,   GPIO_FUNC_SIO, false,false, true,  true, true);
         gpio_conf(SCSI_OUT_ACK,   GPIO_FUNC_SIO, false,false, true,  true, true);
         gpio_conf(SCSI_OUT_ATN,   GPIO_FUNC_SIO, false,false, true,  true, true);
-#endif
+#endif  // PLATFORM_HAS_INITIATOR_MODE
     }
 }
 
@@ -378,6 +390,7 @@ void platform_emergency_log_save()
     crashfile.close();
 }
 
+#ifdef __MBED___
 void mbed_error_hook(const mbed_error_ctx * error_context)
 {
     logmsg("--------------");
@@ -421,6 +434,7 @@ void mbed_error_hook(const mbed_error_ctx * error_context)
         for (int j = 0; j < base_delay * 10; j++) delay_ns(100000);
     }
 }
+#endif // __MBED__
 
 /*****************************************/
 /* Debug logging and watchdog            */
@@ -435,6 +449,7 @@ void mbed_error_hook(const mbed_error_ctx * error_context)
 // 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.
+#ifdef __MBED__
 static void usb_log_poll()
 {
     static uint32_t logpos = 0;
@@ -457,6 +472,7 @@ static void usb_log_poll()
         logpos -= available - actual;
     }
 }
+#endif // __MBED__
 
 // Use ADC to implement supply voltage monitoring for the +3.0V rail.
 // This works by sampling the temperature sensor channel, which has
@@ -487,7 +503,7 @@ static void adc_poll()
     * is playing.
     */
    if (audio_is_active()) return;
-#endif
+#endif  // ENABLE_AUDIO_OUTPUT
 
     int adc_value_max = 0;
     while (!adc_fifo_is_empty())
@@ -511,7 +527,7 @@ static void adc_poll()
             lowest_vdd_seen = vdd_mV - 50; // Small hysteresis to avoid excessive warnings
         }
     }
-#endif
+#endif // PLATFORM_VDD_WARNING_LIMIT_mV > 0
 }
 
 // This function is called for every log message.
@@ -530,11 +546,13 @@ static void watchdog_callback(unsigned alarm_num)
 {
     g_watchdog_timeout -= 1000;
 
+#ifdef __MBED__
     if (g_watchdog_timeout < WATCHDOG_CRASH_TIMEOUT - 1000)
     {
         // Been stuck for at least a second, start dumping USB log
         usb_log_poll();
     }
+#endif  // __MBED__
 
     if (g_watchdog_timeout <= WATCHDOG_CRASH_TIMEOUT - WATCHDOG_BUS_RESET_TIMEOUT)
     {
@@ -581,7 +599,10 @@ static void watchdog_callback(unsigned alarm_num)
                 p += 4;
             }
 
+#ifdef __MBED__
             usb_log_poll();
+#endif // __MBED__
+
             platform_emergency_log_save();
 
             platform_boot_to_main_firmware();
@@ -597,29 +618,49 @@ void platform_reset_watchdog()
 {
     g_watchdog_timeout = WATCHDOG_CRASH_TIMEOUT;
 
+
     if (!g_watchdog_initialized)
     {
-        hardware_alarm_claim(3);
-        hardware_alarm_set_callback(3, &watchdog_callback);
-        hardware_alarm_set_target(3, delayed_by_ms(get_absolute_time(), 1000));
+        int alarm_num = -1;
+        for (int i = 0; i < NUM_TIMERS; i++)
+        {
+            if (!hardware_alarm_is_claimed(i))
+            {
+                alarm_num = i;
+                break;
+            }
+        }
+        if (alarm_num == -1)
+        {
+            logmsg("No free watchdog hardware alarms to claim");
+            return;
+        }
+        hardware_alarm_claim(alarm_num);
+        hardware_alarm_set_callback(alarm_num, &watchdog_callback);
+        hardware_alarm_set_target(alarm_num, delayed_by_ms(get_absolute_time(), 1000));
         g_watchdog_initialized = true;
     }
 
     // USB log is polled here also to make sure any log messages in fault states
     // get passed to USB.
+#ifdef __MBED__
     usb_log_poll();
+#endif // __MBED__
 }
 
 // Poll function that is called every few milliseconds.
 // Can be left empty or used for platform-specific processing.
 void platform_poll()
 {
+#ifdef __MBED__
     usb_log_poll();
+#endif
+
     adc_poll();
     
 #ifdef ENABLE_AUDIO_OUTPUT
     audio_poll();
-#endif
+#endif // ENABLE_AUDIO_OUTPUT
 }
 
 uint8_t platform_get_buttons()
@@ -633,7 +674,7 @@ uint8_t platform_get_buttons()
     // SDA = button 1, SCL = button 2
     if (!gpio_get(GPIO_I2C_SDA)) buttons |= 1;
     if (!gpio_get(GPIO_I2C_SCL)) buttons |= 2;
-#endif
+#endif // defined(ENABLE_AUDIO_OUTPUT)
 
     // Simple debouncing logic: handle button releases after 100 ms delay.
     static uint32_t debounce;
@@ -674,12 +715,14 @@ bool platform_rewrite_flash_page(uint32_t offset, uint8_t buffer[PLATFORM_FLASH_
         }
     }
 
+#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);
@@ -749,7 +792,7 @@ void btldr_reset_handler()
 __attribute__((section(".btldr_vectors")))
 const void * btldr_vectors[2] = {&__StackTop, (void*)&btldr_reset_handler};
 
-#endif
+#endif // PLATFORM_BOOTLOADER_SIZE
 
 /************************************/
 /* ROM drive in extra flash space   */
@@ -816,7 +859,7 @@ bool platform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t count
     return true;
 }
 
-#endif
+#endif // PLATFORM_HAS_ROM_DRIVE
 
 /**********************************************/
 /* Mapping from data bytes to GPIO BOP values */
@@ -912,6 +955,7 @@ const uint16_t g_scsi_parity_check_lookup[512] __attribute__((aligned(1024), sec
 
 } /* extern "C" */
 
+#ifdef __MBED__
 /* Logging from mbed */
 
 static class LogTarget: public mbed::FileHandle {
@@ -938,3 +982,4 @@ mbed::FileHandle *mbed::mbed_override_console(int fd)
 {
     return &g_LogTarget;
 }
+#endif // __MBED__

+ 14 - 0
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.h

@@ -157,6 +157,20 @@ bool platform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t count
 extern const uint16_t g_scsi_parity_lookup[256];
 extern const uint16_t g_scsi_parity_check_lookup[512];
 
+// Network functions
+bool platform_network_supported();
+void platform_network_poll();
+int platform_network_init(char *mac);
+void platform_network_add_multicast_address(uint8_t *mac);
+bool platform_network_wifi_join(char *ssid, char *password);
+int platform_network_wifi_start_scan();
+int platform_network_wifi_scan_finished();
+void platform_network_wifi_dump_scan_list();
+int platform_network_wifi_rssi();
+char * platform_network_wifi_ssid();
+char * platform_network_wifi_bssid();
+int platform_network_wifi_channel();
+
 #ifdef __cplusplus
 }
 

+ 314 - 0
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_network.cpp

@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2023 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_config.h"
+#include <scsi.h>
+#include <network.h>
+
+extern "C" {
+
+#include <cyw43.h>
+#include <pico/cyw43_arch.h>
+
+#ifndef CYW43_IOCTL_GET_RSSI
+#define CYW43_IOCTL_GET_RSSI (0xfe)
+#endif
+
+// A default DaynaPort-compatible MAC
+static const char defaultMAC[] = { 0x00, 0x80, 0x19, 0xc0, 0xff, 0xee };
+
+static bool network_in_use = false;
+
+bool platform_network_supported()
+{
+	/* from cores/rp2040/RP2040Support.h */
+#if !defined(ARDUINO_RASPBERRY_PI_PICO_W)
+	return false;
+#else
+	extern bool __isPicoW;
+	return __isPicoW;
+#endif
+}
+
+int platform_network_init(char *mac)
+{
+	pico_unique_board_id_t board_id;
+	uint8_t set_mac[6], read_mac[6];
+
+	if (!platform_network_supported())
+		return -1;
+
+	logmsg(" ");
+	logmsg("=== Network Initialization ===");
+
+	memset(wifi_network_list, 0, sizeof(wifi_network_list));
+
+	cyw43_deinit(&cyw43_state);
+
+	if (mac == NULL || (mac[0] == 0 && mac[1] == 0 && mac[2] == 0 && mac[3] == 0 && mac[4] == 0 && mac[5] == 0))
+	{
+		mac = (char *)&set_mac;
+		memcpy(mac, defaultMAC, sizeof(set_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]);
+
+		if (board_id.id[3] != 0 && board_id.id[4] != 0 && board_id.id[5] != 0)
+		{
+			mac[3] = board_id.id[3];
+			mac[4] = board_id.id[4];
+			mac[5] = board_id.id[5];
+		}
+
+		memcpy(scsiDev.boardCfg.wifiMACAddress, mac, sizeof(scsiDev.boardCfg.wifiMACAddress));
+	}
+
+	// setting the MAC requires libpico to be compiled with CYW43_USE_OTP_MAC=0
+	memcpy(cyw43_state.mac, mac, sizeof(cyw43_state.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]);
+	if (memcmp(mac, read_mac, sizeof(read_mac)) != 0)
+		logmsg("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]);
+
+	network_in_use = true;
+
+	return 0;
+}
+
+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);
+}
+
+bool platform_network_wifi_join(char *ssid, char *password)
+{
+	int ret;
+
+	if (!platform_network_supported())
+		return false;
+
+	if (password == NULL || password[0] == 0)
+	{
+		logmsg_f("Connecting to Wi-Fi SSID \"%s\" with no authentication", ssid);
+		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);
+		ret = cyw43_arch_wifi_connect_async(ssid, password, CYW43_AUTH_WPA2_MIXED_PSK);
+	}
+	if (ret != 0)
+		logmsg_f("Wi-Fi connection failed: %d", ret);
+	
+	return (ret == 0);
+}
+
+void platform_network_poll()
+{
+	if (!network_in_use)
+		return;
+
+	scsiNetworkPurge();
+	cyw43_arch_poll();
+}
+
+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);
+
+	return ret;
+}
+
+static int platform_network_wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result)
+{
+	struct wifi_network_entry *entry = NULL;
+
+	if (!result || !result->ssid_len || !result->ssid[0])
+		return 0;
+
+	for (int i = 0; i < WIFI_NETWORK_LIST_ENTRY_COUNT; i++)
+	{
+		// take first available
+		if (wifi_network_list[i].ssid[0] == '\0')
+		{
+			entry = &wifi_network_list[i];
+			break;
+		}
+		// or if we've seen this network before, use this slot
+		else if (strcmp((char *)result->ssid, wifi_network_list[i].ssid) == 0)
+		{
+			entry = &wifi_network_list[i];
+			break;
+		}
+	}
+
+	if (!entry)
+	{
+		// no available slots, insert according to our RSSI
+		for (int i = 0; i < WIFI_NETWORK_LIST_ENTRY_COUNT; i++)
+		{
+			if (result->rssi > wifi_network_list[i].rssi)
+			{
+				// shift everything else down
+				for (int j = WIFI_NETWORK_LIST_ENTRY_COUNT - 1; j > i; j--)
+					wifi_network_list[j] = wifi_network_list[j - 1];
+
+				entry = &wifi_network_list[i];
+				memset(entry, 0, sizeof(struct wifi_network_entry));
+				break;
+			}
+		}
+	}
+
+	if (entry == NULL)
+		return 0;
+
+	if (entry->rssi == 0 || result->rssi > entry->rssi)
+	{
+		entry->channel = result->channel;
+		entry->rssi = result->rssi;
+	}
+	if (result->auth_mode & 7)
+		entry->flags = WIFI_NETWORK_FLAGS_AUTH;
+	strncpy(entry->ssid, (const char *)result->ssid, sizeof(entry->ssid));
+	entry->ssid[sizeof(entry->ssid) - 1] = '\0';
+	memcpy(entry->bssid, result->bssid, sizeof(entry->bssid));
+
+	return 0;
+}
+
+int platform_network_wifi_start_scan()
+{
+	if (cyw43_wifi_scan_active(&cyw43_state))
+		return -1;
+
+	cyw43_wifi_scan_options_t scan_options = { 0 };
+	memset(wifi_network_list, 0, sizeof(wifi_network_list));
+	return cyw43_wifi_scan(&cyw43_state, &scan_options, NULL, platform_network_wifi_scan_result);
+}
+
+int platform_network_wifi_scan_finished()
+{
+	return !cyw43_wifi_scan_active(&cyw43_state);
+}
+
+void platform_network_wifi_dump_scan_list()
+{
+	struct wifi_network_entry *entry = NULL;
+	
+	for (int i = 0; i < WIFI_NETWORK_LIST_ENTRY_COUNT; i++)
+	{
+		entry = &wifi_network_list[i];
+
+		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);
+	}
+}
+
+int platform_network_wifi_rssi()
+{
+	int32_t rssi = 0;
+
+    cyw43_ioctl(&cyw43_state, CYW43_IOCTL_GET_RSSI, sizeof(rssi), (uint8_t *)&rssi, CYW43_ITF_STA);
+	return rssi;
+}
+
+char * platform_network_wifi_ssid()
+{
+	struct ssid_t {
+		uint32_t ssid_len;
+		uint8_t ssid[32 + 1];
+	} ssid;
+	static char cur_ssid[32 + 1];
+
+	memset(cur_ssid, 0, sizeof(cur_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);
+		return NULL;
+	}
+
+	ssid.ssid[sizeof(ssid.ssid) - 1] = '\0';
+	if (ssid.ssid_len < sizeof(ssid.ssid))
+		ssid.ssid[ssid.ssid_len] = '\0';
+	
+	strlcpy(cur_ssid, (char *)ssid.ssid, sizeof(cur_ssid));
+	return cur_ssid;
+}
+
+char * platform_network_wifi_bssid()
+{
+	static char bssid[6];
+
+	memset(bssid, 0, sizeof(bssid));
+
+	/* TODO */
+
+	return bssid;
+}
+
+int platform_network_wifi_channel()
+{
+	int32_t channel = 0;
+
+    cyw43_ioctl(&cyw43_state, CYW43_IOCTL_GET_CHANNEL, sizeof(channel), (uint8_t *)&channel, CYW43_ITF_STA);
+	return channel;
+}
+
+// these override weakly-defined functions in pico-sdk
+
+void cyw43_cb_process_ethernet(void *cb_data, int itf, size_t len, const uint8_t *buf)
+{
+	scsiNetworkEnqueue(buf, len);
+}
+
+void cyw43_cb_tcpip_set_link_down(cyw43_t *self, int itf)
+{
+	logmsg_f("Disassociated from Wi-Fi SSID \"%s\"", self->ap_ssid);
+}
+
+void cyw43_cb_tcpip_set_link_up(cyw43_t *self, int itf)
+{
+	char *ssid = platform_network_wifi_ssid();
+
+	if (ssid)
+		logmsg_f("Successfully connected to Wi-Fi SSID \"%s\"", ssid);
+}
+
+}

+ 2 - 0
lib/ZuluSCSI_platform_RP2040/scsi_accel_host.cpp

@@ -155,8 +155,10 @@ void scsi_accel_host_init()
     g_scsi_host_state = SCSIHOST_IDLE;
     scsi_accel_host_config_gpio();
 
+#ifndef ZULUSCSI_NETWORK
     // Load PIO programs
     pio_clear_instruction_memory(SCSI_PIO);
+#endif
 
     // Asynchronous / synchronous SCSI read
     g_scsi_host.pio_offset_async_read = pio_add_program(SCSI_PIO, &scsi_host_async_read_program);

+ 57 - 10
lib/ZuluSCSI_platform_RP2040/scsi_accel_target.cpp

@@ -1,6 +1,9 @@
 /** 
  * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
  * 
+ * This work incorporates work from the following
+ *  Copyright (c) 2023 joshua stein <jcs@jcs.org>
+ * 
  * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
  * 
  * https://www.gnu.org/licenses/gpl-3.0.html
@@ -36,7 +39,11 @@
 #include <hardware/structs/iobank0.h>
 #include <hardware/sync.h>
 #include <audio.h>
+#ifdef __MBED__
 #include <multicore.h>
+#else
+#include <pico/multicore.h>
+#endif
 
 #ifdef ZULUSCSI_PICO
 #include "scsi_accel_target_Pico.pio.h"
@@ -50,10 +57,18 @@
 // SM0: Convert data bytes to lookup addresses to add parity
 // SM1: Write data to SCSI bus
 // SM2: For synchronous mode only, count ACK pulses
-#define SCSI_DMA_PIO pio0
-#define SCSI_PARITY_SM 0
-#define SCSI_DATA_SM 1
-#define SCSI_SYNC_SM 2
+#ifdef ZULUSCSI_NETWORK
+#  define SCSI_DMA_PIO pio0
+#  define SCSI_PARITY_SM 1
+#  define SCSI_DATA_SM 2
+#  define SCSI_SYNC_SM 3
+#else
+#  define SCSI_DMA_PIO pio0
+#  define SCSI_PARITY_SM 0
+#  define SCSI_DATA_SM 1
+#  define SCSI_SYNC_SM 2
+#endif
+
 
 // SCSI bus write acceleration uses 3 or 4 DMA channels (data flow A->B->C->D):
 // A: Bytes from RAM to scsi_parity PIO
@@ -66,10 +81,17 @@
 // B: Lookup from g_scsi_parity_check_lookup and copy to scsi_read_parity PIO
 // C: Addresses from scsi_accel_read PIO to lookup DMA READ_ADDR register
 // D: From pacer to data state machine to trigger transfers
-#define SCSI_DMA_CH_A 0
-#define SCSI_DMA_CH_B 1
-#define SCSI_DMA_CH_C 2
-#define SCSI_DMA_CH_D 3
+#ifdef ZULUSCSI_NETWORK
+#  define SCSI_DMA_CH_A 6
+#  define SCSI_DMA_CH_B 7
+#  define SCSI_DMA_CH_C 8
+#  define SCSI_DMA_CH_D 9
+#else
+#  define SCSI_DMA_CH_A 0
+#  define SCSI_DMA_CH_B 1
+#  define SCSI_DMA_CH_C 2
+#  define SCSI_DMA_CH_D 3
+#endif
 
 static struct {
     uint8_t *app_buf; // Buffer provided by application
@@ -845,10 +867,35 @@ void scsi_accel_rp2040_init()
 {
     g_scsi_dma_state = SCSIDMA_IDLE;
     scsidma_config_gpio();
-
-    // Mark channels as being in use, unless it has been done already
+    
+        if (g_channels_claimed) {
+        // Un-claim all SCSI state machines
+        pio_sm_unclaim(SCSI_DMA_PIO, SCSI_PARITY_SM);
+        pio_sm_unclaim(SCSI_DMA_PIO, SCSI_DATA_SM);
+        pio_sm_unclaim(SCSI_DMA_PIO, SCSI_SYNC_SM);
+
+        // Remove all SCSI programs
+        pio_remove_program(SCSI_DMA_PIO, &scsi_parity_program, g_scsi_dma.pio_offset_parity);
+        pio_remove_program(SCSI_DMA_PIO, &scsi_accel_async_write_program, g_scsi_dma.pio_offset_async_write);
+        pio_remove_program(SCSI_DMA_PIO, &scsi_sync_write_pacer_program, g_scsi_dma.pio_offset_sync_write_pacer);
+        pio_remove_program(SCSI_DMA_PIO, &scsi_sync_write_program, g_scsi_dma.pio_offset_sync_write);
+        pio_remove_program(SCSI_DMA_PIO, &scsi_accel_read_program, g_scsi_dma.pio_offset_read);
+        pio_remove_program(SCSI_DMA_PIO, &scsi_sync_read_pacer_program, g_scsi_dma.pio_offset_sync_read_pacer);
+        pio_remove_program(SCSI_DMA_PIO, &scsi_read_parity_program, g_scsi_dma.pio_offset_read_parity);
+
+        // Un-claim all SCSI DMA channels
+        dma_channel_unclaim(SCSI_DMA_CH_A);
+        dma_channel_unclaim(SCSI_DMA_CH_B);
+        dma_channel_unclaim(SCSI_DMA_CH_C);
+        dma_channel_unclaim(SCSI_DMA_CH_D);
+
+        // Set flag to re-initialize SCSI PIO system
+        g_channels_claimed = false;
+    }
+    
     if (!g_channels_claimed)
     {
+        // Mark channels as being in use, unless it has been done already
         pio_sm_claim(SCSI_DMA_PIO, SCSI_PARITY_SM);
         pio_sm_claim(SCSI_DMA_PIO, SCSI_DATA_SM);
         pio_sm_claim(SCSI_DMA_PIO, SCSI_SYNC_SM);

+ 28 - 1
platformio.ini

@@ -1,7 +1,7 @@
 ; PlatformIO Project Configuration File https://docs.platformio.org/page/projectconf.html
 
 [platformio]
-default_envs = ZuluSCSIv1_0, ZuluSCSIv1_0_mini, ZuluSCSIv1_1, ZuluSCSI_RP2040, ZuluSCSI_RP2040_Audio, ZuluSCSI_Pico, ZuluSCSI_BS2
+default_envs = ZuluSCSIv1_0, ZuluSCSIv1_0_mini, ZuluSCSIv1_1, ZuluSCSI_RP2040, ZuluSCSI_RP2040_Audio, ZuluSCSI_Pico, ZuluSCSI_Pico_Net_DaynaPORT, ZuluSCSI_BS2
 
 ; Example platform to serve as a base for porting efforts
 [env:template]
@@ -124,6 +124,33 @@ build_flags =
     -DZULUSCSI_PICO
     -DDISABLE_SWO
 
+[env:ZuluSCSI_Pico_Net_DaynaPORT]
+platform = https://github.com/maxgerhardt/platform-raspberrypi.git
+platform_packages = platformio/toolchain-gccarmnoneeabi@1.100301.220327
+    framework-arduinopico@https://github.com/BlueSCSI/arduino-pico-internal.git#e139b9c7816602597f473b3231032cca5d71a48a
+framework = arduino
+board = rpipicow
+board_build.core = earlephilhower
+debug_tool = picoprobe
+lib_deps =
+    SdFat=https://github.com/rabbitholecomputing/SdFat#2.2.0-gpt
+    minIni
+    ZuluSCSI_platform_RP2040
+    SCSI2SD
+    CUEParser
+build_flags =
+    -O2 -Isrc -ggdb -g3
+    -Wall -Wno-sign-compare -Wno-ignored-qualifiers
+    -DSPI_DRIVER_SELECT=3
+    -DSD_CHIP_SELECT_MODE=2
+    -DENABLE_DEDICATED_SPI=1
+    -DHAS_SDIO_CLASS
+    -DUSE_ARDUINO=1
+    -DNO_USB=1
+    -DZULUSCSI_PICO
+    -DZULUSCSI_NETWORK
+    -DDISABLE_SWO
+
 
 ; 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.

+ 37 - 10
src/ZuluSCSI.cpp

@@ -7,6 +7,9 @@
  *  BlueSCSI
  *  Copyright (c) 2021  Eric Helgeson, Androda
  *  
+ * This work incorporates work by following
+ *  Copyright (c) 2023 joshua stein <jcs@jcs.org>
+ * 
  *  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  
  *  Free Software Foundation, either version 2 of the License, or (at your  
@@ -370,9 +373,11 @@ bool findHDDImages()
       bool is_cd = (tolower(name[0]) == 'c' && tolower(name[1]) == 'd');
       bool is_fd = (tolower(name[0]) == 'f' && tolower(name[1]) == 'd');
       bool is_mo = (tolower(name[0]) == 'm' && tolower(name[1]) == 'o');
+      bool is_ne = (tolower(name[0]) == 'n' && tolower(name[1]) == 'e');
       bool is_re = (tolower(name[0]) == 'r' && tolower(name[1]) == 'e');
       bool is_tp = (tolower(name[0]) == 't' && tolower(name[1]) == 'p');
-      if (is_hd || is_cd || is_fd || is_mo || is_re || is_tp)
+
+      if (is_hd || is_cd || is_fd || is_mo || is_ne || is_re || is_tp)
       {
         // Check if the image should be loaded to microcontroller flash ROM drive
         bool is_romdrive = false;
@@ -445,12 +450,19 @@ bool findHDDImages()
           continue;
         }
 
+        if (is_ne && !platform_network_supported())
+        {
+          logmsg("-- Ignoring ", fullname, ", networking is not supported on this hardware");
+          continue;
+        }
+
         // Type mapping based on filename.
         // If type is FIXED, the type can still be overridden in .ini file.
         S2S_CFG_TYPE type = S2S_CFG_FIXED;
         if (is_cd) type = S2S_CFG_OPTICAL;
         if (is_fd) type = S2S_CFG_FLOPPY_14MB;
         if (is_mo) type = S2S_CFG_MO;
+        if (is_ne) type = S2S_CFG_NETWORK;
         if (is_re) type = S2S_CFG_REMOVEABLE;
         if (is_tp) type = S2S_CFG_SEQUENTIAL;
 
@@ -499,11 +511,21 @@ bool findHDDImages()
     if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))
     {
       int capacity_kB = ((uint64_t)cfg->scsiSectors * cfg->bytesPerSector) / 1024;
-      logmsg("SCSI ID:", (int)(cfg->scsiId & 7),
-            " BlockSize:", (int)cfg->bytesPerSector,
-            " Type:", (int)cfg->deviceType,
-            " Quirks:", (int)cfg->quirks,
-            " ImageSize:", capacity_kB, "kB");
+
+      if (cfg->deviceType == S2S_CFG_NETWORK)
+      {
+        logmsg("SCSI ID: ", (int)(cfg->scsiId & 7),
+              ", Type: ", (int)cfg->deviceType,
+              ", Quirks: ", (int)cfg->quirks);
+      }
+      else
+      {
+        logmsg("SCSI ID: ", (int)(cfg->scsiId & S2S_CFG_TARGET_ID_BITS),
+              ", BlockSize: ", (int)cfg->bytesPerSector,
+              ", Type: ", (int)cfg->deviceType,
+              ", Quirks: ", (int)cfg->quirks,
+              ", Size: ", capacity_kB, "kB");
+      };
     }
   }
 
@@ -604,11 +626,19 @@ static void reinitSCSI()
   scsiPhyReset();
   scsiDiskInit();
   scsiInit();
+
+  if (scsiDiskCheckAnyNetworkDevicesConfigured())
+  {
+    platform_network_init(scsiDev.boardCfg.wifiMACAddress);
+    platform_network_wifi_join(scsiDev.boardCfg.wifiSSID, scsiDev.boardCfg.wifiPassword);
+  }
   
 }
 
 extern "C" void zuluscsi_setup(void)
 {
+  pio_clear_instruction_memory(pio0);
+  pio_clear_instruction_memory(pio1);
   platform_init();
   platform_late_init();
 
@@ -692,6 +722,7 @@ extern "C" void zuluscsi_main_loop(void)
   platform_reset_watchdog();
   platform_poll();
   diskEjectButtonUpdate(true);
+  platform_network_poll();
   
 #ifdef PLATFORM_HAS_INITIATOR_MODE
   if (platform_is_initiator_mode_enabled())
@@ -762,8 +793,4 @@ extern "C" void zuluscsi_main_loop(void)
       }
     } while (!g_sdcard_present && !g_romdrive_active);
   }
-  else
-  {
-    
-  }
 }

+ 3 - 0
src/ZuluSCSI_config.h

@@ -1,5 +1,6 @@
 /** 
  * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * Copyright (c) 2023 joshua stein <jcs@jcs.org>
  * 
  * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
  * 
@@ -79,6 +80,7 @@
 #define DRIVEINFO_OPTICAL   {"ZULUSCSI", "CDROM",     PLATFORM_REVISION, ""}
 #define DRIVEINFO_FLOPPY    {"ZULUSCSI", "FLOPPY",    PLATFORM_REVISION, ""}
 #define DRIVEINFO_MAGOPT    {"ZULUSCSI", "MO_DRIVE",  PLATFORM_REVISION, ""}
+#define DRIVEINFO_NETWORK   {"ZULUSCSI", "NETWORK",   PLATFORM_REVISION, ""}
 #define DRIVEINFO_TAPE      {"ZULUSCSI", "TAPE",      PLATFORM_REVISION, ""}
 
 // Default SCSI drive information when Apple quirks are enabled
@@ -87,6 +89,7 @@
 #define APPLE_DRIVEINFO_OPTICAL   {"MATSHITA", "CD-ROM CR-8004A",   PLATFORM_REVISION, "2.0a"}
 #define APPLE_DRIVEINFO_FLOPPY    {"TEAC",     "FD235HS",           PLATFORM_REVISION, "1.44"}
 #define APPLE_DRIVEINFO_MAGOPT    {"MOST",     "RMD-5200",          PLATFORM_REVISION, "1.0"}
+#define APPLE_DRIVEINFO_NETWORK   {"Dayna",    "SCSI/Link",       "2.0f", ""}
 #define APPLE_DRIVEINFO_TAPE      {"ZULUSCSI", "APPLE_TAPE",        PLATFORM_REVISION, ""}
 
 // Default delay for SCSI phases.

+ 90 - 22
src/ZuluSCSI_disk.cpp

@@ -2,6 +2,7 @@
  * SCSI2SD V6 - Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
  * Copyright (C) 2014 Doug Brown <doug@downtowndougbrown.com
  * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * Copyright (c) 2023 joshua stein <jcs@jcs.org>
  *  
  * This file is licensed under the GPL version 3 or any later version. 
  * It is derived from disk.c in SCSI2SD V6
@@ -240,6 +241,7 @@ static void setDefaultDriveInfo(int target_idx)
     static const char *driveinfo_optical[4]   = DRIVEINFO_OPTICAL;
     static const char *driveinfo_floppy[4]    = DRIVEINFO_FLOPPY;
     static const char *driveinfo_magopt[4]    = DRIVEINFO_MAGOPT;
+    static const char *driveinfo_network[4]   = DRIVEINFO_NETWORK;
     static const char *driveinfo_tape[4]      = DRIVEINFO_TAPE;
 
     static const char *apl_driveinfo_fixed[4]     = APPLE_DRIVEINFO_FIXED;
@@ -247,6 +249,7 @@ static void setDefaultDriveInfo(int target_idx)
     static const char *apl_driveinfo_optical[4]   = APPLE_DRIVEINFO_OPTICAL;
     static const char *apl_driveinfo_floppy[4]    = APPLE_DRIVEINFO_FLOPPY;
     static const char *apl_driveinfo_magopt[4]    = APPLE_DRIVEINFO_MAGOPT;
+    static const char *apl_driveinfo_network[4]   = APPLE_DRIVEINFO_NETWORK;
     static const char *apl_driveinfo_tape[4]      = APPLE_DRIVEINFO_TAPE;
 
     const char **driveinfo = NULL;
@@ -261,6 +264,7 @@ static void setDefaultDriveInfo(int target_idx)
             case S2S_CFG_OPTICAL:       driveinfo = apl_driveinfo_optical; break;
             case S2S_CFG_FLOPPY_14MB:   driveinfo = apl_driveinfo_floppy; break;
             case S2S_CFG_MO:            driveinfo = apl_driveinfo_magopt; break;
+            case S2S_CFG_NETWORK:       driveinfo = apl_driveinfo_network; break;
             case S2S_CFG_SEQUENTIAL:    driveinfo = apl_driveinfo_tape; break;
             default:                    driveinfo = apl_driveinfo_fixed; break;
         }
@@ -276,6 +280,7 @@ static void setDefaultDriveInfo(int target_idx)
             case S2S_CFG_FLOPPY_14MB:   driveinfo = driveinfo_floppy; break;
             case S2S_CFG_MO:            driveinfo = driveinfo_magopt; break;
             case S2S_CFG_SEQUENTIAL:    driveinfo = driveinfo_tape; break;
+            case S2S_CFG_NETWORK:       driveinfo = driveinfo_network; break;
             default:                    driveinfo = driveinfo_fixed; break;
         }
     }
@@ -347,27 +352,30 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int
         img.scsiId = scsi_id | S2S_CFG_TARGET_ENABLED;
         img.sdSectorStart = 0;
         
-        if (img.scsiSectors == 0)
+        if (type != S2S_CFG_NETWORK)
         {
-            logmsg("---- Error: image file ", filename, " is empty");
-            img.file.close();
-            return false;
-        }
 
-        uint32_t sector_begin = 0, sector_end = 0;
-        if (img.file.isRom())
-        {
-            // ROM is always contiguous, no need to log
-        }
-        else if (img.file.contiguousRange(&sector_begin, &sector_end))
-        {
-            dbgmsg("---- Image file is contiguous, SD card sectors ", (int)sector_begin, " to ", (int)sector_end);
-        }
-        else
-        {
-            logmsg("---- WARNING: file ", filename, " is not contiguous. This will increase read latency.");
-        }
+            if (img.scsiSectors == 0)
+            {
+                logmsg("---- Error: image file ", filename, " is empty");
+                img.file.close();
+                return false;
+            }
 
+            uint32_t sector_begin = 0, sector_end = 0;
+            if (img.file.isRom())
+            {
+                // ROM is always contiguous, no need to log
+            }
+            else if (img.file.contiguousRange(&sector_begin, &sector_end))
+            {
+                dbgmsg("---- Image file is contiguous, SD card sectors ", (int)sector_begin, " to ", (int)sector_end);
+            }
+            else
+            {
+                logmsg("---- WARNING: file ", filename, " is not contiguous. This will increase read latency.");
+            }
+        }
         if (type == S2S_CFG_OPTICAL)
         {
             logmsg("---- Configuring as CD-ROM drive based on image name");
@@ -383,6 +391,11 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int
             logmsg("---- Configuring as magneto-optical based on image name");
             img.deviceType = S2S_CFG_MO;
         }
+        else if (type == S2S_CFG_NETWORK)
+        {
+            logmsg("---- Configuring as network based on image name");
+            img.deviceType = S2S_CFG_NETWORK;
+        }
         else if (type == S2S_CFG_REMOVEABLE)
         {
             logmsg("---- Configuring as removable drive based on image name");
@@ -819,6 +832,20 @@ uint8_t diskEjectButtonUpdate(bool immediate)
     }
 }
 
+bool scsiDiskCheckAnyNetworkDevicesConfigured()
+{
+    for (int i = 0; i < S2S_MAX_TARGETS; i++)
+    {
+        if (g_DiskImages[i].file.isOpen() && (g_DiskImages[i].scsiId & S2S_CFG_TARGET_ENABLED) && g_DiskImages[i].deviceType == S2S_CFG_NETWORK)
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+
 /*******************************/
 /* Config handling for SCSI2SD */
 /*******************************/
@@ -826,6 +853,8 @@ uint8_t diskEjectButtonUpdate(bool immediate)
 extern "C"
 void s2s_configInit(S2S_BoardCfg* config)
 {
+    char tmp[64];
+
     if (SD.exists(CONFIGFILE))
     {
         logmsg("Reading configuration from " CONFIGFILE);
@@ -836,9 +865,8 @@ void s2s_configInit(S2S_BoardCfg* config)
     }
 
     // Get default values from system preset, if any
-    char presetName[32];
-    ini_gets("SCSI", "System", "", presetName, sizeof(presetName), CONFIGFILE);
-    preset_config_t defaults = getSystemPreset(presetName);
+    ini_gets("SCSI", "System", "", tmp, sizeof(tmp), CONFIGFILE);
+    preset_config_t defaults = getSystemPreset(tmp);
 
     if (defaults.presetName)
     {
@@ -916,6 +944,37 @@ void s2s_configInit(S2S_BoardCfg* config)
         logmsg("-- EnableParity = No");
     }
 #endif
+
+    memset(tmp, 0, sizeof(tmp));
+    ini_gets("SCSI", "WiFiMACAddress", "", tmp, sizeof(tmp), CONFIGFILE);
+    if (tmp[0])
+    {
+        // convert from "01:23:45:67:89" to { 0x01, 0x23, 0x45, 0x67, 0x89 }
+        int mac[6];
+        if (sscanf(tmp, "%x:%x:%x:%x:%x:%x", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6)
+        {
+            config->wifiMACAddress[0] = mac[0];
+            config->wifiMACAddress[1] = mac[1];
+            config->wifiMACAddress[2] = mac[2];
+            config->wifiMACAddress[3] = mac[3];
+            config->wifiMACAddress[4] = mac[4];
+            config->wifiMACAddress[5] = mac[5];
+        }
+        else
+        {
+            logmsg("Invalid MAC address format: \"", tmp, "\"");
+            memset(config->wifiMACAddress, 0, sizeof(config->wifiMACAddress));
+        }
+    }
+
+    memset(tmp, 0, sizeof(tmp));
+    ini_gets("SCSI", "WiFiSSID", "", tmp, sizeof(tmp), CONFIGFILE);
+    if (tmp[0]) memcpy(config->wifiSSID, tmp, sizeof(config->wifiSSID));
+
+    memset(tmp, 0, sizeof(tmp));
+    ini_gets("SCSI", "WiFiPassword", "", tmp, sizeof(tmp), CONFIGFILE);
+    if (tmp[0]) memcpy(config->wifiPassword, tmp, sizeof(config->wifiPassword));
+
 }
 
 extern "C"
@@ -1047,7 +1106,16 @@ static void doReadCapacity()
 
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
     uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
-    uint32_t capacity = img.file.size() / bytesPerSector;
+    uint32_t capacity;
+
+    if (unlikely(scsiDev.target->cfg->deviceType == S2S_CFG_NETWORK))
+    {
+        capacity = 1;
+    }
+    else
+    {
+        capacity = img.file.size() / bytesPerSector;
+    }
 
     if (!pmi && lba)
     {

+ 4 - 0
src/ZuluSCSI_disk.h

@@ -2,6 +2,7 @@
  * SCSI2SD V6 - Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
  * Copyright (C) 2014 Doug Brown <doug@downtowndougbrown.com
  * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * Copyright (c) 2023 joshua stein <jcs@jcs.org>
  * 
  * It is derived from disk.h in SCSI2SD V6.
  * 
@@ -137,3 +138,6 @@ void scsiDiskStartRead(uint32_t lba, uint32_t blocks);
 
 // Start data transfer from SCSI bus to disk image
 void scsiDiskStartWrite(uint32_t lba, uint32_t blocks);
+
+// Returns true if there is at least one network device active
+bool scsiDiskCheckAnyNetworkDevicesConfigured();

+ 33 - 0
src/ZuluSCSI_log.cpp

@@ -1,5 +1,6 @@
 /** 
  * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * Copyright (c) 2023 joshua stein <jcs@jcs.org>
  * 
  * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
  * 
@@ -195,3 +196,35 @@ const char *log_get_buffer(uint32_t *startpos, uint32_t *available)
     return result;
 }
 
+void logmsg_f(const char *format, ...)
+{
+    static char out[2048];
+
+    va_list ap;
+    va_start(ap, format);
+    vsnprintf(out, sizeof(out), format, ap);
+    va_end(ap);
+
+    logmsg(out);
+}
+
+void logmsg_buf(const unsigned char *buf, unsigned long size)
+{
+    static char tmp[1500 * 3];
+    static char hex[] = "0123456789abcdef";
+    int o = 0;
+
+    for (int j = 0; j < size; j++) {
+        if (o + 3 >= sizeof(tmp))
+            break;
+
+        if (j != 0)
+            tmp[o++] = ' ';
+        tmp[o++] = hex[(buf[j] >> 4) & 0xf];
+        tmp[o++] = hex[buf[j] & 0xf];
+        tmp[o] = 0;
+    }
+
+    logmsg_f("%s", tmp);
+}
+

+ 13 - 0
src/ZuluSCSI_log.h

@@ -1,5 +1,6 @@
 /** 
  * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * Copyright (c) 2023 joshua stein <jcs@jcs.org>
  * 
  * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
  * 
@@ -68,6 +69,7 @@ inline void log_raw()
     // End of template recursion
 }
 
+
 extern "C" unsigned long millis();
 
 // Variadic template for printing multiple items
@@ -99,3 +101,14 @@ inline void dbgmsg(Params... params)
         log_raw("\r\n");
     }
 }
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+// Log long hex string
+void logmsg_buf(const unsigned char *buf, unsigned long size);
+// Log formatted string
+void logmsg_f(const char *format, ...);
+#ifdef __cplusplus
+}
+#endif