9
0
Fork 0
barebox/drivers/mtd/mtdraw.c

334 lines
8.3 KiB
C

/*
* MTD raw device
*
* Copyright (C) 2011 Robert Jarzmik
*
* 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.
*
* Adds a character devices :
* - mtdraw<N>
*
* Device mtd_raw<N> provides acces to the MTD "pages+OOB". For example if a MTD
* has pages of 512 bytes and OOB of 16 bytes, mtd_oob<N> will be made of blocks
* of 528 bytes, with page data being followed by OOB.
* The layout will be: <page0> <oob0> <page1> <oob1> ... <pageN> <oobN>.
* This means that a read at offset 516 of 20 bytes will give the 12 last bytes
* of the OOB of page0, and the 8 first bytes of page1.
* Same thing applies for writes, which have to be page+oob aligned (ie. offset
* and size should be multiples of (mtd->writesize + mtd->oobsize)).
*/
#include <common.h>
#include <init.h>
#include <malloc.h>
#include <ioctl.h>
#include <errno.h>
#include <linux/mtd/mtd.h>
#include "mtd.h"
/* Must be a multiple of the largest NAND page size */
#define RAW_WRITEBUF_SIZE 4096
/**
* mtdraw - mtdraw device private data
* @cdev: character device "mtdraw<N>"
* @mtd: MTD device to handle read/writes/erases
*
* @writebuf: buffer to handle unaligned writes (ie. writes of sizes which are
* not multiples of MTD (writesize+oobsize)
* @write_fill: number of bytes in writebuf
* @write_ofs: offset in character device (mtdraw) where last write(s) stored
* bytes because of unaligned writes (ie. remain of writesize+oobsize write)
*
* The mtdraw device must allow unaligned writes. This is enabled by a write buffer which gathers data to issue mtd->write_oob() with full page+oob data.
* Suppose writesize=512, oobsize=16.
* A first write of 512 bytes triggers:
* - write_ofs = offset of write()
* - write_fill = 512
* - copy of the 512 provided bytes into writebuf
* - no actual mtd->write if done
* A second write of 512 bytes triggers:
* - copy of the 16 first bytes into writebuf
* - a mtd_write_oob() from writebuf
* - empty writebuf
* - copy the remaining 496 bytes into writebuf
* => write_fill = 496, write_ofs = offset + 528
* Etc ...
*/
struct mtdraw {
struct cdev cdev;
struct mtd_info *mtd;
void *writebuf;
int write_fill;
int write_ofs;
};
static struct mtdraw *to_mtdraw(struct cdev *cdev)
{
return cdev->priv;
}
static struct mtd_info *to_mtd(struct cdev *cdev)
{
struct mtdraw *mtdraw = to_mtdraw(cdev);
return mtdraw->mtd;
}
static ssize_t mtdraw_read_unaligned(struct mtd_info *mtd, void *dst,
size_t count, int skip, ulong offset)
{
struct mtd_oob_ops ops;
ssize_t ret;
int partial = 0;
void *tmp = dst;
if (skip || count < mtd->writesize + mtd->oobsize)
partial = 1;
if (partial)
tmp = malloc(mtd->writesize + mtd->oobsize);
if (!tmp)
return -ENOMEM;
ops.mode = MTD_OPS_RAW;
ops.ooboffs = 0;
ops.datbuf = tmp;
ops.len = mtd->writesize;
ops.oobbuf = tmp + mtd->writesize;
ops.ooblen = mtd->oobsize;
ret = mtd_read_oob(mtd, offset, &ops);
if (ret)
goto err;
if (partial)
memcpy(dst, tmp + skip, count);
ret = count;
err:
if (partial)
free(tmp);
return ret;
}
static ssize_t mtdraw_read(struct cdev *cdev, void *buf, size_t count,
loff_t _offset, ulong flags)
{
struct mtd_info *mtd = to_mtd(cdev);
ssize_t retlen = 0, ret = 1, toread;
ulong numpage;
int skip;
unsigned long offset = _offset;
numpage = offset / (mtd->writesize + mtd->oobsize);
skip = offset % (mtd->writesize + mtd->oobsize);
while (ret > 0 && count > 0) {
toread = min_t(int, count,
mtd->writesize + mtd->oobsize - skip);
ret = mtdraw_read_unaligned(mtd, buf, toread,
skip, numpage++ * mtd->writesize);
buf += ret;
skip = 0;
count -= ret;
retlen += ret;
}
if (ret < 0)
printf("err %zd\n", ret);
else
ret = retlen;
return ret;
}
#ifdef CONFIG_MTD_WRITE
static ssize_t mtdraw_blkwrite(struct mtd_info *mtd, const void *buf,
ulong offset)
{
struct mtd_oob_ops ops;
int ret;
ops.mode = MTD_OPS_RAW;
ops.ooboffs = 0;
ops.datbuf = (void *)buf;
ops.len = mtd->writesize;
ops.oobbuf = (void *)buf + mtd->writesize;
ops.ooblen = mtd->oobsize;
ret = mtd_write_oob(mtd, offset, &ops);
if (!ret)
ret = ops.retlen + ops.oobretlen;
return ret;
}
static void mtdraw_fillbuf(struct mtdraw *mtdraw, const void *src, int nbbytes)
{
memcpy(mtdraw->writebuf + mtdraw->write_fill, src, nbbytes);
mtdraw->write_fill += nbbytes;
}
static ssize_t mtdraw_write(struct cdev *cdev, const void *buf, size_t count,
loff_t _offset, ulong flags)
{
struct mtdraw *mtdraw = to_mtdraw(cdev);
struct mtd_info *mtd = to_mtd(cdev);
int bsz = mtd->writesize + mtd->oobsize;
ulong numpage;
size_t retlen = 0, tofill;
unsigned long offset = _offset;
int ret = 0;
if (mtdraw->write_fill &&
mtdraw->write_ofs + mtdraw->write_fill != offset)
return -EINVAL;
if (mtdraw->write_fill == 0 && offset % bsz)
return -EINVAL;
if (mtdraw->write_fill) {
tofill = min_t(size_t, count, bsz - mtdraw->write_fill);
mtdraw_fillbuf(mtdraw, buf, tofill);
offset += tofill;
count -= tofill;
retlen += tofill;
}
if (mtdraw->write_fill == bsz) {
numpage = mtdraw->write_ofs / (mtd->writesize + mtd->oobsize);
ret = mtdraw_blkwrite(mtd, mtdraw->writebuf,
mtd->writesize * numpage);
mtdraw->write_fill = 0;
}
numpage = offset / (mtd->writesize + mtd->oobsize);
while (ret >= 0 && count >= bsz) {
ret = mtdraw_blkwrite(mtd, buf + retlen,
mtd->writesize * numpage++);
count -= ret;
retlen += ret;
offset += ret;
}
if (ret >= 0 && count) {
mtdraw->write_ofs = offset - mtdraw->write_fill;
mtdraw_fillbuf(mtdraw, buf + retlen, count);
retlen += count;
}
if (ret < 0) {
printf("err %d\n", ret);
return ret;
} else {
return retlen;
}
}
static int mtdraw_erase(struct cdev *cdev, size_t count, loff_t _offset)
{
struct mtd_info *mtd = to_mtd(cdev);
struct erase_info erase;
unsigned long offset = _offset;
int ret;
offset = offset / (mtd->writesize + mtd->oobsize) * mtd->writesize;
count = count / (mtd->writesize + mtd->oobsize) * mtd->writesize;
memset(&erase, 0, sizeof(erase));
erase.mtd = mtd;
erase.addr = offset;
erase.len = mtd->erasesize;
while (count > 0) {
debug("erase %d %d\n", erase.addr, erase.len);
if (!mtd->allow_erasebad)
ret = mtd_block_isbad(mtd, erase.addr);
else
ret = 0;
if (ret > 0) {
printf("Skipping bad block at 0x%08x\n", erase.addr);
} else {
ret = mtd_erase(mtd, &erase);
if (ret)
return ret;
}
erase.addr += mtd->erasesize;
count -= count > mtd->erasesize ? mtd->erasesize : count;
}
return 0;
}
#else
static ssize_t mtdraw_write(struct cdev *cdev, const void *buf, size_t count,
loff_t offset, ulong flags)
{
return 0;
}
static ssize_t mtdraw_erase(struct cdev *cdev, size_t count, loff_t offset)
{
return 0;
}
#endif
static const struct file_operations mtd_raw_fops = {
.read = mtdraw_read,
.write = mtdraw_write,
.erase = mtdraw_erase,
.lseek = dev_lseek_default,
};
static int add_mtdraw_device(struct mtd_info *mtd, char *devname, void **priv)
{
struct mtdraw *mtdraw;
if (mtd->master || mtd->oobsize == 0)
return 0;
mtdraw = xzalloc(sizeof(*mtdraw));
mtdraw->writebuf = xmalloc(RAW_WRITEBUF_SIZE);
mtdraw->mtd = mtd;
mtdraw->cdev.ops = (struct file_operations *)&mtd_raw_fops;
mtdraw->cdev.size = mtd_div_by_wb(mtd->size, mtd) *
(mtd->writesize + mtd->oobsize);
mtdraw->cdev.name = asprintf("%s.raw", mtd->cdev.name);
mtdraw->cdev.priv = mtdraw;
mtdraw->cdev.dev = &mtd->class_dev;
mtdraw->cdev.mtd = mtd;
*priv = mtdraw;
devfs_create(&mtdraw->cdev);
return 0;
}
static int del_mtdraw_device(struct mtd_info *mtd, void **priv)
{
struct mtdraw *mtdraw;
if (mtd->master || mtd->oobsize == 0)
return 0;
mtdraw = *priv;
devfs_remove(&mtdraw->cdev);
free(mtdraw);
return 0;
}
static struct mtddev_hook mtdraw_hook = {
.add_mtd_device = add_mtdraw_device,
.del_mtd_device = del_mtdraw_device,
};
static int __init register_mtdraw(void)
{
mtdcore_add_hook(&mtdraw_hook);
return 0;
}
coredevice_initcall(register_mtdraw);