Move AppFS to it's own repository

This commit is contained in:
Renze Nicolai 2022-05-11 23:09:49 +02:00
parent e2f9affae5
commit 3b4ff97495
10 changed files with 5 additions and 1472 deletions

3
.gitmodules vendored
View file

@ -22,3 +22,6 @@
[submodule "components/mch2022-rp2040"] [submodule "components/mch2022-rp2040"]
path = components/mch2022-rp2040 path = components/mch2022-rp2040
url = https://github.com/badgeteam/esp32-component-mch2022-rp2040.git url = https://github.com/badgeteam/esp32-component-mch2022-rp2040.git
[submodule "components/esp32-component-appfs"]
path = components/appfs
url = https://github.com/badgeteam/esp32-component-appfs.git

1
bootloader_components/main Symbolic link
View file

@ -0,0 +1 @@
../components/appfs/bootloader_main

View file

@ -1,17 +0,0 @@
idf_component_register(SRCS "bootloader_start.c" "appfs_flashfunctions_wrapper.c"
REQUIRES bootloader bootloader_support appfs)
idf_build_get_property(target IDF_TARGET)
# Use the linker script files from the actual bootloader
set(scripts "${IDF_PATH}/components/bootloader/subproject/main/ld/${target}/bootloader.ld"
"${IDF_PATH}/components/bootloader/subproject/main/ld/${target}/bootloader.rom.ld")
target_linker_script(${COMPONENT_LIB} INTERFACE "${scripts}")
target_link_libraries(${COMPONENT_LIB} INTERFACE
"-Wl,--wrap=bootloader_flash_read"
"-Wl,--wrap=bootloader_mmap"
"-Wl,--wrap=bootloader_munmap"
"-Wl,--wrap=bootloader_console_deinit"
)

View file

@ -1,177 +0,0 @@
/*
These functions wrap the flash read and mmap functions. The idea is that normally, they will run the real
functions. However, after appfs_wrapper_init is called with an appfs file and a flash range, any
call to these functions in that range will be redirected to the appfs functions that do the same.
The idea is that this changes the 'view' of that flash range from an (for the rest of the bootloader)
ununderstandable appfs struct mess, to one that looks the same as it would when the selected file
would be directly flashed to the partition. The nice thing here is that we can use the rest of the
bootloader verbatim, without having to modify it.
Not we assume the ovl_start and ovl_end match the position and size of the appfs partition; we use that
if we actually boot an app.
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.
*/
#ifdef BOOTLOADER_BUILD
#include "appfs.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "esp_app_format.h"
#include "soc/soc_memory_types.h"
#include "soc/soc_caps.h"
#include <string.h>
#include "soc/dport_reg.h"
#include "esp32/rom/cache.h"
static const char *TAG="appfs_wrapper";
static appfs_handle_t file_handle=APPFS_INVALID_FD;
static size_t ovl_start, ovl_size;
void appfs_wrapper_init(appfs_handle_t handle, size_t part_start, size_t part_size) {
file_handle=handle;
ovl_start=part_start;
ovl_size=part_size;
}
void appfs_wrapper_deinit() {
file_handle=APPFS_INVALID_FD;
}
//These are the actual functions.
esp_err_t __real_bootloader_flash_read(size_t src_addr, void *dest, size_t size, bool allow_decrypt);
const void *__real_bootloader_mmap(uint32_t src_addr, uint32_t size);
void __real_bootloader_munmap(const void *mapping);
void __real_bootloader_console_deinit();
static bool was_mmapped_to_appfs=false;
IRAM_ATTR const void *__wrap_bootloader_mmap(uint32_t src_addr, uint32_t size) {
if (file_handle!=APPFS_INVALID_FD && src_addr>=ovl_start && src_addr+size<ovl_start+ovl_size) {
ESP_LOGD(TAG, "__wrap_bootloader_mmap: redirecting map to 0x%X", src_addr);
uint8_t *f=appfsBlMmap(file_handle);
return &f[src_addr-ovl_start];
} else {
return __real_bootloader_mmap(src_addr, size);
}
}
IRAM_ATTR void __wrap_bootloader_munmap(const void *mapping) {
if (file_handle!=APPFS_INVALID_FD && was_mmapped_to_appfs) {
ESP_LOGD(TAG, "__wrap_bootloader_munmap");
appfsBlMunmap();
was_mmapped_to_appfs=false;
} else {
__real_bootloader_munmap(mapping);
}
}
IRAM_ATTR esp_err_t __wrap_bootloader_flash_read(size_t src_addr, void *dest, size_t size, bool allow_decrypt) {
if (file_handle!=APPFS_INVALID_FD && src_addr>=ovl_start && src_addr+size<ovl_start+ovl_size) {
ESP_LOGD(TAG, "__wrap_bootloader_flash_read: 0x%X->0x%X, %d bytes", src_addr, (int)dest, size);
return appfs_bootloader_read(file_handle, src_addr-ovl_start, dest, size);
} else {
return __real_bootloader_flash_read(src_addr, dest, size, allow_decrypt);
}
}
IRAM_ATTR static bool should_map(uint32_t load_addr) {
return (load_addr >= SOC_IROM_LOW && load_addr < SOC_IROM_HIGH)
|| (load_addr >= SOC_DROM_LOW && load_addr < SOC_DROM_HIGH);
}
//Note: when this is called, everything to verify and load the app has already been done *EXCEPT* the MMU
//mapping. That is done, but with wrong addresses. We need to re-do that here and then call into
//the app.
static IRAM_ATTR void mmap_and_start_app() {
ESP_LOGD(TAG, "mmap_and_start_app()");
//First, check if we actually need to do this. If loading the appfs app failed (e.g. because it
//got corrupted), the previous routine will fall back to e.g. the factory app. If we would
//adjust the MMU assuming the appfs app had loaded, we would crash.
//Note that this is ESP32-specific.
for (int i = 0; i < DPORT_FLASH_MMU_TABLE_SIZE; i++) {
if (DPORT_PRO_FLASH_MMU_TABLE[i] != DPORT_FLASH_MMU_TABLE_INVALID_VAL) {
int page=DPORT_PRO_FLASH_MMU_TABLE[i]&255;
int addr=page*0x10000;
if (addr<ovl_start || addr>ovl_start+ovl_size) {
ESP_LOGI(TAG, "Not booting appfs app; not adjusting mmu.");
return;
}
}
}
//Undo bootloader mapping. If we don't call this, the rest of the code thinks there's still
//something mapped. Note that for now the address doesn't matter, we feed it 0.
__real_bootloader_munmap(0);
//Map the executable file so we can read its header.
uint8_t *appBytes=appfsBlMmap(file_handle);
const esp_image_header_t *hdr=(const esp_image_header_t*)appBytes;
uint32_t entry_addr=hdr->entry_addr;
AppfsBlRegionToMap mapRegions[8];
int noMaps=0;
uint8_t *pstart=appBytes+sizeof(esp_image_header_t);
uint8_t *p=pstart;
for (int i=0; i<hdr->segment_count; i++) {
esp_image_segment_header_t *shdr=(esp_image_segment_header_t*)p;
p+=sizeof(esp_image_segment_header_t);
if (should_map(shdr->load_addr)) {
mapRegions[noMaps].fileAddr=p-appBytes;
mapRegions[noMaps].mapAddr=shdr->load_addr;
mapRegions[noMaps].length=shdr->data_len;
noMaps++;
ESP_LOGI(TAG, "Segment %d: map to %X size %X", i, shdr->load_addr, shdr->data_len);
} else {
ESP_LOGI(TAG, "Segment %d: ignore (addr %X) size %X", i, shdr->load_addr, shdr->data_len);
}
int l=(shdr->data_len+3)&(~3);
p+=l;
}
ESP_LOGD(TAG, "Unmap");
appfsBlMunmap();
appfsBlMapRegions(file_handle, mapRegions, noMaps);
ESP_LOGD(TAG, "Appfs MMU adjustments done. Starting app at 0x%08x", entry_addr);
typedef void (*entry_t)(void);
entry_t entry = ((entry_t) entry_addr);
(*entry)();
}
//Before the app is started, the bootloader manually sets up the cache. We can't easily intercept
//that in order to do the transformation from fake partition offsets to appfs file contents,
//however the bootloader does have a call that it calls just before it starts up the app. We hook
//that here, manually set the cache regions to the actual app.
IRAM_ATTR void __wrap_bootloader_console_deinit() {
if (file_handle!=APPFS_INVALID_FD) {
mmap_and_start_app();
}
//Actual partition selected. Simply call the actual function.
__real_bootloader_console_deinit();
}
//These functions are used by appfs to access the flash: these should always use unwrapped calls.
IRAM_ATTR const void* appfs_bootloader_mmap(uint32_t src_addr, uint32_t size) {
return __real_bootloader_mmap(src_addr, size);
}
IRAM_ATTR void appfs_bootloader_munmap(const void *mapping) {
return __real_bootloader_munmap(mapping);
}
IRAM_ATTR esp_err_t appfs_bootloader_flash_read(size_t src_addr, void *dest, size_t size, bool allow_decrypt) {
return __real_bootloader_flash_read(src_addr, dest, size, allow_decrypt);
}
#endif

View file

@ -1,18 +0,0 @@
#pragma once
#ifdef BOOTLOADER_BUILD
/*
The appfs wrapper layer wraps (using thr gcc linker wrapping function, see the CMakeFiles.txt
file in this directory) the calls that the rest of the bootloader uses, and maps accesses to
the appfs partition to transparently map to access to the selected appfs file instead.
*/
//Initialize the wrapper. Handle is a handle to the appfs file, part_start and part_size
//must refer to the offset and the size of the appfs partition.
void appfs_wrapper_init(appfs_handle_t handle, size_t part_start, size_t part_size);
//Un-initialize the wrapper. Flash access will always access raw flash after this.
void appfs_wrapper_deinit();
#endif

View file

@ -1,141 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef BOOTLOADER_BUILD
#include <stdbool.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "bootloader_init.h"
#include "bootloader_utility.h"
#include "bootloader_common.h"
#include "appfs.h"
#include "bootloader_flash_priv.h"
#include "appfs_flashfunctions_wrapper.h"
static const char *TAG="bootloader";
//Copied from kchal, which we don't want to link inhere.
//See 8bkc-hal/kchal.c for explanation of the bits in the store0 register
static int appfs_get_new_app() {
uint32_t r=REG_READ(RTC_CNTL_STORE0_REG);
ESP_LOGI(TAG, "RTC store0 reg: %x", r);
if ((r&0xFF000000)!=0xA5000000) return -1;
return r&0xff;
}
//Find the position/size of the appfs partition
static bool find_appfs_part(size_t *pos, size_t *len) {
const esp_partition_info_t *partitions = bootloader_mmap(ESP_PARTITION_TABLE_OFFSET, ESP_PARTITION_TABLE_MAX_LEN);
if (!partitions) {
ESP_LOGE(TAG, "bootloader_mmap(0x%x, 0x%x) failed", ESP_PARTITION_TABLE_OFFSET, ESP_PARTITION_TABLE_MAX_LEN);
return false;
}
ESP_LOGD(TAG, "mapped partition table 0x%x at 0x%x", ESP_PARTITION_TABLE_OFFSET, (intptr_t)partitions);
int num_partitions;
esp_err_t err = esp_partition_table_verify(partitions, true, &num_partitions);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to verify partition table");
return false;
}
bool found=false;
for (int i = 0; i < num_partitions; i++) {
const esp_partition_info_t *partition = &partitions[i];
if (partition->type==APPFS_PART_TYPE && partition->subtype==APPFS_PART_SUBTYPE) {
*pos=partition->pos.offset;
*len=partition->pos.size;
found=true;
}
}
bootloader_munmap(partitions);
return found;
}
/*
* We arrive here after the ROM bootloader finished loading this second stage bootloader from flash.
* The hardware is mostly uninitialized, flash cache is down and the app CPU is in reset.
* We do have a stack, so we can do the initialization in C.
*/
void __attribute__((noreturn)) call_start_cpu0(void)
{
// Hardware initialization
if (bootloader_init() != ESP_OK) {
bootloader_reset();
}
bootloader_state_t bs = {0};
if (!bootloader_utility_load_partition_table(&bs)) {
ESP_LOGE(TAG, "load partition table error!");
bootloader_reset();
}
size_t appfs_pos, appfs_len;
if (!find_appfs_part(&appfs_pos, &appfs_len)) {
ESP_LOGE(TAG, "No appfs found!");
goto error;
}
//We have an appfs
ESP_LOGI(TAG, "AppFs found @ offset 0x%X", appfs_pos);
esp_err_t err=appfsBlInit(appfs_pos, appfs_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "AppFs initialization failed");
appfsBlDeinit();
goto error;
}
ESP_LOGI(TAG, "AppFs initialized");
int app=appfs_get_new_app();
appfs_handle_t handle=0;
if (app<0) {
//Load default app
handle=appfsOpen("chooser.app");
} else {
handle=app;
}
if (handle==APPFS_INVALID_FD) {
ESP_LOGE(TAG, "Couldn't open app (%d)!", app);
appfsBlDeinit();
goto error;
}
ESP_LOGI(TAG, "Wrapping flash functions and booting app...");
appfs_wrapper_init(handle, appfs_pos, appfs_len);
//De-init the high-level parts of appfs. Reading/mmap'ping a file handle still is explicitly
//allowed after this, though.
appfsBlDeinit();
//Note that the rest of the bootloader code has no clue about appfs, and as such won't try
//to boot it. We 'fix' that by chucking the appfs partition (which is now wrapped so the rest
//of the bootloader reads from the selected file when it thinks it loads from the app) into
//the top OTA slot.
bs.ota[0].offset=appfs_pos;
bs.ota[0].size=appfs_len;
bs.app_count=1;
//And bingo bango, we can now boot from appfs as if it is the first ota partition.
bootloader_utility_load_boot_image(&bs, 0);
//Still here? Must be an error.
error:
//Try to fallback to factory part
bootloader_utility_load_boot_image(&bs, -1);
ESP_LOGE(TAG, "Bootloader end");
bootloader_reset();
}
// Return global reent struct if any newlib functions are linked to bootloader
struct _reent *__getreent(void)
{
return _GLOBAL_REENT;
}
#endif

1
components/appfs Submodule

@ -0,0 +1 @@
Subproject commit 6a1563b35f38b426a13d616ab45a1feeee366b43

View file

@ -1,6 +0,0 @@
idf_component_register(SRC_DIRS .
REQUIRES log spi_flash bootloader_support
INCLUDE_DIRS . )
target_compile_options(${COMPONENT_LIB} PRIVATE -DPROJECT_NAME="${CMAKE_PROJECT_NAME}")

View file

@ -1,816 +0,0 @@
/*
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

View file

@ -1,297 +0,0 @@
#ifndef APPFS_H
#define APPFS_H
#include "esp_err.h"
#include <stdint.h>
#include "esp_spi_flash.h"
#ifdef __cplusplus
extern "C" {
#endif
#define APPFS_PART_TYPE 0x43 /*<! Default partition type of an appfs partition */
#define APPFS_PART_SUBTYPE 0x3 /*<! Default partition subtype of an appfs partition */
typedef int appfs_handle_t;
#define APPFS_INVALID_FD -1 /*<! Some functions return this to indicate an error situation */
/**
* @brief Initialize the appfs code and mount the appfs partition.
*
* Run this before using any of the other AppFs APIs
*
* @param type Partition type. Normally you'd pass APPFS_PART_TYPE here.
* @param subtype Partition subtype. Normally you'd pass APPFS_PART_SUBTYPE here.
* @return ESP_OK if all OK, an error from the underlying partition or flash code otherwise.
*/
esp_err_t appfsInit(int type, int subtype);
/**
* @brief Check if a file with the given filename exists.
*
* @param filename Filename to check
* @return 1 if a file with a name which exactly matches filename exists; 0 otherwise.
*/
int appfsExists(const char *filename);
/**
* @brief Check if a file descriptor is valid
*
* Because file descriptors are integers which are more-or-less valid over multiple sessions, they can be stored
* in non-volatile memory and re-used later. When doing this, a sanity check to see if the fd still
* points at something valid may be useful. This function provides that sanity check.
*
* @param fd File descriptor to check
* @return True if fd points to a valid file, false otherwise.
*/
bool appfsFdValid(int fd);
/**
* @brief Open a file on a mounted appfs
*
* @param filename Filename of the file to open
* @return The filedescriptor if succesful, APPFS_INVALID_FD if not. */
appfs_handle_t appfsOpen(const char *filename);
/**
* @brief Close a file on a mounted appfs
*
* @note In the current appfs implementation, this is a no-op. This may change in the future, however.
* @param handle File descriptor to close
*/
void appfsClose(appfs_handle_t handle);
/**
* @brief Delete a file on the appfs
*
* @param filename Name of the file to delete
* @return ESP_OK if file successfully deleted, an error otherwise.
*/
esp_err_t appfsDeleteFile(const char *filename);
/**
* @brief Create a new file on the appfs
*
* Initially, the file will have random contents consisting of whatever used the sectors of
* flash it occupies earlier. Note that this function also opens the file and returns a file
* descriptor to it if succesful; no need for a separate appfsOpen call.
*
* @param filename Name of the file to be created
* @param size Size of the file, in bytes
* @param handle Pointer to an appfs_handle_t which will store the file descriptor of the created file
* @return ESP_OK if file successfully deleted, an error otherwise.
*/
esp_err_t appfsCreateFile(const char *filename, size_t size, appfs_handle_t *handle);
/**
* @brief Map a file into memory
*
* This maps a (portion of a) file into memory, where you can access it as if it was an array of bytes in RAM.
* This uses the MMU and flash cache of the ESP32 to accomplish this effect. The memory is read-only; trying
* to write to it will cause an exception.
*
* @param fd File descriptor of the file to map.
* @param offset Offset into the file where the map starts
* @param len Lenght of the map
* @param out_ptr Pointer to a const void* variable where, if successful, a pointer to the memory is stored.
* @param memory One of SPI_FLASH_MMAP_DATA or SPI_FLASH_MMAP_INST, where the former does a map to data memory
* and the latter a map to instruction memory. You'd normally use the first option.
* @param out_handle Pointer to a spi_flash_mmap_handle_t variable. This variable is needed to later free the
* map again.
* @return ESP_OK if file successfully deleted, an error otherwise.
*/
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);
/**
* @brief Unmap a previously mmap'ped file
*
* This unmaps a region previously mapped with appfsMmap
*
* @param handle Handle obtained in the previous appfsMmap call
*/
void appfsMunmap(spi_flash_mmap_handle_t handle);
/**
* @brief Erase a portion of an appfs file
*
* This sets all bits in the region to be erased to 1, so an appfsWrite can reset selected bits to 0 again.
*
* @param fd File descriptor of file to erase in.
* @param start Start offset of file portion to be erased. Must be aligned to 4KiB.
* @param len Length of file portion to be erased. Must be a multiple of 4KiB.
* @return ESP_OK if file successfully deleted, an error otherwise.
*/
esp_err_t appfsErase(appfs_handle_t fd, size_t start, size_t len);
/**
* @brief Write to a file in appfs
*
* Note: Because this maps directly to a write of the underlying flash memory, this call is only able to
* reset bits in the written area from 1 to 0. If you want to change bits from 0 to 1, call appfsErase on
* the area to be written before calling this function. This function will return success even if the data
* in flash is not the same as the data in the buffer due to bits being 1 in the buffer but 0 on flash.
*
* If the above paragraph is confusing, just remember to erase a region before you write to it.
*
* @param fd File descriptor of file to write to
* @param start Offset into file to start writing
* @param buf Buffer of bytes to write
* @param len Length, in bytes, of data to be written
* @return ESP_OK if write was successful, an error otherwise.
*/
esp_err_t appfsWrite(appfs_handle_t fd, size_t start, uint8_t *buf, size_t len);
/**
* @brief Read a portion of a file in appfs
*
* This function reads ``len`` bytes of file data, starting from offset ``start``, into the buffer
* ``buf``. Note that if you do many reads, it usually is more efficient to map the file into memory and
* read from it that way instead.
*
* @param fd File descriptor of the file
* @param start Offset in the file to start reading from
* @param buf Buffer to contain the read data
* @param len Length, in bytes, of the data to read
* @return ESP_OK if read was successful, an error otherwise.
*/
esp_err_t appfsRead(appfs_handle_t fd, size_t start, void *buf, size_t len);
/**
* @brief Atomically rename a file
*
* This atomically renames a file. If a file with the target name already exists, it will be deleted. This action
* is done atomically, so at any point in time, either the original or the new file will fully exist under the target name.
*
* @param from Original name of file
* @param to Target name of file
* @return ESP_OK if rename was successful, an error otherwise.
*/
esp_err_t appfsRename(const char *from, const char *to);
/**
* @brief Get file information
*
* Given a file descriptor, this returns the name and size of the file. The file descriptor needs
* to be valid for this to work.
*
* @param fd File descriptor
* @param name Pointer to a char pointer. This will be pointed at the filename in memory. There is no need
* to free the pointer memory afterwards. Pointer memory is valid while the file exists / is not
* deleted. Can be NULL if name information is not wanted.
* @param size Pointer to an int where the size of the file will be written, or NULL if this information is
* not wanted.
*/
void appfsEntryInfo(appfs_handle_t fd, const char **name, int *size);
/**
* brief Get the next entry in the appfs.
*
* This function can be used to list all the files existing in the appfs. Pass it APPFS_INVALID_FD when
* calling it for the first time to receive the first file descriptor. Pass it the result of the previous call
* to get the next file descriptor. When this function returns APPFS_INVALID_FD, all files have been enumerated.
* You can use ``appfsEntryInfo()`` to get the file name and size associated with the returned file descriptors
*
* @param fd File descriptor returned by previous call, or APPFS_INVALID_FD to get the first file descriptor
* @return Next file descriptor, or APPFS_INVALID_FD if all files have been enumerated
*/
appfs_handle_t appfsNextEntry(appfs_handle_t fd);
/**
* @brief Get file descriptor of currently running app.
*
* @param ret_app Pointer to variable to hold the file descriptor
* @return ESP_OK on success, an error when e.g. the currently running code isn't located in appfs.
*/
esp_err_t appfsGetCurrentApp(appfs_handle_t *ret_app);
/**
* @brief Get amount of free space in appfs partition
*
* @return amount of free space, in bytes
*/
size_t appfsGetFreeMem();
/**
* @brief Debugging function: dump current appfs state
*
* Prints state of the appfs to stdout.
*/
void appfsDump();
#ifdef BOOTLOADER_BUILD
#include "bootloader_flash_priv.h"
/**
* @brief Appfs bootloader support: struct to hold a region of a file to map
*/
typedef struct {
uint32_t fileAddr; /*<! Offset in file */
uint32_t mapAddr; /*<! Address to map to */
uint32_t length; /*<! Length of region */
} AppfsBlRegionToMap;
/**
* @brief Bootloader only: initialize appfs
*
* @param offset Offset, in bytes, in flash of the appfs partition
* @param len Length, in bytes, of appfs partition
*/
esp_err_t appfsBlInit(uint32_t offset, uint32_t len);
/**
* @brief Bootloader only: de-init appfs. Note that if you have a file handle, you can still
* read/mmap it. This guarantees that the metadata for the appfs is not mapped anymore.
*/
void appfsBlDeinit();
/**
* @brief Bootloader only: Map an entire appfs file into memory
*
* Note that only one file can be mapped at a time, and that between a
* appfsBlMmap and appfsBlMunmap call, the appfs is in a state where the appfs meta information
* is unmapped, meaning other appfs functions cannot be used.
*
* @param fd File descriptor to map
* @return pointer to the mapped file
*/
void* appfsBlMmap(int fd);
/**
* @brief Bootloader only: Un-mmap a file
*/
void appfsBlMunmap();
/**
* @brief Bootloader only: read data from a file
*
* @param fs File descriptor to read from
* @param src_addr Offset in file
* @param dest Dest buffer
* @param size Length to read
* @return ESP_OK if OK
*/
esp_err_t appfs_bootloader_read(int fd, size_t src_addr, void *dest, size_t size);
/*
* @brief Bootloader only: map multiple regions within a file to various memory addressed.
*
* Used to load an app into memory for later execution.
*
* @param fd File descriptor to be mapped in
* @param regions An array of region descriptors
* @param noRegions Amount of regions to map
* @return ESP_OK on success, an error when the map failed.
*/
esp_err_t appfsBlMapRegions(int fd, AppfsBlRegionToMap *regions, int noRegions);
#endif
#ifdef __cplusplus
}
#endif
#endif