Merge branch 'for-next/state'
This commit is contained in:
commit
e77acea709
|
@ -36,7 +36,12 @@ Optional properties:
|
|||
|
||||
* ``algo``: A HMAC algorithm used to detect manipulation of the data
|
||||
or header, sensible values follow this pattern ``hmac(<HASH>)``,
|
||||
e.g. ``hmac(sha256)``.
|
||||
e.g. ``hmac(sha256)``. Only used for ``raw``.
|
||||
* ``backend-stridesize``: Maximum size per copy of the data. Only important for
|
||||
non-MTD devices
|
||||
* ``backend-storage-type``: Type of the storage. This has two options at the
|
||||
moment. For MTD with erasing the correct type is ``circular``. For all other
|
||||
devices and files, ``direct`` is the needed type.
|
||||
|
||||
Variable nodes
|
||||
--------------
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
Barebox State Framework
|
||||
=======================
|
||||
|
||||
The state framework is build to exchange data between Barebox and Linux
|
||||
userspace using a non-volatile storage. There are several components involved.
|
||||
Barebox has a state driver to access the variables. For the Linux Userspace
|
||||
there is a userspace tool.
|
||||
|
||||
Devicetree
|
||||
----------
|
||||
|
||||
Currently the devicetree defines the layout of the variables and data.
|
||||
Variables are fixed size. Several types are supported, see the binding
|
||||
documentation for details.
|
||||
|
||||
Data Formats
|
||||
------------
|
||||
|
||||
The state data can be stored in different ways. Currently two formats are
|
||||
available, ``raw`` and ``dtb``. Both format the state data differently.
|
||||
Basically these are serializers. The raw serializer additionally supports a
|
||||
HMAC algorithm to detect manipulations.
|
||||
|
||||
Storage Backends
|
||||
----------------
|
||||
|
||||
The serialized data can be stored to different backends which are automatically
|
||||
selected depending on the defined backend in the devicetree. Currently two
|
||||
implementations exist, ``circular`` and ``direct``. ``circular`` writes the
|
||||
data sequentially on the backend storage device. Each save is appended until
|
||||
the storage area is full. It then erases the block and starts from offset 0.
|
||||
``circular`` is used for MTD devices with erase functionality. ``direct``
|
||||
writes the data directly to the file without erasing.
|
||||
|
||||
For all backends multiple copies are written to handle read errors.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
The ``state`` command can be used to store and manipulate the state. Using
|
||||
``state`` without argument lists you all available states with their name.
|
||||
``devinfo STATE_NAME`` shows you all variables and their values. ``state -s``
|
||||
stores the state.
|
||||
|
||||
Starting Barebox will automatically load the last written state. If loading the
|
||||
state fails the defaults are used.
|
|
@ -31,6 +31,7 @@ Contents:
|
|||
system-setup
|
||||
reset-reason
|
||||
system-reset
|
||||
state
|
||||
|
||||
* :ref:`search`
|
||||
* :ref:`genindex`
|
||||
|
|
|
@ -44,7 +44,7 @@ obj-$(CONFIG_POLLER) += poller.o
|
|||
obj-$(CONFIG_RESET_SOURCE) += reset_source.o
|
||||
obj-$(CONFIG_SHELL_HUSH) += hush.o
|
||||
obj-$(CONFIG_SHELL_SIMPLE) += parser.o
|
||||
obj-$(CONFIG_STATE) += state.o
|
||||
obj-$(CONFIG_STATE) += state/
|
||||
obj-$(CONFIG_RATP) += ratp.o
|
||||
obj-$(CONFIG_UIMAGE) += image.o uimage.o
|
||||
obj-$(CONFIG_FITIMAGE) += image-fit.o
|
||||
|
|
1720
common/state.c
1720
common/state.c
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,9 @@
|
|||
obj-y += state.o
|
||||
obj-y += state_variables.o
|
||||
obj-y += backend.o
|
||||
obj-y += backend_format_dtb.o
|
||||
obj-y += backend_format_raw.o
|
||||
obj-y += backend_storage.o
|
||||
obj-y += backend_bucket_direct.o
|
||||
obj-y += backend_bucket_circular.o
|
||||
obj-y += backend_bucket_cached.o
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
|
||||
*
|
||||
* 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 <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/string.h>
|
||||
#include <malloc.h>
|
||||
#include <printk.h>
|
||||
|
||||
#include "state.h"
|
||||
|
||||
|
||||
/**
|
||||
* Save the state
|
||||
* @param state
|
||||
* @return
|
||||
*/
|
||||
int state_save(struct state *state)
|
||||
{
|
||||
uint8_t *buf;
|
||||
ssize_t len;
|
||||
int ret;
|
||||
struct state_backend *backend = &state->backend;
|
||||
|
||||
if (!state->dirty)
|
||||
return 0;
|
||||
|
||||
ret = backend->format->pack(backend->format, state, &buf, &len);
|
||||
if (ret) {
|
||||
dev_err(&state->dev, "Failed to pack state with backend format %s, %d\n",
|
||||
backend->format->name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = state_storage_write(&backend->storage, buf, len);
|
||||
if (ret) {
|
||||
dev_err(&state->dev, "Failed to write packed state, %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
state->dirty = 0;
|
||||
|
||||
out:
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* state_load - Loads a state from the backend
|
||||
* @param state The state that should be updated to contain the loaded data
|
||||
* @return 0 on success, -errno on failure. If no state is loaded the previous
|
||||
* values remain in the state.
|
||||
*
|
||||
* This function uses the registered storage backend to read data. All data that
|
||||
* we read is checked for integrity by the formatter. After that we unpack the
|
||||
* data into our state.
|
||||
*/
|
||||
int state_load(struct state *state)
|
||||
{
|
||||
uint8_t *buf;
|
||||
ssize_t len;
|
||||
ssize_t len_hint = 0;
|
||||
int ret;
|
||||
struct state_backend *backend = &state->backend;
|
||||
|
||||
if (backend->format->get_packed_len)
|
||||
len_hint = backend->format->get_packed_len(backend->format,
|
||||
state);
|
||||
ret = state_storage_read(&backend->storage, backend->format,
|
||||
state->magic, &buf, &len, len_hint);
|
||||
if (ret) {
|
||||
dev_err(&state->dev, "Failed to read state with format %s, %d\n",
|
||||
backend->format->name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = backend->format->unpack(backend->format, state, buf, len);
|
||||
if (ret) {
|
||||
dev_err(&state->dev, "Failed to unpack read data with format %s although verified, %d\n",
|
||||
backend->format->name, ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
state->dirty = 0;
|
||||
|
||||
out:
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int state_format_init(struct state_backend *backend,
|
||||
struct device_d *dev, const char *backend_format,
|
||||
struct device_node *node, const char *state_name)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!strcmp(backend_format, "raw")) {
|
||||
ret = backend_format_raw_create(&backend->format, node,
|
||||
state_name, dev);
|
||||
} else if (!strcmp(backend_format, "dtb")) {
|
||||
ret = backend_format_dtb_create(&backend->format, dev);
|
||||
} else {
|
||||
dev_err(dev, "Invalid backend format %s\n",
|
||||
backend_format);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(dev, "Failed to initialize format %s, %d\n",
|
||||
backend_format, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void state_format_free(struct state_backend_format *format)
|
||||
{
|
||||
if (format->free)
|
||||
format->free(format);
|
||||
}
|
||||
|
||||
/**
|
||||
* state_backend_init - Initiates the backend storage and format using the
|
||||
* passed arguments
|
||||
* @param backend state backend
|
||||
* @param dev Device pointer used for prints
|
||||
* @param node the DT device node corresponding to the state
|
||||
* @param backend_format a string describing the format. Valid values are 'raw'
|
||||
* and 'dtb' currently
|
||||
* @param storage_path Path to the backend storage file/device/partition/...
|
||||
* @param state_name Name of the state
|
||||
* @param of_path Path in the devicetree
|
||||
* @param stridesize stridesize in case we have a medium without eraseblocks.
|
||||
* stridesize describes how far apart copies of the same data should be stored.
|
||||
* For blockdevices it makes sense to align them on blocksize.
|
||||
* @param storagetype Type of the storage backend. This may be NULL where we
|
||||
* autoselect some backwardscompatible backend options
|
||||
* @return 0 on success, -errno otherwise
|
||||
*/
|
||||
int state_backend_init(struct state_backend *backend, struct device_d *dev,
|
||||
struct device_node *node, const char *backend_format,
|
||||
const char *storage_path, const char *state_name, const
|
||||
char *of_path, off_t offset, size_t max_size,
|
||||
uint32_t stridesize, const char *storagetype)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = state_format_init(backend, dev, backend_format, node, state_name);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = state_storage_init(&backend->storage, dev, storage_path, offset,
|
||||
max_size, stridesize, storagetype);
|
||||
if (ret)
|
||||
goto out_free_format;
|
||||
|
||||
backend->of_path = of_path;
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_format:
|
||||
state_format_free(backend->format);
|
||||
backend->format = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void state_backend_set_readonly(struct state_backend *backend)
|
||||
{
|
||||
state_storage_set_readonly(&backend->storage);
|
||||
}
|
||||
|
||||
void state_backend_free(struct state_backend *backend)
|
||||
{
|
||||
state_storage_free(&backend->storage);
|
||||
if (backend->format)
|
||||
state_format_free(backend->format);
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
|
||||
*
|
||||
* 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 <common.h>
|
||||
#include "state.h"
|
||||
|
||||
struct state_backend_storage_bucket_cache {
|
||||
struct state_backend_storage_bucket bucket;
|
||||
|
||||
struct state_backend_storage_bucket *raw;
|
||||
|
||||
u8 *data;
|
||||
ssize_t data_len;
|
||||
bool force_write;
|
||||
|
||||
/* For outputs */
|
||||
struct device_d *dev;
|
||||
};
|
||||
|
||||
static inline struct state_backend_storage_bucket_cache
|
||||
*get_bucket_cache(struct state_backend_storage_bucket *bucket)
|
||||
{
|
||||
return container_of(bucket,
|
||||
struct state_backend_storage_bucket_cache,
|
||||
bucket);
|
||||
}
|
||||
|
||||
static inline void state_backend_bucket_cache_drop(
|
||||
struct state_backend_storage_bucket_cache *cache)
|
||||
{
|
||||
if (cache->data) {
|
||||
free(cache->data);
|
||||
cache->data = NULL;
|
||||
cache->data_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int state_backend_bucket_cache_fill(
|
||||
struct state_backend_storage_bucket_cache *cache)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = cache->raw->read(cache->raw, &cache->data, &cache->data_len);
|
||||
if (ret == -EUCLEAN)
|
||||
cache->force_write = true;
|
||||
else if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int state_backend_bucket_cache_read(struct state_backend_storage_bucket *bucket,
|
||||
uint8_t ** buf_out,
|
||||
ssize_t * len_hint)
|
||||
{
|
||||
struct state_backend_storage_bucket_cache *cache =
|
||||
get_bucket_cache(bucket);
|
||||
int ret;
|
||||
|
||||
if (!cache->data) {
|
||||
ret = state_backend_bucket_cache_fill(cache);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (cache->data) {
|
||||
*buf_out = xmemdup(cache->data, cache->data_len);
|
||||
if (!*buf_out)
|
||||
return -ENOMEM;
|
||||
*len_hint = cache->data_len;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int state_backend_bucket_cache_write(struct state_backend_storage_bucket *bucket,
|
||||
const uint8_t * buf, ssize_t len)
|
||||
{
|
||||
struct state_backend_storage_bucket_cache *cache =
|
||||
get_bucket_cache(bucket);
|
||||
int ret;
|
||||
|
||||
if (!cache->force_write) {
|
||||
if (!cache->data)
|
||||
ret = state_backend_bucket_cache_fill(cache);
|
||||
|
||||
if (cache->data_len == len && !memcmp(cache->data, buf, len))
|
||||
return 0;
|
||||
}
|
||||
|
||||
state_backend_bucket_cache_drop(cache);
|
||||
|
||||
ret = cache->raw->write(cache->raw, buf, len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
cache->data = xmemdup(buf, len);
|
||||
cache->data_len = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int state_backend_bucket_cache_init(
|
||||
struct state_backend_storage_bucket *bucket)
|
||||
{
|
||||
struct state_backend_storage_bucket_cache *cache =
|
||||
get_bucket_cache(bucket);
|
||||
|
||||
if (cache->raw->init) {
|
||||
return cache->raw->init(cache->raw);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void state_backend_bucket_cache_free(
|
||||
struct state_backend_storage_bucket *bucket)
|
||||
{
|
||||
struct state_backend_storage_bucket_cache *cache =
|
||||
get_bucket_cache(bucket);
|
||||
|
||||
state_backend_bucket_cache_drop(cache);
|
||||
cache->raw->free(cache->raw);
|
||||
free(cache);
|
||||
}
|
||||
|
||||
int state_backend_bucket_cached_create(struct device_d *dev,
|
||||
struct state_backend_storage_bucket *raw,
|
||||
struct state_backend_storage_bucket **out)
|
||||
{
|
||||
struct state_backend_storage_bucket_cache *cache;
|
||||
|
||||
cache = xzalloc(sizeof(*cache));
|
||||
cache->raw = raw;
|
||||
cache->dev = dev;
|
||||
|
||||
cache->bucket.free = state_backend_bucket_cache_free;
|
||||
cache->bucket.read = state_backend_bucket_cache_read;
|
||||
cache->bucket.write = state_backend_bucket_cache_write;
|
||||
cache->bucket.init = state_backend_bucket_cache_init;
|
||||
|
||||
*out = &cache->bucket;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,515 @@
|
|||
/*
|
||||
* Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
|
||||
*
|
||||
* 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 <asm-generic/ioctl.h>
|
||||
#include <common.h>
|
||||
#include <fcntl.h>
|
||||
#include <fs.h>
|
||||
#include <libfile.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mtd/mtd-abi.h>
|
||||
#include <malloc.h>
|
||||
#include <mtd/mtd-peb.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "state.h"
|
||||
|
||||
|
||||
struct state_backend_storage_bucket_circular {
|
||||
struct state_backend_storage_bucket bucket;
|
||||
|
||||
unsigned int eraseblock; /* Which eraseblock is used */
|
||||
ssize_t writesize; /* Alignment of writes */
|
||||
ssize_t max_size; /* Maximum size of this bucket */
|
||||
|
||||
off_t write_area; /* Start of the write area (relative offset) */
|
||||
uint32_t last_written_length; /* Size of the data written in the storage */
|
||||
|
||||
#ifdef __BAREBOX__
|
||||
struct mtd_info *mtd; /* mtd info (used for io in Barebox)*/
|
||||
#else
|
||||
struct mtd_info_user *mtd;
|
||||
int fd;
|
||||
#endif
|
||||
|
||||
/* For outputs */
|
||||
struct device_d *dev;
|
||||
};
|
||||
|
||||
struct state_backend_storage_bucket_circular_meta {
|
||||
uint32_t magic;
|
||||
uint32_t written_length;
|
||||
};
|
||||
|
||||
static const uint32_t circular_magic = 0x14fa2d02;
|
||||
static const uint8_t free_pattern = 0xff;
|
||||
|
||||
static inline struct state_backend_storage_bucket_circular
|
||||
*get_bucket_circular(struct state_backend_storage_bucket *bucket)
|
||||
{
|
||||
return container_of(bucket,
|
||||
struct state_backend_storage_bucket_circular,
|
||||
bucket);
|
||||
}
|
||||
|
||||
#ifdef __BAREBOX__
|
||||
static int state_mtd_peb_read(struct state_backend_storage_bucket_circular *circ,
|
||||
char *buf, int offset, int len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mtd_peb_read(circ->mtd, buf, circ->eraseblock, offset, len);
|
||||
if (ret == -EBADMSG) {
|
||||
ret = mtd_peb_torture(circ->mtd, circ->eraseblock);
|
||||
if (ret == -EIO) {
|
||||
dev_err(circ->dev, "Tortured eraseblock failed and is marked bad now, PEB %u\n",
|
||||
circ->eraseblock);
|
||||
return -EIO;
|
||||
} else if (ret < 0) {
|
||||
dev_err(circ->dev, "Failed to torture eraseblock, %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
/*
|
||||
* Fill with invalid data so that the next write is done
|
||||
* behind this area
|
||||
*/
|
||||
memset(buf, 0, len);
|
||||
ret = -EUCLEAN;
|
||||
circ->write_area = 0;
|
||||
dev_dbg(circ->dev, "PEB %u has ECC error, forcing rewrite\n",
|
||||
circ->eraseblock);
|
||||
} else if (ret == -EUCLEAN) {
|
||||
dev_dbg(circ->dev, "PEB %u is unclean, forcing rewrite\n",
|
||||
circ->eraseblock);
|
||||
} else if (ret < 0) {
|
||||
dev_err(circ->dev, "Failed to read PEB %u, %d\n",
|
||||
circ->eraseblock, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int state_mtd_peb_write(struct state_backend_storage_bucket_circular *circ,
|
||||
const char *buf, int offset, int len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mtd_peb_write(circ->mtd, buf, circ->eraseblock, offset, len);
|
||||
if (ret == -EBADMSG) {
|
||||
ret = mtd_peb_torture(circ->mtd, circ->eraseblock);
|
||||
if (ret == -EIO) {
|
||||
dev_err(circ->dev, "Tortured eraseblock failed and is marked bad now, PEB %u\n",
|
||||
circ->eraseblock);
|
||||
return -EIO;
|
||||
} else if (ret < 0) {
|
||||
dev_err(circ->dev, "Failed to torture eraseblock, %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
ret = -EUCLEAN;
|
||||
} else if (ret < 0 && ret != -EUCLEAN) {
|
||||
dev_err(circ->dev, "Failed to write PEB %u, %d\n",
|
||||
circ->eraseblock, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int state_mtd_peb_erase(struct state_backend_storage_bucket_circular *circ)
|
||||
{
|
||||
return mtd_peb_erase(circ->mtd, circ->eraseblock);
|
||||
}
|
||||
#else
|
||||
static int state_mtd_peb_read(struct state_backend_storage_bucket_circular *circ,
|
||||
char *buf, int suboffset, int len)
|
||||
{
|
||||
int ret;
|
||||
off_t offset = suboffset;
|
||||
struct mtd_ecc_stats stat1, stat2;
|
||||
bool nostats = false;
|
||||
|
||||
offset += (off_t)circ->eraseblock * circ->mtd->erasesize;
|
||||
|
||||
ret = lseek(circ->fd, offset, SEEK_SET);
|
||||
if (ret < 0) {
|
||||
dev_err(circ->dev, "Failed to set circular read position to %lld, %d\n",
|
||||
offset, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_dbg(circ->dev, "Read state from %ld length %zd\n", offset,
|
||||
len);
|
||||
|
||||
ret = ioctl(circ->fd, ECCGETSTATS, &stat1);
|
||||
if (ret)
|
||||
nostats = true;
|
||||
|
||||
ret = read_full(circ->fd, buf, len);
|
||||
if (ret < 0) {
|
||||
dev_err(circ->dev, "Failed to read circular storage len %zd, %d\n",
|
||||
len, ret);
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (nostats)
|
||||
return 0;
|
||||
|
||||
ret = ioctl(circ->fd, ECCGETSTATS, &stat2);
|
||||
if (ret)
|
||||
return 0;
|
||||
|
||||
if (stat2.failed - stat1.failed > 0) {
|
||||
ret = -EUCLEAN;
|
||||
dev_dbg(circ->dev, "PEB %u has ECC error, forcing rewrite\n",
|
||||
circ->eraseblock);
|
||||
} else if (stat2.corrected - stat1.corrected > 0) {
|
||||
ret = -EUCLEAN;
|
||||
dev_dbg(circ->dev, "PEB %u is unclean, forcing rewrite\n",
|
||||
circ->eraseblock);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int state_mtd_peb_write(struct state_backend_storage_bucket_circular *circ,
|
||||
const char *buf, int suboffset, int len)
|
||||
{
|
||||
int ret;
|
||||
off_t offset = suboffset;
|
||||
|
||||
offset += circ->eraseblock * circ->mtd->erasesize;
|
||||
|
||||
ret = lseek(circ->fd, offset, SEEK_SET);
|
||||
if (ret < 0) {
|
||||
dev_err(circ->dev, "Failed to set position for circular write %ld, %d\n",
|
||||
offset, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = write_full(circ->fd, buf, len);
|
||||
if (ret < 0) {
|
||||
dev_err(circ->dev, "Failed to write circular to %ld length %zd, %d\n",
|
||||
offset, len, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* We keep the fd open, so flush is necessary. We ignore the return
|
||||
* value as flush is currently not supported for mtd under linux.
|
||||
*/
|
||||
flush(circ->fd);
|
||||
|
||||
dev_dbg(circ->dev, "Written state to offset %ld length %zd data length %zd\n",
|
||||
offset, len, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int state_mtd_peb_erase(struct state_backend_storage_bucket_circular *circ)
|
||||
{
|
||||
return erase(circ->fd, circ->mtd->erasesize,
|
||||
(off_t)circ->eraseblock * circ->mtd->erasesize);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int state_backend_bucket_circular_read(struct state_backend_storage_bucket *bucket,
|
||||
uint8_t ** buf_out,
|
||||
ssize_t * len_hint)
|
||||
{
|
||||
struct state_backend_storage_bucket_circular *circ =
|
||||
get_bucket_circular(bucket);
|
||||
ssize_t read_len;
|
||||
off_t offset;
|
||||
uint8_t *buf;
|
||||
int ret;
|
||||
|
||||
/* Storage is empty */
|
||||
if (circ->write_area == 0)
|
||||
return -ENODATA;
|
||||
|
||||
if (!circ->last_written_length) {
|
||||
/*
|
||||
* Last write did not contain length information, assuming old
|
||||
* state and reading from the beginning.
|
||||
*/
|
||||
offset = 0;
|
||||
read_len = min(circ->write_area, (off_t)(circ->max_size -
|
||||
sizeof(struct state_backend_storage_bucket_circular_meta)));
|
||||
circ->write_area = 0;
|
||||
dev_dbg(circ->dev, "Detected old on-storage format\n");
|
||||
} else if (circ->last_written_length > circ->write_area
|
||||
|| !IS_ALIGNED(circ->last_written_length, circ->writesize)) {
|
||||
circ->write_area = 0;
|
||||
dev_err(circ->dev, "Error, invalid number of bytes written last time %d\n",
|
||||
circ->last_written_length);
|
||||
return -EINVAL;
|
||||
} else {
|
||||
/*
|
||||
* Normally we read at the end of the non-free area. The length
|
||||
* of the read is then what we read from the meta data
|
||||
* (last_written_length)
|
||||
*/
|
||||
read_len = circ->last_written_length;
|
||||
offset = circ->write_area - read_len;
|
||||
}
|
||||
|
||||
buf = xmalloc(read_len);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_dbg(circ->dev, "Read state from PEB %u global offset %ld length %zd\n",
|
||||
circ->eraseblock, offset, read_len);
|
||||
|
||||
ret = state_mtd_peb_read(circ, buf, offset, read_len);
|
||||
if (ret < 0 && ret != -EUCLEAN) {
|
||||
dev_err(circ->dev, "Failed to read circular storage len %zd, %d\n",
|
||||
read_len, ret);
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*buf_out = buf;
|
||||
*len_hint = read_len - sizeof(struct state_backend_storage_bucket_circular_meta);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int state_backend_bucket_circular_write(struct state_backend_storage_bucket *bucket,
|
||||
const uint8_t * buf,
|
||||
ssize_t len)
|
||||
{
|
||||
struct state_backend_storage_bucket_circular *circ =
|
||||
get_bucket_circular(bucket);
|
||||
off_t offset;
|
||||
struct state_backend_storage_bucket_circular_meta *meta;
|
||||
uint32_t written_length = ALIGN(len + sizeof(*meta), circ->writesize);
|
||||
int ret;
|
||||
uint8_t *write_buf;
|
||||
|
||||
if (written_length > circ->max_size) {
|
||||
dev_err(circ->dev, "Error, state data too big to be written, to write: %zd, writesize: %zd, length: %zd, available: %zd\n",
|
||||
written_length, circ->writesize, len, circ->max_size);
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
/*
|
||||
* We need zero initialization so that our data comparisons don't show
|
||||
* random changes
|
||||
*/
|
||||
write_buf = xzalloc(written_length);
|
||||
if (!write_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(write_buf, buf, len);
|
||||
meta = (struct state_backend_storage_bucket_circular_meta *)
|
||||
(write_buf + written_length - sizeof(*meta));
|
||||
meta->magic = circular_magic;
|
||||
meta->written_length = written_length;
|
||||
|
||||
if (circ->write_area + written_length >= circ->max_size) {
|
||||
circ->write_area = 0;
|
||||
}
|
||||
/*
|
||||
* If the write area is at the beginning of the page, erase it and write
|
||||
* at offset 0. As we only erase right before writing there are no
|
||||
* conditions where we regularly erase a block multiple times without
|
||||
* writing.
|
||||
*/
|
||||
if (circ->write_area == 0) {
|
||||
dev_dbg(circ->dev, "Erasing PEB %u\n", circ->eraseblock);
|
||||
ret = state_mtd_peb_erase(circ);
|
||||
if (ret) {
|
||||
dev_err(circ->dev, "Failed to erase PEB %u\n",
|
||||
circ->eraseblock);
|
||||
goto out_free;
|
||||
}
|
||||
}
|
||||
|
||||
offset = circ->write_area;
|
||||
|
||||
/*
|
||||
* Update write_area before writing. The write operation may put
|
||||
* arbitrary amount of the data into the storage before failing. In this
|
||||
* case we want to start after that area.
|
||||
*/
|
||||
circ->write_area += written_length;
|
||||
|
||||
ret = state_mtd_peb_write(circ, write_buf, offset, written_length);
|
||||
if (ret < 0 && ret != -EUCLEAN) {
|
||||
dev_err(circ->dev, "Failed to write circular to %ld length %zd, %d\n",
|
||||
offset, written_length, ret);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
dev_dbg(circ->dev, "Written state to PEB %u offset %ld length %zd data length %zd\n",
|
||||
circ->eraseblock, offset, written_length, len);
|
||||
|
||||
out_free:
|
||||
free(write_buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* state_backend_bucket_circular_init - Initialize circular bucket
|
||||
* @param bucket
|
||||
* @return 0 on success, -errno otherwise
|
||||
*
|
||||
* This function searches for the beginning of the written area from the end of
|
||||
* the MTD device. This way it knows where the data ends and where the free area
|
||||
* starts.
|
||||
*/
|
||||
static int state_backend_bucket_circular_init(
|
||||
struct state_backend_storage_bucket *bucket)
|
||||
{
|
||||
struct state_backend_storage_bucket_circular *circ =
|
||||
get_bucket_circular(bucket);
|
||||
int sub_offset;
|
||||
uint32_t written_length = 0;
|
||||
uint8_t *buf;
|
||||
|
||||
buf = xmalloc(circ->writesize);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
for (sub_offset = circ->max_size - circ->writesize; sub_offset >= 0;
|
||||
sub_offset -= circ->writesize) {
|
||||
int ret;
|
||||
|
||||
ret = state_mtd_peb_read(circ, buf, sub_offset,
|
||||
circ->writesize);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mtd_buf_all_ff(buf, circ->writesize);
|
||||
if (!ret) {
|
||||
struct state_backend_storage_bucket_circular_meta *meta;
|
||||
|
||||
meta = (struct state_backend_storage_bucket_circular_meta *)
|
||||
(buf + circ->writesize - sizeof(*meta));
|
||||
|
||||
if (meta->magic != circular_magic)
|
||||
written_length = 0;
|
||||
else
|
||||
written_length = meta->written_length;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
circ->write_area = sub_offset + circ->writesize;
|
||||
circ->last_written_length = written_length;
|
||||
|
||||
free(buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void state_backend_bucket_circular_free(struct
|
||||
state_backend_storage_bucket
|
||||
*bucket)
|
||||
{
|
||||
struct state_backend_storage_bucket_circular *circ =
|
||||
get_bucket_circular(bucket);
|
||||
|
||||
free(circ);
|
||||
}
|
||||
|
||||
#ifdef __BAREBOX__
|
||||
static int bucket_circular_is_block_bad(struct state_backend_storage_bucket_circular *circ)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mtd_peb_is_bad(circ->mtd, circ->eraseblock);
|
||||
if (ret < 0)
|
||||
dev_err(circ->dev, "Failed to determine whether eraseblock %u is bad, %d\n",
|
||||
circ->eraseblock, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
static int bucket_circular_is_block_bad(struct state_backend_storage_bucket_circular *circ)
|
||||
{
|
||||
int ret;
|
||||
loff_t offs = circ->eraseblock * circ->mtd->erasesize;
|
||||
|
||||
ret = ioctl(circ->fd, MEMGETBADBLOCK, &offs);
|
||||
if (ret < 0)
|
||||
dev_err(circ->dev, "Failed to use ioctl to check for bad block at offset %ld, %d\n",
|
||||
offs, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
int state_backend_bucket_circular_create(struct device_d *dev, const char *path,
|
||||
struct state_backend_storage_bucket **bucket,
|
||||
unsigned int eraseblock,
|
||||
ssize_t writesize,
|
||||
struct mtd_info_user *mtd_uinfo,
|
||||
bool lazy_init)
|
||||
{
|
||||
struct state_backend_storage_bucket_circular *circ;
|
||||
int ret;
|
||||
|
||||
circ = xzalloc(sizeof(*circ));
|
||||
circ->eraseblock = eraseblock;
|
||||
circ->writesize = writesize;
|
||||
circ->max_size = mtd_uinfo->erasesize;
|
||||
circ->dev = dev;
|
||||
|
||||
#ifdef __BAREBOX__
|
||||
circ->mtd = mtd_uinfo->mtd;
|
||||
#else
|
||||
circ->mtd = xzalloc(sizeof(*mtd_uinfo));
|
||||
memcpy(circ->mtd, mtd_uinfo, sizeof(*mtd_uinfo));
|
||||
circ->fd = open(path, O_RDWR);
|
||||
if (circ->fd < 0) {
|
||||
pr_err("Failed to open circular bucket '%s'\n", path);
|
||||
return -errno;
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = bucket_circular_is_block_bad(circ);
|
||||
if (ret) {
|
||||
dev_info(dev, "Not using eraseblock %u, it is marked as bad (%d)\n",
|
||||
circ->eraseblock, ret);
|
||||
ret = -EIO;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
circ->bucket.read = state_backend_bucket_circular_read;
|
||||
circ->bucket.write = state_backend_bucket_circular_write;
|
||||
circ->bucket.free = state_backend_bucket_circular_free;
|
||||
*bucket = &circ->bucket;
|
||||
|
||||
if (!lazy_init) {
|
||||
ret = state_backend_bucket_circular_init(*bucket);
|
||||
if (ret)
|
||||
goto out_free;
|
||||
} else {
|
||||
circ->bucket.init = state_backend_bucket_circular_init;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_free:
|
||||
#ifndef __BAREBOX__
|
||||
close(circ->fd);
|
||||
#endif
|
||||
free(circ);
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
|
||||
*
|
||||
* 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 <fcntl.h>
|
||||
#include <fs.h>
|
||||
#include <libfile.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <malloc.h>
|
||||
#include <printk.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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>
|
||||
* Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
|
||||
*
|
||||
* 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 <common.h>
|
||||
#include <of.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
* 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>
|
||||
* Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
|
||||
*
|
||||
* 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 <common.h>
|
||||
#include <common.h>
|
||||
#include <crypto/keystore.h>
|
||||
#include <digest.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <malloc.h>
|
||||
#include <crc.h>
|
||||
#include <of.h>
|
||||
#include <crc.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -0,0 +1,525 @@
|
|||
/*
|
||||
* Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
|
||||
*
|
||||
* 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 <asm-generic/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <fs.h>
|
||||
#include <libfile.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mtd/mtd-abi.h>
|
||||
#include <linux/stat.h>
|
||||
#include <malloc.h>
|
||||
#include <printk.h>
|
||||
|
||||
#include "state.h"
|
||||
|
||||
const unsigned int min_copies_written = 1;
|
||||
|
||||
static int bucket_lazy_init(struct state_backend_storage_bucket *bucket)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (bucket->initialized)
|
||||
return 0;
|
||||
|
||||
if (bucket->init) {
|
||||
ret = bucket->init(bucket);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
bucket->initialized = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* state_storage_write - Writes the given data to the storage
|
||||
* @param storage Storage object
|
||||
* @param buf Buffer with the data
|
||||
* @param len Length of the buffer
|
||||
* @return 0 on success, -errno otherwise
|
||||
*
|
||||
* This function iterates over all registered buckets and executes a write
|
||||
* operation on all of them. Writes are always in the same sequence. This
|
||||
* ensures, that reading in the same sequence will always return the latest
|
||||
* written valid data first.
|
||||
* We try to at least write min_copies_written. If this fails we return with an
|
||||
* error.
|
||||
*/
|
||||
int state_storage_write(struct state_backend_storage *storage,
|
||||
const uint8_t * buf, ssize_t len)
|
||||
{
|
||||
struct state_backend_storage_bucket *bucket;
|
||||
int ret;
|
||||
int copies_written = 0;
|
||||
|
||||
if (storage->readonly)
|
||||
return 0;
|
||||
|
||||
list_for_each_entry(bucket, &storage->buckets, bucket_list) {
|
||||
ret = bucket_lazy_init(bucket);
|
||||
if (ret) {
|
||||
dev_warn(storage->dev, "Failed to init bucket/write state backend bucket, %d\n",
|
||||
ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = bucket->write(bucket, buf, len);
|
||||
if (ret) {
|
||||
dev_warn(storage->dev, "Failed to write state backend bucket, %d\n",
|
||||
ret);
|
||||
} else {
|
||||
++copies_written;
|
||||
}
|
||||
}
|
||||
|
||||
if (copies_written >= min_copies_written)
|
||||
return 0;
|
||||
|
||||
dev_err(storage->dev, "Failed to write state to at least %d buckets. Successfully written to %d buckets\n",
|
||||
min_copies_written, copies_written);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/**
|
||||
* state_storage_restore_consistency - Restore consistency on all storage backends
|
||||
* @param storage Storage object
|
||||
* @param buf Buffer with valid data that should be on all buckets after this operation
|
||||
* @param len Length of the buffer
|
||||
* @return 0 on success, -errno otherwise
|
||||
*
|
||||
* This function brings valid data onto all buckets we have to ensure that all
|
||||
* data copies are in sync. In the current implementation we just write the data
|
||||
* to all buckets. Bucket implementations that need to keep the number of writes
|
||||
* low, can read their own copy first and compare it.
|
||||
*/
|
||||
int state_storage_restore_consistency(struct state_backend_storage *storage,
|
||||
const uint8_t * buf, ssize_t len)
|
||||
{
|
||||
return state_storage_write(storage, buf, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* state_storage_read - Reads valid data from the backend storage
|
||||
* @param storage Storage object
|
||||
* @param format Format of the data that is stored
|
||||
* @param magic state magic value
|
||||
* @param buf The newly allocated data area will be stored in this pointer
|
||||
* @param len The resulting length of the buffer
|
||||
* @param len_hint Hint of how big the data may be.
|
||||
* @return 0 on success, -errno otherwise. buf and len will be set to valid
|
||||
* values on success.
|
||||
*
|
||||
* This function goes through all buckets and tries to read valid data from
|
||||
* them. The first bucket which returns data that is successfully verified
|
||||
* against the data format is used. To ensure the validity of all bucket copies,
|
||||
* we restore the consistency at the end.
|
||||
*/
|
||||
int state_storage_read(struct state_backend_storage *storage,
|
||||
struct state_backend_format *format,
|
||||
uint32_t magic, uint8_t ** buf, ssize_t * len,
|
||||
ssize_t len_hint)
|
||||
{
|
||||
struct state_backend_storage_bucket *bucket;
|
||||
int ret;
|
||||
|
||||
list_for_each_entry(bucket, &storage->buckets, bucket_list) {
|
||||
*len = len_hint;
|
||||
ret = bucket_lazy_init(bucket);
|
||||
if (ret) {
|
||||
dev_warn(storage->dev, "Failed to init bucket/read state backend bucket, %d\n",
|
||||
ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = bucket->read(bucket, buf, len);
|
||||
if (ret) {
|
||||
dev_warn(storage->dev, "Failed to read from state backend bucket, trying next, %d\n",
|
||||
ret);
|
||||
continue;
|
||||
}
|
||||
ret = format->verify(format, magic, *buf, *len);
|
||||
if (!ret) {
|
||||
goto found;
|
||||
}
|
||||
free(*buf);
|
||||
dev_warn(storage->dev, "Failed to verify read copy, trying next bucket, %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
dev_err(storage->dev, "Failed to find any valid state copy in any bucket\n");
|
||||
|
||||
return -ENOENT;
|
||||
|
||||
found:
|
||||
/* A failed restore consistency is not a failure of reading the state */
|
||||
state_storage_restore_consistency(storage, *buf, *len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtd_get_meminfo(const char *path, struct mtd_info_user *meminfo)
|
||||
{
|
||||
int fd, ret;
|
||||
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
pr_err("Failed to open '%s', %d\n", path, ret);
|
||||
return fd;
|
||||
}
|
||||
|
||||
ret = ioctl(fd, MEMGETINFO, meminfo);
|
||||
|
||||
close(fd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef __BAREBOX__
|
||||
#define STAT_GIVES_SIZE(s) (S_ISREG(s.st_mode) || S_ISCHR(s.st_mode))
|
||||
#define BLKGET_GIVES_SIZE(s) 0
|
||||
#else
|
||||
#define STAT_GIVES_SIZE(s) (S_ISREG(s.st_mode))
|
||||
#define BLKGET_GIVES_SIZE(s) (S_ISBLK(s.st_mode))
|
||||
#endif
|
||||
#ifndef BLKGETSIZE64
|
||||
#define BLKGETSIZE64 -1
|
||||
#endif
|
||||
|
||||
static int state_backend_storage_get_size(const char *path, size_t * out_size)
|
||||
{
|
||||
struct mtd_info_user meminfo;
|
||||
struct stat s;
|
||||
int ret;
|
||||
|
||||
ret = stat(path, &s);
|
||||
if (ret)
|
||||
return -errno;
|
||||
|
||||
/*
|
||||
* under Linux, stat() gives the size only on regular files
|
||||
* under barebox, it works on char dev, too
|
||||
*/
|
||||
if (STAT_GIVES_SIZE(s)) {
|
||||
*out_size = s.st_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* this works under Linux on block devs */
|
||||
if (BLKGET_GIVES_SIZE(s)) {
|
||||
int fd;
|
||||
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
|
||||
ret = ioctl(fd, BLKGETSIZE64, out_size);
|
||||
close(fd);
|
||||
if (!ret)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* try mtd next */
|
||||
ret = mtd_get_meminfo(path, &meminfo);
|
||||
if (!ret) {
|
||||
*out_size = meminfo.size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Number of copies that should be allocated */
|
||||
const int desired_copies = 3;
|
||||
|
||||
/**
|
||||
* state_storage_mtd_buckets_init - Creates storage buckets for mtd devices
|
||||
* @param storage Storage object
|
||||
* @param meminfo Info about the mtd device
|
||||
* @param path Path to the device
|
||||
* @param non_circular Use non-circular mode to write data that is compatible with the old on-flash format
|
||||
* @param dev_offset Offset to start at in the device.
|
||||
* @param max_size Maximum size to use for data. May be 0 for infinite.
|
||||
* @return 0 on success, -errno otherwise
|
||||
*
|
||||
* Starting from offset 0 this function tries to create circular buckets on
|
||||
* different offsets in the device. Different copies of the data are located in
|
||||
* different eraseblocks.
|
||||
* For MTD devices we use circular buckets to minimize the number of erases.
|
||||
* Circular buckets write new data always in the next free space.
|
||||
*/
|
||||
static int state_storage_mtd_buckets_init(struct state_backend_storage *storage,
|
||||
struct mtd_info_user *meminfo,
|
||||
const char *path, bool non_circular,
|
||||
off_t dev_offset, size_t max_size)
|
||||
{
|
||||
struct state_backend_storage_bucket *bucket;
|
||||
ssize_t end = dev_offset + max_size;
|
||||
int nr_copies = 0;
|
||||
off_t offset;
|
||||
|
||||
if (!end || end > meminfo->size)
|
||||
end = meminfo->size;
|
||||
|
||||
if (!IS_ALIGNED(dev_offset, meminfo->erasesize)) {
|
||||
dev_err(storage->dev, "Offset within the device is not aligned to eraseblocks. Offset is %ld, erasesize %zu\n",
|
||||
dev_offset, meminfo->erasesize);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (offset = dev_offset; offset < end; offset += meminfo->erasesize) {
|
||||
int ret;
|
||||
ssize_t writesize = meminfo->writesize;
|
||||
unsigned int eraseblock = offset / meminfo->erasesize;
|
||||
bool lazy_init = true;
|
||||
|
||||
if (non_circular)
|
||||
writesize = meminfo->erasesize;
|
||||
|
||||
ret = state_backend_bucket_circular_create(storage->dev, path,
|
||||
&bucket,
|
||||
eraseblock,
|
||||
writesize,
|
||||
meminfo,
|
||||
lazy_init);
|
||||
if (ret) {
|
||||
dev_warn(storage->dev, "Failed to create bucket at '%s' eraseblock %u\n",
|
||||
path, eraseblock);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = state_backend_bucket_cached_create(storage->dev, bucket,
|
||||
&bucket);
|
||||
if (ret) {
|
||||
dev_warn(storage->dev, "Failed to setup cache bucket, continuing without cache, %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
list_add_tail(&bucket->bucket_list, &storage->buckets);
|
||||
++nr_copies;
|
||||
if (nr_copies >= desired_copies)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!nr_copies) {
|
||||
dev_err(storage->dev, "Failed to initialize any state storage bucket\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dev_warn(storage->dev, "Failed to initialize desired amount of buckets, only %d of %d succeeded\n",
|
||||
nr_copies, desired_copies);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int state_storage_file_create(struct device_d *dev, const char *path,
|
||||
size_t fd_size)
|
||||
{
|
||||
int fd;
|
||||
uint8_t *buf;
|
||||
int ret;
|
||||
|
||||
fd = open(path, O_RDWR | O_CREAT, 0600);
|
||||
if (fd < 0) {
|
||||
dev_err(dev, "Failed to open/create file '%s', %d\n", path,
|
||||
-errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
buf = xzalloc(fd_size);
|
||||
if (!buf) {
|
||||
ret = -ENOMEM;
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
ret = write_full(fd, buf, fd_size);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to initialize empty file '%s', %d\n", path,
|
||||
ret);
|
||||
goto out_free;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
out_free:
|
||||
free(buf);
|
||||
out_close:
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* state_storage_file_buckets_init - Create buckets for a conventional file descriptor
|
||||
* @param storage Storage object
|
||||
* @param path Path to file/device
|
||||
* @param dev_offset Offset in the device to start writing at.
|
||||
* @param max_size Maximum size of the data. May be 0 for infinite.
|
||||
* @param stridesize How far apart the different data copies are placed. If
|
||||
* stridesize is 0, only one copy can be created.
|
||||
* @return 0 on success, -errno otherwise
|
||||
*
|
||||
* For blockdevices and other regular files we create direct buckets beginning
|
||||
* at offset 0. Direct buckets are simple and write data always to offset 0.
|
||||
*/
|
||||
static int state_storage_file_buckets_init(struct state_backend_storage *storage,
|
||||
const char *path, off_t dev_offset,
|
||||
size_t max_size, uint32_t stridesize)
|
||||
{
|
||||
struct state_backend_storage_bucket *bucket;
|
||||
size_t fd_size = 0;
|
||||
int ret;
|
||||
off_t offset;
|
||||
int nr_copies = 0;
|
||||
|
||||
ret = state_backend_storage_get_size(path, &fd_size);
|
||||
if (ret) {
|
||||
if (ret != -ENOENT) {
|
||||
dev_err(storage->dev, "Failed to get the filesize of '%s', %d\n",
|
||||
path, ret);
|
||||
return ret;
|
||||
}
|
||||
if (!stridesize) {
|
||||
dev_err(storage->dev, "File '%s' does not exist and no information about the needed size. Please specify stridesize\n",
|
||||
path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (max_size)
|
||||
fd_size = min(dev_offset + stridesize * desired_copies,
|
||||
dev_offset + max_size);
|
||||
else
|
||||
fd_size = dev_offset + stridesize * desired_copies;
|
||||
dev_info(storage->dev, "File '%s' does not exist, creating file of size %zd\n",
|
||||
path, fd_size);
|
||||
ret = state_storage_file_create(storage->dev, path, fd_size);
|
||||
if (ret) {
|
||||
dev_info(storage->dev, "Failed to create file '%s', %d\n",
|
||||
path, ret);
|
||||
return ret;
|
||||
}
|
||||
} else if (max_size) {
|
||||
fd_size = min(fd_size, (size_t)dev_offset + max_size);
|
||||
}
|
||||
|
||||
if (!stridesize) {
|
||||
dev_warn(storage->dev, "WARNING, no stridesize given although we use a direct file write. Starting in degraded mode\n");
|
||||
stridesize = fd_size;
|
||||
}
|
||||
|
||||
for (offset = dev_offset; offset < fd_size; offset += stridesize) {
|
||||
size_t maxsize = min((size_t)stridesize,
|
||||
(size_t)(fd_size - offset));
|
||||
|
||||
ret = state_backend_bucket_direct_create(storage->dev, path,
|
||||
&bucket, offset,
|
||||
maxsize);
|
||||
if (ret) {
|
||||
dev_warn(storage->dev, "Failed to create direct bucket at '%s' offset %ld\n",
|
||||
path, offset);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = state_backend_bucket_cached_create(storage->dev, bucket,
|
||||
&bucket);
|
||||
if (ret) {
|
||||
dev_warn(storage->dev, "Failed to setup cache bucket, continuing without cache, %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
list_add_tail(&bucket->bucket_list, &storage->buckets);
|
||||
++nr_copies;
|
||||
if (nr_copies >= desired_copies)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!nr_copies) {
|
||||
dev_err(storage->dev, "Failed to initialize any state direct storage bucket\n");
|
||||
return -EIO;
|
||||
}
|
||||
dev_warn(storage->dev, "Failed to initialize desired amount of direct buckets, only %d of %d succeeded\n",
|
||||
nr_copies, desired_copies);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* state_storage_init - Init backend storage
|
||||
* @param storage Storage object
|
||||
* @param path Path to the backend storage file
|
||||
* @param dev_offset Offset in the device to start writing at.
|
||||
* @param max_size Maximum size of the data. May be 0 for infinite.
|
||||
* @param stridesize Distance between two copies of the data. Not relevant for MTD
|
||||
* @param storagetype Type of the storage backend. This may be NULL where we
|
||||
* autoselect some backwardscompatible backend options
|
||||
* @return 0 on success, -errno otherwise
|
||||
*
|
||||
* Depending on the filetype, we create mtd buckets or normal file buckets.
|
||||
*/
|
||||
int state_storage_init(struct state_backend_storage *storage,
|
||||
struct device_d *dev, const char *path,
|
||||
off_t offset, size_t max_size, uint32_t stridesize,
|
||||
const char *storagetype)
|
||||
{
|
||||
int ret;
|
||||
struct mtd_info_user meminfo;
|
||||
|
||||
INIT_LIST_HEAD(&storage->buckets);
|
||||
storage->dev = dev;
|
||||
storage->name = storagetype;
|
||||
storage->stridesize = stridesize;
|
||||
|
||||
ret = mtd_get_meminfo(path, &meminfo);
|
||||
if (!ret && !(meminfo.flags & MTD_NO_ERASE)) {
|
||||
bool non_circular = false;
|
||||
if (!storagetype) {
|
||||
non_circular = true;
|
||||
} else if (strcmp(storagetype, "circular")) {
|
||||
dev_warn(storage->dev, "Unknown storagetype '%s', falling back to old format circular storage type.\n",
|
||||
storagetype);
|
||||
non_circular = true;
|
||||
}
|
||||
return state_storage_mtd_buckets_init(storage, &meminfo, path,
|
||||
non_circular, offset,
|
||||
max_size);
|
||||
} else {
|
||||
return state_storage_file_buckets_init(storage, path, offset,
|
||||
max_size, stridesize);
|
||||
}
|
||||
|
||||
dev_err(storage->dev, "storage init done\n");
|
||||
}
|
||||
|
||||
void state_storage_set_readonly(struct state_backend_storage *storage)
|
||||
{
|
||||
storage->readonly = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* state_storage_free - Free backend storage
|
||||
* @param storage Storage object
|
||||
*/
|
||||
void state_storage_free(struct state_backend_storage *storage)
|
||||
{
|
||||
struct state_backend_storage_bucket *bucket;
|
||||
struct state_backend_storage_bucket *bucket_tmp;
|
||||
|
||||
if (!storage->buckets.next)
|
||||
return;
|
||||
|
||||
list_for_each_entry_safe(bucket, bucket_tmp, &storage->buckets,
|
||||
bucket_list) {
|
||||
list_del(&bucket->bucket_list);
|
||||
bucket->free(bucket);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,573 @@
|
|||
/*
|
||||
* 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 <asm-generic/ioctl.h>
|
||||
#include <common.h>
|
||||
#include <digest.h>
|
||||
#include <errno.h>
|
||||
#include <fs.h>
|
||||
#include <crc.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
#include <linux/mtd/mtd-abi.h>
|
||||
#include <malloc.h>
|
||||
#include <state.h>
|
||||
#include <libbb.h>
|
||||
|
||||
#include "state.h"
|
||||
|
||||
/* list of all registered state instances */
|
||||
static LIST_HEAD(state_list);
|
||||
|
||||
static struct state *state_new(const char *name)
|
||||
{
|
||||
struct state *state;
|
||||
int ret;
|
||||
|
||||
state = xzalloc(sizeof(*state));
|
||||
safe_strncpy(state->dev.name, name, MAX_DRIVER_NAME);
|
||||
state->name = state->dev.name;
|
||||
state->dev.id = DEVICE_ID_SINGLE;
|
||||
INIT_LIST_HEAD(&state->variables);
|
||||
|
||||
ret = register_device(&state->dev);
|
||||
if (ret) {
|
||||
pr_err("Failed to register state device %s, %d\n", name, ret);
|
||||
free(state);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
state->dirty = 1;
|
||||
dev_add_param_bool(&state->dev, "dirty", NULL, NULL, &state->dirty,
|
||||
NULL);
|
||||
|
||||
list_add_tail(&state->list, &state_list);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static int state_convert_node_variable(struct state *state,
|
||||
struct device_node *node,
|
||||
struct device_node *parent,
|
||||
const char *parent_name,
|
||||
enum state_convert conv)
|
||||
{
|
||||
const struct variable_type *vtype;
|
||||
struct device_node *child;
|
||||
struct device_node *new_node = NULL;
|
||||
struct state_variable *sv;
|
||||
const char *type_name;
|
||||
char *short_name, *name, *indexs;
|
||||
unsigned int start_size[2];
|
||||
int ret;
|
||||
|
||||
/* strip trailing @<ADDRESS> */
|
||||
short_name = xstrdup(node->name);
|
||||
indexs = strchr(short_name, '@');
|
||||
if (indexs)
|
||||
*indexs = 0;
|
||||
|
||||
/* construct full name */
|
||||
name = basprintf("%s%s%s", parent_name, parent_name[0] ? "." : "",
|
||||
short_name);
|
||||
free(short_name);
|
||||
|
||||
if ((conv == STATE_CONVERT_TO_NODE) || (conv == STATE_CONVERT_FIXUP))
|
||||
new_node = of_new_node(parent, node->name);
|
||||
|
||||
for_each_child_of_node(node, child) {
|
||||
ret = state_convert_node_variable(state, child, new_node, name,
|
||||
conv);
|
||||
if (ret)
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
/* parents are allowed to have no type */
|
||||
ret = of_property_read_string(node, "type", &type_name);
|
||||
if (!list_empty(&node->children) && ret == -EINVAL) {
|
||||
if (conv == STATE_CONVERT_FIXUP) {
|
||||
ret = of_property_write_u32(new_node, "#address-cells",
|
||||
1);
|
||||
if (ret)
|
||||
goto out_free;
|
||||
|
||||
ret = of_property_write_u32(new_node, "#size-cells", 1);
|
||||
if (ret)
|
||||
goto out_free;
|
||||
}
|
||||
ret = 0;
|
||||
goto out_free;
|
||||
} else if (ret) {
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
vtype = state_find_type_by_name(type_name);
|
||||
if (!vtype) {
|
||||
ret = -ENOENT;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
if (conv == STATE_CONVERT_FROM_NODE_CREATE) {
|
||||
sv = vtype->create(state, name, node);
|
||||
if (IS_ERR(sv)) {
|
||||
ret = PTR_ERR(sv);
|
||||
dev_err(&state->dev, "failed to create %s: %s\n", name,
|
||||
strerror(-ret));
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32_array(node, "reg", start_size,
|
||||
ARRAY_SIZE(start_size));
|
||||
if (ret) {
|
||||
dev_err(&state->dev, "%s: reg property not found\n",
|
||||
name);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
if (start_size[1] != sv->size) {
|
||||
dev_err(&state->dev,
|
||||
"%s: size mismatch: type=%s(size=%u) size=%u\n",
|
||||
name, type_name, sv->size, start_size[1]);
|
||||
ret = -EOVERFLOW;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
sv->name = name;
|
||||
sv->start = start_size[0];
|
||||
sv->type = vtype->type;
|
||||
state_add_var(state, sv);
|
||||
} else {
|
||||
sv = state_find_var(state, name);
|
||||
if (IS_ERR(sv)) {
|
||||
/* we ignore this error */
|
||||
dev_dbg(&state->dev, "no such variable: %s: %s\n", name,
|
||||
strerror(-ret));
|
||||
ret = 0;
|
||||
goto out_free;
|
||||
}
|
||||
free(name);
|
||||
|
||||
if ((conv == STATE_CONVERT_TO_NODE)
|
||||
|| (conv == STATE_CONVERT_FIXUP)) {
|
||||
ret = of_set_property(new_node, "type",
|
||||
vtype->type_name,
|
||||
strlen(vtype->type_name) + 1, 1);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
start_size[0] = sv->start;
|
||||
start_size[1] = sv->size;
|
||||
ret = of_property_write_u32_array(new_node, "reg",
|
||||
start_size,
|
||||
ARRAY_SIZE
|
||||
(start_size));
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if ((conv == STATE_CONVERT_TO_NODE) || (conv == STATE_CONVERT_FIXUP))
|
||||
ret = vtype->export(sv, new_node, conv);
|
||||
else
|
||||
ret = vtype->import(sv, node);
|
||||
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
return 0;
|
||||
out_free:free(name);
|
||||
out: return ret;
|
||||
}
|
||||
|
||||
struct device_node *state_to_node(struct state *state,
|
||||
struct device_node *parent,
|
||||
enum state_convert conv)
|
||||
{
|
||||
struct device_node *child;
|
||||
struct device_node *root;
|
||||
int ret;
|
||||
|
||||
root = of_new_node(parent, state->root->name);
|
||||
ret = of_property_write_u32(root, "magic", state->magic);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
for_each_child_of_node(state->root, child) {
|
||||
ret = state_convert_node_variable(state, child, root, "", conv);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
return root;
|
||||
out: of_delete_node(root);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
int state_from_node(struct state *state, struct device_node *node, bool create)
|
||||
{
|
||||
struct device_node *child;
|
||||
enum state_convert conv;
|
||||
int ret;
|
||||
uint32_t magic;
|
||||
|
||||
ret = of_property_read_u32(node, "magic", &magic);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (create) {
|
||||
conv = STATE_CONVERT_FROM_NODE_CREATE;
|
||||
state->root = node;
|
||||
state->magic = magic;
|
||||
} else {
|
||||
conv = STATE_CONVERT_FROM_NODE;
|
||||
if (state->magic && state->magic != magic) {
|
||||
dev_err(&state->dev,
|
||||
"invalid magic 0x%08x, should be 0x%08x\n",
|
||||
magic, state->magic);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
for_each_child_of_node(node, child) {
|
||||
ret = state_convert_node_variable(state, child, NULL, "", conv);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* check for overlapping variables */
|
||||
if (create) {
|
||||
const struct state_variable *sv;
|
||||
|
||||
/* start with second entry */
|
||||
sv = list_first_entry(&state->variables, struct state_variable,
|
||||
list);
|
||||
|
||||
list_for_each_entry_continue(sv, &state->variables, list) {
|
||||
const struct state_variable *last_sv;
|
||||
|
||||
last_sv = list_last_entry(&sv->list,
|
||||
struct state_variable, list);
|
||||
if ((last_sv->start + last_sv->size - 1) < sv->start)
|
||||
continue;
|
||||
|
||||
dev_err(&state->dev,
|
||||
"ERROR: Conflicting variable position between: "
|
||||
"%s (0x%02x..0x%02x) and %s (0x%02x..0x%02x)\n",
|
||||
last_sv->name, last_sv->start,
|
||||
last_sv->start + last_sv->size - 1,
|
||||
sv->name, sv->start, sv->start + sv->size - 1);
|
||||
|
||||
ret |= -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int of_state_fixup(struct device_node *root, void *ctx)
|
||||
{
|
||||
struct state *state = ctx;
|
||||
const char *compatible = "barebox,state";
|
||||
struct device_node *new_node, *node, *parent, *backend_node;
|
||||
struct property *p;
|
||||
int ret;
|
||||
phandle phandle;
|
||||
|
||||
node = of_find_node_by_path_from(root, state->root->full_name);
|
||||
if (node) {
|
||||
/* replace existing node - it will be deleted later */
|
||||
parent = node->parent;
|
||||
} else {
|
||||
char *of_path, *c;
|
||||
|
||||
/* look for parent, remove last '/' from path */
|
||||
of_path = xstrdup(state->root->full_name);
|
||||
c = strrchr(of_path, '/');
|
||||
if (!c)
|
||||
return -ENODEV;
|
||||
*c = '0';
|
||||
parent = of_find_node_by_path(of_path);
|
||||
if (!parent)
|
||||
parent = root;
|
||||
|
||||
free(of_path);
|
||||
}
|
||||
|
||||
/* serialize variable definitions */
|
||||
new_node = state_to_node(state, parent, STATE_CONVERT_FIXUP);
|
||||
if (IS_ERR(new_node))
|
||||
return PTR_ERR(new_node);
|
||||
|
||||
/* compatible */
|
||||
p = of_new_property(new_node, "compatible", compatible,
|
||||
strlen(compatible) + 1);
|
||||
if (!p) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* backend-type */
|
||||
if (!state->backend.format) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
p = of_new_property(new_node, "backend-type",
|
||||
state->backend.format->name,
|
||||
strlen(state->backend.format->name) + 1);
|
||||
if (!p) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* backend phandle */
|
||||
backend_node = of_find_node_by_path_from(root, state->backend.of_path);
|
||||
if (!backend_node) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
phandle = of_node_create_phandle(backend_node);
|
||||
ret = of_property_write_u32(new_node, "backend", phandle);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (!strcmp("raw", state->backend.format->name)) {
|
||||
struct digest *digest =
|
||||
state_backend_format_raw_get_digest(state->backend.format);
|
||||
if (digest) {
|
||||
p = of_new_property(new_node, "algo",
|
||||
digest_name(digest),
|
||||
strlen(digest_name(digest)) + 1);
|
||||
if (!p) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state->backend.storage.name) {
|
||||
p = of_new_property(new_node, "backend-storage-type",
|
||||
state->backend.storage.name,
|
||||
strlen(state->backend.storage.name) + 1);
|
||||
if (!p) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (state->backend.storage.stridesize) {
|
||||
ret = of_property_write_u32(new_node, "backend-stridesize",
|
||||
state->backend.storage.stridesize);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* address-cells + size-cells */
|
||||
ret = of_property_write_u32(new_node, "#address-cells", 1);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = of_property_write_u32(new_node, "#size-cells", 1);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* delete existing node */
|
||||
if (node)
|
||||
of_delete_node(node);
|
||||
|
||||
return 0;
|
||||
|
||||
out: of_delete_node(new_node);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void state_release(struct state *state)
|
||||
{
|
||||
of_unregister_fixup(of_state_fixup, state);
|
||||
list_del(&state->list);
|
||||
unregister_device(&state->dev);
|
||||
state_backend_free(&state->backend);
|
||||
free(state);
|
||||
}
|
||||
|
||||
/*
|
||||
* state_new_from_node - create a new state instance from a device_node
|
||||
*
|
||||
* @node The device_node describing the new state instance
|
||||
* @path Path to the backend device. If NULL the path is constructed
|
||||
* using the path in the backend property of the DT.
|
||||
* @offset Offset in the device path. May be 0 to start at the beginning.
|
||||
* @max_size Maximum size of the area used. This may be 0 to use the full
|
||||
* size.
|
||||
* @readonly This is a read-only state. Note that with this option set,
|
||||
* there are no repairs done.
|
||||
*/
|
||||
struct state *state_new_from_node(struct device_node *node, char *path,
|
||||
off_t offset, size_t max_size, bool readonly)
|
||||
{
|
||||
struct state *state;
|
||||
int ret = 0;
|
||||
int len;
|
||||
const char *backend_type;
|
||||
const char *storage_type;
|
||||
const char *of_path;
|
||||
const char *alias;
|
||||
uint32_t stridesize;
|
||||
|
||||
alias = of_alias_get(node);
|
||||
if (!alias)
|
||||
alias = node->name;
|
||||
|
||||
state = state_new(alias);
|
||||
if (IS_ERR(state))
|
||||
return state;
|
||||
|
||||
of_path = of_get_property(node, "backend", &len);
|
||||
if (!of_path) {
|
||||
ret = -ENODEV;
|
||||
goto out_release_state;
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
/* guess if of_path is a path, not a phandle */
|
||||
if (of_path[0] == '/' && len > 1) {
|
||||
ret = of_find_path(node, "backend", &path, 0);
|
||||
} else {
|
||||
struct device_node *partition_node;
|
||||
|
||||
partition_node = of_parse_phandle(node, "backend", 0);
|
||||
if (!partition_node)
|
||||
goto out_release_state;
|
||||
|
||||
of_path = partition_node->full_name;
|
||||
ret = of_find_path_by_node(partition_node, &path, 0);
|
||||
}
|
||||
if (!path) {
|
||||
pr_err("state failed to parse path to backend\n");
|
||||
ret = -EINVAL;
|
||||
goto out_release_state;
|
||||
}
|
||||
}
|
||||
|
||||
ret = of_property_read_string(node, "backend-type", &backend_type);
|
||||
if (ret) {
|
||||
goto out_release_state;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(node, "backend-stridesize", &stridesize);
|
||||
if (ret) {
|
||||
stridesize = 0;
|
||||
}
|
||||
|
||||
ret = of_property_read_string(node, "backend-storage-type",
|
||||
&storage_type);
|
||||
if (ret) {
|
||||
storage_type = NULL;
|
||||
pr_info("No backend-storage-type found, using default.\n");
|
||||
}
|
||||
|
||||
ret = state_backend_init(&state->backend, &state->dev, node,
|
||||
backend_type, path, alias, of_path, offset,
|
||||
max_size, stridesize, storage_type);
|
||||
if (ret)
|
||||
goto out_release_state;
|
||||
|
||||
if (readonly)
|
||||
state_backend_set_readonly(&state->backend);
|
||||
|
||||
ret = state_from_node(state, node, 1);
|
||||
if (ret) {
|
||||
goto out_release_state;
|
||||
}
|
||||
|
||||
ret = of_register_fixup(of_state_fixup, state);
|
||||
if (ret) {
|
||||
goto out_release_state;
|
||||
}
|
||||
|
||||
ret = state_load(state);
|
||||
if (ret) {
|
||||
pr_warn("Failed to load persistent state, continuing with defaults, %d\n", ret);
|
||||
}
|
||||
|
||||
pr_info("New state registered '%s'\n", alias);
|
||||
|
||||
return state;
|
||||
|
||||
out_release_state:
|
||||
state_release(state);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* state_by_name - find a state instance by name
|
||||
*
|
||||
* @name The name of the state instance
|
||||
*/
|
||||
struct state *state_by_name(const char *name)
|
||||
{
|
||||
struct state *state;
|
||||
|
||||
list_for_each_entry(state, &state_list, list) {
|
||||
if (!strcmp(name, state->name))
|
||||
return state;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* state_by_node - find a state instance by of node
|
||||
*
|
||||
* @node The of node of the state intance
|
||||
*/
|
||||
struct state *state_by_node(const struct device_node *node)
|
||||
{
|
||||
struct state *state;
|
||||
|
||||
list_for_each_entry(state, &state_list, list) {
|
||||
if (state->root == node)
|
||||
return state;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int state_get_name(const struct state *state, char const **name)
|
||||
{
|
||||
*name = xstrdup(state->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void state_info(void)
|
||||
{
|
||||
struct state *state;
|
||||
|
||||
printf("registered state instances:\n");
|
||||
|
||||
list_for_each_entry(state, &state_list, list) {
|
||||
printf("%-20s ", state->name);
|
||||
if (state->backend.format)
|
||||
printf("(backend: %s, path: %s)\n",
|
||||
state->backend.format->name,
|
||||
state->backend.of_path);
|
||||
else
|
||||
printf("(no backend)\n");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
#include <linux/types.h>
|
||||
#include <linux/list.h>
|
||||
#include <driver.h>
|
||||
|
||||
struct state;
|
||||
struct mtd_info_user;
|
||||
|
||||
/**
|
||||
* state_backend_storage_bucket - This class describes a single backend storage
|
||||
* object copy
|
||||
*
|
||||
* @init Optional, initiates the given bucket
|
||||
* @write Required, writes the given data to the storage in any form. Returns 0
|
||||
* on success
|
||||
* @read Required, reads the last successfully written data from the backend
|
||||
* storage. Returns 0 on success and allocates a matching memory area to buf.
|
||||
* len_hint can be a hint of the storage format how large the data to be read
|
||||
* is. After the operation len_hint contains the size of the allocated buffer.
|
||||
* @free Required, Frees all internally used memory
|
||||
* @bucket_list A list element struct to attach this bucket to a list
|
||||
*/
|
||||
struct state_backend_storage_bucket {
|
||||
int (*init) (struct state_backend_storage_bucket * bucket);
|
||||
int (*write) (struct state_backend_storage_bucket * bucket,
|
||||
const uint8_t * buf, ssize_t len);
|
||||
int (*read) (struct state_backend_storage_bucket * bucket,
|
||||
uint8_t ** buf, ssize_t * len_hint);
|
||||
void (*free) (struct state_backend_storage_bucket * bucket);
|
||||
|
||||
bool initialized;
|
||||
struct list_head bucket_list;
|
||||
};
|
||||
|
||||
/**
|
||||
* state_backend_format - This class describes a data format.
|
||||
*
|
||||
* @verify Required, Verifies the validity of the given data. The buffer that is
|
||||
* passed into this function may be larger than the actual data in the buffer.
|
||||
* The magic is supplied by the state to verify that this is an expected state
|
||||
* entity. The function should return 0 on success or a negative errno otherwise.
|
||||
* @pack Required, Packs data from the given state into a newly created buffer.
|
||||
* The buffer and its length are stored in the given argument pointers. Returns
|
||||
* 0 on success, -errno otherwise.
|
||||
* @unpack Required, Unpacks the data from the given buffer into the state. Do
|
||||
* not free the buffer.
|
||||
* @free Optional, Frees all allocated memory and structures.
|
||||
* @name Name of this backend.
|
||||
*/
|
||||
struct state_backend_format {
|
||||
int (*verify) (struct state_backend_format * format, uint32_t magic,
|
||||
const uint8_t * buf, ssize_t len);
|
||||
int (*pack) (struct state_backend_format * format, struct state * state,
|
||||
uint8_t ** buf, ssize_t * len);
|
||||
int (*unpack) (struct state_backend_format * format,
|
||||
struct state * state, const uint8_t * buf, ssize_t len);
|
||||
ssize_t(*get_packed_len) (struct state_backend_format * format,
|
||||
struct state * state);
|
||||
void (*free) (struct state_backend_format * format);
|
||||
const char *name;
|
||||
};
|
||||
|
||||
/**
|
||||
* state_backend_storage - Storage backend of the state.
|
||||
*
|
||||
* @buckets List of storage buckets that are available
|
||||
*/
|
||||
struct state_backend_storage {
|
||||
struct list_head buckets;
|
||||
|
||||
/* For outputs */
|
||||
struct device_d *dev;
|
||||
|
||||
const char *name;
|
||||
|
||||
uint32_t stridesize;
|
||||
|
||||
bool readonly;
|
||||
};
|
||||
|
||||
/**
|
||||
* state_backend - State Backend object
|
||||
*
|
||||
* @format Backend format object
|
||||
* @storage Backend storage object
|
||||
* @of_path Path to the DT node
|
||||
*/
|
||||
struct state_backend {
|
||||
struct state_backend_format *format;
|
||||
struct state_backend_storage storage;
|
||||
const char *of_path;
|
||||
};
|
||||
|
||||
struct state {
|
||||
struct list_head list; /* Entry to enqueue on list of states */
|
||||
|
||||
struct device_d dev;
|
||||
struct device_node *root;
|
||||
const char *name;
|
||||
uint32_t magic;
|
||||
|
||||
struct list_head variables; /* Sorted list of variables */
|
||||
unsigned int dirty;
|
||||
|
||||
struct state_backend backend;
|
||||
};
|
||||
|
||||
enum state_convert {
|
||||
STATE_CONVERT_FROM_NODE,
|
||||
STATE_CONVERT_FROM_NODE_CREATE,
|
||||
STATE_CONVERT_TO_NODE,
|
||||
STATE_CONVERT_FIXUP,
|
||||
};
|
||||
|
||||
enum state_variable_type {
|
||||
STATE_TYPE_INVALID = 0,
|
||||
STATE_TYPE_ENUM,
|
||||
STATE_TYPE_U8,
|
||||
STATE_TYPE_U32,
|
||||
STATE_TYPE_MAC,
|
||||
STATE_TYPE_STRING,
|
||||
};
|
||||
|
||||
struct state_variable;
|
||||
|
||||
/* A variable type (uint32, enum32) */
|
||||
struct variable_type {
|
||||
enum state_variable_type type;
|
||||
const char *type_name;
|
||||
struct list_head list;
|
||||
int (*export) (struct state_variable *, struct device_node *,
|
||||
enum state_convert);
|
||||
int (*import) (struct state_variable *, struct device_node *);
|
||||
struct state_variable *(*create) (struct state * state,
|
||||
const char *name,
|
||||
struct device_node *);
|
||||
};
|
||||
|
||||
/* instance of a single variable */
|
||||
struct state_variable {
|
||||
enum state_variable_type type;
|
||||
struct list_head list;
|
||||
const char *name;
|
||||
unsigned int start;
|
||||
unsigned int size;
|
||||
void *raw;
|
||||
};
|
||||
|
||||
/*
|
||||
* uint32
|
||||
*/
|
||||
struct state_uint32 {
|
||||
struct state_variable var;
|
||||
struct param_d *param;
|
||||
struct state *state;
|
||||
uint32_t value;
|
||||
uint32_t value_default;
|
||||
};
|
||||
|
||||
/*
|
||||
* enum32
|
||||
*/
|
||||
struct state_enum32 {
|
||||
struct state_variable var;
|
||||
struct param_d *param;
|
||||
uint32_t value;
|
||||
uint32_t value_default;
|
||||
const char **names;
|
||||
int num_names;
|
||||
};
|
||||
|
||||
/*
|
||||
* MAC address
|
||||
*/
|
||||
struct state_mac {
|
||||
struct state_variable var;
|
||||
struct param_d *param;
|
||||
uint8_t value[6];
|
||||
uint8_t value_default[6];
|
||||
};
|
||||
|
||||
/*
|
||||
* string
|
||||
*/
|
||||
struct state_string {
|
||||
struct state_variable var;
|
||||
struct param_d *param;
|
||||
struct state *state;
|
||||
char *value;
|
||||
const char *value_default;
|
||||
char raw[];
|
||||
};
|
||||
|
||||
int state_set_dirty(struct param_d *p, void *priv);
|
||||
int state_from_node(struct state *state, struct device_node *node, bool create);
|
||||
struct device_node *state_to_node(struct state *state,
|
||||
struct device_node *parent,
|
||||
enum state_convert conv);
|
||||
int backend_format_raw_create(struct state_backend_format **format,
|
||||
struct device_node *node, const char *secret_name,
|
||||
struct device_d *dev);
|
||||
int backend_format_dtb_create(struct state_backend_format **format,
|
||||
struct device_d *dev);
|
||||
int state_storage_init(struct state_backend_storage *storage,
|
||||
struct device_d *dev, const char *path,
|
||||
off_t offset, size_t max_size, uint32_t stridesize,
|
||||
const char *storagetype);
|
||||
void state_storage_set_readonly(struct state_backend_storage *storage);
|
||||
void state_add_var(struct state *state, struct state_variable *var);
|
||||
struct variable_type *state_find_type_by_name(const char *name);
|
||||
int state_backend_bucket_circular_create(struct device_d *dev, const char *path,
|
||||
struct state_backend_storage_bucket **bucket,
|
||||
unsigned int eraseblock,
|
||||
ssize_t writesize,
|
||||
struct mtd_info_user *mtd_uinfo,
|
||||
bool lazy_init);
|
||||
int state_backend_bucket_cached_create(struct device_d *dev,
|
||||
struct state_backend_storage_bucket *raw,
|
||||
struct state_backend_storage_bucket **out);
|
||||
struct state_variable *state_find_var(struct state *state, const char *name);
|
||||
struct digest *state_backend_format_raw_get_digest(struct state_backend_format
|
||||
*format);
|
||||
int state_backend_init(struct state_backend *backend, struct device_d *dev,
|
||||
struct device_node *node, const char *backend_format,
|
||||
const char *storage_path, const char *state_name, const
|
||||
char *of_path, off_t offset, size_t max_size,
|
||||
uint32_t stridesize, const char *storagetype);
|
||||
void state_backend_set_readonly(struct state_backend *backend);
|
||||
void state_backend_free(struct state_backend *backend);
|
||||
void state_storage_free(struct state_backend_storage *storage);
|
||||
int state_backend_bucket_direct_create(struct device_d *dev, const char *path,
|
||||
struct state_backend_storage_bucket **bucket,
|
||||
off_t offset, ssize_t max_size);
|
||||
int state_storage_write(struct state_backend_storage *storage,
|
||||
const uint8_t * buf, ssize_t len);
|
||||
int state_storage_restore_consistency(struct state_backend_storage
|
||||
*storage, const uint8_t * buf,
|
||||
ssize_t len);
|
||||
int state_storage_read(struct state_backend_storage *storage,
|
||||
struct state_backend_format *format,
|
||||
uint32_t magic, uint8_t **buf, ssize_t *len,
|
||||
ssize_t len_hint);
|
||||
|
||||
static inline struct state_uint32 *to_state_uint32(struct state_variable *s)
|
||||
{
|
||||
return container_of(s, struct state_uint32, var);
|
||||
}
|
||||
|
||||
static inline struct state_enum32 *to_state_enum32(struct state_variable *s)
|
||||
{
|
||||
return container_of(s, struct state_enum32, var);
|
||||
}
|
||||
|
||||
static inline struct state_mac *to_state_mac(struct state_variable *s)
|
||||
{
|
||||
return container_of(s, struct state_mac, var);
|
||||
}
|
||||
|
||||
static inline struct state_string *to_state_string(struct state_variable *s)
|
||||
{
|
||||
return container_of(s, struct state_string, var);
|
||||
}
|
||||
|
||||
static inline int state_string_copy_to_raw(struct state_string *string,
|
||||
const char *src)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
len = strlen(src);
|
||||
if (len > string->var.size)
|
||||
return -EILSEQ;
|
||||
|
||||
/* copy string and clear remaining contents of buffer */
|
||||
memcpy(string->raw, src, len);
|
||||
memset(string->raw + len, 0x0, string->var.size - len);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,493 @@
|
|||
/*
|
||||
* 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 <linux/err.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <malloc.h>
|
||||
#include <net.h>
|
||||
#include <printk.h>
|
||||
#include <of.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "state.h"
|
||||
|
||||
/**
|
||||
* state_set_dirty - Helper function to set the state to dirty. Only used for
|
||||
* state variables callbacks
|
||||
* @param p
|
||||
* @param priv
|
||||
* @return
|
||||
*/
|
||||
int state_set_dirty(struct param_d *p, void *priv)
|
||||
{
|
||||
struct state *state = priv;
|
||||
|
||||
state->dirty = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int state_var_compare(struct list_head *a, struct list_head *b)
|
||||
{
|
||||
struct state_variable *va = list_entry(a, struct state_variable, list);
|
||||
struct state_variable *vb = list_entry(b, struct state_variable, list);
|
||||
|
||||
return va->start < vb->start ? -1 : 1;
|
||||
}
|
||||
|
||||
void state_add_var(struct state *state, struct state_variable *var)
|
||||
{
|
||||
list_add_sort(&var->list, &state->variables, state_var_compare);
|
||||
}
|
||||
|
||||
static int state_uint32_export(struct state_variable *var,
|
||||
struct device_node *node,
|
||||
enum state_convert conv)
|
||||
{
|
||||
struct state_uint32 *su32 = to_state_uint32(var);
|
||||
int ret;
|
||||
|
||||
if (su32->value_default) {
|
||||
ret = of_property_write_u32(node, "default",
|
||||
su32->value_default);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (conv == STATE_CONVERT_FIXUP)
|
||||
return 0;
|
||||
|
||||
return of_property_write_u32(node, "value", su32->value);
|
||||
}
|
||||
|
||||
static int state_uint32_import(struct state_variable *sv,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct state_uint32 *su32 = to_state_uint32(sv);
|
||||
|
||||
of_property_read_u32(node, "default", &su32->value_default);
|
||||
if (of_property_read_u32(node, "value", &su32->value))
|
||||
su32->value = su32->value_default;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int state_uint8_set(struct param_d *p, void *priv)
|
||||
{
|
||||
struct state_uint32 *su32 = priv;
|
||||
struct state *state = su32->state;
|
||||
|
||||
if (su32->value > 255)
|
||||
return -ERANGE;
|
||||
|
||||
return state_set_dirty(p, state);
|
||||
}
|
||||
|
||||
static struct state_variable *state_uint8_create(struct state *state,
|
||||
const char *name,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct state_uint32 *su32;
|
||||
struct param_d *param;
|
||||
|
||||
su32 = xzalloc(sizeof(*su32));
|
||||
|
||||
param = dev_add_param_int(&state->dev, name, state_uint8_set,
|
||||
NULL, &su32->value, "%u", su32);
|
||||
if (IS_ERR(param)) {
|
||||
free(su32);
|
||||
return ERR_CAST(param);
|
||||
}
|
||||
|
||||
su32->param = param;
|
||||
su32->var.size = sizeof(uint8_t);
|
||||
#ifdef __LITTLE_ENDIAN
|
||||
su32->var.raw = &su32->value;
|
||||
#else
|
||||
su32->var.raw = &su32->value + 3;
|
||||
#endif
|
||||
su32->state = state;
|
||||
|
||||
return &su32->var;
|
||||
}
|
||||
|
||||
static struct state_variable *state_uint32_create(struct state *state,
|
||||
const char *name,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct state_uint32 *su32;
|
||||
struct param_d *param;
|
||||
|
||||
su32 = xzalloc(sizeof(*su32));
|
||||
|
||||
param = dev_add_param_int(&state->dev, name, state_set_dirty,
|
||||
NULL, &su32->value, "%u", state);
|
||||
if (IS_ERR(param)) {
|
||||
free(su32);
|
||||
return ERR_CAST(param);
|
||||
}
|
||||
|
||||
su32->param = param;
|
||||
su32->var.size = sizeof(uint32_t);
|
||||
su32->var.raw = &su32->value;
|
||||
|
||||
return &su32->var;
|
||||
}
|
||||
|
||||
static int state_enum32_export(struct state_variable *var,
|
||||
struct device_node *node,
|
||||
enum state_convert conv)
|
||||
{
|
||||
struct state_enum32 *enum32 = to_state_enum32(var);
|
||||
int ret, i, len;
|
||||
char *prop, *str;
|
||||
|
||||
if (enum32->value_default) {
|
||||
ret = of_property_write_u32(node, "default",
|
||||
enum32->value_default);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
len = 0;
|
||||
|
||||
for (i = 0; i < enum32->num_names; i++)
|
||||
len += strlen(enum32->names[i]) + 1;
|
||||
|
||||
prop = xzalloc(len);
|
||||
str = prop;
|
||||
|
||||
for (i = 0; i < enum32->num_names; i++)
|
||||
str += sprintf(str, "%s", enum32->names[i]) + 1;
|
||||
|
||||
ret = of_set_property(node, "names", prop, len, 1);
|
||||
|
||||
free(prop);
|
||||
|
||||
if (conv == STATE_CONVERT_FIXUP)
|
||||
return 0;
|
||||
|
||||
ret = of_property_write_u32(node, "value", enum32->value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int state_enum32_import(struct state_variable *sv,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct state_enum32 *enum32 = to_state_enum32(sv);
|
||||
int len;
|
||||
const __be32 *value, *value_default;
|
||||
|
||||
value = of_get_property(node, "value", &len);
|
||||
if (value && len != sizeof(uint32_t))
|
||||
return -EINVAL;
|
||||
|
||||
value_default = of_get_property(node, "default", &len);
|
||||
if (value_default && len != sizeof(uint32_t))
|
||||
return -EINVAL;
|
||||
|
||||
if (value_default)
|
||||
enum32->value_default = be32_to_cpu(*value_default);
|
||||
if (value)
|
||||
enum32->value = be32_to_cpu(*value);
|
||||
else
|
||||
enum32->value = enum32->value_default;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct state_variable *state_enum32_create(struct state *state,
|
||||
const char *name,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct state_enum32 *enum32;
|
||||
int ret, i, num_names;
|
||||
|
||||
enum32 = xzalloc(sizeof(*enum32));
|
||||
|
||||
num_names = of_property_count_strings(node, "names");
|
||||
if (num_names < 0) {
|
||||
dev_err(&state->dev,
|
||||
"enum32 node without \"names\" property\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
enum32->names = xzalloc(sizeof(char *) * num_names);
|
||||
enum32->num_names = num_names;
|
||||
enum32->var.size = sizeof(uint32_t);
|
||||
enum32->var.raw = &enum32->value;
|
||||
|
||||
for (i = 0; i < num_names; i++) {
|
||||
const char *name;
|
||||
|
||||
ret = of_property_read_string_index(node, "names", i, &name);
|
||||
if (ret)
|
||||
goto out;
|
||||
enum32->names[i] = xstrdup(name);
|
||||
}
|
||||
|
||||
enum32->param = dev_add_param_enum(&state->dev, name, state_set_dirty,
|
||||
NULL, &enum32->value, enum32->names,
|
||||
num_names, state);
|
||||
if (IS_ERR(enum32->param)) {
|
||||
ret = PTR_ERR(enum32->param);
|
||||
goto out;
|
||||
}
|
||||
|
||||
return &enum32->var;
|
||||
out: for (i--; i >= 0; i--)
|
||||
free((char *)enum32->names[i]);
|
||||
free(enum32->names);
|
||||
free(enum32);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static int state_mac_export(struct state_variable *var,
|
||||
struct device_node *node, enum state_convert conv)
|
||||
{
|
||||
struct state_mac *mac = to_state_mac(var);
|
||||
int ret;
|
||||
|
||||
if (!is_zero_ether_addr(mac->value_default)) {
|
||||
ret = of_property_write_u8_array(node, "default",
|
||||
mac->value_default,
|
||||
ARRAY_SIZE(mac->
|
||||
value_default));
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (conv == STATE_CONVERT_FIXUP)
|
||||
return 0;
|
||||
|
||||
return of_property_write_u8_array(node, "value", mac->value,
|
||||
ARRAY_SIZE(mac->value));
|
||||
}
|
||||
|
||||
static int state_mac_import(struct state_variable *sv, struct device_node *node)
|
||||
{
|
||||
struct state_mac *mac = to_state_mac(sv);
|
||||
|
||||
of_property_read_u8_array(node, "default", mac->value_default,
|
||||
ARRAY_SIZE(mac->value_default));
|
||||
if (of_property_read_u8_array(node, "value", mac->value,
|
||||
ARRAY_SIZE(mac->value)))
|
||||
memcpy(mac->value, mac->value_default, ARRAY_SIZE(mac->value));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct state_variable *state_mac_create(struct state *state,
|
||||
const char *name,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct state_mac *mac;
|
||||
int ret;
|
||||
|
||||
mac = xzalloc(sizeof(*mac));
|
||||
|
||||
mac->var.size = ARRAY_SIZE(mac->value);
|
||||
mac->var.raw = mac->value;
|
||||
|
||||
mac->param = dev_add_param_mac(&state->dev, name, state_set_dirty,
|
||||
NULL, mac->value, state);
|
||||
if (IS_ERR(mac->param)) {
|
||||
ret = PTR_ERR(mac->param);
|
||||
goto out;
|
||||
}
|
||||
|
||||
return &mac->var;
|
||||
out: free(mac);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static int state_string_export(struct state_variable *var,
|
||||
struct device_node *node,
|
||||
enum state_convert conv)
|
||||
{
|
||||
struct state_string *string = to_state_string(var);
|
||||
int ret = 0;
|
||||
|
||||
if (string->value_default) {
|
||||
ret = of_set_property(node, "default", string->value_default,
|
||||
strlen(string->value_default) + 1, 1);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (conv == STATE_CONVERT_FIXUP)
|
||||
return 0;
|
||||
|
||||
if (string->value)
|
||||
ret = of_set_property(node, "value", string->value,
|
||||
strlen(string->value) + 1, 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int state_string_import(struct state_variable *sv,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct state_string *string = to_state_string(sv);
|
||||
const char *value = NULL;
|
||||
size_t len;
|
||||
int ret;
|
||||
|
||||
of_property_read_string(node, "default", &string->value_default);
|
||||
if (string->value_default) {
|
||||
len = strlen(string->value_default);
|
||||
if (len > string->var.size)
|
||||
return -EILSEQ;
|
||||
}
|
||||
|
||||
ret = of_property_read_string(node, "value", &value);
|
||||
if (ret)
|
||||
value = string->value_default;
|
||||
|
||||
if (value)
|
||||
return state_string_copy_to_raw(string, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int state_string_set(struct param_d *p, void *priv)
|
||||
{
|
||||
struct state_string *string = priv;
|
||||
struct state *state = string->state;
|
||||
int ret;
|
||||
|
||||
ret = state_string_copy_to_raw(string, string->value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return state_set_dirty(p, state);
|
||||
}
|
||||
|
||||
static int state_string_get(struct param_d *p, void *priv)
|
||||
{
|
||||
struct state_string *string = priv;
|
||||
|
||||
free(string->value);
|
||||
if (string->raw[0])
|
||||
string->value = xstrndup(string->raw, string->var.size);
|
||||
else
|
||||
string->value = xstrdup("");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct state_variable *state_string_create(struct state *state,
|
||||
const char *name,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct state_string *string;
|
||||
uint32_t start_size[2];
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_u32_array(node, "reg", start_size,
|
||||
ARRAY_SIZE(start_size));
|
||||
if (ret) {
|
||||
dev_err(&state->dev, "%s: reg property not found\n", name);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/* limit to arbitrary len of 4k */
|
||||
if (start_size[1] > 4096)
|
||||
return ERR_PTR(-EILSEQ);
|
||||
|
||||
string = xzalloc(sizeof(*string) + start_size[1]);
|
||||
string->var.size = start_size[1];
|
||||
string->var.raw = &string->raw;
|
||||
string->state = state;
|
||||
|
||||
string->param = dev_add_param_string(&state->dev, name,
|
||||
state_string_set, state_string_get,
|
||||
&string->value, string);
|
||||
if (IS_ERR(string->param)) {
|
||||
ret = PTR_ERR(string->param);
|
||||
goto out;
|
||||
}
|
||||
|
||||
return &string->var;
|
||||
out: free(string);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static struct variable_type types[] = {
|
||||
{
|
||||
.type = STATE_TYPE_U8,
|
||||
.type_name = "uint8",
|
||||
.export = state_uint32_export,
|
||||
.import = state_uint32_import,
|
||||
.create = state_uint8_create,
|
||||
}, {
|
||||
.type = STATE_TYPE_U32,
|
||||
.type_name = "uint32",
|
||||
.export = state_uint32_export,
|
||||
.import = state_uint32_import,
|
||||
.create = state_uint32_create,
|
||||
}, {
|
||||
.type = STATE_TYPE_ENUM,
|
||||
.type_name = "enum32",
|
||||
.export = state_enum32_export,
|
||||
.import = state_enum32_import,
|
||||
.create = state_enum32_create,
|
||||
}, {
|
||||
.type = STATE_TYPE_MAC,
|
||||
.type_name = "mac",
|
||||
.export = state_mac_export,
|
||||
.import = state_mac_import,
|
||||
.create = state_mac_create,
|
||||
}, {
|
||||
.type = STATE_TYPE_STRING,
|
||||
.type_name = "string",
|
||||
.export = state_string_export,
|
||||
.import = state_string_import,
|
||||
.create = state_string_create,
|
||||
}
|
||||
};
|
||||
|
||||
struct variable_type *state_find_type_by_name(const char *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(types); i++) {
|
||||
if (!strcmp(name, types[i].type_name)) {
|
||||
return &types[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct state_variable *state_find_var(struct state *state, const char *name)
|
||||
{
|
||||
struct state_variable *sv;
|
||||
|
||||
list_for_each_entry(sv, &state->variables, list) {
|
||||
if (!strcmp(sv->name, name))
|
||||
return sv;
|
||||
}
|
||||
|
||||
return ERR_PTR(-ENOENT);
|
||||
}
|
|
@ -24,75 +24,14 @@
|
|||
static int state_probe(struct device_d *dev)
|
||||
{
|
||||
struct device_node *np = dev->device_node;
|
||||
struct device_node *partition_node;
|
||||
struct state *state;
|
||||
const char *alias;
|
||||
const char *backend_type = NULL;
|
||||
int len, ret;
|
||||
const char *of_path;
|
||||
char *path;
|
||||
bool readonly = false;
|
||||
|
||||
if (!np)
|
||||
return -EINVAL;
|
||||
|
||||
alias = of_alias_get(np);
|
||||
if (!alias)
|
||||
alias = np->name;
|
||||
|
||||
state = state_new_from_node(alias, np);
|
||||
state = state_new_from_node(np, NULL, 0, 0, readonly);
|
||||
if (IS_ERR(state))
|
||||
return PTR_ERR(state);
|
||||
|
||||
of_path = of_get_property(np, "backend", &len);
|
||||
if (!of_path) {
|
||||
ret = -ENODEV;
|
||||
goto out_release;
|
||||
}
|
||||
|
||||
/* guess if of_path is a path, not a phandle */
|
||||
if (of_path[0] == '/' && len > 1) {
|
||||
ret = of_find_path(np, "backend", &path, 0);
|
||||
} else {
|
||||
|
||||
partition_node = of_parse_phandle(np, "backend", 0);
|
||||
if (!partition_node)
|
||||
return -EINVAL;
|
||||
|
||||
of_path = partition_node->full_name;
|
||||
ret = of_find_path_by_node(partition_node, &path, 0);
|
||||
}
|
||||
|
||||
if (ret == -ENODEV)
|
||||
ret = -EPROBE_DEFER;
|
||||
if (ret)
|
||||
goto out_release;
|
||||
|
||||
ret = of_property_read_string(np, "backend-type", &backend_type);
|
||||
if (ret) {
|
||||
goto out_free;
|
||||
} else if (!strcmp(backend_type, "raw")) {
|
||||
ret = state_backend_raw_file(state, of_path, path, 0, 0);
|
||||
} else if (!strcmp(backend_type, "dtb")) {
|
||||
ret = state_backend_dtb_file(state, of_path, path);
|
||||
} else {
|
||||
dev_warn(dev, "invalid backend type: %s\n", backend_type);
|
||||
ret = -ENODEV;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
goto out_free;
|
||||
|
||||
dev_info(dev, "backend: %s, path: %s, of_path: %s\n", backend_type, path, of_path);
|
||||
free(path);
|
||||
|
||||
return 0;
|
||||
|
||||
out_free:
|
||||
free(path);
|
||||
out_release:
|
||||
state_release(state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static __maybe_unused struct of_device_id state_ids[] = {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef __LIBFILE_H
|
||||
#define __LIBFILE_H
|
||||
|
||||
int write_full(int fd, void *buf, size_t size);
|
||||
int write_full(int fd, const void *buf, size_t size);
|
||||
int read_full(int fd, void *buf, size_t size);
|
||||
|
||||
char *read_file_line(const char *fmt, ...);
|
||||
|
|
|
@ -10,13 +10,15 @@ int state_backend_dtb_file(struct state *state, const char *of_path,
|
|||
int state_backend_raw_file(struct state *state, const char *of_path,
|
||||
const char *path, off_t offset, size_t size);
|
||||
|
||||
struct state *state_new_from_node(const char *name, struct device_node *node);
|
||||
struct state *state_new_from_node(struct device_node *node, char *path,
|
||||
off_t offset, size_t max_size, bool readonly);
|
||||
void state_release(struct state *state);
|
||||
|
||||
struct state *state_by_name(const char *name);
|
||||
struct state *state_by_node(const struct device_node *node);
|
||||
int state_get_name(const struct state *state, char const **name);
|
||||
|
||||
int state_load(struct state *state);
|
||||
int state_save(struct state *state);
|
||||
void state_info(void);
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
* Like write, but guarantees to write the full buffer out, else
|
||||
* it returns with an error.
|
||||
*/
|
||||
int write_full(int fd, void *buf, size_t size)
|
||||
int write_full(int fd, const void *buf, size_t size)
|
||||
{
|
||||
size_t insize = size;
|
||||
int now;
|
||||
|
|
Loading…
Reference in New Issue