stm32/mboot: Add support for a raw filesystem.

This is enabled by default if MBOOT_FSLOAD is enabled, although a board
can explicitly disable it by `#define MBOOT_VFS_RAW (0)`.

Signed-off-by: Damien George <damien@micropython.org>
This commit is contained in:
Damien George 2024-03-06 19:01:46 +11:00
parent 3c445f6636
commit 9651046edd
7 changed files with 232 additions and 29 deletions

View File

@ -120,6 +120,7 @@ SRC_C += \
ui.c \
vfs_fat.c \
vfs_lfs.c \
vfs_raw.c \
drivers/bus/softspi.c \
drivers/bus/softqspi.c \
drivers/memory/spiflash.c \

View File

@ -71,13 +71,23 @@ How to use
#define MBOOT_FSLOAD (1)
and then enable one or more of the following depending on what filesystem
support is required in Mboot (note that the FAT driver is read-only and
quite compact, but littlefs supports both read and write so is rather
large):
support is required in Mboot:
#define MBOOT_VFS_FAT (1)
#define MBOOT_VFS_LFS1 (1)
#define MBOOT_VFS_LFS2 (1)
#define MBOOT_VFS_RAW (1)
Note that the FAT and LFS2 drivers are read-only and quite compact, but
LFS1 supports both read and write so is rather large.
The raw filesystem type is enabled by default and is a flat section of
storage containing a single file without any metadata. The raw filesystem
can either be one regoin, or split over two separate, contiguous regions.
The latter is useful for wear levelling: given a chunk of flash, write the
firmware starting at a random block within that chunk and wrap around to
the beginning of the chunk when the end is reached. Then use a split
raw filesystem to inform mboot of this wrapping.
2. Build the board's main application firmware as usual.

View File

@ -39,7 +39,7 @@
#if MBOOT_FSLOAD
#if !(MBOOT_VFS_FAT || MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2)
#if !(MBOOT_VFS_FAT || MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2 || MBOOT_VFS_RAW)
#error Must enable at least one VFS component
#endif
@ -234,28 +234,51 @@ int fsload_process(void) {
// End of elements.
return -MBOOT_ERRNO_FSLOAD_NO_MOUNT;
}
// Extract element arguments based on the element length:
// - 10 bytes: mount_point fs_type uint32_t uint32_t
// - 14 bytes: mount_point fs_type uint32_t uint32_t uint32_t
// - 18 bytes: mount_point fs_type uint32_t uint32_t uint32_t uint32_t
// - 22 bytes: mount_point fs_type uint64_t uint64_t uint32_t
// - 34 bytes: mount_point fs_type uint64_t uint64_t uint64_t uint64_t
mboot_addr_t base_addr;
mboot_addr_t byte_len;
uint32_t block_size = MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE;
if (elem[-1] == 10 || elem[-1] == 14) {
mboot_addr_t arg2 = 0;
mboot_addr_t arg3 = 0;
(void)arg2;
(void)arg3;
uint8_t elem_len = elem[-1];
if (elem_len == 10 || elem_len == 14 || elem_len == 18) {
// 32-bit base and length given, extract them.
base_addr = get_le32(&elem[2]);
byte_len = get_le32(&elem[6]);
if (elem[-1] == 14) {
// Block size given, extract it.
block_size = get_le32(&elem[10]);
if (elem_len >= 14) {
// Argument 2 given, extract it.
arg2 = get_le32(&elem[10]);
if (elem_len == 18) {
// Argument 3 given, extract it.
arg3 = get_le32(&elem[14]);
}
}
#if MBOOT_ADDRESS_SPACE_64BIT
} else if (elem[-1] == 22) {
// 64-bit base and length given, and block size, so extract them.
} else if (elem_len == 22 || elem_len == 34) {
// 64-bit base and length given, so extract them.
base_addr = get_le64(&elem[2]);
byte_len = get_le64(&elem[10]);
block_size = get_le32(&elem[18]);
if (elem_len == 22) {
// 32-bit argument 2 given, extract it.
arg2 = get_le32(&elem[18]);
} else {
// 64-bit argument 2 and 3 given, extract them.
arg2 = get_le64(&elem[18]);
arg3 = get_le64(&elem[26]);
}
#endif
} else {
// Invalid MOUNT element.
return -MBOOT_ERRNO_FSLOAD_INVALID_MOUNT;
}
if (elem[0] == mount_point) {
int ret;
union {
@ -268,27 +291,43 @@ int fsload_process(void) {
#if MBOOT_VFS_LFS2
vfs_lfs2_context_t lfs2;
#endif
#if MBOOT_VFS_RAW
vfs_raw_context_t raw;
#endif
} ctx;
const stream_methods_t *methods;
#if MBOOT_VFS_FAT
if (elem[1] == ELEM_MOUNT_FAT) {
(void)block_size;
ret = vfs_fat_mount(&ctx.fat, base_addr, byte_len);
methods = &vfs_fat_stream_methods;
} else
#endif
#if MBOOT_VFS_LFS1
if (elem[1] == ELEM_MOUNT_LFS1) {
uint32_t block_size = arg2;
if (block_size == 0) {
block_size = MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE;
}
ret = vfs_lfs1_mount(&ctx.lfs1, base_addr, byte_len, block_size);
methods = &vfs_lfs1_stream_methods;
} else
#endif
#if MBOOT_VFS_LFS2
if (elem[1] == ELEM_MOUNT_LFS2) {
uint32_t block_size = arg2;
if (block_size == 0) {
block_size = MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE;
}
ret = vfs_lfs2_mount(&ctx.lfs2, base_addr, byte_len, block_size);
methods = &vfs_lfs2_stream_methods;
} else
#endif
#if MBOOT_VFS_RAW
if (elem[1] == ELEM_MOUNT_RAW) {
ret = vfs_raw_mount(&ctx.raw, base_addr, byte_len, arg2, arg3);
methods = &vfs_raw_stream_methods;
} else
#endif
{
// Unknown filesystem type
return -MBOOT_ERRNO_FSLOAD_INVALID_MOUNT;

View File

@ -9,6 +9,7 @@ import deflate, machine, stm
VFS_FAT = 1
VFS_LFS1 = 2
VFS_LFS2 = 3
VFS_RAW = 4
# Constants for creating mboot elements.
_ELEM_TYPE_END = const(1)
@ -226,28 +227,45 @@ def _create_element(kind, body):
def update_app_elements(
filename, fs_base, fs_len, fs_type=VFS_FAT, fs_blocksize=0, status_addr=None, addr_64bit=False
filename,
fs_base,
fs_len,
fs_type=VFS_FAT,
fs_blocksize=0,
status_addr=None,
addr_64bit=False,
*,
fs_base2=0,
fs_len2=0,
):
# Check firmware is of .dfu or .dfu.gz type
try:
with open(filename, "rb") as f:
hdr = deflate.DeflateIO(f, deflate.GZIP).read(6)
except Exception:
with open(filename, "rb") as f:
hdr = f.read(6)
if hdr != b"DfuSe\x01":
print("Firmware must be a .dfu(.gz) file.")
return ()
if fs_type != VFS_RAW:
# Check firmware is of .dfu or .dfu.gz type
try:
with open(filename, "rb") as f:
hdr = deflate.DeflateIO(f, deflate.GZIP).read(6)
except Exception:
with open(filename, "rb") as f:
hdr = f.read(6)
if hdr != b"DfuSe\x01":
print("Firmware must be a .dfu(.gz) file.")
return ()
if fs_type in (VFS_LFS1, VFS_LFS2) and not fs_blocksize:
raise Exception("littlefs requires fs_blocksize parameter")
mount_point = 1
mount_encoding = "<BBQQL" if addr_64bit else "<BBLLL"
elems = _create_element(
_ELEM_TYPE_MOUNT,
struct.pack(mount_encoding, mount_point, fs_type, fs_base, fs_len, fs_blocksize),
)
if fs_type == VFS_RAW:
mount_encoding = "<BBQQQQ" if addr_64bit else "<BBLLLL"
elems = _create_element(
_ELEM_TYPE_MOUNT,
struct.pack(mount_encoding, mount_point, fs_type, fs_base, fs_len, fs_base2, fs_len2),
)
else:
mount_encoding = "<BBQQL" if addr_64bit else "<BBLLL"
elems = _create_element(
_ELEM_TYPE_MOUNT,
struct.pack(mount_encoding, mount_point, fs_type, fs_base, fs_len, fs_blocksize),
)
elems += _create_element(
_ELEM_TYPE_FSLOAD, struct.pack("<B", mount_point) + bytes(filename, "ascii")
)

View File

@ -92,6 +92,31 @@
#define MBOOT_LED_STATE_LED2 (0x04)
#define MBOOT_LED_STATE_LED3 (0x08)
// Whether to support loading firmware from a filesystem.
#ifndef MBOOT_FSLOAD
#define MBOOT_FSLOAD (0)
#endif
// Whether to support FAT filesystems.
#ifndef MBOOT_VFS_FAT
#define MBOOT_VFS_FAT (0)
#endif
// Whether to support Littlefs v1 filesystems.
#ifndef MBOOT_VFS_LFS1
#define MBOOT_VFS_LFS1 (0)
#endif
// Whether to support Littlefs v2 filesystems.
#ifndef MBOOT_VFS_LFS2
#define MBOOT_VFS_LFS2 (0)
#endif
// Whether to support raw filesystems.
#ifndef MBOOT_VFS_RAW
#define MBOOT_VFS_RAW (MBOOT_FSLOAD)
#endif
// These enum values are passed as the first argument to mboot_state_change() to
// notify of a change in state of the bootloader activity. This function has a
// default implementation in ui.c but can be overridden by a board. Some states
@ -158,6 +183,7 @@ enum {
ELEM_MOUNT_FAT = 1,
ELEM_MOUNT_LFS1,
ELEM_MOUNT_LFS2,
ELEM_MOUNT_RAW,
};
// Configure the type used to hold an address in the mboot address space.

View File

@ -93,4 +93,22 @@ int vfs_lfs2_mount(vfs_lfs2_context_t *ctx, mboot_addr_t base_addr, mboot_addr_t
#endif
#if MBOOT_VFS_RAW
// A raw VFS contains a contiguous, single file without any metadata.
typedef struct _vfs_raw_context_t {
mboot_addr_t seg0_base_addr;
mboot_addr_t seg0_byte_len;
mboot_addr_t seg1_base_addr;
mboot_addr_t seg1_byte_len;
mboot_addr_t file_pos;
} vfs_raw_context_t;
extern const stream_methods_t vfs_raw_stream_methods;
int vfs_raw_mount(vfs_raw_context_t *ctx, mboot_addr_t seg0_base_addr, mboot_addr_t seg0_byte_len, mboot_addr_t seg1_base_addr, mboot_addr_t seg1_byte_len);
#endif
#endif // MICROPY_INCLUDED_STM32_MBOOT_VFS_H

View File

@ -0,0 +1,91 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2024 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 "mboot.h"
#include "vfs.h"
#if MBOOT_FSLOAD && MBOOT_VFS_RAW
#ifndef MIN
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif
int vfs_raw_mount(vfs_raw_context_t *ctx, mboot_addr_t seg0_base_addr, mboot_addr_t seg0_byte_len, mboot_addr_t seg1_base_addr, mboot_addr_t seg1_byte_len) {
ctx->seg0_base_addr = seg0_base_addr;
ctx->seg0_byte_len = seg0_byte_len;
ctx->seg1_base_addr = seg1_base_addr;
ctx->seg1_byte_len = seg1_byte_len;
return 0;
}
static int vfs_raw_stream_open(void *stream_in, const char *fname) {
vfs_raw_context_t *stream = stream_in;
(void)fname;
stream->file_pos = 0;
return 0;
}
static void vfs_raw_stream_close(void *stream_in) {
(void)stream_in;
}
static int vfs_raw_stream_read(void *stream_in, uint8_t *buf, size_t len) {
vfs_raw_context_t *stream = stream_in;
size_t orig_len = len;
while (len) {
mboot_addr_t addr;
mboot_addr_t remain;
if (stream->file_pos < stream->seg0_byte_len) {
// Reading from segment 0.
mboot_addr_t seg0_pos = stream->file_pos;
addr = stream->seg0_base_addr + seg0_pos;
remain = stream->seg0_byte_len - seg0_pos;
} else {
// Reading from segment 1.
mboot_addr_t seg1_pos = stream->file_pos - stream->seg0_byte_len;
addr = stream->seg1_base_addr + seg1_pos;
remain = stream->seg1_byte_len - seg1_pos;
if (!remain) {
// At the end of segment 1.
break;
}
}
size_t l = MIN(len, remain);
hw_read(addr, l, buf);
stream->file_pos += l;
buf += l;
len -= l;
}
return orig_len - len;
}
const stream_methods_t vfs_raw_stream_methods = {
vfs_raw_stream_open,
vfs_raw_stream_close,
vfs_raw_stream_read,
};
#endif // MBOOT_FSLOAD && MBOOT_VFS_RAW