summaryrefslogtreecommitdiffstats
path: root/common/state/state.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/state/state.c')
-rw-r--r--common/state/state.c566
1 files changed, 566 insertions, 0 deletions
diff --git a/common/state/state.c b/common/state/state.c
new file mode 100644
index 0000000..9f4553d
--- /dev/null
+++ b/common/state/state.c
@@ -0,0 +1,566 @@
+/*
+ * 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 <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);
+
+ 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;
+ int ret;
+
+ 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->root = node;
+ 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->root->full_name);
+ 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->root->full_name);
+ c = strrchr(of_path, '/');
+ if (!c)
+ return -ENODEV;
+ *c = '0';
+ parent = of_find_node_by_path(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;
+ }
+ }
+
+ /* 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);
+}
+
+/*
+ * 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 (!path) {
+ pr_err("state failed to parse path to backend\n");
+ ret = -EINVAL;
+ 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;
+ pr_info("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) {
+ pr_warn("Failed to load persistent state, continuing with defaults, %d\n", ret);
+ }
+
+ pr_info("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 (state->root == node)
+ 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");
+ }
+}