oskit/oskit-20020317/boot/pxe/main.c

787 lines
17 KiB
C
Executable File

/*
* Copyright (c) 2000, 2001 University of Utah and the Flux Group.
* All rights reserved.
*
* This file is part of the Flux OSKit. The OSKit is free software, also known
* as "open source;" you can redistribute it and/or modify it under the terms
* of the GNU General Public License (GPL), version 2, as published by the Free
* Software Foundation (FSF). To explore alternate licensing terms, contact
* the University of Utah at csl-dist@cs.utah.edu or +1-801-585-3271.
*
* The OSKit 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 GPL for more details. You should have
* received a copy of the GPL along with the OSKit; see the file COPYING. If
* not, write to the FSF, 59 Temple Place #330, Boston, MA 02111-1307, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <oskit/x86/pc/reset.h>
#include <oskit/x86/i16.h>
#include <oskit/x86/base_trap.h>
#include <oskit/x86/base_vm.h>
#include <oskit/x86/pc/base_i16.h>
#include <oskit/x86/base_gdt.h>
#include <oskit/error.h>
#include <oskit/exec/exec.h>
#include <oskit/lmm.h>
#include <oskit/machine/phys_lmm.h>
#include <oskit/diskpart/pcbios.h>
#include <oskit/boot/bootwhat.h>
#include <oskit/console.h>
#include "boot.h"
#include "pxe.h"
#include "decls.h"
#include "udp.h"
/*
* Define to have the default behaviour for not having server bootinfo
* be to run the "whoami" kernel instead of booting from the disk.
*
* We use this for initially setting up testbed nodes.
*/
#undef RUNWHOMAI
/*
* Define to use BIOS extended disk read operation
* (when available)
*/
#undef USEEDD
/*
* BIOS drive number to read from
* 0x80 == HDD 0
*/
#define BIOSDISK 0x80
/*
* Our segment number
* XXX always zero, just a constant for clarity
*/
#define OURSEG 0
/*
* For now, hardwired.
*/
#define NETBOOT "/tftpboot/netboot"
/*
* Many retries in case network is congested.
*/
#define BOOTINFO_RETRY 20
/*
* Defined in crt0.S
*/
extern unsigned int pxenv_segment; /* Segment of PXENV+ */
extern unsigned int pxenv_offset; /* Offset of PXENV+ */
extern SEGOFF16_t pxe_entrypoint; /* Entrypoint (16bit) of PXE */
/*
* PXENV+ and !PXE pointers.
*/
static pxe_t *pxep;
static pxenv_t *pxenvp;
/*
* Our DHCP information.
*/
struct in_addr myip, serverip, gatewayip;
/* Forward decls */
static int boot_mbr_default(void);
static int boot_mbr_sysid(int systemid);
static int boot_mbr_partition(int partition);
static void boot_system(void);
static void boot_interactive(void);
void delay(int);
int biosdprobe(int);
int biosdread(int, int, int, int, int, void *);
void biosdreset(int);
static int parse_multiboot_spec(char *,
struct in_addr *, char **, char **);
extern char version[], build_info[];
#ifdef USEEDD
int bioseddread(int, int, int, void *);
#endif
int
main(int argc, char **argv)
{
BOOTPLAYER bootplayer;
t_PXENV_GET_CACHED_INFO cacheinfo;
printf("\n%s\n", version);
printf("%s\n", build_info);
/*
* Construct 32 bit addresses for these two.
*/
pxenvp = (pxenv_t *) ((pxenv_segment << 4) + pxenv_offset);
pxep = (pxe_t *) ((pxenvp->PXEPtr.segment << 4) +
pxenvp->PXEPtr.offset);
DPRINTF("PXENV+ at %p, !PXE at %p\n", pxenvp, pxep);
/*
* Check that what we think is !PXE, really is.
*/
if (strncmp("!PXE", pxep->Signature, sizeof(pxep->Signature)))
PANIC("Failed the \"!PXE\" test!");
/*
* Suck out the 16bit PXE entrypoint and stash for the trampoline
* code to find for the intersegment call to the PXE.
*/
pxe_entrypoint = pxep->EntryPointSP;
DPRINTF("PXE Entrypoint is %x:%x\n",
pxep->EntryPointSP.segment, pxep->EntryPointSP.offset);
/*
* Get the information we need from the PXE to find the boot
* server. This is the response to the last DHCP request, which
* provided the name of this program.
*/
cacheinfo.Status = 0;
cacheinfo.PacketType = PXENV_PACKET_TYPE_BINL_REPLY;
cacheinfo.BufferSize = sizeof(bootplayer);
cacheinfo.Buffer.segment = PXESEG(&bootplayer);
cacheinfo.Buffer.offset = PXEOFF(&bootplayer);
pxe_invoke(PXENV_GET_CACHED_INFO, &cacheinfo);
assert(!cacheinfo.Status);
/* Stash */
myip.s_addr = ((struct in_addr *) &bootplayer.cip)->s_addr;
serverip.s_addr = ((struct in_addr *) &bootplayer.sip)->s_addr;
gatewayip.s_addr = ((struct in_addr *) &bootplayer.gip)->s_addr;
/*
* Print some useful info.
*/
{
char cip[16], sip[16], gip[16];
strcpy(cip, inet_ntoa(myip));
strcpy(sip, inet_ntoa(serverip));
strcpy(gip, inet_ntoa(gatewayip));
printf("DHCP Info: IP: %s, Server: %s, Gateway: %s\n",
cip, sip, gip);
}
boot_system();
/*
* We don't want to go through the i16 exit since that
* waits for a character on the console before reboot!
*/
pc_reset();
return 0;
}
void
boot_system(void)
{
boot_info_t boot_info;
boot_what_t *boot_whatp;
int success, i, j, err, actual, port;
int badpkts;
struct in_addr ip;
/*
* Give the user a few seconds to interrupt and go into
* interactive mode. If we return, go through the auto
* boot and hope it works.
*/
printf("Type a key for interactive mode (quick, quick!)\n");
for (i = 0; i < 1000; i++) {
if (console_trygetchar() < 0)
delay(10);
else {
boot_interactive();
break;
}
}
err = udp_open(myip.s_addr);
if (err)
goto mbr;
/*
* Try a bunch of times.
*/
success = badpkts = 0;
for (i = 0; i < BOOTINFO_RETRY; i++) {
memset(&boot_info, 0, sizeof(boot_info));
boot_info.opcode = BIOPCODE_BOOTWHAT_REQUEST;
printf("Sending boot info request %d\n", i);
err = udp_write(serverip.s_addr, gatewayip.s_addr,
BOOTWHAT_SRCPORT, BOOTWHAT_DSTPORT,
&boot_info, sizeof(boot_info));
if (err)
goto mbr;
/*
* UDP read is nonblocking, so wait for a few seconds.
*/
for (j = 0; j < 100; j++) {
err = udp_read(myip.s_addr, BOOTWHAT_SRCPORT,
&boot_info, sizeof(boot_info),
&actual, &port, &ip.s_addr);
if (err) {
delay(100);
continue;
}
if (port != BOOTWHAT_DSTPORT) {
badpkts++;
continue;
}
if (boot_info.opcode == BIOPCODE_BOOTWHAT_REPLY) {
if (boot_info.status == BISTAT_FAIL)
printf("No bootinfo for us on server\n");
else
success = 1;
goto gotit;
}
printf("Bad reply from server\n");
delay(100);
}
}
printf("No reply from server\n");
if (badpkts > 100)
printf("Got %d non-bootinfo packets\n", badpkts);
gotit:
err = udp_close();
if (err)
goto mbr;
if (!success)
goto mbr;
/*
* Got a seemingly valid boot info packet that says what to do.
*/
boot_whatp = (boot_what_t *) &boot_info.data;
err = 0;
switch (boot_whatp->type) {
case BIBOOTWHAT_TYPE_MB:
err = load_multiboot_image(&boot_whatp->what.mb.tftp_ip,
boot_whatp->what.mb.filename,
boot_whatp->cmdline);
break;
case BIBOOTWHAT_TYPE_PART:
err = boot_mbr_partition(boot_whatp->what.partition);
break;
case BIBOOTWHAT_TYPE_SYSID:
err = boot_mbr_sysid(boot_whatp->what.sysid);
default:
PANIC("Invalid type: %d\n", boot_whatp->type);
break;
}
/*
* Well, the only way to get here is if the specified boot failed.
* Fall through to booting the default, and hope it works.
*/
mbr:
#ifdef RUNWHOAMI
{
struct in_addr ip;
inet_aton("155.101.128.70", &ip);
load_multiboot_image(&ip, "/tftpboot/whoami", "");
}
#else
boot_mbr_default();
#endif
}
/*
* Assert support that uses way less space
*/
void
local_panic(void)
{
extern void dump_stack_trace();
printf("ASSERT FAILED!\n");
dump_stack_trace();
boot_mbr_default();
}
static int pxe_isshutdown;
/*
* Get ready to invoke the kernel. Must clean up all the PXE goo.
*/
void
pxe_cleanup(void)
{
t_PXENV_UNDI_SHUTDOWN undi_shutdown;
t_PXENV_UNLOAD_STACK unload_stack;
memset(&undi_shutdown, 0, sizeof(undi_shutdown));
memset(&unload_stack, 0, sizeof(unload_stack));
pxe_invoke(PXENV_UNDI_SHUTDOWN, &undi_shutdown);
assert(! undi_shutdown.Status);
pxe_invoke(PXENV_UNLOAD_STACK, &unload_stack);
assert(! unload_stack.Status);
pxe_isshutdown = 1;
}
/*
* This is the first part of a trampoline into the PXE code.
* Put the processor into 16bit/real mode, and then call the
* little assembly stub that will invoke the PXE like this:
*
* PXE(our data segment, operation code, argument/return block pointer);
*/
void
pxe_invoke(int routine, void *data)
{
extern long pxe_routine;
extern long pxe_databucket;
extern int i16_pxe_invoke(void);
pxe_routine = routine;
pxe_databucket = (long) data;
do_16bit(KERNEL_CS, KERNEL_16_CS,
base_i16_switch_to_real_mode();
i16_pxe_invoke();
base_i16_switch_to_pmode();
);
}
/*
* We need a trapstate to invoke bios calls.
*/
static struct trap_state ts;
/*
* Load the Master Boot Record into the magic location (which happens
* to be _start) using the BIOS readdisk routine.
*/
static void
load_mbr(void)
{
extern char _start[];
int *foo = (int *) _start;
int rv = 0, retry;
int doesedd;
DPRINTF("Calling pxe_cleanup ...\n");
pxe_cleanup();
DPRINTF("Calling biosdread to overwrite _start ...\n");
doesedd = biosdprobe(BIOSDISK);
memset(foo, 0, 512);
for (retry = 0; retry < 3; retry++) {
#ifdef USEEDD
if (doesedd)
rv = bioseddread(BIOSDISK, 0, 1, foo);
else
#endif
rv = biosdread(BIOSDISK, 0, 0, 1, 1, foo);
if (rv == 0)
break;
biosdreset(BIOSDISK);
}
if (rv != 0)
printf("read of MBR failed, err=%x\n", rv);
}
/*
* Check sanity of the MBR.
*/
static int
check_mbr(void)
{
extern char _start[];
bios_label_t *label;
struct bios_partition_info *entry;
int i;
label = (bios_label_t *) (_start + BIOS_LABEL_BYTE_OFFSET);
if (label->magic != BIOS_LABEL_MAGIC)
goto verybad;
entry = (struct bios_partition_info *)
(_start + BIOS_LABEL_BYTE_OFFSET);
for (i = 0; i < 4; i++, entry++) {
if (!entry->n_sectors)
continue;
if (entry->bootid == BIOS_BOOTABLE)
return 1;
}
verybad:
printf("MBR IS BAD! Rebooting ...\n");
pc_reset();
return 0;
}
/*
* After cleaning up the PXE, switch to 16bit/real mode, and then jump
* into the MBR. As long as there is a MBR, and its intact, something
* good should happen (say, Lilo will appear).
*/
static void
boot_mbr(void)
{
extern void i16_boot_mbr(void);
check_mbr();
DPRINTF("Jumping to MBR ...\n");
do_16bit(KERNEL_CS, KERNEL_16_CS,
base_i16_switch_to_real_mode();
i16_boot_mbr();
base_i16_switch_to_pmode();
);
/*
* Never returns of course!
*/
}
/*
* Simply load and boot the MBR right off the disk.
*/
static int
boot_mbr_default(void)
{
printf("Loading MBR and booting active partition\n");
load_mbr();
boot_mbr();
return 0;
}
/*
* Boot a particular sysid. Search the table for that partition and
* make it the active one, then boot. If the type does not exist,
* fallback to booting the default partition.
*
* N.B. we activate the first (lowest numbered) partition of a particular type.
*/
int
boot_mbr_sysid(int sysid)
{
extern char _start[];
int i, found;
struct bios_partition_info *entry;
printf("Loading MBR and booting system ID %d\n", sysid);
load_mbr();
/*
* Look for a non-empty partition that matches the one we want.
*/
entry = (struct bios_partition_info *)
(_start + BIOS_LABEL_BYTE_OFFSET);
for (i = 0; i < 4; i++, entry++) {
if (!entry->n_sectors)
continue;
if (entry->systid == sysid)
break;
}
/*
* Either we found it or it does not exist! If we found it,
* then make it the bootable partition.
*/
if (i < 4) {
entry = (struct bios_partition_info *)
(_start + BIOS_LABEL_BYTE_OFFSET);
found = 0;
for (i = 0; i < 4; i++, entry++) {
if (!entry->n_sectors)
continue;
if (!found && entry->systid == sysid) {
entry->bootid = BIOS_BOOTABLE;
found = 1;
} else
entry->bootid = 0;
}
}
else {
printf("%d: sysid not found in any partition\n", sysid);
return 1;
}
boot_mbr();
return 0;
}
/*
* Boot a particular partition by making it bootable. Better be non-zero.
*/
int
boot_mbr_partition(int partition)
{
extern char _start[];
int i;
struct bios_partition_info *table;
printf("Loading MBR and booting system partition %d\n", partition);
if (partition < 1 || partition > 4) {
printf("%d: invalid partition number\n", partition);
return 1;
}
load_mbr();
table = (struct bios_partition_info *)
(_start + BIOS_LABEL_BYTE_OFFSET);
/* partition is 1-based, table is 0-based */
partition--;
if (! table[partition].n_sectors) {
printf("%d: non-existent partition\n", partition);
return 1;
}
for (i = 0; i < 4; i++) {
if (i == partition)
table[i].bootid = BIOS_BOOTABLE;
else
table[i].bootid = 0;
}
boot_mbr();
return 0;
}
/*
* Allow for an interactive boot specification. Returning implies that
* we go through the automated boot.
*/
static void
boot_interactive(void)
{
#define PROMPT "Enter boot spec: "
char buf[512];
while (1) {
/*
* Flush!
*/
while (console_trygetchar() >= 0)
;
console_putbytes(PROMPT, strlen(PROMPT));
gets(buf);
#define PART "part:"
if (strncmp(buf, PART, strlen(PART)) == 0) {
int part = atoi(&buf[strlen(PART)]);
boot_mbr_partition(part);
}
#define SYSID "sysid:"
else if (strncmp(buf, SYSID, strlen(SYSID)) == 0) {
int sysid = atoi(&buf[strlen(SYSID)]);
boot_mbr_sysid(sysid);
}
#define AUTO "auto"
else if (strncmp(buf, AUTO, strlen(AUTO)) == 0) {
if (!pxe_isshutdown)
return;
}
#define REBOOT "reboot"
else if (strncmp(buf, REBOOT, strlen(REBOOT)) == 0) {
printf("Rebooting ...\n");
pc_reset();
return;
}
else if (isalpha(buf[0]) || isdigit(buf[0]) || buf[0] == '/') {
struct in_addr ip;
char *path, *args;
if (!pxe_isshutdown) {
parse_multiboot_spec(buf, &ip, &path, &args);
load_multiboot_image(&ip, path, args);
}
}
if (pxe_isshutdown)
printf("PXE shutdown, must reboot to use network\n");
printf("Choices are: \n"
" part:<partition number>, or\n"
" sysid:<system ID>, or\n"
" <pathway to multiboot image>, or\n"
" auto (query the bootinfo server again), or\n"
" reboot\n");
}
}
int
biosdprobe(int device)
{
ts.trapno = 0x13;
ts.eax = 0x800;
ts.edx = device;
base_real_int(&ts);
#ifdef USEEDD
/* check for EDD */
ts.eax = 0x4100;
ts.edx = device;
ts.ebx = 0x55aa;
base_real_int(&ts);
if ((ts.eflags & 1) == 0 && (ts.ebx & 0xFFFF) == 0xaa55 &&
(ts.ecx & 1) != 0)
return 1;
#endif
return 0;
}
void
biosdreset(int device)
{
ts.trapno = 0x13;
ts.eax = device >= 0x80 ? 0x0D00 : 0x0000;
ts.edx = device;
DPRINTF("dreset\n");
base_real_int(&ts);
DPRINTF("returns stat=%x\n", (ts.eax >> 8) & 0xFF);
}
/*
* biosdread(dev, cyl, head, sec, nsec, offset)
* Read "nsec" sectors from disk to offset "offset" in boot segment
* BIOS call "INT 0x13 Function 0x2" to read sectors from disk into memory
* Call with %ah = 0x2
* %al = number of sectors
* %ch = cylinder
* %cl = sector
* %dh = head
* %dl = drive (0x80 for hard disk, 0x0 for floppy disk)
* %es:%bx = segment:offset of buffer
* Return:
* %ah = 0x0 on success; err code on failure
*/
int
biosdread(int device, int cyl, int head, int sector, int count, void *addr)
{
ts.trapno = 0x13;
ts.eax = (0x2 << 8) | count;
ts.ecx = (cyl << 8) | sector;
ts.edx = (head << 8) | device;
ts.ebx = ((oskit_u32_t)addr) & 0xFFFF;
ts.v86_es = OURSEG;
DPRINTF("dread to %x:%x\n", ts.v86_es, ts.ebx);
base_real_int(&ts);
DPRINTF("returns cf=%x err=%x\n", ts.eflags&1, (ts.eax>>8) & 0xFF);
return (ts.eflags & 0x1) ? ((ts.eax >> 8) & 0xFF) : 0;
}
#ifdef USEEDD
/*
* Use extended device read.
*/
int
bioseddread(int device, int dblk, int count, void *addr)
{
static unsigned short packet[8];
packet[0] = 0x10;
packet[1] = count;
packet[2] = ((oskit_u32_t)addr) & 0xFFFF;
packet[3] = OURSEG;
packet[4] = dblk & 0xFFFF;
packet[5] = (dblk >> 16) & 0xFFFF;
packet[6] = 0;
packet[7] = 0;
ts.trapno = 0x13;
ts.eax = 0x4200;
ts.edx = device;
ts.ds = OURSEG;
ts.esi = (oskit_u32_t)packet;
DPRINTF("EDD dread to %x:%x\n", packet[3], packet[2]);
base_real_int(&ts);
DPRINTF("returns cf=%x err=%x\n", ts.eflags&1, (ts.eax>>8) & 0xFF);
return (ts.eflags & 0x1) ? ((ts.eax >> 8) & 0xFF) : 0;
}
#endif
/*
* Kind of a hack.
*/
void
delay(int millis)
{
long long count = millis * 200000;
while (count)
count--;
}
/*
* Take a string and separate the IP, path, and arguments into strings.
*/
static int
parse_multiboot_spec(char *cmdline,
struct in_addr *ip, char **path, char **args)
{
char *p;
if (isdigit(*cmdline) && (p = strchr(cmdline, ':'))) {
*p++ = '\0';
inet_aton(cmdline, ip);
}
else {
ip->s_addr = 0;
p = cmdline;
}
*path = p;
if ((p = strchr(p, ' '))) {
*p++ = '\0';
*args = p;
}
else
*args = "";
return 0;
}