/* 
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) Hewlett-Packard (Paul Bame) paul_bame@hp.com
 */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include "load.h"
#include "palo.h"

#define B32(x)	__be32_to_cpu((x))

/* compute the sum of words in an 4-byte aligned region */
int
checksum(void *p, size_t len)
{
    int xsum = 0;
    int *x = (int *)p;
    int i;

    if (0) printf("checksum(%p, %u) = ", p, len);
    len /= 4;

    for (i = 0; i < len; i++)
    {
	xsum += B32(x[i]);
    }

    if (0) printf("0x%08x\n", xsum);

    return (xsum);
}

/* maximum allowed HP boot loader (IPL) size */
#define MAXBLSIZE (256 * 1024)

void
fix_bootloader(int out, int bootloader)
{
    size_t rblsize;		/* sector-rounded boot loader size */
    struct loadable loadable;
    int xsum1;
    void *blimage;
    int r;
    int wide;

    memset(&loadable, 0, sizeof loadable);

    r = prepare_loadable(bootloader, &loadable, &wide);
    if (!r)
    {
	fprintf(stderr, "Can't grok your bootloader executable format\n");
	exit(2);
    }

    rblsize = loadable.size;
    printf("entry 0x%08x size %d\n", loadable.entry, rblsize);
    rblsize += (FW_BLOCKSIZE - 1);
    rblsize &= ~(FW_BLOCKSIZE - 1);

    if (loadable.entry != loadable.first)
    {
	fprintf(stderr, "Entry point addres 0x%08x must be the same as the lowest"
		"address 0x%08x\n", loadable.entry, loadable.first);
	exit(2);
    }

    /* IPL max size is 256k */
    if (rblsize > MAXBLSIZE)
	error(5, loadable.size);

    /* load the boot loader into RAM */
    /* WORKAROUND: since the ELF32 value of p_memsz of the iplelf binary
     * is wrong, just use 5 times more memory here to avoid a segfault. */
    blimage = (void *)malloc(5*MAXBLSIZE);
    assert(blimage != NULL);
    if (!load_loadable(blimage, bootloader, &loadable))
	error(13);

    /* checksum it */
    xsum1 = checksum(blimage, rblsize);

    /* write checksum into RAM -- see ipl/crt0.S!!! */
    ((int *)blimage)[1] = B32(-xsum1);

    /* write it out */
    seekwrite(out, blimage, rblsize, 0);
    close(bootloader);

    free(blimage);
}

int
main(int argc, char *argv[])
{
    int bootloader = -1;
    int out = -1;

    switch(argc)
    {
    case 1:
	bootloader = fileno(stdin);
	out = fileno(stdout);
	break;
    case 2:
	if ((bootloader = open(argv[1], O_RDONLY)) == -1)
	{
	    perror(argv[1]);
	    return 2;
	}
	out = fileno(stdout);
	break;
    case 3:
	if ((bootloader = open(argv[1], O_RDONLY)) == -1)
	{
	    perror(argv[1]);
	    return 2;
	}
	if ((out = open(argv[2], O_RDWR|O_CREAT|O_TRUNC, 0666)) == -1)
	{
	    perror(argv[2]);
	    return 2;
	}
	break;
    default:
	fprintf(stderr, "Usage: %s [ipl [bootable_ipl]]\n", argv[0]);
	return 3;
    }

    fix_bootloader(out, bootloader);

    fsync(out);

    return 0;
}
