diff --git a/Documentation/devicetree/bindings/barebox/barebox,bootstate.rst b/Documentation/devicetree/bindings/barebox/barebox,bootstate.rst
new file mode 100644
index 000000000..f4a16727c
--- /dev/null
+++ b/Documentation/devicetree/bindings/barebox/barebox,bootstate.rst
@@ -0,0 +1,238 @@
+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,state``;
+* ``magic``: A 32bit number used as a magic to identify the state
+
+Optional properties:
+
+* ``backend-type``: should be ``state`` or ``nv``.
+* ``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 ``@
``, 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 in a 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 the 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 ``: 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 2595aa13f..e2bc8f76c 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 847ff76d1..d9840bf86 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 b902f58ec..f51cfb6ed 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 000000000..340061758
--- /dev/null
+++ b/commands/bootchooser.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012 Jan Luebbe
+ * Copyright (C) 2015 Marc Kleine-Budde
+ *
+ * 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
+#include
+#include
+#include
+#include
+#include
+
+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 .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 [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 1c5d14c1c..e873e294e 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -713,6 +713,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 eca1e3533..83b442ef0 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -43,6 +43,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 000000000..5baaf03b7
--- /dev/null
+++ b/common/bootstate.c
@@ -0,0 +1,776 @@
+/*
+ * Copyright (C) 2012 Jan Luebbe
+ * Copyright (C) 2015 Marc Kleine-Budde
+ *
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+/* 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 7a5b14697..9b3ac2448 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 487e4b8ba..603e14ebb 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 000000000..96b9f23c4
--- /dev/null
+++ b/drivers/misc/bootstate.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 Sascha Hauer
+ * Copyright (C) 2015 Marc Kleine-Budde
+ *
+ * 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
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+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 000000000..53102c527
--- /dev/null
+++ b/include/bootstate.h
@@ -0,0 +1,38 @@
+#ifndef __BOOTSTATE_H
+#define __BOOTSTATE_H
+
+#include
+
+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 */