summaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2016-10-10 08:31:06 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2016-10-10 08:31:06 +0200
commitc80ba33cbe1eb2331a0544d0f553d73e091bb322 (patch)
treea2f6af60504bdef24872470cd57a28e007520cd2 /common
parentc5fc5de3fe5c0ab41d1b81ee5bf24ea8fedfe307 (diff)
parentcc532a3f733718526898c14f8137118b7d76a5bc (diff)
downloadbarebox-c80ba33cbe1eb2331a0544d0f553d73e091bb322.tar.gz
barebox-c80ba33cbe1eb2331a0544d0f553d73e091bb322.tar.xz
Merge branch 'for-next/bootchooser'
Diffstat (limited to 'common')
-rw-r--r--common/Kconfig6
-rw-r--r--common/Makefile1
-rw-r--r--common/boot.c7
-rw-r--r--common/bootchooser.c928
-rw-r--r--common/globalvar.c266
5 files changed, 1138 insertions, 70 deletions
diff --git a/common/Kconfig b/common/Kconfig
index be056857f2..59742492d7 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -936,6 +936,12 @@ config STATE_CRYPTO
See Documentation/devicetree/bindings/barebox/barebox,state.rst
for more information.
+config BOOTCHOOSER
+ bool "bootchooser infrastructure"
+ select ENVIRONMENT_VARIABLES
+ select OFTREE
+ select PARAMETER
+
config RESET_SOURCE
bool "detect Reset cause"
depends on GLOBALVAR
diff --git a/common/Makefile b/common/Makefile
index 00bc0e8834..a36ae5e91f 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/
obj-$(CONFIG_RATP) += ratp.o
+obj-$(CONFIG_BOOTCHOOSER) += bootchooser.o
obj-$(CONFIG_UIMAGE) += image.o uimage.o
obj-$(CONFIG_FITIMAGE) += image-fit.o
obj-$(CONFIG_MENUTREE) += menutree.o
diff --git a/common/boot.c b/common/boot.c
index a971cb7a30..123b874b3c 100644
--- a/common/boot.c
+++ b/common/boot.c
@@ -10,6 +10,7 @@
*/
#include <environment.h>
+#include <bootchooser.h>
#include <globalvar.h>
#include <magicvar.h>
#include <watchdog.h>
@@ -274,6 +275,12 @@ int bootentry_create_from_name(struct bootentries *bootentries,
}
}
+ if (IS_ENABLED(CONFIG_BOOTCHOOSER) && !strcmp(name, "bootchooser")) {
+ ret = bootchooser_create_bootentry(bootentries);
+ if (ret > 0)
+ found += ret;
+ }
+
if (!found) {
char *path;
diff --git a/common/bootchooser.c b/common/bootchooser.c
new file mode 100644
index 0000000000..9c110f267e
--- /dev/null
+++ b/common/bootchooser.c
@@ -0,0 +1,928 @@
+/*
+ * 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.
+ */
+#define pr_fmt(fmt) "bootchooser: " fmt
+
+#include <bootchooser.h>
+#include <environment.h>
+#include <globalvar.h>
+#include <magicvar.h>
+#include <command.h>
+#include <libfile.h>
+#include <common.h>
+#include <malloc.h>
+#include <printk.h>
+#include <xfuncs.h>
+#include <envfs.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ioctl.h>
+#include <libbb.h>
+#include <state.h>
+#include <stdio.h>
+#include <init.h>
+#include <crc.h>
+#include <net.h>
+#include <fs.h>
+#include <reset_source.h>
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/err.h>
+
+#define BOOTCHOOSER_PREFIX "global.bootchooser"
+
+static char *available_targets;
+static char *state_prefix;
+static int global_default_attempts = 3;
+static int global_default_priority = 1;
+static int disable_on_zero_attempts;
+static int retry;
+static int last_boot_successful;
+
+struct bootchooser {
+ struct bootentry entry;
+ struct list_head targets;
+ struct bootchooser_target *last_chosen;
+
+ struct state *state;
+ char *state_prefix;
+
+ int verbose;
+ int dryrun;
+};
+
+struct bootchooser_target {
+ struct bootchooser *bootchooser;
+ struct list_head list;
+
+ /* state */
+ unsigned int priority;
+ unsigned int remaining_attempts;
+ int id;
+
+ /* spec */
+ char *name;
+ unsigned int default_attempts;
+ unsigned int default_priority;
+
+ char *boot;
+
+ char *prefix;
+ char *state_prefix;
+};
+
+enum reset_attempts {
+ RESET_ATTEMPTS_POWER_ON,
+ RESET_ATTEMPTS_ALL_ZERO,
+};
+
+static unsigned long reset_attempts;
+
+enum reset_priorities {
+ RESET_PRIORITIES_ALL_ZERO,
+};
+
+static unsigned long reset_priorities;
+
+static int bootchooser_target_ok(struct bootchooser_target *target, const char **reason)
+{
+ if (!target->priority) {
+ if (reason)
+ *reason = "Target disabled (priority = 0)";
+ return false;
+ }
+
+ if (!target->remaining_attempts) {
+ if (reason)
+ *reason = "remaining attempts = 0";
+ return false;
+ }
+
+ if (reason)
+ *reason = "target OK";
+
+ return true;
+}
+
+static void pr_target(struct bootchooser_target *target)
+{
+ const char *reason;
+ int ok;
+
+ ok = bootchooser_target_ok(target, &reason);
+
+ printf("%s\n"
+ " id: %u\n"
+ " priority: %u\n"
+ " default_priority: %u\n"
+ " remaining attempts: %u\n"
+ " default attempts: %u\n"
+ " boot: '%s'\n",
+ target->name, target->id, target->priority, target->default_priority,
+ target->remaining_attempts, target->default_attempts,
+ target->boot);
+ if (!ok)
+ printf(" disabled due to %s\n", reason);
+}
+
+static int pr_setenv(struct bootchooser *bc, const char *fmt, ...)
+{
+ va_list ap;
+ int ret = 0;
+ char *str, *val;
+ const char *oldval;
+
+ va_start(ap, fmt);
+ str = bvasprintf(fmt, ap);
+ va_end(ap);
+
+ if (!str)
+ return -ENOMEM;
+
+ val = strchr(str, '=');
+ if (!val) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ *val++ = '\0';
+
+ oldval = getenv(str);
+ if (!oldval || strcmp(oldval, val)) {
+ if (bc->state)
+ ret = setenv(str, val);
+ else
+ ret = nvvar_add(str, val);
+ }
+
+err:
+ free(str);
+
+ return ret;
+}
+
+static const char *pr_getenv(const char *fmt, ...)
+{
+ va_list ap;
+ char *str;
+ const char *val;
+
+ va_start(ap, fmt);
+ str = bvasprintf(fmt, ap);
+ va_end(ap);
+
+ if (!str)
+ return NULL;
+
+ val = getenv(str);
+
+ free(str);
+
+ return val;
+}
+
+static int getenv_u32(const char *prefix, const char *name, uint32_t *retval)
+{
+ char *str;
+ const char *val;
+
+ str = xasprintf("%s.%s", prefix, name);
+
+ val = getenv(str);
+
+ free(str);
+
+ if (!val)
+ return -ENOENT;
+
+ *retval = simple_strtoul(val, NULL, 0);
+
+ return 0;
+}
+
+static int bootchooser_target_compare(struct list_head *a, struct list_head *b)
+{
+ struct bootchooser_target *bootchooser_a =
+ list_entry(a, struct bootchooser_target, list);
+ struct bootchooser_target *bootchooser_b =
+ list_entry(b, struct bootchooser_target, list);
+
+ /* order with descending priority */
+ return bootchooser_a->priority >= bootchooser_b->priority ? -1 : 1;
+}
+
+/**
+ * bootchooser_target_new - Create a new bootchooser target
+ * @bc: The bootchooser
+ * @name: The name of the new target
+ *
+ * Parses the variables associated with @name, creates a bootchooser
+ * target from it and returns it.
+ */
+static struct bootchooser_target *bootchooser_target_new(struct bootchooser *bc,
+ const char *name)
+{
+ struct bootchooser_target *target = xzalloc(sizeof(*target));
+ const char *val;
+ int ret;
+
+ target->name = xstrdup(name);
+ target->prefix = basprintf("%s.%s", BOOTCHOOSER_PREFIX, name);
+ target->state_prefix = basprintf("%s.%s", bc->state_prefix, name);
+ target->default_attempts = global_default_attempts;
+ target->default_priority = global_default_priority;
+
+ getenv_u32(target->prefix, "default_priority",
+ &target->default_priority);
+ getenv_u32(target->prefix, "default_attempts",
+ &target->default_attempts);
+
+ ret = getenv_u32(target->state_prefix, "priority", &target->priority);
+ if (ret) {
+ pr_warn("Cannot read priority for target %s, using default %d\n",
+ target->name, target->default_priority);
+ target->priority = target->default_priority;
+ }
+
+ ret = getenv_u32(target->state_prefix, "remaining_attempts", &target->remaining_attempts);
+ if (ret) {
+ pr_warn("Cannot read remaining attempts for target %s, using default %d\n",
+ target->name, target->default_attempts);
+ target->remaining_attempts = target->default_attempts;
+ }
+
+ if (target->remaining_attempts && !target->priority) {
+ pr_warn("Disabled target %s has remaining attempts %d, setting to 0\n",
+ target->name, target->remaining_attempts);
+ target->remaining_attempts = 0;
+ }
+
+ val = pr_getenv("%s.boot", target->prefix);
+ if (!val)
+ val = target->name;
+ target->boot = xstrdup(val);
+
+ return target;
+}
+
+/**
+ * bootchooser_target_by_id - Return a target given its id
+ *
+ * Each target has an id, simply counted by the order they appear in
+ * global.bootchooser.targets. We start counting at one to leave 0
+ * for detection of uninitialized variables.
+ */
+static struct bootchooser_target *bootchooser_target_by_id(struct bootchooser *bc,
+ uint32_t id)
+{
+ struct bootchooser_target *target;
+
+ list_for_each_entry(target, &bc->targets, list)
+ if (target->id == id)
+ return target;
+
+ return NULL;
+}
+
+/**
+ * bootchooser_target_disable - Disable a bootchooser target
+ */
+static void bootchooser_target_disable(struct bootchooser_target *target)
+{
+ target->priority = 0;
+ target->remaining_attempts = 0;
+}
+
+/**
+ * bootchooser_target_enabled - test if a target is enabled
+ *
+ * Returns true if a target is enabled, false if it's not.
+ */
+static bool bootchooser_target_enabled(struct bootchooser_target *target)
+{
+ return target->priority != 0;
+}
+
+/**
+ * bootchooser_reset_attempts - reset remaining attempts of targets
+ *
+ * Reset the remaining_attempts counter of all enabled targets
+ * to their default values.
+ */
+static void bootchooser_reset_attempts(struct bootchooser *bc)
+{
+ struct bootchooser_target *target;
+
+ bootchooser_for_each_target(bc, target) {
+ if (bootchooser_target_enabled(target))
+ bootchooser_target_set_attempts(target, -1);
+ }
+}
+
+/**
+ * bootchooser_reset_priorities - reset priorities of targets
+ *
+ * Reset the priorities counter of all targets to their default
+ * values.
+ */
+static void bootchooser_reset_priorities(struct bootchooser *bc)
+{
+ struct bootchooser_target *target;
+
+ bootchooser_for_each_target(bc, target)
+ bootchooser_target_set_priority(target, -1);
+}
+
+/**
+ * bootchooser_get - get a bootchooser instance
+ *
+ * This evaluates the different globalvars and eventually state variables,
+ * creates a bootchooser instance from it and returns it.
+ */
+struct bootchooser *bootchooser_get(void)
+{
+ struct bootchooser *bc;
+ struct bootchooser_target *target;
+ char *targets, *str, *freep = NULL, *delim;
+ int ret = -EINVAL, id = 1;
+ uint32_t last_chosen;
+ static int attempts_resetted;
+
+ bc = xzalloc(sizeof(*bc));
+
+ if (*state_prefix) {
+ if (IS_ENABLED(CONFIG_STATE)) {
+ char *state_devname;
+
+ delim = strchr(state_prefix, '.');
+ if (!delim) {
+ pr_err("state_prefix '%s' has invalid format\n",
+ state_prefix);
+ goto err;
+ }
+ state_devname = xstrndup(state_prefix, delim - state_prefix);
+ bc->state_prefix = xstrdup(state_prefix);
+ bc->state = state_by_name(state_devname);
+ if (!bc->state) {
+ free(state_devname);
+ pr_err("Cannot get state '%s'\n",
+ state_devname);
+ ret = -ENODEV;
+ goto err;
+ }
+ free(state_devname);
+ } else {
+ pr_err("State disabled, cannot use nv.state_prefix=%s\n",
+ state_prefix);
+ ret = -ENODEV;
+ goto err;
+ }
+ } else {
+ bc->state_prefix = xstrdup("nv.bootchooser");
+ }
+
+ INIT_LIST_HEAD(&bc->targets);
+
+ freep = targets = xstrdup(available_targets);
+
+ while (1) {
+ str = strsep(&targets, " ");
+ if (!str || !*str)
+ break;
+
+ target = bootchooser_target_new(bc, str);
+ if (!IS_ERR(target)) {
+ target->id = id;
+ list_add_sort(&target->list, &bc->targets,
+ bootchooser_target_compare);
+ }
+
+ id++;
+ }
+
+ if (id == 1) {
+ pr_err("Target list $global.bootchooser.targets is empty\n");
+ goto err;
+ }
+
+ if (list_empty(&bc->targets)) {
+ pr_err("No targets could be initialized\n");
+ goto err;
+ }
+
+ free(freep);
+
+ if (test_bit(RESET_PRIORITIES_ALL_ZERO, &reset_priorities)) {
+ int priority = 0;
+
+ bootchooser_for_each_target(bc, target)
+ priority += target->priority;
+
+ if (!priority) {
+ pr_info("All targets disabled, re-enabling them\n");
+ bootchooser_reset_priorities(bc);
+ }
+ }
+
+ if (test_bit(RESET_ATTEMPTS_POWER_ON, &reset_attempts) &&
+ reset_source_get() == RESET_POR && !attempts_resetted) {
+ pr_info("Power-on Reset, resetting remaining attempts\n");
+ bootchooser_reset_attempts(bc);
+ attempts_resetted = 1;
+ }
+
+ if (test_bit(RESET_ATTEMPTS_ALL_ZERO, &reset_attempts)) {
+ int attempts = 0;
+
+ bootchooser_for_each_target(bc, target)
+ attempts += target->remaining_attempts;
+
+ if (!attempts) {
+ pr_info("All enabled targets have 0 remaining attempts, resetting them\n");
+ bootchooser_reset_attempts(bc);
+ }
+ }
+
+ ret = getenv_u32(bc->state_prefix, "last_chosen", &last_chosen);
+ if (!ret && last_chosen > 0) {
+ bc->last_chosen = bootchooser_target_by_id(bc, last_chosen);
+ if (!bc->last_chosen)
+ pr_warn("Last booted target with id %d does not exist\n", last_chosen);
+ }
+
+ if (bc->last_chosen && last_boot_successful)
+ bootchooser_target_set_attempts(bc->last_chosen, -1);
+
+ if (disable_on_zero_attempts) {
+ bootchooser_for_each_target(bc, target) {
+ if (!target->remaining_attempts) {
+ pr_info("target %s has 0 remaining attempts, disabling\n",
+ target->name);
+ bootchooser_target_disable(target);
+ }
+ }
+
+ }
+
+ return bc;
+
+err:
+ free(freep);
+ free(bc);
+
+ return ERR_PTR(ret);
+}
+
+/**
+ * bootchooser_save - save a bootchooser to the backing store
+ * @bc: The bootchooser instance to save
+ *
+ * Return: 0 for success, negative error code otherwise
+ */
+int bootchooser_save(struct bootchooser *bc)
+{
+ struct bootchooser_target *target;
+ int ret;
+
+ if (bc->last_chosen)
+ pr_setenv(bc, "%s.last_chosen=%d", bc->state_prefix,
+ bc->last_chosen->id);
+
+ list_for_each_entry(target, &bc->targets, list) {
+ ret = pr_setenv(bc, "%s.remaining_attempts=%d",
+ target->state_prefix,
+ target->remaining_attempts);
+ if (ret)
+ return ret;
+
+ ret = pr_setenv(bc, "%s.priority=%d",
+ target->state_prefix, target->priority);
+ if (ret)
+ return ret;
+ }
+
+ if (IS_ENABLED(CONFIG_STATE) && bc->state) {
+ ret = state_save(bc->state);
+ if (ret) {
+ pr_err("Cannot save state: %s\n", strerror(-ret));
+ return ret;
+ }
+ } else {
+ ret = nvvar_save();
+ if (ret) {
+ pr_err("Cannot save nv variables: %s\n", strerror(-ret));
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * bootchooser_put - release a bootchooser instance
+ * @bc: The bootchooser instance
+ *
+ * This releases a bootchooser instance and the memory associated with it.
+ */
+int bootchooser_put(struct bootchooser *bc)
+{
+ struct bootchooser_target *target, *tmp;
+ int ret;
+
+ ret = bootchooser_save(bc);
+ if (ret)
+ pr_err("Failed to save bootchooser state: %s\n", strerror(-ret));
+
+ list_for_each_entry_safe(target, tmp, &bc->targets, list) {
+ free(target->boot);
+ free(target->prefix);
+ free(target->state_prefix);
+ free(target->name);
+ free(target);
+ }
+
+ free(bc);
+
+ return ret;
+}
+
+/**
+ * bootchooser_info - Show information about a bootchooser instance
+ * @bc: The bootchooser
+ */
+void bootchooser_info(struct bootchooser *bc)
+{
+ struct bootchooser_target *target;
+ const char *reason;
+ int count = 0;
+
+ printf("Good targets (first will be booted next):\n");
+ list_for_each_entry(target, &bc->targets, list) {
+ if (bootchooser_target_ok(target, NULL)) {
+ count++;
+ pr_target(target);
+ }
+ }
+
+ if (!count)
+ printf("none\n");
+
+ count = 0;
+
+ printf("\nDisabled targets:\n");
+ list_for_each_entry(target, &bc->targets, list) {
+ if (!bootchooser_target_ok(target, &reason)) {
+ count++;
+ pr_target(target);
+ }
+ }
+
+ if (!count)
+ printf("none\n");
+
+ printf("\nlast booted target: %s\n", bc->last_chosen ?
+ bc->last_chosen->name : "unknown");
+}
+
+/**
+ * bootchooser_get_target - get the target that shall be booted next
+ * @bc: The bootchooser
+ *
+ * This is the heart of the bootchooser. This function selects the next
+ * target to boot and returns it. The remaining_attempts counter of the
+ * selected target is decreased and the bootchooser state is saved to the
+ * backend.
+ *
+ * Return: The next target
+ */
+struct bootchooser_target *bootchooser_get_target(struct bootchooser *bc)
+{
+ struct bootchooser_target *target;
+
+ list_for_each_entry(target, &bc->targets, list) {
+ if (bootchooser_target_ok(target, NULL))
+ goto found;
+ }
+
+ pr_err("No valid targets found:\n");
+ list_for_each_entry(target, &bc->targets, list)
+ pr_target(target);
+
+ return ERR_PTR(-ENOENT);
+
+found:
+ target->remaining_attempts--;
+
+ if (bc->verbose)
+ pr_info("name=%s decrementing remaining_attempts to %d\n",
+ target->name, target->remaining_attempts);
+
+ if (bc->verbose)
+ pr_info("selected target '%s', boot '%s'\n", target->name, target->boot);
+
+ bc->last_chosen = target;
+
+ bootchooser_save(bc);
+
+ return target;
+}
+
+/**
+ * bootchooser_target_name - get the name of a target
+ * @target: The target
+ *
+ * Given a bootchooser target this function returns its name.
+ *
+ * Return: The name of the target
+ */
+const char *bootchooser_target_name(struct bootchooser_target *target)
+{
+ return target->name;
+}
+
+/**
+ * bootchooser_target_by_name - get a target from name
+ * @bc: The bootchooser
+ * @name: The name of the target to retrieve
+ *
+ * Given a name this function returns the corresponding target.
+ *
+ * Return: The target if found, NULL otherwise
+ */
+struct bootchooser_target *bootchooser_target_by_name(struct bootchooser *bc,
+ const char *name)
+{
+ struct bootchooser_target *target;
+
+ bootchooser_for_each_target(bc, target)
+ if (!strcmp(target->name, name))
+ return target;
+ return NULL;
+}
+
+/**
+ * bootchooser_target_set_attempts - set remaining attempts of a target
+ * @target: The target to change
+ * @attempts: The number of attempts
+ *
+ * This sets the number of remaining attempts for a bootchooser target.
+ * If @attempts is < 0 then the remaining attempts is reset to the default
+ * value.
+ *
+ * Return: 0 for success, negative error code otherwise
+ */
+int bootchooser_target_set_attempts(struct bootchooser_target *target, int attempts)
+{
+ if (attempts >= 0)
+ target->remaining_attempts = attempts;
+ else
+ target->remaining_attempts = target->default_attempts;
+
+ return 0;
+}
+
+/**
+ * bootchooser_target_set_priority - set priority of a target
+ * @target: The target to change
+ * @priority: The priority
+ *
+ * This sets the priority of a bootchooser target. If @priority is < 0
+ * then the priority reset to the default value.
+ *
+ * Return: 0 for success, negative error code otherwise
+ */
+int bootchooser_target_set_priority(struct bootchooser_target *target, int priority)
+{
+ if (priority >= 0)
+ target->priority = priority;
+ else
+ target->priority = target->default_priority;
+
+ return 0;
+}
+
+/**
+ * bootchooser_target_first - get the first target from a bootchooser
+ * @bc: The bootchooser
+ *
+ * Gets the first target from a bootchooser, used for the bootchooser
+ * target iterator.
+ *
+ * Return: The first target or NULL if no target exists
+ */
+struct bootchooser_target *bootchooser_target_first(struct bootchooser *bc)
+{
+ return list_first_entry_or_null(&bc->targets,
+ struct bootchooser_target, list);
+}
+
+/**
+ * bootchooser_target_next - get the next target from a bootchooser
+ * @bc: The bootchooser
+ * @target: The current target
+ *
+ * Gets the next target from a bootchooser, used for the bootchooser
+ * target iterator.
+ *
+ * Return: The first target or NULL if no more targets exist
+ */
+struct bootchooser_target *bootchooser_target_next(struct bootchooser *bc,
+ struct bootchooser_target *target)
+{
+ struct list_head *next = target->list.next;
+
+ if (next == &bc->targets)
+ return NULL;
+
+ return list_entry(next, struct bootchooser_target, list);
+}
+
+/**
+ * bootchooser_last_boot_successful - tell that the last boot was successful
+ *
+ * This tells bootchooser that the last boot was successful.
+ */
+void bootchooser_last_boot_successful(void)
+{
+ last_boot_successful = true;
+}
+
+/**
+ * bootchooser_get_last_chosen - get the target which was chosen last time
+ * @bc: The bootchooser
+ *
+ * Bootchooser stores the id of the target which was last booted in
+ * <state_prefix>.last_chosen. This function returns the target associated
+ * with this id.
+ *
+ * Return: The target which was booted last time
+ */
+struct bootchooser_target *bootchooser_get_last_chosen(struct bootchooser *bc)
+{
+ if (!bc->last_chosen)
+ return ERR_PTR(-ENODEV);
+
+ return bc->last_chosen;
+}
+
+static int bootchooser_boot_one(struct bootchooser *bc, int *tryagain)
+{
+ char *system;
+ struct bootentries *entries;
+ struct bootentry *entry;
+ struct bootchooser_target *target;
+ int ret = 0;
+
+ entries = bootentries_alloc();
+
+ target = bootchooser_get_target(bc);
+ if (IS_ERR(target)) {
+ ret = PTR_ERR(target);
+ *tryagain = 0;
+ goto out;
+ }
+
+ system = basprintf("bootchooser.active=%s", target->name);
+ globalvar_add_simple("linux.bootargs.bootchooser", system);
+ free(system);
+
+ ret = bootentry_create_from_name(entries, target->boot);
+ if (ret <= 0) {
+ printf("Nothing bootable found on '%s'\n", target->boot);
+ *tryagain = 1;
+ ret = -ENODEV;
+ goto out;
+ }
+
+ last_boot_successful = false;
+
+ ret = -ENOENT;
+
+ bootentries_for_each_entry(entries, entry) {
+ ret = boot_entry(entry, bc->verbose, bc->dryrun);
+ if (!ret) {
+ *tryagain = 0;
+ goto out;
+ }
+ }
+
+ *tryagain = 1;
+out:
+ globalvar_set_match("linux.bootargs.bootchooser", NULL);
+
+ bootentries_free(entries);
+
+ return ret;
+}
+
+static int bootchooser_boot(struct bootentry *entry, int verbose, int dryrun)
+{
+ struct bootchooser *bc = container_of(entry, struct bootchooser,
+ entry);
+ int ret, tryagain;
+
+ bc->verbose = verbose;
+ bc->dryrun = dryrun;
+
+ do {
+ ret = bootchooser_boot_one(bc, &tryagain);
+
+ if (!retry)
+ break;
+ } while (tryagain);
+
+ return ret;
+}
+
+static void bootchooser_release(struct bootentry *entry)
+{
+ struct bootchooser *bc = container_of(entry, struct bootchooser,
+ entry);
+
+ bootchooser_put(bc);
+}
+
+/**
+ * bootchooser_create_bootentry - create a boot entry
+ * @entries: The list of bootentries
+ *
+ * This adds a bootchooser to the list of boot entries. Called
+ * by the 'boot' code.
+ *
+ * Return: The number of entries added to the list
+ */
+int bootchooser_create_bootentry(struct bootentries *entries)
+{
+ struct bootchooser *bc = bootchooser_get();
+
+ if (IS_ERR(bc))
+ return PTR_ERR(bc);
+
+ bc->entry.boot = bootchooser_boot;
+ bc->entry.release = bootchooser_release;
+ bc->entry.title = xstrdup("bootchooser");
+ bc->entry.description = xstrdup("bootchooser");
+
+ bootentries_add_entry(entries, &bc->entry);
+
+ return 1;
+}
+
+static const char * const reset_attempts_names[] = {
+ [RESET_ATTEMPTS_POWER_ON] = "power-on",
+ [RESET_ATTEMPTS_ALL_ZERO] = "all-zero",
+};
+
+static const char * const reset_priorities_names[] = {
+ [RESET_PRIORITIES_ALL_ZERO] = "all-zero",
+};
+
+static int bootchooser_init(void)
+{
+ state_prefix = xstrdup("");
+ available_targets = xstrdup("");
+
+ globalvar_add_simple_bool("bootchooser.disable_on_zero_attempts", &disable_on_zero_attempts);
+ globalvar_add_simple_bool("bootchooser.retry", &retry);
+ globalvar_add_simple_string("bootchooser.targets", &available_targets);
+ globalvar_add_simple_string("bootchooser.state_prefix", &state_prefix);
+ globalvar_add_simple_int("bootchooser.default_attempts", &global_default_attempts, "%u");
+ globalvar_add_simple_int("bootchooser.default_priority", &global_default_priority, "%u");
+ globalvar_add_simple_bitmask("bootchooser.reset_attempts", &reset_attempts,
+ reset_attempts_names, ARRAY_SIZE(reset_attempts_names));
+ globalvar_add_simple_bitmask("bootchooser.reset_priorities", &reset_priorities,
+ reset_priorities_names, ARRAY_SIZE(reset_priorities_names));
+ return 0;
+}
+device_initcall(bootchooser_init);
+
+BAREBOX_MAGICVAR_NAMED(global_bootchooser_disable_on_zero_attempts,
+ global.bootchooser.disable_on_zero_attempts,
+ "bootchooser: Disable target when remaining attempts counter reaches 0");
+BAREBOX_MAGICVAR_NAMED(global_bootchooser_retry,
+ global.bootchooser.retry,
+ "bootchooser: Try again when booting a target fails");
+BAREBOX_MAGICVAR_NAMED(global_bootchooser_targets,
+ global.bootchooser.targets,
+ "bootchooser: Space separated list of target names");
+BAREBOX_MAGICVAR_NAMED(global_bootchooser_default_attempts,
+ global.bootchooser.default_attempts,
+ "bootchooser: Default number of attempts for a target");
+BAREBOX_MAGICVAR_NAMED(global_bootchooser_default_priority,
+ global.bootchooser.default_priority,
+ "bootchooser: Default priority for a target");
+BAREBOX_MAGICVAR_NAMED(global_bootchooser_state_prefix,
+ global.bootchooser.state_prefix,
+ "bootchooser: state name prefix, empty for nv backend");
diff --git a/common/globalvar.c b/common/globalvar.c
index 9d6734854e..fb69db973a 100644
--- a/common/globalvar.c
+++ b/common/globalvar.c
@@ -36,6 +36,9 @@ int globalvar_add(const char *name,
{
struct param_d *param;
+ if (!strncmp(name, "global.", 7))
+ name += 7;
+
param = dev_add_param(&global_device, name, set, get, flags);
if (IS_ERR(param))
return PTR_ERR(param);
@@ -44,12 +47,14 @@ int globalvar_add(const char *name,
void globalvar_remove(const char *name)
{
- struct param_d *param = get_param_by_name(&global_device, name);
+ struct param_d *p, *tmp;
- if (!param)
- return;
+ list_for_each_entry_safe(p, tmp, &global_device.parameters, list) {
+ if (fnmatch(name, p->name, 0))
+ continue;
- dev_remove_param(param);
+ dev_remove_param(p);
+ }
}
static int __nv_save(const char *prefix, const char *name, const char *val)
@@ -177,34 +182,14 @@ static int nvvar_device_dispatch(const char *name, struct device_d **dev,
static int nv_set(struct device_d *dev, struct param_d *p, const char *val)
{
int ret;
- int devspace;
- struct device_d *rdev;
- const char *pname;
if (!val)
val = "";
- ret = nvvar_device_dispatch(p->name, &rdev, &pname);
- if (ret < 0)
+ ret = dev_set_param(&global_device, p->name, val);
+ if (ret)
return ret;
- devspace = ret;
-
- if (devspace) {
- if (rdev) {
- ret = dev_set_param(rdev, pname, val);
- if (ret) {
- pr_err("Cannot init param from nv: %s.%s=%s: %s\n",
- dev_name(rdev), pname, val, strerror(-ret));
- return ret;
- }
- }
- } else {
- ret = dev_set_param(&global_device, p->name, val);
- if (ret)
- return ret;
- }
-
free(p->value);
p->value = xstrdup(val);
@@ -229,54 +214,27 @@ static int nv_param_set(struct device_d *dev, struct param_d *p, const char *val
static int __nvvar_add(const char *name, const char *value)
{
- struct param_d *p, *gp;
+ struct param_d *p;
int ret;
- int devspace;
- struct device_d *dev;
- const char *pname;
if (!IS_ENABLED(CONFIG_NVVAR))
return -ENOSYS;
- ret = nvvar_device_dispatch(name, &dev, &pname);
- if (ret < 0)
- return ret;
-
- devspace = ret;
-
- gp = get_param_by_name(&nv_device, name);
- if (gp) {
- if (!devspace) {
- ret = dev_set_param(&global_device, name, value);
- if (ret)
- return ret;
- }
-
- ret = dev_set_param(&nv_device, name, value);
- if (ret)
- return ret;
-
- return 0;
+ /* Get param. If it doesn't exist yet, create it */
+ p = get_param_by_name(&nv_device, name);
+ if (!p) {
+ p = dev_add_param(&nv_device, name, nv_param_set, nv_param_get, 0);
+ if (IS_ERR(p))
+ return PTR_ERR(p);
}
- if (!devspace) {
- ret = globalvar_add_simple(name, value);
- if (ret && ret != -EEXIST)
- return ret;
- }
-
- p = dev_add_param(&nv_device, name, nv_param_set, nv_param_get, 0);
- if (IS_ERR(p))
- return PTR_ERR(p);
+ /* Create corresponding globalvar if it doesn't exist yet */
+ ret = globalvar_add_simple(name, value);
+ if (ret && ret != -EEXIST)
+ return ret;
- if (!value) {
- if (devspace) {
- if (dev)
- value = dev_get_param(dev, pname);
- } else {
- value = dev_get_param(&global_device, name);
- }
- }
+ if (!value)
+ value = dev_get_param(&global_device, name);
return nv_set(&nv_device, p, value);
}
@@ -285,6 +243,9 @@ int nvvar_add(const char *name, const char *value)
{
int ret;
+ if (!strncmp(name, "nv.", 3))
+ name += 3;
+
ret = __nvvar_add(name, value);
if (ret)
return ret;
@@ -418,6 +379,27 @@ void globalvar_set_match(const char *match, const char *val)
}
}
+int globalvar_simple_set(struct device_d *dev, struct param_d *p, const char *val)
+{
+ struct device_d *rdev;
+ const char *pname;
+ int ret;
+
+ ret = nvvar_device_dispatch(p->name, &rdev, &pname);
+ if (ret < 0)
+ return ret;
+
+ if (ret && rdev) {
+ ret = dev_set_param(rdev, pname, val);
+ if (ret)
+ pr_err("Cannot init param from global: %s.%s=%s: %s\n",
+ dev_name(rdev), pname, val, strerror(-ret));
+ }
+
+ /* Pass to the generic function we have overwritten */
+ return dev_param_set_generic(dev, p, val);
+}
+
/*
* globalvar_add_simple
*
@@ -425,11 +407,14 @@ void globalvar_set_match(const char *match, const char *val)
*/
int globalvar_add_simple(const char *name, const char *value)
{
- int ret;
+ struct param_d *param;
- ret = globalvar_add(name, NULL, NULL, 0);
- if (ret && ret != -EEXIST)
- return ret;
+ param = dev_add_param(&global_device, name, globalvar_simple_set, NULL,
+ PARAM_GLOBALVAR_UNQUALIFIED);
+ if (IS_ERR(param)) {
+ if (PTR_ERR(param) != -EEXIST)
+ return PTR_ERR(param);
+ }
if (!value)
return 0;
@@ -437,6 +422,147 @@ int globalvar_add_simple(const char *name, const char *value)
return dev_set_param(&global_device, name, value);
}
+static int globalvar_remove_unqualified(const char *name)
+{
+ struct param_d *p;
+
+ p = get_param_by_name(&global_device, name);
+ if (!p)
+ return 0;
+
+ if (!(p->flags & PARAM_GLOBALVAR_UNQUALIFIED))
+ return -EEXIST;
+
+ dev_remove_param(p);
+
+ return 0;
+}
+
+static void globalvar_nv_sync(const char *name)
+{
+ const char *val;
+
+ val = dev_get_param(&nv_device, name);
+ if (val)
+ dev_set_param(&global_device, name, val);
+}
+
+int globalvar_add_simple_string(const char *name, char **value)
+{
+ struct param_d *p;
+ int ret;
+
+ ret = globalvar_remove_unqualified(name);
+ if (ret)
+ return ret;
+
+ p = dev_add_param_string(&global_device, name, NULL, NULL,
+ value, NULL);
+
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+
+ globalvar_nv_sync(name);
+
+ return 0;
+}
+
+int globalvar_add_simple_int(const char *name, int *value,
+ const char *format)
+{
+ struct param_d *p;
+ int ret;
+
+ ret = globalvar_remove_unqualified(name);
+ if (ret)
+ return ret;
+
+ p = dev_add_param_int(&global_device, name, NULL, NULL,
+ value, format, NULL);
+
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+
+ globalvar_nv_sync(name);
+
+ return 0;
+}
+
+int globalvar_add_simple_bool(const char *name, int *value)
+{
+ struct param_d *p;
+ int ret;
+
+ ret = globalvar_remove_unqualified(name);
+ if (ret)
+ return ret;
+
+ p = dev_add_param_bool(&global_device, name, NULL, NULL,
+ value, NULL);
+
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+
+ globalvar_nv_sync(name);
+
+ return 0;
+}
+
+int globalvar_add_simple_enum(const char *name, int *value,
+ const char * const *names, int max)
+{
+ struct param_d *p;
+ int ret;
+
+ ret = globalvar_remove_unqualified(name);
+ if (ret)
+ return ret;
+
+ p = dev_add_param_enum(&global_device, name, NULL, NULL,
+ value, names, max, NULL);
+
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+
+ globalvar_nv_sync(name);
+
+ return 0;
+}
+
+int globalvar_add_simple_bitmask(const char *name, unsigned long *value,
+ const char * const *names, int max)
+{
+ struct param_d *p;
+
+ p = dev_add_param_bitmask(&global_device, name, NULL, NULL,
+ value, names, max, NULL);
+
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+
+ return 0;
+}
+
+int globalvar_add_simple_ip(const char *name, IPaddr_t *ip)
+{
+ struct param_d *p;
+ int ret;
+
+ ret = globalvar_remove_unqualified(name);
+ if (ret)
+ return ret;
+
+ p = dev_add_param_ip(&global_device, name, NULL, NULL,
+ ip, NULL);
+
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+
+ globalvar_nv_sync(name);
+
+ return 0;
+}
+
static int globalvar_init(void)
{
register_device(&global_device);