/*======================================================================

    Utility to create an FTL partition in a memory region

    Written by David Hinds, dhinds@allegro.stanford.edu

    ftl_format.c 1.3 1996/11/26 07:24:56

======================================================================*/

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/stat.h>

#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/ftl.h>
#include <pcmcia/memory.h>

/*====================================================================*/

static void print_size(u_long s)
{
    if ((s > 0x100000) && ((s % 0x100000) == 0))
	printf("%ld mb", s / 0x100000);
    else if ((s > 0x400) && ((s % 0x400) == 0))
	printf("%ld kb", s / 0x400);
    else
	printf("%ld bytes", s);
}

/*====================================================================*/

static const char LinkTarget[] = {
    0x13, 0x03, 'C', 'I', 'S'
};
static const char DataOrg[] = {
    0x46, 0x39, 0x00, 'F', 'T', 'L', '1', '0', '0', 0x00
};

static void build_header(erase_unit_header_t *hdr, u_long RegionSize,
			 u_long BlockSize, u_long Spare, int Reserve,
			 u_long BootSize)
{
    u_long BootUnits;
    
    /* Default everything to the erased state */
    memset(hdr, 0xff, sizeof(*hdr));
    memcpy(hdr->LinkTargetTuple, LinkTarget, 5);
    memcpy(hdr->DataOrgTuple, DataOrg, 10);
    hdr->EndTuple[0] = hdr->EndTuple[1] = 0xff;
    BootSize = (BootSize + (BlockSize-1)) & ~(BlockSize-1);
    BootUnits = BootSize / BlockSize;
    
    /* We only support 512-byte blocks */
    hdr->BlockSize = 9;
    hdr->EraseUnitSize = 0;
    while (BlockSize > 1) {
	hdr->EraseUnitSize++;
	BlockSize >>= 1;
    }
    hdr->EraseCount = 0;
    hdr->FirstPhysicalEUN = BootUnits;
    hdr->FormattedSize =
	RegionSize - ((Spare + BootUnits) << hdr->EraseUnitSize);
    hdr->FormattedSize -= ((hdr->FormattedSize * Reserve / 100) & ~0xfff);
    hdr->NumEraseUnits = (RegionSize - BootSize) >> hdr->EraseUnitSize;
    hdr->NumTransferUnits = Spare;
    hdr->FirstVMAddress = 0xffffffff;
    hdr->NumVMPages = 0;
    hdr->Flags = 0;
    /* hdr->Code defaults to erased state */
    hdr->SerialNumber = time(NULL);
    /* hdr->AltEUHOffset defaults to erased state */
    /* Leave a little bit of space between the CIS and BAM */
    hdr->BAMOffset = 0x80;

} /* build_header */

/*====================================================================*/

static int format_partition(int fd, int quiet, int interrogate,
			    u_long spare, int reserve, u_long bootsize)
{
    region_info_t region;
    erase_info_t erase;
    erase_unit_header_t hdr;
    u_long step, lun, i, nbam, *bam;
    
    /* Get partition size, block size */
    if (ioctl(fd, MEMGETINFO, &region) != 0) {
	perror("get info failed");
	return -1;
    }
	
    /* Create header */
    build_header(&hdr, region.RegionSize, region.BlockSize,
		 spare, reserve, bootsize);

    if (!quiet) {
	printf("Partition size = ");
	print_size(region.RegionSize);
	printf(", erase unit size = ");
	print_size(region.BlockSize);
	printf(", %ld tranfer units\n", spare);
	if (bootsize != 0) {
	    print_size(hdr.FirstPhysicalEUN << hdr.EraseUnitSize);
	    printf(" allocated for boot image\n");
	}
	printf("Reserved %d%%, formatted size = ", reserve);
	print_size(hdr.FormattedSize);
	printf("\n");
	fflush(stdout);
    }

    if (interrogate) {
	char str[3];
	printf("This will destroy all data on the target device.  "
	       "Confirm (y/n): ");
	if (fgets(str, 3, stdin) == NULL)
	    return -1;
	if ((strcmp(str, "y\n") != 0) && (strcmp(str, "Y\n") != 0))
	    return -1;
    }
    
    /* Create basic block allocation table for control blocks */
    nbam = ((region.BlockSize >> hdr.BlockSize) * sizeof(u_long)
	    + hdr.BAMOffset + (1 << hdr.BlockSize) - 1) >> hdr.BlockSize;
    bam = malloc(nbam * sizeof(u_long));
    for (i = 0; i < nbam; i++)
	bam[i] = BLOCK_CONTROL;
    
    /* Erase partition */
    if (!quiet) {
	printf("Erasing all blocks...\n");
	fflush(stdout);
    }
    erase.Size = region.BlockSize;
    erase.Offset = region.BlockSize * hdr.FirstPhysicalEUN;
    for (i = 0; i < hdr.NumEraseUnits; i++) {
	if (ioctl(fd, MEMERASE, &erase) != 0) {
	    if (!quiet) {
		putchar('\n');
		fflush(stdout);
	    }
	    perror("block erase failed");
	    return -1;
	}
	erase.Offset += erase.Size;
	if (!quiet) {
	    if (region.RegionSize <= 0x800000) {
		if (erase.Offset % 0x100000) {
		    if (!(erase.Offset % 0x20000)) putchar('-');
		}
		else putchar('+');
	    }
	    else {
		if (erase.Offset % 0x800000) {
		    if (!(erase.Offset % 0x100000)) putchar('+');
		}
		else putchar('*');
	    }
	    fflush(stdout);
	}
    }
    if (!quiet) putchar('\n');

    /* Prepare erase units */
    if (!quiet) {
	printf("Writing erase unit headers...\n");
	fflush(stdout);
    }
    lun = 0;
    /* Distribute transfer units over the entire region */
    step = (spare) ? (hdr.NumEraseUnits/spare) : (hdr.NumEraseUnits+1);
    for (i = 0; i < hdr.NumEraseUnits; i++) {
	u_long ofs = (i + hdr.FirstPhysicalEUN) << hdr.EraseUnitSize;
	if (lseek(fd, ofs, SEEK_SET) == -1) {
	    perror("seek failed");
	    break;
	}
	/* Is this a transfer unit? */
	if (((i+1) % step) == 0)
	    hdr.LogicalEUN = 0xffff;
	else {
	    hdr.LogicalEUN = lun;
	    lun++;
	}
	if (write(fd, &hdr, sizeof(hdr)) == -1) {
	    perror("write failed");
	    break;
	}
	if (lseek(fd, ofs + hdr.BAMOffset, SEEK_SET) == -1) {
	    perror("seek failed");
	    break;
	}
	if (write(fd, bam, nbam * sizeof(u_long)) == -1) {
	    perror("write failed");
	    break;
	}
    }
    if (i < hdr.NumEraseUnits)
	return -1;
    else
	return 0;
} /* format_partition */

/*====================================================================*/

void main(int argc, char *argv[])
{
    int quiet, interrogate, reserve;
    int optch, errflg, fd, ret;
    u_long spare, bootsize;
    char *s;
    extern char *optarg;
    struct stat buf;

    quiet = 0;
    interrogate = 0;
    spare = 1;
    reserve = 5;
    errflg = 0;
    bootsize = 0;
    
    while ((optch = getopt(argc, argv, "qis:b:")) != -1) {
	switch (optch) {
	case 'q':
	    quiet = 1; break;
	case 'i':
	    interrogate = 1; break;
	case 's':
	    spare = strtoul(optarg, NULL, 0); break;
	case 'r':
	    reserve = strtoul(optarg, NULL, 0); break;
	case 'b':
	    bootsize = strtoul(optarg, &s, 0);
	    if ((*s == 'k') || (*s == 'K'))
		bootsize *= 1024;
	    break;
	default:
	    errflg = 1; break;
	}
    }
    if (errflg || (optind != argc-1)) {
	fprintf(stderr, "usage: %s [-q] [-i] [-s spare-blocks]"
		" [-r reserve-percent] [-b bootsize] device\n", argv[0]);
	exit(EXIT_FAILURE);
    }

    if (stat(argv[optind], &buf) != 0) {
	perror("status check failed");
	exit(EXIT_FAILURE);
    }
    if (!(buf.st_mode & S_IFCHR)) {
	fprintf(stderr, "%s is not a character special device\n",
		argv[optind]);
	exit(EXIT_FAILURE);
    }
    fd = open(argv[optind], O_RDWR);
    if (fd == -1) {
	perror("open failed");
	exit(EXIT_FAILURE);
    }

    ret = format_partition(fd, quiet, interrogate, spare, reserve,
			   bootsize);
    if (!quiet) {
	if (ret)
	    printf("format failed.\n");
	else
	    printf("format successful.\n");
    }
    close(fd);
    
    exit((ret) ? EXIT_FAILURE : EXIT_SUCCESS);
}
