Embedded bios FTL
Subsystems (2)
PATCH 1/1 Embedded bios FTL
Here is an FTL used by General Software on their Embedded BIOS. Is it okay to commit to cvs?
Sean
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +840 -0
@@ -0,0 +1,840 @@ +/* +* embiosftl.c -- embedded bios flash translation layer +* +* Copyright (C) 2005 Sean Young <[email protected]> +* +* 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 <[email protected]>" ); +MODULE_DESCRIPTION ("Support code for RFD Flash Translation Layer, " +"used by General Software's Embedded BIOS" ); +
usr/src/linux-2.6.9/drivers/mtd/Kconfig renamed from drivers/mtd/Kconfig +9 -0
@@ -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"
usr/src/linux-2.6.9/drivers/mtd/Makefile renamed from drivers/mtd/Makefile +1 -0
@@ -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
Quoted 3 lines · Sean Young
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.
Quoted 12 lines
diff -urpN
linux-2.6.9/drivers/mtd/embiosftl.c/usr/src/linux-2.6.9/drivers/mtd/embiosftl.cusr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +8
-0@@ -0,0 +1,840 @@ +/* +* embiosftl.c -- embedded bios flash translation layer +* +* Copyright (C) 2005 Sean Young <[email protected]> +* +* 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
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 18
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-0+* (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.
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 34
Quoted 7 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +7
-0+#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.
Quoted 21 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +21
-0+/* #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 */
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 67
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-0+ +/* 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.
Quoted 25 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +25
-0+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 */
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 102
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-0+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);
^
Quoted 5 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +5
-0+ +static int scan_header (struct partition_t *part) +{ +int sectors_per_block; +int i, rc =0 ;
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 117
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-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.
Quoted 5 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +5
-0+ 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);
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 132
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-0+ 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.
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 135
Quoted 3 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +3
-0+ 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.
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 146
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-0+ 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()
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 159
Quoted 12 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +12
-0+ 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.
Quoted 4 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +4
-0+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);
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 174
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-0+ +if (retlen != part->header_size) + rc = -EIO ; + +if (rc) + goto err_out; + +if (!build_block_map (part, i)) { + blocks_found++; + }
No brackets needed.
Quoted 9 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +9
-0+ 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; + }
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 193
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-0+ +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.
Quoted 11 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +11
-0+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 ) {
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 215
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-0+ 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?
Quoted 55 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +55
-0+ + 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);
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 280
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-0+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.
Quoted 125 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +125
-0+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; + } + } +
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 415
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-0+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.
Quoted 42 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +42
-0+ + 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 )
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 467
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-0+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.
Quoted 29 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +29
-0+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; + } +
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 506
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-0+ 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.
Quoted 13 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +13
-0+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 ; +
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 529
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-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.
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 538
Quoted 8 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +8
-0+ found_free =1 ; +break ; + } + +if (++block >= part->total_blocks) + block =0 ; + } +while (block != stop);
Same line for those two as well.
Quoted 50 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +50
-0+ +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) +{
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c line 598
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +10
-0+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()
Quoted 272 lines
usr/src/linux-2.6.9/drivers/mtd/embiosftl.c renamed from drivers/mtd/embiosftl.c +241
-0+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 <[email protected]>" ); +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/Kconfigusr/src/linux-2.6.9/drivers/mtd/Kconfig renamed from drivers/mtd/Kconfig +9
-0@@ -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/Makefileusr/src/linux-2.6.9/drivers/mtd/Makefile renamed from drivers/mtd/Makefile +1
-0@@ -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
Quoted 3 lines
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
Jörn
Signature · 2 lines
I don't understand it. Nobody does. -- Richard P. Feynman
Quoted 2 lines · Joern Engel
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.
Quoted 3 lines
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 [email protected]. No response so far.
Any ideas for how it can be improved or reasons why it shouldn't be commited to cvs?
Sean
---
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c renamed from drivers/mtd/rfdftl.c +787 -0
@@ -0,0 +1,787 @@ +/* +* rfdftl.c -- resident flash disk (flash translation layer) +* +* Copyright (C) 2005 Sean Young <[email protected]> +* +* $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 [email protected] */ +#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 <[email protected]>" ); +MODULE_DESCRIPTION ("Support code for RFD Flash Translation Layer, " +"used by General Software's Embedded BIOS" ); +
usr/src/linux-2.6.9/drivers/mtd/Kconfig renamed from drivers/mtd/Kconfig +10 -0
@@ -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"
usr/src/linux-2.6.9/drivers/mtd/Makefile renamed from drivers/mtd/Makefile +1 -0
@@ -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
Quoted 2 lines · Sean Young
Thank you, I've applied your comments. Indeed it is much nicer now.
I have to agree. Nice work.
Quoted 2 lines
AFAICS General Software makes no mention of applicable patents. I am not aware of any patents which are infringed.
That's fine.
Quoted 3 lines
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.
Quoted 2 lines
dwmw2 pointed out that a major block number needs to be allocated, for which I've sent a request to [email protected]. No response so far.
Just keep your current one for now. Fair enough.
Quoted 2 lines
Any ideas for how it can be improved or reasons why it shouldn't be commited to cvs?
Next round of comments...
Quoted 19 lines
diff -urpN
linux-2.6.9/drivers/mtd/rfdftl.c/usr/src/linux-2.6.9/drivers/mtd/rfdftl.cusr/src/linux-2.6.9/drivers/mtd/rfdftl.c renamed from drivers/mtd/rfdftl.c +15
-0@@ -0,0 +1,787 @@ +/* +* rfdftl.c -- resident flash disk (flash translation layer) +* +* Copyright (C) 2005 Sean Young <[email protected]> +* +* $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 +*/ +
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c line 25
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c renamed from drivers/mtd/rfdftl.c +10
-0+#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.
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c line 27
Quoted 1 line
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c renamed from drivers/mtd/rfdftl.c +1
-0+#define PREFIX "rfdftl: "
I personally hate words without any vowels. Ths s jst hrd t rd. You have to decide, though.
Quoted 51 lines
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c renamed from drivers/mtd/rfdftl.c +51
-0+/* Major device # for FTL device */ + +/* A request for this major has been sent to [email protected] */ +#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 */ +
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c line 89
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c renamed from drivers/mtd/rfdftl.c +10
-0+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.
Quoted 65 lines
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c renamed from drivers/mtd/rfdftl.c +65
-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 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; + }
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c line 165
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c renamed from drivers/mtd/rfdftl.c +10
-0+ +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.
Quoted 118 lines
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c renamed from drivers/mtd/rfdftl.c +118
-0+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); + } +
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c line 294
Quoted 10 lines
usr/src/linux-2.6.9/drivers/mtd/rfdftl.c renamed from drivers/mtd/rfdftl.c +10
-0+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
Signature · 3 lines
He who knows others is wise. He who knows himself is enlightened. -- Lao Tsu
Quoted 1 line · Joern Engel
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
---
usr/src/linux-2.6.9/drivers/mtd/rfd_ftl.c renamed from drivers/mtd/rfd_ftl.c +776 -0
@@ -0,0 +1,776 @@ +/* +* rfd_ftl.c -- resident flash disk (flash translation layer) +* +* Copyright (C) 2005 Sean Young <[email protected]> +* +* $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 [email protected] */ +#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 <[email protected]>" ); +MODULE_DESCRIPTION ("Support code for RFD Flash Translation Layer, " +"used by General Software's Embedded BIOS" ); +
usr/src/linux-2.6.9/drivers/mtd/Kconfig renamed from drivers/mtd/Kconfig +10 -0
@@ -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"
usr/src/linux-2.6.9/drivers/mtd/Makefile renamed from drivers/mtd/Makefile +1 -0
@@ -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
Quoted 6 lines · Sean Young
Quoted 1 line · Joern Engel
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!
Quoted 2 lines
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
Signature · 2 lines
This above all: to thine own self be true. -- Shakespeare
Quoted 4 lines · Joern Engel
Quoted 1 line · Sean Young
I'm assuming we're near to a state where it can be committed.
Yep. No objections from my side. Nice job!
Thanks, committed.
Quoted 5 lines
Quoted 2 lines
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
Quoted 4 lines · Sean Young
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.
Quoted 1 line
Signature · 1 line
dwmw2
Quoted 6 lines · Sean Young
Quoted 5 lines · Joern Engel
Quoted 2 lines · Sean Young
According to General Software documentation their implementation "snoops" the fat table in order to see which sectors can be chucked. Hmm...
Quoted 7 lines
Quoted 2 lines
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
Signature · 4 lines
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
Quoted 1 line · Joern Engel
On Thu, 16 June 2005 11:15:46 +0200, Sean Young wrote:
-snip chucking unused sectors-
Quoted 6 lines
Quoted 4 lines
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?
Quoted 6 lines
What you could do, however, might be something similar to this:
o Add an
erase()function to stuctblock_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
Quoted 8 lines
if (bd_ops->erase) bd_ops->erase(block);inside
fs/fat/.
o Implementerase()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
Quoted 7 lines · Sean Young
Quoted 2 lines · Joern Engel
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.
Quoted 11 lines
Quoted 11 lines
What you could do, however, might be something similar to this:
o Add an
erase()function to stuctblock_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 justif (bd_ops->erase) bd_ops->erase(block);inside
fs/fat/.
o Implementerase()for your ftl driver to do something smart.
Quoted 8 lines
Quoted 3 lines
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'tstale()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
Signature · 2 lines
The only real mistake is the one from which we learn nothing. -- John Powell