/* 
 * 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 <stddef.h>
#include <asm/pdc.h>
#include "bootloader.h"
#undef PAGE0
#define	PAGE0	((struct zeropage *)0x00000000)

/*
 * Sequential seekread from boot device.
 * Return -1 if failed, or 0 if successful.
 */
static int pdc_bootdev_read(int fd,
			char *dest,
			unsigned int n,
			__u64 seek)
{
    unsigned long nbytes = 0;
    static __u64 devaddr = 0;

    if (1 && Debug) printf("pdc_bootdev_read(fd:%d, dest:0x%p, n:%u, seek:0x%llx)\n",
			fd, dest, n, seek);

    if ((unsigned int)dest & 0x3f) {
	printf("\nERROR: Boot device I/O buffer not properly aligned.\n");
	return -1;
    } else if (n % FW_BLOCKSIZE) {
	printf("\nERROR: Boot device read size not a multiple of 2048.\n");
	return -1;
    }

    if (PAGE0->mem_boot.cl_class == CL_RANDOM)
    {
	devaddr = seek;
    }
    else
    {

	/* check for rewind semantic */
	if (seek < devaddr)
	{
	    printf("NOTE: pdc_bootdev_read() asked to seek to 0x%llx from 0x%llx, rewinding...\n", seek, devaddr);
	    /* IODC needs devaddr 0 to do a rewind */
	    devaddr = 0;
	}

	while (devaddr < seek)
	{
	    __u64 nseek = seek - devaddr;
	    int count;
	    if (nseek > n)
		nseek = n;
	    if ((count = pdc_iodc_bootin(devaddr, dest, nseek)) < 0)
	    {
	        die("pdc_iodc_bootin() died during seekread\r\n");
		devaddr += nseek;
		if (devaddr > 30*1024*1024)	/* 30MB */
			printf("If you boot via tftp you probably reached the 32MB limit.\n");
		pdc_do_reset();
		return -1;
	    }
	    devaddr += count;
	}
    }

    while (nbytes < n)
    {
	int count;

	/* how many bytes should be read? */
	count = n - nbytes;

	if (Debug) printf("pdc_iodc_bootin(dev:0x%llx, buf:0x%p, count:%u) = ",
		devaddr, dest+nbytes, count);

	count = pdc_iodc_bootin(devaddr, dest + nbytes, count);

	if (Debug) printf("%d\r\n", count);

	if (0 && Debug)
	{
	    int i;
	    for (i = 0; i < 16; i++)
		printf(" %02x", dest[nbytes + i] & 0xff);
	    printf("\n");
	}

	if (count == 0)
	{
	    break;
	}
	else if (count > 0)
	{
	    if (devaddr >= seek)
	    {
		/* normal seekread */
		nbytes += count;
	    }
	    devaddr += count;
	}
	else
	{
	    /* this could happen after a partial good seekread, which will */
	    /* essentially be lost right now */
	    if (Debug) printf("\nERROR: Read from boot device failed (status = %d).\n",
		count);

	    devaddr += (n - nbytes);
	    return -1;
	}
    }

    return nbytes;
}

static void pdc_bootdev_describe(int fd, int *bufalign,
				    int *blocksize)
{
    if (bufalign != 0)
	*bufalign = disk_2gb_limit ? 64 : FW_BLOCKSIZE;

    if (blocksize != 0)
	*blocksize = FW_BLOCKSIZE;
}

/* returns true if OK */
int pdc_bootdev_open()
{
    return fileio_open(pdc_bootdev_describe, pdc_bootdev_read);
}
