/* ************************************************************************** * * 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 * * 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 #include #include #include #include #include #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); }