9
0
Fork 0
barebox/common/state/backend_bucket_circular.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 __attribute__((__packed__)) 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 && ret != -EUCLEAN)
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;
}