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

Martin Hundebøll martin at geanix.com
Fri Jul 13 12:34:13 UTC 2018


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



More information about the U-Boot mailing list