All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Martin Hundebøll" <martin@geanix.com>
To: u-boot@lists.denx.de
Subject: [U-Boot] [RFC] cmd: add bootslot command to select/boot slot based on boot counts
Date: Fri, 13 Jul 2018 14:34:13 +0200	[thread overview]
Message-ID: <20180713123413.4115-1-martin@geanix.com> (raw)

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

             reply	other threads:[~2018-07-13 12:34 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-07-13 12:34 Martin Hundebøll [this message]
2018-07-16  6:05 ` [U-Boot] [RFC] cmd: add bootslot command to select/boot slot based on boot counts Sean Nyekjær
2018-07-30 20:45   ` Martin Hundebøll
2018-08-24 11:13     ` Sean Nyekjær
2019-12-11  8:16 ` Sean Nyekjaer
2020-02-16 15:33 ` Wolfgang Denk

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180713123413.4115-1-martin@geanix.com \
    --to=martin@geanix.com \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.