diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2016-10-10 08:31:06 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2016-10-10 08:31:06 +0200 |
commit | c80ba33cbe1eb2331a0544d0f553d73e091bb322 (patch) | |
tree | a2f6af60504bdef24872470cd57a28e007520cd2 /common | |
parent | c5fc5de3fe5c0ab41d1b81ee5bf24ea8fedfe307 (diff) | |
parent | cc532a3f733718526898c14f8137118b7d76a5bc (diff) | |
download | barebox-c80ba33cbe1eb2331a0544d0f553d73e091bb322.tar.gz barebox-c80ba33cbe1eb2331a0544d0f553d73e091bb322.tar.xz |
Merge branch 'for-next/bootchooser'
Diffstat (limited to 'common')
-rw-r--r-- | common/Kconfig | 6 | ||||
-rw-r--r-- | common/Makefile | 1 | ||||
-rw-r--r-- | common/boot.c | 7 | ||||
-rw-r--r-- | common/bootchooser.c | 928 | ||||
-rw-r--r-- | common/globalvar.c | 266 |
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); |