Message ID | 20180713123413.4115-1-martin@geanix.com |
---|---|
State | RFC |
Delegated to: | Tom Rini |
Headers | show |
Series | [U-Boot,RFC] cmd: add bootslot command to select/boot slot based on boot counts | expand |
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
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" +);