summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/Makefile2
-rw-r--r--common/state.c1720
-rw-r--r--common/state/Makefile9
-rw-r--r--common/state/backend.c188
-rw-r--r--common/state/backend_bucket_cached.c155
-rw-r--r--common/state/backend_bucket_circular.c515
-rw-r--r--common/state/backend_bucket_direct.c180
-rw-r--r--common/state/backend_format_dtb.c150
-rw-r--r--common/state/backend_format_raw.c329
-rw-r--r--common/state/backend_storage.c524
-rw-r--r--common/state/state.c566
-rw-r--r--common/state/state.h275
-rw-r--r--common/state/state_variables.c493
-rw-r--r--drivers/misc/state.c65
-rw-r--r--include/state.h4
15 files changed, 3390 insertions, 1785 deletions
diff --git a/common/Makefile b/common/Makefile
index 99681e2..17fcb5f 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -44,7 +44,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_STATE) += state/
obj-$(CONFIG_RATP) += ratp.o
obj-$(CONFIG_UIMAGE) += image.o uimage.o
obj-$(CONFIG_FITIMAGE) += image-fit.o
diff --git a/common/state.c b/common/state.c
deleted file mode 100644
index 87afff3..0000000
--- a/common/state.c
+++ /dev/null
@@ -1,1720 +0,0 @@
-/*
- * 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 <digest.h>
-#include <environment.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <fs.h>
-#include <crc.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 <crypto/keystore.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;
- 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 (*save)(struct state_backend *backend, struct state *state);
- const char *name;
- const char *of_path;
- const char *path;
- struct digest *digest;
-};
-
-enum state_variable_type {
- STATE_TYPE_INVALID = 0,
- STATE_TYPE_ENUM,
- STATE_TYPE_U8,
- STATE_TYPE_U32,
- STATE_TYPE_S32,
- STATE_TYPE_MAC,
- STATE_TYPE_STRING,
-};
-
-/* 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;
-};
-
-enum state_convert {
- STATE_CONVERT_FROM_NODE,
- STATE_CONVERT_FROM_NODE_CREATE,
- STATE_CONVERT_TO_NODE,
- STATE_CONVERT_FIXUP,
-};
-
-/* 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 *,
- enum state_convert);
- int (*import)(struct state_variable *, 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;
- struct state *state;
- 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, enum state_convert conv)
-{
- 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;
- }
-
- if (conv == STATE_CONVERT_FIXUP)
- return 0;
-
- return of_property_write_u32(node, "value", su32->value);
-}
-
-static int state_uint32_import(struct state_variable *sv,
- 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 int state_uint8_set(struct param_d *p, void *priv)
-{
- struct state_uint32 *su32 = priv;
- struct state *state = su32->state;
-
- if (su32->value > 255)
- return -ERANGE;
-
- return state_set_dirty(p, state);
-}
-
-static struct state_variable *state_uint8_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_uint8_set,
- NULL, &su32->value, "%u", su32);
- if (IS_ERR(param)) {
- free(su32);
- return ERR_CAST(param);
- }
-
- su32->param = param;
- su32->var.size = sizeof(uint8_t);
-#ifdef __LITTLE_ENDIAN
- su32->var.raw = &su32->value;
-#else
- su32->var.raw = &su32->value + 3;
-#endif
- su32->state = state;
-
- return &su32->var;
-}
-
-static struct state_variable *state_int32_create(struct state *state,
- const char *name, struct device_node *node, const char *format)
-{
- 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, format, 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;
-}
-
-static struct state_variable *state_uint32_create(struct state *state,
- const char *name, struct device_node *node)
-{
- return state_int32_create(state, name, node, "%u");
-}
-
-static struct state_variable *state_sint32_create(struct state *state,
- const char *name, struct device_node *node)
-{
- return state_int32_create(state, name, node, "%d");
-}
-
-/*
- * 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, enum state_convert conv)
-{
- 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;
- }
-
- 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);
-
- if (conv == STATE_CONVERT_FIXUP)
- return 0;
-
- ret = of_property_write_u32(node, "value", enum32->value);
- if (ret)
- return ret;
-
- return ret;
-}
-
-static int state_enum32_import(struct state_variable *sv,
- 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");
- if (num_names < 0) {
- dev_err(&state->dev, "enum32 node without \"names\" property\n");
- return ERR_PTR(-EINVAL);
- }
-
- 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, enum state_convert conv)
-{
- struct state_mac *mac = to_state_mac(var);
- int ret;
-
- if (!is_zero_ether_addr(mac->value_default)) {
- ret = of_property_write_u8_array(node, "default", mac->value_default,
- ARRAY_SIZE(mac->value_default));
- if (ret)
- return ret;
- }
-
- if (conv == STATE_CONVERT_FIXUP)
- return 0;
-
- return of_property_write_u8_array(node, "value", mac->value,
- ARRAY_SIZE(mac->value));
-}
-
-static int state_mac_import(struct state_variable *sv,
- 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);
-}
-
-/*
- * string
- */
-struct state_string {
- struct state_variable var;
- struct param_d *param;
- struct state *state;
- char *value;
- const char *value_default;
- char raw[];
-};
-
-static inline struct state_string *to_state_string(struct state_variable *s)
-{
- return container_of(s, struct state_string, var);
-}
-
-static int state_string_export(struct state_variable *var,
- struct device_node *node, enum state_convert conv)
-{
- struct state_string *string = to_state_string(var);
- int ret = 0;
-
- if (string->value_default) {
- ret = of_set_property(node, "default", string->value_default,
- strlen(string->value_default) + 1, 1);
-
- if (ret)
- return ret;
- }
-
- if (conv == STATE_CONVERT_FIXUP)
- return 0;
-
- if (string->value)
- ret = of_set_property(node, "value", string->value,
- strlen(string->value) + 1, 1);
-
- return ret;
-}
-
-static int state_string_copy_to_raw(struct state_string *string,
- const char *src)
-{
- size_t len;
-
- len = strlen(src);
- if (len > string->var.size)
- return -EILSEQ;
-
- /* copy string and clear remaining contents of buffer */
- memcpy(string->raw, src, len);
- memset(string->raw + len, 0x0, string->var.size - len);
-
- return 0;
-}
-
-static int state_string_import(struct state_variable *sv,
- struct device_node *node)
-{
- struct state_string *string = to_state_string(sv);
- const char *value = NULL;
- size_t len;
- int ret;
-
- of_property_read_string(node, "default", &string->value_default);
- if (string->value_default) {
- len = strlen(string->value_default);
- if (len > string->var.size)
- return -EILSEQ;
- }
-
- ret = of_property_read_string(node, "value", &value);
- if (ret)
- value = string->value_default;
-
- if (value)
- return state_string_copy_to_raw(string, value);
-
- return 0;
-}
-
-static int state_string_set(struct param_d *p, void *priv)
-{
- struct state_string *string = priv;
- struct state *state = string->state;
- int ret;
-
- ret = state_string_copy_to_raw(string, string->value);
- if (ret)
- return ret;
-
- return state_set_dirty(p, state);
-}
-
-static int state_string_get(struct param_d *p, void *priv)
-{
- struct state_string *string = priv;
-
- free(string->value);
- if (string->raw[0])
- string->value = xstrndup(string->raw, string->var.size);
- else
- string->value = xstrdup("");
-
- return 0;
-}
-
-static struct state_variable *state_string_create(struct state *state,
- const char *name, struct device_node *node)
-{
- struct state_string *string;
- u32 start_size[2];
- int ret;
-
- 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);
- return ERR_PTR(ret);
- }
-
- /* limit to arbitrary len of 4k */
- if (start_size[1] > 4096)
- return ERR_PTR(-EILSEQ);
-
- string = xzalloc(sizeof(*string) + start_size[1]);
- string->var.size = start_size[1];
- string->var.raw = &string->raw;
- string->state = state;
-
- string->param = dev_add_param_string(&state->dev, name, state_string_set,
- state_string_get, &string->value, string);
- if (IS_ERR(string->param)) {
- ret = PTR_ERR(string->param);
- goto out;
- }
-
- return &string->var;
-out:
- free(string);
- return ERR_PTR(ret);
-}
-
-static struct variable_type types[] = {
- {
- .type = STATE_TYPE_U8,
- .type_name = "uint8",
- .export = state_uint32_export,
- .import = state_uint32_import,
- .create = state_uint8_create,
- }, {
- .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,
- }, {
- .type = STATE_TYPE_STRING,
- .type_name = "string",
- .export = state_string_export,
- .import = state_string_import,
- .create = state_string_create,
- }, {
- .type = STATE_TYPE_S32,
- .type_name = "int32",
- .export = state_uint32_export,
- .import = state_uint32_import,
- .create = state_sint32_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 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);
-}
-
-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) {
- dev_err(&state->dev, "unkown type: %s in %s\n", type_name,
- node->full_name);
- 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;
-}
-
-static 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);
-}
-
-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;
-}
-
-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) {
- ret = -ENODEV;
- goto out;
- }
-
- p = of_new_property(new_node, "backend-type", state->backend->name,
- strlen(state->backend->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 (state->backend->digest) {
- p = of_new_property(new_node, "algo",
- digest_name(state->backend->digest),
- strlen(digest_name(state->backend->digest)) + 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:
- dev_err(&state->dev, "error fixing up device tree with boot state\n");
- 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);
- free(state);
-}
-
-/*
- * 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);
- }
-
- ret = of_register_fixup(of_state_fixup, state);
- if (ret) {
- state_release(state);
- return ERR_PTR(ret);
- }
-
- 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_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_RDONLY);
- 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, NULL, STATE_CONVERT_TO_NODE);
- 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 *of_path, 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->save = state_backend_dtb_save;
- backend->of_path = xstrdup(of_path);
- backend->path = xstrdup(path);
- backend->name = "dtb";
-
- state->backend = backend;
-
- ret = mtd_get_meminfo(backend->path, &meminfo);
- if (!ret && !(meminfo.flags & MTD_NO_ERASE))
- backend_dtb->need_erase = true;
-
- ret = state_backend_dtb_load(backend, state);
- if (ret) {
- dev_warn(&state->dev, "load failed - using defaults\n");
- } else {
- dev_info(&state->dev, "load successful\n");
- state->dirty = 0;
- }
-
- /* ignore return value of load() */
- return 0;
-}
-
-/*
- * Raw backend implementation
- */
-struct state_backend_raw {
- struct state_backend backend;
- unsigned long size_data; /* The raw data size (without header) */
- unsigned long size_full; /* The size header + raw data + hmac */
- unsigned long stride; /* The stride size in bytes of the 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 = {};
- unsigned long max_len;
- int d_len = 0;
- int ret;
- void *buf, *data, *hmac;
-
- max_len = backend_raw->stride;
-
- ret = lseek(fd, offset, SEEK_SET);
- if (ret < 0)
- return ret;
-
- ret = read_full(fd, &header, sizeof(header));
- max_len -= sizeof(header);
- if (ret < 0) {
- dev_err(&state->dev,
- "cannot read header from backend device\n");
- 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;
- }
-
- if (backend_raw->backend.digest) {
- d_len = digest_length(backend_raw->backend.digest);
- max_len -= d_len;
- }
-
- if (header.data_len > max_len) {
- dev_err(&state->dev,
- "invalid data_len %u in header, max is %lu\n",
- header.data_len, max_len);
- return -EINVAL;
- }
-
- buf = xzalloc(sizeof(header) + header.data_len + d_len);
- data = buf + sizeof(header);
- hmac = data + header.data_len;
-
- ret = lseek(fd, offset, SEEK_SET);
- if (ret < 0)
- goto out_free;
-
- ret = read_full(fd, buf, sizeof(header) + header.data_len + d_len);
- if (ret < 0)
- goto out_free;
-
- crc = crc32(0, data, 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;
- }
-
- if (backend_raw->backend.digest) {
- struct digest *d = backend_raw->backend.digest;
-
- ret = digest_init(d);
- if (ret)
- goto out_free;
-
- /* hmac over header and data */
- ret = digest_update(d, buf, sizeof(header) + header.data_len);
- if (ret)
- goto out_free;
-
- ret = digest_verify(d, hmac);
- if (ret < 0)
- goto out_free;
- }
-
- list_for_each_entry(sv, &state->variables, list) {
- if (sv->start + sv->size > header.data_len)
- break;
- memcpy(sv->raw, data + 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) {
- dev_err(&state->dev, "cannot open %s\n", backend->path);
- return fd;
- }
-
- for (i = 0; i < RAW_BACKEND_COPIES; i++) {
- off_t offset = backend_raw->offset + i * backend_raw->stride;
-
- 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_save_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->stride;
-
- dev_dbg(&state->dev, "%s: 0x%08lx 0x%08zx\n",
- __func__, offset, size);
-
- ret = lseek(fd, offset, SEEK_SET);
- if (ret < 0)
- return ret;
-
- protect(fd, backend_raw->stride, offset, false);
-
- if (backend_raw->need_erase) {
- ret = erase(fd, backend_raw->stride, offset);
- if (ret)
- return ret;
- }
-
- ret = write_full(fd, buf, size);
- if (ret < 0)
- return ret;
-
- protect(fd, backend_raw->stride, offset, true);
-
- 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, fd, i;
- void *buf, *data, *hmac;
- struct backend_raw_header *header;
- struct state_variable *sv;
-
- buf = xzalloc(backend_raw->size_full);
-
- header = buf;
- data = buf + sizeof(*header);
- hmac = data + backend_raw->size_data;
-
- 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));
-
- if (backend_raw->backend.digest) {
- struct digest *d = backend_raw->backend.digest;
-
- ret = digest_init(d);
- if (ret)
- goto out_free;
-
- /* hmac over header and data */
- ret = digest_update(d, buf, sizeof(*header) + backend_raw->size_data);
- if (ret)
- goto out_free;
-
- ret = digest_final(d, hmac);
- if (ret < 0)
- goto out_free;
- }
-
- fd = open(backend->path, O_WRONLY);
- if (fd < 0)
- goto out_free;
-
- /* save other slots first */
- for (i = 0; i < RAW_BACKEND_COPIES; i++) {
- if (i == backend_raw->num_copy_read)
- continue;
-
- ret = backend_raw_save_one(backend_raw, state, fd,
- i, buf, backend_raw->size_full);
- if (ret)
- goto out_close;
-
- }
-
- ret = backend_raw_save_one(backend_raw, state, fd,
- backend_raw->num_copy_read, buf, backend_raw->size_full);
- 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;
-}
-
-#ifdef __BAREBOX__
-#define STAT_GIVES_SIZE(s) (S_ISREG(s.st_mode) || S_ISCHR(s.st_mode))
-#define BLKGET_GIVES_SIZE(s) 0
-#ifndef BLKGETSIZE64
-#define BLKGETSIZE64 -1
-#endif
-#else
-#define STAT_GIVES_SIZE(s) (S_ISREG(s.st_mode))
-#define BLKGET_GIVES_SIZE(s) (S_ISBLK(s.st_mode))
-#endif
-
-static int state_backend_raw_file_get_size(const char *path, size_t *out_size)
-{
- struct mtd_info_user meminfo;
- struct stat s;
- int ret;
-
- ret = stat(path, &s);
- if (ret)
- return -errno;
-
- /*
- * under Linux, stat() gives the size only on regular files
- * under barebox, it works on char dev, too
- */
- if (STAT_GIVES_SIZE(s)) {
- *out_size = s.st_size;
- return 0;
- }
-
- /* this works under Linux on block devs */
- if (BLKGET_GIVES_SIZE(s)) {
- int fd;
-
- fd = open(path, O_RDONLY);
- if (fd < 0)
- return -errno;
-
- ret = ioctl(fd, BLKGETSIZE64, out_size);
- close(fd);
- if (!ret)
- return 0;
- }
-
- /* try mtd next */
- ret = mtd_get_meminfo(path, &meminfo);
- if (!ret) {
- *out_size = meminfo.size;
- return 0;
- }
-
- return ret;
-}
-
-static int state_backend_raw_file_init_digest(struct state *state, struct state_backend_raw *backend_raw)
-{
- struct digest *digest;
- struct property *p;
- const char *algo;
- const unsigned char *key;
- int key_len, ret;
-
- p = of_find_property(state->root, "algo", NULL);
- if (!p) /* does not exist */
- return 0;
-
- ret = of_property_read_string(state->root, "algo", &algo);
- if (ret)
- return ret;
-
- if (!IS_ENABLED(CONFIG_STATE_CRYPTO)) {
- dev_err(&state->dev,
- "algo %s specified, but crypto support for state framework (CONFIG_STATE_CRYPTO) not enabled.\n",
- algo);
- return -EINVAL;
- }
-
- ret = keystore_get_secret(state->name, &key, &key_len);
- if (ret == -ENOENT) /* -ENOENT == does not exist */
- return -EPROBE_DEFER;
- else if (ret)
- return ret;
-
- digest = digest_alloc(algo);
- if (!digest) {
- dev_info(&state->dev, "algo %s not found - probe deferred\n", algo);
- return -EPROBE_DEFER;
- }
-
- ret = digest_set_key(digest, key, key_len);
- if (ret) {
- digest_free(digest);
- return ret;
- }
-
- backend_raw->backend.digest = digest;
- backend_raw->size_full = digest_length(digest);
-
- return 0;
-}
-
-/*
- * 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 *of_path,
- const char *path, off_t offset, size_t size)
-{
- struct state_backend_raw *backend_raw;
- struct state_backend *backend;
- struct state_variable *sv;
- struct mtd_info_user meminfo;
- size_t path_size = 0;
- int ret;
-
- if (state->backend)
- return -EBUSY;
-
- ret = state_backend_raw_file_get_size(path, &path_size);
- if (ret)
- return ret;
-
- if (size == 0)
- size = path_size;
- else if (offset + size > path_size)
- return -EINVAL;
-
- backend_raw = xzalloc(sizeof(*backend_raw));
-
- ret = state_backend_raw_file_init_digest(state, backend_raw);
- if (ret) {
- free(backend_raw);
- return ret;
- }
-
- backend = &backend_raw->backend;
- backend->save = state_backend_raw_save;
- backend->of_path = xstrdup(of_path);
- 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.flags & MTD_NO_ERASE)) {
- backend_raw->need_erase = true;
- backend_raw->size_full = ALIGN(backend_raw->size_full,
- meminfo.writesize);
- backend_raw->stride = ALIGN(backend_raw->size_full,
- meminfo.erasesize);
- dev_dbg(&state->dev, "is a mtd, adjust stepsize to %ld\n",
- backend_raw->stride);
- } else {
- backend_raw->stride = backend_raw->size_full;
- }
-
- if (backend_raw->size / backend_raw->stride < RAW_BACKEND_COPIES) {
- dev_err(&state->dev, "not enough space for two copies (%lu each)\n",
- backend_raw->stride);
- ret = -ENOSPC;
- goto err;
- }
-
- ret = state_backend_raw_load(backend, state);
- if (ret) {
- dev_warn(&state->dev, "load failed - using defaults\n");
- } else {
- dev_info(&state->dev, "load successful\n");
- state->dirty = 0;
- }
-
- /* ignore return value of load() */
- return 0;
-err:
- digest_free(backend_raw->backend.digest);
-
- free(backend_raw);
- return ret;
-}
diff --git a/common/state/Makefile b/common/state/Makefile
new file mode 100644
index 0000000..23f7286
--- /dev/null
+++ b/common/state/Makefile
@@ -0,0 +1,9 @@
+obj-y += state.o
+obj-y += state_variables.o
+obj-y += backend.o
+obj-y += backend_format_dtb.o
+obj-y += backend_format_raw.o
+obj-y += backend_storage.o
+obj-y += backend_bucket_direct.o
+obj-y += backend_bucket_circular.o
+obj-y += backend_bucket_cached.o
diff --git a/common/state/backend.c b/common/state/backend.c
new file mode 100644
index 0000000..2f2e6df
--- /dev/null
+++ b/common/state/backend.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2, as published by the Free Software Foundation.
+ *
+ * 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 <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/string.h>
+#include <malloc.h>
+#include <printk.h>
+
+#include "state.h"
+
+
+/**
+ * Save the state
+ * @param state
+ * @return
+ */
+int state_save(struct state *state)
+{
+ uint8_t *buf;
+ ssize_t len;
+ int ret;
+ struct state_backend *backend = &state->backend;
+
+ if (!state->dirty)
+ return 0;
+
+ ret = backend->format->pack(backend->format, state, &buf, &len);
+ if (ret) {
+ dev_err(&state->dev, "Failed to pack state with backend format %s, %d\n",
+ backend->format->name, ret);
+ return ret;
+ }
+
+ ret = state_storage_write(&backend->storage, buf, len);
+ if (ret) {
+ dev_err(&state->dev, "Failed to write packed state, %d\n", ret);
+ goto out;
+ }
+
+ state->dirty = 0;
+
+out:
+ free(buf);
+ return ret;
+}
+
+/**
+ * state_load - Loads a state from the backend
+ * @param state The state that should be updated to contain the loaded data
+ * @return 0 on success, -errno on failure. If no state is loaded the previous
+ * values remain in the state.
+ *
+ * This function uses the registered storage backend to read data. All data that
+ * we read is checked for integrity by the formatter. After that we unpack the
+ * data into our state.
+ */
+int state_load(struct state *state)
+{
+ uint8_t *buf;
+ ssize_t len;
+ ssize_t len_hint = 0;
+ int ret;
+ struct state_backend *backend = &state->backend;
+
+ if (backend->format->get_packed_len)
+ len_hint = backend->format->get_packed_len(backend->format,
+ state);
+ ret = state_storage_read(&backend->storage, backend->format,
+ state->magic, &buf, &len, len_hint);
+ if (ret) {
+ dev_err(&state->dev, "Failed to read state with format %s, %d\n",
+ backend->format->name, ret);
+ return ret;
+ }
+
+ ret = backend->format->unpack(backend->format, state, buf, len);
+ if (ret) {
+ dev_err(&state->dev, "Failed to unpack read data with format %s although verified, %d\n",
+ backend->format->name, ret);
+ goto out;
+ }
+
+ state->dirty = 0;
+
+out:
+ free(buf);
+ return ret;
+}
+
+static int state_format_init(struct state_backend *backend,
+ struct device_d *dev, const char *backend_format,
+ struct device_node *node, const char *state_name)
+{
+ int ret;
+
+ if (!strcmp(backend_format, "raw")) {
+ ret = backend_format_raw_create(&backend->format, node,
+ state_name, dev);
+ } else if (!strcmp(backend_format, "dtb")) {
+ ret = backend_format_dtb_create(&backend->format, dev);
+ } else {
+ dev_err(dev, "Invalid backend format %s\n",
+ backend_format);
+ return -EINVAL;
+ }
+
+ if (ret && ret != -EPROBE_DEFER)
+ dev_err(dev, "Failed to initialize format %s, %d\n",
+ backend_format, ret);
+
+ return ret;
+}
+
+static void state_format_free(struct state_backend_format *format)
+{
+ if (format->free)
+ format->free(format);
+}
+
+/**
+ * state_backend_init - Initiates the backend storage and format using the
+ * passed arguments
+ * @param backend state backend
+ * @param dev Device pointer used for prints
+ * @param node the DT device node corresponding to the state
+ * @param backend_format a string describing the format. Valid values are 'raw'
+ * and 'dtb' currently
+ * @param storage_path Path to the backend storage file/device/partition/...
+ * @param state_name Name of the state
+ * @param of_path Path in the devicetree
+ * @param stridesize stridesize in case we have a medium without eraseblocks.
+ * stridesize describes how far apart copies of the same data should be stored.
+ * For blockdevices it makes sense to align them on blocksize.
+ * @param storagetype Type of the storage backend. This may be NULL where we
+ * autoselect some backwardscompatible backend options
+ * @return 0 on success, -errno otherwise
+ */
+int state_backend_init(struct state_backend *backend, struct device_d *dev,
+ struct device_node *node, const char *backend_format,
+ const char *storage_path, const char *state_name, const
+ char *of_path, off_t offset, size_t max_size,
+ uint32_t stridesize, const char *storagetype)
+{
+ int ret;
+
+ ret = state_format_init(backend, dev, backend_format, node, state_name);
+ if (ret)
+ return ret;
+
+ ret = state_storage_init(&backend->storage, dev, storage_path, offset,
+ max_size, stridesize, storagetype);
+ if (ret)
+ goto out_free_format;
+
+ backend->of_path = of_path;
+
+ return 0;
+
+out_free_format:
+ state_format_free(backend->format);
+ backend->format = NULL;
+
+ return ret;
+}
+
+void state_backend_set_readonly(struct state_backend *backend)
+{
+ state_storage_set_readonly(&backend->storage);
+}
+
+void state_backend_free(struct state_backend *backend)
+{
+ state_storage_free(&backend->storage);
+ if (backend->format)
+ state_format_free(backend->format);
+}
diff --git a/common/state/backend_bucket_cached.c b/common/state/backend_bucket_cached.c
new file mode 100644
index 0000000..781ac2d
--- /dev/null
+++ b/common/state/backend_bucket_cached.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2, as published by the Free Software Foundation.
+ *
+ * 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 "state.h"
+
+struct state_backend_storage_bucket_cache {
+ struct state_backend_storage_bucket bucket;
+
+ struct state_backend_storage_bucket *raw;
+
+ u8 *data;
+ ssize_t data_len;
+ bool force_write;
+
+ /* For outputs */
+ struct device_d *dev;
+};
+
+static inline struct state_backend_storage_bucket_cache
+ *get_bucket_cache(struct state_backend_storage_bucket *bucket)
+{
+ return container_of(bucket,
+ struct state_backend_storage_bucket_cache,
+ bucket);
+}
+
+static inline void state_backend_bucket_cache_drop(
+ struct state_backend_storage_bucket_cache *cache)
+{
+ if (cache->data) {
+ free(cache->data);
+ cache->data = NULL;
+ cache->data_len = 0;
+ }
+}
+
+static int state_backend_bucket_cache_fill(
+ struct state_backend_storage_bucket_cache *cache)
+{
+ int ret;
+
+ ret = cache->raw->read(cache->raw, &cache->data, &cache->data_len);
+ if (ret == -EUCLEAN)
+ cache->force_write = true;
+ else if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int state_backend_bucket_cache_read(struct state_backend_storage_bucket *bucket,
+ uint8_t ** buf_out,
+ ssize_t * len_hint)
+{
+ struct state_backend_storage_bucket_cache *cache =
+ get_bucket_cache(bucket);
+ int ret;
+
+ if (!cache->data) {
+ ret = state_backend_bucket_cache_fill(cache);
+ if (ret)
+ return ret;
+ }
+
+ if (cache->data) {
+ *buf_out = xmemdup(cache->data, cache->data_len);
+ if (!*buf_out)
+ return -ENOMEM;
+ *len_hint = cache->data_len;
+ }
+
+ return 0;
+}
+
+static int state_backend_bucket_cache_write(struct state_backend_storage_bucket *bucket,
+ const uint8_t * buf, ssize_t len)
+{
+ struct state_backend_storage_bucket_cache *cache =
+ get_bucket_cache(bucket);
+ int ret;
+
+ if (!cache->force_write) {
+ if (!cache->data)
+ ret = state_backend_bucket_cache_fill(cache);
+
+ if (cache->data_len == len && !memcmp(cache->data, buf, len))
+ return 0;
+ }
+
+ state_backend_bucket_cache_drop(cache);
+
+ ret = cache->raw->write(cache->raw, buf, len);
+ if (ret)
+ return ret;
+
+ cache->data = xmemdup(buf, len);
+ cache->data_len = len;
+ return 0;
+}
+
+static int state_backend_bucket_cache_init(
+ struct state_backend_storage_bucket *bucket)
+{
+ struct state_backend_storage_bucket_cache *cache =
+ get_bucket_cache(bucket);
+
+ if (cache->raw->init) {
+ return cache->raw->init(cache->raw);
+ }
+
+ return 0;
+}
+
+static void state_backend_bucket_cache_free(
+ struct state_backend_storage_bucket *bucket)
+{
+ struct state_backend_storage_bucket_cache *cache =
+ get_bucket_cache(bucket);
+
+ state_backend_bucket_cache_drop(cache);
+ cache->raw->free(cache->raw);
+ free(cache);
+}
+
+int state_backend_bucket_cached_create(struct device_d *dev,
+ struct state_backend_storage_bucket *raw,
+ struct state_backend_storage_bucket **out)
+{
+ struct state_backend_storage_bucket_cache *cache;
+
+ cache = xzalloc(sizeof(*cache));
+ cache->raw = raw;
+ cache->dev = dev;
+
+ cache->bucket.free = state_backend_bucket_cache_free;
+ cache->bucket.read = state_backend_bucket_cache_read;
+ cache->bucket.write = state_backend_bucket_cache_write;
+ cache->bucket.init = state_backend_bucket_cache_init;
+
+ *out = &cache->bucket;
+
+ return 0;
+}
diff --git a/common/state/backend_bucket_circular.c b/common/state/backend_bucket_circular.c
new file mode 100644
index 0000000..72e165e
--- /dev/null
+++ b/common/state/backend_bucket_circular.c
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2, as published by the Free Software Foundation.
+ *
+ * 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 <fcntl.h>
+#include <fs.h>
+#include <libfile.h>
+#include <linux/kernel.h>
+#include <linux/mtd/mtd-abi.h>
+#include <malloc.h>
+#include <mtd/mtd-peb.h>
+#include <string.h>
+
+#include "state.h"
+
+
+struct state_backend_storage_bucket_circular {
+ struct state_backend_storage_bucket bucket;
+
+ unsigned int eraseblock; /* Which eraseblock is used */
+ ssize_t writesize; /* Alignment of writes */
+ ssize_t max_size; /* Maximum size of this bucket */
+
+ off_t write_area; /* Start of the write area (relative offset) */
+ uint32_t last_written_length; /* Size of the data written in the storage */
+
+#ifdef __BAREBOX__
+ struct mtd_info *mtd; /* mtd info (used for io in Barebox)*/
+#else
+ struct mtd_info_user *mtd;
+ int fd;
+#endif
+
+ /* For outputs */
+ struct device_d *dev;
+};
+
+struct state_backend_storage_bucket_circular_meta {
+ uint32_t magic;
+ uint32_t written_length;
+};
+
+static const uint32_t circular_magic = 0x14fa2d02;
+static const uint8_t free_pattern = 0xff;
+
+static inline struct state_backend_storage_bucket_circular
+ *get_bucket_circular(struct state_backend_storage_bucket *bucket)
+{
+ return container_of(bucket,
+ struct state_backend_storage_bucket_circular,
+ bucket);
+}
+
+#ifdef __BAREBOX__
+static int state_mtd_peb_read(struct state_backend_storage_bucket_circular *circ,
+ char *buf, int offset, int len)
+{
+ int ret;
+
+ ret = mtd_peb_read(circ->mtd, buf, circ->eraseblock, offset, len);
+ if (ret == -EBADMSG) {
+ ret = mtd_peb_torture(circ->mtd, circ->eraseblock);
+ if (ret == -EIO) {
+ dev_err(circ->dev, "Tortured eraseblock failed and is marked bad now, PEB %u\n",
+ circ->eraseblock);
+ return -EIO;
+ } else if (ret < 0) {
+ dev_err(circ->dev, "Failed to torture eraseblock, %d\n",
+ ret);
+ return ret;
+ }
+ /*
+ * Fill with invalid data so that the next write is done
+ * behind this area
+ */
+ memset(buf, 0, len);
+ ret = -EUCLEAN;
+ circ->write_area = 0;
+ dev_dbg(circ->dev, "PEB %u has ECC error, forcing rewrite\n",
+ circ->eraseblock);
+ } else if (ret == -EUCLEAN) {
+ dev_dbg(circ->dev, "PEB %u is unclean, forcing rewrite\n",
+ circ->eraseblock);
+ } else if (ret < 0) {
+ dev_err(circ->dev, "Failed to read PEB %u, %d\n",
+ circ->eraseblock, ret);
+ }
+
+ return ret;
+}
+
+static int state_mtd_peb_write(struct state_backend_storage_bucket_circular *circ,
+ const char *buf, int offset, int len)
+{
+ int ret;
+
+ ret = mtd_peb_write(circ->mtd, buf, circ->eraseblock, offset, len);
+ if (ret == -EBADMSG) {
+ ret = mtd_peb_torture(circ->mtd, circ->eraseblock);
+ if (ret == -EIO) {
+ dev_err(circ->dev, "Tortured eraseblock failed and is marked bad now, PEB %u\n",
+ circ->eraseblock);
+ return -EIO;
+ } else if (ret < 0) {
+ dev_err(circ->dev, "Failed to torture eraseblock, %d\n",
+ ret);
+ return ret;
+ }
+ ret = -EUCLEAN;
+ } else if (ret < 0 && ret != -EUCLEAN) {
+ dev_err(circ->dev, "Failed to write PEB %u, %d\n",
+ circ->eraseblock, ret);
+ }
+
+ return ret;
+}
+
+static int state_mtd_peb_erase(struct state_backend_storage_bucket_circular *circ)
+{
+ return mtd_peb_erase(circ->mtd, circ->eraseblock);
+}
+#else
+static int state_mtd_peb_read(struct state_backend_storage_bucket_circular *circ,
+ char *buf, int suboffset, int len)
+{
+ int ret;
+ off_t offset = suboffset;
+ struct mtd_ecc_stats stat1, stat2;
+ bool nostats = false;
+
+ offset += (off_t)circ->eraseblock * circ->mtd->erasesize;
+
+ ret = lseek(circ->fd, offset, SEEK_SET);
+ if (ret < 0) {
+ dev_err(circ->dev, "Failed to set circular read position to %lld, %d\n",
+ offset, ret);
+ return ret;
+ }
+
+ dev_dbg(circ->dev, "Read state from %ld length %zd\n", offset,
+ len);
+
+ ret = ioctl(circ->fd, ECCGETSTATS, &stat1);
+ if (ret)
+ nostats = true;
+
+ ret = read_full(circ->fd, buf, len);
+ if (ret < 0) {
+ dev_err(circ->dev, "Failed to read circular storage len %zd, %d\n",
+ len, ret);
+ free(buf);
+ return ret;
+ }
+
+ if (nostats)
+ return 0;
+
+ ret = ioctl(circ->fd, ECCGETSTATS, &stat2);
+ if (ret)
+ return 0;
+
+ if (stat2.failed - stat1.failed > 0) {
+ ret = -EUCLEAN;
+ dev_dbg(circ->dev, "PEB %u has ECC error, forcing rewrite\n",
+ circ->eraseblock);
+ } else if (stat2.corrected - stat1.corrected > 0) {
+ ret = -EUCLEAN;
+ dev_dbg(circ->dev, "PEB %u is unclean, forcing rewrite\n",
+ circ->eraseblock);
+ }
+
+ return ret;
+}
+
+static int state_mtd_peb_write(struct state_backend_storage_bucket_circular *circ,
+ const char *buf, int suboffset, int len)
+{
+ int ret;
+ off_t offset = suboffset;
+
+ offset += circ->eraseblock * circ->mtd->erasesize;
+
+ ret = lseek(circ->fd, offset, SEEK_SET);
+ if (ret < 0) {
+ dev_err(circ->dev, "Failed to set position for circular write %ld, %d\n",
+ offset, ret);
+ return ret;
+ }
+
+ ret = write_full(circ->fd, buf, len);
+ if (ret < 0) {
+ dev_err(circ->dev, "Failed to write circular to %ld length %zd, %d\n",
+ offset, len, ret);
+ return ret;
+ }
+
+ /*
+ * We keep the fd open, so flush is necessary. We ignore the return
+ * value as flush is currently not supported for mtd under linux.
+ */
+ flush(circ->fd);
+
+ dev_dbg(circ->dev, "Written state to offset %ld length %zd data length %zd\n",
+ offset, len, len);
+
+ return 0;
+}
+
+static int state_mtd_peb_erase(struct state_backend_storage_bucket_circular *circ)
+{
+ return erase(circ->fd, circ->mtd->erasesize,
+ (off_t)circ->eraseblock * circ->mtd->erasesize);
+}
+#endif
+
+static int state_backend_bucket_circular_read(struct state_backend_storage_bucket *bucket,
+ uint8_t ** buf_out,
+ ssize_t * len_hint)
+{
+ struct state_backend_storage_bucket_circular *circ =
+ get_bucket_circular(bucket);
+ ssize_t read_len;
+ off_t offset;
+ uint8_t *buf;
+ int ret;
+
+ /* Storage is empty */
+ if (circ->write_area == 0)
+ return -ENODATA;
+
+ if (!circ->last_written_length) {
+ /*
+ * Last write did not contain length information, assuming old
+ * state and reading from the beginning.
+ */
+ offset = 0;
+ read_len = min(circ->write_area, (off_t)(circ->max_size -
+ sizeof(struct state_backend_storage_bucket_circular_meta)));
+ circ->write_area = 0;
+ dev_dbg(circ->dev, "Detected old on-storage format\n");
+ } else if (circ->last_written_length > circ->write_area
+ || !IS_ALIGNED(circ->last_written_length, circ->writesize)) {
+ circ->write_area = 0;
+ dev_err(circ->dev, "Error, invalid number of bytes written last time %d\n",
+ circ->last_written_length);
+ return -EINVAL;
+ } else {
+ /*
+ * Normally we read at the end of the non-free area. The length
+ * of the read is then what we read from the meta data
+ * (last_written_length)
+ */
+ read_len = circ->last_written_length;
+ offset = circ->write_area - read_len;
+ }
+
+ buf = xmalloc(read_len);
+ if (!buf)
+ return -ENOMEM;
+
+ dev_dbg(circ->dev, "Read state from PEB %u global offset %ld length %zd\n",
+ circ->eraseblock, offset, read_len);
+
+ ret = state_mtd_peb_read(circ, buf, offset, read_len);
+ if (ret < 0 && ret != -EUCLEAN) {
+ dev_err(circ->dev, "Failed to read circular storage len %zd, %d\n",
+ read_len, ret);
+ free(buf);
+ return ret;
+ }
+
+ *buf_out = buf;
+ *len_hint = read_len - sizeof(struct state_backend_storage_bucket_circular_meta);
+
+ return ret;
+}
+
+static int state_backend_bucket_circular_write(struct state_backend_storage_bucket *bucket,
+ const uint8_t * buf,
+ ssize_t len)
+{
+ struct state_backend_storage_bucket_circular *circ =
+ get_bucket_circular(bucket);
+ off_t offset;
+ struct state_backend_storage_bucket_circular_meta *meta;
+ uint32_t written_length = ALIGN(len + sizeof(*meta), circ->writesize);
+ int ret;
+ uint8_t *write_buf;
+
+ if (written_length > circ->max_size) {
+ dev_err(circ->dev, "Error, state data too big to be written, to write: %zd, writesize: %zd, length: %zd, available: %zd\n",
+ written_length, circ->writesize, len, circ->max_size);
+ return -E2BIG;
+ }
+
+ /*
+ * We need zero initialization so that our data comparisons don't show
+ * random changes
+ */
+ write_buf = xzalloc(written_length);
+ if (!write_buf)
+ return -ENOMEM;
+
+ memcpy(write_buf, buf, len);
+ meta = (struct state_backend_storage_bucket_circular_meta *)
+ (write_buf + written_length - sizeof(*meta));
+ meta->magic = circular_magic;
+ meta->written_length = written_length;
+
+ if (circ->write_area + written_length >= circ->max_size) {
+ circ->write_area = 0;
+ }
+ /*
+ * If the write area is at the beginning of the page, erase it and write
+ * at offset 0. As we only erase right before writing there are no
+ * conditions where we regularly erase a block multiple times without
+ * writing.
+ */
+ if (circ->write_area == 0) {
+ dev_dbg(circ->dev, "Erasing PEB %u\n", circ->eraseblock);
+ ret = state_mtd_peb_erase(circ);
+ if (ret) {
+ dev_err(circ->dev, "Failed to erase PEB %u\n",
+ circ->eraseblock);
+ goto out_free;
+ }
+ }
+
+ offset = circ->write_area;
+
+ /*
+ * Update write_area before writing. The write operation may put
+ * arbitrary amount of the data into the storage before failing. In this
+ * case we want to start after that area.
+ */
+ circ->write_area += written_length;
+
+ ret = state_mtd_peb_write(circ, write_buf, offset, written_length);
+ if (ret < 0 && ret != -EUCLEAN) {
+ dev_err(circ->dev, "Failed to write circular to %ld length %zd, %d\n",
+ offset, written_length, ret);
+ goto out_free;
+ }
+
+ dev_dbg(circ->dev, "Written state to PEB %u offset %ld length %zd data length %zd\n",
+ circ->eraseblock, offset, written_length, len);
+
+out_free:
+ free(write_buf);
+ return ret;
+}
+
+/**
+ * state_backend_bucket_circular_init - Initialize circular bucket
+ * @param bucket
+ * @return 0 on success, -errno otherwise
+ *
+ * This function searches for the beginning of the written area from the end of
+ * the MTD device. This way it knows where the data ends and where the free area
+ * starts.
+ */
+static int state_backend_bucket_circular_init(
+ struct state_backend_storage_bucket *bucket)
+{
+ struct state_backend_storage_bucket_circular *circ =
+ get_bucket_circular(bucket);
+ int sub_offset;
+ uint32_t written_length = 0;
+ uint8_t *buf;
+
+ buf = xmalloc(circ->writesize);
+ if (!buf)
+ return -ENOMEM;
+
+ for (sub_offset = circ->max_size - circ->writesize; sub_offset >= 0;
+ sub_offset -= circ->writesize) {
+ int ret;
+
+ ret = state_mtd_peb_read(circ, buf, sub_offset,
+ circ->writesize);
+ if (ret)
+ return ret;
+
+ ret = mtd_buf_all_ff(buf, circ->writesize);
+ if (!ret) {
+ struct state_backend_storage_bucket_circular_meta *meta;
+
+ meta = (struct state_backend_storage_bucket_circular_meta *)
+ (buf + circ->writesize - sizeof(*meta));
+
+ if (meta->magic != circular_magic)
+ written_length = 0;
+ else
+ written_length = meta->written_length;
+
+ break;
+ }
+ }
+
+ circ->write_area = sub_offset + circ->writesize;
+ circ->last_written_length = written_length;
+
+ free(buf);
+
+ return 0;
+}
+
+static void state_backend_bucket_circular_free(struct
+ state_backend_storage_bucket
+ *bucket)
+{
+ struct state_backend_storage_bucket_circular *circ =
+ get_bucket_circular(bucket);
+
+ free(circ);
+}
+
+#ifdef __BAREBOX__
+static int bucket_circular_is_block_bad(struct state_backend_storage_bucket_circular *circ)
+{
+ int ret;
+
+ ret = mtd_peb_is_bad(circ->mtd, circ->eraseblock);
+ if (ret < 0)
+ dev_err(circ->dev, "Failed to determine whether eraseblock %u is bad, %d\n",
+ circ->eraseblock, ret);
+
+ return ret;
+}
+#else
+static int bucket_circular_is_block_bad(struct state_backend_storage_bucket_circular *circ)
+{
+ int ret;
+ loff_t offs = circ->eraseblock * circ->mtd->erasesize;
+
+ ret = ioctl(circ->fd, MEMGETBADBLOCK, &offs);
+ if (ret < 0)
+ dev_err(circ->dev, "Failed to use ioctl to check for bad block at offset %ld, %d\n",
+ offs, ret);
+
+ return ret;
+}
+#endif
+
+int state_backend_bucket_circular_create(struct device_d *dev, const char *path,
+ struct state_backend_storage_bucket **bucket,
+ unsigned int eraseblock,
+ ssize_t writesize,
+ struct mtd_info_user *mtd_uinfo,
+ bool lazy_init)
+{
+ struct state_backend_storage_bucket_circular *circ;
+ int ret;
+
+ circ = xzalloc(sizeof(*circ));
+ circ->eraseblock = eraseblock;
+ circ->writesize = writesize;
+ circ->max_size = mtd_uinfo->erasesize;
+ circ->dev = dev;
+
+#ifdef __BAREBOX__
+ circ->mtd = mtd_uinfo->mtd;
+#else
+ circ->mtd = xzalloc(sizeof(*mtd_uinfo));
+ memcpy(circ->mtd, mtd_uinfo, sizeof(*mtd_uinfo));
+ circ->fd = open(path, O_RDWR);
+ if (circ->fd < 0) {
+ pr_err("Failed to open circular bucket '%s'\n", path);
+ return -errno;
+ }
+#endif
+
+ ret = bucket_circular_is_block_bad(circ);
+ if (ret) {
+ dev_info(dev, "Not using eraseblock %u, it is marked as bad (%d)\n",
+ circ->eraseblock, ret);
+ ret = -EIO;
+ goto out_free;
+ }
+
+ circ->bucket.read = state_backend_bucket_circular_read;
+ circ->bucket.write = state_backend_bucket_circular_write;
+ circ->bucket.free = state_backend_bucket_circular_free;
+ *bucket = &circ->bucket;
+
+ if (!lazy_init) {
+ ret = state_backend_bucket_circular_init(*bucket);
+ if (ret)
+ goto out_free;
+ } else {
+ circ->bucket.init = state_backend_bucket_circular_init;
+ }
+
+ return 0;
+
+out_free:
+#ifndef __BAREBOX__
+ close(circ->fd);
+#endif
+ free(circ);
+
+ return ret;
+}
diff --git a/common/state/backend_bucket_direct.c b/common/state/backend_bucket_direct.c
new file mode 100644
index 0000000..08892f0
--- /dev/null
+++ b/common/state/backend_bucket_direct.c
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2, as published by the Free Software Foundation.
+ *
+ * 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 <fcntl.h>
+#include <fs.h>
+#include <libfile.h>
+#include <linux/kernel.h>
+#include <malloc.h>
+#include <printk.h>
+
+#include "state.h"
+
+struct state_backend_storage_bucket_direct {
+ struct state_backend_storage_bucket bucket;
+
+ ssize_t offset;
+ ssize_t max_size;
+
+ int fd;
+
+ struct device_d *dev;
+};
+
+struct state_backend_storage_bucket_direct_meta {
+ uint32_t magic;
+ uint32_t written_length;
+};
+static const uint32_t direct_magic = 0x2354fdf3;
+
+static inline struct state_backend_storage_bucket_direct
+ *get_bucket_direct(struct state_backend_storage_bucket *bucket)
+{
+ return container_of(bucket, struct state_backend_storage_bucket_direct,
+ bucket);
+}
+
+static int state_backend_bucket_direct_read(struct state_backend_storage_bucket
+ *bucket, uint8_t ** buf_out,
+ ssize_t * len_hint)
+{
+ struct state_backend_storage_bucket_direct *direct =
+ get_bucket_direct(bucket);
+ struct state_backend_storage_bucket_direct_meta meta;
+ ssize_t read_len;
+ uint8_t *buf;
+ int ret;
+
+ ret = lseek(direct->fd, direct->offset, SEEK_SET);
+ if (ret < 0) {
+ dev_err(direct->dev, "Failed to seek file, %d\n", ret);
+ return ret;
+ }
+ ret = read_full(direct->fd, &meta, sizeof(meta));
+ if (ret < 0) {
+ dev_err(direct->dev, "Failed to read meta data from file, %d\n", ret);
+ return ret;
+ }
+ if (meta.magic == direct_magic) {
+ read_len = meta.written_length;
+ } else {
+ if (*len_hint)
+ read_len = *len_hint;
+ else
+ read_len = direct->max_size;
+ ret = lseek(direct->fd, direct->offset, SEEK_SET);
+ if (ret < 0) {
+ dev_err(direct->dev, "Failed to seek file, %d\n", ret);
+ return ret;
+ }
+ }
+ if (direct->max_size)
+ read_len = min(read_len, direct->max_size);
+
+ buf = xmalloc(read_len);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = read_full(direct->fd, buf, read_len);
+ if (ret < 0) {
+ dev_err(direct->dev, "Failed to read from file, %d\n", ret);
+ free(buf);
+ return ret;
+ }
+
+ *buf_out = buf;
+ *len_hint = read_len;
+
+ return 0;
+}
+
+static int state_backend_bucket_direct_write(struct state_backend_storage_bucket
+ *bucket, const uint8_t * buf,
+ ssize_t len)
+{
+ struct state_backend_storage_bucket_direct *direct =
+ get_bucket_direct(bucket);
+ int ret;
+ struct state_backend_storage_bucket_direct_meta meta;
+
+ if (direct->max_size && len > direct->max_size)
+ return -E2BIG;
+
+ ret = lseek(direct->fd, direct->offset, SEEK_SET);
+ if (ret < 0) {
+ dev_err(direct->dev, "Failed to seek file, %d\n", ret);
+ return ret;
+ }
+
+ meta.magic = direct_magic;
+ meta.written_length = len;
+ ret = write_full(direct->fd, &meta, sizeof(meta));
+ if (ret < 0) {
+ dev_err(direct->dev, "Failed to write metadata to file, %d\n", ret);
+ return ret;
+ }
+
+ ret = write_full(direct->fd, buf, len);
+ if (ret < 0) {
+ dev_err(direct->dev, "Failed to write file, %d\n", ret);
+ return ret;
+ }
+
+ ret = flush(direct->fd);
+ if (ret < 0) {
+ dev_err(direct->dev, "Failed to flush file, %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void state_backend_bucket_direct_free(struct
+ state_backend_storage_bucket
+ *bucket)
+{
+ struct state_backend_storage_bucket_direct *direct =
+ get_bucket_direct(bucket);
+
+ close(direct->fd);
+ free(direct);
+}
+
+int state_backend_bucket_direct_create(struct device_d *dev, const char *path,
+ struct state_backend_storage_bucket **bucket,
+ off_t offset, ssize_t max_size)
+{
+ int fd;
+ struct state_backend_storage_bucket_direct *direct;
+
+ fd = open(path, O_RDWR);
+ if (fd < 0) {
+ dev_err(dev, "Failed to open file '%s', %d\n", path, -errno);
+ close(fd);
+ return -errno;
+ }
+
+ direct = xzalloc(sizeof(*direct));
+ direct->offset = offset;
+ direct->max_size = max_size;
+ direct->fd = fd;
+ direct->dev = dev;
+
+ direct->bucket.read = state_backend_bucket_direct_read;
+ direct->bucket.write = state_backend_bucket_direct_write;
+ direct->bucket.free = state_backend_bucket_direct_free;
+ *bucket = &direct->bucket;
+
+ return 0;
+}
diff --git a/common/state/backend_format_dtb.c b/common/state/backend_format_dtb.c
new file mode 100644
index 0000000..dc19c88
--- /dev/null
+++ b/common/state/backend_format_dtb.c
@@ -0,0 +1,150 @@
+/*
+ * 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>
+ * Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2, as published by the Free Software Foundation.
+ *
+ * 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 <of.h>
+#include <linux/kernel.h>
+#include <malloc.h>
+
+#include "state.h"
+
+struct state_backend_format_dtb {
+ struct state_backend_format format;
+
+ struct device_node *root;
+
+ /* For outputs */
+ struct device_d *dev;
+};
+
+static inline struct state_backend_format_dtb *get_format_dtb(struct
+ state_backend_format
+ *format)
+{
+ return container_of(format, struct state_backend_format_dtb, format);
+}
+
+static int state_backend_format_dtb_verify(struct state_backend_format *format,
+ uint32_t magic, const uint8_t * buf,
+ ssize_t len)
+{
+ struct state_backend_format_dtb *fdtb = get_format_dtb(format);
+ struct device_node *root;
+ struct fdt_header *fdt = (struct fdt_header *)buf;
+ size_t dtb_len = fdt32_to_cpu(fdt->totalsize);
+
+ if (dtb_len > len) {
+ dev_err(fdtb->dev, "Error, stored DTB length (%d) longer than read buffer (%d)\n",
+ dtb_len, len);
+ return -EINVAL;
+ }
+
+ if (fdtb->root) {
+ of_delete_node(fdtb->root);
+ fdtb->root = NULL;
+ }
+
+ root = of_unflatten_dtb(buf);
+ if (IS_ERR(root)) {
+ dev_err(fdtb->dev, "Failed to unflatten dtb from buffer with length %zd, %ld\n",
+ len, PTR_ERR(root));
+ return PTR_ERR(root);
+ }
+
+ fdtb->root = root;
+
+ return 0;
+}
+
+static int state_backend_format_dtb_unpack(struct state_backend_format *format,
+ struct state *state,
+ const uint8_t * buf, ssize_t len)
+{
+ struct state_backend_format_dtb *fdtb = get_format_dtb(format);
+ int ret;
+
+ if (!fdtb->root) {
+ state_backend_format_dtb_verify(format, 0, buf, len);
+ }
+
+ ret = state_from_node(state, fdtb->root, 0);
+ of_delete_node(fdtb->root);
+ fdtb->root = NULL;
+
+ return ret;
+}
+
+static int state_backend_format_dtb_pack(struct state_backend_format *format,
+ struct state *state, uint8_t ** buf,
+ ssize_t * len)
+{
+ struct state_backend_format_dtb *fdtb = get_format_dtb(format);
+ struct device_node *root;
+ struct fdt_header *fdt;
+
+ root = state_to_node(state, NULL, STATE_CONVERT_TO_NODE);
+ if (IS_ERR(root)) {
+ dev_err(fdtb->dev, "Failed to convert state to device node, %ld\n",
+ PTR_ERR(root));
+ return PTR_ERR(root);
+ }
+
+ fdt = of_flatten_dtb(root);
+ if (!fdt) {
+ dev_err(fdtb->dev, "Failed to create flattened dtb\n");
+ of_delete_node(root);
+ return -EINVAL;
+ }
+
+ *buf = (uint8_t *) fdt;
+ *len = fdt32_to_cpu(fdt->totalsize);
+
+ if (fdtb->root)
+ of_delete_node(fdtb->root);
+ fdtb->root = root;
+
+ free(fdt);
+
+ return 0;
+}
+
+static void state_backend_format_dtb_free(struct state_backend_format *format)
+{
+ struct state_backend_format_dtb *fdtb = get_format_dtb(format);
+
+ free(fdtb);
+}
+
+int backend_format_dtb_create(struct state_backend_format **format,
+ struct device_d *dev)
+{
+ struct state_backend_format_dtb *dtb;
+
+ dtb = xzalloc(sizeof(*dtb));
+ if (!dtb)
+ return -ENOMEM;
+
+ dtb->dev = dev;
+ dtb->format.pack = state_backend_format_dtb_pack;
+ dtb->format.unpack = state_backend_format_dtb_unpack;
+ dtb->format.verify = state_backend_format_dtb_verify;
+ dtb->format.free = state_backend_format_dtb_free;
+ dtb->format.name = "dtb";
+ *format = &dtb->format;
+
+ return 0;
+}
diff --git a/common/state/backend_format_raw.c b/common/state/backend_format_raw.c
new file mode 100644
index 0000000..4209424
--- /dev/null
+++ b/common/state/backend_format_raw.c
@@ -0,0 +1,329 @@
+/*
+ * 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>
+ * Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2, as published by the Free Software Foundation.
+ *
+ * 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 <common.h>
+#include <crypto/keystore.h>
+#include <digest.h>
+#include <linux/kernel.h>
+#include <malloc.h>
+#include <crc.h>
+#include <of.h>
+#include <crc.h>
+
+#include "state.h"
+
+struct state_backend_format_raw {
+ struct state_backend_format format;
+
+ struct digest *digest;
+ unsigned int digest_length;
+
+ /* For outputs */
+ struct device_d *dev;
+};
+
+struct backend_raw_header {
+ uint32_t magic;
+ uint16_t reserved;
+ uint16_t data_len;
+ uint32_t data_crc;
+ uint32_t header_crc;
+};
+
+const int format_raw_min_length = sizeof(struct backend_raw_header);
+
+static inline struct state_backend_format_raw *get_format_raw(
+ struct state_backend_format *format)
+{
+ return container_of(format, struct state_backend_format_raw, format);
+}
+
+static int backend_format_raw_verify(struct state_backend_format *format,
+ uint32_t magic, const uint8_t * buf,
+ ssize_t len)
+{
+ uint32_t crc;
+ struct backend_raw_header *header;
+ int d_len = 0;
+ int ret;
+ const uint8_t *data;
+ struct state_backend_format_raw *backend_raw = get_format_raw(format);
+ ssize_t complete_len;
+
+ if (len < format_raw_min_length) {
+ dev_err(backend_raw->dev, "Error, buffer length (%d) is shorter than the minimum required header length\n",
+ len);
+ return -EINVAL;
+ }
+
+ header = (struct backend_raw_header *)buf;
+ crc = crc32(0, header, sizeof(*header) - sizeof(uint32_t));
+ if (crc != header->header_crc) {
+ dev_err(backend_raw->dev, "Error, invalid header crc in raw format, calculated 0x%08x, found 0x%08x\n",
+ crc, header->header_crc);
+ return -EINVAL;
+ }
+
+ if (magic && magic != header->magic) {
+ dev_err(backend_raw->dev, "Error, invalid magic in raw format 0x%08x, should be 0x%08x\n",
+ header->magic, magic);
+ return -EINVAL;
+ }
+
+ if (backend_raw->digest) {
+ d_len = digest_length(backend_raw->digest);
+ }
+
+ complete_len = header->data_len + d_len + format_raw_min_length;
+ if (complete_len > len) {
+ dev_err(backend_raw->dev, "Error, invalid data_len %u in header, have data of len %zu\n",
+ header->data_len, len);
+ return -EINVAL;
+ }
+
+ data = buf + sizeof(*header);
+
+ crc = crc32(0, data, header->data_len);
+ if (crc != header->data_crc) {
+ dev_err(backend_raw->dev, "invalid data crc, calculated 0x%08x, found 0x%08x\n",
+ crc, header->data_crc);
+ return -EINVAL;
+ }
+
+ if (backend_raw->digest) {
+ struct digest *d = backend_raw->digest;
+ const void *hmac = data + header->data_len;
+
+ ret = digest_init(d);
+ if (ret) {
+ dev_err(backend_raw->dev, "Failed to initialize digest, %d\n",
+ ret);
+ return ret;
+ }
+
+ /* hmac over header and data */
+ ret = digest_update(d, buf, sizeof(*header) + header->data_len);
+ if (ret) {
+ dev_err(backend_raw->dev, "Failed to update digest, %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = digest_verify(d, hmac);
+ if (ret < 0) {
+ dev_err(backend_raw->dev, "Failed to verify data, hmac, %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int backend_format_raw_unpack(struct state_backend_format *format,
+ struct state *state, const uint8_t * buf,
+ ssize_t len)
+{
+ struct state_variable *sv;
+ const struct backend_raw_header *header;
+ const uint8_t *data;
+ struct state_backend_format_raw *backend_raw = get_format_raw(format);
+
+ header = (const struct backend_raw_header *)buf;
+ data = buf + sizeof(*header);
+
+ list_for_each_entry(sv, &state->variables, list) {
+ if (sv->start + sv->size > header->data_len) {
+ dev_err(backend_raw->dev, "State variable ends behind valid data, %s\n",
+ sv->name);
+ continue;
+ }
+ memcpy(sv->raw, data + sv->start, sv->size);
+ }
+
+ return 0;
+}
+
+static int backend_format_raw_pack(struct state_backend_format *format,
+ struct state *state, uint8_t ** buf_out,
+ ssize_t * len_out)
+{
+ struct state_backend_format_raw *backend_raw = get_format_raw(format);
+ void *buf, *data, *hmac;
+ struct backend_raw_header *header;
+ struct state_variable *sv;
+ unsigned int size_full;
+ unsigned int size_data;
+ int ret;
+
+ sv = list_last_entry(&state->variables, struct state_variable, list);
+ size_data = sv->start + sv->size;
+ size_full = size_data + sizeof(*header) + backend_raw->digest_length;
+
+ buf = xzalloc(size_full);
+ if (!buf)
+ return -ENOMEM;
+
+ header = buf;
+ data = buf + sizeof(*header);
+ hmac = data + size_data;
+
+ list_for_each_entry(sv, &state->variables, list)
+ memcpy(data + sv->start, sv->raw, sv->size);
+
+ header->magic = state->magic;
+ header->data_len = size_data;
+ header->data_crc = crc32(0, data, size_data);
+ header->header_crc = crc32(0, header,
+ sizeof(*header) - sizeof(uint32_t));
+
+ if (backend_raw->digest) {
+ struct digest *d = backend_raw->digest;
+
+ ret = digest_init(d);
+ if (ret) {
+ dev_err(backend_raw->dev, "Failed to initialize digest for packing, %d\n",
+ ret);
+ goto out_free;
+ }
+
+ /* hmac over header and data */
+ ret = digest_update(d, buf, sizeof(*header) + size_data);
+ if (ret) {
+ dev_err(backend_raw->dev, "Failed to update digest for packing, %d\n",
+ ret);
+ goto out_free;
+ }
+
+ ret = digest_final(d, hmac);
+ if (ret < 0) {
+ dev_err(backend_raw->dev, "Failed to finish digest for packing, %d\n",
+ ret);
+ goto out_free;
+ }
+ }
+
+ *buf_out = buf;
+ *len_out = size_full;
+
+ return 0;
+
+out_free:
+ free(buf);
+
+ return ret;
+}
+
+static void backend_format_raw_free(struct state_backend_format *format)
+{
+ struct state_backend_format_raw *backend_raw = get_format_raw(format);
+
+ free(backend_raw);
+}
+
+static int backend_format_raw_init_digest(struct state_backend_format_raw *raw,
+ struct device_node *root,
+ const char *secret_name)
+{
+ struct digest *digest;
+ struct property *p;
+ const char *algo;
+ const unsigned char *key;
+ int key_len, ret;
+
+ p = of_find_property(root, "algo", NULL);
+ if (!p) /* does not exist */
+ return 0;
+
+ ret = of_property_read_string(root, "algo", &algo);
+ if (ret)
+ return ret;
+
+ if (!IS_ENABLED(CONFIG_STATE_CRYPTO) && IS_ENABLED(__BAREBOX__)) {
+ dev_err(raw->dev, "algo %s specified, but crypto support for state framework (CONFIG_STATE_CRYPTO) not enabled.\n",
+ algo);
+ return -EINVAL;
+ }
+
+ ret = keystore_get_secret(secret_name, &key, &key_len);
+ if (ret == -ENOENT) { /* -ENOENT == does not exist */
+ dev_info(raw->dev, "Could not get secret '%s' - probe deferred\n",
+ secret_name);
+ return -EPROBE_DEFER;
+ } else if (ret) {
+ return ret;
+ }
+
+ digest = digest_alloc(algo);
+ if (!digest) {
+ dev_info(raw->dev, "algo %s not found - probe deferred\n",
+ algo);
+ return -EPROBE_DEFER;
+ }
+
+ ret = digest_set_key(digest, key, key_len);
+ if (ret) {
+ digest_free(digest);
+ return ret;
+ }
+
+ raw->digest = digest;
+ raw->digest_length = digest_length(digest);
+
+ return 0;
+}
+
+int backend_format_raw_create(struct state_backend_format **format,
+ struct device_node *node, const char *secret_name,
+ struct device_d *dev)
+{
+ struct state_backend_format_raw *raw;
+ int ret;
+
+ raw = xzalloc(sizeof(*raw));
+ if (!raw)
+ return -ENOMEM;
+
+ raw->dev = dev;
+ ret = backend_format_raw_init_digest(raw, node, secret_name);
+ if (ret == -EPROBE_DEFER) {
+ return ret;
+ } else if (ret) {
+ dev_err(raw->dev, "Failed initializing digest for raw format, %d\n",
+ ret);
+ free(raw);
+ return ret;
+ }
+
+ raw->format.pack = backend_format_raw_pack;
+ raw->format.unpack = backend_format_raw_unpack;
+ raw->format.verify = backend_format_raw_verify;
+ raw->format.free = backend_format_raw_free;
+ raw->format.name = "raw";
+ *format = &raw->format;
+
+ return 0;
+}
+
+struct digest *state_backend_format_raw_get_digest(struct state_backend_format
+ *format)
+{
+ struct state_backend_format_raw *backend_raw = get_format_raw(format);
+
+ return backend_raw->digest;
+}
diff --git a/common/state/backend_storage.c b/common/state/backend_storage.c
new file mode 100644
index 0000000..efe8bac
--- /dev/null
+++ b/common/state/backend_storage.c
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2, as published by the Free Software Foundation.
+ *
+ * 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 <fcntl.h>
+#include <fs.h>
+#include <libfile.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mtd/mtd-abi.h>
+#include <linux/stat.h>
+#include <malloc.h>
+#include <printk.h>
+
+#include "state.h"
+
+const unsigned int min_copies_written = 1;
+
+static int bucket_lazy_init(struct state_backend_storage_bucket *bucket)
+{
+ int ret;
+
+ if (bucket->initialized)
+ return 0;
+
+ if (bucket->init) {
+ ret = bucket->init(bucket);
+ if (ret)
+ return ret;
+ }
+ bucket->initialized = true;
+
+ return 0;
+}
+
+/**
+ * state_storage_write - Writes the given data to the storage
+ * @param storage Storage object
+ * @param buf Buffer with the data
+ * @param len Length of the buffer
+ * @return 0 on success, -errno otherwise
+ *
+ * This function iterates over all registered buckets and executes a write
+ * operation on all of them. Writes are always in the same sequence. This
+ * ensures, that reading in the same sequence will always return the latest
+ * written valid data first.
+ * We try to at least write min_copies_written. If this fails we return with an
+ * error.
+ */
+int state_storage_write(struct state_backend_storage *storage,
+ const uint8_t * buf, ssize_t len)
+{
+ struct state_backend_storage_bucket *bucket;
+ int ret;
+ int copies_written = 0;
+
+ if (storage->readonly)
+ return 0;
+
+ list_for_each_entry(bucket, &storage->buckets, bucket_list) {
+ ret = bucket_lazy_init(bucket);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to init bucket/write state backend bucket, %d\n",
+ ret);
+ continue;
+ }
+
+ ret = bucket->write(bucket, buf, len);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to write state backend bucket, %d\n",
+ ret);
+ } else {
+ ++copies_written;
+ }
+ }
+
+ if (copies_written >= min_copies_written)
+ return 0;
+
+ dev_err(storage->dev, "Failed to write state to at least %d buckets. Successfully written to %d buckets\n",
+ min_copies_written, copies_written);
+ return -EIO;
+}
+
+/**
+ * state_storage_restore_consistency - Restore consistency on all storage backends
+ * @param storage Storage object
+ * @param buf Buffer with valid data that should be on all buckets after this operation
+ * @param len Length of the buffer
+ * @return 0 on success, -errno otherwise
+ *
+ * This function brings valid data onto all buckets we have to ensure that all
+ * data copies are in sync. In the current implementation we just write the data
+ * to all buckets. Bucket implementations that need to keep the number of writes
+ * low, can read their own copy first and compare it.
+ */
+int state_storage_restore_consistency(struct state_backend_storage *storage,
+ const uint8_t * buf, ssize_t len)
+{
+ return state_storage_write(storage, buf, len);
+}
+
+/**
+ * state_storage_read - Reads valid data from the backend storage
+ * @param storage Storage object
+ * @param format Format of the data that is stored
+ * @param magic state magic value
+ * @param buf The newly allocated data area will be stored in this pointer
+ * @param len The resulting length of the buffer
+ * @param len_hint Hint of how big the data may be.
+ * @return 0 on success, -errno otherwise. buf and len will be set to valid
+ * values on success.
+ *
+ * This function goes through all buckets and tries to read valid data from
+ * them. The first bucket which returns data that is successfully verified
+ * against the data format is used. To ensure the validity of all bucket copies,
+ * we restore the consistency at the end.
+ */
+int state_storage_read(struct state_backend_storage *storage,
+ struct state_backend_format *format,
+ uint32_t magic, uint8_t ** buf, ssize_t * len,
+ ssize_t len_hint)
+{
+ struct state_backend_storage_bucket *bucket;
+ int ret;
+
+ list_for_each_entry(bucket, &storage->buckets, bucket_list) {
+ *len = len_hint;
+ ret = bucket_lazy_init(bucket);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to init bucket/read state backend bucket, %d\n",
+ ret);
+ continue;
+ }
+
+ ret = bucket->read(bucket, buf, len);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to read from state backend bucket, trying next, %d\n",
+ ret);
+ continue;
+ }
+ ret = format->verify(format, magic, *buf, *len);
+ if (!ret) {
+ goto found;
+ }
+ free(*buf);
+ dev_warn(storage->dev, "Failed to verify read copy, trying next bucket, %d\n",
+ ret);
+ }
+
+ dev_err(storage->dev, "Failed to find any valid state copy in any bucket\n");
+
+ return -ENOENT;
+
+found:
+ /* A failed restore consistency is not a failure of reading the state */
+ state_storage_restore_consistency(storage, *buf, *len);
+
+ return 0;
+}
+
+static int mtd_get_meminfo(const char *path, struct mtd_info_user *meminfo)
+{
+ int fd, ret;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ pr_err("Failed to open '%s', %d\n", path, ret);
+ return fd;
+ }
+
+ ret = ioctl(fd, MEMGETINFO, meminfo);
+
+ close(fd);
+
+ return ret;
+}
+
+#ifdef __BAREBOX__
+#define STAT_GIVES_SIZE(s) (S_ISREG(s.st_mode) || S_ISCHR(s.st_mode))
+#define BLKGET_GIVES_SIZE(s) 0
+#else
+#define STAT_GIVES_SIZE(s) (S_ISREG(s.st_mode))
+#define BLKGET_GIVES_SIZE(s) (S_ISBLK(s.st_mode))
+#endif
+#ifndef BLKGETSIZE64
+#define BLKGETSIZE64 -1
+#endif
+
+static int state_backend_storage_get_size(const char *path, size_t * out_size)
+{
+ struct mtd_info_user meminfo;
+ struct stat s;
+ int ret;
+
+ ret = stat(path, &s);
+ if (ret)
+ return -errno;
+
+ /*
+ * under Linux, stat() gives the size only on regular files
+ * under barebox, it works on char dev, too
+ */
+ if (STAT_GIVES_SIZE(s)) {
+ *out_size = s.st_size;
+ return 0;
+ }
+
+ /* this works under Linux on block devs */
+ if (BLKGET_GIVES_SIZE(s)) {
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -errno;
+
+ ret = ioctl(fd, BLKGETSIZE64, out_size);
+ close(fd);
+ if (!ret)
+ return 0;
+ }
+
+ /* try mtd next */
+ ret = mtd_get_meminfo(path, &meminfo);
+ if (!ret) {
+ *out_size = meminfo.size;
+ return 0;
+ }
+
+ return ret;
+}
+
+/* Number of copies that should be allocated */
+const int desired_copies = 3;
+
+/**
+ * state_storage_mtd_buckets_init - Creates storage buckets for mtd devices
+ * @param storage Storage object
+ * @param meminfo Info about the mtd device
+ * @param path Path to the device
+ * @param non_circular Use non-circular mode to write data that is compatible with the old on-flash format
+ * @param dev_offset Offset to start at in the device.
+ * @param max_size Maximum size to use for data. May be 0 for infinite.
+ * @return 0 on success, -errno otherwise
+ *
+ * Starting from offset 0 this function tries to create circular buckets on
+ * different offsets in the device. Different copies of the data are located in
+ * different eraseblocks.
+ * For MTD devices we use circular buckets to minimize the number of erases.
+ * Circular buckets write new data always in the next free space.
+ */
+static int state_storage_mtd_buckets_init(struct state_backend_storage *storage,
+ struct mtd_info_user *meminfo,
+ const char *path, bool non_circular,
+ off_t dev_offset, size_t max_size)
+{
+ struct state_backend_storage_bucket *bucket;
+ ssize_t end = dev_offset + max_size;
+ int nr_copies = 0;
+ off_t offset;
+
+ if (!end || end > meminfo->size)
+ end = meminfo->size;
+
+ if (!IS_ALIGNED(dev_offset, meminfo->erasesize)) {
+ dev_err(storage->dev, "Offset within the device is not aligned to eraseblocks. Offset is %ld, erasesize %zu\n",
+ dev_offset, meminfo->erasesize);
+ return -EINVAL;
+ }
+
+ for (offset = dev_offset; offset < end; offset += meminfo->erasesize) {
+ int ret;
+ ssize_t writesize = meminfo->writesize;
+ unsigned int eraseblock = offset / meminfo->erasesize;
+ bool lazy_init = true;
+
+ if (non_circular)
+ writesize = meminfo->erasesize;
+
+ ret = state_backend_bucket_circular_create(storage->dev, path,
+ &bucket,
+ eraseblock,
+ writesize,
+ meminfo,
+ lazy_init);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to create bucket at '%s' eraseblock %u\n",
+ path, eraseblock);
+ continue;
+ }
+
+ ret = state_backend_bucket_cached_create(storage->dev, bucket,
+ &bucket);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to setup cache bucket, continuing without cache, %d\n",
+ ret);
+ }
+
+ list_add_tail(&bucket->bucket_list, &storage->buckets);
+ ++nr_copies;
+ if (nr_copies >= desired_copies)
+ return 0;
+ }
+
+ if (!nr_copies) {
+ dev_err(storage->dev, "Failed to initialize any state storage bucket\n");
+ return -EIO;
+ }
+
+ dev_warn(storage->dev, "Failed to initialize desired amount of buckets, only %d of %d succeeded\n",
+ nr_copies, desired_copies);
+ return 0;
+}
+
+static int state_storage_file_create(struct device_d *dev, const char *path,
+ size_t fd_size)
+{
+ int fd;
+ uint8_t *buf;
+ int ret;
+
+ fd = open(path, O_RDWR | O_CREAT, 0600);
+ if (fd < 0) {
+ dev_err(dev, "Failed to open/create file '%s', %d\n", path,
+ -errno);
+ return -errno;
+ }
+
+ buf = xzalloc(fd_size);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto out_close;
+ }
+
+ ret = write_full(fd, buf, fd_size);
+ if (ret < 0) {
+ dev_err(dev, "Failed to initialize empty file '%s', %d\n", path,
+ ret);
+ goto out_free;
+ }
+ ret = 0;
+
+out_free:
+ free(buf);
+out_close:
+ close(fd);
+ return ret;
+}
+
+/**
+ * state_storage_file_buckets_init - Create buckets for a conventional file descriptor
+ * @param storage Storage object
+ * @param path Path to file/device
+ * @param dev_offset Offset in the device to start writing at.
+ * @param max_size Maximum size of the data. May be 0 for infinite.
+ * @param stridesize How far apart the different data copies are placed. If
+ * stridesize is 0, only one copy can be created.
+ * @return 0 on success, -errno otherwise
+ *
+ * For blockdevices and other regular files we create direct buckets beginning
+ * at offset 0. Direct buckets are simple and write data always to offset 0.
+ */
+static int state_storage_file_buckets_init(struct state_backend_storage *storage,
+ const char *path, off_t dev_offset,
+ size_t max_size, uint32_t stridesize)
+{
+ struct state_backend_storage_bucket *bucket;
+ size_t fd_size = 0;
+ int ret;
+ off_t offset;
+ int nr_copies = 0;
+
+ ret = state_backend_storage_get_size(path, &fd_size);
+ if (ret) {
+ if (ret != -ENOENT) {
+ dev_err(storage->dev, "Failed to get the filesize of '%s', %d\n",
+ path, ret);
+ return ret;
+ }
+ if (!stridesize) {
+ dev_err(storage->dev, "File '%s' does not exist and no information about the needed size. Please specify stridesize\n",
+ path);
+ return ret;
+ }
+
+ if (max_size)
+ fd_size = min(dev_offset + stridesize * desired_copies,
+ dev_offset + max_size);
+ else
+ fd_size = dev_offset + stridesize * desired_copies;
+ dev_info(storage->dev, "File '%s' does not exist, creating file of size %zd\n",
+ path, fd_size);
+ ret = state_storage_file_create(storage->dev, path, fd_size);
+ if (ret) {
+ dev_info(storage->dev, "Failed to create file '%s', %d\n",
+ path, ret);
+ return ret;
+ }
+ } else if (max_size) {
+ fd_size = min(fd_size, (size_t)dev_offset + max_size);
+ }
+
+ if (!stridesize) {
+ dev_warn(storage->dev, "WARNING, no stridesize given although we use a direct file write. Starting in degraded mode\n");
+ stridesize = fd_size;
+ }
+
+ for (offset = dev_offset; offset < fd_size; offset += stridesize) {
+ size_t maxsize = min((size_t)stridesize,
+ (size_t)(fd_size - offset));
+
+ ret = state_backend_bucket_direct_create(storage->dev, path,
+ &bucket, offset,
+ maxsize);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to create direct bucket at '%s' offset %ld\n",
+ path, offset);
+ continue;
+ }
+
+ ret = state_backend_bucket_cached_create(storage->dev, bucket,
+ &bucket);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to setup cache bucket, continuing without cache, %d\n",
+ ret);
+ }
+
+ list_add_tail(&bucket->bucket_list, &storage->buckets);
+ ++nr_copies;
+ if (nr_copies >= desired_copies)
+ return 0;
+ }
+
+ if (!nr_copies) {
+ dev_err(storage->dev, "Failed to initialize any state direct storage bucket\n");
+ return -EIO;
+ }
+ dev_warn(storage->dev, "Failed to initialize desired amount of direct buckets, only %d of %d succeeded\n",
+ nr_copies, desired_copies);
+
+ return 0;
+}
+
+
+/**
+ * state_storage_init - Init backend storage
+ * @param storage Storage object
+ * @param path Path to the backend storage file
+ * @param dev_offset Offset in the device to start writing at.
+ * @param max_size Maximum size of the data. May be 0 for infinite.
+ * @param stridesize Distance between two copies of the data. Not relevant for MTD
+ * @param storagetype Type of the storage backend. This may be NULL where we
+ * autoselect some backwardscompatible backend options
+ * @return 0 on success, -errno otherwise
+ *
+ * Depending on the filetype, we create mtd buckets or normal file buckets.
+ */
+int state_storage_init(struct state_backend_storage *storage,
+ struct device_d *dev, const char *path,
+ off_t offset, size_t max_size, uint32_t stridesize,
+ const char *storagetype)
+{
+ int ret;
+ struct mtd_info_user meminfo;
+
+ INIT_LIST_HEAD(&storage->buckets);
+ storage->dev = dev;
+ storage->name = storagetype;
+
+ ret = mtd_get_meminfo(path, &meminfo);
+ if (!ret && !(meminfo.flags & MTD_NO_ERASE)) {
+ bool non_circular = false;
+ if (!storagetype) {
+ non_circular = true;
+ } else if (strcmp(storagetype, "circular")) {
+ dev_warn(storage->dev, "Unknown storagetype '%s', falling back to old format circular storage type.\n",
+ storagetype);
+ non_circular = true;
+ }
+ return state_storage_mtd_buckets_init(storage, &meminfo, path,
+ non_circular, offset,
+ max_size);
+ } else {
+ return state_storage_file_buckets_init(storage, path, offset,
+ max_size, stridesize);
+ }
+
+ dev_err(storage->dev, "storage init done\n");
+}
+
+void state_storage_set_readonly(struct state_backend_storage *storage)
+{
+ storage->readonly = true;
+}
+
+/**
+ * state_storage_free - Free backend storage
+ * @param storage Storage object
+ */
+void state_storage_free(struct state_backend_storage *storage)
+{
+ struct state_backend_storage_bucket *bucket;
+ struct state_backend_storage_bucket *bucket_tmp;
+
+ if (!storage->buckets.next)
+ return;
+
+ list_for_each_entry_safe(bucket, bucket_tmp, &storage->buckets,
+ bucket_list) {
+ list_del(&bucket->bucket_list);
+ bucket->free(bucket);
+ }
+}
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");
+ }
+}
diff --git a/common/state/state.h b/common/state/state.h
new file mode 100644
index 0000000..cd54767
--- /dev/null
+++ b/common/state/state.h
@@ -0,0 +1,275 @@
+#include <linux/types.h>
+#include <linux/list.h>
+#include <driver.h>
+
+struct state;
+struct mtd_info_user;
+
+/**
+ * state_backend_storage_bucket - This class describes a single backend storage
+ * object copy
+ *
+ * @init Optional, initiates the given bucket
+ * @write Required, writes the given data to the storage in any form. Returns 0
+ * on success
+ * @read Required, reads the last successfully written data from the backend
+ * storage. Returns 0 on success and allocates a matching memory area to buf.
+ * len_hint can be a hint of the storage format how large the data to be read
+ * is. After the operation len_hint contains the size of the allocated buffer.
+ * @free Required, Frees all internally used memory
+ * @bucket_list A list element struct to attach this bucket to a list
+ */
+struct state_backend_storage_bucket {
+ int (*init) (struct state_backend_storage_bucket * bucket);
+ int (*write) (struct state_backend_storage_bucket * bucket,
+ const uint8_t * buf, ssize_t len);
+ int (*read) (struct state_backend_storage_bucket * bucket,
+ uint8_t ** buf, ssize_t * len_hint);
+ void (*free) (struct state_backend_storage_bucket * bucket);
+
+ bool initialized;
+ struct list_head bucket_list;
+};
+
+/**
+ * state_backend_format - This class describes a data format.
+ *
+ * @verify Required, Verifies the validity of the given data. The buffer that is
+ * passed into this function may be larger than the actual data in the buffer.
+ * The magic is supplied by the state to verify that this is an expected state
+ * entity. The function should return 0 on success or a negative errno otherwise.
+ * @pack Required, Packs data from the given state into a newly created buffer.
+ * The buffer and its length are stored in the given argument pointers. Returns
+ * 0 on success, -errno otherwise.
+ * @unpack Required, Unpacks the data from the given buffer into the state. Do
+ * not free the buffer.
+ * @free Optional, Frees all allocated memory and structures.
+ * @name Name of this backend.
+ */
+struct state_backend_format {
+ int (*verify) (struct state_backend_format * format, uint32_t magic,
+ const uint8_t * buf, ssize_t len);
+ int (*pack) (struct state_backend_format * format, struct state * state,
+ uint8_t ** buf, ssize_t * len);
+ int (*unpack) (struct state_backend_format * format,
+ struct state * state, const uint8_t * buf, ssize_t len);
+ ssize_t(*get_packed_len) (struct state_backend_format * format,
+ struct state * state);
+ void (*free) (struct state_backend_format * format);
+ const char *name;
+};
+
+/**
+ * state_backend_storage - Storage backend of the state.
+ *
+ * @buckets List of storage buckets that are available
+ */
+struct state_backend_storage {
+ struct list_head buckets;
+
+ /* For outputs */
+ struct device_d *dev;
+
+ const char *name;
+
+ bool readonly;
+};
+
+/**
+ * state_backend - State Backend object
+ *
+ * @format Backend format object
+ * @storage Backend storage object
+ * @of_path Path to the DT node
+ */
+struct state_backend {
+ struct state_backend_format *format;
+ struct state_backend_storage storage;
+ const char *of_path;
+};
+
+struct state {
+ struct list_head list; /* Entry to enqueue on list of states */
+
+ struct device_d dev;
+ struct device_node *root;
+ const char *name;
+ uint32_t magic;
+
+ struct list_head variables; /* Sorted list of variables */
+ unsigned int dirty;
+
+ struct state_backend backend;
+};
+
+enum state_convert {
+ STATE_CONVERT_FROM_NODE,
+ STATE_CONVERT_FROM_NODE_CREATE,
+ STATE_CONVERT_TO_NODE,
+ STATE_CONVERT_FIXUP,
+};
+
+enum state_variable_type {
+ STATE_TYPE_INVALID = 0,
+ STATE_TYPE_ENUM,
+ STATE_TYPE_U8,
+ STATE_TYPE_U32,
+ STATE_TYPE_MAC,
+ STATE_TYPE_STRING,
+};
+
+struct state_variable;
+
+/* 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 *,
+ enum state_convert);
+ int (*import) (struct state_variable *, struct device_node *);
+ struct state_variable *(*create) (struct state * state,
+ const char *name,
+ struct device_node *);
+};
+
+/* 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;
+};
+
+/*
+ * uint32
+ */
+struct state_uint32 {
+ struct state_variable var;
+ struct param_d *param;
+ struct state *state;
+ uint32_t value;
+ uint32_t value_default;
+};
+
+/*
+ * 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;
+};
+
+/*
+ * MAC address
+ */
+struct state_mac {
+ struct state_variable var;
+ struct param_d *param;
+ uint8_t value[6];
+ uint8_t value_default[6];
+};
+
+/*
+ * string
+ */
+struct state_string {
+ struct state_variable var;
+ struct param_d *param;
+ struct state *state;
+ char *value;
+ const char *value_default;
+ char raw[];
+};
+
+int state_set_dirty(struct param_d *p, void *priv);
+int state_from_node(struct state *state, struct device_node *node, bool create);
+struct device_node *state_to_node(struct state *state,
+ struct device_node *parent,
+ enum state_convert conv);
+int backend_format_raw_create(struct state_backend_format **format,
+ struct device_node *node, const char *secret_name,
+ struct device_d *dev);
+int backend_format_dtb_create(struct state_backend_format **format,
+ struct device_d *dev);
+int state_storage_init(struct state_backend_storage *storage,
+ struct device_d *dev, const char *path,
+ off_t offset, size_t max_size, uint32_t stridesize,
+ const char *storagetype);
+void state_storage_set_readonly(struct state_backend_storage *storage);
+void state_add_var(struct state *state, struct state_variable *var);
+struct variable_type *state_find_type_by_name(const char *name);
+int state_backend_bucket_circular_create(struct device_d *dev, const char *path,
+ struct state_backend_storage_bucket **bucket,
+ unsigned int eraseblock,
+ ssize_t writesize,
+ struct mtd_info_user *mtd_uinfo,
+ bool lazy_init);
+int state_backend_bucket_cached_create(struct device_d *dev,
+ struct state_backend_storage_bucket *raw,
+ struct state_backend_storage_bucket **out);
+struct state_variable *state_find_var(struct state *state, const char *name);
+struct digest *state_backend_format_raw_get_digest(struct state_backend_format
+ *format);
+int state_backend_init(struct state_backend *backend, struct device_d *dev,
+ struct device_node *node, const char *backend_format,
+ const char *storage_path, const char *state_name, const
+ char *of_path, off_t offset, size_t max_size,
+ uint32_t stridesize, const char *storagetype);
+void state_backend_set_readonly(struct state_backend *backend);
+void state_backend_free(struct state_backend *backend);
+void state_storage_free(struct state_backend_storage *storage);
+int state_backend_bucket_direct_create(struct device_d *dev, const char *path,
+ struct state_backend_storage_bucket **bucket,
+ off_t offset, ssize_t max_size);
+int state_storage_write(struct state_backend_storage *storage,
+ const uint8_t * buf, ssize_t len);
+int state_storage_restore_consistency(struct state_backend_storage
+ *storage, const uint8_t * buf,
+ ssize_t len);
+int state_storage_read(struct state_backend_storage *storage,
+ struct state_backend_format *format,
+ uint32_t magic, uint8_t **buf, ssize_t *len,
+ ssize_t len_hint);
+
+static inline struct state_uint32 *to_state_uint32(struct state_variable *s)
+{
+ return container_of(s, struct state_uint32, var);
+}
+
+static inline struct state_enum32 *to_state_enum32(struct state_variable *s)
+{
+ return container_of(s, struct state_enum32, var);
+}
+
+static inline struct state_mac *to_state_mac(struct state_variable *s)
+{
+ return container_of(s, struct state_mac, var);
+}
+
+static inline struct state_string *to_state_string(struct state_variable *s)
+{
+ return container_of(s, struct state_string, var);
+}
+
+static inline int state_string_copy_to_raw(struct state_string *string,
+ const char *src)
+{
+ size_t len;
+
+ len = strlen(src);
+ if (len > string->var.size)
+ return -EILSEQ;
+
+ /* copy string and clear remaining contents of buffer */
+ memcpy(string->raw, src, len);
+ memset(string->raw + len, 0x0, string->var.size - len);
+
+ return 0;
+}
diff --git a/common/state/state_variables.c b/common/state/state_variables.c
new file mode 100644
index 0000000..0d2a626
--- /dev/null
+++ b/common/state/state_variables.c
@@ -0,0 +1,493 @@
+/*
+ * 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 <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <malloc.h>
+#include <net.h>
+#include <printk.h>
+#include <of.h>
+#include <stdio.h>
+
+#include "state.h"
+
+/**
+ * state_set_dirty - Helper function to set the state to dirty. Only used for
+ * state variables callbacks
+ * @param p
+ * @param priv
+ * @return
+ */
+int state_set_dirty(struct param_d *p, void *priv)
+{
+ struct state *state = priv;
+
+ state->dirty = 1;
+
+ return 0;
+}
+
+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;
+}
+
+void state_add_var(struct state *state, struct state_variable *var)
+{
+ list_add_sort(&var->list, &state->variables, state_var_compare);
+}
+
+static int state_uint32_export(struct state_variable *var,
+ struct device_node *node,
+ enum state_convert conv)
+{
+ 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;
+ }
+
+ if (conv == STATE_CONVERT_FIXUP)
+ return 0;
+
+ return of_property_write_u32(node, "value", su32->value);
+}
+
+static int state_uint32_import(struct state_variable *sv,
+ 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 int state_uint8_set(struct param_d *p, void *priv)
+{
+ struct state_uint32 *su32 = priv;
+ struct state *state = su32->state;
+
+ if (su32->value > 255)
+ return -ERANGE;
+
+ return state_set_dirty(p, state);
+}
+
+static struct state_variable *state_uint8_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_uint8_set,
+ NULL, &su32->value, "%u", su32);
+ if (IS_ERR(param)) {
+ free(su32);
+ return ERR_CAST(param);
+ }
+
+ su32->param = param;
+ su32->var.size = sizeof(uint8_t);
+#ifdef __LITTLE_ENDIAN
+ su32->var.raw = &su32->value;
+#else
+ su32->var.raw = &su32->value + 3;
+#endif
+ su32->state = state;
+
+ return &su32->var;
+}
+
+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, "%u", 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;
+}
+
+static int state_enum32_export(struct state_variable *var,
+ struct device_node *node,
+ enum state_convert conv)
+{
+ 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;
+ }
+
+ 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);
+
+ if (conv == STATE_CONVERT_FIXUP)
+ return 0;
+
+ ret = of_property_write_u32(node, "value", enum32->value);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static int state_enum32_import(struct state_variable *sv,
+ 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");
+ if (num_names < 0) {
+ dev_err(&state->dev,
+ "enum32 node without \"names\" property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ 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);
+}
+
+static int state_mac_export(struct state_variable *var,
+ struct device_node *node, enum state_convert conv)
+{
+ struct state_mac *mac = to_state_mac(var);
+ int ret;
+
+ if (!is_zero_ether_addr(mac->value_default)) {
+ ret = of_property_write_u8_array(node, "default",
+ mac->value_default,
+ ARRAY_SIZE(mac->
+ value_default));
+ if (ret)
+ return ret;
+ }
+
+ if (conv == STATE_CONVERT_FIXUP)
+ return 0;
+
+ return of_property_write_u8_array(node, "value", mac->value,
+ ARRAY_SIZE(mac->value));
+}
+
+static int state_mac_import(struct state_variable *sv, 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 int state_string_export(struct state_variable *var,
+ struct device_node *node,
+ enum state_convert conv)
+{
+ struct state_string *string = to_state_string(var);
+ int ret = 0;
+
+ if (string->value_default) {
+ ret = of_set_property(node, "default", string->value_default,
+ strlen(string->value_default) + 1, 1);
+
+ if (ret)
+ return ret;
+ }
+
+ if (conv == STATE_CONVERT_FIXUP)
+ return 0;
+
+ if (string->value)
+ ret = of_set_property(node, "value", string->value,
+ strlen(string->value) + 1, 1);
+
+ return ret;
+}
+
+static int state_string_import(struct state_variable *sv,
+ struct device_node *node)
+{
+ struct state_string *string = to_state_string(sv);
+ const char *value = NULL;
+ size_t len;
+ int ret;
+
+ of_property_read_string(node, "default", &string->value_default);
+ if (string->value_default) {
+ len = strlen(string->value_default);
+ if (len > string->var.size)
+ return -EILSEQ;
+ }
+
+ ret = of_property_read_string(node, "value", &value);
+ if (ret)
+ value = string->value_default;
+
+ if (value)
+ return state_string_copy_to_raw(string, value);
+
+ return 0;
+}
+
+static int state_string_set(struct param_d *p, void *priv)
+{
+ struct state_string *string = priv;
+ struct state *state = string->state;
+ int ret;
+
+ ret = state_string_copy_to_raw(string, string->value);
+ if (ret)
+ return ret;
+
+ return state_set_dirty(p, state);
+}
+
+static int state_string_get(struct param_d *p, void *priv)
+{
+ struct state_string *string = priv;
+
+ free(string->value);
+ if (string->raw[0])
+ string->value = xstrndup(string->raw, string->var.size);
+ else
+ string->value = xstrdup("");
+
+ return 0;
+}
+
+static struct state_variable *state_string_create(struct state *state,
+ const char *name,
+ struct device_node *node)
+{
+ struct state_string *string;
+ uint32_t start_size[2];
+ int ret;
+
+ 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);
+ return ERR_PTR(ret);
+ }
+
+ /* limit to arbitrary len of 4k */
+ if (start_size[1] > 4096)
+ return ERR_PTR(-EILSEQ);
+
+ string = xzalloc(sizeof(*string) + start_size[1]);
+ string->var.size = start_size[1];
+ string->var.raw = &string->raw;
+ string->state = state;
+
+ string->param = dev_add_param_string(&state->dev, name,
+ state_string_set, state_string_get,
+ &string->value, string);
+ if (IS_ERR(string->param)) {
+ ret = PTR_ERR(string->param);
+ goto out;
+ }
+
+ return &string->var;
+ out: free(string);
+ return ERR_PTR(ret);
+}
+
+static struct variable_type types[] = {
+ {
+ .type = STATE_TYPE_U8,
+ .type_name = "uint8",
+ .export = state_uint32_export,
+ .import = state_uint32_import,
+ .create = state_uint8_create,
+ }, {
+ .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,
+ }, {
+ .type = STATE_TYPE_STRING,
+ .type_name = "string",
+ .export = state_string_export,
+ .import = state_string_import,
+ .create = state_string_create,
+ }
+};
+
+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;
+}
+
+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);
+}
diff --git a/drivers/misc/state.c b/drivers/misc/state.c
index 73356b4..b9eb1b7 100644
--- a/drivers/misc/state.c
+++ b/drivers/misc/state.c
@@ -24,75 +24,14 @@
static int state_probe(struct device_d *dev)
{
struct device_node *np = dev->device_node;
- struct device_node *partition_node;
struct state *state;
- const char *alias;
- const char *backend_type = NULL;
- int len, ret;
- const char *of_path;
- char *path;
+ bool readonly = false;
- if (!np)
- return -EINVAL;
-
- alias = of_alias_get(np);
- if (!alias)
- alias = np->name;
-
- state = state_new_from_node(alias, np);
+ state = state_new_from_node(np, NULL, 0, 0, readonly);
if (IS_ERR(state))
return PTR_ERR(state);
- of_path = of_get_property(np, "backend", &len);
- if (!of_path) {
- ret = -ENODEV;
- goto out_release;
- }
-
- /* guess if of_path is a path, not a phandle */
- if (of_path[0] == '/' && len > 1) {
- ret = of_find_path(np, "backend", &path, 0);
- } else {
-
- partition_node = of_parse_phandle(np, "backend", 0);
- if (!partition_node)
- return -EINVAL;
-
- of_path = partition_node->full_name;
- ret = of_find_path_by_node(partition_node, &path, 0);
- }
-
- if (ret == -ENODEV)
- ret = -EPROBE_DEFER;
- if (ret)
- goto out_release;
-
- ret = of_property_read_string(np, "backend-type", &backend_type);
- if (ret) {
- goto out_free;
- } else if (!strcmp(backend_type, "raw")) {
- ret = state_backend_raw_file(state, of_path, path, 0, 0);
- } else if (!strcmp(backend_type, "dtb")) {
- ret = state_backend_dtb_file(state, of_path, path);
- } else {
- dev_warn(dev, "invalid backend type: %s\n", backend_type);
- ret = -ENODEV;
- goto out_free;
- }
-
- if (ret)
- goto out_free;
-
- dev_info(dev, "backend: %s, path: %s, of_path: %s\n", backend_type, path, of_path);
- free(path);
-
return 0;
-
- out_free:
- free(path);
- out_release:
- state_release(state);
- return ret;
}
static __maybe_unused struct of_device_id state_ids[] = {
diff --git a/include/state.h b/include/state.h
index b3966fd..bc9a574 100644
--- a/include/state.h
+++ b/include/state.h
@@ -10,13 +10,15 @@ int state_backend_dtb_file(struct state *state, const char *of_path,
int state_backend_raw_file(struct state *state, const char *of_path,
const char *path, off_t offset, size_t size);
-struct state *state_new_from_node(const char *name, struct device_node *node);
+struct state *state_new_from_node(struct device_node *node, char *path,
+ off_t offset, size_t max_size, bool readonly);
void state_release(struct state *state);
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);