Browse Source

Plan 9 from Bell Labs 2013-09-04

David du Colombier 10 years ago
parent
commit
858847c031
3 changed files with 351 additions and 297 deletions
  1. 195 216
      sys/man/8/9boot
  2. 29 20
      sys/src/9/pc/sdiahci.c
  3. 127 61
      sys/src/9/pcboot/pxeload.c

+ 195 - 216
sys/man/8/9boot

@@ -1,6 +1,6 @@
 .TH 9BOOT 8
 .SH NAME
-9boot, 9bootpbs, 9load, 9loadusb \- PC bootstrap programs
+9boot, 9bootpbs, 9load, 9loadusb, pbs \- PC bootstrap programs
 .SH SYNOPSIS
 .I none
 .SH DESCRIPTION
@@ -18,7 +18,7 @@ kernel and start it.
 .I 9load
 and
 .I 9loadusb
-are less commonly used variants
+are less-commonly-used variants
 that reside in a FAT file system under the name
 .L 9load
 and bootstrap Plan 9.
@@ -66,28 +66,55 @@ kernel
 .\" .IR 9load ,
 .\" kernel
 .PP
+In summary,
+Plan 9 is usually booted on a PC
+by using a PXE-capable BIOS to boot
+.I 9boot
+directly over the ethernet.
+File servers that must be able to boot when other machines are down
+boot directly from a Plan 9 disk partition
+.\" or boot floppy
+prepared using
+.B format
+to install the appropriate files and bootstrap sectors
+(see
+.IR prep (8)).
+.PP
 Details follow.
-.SS "Loading these bootstraps"
+.SS Kernel loading
 .I 9boot
 is a bootstrap program that loads and starts a program,
 typically the kernel, on a PC.
 It is run by the PXE boot ROM of a PC,
 which loads
 .I 9boot
-at location
+at physical address
 .B 0x7C00
 (31K).
+When it starts running,
+it switches to 32-bit mode.
+It then double maps the first 16Mb of physical memory to
+virtual addresses
+.B 0
+and
+.BR 0x80000000 .
+Only devices which can be automatically configured,
+e.g. most PCI ethernet adapters,
+will be recognised.
+If the file
+.BI /cfg/pxe/ ether
+can be located via a DHCP server,
+where
+.I ether
+is the lower-case MAC address of a recognised ethernet adapter,
+the contents are obtained by TFTP and used as a
+.IR plan9.ini (8).
 .I 9boot
-reads a
-.IR plan.ini (8)
-file from
-.B /cfg/pxe
-via PXE,
-then loads the named
+then loads the
 .I bootfile
-via TFTP,
+named within via TFTP,
 trying each ethernet in sequence,
-at the entry address specified by the header,
+at the entry address specified by the kernel executable's header,
 usually virtual
 .BR 0xF0100020 .
 After loading,
@@ -95,78 +122,136 @@ After loading,
 creates a Gnu Multiboot header in low memory for
 the benefit of the loaded kernel
 and
-control is passed to the entry location.
+control is passed to the entry location
+in 32-bit protected mode, even for 64-bit kernels.
 So far, only
 .B amd64
 kernels expect Multiboot headers.
 .PP
+Some options in
+.B plan9.ini
+are used by
+.IR 9boot :
+.TF bootfile=manual
+.TP
+.B console
+.TP
+.B baud
+Specifies the console device and baud rate if not a display.
+.TP
+.BI ether n
+Ethernet interfaces. These can be used to load the
+.I bootfile
+over a network.
+.TP
+.BI bootfile= bootfile
+Specifies the
+.IR bootfile .
+.ig
+.TP
+.B bootfile=auto
+Default.
+.TP
+.B bootfile=local
+Like
+.IR auto ,
+but do not attempt to load over the network.
+..
+.TP
+.B bootfile=manual
+After determining which devices are available for loading from,
+enter prompt mode.
+.PD
+.PP
 .I 9load
 is a similar bootstrap program,
-run by the PC partition boot sector program (PBS),
+loaded by the PC partition boot sector program (PBS),
 which usually resides in the first
-sector of the active partition.
-A copy of the Plan 9 PBS is kept in
-.BR /386/pbs ,
-but due to the ``cylinder-head-sector'' (CHS) addressing mode of old BIOSes, it can only
-operate up to 8.5GB into the disk.
-Plan 9 partitions further into the disk
-can only be booted using
-.BR /386/pbslba ,
-and then only if the machine's BIOS supports
-linear block addressing (LBA) mode for disk transfers.
-.PP
-When booting from disk,
-.\" or floppy,
-the BIOS loads the
-first sector of the medium at location
-.BR 0x7C00 .
-In the case of a disk, it is the master boot record (MBR).
-.\" In the case of a floppy, this is the PBS.
-The MBR copies itself to address
-.BR 0x600 ,
-finds the active partition and loads its PBS at address
-.BR 0x7C00 .
-A copy of the Plan 9 MBR is kept in
-.BR /386/mbr ;
-some commercial MBRs cannot read sectors
-past 2GB.
-The Plan 9 MBR can read sectors up to 8.5GB into
-the disk, and further if the BIOS supports LBA.
-The single file
-.B /386/mbr
-detects whether the BIOS supports LBA and
-acts appropriately, defaulting to CHS mode
-when LBA is not present.
-The PBSs cannot do this due to code size limitations.
-The Plan 9 MBR is suitable for booting non-Plan-9
-operating systems,
-and (modulo the large disk constraints just described)
-non-Plan-9 MBRs are suitable for booting Plan 9.
+sector of the active disk partition.
+It is initially loaded at physical address
+.BR 0x10000 (64K);
+it begins execution at virtual address
+.BR 0x80010000 .
+In order to find configuration information,
+.I 9load
+searches all units on devices
+.\" .BR fd
+.\" and
+.B sd?[0-9]*
+(all
+.B sd
+devices),
+for a file called
+.\" .B plan9\eplan9.ini
+.\" or
+.B plan9.ini
+(see
+.IR plan9.ini (8))
+on a FAT partition named
+.B dos
+or
+.BR 9fat .
+If one is found, searching stops and the file is read into memory
+at physical address
+.B 0x1200
+where it can be found later by any loaded
+.IR bootfile .
 .PP
+When the search for
+.B plan9.ini
+is done,
 .I 9load
-begins execution at virtual address
-.B 0x80010000
-(physical 64K) and
-loads the
+proceeds to determine which bootfile to load.
+If there was no
 .I bootfile
-at the entry address specified by the header,
-usually virtual
-.BR 0xF0100020 .
-After loading, control is passed to the entry location.
+option,
+.I 9load
+searches
+.B sd?[0-9]*
+FAT partitions for a kernel
+(any file named
+.BR 9pc* ,
+.B 9k8*
+or
+.BR 9k10* )
+and if it finds exactly one kernel in a given FAT partition,
+chooses it.
+.I 9load
+then attempts to load the
+.IR bootfile .
+.ig
+unless
+the
+.B bootfile=manual
+option was given, in which case prompt mode is entered immediately.
+..
+.ig
+If the default device is
+.BR fd ,
+.I 9load
+will prompt the user for input before proceeding with the
+default bootfile load after 5 seconds;
+this prompt is omitted if a
+.I bootfile
+option
+was given.
+..
 .PP
-In summary,
-Plan 9 is usually booted on a PC
-by using a PXE-capable BIOS to boot
-.I 9boot
-directly over the ethernet.
-File servers that must be able to boot when other machines are down
-boot directly from a Plan 9 disk partition
-.\" or boot floppy
-prepared using
-.B format
-to install the appropriate files and bootstrap sectors
-(see
-.IR prep (8)).
+.I 9load
+prints the list of available
+.IR device s
+and
+enters prompt mode on encountering any error
+or if directed to do so by a
+.B bootfile=manual
+option.
+In prompt mode, the user is required to type
+a
+.IB bootfile
+in response to the
+.L Boot
+.L from:
+prompt.
 .br
 .ne 4
 .SS Bootfile
@@ -299,150 +384,44 @@ A special case of
 that uses
 .BI bios n
 to read from a FAT file system.
-.SS Kernel loading
-.I 9boot
-is initially loaded by the PXE BIOS at physical address
-.BR 0x7C00 .
-When it starts running,
-it switches to 32-bit mode.
-It then double maps the first 16Mb of physical memory to
-virtual addresses
-.B 0
-and
-.BR 0x80000000 .
-Only devices which can be automatically configured,
-e.g. most PCI ethernet adapters,
-will be recognised.
-If the file
-.BI /cfg/pxe/ ether
-can be located via a DHCP server,
-where
-.I ether
-is the lower-case MAC address of a recognised ethernet adapter,
-the contents are obtained by TFTP and used as a
-.IR plan9.ini .
-.PP
-.I 9load
-differs slightly in operation from
-.IR 9boot .
-It is initially loaded by a partition boot sector at physical address
-.BR 0x10000 .
-In order to find configuration information,
-.I 9load
-searches all units on devices
-.\" .BR fd
-.\" and
-.B sd?[0-9]*
-(all
-.B sd
-devices),
-for a file called
-.\" .B plan9\eplan9.ini
-.\" or
-.B plan9.ini
-(see
-.IR plan9.ini (8))
-on a FAT partition named
-.B dos
-or
-.BR 9fat .
-If one is found, searching stops and the file is read into memory
-at physical address
-.B 0x1200
-where it can be found later by any loaded
-.IR bootfile .
-Some options in
-.B plan9.ini
-are used by
-.IR 9boot :
-.TF bootfile=manual
-.TP
-.B console
-.TP
-.B baud
-Specifies the console device and baud rate if not a display.
-.TP
-.BI ether n
-Ethernet interfaces. These can be used to load the
-.I bootfile
-over a network.
-.TP
-.BI bootfile= bootfile
-Specifies the
-.IR bootfile .
-This option is overridden by a command-line argument.
-.ig
-.TP
-.B bootfile=auto
-Default.
-.TP
-.B bootfile=local
-Like
-.IR auto ,
-but do not attempt to load over the network.
-..
-.TP
-.B bootfile=manual
-After determining which devices are available for loading from,
-enter prompt mode.
-.PD
-.PP
-When the search for
-.B plan9.ini
-is done,
-.I 9load
-proceeds to determine which bootfile to load.
-If there was no
-.I bootfile
-option,
-.I 9load
-searches
-.B sd?[0-9]*
-FAT partitions for a kernel
-(any file named
-.BR 9pc* ,
-.B 9k8*
-or
-.BR 9k10* )
-and if it finds exactly one kernel in a given FAT partition,
-chooses it.
-.I 9load
-then attempts to load the
-.IR bootfile .
-.ig
-unless
-the
-.B bootfile=manual
-option was given, in which case prompt mode is entered immediately.
-..
-.ig
-If the default device is
-.BR fd ,
-.I 9load
-will prompt the user for input before proceeding with the
-default bootfile load after 5 seconds;
-this prompt is omitted if
-a command-line argument or
-.I bootfile
-option
-was given.
-..
+.SS Boot Sectors
+A copy of the Plan 9 PBS is kept in
+.BR /386/pbs ,
+but due to the ``cylinder-head-sector'' (CHS) addressing mode of old BIOSes, it can only
+operate up to 8.5GB into the disk.
+Plan 9 partitions further into the disk
+can only be booted using
+.BR /386/pbslba ,
+and then only if the machine's BIOS supports
+linear block addressing (LBA) mode for disk transfers.
 .PP
-.I 9load
-prints the list of available
-.IR device s
-and
-enters prompt mode on encountering any error
-or if directed to do so by a
-.B bootfile=manual
-option.
-In prompt mode, the user is required to type
-a
-.IB bootfile
-in response to the
-.L Boot
-.L from:
-prompt.
+When booting from disk,
+.\" or floppy,
+the BIOS loads the
+first sector of the medium at location
+.BR 0x7C00 .
+In the case of a disk, it is the master boot record (MBR).
+.\" In the case of a floppy, this is the PBS.
+The MBR copies itself to address
+.BR 0x600 ,
+finds the active partition and loads its PBS at address
+.BR 0x7C00 .
+A copy of the Plan 9 MBR is kept in
+.BR /386/mbr ;
+some commercial MBRs cannot read sectors
+past 2GB.
+The Plan 9 MBR can read sectors up to 8.5GB into
+the disk, and further if the BIOS supports LBA.
+The single file
+.B /386/mbr
+detects whether the BIOS supports LBA and
+acts appropriately, defaulting to CHS mode
+when LBA is not present.
+The PBSs cannot do this due to code size limitations.
+The Plan 9 MBR is suitable for booting non-Plan-9
+operating systems,
+and (modulo the large disk constraints just described)
+non-Plan-9 MBRs are suitable for booting Plan 9.
 .br
 .ne 4
 .SS Other facilities and caveats

+ 29 - 20
sys/src/9/pc/sdiahci.c

@@ -1,10 +1,6 @@
 /*
  * ahci serial ata driver
  * copyright © 2007-8 coraid, inc.
- *
- * there was a great deal of locking of single operations (e.g.,
- * atomic assignments); it's not clear what that locking was intended to
- * prevent.
  */
 
 #include "u.h"
@@ -1029,6 +1025,14 @@ configdrive(Drive *d)
 	return 0;
 }
 
+static void
+setstate(Drive *d, int state)
+{
+	ilock(d);
+	d->state = state;
+	iunlock(d);
+}
+
 static void
 resetdisk(Drive *d)
 {
@@ -1057,10 +1061,10 @@ resetdisk(Drive *d)
 	iunlock(d);
 
 	qlock(&d->portm);
-	if(p->cmd&Ast && ahciswreset(&d->portc) == -1){
-		d->state = Dportreset;	/* get a bigger stick. */
-	} else {
-		d->state = Dmissing;
+	if(p->cmd&Ast && ahciswreset(&d->portc) == -1)
+		setstate(d, Dportreset);	/* get a bigger stick. */
+	else {
+		setstate(d, Dmissing);
 		configdrive(d);
 	}
 	dprint("ahci: %s: resetdisk: %s → %s\n", (d->unit? d->unit->name: nil),
@@ -1098,9 +1102,7 @@ newdrive(Drive *d)
 		if(ahcirecover(c) == -1)
 			goto lose;
 	}
-
-	d->state = Dready;
-
+	setstate(d, Dready);
 	qunlock(c->m);
 
 	idprint("%s: %sLBA %,llud sectors: %s %s %s %s\n", d->unit->name,
@@ -1110,7 +1112,7 @@ newdrive(Drive *d)
 
 lose:
 	idprint("%s: can't be initialized\n", d->unit->name);
-	d->state = Dnull;
+	setstate(d, Dnull);
 	qunlock(c->m);
 	return -1;
 }
@@ -1298,9 +1300,12 @@ isctlrjabbering(Ctlr *c, ulong cause)
 		c->intrs = 0;
 		c->lastintr0 = now;
 	}
-	if (++c->intrs > Maxintrspertick)
-		panic("sdiahci: too many intrs per tick for no serviced "
-			"drive; cause %#lux mport %d", cause, c->mport);
+	if (++c->intrs > Maxintrspertick) {
+		iprint("sdiahci: %lud intrs per tick for no serviced "
+			"drive; cause %#lux mport %d\n",
+			c->intrs, cause, c->mport);
+		c->intrs = 0;
+	}
 }
 
 static void
@@ -1313,9 +1318,11 @@ isdrivejabbering(Drive *d)
 		d->intrs = 0;
 		d->lastintr0 = now;
 	}
-	if (++d->intrs > Maxintrspertick)
-		panic("sdiahci: too many interrupts per tick for %s",
-			d->unit->name);
+	if (++d->intrs > Maxintrspertick) {
+		iprint("sdiahci: %lud interrupts per tick for %s\n",
+			d->intrs, d->unit->name);
+		d->intrs = 0;
+	}
 }
 
 static void
@@ -1646,7 +1653,7 @@ waitready(Drive *d)
 		esleep(250);
 	}
 	print("%s: not responding; offline\n", d->unit->name);
-	d->state = Doffline;
+	setstate(d, Doffline);
 	return -1;
 }
 
@@ -2173,7 +2180,9 @@ forcemode(Drive *d, char *mode)
 			break;
 	if(i == nelem(modename))
 		i = 0;
+	ilock(d);
 	d->mode = i;
+	iunlock(d);
 }
 
 static void
@@ -2202,7 +2211,7 @@ forcestate(Drive *d, char *state)
 			break;
 	if(i == nelem(diskstates))
 		error(Ebadctl);
-	d->state = i;
+	setstate(d, i);
 }
 
 /*

+ 127 - 61
sys/src/9/pcboot/pxeload.c

@@ -33,6 +33,10 @@ enum {
 	Prefsegsize =	1400,
 	Maxsegsize =	2048,
 	Bufsz =		Maxsegsize + 2,
+
+	Ok =		0,
+	Err =		-1,
+	Nonexist =	-2,
 };
 
 typedef struct Ethaddr Ethaddr;
@@ -73,10 +77,12 @@ struct Ethaddr {		/* communication with sleep procs */
 };
 
 static char ethernm[] = "ether";
+static uchar myea[Eaddrlen];
+static Pxenetaddr myaddr;		/* actually, local ip addr & port */
 
 /*
  * there can be at most one concurrent tftp session until we move these
- * variables into Openeth or some other struct.
+ * variables into Openeth or some other struct (Tftpstate).
  */
 static ushort tftpport;
 static int tftpblockno;
@@ -84,12 +90,11 @@ static int tftpphase;
 static int progress;
 static int segsize;
 static Tftp *tftpb;
-
-static uchar myea[Eaddrlen];
-static Pxenetaddr myaddr;		/* actually, local ip addr & port */
 static Pxenetaddr tftpserv;		/* actually, remote ip addr & port */
 static Pxenetaddr bootpserv;
 
+static int	tftpconnect(Openeth *, Bootp *);
+
 uchar *
 etheraddr(Openeth *oe)
 {
@@ -404,15 +409,19 @@ tftpread1st(Openeth *oe, Pxenetaddr *a, char *name, Tftp *tftp)
 		switch((tftp->header[0]<<8)|tftp->header[1]){
 
 		case Tftp_ERROR:
+			if(strstr((char *)tftp->data, "does not exist") != nil){
+				print("%s\n", (char*)tftp->data);
+				return Nonexist;
+			}
 			print("tftpread1st: error (%d): %s\n",
 				(tftp->header[2]<<8)|tftp->header[3], (char*)tftp->data);
-			return -1;
+			return Err;
 
 		case Tftp_OACK:
 			n = optval("blksize", (char *)tftp->header+2, rlen-2);
 			if (n <= 0) {
 				nak(oe, a, 0, "bad blksize option value", 0);
-				return -1;
+				return Err;
 			}
 			segsize = n;
 			/* no bytes stashed in tftp.data */
@@ -426,7 +435,7 @@ tftpread1st(Openeth *oe, Pxenetaddr *a, char *name, Tftp *tftp)
 			if(len != tftpblockno){
 				print("tftpread1st: block error: %d\n", len);
 				nak(oe, a, 1, "block error", 0);
-				return -1;
+				return Err;
 			}
 			rlen -= Tftphdrsz;
 			if(rlen < segsize)
@@ -437,12 +446,12 @@ tftpread1st(Openeth *oe, Pxenetaddr *a, char *name, Tftp *tftp)
 		default:
 			print("tftpread1st: unexpected pkt type recv'd\n");
 			nak(oe, a, 0, "unexpected pkt type recv'd", 0);
-			return -1;
+			return Err;
 		}
 	}
 
 	print("tftpread1st: failed to connect to server (%I!%d)\n", a->ip, oport);
-	return -1;
+	return Err;
 }
 
 static int
@@ -606,6 +615,9 @@ tftpopen(Openeth *oe, char *file, Bootp *rep)
 	char buf[128];
 	static uchar ipv4noaddr[IPv4addrlen];
 
+	if (tftpconnect(oe, rep) < 0)
+		return Err;
+
 	/*
 	 * read file from tftp server in bootp answer
 	 */
@@ -641,13 +653,15 @@ tftpopen(Openeth *oe, char *file, Bootp *rep)
 	return tftpread1st(oe, &tftpserv, filename, tftpb);
 }
 
+/* load the kernel in file via tftp on oe */
 int
 tftpboot(Openeth *oe, char *file, Bootp *rep, Boot *b)
 {
 	int n;
 
+	/* file must exist, else it's an error */
 	if((n = tftpopen(oe, file, rep)) < 0)
-		return -1;
+		return n;
 
 	progress = 0;			/* no more dots; we're on a roll now */
 	print(" ");			/* after "sys (ip!port): kernel ..." */
@@ -661,7 +675,7 @@ tftpboot(Openeth *oe, char *file, Bootp *rep, Boot *b)
 	else
 		nak(oe, &tftpserv, 3, "ok", 0);	/* tftpclose to abort transfer */
 	bootpass(b, nil, 0);	/* boot if possible */
-	return -1;
+	return Err;
 }
 
 /* leave the channel to /net/ipifc/clone open */
@@ -816,9 +830,8 @@ optget(uchar *p, int op, int *np)
 			continue;
 		}
 		if(np != nil){
-			if(*np > len) {
+			if(*np > len)
 				return 0;
-			}
 			*np = len;
 		}
 		return p;
@@ -971,13 +984,13 @@ tftprdfile(Openeth *oe, int openread, void* va, long len)
 			break;
 
 		if((n = tftpread(oe, &tftpserv, tftpb, segsize)) < 0)
-			return -1;
+			return n;
 	}
 	return p-v;
 }
 
 static int
-newtftpconn(Openeth *oe, Bootp *rep)
+tftpconnect(Openeth *oe, Bootp *rep)
 {
 	char num[16], dialstr[64];
 
@@ -1025,31 +1038,20 @@ setipcfg(Openeth *oe, Bootp *rep)
 	return 0;
 }
 
+/*
+ * use bootp answer (rep) to open cfgpxe.
+ * reads first pkt of cfgpxe into tftpb->data.
+ */
 static int
-getkernname(Openeth *oe, Bootp *rep, Kernname *kp)
+rdcfgpxe(Openeth *oe, Bootp *rep, char *cfgpxe)
 {
 	int n;
-	char *ini, *p;
-	char cfgpxe[32], buf[64];
+	char *ini;
 
-	if (kp->bootfile) {
-		print("getkernname: already have bootfile %s\n", kp->bootfile);
-		return 0;
-	}
-	if (newtftpconn(oe, rep) < 0)
-		return -1;
-
-	/* use our mac address instead of relying on a bootp answer */
-	snprint(cfgpxe, sizeof cfgpxe, "/cfg/pxe/%E", myea);
-	/*
-	 * use bootp answer (rep) to open cfgpxe.
-	 * reads first pkt of cfgpxe into tftpb->data.
-	 */
+	/* cfgpxe is optional */
 	n = tftpopen(oe, cfgpxe, rep);
-	if (n < 0) {
-		print("\nfailed.\n");
-		return -1;
-	}
+	if (n < 0)
+		return n;
 	if (Debug)
 		print("\opened %s\n", cfgpxe);
 
@@ -1059,7 +1061,7 @@ getkernname(Openeth *oe, Bootp *rep, Kernname *kp)
 	if (n < 0) {
 		print("error reading %s\n", cfgpxe);
 		free(ini);
-		return -1;
+		return n;
 	}
 	print(" read %d bytes", n);
 
@@ -1068,17 +1070,17 @@ getkernname(Openeth *oe, Bootp *rep, Kernname *kp)
 	 * thus we can't free ini.
 	 */
 	dotini(ini);
-	i8250console();		/* configure serial port with defaults */
+	return Ok;
+}
 
-	kp->edev = kp->bootfile = nil;
-	p = getconf("bootfile");
-	if (p)
-		kstrdup(&kp->bootfile, p);
-	if (kp->bootfile == nil)
-		askbootfile(buf, sizeof buf, &kp->bootfile, Promptsecs,
-			"ether0!/386/9pccpu");
-	if (strcmp(kp->bootfile, "manual") == 0)
-		askbootfile(buf, sizeof buf, &kp->bootfile, 0, "");
+/*
+ * break kp->bootfile into kp->edev & kp->bootfile,
+ * copy any args for new kernel to low memory.
+ */
+static int
+parsebootfile(Kernname *kp)
+{
+	char *p;
 
 	p = strchr(kp->bootfile, '!');
 	if (p != nil) {
@@ -1088,16 +1090,57 @@ getkernname(Openeth *oe, Bootp *rep, Kernname *kp)
 		kstrdup(&kp->bootfile, p);
 		if (strncmp(kp->edev, ethernm, sizeof ethernm - 1) != 0) {
 			print("bad ether device %s\n", kp->edev);
-			return -1;
+			return Err;
 		}
 	}
 
-	/* pass arguments to kernels that can use them */
+	/* pass any arguments to kernels that expect them */
 	strecpy(BOOTLINE, BOOTLINE+BOOTLINELEN, kp->bootfile);
 	p = strchr(kp->bootfile, ' ');
 	if(p != nil)
 		*p = '\0';
-	return 0;
+	return Ok;
+}
+
+static int
+getkernname(Openeth *oe, Bootp *rep, Kernname *kp)
+{
+	int n;
+	char *p;
+	char cfgpxe[32], buf[64];
+
+	if (kp->bootfile) {
+		/* i think returning here is a bad idea */
+		// print("getkernname: already have bootfile %s\n",
+		//	kp->bootfile);
+		free(kp->bootfile);
+		// return Ok;
+	}
+	kp->edev = kp->bootfile = nil;
+	i8250console();		/* configure serial port with defaults */
+
+	/* use our mac address instead of relying on a bootp answer. */
+	snprint(cfgpxe, sizeof cfgpxe, "/cfg/pxe/%E", myea);
+	n = rdcfgpxe(oe, rep, cfgpxe);
+	switch (n) {
+	case Ok:
+		p = getconf("bootfile");
+		if (p)
+			kstrdup(&kp->bootfile, p);
+		if (kp->bootfile == nil)
+			askbootfile(buf, sizeof buf, &kp->bootfile, Promptsecs,
+				"ether0!/386/9pccpu");
+		if (strcmp(kp->bootfile, "manual") == 0)
+			askbootfile(buf, sizeof buf, &kp->bootfile, 0, "");
+		break;
+	case Err:
+		print("\nfailed.\n");
+		return n;
+	case Nonexist:
+		askbootfile(buf, sizeof buf, &kp->bootfile, 0, "");
+		break;
+	}
+	return parsebootfile(kp);
 }
 
 static void
@@ -1119,39 +1162,62 @@ unbinddevip(Openeth *oe)
  * phase 2: load /cfg/pxe, parse it, extract kernel filename.
  * phase 3: load kernel and jump to it.
  */
-static void
+static int
 tftpload(Openeth *oe, Kernname *kp)
 {
+	int r, n;
+	char buf[64];
 	Bootp rep;
 	Boot boot;
 
+	r = -1;
 	if(waserror()) {
 		print("tftpload: %s\n", up->errstr);
 		closeudp(oe);
 		unbinddevip(oe);
-		return;
+		return r;
 	}
 
 	memset(&rep, 0, sizeof rep);
-	if (setipcfg(oe, &rep) >= 0 &&
-	    getkernname(oe, &rep, kp) >= 0 &&
-	    (!kp->edev ||
-	     oe->ctlrno == strtol(kp->edev + sizeof ethernm - 1, 0, 10)) &&
-	    newtftpconn(oe, &rep) >= 0) {
+	if (setipcfg(oe, &rep) < 0)
+		error("can't set ip config");
+
+	n = getkernname(oe, &rep, kp);
+	if (n < 0) {
+		r = n;			/* pass reason back to caller */
+		USED(r);
+		nexterror();
+	}
+	do {
+		if (kp->edev &&
+		    oe->ctlrno != strtol(kp->edev + sizeof ethernm - 1, 0, 10)){
+			/* user specified an ether & it's not this one; next! */
+			r = Ok;
+			USED(r);
+			nexterror();
+		}
+
 		memset(&boot, 0, sizeof boot);
 		boot.state = INITKERNEL;
-		tftpboot(oe, kp->bootfile, &rep, &boot);
-	}
+		r = tftpboot(oe, kp->bootfile, &rep, &boot);
+
+		/* we failed or bootfile asked for another ether */
+		if (r == Nonexist)
+			do {
+				askbootfile(buf, sizeof buf, &kp->bootfile, 0, "");
+			} while (parsebootfile(kp) != Ok);
+	} while (r == Nonexist);
 
-	/* we failed or bootfile asked for another ether */
 	poperror();
 	closeudp(oe);
 	unbinddevip(oe);
+	return r;
 }
 
 static int
 etherload(int eth, Kernname *kp)
 {
+	int r;
 	Openeth *oe;
 
 	print("pxe on ether%d ", eth);
@@ -1163,11 +1229,11 @@ etherload(int eth, Kernname *kp)
 		oe->ctlrno);
 	initbind(oe);
 
-	tftpload(oe, kp);
+	r = tftpload(oe, kp);
 
 	/* failed to boot; keep going */
 	unmount(nil, "/net");
-	return 0;
+	return r;
 }
 
 static int