summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien George <damien@micropython.org>2024-03-14 10:28:00 +1100
committerDamien George <damien@micropython.org>2024-03-19 11:41:55 +1100
commit52c678c6f85b17e542520b44e2facaae44f61b11 (patch)
treef0c8779f35051fc56f238f58879f6a85571cb841
parent899592ac341ed9f10ee554424f964f6880fc8c48 (diff)
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 <damien@micropython.org>
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO6/appupdate.py70
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO6/manifest.py1
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO6/spiflash.c166
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO6/spiflash.py90
4 files changed, 212 insertions, 115 deletions
diff --git a/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py b/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py
index 9cfbc06e5..8229004fb 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 1be4246e3..afbfdc0a8 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 000000000..37df7c953
--- /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 e483ace95..000000000
--- 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