patch-2.4.0-test12 linux/drivers/mtd/nftl.c
Next file: linux/drivers/mtd/nftlmount.c
Previous file: linux/drivers/mtd/mtdpart.c
Back to the patch index
Back to the overall index
- Lines: 1810
- Date:
Mon Dec 11 14:57:58 2000
- Orig file:
v2.4.0-test11/linux/drivers/mtd/nftl.c
- Orig date:
Thu Jul 13 10:19:55 2000
diff -u --recursive --new-file v2.4.0-test11/linux/drivers/mtd/nftl.c linux/drivers/mtd/nftl.c
@@ -1,32 +1,35 @@
-
/* Linux driver for NAND Flash Translation Layer */
/* (c) 1999 Machine Vision Holdings, Inc. */
/* Author: David Woodhouse <dwmw2@infradead.org> */
-/* $Id: nftl.c,v 1.36 2000/07/13 14:14:20 dwmw2 Exp $ */
+/* $Id: nftl.c,v 1.57 2000/12/01 17:51:54 dwmw2 Exp $ */
/*
- The contents of this file are distributed under the GNU Public
- Licence version 2 ("GPL"). The legal note below refers only to the
- _use_ of the code in some jurisdictions, and does not in any way
- affect the copying, distribution and modification of this code,
- which is permitted under the terms of the GPL.
+ The contents of this file are distributed under the GNU General
+ Public License version 2 ("GPL"). The author places no additional
+ restrictions of any kind on it. However, local legislation in some
+ countries may restrict the use of the algorithms implemented by this
+ code in certain circumstances.
+
+ The legal note below refers only to the _use_ of the code in the
+ affected jurisdictions, and does not in any way affect the copying,
+ distribution and modification of this code, which are permitted, and
+ indeed required, under the terms of the GPL.
Section 0 of the GPL says:
-
"Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope."
You may copy, distribute and modify this code to your hearts'
content - it's just that in some jurisdictions, you may only _use_
- it under the terms of the licence below. This puts it in a similar
- situation to the ISDN code, which you may need telco approval to
- use, and indeed any code which has uses that may be restricted in
- law. For example, certain malicious uses of the networking stack
- may be illegal, but that doesn't prevent the networking code from
- being under GPL.
+ it under the terms of the patent grant below. This puts it in a
+ similar situation to the ISDN code, which you may need telco
+ approval to use, and indeed any code which has uses that may be
+ restricted in law. For example, certain malicious uses of the
+ networking stack may be illegal, but that doesn't prevent the
+ networking code from being under GPL.
In fact the ISDN case is worse than this, because modification of
- the code automatically invalidates its approval. Modificiation,
+ the code automatically invalidates its approval. Modification,
unlike usage, _is_ one of the rights which is protected by the
GPL. Happily, the law in those places where approval is required
doesn't actually prevent you from modifying the code - it's just
@@ -34,7 +37,7 @@
because usage isn't addressed by the GPL, that's just fine.
dwmw2@infradead.org
- 6/7/0
+ 30/10/0
LEGAL NOTE: The NFTL format is patented by M-Systems. They have
granted a licence for its use with their DiskOnChip products:
@@ -51,10 +54,6 @@
#define PRERELEASE
-#ifdef NFTL_DEBUG
-#define DEBUGLVL debug
-#endif
-
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -67,42 +66,28 @@
#include <linux/malloc.h>
#include <linux/sched.h>
#include <linux/init.h>
+#include <linux/blkpg.h>
+#ifdef CONFIG_KMOD
+#include <linux/kmod.h>
+#endif
#include <linux/mtd/mtd.h>
#include <linux/mtd/nftl.h>
#include <linux/mtd/compatmac.h>
-#undef WE_KNOW_WTF_THIS_DOES_NOT_WORK
+/* maximum number of loops while examining next block, to have a
+ chance to detect consistency problems (they should never happen
+ because of the checks done in the mounting */
+
+#define MAX_LOOPS 10000
/* NFTL block device stuff */
#define MAJOR_NR NFTL_MAJOR
#define DEVICE_REQUEST nftl_request
#define DEVICE_OFF(device)
-#ifdef WE_KNOW_WTF_THIS_DOES_NOT_WORK
-#define LOCAL_END_REQUEST
-#endif
-#include <linux/blk.h>
-#include <linux/hdreg.h>
-
-
-#ifdef WE_KNOW_WTF_THIS_DOES_NOT_WORK
-
-static void nftl_end_request(struct request *req, int res)
-{
- req->sector += req->current_nr_sectors;
- req->nr_sectors -= req->current_nr_sectors;
-
- if (end_that_request_first( req, res, "nftl" ))
- return;
- end_that_request_last( req );
-}
-#endif
-
-#ifdef NFTL_DEBUG
-static int debug = NFTL_DEBUG;
-MODULE_PARM(debug, "i");
-#endif
+#include <linux/blk.h>
+#include <linux/hdreg.h>
/* Linux-specific block device functions */
@@ -110,11 +95,10 @@
* encountered, except ...
*/
-static int nftl_sizes[256]={0,};
+static int nftl_sizes[256] = {0,};
static int nftl_blocksizes[256] = {0,};
/* .. for the Linux partition table handling. */
-
struct hd_struct part_table[256] = {{0,0},};
#if LINUX_VERSION_CODE < 0x20328
@@ -123,8 +107,8 @@
#endif
static struct gendisk nftl_gendisk = {
- NFTL_MAJOR, /* Major number */
- "nftl", /* Major name */
+ MAJOR_NR, /* Major number */
+ "nftl", /* Major name */
4, /* Bits to shift to get real from partition */
15, /* Number of partitions per real */
#if LINUX_VERSION_CODE < 0x20328
@@ -138,281 +122,129 @@
NULL /* next */
};
-
struct NFTLrecord *NFTLs[MAX_NFTLS] = {NULL};
-static void NFTL_setup(struct mtd_info *mtd, unsigned long ofs,
- struct NFTLMediaHeader *hdr)
+static void NFTL_setup(struct mtd_info *mtd)
{
int i;
- struct NFTLrecord *thisNFTL;
+ struct NFTLrecord *nftl;
unsigned long temp;
int firstfree = -1;
- DEBUG(1,"NFTL_setup\n");
+ DEBUG(MTD_DEBUG_LEVEL1,"NFTL_setup\n");
- for (i=0; i < MAX_NFTLS; i++) {
- if (!NFTLs[i] && firstfree==-1)
+ for (i = 0; i < MAX_NFTLS; i++) {
+ if (!NFTLs[i] && firstfree == -1)
firstfree = i;
- else if (NFTLs[i] && NFTLs[i]->mtd == mtd &&
- NFTLs[i]->MediaHdr.FirstPhysicalEUN == hdr->FirstPhysicalEUN) {
+ else if (NFTLs[i] && NFTLs[i]->mtd == mtd) {
/* This is a Spare Media Header for an NFTL we've already found */
- DEBUG(1, "Spare Media Header for NFTL %d found at %lx\n",i, ofs);
- NFTLs[i]->SpareMediaUnit = ofs / mtd->erasesize;
+ DEBUG(MTD_DEBUG_LEVEL1, "MTD already mounted as NFTL\n");
return;
}
}
-
-
- /* OK, it's a new one. Set up all the data structures. */
-#ifdef PSYCHO_DEBUG
- printk("Found new NFTL nftl%c at offset %lx\n",firstfree + 'a', ofs);
-#endif
- if (hdr->UnitSizeFactor != 0xff) {
- printk("Sorry, we don't support UnitSizeFactor of != 1 yet\n");
+ if (firstfree == -1) {
+ printk(KERN_WARNING "No more NFTL slot available\n");
return;
- }
-
- thisNFTL = kmalloc(sizeof(struct NFTLrecord), GFP_KERNEL);
- if (!thisNFTL) {
+ }
+
+ nftl = kmalloc(sizeof(struct NFTLrecord), GFP_KERNEL);
+ if (!nftl) {
printk(KERN_WARNING "Out of memory for NFTL data structures\n");
return;
}
- init_MUTEX(&thisNFTL->mutex);
- thisNFTL->EraseSize = mtd->erasesize;
- memcpy(&thisNFTL->MediaHdr, hdr, sizeof(*hdr));
- thisNFTL->mtd = mtd;
- thisNFTL->MediaUnit = ofs / mtd->erasesize;
- thisNFTL->SpareMediaUnit = 0xffff;
- thisNFTL->numvunits = le32_to_cpu(thisNFTL->MediaHdr.FormattedSize) / 8192;
- thisNFTL->nr_sects = thisNFTL->numvunits * (thisNFTL->EraseSize / 512);
- thisNFTL->usecount = 0;
-
- thisNFTL->cylinders = 1024;
- thisNFTL->heads = 16;
-
- temp = thisNFTL->cylinders * thisNFTL->heads;
- thisNFTL->sectors = thisNFTL->nr_sects / temp;
-
- if (thisNFTL->nr_sects % temp) {
-
- thisNFTL->sectors++;
- temp = thisNFTL->cylinders * thisNFTL->sectors;
- thisNFTL->heads = thisNFTL->nr_sects / temp;
-
- if (thisNFTL->nr_sects & temp) {
- thisNFTL->heads++;
- temp = thisNFTL->heads * thisNFTL->sectors;
-
- thisNFTL->cylinders = thisNFTL->nr_sects / temp;
- }
- }
- if (thisNFTL->nr_sects != thisNFTL->heads * thisNFTL->cylinders *
- thisNFTL->sectors) {
- printk(KERN_WARNING "Cannot calculate an NFTL geometry to match size of 0x%lx.\n", thisNFTL->nr_sects);
- printk(KERN_WARNING "Using C:%d H:%d S:%d (== %lx sects)\n",
- thisNFTL->cylinders, thisNFTL->heads ,
- thisNFTL->sectors,
- (long)thisNFTL->cylinders * (long)thisNFTL->heads *
- (long)thisNFTL->sectors );
-
- /* Oh no we don't
- * thisNFTL->nr_sects = thisNFTL->heads * thisNFTL->cylinders * thisNFTL->sectors;
- */
- }
-
- thisNFTL->EUNtable = kmalloc( 2 * thisNFTL->numvunits,
- GFP_KERNEL);
- if (!thisNFTL->EUNtable) {
- printk("ENOMEM\n");
- kfree(thisNFTL);
- return;
- }
- memset(thisNFTL->EUNtable, 0xff, 2 * thisNFTL->numvunits);
-
- thisNFTL->VirtualUnitTable = kmalloc( 2 * le16_to_cpu(thisNFTL->MediaHdr.NumEraseUnits) , GFP_KERNEL);
- if (!thisNFTL->VirtualUnitTable) {
- printk("ENOMEM\n");
- kfree(thisNFTL->EUNtable);
- kfree(thisNFTL);
- return;
- }
- memset(thisNFTL->VirtualUnitTable, 0xff, 2 * le16_to_cpu(thisNFTL->MediaHdr.NumEraseUnits));
-
- thisNFTL->ReplUnitTable = kmalloc( 2 * le16_to_cpu(thisNFTL->MediaHdr.NumEraseUnits) , GFP_KERNEL);
- if (!thisNFTL->ReplUnitTable) {
- printk("ENOMEM\n");
- kfree(thisNFTL->VirtualUnitTable);
- kfree(thisNFTL->EUNtable);
- kfree(thisNFTL);
+ init_MUTEX(&nftl->mutex);
+
+ /* get physical parameters */
+ nftl->EraseSize = mtd->erasesize;
+ nftl->nb_blocks = mtd->size / mtd->erasesize;
+ nftl->mtd = mtd;
+
+ if (NFTL_mount(nftl) < 0) {
+ printk(KERN_WARNING "Could not mount NFTL device\n");
+ kfree(nftl);
return;
- }
- memset(thisNFTL->ReplUnitTable, 0xff, 2 *le16_to_cpu(thisNFTL->MediaHdr.NumEraseUnits) );
-
- /* Ought to check the media header for bad blocks */
- thisNFTL->lastEUN = le16_to_cpu(thisNFTL->MediaHdr.NumEraseUnits) +
- le16_to_cpu(thisNFTL->MediaHdr.FirstPhysicalEUN) - 1;
- thisNFTL->numfreeEUNs = 0;
+ }
- /* Scan each physical Erase Unit for validity and to find the
- Virtual Erase Unit Chain to which it belongs */
-
- for (i=le16_to_cpu(thisNFTL->MediaHdr.FirstPhysicalEUN);
- i <= thisNFTL->lastEUN; i++) {
-
- union nftl_uci uci;
- unsigned long ofs;
- size_t retlen;
- ofs = i * thisNFTL->EraseSize;
-
- MTD_READOOB(mtd, (i * thisNFTL->EraseSize) + 512 + 8, 8, &retlen, (char *)&uci);
-
- if (uci.b.EraseMark != cpu_to_le16(0x3c69) ||
- uci.b.EraseMark1 != cpu_to_le16(0x3c69)) {
- printk("EUN %d: EraseMark not 0x3c69 (0x%4.4x 0x%4.4x instead)\n",
- i, le16_to_cpu(uci.b.EraseMark), le16_to_cpu(uci.b.EraseMark1));
- thisNFTL->VirtualUnitTable[i] = 0x7fff;
- thisNFTL->ReplUnitTable[i] = 0xffff;
- continue;
- }
-
- MTD_READOOB(mtd, (i * thisNFTL->EraseSize) + 8, 8, &retlen, (u_char *)&uci);
-
- if (uci.a.VirtUnitNum != uci.a.SpareVirtUnitNum)
- printk("EUN %d: VirtualUnitNumber (%x) != SpareVirtualUnitNumber (%x)\n",
- i, le16_to_cpu(uci.a.VirtUnitNum),
- le16_to_cpu(uci.a.SpareVirtUnitNum));
-
- if (uci.a.ReplUnitNum != uci.a.SpareReplUnitNum)
- printk("EUN %d: ReplacementUnitNumber (%x) != SpareReplacementUnitNumber (%x)\n",
- i, le16_to_cpu(uci.a.ReplUnitNum),
- le16_to_cpu(uci.a.SpareReplUnitNum));
-
- /* We don't actually _do_ anything about the above, just whinge */
-
- thisNFTL->VirtualUnitTable[i] = le16_to_cpu(uci.a.VirtUnitNum);
- thisNFTL->ReplUnitTable[i] = le16_to_cpu(uci.a.ReplUnitNum);
-
- /* if (!(VUN & 0x8000) && VUN < (arraybounds)).. optimises to: */
- if (le16_to_cpu(uci.a.VirtUnitNum) < thisNFTL->numvunits)
- thisNFTL->EUNtable[le16_to_cpu(uci.a.VirtUnitNum) & 0x7fff] = i;
-
- if (uci.a.VirtUnitNum == 0xffff) {
- /* Free block */
- thisNFTL->LastFreeEUN = i;
- thisNFTL->numfreeEUNs++;
- }
-
- }
- NFTLs[firstfree] = thisNFTL;
- thisNFTL->LastFreeEUN = le16_to_cpu(thisNFTL->MediaHdr.FirstPhysicalEUN);
-
- //#define PSYCHO_DEBUG
+ /* OK, it's a new one. Set up all the data structures. */
#ifdef PSYCHO_DEBUG
- for (i=0; i < 10/* thisNFTL->numvunits*/; i++) {
- u16 curEUN = thisNFTL->EUNtable[i];
- int sillycount=100;
-
- printk("Virtual Unit #%d: ",i);
- if (!curEUN || curEUN == 0xffff) {
- printk("Not present\n");
- continue;
- }
- printk("%d", curEUN);
-
- while ((curEUN = thisNFTL->ReplUnitTable[curEUN]) != 0xffff && --sillycount) {
- printk(", %d", curEUN & 0xffff);
-
+ printk("Found new NFTL nftl%c\n", firstfree + 'a');
+#endif
+
+ /* linux stuff */
+ nftl->usecount = 0;
+ nftl->cylinders = 1024;
+ nftl->heads = 16;
+
+ temp = nftl->cylinders * nftl->heads;
+ nftl->sectors = nftl->nr_sects / temp;
+ if (nftl->nr_sects % temp) {
+ nftl->sectors++;
+ temp = nftl->cylinders * nftl->sectors;
+ nftl->heads = nftl->nr_sects / temp;
+
+ if (nftl->nr_sects % temp) {
+ nftl->heads++;
+ temp = nftl->heads * nftl->sectors;
+ nftl->cylinders = nftl->nr_sects / temp;
}
- printk("\n");
}
-#endif
- /* OK. Now we deal with the fact that we're in the real world. Sometimes
- things don't actually happen the way they're supposed to. Find, fix,
- and whinge about the most common deviations from spec that we have
- been known to encounter.
- */
- /* Except that I haven't implemented that bit yet :) */
+ if (nftl->nr_sects != nftl->heads * nftl->cylinders * nftl->sectors) {
+ printk(KERN_WARNING "Cannot calculate an NFTL geometry to "
+ "match size of 0x%lx.\n", nftl->nr_sects);
+ printk(KERN_WARNING "Using C:%d H:%d S:%d (== 0x%lx sects)\n",
+ nftl->cylinders, nftl->heads , nftl->sectors,
+ (long)nftl->cylinders * (long)nftl->heads * (long)nftl->sectors );
+ /* Oh no we don't have nftl->nr_sects = nftl->heads * nftl->cylinders * nftl->sectors; */
+ }
+ NFTLs[firstfree] = nftl;
/* Finally, set up the block device sizes */
- nftl_sizes[firstfree * 16]=thisNFTL->nr_sects;
-// nftl_blocksizes[firstfree*16] = 512;
- part_table[firstfree * 16].nr_sects = thisNFTL->nr_sects;
+ nftl_sizes[firstfree * 16] = nftl->nr_sects;
+ //nftl_blocksizes[firstfree*16] = 512;
+ part_table[firstfree * 16].nr_sects = nftl->nr_sects;
+
+ /* partition check ... */
#if LINUX_VERSION_CODE < 0x20328
resetup_one_dev(&nftl_gendisk, firstfree);
#else
- grok_partitions(&nftl_gendisk, firstfree, 1<<4, thisNFTL->nr_sects);
+ grok_partitions(&nftl_gendisk, firstfree, 1<<4, nftl->nr_sects);
#endif
-
}
-
static void NFTL_unsetup(int i)
{
- struct NFTLrecord *thisNFTL = NFTLs[i];
+ struct NFTLrecord *nftl = NFTLs[i];
- DEBUG(1, "NFTL_unsetup %d\n", i);
+ DEBUG(MTD_DEBUG_LEVEL1, "NFTL_unsetup %d\n", i);
NFTLs[i] = NULL;
- if (thisNFTL->VirtualUnitTable)
- kfree(thisNFTL->VirtualUnitTable);
- if (thisNFTL->ReplUnitTable)
- kfree(thisNFTL->ReplUnitTable);
- if (thisNFTL->EUNtable)
- kfree(thisNFTL->EUNtable);
+ if (nftl->ReplUnitTable)
+ kfree(nftl->ReplUnitTable);
+ if (nftl->EUNtable)
+ kfree(nftl->EUNtable);
- kfree(thisNFTL);
+ kfree(nftl);
}
-
-
-
/* Search the MTD device for NFTL partitions */
static void NFTL_notify_add(struct mtd_info *mtd)
{
- int i;
- unsigned long ofs;
- struct NFTLMediaHeader hdr;
-
- DEBUG(1, "NFTL_notify_add for %s\n", mtd->name);
+ DEBUG(MTD_DEBUG_LEVEL1, "NFTL_notify_add for %s\n", mtd->name);
if (mtd) {
- if (!mtd->read_oob) /* If this MTD doesn't have out-of-band data,
- then there's no point continuing */
- {
- DEBUG(1, "No OOB data, quitting\n");
+ if (!mtd->read_oob) {
+ /* If this MTD doesn't have out-of-band data,
+ then there's no point continuing */
+ DEBUG(MTD_DEBUG_LEVEL1, "No OOB data, quitting\n");
return;
}
- DEBUG(3, "mtd->read = %p,size = %d, erasesize = %d\n",
- mtd->read, mtd->size, mtd->erasesize);
- for (ofs = 0; ofs < mtd->size ; ofs += mtd->erasesize) {
- size_t retlen = 0;
- MTD_READ(mtd, ofs, sizeof(hdr), &retlen, (u_char *)&hdr);
-
- if (retlen < sizeof(hdr))
- {
- continue;
- }
-
- if (!strncmp(hdr.DataOrgID, "ANAND", 6)) {
- DEBUG(2, "Valid NFTL partition at ofs %ld\n", ofs);
- NFTL_setup(mtd, ofs, &hdr);
- }
- else {
- DEBUG(3,"No valid NFTL Partition at ofs %d\n", ofs);
- for(i = 0; i < 6; i++) {
- DEBUG(3,"%x, ", hdr.DataOrgID[i]);
- }
- DEBUG(3," = %s\n", hdr.DataOrgID);
- DEBUG(3,"%d, %d, %d, %d\n", hdr.NumEraseUnits, hdr.FirstPhysicalEUN,
- hdr.FormattedSize, hdr.UnitSizeFactor);
+ DEBUG(MTD_DEBUG_LEVEL3, "mtd->read = %p, size = %d, erasesize = %d\n",
+ mtd->read, mtd->size, mtd->erasesize);
- }
- }
- return;
+ NFTL_setup(mtd);
}
}
@@ -420,100 +252,101 @@
{
int i;
- for (i=0; i< MAX_NFTLS; i++) {
+ for (i = 0; i < MAX_NFTLS; i++) {
if (NFTLs[i] && NFTLs[i]->mtd == mtd)
NFTL_unsetup(i);
}
}
-
#ifdef CONFIG_NFTL_RW
/* Actual NFTL access routines */
-
-
-static u16 NFTL_findfreeblock( struct NFTLrecord *thisNFTL, int desperate )
+/* NFTL_findfreeblock: Find a free Erase Unit on the NFTL partition. This function is used
+ * when the give Virtual Unit Chain
+ */
+static u16 NFTL_findfreeblock(struct NFTLrecord *nftl, int desperate )
{
/* For a given Virtual Unit Chain: find or create a free block and
add it to the chain */
/* We're passed the number of the last EUN in the chain, to save us from
having to look it up again */
-
- u16 pot = thisNFTL->LastFreeEUN;
+ u16 pot = nftl->LastFreeEUN;
int silly = -1;
/* Normally, we force a fold to happen before we run out of free blocks completely */
-
- if (!desperate && thisNFTL->numfreeEUNs < 2) {
- // printk("NFTL_findfreeblock: there are too few free EUNs\n");
+ if (!desperate && nftl->numfreeEUNs < 2) {
+ DEBUG(MTD_DEBUG_LEVEL1, "NFTL_findfreeblock: there are too few free EUNs\n");
return 0xffff;
}
/* Scan for a free block */
-
do {
- if (thisNFTL->VirtualUnitTable[pot] == 0xffff) {
- thisNFTL->LastFreeEUN = pot;
- thisNFTL->numfreeEUNs--;
+ if (nftl->ReplUnitTable[pot] == BLOCK_FREE) {
+ nftl->LastFreeEUN = pot;
+ nftl->numfreeEUNs--;
return pot;
}
- if (++pot > thisNFTL->lastEUN)
- pot = le16_to_cpu(thisNFTL->MediaHdr.FirstPhysicalEUN);
+ /* This will probably point to the MediaHdr unit itself,
+ right at the beginning of the partition. But that unit
+ (and the backup unit too) should have the UCI set
+ up so that it's not selected for overwriting */
+ if (++pot > nftl->lastEUN)
+ pot = le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN);
if (!silly--) {
- printk("Tell Dave he fucked up. LastFreeEUN = %d, FirstEUN = %d\n",
- thisNFTL->LastFreeEUN, le16_to_cpu(thisNFTL->MediaHdr.FirstPhysicalEUN));
+ printk("Argh! No free blocks found! LastFreeEUN = %d, "
+ "FirstEUN = %d\n", nftl->LastFreeEUN,
+ le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN));
return 0xffff;
}
-
- } while (pot != thisNFTL->LastFreeEUN);
+ } while (pot != nftl->LastFreeEUN);
return 0xffff;
}
-
-
-
-
-static u16 NFTL_foldchain (struct NFTLrecord *thisNFTL, u16 thisVUC, unsigned pendingblock )
+static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned pendingblock )
{
- u16 BlockMap[thisNFTL->EraseSize / 512];
- unsigned char BlockLastState[thisNFTL->EraseSize / 512];
- unsigned char BlockFreeFound[thisNFTL->EraseSize / 512];
- u16 thisEUN;
+ u16 BlockMap[MAX_SECTORS_PER_UNIT];
+ unsigned char BlockLastState[MAX_SECTORS_PER_UNIT];
+ unsigned char BlockFreeFound[MAX_SECTORS_PER_UNIT];
+ unsigned int thisEUN;
int block;
- int silly = -1;
- u16 targetEUN = 0xffff;
+ int silly;
+ unsigned int targetEUN;
struct nftl_oob oob;
int inplace = 1;
+ size_t retlen;
memset(BlockMap, 0xff, sizeof(BlockMap));
memset(BlockFreeFound, 0, sizeof(BlockFreeFound));
- thisEUN = thisNFTL->EUNtable[thisVUC];
+ thisEUN = nftl->EUNtable[thisVUC];
- if (thisEUN == 0xffff) {
- printk(KERN_WARNING "Trying to fold non-existent Virtual Unit Chain %d!\n", thisVUC);
- return 0xffff;
+ if (thisEUN == BLOCK_NIL) {
+ printk(KERN_WARNING "Trying to fold non-existent "
+ "Virtual Unit Chain %d!\n", thisVUC);
+ return BLOCK_NIL;
}
/* Scan to find the Erase Unit which holds the actual data for each
512-byte block within the Chain.
*/
+ silly = MAX_LOOPS;
+ targetEUN = BLOCK_NIL;
+ while (thisEUN <= nftl->lastEUN ) {
+ unsigned int status, foldmark;
- while( thisEUN <= thisNFTL->lastEUN ) {
- size_t retlen;
-
targetEUN = thisEUN;
-
- for (block = 0 ; block < thisNFTL->EraseSize / 512; block ++) {
-
- MTD_READOOB(thisNFTL->mtd, (thisEUN * thisNFTL->EraseSize) + (block * 512),16 , &retlen, (char *)&oob);
-
+ for (block = 0; block < nftl->EraseSize / 512; block ++) {
+ MTD_READOOB(nftl->mtd,
+ (thisEUN * nftl->EraseSize) + (block * 512),
+ 16 , &retlen, (char *)&oob);
if (block == 2) {
- if (oob.u.c.WriteInh != 0xffffffff) {
- printk("Write Inhibited on EUN %d\n", thisEUN);
+ foldmark = oob.u.c.FoldMark | oob.u.c.FoldMark1;
+ if (foldmark == FOLD_MARK_IN_PROGRESS) {
+ DEBUG(MTD_DEBUG_LEVEL1,
+ "Write Inhibited on EUN %d\n", thisEUN);
inplace = 0;
} else {
/* There's no other reason not to do inplace,
@@ -522,153 +355,139 @@
inplace = 1;
}
}
+ status = oob.b.Status | oob.b.Status1;
+ BlockLastState[block] = status;
- BlockLastState[block] = (unsigned char) oob.b.Status & 0xff;
-
- switch(oob.b.Status) {
- case __constant_cpu_to_le16(BLOCK_FREE):
- BlockFreeFound[block]=1;
+ switch(status) {
+ case SECTOR_FREE:
+ BlockFreeFound[block] = 1;
break;
- case __constant_cpu_to_le16(BLOCK_USED):
+ case SECTOR_USED:
if (!BlockFreeFound[block])
BlockMap[block] = thisEUN;
else
- printk(KERN_WARNING "BLOCK_USED found after BLOCK_FREE in Virtual Unit Chain %d for block %d\n", thisVUC, block);
+ printk(KERN_WARNING
+ "SECTOR_USED found after SECTOR_FREE "
+ "in Virtual Unit Chain %d for block %d\n",
+ thisVUC, block);
break;
- case __constant_cpu_to_le16(BLOCK_IGNORE):
- case __constant_cpu_to_le16(BLOCK_DELETED):
+ case SECTOR_IGNORE:
+ case SECTOR_DELETED:
break;
default:
- printk("Unknown status for block %d in EUN %d: %x\n",block,thisEUN, oob.b.Status);
+ printk("Unknown status for block %d in EUN %d: %x\n",
+ block, thisEUN, status);
}
}
if (!silly--) {
- printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n", thisVUC);
- return 0xffff;
+ printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n",
+ thisVUC);
+ return BLOCK_NIL;
}
- thisEUN = thisNFTL->ReplUnitTable[thisEUN] & 0x7fff;
+ thisEUN = nftl->ReplUnitTable[thisEUN];
}
if (inplace) {
/* We're being asked to be a fold-in-place. Check
- that all blocks are either present or BLOCK_FREE
+ that all blocks are either present or SECTOR_FREE
in the target block. If not, we're going to have
to fold out-of-place anyway.
*/
-
- for (block = 0; block < thisNFTL->EraseSize / 512 ; block++) {
-
- if (BlockLastState[block] != (unsigned char) (cpu_to_le16(BLOCK_FREE) & 0xff) &&
+ for (block = 0; block < nftl->EraseSize / 512 ; block++) {
+ if (BlockLastState[block] != SECTOR_FREE &&
BlockMap[block] != targetEUN) {
- DEBUG(1, "Setting inplace to 0. VUC %d, block %d was %x lastEUN, and is in EUN %d (%s) %d\n",
- thisVUC, block, BlockLastState[block], BlockMap[block] , BlockMap[block]==targetEUN?"==":"!=", targetEUN);
-
+ DEBUG(MTD_DEBUG_LEVEL1, "Setting inplace to 0. VUC %d, "
+ "block %d was %x lastEUN, "
+ "and is in EUN %d (%s) %d\n",
+ thisVUC, block, BlockLastState[block],
+ BlockMap[block],
+ BlockMap[block]== targetEUN ? "==" : "!=",
+ targetEUN);
inplace = 0;
break;
}
}
- if ( pendingblock >= (thisVUC * (thisNFTL->EraseSize / 512)) &&
- pendingblock < ((thisVUC + 1)* (thisNFTL->EraseSize / 512)) &&
- BlockLastState[ pendingblock - (thisVUC * (thisNFTL->EraseSize / 512))] !=
- (unsigned char) (cpu_to_le16(BLOCK_FREE) & 0xff)) {
- DEBUG(1, "Pending write not free in EUN %d. Folding out of place.\n", targetEUN);
+ if (pendingblock >= (thisVUC * (nftl->EraseSize / 512)) &&
+ pendingblock < ((thisVUC + 1)* (nftl->EraseSize / 512)) &&
+ BlockLastState[pendingblock - (thisVUC * (nftl->EraseSize / 512))] !=
+ SECTOR_FREE) {
+ DEBUG(MTD_DEBUG_LEVEL1, "Pending write not free in EUN %d. "
+ "Folding out of place.\n", targetEUN);
inplace = 0;
}
-
}
if (!inplace) {
- DEBUG(1, "Cannot fold Virtual Unit Chain %d in place. Trying out-of-place\n", thisVUC);
+ DEBUG(MTD_DEBUG_LEVEL1, "Cannot fold Virtual Unit Chain %d in place. "
+ "Trying out-of-place\n", thisVUC);
/* We need to find a targetEUN to fold into. */
- targetEUN = NFTL_findfreeblock(thisNFTL, 1);
- if (targetEUN == 0xffff) {
- /* Ouch. Now we're screwed. We need to do a
- fold-in-place of another chain to make room
- for this one. We need a better way of selecting
- which chain to fold, because makefreeblock will
- only ask us to fold the same one again.
- */
- printk(KERN_WARNING"NFTL_findfreeblock(desperate) returns 0xffff.\n");
- return 0xffff;
+ targetEUN = NFTL_findfreeblock(nftl, 1);
+ if (targetEUN == BLOCK_NIL) {
+ /* Ouch. Now we're screwed. We need to do a
+ fold-in-place of another chain to make room
+ for this one. We need a better way of selecting
+ which chain to fold, because makefreeblock will
+ only ask us to fold the same one again.
+ */
+ printk(KERN_WARNING
+ "NFTL_findfreeblock(desperate) returns 0xffff.\n");
+ return BLOCK_NIL;
}
-
- }
-
+ } else {
+ /* We put a fold mark in the chain we are folding only if
+ we fold in place to help the mount check code. If we do
+ not fold in place, it is possible to find the valid
+ chain by selecting the longer one */
+ oob.u.c.FoldMark = oob.u.c.FoldMark1 = cpu_to_le16(FOLD_MARK_IN_PROGRESS);
+ oob.u.c.unused = 0xffffffff;
+ MTD_WRITEOOB(nftl->mtd, (nftl->EraseSize * targetEUN) + 2 * 512 + 8,
+ 8, &retlen, (char *)&oob.u);
+ }
/* OK. We now know the location of every block in the Virtual Unit Chain,
and the Erase Unit into which we are supposed to be copying.
Go for it.
*/
-
- DEBUG(1,"Folding chain %d into unit %d\n", thisVUC, targetEUN);
-
- for (block = 0; block < thisNFTL->EraseSize / 512 ; block++) {
+ DEBUG(MTD_DEBUG_LEVEL1,"Folding chain %d into unit %d\n", thisVUC, targetEUN);
+ for (block = 0; block < nftl->EraseSize / 512 ; block++) {
unsigned char movebuf[512];
- struct nftl_oob oob;
- size_t retlen;
-
- memset(&oob, 0xff, sizeof(oob));
+ int ret;
/* If it's in the target EUN already, or if it's pending write, do nothing */
- if (BlockMap[block] == targetEUN ||(pendingblock == (thisVUC * (thisNFTL->EraseSize / 512) + block))) {
- /* Except if it's the first block, in which case we have to
- set the UnitNumbers */
- if (block == 0) {
-
- thisNFTL->mtd->read_oob(thisNFTL->mtd, (thisNFTL->EraseSize * targetEUN) ,
- 16, &retlen, (char *)&oob);
-
- // printk("Setting VirtUnitNum on EUN %d to %x, was %x\n", targetEUN, thisVUC,
- // le16_to_cpu(oob.u.a.VirtUnitNum));
-
- oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC & 0x7fff);
-
- thisNFTL->mtd->write_oob(thisNFTL->mtd, (thisNFTL->EraseSize * targetEUN) ,
- 16, &retlen, (char *)&oob);
- }
+ if (BlockMap[block] == targetEUN ||
+ (pendingblock == (thisVUC * (nftl->EraseSize / 512) + block))) {
continue;
}
- oob.b.Status = BLOCK_USED;
-
- switch(block) {
- case 0:
- oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC & 0x7fff);
- // printk("Setting VirtUnitNum on EUN %d to %x\n", targetEUN, thisVUC);
-
- oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = 0xffff;
- break;
-
- case 1:
- oob.u.b.WearInfo = cpu_to_le32(3); // We don't use this, but M-Systems' drivers do
- oob.u.b.EraseMark = oob.u.b.EraseMark1 = cpu_to_le16(0x3c69);
- break;
-
- case 2:
- default:
- oob.u.c.WriteInh = 0xffffffff;
- oob.u.c.unused = 0xffffffff;
- }
- if (thisNFTL->mtd->read_ecc(thisNFTL->mtd, (thisNFTL->EraseSize * BlockMap[block]) + (block * 512),
- 512, &retlen, movebuf, (char *)&oob) == -EIO) {
- if (thisNFTL->mtd->read_ecc(thisNFTL->mtd, (thisNFTL->EraseSize * BlockMap[block]) + (block * 512),
- 512, &retlen, movebuf, (char *)&oob) != -EIO)
- printk("Error went away on retry.\n");
- }
-
- thisNFTL->mtd->write_ecc(thisNFTL->mtd, (thisNFTL->EraseSize * targetEUN) + (block * 512),
- 512, &retlen, movebuf, (char *)&oob);
-
-
- /* FIXME: Add some error checking.... */
- thisNFTL->mtd->write_oob(thisNFTL->mtd, (thisNFTL->EraseSize * targetEUN) + (block * 512),
- 16, &retlen, (char *)&oob);
-
- }
+ /* copy only in non free block (free blocks can only
+ happen in case of media errors or deleted blocks) */
+ if (BlockMap[block] == BLOCK_NIL)
+ continue;
+
+ ret = MTD_READECC(nftl->mtd, (nftl->EraseSize * BlockMap[block])
+ + (block * 512), 512, &retlen, movebuf, (char *)&oob);
+ if (ret < 0) {
+ ret = MTD_READECC(nftl->mtd, (nftl->EraseSize * BlockMap[block])
+ + (block * 512), 512, &retlen,
+ movebuf, (char *)&oob);
+ if (ret != -EIO)
+ printk("Error went away on retry.\n");
+ }
+ MTD_WRITEECC(nftl->mtd, (nftl->EraseSize * targetEUN) + (block * 512),
+ 512, &retlen, movebuf, (char *)&oob);
+ }
+
+ /* add the header so that it is now a valid chain */
+ oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum
+ = cpu_to_le16(thisVUC);
+ oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = 0xffff;
+
+ MTD_WRITEOOB(nftl->mtd, (nftl->EraseSize * targetEUN) + 8,
+ 8, &retlen, (char *)&oob.u);
/* OK. We've moved the whole lot into the new block. Now we have to free the original blocks. */
@@ -677,78 +496,37 @@
shouldn't actually lose data in this case. It's just that when we load up on a medium which
has duplicate chains, we need to free one of the chains because it's not necessary any more.
*/
-
-
- thisEUN = thisNFTL->EUNtable[thisVUC];
+ thisEUN = nftl->EUNtable[thisVUC];
+ DEBUG(MTD_DEBUG_LEVEL1,"Want to erase\n");
- DEBUG(1,"Want to erase\n");
/* For each block in the old chain (except the targetEUN of course),
free it and make it available for future use */
+ while (thisEUN <= nftl->lastEUN && thisEUN != targetEUN) {
+ unsigned int EUNtmp;
- while( thisEUN <= thisNFTL->lastEUN && thisEUN != targetEUN) {
- size_t retlen;
- struct erase_info *instr;
- u16 EUNtmp;
-
- instr = kmalloc(sizeof(struct erase_info), GFP_KERNEL);
- if (!instr) {
- printk(KERN_WARNING "Out of memory for struct erase_info\n");
-
- EUNtmp = thisEUN;
-
- thisEUN = thisNFTL->ReplUnitTable[EUNtmp] & 0x7fff;
- thisNFTL->VirtualUnitTable[EUNtmp] = 0x7fff;
- thisNFTL->ReplUnitTable[EUNtmp] = 0xffff;
- } else {
- memset(instr, 0, sizeof(struct erase_info));
- instr->addr = thisEUN * thisNFTL->EraseSize;
- instr->len = thisNFTL->EraseSize;
-
- MTD_ERASE(thisNFTL->mtd, instr);
- /* This is an async interface. Or will be. At which point
- this code will break. */
-
-#if 0
- MTD_READOOB(thisNFTL->mtd, (thisEUN * thisNFTL->EraseSize) + 512, 16, &retlen, (char *)&oob);
-
- printk("After erasing, EUN %d contains: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
- thisEUN, oob.b.ECCSig[0],
- oob.b.ECCSig[1],
- oob.b.ECCSig[2],
- oob.b.ECCSig[3],
- oob.b.ECCSig[4],
- oob.b.ECCSig[5]);
-#endif
- memset(&oob, 0xff, sizeof(oob));
- oob.u.b.WearInfo = cpu_to_le32(3);
- oob.u.b.EraseMark = oob.u.b.EraseMark1 = cpu_to_le16(0x3c69);
-
- MTD_WRITEOOB(thisNFTL->mtd, (thisEUN * thisNFTL->EraseSize) + 512, 16, &retlen, (char *)&oob);
-
- EUNtmp = thisEUN;
-
- thisEUN = thisNFTL->ReplUnitTable[EUNtmp] & 0x7fff;
- thisNFTL->VirtualUnitTable[EUNtmp] = 0xffff;
- thisNFTL->ReplUnitTable[EUNtmp] = 0xffff;
-
- thisNFTL->numfreeEUNs++;
-
- }
-
- // shifted upwards: thisEUN = thisNFTL->ReplUnitTable[thisEUN] & 0x7fff;
+ EUNtmp = nftl->ReplUnitTable[thisEUN];
+ if (NFTL_formatblock(nftl, thisEUN) < 0) {
+ /* could not erase : mark block as reserved
+ * FixMe: Update Bad Unit Table on disk
+ */
+ nftl->ReplUnitTable[thisEUN] = BLOCK_RESERVED;
+ } else {
+ /* correctly erased : mark it as free */
+ nftl->ReplUnitTable[thisEUN] = BLOCK_FREE;
+ nftl->numfreeEUNs++;
+ }
+ thisEUN = EUNtmp;
}
/* Make this the new start of chain for thisVUC */
- thisNFTL->VirtualUnitTable[targetEUN] = thisVUC;
- thisNFTL->ReplUnitTable[targetEUN] = 0xffff;
+ nftl->ReplUnitTable[targetEUN] = BLOCK_NIL;
+ nftl->EUNtable[thisVUC] = targetEUN;
- thisNFTL->EUNtable[thisVUC] = targetEUN;
return targetEUN;
-
}
-u16 NFTL_makefreeblock( struct NFTLrecord *thisNFTL , unsigned pendingblock)
+u16 NFTL_makefreeblock( struct NFTLrecord *nftl , unsigned pendingblock)
{
/* This is the part that needs some cleverness applied.
For now, I'm doing the minimum applicable to actually
@@ -757,23 +535,21 @@
and we also need to do some assessment of the results when
the system loses power half-way through the routine.
*/
-
u16 LongestChain = 0;
u16 ChainLength = 0, thislen;
u16 chain, EUN;
-
- for (chain=0; chain < thisNFTL->MediaHdr.FormattedSize / thisNFTL->EraseSize; chain++) {
- EUN = thisNFTL->EUNtable[chain];
-
+ for (chain = 0; chain < nftl->MediaHdr.FormattedSize / nftl->EraseSize; chain++) {
+ EUN = nftl->EUNtable[chain];
thislen = 0;
- while (EUN <= thisNFTL->lastEUN) {
+ while (EUN <= nftl->lastEUN) {
thislen++;
- // printk("VUC %d reaches len %d with EUN %d\n", chain, thislen, EUN);
- EUN = thisNFTL->ReplUnitTable[EUN] & 0x7fff;
+ //printk("VUC %d reaches len %d with EUN %d\n", chain, thislen, EUN);
+ EUN = nftl->ReplUnitTable[EUN] & 0x7fff;
if (thislen > 0xff00) {
- printk("Endless loop in Virtual Chain %d: Unit %x\n", chain, EUN);
+ printk("Endless loop in Virtual Chain %d: Unit %x\n",
+ chain, EUN);
}
if (thislen > 0xff10) {
/* Actually, don't return failure. Just ignore this chain and
@@ -781,42 +557,38 @@
thislen = 0;
break;
}
-
}
-
if (thislen > ChainLength) {
- // printk("New longest chain is %d with length %d\n", chain, thislen);
+ //printk("New longest chain is %d with length %d\n", chain, thislen);
ChainLength = thislen;
LongestChain = chain;
}
- }
+ }
if (ChainLength < 2) {
- printk(KERN_WARNING "No Virtual Unit Chains available for folding. Failing request\n");
+ printk(KERN_WARNING "No Virtual Unit Chains available for folding. "
+ "Failing request\n");
return 0xffff;
}
-
- return NFTL_foldchain (thisNFTL, LongestChain, pendingblock);
+
+ return NFTL_foldchain (nftl, LongestChain, pendingblock);
}
/* NFTL_findwriteunit: Return the unit number into which we can write
for this block. Make it available if it isn't already
*/
-
-static inline u16 NFTL_findwriteunit(struct NFTLrecord *thisNFTL, unsigned block)
+static inline u16 NFTL_findwriteunit(struct NFTLrecord *nftl, unsigned block)
{
u16 lastEUN;
- u16 thisVUC = block / (thisNFTL->EraseSize / 512);
- u16 writeEUN;
- unsigned long blockofs = (block * 512) & (thisNFTL->EraseSize -1);
+ u16 thisVUC = block / (nftl->EraseSize / 512);
+ unsigned int writeEUN;
+ unsigned long blockofs = (block * 512) & (nftl->EraseSize -1);
size_t retlen;
- int silly = 0x10000, silly2 = 3;
+ int silly, silly2 = 3;
struct nftl_oob oob;
- int debug=0;
do {
-
/* Scan the media to find a unit in the VUC which has
a free space for the block in question.
*/
@@ -824,28 +596,30 @@
/* This condition catches the 0x[7f]fff cases, as well as
being a sanity check for past-end-of-media access
*/
- lastEUN = 0xffff;
- writeEUN = thisNFTL->EUNtable[thisVUC];
-
- while(writeEUN <= thisNFTL->lastEUN) {
+ lastEUN = BLOCK_NIL;
+ writeEUN = nftl->EUNtable[thisVUC];
+ silly = MAX_LOOPS;
+ while (writeEUN <= nftl->lastEUN) {
struct nftl_bci bci;
size_t retlen;
-
+ unsigned int status;
+
lastEUN = writeEUN;
+
+ MTD_READOOB(nftl->mtd, (writeEUN * nftl->EraseSize) + blockofs,
+ 8, &retlen, (char *)&bci);
- MTD_READOOB(thisNFTL->mtd, (writeEUN * thisNFTL->EraseSize)
- + blockofs,8, &retlen, (char *)&bci);
-
- if (debug)
- printk("Status of block %d in EUN %d is %x\n", block , writeEUN, le16_to_cpu(bci.Status));
+ DEBUG(MTD_DEBUG_LEVEL2, "Status of block %d in EUN %d is %x\n",
+ block , writeEUN, le16_to_cpu(bci.Status));
- switch(bci.Status) {
- case __constant_cpu_to_le16(BLOCK_FREE):
+ status = bci.Status | bci.Status1;
+ switch(status) {
+ case SECTOR_FREE:
return writeEUN;
- case __constant_cpu_to_le16(BLOCK_DELETED):
- case __constant_cpu_to_le16(BLOCK_USED):
- case __constant_cpu_to_le16(BLOCK_IGNORE):
+ case SECTOR_DELETED:
+ case SECTOR_USED:
+ case SECTOR_IGNORE:
break;
default:
// Invalid block. Don't use it any more. Must implement.
@@ -853,35 +627,35 @@
}
if (!silly--) {
- printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n", thisVUC);
+ printk(KERN_WARNING
+ "Infinite loop in Virtual Unit Chain 0x%x\n",
+ thisVUC);
return 0xffff;
}
/* Skip to next block in chain */
-
- writeEUN = thisNFTL->ReplUnitTable[writeEUN] & 0x7fff;
+ writeEUN = nftl->ReplUnitTable[writeEUN];
}
/* OK. We didn't find one in the existing chain, or there
is no existing chain. */
/* Try to find an already-free block */
+ writeEUN = NFTL_findfreeblock(nftl, 0);
- writeEUN = NFTL_findfreeblock(thisNFTL, 0);
-
- if (writeEUN == 0xffff) {
+ if (writeEUN == BLOCK_NIL) {
/* That didn't work - there were no free blocks just
waiting to be picked up. We're going to have to fold
a chain to make room.
*/
/* First remember the start of this chain */
- // u16 startEUN = thisNFTL->EUNtable[thisVUC];
+ //u16 startEUN = nftl->EUNtable[thisVUC];
//printk("Write to VirtualUnitChain %d, calling makefreeblock()\n", thisVUC);
- writeEUN = NFTL_makefreeblock(thisNFTL, block);
+ writeEUN = NFTL_makefreeblock(nftl, 0xffff);
- if (writeEUN == 0xffff) {
+ if (writeEUN == BLOCK_NIL) {
/* Ouch. This should never happen - we should
always be able to make some room somehow.
If we get here, we've allocated more storage
@@ -889,235 +663,223 @@
routine is missing something.
*/
printk(KERN_WARNING "Cannot make free space.\n");
- return 0xffff;
+ return BLOCK_NIL;
}
- // printk("Restarting scan\n");
- lastEUN = 0xffff;
- // debug = 1;
+ //printk("Restarting scan\n");
+ lastEUN = BLOCK_NIL;
continue;
-#if 0
- if (startEUN != thisNFTL->EUNtable[thisVUC]) {
- /* The fold operation has moved the chain
- that we're looking at. Start the scan again.
- */
- continue;
- }
-#endif
}
/* We've found a free block. Insert it into the chain. */
- if (lastEUN != 0xffff) {
- /* Addition to an existing chain. Make the previous
- last block in the chain point to this one.
- */
-
- //printk("Linking EUN %d to EUN %d in VUC %d\n",
- // lastEUN, writeEUN, thisVUC);
- /* Both in our cache... */
- thisNFTL->ReplUnitTable[lastEUN] = writeEUN;
-
-
- /* ... and on the flash itself */
- MTD_READOOB(thisNFTL->mtd, (lastEUN * thisNFTL->EraseSize), 16, &retlen,
- (char *)&oob);
+ if (lastEUN != BLOCK_NIL) {
+ thisVUC |= 0x8000; /* It's a replacement block */
+ } else {
+ /* The first block in a new chain */
+ nftl->EUNtable[thisVUC] = writeEUN;
+ }
- oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = cpu_to_le16(writeEUN);
+ /* set up the actual EUN we're writing into */
+ /* Both in our cache... */
+ nftl->ReplUnitTable[writeEUN] = BLOCK_NIL;
- MTD_WRITEOOB(thisNFTL->mtd, (lastEUN * thisNFTL->EraseSize), 16, &retlen,
- (char *)&oob);
+ /* ... and on the flash itself */
+ MTD_READOOB(nftl->mtd, writeEUN * nftl->EraseSize + 8, 8,
+ &retlen, (char *)&oob.u);
- thisVUC |= 0x8000; /* It's a replacement block */
- } else {
- /* The first block in a new chain */
- thisNFTL->EUNtable[thisVUC] = writeEUN;
- }
+ oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC);
- /* Now set up the actual EUN we're writing into */
+ MTD_WRITEOOB(nftl->mtd, writeEUN * nftl->EraseSize + 8, 8,
+ &retlen, (char *)&oob.u);
+ /* we link the new block to the chain only after the
+ block is ready. It avoids the case where the chain
+ could point to a free block */
+ if (lastEUN != BLOCK_NIL) {
/* Both in our cache... */
- thisNFTL->VirtualUnitTable[writeEUN] = thisVUC;
- thisNFTL->ReplUnitTable[writeEUN] = 0xffff;
-
+ nftl->ReplUnitTable[lastEUN] = writeEUN;
/* ... and on the flash itself */
- MTD_READOOB(thisNFTL->mtd, writeEUN * thisNFTL->EraseSize, 16,
- &retlen, (char *)&oob);
+ MTD_READOOB(nftl->mtd, (lastEUN * nftl->EraseSize) + 8,
+ 8, &retlen, (char *)&oob.u);
- oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC);
+ oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum
+ = cpu_to_le16(writeEUN);
- MTD_WRITEOOB(thisNFTL->mtd, writeEUN * thisNFTL->EraseSize, 16,
- &retlen, (char *)&oob);
+ MTD_WRITEOOB(nftl->mtd, (lastEUN * nftl->EraseSize) + 8,
+ 8, &retlen, (char *)&oob.u);
+ }
return writeEUN;
} while (silly2--);
- printk(KERN_WARNING "Error folding to make room for Virtual Unit Chain 0x%x\n", thisVUC);
+ printk(KERN_WARNING "Error folding to make room for Virtual Unit Chain 0x%x\n",
+ thisVUC);
return 0xffff;
}
-static int NFTL_writeblock(struct NFTLrecord *thisNFTL, unsigned block,
- char *buffer)
+static int NFTL_writeblock(struct NFTLrecord *nftl, unsigned block, char *buffer)
{
u16 writeEUN;
- unsigned long blockofs = (block * 512) & (thisNFTL->EraseSize -1);
+ unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1);
size_t retlen;
- u16 eccbuf[8];
+ u8 eccbuf[6];
- // if (thisEUN == 0xffff) thisEUN = 0;
+ writeEUN = NFTL_findwriteunit(nftl, block);
- writeEUN = NFTL_findwriteunit(thisNFTL, block);
-
-// printk("writeblock(%d): Write to Unit %d\n", block, writeEUN);
-
- if (writeEUN == 0xffff) {
- printk(KERN_WARNING "NFTL_writeblock(): Cannot find block to write to\n");
+ if (writeEUN == BLOCK_NIL) {
+ printk(KERN_WARNING
+ "NFTL_writeblock(): Cannot find block to write to\n");
/* If we _still_ haven't got a block to use, we're screwed */
return 1;
}
-// printk("Writing block %lx to EUN %x\n",block, writeEUN);
-
- thisNFTL->mtd->write_ecc(thisNFTL->mtd,
- (writeEUN * thisNFTL->EraseSize) + blockofs,
- 512, &retlen, (char *)buffer, (char *)eccbuf);
- eccbuf[3] = BLOCK_USED;
- eccbuf[4] = eccbuf[5] = eccbuf[6] = eccbuf[7] = 0xffff;
-
- thisNFTL->mtd->write_oob(thisNFTL->mtd,
- (writeEUN * thisNFTL->EraseSize) + blockofs,
- 16, &retlen, (char *)eccbuf);
+ MTD_WRITEECC(nftl->mtd, (writeEUN * nftl->EraseSize) + blockofs,
+ 512, &retlen, (char *)buffer, (char *)eccbuf);
+ /* no need to write SECTOR_USED flags since they are written in mtd_writeecc */
return 0;
}
-
#endif /* CONFIG_NFTL_RW */
-static int NFTL_readblock(struct NFTLrecord *thisNFTL,
- unsigned block, char *buffer)
+static int NFTL_readblock(struct NFTLrecord *nftl, unsigned block, char *buffer)
{
- u16 lastgoodEUN = 0xffff;
- u16 thisEUN = thisNFTL->EUNtable[block / (thisNFTL->EraseSize / 512)];
- unsigned long blockofs = (block * 512) & (thisNFTL->EraseSize -1);
-
- int silly = -1;
+ u16 lastgoodEUN;
+ u16 thisEUN = nftl->EUNtable[block / (nftl->EraseSize / 512)];
+ unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1);
+ unsigned int status;
+ int silly = MAX_LOOPS;
+ size_t retlen;
+ struct nftl_bci bci;
+
+ lastgoodEUN = BLOCK_NIL;
+
+ if (thisEUN != BLOCK_NIL) {
+ while (thisEUN < nftl->nb_blocks) {
+ if (MTD_READOOB(nftl->mtd, (thisEUN * nftl->EraseSize) + blockofs,
+ 8, &retlen, (char *)&bci) < 0)
+ status = SECTOR_IGNORE;
+ else
+ status = bci.Status | bci.Status1;
+
+ switch (status) {
+ case SECTOR_FREE:
+ /* no modification of a sector should follow a free sector */
+ goto the_end;
+ case SECTOR_DELETED:
+ lastgoodEUN = BLOCK_NIL;
+ break;
+ case SECTOR_USED:
+ lastgoodEUN = thisEUN;
+ break;
+ case SECTOR_IGNORE:
+ break;
+ default:
+ printk("Unknown status for block %d in EUN %d: %x\n",
+ block, thisEUN, status);
+ break;
+ }
- if (thisEUN == 0xffff) thisEUN = 0;
-
- while(thisEUN && (thisEUN & 0x7fff) != 0x7fff) {
- struct nftl_bci bci;
- size_t retlen;
-
- MTD_READOOB(thisNFTL->mtd, (thisEUN * thisNFTL->EraseSize) + blockofs,8, &retlen, (char *)&bci);
-
- switch(bci.Status) {
- case __constant_cpu_to_le16(BLOCK_FREE):
- thisEUN = 0;
- break;
- case __constant_cpu_to_le16(BLOCK_USED):
- lastgoodEUN = thisEUN;
- break;
- case __constant_cpu_to_le16(BLOCK_IGNORE):
- case __constant_cpu_to_le16(BLOCK_DELETED):
- break;
- default:
- printk("Unknown status for block %d in EUN %d: %x\n",block,thisEUN, bci.Status);
+ if (!silly--) {
+ printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n",
+ block / (nftl->EraseSize / 512));
+ return 1;
+ }
+ thisEUN = nftl->ReplUnitTable[thisEUN];
}
+ }
- if (!silly--) {
- printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n",block / (thisNFTL->EraseSize / 512));
- return 1;
- }
- if (thisEUN)
- thisEUN = thisNFTL->ReplUnitTable[thisEUN] & 0x7fff;
- }
- if (lastgoodEUN == 0xffff) {
+ the_end:
+ if (lastgoodEUN == BLOCK_NIL) {
+ /* the requested block is not on the media, return all 0x00 */
memset(buffer, 0, 512);
} else {
- loff_t ptr = (lastgoodEUN * thisNFTL->EraseSize) + blockofs;
+ loff_t ptr = (lastgoodEUN * nftl->EraseSize) + blockofs;
size_t retlen;
u_char eccbuf[6];
- thisNFTL->mtd->read_ecc(thisNFTL->mtd, ptr, 512, &retlen, buffer, eccbuf);
+ if (MTD_READECC(nftl->mtd, ptr, 512, &retlen, buffer, eccbuf))
+ return -EIO;
}
return 0;
}
-
-static int nftl_ioctl(struct inode * inode, struct file * file,
- unsigned int cmd, unsigned long arg)
+static int nftl_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg)
{
- struct NFTLrecord *thisNFTL;
+ struct NFTLrecord *nftl;
- thisNFTL = NFTLs[MINOR(inode->i_rdev) / 16];
-
- if (!thisNFTL) return -EINVAL;
+ nftl = NFTLs[MINOR(inode->i_rdev) / 16];
+ if (!nftl) return -EINVAL;
switch (cmd) {
case HDIO_GETGEO: {
struct hd_geometry g;
- g.heads = thisNFTL->heads;
- g.sectors = thisNFTL->sectors;
- g.cylinders = thisNFTL->cylinders;
+ g.heads = nftl->heads;
+ g.sectors = nftl->sectors;
+ g.cylinders = nftl->cylinders;
g.start = part_table[MINOR(inode->i_rdev)].start_sect;
return copy_to_user((void *)arg, &g, sizeof g) ? -EFAULT : 0;
}
case BLKGETSIZE: /* Return device size */
- if (!arg) return -EINVAL;
+ if (!arg) return -EINVAL;
return put_user(part_table[MINOR(inode->i_rdev)].nr_sects,
(long *) arg);
case BLKFLSBUF:
- if(!capable(CAP_SYS_ADMIN)) return -EACCES;
+ if (!capable(CAP_SYS_ADMIN)) return -EACCES;
fsync_dev(inode->i_rdev);
invalidate_buffers(inode->i_rdev);
- if (thisNFTL->mtd->sync)
- thisNFTL->mtd->sync(thisNFTL->mtd);
+ if (nftl->mtd->sync)
+ nftl->mtd->sync(nftl->mtd);
return 0;
case BLKRRPART:
if (!capable(CAP_SYS_ADMIN)) return -EACCES;
- if (thisNFTL->usecount > 1) {
- // printk("Use count %d\n", thisNFTL->usecount);
- return -EBUSY;
- }
+ if (nftl->usecount > 1) return -EBUSY;
#if LINUX_VERSION_CODE < 0x20328
- resetup_one_dev(&nftl_gendisk, MINOR(inode->i_dev) / 16);
+ resetup_one_dev(&nftl_gendisk, MINOR(inode->i_rdev) / 16);
#else
- grok_partitions(&nftl_gendisk, MINOR(inode->i_dev) / 16, 1<<4, thisNFTL->nr_sects);
+ grok_partitions(&nftl_gendisk, MINOR(inode->i_rdev) / 16,
+ 1<<4, nftl->nr_sects);
#endif
return 0;
-
- // RO_IOCTLS(inode->i_rdev, arg); /* ref. linux/blk.h */
+
+#if (LINUX_VERSION_CODE < 0x20303)
+ RO_IOCTLS(inode->i_rdev, arg); /* ref. linux/blk.h */
+#else
+ case BLKROSET:
+ case BLKROGET:
+ case BLKSSZGET:
+ return blk_ioctl(inode->i_rdev, cmd, arg);
+#endif
+
default:
return -EINVAL;
}
}
-
void nftl_request(RQFUNC_ARG)
{
unsigned int dev, block, nsect;
- struct NFTLrecord *thisNFTL;
+ struct NFTLrecord *nftl;
char *buffer;
struct request *req;
int res;
while (1) {
INIT_REQUEST; /* blk.h */
-
req = CURRENT;
-#ifdef WE_KNOW_WTF_THIS_DOES_NOT_WORK
- blkdev_dequeue_request(req);
- spin_unlock_irq(&io_request_lock);
-#else
- req = CURRENT;
-#endif
- DEBUG(2,"NFTL_request\n");
- DEBUG(3,"NFTL %d request, %lx, %lx", req->cmd,
- req->sector, req->current_nr_sectors);
+ /* We can do this because the generic code knows not to
+ touch the request at the head of the queue */
+ spin_unlock_irq(&io_request_lock);
+
+ DEBUG(MTD_DEBUG_LEVEL2, "NFTL_request\n");
+ DEBUG(MTD_DEBUG_LEVEL3, "NFTL %s request, from sector 0x%04lx for 0x%04lx sectors\n",
+ (req->cmd == READ) ? "Read " : "Write",
+ req->sector, req->current_nr_sectors);
+
dev = MINOR(req->rq_dev);
block = req->sector;
nsect = req->current_nr_sectors;
@@ -1125,21 +887,23 @@
res = 1; /* succeed */
if (dev >= MAX_NFTLS * 16) {
- printk("fl: bad minor number: device=%s\n",
+ /* there is no such partition */
+ printk("nftl: bad minor number: device = %s\n",
kdevname(req->rq_dev));
res = 0; /* fail */
goto repeat;
}
- thisNFTL = NFTLs[dev / 16];
- DEBUG(3,"Waiting for mutex\n");
- down(&thisNFTL->mutex);
- DEBUG(3,"Got mutex\n");
-
- if (block + nsect >= part_table[dev].nr_sects) {
- printk("nftl%c%d: bad access: block=%d, count=%d\n",
+ nftl = NFTLs[dev / 16];
+ DEBUG(MTD_DEBUG_LEVEL3, "Waiting for mutex\n");
+ down(&nftl->mutex);
+ DEBUG(MTD_DEBUG_LEVEL3, "Got mutex\n");
+
+ if (block + nsect > part_table[dev].nr_sects) {
+ /* access past the end of device */
+ printk("nftl%c%d: bad access: block = %d, count = %d\n",
(MINOR(req->rq_dev)>>6)+'a', dev & 0xf, block, nsect);
- up(&thisNFTL->mutex);
+ up(&nftl->mutex);
res = 0; /* fail */
goto repeat;
}
@@ -1147,75 +911,80 @@
block += part_table[dev].start_sect;
if (req->cmd == READ) {
- DEBUG(2,"NFTL read\n");
- for ( ; nsect > 0; nsect-- , block++, buffer+= 512) {
+ DEBUG(MTD_DEBUG_LEVEL2, "NFTL read request of 0x%x sectors @ %x "
+ "(req->nr_sectors == %lx)\n", nsect, block, req->nr_sectors);
+
+ for ( ; nsect > 0; nsect-- , block++, buffer += 512) {
/* Read a single sector to req->buffer + (512 * i) */
-
- if (NFTL_readblock(thisNFTL, block, buffer)) {
- DEBUG(2,"NFTL read request failed\n");
- up(&thisNFTL->mutex);
+ if (NFTL_readblock(nftl, block, buffer)) {
+ DEBUG(MTD_DEBUG_LEVEL2, "NFTL read request failed\n");
+ up(&nftl->mutex);
res = 0;
goto repeat;
}
}
- DEBUG(2,"NFTL read request completed OK\n");
- up(&thisNFTL->mutex);
+
+ DEBUG(MTD_DEBUG_LEVEL2,"NFTL read request completed OK\n");
+ up(&nftl->mutex);
goto repeat;
- }
- else if (req->cmd == WRITE) {
- DEBUG(2,"NFTL write request of 0x%x sectors @ %x (req->nr_sectors == %lx\n",nsect, block, req->nr_sectors);
+ } else if (req->cmd == WRITE) {
+ DEBUG(MTD_DEBUG_LEVEL2, "NFTL write request of 0x%x sectors @ %x "
+ "(req->nr_sectors == %lx)\n", nsect, block,
+ req->nr_sectors);
#ifdef CONFIG_NFTL_RW
- for ( ; nsect > 0; nsect-- , block++, buffer+= 512) {
+ for ( ; nsect > 0; nsect-- , block++, buffer += 512) {
/* Read a single sector to req->buffer + (512 * i) */
-
- if (NFTL_writeblock(thisNFTL, block, buffer)) {
- DEBUG(1,"NFTL write request failed\n");
-
- up(&thisNFTL->mutex);
+ if (NFTL_writeblock(nftl, block, buffer)) {
+ DEBUG(MTD_DEBUG_LEVEL1,"NFTL write request failed\n");
+ up(&nftl->mutex);
res = 0;
goto repeat;
}
}
- DEBUG(2,"NFTL write request completed OK\n");
+ DEBUG(MTD_DEBUG_LEVEL2,"NFTL write request completed OK\n");
#else
- res=0; /* Writes always fail */
+ res = 0; /* Writes always fail */
#endif /* CONFIG_NFTL_RW */
- up(&thisNFTL->mutex);
+ up(&nftl->mutex);
goto repeat;
- }
- else {
- DEBUG(0,"NFTL ??? request\n");
- up(&thisNFTL->mutex);
+ } else {
+ DEBUG(MTD_DEBUG_LEVEL0, "NFTL unknown request\n");
+ up(&nftl->mutex);
res = 0;
goto repeat;
}
repeat:
- DEBUG(3,"end_request(%d)\n", res);
-#ifdef WE_KNOW_WTF_THIS_DOES_NOT_WORK
+ DEBUG(MTD_DEBUG_LEVEL3, "end_request(%d)\n", res);
spin_lock_irq(&io_request_lock);
- nftl_end_request(req, res);
-#else
end_request(res);
-#endif
}
}
static int nftl_open(struct inode *ip, struct file *fp)
{
+ int nftlnum = MINOR(ip->i_rdev) / 16;
struct NFTLrecord *thisNFTL;
- thisNFTL = NFTLs[MINOR(ip->i_rdev) / 16];
+ thisNFTL = NFTLs[nftlnum];
- DEBUG(2,"NFTL_open\n");
+ DEBUG(MTD_DEBUG_LEVEL2,"NFTL_open\n");
+#ifdef CONFIG_KMOD
+ if (!thisNFTL && nftlnum == 0) {
+ request_module("docprobe");
+ thisNFTL = NFTLs[nftlnum];
+ }
+#endif
if (!thisNFTL) {
- DEBUG(2,"ENODEV: thisNFTL = %d, minor = %d, ip = %p, fp = %p\n",
- MINOR(ip->i_rdev) / 16,ip->i_rdev,ip, fp);
+ DEBUG(MTD_DEBUG_LEVEL2,"ENODEV: thisNFTL = %d, minor = %d, ip = %p, fp = %p\n",
+ nftlnum, ip->i_rdev, ip, fp);
return -ENODEV;
}
+
#ifndef CONFIG_NFTL_RW
if (fp->f_mode & FMODE_WRITE)
- return -EROFS;
+ return -EROFS;
#endif /* !CONFIG_NFTL_RW */
+
thisNFTL->usecount++;
MOD_INC_USE_COUNT;
if (!get_mtd_device(thisNFTL->mtd, -1)) {
@@ -1233,8 +1002,8 @@
thisNFTL = NFTLs[MINOR(inode->i_rdev) / 16];
- DEBUG(2, "NFTL_release\n");
-
+ DEBUG(MTD_DEBUG_LEVEL2, "NFTL_release\n");
+
fsync_dev(inode->i_rdev);
if (sb)
invalidate_inodes(sb);
@@ -1251,19 +1020,19 @@
}
#if LINUX_VERSION_CODE < 0x20326
static struct file_operations nftl_fops = {
- read: block_read,
- write: block_write,
- ioctl: nftl_ioctl,
- open: nftl_open,
- release: nftl_release,
- fsync: block_fsync,
+ read: block_read,
+ write: block_write,
+ ioctl: nftl_ioctl,
+ open: nftl_open,
+ release: nftl_release,
+ fsync: block_fsync,
};
#else
static struct block_device_operations nftl_fops =
{
- open: nftl_open,
- release: nftl_release,
- ioctl: nftl_ioctl
+ open: nftl_open,
+ release: nftl_release,
+ ioctl: nftl_ioctl
};
#endif
@@ -1275,39 +1044,39 @@
*
****************************************************************************/
-#if LINUX_VERSION_CODE < 0x20300
-#ifdef MODULE
+#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE)
#define init_nftl init_module
#define cleanup_nftl cleanup_module
#endif
-#define __exit
-#endif
static struct mtd_notifier nftl_notifier = {NFTL_notify_add, NFTL_notify_remove, NULL};
-
/* static int __init init_nftl(void) */
int __init init_nftl(void)
{
int i;
- printk(KERN_NOTICE "M-Systems NAND Flash Translation Layer driver. (C) 1999 MVHI\n");
+ printk(KERN_NOTICE
+ "M-Systems NAND Flash Translation Layer driver. (C) 1999 MVHI\n");
#ifdef PRERELEASE
- printk(KERN_INFO"$Id: nftl.c,v 1.36 2000/07/13 14:14:20 dwmw2 Exp $\n");
+ printk(KERN_INFO"$Id: nftl.c,v 1.57 2000/12/01 17:51:54 dwmw2 Exp $\n");
#endif
- if (register_blkdev(NFTL_MAJOR, "nftl", &nftl_fops)){
- printk("unable to register NFTL block device\n");
+ if (register_blkdev(MAJOR_NR, "nftl", &nftl_fops)){
+ printk("unable to register NFTL block device on major %d\n", MAJOR_NR);
+ return -EBUSY;
} else {
#if LINUX_VERSION_CODE < 0x20320
- blk_dev[MAJOR_NR].request_fn = nftl_request;
+ blk_dev[MAJOR_NR].request_fn = nftl_request;
#else
- blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), &nftl_request);
+ blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), &nftl_request);
#endif
- for (i=0; i < 256 ; i++) {
+ /* set block size to 1kB each */
+ for (i = 0; i < 256; i++) {
nftl_blocksizes[i] = 1024;
}
- blksize_size[NFTL_MAJOR] = nftl_blocksizes;
+ blksize_size[MAJOR_NR] = nftl_blocksizes;
+
nftl_gendisk.next = gendisk_head;
gendisk_head = &nftl_gendisk;
}
@@ -1319,25 +1088,25 @@
static void __exit cleanup_nftl(void)
{
- struct gendisk *gd, **gdp;
+ struct gendisk *gd, **gdp;
- unregister_mtd_user(&nftl_notifier);
-
- unregister_blkdev(NFTL_MAJOR, "nftl");
+ unregister_mtd_user(&nftl_notifier);
+ unregister_blkdev(MAJOR_NR, "nftl");
+
#if LINUX_VERSION_CODE < 0x20320
- blk_dev[MAJOR_NR].request_fn = 0;
+ blk_dev[MAJOR_NR].request_fn = 0;
#else
- blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
+ blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
#endif
- for (gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next))
- if (*gdp == &nftl_gendisk) {
- gd = *gdp; *gdp = gd->next;
- break;
- }
-
+
+ /* remove ourself from generic harddisk list
+ FIXME: why can't I found this partition on /proc/partition */
+ for (gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next))
+ if (*gdp == &nftl_gendisk) {
+ gd = *gdp; *gdp = gd->next;
+ break;
+ }
}
-#if LINUX_VERSION_CODE > 0x20300
module_init(init_nftl);
module_exit(cleanup_nftl);
-#endif
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)