[PATCH] bootstate: add framework for redundant boot scenarios

Marc Kleine-Budde mkl at pengutronix.de
Wed May 27 08:22:58 PDT 2015


There are several use cases where a redundant Linux system is needed. The
barebox,bootstate framework provides the building blocks to model different
use cases without the need to start from the scratch over and over again.

For more information see the included
Documentation/devicetree/bindings/barebox/barebox,bootstate.rst file.

Signed-off-by: Marc Kleine-Budde <mkl at pengutronix.de>
---
 .../bindings/barebox/barebox,bootstate.rst         | 237 +++++++
 arch/sandbox/dts/sandbox.dts                       |  83 +++
 commands/Kconfig                                   |   5 +
 commands/Makefile                                  |   1 +
 commands/bootchooser.c                             | 101 +++
 common/Kconfig                                     |   7 +
 common/Makefile                                    |   1 +
 common/bootstate.c                                 | 776 +++++++++++++++++++++
 drivers/misc/Kconfig                               |   3 +
 drivers/misc/Makefile                              |   1 +
 drivers/misc/bootstate.c                           |  68 ++
 include/bootstate.h                                |  38 +
 12 files changed, 1321 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/barebox/barebox,bootstate.rst
 create mode 100644 commands/bootchooser.c
 create mode 100644 common/bootstate.c
 create mode 100644 drivers/misc/bootstate.c
 create mode 100644 include/bootstate.h

diff --git a/Documentation/devicetree/bindings/barebox/barebox,bootstate.rst b/Documentation/devicetree/bindings/barebox/barebox,bootstate.rst
new file mode 100644
index 000000000000..dd44e31ced80
--- /dev/null
+++ b/Documentation/devicetree/bindings/barebox/barebox,bootstate.rst
@@ -0,0 +1,237 @@
+barebox bootstate
+=================
+
+Overview
+--------
+
+There are several use cases where a redundant Linux system is needed.
+The ``barebox,bootstate`` framework provides the building blocks to
+model different use cases without the need to start from the scratch
+over and over again.
+
+The ``barebox,bootstate`` works on abstract boot targets, each with a
+set of properties and implements an algorithm which selects the
+highest priority target to boot.
+
+A set of boot targets can be described in a devicetree node. This
+node could be part of the regular devicetree blob or it could be an
+extra devicetree for the bootstate.
+
+A bootstate node contains a description of a set of boot targets along
+with a place where to store the mutable state. Currently implemented
+backends are :ref:`barebox,state` and ``nv`` (:ref:`command_nv`)
+variables.
+
+Required properties:
+
+* ``compatible``: should be ``barebox,bootstate``;
+* ``backend-type``: should be ``state`` or ``nv``.
+
+Optional properties:
+
+* ``backend``: phandle to the :ref:`barebox,state` backend
+
+
+boot target nodes - immutable description
+-----------------------------------------
+
+These are subnodes of a bootstate node, each describing a boot
+target. The node name may end with ``@<ADDRESS>``, but the suffix is
+sripped from the target name.
+
+Optional properties:
+
+* ``default_attempts``: If the boot attempts counter is reset, this
+  value is used.
+
+Example::
+
+  bootstate: bootstate {
+  	compatible = "barebox,bootstate";
+  	backend-type = "state";
+  	backend = <&state>;
+
+  	system0 {
+  		default_attempts = <3>;
+  	};
+
+  	system1 {
+  		default_attempts = <3>;
+  	};
+  };
+
+In this example a bootstate, using a :ref:`barebox,state` backend with
+two boot target ``system0`` and ``system1`` is defined. When the boot
+attempts counter is reset, the default value of ``3`` is used for both
+targets.
+
+
+boot target nodes - mutable state
+---------------------------------
+
+The above example uses a :ref:`barebox,state` backend, which requires
+some additional configuration to hold the mutable
+state. :ref:`barebox,state` has to be explicidly configured, while
+``nv`` (:ref:`command_nv`) variables are created on the fly.
+
+The state of each boot target consists of the following ``uint32``
+varibles:
+
+* ``remaining_attempts``: holds the number of remaining boot
+  attempts. This variable is changed the the bootstate algorithm
+  during boot.
+* ``priority``: defines the priority of the boot target. Higher number
+  indicate a higher priority, If two boot target have the same
+  priority the one defined first in the device tree has precedence.
+  The ``priority`` can optionally be changed by the algorithm to 0, if
+  the boot target is decremented to ``0`` remaining boot attempts. A
+  ``priority`` of ``0`` means the boot target is **deactivated** and
+  will not be considered a valid target during further boots. If the
+  remaining attempts counter is reset, a target with priority 0 is
+  **not** changed.
+* ``ok``: this is an opaque value, it's not accessed by the bootstate
+  algorithm. It can be used be the Linux system to track the first
+  boot after an update.
+
+The bootstate can also hold a default watchdog timeout (in seconds),
+which can be activated by the bootstate algorithm.
+
+Example::
+
+  state: state {
+  	magic = <0x4d433230>;
+  	compatible = "barebox,state";
+  	backend-type = "raw";
+  	backend = <&backend_state>;
+  	#address-cells = <1>;
+  	#size-cells = <1>;
+
+  	bootstate {
+  		#address-cells = <1>;
+  		#size-cells = <1>;
+
+  		system0 {
+  			#address-cells = <1>;
+  			#size-cells = <1>;
+
+  			remaining_attempts {
+  				reg = <0x0 0x4>;
+  				type = "uint32";
+  			};
+  			priority {
+  				reg = <0x4 0x4>;
+  				type = "uint32";
+  			};
+  			ok {
+  				reg = <0x8 0x4>;
+  				type = "uint32";
+  			};
+  		};
+
+  		system1 {
+  			#address-cells = <1>;
+  			#size-cells = <1>;
+
+  			remaining_attempts {
+  				reg = <0x10 0x4>;
+  				type = "uint32";
+  			};
+  			priority {
+  				reg = <0x14 0x4>;
+  				type = "uint32";
+  			};
+  			ok {
+  				reg = <0x18 0x4>;
+  				type = "uint32";
+  			};
+  		};
+
+  		watchdog_timeout {
+  			reg = <0x20 0x4>;
+  			type = "uint32";
+  			default = <60>;
+  		};
+  	};
+  };
+
+This example defines two boot target (``system0`` and ``system1``) and
+a watchdog timeout of ``60`` seconds.
+
+
+Backends
+--------
+
+Currently two backends exist. The :ref:`barebox,state` backend is a
+bit more complicated to setup, as all boot target have to be described
+in the referenced :ref:`barebox,state` in the device tree. On the
+upside, the advantages of the (possible redundant storage, etc...) of
+the :ref:`barebox,state` is gained for free.
+
+The :ref:`command_nv` backend is a lot simpler, no special setup is
+needed, it should run on every board, which already implements a
+read/writeable barebox environment.
+
+
+Algorithm
+---------
+
+The low level algorithm is implemented by the
+``bootstate_get_target()`` function. Its job is to iterate over all
+boot sources and return the name (as a string) of the choosen boot
+target.
+
+The algorithm iterates over all boot target defined under the
+associated device tree node and picks the one with the highest
+``priority`` (higher number have a higher priority) where the
+``remaining_attempts`` is greater than zero. A pointer to the name of
+the boot target is returned, the string should be freed via ``free()``.
+
+The behaviour can be modified with the flags paramter. The following
+flags are currently supported:
+
+* ``BOOTCHOOSER_FLAG_ATTEMPTS_KEEP``: the ``remaining_attempts``
+  counter of the choosen boot target is not changed.
+* ``BOOTCHOOSER_FLAG_ATTEMPTS_DEC``: the ``remaining_attempts``
+  counter of the choosen boot target is decremented by one.
+* ``BOOTCHOOSER_FLAG_ATTEMPTS_RESET``: the ``remaining_attempts``
+  counter of all *active* boot targets (those with ``priority > 0``)
+  are reset to their default values as defined in the immutable
+  description by ``default_attempts``.
+* ``BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS``: if used together
+  with ``BOOTCHOOSER_FLAG_ATTEMPTS_DEC`` and the
+  ``remaining_attempts`` counter of the choosen boot target is
+  decremented to ``0``, the boot target is deactivated for further
+  boot attempts (although *this* boot is attemped as usual). This is
+  done by setting the ``priority`` to ``0``.
+* ``BOOTCHOOSER_FLAG_VERBOSE``: increases the verbosity of the output
+
+
+Frontend
+--------
+
+The shell command ``bootchooser`` (:ref:`command_bootchooser`) can be
+used to choose and start a boot target by a shell one-liner. The
+command picks the boot target with the highes priority and calls the
+``boot`` (:ref:`command_boot`) command with the selected boot target
+as its first and only parameter.
+
+The ``bootchooser`` command implements command line paramter versions
+of the above described flags:
+
+* ``-k``: keep boot attempts
+* ``-d``: decrement boot attempts
+* ``-r``: reset boot attempts
+* ``-z``: deactivate on zero remaining attempts
+* ``-v``: verbose output
+
+Next to the standard parameters, these additional options are
+implemented:
+
+* ``-D``: dryrun - do not boot (all other functionality is active) - a
+  specified watchdog timeout will be activated.
+* ``-R``: retry - if booting fails, the chose next target, but
+  decrement its attemts. Note: if the current target has still the
+  highes priority and remaining attemts, it will be selected again.
+* ``-w <TIMEOUT_IN_SEC>``: activate watchdog - if no parameter is
+  given, the timeout from the device tree is used. A given parameter
+  overwrites the device tree default.
diff --git a/arch/sandbox/dts/sandbox.dts b/arch/sandbox/dts/sandbox.dts
index 2595aa13fa62..e2bc8f76c2e3 100644
--- a/arch/sandbox/dts/sandbox.dts
+++ b/arch/sandbox/dts/sandbox.dts
@@ -3,5 +3,88 @@
 #include "skeleton.dtsi"
 
 / {
+	aliases {
+		state = &state;
+	};
 
+	state: state {
+		magic = <0x4d433230>;
+		compatible = "barebox,state";
+		backend-type = "dtb";
+		backend = "/fd0";
+
+		bootstate {
+			system0 {
+				#address-cells = <1>;
+				#size-cells = <1>;
+
+				remaining_attempts {
+					reg = <0x0 0x4>;
+					type = "uint32";
+				};
+				priority {
+					reg = <0x4 0x4>;
+					type = "uint32";
+				};
+				ok {
+					reg = <0x8 0x4>;
+					type = "uint32";
+				};
+			};
+
+			system1 {
+				#address-cells = <1>;
+				#size-cells = <1>;
+
+				remaining_attempts {
+					reg = <0x10 0x4>;
+					type = "uint32";
+				};
+				priority {
+					reg = <0x14 0x4>;
+					type = "uint32";
+				};
+				ok {
+					reg = <0x18 0x4>;
+					type = "uint32";
+				};
+			};
+
+			factory {
+				#address-cells = <1>;
+				#size-cells = <1>;
+
+				remaining_attempts {
+					reg = <0x20 0x4>;
+					type = "uint32";
+				};
+				priority {
+					reg = <0x24 0x4>;
+					type = "uint32";
+				};
+				ok {
+					reg = <0x28 0x4>;
+					type = "uint32";
+				};
+			};
+		};
+	};
+
+	bootstate: bootstate {
+		compatible = "barebox,bootstate";
+		backend-type = "state"; // or "nv", or "efivar"
+		backend = <&state>;
+
+		system0 {
+			default_attempts = <3>;
+		};
+
+		system1 {
+			default_attempts = <3>;
+		};
+
+		factory {
+			default_attempts = <3>;
+		};
+	};
 };
diff --git a/commands/Kconfig b/commands/Kconfig
index 25c77a85c5dc..85dd88f7615c 100644
--- a/commands/Kconfig
+++ b/commands/Kconfig
@@ -2101,6 +2101,11 @@ config CMD_STATE
 	depends on STATE
 	prompt "state"
 
+config CMD_BOOTCHOOSER
+	tristate
+	depends on BOOTSTATE
+	prompt "bootchooser"
+
 # end Miscellaneous commands
 endmenu
 
diff --git a/commands/Makefile b/commands/Makefile
index b902f58ec5cc..f51cfb6ed492 100644
--- a/commands/Makefile
+++ b/commands/Makefile
@@ -111,3 +111,4 @@ obj-$(CONFIG_CMD_CMP)		+= cmp.o
 obj-$(CONFIG_CMD_NV)		+= nv.o
 obj-$(CONFIG_CMD_DEFAULTENV)	+= defaultenv.o
 obj-$(CONFIG_CMD_STATE)		+= state.o
+obj-$(CONFIG_CMD_BOOTCHOOSER)	+= bootchooser.o
diff --git a/commands/bootchooser.c b/commands/bootchooser.c
new file mode 100644
index 000000000000..34006175818f
--- /dev/null
+++ b/commands/bootchooser.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012 Jan Luebbe <j.luebbe at pengutronix.de>
+ * Copyright (C) 2015 Marc Kleine-Budde <mkl at pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <bootstate.h>
+#include <command.h>
+#include <common.h>
+#include <getopt.h>
+#include <malloc.h>
+#include <stdio.h>
+
+static int do_bootchooser(int argc, char *argv[])
+{
+	unsigned flags = 0, timeout = 0;
+	char *name = NULL;
+	int opt, ret;
+
+	while ((opt = getopt(argc, argv, "kdrzvDRw::")) > 0) {
+		switch (opt) {
+		case 'k':
+			flags |= BOOTCHOOSER_FLAG_ATTEMPTS_KEEP;
+			break;
+		case 'd':
+			flags |= BOOTCHOOSER_FLAG_ATTEMPTS_DEC;
+			break;
+		case 'r':
+			flags |= BOOTCHOOSER_FLAG_ATTEMPTS_RESET;
+			break;
+		case 'z':
+			flags |= BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS;
+			break;
+		case 'v':
+			flags |= BOOTCHOOSER_FLAG_VERBOSE;
+			break;
+		case 'D':
+			flags |= BOOTCHOOSER_FLAG_DRYRUN;
+			break;
+		case 'R':
+			flags |= BOOTCHOOSER_FLAG_RETRY_WITH_DEC;
+			break;
+		case 'w':
+			if (optarg)
+				timeout = simple_strtoul(optarg, NULL, 0);
+			else
+				flags |= BOOTCHOOSER_FLAG_WATCHDOG_TIMEOUT_FROM_STATE;
+			flags |= BOOTCHOOSER_FLAG_WATCHDOG_ENABLE;
+			break;
+		default:
+			return COMMAND_ERROR_USAGE;
+		}
+	}
+
+	if (optind < argc)
+		name = argv[optind];
+
+	if (!(flags & (BOOTCHOOSER_FLAG_ATTEMPTS_KEEP |
+		       BOOTCHOOSER_FLAG_ATTEMPTS_DEC |
+		       BOOTCHOOSER_FLAG_ATTEMPTS_RESET))) {
+		bootstate_info();
+		return 0;
+	}
+
+	if ((flags & BOOTCHOOSER_FLAG_ATTEMPTS_KEEP) &&
+	    (flags & (BOOTCHOOSER_FLAG_ATTEMPTS_DEC | BOOTCHOOSER_FLAG_ATTEMPTS_RESET)))
+		return COMMAND_ERROR_USAGE;
+
+	ret = bootstate_bootchooser(name, flags, timeout);
+
+	return ret ? COMMAND_ERROR : COMMAND_SUCCESS;
+}
+
+BAREBOX_CMD_HELP_START(bootchooser)
+BAREBOX_CMD_HELP_TEXT("Options:")
+BAREBOX_CMD_HELP_OPT ("-k","keep - boot, don't modify attempts counter")
+BAREBOX_CMD_HELP_OPT ("-d","decrement - boot, but decrement attempts counter by one")
+BAREBOX_CMD_HELP_OPT ("-r","reset - boot, but reset _all_ attempts counter to default")
+BAREBOX_CMD_HELP_OPT ("-z","deactivate choosen target in on zero remaining boot attemts")
+BAREBOX_CMD_HELP_OPT ("-v","verbose output")
+BAREBOX_CMD_HELP_OPT ("-D","dryrun. Do not boot - but handle watchdog and reset.")
+BAREBOX_CMD_HELP_OPT ("-R","retry - boot, retry next boot target and decrement attempts")
+BAREBOX_CMD_HELP_OPT ("-w","activate watchdog, use timeout specified in <BOOTSTATE>.watchdog_timeout")
+BAREBOX_CMD_HELP_END
+
+BAREBOX_CMD_START(bootchooser)
+	.cmd = do_bootchooser,
+	BAREBOX_CMD_DESC("automatically select a boot target and boot")
+	BAREBOX_CMD_OPTS("[-kdrzvDR] -w <TIMEOUT> [BOOTSTATE]")
+	BAREBOX_CMD_GROUP(CMD_GRP_MISC)
+	BAREBOX_CMD_HELP(cmd_bootchooser_help)
+BAREBOX_CMD_END
diff --git a/common/Kconfig b/common/Kconfig
index 3dfb5ac19494..791fd89d4c1e 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -716,6 +716,13 @@ config STATE
 	select OFTREE
 	select PARAMETER
 
+config BOOTSTATE
+	bool "bootstate infrastructure"
+	depends on OF_BAREBOX_DRIVERS
+	select ENVIRONMENT_VARIABLES
+	select OFTREE
+	select PARAMETER
+
 config RESET_SOURCE
 	bool "detect Reset cause"
 	depends on GLOBALVAR
diff --git a/common/Makefile b/common/Makefile
index 2738238c67a8..ed336e257e33 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_RESET_SOURCE)	+= reset_source.o
 obj-$(CONFIG_SHELL_HUSH)	+= hush.o
 obj-$(CONFIG_SHELL_SIMPLE)	+= parser.o
 obj-$(CONFIG_STATE)		+= state.o
+obj-$(CONFIG_BOOTSTATE)		+= bootstate.o
 obj-$(CONFIG_UIMAGE)		+= image.o uimage.o
 obj-$(CONFIG_MENUTREE)		+= menutree.o
 obj-$(CONFIG_EFI_GUID)		+= efi-guid.o
diff --git a/common/bootstate.c b/common/bootstate.c
new file mode 100644
index 000000000000..5baaf03b7bfe
--- /dev/null
+++ b/common/bootstate.c
@@ -0,0 +1,776 @@
+/*
+ * Copyright (C) 2012 Jan Luebbe <j.luebbe at pengutronix.de>
+ * Copyright (C) 2015 Marc Kleine-Budde <mkl at pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <bootstate.h>
+#include <common.h>
+#include <envfs.h>
+#include <environment.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fs.h>
+#include <globalvar.h>
+#include <init.h>
+#include <ioctl.h>
+#include <libbb.h>
+#include <libfile.h>
+#include <malloc.h>
+#include <net.h>
+#include <printk.h>
+#include <state.h>
+#include <stdio.h>
+#include <watchdog.h>
+#include <xfuncs.h>
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mtd/mtd-abi.h>
+#include <linux/mtd/mtd.h>
+
+#include <asm/unaligned.h>
+
+/* list of all registered bootstate instances */
+static LIST_HEAD(bootstate_list);
+
+struct state_backend;
+
+struct bootstate {
+	struct device_d dev;
+	const char *name;
+	struct list_head list;
+	struct list_head targets;
+	struct list_head targets_unsorted;
+	struct bootstate_backend *backend;
+	bool dirty;
+};
+
+struct bootstate_backend {
+	int (*load)(struct bootstate_backend *backend, struct bootstate *bootstate);
+	int (*save)(struct bootstate_backend *backend, struct bootstate *bootstate);
+	const char *name;
+	const char *path;
+};
+
+struct bootstate_target {
+	struct list_head list;
+	struct list_head list_unsorted;
+
+	/* state */
+	unsigned int priority;
+	unsigned int remaining_attempts;
+	bool ok;
+
+	/* spec */
+	const char *name;
+	unsigned int default_attempts;
+};
+
+static void pr_target(struct bootstate *bootstate, struct bootstate_target *target)
+{
+	printf("%s: target: name=%s prio=%u, ok=%d, rem=%u, def=%u\n",
+	       bootstate->name, target->name, target->priority, target->ok,
+	       target->remaining_attempts, target->default_attempts);
+}
+
+static struct bootstate *bootstate_new(const char *name)
+{
+	struct bootstate *bootstate;
+	int ret;
+
+	bootstate = xzalloc(sizeof(*bootstate));
+	safe_strncpy(bootstate->dev.name, name, MAX_DRIVER_NAME);
+	bootstate->name = bootstate->dev.name;
+	bootstate->dev.id = DEVICE_ID_DYNAMIC;
+	INIT_LIST_HEAD(&bootstate->targets);
+	INIT_LIST_HEAD(&bootstate->targets_unsorted);
+
+	ret = register_device(&bootstate->dev);
+	if (ret) {
+		free(bootstate);
+		return ERR_PTR(ret);
+	}
+
+	list_add_tail(&bootstate->list, &bootstate_list);
+
+	return bootstate;
+}
+
+static void bootstate_release(struct bootstate *bootstate)
+{
+	unregister_device(&bootstate->dev);
+	free(bootstate);
+}
+
+static int bootstate_target_compare(struct list_head *a, struct list_head *b)
+{
+	struct bootstate_target *bootstate_a = list_entry(a, struct bootstate_target, list);
+	struct bootstate_target *bootstate_b = list_entry(b, struct bootstate_target, list);
+
+	/* order descending */
+	return bootstate_a->priority >= bootstate_b->priority ? -1 : 1;
+}
+
+static void bootstate_target_add(struct bootstate *bootstate, struct bootstate_target *target)
+{
+	list_del(&target->list);
+	list_add_sort(&target->list, &bootstate->targets, bootstate_target_compare);
+}
+
+static int bootstate_variable_read_u32(const struct bootstate *bootstate,
+				       const char *name, uint32_t *out_val)
+{
+	char *var;
+	int ret;
+
+	var = asprintf("%s.%s.%s", bootstate->backend->path, bootstate->name, name);
+	ret = getenv_uint(var, out_val);
+	free(var);
+
+	return ret;
+}
+
+static int bootstate_backend_variable_read_target_u32(const struct bootstate_backend *backend,
+						      const struct bootstate *bootstate,
+						      const struct bootstate_target *target,
+						      const char *name, uint32_t *out_val)
+{
+	char *var;
+	int ret;
+
+	var = asprintf("%s.%s.%s.%s", backend->path, bootstate->name,
+		       target->name, name);
+	ret = getenv_uint(var, out_val);
+	free(var);
+
+	return ret;
+}
+
+static int bootstate_backend_variable_write_target_u32(const struct bootstate_backend *backend,
+						       const struct bootstate *bootstate,
+						       const struct bootstate_target *target,
+						       const char *name, uint32_t in_val)
+{
+	char *var;
+	char *val;
+	int ret;
+
+	var = asprintf("%s.%s.%s.%s", backend->path, bootstate->name,
+		       target->name, name);
+	val = asprintf("%d", in_val);
+	ret = setenv(var, val);
+	free(val);
+	free(var);
+
+	return ret;
+}
+
+static int bootstate_variable_nv_init_u32(const struct bootstate_backend *backend,
+					  const struct bootstate *bootstate,
+					  const struct bootstate_target *target,
+					  const char *name)
+{
+	char *var;
+	int ret;
+
+	var = asprintf("%s.%s.%s", bootstate->name, target->name, name);
+	ret = nvvar_add(var, "0");
+	free(var);
+
+	return ret;
+}
+
+static struct bootstate_target *bootstate_target_find(const struct bootstate *bootstate,
+						      const char *name)
+{
+	struct bootstate_target *target;
+
+	list_for_each_entry(target, &bootstate->targets, list) {
+		if (!strcmp(target->name, name))
+			return target;
+	}
+
+	return ERR_PTR(-ENOENT);
+}
+
+static int bootstate_target_from_node(struct bootstate *bootstate, const struct device_node *node, bool create)
+{
+	struct bootstate_target *target;
+	char *name, *indexs;
+	int ret;
+
+	name = xstrdup(node->name);
+	indexs = strchr(name, '@');
+	if (indexs)
+		*indexs++ = 0;
+
+	if (create) {
+		/* create*/
+		target = xzalloc(sizeof(*target));
+
+		target->name = xstrdup(name);
+		list_add_tail(&target->list, &bootstate->targets);
+		list_add_tail(&target->list_unsorted,
+			      &bootstate->targets_unsorted);
+	} else {
+		target = bootstate_target_find(bootstate, name);
+		if (IS_ERR(target)) {
+			int ret = PTR_ERR(target);
+			pr_err("no such boot target: %s: %s\n",
+			       name, strerror(-ret));
+			return ret;
+		}
+	}
+
+	/* init */
+	ret = of_property_read_u32(node, "default_attempts",
+				   &target->default_attempts);
+	if (ret)
+		return ret;
+
+	free(name);
+
+	return 0;
+}
+
+static int bootstate_from_node(struct bootstate *bootstate,
+			       const struct device_node *node, bool create)
+{
+	struct device_node *child;
+	int ret;
+
+	for_each_child_of_node(node, child) {
+		ret = bootstate_target_from_node(bootstate, child, create);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int bootstate_backend_load_one(const struct bootstate_backend *backend,
+				      const struct bootstate *bootstate,
+				      struct bootstate_target *target)
+{
+	uint32_t tmp;
+	int ret;
+
+	ret = bootstate_backend_variable_read_target_u32(backend, bootstate, target,
+							 "remaining_attempts",
+							 &target->remaining_attempts);
+	if (ret)
+		return ret;
+
+	ret = bootstate_backend_variable_read_target_u32(backend, bootstate, target,
+							 "priority", &target->priority);
+	if (ret)
+		return ret;
+
+	ret = bootstate_backend_variable_read_target_u32(backend, bootstate, target,
+							 "ok", &tmp);
+	if (ret)
+		return ret;
+
+	target->ok = !!tmp;
+
+	return ret;
+}
+
+static int bootstate_backend_load(struct bootstate_backend *backend,
+				  struct bootstate *bootstate)
+{
+	struct bootstate_target *target;
+	int ret;
+
+	list_for_each_entry(target, &bootstate->targets_unsorted, list_unsorted) {
+		ret = bootstate_backend_load_one(backend, bootstate, target);
+		if (ret)
+			return ret;
+		bootstate_target_add(bootstate, target);
+	}
+
+	return 0;
+}
+
+static int bootstate_backend_save_one(const struct bootstate_backend *backend,
+				      const struct bootstate *bootstate,
+				      struct bootstate_target *target)
+{
+	int ret;
+
+	ret = bootstate_backend_variable_write_target_u32(backend, bootstate, target,
+							  "remaining_attempts",
+							  target->remaining_attempts);
+	if (ret)
+		return ret;
+
+	ret = bootstate_backend_variable_write_target_u32(backend, bootstate, target,
+							  "priority", target->priority);
+	if (ret)
+		return ret;
+
+	ret = bootstate_backend_variable_write_target_u32(backend, bootstate, target,
+							  "ok", target->ok);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int bootstate_backend_save(const struct bootstate_backend *backend,
+				  const struct bootstate *bootstate)
+{
+	struct bootstate_target *target;
+	int ret;
+
+	list_for_each_entry(target, &bootstate->targets, list) {
+		ret = bootstate_backend_save_one(backend, bootstate, target);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int bootstate_backend_nv_init_one(const struct bootstate_backend *backend,
+					 const struct bootstate *bootstate,
+					 struct bootstate_target *target)
+{
+	int ret;
+
+	ret = bootstate_variable_nv_init_u32(backend, bootstate, target,
+					     "remaining_attempts");
+	if (ret)
+		return ret;
+
+	ret = bootstate_variable_nv_init_u32(backend, bootstate, target,
+					     "priority");
+	if (ret)
+		return ret;
+
+	ret = bootstate_variable_nv_init_u32(backend, bootstate, target,
+					     "ok");
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int bootstate_backend_nv_init(struct bootstate_backend *backend,
+				     struct bootstate *bootstate)
+{
+	struct bootstate_target *target;
+	int ret;
+
+	list_for_each_entry(target, &bootstate->targets_unsorted, list_unsorted) {
+		ret = bootstate_backend_nv_init_one(backend, bootstate, target);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int bootstate_backend_nv_save(struct bootstate_backend *backend,
+				     struct bootstate *bootstate)
+{
+	int ret;
+
+	ret = bootstate_backend_save(backend, bootstate);
+	if (ret)
+		return ret;
+
+	return envfs_save(NULL, NULL, 0);
+}
+
+static int bootstate_backend_nv_load(struct bootstate_backend *backend,
+				     struct bootstate *bootstate)
+{
+	return bootstate_backend_load(backend, bootstate);
+}
+
+struct bootstate_backend_nv {
+	struct bootstate_backend backend;
+};
+
+int bootstate_backend_nv(struct bootstate *bootstate)
+{
+	struct bootstate_backend_nv *backend_nv;
+	struct bootstate_backend *backend;
+
+	if (bootstate->backend)
+		return -EBUSY;
+
+	backend_nv = xzalloc(sizeof(*backend_nv));
+	backend = &backend_nv->backend;
+
+	backend->load = bootstate_backend_nv_load;
+	backend->save = bootstate_backend_nv_save;
+	backend->name = "nv";
+	backend->path = "nv";
+
+	bootstate->backend = backend;
+
+	return bootstate_backend_nv_init(backend, bootstate);
+}
+
+struct bootstate_backend_state {
+	struct bootstate_backend backend;
+	struct state *state;
+};
+
+static int bootstate_backend_state_save(struct bootstate_backend *backend,
+					struct bootstate *bootstate)
+{
+	struct bootstate_backend_state *backend_state =
+		container_of(backend, struct bootstate_backend_state, backend);
+	int ret;
+
+	ret = bootstate_backend_save(backend, bootstate);
+	if (ret)
+		return ret;
+
+	return state_save(backend_state->state);
+}
+
+static int bootstate_backend_state_load(struct bootstate_backend *backend,
+					struct bootstate *bootstate)
+{
+	return bootstate_backend_load(backend, bootstate);
+}
+
+int bootstate_backend_state(struct bootstate *bootstate, const struct device_node *node)
+{
+	struct bootstate_backend_state *backend_state;
+	struct bootstate_backend *backend;
+	const struct device_node *state_node;
+
+	if (bootstate->backend)
+		return -EBUSY;
+
+	backend_state = xzalloc(sizeof(*backend_state));
+	backend = &backend_state->backend;
+
+	backend->load = bootstate_backend_state_load;
+	backend->save = bootstate_backend_state_save;
+	backend->name = "state";
+
+	bootstate->backend = backend;
+
+	state_node = of_parse_phandle(node, "backend", 0);
+	if (!state_node)
+		return -EINVAL;
+
+	backend_state->state = state_by_node(state_node);
+	if (!backend_state->state)
+		return -EINVAL;
+
+	return state_get_name(backend_state->state, &backend->path);
+}
+
+/*
+ * bootstate_new_from_node - create a new bootstate instance from a device_node
+ *
+ * @name	The name of the new bootstate instance
+ * @node	The device_node describing the new bootstate instance
+ */
+struct bootstate *bootstate_new_from_node(const char *name, const struct device_node *node)
+{
+	struct bootstate *bootstate;
+	int ret;
+
+	pr_debug("%s: node=%s, name=%s\n", __func__, node->full_name, name);
+
+	bootstate = bootstate_new(name);
+	if (!bootstate)
+		return ERR_PTR(-EINVAL);
+
+	ret = bootstate_from_node(bootstate, node, true);
+	if (ret) {
+		bootstate_release(bootstate);
+		return ERR_PTR(ret);
+	}
+
+	return bootstate;
+}
+
+/*
+ * bootstate_by_name - find a bootstate instance by name
+ *
+ * @name	The name of the state instance
+ */
+struct bootstate *bootstate_by_name(const char *name)
+{
+	struct bootstate *bs;
+
+	list_for_each_entry(bs, &bootstate_list, list) {
+		if (!strcmp(name, bs->name))
+			return bs;
+	}
+
+	return NULL;
+}
+
+/*
+ * bootstate_load - load a bootstate from the backing store
+ *
+ * @bootstate	The state instance to load
+ */
+static int bootstate_load(struct bootstate *bootstate)
+{
+	int ret;
+
+	if (!bootstate->backend)
+		return -ENOSYS;
+
+	ret = bootstate->backend->load(bootstate->backend, bootstate);
+	if (ret)
+		bootstate->dirty = 1;
+	else
+		bootstate->dirty = 0;
+
+	return ret;
+}
+
+/*
+ * bootstate_save - save a bootstate to the backing store
+ *
+ * @bootstate	The bootstate instance to save
+ */
+static int bootstate_save(struct bootstate *bootstate)
+{
+	int ret;
+
+	if (!bootstate->dirty)
+		return 0;
+
+	if (!bootstate->backend)
+		return -ENOSYS;
+
+	ret = bootstate->backend->save(bootstate->backend, bootstate);
+	if (ret)
+		return ret;
+
+	bootstate->dirty = 0;
+
+	return 0;
+}
+
+void bootstate_info(void)
+{
+	struct bootstate *bootstate;
+
+	printf("registered bootstate instances:\n");
+
+	list_for_each_entry(bootstate, &bootstate_list, list) {
+		printf("%-20s ", bootstate->name);
+		printf("(backend: %s, path: %s)\n",
+		       bootstate->backend->name, bootstate->backend->path);
+	}
+}
+
+#define __BF(arg) [__BOOTCHOOSER_FLAG_##arg##_SHIFT] = __stringify(arg)
+
+static const char * const bootstate_flags_str[] = {
+	__BF(ATTEMPTS_KEEP),
+	__BF(ATTEMPTS_DEC),
+	__BF(ATTEMPTS_RESET),
+	__BF(DEACTIVATE_ON_ZERO_ATTEMPTS),
+	__BF(VERBOSE),
+	__BF(DRYRUN),
+	__BF(RETRY_WITH_DEC),
+	__BF(WATCHDOG_ENABLE),
+	__BF(WATCHDOG_TIMEOUT_FROM_STATE),
+};
+
+#undef __BF
+
+#define pr(verbose, format, args...) \
+	({ \
+		(verbose) ? pr_info((format), ##args) : 0; \
+	})
+
+void _pr_flags(struct bootstate *bootstate, unsigned flags)
+{
+	int i;
+
+	pr_info("%s: flags=0x%08x\n", bootstate->name, flags);
+
+	for (i = 0; i < ARRAY_SIZE(bootstate_flags_str); i++) {
+		if (flags & (1 << i))
+			pr_info("%s: -> %s\n", bootstate->name,
+				bootstate_flags_str[i]);
+	}
+}
+
+#define pr_flags(verbose, bootstate, flags) \
+	({ \
+		(verbose) ? _pr_flags(bootstate, flags) : 0; \
+	})
+
+/*
+ * bootstate_get_target - create a new state instance from a device_node
+ *
+ * @bootstate	the bootstate instance to work in
+ * @flags	supported flags:
+ *		BOOTCHOOSER_FLAG_VERBOSE
+ *		BOOTCHOOSER_FLAG_ATTEMPTS_KEEP
+ *		BOOTCHOOSER_FLAG_ATTEMPTS_DEC
+ *		BOOTCHOOSER_FLAG_ATTEMPTS_RESET
+ *		BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS
+ * @target_out	a string to the choosen boot target is returned via
+ *		this paramater
+ */
+int bootstate_get_target(struct bootstate *bootstate, unsigned flags, char **target_out)
+{
+	struct bootstate_target *target;
+	int ret;
+	bool found = false;
+	bool v = flags & BOOTCHOOSER_FLAG_VERBOSE;
+
+	pr_flags(v, bootstate, flags);
+
+	ret = bootstate_load(bootstate);
+	if (ret)
+		return ret;
+
+	if (flags & BOOTCHOOSER_FLAG_ATTEMPTS_RESET) {
+		list_for_each_entry(target, &bootstate->targets, list) {
+			if (target->priority ==  0)
+				continue;
+
+			target->remaining_attempts = target->default_attempts;
+			bootstate->dirty = true;
+
+			pr(v, "%s: target: name=%s setting rem to %d due to %s\n",
+			   bootstate->name, target->name, target->default_attempts,
+			   bootstate_flags_str[__BOOTCHOOSER_FLAG_ATTEMPTS_RESET_SHIFT]);
+		}
+		pr(v, "%s: --------\n", bootstate->name);
+	}
+
+	list_for_each_entry(target, &bootstate->targets, list) {
+		pr_target(bootstate, target);
+
+		if (found)
+			continue;
+
+		if (target->priority == 0) {
+			pr(v, "%s: name=%s prio=%d - trying next\n",
+			   bootstate->name, target->name, target->priority);
+			continue;
+		}
+
+		if (target->remaining_attempts == 0) {
+			pr(v, "%s: name=%s remaining attempts == 0 - trying next\n",
+			   bootstate->name, target->name);
+			continue;
+		}
+
+		if (flags & BOOTCHOOSER_FLAG_ATTEMPTS_DEC) {
+			bootstate->dirty = true;
+			target->remaining_attempts--;
+
+			pr(v, "%s: name=%s decrementing remaining_attempts to %d due to %s\n",
+			   bootstate->name, target->name,
+			   target->remaining_attempts,
+			   bootstate_flags_str[__BOOTCHOOSER_FLAG_ATTEMPTS_DEC_SHIFT]);
+
+			if ((target->remaining_attempts == 0) &&
+			    (flags & BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS)) {
+				target->priority = 0;
+
+				pr(v, "%s: name=%s deactivating target (setting priority = 0) due to %s\n",
+				   bootstate->name, target->name,
+				   bootstate_flags_str[__BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS_SHIFT]);
+			}
+		}
+
+		found = true;
+		*target_out = strdup(target->name);
+		pr_debug("%s: selected target '%s'\n", __func__, target->name);
+		if (!v)
+			goto out;
+
+		pr(v, "%s: --- other bootsources ---\n", bootstate->name);
+	}
+
+ out:
+	bootstate_save(bootstate);
+
+	if (!found)
+		return -ENOENT;
+
+	return 0;
+}
+
+int bootstate_bootchooser(char *name, unsigned flags, unsigned timeout)
+{
+	struct bootstate *bootstate;
+	bool v = flags & BOOTCHOOSER_FLAG_VERBOSE;
+	char *target;
+	int ret;
+
+	if (!name)
+		name = "bootstate";
+
+	bootstate = bootstate_by_name(name);
+	if (!bootstate)
+		return -ENOENT;
+
+	if (flags & BOOTCHOOSER_FLAG_WATCHDOG_ENABLE) {
+		if (flags & BOOTCHOOSER_FLAG_WATCHDOG_TIMEOUT_FROM_STATE) {
+			ret = bootstate_variable_read_u32(bootstate, "watchdog_timeout",
+							  &timeout);
+			if (ret)
+				return ret;
+		}
+
+		if (timeout != 0) {
+			pr(v, "%s: starting watchdog with timeout=%ds\n",
+			   __func__, timeout);
+
+			ret = watchdog_set_timeout(timeout);
+			if (ret)
+				return ret;
+		}
+	}
+
+	while (true) {
+		char *cmd;
+
+		ret = bootstate_get_target(bootstate, flags, &target);
+		if (ret)
+			return ret;
+
+		cmd = asprintf("boot %s", target);
+		free(target);
+		pr_info("%srunning: %s...\n",
+			flags & BOOTCHOOSER_FLAG_DRYRUN ? "not " : "", cmd);
+		if (!(flags & BOOTCHOOSER_FLAG_DRYRUN))
+			ret = run_command(cmd);
+		free(cmd);
+
+		if (flags & BOOTCHOOSER_FLAG_RETRY_WITH_DEC) {
+			flags |= BOOTCHOOSER_FLAG_ATTEMPTS_DEC;
+			flags &= ~(BOOTCHOOSER_FLAG_ATTEMPTS_RESET |
+				   BOOTCHOOSER_FLAG_ATTEMPTS_KEEP);
+			continue;
+		}
+
+		return ret;
+	}
+
+	return -ENOENT;
+}
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 7a5b14697efd..9b3ac244832b 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -19,4 +19,7 @@ config STATE_DRV
 	tristate "state driver"
 	depends on STATE
 
+config BOOTSTATE_DRV
+	tristate "bootstate driver"
+
 endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 487e4b8ba2e5..603e14ebb5de 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -5,3 +5,4 @@
 obj-$(CONFIG_JTAG)		+= jtag.o
 obj-$(CONFIG_SRAM)		+= sram.o
 obj-$(CONFIG_STATE_DRV)		+= state.o
+obj-$(CONFIG_BOOTSTATE_DRV)	+= bootstate.o
diff --git a/drivers/misc/bootstate.c b/drivers/misc/bootstate.c
new file mode 100644
index 000000000000..96b9f23c44da
--- /dev/null
+++ b/drivers/misc/bootstate.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 Sascha Hauer <s.hauer at pengutronix.de>
+ * Copyright (C) 2015 Marc Kleine-Budde <mkl at pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <bootstate.h>
+#include <driver.h>
+#include <init.h>
+#include <malloc.h>
+#include <of.h>
+#include <string.h>
+
+#include <linux/err.h>
+
+static int bootstate_probe(struct device_d *dev)
+{
+	struct device_node *np = dev->device_node;
+	struct bootstate *bootstate;
+	const char *alias;
+	const char *backend_type = NULL;
+	int ret;
+
+	if (!np)
+		return -EINVAL;
+
+	alias = of_alias_get(np);
+	if (!alias)
+		alias = "bootstate";
+
+	bootstate = bootstate_new_from_node(alias, np);
+	if (IS_ERR(bootstate))
+		return PTR_ERR(bootstate);
+
+	of_property_read_string(np, "backend-type", &backend_type);
+	if (!strcmp(backend_type, "state"))
+		ret = bootstate_backend_state(bootstate, np);
+	else if (!strcmp(backend_type, "nv"))
+		ret = bootstate_backend_nv(bootstate);
+	else
+		return -EINVAL;
+
+	return ret;
+}
+
+static __maybe_unused struct of_device_id bootstate_ids[] = {
+	{
+		.compatible = "barebox,bootstate",
+	}, {
+		/* sentinel */
+	}
+};
+
+static struct driver_d bootstate_driver = {
+	.name = "bootstate",
+	.probe = bootstate_probe,
+	.of_compatible = DRV_OF_COMPAT(bootstate_ids),
+};
+device_platform_driver(bootstate_driver);
diff --git a/include/bootstate.h b/include/bootstate.h
new file mode 100644
index 000000000000..53102c527060
--- /dev/null
+++ b/include/bootstate.h
@@ -0,0 +1,38 @@
+#ifndef __BOOTSTATE_H
+#define __BOOTSTATE_H
+
+#include <of.h>
+
+struct bootstate *bootstate_new_from_node(const char *name, const struct device_node *node);
+struct bootstate *bootstate_find_by_name(const char *name);
+struct bootstate *bootstate_by_name(const char *name);
+void bootstate_info(void);
+int bootstate_backend_nv(struct bootstate *bootstate);
+int bootstate_backend_state(struct bootstate *bootstate, const struct device_node *node);
+
+enum {
+	__BOOTCHOOSER_FLAG_ATTEMPTS_KEEP_SHIFT,
+	__BOOTCHOOSER_FLAG_ATTEMPTS_DEC_SHIFT,
+	__BOOTCHOOSER_FLAG_ATTEMPTS_RESET_SHIFT,
+	__BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS_SHIFT,
+	__BOOTCHOOSER_FLAG_VERBOSE_SHIFT,
+	__BOOTCHOOSER_FLAG_DRYRUN_SHIFT,
+	__BOOTCHOOSER_FLAG_RETRY_WITH_DEC_SHIFT,
+	__BOOTCHOOSER_FLAG_WATCHDOG_ENABLE_SHIFT,
+	__BOOTCHOOSER_FLAG_WATCHDOG_TIMEOUT_FROM_STATE_SHIFT,
+};
+
+#define BOOTCHOOSER_FLAG_ATTEMPTS_KEEP (1 << __BOOTCHOOSER_FLAG_ATTEMPTS_KEEP_SHIFT)
+#define BOOTCHOOSER_FLAG_ATTEMPTS_DEC (1 << __BOOTCHOOSER_FLAG_ATTEMPTS_DEC_SHIFT)
+#define BOOTCHOOSER_FLAG_ATTEMPTS_RESET (1 << __BOOTCHOOSER_FLAG_ATTEMPTS_RESET_SHIFT)
+#define BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS (1 << __BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS_SHIFT)
+#define BOOTCHOOSER_FLAG_VERBOSE (1 << __BOOTCHOOSER_FLAG_VERBOSE_SHIFT)
+#define BOOTCHOOSER_FLAG_DRYRUN (1 << __BOOTCHOOSER_FLAG_DRYRUN_SHIFT)
+#define BOOTCHOOSER_FLAG_RETRY_WITH_DEC (1 << __BOOTCHOOSER_FLAG_RETRY_WITH_DEC_SHIFT)
+#define BOOTCHOOSER_FLAG_WATCHDOG_ENABLE (1 << __BOOTCHOOSER_FLAG_WATCHDOG_ENABLE_SHIFT)
+#define BOOTCHOOSER_FLAG_WATCHDOG_TIMEOUT_FROM_STATE (1 << __BOOTCHOOSER_FLAG_WATCHDOG_TIMEOUT_FROM_STATE_SHIFT)
+
+int bootstate_get_target(struct bootstate *bootstate, unsigned flags, char **target_out);
+int bootstate_bootchooser(char *name, unsigned flags, unsigned watchdog_timeout_s);
+
+#endif /* __BOOTSTATE_H */
-- 
2.1.4




More information about the barebox mailing list