// SPDX-License-Identifier: GPL-2.0-or-later
/*
* inftlcore.c -- Linux driver for Inverse Flash Translation Layer (INFTL)
*
* Copyright © 2002, Greg Ungerer (gerg@snapgear.com)
*
* Based heavily on the nftlcore.c code which is:
* Copyright © 1999 Machine Vision Holdings, Inc.
* Copyright © 1999 David Woodhouse <dwmw2@infradead.org>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/hdreg.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nftl.h>
#include <linux/mtd/inftl.h>
#include <linux/mtd/rawnand.h>
#include <linux/uaccess.h>
#include <asm/errno.h>
#include <asm/io.h>
/*
* Maximum number of loops while examining next block, to have a
* chance to detect consistency problems (they should never happen
* because of the checks done in the mounting.
*/
#define MAX_LOOPS 10000
static void inftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
{
struct INFTLrecord *inftl;
unsigned long temp;
if (!mtd_type_is_nand(mtd) || mtd->size > UINT_MAX)
return;
/* OK, this is moderately ugly. But probably safe. Alternatives? */
if (memcmp(mtd->name, "DiskOnChip", 10))
return;
if (!mtd->_block_isbad) {
printk(KERN_ERR
"INFTL no longer supports the old DiskOnChip drivers loaded via docprobe.\n"
"Please use the new diskonchip driver under the NAND subsystem.\n");
return;
}
pr_debug("INFTL: add_mtd for %s\n", mtd->name);
inftl = kzalloc(sizeof(*inftl), GFP_KERNEL);
if (!inftl)
return;
inftl->mbd.mtd = mtd;
inftl->mbd.devnum = -1;
inftl->mbd.tr = tr;
if (INFTL_mount(inftl) < 0) {
printk(KERN_WARNING "INFTL: could not mount device\n");
kfree(inftl);
return;
}
/* OK, it's a new one. Set up all the data structures. */
/* Calculate geometry */
inftl->cylinders = 1024;
inftl->heads = 16;
temp = inftl->cylinders * inftl->heads;
inftl->sectors = inftl->mbd.size / temp;
if (inftl->mbd.size % temp) {
inftl->sectors++;
temp = inftl->cylinders * inftl->sectors;
inftl->heads = inftl->mbd.size / temp;
if (inftl->mbd.size % temp) {
inftl->heads++;
temp = inftl->heads * inftl->sectors;
inftl->cylinders = inftl->mbd.size / temp;
}
}
if (inftl->mbd.size != inftl->heads * inftl->cylinders * inftl->sectors) {
/*
Oh no we don't have
mbd.size == heads * cylinders * sectors
*/
printk(KERN_WARNING "INFTL: cannot calculate a geometry to "
"match size of 0x%lx.\n", inftl->mbd.size);
printk(KERN_WARNING "INFTL: using C:%d H:%d S:%d "
"(== 0x%lx sects)\n",
inftl->cylinders, inftl->heads , inftl->sectors,
(long)inftl->cylinders * (long)inftl->heads *
(long)inftl->sectors );
}
if (add_mtd_blktrans_dev(&inftl->mbd)) {
kfree(inftl->PUtable);
kfree(inftl->VUtable);
kfree(inftl);
return;
}
#ifdef PSYCHO_DEBUG
printk(KERN_INFO "INFTL: Found new inftl%c\n", inftl->mbd.devnum + 'a');
#endif
return;
}
static void inftl_remove_dev(struct mtd_blktrans_dev *dev)
{
struct INFTLrecord *inftl = (void *)dev;
pr_debug("INFTL: remove_dev (i=%d)\n", dev->devnum);
del_mtd_blktrans_dev(dev);
kfree(inftl->PUtable);
kfree(inftl->VUtable);
}
/*
* Actual INFTL access routines.
*/
/*
* Read oob data from flash
*/
int inftl_read_oob(struct mtd_info *mtd, loff_t offs, size_t len,
size_t *retlen, uint8_t *buf)
{
struct mtd_oob_ops ops;
int res;
ops.mode = MTD_OPS_PLACE_OOB;
ops.ooboffs = offs & (mtd->writesize - 1);
ops.ooblen = len;
ops.oobbuf = buf;
ops.datbuf = NULL;
res = mtd_read_oob(mtd, offs & ~(mtd->writesize - 1), &ops);
*retlen = ops.oobretlen;
return res;
}
/*
* Write oob data to flash
*/
int inftl_write_oob(struct mtd_info *mtd, loff_t offs, size_t len,
size_t *retlen, uint8_t *buf)
{
struct mtd_oob_ops ops;
int res;
ops.mode = MTD_OPS_PLACE_OOB;
ops.ooboffs = offs & (mtd->writesize - 1);
ops.ooblen = len;
ops.oobbuf = buf;
ops.datbuf = NULL;
res = mtd_write_oob(mtd, offs & ~(mtd->writesize - 1), &ops);
*retlen = ops.oobretlen;
return res;
}
/*
* Write data and oob to flash
*/
static int inftl_write(struct mtd_info *mtd, loff_t offs, size_t len,
size_t *retlen, uint8_t *buf, uint8_t *oob)
{
struct mtd_oob_ops ops;
int res;
ops.mode = MTD_OPS_PLACE_OOB;
ops.ooboffs = offs;
ops.ooblen = mtd->oobsize;
ops.oobbuf = oob;
ops.datbuf = buf;
ops.len = len;
res = mtd_write_oob(mtd, offs & ~(mtd->writesize - 1), &ops);
*retlen = ops.retlen;
return res;
}
/*
* INFTL_findfreeblock: Find a free Erase Unit on the INFTL partition.
* This function is used when the give Virtual Unit Chain.
*/
static u16 INFTL_findfreeblock(struct INFTLrecord *inftl, int