diff --git a/Makefile.am b/Makefile.am index 28a8bef..a8dcd18 100644 --- a/Makefile.am +++ b/Makefile.am @@ -51,20 +51,39 @@ src/crypto/sha2.c \ src/keystore-blob.c \ src/base64.c \ + src/barebox-state/backend_bucket_circular.c \ + src/barebox-state/backend_bucket_direct.c \ + src/barebox-state/backend.c \ + src/barebox-state/backend_format_dtb.c \ + src/barebox-state/backend_format_raw.c \ + src/barebox-state/backend_storage.c \ + src/barebox-state/state.c \ + src/barebox-state/state.h \ + src/barebox-state/state_variables.c \ src/barebox-state.c \ + src/barebox-state.h \ \ src/asm/unaligned.h \ - src/barebox-state.h \ src/base64.h \ src/crypto/internal.h \ src/crypto/sha.h \ src/digest.h \ src/fs.h \ src/init.h \ - src/keystore.h \ + src/crypto/keystore.h \ src/linux/err.h \ src/linux/list.h \ - src/module.h + src/module.h \ + src/libfile.h \ + src/linux/mtd/mtd-abi.h \ + src/printk.h \ + src/net.h \ + src/state.h \ + src/libbb.h \ + src/fdt.h \ + src/of.h \ + src/mtd/mtd-peb.h \ + src/driver.h barebox_state_CFLAGS = $(LIBDT_CFLAGS) barebox_state_LDADD = src/libdt-utils.la diff --git a/src/barebox-state.c b/src/barebox-state.c index bc1010e..d12cd8b 100644 --- a/src/barebox-state.c +++ b/src/barebox-state.c @@ -32,12 +32,9 @@ #include #include -#include -#include -#include -#include -#include -#include +#include +#include
+#include static int verbose; @@ -55,1677 +52,46 @@ static int __state_string_set(struct state_variable *var, const char *val); static char *__state_string_get(struct state_variable *var); -#define asprintf(fmt, arg...) barebox_asprintf(fmt, ##arg) - -/* ------------------------------------------------------------ */ - -#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_MAC, - STATE_TYPE_STRING, -}; - -/* instance of a single variable */ -struct state_variable { +struct variable_str_type { enum state_variable_type type; - struct list_head list; - const char *name; - unsigned int start; - unsigned int size; - void *raw; + char *type_name; + + char *(*get)(struct state_variable *var); + int (*set)(struct state_variable *var, const char *val); + void (*info)(struct state_variable *var); }; -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 *); - char *(*__get)(struct state_variable *); - int (*__set)(struct state_variable *, const char *val); - void (*__info)(struct state_variable *); -}; - -/* 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_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; -} - -/* - * 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; - - num_names = of_property_count_strings(node, "names"); - if (num_names < 0) { - pr_err("enum32 node without \"names\" property\n"); - return ERR_PTR(-EINVAL); - } - - enum32 = xzalloc(sizeof(*enum32)); - - 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[] = { +static struct variable_str_type types[] = { { .type = STATE_TYPE_U8, .type_name = "uint8", - .export = state_uint32_export, - .import = state_uint32_import, - .create = state_uint8_create, - .__set = __state_uint8_set, - .__get = __state_uint32_get, + .set = __state_uint8_set, + .get = __state_uint32_get, }, { .type = STATE_TYPE_U32, .type_name = "uint32", - .export = state_uint32_export, - .import = state_uint32_import, - .create = state_uint32_create, - .__set = __state_uint32_set, - .__get = __state_uint32_get, + .set = __state_uint32_set, + .get = __state_uint32_get, }, { .type = STATE_TYPE_ENUM, .type_name = "enum32", - .export = state_enum32_export, - .import = state_enum32_import, - .create = state_enum32_create, - .__set = __state_enum32_set, - .__get = __state_enum32_get, - .__info = __state_enum32_info, + .set = __state_enum32_set, + .get = __state_enum32_get, + .info = __state_enum32_info, }, { .type = STATE_TYPE_MAC, .type_name = "mac", - .export = state_mac_export, - .import = state_mac_import, - .create = state_mac_create, - .__set = __state_mac_set, - .__get = __state_mac_get, + .set = __state_mac_set, + .get = __state_mac_get, }, { .type = STATE_TYPE_STRING, .type_name = "string", - .export = state_string_export, - .import = state_string_import, - .create = state_string_create, - .__set = __state_string_set, - .__get = __state_string_get, + .set = __state_string_set, + .get = __state_string_get, }, }; -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 = asprintf("%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; -} - -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: - 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; - const char *algo; - const unsigned char *key; - int key_len, ret; - - ret = of_property_read_string(state->root, "algo", &algo); - if (ret == -EINVAL) /* -EINVAL == does not exist */ - return 0; - else if (ret) - return ret; - - 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; -} - -/* ------------------------------------------------------------ */ - -#undef asprintf - -static struct variable_type *state_find_type(enum state_variable_type type) +static struct variable_str_type *state_find_type(enum state_variable_type type) { int i; @@ -1902,7 +268,7 @@ char *state_get_var(struct state *state, const char *var) { struct state_variable *sv; - struct variable_type *vtype; + struct variable_str_type *vtype; sv = state_find_var(state, var); if (IS_ERR(sv)) @@ -1912,13 +278,13 @@ if (!vtype) return NULL; - return vtype->__get(sv); + return vtype->get(sv); } static int state_set_var(struct state *state, const char *var, const char *val) { struct state_variable *sv; - struct variable_type *vtype; + struct variable_str_type *vtype; int ret; sv = state_find_var(state, var); @@ -1929,10 +295,10 @@ if (!vtype) return -ENODEV; - if (!vtype->__set) + if (!vtype->set) return -EPERM; - ret = vtype->__set(sv, val); + ret = vtype->set(sv, val); if (ret) return ret; @@ -1984,36 +350,25 @@ of_print_nodes(node, 0); } - state = state_new_from_node(node->name, node); + partition_node = of_parse_phandle(node, "backend", 0); + if (!partition_node) { + fprintf(stderr, "cannot find backend node in %s\n", node->full_name); + exit (1); + } + + ret = of_get_devicepath(partition_node, &devpath, &offset, &size); + if (ret) { + fprintf(stderr, "Cannot find backend path in %s\n", node->full_name); + return ERR_PTR(ret); + } + + state = state_new_from_node(node, devpath, offset, size); if (IS_ERR(state)) { fprintf(stderr, "unable to initlialize state: %s\n", strerror(PTR_ERR(state))); return ERR_CAST(state); } - partition_node = of_parse_phandle(node, "backend", 0); - if (!partition_node) { - fprintf(stderr, "cannot find backend node in %s\n", node->full_name); - exit (1); - } - - ret = of_get_devicepath(partition_node, &devpath, &offset, &size); - if (ret) { - fprintf(stderr, "Cannot find backend path in %s\n", node->full_name); - return ERR_PTR(ret); - } - - of_property_read_string(node, "backend-type", &backend_type); - if (!strcmp(backend_type, "raw")) - ret = state_backend_raw_file(state, partition_node->full_name, devpath, offset, size); - else - fprintf(stderr, "invalid backend type: %s\n", backend_type); - - if (ret) { - fprintf(stderr, "Cannot initialize backend: %s\n", strerror(-ret)); - return ERR_PTR(ret); - } - return state; } @@ -2108,7 +463,7 @@ if (do_dump) { state_for_each_var(state, v) { - struct variable_type *vtype; + struct variable_str_type *vtype; vtype = state_find_type(v->type); if (!vtype) { @@ -2116,11 +471,11 @@ exit(1); } - printf("%s=%s", v->name, vtype->__get(v)); + printf("%s=%s", v->name, vtype->get(v)); if (verbose) { printf(", type=%s", vtype->type_name); - if (vtype->__info) - vtype->__info(v); + if (vtype->info) + vtype->info(v); } printf("\n"); } @@ -2128,7 +483,7 @@ if (do_dump_shell) { state_for_each_var(state, v) { - struct variable_type *vtype; + struct variable_str_type *vtype; char *name, *ptr; int i; @@ -2139,7 +494,7 @@ *ptr++ = '_'; vtype = state_find_type(v->type); - printf("%s_%s=\"%s\"\n", state->name, name, vtype->__get(v)); + printf("%s_%s=\"%s\"\n", state->name, name, vtype->get(v)); } } diff --git a/src/barebox-state/backend.c b/src/barebox-state/backend.c new file mode 100644 index 0000000..054f8d6 --- /dev/null +++ b/src/barebox-state/backend.c @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2016 Pengutronix, Markus Pargmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include "state.h" + + +/** + * Save the state + * @param state + * @return + */ +int state_save(struct state *state) +{ + uint8_t *buf; + ssize_t len; + int ret; + struct state_backend *backend = &state->backend; + + if (!state->dirty) + return 0; + + ret = backend->format->pack(backend->format, state, &buf, &len); + if (ret) { + dev_err(&state->dev, "Failed to pack state with backend format %s, %d\n", + backend->format->name, ret); + return ret; + } + + ret = state_storage_write(&backend->storage, buf, len); + if (ret) { + dev_err(&state->dev, "Failed to write packed state, %d\n", ret); + goto out; + } + + state->dirty = 0; + +out: + free(buf); + return ret; +} + +/** + * state_load - Loads a state from the backend + * @param state The state that should be updated to contain the loaded data + * @return 0 on success, -errno on failure. If no state is loaded the previous + * values remain in the state. + * + * This function uses the registered storage backend to read data. All data that + * we read is checked for integrity by the formatter. After that we unpack the + * data into our state. + */ +int state_load(struct state *state) +{ + uint8_t *buf; + ssize_t len; + ssize_t len_hint = 0; + int ret; + struct state_backend *backend = &state->backend; + + if (backend->format->get_packed_len) + len_hint = backend->format->get_packed_len(backend->format, + state); + ret = state_storage_read(&backend->storage, backend->format, + state->magic, &buf, &len, len_hint); + if (ret) { + dev_err(&state->dev, "Failed to read state with format %s, %d\n", + backend->format->name, ret); + return ret; + } + + ret = backend->format->unpack(backend->format, state, buf, len); + if (ret) { + dev_err(&state->dev, "Failed to unpack read data with format %s although verified, %d\n", + backend->format->name, ret); + goto out; + } + + state->dirty = 0; + +out: + free(buf); + return ret; +} + +static int state_format_init(struct state_backend *backend, + struct device_d *dev, const char *backend_format, + struct device_node *node, const char *state_name) +{ + int ret; + + if (!strcmp(backend_format, "raw")) { + ret = backend_format_raw_create(&backend->format, node, + state_name, dev); + } else if (!strcmp(backend_format, "dtb")) { + ret = backend_format_dtb_create(&backend->format, dev); + } else { + dev_err(dev, "Invalid backend format %s\n", + backend_format); + return -EINVAL; + } + + if (ret && ret != -EPROBE_DEFER) + dev_err(dev, "Failed to initialize format %s, %d\n", + backend_format, ret); + + return ret; +} + +static void state_format_free(struct state_backend_format *format) +{ + if (format->free) + format->free(format); +} + +/** + * state_backend_init - Initiates the backend storage and format using the + * passed arguments + * @param backend state backend + * @param dev Device pointer used for prints + * @param node the DT device node corresponding to the state + * @param backend_format a string describing the format. Valid values are 'raw' + * and 'dtb' currently + * @param storage_path Path to the backend storage file/device/partition/... + * @param state_name Name of the state + * @param of_path Path in the devicetree + * @param stridesize stridesize in case we have a medium without eraseblocks. + * stridesize describes how far apart copies of the same data should be stored. + * For blockdevices it makes sense to align them on blocksize. + * @param storagetype Type of the storage backend. This may be NULL where we + * autoselect some backwardscompatible backend options + * @return 0 on success, -errno otherwise + */ +int state_backend_init(struct state_backend *backend, struct device_d *dev, + struct device_node *node, const char *backend_format, + const char *storage_path, const char *state_name, const + char *of_path, off_t offset, size_t max_size, + uint32_t stridesize, const char *storagetype) +{ + struct state_backend_storage_bucket *bucket; + struct state_backend_storage_bucket *bucket_tmp; + int ret; + + ret = state_format_init(backend, dev, backend_format, node, state_name); + if (ret) + return ret; + + ret = state_storage_init(&backend->storage, dev, storage_path, offset, + max_size, stridesize, storagetype); + if (ret) + goto out_free_format; + + list_for_each_entry_safe(bucket, bucket_tmp, &backend->storage.buckets, + bucket_list) { + if (!bucket->init) + continue; + + ret = bucket->init(bucket); + if (ret) { + dev_warn(dev, "Bucket init failed, state degraded, %d\n", + ret); + list_del(&bucket->bucket_list); + bucket->free(bucket); + continue; + } + } + + if (list_empty(&backend->storage.buckets)) { + dev_err(dev, "Failed to initialize any state bucket\n"); + ret = -EIO; + goto out_free_storage; + } + + + backend->of_path = of_path; + + return 0; + +out_free_storage: + state_storage_free(&backend->storage); +out_free_format: + state_format_free(backend->format); + backend->format = NULL; + + return ret; +} + +void state_backend_free(struct state_backend *backend) +{ + state_storage_free(&backend->storage); + if (backend->format) + state_format_free(backend->format); +} diff --git a/src/barebox-state/backend_bucket_circular.c b/src/barebox-state/backend_bucket_circular.c new file mode 100644 index 0000000..926ef49 --- /dev/null +++ b/src/barebox-state/backend_bucket_circular.c @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2016 Pengutronix, Markus Pargmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "state.h" + + +struct state_backend_storage_bucket_circular { + struct state_backend_storage_bucket bucket; + + unsigned int eraseblock; /* Which eraseblock is used */ + ssize_t writesize; /* Alignment of writes */ + ssize_t max_size; /* Maximum size of this bucket */ + + off_t write_area; /* Start of the write area (relative offset) */ + uint32_t last_written_length; /* Size of the data written in the storage */ + +#ifdef __BAREBOX__ + struct mtd_info *mtd; /* mtd info (used for io in Barebox)*/ +#else + struct mtd_info_user *mtd; + int fd; +#endif + + /* Cached data of the last read/write */ + u8 *current_data; + ssize_t current_data_len; + + /* 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; + } + } else if (ret < 0 && ret != -EUCLEAN) { + dev_err(circ->dev, "Failed to read PEB %u, %d\n", + circ->eraseblock, ret); + return ret; + } + + return 0; +} + +static int state_mtd_peb_write(struct state_backend_storage_bucket_circular *circ, + const char *buf, int offset, int len) +{ + int ret; + + ret = mtd_peb_write(circ->mtd, buf, circ->eraseblock, offset, len); + if (ret == -EBADMSG) { + ret = mtd_peb_torture(circ->mtd, circ->eraseblock); + if (ret == -EIO) { + dev_err(circ->dev, "Tortured eraseblock failed and is marked bad now, PEB %u\n", + circ->eraseblock); + return -EIO; + } else if (ret < 0) { + dev_err(circ->dev, "Failed to torture eraseblock, %d\n", + ret); + return ret; + } + } else if (ret < 0 && ret != -EUCLEAN) { + dev_err(circ->dev, "Failed to write PEB %u, %d\n", + circ->eraseblock, ret); + return ret; + } + + return 0; +} + +static int state_mtd_peb_erase(struct state_backend_storage_bucket_circular *circ) +{ + return mtd_peb_erase(circ->mtd, circ->eraseblock); +} +#else +static int state_mtd_peb_read(struct state_backend_storage_bucket_circular *circ, + char *buf, int suboffset, int len) +{ + int ret; + off_t offset = suboffset; + + 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 = 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; + } + + return 0; +} + +static int state_mtd_peb_write(struct state_backend_storage_bucket_circular *circ, + const char *buf, int suboffset, int len) +{ + int ret; + off_t offset = suboffset; + + offset += circ->eraseblock * circ->mtd->erasesize; + + ret = lseek(circ->fd, offset, SEEK_SET); + if (ret < 0) { + dev_err(circ->dev, "Failed to set position for circular write %ld, %d\n", + offset, ret); + return ret; + } + + ret = write_full(circ->fd, buf, len); + if (ret < 0) { + dev_err(circ->dev, "Failed to write circular to %ld length %zd, %d\n", + offset, len, ret); + return ret; + } + + /* + * We keep the fd open, so flush is necessary. We ignore the return + * value as flush is currently not supported for mtd under linux. + */ + flush(circ->fd); + + dev_dbg(circ->dev, "Written state to offset %ld length %zd data length %zd\n", + offset, len, len); + + return 0; +} + +static int state_mtd_peb_erase(struct state_backend_storage_bucket_circular *circ) +{ + return erase(circ->fd, circ->mtd->erasesize, + (off_t)circ->eraseblock * circ->mtd->erasesize); +} +#endif + +static int state_backend_bucket_circular_fill_cache( + struct state_backend_storage_bucket *bucket) +{ + struct state_backend_storage_bucket_circular *circ = + get_bucket_circular(bucket); + ssize_t read_len; + off_t offset; + uint8_t *buf; + int ret; + + /* Storage is empty */ + if (circ->write_area == 0) + return -ENODATA; + + if (!circ->last_written_length) { + /* + * Last write did not contain length information, assuming old + * state and reading from the beginning. + */ + offset = 0; + read_len = min(circ->write_area, (off_t)(circ->max_size - + sizeof(struct state_backend_storage_bucket_circular_meta))); + circ->write_area = 0; + dev_dbg(circ->dev, "Detected old on-storage format\n"); + } else if (circ->last_written_length > circ->write_area + || !IS_ALIGNED(circ->last_written_length, circ->writesize)) { + circ->write_area = 0; + dev_err(circ->dev, "Error, invalid number of bytes written last time %d\n", + circ->last_written_length); + return -EINVAL; + } else { + /* + * Normally we read at the end of the non-free area. The length + * of the read is then what we read from the meta data + * (last_written_length) + */ + read_len = circ->last_written_length; + offset = circ->write_area - read_len; + } + + buf = xmalloc(read_len); + if (!buf) + return -ENOMEM; + + dev_dbg(circ->dev, "Read state from PEB %u global offset %ld length %zd\n", + circ->eraseblock, offset, read_len); + + ret = state_mtd_peb_read(circ, buf, offset, read_len); + if (ret < 0) { + dev_err(circ->dev, "Failed to read circular storage len %zd, %d\n", + read_len, ret); + free(buf); + return ret; + } + + circ->current_data = buf; + circ->current_data_len = read_len; + + return 0; +} + +static int state_backend_bucket_circular_read(struct state_backend_storage_bucket *bucket, + uint8_t ** buf_out, + ssize_t * len_hint) +{ + struct state_backend_storage_bucket_circular *circ = + get_bucket_circular(bucket); + int ret; + + if (!circ->current_data) { + ret = state_backend_bucket_circular_fill_cache(bucket); + if (ret) + return ret; + } + + /* + * We keep the internal copy in the cache and duplicate the data for + * external use + */ + *buf_out = xmemdup(circ->current_data, circ->current_data_len); + if (!*buf_out) + return -ENOMEM; + + *len_hint = circ->current_data_len - sizeof(struct state_backend_storage_bucket_circular_meta); + + return 0; +} + +static int state_backend_bucket_circular_write(struct state_backend_storage_bucket *bucket, + const uint8_t * buf, + ssize_t len) +{ + struct state_backend_storage_bucket_circular *circ = + get_bucket_circular(bucket); + off_t offset; + struct state_backend_storage_bucket_circular_meta *meta; + uint32_t written_length = ALIGN(len + sizeof(*meta), circ->writesize); + int ret; + uint8_t *write_buf; + + if (written_length > circ->max_size) { + dev_err(circ->dev, "Error, state data too big to be written, to write: %zd, writesize: %zd, length: %zd, available: %zd\n", + written_length, circ->writesize, len, circ->max_size); + return -E2BIG; + } + + /* + * We need zero initialization so that our data comparisons don't show + * random changes + */ + write_buf = xzalloc(written_length); + if (!write_buf) + return -ENOMEM; + + memcpy(write_buf, buf, len); + meta = (struct state_backend_storage_bucket_circular_meta *) + (write_buf + written_length - sizeof(*meta)); + meta->magic = circular_magic; + meta->written_length = written_length; + + /* Nothing in cache? Then read to see what is on the device currently */ + if (!circ->current_data) { + state_backend_bucket_circular_fill_cache(bucket); + } + + /* + * If we would write the same data that is currently on the device, we + * can return successfully without writing. + * Note that the cache may still be empty if the storage is empty or + * errors occured. + */ + if (circ->current_data) { + dev_dbg(circ->dev, "Comparing cached data, writing %zd bytes, cached %zd bytes\n", + written_length, circ->current_data_len); + if (written_length == circ->current_data_len && + !memcmp(circ->current_data, write_buf, written_length)) { + dev_dbg(circ->dev, "Data already on device, not writing again\n"); + goto out_free; + } else { + free(circ->current_data); + circ->current_data = NULL; + } + } + + if (circ->write_area + written_length >= circ->max_size) { + circ->write_area = 0; + } + /* + * If the write area is at the beginning of the page, erase it and write + * at offset 0. As we only erase right before writing there are no + * conditions where we regularly erase a block multiple times without + * writing. + */ + if (circ->write_area == 0) { + dev_dbg(circ->dev, "Erasing PEB %u\n", circ->eraseblock); + ret = state_mtd_peb_erase(circ); + if (ret) { + dev_err(circ->dev, "Failed to erase PEB %u\n", + circ->eraseblock); + goto out_free; + } + } + + offset = circ->write_area; + + /* + * Update write_area before writing. The write operation may put + * arbitrary amount of the data into the storage before failing. In this + * case we want to start after that area. + */ + circ->write_area += written_length; + + ret = state_mtd_peb_write(circ, write_buf, offset, written_length); + if (ret < 0) { + dev_err(circ->dev, "Failed to write circular to %ld length %zd, %d\n", + offset, written_length, ret); + goto out_free; + } + + dev_dbg(circ->dev, "Written state to PEB %u offset %ld length %zd data length %zd\n", + circ->eraseblock, offset, written_length, len); + + /* Put written data into the cache */ + WARN_ON(circ->current_data); + circ->current_data = write_buf; + circ->current_data_len = written_length; + write_buf = NULL; + +out_free: + if (write_buf) + free(write_buf); + return 0; +} + +/** + * state_backend_bucket_circular_init - Initialize circular bucket + * @param bucket + * @return 0 on success, -errno otherwise + * + * This function searches for the beginning of the written area from the end of + * the MTD device. This way it knows where the data ends and where the free area + * starts. + */ +static int state_backend_bucket_circular_init( + struct state_backend_storage_bucket *bucket) +{ + struct state_backend_storage_bucket_circular *circ = + get_bucket_circular(bucket); + int sub_offset; + uint32_t written_length = 0; + uint8_t *buf; + + buf = xmalloc(circ->writesize); + if (!buf) + return -ENOMEM; + + for (sub_offset = circ->max_size - circ->writesize; sub_offset >= 0; + sub_offset -= circ->writesize) { + int ret; + + ret = state_mtd_peb_read(circ, buf, sub_offset, + circ->writesize); + if (ret) + return ret; + + ret = mtd_buf_all_ff(buf, circ->writesize); + if (!ret) { + struct state_backend_storage_bucket_circular_meta *meta; + + meta = (struct state_backend_storage_bucket_circular_meta *) + (buf + circ->writesize - sizeof(*meta)); + + if (meta->magic != circular_magic) + written_length = 0; + else + written_length = meta->written_length; + + break; + } + } + + circ->write_area = sub_offset + circ->writesize; + circ->last_written_length = written_length; + + free(buf); + + return 0; +} + +static void state_backend_bucket_circular_free(struct + state_backend_storage_bucket + *bucket) +{ + struct state_backend_storage_bucket_circular *circ = + get_bucket_circular(bucket); + + if (circ->current_data) + free(circ->current_data); + free(circ); +} + +#ifdef __BAREBOX__ +static int bucket_circular_is_block_bad(struct state_backend_storage_bucket_circular *circ) +{ + int ret; + + ret = mtd_peb_is_bad(circ->mtd, circ->eraseblock); + if (ret < 0) + dev_err(circ->dev, "Failed to determine whether eraseblock %u is bad, %d\n", + circ->eraseblock, ret); + + return ret; +} +#else +static int bucket_circular_is_block_bad(struct state_backend_storage_bucket_circular *circ) +{ + int ret; + loff_t offs = circ->eraseblock * circ->mtd->erasesize; + + ret = ioctl(circ->fd, MEMGETBADBLOCK, &offs); + if (ret < 0) + dev_err(circ->dev, "Failed to use ioctl to check for bad block at offset %ld, %d\n", + offs, ret); + + return ret; +} +#endif + +int state_backend_bucket_circular_create(struct device_d *dev, const char *path, + struct state_backend_storage_bucket **bucket, + unsigned int eraseblock, + ssize_t writesize, + struct mtd_info_user *mtd_uinfo) +{ + struct state_backend_storage_bucket_circular *circ; + int ret; + + circ = xzalloc(sizeof(*circ)); + circ->eraseblock = eraseblock; + circ->writesize = writesize; + circ->max_size = mtd_uinfo->erasesize; + circ->dev = dev; + +#ifdef __BAREBOX__ + circ->mtd = mtd_uinfo->mtd; +#else + circ->mtd = xzalloc(sizeof(*mtd_uinfo)); + memcpy(circ->mtd, mtd_uinfo, sizeof(*mtd_uinfo)); + circ->fd = open(path, O_RDWR); + if (circ->fd < 0) { + pr_err("Failed to open circular bucket '%s'\n", path); + return -errno; + } +#endif + + ret = bucket_circular_is_block_bad(circ); + if (ret) { + dev_info(dev, "Not using eraseblock %u, it is marked as bad (%d)\n", + circ->eraseblock, ret); + ret = -EIO; + goto out_free; + } + + circ->bucket.init = state_backend_bucket_circular_init; + 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; + + return 0; + +out_free: +#ifndef __BAREBOX__ + close(circ->fd); +#endif + free(circ); + + return ret; +} diff --git a/src/barebox-state/backend_bucket_direct.c b/src/barebox-state/backend_bucket_direct.c new file mode 100644 index 0000000..08892f0 --- /dev/null +++ b/src/barebox-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/src/barebox-state/backend_format_dtb.c b/src/barebox-state/backend_format_dtb.c new file mode 100644 index 0000000..dc19c88 --- /dev/null +++ b/src/barebox-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/src/barebox-state/backend_format_raw.c b/src/barebox-state/backend_format_raw.c new file mode 100644 index 0000000..ad55b9f --- /dev/null +++ b/src/barebox-state/backend_format_raw.c @@ -0,0 +1,327 @@ +/* + * 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 "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/src/barebox-state/backend_storage.c b/src/barebox-state/backend_storage.c new file mode 100644 index 0000000..4620eda --- /dev/null +++ b/src/barebox-state/backend_storage.c @@ -0,0 +1,470 @@ +/* + * 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; + +/** + * 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; + + list_for_each_entry(bucket, &storage->buckets, bucket_list) { + 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->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; + + if (non_circular) + writesize = meminfo->erasesize; + + ret = state_backend_bucket_circular_create(storage->dev, path, + &bucket, + eraseblock, + writesize, + meminfo); + if (ret) { + dev_warn(storage->dev, "Failed to create bucket at '%s' eraseblock %u\n", + path, eraseblock); + continue; + } + + 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; + } + + 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"); +} + +/** + * 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/src/barebox-state/state.c b/src/barebox-state/state.c new file mode 100644 index 0000000..7ea29e1 --- /dev/null +++ b/src/barebox-state/state.c @@ -0,0 +1,570 @@ +/* + * 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 "state.h" + +/* list of all registered state instances */ +static LIST_HEAD(state_list); + +static int state_set_deny(struct param_d *p, void *priv) +{ + return -EROFS; +} + +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", state_set_deny, NULL, &state->dirty, + NULL); + dev_add_param_bool(&state->dev, "default", state_set_deny, NULL, &state->state_default, + NULL); + + list_add_tail(&state->list, &state_list); + + return state; +} + +#ifndef __BAREBOX__ +#define asprintf(fmt, arg...) barebox_asprintf(fmt, ##arg) +#endif + +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 = asprintf("%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; + } + } + } + + 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. + */ +struct state *state_new_from_node(struct device_node *node, char *path, + off_t offset, size_t max_size) +{ + 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; + + 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); + state->state_default = 1; + } + + 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/src/barebox-state/state.h b/src/barebox-state/state.h new file mode 100644 index 0000000..2226356 --- /dev/null +++ b/src/barebox-state/state.h @@ -0,0 +1,267 @@ +#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); + + 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; +}; + +/** + * 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; /* State is different from stored state */ + unsigned int state_default; /* State has default values */ + + 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_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); +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_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/src/barebox-state/state_variables.c b/src/barebox-state/state_variables.c new file mode 100644 index 0000000..647a898 --- /dev/null +++ b/src/barebox-state/state_variables.c @@ -0,0 +1,494 @@ +/* + * 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; + state->state_default = 0; + + 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/src/crypto/keystore.h b/src/crypto/keystore.h new file mode 100644 index 0000000..193a413 --- /dev/null +++ b/src/crypto/keystore.h @@ -0,0 +1,24 @@ +/* + * (C) Copyright 2015 Pengutronix, Marc Kleine-Budde + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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; version 2 of + * the License. + * + * 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. + * + */ + +#ifndef __KEYSTORE_H +#define __KEYSTORE_H + +int keystore_get_secret(const char *name, const unsigned char **key, int *key_len); + +#endif diff --git a/src/driver.h b/src/driver.h new file mode 100644 index 0000000..40a8c17 --- /dev/null +++ b/src/driver.h @@ -0,0 +1 @@ +/* empty */ diff --git a/src/dt/common.h b/src/dt/common.h index 3f3707c..69d54e3 100644 --- a/src/dt/common.h +++ b/src/dt/common.h @@ -42,11 +42,26 @@ #endif #define pr_err(fmt, arg...) fprintf(stderr, fmt, ##arg) +#define pr_warn(fmt, arg...) pr_err(fmt, ##arg) +#define pr_info(fmt, arg...) fprintf(stdout, fmt, ##arg) #define dev_err(dev, fmt, arg...) pr_err(fmt, ##arg) #define dev_warn(dev, fmt, arg...) pr_err(fmt, ##arg) #define dev_info(dev, fmt, arg...) pr_err(fmt, ##arg) #define dev_dbg(dev, fmt, arg...) pr_debug(fmt, ##arg) +#define __WARN() do { \ + printf("WARNING: at %s:%d/%s()!\n", __FILE__, __LINE__, __FUNCTION__); \ +} while (0) + +#ifndef WARN_ON +#define WARN_ON(condition) ({ \ + int __ret_warn_on = !!(condition); \ + if (__ret_warn_on) \ + __WARN(); \ + __ret_warn_on; \ +}) +#endif + #ifndef EPROBE_DEFER #define EPROBE_DEFER 517 #endif @@ -65,6 +80,15 @@ return xzalloc(size); } +static inline void *xmemdup(const void *orig, size_t size) +{ + void *buf = xmalloc(size); + + memcpy(buf, orig, size); + + return buf; +} + #define EXPORT_SYMBOL(sym) #define EXPORT_SYMBOL_GPL(sym) @@ -272,7 +296,7 @@ * Like write, but guarantees to write the full buffer out, else * it returns with an error. */ -static inline int write_full(int fd, void *buf, size_t size) +static inline int write_full(int fd, const void *buf, size_t size) { size_t insize = size; int now; @@ -414,6 +438,7 @@ #define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a) - 1) #define __ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask)) +#define IS_ALIGNED(x, a) (((x) & ((typeof(x))(a) - 1)) == 0) #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) @@ -554,4 +579,38 @@ uint32_t crc32(uint32_t crc, const void *_buf, unsigned int len); uint32_t crc32_no_comp(uint32_t crc, const void *_buf, unsigned int len); +#define flush(fd) fsync(fd) + +static inline int mtd_buf_all_ff(const void *buf, unsigned int len) +{ + while ((unsigned long)buf & 0x3) { + if (*(const uint8_t *)buf != 0xff) + return 0; + len--; + if (!len) + return 1; + buf++; + } + + while (len > 0x3) { + if (*(const uint32_t *)buf != 0xffffffff) + return 0; + + len -= sizeof(uint32_t); + if (!len) + return 1; + + buf += sizeof(uint32_t); + } + + while (len) { + if (*(const uint8_t *)buf != 0xff) + return 0; + len--; + buf++; + } + + return 1; +} + #endif /* __DT_COMMON_H */ diff --git a/src/dt/dt.h b/src/dt/dt.h index b6088c4..0ac6bcd 100644 --- a/src/dt/dt.h +++ b/src/dt/dt.h @@ -368,4 +368,16 @@ struct device_node *of_read_proc_devicetree(void); +static inline int of_find_path(struct device_node *node, const char *propname, + char **outpath, unsigned flags) +{ + return -ENOSYS; +} + +static inline int of_find_path_by_node(struct device_node *node, char **outpath, + unsigned flags) +{ + return -ENOSYS; +} + #endif /* __DT_DT_H */ diff --git a/src/fdt.h b/src/fdt.h new file mode 100644 index 0000000..2617433 --- /dev/null +++ b/src/fdt.h @@ -0,0 +1,8 @@ +#ifndef _FDT_H +#define _FDT_H + +#include + +#define fdt32_to_cpu(x) be32_to_cpu(x) + +#endif diff --git a/src/keystore-blob.c b/src/keystore-blob.c index 2d8d81a..089fea7 100644 --- a/src/keystore-blob.c +++ b/src/keystore-blob.c @@ -7,9 +7,10 @@ */ #include -#include -#include +#include #include +#include +#include static const char keystore_state_name[] = "/blobs"; static const char blob_gen_payload[] = "/sys/bus/platform/devices/blob_gen/payload"; diff --git a/src/keystore.h b/src/keystore.h deleted file mode 100644 index 193a413..0000000 --- a/src/keystore.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * (C) Copyright 2015 Pengutronix, Marc Kleine-Budde - * - * See file CREDITS for list of people who contributed to this - * project. - * - * 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; version 2 of - * the License. - * - * 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. - * - */ - -#ifndef __KEYSTORE_H -#define __KEYSTORE_H - -int keystore_get_secret(const char *name, const unsigned char **key, int *key_len); - -#endif diff --git a/src/libbb.h b/src/libbb.h new file mode 100644 index 0000000..9f9d32d --- /dev/null +++ b/src/libbb.h @@ -0,0 +1,6 @@ +#ifndef __LIBBB_H +#define __LIBBB_H + +#include
+ +#endif diff --git a/src/libfile.h b/src/libfile.h new file mode 100644 index 0000000..40a8c17 --- /dev/null +++ b/src/libfile.h @@ -0,0 +1 @@ +/* empty */ diff --git a/src/linux/mtd/mtd-abi.h b/src/linux/mtd/mtd-abi.h new file mode 100644 index 0000000..bd0de76 --- /dev/null +++ b/src/linux/mtd/mtd-abi.h @@ -0,0 +1 @@ +#include diff --git a/src/mtd/mtd-peb.h b/src/mtd/mtd-peb.h new file mode 100644 index 0000000..40a8c17 --- /dev/null +++ b/src/mtd/mtd-peb.h @@ -0,0 +1 @@ +/* empty */ diff --git a/src/net.h b/src/net.h new file mode 100644 index 0000000..40a8c17 --- /dev/null +++ b/src/net.h @@ -0,0 +1 @@ +/* empty */ diff --git a/src/of.h b/src/of.h new file mode 100644 index 0000000..4cbf197 --- /dev/null +++ b/src/of.h @@ -0,0 +1,7 @@ +#ifndef __OF_H +#define __OF_H + +#include +#include
+ +#endif diff --git a/src/printk.h b/src/printk.h new file mode 100644 index 0000000..a0adcaa --- /dev/null +++ b/src/printk.h @@ -0,0 +1 @@ +#include
diff --git a/src/state.h b/src/state.h new file mode 100644 index 0000000..48c60ea --- /dev/null +++ b/src/state.h @@ -0,0 +1,25 @@ +#ifndef __STATE_H +#define __STATE_H + +#include + +struct state; + +int state_backend_dtb_file(struct state *state, const char *of_path, + const char *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(struct device_node *node, char *path, + off_t offset, size_t max_size); +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); + +#endif /* __STATE_H */