Files
pico/projects/tzpuPico/esp32/main/SDCard.cpp
2026-04-30 08:58:26 +01:00

874 lines
31 KiB
C++

/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name: SDCard.cpp
// Created: Jan 2025
// Version: v1.1
// Author(s): Philip Smart
// Description: Class definition to encapsulate the Espressif SD Card MMC Interface.
// v1.1: All read/write handlers rewritten to use binary IPC SPI frames.
// 32-sector FIFO read cache for CPM/RFS sequential access.
// Burst read/write (up to IPCF_MAX_SECTORS per SPI transaction).
// CRC32 integrity checking (hardware-assisted via esp_rom_crc32_le).
// Credits:
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
//
// History: v1.00 Jan 2025 - Initial write.
// v1.10 Mar 2025 - Binary IPC, burst, sector cache, CRC32.
//
// 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/>.
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_slave.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_rom_crc.h"
#include "sdmmc_cmd.h"
#include "driver/sdspi_host.h"
#include "driver/spi_common.h"
#include "driver/sdmmc_host.h"
#include "esp_vfs_fat.h"
#include <string>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <functional>
#include "IO.h"
#include "SDCard.h"
#include "ipc_protocol.h"
#define SDTAG "SDCARD"
// ---------------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------------
SDCard::SDCard()
{
ioBuf = IO_getIoBuf();
rp2350Header = nullptr;
cacheHead = 0;
memset(sectorCache, 0, sizeof(sectorCache));
}
// ---------------------------------------------------------------------------
// SD card mount / initialisation (unchanged from v1.0)
// ---------------------------------------------------------------------------
void SDCard::init()
{
esp_err_t ret;
esp_vfs_fat_sdmmc_mount_config_t sdMountConf;
sdmmc_slot_config_t sdSlotConf;
sdmmc_host_t sdHost;
sdMountConf = {.format_if_mount_failed = false, .max_files = 5, .allocation_unit_size = 16 * 1024, .disk_status_check_enable = true};
ESP_LOGI(SDTAG, "Initializing SD card (SDMMC)");
sdHost = SDMMC_HOST_DEFAULT();
sdSlotConf = SDMMC_SLOT_CONFIG_DEFAULT();
sdSlotConf.width = 4;
sdSlotConf.clk = (gpio_num_t) CONFIG_SD_CLK;
sdSlotConf.cmd = (gpio_num_t) CONFIG_SD_CMD;
sdSlotConf.d0 = (gpio_num_t) CONFIG_SD_DAT0;
sdSlotConf.d1 = (gpio_num_t) CONFIG_SD_DAT1;
sdSlotConf.d2 = (gpio_num_t) CONFIG_SD_DAT2;
sdSlotConf.d3 = (gpio_num_t) CONFIG_SD_DAT3;
sdSlotConf.cd = (gpio_num_t) CONFIG_SD_CDDETECT;
sdSlotConf.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
ESP_LOGI(SDTAG, "Mounting filesystem");
ret = esp_vfs_fat_sdmmc_mount(sdCardMountPoint.c_str(), &sdHost, &sdSlotConf, &sdMountConf, &sdCard);
if (ret != ESP_OK)
{
if (ret == ESP_FAIL)
ESP_LOGE(SDTAG, "Failed to mount filesystem.");
else
ESP_LOGE(SDTAG, "Failed to initialize card (%s).", esp_err_to_name(ret));
return;
}
ESP_LOGI(SDTAG, "Filesystem mounted.");
sdmmc_card_print_info(stdout, sdCard);
}
// ---------------------------------------------------------------------------
// Recursive directory deletion (unchanged from v1.0)
// ---------------------------------------------------------------------------
bool SDCard::deleteDir(const std::string &path)
{
if (path.compare(SD_CARD_MOUNT_POINT) == 0 || path.compare(SD_CARD_MOUNT_POINT "/") == 0 || path.compare("/") == 0)
{
ESP_LOGE(SDTAG, "Cannot delete root directory: %s", path.c_str());
return false;
}
DIR *dir = opendir(path.c_str());
if (!dir)
{
ESP_LOGE(SDTAG, "Failed to open directory: %s", path.c_str());
return false;
}
struct dirent *entry;
while ((entry = readdir(dir)) != nullptr)
{
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
std::string fullPath = path + "/" + entry->d_name;
struct stat statbuf;
if (stat(fullPath.c_str(), &statbuf) != 0)
{
ESP_LOGE(SDTAG, "Failed to stat: %s", fullPath.c_str());
closedir(dir);
return false;
}
if (S_ISDIR(statbuf.st_mode))
{
if (!deleteDir(fullPath))
{
closedir(dir);
return false;
}
}
else
{
if (unlink(fullPath.c_str()) != 0)
{
ESP_LOGE(SDTAG, "Failed to delete file: %s", fullPath.c_str());
closedir(dir);
return false;
}
}
}
closedir(dir);
if (rmdir(path.c_str()) != 0)
{
ESP_LOGE(SDTAG, "Failed to delete directory: %s", path.c_str());
return false;
}
return true;
}
// ---------------------------------------------------------------------------
// Sector cache helpers
// ---------------------------------------------------------------------------
// Returns cache slot index if hit, -1 if miss.
int SDCard::cacheLookup(const char *filename, uint32_t fileOffset)
{
for (int i = 0; i < SECTOR_CACHE_SIZE; i++)
{
if (sectorCache[i].valid && sectorCache[i].fileOffset == fileOffset && strncmp(sectorCache[i].filename, filename, IPCF_FILENAME_LEN) == 0)
{
return i;
}
}
return -1;
}
// Insert a sector into the cache using FIFO eviction.
void SDCard::cacheInsert(const char *filename, uint32_t fileOffset, const uint8_t *data)
{
t_SectorCacheEntry *e = &sectorCache[cacheHead];
strncpy(e->filename, filename, IPCF_FILENAME_LEN - 1);
e->filename[IPCF_FILENAME_LEN - 1] = '\0';
e->fileOffset = fileOffset;
memcpy(e->data, data, IPCF_SECTOR_SIZE);
e->valid = true;
cacheHead = (cacheHead + 1) % SECTOR_CACHE_SIZE;
}
// Invalidate any cached entry matching filename + fileOffset (write path).
void SDCard::cacheInvalidate(const char *filename, uint32_t fileOffset)
{
for (int i = 0; i < SECTOR_CACHE_SIZE; i++)
{
if (sectorCache[i].valid && sectorCache[i].fileOffset == fileOffset && strncmp(sectorCache[i].filename, filename, IPCF_FILENAME_LEN) == 0)
{
sectorCache[i].valid = false;
}
}
}
// ---------------------------------------------------------------------------
// Internal helper — build and send an error response frame.
// ---------------------------------------------------------------------------
void SDCard::sendErrResp(FSPI &fspi, uint8_t status, uint8_t command, TickType_t timeout)
{
t_IpcFrameHdr hdr = {};
hdr.frameType = IPCF_TYPE_RESPONSE;
hdr.command = command;
hdr.status = status;
hdr.payloadLen = 0;
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_CRC_SIZE;
fspi.sendBinaryResp(&hdr, NULL, 0, respSize, timeout);
}
// ---------------------------------------------------------------------------
// Internal helper — receive write payload + CRC32 from RP2350.
// Returns ESP_OK on success, error code on CRC mismatch or SPI failure.
// ---------------------------------------------------------------------------
esp_err_t SDCard::receiveWritePayload(FSPI &fspi, uint8_t *buf, uint32_t len, uint32_t expectedCrc)
{
// The RP2350 sends: payload (len bytes) | CRC32 (4 bytes).
// We receive into ipcCmdBuf (DMA-capable) then copy to buf.
// ESP32 SPI slave DMA requires transaction size to be a multiple of 4 bytes.
// Round up so the DMA does not silently truncate the last CRC byte(s).
// The RP2350 pads its T2 DMA to the same rounded-up length (zeros after CRC).
uint32_t frameLen = (len + IPCF_CRC_SIZE + 3u) & ~3u;
spi_slave_transaction_t t = {};
t.length = frameLen * 8;
t.rx_buffer = fspi.ipcCmdBuf;
t.tx_buffer = fspi.ipcRespBuf; // zeros (already cleared)
esp_err_t ret = spi_slave_transmit(FSPI_HOST, &t, portMAX_DELAY);
if (ret != ESP_OK)
return ret;
// Verify CRC32 of received payload.
uint32_t recvCrc;
memcpy(&recvCrc, fspi.ipcCmdBuf + len, IPCF_CRC_SIZE);
uint32_t calcCrc = esp_rom_crc32_le(0, fspi.ipcCmdBuf, len);
if (calcCrc != recvCrc)
{
ESP_LOGE(SDTAG, "Write payload CRC mismatch: calc=%08lx recv=%08lx (len=%lu)", calcCrc, recvCrc, (unsigned long) len);
// Diagnostic: print first 16 received bytes and the CRC bytes.
ESP_LOGE(SDTAG,
" recv[0..15]: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
fspi.ipcCmdBuf[0],
fspi.ipcCmdBuf[1],
fspi.ipcCmdBuf[2],
fspi.ipcCmdBuf[3],
fspi.ipcCmdBuf[4],
fspi.ipcCmdBuf[5],
fspi.ipcCmdBuf[6],
fspi.ipcCmdBuf[7],
fspi.ipcCmdBuf[8],
fspi.ipcCmdBuf[9],
fspi.ipcCmdBuf[10],
fspi.ipcCmdBuf[11],
fspi.ipcCmdBuf[12],
fspi.ipcCmdBuf[13],
fspi.ipcCmdBuf[14],
fspi.ipcCmdBuf[15]);
ESP_LOGE(SDTAG,
" crc_bytes @ offset %lu: %02X %02X %02X %02X",
(unsigned long) len,
fspi.ipcCmdBuf[len + 0],
fspi.ipcCmdBuf[len + 1],
fspi.ipcCmdBuf[len + 2],
fspi.ipcCmdBuf[len + 3]);
return ESP_ERR_INVALID_CRC;
}
memcpy(buf, fspi.ipcCmdBuf, len);
return ESP_OK;
}
// ---------------------------------------------------------------------------
// readSectorViaSPI — read one IPCF_SECTOR_SIZE (512-byte) sector.
// Checks the read cache first. On a cache miss, reads SECTOR_READAHEAD
// consecutive sectors in a single fread() and caches them all. Subsequent
// single-sector requests for offsets +512, +1024, ... hit the cache and
// cost only one SPI round-trip with no SD card I/O — critical for CPM DIR
// and other sequential-access patterns.
// ---------------------------------------------------------------------------
bool SDCard::readSectorViaSPI(const t_IpcFrameHdr &frame, FSPI &fspi)
{
const char *filename = frame.filename;
uint32_t fileOffset = frame.fileOffset;
uint8_t command = frame.command;
// --- Cache lookup ---
int slot = cacheLookup(filename, fileOffset);
if (slot >= 0)
{
// Cache hit — no SD card I/O needed.
t_IpcFrameHdr hdr = {};
hdr.frameType = IPCF_TYPE_RESPONSE;
hdr.command = command;
hdr.status = IPCF_STATUS_OK;
hdr.payloadLen = IPCF_SECTOR_SIZE;
hdr.sectorCount = 1;
hdr.fileOffset = fileOffset;
hdr.flags = IPCF_FLAG_LAST_CHUNK;
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_SECTOR_SIZE + IPCF_CRC_SIZE;
esp_err_t ret = fspi.sendBinaryResp(&hdr, sectorCache[slot].data, IPCF_SECTOR_SIZE, respSize, portMAX_DELAY);
return (ret == ESP_OK);
}
// --- Cache miss — open file and read ahead ---
std::string filepath = SD_CARD_MOUNT_POINT "/";
filepath.append(filename);
FILE *f = fopen(filepath.c_str(), "r");
if (!f)
{
ESP_LOGE(SDTAG, "readSector: open failed (%s)", filepath.c_str());
sendErrResp(fspi, IPCF_STATUS_NOTFOUND, command);
return false;
}
fseek(f, 0L, SEEK_END);
int fileSize = ftell(f);
if ((int) (fileOffset + IPCF_SECTOR_SIZE) > fileSize)
{
fclose(f);
ESP_LOGE(SDTAG, "readSector: offset %lu out of range (fileSize=%d)", fileOffset, fileSize);
sendErrResp(fspi, IPCF_STATUS_ERR, command);
return false;
}
// Clamp read-ahead to file boundary.
int numAhead = SECTOR_READAHEAD;
while (numAhead > 1 && (int) (fileOffset + (uint32_t) numAhead * IPCF_SECTOR_SIZE) > fileSize)
numAhead--;
// Allocate heap buffer for the read-ahead block.
// Fall back to a single-sector stack buffer if heap is tight.
uint8_t stackBuf[IPCF_SECTOR_SIZE];
uint8_t *raBuf = (uint8_t *) heap_caps_malloc((size_t) numAhead * IPCF_SECTOR_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
uint8_t *readBuf = raBuf ? raBuf : stackBuf;
int numToRead = raBuf ? numAhead : 1;
fseek(f, (long) fileOffset, SEEK_SET);
size_t totalRead = fread(readBuf, 1, (size_t) numToRead * IPCF_SECTOR_SIZE, f);
fclose(f);
if (totalRead < IPCF_SECTOR_SIZE)
{
if (raBuf)
free(raBuf);
sendErrResp(fspi, IPCF_STATUS_ERR, command);
return false;
}
// Insert all complete sectors into the cache (requested + read-ahead).
int sectorsRead = (int) (totalRead / IPCF_SECTOR_SIZE);
for (int i = 0; i < sectorsRead; i++)
{
uint8_t *sec = readBuf + (size_t) i * IPCF_SECTOR_SIZE;
size_t secBytes = totalRead - (size_t) i * IPCF_SECTOR_SIZE;
if (secBytes < IPCF_SECTOR_SIZE)
memset(sec + secBytes, 0, IPCF_SECTOR_SIZE - secBytes);
cacheInsert(filename, fileOffset + (uint32_t) i * IPCF_SECTOR_SIZE, sec);
}
// Build and send response for the originally requested sector (readBuf[0]).
t_IpcFrameHdr hdr = {};
hdr.frameType = IPCF_TYPE_RESPONSE;
hdr.command = command;
hdr.status = IPCF_STATUS_OK;
hdr.payloadLen = IPCF_SECTOR_SIZE;
hdr.sectorCount = 1;
hdr.fileOffset = fileOffset;
hdr.flags = IPCF_FLAG_LAST_CHUNK;
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_SECTOR_SIZE + IPCF_CRC_SIZE;
esp_err_t ret = fspi.sendBinaryResp(&hdr, readBuf, IPCF_SECTOR_SIZE, respSize, portMAX_DELAY);
if (raBuf)
free(raBuf);
return (ret == ESP_OK);
}
// ---------------------------------------------------------------------------
// readBurstViaSPI — read up to IPCF_MAX_SECTORS consecutive sectors.
// All sectors returned in a single SPI transaction. Sectors served from
// cache where available; any uncached sectors are read from SD.
// ---------------------------------------------------------------------------
bool SDCard::readBurstViaSPI(const t_IpcFrameHdr &frame, FSPI &fspi)
{
const char *filename = frame.filename;
uint32_t fileOffset = frame.fileOffset;
int numSectors = (int) frame.sectorCount;
uint8_t command = frame.command;
if (numSectors < 1)
numSectors = 1;
if (numSectors > IPCF_MAX_SECTORS)
numSectors = IPCF_MAX_SECTORS;
// Allocate a DMA-capable burst buffer.
uint32_t totalBytes = (uint32_t) numSectors * IPCF_SECTOR_SIZE;
uint8_t *burstBuf = (uint8_t *) heap_caps_malloc(totalBytes, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
if (!burstBuf)
{
ESP_LOGE(SDTAG, "readBurst: malloc failed (%lu bytes)", totalBytes);
sendErrResp(fspi, IPCF_STATUS_ERR, command);
return false;
}
// Open file for sectors not in cache.
std::string filepath = SD_CARD_MOUNT_POINT "/";
filepath.append(filename);
FILE *f = NULL;
bool result = true;
for (int s = 0; s < numSectors && result; s++)
{
uint32_t secOffset = fileOffset + (uint32_t) s * IPCF_SECTOR_SIZE;
uint8_t *dest = burstBuf + (size_t) s * IPCF_SECTOR_SIZE;
int slot = cacheLookup(filename, secOffset);
if (slot >= 0)
{
// Cache hit.
memcpy(dest, sectorCache[slot].data, IPCF_SECTOR_SIZE);
}
else
{
// Cache miss — open file on first miss.
if (!f)
{
f = fopen(filepath.c_str(), "r");
if (!f)
{
ESP_LOGE(SDTAG, "readBurst: open failed (%s)", filepath.c_str());
result = false;
break;
}
}
fseek(f, (long) secOffset, SEEK_SET);
size_t rd = fread(dest, 1, IPCF_SECTOR_SIZE, f);
if (rd < IPCF_SECTOR_SIZE)
memset(dest + rd, 0, IPCF_SECTOR_SIZE - rd);
// Cache this sector.
cacheInsert(filename, secOffset, dest);
}
}
if (f)
fclose(f);
if (!result)
{
free(burstBuf);
sendErrResp(fspi, IPCF_STATUS_NOTFOUND, command);
return false;
}
// Build and send response.
t_IpcFrameHdr hdr = {};
hdr.frameType = IPCF_TYPE_RESPONSE;
hdr.command = command;
hdr.status = IPCF_STATUS_OK;
hdr.payloadLen = (uint16_t) totalBytes;
hdr.sectorCount = (uint16_t) numSectors;
hdr.fileOffset = fileOffset;
hdr.flags = IPCF_FLAG_LAST_CHUNK;
uint32_t respSize = IPCF_HEADER_SIZE + totalBytes + IPCF_CRC_SIZE;
esp_err_t ret = fspi.sendBinaryResp(&hdr, burstBuf, totalBytes, respSize, portMAX_DELAY);
free(burstBuf);
return (ret == ESP_OK);
}
// ---------------------------------------------------------------------------
// readFileViaSPI — send ONE chunk of a file per command invocation.
// Used by RFILE, RFD, RQD, RRF commands.
//
// The RP2350 drives chunked file transfers by issuing one command per chunk,
// advancing fileOffset by the received payloadLen each time. The ESP32
// must NOT loop internally — doing so would collide with the RP2350's next
// command SPI transaction.
//
// Response header carries:
// fileOffset = total file size (so RP2350 knows when to stop)
// payloadLen = bytes in this chunk
// flags = IPCF_FLAG_LAST_CHUNK when this chunk reaches EOF
// ---------------------------------------------------------------------------
bool SDCard::readFileViaSPI(const t_IpcFrameHdr &frame, FSPI &fspi)
{
const char *filename = frame.filename;
uint32_t reqOffset = frame.fileOffset; // start position for this chunk
uint8_t command = frame.command;
std::string filepath = SD_CARD_MOUNT_POINT "/";
filepath.append(filename);
FILE *f = fopen(filepath.c_str(), "r");
if (!f)
{
ESP_LOGE(SDTAG, "readFile: open failed (%s)", filepath.c_str());
sendErrResp(fspi, IPCF_STATUS_NOTFOUND, command);
return false;
}
fseek(f, 0L, SEEK_END);
int fileSize = ftell(f);
if (reqOffset >= (uint32_t) fileSize)
{
fclose(f);
// Past EOF — send last-chunk response with zero payload so RP2350 terminates.
t_IpcFrameHdr hdr = {};
hdr.frameType = IPCF_TYPE_RESPONSE;
hdr.command = command;
hdr.status = IPCF_STATUS_OK;
hdr.payloadLen = 0;
hdr.fileOffset = (uint32_t) fileSize;
hdr.flags = IPCF_FLAG_LAST_CHUNK;
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_CRC_SIZE;
fspi.sendBinaryResp(&hdr, NULL, 0, respSize, portMAX_DELAY);
return true;
}
fseek(f, (long) reqOffset, SEEK_SET);
// Allocate DMA-capable chunk buffer.
uint8_t *chunkBuf = (uint8_t *) heap_caps_malloc(IPCF_MAX_PAYLOAD, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
if (!chunkBuf)
{
fclose(f);
sendErrResp(fspi, IPCF_STATUS_ERR, command);
return false;
}
size_t chunk = fread(chunkBuf, 1, IPCF_MAX_PAYLOAD, f);
fclose(f);
if (chunk == 0)
{
free(chunkBuf);
sendErrResp(fspi, IPCF_STATUS_ERR, command);
return false;
}
bool lastChunk = ((int) (reqOffset + chunk) >= fileSize);
t_IpcFrameHdr hdr = {};
hdr.frameType = IPCF_TYPE_RESPONSE;
hdr.command = command;
hdr.status = IPCF_STATUS_OK;
hdr.payloadLen = (uint16_t) chunk;
hdr.fileOffset = (uint32_t) fileSize; // carry total size
hdr.flags = lastChunk ? IPCF_FLAG_LAST_CHUNK : IPCF_FLAG_MORE_DATA;
uint32_t respSize = IPCF_HEADER_SIZE + (uint32_t) chunk + IPCF_CRC_SIZE;
esp_err_t ret = fspi.sendBinaryResp(&hdr, chunkBuf, (uint32_t) chunk, respSize, portMAX_DELAY);
free(chunkBuf);
return (ret == ESP_OK);
}
// ---------------------------------------------------------------------------
// writeSectorViaSPI — receive one sector from RP2350, write to file.
// Invalidates any cached copy of the sector.
// ---------------------------------------------------------------------------
bool SDCard::writeSectorViaSPI(const t_IpcFrameHdr &frame, FSPI &fspi)
{
const char *filename = frame.filename;
uint32_t fileOffset = frame.fileOffset;
uint8_t command = frame.command;
// Use the actual payload length from the T1 header — the WD1773 driver may
// send 256-byte sectors (MZ-2Z009E) rather than 512. Fall back to
// IPCF_SECTOR_SIZE for backwards compatibility if payloadLen is zero.
uint32_t payloadLen = frame.payloadLen > 0 ? frame.payloadLen : IPCF_SECTOR_SIZE;
if (payloadLen > IPCF_SECTOR_SIZE)
payloadLen = IPCF_SECTOR_SIZE;
// Invalidate cached sector before write.
cacheInvalidate(filename, fileOffset);
// Receive payload from RP2350 (payloadLen + CRC32).
uint8_t sectorBuf[IPCF_SECTOR_SIZE];
if (receiveWritePayload(fspi, sectorBuf, payloadLen, 0) != ESP_OK)
{
sendErrResp(fspi, IPCF_STATUS_CRCFAIL, command);
return false;
}
// Open file and seek to write position.
std::string filepath = SD_CARD_MOUNT_POINT "/";
filepath.append(filename);
FILE *f = fopen(filepath.c_str(), "r+");
if (!f)
{
ESP_LOGE(SDTAG, "writeSector: open failed (%s)", filepath.c_str());
sendErrResp(fspi, IPCF_STATUS_NOTFOUND, command);
return false;
}
fseek(f, (long) fileOffset, SEEK_SET);
size_t written = fwrite(sectorBuf, 1, payloadLen, f);
fflush(f);
fclose(f);
if (written != payloadLen)
{
ESP_LOGE(SDTAG, "writeSector: short write %zu/%lu", written, (unsigned long) payloadLen);
sendErrResp(fspi, IPCF_STATUS_ERR, command);
return false;
}
// Send success response (header + CRC only, no payload).
t_IpcFrameHdr hdr = {};
hdr.frameType = IPCF_TYPE_RESPONSE;
hdr.command = command;
hdr.status = IPCF_STATUS_OK;
hdr.payloadLen = 0;
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_CRC_SIZE;
esp_err_t ret = fspi.sendBinaryResp(&hdr, NULL, 0, respSize, portMAX_DELAY);
return (ret == ESP_OK);
}
// ---------------------------------------------------------------------------
// writeBurstViaSPI — receive up to IPCF_MAX_SECTORS sectors, write to file.
// All sectors received in one SPI transaction.
// ---------------------------------------------------------------------------
bool SDCard::writeBurstViaSPI(const t_IpcFrameHdr &frame, FSPI &fspi)
{
const char *filename = frame.filename;
uint32_t fileOffset = frame.fileOffset;
int numSectors = (int) frame.sectorCount;
uint8_t command = frame.command;
if (numSectors < 1)
numSectors = 1;
if (numSectors > IPCF_MAX_SECTORS)
numSectors = IPCF_MAX_SECTORS;
// Use actual payload length from T1 header — sectors may be < 512 bytes.
uint32_t totalBytes = frame.payloadLen > 0 ? frame.payloadLen : (uint32_t) numSectors * IPCF_SECTOR_SIZE;
if (totalBytes > IPCF_MAX_PAYLOAD)
totalBytes = IPCF_MAX_PAYLOAD;
// Determine per-sector size for cache invalidation.
uint32_t sectorLen = totalBytes / (uint32_t) numSectors;
if (sectorLen == 0)
sectorLen = IPCF_SECTOR_SIZE;
// Invalidate cached sectors in the range.
for (int s = 0; s < numSectors; s++)
cacheInvalidate(filename, fileOffset + (uint32_t) s * sectorLen);
// Allocate DMA-capable receive buffer.
uint8_t *burstBuf = (uint8_t *) heap_caps_malloc(totalBytes, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
if (!burstBuf)
{
sendErrResp(fspi, IPCF_STATUS_ERR, command);
return false;
}
// Receive write payload (totalBytes + CRC32).
if (receiveWritePayload(fspi, burstBuf, totalBytes, 0) != ESP_OK)
{
free(burstBuf);
sendErrResp(fspi, IPCF_STATUS_CRCFAIL, command);
return false;
}
// Open file and write all sectors.
std::string filepath = SD_CARD_MOUNT_POINT "/";
filepath.append(filename);
FILE *f = fopen(filepath.c_str(), "r+");
if (!f)
{
free(burstBuf);
sendErrResp(fspi, IPCF_STATUS_NOTFOUND, command);
return false;
}
fseek(f, (long) fileOffset, SEEK_SET);
size_t written = fwrite(burstBuf, 1, totalBytes, f);
fflush(f);
fclose(f);
free(burstBuf);
if (written != totalBytes)
{
ESP_LOGE(SDTAG, "writeBurst: short write %zu/%lu", written, totalBytes);
sendErrResp(fspi, IPCF_STATUS_ERR, command);
return false;
}
// Success response.
t_IpcFrameHdr hdr = {};
hdr.frameType = IPCF_TYPE_RESPONSE;
hdr.command = command;
hdr.status = IPCF_STATUS_OK;
hdr.payloadLen = 0;
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_CRC_SIZE;
esp_err_t ret = fspi.sendBinaryResp(&hdr, NULL, 0, respSize, portMAX_DELAY);
return (ret == ESP_OK);
}
// ---------------------------------------------------------------------------
// writeFileViaSPI — receive an entire file from RP2350 in chunks.
// Each chunk arrives as: [command frame] [payload + CRC32] pairs.
// The file is created/overwritten at the path specified in frame.filename.
// ---------------------------------------------------------------------------
bool SDCard::writeFileViaSPI(const t_IpcFrameHdr &frame, FSPI &fspi)
{
const char *filename = frame.filename;
uint32_t fileOffset = frame.fileOffset; // byte offset of this chunk
uint32_t chunkLen = frame.payloadLen; // bytes to receive
uint8_t command = frame.command;
bool lastChunk = (frame.flags & IPCF_FLAG_LAST_CHUNK) != 0;
// Allocate DMA receive buffer.
uint8_t *chunkBuf = (uint8_t *) heap_caps_malloc(chunkLen, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
if (!chunkBuf)
{
sendErrResp(fspi, IPCF_STATUS_ERR, command);
return false;
}
// Receive payload.
if (receiveWritePayload(fspi, chunkBuf, chunkLen, 0) != ESP_OK)
{
free(chunkBuf);
sendErrResp(fspi, IPCF_STATUS_CRCFAIL, command);
return false;
}
// Open for write (first chunk: create/truncate; subsequent chunks: append).
std::string filepath = SD_CARD_MOUNT_POINT "/";
filepath.append(filename);
FILE *f = fopen(filepath.c_str(), fileOffset == 0 ? "w" : "r+");
if (!f)
{
free(chunkBuf);
sendErrResp(fspi, IPCF_STATUS_NOTFOUND, command);
return false;
}
fseek(f, (long) fileOffset, SEEK_SET);
size_t written = fwrite(chunkBuf, 1, chunkLen, f);
fflush(f);
fclose(f);
free(chunkBuf);
if (written != chunkLen)
{
sendErrResp(fspi, IPCF_STATUS_ERR, command);
return false;
}
// Send status response.
t_IpcFrameHdr hdr = {};
hdr.frameType = IPCF_TYPE_RESPONSE;
hdr.command = command;
hdr.status = IPCF_STATUS_OK;
hdr.payloadLen = 0;
hdr.flags = lastChunk ? IPCF_FLAG_LAST_CHUNK : 0;
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_CRC_SIZE;
esp_err_t ret = fspi.sendBinaryResp(&hdr, NULL, 0, respSize, portMAX_DELAY);
return (ret == ESP_OK);
}
// ---------------------------------------------------------------------------
// storeRP2350Info — receive the RP2350 partition header sent by INF command.
// The payload is already in fspi.ipcCmdBuf (received during command transaction).
// Stores a copy in ioBuf for WiFi to access (web interface).
// ---------------------------------------------------------------------------
bool SDCard::storeRP2350Info(const t_IpcFrameHdr &frame, FSPI &fspi)
{
// The INF write payload was received in the second SPI transaction by
// FSPI_sendBinaryCmd() on the RP2350 side, which means the ESP32's
// receiveWritePayload() must be called to get it.
uint32_t payloadSize = frame.payloadLen;
if (payloadSize == 0 || payloadSize > IPCF_MAX_PAYLOAD)
{
sendErrResp(fspi, IPCF_STATUS_ERR, frame.command);
return false;
}
uint8_t *infoBuf = (uint8_t *) malloc(payloadSize);
if (!infoBuf)
{
sendErrResp(fspi, IPCF_STATUS_ERR, frame.command);
return false;
}
if (receiveWritePayload(fspi, infoBuf, payloadSize, 0) != ESP_OK)
{
free(infoBuf);
sendErrResp(fspi, IPCF_STATUS_CRCFAIL, frame.command);
return false;
}
// Store partition header + extended core config for WiFi subsystem access.
// rp2350Header points to wifiCtrl.run.rp2350FlashHeader.
if (rp2350Header && payloadSize >= sizeof(t_FlashPartitionHeader))
{
memcpy(rp2350Header, infoBuf, sizeof(t_FlashPartitionHeader));
// Extended payload fields — extract via WiFi's wifiCtrl.run fields directly.
// The rp2350Header pointer points to wifiCtrl.run.rp2350FlashHeader, and the
// fields after it (rp2350CpuFreq, rp2350PsramFreq, etc.) are at known offsets.
// Backwards-compatible: only extract fields present in the payload.
if (payloadSize > sizeof(t_FlashPartitionHeader))
{
t_FlashInfoPayload *info = (t_FlashInfoPayload *) infoBuf;
// Core config fields (cpufreq..psramSize) — present in all extended payloads.
const size_t coreExtSize = offsetof(t_FlashInfoPayload, hostClkHz);
if (payloadSize >= coreExtSize)
{
int32_t *extra = (int32_t *) (rp2350Header + 1);
extra[0] = info->cpufreq;
extra[1] = info->psramfreq;
extra[2] = info->voltage;
uint32_t *extra32 = (uint32_t *) &extra[3];
extra32[0] = info->flashSize;
extra32[1] = info->psramSize;
}
// Host clock + emulation speed — added after psramSize.
if (payloadSize >= offsetof(t_FlashInfoPayload, driverSummary))
{
int32_t *extra = (int32_t *) (rp2350Header + 1);
uint32_t *extra32 = (uint32_t *) &extra[3];
extra32[2] = info->hostClkHz; // rp2350HostClkHz
extra32[3] = info->emulSpeedHz; // rp2350EmulSpeedHz
}
// Driver summary — last field, variable-length content.
uint32_t *extra32base = (uint32_t *) ((int32_t *)(rp2350Header + 1) + 3);
char *drvSummary = (char *) &extra32base[4]; // After flashSize, psramSize, hostClkHz, emulSpeedHz
if (payloadSize >= sizeof(t_FlashInfoPayload))
{
memcpy(drvSummary, info->driverSummary, FLASHHDR_DRIVER_SUMMARY_SIZE);
drvSummary[FLASHHDR_DRIVER_SUMMARY_SIZE - 1] = '\0';
}
else
{
drvSummary[0] = '\0';
}
}
}
free(infoBuf);
// Send success response.
t_IpcFrameHdr hdr = {};
hdr.frameType = IPCF_TYPE_RESPONSE;
hdr.command = frame.command;
hdr.status = IPCF_STATUS_OK;
hdr.payloadLen = 0;
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_CRC_SIZE;
esp_err_t ret = fspi.sendBinaryResp(&hdr, NULL, 0, respSize, portMAX_DELAY);
return (ret == ESP_OK);
}