summaryrefslogtreecommitdiffstats
path: root/common/state/backend_storage.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/state/backend_storage.c')
-rw-r--r--common/state/backend_storage.c456
1 files changed, 175 insertions, 281 deletions
diff --git a/common/state/backend_storage.c b/common/state/backend_storage.c
index 5dc8c50267..9ed6ad79ac 100644
--- a/common/state/backend_storage.c
+++ b/common/state/backend_storage.c
@@ -25,24 +25,29 @@
#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;
+/*
+ * The state framework stores data in so called buckets. A bucket is
+ * exactly one copy of the state we want to store. On flash type media
+ * a bucket corresponds to a single eraseblock. On media which do not
+ * need an erase operation a bucket corresponds to a storage area of
+ * @stridesize bytes.
+ *
+ * For redundancy and to make sure that we have valid data on the storage
+ * device at any time the state framework stores multiple buckets. The strategy
+ * is as follows:
+ *
+ * When loading the state from the storage we iterate over the buckets. We
+ * take the first one we find which has valid crcs. The next step is to
+ * restore consistency between the different buckets. This means rewriting
+ * a bucket when it signalled it needs refresh (i.e. returned -EUCLEAN)
+ * or when contains data different from the bucket we use.
+ *
+ * When the state backend initialized successfully we already restored
+ * consistency which means all buckets contain the same data. This means
+ * when storing a new state we can just write all buckets in order.
+ */
- return 0;
-}
+static const unsigned int min_buckets_written = 1;
/**
* state_storage_write - Writes the given data to the storage
@@ -55,60 +60,64 @@ static int bucket_lazy_init(struct state_backend_storage_bucket *bucket)
* 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
+ * We try to at least write min_buckets_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)
+ const void * buf, ssize_t len)
{
struct state_backend_storage_bucket *bucket;
int ret;
- int copies_written = 0;
+ int buckets_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;
+ ++buckets_written;
}
}
- if (copies_written >= min_copies_written)
+ if (buckets_written >= min_buckets_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);
+ min_buckets_written, buckets_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)
+static int bucket_refresh(struct state_backend_storage *storage,
+ struct state_backend_storage_bucket *bucket, void *buf, ssize_t len)
{
- return state_storage_write(storage, buf, len);
+ int ret;
+
+ if (bucket->needs_refresh)
+ goto refresh;
+
+ if (bucket->len != len)
+ goto refresh;
+
+ if (memcmp(bucket->buf, buf, len))
+ goto refresh;
+
+ return 0;
+
+refresh:
+ ret = bucket->write(bucket, buf, len);
+
+ if (ret)
+ dev_warn(storage->dev, "Failed to restore bucket %d@0x%08lx\n",
+ bucket->num, bucket->offset);
+ else
+ dev_info(storage->dev, "restored bucket %d@0x%08lx\n",
+ bucket->num, bucket->offset);
+
+ return ret;
}
/**
@@ -118,7 +127,7 @@ int state_storage_restore_consistency(struct state_backend_storage *storage,
* @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.
+ * @param flags flags controlling how to load state
* @return 0 on success, -errno otherwise. buf and len will be set to valid
* values on success.
*
@@ -129,43 +138,65 @@ int state_storage_restore_consistency(struct state_backend_storage *storage,
*/
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)
+ uint32_t magic, void **buf, ssize_t *len,
+ enum state_flags flags)
{
- struct state_backend_storage_bucket *bucket;
+ struct state_backend_storage_bucket *bucket, *bucket_used = NULL;
int ret;
+ /*
+ * Iterate over all buckets. The first valid one we find is the
+ * one we want to use.
+ */
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);
+ ret = bucket->read(bucket, &bucket->buf, &bucket->len);
+ if (ret == -EUCLEAN)
+ bucket->needs_refresh = 1;
+ else if (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);
+ /*
+ * Verify the buffer crcs. The buffer length is passed in the len argument,
+ * .verify overwrites it with the length actually used.
+ */
+ ret = format->verify(format, magic, bucket->buf, &bucket->len, flags);
+ if (!ret && !bucket_used)
+ bucket_used = bucket;
+ }
+
+ if (!bucket_used) {
+ dev_err(storage->dev, "Failed to find any valid state copy in any bucket\n");
+
+ return -ENOENT;
+ }
+
+ dev_info(storage->dev, "Using bucket %d@0x%08lx\n", bucket_used->num, bucket_used->offset);
+
+ /*
+ * Restore/refresh all buckets except the one we currently use (in case
+ * it's the only usable bucket at the moment)
+ */
+ list_for_each_entry(bucket, &storage->buckets, bucket_list) {
+ if (bucket == bucket_used)
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);
+
+ ret = bucket_refresh(storage, bucket, bucket_used->buf, bucket_used->len);
+
+ /* Free buffer from the unused buckets */
+ free(bucket->buf);
+ bucket->buf = NULL;
}
- dev_err(storage->dev, "Failed to find any valid state copy in any bucket\n");
+ /*
+ * Restore/refresh the bucket we currently use
+ */
+ ret = bucket_refresh(storage, bucket_used, bucket_used->buf, bucket_used->len);
- return -ENOENT;
+ *buf = bucket_used->buf;
+ *len = bucket_used->len;
-found:
- /* A failed restore consistency is not a failure of reading the state */
- state_storage_restore_consistency(storage, *buf, *len);
+ /* buffer from the used bucket is passed to the caller, do not free */
+ bucket_used->buf = NULL;
return 0;
}
@@ -187,267 +218,127 @@ static int mtd_get_meminfo(const char *path, struct mtd_info_user *meminfo)
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;
+/* Number of buckets that should be used */
+static const int desired_buckets = 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.
+ * @param circular If false, use non-circular mode to write data that is compatible with the old on-flash format
* @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.
+ * This function iterates over the eraseblocks and creates one bucket on
+ * each eraseblock until we have the number of desired buckets. Bad blocks
+ * will be skipped and the next block will be used.
*/
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 mtd_info_user *meminfo, bool circular)
{
struct state_backend_storage_bucket *bucket;
- ssize_t end = dev_offset + max_size;
- int nr_copies = 0;
+ ssize_t end = storage->offset + storage->max_size;
+ int n_buckets = 0;
off_t offset;
+ ssize_t writesize;
if (!end || end > meminfo->size)
end = meminfo->size;
- if (!IS_ALIGNED(dev_offset, meminfo->erasesize)) {
+ if (!IS_ALIGNED(storage->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);
+ storage->offset, meminfo->erasesize);
return -EINVAL;
}
- for (offset = dev_offset; offset < end; offset += meminfo->erasesize) {
+ if (circular)
+ writesize = meminfo->writesize;
+ else
+ writesize = meminfo->erasesize;
+
+ for (offset = storage->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,
+ ret = state_backend_bucket_circular_create(storage->dev, storage->path,
&bucket,
eraseblock,
writesize,
- meminfo,
- lazy_init);
- if (ret) {
- dev_warn(storage->dev, "Failed to create bucket at '%s' eraseblock %u\n",
- path, eraseblock);
+ meminfo);
+ if (ret)
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);
- }
+ bucket->offset = offset;
+ bucket->num = n_buckets;
list_add_tail(&bucket->bucket_list, &storage->buckets);
- ++nr_copies;
- if (nr_copies >= desired_copies)
+ ++n_buckets;
+ if (n_buckets >= desired_buckets)
return 0;
}
- if (!nr_copies) {
+ if (!n_buckets) {
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);
+ n_buckets, desired_buckets);
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.
+ * direct buckets are simpler than circular buckets and can be used on blockdevices
+ * and mtd devices that don't need erase (MRAM). Also used for EEPROMs.
*/
-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)
+static int state_storage_file_buckets_init(struct state_backend_storage *storage)
{
struct state_backend_storage_bucket *bucket;
- size_t fd_size = 0;
- int ret;
+ int ret, n;
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);
- }
+ int n_buckets = 0;
+ uint32_t stridesize = storage->stridesize;
+ size_t max_size = storage->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;
+ dev_err(storage->dev, "stridesize unspecified\n");
+ return -EINVAL;
}
- for (offset = dev_offset; offset < fd_size; offset += stridesize) {
- size_t maxsize = min((size_t)stridesize,
- (size_t)(fd_size - offset));
+ if (max_size && max_size < desired_buckets * stridesize) {
+ dev_err(storage->dev, "device is too small to hold %d copies\n", desired_buckets);
+ return -EINVAL;
+ }
- ret = state_backend_bucket_direct_create(storage->dev, path,
+ for (n = 0; n < desired_buckets; n++) {
+ offset = storage->offset + n * stridesize;
+ ret = state_backend_bucket_direct_create(storage->dev, storage->path,
&bucket, offset,
- maxsize);
+ stridesize);
if (ret) {
dev_warn(storage->dev, "Failed to create direct bucket at '%s' offset %ld\n",
- path, offset);
+ storage->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);
- }
+ bucket->offset = offset;
+ bucket->num = n_buckets;
list_add_tail(&bucket->bucket_list, &storage->buckets);
- ++nr_copies;
- if (nr_copies >= desired_copies)
- return 0;
+ ++n_buckets;
}
- if (!nr_copies) {
+ if (!n_buckets) {
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);
+
+ if (n_buckets < desired_buckets)
+ dev_warn(storage->dev, "Failed to initialize desired amount of direct buckets, only %d of %d succeeded\n",
+ n_buckets, desired_buckets);
return 0;
}
@@ -455,7 +346,6 @@ static int state_storage_file_buckets_init(struct state_backend_storage *storage
/**
* 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.
@@ -466,37 +356,39 @@ static int state_storage_file_buckets_init(struct state_backend_storage *storage
*
* 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,
+int state_storage_init(struct state *state, const char *path,
off_t offset, size_t max_size, uint32_t stridesize,
const char *storagetype)
{
+ struct state_backend_storage *storage = &state->storage;
int ret = -ENODEV;
struct mtd_info_user meminfo;
INIT_LIST_HEAD(&storage->buckets);
- storage->dev = dev;
+ storage->dev = &state->dev;
storage->name = storagetype;
storage->stridesize = stridesize;
+ storage->offset = offset;
+ storage->max_size = max_size;
+ storage->path = xstrdup(path);
if (IS_ENABLED(CONFIG_MTD))
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;
+ bool circular;
+ if (!storagetype || !strcmp(storagetype, "circular")) {
+ circular = true;
+ } else if (!strcmp(storagetype, "noncircular")) {
+ dev_warn(storage->dev, "using old format circular storage type.\n");
+ circular = false;
+ } else {
+ dev_warn(storage->dev, "unknown storage type '%s'\n", storagetype);
+ return -EINVAL;
}
- return state_storage_mtd_buckets_init(storage, &meminfo, path,
- non_circular, offset,
- max_size);
+ return state_storage_mtd_buckets_init(storage, &meminfo, circular);
} else {
- return state_storage_file_buckets_init(storage, path, offset,
- max_size, stridesize);
+ return state_storage_file_buckets_init(storage);
}
dev_err(storage->dev, "storage init done\n");
@@ -524,4 +416,6 @@ void state_storage_free(struct state_backend_storage *storage)
list_del(&bucket->bucket_list);
bucket->free(bucket);
}
+
+ free(storage->path);
}