From mboxrd@z Thu Jan 1 00:00:00 1970 Return-path: Received: from metis.ext.pengutronix.de ([2001:67c:670:201:290:27ff:fe1d:cc33]) by bombadil.infradead.org with esmtps (Exim 4.85_2 #1 (Red Hat Linux)) id 1bKi45-0002Pj-Oh for barebox@lists.infradead.org; Wed, 06 Jul 2016 08:20:25 +0000 From: Markus Pargmann Date: Wed, 6 Jul 2016 10:19:43 +0200 Message-Id: <1467793185-3709-3-git-send-email-mpa@pengutronix.de> In-Reply-To: <1467793185-3709-1-git-send-email-mpa@pengutronix.de> References: <1467793185-3709-1-git-send-email-mpa@pengutronix.de> List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: "barebox" Errors-To: barebox-bounces+u.kleine-koenig=pengutronix.de@lists.infradead.org Subject: [PATCH v3 2/4] state: Refactor state framework To: barebox@lists.infradead.org Cc: mgr@pengutronix.de 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. 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 b*cket writes any new data to the end of the bucket (for *educed erases on NAND). A direct bucket directly writ*s at one location. * * * * * * * * * .-----------------------. * .-------------------------. | backend_bucket_direct | * | backend_bucket_circular | '-----------------------' * '-------------------------' ^ * ^ | * | | * | | * | | .-----------------------. | '--| backend_bucket_cached |---' '-----------------------' A backend_bucket_cached is a transparent bucket that directly uses another bucket as backend device and caches all accesses. Signed-off-by: Markus Pargmann --- common/Makefile | 2 +- common/state.c | 1720 -------------------------------- common/state/Makefile | 9 + common/state/backend.c | 188 ++++ common/state/backend_bucket_cached.c | 155 +++ common/state/backend_bucket_circular.c | 515 ++++++++++ common/state/backend_bucket_direct.c | 180 ++++ common/state/backend_format_dtb.c | 150 +++ common/state/backend_format_raw.c | 329 ++++++ common/state/backend_storage.c | 524 ++++++++++ common/state/state.c | 566 +++++++++++ common/state/state.h | 275 +++++ common/state/state_variables.c | 493 +++++++++ drivers/misc/state.c | 65 +- include/state.h | 4 +- 15 files changed, 3390 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_cached.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..23f72862b995 --- /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 000000000000..2f2e6dfd32d1 --- /dev/null +++ b/common/state/backend.c @@ -0,0 +1,188 @@ +/* + * 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) +{ + 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 000000000000..781ac2debd2a --- /dev/null +++ b/common/state/backend_bucket_cached.c @@ -0,0 +1,155 @@ +/* + * 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 "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 000000000000..72e165e4375b --- /dev/null +++ b/common/state/backend_bucket_circular.c @@ -0,0 +1,515 @@ +/* + * 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 + + /* 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 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); + } + + 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 000000000000..420942413079 --- /dev/null +++ b/common/state/backend_format_raw.c @@ -0,0 +1,329 @@ +/* + * 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 +#include +#include +#include +#include + +#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 000000000000..efe8bac05da0 --- /dev/null +++ b/common/state/backend_storage.c @@ -0,0 +1,524 @@ +/* + * 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" + +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 000000000000..9f4553d69a55 --- /dev/null +++ b/common/state/state.c @@ -0,0 +1,566 @@ +/* + * 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 "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 @
*/ + 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 000000000000..cd5476783056 --- /dev/null +++ b/common/state/state.h @@ -0,0 +1,275 @@ +#include +#include +#include + +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 000000000000..0d2a626a2397 --- /dev/null +++ b/common/state/state_variables.c @@ -0,0 +1,493 @@ +/* + * 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 "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 73356b45a011..b9eb1b7bb24a 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 b3966fd99e79..bc9a57409319 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); -- 2.8.1 _______________________________________________ barebox mailing list barebox@lists.infradead.org http://lists.infradead.org/mailman/listinfo/barebox