Sfoglia il codice sorgente

network: Add SCSI ethernet support emulating a Dayna SCSI/Link

Add SCSI commands to start a Wi-Fi scan, check status, get SSID
list, and switch networks.

To avoid running out of usable opcodes that System 6's SCSI Manager
will properly send, use one op code for all wifi commands and put a
sub-command in cdb[2].
joshua stein 2 anni fa
parent
commit
868f4e717a

+ 14 - 0
lib/BlueSCSI_platform_RP2040/BlueSCSI_platform.h

@@ -112,6 +112,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();
+
 // Below are GPIO access definitions that are used from scsiPhy.cpp.
 
 // Write a single SCSI pin.

+ 311 - 0
lib/BlueSCSI_platform_RP2040/BlueSCSI_platform_network.cpp

@@ -0,0 +1,311 @@
+/*
+ * 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 "BlueSCSI_platform.h"
+#include "BlueSCSI_log.h"
+#include "BlueSCSI_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;
+
+	log(" ");
+	log("=== 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)
+			log_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);
+	log_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)
+		log("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)
+		log_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)
+	{
+		log_f("Connecting to Wi-Fi SSID \"%s\" with no authentication", ssid);
+		ret = cyw43_arch_wifi_connect_async(ssid, NULL, CYW43_AUTH_OPEN);
+	}
+	else
+	{
+		log_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)
+		log_f("Wi-Fi connection failed: %d", ret);
+	
+	return (ret == 0);
+}
+
+void platform_network_poll()
+{
+	if (network_in_use)
+		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)
+		log_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_FLAG_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;
+			
+		log_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)
+	{
+		log_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)
+{
+	log_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)
+		log_f("Successfully connected to Wi-Fi SSID \"%s\"", ssid);
+}
+
+}

+ 7 - 3
lib/SCSI2SD/include/scsi2sd.h

@@ -73,8 +73,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;
 
 typedef enum
@@ -139,7 +139,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

+ 10 - 1
lib/SCSI2SD/src/firmware/inquiry.c

@@ -201,6 +201,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,9 +269,13 @@ uint8_t getDeviceTypeQualifier()
 		return 0;
 		break;
 
+	case S2S_CFG_NETWORK:
+		// processor device
+		return 0x03;
+		break;
+
 	default:
 		// Accept defaults for a fixed disk.
 		return 0;
 	}
 }
-

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

@@ -0,0 +1,452 @@
+/*
+ * 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 scsiNetworkPackets[NETWORK_PACKET_QUEUE_SIZE][NETWORK_PACKET_MAX_SIZE];
+static uint16_t scsiNetworkPacketSizes[NETWORK_PACKET_QUEUE_SIZE];
+static uint8_t scsiNetworkPacketWriteIndex = 0;
+static uint8_t scsiNetworkPacketReadIndex = 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;
+	uint8_t command = scsiDev.cdb[0];
+	uint32_t size = scsiDev.cdb[4] + (scsiDev.cdb[3] << 8);
+	long psize;
+	uint8_t cont = (scsiDev.cdb[5] == 0x80);
+
+	if (g_log_debug)
+	{
+		log_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 (scsiNetworkPacketReadIndex == scsiNetworkPacketWriteIndex)
+		{
+			// nothing available
+			memset(scsiDev.data, 0, 6);
+			scsiDev.dataLen = 6;
+		}
+		else
+		{
+			psize = scsiNetworkPacketSizes[scsiNetworkPacketReadIndex];
+
+			// pad smaller packets
+			if (psize < 64)
+			{
+				psize = 64;
+			}
+			else if (psize + 6 > size)
+			{
+				log_f("%s: packet size too big (%d)", __func__, psize);
+				psize = size - 6;
+			}
+
+			if (g_log_debug)
+			{
+				log_f("%s: sending packet[%d] to host of size %zu + 6", __func__, scsiNetworkPacketReadIndex, psize);
+			}
+
+			scsiDev.dataLen = psize + 6; // 2-byte length + 4-byte flag + packet
+			memcpy(scsiDev.data + 6, scsiNetworkPackets[scsiNetworkPacketReadIndex], psize);
+			scsiDev.data[0] = (psize >> 8) & 0xff;
+			scsiDev.data[1] = psize & 0xff;
+
+			if (scsiNetworkPacketReadIndex == NETWORK_PACKET_QUEUE_SIZE - 1)
+				scsiNetworkPacketReadIndex = 0;
+			else
+				scsiNetworkPacketReadIndex++;
+
+			// flags
+			scsiDev.data[2] = 0;
+			scsiDev.data[3] = 0;
+			scsiDev.data[4] = 0;
+			// more data to read?
+			scsiDev.data[5] = (scsiNetworkPacketReadIndex == scsiNetworkPacketWriteIndex ? 0 : 0x10);
+
+			if (g_log_debug)
+			{
+				log_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)
+		{
+			log_f("%s: read packet from host of size %zu - %d (parity error %d)", __func__, size, (cont ? 4 : 0), parityError);
+			log_buf(scsiDev.data, size);
+		}
+
+		if (cont)
+		{
+			size = (scsiDev.data[0] << 8) | scsiDev.data[1];
+			off = 4;
+		}
+
+		platform_network_send(scsiDev.data + off, size);
+
+		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)
+		{
+			log_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)
+			{
+				log_f("%s: enable interface", __func__);
+			}
+			scsiNetworkEnabled = true;
+			scsiNetworkPacketWriteIndex = 0;
+			scsiNetworkPacketReadIndex = 0;
+		}
+		else
+		{
+			if (g_log_debug)
+			{
+				log_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)
+		{
+			log_f("in scsiNetworkCommand with wi-fi command 0x%02x (size %d)", scsiDev.cdb[1], size);
+		}
+
+		switch (scsiDev.cdb[1]) {
+		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))
+				{
+					log_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)) {
+				log_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)
+			{
+				log_f("%s: read join request from host:", __func__);
+				log_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(scsiNetworkPackets[0]))
+	{
+		if (g_log_debug)
+			log_f("%s: dropping incoming network packet, too large (%zu > %zu)", __func__, len, sizeof(scsiNetworkPackets[0]));
+		return 0;
+	}
+
+	memcpy(scsiNetworkPackets[scsiNetworkPacketWriteIndex], buf, len);
+	uint32_t crc = crc32(buf, len);
+	scsiNetworkPackets[scsiNetworkPacketWriteIndex][len] = crc & 0xff;
+	scsiNetworkPackets[scsiNetworkPacketWriteIndex][len + 1] = (crc >> 8) & 0xff;
+	scsiNetworkPackets[scsiNetworkPacketWriteIndex][len + 2] = (crc >> 16) & 0xff;
+	scsiNetworkPackets[scsiNetworkPacketWriteIndex][len + 3] = (crc >> 24) & 0xff;
+
+	scsiNetworkPacketSizes[scsiNetworkPacketWriteIndex] = len + 4;
+
+	if (scsiNetworkPacketWriteIndex == NETWORK_PACKET_QUEUE_SIZE - 1)
+		scsiNetworkPacketWriteIndex = 0;
+	else
+		scsiNetworkPacketWriteIndex++;
+
+	if (scsiNetworkPacketWriteIndex == scsiNetworkPacketReadIndex)
+	{
+		if (g_log_debug)
+			log_f("%s: dropping packets in ring, write index caught up to read index", __func__);
+	}
+	
+	return 1;
+}

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

@@ -0,0 +1,64 @@
+/*
+ * 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   30		// 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;
+#define WIFI_NETWORK_FLAG_AUTH 0x1
+	uint8_t _padding;
+};
+#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);
+
+extern int platform_network_send(uint8_t *buf, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

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

@@ -27,6 +27,7 @@
 #include "bsp.h"
 #include "cdrom.h"
 //#include "debug.h"
+#include "network.h"
 #include "tape.h"
 #include "mo.h"
 #include "vendor.h"
@@ -584,7 +585,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.
 	}
@@ -1411,4 +1413,3 @@ int scsiReconnect()
 	return reconnected;
 }
 */
-

+ 1 - 1
platformio.ini

@@ -14,7 +14,7 @@ default_envs = BlueSCSI_Pico
 [env:BlueSCSI_Pico]
 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#f0a51a8eef41df659cb5e0d495a7fa03a6d28666
+    framework-arduinopico@https://github.com/BlueSCSI/arduino-pico-internal.git#e139b9c7816602597f473b3231032cca5d71a48a
 framework = arduino
 board = rpipicow
 board_build.core = earlephilhower

+ 32 - 6
src/BlueSCSI.cpp

@@ -187,6 +187,8 @@ const char * typeToChar(int deviceType)
     return "Floppy1.4MB";
   case S2S_CFG_MO:
     return "MO";
+  case S2S_CFG_NETWORK:
+    return "Network";
   case S2S_CFG_SEQUENTIAL:
     return "Tape";
   case S2S_CFG_REMOVEABLE:
@@ -277,6 +279,7 @@ 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');
 
@@ -286,7 +289,7 @@ bool findHDDImages()
         continue;
       }
 
-      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;
@@ -351,12 +354,19 @@ bool findHDDImages()
           continue;
         }
 
+        if (is_ne && !platform_network_supported())
+        {
+          log("-- 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;
 
@@ -411,11 +421,21 @@ bool findHDDImages()
     if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))
     {
       int capacity_kB = ((uint64_t)cfg->scsiSectors * cfg->bytesPerSector) / 1024;
-      log("* ID: ", (int)(cfg->scsiId & S2S_CFG_TARGET_ID_BITS),
-            ", BlockSize: ", (int)cfg->bytesPerSector,
-            ", Type: ", typeToChar((int)cfg->deviceType),
-            ", Quirks: ", quirksToChar((int)cfg->quirks),
-            ", Size: ", capacity_kB, "kB");
+
+      if (cfg->deviceType == S2S_CFG_NETWORK)
+      {
+        log("* ID: ", (int)(cfg->scsiId & 7),
+              ", Type: ", typeToChar((int)cfg->deviceType),
+              ", Quirks: ", quirksToChar((int)cfg->quirks));
+      }
+      else
+      {
+        log("* ID: ", (int)(cfg->scsiId & S2S_CFG_TARGET_ID_BITS),
+              ", BlockSize: ", (int)cfg->bytesPerSector,
+              ", Type: ", typeToChar((int)cfg->deviceType),
+              ", Quirks: ", quirksToChar((int)cfg->quirks),
+              ", Size: ", capacity_kB, "kB");
+      }
     }
   }
 
@@ -518,6 +538,11 @@ static void reinitSCSI()
   scsiDiskInit();
   scsiInit();
 
+  if (scsiDiskCheckAnyNetworkDevicesConfigured())
+  {
+    platform_network_init(scsiDev.boardCfg.wifiMACAddress);
+    platform_network_wifi_join(scsiDev.boardCfg.wifiSSID, scsiDev.boardCfg.wifiPassword);
+  }
 }
 
 extern "C" void bluescsi_setup(void)
@@ -590,6 +615,7 @@ extern "C" void bluescsi_main_loop(void)
   platform_reset_watchdog();
   platform_poll();
   diskEjectButtonUpdate(true);
+  platform_network_poll();
   
 #ifdef PLATFORM_HAS_INITIATOR_MODE
   if (platform_is_initiator_mode_enabled())

+ 2 - 0
src/BlueSCSI_config.h

@@ -55,6 +55,7 @@
 #define DRIVEINFO_OPTICAL   {"BlueSCSI", "CDROM",     PLATFORM_REVISION, ""}
 #define DRIVEINFO_FLOPPY    {"BlueSCSI", "FLOPPY",    PLATFORM_REVISION, ""}
 #define DRIVEINFO_MAGOPT    {"BlueSCSI", "MO_DRIVE",  PLATFORM_REVISION, ""}
+#define DRIVEINFO_NETWORK   {"BlueSCSI", "NETWORK",   PLATFORM_REVISION, ""}
 #define DRIVEINFO_TAPE      {"BlueSCSI", "TAPE",      PLATFORM_REVISION, ""}
 
 // Default SCSI drive information when Apple quirks are enabled
@@ -63,6 +64,7 @@
 #define APPLE_DRIVEINFO_OPTICAL   {"BlueSCSI", "CD-ROM CDU-55S",  "1.9a", ""}
 #define APPLE_DRIVEINFO_FLOPPY    {"TEAC",     "FD235HS",         "1.44", ""}
 #define APPLE_DRIVEINFO_MAGOPT    {"MOST",     "RMD-5200",        "1.0",  ""}
+#define APPLE_DRIVEINFO_NETWORK   {"Dayna",    "SCSI/Link",       "2.0f", ""}
 #define APPLE_DRIVEINFO_TAPE      {"BlueSCSI", "APPLE_TAPE",      "",     ""}
 
 // Default delay for SCSI phases.

+ 88 - 17
src/BlueSCSI_disk.cpp

@@ -262,6 +262,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;
@@ -269,6 +270,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;
@@ -283,6 +285,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;
         }
@@ -297,6 +300,7 @@ static void setDefaultDriveInfo(int target_idx)
             case S2S_CFG_OPTICAL:       driveinfo = driveinfo_optical; break;
             case S2S_CFG_FLOPPY_14MB:   driveinfo = driveinfo_floppy; break;
             case S2S_CFG_MO:            driveinfo = driveinfo_magopt; break;
+            case S2S_CFG_NETWORK:       driveinfo = driveinfo_network; break;
             case S2S_CFG_SEQUENTIAL:    driveinfo = driveinfo_tape; break;
             default:                    driveinfo = driveinfo_fixed; break;
         }
@@ -369,21 +373,24 @@ 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)
         {
-            log("---- Error: image file ", filename, " is empty");
-            img.file.close();
-            return false;
-        }
+            if (img.scsiSectors == 0)
+            {
+                log("---- 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))
-        {
-            log("---- WARNING: file ", filename, " is fragmented, see https://github.com/BlueSCSI/BlueSCSI-v2/wiki/Image-File-Fragmentation");
+            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))
+            {
+                log("---- WARNING: file ", filename, " is fragmented, see https://github.com/BlueSCSI/BlueSCSI-v2/wiki/Image-File-Fragmentation");
+            }
         }
 
         if (type == S2S_CFG_OPTICAL)
@@ -401,6 +408,11 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int
             log("---- Configuring as magneto-optical based on image name");
             img.deviceType = S2S_CFG_MO;
         }
+        else if (type == S2S_CFG_NETWORK)
+        {
+            log("---- Configuring as network based on image name");
+            img.deviceType = S2S_CFG_NETWORK;
+        }
         else if (type == S2S_CFG_REMOVEABLE)
         {
             log("---- Configuring as removable drive based on image name");
@@ -878,6 +890,19 @@ 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 */
 /*******************************/
@@ -885,6 +910,8 @@ uint8_t diskEjectButtonUpdate(bool immediate)
 extern "C"
 void s2s_configInit(S2S_BoardCfg* config)
 {
+    char tmp[64];
+
     if (SD.exists(CONFIGFILE))
     {
         log("Reading configuration from " CONFIGFILE);
@@ -895,9 +922,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)
     {
@@ -994,6 +1020,42 @@ void s2s_configInit(S2S_BoardCfg* config)
     {
         debuglog("-- ReinsertCDOnInquiry is disabled");
     }
+
+    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
+        {
+            log("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"
@@ -1125,7 +1187,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)
     {

+ 3 - 0
src/BlueSCSI_disk.h

@@ -140,3 +140,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();