///////////////////////////////////////////////////////////////////////////////////////////////////////// // // 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 // // 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 . ///////////////////////////////////////////////////////////////////////////////////////////////////////// #ifndef CMDPROC_H #define CMDPROC_H #include #include #include #include #include #include #include #include #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 #include #include #include #include #include #include #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 constexpr typename std::underlying_type::type to_underlying(E e) noexcept { return static_cast::type>(e); } virtual float version(void) { return (COMMANDPROCESSOR_VERSION); } protected: private: WiFi &wifi; FSPI &fspi; SDCard &sdcard; std::vector 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, " %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