From 2c31326cf2a81137abe8cd43286fda867119112b Mon Sep 17 00:00:00 2001 From: Michal Lenc Date: Sun, 20 Oct 2024 20:48:58 +0200 Subject: [PATCH] boot: add NuttX bootloader with update and recovery support This commit adds NuttX based bootloader with the support for image update and recovery if not confirmed. The algorithm utilizes three flash partitions: primary (image runs from this area), secondary and tertiary. Secondary and tertiary areas are used for update upload and recovery. The update is performed by simple copy from update area to primary area with recovery being created in recovery area if not already present. Once image is confirmed by the user, the image in update area is confirmed as well, update area becomes recovery area and vice versa. This means the recovery is always present (except for the first update) and subsequent updates just copy image from update to primary. This makes the update significantly faster and more considerable to flash wear while keeping the recovery/revert possibility. A header (aligned to flash's erase size) must be added to the beginning of the image. Python script nximage.py can be used to prepend this header to built binary. The algorithm also uses one erase page at the end of a partition (partition, not image!) to store flags used to indicate image confirm status and to detect update/recovery partitions. Any program uploading update image to the update partition has to erase this page for the boot to work correctly! The algorithm implementation is based on a patch initially developed for MCUboot project but rejected by the project's maintainers https://github.com/mcu-tools/mcuboot/pull/1902 Signed-off-by: Michal Lenc --- boot/nxboot/CMakeLists.txt | 32 ++ boot/nxboot/Kconfig | 88 ++++ boot/nxboot/Make.defs | 26 + boot/nxboot/Makefile | 34 ++ boot/nxboot/include/nxboot.h | 181 +++++++ boot/nxboot/loader/boot.c | 741 +++++++++++++++++++++++++++++ boot/nxboot/loader/flash.c | 311 ++++++++++++ boot/nxboot/loader/flash.h | 168 +++++++ boot/nxboot/nxboot_main.c | 102 ++++ boot/nxboot/tools/nximage.py | 113 +++++ boot/nxboot/tools/requirements.txt | 2 + 11 files changed, 1798 insertions(+) create mode 100644 boot/nxboot/CMakeLists.txt create mode 100644 boot/nxboot/Kconfig create mode 100644 boot/nxboot/Make.defs create mode 100644 boot/nxboot/Makefile create mode 100644 boot/nxboot/include/nxboot.h create mode 100644 boot/nxboot/loader/boot.c create mode 100644 boot/nxboot/loader/flash.c create mode 100644 boot/nxboot/loader/flash.h create mode 100644 boot/nxboot/nxboot_main.c create mode 100644 boot/nxboot/tools/nximage.py create mode 100644 boot/nxboot/tools/requirements.txt diff --git a/boot/nxboot/CMakeLists.txt b/boot/nxboot/CMakeLists.txt new file mode 100644 index 00000000000..fb405627780 --- /dev/null +++ b/boot/nxboot/CMakeLists.txt @@ -0,0 +1,32 @@ +# ############################################################################## +# apps/boot/nxboot/CMakeLists.txt +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_BOOT_NXBOOT) + nuttx_add_library(nxboot) + set(SRCS loader/boot.c loader/flash.c) + + if(BOOT_NXBOOT) + nuttx_add_application(NAME nxboot_loader SRCS nxboot_main.c + INCLUDE_DIRECTORIES include) + endif() + + target_include_directories(nxboot PUBLIC include) + target_sources(nxboot PRIVATE ${SRCS}) +endif() diff --git a/boot/nxboot/Kconfig b/boot/nxboot/Kconfig new file mode 100644 index 00000000000..cbf596a180e --- /dev/null +++ b/boot/nxboot/Kconfig @@ -0,0 +1,88 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +menuconfig BOOT_NXBOOT + bool "NuttX bootloader" + default n + select BCH + ---help--- + Enable support for the minimal NuttX based bootloader. + +if BOOT_NXBOOT + +config NXBOOT_PRIMARY_SLOT_PATH + string "Application firmware primary image slot path" + default "/dev/ota0" + ---help--- + The path to the application firmware image primary slot character + device driver. The image runs from this location. + Default: /dev/ota0 + +config NXBOOT_SECONDARY_SLOT_PATH + string "Application firmware secondary image slot path" + default "/dev/ota1" + ---help--- + The path to the application firmware image primary slot character + device driver. This is either update or recovery slot. + Default: /dev/ota1 + +config NXBOOT_TERTIARY_SLOT_PATH + string "Application firmware tertiary image slot path" + default "/dev/ota2" + ---help--- + The path to the application firmware image primary slot character + device driver. This is either update or recovery slot. + Default: /dev/ota2 + +config NXBOOT_HEADER_SIZE + hex "Application firmware image header size" + default 0x200 + ---help--- + Note that this size should be aligned with the program memory write + page size! + +config NXBOOT_BOOTLOADER + bool "Build nxboot bootloader application" + default n + select BOARDCTL + select BOARDCTL_BOOT_IMAGE + ---help--- + This option builds and links a bootloader application. This application + should be an entry function for NuttX. It checks for possible update/ + revert operation, performs it and boot the correct image. + +if NXBOOT_BOOTLOADER + +config NXBOOT_SWRESET_ONLY + bool "Perform update/revert only on SW reset" + default n + select BOARDCTL_RESET_CAUSE + ---help--- + This option ensures the update/revert is performed only for following + reset causes: + BOARDIOC_RESETCAUSE_CPU_SOFT: software reset + BOARDIOC_RESETCAUSE_CPU_RWDT: watchdog error + BOARDIOC_RESETCAUSE_PIN: reset button + + This way the board can keep its image (even if not confirmed) during + for example power shutdown and perform update/revent only if expected + based on user/maintainer input. + +config NXBOOT_PREVENT_DOWNGRADE + bool "Perform update only for newer version" + default n + ---help--- + NXboot uses Semantic Version 2.0.0 (without build metadata). By default + the update is performed for every version that doesn't match the + currently running one. If NXBOOT_PREVENT_DOWNGRADE selected, update is + performed only for newer versions (according to Semantic Version + preference rules). + + WARNING: NXboot currently implementes preferences only for + MAJOR.MINOR.PATCH and ignores prerelease. + +endif # NXBOOT_BOOTLOADER + +endif # BOOT_NXBOOT diff --git a/boot/nxboot/Make.defs b/boot/nxboot/Make.defs new file mode 100644 index 00000000000..6e7660c45cc --- /dev/null +++ b/boot/nxboot/Make.defs @@ -0,0 +1,26 @@ +############################################################################ +# apps/boot/nxboot/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_BOOT_NXBOOT),) +CONFIGURED_APPS += $(APPDIR)/boot/nxboot + +CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/boot/nxboot/include + +endif diff --git a/boot/nxboot/Makefile b/boot/nxboot/Makefile new file mode 100644 index 00000000000..f9666c7aa0d --- /dev/null +++ b/boot/nxboot/Makefile @@ -0,0 +1,34 @@ +############################################################################ +# apps/boot/nxboot/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +ifneq ($(CONFIG_NXBOOT_BOOTLOADER),) +PROGNAME = nxboot_loader +PRIORITY = SCHED_PRIORITY_DEFAULT +STACKSIZE = $(CONFIG_DEFAULT_TASK_STACKSIZE) + +MAINSRC = nxboot_main.c +endif + +CSRCS := loader/boot.c \ + loader/flash.c + +include $(APPDIR)/Application.mk diff --git a/boot/nxboot/include/nxboot.h b/boot/nxboot/include/nxboot.h new file mode 100644 index 00000000000..7047cf6e1df --- /dev/null +++ b/boot/nxboot/include/nxboot.h @@ -0,0 +1,181 @@ +/**************************************************************************** + * apps/boot/nxboot/include/nxboot.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __BOOT_NXBOOT_INCLUDE_NXBOOT_H +#define __BOOT_NXBOOT_INCLUDE_NXBOOT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define NXBOOT_PRIMARY_SLOT_NUM (0) +#define NXBOOT_SECONDARY_SLOT_NUM (1) +#define NXBOOT_TERTIARY_SLOT_NUM (2) + +/* Offsets to write pages containing confirmed and updated flags. These + * pages are located at the end of the partition, therefore index 0 means + * the first page from the end. + */ + +#define NXBOOT_CONFIRMED_PAGE_INDEX (0) +#define NXBOOT_UPDATED_PAGE_INDEX (1) + +#define NXBOOT_HEADER_MAGIC 0x534f584e /* NXOS. */ +#define NXBOOT_HEADER_MAGIC_INV 0xaca0abb1 /* NXOS inverted. This is used + * for images uploaded directly + * to the primary flash with + * the debugger. These images + * does not have precalculated + * CRC and flags at the + * end of the partition, but + * are considered to be valid. + */ + +#define NXBOOT_HEADER_PRERELEASE_MAXLEN 110 + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +enum nxboot_update_type +{ + NXBOOT_UPDATE_TYPE_NONE = 0, /* No action to do */ + NXBOOT_UPDATE_TYPE_UPDATE = 1, /* Update will take place upon reboot */ + NXBOOT_UPDATE_TYPE_REVERT = 2, /* Revert will take place upon reboot */ +}; + +/* Versioning is according to Semantic Versioning 2.0.0 + * refer to (https://semver.org/spec/v2.0.0.html) + */ + +struct nxboot_img_version +{ + uint16_t major; /* MAJOR version */ + uint16_t minor; /* MINOR version */ + uint16_t patch; /* PATCH version */ + + char pre_release[NXBOOT_HEADER_PRERELEASE_MAXLEN]; /* Additional pre-release version */ +}; + +struct nxboot_img_header +{ + uint32_t magic; /* Header magic */ + uint32_t size; /* Image size (excluding the header) */ + uint32_t crc; /* CRC32 of image (excluding the header). */ + + struct nxboot_img_version img_version; /* Image version */ +}; +static_assert(CONFIG_NXBOOT_HEADER_SIZE > sizeof(struct nxboot_img_header), + "CONFIG_NXBOOT_HEADER_SIZE has to be larger than" + "sizeof(struct nxboot_img_header)"); + +struct nxboot_state +{ + int update; /* Number of update slot */ + int recovery; /* Number of recovery slot */ + bool recovery_valid; /* True if recovery image contains valid recovery */ + bool primary_confirmed; /* True if primary slot is confirmed */ + enum nxboot_update_type next_boot; /* True if update slot has a valid image */ +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: nxboot_get_state + * + * Description: + * Gets the current bootloader state and stores it in the nxboot_state + * structure passed as an argument. This function may be used to determine + * which slot is update slot and where should application save incoming + * firmware. + * + * Input parameters: + * state: The pointer to nxboot_state structure. The state is stored here. + * + * Returned Value: + * 0 on success, -1 and sets errno on failure. + * + ****************************************************************************/ + +int nxboot_get_state(struct nxboot_state *state); + +/**************************************************************************** + * Name: nxboot_get_confirm + * + * Description: + * This function can be used to determine whether primary image is + * confirmed or not. This provides more direct access to confirm + * state compared to nxboot_get_state function that returns the full + * state of the bootloader. + * + * Returned Value: + * 1 means confirmed, 0 not confirmed, -1 and sets errno on failure. + * + ****************************************************************************/ + +int nxboot_get_confirm(void); + +/**************************************************************************** + * Name: nxboot_confirm + * + * Description: + * Confirms the image currently located in primary partition and marks + * its copy in update partition as a recovery. + * + * Returned Value: + * 0 on success, -1 and sets errno on failure. + * + ****************************************************************************/ + +int nxboot_confirm(void); + +/**************************************************************************** + * Name: nxboot_perform_swap + * + * Description: + * Checks for the possible firmware update and performs it by copying + * update image to primary slot or recovery image to primary slot in case + * of the revert. In any situation, this function ends with the valid + * image in primary slot. + * + * This is an entry point function that should be called from the + * bootloader application. + * + * Input parameters: + * check_only: Only repairs corrupted update, but do not start another one + * + * Returned Value: + * 0 on success, -1 and sets errno on failure. + * + ****************************************************************************/ + +int nxboot_perform_update(bool check_only); + +#endif /* __BOOT_NXBOOT_INCLUDE_NXBOOT_H */ diff --git a/boot/nxboot/loader/boot.c b/boot/nxboot/loader/boot.c new file mode 100644 index 00000000000..d2c0d2688fa --- /dev/null +++ b/boot/nxboot/loader/boot.c @@ -0,0 +1,741 @@ +/**************************************************************************** + * apps/boot/nxboot/loader/boot.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "flash.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifdef CONFIG_NXBOOT_PREVENT_DOWNGRADE + # warning "Downgrade prevention currently ignores prerelease." +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline bool get_image_flag(int fd, int index) +{ + uint8_t flag; + struct flash_partition_info info; + + if (flash_partition_info(fd, &info) < 0) + { + return false; + } + + if (flash_partition_read(fd, &flag, 1, + info.size - info.blocksize * (index + 1)) < 0) + { + return false; + } + + return flag == 0xfe; +} + +static inline int set_image_flag(int fd, int index) +{ + uint8_t flag; + struct flash_partition_info info; + + if (flash_partition_info(fd, &info) < 0) + { + return ERROR; + } + + flag = 0xfe; + return flash_partition_write(fd, &flag, 1, + info.size - (info.blocksize * (index + 1))); +} + +static inline void get_image_header(int fd, struct nxboot_img_header *header) +{ + int ret; + ret = flash_partition_read(fd, header, sizeof *header, 0); + if (ret < 0) + { + /* Something went wrong, treat the partition as empty. */ + + memset(header, 0, sizeof *header); + } +} + +static inline bool validate_image_header(struct nxboot_img_header *header) +{ + return header->magic == NXBOOT_HEADER_MAGIC || + header->magic == NXBOOT_HEADER_MAGIC_INV; +} + +static uint32_t calculate_crc(int fd, struct nxboot_img_header *header) +{ + char *buf; + int remain; + int readsiz; + off_t off; + uint32_t crc; + struct flash_partition_info info; + + if (flash_partition_info(fd, &info) < 0) + { + return false; + } + + buf = malloc(info.blocksize); + if (!buf) + { + return false; + } + + crc = 0xffffffff; + remain = header->size; + off = CONFIG_NXBOOT_HEADER_SIZE; + while (remain > 0) + { + readsiz = remain > info.blocksize ? info.blocksize : remain; + if (flash_partition_read(fd, buf, readsiz, off) != 0) + { + free(buf); + return 0xffffffff; + } + + off += readsiz; + remain -= readsiz; + crc = crc32part((uint8_t *)buf, readsiz, crc); + } + + free(buf); + return ~crc; +} + +static int copy_partition(int from, int where) +{ + struct nxboot_img_header header; + struct flash_partition_info info_from; + struct flash_partition_info info_where; + uint32_t crc; + uint32_t magic; + int readsiz; + int remain; + int blocksize; + off_t off; + char *buf; + + get_image_header(from, &header); + + if (flash_partition_info(from, &info_from) < 0) + { + return ERROR; + } + + if (flash_partition_info(where, &info_where) < 0) + { + return ERROR; + } + + if ((header.size + CONFIG_NXBOOT_HEADER_SIZE) > info_where.size) + { + return ERROR; + } + + blocksize = MAX(info_from.blocksize, info_where.blocksize); + + buf = malloc(blocksize); + if (!buf) + { + return ERROR; + } + + if (flash_partition_erase_last_sector(where) < 0) + { + return ERROR; + } + + remain = header.size + CONFIG_NXBOOT_HEADER_SIZE; + off = 0; + if (header.magic == NXBOOT_HEADER_MAGIC_INV) + { + /* This means we are doing a recovery of a primary image + * without the precalculated CRC. Calculate CRC and insert it + * into the recovery image header. Also flip header's magic to + * indicate this is an image with valid CRC. + */ + + magic = NXBOOT_HEADER_MAGIC; + crc = calculate_crc(from, &header); + if (flash_partition_read(from, buf, blocksize, 0) < 0) + { + free(buf); + return ERROR; + } + + memcpy(buf + offsetof(struct nxboot_img_header, magic), &magic, + sizeof magic); + memcpy(buf + offsetof(struct nxboot_img_header, crc), &crc, + sizeof crc); + if (flash_partition_write(where, buf, blocksize, 0) < 0) + { + free(buf); + return ERROR; + } + + off += blocksize; + remain -= blocksize; + } + + while (remain > 0) + { + readsiz = remain > blocksize ? blocksize : remain; + if (flash_partition_read(from, buf, readsiz, off) < 0) + { + free(buf); + return ERROR; + } + + if (flash_partition_write(where, buf, readsiz, off) < 0) + { + free(buf); + return ERROR; + } + + off += readsiz; + remain -= readsiz; + } + + if (header.magic != NXBOOT_HEADER_MAGIC_INV) + { + /* Copy currently set flags but only if the image has + * precalculated CRC. + */ + + if (get_image_flag(from, NXBOOT_UPDATED_PAGE_INDEX)) + { + set_image_flag(where, NXBOOT_UPDATED_PAGE_INDEX); + } + + if (get_image_flag(from, NXBOOT_CONFIRMED_PAGE_INDEX)) + { + set_image_flag(where, NXBOOT_CONFIRMED_PAGE_INDEX); + } + } + else + { + set_image_flag(where, NXBOOT_CONFIRMED_PAGE_INDEX); + } + + free(buf); + return OK; +} + +static bool validate_image(int fd) +{ + struct nxboot_img_header header; + + get_image_header(fd, &header); + if (!validate_image_header(&header)) + { + return false; + } + + if (header.magic == NXBOOT_HEADER_MAGIC_INV) + { + /* Images with no precalculated CRC are considered valid. These + * should be the images that are uploaded directly to the primary + * paritition with debugger/flasher and are not uploaded by the + * bootloader. These images also don't have confirmed flags, + * altough they are considered stable. + */ + + return true; + } + else + { + return calculate_crc(fd, &header) == header.crc; + } +} + +static bool compare_versions(struct nxboot_img_version *v1, + struct nxboot_img_version *v2) +{ +#ifndef CONFIG_NXBOOT_PREVENT_DOWNGRADE + int i; + if (v1->major != v2->major || + v1->minor != v2->minor || + v1->patch != v2->patch) + { + return false; + } + + for (i = 0; i < NXBOOT_HEADER_PRERELEASE_MAXLEN; i++) + { + if (v1->pre_release[i] != v2->pre_release[i]) + { + return false; + } + } + + return true; +#else + if (v1->major > v2->major || + v1->minor > v2->minor || + v1->patch > v2->patch) + { + return true; + } + + if (v1->major == v2->major && + v1->minor == v2->minor && + v1->patch == v2->patch) + { + /* TODO: compare prerelease */ + } + + return false; +#endif +} + +static enum nxboot_update_type + get_update_type(int primary, int update, int recovery, + struct nxboot_img_header *primary_header, + struct nxboot_img_header *update_header) +{ + if (get_image_flag(recovery, NXBOOT_CONFIRMED_PAGE_INDEX) && + get_image_flag(update, NXBOOT_UPDATED_PAGE_INDEX) && + ((!get_image_flag(primary, NXBOOT_CONFIRMED_PAGE_INDEX) && + primary_header->magic != NXBOOT_HEADER_MAGIC_INV) || + !validate_image(primary)) && validate_image(recovery)) + { + return NXBOOT_UPDATE_TYPE_REVERT; + } + + if (!get_image_flag(update, NXBOOT_CONFIRMED_PAGE_INDEX) && + !get_image_flag(update, NXBOOT_UPDATED_PAGE_INDEX) && + validate_image(update)) + { + if (compare_versions(&primary_header->img_version, + &update_header->img_version) && + validate_image(primary)) + { + return NXBOOT_UPDATE_TYPE_NONE; + } + + return NXBOOT_UPDATE_TYPE_UPDATE; + } + + return NXBOOT_UPDATE_TYPE_NONE; +} + +static int perform_update(struct nxboot_state *state, bool check_only) +{ + int update; + int recovery; + int primary; + int secondary; + int tertiary; + bool primary_valid; + + primary = flash_partition_open(CONFIG_NXBOOT_PRIMARY_SLOT_PATH); + assert(primary >= 0); + + secondary = flash_partition_open(CONFIG_NXBOOT_SECONDARY_SLOT_PATH); + assert(secondary >= 0); + + tertiary = flash_partition_open(CONFIG_NXBOOT_TERTIARY_SLOT_PATH); + assert(tertiary >= 0); + + if (state->update == NXBOOT_SECONDARY_SLOT_NUM) + { + update = secondary; + recovery = tertiary; + } + else + { + update = tertiary; + recovery = secondary; + } + + if (state->next_boot == NXBOOT_UPDATE_TYPE_REVERT && + (!check_only || !validate_image(primary))) + { + if (validate_image(recovery)) + { + syslog(LOG_INFO, "Reverting image to recovery.\n"); + copy_partition(recovery, primary); + } + } + else + { + primary_valid = validate_image(primary); + if (primary_valid && check_only) + { + /* Skip if primary image is valid (does not mather whether + * confirmed or not) and check_only option is set. + */ + + goto perform_update_done; + } + + if (!state->recovery_valid && state->primary_confirmed && + primary_valid) + { + /* Save current image as recovery only if it is valid and + * confirmed. We have to check this in case of restart + * during update process. + * If board is restarted during update, primary slot contains + * non-valid image and we do not want to copy this image to + * recovery slot. + * There also might be a case where primary image is valid + * but not confirmed (timing of board reset right after + * update is uploaded to secondary). Still we do not want + * to upload this to recovery. + */ + + syslog(LOG_INFO, "Creating recovery image.\n"); + copy_partition(primary, recovery); + if (!validate_image(recovery)) + { + syslog(LOG_INFO, "New recovery is not valid, stop update\n"); + goto perform_update_done; + } + + syslog(LOG_INFO, "Recovery image created.\n"); + } + + if (validate_image(update)) + { + /* Perform update only if update slot contains valid image. */ + + syslog(LOG_INFO, "Updating from update image.\n"); + copy_partition(update, primary); + + /* Mark update slot as updated. This is to prevent repeated + * updates. + */ + + set_image_flag(update, NXBOOT_UPDATED_PAGE_INDEX); + } + } + +perform_update_done: + flash_partition_close(primary); + flash_partition_close(secondary); + flash_partition_close(tertiary); + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: nxboot_get_state + * + * Description: + * Gets the current bootloader state and stores it in the nxboot_state + * structure passed as an argument. This function may be used to determine + * which slot is update slot and where should application save incoming + * firmware. + * + * Input parameters: + * state: The pointer to nxboot_state structure. The state is stored here. + * + * Returned Value: + * 0 on success, -1 and sets errno on failure. + * + ****************************************************************************/ + +int nxboot_get_state(struct nxboot_state *state) +{ + int primary; + int secondary; + int tertiary; + int update; + int recovery; + struct nxboot_img_header primary_header; + struct nxboot_img_header secondary_header; + struct nxboot_img_header tertiary_header; + struct nxboot_img_header *update_header; + + memset(state, 0, sizeof *state); + + primary = flash_partition_open(CONFIG_NXBOOT_PRIMARY_SLOT_PATH); + if (primary < 0) + { + return ERROR; + } + + secondary = flash_partition_open(CONFIG_NXBOOT_SECONDARY_SLOT_PATH); + if (secondary < 0) + { + flash_partition_close(primary); + return ERROR; + } + + tertiary = flash_partition_open(CONFIG_NXBOOT_TERTIARY_SLOT_PATH); + if (tertiary < 0) + { + flash_partition_close(primary); + flash_partition_close(secondary); + return ERROR; + } + + get_image_header(primary, &primary_header); + get_image_header(secondary, &secondary_header); + get_image_header(tertiary, &tertiary_header); + + update = secondary; + recovery = tertiary; + update_header = &secondary_header; + state->update = NXBOOT_SECONDARY_SLOT_NUM; + state->recovery = NXBOOT_TERTIARY_SLOT_NUM; + if (get_image_flag(secondary, NXBOOT_CONFIRMED_PAGE_INDEX) && + validate_image(secondary)) + { + /* Secondary image is confirmed and valid, use this as + * a potential recovery. + */ + + update = tertiary; + recovery = secondary; + state->recovery = NXBOOT_SECONDARY_SLOT_NUM; + state->update = NXBOOT_TERTIARY_SLOT_NUM; + update_header = &tertiary_header; + + if (secondary_header.crc == primary_header.crc) + { + state->recovery_valid = true; + } + } + else if (get_image_flag(tertiary, NXBOOT_CONFIRMED_PAGE_INDEX) && + tertiary_header.crc == primary_header.crc && + validate_image(tertiary)) + { + state->recovery_valid = true; + } + + if (get_image_flag(primary, NXBOOT_CONFIRMED_PAGE_INDEX) || + primary_header.magic == NXBOOT_HEADER_MAGIC_INV) + { + state->primary_confirmed = true; + } + + state->next_boot = get_update_type(primary, update, recovery, + &primary_header, update_header); + + flash_partition_close(primary); + flash_partition_close(secondary); + flash_partition_close(tertiary); + return OK; +} + +/**************************************************************************** + * Name: nxboot_get_confirm + * + * Description: + * This function can be used to determine whether primary image is + * confirmed or not. This provides more direct access to confirm + * state compared to nxboot_get_state function that returns the full + * state of the bootloader. + * + * Returned Value: + * 1 means confirmed, 0 not confirmed, -1 and sets errno on failure. + * + ****************************************************************************/ + +int nxboot_get_confirm(void) +{ + int primary; + + primary = flash_partition_open(CONFIG_NXBOOT_PRIMARY_SLOT_PATH); + if (primary < 0) + { + return ERROR; + } + + if (get_image_flag(primary, NXBOOT_CONFIRMED_PAGE_INDEX)) + { + return 1; + } + + return 0; +} + +/**************************************************************************** + * Name: nxboot_confirm + * + * Description: + * Confirms the image currently located in primary partition and marks + * its copy in update partition as a recovery. + * + * Returned Value: + * 0 on success, -1 and sets errno on failure. + * + ****************************************************************************/ + +int nxboot_confirm(void) +{ + int ret; + int update; + int primary; + int secondary; + int tertiary; + int recovery; + struct nxboot_state state; + + nxboot_get_state(&state); + if (state.primary_confirmed) + { + return OK; + } + + primary = flash_partition_open(CONFIG_NXBOOT_PRIMARY_SLOT_PATH); + if (primary < 0) + { + return ERROR; + } + + secondary = flash_partition_open(CONFIG_NXBOOT_SECONDARY_SLOT_PATH); + if (secondary < 0) + { + flash_partition_close(primary); + return ERROR; + } + + tertiary = flash_partition_open(CONFIG_NXBOOT_TERTIARY_SLOT_PATH); + if (tertiary < 0) + { + flash_partition_close(primary); + flash_partition_close(secondary); + return ERROR; + } + + if (state.update == NXBOOT_SECONDARY_SLOT_NUM) + { + update = secondary; + recovery = tertiary; + } + else + { + update = tertiary; + recovery = secondary; + } + + /* We need to mark both primary and update partitions as confirmed + * (update partition will become recovery once confirmed) and + * we have to remove confirmed flag from old recovery and set updated + * flag there. This is to prevent old recovery still identify as + * recovery and not to look as possible update. Therefore remove the + * entire last sector (clears confirmed flag) and write updated + * flag. + */ + + ret = OK; + if (set_image_flag(primary, NXBOOT_CONFIRMED_PAGE_INDEX) < 0) + { + ret = ERROR; + goto confirm_done; + } + + if (set_image_flag(update, NXBOOT_CONFIRMED_PAGE_INDEX) < 0) + { + ret = ERROR; + goto confirm_done; + } + + if (flash_partition_erase_last_sector(recovery) < 0) + { + ret = ERROR; + goto confirm_done; + } + + if (set_image_flag(recovery, NXBOOT_UPDATED_PAGE_INDEX) < 0) + { + ret = ERROR; + goto confirm_done; + } + +confirm_done: + flash_partition_close(primary); + flash_partition_close(secondary); + flash_partition_close(tertiary); + + return ret; +} + +/**************************************************************************** + * Name: nxboot_perform_update + * + * Description: + * Checks for the possible firmware update and performs it by copying + * update image to primary slot or recovery image to primary slot in case + * of the revert. In any situation, this function ends with the valid + * image in primary slot. + * + * This is an entry point function that should be called from the + * bootloader application. + * + * Input parameters: + * check_only: Only repairs corrupted update, but do not start another one + * + * Returned Value: + * 0 on success, -1 and sets errno on failure. + * + ****************************************************************************/ + +int nxboot_perform_update(bool check_only) +{ + int ret; + struct nxboot_state state; + + ret = nxboot_get_state(&state); + if (ret < 0) + { + return ERROR; + } + + if (state.next_boot != NXBOOT_UPDATE_TYPE_NONE) + { + /* We either want to update or revert. */ + + ret = perform_update(&state, check_only); + if (ret < 0) + { + syslog(LOG_ERR, "Update process failed. %s\n", + strerror(errno)); + } + } + + return ret; +} diff --git a/boot/nxboot/loader/flash.c b/boot/nxboot/loader/flash.c new file mode 100644 index 00000000000..fdeaf77f28e --- /dev/null +++ b/boot/nxboot/loader/flash.c @@ -0,0 +1,311 @@ +/**************************************************************************** + * apps/boot/nxboot/loader/flash.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "flash.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: flash_partition_open + * + * Description: + * Opens the partition based on a given name and returns the file + * descriptor to it. + * + * Input parameters: + * path: Path to the device. + * + * Returned Value: + * Valid file descriptor on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_open(const char *path) +{ + int fd; + + fd = open(path, O_RDWR); + if (fd < 0) + { + syslog(LOG_ERR, "Could not open %s partition: %s\n", + path, strerror(errno)); + return ERROR; + } + + return fd; +} + +/**************************************************************************** + * Name: flash_partition_close + * + * Description: + * Closes opened partition. + * + * Input parameters: + * fd: Valid file descriptor. + * + * Returned Value: + * 0 on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_close(int fd) +{ + return close(fd); +} + +/**************************************************************************** + * Name: flash_partition_write + * + * Description: + * Writes count data pointed to by buf at offset off to a partition + * referenced by file descriptor fd. + * + * Input parameters: + * fd: Valid file descriptor. + * buf: The pointer to data to be written. + * count: Number of bytes to be written. + * off: Write offset in bytes. + * + * Returned Value: + * 0 on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_write(int fd, const void *buf, size_t count, off_t off) +{ + int ret; + off_t pos; + size_t size; + ssize_t nbytes; + struct mtd_geometry_s geometry; + + ret = ioctl(fd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&geometry)); + if (ret < 0) + { + syslog(LOG_ERR, "ioctl MTDIOC_GEOMETRY failed: %s\n", strerror(errno)); + return ERROR; + } + + size = geometry.erasesize * geometry.neraseblocks; + if (count + off > size) + { + syslog(LOG_ERR, "Trying to write outside of flash area.\n"); + return ERROR; + } + + pos = lseek(fd, off, SEEK_SET); + if (pos != off) + { + syslog(LOG_ERR, "Could not seek to %ld: %s\n", off, strerror(errno)); + return ERROR; + } + + nbytes = write(fd, buf, count); + if (nbytes != count) + { + syslog(LOG_ERR, "Write to offset %ld failed %s\n", + off, strerror(errno)); + return ERROR; + } + + return OK; +} + +/**************************************************************************** + * Name: flash_partition_read + * + * Description: + * Read count data to buffer buf at offset off from a partition + * referenced by file descriptor fd. + * + * Input parameters: + * fd: Valid file descriptor. + * buf: The pointer where read data are stored. + * count: Number of bytes to be read. + * off: Read offset in bytes. + * + * Returned Value: + * 0 on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_read(int fd, void *buf, size_t count, off_t off) +{ + int ret; + off_t pos; + size_t size; + ssize_t nbytes; + struct mtd_geometry_s geometry; + + ret = ioctl(fd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&geometry)); + if (ret < 0) + { + syslog(LOG_ERR, "ioctl MTDIOC_GEOMETRY failed: %s\n", strerror(errno)); + return ERROR; + } + + size = geometry.erasesize * geometry.neraseblocks; + if (count + off > size) + { + syslog(LOG_ERR, "Trying to read outside of flash area.\n"); + return ERROR; + } + + pos = lseek(fd, off, SEEK_SET); + if (pos != off) + { + syslog(LOG_ERR, "Could not seek to %ld: %s\n", off, strerror(errno)); + return ERROR; + } + + nbytes = read(fd, buf, count); + if (nbytes != count) + { + syslog(LOG_ERR, "Read from offset %ld failed %s\n", + off, strerror(errno)); + return ERROR; + } + + return OK; +} + +/**************************************************************************** + * Name: flash_partition_erase + * + * Description: + * Erases the entire partition. + * + * Input parameters: + * fd: Valid file descriptor. + * + * Returned Value: + * 0 on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_erase(int fd) +{ + int ret; + + ret = ioctl(fd, MTDIOC_BULKERASE, 0); + if (ret < 0) + { + syslog(LOG_ERR, "Could not erase the partition: %s\n", + strerror(errno)); + return ERROR; + } + + return OK; +} + +/**************************************************************************** + * Name: flash_partition_erase_last_sector + * + * Description: + * Erases the last sector of the partition + * + * Input parameters: + * fd: Valid file descriptor. + * + * Returned Value: + * 0 on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_erase_last_sector(int fd) +{ + int ret; + struct mtd_erase_s erase; + struct flash_partition_info info; + + if (flash_partition_info(fd, &info) < 0) + { + return ERROR; + } + + erase.startblock = info.neraseblocks - 1; + erase.nblocks = 1; + + ret = ioctl(fd, MTDIOC_ERASESECTORS, &erase); + if (ret < 0) + { + syslog(LOG_ERR, "Could not erase the partition: %s\n", + strerror(errno)); + return ERROR; + } + + return OK; +} + +/**************************************************************************** + * Name: flash_partition_info + * + * Description: + * Returns the size of one block. + * + * Input parameters: + * fd: Valid file descriptor. + * info: Pointer to flash_partition_info structure where info is filled. + * + * Returned Value: + * Size of the block on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_info(int fd, struct flash_partition_info *info) +{ + int ret; + struct mtd_geometry_s geometry; + + ret = ioctl(fd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&geometry)); + if (ret < 0) + { + syslog(LOG_ERR, "ioctl MTDIOC_GEOMETRY failed: %s\n", strerror(errno)); + return ERROR; + } + + info->blocksize = geometry.blocksize; + info->size = geometry.erasesize * geometry.neraseblocks; + info->neraseblocks = geometry.neraseblocks; + + return OK; +} diff --git a/boot/nxboot/loader/flash.h b/boot/nxboot/loader/flash.h new file mode 100644 index 00000000000..1fb4be56d3e --- /dev/null +++ b/boot/nxboot/loader/flash.h @@ -0,0 +1,168 @@ +/**************************************************************************** + * apps/boot/nxboot/loader/flash.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __BOOT_NXBOOT_LOADER_FLASH_H +#define __BOOT_NXBOOT_LOADER_FLASH_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct flash_partition_info +{ + int size; + int blocksize; + int neraseblocks; +}; + +/**************************************************************************** + * Public Functions Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: flash_partition_open + * + * Description: + * Opens the partition based on a given name and returns the file + * descriptor to it. + * + * Input parameters: + * path: Path to the device. + * + * Returned Value: + * Valid file descriptor on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_open(const char *path); + +/**************************************************************************** + * Name: flash_partition_close + * + * Description: + * Closes opened partition. + * + * Input parameters: + * fd: Valid file descriptor. + * + * Returned Value: + * 0 on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_close(int fd); + +/**************************************************************************** + * Name: flash_partition_write + * + * Description: + * Writes count data pointed to by buf at offset off to a partition + * referenced by file descriptor fd. + * + * Input parameters: + * fd: Valid file descriptor. + * buf: The pointer to data to be written. + * count: Number of bytes to be written. + * off: Write offset in bytes. + * + * Returned Value: + * 0 on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_write(int fd, const void *buf, size_t count, off_t off); + +/**************************************************************************** + * Name: flash_partition_read + * + * Description: + * Read count data to buffer buf at offset off from a partition + * referenced by file descriptor fd. + * + * Input parameters: + * fd: Valid file descriptor. + * buf: The pointer where read data are stored. + * count: Number of bytes to be read. + * off: Read offset in bytes. + * + * Returned Value: + * 0 on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_read(int fd, void *buf, size_t count, off_t off); + +/**************************************************************************** + * Name: flash_partition_erase + * + * Description: + * Erases the entire partition. + * + * Input parameters: + * fd: Valid file descriptor. + * + * Returned Value: + * 0 on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_erase(int fd); + +/**************************************************************************** + * Name: flash_partition_erase_last_sector + * + * Description: + * Erases the last sector of the partition + * + * Input parameters: + * fd: Valid file descriptor. + * + * Returned Value: + * 0 on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_erase_last_sector(int fd); + +/**************************************************************************** + * Name: flash_partition_info + * + * Description: + * Returns the size of one block. + * + * Input parameters: + * fd: Valid file descriptor. + * info: Pointer to flash_partition_info structure where info is filled. + * + * Returned Value: + * Size of the block on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_info(int fd, struct flash_partition_info *info); + +#endif /* __BOOT_NXBOOT_LOADER_FLASH_H */ diff --git a/boot/nxboot/nxboot_main.c b/boot/nxboot/nxboot_main.c new file mode 100644 index 00000000000..81ae2f40cdf --- /dev/null +++ b/boot/nxboot/nxboot_main.c @@ -0,0 +1,102 @@ +/**************************************************************************** + * apps/boot/nxboot/nxboot_main.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: nxboot_main + * + * Description: + * NuttX bootlaoder entry point. + * + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + struct boardioc_boot_info_s info; + bool check_only; +#ifdef CONFIG_NXBOOT_SWRESET_ONLY + int ret; + FAR struct boardioc_reset_cause_s cause; +#endif + +#if defined(CONFIG_BOARDCTL) && !defined(CONFIG_NSH_ARCHINIT) + /* Perform architecture-specific initialization (if configured) */ + + boardctl(BOARDIOC_INIT, 0); + +#ifdef CONFIG_BOARDCTL_FINALINIT + /* Perform architecture-specific final-initialization (if configured) */ + + boardctl(BOARDIOC_FINALINIT, 0); +#endif +#endif + + syslog(LOG_INFO, "*** nxboot ***\n"); + +#ifdef CONFIG_NXBOOT_SWRESET_ONLY + check_only = true; + ret = boardctl(BOARDIOC_RESET_CAUSE, (uintptr_t)&cause); + if (ret >= 0) + { + if (cause.cause == BOARDIOC_RESETCAUSE_CPU_SOFT || + cause.cause == BOARDIOC_RESETCAUSE_CPU_RWDT || + cause.cause == BOARDIOC_RESETCAUSE_PIN) + { + check_only = false; + } + else + { + syslog(LOG_INFO, "Power reset detected, performing check only.\n"); + } + } +#else + check_only = false; +#endif + + if (nxboot_perform_update(check_only) < 0) + { + syslog(LOG_ERR, "Could not find bootable image.\n"); + return 0; + } + + syslog(LOG_INFO, "Found bootable image, boot from primary.\n"); + + /* Call board specific image boot */ + + info.path = CONFIG_NXBOOT_PRIMARY_SLOT_PATH; + info.header_size = CONFIG_NXBOOT_HEADER_SIZE; + + return boardctl(BOARDIOC_BOOT_IMAGE, (uintptr_t)&info); +} diff --git a/boot/nxboot/tools/nximage.py b/boot/nxboot/tools/nximage.py new file mode 100644 index 00000000000..694e93ccee1 --- /dev/null +++ b/boot/nxboot/tools/nximage.py @@ -0,0 +1,113 @@ +"""Python script that prepares the NuttX image to be used with NX bootloader""" + +import argparse +import io +import os +import struct +import zlib + +import semantic_version + + +class NxImage: + def __init__( + self, path: str, result: str, version: str, header_size: int, primary: bool + ) -> None: + self.path = path + self.result = result + self.size = os.stat(path).st_size + self.version = semantic_version.Version(version) + self.header_size = header_size + self.primary = primary + self.crc = 0 + + with open(path, "rb") as f: + while data := f.read(io.DEFAULT_BUFFER_SIZE): + self.crc = zlib.crc32(data, self.crc) + + def __repr__(self): + repr = ( + "" + ) + return repr + + def add_header(self): + with open(self.path, "r+b") as src, open(self.result, "w+b") as dest: + if self.primary: + dest.write(b"\xb1\xab\xa0\xac") + else: + dest.write(b"\x4e\x58\x4f\x53") + dest.write(struct.pack(" argparse.Namespace: + """Parse passed arguments and return result.""" + parser = argparse.ArgumentParser(description="Tool for Nuttx Bootloader") + parser.add_argument( + "--version", + default="0.0.0", + help="Image version according to Semantic Versioning 2.0.0.", + ) + parser.add_argument( + "--header_size", + type=lambda x: int(x, 0), + default=0x200, + help="Size of the image header.", + ) + parser.add_argument( + "--primary", + action="store_true", + help="Primary image intended to be uploaded directly to primary memory.", + ) + parser.add_argument( + "-v", + action="store_true", + help="Verbose output. This prints information abourt the created image.", + ) + parser.add_argument( + "PATH", + default="nuttx.bin", + help="Path to the NuttX image.", + ) + parser.add_argument( + "RESULT", + default="nuttx.img", + help="Path where the resulting NuttX image is stored.", + ) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + image = NxImage(args.PATH, args.RESULT, args.version, args.header_size, args.primary) + image.add_header() + if args.v: + print(image) + + +if __name__ == "__main__": + main() diff --git a/boot/nxboot/tools/requirements.txt b/boot/nxboot/tools/requirements.txt new file mode 100644 index 00000000000..62b4122af4a --- /dev/null +++ b/boot/nxboot/tools/requirements.txt @@ -0,0 +1,2 @@ +argparse>=1.4.0 +semantic_version>=2.10.0