9
0
Fork 0

bootstate: add

Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
This commit is contained in:
Marc Kleine-Budde 2015-02-20 09:19:14 +01:00 committed by Jan Luebbe
parent 42754c2345
commit 51f97717c8
12 changed files with 1322 additions and 0 deletions

View File

@ -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 ``@<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 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 <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.

View File

@ -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>;
};
};
};

View File

@ -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

View File

@ -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

101
commands/bootchooser.c Normal file
View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2012 Jan Luebbe <j.luebbe@pengutronix.de>
* Copyright (C) 2015 Marc Kleine-Budde <mkl@pengutronix.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <bootstate.h>
#include <command.h>
#include <common.h>
#include <getopt.h>
#include <malloc.h>
#include <stdio.h>
static int do_bootchooser(int argc, char *argv[])
{
unsigned flags = 0, timeout = 0;
char *name = NULL;
int opt, ret;
while ((opt = getopt(argc, argv, "kdrzvDRw::")) > 0) {
switch (opt) {
case 'k':
flags |= BOOTCHOOSER_FLAG_ATTEMPTS_KEEP;
break;
case 'd':
flags |= BOOTCHOOSER_FLAG_ATTEMPTS_DEC;
break;
case 'r':
flags |= BOOTCHOOSER_FLAG_ATTEMPTS_RESET;
break;
case 'z':
flags |= BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS;
break;
case 'v':
flags |= BOOTCHOOSER_FLAG_VERBOSE;
break;
case 'D':
flags |= BOOTCHOOSER_FLAG_DRYRUN;
break;
case 'R':
flags |= BOOTCHOOSER_FLAG_RETRY_WITH_DEC;
break;
case 'w':
if (optarg)
timeout = simple_strtoul(optarg, NULL, 0);
else
flags |= BOOTCHOOSER_FLAG_WATCHDOG_TIMEOUT_FROM_STATE;
flags |= BOOTCHOOSER_FLAG_WATCHDOG_ENABLE;
break;
default:
return COMMAND_ERROR_USAGE;
}
}
if (optind < argc)
name = argv[optind];
if (!(flags & (BOOTCHOOSER_FLAG_ATTEMPTS_KEEP |
BOOTCHOOSER_FLAG_ATTEMPTS_DEC |
BOOTCHOOSER_FLAG_ATTEMPTS_RESET))) {
bootstate_info();
return 0;
}
if ((flags & BOOTCHOOSER_FLAG_ATTEMPTS_KEEP) &&
(flags & (BOOTCHOOSER_FLAG_ATTEMPTS_DEC | BOOTCHOOSER_FLAG_ATTEMPTS_RESET)))
return COMMAND_ERROR_USAGE;
ret = bootstate_bootchooser(name, flags, timeout);
return ret ? COMMAND_ERROR : COMMAND_SUCCESS;
}
BAREBOX_CMD_HELP_START(bootchooser)
BAREBOX_CMD_HELP_TEXT("Options:")
BAREBOX_CMD_HELP_OPT ("-k","keep - boot, don't modify attempts counter")
BAREBOX_CMD_HELP_OPT ("-d","decrement - boot, but decrement attempts counter by one")
BAREBOX_CMD_HELP_OPT ("-r","reset - boot, but reset _all_ attempts counter to default")
BAREBOX_CMD_HELP_OPT ("-z","deactivate choosen target in on zero remaining boot attemts")
BAREBOX_CMD_HELP_OPT ("-v","verbose output")
BAREBOX_CMD_HELP_OPT ("-D","dryrun. Do not boot - but handle watchdog and reset.")
BAREBOX_CMD_HELP_OPT ("-R","retry - boot, retry next boot target and decrement attempts")
BAREBOX_CMD_HELP_OPT ("-w","activate watchdog, use timeout specified in <BOOTSTATE>.watchdog_timeout")
BAREBOX_CMD_HELP_END
BAREBOX_CMD_START(bootchooser)
.cmd = do_bootchooser,
BAREBOX_CMD_DESC("automatically select a boot target and boot")
BAREBOX_CMD_OPTS("[-kdrzvDR] -w <TIMEOUT> [BOOTSTATE]")
BAREBOX_CMD_GROUP(CMD_GRP_MISC)
BAREBOX_CMD_HELP(cmd_bootchooser_help)
BAREBOX_CMD_END

View File

@ -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

View File

@ -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

776
common/bootstate.c Normal file
View File

@ -0,0 +1,776 @@
/*
* Copyright (C) 2012 Jan Luebbe <j.luebbe@pengutronix.de>
* Copyright (C) 2015 Marc Kleine-Budde <mkl@pengutronix.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <bootstate.h>
#include <common.h>
#include <envfs.h>
#include <environment.h>
#include <errno.h>
#include <fcntl.h>
#include <fs.h>
#include <globalvar.h>
#include <init.h>
#include <ioctl.h>
#include <libbb.h>
#include <libfile.h>
#include <malloc.h>
#include <net.h>
#include <printk.h>
#include <state.h>
#include <stdio.h>
#include <watchdog.h>
#include <xfuncs.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mtd/mtd-abi.h>
#include <linux/mtd/mtd.h>
#include <asm/unaligned.h>
/* list of all registered bootstate instances */
static LIST_HEAD(bootstate_list);
struct state_backend;
struct bootstate {
struct device_d dev;
const char *name;
struct list_head list;
struct list_head targets;
struct list_head targets_unsorted;
struct bootstate_backend *backend;
bool dirty;
};
struct bootstate_backend {
int (*load)(struct bootstate_backend *backend, struct bootstate *bootstate);
int (*save)(struct bootstate_backend *backend, struct bootstate *bootstate);
const char *name;
const char *path;
};
struct bootstate_target {
struct list_head list;
struct list_head list_unsorted;
/* state */
unsigned int priority;
unsigned int remaining_attempts;
bool ok;
/* spec */
const char *name;
unsigned int default_attempts;
};
static void pr_target(struct bootstate *bootstate, struct bootstate_target *target)
{
printf("%s: target: name=%s prio=%u, ok=%d, rem=%u, def=%u\n",
bootstate->name, target->name, target->priority, target->ok,
target->remaining_attempts, target->default_attempts);
}
static struct bootstate *bootstate_new(const char *name)
{
struct bootstate *bootstate;
int ret;
bootstate = xzalloc(sizeof(*bootstate));
safe_strncpy(bootstate->dev.name, name, MAX_DRIVER_NAME);
bootstate->name = bootstate->dev.name;
bootstate->dev.id = DEVICE_ID_DYNAMIC;
INIT_LIST_HEAD(&bootstate->targets);
INIT_LIST_HEAD(&bootstate->targets_unsorted);
ret = register_device(&bootstate->dev);
if (ret) {
free(bootstate);
return ERR_PTR(ret);
}
list_add_tail(&bootstate->list, &bootstate_list);
return bootstate;
}
static void bootstate_release(struct bootstate *bootstate)
{
unregister_device(&bootstate->dev);
free(bootstate);
}
static int bootstate_target_compare(struct list_head *a, struct list_head *b)
{
struct bootstate_target *bootstate_a = list_entry(a, struct bootstate_target, list);
struct bootstate_target *bootstate_b = list_entry(b, struct bootstate_target, list);
/* order descending */
return bootstate_a->priority >= bootstate_b->priority ? -1 : 1;
}
static void bootstate_target_add(struct bootstate *bootstate, struct bootstate_target *target)
{
list_del(&target->list);
list_add_sort(&target->list, &bootstate->targets, bootstate_target_compare);
}
static int bootstate_variable_read_u32(const struct bootstate *bootstate,
const char *name, uint32_t *out_val)
{
char *var;
int ret;
var = asprintf("%s.%s.%s", bootstate->backend->path, bootstate->name, name);
ret = getenv_uint(var, out_val);
free(var);
return ret;
}
static int bootstate_backend_variable_read_target_u32(const struct bootstate_backend *backend,
const struct bootstate *bootstate,
const struct bootstate_target *target,
const char *name, uint32_t *out_val)
{
char *var;
int ret;
var = asprintf("%s.%s.%s.%s", backend->path, bootstate->name,
target->name, name);
ret = getenv_uint(var, out_val);
free(var);
return ret;
}
static int bootstate_backend_variable_write_target_u32(const struct bootstate_backend *backend,
const struct bootstate *bootstate,
const struct bootstate_target *target,
const char *name, uint32_t in_val)
{
char *var;
char *val;
int ret;
var = asprintf("%s.%s.%s.%s", backend->path, bootstate->name,
target->name, name);
val = asprintf("%d", in_val);
ret = setenv(var, val);
free(val);
free(var);
return ret;
}
static int bootstate_variable_nv_init_u32(const struct bootstate_backend *backend,
const struct bootstate *bootstate,
const struct bootstate_target *target,
const char *name)
{
char *var;
int ret;
var = asprintf("%s.%s.%s", bootstate->name, target->name, name);
ret = nvvar_add(var, "0");
free(var);
return ret;
}
static struct bootstate_target *bootstate_target_find(const struct bootstate *bootstate,
const char *name)
{
struct bootstate_target *target;
list_for_each_entry(target, &bootstate->targets, list) {
if (!strcmp(target->name, name))
return target;
}
return ERR_PTR(-ENOENT);
}
static int bootstate_target_from_node(struct bootstate *bootstate, const struct device_node *node, bool create)
{
struct bootstate_target *target;
char *name, *indexs;
int ret;
name = xstrdup(node->name);
indexs = strchr(name, '@');
if (indexs)
*indexs++ = 0;
if (create) {
/* create*/
target = xzalloc(sizeof(*target));
target->name = xstrdup(name);
list_add_tail(&target->list, &bootstate->targets);
list_add_tail(&target->list_unsorted,
&bootstate->targets_unsorted);
} else {
target = bootstate_target_find(bootstate, name);
if (IS_ERR(target)) {
int ret = PTR_ERR(target);
pr_err("no such boot target: %s: %s\n",
name, strerror(-ret));
return ret;
}
}
/* init */
ret = of_property_read_u32(node, "default_attempts",
&target->default_attempts);
if (ret)
return ret;
free(name);
return 0;
}
static int bootstate_from_node(struct bootstate *bootstate,
const struct device_node *node, bool create)
{
struct device_node *child;
int ret;
for_each_child_of_node(node, child) {
ret = bootstate_target_from_node(bootstate, child, create);
if (ret)
return ret;
}
return 0;
}
static int bootstate_backend_load_one(const struct bootstate_backend *backend,
const struct bootstate *bootstate,
struct bootstate_target *target)
{
uint32_t tmp;
int ret;
ret = bootstate_backend_variable_read_target_u32(backend, bootstate, target,
"remaining_attempts",
&target->remaining_attempts);
if (ret)
return ret;
ret = bootstate_backend_variable_read_target_u32(backend, bootstate, target,
"priority", &target->priority);
if (ret)
return ret;
ret = bootstate_backend_variable_read_target_u32(backend, bootstate, target,
"ok", &tmp);
if (ret)
return ret;
target->ok = !!tmp;
return ret;
}
static int bootstate_backend_load(struct bootstate_backend *backend,
struct bootstate *bootstate)
{
struct bootstate_target *target;
int ret;
list_for_each_entry(target, &bootstate->targets_unsorted, list_unsorted) {
ret = bootstate_backend_load_one(backend, bootstate, target);
if (ret)
return ret;
bootstate_target_add(bootstate, target);
}
return 0;
}
static int bootstate_backend_save_one(const struct bootstate_backend *backend,
const struct bootstate *bootstate,
struct bootstate_target *target)
{
int ret;
ret = bootstate_backend_variable_write_target_u32(backend, bootstate, target,
"remaining_attempts",
target->remaining_attempts);
if (ret)
return ret;
ret = bootstate_backend_variable_write_target_u32(backend, bootstate, target,
"priority", target->priority);
if (ret)
return ret;
ret = bootstate_backend_variable_write_target_u32(backend, bootstate, target,
"ok", target->ok);
if (ret)
return ret;
return 0;
}
static int bootstate_backend_save(const struct bootstate_backend *backend,
const struct bootstate *bootstate)
{
struct bootstate_target *target;
int ret;
list_for_each_entry(target, &bootstate->targets, list) {
ret = bootstate_backend_save_one(backend, bootstate, target);
if (ret)
return ret;
}
return 0;
}
static int bootstate_backend_nv_init_one(const struct bootstate_backend *backend,
const struct bootstate *bootstate,
struct bootstate_target *target)
{
int ret;
ret = bootstate_variable_nv_init_u32(backend, bootstate, target,
"remaining_attempts");
if (ret)
return ret;
ret = bootstate_variable_nv_init_u32(backend, bootstate, target,
"priority");
if (ret)
return ret;
ret = bootstate_variable_nv_init_u32(backend, bootstate, target,
"ok");
if (ret)
return ret;
return 0;
}
static int bootstate_backend_nv_init(struct bootstate_backend *backend,
struct bootstate *bootstate)
{
struct bootstate_target *target;
int ret;
list_for_each_entry(target, &bootstate->targets_unsorted, list_unsorted) {
ret = bootstate_backend_nv_init_one(backend, bootstate, target);
if (ret)
return ret;
}
return 0;
}
static int bootstate_backend_nv_save(struct bootstate_backend *backend,
struct bootstate *bootstate)
{
int ret;
ret = bootstate_backend_save(backend, bootstate);
if (ret)
return ret;
return envfs_save(NULL, NULL, 0);
}
static int bootstate_backend_nv_load(struct bootstate_backend *backend,
struct bootstate *bootstate)
{
return bootstate_backend_load(backend, bootstate);
}
struct bootstate_backend_nv {
struct bootstate_backend backend;
};
int bootstate_backend_nv(struct bootstate *bootstate)
{
struct bootstate_backend_nv *backend_nv;
struct bootstate_backend *backend;
if (bootstate->backend)
return -EBUSY;
backend_nv = xzalloc(sizeof(*backend_nv));
backend = &backend_nv->backend;
backend->load = bootstate_backend_nv_load;
backend->save = bootstate_backend_nv_save;
backend->name = "nv";
backend->path = "nv";
bootstate->backend = backend;
return bootstate_backend_nv_init(backend, bootstate);
}
struct bootstate_backend_state {
struct bootstate_backend backend;
struct state *state;
};
static int bootstate_backend_state_save(struct bootstate_backend *backend,
struct bootstate *bootstate)
{
struct bootstate_backend_state *backend_state =
container_of(backend, struct bootstate_backend_state, backend);
int ret;
ret = bootstate_backend_save(backend, bootstate);
if (ret)
return ret;
return state_save(backend_state->state);
}
static int bootstate_backend_state_load(struct bootstate_backend *backend,
struct bootstate *bootstate)
{
return bootstate_backend_load(backend, bootstate);
}
int bootstate_backend_state(struct bootstate *bootstate, const struct device_node *node)
{
struct bootstate_backend_state *backend_state;
struct bootstate_backend *backend;
const struct device_node *state_node;
if (bootstate->backend)
return -EBUSY;
backend_state = xzalloc(sizeof(*backend_state));
backend = &backend_state->backend;
backend->load = bootstate_backend_state_load;
backend->save = bootstate_backend_state_save;
backend->name = "state";
bootstate->backend = backend;
state_node = of_parse_phandle(node, "backend", 0);
if (!state_node)
return -EINVAL;
backend_state->state = state_by_node(state_node);
if (!backend_state->state)
return -EINVAL;
return state_get_name(backend_state->state, &backend->path);
}
/*
* bootstate_new_from_node - create a new bootstate instance from a device_node
*
* @name The name of the new bootstate instance
* @node The device_node describing the new bootstate instance
*/
struct bootstate *bootstate_new_from_node(const char *name, const struct device_node *node)
{
struct bootstate *bootstate;
int ret;
pr_debug("%s: node=%s, name=%s\n", __func__, node->full_name, name);
bootstate = bootstate_new(name);
if (!bootstate)
return ERR_PTR(-EINVAL);
ret = bootstate_from_node(bootstate, node, true);
if (ret) {
bootstate_release(bootstate);
return ERR_PTR(ret);
}
return bootstate;
}
/*
* bootstate_by_name - find a bootstate instance by name
*
* @name The name of the state instance
*/
struct bootstate *bootstate_by_name(const char *name)
{
struct bootstate *bs;
list_for_each_entry(bs, &bootstate_list, list) {
if (!strcmp(name, bs->name))
return bs;
}
return NULL;
}
/*
* bootstate_load - load a bootstate from the backing store
*
* @bootstate The state instance to load
*/
static int bootstate_load(struct bootstate *bootstate)
{
int ret;
if (!bootstate->backend)
return -ENOSYS;
ret = bootstate->backend->load(bootstate->backend, bootstate);
if (ret)
bootstate->dirty = 1;
else
bootstate->dirty = 0;
return ret;
}
/*
* bootstate_save - save a bootstate to the backing store
*
* @bootstate The bootstate instance to save
*/
static int bootstate_save(struct bootstate *bootstate)
{
int ret;
if (!bootstate->dirty)
return 0;
if (!bootstate->backend)
return -ENOSYS;
ret = bootstate->backend->save(bootstate->backend, bootstate);
if (ret)
return ret;
bootstate->dirty = 0;
return 0;
}
void bootstate_info(void)
{
struct bootstate *bootstate;
printf("registered bootstate instances:\n");
list_for_each_entry(bootstate, &bootstate_list, list) {
printf("%-20s ", bootstate->name);
printf("(backend: %s, path: %s)\n",
bootstate->backend->name, bootstate->backend->path);
}
}
#define __BF(arg) [__BOOTCHOOSER_FLAG_##arg##_SHIFT] = __stringify(arg)
static const char * const bootstate_flags_str[] = {
__BF(ATTEMPTS_KEEP),
__BF(ATTEMPTS_DEC),
__BF(ATTEMPTS_RESET),
__BF(DEACTIVATE_ON_ZERO_ATTEMPTS),
__BF(VERBOSE),
__BF(DRYRUN),
__BF(RETRY_WITH_DEC),
__BF(WATCHDOG_ENABLE),
__BF(WATCHDOG_TIMEOUT_FROM_STATE),
};
#undef __BF
#define pr(verbose, format, args...) \
({ \
(verbose) ? pr_info((format), ##args) : 0; \
})
void _pr_flags(struct bootstate *bootstate, unsigned flags)
{
int i;
pr_info("%s: flags=0x%08x\n", bootstate->name, flags);
for (i = 0; i < ARRAY_SIZE(bootstate_flags_str); i++) {
if (flags & (1 << i))
pr_info("%s: -> %s\n", bootstate->name,
bootstate_flags_str[i]);
}
}
#define pr_flags(verbose, bootstate, flags) \
({ \
(verbose) ? _pr_flags(bootstate, flags) : 0; \
})
/*
* bootstate_get_target - create a new state instance from a device_node
*
* @bootstate the bootstate instance to work in
* @flags supported flags:
* BOOTCHOOSER_FLAG_VERBOSE
* BOOTCHOOSER_FLAG_ATTEMPTS_KEEP
* BOOTCHOOSER_FLAG_ATTEMPTS_DEC
* BOOTCHOOSER_FLAG_ATTEMPTS_RESET
* BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS
* @target_out a string to the choosen boot target is returned via
* this paramater
*/
int bootstate_get_target(struct bootstate *bootstate, unsigned flags, char **target_out)
{
struct bootstate_target *target;
int ret;
bool found = false;
bool v = flags & BOOTCHOOSER_FLAG_VERBOSE;
pr_flags(v, bootstate, flags);
ret = bootstate_load(bootstate);
if (ret)
return ret;
if (flags & BOOTCHOOSER_FLAG_ATTEMPTS_RESET) {
list_for_each_entry(target, &bootstate->targets, list) {
if (target->priority == 0)
continue;
target->remaining_attempts = target->default_attempts;
bootstate->dirty = true;
pr(v, "%s: target: name=%s setting rem to %d due to %s\n",
bootstate->name, target->name, target->default_attempts,
bootstate_flags_str[__BOOTCHOOSER_FLAG_ATTEMPTS_RESET_SHIFT]);
}
pr(v, "%s: --------\n", bootstate->name);
}
list_for_each_entry(target, &bootstate->targets, list) {
pr_target(bootstate, target);
if (found)
continue;
if (target->priority == 0) {
pr(v, "%s: name=%s prio=%d - trying next\n",
bootstate->name, target->name, target->priority);
continue;
}
if (target->remaining_attempts == 0) {
pr(v, "%s: name=%s remaining attempts == 0 - trying next\n",
bootstate->name, target->name);
continue;
}
if (flags & BOOTCHOOSER_FLAG_ATTEMPTS_DEC) {
bootstate->dirty = true;
target->remaining_attempts--;
pr(v, "%s: name=%s decrementing remaining_attempts to %d due to %s\n",
bootstate->name, target->name,
target->remaining_attempts,
bootstate_flags_str[__BOOTCHOOSER_FLAG_ATTEMPTS_DEC_SHIFT]);
if ((target->remaining_attempts == 0) &&
(flags & BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS)) {
target->priority = 0;
pr(v, "%s: name=%s deactivating target (setting priority = 0) due to %s\n",
bootstate->name, target->name,
bootstate_flags_str[__BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS_SHIFT]);
}
}
found = true;
*target_out = strdup(target->name);
pr_debug("%s: selected target '%s'\n", __func__, target->name);
if (!v)
goto out;
pr(v, "%s: --- other bootsources ---\n", bootstate->name);
}
out:
bootstate_save(bootstate);
if (!found)
return -ENOENT;
return 0;
}
int bootstate_bootchooser(char *name, unsigned flags, unsigned timeout)
{
struct bootstate *bootstate;
bool v = flags & BOOTCHOOSER_FLAG_VERBOSE;
char *target;
int ret;
if (!name)
name = "bootstate";
bootstate = bootstate_by_name(name);
if (!bootstate)
return -ENOENT;
if (flags & BOOTCHOOSER_FLAG_WATCHDOG_ENABLE) {
if (flags & BOOTCHOOSER_FLAG_WATCHDOG_TIMEOUT_FROM_STATE) {
ret = bootstate_variable_read_u32(bootstate, "watchdog_timeout",
&timeout);
if (ret)
return ret;
}
if (timeout != 0) {
pr(v, "%s: starting watchdog with timeout=%ds\n",
__func__, timeout);
ret = watchdog_set_timeout(timeout);
if (ret)
return ret;
}
}
while (true) {
char *cmd;
ret = bootstate_get_target(bootstate, flags, &target);
if (ret)
return ret;
cmd = asprintf("boot %s", target);
free(target);
pr_info("%srunning: %s...\n",
flags & BOOTCHOOSER_FLAG_DRYRUN ? "not " : "", cmd);
if (!(flags & BOOTCHOOSER_FLAG_DRYRUN))
ret = run_command(cmd);
free(cmd);
if (flags & BOOTCHOOSER_FLAG_RETRY_WITH_DEC) {
flags |= BOOTCHOOSER_FLAG_ATTEMPTS_DEC;
flags &= ~(BOOTCHOOSER_FLAG_ATTEMPTS_RESET |
BOOTCHOOSER_FLAG_ATTEMPTS_KEEP);
continue;
}
return ret;
}
return -ENOENT;
}

View File

@ -19,4 +19,7 @@ config STATE_DRV
tristate "state driver"
depends on STATE
config BOOTSTATE_DRV
tristate "bootstate driver"
endmenu

View File

@ -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

68
drivers/misc/bootstate.c Normal file
View File

@ -0,0 +1,68 @@
/*
* Copyright (C) 2013 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2015 Marc Kleine-Budde <mkl@pengutronix.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <bootstate.h>
#include <driver.h>
#include <init.h>
#include <malloc.h>
#include <of.h>
#include <string.h>
#include <linux/err.h>
static int bootstate_probe(struct device_d *dev)
{
struct device_node *np = dev->device_node;
struct bootstate *bootstate;
const char *alias;
const char *backend_type = NULL;
int ret;
if (!np)
return -EINVAL;
alias = of_alias_get(np);
if (!alias)
alias = "bootstate";
bootstate = bootstate_new_from_node(alias, np);
if (IS_ERR(bootstate))
return PTR_ERR(bootstate);
of_property_read_string(np, "backend-type", &backend_type);
if (!strcmp(backend_type, "state"))
ret = bootstate_backend_state(bootstate, np);
else if (!strcmp(backend_type, "nv"))
ret = bootstate_backend_nv(bootstate);
else
return -EINVAL;
return ret;
}
static __maybe_unused struct of_device_id bootstate_ids[] = {
{
.compatible = "barebox,bootstate",
}, {
/* sentinel */
}
};
static struct driver_d bootstate_driver = {
.name = "bootstate",
.probe = bootstate_probe,
.of_compatible = DRV_OF_COMPAT(bootstate_ids),
};
device_platform_driver(bootstate_driver);

38
include/bootstate.h Normal file
View File

@ -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 */