New drivers: - MZ-1500 persona driver with MZ-700/MZ-1500 mode switch, PCG bank switching, PSG stubs, physical I/O forwarding for virtual mode - MZ-2200 persona driver (based on MZ-2000) - MZ-1R23/MZ-1R24 Kanji ROM / Dictionary ROM board (B8h/B9h IDM) - MZ-1R37 640KB EMM (ACh/ADh, no auto-increment, 20-bit addressing) - PIO-3034 320KB EMM (configurable base, 19-bit, auto-increment) - Celestite LAN/Memory composite board: - W5100 TCP/IP via ESP32 WiFi (connect, send, recv, ping) - Integrated MZ-1R12 32/64KB CMOS RAM with SD persistence - Integrated MZ-1R37 640KB EMM with SD persistence - UFM flash, unlock state machine, interrupt controller Celestite networking (Phase 2): - New IPC commands: NET_CFG, NET_SOCK, NET_SEND, NET_RECV, NET_PING - ESP32 BSD socket handlers with non-blocking connect and recv - Shared volatile struct for cross-core results (bypasses responseQueue) - Inline IDM read check for socket status and recv data - Z80 test programs: celestite_test.asm (17 tests), celestite_stress.asm (loop) MZ-1500 virtual mode fixes: - I/O writes forwarded to physical hardware (PSG, bank switching, PCG) - E000-E7FF always stays physical during bank switching - PCG bank (F000-FFFF) properly remapped to PHYSICAL when open QDF format support: - Japanese standard QD format auto-detected on load (81936 bytes) - Hunt pattern changed to 00+16 (mark+sync) for inter-block gap handling - Sync stripping handles long preambles (9+ bytes) - MZQDTool updated with -j flag and format conversion Other: - Debug shell load command: len parameter now optional - FSPI filename field: memcpy instead of strncpy for binary data - Interface availability expanded across MZ-700/1500/80A/2000/2200 - Web GUI: param hints for Celestite, updated driver interface lists Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
271 lines
9.4 KiB
C++
Vendored
271 lines
9.4 KiB
C++
Vendored
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Name: CommandProcessor.h
|
|
// Created: Jan 2025
|
|
// Version: v1.1
|
|
// Author(s): Philip Smart
|
|
// Description: Class definition to encapsulate a command processor, whose purpose is to act as slave
|
|
// taking commands from the RP2350 and execute them.
|
|
// v1.1: Command reception moved to binary SPI IPC protocol.
|
|
// Binary opcode dispatch replaces ASCII string map lookup.
|
|
// RBURST/WBURST commands added for multi-sector burst I/O.
|
|
// vTaskDelay(1) polling replaced by SPI blocking wait.
|
|
// UART path retained for INF and OOB (legacy compatibility).
|
|
// Credits:
|
|
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
|
//
|
|
// History: v1.00 Jan 2025 - Initial write.
|
|
// v1.10 Mar 2026 - Binary SPI dispatch, burst commands, SPI-driven wait loop.
|
|
//
|
|
// Notes: See Makefile to enable/disable conditional components
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// This source file is free software: you can redistribute it and#or modify
|
|
// it under the terms of the GNU General Public License as published
|
|
// by the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This source file is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef CMDPROC_H
|
|
#define CMDPROC_H
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
#include <iostream>
|
|
#include <vector>
|
|
#include <map>
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/queue.h"
|
|
#include "driver/spi_slave.h"
|
|
#include "driver/gpio.h"
|
|
#include "esp_system.h"
|
|
#include "esp_log.h"
|
|
#include "sdmmc_cmd.h"
|
|
#include "driver/sdspi_host.h"
|
|
#include "driver/spi_common.h"
|
|
#include "esp_vfs_fat.h"
|
|
#include <string>
|
|
#include <functional>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include "lwip/sockets.h"
|
|
#include "esp_netif.h"
|
|
#include "FSPI.h"
|
|
#include "WiFi.h"
|
|
#include "cJSON.h"
|
|
#include "SDCard.h"
|
|
#include "ipc_protocol.h"
|
|
|
|
// Define Command Processor class.
|
|
class CommandProcessor : public WiFi, FSPI, SDCard
|
|
{
|
|
#define NUMELEM(a) (sizeof(a) / sizeof(a[0]))
|
|
#define COMMANDPROCESSOR_VERSION 1.10
|
|
#define CMD_BUF_SIZE (128)
|
|
#define CMD_SIZE (3)
|
|
#define CMD_SEPERATOR ':'
|
|
#define TASK_STACK_SIZE (8192)
|
|
|
|
// Command loop uses portMAX_DELAY — see CommandProcessor.cpp for rationale.
|
|
|
|
public:
|
|
virtual ~CommandProcessor(void) {};
|
|
void processCommand(const t_IpcFrameHdr &frame);
|
|
void waitForCommand(void);
|
|
void start(void);
|
|
void init();
|
|
|
|
// Constructor — sets references and command table.
|
|
CommandProcessor(WiFi &wifi, FSPI &fspi, SDCard &sdcard, cJSON *config) : wifi(wifi), fspi(fspi), sdcard(sdcard)
|
|
{
|
|
(void) config;
|
|
for (int i = 0; i < 4; i++) netSockFd[i] = -1;
|
|
}
|
|
|
|
const char *getClassName(const std::string &prettyFunction)
|
|
{
|
|
size_t colons = prettyFunction.find("::");
|
|
if (colons == std::string::npos)
|
|
return "::";
|
|
size_t begin = prettyFunction.substr(0, colons).rfind(" ") + 1;
|
|
size_t end = colons - begin;
|
|
return (prettyFunction.substr(begin, end).c_str());
|
|
}
|
|
|
|
void replaceExt(std::string &fileName, const std::string &newExt)
|
|
{
|
|
std::string::size_type extPos = fileName.rfind('.', fileName.length());
|
|
if (extPos != std::string::npos)
|
|
fileName.replace(extPos + 1, newExt.length(), newExt);
|
|
}
|
|
|
|
template <typename E> constexpr typename std::underlying_type<E>::type to_underlying(E e) noexcept
|
|
{
|
|
return static_cast<typename std::underlying_type<E>::type>(e);
|
|
}
|
|
|
|
virtual float version(void)
|
|
{
|
|
return (COMMANDPROCESSOR_VERSION);
|
|
}
|
|
|
|
protected:
|
|
private:
|
|
WiFi &wifi;
|
|
FSPI &fspi;
|
|
SDCard &sdcard;
|
|
|
|
std::vector<std::string> split(const std::string &s, const std::string &delimiter);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Binary IPC command dispatch — indexed by IPCF_CMD_* opcode.
|
|
// Each method receives the full decoded command frame.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void cmdNop(const t_IpcFrameHdr &frame); // defined in CommandProcessor.cpp
|
|
|
|
void cmdReadSector(const t_IpcFrameHdr &frame)
|
|
{
|
|
sdcard.readSectorViaSPI(frame, fspi);
|
|
}
|
|
|
|
void cmdReadBurst(const t_IpcFrameHdr &frame)
|
|
{
|
|
sdcard.readBurstViaSPI(frame, fspi);
|
|
}
|
|
|
|
void cmdWriteSector(const t_IpcFrameHdr &frame)
|
|
{
|
|
sdcard.writeSectorViaSPI(frame, fspi);
|
|
}
|
|
|
|
void cmdWriteBurst(const t_IpcFrameHdr &frame)
|
|
{
|
|
sdcard.writeBurstViaSPI(frame, fspi);
|
|
}
|
|
|
|
void cmdReadFile(const t_IpcFrameHdr &frame)
|
|
{
|
|
// Handles RFILE, RFD, RQD, RRF — all read file variants.
|
|
// WiFi tracking for floppy/QD:
|
|
if (frame.command == IPCF_CMD_RFD)
|
|
wifi.setFloppyDiskFile(std::string(frame.filename), frame.diskNo);
|
|
else if (frame.command == IPCF_CMD_RQD)
|
|
wifi.setQuickDiskFile(std::string(frame.filename), frame.diskNo);
|
|
else if (frame.command == IPCF_CMD_RRF)
|
|
wifi.setRamFile(std::string(frame.filename), frame.diskNo);
|
|
sdcard.readFileViaSPI(frame, fspi);
|
|
}
|
|
|
|
void cmdWriteFile(const t_IpcFrameHdr &frame)
|
|
{
|
|
sdcard.writeFileViaSPI(frame, fspi);
|
|
}
|
|
|
|
void cmdReadInfo(const t_IpcFrameHdr &frame)
|
|
{
|
|
extern void CP_markInitialSpiDone(void);
|
|
if (sdcard.storeRP2350Info(frame, fspi))
|
|
{
|
|
CP_markInitialSpiDone();
|
|
}
|
|
}
|
|
|
|
void cmdReadDir(const t_IpcFrameHdr &frame)
|
|
{
|
|
// Build a directory listing as text and send back as payload.
|
|
// The path is in frame.filename (relative to SD card mount point).
|
|
char fullPath[256];
|
|
if (frame.filename[0] == '\0')
|
|
snprintf(fullPath, sizeof(fullPath), "%s", SD_CARD_MOUNT_POINT);
|
|
else
|
|
snprintf(fullPath, sizeof(fullPath), "%s/%s", SD_CARD_MOUNT_POINT, frame.filename);
|
|
|
|
// Remove trailing slash if present (except for mount root).
|
|
size_t plen = strlen(fullPath);
|
|
if (plen > strlen(SD_CARD_MOUNT_POINT) && fullPath[plen - 1] == '/')
|
|
fullPath[plen - 1] = '\0';
|
|
|
|
ESP_LOGI("CMDPROC", "DIR: '%s' → '%s'", frame.filename, fullPath);
|
|
DIR *dir = opendir(fullPath);
|
|
t_IpcFrameHdr hdr = {};
|
|
hdr.frameType = IPCF_TYPE_RESPONSE;
|
|
hdr.command = frame.command;
|
|
|
|
if (!dir)
|
|
{
|
|
hdr.status = IPCF_STATUS_ERR;
|
|
hdr.payloadLen = 0;
|
|
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_CRC_SIZE;
|
|
fspi.sendBinaryResp(&hdr, NULL, 0, respSize, portMAX_DELAY);
|
|
return;
|
|
}
|
|
|
|
// Build listing into a buffer. Use the DMA TX buffer scratch area.
|
|
static char dirBuf[IPCF_MAX_PAYLOAD];
|
|
int pos = 0;
|
|
struct dirent *entry;
|
|
|
|
while ((entry = readdir(dir)) != NULL && pos < (int) sizeof(dirBuf) - 80)
|
|
{
|
|
// Stat the entry for size and type.
|
|
char entryPath[512];
|
|
struct stat st;
|
|
snprintf(entryPath, sizeof(entryPath), "%.255s/%.255s", fullPath, entry->d_name);
|
|
bool isDir = (entry->d_type == DT_DIR);
|
|
long sz = 0;
|
|
if (!isDir && stat(entryPath, &st) == 0)
|
|
sz = (long) st.st_size;
|
|
|
|
if (isDir)
|
|
pos += snprintf(dirBuf + pos, sizeof(dirBuf) - pos, " <DIR> %s\n", entry->d_name);
|
|
else
|
|
pos += snprintf(dirBuf + pos, sizeof(dirBuf) - pos, " %7ld %s\n", sz, entry->d_name);
|
|
}
|
|
closedir(dir);
|
|
|
|
hdr.status = IPCF_STATUS_OK;
|
|
hdr.payloadLen = (uint16_t) pos;
|
|
uint32_t respSize = IPCF_HEADER_SIZE + pos + IPCF_CRC_SIZE;
|
|
fspi.sendBinaryResp(&hdr, (uint8_t *) dirBuf, pos, respSize, portMAX_DELAY);
|
|
}
|
|
|
|
void cmdUnknown(const t_IpcFrameHdr &frame)
|
|
{
|
|
ESP_LOGE("CMDPROC", "Unknown binary cmd opcode: %02X", frame.command);
|
|
// Send error response.
|
|
t_IpcFrameHdr hdr = {};
|
|
hdr.frameType = IPCF_TYPE_RESPONSE;
|
|
hdr.command = frame.command;
|
|
hdr.status = IPCF_STATUS_ERR;
|
|
hdr.payloadLen = 0;
|
|
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_CRC_SIZE;
|
|
fspi.sendBinaryResp(&hdr, NULL, 0, respSize, portMAX_DELAY);
|
|
}
|
|
|
|
// Network command handlers (Celestite W5100 emulation).
|
|
void cmdNetCfg(const t_IpcFrameHdr &frame);
|
|
void cmdNetSocket(const t_IpcFrameHdr &frame);
|
|
void cmdNetSend(const t_IpcFrameHdr &frame);
|
|
void cmdNetRecv(const t_IpcFrameHdr &frame);
|
|
void cmdNetPing(const t_IpcFrameHdr &frame);
|
|
|
|
int netSockFd[4]; // BSD socket file descriptors, one per W5100 socket.
|
|
};
|
|
#endif // CMDPROC_H
|