summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2015-04-13 12:57:14 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2015-04-13 12:57:14 +0200
commitee0035e5ccee0056e23b2dda2cade0c5ca756713 (patch)
tree9e02a89c11c7574bace93f2c3731c4175e0ea649
parenta76a6bc8fd62bc26140d421d09e71624ee13aa47 (diff)
parent1487e6b9ad2ec0fafb0a2a35c921a33fb6c37fec (diff)
downloadbarebox-ee0035e5ccee0056e23b2dda2cade0c5ca756713.tar.gz
barebox-ee0035e5ccee0056e23b2dda2cade0c5ca756713.tar.xz
Merge branch 'for-next/state'
-rw-r--r--Documentation/devicetree/bindings/barebox/barebox,state.rst107
-rw-r--r--commands/Kconfig5
-rw-r--r--commands/Makefile1
-rw-r--r--commands/state.c77
-rw-r--r--common/Kconfig7
-rw-r--r--common/Makefile1
-rw-r--r--common/state.c1198
-rw-r--r--drivers/misc/Kconfig4
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/state.c81
-rw-r--r--include/state.h21
11 files changed, 1503 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/barebox/barebox,state.rst b/Documentation/devicetree/bindings/barebox/barebox,state.rst
new file mode 100644
index 0000000000..42b2a34d3a
--- /dev/null
+++ b/Documentation/devicetree/bindings/barebox/barebox,state.rst
@@ -0,0 +1,107 @@
+barebox,state
+=============
+
+Overview
+--------
+
+
+Boards often have the need to store variables in persistent memory.
+The constraints are often different from what the regular environment
+can do:
+
+* compact binary format to make it suitable for small EEPROMs/MRAMs
+* atomic save/restore of the whole variable set
+* redundancy
+
+``barebox,state`` is a framework to describe, access, store and
+restore a set of variables. A state variable set can be fully
+described in a devicetree node. This node could be part of the regular
+devicetree blob or it could be an extra devicetree solely for the
+state. The state variable set contains variables of different types
+and a place to store the variable set.
+
+A state node contains a description of a set of variables along with a
+place where the variables are stored.
+
+Required properties:
+
+* ``compatible``: should be ``barebox,state``;
+* ``magic``: A 32bit number used as a magic to identify the state
+
+Optional properties:
+
+* ``backend``: describes where the data for this state is stored
+* ``backend-type``: should be ``raw`` or ``dtb``.
+
+Variable nodes
+--------------
+
+These are subnodes of a state node each describing a single
+variable. The node name may end with ``@<ADDRESS>``, but the suffix is
+sripped from the variable name.
+
+State variables have a type. Currenty supported types are: ``uint32``,
+``enum32`` and ``mac`` address. Fixed length strings are planned but
+not implemented. Variable length strings are not planned.
+
+Required properties:
+
+* ``reg``: Standard ``reg`` property with ``#address-cells = <1>`` and
+ ``#size-cells = <1>``. Defines the ``offset`` and ``size`` of the
+ variable in the ``raw`` backend. ``size`` must fit the node
+ ``type``. Variables are not allowed to overlap.
+* ``type``: Should be ``uint32``, ``enum32`` or ``mac`` for the type
+ of the variable
+* ``names``: For ``enum32`` values only, this specifies the values
+ possible for ``enum32``.
+
+Optional properties:
+
+* ``default``: The default value if the variable cannot be read from
+ storage. For ``enum32`` values it is an integer representing an
+ offset into the names array.
+
+Example::
+
+ state: state@0 {
+ magic = <0x27031977>;
+ compatible = "barebox,state";
+ backend-type = "raw";
+ backend = &eeprom, "partname:state";
+
+ foo {
+ reg = <0x00 0x4>;
+ type = "u32";
+ default = <0x0>;
+ };
+
+ bar {
+ reg = <0x10 0x4>;
+ type = "enum32";
+ names = "baz", "qux";
+ default ="qux";
+ };
+ };
+
+Backends
+--------
+
+Currently two backends exist. The raw backend is a very compact format
+consisting of a magic value for identification, the raw values and a
+CRC. Two copies are maintained for making sure that during update the
+storage device still contains a valid state. The dtb backend stores
+the state as a devicetree binary blob. This is exactly the original
+devicetree description of the state itself, but additionally contains
+the actual values of the variables. Unlike the raw state backend the
+dtb state backend can describe itself.
+
+Frontend
+--------
+
+As frontend a state instance is a regular barebox device which has
+device parameters for the state variables. With this the variables can
+be accessed like normal shell variables. The ``state`` command is used
+to save/restore a state to the backend device.
+
+After initializing the variable can be accessed with ``$state.foo``.
+``state -s`` stores the state to eeprom.
diff --git a/commands/Kconfig b/commands/Kconfig
index 2618eda2f6..847ff76d1d 100644
--- a/commands/Kconfig
+++ b/commands/Kconfig
@@ -2096,6 +2096,11 @@ config CMD_TIME
Note: This command depends on COMMAND being interruptible,
otherwise the timer may overrun resulting in incorrect results
+config CMD_STATE
+ tristate
+ depends on STATE
+ prompt "state"
+
# end Miscellaneous commands
endmenu
diff --git a/commands/Makefile b/commands/Makefile
index d69e3f0b06..b902f58ec5 100644
--- a/commands/Makefile
+++ b/commands/Makefile
@@ -110,3 +110,4 @@ obj-$(CONFIG_CMD_FIRMWARELOAD) += firmwareload.o
obj-$(CONFIG_CMD_CMP) += cmp.o
obj-$(CONFIG_CMD_NV) += nv.o
obj-$(CONFIG_CMD_DEFAULTENV) += defaultenv.o
+obj-$(CONFIG_CMD_STATE) += state.o
diff --git a/commands/state.c b/commands/state.c
new file mode 100644
index 0000000000..82c29d00c8
--- /dev/null
+++ b/commands/state.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2012 Jan Luebbe <j.luebbe@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 <common.h>
+#include <getopt.h>
+#include <command.h>
+#include <state.h>
+
+static int do_state(int argc, char *argv[])
+{
+ int opt, ret = 0;
+ struct state *state = NULL;
+ int do_save = 0, do_load = 0;
+ const char *statename = "state";
+
+ while ((opt = getopt(argc, argv, "sl")) > 0) {
+ switch (opt) {
+ case 's':
+ do_save = 1;
+ break;
+ case 'l':
+ do_load = 1;
+ break;
+ default:
+ return COMMAND_ERROR_USAGE;
+ }
+ }
+
+ if (do_save && do_load)
+ return COMMAND_ERROR_USAGE;
+
+ if (!do_save && !do_load) {
+ state_info();
+ return 0;
+ }
+
+ if (optind < argc)
+ statename = argv[optind];
+
+ state = state_by_name(statename);
+ if (!state) {
+ printf("cannot find state %s\n", statename);
+ return -ENOENT;
+ }
+
+ if (do_save)
+ ret = state_save(state);
+ else if (do_load)
+ ret = state_load(state);
+
+ return ret;
+}
+
+static const __maybe_unused char cmd_state_help[] =
+"Usage: state [OPTIONS] [STATENAME]\n"
+"\n"
+"options:\n"
+"-s save state\n"
+"-l load state\n";
+
+BAREBOX_CMD_START(state)
+ .cmd = do_state,
+ BAREBOX_CMD_DESC("handle state information")
+ BAREBOX_CMD_GROUP(CMD_GRP_MISC)
+ BAREBOX_CMD_HELP(cmd_state_help)
+BAREBOX_CMD_END
diff --git a/common/Kconfig b/common/Kconfig
index 242f1e4047..1c5d14c1c0 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -706,6 +706,13 @@ config BAREBOXCRC32_TARGET
config POLLER
bool "generic polling infrastructure"
+config STATE
+ bool "generic state infrastructure"
+ depends on OF_BAREBOX_DRIVERS
+ 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 6bfbfb4745..eca1e3533c 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_POLLER) += poller.o
obj-$(CONFIG_RESET_SOURCE) += reset_source.o
obj-$(CONFIG_SHELL_HUSH) += hush.o
obj-$(CONFIG_SHELL_SIMPLE) += parser.o
+obj-$(CONFIG_STATE) += state.o
obj-$(CONFIG_UIMAGE) += image.o uimage.o
obj-$(CONFIG_MENUTREE) += menutree.o
obj-$(CONFIG_EFI_GUID) += efi-guid.o
diff --git a/common/state.c b/common/state.c
new file mode 100644
index 0000000000..b677a9f01d
--- /dev/null
+++ b/common/state.c
@@ -0,0 +1,1198 @@
+/*
+ * 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 <common.h>
+#include <environment.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fs.h>
+#include <init.h>
+#include <ioctl.h>
+#include <libbb.h>
+#include <libfile.h>
+#include <malloc.h>
+#include <net.h>
+#include <state.h>
+#include <xfuncs.h>
+
+#include <linux/mtd/mtd-abi.h>
+#include <linux/mtd/mtd.h>
+#include <linux/list.h>
+#include <linux/err.h>
+
+#include <asm/unaligned.h>
+
+#define RAW_BACKEND_COPIES 2
+
+struct state_backend;
+
+struct state {
+ struct device_d dev;
+ const struct device_node *root;
+ struct list_head variables;
+ const char *name;
+ struct list_head list;
+ struct state_backend *backend;
+ uint32_t magic;
+ unsigned int dirty;
+};
+
+struct state_backend {
+ int (*load)(struct state_backend *backend, struct state *state);
+ int (*save)(struct state_backend *backend, struct state *state);
+ const char *name;
+ const char *path;
+};
+
+enum state_variable_type {
+ STATE_TYPE_INVALID = 0,
+ STATE_TYPE_ENUM,
+ STATE_TYPE_U32,
+ STATE_TYPE_MAC,
+};
+
+/* instance of a single variable */
+struct state_variable {
+ enum state_variable_type type;
+ struct list_head list;
+ const char *name;
+ unsigned int start;
+ unsigned int size;
+ void *raw;
+};
+
+/* A variable type (uint32, enum32) */
+struct variable_type {
+ enum state_variable_type type;
+ const char *type_name;
+ struct list_head list;
+ int (*export)(struct state_variable *, struct device_node *);
+ int (*import)(struct state_variable *, const struct device_node *);
+ struct state_variable *(*create)(struct state *state,
+ const char *name, struct device_node *);
+};
+
+/* list of all registered state instances */
+static LIST_HEAD(state_list);
+
+static int state_set_dirty(struct param_d *p, void *priv)
+{
+ struct state *state = priv;
+
+ state->dirty = 1;
+
+ return 0;
+}
+
+/*
+ * uint32
+ */
+struct state_uint32 {
+ struct state_variable var;
+ struct param_d *param;
+ uint32_t value;
+ uint32_t value_default;
+};
+
+static int state_var_compare(struct list_head *a, struct list_head *b)
+{
+ struct state_variable *va = list_entry(a, struct state_variable, list);
+ struct state_variable *vb = list_entry(b, struct state_variable, list);
+
+ return va->start < vb->start ? -1 : 1;
+}
+
+static void state_add_var(struct state *state, struct state_variable *var)
+{
+ list_add_sort(&var->list, &state->variables, state_var_compare);
+}
+
+static inline struct state_uint32 *to_state_uint32(struct state_variable *s)
+{
+ return container_of(s, struct state_uint32, var);
+}
+
+static int state_uint32_export(struct state_variable *var,
+ struct device_node *node)
+{
+ struct state_uint32 *su32 = to_state_uint32(var);
+ int ret;
+
+ if (su32->value_default) {
+ ret = of_property_write_u32(node, "default",
+ su32->value_default);
+ if (ret)
+ return ret;
+ }
+
+ return of_property_write_u32(node, "value", su32->value);
+}
+
+static int state_uint32_import(struct state_variable *sv,
+ const struct device_node *node)
+{
+ struct state_uint32 *su32 = to_state_uint32(sv);
+
+ of_property_read_u32(node, "default", &su32->value_default);
+ if (of_property_read_u32(node, "value", &su32->value))
+ su32->value = su32->value_default;
+
+ return 0;
+}
+
+static struct state_variable *state_uint32_create(struct state *state,
+ const char *name, struct device_node *node)
+{
+ struct state_uint32 *su32;
+ struct param_d *param;
+
+ su32 = xzalloc(sizeof(*su32));
+
+ param = dev_add_param_int(&state->dev, name, state_set_dirty,
+ NULL, &su32->value, "%d", state);
+ if (IS_ERR(param)) {
+ free(su32);
+ return ERR_CAST(param);
+ }
+
+ su32->param = param;
+ su32->var.size = sizeof(uint32_t);
+ su32->var.raw = &su32->value;
+
+ return &su32->var;
+}
+
+/*
+ * enum32
+ */
+struct state_enum32 {
+ struct state_variable var;
+ struct param_d *param;
+ uint32_t value;
+ uint32_t value_default;
+ const char **names;
+ int num_names;
+};
+
+static inline struct state_enum32 *to_state_enum32(struct state_variable *s)
+{
+ return container_of(s, struct state_enum32, var);
+}
+
+static int state_enum32_export(struct state_variable *var,
+ struct device_node *node)
+{
+ struct state_enum32 *enum32 = to_state_enum32(var);
+ int ret, i, len;
+ char *prop, *str;
+
+ if (enum32->value_default) {
+ ret = of_property_write_u32(node, "default",
+ enum32->value_default);
+ if (ret)
+ return ret;
+ }
+
+ ret = of_property_write_u32(node, "value", enum32->value);
+ if (ret)
+ return ret;
+
+ len = 0;
+
+ for (i = 0; i < enum32->num_names; i++)
+ len += strlen(enum32->names[i]) + 1;
+
+ prop = xzalloc(len);
+ str = prop;
+
+ for (i = 0; i < enum32->num_names; i++)
+ str += sprintf(str, "%s", enum32->names[i]) + 1;
+
+ ret = of_set_property(node, "names", prop, len, 1);
+
+ free(prop);
+
+ return ret;
+}
+
+static int state_enum32_import(struct state_variable *sv,
+ const struct device_node *node)
+{
+ struct state_enum32 *enum32 = to_state_enum32(sv);
+ int len;
+ const __be32 *value, *value_default;
+
+ value = of_get_property(node, "value", &len);
+ if (value && len != sizeof(uint32_t))
+ return -EINVAL;
+
+ value_default = of_get_property(node, "default", &len);
+ if (value_default && len != sizeof(uint32_t))
+ return -EINVAL;
+
+ if (value_default)
+ enum32->value_default = be32_to_cpu(*value_default);
+ if (value)
+ enum32->value = be32_to_cpu(*value);
+ else
+ enum32->value = enum32->value_default;
+
+ return 0;
+}
+
+static struct state_variable *state_enum32_create(struct state *state,
+ const char *name, struct device_node *node)
+{
+ struct state_enum32 *enum32;
+ int ret, i, num_names;
+
+ enum32 = xzalloc(sizeof(*enum32));
+
+ num_names = of_property_count_strings(node, "names");
+
+ enum32->names = xzalloc(sizeof(char *) * num_names);
+ enum32->num_names = num_names;
+ enum32->var.size = sizeof(uint32_t);
+ enum32->var.raw = &enum32->value;
+
+ for (i = 0; i < num_names; i++) {
+ const char *name;
+
+ ret = of_property_read_string_index(node, "names", i, &name);
+ if (ret)
+ goto out;
+ enum32->names[i] = xstrdup(name);
+ }
+
+ enum32->param = dev_add_param_enum(&state->dev, name, state_set_dirty,
+ NULL, &enum32->value, enum32->names, num_names, state);
+ if (IS_ERR(enum32->param)) {
+ ret = PTR_ERR(enum32->param);
+ goto out;
+ }
+
+ return &enum32->var;
+out:
+ for (i--; i >= 0; i--)
+ free((char *)enum32->names[i]);
+ free(enum32->names);
+ free(enum32);
+ return ERR_PTR(ret);
+}
+
+/*
+ * MAC address
+ */
+struct state_mac {
+ struct state_variable var;
+ struct param_d *param;
+ uint8_t value[6];
+ uint8_t value_default[6];
+};
+
+static inline struct state_mac *to_state_mac(struct state_variable *s)
+{
+ return container_of(s, struct state_mac, var);
+}
+
+static int state_mac_export(struct state_variable *var,
+ struct device_node *node)
+{
+ struct state_mac *mac = to_state_mac(var);
+ int ret;
+
+ ret = of_property_write_u8_array(node, "default", mac->value_default,
+ ARRAY_SIZE(mac->value_default));
+ if (ret)
+ return ret;
+
+ return of_property_write_u8_array(node, "value", mac->value,
+ ARRAY_SIZE(mac->value));
+}
+
+static int state_mac_import(struct state_variable *sv,
+ const struct device_node *node)
+{
+ struct state_mac *mac = to_state_mac(sv);
+
+ of_property_read_u8_array(node, "default", mac->value_default,
+ ARRAY_SIZE(mac->value_default));
+ if (of_property_read_u8_array(node, "value", mac->value,
+ ARRAY_SIZE(mac->value)))
+ memcpy(mac->value, mac->value_default, ARRAY_SIZE(mac->value));
+
+ return 0;
+}
+
+static struct state_variable *state_mac_create(struct state *state,
+ const char *name, struct device_node *node)
+{
+ struct state_mac *mac;
+ int ret;
+
+ mac = xzalloc(sizeof(*mac));
+
+ mac->var.size = ARRAY_SIZE(mac->value);
+ mac->var.raw = mac->value;
+
+ mac->param = dev_add_param_mac(&state->dev, name, state_set_dirty,
+ NULL, mac->value, state);
+ if (IS_ERR(mac->param)) {
+ ret = PTR_ERR(mac->param);
+ goto out;
+ }
+
+ return &mac->var;
+out:
+ free(mac);
+ return ERR_PTR(ret);
+}
+
+static struct variable_type types[] = {
+ {
+ .type = STATE_TYPE_U32,
+ .type_name = "uint32",
+ .export = state_uint32_export,
+ .import = state_uint32_import,
+ .create = state_uint32_create,
+ }, {
+ .type = STATE_TYPE_ENUM,
+ .type_name = "enum32",
+ .export = state_enum32_export,
+ .import = state_enum32_import,
+ .create = state_enum32_create,
+ }, {
+ .type = STATE_TYPE_MAC,
+ .type_name = "mac",
+ .export = state_mac_export,
+ .import = state_mac_import,
+ .create = state_mac_create,
+ },
+};
+
+static struct variable_type *state_find_type_by_name(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(types); i++) {
+ if (!strcmp(name, types[i].type_name)) {
+ return &types[i];
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Generic state functions
+ */
+
+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) {
+ 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 void state_release(struct state *state)
+{
+ list_del(&state->list);
+ unregister_device(&state->dev);
+ free(state);
+}
+
+static struct state_variable *state_find_var(struct state *state,
+ const char *name)
+{
+ struct state_variable *sv;
+
+ list_for_each_entry(sv, &state->variables, list) {
+ if (!strcmp(sv->name, name))
+ return sv;
+ }
+
+ return ERR_PTR(-ENOENT);
+}
+
+enum state_convert {
+ STATE_CONVERT_FROM_NODE,
+ STATE_CONVERT_FROM_NODE_CREATE,
+ STATE_CONVERT_TO_NODE,
+};
+
+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 = asprintf("%s%s%s",
+ parent_name, parent_name[0] ? "." : "", short_name);
+ free(short_name);
+
+ if (conv == STATE_CONVERT_TO_NODE)
+ 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) {
+ 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)
+ goto out_free;
+
+ if (start_size[1] != sv->size) {
+ dev_err(&state->dev,
+ "size mismatch: type=%s(size=%u) size=%u\n",
+ 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) {
+ 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)
+ ret = vtype->export(sv, new_node);
+ else
+ ret = vtype->import(sv, node);
+
+ if (ret)
+ goto out;
+
+ return 0;
+out_free:
+ free(name);
+out:
+ return ret;
+}
+
+static struct device_node *state_to_node(struct state *state)
+{
+ struct device_node *child;
+ struct device_node *root;
+ int ret;
+
+ root = of_new_node(NULL, NULL);
+ 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, "",
+ STATE_CONVERT_TO_NODE);
+ if (ret)
+ goto out;
+ }
+
+ return root;
+out:
+ of_delete_node(root);
+ return ERR_PTR(ret);
+}
+
+static 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;
+}
+
+/*
+ * state_new_from_node - create a new state instance from a device_node
+ *
+ * @name The name of the new state instance
+ * @node The device_node describing the new state instance
+ */
+struct state *state_new_from_node(const char *name, struct device_node *node)
+{
+ struct state *state;
+ int ret;
+
+ state = state_new(name);
+ if (IS_ERR(state))
+ return state;
+
+ ret = state_from_node(state, node, 1);
+ if (ret) {
+ state_release(state);
+ return ERR_PTR(ret);
+ }
+
+ return state;
+}
+
+/*
+ * state_new_from_fdt - create a new state instance from a fdt binary blob
+ *
+ * @name The name of the new state instance
+ * @fdt The fdt binary blob describing the new state instance
+ */
+struct state *state_new_from_fdt(const char *name, void *fdt)
+{
+ struct state *state;
+ struct device_node *root;
+
+ root = of_unflatten_dtb(fdt);
+ if (!root)
+ return ERR_PTR(-EINVAL);
+
+ state = state_new_from_node(name, root);
+
+ of_delete_node(root);
+
+ return state;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * state_load - load a state from the backing store
+ *
+ * @state The state instance to load
+ */
+int state_load(struct state *state)
+{
+ int ret;
+
+ if (!state->backend)
+ return -ENOSYS;
+
+ ret = state->backend->load(state->backend, state);
+ if (ret)
+ state->dirty = 1;
+ else
+ state->dirty = 0;
+
+ return ret;
+}
+
+/*
+ * state_save - save a state to the backing store
+ *
+ * @state The state instance to save
+ */
+int state_save(struct state *state)
+{
+ int ret;
+
+ if (!state->dirty)
+ return 0;
+
+ if (!state->backend)
+ return -ENOSYS;
+
+ ret = state->backend->save(state->backend, state);
+ if (ret)
+ return ret;
+
+ state->dirty = 0;
+
+ 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)
+ printf("(backend: %s, path: %s)\n",
+ state->backend->name, state->backend->path);
+ else
+ printf("(no backend)\n");
+ }
+}
+
+static int mtd_get_meminfo(const char *path, struct mtd_info_user *meminfo)
+{
+ int fd, ret;
+
+ fd = open(path, O_RDWR);
+ if (fd < 0)
+ return fd;
+
+ ret = ioctl(fd, MEMGETINFO, meminfo);
+
+ close(fd);
+
+ return ret;
+}
+
+/*
+ * DTB backend implementation
+ */
+struct state_backend_dtb {
+ struct state_backend backend;
+ bool need_erase;
+};
+
+static int state_backend_dtb_load(struct state_backend *backend,
+ struct state *state)
+{
+ struct device_node *root;
+ void *fdt;
+ int ret;
+ size_t len;
+
+ fdt = read_file(backend->path, &len);
+ if (!fdt) {
+ dev_err(&state->dev, "cannot read %s\n", backend->path);
+ return -EINVAL;
+ }
+
+ root = of_unflatten_dtb(fdt);
+
+ free(fdt);
+
+ if (IS_ERR(root))
+ return PTR_ERR(root);
+
+ ret = state_from_node(state, root, 0);
+
+ return ret;
+}
+
+static int state_backend_dtb_save(struct state_backend *backend,
+ struct state *state)
+{
+ struct state_backend_dtb *backend_dtb = container_of(backend,
+ struct state_backend_dtb, backend);
+ int ret, fd;
+ struct device_node *root;
+ struct fdt_header *fdt;
+
+ root = state_to_node(state);
+ if (IS_ERR(root))
+ return PTR_ERR(root);
+
+ fdt = of_flatten_dtb(root);
+ if (!fdt)
+ return -EINVAL;
+
+ fd = open(backend->path, O_WRONLY);
+ if (fd < 0) {
+ ret = fd;
+ goto out;
+ }
+
+ if (backend_dtb->need_erase) {
+ ret = erase(fd, fdt32_to_cpu(fdt->totalsize), 0);
+ if (ret) {
+ close(fd);
+ goto out;
+ }
+ }
+
+ ret = write_full(fd, fdt, fdt32_to_cpu(fdt->totalsize));
+
+ close(fd);
+
+ if (ret < 0)
+ goto out;
+
+ ret = 0;
+out:
+ free(fdt);
+ of_delete_node(root);
+
+ return ret;
+}
+
+/*
+ * state_backend_dtb_file - create a dtb backend store for a state instance
+ *
+ * @state The state instance to work on
+ * @path The path where the state will be stored to
+ */
+int state_backend_dtb_file(struct state *state, const char *path)
+{
+ struct state_backend_dtb *backend_dtb;
+ struct state_backend *backend;
+ struct mtd_info_user meminfo;
+ int ret;
+
+ if (state->backend)
+ return -EBUSY;
+
+ backend_dtb = xzalloc(sizeof(*backend_dtb));
+ backend = &backend_dtb->backend;
+
+ backend->load = state_backend_dtb_load;
+ backend->save = state_backend_dtb_save;
+ backend->path = xstrdup(path);
+ backend->name = "dtb";
+
+ state->backend = backend;
+
+ ret = mtd_get_meminfo(backend->path, &meminfo);
+ if (!ret && !(meminfo.mtd->flags & MTD_NO_ERASE))
+ backend_dtb->need_erase = true;
+
+ return 0;
+}
+
+/*
+ * Raw backend implementation
+ */
+struct state_backend_raw {
+ struct state_backend backend;
+ unsigned long size_data; /* The raw data size (without magic and crc) */
+ unsigned long size_full;
+ unsigned long step; /* The step in bytes between two copies */
+ off_t offset; /* offset in the storage file */
+ size_t size; /* size of the storage area */
+ int num_copy_read; /* The first successfully read copy */
+ bool need_erase;
+};
+
+struct backend_raw_header {
+ uint32_t magic;
+ uint16_t reserved;
+ uint16_t data_len;
+ uint32_t data_crc;
+ uint32_t header_crc;
+};
+
+static int backend_raw_load_one(struct state_backend_raw *backend_raw,
+ struct state *state, int fd, off_t offset)
+{
+ uint32_t crc;
+ struct state_variable *sv;
+ struct backend_raw_header header = {};
+ int ret;
+ void *buf;
+
+ ret = lseek(fd, offset, SEEK_SET);
+ if (ret < 0)
+ return ret;
+
+ ret = read_full(fd, &header, sizeof(header));
+ if (ret < 0)
+ return ret;
+
+ crc = crc32(0, &header, sizeof(header) - sizeof(uint32_t));
+ if (crc != header.header_crc) {
+ dev_err(&state->dev,
+ "invalid header crc, calculated 0x%08x, found 0x%08x\n",
+ crc, header.header_crc);
+ return -EINVAL;
+ }
+
+ if (state->magic && state->magic != header.magic) {
+ dev_err(&state->dev,
+ "invalid magic 0x%08x, should be 0x%08x\n",
+ header.magic, state->magic);
+ return -EINVAL;
+ }
+
+ buf = xzalloc(header.data_len);
+
+ ret = read_full(fd, buf, header.data_len);
+ if (ret < 0)
+ goto out_free;
+
+ crc = crc32(0, buf, header.data_len);
+ if (crc != header.data_crc) {
+ dev_err(&state->dev,
+ "invalid crc, calculated 0x%08x, found 0x%08x\n",
+ crc, header.data_crc);
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+ list_for_each_entry(sv, &state->variables, list) {
+ if (sv->start + sv->size > header.data_len)
+ break;
+ memcpy(sv->raw, buf + sv->start, sv->size);
+ }
+
+ free(buf);
+ return 0;
+
+ out_free:
+ free(buf);
+ return ret;
+}
+
+static int state_backend_raw_load(struct state_backend *backend,
+ struct state *state)
+{
+ struct state_backend_raw *backend_raw = container_of(backend,
+ struct state_backend_raw, backend);
+ int ret = 0, fd, i;
+
+ fd = open(backend->path, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ for (i = 0; i < RAW_BACKEND_COPIES; i++) {
+ off_t offset = backend_raw->offset + i * backend_raw->step;
+
+ ret = backend_raw_load_one(backend_raw, state, fd, offset);
+ if (!ret) {
+ backend_raw->num_copy_read = i;
+ dev_dbg(&state->dev,
+ "copy %d successfully loaded\n", i);
+ break;
+ }
+ }
+
+ close(fd);
+
+ return ret;
+}
+
+static int backend_raw_write_one(struct state_backend_raw *backend_raw,
+ struct state *state, int fd, int num, void *buf, size_t size)
+{
+ int ret;
+ off_t offset = backend_raw->offset + num * backend_raw->step;
+
+ dev_dbg(&state->dev, "%s: 0x%08lx 0x%08zx\n",
+ __func__, offset, size);
+
+ ret = lseek(fd, offset, SEEK_SET);
+ if (ret < 0)
+ return ret;
+
+ if (backend_raw->need_erase) {
+ ret = erase(fd, backend_raw->size_full, offset);
+ if (ret)
+ return ret;
+ }
+
+ ret = write_full(fd, buf, size);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int state_backend_raw_save(struct state_backend *backend,
+ struct state *state)
+{
+ struct state_backend_raw *backend_raw = container_of(backend,
+ struct state_backend_raw, backend);
+ int ret = 0, size, fd;
+ void *buf, *data;
+ struct backend_raw_header *header;
+ struct state_variable *sv;
+
+ size = backend_raw->size_data + sizeof(struct backend_raw_header);
+
+ buf = xzalloc(size);
+
+ header = buf;
+ data = buf + sizeof(*header);
+
+ list_for_each_entry(sv, &state->variables, list)
+ memcpy(data + sv->start, sv->raw, sv->size);
+
+ header->magic = state->magic;
+ header->data_len = backend_raw->size_data;
+ header->data_crc = crc32(0, data, backend_raw->size_data);
+ header->header_crc = crc32(0, header,
+ sizeof(*header) - sizeof(uint32_t));
+
+ fd = open(backend->path, O_WRONLY);
+ if (fd < 0)
+ goto out_free;
+
+ ret = backend_raw_write_one(backend_raw, state, fd,
+ !backend_raw->num_copy_read, buf, size);
+ if (ret)
+ goto out_close;
+
+ ret = backend_raw_write_one(backend_raw, state, fd,
+ backend_raw->num_copy_read, buf, size);
+ if (ret)
+ goto out_close;
+
+ dev_dbg(&state->dev, "wrote state to %s\n", backend->path);
+out_close:
+ close(fd);
+out_free:
+ free(buf);
+
+ return ret;
+}
+
+/*
+ * state_backend_raw_file - create a raw file backend store for a state instance
+ *
+ * @state The state instance to work on
+ * @path The path where the state will be stored to
+ * @offset The offset in the storage file
+ * @size The maximum size to use in the storage file
+ *
+ * This backend stores raw binary data from a state instance. The
+ * binary data is protected with a magic value which has to match and
+ * a crc32 that must be valid. Two copies are stored, sufficient
+ * space must be available.
+
+ * @path can be a path to a device or a regular file. When it's a
+ * device @size may be 0. The two copies are spread to different
+ * eraseblocks if approriate for this device.
+ */
+int state_backend_raw_file(struct state *state, const char *path, off_t offset,
+ size_t size)
+{
+ struct state_backend_raw *backend_raw;
+ struct state_backend *backend;
+ struct state_variable *sv;
+ int ret;
+ struct stat s;
+ struct mtd_info_user meminfo;
+
+ if (state->backend)
+ return -EBUSY;
+
+ ret = stat(path, &s);
+ if (!ret && S_ISCHR(s.st_mode)) {
+ if (size == 0)
+ size = s.st_size;
+ else if (offset + size > s.st_size)
+ return -EINVAL;
+ }
+
+ backend_raw = xzalloc(sizeof(*backend_raw));
+ backend = &backend_raw->backend;
+
+ backend->load = state_backend_raw_load;
+ backend->save = state_backend_raw_save;
+ backend->path = xstrdup(path);
+ backend->name = "raw";
+
+ sv = list_last_entry(&state->variables, struct state_variable, list);
+ backend_raw->size_data = sv->start + sv->size;
+ backend_raw->offset = offset;
+ backend_raw->size = size;
+ backend_raw->size_full = backend_raw->size_data +
+ sizeof(struct backend_raw_header);
+
+ state->backend = backend;
+
+ ret = mtd_get_meminfo(backend->path, &meminfo);
+ if (!ret && !(meminfo.mtd->flags & MTD_NO_ERASE)) {
+ backend_raw->need_erase = true;
+ backend_raw->step = ALIGN(backend_raw->size_full,
+ meminfo.erasesize);
+ dev_dbg(&state->dev, "is a mtd, adjust stepsize to %ld\n",
+ backend_raw->step);
+ } else {
+ backend_raw->step = backend_raw->size_full;
+ }
+
+ if (backend_raw->size / backend_raw->step < RAW_BACKEND_COPIES) {
+ dev_err(&state->dev, "not enough space for two copies\n");
+ ret = -ENOSPC;
+ goto err;
+ }
+
+ return 0;
+err:
+ free(backend_raw);
+ return ret;
+}
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c34a4af512..7a5b14697e 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -15,4 +15,8 @@ config SRAM
help
This driver adds support for memory mapped SRAM.
+config STATE_DRV
+ tristate "state driver"
+ depends on STATE
+
endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 908c8cb708..487e4b8ba2 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -4,3 +4,4 @@
obj-$(CONFIG_JTAG) += jtag.o
obj-$(CONFIG_SRAM) += sram.o
+obj-$(CONFIG_STATE_DRV) += state.o
diff --git a/drivers/misc/state.c b/drivers/misc/state.c
new file mode 100644
index 0000000000..f066a836cb
--- /dev/null
+++ b/drivers/misc/state.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 Sascha Hauer <s.hauer@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 <common.h>
+#include <driver.h>
+#include <init.h>
+#include <malloc.h>
+#include <of.h>
+#include <state.h>
+
+#include <linux/err.h>
+
+static int state_probe(struct device_d *dev)
+{
+ struct device_node *np = dev->device_node;
+ struct state *state;
+ const char *alias;
+ const char *backend_type = NULL;
+ int ret;
+ char *path;
+
+ if (!np)
+ return -EINVAL;
+
+ alias = of_alias_get(np);
+ if (!alias)
+ alias = "state";
+
+ state = state_new_from_node(alias, np);
+ if (IS_ERR(state))
+ return PTR_ERR(state);
+
+ ret = of_find_path(np, "backend", &path);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "outpath: %s\n", path);
+
+ ret = of_property_read_string(np, "backend-type", &backend_type);
+ if (ret)
+ return ret;
+ else if (!strcmp(backend_type, "raw"))
+ ret = state_backend_raw_file(state, path, 0, 0);
+ else if (!strcmp(backend_type, "dtb"))
+ ret = state_backend_dtb_file(state, path);
+ else
+ dev_warn(dev, "invalid backend type: %s\n", backend_type);
+
+ if (ret)
+ return ret;
+
+ state_load(state);
+
+ return 0;
+}
+
+static __maybe_unused struct of_device_id state_ids[] = {
+ {
+ .compatible = "barebox,state",
+ }, {
+ /* sentinel */
+ }
+};
+
+static struct driver_d state_driver = {
+ .name = "state",
+ .probe = state_probe,
+ .of_compatible = DRV_OF_COMPAT(state_ids),
+};
+device_platform_driver(state_driver);
diff --git a/include/state.h b/include/state.h
new file mode 100644
index 0000000000..95bf8d263b
--- /dev/null
+++ b/include/state.h
@@ -0,0 +1,21 @@
+#ifndef __STATE_H
+#define __STATE_H
+
+struct state;
+
+int state_backend_dtb_file(struct state *state, const char *path);
+int state_backend_raw_file(struct state *state, const char *path,
+ off_t offset, size_t size);
+
+struct state *state_new_from_fdt(const char *name, void *fdt);
+struct state *state_new_from_node(const char *name, struct device_node *node);
+
+struct state *state_by_name(const char *name);
+struct state *state_by_node(const struct device_node *node);
+int state_get_name(const struct state *state, char const **name);
+
+int state_load(struct state *state);
+int state_save(struct state *state);
+void state_info(void);
+
+#endif /* __STATE_H */