423 lines
12 KiB
C
423 lines
12 KiB
C
/*
|
|
* nftl_format.c: Creating a NFTL/INFTL partition on an MTD device
|
|
*
|
|
* 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
|
|
*
|
|
* ToDo:
|
|
* 1. UnitSizeFactor != 0xFF cases
|
|
* 2. test, test, and test !!!
|
|
*/
|
|
|
|
#define PROGRAM_NAME "nftl_format"
|
|
|
|
#define _XOPEN_SOURCE 500 /* for pread/pwrite */
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mount.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
#include <asm/types.h>
|
|
#include <mtd/mtd-user.h>
|
|
#include <mtd/nftl-user.h>
|
|
#include <mtd/inftl-user.h>
|
|
#include <mtd_swab.h>
|
|
|
|
unsigned char BadUnitTable[MAX_ERASE_ZONES];
|
|
unsigned char *readbuf;
|
|
unsigned char *writebuf[4];
|
|
|
|
mtd_info_t meminfo;
|
|
erase_info_t erase;
|
|
int fd;
|
|
struct NFTLMediaHeader *NFTLhdr;
|
|
struct INFTLMediaHeader *INFTLhdr;
|
|
|
|
static int do_oobcheck = 1;
|
|
static int do_rwecheck = 1;
|
|
|
|
static unsigned char check_block_1(unsigned long block)
|
|
{
|
|
unsigned char oobbuf[16];
|
|
struct mtd_oob_buf oob = { 0, 16, oobbuf };
|
|
|
|
oob.start = block * meminfo.erasesize;
|
|
if (ioctl(fd, MEMREADOOB, &oob))
|
|
return ZONE_BAD_ORIGINAL;
|
|
|
|
if(oobbuf[5] == 0)
|
|
return ZONE_BAD_ORIGINAL;
|
|
|
|
oob.start = block * meminfo.erasesize + 512 /* FIXME */;
|
|
if (ioctl(fd, MEMREADOOB, &oob))
|
|
return ZONE_BAD_ORIGINAL;
|
|
|
|
if(oobbuf[5] == 0)
|
|
return ZONE_BAD_ORIGINAL;
|
|
|
|
return ZONE_GOOD;
|
|
}
|
|
|
|
static unsigned char check_block_2(unsigned long block)
|
|
{
|
|
unsigned long ofs = block * meminfo.erasesize;
|
|
unsigned long blockofs;
|
|
|
|
/* Erase test */
|
|
erase.start = ofs;
|
|
|
|
for (blockofs = 0; blockofs < meminfo.erasesize; blockofs += 512) {
|
|
pread(fd, readbuf, 512, ofs + blockofs);
|
|
if (memcmp(readbuf, writebuf[0], 512)) {
|
|
/* Block wasn't 0xff after erase */
|
|
printf(": Block not 0xff after erase\n");
|
|
return ZONE_BAD_ORIGINAL;
|
|
}
|
|
|
|
pwrite(fd, writebuf[1], 512, blockofs + ofs);
|
|
pread(fd, readbuf, 512, blockofs + ofs);
|
|
if (memcmp(readbuf, writebuf[1], 512)) {
|
|
printf(": Block not zero after clearing\n");
|
|
return ZONE_BAD_ORIGINAL;
|
|
}
|
|
}
|
|
|
|
/* Write test */
|
|
if (ioctl(fd, MEMERASE, &erase) != 0) {
|
|
printf(": Second erase failed (%s)\n", strerror(errno));
|
|
return ZONE_BAD_ORIGINAL;
|
|
}
|
|
for (blockofs = 0; blockofs < meminfo.erasesize; blockofs += 512) {
|
|
pwrite(fd, writebuf[2], 512, blockofs + ofs);
|
|
pread(fd, readbuf, 512, blockofs + ofs);
|
|
if (memcmp(readbuf, writebuf[2], 512)) {
|
|
printf(": Block not 0x5a after writing\n");
|
|
return ZONE_BAD_ORIGINAL;
|
|
}
|
|
}
|
|
|
|
if (ioctl(fd, MEMERASE, &erase) != 0) {
|
|
printf(": Third erase failed (%s)\n", strerror(errno));
|
|
return ZONE_BAD_ORIGINAL;
|
|
}
|
|
for (blockofs = 0; blockofs < meminfo.erasesize; blockofs += 512) {
|
|
pwrite(fd, writebuf[3], 512, blockofs + ofs);
|
|
pread(fd, readbuf, 512, blockofs + ofs);
|
|
if (memcmp(readbuf, writebuf[3], 512)) {
|
|
printf(": Block not 0xa5 after writing\n");
|
|
return ZONE_BAD_ORIGINAL;
|
|
}
|
|
}
|
|
if (ioctl(fd, MEMERASE, &erase) != 0) {
|
|
printf(": Fourth erase failed (%s)\n", strerror(errno));
|
|
return ZONE_BAD_ORIGINAL;
|
|
}
|
|
return ZONE_GOOD;
|
|
}
|
|
|
|
static unsigned char erase_block(unsigned long block)
|
|
{
|
|
unsigned char status;
|
|
int ret;
|
|
|
|
status = (do_oobcheck) ? check_block_1(block) : ZONE_GOOD;
|
|
erase.start = block * meminfo.erasesize;
|
|
|
|
if (status != ZONE_GOOD) {
|
|
printf("\rSkipping bad zone (factory marked) #%ld @ 0x%x\n", block, erase.start);
|
|
fflush(stdout);
|
|
return status;
|
|
}
|
|
|
|
printf("\r\t Erasing Zone #%ld @ 0x%x", block, erase.start);
|
|
fflush(stdout);
|
|
|
|
if ((ret=ioctl(fd, MEMERASE, &erase)) != 0) {
|
|
printf(": Erase failed (%s)\n", strerror(errno));
|
|
return ZONE_BAD_ORIGINAL;
|
|
}
|
|
|
|
if (do_rwecheck) {
|
|
printf("\r\tChecking Zone #%ld @ 0x%x", block, erase.start);
|
|
fflush(stdout);
|
|
status = check_block_2(block);
|
|
if (status != ZONE_GOOD) {
|
|
printf("\rSkipping bad zone (RWE test failed) #%ld @ 0x%x\n", block, erase.start);
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static int checkbbt(void)
|
|
{
|
|
unsigned char bbt[512];
|
|
unsigned char bits;
|
|
int i, addr;
|
|
|
|
if (pread(fd, bbt, 512, 0x800) < 0) {
|
|
printf("%s: failed to read BBT, errno=%d\n", PROGRAM_NAME, errno);
|
|
return (-1);
|
|
}
|
|
|
|
|
|
for (i = 0; (i < 512); i++) {
|
|
addr = i / 4;
|
|
bits = 0x3 << ((i % 4) * 2);
|
|
if ((bbt[addr] & bits) == 0) {
|
|
BadUnitTable[i] = ZONE_BAD_ORIGINAL;
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
void usage(int rc)
|
|
{
|
|
fprintf(stderr, "Usage: %s [-ib] <mtddevice> [<start offset> [<size>]]\n", PROGRAM_NAME);
|
|
exit(rc);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
unsigned long startofs = 0, part_size = 0;
|
|
unsigned long ezones = 0, ezone = 0, bad_zones = 0;
|
|
unsigned char unit_factor = 0xFF;
|
|
long MediaUnit1 = -1, MediaUnit2 = -1;
|
|
long MediaUnitOff1 = 0, MediaUnitOff2 = 0;
|
|
unsigned char oobbuf[16];
|
|
struct mtd_oob_buf oob = {0, 16, oobbuf};
|
|
char *mtddevice;
|
|
const char *nftl;
|
|
int c, do_inftl = 0, do_bbt = 0;
|
|
|
|
|
|
printf("version 1.24 2005/11/07 11:15:13 gleixner\n");
|
|
|
|
if (argc < 2)
|
|
usage(1);
|
|
|
|
nftl = "NFTL";
|
|
|
|
while ((c = getopt(argc, argv, "?hib")) > 0) {
|
|
switch (c) {
|
|
case 'i':
|
|
nftl = "INFTL";
|
|
do_inftl = 1;
|
|
break;
|
|
case 'b':
|
|
do_bbt = 1;
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
usage(0);
|
|
break;
|
|
default:
|
|
usage(1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
mtddevice = argv[optind++];
|
|
if (argc > optind) {
|
|
startofs = strtoul(argv[optind++], NULL, 0);
|
|
}
|
|
if (argc > optind) {
|
|
part_size = strtoul(argv[optind++], NULL, 0);
|
|
}
|
|
|
|
// Open and size the device
|
|
if ((fd = open(mtddevice, O_RDWR)) < 0) {
|
|
perror("Open flash device");
|
|
return 1;
|
|
}
|
|
|
|
if (ioctl(fd, MEMGETINFO, &meminfo) != 0) {
|
|
perror("ioctl(MEMGETINFO)");
|
|
close(fd);
|
|
return 1;
|
|
}
|
|
|
|
switch (meminfo.erasesize) {
|
|
case 0x1000:
|
|
case 0x2000:
|
|
case 0x4000:
|
|
case 0x8000:
|
|
break;
|
|
default:
|
|
printf("Unrecognized Erase size, 0x%x - I'm confused\n",
|
|
meminfo.erasesize);
|
|
close(fd);
|
|
return 1;
|
|
}
|
|
writebuf[0] = malloc(meminfo.erasesize * 5);
|
|
if (!writebuf[0]) {
|
|
printf("Malloc failed\n");
|
|
close(fd);
|
|
return 1;
|
|
}
|
|
writebuf[1] = writebuf[0] + meminfo.erasesize;
|
|
writebuf[2] = writebuf[1] + meminfo.erasesize;
|
|
writebuf[3] = writebuf[2] + meminfo.erasesize;
|
|
readbuf = writebuf[3] + meminfo.erasesize;
|
|
memset(writebuf[0], 0xff, meminfo.erasesize);
|
|
memset(writebuf[1], 0x00, meminfo.erasesize);
|
|
memset(writebuf[2], 0x5a, meminfo.erasesize);
|
|
memset(writebuf[3], 0xa5, meminfo.erasesize);
|
|
memset(BadUnitTable, ZONE_GOOD, MAX_ERASE_ZONES);
|
|
|
|
if (part_size == 0 || (part_size > meminfo.size - startofs))
|
|
/* the user doest not or incorrectly specify NFTL partition size */
|
|
part_size = meminfo.size - startofs;
|
|
|
|
erase.length = meminfo.erasesize;
|
|
ezones = part_size / meminfo.erasesize;
|
|
|
|
if (ezones > MAX_ERASE_ZONES) {
|
|
/* Ought to change the UnitSizeFactor. But later. */
|
|
part_size = meminfo.erasesize * MAX_ERASE_ZONES;
|
|
ezones = MAX_ERASE_ZONES;
|
|
unit_factor = 0xFF;
|
|
}
|
|
|
|
/* If using device BBT then parse that now */
|
|
if (do_bbt) {
|
|
checkbbt();
|
|
do_oobcheck = 0;
|
|
do_rwecheck = 0;
|
|
}
|
|
|
|
/* Phase 1. Erasing and checking each erase zones in the NFTL partition.
|
|
N.B. Erase Zones not used by the NFTL partition are untouched and marked ZONE_GOOD */
|
|
printf("Phase 1. Checking and erasing Erase Zones from 0x%08lx to 0x%08lx\n",
|
|
startofs, startofs + part_size);
|
|
for (ezone = startofs / meminfo.erasesize;
|
|
ezone < (ezones + startofs / meminfo.erasesize); ezone++) {
|
|
if (BadUnitTable[ezone] != ZONE_GOOD)
|
|
continue;
|
|
if ((BadUnitTable[ezone] = erase_block(ezone)) == ZONE_GOOD) {
|
|
if (MediaUnit1 == -1) {
|
|
MediaUnit1 = ezone;
|
|
} else if (MediaUnit2 == -1) {
|
|
MediaUnit2 = ezone;
|
|
}
|
|
} else {
|
|
bad_zones++;
|
|
}
|
|
}
|
|
printf("\n");
|
|
|
|
/* N.B. from dump of M-System original chips, NumEraseUnits counts the 2 Erase Unit used
|
|
by MediaHeader and the FirstPhysicalEUN starts from the MediaHeader */
|
|
if (do_inftl) {
|
|
unsigned long maxzones, pezstart, pezend, numvunits;
|
|
|
|
INFTLhdr = (struct INFTLMediaHeader *) (writebuf[0]);
|
|
strcpy(INFTLhdr->bootRecordID, "BNAND");
|
|
INFTLhdr->NoOfBootImageBlocks = cpu_to_le32(0);
|
|
INFTLhdr->NoOfBinaryPartitions = cpu_to_le32(0);
|
|
INFTLhdr->NoOfBDTLPartitions = cpu_to_le32(1);
|
|
INFTLhdr->BlockMultiplierBits = cpu_to_le32(0);
|
|
INFTLhdr->FormatFlags = cpu_to_le32(0);
|
|
INFTLhdr->OsakVersion = cpu_to_le32(OSAK_VERSION);
|
|
INFTLhdr->PercentUsed = cpu_to_le32(PERCENTUSED);
|
|
/*
|
|
* Calculate number of virtual units we will have to work
|
|
* with. I am calculating out the known bad units here, not
|
|
* sure if that is what M-Systems do...
|
|
*/
|
|
MediaUnit2 = MediaUnit1;
|
|
MediaUnitOff2 = 4096;
|
|
maxzones = meminfo.size / meminfo.erasesize;
|
|
pezstart = startofs / meminfo.erasesize + 1;
|
|
pezend = startofs / meminfo.erasesize + ezones - 1;
|
|
numvunits = (ezones - 2) * PERCENTUSED / 100;
|
|
for (ezone = pezstart; ezone < maxzones; ezone++) {
|
|
if (BadUnitTable[ezone] != ZONE_GOOD) {
|
|
if (numvunits > 1)
|
|
numvunits--;
|
|
}
|
|
}
|
|
|
|
INFTLhdr->Partitions[0].virtualUnits = cpu_to_le32(numvunits);
|
|
INFTLhdr->Partitions[0].firstUnit = cpu_to_le32(pezstart);
|
|
INFTLhdr->Partitions[0].lastUnit = cpu_to_le32(pezend);
|
|
INFTLhdr->Partitions[0].flags = cpu_to_le32(INFTL_BDTL);
|
|
INFTLhdr->Partitions[0].spareUnits = cpu_to_le32(0);
|
|
INFTLhdr->Partitions[0].Reserved0 = INFTLhdr->Partitions[0].firstUnit;
|
|
INFTLhdr->Partitions[0].Reserved1 = cpu_to_le32(0);
|
|
|
|
} else {
|
|
|
|
NFTLhdr = (struct NFTLMediaHeader *) (writebuf[0]);
|
|
strcpy(NFTLhdr->DataOrgID, "ANAND");
|
|
NFTLhdr->NumEraseUnits = cpu_to_le16(part_size / meminfo.erasesize);
|
|
NFTLhdr->FirstPhysicalEUN = cpu_to_le16(MediaUnit1);
|
|
/* N.B. we reserve 2 more Erase Units for "folding" of Virtual Unit Chain */
|
|
NFTLhdr->FormattedSize = cpu_to_le32(part_size - ( (5+bad_zones) * meminfo.erasesize));
|
|
NFTLhdr->UnitSizeFactor = unit_factor;
|
|
}
|
|
|
|
/* Phase 2. Writing NFTL Media Headers and Bad Unit Table */
|
|
printf("Phase 2.a Writing %s Media Header and Bad Unit Table\n", nftl);
|
|
pwrite(fd, writebuf[0], 512, MediaUnit1 * meminfo.erasesize + MediaUnitOff1);
|
|
for (ezone = 0; ezone < (meminfo.size / meminfo.erasesize); ezone += 512) {
|
|
pwrite(fd, BadUnitTable + ezone, 512,
|
|
(MediaUnit1 * meminfo.erasesize) + 512 * (1 + ezone / 512));
|
|
}
|
|
|
|
#if 0
|
|
printf(" MediaHeader contents:\n");
|
|
printf(" NumEraseUnits: %d\n", le16_to_cpu(NFTLhdr->NumEraseUnits));
|
|
printf(" FirstPhysicalEUN: %d\n", le16_to_cpu(NFTLhdr->FirstPhysicalEUN));
|
|
printf(" FormattedSize: %d (%d sectors)\n", le32_to_cpu(NFTLhdr->FormattedSize),
|
|
le32_to_cpu(NFTLhdr->FormattedSize)/512);
|
|
#endif
|
|
printf("Phase 2.b Writing Spare %s Media Header and Spare Bad Unit Table\n", nftl);
|
|
pwrite(fd, writebuf[0], 512, MediaUnit2 * meminfo.erasesize + MediaUnitOff2);
|
|
for (ezone = 0; ezone < (meminfo.size / meminfo.erasesize); ezone += 512) {
|
|
pwrite(fd, BadUnitTable + ezone, 512,
|
|
(MediaUnit2 * meminfo.erasesize + MediaUnitOff2) + 512 * (1 + ezone / 512));
|
|
}
|
|
|
|
/* UCI #1 for newly erased Erase Unit */
|
|
memset(oobbuf, 0xff, 16);
|
|
oobbuf[11] = oobbuf[10] = oobbuf[9] = 0;
|
|
oobbuf[8] = (do_inftl) ? 0x00 : 0x03;
|
|
oobbuf[12] = oobbuf[14] = 0x69;
|
|
oobbuf[13] = oobbuf[15] = 0x3c;
|
|
|
|
/* N.B. The Media Header and Bad Erase Unit Table are considered as Free Erase Unit
|
|
by M-System i.e. their Virtual Unit Number == 0xFFFF in the Unit Control Information #0,
|
|
but their Block Status is BLOCK_USED (0x5555) in their Block Control Information */
|
|
/* Phase 3. Writing Unit Control Information for each Erase Unit */
|
|
printf("Phase 3. Writing Unit Control Information to each Erase Unit\n");
|
|
for (ezone = MediaUnit1; ezone < (ezones + startofs / meminfo.erasesize); ezone++) {
|
|
/* write UCI #1 to each Erase Unit */
|
|
if (BadUnitTable[ezone] != ZONE_GOOD)
|
|
continue;
|
|
oob.start = (ezone * meminfo.erasesize) + 512 + (do_inftl * 512);
|
|
if (ioctl(fd, MEMWRITEOOB, &oob))
|
|
printf("MEMWRITEOOB at %lx: %s\n", (unsigned long)oob.start, strerror(errno));
|
|
}
|
|
|
|
exit(0);
|
|
}
|