[U-Boot] [RFC] cmd: add bootslot command to select/boot slot based on boot counts

The existing bootcount feature is targeted systems with a primary, and a rescue boot setup, where the number of boot tries to the primary boot is tracked. If the number exceeds the limit, the alternative/rescue is booted.
This patch adds support for a more sophisticated setup, where more than two boot slots can exist, and the order of slots can be configured.
The 'bootcommand' command reads the configured slots (and their priority/order) from a configured environment variable ("bootslots" by default). For each conifgured slot, a remaining boot count is maintained in an evnironment variable ("bootcount_<slot>" by default). If the first boot slot has positive boot count, it is booted using the slot specific boot command ("bootcmd_<slot>" by default). Otherwise the next slot is checked.
An example environment when using the bootslot command with two slots ("a" and "b"):
bootslots=a b bootcount_a=3 bootcount_b=3 bootcmd_a=setenv bootargs $bootargs root=/dev/mmcblk0p1; booti $loadaddr bootcmd_b=setenv bootargs $bootargs root=/dev/mmcblk0p2; booti $loadaddr
Once linux is booted, it resets the bootcount variable for the booted slot using "fw_setenv":
fw_setenv bootcount_a 3
When the non-booted slot is updated, the order is updated by setting the bootslots variable with "fw_setenv":
fw_setenv bootslots=b a
Signed-off-by: Martin Hundebøll martin@geanix.com --- cmd/Kconfig | 42 +++++++++ cmd/Makefile | 1 + cmd/bootslot.c | 225 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 cmd/bootslot.c
diff --git a/cmd/Kconfig b/cmd/Kconfig index aec209006d..3919606e74 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1277,6 +1277,48 @@ config CMD_BOOTCOUNT Enable the bootcount command, which allows interrogation and reset of the bootcounter.
+config CMD_BOOTSLOT + bool "Enable support for multiple boot slots" + help + Parses an ordered list of configured boot slot names (e.g. "a b") + and selects a corresponding boot command based on the current + boot counter for each slot. + +config CMD_BOOTSLOT_ENV_SLOTS + string "Environment variable to read bootslots from" + depends on CMD_BOOTSLOT + default "bootslots" + help + Configures the environment variable to read out when looking for a + list of available boot sloots. + +config CMD_BOOTSLOT_ENV_COUNT + string "Environment variable format string to read/write slot boot count from/to" + depends on CMD_BOOTSLOT + default "bootcount_%s" + help + Configures the prefix to use when reading the boot count for a + specific slot. The prefix is concatenated with the slot name, so + that the boot count for slot "a" is read and saved to "<prefix>a". + +config CMD_BOOTSLOT_ENV_CMD + string "Environment variable format string to read slot boot command from" + depends on CMD_BOOTSLOT + default "bootcmd_%s" + help + Configures the prefix to use when reading the boot command for + specific slot. The prefix is concatenated with the slot name, so + that the boot command for slot "a" is read from "<prefix>a". + +config CMD_BOOTSLOT_DEFAULT_COUNT + int "Default boot count for each configured slot" + depends on CMD_BOOTSLOT + default 3 + help + The default number of times a slot is tried before proceeding to the + slot. The default is used when a slot has no count yet, or when it + is reset with the "bootslot reset" command. + config CMD_BSP bool "Enable board-specific commands" help diff --git a/cmd/Makefile b/cmd/Makefile index 323f1fd2c7..68c8e50c91 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_CMD_BOOTCOUNT) += bootcount.o obj-$(CONFIG_CMD_BOOTEFI) += bootefi.o obj-$(CONFIG_CMD_BOOTMENU) += bootmenu.o obj-$(CONFIG_CMD_BOOTSTAGE) += bootstage.o +obj-$(CONFIG_CMD_BOOTSLOT) += bootslot.o obj-$(CONFIG_CMD_BOOTZ) += bootz.o obj-$(CONFIG_CMD_BOOTI) += booti.o obj-$(CONFIG_CMD_BTRFS) += btrfs.o diff --git a/cmd/bootslot.c b/cmd/bootslot.c new file mode 100644 index 0000000000..03897b1f60 --- /dev/null +++ b/cmd/bootslot.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2018, Geanix, All rights reserved. + */ + +#include <common.h> +#include <environment.h> +#include <stdlib.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/compat.h> +#include <vsprintf.h> + +static char *bootslot_envname(const char *fmt, const char *slot) +{ + int len = strlen(fmt) + strlen(slot); + char *envname = malloc(len + 1); + + sprintf(envname, fmt, slot); + + return envname; +} + +static unsigned long bootslot_get_count(const char *slot) +{ + char *envname; + unsigned long count; + + envname = bootslot_envname(CONFIG_CMD_BOOTSLOT_ENV_COUNT, slot); + count = env_get_ulong(envname, 10, CONFIG_CMD_BOOTSLOT_DEFAULT_COUNT); + free(envname); + + return count; +} + +static void bootslot_set_count(const char *slot, unsigned long count) +{ + char *envname; + + envname = bootslot_envname(CONFIG_CMD_BOOTSLOT_ENV_COUNT, slot); + env_set_ulong(envname, count); + free(envname); +} + +static const char *bootslot_get_cmd(const char *slot) +{ + char *envname; + char *cmd; + + envname = bootslot_envname(CONFIG_CMD_BOOTSLOT_ENV_CMD, slot); + cmd = env_get(envname); + free(envname); + + return cmd; +} + +static char *bootslot_get_slots(int argc, char * const argv[]) +{ + char *slots = NULL; + int len = 1; /* make room for terminating \0 */ + int i; + + /* read boot slots from environment if no args are given, or + * duplicate the argument if a single argument is given */ + if (argc == 1) + return strdup(env_get(CONFIG_CMD_BOOTSLOT_ENV_SLOTS)); + else if (argc == 2) + return strdup(argv[1]); + + /* compute the string length of the list of slots */ + for (i = 1; i < argc; i++) + len += strlen(argv[i]) + 1; /* add room for space separator */ + + /* allocate the string buffer and copy each slot argument to it */ + slots = kzalloc(len, 0); + strcpy(slots, argv[1]); + + for (i = 2; i < argc; i++) { + strcat(slots, " "); + strcat(slots, argv[i]); + } + + return slots; +} + +static int do_bootslot_list(cmd_tbl_t *cmdtp, int flag, int argc, + char * const argv[]) +{ + char *mem = bootslot_get_slots(argc, argv); + char *slots = mem; + char *slot; + + if (slots == NULL) + return CMD_RET_USAGE; + + printf("slot\tcount\tbootcmd\n"); + while ((slot = strsep(&slots, " ")) != NULL) { + unsigned long count; + const char *bootcmd; + + if (strlen(slot) == 0) + continue; + + count = bootslot_get_count(slot); + bootcmd = bootslot_get_cmd(slot); + + if (bootcmd) + printf("%s\t%lu\t%s\n", slot, count, bootcmd); + else + printf("%s\t%lu\t<not defined>\n", slot, count); + } + + free(mem); + + return CMD_RET_SUCCESS; +} + +static int do_bootslot_reset(cmd_tbl_t *cmdtp, int flag, int argc, + char * const argv[]) +{ + char *mem = bootslot_get_slots(argc, argv); + char *slots = mem; + char *slot; + + if (slots == NULL) + return CMD_RET_USAGE; + + while ((slot = strsep(&slots, " ")) != NULL) { + if (strlen(slot) == 0) + continue; + bootslot_set_count(slot, CONFIG_CMD_BOOTSLOT_DEFAULT_COUNT); + } + + free(mem); + + return CMD_RET_SUCCESS; +} + +static int do_bootslot_boot(cmd_tbl_t *cmdtp, int flag, int argc, + char * const argv[]) +{ + char *mem = bootslot_get_slots(argc, argv); + char *slots = mem; + char *slot; + bool found_valid = false; + + if (slots == NULL) + return CMD_RET_USAGE; + + while ((slot = strsep(&slots, " ")) != NULL) { + const char *bootcmd; + unsigned long count; + + if (strlen(slot) == 0) + continue; + + count = bootslot_get_count(slot); + if (count == 0) { + printf("slot %s bootcount is zero; trying next...\n", + slot); + continue; + } + + bootcmd = bootslot_get_cmd(slot); + if (bootcmd == NULL) { + printf("slot %s bootcmd not found; trying next...\n", + slot); + continue; + } + + printf("slot %s has %lu boot tries remaining; booting...\n", + slot, count); + found_valid = true; + bootslot_set_count(slot, --count); + env_save(); + run_command_list(bootcmd, -1, CMD_FLAG_ENV); + break; + } + + free(mem); + + if (found_valid == false) { + printf("no valid bootslot found; resetting counters\n"); + run_command("bootslot reset", 0); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static cmd_tbl_t cmd_bootslot_sub[] = { + U_BOOT_CMD_MKENT(boot, INT_MAX, 0, do_bootslot_boot, "", ""), + U_BOOT_CMD_MKENT(list, INT_MAX, 1, do_bootslot_list, "", ""), + U_BOOT_CMD_MKENT(reset, INT_MAX, 1, do_bootslot_reset, "", ""), +}; + +/* + * Process a bootslots sub-command + */ +static int do_bootslot(cmd_tbl_t *cmdtp, int flag, int argc, + char * const argv[]) +{ + cmd_tbl_t *c; + + /* Strip off leading 'bootslot' command argument */ + argc--; + argv++; + + c = find_cmd_tbl(argv[0], cmd_bootslot_sub, + ARRAY_SIZE(cmd_bootslot_sub)); + + if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +} + + +U_BOOT_CMD(bootslot, INT_MAX, 1, do_bootslot, + "Bootslot command", + " - select and boot slot based on counters\n" + "boot [<slot>] - Boot the passed or first valid slot in $bootslots\n" + "list [<slot>] - List remaining boot tries for passed or all slots in $bootslots\n" + "reset [<slot>] - Reset remaining boot tries for all or passed slot\n" +);

Hi,
On 2018-07-13 14:34, Martin Hundebøll wrote:
The existing bootcount feature is targeted systems with a primary, and a rescue boot setup, where the number of boot tries to the primary boot is tracked. If the number exceeds the limit, the alternative/rescue is booted.
This patch adds support for a more sophisticated setup, where more than two boot slots can exist, and the order of slots can be configured.
The 'bootcommand' command reads the configured slots (and their priority/order) from a configured environment variable ("bootslots" by default). For each conifgured slot, a remaining boot count is maintained in an evnironment variable ("bootcount_<slot>" by default). If the first boot slot has positive boot count, it is booted using the slot specific boot command ("bootcmd_<slot>" by default). Otherwise the next slot is checked.
An example environment when using the bootslot command with two slots ("a" and "b"):
With RAUC bootslot's is specified with uppercase letters, uppercase is not preserved. We end up with BOOT_b_LEFT=2... botocmd_* is with lowercase, just to make things easier.
/Sean
bootslots=a b bootcount_a=3 bootcount_b=3 bootcmd_a=setenv bootargs $bootargs root=/dev/mmcblk0p1; booti $loadaddr bootcmd_b=setenv bootargs $bootargs root=/dev/mmcblk0p2; booti $loadaddr
Once linux is booted, it resets the bootcount variable for the booted slot using "fw_setenv":
fw_setenv bootcount_a 3
When the non-booted slot is updated, the order is updated by setting the bootslots variable with "fw_setenv":
fw_setenv bootslots=b a
Signed-off-by: Martin Hundebøll martin@geanix.com
Tested-by: Sean Nyekjaer sean.nyekjaer@prevas.dk
cmd/Kconfig | 42 +++++++++ cmd/Makefile | 1 + cmd/bootslot.c | 225 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 cmd/bootslot.c
diff --git a/cmd/Kconfig b/cmd/Kconfig index aec209006d..3919606e74 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1277,6 +1277,48 @@ config CMD_BOOTCOUNT Enable the bootcount command, which allows interrogation and reset of the bootcounter.
+config CMD_BOOTSLOT
- bool "Enable support for multiple boot slots"
- help
Parses an ordered list of configured boot slot names (e.g. "a b")
and selects a corresponding boot command based on the current
boot counter for each slot.
+config CMD_BOOTSLOT_ENV_SLOTS
- string "Environment variable to read bootslots from"
- depends on CMD_BOOTSLOT
- default "bootslots"
- help
Configures the environment variable to read out when looking for a
list of available boot sloots.
+config CMD_BOOTSLOT_ENV_COUNT
- string "Environment variable format string to read/write slot boot count from/to"
- depends on CMD_BOOTSLOT
- default "bootcount_%s"
- help
Configures the prefix to use when reading the boot count for a
specific slot. The prefix is concatenated with the slot name, so
that the boot count for slot "a" is read and saved to "<prefix>a".
+config CMD_BOOTSLOT_ENV_CMD
- string "Environment variable format string to read slot boot command from"
- depends on CMD_BOOTSLOT
- default "bootcmd_%s"
- help
Configures the prefix to use when reading the boot command for
specific slot. The prefix is concatenated with the slot name, so
that the boot command for slot "a" is read from "<prefix>a".
+config CMD_BOOTSLOT_DEFAULT_COUNT
- int "Default boot count for each configured slot"
- depends on CMD_BOOTSLOT
- default 3
- help
The default number of times a slot is tried before proceeding to the
slot. The default is used when a slot has no count yet, or when it
is reset with the "bootslot reset" command.
- config CMD_BSP bool "Enable board-specific commands" help
diff --git a/cmd/Makefile b/cmd/Makefile index 323f1fd2c7..68c8e50c91 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_CMD_BOOTCOUNT) += bootcount.o obj-$(CONFIG_CMD_BOOTEFI) += bootefi.o obj-$(CONFIG_CMD_BOOTMENU) += bootmenu.o obj-$(CONFIG_CMD_BOOTSTAGE) += bootstage.o +obj-$(CONFIG_CMD_BOOTSLOT) += bootslot.o obj-$(CONFIG_CMD_BOOTZ) += bootz.o obj-$(CONFIG_CMD_BOOTI) += booti.o obj-$(CONFIG_CMD_BTRFS) += btrfs.o diff --git a/cmd/bootslot.c b/cmd/bootslot.c new file mode 100644 index 0000000000..03897b1f60 --- /dev/null +++ b/cmd/bootslot.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- Copyright (c) 2018, Geanix, All rights reserved.
- */
+#include <common.h> +#include <environment.h> +#include <stdlib.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/compat.h> +#include <vsprintf.h>
+static char *bootslot_envname(const char *fmt, const char *slot) +{
- int len = strlen(fmt) + strlen(slot);
- char *envname = malloc(len + 1);
- sprintf(envname, fmt, slot);
- return envname;
+}
+static unsigned long bootslot_get_count(const char *slot) +{
- char *envname;
- unsigned long count;
- envname = bootslot_envname(CONFIG_CMD_BOOTSLOT_ENV_COUNT, slot);
- count = env_get_ulong(envname, 10, CONFIG_CMD_BOOTSLOT_DEFAULT_COUNT);
- free(envname);
- return count;
+}
+static void bootslot_set_count(const char *slot, unsigned long count) +{
- char *envname;
- envname = bootslot_envname(CONFIG_CMD_BOOTSLOT_ENV_COUNT, slot);
- env_set_ulong(envname, count);
- free(envname);
+}
+static const char *bootslot_get_cmd(const char *slot) +{
- char *envname;
- char *cmd;
- envname = bootslot_envname(CONFIG_CMD_BOOTSLOT_ENV_CMD, slot);
- cmd = env_get(envname);
- free(envname);
- return cmd;
+}
+static char *bootslot_get_slots(int argc, char * const argv[]) +{
- char *slots = NULL;
- int len = 1; /* make room for terminating \0 */
- int i;
- /* read boot slots from environment if no args are given, or
* duplicate the argument if a single argument is given */
- if (argc == 1)
return strdup(env_get(CONFIG_CMD_BOOTSLOT_ENV_SLOTS));
- else if (argc == 2)
return strdup(argv[1]);
- /* compute the string length of the list of slots */
- for (i = 1; i < argc; i++)
len += strlen(argv[i]) + 1; /* add room for space separator */
- /* allocate the string buffer and copy each slot argument to it */
- slots = kzalloc(len, 0);
- strcpy(slots, argv[1]);
- for (i = 2; i < argc; i++) {
strcat(slots, " ");
strcat(slots, argv[i]);
- }
- return slots;
+}
+static int do_bootslot_list(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
+{
- char *mem = bootslot_get_slots(argc, argv);
- char *slots = mem;
- char *slot;
- if (slots == NULL)
return CMD_RET_USAGE;
- printf("slot\tcount\tbootcmd\n");
- while ((slot = strsep(&slots, " ")) != NULL) {
unsigned long count;
const char *bootcmd;
if (strlen(slot) == 0)
continue;
count = bootslot_get_count(slot);
bootcmd = bootslot_get_cmd(slot);
if (bootcmd)
printf("%s\t%lu\t%s\n", slot, count, bootcmd);
else
printf("%s\t%lu\t<not defined>\n", slot, count);
- }
- free(mem);
- return CMD_RET_SUCCESS;
+}
+static int do_bootslot_reset(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
+{
- char *mem = bootslot_get_slots(argc, argv);
- char *slots = mem;
- char *slot;
- if (slots == NULL)
return CMD_RET_USAGE;
- while ((slot = strsep(&slots, " ")) != NULL) {
if (strlen(slot) == 0)
continue;
bootslot_set_count(slot, CONFIG_CMD_BOOTSLOT_DEFAULT_COUNT);
- }
- free(mem);
- return CMD_RET_SUCCESS;
+}
+static int do_bootslot_boot(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
+{
- char *mem = bootslot_get_slots(argc, argv);
- char *slots = mem;
- char *slot;
- bool found_valid = false;
- if (slots == NULL)
return CMD_RET_USAGE;
- while ((slot = strsep(&slots, " ")) != NULL) {
const char *bootcmd;
unsigned long count;
if (strlen(slot) == 0)
continue;
count = bootslot_get_count(slot);
if (count == 0) {
printf("slot %s bootcount is zero; trying next...\n",
slot);
continue;
}
bootcmd = bootslot_get_cmd(slot);
if (bootcmd == NULL) {
printf("slot %s bootcmd not found; trying next...\n",
slot);
continue;
}
printf("slot %s has %lu boot tries remaining; booting...\n",
slot, count);
found_valid = true;
bootslot_set_count(slot, --count);
env_save();
run_command_list(bootcmd, -1, CMD_FLAG_ENV);
break;
- }
- free(mem);
- if (found_valid == false) {
printf("no valid bootslot found; resetting counters\n");
run_command("bootslot reset", 0);
return CMD_RET_FAILURE;
- }
- return CMD_RET_SUCCESS;
+}
+static cmd_tbl_t cmd_bootslot_sub[] = {
- U_BOOT_CMD_MKENT(boot, INT_MAX, 0, do_bootslot_boot, "", ""),
- U_BOOT_CMD_MKENT(list, INT_MAX, 1, do_bootslot_list, "", ""),
- U_BOOT_CMD_MKENT(reset, INT_MAX, 1, do_bootslot_reset, "", ""),
+};
+/*
- Process a bootslots sub-command
- */
+static int do_bootslot(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
+{
- cmd_tbl_t *c;
- /* Strip off leading 'bootslot' command argument */
- argc--;
- argv++;
- c = find_cmd_tbl(argv[0], cmd_bootslot_sub,
ARRAY_SIZE(cmd_bootslot_sub));
- if (c)
return c->cmd(cmdtp, flag, argc, argv);
- else
return CMD_RET_USAGE;
+}
+U_BOOT_CMD(bootslot, INT_MAX, 1, do_bootslot,
- "Bootslot command",
- " - select and boot slot based on counters\n"
- "boot [<slot>] - Boot the passed or first valid slot in $bootslots\n"
- "list [<slot>] - List remaining boot tries for passed or all slots in $bootslots\n"
- "reset [<slot>] - Reset remaining boot tries for all or passed slot\n"
+);

Hi,
On 2018-07-16 08:05, Sean Nyekjær wrote:
Hi,
On 2018-07-13 14:34, Martin Hundebøll wrote:
The existing bootcount feature is targeted systems with a primary, and a rescue boot setup, where the number of boot tries to the primary boot is tracked. If the number exceeds the limit, the alternative/rescue is booted.
This patch adds support for a more sophisticated setup, where more than two boot slots can exist, and the order of slots can be configured.
The 'bootcommand' command reads the configured slots (and their priority/order) from a configured environment variable ("bootslots" by default). For each conifgured slot, a remaining boot count is maintained in an evnironment variable ("bootcount_<slot>" by default). If the first boot slot has positive boot count, it is booted using the slot specific boot command ("bootcmd_<slot>" by default). Otherwise the next slot is checked.
An example environment when using the bootslot command with two slots ("a" and "b"):
With RAUC bootslot's is specified with uppercase letters, uppercase is not preserved. We end up with BOOT_b_LEFT=2... botocmd_* is with lowercase, just to make things easier.
I cannot reproduce the lowercase issue. Can you send me your (def)config and environment?
% grep BOOTSLOT .config CONFIG_CMD_BOOTSLOT=y CONFIG_CMD_BOOTSLOT_ENV_SLOTS="BOOTORDER" CONFIG_CMD_BOOTSLOT_ENV_COUNT="BOOT_%s_LEFT" CONFIG_CMD_BOOTSLOT_ENV_CMD="BOOTCMD_%s" CONFIG_CMD_BOOTSLOT_DEFAULT_COUNT=3
% grep -i boot board/raspberrypi/rpi/env.txt BOOTORDER=A B bootargs_all=console=ttyS0,115200n8 rootfstype=squashfs rootwait bootargs_a=root=/dev/mmcblk0p2 bootslot=A bootargs_b=root=/dev/mmcblk0p3 bootslot=B set_bootargs_a=setenv bootargs $bootargs_all $bootargs_a set_bootargs_b=setenv bootargs $bootargs_all $bootargs_b bootcmd_common=run load_kernel; booti $loadaddr - $fdtcontroladdr BOOTCMD_A=run set_bootargs_a bootcmd_common BOOTCMD_B=run set_bootargs_b bootcmd_common bootcmd=bootslot boot
// Martin
/Sean
bootslots=a b bootcount_a=3 bootcount_b=3 bootcmd_a=setenv bootargs $bootargs root=/dev/mmcblk0p1; booti $loadaddr bootcmd_b=setenv bootargs $bootargs root=/dev/mmcblk0p2; booti $loadaddr
Once linux is booted, it resets the bootcount variable for the booted slot using "fw_setenv":
fw_setenv bootcount_a 3
When the non-booted slot is updated, the order is updated by setting the bootslots variable with "fw_setenv":
fw_setenv bootslots=b a
Signed-off-by: Martin Hundebøll martin@geanix.com
Tested-by: Sean Nyekjaer sean.nyekjaer@prevas.dk
cmd/Kconfig | 42 +++++++++ cmd/Makefile | 1 + cmd/bootslot.c | 225 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 cmd/bootslot.c
diff --git a/cmd/Kconfig b/cmd/Kconfig index aec209006d..3919606e74 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1277,6 +1277,48 @@ config CMD_BOOTCOUNT Enable the bootcount command, which allows interrogation and reset of the bootcounter. +config CMD_BOOTSLOT + bool "Enable support for multiple boot slots" + help + Parses an ordered list of configured boot slot names (e.g. "a b") + and selects a corresponding boot command based on the current + boot counter for each slot.
+config CMD_BOOTSLOT_ENV_SLOTS + string "Environment variable to read bootslots from" + depends on CMD_BOOTSLOT + default "bootslots" + help + Configures the environment variable to read out when looking for a + list of available boot sloots.
+config CMD_BOOTSLOT_ENV_COUNT + string "Environment variable format string to read/write slot boot count from/to" + depends on CMD_BOOTSLOT + default "bootcount_%s" + help + Configures the prefix to use when reading the boot count for a + specific slot. The prefix is concatenated with the slot name, so + that the boot count for slot "a" is read and saved to "<prefix>a".
+config CMD_BOOTSLOT_ENV_CMD + string "Environment variable format string to read slot boot command from" + depends on CMD_BOOTSLOT + default "bootcmd_%s" + help + Configures the prefix to use when reading the boot command for + specific slot. The prefix is concatenated with the slot name, so + that the boot command for slot "a" is read from "<prefix>a".
+config CMD_BOOTSLOT_DEFAULT_COUNT + int "Default boot count for each configured slot" + depends on CMD_BOOTSLOT + default 3 + help + The default number of times a slot is tried before proceeding to the + slot. The default is used when a slot has no count yet, or when it + is reset with the "bootslot reset" command.
config CMD_BSP bool "Enable board-specific commands" help diff --git a/cmd/Makefile b/cmd/Makefile index 323f1fd2c7..68c8e50c91 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_CMD_BOOTCOUNT) += bootcount.o obj-$(CONFIG_CMD_BOOTEFI) += bootefi.o obj-$(CONFIG_CMD_BOOTMENU) += bootmenu.o obj-$(CONFIG_CMD_BOOTSTAGE) += bootstage.o +obj-$(CONFIG_CMD_BOOTSLOT) += bootslot.o obj-$(CONFIG_CMD_BOOTZ) += bootz.o obj-$(CONFIG_CMD_BOOTI) += booti.o obj-$(CONFIG_CMD_BTRFS) += btrfs.o diff --git a/cmd/bootslot.c b/cmd/bootslot.c new file mode 100644 index 0000000000..03897b1f60 --- /dev/null +++ b/cmd/bootslot.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- Copyright (c) 2018, Geanix, All rights reserved.
- */
+#include <common.h> +#include <environment.h> +#include <stdlib.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/compat.h> +#include <vsprintf.h>
+static char *bootslot_envname(const char *fmt, const char *slot) +{ + int len = strlen(fmt) + strlen(slot); + char *envname = malloc(len + 1);
+ sprintf(envname, fmt, slot);
+ return envname; +}
+static unsigned long bootslot_get_count(const char *slot) +{ + char *envname; + unsigned long count;
+ envname = bootslot_envname(CONFIG_CMD_BOOTSLOT_ENV_COUNT, slot); + count = env_get_ulong(envname, 10, CONFIG_CMD_BOOTSLOT_DEFAULT_COUNT); + free(envname);
+ return count; +}
+static void bootslot_set_count(const char *slot, unsigned long count) +{ + char *envname;
+ envname = bootslot_envname(CONFIG_CMD_BOOTSLOT_ENV_COUNT, slot); + env_set_ulong(envname, count); + free(envname); +}
+static const char *bootslot_get_cmd(const char *slot) +{ + char *envname; + char *cmd;
+ envname = bootslot_envname(CONFIG_CMD_BOOTSLOT_ENV_CMD, slot); + cmd = env_get(envname); + free(envname);
+ return cmd; +}
+static char *bootslot_get_slots(int argc, char * const argv[]) +{ + char *slots = NULL; + int len = 1; /* make room for terminating \0 */ + int i;
+ /* read boot slots from environment if no args are given, or + * duplicate the argument if a single argument is given */ + if (argc == 1) + return strdup(env_get(CONFIG_CMD_BOOTSLOT_ENV_SLOTS)); + else if (argc == 2) + return strdup(argv[1]);
+ /* compute the string length of the list of slots */ + for (i = 1; i < argc; i++) + len += strlen(argv[i]) + 1; /* add room for space separator */
+ /* allocate the string buffer and copy each slot argument to it */ + slots = kzalloc(len, 0); + strcpy(slots, argv[1]);
+ for (i = 2; i < argc; i++) { + strcat(slots, " "); + strcat(slots, argv[i]); + }
+ return slots; +}
+static int do_bootslot_list(cmd_tbl_t *cmdtp, int flag, int argc, + char * const argv[]) +{ + char *mem = bootslot_get_slots(argc, argv); + char *slots = mem; + char *slot;
+ if (slots == NULL) + return CMD_RET_USAGE;
+ printf("slot\tcount\tbootcmd\n"); + while ((slot = strsep(&slots, " ")) != NULL) { + unsigned long count; + const char *bootcmd;
+ if (strlen(slot) == 0) + continue;
+ count = bootslot_get_count(slot); + bootcmd = bootslot_get_cmd(slot);
+ if (bootcmd) + printf("%s\t%lu\t%s\n", slot, count, bootcmd); + else + printf("%s\t%lu\t<not defined>\n", slot, count); + }
+ free(mem);
+ return CMD_RET_SUCCESS; +}
+static int do_bootslot_reset(cmd_tbl_t *cmdtp, int flag, int argc, + char * const argv[]) +{ + char *mem = bootslot_get_slots(argc, argv); + char *slots = mem; + char *slot;
+ if (slots == NULL) + return CMD_RET_USAGE;
+ while ((slot = strsep(&slots, " ")) != NULL) { + if (strlen(slot) == 0) + continue; + bootslot_set_count(slot, CONFIG_CMD_BOOTSLOT_DEFAULT_COUNT); + }
+ free(mem);
+ return CMD_RET_SUCCESS; +}
+static int do_bootslot_boot(cmd_tbl_t *cmdtp, int flag, int argc, + char * const argv[]) +{ + char *mem = bootslot_get_slots(argc, argv); + char *slots = mem; + char *slot; + bool found_valid = false;
+ if (slots == NULL) + return CMD_RET_USAGE;
+ while ((slot = strsep(&slots, " ")) != NULL) { + const char *bootcmd; + unsigned long count;
+ if (strlen(slot) == 0) + continue;
+ count = bootslot_get_count(slot); + if (count == 0) { + printf("slot %s bootcount is zero; trying next...\n", + slot); + continue; + }
+ bootcmd = bootslot_get_cmd(slot); + if (bootcmd == NULL) { + printf("slot %s bootcmd not found; trying next...\n", + slot); + continue; + }
+ printf("slot %s has %lu boot tries remaining; booting...\n", + slot, count); + found_valid = true; + bootslot_set_count(slot, --count); + env_save(); + run_command_list(bootcmd, -1, CMD_FLAG_ENV); + break; + }
+ free(mem);
+ if (found_valid == false) { + printf("no valid bootslot found; resetting counters\n"); + run_command("bootslot reset", 0); + return CMD_RET_FAILURE; + }
+ return CMD_RET_SUCCESS; +}
+static cmd_tbl_t cmd_bootslot_sub[] = { + U_BOOT_CMD_MKENT(boot, INT_MAX, 0, do_bootslot_boot, "", ""), + U_BOOT_CMD_MKENT(list, INT_MAX, 1, do_bootslot_list, "", ""), + U_BOOT_CMD_MKENT(reset, INT_MAX, 1, do_bootslot_reset, "", ""), +};
+/*
- Process a bootslots sub-command
- */
+static int do_bootslot(cmd_tbl_t *cmdtp, int flag, int argc, + char * const argv[]) +{ + cmd_tbl_t *c;
+ /* Strip off leading 'bootslot' command argument */ + argc--; + argv++;
+ c = find_cmd_tbl(argv[0], cmd_bootslot_sub, + ARRAY_SIZE(cmd_bootslot_sub));
+ if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +}
+U_BOOT_CMD(bootslot, INT_MAX, 1, do_bootslot, + "Bootslot command", + " - select and boot slot based on counters\n" + "boot [<slot>] - Boot the passed or first valid slot in $bootslots\n" + "list [<slot>] - List remaining boot tries for passed or all slots in $bootslots\n" + "reset [<slot>] - Reset remaining boot tries for all or passed slot\n" +);

With RAUC bootslot's is specified with uppercase letters, uppercase is not preserved. We end up with BOOT_b_LEFT=2... botocmd_* is with lowercase, just to make things easier.
I cannot reproduce the lowercase issue. Can you send me your (def)config and environment?
% grep BOOTSLOT .config CONFIG_CMD_BOOTSLOT=y CONFIG_CMD_BOOTSLOT_ENV_SLOTS="BOOTORDER" CONFIG_CMD_BOOTSLOT_ENV_COUNT="BOOT_%s_LEFT" CONFIG_CMD_BOOTSLOT_ENV_CMD="BOOTCMD_%s" CONFIG_CMD_BOOTSLOT_DEFAULT_COUNT=3
% grep -i boot board/raspberrypi/rpi/env.txt BOOTORDER=A B bootargs_all=console=ttyS0,115200n8 rootfstype=squashfs rootwait bootargs_a=root=/dev/mmcblk0p2 bootslot=A bootargs_b=root=/dev/mmcblk0p3 bootslot=B set_bootargs_a=setenv bootargs $bootargs_all $bootargs_a set_bootargs_b=setenv bootargs $bootargs_all $bootargs_b bootcmd_common=run load_kernel; booti $loadaddr - $fdtcontroladdr BOOTCMD_A=run set_bootargs_a bootcmd_common BOOTCMD_B=run set_bootargs_b bootcmd_common bootcmd=bootslot boot
Hi,
I'm not able to reproduce the lowercase issue, I must have got the config wrong :-)
Please submit a [PATCH] for this as it could be very useful.
/Sean

On 13/07/2018 14.34, Martin Hundebøll wrote:
The existing bootcount feature is targeted systems with a primary, and a rescue boot setup, where the number of boot tries to the primary boot is tracked. If the number exceeds the limit, the alternative/rescue is booted.
This patch adds support for a more sophisticated setup, where more than two boot slots can exist, and the order of slots can be configured.
The 'bootcommand' command reads the configured slots (and their priority/order) from a configured environment variable ("bootslots" by default). For each conifgured slot, a remaining boot count is maintained in an evnironment variable ("bootcount_<slot>" by default). If the first boot slot has positive boot count, it is booted using the slot specific boot command ("bootcmd_<slot>" by default). Otherwise the next slot is checked.
An example environment when using the bootslot command with two slots ("a" and "b"):
bootslots=a b bootcount_a=3 bootcount_b=3 bootcmd_a=setenv bootargs $bootargs root=/dev/mmcblk0p1; booti $loadaddr bootcmd_b=setenv bootargs $bootargs root=/dev/mmcblk0p2; booti $loadaddr
Once linux is booted, it resets the bootcount variable for the booted slot using "fw_setenv":
fw_setenv bootcount_a 3
When the non-booted slot is updated, the order is updated by setting the bootslots variable with "fw_setenv":
fw_setenv bootslots=b a
Signed-off-by: Martin Hundebøll martin@geanix.com Tested-by: Sean Nyekjaer sean.nyekjaer@prevas.dk
We have used this for the past 1.5 years. Will you post this as a patch?
/Sean

Dear Martin,
In message 20180713123413.4115-1-martin@geanix.com you wrote:
The existing bootcount feature is targeted systems with a primary, and a rescue boot setup, where the number of boot tries to the primary boot is tracked. If the number exceeds the limit, the alternative/rescue is booted.
This is not a correct summary of the current implementation. If the boot count exceeds the limit, some _alternative_boot_command_ is executed.
The trivial case is to boot another image / partion or such.
Butt nothing prevents you to run any more sophisticated scripts here.
This patch adds support for a more sophisticated setup, where more than two boot slots can exist, and the order of slots can be configured.
This can easily implemented with a few lines of shell code as well.
3 files changed, 268 insertions(+)
I think it makes no sense to add a lot of code that can be implemented as a few lines of shell scripts as well, especially as this code is neither time critical nor has the potential of becoming widely used.
NAKed-by: Wolfgang Denk wd@denx.de
Best regards,
Wolfgang Denk
participants (4)
-
Martin Hundebøll
-
Sean Nyekjaer
-
Sean Nyekjær
-
Wolfgang Denk