diff mbox series

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

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

Commit Message

Martin Hundebøll July 13, 2018, 12:34 p.m. UTC
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

Comments

Sean Nyekjær July 16, 2018, 6:05 a.m. UTC | #1
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"
> +);
>
Martin Hundebøll July 30, 2018, 8:45 p.m. UTC | #2
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"
>> +);
>>
Sean Nyekjær Aug. 24, 2018, 11:13 a.m. UTC | #3
>> 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
Sean Nyekjaer Dec. 11, 2019, 8:16 a.m. UTC | #4
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
Wolfgang Denk Feb. 16, 2020, 3:33 p.m. UTC | #5
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 mbox series

Patch

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"
+);