On Wed, May 27, 2015 at 05:22:58PM +0200, Marc Kleine-Budde wrote: > 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. Hello, is there any reason why this wasn't applied? I tested it and it works fine. There is also a watchdog handling now in commands/boot.c so we can rework the bootchooser. Jan > > For more information see the included > Documentation/devicetree/bindings/barebox/barebox,bootstate.rst file. > > Signed-off-by: Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx> > --- > .../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@xxxxxxxxxxxxxx> > + * Copyright (C) 2015 Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx> > + * > + * 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@xxxxxxxxxxxxxx> > + * Copyright (C) 2015 Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx> > + * > + * 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@xxxxxxxxxxxxxx> > + * Copyright (C) 2015 Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx> > + * > + * 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 > > > _______________________________________________ > barebox mailing list > barebox@xxxxxxxxxxxxxxxxxxx > http://lists.infradead.org/mailman/listinfo/barebox _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox