From: Marc Kleine-Budde Date: Fri, 20 Feb 2015 09:19:14 +0100 Subject: [PATCH] bootstate: add framework for redundant boot scenarios 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. Signed-off-by: Marc Kleine-Budde --- .../bindings/barebox/barebox,bootstate.rst | 236 +++++++ arch/sandbox/dts/sandbox.dts | 83 +++ commands/Kconfig | 5 + commands/Makefile | 1 + commands/bootchooser.c | 101 +++ common/Kconfig | 8 + common/Makefile | 1 + common/bootstate.c | 781 +++++++++++++++++++++ drivers/misc/Kconfig | 5 + drivers/misc/Makefile | 1 + drivers/misc/bootstate.c | 79 +++ include/bootstate.h | 39 + 12 files changed, 1340 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..0f7131536798 --- /dev/null +++ b/Documentation/devicetree/bindings/barebox/barebox,bootstate.rst @@ -0,0 +1,236 @@ +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 ``@
``, but the suffix is +stripped 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 nodes ``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 by the bootstate algorithm during boot. +* ``priority``: defines the priority of the boot target. Higher number + indicate a higher priority, If two boot targets 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 targets (``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 targets 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 highest 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, then chose next target, but + decrement its attempts. Note: if the current target has still the + highest 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 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 875c5f4f01ed..603e887b15ec 100644 --- a/commands/Kconfig +++ b/commands/Kconfig @@ -2189,6 +2189,11 @@ config CMD_SPD_DECODE help decode spd eeprom +config CMD_BOOTCHOOSER + tristate + depends on BOOTSTATE + prompt "bootchooser" + # end Miscellaneous commands endmenu diff --git a/commands/Makefile b/commands/Makefile index f1b482f04934..bb3b40809af5 100644 --- a/commands/Makefile +++ b/commands/Makefile @@ -117,3 +117,4 @@ obj-$(CONFIG_CMD_DHRYSTONE) += dhrystone.o obj-$(CONFIG_CMD_SPD_DECODE) += spd_decode.o obj-$(CONFIG_CMD_MMC_EXTCSD) += mmc_extcsd.o obj-$(CONFIG_CMD_NAND_BITFLIP) += nand-bitflip.o +obj-$(CONFIG_CMD_BOOTCHOOSER) += bootchooser.o diff --git a/commands/bootchooser.c b/commands/bootchooser.c new file mode 100644 index 000000000000..06ca5e41d527 --- /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 counters to default") +BAREBOX_CMD_HELP_OPT ("-z","deactivate chosen target on zero remaining boot attempts") +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 7c09e8c117f1..f17769661ee6 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -792,6 +792,14 @@ config STATE_CRYPTO See Documentation/devicetree/bindings/barebox/barebox,state.rst for more information. +config BOOTSTATE + bool "bootstate infrastructure" + depends on OF_BAREBOX_DRIVERS + select ENVIRONMENT_VARIABLES + select OFTREE + select PARAMETER + select BOOTSTATE_DRV + config RESET_SOURCE bool "detect Reset cause" depends on GLOBALVAR diff --git a/common/Makefile b/common/Makefile index d99ca7b7ac74..5505449874ad 100644 --- a/common/Makefile +++ b/common/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_SHELL_HUSH) += hush.o obj-$(CONFIG_SHELL_SIMPLE) += parser.o obj-$(CONFIG_STATE) += state.o obj-$(CONFIG_RATP) += ratp.o +obj-$(CONFIG_BOOTSTATE) += bootstate.o obj-$(CONFIG_UIMAGE) += image.o uimage.o obj-$(CONFIG_FITIMAGE) += image-fit.o obj-$(CONFIG_MENUTREE) += menutree.o diff --git a/common/bootstate.c b/common/bootstate.c new file mode 100644 index 000000000000..3dc18813064d --- /dev/null +++ b/common/bootstate.c @@ -0,0 +1,781 @@ +/* + * 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; +} + +void bootstate_release(struct bootstate *bootstate) +{ + list_del(&bootstate->list); + 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; + struct state *state; + + if (bootstate->backend) + return -EBUSY; + + state_node = of_parse_phandle(node, "backend", 0); + if (!state_node) + return -EINVAL; + + state = state_by_node(state_node); + if (!state) + return -EPROBE_DEFER; + + backend_state = xzalloc(sizeof(*backend_state)); + backend_state->state = state; + + backend = &backend_state->backend; + backend->load = bootstate_backend_state_load; + backend->save = bootstate_backend_state_save; + backend->name = "state"; + + bootstate->backend = backend; + + 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) { + pr_err("Bootstate '%s' not found.\n", name); + return -ENODEV; + } + + 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..2459051e1db1 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -19,4 +19,9 @@ config STATE_DRV tristate "state driver" depends on STATE +config BOOTSTATE_DRV + tristate "bootstate driver" + depends on BOOTSTATE + depends on STATE + 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..3ec9b8fda86b --- /dev/null +++ b/drivers/misc/bootstate.c @@ -0,0 +1,79 @@ +/* + * 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 + +#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 { + dev_warn(dev, "invalid backend type: %s\n", backend_type); + ret = -ENODEV; + goto out_release; + } + + if (ret) + goto out_release; + + return 0; + + out_release: + bootstate_release(bootstate); + 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..22631c902f60 --- /dev/null +++ b/include/bootstate.h @@ -0,0 +1,39 @@ +#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_release(struct bootstate *bootstate); +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 */