/* 
 * nftl_format.c: Creating a NFTL partition on an MTD device
 *
 *
 * $Id: nftl_format.c,v 1.22 2004/05/05 15:19:57 dwmw2 Exp $
 *
 * 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 _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>

#define swab16(x) \
        ((__u16)( \
                (((__u16)(x) & (__u16)0x00ffU) << 8) | \
                (((__u16)(x) & (__u16)0xff00U) >> 8) ))
#define swab32(x) \
        ((__u32)( \
                (((__u32)(x) & (__u32)0x000000ffUL) << 24) | \
                (((__u32)(x) & (__u32)0x0000ff00UL) <<  8) | \
                (((__u32)(x) & (__u32)0x00ff0000UL) >>  8) | \
                (((__u32)(x) & (__u32)0xff000000UL) >> 24) ))

#if __BYTE_ORDER == __BIG_ENDIAN
#define cpu_to_le16(x) ({ __u16 _x = x; swab16(_x); })
#define cpu_to_le32(x) ({ __u32 _x = x; swab32(_x); })
#else
#define cpu_to_le16(x) (x)
#define cpu_to_le32(x) (x)
#endif
#define le32_to_cpu(x) cpu_to_le32(x)
#define le16_to_cpu(x) cpu_to_le16(x)

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;

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 = check_block_1(block);
	int ret;

	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;
}

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;
	unsigned char oobbuf[16];
	struct mtd_oob_buf oob = {0, 16, oobbuf};

	printf("$Id: nftl_format.c,v 1.22 2004/05/05 15:19:57 dwmw2 Exp $\n");

	if (argc < 2) {
		fprintf(stderr, "Usage: %s <mtddevice> [<start offset> [<size>]]\n",
			argv[0]);
		return 1;
	}

	if (argc > 2) {
		startofs = strtoul(argv[2], NULL, 0);
	}
	if (argc > 3) {
		part_size = strtoul(argv[3], NULL, 0);
	}

	// Open and size the device
	if ((fd = open(argv[1], 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:
		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;
	}

	/* 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] = 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 */
	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 NFTL Media Header and Bad Unit Table\n");
	pwrite(fd, writebuf[0], 512, MediaUnit1 * meminfo.erasesize);
	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 NFTL Media Header and Spare Bad Unit Table\n");
	pwrite(fd, writebuf[0], 512, MediaUnit2 * meminfo.erasesize);
	for (ezone = 0; ezone < (meminfo.size / meminfo.erasesize); ezone += 512) {
		pwrite(fd, BadUnitTable + ezone, 512,
		       (MediaUnit2 * meminfo.erasesize) + 512 * (1 + ezone / 512));
	}

	/* UCI #1 for newly erased Erase Unit */
	memset(oobbuf, 0xff, 16);
	oobbuf[11] = oobbuf[10] = oobbuf[9] = 0;
	oobbuf[8]  = 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 = cpu_to_le16(NFTLhdr->FirstPhysicalEUN);
	     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;
		if (ioctl(fd, MEMWRITEOOB, &oob))
			printf("MEMWRITEOOB at %lx: %s\n", (unsigned long)oob.start, strerror(errno));
	}

	exit(0);
}
