9
0
Fork 0
barebox/common/state/state.c

595 lines
14 KiB
C

/*
* Copyright (C) 2012-2014 Pengutronix, Jan Luebbe <j.luebbe@pengutronix.de>
* Copyright (C) 2013-2014 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2015 Pengutronix, 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.
*/
#include <asm-generic/ioctl.h>
#include <common.h>
#include <digest.h>
#include <errno.h>
#include <fs.h>
#include <crc.h>
#include <init.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/mtd/mtd-abi.h>
#include <malloc.h>
#include <state.h>
#include <libbb.h>
#include "state.h"
/* list of all registered state instances */
static LIST_HEAD(state_list);
static struct state *state_new(const char *name)
{
struct state *state;
int ret;
state = xzalloc(sizeof(*state));
safe_strncpy(state->dev.name, name, MAX_DRIVER_NAME);
state->name = state->dev.name;
state->dev.id = DEVICE_ID_SINGLE;
INIT_LIST_HEAD(&state->variables);
ret = register_device(&state->dev);
if (ret) {
pr_err("Failed to register state device %s, %d\n", name, ret);
free(state);
return ERR_PTR(ret);
}
state->dirty = 1;
dev_add_param_bool(&state->dev, "dirty", NULL, NULL, &state->dirty,
NULL);
state->save_on_shutdown = 1;
dev_add_param_bool(&state->dev, "save_on_shutdown", NULL, NULL,
&state->save_on_shutdown, NULL);
list_add_tail(&state->list, &state_list);
return state;
}
static int state_convert_node_variable(struct state *state,
struct device_node *node,
struct device_node *parent,
const char *parent_name,
enum state_convert conv)
{
const struct variable_type *vtype;
struct device_node *child;
struct device_node *new_node = NULL;
struct state_variable *sv;
const char *type_name;
char *short_name, *name, *indexs;
unsigned int start_size[2];
int ret;
/* strip trailing @<ADDRESS> */
short_name = xstrdup(node->name);
indexs = strchr(short_name, '@');
if (indexs)
*indexs = 0;
/* construct full name */
name = basprintf("%s%s%s", parent_name, parent_name[0] ? "." : "",
short_name);
free(short_name);
if ((conv == STATE_CONVERT_TO_NODE) || (conv == STATE_CONVERT_FIXUP))
new_node = of_new_node(parent, node->name);
for_each_child_of_node(node, child) {
ret = state_convert_node_variable(state, child, new_node, name,
conv);
if (ret)
goto out_free;
}
/* parents are allowed to have no type */
ret = of_property_read_string(node, "type", &type_name);
if (!list_empty(&node->children) && ret == -EINVAL) {
if (conv == STATE_CONVERT_FIXUP) {
ret = of_property_write_u32(new_node, "#address-cells",
1);
if (ret)
goto out_free;
ret = of_property_write_u32(new_node, "#size-cells", 1);
if (ret)
goto out_free;
}
ret = 0;
goto out_free;
} else if (ret) {
goto out_free;
}
vtype = state_find_type_by_name(type_name);
if (!vtype) {
ret = -ENOENT;
goto out_free;
}
if (conv == STATE_CONVERT_FROM_NODE_CREATE) {
sv = vtype->create(state, name, node);
if (IS_ERR(sv)) {
ret = PTR_ERR(sv);
dev_err(&state->dev, "failed to create %s: %s\n", name,
strerror(-ret));
goto out_free;
}
ret = of_property_read_u32_array(node, "reg", start_size,
ARRAY_SIZE(start_size));
if (ret) {
dev_err(&state->dev, "%s: reg property not found\n",
name);
goto out_free;
}
if (start_size[1] != sv->size) {
dev_err(&state->dev,
"%s: size mismatch: type=%s(size=%u) size=%u\n",
name, type_name, sv->size, start_size[1]);
ret = -EOVERFLOW;
goto out_free;
}
sv->name = name;
sv->start = start_size[0];
sv->type = vtype->type;
state_add_var(state, sv);
} else {
sv = state_find_var(state, name);
if (IS_ERR(sv)) {
/* we ignore this error */
dev_dbg(&state->dev, "no such variable: %s: %s\n", name,
strerror(-ret));
ret = 0;
goto out_free;
}
free(name);
if ((conv == STATE_CONVERT_TO_NODE)
|| (conv == STATE_CONVERT_FIXUP)) {
ret = of_set_property(new_node, "type",
vtype->type_name,
strlen(vtype->type_name) + 1, 1);
if (ret)
goto out;
start_size[0] = sv->start;
start_size[1] = sv->size;
ret = of_property_write_u32_array(new_node, "reg",
start_size,
ARRAY_SIZE
(start_size));
if (ret)
goto out;
}
}
if ((conv == STATE_CONVERT_TO_NODE) || (conv == STATE_CONVERT_FIXUP))
ret = vtype->export(sv, new_node, conv);
else
ret = vtype->import(sv, node);
if (ret)
goto out;
return 0;
out_free:free(name);
out: return ret;
}
struct device_node *state_to_node(struct state *state,
struct device_node *parent,
enum state_convert conv)
{
struct device_node *child;
struct device_node *root, *state_root;
int ret;
state_root = of_find_node_by_path(state->of_path);
if (!state_root)
return ERR_PTR(-ENODEV);
root = of_new_node(parent, state_root->name);
ret = of_property_write_u32(root, "magic", state->magic);
if (ret)
goto out;
for_each_child_of_node(state_root, child) {
ret = state_convert_node_variable(state, child, root, "", conv);
if (ret)
goto out;
}
return root;
out: of_delete_node(root);
return ERR_PTR(ret);
}
int state_from_node(struct state *state, struct device_node *node, bool create)
{
struct device_node *child;
enum state_convert conv;
int ret;
uint32_t magic;
ret = of_property_read_u32(node, "magic", &magic);
if (ret)
return ret;
if (create) {
conv = STATE_CONVERT_FROM_NODE_CREATE;
state->of_path = xstrdup(node->full_name);
state->magic = magic;
} else {
conv = STATE_CONVERT_FROM_NODE;
if (state->magic && state->magic != magic) {
dev_err(&state->dev,
"invalid magic 0x%08x, should be 0x%08x\n",
magic, state->magic);
return -EINVAL;
}
}
for_each_child_of_node(node, child) {
ret = state_convert_node_variable(state, child, NULL, "", conv);
if (ret)
return ret;
}
/* check for overlapping variables */
if (create) {
const struct state_variable *sv;
/* start with second entry */
sv = list_first_entry(&state->variables, struct state_variable,
list);
list_for_each_entry_continue(sv, &state->variables, list) {
const struct state_variable *last_sv;
last_sv = list_last_entry(&sv->list,
struct state_variable, list);
if ((last_sv->start + last_sv->size - 1) < sv->start)
continue;
dev_err(&state->dev,
"ERROR: Conflicting variable position between: "
"%s (0x%02x..0x%02x) and %s (0x%02x..0x%02x)\n",
last_sv->name, last_sv->start,
last_sv->start + last_sv->size - 1,
sv->name, sv->start, sv->start + sv->size - 1);
ret |= -EINVAL;
}
}
return ret;
}
static int of_state_fixup(struct device_node *root, void *ctx)
{
struct state *state = ctx;
const char *compatible = "barebox,state";
struct device_node *new_node, *node, *parent, *backend_node;
struct property *p;
int ret;
phandle phandle;
node = of_find_node_by_path_from(root, state->of_path);
if (node) {
/* replace existing node - it will be deleted later */
parent = node->parent;
} else {
char *of_path, *c;
/* look for parent, remove last '/' from path */
of_path = xstrdup(state->of_path);
c = strrchr(of_path, '/');
if (!c)
return -ENODEV;
*c = '\0';
parent = of_find_node_by_path_from(root, of_path);
if (!parent)
parent = root;
free(of_path);
}
/* serialize variable definitions */
new_node = state_to_node(state, parent, STATE_CONVERT_FIXUP);
if (IS_ERR(new_node))
return PTR_ERR(new_node);
/* compatible */
p = of_new_property(new_node, "compatible", compatible,
strlen(compatible) + 1);
if (!p) {
ret = -ENOMEM;
goto out;
}
/* backend-type */
if (!state->backend.format) {
ret = -ENODEV;
goto out;
}
p = of_new_property(new_node, "backend-type",
state->backend.format->name,
strlen(state->backend.format->name) + 1);
if (!p) {
ret = -ENOMEM;
goto out;
}
/* backend phandle */
backend_node = of_find_node_by_path_from(root, state->backend.of_path);
if (!backend_node) {
ret = -ENODEV;
goto out;
}
phandle = of_node_create_phandle(backend_node);
ret = of_property_write_u32(new_node, "backend", phandle);
if (ret)
goto out;
if (!strcmp("raw", state->backend.format->name)) {
struct digest *digest =
state_backend_format_raw_get_digest(state->backend.format);
if (digest) {
p = of_new_property(new_node, "algo",
digest_name(digest),
strlen(digest_name(digest)) + 1);
if (!p) {
ret = -ENOMEM;
goto out;
}
}
}
if (state->backend.storage.name) {
p = of_new_property(new_node, "backend-storage-type",
state->backend.storage.name,
strlen(state->backend.storage.name) + 1);
if (!p) {
ret = -ENOMEM;
goto out;
}
}
if (state->backend.storage.stridesize) {
ret = of_property_write_u32(new_node, "backend-stridesize",
state->backend.storage.stridesize);
if (ret)
goto out;
}
/* address-cells + size-cells */
ret = of_property_write_u32(new_node, "#address-cells", 1);
if (ret)
goto out;
ret = of_property_write_u32(new_node, "#size-cells", 1);
if (ret)
goto out;
/* delete existing node */
if (node)
of_delete_node(node);
return 0;
out: of_delete_node(new_node);
return ret;
}
void state_release(struct state *state)
{
of_unregister_fixup(of_state_fixup, state);
list_del(&state->list);
unregister_device(&state->dev);
state_backend_free(&state->backend);
free(state->of_path);
free(state);
}
/*
* state_new_from_node - create a new state instance from a device_node
*
* @node The device_node describing the new state instance
* @path Path to the backend device. If NULL the path is constructed
* using the path in the backend property of the DT.
* @offset Offset in the device path. May be 0 to start at the beginning.
* @max_size Maximum size of the area used. This may be 0 to use the full
* size.
* @readonly This is a read-only state. Note that with this option set,
* there are no repairs done.
*/
struct state *state_new_from_node(struct device_node *node, char *path,
off_t offset, size_t max_size, bool readonly)
{
struct state *state;
int ret = 0;
int len;
const char *backend_type;
const char *storage_type;
const char *of_path;
const char *alias;
uint32_t stridesize;
alias = of_alias_get(node);
if (!alias)
alias = node->name;
state = state_new(alias);
if (IS_ERR(state))
return state;
of_path = of_get_property(node, "backend", &len);
if (!of_path) {
ret = -ENODEV;
goto out_release_state;
}
if (!path) {
/* guess if of_path is a path, not a phandle */
if (of_path[0] == '/' && len > 1) {
ret = of_find_path(node, "backend", &path, 0);
} else {
struct device_node *partition_node;
partition_node = of_parse_phandle(node, "backend", 0);
if (!partition_node)
goto out_release_state;
of_path = partition_node->full_name;
ret = of_find_path_by_node(partition_node, &path, 0);
}
if (ret) {
if (ret != -EPROBE_DEFER)
dev_err(&state->dev, "state failed to parse path to backend: %s\n",
strerror(-ret));
goto out_release_state;
}
}
ret = of_property_read_string(node, "backend-type", &backend_type);
if (ret) {
goto out_release_state;
}
ret = of_property_read_u32(node, "backend-stridesize", &stridesize);
if (ret) {
stridesize = 0;
}
ret = of_property_read_string(node, "backend-storage-type",
&storage_type);
if (ret) {
storage_type = NULL;
dev_info(&state->dev, "No backend-storage-type found, using default.\n");
}
ret = state_backend_init(&state->backend, &state->dev, node,
backend_type, path, alias, of_path, offset,
max_size, stridesize, storage_type);
if (ret)
goto out_release_state;
if (readonly)
state_backend_set_readonly(&state->backend);
ret = state_from_node(state, node, 1);
if (ret) {
goto out_release_state;
}
ret = of_register_fixup(of_state_fixup, state);
if (ret) {
goto out_release_state;
}
ret = state_load(state);
if (ret) {
dev_warn(&state->dev, "Failed to load persistent state, continuing with defaults, %d\n", ret);
}
dev_info(&state->dev, "New state registered '%s'\n", alias);
return state;
out_release_state:
state_release(state);
return ERR_PTR(ret);
}
/*
* state_by_name - find a state instance by name
*
* @name The name of the state instance
*/
struct state *state_by_name(const char *name)
{
struct state *state;
list_for_each_entry(state, &state_list, list) {
if (!strcmp(name, state->name))
return state;
}
return NULL;
}
/*
* state_by_node - find a state instance by of node
*
* @node The of node of the state intance
*/
struct state *state_by_node(const struct device_node *node)
{
struct state *state;
list_for_each_entry(state, &state_list, list) {
if (!strcmp(state->of_path, node->full_name))
return state;
}
return NULL;
}
int state_get_name(const struct state *state, char const **name)
{
*name = xstrdup(state->name);
return 0;
}
void state_info(void)
{
struct state *state;
printf("registered state instances:\n");
list_for_each_entry(state, &state_list, list) {
printf("%-20s ", state->name);
if (state->backend.format)
printf("(backend: %s, path: %s)\n",
state->backend.format->name,
state->backend.of_path);
else
printf("(no backend)\n");
}
}
static void state_shutdown(void)
{
struct state *state;
list_for_each_entry(state, &state_list, list) {
if (state->save_on_shutdown)
state_save(state);
}
}
predevshutdown_exitcall(state_shutdown);