[U-Boot] [RFC] cmd: add bootslot command to select/boot slot based on boot counts
Martin Hundebøll
martin at geanix.com
Mon Jul 30 20:45:22 UTC 2018
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 at geanix.com>
> Tested-by: Sean Nyekjaer <sean.nyekjaer at 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"
>> +);
>>
More information about the U-Boot
mailing list