/* 
 * 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 "bootloader.h"

/* A bunch of little routines to make reading the boot device easier.
 *    Caller can seekread into unaligned buffers
 *      Caller can seekread arbitrary amounts of data
 *      Caller can sort-of pretend they have a random-access device,
 *              as long as they only "seek" forwards.
 *      Caller need not keep track of where they are as much as before.
 *      Caller doesn't need to handle error returns -- there aren't any
 *              because a good error recovery isn't designed in, nor
 *              does bootdev_sread() check/recover in some cases I think.
 *
 *                                      -Paul Bame
 */

/* this struct should be considered private except to b2b.c */
struct bs
{
    short fd;	/* fd of underlying device */

    /* block size of underlying device */
    int blocksize;
    /* alignment for reads from device */
    int align;

    /* data in 'bd_readbuf' was seekread from boot device offset 'bd_bufoffset' */
    __u64 devoffset;

    /* pointer to the properly-aligned 'blocksize' buffer */
    char *readbuf;
};

static struct bs *blockio = 0;

static int
delivercache(struct bs *b, char **buf, __u64 *devaddr, __u64 endaddr)
{
    __u64 block = *devaddr & ~((__u64)b->blocksize - 1);
    int offset = *devaddr - block;
    int n = 0;

    if (block == b->devoffset)		/* anything useful in the cache? */
    {
	n = b->blocksize - offset;	/* we could deliver this many bytes */
	if (n > endaddr - *devaddr)	/* user might want fewer bytes */
	    n = endaddr - *devaddr;

	/* copy into user's buffer */
	memcpy(*buf, b->readbuf + offset, n);
	if (0 && Debug) printf("delivercache: %d bytes from devaddr 0x%llx to 0x%p\r\n",
		n, *devaddr, *buf);

	*devaddr += n;
	*buf += n;
    }

    return n;
}

static int
cacheblock(struct bs *b, __u64 devaddr)
{
    int n;
    __u64 block = devaddr & ~((__u64)b->blocksize - 1);

    if ((n = seekread(b->fd, b->readbuf, b->blocksize, block)) != b->blocksize)
    {
	if (Debug)
		printf("byteio_read: seekread() returned %d expected %d\n",
			n, b->blocksize);
	    
	return 0;
    }
    b->devoffset = block;
    if (0 && Debug) printf("cacheblock: cached block at devaddr 0x%llx\r\n", devaddr);
    return 1;
}

static int byteio_read(int fd, char *buf, unsigned count, __u64 devaddr)
{
    struct bs *b = &blockio[fd];
    __u64 endaddr = devaddr + count;
    __u64 remember = devaddr;

    if (Debug) printf("byteio_read(fd:%d, buf:%p, count:%u, devaddr:0x%llx)\r\n",
	fd, buf, count, devaddr);

    while (devaddr < endaddr)
    {
	int n, bigread;
	char *alignedbuf;

	if (0 && Debug) printf("b_r: buf:0x%p devaddr 0x%llx %s endaddr 0x%llx\r\n",
		buf, devaddr, 
		(devaddr & (b->blocksize - 1)) == 0 ? "aligned" : "unaligned",
		endaddr);

	if (delivercache(b, &buf, &devaddr, endaddr) > 0)
	    continue;

	/* if not block aligned, use the cache */
	if ((devaddr & (b->blocksize - 1)) != 0)
	{
	    if (!cacheblock(b, devaddr))
	    	return -1;
	    continue;
	}

	/* devaddr is aligned.  If < 2 blocks remain, just use the cache */
	if (endaddr - devaddr < b->blocksize * 2)
	{
	    if (!cacheblock(b, devaddr))
	    	return -1;
	    continue;
	}

	/* devaddr is aligned and there are >= 2 blocks remaining */
	bigread = endaddr - devaddr;

	/* user's buf is probably not aligned */
	alignedbuf = (char *)(((unsigned int) buf + (b->align - 1)) &
			~(b->align - 1));

	/* alignment loss */
	bigread -= alignedbuf - buf;

	/* must be a multiple of blocksize */
	bigread &= ~((__u64)b->blocksize - 1);

	if (Debug) printf("reading %u bytes from dev 0x%llx to ubuf %p\r\n",
				bigread, devaddr, alignedbuf);
	/* read the blocks into user's buf */
	if ((n = seekread(b->fd, alignedbuf, bigread, devaddr))
		    != bigread)
	{
	    if (Debug)
		printf("byteio_read: seekread() returned %d expected %d\n",
			n, bigread);
		
	    return -1;
	}

	if (buf != alignedbuf)
	{
	    /* move user's data to the right place in their buffer */
	    if (Debug) printf("moving %u bytes from %p to %p\r\n", n, alignedbuf, buf);
	    memcpy(buf, alignedbuf, n);
	}
	devaddr += n;
	buf += n;
    }

    return devaddr - remember;
}

static void byteio_describe(int fd, int *bufalign,
				int *blocksize)
{
    if (bufalign != 0)
	*bufalign = 1;

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

/* returns true if OK */
int byteio_open(int otherfd)
{
    int fd = fileio_open(byteio_describe, byteio_read);

    if (blockio == 0)
    {
	int sz = sizeof blockio[0] * MAX_FD;
	blockio = (struct bs *)malloc(sz);
    }

    if (fd >= 0)
    {
	struct bs *b = &blockio[fd];

	memset(b, 0, sizeof *b);
	b->fd = otherfd;
	describe(otherfd, &b->align, &b->blocksize);

	b->readbuf = malloc_aligned(b->blocksize, b->align);

	if (!cacheblock(b, 0))
	    die("initial read failed");
    }

    return fd;
}
