/*
This file is part of JOP, the Java Optimized Processor
see <http://www.jopdesign.com/>
Copyright (C) 2009, Martin Schoeberl (martin@jopdesign.com)
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 3 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, see <http://www.gnu.org/licenses/>.
*/
/**
*
*/
package util;
/**
* NAND interface with bad block mapping.
*
* About 6% (1/16) of the NAND is reserved mapping of bad blocks. The bad block
* map is stored in the last or last-1 block and kept in memory.<br>
*
* <p>Usage of spare bytes:<br>
* B0-B1: type<br>
* B2-B3: size for the last page<br>
* B4-B5: logical block number<br>
* B6-B7: bad block marker (!= 0xffff); in 1st and 2nd page<br>
* B8-B9: TBD<br>
* B10-B11: TBD<br>
* B12-B13: TBD<br>
* B14-B15: checksum<br>
*
* Idea: we could go down on BB table (mapBlock) till meeting the actual used
* blocks (badCount).
*
* @author Martin Schoeberl (martin@jopdesign.com)
*
*/
public class Nand extends NandLowLevel {
final static int TYPE_BBMAP = 1;
final static int TYPE_DATA = 2;
/**
* Mapping of bad blocks. Lower 16 bits is the logical block number, upper
* 16 bits are the block in the reserved block area if mapped.
*/
int remap[];
int[] localData = new int[WORDS];
int mapBlock;
public Nand() {
super();
int maxRemap = getNrOfBlocks() / 16 - 2;
if (maxRemap < 0) {
// we have not found a NAND Flash
return;
}
remap = new int[maxRemap];
for (int i = 0; i < remap.length; i++) {
remap[i] = -1;
}
mapBlock = getNrOfBlocks() - 1;
if (isBad(mapBlock)) {
--mapBlock;
if (isBad(mapBlock)) {
System.out.println("NAND Flash is dead!");
}
}
System.out.println("Read bad block table");
if (!readBBTable()) {
System.out.println("Initializing NAND Flash");
initMap();
}
System.out.print(badCount());
System.out.println(" bad blocks:");
for (int i = 0; i < badCount(); ++i) {
System.out.println("\t" + (remap[i] & 0xffff) + " -> "
+ (remap[i] >>> 16));
}
System.out.println("map block " + mapBlock);
}
public boolean isAvailable() {
return getNrOfBlocks() != 0;
}
/**
* Size in usable 16 KB blocks.
*
* @return
*/
public int size() {
return getNrOfBlocks() - remap.length - 2;
}
/**
* Erase one block.
*
* @param block
*/
public void erase(int block) {
block = getPhysicalBlock(block);
if (!eraseBlock(block)) {
addBadBlock(block);
markBad(block);
block = getPhysicalBlock(block);
eraseBlock(block);
}
}
/**
* Write one block marked as data type.
*
* @param data
* @param block
* @param page
* @return
*/
public boolean write(int[] data, int block, int page, int size) {
int origBlock = getPhysicalBlock(block);
fillSpare(localSpare, TYPE_DATA, origBlock, size);
block = getPhysicalBlock(block);
boolean ret = writePage(data, localSpare, block, page);
retry: while (!ret) {
// int markBlock = block;
// copy content
addBadBlock(block);
if (LOG) {
System.out.print("Copy write block ");
System.out.print(block);
}
block = getPhysicalBlock(block);
if (LOG) {
System.out.print(" to ");
System.out.print(block);
}
for (int i = 0; i < page; ++i) {
readPage(localData, origBlock, i);
fillSpare(localSpare, TYPE_DATA, origBlock);
if (!writePage(localData, localSpare, block, i)) {
markBad(block);
continue retry;
}
}
// write page
fillSpare(localSpare, TYPE_DATA, origBlock, size);
if (!writePage(data, localSpare, block, page)) {
continue retry;
}
// mark the former black as bad
// not now as dual bad block would destroy the
// original block
// markBad(markBlock);
ret = true;
}
return ret;
}
/**
* Read one page and return the size of the page.
*
* @param data
* @param block
* @param page
* @return
*/
public int read(int[] data, int block, int page) {
block = getPhysicalBlock(block);
boolean ok = readPage(data, localSpare, block, page);
return ok ? (localSpare[0] & 0xffff) : -1;
}
private void fillSpare(int[] sp, int type, int logicBlock) {
for (int i = 0; i < SPARE_WORDS; ++i) {
sp[i] = -1;
}
sp[0] = (type << 16) | 0xffff;
sp[1] = (logicBlock << 16) | 0xffff;
}
private void fillSpare(int[] sp, int type, int logicBlock, int size) {
for (int i = 0; i < SPARE_WORDS; ++i) {
sp[i] = -1;
}
sp[0] = (type << 16) | size;
sp[1] = (logicBlock << 16) | 0xffff;
}
int getPhysicalBlock(int block) {
for (int maxIndir = 0; maxIndir < 3; ++maxIndir) {
for (int i = 0; i < badCount(); ++i) {
if ((remap[i] & 0xffff) == block) {
// if (LOG) {
// System.out.print("remap ");
// System.out.print(block);
// }
block = remap[i] >> 16;
// if (LOG) {
// System.out.print(" -> ");
// System.out.println(block);
// }
}
}
}
return block;
}
/**
* Detect if BB table exists and read it in.
*
* @return
*/
private boolean readBBTable() {
readPage(null, localSpare, mapBlock, 0);
if ((localSpare[0] >>> 16) != TYPE_BBMAP) {
return false;
}
int page = 0;
for (int i = 0; i < remap.length; i++) {
if (i % (512 / 4) == 0) {
if (!readPage(localData, mapBlock, page)) {
return false;
}
++page;
}
remap[i] = localData[i % (512 / 4)];
}
return true;
}
private boolean isBad(int block) {
boolean bad = false;
for (int i = 0; i < 2; ++i) {
readPage(null, localSpare, block, i);
if ((localSpare[1] & 0xffff) != 0xffff) {
bad = true;
}
}
return bad;
}
private void markBad(int block) {
for (int i = 0; i < SPARE_WORDS; ++i) {
localSpare[i] = -1;
}
localSpare[1] &= 0xffff0000;
eraseBlock(block);
writePage(null, localSpare, block, 0);
writePage(null, localSpare, block, 1);
}
private void initMap() {
for (int i = 0; i < getNrOfBlocks() - 2; ++i) {
if (isBad(i)) {
addBadBlock(i);
}
}
updateMap();
}
private void addBadBlock(int block) {
if (LOG) {
System.out.print("Mark bad block ");
System.out.println(block);
}
int cnt = badCount();
int reservedStart = getNrOfBlocks() - remap.length - 2;
// Check whether cnt is not higher than the maximum number of
// spare blocks. Otherwise referencing remap[cnt] will result
// in an ArrayIndexOutOfBoundsException.
if (cnt >= getNrOfBlocks() / 16 - 2) {
System.out.println("Not enough spare blocks available.");
System.exit(1);
}
remap[cnt] = ((reservedStart + cnt) << 16) + block;
// if not yet marked in the spare do it.
updateMap();
}
private void updateMap() {
if (!updateMap(mapBlock)) {
markBad(mapBlock);
mapBlock--;
if (mapBlock < getNrOfBlocks() - 2 || isBad(mapBlock)) {
System.out.println("NAND Flash is dead!");
}
if (!updateMap(mapBlock)) {
markBad(mapBlock);
System.out.println("NAND Flash is dead!");
}
}
}
/**
* Write the mapping of bad blocks into one block.
*
* @param mapIdx
* Block to be used
* @return returns false if not possible (e.g., due to use a bad block for
* the mapping)
*/
private boolean updateMap(int mapIdx) {
if (!eraseBlock(mapIdx)) {
return false;
}
int cnt = 0;
int nrBad = badCount();
fillSpare(localSpare, TYPE_BBMAP, -1);
// write mapping table, but at least an empty one
for (int i = 0; i < PAGES_PER_BLOCK && cnt <= nrBad; ++i) {
for (int j = 0; j < WORDS; ++j, ++cnt) {
if (cnt < nrBad) {
localData[j] = remap[cnt];
} else {
localData[j] = -1;
}
}
// TODO: add ECC
if (!writePage(localData, localSpare, mapBlock, i)) {
return false;
}
}
return true;
}
private int badCount() {
int i;
for (i = 0; i < remap.length && remap[i] != -1; ++i) {
;
}
return i;
}
public static void main(String args[]) {
Nand nand = new Nand();
System.out.println(nand.readBBTable());
// nand.eraseAll();
}
}