[MTD] Add Resident Flash Disk (RFD) support

This type of flash translation layer (FTL) is used by the Embedded BIOS
by General Software. It is known as the Resident Flash Disk (RFD), see:

http://www.gensw.com/pages/prod/bios/rfd.htm

Signed-off-by: Sean Young <sean@mess.org>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index 027054d..843a1cb 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -1,4 +1,4 @@
-# $Id: Kconfig,v 1.7 2004/11/22 11:33:56 ijc Exp $
+# $Id: Kconfig,v 1.9 2005/06/16 08:49:29 sean Exp $
 
 menu "Memory Technology Devices (MTD)"
 
@@ -253,6 +253,16 @@
 	  permitted to copy, modify and distribute the code as you wish. Just
 	  not use it.
 
+config RFD_FTL
+        tristate "Resident Flash Disk (Flash Translation Layer) support"
+	depends on MTD
+	---help---
+	  This provides support for the flash translation layer known 
+	  as the Resident Flash Disk (RFD), as used by the Embedded BIOS 
+	  of General Software.
+	  See http://www.gensw.com/pages/prod/bios/rfd.htm for further
+	  information.
+
 source "drivers/mtd/chips/Kconfig"
 
 source "drivers/mtd/maps/Kconfig"
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index e4ad588..cb16b7d 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -1,7 +1,7 @@
 #
 # Makefile for the memory technology device drivers.
 #
-# $Id: Makefile.common,v 1.5 2004/08/10 20:51:49 dwmw2 Exp $
+# $Id: Makefile.common,v 1.6 2005/06/16 08:49:29 sean Exp $
 
 # Core functionality.
 mtd-y				:= mtdcore.o
@@ -20,6 +20,7 @@
 obj-$(CONFIG_FTL)		+= ftl.o mtd_blkdevs.o
 obj-$(CONFIG_NFTL)		+= nftl.o mtd_blkdevs.o
 obj-$(CONFIG_INFTL)		+= inftl.o mtd_blkdevs.o
+obj-$(CONFIG_RFD_FTL)		+= rfd_ftl.o mtd_blkdevs.o
 
 nftl-objs		:= nftlcore.o nftlmount.o
 inftl-objs		:= inftlcore.o inftlmount.o
diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig
index 44781a8..aa5d71f8 100644
--- a/drivers/mtd/maps/Kconfig
+++ b/drivers/mtd/maps/Kconfig
@@ -97,14 +97,11 @@
 	depends on X86 && MTD_JEDECPROBE && MTD_PARTITIONS
 	help
 	  This provides a driver for the on-board flash of the Technologic
-	  System's TS-5500 board. The flash is split into 3 partitions
+	  System's TS-5500 board. The 2MB flash is split into 3 partitions
 	  which are accessed as separate MTD devices.
 
-	  mtd0 and mtd2 are the two BIOS drives. Unfortunately the BIOS
-	  uses a proprietary flash translation layer from General Software,
-	  which is not supported (the drives cannot be mounted). You can
-	  create your own file system (jffs for example), but the BIOS
-	  won't be able to boot from it.
+	  mtd0 and mtd2 are the two BIOS drives, which use the resident
+	  flash disk (RFD) flash translation layer.
 
 	  mtd1 allows you to reprogram your BIOS. BE VERY CAREFUL.
 
diff --git a/drivers/mtd/maps/ts5500_flash.c b/drivers/mtd/maps/ts5500_flash.c
index 3ebd90f..286dd82 100644
--- a/drivers/mtd/maps/ts5500_flash.c
+++ b/drivers/mtd/maps/ts5500_flash.c
@@ -19,13 +19,11 @@
  *
  * Note:
  * - In order for detection to work, jumper 3 must be set.
- * - Drive A and B use a proprietary FTL from General Software which isn't 
- *   supported as of yet so standard drives can't be mounted; you can create 
- *   your own (e.g. jffs) file system.
+ * - Drive A and B use the resident flash disk (RFD) flash translation layer. 
  * - If you have created your own jffs file system and the bios overwrites 
  *   it during boot, try disabling Drive A: and B: in the boot order.
  *
- * $Id: ts5500_flash.c,v 1.2 2004/11/28 09:40:40 dwmw2 Exp $
+ * $Id: ts5500_flash.c,v 1.3 2005/06/16 08:49:30 sean Exp $
  */
 
 #include <linux/config.h>
diff --git a/drivers/mtd/rfd_ftl.c b/drivers/mtd/rfd_ftl.c
new file mode 100644
index 0000000..7b9f359
--- /dev/null
+++ b/drivers/mtd/rfd_ftl.c
@@ -0,0 +1,855 @@
+/*
+ * rfd_ftl.c -- resident flash disk (flash translation layer)
+ *
+ * Copyright (C) 2005  Sean Young <sean@mess.org>
+ *
+ * $Id: rfd_ftl.c,v 1.4 2005/07/31 22:49:14 sean Exp $
+ *
+ * This type of flash translation layer (FTL) is used by the Embedded BIOS
+ * by General Software. It is known as the Resident Flash Disk (RFD), see:
+ *
+ *	http://www.gensw.com/pages/prod/bios/rfd.htm
+ *
+ * based on ftl.c
+ */
+
+#include <linux/hdreg.h>
+#include <linux/init.h>
+#include <linux/mtd/blktrans.h>
+#include <linux/mtd/mtd.h>
+#include <linux/vmalloc.h>
+
+#include <asm/types.h>
+
+#define const_cpu_to_le16	__constant_cpu_to_le16
+
+static int block_size = 0;
+module_param(block_size, int, 0);
+MODULE_PARM_DESC(block_size, "Block size to use by RFD, defaults to erase unit size");
+
+#define PREFIX "rfd_ftl: "
+
+/* Major device # for FTL device */
+
+/* A request for this major has been sent to device@lanana.org */
+#ifndef RFD_FTL_MAJOR
+#define RFD_FTL_MAJOR		95
+#endif
+
+/* Maximum number of partitions in an FTL region */
+#define PART_BITS		4
+
+/* An erase unit should start with this value */
+#define RFD_MAGIC		0x9193
+
+/* the second value is 0xffff or 0xffc8; function unknown */
+
+/* the third value is always 0xffff, ignored */
+
+/* next is an array of mapping for each corresponding sector */
+#define HEADER_MAP_OFFSET	3
+#define SECTOR_DELETED		0x0000
+#define SECTOR_ZERO		0xfffe
+#define SECTOR_FREE		0xffff
+
+#define SECTOR_SIZE		512
+
+#define SECTORS_PER_TRACK	63
+
+struct block {
+	enum {
+		BLOCK_OK,
+		BLOCK_ERASING,
+		BLOCK_ERASED,
+		BLOCK_FAILED
+	} state;
+	int free_sectors;
+	int used_sectors;
+	int erases;
+	u_long offset;
+};
+
+struct partition {
+	struct mtd_blktrans_dev mbd;
+
+	u_int block_size;		/* size of erase unit */
+	u_int total_blocks;		/* number of erase units */
+	u_int header_sectors_per_block;	/* header sectors in erase unit */
+	u_int data_sectors_per_block;	/* data sectors in erase unit */
+	u_int sector_count;		/* sectors in translated disk */
+	u_int header_size;		/* bytes in header sector */
+	int reserved_block;		/* block next up for reclaim */
+	int current_block;		/* block to write to */
+	u16 *header_cache;		/* cached header */
+
+	int is_reclaiming;
+	int cylinders;
+	int errors;
+	u_long *sector_map;
+	struct block *blocks;
+};
+
+static int rfd_ftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf);
+
+static int build_block_map(struct partition *part, int block_no)
+{
+	struct block *block = &part->blocks[block_no];
+	int i;
+	
+	block->offset = part->block_size * block_no;
+
+	if (le16_to_cpu(part->header_cache[0]) != RFD_MAGIC) {
+		block->state = BLOCK_ERASED; /* assumption */
+		block->free_sectors = part->data_sectors_per_block;
+		part->reserved_block = block_no;
+		return 1;
+	}
+
+	block->state = BLOCK_OK;
+
+	for (i=0; i<part->data_sectors_per_block; i++) {
+		u16 entry;
+		
+		entry = le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i]);
+
+		if (entry == SECTOR_DELETED)
+			continue;
+	
+		if (entry == SECTOR_FREE) {
+			block->free_sectors++;
+			continue;
+		}
+
+		if (entry == SECTOR_ZERO)
+			entry = 0;
+	
+		if (entry >= part->sector_count) {
+			printk(KERN_NOTICE PREFIX 
+				"'%s': unit #%d: entry %d corrupt, "
+				"sector %d out of range\n",
+				part->mbd.mtd->name, block_no, i, entry);
+			continue;
+		}
+
+		if (part->sector_map[entry] != -1) {
+			printk(KERN_NOTICE PREFIX 
+				"'%s': more than one entry for sector %d\n",
+				part->mbd.mtd->name, entry);
+			part->errors = 1;
+			continue;
+		}
+
+		part->sector_map[entry] = block->offset + 
+			(i + part->header_sectors_per_block) * SECTOR_SIZE;
+
+		block->used_sectors++;
+	}
+
+	if (block->free_sectors == part->data_sectors_per_block)
+		part->reserved_block = block_no;
+
+	return 0;
+}
+
+static int scan_header(struct partition *part)
+{
+	int sectors_per_block;
+	int i, rc = -ENOMEM;
+	int blocks_found;
+	size_t retlen;
+
+	sectors_per_block = part->block_size / SECTOR_SIZE;
+	part->total_blocks = part->mbd.mtd->size / part->block_size;
+
+	if (part->total_blocks < 2)
+		return -ENOENT;
+
+	/* each erase block has three bytes header, followed by the map */
+	part->header_sectors_per_block = 
+			((HEADER_MAP_OFFSET + sectors_per_block) * 
+		 	sizeof(u16) + SECTOR_SIZE - 1) / SECTOR_SIZE;
+
+	part->data_sectors_per_block = sectors_per_block - 
+			part->header_sectors_per_block;
+
+	part->header_size = (HEADER_MAP_OFFSET + 
+			part->data_sectors_per_block) * sizeof(u16);
+
+	part->cylinders = (part->data_sectors_per_block *
+			(part->total_blocks - 1) - 1) / SECTORS_PER_TRACK;
+
+	part->sector_count = part->cylinders * SECTORS_PER_TRACK;
+
+	part->current_block = -1;
+	part->reserved_block = -1;
+	part->is_reclaiming = 0;
+
+	part->header_cache = kmalloc(part->header_size, GFP_KERNEL);
+	if (!part->header_cache)
+		goto err;
+
+	part->blocks = kcalloc(part->total_blocks, sizeof(struct block), 
+			GFP_KERNEL);
+	if (!part->blocks)
+		goto err;
+
+	part->sector_map = vmalloc(part->sector_count * sizeof(u_long));
+	if (!part->sector_map) {
+		printk(KERN_ERR PREFIX "'%s': unable to allocate memory for "
+			"sector map", part->mbd.mtd->name);
+		goto err;
+	}
+
+	for (i=0; i<part->sector_count; i++) 
+		part->sector_map[i] = -1;
+
+	for (i=0, blocks_found=0; i<part->total_blocks; i++) {
+		rc = part->mbd.mtd->read(part->mbd.mtd, 
+				i * part->block_size, part->header_size,
+				&retlen, (u_char*)part->header_cache);
+
+		if (!rc && retlen != part->header_size)
+			rc = -EIO;
+
+		if (rc) 
+			goto err;
+
+		if (!build_block_map(part, i))
+			blocks_found++;
+	}
+
+	if (blocks_found == 0) {
+		printk(KERN_NOTICE PREFIX "no RFD magic found in '%s'\n",
+				part->mbd.mtd->name);
+		rc = -ENOENT;
+		goto err;
+	}
+
+	if (part->reserved_block == -1) {
+		printk(KERN_NOTICE PREFIX "'%s': no empty erase unit found\n", 
+				part->mbd.mtd->name);
+
+		part->errors = 1;
+	}
+
+	return 0;
+
+err:
+	vfree(part->sector_map);
+	kfree(part->header_cache);
+	kfree(part->blocks);
+
+	return rc;
+}
+
+static int rfd_ftl_readsect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)
+{
+	struct partition *part = (struct partition*)dev;
+	u_long addr;
+	size_t retlen;
+	int rc;
+	
+	if (sector >= part->sector_count)
+		return -EIO;
+
+	addr = part->sector_map[sector];
+	if (addr != -1) {
+		rc = part->mbd.mtd->read(part->mbd.mtd, addr, SECTOR_SIZE,
+						&retlen, (u_char*)buf);
+		if (!rc && retlen != SECTOR_SIZE)
+			rc = -EIO;
+
+		if (rc) {
+			printk(KERN_WARNING PREFIX "error reading '%s' at "
+				"0x%lx\n", part->mbd.mtd->name, addr);
+			return rc;
+		}
+	} else
+		memset(buf, 0, SECTOR_SIZE);
+	
+	return 0;
+} 
+
+static void erase_callback(struct erase_info *erase)
+{
+	struct partition *part;
+	u16 magic;
+	int i, rc;
+	size_t retlen;
+
+	part = (struct partition*)erase->priv;
+
+	i = erase->addr / part->block_size;
+	if (i >= part->total_blocks || part->blocks[i].offset != erase->addr) {
+		printk(KERN_ERR PREFIX "erase callback for unknown offset %x "
+				"on '%s'\n", erase->addr, part->mbd.mtd->name);
+		return;
+	}
+
+	if (erase->state != MTD_ERASE_DONE) {
+		printk(KERN_WARNING PREFIX "erase failed at 0x%x on '%s', "
+				"state %d\n", erase->addr, 
+				part->mbd.mtd->name, erase->state);
+
+		part->blocks[i].state = BLOCK_FAILED;
+		part->blocks[i].free_sectors = 0;
+		part->blocks[i].used_sectors = 0;
+
+		kfree(erase);
+
+		return;
+	}
+
+	magic = const_cpu_to_le16(RFD_MAGIC);
+
+	part->blocks[i].state = BLOCK_ERASED;
+	part->blocks[i].free_sectors = part->data_sectors_per_block;
+	part->blocks[i].used_sectors = 0;
+	part->blocks[i].erases++;
+
+	rc = part->mbd.mtd->write(part->mbd.mtd, 
+		part->blocks[i].offset, sizeof(magic), &retlen, 
+		(u_char*)&magic);
+	
+	if (!rc && retlen != sizeof(magic))
+		rc = -EIO;
+
+	if (rc) {
+		printk(KERN_NOTICE PREFIX "'%s': unable to write RFD "
+				"header at 0x%lx\n",
+				part->mbd.mtd->name, 
+				part->blocks[i].offset);
+		part->blocks[i].state = BLOCK_FAILED;
+	}
+	else
+		part->blocks[i].state = BLOCK_OK;
+
+	kfree(erase);
+}
+
+static int erase_block(struct partition *part, int block)
+{
+	struct erase_info *erase;
+	int rc = -ENOMEM;
+
+	erase = kmalloc(sizeof(struct erase_info), GFP_KERNEL);
+	if (!erase)
+		goto err;
+
+	erase->mtd = part->mbd.mtd;
+	erase->callback = erase_callback;
+	erase->addr = part->blocks[block].offset;
+	erase->len = part->block_size;
+	erase->priv = (u_long)part;
+
+	part->blocks[block].state = BLOCK_ERASING;
+	part->blocks[block].free_sectors = 0;
+
+	rc = part->mbd.mtd->erase(part->mbd.mtd, erase);
+
+	if (rc) {
+		printk(KERN_WARNING PREFIX "erase of region %x,%x on '%s' "
+				"failed\n", erase->addr, erase->len,
+				part->mbd.mtd->name);
+		kfree(erase);
+	}
+
+err:
+	return rc;
+}
+
+static int move_block_contents(struct partition *part, int block_no, u_long *old_sector)
+{
+	void *sector_data;
+	u16 *map;
+	size_t retlen;
+	int i, rc = -ENOMEM;
+
+	part->is_reclaiming = 1;
+
+	sector_data = kmalloc(SECTOR_SIZE, GFP_KERNEL);
+	if (!sector_data)
+		goto err3;
+
+	map = kmalloc(part->header_size, GFP_KERNEL);
+	if (!map)
+		goto err2;
+	
+	rc = part->mbd.mtd->read(part->mbd.mtd, 
+		part->blocks[block_no].offset, part->header_size, 
+		&retlen, (u_char*)map);
+	
+	if (!rc && retlen != part->header_size)
+		rc = -EIO;
+
+	if (rc) {
+		printk(KERN_NOTICE PREFIX "error reading '%s' at "
+			"0x%lx\n", part->mbd.mtd->name, 
+			part->blocks[block_no].offset);
+
+		goto err;
+	}
+
+	for (i=0; i<part->data_sectors_per_block; i++) {
+		u16 entry = le16_to_cpu(map[HEADER_MAP_OFFSET + i]);
+		u_long addr;
+
+
+		if (entry == SECTOR_FREE || entry == SECTOR_DELETED)
+			continue;
+
+		if (entry == SECTOR_ZERO) 
+			entry = 0;
+
+		/* already warned about and ignored in build_block_map() */
+		if (entry >= part->sector_count) 
+			continue;
+
+		addr = part->blocks[block_no].offset +
+			(i + part->header_sectors_per_block) * SECTOR_SIZE;
+
+		if (*old_sector == addr) {
+			*old_sector = -1;
+			if (!part->blocks[block_no].used_sectors--) {
+				rc = erase_block(part, block_no);
+				break;
+			}
+			continue;
+		}
+		rc = part->mbd.mtd->read(part->mbd.mtd, addr,
+			SECTOR_SIZE, &retlen, sector_data);
+	
+		if (!rc && retlen != SECTOR_SIZE)
+			rc = -EIO;
+
+		if (rc) {
+			printk(KERN_NOTICE PREFIX "'%s': Unable to "
+				"read sector for relocation\n",
+				part->mbd.mtd->name);
+
+			goto err;
+		}
+		
+		rc = rfd_ftl_writesect((struct mtd_blktrans_dev*)part,
+				entry, sector_data);
+		
+		if (rc) 
+			goto err;
+	}
+
+err:
+	kfree(map);
+err2:
+	kfree(sector_data);
+err3:
+	part->is_reclaiming = 0;
+
+	return rc;
+}
+
+static int reclaim_block(struct partition *part, u_long *old_sector) 
+{
+	int block, best_block, score, old_sector_block;
+	int rc;
+	
+	/* we have a race if sync doesn't exist */
+	if (part->mbd.mtd->sync)
+		part->mbd.mtd->sync(part->mbd.mtd);
+
+	score = 0x7fffffff; /* MAX_INT */
+	best_block = -1;
+	if (*old_sector != -1)
+		old_sector_block = *old_sector / part->block_size;
+	else
+		old_sector_block = -1;
+
+	for (block=0; block<part->total_blocks; block++) {
+		int this_score;
+
+		if (block == part->reserved_block)
+			continue;
+
+		/*
+		 * Postpone reclaiming if there is a free sector as
+		 * more removed sectors is more efficient (have to move
+		 * less).
+		 */
+		if (part->blocks[block].free_sectors) 
+			return 0;
+
+		this_score = part->blocks[block].used_sectors;
+
+		if (block == old_sector_block) 
+			this_score--;
+		else {
+			/* no point in moving a full block */
+			if (part->blocks[block].used_sectors == 
+					part->data_sectors_per_block)
+				continue;
+		}
+
+		this_score += part->blocks[block].erases;
+
+		if (this_score < score) {
+			best_block = block;
+			score = this_score;
+		}
+	}
+
+	if (best_block == -1)
+		return -ENOSPC;
+
+	part->current_block = -1;
+	part->reserved_block = best_block;
+
+	pr_debug("reclaim_block: reclaiming block #%d with %d used "
+		 "%d free sectors\n", best_block,
+		 part->blocks[best_block].used_sectors,
+		 part->blocks[best_block].free_sectors);
+
+	if (part->blocks[best_block].used_sectors)
+		rc = move_block_contents(part, best_block, old_sector);
+	else
+		rc = erase_block(part, best_block);
+
+	return rc;
+}
+
+/*
+ * IMPROVE: It would be best to choose the block with the most deleted sectors,
+ * because if we fill that one up first it'll have the most chance of having
+ * the least live sectors at reclaim.
+ */
+static int find_free_block(const struct partition *part)
+{
+	int block, stop;
+
+	block = part->current_block == -1 ?
+			jiffies % part->total_blocks : part->current_block;
+	stop = block;
+
+	do {
+		if (part->blocks[block].free_sectors && 
+				block != part->reserved_block)
+			return block;
+
+		if (++block >= part->total_blocks)
+			block = 0;
+
+	} while (block != stop);
+
+	return -1;
+}
+
+static int find_writeable_block(struct partition *part, u_long *old_sector)
+{
+	int rc, block;
+	size_t retlen;
+
+	block = find_free_block(part);
+
+	if (block == -1) {
+		if (!part->is_reclaiming) {
+			rc = reclaim_block(part, old_sector);
+			if (rc)
+				goto err;
+
+			block = find_free_block(part);
+		}
+
+		if (block == -1) {
+			rc = -ENOSPC;
+			goto err;
+		}
+	}
+
+	rc = part->mbd.mtd->read(part->mbd.mtd, part->blocks[block].offset, 
+		part->header_size, &retlen, (u_char*)part->header_cache);
+
+	if (!rc && retlen != part->header_size)
+		rc = -EIO;
+
+	if (rc) {
+		printk(KERN_NOTICE PREFIX "'%s': unable to read header at "
+				"0x%lx\n", part->mbd.mtd->name, 
+				part->blocks[block].offset);
+		goto err;
+	}
+
+	part->current_block = block;
+
+err:
+	return rc;
+}	
+
+static int mark_sector_deleted(struct partition *part, u_long old_addr)
+{
+	int block, offset, rc;
+	u_long addr;
+	size_t retlen;
+	u16 del = const_cpu_to_le16(SECTOR_DELETED);
+
+	block = old_addr / part->block_size;
+	offset = (old_addr % part->block_size) / SECTOR_SIZE - 
+		part->header_sectors_per_block;
+
+	addr = part->blocks[block].offset +
+			(HEADER_MAP_OFFSET + offset) * sizeof(u16);
+	rc = part->mbd.mtd->write(part->mbd.mtd, addr,
+		sizeof(del), &retlen, (u_char*)&del);
+
+	if (!rc && retlen != sizeof(del))
+		rc = -EIO;
+
+	if (rc) {
+		printk(KERN_WARNING PREFIX "error writing '%s' at "
+			"0x%lx\n", part->mbd.mtd->name, addr);
+		if (rc) 
+			goto err;
+	}
+	if (block == part->current_block)
+		part->header_cache[offset + HEADER_MAP_OFFSET] = del;
+
+	part->blocks[block].used_sectors--;
+
+	if (!part->blocks[block].used_sectors &&
+	    !part->blocks[block].free_sectors)
+		rc = erase_block(part, block);
+
+err:
+	return rc;
+}
+
+static int find_free_sector(const struct partition *part, const struct block *block)
+{
+	int i, stop;
+
+	i = stop = part->data_sectors_per_block - block->free_sectors;
+
+	do {
+		if (le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i]) 
+				== SECTOR_FREE)
+			return i;
+
+		if (++i == part->data_sectors_per_block)
+			i = 0;
+	}
+	while(i != stop);
+
+	return -1;
+}
+
+static int do_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf, ulong *old_addr)
+{
+	struct partition *part = (struct partition*)dev;
+	struct block *block;
+	u_long addr;
+	int i;
+	int rc;
+	size_t retlen;
+	u16 entry;
+
+	if (part->current_block == -1 ||
+		!part->blocks[part->current_block].free_sectors) {
+
+		rc = find_writeable_block(part, old_addr);
+		if (rc) 
+			goto err;
+	}
+
+	block = &part->blocks[part->current_block];
+
+	i = find_free_sector(part, block);
+
+	if (i < 0) {
+		rc = -ENOSPC;
+		goto err;
+	}
+		
+	addr = (i + part->header_sectors_per_block) * SECTOR_SIZE + 
+		block->offset;
+	rc = part->mbd.mtd->write(part->mbd.mtd, 
+		addr, SECTOR_SIZE, &retlen, (u_char*)buf);
+
+	if (!rc && retlen != SECTOR_SIZE)
+		rc = -EIO;
+
+	if (rc) {
+		printk(KERN_WARNING PREFIX "error writing '%s' at 0x%lx\n",
+				part->mbd.mtd->name, addr);
+		if (rc) 
+			goto err;
+	}
+
+	part->sector_map[sector] = addr;
+
+	entry = cpu_to_le16(sector == 0 ? SECTOR_ZERO : sector);
+
+	part->header_cache[i + HEADER_MAP_OFFSET] = entry;
+
+	addr = block->offset + (HEADER_MAP_OFFSET + i) * sizeof(u16);
+	rc = part->mbd.mtd->write(part->mbd.mtd, addr,
+			sizeof(entry), &retlen, (u_char*)&entry);
+
+	if (!rc && retlen != sizeof(entry))
+		rc = -EIO;
+
+	if (rc) {
+		printk(KERN_WARNING PREFIX "error writing '%s' at 0x%lx\n",
+				part->mbd.mtd->name, addr);
+		if (rc) 
+			goto err;
+	}
+	block->used_sectors++;
+	block->free_sectors--;
+
+err:
+	return rc;
+}
+
+static int rfd_ftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)
+{
+	struct partition *part = (struct partition*)dev;
+	u_long old_addr;
+	int i;
+	int rc = 0;
+
+	pr_debug("rfd_ftl_writesect(sector=0x%lx)\n", sector);
+
+	if (part->reserved_block == -1) {
+		rc = -EACCES;
+		goto err;
+	}
+
+	if (sector >= part->sector_count) {
+		rc = -EIO;
+		goto err;
+	}
+
+	old_addr = part->sector_map[sector];
+
+	for (i=0; i<SECTOR_SIZE; i++) {
+		if (!buf[i])
+			continue;
+
+		rc = do_writesect(dev, sector, buf, &old_addr);
+		if (rc)
+			goto err;
+		break;
+	}
+
+	if (i == SECTOR_SIZE) 
+		part->sector_map[sector] = -1;
+
+	if (old_addr != -1)
+		rc = mark_sector_deleted(part, old_addr);
+
+err:
+	return rc;
+}
+
+static int rfd_ftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
+{
+	struct partition *part = (struct partition*)dev;
+
+	geo->heads = 1;
+	geo->sectors = SECTORS_PER_TRACK;
+	geo->cylinders = part->cylinders;
+
+	return 0;
+}
+
+static void rfd_ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+	struct partition *part;
+
+	if (mtd->type != MTD_NORFLASH)
+		return;
+
+	part = kcalloc(1, sizeof(struct partition), GFP_KERNEL);
+	if (!part)
+		return;
+
+	part->mbd.mtd = mtd;
+
+	if (block_size)
+		part->block_size = block_size;
+	else {
+		if (!mtd->erasesize) {
+			printk(KERN_NOTICE PREFIX "please provide block_size");
+			return;
+		}
+		else
+			part->block_size = mtd->erasesize;
+	}
+
+	if (scan_header(part) == 0) {
+		part->mbd.size = part->sector_count;
+		part->mbd.blksize = SECTOR_SIZE;
+		part->mbd.tr = tr;
+		part->mbd.devnum = -1;
+		if (!(mtd->flags & MTD_WRITEABLE))
+			part->mbd.readonly = 1;
+		else if (part->errors) {
+			printk(KERN_NOTICE PREFIX "'%s': errors found, "
+					"setting read-only", mtd->name);
+			part->mbd.readonly = 1;
+		}
+
+		printk(KERN_INFO PREFIX "name: '%s' type: %d flags %x\n",
+				mtd->name, mtd->type, mtd->flags);
+
+		if (!add_mtd_blktrans_dev((void*)part))
+			return;
+	} 
+
+	kfree(part);
+}
+
+static void rfd_ftl_remove_dev(struct mtd_blktrans_dev *dev)
+{
+	struct partition *part = (struct partition*)dev;
+	int i;
+
+	for (i=0; i<part->total_blocks; i++) {
+		pr_debug("rfd_ftl_remove_dev:'%s': erase unit #%02d: %d erases\n",
+			part->mbd.mtd->name, i, part->blocks[i].erases);
+	}
+
+	del_mtd_blktrans_dev(dev);
+	vfree(part->sector_map);
+	kfree(part->header_cache);
+	kfree(part->blocks);
+	kfree(part);
+}
+
+struct mtd_blktrans_ops rfd_ftl_tr = {
+	.name		= "rfd",
+	.major		= RFD_FTL_MAJOR,
+	.part_bits	= PART_BITS,
+	.readsect	= rfd_ftl_readsect,
+	.writesect	= rfd_ftl_writesect, 
+	.getgeo		= rfd_ftl_getgeo,
+	.add_mtd	= rfd_ftl_add_mtd,
+	.remove_dev	= rfd_ftl_remove_dev,
+	.owner		= THIS_MODULE,
+};
+
+static int __init init_rfd_ftl(void)
+{
+	return register_mtd_blktrans(&rfd_ftl_tr);
+}
+
+static void __exit cleanup_rfd_ftl(void)
+{
+	deregister_mtd_blktrans(&rfd_ftl_tr);
+}
+
+module_init(init_rfd_ftl);
+module_exit(cleanup_rfd_ftl);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sean Young <sean@mess.org>");
+MODULE_DESCRIPTION("Support code for RFD Flash Translation Layer, "
+		"used by General Software's Embedded BIOS");
+