mch2022-talktopics/components/appfs/appfs.c

816 lines
27 KiB
C

/*
Theory of operation:
An appfs filesystem is meant to store executable applications (=ESP32 programs) alongside other
data that is mmap()-able as a contiguous file.
Appfs does that by making sure the files rigidly adhere to the 64K-page-structure (called a 'sector'
in this description) as predicated by the ESP32s MMU. This way, every file can be mmap()'ed into a
contiguous region or ran as an ESP32 application. (For the future, maybe: Smaller files can be stored
in parts of a 64K page, as long as all are contiguous and none cross any 64K boundaries.
What about fragmentation tho?)
Because of these reasons, only a few operations are available:
- Creating a file. This needs the filesize to be known beforehand; a file cannot change size afterwards.
- Modifying a file. This follows the same rules as spi_flash_* because it maps directly to the underlying flash.
- Deleting a file
- Mmap()ping a file
This makes the interface to appfs more akin to the partition interface than to a real filesystem.
At the moment, appfs is not yet tested with encrypted flash; compatibility is unknown.
Filesystem meta-info is stored using the first sector: there are 2 32K half-sectors there with management
info. Each has a serial and a checksum. The sector with the highest serial and a matching checksum is
taken as current; the data will ping-pong between the sectors. (And yes, this means the pages in these
sectors will be rewritten every time a file is added/removed. Appfs is built with the assumption that
it's a mostly store-only filesystem and apps will only change every now and then. The flash chips
connected to the ESP32 chips usually can do up to 100.000 erases, so for most purposes the lifetime of
the flash with appfs on it exceeds the lifetime of the product.)
Appfs assumes a partition of 16MiB or less, allowing for 256 128-byte sector descriptors to be stored in
the management half-sectors. The first descriptor is a header used for filesystem meta-info.
Metainfo is stored per sector; each sector descriptor contains a zero-terminated filename (no
directories are supported, but '/' is an usable character), the size of the file and a pointer to the
next entry for the file if needed. The filename is only set for the first sector; it is all zeroes
(actually: ignored) for other entries.
Integrity of the meta-info is guaranteed: the file system will never be in a state where sectors are
lost or anything. Integrity of data is *NOT* guaranteed: on power loss, data may be half-written,
contain sectors with only 0xff, and so on. It's up to the user to take care of this. However, files that
are not written to do not run the risk of getting corrupted.
With regards to this code: it is assumed that an ESP32 will only have one appfs on flash, so everything
is implemented as a singleton.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <alloca.h>
#include <rom/crc.h>
#include "esp_spi_flash.h"
#include "esp_partition.h"
#include "esp_log.h"
#include "esp_err.h"
#include "appfs.h"
#include "rom/cache.h"
#include "sdkconfig.h"
#if !CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED
#error "Appfs will not work with SPI flash dangerous regions checking. Please use 'make menuconfig' to enable writing to dangerous regions."
#endif
static const char *TAG = "appfs";
#define APPFS_SECTOR_SZ SPI_FLASH_MMU_PAGE_SIZE
#define APPFS_META_SZ (APPFS_SECTOR_SZ/2)
#define APPFS_META_CNT 2
#define APPFS_META_DESC_SZ 128
#define APPFS_PAGES 255
#define APPFS_MAGIC "AppFsDsc"
#define APPFS_USE_FREE 0xff //No file allocated here
#define APPFS_ILLEGAL 0x55 //Sector cannot be used (usually because it's outside the partition)
#define APPFS_USE_DATA 0 //Sector is in use for data
typedef struct __attribute__ ((__packed__)) {
uint8_t magic[8]; //must be AppFsDsc
uint32_t serial;
uint32_t crc32;
uint8_t reserved[128-16];
} AppfsHeader;
typedef struct __attribute__ ((__packed__)) {
char name[112]; //Only set for 1st sector of file. Rest has name set to 0xFF 0xFF ...
uint32_t size; //in bytes
uint8_t next; //next page containing the next 64K of the file; 0 if no next page (Because allocation always starts at 0 and pages can't refer to a lower page, 0 can never occur normally)
uint8_t used; //one of APPFS_USE_*
uint8_t reserved[10];
} AppfsPageInfo;
typedef struct __attribute__ ((__packed__)) {
AppfsHeader hdr;
AppfsPageInfo page[APPFS_PAGES];
} AppfsMeta;
static int appfsActiveMeta=0; //number of currently active metadata half-sector (0 or 1)
static const AppfsMeta *appfsMeta=NULL; //mmap'ed flash
#ifndef BOOTLOADER_BUILD
static const esp_partition_t *appfsPart=NULL;
static spi_flash_mmap_handle_t appfsMetaMmapHandle;
#else
static uint32_t appfsPartOffset=0;
#endif
static int page_in_part(int page) {
#ifndef BOOTLOADER_BUILD
return ((page+1)*APPFS_SECTOR_SZ < appfsPart->size);
#else
return 1;
#endif
}
//Find active meta half-sector. Updates appfsActiveMeta to the most current one and returns ESP_OK success.
//Returns ESP_ERR_NOT_FOUND when no active metasector is found.
static esp_err_t findActiveMeta() {
int validSec=0; //bitmap of valid sectors
uint32_t serial[APPFS_META_CNT]={0};
AppfsHeader hdr;
for (int sec=0; sec<APPFS_META_CNT; sec++) {
//Read header
memcpy(&hdr, &appfsMeta[sec].hdr, sizeof(AppfsHeader));
if (memcmp(hdr.magic, APPFS_MAGIC, 8)==0) {
//Save serial
serial[sec]=hdr.serial;
//Save and zero CRC
uint32_t expectedCrc=hdr.crc32;
hdr.crc32=0;
uint32_t crc=0;
crc=crc32_le(crc, (const uint8_t *)&hdr, APPFS_META_DESC_SZ);
for (int j=0; j<APPFS_PAGES; j++) {
crc=crc32_le(crc, (const uint8_t *)&appfsMeta[sec].page[j], APPFS_META_DESC_SZ);
}
if (crc==expectedCrc) {
validSec|=(1<<sec);
} else {
ESP_LOGD(TAG, "Meta sector %d does not have a valid CRC (have %X expected %X.", sec, crc, expectedCrc);
}
} else {
ESP_LOGD(TAG, "Meta sector %d does not have a valid magic header.", sec);
}
}
//Here, validSec should be a bitmap of sectors that are valid, while serials[] should contain their
//serials.
int best=-1;
for (int sec=0; sec<APPFS_META_CNT; sec++) {
if (validSec&(1<<sec)) {
if (best==-1 || serial[sec]>serial[best]) best=sec;
}
}
ESP_LOGI(TAG, "Meta page 0: %svalid (serial %d)", (validSec&1)?"":"in", serial[0]);
ESP_LOGI(TAG, "Meta page 1: %svalid (serial %d)", (validSec&2)?"":"in", serial[1]);
//'best' here is either still -1 (no valid sector found) or the sector with the highest valid serial.
if (best==-1) {
ESP_LOGI(TAG, "No valid page found.");
//Eek! Nothing found!
return ESP_ERR_NOT_FOUND;
} else {
ESP_LOGI(TAG, "Using page %d as current.", best);
}
appfsActiveMeta=best;
return ESP_OK;
}
#ifdef BOOTLOADER_BUILD
IRAM_ATTR
#endif
static int appfsGetFirstPageFor(const char *filename) {
for (int j=0; j<APPFS_PAGES; j++) {
if (appfsMeta[appfsActiveMeta].page[j].used==APPFS_USE_DATA && strcmp(appfsMeta[appfsActiveMeta].page[j].name, filename)==0) {
return j;
}
}
//Nothing found.
return APPFS_INVALID_FD;
}
bool appfsFdValid(int fd) {
if (fd<0 || fd>=APPFS_PAGES) return false;
if (appfsMeta[appfsActiveMeta].page[(int)fd].used!=APPFS_USE_DATA) return false;
if (appfsMeta[appfsActiveMeta].page[(int)fd].name[0]==0xff) return false;
return true;
}
int appfsExists(const char *filename) {
return (appfsGetFirstPageFor(filename)==-1)?0:1;
}
appfs_handle_t appfsOpen(const char *filename) {
return appfsGetFirstPageFor(filename);
}
void appfsClose(appfs_handle_t handle) {
//Not needed in this implementation. Added for possible later use (concurrency?)
}
void appfsEntryInfo(appfs_handle_t fd, const char **name, int *size) {
if (name) *name=appfsMeta[appfsActiveMeta].page[fd].name;
if (size) *size=appfsMeta[appfsActiveMeta].page[fd].size;
}
appfs_handle_t appfsNextEntry(appfs_handle_t fd) {
if (fd==APPFS_INVALID_FD) {
fd=0;
} else {
fd++;
}
if (fd>=APPFS_PAGES || fd<0) return APPFS_INVALID_FD;
while (appfsMeta[appfsActiveMeta].page[fd].used!=APPFS_USE_DATA || appfsMeta[appfsActiveMeta].page[fd].name[0]==0xff) {
fd++;
if (fd>=APPFS_PAGES) return APPFS_INVALID_FD;
}
return fd;
}
size_t appfsGetFreeMem() {
size_t ret=0;
for (int i=0; i<APPFS_PAGES; i++) {
if (appfsMeta[appfsActiveMeta].page[i].used==APPFS_USE_FREE && page_in_part(i)) {
ret+=APPFS_SECTOR_SZ;
}
}
return ret;
}
#ifdef BOOTLOADER_BUILD
/*
Note that IRAM_ATTR is used here to make sure the functions that are used when/after the app loadable
segments are loaded, won't be overwritten. The IRAM_ATTR in the bootloader code dumps the function
in the loader segment instead of in random IRAM.
*/
#include "bootloader_flash_priv.h"
#include "soc/soc.h"
#include "esp_cpu.h"
#include "soc/rtc.h"
#include "soc/dport_reg.h"
//These can be overridden if we need a custom implementation of bootloader_mmap/munmap.
IRAM_ATTR __attribute__ ((weak)) const void *appfs_bootloader_mmap(uint32_t src_addr, uint32_t size) {
return bootloader_mmap(src_addr, size);
}
IRAM_ATTR __attribute__ ((weak)) void appfs_bootloader_munmap(const void *mapping) {
bootloader_munmap(mapping);
}
IRAM_ATTR __attribute__ ((weak)) esp_err_t appfs_bootloader_flash_read(size_t src_addr, void *dest, size_t size, bool allow_decrypt) {
return bootloader_flash_read(src_addr, dest, size, allow_decrypt);
}
static uint8_t next_page_for[256];
static int keep_meta_mapped=0;
esp_err_t appfsBlInit(uint32_t offset, uint32_t len) {
//Compile-time sanity check on size of structs
_Static_assert(sizeof(AppfsHeader)==APPFS_META_DESC_SZ, "sizeof AppfsHeader != 128bytes");
_Static_assert(sizeof(AppfsPageInfo)==APPFS_META_DESC_SZ, "sizeof AppfsPageInfo != 128bytes");
_Static_assert(sizeof(AppfsMeta)==APPFS_META_SZ, "sizeof AppfsMeta != APPFS_META_SZ");
//Map meta page
appfsMeta=appfs_bootloader_mmap(offset, APPFS_SECTOR_SZ);
if (!appfsMeta) return ESP_ERR_NOT_FOUND;
if (findActiveMeta()!=ESP_OK) {
//No valid metadata half-sector found. Initialize the first sector.
ESP_LOGE(TAG, "No valid meta info found. Bailing out.");
return ESP_ERR_NOT_FOUND;
}
for (int i=0; i<256; i++) {
next_page_for[i]=appfsMeta[appfsActiveMeta].page[i].next;
}
appfsPartOffset=offset;
keep_meta_mapped=1;
ESP_LOGD(TAG, "Initialized.");
return ESP_OK;
}
void appfsBlDeinit() {
ESP_LOGI(TAG, "Appfs deinit");
if (appfsMeta) appfs_bootloader_munmap(appfsMeta);
appfsMeta=NULL;
keep_meta_mapped=0;
}
#define MMU_BLOCK0_VADDR 0x3f400000
#define MMU_BLOCK50_VADDR 0x3f720000
IRAM_ATTR esp_err_t appfsBlMapRegions(int fd, AppfsBlRegionToMap *regions, int noRegions) {
uint8_t pages[255];
int page_count=0;
int page=fd;
do {
pages[page_count++]=page;
page=next_page_for[page];
} while (page!=0 && page_count<255);
if (appfsMeta) appfs_bootloader_munmap(appfsMeta);
appfsMeta=NULL;
Cache_Read_Disable( 0 );
Cache_Flush( 0 );
for (int i = 0; i < DPORT_FLASH_MMU_TABLE_SIZE; i++) {
DPORT_PRO_FLASH_MMU_TABLE[i] = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
}
for (int i=0; i<noRegions; i++) {
uint32_t p=regions[i].fileAddr/APPFS_SECTOR_SZ;
uint32_t d=regions[i].mapAddr&~(APPFS_SECTOR_SZ-1);
for (uint32_t a=0; a<regions[i].length; a+=APPFS_SECTOR_SZ) {
ESP_LOGI(TAG, "Flash mmap seg %d: %X from %X", i, d, appfsPartOffset+((pages[p]+1)*APPFS_SECTOR_SZ));
for (int cpu=0; cpu<2; cpu++) {
int e = cache_flash_mmu_set(cpu, 0, d, appfsPartOffset+((pages[p]+1)*APPFS_SECTOR_SZ), 64, 1);
if (e != 0) {
ESP_LOGE(TAG, "cache_flash_mmu_set failed for cpu %d: %d", cpu, e);
Cache_Read_Enable(0);
return ESP_ERR_NO_MEM;
}
}
d+=APPFS_SECTOR_SZ;
p++;
if (p>page_count) return ESP_ERR_NO_MEM;
}
}
DPORT_REG_CLR_BIT( DPORT_PRO_CACHE_CTRL1_REG, (DPORT_PRO_CACHE_MASK_IRAM0) | (DPORT_PRO_CACHE_MASK_IRAM1 & 0) | (DPORT_PRO_CACHE_MASK_IROM0 & 0) | DPORT_PRO_CACHE_MASK_DROM0 | DPORT_PRO_CACHE_MASK_DRAM1 );
DPORT_REG_CLR_BIT( DPORT_APP_CACHE_CTRL1_REG, (DPORT_APP_CACHE_MASK_IRAM0) | (DPORT_APP_CACHE_MASK_IRAM1 & 0) | (DPORT_APP_CACHE_MASK_IROM0 & 0) | DPORT_APP_CACHE_MASK_DROM0 | DPORT_APP_CACHE_MASK_DRAM1 );
Cache_Read_Enable( 0 );
return ESP_OK;
}
IRAM_ATTR void* appfsBlMmap(int fd) {
if (appfsMeta) appfs_bootloader_munmap(appfsMeta);
appfsMeta=NULL;
//Bootloader_mmap only allows mapping of one consecutive memory range. We need more than that, so we essentially
//replicate the function here.
Cache_Read_Disable(0);
Cache_Flush(0);
int page=fd;
for (int i=0; i<50; i++) {
// ESP_LOGI(TAG, "Mapping flash addr %X to mem addr %X for page %d", appfsPartOffset+((pages[i]+1)*APPFS_SECTOR_SZ), MMU_BLOCK0_VADDR+(i*APPFS_SECTOR_SZ), pages[i]);
int e = cache_flash_mmu_set(0, 0, MMU_BLOCK0_VADDR+(i*APPFS_SECTOR_SZ),
appfsPartOffset+((page+1)*APPFS_SECTOR_SZ), 64, 1);
if (e != 0) {
ESP_LOGE(TAG, "cache_flash_mmu_set failed: %d", e);
Cache_Read_Enable(0);
return NULL;
}
page=next_page_for[page];
if (page==0) break;
}
Cache_Read_Enable(0);
return (void *)(MMU_BLOCK0_VADDR);
}
IRAM_ATTR void appfsBlMunmap() {
/* Full MMU reset */
Cache_Read_Disable(0);
Cache_Flush(0);
mmu_init(0);
if (keep_meta_mapped) {
appfsMeta=appfs_bootloader_mmap(appfsPartOffset, APPFS_SECTOR_SZ);
}
}
IRAM_ATTR esp_err_t appfs_bootloader_read(int fd, size_t src_addr, void *dest, size_t size) {
int page=fd;
int pos=0;
int have_read=0;
uint8_t *destp=(uint8_t*)dest;
int offset_in_page=src_addr&(APPFS_SECTOR_SZ-1);
for (int i=0; i<255; i++) {
if (pos+APPFS_SECTOR_SZ-1>=src_addr) {
size_t rsize=APPFS_SECTOR_SZ-offset_in_page;
if (rsize>size) rsize=size;
esp_err_t r=appfs_bootloader_flash_read(appfsPartOffset+((page+1)*APPFS_SECTOR_SZ)+offset_in_page, destp+have_read, rsize, true);
if (r!=ESP_OK) return r;
offset_in_page=0;
have_read+=rsize;
if (have_read>=size) {
return ESP_OK;
}
}
page=next_page_for[page];
if (page==0) break;
pos+=APPFS_SECTOR_SZ;
}
return ESP_OK;
}
#else //so if !BOOTLOADER_BUILD
//Modifies the header in hdr to the correct crc and writes it to meta info no metano.
//Assumes the serial etc is in order already, and the header section for metano has been erased.
static esp_err_t writeHdr(AppfsHeader *hdr, int metaNo) {
hdr->crc32=0;
uint32_t crc=0;
crc=crc32_le(crc, (const uint8_t *)hdr, APPFS_META_DESC_SZ);
for (int j=0; j<APPFS_PAGES; j++) {
crc=crc32_le(crc, (const uint8_t *)&appfsMeta[metaNo].page[j], APPFS_META_DESC_SZ);
}
hdr->crc32=crc;
return esp_partition_write(appfsPart, metaNo*APPFS_META_SZ, hdr, sizeof(AppfsHeader));
}
//Kill all existing filesystem metadata and re-initialize the fs.
static esp_err_t initializeFs() {
esp_err_t r;
//Kill management sector
r=esp_partition_erase_range(appfsPart, 0, APPFS_SECTOR_SZ);
if (r!=ESP_OK) return r;
//All the data pages are now set to 'free'. Add a header that makes the entire mess valid.
AppfsHeader hdr;
memset(&hdr, 0xff, sizeof(hdr));
memcpy(hdr.magic, APPFS_MAGIC, 8);
hdr.serial=0;
//Mark pages outside of partition as invalid.
int lastPage=(appfsPart->size/APPFS_SECTOR_SZ);
for (int j=lastPage; j<APPFS_PAGES; j++) {
AppfsPageInfo pi;
memset(&pi, 0xff, sizeof(pi));
pi.used=APPFS_ILLEGAL;
r=esp_partition_write(appfsPart, 0*APPFS_META_SZ+(j+1)*APPFS_META_DESC_SZ, &pi, sizeof(pi));
if (r!=ESP_OK) return r;
}
writeHdr(&hdr, 0);
ESP_LOGI(TAG, "Re-initialized appfs: %d pages", lastPage);
//Officially, we should also write the CRCs... we don't do this here because during the
//runtime of this, the CRCs aren't checked and when the device reboots, it'll re-initialize
//the fs anyway.
appfsActiveMeta=0;
return ESP_OK;
}
esp_err_t appfsInit(int type, int subtype) {
esp_err_t r;
//Compile-time sanity check on size of structs
_Static_assert(sizeof(AppfsHeader)==APPFS_META_DESC_SZ, "sizeof AppfsHeader != 128bytes");
_Static_assert(sizeof(AppfsPageInfo)==APPFS_META_DESC_SZ, "sizeof AppfsPageInfo != 128bytes");
_Static_assert(sizeof(AppfsMeta)==APPFS_META_SZ, "sizeof AppfsMeta != APPFS_META_SZ");
//Find the indicated partition
appfsPart=esp_partition_find_first(type, subtype, NULL);
if (!appfsPart) return ESP_ERR_NOT_FOUND;
//Memory map the appfs header so we can Do Stuff with it
r=esp_partition_mmap(appfsPart, 0, APPFS_SECTOR_SZ, SPI_FLASH_MMAP_DATA, (const void**)&appfsMeta, &appfsMetaMmapHandle);
if (r!=ESP_OK) return r;
if (findActiveMeta()!=ESP_OK) {
//No valid metadata half-sector found. Initialize the first sector.
ESP_LOGE(TAG, "No valid meta info found. Re-initializing fs.");
initializeFs();
}
ESP_LOGD(TAG, "Initialized.");
return ESP_OK;
}
static esp_err_t writePageInfo(int newMeta, int page, AppfsPageInfo *pi) {
return esp_partition_write(appfsPart, newMeta*APPFS_META_SZ+(page+1)*APPFS_META_DESC_SZ, pi, sizeof(AppfsPageInfo));
}
//This essentially writes a new meta page without any references to the file indicated.
esp_err_t appfsDeleteFile(const char *filename) {
esp_err_t r;
int next=-1;
int newMeta;
AppfsHeader hdr;
AppfsPageInfo pi;
//See if we actually need to do something
if (!appfsExists(filename)) return 0;
//Create a new management sector
newMeta=(appfsActiveMeta+1)%APPFS_META_CNT;
r=esp_partition_erase_range(appfsPart, newMeta*APPFS_META_SZ, APPFS_META_SZ);
if (r!=ESP_OK) return r;
//Prepare header
memcpy(&hdr, &appfsMeta[appfsActiveMeta].hdr, sizeof(hdr));
hdr.serial++;
hdr.crc32=0;
for (int j=0; j<APPFS_PAGES; j++) {
int needDelete=0;
//Grab old page info from current meta sector
memcpy(&pi, &appfsMeta[appfsActiveMeta].page[j], sizeof(pi));
if (next==-1) {
if (pi.used==APPFS_USE_DATA && strcmp(pi.name, filename)==0) {
needDelete=1;
next=pi.next;
}
} else if (next==0) {
//File is killed entirely. No need to look for anything.
} else {
//Look for next sector of file
if (j==next) {
needDelete=1;
next=pi.next;
}
}
if (needDelete) {
//Page info is 0xff anyway. No need to explicitly write that.
} else {
r=writePageInfo(newMeta, j, &pi);
if (r!=ESP_OK) return r;
}
}
r=writeHdr(&hdr, newMeta);
appfsActiveMeta=newMeta;
return r;
}
//This essentially writes a new meta page with the name changed.
//If a file is found with the name, we also delete it.
esp_err_t appfsRename(const char *from, const char *to) {
esp_err_t r;
int newMeta;
AppfsHeader hdr;
AppfsPageInfo pi;
//See if we actually need to do something
if (!appfsExists(from)) return ESP_FAIL;
//Create a new management sector
newMeta=(appfsActiveMeta+1)%APPFS_META_CNT;
r=esp_partition_erase_range(appfsPart, newMeta*APPFS_META_SZ, APPFS_META_SZ);
if (r!=ESP_OK) return r;
//Prepare header
memcpy(&hdr, &appfsMeta[appfsActiveMeta].hdr, sizeof(hdr));
hdr.serial++;
hdr.crc32=0;
int nextDelete=-1;
for (int j=0; j<APPFS_PAGES; j++) {
//Grab old page info from current meta sector
memcpy(&pi, &appfsMeta[appfsActiveMeta].page[j], sizeof(pi));
int needDelete=0;
if (nextDelete==-1 && pi.used==APPFS_USE_DATA && strcmp(pi.name, to)==0) {
//First page of the dest file. We need to delete this!
nextDelete=pi.next;
needDelete=1;
} else if (nextDelete==j) {
//A page in the file to be deleted.
nextDelete=pi.next;
needDelete=1;
} else if (pi.used==APPFS_USE_DATA && strcmp(pi.name, from)==0) {
//Found old name. Rename to new.
strncpy(pi.name, to, sizeof(pi.name));
pi.name[sizeof(pi.name)-1]=0;
}
//If hdr needs deletion, leave it at 0xfffff...
if (!needDelete) {
r=writePageInfo(newMeta, j, &pi);
if (r!=ESP_OK) return r;
}
}
r=writeHdr(&hdr, newMeta);
appfsActiveMeta=newMeta;
return r;
}
//Allocate space for a new file. Will kill any existing files if needed.
//Warning: may kill old file but not create new file if new file won't fit on fs, even with old file removed.
//ToDo: in that case, fail before deleting file.
esp_err_t appfsCreateFile(const char *filename, size_t size, appfs_handle_t *handle) {
esp_err_t r;
//If there are any references to this file, kill 'em.
appfsDeleteFile(filename);
ESP_LOGD(TAG, "Creating new file '%s'", filename);
//Figure out what pages to reserve for the file, and the next link structure.
uint8_t nextForPage[APPFS_PAGES]; //Next page if used for file, APPFS_PAGES if not
//Mark all pages as unused for file.
for (int j=0; j<APPFS_PAGES; j++) nextForPage[j]=APPFS_PAGES;
//Find pages where we can store data for the file.
int first=-1, prev=-1;
int sizeLeft=size;
for (int j=0; j<APPFS_PAGES; j++) {
if (appfsMeta[appfsActiveMeta].page[j].used==APPFS_USE_FREE && page_in_part(j)) {
ESP_LOGD(TAG, "Using page %d...", j);
if (prev==-1) {
first=j; //first free page; save to store name here.
} else {
nextForPage[prev]=j; //mark prev page to go here
}
nextForPage[j]=0; //end of file... for now.
prev=j;
sizeLeft-=APPFS_SECTOR_SZ;
if (sizeLeft<=0) break;
}
}
if (sizeLeft>0) {
//Eek! Can't allocate enough space!
ESP_LOGD(TAG, "Not enough free space!");
return ESP_ERR_NO_MEM;
}
//Re-write a new meta page but with file allocated
int newMeta=(appfsActiveMeta+1)%APPFS_META_CNT;
ESP_LOGD(TAG, "Re-writing meta data to meta page %d...", newMeta);
r=esp_partition_erase_range(appfsPart, newMeta*APPFS_META_SZ, APPFS_META_SZ);
if (r!=ESP_OK) return r;
//Prepare header
AppfsHeader hdr;
memcpy(&hdr, &appfsMeta[appfsActiveMeta].hdr, sizeof(hdr));
hdr.serial++;
hdr.crc32=0;
for (int j=0; j<APPFS_PAGES; j++) {
AppfsPageInfo pi;
if (nextForPage[j]!=APPFS_PAGES) {
//This is part of the file. Rewrite page data to indicate this.
memset(&pi, 0xff, sizeof(pi));
if (j==first) {
//First page. Copy name and size.
strcpy(pi.name, filename);
pi.size=size;
}
pi.used=APPFS_USE_DATA;
pi.next=nextForPage[j];
} else {
//Grab old page info from current meta sector
memcpy(&pi, &appfsMeta[appfsActiveMeta].page[j], sizeof(pi));
}
if (pi.used!=APPFS_USE_FREE) {
r=writePageInfo(newMeta, j, &pi);
if (r!=ESP_OK) return r;
}
}
//Write header and make active.
r=writeHdr(&hdr, newMeta);
appfsActiveMeta=newMeta;
if (handle) *handle=first;
ESP_LOGD(TAG, "Re-writing meta data done.");
return r;
}
esp_err_t appfsMmap(appfs_handle_t fd, size_t offset, size_t len, const void** out_ptr,
spi_flash_mmap_memory_t memory, spi_flash_mmap_handle_t* out_handle) {
esp_err_t r;
int page=(int)fd;
if (!appfsFdValid(page)) return ESP_ERR_NOT_FOUND;
ESP_LOGD(TAG, "Mmapping file %s, offset %d, size %d", appfsMeta[appfsActiveMeta].page[page].name, offset, len);
if (appfsMeta[appfsActiveMeta].page[page].size < (offset+len)) {
ESP_LOGD(TAG, "Can't map file: trying to map byte %d in file of len %d\n", (offset+len), appfsMeta[appfsActiveMeta].page[page].size);
return ESP_ERR_INVALID_SIZE;
}
int dataStartPage=(appfsPart->address/SPI_FLASH_MMU_PAGE_SIZE)+1;
while (offset >= APPFS_SECTOR_SZ) {
page=appfsMeta[appfsActiveMeta].page[page].next;
offset-=APPFS_SECTOR_SZ;
ESP_LOGD(TAG, "Skipping a page (to page %d), remaining offset 0x%X", page, offset);
}
int *pages=alloca(sizeof(int)*((len/APPFS_SECTOR_SZ)+1));
int nopages=0;
size_t mappedlen=0;
while(len>mappedlen) {
pages[nopages++]=page+dataStartPage;
ESP_LOGD(TAG, "Mapping page %d (part offset %d).", page, dataStartPage);
page=appfsMeta[appfsActiveMeta].page[page].next;
mappedlen+=APPFS_SECTOR_SZ;
}
r=spi_flash_mmap_pages(pages, nopages, memory, out_ptr, out_handle);
if (r!=ESP_OK) {
ESP_LOGD(TAG, "Can't map file: pi_flash_mmap_pages returned %d\n", r);
return r;
}
*out_ptr=((uint8_t*)*out_ptr)+offset;
return ESP_OK;
}
void appfsMunmap(spi_flash_mmap_handle_t handle) {
spi_flash_munmap(handle);
}
//Just mmaps and memcpys the data. Maybe not the fastest ever, but hey, if you want that you should mmap
//and read from the flash cache memory area yourself.
esp_err_t appfsRead(appfs_handle_t fd, size_t start, void *buf, size_t len) {
const void *flash;
spi_flash_mmap_handle_t handle;
esp_err_t r=appfsMmap(fd, start, len, &flash, SPI_FLASH_MMAP_DATA, &handle);
if (r!=ESP_OK) return r;
memcpy(buf, flash, len);
spi_flash_munmap(handle);
return ESP_OK;
}
esp_err_t appfsErase(appfs_handle_t fd, size_t start, size_t len) {
esp_err_t r;
int page=(int)fd;
if (!appfsFdValid(page)) return ESP_ERR_NOT_FOUND;
//Bail out if trying to erase past the file end.
//Allow erases past the end of the file but still within the page reserved for the file.
int roundedSize=(appfsMeta[appfsActiveMeta].page[page].size+(APPFS_SECTOR_SZ-1))&(~(APPFS_SECTOR_SZ-1));
if (roundedSize < (start+len)) {
return ESP_ERR_INVALID_SIZE;
}
//Find initial page
while (start >= APPFS_SECTOR_SZ) {
page=appfsMeta[appfsActiveMeta].page[page].next;
start-=APPFS_SECTOR_SZ;
}
//Page now is the initial page. Start is the offset into the page we need to start at.
while (len>0) {
size_t size=len;
//Make sure we do not go over a page boundary
if ((size+start)>APPFS_SECTOR_SZ) size=APPFS_SECTOR_SZ-start;
ESP_LOGD(TAG, "Erasing page %d offset 0x%X size 0x%X", page, start, size);
r=esp_partition_erase_range(appfsPart, (page+1)*APPFS_SECTOR_SZ+start, size);
if (r!=ESP_OK) return r;
page=appfsMeta[appfsActiveMeta].page[page].next;
len-=size;
start=0; //offset is not needed anymore
}
return ESP_OK;
}
esp_err_t appfsWrite(appfs_handle_t fd, size_t start, uint8_t *buf, size_t len) {
esp_err_t r;
int page=(int)fd;
if (!appfsFdValid(page)) return ESP_ERR_NOT_FOUND;
if (appfsMeta[appfsActiveMeta].page[page].size < (start+len)) {
return ESP_ERR_INVALID_SIZE;
}
while (start > APPFS_SECTOR_SZ) {
page=appfsMeta[appfsActiveMeta].page[page].next;
start-=APPFS_SECTOR_SZ;
}
while (len>0) {
size_t size=len;
if (size+start>APPFS_SECTOR_SZ) size=APPFS_SECTOR_SZ-start;
ESP_LOGD(TAG, "Writing to page %d offset %d size %d", page, start, size);
r=esp_partition_write(appfsPart, (page+1)*APPFS_SECTOR_SZ+start, buf, size);
if (r!=ESP_OK) return r;
page=appfsMeta[appfsActiveMeta].page[page].next;
len-=size;
buf+=size;
start=0;
}
return ESP_OK;
}
void appfsDump() {
printf("AppFsDump: ..=free XX=illegal no=next page\n");
for (int i=0; i<16; i++) printf("%02X-", i);
printf("\n");
for (int i=0; i<APPFS_PAGES; i++) {
if (!page_in_part(i)) {
printf(" ");
} else if (appfsMeta[appfsActiveMeta].page[i].used==APPFS_USE_FREE) {
printf("..");
} else if (appfsMeta[appfsActiveMeta].page[i].used==APPFS_USE_DATA) {
printf("%02X", appfsMeta[appfsActiveMeta].page[i].next);
} else if (appfsMeta[appfsActiveMeta].page[i].used==APPFS_ILLEGAL) {
printf("XX");
} else {
printf("??");
}
if ((i&15)==15) {
printf("\n");
} else {
printf(" ");
}
}
printf("\n");
for (int i=0; i<APPFS_PAGES; i++) {
if (appfsMeta[appfsActiveMeta].page[i].used==APPFS_USE_DATA && appfsMeta[appfsActiveMeta].page[i].name[0]!=0xff) {
printf("File %s starts at page %d\n", appfsMeta[appfsActiveMeta].page[i].name, i);
}
}
}
esp_err_t appfsGetCurrentApp(appfs_handle_t *ret_app) {
//Grab offset of this function in appfs
size_t phys_offs = spi_flash_cache2phys(appfsGetCurrentApp);
phys_offs -= appfsPart->address;
int page=(phys_offs/APPFS_SECTOR_SZ)-1;
if (page<0 || page>=APPFS_PAGES) {
return ESP_ERR_NOT_FOUND;
}
//Find first sector for this page.
int tries=APPFS_PAGES; //make sure this loop always exits
while (appfsMeta[appfsActiveMeta].page[page].name[0]==0xff) {
int i;
for (i=0; i<APPFS_PAGES; i++) {
if (appfsMeta[appfsActiveMeta].page[i].next==page) {
page=i;
break;
}
}
//See if what we have still makes sense.
if (tries==0 || i>=APPFS_PAGES) return ESP_ERR_NOT_FOUND;
tries--;
}
//Okay, found!
*ret_app=page;
return ESP_OK;
}
#endif