[PATCH 8/9] mtd: SmartMedia/xD FTL

Maxim Levitsky maximlevitsky at gmail.com
Wed Jan 6 16:53:45 EST 2010


>From d9e0e1a8cb54011ed2775aa82e71e1a5ba3b5880 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <maximlevitsky at gmail.com>
Date: Wed, 6 Jan 2010 23:06:24 +0200
Subject: [PATCH 8/9] mtd: SmartMedia/xD FTL

---
 drivers/mtd/Kconfig  |   12 +
 drivers/mtd/Makefile |    1 +
 drivers/mtd/sm_ftl.c | 1070 ++++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/mtd/sm_ftl.h |   79 ++++
 4 files changed, 1162 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mtd/sm_ftl.c
 create mode 100644 drivers/mtd/sm_ftl.h

diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index ebeabd6..e13bf41 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -313,6 +313,18 @@ config SSFDC
 	  This enables read only access to SmartMedia formatted NAND
 	  flash. You can mount it with FAT file system.
 
+
+config SM_FTL
+	tristate "SmartMedia/xD new translation layer"
+	depends on EXPERIMENTAL
+	select MTD_SM_COMMON
+	help
+	  This enables new and very EXPERMENTAL support for SmartMedia/xD
+	  FTL (Flash tanslation layer)
+	  Write support isn't yet well tested, therefore this code IS likely to
+	  eat your card, so please don't use it together with valuable data.
+	  Use readonly driver (CONFIG_SSFDC) instead.
+
 config MTD_OOPS
 	tristate "Log panic/oops to an MTD buffer"
 	depends on MTD
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 02c5b17..02f6375 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_NFTL)		+= nftl.o
 obj-$(CONFIG_INFTL)		+= inftl.o
 obj-$(CONFIG_RFD_FTL)		+= rfd_ftl.o
 obj-$(CONFIG_SSFDC)		+= ssfdc.o
+obj-$(CONFIG_SM_FTL)		+= sm_ftl.o
 obj-$(CONFIG_MTD_OOPS)		+= mtdoops.o
 
 nftl-objs		:= nftlcore.o nftlmount.o
diff --git a/drivers/mtd/sm_ftl.c b/drivers/mtd/sm_ftl.c
new file mode 100644
index 0000000..41b52ac
--- /dev/null
+++ b/drivers/mtd/sm_ftl.c
@@ -0,0 +1,1070 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * SmartMedia/xD translation layer
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/random.h>
+#include <linux/hdreg.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <asm/bitops.h>
+#include "sm_common.h"
+#include "sm_ftl.h"
+
+static u8 tmp_buffer[SM_SECTOR_SIZE];
+static int cache_size = 5;
+
+module_param(cache_size, int, S_IRUGO);
+MODULE_PARM_DESC(cache_size,
+		"Number of blocks to hold in the cache (5 default)");
+
+
+static void sm_erase_callback (struct erase_info *self);
+static void sm_erase_block(struct sm_ftl *ftl, int zone_num, s16 block,
+								int put_free);
+static void sm_mark_block_bad(struct sm_ftl *ftl, int zone_num, int block);
+static int sm_cache_flush_thread(void *data);
+
+
+static const struct chs_entry chs_table[] = {
+	{ 1,    125,  4,  4  },
+	{ 2,    125,  4,  8  },
+	{ 4,    250,  4,  8  },
+	{ 8,    250,  4,  16 },
+	{ 16,   500,  4,  16 },
+	{ 32,   500,  8,  16 },
+	{ 64,   500,  8,  32 },
+	{ 128,  500,  16, 32 },
+	{ 256,  1000, 16, 32 },
+	{ 512,  1015, 32, 63 },
+	{ 1024, 985,  33, 63 },
+	{ 2048, 985,  33, 63 },
+	{ 0 },
+};
+
+/* Find out media parameters.
+ * This ideally has to be based on nand id, but for now device size is enough */
+int sm_get_media_info (struct sm_ftl *ftl, struct mtd_info *mtd)
+{
+	int i;
+	int size_in_megs = mtd->size / (1024 * 1024);
+	ftl->readonly = mtd->type == MTD_ROM;
+
+	/* Manual settings for very old devices */
+	ftl->zone_count = 1;
+	ftl->smallpagenand = 0;
+
+	switch (size_in_megs){
+	case 1:
+		/* 1 Mb flas/rom SmartMedia card (256 byte pages)*/
+		ftl->zone_size = 256;
+		ftl->max_lba = 250;
+		ftl->block_size = 8 * SM_SECTOR_SIZE;
+		ftl->smallpagenand = 1;
+
+		break;
+	case 2:
+		/* 2 Mb flash SmartMedia (256 byte pages)*/
+		if (!mtd->writesize == SM_SMALL_PAGE) {
+			ftl->zone_size = 512;
+			ftl->max_lba = 500;
+			ftl->block_size = 8 * SM_SECTOR_SIZE;
+			ftl->smallpagenand = 1;
+		/* 2 Mb rom SmartMedia */
+		} else {
+			ftl->zone_size = 256;
+			ftl->max_lba = 250;
+			ftl->block_size = 16 * SM_SECTOR_SIZE;
+		}
+		break;
+	case 4:
+		/* 4 Mb flash/rom SmartMedia device */
+		ftl->zone_size = 512;
+		ftl->max_lba = 500;
+		ftl->block_size = 16 * SM_SECTOR_SIZE;
+		break;
+	case 8:
+		/* 8 Mb flash/rom SmartMedia device */
+		ftl->zone_size = 1024;
+		ftl->max_lba = 1000;
+		ftl->block_size = 16 * SM_SECTOR_SIZE;
+	}
+
+	/* Minimum xD size is 16M, and thus all xD cards have standard zone
+	   sizes. SmartMedia cards exist up to 128 Mb and have same layout*/
+	if (size_in_megs >= 16) {
+		ftl->zone_count = size_in_megs / 16;
+		ftl->zone_size = 1024;
+		ftl->max_lba = 1000;
+		ftl->block_size = 32 * SM_SECTOR_SIZE;
+	}
+
+	/* Test for proper write and erase sizes */
+	if (mtd->erasesize > ftl->block_size)
+		return -ENODEV;
+
+	if (mtd->writesize > SM_SECTOR_SIZE)
+		return -ENODEV;
+
+	/* For now, don't support small page nand */
+	if (ftl->smallpagenand)
+		return -ENODEV;
+
+	/* This shouldn't happen */
+	if (ftl->zone_count * ftl->zone_size * ftl->block_size != mtd->size)
+		return -ENODEV;
+
+	/* Find geometry information */
+	for (i = 0 ; i < ARRAY_SIZE(chs_table) ; i++)
+		 if (chs_table[i].size == size_in_megs) {
+			ftl->cylinders = chs_table[i].cyl;
+			ftl->heads = chs_table[i].head;
+			ftl->sectors = chs_table[i].sec;
+			return 0;
+		}
+
+	ftl->cylinders = 985;
+	ftl->heads =  33;
+	ftl->sectors = 63;
+	return 0;
+}
+
+/* Make offset from parts */
+static loff_t sm_mkoffset(struct sm_ftl *ftl, int zone, int block, int boffset)
+{
+	WARN_ON(boffset & (SM_SECTOR_SIZE - 1));
+	WARN_ON(zone < 0 || zone >= ftl->zone_count);
+	WARN_ON(block >= ftl->zone_size);
+	WARN_ON(boffset > ftl->block_size);
+
+	if (block == -1)
+		return -1;
+
+	return (zone * ftl->zone_size + block) * ftl->block_size + boffset;
+}
+
+/* Breaks offset into parts */
+static void sm_break_offset(struct sm_ftl *ftl, loff_t offset,
+					int *zone, int *block, int *boffset)
+{
+	*boffset = offset % ftl->block_size;
+	offset /= ftl->block_size;
+	*block = offset % ftl->max_lba;
+	offset /= ftl->max_lba;
+
+	if (offset >= ftl->zone_count)
+		dbg("sm_break_offset: try to access a zone %lx",
+						(long unsigned int)offset);
+
+	*zone = offset >= ftl->zone_count ? -1 : offset;
+}
+
+/* Reads a sector + oob*/
+static int sm_read_sector(struct sm_ftl *ftl,
+		int zone, int block, int boffset,
+				u8* buffer, struct sm_oob *oob)
+{
+	struct mtd_oob_ops ops;
+	struct sm_oob tmp_oob;
+	struct mtd_info *mtd = ftl->trans->mtd;
+	int ret;
+	loff_t offset;
+
+	ops.len = SM_SECTOR_SIZE;
+	ops.datbuf = buffer;
+
+	if (!oob)
+		oob = &tmp_oob;
+
+	if (!mtd)
+		return -ENODEV;
+
+	/* TODO: raw mode doesn't check the ecc + reads to data buffer*/
+	/* This works only with empty oob layout */
+	ops.mode = MTD_OOB_PLACE; 
+	ops.ooboffs = 0;
+	ops.ooblen = SM_OOB_SIZE;
+	ops.oobbuf = (void*)oob;
+
+	/* FTL can contain -1 entries that are by default filled with bits */
+	if (block == -1) {
+
+		if (buffer)
+			memset(buffer, 0xFF, SM_SECTOR_SIZE);
+		memset(oob, 0xFF, SM_OOB_SIZE);
+		return 0;
+	}
+
+	offset = sm_mkoffset(ftl, zone, block, boffset);
+	ret = mtd->read_oob(mtd, offset, &ops);
+
+	if (ret) {
+		return -EIO;
+		dbg("read of sector at 0x%lx failed with error %d",
+					(long unsigned int)offset, ret);
+	}
+
+	if (ops.oobretlen != SM_OOB_SIZE) {
+		dbg("read of sector at 0x%lx failed with wrong oob len %d",
+				(long unsigned int)offset, (int)ops.oobretlen);
+		return -EIO;
+	}
+
+	if (buffer && sm_sector_valid(oob)) {
+		dbg("read of sector at 0x%lxfailed because "
+			"it is marked as invalid",
+						(long unsigned int)offset);
+		return -EIO;
+	}
+
+	/* TODO: for small page flash, we need to check ecc here,
+		because nand subsystem really can't handle this... */
+
+	return 0;	
+}
+
+/* Write a block using data and lba */
+static int sm_write_block (struct sm_ftl *ftl, u8 *buf,
+					int zone_num, int block, int lba)
+{
+	struct mtd_oob_ops ops;
+	struct mtd_info *mtd = ftl->trans->mtd;
+	int boffset;
+	loff_t offset;
+	int retry;
+
+	struct sm_oob oob;
+	memset(&oob, 0xFF, sizeof(oob));
+	sm_write_lba(&oob, lba);
+
+	if(!mtd)
+		return -ENODEV;
+
+	if (zone_num == 0 && (block == ftl->cis_block || block == 0)) {
+		dbg("attempted to write the CIS!");
+		return -EIO;
+	}
+
+	/* TODO: for small page flash, we need to compute & write ecc here,
+		 because nand subsystem really can't handle this... */
+
+
+	ops.len = SM_SECTOR_SIZE;
+
+	/* TODO: raw mode doesn't check the ecc + reads to data buffer*/
+	/* This works only with empty oob layout */
+
+	ops.mode = MTD_OOB_PLACE;
+	ops.ooboffs = 0;
+	ops.ooblen = SM_OOB_SIZE;
+	ops.oobbuf = (void*)&oob;
+
+	/* Use write_oob here because some xD cards only accept writes that
+		contain both page and oob write. These cards most likely
+		do their own ftl */
+
+	offset = sm_mkoffset (ftl, zone_num, block, 0);
+
+restart:
+	for (boffset = 0; boffset < ftl->block_size;
+				boffset += SM_SECTOR_SIZE) {
+
+		ops.datbuf = buf + boffset;
+
+		if (!ftl->trans->mtd->write_oob(ftl->trans->mtd,
+						offset + boffset, &ops))
+			continue;
+
+		if (!retry) {
+			dbg("write of block %d in zone %d failed, erasing it",
+							block, zone_num);
+
+			/* If write fails. try to erase the block */
+			sm_erase_block(ftl, zone_num, block, 0);
+			retry = 1;
+			goto restart;
+		} else {
+			dbg("write of block %d in zone %d failed again"
+				", marking as bad", block, zone_num);
+
+			sm_mark_block_bad(ftl, zone_num, block);
+			return -EIO;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Erase a block within a zone
+ * If erase succedes, it updates free block fifo
+ */
+static void sm_erase_block(struct sm_ftl *ftl, int zone_num, s16 block,
+								int put_free)
+{
+	struct ftl_zone *zone = &ftl->zones[zone_num];
+	struct erase_info erase;
+	struct mtd_info *mtd = ftl->trans->mtd;
+
+	if(!mtd)
+		return;
+
+	erase.mtd = ftl->trans->mtd;
+	erase.callback = sm_erase_callback;
+	erase.addr = sm_mkoffset(ftl, zone_num, block, 0);
+	erase.len = ftl->block_size;
+	erase.priv = (u_long)ftl;
+
+	if (zone_num == 0 && (block == ftl->cis_block || block == 0)) {
+		dbg("attempted to erase the CIS!");
+		return;
+	}
+
+	if (ftl->trans->mtd->erase(ftl->trans->mtd, &erase)) {
+		dbg("erase of block %d in zone %d failed in mtd->erase call",
+			block, zone_num);
+		goto error;
+	}
+
+	wait_for_completion(&ftl->erase_completion);
+
+	if(ftl->erase_error) {
+		dbg("erase of block %d in zone %d failed after wait",
+			block, zone_num);
+		goto error;
+	}
+	
+	if (put_free)
+		kfifo_put(zone->free_sectors, (const unsigned char *)&block, 2);
+	return;
+
+error:
+	sm_mark_block_bad(ftl, zone_num, block);
+	return;
+}
+
+static void sm_erase_callback (struct erase_info *self)
+{
+	struct sm_ftl *ftl = (struct sm_ftl *)self->priv;
+	ftl->erase_error = (self->state == MTD_ERASE_FAILED);
+	complete(&ftl->erase_completion);
+}
+
+
+/*
+ * Throughtly test that block is valid and belongs
+ * to specified LBA. Tries to erase it if not
+ */
+static int sm_check_block_lba (struct sm_ftl *ftl, int zone, int block, int lba)
+{
+	int boffset;
+	struct sm_oob oob;
+	int tmp;
+
+	for (boffset = 0; boffset < ftl->block_size;
+					boffset += SM_SECTOR_SIZE) {
+
+		if (sm_read_sector(ftl, zone, block, boffset, tmp_buffer,
+								&oob)) {
+			dbg("block check: fail in sector %d in zone %d",
+				block, zone);
+			goto erase;
+		}
+
+		if (sm_block_valid(&oob) || sm_sector_valid(&oob)) {
+			dbg("block check: block/sector status invalid"
+				" for sector %d in zone %d",
+				block, zone);
+			goto erase;
+		}
+
+		if ((tmp = sm_read_lba(&oob)) != lba) {
+			dbg("block check: block offset %d, we get "
+				"different LBA (%d), should get %d",
+				boffset, tmp, lba);
+			goto erase;
+		}
+	}
+	return 0;
+erase:
+	sm_erase_block(ftl, zone, block, 1);
+	return -EIO;
+}
+
+/* Mark whole block at offset 'offs' as bad.
+   We don't use mtd functions, because we know exectly how to do that
+*/
+static void sm_mark_block_bad(struct sm_ftl *ftl, int zone_num, int block) {
+
+	struct mtd_oob_ops ops;
+	struct sm_oob oob;
+	int boffset;
+	int offset = sm_mkoffset(ftl, zone_num, block, 0);
+	struct mtd_info *mtd = ftl->trans->mtd;
+
+	if(!mtd)
+		return;
+
+	dbg("marking block %d of zone %d as bad", block, zone_num);
+
+	memset (&oob, 0xFF, sizeof(oob));
+	oob.block_status = 0xF0;
+
+	ops.mode = MTD_OOB_RAW;
+	ops.ooboffs = 0;
+	ops.ooblen = SM_OOB_SIZE;
+	ops.oobbuf = (void*)&oob;
+	ops.datbuf = NULL;
+
+	/* We aren't checking the return value, because we don't care */
+	for (boffset = 0; boffset < ftl->block_size; boffset += SM_SECTOR_SIZE)
+		mtd->write_oob(mtd, offset + boffset, &ops);
+}
+
+/*
+ * Initialize FTL mapping for one zone
+ */
+struct ftl_zone* sm_initialize_zone(struct sm_ftl *ftl, int zone_num)
+{
+	struct sm_oob oob;
+	struct ftl_zone *zone;
+	u16 block;
+	int lba;
+	int i = 0;
+
+	if (zone_num >= ftl->zone_count || zone_num < 0) {
+		dbg("invalid zone (%d) was attempted to initialize", zone_num);
+		return (struct ftl_zone*)-ENODEV;
+	}
+
+	zone = &ftl->zones[zone_num];
+	if (zone->initialized)
+		return zone;
+
+	dbg("initializing zone %d", zone_num);
+
+	if (!(zone->lba_to_phys_table = kmalloc(ftl->max_lba * 2, GFP_KERNEL)))
+		return ERR_PTR(-ENOMEM);
+
+	zone->free_sectors = kfifo_alloc (ftl->zone_size * 2,
+		GFP_KERNEL, &ftl->fifo_lock);
+
+	if (!zone->free_sectors) {
+		kfree(zone->lba_to_phys_table);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	memset(zone->lba_to_phys_table, -1, ftl->max_lba * 2);
+
+	for (block = 0 ; block < ftl->zone_size ; block++) {
+
+		/* Skip blocks till the CIS (including) */
+		if (zone_num == 0 && block <= ftl->cis_block) {
+
+			if (block != ftl->cis_block)
+				dbg ("skipping block %d because"
+					" it is before the CIS (%d)",
+						block, ftl->cis_block);
+			continue;
+		}
+
+		/* Not much that we can do with blocks without
+		 * even readable oob... - skip*/
+		/* Shouldn't happen though */
+		if (sm_read_sector(ftl, zone_num, block, 0, NULL, &oob)) {
+			dbg ("skipping block %d because it's "
+				"oob was unreadable", block);
+			continue;
+		}
+
+		/* Blocks with 0xFFs in the oob area are assumed free -
+			add to free table*/
+		lba = sm_read_lba(&oob);
+		if (lba == -1) {
+			kfifo_put(zone->free_sectors,
+				(unsigned char *)&block, 2);
+			continue;
+		}
+
+		/* Blocks that are marked as invalid aren't for sure usable */
+		/* If such block has correct LBA and no other block has it,
+			return errors on read */
+		if (sm_block_valid(&oob)) {
+			if (lba >= 0 && lba < ftl->max_lba)
+				zone->lba_to_phys_table[lba] = -2;
+			dbg ("skipping block %d because it was marked bad",
+									block);
+			continue;
+		}
+
+		/* Try to erase blocks that have invalid LBA,
+			but marked as valid */
+		if (lba >= ftl->max_lba || lba == -2) {
+			dbg ("erasing block %d because it has invalid LBA (%d)",
+				 block, lba);
+
+			sm_erase_block(ftl, zone_num, block, 1);
+			continue;
+		}
+
+		/* If there is no collision,
+			just put the sector in the FTL table */
+		if(zone->lba_to_phys_table[lba] < 0) {
+			//dbg("LBA %04d -> PH %04d", lba, block);
+			zone->lba_to_phys_table[lba] = block;
+			continue;
+		}
+
+		dbg ("collision of LBA %d between blocks %d and %d in zone %d",
+			lba, zone->lba_to_phys_table[lba], block, zone_num);
+
+		/* Otherwise, carefully see if one of them is invalid*/
+		if (sm_check_block_lba(ftl, zone_num, block, lba))
+			continue;
+
+		if (sm_check_block_lba(ftl, zone_num,
+				zone->lba_to_phys_table[lba], lba))
+			continue;
+
+		/* Now both blocks are valid and share same LBA...
+		   I guess only solution is to throw a dice.... */
+		dbg("erasing the later");
+		sm_erase_block(ftl, zone_num, block, 1);
+	}
+
+	dbg("zone initialized");
+	zone->initialized = 1;
+
+	/* No free sectors, means that the zone is heavily damaged, write won't
+		work, but it can still can be (partially) read */
+	if (!kfifo_len(zone->free_sectors)) {
+		dbg("no free blocks in zone %d", zone_num);
+		return zone;
+	}
+
+	return zone;
+
+	/* Randomize first block we write to */
+	get_random_bytes(&i, 2);
+	i %= (kfifo_len(zone->free_sectors) / 2);
+
+
+	while (i--) {
+		kfifo_get(zone->free_sectors, (unsigned char *)&block, 2);
+		kfifo_put(zone->free_sectors, (const unsigned char *)&block, 2);
+	}
+	return zone;
+}
+
+/* Write one cached block to hardware */
+int sm_cache_block_write(struct sm_ftl *ftl, struct cached_block *cache_entry)
+{
+	struct ftl_zone *zone = &ftl->zones[cache_entry->zone];
+
+	int sector_num;
+	u16 write_sector;
+	int zone_num = cache_entry->zone;
+	int block_num;
+
+	if (zone_num < 0) {
+		dbg("invalid zone num given.... %d", zone_num);
+		return -1;
+	}
+
+	block_num = zone->lba_to_phys_table[cache_entry->lba];
+
+
+	/* Read all unread areas of the cache block*/
+	for_each_bit(sector_num, &cache_entry->data_invalid_bitmap,
+		ftl->block_size / SM_SECTOR_SIZE) {
+
+
+		if (sm_read_sector(ftl,
+			zone_num, block_num, sector_num * SM_SECTOR_SIZE,
+			cache_entry->data + sector_num * SM_SECTOR_SIZE, NULL))
+			return -EIO;
+	}
+restart:
+	/* No spare blocks */
+	/* We could still continue by erasing the current block,
+		but for such worn out media it doesn't worth the trouble,
+			and the dangers */
+
+	if (!kfifo_len(zone->free_sectors)) {
+		dbg("no free sectors for write!");
+		return -EIO;
+	}
+
+	kfifo_get(zone->free_sectors, (unsigned char *)&write_sector, 2);
+
+	if (sm_write_block(ftl, cache_entry->data, zone_num, write_sector,
+							cache_entry->lba))
+			goto restart;
+
+	/* Update the FTL table */
+	zone->lba_to_phys_table[cache_entry->lba] = write_sector;
+
+	/* Write succesfull, so erase and free the old block */
+	if (block_num > 0)
+		sm_erase_block(ftl, zone_num, block_num, 1);
+	return 0;
+}
+
+
+/* Initialize new/used cache entry */
+static int sm_cache_block_init(struct sm_ftl *ftl,
+				struct cached_block *cache_entry)
+{
+	if (!cache_entry->data)
+		cache_entry->data = kmalloc(ftl->block_size, GFP_KERNEL);
+
+	if (!cache_entry->data)
+		return -ENOMEM;
+
+	cache_entry->data_invalid_bitmap = 0xFFFFFFFF;
+	cache_entry->lba = -1;
+	cache_entry->zone = -1;
+	
+	return 0;
+}
+
+
+/* Flushes write cache, have to be run with ->cache_lock held */
+static int __sm_cache_flush(struct sm_ftl *ftl)
+{
+	struct cached_block *cache_entry = NULL, *tmp_entry;
+	struct mtd_info *mtd = ftl->trans->mtd;
+	int error;
+
+	if (ftl->readonly)
+		return  -EROFS;
+
+	if (list_empty(&ftl->cache))
+		return 0;
+
+	if(!mtd)
+		return -ENODEV;
+
+	list_for_each_entry_safe(cache_entry, tmp_entry, &ftl->cache,
+								list_member) {
+
+		/* Write should never fail, unless media is worn out */
+		if (sm_cache_block_write(ftl, cache_entry)) {
+			dbg("sm_ftl: failed to write block %d at zone %d",
+				(int)cache_entry->lba, cache_entry->zone);
+			ftl->readonly = 1;
+			return -EIO;
+		}
+
+		list_del(&cache_entry->list_member);
+		list_add(&cache_entry->list_member, &ftl->free_cache);
+
+		if ((error = sm_cache_block_init(ftl, cache_entry)))
+			return error;
+	}
+	return 0;
+}
+
+
+/* Flushes the write cache */
+static int sm_cache_flush(struct sm_ftl *ftl)
+{
+	int retval;
+	mutex_lock(&ftl->cache_mutex);
+	retval = __sm_cache_flush(ftl);
+	mutex_unlock(&ftl->cache_mutex);
+	return retval;
+}
+
+/* Frees the write cache */
+static void sm_free_cache(struct sm_ftl *ftl)
+{
+	struct cached_block *cache_entry;
+
+	mutex_lock(&ftl->cache_mutex);
+	while(!list_empty(&ftl->free_cache)) {
+		cache_entry = list_first_entry(&ftl->free_cache,
+				struct cached_block, list_member);
+
+		kfree(cache_entry->data);
+		list_del(&cache_entry->list_member);
+		kfree(cache_entry);
+	}
+	mutex_unlock(&ftl->cache_mutex);
+}
+
+
+/* outside interface: open the device */
+static int sm_open(struct mtd_blktrans_dev *dev)
+{
+	struct sm_ftl *ftl = dev->priv;
+	ftl->flush_thread = kthread_run (sm_cache_flush_thread,
+				ftl, "smflush%d", dev->mtd->index);
+
+	if (IS_ERR(ftl->flush_thread))
+		return PTR_ERR(ftl->flush_thread);
+	return 0;
+}
+
+/* outside interface: read a sector */
+static int sm_read(struct mtd_blktrans_dev *dev,
+				unsigned long sect_no, char *buf)
+{
+	struct sm_ftl *ftl = dev->priv;
+	struct ftl_zone *zone;
+	struct cached_block *cache_entry = NULL;
+	int error = 0;
+	int cache_found = 0;
+
+	int zone_num, block, boffset;
+
+	sm_break_offset(ftl, sect_no << 9, &zone_num, &block, &boffset);
+
+	zone = sm_initialize_zone(ftl, zone_num);
+	if (IS_ERR (zone))
+		return PTR_ERR(zone);
+
+	mutex_lock(&ftl->cache_mutex);
+
+	/* Have to look at cache first */
+	list_for_each_entry(cache_entry, &ftl->cache, list_member)
+		if (cache_entry->zone == zone_num &&
+			cache_entry->lba == block &&
+
+			!test_bit(boffset / SM_SECTOR_SIZE,
+				&cache_entry->data_invalid_bitmap)) {
+
+			memcpy(buf, cache_entry->data + boffset, SM_SECTOR_SIZE);
+			goto unlock;
+		}
+
+
+	/* Translate the block and return if doesn't exist in the table */
+	block = zone->lba_to_phys_table[block];
+
+	if (block == -1) {
+		memset(buf, 0xFF, SM_SECTOR_SIZE);
+		goto unlock;
+	}
+
+	if (block == -2) {
+		dbg("read block %d of zone %d marked invalid in the ftl",
+							block, zone_num);
+		error = -EIO;
+		goto unlock;
+	}
+
+	/* Do the read. The below relies on proper ftl setup and underlying
+	  driver to check at least the ecc
+	*/
+	if (sm_read_sector(ftl, zone_num, block, boffset, buf, NULL)) {
+		error = -EIO;
+		goto unlock;
+	}
+
+	/* If we already have the cache entry, then add the data there, because
+		we will need it anyway..*/
+	if (cache_found) {
+		memcpy(cache_entry->data + boffset, buf, SM_SECTOR_SIZE);
+		clear_bit(boffset / SM_SECTOR_SIZE,
+				&cache_entry->data_invalid_bitmap);
+	}
+unlock:
+	mutex_unlock(&ftl->cache_mutex);
+	return error;
+}
+
+
+/* outside interface: write a sector */
+static int sm_write(struct mtd_blktrans_dev *dev,
+				unsigned long sec_no, char *buf)
+{
+	struct sm_ftl *ftl = dev->priv;
+	struct ftl_zone *zone;
+	struct cached_block *cache_entry = NULL;
+	int error;
+	int zone_num, block, boffset;
+	int cache_found = 0;
+
+	if (ftl->readonly)
+		return -EROFS;
+
+	sm_break_offset(ftl, sec_no << 9, &zone_num, &block, &boffset);
+
+	zone = sm_initialize_zone(ftl, zone_num);
+	if (IS_ERR(zone))
+		return PTR_ERR(zone);
+
+	/* Try to write the cache if possible */
+	mutex_lock(&ftl->cache_mutex);
+
+	/* Try to find existing cache entry */
+	list_for_each_entry(cache_entry, &ftl->cache, list_member)
+		if (cache_entry->zone == zone_num &&
+				cache_entry->lba == block) {
+			cache_found = 1;
+			break;
+		}
+
+	/* Entry not in the cache, create new cache entry */
+	if (!cache_found) {
+
+		/* Flush the cache if full */
+		if (list_empty(&ftl->free_cache))
+			if ((error = __sm_cache_flush(ftl)))
+				goto unlock;
+
+		BUG_ON(list_empty(&ftl->free_cache));
+
+		cache_entry = list_first_entry (&ftl->free_cache,
+			struct cached_block, list_member);
+
+		cache_entry->lba = block;
+		cache_entry->zone = zone_num;
+
+		list_del(&cache_entry->list_member);
+		list_add(&cache_entry->list_member, &ftl->cache);
+	}
+
+	/* And finally put data there */
+	memcpy(cache_entry->data + boffset, buf, SM_SECTOR_SIZE);
+	clear_bit(boffset / SM_SECTOR_SIZE, &cache_entry->data_invalid_bitmap);
+unlock:
+	mutex_unlock(&ftl->cache_mutex);
+	return error;
+}
+
+/* outside interface: flush everything */
+static int sm_flush(struct mtd_blktrans_dev *dev)
+{
+	struct sm_ftl *ftl = dev->priv;
+	return sm_cache_flush(ftl);
+}
+
+/* outside interface: last user has quit using the device,
+						also called on removal */
+static int sm_release (struct mtd_blktrans_dev *dev)
+{
+	struct sm_ftl *ftl = dev->priv;
+	sm_cache_flush(ftl);
+	kthread_stop(ftl->flush_thread);
+	return 0;
+}
+
+/* outside interface: get geometry */
+static int sm_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
+{
+	struct sm_ftl *ftl = dev->priv;
+	geo->heads = ftl->heads;
+	geo->sectors = ftl->sectors;
+	geo->cylinders = ftl->cylinders;
+	return 0;
+}
+
+
+/* Periodic cache flush thread */
+static int sm_cache_flush_thread(void *data)
+{
+	struct sm_ftl *ftl = (struct sm_ftl *)data;
+
+	set_freezable();
+	while(!kthread_should_stop()) {
+
+		try_to_freeze();
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(msecs_to_jiffies(500));
+		sm_cache_flush(ftl);
+	}
+
+	return 0;
+}
+
+static const u8 cis_signature[] = {
+	0x01, 0x03, 0xD9, 0x01, 0xFF, 0x18, 0x02, 0xDF, 0x01, 0x20
+};
+
+/* Locate the CIS */
+static int sm_find_cis(struct sm_ftl *ftl)
+{
+	int block, boffset;
+	struct sm_oob oob;
+	int block_found = 0;
+
+
+	/* Scan for first valid block */
+	for (block = 0 ; block < ftl->zone_size - ftl->max_lba ; block++) {
+		if (sm_read_sector(ftl, 0, block, 0, NULL, &oob))
+			continue;
+
+		if (sm_block_valid(&oob))
+			continue;
+
+		block_found = 1;
+		break;
+	}
+
+	if (!block_found)
+		return -EIO;
+
+	/* Block might be still partially damaged, so scan for first valid
+		sector */
+	for (boffset = 0 ; boffset < ftl->block_size;
+					boffset += SM_SECTOR_SIZE) {
+
+		if (sm_read_sector(ftl, 0, block, boffset, tmp_buffer, &oob))
+			continue;
+
+		if (!memcmp (tmp_buffer, cis_signature, sizeof(cis_signature)))
+			goto found;
+
+		if (!memcmp (tmp_buffer + SM_SECTOR_SIZE / 2, cis_signature,
+							sizeof(cis_signature)))
+			goto found;
+		return -EIO;
+	}
+found:
+	ftl->cis_block = block;
+	dbg("CIS block found at offset %d", block * ftl->block_size + boffset);
+	return 0;
+}
+
+/* external interface: main initialization function */
+static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+	struct mtd_blktrans_dev *trans;
+	struct sm_ftl *ftl;
+	int i;
+	struct cached_block *cache_entry;
+
+
+	/* Allocate & initialize our private structure */
+	if (!(ftl = kzalloc (sizeof (struct sm_ftl), GFP_KERNEL)))
+		goto error1;
+
+	INIT_LIST_HEAD(&ftl->cache);
+	INIT_LIST_HEAD(&ftl->free_cache);
+	mutex_init(&ftl->cache_mutex);
+	init_completion(&ftl->erase_completion);
+
+	/* Read media information */
+	if (sm_get_media_info(ftl, mtd))
+		goto error2;
+
+	/* Allocate zone array, it will be initialized on demand */
+	ftl->zones = kzalloc (sizeof (struct ftl_zone) * ftl->zone_count,
+								GFP_KERNEL);
+	if (!ftl->zones)
+		goto error2;
+
+	/* Allocate write cache */
+	INIT_LIST_HEAD(&ftl->cache);
+	INIT_LIST_HEAD(&ftl->free_cache);
+
+	for (i = 0 ; i < cache_size ; i++) {
+		cache_entry = kzalloc (sizeof (struct cached_block),
+								GFP_KERNEL);
+		if (!cache_entry)
+			break;
+
+		if (sm_cache_block_init(ftl, cache_entry)) {
+			kfree(cache_entry);
+			break;
+		}
+		list_add(&cache_entry->list_member, &ftl->free_cache);
+	}
+
+	if (list_empty(&ftl->free_cache))
+		goto error3;
+
+	/* Allocate upper layer structure and initialize it */
+	if (!(trans = kzalloc (sizeof(struct mtd_blktrans_dev), GFP_KERNEL)))
+		goto error4;
+
+	ftl->trans = trans;
+	trans->priv = ftl;
+
+	trans->tr = tr;
+	trans->mtd = mtd;
+	trans->devnum = -1;
+	trans->size = (ftl->block_size * ftl->max_lba * ftl->zone_count) >> 9;
+	trans->readonly = ftl->readonly;
+
+	if (sm_find_cis(ftl))
+		goto error4;
+
+	/* Register device*/
+	if (add_mtd_blktrans_dev(trans))
+		goto error5;
+
+	dbg("Found %d MiB SmartMedia/xD card on %s",
+		(int)(mtd->size / (1024 * 1024)), mtd->name);
+
+	dbg("FTL layout:");
+	dbg("%d zones, each consists of %d blocks (+%d spares)",
+		ftl->zone_count, ftl->max_lba,
+		ftl->zone_size - ftl->max_lba);
+	dbg("each block consists of %d bytes",
+		ftl->block_size);
+
+	return;
+error5:
+	kfree(trans);
+error4:
+	sm_free_cache(ftl);
+error3:
+	kfree(ftl->zones);
+error2:
+	kfree(ftl);
+error1:
+	return;
+}
+
+/* main interface: device {surprise,} removal */
+static void sm_remove_dev (struct mtd_blktrans_dev *dev)
+{
+	struct sm_ftl *ftl = dev->priv;
+	dbg("removing the ftl device");
+	del_mtd_blktrans_dev(dev);
+	kfree(ftl->zones);
+	sm_free_cache(ftl);
+	kfree(ftl); /* WE free that here, but the ->release can still
+			be called after ..... fuck */
+}
+
+static struct mtd_blktrans_ops sm_ftl_ops = {
+	.name		= "smblk",
+	.major		= -1,
+	.part_bits	= SM_FTL_PARTN_BITS,
+	.blksize	= SM_SECTOR_SIZE,
+	.getgeo		= sm_getgeo,
+	.readsect	= sm_read,
+	.writesect	= sm_write,
+	.add_mtd	= sm_add_mtd,
+	.remove_dev	= sm_remove_dev,
+	.open 		= sm_open,
+	.release	= sm_release,
+	.flush		= sm_flush,
+	.owner		= THIS_MODULE,
+};
+
+static __init int sm_module_init(void)
+{
+	return register_mtd_blktrans(&sm_ftl_ops);
+}
+
+static void __exit sm_module_exit(void)
+{
+	deregister_mtd_blktrans(&sm_ftl_ops);
+}
+
+module_init(sm_module_init);
+module_exit(sm_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Maxim Levitsky <maximlevitsky at gmail.com>");
+MODULE_DESCRIPTION("Smartmedia/xD mtd translation layer");
\ No newline at end of file
diff --git a/drivers/mtd/sm_ftl.h b/drivers/mtd/sm_ftl.h
new file mode 100644
index 0000000..5edf372
--- /dev/null
+++ b/drivers/mtd/sm_ftl.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * SmartMedia/xD translation layer
+ *
+ * Based loosly on ssfdc.c which is
+ *  (c) 2005 Eptar srl
+ *  Author: Claudio Lanconelli <lanconelli.claudio at eptar.com>
+ *
+ * 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.
+ */
+
+#include <linux/mtd/nand.h>
+#include <linux/mtd/blktrans.h>
+#include <linux/list.h>
+#include <linux/kfifo.h>
+#include <linux/sched.h>
+#include <linux/completion.h>
+#include <linux/spinlock.h>
+
+
+struct ftl_zone {
+	int initialized;
+	s16 *lba_to_phys_table;		/* LBA to physical table */
+	struct kfifo *free_sectors;	/* queue of free sectors */
+};
+
+
+struct cached_block {
+	int zone;
+	unsigned long lba;
+	unsigned char *data;
+	long unsigned int data_invalid_bitmap;
+	struct list_head list_member;
+};
+	
+
+struct sm_ftl {
+	struct mtd_blktrans_dev *trans;
+	struct ftl_zone *zones;
+	struct list_head cache;
+	struct list_head free_cache;
+	struct mutex cache_mutex;
+	struct completion erase_completion;
+	struct task_struct *flush_thread;
+	int erase_error;
+
+	int block_size;		/* block size in bytes */
+	int zone_size;		/* zone size in blocks */
+	int zone_count;		/* number of zones */
+	int max_lba;		/* maximum lba in a zone */
+	int smallpagenand;	/* 256 bytes/page nand */
+
+	int readonly;
+
+	/* geometry stuff */
+	int heads;
+	int sectors;
+	int cylinders;
+
+	/*Misc */
+	int cis_block;
+
+	spinlock_t fifo_lock;
+};
+
+struct chs_entry {
+	unsigned long size;
+	unsigned short cyl;
+	unsigned char head;
+	unsigned char sec;
+};
+
+
+#define SM_FTL_PARTN_BITS	3
+
+#define dbg(format, ...) \
+	printk (KERN_ERR "sm_ftl" ": " format "\n", ## __VA_ARGS__)
-- 
1.6.3.3






More information about the linux-mtd mailing list