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>
516 lines
13 KiB
C
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;
|
|
}
|