* [PATCH] Embedded bios FTL
@ 2005-06-12 14:47 Sean Young
2005-06-13 12:36 ` Jörn Engel
0 siblings, 1 reply; 11+ messages in thread
From: Sean Young @ 2005-06-12 14:47 UTC (permalink / raw)
To: linux-mtd
Here is an FTL used by General Software on their Embedded BIOS. Is it
okay to commit to cvs?
Sean
diff -urpN linux-2.6.9/drivers/mtd/embiosftl.c /usr/src/linux-2.6.9/drivers/mtd/embiosftl.c
--- linux-2.6.9/drivers/mtd/embiosftl.c 1970-01-01 01:00:00.000000000 +0100
+++ /usr/src/linux-2.6.9/drivers/mtd/embiosftl.c 2005-06-12 16:08:59.000000000 +0200
@@ -0,0 +1,840 @@
+/*
+ * embiosftl.c -- embedded bios flash translation layer
+ *
+ * Copyright (C) 2005 Sean Young <sean@mess.org>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * $Id: embiosftl.c,v 1.0 2005/06/12 15:33:26 sean Exp $
+ *
+ * This flash translation layer (FTL) is built into 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
+ */
+
+#include <linux/init.h>
+#include <asm/types.h>
+#include <linux/mtd/blktrans.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/ftl.h>
+#include <linux/hdreg.h>
+#include <linux/vmalloc.h>
+
+
+/* #define EBFTL_DEBUG */
+
+static int block_size = 0;
+MODULE_PARM(block_size, "i");
+
+#define PREFIX "embiosftl: "
+
+/* Major device # for FTL device */
+#ifndef FTL_MAJOR
+#define FTL_MAJOR 44
+#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
+
+struct block_t {
+ enum {
+ BLOCK_OK,
+ BLOCK_PREPARED,
+ BLOCK_ERASING,
+ BLOCK_ERASED,
+ BLOCK_FAILED
+ } state;
+ int free_sectors;
+ int used_sectors;
+ int erases;
+ u_long offset;
+};
+
+struct partition_t {
+ 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 */
+ u_int total_free_sectors; /* total number of free sectors */
+ int reserved_block; /* block next up for reclaim */
+ int current_block; /* block to write to */
+ u16 *header_cache; /* cached header */
+
+ int is_reclaiming;
+ u_long *sector_map;
+ struct block_t *blocks;
+};
+
+static int ebftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf);
+static int build_block_map(struct partition_t *part, int block_no);
+static void erase_callback (struct erase_info *erase);
+
+static int scan_header(struct partition_t *part)
+{
+ int sectors_per_block;
+ int i, rc = 0;
+ int blocks_found;
+ size_t retlen;
+
+ sectors_per_block = part->block_size / SECTOR_SIZE;
+ part->total_blocks = part->mbd.mtd->size / part->block_size;
+
+ /* 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->sector_count = part->data_sectors_per_block *
+ (part->total_blocks - 1);
+ 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) {
+ printk (KERN_ERR PREFIX "failed to malloc %d bytes to scan "
+ "'%s'\n", part->header_size, part->mbd.mtd->name);
+ rc = -ENOMEM;
+ goto err_out_header_cache_malloc_fail;
+ }
+
+ part->blocks = kmalloc(part->total_blocks * sizeof(struct block_t),
+ GFP_KERNEL);
+ if(!part->blocks) {
+ printk (KERN_ERR PREFIX "failed to malloc %d bytes to scan "
+ "'%s'\n", part->total_blocks * sizeof(struct block_t),
+ part->mbd.mtd->name);
+ rc = -ENOMEM;
+ goto err_out_blocks_malloc_fail;
+ }
+ memset(part->blocks, 0, part->total_blocks * sizeof(struct block_t));
+
+ part->sector_map = vmalloc(part->sector_count * sizeof(u_long));
+
+ if(!part->sector_map) {
+ printk (KERN_ERR PREFIX "failed to malloc %d bytes to scan "
+ "'%s'\n", part->sector_count * sizeof(u_long),
+ part->mbd.mtd->name);
+ rc = -ENOMEM;
+ goto err_out_sector_map_malloc_fail;
+ }
+
+ 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(retlen != part->header_size)
+ rc = -EIO;
+
+ if(rc)
+ goto err_out;
+
+ if(!build_block_map(part, i)) {
+ blocks_found++;
+ }
+ part->total_free_sectors += part->blocks[i].free_sectors;
+ }
+
+ if(blocks_found == 0) {
+ printk(KERN_NOTICE PREFIX "no FTL header found for '%s'.\n",
+ part->mbd.mtd->name);
+ rc = -ENOENT;
+ goto err_out;
+ }
+
+ return 0;
+
+err_out:
+ vfree(part->sector_map);
+err_out_sector_map_malloc_fail:
+ kfree(part->header_cache);
+err_out_blocks_malloc_fail:
+ kfree(part->blocks);
+err_out_header_cache_malloc_fail:
+
+ return rc;
+}
+
+static int build_block_map(struct partition_t *part, int block_no)
+{
+ int i;
+ struct block_t *block = &part->blocks[block_no];
+
+ 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 s;
+
+ s = __le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i]);
+ if(SECTOR_DELETED == s) {
+ continue;
+ }
+ if(SECTOR_FREE == s) {
+ block->free_sectors++;
+ continue;
+ }
+ if(SECTOR_ZERO == s) {
+ s = 0;
+ }
+ if(s >= 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, s);
+ continue;
+ }
+ if(part->sector_map[s] != -1) {
+ printk(KERN_NOTICE PREFIX
+ "'%s': unit #%d: entry %d corrupt, "
+ "sector %d linked twice\n",
+ part->mbd.mtd->name, block_no, i, s);
+ continue;
+ }
+
+ part->sector_map[s] = 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 ebftl_readsect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)
+{
+ struct partition_t *part= (struct partition_t*)dev;
+ u_long addr;
+ size_t retlen;
+ int rc;
+
+ if(sector >= part->sector_count) {
+ printk(KERN_NOTICE PREFIX "'%s': bad read offset %lu => %u\n",
+ part->mbd.mtd->name, 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(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 int erase_block(struct partition_t *part, int block)
+{
+ struct erase_info *erase;
+ int rc = 0;
+
+ erase = kmalloc(sizeof(struct erase_info), GFP_KERNEL);
+ if(!erase) {
+ printk(KERN_WARNING PREFIX "unable to allocate memory to "
+ "reclaim block for '%s'\n", part->mbd.mtd->name);
+ rc = -ENOMEM;
+ goto err_out_erase_malloc_fail;
+ }
+
+ 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;
+
+ 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_out_erase_malloc_fail:
+
+ return rc;
+}
+
+static void erase_callback (struct erase_info *erase)
+{
+ struct partition_t *part;
+ int i;
+
+ part = (struct partition_t*)erase->priv;
+ for (i=0; i<part->total_blocks; i++) {
+ if(part->blocks[i].offset == erase->addr) {
+ break;
+ }
+ }
+
+ if(i == part->total_blocks) {
+ printk(KERN_ERR PREFIX "internal error: erase callback "
+ "for unknown offset %x on '%s'\n",
+ erase->addr, part->mbd.mtd->name);
+ return;
+ }
+
+ if(erase->state == MTD_ERASE_DONE) {
+ 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++;
+ }
+ else {
+ 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;
+ }
+ part->total_free_sectors += part->blocks[i].free_sectors;
+
+ kfree(erase);
+}
+
+static int reclaim_block (struct partition_t *part, u_long *skip)
+{
+ int block, best_block, score, skip_block;
+ u_char *sector = NULL;
+ u16 *map = NULL;
+ int i, rc = 0;
+ size_t retlen;
+
+ BUG_ON(part->is_reclaiming);
+
+ if(part->mbd.mtd->sync)
+ part->mbd.mtd->sync(part->mbd.mtd);
+
+ score = 0x7fffffff; /* MAX_INT */
+ best_block = -1;
+ if(skip && *skip != -1)
+ skip_block = *skip / part->block_size;
+ else
+ skip_block = -1;
+
+ for(block=0; block<part->total_blocks; block++) {
+ int this_score;
+
+ if(block == part->reserved_block)
+ continue;
+
+ if(part->blocks[block].free_sectors)
+ return 0;
+
+ if(block == skip_block)
+ this_score = part->blocks[block].used_sectors - 1;
+ else {
+ if(part->blocks[block].used_sectors ==
+ part->data_sectors_per_block)
+ continue;
+
+ this_score = part->blocks[block].used_sectors;
+ }
+
+ 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;
+
+ if(!part->blocks[best_block].used_sectors) {
+ erase_block(part, best_block);
+ }
+ else {
+
+ part->is_reclaiming = 1;
+
+ sector = kmalloc(SECTOR_SIZE, GFP_KERNEL);
+ if(!sector) {
+ printk(KERN_WARNING PREFIX "unable to allocate memory "
+ "to reclaim block for '%s'\n",
+ part->mbd.mtd->name);
+ rc = -ENOMEM;
+ goto err_out;
+ }
+ map = kmalloc(part->header_size, GFP_KERNEL);
+ if(!map) {
+ printk(KERN_WARNING PREFIX "unable to allocate memory "
+ "to reclaim block for '%s'\n",
+ part->mbd.mtd->name);
+ rc = -ENOMEM;
+ goto err_out;
+ }
+
+ rc = part->mbd.mtd->read(part->mbd.mtd,
+ part->blocks[best_block].offset, part->header_size,
+ &retlen, (u_char*)map);
+
+ if(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[best_block].offset);
+
+ goto err_out;
+ }
+
+ for(i=0; i<part->data_sectors_per_block; i++) {
+ u16 s;
+ u_long addr;
+
+ s = __le16_to_cpu(map[HEADER_MAP_OFFSET + i]);
+
+ if(s == SECTOR_FREE || s == SECTOR_DELETED)
+ continue;
+
+ if(s == SECTOR_ZERO)
+ s = 0;
+
+ if(s >= part->sector_count) {
+ printk(KERN_NOTICE PREFIX "'%s' existing "
+ "sector %d out of range (max %d)\n",
+ part->mbd.mtd->name,
+ s, part->sector_count);
+ continue;
+ }
+
+ addr = part->blocks[best_block].offset +
+ (i + part->header_sectors_per_block)
+ * SECTOR_SIZE;
+
+ if(skip && *skip == addr) {
+ *skip = -1;
+ part->blocks[best_block].used_sectors--;
+ if(!part->blocks[best_block].used_sectors) {
+ erase_block(part, best_block);
+ }
+ continue;
+ }
+ rc = part->mbd.mtd->read(part->mbd.mtd, addr,
+ SECTOR_SIZE, &retlen, sector);
+
+ if(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_out;
+ }
+
+ rc = ebftl_writesect((struct mtd_blktrans_dev*)part,
+ s, sector);
+
+ if(rc) goto err_out;
+ }
+ }
+
+
+err_out:
+ if(map) kfree(map);
+ if(sector) kfree(sector);
+ part->is_reclaiming = 0;
+
+ return rc;
+}
+
+static int find_free_block (struct partition_t *part, u_long *skip)
+{
+ u16 s;
+ int rc, retlen;
+ int block, stop;
+ int found_free = 0;
+
+ while(1) {
+ 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 &&
+ (part->blocks[block].state == BLOCK_OK ||
+ part->blocks[block].state == BLOCK_ERASED)) {
+
+ found_free = 1;
+ break;
+ }
+
+ if(++block >= part->total_blocks)
+ block = 0;
+ }
+ while(block != stop);
+
+ if(found_free)
+ break;
+
+ rc = reclaim_block(part, skip);
+ if(rc)
+ return rc;
+ }
+
+ part->current_block = block;
+
+ if(part->blocks[block].state == BLOCK_ERASED) {
+ s = __cpu_to_le16(RFD_MAGIC);
+
+ rc = part->mbd.mtd->write(part->mbd.mtd,
+ part->blocks[block].offset, sizeof(u16), &retlen,
+ (u_char*)&s);
+
+ if(retlen != sizeof(u16))
+ rc = -EIO;
+
+ if(rc) {
+ printk(KERN_NOTICE PREFIX "'%s': unable to write RFD "
+ "header at 0x%lx\n",
+ part->mbd.mtd->name,
+ part->blocks[block].offset);
+ return -EIO;
+ }
+ part->blocks[block].state = BLOCK_OK;
+ }
+
+ rc = part->mbd.mtd->read(part->mbd.mtd,
+ part->blocks[part->current_block].offset, part->header_size,
+ &retlen, (u_char*)part->header_cache);
+
+ if(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[part->current_block].offset);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int ebftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)
+{
+ struct partition_t *part= (struct partition_t*)dev;
+ u_long old_addr, addr, mtd_addr;
+ int i;
+ int rc;
+ size_t retlen;
+ u16 s;
+
+#ifdef EBFTL_DEBUG
+ printk(KERN_NOTICE PREFIX "writing sector 0x%lx\n", sector);
+#endif
+
+ if(part->reserved_block == -1)
+ return -EACCES;
+
+ if(sector >= part->sector_count)
+ return -EIO;
+
+ old_addr = part->sector_map[sector];
+
+ if(part->current_block == -1 ||
+ !part->blocks[part->current_block].free_sectors) {
+
+ rc = find_free_block(part, &old_addr);
+ if(rc) goto err_out;
+ }
+
+#ifdef EBFTL_DEBUG
+ printk(KERN_NOTICE PREFIX "after find_free_block 0x%lx\n", sector);
+#endif
+
+ for(i=0; i<part->data_sectors_per_block; i++) {
+ if(__le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i])
+ == SECTOR_FREE) {
+ break;
+ }
+ }
+ BUG_ON(part->data_sectors_per_block == i);
+
+ addr = (i + part->header_sectors_per_block) * SECTOR_SIZE +
+ part->blocks[part->current_block].offset;
+
+ rc = part->mbd.mtd->write(part->mbd.mtd,
+ addr, SECTOR_SIZE, &retlen, (u_char*)buf);
+
+ if(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_out;
+ }
+
+ part->sector_map[sector] = addr;
+
+ s = __cpu_to_le16(sector == 0 ? SECTOR_ZERO : sector);
+
+ part->header_cache[i + HEADER_MAP_OFFSET] = s;
+ mtd_addr = part->blocks[part->current_block].offset +
+ (HEADER_MAP_OFFSET + i) * sizeof(u16);
+ rc = part->mbd.mtd->write(part->mbd.mtd, mtd_addr,
+ sizeof(u16), &retlen, (u_char*)&s);
+
+ if(retlen != sizeof(u16))
+ rc = -EIO;
+
+ if(rc) {
+ printk(KERN_WARNING PREFIX "error writing '%s' at 0x%lx\n",
+ part->mbd.mtd->name, mtd_addr);
+ if(rc) goto err_out;
+ }
+ part->blocks[part->current_block].used_sectors++;
+ part->blocks[part->current_block].free_sectors--;
+ part->total_free_sectors--;
+
+ if(old_addr != -1) {
+ int block, offset;
+ u16 old, del = __cpu_to_le16(SECTOR_DELETED);
+
+ block = old_addr / part->block_size;
+ offset = (old_addr % part->block_size) / SECTOR_SIZE -
+ part->header_sectors_per_block;
+
+ mtd_addr = part->blocks[block].offset +
+ (HEADER_MAP_OFFSET + offset) * sizeof(u16);
+ rc = part->mbd.mtd->read(part->mbd.mtd, mtd_addr,
+ sizeof(old), &retlen, (u_char*)&old);
+
+ if(retlen != sizeof(old))
+ rc = -EIO;
+
+ if(rc) {
+ printk(KERN_WARNING PREFIX "error reading '%s' at "
+ "0x%lx\n", part->mbd.mtd->name, mtd_addr);
+ if(rc) goto err_out;
+ }
+ if(old != s) {
+ printk(KERN_NOTICE PREFIX "index of '%s' corrupt, old "
+ "entry of %x is %x (addr was %lx, now %lx)\n",
+ part->mbd.mtd->name, old, s, old_addr, addr);
+ }
+
+ mtd_addr = part->blocks[block].offset +
+ (HEADER_MAP_OFFSET + offset) * sizeof(u16);
+ rc = part->mbd.mtd->write(part->mbd.mtd, mtd_addr,
+ sizeof(del), &retlen, (u_char*)&del);
+
+ if(retlen != sizeof(del))
+ rc = -EIO;
+
+ if(rc) {
+ printk(KERN_WARNING PREFIX "error writing '%s' at "
+ "0x%lx\n", part->mbd.mtd->name, mtd_addr);
+ if(rc) goto err_out;
+ }
+ 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) {
+
+ part->blocks[block].state = BLOCK_PREPARED;
+
+ erase_block(part, block);
+ }
+ }
+
+err_out:
+ return rc;
+}
+
+static int ebftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
+{
+ struct partition_t *part = (struct partition_t*)dev;
+
+ geo->heads = 1;
+ geo->sectors = part->data_sectors_per_block;
+ geo->cylinders = part->total_blocks - 1;
+
+ return 0;
+}
+
+static void ebftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+ struct partition_t *partition;
+
+ partition = kmalloc(sizeof(struct partition_t), GFP_KERNEL);
+
+ if (!partition) {
+ printk(KERN_WARNING PREFIX "out of memory to scan '%s'\n",
+ mtd->name);
+ return;
+ }
+
+ memset(partition, 0, sizeof(struct partition_t));
+
+ partition->mbd.mtd = mtd;
+
+ if(block_size) {
+ partition->block_size = block_size;
+ }
+ else {
+ if(!mtd->erasesize) {
+ printk(KERN_NOTICE PREFIX "please provide block_size");
+ return;
+ }
+ else {
+ partition->block_size = mtd->erasesize;
+ }
+ }
+
+ if (scan_header(partition) == 0) {
+
+ partition->mbd.size = SECTOR_SIZE * partition->sector_count;
+ partition->mbd.blksize = SECTOR_SIZE;
+ partition->mbd.tr = tr;
+ partition->mbd.devnum = -1;
+ if(!(mtd->flags & MTD_WRITEABLE)) {
+ partition->mbd.readonly = 1;
+ }
+ else if(partition->reserved_block == -1) {
+ printk(KERN_NOTICE PREFIX "'%s': no empty erase unit "
+ "found, setting read-only\n",
+ partition->mbd.mtd->name);
+
+ partition->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 *)partition)) {
+ return;
+ }
+ }
+
+ kfree(partition);
+}
+
+static void ebftl_remove_dev(struct mtd_blktrans_dev *dev)
+{
+ struct partition_t *part = (struct partition_t*)dev;
+#ifdef EBFTL_DEBUG
+ int i;
+
+ for (i=0; i<part->total_blocks; i++) {
+ printk(KERN_NOTICE PREFIX "'%s': erase unit #%02d: %d erases\n",
+ part->mbd.mtd->name, i, part->blocks[i].erases);
+ }
+#endif
+
+ del_mtd_blktrans_dev(dev);
+ vfree(part->sector_map);
+ kfree(part->header_cache);
+ kfree(part->blocks);
+ kfree(part);
+
+}
+
+struct mtd_blktrans_ops ebftl_tr = {
+ .name = "embiosftl",
+ .major = FTL_MAJOR,
+ .part_bits = PART_BITS,
+ .readsect = ebftl_readsect,
+ .writesect = ebftl_writesect,
+ .getgeo = ebftl_getgeo,
+ .add_mtd = ebftl_add_mtd,
+ .remove_dev = ebftl_remove_dev,
+ .owner = THIS_MODULE,
+};
+
+static int __init init_ebftl(void)
+{
+ return register_mtd_blktrans(&ebftl_tr);
+}
+
+static void __exit cleanup_ebftl(void)
+{
+ deregister_mtd_blktrans(&ebftl_tr);
+}
+
+module_init(init_ebftl);
+module_exit(cleanup_ebftl);
+
+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");
+
diff -urpN linux-2.6.9/drivers/mtd/Kconfig /usr/src/linux-2.6.9/drivers/mtd/Kconfig
--- linux-2.6.9/drivers/mtd/Kconfig 2005-06-12 15:56:16.000000000 +0200
+++ /usr/src/linux-2.6.9/drivers/mtd/Kconfig 2005-06-12 14:16:09.000000000 +0200
@@ -253,6 +253,15 @@ config INFTL
permitted to copy, modify and distribute the code as you wish. Just
not use it.
+config EMBIOSFTL
+ tristate "Embedded BIOS FTL (Flash Translation Layer) support"
+ depends on MTD
+ ---help---
+ This provides support for the Flash Translation Layer as used
+ by the Embedded BIOS of General Software. There is a blurb at
+
+ http://www.gensw.com/pages/prod/bios/rfd.htm
+
source "drivers/mtd/chips/Kconfig"
source "drivers/mtd/maps/Kconfig"
diff -urpN linux-2.6.9/drivers/mtd/Makefile /usr/src/linux-2.6.9/drivers/mtd/Makefile
--- linux-2.6.9/drivers/mtd/Makefile 2004-10-18 23:53:51.000000000 +0200
+++ /usr/src/linux-2.6.9/drivers/mtd/Makefile 2005-06-12 14:15:56.000000000 +0200
@@ -20,6 +20,7 @@ obj-$(CONFIG_MTD_BLOCK_RO) += mtdblock_r
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_EMBIOSFTL) += embiosftl.o mtd_blkdevs.o
nftl-objs := nftlcore.o nftlmount.o
inftl-objs := inftlcore.o inftlmount.o
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] Embedded bios FTL
2005-06-12 14:47 [PATCH] Embedded bios FTL Sean Young
@ 2005-06-13 12:36 ` Jörn Engel
2005-06-14 19:28 ` Sean Young
0 siblings, 1 reply; 11+ messages in thread
From: Jörn Engel @ 2005-06-13 12:36 UTC (permalink / raw)
To: Sean Young; +Cc: linux-mtd
On Sun, 12 June 2005 16:47:08 +0200, Sean Young wrote:
>
> Here is an FTL used by General Software on their Embedded BIOS. Is it
> okay to commit to cvs?
In principle, yes. But this is a good time for some review, so please
take a look at my comments first. I didn't bother repeating myself
over and over, so you have to apply some comments to many examples.
Also, do you know of any patents this code would infringe? You don't
have to actively look for any - that is a dangerous hobby for any
developer. Just tell if you already know of such a thing.
> diff -urpN linux-2.6.9/drivers/mtd/embiosftl.c /usr/src/linux-2.6.9/drivers/mtd/embiosftl.c
> --- linux-2.6.9/drivers/mtd/embiosftl.c 1970-01-01 01:00:00.000000000 +0100
> +++ /usr/src/linux-2.6.9/drivers/mtd/embiosftl.c 2005-06-12 16:08:59.000000000 +0200
> @@ -0,0 +1,840 @@
> +/*
> + * embiosftl.c -- embedded bios flash translation layer
> + *
> + * Copyright (C) 2005 Sean Young <sean@mess.org>
> + *
> + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
I don't like these legalese figleaves in the first few lines of every
file too much. IANAL, but to my understanding they don't make a big
legal difference. Reading of the code is harder, though.
> +#include <linux/init.h>
> +#include <asm/types.h>
> +#include <linux/mtd/blktrans.h>
> +#include <linux/mtd/mtd.h>
> +#include <linux/mtd/ftl.h>
> +#include <linux/hdreg.h>
> +#include <linux/vmalloc.h>
People prefer to keep asm- and linux-includes seperate. I tend to
sort the list as well, but that's just me.
> +/* #define EBFTL_DEBUG */
> +
> +static int block_size = 0;
> +MODULE_PARM(block_size, "i");
> +
> +#define PREFIX "embiosftl: "
> +
> +/* Major device # for FTL device */
> +#ifndef FTL_MAJOR
> +#define FTL_MAJOR 44
> +#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
> +
> +struct block_t {
The _t suffix for structs is generally frowned upon.
> + enum {
> + BLOCK_OK,
> + BLOCK_PREPARED,
> + BLOCK_ERASING,
> + BLOCK_ERASED,
> + BLOCK_FAILED
> + } state;
> + int free_sectors;
> + int used_sectors;
> + int erases;
> + u_long offset;
> +};
> +
> +struct partition_t {
> + 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 */
> + u_int total_free_sectors; /* total number of free sectors */
> + int reserved_block; /* block next up for reclaim */
> + int current_block; /* block to write to */
> + u16 *header_cache; /* cached header */
> +
> + int is_reclaiming;
> + u_long *sector_map;
> + struct block_t *blocks;
> +};
> +
> +static int ebftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf);
> +static int build_block_map(struct partition_t *part, int block_no);
> +static void erase_callback (struct erase_info *erase);
^
> +
> +static int scan_header(struct partition_t *part)
> +{
> + int sectors_per_block;
> + int i, rc = 0;
> + int blocks_found;
> + size_t retlen;
> +
> + sectors_per_block = part->block_size / SECTOR_SIZE;
> + part->total_blocks = part->mbd.mtd->size / part->block_size;
> +
> + /* 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;
The "+ SECTOR_SIZE - 1) / SECTOR_SIZE" part is fairly common. I'm
surprised there is no #define for this in <linux/kernel.h>. You could
create your own and place it near the top of this file, then try to
move it into <linux/kernel.h> later.
> + 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->sector_count = part->data_sectors_per_block *
> + (part->total_blocks - 1);
> + 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) {
> + printk (KERN_ERR PREFIX "failed to malloc %d bytes to scan "
> + "'%s'\n", part->header_size, part->mbd.mtd->name);
In the long term, these messages should just go.
> + rc = -ENOMEM;
> + goto err_out_header_cache_malloc_fail;
> + }
Also, you can set rc once unconditionally. Without the printk(), this
should even result in slightly more efficient binary code (according
to Linus, I didn't check it).
Basically:
rc = -ENOMEM;
try this;
if (this failed)
goto out;
try that;
if (that failed)
goto out;
And while we're at it, "out", "err" or "out_fail" are much nicer than
"err_out_header_cache_malloc_fail". You don't have to fear name
collisions either, as labels have function scope only.
> + part->blocks = kmalloc(part->total_blocks * sizeof(struct block_t),
> + GFP_KERNEL);
> + if(!part->blocks) {
> + printk (KERN_ERR PREFIX "failed to malloc %d bytes to scan "
> + "'%s'\n", part->total_blocks * sizeof(struct block_t),
> + part->mbd.mtd->name);
> + rc = -ENOMEM;
> + goto err_out_blocks_malloc_fail;
> + }
> + memset(part->blocks, 0, part->total_blocks * sizeof(struct block_t));
kcalloc()
> + part->sector_map = vmalloc(part->sector_count * sizeof(u_long));
> +
> + if(!part->sector_map) {
> + printk (KERN_ERR PREFIX "failed to malloc %d bytes to scan "
> + "'%s'\n", part->sector_count * sizeof(u_long),
> + part->mbd.mtd->name);
> + rc = -ENOMEM;
> + goto err_out_sector_map_malloc_fail;
> + }
> +
> + for(i=0; i<part->sector_count; i++)
> + part->sector_map[i] = -1;
If and for require a space, functions don't. I didn't invent this
rule, but following it makes the code more consistent with the rest of
the kernel.
> + 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(retlen != part->header_size)
> + rc = -EIO;
> +
> + if(rc)
> + goto err_out;
> +
> + if(!build_block_map(part, i)) {
> + blocks_found++;
> + }
No brackets needed.
> + part->total_free_sectors += part->blocks[i].free_sectors;
> + }
> +
> + if(blocks_found == 0) {
> + printk(KERN_NOTICE PREFIX "no FTL header found for '%s'.\n",
> + part->mbd.mtd->name);
> + rc = -ENOENT;
> + goto err_out;
> + }
> +
> + return 0;
> +
> +err_out:
> + vfree(part->sector_map);
> +err_out_sector_map_malloc_fail:
> + kfree(part->header_cache);
> +err_out_blocks_malloc_fail:
> + kfree(part->blocks);
> +err_out_header_cache_malloc_fail:
kfree(NULL) is a noop. You can use this to make the error handling
code a bit simpler and always jump to the same target. Just have to
make sure that all parameters are either kmalloc()ed or zero.
> + return rc;
> +}
> +
> +static int build_block_map(struct partition_t *part, int block_no)
> +{
> + int i;
> + struct block_t *block = &part->blocks[block_no];
> +
> + 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 s;
What is "s" used for?
> +
> + s = __le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i]);
> + if(SECTOR_DELETED == s) {
> + continue;
> + }
> + if(SECTOR_FREE == s) {
> + block->free_sectors++;
> + continue;
> + }
> + if(SECTOR_ZERO == s) {
> + s = 0;
> + }
> + if(s >= 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, s);
> + continue;
> + }
> + if(part->sector_map[s] != -1) {
> + printk(KERN_NOTICE PREFIX
> + "'%s': unit #%d: entry %d corrupt, "
> + "sector %d linked twice\n",
> + part->mbd.mtd->name, block_no, i, s);
> + continue;
> + }
> +
> + part->sector_map[s] = 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 ebftl_readsect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)
> +{
> + struct partition_t *part= (struct partition_t*)dev;
> + u_long addr;
> + size_t retlen;
> + int rc;
> +
> + if(sector >= part->sector_count) {
> + printk(KERN_NOTICE PREFIX "'%s': bad read offset %lu => %u\n",
> + part->mbd.mtd->name, 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(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
Above two should go on the same line.
> + memset(buf, 0, SECTOR_SIZE);
> +
> + return 0;
> +}
> +
> +static int erase_block(struct partition_t *part, int block)
> +{
> + struct erase_info *erase;
> + int rc = 0;
> +
> + erase = kmalloc(sizeof(struct erase_info), GFP_KERNEL);
> + if(!erase) {
> + printk(KERN_WARNING PREFIX "unable to allocate memory to "
> + "reclaim block for '%s'\n", part->mbd.mtd->name);
> + rc = -ENOMEM;
> + goto err_out_erase_malloc_fail;
> + }
> +
> + 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;
> +
> + 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_out_erase_malloc_fail:
> +
> + return rc;
> +}
> +
> +static void erase_callback (struct erase_info *erase)
> +{
> + struct partition_t *part;
> + int i;
> +
> + part = (struct partition_t*)erase->priv;
> + for (i=0; i<part->total_blocks; i++) {
> + if(part->blocks[i].offset == erase->addr) {
> + break;
> + }
> + }
> +
> + if(i == part->total_blocks) {
> + printk(KERN_ERR PREFIX "internal error: erase callback "
> + "for unknown offset %x on '%s'\n",
> + erase->addr, part->mbd.mtd->name);
> + return;
> + }
> +
> + if(erase->state == MTD_ERASE_DONE) {
> + 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++;
> + }
> + else {
> + 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;
> + }
> + part->total_free_sectors += part->blocks[i].free_sectors;
> +
> + kfree(erase);
> +}
> +
> +static int reclaim_block (struct partition_t *part, u_long *skip)
> +{
> + int block, best_block, score, skip_block;
> + u_char *sector = NULL;
> + u16 *map = NULL;
> + int i, rc = 0;
> + size_t retlen;
> +
> + BUG_ON(part->is_reclaiming);
> +
> + if(part->mbd.mtd->sync)
> + part->mbd.mtd->sync(part->mbd.mtd);
> +
> + score = 0x7fffffff; /* MAX_INT */
> + best_block = -1;
> + if(skip && *skip != -1)
> + skip_block = *skip / part->block_size;
> + else
> + skip_block = -1;
> +
> + for(block=0; block<part->total_blocks; block++) {
> + int this_score;
> +
> + if(block == part->reserved_block)
> + continue;
> +
> + if(part->blocks[block].free_sectors)
> + return 0;
> +
> + if(block == skip_block)
> + this_score = part->blocks[block].used_sectors - 1;
> + else {
> + if(part->blocks[block].used_sectors ==
> + part->data_sectors_per_block)
> + continue;
> +
> + this_score = part->blocks[block].used_sectors;
> + }
> +
> + 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;
> +
> + if(!part->blocks[best_block].used_sectors) {
> + erase_block(part, best_block);
> + }
> + else {
One line.
> +
> + part->is_reclaiming = 1;
> +
> + sector = kmalloc(SECTOR_SIZE, GFP_KERNEL);
> + if(!sector) {
> + printk(KERN_WARNING PREFIX "unable to allocate memory "
> + "to reclaim block for '%s'\n",
> + part->mbd.mtd->name);
> + rc = -ENOMEM;
> + goto err_out;
> + }
> + map = kmalloc(part->header_size, GFP_KERNEL);
> + if(!map) {
> + printk(KERN_WARNING PREFIX "unable to allocate memory "
> + "to reclaim block for '%s'\n",
> + part->mbd.mtd->name);
> + rc = -ENOMEM;
> + goto err_out;
> + }
> +
> + rc = part->mbd.mtd->read(part->mbd.mtd,
> + part->blocks[best_block].offset, part->header_size,
> + &retlen, (u_char*)map);
> +
> + if(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[best_block].offset);
> +
> + goto err_out;
> + }
> +
> + for(i=0; i<part->data_sectors_per_block; i++) {
> + u16 s;
> + u_long addr;
> +
> + s = __le16_to_cpu(map[HEADER_MAP_OFFSET + i]);
> +
> + if(s == SECTOR_FREE || s == SECTOR_DELETED)
> + continue;
> +
> + if(s == SECTOR_ZERO)
> + s = 0;
> +
> + if(s >= part->sector_count) {
> + printk(KERN_NOTICE PREFIX "'%s' existing "
> + "sector %d out of range (max %d)\n",
> + part->mbd.mtd->name,
> + s, part->sector_count);
Too many indentations. When you read something like this, it is about
time to think about the code some more.
> + continue;
> + }
> +
> + addr = part->blocks[best_block].offset +
> + (i + part->header_sectors_per_block)
> + * SECTOR_SIZE;
> +
> + if(skip && *skip == addr) {
> + *skip = -1;
> + part->blocks[best_block].used_sectors--;
> + if(!part->blocks[best_block].used_sectors) {
> + erase_block(part, best_block);
> + }
> + continue;
> + }
> + rc = part->mbd.mtd->read(part->mbd.mtd, addr,
> + SECTOR_SIZE, &retlen, sector);
> +
> + if(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_out;
> + }
> +
> + rc = ebftl_writesect((struct mtd_blktrans_dev*)part,
> + s, sector);
> +
> + if(rc) goto err_out;
> + }
> + }
> +
> +
> +err_out:
> + if(map) kfree(map);
*NEVER* have condition and conditional code on a single line. People
can easily miss what the code is really doing. Unless you intend to
confuse any readers. In that case you should submit the code to
IOCCC, not to the kernel.
Apart from that, kfree(NULL) works just fine, so just call it
unconditionally.
> + if(sector) kfree(sector);
> + part->is_reclaiming = 0;
> +
> + return rc;
> +}
> +
> +static int find_free_block (struct partition_t *part, u_long *skip)
> +{
> + u16 s;
> + int rc, retlen;
> + int block, stop;
> + int found_free = 0;
> +
> + while(1) {
> + 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 &&
> + (part->blocks[block].state == BLOCK_OK ||
> + part->blocks[block].state == BLOCK_ERASED)) {
Horribly long. Create a new variable and assign part->blocks[block]
to it. Then use the variable instead.
> + found_free = 1;
> + break;
> + }
> +
> + if(++block >= part->total_blocks)
> + block = 0;
> + }
> + while(block != stop);
Same line for those two as well.
> +
> + if(found_free)
> + break;
> +
> + rc = reclaim_block(part, skip);
> + if(rc)
> + return rc;
> + }
> +
> + part->current_block = block;
> +
> + if(part->blocks[block].state == BLOCK_ERASED) {
> + s = __cpu_to_le16(RFD_MAGIC);
> +
> + rc = part->mbd.mtd->write(part->mbd.mtd,
> + part->blocks[block].offset, sizeof(u16), &retlen,
> + (u_char*)&s);
> +
> + if(retlen != sizeof(u16))
> + rc = -EIO;
> +
> + if(rc) {
> + printk(KERN_NOTICE PREFIX "'%s': unable to write RFD "
> + "header at 0x%lx\n",
> + part->mbd.mtd->name,
> + part->blocks[block].offset);
> + return -EIO;
> + }
> + part->blocks[block].state = BLOCK_OK;
> + }
> +
> + rc = part->mbd.mtd->read(part->mbd.mtd,
> + part->blocks[part->current_block].offset, part->header_size,
> + &retlen, (u_char*)part->header_cache);
> +
> + if(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[part->current_block].offset);
> + return rc;
> + }
> +
> + return 0;
> +}
> +
> +static int ebftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)
> +{
> + struct partition_t *part= (struct partition_t*)dev;
> + u_long old_addr, addr, mtd_addr;
> + int i;
> + int rc;
> + size_t retlen;
> + u16 s;
> +
> +#ifdef EBFTL_DEBUG
> + printk(KERN_NOTICE PREFIX "writing sector 0x%lx\n", sector);
> +#endif
pr_debug()
> + if(part->reserved_block == -1)
> + return -EACCES;
> +
> + if(sector >= part->sector_count)
> + return -EIO;
> +
> + old_addr = part->sector_map[sector];
> +
> + if(part->current_block == -1 ||
> + !part->blocks[part->current_block].free_sectors) {
> +
> + rc = find_free_block(part, &old_addr);
> + if(rc) goto err_out;
> + }
> +
> +#ifdef EBFTL_DEBUG
> + printk(KERN_NOTICE PREFIX "after find_free_block 0x%lx\n", sector);
> +#endif
> +
> + for(i=0; i<part->data_sectors_per_block; i++) {
> + if(__le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i])
> + == SECTOR_FREE) {
> + break;
> + }
> + }
> + BUG_ON(part->data_sectors_per_block == i);
> +
> + addr = (i + part->header_sectors_per_block) * SECTOR_SIZE +
> + part->blocks[part->current_block].offset;
> +
> + rc = part->mbd.mtd->write(part->mbd.mtd,
> + addr, SECTOR_SIZE, &retlen, (u_char*)buf);
> +
> + if(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_out;
> + }
> +
> + part->sector_map[sector] = addr;
> +
> + s = __cpu_to_le16(sector == 0 ? SECTOR_ZERO : sector);
> +
> + part->header_cache[i + HEADER_MAP_OFFSET] = s;
> + mtd_addr = part->blocks[part->current_block].offset +
> + (HEADER_MAP_OFFSET + i) * sizeof(u16);
> + rc = part->mbd.mtd->write(part->mbd.mtd, mtd_addr,
> + sizeof(u16), &retlen, (u_char*)&s);
> +
> + if(retlen != sizeof(u16))
> + rc = -EIO;
> +
> + if(rc) {
> + printk(KERN_WARNING PREFIX "error writing '%s' at 0x%lx\n",
> + part->mbd.mtd->name, mtd_addr);
> + if(rc) goto err_out;
> + }
> + part->blocks[part->current_block].used_sectors++;
> + part->blocks[part->current_block].free_sectors--;
> + part->total_free_sectors--;
> +
> + if(old_addr != -1) {
> + int block, offset;
> + u16 old, del = __cpu_to_le16(SECTOR_DELETED);
> +
> + block = old_addr / part->block_size;
> + offset = (old_addr % part->block_size) / SECTOR_SIZE -
> + part->header_sectors_per_block;
> +
> + mtd_addr = part->blocks[block].offset +
> + (HEADER_MAP_OFFSET + offset) * sizeof(u16);
> + rc = part->mbd.mtd->read(part->mbd.mtd, mtd_addr,
> + sizeof(old), &retlen, (u_char*)&old);
> +
> + if(retlen != sizeof(old))
> + rc = -EIO;
> +
> + if(rc) {
> + printk(KERN_WARNING PREFIX "error reading '%s' at "
> + "0x%lx\n", part->mbd.mtd->name, mtd_addr);
> + if(rc) goto err_out;
> + }
> + if(old != s) {
> + printk(KERN_NOTICE PREFIX "index of '%s' corrupt, old "
> + "entry of %x is %x (addr was %lx, now %lx)\n",
> + part->mbd.mtd->name, old, s, old_addr, addr);
> + }
> +
> + mtd_addr = part->blocks[block].offset +
> + (HEADER_MAP_OFFSET + offset) * sizeof(u16);
> + rc = part->mbd.mtd->write(part->mbd.mtd, mtd_addr,
> + sizeof(del), &retlen, (u_char*)&del);
> +
> + if(retlen != sizeof(del))
> + rc = -EIO;
> +
> + if(rc) {
> + printk(KERN_WARNING PREFIX "error writing '%s' at "
> + "0x%lx\n", part->mbd.mtd->name, mtd_addr);
> + if(rc) goto err_out;
> + }
> + 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) {
> +
> + part->blocks[block].state = BLOCK_PREPARED;
> +
> + erase_block(part, block);
> + }
> + }
> +
> +err_out:
> + return rc;
> +}
> +
> +static int ebftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
> +{
> + struct partition_t *part = (struct partition_t*)dev;
> +
> + geo->heads = 1;
> + geo->sectors = part->data_sectors_per_block;
> + geo->cylinders = part->total_blocks - 1;
> +
> + return 0;
> +}
> +
> +static void ebftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
> +{
> + struct partition_t *partition;
> +
> + partition = kmalloc(sizeof(struct partition_t), GFP_KERNEL);
> +
> + if (!partition) {
> + printk(KERN_WARNING PREFIX "out of memory to scan '%s'\n",
> + mtd->name);
> + return;
> + }
> +
> + memset(partition, 0, sizeof(struct partition_t));
> +
> + partition->mbd.mtd = mtd;
> +
> + if(block_size) {
> + partition->block_size = block_size;
> + }
> + else {
> + if(!mtd->erasesize) {
> + printk(KERN_NOTICE PREFIX "please provide block_size");
> + return;
> + }
> + else {
> + partition->block_size = mtd->erasesize;
> + }
> + }
> +
> + if (scan_header(partition) == 0) {
> +
> + partition->mbd.size = SECTOR_SIZE * partition->sector_count;
> + partition->mbd.blksize = SECTOR_SIZE;
> + partition->mbd.tr = tr;
> + partition->mbd.devnum = -1;
> + if(!(mtd->flags & MTD_WRITEABLE)) {
> + partition->mbd.readonly = 1;
> + }
> + else if(partition->reserved_block == -1) {
> + printk(KERN_NOTICE PREFIX "'%s': no empty erase unit "
> + "found, setting read-only\n",
> + partition->mbd.mtd->name);
> +
> + partition->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 *)partition)) {
> + return;
> + }
> + }
> +
> + kfree(partition);
> +}
> +
> +static void ebftl_remove_dev(struct mtd_blktrans_dev *dev)
> +{
> + struct partition_t *part = (struct partition_t*)dev;
> +#ifdef EBFTL_DEBUG
> + int i;
> +
> + for (i=0; i<part->total_blocks; i++) {
> + printk(KERN_NOTICE PREFIX "'%s': erase unit #%02d: %d erases\n",
> + part->mbd.mtd->name, i, part->blocks[i].erases);
> + }
> +#endif
> +
> + del_mtd_blktrans_dev(dev);
> + vfree(part->sector_map);
> + kfree(part->header_cache);
> + kfree(part->blocks);
> + kfree(part);
> +
> +}
> +
> +struct mtd_blktrans_ops ebftl_tr = {
> + .name = "embiosftl",
> + .major = FTL_MAJOR,
> + .part_bits = PART_BITS,
> + .readsect = ebftl_readsect,
> + .writesect = ebftl_writesect,
> + .getgeo = ebftl_getgeo,
> + .add_mtd = ebftl_add_mtd,
> + .remove_dev = ebftl_remove_dev,
> + .owner = THIS_MODULE,
> +};
> +
> +static int __init init_ebftl(void)
> +{
> + return register_mtd_blktrans(&ebftl_tr);
> +}
> +
> +static void __exit cleanup_ebftl(void)
> +{
> + deregister_mtd_blktrans(&ebftl_tr);
> +}
> +
> +module_init(init_ebftl);
> +module_exit(cleanup_ebftl);
> +
> +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");
> +
> diff -urpN linux-2.6.9/drivers/mtd/Kconfig /usr/src/linux-2.6.9/drivers/mtd/Kconfig
> --- linux-2.6.9/drivers/mtd/Kconfig 2005-06-12 15:56:16.000000000 +0200
> +++ /usr/src/linux-2.6.9/drivers/mtd/Kconfig 2005-06-12 14:16:09.000000000 +0200
> @@ -253,6 +253,15 @@ config INFTL
> permitted to copy, modify and distribute the code as you wish. Just
> not use it.
>
> +config EMBIOSFTL
> + tristate "Embedded BIOS FTL (Flash Translation Layer) support"
> + depends on MTD
> + ---help---
> + This provides support for the Flash Translation Layer as used
> + by the Embedded BIOS of General Software. There is a blurb at
> +
> + http://www.gensw.com/pages/prod/bios/rfd.htm
> +
> source "drivers/mtd/chips/Kconfig"
>
> source "drivers/mtd/maps/Kconfig"
> diff -urpN linux-2.6.9/drivers/mtd/Makefile /usr/src/linux-2.6.9/drivers/mtd/Makefile
> --- linux-2.6.9/drivers/mtd/Makefile 2004-10-18 23:53:51.000000000 +0200
> +++ /usr/src/linux-2.6.9/drivers/mtd/Makefile 2005-06-12 14:15:56.000000000 +0200
> @@ -20,6 +20,7 @@ obj-$(CONFIG_MTD_BLOCK_RO) += mtdblock_r
> 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_EMBIOSFTL) += embiosftl.o mtd_blkdevs.o
>
> nftl-objs := nftlcore.o nftlmount.o
> inftl-objs := inftlcore.o inftlmount.o
>
> ______________________________________________________
> Linux MTD discussion mailing list
> http://lists.infradead.org/mailman/listinfo/linux-mtd/
Jörn
--
I don't understand it. Nobody does.
-- Richard P. Feynman
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] Embedded bios FTL
2005-06-13 12:36 ` Jörn Engel
@ 2005-06-14 19:28 ` Sean Young
2005-06-14 22:18 ` Jörn Engel
0 siblings, 1 reply; 11+ messages in thread
From: Sean Young @ 2005-06-14 19:28 UTC (permalink / raw)
To: Joern Engel; +Cc: linux-mtd
On Mon, Jun 13, 2005 at 02:36:26PM +0200, Joern Engel wrote:
> In principle, yes. But this is a good time for some review, so please
> take a look at my comments first.
Thank you, I've applied your comments. Indeed it is much nicer now.
> Also, do you know of any patents this code would infringe? You don't
> have to actively look for any - that is a dangerous hobby for any
> developer. Just tell if you already know of such a thing.
AFAICS General Software makes no mention of applicable patents. I am not
aware of any patents which are infringed.
I've renamed it to `rfd' as `/dev/rfda' is much nicer than
`/dev/embiosftla', IMHO. Documentation refers to the RFD anyway, not to
the product which uses it (which isn't just the bios anyway).
dwmw2 pointed out that a major block number needs to be allocated, for
which I've sent a request to device@lanana.org. No response so far.
Any ideas for how it can be improved or reasons why it shouldn't be
commited to cvs?
Sean
---
diff -urpN linux-2.6.9/drivers/mtd/rfdftl.c /usr/src/linux-2.6.9/drivers/mtd/rfdftl.c
--- linux-2.6.9/drivers/mtd/rfdftl.c 1970-01-01 01:00:00.000000000 +0100
+++ /usr/src/linux-2.6.9/drivers/mtd/rfdftl.c 2005-06-14 20:41:34.000000000 +0200
@@ -0,0 +1,787 @@
+/*
+ * rfdftl.c -- resident flash disk (flash translation layer)
+ *
+ * Copyright (C) 2005 Sean Young <sean@mess.org>
+ *
+ * $Id: rfdftl.c,v 1.0 2005/06/14 15:33:26 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>
+
+static int block_size = 0;
+MODULE_PARM(block_size, "i");
+
+#define PREFIX "rfdftl: "
+
+/* Major device # for FTL device */
+
+/* A request for this major has been sent to device@lanana.org */
+#ifndef RFDFTL_MAJOR
+#define RFDFTL_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
+
+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;
+ u_long *sector_map;
+ struct block *blocks;
+};
+
+static int rfdftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf);
+static int build_block_map(struct partition *part, int block_no);
+static void erase_callback(struct erase_info *erase);
+static int move_block_contents(struct partition *part, int block_no, u_long *old_sector);
+static int mark_sector_removed(struct partition *part, u_long old_sector);
+
+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;
+
+ /* 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->sector_count = part->data_sectors_per_block *
+ (part->total_blocks - 1);
+ 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 err4;
+
+ part->blocks = kcalloc(part->total_blocks, sizeof(struct block),
+ GFP_KERNEL);
+ if (!part->blocks)
+ goto err3;
+
+ 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 err2;
+ }
+
+ 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 FTL header found for '%s'.\n",
+ part->mbd.mtd->name);
+ rc = -ENOENT;
+ goto err;
+ }
+
+ return 0;
+
+err:
+ vfree(part->sector_map);
+err2:
+ kfree(part->header_cache);
+err3:
+ kfree(part->blocks);
+err4:
+
+ return rc;
+}
+
+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': unit #%d: entry %d corrupt, "
+ "sector %d linked twice\n",
+ part->mbd.mtd->name, block_no, i, entry);
+ 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 rfdftl_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 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 void erase_callback(struct erase_info *erase)
+{
+ struct partition *part;
+ int i;
+
+ 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 "internal error: erase callback "
+ "for unknown offset %x on '%s'\n",
+ erase->addr, part->mbd.mtd->name);
+ return;
+ }
+
+ if (erase->state == MTD_ERASE_DONE) {
+ 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++;
+ } else {
+ 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);
+}
+
+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;
+
+ /*
+ * post-pone 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;
+}
+
+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;
+
+ if (entry >= part->sector_count) {
+ printk(KERN_NOTICE PREFIX "'%s' existing "
+ "sector %d out of range (max %d)\n",
+ part->mbd.mtd->name,
+ 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;
+ part->blocks[block_no].used_sectors--;
+ 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 = rfdftl_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 find_free_block (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;
+ }
+ }
+
+ if (part->blocks[block].state == BLOCK_ERASED) {
+ u16 magic = cpu_to_le16(RFD_MAGIC);
+
+ rc = part->mbd.mtd->write(part->mbd.mtd,
+ part->blocks[block].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[block].offset);
+ goto err;
+ }
+ part->blocks[block].state = BLOCK_OK;
+ }
+
+ 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 rfdftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)
+{
+ struct partition *part= (struct partition*)dev;
+ u_long old_addr, addr;
+ int i;
+ int rc;
+ size_t retlen;
+ u16 entry;
+
+ pr_debug("rfdftl_writesect(sector=0x%lx)\n", sector);
+
+ if (part->reserved_block == -1)
+ return -EACCES;
+
+ if (sector >= part->sector_count)
+ return -EIO;
+
+ old_addr = part->sector_map[sector];
+
+ if (part->current_block == -1 ||
+ !part->blocks[part->current_block].free_sectors) {
+
+ rc = find_writeable_block(part, &old_addr);
+ if (rc)
+ goto err;
+ }
+
+ for (i=0; i<part->data_sectors_per_block; i++) {
+ if (le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i])
+ == SECTOR_FREE)
+ break;
+ }
+ BUG_ON(part->data_sectors_per_block == i);
+
+ addr = (i + part->header_sectors_per_block) * SECTOR_SIZE +
+ part->blocks[part->current_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 = part->blocks[part->current_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;
+ }
+ part->blocks[part->current_block].used_sectors++;
+ part->blocks[part->current_block].free_sectors--;
+
+ if (old_addr != -1)
+ rc = mark_sector_removed(part, old_addr);
+
+err:
+ return rc;
+}
+
+static int mark_sector_removed(struct partition *part, u_long old_addr)
+{
+ int block, offset, rc;
+ u_long addr;
+ size_t retlen;
+ u16 del = 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 rfdftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
+{
+ struct partition *part = (struct partition*)dev;
+
+ geo->heads = 1;
+ geo->sectors = part->data_sectors_per_block;
+ geo->cylinders = part->total_blocks - 1;
+
+ return 0;
+}
+
+static void rfdftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+ struct partition *part;
+
+ 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->reserved_block == -1) {
+ printk(KERN_NOTICE PREFIX "'%s': no empty erase unit "
+ "found, setting read-only\n",
+ part->mbd.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 rfdftl_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("rfdftl_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 rfdftl_tr = {
+ .name = "rfd",
+ .major = RFDFTL_MAJOR,
+ .part_bits = PART_BITS,
+ .readsect = rfdftl_readsect,
+ .writesect = rfdftl_writesect,
+ .getgeo = rfdftl_getgeo,
+ .add_mtd = rfdftl_add_mtd,
+ .remove_dev = rfdftl_remove_dev,
+ .owner = THIS_MODULE,
+};
+
+static int __init init_rfdftl(void)
+{
+ return register_mtd_blktrans(&rfdftl_tr);
+}
+
+static void __exit cleanup_rfdftl(void)
+{
+ deregister_mtd_blktrans(&rfdftl_tr);
+}
+
+module_init(init_rfdftl);
+module_exit(cleanup_rfdftl);
+
+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");
+
diff -urpN linux-2.6.9/drivers/mtd/Kconfig /usr/src/linux-2.6.9/drivers/mtd/Kconfig
--- linux-2.6.9/drivers/mtd/Kconfig 2005-06-13 00:27:43.000000000 +0200
+++ /usr/src/linux-2.6.9/drivers/mtd/Kconfig 2005-06-14 19:54:37.000000000 +0200
@@ -253,6 +253,16 @@ config INFTL
permitted to copy, modify and distribute the code as you wish. Just
not use it.
+config RFDFTL
+ 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. There is a blurb at:
+
+ http://www.gensw.com/pages/prod/bios/rfd.htm
+
source "drivers/mtd/chips/Kconfig"
source "drivers/mtd/maps/Kconfig"
diff -urpN linux-2.6.9/drivers/mtd/Makefile /usr/src/linux-2.6.9/drivers/mtd/Makefile
--- linux-2.6.9/drivers/mtd/Makefile 2004-10-18 23:53:51.000000000 +0200
+++ /usr/src/linux-2.6.9/drivers/mtd/Makefile 2005-06-14 20:01:46.000000000 +0200
@@ -20,6 +20,7 @@ obj-$(CONFIG_MTD_BLOCK_RO) += mtdblock_r
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_RFDFTL) += rfdftl.o mtd_blkdevs.o
nftl-objs := nftlcore.o nftlmount.o
inftl-objs := inftlcore.o inftlmount.o
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] Embedded bios FTL
2005-06-14 19:28 ` Sean Young
@ 2005-06-14 22:18 ` Jörn Engel
2005-06-15 19:27 ` Sean Young
0 siblings, 1 reply; 11+ messages in thread
From: Jörn Engel @ 2005-06-14 22:18 UTC (permalink / raw)
To: Sean Young; +Cc: linux-mtd
On Tue, 14 June 2005 21:28:09 +0200, Sean Young wrote:
>
> Thank you, I've applied your comments. Indeed it is much nicer now.
I have to agree. Nice work.
> AFAICS General Software makes no mention of applicable patents. I am not
> aware of any patents which are infringed.
That's fine.
> I've renamed it to `rfd' as `/dev/rfda' is much nicer than
> `/dev/embiosftla', IMHO. Documentation refers to the RFD anyway, not to
> the product which uses it (which isn't just the bios anyway).
Yep, makes sense.
> dwmw2 pointed out that a major block number needs to be allocated, for
> which I've sent a request to device@lanana.org. No response so far.
Just keep your current one for now. Fair enough.
> Any ideas for how it can be improved or reasons why it shouldn't be
> commited to cvs?
Next round of comments...
> diff -urpN linux-2.6.9/drivers/mtd/rfdftl.c /usr/src/linux-2.6.9/drivers/mtd/rfdftl.c
> --- linux-2.6.9/drivers/mtd/rfdftl.c 1970-01-01 01:00:00.000000000 +0100
> +++ /usr/src/linux-2.6.9/drivers/mtd/rfdftl.c 2005-06-14 20:41:34.000000000 +0200
> @@ -0,0 +1,787 @@
> +/*
> + * rfdftl.c -- resident flash disk (flash translation layer)
> + *
> + * Copyright (C) 2005 Sean Young <sean@mess.org>
> + *
> + * $Id: rfdftl.c,v 1.0 2005/06/14 15:33:26 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>
> +
> +static int block_size = 0;
> +MODULE_PARM(block_size, "i");
/* DEPRECATED: Do not use. */
#define MODULE_PARM(var,type)
Better change this to module_param_call() or one of its relatives.
> +#define PREFIX "rfdftl: "
I personally hate words without any vowels. Ths s jst hrd t rd. You
have to decide, though.
> +/* Major device # for FTL device */
> +
> +/* A request for this major has been sent to device@lanana.org */
> +#ifndef RFDFTL_MAJOR
> +#define RFDFTL_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
> +
> +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;
> + u_long *sector_map;
> + struct block *blocks;
> +};
> +
> +static int rfdftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf);
> +static int build_block_map(struct partition *part, int block_no);
> +static void erase_callback(struct erase_info *erase);
> +static int move_block_contents(struct partition *part, int block_no, u_long *old_sector);
> +static int mark_sector_removed(struct partition *part, u_long old_sector);
Function prototypes for static functions don't have many friends in
the kernel community. Usually you can rearrange the order below and
then remove the prototypes.
> +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;
> +
> + /* 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->sector_count = part->data_sectors_per_block *
> + (part->total_blocks - 1);
> + 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 err4;
> +
> + part->blocks = kcalloc(part->total_blocks, sizeof(struct block),
> + GFP_KERNEL);
> + if (!part->blocks)
> + goto err3;
> +
> + 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 err2;
> + }
> +
> + 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 FTL header found for '%s'.\n",
> + part->mbd.mtd->name);
> + rc = -ENOENT;
> + goto err;
> + }
> +
> + return 0;
> +
> +err:
> + vfree(part->sector_map);
> +err2:
> + kfree(part->header_cache);
> +err3:
> + kfree(part->blocks);
> +err4:
Both vfree and kfree are happy with argument being NULL. Looking at
the only caller of this function, part->everything is NULL when this
is called. So you can remove all targets except err and change the
gotos.
> + return rc;
> +}
> +
> +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': unit #%d: entry %d corrupt, "
> + "sector %d linked twice\n",
> + part->mbd.mtd->name, block_no, i, entry);
> + 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 rfdftl_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 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 void erase_callback(struct erase_info *erase)
> +{
> + struct partition *part;
> + int i;
> +
> + part = (struct partition*)erase->priv;
This cast shouldn't exist. Sadly, C doesn't support a truely opaque
data type that automatically casts to anything. Would be a very
useful extension.
The rest of the code is very nice reading. I cannot find any further
problems without actually using my brain and trying to understand it.
Considering the last few beers and the short night ahead of me, I'll
pass. :)
Hope I didn't create too many typos in this state.
Jörn
--
He who knows others is wise.
He who knows himself is enlightened.
-- Lao Tsu
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] Embedded bios FTL
2005-06-14 22:18 ` Jörn Engel
@ 2005-06-15 19:27 ` Sean Young
2005-06-15 21:01 ` Jörn Engel
0 siblings, 1 reply; 11+ messages in thread
From: Sean Young @ 2005-06-15 19:27 UTC (permalink / raw)
To: Joern Engel; +Cc: linux-mtd
On Wed, Jun 15, 2005 at 12:18:44AM +0200, Joern Engel wrote:
> Next round of comments...
Thank you. The improved version...
I'm assuming we're near to a state where it can be committed.
According to General Software documentation their implementation "snoops"
the fat table in order to see which sectors can be chucked. Hmm...
Sean
---
diff -urpN linux-2.6.9/drivers/mtd/rfd_ftl.c /usr/src/linux-2.6.9/drivers/mtd/rfd_ftl.c
--- linux-2.6.9/drivers/mtd/rfd_ftl.c 1970-01-01 01:00:00.000000000 +0100
+++ /usr/src/linux-2.6.9/drivers/mtd/rfd_ftl.c 2005-06-15 20:19:11.000000000 +0200
@@ -0,0 +1,776 @@
+/*
+ * rfd_ftl.c -- resident flash disk (flash translation layer)
+ *
+ * Copyright (C) 2005 Sean Young <sean@mess.org>
+ *
+ * $Id: rfd_ftl.c,v 1.0 2005/06/15 15:33:26 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>
+
+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
+
+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;
+ 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': unit #%d: entry %d corrupt, "
+ "sector %d linked twice\n",
+ part->mbd.mtd->name, block_no, i, entry);
+ 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;
+
+ /* 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->sector_count = part->data_sectors_per_block *
+ (part->total_blocks - 1);
+ 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 FTL header found for '%s'.\n",
+ part->mbd.mtd->name);
+ rc = -ENOENT;
+ goto err;
+ }
+
+ 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;
+ int i;
+
+ 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 "internal error: erase callback "
+ "for unknown offset %x on '%s'\n",
+ erase->addr, part->mbd.mtd->name);
+ return;
+ }
+
+ if (erase->state == MTD_ERASE_DONE) {
+ 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++;
+ } else {
+ 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);
+}
+
+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;
+}
+
+static int find_free_block (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;
+ }
+ }
+
+ if (part->blocks[block].state == BLOCK_ERASED) {
+ u16 magic = cpu_to_le16(RFD_MAGIC);
+
+ rc = part->mbd.mtd->write(part->mbd.mtd,
+ part->blocks[block].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[block].offset);
+ goto err;
+ }
+ part->blocks[block].state = BLOCK_OK;
+ }
+
+ 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_removed(struct partition *part, u_long old_addr)
+{
+ int block, offset, rc;
+ u_long addr;
+ size_t retlen;
+ u16 del = 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 rfd_ftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)
+{
+ struct partition *part= (struct partition*)dev;
+ u_long old_addr, addr;
+ int i;
+ int rc;
+ size_t retlen;
+ u16 entry;
+
+ pr_debug("rfd_ftl_writesect(sector=0x%lx)\n", sector);
+
+ if (part->reserved_block == -1)
+ return -EACCES;
+
+ if (sector >= part->sector_count)
+ return -EIO;
+
+ old_addr = part->sector_map[sector];
+
+ if (part->current_block == -1 ||
+ !part->blocks[part->current_block].free_sectors) {
+
+ rc = find_writeable_block(part, &old_addr);
+ if (rc)
+ goto err;
+ }
+
+ for (i=0; i<part->data_sectors_per_block; i++) {
+ if (le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i])
+ == SECTOR_FREE)
+ break;
+ }
+ BUG_ON(part->data_sectors_per_block == i);
+
+ addr = (i + part->header_sectors_per_block) * SECTOR_SIZE +
+ part->blocks[part->current_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 = part->blocks[part->current_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;
+ }
+ part->blocks[part->current_block].used_sectors++;
+ part->blocks[part->current_block].free_sectors--;
+
+ if (old_addr != -1)
+ rc = mark_sector_removed(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 = part->data_sectors_per_block;
+ geo->cylinders = part->total_blocks - 1;
+
+ return 0;
+}
+
+static void rfd_ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+ struct partition *part;
+
+ 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->reserved_block == -1) {
+ printk(KERN_NOTICE PREFIX "'%s': no empty erase unit "
+ "found, setting read-only\n",
+ part->mbd.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");
+
diff -urpN linux-2.6.9/drivers/mtd/Kconfig /usr/src/linux-2.6.9/drivers/mtd/Kconfig
--- linux-2.6.9/drivers/mtd/Kconfig 2005-06-13 00:27:43.000000000 +0200
+++ /usr/src/linux-2.6.9/drivers/mtd/Kconfig 2005-06-15 13:48:54.000000000 +0200
@@ -253,6 +253,16 @@ config INFTL
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. There is a blurb at:
+
+ http://www.gensw.com/pages/prod/bios/rfd.htm
+
source "drivers/mtd/chips/Kconfig"
source "drivers/mtd/maps/Kconfig"
diff -urpN linux-2.6.9/drivers/mtd/Makefile /usr/src/linux-2.6.9/drivers/mtd/Makefile
--- linux-2.6.9/drivers/mtd/Makefile 2004-10-18 23:53:51.000000000 +0200
+++ /usr/src/linux-2.6.9/drivers/mtd/Makefile 2005-06-15 13:49:05.000000000 +0200
@@ -20,6 +20,7 @@ obj-$(CONFIG_MTD_BLOCK_RO) += mtdblock_r
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
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] Embedded bios FTL
2005-06-15 19:27 ` Sean Young
@ 2005-06-15 21:01 ` Jörn Engel
2005-06-16 9:15 ` Sean Young
0 siblings, 1 reply; 11+ messages in thread
From: Jörn Engel @ 2005-06-15 21:01 UTC (permalink / raw)
To: Sean Young; +Cc: linux-mtd
On Wed, 15 June 2005 21:27:54 +0200, Sean Young wrote:
> On Wed, Jun 15, 2005 at 12:18:44AM +0200, Joern Engel wrote:
> > Next round of comments...
>
> Thank you. The improved version...
>
> I'm assuming we're near to a state where it can be committed.
Yep. No objections from my side. Nice job!
> According to General Software documentation their implementation "snoops"
> the fat table in order to see which sectors can be chucked. Hmm...
That's a smart thing to do if you can be certain the user is running
fat on top. Being certain ain't so easy, though.
Jörn
--
This above all: to thine own self be true.
-- Shakespeare
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] Embedded bios FTL
2005-06-15 21:01 ` Jörn Engel
@ 2005-06-16 9:15 ` Sean Young
2005-06-16 10:28 ` David Woodhouse
2005-06-16 10:29 ` Jörn Engel
0 siblings, 2 replies; 11+ messages in thread
From: Sean Young @ 2005-06-16 9:15 UTC (permalink / raw)
To: Joern Engel; +Cc: linux-mtd
On Wed, Jun 15, 2005 at 11:01:55PM +0200, Joern Engel wrote:
> On Wed, 15 June 2005 21:27:54 +0200, Sean Young wrote:
> > I'm assuming we're near to a state where it can be committed.
>
> Yep. No objections from my side. Nice job!
Thanks, committed.
> > According to General Software documentation their implementation "snoops"
> > the fat table in order to see which sectors can be chucked. Hmm...
>
> That's a smart thing to do if you can be certain the user is running
> fat on top. Being certain ain't so easy, though.
Indeed so. Besides, it would be more elegant if the file system driver
passed this on to the block device, via an ioctl. Would such a thing
stand up to scrutiny? As far as I can see, only the ftl block devices
would benefit.
Sean
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] Embedded bios FTL
2005-06-16 9:15 ` Sean Young
@ 2005-06-16 10:28 ` David Woodhouse
2005-06-16 10:29 ` Jörn Engel
1 sibling, 0 replies; 11+ messages in thread
From: David Woodhouse @ 2005-06-16 10:28 UTC (permalink / raw)
To: Sean Young; +Cc: linux-mtd
On Thu, 2005-06-16 at 11:15 +0200, Sean Young wrote:
> Indeed so. Besides, it would be more elegant if the file system driver
> passed this on to the block device, via an ioctl. Would such a thing
> stand up to scrutiny? As far as I can see, only the ftl block devices
> would benefit.
I believe there's a command which can be given to CompactFlash devices
which does basically the same thing. I'd like to see it added to the
block device interface. Probably not an ioctl -- probably a bio command
like barriers instead.
>
--
dwmw2
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] Embedded bios FTL
2005-06-16 9:15 ` Sean Young
2005-06-16 10:28 ` David Woodhouse
@ 2005-06-16 10:29 ` Jörn Engel
2005-06-16 11:54 ` Sean Young
1 sibling, 1 reply; 11+ messages in thread
From: Jörn Engel @ 2005-06-16 10:29 UTC (permalink / raw)
To: Sean Young; +Cc: linux-mtd
On Thu, 16 June 2005 11:15:46 +0200, Sean Young wrote:
> On Wed, Jun 15, 2005 at 11:01:55PM +0200, Joern Engel wrote:
> > On Wed, 15 June 2005 21:27:54 +0200, Sean Young wrote:
>
> > > According to General Software documentation their implementation "snoops"
> > > the fat table in order to see which sectors can be chucked. Hmm...
> >
> > That's a smart thing to do if you can be certain the user is running
> > fat on top. Being certain ain't so easy, though.
>
> Indeed so. Besides, it would be more elegant if the file system driver
> passed this on to the block device, via an ioctl. Would such a thing
> stand up to scrutiny? As far as I can see, only the ftl block devices
> would benefit.
An ioctl() would have a *really* hard time to make it into the kernel.
What you could do, however, might be something similar to this:
o Add an erase() function to stuct block_device_operations
o Have fat call that function whenever a block is know unused.
o Make sure only to call the function, if it's non-NULL. Maybe a
wrapper, maybe just
if (bd_ops->erase)
bd_ops->erase(block);
inside fs/fat/.
o Implement erase() for your ftl driver to do something smart.
Be careful, though. I haven't worked on block devices for a very long
time and could be talking out of my arse. If you send such a patch to
akpm and get flamed into a pile of ashes - don't blame me. ;)
Seriously, the erase() function should be considered an optimization
only, at least for now. If it doesn't exist - fine. If it exists but
doesn't do anything - fine. If it does something - great.
Jörn
--
Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are,
by definition, not smart enough to debug it.
-- Brian W. Kernighan
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] Embedded bios FTL
2005-06-16 10:29 ` Jörn Engel
@ 2005-06-16 11:54 ` Sean Young
2005-06-16 12:44 ` Jörn Engel
0 siblings, 1 reply; 11+ messages in thread
From: Sean Young @ 2005-06-16 11:54 UTC (permalink / raw)
To: Joern Engel; +Cc: linux-mtd
On Thu, Jun 16, 2005 at 12:29:05PM +0200, Joern Engel wrote:
> On Thu, 16 June 2005 11:15:46 +0200, Sean Young wrote:
-snip chucking unused sectors-
> > Indeed so. Besides, it would be more elegant if the file system driver
> > passed this on to the block device, via an ioctl. Would such a thing
> > stand up to scrutiny? As far as I can see, only the ftl block devices
> > would benefit.
>
> An ioctl() would have a *really* hard time to make it into the kernel.
I agree that ioctl()s are ugly, but it does have the advantage that it
is accessible from user-space. Excuse my ignorance, but what is the
motivation for this?
> What you could do, however, might be something similar to this:
>
> o Add an erase() function to stuct block_device_operations
> o Have fat call that function whenever a block is know unused.
> o Make sure only to call the function, if it's non-NULL. Maybe a
> wrapper, maybe just
> if (bd_ops->erase)
> bd_ops->erase(block);
> inside fs/fat/.
> o Implement erase() for your ftl driver to do something smart.
>
> Be careful, though. I haven't worked on block devices for a very long
> time and could be talking out of my arse. If you send such a patch to
> akpm and get flamed into a pile of ashes - don't blame me. ;)
I'll have to spend more time looking at the block devices/bio code
before I can start. :)
erase() sounds rather mtd-specific. Wouldn't stale() be better?
Sean
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] Embedded bios FTL
2005-06-16 11:54 ` Sean Young
@ 2005-06-16 12:44 ` Jörn Engel
0 siblings, 0 replies; 11+ messages in thread
From: Jörn Engel @ 2005-06-16 12:44 UTC (permalink / raw)
To: Sean Young; +Cc: linux-mtd
On Thu, 16 June 2005 13:54:59 +0200, Sean Young wrote:
> On Thu, Jun 16, 2005 at 12:29:05PM +0200, Joern Engel wrote:
> >
> > An ioctl() would have a *really* hard time to make it into the kernel.
>
> I agree that ioctl()s are ugly, but it does have the advantage that it
> is accessible from user-space. Excuse my ignorance, but what is the
> motivation for this?
Past experience, mostly. 95% of all ioctl() uses are for Crap(tm).
Mostly because people having some stupid idea can easily add an
ioctl() and have a hard time adding a new syscall or similar.
After a while, people caught up to this and by today, a new ioctl()
raises the red flag that someone stupid is trying to do something
stupid and should not be allowed to do so.
For the remaining 5% of good ideas, an ioctl number has no advantage
over a new function with a self-explaining name.
> > What you could do, however, might be something similar to this:
> >
> > o Add an erase() function to stuct block_device_operations
> > o Have fat call that function whenever a block is know unused.
> > o Make sure only to call the function, if it's non-NULL. Maybe a
> > wrapper, maybe just
> > if (bd_ops->erase)
> > bd_ops->erase(block);
> > inside fs/fat/.
> > o Implement erase() for your ftl driver to do something smart.
> >
> > Be careful, though. I haven't worked on block devices for a very long
> > time and could be talking out of my arse. If you send such a patch to
> > akpm and get flamed into a pile of ashes - don't blame me. ;)
>
> I'll have to spend more time looking at the block devices/bio code
> before I can start. :)
>
> erase() sounds rather mtd-specific. Wouldn't stale() be better?
You're right. Even for mtd, erase would be the wrong name. The
semantic is not to erase the full erase block (and to do it now,
imperatively) but to return control over a partial erase block to the
ftl. Hmm.
For block devices, the semantic is that no read() to this block will
happen before the next write() to it. forget() might be a decent name
as well. Then the block device has the option to change the data
within this block as it pleases. If the next read() happens before
the next write(), random data will be returned.
PS: Yeah, I know, block devices have request(), instead of read() and
write(). Same thing.
Jörn
--
The only real mistake is the one from which we learn nothing.
-- John Powell
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2005-06-16 12:44 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-06-12 14:47 [PATCH] Embedded bios FTL Sean Young
2005-06-13 12:36 ` Jörn Engel
2005-06-14 19:28 ` Sean Young
2005-06-14 22:18 ` Jörn Engel
2005-06-15 19:27 ` Sean Young
2005-06-15 21:01 ` Jörn Engel
2005-06-16 9:15 ` Sean Young
2005-06-16 10:28 ` David Woodhouse
2005-06-16 10:29 ` Jörn Engel
2005-06-16 11:54 ` Sean Young
2005-06-16 12:44 ` Jörn Engel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox