summaryrefslogtreecommitdiffstats
path: root/common/bootchooser.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/bootchooser.c')
-rw-r--r--common/bootchooser.c928
1 files changed, 928 insertions, 0 deletions
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");