barebox/common/state/backend_bucket_circular.c
Markus Pargmann c999b507da state: Refactor state framework
The state framework grew organically over the time. Unfortunately the
architecture and abstractions disappeared during this period.

This patch refactors the framework to recreate the abstractions. The
main focus was the backend with its storage. The main use-case was to
offer better NAND support with less erase cycles and interchangeable
data formats (dtb,raw).

The general architecture now has a backend which consists of a data
format and storage. The storage consists of multiple storage buckets
each holding exactly one copy of the state data. A data format describes
a data serialization for the state framework. This can be either dtb or
raw. A storage bucket is a storage location which is used to store any
data. There is a (new) circular type which writes changes behind the
last written data and therefore reduces the number of erases. The other
type is a direct bucket which writes directly to a storage offset for
all non-erase storage.

Furthermore this patch splits up all classes into different files in a
subdirectory.

This is currently all in one patch as I can't see a good way to split
the changes up without having a non-working state framework in between.

The following diagram shows the new architecture roughly:

           .----------.
           |  state   |
           '----------'
                 |
                 |
                 v
  .----------------------------.
  |       state_backend        |
  |----------------------------|
  | + state_load(*state);      |
  | + state_save(*state);      |
  | + state_backend_init(...); |
  |                            |
  |                            |
  '----------------------------'
    |            |                   The format describes
    |            |                   how the state data
    |            '------------->     is serialized
    |   .--------------------------------------------.
    |   |      state_backend_format <INTERFACE>      |
    |   |--------------------------------------------|
    |   | + verify(*format, magic, *buf, len);       |
    |   | + pack(*format, *state, **buf, len);       |
    |   | + unpack(*format, *state, *buf, len);      |
    |   | + get_packed_len(*format, *state);         |
    |   | + free(*format);                           |
    |   '--------------------------------------------'
    |              ^                      ^
    |              *                      *
    |              *                      *
    |   .--------------------. .--------------------.
    |   | backend_format_dtb | | backend_format_raw |
    |   '--------------------' '--------------------'
    |
    |
    |
    v
.----------------------------------------------------------.
|                  state_backend_storage                   |
|----------------------------------------------------------|
| + init(...);                                             |
| + free(*storage);                                        |
| + read(*storage, *format, magic, **buf, *len, len_hint); |
| + write(*storage, *buf, len);                            |
| + restore_consistency(*storage, *buf, len);              |
'----------------------------------------------------------'
                              |
     The backend storage is responsible to manage multiple
     data copies and distribute them onto several buckets.
     Read data is verified against the given format to
     ensure that the read data is correct.
                              |
                              |
                              |
                              |
                              |
                              v
        .------------------------------------------.
        | state_backend_storage_bucket <INTERFACE> |
        |------------------------------------------|
        | + init(*bucket);                         |
        | + write(*bucket, *buf, len);             |
        | + read(*bucket, **buf, len_hint);        |
        | + free(*bucket);                         |
        '------------------------------------------'
                      ^     ^      ^
                     *      *       *
                    *       *        *
 A storage bucket represents*exactly one data copy at one
 data location. A circular b*cket writes any new data to
 the end of the bucket (for *educed erases on NAND). A
 direct bucket directly writ*s at one location.
               *            *             *
              *             *              *
             *              *               *
 .-----------------------.  *  .-------------------------.
 | backend_bucket_direct |  *  | backend_bucket_circular |
 '-----------------------'  *  '-------------------------'
             ^              *               ^
             |              *               |
             |              *               |
             |              *               |
             |  .-----------------------.   |
             '--| backend_bucket_cached |---'
                '-----------------------'
             A backend_bucket_cached is a transparent
             bucket that directly uses another bucket
             as backend device and caches all accesses.

Signed-off-by: Markus Pargmann <mpa@pengutronix.de>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
2016-07-08 08:59:31 +02:00

516 lines
13 KiB
C

/*
* 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;
}