[PATCH] [MTD] NAND : SFTL (Simple Flash Transration Layer)

katsuki.uwatoko at toshiba.co.jp katsuki.uwatoko at toshiba.co.jp
Wed Dec 14 02:03:03 EST 2011


This is a new Flash Translation Layer for NAND Flash on mtd.

mtd: SFTL (Simple Flash Translation Layer)

Introduction
------------

SFTL is a flash translation layer for NAND Flash on mtd_blkdevs
interface.  This is mainly-useful for read-oriented use (ex. a storage
for a boot image) because this provides simple wear-leveling and
scrubbing. The features are:

 * sftl provides wear-leveling (not static wear-leveling) and scrubbing.
 * sftl has one erase block size cache.
 * sftl uses 6 bytes in OOB for a logical address, status, version.
 * the erase block scan at init is fast.
 * a unit of read/write from/to MTD is an erase block.

Module parameters
-----------------

  mtd=		MTD devices to attach.
		Parameter format: mtd=<num>
		Multiple mtd parameters may be specified.

  rb=		percents of reserved eraseblocks for bad blocks.
		This parameter must be from 1 to 90. The default is 2.
		The number of reserved blocks must be more than 5.

Signed-off-by: UWATOKO Katsuki <katsuki.uwatoko at toshiba.co.jp>
---
 drivers/mtd/Kconfig  |   17 +
 drivers/mtd/Makefile |    1 +
 drivers/mtd/sftl.c   | 1152 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1170 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mtd/sftl.c

diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index 318a869..b8435f8 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -309,6 +309,23 @@ config MTD_SWAP
 	  The driver provides wear leveling by storing erase counter into the
 	  OOB.
 
+config SFTL
+	tristate "SFTL (Simple Translation Layer) support"
+	depends on BLOCK && MTD_NAND
+	default n
+	select MTD_BLKDEVS
+	help
+	  Provides block device driver for NAND flash and simple wear-leveling
+	  and scrubbing.
+	  This is mainly-useful for read-oriented use.
+	  (ex. a storage for a boot image and so on.)
+
+config SFTL_RESERVE_BLK_PERCENT
+	int "percents of reserved erasedblocks for bad blocks."
+	depends on SFTL
+	range 1 90
+	default "2"
+
 source "drivers/mtd/chips/Kconfig"
 
 source "drivers/mtd/maps/Kconfig"
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 9aaac3a..7fb2933 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_SSFDC)		+= ssfdc.o
 obj-$(CONFIG_SM_FTL)		+= sm_ftl.o
 obj-$(CONFIG_MTD_OOPS)		+= mtdoops.o
 obj-$(CONFIG_MTD_SWAP)		+= mtdswap.o
+obj-$(CONFIG_SFTL)		+= sftl.o
 
 nftl-objs		:= nftlcore.o nftlmount.o
 inftl-objs		:= inftlcore.o inftlmount.o
diff --git a/drivers/mtd/sftl.c b/drivers/mtd/sftl.c
new file mode 100644
index 0000000..0984f02
--- /dev/null
+++ b/drivers/mtd/sftl.c
@@ -0,0 +1,1152 @@
+/*
+ * Simple Flash Translation Layer (sftl)
+ *
+ * (C) Copyright TOSHIBA CORPORATION 2011
+ * All Rights Reserved.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+
+Introduction
+------------
+
+SFTL is a flash translation layer for NAND Flash on mtd_blkdevs
+interface.  This is mainly-useful for read-oriented use (ex. a storage
+for a boot image) because this provides simple wear-leveling and
+scrubbing. The features are:
+
+ * sftl provides wear-leveling (not static wear-leveling) and scrubbing.
+ * sftl has one erase block size cache.
+ * sftl uses 6 bytes in OOB for a logical address, status, version.
+ * the erase block scan at init is fast.
+ * a unit of read/write from/to MTD is an erase block.
+
+Module parameters
+-----------------
+
+  mtd=		MTD devices to attach.
+		Parameter format: mtd=<num>
+		Multiple mtd parameters may be specified.
+
+  rb=		percents of reserved eraseblocks for bad blocks.
+		This parameter must be from 1 to 90. The default is 2.
+		The number of reserved blocks must be more than 5.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/blktrans.h>
+#include <linux/blkdev.h>
+#include <linux/sched.h>
+#include <linux/sysfs.h>
+
+/* definitions */
+
+#define SFTL_VERSION "1.0"
+
+#define SFTL_MAX_DEVICES   32
+#define SFTL_PARTN_BITS 3
+
+static int mtd_devs;
+static int mtd_dev_param[SFTL_MAX_DEVICES];
+
+static int reserve_blk_percent = CONFIG_SFTL_RESERVE_BLK_PERCENT;
+static int sftl_read_retries = 10;
+static int sftl_write_retries = 10;
+static int sftl_erase_retries = 5;
+
+#define SFTL_SECTOR_SIZE	512
+#define SFTL_SECTOR_SHIFT	9
+
+#define MINIMUM_PBLOCKS         32
+#define MINIMUM_RESERVE_PBLOCKS 5
+
+/* sftl oob data format */
+
+struct sftl_oobdata {
+	__le32 lnum;
+	uint8_t version;
+	uint8_t status;
+} __packed;
+
+#define SFTL_LNUM_PBIT     0x80000000
+#define SFTL_PNUM_NOASSIGN 0x80000000	/* this is used in laddr_tbl. */
+#define SFTL_VERS_PBIT     0x80
+
+#define STATUS_ASSIGN      (0xff)	/* ASSIGN */
+#define STATUS_FREE        (0x00)	/* FREE */
+#define STATUS_BITFLIP     (0x55)	/* bitfliped OOB, this is a SW status */
+#define STATUS_BAD         (0xaa)	/* badblock, this is a SW status */
+
+struct sftl_pdata {
+	struct list_head list;
+	struct sftl_oobdata oobd;
+};
+
+/* sftl device structure */
+
+struct sftl_dev {
+	struct mtd_blktrans_dev *mbd;
+	struct mtd_info *mtd;           /* mtd device */
+
+	uint32_t *laddr_tbl;            /* pnum table indexed by lnum */
+	struct sftl_pdata *pdata_tbl;	/* physical data table */
+
+	/*  every physical block must be on one of following lists. */
+	struct list_head assign_list;
+	struct list_head bad_list;
+	struct list_head free_list;
+
+	int pblocks;		        /* number of physical blocks */
+	int lblocks;		        /* number of logical blocks */
+	int badblocks;		        /* number of badblocks */
+
+	/* cache */
+	char *cache_buf;
+	uint32_t cache_lnum;
+	uint8_t cache_state;
+};
+
+#define CACHE_EMPTY       0x00
+#define CACHE_VALID       0x01
+#define CACHE_DIRTY       0x02
+#define CACHE_STATUS_MASK 0x03
+
+#define sftl_err(format, ...) \
+	pr_err("sftl:mtd%d: " format, sftl->mtd->index, ##__VA_ARGS__)
+
+#define sftl_info(format, ...) \
+	pr_info("sftl:mtd%d: " format, sftl->mtd->index, ##__VA_ARGS__)
+
+#define sftl_warn(format, ...) \
+	pr_warn("sftl:mtd%d: " format, sftl->mtd->index, ##__VA_ARGS__)
+
+#define sftl_dbg(format, ...) \
+	pr_debug("sftl:mtd%d: " format, sftl->mtd->index, ##__VA_ARGS__)
+
+/****************************************************************************
+ *
+ * disk_attributes
+ *
+ ****************************************************************************/
+
+#define SFTL_ATTR(_name)                                                  \
+static ssize_t sftl_attr_##_name##_show(struct device *device,            \
+				     struct device_attribute *attr,       \
+				     char *buf)                           \
+{                                                                         \
+	struct gendisk *gd = dev_to_disk(device);                         \
+	struct mtd_blktrans_dev *dev = gd->private_data;                  \
+	struct sftl_dev *sftl = dev->priv;                                \
+	return sprintf(buf, "%d\n", sftl->_name);                         \
+}                                                                         \
+static DEVICE_ATTR(_name, S_IRUGO, sftl_attr_##_name##_show, NULL);
+
+SFTL_ATTR(lblocks);
+SFTL_ATTR(pblocks);
+SFTL_ATTR(badblocks);
+
+static struct attribute *sftl_attrs[] = {
+	&dev_attr_lblocks.attr,
+	&dev_attr_pblocks.attr,
+	&dev_attr_badblocks.attr,
+	NULL,
+};
+
+static struct attribute_group sftl_attribute_group = {
+	.name = "sftl",
+	.attrs = sftl_attrs,
+};
+
+/****************************************************************************
+ *
+ * pnum/lnum/snum helper functions/macros
+ *
+ ****************************************************************************/
+
+static inline uint32_t get_pnum_by_pdata(struct sftl_dev *sftl,
+					 struct sftl_pdata *pdata)
+{
+	return pdata - &sftl->pdata_tbl[0];
+}
+
+static inline uint32_t get_pnum_by_lnum(struct sftl_dev *sftl, uint32_t lnum)
+{
+	return sftl->laddr_tbl[lnum];
+}
+
+static inline int get_lnum_by_snum(struct sftl_dev *sftl,
+				   uint32_t *lnum, uint32_t *offs,
+				   uint32_t snum)
+{
+	uint32_t sectors_per_block = sftl->mtd->erasesize >> SFTL_SECTOR_SHIFT;
+
+	*offs = (snum % sectors_per_block) * SFTL_SECTOR_SIZE;
+	*lnum = (snum / sectors_per_block);
+
+	if (*lnum >= sftl->lblocks) {
+		sftl_err("%s: sector 0x%08x is too big.\n",  __func__, snum);
+		return -1;
+	}
+
+	sftl_dbg("%s: L:0x%08x::S:0x%08x::O:0x%08x\n", __func__, *lnum, snum,
+		 *offs);
+
+	return 0;
+}
+
+#define PDATA_LNUM(p)      (le32_to_cpu((p)->oobd.lnum))
+#define PDATA_VERSION(p)   ((p)->oobd.version)
+#define PDATA_STATUS(p)    ((p)->oobd.status)
+
+/****************************************************************************
+ *
+ * OOB helper functions/macro
+ *
+ ****************************************************************************/
+
+static inline uint32_t lnum_parity(uint32_t lnum)
+{
+	return (hweight32(lnum & ~SFTL_LNUM_PBIT) & 1) << 31;
+}
+
+static inline uint8_t version_parity(uint8_t version)
+{
+	return (hweight8(version & ~SFTL_VERS_PBIT) & 1) << 7;
+}
+
+static int check_oobdata(struct sftl_dev *sftl, struct sftl_oobdata *oobd)
+{
+	if (oobd->status == STATUS_FREE)
+		return 0;
+
+	if (oobd->status != STATUS_ASSIGN)
+		return -1;
+
+	if (lnum_parity(le32_to_cpu(oobd->lnum)) !=
+	    (le32_to_cpu(oobd->lnum) & SFTL_LNUM_PBIT))
+		return -2;
+
+	if ((le32_to_cpu(oobd->lnum) & ~SFTL_LNUM_PBIT) >= sftl->lblocks)
+		return -3;
+
+	if (version_parity(oobd->version) != (oobd->version & SFTL_VERS_PBIT))
+		return -4;
+
+	return 0;
+}
+
+static inline int check_version(struct sftl_dev *sftl,
+				struct sftl_pdata *old, struct sftl_pdata *new)
+{
+	uint8_t check = (PDATA_VERSION(old) - PDATA_VERSION(new));
+
+	if (PDATA_STATUS(old) == STATUS_ASSIGN
+	    && PDATA_STATUS(new) == STATUS_BITFLIP)
+		return 1;	/* choose old */
+
+	if (PDATA_STATUS(old) == STATUS_BITFLIP
+	    && PDATA_STATUS(new) == STATUS_ASSIGN)
+		return 0;	/* choose new */
+
+	/* both BITFLIP or both ASSIGN */
+
+	if (check & 0x40)
+		return 1;	/* choose old */
+	else
+		return 0;	/* choose new */
+}
+
+#define INITOPS_OOB(ops, oobd)  do {	                                \
+		memset(&ops, 0, sizeof(struct mtd_oob_ops));	        \
+		ops.mode	= MTD_OPS_AUTO_OOB;			\
+		ops.ooblen	= sizeof(struct sftl_oobdata);		\
+		ops.oobbuf	= (uint8_t *) oobd;			\
+	} while (0);
+
+/****************************************************************************
+ *
+ * mtd wrapper functions
+ *
+ ****************************************************************************/
+
+/* Read physical block */
+static int read_pb(struct sftl_dev *sftl, uint32_t pnum, uint8_t * buf)
+{
+	struct mtd_info *mtd = sftl->mtd;
+	struct sftl_oobdata *oobd = &sftl->pdata_tbl[pnum].oobd;
+	struct mtd_oob_ops ops;
+	int ret, subret = 0;
+	size_t retlen;
+
+	sftl_dbg("%s: P:0x%08x\n", __func__, pnum);
+
+	/* read the first page data with oob and check the oob,
+	   because the oob was not read in init (build_lblock_map) */
+
+	INITOPS_OOB(ops, oobd);
+	ops.len = mtd->writesize;
+	ops.datbuf = buf;
+
+	ret = mtd->read_oob(mtd, pnum * mtd->erasesize, &ops);
+
+	if (mtd_is_bitflip(ret))
+		subret = ret;
+	else if (ret) {
+		sftl_err("%s 1st page: P:0x%08x::%d\n",
+			  __func__, pnum, ret);
+		goto out;
+	}
+
+	/* check oob */
+
+	if (check_oobdata(sftl, oobd)) {
+		sftl_err("%s 1st page: check_oobdata fail P:0x%08x\n",
+			  __func__, pnum);
+		subret = -EUCLEAN;
+	}
+
+	/* read the others without oob */
+
+	ret = mtd->read(mtd, pnum * mtd->erasesize + mtd->writesize,
+			mtd->erasesize - mtd->writesize, &retlen,
+			buf + mtd->writesize);
+
+	if (ret)
+		sftl_err("read_pb: P:0x%08x::%d\n", pnum, ret);
+
+ out:
+	if (!ret)
+		ret = subret;
+
+	return ret;
+}
+
+/* Write physical block w/ version */
+static int write_pb(struct sftl_dev *sftl,
+		    uint32_t pnum, int32_t lnum, uint8_t version, uint8_t *buf)
+{
+	struct mtd_info *mtd = sftl->mtd;
+	struct sftl_oobdata *oobd = &sftl->pdata_tbl[pnum].oobd;
+	struct mtd_oob_ops ops;
+	size_t retlen;
+	int ret;
+
+	sftl_dbg("%s: P:0x%08x::L:0x%08x\n", __func__, pnum, lnum);
+
+	oobd->lnum = cpu_to_le32(lnum_parity(lnum) | lnum);
+	oobd->version = version_parity(version) | version;
+	oobd->status = STATUS_ASSIGN;
+
+	/* set up ops for the first and the last page */
+
+	INITOPS_OOB(ops, oobd);
+	ops.len = mtd->writesize;
+
+	/* First Page w/ the info in oob */
+
+	ops.datbuf = buf;
+	ret = mtd->write_oob(mtd, pnum * mtd->erasesize, &ops);
+
+	if (ret) {
+		sftl_err("%s: 1st page error\n", __func__);
+		goto out;
+	}
+
+	/* Middle Pages w/o the info in oob */
+
+	ret = mtd->write(mtd, pnum * mtd->erasesize + mtd->writesize,
+			 mtd->erasesize - (mtd->writesize * 2),
+			 &retlen, buf + mtd->writesize);
+
+	if (ret) {
+		sftl_err("%s: middle pages error\n", __func__);
+		goto out;
+	}
+
+	/* Last Page w/ the info in oob */
+
+	ops.datbuf = buf + mtd->erasesize - mtd->writesize;
+	ret =
+	    mtd->write_oob(mtd, (pnum + 1) * mtd->erasesize - mtd->writesize,
+			   &ops);
+ out:
+	if (ret)
+		sftl_err("%s: P:0x%08x::%d\n", __func__, pnum, ret);
+
+	return ret;
+}
+
+/* read physical block oob w/ offset */
+static int read_pb_off_oob(struct sftl_dev *sftl, uint32_t pnum, loff_t offs)
+{
+	struct mtd_info *mtd = sftl->mtd;
+	struct sftl_oobdata *oobd = &sftl->pdata_tbl[pnum].oobd;
+	struct mtd_oob_ops ops;
+	int ret;
+
+	sftl_dbg("%s: P:0x%08x::O:0x%08llx\n", __func__, pnum, offs);
+
+	INITOPS_OOB(ops, oobd);
+
+	offs += (loff_t) pnum * mtd->erasesize;
+
+	ret = mtd->read_oob(mtd, offs, &ops);
+
+	return ret;
+}
+
+/* write physical block oob w/ offset */
+static int write_pb_off_oob(struct sftl_dev *sftl,
+			    uint32_t pnum,
+			    loff_t offs, struct sftl_oobdata *oobd)
+{
+	struct mtd_info *mtd = sftl->mtd;
+	struct mtd_oob_ops ops;
+	int ret;
+
+	sftl_dbg("%s: P:0x%08x::O:0x%08llx\n", __func__, pnum, offs);
+
+	INITOPS_OOB(ops, oobd);
+
+	offs += (loff_t) pnum * mtd->erasesize;
+
+	ret = mtd->write_oob(mtd, offs, &ops);
+
+	return ret;
+}
+
+static void erase_pb_callback(struct erase_info *instr)
+{
+	wake_up((wait_queue_head_t *) instr->priv);
+}
+
+/* erase physical block */
+static int erase_pb(struct sftl_dev *sftl, uint32_t pnum)
+{
+	struct mtd_info *mtd = sftl->mtd;
+	struct erase_info erase;
+	wait_queue_head_t waitq;
+	int ret = 0, retries = 0;
+
+	sftl_dbg("%s: P:0x%08x\n", __func__, pnum);
+
+ retry:
+	init_waitqueue_head(&waitq);
+	memset(&erase, 0, sizeof(struct erase_info));
+
+	erase.mtd = mtd;
+	erase.callback = erase_pb_callback;
+	erase.priv = (unsigned long) &waitq;
+	erase.addr = pnum * mtd->erasesize;
+	erase.len = mtd->erasesize;
+	ret = mtd->erase(mtd, &erase);
+
+	if (ret)
+		goto out;
+
+	ret = wait_event_interruptible(waitq,
+				       erase.state == MTD_ERASE_DONE ||
+				       erase.state == MTD_ERASE_FAILED);
+	if (ret) {
+		sftl_err("Interrupted erase block P:0x%08x::%d\n",
+			 pnum, ret);
+		ret = -EINTR;
+		goto out;
+	}
+
+	if (erase.state == MTD_ERASE_FAILED) {
+		if (retries++ < sftl_erase_retries) {
+			yield();
+			sftl_warn("erase retry at P:0x%08x\n", pnum);
+			goto retry;
+		}
+		ret = -EIO;
+	}
+
+ out:
+	if (ret)
+		sftl_err("%s error: P:0x%08x::%d\n", __func__, pnum, ret);
+
+	return ret;
+}
+
+/* mark physical block as bad */
+static int markbad_pb(struct sftl_dev *sftl, uint32_t pnum)
+{
+	struct sftl_pdata *pdata = &sftl->pdata_tbl[pnum];
+	struct mtd_info *mtd = sftl->mtd;
+	int ret;
+
+	PDATA_STATUS(pdata) = STATUS_BAD;
+	ret = mtd->block_markbad(mtd, pnum * mtd->erasesize);
+
+	if (ret)
+		sftl_err("%s error: P:0x%08x::%d\n", __func__, pnum, ret);
+
+	return ret;
+}
+
+/****************************************************************************
+ *
+ * other functions
+ *
+ ****************************************************************************/
+
+static int read_pb_oob(struct sftl_dev *sftl, uint32_t pnum)
+{
+	struct mtd_info *mtd = sftl->mtd;
+	struct sftl_pdata *pdata = &sftl->pdata_tbl[pnum];
+	struct sftl_oobdata *oobd = &pdata->oobd;
+	int ret;
+
+	sftl_dbg("%s: P:0x%08x\n", __func__, pnum);
+
+	/* read the last page */
+
+	ret = read_pb_off_oob(sftl, pnum,
+			      (loff_t) (mtd->erasesize - mtd->writesize));
+
+	if (!ret)
+		/* Check bitflip */
+		if (!check_oobdata(sftl, oobd))
+			goto out;
+
+	/* the last page data is bitfliped, so try to read the first page */
+
+	ret = read_pb_off_oob(sftl, pnum, (loff_t) 0);
+
+	if (!ret) {
+		/* Check bitflip */
+		ret = check_oobdata(sftl, oobd);
+		if (!ret) {
+			oobd->status = STATUS_BITFLIP;
+		} else {
+			if (oobd->status != 0xff)
+				sftl_warn("check_oobdata error at P:0x%08x:%d\n",
+					  pnum, ret);
+			ret = -EIO;
+			goto out;
+		}
+	} else
+		sftl_err("read_pb_oob error at P:0x%08x:%d\n", pnum, ret);
+
+ out:
+	oobd->lnum &= cpu_to_le32(~SFTL_LNUM_PBIT);
+	oobd->version &= ~SFTL_VERS_PBIT;
+
+	return ret;
+}
+
+static int markerase_pb_putlist(struct sftl_dev *sftl, uint32_t pnum)
+{
+	struct mtd_info *mtd = sftl->mtd;
+	struct sftl_pdata *pdata = &sftl->pdata_tbl[pnum];
+	struct sftl_oobdata *oobd = &pdata->oobd;
+	int ret;
+
+	sftl_dbg("%s: P:0x%08x\n", __func__, pnum);
+
+	memset(oobd, 0xff, sizeof(struct sftl_oobdata));
+	PDATA_STATUS(pdata) = STATUS_FREE;
+
+	ret = write_pb_off_oob(sftl, pnum, 0, oobd);	/* first page */
+
+	if (!ret)
+		/* last page */
+		ret = write_pb_off_oob(sftl, pnum,
+				       mtd->erasesize - mtd->writesize, oobd);
+
+	if (ret) {
+		sftl_err("markerase error at P:0x%08x::%d\n", pnum, ret);
+		if (erase_pb(sftl, pnum))
+			markbad_pb(sftl, pnum);	/* ret value is not handled */
+	}
+
+	list_del(&pdata->list);
+
+	if (!ret)
+		/* put this to free_list */
+		list_add_tail(&pdata->list, &sftl->free_list);
+	else {
+		/* put this to bad_list, when write error. */
+		sftl->badblocks++;
+		list_add_tail(&pdata->list, &sftl->bad_list);
+	}
+
+	return ret;
+}
+
+static int write_lnum(struct sftl_dev *sftl, uint32_t lnum, uint8_t * buf)
+{
+	struct sftl_pdata *erase_candidate;
+	uint32_t pnum, oldpnum;
+	int ret;
+	uint8_t version = 0x7f;
+	int retries = 0;
+
+	sftl_dbg("%s: L:0x%08x\n", __func__, lnum);
+
+ retry:
+	/* pick a candidate block for erase */
+
+	if (list_empty(&sftl->free_list)) {
+		sftl_err("%s: depletion of free blocks\n", __func__);
+		ret = -1;
+		goto out;
+	}
+
+	erase_candidate = list_first_entry(&sftl->free_list,
+					   struct sftl_pdata, list);
+
+	/* erase */
+
+	pnum = get_pnum_by_pdata(sftl, erase_candidate);
+	ret = erase_pb(sftl, pnum);
+	if (ret) {
+		sftl_err("%s: erase error\n", __func__);
+		markerase_pb_putlist(sftl, pnum);
+		if (retries++ < sftl_write_retries) {
+			yield();
+			sftl_warn("%s: erase retry at P:0x%08x\n",
+				  __func__, pnum);
+			goto retry;
+		}
+		sftl_err("%s: erase error at P:0x%08x\n", __func__, pnum);
+		goto out;
+	}
+
+	/* write w/ version */
+
+	oldpnum = sftl->laddr_tbl[lnum];
+	if (!(oldpnum & SFTL_PNUM_NOASSIGN)) {
+		/* decrement the version */
+		version = ((sftl->pdata_tbl[oldpnum].oobd.version) - 1)
+			& ~SFTL_VERS_PBIT;
+	}
+
+	ret = write_pb(sftl, pnum, lnum, version, buf);
+	if (ret) {
+		sftl_err("%s: write error\n", __func__);
+		markerase_pb_putlist(sftl, pnum);
+		if (retries++ < sftl_write_retries) {
+			yield();
+			sftl_warn("%s: write retry at P:0x%08x\n",
+				  __func__, pnum);
+			goto retry;
+		}
+		sftl_err("%s: write error at P:0x%08x\n", __func__, pnum);
+		goto out;
+	}
+
+	if (!(oldpnum & SFTL_PNUM_NOASSIGN)) {
+		/* this lnum is already assigned */
+		markerase_pb_putlist(sftl, oldpnum);
+	}
+
+	/* update laddr_tbl and put this to assign_list */
+	sftl->laddr_tbl[lnum] = pnum;
+	list_del(&erase_candidate->list);
+	list_add(&erase_candidate->list, &sftl->assign_list);
+ out:
+	return ret;
+}
+
+static int read_lnum(struct sftl_dev *sftl, uint32_t lnum, uint8_t * buf)
+{
+	struct mtd_info *mtd = sftl->mtd;
+	uint32_t pnum = get_pnum_by_lnum(sftl, lnum);
+	int retries = sftl_read_retries;
+	int ret;
+
+	sftl_dbg("%s: L:0x%08x\n", __func__, lnum);
+
+	if (pnum & SFTL_PNUM_NOASSIGN) {
+		memset(buf, 0xff, mtd->erasesize);
+		ret = 0;
+		goto out;
+	}
+
+	do {
+		ret = read_pb(sftl, pnum, buf);
+		if (!ret || mtd_is_bitflip(ret))
+			break;
+	} while (retries--);
+
+ out:
+	return ret;
+}
+
+static int read_lnum_scrub(struct sftl_dev *sftl, uint32_t lnum, uint8_t * buf)
+{
+	int ret;
+
+	sftl_dbg("%s: L:0x%08x\n", __func__, lnum);
+
+	ret = read_lnum(sftl, lnum, buf);
+
+	if (mtd_is_bitflip(ret)) {
+		sftl_info("scrub at P:0x%08x::L:0x%08x\n",
+			  get_pnum_by_lnum(sftl, lnum), lnum);
+		ret = write_lnum(sftl, lnum, buf);
+	}
+
+	return ret;
+}
+
+/* Build the logic block map */
+static int build_lblock_map(struct sftl_dev *sftl)
+{
+	struct mtd_info *mtd = sftl->mtd;
+	uint32_t pnum;
+	int ret;
+	struct list_head erase_list, bitflip_list;
+	struct list_head *pos, *next;
+
+	INIT_LIST_HEAD(&erase_list);
+	INIT_LIST_HEAD(&bitflip_list);
+
+	for (pnum = 0; pnum < sftl->pblocks; pnum++) {
+		struct sftl_pdata *this_pdata = &sftl->pdata_tbl[pnum];
+
+		/* skip bad blocks */
+
+		if (mtd->block_isbad(mtd, pnum * mtd->erasesize)) {
+			PDATA_STATUS(this_pdata) = STATUS_BAD;
+			sftl_info("P:0x%08x: bad block\n", pnum);
+			sftl->badblocks++;
+			list_add_tail(&this_pdata->list, &sftl->bad_list);
+			continue;
+		}
+
+		ret = read_pb_oob(sftl, pnum);
+
+		if (ret) {
+			list_add(&this_pdata->list, &erase_list);
+			continue;
+		}
+
+		if (PDATA_STATUS(this_pdata) == STATUS_FREE) {
+			list_add(&this_pdata->list, &sftl->free_list);
+			continue;
+		}
+
+		/* get logical address, STATUS_ASSIGN/STATUS_BITFLIP */
+
+		if (!(sftl->laddr_tbl[PDATA_LNUM(this_pdata)]
+		      & SFTL_PNUM_NOASSIGN)) {
+			struct sftl_pdata *old_pdata =
+				&sftl->pdata_tbl[get_pnum_by_lnum(sftl,
+						  PDATA_LNUM(this_pdata))];
+
+			/* the lnum is already assigned. */
+
+			if (check_version(sftl, old_pdata, this_pdata)) {
+				/* Just put this to erase list */
+				list_add(&this_pdata->list, &erase_list);
+				continue;
+			} else {
+				/* Replace it, so put the old to erase list */
+				list_del(&old_pdata->list);
+				list_add(&old_pdata->list, &erase_list);
+			}
+		}
+
+		/* assign this */
+
+		sftl->laddr_tbl[PDATA_LNUM(this_pdata)] = pnum;
+
+		if (PDATA_STATUS(this_pdata) == STATUS_BITFLIP) {
+			sftl_info("P:0x%08x: bitflip block\n", pnum);
+			list_add(&this_pdata->list, &bitflip_list);
+		} else
+			list_add(&this_pdata->list, &sftl->assign_list);
+	}
+
+	/* handle erase blocks */
+
+	list_for_each_safe(pos, next, &erase_list) {
+		struct sftl_pdata *this_pdata =
+			list_entry(pos, struct sftl_pdata, list);
+		markerase_pb_putlist(sftl, get_pnum_by_pdata(sftl, this_pdata));
+	}
+
+	/* handle bitfliped blocks */
+
+	list_for_each_safe(pos, next, &bitflip_list) {
+		struct sftl_pdata *this_pdata =
+		    list_entry(pos, struct sftl_pdata, list);
+		read_lnum(sftl, PDATA_LNUM(this_pdata), sftl->cache_buf);
+		write_lnum(sftl, PDATA_LNUM(this_pdata), sftl->cache_buf);
+	}
+
+	return 0;
+}
+
+/****************************************************************************
+ *
+ * read/write w/ cache management functions
+ *
+ ****************************************************************************/
+
+static int sftl_cache_flush(struct sftl_dev *sftl)
+{
+	int ret = 0;
+
+	sftl_dbg("%s: L:0x%08x:STATUS 0x%02x\n", __func__,
+		sftl->cache_lnum, sftl->cache_state);
+
+	if ((sftl->cache_state & CACHE_STATUS_MASK) ==
+	    (CACHE_VALID | CACHE_DIRTY))
+		/* cache flush */
+		ret = write_lnum(sftl, sftl->cache_lnum, sftl->cache_buf);
+
+	sftl->cache_state = CACHE_EMPTY;	/* invalid, anyway. */
+
+	return ret;
+}
+
+static int fill_lnum_cache(struct sftl_dev *sftl, uint32_t lnum)
+{
+	int ret;
+
+	sftl_dbg("%s: L:0x%08x:STATUS 0x%02x\n", __func__,
+		sftl->cache_lnum, sftl->cache_state);
+
+	if ((sftl->cache_state & CACHE_VALID) && sftl->cache_lnum == lnum)
+		/* do nothing becase cache hit */
+		return 0;
+
+	ret = sftl_cache_flush(sftl);
+	if (ret)
+		goto out;
+
+	ret = read_lnum_scrub(sftl, lnum, sftl->cache_buf);
+
+ out:
+	if (!ret) {
+		sftl->cache_state = CACHE_VALID;
+		sftl->cache_lnum = lnum;
+	} else
+		sftl->cache_state = CACHE_EMPTY;
+
+	return ret;
+}
+
+/***** the following functions are mtd_blktrans_ops functions *****/
+
+static int sftl_readsect(struct mtd_blktrans_dev *dev, unsigned long snum,
+			 char *buf)
+{
+	struct sftl_dev *sftl = dev->priv;
+	uint32_t lnum, offs;
+	int ret;
+
+	if (get_lnum_by_snum(sftl, &lnum, &offs, snum)) {
+		ret = -EIO;
+		goto out;
+	}
+
+	sftl_dbg("%s: L:0x%08x::S:0x%08lx\n", __func__, lnum, snum);
+
+	if (get_pnum_by_lnum(sftl, lnum) & SFTL_PNUM_NOASSIGN) {
+		sftl_dbg("%s: NOASSIGN lnum\n", __func__);
+		memset(buf, 0xff, SFTL_SECTOR_SIZE);
+		ret = 0;
+		goto out;
+	}
+
+	ret = fill_lnum_cache(sftl, lnum);
+	if (ret)
+		goto out;
+
+	memcpy(buf, &sftl->cache_buf[offs], SFTL_SECTOR_SIZE);
+
+ out:
+	if (ret)
+		sftl_err("readsect error S:0x%8x::%d\n", (uint32_t) snum, ret);
+
+	return ret;
+}
+
+static int sftl_writesect(struct mtd_blktrans_dev *dev, unsigned long snum,
+			  char *buf)
+{
+	struct sftl_dev *sftl = dev->priv;
+	uint32_t lnum, offs;
+	int ret;
+
+	if (get_lnum_by_snum(sftl, &lnum, &offs, snum)) {
+		ret = -EIO;
+		goto out;
+	}
+
+	sftl_dbg("%s: L:0x%08x::S:0x%08lx\n", __func__, lnum, snum);
+
+	ret = fill_lnum_cache(sftl, lnum);
+	if (ret)
+		goto out;
+
+	sftl->cache_state |= CACHE_DIRTY;
+	memcpy(&sftl->cache_buf[offs], buf, SFTL_SECTOR_SIZE);
+ out:
+	if (ret)
+		sftl_err("writesect error S:0x%08x::%d\n", (uint32_t) snum,
+			 ret);
+
+	return ret;
+}
+
+static void sftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+	int i = 0;
+	struct sftl_dev *sftl;
+
+	for (i = 0; i < mtd_devs; i++)
+		if (mtd->index == mtd_dev_param[i])
+			break;
+
+	if (i == mtd_devs) {
+		pr_debug("sftl: not found a registered mtd%d\n", mtd->index);
+		return;
+	}
+
+	/* Check if this is NAND flash */
+	if (mtd->type != MTD_NANDFLASH) {
+		pr_err("sftl: mtd%d is not NAND Flash\n", mtd->index);
+		return;
+	}
+
+	sftl = kzalloc(sizeof(struct sftl_dev), GFP_KERNEL);
+	if (!sftl) {
+		pr_err("sftl:mtd%d: out of memory for data structures\n",
+		       mtd->index);
+		return;
+	}
+
+	sftl->mtd = mtd;
+
+	sftl->mbd = kzalloc(sizeof(struct mtd_blktrans_dev), GFP_KERNEL);
+	if (!sftl->mbd) {
+		sftl_err("out of memory for data structures\n");
+		goto out;
+	}
+	sftl->mbd->priv = sftl;
+	sftl->mbd->mtd = mtd;
+	sftl->mbd->devnum = mtd->index;
+	sftl->mbd->tr = tr;
+
+	sftl->mbd->disk_attributes = &sftl_attribute_group;
+
+	sftl->pblocks = (uint32_t) (mtd->size >> mtd->erasesize_shift);
+
+	if (sftl->pblocks < MINIMUM_PBLOCKS) {
+		sftl_err("%d PBLOCK is too small, larger than %d\n",
+			 sftl->pblocks, MINIMUM_PBLOCKS);
+		goto out;
+	}
+
+	sftl->lblocks =
+	    sftl->pblocks - (sftl->pblocks / 100) * reserve_blk_percent;
+
+	if (sftl->pblocks - sftl->lblocks < MINIMUM_RESERVE_PBLOCKS) {
+		sftl_warn("%d reserve block is too small, larger than %d\n",
+			  sftl->pblocks - sftl->lblocks,
+			  MINIMUM_RESERVE_PBLOCKS);
+		sftl->lblocks = sftl->pblocks - MINIMUM_RESERVE_PBLOCKS;
+	}
+
+	/* Initialize cache */
+	sftl->cache_state = CACHE_EMPTY;
+	sftl->cache_lnum = SFTL_PNUM_NOASSIGN;
+	sftl->cache_buf = kzalloc(mtd->erasesize, GFP_KERNEL);
+	if (!sftl->cache_buf) {
+		sftl_err("out of memory for data structures\n");
+		goto out;
+	}
+
+	sftl->mbd->size = (sftl->lblocks << mtd->erasesize_shift)
+		/ SFTL_SECTOR_SIZE;
+	sftl->badblocks = 0;
+
+	INIT_LIST_HEAD(&sftl->assign_list);
+	INIT_LIST_HEAD(&sftl->bad_list);
+	INIT_LIST_HEAD(&sftl->free_list);
+
+	/* Allocate pnum table indexed by lnum */
+	sftl->laddr_tbl = kmalloc(sizeof(uint32_t) * sftl->lblocks,
+				  GFP_KERNEL);
+	if (!sftl->laddr_tbl) {
+		sftl_err("out of memory for data structures\n");
+		goto out;
+	}
+
+	memset(sftl->laddr_tbl, 0xff, sizeof(uint32_t) * sftl->lblocks);
+
+	/* Allocate physical data table */
+	sftl->pdata_tbl = kzalloc(sizeof(struct sftl_pdata) * sftl->pblocks,
+				  GFP_KERNEL);
+
+	if (!sftl->pdata_tbl) {
+		sftl_err("out of memory for data structures\n");
+		goto out;
+	}
+
+	/* Build logical block map */
+	if (build_lblock_map(sftl) < 0) {
+		sftl_err("build_lblock_map failed\n");
+		goto out;
+	}
+
+	/* Register block device */
+	if (add_mtd_blktrans_dev(sftl->mbd)) {
+		sftl_err("add_mtd_blktrans_dev failed\n");
+		goto out;
+	}
+
+	sftl_info("%s: Physical Block %d, Logical blocks %d, "
+		  "Reserved blocks %d, Bad blocks %d\n",
+		  sftl->mbd->disk->disk_name, sftl->pblocks, sftl->lblocks,
+		  sftl->pblocks - sftl->lblocks, sftl->badblocks);
+
+	return;
+
+ out:
+	kfree(sftl->mbd);
+	kfree(sftl->pdata_tbl);
+	kfree(sftl->laddr_tbl);
+	kfree(sftl->cache_buf);
+	kfree(sftl);
+}
+
+static void sftl_remove_dev(struct mtd_blktrans_dev *dev)
+{
+	struct sftl_dev *sftl = dev->priv;
+	struct mtd_info *mtd = sftl->mtd;
+	int i;
+
+	sftl_dbg("sftl: remove_dev (%d)\n", dev->devnum);
+
+	for (i = 0; i < mtd_devs; i++)
+		if (mtd->index == mtd_dev_param[i])
+			break;
+
+	if (i == mtd_devs) {
+		sftl_warn("not found a registered mtd%d\n", mtd->index);
+		return;
+	}
+
+	del_mtd_blktrans_dev(dev);
+	kfree(sftl->pdata_tbl);
+	kfree(sftl->laddr_tbl);
+	kfree(sftl->cache_buf);
+	kfree(sftl);
+}
+
+static int sftl_release(struct mtd_blktrans_dev *dev)
+{
+	struct sftl_dev *sftl = dev->priv;
+
+	sftl_dbg("%s\n", __func__);
+
+	return sftl_cache_flush(sftl);
+}
+
+static int sftl_flush(struct mtd_blktrans_dev *dev)
+{
+	struct sftl_dev *sftl = dev->priv;
+
+	sftl_dbg("%s\n", __func__);
+
+	return sftl_cache_flush(sftl);
+}
+
+/****************************************************************************
+ *
+ * Module stuff
+ *
+ ****************************************************************************/
+
+static struct mtd_blktrans_ops sftl_tr = {
+	.name = "sftl",
+	.blksize = SFTL_SECTOR_SIZE,
+	.part_bits = SFTL_PARTN_BITS,
+	.flush = sftl_flush,
+	.release = sftl_release,
+	.readsect = sftl_readsect,
+	.writesect = sftl_writesect,
+	.add_mtd = sftl_add_mtd,
+	.remove_dev = sftl_remove_dev,
+	.owner = THIS_MODULE,
+};
+
+static int __init init_sftl(void)
+{
+	pr_info("SFTL version %s\n", SFTL_VERSION);
+
+	if (reserve_blk_percent > 90 || reserve_blk_percent < 1) {
+		pr_warn("sftl: the specified reserve_blk_percent is out of range.\n");
+		reserve_blk_percent = CONFIG_SFTL_RESERVE_BLK_PERCENT;
+		pr_warn("set default reserve_blk_percent %d\n",
+			reserve_blk_percent);
+	}
+
+	return register_mtd_blktrans(&sftl_tr);
+}
+
+static void __exit cleanup_sftl(void)
+{
+	deregister_mtd_blktrans(&sftl_tr);
+}
+
+static int __init sftl_mtd_parse(const char *val, struct kernel_param *kp)
+{
+	if (!val)
+		return -EINVAL;
+
+	if (mtd_devs == SFTL_MAX_DEVICES) {
+		pr_err("sftl: too many parameters, max. is %d\n",
+		       SFTL_MAX_DEVICES);
+		return -EINVAL;
+	}
+
+	if (kstrtoint(val, 0, &mtd_dev_param[mtd_devs]))
+		return -EINVAL;
+
+	mtd_devs++;
+
+	return 0;
+}
+
+module_param_call(mtd, sftl_mtd_parse, NULL, NULL, 000);
+MODULE_PARM_DESC(mtd, "MTD devices to attach. ");
+
+module_param_named(rb, reserve_blk_percent, int, 0);
+MODULE_PARM_DESC(rb, "percents of reserved blocks for bad blocks.");
+
+module_init(init_sftl);
+module_exit(cleanup_sftl);
+
+MODULE_AUTHOR("TOSHIBA Corporation");
+MODULE_DESCRIPTION("SFTL");
+MODULE_LICENSE("GPL");
-- 
1.7.4.1



More information about the linux-mtd mailing list