circuitpython/ports/stm32/mboot/fsload.c
Damien George ff04b78ffd stm32/mboot: Add support for loading gzip'd firmware from a filesystem.
This adds support to mboot to load and program application firmware from
a .dfu.gz file on the board's filesystem.  See mboot/README.md for details.
2019-02-15 15:09:48 +11:00

292 lines
7.8 KiB
C

/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <string.h>
#include "py/mphal.h"
#include "lib/oofatfs/ff.h"
#include "extmod/uzlib/uzlib.h"
#include "mboot.h"
#if MBOOT_FSLOAD
#define DICT_SIZE (1 << 15)
typedef struct _gz_stream_t {
FIL fp;
TINF_DATA tinf;
uint8_t buf[512];
uint8_t dict[DICT_SIZE];
} gz_stream_t;
static gz_stream_t gz_stream;
static int gz_stream_read_src(TINF_DATA *tinf) {
UINT n;
FRESULT res = f_read(&gz_stream.fp, gz_stream.buf, sizeof(gz_stream.buf), &n);
if (res != FR_OK) {
return -1;
}
if (n == 0) {
return -1;
}
tinf->source = gz_stream.buf + 1;
tinf->source_limit = gz_stream.buf + n;
return gz_stream.buf[0];
}
static int gz_stream_open(FATFS *fatfs, const char *filename) {
FRESULT res = f_open(fatfs, &gz_stream.fp, filename, FA_READ);
if (res != FR_OK) {
return -1;
}
memset(&gz_stream.tinf, 0, sizeof(gz_stream.tinf));
gz_stream.tinf.readSource = gz_stream_read_src;
int st = uzlib_gzip_parse_header(&gz_stream.tinf);
if (st != TINF_OK) {
f_close(&gz_stream.fp);
return -1;
}
uzlib_uncompress_init(&gz_stream.tinf, gz_stream.dict, DICT_SIZE);
return 0;
}
static int gz_stream_read(size_t len, uint8_t *buf) {
gz_stream.tinf.dest = buf;
gz_stream.tinf.dest_limit = buf + len;
int st = uzlib_uncompress_chksum(&gz_stream.tinf);
if (st == TINF_DONE) {
return 0;
}
if (st < 0) {
return st;
}
return gz_stream.tinf.dest - buf;
}
static int fsload_program_file(FATFS *fatfs, const char *filename, bool write_to_flash) {
int res = gz_stream_open(fatfs, filename);
if (res != 0) {
return res;
}
// Parse DFU
uint8_t buf[512];
size_t file_offset;
// Read file header, <5sBIB
res = gz_stream_read(11, buf);
if (res != 11) {
return -1;
}
file_offset = 11;
// Validate header, version 1
if (memcmp(buf, "DfuSe\x01", 6) != 0) {
return -1;
}
// Must have only 1 target
if (buf[10] != 1) {
return -2;
}
// Get total size
uint32_t total_size = get_le32(buf + 6);
// Read target header, <6sBi255sII
res = gz_stream_read(274, buf);
if (res != 274) {
return -1;
}
file_offset += 274;
// Validate target header, with alt being 0
if (memcmp(buf, "Target\x00", 7) != 0) {
return -1;
}
// Get target size and number of elements
uint32_t target_size = get_le32(buf + 266);
uint32_t num_elems = get_le32(buf + 270);
size_t file_offset_target = file_offset;
// Parse each element
for (size_t elem = 0; elem < num_elems; ++elem) {
// Read element header, <II
res = gz_stream_read(8, buf);
if (res != 8) {
return -1;
}
file_offset += 8;
// Get element destination address and size
uint32_t elem_addr = get_le32(buf);
uint32_t elem_size = get_le32(buf + 4);
// Erase flash before writing
if (write_to_flash) {
uint32_t addr = elem_addr;
while (addr < elem_addr + elem_size) {
res = do_page_erase(addr, &addr);
if (res != 0) {
return res;
}
}
}
// Read element data and possibly write to flash
for (uint32_t s = elem_size; s;) {
uint32_t l = s;
if (l > sizeof(buf)) {
l = sizeof(buf);
}
res = gz_stream_read(l, buf);
if (res != l) {
return -1;
}
if (write_to_flash) {
res = do_write(elem_addr, buf, l);
if (res != 0) {
return -1;
}
elem_addr += l;
}
s -= l;
}
file_offset += elem_size;
}
if (target_size != file_offset - file_offset_target) {
return -1;
}
if (total_size != file_offset) {
return -1;
}
// Read trailing info
res = gz_stream_read(16, buf);
if (res != 16) {
return -1;
}
// TODO validate CRC32
return 0;
}
static int fsload_process_fatfs(uint32_t base_addr, uint32_t byte_len, size_t fname_len, const char *fname) {
fsload_bdev_t bdev = {base_addr, byte_len};
FATFS fatfs;
fatfs.drv = &bdev;
FRESULT res = f_mount(&fatfs);
if (res != FR_OK) {
return -1;
}
FF_DIR dp;
res = f_opendir(&fatfs, &dp, "/");
if (res != FR_OK) {
return -1;
}
// Search for firmware file with correct name
int r;
for (;;) {
FILINFO fno;
res = f_readdir(&dp, &fno);
char *fn = fno.fname;
if (res != FR_OK || fn[0] == 0) {
// Finished listing dir, no firmware found
r = -1;
break;
}
if (memcmp(fn, fname, fname_len) == 0 && fn[fname_len] == '\0') {
// Found firmware
led_state_all(2);
r = fsload_program_file(&fatfs, fn, false);
if (r == 0) {
// Firmware is valid, program it
led_state_all(4);
r = fsload_program_file(&fatfs, fn, true);
}
break;
}
}
return r;
}
int fsload_process(void) {
const uint8_t *elem = elem_search(ELEM_DATA_START, ELEM_TYPE_FSLOAD);
if (elem == NULL || elem[-1] < 2) {
return -1;
}
uint8_t mount_point = elem[0];
uint8_t fname_len = elem[-1] - 1;
const char *fname = (const char*)&elem[1];
elem = ELEM_DATA_START;
for (;;) {
elem = elem_search(elem, ELEM_TYPE_MOUNT);
if (elem == NULL || elem[-1] != 10) {
// End of elements, or invalid MOUNT element
return -1;
}
if (elem[0] == mount_point) {
uint32_t base_addr = get_le32(&elem[2]);
uint32_t byte_len = get_le32(&elem[6]);
if (elem[1] == ELEM_MOUNT_FAT) {
int ret = fsload_process_fatfs(base_addr, byte_len, fname_len, fname);
// Flash LEDs based on success/failure of update
for (int i = 0; i < 4; ++i) {
if (ret == 0) {
led_state_all(7);
} else {
led_state_all(1);
}
mp_hal_delay_ms(100);
led_state_all(0);
mp_hal_delay_ms(100);
}
return ret;
}
// Unknown filesystem type
return -1;
}
elem += elem[-1];
}
}
#endif // MBOOT_FSLOAD