/* * Copyright (C) 2012 Jan Luebbe * Copyright (C) 2015 Marc Kleine-Budde * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* list of all registered bootstate instances */ static LIST_HEAD(bootstate_list); struct state_backend; struct bootstate { struct device_d dev; const char *name; struct list_head list; struct list_head targets; struct list_head targets_unsorted; struct bootstate_backend *backend; bool dirty; }; struct bootstate_backend { int (*load)(struct bootstate_backend *backend, struct bootstate *bootstate); int (*save)(struct bootstate_backend *backend, struct bootstate *bootstate); const char *name; const char *path; }; struct bootstate_target { struct list_head list; struct list_head list_unsorted; /* state */ unsigned int priority; unsigned int remaining_attempts; bool ok; /* spec */ const char *name; unsigned int default_attempts; }; static void pr_target(struct bootstate *bootstate, struct bootstate_target *target) { printf("%s: target: name=%s prio=%u, ok=%d, rem=%u, def=%u\n", bootstate->name, target->name, target->priority, target->ok, target->remaining_attempts, target->default_attempts); } static struct bootstate *bootstate_new(const char *name) { struct bootstate *bootstate; int ret; bootstate = xzalloc(sizeof(*bootstate)); safe_strncpy(bootstate->dev.name, name, MAX_DRIVER_NAME); bootstate->name = bootstate->dev.name; bootstate->dev.id = DEVICE_ID_DYNAMIC; INIT_LIST_HEAD(&bootstate->targets); INIT_LIST_HEAD(&bootstate->targets_unsorted); ret = register_device(&bootstate->dev); if (ret) { free(bootstate); return ERR_PTR(ret); } list_add_tail(&bootstate->list, &bootstate_list); return bootstate; } static void bootstate_release(struct bootstate *bootstate) { unregister_device(&bootstate->dev); free(bootstate); } static int bootstate_target_compare(struct list_head *a, struct list_head *b) { struct bootstate_target *bootstate_a = list_entry(a, struct bootstate_target, list); struct bootstate_target *bootstate_b = list_entry(b, struct bootstate_target, list); /* order descending */ return bootstate_a->priority >= bootstate_b->priority ? -1 : 1; } static void bootstate_target_add(struct bootstate *bootstate, struct bootstate_target *target) { list_del(&target->list); list_add_sort(&target->list, &bootstate->targets, bootstate_target_compare); } static int bootstate_variable_read_u32(const struct bootstate *bootstate, const char *name, uint32_t *out_val) { char *var; int ret; var = asprintf("%s.%s.%s", bootstate->backend->path, bootstate->name, name); ret = getenv_uint(var, out_val); free(var); return ret; } static int bootstate_backend_variable_read_target_u32(const struct bootstate_backend *backend, const struct bootstate *bootstate, const struct bootstate_target *target, const char *name, uint32_t *out_val) { char *var; int ret; var = asprintf("%s.%s.%s.%s", backend->path, bootstate->name, target->name, name); ret = getenv_uint(var, out_val); free(var); return ret; } static int bootstate_backend_variable_write_target_u32(const struct bootstate_backend *backend, const struct bootstate *bootstate, const struct bootstate_target *target, const char *name, uint32_t in_val) { char *var; char *val; int ret; var = asprintf("%s.%s.%s.%s", backend->path, bootstate->name, target->name, name); val = asprintf("%d", in_val); ret = setenv(var, val); free(val); free(var); return ret; } static int bootstate_variable_nv_init_u32(const struct bootstate_backend *backend, const struct bootstate *bootstate, const struct bootstate_target *target, const char *name) { char *var; int ret; var = asprintf("%s.%s.%s", bootstate->name, target->name, name); ret = nvvar_add(var, "0"); free(var); return ret; } static struct bootstate_target *bootstate_target_find(const struct bootstate *bootstate, const char *name) { struct bootstate_target *target; list_for_each_entry(target, &bootstate->targets, list) { if (!strcmp(target->name, name)) return target; } return ERR_PTR(-ENOENT); } static int bootstate_target_from_node(struct bootstate *bootstate, const struct device_node *node, bool create) { struct bootstate_target *target; char *name, *indexs; int ret; name = xstrdup(node->name); indexs = strchr(name, '@'); if (indexs) *indexs++ = 0; if (create) { /* create*/ target = xzalloc(sizeof(*target)); target->name = xstrdup(name); list_add_tail(&target->list, &bootstate->targets); list_add_tail(&target->list_unsorted, &bootstate->targets_unsorted); } else { target = bootstate_target_find(bootstate, name); if (IS_ERR(target)) { int ret = PTR_ERR(target); pr_err("no such boot target: %s: %s\n", name, strerror(-ret)); return ret; } } /* init */ ret = of_property_read_u32(node, "default_attempts", &target->default_attempts); if (ret) return ret; free(name); return 0; } static int bootstate_from_node(struct bootstate *bootstate, const struct device_node *node, bool create) { struct device_node *child; int ret; for_each_child_of_node(node, child) { ret = bootstate_target_from_node(bootstate, child, create); if (ret) return ret; } return 0; } static int bootstate_backend_load_one(const struct bootstate_backend *backend, const struct bootstate *bootstate, struct bootstate_target *target) { uint32_t tmp; int ret; ret = bootstate_backend_variable_read_target_u32(backend, bootstate, target, "remaining_attempts", &target->remaining_attempts); if (ret) return ret; ret = bootstate_backend_variable_read_target_u32(backend, bootstate, target, "priority", &target->priority); if (ret) return ret; ret = bootstate_backend_variable_read_target_u32(backend, bootstate, target, "ok", &tmp); if (ret) return ret; target->ok = !!tmp; return ret; } static int bootstate_backend_load(struct bootstate_backend *backend, struct bootstate *bootstate) { struct bootstate_target *target; int ret; list_for_each_entry(target, &bootstate->targets_unsorted, list_unsorted) { ret = bootstate_backend_load_one(backend, bootstate, target); if (ret) return ret; bootstate_target_add(bootstate, target); } return 0; } static int bootstate_backend_save_one(const struct bootstate_backend *backend, const struct bootstate *bootstate, struct bootstate_target *target) { int ret; ret = bootstate_backend_variable_write_target_u32(backend, bootstate, target, "remaining_attempts", target->remaining_attempts); if (ret) return ret; ret = bootstate_backend_variable_write_target_u32(backend, bootstate, target, "priority", target->priority); if (ret) return ret; ret = bootstate_backend_variable_write_target_u32(backend, bootstate, target, "ok", target->ok); if (ret) return ret; return 0; } static int bootstate_backend_save(const struct bootstate_backend *backend, const struct bootstate *bootstate) { struct bootstate_target *target; int ret; list_for_each_entry(target, &bootstate->targets, list) { ret = bootstate_backend_save_one(backend, bootstate, target); if (ret) return ret; } return 0; } static int bootstate_backend_nv_init_one(const struct bootstate_backend *backend, const struct bootstate *bootstate, struct bootstate_target *target) { int ret; ret = bootstate_variable_nv_init_u32(backend, bootstate, target, "remaining_attempts"); if (ret) return ret; ret = bootstate_variable_nv_init_u32(backend, bootstate, target, "priority"); if (ret) return ret; ret = bootstate_variable_nv_init_u32(backend, bootstate, target, "ok"); if (ret) return ret; return 0; } static int bootstate_backend_nv_init(struct bootstate_backend *backend, struct bootstate *bootstate) { struct bootstate_target *target; int ret; list_for_each_entry(target, &bootstate->targets_unsorted, list_unsorted) { ret = bootstate_backend_nv_init_one(backend, bootstate, target); if (ret) return ret; } return 0; } static int bootstate_backend_nv_save(struct bootstate_backend *backend, struct bootstate *bootstate) { int ret; ret = bootstate_backend_save(backend, bootstate); if (ret) return ret; return envfs_save(NULL, NULL, 0); } static int bootstate_backend_nv_load(struct bootstate_backend *backend, struct bootstate *bootstate) { return bootstate_backend_load(backend, bootstate); } struct bootstate_backend_nv { struct bootstate_backend backend; }; int bootstate_backend_nv(struct bootstate *bootstate) { struct bootstate_backend_nv *backend_nv; struct bootstate_backend *backend; if (bootstate->backend) return -EBUSY; backend_nv = xzalloc(sizeof(*backend_nv)); backend = &backend_nv->backend; backend->load = bootstate_backend_nv_load; backend->save = bootstate_backend_nv_save; backend->name = "nv"; backend->path = "nv"; bootstate->backend = backend; return bootstate_backend_nv_init(backend, bootstate); } struct bootstate_backend_state { struct bootstate_backend backend; struct state *state; }; static int bootstate_backend_state_save(struct bootstate_backend *backend, struct bootstate *bootstate) { struct bootstate_backend_state *backend_state = container_of(backend, struct bootstate_backend_state, backend); int ret; ret = bootstate_backend_save(backend, bootstate); if (ret) return ret; return state_save(backend_state->state); } static int bootstate_backend_state_load(struct bootstate_backend *backend, struct bootstate *bootstate) { return bootstate_backend_load(backend, bootstate); } int bootstate_backend_state(struct bootstate *bootstate, const struct device_node *node) { struct bootstate_backend_state *backend_state; struct bootstate_backend *backend; const struct device_node *state_node; if (bootstate->backend) return -EBUSY; backend_state = xzalloc(sizeof(*backend_state)); backend = &backend_state->backend; backend->load = bootstate_backend_state_load; backend->save = bootstate_backend_state_save; backend->name = "state"; bootstate->backend = backend; state_node = of_parse_phandle(node, "backend", 0); if (!state_node) return -EINVAL; backend_state->state = state_by_node(state_node); if (!backend_state->state) return -EINVAL; return state_get_name(backend_state->state, &backend->path); } /* * bootstate_new_from_node - create a new bootstate instance from a device_node * * @name The name of the new bootstate instance * @node The device_node describing the new bootstate instance */ struct bootstate *bootstate_new_from_node(const char *name, const struct device_node *node) { struct bootstate *bootstate; int ret; pr_debug("%s: node=%s, name=%s\n", __func__, node->full_name, name); bootstate = bootstate_new(name); if (!bootstate) return ERR_PTR(-EINVAL); ret = bootstate_from_node(bootstate, node, true); if (ret) { bootstate_release(bootstate); return ERR_PTR(ret); } return bootstate; } /* * bootstate_by_name - find a bootstate instance by name * * @name The name of the state instance */ struct bootstate *bootstate_by_name(const char *name) { struct bootstate *bs; list_for_each_entry(bs, &bootstate_list, list) { if (!strcmp(name, bs->name)) return bs; } return NULL; } /* * bootstate_load - load a bootstate from the backing store * * @bootstate The state instance to load */ static int bootstate_load(struct bootstate *bootstate) { int ret; if (!bootstate->backend) return -ENOSYS; ret = bootstate->backend->load(bootstate->backend, bootstate); if (ret) bootstate->dirty = 1; else bootstate->dirty = 0; return ret; } /* * bootstate_save - save a bootstate to the backing store * * @bootstate The bootstate instance to save */ static int bootstate_save(struct bootstate *bootstate) { int ret; if (!bootstate->dirty) return 0; if (!bootstate->backend) return -ENOSYS; ret = bootstate->backend->save(bootstate->backend, bootstate); if (ret) return ret; bootstate->dirty = 0; return 0; } void bootstate_info(void) { struct bootstate *bootstate; printf("registered bootstate instances:\n"); list_for_each_entry(bootstate, &bootstate_list, list) { printf("%-20s ", bootstate->name); printf("(backend: %s, path: %s)\n", bootstate->backend->name, bootstate->backend->path); } } #define __BF(arg) [__BOOTCHOOSER_FLAG_##arg##_SHIFT] = __stringify(arg) static const char * const bootstate_flags_str[] = { __BF(ATTEMPTS_KEEP), __BF(ATTEMPTS_DEC), __BF(ATTEMPTS_RESET), __BF(DEACTIVATE_ON_ZERO_ATTEMPTS), __BF(VERBOSE), __BF(DRYRUN), __BF(RETRY_WITH_DEC), __BF(WATCHDOG_ENABLE), __BF(WATCHDOG_TIMEOUT_FROM_STATE), }; #undef __BF #define pr(verbose, format, args...) \ ({ \ (verbose) ? pr_info((format), ##args) : 0; \ }) void _pr_flags(struct bootstate *bootstate, unsigned flags) { int i; pr_info("%s: flags=0x%08x\n", bootstate->name, flags); for (i = 0; i < ARRAY_SIZE(bootstate_flags_str); i++) { if (flags & (1 << i)) pr_info("%s: -> %s\n", bootstate->name, bootstate_flags_str[i]); } } #define pr_flags(verbose, bootstate, flags) \ ({ \ (verbose) ? _pr_flags(bootstate, flags) : 0; \ }) /* * bootstate_get_target - create a new state instance from a device_node * * @bootstate the bootstate instance to work in * @flags supported flags: * BOOTCHOOSER_FLAG_VERBOSE * BOOTCHOOSER_FLAG_ATTEMPTS_KEEP * BOOTCHOOSER_FLAG_ATTEMPTS_DEC * BOOTCHOOSER_FLAG_ATTEMPTS_RESET * BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS * @target_out a string to the choosen boot target is returned via * this paramater */ int bootstate_get_target(struct bootstate *bootstate, unsigned flags, char **target_out) { struct bootstate_target *target; int ret; bool found = false; bool v = flags & BOOTCHOOSER_FLAG_VERBOSE; pr_flags(v, bootstate, flags); ret = bootstate_load(bootstate); if (ret) return ret; if (flags & BOOTCHOOSER_FLAG_ATTEMPTS_RESET) { list_for_each_entry(target, &bootstate->targets, list) { if (target->priority == 0) continue; target->remaining_attempts = target->default_attempts; bootstate->dirty = true; pr(v, "%s: target: name=%s setting rem to %d due to %s\n", bootstate->name, target->name, target->default_attempts, bootstate_flags_str[__BOOTCHOOSER_FLAG_ATTEMPTS_RESET_SHIFT]); } pr(v, "%s: --------\n", bootstate->name); } list_for_each_entry(target, &bootstate->targets, list) { pr_target(bootstate, target); if (found) continue; if (target->priority == 0) { pr(v, "%s: name=%s prio=%d - trying next\n", bootstate->name, target->name, target->priority); continue; } if (target->remaining_attempts == 0) { pr(v, "%s: name=%s remaining attempts == 0 - trying next\n", bootstate->name, target->name); continue; } if (flags & BOOTCHOOSER_FLAG_ATTEMPTS_DEC) { bootstate->dirty = true; target->remaining_attempts--; pr(v, "%s: name=%s decrementing remaining_attempts to %d due to %s\n", bootstate->name, target->name, target->remaining_attempts, bootstate_flags_str[__BOOTCHOOSER_FLAG_ATTEMPTS_DEC_SHIFT]); if ((target->remaining_attempts == 0) && (flags & BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS)) { target->priority = 0; pr(v, "%s: name=%s deactivating target (setting priority = 0) due to %s\n", bootstate->name, target->name, bootstate_flags_str[__BOOTCHOOSER_FLAG_DEACTIVATE_ON_ZERO_ATTEMPTS_SHIFT]); } } found = true; *target_out = strdup(target->name); pr_debug("%s: selected target '%s'\n", __func__, target->name); if (!v) goto out; pr(v, "%s: --- other bootsources ---\n", bootstate->name); } out: bootstate_save(bootstate); if (!found) return -ENOENT; return 0; } int bootstate_bootchooser(char *name, unsigned flags, unsigned timeout) { struct bootstate *bootstate; bool v = flags & BOOTCHOOSER_FLAG_VERBOSE; char *target; int ret; if (!name) name = "bootstate"; bootstate = bootstate_by_name(name); if (!bootstate) return -ENOENT; if (flags & BOOTCHOOSER_FLAG_WATCHDOG_ENABLE) { if (flags & BOOTCHOOSER_FLAG_WATCHDOG_TIMEOUT_FROM_STATE) { ret = bootstate_variable_read_u32(bootstate, "watchdog_timeout", &timeout); if (ret) return ret; } if (timeout != 0) { pr(v, "%s: starting watchdog with timeout=%ds\n", __func__, timeout); ret = watchdog_set_timeout(timeout); if (ret) return ret; } } while (true) { char *cmd; ret = bootstate_get_target(bootstate, flags, &target); if (ret) return ret; cmd = asprintf("boot %s", target); free(target); pr_info("%srunning: %s...\n", flags & BOOTCHOOSER_FLAG_DRYRUN ? "not " : "", cmd); if (!(flags & BOOTCHOOSER_FLAG_DRYRUN)) ret = run_command(cmd); free(cmd); if (flags & BOOTCHOOSER_FLAG_RETRY_WITH_DEC) { flags |= BOOTCHOOSER_FLAG_ATTEMPTS_DEC; flags &= ~(BOOTCHOOSER_FLAG_ATTEMPTS_RESET | BOOTCHOOSER_FLAG_ATTEMPTS_KEEP); continue; } return ret; } return -ENOENT; }