If of_state_fixup() fails for some reason, boot/bootm will fail, too However, it is completely unclear where the error has happened, and to the user it rather seems like the kernel device tree loaded from some file was broken/corrupt, not that in fact some fixup routine was failing. Adding a meaningful error message can help debugging significantly. Signed-off-by: Harald Welte <laforge@gnumonks.org> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
1720 lines
37 KiB
C
1720 lines
37 KiB
C
/*
|
|
* Copyright (C) 2012-2014 Pengutronix, Jan Luebbe <j.luebbe@pengutronix.de>
|
|
* Copyright (C) 2013-2014 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
|
|
* Copyright (C) 2015 Pengutronix, Marc Kleine-Budde <mkl@pengutronix.de>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <digest.h>
|
|
#include <environment.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <fs.h>
|
|
#include <init.h>
|
|
#include <ioctl.h>
|
|
#include <libbb.h>
|
|
#include <libfile.h>
|
|
#include <malloc.h>
|
|
#include <net.h>
|
|
#include <state.h>
|
|
#include <xfuncs.h>
|
|
|
|
#include <crypto/keystore.h>
|
|
|
|
#include <linux/mtd/mtd-abi.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/list.h>
|
|
#include <linux/err.h>
|
|
|
|
#include <asm/unaligned.h>
|
|
|
|
#define RAW_BACKEND_COPIES 2
|
|
|
|
struct state_backend;
|
|
|
|
struct state {
|
|
struct device_d dev;
|
|
struct device_node *root;
|
|
struct list_head variables;
|
|
const char *name;
|
|
struct list_head list;
|
|
struct state_backend *backend;
|
|
uint32_t magic;
|
|
unsigned int dirty;
|
|
};
|
|
|
|
struct state_backend {
|
|
int (*save)(struct state_backend *backend, struct state *state);
|
|
const char *name;
|
|
const char *of_path;
|
|
const char *path;
|
|
struct digest *digest;
|
|
};
|
|
|
|
enum state_variable_type {
|
|
STATE_TYPE_INVALID = 0,
|
|
STATE_TYPE_ENUM,
|
|
STATE_TYPE_U8,
|
|
STATE_TYPE_U32,
|
|
STATE_TYPE_S32,
|
|
STATE_TYPE_MAC,
|
|
STATE_TYPE_STRING,
|
|
};
|
|
|
|
/* instance of a single variable */
|
|
struct state_variable {
|
|
enum state_variable_type type;
|
|
struct list_head list;
|
|
const char *name;
|
|
unsigned int start;
|
|
unsigned int size;
|
|
void *raw;
|
|
};
|
|
|
|
enum state_convert {
|
|
STATE_CONVERT_FROM_NODE,
|
|
STATE_CONVERT_FROM_NODE_CREATE,
|
|
STATE_CONVERT_TO_NODE,
|
|
STATE_CONVERT_FIXUP,
|
|
};
|
|
|
|
/* A variable type (uint32, enum32) */
|
|
struct variable_type {
|
|
enum state_variable_type type;
|
|
const char *type_name;
|
|
struct list_head list;
|
|
int (*export)(struct state_variable *, struct device_node *,
|
|
enum state_convert);
|
|
int (*import)(struct state_variable *, struct device_node *);
|
|
struct state_variable *(*create)(struct state *state,
|
|
const char *name, struct device_node *);
|
|
};
|
|
|
|
/* list of all registered state instances */
|
|
static LIST_HEAD(state_list);
|
|
|
|
static int state_set_dirty(struct param_d *p, void *priv)
|
|
{
|
|
struct state *state = priv;
|
|
|
|
state->dirty = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* uint32
|
|
*/
|
|
struct state_uint32 {
|
|
struct state_variable var;
|
|
struct param_d *param;
|
|
struct state *state;
|
|
uint32_t value;
|
|
uint32_t value_default;
|
|
};
|
|
|
|
static int state_var_compare(struct list_head *a, struct list_head *b)
|
|
{
|
|
struct state_variable *va = list_entry(a, struct state_variable, list);
|
|
struct state_variable *vb = list_entry(b, struct state_variable, list);
|
|
|
|
return va->start < vb->start ? -1 : 1;
|
|
}
|
|
|
|
static void state_add_var(struct state *state, struct state_variable *var)
|
|
{
|
|
list_add_sort(&var->list, &state->variables, state_var_compare);
|
|
}
|
|
|
|
static inline struct state_uint32 *to_state_uint32(struct state_variable *s)
|
|
{
|
|
return container_of(s, struct state_uint32, var);
|
|
}
|
|
|
|
static int state_uint32_export(struct state_variable *var,
|
|
struct device_node *node, enum state_convert conv)
|
|
{
|
|
struct state_uint32 *su32 = to_state_uint32(var);
|
|
int ret;
|
|
|
|
if (su32->value_default) {
|
|
ret = of_property_write_u32(node, "default",
|
|
su32->value_default);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (conv == STATE_CONVERT_FIXUP)
|
|
return 0;
|
|
|
|
return of_property_write_u32(node, "value", su32->value);
|
|
}
|
|
|
|
static int state_uint32_import(struct state_variable *sv,
|
|
struct device_node *node)
|
|
{
|
|
struct state_uint32 *su32 = to_state_uint32(sv);
|
|
|
|
of_property_read_u32(node, "default", &su32->value_default);
|
|
if (of_property_read_u32(node, "value", &su32->value))
|
|
su32->value = su32->value_default;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int state_uint8_set(struct param_d *p, void *priv)
|
|
{
|
|
struct state_uint32 *su32 = priv;
|
|
struct state *state = su32->state;
|
|
|
|
if (su32->value > 255)
|
|
return -ERANGE;
|
|
|
|
return state_set_dirty(p, state);
|
|
}
|
|
|
|
static struct state_variable *state_uint8_create(struct state *state,
|
|
const char *name, struct device_node *node)
|
|
{
|
|
struct state_uint32 *su32;
|
|
struct param_d *param;
|
|
|
|
su32 = xzalloc(sizeof(*su32));
|
|
|
|
param = dev_add_param_int(&state->dev, name, state_uint8_set,
|
|
NULL, &su32->value, "%u", su32);
|
|
if (IS_ERR(param)) {
|
|
free(su32);
|
|
return ERR_CAST(param);
|
|
}
|
|
|
|
su32->param = param;
|
|
su32->var.size = sizeof(uint8_t);
|
|
#ifdef __LITTLE_ENDIAN
|
|
su32->var.raw = &su32->value;
|
|
#else
|
|
su32->var.raw = &su32->value + 3;
|
|
#endif
|
|
su32->state = state;
|
|
|
|
return &su32->var;
|
|
}
|
|
|
|
static struct state_variable *state_int32_create(struct state *state,
|
|
const char *name, struct device_node *node, const char *format)
|
|
{
|
|
struct state_uint32 *su32;
|
|
struct param_d *param;
|
|
|
|
su32 = xzalloc(sizeof(*su32));
|
|
|
|
param = dev_add_param_int(&state->dev, name, state_set_dirty,
|
|
NULL, &su32->value, format, state);
|
|
if (IS_ERR(param)) {
|
|
free(su32);
|
|
return ERR_CAST(param);
|
|
}
|
|
|
|
su32->param = param;
|
|
su32->var.size = sizeof(uint32_t);
|
|
su32->var.raw = &su32->value;
|
|
|
|
return &su32->var;
|
|
}
|
|
|
|
static struct state_variable *state_uint32_create(struct state *state,
|
|
const char *name, struct device_node *node)
|
|
{
|
|
return state_int32_create(state, name, node, "%u");
|
|
}
|
|
|
|
static struct state_variable *state_sint32_create(struct state *state,
|
|
const char *name, struct device_node *node)
|
|
{
|
|
return state_int32_create(state, name, node, "%d");
|
|
}
|
|
|
|
/*
|
|
* enum32
|
|
*/
|
|
struct state_enum32 {
|
|
struct state_variable var;
|
|
struct param_d *param;
|
|
uint32_t value;
|
|
uint32_t value_default;
|
|
const char **names;
|
|
int num_names;
|
|
};
|
|
|
|
static inline struct state_enum32 *to_state_enum32(struct state_variable *s)
|
|
{
|
|
return container_of(s, struct state_enum32, var);
|
|
}
|
|
|
|
static int state_enum32_export(struct state_variable *var,
|
|
struct device_node *node, enum state_convert conv)
|
|
{
|
|
struct state_enum32 *enum32 = to_state_enum32(var);
|
|
int ret, i, len;
|
|
char *prop, *str;
|
|
|
|
if (enum32->value_default) {
|
|
ret = of_property_write_u32(node, "default",
|
|
enum32->value_default);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
len = 0;
|
|
|
|
for (i = 0; i < enum32->num_names; i++)
|
|
len += strlen(enum32->names[i]) + 1;
|
|
|
|
prop = xzalloc(len);
|
|
str = prop;
|
|
|
|
for (i = 0; i < enum32->num_names; i++)
|
|
str += sprintf(str, "%s", enum32->names[i]) + 1;
|
|
|
|
ret = of_set_property(node, "names", prop, len, 1);
|
|
|
|
free(prop);
|
|
|
|
if (conv == STATE_CONVERT_FIXUP)
|
|
return 0;
|
|
|
|
ret = of_property_write_u32(node, "value", enum32->value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int state_enum32_import(struct state_variable *sv,
|
|
struct device_node *node)
|
|
{
|
|
struct state_enum32 *enum32 = to_state_enum32(sv);
|
|
int len;
|
|
const __be32 *value, *value_default;
|
|
|
|
value = of_get_property(node, "value", &len);
|
|
if (value && len != sizeof(uint32_t))
|
|
return -EINVAL;
|
|
|
|
value_default = of_get_property(node, "default", &len);
|
|
if (value_default && len != sizeof(uint32_t))
|
|
return -EINVAL;
|
|
|
|
if (value_default)
|
|
enum32->value_default = be32_to_cpu(*value_default);
|
|
if (value)
|
|
enum32->value = be32_to_cpu(*value);
|
|
else
|
|
enum32->value = enum32->value_default;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct state_variable *state_enum32_create(struct state *state,
|
|
const char *name, struct device_node *node)
|
|
{
|
|
struct state_enum32 *enum32;
|
|
int ret, i, num_names;
|
|
|
|
enum32 = xzalloc(sizeof(*enum32));
|
|
|
|
num_names = of_property_count_strings(node, "names");
|
|
if (num_names < 0) {
|
|
dev_err(&state->dev, "enum32 node without \"names\" property\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
enum32->names = xzalloc(sizeof(char *) * num_names);
|
|
enum32->num_names = num_names;
|
|
enum32->var.size = sizeof(uint32_t);
|
|
enum32->var.raw = &enum32->value;
|
|
|
|
for (i = 0; i < num_names; i++) {
|
|
const char *name;
|
|
|
|
ret = of_property_read_string_index(node, "names", i, &name);
|
|
if (ret)
|
|
goto out;
|
|
enum32->names[i] = xstrdup(name);
|
|
}
|
|
|
|
enum32->param = dev_add_param_enum(&state->dev, name, state_set_dirty,
|
|
NULL, &enum32->value, enum32->names, num_names, state);
|
|
if (IS_ERR(enum32->param)) {
|
|
ret = PTR_ERR(enum32->param);
|
|
goto out;
|
|
}
|
|
|
|
return &enum32->var;
|
|
out:
|
|
for (i--; i >= 0; i--)
|
|
free((char *)enum32->names[i]);
|
|
free(enum32->names);
|
|
free(enum32);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/*
|
|
* MAC address
|
|
*/
|
|
struct state_mac {
|
|
struct state_variable var;
|
|
struct param_d *param;
|
|
uint8_t value[6];
|
|
uint8_t value_default[6];
|
|
};
|
|
|
|
static inline struct state_mac *to_state_mac(struct state_variable *s)
|
|
{
|
|
return container_of(s, struct state_mac, var);
|
|
}
|
|
|
|
static int state_mac_export(struct state_variable *var,
|
|
struct device_node *node, enum state_convert conv)
|
|
{
|
|
struct state_mac *mac = to_state_mac(var);
|
|
int ret;
|
|
|
|
if (!is_zero_ether_addr(mac->value_default)) {
|
|
ret = of_property_write_u8_array(node, "default", mac->value_default,
|
|
ARRAY_SIZE(mac->value_default));
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (conv == STATE_CONVERT_FIXUP)
|
|
return 0;
|
|
|
|
return of_property_write_u8_array(node, "value", mac->value,
|
|
ARRAY_SIZE(mac->value));
|
|
}
|
|
|
|
static int state_mac_import(struct state_variable *sv,
|
|
struct device_node *node)
|
|
{
|
|
struct state_mac *mac = to_state_mac(sv);
|
|
|
|
of_property_read_u8_array(node, "default", mac->value_default,
|
|
ARRAY_SIZE(mac->value_default));
|
|
if (of_property_read_u8_array(node, "value", mac->value,
|
|
ARRAY_SIZE(mac->value)))
|
|
memcpy(mac->value, mac->value_default, ARRAY_SIZE(mac->value));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct state_variable *state_mac_create(struct state *state,
|
|
const char *name, struct device_node *node)
|
|
{
|
|
struct state_mac *mac;
|
|
int ret;
|
|
|
|
mac = xzalloc(sizeof(*mac));
|
|
|
|
mac->var.size = ARRAY_SIZE(mac->value);
|
|
mac->var.raw = mac->value;
|
|
|
|
mac->param = dev_add_param_mac(&state->dev, name, state_set_dirty,
|
|
NULL, mac->value, state);
|
|
if (IS_ERR(mac->param)) {
|
|
ret = PTR_ERR(mac->param);
|
|
goto out;
|
|
}
|
|
|
|
return &mac->var;
|
|
out:
|
|
free(mac);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/*
|
|
* string
|
|
*/
|
|
struct state_string {
|
|
struct state_variable var;
|
|
struct param_d *param;
|
|
struct state *state;
|
|
char *value;
|
|
const char *value_default;
|
|
char raw[];
|
|
};
|
|
|
|
static inline struct state_string *to_state_string(struct state_variable *s)
|
|
{
|
|
return container_of(s, struct state_string, var);
|
|
}
|
|
|
|
static int state_string_export(struct state_variable *var,
|
|
struct device_node *node, enum state_convert conv)
|
|
{
|
|
struct state_string *string = to_state_string(var);
|
|
int ret = 0;
|
|
|
|
if (string->value_default) {
|
|
ret = of_set_property(node, "default", string->value_default,
|
|
strlen(string->value_default) + 1, 1);
|
|
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (conv == STATE_CONVERT_FIXUP)
|
|
return 0;
|
|
|
|
if (string->value)
|
|
ret = of_set_property(node, "value", string->value,
|
|
strlen(string->value) + 1, 1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int state_string_copy_to_raw(struct state_string *string,
|
|
const char *src)
|
|
{
|
|
size_t len;
|
|
|
|
len = strlen(src);
|
|
if (len > string->var.size)
|
|
return -EILSEQ;
|
|
|
|
/* copy string and clear remaining contents of buffer */
|
|
memcpy(string->raw, src, len);
|
|
memset(string->raw + len, 0x0, string->var.size - len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int state_string_import(struct state_variable *sv,
|
|
struct device_node *node)
|
|
{
|
|
struct state_string *string = to_state_string(sv);
|
|
const char *value = NULL;
|
|
size_t len;
|
|
int ret;
|
|
|
|
of_property_read_string(node, "default", &string->value_default);
|
|
if (string->value_default) {
|
|
len = strlen(string->value_default);
|
|
if (len > string->var.size)
|
|
return -EILSEQ;
|
|
}
|
|
|
|
ret = of_property_read_string(node, "value", &value);
|
|
if (ret)
|
|
value = string->value_default;
|
|
|
|
if (value)
|
|
return state_string_copy_to_raw(string, value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int state_string_set(struct param_d *p, void *priv)
|
|
{
|
|
struct state_string *string = priv;
|
|
struct state *state = string->state;
|
|
int ret;
|
|
|
|
ret = state_string_copy_to_raw(string, string->value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return state_set_dirty(p, state);
|
|
}
|
|
|
|
static int state_string_get(struct param_d *p, void *priv)
|
|
{
|
|
struct state_string *string = priv;
|
|
|
|
free(string->value);
|
|
if (string->raw[0])
|
|
string->value = xstrndup(string->raw, string->var.size);
|
|
else
|
|
string->value = xstrdup("");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct state_variable *state_string_create(struct state *state,
|
|
const char *name, struct device_node *node)
|
|
{
|
|
struct state_string *string;
|
|
u32 start_size[2];
|
|
int ret;
|
|
|
|
ret = of_property_read_u32_array(node, "reg", start_size,
|
|
ARRAY_SIZE(start_size));
|
|
if (ret) {
|
|
dev_err(&state->dev,
|
|
"%s: reg property not found\n", name);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/* limit to arbitrary len of 4k */
|
|
if (start_size[1] > 4096)
|
|
return ERR_PTR(-EILSEQ);
|
|
|
|
string = xzalloc(sizeof(*string) + start_size[1]);
|
|
string->var.size = start_size[1];
|
|
string->var.raw = &string->raw;
|
|
string->state = state;
|
|
|
|
string->param = dev_add_param_string(&state->dev, name, state_string_set,
|
|
state_string_get, &string->value, string);
|
|
if (IS_ERR(string->param)) {
|
|
ret = PTR_ERR(string->param);
|
|
goto out;
|
|
}
|
|
|
|
return &string->var;
|
|
out:
|
|
free(string);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
static struct variable_type types[] = {
|
|
{
|
|
.type = STATE_TYPE_U8,
|
|
.type_name = "uint8",
|
|
.export = state_uint32_export,
|
|
.import = state_uint32_import,
|
|
.create = state_uint8_create,
|
|
}, {
|
|
.type = STATE_TYPE_U32,
|
|
.type_name = "uint32",
|
|
.export = state_uint32_export,
|
|
.import = state_uint32_import,
|
|
.create = state_uint32_create,
|
|
}, {
|
|
.type = STATE_TYPE_ENUM,
|
|
.type_name = "enum32",
|
|
.export = state_enum32_export,
|
|
.import = state_enum32_import,
|
|
.create = state_enum32_create,
|
|
}, {
|
|
.type = STATE_TYPE_MAC,
|
|
.type_name = "mac",
|
|
.export = state_mac_export,
|
|
.import = state_mac_import,
|
|
.create = state_mac_create,
|
|
}, {
|
|
.type = STATE_TYPE_STRING,
|
|
.type_name = "string",
|
|
.export = state_string_export,
|
|
.import = state_string_import,
|
|
.create = state_string_create,
|
|
}, {
|
|
.type = STATE_TYPE_S32,
|
|
.type_name = "int32",
|
|
.export = state_uint32_export,
|
|
.import = state_uint32_import,
|
|
.create = state_sint32_create,
|
|
},
|
|
};
|
|
|
|
static struct variable_type *state_find_type_by_name(const char *name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(types); i++) {
|
|
if (!strcmp(name, types[i].type_name)) {
|
|
return &types[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Generic state functions
|
|
*/
|
|
|
|
static struct state *state_new(const char *name)
|
|
{
|
|
struct state *state;
|
|
int ret;
|
|
|
|
state = xzalloc(sizeof(*state));
|
|
safe_strncpy(state->dev.name, name, MAX_DRIVER_NAME);
|
|
state->name = state->dev.name;
|
|
state->dev.id = DEVICE_ID_SINGLE;
|
|
INIT_LIST_HEAD(&state->variables);
|
|
|
|
ret = register_device(&state->dev);
|
|
if (ret) {
|
|
free(state);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
state->dirty = 1;
|
|
dev_add_param_bool(&state->dev, "dirty", NULL, NULL, &state->dirty,
|
|
NULL);
|
|
|
|
list_add_tail(&state->list, &state_list);
|
|
|
|
return state;
|
|
}
|
|
|
|
static struct state_variable *state_find_var(struct state *state,
|
|
const char *name)
|
|
{
|
|
struct state_variable *sv;
|
|
|
|
list_for_each_entry(sv, &state->variables, list) {
|
|
if (!strcmp(sv->name, name))
|
|
return sv;
|
|
}
|
|
|
|
return ERR_PTR(-ENOENT);
|
|
}
|
|
|
|
static int state_convert_node_variable(struct state *state,
|
|
struct device_node *node, struct device_node *parent,
|
|
const char *parent_name, enum state_convert conv)
|
|
{
|
|
const struct variable_type *vtype;
|
|
struct device_node *child;
|
|
struct device_node *new_node = NULL;
|
|
struct state_variable *sv;
|
|
const char *type_name;
|
|
char *short_name, *name, *indexs;
|
|
unsigned int start_size[2];
|
|
int ret;
|
|
|
|
/* strip trailing @<ADDRESS> */
|
|
short_name = xstrdup(node->name);
|
|
indexs = strchr(short_name, '@');
|
|
if (indexs)
|
|
*indexs = 0;
|
|
|
|
/* construct full name */
|
|
name = 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) {
|
|
dev_err(&state->dev, "unkown type: %s in %s\n", type_name,
|
|
node->full_name);
|
|
ret = -ENOENT;
|
|
goto out_free;
|
|
}
|
|
|
|
if (conv == STATE_CONVERT_FROM_NODE_CREATE) {
|
|
sv = vtype->create(state, name, node);
|
|
if (IS_ERR(sv)) {
|
|
ret = PTR_ERR(sv);
|
|
dev_err(&state->dev, "failed to create %s: %s\n",
|
|
name, strerror(-ret));
|
|
goto out_free;
|
|
}
|
|
|
|
ret = of_property_read_u32_array(node, "reg", start_size,
|
|
ARRAY_SIZE(start_size));
|
|
if (ret) {
|
|
dev_err(&state->dev,
|
|
"%s: reg property not found\n", name);
|
|
goto out_free;
|
|
}
|
|
|
|
if (start_size[1] != sv->size) {
|
|
dev_err(&state->dev,
|
|
"%s: size mismatch: type=%s(size=%u) size=%u\n",
|
|
name, type_name, sv->size, start_size[1]);
|
|
ret = -EOVERFLOW;
|
|
goto out_free;
|
|
}
|
|
|
|
sv->name = name;
|
|
sv->start = start_size[0];
|
|
sv->type = vtype->type;
|
|
state_add_var(state, sv);
|
|
} else {
|
|
sv = state_find_var(state, name);
|
|
if (IS_ERR(sv)) {
|
|
/* we ignore this error */
|
|
dev_dbg(&state->dev,
|
|
"no such variable: %s: %s\n",
|
|
name, strerror(-ret));
|
|
ret = 0;
|
|
goto out_free;
|
|
}
|
|
free(name);
|
|
|
|
if ((conv == STATE_CONVERT_TO_NODE) ||
|
|
(conv == STATE_CONVERT_FIXUP)) {
|
|
ret = of_set_property(new_node, "type",
|
|
vtype->type_name,
|
|
strlen(vtype->type_name) + 1, 1);
|
|
if (ret)
|
|
goto out;
|
|
|
|
start_size[0] = sv->start;
|
|
start_size[1] = sv->size;
|
|
ret = of_property_write_u32_array(new_node, "reg",
|
|
start_size,
|
|
ARRAY_SIZE(start_size));
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if ((conv == STATE_CONVERT_TO_NODE) ||
|
|
(conv == STATE_CONVERT_FIXUP))
|
|
ret = vtype->export(sv, new_node, conv);
|
|
else
|
|
ret = vtype->import(sv, node);
|
|
|
|
if (ret)
|
|
goto out;
|
|
|
|
return 0;
|
|
out_free:
|
|
free(name);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static struct device_node *state_to_node(struct state *state, struct device_node *parent,
|
|
enum state_convert conv)
|
|
{
|
|
struct device_node *child;
|
|
struct device_node *root;
|
|
int ret;
|
|
|
|
root = of_new_node(parent, state->root->name);
|
|
ret = of_property_write_u32(root, "magic", state->magic);
|
|
if (ret)
|
|
goto out;
|
|
|
|
for_each_child_of_node(state->root, child) {
|
|
ret = state_convert_node_variable(state, child, root, "",
|
|
conv);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
return root;
|
|
out:
|
|
of_delete_node(root);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
static int state_from_node(struct state *state, struct device_node *node,
|
|
bool create)
|
|
{
|
|
struct device_node *child;
|
|
enum state_convert conv;
|
|
int ret;
|
|
uint32_t magic;
|
|
|
|
ret = of_property_read_u32(node, "magic", &magic);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (create) {
|
|
conv = STATE_CONVERT_FROM_NODE_CREATE;
|
|
state->root = node;
|
|
state->magic = magic;
|
|
} else {
|
|
conv = STATE_CONVERT_FROM_NODE;
|
|
if (state->magic && state->magic != magic) {
|
|
dev_err(&state->dev,
|
|
"invalid magic 0x%08x, should be 0x%08x\n",
|
|
magic, state->magic);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
for_each_child_of_node(node, child) {
|
|
ret = state_convert_node_variable(state, child, NULL, "", conv);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/* check for overlapping variables */
|
|
if (create) {
|
|
const struct state_variable *sv;
|
|
|
|
/* start with second entry */
|
|
sv = list_first_entry(&state->variables,
|
|
struct state_variable, list);
|
|
|
|
list_for_each_entry_continue(sv, &state->variables, list) {
|
|
const struct state_variable *last_sv;
|
|
|
|
last_sv = list_last_entry(&sv->list,
|
|
struct state_variable, list);
|
|
if ((last_sv->start + last_sv->size - 1) < sv->start)
|
|
continue;
|
|
|
|
dev_err(&state->dev,
|
|
"ERROR: Conflicting variable position between: "
|
|
"%s (0x%02x..0x%02x) and %s (0x%02x..0x%02x)\n",
|
|
last_sv->name, last_sv->start,
|
|
last_sv->start + last_sv->size - 1,
|
|
sv->name, sv->start, sv->start + sv->size - 1);
|
|
|
|
ret |= -EINVAL;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int of_state_fixup(struct device_node *root, void *ctx)
|
|
{
|
|
struct state *state = ctx;
|
|
const char *compatible = "barebox,state";
|
|
struct device_node *new_node, *node, *parent, *backend_node;
|
|
struct property *p;
|
|
int ret;
|
|
phandle phandle;
|
|
|
|
node = of_find_node_by_path_from(root, state->root->full_name);
|
|
if (node) {
|
|
/* replace existing node - it will be deleted later */
|
|
parent = node->parent;
|
|
} else {
|
|
char *of_path, *c;
|
|
|
|
/* look for parent, remove last '/' from path */
|
|
of_path = xstrdup(state->root->full_name);
|
|
c = strrchr(of_path, '/');
|
|
if (!c)
|
|
return -ENODEV;
|
|
*c = '0';
|
|
parent = of_find_node_by_path(of_path);
|
|
if (!parent)
|
|
parent = root;
|
|
|
|
free(of_path);
|
|
}
|
|
|
|
/* serialize variable definitions */
|
|
new_node = state_to_node(state, parent, STATE_CONVERT_FIXUP);
|
|
if (IS_ERR(new_node))
|
|
return PTR_ERR(new_node);
|
|
|
|
/* compatible */
|
|
p = of_new_property(new_node, "compatible", compatible,
|
|
strlen(compatible) + 1);
|
|
if (!p) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/* backend-type */
|
|
if (!state->backend) {
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
p = of_new_property(new_node, "backend-type", state->backend->name,
|
|
strlen(state->backend->name) + 1);
|
|
if (!p) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/* backend phandle */
|
|
backend_node = of_find_node_by_path_from(root, state->backend->of_path);
|
|
if (!backend_node) {
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
phandle = of_node_create_phandle(backend_node);
|
|
ret = of_property_write_u32(new_node, "backend", phandle);
|
|
if (ret)
|
|
goto out;
|
|
|
|
if (state->backend->digest) {
|
|
p = of_new_property(new_node, "algo",
|
|
digest_name(state->backend->digest),
|
|
strlen(digest_name(state->backend->digest)) + 1);
|
|
if (!p) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* address-cells + size-cells */
|
|
ret = of_property_write_u32(new_node, "#address-cells", 1);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = of_property_write_u32(new_node, "#size-cells", 1);
|
|
if (ret)
|
|
goto out;
|
|
|
|
/* delete existing node */
|
|
if (node)
|
|
of_delete_node(node);
|
|
|
|
return 0;
|
|
|
|
out:
|
|
dev_err(&state->dev, "error fixing up device tree with boot state\n");
|
|
of_delete_node(new_node);
|
|
return ret;
|
|
}
|
|
|
|
void state_release(struct state *state)
|
|
{
|
|
of_unregister_fixup(of_state_fixup, state);
|
|
list_del(&state->list);
|
|
unregister_device(&state->dev);
|
|
free(state);
|
|
}
|
|
|
|
/*
|
|
* state_new_from_node - create a new state instance from a device_node
|
|
*
|
|
* @name The name of the new state instance
|
|
* @node The device_node describing the new state instance
|
|
*/
|
|
struct state *state_new_from_node(const char *name, struct device_node *node)
|
|
{
|
|
struct state *state;
|
|
int ret;
|
|
|
|
state = state_new(name);
|
|
if (IS_ERR(state))
|
|
return state;
|
|
|
|
ret = state_from_node(state, node, 1);
|
|
if (ret) {
|
|
state_release(state);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
ret = of_register_fixup(of_state_fixup, state);
|
|
if (ret) {
|
|
state_release(state);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
/*
|
|
* state_by_name - find a state instance by name
|
|
*
|
|
* @name The name of the state instance
|
|
*/
|
|
struct state *state_by_name(const char *name)
|
|
{
|
|
struct state *state;
|
|
|
|
list_for_each_entry(state, &state_list, list) {
|
|
if (!strcmp(name, state->name))
|
|
return state;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* state_by_node - find a state instance by of node
|
|
*
|
|
* @node The of node of the state intance
|
|
*/
|
|
struct state *state_by_node(const struct device_node *node)
|
|
{
|
|
struct state *state;
|
|
|
|
list_for_each_entry(state, &state_list, list) {
|
|
if (state->root == node)
|
|
return state;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int state_get_name(const struct state *state, char const **name)
|
|
{
|
|
*name = xstrdup(state->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* state_save - save a state to the backing store
|
|
*
|
|
* @state The state instance to save
|
|
*/
|
|
int state_save(struct state *state)
|
|
{
|
|
int ret;
|
|
|
|
if (!state->dirty)
|
|
return 0;
|
|
|
|
if (!state->backend)
|
|
return -ENOSYS;
|
|
|
|
ret = state->backend->save(state->backend, state);
|
|
if (ret)
|
|
return ret;
|
|
|
|
state->dirty = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void state_info(void)
|
|
{
|
|
struct state *state;
|
|
|
|
printf("registered state instances:\n");
|
|
|
|
list_for_each_entry(state, &state_list, list) {
|
|
printf("%-20s ", state->name);
|
|
if (state->backend)
|
|
printf("(backend: %s, path: %s)\n",
|
|
state->backend->name, state->backend->path);
|
|
else
|
|
printf("(no backend)\n");
|
|
}
|
|
}
|
|
|
|
static int mtd_get_meminfo(const char *path, struct mtd_info_user *meminfo)
|
|
{
|
|
int fd, ret;
|
|
|
|
fd = open(path, O_RDONLY);
|
|
if (fd < 0)
|
|
return fd;
|
|
|
|
ret = ioctl(fd, MEMGETINFO, meminfo);
|
|
|
|
close(fd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* DTB backend implementation
|
|
*/
|
|
struct state_backend_dtb {
|
|
struct state_backend backend;
|
|
bool need_erase;
|
|
};
|
|
|
|
static int state_backend_dtb_load(struct state_backend *backend,
|
|
struct state *state)
|
|
{
|
|
struct device_node *root;
|
|
void *fdt;
|
|
int ret;
|
|
size_t len;
|
|
|
|
fdt = read_file(backend->path, &len);
|
|
if (!fdt) {
|
|
dev_err(&state->dev, "cannot read %s\n", backend->path);
|
|
return -EINVAL;
|
|
}
|
|
|
|
root = of_unflatten_dtb(fdt);
|
|
|
|
free(fdt);
|
|
|
|
if (IS_ERR(root))
|
|
return PTR_ERR(root);
|
|
|
|
ret = state_from_node(state, root, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int state_backend_dtb_save(struct state_backend *backend,
|
|
struct state *state)
|
|
{
|
|
struct state_backend_dtb *backend_dtb = container_of(backend,
|
|
struct state_backend_dtb, backend);
|
|
int ret, fd;
|
|
struct device_node *root;
|
|
struct fdt_header *fdt;
|
|
|
|
root = state_to_node(state, NULL, STATE_CONVERT_TO_NODE);
|
|
if (IS_ERR(root))
|
|
return PTR_ERR(root);
|
|
|
|
fdt = of_flatten_dtb(root);
|
|
if (!fdt)
|
|
return -EINVAL;
|
|
|
|
fd = open(backend->path, O_WRONLY);
|
|
if (fd < 0) {
|
|
ret = fd;
|
|
goto out;
|
|
}
|
|
|
|
if (backend_dtb->need_erase) {
|
|
ret = erase(fd, fdt32_to_cpu(fdt->totalsize), 0);
|
|
if (ret) {
|
|
close(fd);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
ret = write_full(fd, fdt, fdt32_to_cpu(fdt->totalsize));
|
|
|
|
close(fd);
|
|
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
ret = 0;
|
|
out:
|
|
free(fdt);
|
|
of_delete_node(root);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* state_backend_dtb_file - create a dtb backend store for a state instance
|
|
*
|
|
* @state The state instance to work on
|
|
* @path The path where the state will be stored to
|
|
*/
|
|
int state_backend_dtb_file(struct state *state, const char *of_path, const char *path)
|
|
{
|
|
struct state_backend_dtb *backend_dtb;
|
|
struct state_backend *backend;
|
|
struct mtd_info_user meminfo;
|
|
int ret;
|
|
|
|
if (state->backend)
|
|
return -EBUSY;
|
|
|
|
backend_dtb = xzalloc(sizeof(*backend_dtb));
|
|
backend = &backend_dtb->backend;
|
|
|
|
backend->save = state_backend_dtb_save;
|
|
backend->of_path = xstrdup(of_path);
|
|
backend->path = xstrdup(path);
|
|
backend->name = "dtb";
|
|
|
|
state->backend = backend;
|
|
|
|
ret = mtd_get_meminfo(backend->path, &meminfo);
|
|
if (!ret && !(meminfo.flags & MTD_NO_ERASE))
|
|
backend_dtb->need_erase = true;
|
|
|
|
ret = state_backend_dtb_load(backend, state);
|
|
if (ret) {
|
|
dev_warn(&state->dev, "load failed - using defaults\n");
|
|
} else {
|
|
dev_info(&state->dev, "load successful\n");
|
|
state->dirty = 0;
|
|
}
|
|
|
|
/* ignore return value of load() */
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Raw backend implementation
|
|
*/
|
|
struct state_backend_raw {
|
|
struct state_backend backend;
|
|
unsigned long size_data; /* The raw data size (without header) */
|
|
unsigned long size_full; /* The size header + raw data + hmac */
|
|
unsigned long stride; /* The stride size in bytes of the copies */
|
|
off_t offset; /* offset in the storage file */
|
|
size_t size; /* size of the storage area */
|
|
int num_copy_read; /* The first successfully read copy */
|
|
bool need_erase;
|
|
};
|
|
|
|
struct backend_raw_header {
|
|
uint32_t magic;
|
|
uint16_t reserved;
|
|
uint16_t data_len;
|
|
uint32_t data_crc;
|
|
uint32_t header_crc;
|
|
};
|
|
|
|
static int backend_raw_load_one(struct state_backend_raw *backend_raw,
|
|
struct state *state, int fd, off_t offset)
|
|
{
|
|
uint32_t crc;
|
|
struct state_variable *sv;
|
|
struct backend_raw_header header = {};
|
|
unsigned long max_len;
|
|
int d_len = 0;
|
|
int ret;
|
|
void *buf, *data, *hmac;
|
|
|
|
max_len = backend_raw->stride;
|
|
|
|
ret = lseek(fd, offset, SEEK_SET);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = read_full(fd, &header, sizeof(header));
|
|
max_len -= sizeof(header);
|
|
if (ret < 0) {
|
|
dev_err(&state->dev,
|
|
"cannot read header from backend device\n");
|
|
return ret;
|
|
}
|
|
|
|
crc = crc32(0, &header, sizeof(header) - sizeof(uint32_t));
|
|
if (crc != header.header_crc) {
|
|
dev_err(&state->dev,
|
|
"invalid header crc, calculated 0x%08x, found 0x%08x\n",
|
|
crc, header.header_crc);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (state->magic && state->magic != header.magic) {
|
|
dev_err(&state->dev,
|
|
"invalid magic 0x%08x, should be 0x%08x\n",
|
|
header.magic, state->magic);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (backend_raw->backend.digest) {
|
|
d_len = digest_length(backend_raw->backend.digest);
|
|
max_len -= d_len;
|
|
}
|
|
|
|
if (header.data_len > max_len) {
|
|
dev_err(&state->dev,
|
|
"invalid data_len %u in header, max is %lu\n",
|
|
header.data_len, max_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
buf = xzalloc(sizeof(header) + header.data_len + d_len);
|
|
data = buf + sizeof(header);
|
|
hmac = data + header.data_len;
|
|
|
|
ret = lseek(fd, offset, SEEK_SET);
|
|
if (ret < 0)
|
|
goto out_free;
|
|
|
|
ret = read_full(fd, buf, sizeof(header) + header.data_len + d_len);
|
|
if (ret < 0)
|
|
goto out_free;
|
|
|
|
crc = crc32(0, data, header.data_len);
|
|
if (crc != header.data_crc) {
|
|
dev_err(&state->dev,
|
|
"invalid crc, calculated 0x%08x, found 0x%08x\n",
|
|
crc, header.data_crc);
|
|
ret = -EINVAL;
|
|
goto out_free;
|
|
}
|
|
|
|
if (backend_raw->backend.digest) {
|
|
struct digest *d = backend_raw->backend.digest;
|
|
|
|
ret = digest_init(d);
|
|
if (ret)
|
|
goto out_free;
|
|
|
|
/* hmac over header and data */
|
|
ret = digest_update(d, buf, sizeof(header) + header.data_len);
|
|
if (ret)
|
|
goto out_free;
|
|
|
|
ret = digest_verify(d, hmac);
|
|
if (ret < 0)
|
|
goto out_free;
|
|
}
|
|
|
|
list_for_each_entry(sv, &state->variables, list) {
|
|
if (sv->start + sv->size > header.data_len)
|
|
break;
|
|
memcpy(sv->raw, data + sv->start, sv->size);
|
|
}
|
|
|
|
free(buf);
|
|
return 0;
|
|
|
|
out_free:
|
|
free(buf);
|
|
return ret;
|
|
}
|
|
|
|
static int state_backend_raw_load(struct state_backend *backend,
|
|
struct state *state)
|
|
{
|
|
struct state_backend_raw *backend_raw = container_of(backend,
|
|
struct state_backend_raw, backend);
|
|
int ret = 0, fd, i;
|
|
|
|
fd = open(backend->path, O_RDONLY);
|
|
if (fd < 0) {
|
|
dev_err(&state->dev, "cannot open %s\n", backend->path);
|
|
return fd;
|
|
}
|
|
|
|
for (i = 0; i < RAW_BACKEND_COPIES; i++) {
|
|
off_t offset = backend_raw->offset + i * backend_raw->stride;
|
|
|
|
ret = backend_raw_load_one(backend_raw, state, fd, offset);
|
|
if (!ret) {
|
|
backend_raw->num_copy_read = i;
|
|
dev_dbg(&state->dev,
|
|
"copy %d successfully loaded\n", i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
close(fd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int backend_raw_save_one(struct state_backend_raw *backend_raw,
|
|
struct state *state, int fd, int num, void *buf, size_t size)
|
|
{
|
|
int ret;
|
|
off_t offset = backend_raw->offset + num * backend_raw->stride;
|
|
|
|
dev_dbg(&state->dev, "%s: 0x%08lx 0x%08zx\n",
|
|
__func__, offset, size);
|
|
|
|
ret = lseek(fd, offset, SEEK_SET);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
protect(fd, backend_raw->stride, offset, false);
|
|
|
|
if (backend_raw->need_erase) {
|
|
ret = erase(fd, backend_raw->stride, offset);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
ret = write_full(fd, buf, size);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
protect(fd, backend_raw->stride, offset, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int state_backend_raw_save(struct state_backend *backend,
|
|
struct state *state)
|
|
{
|
|
struct state_backend_raw *backend_raw = container_of(backend,
|
|
struct state_backend_raw, backend);
|
|
int ret = 0, fd, i;
|
|
void *buf, *data, *hmac;
|
|
struct backend_raw_header *header;
|
|
struct state_variable *sv;
|
|
|
|
buf = xzalloc(backend_raw->size_full);
|
|
|
|
header = buf;
|
|
data = buf + sizeof(*header);
|
|
hmac = data + backend_raw->size_data;
|
|
|
|
list_for_each_entry(sv, &state->variables, list)
|
|
memcpy(data + sv->start, sv->raw, sv->size);
|
|
|
|
header->magic = state->magic;
|
|
header->data_len = backend_raw->size_data;
|
|
header->data_crc = crc32(0, data, backend_raw->size_data);
|
|
header->header_crc = crc32(0, header,
|
|
sizeof(*header) - sizeof(uint32_t));
|
|
|
|
if (backend_raw->backend.digest) {
|
|
struct digest *d = backend_raw->backend.digest;
|
|
|
|
ret = digest_init(d);
|
|
if (ret)
|
|
goto out_free;
|
|
|
|
/* hmac over header and data */
|
|
ret = digest_update(d, buf, sizeof(*header) + backend_raw->size_data);
|
|
if (ret)
|
|
goto out_free;
|
|
|
|
ret = digest_final(d, hmac);
|
|
if (ret < 0)
|
|
goto out_free;
|
|
}
|
|
|
|
fd = open(backend->path, O_WRONLY);
|
|
if (fd < 0)
|
|
goto out_free;
|
|
|
|
/* save other slots first */
|
|
for (i = 0; i < RAW_BACKEND_COPIES; i++) {
|
|
if (i == backend_raw->num_copy_read)
|
|
continue;
|
|
|
|
ret = backend_raw_save_one(backend_raw, state, fd,
|
|
i, buf, backend_raw->size_full);
|
|
if (ret)
|
|
goto out_close;
|
|
|
|
}
|
|
|
|
ret = backend_raw_save_one(backend_raw, state, fd,
|
|
backend_raw->num_copy_read, buf, backend_raw->size_full);
|
|
if (ret)
|
|
goto out_close;
|
|
|
|
dev_dbg(&state->dev, "wrote state to %s\n", backend->path);
|
|
out_close:
|
|
close(fd);
|
|
out_free:
|
|
free(buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef __BAREBOX__
|
|
#define STAT_GIVES_SIZE(s) (S_ISREG(s.st_mode) || S_ISCHR(s.st_mode))
|
|
#define BLKGET_GIVES_SIZE(s) 0
|
|
#ifndef BLKGETSIZE64
|
|
#define BLKGETSIZE64 -1
|
|
#endif
|
|
#else
|
|
#define STAT_GIVES_SIZE(s) (S_ISREG(s.st_mode))
|
|
#define BLKGET_GIVES_SIZE(s) (S_ISBLK(s.st_mode))
|
|
#endif
|
|
|
|
static int state_backend_raw_file_get_size(const char *path, size_t *out_size)
|
|
{
|
|
struct mtd_info_user meminfo;
|
|
struct stat s;
|
|
int ret;
|
|
|
|
ret = stat(path, &s);
|
|
if (ret)
|
|
return -errno;
|
|
|
|
/*
|
|
* under Linux, stat() gives the size only on regular files
|
|
* under barebox, it works on char dev, too
|
|
*/
|
|
if (STAT_GIVES_SIZE(s)) {
|
|
*out_size = s.st_size;
|
|
return 0;
|
|
}
|
|
|
|
/* this works under Linux on block devs */
|
|
if (BLKGET_GIVES_SIZE(s)) {
|
|
int fd;
|
|
|
|
fd = open(path, O_RDONLY);
|
|
if (fd < 0)
|
|
return -errno;
|
|
|
|
ret = ioctl(fd, BLKGETSIZE64, out_size);
|
|
close(fd);
|
|
if (!ret)
|
|
return 0;
|
|
}
|
|
|
|
/* try mtd next */
|
|
ret = mtd_get_meminfo(path, &meminfo);
|
|
if (!ret) {
|
|
*out_size = meminfo.size;
|
|
return 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int state_backend_raw_file_init_digest(struct state *state, struct state_backend_raw *backend_raw)
|
|
{
|
|
struct digest *digest;
|
|
struct property *p;
|
|
const char *algo;
|
|
const unsigned char *key;
|
|
int key_len, ret;
|
|
|
|
p = of_find_property(state->root, "algo", NULL);
|
|
if (!p) /* does not exist */
|
|
return 0;
|
|
|
|
ret = of_property_read_string(state->root, "algo", &algo);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!IS_ENABLED(CONFIG_STATE_CRYPTO)) {
|
|
dev_err(&state->dev,
|
|
"algo %s specified, but crypto support for state framework (CONFIG_STATE_CRYPTO) not enabled.\n",
|
|
algo);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = keystore_get_secret(state->name, &key, &key_len);
|
|
if (ret == -ENOENT) /* -ENOENT == does not exist */
|
|
return -EPROBE_DEFER;
|
|
else if (ret)
|
|
return ret;
|
|
|
|
digest = digest_alloc(algo);
|
|
if (!digest) {
|
|
dev_info(&state->dev, "algo %s not found - probe deferred\n", algo);
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
ret = digest_set_key(digest, key, key_len);
|
|
if (ret) {
|
|
digest_free(digest);
|
|
return ret;
|
|
}
|
|
|
|
backend_raw->backend.digest = digest;
|
|
backend_raw->size_full = digest_length(digest);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* state_backend_raw_file - create a raw file backend store for a state instance
|
|
*
|
|
* @state The state instance to work on
|
|
* @path The path where the state will be stored to
|
|
* @offset The offset in the storage file
|
|
* @size The maximum size to use in the storage file
|
|
*
|
|
* This backend stores raw binary data from a state instance. The
|
|
* binary data is protected with a magic value which has to match and
|
|
* a crc32 that must be valid. Two copies are stored, sufficient
|
|
* space must be available.
|
|
|
|
* @path can be a path to a device or a regular file. When it's a
|
|
* device @size may be 0. The two copies are spread to different
|
|
* eraseblocks if approriate for this device.
|
|
*/
|
|
int state_backend_raw_file(struct state *state, const char *of_path,
|
|
const char *path, off_t offset, size_t size)
|
|
{
|
|
struct state_backend_raw *backend_raw;
|
|
struct state_backend *backend;
|
|
struct state_variable *sv;
|
|
struct mtd_info_user meminfo;
|
|
size_t path_size = 0;
|
|
int ret;
|
|
|
|
if (state->backend)
|
|
return -EBUSY;
|
|
|
|
ret = state_backend_raw_file_get_size(path, &path_size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (size == 0)
|
|
size = path_size;
|
|
else if (offset + size > path_size)
|
|
return -EINVAL;
|
|
|
|
backend_raw = xzalloc(sizeof(*backend_raw));
|
|
|
|
ret = state_backend_raw_file_init_digest(state, backend_raw);
|
|
if (ret) {
|
|
free(backend_raw);
|
|
return ret;
|
|
}
|
|
|
|
backend = &backend_raw->backend;
|
|
backend->save = state_backend_raw_save;
|
|
backend->of_path = xstrdup(of_path);
|
|
backend->path = xstrdup(path);
|
|
backend->name = "raw";
|
|
|
|
sv = list_last_entry(&state->variables, struct state_variable, list);
|
|
backend_raw->size_data = sv->start + sv->size;
|
|
backend_raw->offset = offset;
|
|
backend_raw->size = size;
|
|
backend_raw->size_full += backend_raw->size_data +
|
|
sizeof(struct backend_raw_header);
|
|
|
|
state->backend = backend;
|
|
|
|
ret = mtd_get_meminfo(backend->path, &meminfo);
|
|
if (!ret && !(meminfo.flags & MTD_NO_ERASE)) {
|
|
backend_raw->need_erase = true;
|
|
backend_raw->size_full = ALIGN(backend_raw->size_full,
|
|
meminfo.writesize);
|
|
backend_raw->stride = ALIGN(backend_raw->size_full,
|
|
meminfo.erasesize);
|
|
dev_dbg(&state->dev, "is a mtd, adjust stepsize to %ld\n",
|
|
backend_raw->stride);
|
|
} else {
|
|
backend_raw->stride = backend_raw->size_full;
|
|
}
|
|
|
|
if (backend_raw->size / backend_raw->stride < RAW_BACKEND_COPIES) {
|
|
dev_err(&state->dev, "not enough space for two copies (%lu each)\n",
|
|
backend_raw->stride);
|
|
ret = -ENOSPC;
|
|
goto err;
|
|
}
|
|
|
|
ret = state_backend_raw_load(backend, state);
|
|
if (ret) {
|
|
dev_warn(&state->dev, "load failed - using defaults\n");
|
|
} else {
|
|
dev_info(&state->dev, "load successful\n");
|
|
state->dirty = 0;
|
|
}
|
|
|
|
/* ignore return value of load() */
|
|
return 0;
|
|
err:
|
|
digest_free(backend_raw->backend.digest);
|
|
|
|
free(backend_raw);
|
|
return ret;
|
|
}
|