Merge pull request #19 from badgeteam/renze/appfs-as-module
Move AppFS to it's own repository
This commit is contained in:
commit
ccae27506e
10 changed files with 5 additions and 1472 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -22,6 +22,9 @@
|
||||||
[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
|
||||||
[submodule "components/ws2812"]
|
[submodule "components/ws2812"]
|
||||||
path = components/ws2812
|
path = components/ws2812
|
||||||
url = https://github.com/badgeteam/esp32-component-ws2812.git
|
url = https://github.com/badgeteam/esp32-component-ws2812.git
|
||||||
|
|
1
bootloader_components/main
Symbolic link
1
bootloader_components/main
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../components/appfs/bootloader_main
|
|
@ -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"
|
|
||||||
)
|
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
1
components/appfs
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 6a1563b35f38b426a13d616ab45a1feeee366b43
|
|
@ -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}")
|
|
|
@ -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
|
|
|
@ -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
|
|
Loading…
Reference in a new issue