blob: 07b34cda50903bd2d520739fed9d73639042d959 [file] [log] [blame]
/*
* 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)
{
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)
{
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;
}