874 lines
31 KiB
C++
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 = §orCache[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);
|
|
}
|