mch2022-talktopics/factory_test/bootloader_components/main/appfs_flashfunctions_wrapper.c
2022-01-23 01:27:22 +01:00

159 lines
5.9 KiB
C

/*
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>
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_LOGI(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) {
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) {
uint8_t *f=appfsBlMmap(file_handle);
ESP_LOGI(TAG, "__wrap_bootloader_flash_read: 0x%X->0x%X, %d bytes", src_addr, (int)dest, size);
memcpy(dest, &f[src_addr-ovl_start], size);
appfsBlMunmap();
return ESP_OK;
} 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_LOGI(TAG, "mmap_and_start_app()");
//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);
//Appfs is not gonna like that its metadata is not mmapped. We call this routine as what it does
//'under the hood' is clear the mmu and reset it to mmap only the appfs meta info.
appfsBlMunmap();
//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_LOGI(TAG, "Unmap");
appfsBlMunmap();
appfsBlMapRegions(file_handle, mapRegions, noMaps);
ESP_LOGD(TAG, "start: 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);
}
#endif