From 52c678c6f85b17e542520b44e2facaae44f61b11 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 14 Mar 2024 10:28:00 +1100 Subject: [PATCH] stm32/boards/LEGO_HUB_NO6: Use a raw filesystem for mboot to update fw. The C-based SPI flash driver is needed because the `_copy_file_to_raw_filesystem()` function must copy from a filesystem (eg FAT) to another part of flash, and the same C code must be used for both reading (from FAT) and writing (to flash). Signed-off-by: Damien George --- ports/stm32/boards/LEGO_HUB_NO6/appupdate.py | 70 +++++--- ports/stm32/boards/LEGO_HUB_NO6/manifest.py | 1 - ports/stm32/boards/LEGO_HUB_NO6/spiflash.c | 166 +++++++++++++++++++ ports/stm32/boards/LEGO_HUB_NO6/spiflash.py | 90 ---------- 4 files changed, 212 insertions(+), 115 deletions(-) create mode 100644 ports/stm32/boards/LEGO_HUB_NO6/spiflash.c delete mode 100644 ports/stm32/boards/LEGO_HUB_NO6/spiflash.py diff --git a/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py b/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py index 9cfbc06e56..8229004fbb 100644 --- a/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py +++ b/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py @@ -1,39 +1,61 @@ # Application firmware update function for LEGO_HUB_NO6. -# MIT license; Copyright (c) 2022 Damien P. George +# MIT license; Copyright (c) 2022-2024 Damien P. George from micropython import const -import struct, machine, fwupdate, spiflash, pyb +import random, struct, machine, fwupdate, spiflash, pyb _IOCTL_BLOCK_COUNT = const(4) _IOCTL_BLOCK_SIZE = const(5) -_FILESYSTEM_ADDR = const(0x8000_0000 + 1024 * 1024) - -# Roundabout way to get actual filesystem size from config. -# This takes into account the 1M "reserved" section of the flash memory. -flash = pyb.Flash(start=0) -_FILESYSTEM_LEN = flash.ioctl(_IOCTL_BLOCK_COUNT, None) * flash.ioctl(_IOCTL_BLOCK_SIZE, None) +# Mboot addresses the external SPI flash at this location. +_MBOOT_SPIFLASH_BASE_ADDR = 0x80000000 + +# The raw filesystem is in the first 1MiB of external SPI flash, +# but skip the first and last flash sectors. +_RAW_FILESYSTEM_ADDR = const(4 * 1024) +_RAW_FILESYSTEM_LEN = const(1016 * 1024) + + +def _copy_file_to_raw_filesystem(filename, flash, block): + block_count = flash.ioctl(_IOCTL_BLOCK_COUNT, 0) + block_size = flash.ioctl(_IOCTL_BLOCK_SIZE, 0) + buf = bytearray(block_size) + with open(filename, "rb") as file: + while True: + n = file.readinto(buf) + if not n: + break + flash.writeblocks(block, buf) + block += 1 + if block >= block_count: + print("|", end="") + block = 0 + print(".", end="") + print() def update_app(filename): print(f"Updating application firmware from {filename}") - # Create the elements for the mboot filesystem-load operation. - elems = fwupdate.update_app_elements(filename, _FILESYSTEM_ADDR, _FILESYSTEM_LEN) - if not elems: - return - - # Create a SPI flash object. - spi = machine.SoftSPI( - sck=machine.Pin.board.FLASH_SCK, - mosi=machine.Pin.board.FLASH_MOSI, - miso=machine.Pin.board.FLASH_MISO, - baudrate=50_000_000, - ) - cs = machine.Pin(machine.Pin.board.FLASH_NSS, machine.Pin.OUT, value=1) - # We can't use pyb.Flash() because we need to write to the "reserved" 1M area. - flash = spiflash.SPIFlash(spi, cs) + flash = spiflash.SPIFlash(start=_RAW_FILESYSTEM_ADDR, len=_RAW_FILESYSTEM_LEN) + + # Partition the raw filesystem into two segments for wear levelling. + block_count = flash.ioctl(_IOCTL_BLOCK_COUNT, 0) + block_size = flash.ioctl(_IOCTL_BLOCK_SIZE, 0) + block_start = random.randrange(0, block_count) + print(f"Raw filesystem block layout: 0 .. {block_start} .. {block_count}") + + # Copy the file to the special raw filesystem. + _copy_file_to_raw_filesystem(filename, flash, block_start) # Enter mboot with a request to do a filesystem-load update. - machine.bootloader(elems) + # Note: the filename doesn't mean anything here, but still needs to be non-empty. + fwupdate.update_mpy( + filename, + fs_type=fwupdate.VFS_RAW, + fs_base=_MBOOT_SPIFLASH_BASE_ADDR + _RAW_FILESYSTEM_ADDR + block_start * block_size, + fs_len=(block_count - block_start) * block_size, + fs_base2=_MBOOT_SPIFLASH_BASE_ADDR + _RAW_FILESYSTEM_ADDR, + fs_len2=block_start * block_size, + ) diff --git a/ports/stm32/boards/LEGO_HUB_NO6/manifest.py b/ports/stm32/boards/LEGO_HUB_NO6/manifest.py index 1be4246e37..afbfdc0a82 100644 --- a/ports/stm32/boards/LEGO_HUB_NO6/manifest.py +++ b/ports/stm32/boards/LEGO_HUB_NO6/manifest.py @@ -4,5 +4,4 @@ include("$(PORT_DIR)/boards/manifest.py") # Modules for application firmware update. module("fwupdate.py", base_path="$(PORT_DIR)/mboot", opt=3) -module("spiflash.py", opt=3) module("appupdate.py", opt=3) diff --git a/ports/stm32/boards/LEGO_HUB_NO6/spiflash.c b/ports/stm32/boards/LEGO_HUB_NO6/spiflash.c new file mode 100644 index 0000000000..37df7c9530 --- /dev/null +++ b/ports/stm32/boards/LEGO_HUB_NO6/spiflash.c @@ -0,0 +1,166 @@ +/* + * 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. + */ + +#if !BUILDING_MBOOT + +#include "py/runtime.h" +#include "extmod/vfs.h" + +#include "storage.h" + +// Expose the entire external SPI flash as an object with the block protocol. + +#define FLASH_SIZE (MICROPY_HW_SPIFLASH_OFFSET_BYTES + MICROPY_HW_SPIFLASH_SIZE_BITS / 8) +#define BLOCK_SIZE (4096) + +typedef struct _spi_flash_obj_t { + mp_obj_base_t base; + uint32_t start; // in bytes + uint32_t len; // in bytes +} spi_flash_obj_t; + +static const mp_obj_type_t spi_flash_type; + +static void spi_flash_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + spi_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "SPIFlash(start=%u, len=%u)", self->start, self->len); +} + +static mp_obj_t spi_flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_start, ARG_len }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_len, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + spi_flash_obj_t *self = mp_obj_malloc(spi_flash_obj_t, &spi_flash_type); + + mp_int_t start = args[ARG_start].u_int; + if (!(0 <= start && start < FLASH_SIZE && start % BLOCK_SIZE == 0)) { + mp_raise_ValueError(NULL); + } + + mp_int_t len = args[ARG_len].u_int; + if (len == -1) { + len = FLASH_SIZE - start; + } else if (!(0 < len && start + len <= FLASH_SIZE && len % BLOCK_SIZE == 0)) { + mp_raise_ValueError(NULL); + } + + self->start = start; + self->len = len; + + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t spi_flash_readblocks(size_t n_args, const mp_obj_t *args) { + spi_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t block_num = self->start / BLOCK_SIZE + mp_obj_get_int(args[1]); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_WRITE); + int ret = spi_bdev_readblocks_raw(&spi_bdev, bufinfo.buf, block_num, 0, bufinfo.len); + return MP_OBJ_NEW_SMALL_INT(ret); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(spi_flash_readblocks_obj, 3, 3, spi_flash_readblocks); + +static mp_obj_t spi_flash_writeblocks(size_t n_args, const mp_obj_t *args) { + spi_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t block_num = self->start / BLOCK_SIZE + mp_obj_get_int(args[1]); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); + int ret = spi_bdev_eraseblocks_raw(&spi_bdev, block_num, bufinfo.len); + if (ret == 0) { + ret = spi_bdev_writeblocks_raw(&spi_bdev, bufinfo.buf, block_num, 0, bufinfo.len); + } + return MP_OBJ_NEW_SMALL_INT(ret); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(spi_flash_writeblocks_obj, 3, 3, spi_flash_writeblocks); + +static mp_obj_t spi_flash_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t arg_in) { + spi_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); + + mp_int_t cmd = mp_obj_get_int(cmd_in); + switch (cmd) { + case MP_BLOCKDEV_IOCTL_INIT: + storage_init(); + return MP_OBJ_NEW_SMALL_INT(0); + + case MP_BLOCKDEV_IOCTL_DEINIT: + return MP_OBJ_NEW_SMALL_INT(0); + + case MP_BLOCKDEV_IOCTL_SYNC: + return MP_OBJ_NEW_SMALL_INT(0); + + case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: { + mp_int_t n = self->len / BLOCK_SIZE; + return MP_OBJ_NEW_SMALL_INT(n); + } + + case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: + return MP_OBJ_NEW_SMALL_INT(BLOCK_SIZE); + + default: + return mp_const_none; + } +} +static MP_DEFINE_CONST_FUN_OBJ_3(spi_flash_ioctl_obj, spi_flash_ioctl); + +static const mp_rom_map_elem_t spi_flash_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&spi_flash_readblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&spi_flash_writeblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&spi_flash_ioctl_obj) }, +}; +static MP_DEFINE_CONST_DICT(spi_flash_locals_dict, spi_flash_locals_dict_table); + +static MP_DEFINE_CONST_OBJ_TYPE( + spi_flash_type, + MP_QSTR_SPIFlash, + MP_TYPE_FLAG_NONE, + make_new, spi_flash_make_new, + print, spi_flash_print, + locals_dict, &spi_flash_locals_dict + ); + +/******************************************************************************/ +// The `spiflash` module. + +static const mp_rom_map_elem_t spiflash_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_spiflash) }, + + { MP_ROM_QSTR(MP_QSTR_SPIFlash), MP_ROM_PTR(&spi_flash_type) }, +}; +static MP_DEFINE_CONST_DICT(spiflash_module_globals, spiflash_module_globals_table); + +const mp_obj_module_t spiflash_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&spiflash_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_spiflash, spiflash_module); + +#endif diff --git a/ports/stm32/boards/LEGO_HUB_NO6/spiflash.py b/ports/stm32/boards/LEGO_HUB_NO6/spiflash.py deleted file mode 100644 index e483ace950..0000000000 --- a/ports/stm32/boards/LEGO_HUB_NO6/spiflash.py +++ /dev/null @@ -1,90 +0,0 @@ -# MicroPython driver for SPI flash -# MIT license; Copyright (c) 2022 Damien P. George - -from micropython import const - -_PAGE_SIZE = const(256) # maximum bytes writable in one SPI transfer -_CMD_WRITE = const(0x02) -_CMD_READ = const(0x03) -_CMD_RDSR = const(0x05) -_CMD_WREN = const(0x06) -_CMD_WRITE_32 = const(0x12) -_CMD_READ_32 = const(0x13) -_CMD_SEC_ERASE = const(0x20) -_CMD_SEC_ERASE_32 = const(0x21) -_CMD_JEDEC_ID = const(0x9F) - - -class SPIFlash: - def __init__(self, spi, cs): - self.spi = spi - self.cs = cs - self.id = self._get_id() - # flash chip on Hub No. 6 uses 32-bit addressing - _32_bit = self.id == b"\xef\x40\x19" - self._READ = _CMD_READ_32 if _32_bit else _CMD_READ - self._WRITE = _CMD_WRITE_32 if _32_bit else _CMD_WRITE - self._ERASE = _CMD_SEC_ERASE_32 if _32_bit else _CMD_SEC_ERASE - - def _get_id(self): - self.cs(0) - self.spi.write(bytearray([_CMD_JEDEC_ID])) - buf = self.spi.read(3) - self.cs(1) - return buf - - def _wait_wel1(self): - # wait WEL=1 - self.cs(0) - self.spi.write(bytearray([_CMD_RDSR])) - buf = bytearray(1) - while True: - self.spi.readinto(buf) - if buf[0] & 2: - break - self.cs(1) - - def _wait_wip0(self): - # wait WIP=0 - self.cs(0) - self.spi.write(bytearray([_CMD_RDSR])) - buf = bytearray(1) - while True: - self.spi.readinto(buf) - if not (buf[0] & 1): - break - self.cs(1) - - def _flash_modify(self, cmd, addr, buf): - self.cs(0) - self.spi.write(bytearray([_CMD_WREN])) - self.cs(1) - self._wait_wel1() - self.cs(0) - self.spi.write(bytearray([cmd, addr >> 24, addr >> 16, addr >> 8, addr])) - if buf: - self.spi.write(buf) - self.cs(1) - self._wait_wip0() - - def erase_block(self, addr): - self._flash_modify(self._ERASE, addr, None) - - def readinto(self, addr, buf): - self.cs(0) - self.spi.write(bytearray([self._READ, addr >> 16, addr >> 8, addr])) - self.spi.readinto(buf) - self.cs(1) - - def write(self, addr, buf): - offset = addr & (_PAGE_SIZE - 1) - remain = len(buf) - buf = memoryview(buf) - buf_offset = 0 - while remain: - l = min(_PAGE_SIZE - offset, remain) - self._flash_modify(self._WRITE, addr, buf[buf_offset : buf_offset + l]) - remain -= l - addr += l - buf_offset += l - offset = 0