/*
**************************************************************************
*
* Boot-ROM-Code to load an operating system across a TCP/IP network.
*
* Module: load.c
* Purpose: Get boot image from server
* Entries: load
*
**************************************************************************
*
* Copyright (C) 1995-2003 Gero Kuhlmann <gero@gkminix.han.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: load.c,v 1.6 2003/01/25 23:29:41 gkminix Exp $
*/
#include <general.h>
#include <memory.h>
#include <kernel/net.h>
#include <kernel/arpa.h>
#include <kernel/romlib.h>
#include <pxe/common.h>
#include "bootpriv.h"
#include "load.h"
/*
**************************************************************************
*
* Header modes:
*/
#define HDRMODE_DOS 0 /* DOS header mode */
#define HDRMODE_NETBOOT 1 /* Netboot header mode */
#define HDRMODE_PXE 2 /* PXE header mode */
/*
**************************************************************************
*
* Global variables:
*/
struct loadparams ldparams; /* TFTP read file parameters */
static struct imghdr headbuf; /* Temporary header buffer */
static struct loadrec *recp; /* Pointer to load record */
static unsigned long highmem; /* Top of conventional memory */
static unsigned long topmem; /* Top of memory */
static unsigned long laststart; /* Start of last memory block */
static unsigned long lastend; /* End of last memory block */
static unsigned long nextput; /* Address to put next block */
static unsigned long header; /* Linear address of header */
static unsigned long remaining; /* Remaining bytes in record */
static short hdrmode; /* Header mode */
static short ldmode; /* Loading mode */
static short quiet; /* Quiet flag */
static int recnum; /* Number of current record */
/*
**************************************************************************
*
* Put TFTP buffer into memory, either into conventional or extended
* memory.
* This routine requires the following global variables to be setup
* properly: nextput, highmem, topmem, header
*/
static int put_mem(buf, len, conv_only)
unsigned char *buf;
unsigned int len;
int conv_only;
{
unsigned int chunk_size;
register int ret = FALSE;
/* Check if destination pointer is valid */
if (nextput < (LOWMEM << 4) ||
(nextput >= header && nextput < (header + sizeof(struct imghdr))))
goto putmem_ret;
if (len == 0)
goto putmem_true;
/* Put block into lower memory */
chunk_size = len;
if (nextput < highmem) {
if ((nextput + len) >= highmem)
len = (int)(highmem - nextput);
#ifdef LDDEBUG
printf(" low=%lx, len=%u ", nextput, len);
#endif
if (!lmove(nextput, buf, len))
goto putmem_ret;
nextput += len;
buf += len;
chunk_size -= len;
if (chunk_size > 0)
nextput = (EXTMEM << 4);
} else if (nextput < (EXTMEM << 4))
goto putmem_ret;
/* Put block into high memory */
if (chunk_size > 0) {
if (conv_only || nextput >= topmem || nextput + chunk_size > topmem)
goto putmem_ret;
#ifdef LDDEBUG
printf(" high=%lx, len=%u ", nextput, len);
#endif
if (!lmove(nextput, buf, chunk_size))
goto putmem_ret;
nextput += chunk_size;
}
putmem_true:
ret = TRUE;
putmem_ret:
return(ret);
}
/*
**************************************************************************
*
* Decode record header and advance pointer to next record.
*/
static int next_record()
{
register struct loadrec *rp;
int offset;
if ((rp = recp) == NULL)
return(FALSE);
nextput = rp->lr_addr;
if (LR_IS_B0(rp->lr_flags)) {
if (LR_IS_B1(rp->lr_flags)) {
/*
* B0 = 1 && B1 = 1
* Load address is subtracted from the start of
* the last image loaded.
*/
if (nextput > laststart)
nextput = 0; /* will give error */
else
nextput = laststart - nextput;
} else {
/*
* B0 = 1 && B1 = 0
* Load address is added to the last byte of the memory area
* used by the previous image.
*/
nextput += lastend;
}
} else {
if (LR_IS_B1(rp->lr_flags)) {
/*
* B0 = 0 && B1 = 1
* Load address is subtracted from the last writable location
* in memory.
*/
if (nextput > topmem)
nextput = 0; /* will give error */
else
nextput = topmem - nextput;
} else {
/*
* B0 = 0 && B1 = 0
* Load address is used as is.
*/
}
}
laststart = nextput;
lastend = laststart + rp->lr_mlen;
remaining = rp->lr_ilen;
/* Compute pointer to next load record */
if (nextput == 0 ||
(offset = LR_HDRLEN(rp->lr_flags)) < sizeof(struct loadrec))
return(FALSE);
offset += LR_VENDLEN(rp->lr_flags);
if (!LR_IS_EOF(rp->lr_flags)) {
rp = (struct loadrec *)((unsigned char *)rp + offset);
if (((int)rp - (int)(&headbuf)) >= sizeof(struct imghdr))
return(FALSE);
} else
rp = NULL;
recp = rp;
return(TRUE);
}
/*
**************************************************************************
*
* Decode the header information. We have three different types of headers:
* - the header contains 0xaa/0x55 at the end of the block: this is the
* magic ID of a DOS boot sector, so load this sector into 0x07c00,
* and the rest starting at 0x10000, and then jump to 0x07c00
* - the header contains a magic word at the beginning of the block: this
* indicates a special header block with loading and starting information
* for the rest of the image (modes 2 and 3). If the RETURN flag is set
* in the header, this indicates that the loaded image will possibly
* use interrupts and services provided by the bootrom and also might
* return to the bootrom.
* - the header doesnt contain any special identification. Handle this
* as a PXE boot image file. It gets loaded at 0x07c00.
*/
static int decode_header(buf, len)
char *buf;
unsigned int len;
{
register struct imghdr *hp;
int offset;
int ret = PXENV_STATUS_SUCCESS;
/*
* Copy the header into a temporary buffer, so that we don't have to
* use far pointers.
*/
if (len > sizeof(headbuf))
len = sizeof(headbuf);
hp = &headbuf;
memcpy(hp, buf, len);
ldparams.header = 0L;
header = 0L;
/* First determine the mode of the header */
hdrmode = HDRMODE_PXE;
ldmode = LDMODE_KEEP_PXE;
if (len == sizeof(struct imghdr) && ldparams.tftp.buffer == 0) {
if (hp->ih_magic1 == IH_MAGIC1) {
hdrmode = HDRMODE_NETBOOT;
if (IH_KEEP_ALL(hp->ih_flags))
ldmode = LDMODE_KEEP_ALL;
else if (IH_KEEP_UNDI(hp->ih_flags))
ldmode = LDMODE_KEEP_UNDI;
else
ldmode = LDMODE_KEEP_NONE;
} else if (hp->ih_magic2 == IH_MAGIC2) {
hdrmode = HDRMODE_DOS;
ldmode = LDMODE_KEEP_NONE;
}
}
ldparams.mode = ldmode;
#ifdef LDDEBUG
printf("header mode %u, load mode %u,", hdrmode, ldmode);
#endif
/*
* For DOS or PXE modes setup all the parameters needed by put_mem. Use
* saved header block in order to restore it lateron at it's correct
* position.
*/
if (hdrmode != HDRMODE_NETBOOT) {
if (hdrmode == HDRMODE_DOS || ldparams.tftp.buffer == 0) {
nextput = far2long(BOOTBLOCK);
ldparams.exec = BOOTBLOCK;
} else {
nextput = ldparams.tftp.buffer;
highmem = topmem = nextput + ldparams.tftp.bufsize;
ldparams.exec = long2far(nextput);
}
if (!put_mem((unsigned char *)hp, len, FALSE))
ret = PXENV_STATUS_MCOPY_PROBLEM;
else if (hdrmode == HDRMODE_DOS)
nextput = DOSEXTMEM << 4;
return(ret);
}
/*
* With netboot mode first save the header into it's final place. Then
* setup the first record pointer.
*/
nextput = laststart = far2long(hp->ih_locn);
#ifdef LDDEBUG
printf("header=%lx, ", nextput);
#endif
if (!put_mem((unsigned char *)hp, len, TRUE))
ret = PXENV_STATUS_MCOPY_PROBLEM;
header = laststart;
lastend = laststart + len;
ldparams.header = hp->ih_locn;
ldparams.exec = hp->ih_execute;
#ifdef LDDEBUG
printf("headerend=%lx, ", lastend);
#endif
offset = IH_HDRLEN(hp->ih_flags) + IH_VENDLEN(hp->ih_flags);
if (offset < IH_HEADERSIZE ||
offset >= sizeof(struct imghdr) - sizeof(struct loadrec) - 2)
ret = PXENV_STATUS_IMAGE_INVALID;
if (ret == PXENV_STATUS_SUCCESS) {
recp = (struct loadrec *)((unsigned char *)hp + offset);
#ifdef LDDEBUG
printf("record=%x\n", recp);
#endif
if (!next_record())
ret = PXENV_STATUS_IMAGE_INVALID;
}
return(ret);
}
/*
**************************************************************************
*
* Load boot image from server
*/
int load(quietflag)
int quietflag;
{
unsigned int len, blocknum, xlen;
register unsigned char *cp;
int ret = PXENV_STATUS_SUCCESS;
/* Setup global values */
recp = NULL;
recnum = 0;
remaining = 0L;
quiet = quietflag;
if (!quiet)
printf("Loading %ls\n", ldparams.tftp.filename,
sizeof(ldparams.tftp.filename));
#ifndef NODISK
/* Load a bootblock from disk if requested */
if (!memcmp(ldparams.tftp.filename, "/dev/", 5)) {
ldparams.mode = LDMODE_KEEP_NONE;
if (ldparams.tftp.buffer == 0)
ldparams.exec = BOOTBLOCK;
else if (ldparams.tftp.bufsize >= BOOTBSIZE)
ldparams.exec = ldparams.tftp.buffer;
else
ret = PXENV_STATUS_TFTP_TOO_MANY;
if (ret == PXENV_STATUS_SUCCESS)
ret = loaddisk(&(ldparams.tftp.filename[5]), ldparams.exec,
&(ldparams.header), &(ldparams.drive));
goto load_ret;
}
#endif
/* Determine top of memory pointers */
highmem = convmem();
if ((topmem = extmem() + (EXTMEM << 4)) == (EXTMEM << 4))
topmem = highmem;
#ifdef LDDEBUG
printf("highmem=%lx topmem=%lx\n", highmem, topmem);
#endif
/* Open the TFTP connection */
if ((ret = tftp_open(ldparams.tftp.server,
ldparams.tftp.gateway, ldparams.tftp.filename,
sizeof(ldparams.tftp.filename))) != PXENV_STATUS_SUCCESS)
goto load_ret;
if (!quiet) {
printf("Options: Blocksize %u", tftpbufsize);
if (filesize != -1L)
printf(", Filesize %lu", filesize);
if (srvrtout != -1L)
printf(", Timeout %lu", srvrtout);
printf("\n");
}
/* Read the image file header */
if ((ret = tftp_get()) == PXENV_STATUS_SUCCESS &&
(ret = decode_header(tftpbuf, tftpbuflen)) == PXENV_STATUS_SUCCESS) {
len = tftpbuflen - sizeof(struct imghdr);
cp = tftpbuf + sizeof(struct imghdr);
blocknum = 1;
if (!quiet)
printf("Block 1 ");
}
/* Read all blocks of image file */
while (ret == PXENV_STATUS_SUCCESS) {
if (len == 0) {
if (tftpbuflen < tftpbufsize ||
(ret = tftp_get()) != PXENV_STATUS_SUCCESS)
break;
len = tftpbuflen;
cp = tftpbuf;
blocknum++;
if (!quiet)
printf("\rBlock %u ", blocknum);
}
if (hdrmode != HDRMODE_NETBOOT) {
if (!put_mem(cp, len, FALSE))
ret = PXENV_STATUS_MCOPY_PROBLEM;
len = 0;
continue;
}
/* Handle netboot header mode loading. */
if (len > remaining) {
if (!put_mem(cp, (unsigned int)remaining, FALSE)) {
ret = PXENV_STATUS_MCOPY_PROBLEM;
break;
}
cp += (unsigned int)remaining;
len -= (unsigned int)remaining;
if (!next_record())
break;
} else {
if (!put_mem(cp, len, FALSE)) {
ret = PXENV_STATUS_MCOPY_PROBLEM;
break;
}
remaining -= len;
if (remaining == 0L && !next_record())
break;
len = 0;
}
}
/* Check for errors and close the TFTP connection */
if (ret == PXENV_STATUS_SUCCESS && (recp != NULL || remaining != 0L))
ret = PXENV_STATUS_IMAGE_INVALID;
if (ret == PXENV_STATUS_SUCCESS)
ret = tftp_close();
else
tftp_close();
load_ret:
if (!quiet) {
if (ret == PXENV_STATUS_SUCCESS)
printf("ok\n");
}
return(ret);
}
syntax highlighted by Code2HTML, v. 0.9.1