bootstate: add
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
This commit is contained in:
parent
42754c2345
commit
51f97717c8
|
@ -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.
|
|
@ -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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -19,4 +19,7 @@ config STATE_DRV
|
|||
tristate "state driver"
|
||||
depends on STATE
|
||||
|
||||
config BOOTSTATE_DRV
|
||||
tristate "bootstate driver"
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
|
@ -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 */
|
Loading…
Reference in New Issue