929 lines
22 KiB
C
929 lines
22 KiB
C
/*
|
|
* 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");
|