diff options
Diffstat (limited to 'src/state.c')
-rw-r--r-- | src/state.c | 1309 |
1 files changed, 1309 insertions, 0 deletions
diff --git a/src/state.c b/src/state.c new file mode 100644 index 0000000..97c6b4a --- /dev/null +++ b/src/state.c @@ -0,0 +1,1309 @@ +/* + * state.c - state handling tool + * + * Copyright (c) 2014 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix + * + * 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 <libudev.h> +#include <common.h> +#include <dirent.h> +#include <getopt.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <dt.h> + +#include <asm/byteorder.h> +#include <linux/types.h> +#include <mtd/mtd-abi.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> + +static int verbose; + +enum state_variable_type { + STATE_TYPE_INVALID = 0, + STATE_TYPE_ENUM, + STATE_TYPE_U32, + STATE_TYPE_MAC, +}; + +struct state_backend; + +struct state { + struct list_head variables; + const char *name; + struct list_head list; + struct state_backend *backend; + uint32_t magic; + int dirty; +}; + +struct state_backend { + int (*load)(struct state_backend *backend, struct state *state); + int (*save)(struct state_backend *backend, struct state *state); + const char *name; + char *path; +}; + +/* list of all registered state instances */ +static LIST_HEAD(state_list); + +/* instance of a single variable */ +struct state_variable { + enum state_variable_type type; + struct list_head list; + char *name; + void *raw; + int raw_size; + int index; +}; + +/* A variable type (uint32, enum32) */ +struct variable_type { + enum state_variable_type type; + const char *type_name; + struct list_head list; + int (*export)(struct state_variable *, struct device_node *); + int (*init)(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 *); +}; + +/* + * uint32 + */ +struct state_uint32 { + struct state_variable var; + struct param_d *param; + uint32_t value; + uint32_t value_default; +}; + +static int state_var_compare(struct list_head *a, struct list_head *b) +{ + struct state_variable *va = list_entry(a, struct state_variable, list); + struct state_variable *vb = list_entry(b, struct state_variable, list); + + if (va->index == vb->index) + return 0; + + return va->index < vb->index ? -1 : 1; +} + +static int state_add_variable(struct state *state, struct state_variable *var) +{ + list_add_sort(&var->list, &state->variables, state_var_compare); + + return 0; +} + +static inline struct state_uint32 *to_state_uint32(struct state_variable *s) +{ + return container_of(s, struct state_uint32, var); +} + +static int state_uint32_export(struct state_variable *var, + struct device_node *node) +{ + struct state_uint32 *su32 = to_state_uint32(var); + int ret; + + ret = of_property_write_u32(node, "default", su32->value_default); + if (ret) + return ret; + + return of_property_write_u32(node, "value", su32->value); +} + +static int state_uint32_init(struct state_variable *sv, + struct device_node *node) +{ + int len; + const __be32 *value, *value_default; + struct state_uint32 *su32 = to_state_uint32(sv); + + value_default = of_get_property(node, "default", &len); + if (value_default && len != sizeof(uint32_t)) + return -EINVAL; + + value = of_get_property(node, "value", &len); + if (value && len != sizeof(uint32_t)) + return -EINVAL; + + if (value_default) + su32->value_default = be32_to_cpu(*value_default); + if (value) + su32->value = be32_to_cpu(*value); + else + su32->value = su32->value_default; + + return 0; +} + +static struct state_variable *state_uint32_create(struct state *state, + const char *name, struct device_node *node) +{ + struct state_uint32 *su32; + + su32 = xzalloc(sizeof(*su32)); + + pr_debug("%s: %s\n", __func__, name); + + su32->var.raw_size = sizeof(uint32_t); + su32->var.raw = &su32->value; + + return &su32->var; +} + +static int state_uint32_set(struct state_variable *var, const char *val) +{ + struct state_uint32 *su32 = to_state_uint32(var); + + su32->value = strtoul(val, NULL, 0); + + return 0; +} + +static char *state_uint32_get(struct state_variable *var) +{ + struct state_uint32 *su32 = to_state_uint32(var); + char *str; + int ret; + + ret = asprintf(&str, "%d", su32->value); + if (ret < 0) + return ERR_PTR(-ENOMEM); + + return str; +} + +/* + * enum32 + */ +struct state_enum32 { + struct state_variable var; + struct param_d *param; + uint32_t value; + uint32_t value_default; + const char **names; + int num_names; +}; + +static inline struct state_enum32 *to_state_enum32(struct state_variable *s) +{ + return container_of(s, struct state_enum32, var); +} + +static int state_enum32_export(struct state_variable *var, + struct device_node *node) +{ + struct state_enum32 *enum32 = to_state_enum32(var); + int ret, i, len; + char *prop, *str; + + ret = of_property_write_u32(node, "default", enum32->value_default); + if (ret) + return ret; + + ret = of_property_write_u32(node, "value", enum32->value); + if (ret) + return ret; + + len = 0; + + for (i = 0; i < enum32->num_names; i++) + len += strlen(enum32->names[i]) + 1; + + prop = xzalloc(len); + str = prop; + + for (i = 0; i < enum32->num_names; i++) + str += sprintf(str, "%s", enum32->names[i]) + 1; + + ret = of_set_property(node, "names", prop, len, 1); + + free(prop); + + return ret; +} + +static int state_enum32_init(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"); + + enum32->names = xzalloc(sizeof(char *) * num_names); + enum32->num_names = num_names; + enum32->var.raw_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] = strdup(name); + } + + pr_debug("%s: %s\n", __func__, name); + + return &enum32->var; +out: + free(enum32->names); + free(enum32); + return ERR_PTR(ret); +} + +static int state_enum32_set(struct state_variable *sv, const char *val) +{ + struct state_enum32 *enum32 = to_state_enum32(sv); + int i; + + for (i = 0; i < enum32->num_names; i++) { + if (!strcmp(enum32->names[i], val)) { + enum32->value = i; + return 0; + } + } + + return -EINVAL; +} + +static char *state_enum32_get(struct state_variable *var) +{ + struct state_enum32 *enum32 = to_state_enum32(var); + char *str; + int ret; + + ret = asprintf(&str, "%s", enum32->names[enum32->value]); + if (ret < 0) + return ERR_PTR(-ENOMEM); + + return str; +} + +static void state_enum32_info(struct state_variable *var) +{ + struct state_enum32 *enum32 = to_state_enum32(var); + int i; + + printf(", values=["); + + for (i = 0; i < enum32->num_names; i++) + printf("%s%s", enum32->names[i], + i == enum32->num_names - 1 ? "" : ","); + printf("]"); +} + +/* + * MAC address + */ +struct state_mac { + struct state_variable var; + struct param_d *param; + uint8_t value[6]; + uint8_t value_default[6]; +}; + +static inline struct state_mac *to_state_mac(struct state_variable *s) +{ + return container_of(s, struct state_mac, var); +} + +static int state_mac_export(struct state_variable *var, + struct device_node *node) +{ + struct state_mac *mac = to_state_mac(var); + int ret; + + ret = of_property_write_u8_array(node, "default", mac->value_default, 6); + if (ret) + return ret; + + return of_property_write_u8_array(node, "value", mac->value, 6); +} + +static int state_mac_init(struct state_variable *sv, struct device_node *node) +{ + struct state_mac *mac = to_state_mac(sv); + uint8_t value[6] = {}; + uint8_t value_default[6] = {}; + + of_property_read_u8_array(node, "default", value_default, 6); + memcpy(mac->value_default, value_default, 6); + + if (!of_property_read_u8_array(node, "value", value, 6)) + memcpy(mac->value, value, 6); + else + memcpy(mac->value, value_default, 6); + + 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.raw_size = 6; + mac->var.raw = mac->value; + + pr_debug("%s: %s\n", __func__, name); + + return &mac->var; +out: + free(mac); + return ERR_PTR(ret); +} + +int string_to_ethaddr(const char *str, uint8_t enetaddr[6]) +{ + int reg; + char *e; + + if (!str || strlen(str) != 17) { + memset(enetaddr, 0, 6); + return -EINVAL; + } + + if (str[2] != ':' || str[5] != ':' || str[8] != ':' || + str[11] != ':' || str[14] != ':') + return -EINVAL; + + for (reg = 0; reg < 6; ++reg) { + enetaddr[reg] = strtoul(str, &e, 16); + str = e + 1; + } + + return 0; +} + +static int state_mac_set(struct state_variable *var, const char *val) +{ + struct state_mac *mac = to_state_mac(var); + char mac_save[6]; + int ret; + + ret = string_to_ethaddr(val, mac_save); + if (ret) + return ret; + + memcpy(mac->value, mac_save, 6); + + return 0; +} + +static char *state_mac_get(struct state_variable *var) +{ + struct state_mac *mac = to_state_mac(var); + char *str; + int ret; + + ret = asprintf(&str, "%02x:%02x:%02x:%02x:%02x:%02x", + mac->value[0], mac->value[1], mac->value[2], + mac->value[3], mac->value[4], mac->value[5]); + if (ret < 0) + return ERR_PTR(-ENOMEM); + + return str; +} + +static struct variable_type types[] = { + { + .type = STATE_TYPE_U32, + .type_name = "uint32", + .export = state_uint32_export, + .init = state_uint32_init, + .create = state_uint32_create, + .set = state_uint32_set, + .get = state_uint32_get, + }, { + .type = STATE_TYPE_ENUM, + .type_name = "enum32", + .export = state_enum32_export, + .init = state_enum32_init, + .create = state_enum32_create, + .set = state_enum32_set, + .get = state_enum32_get, + .info = state_enum32_info, + }, { + .type = STATE_TYPE_MAC, + .type_name = "mac", + .export = state_mac_export, + .init = state_mac_init, + .create = state_mac_create, + .set = state_mac_set, + .get = state_mac_get, + }, +}; + +static struct variable_type *state_find_type(enum state_variable_type type) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(types); i++) { + if (type == types[i].type) { + return &types[i]; + } + } + + return NULL; +} + +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)); + state->name = name; + INIT_LIST_HEAD(&state->variables); + + list_add_tail(&state->list, &state_list); + + return state; +} + +static void state_release(struct state *state) +{ + free(state); +} + +static struct device_node *state_to_node(struct state *state) +{ + struct device_node *root, *node; + struct state_variable *sv; + int ret; + + root = of_new_node(NULL, NULL); + + list_for_each_entry(sv, &state->variables, list) { + struct variable_type *vtype; + char *name; + + asprintf(&name, "%s@%d", sv->name, sv->index); + node = of_new_node(root, name); + free(name); + + vtype = state_find_type(sv->type); + if (!vtype) { + ret = -ENOENT; + goto out; + } + + of_set_property(node, "type", vtype->type_name, + strlen(vtype->type_name) + 1, 1); + + ret = vtype->export(sv, node); + if (ret) + goto out; + } + + of_property_write_u32(root, "magic", state->magic); + + return root; +out: + of_delete_node(root); + return ERR_PTR(ret); +} + +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 NULL; +} + +static char *state_get_var(struct state *state, const char *var) +{ + struct state_variable *sv; + struct variable_type *vtype; + + sv = state_find_var(state, var); + if (!sv) + return NULL; + + vtype = state_find_type(sv->type); + + 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; + int ret; + + sv = state_find_var(state, var); + if (!sv) + return -ENOENT; + + vtype = state_find_type(sv->type); + + if (!vtype->set) + return -EPERM; + + ret = vtype->set(sv, val); + if (ret) + return ret; + + state->dirty = 1; + + return 0; +} + +static int state_variable_from_node(struct state *state, struct device_node *node, + bool create) +{ + struct variable_type *vtype; + struct state_variable *sv; + char *name, *indexs; + int index = 0; + const char *type_name = NULL; + + of_property_read_string(node, "type", &type_name); + if (!type_name) + return -EINVAL; + + vtype = state_find_type_by_name(type_name); + if (!vtype) + return -ENOENT; + + name = strdup(node->name); + indexs = strchr(name, '@'); + if (indexs) { + *indexs++ = 0; + index = strtoul(indexs, NULL, 10); + } + + if (create) { + sv = vtype->create(state, name, node); + if (IS_ERR(sv)) { + int ret = PTR_ERR(sv); + pr_err("failed to create %s: %s\n", name, strerror(-ret)); + return ret; + } + sv->name = name; + sv->type = vtype->type; + sv->index = index; + state_add_variable(state, sv); + } else { + sv = state_find_var(state, name); + if (IS_ERR(sv)) { + int ret = PTR_ERR(sv); + pr_err("no such variable: %s: %s\n", name, strerror(-ret)); + return ret; + } + } + + vtype->init(sv, node); + + return 0; +} + +int state_from_node(struct state *state, struct device_node *node, bool create) +{ + struct device_node *child; + int ret; + uint32_t magic; + + of_property_read_u32(node, "magic", &magic); + + if (create) { + state->magic = magic; + } else { + if (state->magic && state->magic != magic) { + pr_err("invalid magic 0x%08x, should be 0x%08x\n", + magic, state->magic); + return -EINVAL; + } + } + + for_each_child_of_node(node, child) { + ret = state_variable_from_node(state, child, create); + if (ret) + return ret; + } + + return 0; +} + +/* + * 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 (!state) + return ERR_PTR(-EINVAL); + + ret = state_from_node(state, node, 1); + 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 *s; + + list_for_each_entry(s, &state_list, list) { + if (!strcmp(name, s->name)) + return s; + } + + return NULL; +} + +/* + * state_load - load a state from the backing store + * + * @state The state instance to load + */ +int state_load(struct state *state) +{ + int ret; + + if (!state->backend) + return -ENOSYS; + + ret = state->backend->load(state->backend, state); + if (ret) + return ret; + + state->dirty = 0; + + 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->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 *s; + + printf("registered state instances:\n"); + + list_for_each_entry(s, &state_list, list) { + printf("%-20s ", s->name); + if (s->backend) + printf("(backend: %s, path: %s)\n", s->backend->name, s->backend->path); + else + printf("(no backend)\n"); + } +} + +static int get_meminfo(const char *path, struct mtd_info_user *meminfo) +{ + int fd, ret; + + fd = open(path, O_RDWR); + if (fd < 0) + return -errno; + + ret = ioctl(fd, MEMGETINFO, meminfo); + + close(fd); + + if (ret) + return -errno; + + return 0; +} + +/* + * Raw backend implementation + */ +struct state_backend_raw { + struct state_backend backend; + struct state *state; + unsigned long size_data; /* The raw data size (without magic and crc) */ + unsigned long size_full; + unsigned long step; /* The step in bytes between two copies */ + off_t offset; /* offset in the storage file */ + size_t size; /* size of the storage area */ + int need_erase; + int num_copy_read; /* The first successfully read copy */ +}; + +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, + int fd, off_t offset) +{ + struct state *state = backend_raw->state; + uint32_t crc; + void *off; + struct state_variable *sv; + struct backend_raw_header header = {}; + int ret, len; + void *buf; + + ret = lseek(fd, offset, SEEK_SET); + if (ret < 0) + return ret; + + ret = read(fd, &header, sizeof(header)); + if (ret < 0) + return ret; + if (ret < sizeof(header)) + return -EINVAL; + + crc = crc32(0, &header, sizeof(header) - sizeof(uint32_t)); + if (crc != header.header_crc) { + pr_err("invalid header crc, calculated 0x%08x, found 0x%08x\n", + crc, header.header_crc); + return -EINVAL; + } + + if (state->magic && state->magic != header.magic) { + pr_err("invalid magic 0x%08x, should be 0x%08x\n", + header.magic, state->magic); + return -EINVAL; + } + + buf = xzalloc(header.data_len); + + ret = read(fd, buf, header.data_len); + if (ret < 0) + return ret; + if (ret < header.data_len) + return -EINVAL; + + crc = crc32(0, buf, header.data_len); + if (crc != header.data_crc) { + pr_err("invalid crc, calculated 0x%08x, found 0x%08x\n", + crc, header.data_crc); + return -EINVAL; + } + + off = buf; + len = header.data_len; + + list_for_each_entry(sv, &state->variables, list) { + if (len < sv->raw_size) { + break; + } + memcpy(sv->raw, off, sv->raw_size); + off += sv->raw_size; + len -= sv->raw_size; + } + + free(buf); + + return 0; +} + +static int state_backend_raw_load(struct state_backend *backend, struct state *state) +{ + struct state_backend_raw *backend_raw = container_of(backend, + struct state_backend_raw, backend); + int ret = 0, fd, i; + + fd = open(backend->path, O_RDONLY); + if (fd < 0) + return fd; + + for (i = 0; i < 2; i++) { + off_t offset = backend_raw->offset + i * backend_raw->step; + + ret = backend_raw_load_one(backend_raw, fd, offset); + if (!ret) { + backend_raw->num_copy_read = i; + pr_debug("copy %d successfully loaded\n", i); + break; + } + } + + close(fd); + + return ret; +} + +static int backend_raw_write_one(struct state_backend_raw *backend_raw, + int fd, int num, void *buf, size_t size) +{ + int ret; + off_t offset = backend_raw->offset + num * backend_raw->step; + + pr_debug(&backend_raw->state->dev, "%s: 0x%08lx 0x%08x\n", + __func__, offset, size); + + if (backend_raw->need_erase) { + struct erase_info_user erase = { + .start = offset, + .length = backend_raw->step, + }; + + ret = ioctl(fd, MEMERASE, &erase); + if (ret < 0) + return -errno; + } + + ret = lseek(fd, offset, SEEK_SET); + if (ret < 0) + return -errno; + + ret = write(fd, buf, size); + if (ret < 0) + return -errno; + + return 0; +} + +static int state_backend_raw_save(struct state_backend *backend, struct state *state) +{ + struct state_backend_raw *backend_raw = container_of(backend, + struct state_backend_raw, backend); + int ret = 0, size, fd; + void *buf, *freep, *off, *data, *tmp; + struct backend_raw_header *header; + struct state_variable *sv; + + size = backend_raw->size_data + sizeof(struct backend_raw_header); + + freep = off = buf = xzalloc(size); + + header = buf; + data = buf + sizeof(*header); + + tmp = data; + list_for_each_entry(sv, &state->variables, list) { + memcpy(tmp, sv->raw, sv->raw_size); + tmp += sv->raw_size; + } + + header->magic = state->magic; + header->data_len = backend_raw->size_data; + header->data_crc = crc32(0, data, backend_raw->size_data); + header->header_crc = crc32(0, header, sizeof(*header) - sizeof(uint32_t)); + + fd = open(backend->path, O_WRONLY); + if (fd < 0) + return fd; + + ret = backend_raw_write_one(backend_raw, fd, !backend_raw->num_copy_read, buf, size); + if (ret) + goto out; + + ret = backend_raw_write_one(backend_raw, fd, backend_raw->num_copy_read, buf, size); + if (ret) + goto out; + + pr_debug("wrote state to %s\n", backend->path); +out: + close(fd); + free(buf); + + return ret; +} + +/* + * state_backend_raw_file - create a raw file backend store for a state instance + * + * @state The state instance to work on + * @path The path where the state will be stored to + * @offset The offset in the storage file + * @size The maximum size to use in the storage file + * + * This backend stores raw binary data from a state instance. The binary data is + * protected with a magic value which has to match and a crc32 that must be valid. + * Up to four copies are stored if there is sufficient space available. + * @path can be a path to a device or a regular file. When it's a device @size may + * be 0. The four copies a spread to different eraseblocks if approriate for this + * device. + */ +int state_backend_raw_file(struct state *state, const char *path, off_t offset, + size_t size) +{ + struct state_backend_raw *backend_raw; + struct state_backend *backend; + struct state_variable *sv; + int ret; + struct stat s; + struct mtd_info_user meminfo; + + if (state->backend) + return -EBUSY; + + ret = stat(path, &s); + if (!ret && !S_ISCHR(s.st_mode)) { + if (size == 0) + size = s.st_size; + else if (offset + size > s.st_size) + return -EINVAL; + } + + backend_raw = xzalloc(sizeof(*backend_raw)); + backend = &backend_raw->backend; + + backend->load = state_backend_raw_load; + backend->save = state_backend_raw_save; + backend->path = strdup(path); + backend->name = "raw"; + + list_for_each_entry(sv, &state->variables, list) + backend_raw->size_data += sv->raw_size; + + backend_raw->state = state; + backend_raw->offset = offset; + backend_raw->size_full = backend_raw->size_data + sizeof(struct backend_raw_header); + + state->backend = backend; + + ret = get_meminfo(backend->path, &meminfo); + if (!ret) { + if (!size) + size = meminfo.size; + backend_raw->need_erase = 1; + backend_raw->step = ALIGN(backend_raw->size_full, meminfo.erasesize); + if (verbose) + fprintf(stderr, "%s is a mtd of size %d, adjust stepsize to %ld\n", + path, meminfo.size, backend_raw->step); + } else { + backend_raw->step = backend_raw->size_full; + } + + backend_raw->size = size; + + if (backend_raw->size / backend_raw->step < 2) { + pr_err("not enough space for two copies, have %d, need %d\n", + backend_raw->size, backend_raw->step * 2); + ret = -ENOSPC; + goto err; + } + + return 0; +err: + free(backend_raw); + return ret; +} + +static struct state *state_get(const char *name) +{ + struct device_node *root, *node; + char *path; + struct state *state; + int ret; + const char *backend_type = NULL; + struct of_path op; + struct state_variable *v; + + root = of_read_proc_devicetree(); + if (IS_ERR(root)) { + fprintf(stderr, "Unable to read devicetree from /proc/device-tree: %s\n", + strerror(-PTR_ERR(root))); + return ERR_CAST(root); + } + + of_set_root_node(root); + + node = of_find_node_by_path_or_alias(root, name); + if (!node) { + fprintf(stderr, "no such node: %s\n", name); + return ERR_PTR(-ENOENT); + } + + if (verbose > 1) { + printf("found state node %s:\n", node->full_name); + of_print_nodes(node, 0); + } + + state = state_new_from_node("state", node); + if (IS_ERR(state)) { + fprintf(stderr, "unable to initlialize state: %s\n", + strerror(PTR_ERR(state))); + return ERR_CAST(state); + } + + ret = of_find_path(node, "backend", &op); + 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, op.devpath, op.offset, op.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; +} + +enum opt { + OPT_DUMP_SHELL = 1, +}; + +static struct option long_options[] = { + {"get", required_argument, 0, 'g' }, + {"set", required_argument, 0, 's' }, + {"name", required_argument, 0, 'n' }, + {"dump", no_argument, 0, 'd' }, + {"dump-shell", no_argument, 0, OPT_DUMP_SHELL }, + {"init", no_argument, 0, 'i' }, + {"verbose", no_argument, 0, 'v' }, + {"help", no_argument, 0, 'h' }, +}; + +static void usage(char *name) +{ + printf( +"Usage: %s [OPTIONS]\n" +"\n" +"-g, --get <variable> get the value of a variable\n" +"-s, --set <variable>=<value> set the value of a variable\n" +"-n, --name <name> specify the state to use (default=\"state\")\n" +"-d, --dump dump the state\n" +"--dump-shell dump the state suitable for shell sourcing\n" +"--init initialize the state (do not load from storage)\n" +"-v, --verbose increase verbosity\n" +"--help this help\n", + name); +} + +#define state_for_each_var(state, var) \ + list_for_each_entry(var, &(state)->variables, list) + +struct state_set_get { + char *arg; + int get; + struct list_head list; +}; + +int main(int argc, char *argv[]) +{ + struct state *state; + struct state_variable *v; + int ret, c, option_index; + int do_dump = 0, do_dump_shell = 0, do_initialize = 0; + struct state_set_get *sg; + struct list_head sg_list; + char *statename = "state"; + + INIT_LIST_HEAD(&sg_list); + + while (1) { + c = getopt_long(argc, argv, "hg:s:divn:", long_options, &option_index); + if (c < 0) + break; + switch (c) { + case 'h': + usage(argv[0]); + exit(0); + case 'g': + sg = xzalloc(sizeof(*sg)); + sg->get = 1; + sg->arg = optarg; + list_add_tail(&sg->list, &sg_list); + break; + case 's': + sg = xzalloc(sizeof(*sg)); + sg->get = 0; + sg->arg = optarg; + list_add_tail(&sg->list, &sg_list); + break; + case 'd': + do_dump = 1; + break; + case 'i': + do_initialize = 1; + break; + case OPT_DUMP_SHELL: + do_dump_shell = 1; + break; + case 'v': + verbose++; + break; + case 'n': + statename = optarg; + } + } + + state = state_get(statename); + if (IS_ERR(state)) + exit(1); + + ret = state_load(state); + if (!do_initialize && ret) { + fprintf(stderr, "Cannot load state: %s\n", strerror(-ret)); + exit(1); + } + + if (do_dump) { + state_for_each_var(state, v) { + struct variable_type *vtype; + vtype = state_find_type(v->type); + printf("%s=%s", v->name, vtype->get(v)); + if (verbose) { + printf(", type=%s", vtype->type_name); + if (vtype->info) + vtype->info(v); + } + printf("\n"); + } + } + + if (do_dump_shell) { + state_for_each_var(state, v) { + struct variable_type *vtype; + vtype = state_find_type(v->type); + printf("STATE_%s=\"%s\"\n", v->name, vtype->get(v)); + } + } + + list_for_each_entry(sg, &sg_list, list) { + if (sg->get) { + char *val = state_get_var(state, sg->arg); + if (!val) { + fprintf(stderr, "no such variable: %s\n", sg->arg); + exit (1); + } + + printf("%s\n", val); + } else { + char *var, *val; + + var = sg->arg; + val = index(sg->arg, '='); + if (!val) { + fprintf(stderr, "usage: -s var=val\n"); + exit (1); + } + *val++ = '\0'; + ret = state_set_var(state, var, val); + if (ret) { + fprintf(stderr, "Failed to set variable %s to %s: %s\n", + var, val, strerror(-ret)); + exit(1); + } + } + } + + if (state->dirty) { + ret = state_save(state); + if (ret) { + fprintf(stderr, "Failed to save state: %s\n", strerror(-ret)); + exit(1); + } + } + + return 0; +} |