diff mbox

[1/1] Blockrom: badblock-free RO MTD blockdev access

Message ID 1336379956-17052-1-git-send-email-github@thilo-fromm.de
State New, archived
Headers show

Commit Message

Thilo Fromm May 7, 2012, 8:39 a.m. UTC
The blockrom MTD driver provides bad block free read access to MTDs via
	/dev/blockromX device files. Bad blocks are automatically skipped upon read,
	and reading continues with the next good block.

	This allows for read-only filesystems to exist in MTDs with bad blocks:
	usually the baddies are skipped when writing the filesystem into MTD by
	the tool performing the write (e.g. nandwrite). To successfully mount
	these filesystems, however, you will need a translation layer that skips
	bad blocks upon read as well. blockrom is such a translation layer.

Signed-off-by: Thilo Fromm <github@thilo-fromm.de>
---
 drivers/mtd/Kconfig    |   14 +++
 drivers/mtd/Makefile   |    1 +
 drivers/mtd/blockrom.c |  234 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 249 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mtd/blockrom.c

Comments

Thilo Fromm May 7, 2012, 9:57 a.m. UTC | #1
Hello *.*,

On 7 May 2012 10:39, Thilo Fromm <github@thilo-fromm.de> wrote:
>        The blockrom MTD driver provides bad block free read access to MTDs via
>        /dev/blockromX device files. Bad blocks are automatically skipped upon read,
>        and reading continues with the next good block.
>
>        This allows for read-only filesystems to exist in MTDs with bad blocks:
>        usually the baddies are skipped when writing the filesystem into MTD by
>        the tool performing the write (e.g. nandwrite). To successfully mount
>        these filesystems, however, you will need a translation layer that skips
>        bad blocks upon read as well. blockrom is such a translation layer.
>

Some remarks:
- moved bad block mapping table generation to driver initialisation time.
  Further optimization opportunities include e.g. doing table generation
  in the background (work queue or kernel task).
  The original driver did lazy block mapping, i.e. mapped when a block
  was accessed for the first time.
- driver handle already has "spare blocks" and "management blocks" fields
  which are currently unused but provide for addiition of a very simple
  scrubbing mechanism later on.

I understand that the main focus of the linux-mtd community is on
ubi_blk for generic r/w block access, one layer above MTD. I provide
this FTL for simple bad block-free r/o access directly on the MTD
level in the hope that it is useful for generic read-only FS. Comments
and suggestions are welcome.

I'm willing to put extra effort into blockrom should the linux-mtd
community be interested into a merge despite of ubi_blk.

Regards,
Thilo
diff mbox

Patch

diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index 1e2cbf5..bbcbabc 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -226,6 +226,20 @@  config MTD_BLOCK_RO
 	  You do not need this option for use with the DiskOnChip devices. For
 	  those, enable NFTL support (CONFIG_NFTL) instead.
 
+config MTD_BLOCK_ROM_BBFREE
+	tristate "Bad-block-skipiping readonly access to MTD devices"
+	depends on MTD_BLOCK!=y && MTD
+	help
+	  The blockrom MTD driver provides bad block free read access to MTDs via
+	  /dev/blockromX device files. Bad blocks are automatically skipped upon read,
+	  and reading continues with the next block.
+
+	  This allows for read-only filesystems to exist in MTDs with bad blocks:
+	  usually the baddies are skipped when writing the filesystem into MTD by
+	  the tool performing the write (e.g. nandwrite). To successfully mount 
+	  these filesystems, however, you will need a translation layer that skips 
+	  bad blocks upon read as well. blockrom is such a translation layer.
+
 config FTL
 	tristate "FTL (Flash Translation Layer) support"
 	depends on BLOCK
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 760abc5..2337097 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -19,6 +19,7 @@  obj-$(CONFIG_MTD_CHAR)		+= mtdchar.o
 obj-$(CONFIG_MTD_BLKDEVS)	+= mtd_blkdevs.o
 obj-$(CONFIG_MTD_BLOCK)		+= mtdblock.o
 obj-$(CONFIG_MTD_BLOCK_RO)	+= mtdblock_ro.o
+obj-$(CONFIG_MTD_BLOCK_ROM_BBFREE)	+= blockrom.o
 obj-$(CONFIG_FTL)		+= ftl.o
 obj-$(CONFIG_NFTL)		+= nftl.o
 obj-$(CONFIG_INFTL)		+= inftl.o
diff --git a/drivers/mtd/blockrom.c b/drivers/mtd/blockrom.c
new file mode 100644
index 0000000..b25496c
--- /dev/null
+++ b/drivers/mtd/blockrom.c
@@ -0,0 +1,234 @@ 
+/*
+ *  Readonly Block Device Layer Over MTD
+ *
+ *  (C) 2006 Baydarov Konstantin <kbaidarov@dev.rtsoft.ru>
+ *           Pantelis Antoniou <panto@intracom.gr>
+ *           David Woodhouse <dwmw2@infradead.org>
+ *  (C) 2012 DResearch Fahrzeugelektronik GmbH,
+ *           Thilo Fromm <kontakt@thilo-fromm.de>
+ *
+ *         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.
+ *
+ *
+ *        The blockrom MTD driver provides bad block free read access to MTDs via
+ *        /dev/blockromX device files. Bad blocks are automatically skipped upon read,
+ *        and reading continues with the next good block.
+ *
+ *        This allows for read-only filesystems to exist in MTDs with bad blocks:
+ *        usually the baddies are skipped when writing the filesystem into MTD by
+ *        the tool performing the write (e.g. nandwrite). To successfully mount 
+ *        these filesystems, however, you will need a translation layer that skips 
+ *        bad blocks upon read as well. blockrom is such a translation layer.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/blktrans.h>
+
+struct mtd_block_map {
+	struct  mtd_blktrans_dev dev;
+	/* block map for RO */
+	uint32_t *block_map;
+	uint32_t blocks_total;
+	uint32_t blocks_bad;
+	uint32_t blocks_spare;
+	uint32_t blocks_mgmt;
+};
+
+/*
+ * private functions
+ */
+
+static size_t user_blocks(struct mtd_block_map * map)
+{
+	return    map->blocks_total
+		- map->blocks_bad
+		- map->blocks_spare
+		- map->blocks_mgmt;
+}
+
+static void free_map(struct mtd_block_map * map)
+{
+	if (map) {
+		if (map->block_map)
+			kfree (map->block_map);
+		kfree (map);
+	}
+}
+
+static uint32_t map_block(struct mtd_block_map * map, int32_t block)
+{
+	uint32_t mapped_start = map->block_map[ block ],
+	 	 mapped;
+
+	for (mapped = mapped_start; mapped < map->blocks_total; mapped++)
+		if (0 == map->dev.mtd->block_isbad(
+			map->dev.mtd, mapped * map->dev.mtd->erasesize))
+			break;
+
+	/* store mapping of the current source block */
+	map->block_map[ block ] = mapped;
+
+	/* set next map entry so we can continue where we left */
+	if (block + 1 < map->blocks_total)
+		map->block_map[ block + 1 ] = mapped + 1;
+
+	return mapped;
+}
+
+static void map_all_blocks(struct mtd_block_map * map, struct mtd_info * mtd)
+{
+	unsigned int block, 
+		     expect_mapped = 0;
+
+	for (block=0; block < map->blocks_total; block++) {
+		uint32_t mapped;
+		
+		mapped = map_block (map, block);
+		map->blocks_bad += (mapped - expect_mapped);
+
+		expect_mapped = mapped + 1;
+	}
+}
+
+static struct mtd_block_map * init_map(struct mtd_info * mtd) 
+{
+	struct mtd_block_map *map = kzalloc(sizeof(*map), GFP_KERNEL);
+
+	if (map == NULL)
+		return NULL;
+
+	map->dev.mtd 	= mtd;
+	map->dev.devnum	= mtd->index;
+
+	map->blocks_total  = div_u64(mtd->size, mtd->erasesize);
+	map->block_map = kzalloc(sizeof(*map->block_map) * map->blocks_total, 
+			GFP_KERNEL);
+	if (map->block_map == NULL){
+		free_map(map);
+		return NULL;
+	}
+
+	/* init first map entry, then fill mapping table */
+	map->block_map[0]  = 0;
+	map_all_blocks(map, mtd);
+
+	map->dev.size 	  = user_blocks(map) * (mtd->erasesize / 512);
+	map->dev.readonly = 1;
+
+	return map;
+}
+
+static void print_mtd_info(struct mtd_block_map * map)
+{
+	printk(KERN_INFO "blockrom%d: %6d KiB; EBs %6d user, "
+		"%6d spare, %6d mgmt, %6d bad, %6d toal\n",
+		map->dev.mtd->index, 
+		user_blocks(map) * map->dev.mtd->erasesize / 1024, 
+		user_blocks(map),
+		map->blocks_spare, map->blocks_mgmt, map->blocks_bad, map->blocks_total);
+}
+
+/*
+ * mtd_blktrans_dev interface implementation
+ */
+
+static int blockrom_readsect(struct mtd_blktrans_dev *dev,
+			      unsigned long block, char *buf)
+{
+	size_t retlen;
+	uint32_t flash_block;
+	struct mtd_block_map *map = container_of(dev, struct mtd_block_map, dev);
+	loff_t addr, offs;
+
+	/* convert HDD block no. to flash block no. */
+	flash_block = (block * 512) / map->dev.mtd->erasesize;
+
+	if (flash_block >= map->blocks_total) {
+		printk(KERN_ERR "blockrom%d: "
+				"trying to access beyond end of device.",
+				map->dev.devnum);
+		return -ENXIO;
+	}
+
+	offs  = map->block_map[ flash_block ];
+	offs *= map->dev.mtd->erasesize;
+
+	addr = offs | ((block * 512) & (map->dev.mtd->erasesize - 1));
+
+	return dev->mtd->read(dev->mtd, addr, 512, &retlen, buf);
+}
+
+static int blockrom_writesect(struct mtd_blktrans_dev *dev,
+			      unsigned long block, char *buf)
+{
+	size_t retlen;
+
+	if (dev->mtd->write(dev->mtd, (block * 512), 512, &retlen, buf))
+		return 1;
+
+	return 0;
+}
+
+static void blockrom_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+	struct mtd_block_map * map;
+
+	/* if no bad block checking is possible we don't handle the MTD */
+	if (mtd->block_isbad == NULL)
+		return;
+
+	map = init_map(mtd);
+
+	if (NULL == map)
+		return;
+
+	map->dev.tr  = tr;
+
+	if (add_mtd_blktrans_dev(&(map->dev)))
+		free_map(map);
+	else
+		print_mtd_info(map);
+}
+
+static void blockrom_remove_dev(struct mtd_blktrans_dev *dev)
+{
+	struct mtd_block_map *map = container_of(dev, struct mtd_block_map, dev);
+
+	del_mtd_blktrans_dev(dev);
+	free_map(map);
+}
+
+static struct mtd_blktrans_ops blockrom_tr = {
+	.name		= "blockrom",
+	.major		= 258,
+	.part_bits	= 0,
+	.blksize	= 512,
+	.readsect	= blockrom_readsect,
+	.writesect	= blockrom_writesect,
+	.add_mtd	= blockrom_add_mtd,
+	.remove_dev	= blockrom_remove_dev,
+	.owner		= THIS_MODULE,
+};
+
+static int __init blockrom_init(void)
+{
+	return register_mtd_blktrans(&blockrom_tr);
+}
+
+static void __exit blockrom_exit(void)
+{
+	deregister_mtd_blktrans(&blockrom_tr);
+}
+
+module_init(blockrom_init);
+module_exit(blockrom_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Baydarov Konstantin <kbaidarov@dev.rtsoft.ru>");
+MODULE_AUTHOR("Thilo Fromm <kontakt@thilo-fromm.de>");
+MODULE_DESCRIPTION("Readonly Bad-Block Skipping Block Device Translation Layer");