Hi, On Friday 17 June 2016 11:14:34 Markus Pargmann wrote: > The state framework grew organically over the time. Unfortunately the > architecture and abstractions disappeared during this period. > > This patch refactors the framework to recreate the abstractions. The > main focus was the backend with its storage. The main use-case was to > offer better NAND support with less erase cycles and interchangeable > data formats (dtb,raw). > > The general architecture now has a backend which consists of a data > format and storage. The storage consists of multiple storage buckets > each holding exactly one copy of the state data. A data format describes > a data serialization for the state framework. This can be either dtb or > raw. A storage bucket is a storage location which is used to store any > data. There is a (new) circular type which writes changes behind the > last written data and therefore reduces the number of erases. The other > type is a direct bucket which writes directly to a storage offset for > all non-erase storage. > > Furthermore this patch splits up all classes into different files in a > subdirectory. > > This is currently all in one patch as I can't see a good way to split > the changes up without having a non-working state framework in between. We discovered some small bugs in this. I will send a new version. Best Regards, Markus > > The following diagram shows the new architecture roughly: > > .----------. > | state | > '----------' > | > | > v > .----------------------------. > | state_backend | > |----------------------------| > | + state_load(*state); | > | + state_save(*state); | > | + state_backend_init(...); | > | | > | | > '----------------------------' > | | The format describes > | | how the state data > | '-------------> is serialized > | .--------------------------------------------. > | | state_backend_format | > | |--------------------------------------------| > | | + verify(*format, magic, *buf, len); | > | | + pack(*format, *state, **buf, len); | > | | + unpack(*format, *state, *buf, len); | > | | + get_packed_len(*format, *state); | > | | + free(*format); | > | '--------------------------------------------' > | ^ ^ > | * * > | * * > | .--------------------. .--------------------. > | | backend_format_dtb | | backend_format_raw | > | '--------------------' '--------------------' > | > | > | > v > .----------------------------------------------------------. > | state_backend_storage | > |----------------------------------------------------------| > | + init(...); | > | + free(*storage); | > | + read(*storage, *format, magic, **buf, *len, len_hint); | > | + write(*storage, *buf, len); | > | + restore_consistency(*storage, *buf, len); | > '----------------------------------------------------------' > | > The backend storage is responsible to manage multiple > data copies and distribute them onto several buckets. > Read data is verified against the given format to > ensure that the read data is correct. > | > | > | > | > | > v > .------------------------------------------. > | state_backend_storage_bucket | > |------------------------------------------| > | + init(*bucket); | > | + write(*bucket, *buf, len); | > | + read(*bucket, **buf, len_hint); | > | + free(*bucket); | > '------------------------------------------' > ^ ^ > * * > * * > A storage bucket represents exactly one data copy at one > data location. A circular bucket writes any new data to > the end of the bucket (for reduced erases on NAND). A > direct bucket directly writes at one location. > * * > * * > * * > .-----------------------. .-------------------------. > | backend_bucket_direct | | backend_bucket_circular | > '-----------------------' '-------------------------' > > Signed-off-by: Markus Pargmann > --- > common/Makefile | 2 +- > common/state.c | 1720 -------------------------------- > common/state/Makefile | 8 + > common/state/backend.c | 209 ++++ > common/state/backend_bucket_circular.c | 580 +++++++++++ > common/state/backend_bucket_direct.c | 180 ++++ > common/state/backend_format_dtb.c | 150 +++ > common/state/backend_format_raw.c | 327 ++++++ > common/state/backend_storage.c | 470 +++++++++ > common/state/state.c | 564 +++++++++++ > common/state/state.h | 266 +++++ > common/state/state_variables.c | 493 +++++++++ > drivers/misc/state.c | 64 +- > include/state.h | 4 +- > 14 files changed, 3252 insertions(+), 1785 deletions(-) > delete mode 100644 common/state.c > create mode 100644 common/state/Makefile > create mode 100644 common/state/backend.c > create mode 100644 common/state/backend_bucket_circular.c > create mode 100644 common/state/backend_bucket_direct.c > create mode 100644 common/state/backend_format_dtb.c > create mode 100644 common/state/backend_format_raw.c > create mode 100644 common/state/backend_storage.c > create mode 100644 common/state/state.c > create mode 100644 common/state/state.h > create mode 100644 common/state/state_variables.c > > diff --git a/common/Makefile b/common/Makefile > index 99681e21215b..17fcb5f24a04 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 87afff305661..000000000000 > --- a/common/state.c > +++ /dev/null > @@ -1,1720 +0,0 @@ > -/* > - * Copyright (C) 2012-2014 Pengutronix, Jan Luebbe > - * Copyright (C) 2013-2014 Pengutronix, Sascha Hauer > - * Copyright (C) 2015 Pengutronix, Marc Kleine-Budde > - * > - * This program is free software; you can redistribute it and/or modify > - * it under the terms of the GNU General Public License as published by > - * the Free Software Foundation; either version 2 of the License, or > - * (at your option) any later version. > - * > - * This program is distributed in the hope that it will be useful, > - * but WITHOUT ANY WARRANTY; without even the implied warranty of > - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > - * GNU General Public License for more details. > - */ > - > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > - > -#include > - > -#include > -#include > -#include > -#include > - > -#include > - > -#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 @
*/ > - 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 000000000000..5d6dcb26dfeb > --- /dev/null > +++ b/common/state/Makefile > @@ -0,0 +1,8 @@ > +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 > diff --git a/common/state/backend.c b/common/state/backend.c > new file mode 100644 > index 000000000000..054f8d646679 > --- /dev/null > +++ b/common/state/backend.c > @@ -0,0 +1,209 @@ > +/* > + * Copyright (C) 2016 Pengutronix, Markus Pargmann > + * > + * 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 > +#include > +#include > +#include > +#include > + > +#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) > +{ > + struct state_backend_storage_bucket *bucket; > + struct state_backend_storage_bucket *bucket_tmp; > + 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; > + > + list_for_each_entry_safe(bucket, bucket_tmp, &backend->storage.buckets, > + bucket_list) { > + if (!bucket->init) > + continue; > + > + ret = bucket->init(bucket); > + if (ret) { > + dev_warn(dev, "Bucket init failed, state degraded, %d\n", > + ret); > + list_del(&bucket->bucket_list); > + bucket->free(bucket); > + continue; > + } > + } > + > + if (list_empty(&backend->storage.buckets)) { > + dev_err(dev, "Failed to initialize any state bucket\n"); > + ret = -EIO; > + goto out_free_storage; > + } > + > + > + backend->of_path = of_path; > + > + return 0; > + > +out_free_storage: > + state_storage_free(&backend->storage); > +out_free_format: > + state_format_free(backend->format); > + backend->format = NULL; > + > + return ret; > +} > + > +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_circular.c b/common/state/backend_bucket_circular.c > new file mode 100644 > index 000000000000..64e9be2e9979 > --- /dev/null > +++ b/common/state/backend_bucket_circular.c > @@ -0,0 +1,580 @@ > +/* > + * Copyright (C) 2016 Pengutronix, Markus Pargmann > + * > + * 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#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 > + > + /* Cached data of the last read/write */ > + u8 *current_data; > + ssize_t current_data_len; > + > + bool force_rewrite; /* In case of degradation, force a rewrite */ > + > + /* 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); > + circ->force_rewrite = true; > + circ->write_area = 0; > + dev_dbg(circ->dev, "PEB %u has ECC error, forcing rewrite\n", > + circ->eraseblock); > + } else if (ret == -EUCLEAN) { > + circ->force_rewrite = true; > + 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; > + } > + > + return 0; > +} > + > +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; > + } > + circ->force_rewrite = true; > + } else if (ret < 0 && ret != -EUCLEAN) { > + dev_err(circ->dev, "Failed to write PEB %u, %d\n", > + circ->eraseblock, ret); > + return ret; > + } > + > + return 0; > +} > + > +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) { > + circ->force_rewrite = true; > + dev_dbg(circ->dev, "PEB %u has ECC error, forcing rewrite\n", > + circ->eraseblock); > + } else if (stat2.corrected - stat1.corrected > 0) { > + circ->force_rewrite = true; > + dev_dbg(circ->dev, "PEB %u is unclean, forcing rewrite\n", > + circ->eraseblock); > + } > + > + return 0; > +} > + > +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_fill_cache( > + struct state_backend_storage_bucket *bucket) > +{ > + 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) { > + dev_err(circ->dev, "Failed to read circular storage len %zd, %d\n", > + read_len, ret); > + free(buf); > + return ret; > + } > + > + circ->current_data = buf; > + circ->current_data_len = read_len; > + > + return 0; > +} > + > +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); > + int ret; > + > + if (!circ->current_data) { > + ret = state_backend_bucket_circular_fill_cache(bucket); > + if (ret) > + return ret; > + } > + > + /* > + * We keep the internal copy in the cache and duplicate the data for > + * external use > + */ > + *buf_out = xmemdup(circ->current_data, circ->current_data_len); > + if (!*buf_out) > + return -ENOMEM; > + > + *len_hint = circ->current_data_len - sizeof(struct state_backend_storage_bucket_circular_meta); > + > + return 0; > +} > + > +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; > + > + /* Nothing in cache? Then read to see what is on the device currently */ > + if (!circ->current_data) { > + state_backend_bucket_circular_fill_cache(bucket); > + } > + > + /* > + * If we would write the same data that is currently on the device, we > + * can return successfully without writing. > + * Note that the cache may still be empty if the storage is empty or > + * errors occured. > + */ > + if (circ->current_data) { > + dev_dbg(circ->dev, "Comparing cached data, writing %zd bytes, cached %zd bytes\n", > + written_length, circ->current_data_len); > + if (!circ->force_rewrite && > + written_length == circ->current_data_len && > + !memcmp(circ->current_data, write_buf, written_length)) { > + dev_dbg(circ->dev, "Data already on device, not writing again\n"); > + goto out_free; > + } else { > + free(circ->current_data); > + circ->current_data = NULL; > + } > + } > + > + 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) { > + 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); > + > + /* Put written data into the cache */ > + WARN_ON(circ->current_data); > + circ->current_data = write_buf; > + circ->current_data_len = written_length; > + write_buf = NULL; > + circ->force_rewrite = false; > + > +out_free: > + if (write_buf) > + free(write_buf); > + return 0; > +} > + > +/** > + * 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); > + > + if (circ->current_data) > + free(circ->current_data); > + 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) > +{ > + 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; > + > + ret = state_backend_bucket_circular_init(*bucket); > + if (ret) > + goto out_free; > + > + 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 000000000000..08892f001e25 > --- /dev/null > +++ b/common/state/backend_bucket_direct.c > @@ -0,0 +1,180 @@ > +/* > + * Copyright (C) 2016 Pengutronix, Markus Pargmann > + * > + * 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 > +#include > +#include > +#include > +#include > +#include > + > +#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 000000000000..dc19c888e5b6 > --- /dev/null > +++ b/common/state/backend_format_dtb.c > @@ -0,0 +1,150 @@ > +/* > + * Copyright (C) 2012-2014 Pengutronix, Jan Luebbe > + * Copyright (C) 2013-2014 Pengutronix, Sascha Hauer > + * Copyright (C) 2015 Pengutronix, Marc Kleine-Budde > + * Copyright (C) 2016 Pengutronix, Markus Pargmann > + * > + * 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 > +#include > +#include > +#include > + > +#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); > -- Pengutronix e.K. | | Industrial Linux Solutions | http://www.pengutronix.de/ | Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |