📄 inftlcore.c
字号:
/* * inftlcore.c -- Linux driver for Inverse Flash Translation Layer (INFTL) * * (C) Copyright 2002, Greg Ungerer (gerg@snapgear.com) * * Based heavily on the nftlcore.c code which is: * (c) 1999 Machine Vision Holdings, Inc. * Author: David Woodhouse <dwmw2@infradead.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */#include <linux/kernel.h>#include <linux/module.h>#include <linux/delay.h>#include <linux/slab.h>#include <linux/sched.h>#include <linux/init.h>#include <linux/kmod.h>#include <linux/hdreg.h>#include <linux/mtd/mtd.h>#include <linux/mtd/nftl.h>#include <linux/mtd/inftl.h>#include <linux/mtd/nand.h>#include <asm/uaccess.h>#include <asm/errno.h>#include <asm/io.h>/* * 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 10000static void inftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd){ struct INFTLrecord *inftl; unsigned long temp; if (mtd->type != MTD_NANDFLASH) return; /* OK, this is moderately ugly. But probably safe. Alternatives? */ if (memcmp(mtd->name, "DiskOnChip", 10)) return; if (!mtd->block_isbad) { printk(KERN_ERR"INFTL no longer supports the old DiskOnChip drivers loaded via docprobe.\n""Please use the new diskonchip driver under the NAND subsystem.\n"); return; } DEBUG(MTD_DEBUG_LEVEL3, "INFTL: add_mtd for %s\n", mtd->name); inftl = kzalloc(sizeof(*inftl), GFP_KERNEL); if (!inftl) { printk(KERN_WARNING "INFTL: Out of memory for data structures\n"); return; } inftl->mbd.mtd = mtd; inftl->mbd.devnum = -1; inftl->mbd.tr = tr; if (INFTL_mount(inftl) < 0) { printk(KERN_WARNING "INFTL: could not mount device\n"); kfree(inftl); return; } /* OK, it's a new one. Set up all the data structures. */ /* Calculate geometry */ inftl->cylinders = 1024; inftl->heads = 16; temp = inftl->cylinders * inftl->heads; inftl->sectors = inftl->mbd.size / temp; if (inftl->mbd.size % temp) { inftl->sectors++; temp = inftl->cylinders * inftl->sectors; inftl->heads = inftl->mbd.size / temp; if (inftl->mbd.size % temp) { inftl->heads++; temp = inftl->heads * inftl->sectors; inftl->cylinders = inftl->mbd.size / temp; } } if (inftl->mbd.size != inftl->heads * inftl->cylinders * inftl->sectors) { /* Oh no we don't have mbd.size == heads * cylinders * sectors */ printk(KERN_WARNING "INFTL: cannot calculate a geometry to " "match size of 0x%lx.\n", inftl->mbd.size); printk(KERN_WARNING "INFTL: using C:%d H:%d S:%d " "(== 0x%lx sects)\n", inftl->cylinders, inftl->heads , inftl->sectors, (long)inftl->cylinders * (long)inftl->heads * (long)inftl->sectors ); } if (add_mtd_blktrans_dev(&inftl->mbd)) { kfree(inftl->PUtable); kfree(inftl->VUtable); kfree(inftl); return; }#ifdef PSYCHO_DEBUG printk(KERN_INFO "INFTL: Found new inftl%c\n", inftl->mbd.devnum + 'a');#endif return;}static void inftl_remove_dev(struct mtd_blktrans_dev *dev){ struct INFTLrecord *inftl = (void *)dev; DEBUG(MTD_DEBUG_LEVEL3, "INFTL: remove_dev (i=%d)\n", dev->devnum); del_mtd_blktrans_dev(dev); kfree(inftl->PUtable); kfree(inftl->VUtable); kfree(inftl);}/* * Actual INFTL access routines. *//* * Read oob data from flash */int inftl_read_oob(struct mtd_info *mtd, loff_t offs, size_t len, size_t *retlen, uint8_t *buf){ struct mtd_oob_ops ops; int res; ops.mode = MTD_OOB_PLACE; ops.ooboffs = offs & (mtd->writesize - 1); ops.ooblen = len; ops.oobbuf = buf; ops.datbuf = NULL; res = mtd->read_oob(mtd, offs & ~(mtd->writesize - 1), &ops); *retlen = ops.oobretlen; return res;}/* * Write oob data to flash */int inftl_write_oob(struct mtd_info *mtd, loff_t offs, size_t len, size_t *retlen, uint8_t *buf){ struct mtd_oob_ops ops; int res; ops.mode = MTD_OOB_PLACE; ops.ooboffs = offs & (mtd->writesize - 1); ops.ooblen = len; ops.oobbuf = buf; ops.datbuf = NULL; res = mtd->write_oob(mtd, offs & ~(mtd->writesize - 1), &ops); *retlen = ops.oobretlen; return res;}/* * Write data and oob to flash */static int inftl_write(struct mtd_info *mtd, loff_t offs, size_t len, size_t *retlen, uint8_t *buf, uint8_t *oob){ struct mtd_oob_ops ops; int res; ops.mode = MTD_OOB_PLACE; ops.ooboffs = offs; ops.ooblen = mtd->oobsize; ops.oobbuf = oob; ops.datbuf = buf; ops.len = len; res = mtd->write_oob(mtd, offs & ~(mtd->writesize - 1), &ops); *retlen = ops.retlen; return res;}/* * INFTL_findfreeblock: Find a free Erase Unit on the INFTL partition. * This function is used when the give Virtual Unit Chain. */static u16 INFTL_findfreeblock(struct INFTLrecord *inftl, int desperate){ u16 pot = inftl->LastFreeEUN; int silly = inftl->nb_blocks; DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_findfreeblock(inftl=%p," "desperate=%d)\n", inftl, desperate); /* * Normally, we force a fold to happen before we run out of free * blocks completely. */ if (!desperate && inftl->numfreeEUNs < 2) { DEBUG(MTD_DEBUG_LEVEL1, "INFTL: there are too few free " "EUNs (%d)\n", inftl->numfreeEUNs); return 0xffff; } /* Scan for a free block */ do { if (inftl->PUtable[pot] == BLOCK_FREE) { inftl->LastFreeEUN = pot; return pot; } if (++pot > inftl->lastEUN) pot = 0; if (!silly--) { printk(KERN_WARNING "INFTL: no free blocks found! " "EUN range = %d - %d\n", 0, inftl->LastFreeEUN); return BLOCK_NIL; } } while (pot != inftl->LastFreeEUN); return BLOCK_NIL;}static u16 INFTL_foldchain(struct INFTLrecord *inftl, unsigned thisVUC, unsigned pendingblock){ u16 BlockMap[MAX_SECTORS_PER_UNIT]; unsigned char BlockDeleted[MAX_SECTORS_PER_UNIT]; unsigned int thisEUN, prevEUN, status; struct mtd_info *mtd = inftl->mbd.mtd; int block, silly; unsigned int targetEUN; struct inftl_oob oob; size_t retlen; DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_foldchain(inftl=%p,thisVUC=%d," "pending=%d)\n", inftl, thisVUC, pendingblock); memset(BlockMap, 0xff, sizeof(BlockMap)); memset(BlockDeleted, 0, sizeof(BlockDeleted)); thisEUN = targetEUN = inftl->VUtable[thisVUC]; if (thisEUN == BLOCK_NIL) { printk(KERN_WARNING "INFTL: 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; while (thisEUN < inftl->nb_blocks) { for (block = 0; block < inftl->EraseSize/SECTORSIZE; block ++) { if ((BlockMap[block] != 0xffff) || BlockDeleted[block]) continue; if (inftl_read_oob(mtd, (thisEUN * inftl->EraseSize) + (block * SECTORSIZE), 16, &retlen, (char *)&oob) < 0) status = SECTOR_IGNORE; else status = oob.b.Status | oob.b.Status1; switch(status) { case SECTOR_FREE: case SECTOR_IGNORE: break; case SECTOR_USED: BlockMap[block] = thisEUN; continue; case SECTOR_DELETED: BlockDeleted[block] = 1; continue; default: printk(KERN_WARNING "INFTL: unknown status " "for block %d in EUN %d: %x\n", block, thisEUN, status); break; } } if (!silly--) { printk(KERN_WARNING "INFTL: infinite loop in Virtual " "Unit Chain 0x%x\n", thisVUC); return BLOCK_NIL; } thisEUN = inftl->PUtable[thisEUN]; } /* * 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(MTD_DEBUG_LEVEL1, "INFTL: folding chain %d into unit %d\n", thisVUC, targetEUN); for (block = 0; block < inftl->EraseSize/SECTORSIZE ; block++) { unsigned char movebuf[SECTORSIZE]; int ret; /* * If it's in the target EUN already, or if it's pending write, * do nothing. */ if (BlockMap[block] == targetEUN || (pendingblock == (thisVUC * (inftl->EraseSize / SECTORSIZE) + block))) { continue; } /* * 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->read(mtd, (inftl->EraseSize * BlockMap[block]) + (block * SECTORSIZE), SECTORSIZE, &retlen, movebuf); if (ret < 0 && ret != -EUCLEAN) { ret = mtd->read(mtd, (inftl->EraseSize * BlockMap[block]) + (block * SECTORSIZE), SECTORSIZE, &retlen, movebuf); if (ret != -EIO) DEBUG(MTD_DEBUG_LEVEL1, "INFTL: error went " "away on retry?\n"); } memset(&oob, 0xff, sizeof(struct inftl_oob)); oob.b.Status = oob.b.Status1 = SECTOR_USED; inftl_write(inftl->mbd.mtd, (inftl->EraseSize * targetEUN) + (block * SECTORSIZE), SECTORSIZE, &retlen, movebuf, (char *)&oob); } /* * Newest unit in chain now contains data from _all_ older units. * So go through and erase each unit in chain, oldest first. (This * is important, by doing oldest first if we crash/reboot then it * it is relatively simple to clean up the mess). */ DEBUG(MTD_DEBUG_LEVEL1, "INFTL: want to erase virtual chain %d\n", thisVUC); for (;;) { /* Find oldest unit in chain. */ thisEUN = inftl->VUtable[thisVUC]; prevEUN = BLOCK_NIL; while (inftl->PUtable[thisEUN] != BLOCK_NIL) { prevEUN = thisEUN; thisEUN = inftl->PUtable[thisEUN]; } /* Check if we are all done */ if (thisEUN == targetEUN) break; /* Unlink the last block from the chain. */ inftl->PUtable[prevEUN] = BLOCK_NIL; /* Now try to erase it. */ if (INFTL_formatblock(inftl, thisEUN) < 0) { /* * Could not erase : mark block as reserved. */ inftl->PUtable[thisEUN] = BLOCK_RESERVED; } else { /* Correctly erased : mark it as free */ inftl->PUtable[thisEUN] = BLOCK_FREE; inftl->numfreeEUNs++; } } return targetEUN;}static u16 INFTL_makefreeblock(struct INFTLrecord *inftl, unsigned pendingblock){ /* * This is the part that needs some cleverness applied. * For now, I'm doing the minimum applicable to actually * get the thing to work. * Wear-levelling and other clever stuff needs to be implemented * 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; DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_makefreeblock(inftl=%p," "pending=%d)\n", inftl, pendingblock); for (chain = 0; chain < inftl->nb_blocks; chain++) { EUN = inftl->VUtable[chain]; thislen = 0; while (EUN <= inftl->lastEUN) { thislen++; EUN = inftl->PUtable[EUN]; if (thislen > 0xff00) { printk(KERN_WARNING "INFTL: endless loop in " "Virtual Chain %d: Unit %x\n", chain, EUN); /* * Actually, don't return failure. * Just ignore this chain and get on with it. */ thislen = 0; break; } } if (thislen > ChainLength) { ChainLength = thislen; LongestChain = chain; } } if (ChainLength < 2) { printk(KERN_WARNING "INFTL: no Virtual Unit Chains available " "for folding. Failing request\n"); return BLOCK_NIL; } return INFTL_foldchain(inftl, LongestChain, pendingblock);}static int nrbits(unsigned int val, int bitcount){ int i, total = 0; for (i = 0; (i < bitcount); i++) total += (((0x1 << i) & val) ? 1 : 0); return total;}/* * INFTL_findwriteunit: Return the unit number into which we can write * for this block. Make it available if it isn't already. */static inline u16 INFTL_findwriteunit(struct INFTLrecord *inftl, unsigned block){ unsigned int thisVUC = block / (inftl->EraseSize / SECTORSIZE); unsigned int thisEUN, writeEUN, prev_block, status; unsigned long blockofs = (block * SECTORSIZE) & (inftl->EraseSize -1); struct mtd_info *mtd = inftl->mbd.mtd; struct inftl_oob oob; struct inftl_bci bci; unsigned char anac, nacs, parity; size_t retlen; int silly, silly2 = 3;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -