/*  Copyright 2009-2011, Unpublished Work of Technologic Systems
 *  All Rights Reserved.
 *
 *  THIS WORK IS AN UNPUBLISHED WORK AND CONTAINS CONFIDENTIAL,
 *  PROPRIETARY AND TRADE SECRET INFORMATION OF TECHNOLOGIC SYSTEMS.
 *  ACCESS TO THIS WORK IS RESTRICTED TO (I) TECHNOLOGIC SYSTEMS EMPLOYEES
 *  WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE OF THEIR
 *  ASSIGNMENTS  AND (II) ENTITIES OTHER THAN TECHNOLOGIC SYSTEMS WHO
 *  HAVE ENTERED INTO  APPROPRIATE LICENSE AGREEMENTS.  NO PART OF THIS
 *  WORK MAY BE USED, PRACTICED, PERFORMED, COPIED, DISTRIBUTED, REVISED,
 *  MODIFIED, TRANSLATED, ABRIDGED, CONDENSED, EXPANDED, COLLECTED,
 *  COMPILED,LINKED,RECAST, TRANSFORMED, ADAPTED IN ANY FORM OR BY ANY
 *  MEANS,MANUAL, MECHANICAL, CHEMICAL, ELECTRICAL, ELECTRONIC, OPTICAL,
 *  BIOLOGICAL, OR OTHERWISE WITHOUT THE PRIOR WRITTEN PERMISSION AND
 *  CONSENT OF TECHNOLOGIC SYSTEMS . ANY USE OR EXPLOITATION OF THIS WORK
 *  WITHOUT THE PRIOR WRITTEN CONSENT OF TECHNOLOGIC SYSTEMS  COULD
 *  SUBJECT THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
 */

/* To compile nandctl, use the appropriate cross compiler and run the
 * command:
 *
 *   gcc -Wall -O -mcpu=arm9 -o nandctl nandctl.c
 *
 * nandcore.c should be in the same directory as nandctl.c
 *
 * On uclibc based initrd's, the following additional gcc options are
 * necessary: -Wl,--rpath,/slib -Wl,-dynamic-linker,/slib/ld-uClibc.so.0
 */

const char copyright[] = "Copyright (c) Technologic Systems - " __DATE__ ;

#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <linux/unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>

#include "nandcore.h"


#define ntohll(x)  bswap_64(x)
#define htonll(x)  bswap_64(x)

#define CNS21XX_SBUS_A5       (_BIT(15))
#define CNS21XX_SBUS_A6       (_BIT(16))
#define CNS21XX_BLS1          (_BIT(17))
#define CNS21XX_BLS0          (_BIT(3))

#define CNS3XXX_SBUS_A5       (_BIT(0))
#define CNS3XXX_SBUS_A6       (_BIT(1))
#define CNS3XXX_BLS1          (_BIT(12))
#define CNS3XXX_BLS0          (_BIT(11))


#ifndef TEMP_FAILURE_RETRY
# define TEMP_FAILURE_RETRY(expression) \
  (__extension__                                                              \
    ({ long int __result;                                                     \
       do __result = (long int) (expression);                                 \
       while (__result == -1L && errno == EINTR);                             \
       __result; }))
#endif


static volatile unsigned int *cvspiregs, *cvgpioregs;
static int last_gpio_adr = 0;
static int size;
static int nbdbufsz;
static unsigned char *nbdbuf;
static unsigned char *csumtbl;
static int opt_foreground = 0;

struct nbd_request {
	unsigned int magic;
	char type[4];
	char handle[8];
	unsigned int fromhi;
	unsigned int fromlo;
	unsigned int len;
};

struct nbd_reply {
	char magic[4];
	unsigned int error;
	char handle[8];
};


static unsigned int is_cavium(void) __attribute__ ((pure));
static unsigned int is_cavium(void)  {
	FILE *cpuinfo;
	unsigned char buf[4096];
	static unsigned int checked = 0;
	static unsigned int iscavium = 0;

	if (!checked) {
		cpuinfo = fopen("/proc/cpuinfo", "r");
		if (cpuinfo == NULL) {
			perror("/proc/cpuinfo");
			exit(4);
		}
		bzero(buf, 4096);
		fread(buf, 1, 4095, cpuinfo);
		fclose(cpuinfo);
		if (strstr((char*)buf, "FA526")) iscavium = 1;
		checked = 1;
	}
	return iscavium;
}


static unsigned int is_arm11mpcore(void) __attribute__ ((pure));
static unsigned int is_arm11mpcore(void)  {
	FILE *cpuinfo;
	char line[80];
	static unsigned int checked = 0;
	static unsigned int isarm11mpcore = 0;

	if (!checked) {
		cpuinfo = fopen("/proc/cpuinfo", "r");
		if (cpuinfo == NULL) {
			perror("/proc/cpuinfo");
			exit(4);
		}

      while(!feof(cpuinfo)) {
         char *cp;
         fgets(line, sizeof(line), cpuinfo);

         cp = line;
         while (*cp) {
            *cp = tolower(*cp);
            cp++;
         }

         if (strstr(line, "cpu part") && strstr(line, "0xb02")) {
            isarm11mpcore = 1;
            break;
         }
      }
		fclose(cpuinfo);
		checked = 1;
	}

	return isarm11mpcore;
}


/*
   pxa168 is the Marvell 88AP168 (Armada) SoC.
*/
static unsigned int is_pxa168(void) __attribute__ ((pure));
static unsigned int is_pxa168(void)  {

	static unsigned int checked = 0;
	static unsigned int ispxa168 = 0;

	if (!checked) {
      FILE *cpuinfo;
      unsigned char buf[80];
		cpuinfo = fopen("/proc/cpuinfo", "r");
		if (cpuinfo == NULL) {
			perror("/proc/cpuinfo");
			exit(4);
		}

		while (fgets((char*)buf, sizeof(buf), cpuinfo)) {
         if (strstr((char*)buf, "Marvell Mohawk")) {
            ispxa168 = 1;
               break;
         }
      }
      checked = 1;
		fclose(cpuinfo);
	}
	return ispxa168;
}


/*
   mx51 is the Freescale MX51 SoC.
*/
static unsigned int is_mx51(void) __attribute__ ((pure));
static unsigned int is_mx51(void)  {

    static unsigned int checked = 0;
    static unsigned int ismx51 = 0;

    if (!checked) {
      FILE *cpuinfo;
      char buf[80];
        cpuinfo = fopen("/proc/cpuinfo", "r");
        if (cpuinfo == NULL) {
            perror("/proc/cpuinfo");
            exit(4);
        }

        while (fgets((char*)buf, sizeof(buf), cpuinfo)) {
         if (strstr(buf, "TS-48XX") || strstr((char*)buf, "MX51")) {
            ismx51 = 1;
               break;
         }
      }
      checked = 1;
        fclose(cpuinfo);
    }
    return ismx51;
}


void poke8(unsigned int adr, unsigned char dat) {

   //printf("poke8 0x%08X 0x%02X\n\t", adr, dat);

	if (!cvspiregs)
      *(unsigned char *)adr = dat;
	else {

      if (is_arm11mpcore()) {
         if (adr & 0x1) {
            cvgpioregs[0] = CNS3XXX_BLS1;
            poke16(adr, dat << 8);
         } else {
            cvgpioregs[0] = CNS3XXX_BLS0;
            poke16(adr, dat);
         }
         cvgpioregs[0x10/4] = (CNS3XXX_BLS1 | CNS3XXX_BLS0);   // Set bits
      } else {

         if (adr & 0x1) {
            cvgpioregs[0] = CNS21XX_BLS1;
            poke16(adr, dat << 8);
         } else {
            cvgpioregs[0] = CNS21XX_BLS0;
            poke16(adr, dat);
         }
         cvgpioregs[0x10/4] = (CNS21XX_BLS1 | CNS21XX_BLS0);   // Set bits
      }
	}
}


void poke16(unsigned int adr, unsigned short dat) {

	unsigned int dummy, d;

   //printf("poke16 0x%08X 0x%02X   ", adr, dat);

	if (!cvspiregs) {
		*(unsigned short *)adr = dat;
		return;
	}
	d = dat;
	if ((unsigned int)last_gpio_adr != adr >> 5) {
		last_gpio_adr = adr >> 5;

      if (is_arm11mpcore()) {
         cvgpioregs[0x14/4] = CNS3XXX_SBUS_A5 | CNS3XXX_SBUS_A6;  // Clear bits
         cvgpioregs[0x10/4] = (adr>>5) & 0x1F;                    // Set bits
      } else {

         cvgpioregs[0] = (cvgpioregs[0] & ~(0x3<<15))|((adr>>5)<<15);
      }
	}

	adr &= 0x1f;
   dummy = 0;     // to get rid of compiler warning

   /*
   printf("pins: 0x%08lX BL1=%d BL0=%d addr=%d%d-%d%d%d%d%d\n",
      cvgpioregs[0],
      cvgpioregs[0] & CNS3XXX_BLS1?1:0,
      cvgpioregs[0] & CNS3XXX_BLS0?1:0,
      cvgpioregs[0] & CNS3XXX_SBUS_A6?1:0,
      cvgpioregs[0] & CNS3XXX_SBUS_A5?1:0,
      adr & _BIT(4)?1:0,
      adr & _BIT(3)?1:0,
      adr & _BIT(2)?1:0,
      adr & _BIT(1)?1:0,
      adr & _BIT(0)?1:0
      );
   */

	asm volatile (
		"mov %0, %1, lsl #18\n"
		"orr %0, %0, #0x800000\n"
		"orr %0, %0, %2, lsl #3\n"
		"3: ldr r1, [%3, #0x64]\n"
		"cmp r1, #0x0\n"
		"bne 3b\n"
		"2: str %0, [%3, #0x50]\n"
		"1: ldr r1, [%3, #0x64]\n"
		"cmp r1, #0x0\n"
		"beq 1b\n"
		"ldr %0, [%3, #0x58]\n"
		"ands r1, %0, #0x1\n"
		"moveq %0, #0x0\n"
		"beq 3b\n"
		: "+r"(dummy) : "r"(adr), "r"(d), "r"(cvspiregs) : "r1","cc"
	);

}


unsigned short peek16(unsigned int adr) {
	unsigned short ret;

//   printf("peek16 0x%08X  ", adr);

	if (!cvspiregs) {
		ret = *(unsigned short *)adr;
		return ret;
	}

	if ((unsigned int)last_gpio_adr != adr >> 5) {
		last_gpio_adr = adr >> 5;

      if (is_arm11mpcore()) {
         cvgpioregs[0x14/4] = (CNS3XXX_SBUS_A5 | CNS3XXX_SBUS_A6);                  // Clear bits
         cvgpioregs[0x10/4] = (((adr>>5)&0x1F) | CNS3XXX_BLS0 | CNS3XXX_BLS1);         // Set bits
      } else {
         cvgpioregs[0] = ((adr>>5)<<15|1<<3|1<<17);
      }
	}

	adr &= 0x1f;
   ret = 0;
   /*
   printf("pins: 0x%08lX BL1=%d BL0=%d addr=%d%d-%d%d%d%d%d\n",
      cvgpioregs[0],
      cvgpioregs[0] & CNS3XXX_BLS1?1:0,
      cvgpioregs[0] & CNS3XXX_BLS0?1:0,
      cvgpioregs[0] & CNS3XXX_SBUS_A6?1:0,
      cvgpioregs[0] & CNS3XXX_SBUS_A5?1:0,
      adr & _BIT(4)?1:0,
      adr & _BIT(3)?1:0,
      adr & _BIT(2)?1:0,
      adr & _BIT(1)?1:0,
      adr & _BIT(0)?1:0
      );
   */

   if (is_arm11mpcore()) {

	asm volatile (
		"mov %0, %1, lsl #18\n"
		"2: str %0, [%2, #0x50]\n"
		"1: ldr r1, [%2, #0x64]\n"
		"cmp r1, #0x0\n"
		"beq 1b\n"
		"ldr %0, [%2, #0x58]\n"

      "mov %0, %0, lsr #1\n"     // Why do we have to ROR the read data?

		"ands r1, %0, #0x10000\n"
		"bicne %0, %0, #0xff0000\n"
		"moveq %0, #0x0\n"
		"beq 2b\n"
		: "+r"(ret) : "r"(adr), "r"(cvspiregs) : "r1", "cc"
	);

   } else {

      asm volatile (
		"mov %0, %1, lsl #18\n"
		"2: str %0, [%2, #0x50]\n"
		"1: ldr r1, [%2, #0x64]\n"
		"cmp r1, #0x0\n"
		"beq 1b\n"
		"ldr %0, [%2, #0x58]\n"
		"ands r1, %0, #0x10000\n"
		"bicne %0, %0, #0xff0000\n"
		"moveq %0, #0x0\n"
		"beq 2b\n"
		: "+r"(ret) : "r"(adr), "r"(cvspiregs) : "r1", "cc"
	);
   }


	return ret;

}


inline void
poke16_stream(unsigned int adr, unsigned short *dat, unsigned int len) {
	volatile unsigned int *spi = cvspiregs;
	unsigned int cmd, ret, i, j;

//   printf("poke16_stream 0x%08X 0x%02X\n", adr, dat);

	if (!spi) {
		while (len--) *(unsigned short *)adr = *dat++;
		return;
	}

	spi[0x4c/4] = 0x0;	/* continuous CS# */
	cmd = (adr<<18) | 0x800000 | (dat[0]<<3) | (dat[1]>>13);
	do {
		spi[0x50/4] = cmd;
		cmd = dat[1]>>13;
		while (spi[0x64/4] == 0);
		ret = spi[0x58/4];
		assert (spi[0x64/4] == 0); /* */
	} while (!(ret & 0x1));

	spi[0x40/4] = 0x80000c01; /* 16 bit mode */
	i = len - 1;
	len -= 6;
	dat++;

	for (j = 0; j < 4; j++) {
		spi[0x50/4] = (dat[0]<<3) | (dat[1]>>13);
		dat++;
	}

	while (len--) {
		spi[0x50/4] = (dat[0]<<3) | (dat[1]>>13);
		dat++;
		while (spi[0x64/4] == 0);
		spi[0x58/4];
		i--;
	}

	spi[0x4c/4] = 0x4;	/* deassert CS# */
	spi[0x50/4] = dat[0]<<3;

	while (i) {
		while ((spi[0x64/4]) == 0);
		spi[0x58/4];
		i--;
	}

	spi[0x40/4] = 0x80000c02; /* 24 bit mode */

}

#if 0
// Who's using this...?
static void poke32(unsigned int adr, unsigned int dat) {
	if (!cvspiregs) *(unsigned int *)adr = dat;
	else {
		poke16(adr, dat & 0xffff);
		poke16(adr + 2, dat >> 16);
	}
}


static unsigned char peek8(unsigned int adr) {
	unsigned char ret;
	if (!cvspiregs) ret = *(unsigned char *)adr;
	else {
		unsigned short x;
		x = peek16(adr);
		if (adr & 0x1) ret = x >> 8;
		else ret = x & 0xff;
	}
	return ret;
}
#endif


#if 0
// Who's using this...?
static unsigned int peek32(unsigned int adr) {
	unsigned int ret;
	if (!cvspiregs) ret = *(unsigned int *)adr;
	else {
		unsigned short l, h;
		l = peek16(adr);
		h = peek16(adr + 2);
		ret = (l|(h<<16));
	}
	return ret;
}
#endif


static inline
void peek16_stream(unsigned int adr, unsigned short *dat, unsigned int len) {
	unsigned int dummy = 0;

   printf("peek16_stream 0x%08X\n", adr);

	if (!cvspiregs) while (len--) *dat++ = *(unsigned short *)adr;
	else asm volatile(
		"mov %0, #0x0\n"
		"str %0, [%4, #0x4c]\n"
		"mov %1, %1, lsl #18\n"
		"orr %1, %1, #(1<<15)\n"
		"2: str %1, [%4, #0x50]\n"
		"1: ldr %0, [%4, #0x64]\n"
		"cmp %0, #0x0\n"
		"beq 1b\n"
		"ldr %0, [%4, #0x58]\n"
		"tst %0, #0x10000\n"
		"beq 2b\n"
		"\n"
		"3:\n"
		"strh %0, [%3], #0x2\n"
		"mov %0, #0x80000001\n"
		"orr %0, %0, #0xc00\n"
		"str %0, [%4, #0x40]\n"
		"ldr %0, [%4, #0x40]\n"
		"str %1, [%4, #0x50]\n"
		"str %1, [%4, #0x50]\n"
		"sub %2, %2, #0x4\n"
		"4: str %1, [%4, #0x50]\n"
		"5: ldr %0, [%4, #0x64]\n"
		"cmp %0, #0x0\n"
		"beq 5b\n"
		"ldr %0, [%4, #0x58]\n"
		"subs %2, %2, #0x1\n"
		"strh %0, [%3], #0x2\n"
		"bne 4b\n"
		"\n"
		"mov %0, #0x4\n"
		"str %0, [%4, #0x4c]\n"
		"mov %1, #0x0\n"
		"str %1, [%4, #0x50]\n"
		"6: ldr %0, [%4, #0x64]\n"
		"cmp %0, #0x0\n"
		"beq 6b\n"
		"ldr %0, [%4, #0x58]\n"
		"strh %0, [%3], #0x2\n"
		"\n"
		"7: ldr %0, [%4, #0x64]\n"
		"cmp %0, #0x0\n"
		"beq 7b\n"
		"ldr %0, [%4, #0x58]\n"
		"strh %0, [%3], #0x2\n"
		"\n"
		"8: ldr %0, [%4, #0x64]\n"
		"cmp %0, #0x0\n"
		"beq 8b\n"
		"ldr %0, [%4, #0x58]\n"
		"strh %0, [%3], #0x2\n"
		"\n"
		"mov %0, #0x80000002\n"
		"orr %0, %0, #0xc00\n"
		"str %0, [%4, #0x40]\n"
		: "+r"(dummy), "+r"(adr), "+r"(len), "+r"(dat)
		: "r"(cvspiregs)
		: "cc", "memory"
	);
}

/*
#define NCPEEK16(nc, x)		peek16((nc)->nc_regstart + (x))
#define NCPOKE16(nc, x, y)	poke16((nc)->nc_regstart + (x), (y))
#define NCPOKE8(nc, x, y)	poke8((nc)->nc_regstart + (x), (y))
#define NCPOKE16_STREAM(nc, x, y, z)	\
  poke16_stream((nc)->nc_regstart + (x), (y), (z))
#define NCPEEK16_STREAM(nc, x, y, z)	\
  peek16_stream((nc)->nc_regstart + (x), (y), (z))


#include "nandcore.c"
*/

static void reservemem(void) {
	char dummy[32768];
	int i, pgsize;
	FILE *maps;

	pgsize = getpagesize();
	mlockall(MCL_CURRENT|MCL_FUTURE);
	for (i = 0; i < (int)sizeof(dummy); i += 4096) {
		dummy[i] = 0;
	}

	maps = fopen("/proc/self/maps", "r");
	if (maps == NULL) {
		perror("/proc/self/maps");
		exit(1);
	}
	while (!feof(maps)) {
		unsigned int s, e, i, x;
		char m[PATH_MAX + 1];
		char perms[16];
		int r = fscanf(maps, "%x-%x %s %*x %x:%*x %*d",
		  &s, &e, perms, &x);
		if (r == EOF) break;
		assert (r == 4);

		i = 0;
		while ((r = fgetc(maps)) != '\n') {
			if (r == EOF) break;
			m[i++] = r;
		}
		m[i] = '\0';
		assert(s <= e && (s & 0xfff) == 0);
		if(perms[0] == 'r' && perms[3] == 'p' && x != 1)
		  while(s < e) {
			volatile unsigned char *ptr = (unsigned char *)s;
			unsigned char d;
			d = *ptr;
			if(perms[1] == 'w') *ptr = d;
			s += pgsize;
		}
	}
	fclose(maps);
}


static int semid = -1;
static int sbuslocked = 0;
static void sbuslock(void) {
	int r;
	struct sembuf sop;
	if (semid == -1) {
		key_t semkey;
		reservemem();
		semkey = 0x75000000;
		semid = semget(semkey, 1, IPC_CREAT|IPC_EXCL|0777);
		if (semid != -1) {
			sop.sem_num = 0;
			sop.sem_op = 1;
			sop.sem_flg = 0;
			r = TEMP_FAILURE_RETRY(semop(semid, &sop, 1));
			assert (r != -1);
		} else semid = semget(semkey, 1, 0777);
		assert (semid != -1);
	}
	sop.sem_num = 0;
	sop.sem_op = -1;
	sop.sem_flg = SEM_UNDO;
	r = TEMP_FAILURE_RETRY(semop(semid, &sop, 1));
	assert (r == 0);

   if (is_arm11mpcore()) {
      cvgpioregs[0] = (CNS3XXX_BLS1|CNS3XXX_BLS0);
   } else {

      cvgpioregs[0] = (CNS21XX_BLS1|CNS21XX_BLS0);
   }

	sbuslocked = 1;
}


static void sbusunlock(void) {
	struct sembuf sop = { 0, 1, SEM_UNDO};
	int r;
	if (!sbuslocked) return;
	r = TEMP_FAILURE_RETRY(semop(semid, &sop, 1));
	assert (r == 0);
	sbuslocked = 0;
}


static void sbuspreempt(void) {
	int r;
	r = TEMP_FAILURE_RETRY(semctl(semid, 0, GETNCNT));
	assert (r != -1);
	if (r) {
		sbusunlock();
		sched_yield();
		sbuslock();
	}
}


static void irqwait(void *arg __attribute__((unused)),
            unsigned int x __attribute__((unused))) {
	sbuspreempt();
}


void usage(char **argv) {
	fprintf(stderr, "Usage: %s [OPTION] ...\n"
	  "Technologic Systems NAND flash manipulation.\n"
	  "\n"
	  "General options:\n"
	  "  -R, --read=N            Read N blocks of flash to stdout\n"
	  "  -W, --write=N           Write N blocks to flash\n"
	  "  -x, --writeset=BYTE     Write BYTE as value (default 0)\n"
	  "  -i, --writeimg=FILE     Use FILE as file to write to NAND\n"
	  "  -t, --writetest         Run write speed test\n"
	  "  -r, --readtest          Run read speed test\n"
	  "  -n, --random=SEED       Do random seeks for tests\n"
	  "  -z, --blocksize=SZ      Use SZ bytes each read/write call\n"
	  "  -k, --seek=SECTOR       Seek to 512b sector number SECTOR\n"
	  "  -e, --erase=NSECTORS    Erase NSECTORS 512b sectors\n"
	  "  -d, --nbdserver=NBDSPEC Run NBD userspace block driver server\n"
	  "  -I, --bind=IPADDR       Bind NBD server to IPADDR\n"
	  "  -Q, --stats             Print NBD server stats\n"
	  "  -m, --dmesg             Print log of NAND activity\n"
	  "  -f, --foreground        Run NBD server in foreground\n"
	  "  -l, --lun=N             Use chip number N\n"
	  "  -X, --xnand             Use XNAND RAID layer\n"
	  "  -A, --autormw           Use AUTORMW layer\n"
	  "  -s, --stress=BLOCK      Stress block BLOCK until it breaks\n"
          "  -H, --hwtest=BLOCK      Hardware profile block BLOCK\n"
	  "  -b, --break=SECTOR      Erase sector SECTOR for testing\n"
	  "  -I, --xnandinit=NSECT   Initialize flash chip for XNAND RAID\n"
	  "  -L, --listbb            List all factory bad blocks\n"
	  "  -a, --audit             Check integrity of XNAND data\n"
	  "  -Y, --yes               Answer yes to all audit repairs\n"
	  "  -N, --no                Answer no to all audit repairs\n"
	  "  -v, --verbose           Be verbose (-vv for maximum)\n"
          "  -P, --printmbr          Print MBR and partition table\n"
          "  -M, --setmbr            Write MBR from environment variables\n"
	  "  -h, --help              This help\n"
	  "\n"
	  "When running a NBD server, NBDSPEC is a comma separated list of\n"
	  "devices and partitions for the NBD servers starting at port 7525.\n"
	  "e.g. \"lun0:part1,lun1:disc\" corresponds to 2 NBD servers, one at port\n"
	  "7525 serving the first partition of chip #0, and the other at TCP\n"
	  "port 7526 serving the whole disc device of chip #1.\n",
	  argv[0]
	);
}

static inline
int get_ptbl_offs(struct nandcore *nc, int part, int *type, int *sz) {
	unsigned char mbr[512];
	int ret, ext = 0;

	ret = nandread(nc, 0, mbr, 1);
	if (ret != 0) return -1;

	if (part > 4) { /* extended partition */
		int i, oext;
		for (i = 0; i < 4; i++)
		  if (mbr[0x1be + (16 * i) + 4] == 5) break;

		if (i == 4) return -1;

		oext = mbr[0x1be + (16 * i) + 8];
		oext |= mbr[0x1be + (16 * i) + 9] << 8;
		oext |= mbr[0x1be + (16 * i) + 10] << 16;
		oext |= mbr[0x1be + (16 * i) + 11] << 24;
		ext = oext;

		//if (is_cavium()) sbuslock();
		ret = nandread(nc, oext, mbr, 1);
		//if (is_cavium()) sbusunlock();
		if (ret != 0) return -1;

		/* We now have the first EBR in 'mbr' from sector 'oext' */

		/* Skip over 'part' - 5 EBRs -- we don't want them */
		while (part > 5) {
			int n;
			n = mbr[0x1d6];
			n |= mbr[0x1d7] << 8;
			n |= mbr[0x1d8] << 16;
			n |= mbr[0x1d9] << 24;
			ext = oext + n;

			if (mbr[0x1d2] == 0) return -1;

			//if (is_cavium()) sbuslock();
			ret = nandread(nc, ext, mbr, 1);
			//if (is_cavium()) sbusunlock();
			if (ret != 0) return -1;

			part--;
		}

		part -= 4;
	}

	part--;
	ret = mbr[0x1be + (16 * part) + 8];
	ret |= mbr[0x1be + (16 * part) + 9] << 8;
	ret |= mbr[0x1be + (16 * part) + 10] << 16;
	ret |= mbr[0x1be + (16 * part) + 11] << 24;
	ret += ext;

	if (type) *type = mbr[0x1be + (16 * part) + 4];
	if (sz) {
		*sz = mbr[0x1be + (16 * part) + 12];
		*sz |= mbr[0x1be + (16 * part) + 13] << 8;
		*sz |= mbr[0x1be + (16 * part) + 14] << 16;
		*sz |= mbr[0x1be + (16 * part) + 15] << 24;
	}

	return ret;
}

static
int setmbr(struct nandcore *nc) {
	unsigned char mbr[512];
	unsigned int sz, offs;
	int ret, i;
	char var[32];
	char *e;

	//if (is_cavium()) sbuslock();
	ret = nandread(nc, 0, mbr, 1);
	//if (is_cavium()) sbusunlock();
	if (ret) return ret;
	//if (mbr[510] != 0x55 || mbr[511] != 0xaa) memcpy(mbr, cavium_mbr, 512);
	//else memcpy(mbr, cavium_mbr, 446);

	for (i = 0; i < 4; i++) {
		sprintf(var, "part%d_offs", i+1);
		e = getenv(var);
		if (e) {
			offs = strtoul(e, NULL, 0);
			mbr[0x1be + (16 * i) + 8] = offs & 0xff;
			mbr[0x1be + (16 * i) + 9] |= (offs>>8) & 0xff;
			mbr[0x1be + (16 * i) + 10] |= (offs>>16) & 0xff;
			mbr[0x1be + (16 * i) + 11] |= offs>>24;
		}
		sprintf(var, "part%d_sz", i+1);
		e = getenv(var);
		if (e) {
			sz = strtoul(e, NULL, 0);
			mbr[0x1be + (16 * i) + 12] = sz & 0xff;
			mbr[0x1be + (16 * i) + 13] |= (sz>>8) & 0xff;
			mbr[0x1be + (16 * i) + 14] |= (sz>>16) & 0xff;
			mbr[0x1be + (16 * i) + 15] |= sz>>24;
		}
		sprintf(var, "part%d_type", i+1);
		e = getenv(var);
		if (e) mbr[0x1be + (16 * i) + 4] = strtoul(e, NULL, 0);
	}
	//if (is_cavium()) sbuslock();
	nandwrite(nc, 0, mbr, 1);
	nandread(nc, 0, mbr, 1); /* To ensure write is terminated */
	//if (is_cavium()) sbusunlock();
	return 0;
}


static inline
unsigned long get_ptbl_length(struct nandcore *nc, int part) {
   unsigned char mbr[512];
   unsigned long ret;
   ret = nandread(nc, 0, mbr, 1);
   if (ret != 0) return -1;

   if (part==0)
      return size;

   part--;
   ret = mbr[0x1be + (16 * part) + 12];
   ret |= mbr[0x1be + (16 * part) + 13] << 8;
   ret |= mbr[0x1be + (16 * part) + 14] << 16;
   ret |= mbr[0x1be + (16 * part) + 15] << 24;

   return ret;
}


static inline
void nbdwrite(struct nandcore *nc, int fd, unsigned int sector, int len) {
	int ret, i;
	unsigned char *buf = nbdbuf;

	while (len > nbdbufsz) {
		nbdwrite(nc, fd, sector, nbdbufsz);
		sector += nbdbufsz;
		len -= nbdbufsz;
	}

	if (is_cavium()) sbuslock();
	ret = nandread(nc, sector, buf, len);
	if (is_cavium()) sbusunlock();
	assert (ret == 0);
#ifndef NOCHECKSUM
	for (i = 0; i < len; i++) {
		int j, csum;
		unsigned char *c = &buf[512 * i];
		for (csum = 0, j = 0; j < 512; j++) {
			csum += c[j];
		}
		if (csumtbl[i + sector] & 0x80) {
			if ((csumtbl[i + sector] & 0x7f) != (csum & 0x7f)) {
				fprintf(stderr,
				  "nbd checksum error sector %d! csum=0x%x\n",
				  	i+sector, csum & 0x7f);
			}
			assert((csumtbl[i + sector] & 0x7f) == (csum & 0x7f));
		} else {
			csumtbl[i + sector] = 0x80 | (csum & 0x7f);
		}
	}
#endif

	len = len << 9;
	while (len) {
		ret = TEMP_FAILURE_RETRY(write(fd, buf, len));
		assert (ret > 0);
		len -= ret;
		buf += ret;
	}
}


static inline
void nbdread(struct nandcore *nc, int fd, unsigned int sector, int len) {
	int ret, i;
	unsigned char *buf = nbdbuf;
	int rem;

	while (len > nbdbufsz) {
		nbdread(nc, fd, sector, nbdbufsz);
		sector += nbdbufsz;
		len -= nbdbufsz;
	}

	rem = len << 9;
	while (rem) {
		ret = TEMP_FAILURE_RETRY(read(fd, buf, rem));
		assert (ret > 0);
		rem -= ret;
		buf += ret;
	}

#ifndef NOCHECKSUM
	for (i = 0; i < len; i++) {
		int j, csum;
		unsigned char *c = &nbdbuf[512 * i];
		for (csum = 0, j = 0; j < 512; j++) {
			csum += c[j];
		}
		csumtbl[i + sector] = 0x80 | (csum & 0x7f);
	}
#endif

	//if (is_cavium()) sbuslock();
	ret = nandwrite(nc, sector, nbdbuf, len);
	//if (is_cavium()) sbusunlock();
	assert (ret == 0);
}


static inline
int xread(int fd, void *d, int len) {
	int olen = len;
	unsigned char *buf = d;
	int x;

	if (buf == NULL) {
		buf = nbdbuf;
		while (len > (nbdbufsz << 9)) {
			x = xread(fd, buf, nbdbufsz);
			if (x == 0) return 0;
			len -= x;
		}
		if (len) {
			x = xread(fd, buf, len);
			if (x != len) return 0;
		}
	}

	while (len > 0) {
		x = TEMP_FAILURE_RETRY(read(fd, buf, len));
		if (x == 0) return 0;
		buf += x;
		len -= x;
	}
	return olen;
}

static inline
int xwrite(int fd, void *d, int len) {
	int olen = len;
	unsigned char *buf = d;

	while (len > 0) {
		int x;
		x = TEMP_FAILURE_RETRY(write(fd, buf, len));
		if (x == -1) return 0;
		buf += x;
		len -= x;
	}
	return olen;
}

static int do_random_read, do_scrubreset;
static void alarmsig(int x __attribute__((unused))) {
	static unsigned int timeseq;

	do_random_read = 1;
	if ((timeseq & 0x3) == 0) do_scrubreset = 1;
	timeseq++;
}


   static void handler(int signum)
{
    /* Take appropriate actions for signal delivery */

      printf("handler(%d)\n", signum);
}


static
void nbdserver(struct nandcore *nc, const char *arg, struct in_addr iface) {
	int i, n, maxfd = 0;
	int infd[8], remfd[8];
	int offs[8], luns[8];
	int numnbd;
	struct sockaddr_in sa;
	struct nbd_request req;
	struct nbd_reply resp;
	unsigned int reqmagic;
	fd_set rfds;
	struct sigaction siga;
	struct itimerval it;
	FILE *urandom = NULL;
	key_t shmkey;
	int shmid;
	int read_prev = 0;
	unsigned int next_sect = 0;
	volatile unsigned long long *sbus_shm;
        char s[80];

	siga.sa_handler = alarmsig;
	sigemptyset(&siga.sa_mask);
	siga.sa_flags = 0;
	it.it_value.tv_usec = it.it_interval.tv_usec = 0;
	it.it_interval.tv_sec = it.it_value.tv_sec = 15 * 60;

	urandom = fopen("/dev/urandom", "r");
	assert(urandom != NULL);

	FD_ZERO(&rfds);

	numnbd = 0;
	while(*arg) {
		if (*arg == ',') arg++;
		if (strncmp(arg, "lun", 3) != 0) break;
		arg += 3;
		if (!isdigit(*arg)) break;
		n = *arg - '0';
		assert(n == 0); /* not yet ready for lun 1 */
		// if (sd->sd_lun == n) sdcs[numnbd] = sd;
		// else sdcs[numnbd] = NULL;
		luns[numnbd] = n;
		arg++;
		if (*arg != ':') break;
		arg++;
		if (strncmp(arg, "part", 4) == 0) {
			arg += 4;
			if (!isdigit(*arg)) break;
			offs[numnbd] = -(*arg - '0');
			arg++;
		} else if (strncmp(arg, "disc", 4) == 0) {
			arg += 4;
			offs[numnbd] = 0;
		} else break;
		numnbd++;
	}

	if (*arg != 0) {
		fprintf(stderr, "NBDSPEC parse error\n");
		exit(9);
	}

	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = iface.s_addr;
	for (i = 0; i < numnbd; i++) {
		int r, sk, x = 1;
		/* TCP server socket */
		sa.sin_port = htons(7525 + i);
		sk = socket(PF_INET, SOCK_STREAM, 0);
		setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &x, 4);
		assert(sk != -1);

		r = bind(sk, (struct sockaddr *)&sa, sizeof(sa));
		if (r) {
			perror("bind");
			exit(1);
		}
		r = listen(sk, 5);
		assert(r != -1);
		r = fcntl(sk, F_SETFL, O_NONBLOCK);
		assert(r != -1);
		if (sk > maxfd) maxfd = sk;
		FD_SET(sk, &rfds);
		infd[i] = sk;

		remfd[i] = -1;

		printf("/dev/nbd%d ready on port #%d (%ld sectors)\n",
		  i, ntohs(sa.sin_port), get_ptbl_length(nc, i));
	}
	resp.magic[0] = 0x67;
	resp.magic[1] = 0x44;
	resp.magic[2] = 0x66;
	resp.magic[3] = 0x98;
	reqmagic = htonl(0x25609513);
	if (!opt_foreground) daemon(0, 1);

	signal(SIGHUP, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);
	sigaction(SIGALRM, &siga, NULL);
	setitimer(ITIMER_REAL, &it, NULL);
	do_random_read = 1;





	{
    struct sigaction sa;


    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART; /* Restart functions if
                                 interrupted by handler */
    if (sigaction(SIGCONT, &sa, NULL) == -1)
        /* Handle error */;


    /* Further code */
	}

	shmkey = 0x75000000;
	shmid = shmget(shmkey, 0x1000, IPC_CREAT);
	assert(shmid != -1);
	sbus_shm = shmat(shmid, NULL, 0);
	sbus_shm += 16;
	sbus_shm[0] = getpid();

superloop_start:
	if (do_random_read) {
		unsigned int test;
		fread(&test, 4, 1, urandom);
		test = test % nc->size;
		nandlog(nc, "doing random sector read\n");

		if (is_cavium()) sbuslock();
		if (nc->flags & NAND_XNAND) {
			if (nc->write_parked) nandread(nc, 0, nbdbuf, 1);
			nc->flags &= ~NAND_XNAND;
			nandread(nc, test, nbdbuf, 1);
			nc->flags |= NAND_XNAND;
		} else nandread(nc, test, nbdbuf, 1);
		if (is_cavium()) sbusunlock();

		do_random_read = 0;
	}

	if (nc->write_parked && (nc->flags & NAND_XNAND)) {
		struct timeval tv;
		tv.tv_sec = 0;
		tv.tv_usec = 100000;
		n = select(maxfd + 1, &rfds, NULL, NULL, &tv);
		if (n == 0) {
			//if (is_cavium()) sbuslock();
			sbus_shm[14]++;
			read_prev = next_sect = 1;
			nandread(nc, 0, nbdbuf, 1);
			//if (is_cavium()) sbusunlock();
		}
	} else {
		n = select(maxfd + 1, &rfds, NULL, NULL, NULL);
	}

	if (n == -1 && errno == EINTR) FD_ZERO(&rfds);

	for (i = 0; i < numnbd; i++) {
		int r;
		if (remfd[i] == -1) continue;

		if (FD_ISSET(remfd[i], &rfds)) {
			unsigned int sec, len;
			req.magic = 0;
			r = xread(remfd[i], &req, sizeof(req));
			if (r == 0) {
				nandlog(nc, strerror(errno));
				nandlog(nc, "xread returns 0\n");
				goto eof;
			}
			assert (r == sizeof(req));
			assert (req.magic == reqmagic);

			resp.error = 0;
			/* lazy SD init */
//			if (sdcs[i] == NULL) {
//				sdcs[i] = malloc(sizeof(struct sdcore));
//				memcpy(sdcs[i], sd, sizeof(struct sdcore));
//				sdcs[i]->sd_lun = luns[i];
//				if (sdreset(sdcs[i]) == 0) resp.error = 1;
//			} else resp.error = 0;


			/* lazy partition table parse */
			if (offs[i] < 0 && !resp.error) {
				r = get_ptbl_offs(nc, -offs[i], NULL, NULL);
				if (r > 0) offs[i] = r;
				else resp.error = htonl(1);
			}

			memcpy(resp.handle, req.handle, 8);

			len = ntohl(req.len);
			assert ((len & 0x1ff) == 0);
			len = len >> 9;
			sec = (ntohl(req.fromhi) & 0x1ff) << 23;
			sec |= ntohl(req.fromlo) >> 9;
			sec += offs[i];

//			printf("Req: Sector %d Length %d\n", sec, len);

			if ((sec + len) > (unsigned int)size) {
				fprintf(stderr,
				  "beyond end of device: %x-%x\n",
				  sec, sec + len - 1);
				resp.error = htonl(1);
				sbus_shm[5]++;
			}

			if (resp.error) {
				if (req.type[3] == 1) {
					fprintf(stderr, "err write %d\n", len);
					xread(remfd[i], NULL, len << 9);
				}
				r = xwrite(remfd[i], &resp, sizeof(resp));
				if (r == 0) {
					nandlog(nc, strerror(errno));
					nandlog(nc, "xwrite returns 0\n");
					goto eof;
				}
				assert (r == sizeof(resp));
				continue;
			}

			if (req.type[3] == 0) { /* READ */
			//	if (!(random() & 0x1f))
			//		nc->flags ^= NAND_TRY1;
			//	else if (!(random() & 0x1f))
			//		nc->flags ^= NAND_TRY2;
			//	else
			//		nc->flags &= ~(NAND_TRY1 | NAND_TRY2);
				if (nc->verbose > 1) fprintf(stderr,
					"nbd read at %d len %d\n", sec, len);
                                sprintf(s, "nbd read at %d len %d\n",
                                        sec, len);
                                nandlog(nc, s);
				r = xwrite(remfd[i], &resp, sizeof(resp));
				if (r == 0) {
					nandlog(nc, strerror(errno));
					nandlog(nc, "xwrite#2 returns 0\n");
					goto eof;
				}
				assert (r == sizeof(resp));
				nbdwrite(nc, remfd[i], sec, len);
				sbus_shm[1]++;
				sbus_shm[2] += len;
				if (!read_prev || sec != next_sect)
				  sbus_shm[14]++;
				read_prev = 1;
				next_sect = sec + len;

			} else if (req.type[3] == 1) { /* WRITE */
			//	if (!(random() & 0x1f))
			//		nc->flags ^= NAND_TRY1;
			//	else if (!(random() & 0x1f))
			//		nc->flags ^= NAND_TRY2;
			//	else
			//		nc->flags &= ~(NAND_TRY1 | NAND_TRY2);
				if (nc->verbose > 1) fprintf(stderr,
					"nbd write at %d len %d\n", sec, len);
                                sprintf(s, "nbd write at %d len %d\n",
                                        sec, len);
                                nandlog(nc, s);
				nbdread(nc, remfd[i], sec, len);
				sbus_shm[3]++;
				sbus_shm[4] += len;
				if (read_prev || sec != next_sect)
				  sbus_shm[15]++;
				read_prev = 0;
				next_sect = sec + len;

				r = xwrite(remfd[i], &resp, sizeof(resp));
				if (r == 0) {
					nandlog(nc, strerror(errno));
					nandlog(nc, "xwrite#3 returns 0\n");
					goto eof;
				}
				assert (r == sizeof(resp));
			} else {
				nandlog(nc, "crazy req.type value from NBD kernel module\n");
				goto eof;
			}

		} else FD_SET(remfd[i], &rfds);

		continue;

eof:
       printf("EOF on handle %d\n", infd[i]);

		nandlog(nc, "socket EOF\n");
		shutdown(remfd[i], SHUT_RDWR);
		FD_CLR(remfd[i], &rfds);
		TEMP_FAILURE_RETRY(close(remfd[i]));
		remfd[i] = -1;
	}

	for (i = 0; i < numnbd; i++) {
		/* new connection requests pending accept? */
		if (FD_ISSET(infd[i], &rfds)) {
			int r, sk, tos = IPTOS_LOWDELAY;
			int x;
			unsigned long long ull;
			char d[128] =
			  {0x00, 0x00, 0x42, 0x02, 0x81, 0x86, 0x12, 0x53};
			struct linger l;
			if (remfd[i] != -1) {
				shutdown(remfd[i], SHUT_RDWR);
				FD_CLR(remfd[i], &rfds);
				TEMP_FAILURE_RETRY(close(remfd[i]));
				remfd[i] = -1;
			}
			sk = TEMP_FAILURE_RETRY(accept(infd[i], NULL, 0));
			if (sk == -1) {
				FD_SET(infd[i], &rfds);
				continue;
			}

			//printf("nand size is %d, i=%d\n", size, i);
//XXX: investigate
         if (i==0)
            ull = size << 9;
         else
            ull= get_ptbl_length(nc, i) << 9;

         //printf("Accepted %d, sz = %lld bytes\n", i, ull);

			setsockopt(sk, IPPROTO_IP, IP_TOS, &tos, 4);
			x = 1;
			setsockopt(sk, IPPROTO_TCP, TCP_NODELAY, &x, 4);
			setsockopt(sk, SOL_SOCKET, SO_KEEPALIVE, &x, 4);
			setsockopt(sk, SOL_SOCKET, SO_OOBINLINE, &x, 4);
			l.l_onoff = 0;
			l.l_linger = 0;
			setsockopt(sk, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
			if (sk > maxfd) maxfd = sk;
			FD_SET(sk, &rfds);
			remfd[i] = sk;
			r = xwrite(sk, "NBDMAGIC", 8);
			assert (r == 8);
			r = xwrite(sk, d, 8);
			assert (r == 8);
			/* size */
			d[0] = 0; d[1] = 0; d[2] = 0; d[3] = 0x80;
			d[4] = 0; d[5] = 0; d[6] = 0; d[7] = 0;
//		r = xwrite(sk, d, 8);

		ull = htonll(ull);
		r = xwrite(sk, &ull, 8);

			assert (r == 8);
			bzero(d, 128);
			r = xwrite(sk, d, 128);
			assert (r == 128);
		} else {
			FD_SET(infd[i], &rfds);
		}
	}

	if (do_scrubreset) {
		reset_scrubbed_sectors(nc);
		do_scrubreset = 0;
	}

	if (nc->rfails || nc->wfails || nc->xfixs || nc->scrubs ||
	  nc->fallbacks1 || nc->fallbacks2 || nc->fallbacks3 || nc->erases) {
		sbus_shm[6] += nc->xfixs;
		sbus_shm[7] += nc->scrubs;
		sbus_shm[8] += nc->fallbacks1;
		sbus_shm[9] += nc->fallbacks2;
		sbus_shm[10] += nc->fallbacks3;
		sbus_shm[11] += nc->wfails;
		sbus_shm[12] += nc->rfails;
		sbus_shm[13] += nc->erases;
		nc->scrubs = nc->xfixs = nc->wfails = nc->rfails = 0;
		nc->fallbacks1 = nc->fallbacks2 = nc->fallbacks3 = 0;
		nc->erases = 0;
	}
	goto superloop_start;

}

void rand_fill(unsigned char *buf, unsigned int len) {
	unsigned int i;
	int *p;

	p = (int *)buf;
	for(i=0; i < (len >> 2); i++)
		p[i] = random();
}

/************************************************
 * setTerm()
 *
 * Set the terminal to CANONICAL/NON-CANONICAL mode
 ************************************************/
void setTerm(int mode)
{
	struct termios newTerm;
	int fid= open(ttyname(fileno(stdin)), O_RDWR|O_NDELAY);

	if (fid < 0)
	  fprintf(stderr, "can't open terminal\n");
	else {
		tcgetattr(fid, &newTerm);
		if(mode == 1)
			newTerm.c_lflag|= ICANON;
		else
			newTerm.c_lflag&= ~ICANON;
		tcsetattr(fid, TCSANOW, &newTerm);
		close(fid);
	}

}//setTerm()

int ignoreerror(void *arg __attribute__((unused)),
   unsigned int sector, unsigned char *buf __attribute__((unused))) {
	static int i;

	fprintf(stderr, "ioerror%d_ignored=0x%x\n", i++, sector);
	return 0;
}


int main(int argc, char **argv) {
	struct nandcore nc;
	int devmem, i, c, ret, partseek;
	int opt_writetest = 0, opt_blocksz = 1;
	int opt_readtest = 0, opt_read = 0, opt_readblks = 0;
	int opt_writeset = 0, opt_write = 0, opt_writeblks = 0;
	int opt_seek = 0, opt_erase = 0, opt_autormw = 0;
	int opt_nbdserver = 0, opt_lun = 0, opt_xnand = 0;
	int opt_stress = 0, opt_break = 0, opt_verbose = 1;
	int opt_audit = 0, opt_fixyes = 0, opt_fixno = 0;
	char *opt_nbdarg = NULL;
	int opt_random = 0, opt_xnandinit = 0, opt_bugreproduce = 0;
	int opt_listbb = 0, opt_hwtest = 0, opt_stats = 0, opt_dmesg = 0;
	int opt_printmbr = 0, opt_setmbr = 0;
	struct in_addr opt_bind = { INADDR_ANY };
	unsigned int sizemask, randomseed = 0;
	FILE *ifile = NULL;
        key_t logkey;
        int logid;
	static struct option long_options[] = {
	  { "printmbr", 0, 0, 'P' },
	  { "setmbr", 0, 0, 'M' },
	  { "stats", 0, 0, 'Q' },
	  { "dmesg", 0, 0, 'm' },
	  { "bind", 1, 0, 'D' },
	  { "writetest", 0, 0, 't' },
	  { "writeset", 1, 0, 'x' },
	  { "writeimg", 1, 0, 'i' },
	  { "seek", 1, 0, 'k' },
	  { "readtest", 0, 0, 'r' },
	  { "read", 1, 0, 'R' },
	  { "random", 1, 0, 'n' },
	  { "write", 1, 0, 'W' },
	  { "blocksize", 1, 0, 'z' },
	  { "erase", 1, 0, 'e' },
	  { "stress", 1, 0, 's' },
	  { "break", 1, 0, 'b' },
	  { "nbdserver", 1, 0, 'd' },
	  { "lun", 1, 0, 'l' },
	  { "xnand", 0, 0, 'X' },
	  { "autormw", 0, 0, 'A' },
	  { "foreground", 0, 0, 'f' },
	  { "xnandinit", 1, 0, 'I' },
	  { "listbb", 0, 0, 'L' },
	  { "audit", 0, 0, 'a' },
	  { "yes", 0, 0, 'Y' },
	  { "no", 0, 0, 'N' },
	  { "verbose", 0, 0, 'v' },
	  { "help", 0, 0, 'h' },
	  { "bugreproduce", 0, 0, 'B' },
          { "hwtest", 1, 0, 'H' },
	  { 0, 0, 0, 0 }
	};


	while((c = getopt_long(argc, argv,
	  "Ve:tx:mi:k:rR:n:W:z:d:l:hAXfI:s:b:aYNBvLD:QH:PM",
	  long_options, NULL)) != -1) {
		switch (c) {
		case 'P':
			opt_printmbr = 1;
			break;
		case 'm':
			opt_dmesg = 1;
			break;
		case 'M':
			opt_setmbr = 1;
			break;
		case 'Q':
			opt_stats = 1;
			break;
		case 'D':
			i = inet_aton(optarg, &opt_bind);
			if (i == 0) {
				fprintf(stderr, "Bad arg: %s\n", optarg);
				return 3;
			}
			break;
		case 'L':
			opt_listbb = 1;
			break;
		case 'I':
			opt_xnandinit = strtoul(optarg, NULL, 0);
			break;
		case 'a':
			opt_audit = 1;
			break;
		case 'Y':
			opt_fixyes = 1;
			break;
		case 'N':
			opt_fixno = 1;
			break;
		case 'v':
			opt_verbose++;
			break;
		case 'f':
			opt_foreground = 1;
			break;
		case 'X':
			opt_xnand = 1;
			break;
		case 'A':
			opt_autormw = 1;
			break;
		case 'l':
			opt_lun = strtoul(optarg, NULL, 0);
			break;
		case 'e':
			opt_erase = strtoul(optarg, NULL, 0);
			break;
		case 'd':
			opt_nbdserver = 1;
			opt_nbdarg = strdup(optarg);
			break;
		case 'k':
			if (strcmp(optarg, "kernel") == 0)
			  opt_seek = -32;
			else if (strcmp(optarg, "initrd") == 0)
			  opt_seek = -33;
			else if (optarg[0] == 'p' && optarg[1] == 'a' &&
			  optarg[2] == 'r' && optarg[3] == 't')
			  opt_seek = -1 * (optarg[4] - '0');
			else opt_seek = strtoul(optarg, NULL, 0);
			break;
		case 'i':
			i = strlen(optarg);
			if (strcmp("-", optarg) == 0) ifile = stdin;
			else if (strcmp(".bz2", &optarg[i - 4]) == 0) {
				char b[512];
				snprintf(b, 512, "exec bunzip2 -c '%s'",
				  optarg);
				ifile = popen(b, "r");
			} else ifile = fopen(optarg, "r");
			if (!ifile) {
				perror(optarg);
				return 3;
			}
			if (opt_writeblks == 0) opt_writeblks = INT_MAX;
			opt_write = 1;
			break;
		case 'n':
			randomseed = strtoul(optarg, NULL, 0);
			fprintf(stderr, "randomseed=0x%x\n", randomseed);
			opt_random = 1;
			break;
		case 'x':
			opt_writeset = strtoul(optarg, NULL, 0);
			break;
		case 'W':
			opt_write = 1;
			opt_writeblks = strtoul(optarg, NULL, 0);
			break;
		case 'R':
			opt_read = 1;
			opt_readblks = strtoul(optarg, NULL, 0);
			break;
		case 'r':
			opt_readtest = 1;
			break;
		case 't':
			opt_writetest = 1;
			break;
		case 's':
			opt_stress = strtoul(optarg, NULL, 0);
			break;
		case 'b':
			opt_break = strtoul(optarg, NULL, 0);
			break;
		case 'z':
			opt_blocksz = strtoul(optarg, NULL, 0) / 512;
			break;
		case 'B':
			opt_bugreproduce = 1;
			break;
		case 'H':
			opt_hwtest = strtoul(optarg, NULL, 0);
			break;
		case 'h':
		default:
			usage(argv);
			return(1);
		}
	}

	if (opt_stats) {
		key_t shmkey;
		int shmid;
		unsigned long long *sbus_shm;

		shmkey = 0x75000000;
		shmid = shmget(shmkey, 0x1000, IPC_CREAT);
		assert(shmid != -1);
		sbus_shm = shmat(shmid, NULL, 0);
		sbus_shm += 16;

		errno = 0;
		if (getpriority(PRIO_PROCESS, sbus_shm[0]) == -1 &&
		  errno == ESRCH) sbus_shm[0] = 0;

		fprintf(stderr, "nbdpid=%lld\n", sbus_shm[0]);
		fprintf(stderr, "nbd_readreqs=%lld\n", sbus_shm[1]);
		fprintf(stderr, "nbd_read_blks=%lld\n", sbus_shm[2]);
		fprintf(stderr, "nbd_writereqs=%lld\n", sbus_shm[3]);
		fprintf(stderr, "nbd_write_blks=%lld\n", sbus_shm[4]);
		fprintf(stderr, "nbd_seek_past_eof_errs=%lld\n", sbus_shm[5]);
		fprintf(stderr, "xnand_xfixs=%lld\n", sbus_shm[6]);
		fprintf(stderr, "xnand_scrubs=%lld\n", sbus_shm[7]);
		fprintf(stderr, "xnand_fallbacks=%lld\n", sbus_shm[8]);
		fprintf(stderr, "xnand_level2_fallbacks=%lld\n", sbus_shm[9]);
		fprintf(stderr, "xnand_level3_fallbacks=%lld\n", sbus_shm[10]);
		fprintf(stderr, "xnand_write_fails=%lld\n", sbus_shm[11]);
		fprintf(stderr, "xnand_data_losses=%lld\n", sbus_shm[12]);
		fprintf(stderr, "xnand_blk_erases=%lld\n", sbus_shm[13]);
		if (sbus_shm[13]) fprintf(stderr, "xnand_efficiency=%lld\n",
		  (sbus_shm[4]*3*100000)/(sbus_shm[13]*256));
		fprintf(stderr, "read_seeks=%lld\n", sbus_shm[14]);
		fprintf(stderr, "write_seeks=%lld\n", sbus_shm[15]);
		return 0;
	}

        logkey = 0x75500000;
        logid = shmget(logkey, 0x4000, IPC_CREAT);
        assert(logid != -1);

	if (opt_dmesg) {
                unsigned int *x, logstart, logsize;
                char *s;
                nc.logshm = shmat(logid, NULL, 0);
                x = (unsigned int *)nc.logshm;
                if (*x != NAND_LOG_MAGIC) {
                        fprintf(stderr, "No nand activity has been logged.\n");
                        return 0;
                }
                logstart = x[1];
                logsize = x[2];
                s = (char*)x;
                s += 12 + logstart;
                if (0 == logstart) {
			fwrite(s, 1, logsize, stdout);
                        return 0;
                }
                fwrite(s, 1, 0x4000 - logstart - 12, stdout);
                s = (char*)x;
                s += 12;
                fwrite(s, 1, logstart, stdout);
                return 0;
        }

	if (opt_audit) opt_xnand = 1;
	if (opt_autormw && opt_xnand) {
		fprintf(stderr, "Cannot use both XNAND and AUTORMW\n");
		return(5);
	}

	devmem = open("/dev/mem", O_RDWR|O_SYNC);
	assert(devmem != -1);
	bzero(&nc, sizeof(nc));

	if (is_cavium()) {
		cvspiregs = (unsigned int *) mmap(0, 4096,
		  PROT_READ | PROT_WRITE, MAP_SHARED, devmem, 0x71000000);
		cvgpioregs = (unsigned int *) mmap(0, 4096,
		  PROT_READ | PROT_WRITE, MAP_SHARED, devmem,
		  is_arm11mpcore()? 0x74000000 : 0x7c000000);

		//sbuslock();
		cvspiregs[0x64 / 4] = 0x0; /* RX IRQ threahold 0 */
		cvspiregs[0x40 / 4] = 0x80000c02; /* 24-bit mode, no byte swap */
		cvspiregs[0x60 / 4] = 0x0; /* 0 clock inter-transfer delay */
		cvspiregs[0x6c / 4] = 0x0; /* disable interrupts */
		cvspiregs[0x4c / 4] = 0x4; /* deassert CS# */
		for (i = 0; i < 8; i++) cvspiregs[0x58 / 4];
		last_gpio_adr = 3;

		if (is_arm11mpcore())
			cvgpioregs[0] = (CNS3XXX_SBUS_A5 | CNS3XXX_SBUS_A6 |
			  CNS3XXX_BLS0 | CNS3XXX_BLS1);
		else cvgpioregs[0] = (CNS21XX_BLS0 | CNS21XX_BLS1);
		cvgpioregs[0] = (1<<17|1<<3);
		nc.os_irqwait = irqwait;
		nc.nc_regstart = 0x10;

		if (is_arm11mpcore()) {
			unsigned int *clkregs, *cvmiscregs;
			unsigned int prev_gpioa_en, prev_gpioa_ddr;

			fprintf(stderr, "Looks like an ARM11 MPCore\n");

			cvmiscregs = (unsigned int *) mmap(0, 4096,
			  PROT_READ | PROT_WRITE, MAP_SHARED, devmem, 0x76000000);

			clkregs   = (unsigned int *) mmap(0, 4096,
			  PROT_READ | PROT_WRITE, MAP_SHARED, devmem, 0x77000000);

			prev_gpioa_en = cvmiscregs[0x14/4];
			prev_gpioa_ddr = cvgpioregs[0x8/4];

			// Clock Gate Register
			clkregs[0] |= _BIT(3) | _BIT(10);      // enable SPI and GPIO clocks

			// Reset Register
			clkregs[0x04/4] |= _BIT(3)  | _BIT(10);    // enable SPI and GPIO

			// GPIOA_PIN_EN --  make 'em GPIO
			cvmiscregs[0x14/4] &=  ~(CNS3XXX_SBUS_A5 | CNS3XXX_SBUS_A6 |
			  CNS3XXX_BLS1 | CNS3XXX_BLS0 |
			  _BIT(13) | _BIT(12) | _BIT(11));

			// GPIOA Data Direction -- make 'em outputs
			cvgpioregs[0x8/4] |=  (CNS3XXX_SBUS_A5 | CNS3XXX_SBUS_A6 |
			  CNS3XXX_BLS1 | CNS3XXX_BLS0 |
			  _BIT(13) | _BIT(12) | _BIT(11));

			// GPIOB_PIN_EN
			cvmiscregs[0x18/4] |= 0xF00;   // GPIO_B, enable SPI bits

			// GPIOA Data Out, magic-sequence 110b on bits 13,12,11
			cvgpioregs[0] &= ~_BIT(11);
			cvgpioregs[0] |= (_BIT(13) | _BIT(12));

			// SPI bit-rate
			cvspiregs[0x48 / 4] = 1;      // 25MHz

			cvspiregs[0x58 / 4];
			cvspiregs[0x58 / 4];
			cvspiregs[0x58 / 4];
			cvspiregs[0x58 / 4];

			last_gpio_adr = 3;
			cvgpioregs[0] &= ~(_BIT(13) | _BIT(12) | _BIT(11));  // Clear bits
			cvgpioregs[0] |= (CNS3XXX_SBUS_A5 | CNS3XXX_SBUS_A6 | CNS3XXX_BLS0 | CNS3XXX_BLS1);  // Set bits
		}

	}
	else if (is_pxa168()) {
		nc.flags |= NAND_MARVELL;
		nc.nc_regstart = (unsigned int)mmap(0, 0x800,
		  PROT_READ|PROT_WRITE, MAP_SHARED, devmem, NAND_FLASH_CONTROLLER_BASE);
	}
	else if (is_mx51()) {
		nc.flags |= NAND_MX51;
		nc.nc_regstart = (unsigned int)mmap(0, 0x10000,
		  PROT_READ|PROT_WRITE, MAP_SHARED, devmem, MX51_NAND_FLASH_CONTROLLER_AXI_BASE);
		nc.nc_extraregs = (unsigned int)mmap(0, 0x4000,
		  PROT_READ|PROT_WRITE, MAP_SHARED, devmem, MX51_NAND_FLASH_CONTROLLER_IP_BASE);
	}
	else {
		nc.nc_regstart = (unsigned int)mmap(0, 4096,
		  PROT_READ|PROT_WRITE, MAP_SHARED, devmem, 0x30000000);
		//nc.nc_regstarts += 0x100/sizeof(unsigned int);
		nc.nc_regstart += 0x100/sizeof(unsigned int);
	}

	if (opt_autormw && !opt_xnandinit) nc.flags |= NAND_AUTORMW;
	else if (opt_xnand && !opt_xnandinit) nc.flags |= NAND_XNAND;
	else nc.flags |= NAND_RAW;
	nc.verbose = opt_verbose;
        nc.logshm = shmat(logid, NULL, 0);

	size = nandreset(&nc);
	//if (is_cavium()) sbusunlock();

	if (size == 0) {
		fprintf(stderr, "Error.  size is zero, can't continue\n");
		exit(1);
	}

	fprintf(stderr, "chipid=0x%04x\nsectors=%d\nblocks=%d\nbytes=%d\n",
	  (unsigned short)nc.chipid, size, size/nc.erasesize, nc.erasesize*512);

	//assert(manu == 0xc2 && dev == 0x2016);
	if (opt_writetest || opt_readtest || opt_read || opt_write ||
	  opt_nbdserver) fprintf(stderr, "blocksize=%d\n", opt_blocksz * 512);
	sizemask = 0;
	for (i = 0; i < 32; i++) {
		if (size > (1 << (i + 1))) sizemask = (sizemask << 1) | 0x1;
		else break;
	}

#ifndef NOCHECKSUM
	csumtbl = malloc(size);
	bzero(csumtbl, size);
#endif
	if (opt_printmbr) {
		int ty, sz, offs, hasext;
		for (i = 0, hasext = 0; i < 4; i++) {
			offs = get_ptbl_offs(&nc, i+1, &ty, &sz);
			fprintf(stderr, "part%d_offs=0x%x\n", i+1, offs);
			fprintf(stderr, "part%d_sz=0x%x\n", i+1, sz);
			fprintf(stderr, "part%d_type=0x%x\n", i+1, ty);
			if (ty == 5) {
				fprintf(stderr, "has_extended_partition=1\n");
				hasext = 1;
			}
		}
		i = 4;
		if (hasext)
		  while ((offs = get_ptbl_offs(&nc, i+1, &ty, &sz)) != -1) {
			fprintf(stderr, "part%d_offs=0x%x\n", i+1, offs);
			fprintf(stderr, "part%d_sz=0x%x\n", i+1, sz);
			fprintf(stderr, "part%d_type=0x%x\n", i+1, ty);
			i++;
		}
	}

	if (opt_setmbr) fprintf(stderr, "setmbr_ok=%d\n", setmbr(&nc) ? 0 : 1);

	if (opt_seek == -32) { /* kernel partition */
		int ty;
		partseek = 1;
		for (i = 0; i < 4; i++) {
			opt_seek = get_ptbl_offs(&nc, i, &ty, NULL);
			if (ty == 0xda) break;
		}
	} else if (opt_seek == -33) { /* initrd partition */
		int ty, np = 0;
		partseek = 1;
		for (i = 0; i < 4; i++) {
			opt_seek = get_ptbl_offs(&nc, i, &ty, NULL);
			if (ty == 0xda) np++;
			if (np == 2) break;
		}
	} else if (opt_seek < 0) {
		opt_seek = get_ptbl_offs(&nc, -opt_seek, NULL, NULL);
		partseek = 1;
	} else partseek = 0;

	if (opt_xnandinit) {
		unsigned int j;
		unsigned int *zeros = calloc(nc.erasesize, 512+16);   // 16 bytes spare per sector

		assert(zeros);

		fprintf(stderr, "xnandinit_sectors=%d\n", opt_xnandinit);
		opt_seek *= 2;
		opt_xnandinit *= 2;

		assert((opt_seek & 0xff) == 0);
		assert((opt_xnandinit & 0xff) == 0);
		assert((opt_seek + opt_xnandinit) <= size);

		if (is_cavium()) sbuslock();
		/* XXX: assumes 128Kbyte erasesize */

		if (nc.erasesize==256) {
			for (j = opt_seek; j < (unsigned int)(opt_seek + opt_xnandinit); j += 256) {
				nanderase(&nc, xnand_sector(j), 256);
				nandwrite(&nc, xnand_sector(j),
				(unsigned char *)zeros, 256);
			}
		}
		else {
			for (j = opt_seek; j < (unsigned int)(opt_seek + opt_xnandinit); j += nc.erasesize) {
				nanderase(&nc, xnand_sector_512KB(j), nc.erasesize);
				nandwrite(&nc, xnand_sector_512KB(j),
				(unsigned char *)zeros, nc.erasesize);
			}
		}

		if (is_cavium()) sbusunlock();
		fprintf(stderr, "xnandinit_ok=%d\n", size > 0 ? 1 : 0);

		free(zeros);

		return 0;
	}

	if (opt_listbb) {
		int n, j, danger;
		unsigned int bbs[20];
		/* You might think there would be a comment here explaining
		 * the magic number ffff8f00 that appears below.  Suffice it
		 * to say that if two sector numbers are the same after
		 * applying that mask, it means they are part of the same
		 * RAID "area" in XNAND.  Multiple bad blocks in one area
		 * is bad, so if xnand_fail_danger is non-zero, the flash
		 * should be replaced before the unit is deployed.
		 */
		bzero(bbs, 80);
		danger = 0;


		if (is_cavium()) sbuslock();
		for (n = i = 0; i < size; i += nc.erasesize) { /* Assumes 128Kbyte (or 256*512) per block */

			if (is_sector_bad(&nc, i)) {
				if (is_cavium()) sbusunlock();
				bbs[n] = i & 0xffff8f00;
				j = 0;
				while (j < n) {
					if (bbs[j] == bbs[n]) danger++;
					j++;
				}

				fprintf(stderr, "bad_block%d=0x%x\n", n++, i);
				if (is_cavium()) sbuslock();
			} else if (is_cavium()) sbuspreempt();
		}
		if (is_cavium()) sbusunlock();
		fprintf(stderr, "xnand_fail_danger=%d\n", danger);
	}

	if (opt_writetest) {
		time_t start;
		unsigned char *buf, *bufpg;
		unsigned int j;

		buf = malloc(opt_blocksz * 512 + 4095);
		bufpg = (unsigned char *)(((unsigned int)buf + 4095) & ~0xfff);
		memset(bufpg, opt_writeset, opt_blocksz * 512);
		srandom(randomseed);
		fprintf(stderr, "write_byte=0x%x\n", opt_writeset);
		i = 0;
		j = 0;
		/* Synchronize to the next second transition */
		start = time(NULL);
		while (start == time(NULL));
		start = time(NULL);
		while (time(NULL) - start < 10) {
			if (is_cavium()) sbuslock();
			ret = nandwrite(&nc, j, bufpg, opt_blocksz);
			if (is_cavium()) sbusunlock();
			assert(ret == 0);
			i += opt_blocksz;
			if (opt_random) j = random() & sizemask;
			else {
				j = (j + opt_blocksz) & sizemask;
				if ((j + opt_blocksz) >= (unsigned int)size) j = 0;
			}
		}
		/* read assures previous writes are committed: */
      		if (is_cavium()) sbuslock();
      		ret = nandread(&nc, 0, bufpg, 1);
      		if (is_cavium()) sbusunlock();
      		assert(ret == 0);

		fprintf(stderr, "writetest_kbps=%d\n", i / 20);
		fprintf(stderr, "writetest_xfrps=%d\n",
		  i / 10 / opt_blocksz);
		free(buf);
	}

	if (opt_erase) {
		struct timeval tv1, tv2;
		int r, j;

		gettimeofday(&tv1, NULL);
		nc.os_ioerror = ignoreerror;
		if (is_cavium()) sbuslock();
		r = nanderase(&nc, opt_seek, opt_erase);
		if (is_cavium()) sbusunlock();
		gettimeofday(&tv2, NULL);
		j = (tv2.tv_sec - tv1.tv_sec) * 1000;
		j += tv2.tv_usec / 1000 - tv1.tv_usec / 1000;
		if (j <= 0) j = 1;
		fprintf(stderr, "erase_ok=%d\n", r ? 0 : 1);
		fprintf(stderr, "erase_kb=%d\n", opt_erase / 2);
		fprintf(stderr, "erase_kbps=%d\n", (opt_erase * 500 / j));
		nc.os_ioerror = NULL;
	}

	if (opt_readtest) {
		time_t start;
		unsigned char *buf, *bufpg;
		unsigned int j;
		int l = opt_blocksz * 512;

		buf = malloc(l + 8191);
		bufpg = (unsigned char *)(((unsigned int)buf + 4095) & ~0xfff);
		srandom(randomseed);
		bzero(bufpg, l);
		i = 0;
		j = 0;
		/* Synchronize to the next second transition */
		start = time(NULL);
		while (start == time(NULL));
		start = time(NULL);
		while (time(NULL) - start < 10) {
			if (is_cavium()) sbuslock();
			ret = nandread(&nc, j, bufpg, opt_blocksz);
			if (is_cavium()) sbusunlock();
			assert(ret == 0);
			i += opt_blocksz;

			if (opt_random) j = random() & sizemask;
			else {
				j = (j + opt_blocksz) & sizemask;
				if ((j + opt_blocksz) >= (unsigned int)size) j = 0;
			}
		}

		fprintf(stderr, "readtest_kbps=%d\n", i / 20);
		fprintf(stderr, "readtest_xfrps=%d\n",
		  i / 10 / opt_blocksz);
		fprintf(stderr, "readtest_eccfixups=%d\n", nc.eccfixups);
		fprintf(stderr, "xfixs=%d\n", nc.xfixs);
		fprintf(stderr, "scrubs=%d\n", nc.scrubs);
		free(buf);
	}

	if (opt_read) {
		unsigned char *buf, *bufpg;
		struct timeval tv1, tv2;
		unsigned int j;

		buf = malloc(opt_blocksz * 512 + 8191);
		assert(buf);
		bzero(buf, opt_blocksz * 512);
		bufpg = (unsigned char *)(((unsigned int)buf + 4095) & ~0xfff);
		srandom(randomseed);
		if (opt_readblks > (size / opt_blocksz) || opt_readblks == -1)
		  opt_readblks = (size / opt_blocksz);

		gettimeofday(&tv1, NULL);
		for (i = 0, j = opt_seek; i < opt_readblks; i++) {
			//if (is_cavium()) sbuslock();
			if ((j + opt_blocksz) > (unsigned int)size) {
			  fprintf(stderr, "This shouldn't happen!\n");
			  ret = nandread(&nc, j, bufpg, size - j);
			} else
			  ret = nandread(&nc, j, bufpg, opt_blocksz);
			//if (is_cavium()) sbusunlock();
			assert(ret == 0);
			ret = fwrite(bufpg, opt_blocksz * 512, 1, stdout);
			if (ret != 1) break;

			if (opt_random) j = (unsigned int)random() & sizemask;
			else j += opt_blocksz;
		}
		gettimeofday(&tv2, NULL);
		j = (tv2.tv_sec - tv1.tv_sec) * 1000;
		j += tv2.tv_usec / 1000 - tv1.tv_usec / 1000;
		if (j <= 0) j = 1;
		i = i * opt_blocksz;
		fprintf(stderr, "read_kb=%d\n", i / 2);
		fprintf(stderr, "read_kbps=%d\n", (i * 500 / j));
		fprintf(stderr, "read_eccfixups=%d\n", nc.eccfixups);
		fprintf(stderr, "xfixs=%d\n", nc.xfixs);
		fprintf(stderr, "scrubs=%d\n", nc.scrubs);
		free(buf);
	}

	if (opt_write) {
		unsigned char *buf, *bufpg;
		struct timeval tv1, tv2;
		unsigned int j;
		int errs=0;

		buf = malloc(opt_blocksz * 512 + 8191);
		bufpg = (unsigned char *)(((unsigned int)buf + 4095) & ~0xfff);
		memset(bufpg, opt_writeset, opt_blocksz * 512);
		srandom(randomseed);
		if (opt_writeblks > (size / opt_blocksz) || opt_writeblks == -1)
		  opt_writeblks = (size / opt_blocksz);
		gettimeofday(&tv1, NULL);
		if (opt_seek && ifile && !partseek) {
			ret = fseek(ifile, opt_seek * 512, SEEK_SET);
			if (ret == -1) for (i = 0; i < opt_seek; i++) {
				fread(bufpg, 512, 1, ifile);
			}
		}
		for (i = 0, j = opt_seek; i < opt_writeblks; i++) {
			if (ifile) {
				ret = fread(bufpg, opt_blocksz * 512, 1, ifile);
				if (ret == 0 && feof(ifile)) break;
				assert(ret == 1);
			}
			if (is_cavium()) sbuslock();
			ret = nandwrite(&nc, j, bufpg, opt_blocksz);
			if (is_cavium()) sbusunlock();
			if (ret != 0) {
				fprintf(stderr, "error%d=%d\n", errs++, j);
				if (is_cavium()) sbuslock();
				ret = nandwrite(&nc, j, bufpg, opt_blocksz);
				if (is_cavium()) sbusunlock();
			}
			if (ret != 0) {
				fprintf(stderr, "perm_write_error=1\n");
				exit(1);
			}
			if (opt_random) j = (unsigned int)random() & sizemask;
			else j += opt_blocksz;
		}
		/* read assures previous writes are committed: */
      		if (is_cavium()) sbuslock();
      		ret = nandread(&nc, 0, bufpg, 1);
      		if (is_cavium()) sbusunlock();
      		assert(ret == 0);

		gettimeofday(&tv2, NULL);
		j = (tv2.tv_sec - tv1.tv_sec) * 1000;
		j += tv2.tv_usec / 1000 - tv1.tv_usec / 1000;
		if (j <= 0) j = 1;
		i = i * opt_blocksz;
		fprintf(stderr, "write_kb=%d\n", i / 2);
		fprintf(stderr, "write_kbps=%d\n", (i * 500 / j));
		free(buf);
	}

	if (opt_audit) {
		unsigned char *buf, *bufpg, *buf2, *bufpg2;
		unsigned int block = 0, bad = 0, problem = 0, fixes = 0;
		unsigned int retry = 0, make_fix = 0;

		buf = malloc((nc.erasesize*512) + 8191);
		bufpg = (unsigned char *)(((unsigned int)buf + 4095) & ~0xfff);
		buf2 = malloc((nc.erasesize*512) + 8191);
		bufpg2 = (unsigned char *)(((unsigned int)buf + 4095) & ~0xfff);
		bzero(bufpg, (nc.erasesize*512));
		bzero(bufpg2, (nc.erasesize*512));

		fprintf(stderr, "Auditing XNAND data..(%d blocks)\n",
				size/nc.erasesize);
		while (block < (unsigned int)size/nc.erasesize) {
			fprintf(stderr, " [%4d/%4d]", block, size/nc.erasesize);
			fflush(stderr);
			fprintf(stderr, "\b\b\b\b\b\b\b\b\b\b\b\b");
			nc.fallbacks1 = nc.fallbacks2 = 0;
			nc.flags &= ~(NAND_TRY1 | NAND_TRY2);
			if (is_cavium()) sbuslock();
			ret = nandread(&nc, nc.erasesize*block, bufpg, nc.erasesize);
			if (is_cavium()) sbusunlock();
			assert (ret == 0);
			if (nc.fallbacks1) {
				fprintf(stderr,
				  "Unable to read primary block %d.", block);
				problem = 1;
			}
			nc.flags |= NAND_TRY1;
			if (is_cavium()) sbuslock();
			ret = nandread(&nc, nc.erasesize*block, bufpg2, nc.erasesize);
			if (is_cavium()) sbusunlock();
			nc.flags &= ~NAND_TRY1;
			if (!problem && (ret || nc.fallbacks2)) {
				fprintf(stderr,
				  "Unable to read parity data for block %d.",
				  block);
				problem = 2;
			}
			ret = memcmp(bufpg, bufpg2, nc.erasesize*512);
			if (!problem && ret) {
				fprintf(stderr,
				  "Found conflicting data for block %d.", block);
				problem = 1;
			}
			if(problem && retry) {
				fprintf(stderr, "\nUnable to fix.\n");
				bad++;
				block++;
				retry = 0;
				problem = 0;
			} else if (!problem && retry) {
				fprintf(stderr,
				  "Block %d fixed.\n", block);
				fixes++;
				block++;
				retry = 0;
			} else if (problem) {
				if (opt_fixyes) {
					fprintf(stderr, " Repairing..\n");
					make_fix = 1;
				} else if (opt_fixno) {
					fprintf(stderr, " Not repaired.\n");
					make_fix = 0;
				} else {
					fprintf(stderr, " Repair? (y/n) ");
					setTerm(0);
					ret = getchar();
					setTerm(1);
					make_fix = ret == 'y' || ret == 'Y';
					fprintf(stderr, "\n");
				}
				if (make_fix) {
					if (is_cavium()) sbuslock();
					ret = nandwrite(&nc, nc.erasesize*block,
							bufpg, nc.erasesize);
					if (is_cavium()) sbusunlock();
					assert (ret == 0);
					retry = 1;
					problem = 0;
				} else {
					block++;
					bad++;
					retry = 0;
					problem = 0;
				}
			} else { /* normal case, no problems: */
				block++;
			}
		}
		fprintf(stderr, "Audit complete.\n");
		fprintf(stderr, "Bad blocks=%d\n", bad);
		fprintf(stderr, "Repaired blocks=%d\n", fixes);
	}

	if (opt_stress) {
		unsigned char *buf, *bufpg, *buf2, *buf3;

		buf = malloc(0x20000 + 8191);
		buf2 = malloc(0x20000 + 8191);
		buf3 = malloc(0x20000 + 8191);
		bzero(buf, opt_blocksz * 512);
		bufpg = (unsigned char *)(((unsigned int)buf + 4095) & ~0xfff);
		rand_fill(buf2, 0x20000);
		rand_fill(buf3, 0x20000);

		fprintf(stderr, "Stressing block %d..\n", opt_stress);
		i=0;
		while(1) {
			if (i & 1) memcpy(bufpg, buf2, 0x20000);
	        	else memcpy(bufpg, buf3, 0x20000);
        		if (is_cavium()) sbuslock();
			ret = nanderase(&nc, opt_stress * 256, 256);
			if (is_cavium()) sbusunlock();
			assert(ret==0);
        		if (is_cavium()) sbuslock();
			ret = nandwrite(&nc, 256 * opt_stress, bufpg, 256);
			if (is_cavium()) sbusunlock();
			assert(ret==0);
        		if (is_cavium()) sbuslock();
			ret = nandread(&nc, 256 * opt_stress, bufpg, 256);
			if (is_cavium()) sbusunlock();
			assert(ret==0);
			if (i & 1) assert(memcmp(bufpg, buf2, 0x20000) == 0);
			else assert(memcmp(bufpg, buf3, 0x20000) == 0);
			i++;
			if ((i % 8192) == 0) fprintf(stderr, "%d\t%d\n",
			  i/8192, nc.eccfixups);
			if ((i & 3) == 0) {
				if (is_cavium()) sbuslock();
				poke16(0x62, peek16(0x62) ^ 0x8000);
				if (is_cavium()) sbusunlock();
			}
		}
	}

	if (opt_break) {
		unsigned char *buf, *bufpg;
		unsigned int b, j;

		buf = malloc(0x20000 + 8191);
		bzero(buf, opt_blocksz * 512);
		bufpg = (unsigned char *)(((unsigned int)buf + 4095) & ~0xfff);

		opt_break = opt_break & ~0x3;
		b = opt_break & ~0xff;
		fprintf(stderr, "b=%d\n", b);
		fprintf(stderr, "Breaking sectors %d-%d\n", opt_break,
		  opt_break + 3);
		assert(opt_xnand == 0);
		if (is_cavium()) sbuslock();
		ret = nandread(&nc, b, bufpg, 256);
		if (is_cavium()) sbusunlock();
		assert (ret == 0);
		if (is_cavium()) sbuslock();
		ret = nanderase(&nc, b, 256);
		if (is_cavium()) sbusunlock();
		assert (ret == 0);
		for(j = 0; j < 256; j += 4) if (j+b != (unsigned int)opt_break) {
			if (is_cavium()) sbuslock();
			ret = nandwrite(&nc, j+b, bufpg + j * 512, 4);
			if (is_cavium()) sbusunlock();
			assert (ret == 0);
		}
	}

	if (opt_hwtest) { /* XXX can't hardware test block 0 */
	unsigned char *buf, *bufpg, *buf2;
	unsigned int milestone, rewrites, rereads, looping, alive;

	buf = malloc(0x20000 + 8191);
	buf2 = malloc(0x20000 + 8191);
	bzero(buf, opt_blocksz * 512);
	bufpg = (unsigned char *)(((unsigned int)buf + 4095) & ~0xfff);
	looping = 1;
	rewrites = 0;
	while (looping) {
		while (rewrites % 1000 != 1) {
			rand_fill(buf2, 0x20000);
			memcpy(bufpg, buf2, 0x20000);
			if (is_cavium()) sbuslock();
			ret = nandwrite(&nc, opt_hwtest, bufpg, 256);
			if (is_cavium()) sbusunlock();
			assert (ret == 0);
			rewrites++;
			fprintf(stderr, "writes: %d\n", rewrites);
		}
		alive = 1;
		rereads = 0;
		milestone = 1;
		nc.eccfixups = 0;
		while (alive) {
			if (is_cavium()) sbuslock();
			ret = nandread(&nc, opt_hwtest, bufpg, 256);
			if (is_cavium()) sbusunlock();
			rereads++;
			if (ret) alive = 0;
			else if (memcmp(bufpg, buf2, 0x20000)) {
				fprintf(stderr,
				  "SILENT DATA CORRUPTION!!\n");
				alive = 0;
			} else if (nc.eccfixups >= milestone) {
				fprintf(stderr, "  fixup %d: %d\n",
				  milestone, rereads);
				milestone = milestone * 10;
			}
		}
		fprintf(stderr, "  Data corruption: %d\n", rereads);
		if (rereads < 10) looping = 0;
		}
	}

	if (opt_nbdserver) {
		nbdbufsz = opt_blocksz;
		nbdbuf = malloc(opt_blocksz * 512 + 8191);
		nbdbuf = (unsigned char *)(((unsigned int)nbdbuf + 4095) & ~0xfff);
		bzero(nbdbuf, opt_blocksz * 512);
		nbdserver(&nc, opt_nbdarg, opt_bind);
	}

	return 0;
}
