apps/boot/nxboot: Enhancements to add progress messages and copy-to-RAM feature

This adds Kconfig-enabled progress messages that are output to stdout.

It also adds Kconfig-enabled option to copy the validated and bootable image to RAM

Signed-off by: Tim Hardisty <timh@jti.uk.com>
This commit is contained in:
Tim Hardisty 2025-04-24 11:15:26 +01:00 committed by Xiang Xiao
parent 4b8b96f060
commit bcd1b46946
5 changed files with 454 additions and 20 deletions

View file

@ -64,6 +64,36 @@ config NXBOOT_BOOTLOADER
if NXBOOT_BOOTLOADER
config NXBOOT_COPY_TO_RAM
bool "Copy bootable image to RAM before calling board boot-image function"
default n
---help---
The is a board and/or arch specific option that may be used when running
directly from flash, especially if external flash, even in XIP mode, is too
slow.
Running from RAM usually results in faster execution but the board must, of
course, have sufficient RAM available for the application image, stack
and heap.
config NXBOOT_RAMSTART
hex "Start address in RAM that the application is to be loaded"
default 0x0
depends on NXBOOT_COPY_TO_RAM
---help---
This will be board specific. A check of the board's linker script
may be informative. For example the SAMA5D2-XULT eval board's uboot
linker script - boards/arm/sama5/sama5d2-xult/scripts/uboot.ld - has:
sdram (W!RX) : ORIGIN = 0x20008000, LENGTH = 256M - 32K
This shows the load address to be 0x20008000 and would be the address
to use here if the same linker script is to be be used for NXboot.
Typically the address is the base address of the RAM to be used, plus the
size of the NXboot image itself. The example above has reserved
32KiB (0x8000) for this from the 256MiB available on the board at
address 0x20000000.
config NXBOOT_SWRESET_ONLY
bool "Perform update/revert only on SW reset"
default n
@ -86,12 +116,35 @@ config NXBOOT_PREVENT_DOWNGRADE
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
performed only for newer versions (according to Semantic Version
preference rules).
WARNING: NXboot currently implements preferences only for
MAJOR.MINOR.PATCH and ignores prerelease.
config NXBOOT_PRINTF_PROGRESS
bool "Enable progress messages to be sent to STDOUT"
default y
---help---
This will display progress during typically lengthy operations:
- Calculating checksums
- copying images between slots
Note: the NXboot binary will be approximately 2KiB larger with this enabled.
choice
prompt "Choose preferred progress indication type"
depends on NXBOOT_PRINTF_PROGRESS
default NXBOOT_PRINTF_PROGRESS_PERCENT
config NXBOOT_PRINTF_PROGRESS_DOTS
bool "Display progress using sequential dots"
config NXBOOT_PRINTF_PROGRESS_PERCENT
bool "Display progress using percentage remaining"
endchoice
endif # NXBOOT_BOOTLOADER
endif # BOOT_NXBOOT

View file

@ -35,6 +35,8 @@
* Pre-processor Definitions
****************************************************************************/
#define ERROR -1
#define NXBOOT_PRIMARY_SLOT_NUM (0)
#define NXBOOT_SECONDARY_SLOT_NUM (1)
#define NXBOOT_TERTIARY_SLOT_NUM (2)
@ -117,6 +119,7 @@ struct nxboot_img_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)");
@ -131,10 +134,42 @@ struct nxboot_state
enum nxboot_update_type next_boot; /* nxboot_update_type with next operation */
};
enum progress_type_e
{
nxboot_info = 0, /* Prefixes arg. string with "INFO:" */
nxboot_error, /* Prefixes arg. string with "ERR:" */
nxboot_progress_start, /* Prints arg. string with no newline to allow ..... sequence to follow */
nxboot_progress_dot, /* Prints of a "." to the ..... progress sequence */
nxboot_progress_percent, /* Displays progress as % remaining */
nxboot_progress_end, /* Flags end of a "..." progrees sequence and prints newline */
};
enum progress_msg_e
{
startup_msg = 0,
power_reset,
soft_reset,
found_bootable_image,
no_bootable_image,
boardioc_image_boot_fail,
ramcopy_started,
recovery_revert,
recovery_create,
update_from_update,
validate_primary,
validate_recovery,
validate_update,
recovery_created,
recovery_invalid,
update_failed,
};
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
void nxboot_progress(enum progress_type_e type, ...);
/****************************************************************************
* Name: nxboot_get_state
*
@ -148,7 +183,7 @@ struct nxboot_state
* state: The pointer to nxboot_state structure. The state is stored here.
*
* Returned Value:
* 0 on success, -1 and sets errno on failure.
* OK (0) on success, ERROR (-1) and sets errno on failure.
*
****************************************************************************/
@ -164,7 +199,7 @@ int nxboot_get_state(struct nxboot_state *state);
* if afterwards.
*
* Returned Value:
* Valid file descriptor on success, -1 and sets errno on failure.
* Valid file descriptor on success, ERROR (-1) and sets errno on failure.
*
****************************************************************************/
@ -180,7 +215,7 @@ int nxboot_open_update_partition(void);
* state of the bootloader.
*
* Returned Value:
* 1 means confirmed, 0 not confirmed, -1 and sets errno on failure.
* 1 if confirmed, OK (0) on success, ERROR (-1) and sets errno on failure.
*
****************************************************************************/
@ -194,14 +229,14 @@ int nxboot_get_confirm(void);
* its copy in update partition as a recovery.
*
* Returned Value:
* 0 on success, -1 and sets errno on failure.
* OK (0) on success, ERROR (-1) and sets errno on failure.
*
****************************************************************************/
int nxboot_confirm(void);
/****************************************************************************
* Name: nxboot_perform_swap
* Name: nxboot_perform_update
*
* Description:
* Checks for the possible firmware update and performs it by copying
@ -216,10 +251,29 @@ int nxboot_confirm(void);
* check_only: Only repairs corrupted update, but do not start another one
*
* Returned Value:
* 0 on success, -1 and sets errno on failure.
* OK (0) on success, ERROR (-1) and sets errno on failure.
*
****************************************************************************/
int nxboot_perform_update(bool check_only);
/****************************************************************************
* Name: nxboot_ramcopy
*
* Description:
* Copies the (already) validate bootable image to RAM memory
*
* NOTE - no checking that the RAM location is correct, nor that the
* image size is appropriate for that RAM address!
*
* Input parameters:
* none
*
* Returned Value:
* OK (0) on success, ERROR (-1) on fail
*
****************************************************************************/
int nxboot_ramcopy(void);
#endif /* __BOOT_NXBOOT_INCLUDE_NXBOOT_H */

View file

@ -79,6 +79,9 @@ static uint32_t calculate_crc(int fd, struct nxboot_img_header *header)
off_t off;
uint32_t crc;
struct flash_partition_info info;
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
int total_size;
#endif
if (flash_partition_info(fd, &info) < 0)
{
@ -94,6 +97,9 @@ static uint32_t calculate_crc(int fd, struct nxboot_img_header *header)
crc = 0xffffffff;
off = offsetof(struct nxboot_img_header, crc) + sizeof crc;
remain = header->size + header->header_size - off;
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
total_size = remain;
#endif
while (remain > 0)
{
readsiz = remain > info.blocksize ? info.blocksize : remain;
@ -106,6 +112,15 @@ static uint32_t calculate_crc(int fd, struct nxboot_img_header *header)
off += readsiz;
remain -= readsiz;
crc = crc32part((uint8_t *)buf, readsiz, crc);
if ((remain % 25) == 0)
{
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
nxboot_progress(nxboot_progress_percent,
((total_size - remain) * 100) / total_size);
#else
nxboot_progress(nxboot_progress_dot);
#endif
}
}
free(buf);
@ -124,6 +139,9 @@ static int copy_partition(int from, int where, struct nxboot_state *state,
int blocksize;
off_t off;
char *buf;
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
int total_size;
#endif
get_image_header(from, &header);
@ -143,6 +161,9 @@ static int copy_partition(int from, int where, struct nxboot_state *state,
return ERROR;
}
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
total_size = remain * 100;
#endif
blocksize = MAX(info_from.blocksize, info_where.blocksize);
buf = malloc(blocksize);
@ -202,6 +223,15 @@ static int copy_partition(int from, int where, struct nxboot_state *state,
off += readsiz;
remain -= readsiz;
if ((remain % 25) == 0)
{
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
nxboot_progress(nxboot_progress_percent,
((total_size - remain) * 100) / total_size);
#else
nxboot_progress(nxboot_progress_dot);
#endif
}
}
free(buf);
@ -218,6 +248,7 @@ static bool validate_image(int fd)
return false;
}
syslog(LOG_INFO, "Validating image.\n");
return calculate_crc(fd, &header) == header.crc;
}
@ -268,18 +299,24 @@ static enum nxboot_update_type
struct nxboot_img_header *update_header,
struct nxboot_img_header *recovery_header)
{
nxboot_progress(nxboot_progress_start, validate_primary);
bool primary_valid = validate_image(primary);
nxboot_progress(nxboot_progress_end);
nxboot_progress(nxboot_progress_start, validate_update);
if (update_header->magic == NXBOOT_HEADER_MAGIC && validate_image(update))
{
if (primary_header->crc != update_header->crc ||
!compare_versions(&primary_header->img_version,
&update_header->img_version) || !primary_valid)
{
nxboot_progress(nxboot_progress_end);
return NXBOOT_UPDATE_TYPE_UPDATE;
}
}
nxboot_progress(nxboot_progress_end);
if (IS_INTERNAL_MAGIC(recovery_header->magic) && state->recovery_valid &&
((IS_INTERNAL_MAGIC(primary_header->magic) &&
!state->primary_confirmed) || !primary_valid))
@ -292,6 +329,7 @@ static enum nxboot_update_type
static int perform_update(struct nxboot_state *state, bool check_only)
{
int successful;
int update;
int recovery;
int primary;
@ -331,18 +369,25 @@ static int perform_update(struct nxboot_state *state, bool check_only)
recovery = secondary;
}
nxboot_progress(nxboot_progress_start, validate_primary);
if (state->next_boot == NXBOOT_UPDATE_TYPE_REVERT &&
(!check_only || !validate_image(primary)))
{
nxboot_progress(nxboot_progress_end);
if (state->recovery_valid)
{
syslog(LOG_INFO, "Reverting image to recovery.\n");
nxboot_progress(nxboot_progress_start, recovery_revert);
copy_partition(recovery, primary, state, false);
nxboot_progress(nxboot_progress_end);
}
}
else
{
nxboot_progress(nxboot_progress_end);
nxboot_progress(nxboot_progress_start, validate_primary);
primary_valid = validate_image(primary);
nxboot_progress(nxboot_progress_end);
if (primary_valid && check_only)
{
/* Skip if primary image is valid (does not mather whether
@ -368,21 +413,33 @@ static int perform_update(struct nxboot_state *state, bool check_only)
*/
syslog(LOG_INFO, "Creating recovery image.\n");
nxboot_progress(nxboot_progress_start, recovery_create);
copy_partition(primary, recovery, state, false);
if (!validate_image(recovery))
nxboot_progress(nxboot_progress_end);
nxboot_progress(nxboot_progress_start, validate_recovery);
successful = validate_image(recovery);
nxboot_progress(nxboot_progress_end);
if (!successful)
{
syslog(LOG_INFO, "New recovery is not valid, stop update.\n");
syslog(LOG_INFO,
"New recovery is not valid,stop update.\n");
nxboot_progress(nxboot_info, recovery_invalid);
goto perform_update_done;
}
syslog(LOG_INFO, "Recovery image created.\n");
nxboot_progress(nxboot_info, recovery_created);
}
if (validate_image(update))
nxboot_progress(nxboot_progress_start, validate_update);
successful = validate_image(update);
nxboot_progress(nxboot_progress_end);
if (successful)
{
/* Perform update only if update slot contains valid image. */
syslog(LOG_INFO, "Updating from update image.\n");
nxboot_progress(nxboot_progress_start, update_from_update);
if (copy_partition(update, primary, state, true) >= 0)
{
/* Erase the first sector of update partition. This marks the
@ -393,6 +450,8 @@ static int perform_update(struct nxboot_state *state, bool check_only)
flash_partition_erase_first_sector(update);
}
nxboot_progress(nxboot_progress_end);
}
}
@ -403,6 +462,46 @@ perform_update_done:
return OK;
}
#ifdef CONFIG_NXBOOT_COPY_TO_RAM
int nxboot_ramcopy(void)
{
int ret = OK;
int primary;
struct nxboot_img_header header;
ssize_t bytes;
static uint8_t *buf;
primary = flash_partition_open(CONFIG_NXBOOT_PRIMARY_SLOT_PATH);
if (primary < 0)
{
return ERROR;
}
get_image_header(primary, &header);
buf = malloc(header.size);
if (!buf)
{
ret = ERROR;
goto exit_with_error;
}
bytes = pread(primary, buf, header.size, header.header_size);
if (bytes != header.size)
{
ret = ERROR;
goto exit_with_error;
}
memcpy((uint32_t *)CONFIG_NXBOOT_RAMSTART, buf, header.size);
exit_with_error:
flash_partition_close(primary);
free(buf);
return ret;
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
@ -528,7 +627,9 @@ int nxboot_get_state(struct nxboot_state *state)
state->update = NXBOOT_TERTIARY_SLOT_NUM;
}
nxboot_progress(nxboot_progress_start, validate_recovery);
state->recovery_valid = validate_image(recovery);
nxboot_progress(nxboot_progress_end);
state->recovery_present = primary_header.crc == recovery_header->crc;
/* The image is confirmed if it has either NXBOOT_HEADER_MAGIC or a
@ -611,7 +712,7 @@ int nxboot_get_confirm(void)
int recovery;
int recovery_pointer;
char *path;
int ret = 0;
int ret = OK;
struct nxboot_img_header primary_header;
struct nxboot_img_header recovery_header;
@ -799,6 +900,7 @@ int nxboot_perform_update(bool check_only)
*/
syslog(LOG_ERR, "Update process failed: %s\n", strerror(errno));
nxboot_progress(nxboot_error, update_failed);
}
}
@ -823,3 +925,4 @@ int nxboot_perform_update(bool check_only)
return ret;
}

View file

@ -202,8 +202,8 @@ int flash_partition_read(int fd, void *buf, size_t count, off_t off)
nbytes = read(fd, buf, count);
if (nbytes != count)
{
syslog(LOG_ERR, "Read from offset %ld failed %s\n",
off, strerror(errno));
syslog(LOG_ERR, "Read from offset %ld failed %s\n", off,
strerror(errno));
return ERROR;
}
@ -265,7 +265,7 @@ int flash_partition_erase_first_sector(int fd)
if (ret < 0)
{
syslog(LOG_ERR, "Could not erase the partition: %s\n",
strerror(errno));
strerror(errno));
return ERROR;
}
@ -295,7 +295,8 @@ int flash_partition_info(int fd, struct flash_partition_info *info)
ret = ioctl(fd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&geometry));
if (ret < 0)
{
syslog(LOG_ERR, "ioctl MTDIOC_GEOMETRY failed: %s\n", strerror(errno));
syslog(LOG_ERR, "ioctl MTDIOC_GEOMETRY failed: %s\n",
strerror(errno));
return ERROR;
}

View file

@ -28,28 +28,235 @@
#include <stdio.h>
#include <syslog.h>
#include <nuttx/ascii.h>
#include <sys/param.h>
#include <nxboot.h>
#include <sys/boardctl.h>
/****************************************************************************
* Private Types
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
static bool g_progress_started = false;
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
static bool g_progress_percent_started = false;
#endif
/****************************************************************************
* Private Data
****************************************************************************/
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS
# ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
static const char backtab[] =
{
ASCII_BS, ASCII_BS, ASCII_BS, ASCII_BS, '\0',
};
# endif
static const char *progress_msgs[] =
{
[startup_msg] = "*** nxboot ***",
[power_reset] = "Power Reset detected, check images only",
[soft_reset] = "Soft reset detected, check for update",
[found_bootable_image] = "Found bootable image, boot from primary",
[no_bootable_image] = "No bootable image found",
[boardioc_image_boot_fail] = "Board failed to boot bootable image",
[ramcopy_started] = "Copying bootable image to RAM",
[recovery_revert] = "Reverting image to recovery",
[recovery_create] = "Creating recovery image",
[update_from_update] = "Updating from update image",
[validate_primary] = "Validating primary image",
[validate_recovery] = "Validating recovery image",
[validate_update] = "Validating update image",
[recovery_created] = "Recovery image created",
[recovery_invalid] = "Recovery image invalid, update stopped",
[update_failed] = "Update failed",
};
#endif /* CONFIG_NXBOOT_PRINTF_PROGRESS */
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: nxboot_progress
*
* Description:
* If enabled, this function prints progress messages to stdout.
* Messages are handled via integer enums, allowing this function to be
* easily replaced if required with no changes needed to the underlying
* code
*
* Input Parameters:
* type - the progress message type to be printed, as per progress_type_e:
* - nxboot_info: Prefixes arg. string with "INFO:"
* - nxboot_error: Prefixes arg. string with "ERR:"
* - nxboot_progress_start: Prints arg. string with no newline
* to allow a ..... sequence
* or % remaining to follow
* - nxboot_progress_dot: Prints a "." to the ..... sequence
* - nxboot_progress_percent: Displays progress as % remaining
* - nxboot_progress_end, Flags end of a progrees sequence
*
* ... - variadic argument:
* - the enum (int) reference to the message string or
* - the % remaining in the case of type: nxboot_progress_percent
*
* Returned Value:
* None
*
****************************************************************************/
void nxboot_progress(enum progress_type_e type, ...)
{
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS
va_list arg;
va_start(arg, type);
switch (type)
{
case nxboot_info:
{
int idx = va_arg(arg, int);
assert(progress_msgs[idx]);
if (strlen(progress_msgs[idx]) == 0)
{
syslog(LOG_ERR, "No progress message for idx: %d\n", idx);
break;
}
dprintf(STDOUT_FILENO, "%s\n", progress_msgs[idx]);
}
break;
case nxboot_error:
{
int idx = va_arg(arg, int);
assert(progress_msgs[idx]);
if (strlen(progress_msgs[idx]) == 0)
{
syslog(LOG_ERR, "No progress message for idx: %d\n", idx);
break;
}
dprintf(STDOUT_FILENO, "ERROR: %s\n", progress_msgs[idx]);
}
break;
case nxboot_progress_start:
{
int idx = va_arg(arg, int);
assert(progress_msgs[idx]);
if (strlen(progress_msgs[idx]) == 0)
{
syslog(LOG_ERR, "No progress message for idx: %d\n", idx);
break;
}
dprintf(STDOUT_FILENO, "%s", progress_msgs[idx]);
g_progress_started = true;
}
break;
case nxboot_progress_dot:
{
assert(g_progress_started);
if (!g_progress_started)
{
syslog(LOG_ERR, "Progress dot requested "
"but no previous progress start\n");
}
else
{
dprintf(STDOUT_FILENO, ".");
}
}
break;
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
case nxboot_progress_percent:
{
assert(g_progress_started);
if (!g_progress_started)
{
syslog(LOG_ERR, "Progress percent requested "
"but no previous progress start\n");
}
else
{
int percent = va_arg(arg, int);
if (!g_progress_percent_started)
{
g_progress_percent_started = true;
dprintf(STDOUT_FILENO, ": ");
}
else
{
dprintf(STDOUT_FILENO, "%s", backtab);
}
dprintf(STDOUT_FILENO, "%3d%%", MIN(percent, 100));
}
}
break;
#endif
case nxboot_progress_end:
{
assert(g_progress_started);
if (!g_progress_started)
{
syslog(LOG_ERR, "Progress dot stop requested "
"but no previous progress start\n");
}
else
{
dprintf(STDOUT_FILENO, "\n");
}
g_progress_started = false;
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
g_progress_percent_started = false;
#endif
}
break;
default:
{
assert(false);
syslog(LOG_ERR, "Invalid progress message type: %d\n", type);
dprintf(STDOUT_FILENO, "progress: unknown type!\n");
}
break;
}
va_end(arg);
#endif /* CONFIG_NXBOOT_PRINTF_PROGRESS */
}
/****************************************************************************
* Name: nxboot_main
*
* Description:
* NuttX bootlaoder entry point.
* NuttX bootloader entry point.
*
****************************************************************************/
int main(int argc, FAR char *argv[])
{
int ret;
struct boardioc_boot_info_s info;
bool check_only;
#ifdef CONFIG_NXBOOT_SWRESET_ONLY
int ret;
FAR struct boardioc_reset_cause_s cause;
#endif
@ -66,6 +273,7 @@ int main(int argc, FAR char *argv[])
#endif
syslog(LOG_INFO, "*** nxboot ***\n");
nxboot_progress(nxboot_info, startup_msg);
#ifdef CONFIG_NXBOOT_SWRESET_ONLY
check_only = true;
@ -77,10 +285,12 @@ int main(int argc, FAR char *argv[])
cause.cause == BOARDIOC_RESETCAUSE_PIN)
{
check_only = false;
nxboot_progress(nxboot_info, soft_reset);
}
else
{
syslog(LOG_INFO, "Power reset detected, performing check only.\n");
nxboot_progress(nxboot_info, power_reset);
}
}
#else
@ -90,15 +300,28 @@ int main(int argc, FAR char *argv[])
if (nxboot_perform_update(check_only) < 0)
{
syslog(LOG_ERR, "Could not find bootable image.\n");
return 0;
nxboot_progress(nxboot_error, no_bootable_image);
return OK;
}
syslog(LOG_INFO, "Found bootable image, boot from primary.\n");
nxboot_progress(nxboot_info, found_bootable_image);
#ifdef CONFIG_NXBOOT_COPY_TO_RAM
syslog(LOG_INFO, "Copying image to RAM.\n");
nxboot_ramcopy();
#endif
/* 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);
ret = boardctl(BOARDIOC_BOOT_IMAGE, (uintptr_t)&info);
/* Only get here if the board boot fails */
nxboot_progress(nxboot_error, boardioc_image_boot_fail);
return ret;
}