From 55e070128fd9b8d47ef766b60e9bec4fd9321a52 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Wed, 29 Sep 2021 14:02:14 +0200 Subject: [PATCH 01/15] pbio/platform/prime_hub: Enable SPI for flash. --- lib/pbio/platform/prime_hub/platform.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/pbio/platform/prime_hub/platform.c b/lib/pbio/platform/prime_hub/platform.c index 90df9bdbf..cdbe973ff 100644 --- a/lib/pbio/platform/prime_hub/platform.c +++ b/lib/pbio/platform/prime_hub/platform.c @@ -671,6 +671,30 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi) { gpio_init.Alternate = GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOA, &gpio_init); } + if (hspi->Instance == SPI2) { + // External flash + GPIO_InitTypeDef gpio_init; + + // /CS, active low + gpio_init.Pin = GPIO_PIN_12; + gpio_init.Mode = GPIO_MODE_OUTPUT_PP; + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + HAL_GPIO_Init(GPIOB, &gpio_init); + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); + + // SPI2_SCK + gpio_init.Pin = GPIO_PIN_13; + gpio_init.Mode = GPIO_MODE_AF_PP; + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + gpio_init.Alternate = GPIO_AF5_SPI2; + HAL_GPIO_Init(GPIOB, &gpio_init); + + // SPI2_MISO | SPI2_MOSI + gpio_init.Pin = GPIO_PIN_2 | GPIO_PIN_3; + HAL_GPIO_Init(GPIOC, &gpio_init); + } } void SPI1_IRQHandler(void) { @@ -808,7 +832,7 @@ void SystemInit(void) { RCC->APB1ENR |= RCC_APB1ENR_USART2EN | RCC_APB1ENR_UART4EN | RCC_APB1ENR_UART5EN | RCC_APB1ENR_UART7EN | RCC_APB1ENR_UART8EN | RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM4EN | RCC_APB1ENR_TIM5EN | RCC_APB1ENR_TIM6EN | RCC_APB1ENR_TIM12EN | - RCC_APB1ENR_I2C2EN | RCC_APB1ENR_DACEN; + RCC_APB1ENR_I2C2EN | RCC_APB1ENR_DACEN | RCC_APB1ENR_SPI2EN; RCC->APB2ENR |= RCC_APB2ENR_TIM1EN | RCC_APB2ENR_TIM8EN | RCC_APB2ENR_UART9EN | RCC_APB2ENR_UART10EN | RCC_APB2ENR_ADC1EN | RCC_APB2ENR_SPI1EN | RCC_APB2ENR_SYSCFGEN; RCC->AHB2ENR |= RCC_AHB2ENR_OTGFSEN; From e1754c06433140fd970941ff801b5952687cb50a Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Wed, 29 Sep 2021 14:27:52 +0200 Subject: [PATCH 02/15] pybricks.experimental: Read a file from flash. This provides some test code to experiment with the file system. --- .../experimental/pb_module_experimental.c | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) diff --git a/pybricks/experimental/pb_module_experimental.c b/pybricks/experimental/pb_module_experimental.c index 919076ab4..58e6f7cf4 100644 --- a/pybricks/experimental/pb_module_experimental.c +++ b/pybricks/experimental/pb_module_experimental.c @@ -7,11 +7,17 @@ #include "py/mphal.h" #include "py/obj.h" +#include "py/objstr.h" #include "py/runtime.h" +#include "py/mperrno.h" + +#include #include #include +#include + #include #include @@ -49,6 +55,272 @@ STATIC mp_obj_t mod_experimental_pthread_raise(mp_obj_t thread_id_in, mp_obj_t e STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_experimental_pthread_raise_obj, mod_experimental_pthread_raise); #endif // PYBRICKS_HUB_EV3BRICK +#if PYBRICKS_HUB_PRIMEHUB + +#include +#include +#include "lfs.h" + +STATIC void flash_enable(bool enable) { + if (enable) { + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); + } else { + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); + } +} + +SPI_HandleTypeDef hspi2; + +STATIC mp_obj_t experimental_flash_init_spi(void) { + + // SPI2 parameter configuration + hspi2.Instance = SPI2; + hspi2.Init.Mode = SPI_MODE_MASTER; + hspi2.Init.Direction = SPI_DIRECTION_2LINES; + hspi2.Init.DataSize = SPI_DATASIZE_8BIT; + hspi2.Init.CLKPolarity = SPI_POLARITY_LOW; + hspi2.Init.CLKPhase = SPI_PHASE_1EDGE; + hspi2.Init.NSS = SPI_NSS_SOFT; + hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; + hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB; + + if (HAL_SPI_Init(&hspi2) != HAL_OK) { + pb_assert(PBIO_ERROR_IO); + } + + // Enable flash + flash_enable(true); + + // Command parameters + uint32_t timeout = 100; + uint8_t CMD_GET_ID = 0x9F; + uint8_t CMD_EXIT_4_BYTE_ADDRESS_MODE = 0xE9; + uint8_t id_data[3]; + HAL_StatusTypeDef err; + + // Send ID command + err = HAL_SPI_Transmit(&hspi2, &CMD_GET_ID, sizeof(CMD_GET_ID), timeout); + if (err != 0) { + pb_assert(PBIO_ERROR_IO); + } + + // Get ID command reply + err = HAL_SPI_Receive(&hspi2, id_data, sizeof(id_data), timeout); + if (err != 0) { + pb_assert(PBIO_ERROR_IO); + } + flash_enable(false); + + // Verify flash device ID + if (id_data[0] != 239 || id_data[1] != 64 || id_data[2] != 25) { + pb_assert(PBIO_ERROR_NO_DEV); + } + + // Exit 4-byte address mode + flash_enable(true); + err = HAL_SPI_Transmit(&hspi2, &CMD_EXIT_4_BYTE_ADDRESS_MODE, sizeof(CMD_EXIT_4_BYTE_ADDRESS_MODE), timeout); + if (err != 0) { + pb_assert(PBIO_ERROR_IO); + } + + // Disable flash + flash_enable(false); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(experimental_flash_init_spi_obj, experimental_flash_init_spi); + +STATIC HAL_StatusTypeDef flash_read(uint32_t address, uint8_t *buffer, uint32_t size) { + + uint32_t timeout = 100; + + // First 1MiB is reserved for firmware updates with official firmware. + address += 1024 * 1024; + + uint8_t address_bytes[4]; + + // Read command + address_bytes[0] = 0x03; + + // Address as big endian + address_bytes[1] = (address & 0x00ff0000) >> 16; + address_bytes[2] = (address & 0x0000ff00) >> 8; + address_bytes[3] = address & 0x000000ff; + + // Write the read command with address + flash_enable(true); + HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, address_bytes, 4, 100); + if (err != HAL_OK) { + return err; + } + + // Receive data + err = HAL_SPI_Receive(&hspi2, buffer, size, timeout); + flash_enable(false); + return err; +} + +int block_device_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { + + lfs_size_t done = 0; + + while (done < size) { + + // How many bytes to read in one go. + lfs_size_t read_now = size - done; + if (read_now > c->read_size) { + read_now = c->read_size; + } + + // Read chunk of flash. + if (flash_read(block * c->block_size + off + done, buffer + done, read_now) != HAL_OK) { + return LFS_ERR_IO; + } + done += read_now; + + // Give MicroPython and PBIO some time. + MICROPY_EVENT_POLL_HOOK; + } + + return 0; +} + +int block_device_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { + return -1; +} + +int block_device_erase(const struct lfs_config *c, lfs_block_t block) { + return -1; +} + +int block_device_sync(const struct lfs_config *c) { + return 0; +} + +lfs_t lfs; +lfs_file_t file; + +uint8_t lfs_read_buf[512]; +uint8_t lfs_prog_buf[512]; +uint8_t lfs_lookahead_buf[64]; +uint8_t lfs_file_buf[512]; + +struct lfs_config cfg = { + .read = block_device_read, + .prog = block_device_prog, + .erase = block_device_erase, + .sync = block_device_sync, + + .read_size = 32, + .prog_size = 32, + .block_size = 4096, + .block_count = 7936, + .lookahead = 32, + + .read_buffer = lfs_read_buf, + .prog_buffer = lfs_prog_buf, + .lookahead_buffer = lfs_lookahead_buf, + .file_buffer = lfs_file_buf, +}; + + +STATIC mp_obj_t experimental_flash_init_littlefs(void) { + + uint8_t lfs_data[58]; + + // Read littlefs data + if (flash_read(0, lfs_data, sizeof(lfs_data)) != HAL_OK) { + pb_assert(PBIO_ERROR_IO); + } + + // Verify magic string for littlefs V1. Anything else is not supported. + const char *magic = "littlefs"; + if (memcmp(&lfs_data[40], magic, sizeof(magic) - 1) != 0) { + mp_print_str(&mp_plat_print, "check magic"); + pb_assert(PBIO_ERROR_NOT_SUPPORTED); + } + + // Verify block parameters + uint32_t block_size = pbio_get_uint32_le(&lfs_data[28]); + uint32_t block_count = pbio_get_uint32_le(&lfs_data[32]); + + if (cfg.block_size != block_size || cfg.block_count != block_count) { + pb_assert(PBIO_ERROR_NOT_SUPPORTED); + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(experimental_flash_init_littlefs_obj, experimental_flash_init_littlefs); + +STATIC mp_obj_t experimental_flash_read_raw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args, + PB_ARG_REQUIRED(address), + PB_ARG_REQUIRED(len)); + + uint32_t read_address = mp_obj_get_int(address_in); + uint32_t read_len = mp_obj_get_int(len_in); + + // Allocate read data + uint8_t *read_data = m_malloc(read_len); + + // Read flash + if (flash_read(read_address, read_data, read_len) != HAL_OK) { + pb_assert(PBIO_ERROR_IO); + } + + // Return bytes read + return mp_obj_new_bytes(read_data, read_len); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(experimental_flash_read_raw_obj, 0, experimental_flash_read_raw); + + +STATIC mp_obj_t experimental_flash_read_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args, + PB_ARG_DEFAULT_NONE(path)); + + // Get file path + GET_STR_DATA_LEN(path_in, path, path_len); + + // Mount file system + int lfs_err = lfs_mount(&lfs, &cfg); + if (lfs_err) { + pb_assert(PBIO_ERROR_NO_DEV); + } + + // Open file + lfs_err = lfs_file_open(&lfs, &file, (const char *)path, LFS_O_RDONLY); + switch (lfs_err) + { + case LFS_ERR_OK: + break; + case LFS_ERR_NOENT: + mp_raise_OSError(ENOENT); + break; + case LFS_ERR_ISDIR: + mp_raise_OSError(EISDIR); + default: + pb_assert(PBIO_ERROR_IO); + break; + } + + // Read file contents + uint8_t *file_buf = m_new(uint8_t, file.size); + lfs_size_t ret = lfs_file_read(&lfs, &file, file_buf, file.size); + if (((int)ret) == LFS_ERR_IO) { + pb_assert(PBIO_ERROR_IO); + } + + // Clean up + lfs_file_close(&lfs, &file); + lfs_unmount(&lfs); + + // Return data + return mp_obj_new_bytes(file_buf, file.size); +} +// See also experimental_globals_table below. This function object is added there to make it importable. +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(experimental_flash_read_file_obj, 0, experimental_flash_read_file); + +#endif // PYBRICKS_HUB_PRIMEHUB // pybricks.experimental.hello_world STATIC mp_obj_t experimental_hello_world(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -97,6 +369,12 @@ STATIC const mp_rom_map_elem_t experimental_globals_table[] = { #else { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_experimental) }, #endif // PYBRICKS_HUB_EV3BRICK + #if PYBRICKS_HUB_PRIMEHUB + { MP_ROM_QSTR(MP_QSTR_flash_init_spi), MP_ROM_PTR(&experimental_flash_init_spi_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_init_littlefs), MP_ROM_PTR(&experimental_flash_init_littlefs_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_read_raw), MP_ROM_PTR(&experimental_flash_read_raw_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_read_file), MP_ROM_PTR(&experimental_flash_read_file_obj) }, + #endif // PYBRICKS_HUB_PRIMEHUB { MP_ROM_QSTR(MP_QSTR_hello_world), MP_ROM_PTR(&experimental_hello_world_obj) }, }; STATIC MP_DEFINE_CONST_DICT(pb_module_experimental_globals, experimental_globals_table); From 8913e0a59ddb53d6e85059ae7c9e48e1601ea52e Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Sat, 2 Oct 2021 13:15:49 +0200 Subject: [PATCH 03/15] pybricks.experimental: Add basic writing. This combines the following commits: pybricks.experimental: Use 4 bytes for flash addresses. The upstream MicroPython board exits 4 byte mode, but 4 bytes are needed beyond 16Mb. pybricks.experimental: Toggle flash to activate write state. pybricks.experimental: Only write enable. Disable happens automatically on each erase and/or write. pybricks.experimental: Drop flash debug print. pybricks.experimental: Wait for write complete. --- bricks/stm32/stm32.mk | 2 +- .../experimental/pb_module_experimental.c | 218 ++++++++++++++++-- 2 files changed, 201 insertions(+), 19 deletions(-) diff --git a/bricks/stm32/stm32.mk b/bricks/stm32/stm32.mk index 8e35b2763..0233e7f16 100644 --- a/bricks/stm32/stm32.mk +++ b/bricks/stm32/stm32.mk @@ -565,7 +565,7 @@ ifeq ($(PB_LIB_BTSTACK),1) OBJ += $(addprefix $(BUILD)/, $(BTSTACK_SRC_C:.c=.o)) endif ifeq ($(PB_LIB_LITTLEFS),1) -CFLAGS+= -DLFS_NO_ASSERT -DLFS_NO_MALLOC -DLFS_NO_DEBUG -DLFS_NO_WARN -DLFS_NO_ERROR -DLFS_READONLY +CFLAGS+= -DLFS_NO_ASSERT -DLFS_NO_MALLOC -DLFS_NO_DEBUG -DLFS_NO_WARN -DLFS_NO_ERROR OBJ += $(addprefix $(BUILD)/, $(LITTLEFS_SRC_C:.c=.o)) endif ifeq ($(PB_USE_HAL),1) diff --git a/pybricks/experimental/pb_module_experimental.c b/pybricks/experimental/pb_module_experimental.c index 58e6f7cf4..6258d4033 100644 --- a/pybricks/experimental/pb_module_experimental.c +++ b/pybricks/experimental/pb_module_experimental.c @@ -94,7 +94,6 @@ STATIC mp_obj_t experimental_flash_init_spi(void) { // Command parameters uint32_t timeout = 100; uint8_t CMD_GET_ID = 0x9F; - uint8_t CMD_EXIT_4_BYTE_ADDRESS_MODE = 0xE9; uint8_t id_data[3]; HAL_StatusTypeDef err; @@ -116,19 +115,51 @@ STATIC mp_obj_t experimental_flash_init_spi(void) { pb_assert(PBIO_ERROR_NO_DEV); } - // Exit 4-byte address mode + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(experimental_flash_init_spi_obj, experimental_flash_init_spi); + +STATIC HAL_StatusTypeDef flash_status_read(uint8_t *status) { + + uint32_t timeout = 100; + + // Read status command + uint8_t command = 0x05; + + // Write the read status command flash_enable(true); - err = HAL_SPI_Transmit(&hspi2, &CMD_EXIT_4_BYTE_ADDRESS_MODE, sizeof(CMD_EXIT_4_BYTE_ADDRESS_MODE), timeout); - if (err != 0) { - pb_assert(PBIO_ERROR_IO); + HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), 100); + if (err != HAL_OK) { + return err; } - // Disable flash + // Receive data + err = HAL_SPI_Receive(&hspi2, status, 1, timeout); flash_enable(false); + return err; +} - return mp_const_none; +#define FLASH_BUSY (0x01) +#define FLASH_WRITE_ENABLED (0x02) + +STATIC HAL_StatusTypeDef flash_wait_ready() { + uint8_t status = FLASH_BUSY; + HAL_StatusTypeDef err; + uint32_t start_time = mp_hal_ticks_ms(); + + while (status & (FLASH_BUSY | FLASH_WRITE_ENABLED)) { + err = flash_status_read(&status); + if (err != HAL_OK) { + return err; + } + if (mp_hal_ticks_ms() - start_time > 100) { + return HAL_TIMEOUT; + } + MICROPY_EVENT_POLL_HOOK; + } + return HAL_OK; } -MP_DEFINE_CONST_FUN_OBJ_0(experimental_flash_init_spi_obj, experimental_flash_init_spi); + STATIC HAL_StatusTypeDef flash_read(uint32_t address, uint8_t *buffer, uint32_t size) { @@ -137,19 +168,20 @@ STATIC HAL_StatusTypeDef flash_read(uint32_t address, uint8_t *buffer, uint32_t // First 1MiB is reserved for firmware updates with official firmware. address += 1024 * 1024; - uint8_t address_bytes[4]; + uint8_t address_bytes[5]; // Read command - address_bytes[0] = 0x03; + address_bytes[0] = 0x13; // Address as big endian - address_bytes[1] = (address & 0x00ff0000) >> 16; - address_bytes[2] = (address & 0x0000ff00) >> 8; - address_bytes[3] = address & 0x000000ff; + address_bytes[1] = (address & 0xff000000) >> 24; + address_bytes[2] = (address & 0x00ff0000) >> 16; + address_bytes[3] = (address & 0x0000ff00) >> 8; + address_bytes[4] = address & 0x000000ff; // Write the read command with address flash_enable(true); - HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, address_bytes, 4, 100); + HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, address_bytes, sizeof(address_bytes), 100); if (err != HAL_OK) { return err; } @@ -160,6 +192,79 @@ STATIC HAL_StatusTypeDef flash_read(uint32_t address, uint8_t *buffer, uint32_t return err; } +STATIC HAL_StatusTypeDef write_enable() { + uint8_t command = 0x06; + flash_enable(true); + HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), 100); + flash_enable(false); + return err; +} + +STATIC HAL_StatusTypeDef flash_write(uint32_t address, const uint8_t *buffer, uint32_t size) { + + uint32_t timeout = 100; + + // First 1MiB is reserved for firmware updates with official firmware. + address += 1024 * 1024; + + uint8_t address_bytes[5]; + + // Write command + address_bytes[0] = 0x12; + + // Address as big endian + address_bytes[1] = (address & 0xff000000) >> 24; + address_bytes[2] = (address & 0x00ff0000) >> 16; + address_bytes[3] = (address & 0x0000ff00) >> 8; + address_bytes[4] = address & 0x000000ff; + + // Enable write mode + HAL_StatusTypeDef err = write_enable(); + if (err != HAL_OK) { + return err; + } + + // Write the write command with address + flash_enable(true); + err = HAL_SPI_Transmit(&hspi2, address_bytes, sizeof(address_bytes), timeout); + if (err != HAL_OK) { + flash_enable(false); + return err; + } + + // Write the data + err = HAL_SPI_Transmit(&hspi2, (uint8_t *)buffer, size, timeout); + flash_enable(false); + return flash_wait_ready(); +} + +STATIC HAL_StatusTypeDef flash_block_erase(uint32_t address) { + + address += 1024 * 1024; + + // Enable write mode + HAL_StatusTypeDef err = write_enable(); + if (err != HAL_OK) { + return err; + } + + uint8_t address_bytes[5]; + address_bytes[0] = 0x21; + address_bytes[1] = (address & 0xff000000) >> 24; + address_bytes[2] = (address & 0x00ff0000) >> 16; + address_bytes[3] = (address & 0x0000ff00) >> 8; + address_bytes[4] = address & 0x000000ff; + + // Send erase command + flash_enable(true); + err = HAL_SPI_Transmit(&hspi2, address_bytes, sizeof(address_bytes), 100); + flash_enable(false); + + err = flash_wait_ready(); + + return err; +} + int block_device_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { lfs_size_t done = 0; @@ -181,16 +286,48 @@ int block_device_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t o // Give MicroPython and PBIO some time. MICROPY_EVENT_POLL_HOOK; } - return 0; } int block_device_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { - return -1; + lfs_size_t done = 0; + + while (done < size) { + + // How many bytes to write in one go. + lfs_size_t write_now = size - done; + + // Must not be larger than write size + if (write_now > c->prog_size) { + write_now = c->prog_size; + } + + // Must not wrap around the page + lfs_size_t write_max = 256 - ((off + done) & 0xFF); + if (write_now > write_max) { + write_now = write_max; + } + + // Write chunk of flash. + if (flash_write(block * c->block_size + off + done, buffer + done, write_now) != HAL_OK) { + return LFS_ERR_IO; + } + done += write_now; + + // Give MicroPython and PBIO some time. + MICROPY_EVENT_POLL_HOOK; + } + + return 0; } int block_device_erase(const struct lfs_config *c, lfs_block_t block) { - return -1; + // Erase block of flash. Block size assumed to match erase size. + + if (flash_block_erase(block * c->block_size) != HAL_OK) { + return LFS_ERR_IO; + } + return LFS_ERR_OK; } int block_device_sync(const struct lfs_config *c) { @@ -236,7 +373,6 @@ STATIC mp_obj_t experimental_flash_init_littlefs(void) { // Verify magic string for littlefs V1. Anything else is not supported. const char *magic = "littlefs"; if (memcmp(&lfs_data[40], magic, sizeof(magic) - 1) != 0) { - mp_print_str(&mp_plat_print, "check magic"); pb_assert(PBIO_ERROR_NOT_SUPPORTED); } @@ -320,6 +456,51 @@ STATIC mp_obj_t experimental_flash_read_file(size_t n_args, const mp_obj_t *pos_ // See also experimental_globals_table below. This function object is added there to make it importable. STATIC MP_DEFINE_CONST_FUN_OBJ_KW(experimental_flash_read_file_obj, 0, experimental_flash_read_file); + +STATIC mp_obj_t experimental_flash_write_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args, + PB_ARG_REQUIRED(path), + PB_ARG_REQUIRED(data)); + + // Get file path + GET_STR_DATA_LEN(path_in, path, path_len); + GET_STR_DATA_LEN(data_in, data, data_len); + + // Mount file system + int lfs_err = lfs_mount(&lfs, &cfg); + if (lfs_err) { + pb_assert(PBIO_ERROR_NO_DEV); + } + + // Open file + lfs_err = lfs_file_open(&lfs, &file, (const char *)path, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC); + + switch (lfs_err) + { + case LFS_ERR_OK: + break; + case LFS_ERR_ISDIR: + mp_raise_OSError(EISDIR); + default: + pb_assert(PBIO_ERROR_IO); + break; + } + + // write file contents + lfs_size_t ret = lfs_file_write(&lfs, &file, data, data_len); + if (((int)ret) == LFS_ERR_IO) { + pb_assert(PBIO_ERROR_IO); + } + + // Clean up + lfs_file_close(&lfs, &file); + lfs_unmount(&lfs); + + return mp_const_none; +} +// See also experimental_globals_table below. This function object is added there to make it importable. +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(experimental_flash_write_file_obj, 0, experimental_flash_write_file); + #endif // PYBRICKS_HUB_PRIMEHUB // pybricks.experimental.hello_world @@ -374,6 +555,7 @@ STATIC const mp_rom_map_elem_t experimental_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_flash_init_littlefs), MP_ROM_PTR(&experimental_flash_init_littlefs_obj) }, { MP_ROM_QSTR(MP_QSTR_flash_read_raw), MP_ROM_PTR(&experimental_flash_read_raw_obj) }, { MP_ROM_QSTR(MP_QSTR_flash_read_file), MP_ROM_PTR(&experimental_flash_read_file_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_write_file), MP_ROM_PTR(&experimental_flash_write_file_obj) }, #endif // PYBRICKS_HUB_PRIMEHUB { MP_ROM_QSTR(MP_QSTR_hello_world), MP_ROM_PTR(&experimental_hello_world_obj) }, }; From 5e3f2afa893f958f2af369b559daf55b38139a4a Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 4 Oct 2021 13:18:40 +0200 Subject: [PATCH 04/15] pybricks.experimental: Refactor flash code. Make it somewhat more generic so we can experiment with reading/saving a main.py user script from main.c Also enlarge buffers. Also add mkdir command to create /_pybricks if it doesn't exist. --- bricks/stm32/stm32.mk | 1 + .../experimental/pb_module_experimental.c | 407 +-------------- pybricks/util_pb/pb_flash.c | 470 ++++++++++++++++++ pybricks/util_pb/pb_flash.h | 27 + 4 files changed, 507 insertions(+), 398 deletions(-) create mode 100644 pybricks/util_pb/pb_flash.c create mode 100644 pybricks/util_pb/pb_flash.h diff --git a/bricks/stm32/stm32.mk b/bricks/stm32/stm32.mk index 0233e7f16..2d002a597 100644 --- a/bricks/stm32/stm32.mk +++ b/bricks/stm32/stm32.mk @@ -264,6 +264,7 @@ PYBRICKS_PYBRICKS_SRC_C = $(addprefix pybricks/,\ util_pb/pb_conversions.c \ util_pb/pb_device_stm32.c \ util_pb/pb_error.c \ + util_pb/pb_flash.c \ util_pb/pb_imu.c \ util_pb/pb_task.c \ ) diff --git a/pybricks/experimental/pb_module_experimental.c b/pybricks/experimental/pb_module_experimental.c index 6258d4033..af924bc45 100644 --- a/pybricks/experimental/pb_module_experimental.c +++ b/pybricks/experimental/pb_module_experimental.c @@ -57,336 +57,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_experimental_pthread_raise_obj, mod_experim #if PYBRICKS_HUB_PRIMEHUB -#include -#include -#include "lfs.h" - -STATIC void flash_enable(bool enable) { - if (enable) { - HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); - } else { - HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); - } -} - -SPI_HandleTypeDef hspi2; - -STATIC mp_obj_t experimental_flash_init_spi(void) { - - // SPI2 parameter configuration - hspi2.Instance = SPI2; - hspi2.Init.Mode = SPI_MODE_MASTER; - hspi2.Init.Direction = SPI_DIRECTION_2LINES; - hspi2.Init.DataSize = SPI_DATASIZE_8BIT; - hspi2.Init.CLKPolarity = SPI_POLARITY_LOW; - hspi2.Init.CLKPhase = SPI_PHASE_1EDGE; - hspi2.Init.NSS = SPI_NSS_SOFT; - hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; - hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB; - - if (HAL_SPI_Init(&hspi2) != HAL_OK) { - pb_assert(PBIO_ERROR_IO); - } - - // Enable flash - flash_enable(true); - - // Command parameters - uint32_t timeout = 100; - uint8_t CMD_GET_ID = 0x9F; - uint8_t id_data[3]; - HAL_StatusTypeDef err; - - // Send ID command - err = HAL_SPI_Transmit(&hspi2, &CMD_GET_ID, sizeof(CMD_GET_ID), timeout); - if (err != 0) { - pb_assert(PBIO_ERROR_IO); - } - - // Get ID command reply - err = HAL_SPI_Receive(&hspi2, id_data, sizeof(id_data), timeout); - if (err != 0) { - pb_assert(PBIO_ERROR_IO); - } - flash_enable(false); - - // Verify flash device ID - if (id_data[0] != 239 || id_data[1] != 64 || id_data[2] != 25) { - pb_assert(PBIO_ERROR_NO_DEV); - } - - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_0(experimental_flash_init_spi_obj, experimental_flash_init_spi); - -STATIC HAL_StatusTypeDef flash_status_read(uint8_t *status) { - - uint32_t timeout = 100; - - // Read status command - uint8_t command = 0x05; - - // Write the read status command - flash_enable(true); - HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), 100); - if (err != HAL_OK) { - return err; - } - - // Receive data - err = HAL_SPI_Receive(&hspi2, status, 1, timeout); - flash_enable(false); - return err; -} - -#define FLASH_BUSY (0x01) -#define FLASH_WRITE_ENABLED (0x02) - -STATIC HAL_StatusTypeDef flash_wait_ready() { - uint8_t status = FLASH_BUSY; - HAL_StatusTypeDef err; - uint32_t start_time = mp_hal_ticks_ms(); - - while (status & (FLASH_BUSY | FLASH_WRITE_ENABLED)) { - err = flash_status_read(&status); - if (err != HAL_OK) { - return err; - } - if (mp_hal_ticks_ms() - start_time > 100) { - return HAL_TIMEOUT; - } - MICROPY_EVENT_POLL_HOOK; - } - return HAL_OK; -} - - -STATIC HAL_StatusTypeDef flash_read(uint32_t address, uint8_t *buffer, uint32_t size) { - - uint32_t timeout = 100; - - // First 1MiB is reserved for firmware updates with official firmware. - address += 1024 * 1024; - - uint8_t address_bytes[5]; - - // Read command - address_bytes[0] = 0x13; - - // Address as big endian - address_bytes[1] = (address & 0xff000000) >> 24; - address_bytes[2] = (address & 0x00ff0000) >> 16; - address_bytes[3] = (address & 0x0000ff00) >> 8; - address_bytes[4] = address & 0x000000ff; - - // Write the read command with address - flash_enable(true); - HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, address_bytes, sizeof(address_bytes), 100); - if (err != HAL_OK) { - return err; - } - - // Receive data - err = HAL_SPI_Receive(&hspi2, buffer, size, timeout); - flash_enable(false); - return err; -} - -STATIC HAL_StatusTypeDef write_enable() { - uint8_t command = 0x06; - flash_enable(true); - HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), 100); - flash_enable(false); - return err; -} - -STATIC HAL_StatusTypeDef flash_write(uint32_t address, const uint8_t *buffer, uint32_t size) { - - uint32_t timeout = 100; - - // First 1MiB is reserved for firmware updates with official firmware. - address += 1024 * 1024; - - uint8_t address_bytes[5]; - - // Write command - address_bytes[0] = 0x12; - - // Address as big endian - address_bytes[1] = (address & 0xff000000) >> 24; - address_bytes[2] = (address & 0x00ff0000) >> 16; - address_bytes[3] = (address & 0x0000ff00) >> 8; - address_bytes[4] = address & 0x000000ff; - - // Enable write mode - HAL_StatusTypeDef err = write_enable(); - if (err != HAL_OK) { - return err; - } - - // Write the write command with address - flash_enable(true); - err = HAL_SPI_Transmit(&hspi2, address_bytes, sizeof(address_bytes), timeout); - if (err != HAL_OK) { - flash_enable(false); - return err; - } - - // Write the data - err = HAL_SPI_Transmit(&hspi2, (uint8_t *)buffer, size, timeout); - flash_enable(false); - return flash_wait_ready(); -} - -STATIC HAL_StatusTypeDef flash_block_erase(uint32_t address) { - - address += 1024 * 1024; - - // Enable write mode - HAL_StatusTypeDef err = write_enable(); - if (err != HAL_OK) { - return err; - } - - uint8_t address_bytes[5]; - address_bytes[0] = 0x21; - address_bytes[1] = (address & 0xff000000) >> 24; - address_bytes[2] = (address & 0x00ff0000) >> 16; - address_bytes[3] = (address & 0x0000ff00) >> 8; - address_bytes[4] = address & 0x000000ff; - - // Send erase command - flash_enable(true); - err = HAL_SPI_Transmit(&hspi2, address_bytes, sizeof(address_bytes), 100); - flash_enable(false); - - err = flash_wait_ready(); - - return err; -} - -int block_device_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { - - lfs_size_t done = 0; - - while (done < size) { - - // How many bytes to read in one go. - lfs_size_t read_now = size - done; - if (read_now > c->read_size) { - read_now = c->read_size; - } - - // Read chunk of flash. - if (flash_read(block * c->block_size + off + done, buffer + done, read_now) != HAL_OK) { - return LFS_ERR_IO; - } - done += read_now; - - // Give MicroPython and PBIO some time. - MICROPY_EVENT_POLL_HOOK; - } - return 0; -} - -int block_device_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { - lfs_size_t done = 0; - - while (done < size) { - - // How many bytes to write in one go. - lfs_size_t write_now = size - done; - - // Must not be larger than write size - if (write_now > c->prog_size) { - write_now = c->prog_size; - } - - // Must not wrap around the page - lfs_size_t write_max = 256 - ((off + done) & 0xFF); - if (write_now > write_max) { - write_now = write_max; - } - - // Write chunk of flash. - if (flash_write(block * c->block_size + off + done, buffer + done, write_now) != HAL_OK) { - return LFS_ERR_IO; - } - done += write_now; - - // Give MicroPython and PBIO some time. - MICROPY_EVENT_POLL_HOOK; - } - - return 0; -} - -int block_device_erase(const struct lfs_config *c, lfs_block_t block) { - // Erase block of flash. Block size assumed to match erase size. - - if (flash_block_erase(block * c->block_size) != HAL_OK) { - return LFS_ERR_IO; - } - return LFS_ERR_OK; -} - -int block_device_sync(const struct lfs_config *c) { - return 0; -} - -lfs_t lfs; -lfs_file_t file; - -uint8_t lfs_read_buf[512]; -uint8_t lfs_prog_buf[512]; -uint8_t lfs_lookahead_buf[64]; -uint8_t lfs_file_buf[512]; - -struct lfs_config cfg = { - .read = block_device_read, - .prog = block_device_prog, - .erase = block_device_erase, - .sync = block_device_sync, - - .read_size = 32, - .prog_size = 32, - .block_size = 4096, - .block_count = 7936, - .lookahead = 32, - - .read_buffer = lfs_read_buf, - .prog_buffer = lfs_prog_buf, - .lookahead_buffer = lfs_lookahead_buf, - .file_buffer = lfs_file_buf, -}; - - -STATIC mp_obj_t experimental_flash_init_littlefs(void) { - - uint8_t lfs_data[58]; - - // Read littlefs data - if (flash_read(0, lfs_data, sizeof(lfs_data)) != HAL_OK) { - pb_assert(PBIO_ERROR_IO); - } - - // Verify magic string for littlefs V1. Anything else is not supported. - const char *magic = "littlefs"; - if (memcmp(&lfs_data[40], magic, sizeof(magic) - 1) != 0) { - pb_assert(PBIO_ERROR_NOT_SUPPORTED); - } - - // Verify block parameters - uint32_t block_size = pbio_get_uint32_le(&lfs_data[28]); - uint32_t block_count = pbio_get_uint32_le(&lfs_data[32]); - - if (cfg.block_size != block_size || cfg.block_count != block_count) { - pb_assert(PBIO_ERROR_NOT_SUPPORTED); - } - - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_0(experimental_flash_init_littlefs_obj, experimental_flash_init_littlefs); +#include STATIC mp_obj_t experimental_flash_read_raw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args, @@ -400,16 +71,13 @@ STATIC mp_obj_t experimental_flash_read_raw(size_t n_args, const mp_obj_t *pos_a uint8_t *read_data = m_malloc(read_len); // Read flash - if (flash_read(read_address, read_data, read_len) != HAL_OK) { - pb_assert(PBIO_ERROR_IO); - } + pb_assert(pb_flash_raw_read(read_address, read_data, read_len)); // Return bytes read return mp_obj_new_bytes(read_data, read_len); } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(experimental_flash_read_raw_obj, 0, experimental_flash_read_raw); - STATIC mp_obj_t experimental_flash_read_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args, PB_ARG_DEFAULT_NONE(path)); @@ -417,46 +85,19 @@ STATIC mp_obj_t experimental_flash_read_file(size_t n_args, const mp_obj_t *pos_ // Get file path GET_STR_DATA_LEN(path_in, path, path_len); - // Mount file system - int lfs_err = lfs_mount(&lfs, &cfg); - if (lfs_err) { - pb_assert(PBIO_ERROR_NO_DEV); - } - - // Open file - lfs_err = lfs_file_open(&lfs, &file, (const char *)path, LFS_O_RDONLY); - switch (lfs_err) - { - case LFS_ERR_OK: - break; - case LFS_ERR_NOENT: - mp_raise_OSError(ENOENT); - break; - case LFS_ERR_ISDIR: - mp_raise_OSError(EISDIR); - default: - pb_assert(PBIO_ERROR_IO); - break; - } + // Mount the file system and open the file + uint32_t size; + pb_assert(pb_flash_file_open_get_size((const char *)path, &size)); // Read file contents - uint8_t *file_buf = m_new(uint8_t, file.size); - lfs_size_t ret = lfs_file_read(&lfs, &file, file_buf, file.size); - if (((int)ret) == LFS_ERR_IO) { - pb_assert(PBIO_ERROR_IO); - } - - // Clean up - lfs_file_close(&lfs, &file); - lfs_unmount(&lfs); + uint8_t *file_buf = m_new(uint8_t, size); + pb_assert(pb_flash_file_read(file_buf, size)); // Return data - return mp_obj_new_bytes(file_buf, file.size); + return mp_obj_new_bytes(file_buf, size); } -// See also experimental_globals_table below. This function object is added there to make it importable. STATIC MP_DEFINE_CONST_FUN_OBJ_KW(experimental_flash_read_file_obj, 0, experimental_flash_read_file); - STATIC mp_obj_t experimental_flash_write_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args, PB_ARG_REQUIRED(path), @@ -466,35 +107,7 @@ STATIC mp_obj_t experimental_flash_write_file(size_t n_args, const mp_obj_t *pos GET_STR_DATA_LEN(path_in, path, path_len); GET_STR_DATA_LEN(data_in, data, data_len); - // Mount file system - int lfs_err = lfs_mount(&lfs, &cfg); - if (lfs_err) { - pb_assert(PBIO_ERROR_NO_DEV); - } - - // Open file - lfs_err = lfs_file_open(&lfs, &file, (const char *)path, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC); - - switch (lfs_err) - { - case LFS_ERR_OK: - break; - case LFS_ERR_ISDIR: - mp_raise_OSError(EISDIR); - default: - pb_assert(PBIO_ERROR_IO); - break; - } - - // write file contents - lfs_size_t ret = lfs_file_write(&lfs, &file, data, data_len); - if (((int)ret) == LFS_ERR_IO) { - pb_assert(PBIO_ERROR_IO); - } - - // Clean up - lfs_file_close(&lfs, &file); - lfs_unmount(&lfs); + pb_assert(pb_flash_file_write((const char *)path, (const uint8_t *)data, data_len)); return mp_const_none; } @@ -551,8 +164,6 @@ STATIC const mp_rom_map_elem_t experimental_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_experimental) }, #endif // PYBRICKS_HUB_EV3BRICK #if PYBRICKS_HUB_PRIMEHUB - { MP_ROM_QSTR(MP_QSTR_flash_init_spi), MP_ROM_PTR(&experimental_flash_init_spi_obj) }, - { MP_ROM_QSTR(MP_QSTR_flash_init_littlefs), MP_ROM_PTR(&experimental_flash_init_littlefs_obj) }, { MP_ROM_QSTR(MP_QSTR_flash_read_raw), MP_ROM_PTR(&experimental_flash_read_raw_obj) }, { MP_ROM_QSTR(MP_QSTR_flash_read_file), MP_ROM_PTR(&experimental_flash_read_file_obj) }, { MP_ROM_QSTR(MP_QSTR_flash_write_file), MP_ROM_PTR(&experimental_flash_write_file_obj) }, diff --git a/pybricks/util_pb/pb_flash.c b/pybricks/util_pb/pb_flash.c new file mode 100644 index 000000000..0003cae18 --- /dev/null +++ b/pybricks/util_pb/pb_flash.c @@ -0,0 +1,470 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2021 The Pybricks Authors + + + +#include "py/mpconfig.h" + +#if PYBRICKS_HUB_PRIMEHUB + +#include + +#include +#include + +#include +#include + +#include "py/mphal.h" + +#include "lfs.h" + +#include + +SPI_HandleTypeDef hspi2; + +// Whether the SPI and filesystem have been initialized once after boot. +static bool flash_initialized = false; + +// True sets the notCS pin, False resets it +static void flash_enable(bool enable) { + if (enable) { + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); + } else { + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); + } +} + +// Configure SPI +static pbio_error_t pb_flash_spi_init(void) { + + // SPI2 parameter configuration + hspi2.Instance = SPI2; + hspi2.Init.Mode = SPI_MODE_MASTER; + hspi2.Init.Direction = SPI_DIRECTION_2LINES; + hspi2.Init.DataSize = SPI_DATASIZE_8BIT; + hspi2.Init.CLKPolarity = SPI_POLARITY_LOW; + hspi2.Init.CLKPhase = SPI_PHASE_1EDGE; + hspi2.Init.NSS = SPI_NSS_SOFT; + hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; + hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB; + + // Save settings + if (HAL_SPI_Init(&hspi2) != HAL_OK) { + return PBIO_ERROR_IO; + } + + // Enable flash + flash_enable(true); + + // Command parameters + uint32_t timeout = 100; + uint8_t CMD_GET_ID = 0x9F; + uint8_t id_data[3]; + HAL_StatusTypeDef err; + + // Send ID command + err = HAL_SPI_Transmit(&hspi2, &CMD_GET_ID, sizeof(CMD_GET_ID), timeout); + if (err != 0) { + return PBIO_ERROR_IO; + } + + // Get ID command reply + err = HAL_SPI_Receive(&hspi2, id_data, sizeof(id_data), timeout); + if (err != 0) { + return PBIO_ERROR_IO; + } + flash_enable(false); + + // Verify flash device ID + if (id_data[0] != 239 || id_data[1] != 64 || id_data[2] != 25) { + return PBIO_ERROR_NO_DEV; + } + + // Set flag to indicate flash is ready + flash_initialized = true; + + return PBIO_SUCCESS; +} + +static HAL_StatusTypeDef flash_status_read(uint8_t *status) { + + uint32_t timeout = 100; + + // Read status command + uint8_t command = 0x05; + + // Write the read status command + flash_enable(true); + HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), 100); + if (err != HAL_OK) { + return err; + } + + // Receive data + err = HAL_SPI_Receive(&hspi2, status, 1, timeout); + flash_enable(false); + return err; +} + +static HAL_StatusTypeDef flash_wait_ready() { + uint8_t status = 0x03; + HAL_StatusTypeDef err; + uint32_t start_time = mp_hal_ticks_ms(); + + // While write enabled and / or busy, wait + while (status & 0x03) { + err = flash_status_read(&status); + if (err != HAL_OK) { + return err; + } + if (mp_hal_ticks_ms() - start_time > 100) { + return HAL_TIMEOUT; + } + MICROPY_EVENT_POLL_HOOK; + } + return HAL_OK; +} + +static HAL_StatusTypeDef flash_read(uint32_t address, uint8_t *buffer, uint32_t size) { + + uint32_t timeout = 100; + + // First 1MiB is reserved for firmware updates with official firmware. + address += 1024 * 1024; + + uint8_t address_bytes[5]; + + // Read command + address_bytes[0] = 0x13; + + // Address as big endian + address_bytes[1] = (address & 0xff000000) >> 24; + address_bytes[2] = (address & 0x00ff0000) >> 16; + address_bytes[3] = (address & 0x0000ff00) >> 8; + address_bytes[4] = address & 0x000000ff; + + // Write the read command with address + flash_enable(true); + HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, address_bytes, sizeof(address_bytes), 100); + if (err != HAL_OK) { + return err; + } + + // Receive data + err = HAL_SPI_Receive(&hspi2, buffer, size, timeout); + flash_enable(false); + return err; +} + +static HAL_StatusTypeDef write_enable() { + uint8_t command = 0x06; + flash_enable(true); + HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), 100); + flash_enable(false); + return err; +} + +static HAL_StatusTypeDef flash_write(uint32_t address, const uint8_t *buffer, uint32_t size) { + + uint32_t timeout = 100; + + // First 1MiB is reserved for firmware updates with official firmware. + address += 1024 * 1024; + + uint8_t address_bytes[5]; + + // Write command + address_bytes[0] = 0x12; + + // Address as big endian + address_bytes[1] = (address & 0xff000000) >> 24; + address_bytes[2] = (address & 0x00ff0000) >> 16; + address_bytes[3] = (address & 0x0000ff00) >> 8; + address_bytes[4] = address & 0x000000ff; + + // Enable write mode + HAL_StatusTypeDef err = write_enable(); + if (err != HAL_OK) { + return err; + } + + // Write the write command with address + flash_enable(true); + err = HAL_SPI_Transmit(&hspi2, address_bytes, sizeof(address_bytes), timeout); + if (err != HAL_OK) { + flash_enable(false); + return err; + } + + // Write the data + err = HAL_SPI_Transmit(&hspi2, (uint8_t *)buffer, size, timeout); + flash_enable(false); + return flash_wait_ready(); +} + +static HAL_StatusTypeDef flash_block_erase(uint32_t address) { + + address += 1024 * 1024; + + // Enable write mode + HAL_StatusTypeDef err = write_enable(); + if (err != HAL_OK) { + return err; + } + + uint8_t address_bytes[5]; + address_bytes[0] = 0x21; + address_bytes[1] = (address & 0xff000000) >> 24; + address_bytes[2] = (address & 0x00ff0000) >> 16; + address_bytes[3] = (address & 0x0000ff00) >> 8; + address_bytes[4] = address & 0x000000ff; + + // Send erase command + flash_enable(true); + err = HAL_SPI_Transmit(&hspi2, address_bytes, sizeof(address_bytes), 100); + flash_enable(false); + + err = flash_wait_ready(); + + return err; +} + +int block_device_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { + + lfs_size_t done = 0; + + while (done < size) { + + // How many bytes to read in one go. + lfs_size_t read_now = size - done; + if (read_now > c->read_size) { + read_now = c->read_size; + } + + // Read chunk of flash. + if (flash_read(block * c->block_size + off + done, buffer + done, read_now) != HAL_OK) { + return LFS_ERR_IO; + } + done += read_now; + + // Give MicroPython and PBIO some time. + MICROPY_EVENT_POLL_HOOK; + } + return 0; +} + +int block_device_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { + lfs_size_t done = 0; + + while (done < size) { + + // How many bytes to write in one go. + lfs_size_t write_now = size - done; + + // Must not be larger than write size + if (write_now > c->prog_size) { + write_now = c->prog_size; + } + + // Must not wrap around the page + lfs_size_t write_max = 256 - ((off + done) & 0xFF); + if (write_now > write_max) { + write_now = write_max; + } + + // Write chunk of flash. + if (flash_write(block * c->block_size + off + done, buffer + done, write_now) != HAL_OK) { + return LFS_ERR_IO; + } + done += write_now; + + // Give MicroPython and PBIO some time. + MICROPY_EVENT_POLL_HOOK; + } + + return 0; +} + +int block_device_erase(const struct lfs_config *c, lfs_block_t block) { + // Erase block of flash. Block size assumed to match erase size. + + if (flash_block_erase(block * c->block_size) != HAL_OK) { + return LFS_ERR_IO; + } + return LFS_ERR_OK; +} + +int block_device_sync(const struct lfs_config *c) { + return 0; +} + +lfs_t lfs; +lfs_file_t file; + +uint8_t lfs_read_buf[256]; +uint8_t lfs_prog_buf[256]; +uint8_t lfs_lookahead_buf[32]; +uint8_t lfs_file_buf[256]; + +struct lfs_config cfg = { + .read = block_device_read, + .prog = block_device_prog, + .erase = block_device_erase, + .sync = block_device_sync, + + .read_size = 256, + .prog_size = 256, + .block_size = 4096, + .block_count = 7936, + .lookahead = 32, + + .read_buffer = lfs_read_buf, + .prog_buffer = lfs_prog_buf, + .lookahead_buffer = lfs_lookahead_buf, + .file_buffer = lfs_file_buf, +}; + +static pbio_error_t pb_flash_init(void) { + + // Init SPI + pbio_error_t err = pb_flash_spi_init(); + if (err != PBIO_SUCCESS) { + return err; + } + + uint8_t lfs_data[58]; + + // Read littlefs data + if (flash_read(0, lfs_data, sizeof(lfs_data)) != HAL_OK) { + return PBIO_ERROR_IO; + } + + // Verify magic string for littlefs V1. Anything else is not supported. + const char *magic = "littlefs"; + if (memcmp(&lfs_data[40], magic, sizeof(magic) - 1) != 0) { + return PBIO_ERROR_NOT_SUPPORTED; + } + + // Verify block parameters + uint32_t block_size = pbio_get_uint32_le(&lfs_data[28]); + uint32_t block_count = pbio_get_uint32_le(&lfs_data[32]); + + if (cfg.block_size != block_size || cfg.block_count != block_count) { + return PBIO_ERROR_NOT_SUPPORTED; + } + + flash_initialized = true; + return PBIO_SUCCESS; +} + +pbio_error_t pb_flash_raw_read(uint32_t address, uint8_t *buf, uint32_t size) { + + if (!flash_initialized) { + pbio_error_t err = pb_flash_spi_init(); + if (err != PBIO_SUCCESS) { + return err; + } + } + + if (flash_read(address, buf, size) != HAL_OK) { + return PBIO_ERROR_FAILED; + } + return PBIO_SUCCESS; +} + +pbio_error_t pb_flash_file_open_get_size(const char *path, uint32_t *size) { + + // Check that flash was initialized + if (!flash_initialized) { + pbio_error_t err = pb_flash_spi_init(); + if (err != PBIO_SUCCESS) { + return err; + } + } + + // Mount file system + if (lfs_mount(&lfs, &cfg) != LFS_ERR_OK) { + return PBIO_ERROR_FAILED; + } + + // Open file + if (lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) != LFS_ERR_OK) { + return PBIO_ERROR_FAILED; + } + *size = file.size; + return PBIO_SUCCESS; +} + +pbio_error_t pb_flash_file_read(uint8_t *buf, uint32_t size) { + + // Check that flash was initialized + if (!flash_initialized) { + pbio_error_t err = pb_flash_spi_init(); + if (err != PBIO_SUCCESS) { + return err; + } + } + + // Allow only read in one go for now + if (size != file.size) { + return PBIO_ERROR_INVALID_ARG; + } + + // Read whole file to buffer + if (lfs_file_read(&lfs, &file, buf, size) != size) { + return PBIO_ERROR_FAILED; + } + + // Close the file and unmount + if (lfs_file_close(&lfs, &file) != LFS_ERR_OK) { + return PBIO_ERROR_FAILED; + } + if (lfs_unmount(&lfs) != LFS_ERR_OK) { + return PBIO_ERROR_FAILED; + } + return PBIO_SUCCESS; +} + +pbio_error_t pb_flash_file_write(const char *path, const uint8_t *buf, uint32_t size) { + + // Check that flash was initialized + if (!flash_initialized) { + pbio_error_t err = pb_flash_init(); + if (err != PBIO_SUCCESS) { + return err; + } + } + + // Mount file system + if (lfs_mount(&lfs, &cfg) != LFS_ERR_OK) { + return PBIO_ERROR_FAILED; + } + + // Ensure there is a _pybricks system folder + int lfs_err = lfs_mkdir(&lfs, "_pybricks"); + if (!(lfs_err == LFS_ERR_OK || lfs_err == LFS_ERR_EXIST)) { + return PBIO_ERROR_FAILED; + } + + // Open file + if (lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) != LFS_ERR_OK) { + return PBIO_ERROR_FAILED; + } + + // write file contents + if (lfs_file_write(&lfs, &file, buf, size) != size) { + return PBIO_ERROR_FAILED; + } + + // Close the file and unmount + if (lfs_file_close(&lfs, &file) != LFS_ERR_OK) { + return PBIO_ERROR_FAILED; + } + if (lfs_unmount(&lfs) != LFS_ERR_OK) { + return PBIO_ERROR_FAILED; + } + return PBIO_SUCCESS; +} + +#endif // PYBRICKS_HUB_PRIMEHUB diff --git a/pybricks/util_pb/pb_flash.h b/pybricks/util_pb/pb_flash.h new file mode 100644 index 000000000..1d1e3b954 --- /dev/null +++ b/pybricks/util_pb/pb_flash.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2021 The Pybricks Authors + +#ifndef _PB_FLASH_H_ +#define _PB_FLASH_H_ + +#if PYBRICKS_HUB_PRIMEHUB + +#include + +#include + +// Read raw bytes from flash, ignoring file system +pbio_error_t pb_flash_raw_read(uint32_t address, uint8_t *buf, uint32_t size); + +// Open one file and get its size. +pbio_error_t pb_flash_file_open_get_size(const char *path, uint32_t *size); + +// Read the file that has been opened, all at once, then close. +pbio_error_t pb_flash_file_read(uint8_t *buf, uint32_t size); + +// Open a file for writing, write data, anc close. +pbio_error_t pb_flash_file_write(const char *path, const uint8_t *buf, uint32_t size); + +#endif + +#endif // _PB_FLASH_H_ From de725ff7888d2567d2c69f217a3413e03871ee61 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 4 Oct 2021 15:10:25 +0200 Subject: [PATCH 05/15] bricks/stm32/main: Save programs on Primehub. This hub has dedicated storage space for user programs. Now the downloaded program is stored so it can be started with the button later. It is saved to /_pybricks/main.mpy. This needs to be reworked extensively to make file access clean and non-blocking, but it makes the hub much more useable for now. --- bricks/stm32/main.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/bricks/stm32/main.c b/bricks/stm32/main.c index 4ff654c80..bcaaf2a17 100644 --- a/bricks/stm32/main.c +++ b/bricks/stm32/main.c @@ -14,6 +14,7 @@ #include #include +#include #include "shared/readline/readline.h" #include "shared/runtime/gchelper.h" @@ -185,8 +186,32 @@ static uint32_t get_user_program(uint8_t **buf, uint32_t *free_len) { // If button was pressed, return code to run script in flash if (err == PBIO_ERROR_CANCELED) { + #if PYBRICKS_HUB_PRIMEHUB + // Open existing main.mpy file from flash + uint32_t size = 0; + if (pb_flash_file_open_get_size("/_pybricks/main.mpy", &size) != PBIO_SUCCESS) { + return 0; + } + // Check size and allocate buffer + if (size > MPY_MAX_BYTES) { + return 0; + } + *buf = m_malloc(size); + if (*buf == NULL) { + return 0; + } + // Read the file contents + if (pb_flash_file_read(*buf, size) != PBIO_SUCCESS) { + m_free(*buf); + return 0; + } + *free_len = size; + return size; + #else + // Load main program embedded in firmware *buf = &_pb_user_mpy_data; return _pb_user_mpy_size; + #endif } // Handle other errors @@ -220,6 +245,12 @@ static uint32_t get_user_program(uint8_t **buf, uint32_t *free_len) { } *free_len = len; + + #if PYBRICKS_HUB_PRIMEHUB + // Save program as file + pb_flash_file_write("/_pybricks/main.mpy", *buf, len); + #endif // PYBRICKS_HUB_PRIMEHUB + return len; } From d357484af93da7bc71b9a3a69ca0aad7a751c60f Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Wed, 6 Oct 2021 12:50:47 +0200 Subject: [PATCH 06/15] pybricks/util_pb/pb_flash: Increase baudrate. This makes reading and writing much faster. Also fix lookahead buffer size. --- pybricks/util_pb/pb_flash.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pybricks/util_pb/pb_flash.c b/pybricks/util_pb/pb_flash.c index 0003cae18..719b5fb90 100644 --- a/pybricks/util_pb/pb_flash.c +++ b/pybricks/util_pb/pb_flash.c @@ -46,7 +46,7 @@ static pbio_error_t pb_flash_spi_init(void) { hspi2.Init.CLKPolarity = SPI_POLARITY_LOW; hspi2.Init.CLKPhase = SPI_PHASE_1EDGE; hspi2.Init.NSS = SPI_NSS_SOFT; - hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; + hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB; // Save settings @@ -304,7 +304,7 @@ lfs_file_t file; uint8_t lfs_read_buf[256]; uint8_t lfs_prog_buf[256]; -uint8_t lfs_lookahead_buf[32]; +uint8_t lfs_lookahead_buf[256]; uint8_t lfs_file_buf[256]; struct lfs_config cfg = { @@ -317,7 +317,7 @@ struct lfs_config cfg = { .prog_size = 256, .block_size = 4096, .block_count = 7936, - .lookahead = 32, + .lookahead = sizeof(lfs_lookahead_buf) * 8, .read_buffer = lfs_read_buf, .prog_buffer = lfs_prog_buf, From 0cb007171138b908075f8b6e4adc318b7d92c78a Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Wed, 6 Oct 2021 13:30:50 +0200 Subject: [PATCH 07/15] pybricks/util_pb/pb_flash: Move initialization. Init and scan file system only once. --- bricks/stm32/main.c | 6 +++ pybricks/util_pb/pb_flash.c | 80 ++++++++++++++----------------------- pybricks/util_pb/pb_flash.h | 3 ++ 3 files changed, 39 insertions(+), 50 deletions(-) diff --git a/bricks/stm32/main.c b/bricks/stm32/main.c index bcaaf2a17..a599662cd 100644 --- a/bricks/stm32/main.c +++ b/bricks/stm32/main.c @@ -359,6 +359,12 @@ static void run_user_program(uint32_t len, uint8_t *buf, uint32_t free_len) { } static void stm32_main(void) { + + #if PYBRICKS_HUB_PRIMEHUB + mp_hal_delay_ms(500); + pb_flash_init(); + #endif + soft_reset: // Stack limit should be less than real stack size, so we have a chance // to recover from limit hit. (Limit is measured in bytes.) diff --git a/pybricks/util_pb/pb_flash.c b/pybricks/util_pb/pb_flash.c index 719b5fb90..e49fe9c37 100644 --- a/pybricks/util_pb/pb_flash.c +++ b/pybricks/util_pb/pb_flash.c @@ -81,9 +81,6 @@ static pbio_error_t pb_flash_spi_init(void) { return PBIO_ERROR_NO_DEV; } - // Set flag to indicate flash is ready - flash_initialized = true; - return PBIO_SUCCESS; } @@ -325,7 +322,12 @@ struct lfs_config cfg = { .file_buffer = lfs_file_buf, }; -static pbio_error_t pb_flash_init(void) { +pbio_error_t pb_flash_init(void) { + + // Initialize only once + if (flash_initialized) { + return PBIO_SUCCESS; + } // Init SPI pbio_error_t err = pb_flash_spi_init(); @@ -346,27 +348,36 @@ static pbio_error_t pb_flash_init(void) { return PBIO_ERROR_NOT_SUPPORTED; } - // Verify block parameters + // Verify block parameters and on-flash version of littlefs uint32_t block_size = pbio_get_uint32_le(&lfs_data[28]); uint32_t block_count = pbio_get_uint32_le(&lfs_data[32]); + uint32_t version = pbio_get_uint32_le(&lfs_data[36]); - if (cfg.block_size != block_size || cfg.block_count != block_count) { + if (cfg.block_size != block_size || cfg.block_count != block_count || version != 0x00010001) { return PBIO_ERROR_NOT_SUPPORTED; } + // Mount file system + if (lfs_mount(&lfs, &cfg) != LFS_ERR_OK) { + return PBIO_ERROR_FAILED; + } + + // Ensure there is a _pybricks system folder + int lfs_err = lfs_mkdir(&lfs, "_pybricks"); + if (!(lfs_err == LFS_ERR_OK || lfs_err == LFS_ERR_EXIST)) { + return PBIO_ERROR_FAILED; + } + + // We're ready to read and write now. flash_initialized = true; - return PBIO_SUCCESS; + + // The first write takes a bit longer, so do it now. + const uint8_t *pybricks_magic = (const uint8_t *)"pybricks"; + return pb_flash_file_write("_pybricks/boot.txt", pybricks_magic, sizeof(pybricks_magic) - 1); } pbio_error_t pb_flash_raw_read(uint32_t address, uint8_t *buf, uint32_t size) { - if (!flash_initialized) { - pbio_error_t err = pb_flash_spi_init(); - if (err != PBIO_SUCCESS) { - return err; - } - } - if (flash_read(address, buf, size) != HAL_OK) { return PBIO_ERROR_FAILED; } @@ -377,15 +388,7 @@ pbio_error_t pb_flash_file_open_get_size(const char *path, uint32_t *size) { // Check that flash was initialized if (!flash_initialized) { - pbio_error_t err = pb_flash_spi_init(); - if (err != PBIO_SUCCESS) { - return err; - } - } - - // Mount file system - if (lfs_mount(&lfs, &cfg) != LFS_ERR_OK) { - return PBIO_ERROR_FAILED; + return PBIO_ERROR_INVALID_OP; } // Open file @@ -400,10 +403,7 @@ pbio_error_t pb_flash_file_read(uint8_t *buf, uint32_t size) { // Check that flash was initialized if (!flash_initialized) { - pbio_error_t err = pb_flash_spi_init(); - if (err != PBIO_SUCCESS) { - return err; - } + return PBIO_ERROR_INVALID_OP; } // Allow only read in one go for now @@ -416,13 +416,10 @@ pbio_error_t pb_flash_file_read(uint8_t *buf, uint32_t size) { return PBIO_ERROR_FAILED; } - // Close the file and unmount + // Close the file if (lfs_file_close(&lfs, &file) != LFS_ERR_OK) { return PBIO_ERROR_FAILED; } - if (lfs_unmount(&lfs) != LFS_ERR_OK) { - return PBIO_ERROR_FAILED; - } return PBIO_SUCCESS; } @@ -430,21 +427,7 @@ pbio_error_t pb_flash_file_write(const char *path, const uint8_t *buf, uint32_t // Check that flash was initialized if (!flash_initialized) { - pbio_error_t err = pb_flash_init(); - if (err != PBIO_SUCCESS) { - return err; - } - } - - // Mount file system - if (lfs_mount(&lfs, &cfg) != LFS_ERR_OK) { - return PBIO_ERROR_FAILED; - } - - // Ensure there is a _pybricks system folder - int lfs_err = lfs_mkdir(&lfs, "_pybricks"); - if (!(lfs_err == LFS_ERR_OK || lfs_err == LFS_ERR_EXIST)) { - return PBIO_ERROR_FAILED; + return PBIO_ERROR_INVALID_OP; } // Open file @@ -457,13 +440,10 @@ pbio_error_t pb_flash_file_write(const char *path, const uint8_t *buf, uint32_t return PBIO_ERROR_FAILED; } - // Close the file and unmount + // Close the file if (lfs_file_close(&lfs, &file) != LFS_ERR_OK) { return PBIO_ERROR_FAILED; } - if (lfs_unmount(&lfs) != LFS_ERR_OK) { - return PBIO_ERROR_FAILED; - } return PBIO_SUCCESS; } diff --git a/pybricks/util_pb/pb_flash.h b/pybricks/util_pb/pb_flash.h index 1d1e3b954..6a704977d 100644 --- a/pybricks/util_pb/pb_flash.h +++ b/pybricks/util_pb/pb_flash.h @@ -10,6 +10,9 @@ #include +// Initialize and mount the file system +pbio_error_t pb_flash_init(void); + // Read raw bytes from flash, ignoring file system pbio_error_t pb_flash_raw_read(uint32_t address, uint8_t *buf, uint32_t size); From 8971de7f59397e53353a4cff9663dc15657f259e Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Tue, 19 Oct 2021 15:58:54 +0200 Subject: [PATCH 08/15] pybricks/util_pb/pb_flash: Refactor. This refactors some common code for writing SPI commands and removes some hardcoded parameters. --- lib/pbio/include/pbio/util.h | 16 ++++ pybricks/util_pb/pb_flash.c | 159 +++++++++++++++++------------------ 2 files changed, 92 insertions(+), 83 deletions(-) diff --git a/lib/pbio/include/pbio/util.h b/lib/pbio/include/pbio/util.h index b22f4fb3e..236570804 100644 --- a/lib/pbio/include/pbio/util.h +++ b/lib/pbio/include/pbio/util.h @@ -102,6 +102,22 @@ void pbio_set_uint32_le(uint8_t *buf, uint32_t value) { buf[3] = value >> 24; } +#ifndef DOXYGEN +static inline +#endif +/** + * Packs 32-bit big endian value into buffer. + * + * @param [in] buf The buffer. + * @param [in] value The value. + */ +void pbio_set_uint32_be(uint8_t *buf, uint32_t value) { + buf[0] = value >> 24; + buf[1] = value >> 16; + buf[2] = value >> 8; + buf[3] = value; +} + bool pbio_uuid128_reverse_compare(const uint8_t *uuid1, const uint8_t *uuid2); void pbio_uuid128_reverse_copy(uint8_t *dst, const uint8_t *src); diff --git a/pybricks/util_pb/pb_flash.c b/pybricks/util_pb/pb_flash.c index e49fe9c37..605a878f3 100644 --- a/pybricks/util_pb/pb_flash.c +++ b/pybricks/util_pb/pb_flash.c @@ -21,6 +21,27 @@ #include + +#define FLASH_SIZE_TOTAL (0x2000000) +#define FLASH_SIZE_BOOT (0x100000) +#define FLASH_SIZE_USER (FLASH_SIZE_TOTAL - FLASH_SIZE_BOOT) + +#define FLASH_TIMEOUT (100) + +enum { + FLASH_CMD_GET_STATUS = 0x05, + FLASH_CMD_WRITE_ENABLE = 0x06, + FLASH_CMD_WRITE_DATA = 0x12, + FLASH_CMD_READ_DATA = 0x13, + FLASH_CMD_ERASE_BLOCK = 0x21, + FLASH_CMD_GET_ID = 0x9F, +}; + +enum { + FLASH_STATUS_BUSY = 0x01, + FLASH_STATUS_WRITE_ENABLED = 0x02, +}; + SPI_HandleTypeDef hspi2; // Whether the SPI and filesystem have been initialized once after boot. @@ -58,19 +79,18 @@ static pbio_error_t pb_flash_spi_init(void) { flash_enable(true); // Command parameters - uint32_t timeout = 100; - uint8_t CMD_GET_ID = 0x9F; + uint8_t command = FLASH_CMD_GET_ID; uint8_t id_data[3]; HAL_StatusTypeDef err; // Send ID command - err = HAL_SPI_Transmit(&hspi2, &CMD_GET_ID, sizeof(CMD_GET_ID), timeout); + err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), FLASH_TIMEOUT); if (err != 0) { return PBIO_ERROR_IO; } // Get ID command reply - err = HAL_SPI_Receive(&hspi2, id_data, sizeof(id_data), timeout); + err = HAL_SPI_Receive(&hspi2, id_data, sizeof(id_data), FLASH_TIMEOUT); if (err != 0) { return PBIO_ERROR_IO; } @@ -86,36 +106,34 @@ static pbio_error_t pb_flash_spi_init(void) { static HAL_StatusTypeDef flash_status_read(uint8_t *status) { - uint32_t timeout = 100; - // Read status command - uint8_t command = 0x05; + uint8_t command = FLASH_CMD_GET_STATUS; // Write the read status command flash_enable(true); - HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), 100); + HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), FLASH_TIMEOUT); if (err != HAL_OK) { return err; } // Receive data - err = HAL_SPI_Receive(&hspi2, status, 1, timeout); + err = HAL_SPI_Receive(&hspi2, status, 1, FLASH_TIMEOUT); flash_enable(false); return err; } static HAL_StatusTypeDef flash_wait_ready() { - uint8_t status = 0x03; + uint8_t status = FLASH_STATUS_BUSY | FLASH_STATUS_WRITE_ENABLED; HAL_StatusTypeDef err; uint32_t start_time = mp_hal_ticks_ms(); // While write enabled and / or busy, wait - while (status & 0x03) { + while (status & (FLASH_STATUS_BUSY | FLASH_STATUS_WRITE_ENABLED)) { err = flash_status_read(&status); if (err != HAL_OK) { return err; } - if (mp_hal_ticks_ms() - start_time > 100) { + if (mp_hal_ticks_ms() - start_time > FLASH_TIMEOUT) { return HAL_TIMEOUT; } MICROPY_EVENT_POLL_HOOK; @@ -123,111 +141,86 @@ static HAL_StatusTypeDef flash_wait_ready() { return HAL_OK; } -static HAL_StatusTypeDef flash_read(uint32_t address, uint8_t *buffer, uint32_t size) { - - uint32_t timeout = 100; - - // First 1MiB is reserved for firmware updates with official firmware. - address += 1024 * 1024; +static HAL_StatusTypeDef flash_send_address_command(uint8_t command, uint32_t address) { - uint8_t address_bytes[5]; - - // Read command - address_bytes[0] = 0x13; + // Can only read/write user partition + if (address > FLASH_SIZE_USER) { + return HAL_ERROR; + } - // Address as big endian - address_bytes[1] = (address & 0xff000000) >> 24; - address_bytes[2] = (address & 0x00ff0000) >> 16; - address_bytes[3] = (address & 0x0000ff00) >> 8; - address_bytes[4] = address & 0x000000ff; + // Pack command and address as big endian + uint8_t data[5] = {command}; + pbio_set_uint32_be(&data[1], address + FLASH_SIZE_BOOT); - // Write the read command with address + // Enable flash flash_enable(true); - HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, address_bytes, sizeof(address_bytes), 100); + + // Send address command + HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, data, sizeof(data), FLASH_TIMEOUT); if (err != HAL_OK) { + flash_enable(false); return err; } + return err; +} + +static HAL_StatusTypeDef flash_read(uint32_t address, uint8_t *buffer, uint32_t size) { + + // Enable flash and send the read command with address + HAL_StatusTypeDef err = flash_send_address_command(FLASH_CMD_READ_DATA, address); // Receive data - err = HAL_SPI_Receive(&hspi2, buffer, size, timeout); + err = HAL_SPI_Receive(&hspi2, buffer, size, FLASH_TIMEOUT); flash_enable(false); return err; } static HAL_StatusTypeDef write_enable() { - uint8_t command = 0x06; + uint8_t command = FLASH_CMD_WRITE_ENABLE; flash_enable(true); - HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), 100); + HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), FLASH_TIMEOUT); flash_enable(false); return err; } static HAL_StatusTypeDef flash_write(uint32_t address, const uint8_t *buffer, uint32_t size) { - uint32_t timeout = 100; - - // First 1MiB is reserved for firmware updates with official firmware. - address += 1024 * 1024; - - uint8_t address_bytes[5]; - - // Write command - address_bytes[0] = 0x12; - - // Address as big endian - address_bytes[1] = (address & 0xff000000) >> 24; - address_bytes[2] = (address & 0x00ff0000) >> 16; - address_bytes[3] = (address & 0x0000ff00) >> 8; - address_bytes[4] = address & 0x000000ff; - // Enable write mode HAL_StatusTypeDef err = write_enable(); if (err != HAL_OK) { return err; } - // Write the write command with address - flash_enable(true); - err = HAL_SPI_Transmit(&hspi2, address_bytes, sizeof(address_bytes), timeout); - if (err != HAL_OK) { - flash_enable(false); - return err; - } + // Enable flash and send the write command with address + err = flash_send_address_command(FLASH_CMD_WRITE_DATA, address); // Write the data - err = HAL_SPI_Transmit(&hspi2, (uint8_t *)buffer, size, timeout); + err = HAL_SPI_Transmit(&hspi2, (uint8_t *)buffer, size, FLASH_TIMEOUT); flash_enable(false); + if (err != HAL_OK) { + return err; + } return flash_wait_ready(); } static HAL_StatusTypeDef flash_block_erase(uint32_t address) { - address += 1024 * 1024; - // Enable write mode HAL_StatusTypeDef err = write_enable(); if (err != HAL_OK) { return err; } - uint8_t address_bytes[5]; - address_bytes[0] = 0x21; - address_bytes[1] = (address & 0xff000000) >> 24; - address_bytes[2] = (address & 0x00ff0000) >> 16; - address_bytes[3] = (address & 0x0000ff00) >> 8; - address_bytes[4] = address & 0x000000ff; - - // Send erase command - flash_enable(true); - err = HAL_SPI_Transmit(&hspi2, address_bytes, sizeof(address_bytes), 100); + // Enable flash and send the erase block command with address + err = flash_send_address_command(FLASH_CMD_ERASE_BLOCK, address); flash_enable(false); - - err = flash_wait_ready(); - - return err; + if (err != HAL_OK) { + return err; + } + return flash_wait_ready(); } -int block_device_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { +static int block_device_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { lfs_size_t done = 0; @@ -251,7 +244,7 @@ int block_device_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t o return 0; } -int block_device_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { +static int block_device_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { lfs_size_t done = 0; while (done < size) { @@ -283,7 +276,7 @@ int block_device_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t o return 0; } -int block_device_erase(const struct lfs_config *c, lfs_block_t block) { +static int block_device_erase(const struct lfs_config *c, lfs_block_t block) { // Erase block of flash. Block size assumed to match erase size. if (flash_block_erase(block * c->block_size) != HAL_OK) { @@ -292,19 +285,19 @@ int block_device_erase(const struct lfs_config *c, lfs_block_t block) { return LFS_ERR_OK; } -int block_device_sync(const struct lfs_config *c) { +static int block_device_sync(const struct lfs_config *c) { return 0; } -lfs_t lfs; -lfs_file_t file; +static lfs_t lfs; +static lfs_file_t file; -uint8_t lfs_read_buf[256]; -uint8_t lfs_prog_buf[256]; -uint8_t lfs_lookahead_buf[256]; -uint8_t lfs_file_buf[256]; +static uint8_t lfs_read_buf[256]; +static uint8_t lfs_prog_buf[256]; +static uint8_t lfs_lookahead_buf[256]; +static uint8_t lfs_file_buf[256]; -struct lfs_config cfg = { +static const struct lfs_config cfg = { .read = block_device_read, .prog = block_device_prog, .erase = block_device_erase, From b62ffc100ad4cbf726926e0321d640f20ef40852 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Tue, 19 Oct 2021 21:38:46 +0200 Subject: [PATCH 09/15] pybricks/util_pb/pb_flash: Add address offset. --- pybricks/util_pb/pb_flash.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/pybricks/util_pb/pb_flash.c b/pybricks/util_pb/pb_flash.c index 605a878f3..397d2e8a8 100644 --- a/pybricks/util_pb/pb_flash.c +++ b/pybricks/util_pb/pb_flash.c @@ -148,9 +148,10 @@ static HAL_StatusTypeDef flash_send_address_command(uint8_t command, uint32_t ad return HAL_ERROR; } - // Pack command and address as big endian + // Pack command and address as big endian. This is the absolute address, + // starting in the boot partition. uint8_t data[5] = {command}; - pbio_set_uint32_be(&data[1], address + FLASH_SIZE_BOOT); + pbio_set_uint32_be(&data[1], address); // Enable flash flash_enable(true); @@ -166,8 +167,12 @@ static HAL_StatusTypeDef flash_send_address_command(uint8_t command, uint32_t ad static HAL_StatusTypeDef flash_read(uint32_t address, uint8_t *buffer, uint32_t size) { - // Enable flash and send the read command with address - HAL_StatusTypeDef err = flash_send_address_command(FLASH_CMD_READ_DATA, address); + if (address + size > FLASH_SIZE_USER) { + return HAL_ERROR; + } + + // Enable flash and send the read command with address, offset by boot partition + HAL_StatusTypeDef err = flash_send_address_command(FLASH_CMD_READ_DATA, address + FLASH_SIZE_BOOT); // Receive data err = HAL_SPI_Receive(&hspi2, buffer, size, FLASH_TIMEOUT); @@ -185,14 +190,18 @@ static HAL_StatusTypeDef write_enable() { static HAL_StatusTypeDef flash_write(uint32_t address, const uint8_t *buffer, uint32_t size) { + if (address + size > FLASH_SIZE_USER) { + return HAL_ERROR; + } + // Enable write mode HAL_StatusTypeDef err = write_enable(); if (err != HAL_OK) { return err; } - // Enable flash and send the write command with address - err = flash_send_address_command(FLASH_CMD_WRITE_DATA, address); + // Enable flash and send the write command with address, offset by boot partition + err = flash_send_address_command(FLASH_CMD_WRITE_DATA, address + FLASH_SIZE_BOOT); // Write the data err = HAL_SPI_Transmit(&hspi2, (uint8_t *)buffer, size, FLASH_TIMEOUT); @@ -205,14 +214,18 @@ static HAL_StatusTypeDef flash_write(uint32_t address, const uint8_t *buffer, ui static HAL_StatusTypeDef flash_block_erase(uint32_t address) { + if (address > FLASH_SIZE_USER) { + return HAL_ERROR; + } + // Enable write mode HAL_StatusTypeDef err = write_enable(); if (err != HAL_OK) { return err; } - // Enable flash and send the erase block command with address - err = flash_send_address_command(FLASH_CMD_ERASE_BLOCK, address); + // Enable flash and send the erase block command with address, offset by boot partition + err = flash_send_address_command(FLASH_CMD_ERASE_BLOCK, address + FLASH_SIZE_BOOT); flash_enable(false); if (err != HAL_OK) { return err; From 4b29eb389adc24e8b020174f2e2017311ce26ea8 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Tue, 19 Oct 2021 19:21:25 +0200 Subject: [PATCH 10/15] pybricks/util_pb/pb_flash: Add firmware restore. This adds an experimental function that can restore a previously backed up firmware file. --- .../experimental/pb_module_experimental.c | 8 + pybricks/util_pb/pb_flash.c | 244 +++++++++++++++--- pybricks/util_pb/pb_flash.h | 5 +- 3 files changed, 215 insertions(+), 42 deletions(-) diff --git a/pybricks/experimental/pb_module_experimental.c b/pybricks/experimental/pb_module_experimental.c index af924bc45..369d17538 100644 --- a/pybricks/experimental/pb_module_experimental.c +++ b/pybricks/experimental/pb_module_experimental.c @@ -114,6 +114,13 @@ STATIC mp_obj_t experimental_flash_write_file(size_t n_args, const mp_obj_t *pos // See also experimental_globals_table below. This function object is added there to make it importable. STATIC MP_DEFINE_CONST_FUN_OBJ_KW(experimental_flash_write_file_obj, 0, experimental_flash_write_file); +// pybricks.experimental.restore_firmware +STATIC mp_obj_t experimental_restore_firmware(void) { + pb_assert(pb_flash_restore_firmware()); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(experimental_restore_firmware_obj, experimental_restore_firmware); + #endif // PYBRICKS_HUB_PRIMEHUB // pybricks.experimental.hello_world @@ -167,6 +174,7 @@ STATIC const mp_rom_map_elem_t experimental_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_flash_read_raw), MP_ROM_PTR(&experimental_flash_read_raw_obj) }, { MP_ROM_QSTR(MP_QSTR_flash_read_file), MP_ROM_PTR(&experimental_flash_read_file_obj) }, { MP_ROM_QSTR(MP_QSTR_flash_write_file), MP_ROM_PTR(&experimental_flash_write_file_obj) }, + { MP_ROM_QSTR(MP_QSTR_restore_firmware), MP_ROM_PTR(&experimental_restore_firmware_obj) }, #endif // PYBRICKS_HUB_PRIMEHUB { MP_ROM_QSTR(MP_QSTR_hello_world), MP_ROM_PTR(&experimental_hello_world_obj) }, }; diff --git a/pybricks/util_pb/pb_flash.c b/pybricks/util_pb/pb_flash.c index 397d2e8a8..90b358564 100644 --- a/pybricks/util_pb/pb_flash.c +++ b/pybricks/util_pb/pb_flash.c @@ -25,6 +25,9 @@ #define FLASH_SIZE_TOTAL (0x2000000) #define FLASH_SIZE_BOOT (0x100000) #define FLASH_SIZE_USER (FLASH_SIZE_TOTAL - FLASH_SIZE_BOOT) +#define FLASH_SIZE_ERASE (0x1000) +#define FLASH_SIZE_SIZE (4) +#define FLASH_SIZE_WRITE (256) #define FLASH_TIMEOUT (100) @@ -113,6 +116,7 @@ static HAL_StatusTypeDef flash_status_read(uint8_t *status) { flash_enable(true); HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), FLASH_TIMEOUT); if (err != HAL_OK) { + flash_enable(false); return err; } @@ -122,7 +126,7 @@ static HAL_StatusTypeDef flash_status_read(uint8_t *status) { return err; } -static HAL_StatusTypeDef flash_wait_ready() { +static HAL_StatusTypeDef flash_wait_ready(void) { uint8_t status = FLASH_STATUS_BUSY | FLASH_STATUS_WRITE_ENABLED; HAL_StatusTypeDef err; uint32_t start_time = mp_hal_ticks_ms(); @@ -136,12 +140,19 @@ static HAL_StatusTypeDef flash_wait_ready() { if (mp_hal_ticks_ms() - start_time > FLASH_TIMEOUT) { return HAL_TIMEOUT; } - MICROPY_EVENT_POLL_HOOK; } return HAL_OK; } -static HAL_StatusTypeDef flash_send_address_command(uint8_t command, uint32_t address) { +static HAL_StatusTypeDef flash_write_enable(void) { + uint8_t command = FLASH_CMD_WRITE_ENABLE; + flash_enable(true); + HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), FLASH_TIMEOUT); + flash_enable(false); + return err; +} + +static HAL_StatusTypeDef flash_send_address_command(uint8_t command, uint32_t address, bool keep_flash_enabled) { // Can only read/write user partition if (address > FLASH_SIZE_USER) { @@ -162,17 +173,23 @@ static HAL_StatusTypeDef flash_send_address_command(uint8_t command, uint32_t ad flash_enable(false); return err; } - return err; -} -static HAL_StatusTypeDef flash_read(uint32_t address, uint8_t *buffer, uint32_t size) { - - if (address + size > FLASH_SIZE_USER) { - return HAL_ERROR; + // On success, keep flash enabled if we are asked to + if (!keep_flash_enabled) { + flash_enable(false); } + return HAL_OK; +} + +static HAL_StatusTypeDef flash_raw_read(uint32_t address, uint8_t *buffer, uint32_t size) { + // Enable flash and send the read command with address, offset by boot partition - HAL_StatusTypeDef err = flash_send_address_command(FLASH_CMD_READ_DATA, address + FLASH_SIZE_BOOT); + HAL_StatusTypeDef err = flash_send_address_command(FLASH_CMD_READ_DATA, address, true); + if (err != HAL_OK) { + flash_enable(false); + return err; + } // Receive data err = HAL_SPI_Receive(&hspi2, buffer, size, FLASH_TIMEOUT); @@ -180,28 +197,21 @@ static HAL_StatusTypeDef flash_read(uint32_t address, uint8_t *buffer, uint32_t return err; } -static HAL_StatusTypeDef write_enable() { - uint8_t command = FLASH_CMD_WRITE_ENABLE; - flash_enable(true); - HAL_StatusTypeDef err = HAL_SPI_Transmit(&hspi2, &command, sizeof(command), FLASH_TIMEOUT); - flash_enable(false); - return err; -} - -static HAL_StatusTypeDef flash_write(uint32_t address, const uint8_t *buffer, uint32_t size) { - if (address + size > FLASH_SIZE_USER) { - return HAL_ERROR; - } +static HAL_StatusTypeDef flash_raw_write(uint32_t address, const uint8_t *buffer, uint32_t size) { // Enable write mode - HAL_StatusTypeDef err = write_enable(); + HAL_StatusTypeDef err = flash_write_enable(); if (err != HAL_OK) { return err; } // Enable flash and send the write command with address, offset by boot partition - err = flash_send_address_command(FLASH_CMD_WRITE_DATA, address + FLASH_SIZE_BOOT); + err = flash_send_address_command(FLASH_CMD_WRITE_DATA, address, true); + if (err != HAL_OK) { + flash_enable(false); + return err; + } // Write the data err = HAL_SPI_Transmit(&hspi2, (uint8_t *)buffer, size, FLASH_TIMEOUT); @@ -212,27 +222,44 @@ static HAL_StatusTypeDef flash_write(uint32_t address, const uint8_t *buffer, ui return flash_wait_ready(); } -static HAL_StatusTypeDef flash_block_erase(uint32_t address) { - - if (address > FLASH_SIZE_USER) { - return HAL_ERROR; - } +static HAL_StatusTypeDef flash_raw_block_erase(uint32_t address) { // Enable write mode - HAL_StatusTypeDef err = write_enable(); + HAL_StatusTypeDef err = flash_write_enable(); if (err != HAL_OK) { return err; } // Enable flash and send the erase block command with address, offset by boot partition - err = flash_send_address_command(FLASH_CMD_ERASE_BLOCK, address + FLASH_SIZE_BOOT); - flash_enable(false); + err = flash_send_address_command(FLASH_CMD_ERASE_BLOCK, address, false); if (err != HAL_OK) { return err; } return flash_wait_ready(); } + +static HAL_StatusTypeDef flash_user_read(uint32_t address, uint8_t *buffer, uint32_t size) { + if (address + size > FLASH_SIZE_USER) { + return HAL_ERROR; + } + return flash_raw_read(address + FLASH_SIZE_BOOT, buffer, size); +} + +static HAL_StatusTypeDef flash_user_write(uint32_t address, const uint8_t *buffer, uint32_t size) { + if (address + size > FLASH_SIZE_USER) { + return HAL_ERROR; + } + return flash_raw_write(address + FLASH_SIZE_BOOT, buffer, size); +} + +static HAL_StatusTypeDef flash_user_block_erase(uint32_t address) { + if (address > FLASH_SIZE_USER) { + return HAL_ERROR; + } + return flash_raw_block_erase(address + FLASH_SIZE_BOOT); +} + static int block_device_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { lfs_size_t done = 0; @@ -246,7 +273,7 @@ static int block_device_read(const struct lfs_config *c, lfs_block_t block, lfs_ } // Read chunk of flash. - if (flash_read(block * c->block_size + off + done, buffer + done, read_now) != HAL_OK) { + if (flash_user_read(block * c->block_size + off + done, buffer + done, read_now) != HAL_OK) { return LFS_ERR_IO; } done += read_now; @@ -254,7 +281,7 @@ static int block_device_read(const struct lfs_config *c, lfs_block_t block, lfs_ // Give MicroPython and PBIO some time. MICROPY_EVENT_POLL_HOOK; } - return 0; + return LFS_ERR_OK; } static int block_device_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { @@ -277,7 +304,7 @@ static int block_device_prog(const struct lfs_config *c, lfs_block_t block, lfs_ } // Write chunk of flash. - if (flash_write(block * c->block_size + off + done, buffer + done, write_now) != HAL_OK) { + if (flash_user_write(block * c->block_size + off + done, buffer + done, write_now) != HAL_OK) { return LFS_ERR_IO; } done += write_now; @@ -286,15 +313,19 @@ static int block_device_prog(const struct lfs_config *c, lfs_block_t block, lfs_ MICROPY_EVENT_POLL_HOOK; } - return 0; + return LFS_ERR_OK; } static int block_device_erase(const struct lfs_config *c, lfs_block_t block) { // Erase block of flash. Block size assumed to match erase size. - if (flash_block_erase(block * c->block_size) != HAL_OK) { + if (flash_user_block_erase(block * c->block_size) != HAL_OK) { return LFS_ERR_IO; } + + // Give MicroPython and PBIO some time. + MICROPY_EVENT_POLL_HOOK; + return LFS_ERR_OK; } @@ -344,7 +375,7 @@ pbio_error_t pb_flash_init(void) { uint8_t lfs_data[58]; // Read littlefs data - if (flash_read(0, lfs_data, sizeof(lfs_data)) != HAL_OK) { + if (flash_user_read(0, lfs_data, sizeof(lfs_data)) != HAL_OK) { return PBIO_ERROR_IO; } @@ -384,12 +415,28 @@ pbio_error_t pb_flash_init(void) { pbio_error_t pb_flash_raw_read(uint32_t address, uint8_t *buf, uint32_t size) { - if (flash_read(address, buf, size) != HAL_OK) { + if (flash_raw_read(address, buf, size) != HAL_OK) { return PBIO_ERROR_FAILED; } return PBIO_SUCCESS; } +int lfs_file_open_retry(const char *path, int flags) { + // Try to open the file + int err = lfs_file_open(&lfs, &file, path, flags); + + // If file is already open, close it and retry + if (err == LFS_ERR_NOMEM) { + err = lfs_file_close(&lfs, &file); + if (err != LFS_ERR_OK) { + return err; + } + + err = lfs_file_open(&lfs, &file, path, flags); + } + return err; +} + pbio_error_t pb_flash_file_open_get_size(const char *path, uint32_t *size) { // Check that flash was initialized @@ -398,7 +445,7 @@ pbio_error_t pb_flash_file_open_get_size(const char *path, uint32_t *size) { } // Open file - if (lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) != LFS_ERR_OK) { + if (lfs_file_open_retry(path, LFS_O_RDONLY) != LFS_ERR_OK) { return PBIO_ERROR_FAILED; } *size = file.size; @@ -437,7 +484,7 @@ pbio_error_t pb_flash_file_write(const char *path, const uint8_t *buf, uint32_t } // Open file - if (lfs_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) != LFS_ERR_OK) { + if (lfs_file_open_retry(path, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) != LFS_ERR_OK) { return PBIO_ERROR_FAILED; } @@ -453,4 +500,119 @@ pbio_error_t pb_flash_file_write(const char *path, const uint8_t *buf, uint32_t return PBIO_SUCCESS; } +pbio_error_t pb_flash_restore_firmware(void) { + // Check that flash was initialized + if (!flash_initialized) { + return PBIO_ERROR_INVALID_OP; + } + + mp_printf(&mp_plat_print, "Checking firmware files.\n"); + + // Open meta file + if (lfs_file_open_retry("_firmware/lego-firmware.metadata.json", LFS_O_RDONLY) != LFS_ERR_OK) { + return PBIO_ERROR_FAILED; + } + // Read meta file + char *meta_data = m_new(char, file.size); + if (lfs_file_read(&lfs, &file, meta_data, file.size) != file.size) { + return PBIO_ERROR_FAILED; + } + // Close meta file + if (lfs_file_close(&lfs, &file) != LFS_ERR_OK) { + return PBIO_ERROR_FAILED; + } + + // Check for device ID in meta data. Todo: Generalize. + const char hub_id_key[] = "\"device-id\":"; + char *hub_id_data = strstr(meta_data, hub_id_key) + sizeof(hub_id_key); + if (strncmp(hub_id_data, "129", 3)) { + return PBIO_ERROR_FAILED; + } + + mp_printf(&mp_plat_print, "Preparing to restore firmware.\n"); + + HAL_StatusTypeDef err; + + // Erase boot partition + for (uint32_t address = 0; address < FLASH_SIZE_BOOT; address += FLASH_SIZE_ERASE) { + err = flash_raw_block_erase(address); + if (err != HAL_OK) { + return PBIO_ERROR_IO; + } + MICROPY_EVENT_POLL_HOOK; + } + + mp_printf(&mp_plat_print, "Restoring firmware.\n"); + + // Todo: verify the other meta data details, like size. + + // Open firmware file + if (lfs_file_open_retry("_firmware/lego-firmware.bin", LFS_O_RDONLY) != LFS_ERR_OK) { + return PBIO_ERROR_FAILED; + } + + // All known back up firmwares are much larger than this. + if (file.size < 128 * 1024) { + // TODO: Check that sha256 matches metadata + return PBIO_ERROR_FAILED; + } + + // Initialize buffers + lfs_size_t done = 0; + lfs_size_t read_size, write_size; + uint8_t buf[FLASH_SIZE_WRITE]; + uint8_t *buf_start; + + // Read/write the firmware page by page + while (done < file.size) { + + // The first page is a special case + if (done == 0) { + // It starts of with the firmware size + pbio_set_uint32_le(buf, file.size); + + // We read just enough to fill up the page + buf_start = buf + FLASH_SIZE_SIZE; + read_size = FLASH_SIZE_WRITE - FLASH_SIZE_SIZE; + + // We still write a full page + write_size = FLASH_SIZE_WRITE; + } + // Otherwise, we just look at how much is remaining + else { + // Take the remaining size, or at most one page + read_size = file.size - done; + if (read_size > sizeof(buf)) { + read_size = sizeof(buf); + } + // We write as much as we read + write_size = read_size; + buf_start = buf; + } + + // Read data from the firmware backup file + if (lfs_file_read(&lfs, &file, buf_start, read_size) != read_size) { + return PBIO_ERROR_FAILED; + } + + // Write the data + err = flash_raw_write(done == 0 ? 0 : done + 4, buf, write_size); + if (err != HAL_OK) { + return PBIO_ERROR_IO; + } + + done += read_size; + } + + // Close firmware file + if (lfs_file_close(&lfs, &file) != LFS_ERR_OK) { + return PBIO_ERROR_FAILED; + } + + mp_printf(&mp_plat_print, "Done. You may now reboot. Please keep USB attached.\n"); + + + return PBIO_SUCCESS; +} + #endif // PYBRICKS_HUB_PRIMEHUB diff --git a/pybricks/util_pb/pb_flash.h b/pybricks/util_pb/pb_flash.h index 6a704977d..38d77a315 100644 --- a/pybricks/util_pb/pb_flash.h +++ b/pybricks/util_pb/pb_flash.h @@ -22,9 +22,12 @@ pbio_error_t pb_flash_file_open_get_size(const char *path, uint32_t *size); // Read the file that has been opened, all at once, then close. pbio_error_t pb_flash_file_read(uint8_t *buf, uint32_t size); -// Open a file for writing, write data, anc close. +// Open a file for writing, write data, and close. pbio_error_t pb_flash_file_write(const char *path, const uint8_t *buf, uint32_t size); +// Restore a previously backed up firmware file. +pbio_error_t pb_flash_restore_firmware(void); + #endif #endif // _PB_FLASH_H_ From 7c89fa8dcab756c9643a78f3b86bc5fd20706af9 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Wed, 27 Oct 2021 15:26:55 +0200 Subject: [PATCH 11/15] bricks/stm32/configport: Enable ujson. This is quite useful for hubs that can read and write from external storage. It is currently enabled on all hubs with the PYBRICKS_STM32_OPT_EXTRA_MOD flag. It is also used for the firmware restore process, so we can parse JSON files to check meta data without reinventing the wheel. --- CHANGELOG.md | 1 + bricks/stm32/configport.h | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7a3dfe02..4b65ac279 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## [Unreleased] ### Added +- Enabled `ujson` module. - Added ability to use more than one `DriveBase` in the same script. - Added `Motor.busy()` and `Motor.stalled()` methods, which (in case of `Motor`) are shorthand for `not Motor.control.done()` and diff --git a/bricks/stm32/configport.h b/bricks/stm32/configport.h index daff20774..06ee30512 100644 --- a/bricks/stm32/configport.h +++ b/bricks/stm32/configport.h @@ -61,6 +61,7 @@ #define MICROPY_PY_ALL_SPECIAL_METHODS (PYBRICKS_STM32_OPT_EXTRA_MOD) #define MICROPY_PY_REVERSE_SPECIAL_METHODS (PYBRICKS_STM32_OPT_EXTRA_MOD) #define MICROPY_PY_IO (PYBRICKS_STM32_OPT_EXTRA_MOD) +#define MICROPY_PY_UJSON (PYBRICKS_STM32_OPT_EXTRA_MOD) #define MICROPY_PY_STRUCT (PYBRICKS_STM32_OPT_EXTRA_MOD) #define MICROPY_PY_SYS (PYBRICKS_STM32_OPT_EXTRA_MOD) #define MICROPY_PY_SYS_EXIT (0) From 9c31ae5311e979c2d2fd78930181dd8a52971a67 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Wed, 27 Oct 2021 15:28:44 +0200 Subject: [PATCH 12/15] pybricks/util_pb/pb_flash: Adds checks to firmware restore. Verify the firmware meta data before installing it. Also reset the hub when we are done in order to complete the installation. --- pybricks/util_pb/pb_flash.c | 68 ++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/pybricks/util_pb/pb_flash.c b/pybricks/util_pb/pb_flash.c index 90b358564..05a63222e 100644 --- a/pybricks/util_pb/pb_flash.c +++ b/pybricks/util_pb/pb_flash.c @@ -9,6 +9,9 @@ #include +#include +#include + #include #include @@ -16,6 +19,7 @@ #include #include "py/mphal.h" +#include "py/runtime.h" #include "lfs.h" @@ -500,36 +504,69 @@ pbio_error_t pb_flash_file_write(const char *path, const uint8_t *buf, uint32_t return PBIO_SUCCESS; } +extern const mp_obj_module_t mp_module_ujson; + pbio_error_t pb_flash_restore_firmware(void) { // Check that flash was initialized if (!flash_initialized) { return PBIO_ERROR_INVALID_OP; } - mp_printf(&mp_plat_print, "Checking firmware files.\n"); + // Check that the hub is plugged in and charging. + if (pbdrv_charger_get_status() != PBDRV_CHARGER_STATUS_CHARGE) { + mp_printf(&mp_plat_print, "Please connect the hub via USB.\n"); + // TODO: Stop here once charging is fully implemented. + // return PBIO_ERROR_FAILED; + } + + mp_printf(&mp_plat_print, "Checking firmware backup files.\n"); // Open meta file if (lfs_file_open_retry("_firmware/lego-firmware.metadata.json", LFS_O_RDONLY) != LFS_ERR_OK) { + mp_printf(&mp_plat_print, "Unable to open backup meta data file.\n"); return PBIO_ERROR_FAILED; } // Read meta file - char *meta_data = m_new(char, file.size); + size_t meta_size = file.size; + char *meta_data = m_new(char, meta_size); if (lfs_file_read(&lfs, &file, meta_data, file.size) != file.size) { + mp_printf(&mp_plat_print, "Unable to read backup meta data file.\n"); return PBIO_ERROR_FAILED; } + // Close meta file if (lfs_file_close(&lfs, &file) != LFS_ERR_OK) { + mp_printf(&mp_plat_print, "Unable to close backup meta data file.\n"); return PBIO_ERROR_FAILED; } - // Check for device ID in meta data. Todo: Generalize. - const char hub_id_key[] = "\"device-id\":"; - char *hub_id_data = strstr(meta_data, hub_id_key) + sizeof(hub_id_key); - if (strncmp(hub_id_data, "129", 3)) { + // Read meta data into MicroPython dictionary. + mp_obj_t loads_func = mp_obj_dict_get(mp_module_ujson.globals, MP_ROM_QSTR(MP_QSTR_loads)); + mp_obj_t meta_dict = mp_call_function_1(loads_func, mp_obj_new_str(meta_data, meta_size)); + + // Get relevant meta data + mp_int_t meta_firmware_size = mp_obj_get_int(mp_obj_dict_get(meta_dict, MP_OBJ_NEW_QSTR(qstr_from_str("firmware-size")))); + mp_int_t meta_device_id = mp_obj_get_int(mp_obj_dict_get(meta_dict, MP_OBJ_NEW_QSTR(qstr_from_str("device-id")))); + mp_obj_t meta_firmware_version = mp_obj_dict_get(meta_dict, MP_OBJ_NEW_QSTR(qstr_from_str("firmware-version"))); + + mp_printf(&mp_plat_print, "Detected firmware backup: "); + mp_obj_print(meta_firmware_version, PRINT_STR); + mp_printf(&mp_plat_print, " (%d) bytes.\n", meta_firmware_size); + + // TODO: Get from platform data or hub type + #if PYBRICKS_HUB_PRIMEHUB + mp_int_t valid_device_id = 0x81; + #elif PYBRICKS_HUB_ESSENTIALHUB + mp_int_t valid_device_id = 0x83; + #endif + + // Verify meta data + if (meta_device_id != valid_device_id) { + mp_printf(&mp_plat_print, "The firmware backup is not valid for this hub.\n"); return PBIO_ERROR_FAILED; } - mp_printf(&mp_plat_print, "Preparing to restore firmware.\n"); + mp_printf(&mp_plat_print, "Preparing storage for firmware installation...\n"); HAL_StatusTypeDef err; @@ -537,23 +574,24 @@ pbio_error_t pb_flash_restore_firmware(void) { for (uint32_t address = 0; address < FLASH_SIZE_BOOT; address += FLASH_SIZE_ERASE) { err = flash_raw_block_erase(address); if (err != HAL_OK) { + mp_printf(&mp_plat_print, "Unable to erase storage.\n"); return PBIO_ERROR_IO; } MICROPY_EVENT_POLL_HOOK; } - mp_printf(&mp_plat_print, "Restoring firmware.\n"); - - // Todo: verify the other meta data details, like size. + mp_printf(&mp_plat_print, "Restoring firmware...\n"); // Open firmware file if (lfs_file_open_retry("_firmware/lego-firmware.bin", LFS_O_RDONLY) != LFS_ERR_OK) { + mp_printf(&mp_plat_print, "Unable to open backup firmware file.\n"); return PBIO_ERROR_FAILED; } // All known back up firmwares are much larger than this. - if (file.size < 128 * 1024) { + if (file.size < 128 * 1024 || file.size != meta_firmware_size) { // TODO: Check that sha256 matches metadata + mp_printf(&mp_plat_print, "Invalid backup firmware file.\n"); return PBIO_ERROR_FAILED; } @@ -592,12 +630,14 @@ pbio_error_t pb_flash_restore_firmware(void) { // Read data from the firmware backup file if (lfs_file_read(&lfs, &file, buf_start, read_size) != read_size) { + mp_printf(&mp_plat_print, "Unable to read backup firmware file.\n"); return PBIO_ERROR_FAILED; } // Write the data err = flash_raw_write(done == 0 ? 0 : done + 4, buf, write_size); if (err != HAL_OK) { + mp_printf(&mp_plat_print, "Unable to copy the backup firmware file.\n"); return PBIO_ERROR_IO; } @@ -606,11 +646,13 @@ pbio_error_t pb_flash_restore_firmware(void) { // Close firmware file if (lfs_file_close(&lfs, &file) != LFS_ERR_OK) { + mp_printf(&mp_plat_print, "Unable to close the backup firmware file.\n"); return PBIO_ERROR_FAILED; } - mp_printf(&mp_plat_print, "Done. You may now reboot. Please keep USB attached.\n"); - + mp_printf(&mp_plat_print, "Done. The hub will now reboot. Please keep USB attached.\n"); + mp_hal_delay_ms(500); + pbdrv_reset(PBDRV_RESET_ACTION_RESET); return PBIO_SUCCESS; } From 8a988ad7ae85157a08e9aac4385436154597ef10 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 23 Dec 2021 15:56:46 +0100 Subject: [PATCH 13/15] pybricks/util_pb: Enable experimental flash on Essential Hub. Since flash needs to be rewritten anyway, just use #if for hub-specific code. --- bricks/stm32/main.c | 8 ++-- lib/pbio/platform/essential_hub/platform.c | 29 +++++++++++++- .../experimental/pb_module_experimental.c | 8 ++-- pybricks/util_pb/pb_flash.c | 40 ++++++++++++++----- pybricks/util_pb/pb_flash.h | 2 +- 5 files changed, 68 insertions(+), 19 deletions(-) diff --git a/bricks/stm32/main.c b/bricks/stm32/main.c index a599662cd..06c14b838 100644 --- a/bricks/stm32/main.c +++ b/bricks/stm32/main.c @@ -186,7 +186,7 @@ static uint32_t get_user_program(uint8_t **buf, uint32_t *free_len) { // If button was pressed, return code to run script in flash if (err == PBIO_ERROR_CANCELED) { - #if PYBRICKS_HUB_PRIMEHUB + #if (PYBRICKS_HUB_PRIMEHUB || PYBRICKS_HUB_ESSENTIALHUB) // Open existing main.mpy file from flash uint32_t size = 0; if (pb_flash_file_open_get_size("/_pybricks/main.mpy", &size) != PBIO_SUCCESS) { @@ -246,10 +246,10 @@ static uint32_t get_user_program(uint8_t **buf, uint32_t *free_len) { *free_len = len; - #if PYBRICKS_HUB_PRIMEHUB + #if (PYBRICKS_HUB_PRIMEHUB || PYBRICKS_HUB_ESSENTIALHUB) // Save program as file pb_flash_file_write("/_pybricks/main.mpy", *buf, len); - #endif // PYBRICKS_HUB_PRIMEHUB + #endif // (PYBRICKS_HUB_PRIMEHUB || PYBRICKS_HUB_ESSENTIALHUB) return len; } @@ -360,7 +360,7 @@ static void run_user_program(uint32_t len, uint8_t *buf, uint32_t free_len) { static void stm32_main(void) { - #if PYBRICKS_HUB_PRIMEHUB + #if (PYBRICKS_HUB_PRIMEHUB || PYBRICKS_HUB_ESSENTIALHUB) mp_hal_delay_ms(500); pb_flash_init(); #endif diff --git a/lib/pbio/platform/essential_hub/platform.c b/lib/pbio/platform/essential_hub/platform.c index d0c76c91c..7629fe96a 100644 --- a/lib/pbio/platform/essential_hub/platform.c +++ b/lib/pbio/platform/essential_hub/platform.c @@ -434,6 +434,33 @@ void DMA2_Stream0_IRQHandler(void) { pbdrv_adc_stm32_hal_handle_irq(); } +void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi) { + if (hspi->Instance == SPI2) { + // External flash + GPIO_InitTypeDef gpio_init; + + // /CS, active low + gpio_init.Pin = GPIO_PIN_12; + gpio_init.Mode = GPIO_MODE_OUTPUT_PP; + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + HAL_GPIO_Init(GPIOB, &gpio_init); + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); + + // SPI2_SCK + gpio_init.Pin = GPIO_PIN_10; + gpio_init.Mode = GPIO_MODE_AF_PP; + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + gpio_init.Alternate = GPIO_AF5_SPI2; + HAL_GPIO_Init(GPIOB, &gpio_init); + + // SPI2_MISO | SPI2_MOSI + gpio_init.Pin = GPIO_PIN_2 | GPIO_PIN_3; + HAL_GPIO_Init(GPIOC, &gpio_init); + } +} + // USB void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) { @@ -567,7 +594,7 @@ void SystemInit(void) { RCC_AHB1ENR_GPIODEN | RCC_AHB1ENR_DMA1EN | RCC_AHB1ENR_DMA2EN; RCC->APB1ENR |= RCC_APB1ENR_USART2EN | RCC_APB1ENR_USART3EN | RCC_APB1ENR_UART5EN | RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM4EN | - RCC_APB1ENR_I2C3EN | RCC_APB1ENR_FMPI2C1EN; + RCC_APB1ENR_I2C3EN | RCC_APB1ENR_FMPI2C1EN | RCC_APB1ENR_SPI2EN; RCC->APB2ENR |= RCC_APB2ENR_TIM8EN | RCC_APB2ENR_ADC1EN | RCC_APB2ENR_SYSCFGEN; RCC->AHB2ENR |= RCC_AHB2ENR_OTGFSEN; diff --git a/pybricks/experimental/pb_module_experimental.c b/pybricks/experimental/pb_module_experimental.c index 369d17538..bdae4cd4a 100644 --- a/pybricks/experimental/pb_module_experimental.c +++ b/pybricks/experimental/pb_module_experimental.c @@ -55,7 +55,7 @@ STATIC mp_obj_t mod_experimental_pthread_raise(mp_obj_t thread_id_in, mp_obj_t e STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_experimental_pthread_raise_obj, mod_experimental_pthread_raise); #endif // PYBRICKS_HUB_EV3BRICK -#if PYBRICKS_HUB_PRIMEHUB +#if (PYBRICKS_HUB_PRIMEHUB || PYBRICKS_HUB_ESSENTIALHUB) #include @@ -121,7 +121,7 @@ STATIC mp_obj_t experimental_restore_firmware(void) { } MP_DEFINE_CONST_FUN_OBJ_0(experimental_restore_firmware_obj, experimental_restore_firmware); -#endif // PYBRICKS_HUB_PRIMEHUB +#endif // (PYBRICKS_HUB_PRIMEHUB || PYBRICKS_HUB_ESSENTIALHUB) // pybricks.experimental.hello_world STATIC mp_obj_t experimental_hello_world(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -170,12 +170,12 @@ STATIC const mp_rom_map_elem_t experimental_globals_table[] = { #else { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_experimental) }, #endif // PYBRICKS_HUB_EV3BRICK - #if PYBRICKS_HUB_PRIMEHUB + #if (PYBRICKS_HUB_PRIMEHUB || PYBRICKS_HUB_ESSENTIALHUB) { MP_ROM_QSTR(MP_QSTR_flash_read_raw), MP_ROM_PTR(&experimental_flash_read_raw_obj) }, { MP_ROM_QSTR(MP_QSTR_flash_read_file), MP_ROM_PTR(&experimental_flash_read_file_obj) }, { MP_ROM_QSTR(MP_QSTR_flash_write_file), MP_ROM_PTR(&experimental_flash_write_file_obj) }, { MP_ROM_QSTR(MP_QSTR_restore_firmware), MP_ROM_PTR(&experimental_restore_firmware_obj) }, - #endif // PYBRICKS_HUB_PRIMEHUB + #endif // (PYBRICKS_HUB_PRIMEHUB || PYBRICKS_HUB_ESSENTIALHUB) { MP_ROM_QSTR(MP_QSTR_hello_world), MP_ROM_PTR(&experimental_hello_world_obj) }, }; STATIC MP_DEFINE_CONST_DICT(pb_module_experimental_globals, experimental_globals_table); diff --git a/pybricks/util_pb/pb_flash.c b/pybricks/util_pb/pb_flash.c index 05a63222e..52b006d25 100644 --- a/pybricks/util_pb/pb_flash.c +++ b/pybricks/util_pb/pb_flash.c @@ -5,7 +5,7 @@ #include "py/mpconfig.h" -#if PYBRICKS_HUB_PRIMEHUB +#if (PYBRICKS_HUB_PRIMEHUB || PYBRICKS_HUB_ESSENTIALHUB) #include @@ -25,8 +25,12 @@ #include +#if PYBRICKS_HUB_PRIMEHUB +#define FLASH_SIZE_TOTAL (32 * 0x100000) +#else +#define FLASH_SIZE_TOTAL (4 * 0x100000) +#endif -#define FLASH_SIZE_TOTAL (0x2000000) #define FLASH_SIZE_BOOT (0x100000) #define FLASH_SIZE_USER (FLASH_SIZE_TOTAL - FLASH_SIZE_BOOT) #define FLASH_SIZE_ERASE (0x1000) @@ -38,10 +42,16 @@ enum { FLASH_CMD_GET_STATUS = 0x05, FLASH_CMD_WRITE_ENABLE = 0x06, - FLASH_CMD_WRITE_DATA = 0x12, + FLASH_CMD_GET_ID = 0x9F, + #if PYBRICKS_HUB_PRIMEHUB FLASH_CMD_READ_DATA = 0x13, FLASH_CMD_ERASE_BLOCK = 0x21, - FLASH_CMD_GET_ID = 0x9F, + FLASH_CMD_WRITE_DATA = 0x12, + #else + FLASH_CMD_READ_DATA = 0x03, + FLASH_CMD_ERASE_BLOCK = 0x20, + FLASH_CMD_WRITE_DATA = 0x02, + #endif }; enum { @@ -103,8 +113,14 @@ static pbio_error_t pb_flash_spi_init(void) { } flash_enable(false); + #if PYBRICKS_HUB_PRIMEHUB + const uint8_t valid_id[] = {0xEF, 0x40, 0x19}; + #else + const uint8_t valid_id[] = {0xEF, 0x40, 0x16}; + #endif + // Verify flash device ID - if (id_data[0] != 239 || id_data[1] != 64 || id_data[2] != 25) { + if (memcmp(valid_id, id_data, sizeof(valid_id))) { return PBIO_ERROR_NO_DEV; } @@ -165,8 +181,14 @@ static HAL_StatusTypeDef flash_send_address_command(uint8_t command, uint32_t ad // Pack command and address as big endian. This is the absolute address, // starting in the boot partition. + #if PYBRICKS_HUB_PRIMEHUB uint8_t data[5] = {command}; pbio_set_uint32_be(&data[1], address); + #else + uint8_t data[4]; + pbio_set_uint32_be(&data[0], address); + data[0] = command; + #endif // Enable flash flash_enable(true); @@ -353,8 +375,8 @@ static const struct lfs_config cfg = { .read_size = 256, .prog_size = 256, - .block_size = 4096, - .block_count = 7936, + .block_size = FLASH_SIZE_ERASE, + .block_count = FLASH_SIZE_USER / FLASH_SIZE_ERASE, .lookahead = sizeof(lfs_lookahead_buf) * 8, .read_buffer = lfs_read_buf, @@ -556,7 +578,7 @@ pbio_error_t pb_flash_restore_firmware(void) { // TODO: Get from platform data or hub type #if PYBRICKS_HUB_PRIMEHUB mp_int_t valid_device_id = 0x81; - #elif PYBRICKS_HUB_ESSENTIALHUB + #else mp_int_t valid_device_id = 0x83; #endif @@ -657,4 +679,4 @@ pbio_error_t pb_flash_restore_firmware(void) { return PBIO_SUCCESS; } -#endif // PYBRICKS_HUB_PRIMEHUB +#endif // (PYBRICKS_HUB_PRIMEHUB || PYBRICKS_HUB_ESSENTIALHUB) diff --git a/pybricks/util_pb/pb_flash.h b/pybricks/util_pb/pb_flash.h index 38d77a315..e83ad656c 100644 --- a/pybricks/util_pb/pb_flash.h +++ b/pybricks/util_pb/pb_flash.h @@ -4,7 +4,7 @@ #ifndef _PB_FLASH_H_ #define _PB_FLASH_H_ -#if PYBRICKS_HUB_PRIMEHUB +#if (PYBRICKS_HUB_PRIMEHUB || PYBRICKS_HUB_ESSENTIALHUB) #include From e75d2c17d5295d29f9953c9c947b0f3667964a37 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 13 Jan 2022 11:25:54 +0100 Subject: [PATCH 14/15] pybricks.experimental: Hide experimental file i/o. Since flash read/write is blocking, we shouldn't allow this for user programs. These are useful for debugging the firmare installation process, but we can drop them once we have decent file i/o capability. --- pybricks/experimental/pb_module_experimental.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pybricks/experimental/pb_module_experimental.c b/pybricks/experimental/pb_module_experimental.c index bdae4cd4a..7adf12b1f 100644 --- a/pybricks/experimental/pb_module_experimental.c +++ b/pybricks/experimental/pb_module_experimental.c @@ -76,7 +76,7 @@ STATIC mp_obj_t experimental_flash_read_raw(size_t n_args, const mp_obj_t *pos_a // Return bytes read return mp_obj_new_bytes(read_data, read_len); } -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(experimental_flash_read_raw_obj, 0, experimental_flash_read_raw); +MP_DEFINE_CONST_FUN_OBJ_KW(experimental_flash_read_raw_obj, 0, experimental_flash_read_raw); STATIC mp_obj_t experimental_flash_read_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args, @@ -96,7 +96,7 @@ STATIC mp_obj_t experimental_flash_read_file(size_t n_args, const mp_obj_t *pos_ // Return data return mp_obj_new_bytes(file_buf, size); } -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(experimental_flash_read_file_obj, 0, experimental_flash_read_file); +MP_DEFINE_CONST_FUN_OBJ_KW(experimental_flash_read_file_obj, 0, experimental_flash_read_file); STATIC mp_obj_t experimental_flash_write_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args, @@ -112,7 +112,7 @@ STATIC mp_obj_t experimental_flash_write_file(size_t n_args, const mp_obj_t *pos return mp_const_none; } // See also experimental_globals_table below. This function object is added there to make it importable. -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(experimental_flash_write_file_obj, 0, experimental_flash_write_file); +MP_DEFINE_CONST_FUN_OBJ_KW(experimental_flash_write_file_obj, 0, experimental_flash_write_file); // pybricks.experimental.restore_firmware STATIC mp_obj_t experimental_restore_firmware(void) { @@ -171,9 +171,6 @@ STATIC const mp_rom_map_elem_t experimental_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_experimental) }, #endif // PYBRICKS_HUB_EV3BRICK #if (PYBRICKS_HUB_PRIMEHUB || PYBRICKS_HUB_ESSENTIALHUB) - { MP_ROM_QSTR(MP_QSTR_flash_read_raw), MP_ROM_PTR(&experimental_flash_read_raw_obj) }, - { MP_ROM_QSTR(MP_QSTR_flash_read_file), MP_ROM_PTR(&experimental_flash_read_file_obj) }, - { MP_ROM_QSTR(MP_QSTR_flash_write_file), MP_ROM_PTR(&experimental_flash_write_file_obj) }, { MP_ROM_QSTR(MP_QSTR_restore_firmware), MP_ROM_PTR(&experimental_restore_firmware_obj) }, #endif // (PYBRICKS_HUB_PRIMEHUB || PYBRICKS_HUB_ESSENTIALHUB) { MP_ROM_QSTR(MP_QSTR_hello_world), MP_ROM_PTR(&experimental_hello_world_obj) }, From 5bf94d925ed5cd30bfd71de6b6930801365bacec Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 13 Jan 2022 15:22:13 +0100 Subject: [PATCH 15/15] bricks/stm32/install_pybricks: Add compatible firmware versions. This ensures the installer does not give unexpected result when it is used with newer firmware. --- bricks/stm32/install_pybricks.py | 44 ++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/bricks/stm32/install_pybricks.py b/bricks/stm32/install_pybricks.py index a5eea53f3..76222f7ca 100644 --- a/bricks/stm32/install_pybricks.py +++ b/bricks/stm32/install_pybricks.py @@ -40,17 +40,32 @@ ERROR_BAD_FIRMWARE = """Invalid firmware file.""" ERROR_BAD_HUB = """Incorrect hub type.""" ERROR_DUAL_BOOT = """You are running dual-boot. Please re-install the official firmware first.""" +ERROR_INCOMPATIBLE_FIRMWARE = """The detected firmware is not compatible with this installer.""" ERROR_EXTERNAL_FLASH = """Unable to create space for Pybricks firmware.""" ERROR_FIRMWARE_COPY_FAILED = """Unable to copy the Pybricks firmware.""" -# Constants. +# Flash constants. FLASH_FIRMWARE_START = 0x8008000 FLASH_READ_SIZE = 32 FLASH_WRITE_SIZE = FLASH_READ_SIZE * 4 + +# Hub IDs. +HUB_ID_SPIKE_PRIME = 0x81 +HUB_ID_SPIKE_ESSENTIAL = 0x83 HUB_IDS = { - "LEGO Technic Large Hub(0x0009)": 0x81, - "LEGO Technic Large Hub(0x0010)": 0x81, - "LEGO Technic Small Hub(0x000D)": 0x83, + "LEGO Technic Large Hub(0x0009)": HUB_ID_SPIKE_PRIME, + "LEGO Technic Large Hub(0x0010)": HUB_ID_SPIKE_PRIME, + "LEGO Technic Small Hub(0x000D)": HUB_ID_SPIKE_ESSENTIAL, +} + +# LEGO firmware versions on which Pybricks installation was successfully tested. +KNOWN_SPIKE_PRIME_FW_VERSIONS = { + "v1.3.00.0000-e8c274a": "SPIKE App v2.0.4", + "v1.4.01.0000-594ce3d": "MINDSTORMS Robot Inventor App v10.3.0", +} +KNOWN_SPIKE_ESSENTIAL_FW_VERSIONS = { + "v1.0.00.0000-d94a557": "SPIKE App v2.0.0", + "v1.0.00.0070-51a2ff4": "SPIKE App v2.0.4", } @@ -75,8 +90,8 @@ def get_lego_firmware_info(): """Gets information about the running firmware.""" # Get firmware/device ID - hub_id = firmware.id_string() - if hub_id not in HUB_IDS: + id_string = firmware.id_string() + if id_string not in HUB_IDS: stop_installation(ERROR_BAD_HUB) # Get firmware reset vector @@ -90,7 +105,7 @@ def get_lego_firmware_info(): firmware_version_position = read_internal_flash_int(FLASH_FIRMWARE_START + 0x200) firmware_version = read_internal_flash(firmware_version_position, 20).decode() - return hub_id, firmware_version, firmware_size, firmware_reset_vector + return id_string, firmware_version, firmware_size, firmware_reset_vector def get_lego_firmware(size): @@ -134,7 +149,7 @@ def get_file_hash(path): def install(auto_reboot=True): # Get information about the running original LEGO firmware. - hub_id, lego_version, lego_size, lego_reset_vector = get_lego_firmware_info() + id_string, lego_version, lego_size, lego_reset_vector = get_lego_firmware_info() # Exit if user is running (outdated) Pybricks dual-boot firmware. if lego_reset_vector >= 0x80C0000: @@ -145,6 +160,15 @@ def install(auto_reboot=True): print(" Version:", lego_version) print(" Size:", lego_size / 1024, "KB") + if ( + HUB_IDS[id_string] == HUB_ID_SPIKE_PRIME + and lego_version not in KNOWN_SPIKE_PRIME_FW_VERSIONS + ) or ( + HUB_IDS[id_string] == HUB_ID_SPIKE_ESSENTIAL + and lego_version not in KNOWN_SPIKE_ESSENTIAL_FW_VERSIONS + ): + stop_installation(ERROR_INCOMPATIBLE_FIRMWARE) + # Back up the LEGO firmware so we can restore it from Pybricks later. print("Creating backup at:", LEGO_FIRMWARE_PATH) with open(LEGO_FIRMWARE_PATH, "wb") as backup_file: @@ -156,7 +180,7 @@ def install(auto_reboot=True): with open(LEGO_METADATA_PATH, "w") as lego_meta_file: lego_backup_size, lego_backup_hash = get_file_hash(LEGO_FIRMWARE_PATH) lego_info = { - "device-id": HUB_IDS[hub_id], + "device-id": HUB_IDS[id_string], "firmware-sha256": lego_backup_hash, "firmware-size": lego_size, "firmware-version": lego_version, @@ -175,7 +199,7 @@ def install(auto_reboot=True): stop_installation(ERROR_BAD_FIRMWARE) # Check that this firmware is made for this hub. - if HUB_IDS[hub_id] != pybricks_info["device-id"]: + if HUB_IDS[id_string] != pybricks_info["device-id"]: stop_installation(ERROR_BAD_HUB) # Display Pybricks firmware information.