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..92c0b391108 --- /dev/null +++ b/boot/nxboot/tools/nximage.py @@ -0,0 +1,115 @@ +"""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