Clean checkin of the 6.03 code

BUG: 32495852
Change-Id: I5038a3bb41e217380c1188463daec07b1e9b6b48
diff --git a/gpxe/src/arch/i386/Makefile b/gpxe/src/arch/i386/Makefile
new file mode 100644
index 0000000..dd8da80
--- /dev/null
+++ b/gpxe/src/arch/i386/Makefile
@@ -0,0 +1,117 @@
+# Force i386-only instructions
+#
+CFLAGS		+= -march=i386
+
+# Code size reduction.
+#
+CFLAGS		+= -fomit-frame-pointer
+
+# Code size reduction.
+#
+ifeq ($(CCTYPE),gcc)
+CFLAGS		+= -fstrength-reduce
+endif
+
+# Code size reduction.  gcc3 needs a different syntax to gcc2 if you
+# want to avoid spurious warnings.
+#
+ifeq ($(CCTYPE),gcc)
+GCC_VERSION	:= $(subst ., ,$(shell $(CC) -dumpversion))
+GCC_MAJOR	:= $(firstword $(GCC_VERSION))
+ifeq ($(GCC_MAJOR),2)
+CFLAGS		+= -malign-jumps=1 -malign-loops=1 -malign-functions=1
+else
+CFLAGS		+= -falign-jumps=1 -falign-loops=1 -falign-functions=1
+endif # gcc2
+endif # gcc
+
+# Code size reduction.  This is almost always a win.  The kernel uses
+# it, too.
+#
+ifeq ($(CCTYPE),gcc)
+CFLAGS		+= -mpreferred-stack-boundary=2
+endif
+
+# Code size reduction.  Use regparm for all functions - C functions
+# called from assembly (or vice versa) need __asmcall now
+#
+CFLAGS		+= -mregparm=3
+
+# Code size reduction.  Use -mrtd (same __asmcall requirements as above)
+ifeq ($(CCTYPE),gcc)
+CFLAGS		+= -mrtd
+endif
+
+# Code size reduction.  This is the logical complement to -mregparm=3.
+# It doesn't currently buy us anything, but if anything ever tries to
+# return small structures, let's be prepared
+#
+CFLAGS		+= -freg-struct-return
+
+# Force 32-bit code even on an x86-64 machine
+#
+CFLAGS		+= -m32
+ASFLAGS		+= --32
+ifeq ($(HOST_OS),FreeBSD)
+LDFLAGS		+= -m elf_i386_fbsd
+else
+LDFLAGS		+= -m elf_i386
+endif
+
+# EFI requires -fshort-wchar, and nothing else currently uses wchar_t
+#
+CFLAGS		+= -fshort-wchar
+
+# We need to undefine the default macro "i386" when compiling .S
+# files, otherwise ".arch i386" translates to ".arch 1"...
+#
+CFLAGS			+= -Ui386
+
+# Locations of utilities
+#
+ISOLINUX_BIN	= /usr/lib/syslinux/isolinux.bin
+
+# i386-specific directories containing source files
+#
+SRCDIRS		+= arch/i386/core arch/i386/transitions arch/i386/prefix
+SRCDIRS		+= arch/i386/firmware/pcbios
+SRCDIRS		+= arch/i386/image
+SRCDIRS		+= arch/i386/drivers
+SRCDIRS		+= arch/i386/drivers/net
+SRCDIRS		+= arch/i386/interface/pcbios
+SRCDIRS		+= arch/i386/interface/pxe
+SRCDIRS		+= arch/i386/interface/pxeparent
+SRCDIRS 	+= arch/i386/interface/syslinux
+SRCDIRS		+= arch/i386/hci/commands
+
+# The various xxx_loader.c files are #included into core/loader.c and
+# should not be compiled directly.
+#
+NON_AUTO_SRCS	+= arch/i386/core/aout_loader.c
+NON_AUTO_SRCS	+= arch/i386/core/freebsd_loader.c
+NON_AUTO_SRCS	+= arch/i386/core/wince_loader.c
+
+# Include common x86 Makefile
+#
+MAKEDEPS	+= arch/x86/Makefile
+include arch/x86/Makefile
+
+# Include platform-specific Makefile
+#
+MAKEDEPS	+= arch/i386/Makefile.$(PLATFORM)
+include arch/i386/Makefile.$(PLATFORM)
+
+# Some suffixes (e.g. %.fd0) are generated directly from other
+# finished files (e.g. %.dsk), rather than having their own prefix.
+
+# rule to write disk images to /dev/fd0
+NON_AUTO_MEDIA	+= fd0
+%fd0 : %dsk
+	$(QM)$(ECHO) "  [DD] $@"
+	$(Q)dd if=$< bs=512 conv=sync of=/dev/fd0
+	$(Q)sync
+
+# Add NON_AUTO_MEDIA to the media list, so that they show up in the
+# output of "make"
+#
+MEDIA		+= $(NON_AUTO_MEDIA)
diff --git a/gpxe/src/arch/i386/Makefile.efi b/gpxe/src/arch/i386/Makefile.efi
new file mode 100644
index 0000000..8d651b0
--- /dev/null
+++ b/gpxe/src/arch/i386/Makefile.efi
@@ -0,0 +1,10 @@
+# -*- makefile -*- : Force emacs to use Makefile mode
+
+# Specify EFI image builder
+#
+ELF2EFI		= $(ELF2EFI32)
+
+# Include generic EFI Makefile
+#
+MAKEDEPS	+= arch/x86/Makefile.efi
+include arch/x86/Makefile.efi
diff --git a/gpxe/src/arch/i386/Makefile.pcbios b/gpxe/src/arch/i386/Makefile.pcbios
new file mode 100644
index 0000000..e38fbca
--- /dev/null
+++ b/gpxe/src/arch/i386/Makefile.pcbios
@@ -0,0 +1,70 @@
+# -*- makefile -*- : Force emacs to use Makefile mode
+
+# The i386 linker script
+#
+LDSCRIPT	= arch/i386/scripts/i386.lds
+
+# Stop ld from complaining about our customised linker script
+#
+LDFLAGS		+= -N --no-check-sections
+
+# Media types.
+#
+MEDIA		+= rom
+MEDIA		+= hrom
+MEDIA		+= xrom
+MEDIA		+= pxe
+MEDIA		+= kpxe
+MEDIA		+= kkpxe
+MEDIA		+= lkrn
+MEDIA		+= dsk
+MEDIA		+= nbi
+MEDIA		+= hd
+MEDIA		+= raw
+
+# Padding rules
+#
+PAD_rom		= $(PADIMG) --blksize=512 --byte=0xff $@
+PAD_hrom	= $(PAD_rom)
+PAD_xrom	= $(PAD_rom)
+PAD_dsk		= $(PADIMG) --blksize=512 $@
+PAD_hd		= $(PADIMG) --blksize=32768 $@
+
+# rule to make a non-emulation ISO boot image
+NON_AUTO_MEDIA	+= iso
+%iso:	%lkrn util/geniso
+	$(QM)$(ECHO) "  [GENISO] $@"
+	$(Q)ISOLINUX_BIN=$(ISOLINUX_BIN) bash util/geniso $@ $<
+
+# rule to make a floppy emulation ISO boot image
+NON_AUTO_MEDIA	+= liso
+%liso:	%lkrn util/genliso
+	$(QM)$(ECHO) "  [GENLISO] $@"
+	$(Q)bash util/genliso $@ $<
+
+# rule to make a syslinux floppy image (mountable, bootable)
+NON_AUTO_MEDIA	+= sdsk
+%sdsk:	%lkrn util/gensdsk
+	$(QM)$(ECHO) "  [GENSDSK] $@"
+	$(Q)bash util/gensdsk $@ $<
+
+# Special target for building Master Boot Record binary
+$(BIN)/mbr.bin : $(BIN)/mbr.o
+	$(QM)$(ECHO) "  [OBJCOPY] $@"
+	$(Q)$(OBJCOPY) -O binary $< $@
+
+# rule to make a USB disk image
+$(BIN)/usbdisk.bin : $(BIN)/usbdisk.o
+	$(QM)$(ECHO) "  [OBJCOPY] $@"
+	$(Q)$(OBJCOPY) -O binary $< $@
+
+NON_AUTO_MEDIA	+= usb
+%usb: $(BIN)/usbdisk.bin %hd
+	$(QM)$(ECHO) "  [FINISH] $@"
+	$(Q)cat $^ > $@
+
+# Padded floppy image (e.g. for iLO)
+NON_AUTO_MEDIA += pdsk
+%pdsk : %dsk
+	$(Q)cp $< $@
+	$(Q)$(PADIMG) --blksize=1474560 $@
diff --git a/gpxe/src/arch/i386/README.i386 b/gpxe/src/arch/i386/README.i386
new file mode 100644
index 0000000..b9b79cc
--- /dev/null
+++ b/gpxe/src/arch/i386/README.i386
@@ -0,0 +1,197 @@
+Etherboot/NILO i386 initialisation path and external call interface
+===================================================================
+
+1. Background
+
+GCC compiles 32-bit code.  It is capable of producing
+position-independent code, but the resulting binary is about 25%
+bigger than the corresponding fixed-position code.  Since one main use
+of Etherboot is as firmware to be burned into an EPROM, code size must
+be kept as small as possible.
+
+This means that we want to compile fixed-position code with GCC, and
+link it to have a predetermined start address.  The problem then is
+that we must know the address that the code will be loaded to when it
+runs.  There are several ways to solve this:
+
+1. Pick an address, link the code with this start address, then make
+   sure that the code gets loaded at that location.  This is
+   problematic, because we may pick an address that we later end up
+   wanting to use to load the operating system that we're booting.
+
+2. Pick an address, link the code with this start address, then set up
+   virtual addressing so that the virtual addresses match the
+   link-time addresses regardless of the real physical address that
+   the code is loaded to.  This enables us to relocate Etherboot to
+   the top of high memory, where it will be out of the way of any
+   loading operating system.
+
+3. Link the code with a text start address of zero and a data start
+   address also of zero.  Use 16-bit real mode and the
+   quasi-position-independence it gives you via segment addressing.
+   Doing this requires that we generate 16-bit code, rather than
+   32-bit code, and restricts us to a maximum of 64kB in each segment.
+
+There are other possible approaches (e.g. including a relocation table
+and code that performs standard dynamic relocation), but the three
+options listed above are probably the best available.
+
+Etherboot can be invoked in a variety of ways (ROM, floppy, as a PXE
+NBP, etc).  Several of these ways involve control being passed to
+Etherboot with the CPU in 16-bit real mode.  Some will involve the CPU
+being in 32-bit protected mode, and there's an outside chance that
+some may involve the CPU being in 16-bit protected mode.  We will
+almost certainly have to effect a CPU mode change in order to reach
+the mode we want to be in to execute the C code.
+
+Additionally, Etherboot may wish to call external routines, such as
+BIOS interrupts, which must be called in 16-bit real mode.  When
+providing a PXE API, Etherboot must provide a mechanism for external
+code to call it from 16-bit real mode.
+
+Not all i386 builds of Etherboot will want to make real-mode calls.
+For example, when built for LinuxBIOS rather than the standard PCBIOS,
+no real-mode calls are necessary.
+
+For the ultimate in PXE compatibility, we may want to build Etherboot
+to run permanently in real mode.
+
+There is a wide variety of potential combinations of mode switches
+that we may wish to implement.  There are additional complications,
+such as the inability to access a high-memory stack when running in
+real mode.
+
+2. Transition libraries
+
+To handle all these various combinations of mode switches, we have
+several "transition" libraries in Etherboot.  We also have the concept
+of an "internal" and an "external" environment.  The internal
+environment is the environment within which we can execute C code.
+The external environment is the environment of whatever external code
+we're trying to interface to, such as the system BIOS or a PXE NBP.
+
+As well as having a separate addressing scheme, the internal
+environment also has a separate stack.
+
+The transition libraries are:
+
+a) librm
+
+librm handles transitions between an external 16-bit real-mode
+environment and an internal 32-bit protected-mode environment with
+virtual addresses.
+
+b) libkir
+
+libkir handles transitions between an external 16-bit real-mode (or
+16:16 or 16:32 protected-mode) environment and an internal 16-bit
+real-mode (or 16:16 protected-mode) environment.
+
+c) libpm
+
+libpm handles transitions between an external 32-bit protected-mode
+environment with flat physical addresses and an internal 32-bit
+protected-mode environment with virtual addresses.
+
+The transition libraries handle the transitions required when
+Etherboot is started up for the first time, the transitions required
+to execute any external code, and the transitions required when
+Etherboot exits (if it exits).  When Etherboot provides a PXE API,
+they also handle the transitions required when a PXE client makes a
+PXE API call to Etherboot.
+
+Etherboot may use multiple transition libraries.  For example, an
+Etherboot ELF image does not require librm for its initial transitions
+from prefix to runtime, but may require librm for calling external
+real-mode functions.
+
+3. Setup and initialisation
+
+Etherboot is conceptually divided into the prefix, the decompressor,
+and the runtime image.  (For non-compressed images, the decompressor
+is a no-op.)  The complete image comprises all three parts and is
+distinct from the runtime image, which exclude the prefix and the
+decompressor.
+
+The prefix does several tasks:
+
+  Load the complete image into memory.  (For example, the floppy
+  prefix issues BIOS calls to load the remainder of the complete image
+  from the floppy disk into RAM, and the ISA ROM prefix copies the ROM
+  contents into RAM for faster access.)
+
+  Call the decompressor, if the runtime image is compressed.  This
+  decompresses the runtime image.
+
+  Call the runtime image's setup() routine.  This is a routine
+  implemented in assembly code which sets up the internal environment
+  so that C code can execute.
+
+  Call the runtime image's arch_initialise() routine.  This is a
+  routine implemented in C which does some basic startup tasks, such
+  as initialising the console device, obtaining a memory map and
+  relocating the runtime image to high memory.
+
+  Call the runtime image's arch_main() routine.  This records the exit
+  mechanism requested by the prefix and calls main().  (The prefix
+  needs to register an exit mechanism because by the time main()
+  returns, the memory occupied by the prefix has most likely been
+  overwritten.)
+
+When acting as a PXE ROM, the ROM prefix contains an UNDI loader
+routine in addition to its usual code.  The UNDI loader performs a
+similar sequence of steps:
+
+  Load the complete image into memory.
+
+  Call the decompressor.
+
+  Call the runtime image's setup() routine.
+
+  Call the runtime image's arch_initialise() routine.
+
+  Call the runtime image's install_pxe_stack() routine.
+
+  Return to caller.
+
+The runtime image's setup() routine will perform the following steps:
+
+  Switch to the internal environment using an appropriate transition
+  library.  This will record the parameters of the external
+  environment.
+
+  Set up the internal environment: load a stack, and set up a GDT for
+  virtual addressing if virtual addressing is to be used.
+
+  Switch back to the external environment using the transition
+  library.  This will record the parameters of the internal
+  environment.
+
+Once the setup() routine has returned, the internal environment has been
+set up ready for C code to run.  The prefix can call C routines using
+a function from the transition library.
+
+The runtime image's arch_initialise() routine will perform the
+following steps:
+
+  Zero the bss
+
+  Initialise the console device(s) and print a welcome message.
+
+  Obtain a memory map via the INT 15,E820 BIOS call or suitable
+  fallback mechanism. [not done if libkir is being used]
+
+  Relocate the runtime image to the top of high memory. [not done if
+  libkir is being used]
+
+  Install librm to base memory. [done only if librm is being used]
+
+  Call initialise().
+
+  Return to the prefix, setting registers to indicate to the prefix
+  the new location of the transition library, if applicable.  Which
+  registers these are is specific to the transition library being
+  used.
+
+Once the arch_initialise() routine has returned, the prefix will
+probably call arch_main().
diff --git a/gpxe/src/arch/i386/core/aout_loader.c b/gpxe/src/arch/i386/core/aout_loader.c
new file mode 100644
index 0000000..f85620e
--- /dev/null
+++ b/gpxe/src/arch/i386/core/aout_loader.c
@@ -0,0 +1,144 @@
+/* a.out */
+struct exec {
+	unsigned long      a_midmag;	/* flags<<26 | mid<<16 | magic */
+	unsigned long      a_text;	/* text segment size */
+	unsigned long      a_data;	/* initialized data size */
+	unsigned long      a_bss;	/* uninitialized data size */
+	unsigned long      a_syms;	/* symbol table size */
+	unsigned long      a_entry;	/* entry point */
+	unsigned long      a_trsize;	/* text relocation size */
+	unsigned long      a_drsize;	/* data relocation size */
+};
+
+struct aout_state {
+	struct exec head;
+	unsigned long curaddr;
+	int segment;			/* current segment number, -1 for none */
+	unsigned long loc;		/* start offset of current block */
+	unsigned long skip;		/* padding to be skipped to current segment */
+	unsigned long toread;		/* remaining data to be read in the segment */
+};
+
+static struct aout_state astate;
+
+static sector_t aout_download(unsigned char *data, unsigned int len, int eof);
+static inline os_download_t aout_probe(unsigned char *data, unsigned int len)
+{
+	unsigned long start, mid, end, istart, iend;
+	if (len < sizeof(astate.head)) {
+		return 0;
+	}
+	memcpy(&astate.head, data, sizeof(astate.head));
+	if ((astate.head.a_midmag & 0xffff) != 0x010BL) {
+		return 0;
+	}
+	
+	printf("(a.out");
+	aout_freebsd_probe();
+	printf(")... ");
+	/* Check the aout image */
+	start  = astate.head.a_entry;
+	mid    = (((start + astate.head.a_text) + 4095) & ~4095) + astate.head.a_data;
+	end    = ((mid + 4095) & ~4095) + astate.head.a_bss;
+	istart = 4096;
+	iend   = istart + (mid - start);
+	if (!prep_segment(start, mid, end, istart, iend))
+		return dead_download;
+	astate.segment = -1;
+	astate.loc = 0;
+	astate.skip = 0;
+	astate.toread = 0;
+	return aout_download;
+}
+
+static sector_t aout_download(unsigned char *data, unsigned int len, int eof)
+{
+	unsigned int offset;	/* working offset in the current data block */
+
+	offset = 0;
+
+#ifdef AOUT_LYNX_KDI
+	astate.segment++;
+	if (astate.segment == 0) {
+		astate.curaddr = 0x100000;
+		astate.head.a_entry = astate.curaddr + 0x20;
+	}
+	memcpy(phys_to_virt(astate.curaddr), data, len);
+	astate.curaddr += len;
+	return 0;
+#endif
+
+	do {
+		if (astate.segment != -1) {
+			if (astate.skip) {
+				if (astate.skip >= len - offset) {
+					astate.skip -= len - offset;
+					break;
+				}
+				offset += astate.skip;
+				astate.skip = 0;
+			}
+
+			if (astate.toread) {
+				if (astate.toread >= len - offset) {
+					memcpy(phys_to_virt(astate.curaddr), data+offset,
+						len - offset);
+					astate.curaddr += len - offset;
+					astate.toread -= len - offset;
+					break;
+				}
+				memcpy(phys_to_virt(astate.curaddr), data+offset, astate.toread);
+				offset += astate.toread;
+				astate.toread = 0;
+			}
+		}
+
+		/* Data left, but current segment finished - look for the next
+		 * segment.  This is quite simple for a.out files.  */
+		astate.segment++;
+		switch (astate.segment) {
+		case 0:
+			/* read text */
+			astate.curaddr = astate.head.a_entry;
+			astate.skip = 4096;
+			astate.toread = astate.head.a_text;
+			break;
+		case 1:
+			/* read data */
+			/* skip and curaddr may be wrong, but I couldn't find
+			 * examples where this failed.  There is no reasonable
+			 * documentation for a.out available.  */
+			astate.skip = ((astate.curaddr + 4095) & ~4095) - astate.curaddr;
+			astate.curaddr = (astate.curaddr + 4095) & ~4095;
+			astate.toread = astate.head.a_data;
+			break;
+		case 2:
+			/* initialize bss and start kernel */
+			astate.curaddr = (astate.curaddr + 4095) & ~4095;
+			astate.skip = 0;
+			astate.toread = 0;
+			memset(phys_to_virt(astate.curaddr), '\0', astate.head.a_bss);
+			goto aout_startkernel;
+		default:
+			break;
+		}
+	} while (offset < len);
+
+	astate.loc += len;
+
+	if (eof) {
+		unsigned long entry;
+
+aout_startkernel:
+		entry = astate.head.a_entry;
+		done(1);
+
+		aout_freebsd_boot();
+#ifdef AOUT_LYNX_KDI
+		xstart32(entry);
+#endif
+		printf("unexpected a.out variant\n");
+		longjmp(restart_etherboot, -2);
+	}
+	return 0;
+}
diff --git a/gpxe/src/arch/i386/core/basemem_packet.c b/gpxe/src/arch/i386/core/basemem_packet.c
new file mode 100644
index 0000000..d487cce
--- /dev/null
+++ b/gpxe/src/arch/i386/core/basemem_packet.c
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/**
+ * @file
+ *
+ * Packet buffer in base memory.  Used by various components which
+ * need to pass packets to and from external real-mode code.
+ *
+ */
+
+#include <basemem_packet.h>
+
+#undef basemem_packet
+char __bss16_array ( basemem_packet, [BASEMEM_PACKET_LEN] );
diff --git a/gpxe/src/arch/i386/core/cpu.c b/gpxe/src/arch/i386/core/cpu.c
new file mode 100644
index 0000000..c24fa4e
--- /dev/null
+++ b/gpxe/src/arch/i386/core/cpu.c
@@ -0,0 +1,73 @@
+#include <stdint.h>
+#include <string.h>
+#include <cpu.h>
+
+/** @file
+ *
+ * CPU identification
+ *
+ */
+
+/**
+ * Test to see if CPU flag is changeable
+ *
+ * @v flag		Flag to test
+ * @ret can_change	Flag is changeable
+ */
+static inline int flag_is_changeable ( unsigned int flag ) {
+	uint32_t f1, f2;
+
+	__asm__ ( "pushfl\n\t"
+		  "pushfl\n\t"
+		  "popl %0\n\t"
+		  "movl %0,%1\n\t"
+		  "xorl %2,%0\n\t"
+		  "pushl %0\n\t"
+		  "popfl\n\t"
+		  "pushfl\n\t"
+		  "popl %0\n\t"
+		  "popfl\n\t"
+		  : "=&r" ( f1 ), "=&r" ( f2 )
+		  : "ir" ( flag ) );
+
+	return ( ( ( f1 ^ f2 ) & flag ) != 0 );
+}
+
+/**
+ * Get CPU information
+ *
+ * @v cpu		CPU information structure to fill in
+ */
+void get_cpuinfo ( struct cpuinfo_x86 *cpu ) {
+	unsigned int cpuid_level;
+	unsigned int cpuid_extlevel;
+	unsigned int discard_1, discard_2, discard_3;
+
+	memset ( cpu, 0, sizeof ( *cpu ) );
+
+	/* Check for CPUID instruction */
+	if ( ! flag_is_changeable ( X86_EFLAGS_ID ) ) {
+		DBG ( "CPUID not supported\n" );
+		return;
+	}
+
+	/* Get features, if present */
+	cpuid ( 0x00000000, &cpuid_level, &discard_1,
+		&discard_2, &discard_3 );
+	if ( cpuid_level >= 0x00000001 ) {
+		cpuid ( 0x00000001, &discard_1, &discard_2,
+			&discard_3, &cpu->features );
+	} else {
+		DBG ( "CPUID cannot return capabilities\n" );
+	}
+
+	/* Get 64-bit features, if present */
+	cpuid ( 0x80000000, &cpuid_extlevel, &discard_1,
+		&discard_2, &discard_3 );
+	if ( ( cpuid_extlevel & 0xffff0000 ) == 0x80000000 ) {
+		if ( cpuid_extlevel >= 0x80000001 ) {
+			cpuid ( 0x80000001, &discard_1, &discard_2,
+				&discard_3, &cpu->amd_features );
+		}
+	}
+}
diff --git a/gpxe/src/arch/i386/core/dumpregs.c b/gpxe/src/arch/i386/core/dumpregs.c
new file mode 100644
index 0000000..82dc218
--- /dev/null
+++ b/gpxe/src/arch/i386/core/dumpregs.c
@@ -0,0 +1,23 @@
+#include <stdio.h>
+#include <realmode.h>
+
+void __asmcall _dump_regs ( struct i386_all_regs *ix86 ) {
+
+	__asm__ __volatile__ (
+		TEXT16_CODE ( ".globl dump_regs\n\t"
+			      "\ndump_regs:\n\t"
+			      "pushl $_dump_regs\n\t"
+			      "pushw %%cs\n\t"
+			      "call prot_call\n\t"
+			      "addr32 leal 4(%%esp), %%esp\n\t"
+			      "ret\n\t" ) : : );
+
+	printf ( "EAX=%08x EBX=%08x ECX=%08x EDX=%08x\n"
+		 "ESI=%08x EDI=%08x EBP=%08x ESP=%08x\n"
+		 "CS=%04x SS=%04x DS=%04x ES=%04x FS=%04x GS=%04x\n",
+		 ix86->regs.eax, ix86->regs.ebx, ix86->regs.ecx,
+		 ix86->regs.edx, ix86->regs.esi, ix86->regs.edi,
+		 ix86->regs.ebp, ix86->regs.esp,
+		 ix86->segs.cs, ix86->segs.ss, ix86->segs.ds,
+		 ix86->segs.es, ix86->segs.fs, ix86->segs.gs );
+}
diff --git a/gpxe/src/arch/i386/core/freebsd_loader.c b/gpxe/src/arch/i386/core/freebsd_loader.c
new file mode 100644
index 0000000..464f6d9
--- /dev/null
+++ b/gpxe/src/arch/i386/core/freebsd_loader.c
@@ -0,0 +1,377 @@
+/* bootinfo */
+#define BOOTINFO_VERSION 1
+#define NODEV           (-1)    /* non-existent device */
+#define PAGE_SHIFT      12              /* LOG2(PAGE_SIZE) */
+#define PAGE_SIZE       (1<<PAGE_SHIFT) /* bytes/page */
+#define PAGE_MASK       (PAGE_SIZE-1)
+#define N_BIOS_GEOM     8
+
+struct bootinfo {
+        unsigned int            bi_version;
+        const unsigned char     *bi_kernelname;
+        struct nfs_diskless     *bi_nfs_diskless;
+                                /* End of fields that are always present. */
+#define bi_endcommon            bi_n_bios_used
+        unsigned int            bi_n_bios_used;
+        unsigned long           bi_bios_geom[N_BIOS_GEOM];
+        unsigned int            bi_size;
+        unsigned char           bi_memsizes_valid;
+        unsigned char           bi_pad[3];
+        unsigned long           bi_basemem;
+        unsigned long           bi_extmem;
+        unsigned long           bi_symtab;
+        unsigned long           bi_esymtab;
+	/* Note that these are in the FreeBSD headers but were not here... */
+	unsigned long           bi_kernend;		/* end of kernel space */
+	unsigned long           bi_envp;		/* environment */
+	unsigned long           bi_modulep;		/* preloaded modules */
+};
+
+static struct bootinfo bsdinfo;
+
+#ifdef ELF_IMAGE
+static Elf32_Shdr *shdr;	/* To support the FreeBSD kludge! */
+static Address symtab_load;
+static Address symstr_load;
+static int symtabindex;
+static int symstrindex;
+#endif
+
+static enum {
+	Unknown, Tagged, Aout, Elf, Aout_FreeBSD, Elf_FreeBSD,
+} image_type = Unknown;
+
+static unsigned int off;
+
+
+#ifdef ELF_IMAGE
+static void elf_freebsd_probe(void)
+{
+	image_type = Elf;
+	if (	(estate.e.elf32.e_entry & 0xf0000000) && 
+		(estate.e.elf32.e_type == ET_EXEC))
+	{
+		image_type = Elf_FreeBSD;
+		printf("/FreeBSD");
+		off = -(estate.e.elf32.e_entry & 0xff000000);
+		estate.e.elf32.e_entry += off;
+	}
+	/* Make sure we have a null to start with... */
+	shdr = 0;
+	
+	/* Clear the symbol index values... */
+	symtabindex = -1;
+	symstrindex = -1;
+	
+	/* ...and the load addresses of the symbols  */
+	symtab_load = 0;
+	symstr_load = 0;
+}
+
+static void elf_freebsd_fixup_segment(void)
+{
+	if (image_type == Elf_FreeBSD) {
+		estate.p.phdr32[estate.segment].p_paddr += off;
+	}
+}
+
+static void elf_freebsd_find_segment_end(void)
+{
+	/* Count the bytes read even for the last block
+	 * as we will need to know where the last block
+	 * ends in order to load the symbols correctly.
+	 * (plus it could be useful elsewhere...)
+	 * Note that we need to count the actual size,
+	 * not just the end of the disk image size.
+	 */
+	estate.curaddr += 
+		(estate.p.phdr32[estate.segment].p_memsz - 
+		estate.p.phdr32[estate.segment].p_filesz);
+}
+
+static int elf_freebsd_debug_loader(unsigned int offset)
+{
+	/* No more segments to be loaded - time to start the
+	 * nasty state machine to support the loading of
+	 * FreeBSD debug symbols due to the fact that FreeBSD
+	 * uses/exports the kernel's debug symbols in order
+	 * to make much of the system work!  Amazing (arg!)
+	 *
+	 * We depend on the fact that for the FreeBSD kernel,
+	 * there is only one section of debug symbols and that
+	 * the section is after all of the loaded sections in
+	 * the file.  This assumes a lot but is somewhat required
+	 * to make this code not be too annoying.  (Where do you
+	 * load symbols when the code has not loaded yet?)
+	 * Since this function is actually just a callback from
+	 * the network data transfer code, we need to be able to
+	 * work with the data as it comes in.  There is no chance
+	 * for doing a seek other than forwards.
+	 *
+	 * The process we use is to first load the section
+	 * headers.  Once they are loaded (shdr != 0) we then
+	 * look for where the symbol table and symbol table
+	 * strings are and setup some state that we found
+	 * them and fall into processing the first one (which
+	 * is the symbol table) and after that has been loaded,
+	 * we try the symbol strings.  Note that the order is
+	 * actually required as the memory image depends on
+	 * the symbol strings being loaded starting at the
+	 * end of the symbol table.  The kernel assumes this
+	 * layout of the image.
+	 *
+	 * At any point, if we get to the end of the load file
+	 * or the section requested is earlier in the file than
+	 * the current file pointer, we just end up falling
+	 * out of this and booting the kernel without this
+	 * information.
+	 */
+
+	/* Make sure that the next address is long aligned... */
+	/* Assumes size of long is a power of 2... */
+	estate.curaddr = (estate.curaddr + sizeof(long) - 1) & ~(sizeof(long) - 1);
+	
+	/* If we have not yet gotten the shdr loaded, try that */
+	if (shdr == 0)
+	{
+		estate.toread = estate.e.elf32.e_shnum * estate.e.elf32.e_shentsize;
+		estate.skip = estate.e.elf32.e_shoff - (estate.loc + offset);
+		if (estate.toread)
+		{
+#if ELF_DEBUG
+			printf("shdr *, size %lX, curaddr %lX\n", 
+				estate.toread, estate.curaddr);
+#endif
+			
+			/* Start reading at the curaddr and make that the shdr */
+			shdr = (Elf32_Shdr *)phys_to_virt(estate.curaddr);
+			
+			/* Start to read... */
+			return 1;
+		}
+	}
+	else
+	{
+		/* We have the shdr loaded, check if we have found
+		 * the indexs where the symbols are supposed to be */
+		if ((symtabindex == -1) && (symstrindex == -1))
+		{
+			int i;
+			/* Make sure that the address is page aligned... */
+			/* Symbols need to start in their own page(s)... */
+			estate.curaddr = (estate.curaddr + 4095) & ~4095;
+			
+			/* Need to make new indexes... */
+			for (i=0; i < estate.e.elf32.e_shnum; i++)
+			{
+				if (shdr[i].sh_type == SHT_SYMTAB)
+				{
+					int j;
+					for (j=0; j < estate.e.elf32.e_phnum; j++)
+					{
+						/* Check only for loaded sections */
+						if ((estate.p.phdr32[j].p_type | 0x80) == (PT_LOAD | 0x80))
+						{
+							/* Only the extra symbols */
+							if ((shdr[i].sh_offset >= estate.p.phdr32[j].p_offset) &&
+								((shdr[i].sh_offset + shdr[i].sh_size) <=
+									(estate.p.phdr32[j].p_offset + estate.p.phdr32[j].p_filesz)))
+							{
+								shdr[i].sh_offset=0;
+								shdr[i].sh_size=0;
+								break;
+							}
+						}
+					}
+					if ((shdr[i].sh_offset != 0) && (shdr[i].sh_size != 0))
+					{
+						symtabindex = i;
+						symstrindex = shdr[i].sh_link;
+					}
+				}
+			}
+		}
+		
+		/* Check if we have a symbol table index and have not loaded it */
+		if ((symtab_load == 0) && (symtabindex >= 0))
+		{
+			/* No symbol table yet?  Load it first... */
+			
+			/* This happens to work out in a strange way.
+			 * If we are past the point in the file already,
+			 * we will skip a *large* number of bytes which
+			 * ends up bringing us to the end of the file and
+			 * an old (default) boot.  Less code and lets
+			 * the state machine work in a cleaner way but this
+			 * is a nasty side-effect trick... */
+			estate.skip = shdr[symtabindex].sh_offset - (estate.loc + offset);
+			
+			/* And we need to read this many bytes... */
+			estate.toread = shdr[symtabindex].sh_size;
+			
+			if (estate.toread)
+			{
+#if ELF_DEBUG
+				printf("db sym, size %lX, curaddr %lX\n", 
+					estate.toread, estate.curaddr);
+#endif
+				/* Save where we are loading this... */
+				symtab_load = estate.curaddr;
+				
+				*((long *)phys_to_virt(estate.curaddr)) = estate.toread;
+				estate.curaddr += sizeof(long);
+				
+				/* Start to read... */
+				return 1;
+			}
+		}
+		else if ((symstr_load == 0) && (symstrindex >= 0))
+		{
+			/* We have already loaded the symbol table, so
+			 * now on to the symbol strings... */
+			
+			
+			/* Same nasty trick as above... */
+			estate.skip = shdr[symstrindex].sh_offset - (estate.loc + offset);
+			
+			/* And we need to read this many bytes... */
+			estate.toread = shdr[symstrindex].sh_size;
+			
+			if (estate.toread)
+			{
+#if ELF_DEBUG
+				printf("db str, size %lX, curaddr %lX\n", 
+					estate.toread, estate.curaddr);
+#endif
+				/* Save where we are loading this... */
+				symstr_load = estate.curaddr;
+				
+				*((long *)phys_to_virt(estate.curaddr)) = estate.toread;
+				estate.curaddr += sizeof(long);
+				
+				/* Start to read... */
+				return 1;
+			}
+		}
+	}
+	/* all done */
+	return 0;
+}
+
+static void elf_freebsd_boot(unsigned long entry) 
+{
+	if (image_type != Elf_FreeBSD)
+		return;
+
+	memset(&bsdinfo, 0, sizeof(bsdinfo));
+	bsdinfo.bi_basemem = meminfo.basememsize;
+	bsdinfo.bi_extmem = meminfo.memsize;
+	bsdinfo.bi_memsizes_valid = 1;
+	bsdinfo.bi_version = BOOTINFO_VERSION;
+	bsdinfo.bi_kernelname = virt_to_phys(KERNEL_BUF);
+	bsdinfo.bi_nfs_diskless = NULL;
+	bsdinfo.bi_size = sizeof(bsdinfo);
+#define RB_BOOTINFO     0x80000000      /* have `struct bootinfo *' arg */  
+	if(freebsd_kernel_env[0] != '\0'){
+		freebsd_howto |= RB_BOOTINFO;
+		bsdinfo.bi_envp = (unsigned long)freebsd_kernel_env;
+	}
+	
+	/* Check if we have symbols loaded, and if so,
+	 * made the meta_data needed to pass those to
+	 * the kernel. */
+	if ((symtab_load !=0) && (symstr_load != 0))
+	{
+		unsigned long *t;
+		
+		bsdinfo.bi_symtab = symtab_load;
+		
+		/* End of symbols (long aligned...) */
+		/* Assumes size of long is a power of 2... */
+		bsdinfo.bi_esymtab = (symstr_load +
+			sizeof(long) +
+			*((long *)phys_to_virt(symstr_load)) +
+			sizeof(long) - 1) & ~(sizeof(long) - 1);
+		
+		/* Where we will build the meta data... */
+		t = phys_to_virt(bsdinfo.bi_esymtab);
+		
+#if ELF_DEBUG
+		printf("Metadata at %lX\n",t);
+#endif
+		
+		/* Set up the pointer to the memory... */
+		bsdinfo.bi_modulep = virt_to_phys(t);
+		
+		/* The metadata structure is an array of 32-bit
+		 * words where we store some information about the
+		 * system.  This is critical, as FreeBSD now looks
+		 * only for the metadata for the extended symbol
+		 * information rather than in the bootinfo.
+		 */
+		/* First, do the kernel name and the kernel type */
+		/* Note that this assumed x86 byte order... */
+		
+		/* 'kernel\0\0' */
+		*t++=MODINFO_NAME; *t++= 7; *t++=0x6E72656B; *t++=0x00006C65;
+		
+		/* 'elf kernel\0\0' */
+		*t++=MODINFO_TYPE; *t++=11; *t++=0x20666C65; *t++=0x6E72656B; *t++ = 0x00006C65;
+		
+		/* Now the symbol start/end - note that they are
+		 * here in local/physical address - the Kernel
+		 * boot process will relocate the addresses. */
+		*t++=MODINFOMD_SSYM | MODINFO_METADATA; *t++=sizeof(*t); *t++=bsdinfo.bi_symtab;
+		*t++=MODINFOMD_ESYM | MODINFO_METADATA; *t++=sizeof(*t); *t++=bsdinfo.bi_esymtab;
+		
+		*t++=MODINFO_END; *t++=0; /* end of metadata */
+		
+		/* Since we have symbols we need to make
+		 * sure that the kernel knows its own end
+		 * of memory...  It is not _end but after
+		 * the symbols and the metadata... */
+		bsdinfo.bi_kernend = virt_to_phys(t);
+		
+		/* Signal locore.s that we have a valid bootinfo
+		 * structure that was completely filled in. */
+		freebsd_howto |= 0x80000000;
+	}
+	
+	xstart32(entry, freebsd_howto, NODEV, 0, 0, 0, 
+		virt_to_phys(&bsdinfo), 0, 0, 0);
+	longjmp(restart_etherboot, -2);
+}
+#endif
+
+#ifdef AOUT_IMAGE
+static void aout_freebsd_probe(void)
+{
+	image_type = Aout;
+	if (((astate.head.a_midmag >> 16) & 0xffff) == 0) {
+		/* Some other a.out variants have a different
+		 * value, and use other alignments (e.g. 1K),
+		 * not the 4K used by FreeBSD.  */
+		image_type = Aout_FreeBSD;
+		printf("/FreeBSD");
+		off = -(astate.head.a_entry & 0xff000000);
+		astate.head.a_entry += off;
+	}
+}
+
+static void aout_freebsd_boot(void)
+{
+	if (image_type == Aout_FreeBSD) {
+		memset(&bsdinfo, 0, sizeof(bsdinfo));
+		bsdinfo.bi_basemem = meminfo.basememsize;
+		bsdinfo.bi_extmem = meminfo.memsize;
+		bsdinfo.bi_memsizes_valid = 1;
+		bsdinfo.bi_version = BOOTINFO_VERSION;
+		bsdinfo.bi_kernelname = virt_to_phys(KERNEL_BUF);
+		bsdinfo.bi_nfs_diskless = NULL;
+		bsdinfo.bi_size = sizeof(bsdinfo);
+		xstart32(astate.head.a_entry, freebsd_howto, NODEV, 0, 0, 0, 
+			virt_to_phys(&bsdinfo), 0, 0, 0);
+		longjmp(restart_etherboot, -2);
+	}
+}
+#endif
diff --git a/gpxe/src/arch/i386/core/gdbidt.S b/gpxe/src/arch/i386/core/gdbidt.S
new file mode 100644
index 0000000..cd8b38a
--- /dev/null
+++ b/gpxe/src/arch/i386/core/gdbidt.S
@@ -0,0 +1,215 @@
+/*
+ * Interrupt Descriptor Table (IDT) setup and interrupt handlers for GDB stub.
+ */
+
+#include <librm.h>
+
+#define SIZEOF_I386_REGS	32
+#define SIZEOF_I386_FLAGS	4
+
+/****************************************************************************
+ * Interrupt Descriptor Table
+ ****************************************************************************
+ */
+	.section ".data16", "aw", @progbits
+	.globl idtr
+idtr:
+idt_limit:
+	.word	idt_length - 1
+idt_base:
+	.long	0
+
+/* IDT entries have the following format:
+ * offset_lo, segment selector, flags, offset_hi
+ *
+ * Since it is not possible to specify relocations in arbitrary
+ * expressions like (int_overflow & 0xffff), we initialise the
+ * IDT with entries in an incorrect format.
+ *
+ * The entries are shuffled into the correct format in init_librm().
+ */
+#define IDT_ENTRY_EMPTY(name) .word 0, 0, 0, 0
+#define IDT_ENTRY_PRESENT(name) \
+	.long	int_##name; \
+	.word	0x8e00, VIRTUAL_CS
+
+.align 16
+idt:
+	IDT_ENTRY_PRESENT(divide_error)
+	IDT_ENTRY_PRESENT(debug_trap)
+	IDT_ENTRY_EMPTY(non_maskable_interrupt)
+	IDT_ENTRY_PRESENT(breakpoint)
+	IDT_ENTRY_PRESENT(overflow)
+	IDT_ENTRY_PRESENT(bound_range_exceeded)
+	IDT_ENTRY_PRESENT(invalid_opcode)
+	IDT_ENTRY_EMPTY(device_not_available)
+	IDT_ENTRY_PRESENT(double_fault)
+	IDT_ENTRY_EMPTY(coprocessor_segment_overrun)
+	IDT_ENTRY_PRESENT(invalid_tss)
+	IDT_ENTRY_PRESENT(segment_not_present)
+	IDT_ENTRY_PRESENT(stack_segment_fault)
+	IDT_ENTRY_PRESENT(general_protection)
+	IDT_ENTRY_PRESENT(page_fault)
+idt_end:
+	.equ	idt_length, idt_end - idt
+
+/* The IDT entries are fixed up (once) in init_librm() */
+idt_fixed:
+	.byte	0
+
+/****************************************************************************
+ * idt_init (real-mode near call, 16-bit real-mode near return address)
+ *
+ * Initialise the IDT, called from init_librm.
+ *
+ * Parameters:
+ *   %eax : IDT base address
+ *
+ * Destroys %ax, %bx, and %di.
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+	.code16
+	.globl idt_init
+idt_init:
+	movl	%eax, idt_base
+	addl	$idt, idt_base
+
+	/* IDT entries are only fixed up once */
+	movb	idt_fixed, %al
+	orb	%al, %al
+	jnz	2f
+	movb	$1, idt_fixed
+
+	/* Shuffle IDT entries into the correct format */
+	movb	$(idt_length / 8), %al
+	movw	$idt, %bx
+	or	%al, %al
+	jz	2f
+1:
+	movw	2(%bx), %di
+	xchg	%di, 6(%bx)
+	movw	%di, 2(%bx)
+	addw	$8, %bx
+	dec	%al
+	jnz	1b
+2:
+	ret
+
+/****************************************************************************
+ * Interrupt handlers
+ ****************************************************************************
+ */
+	.section ".text", "ax", @progbits
+	.code32
+
+/* POSIX signal numbers for reporting traps to GDB */
+#define SIGILL 4
+#define SIGTRAP 5
+#define SIGBUS 7
+#define SIGFPE 8
+#define SIGSEGV 11
+#define SIGSTKFLT 16
+
+int_divide_error:
+	pushl	$SIGFPE
+	jmp	do_interrupt
+
+int_debug_trap:
+int_breakpoint:
+	pushl	$SIGTRAP
+	jmp	do_interrupt
+
+int_overflow:
+int_bound_range_exceeded:
+	pushl	$SIGSTKFLT
+	jmp	do_interrupt
+
+int_invalid_opcode:
+	pushl	$SIGILL
+	jmp	do_interrupt
+
+int_double_fault:
+	movl	$SIGBUS, (%esp)
+	jmp	do_interrupt
+
+int_invalid_tss:
+int_segment_not_present:
+int_stack_segment_fault:
+int_general_protection:
+int_page_fault:
+	movl	$SIGSEGV, (%esp)
+	jmp	do_interrupt
+
+/* When invoked, the stack contains: eflags, cs, eip, signo. */
+#define IH_OFFSET_GDB_REGS ( 0 )
+#define IH_OFFSET_GDB_EIP ( IH_OFFSET_GDB_REGS + SIZEOF_I386_REGS )
+#define IH_OFFSET_GDB_EFLAGS ( IH_OFFSET_GDB_EIP + 4 )
+#define IH_OFFSET_GDB_SEG_REGS ( IH_OFFSET_GDB_EFLAGS + SIZEOF_I386_FLAGS )
+#define IH_OFFSET_GDB_END ( IH_OFFSET_GDB_SEG_REGS + 6 * 4 )
+#define IH_OFFSET_SIGNO ( IH_OFFSET_GDB_END )
+#define IH_OFFSET_OLD_EIP ( IH_OFFSET_SIGNO + 4 )
+#define IH_OFFSET_OLD_CS ( IH_OFFSET_OLD_EIP + 4 )
+#define IH_OFFSET_OLD_EFLAGS ( IH_OFFSET_OLD_CS + 4 )
+#define IH_OFFSET_END ( IH_OFFSET_OLD_EFLAGS + 4 )
+
+/* We also access the stack whilst still storing or restoring
+ * the register snapshot.  Since ESP is in flux, we need
+ * special offsets.
+ */
+#define IH_OFFSET_FLUX_OLD_CS ( IH_OFFSET_OLD_CS - 44 )
+#define IH_OFFSET_FLUX_OLD_EFLAGS ( IH_OFFSET_OLD_EFLAGS - 40 )
+#define IH_OFFSET_FLUX_OLD_EIP ( IH_OFFSET_OLD_EIP - 36 )
+#define IH_OFFSET_FLUX_END ( IH_OFFSET_END - 20 )
+do_interrupt:
+	/* Store CPU state in GDB register snapshot */
+	pushw	$0
+	pushw	%gs
+	pushw	$0
+	pushw	%fs
+	pushw	$0
+	pushw	%es
+	pushw	$0
+	pushw	%ds
+	pushw	$0
+	pushw	%ss
+	pushw	$0
+	pushw	IH_OFFSET_FLUX_OLD_CS + 2(%esp)
+	pushl	IH_OFFSET_FLUX_OLD_EFLAGS(%esp)
+	pushl	IH_OFFSET_FLUX_OLD_EIP(%esp)
+	pushl	%edi
+	pushl	%esi
+	pushl	%ebp
+	leal	IH_OFFSET_FLUX_END(%esp), %edi
+	pushl	%edi /* old ESP */
+	pushl	%ebx
+	pushl	%edx
+	pushl	%ecx
+	pushl	%eax
+
+	/* Call GDB stub exception handler */
+	pushl	%esp
+	pushl	(IH_OFFSET_SIGNO + 4)(%esp)
+	call	gdbmach_handler
+	addl	$8, %esp
+
+	/* Restore CPU state from GDB register snapshot */
+	popl	%eax
+	popl	%ecx
+	popl	%edx
+	popl	%ebx
+	addl	$4, %esp /* Changing ESP currently not supported */
+	popl	%ebp
+	popl	%esi
+	popl	%edi
+	popl	IH_OFFSET_FLUX_OLD_EIP(%esp)
+	popl	IH_OFFSET_FLUX_OLD_EFLAGS(%esp)
+	popl	IH_OFFSET_FLUX_OLD_CS(%esp)
+	popl	%ss
+	popl	%ds
+	popl	%es
+	popl	%fs
+	popl	%gs
+
+	addl	$4, %esp /* drop signo */
+	iret
diff --git a/gpxe/src/arch/i386/core/gdbmach.c b/gpxe/src/arch/i386/core/gdbmach.c
new file mode 100644
index 0000000..97827ec
--- /dev/null
+++ b/gpxe/src/arch/i386/core/gdbmach.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2008 Stefan Hajnoczi <stefanha@gmail.com>.
+ *
+ * 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.
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <assert.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/gdbstub.h>
+#include <gdbmach.h>
+
+/** @file
+ *
+ * GDB architecture-specific bits for i386
+ *
+ */
+
+enum {
+	DR7_CLEAR = 0x00000400,    /* disable hardware breakpoints */
+	DR6_CLEAR = 0xffff0ff0,    /* clear breakpoint status */
+};
+
+/** Hardware breakpoint, fields stored in x86 bit pattern form */
+struct hwbp {
+	int type;           /* type (1=write watchpoint, 3=access watchpoint) */
+	unsigned long addr; /* linear address */
+	size_t len;         /* length (0=1-byte, 1=2-byte, 3=4-byte) */
+	int enabled;
+};
+
+static struct hwbp hwbps [ 4 ];
+static gdbreg_t dr7 = DR7_CLEAR;
+
+static struct hwbp *gdbmach_find_hwbp ( int type, unsigned long addr, size_t len ) {
+	struct hwbp *available = NULL;
+	unsigned int i;
+	for ( i = 0; i < sizeof hwbps / sizeof hwbps [ 0 ]; i++ ) {
+		if ( hwbps [ i ].type == type && hwbps [ i ].addr == addr && hwbps [ i ].len == len ) {
+			return &hwbps [ i ];
+		}
+		if ( !hwbps [ i ].enabled ) {
+			available = &hwbps [ i ];
+		}
+	}
+	return available;
+}
+
+static void gdbmach_commit_hwbp ( struct hwbp *bp ) {
+	unsigned int regnum = bp - hwbps;
+
+	/* Set breakpoint address */
+	assert ( regnum < ( sizeof hwbps / sizeof hwbps [ 0 ] ) );
+	switch ( regnum ) {
+		case 0:
+			__asm__ __volatile__ ( "movl %0, %%dr0\n" : : "r" ( bp->addr ) );
+			break;
+		case 1:
+			__asm__ __volatile__ ( "movl %0, %%dr1\n" : : "r" ( bp->addr ) );
+			break;
+		case 2:
+			__asm__ __volatile__ ( "movl %0, %%dr2\n" : : "r" ( bp->addr ) );
+			break;
+		case 3:
+			__asm__ __volatile__ ( "movl %0, %%dr3\n" : : "r" ( bp->addr ) );
+			break;
+	}
+
+	/* Set type */
+	dr7 &= ~( 0x3 << ( 16 + 4 * regnum ) );
+	dr7 |= bp->type << ( 16 + 4 * regnum );
+
+	/* Set length */
+	dr7 &= ~( 0x3 << ( 18 + 4 * regnum ) );
+	dr7 |= bp->len << ( 18 + 4 * regnum );
+
+	/* Set/clear local enable bit */
+	dr7 &= ~( 0x3 << 2 * regnum );
+ 	dr7 |= bp->enabled << 2 * regnum;
+}
+
+int gdbmach_set_breakpoint ( int type, unsigned long addr, size_t len, int enable ) {
+	struct hwbp *bp;
+	
+	/* Check and convert breakpoint type to x86 type */
+	switch ( type ) {
+		case GDBMACH_WATCH:
+			type = 0x1;
+			break;
+		case GDBMACH_AWATCH:
+			type = 0x3;
+			break;
+		default:
+			return 0; /* unsupported breakpoint type */
+	}
+
+	/* Only lengths 1, 2, and 4 are supported */
+	if ( len != 2 && len != 4 ) {
+		len = 1;
+	}
+	len--; /* convert to x86 breakpoint length bit pattern */
+
+	/* Calculate linear address by adding segment base */
+	addr += virt_offset;
+
+	/* Set up the breakpoint */
+	bp = gdbmach_find_hwbp ( type, addr, len );
+	if ( !bp ) {
+		return 0; /* ran out of hardware breakpoints */
+	}
+	bp->type = type;
+	bp->addr = addr;
+	bp->len = len;
+	bp->enabled = enable;
+	gdbmach_commit_hwbp ( bp );
+	return 1;
+}
+
+static void gdbmach_disable_hwbps ( void ) {
+	/* Store and clear hardware breakpoints */
+	__asm__ __volatile__ ( "movl %0, %%dr7\n" : : "r" ( DR7_CLEAR ) );
+}
+
+static void gdbmach_enable_hwbps ( void ) {
+	/* Clear breakpoint status register */
+	__asm__ __volatile__ ( "movl %0, %%dr6\n" : : "r" ( DR6_CLEAR ) );
+
+	/* Restore hardware breakpoints */
+	__asm__ __volatile__ ( "movl %0, %%dr7\n" : : "r" ( dr7 ) );
+}
+
+__asmcall void gdbmach_handler ( int signo, gdbreg_t *regs ) {
+	gdbmach_disable_hwbps();
+	gdbstub_handler ( signo, regs );
+	gdbmach_enable_hwbps();
+}
diff --git a/gpxe/src/arch/i386/core/nulltrap.c b/gpxe/src/arch/i386/core/nulltrap.c
new file mode 100644
index 0000000..3046fbe
--- /dev/null
+++ b/gpxe/src/arch/i386/core/nulltrap.c
@@ -0,0 +1,51 @@
+#include <stdint.h>
+#include <stdio.h>
+
+__attribute__ (( noreturn, section ( ".text.null_trap" ) ))
+void null_function_trap ( void ) {
+	void *stack;
+
+	/* 128 bytes of NOPs; the idea of this is that if something
+	 * dereferences a NULL pointer and overwrites us, we at least
+	 * have some chance of still getting to execute the printf()
+	 * statement.
+	 */
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+	__asm__ __volatile__ ( "nop ; nop ; nop ; nop" );
+
+	__asm__ __volatile__ ( "movl %%esp, %0" : "=r" ( stack ) );
+	printf ( "NULL method called from %p (stack %p)\n", 
+		 __builtin_return_address ( 0 ), stack );
+	DBG_HD ( stack, 256 );
+	while ( 1 ) {}
+}
diff --git a/gpxe/src/arch/i386/core/pic8259.c b/gpxe/src/arch/i386/core/pic8259.c
new file mode 100644
index 0000000..1e2d23c
--- /dev/null
+++ b/gpxe/src/arch/i386/core/pic8259.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <gpxe/io.h>
+#include <pic8259.h>
+
+/** @file
+ *
+ * Minimal support for the 8259 Programmable Interrupt Controller
+ *
+ */
+
+/**
+ * Send non-specific EOI(s)
+ *
+ * @v irq		IRQ number
+ *
+ * This seems to be inherently unsafe.
+ */
+static inline void send_nonspecific_eoi ( unsigned int irq ) {
+	DBG ( "Sending non-specific EOI for IRQ %d\n", irq );
+	if ( irq >= IRQ_PIC_CUTOFF ) {
+		outb ( ICR_EOI_NON_SPECIFIC, PIC2_ICR );
+	}		
+	outb ( ICR_EOI_NON_SPECIFIC, PIC1_ICR );
+}
+
+/**
+ * Send specific EOI(s)
+ *
+ * @v irq		IRQ number
+ */
+static inline void send_specific_eoi ( unsigned int irq ) {
+	DBG ( "Sending specific EOI for IRQ %d\n", irq );
+	if ( irq >= IRQ_PIC_CUTOFF ) {
+		outb ( ( ICR_EOI_SPECIFIC | ICR_VALUE ( CHAINED_IRQ ) ),
+		       ICR_REG ( CHAINED_IRQ ) );
+	}
+	outb ( ( ICR_EOI_SPECIFIC | ICR_VALUE ( irq ) ), ICR_REG ( irq ) );
+}
+
+/**
+ * Send End-Of-Interrupt to the PIC
+ *
+ * @v irq		IRQ number
+ */
+void send_eoi ( unsigned int irq ) {
+	send_specific_eoi ( irq );
+}
diff --git a/gpxe/src/arch/i386/core/rdtsc_timer.c b/gpxe/src/arch/i386/core/rdtsc_timer.c
new file mode 100644
index 0000000..7667917
--- /dev/null
+++ b/gpxe/src/arch/i386/core/rdtsc_timer.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/** @file
+ *
+ * RDTSC timer
+ *
+ */
+
+#include <assert.h>
+#include <gpxe/timer.h>
+#include <gpxe/timer2.h>
+
+/**
+ * Number of TSC ticks per microsecond
+ *
+ * This is calibrated on the first use of the timer.
+ */
+static unsigned long rdtsc_ticks_per_usec;
+
+/**
+ * Delay for a fixed number of microseconds
+ *
+ * @v usecs		Number of microseconds for which to delay
+ */
+static void rdtsc_udelay ( unsigned long usecs ) {
+	unsigned long start;
+	unsigned long elapsed;
+
+	/* Sanity guard, since we may divide by this */
+	if ( ! usecs )
+		usecs = 1;
+
+	start = currticks();
+	if ( rdtsc_ticks_per_usec ) {
+		/* Already calibrated; busy-wait until done */
+		do {
+			elapsed = ( currticks() - start );
+		} while ( elapsed < ( usecs * rdtsc_ticks_per_usec ) );
+	} else {
+		/* Not yet calibrated; use timer2 and calibrate
+		 * based on result.
+		 */
+		timer2_udelay ( usecs );
+		elapsed = ( currticks() - start );
+		rdtsc_ticks_per_usec = ( elapsed / usecs );
+		DBG ( "RDTSC timer calibrated: %ld ticks in %ld usecs "
+		      "(%ld MHz)\n", elapsed, usecs,
+		      ( rdtsc_ticks_per_usec << TSC_SHIFT ) );
+	}
+}
+
+/**
+ * Get number of ticks per second
+ *
+ * @ret ticks_per_sec	Number of ticks per second
+ */
+static unsigned long rdtsc_ticks_per_sec ( void ) {
+
+	/* Calibrate timer, if not already done */
+	if ( ! rdtsc_ticks_per_usec )
+		udelay ( 1 );
+
+	/* Sanity check */
+	assert ( rdtsc_ticks_per_usec != 0 );
+
+	return ( rdtsc_ticks_per_usec * 1000 * 1000 );
+}
+
+PROVIDE_TIMER ( rdtsc, udelay, rdtsc_udelay );
+PROVIDE_TIMER_INLINE ( rdtsc, currticks );
+PROVIDE_TIMER ( rdtsc, ticks_per_sec, rdtsc_ticks_per_sec );
diff --git a/gpxe/src/arch/i386/core/relocate.c b/gpxe/src/arch/i386/core/relocate.c
new file mode 100644
index 0000000..44e764f
--- /dev/null
+++ b/gpxe/src/arch/i386/core/relocate.c
@@ -0,0 +1,170 @@
+#include <gpxe/io.h>
+#include <registers.h>
+#include <gpxe/memmap.h>
+
+/*
+ * Originally by Eric Biederman
+ *
+ * Heavily modified by Michael Brown 
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/*
+ * The linker passes in the symbol _max_align, which is the alignment
+ * that we must preserve, in bytes.
+ *
+ */
+extern char _max_align[];
+#define max_align ( ( unsigned int ) _max_align )
+
+/* Linker symbols */
+extern char _textdata[];
+extern char _etextdata[];
+
+/* within 1MB of 4GB is too close. 
+ * MAX_ADDR is the maximum address we can easily do DMA to.
+ *
+ * Not sure where this constraint comes from, but kept it from Eric's
+ * old code - mcb30
+ */
+#define MAX_ADDR (0xfff00000UL)
+
+/**
+ * Relocate Etherboot
+ *
+ * @v ix86		x86 register dump from prefix
+ * @ret ix86		x86 registers to return to prefix
+ *
+ * This finds a suitable location for Etherboot near the top of 32-bit
+ * address space, and returns the physical address of the new location
+ * to the prefix in %edi.
+ */
+__asmcall void relocate ( struct i386_all_regs *ix86 ) {
+	struct memory_map memmap;
+	unsigned long start, end, size, padded_size;
+	unsigned long new_start, new_end;
+	unsigned i;
+
+	/* Get memory map and current location */
+	get_memmap ( &memmap );
+	start = virt_to_phys ( _textdata );
+	end = virt_to_phys ( _etextdata );
+	size = ( end - start );
+	padded_size = ( size + max_align - 1 );
+
+	DBG ( "Relocate: currently at [%lx,%lx)\n"
+	      "...need %lx bytes for %d-byte alignment\n",
+	      start, end, padded_size, max_align );
+
+	/* Walk through the memory map and find the highest address
+	 * below 4GB that etherboot will fit into.  Ensure etherboot
+	 * lies entirely within a range with A20=0.  This means that
+	 * even if something screws up the state of the A20 line, the
+	 * etherboot code is still visible and we have a chance to
+	 * diagnose the problem.
+	 */
+	new_end = end;
+	for ( i = 0 ; i < memmap.count ; i++ ) {
+		struct memory_region *region = &memmap.regions[i];
+		unsigned long r_start, r_end;
+
+		DBG ( "Considering [%llx,%llx)\n", region->start, region->end);
+		
+		/* Truncate block to MAX_ADDR.  This will be less than
+		 * 4GB, which means that we can get away with using
+		 * just 32-bit arithmetic after this stage.
+		 */
+		if ( region->start > MAX_ADDR ) {
+			DBG ( "...starts after MAX_ADDR=%lx\n", MAX_ADDR );
+			continue;
+		}
+		r_start = region->start;
+		if ( region->end > MAX_ADDR ) {
+			DBG ( "...end truncated to MAX_ADDR=%lx\n", MAX_ADDR );
+			r_end = MAX_ADDR;
+		} else {
+			r_end = region->end;
+		}
+		
+		/* Shrink the range down to use only even megabytes
+		 * (i.e. A20=0).
+		 */
+		if ( ( r_end - 1 ) & 0x100000 ) {
+			/* If last byte that might be used (r_end-1)
+			 * is in an odd megabyte, round down r_end to
+			 * the top of the next even megabyte.
+			 *
+			 * Make sure that we don't accidentally wrap
+			 * r_end below 0.
+			 */
+			if ( r_end >= 1 ) {
+				r_end = ( r_end - 1 ) & ~0xfffff;
+				DBG ( "...end truncated to %lx "
+				      "(avoid ending in odd megabyte)\n",
+				      r_end );
+			}
+		} else if ( ( r_end - size ) & 0x100000 ) {
+			/* If the last byte that might be used
+			 * (r_end-1) is in an even megabyte, but the
+			 * first byte that might be used (r_end-size)
+			 * is an odd megabyte, round down to the top
+			 * of the next even megabyte.
+			 * 
+			 * Make sure that we don't accidentally wrap
+			 * r_end below 0.
+			 */
+			if ( r_end >= 0x100000 ) {
+				r_end = ( r_end - 0x100000 ) & ~0xfffff;
+				DBG ( "...end truncated to %lx "
+				      "(avoid starting in odd megabyte)\n",
+				      r_end );
+			}
+		}
+
+		DBG ( "...usable portion is [%lx,%lx)\n", r_start, r_end );
+
+		/* If we have rounded down r_end below r_ start, skip
+		 * this block.
+		 */
+		if ( r_end < r_start ) {
+			DBG ( "...truncated to negative size\n" );
+			continue;
+		}
+
+		/* Check that there is enough space to fit in Etherboot */
+		if ( ( r_end - r_start ) < size ) {
+			DBG ( "...too small (need %lx bytes)\n", size );
+			continue;
+		}
+
+		/* If the start address of the Etherboot we would
+		 * place in this block is higher than the end address
+		 * of the current highest block, use this block.
+		 *
+		 * Note that this avoids overlaps with the current
+		 * Etherboot, as well as choosing the highest of all
+		 * viable blocks.
+		 */
+		if ( ( r_end - size ) > new_end ) {
+			new_end = r_end;
+			DBG ( "...new best block found.\n" );
+		}
+	}
+
+	/* Calculate new location of Etherboot, and align it to the
+	 * required alignemnt.
+	 */
+	new_start = new_end - padded_size;
+	new_start += ( start - new_start ) & ( max_align - 1 );
+	new_end = new_start + size;
+
+	DBG ( "Relocating from [%lx,%lx) to [%lx,%lx)\n",
+	      start, end, new_start, new_end );
+	
+	/* Let prefix know what to copy */
+	ix86->regs.esi = start;
+	ix86->regs.edi = new_start;
+	ix86->regs.ecx = size;
+}
diff --git a/gpxe/src/arch/i386/core/setjmp.S b/gpxe/src/arch/i386/core/setjmp.S
new file mode 100644
index 0000000..0372714
--- /dev/null
+++ b/gpxe/src/arch/i386/core/setjmp.S
@@ -0,0 +1,42 @@
+/* setjmp and longjmp. Use of these functions is deprecated. */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+	.text
+	.arch i386
+	.code32
+	
+/**************************************************************************
+SETJMP - Save stack context for non-local goto
+**************************************************************************/
+	.globl	setjmp
+setjmp:
+	movl	4(%esp),%ecx		/* jmpbuf */
+	movl	0(%esp),%edx		/* return address */
+	movl	%edx,0(%ecx)
+	movl	%ebx,4(%ecx)
+	movl	%esp,8(%ecx)
+	movl	%ebp,12(%ecx)
+	movl	%esi,16(%ecx)
+	movl	%edi,20(%ecx)
+	movl	$0,%eax
+	ret
+
+/**************************************************************************
+LONGJMP - Non-local jump to a saved stack context
+**************************************************************************/
+	.globl	longjmp
+longjmp:
+	movl	4(%esp),%edx		/* jumpbuf */
+	movl	8(%esp),%eax		/* result */
+	movl	0(%edx),%ecx
+	movl	4(%edx),%ebx
+	movl	8(%edx),%esp
+	movl	12(%edx),%ebp
+	movl	16(%edx),%esi
+	movl	20(%edx),%edi
+	cmpl	$0,%eax
+	jne	1f
+	movl	$1,%eax
+1:	movl	%ecx,0(%esp)
+	ret
diff --git a/gpxe/src/arch/i386/core/stack.S b/gpxe/src/arch/i386/core/stack.S
new file mode 100644
index 0000000..737ec0e
--- /dev/null
+++ b/gpxe/src/arch/i386/core/stack.S
@@ -0,0 +1,15 @@
+FILE_LICENCE ( GPL2_OR_LATER )
+
+	.arch i386
+
+/****************************************************************************
+ * Internal stack
+ ****************************************************************************
+ */
+	.section ".stack", "aw", @nobits
+	.align 8
+	.globl _stack
+_stack:
+	.space 4096
+	.globl _estack
+_estack:
diff --git a/gpxe/src/arch/i386/core/stack16.S b/gpxe/src/arch/i386/core/stack16.S
new file mode 100644
index 0000000..523f028
--- /dev/null
+++ b/gpxe/src/arch/i386/core/stack16.S
@@ -0,0 +1,15 @@
+FILE_LICENCE ( GPL2_OR_LATER )
+
+	.arch i386
+
+/****************************************************************************
+ * Internal stack
+ ****************************************************************************
+ */
+	.section ".stack16", "aw", @nobits
+	.align 8
+	.globl _stack16
+_stack16:
+	.space 4096
+	.globl _estack16
+_estack16:
diff --git a/gpxe/src/arch/i386/core/timer2.c b/gpxe/src/arch/i386/core/timer2.c
new file mode 100644
index 0000000..6e76b2e
--- /dev/null
+++ b/gpxe/src/arch/i386/core/timer2.c
@@ -0,0 +1,87 @@
+/*
+ * arch/i386/core/i386_timer.c
+ *
+ * Use the "System Timer 2" to implement the udelay callback in
+ * the BIOS timer driver. Also used to calibrate the clock rate
+ * in the RTDSC timer driver.
+ * 
+ * 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, or (at
+ * your option) any later version.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stddef.h>
+#include <gpxe/timer2.h>
+#include <gpxe/io.h>
+
+/* Timers tick over at this rate */
+#define TIMER2_TICKS_PER_SEC	1193180U
+
+/* Parallel Peripheral Controller Port B */
+#define	PPC_PORTB	0x61
+
+/* Meaning of the port bits */
+#define	PPCB_T2OUT	0x20	/* Bit 5 */
+#define	PPCB_SPKR	0x02	/* Bit 1 */
+#define	PPCB_T2GATE	0x01	/* Bit 0 */
+
+/* Ports for the 8254 timer chip */
+#define	TIMER2_PORT	0x42
+#define	TIMER_MODE_PORT	0x43
+
+/* Meaning of the mode bits */
+#define	TIMER0_SEL	0x00
+#define	TIMER1_SEL	0x40
+#define	TIMER2_SEL	0x80
+#define	READBACK_SEL	0xC0
+
+#define	LATCH_COUNT	0x00
+#define	LOBYTE_ACCESS	0x10
+#define	HIBYTE_ACCESS	0x20
+#define	WORD_ACCESS	0x30
+
+#define	MODE0		0x00
+#define	MODE1		0x02
+#define	MODE2		0x04
+#define	MODE3		0x06
+#define	MODE4		0x08
+#define	MODE5		0x0A
+
+#define	BINARY_COUNT	0x00
+#define	BCD_COUNT	0x01
+
+static void load_timer2 ( unsigned int ticks ) {
+	/*
+	 * Now let's take care of PPC channel 2
+	 *
+	 * Set the Gate high, program PPC channel 2 for mode 0,
+	 * (interrupt on terminal count mode), binary count,
+	 * load 5 * LATCH count, (LSB and MSB) to begin countdown.
+	 *
+	 * Note some implementations have a bug where the high bits byte
+	 * of channel 2 is ignored.
+	 */
+	/* Set up the timer gate, turn off the speaker */
+	/* Set the Gate high, disable speaker */
+	outb((inb(PPC_PORTB) & ~PPCB_SPKR) | PPCB_T2GATE, PPC_PORTB);
+	/* binary, mode 0, LSB/MSB, Ch 2 */
+	outb(TIMER2_SEL|WORD_ACCESS|MODE0|BINARY_COUNT, TIMER_MODE_PORT);
+	/* LSB of ticks */
+	outb(ticks & 0xFF, TIMER2_PORT);
+	/* MSB of ticks */
+	outb(ticks >> 8, TIMER2_PORT);
+}
+
+static int timer2_running ( void ) {
+	return ((inb(PPC_PORTB) & PPCB_T2OUT) == 0);
+}
+
+void timer2_udelay ( unsigned long usecs ) {
+	load_timer2 ( ( usecs * TIMER2_TICKS_PER_SEC ) / ( 1000 * 1000 ) );
+	while (timer2_running()) {
+		/* Do nothing */
+	}
+}
diff --git a/gpxe/src/arch/i386/core/video_subr.c b/gpxe/src/arch/i386/core/video_subr.c
new file mode 100644
index 0000000..c821cd0
--- /dev/null
+++ b/gpxe/src/arch/i386/core/video_subr.c
@@ -0,0 +1,104 @@
+/*
+ *
+ * modified from linuxbios code
+ * by Cai Qiang <rimy2000@hotmail.com>
+ *
+ */
+
+#include "stddef.h"
+#include "string.h"
+#include <gpxe/io.h>
+#include "console.h"
+#include <gpxe/init.h>
+#include "vga.h"
+
+struct console_driver vga_console;
+
+static char *vidmem;		/* The video buffer */
+static int video_line, video_col;
+
+#define VIDBUFFER 0xB8000	
+
+static void memsetw(void *s, int c, unsigned int n)
+{
+	unsigned int i;
+	u16 *ss = (u16 *) s;
+
+	for (i = 0; i < n; i++) {
+		ss[i] = ( u16 ) c;
+	}
+}
+
+static void video_init(void)
+{
+	static int inited=0;
+
+	vidmem = (char *)phys_to_virt(VIDBUFFER);
+
+	if (!inited) {
+		video_line = 0;
+		video_col = 0;
+	
+	 	memsetw(vidmem, VGA_ATTR_CLR_WHT, 2*1024); //
+
+		inited=1;
+	}
+}
+
+static void video_scroll(void)
+{
+	int i;
+
+	memcpy(vidmem, vidmem + COLS * 2, (LINES - 1) * COLS * 2);
+	for (i = (LINES - 1) * COLS * 2; i < LINES * COLS * 2; i += 2)
+		vidmem[i] = ' ';
+}
+
+static void vga_putc(int byte)
+{
+	if (byte == '\n') {
+		video_line++;
+		video_col = 0;
+
+	} else if (byte == '\r') {
+		video_col = 0;
+
+	} else if (byte == '\b') {
+		video_col--;
+
+	} else if (byte == '\t') {
+		video_col += 4;
+
+	} else if (byte == '\a') {
+		//beep
+		//beep(500);
+
+	} else {
+		vidmem[((video_col + (video_line *COLS)) * 2)] = byte;
+		vidmem[((video_col + (video_line *COLS)) * 2) +1] = VGA_ATTR_CLR_WHT;
+		video_col++;
+	}
+	if (video_col < 0) {
+		video_col = 0;
+	}
+	if (video_col >= COLS) {
+		video_line++;
+		video_col = 0;
+	}
+	if (video_line >= LINES) {
+		video_scroll();
+		video_line--;
+	}
+	// move the cursor
+	write_crtc((video_col + (video_line *COLS)) >> 8, CRTC_CURSOR_HI);
+	write_crtc((video_col + (video_line *COLS)) & 0x0ff, CRTC_CURSOR_LO);
+}
+
+struct console_driver vga_console __console_driver = {
+	.putchar = vga_putc,
+	.disabled = 1,
+};
+
+struct init_fn video_init_fn __init_fn ( INIT_EARLY ) = {
+	.initialise = video_init,
+};
diff --git a/gpxe/src/arch/i386/core/virtaddr.S b/gpxe/src/arch/i386/core/virtaddr.S
new file mode 100644
index 0000000..aae1e1e
--- /dev/null
+++ b/gpxe/src/arch/i386/core/virtaddr.S
@@ -0,0 +1,103 @@
+/*
+ * Functions to support the virtual addressing method of relocation
+ * that Etherboot uses.
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+#include "librm.h"
+		
+	.arch i386
+	.text
+	.code32
+	
+/****************************************************************************
+ * _virt_to_phys (virtual addressing)
+ *
+ * Switch from virtual to flat physical addresses.  %esp is adjusted
+ * to a physical value.  Segment registers are set to flat physical
+ * selectors.  All other registers are preserved.  Flags are
+ * preserved.
+ *
+ * Parameters: none
+ * Returns: none
+ ****************************************************************************
+ */
+	.globl _virt_to_phys
+_virt_to_phys:
+	/* Preserve registers and flags */
+	pushfl
+	pushl	%eax
+	pushl	%ebp
+
+	/* Change return address to a physical address */
+	movl	virt_offset, %ebp
+	addl	%ebp, 12(%esp)
+
+	/* Switch to physical code segment */
+	pushl	$PHYSICAL_CS
+	leal	1f(%ebp), %eax
+	pushl	%eax
+	lret
+1:
+	/* Reload other segment registers and adjust %esp */
+	movl	$PHYSICAL_DS, %eax
+	movl	%eax, %ds
+	movl	%eax, %es	
+	movl	%eax, %fs	
+	movl	%eax, %gs
+	movl	%eax, %ss	
+	addl	%ebp, %esp
+
+	/* Restore registers and flags, and return */
+	popl	%ebp
+	popl	%eax
+	popfl
+	ret
+
+/****************************************************************************
+ * _phys_to_virt (flat physical addressing)
+ *
+ * Switch from flat physical to virtual addresses.  %esp is adjusted
+ * to a virtual value.  Segment registers are set to virtual
+ * selectors.  All other registers are preserved.  Flags are
+ * preserved.
+ *
+ * Note that this depends on the GDT already being correctly set up
+ * (e.g. by a call to run_here()).
+ *
+ * Parameters: none
+ * Returns: none
+ ****************************************************************************
+ */
+	.globl _phys_to_virt
+_phys_to_virt:
+	/* Preserve registers and flags */
+	pushfl
+	pushl	%eax
+	pushl	%ebp
+
+	/* Switch to virtual code segment */
+	ljmp	$VIRTUAL_CS, $1f
+1:	
+	/* Reload data segment registers */
+	movl	$VIRTUAL_DS, %eax
+	movl	%eax, %ds
+	movl	%eax, %es	
+	movl	%eax, %fs	
+	movl	%eax, %gs
+
+	/* Reload stack segment and adjust %esp */
+	movl	virt_offset, %ebp
+	movl	%eax, %ss	
+	subl	%ebp, %esp
+
+	/* Change the return address to a virtual address */
+	subl	%ebp, 12(%esp)
+
+	/* Restore registers and flags, and return */
+	popl	%ebp
+	popl	%eax
+	popfl
+	ret
diff --git a/gpxe/src/arch/i386/core/wince_loader.c b/gpxe/src/arch/i386/core/wince_loader.c
new file mode 100644
index 0000000..f452b65
--- /dev/null
+++ b/gpxe/src/arch/i386/core/wince_loader.c
@@ -0,0 +1,273 @@
+#define LOAD_DEBUG	0
+
+static int get_x_header(unsigned char *data, unsigned long now);
+static void jump_2ep();
+static unsigned char ce_signature[] = {'B', '0', '0', '0', 'F', 'F', '\n',};
+static char ** ep;
+
+#define BOOT_ARG_PTR_LOCATION 0x001FFFFC
+
+typedef struct _BOOT_ARGS{
+	unsigned char ucVideoMode;
+	unsigned char ucComPort;
+	unsigned char ucBaudDivisor;
+	unsigned char ucPCIConfigType;
+	
+	unsigned long dwSig;
+	#define BOOTARG_SIG 0x544F4F42
+	unsigned long dwLen;
+	
+	unsigned char ucLoaderFlags;
+	unsigned char ucEshellFlags;
+	unsigned char ucEdbgAdapterType;
+	unsigned char ucEdbgIRQ;
+	
+	unsigned long dwEdbgBaseAddr;
+	unsigned long dwEdbgDebugZone;	
+	unsigned long dwDHCPLeaseTime;
+	unsigned long dwEdbgFlags;
+	
+	unsigned long dwEBootFlag;
+	unsigned long dwEBootAddr;
+	unsigned long dwLaunchAddr;
+	
+	unsigned long pvFlatFrameBuffer;
+	unsigned short vesaMode;
+	unsigned short cxDisplayScreen;
+	unsigned short cyDisplayScreen;
+	unsigned short cxPhysicalScreen;
+	unsigned short cyPhysicalScreen;
+	unsigned short cbScanLineLength;
+	unsigned short bppScreen;
+	
+	unsigned char RedMaskSize;
+	unsigned char REdMaskPosition;
+	unsigned char GreenMaskSize;
+	unsigned char GreenMaskPosition;
+	unsigned char BlueMaskSize;
+	unsigned char BlueMaskPosition;
+} BOOT_ARGS;
+
+BOOT_ARGS BootArgs;
+
+static struct segment_info{
+	unsigned long addr;		// Section Address
+	unsigned long size;		// Section Size
+	unsigned long checksum;		// Section CheckSum
+} X;
+
+#define PSIZE	(1500)			//Max Packet Size
+#define DSIZE  (PSIZE+12)
+static unsigned long dbuffer_available =0;
+static unsigned long not_loadin =0;
+static unsigned long d_now =0;
+
+unsigned long entry;
+static unsigned long ce_curaddr;
+
+
+static sector_t ce_loader(unsigned char *data, unsigned int len, int eof);
+static os_download_t wince_probe(unsigned char *data, unsigned int len)
+{
+	if (strncmp(ce_signature, data, sizeof(ce_signature)) != 0) {
+		return 0;
+	}
+	printf("(WINCE)");
+	return ce_loader;
+}
+
+static sector_t ce_loader(unsigned char *data, unsigned int len, int eof)
+{
+	static unsigned char dbuffer[DSIZE];
+	int this_write = 0;
+	static int firsttime = 1;
+
+	/*
+	 *	new packet in, we have to 
+	 *	[1] copy data to dbuffer,
+	 *
+	 *	update...
+	 *	[2]  dbuffer_available
+	 */
+	memcpy( (dbuffer+dbuffer_available), data, len);	//[1]
+	dbuffer_available += len;	// [2]
+	len = 0;
+
+	d_now = 0;
+	
+#if 0
+	printf("dbuffer_available =%ld \n", dbuffer_available);
+#endif 
+	
+	if (firsttime) 
+	{
+		d_now = sizeof(ce_signature);
+		printf("String Physical Address = %lx \n", 
+			*(unsigned long *)(dbuffer+d_now));
+		
+		d_now += sizeof(unsigned long);
+		printf("Image Size = %ld [%lx]\n", 
+			*(unsigned long *)(dbuffer+d_now), 
+			*(unsigned long *)(dbuffer+d_now));
+		
+		d_now += sizeof(unsigned long);
+		dbuffer_available -= d_now;			
+		
+		d_now = (unsigned long)get_x_header(dbuffer, d_now);
+		firsttime = 0;
+	}
+	
+	if (not_loadin == 0)
+	{
+		d_now = get_x_header(dbuffer, d_now);
+	}
+	
+	while ( not_loadin > 0 )
+	{
+		/* dbuffer do not have enough data to loading, copy all */
+#if LOAD_DEBUG
+		printf("[0] not_loadin = [%ld], dbuffer_available = [%ld] \n", 
+			not_loadin, dbuffer_available);
+		printf("[0] d_now = [%ld] \n", d_now);
+#endif
+		
+		if( dbuffer_available <= not_loadin)
+		{
+			this_write = dbuffer_available ;
+			memcpy(phys_to_virt(ce_curaddr), (dbuffer+d_now), this_write );
+			ce_curaddr += this_write;
+			not_loadin -= this_write;
+			
+			/* reset index and available in the dbuffer */
+			dbuffer_available = 0;
+			d_now = 0;
+#if LOAD_DEBUG
+			printf("[1] not_loadin = [%ld], dbuffer_available = [%ld] \n", 
+				not_loadin, dbuffer_available);
+			printf("[1] d_now = [%ld], this_write = [%d] \n", 
+				d_now, this_write);
+#endif
+				
+			// get the next packet...
+			return (0);
+		}
+			
+		/* dbuffer have more data then loading ... , copy partital.... */
+		else
+		{
+			this_write = not_loadin;
+			memcpy(phys_to_virt(ce_curaddr), (dbuffer+d_now), this_write);
+			ce_curaddr += this_write;
+			not_loadin = 0;
+			
+			/* reset index and available in the dbuffer */
+			dbuffer_available -= this_write;
+			d_now += this_write;
+#if LOAD_DEBUG
+			printf("[2] not_loadin = [%ld], dbuffer_available = [%ld] \n", 
+				not_loadin, dbuffer_available);
+			printf("[2] d_now = [%ld], this_write = [%d] \n\n", 
+				d_now, this_write);
+#endif
+			
+			/* dbuffer not empty, proceed processing... */
+			
+			// don't have enough data to get_x_header..
+			if ( dbuffer_available < (sizeof(unsigned long) * 3) )
+			{
+//				printf("we don't have enough data remaining to call get_x. \n");
+				memcpy( (dbuffer+0), (dbuffer+d_now), dbuffer_available);
+				return (0);
+			}
+			else
+			{
+#if LOAD_DEBUG				
+				printf("with remaining data to call get_x \n");
+				printf("dbuffer available = %ld , d_now = %ld\n", 
+					dbuffer_available, d_now);
+#endif					
+				d_now = get_x_header(dbuffer, d_now);
+			}
+		}
+	}
+	return (0);
+}
+
+static int get_x_header(unsigned char *dbuffer, unsigned long now)
+{
+	X.addr = *(unsigned long *)(dbuffer + now);
+	X.size = *(unsigned long *)(dbuffer + now + sizeof(unsigned long));
+	X.checksum = *(unsigned long *)(dbuffer + now + sizeof(unsigned long)*2);
+
+	if (X.addr == 0)
+	{
+		entry = X.size;
+		done(1);
+		printf("Entry Point Address = [%lx] \n", entry);
+		jump_2ep();		
+	}
+
+	if (!prep_segment(X.addr, X.addr + X.size, X.addr + X.size, 0, 0)) {
+		longjmp(restart_etherboot, -2);
+	}
+
+	ce_curaddr = X.addr;
+	now += sizeof(unsigned long)*3;
+
+	/* re-calculate dbuffer available... */
+	dbuffer_available -= sizeof(unsigned long)*3;
+
+	/* reset index of this section */
+	not_loadin = X.size;
+	
+#if 1
+	printf("\n");
+	printf("\t Section Address = [%lx] \n", X.addr);
+	printf("\t Size = %d [%lx]\n", X.size, X.size);
+	printf("\t Checksum = %ld [%lx]\n", X.checksum, X.checksum);
+#endif
+#if LOAD_DEBUG
+	printf("____________________________________________\n");
+	printf("\t dbuffer_now = %ld \n", now);
+	printf("\t dbuffer available = %ld \n", dbuffer_available);
+	printf("\t not_loadin = %ld \n", not_loadin);
+#endif
+
+	return now;
+}
+
+static void jump_2ep()
+{
+	BootArgs.ucVideoMode = 1;
+	BootArgs.ucComPort = 1;
+	BootArgs.ucBaudDivisor = 1;
+	BootArgs.ucPCIConfigType = 1;	// do not fill with 0
+	
+	BootArgs.dwSig = BOOTARG_SIG;
+	BootArgs.dwLen = sizeof(BootArgs);
+	
+	if(BootArgs.ucVideoMode == 0)
+	{
+		BootArgs.cxDisplayScreen = 640;
+		BootArgs.cyDisplayScreen = 480;
+		BootArgs.cxPhysicalScreen = 640;
+		BootArgs.cyPhysicalScreen = 480;
+		BootArgs.bppScreen = 16;
+		BootArgs.cbScanLineLength  = 1024;
+		BootArgs.pvFlatFrameBuffer = 0x800a0000;	// ollie say 0x98000000
+	}	
+	else if(BootArgs.ucVideoMode != 0xFF)
+	{
+		BootArgs.cxDisplayScreen = 0;
+		BootArgs.cyDisplayScreen = 0;
+		BootArgs.cxPhysicalScreen = 0;
+		BootArgs.cyPhysicalScreen = 0;
+		BootArgs.bppScreen = 0;
+		BootArgs.cbScanLineLength  = 0;
+		BootArgs.pvFlatFrameBuffer = 0;	
+	}
+
+	ep = phys_to_virt(BOOT_ARG_PTR_LOCATION);
+	*ep= virt_to_phys(&BootArgs);
+	xstart32(entry);
+}
diff --git a/gpxe/src/arch/i386/core/x86_io.c b/gpxe/src/arch/i386/core/x86_io.c
new file mode 100644
index 0000000..d2c363b
--- /dev/null
+++ b/gpxe/src/arch/i386/core/x86_io.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <gpxe/io.h>
+#include <gpxe/x86_io.h>
+
+/** @file
+ *
+ * gPXE I/O API for x86
+ *
+ */
+
+/**
+ * Read 64-bit qword from memory-mapped device
+ *
+ * @v io_addr		I/O address
+ * @ret data		Value read
+ *
+ * This routine uses MMX instructions.
+ */
+static uint64_t x86_readq ( volatile uint64_t *io_addr ) {
+	uint64_t data;
+        __asm__ __volatile__ ( "pushl %%edx\n\t"
+			       "pushl %%eax\n\t"
+			       "movq (%1), %%mm0\n\t"
+			       "movq %%mm0, (%%esp)\n\t"
+			       "popl %%eax\n\t"
+			       "popl %%edx\n\t"
+			       "emms\n\t"
+                               : "=A" ( data ) : "r" ( io_addr ) );
+	return data;
+}
+
+/**
+ * Write 64-bit qword to memory-mapped device
+ *
+ * @v data		Value to write
+ * @v io_addr		I/O address
+ *
+ * This routine uses MMX instructions.
+ */
+static void x86_writeq ( uint64_t data, volatile uint64_t *io_addr ) {
+	__asm__ __volatile__ ( "pushl %%edx\n\t"
+			       "pushl %%eax\n\t"
+			       "movq (%%esp), %%mm0\n\t"
+			       "movq %%mm0, (%1)\n\t"
+			       "popl %%eax\n\t"
+			       "popl %%edx\n\t"
+			       "emms\n\t"
+			       : : "A" ( data ), "r" ( io_addr ) );
+}
+
+PROVIDE_IOAPI_INLINE ( x86, phys_to_bus );
+PROVIDE_IOAPI_INLINE ( x86, bus_to_phys );
+PROVIDE_IOAPI_INLINE ( x86, ioremap );
+PROVIDE_IOAPI_INLINE ( x86, iounmap );
+PROVIDE_IOAPI_INLINE ( x86, io_to_bus );
+PROVIDE_IOAPI_INLINE ( x86, readb );
+PROVIDE_IOAPI_INLINE ( x86, readw );
+PROVIDE_IOAPI_INLINE ( x86, readl );
+PROVIDE_IOAPI ( x86, readq, x86_readq );
+PROVIDE_IOAPI_INLINE ( x86, writeb );
+PROVIDE_IOAPI_INLINE ( x86, writew );
+PROVIDE_IOAPI_INLINE ( x86, writel );
+PROVIDE_IOAPI ( x86, writeq, x86_writeq );
+PROVIDE_IOAPI_INLINE ( x86, inb );
+PROVIDE_IOAPI_INLINE ( x86, inw );
+PROVIDE_IOAPI_INLINE ( x86, inl );
+PROVIDE_IOAPI_INLINE ( x86, outb );
+PROVIDE_IOAPI_INLINE ( x86, outw );
+PROVIDE_IOAPI_INLINE ( x86, outl );
+PROVIDE_IOAPI_INLINE ( x86, insb );
+PROVIDE_IOAPI_INLINE ( x86, insw );
+PROVIDE_IOAPI_INLINE ( x86, insl );
+PROVIDE_IOAPI_INLINE ( x86, outsb );
+PROVIDE_IOAPI_INLINE ( x86, outsw );
+PROVIDE_IOAPI_INLINE ( x86, outsl );
+PROVIDE_IOAPI_INLINE ( x86, iodelay );
+PROVIDE_IOAPI_INLINE ( x86, mb );
diff --git a/gpxe/src/arch/i386/drivers/net/undi.c b/gpxe/src/arch/i386/drivers/net/undi.c
new file mode 100644
index 0000000..c6e253c
--- /dev/null
+++ b/gpxe/src/arch/i386/drivers/net/undi.c
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <gpxe/pci.h>
+#include <undi.h>
+#include <undirom.h>
+#include <undiload.h>
+#include <undinet.h>
+#include <undipreload.h>
+
+/** @file
+ *
+ * UNDI PCI driver
+ *
+ */
+
+/**
+ * Find UNDI ROM for PCI device
+ *
+ * @v pci		PCI device
+ * @ret undirom		UNDI ROM, or NULL
+ *
+ * Try to find a driver for this device.  Try an exact match on the
+ * ROM address first, then fall back to a vendor/device ID match only
+ */
+static struct undi_rom * undipci_find_rom ( struct pci_device *pci ) {
+	struct undi_rom *undirom;
+	unsigned long rombase;
+	
+	rombase = pci_bar_start ( pci, PCI_ROM_ADDRESS );
+	undirom = undirom_find_pci ( pci->vendor, pci->device, rombase );
+	if ( ! undirom )
+		undirom = undirom_find_pci ( pci->vendor, pci->device, 0 );
+	return undirom;
+}
+
+/**
+ * Probe PCI device
+ *
+ * @v pci		PCI device
+ * @v id		PCI ID
+ * @ret rc		Return status code
+ */
+static int undipci_probe ( struct pci_device *pci,
+			   const struct pci_device_id *id __unused ) {
+	struct undi_device *undi;
+	struct undi_rom *undirom;
+	unsigned int busdevfn = PCI_BUSDEVFN ( pci->bus, pci->devfn );
+	int rc;
+
+	/* Ignore non-network devices */
+	if ( PCI_BASE_CLASS ( pci->class ) != PCI_BASE_CLASS_NETWORK )
+		return -ENOTTY;
+
+	/* Allocate UNDI device structure */
+	undi = zalloc ( sizeof ( *undi ) );
+	if ( ! undi )
+		return -ENOMEM;
+	pci_set_drvdata ( pci, undi );
+
+	/* Find/create our pixie */
+	if ( preloaded_undi.pci_busdevfn == busdevfn ) {
+		/* Claim preloaded UNDI device */
+		DBGC ( undi, "UNDI %p using preloaded UNDI device\n", undi );
+		memcpy ( undi, &preloaded_undi, sizeof ( *undi ) );
+		memset ( &preloaded_undi, 0, sizeof ( preloaded_undi ) );
+	} else {
+		/* Find UNDI ROM for PCI device */
+		if ( ! ( undirom = undipci_find_rom ( pci ) ) ) {
+			rc = -ENODEV;
+			goto err_find_rom;
+		}
+
+		/* Call UNDI ROM loader to create pixie */
+		if ( ( rc = undi_load_pci ( undi, undirom, busdevfn ) ) != 0 )
+			goto err_load_pci;
+	}
+
+	/* Add to device hierarchy */
+	snprintf ( undi->dev.name, sizeof ( undi->dev.name ),
+		   "UNDI-%s", pci->dev.name );
+	memcpy ( &undi->dev.desc, &pci->dev.desc, sizeof ( undi->dev.desc ) );
+	undi->dev.parent = &pci->dev;
+	INIT_LIST_HEAD ( &undi->dev.children );
+	list_add ( &undi->dev.siblings, &pci->dev.children );
+
+	/* Create network device */
+	if ( ( rc = undinet_probe ( undi ) ) != 0 )
+		goto err_undinet_probe;
+	
+	return 0;
+
+ err_undinet_probe:
+	undi_unload ( undi );
+	list_del ( &undi->dev.siblings );
+ err_find_rom:
+ err_load_pci:
+	free ( undi );
+	pci_set_drvdata ( pci, NULL );
+	return rc;
+}
+
+/**
+ * Remove PCI device
+ *
+ * @v pci	PCI device
+ */
+static void undipci_remove ( struct pci_device *pci ) {
+	struct undi_device *undi = pci_get_drvdata ( pci );
+
+	undinet_remove ( undi );
+	undi_unload ( undi );
+	list_del ( &undi->dev.siblings );
+	free ( undi );
+	pci_set_drvdata ( pci, NULL );
+}
+
+static struct pci_device_id undipci_nics[] = {
+PCI_ROM ( 0xffff, 0xffff, "undipci", "UNDI (PCI)", 0 ),
+};
+
+struct pci_driver undipci_driver __pci_driver = {
+	.ids = undipci_nics,
+	.id_count = ( sizeof ( undipci_nics ) / sizeof ( undipci_nics[0] ) ),
+	.probe = undipci_probe,
+	.remove = undipci_remove,
+};
diff --git a/gpxe/src/arch/i386/drivers/net/undiisr.S b/gpxe/src/arch/i386/drivers/net/undiisr.S
new file mode 100644
index 0000000..b27effe
--- /dev/null
+++ b/gpxe/src/arch/i386/drivers/net/undiisr.S
@@ -0,0 +1,87 @@
+FILE_LICENCE ( GPL2_OR_LATER )
+
+#define PXENV_UNDI_ISR 0x0014
+#define PXENV_UNDI_ISR_IN_START 1
+#define PXENV_UNDI_ISR_OUT_OURS 0
+#define PXENV_UNDI_ISR_OUT_NOT_OURS 1
+
+#define IRQ_PIC_CUTOFF 8
+#define ICR_EOI_NON_SPECIFIC 0x20
+#define PIC1_ICR 0x20
+#define PIC2_ICR 0xa0
+	
+	.text
+	.arch i386
+	.code16
+
+	.section ".text16", "ax", @progbits
+	.globl undiisr
+undiisr:
+	
+	/* Preserve registers */
+	pushw	%ds
+	pushw	%es
+	pushw	%fs
+	pushw	%gs
+	pushfl
+	pushal
+
+	/* Set up our segment registers */
+	movw	%cs:rm_ds, %ax
+	movw	%ax, %ds
+
+	/* Check that we have an UNDI entry point */
+	cmpw	$0, pxeparent_entry_point
+	je	chain
+	
+	/* Issue UNDI API call */
+	movw	%ax, %es
+	movw	$undinet_params, %di
+	movw	$PXENV_UNDI_ISR, %bx
+	movw	$PXENV_UNDI_ISR_IN_START, funcflag
+	pushw	%es
+	pushw	%di
+	pushw	%bx
+	lcall	*pxeparent_entry_point
+	cli	/* Just in case */
+	addw	$6, %sp
+	cmpw	$PXENV_UNDI_ISR_OUT_OURS, funcflag
+	jne	eoi
+	
+trig:	/* Record interrupt occurence */
+	incb	undiisr_trigger_count
+
+eoi:	/* Send EOI */
+	movb	$ICR_EOI_NON_SPECIFIC, %al
+	cmpb	$IRQ_PIC_CUTOFF, undiisr_irq
+	jb	1f
+	outb	%al, $PIC2_ICR
+1:	outb	%al, $PIC1_ICR
+	jmp	exit
+	
+chain:	/* Chain to next handler */
+	pushfw
+	lcall	*undiisr_next_handler
+	
+exit:	/* Restore registers and return */
+	cli
+	popal
+	movzwl	%sp, %esp
+	addr32	movl -20(%esp), %esp	/* %esp isn't restored by popal */
+	popfl
+	popw	%gs
+	popw	%fs
+	popw	%es
+	popw	%ds
+	iret
+
+	.section ".data16", "aw", @progbits
+undinet_params:
+status:			.word	0
+funcflag:		.word	0
+bufferlength:		.word	0
+framelength:		.word	0
+frameheaderlength:	.word	0
+frame:			.word	0, 0
+prottype:		.byte	0
+pkttype:		.byte	0
diff --git a/gpxe/src/arch/i386/drivers/net/undiload.c b/gpxe/src/arch/i386/drivers/net/undiload.c
new file mode 100644
index 0000000..1d4e88d
--- /dev/null
+++ b/gpxe/src/arch/i386/drivers/net/undiload.c
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pxe.h>
+#include <realmode.h>
+#include <bios.h>
+#include <pnpbios.h>
+#include <basemem.h>
+#include <gpxe/pci.h>
+#include <undi.h>
+#include <undirom.h>
+#include <undiload.h>
+
+/** @file
+ *
+ * UNDI load/unload
+ *
+ */
+
+/** Parameter block for calling UNDI loader */
+static struct s_UNDI_LOADER __bss16 ( undi_loader );
+#define undi_loader __use_data16 ( undi_loader )
+
+/** UNDI loader entry point */
+static SEGOFF16_t __bss16 ( undi_loader_entry );
+#define undi_loader_entry __use_data16 ( undi_loader_entry )
+
+/**
+ * Call UNDI loader to create a pixie
+ *
+ * @v undi		UNDI device
+ * @v undirom		UNDI ROM
+ * @ret rc		Return status code
+ */
+int undi_load ( struct undi_device *undi, struct undi_rom *undirom ) {
+	struct s_PXE ppxe;
+	unsigned int fbms_seg;
+	uint16_t exit;
+	int rc;
+
+	/* Set up START_UNDI parameters */
+	memset ( &undi_loader, 0, sizeof ( undi_loader ) );
+	undi_loader.AX = undi->pci_busdevfn;
+	undi_loader.BX = undi->isapnp_csn;
+	undi_loader.DX = undi->isapnp_read_port;
+	undi_loader.ES = BIOS_SEG;
+	undi_loader.DI = find_pnp_bios();
+
+	/* Allocate base memory for PXE stack */
+	undi->restore_fbms = get_fbms();
+	fbms_seg = ( undi->restore_fbms << 6 );
+	fbms_seg -= ( ( undirom->code_size + 0x0f ) >> 4 );
+	undi_loader.UNDI_CS = fbms_seg;
+	fbms_seg -= ( ( undirom->data_size + 0x0f ) >> 4 );
+	undi_loader.UNDI_DS = fbms_seg;
+
+	/* Debug info */
+	DBGC ( undi, "UNDI %p loading UNDI ROM %p to CS %04x DS %04x for ",
+	       undi, undirom, undi_loader.UNDI_CS, undi_loader.UNDI_DS );
+	if ( undi->pci_busdevfn != UNDI_NO_PCI_BUSDEVFN ) {
+		unsigned int bus = ( undi->pci_busdevfn >> 8 );
+		unsigned int devfn = ( undi->pci_busdevfn & 0xff );
+		DBGC ( undi, "PCI %02x:%02x.%x\n",
+		       bus, PCI_SLOT ( devfn ), PCI_FUNC ( devfn ) );
+	}
+	if ( undi->isapnp_csn != UNDI_NO_ISAPNP_CSN ) {
+		DBGC ( undi, "ISAPnP(%04x) CSN %04x\n",
+		       undi->isapnp_read_port, undi->isapnp_csn );
+	}
+
+	/* Call loader */
+	undi_loader_entry = undirom->loader_entry;
+	__asm__ __volatile__ ( REAL_CODE ( "pushw %%ds\n\t"
+					   "pushw %%ax\n\t"
+					   "lcall *undi_loader_entry\n\t"
+					   "addw $4, %%sp\n\t" )
+			       : "=a" ( exit )
+			       : "a" ( __from_data16 ( &undi_loader ) )
+			       : "ebx", "ecx", "edx", "esi", "edi", "ebp" );
+
+	/* UNDI API calls may rudely change the status of A20 and not
+	 * bother to restore it afterwards.  Intel is known to be
+	 * guilty of this.
+	 *
+	 * Note that we will return to this point even if A20 gets
+	 * screwed up by the UNDI driver, because Etherboot always
+	 * resides in an even megabyte of RAM.
+	 */	
+	gateA20_set();
+
+	if ( exit != PXENV_EXIT_SUCCESS ) {
+		rc = -undi_loader.Status;
+		if ( rc == 0 ) /* Paranoia */
+			rc = -EIO;
+		DBGC ( undi, "UNDI %p loader failed: %s\n",
+		       undi, strerror ( rc ) );
+		return rc;
+	}
+
+	/* Populate PXE device structure */
+	undi->pxenv = undi_loader.PXENVptr;
+	undi->ppxe = undi_loader.PXEptr;
+	copy_from_real ( &ppxe, undi->ppxe.segment, undi->ppxe.offset,
+			 sizeof ( ppxe ) );
+	undi->entry = ppxe.EntryPointSP;
+	DBGC ( undi, "UNDI %p loaded PXENV+ %04x:%04x !PXE %04x:%04x "
+	       "entry %04x:%04x\n", undi, undi->pxenv.segment,
+	       undi->pxenv.offset, undi->ppxe.segment, undi->ppxe.offset,
+	       undi->entry.segment, undi->entry.offset );
+
+	/* Update free base memory counter */
+	undi->fbms = ( fbms_seg >> 6 );
+	set_fbms ( undi->fbms );
+	DBGC ( undi, "UNDI %p using [%d,%d) kB of base memory\n",
+	       undi, undi->fbms, undi->restore_fbms );
+
+	return 0;
+}
+
+/**
+ * Unload a pixie
+ *
+ * @v undi		UNDI device
+ * @ret rc		Return status code
+ *
+ * Erases the PXENV+ and !PXE signatures, and frees the used base
+ * memory (if possible).
+ */
+int undi_unload ( struct undi_device *undi ) {
+	static uint32_t dead = 0xdeaddead;
+
+	DBGC ( undi, "UNDI %p unloading\n", undi );
+
+	/* Erase signatures */
+	if ( undi->pxenv.segment )
+		put_real ( dead, undi->pxenv.segment, undi->pxenv.offset );
+	if ( undi->ppxe.segment )
+		put_real ( dead, undi->ppxe.segment, undi->ppxe.offset );
+
+	/* Free base memory, if possible */
+	if ( undi->fbms == get_fbms() ) {
+		DBGC ( undi, "UNDI %p freeing [%d,%d) kB of base memory\n",
+		       undi, undi->fbms, undi->restore_fbms );
+		set_fbms ( undi->restore_fbms );
+		return 0;
+	} else {
+		DBGC ( undi, "UNDI %p leaking [%d,%d) kB of base memory\n",
+		       undi, undi->fbms, undi->restore_fbms );
+		return -EBUSY;
+	}
+}
diff --git a/gpxe/src/arch/i386/drivers/net/undinet.c b/gpxe/src/arch/i386/drivers/net/undinet.c
new file mode 100644
index 0000000..83b79e7
--- /dev/null
+++ b/gpxe/src/arch/i386/drivers/net/undinet.c
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <string.h>
+#include <pxe.h>
+#include <realmode.h>
+#include <pic8259.h>
+#include <biosint.h>
+#include <pnpbios.h>
+#include <basemem_packet.h>
+#include <gpxe/io.h>
+#include <gpxe/iobuf.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/if_ether.h>
+#include <gpxe/ethernet.h>
+#include <undi.h>
+#include <undinet.h>
+#include <pxeparent.h>
+
+
+/** @file
+ *
+ * UNDI network device driver
+ *
+ */
+
+/** An UNDI NIC */
+struct undi_nic {
+	/** Assigned IRQ number */
+	unsigned int irq;
+	/** Currently processing ISR */
+	int isr_processing;
+	/** Bug workarounds */
+	int hacks;
+};
+
+/**
+ * @defgroup undi_hacks UNDI workarounds
+ * @{
+ */
+
+/** Work around Etherboot 5.4 bugs */
+#define UNDI_HACK_EB54		0x0001
+
+/** @} */
+
+static void undinet_close ( struct net_device *netdev );
+
+/** Address of UNDI entry point */
+static SEGOFF16_t undinet_entry;
+
+/*****************************************************************************
+ *
+ * UNDI interrupt service routine
+ *
+ *****************************************************************************
+ */
+
+/**
+ * UNDI interrupt service routine
+ *
+ * The UNDI ISR increments a counter (@c trigger_count) and exits.
+ */
+extern void undiisr ( void );
+
+/** IRQ number */
+uint8_t __data16 ( undiisr_irq );
+#define undiisr_irq __use_data16 ( undiisr_irq )
+
+/** IRQ chain vector */
+struct segoff __data16 ( undiisr_next_handler );
+#define undiisr_next_handler __use_data16 ( undiisr_next_handler )
+
+/** IRQ trigger count */
+volatile uint8_t __data16 ( undiisr_trigger_count ) = 0;
+#define undiisr_trigger_count __use_data16 ( undiisr_trigger_count )
+
+/** Last observed trigger count */
+static unsigned int last_trigger_count = 0;
+
+/**
+ * Hook UNDI interrupt service routine
+ *
+ * @v irq		IRQ number
+ */
+static void undinet_hook_isr ( unsigned int irq ) {
+
+	assert ( irq <= IRQ_MAX );
+	assert ( undiisr_irq == 0 );
+
+	undiisr_irq = irq;
+	hook_bios_interrupt ( IRQ_INT ( irq ),
+			      ( ( unsigned int ) undiisr ),
+			      &undiisr_next_handler );
+}
+
+/**
+ * Unhook UNDI interrupt service routine
+ *
+ * @v irq		IRQ number
+ */
+static void undinet_unhook_isr ( unsigned int irq ) {
+
+	assert ( irq <= IRQ_MAX );
+
+	unhook_bios_interrupt ( IRQ_INT ( irq ),
+				( ( unsigned int ) undiisr ),
+				&undiisr_next_handler );
+	undiisr_irq = 0;
+}
+
+/**
+ * Test to see if UNDI ISR has been triggered
+ *
+ * @ret triggered	ISR has been triggered since last check
+ */
+static int undinet_isr_triggered ( void ) {
+	unsigned int this_trigger_count;
+
+	/* Read trigger_count.  Do this only once; it is volatile */
+	this_trigger_count = undiisr_trigger_count;
+
+	if ( this_trigger_count == last_trigger_count ) {
+		/* Not triggered */
+		return 0;
+	} else {
+		/* Triggered */
+		last_trigger_count = this_trigger_count;
+		return 1;
+	}
+}
+
+/*****************************************************************************
+ *
+ * UNDI network device interface
+ *
+ *****************************************************************************
+ */
+
+/** UNDI transmit buffer descriptor */
+static struct s_PXENV_UNDI_TBD __data16 ( undinet_tbd );
+#define undinet_tbd __use_data16 ( undinet_tbd )
+
+/**
+ * Transmit packet
+ *
+ * @v netdev		Network device
+ * @v iobuf		I/O buffer
+ * @ret rc		Return status code
+ */
+static int undinet_transmit ( struct net_device *netdev,
+			      struct io_buffer *iobuf ) {
+	struct s_PXENV_UNDI_TRANSMIT undi_transmit;
+	size_t len = iob_len ( iobuf );
+	int rc;
+
+	/* Technically, we ought to make sure that the previous
+	 * transmission has completed before we re-use the buffer.
+	 * However, many PXE stacks (including at least some Intel PXE
+	 * stacks and Etherboot 5.4) fail to generate TX completions.
+	 * In practice this won't be a problem, since our TX datapath
+	 * has a very low packet volume and we can get away with
+	 * assuming that a TX will be complete by the time we want to
+	 * transmit the next packet.
+	 */
+
+	/* Copy packet to UNDI I/O buffer */
+	if ( len > sizeof ( basemem_packet ) )
+		len = sizeof ( basemem_packet );
+	memcpy ( &basemem_packet, iobuf->data, len );
+
+	/* Create PXENV_UNDI_TRANSMIT data structure */
+	memset ( &undi_transmit, 0, sizeof ( undi_transmit ) );
+	undi_transmit.DestAddr.segment = rm_ds;
+	undi_transmit.DestAddr.offset = __from_data16 ( &undinet_tbd );
+	undi_transmit.TBD.segment = rm_ds;
+	undi_transmit.TBD.offset = __from_data16 ( &undinet_tbd );
+
+	/* Create PXENV_UNDI_TBD data structure */
+	undinet_tbd.ImmedLength = len;
+	undinet_tbd.Xmit.segment = rm_ds;
+	undinet_tbd.Xmit.offset = __from_data16 ( basemem_packet );
+
+	/* Issue PXE API call */
+	if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_TRANSMIT,
+				     &undi_transmit,
+				     sizeof ( undi_transmit ) ) ) != 0 )
+		goto done;
+
+	/* Free I/O buffer */
+	netdev_tx_complete ( netdev, iobuf );
+
+ done:
+	return rc;
+}
+
+/** 
+ * Poll for received packets
+ *
+ * @v netdev		Network device
+ *
+ * Fun, fun, fun.  UNDI drivers don't use polling; they use
+ * interrupts.  We therefore cheat and pretend that an interrupt has
+ * occurred every time undinet_poll() is called.  This isn't too much
+ * of a hack; PCI devices share IRQs and so the first thing that a
+ * proper ISR should do is call PXENV_UNDI_ISR to determine whether or
+ * not the UNDI NIC generated the interrupt; there is no harm done by
+ * spurious calls to PXENV_UNDI_ISR.  Similarly, we wouldn't be
+ * handling them any more rapidly than the usual rate of
+ * undinet_poll() being called even if we did implement a full ISR.
+ * So it should work.  Ha!
+ *
+ * Addendum (21/10/03).  Some cards don't play nicely with this trick,
+ * so instead of doing it the easy way we have to go to all the hassle
+ * of installing a genuine interrupt service routine and dealing with
+ * the wonderful 8259 Programmable Interrupt Controller.  Joy.
+ *
+ * Addendum (10/07/07).  When doing things such as iSCSI boot, in
+ * which we have to co-operate with a running OS, we can't get away
+ * with the "ISR-just-increments-a-counter-and-returns" trick at all,
+ * because it involves tying up the PIC for far too long, and other
+ * interrupt-dependent components (e.g. local disks) start breaking.
+ * We therefore implement a "proper" ISR which calls PXENV_UNDI_ISR
+ * from within interrupt context in order to deassert the device
+ * interrupt, and sends EOI if applicable.
+ */
+static void undinet_poll ( struct net_device *netdev ) {
+	struct undi_nic *undinic = netdev->priv;
+	struct s_PXENV_UNDI_ISR undi_isr;
+	struct io_buffer *iobuf = NULL;
+	size_t len;
+	size_t frag_len;
+	size_t max_frag_len;
+	int rc;
+
+	if ( ! undinic->isr_processing ) {
+		/* Do nothing unless ISR has been triggered */
+		if ( ! undinet_isr_triggered() ) {
+			/* Allow interrupt to occur */
+			__asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
+							   "nop\n\t"
+							   "nop\n\t"
+							   "cli\n\t" ) : : );
+			return;
+		}
+
+		/* Start ISR processing */
+		undinic->isr_processing = 1;
+		undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
+	} else {
+		/* Continue ISR processing */
+		undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
+	}
+
+	/* Run through the ISR loop */
+	while ( 1 ) {
+		if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_ISR,
+					     &undi_isr,
+					     sizeof ( undi_isr ) ) ) != 0 )
+			break;
+		switch ( undi_isr.FuncFlag ) {
+		case PXENV_UNDI_ISR_OUT_TRANSMIT:
+			/* We don't care about transmit completions */
+			break;
+		case PXENV_UNDI_ISR_OUT_RECEIVE:
+			/* Packet fragment received */
+			len = undi_isr.FrameLength;
+			frag_len = undi_isr.BufferLength;
+			if ( ( len == 0 ) || ( len < frag_len ) ) {
+				/* Don't laugh.  VMWare does it. */
+				DBGC ( undinic, "UNDINIC %p reported insane "
+				       "fragment (%zd of %zd bytes)\n",
+				       undinic, frag_len, len );
+				netdev_rx_err ( netdev, NULL, -EINVAL );
+				break;
+			}
+			if ( ! iobuf )
+				iobuf = alloc_iob ( len );
+			if ( ! iobuf ) {
+				DBGC ( undinic, "UNDINIC %p could not "
+				       "allocate %zd bytes for RX buffer\n",
+				       undinic, len );
+				/* Fragment will be dropped */
+				netdev_rx_err ( netdev, NULL, -ENOMEM );
+				goto done;
+			}
+			max_frag_len = iob_tailroom ( iobuf );
+			if ( frag_len > max_frag_len ) {
+				DBGC ( undinic, "UNDINIC %p fragment too big "
+				       "(%zd+%zd does not fit into %zd)\n",
+				       undinic, iob_len ( iobuf ), frag_len,
+				       ( iob_len ( iobuf ) + max_frag_len ) );
+				frag_len = max_frag_len;
+			}
+			copy_from_real ( iob_put ( iobuf, frag_len ),
+					 undi_isr.Frame.segment,
+					 undi_isr.Frame.offset, frag_len );
+			if ( iob_len ( iobuf ) == len ) {
+				/* Whole packet received; deliver it */
+				netdev_rx ( netdev, iob_disown ( iobuf ) );
+				/* Etherboot 5.4 fails to return all packets
+				 * under mild load; pretend it retriggered.
+				 */
+				if ( undinic->hacks & UNDI_HACK_EB54 )
+					--last_trigger_count;
+			}
+			break;
+		case PXENV_UNDI_ISR_OUT_DONE:
+			/* Processing complete */
+			undinic->isr_processing = 0;
+			goto done;
+		default:
+			/* Should never happen.  VMWare does it routinely. */
+			DBGC ( undinic, "UNDINIC %p ISR returned invalid "
+			       "FuncFlag %04x\n", undinic, undi_isr.FuncFlag );
+			undinic->isr_processing = 0;
+			goto done;
+		}
+		undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
+	}
+
+ done:
+	if ( iobuf ) {
+		DBGC ( undinic, "UNDINIC %p returned incomplete packet "
+		       "(%zd of %zd)\n", undinic, iob_len ( iobuf ),
+		       ( iob_len ( iobuf ) + iob_tailroom ( iobuf ) ) );
+		netdev_rx_err ( netdev, iobuf, -EINVAL );
+	}
+}
+
+/**
+ * Open NIC
+ *
+ * @v netdev		Net device
+ * @ret rc		Return status code
+ */
+static int undinet_open ( struct net_device *netdev ) {
+	struct undi_nic *undinic = netdev->priv;
+	struct s_PXENV_UNDI_SET_STATION_ADDRESS undi_set_address;
+	struct s_PXENV_UNDI_OPEN undi_open;
+	int rc;
+
+	/* Hook interrupt service routine and enable interrupt */
+	undinet_hook_isr ( undinic->irq );
+	enable_irq ( undinic->irq );
+	send_eoi ( undinic->irq );
+
+	/* Set station address.  Required for some PXE stacks; will
+	 * spuriously fail on others.  Ignore failures.  We only ever
+	 * use it to set the MAC address to the card's permanent value
+	 * anyway.
+	 */
+	memcpy ( undi_set_address.StationAddress, netdev->ll_addr,
+		 sizeof ( undi_set_address.StationAddress ) );
+	pxeparent_call ( undinet_entry, PXENV_UNDI_SET_STATION_ADDRESS,
+			 &undi_set_address, sizeof ( undi_set_address ) );
+
+	/* Open NIC.  We ask for promiscuous operation, since it's the
+	 * only way to ask for all multicast addresses.  On any
+	 * switched network, it shouldn't really make a difference to
+	 * performance.
+	 */
+	memset ( &undi_open, 0, sizeof ( undi_open ) );
+	undi_open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST | FLTR_PRMSCS );
+	if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_OPEN,
+				     &undi_open, sizeof ( undi_open ) ) ) != 0 )
+		goto err;
+
+	DBGC ( undinic, "UNDINIC %p opened\n", undinic );
+	return 0;
+
+ err:
+	undinet_close ( netdev );
+	return rc;
+}
+
+/**
+ * Close NIC
+ *
+ * @v netdev		Net device
+ */
+static void undinet_close ( struct net_device *netdev ) {
+	struct undi_nic *undinic = netdev->priv;
+	struct s_PXENV_UNDI_ISR undi_isr;
+	struct s_PXENV_UNDI_CLOSE undi_close;
+	int rc;
+
+	/* Ensure ISR has exited cleanly */
+	while ( undinic->isr_processing ) {
+		undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
+		if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_ISR,
+					     &undi_isr,
+					     sizeof ( undi_isr ) ) ) != 0 )
+			break;
+		switch ( undi_isr.FuncFlag ) {
+		case PXENV_UNDI_ISR_OUT_TRANSMIT:
+		case PXENV_UNDI_ISR_OUT_RECEIVE:
+			/* Continue draining */
+			break;
+		default:
+			/* Stop processing */
+			undinic->isr_processing = 0;
+			break;
+		}
+	}
+
+	/* Close NIC */
+	pxeparent_call ( undinet_entry, PXENV_UNDI_CLOSE,
+			 &undi_close, sizeof ( undi_close ) );
+
+	/* Disable interrupt and unhook ISR */
+	disable_irq ( undinic->irq );
+	undinet_unhook_isr ( undinic->irq );
+
+	DBGC ( undinic, "UNDINIC %p closed\n", undinic );
+}
+
+/**
+ * Enable/disable interrupts
+ *
+ * @v netdev		Net device
+ * @v enable		Interrupts should be enabled
+ */
+static void undinet_irq ( struct net_device *netdev, int enable ) {
+	struct undi_nic *undinic = netdev->priv;
+
+	/* Cannot support interrupts yet */
+	DBGC ( undinic, "UNDINIC %p cannot %s interrupts\n",
+	       undinic, ( enable ? "enable" : "disable" ) );
+}
+
+/** UNDI network device operations */
+static struct net_device_operations undinet_operations = {
+	.open		= undinet_open,
+	.close		= undinet_close,
+	.transmit	= undinet_transmit,
+	.poll		= undinet_poll,
+	.irq   		= undinet_irq,
+};
+
+/**
+ * Probe UNDI device
+ *
+ * @v undi		UNDI device
+ * @ret rc		Return status code
+ */
+int undinet_probe ( struct undi_device *undi ) {
+	struct net_device *netdev;
+	struct undi_nic *undinic;
+	struct s_PXENV_START_UNDI start_undi;
+	struct s_PXENV_UNDI_STARTUP undi_startup;
+	struct s_PXENV_UNDI_INITIALIZE undi_initialize;
+	struct s_PXENV_UNDI_GET_INFORMATION undi_info;
+	struct s_PXENV_UNDI_GET_IFACE_INFO undi_iface;
+	struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
+	struct s_PXENV_UNDI_CLEANUP undi_cleanup;
+	struct s_PXENV_STOP_UNDI stop_undi;
+	int rc;
+
+	/* Allocate net device */
+	netdev = alloc_etherdev ( sizeof ( *undinic ) );
+	if ( ! netdev )
+		return -ENOMEM;
+	netdev_init ( netdev, &undinet_operations );
+	undinic = netdev->priv;
+	undi_set_drvdata ( undi, netdev );
+	netdev->dev = &undi->dev;
+	memset ( undinic, 0, sizeof ( *undinic ) );
+	undinet_entry = undi->entry;
+	DBGC ( undinic, "UNDINIC %p using UNDI %p\n", undinic, undi );
+
+	/* Hook in UNDI stack */
+	if ( ! ( undi->flags & UNDI_FL_STARTED ) ) {
+		memset ( &start_undi, 0, sizeof ( start_undi ) );
+		start_undi.AX = undi->pci_busdevfn;
+		start_undi.BX = undi->isapnp_csn;
+		start_undi.DX = undi->isapnp_read_port;
+		start_undi.ES = BIOS_SEG;
+		start_undi.DI = find_pnp_bios();
+		if ( ( rc = pxeparent_call ( undinet_entry, PXENV_START_UNDI,
+					     &start_undi,
+					     sizeof ( start_undi ) ) ) != 0 )
+			goto err_start_undi;
+	}
+	undi->flags |= UNDI_FL_STARTED;
+
+	/* Bring up UNDI stack */
+	if ( ! ( undi->flags & UNDI_FL_INITIALIZED ) ) {
+		memset ( &undi_startup, 0, sizeof ( undi_startup ) );
+		if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_STARTUP,
+					     &undi_startup,
+					     sizeof ( undi_startup ) ) ) != 0 )
+			goto err_undi_startup;
+		memset ( &undi_initialize, 0, sizeof ( undi_initialize ) );
+		if ( ( rc = pxeparent_call ( undinet_entry,
+					     PXENV_UNDI_INITIALIZE,
+					     &undi_initialize,
+					     sizeof ( undi_initialize ))) != 0 )
+			goto err_undi_initialize;
+	}
+	undi->flags |= UNDI_FL_INITIALIZED;
+
+	/* Get device information */
+	memset ( &undi_info, 0, sizeof ( undi_info ) );
+	if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_GET_INFORMATION,
+				     &undi_info, sizeof ( undi_info ) ) ) != 0 )
+		goto err_undi_get_information;
+	memcpy ( netdev->hw_addr, undi_info.PermNodeAddress, ETH_ALEN );
+	undinic->irq = undi_info.IntNumber;
+	if ( undinic->irq > IRQ_MAX ) {
+		DBGC ( undinic, "UNDINIC %p invalid IRQ %d\n",
+		       undinic, undinic->irq );
+		goto err_bad_irq;
+	}
+	DBGC ( undinic, "UNDINIC %p is %s on IRQ %d\n",
+	       undinic, eth_ntoa ( netdev->hw_addr ), undinic->irq );
+
+	/* Get interface information */
+	memset ( &undi_iface, 0, sizeof ( undi_iface ) );
+	if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_GET_IFACE_INFO,
+				     &undi_iface,
+				     sizeof ( undi_iface ) ) ) != 0 )
+		goto err_undi_get_iface_info;
+	DBGC ( undinic, "UNDINIC %p has type %s, speed %d, flags %08x\n",
+	       undinic, undi_iface.IfaceType, undi_iface.LinkSpeed,
+	       undi_iface.ServiceFlags );
+	if ( strncmp ( ( ( char * ) undi_iface.IfaceType ), "Etherboot",
+		       sizeof ( undi_iface.IfaceType ) ) == 0 ) {
+		DBGC ( undinic, "UNDINIC %p Etherboot 5.4 workaround enabled\n",
+		       undinic );
+		undinic->hacks |= UNDI_HACK_EB54;
+	}
+
+	/* Mark as link up; we don't handle link state */
+	netdev_link_up ( netdev );
+
+	/* Register network device */
+	if ( ( rc = register_netdev ( netdev ) ) != 0 )
+		goto err_register;
+
+	DBGC ( undinic, "UNDINIC %p added\n", undinic );
+	return 0;
+
+ err_register:
+ err_undi_get_iface_info:
+ err_bad_irq:
+ err_undi_get_information:
+ err_undi_initialize:
+	/* Shut down UNDI stack */
+	memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
+	pxeparent_call ( undinet_entry, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
+			 sizeof ( undi_shutdown ) );
+	memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
+	pxeparent_call ( undinet_entry, PXENV_UNDI_CLEANUP, &undi_cleanup,
+			 sizeof ( undi_cleanup ) );
+	undi->flags &= ~UNDI_FL_INITIALIZED;
+ err_undi_startup:
+	/* Unhook UNDI stack */
+	memset ( &stop_undi, 0, sizeof ( stop_undi ) );
+	pxeparent_call ( undinet_entry, PXENV_STOP_UNDI, &stop_undi,
+			 sizeof ( stop_undi ) );
+	undi->flags &= ~UNDI_FL_STARTED;
+ err_start_undi:
+	netdev_nullify ( netdev );
+	netdev_put ( netdev );
+	undi_set_drvdata ( undi, NULL );
+	return rc;
+}
+
+/**
+ * Remove UNDI device
+ *
+ * @v undi		UNDI device
+ */
+void undinet_remove ( struct undi_device *undi ) {
+	struct net_device *netdev = undi_get_drvdata ( undi );
+	struct undi_nic *undinic = netdev->priv;
+	struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
+	struct s_PXENV_UNDI_CLEANUP undi_cleanup;
+	struct s_PXENV_STOP_UNDI stop_undi;
+
+	/* Unregister net device */
+	unregister_netdev ( netdev );
+
+	/* If we are preparing for an OS boot, or if we cannot exit
+	 * via the PXE stack, then shut down the PXE stack.
+	 */
+	if ( ! ( undi->flags & UNDI_FL_KEEP_ALL ) ) {
+
+		/* Shut down UNDI stack */
+		memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
+		pxeparent_call ( undinet_entry, PXENV_UNDI_SHUTDOWN,
+				 &undi_shutdown, sizeof ( undi_shutdown ) );
+		memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
+		pxeparent_call ( undinet_entry, PXENV_UNDI_CLEANUP,
+				 &undi_cleanup, sizeof ( undi_cleanup ) );
+		undi->flags &= ~UNDI_FL_INITIALIZED;
+
+		/* Unhook UNDI stack */
+		memset ( &stop_undi, 0, sizeof ( stop_undi ) );
+		pxeparent_call ( undinet_entry, PXENV_STOP_UNDI, &stop_undi,
+				 sizeof ( stop_undi ) );
+		undi->flags &= ~UNDI_FL_STARTED;
+	}
+
+	/* Clear entry point */
+	memset ( &undinet_entry, 0, sizeof ( undinet_entry ) );
+
+	/* Free network device */
+	netdev_nullify ( netdev );
+	netdev_put ( netdev );
+
+	DBGC ( undinic, "UNDINIC %p removed\n", undinic );
+}
diff --git a/gpxe/src/arch/i386/drivers/net/undionly.c b/gpxe/src/arch/i386/drivers/net/undionly.c
new file mode 100644
index 0000000..7dfb5d1
--- /dev/null
+++ b/gpxe/src/arch/i386/drivers/net/undionly.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gpxe/device.h>
+#include <gpxe/init.h>
+#include <undi.h>
+#include <undinet.h>
+#include <undipreload.h>
+
+/** @file
+ *
+ * "Pure" UNDI driver
+ *
+ * This is the UNDI driver without explicit support for PCI or any
+ * other bus type.  It is capable only of using the preloaded UNDI
+ * device.  It must not be combined in an image with any other
+ * drivers.
+ *
+ * If you want a PXE-loadable image that contains only the UNDI
+ * driver, build "bin/undionly.kpxe".
+ *
+ * If you want any other image format, or any other drivers in
+ * addition to the UNDI driver, build e.g. "bin/undi.dsk".
+ */
+
+/**
+ * Probe UNDI root bus
+ *
+ * @v rootdev		UNDI bus root device
+ *
+ * Scans the UNDI bus for devices and registers all devices it can
+ * find.
+ */
+static int undibus_probe ( struct root_device *rootdev ) {
+	struct undi_device *undi = &preloaded_undi;
+	int rc;
+
+	/* Check for a valie preloaded UNDI device */
+	if ( ! undi->entry.segment ) {
+		DBG ( "No preloaded UNDI device found!\n" );
+		return -ENODEV;
+	}
+
+	/* Add to device hierarchy */
+	strncpy ( undi->dev.name, "UNDI",
+		  ( sizeof ( undi->dev.name ) - 1 ) );
+	if ( undi->pci_busdevfn != UNDI_NO_PCI_BUSDEVFN ) {
+		undi->dev.desc.bus_type = BUS_TYPE_PCI;
+		undi->dev.desc.location = undi->pci_busdevfn;
+		undi->dev.desc.vendor = undi->pci_vendor;
+		undi->dev.desc.device = undi->pci_device;
+	} else if ( undi->isapnp_csn != UNDI_NO_ISAPNP_CSN ) {
+		undi->dev.desc.bus_type = BUS_TYPE_ISAPNP;
+	}
+	undi->dev.parent = &rootdev->dev;
+	list_add ( &undi->dev.siblings, &rootdev->dev.children);
+	INIT_LIST_HEAD ( &undi->dev.children );
+
+	/* Create network device */
+	if ( ( rc = undinet_probe ( undi ) ) != 0 )
+		goto err;
+
+	return 0;
+
+ err:
+	list_del ( &undi->dev.siblings );
+	return rc;
+}
+
+/**
+ * Remove UNDI root bus
+ *
+ * @v rootdev		UNDI bus root device
+ */
+static void undibus_remove ( struct root_device *rootdev __unused ) {
+	struct undi_device *undi = &preloaded_undi;
+
+	undinet_remove ( undi );
+	list_del ( &undi->dev.siblings );
+}
+
+/** UNDI bus root device driver */
+static struct root_driver undi_root_driver = {
+	.probe = undibus_probe,
+	.remove = undibus_remove,
+};
+
+/** UNDI bus root device */
+struct root_device undi_root_device __root_device = {
+	.dev = { .name = "UNDI" },
+	.driver = &undi_root_driver,
+};
+
+/**
+ * Prepare for exit
+ *
+ * @v flags		Shutdown flags
+ */
+static void undionly_shutdown ( int flags ) {
+	/* If we are shutting down to boot an OS, clear the "keep PXE
+	 * stack" flag.
+	 */
+	if ( flags & SHUTDOWN_BOOT )
+		preloaded_undi.flags &= ~UNDI_FL_KEEP_ALL;
+}
+
+struct startup_fn startup_undionly __startup_fn ( STARTUP_LATE ) = {
+	.shutdown = undionly_shutdown,
+};
diff --git a/gpxe/src/arch/i386/drivers/net/undipreload.c b/gpxe/src/arch/i386/drivers/net/undipreload.c
new file mode 100644
index 0000000..a4b2f4a
--- /dev/null
+++ b/gpxe/src/arch/i386/drivers/net/undipreload.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <realmode.h>
+#include <undipreload.h>
+
+/** @file
+ *
+ * Preloaded UNDI stack
+ *
+ */
+
+/**
+ * Preloaded UNDI device
+ *
+ * This is the UNDI device that was present when Etherboot started
+ * execution (i.e. when loading a .kpxe image).  The first driver to
+ * claim this device must zero out this data structure.
+ */
+struct undi_device __data16 ( preloaded_undi );
diff --git a/gpxe/src/arch/i386/drivers/net/undirom.c b/gpxe/src/arch/i386/drivers/net/undirom.c
new file mode 100644
index 0000000..2463d96
--- /dev/null
+++ b/gpxe/src/arch/i386/drivers/net/undirom.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pxe.h>
+#include <realmode.h>
+#include <undirom.h>
+
+/** @file
+ *
+ * UNDI expansion ROMs
+ *
+ */
+
+/** List of all UNDI ROMs */
+static LIST_HEAD ( undiroms );
+
+/**
+ * Parse PXE ROM ID structure
+ *
+ * @v undirom		UNDI ROM
+ * @v pxeromid		Offset within ROM to PXE ROM ID structure
+ * @ret rc		Return status code
+ */
+static int undirom_parse_pxeromid ( struct undi_rom *undirom,
+				   unsigned int pxeromid ) {
+	struct undi_rom_id undi_rom_id;
+	unsigned int undiloader;
+
+	DBGC ( undirom, "UNDIROM %p has PXE ROM ID at %04x:%04x\n", undirom,
+	       undirom->rom_segment, pxeromid );
+
+	/* Read PXE ROM ID structure and verify */
+	copy_from_real ( &undi_rom_id, undirom->rom_segment, pxeromid,
+			 sizeof ( undi_rom_id ) );
+	if ( undi_rom_id.Signature != UNDI_ROM_ID_SIGNATURE ) {
+		DBGC ( undirom, "UNDIROM %p has bad PXE ROM ID signature "
+		       "%08x\n", undirom, undi_rom_id.Signature );
+		return -EINVAL;
+	}
+
+	/* Check for UNDI loader */
+	undiloader = undi_rom_id.UNDILoader;
+	if ( ! undiloader ) {
+		DBGC ( undirom, "UNDIROM %p has no UNDI loader\n", undirom );
+		return -EINVAL;
+	}
+
+	/* Fill in UNDI ROM loader fields */
+	undirom->loader_entry.segment = undirom->rom_segment;
+	undirom->loader_entry.offset = undiloader;
+	undirom->code_size = undi_rom_id.CodeSize;
+	undirom->data_size = undi_rom_id.DataSize;
+
+	DBGC ( undirom, "UNDIROM %p has UNDI loader at %04x:%04x "
+	       "(code %04zx data %04zx)\n", undirom,
+	       undirom->loader_entry.segment, undirom->loader_entry.offset,
+	       undirom->code_size, undirom->data_size );
+	return 0;
+}
+
+/**
+ * Parse PCI expansion header
+ *
+ * @v undirom		UNDI ROM
+ * @v pcirheader	Offset within ROM to PCI expansion header
+ */
+static int undirom_parse_pcirheader ( struct undi_rom *undirom,
+				     unsigned int pcirheader ) {
+	struct pcir_header pcir_header;
+
+	DBGC ( undirom, "UNDIROM %p has PCI expansion header at %04x:%04x\n",
+	       undirom, undirom->rom_segment, pcirheader );
+
+	/* Read PCI expansion header and verify */
+	copy_from_real ( &pcir_header, undirom->rom_segment, pcirheader,
+			 sizeof ( pcir_header ) );
+	if ( pcir_header.signature != PCIR_SIGNATURE ) {
+		DBGC ( undirom, "UNDIROM %p has bad PCI expansion header "
+		       "signature %08x\n", undirom, pcir_header.signature );
+		return -EINVAL;
+	}
+
+	/* Fill in UNDI ROM PCI device fields */
+	undirom->bus_type = PCI_NIC;
+	undirom->bus_id.pci.vendor_id = pcir_header.vendor_id;
+	undirom->bus_id.pci.device_id = pcir_header.device_id;
+
+	DBGC ( undirom, "UNDIROM %p is for PCI devices %04x:%04x\n", undirom,
+	       undirom->bus_id.pci.vendor_id, undirom->bus_id.pci.device_id );
+	return 0;
+	
+}
+
+/**
+ * Probe UNDI ROM
+ *
+ * @v rom_segment	ROM segment address
+ * @ret rc		Return status code
+ */
+static int undirom_probe ( unsigned int rom_segment ) {
+	struct undi_rom *undirom = NULL;
+	struct undi_rom_header romheader;
+	size_t rom_len;
+	unsigned int pxeromid;
+	unsigned int pcirheader;
+	int rc;
+
+	/* Read expansion ROM header and verify */
+	copy_from_real ( &romheader, rom_segment, 0, sizeof ( romheader ) );
+	if ( romheader.Signature != ROM_SIGNATURE ) {
+		rc = -EINVAL;
+		goto err;
+	}
+	rom_len = ( romheader.ROMLength * 512 );
+
+	/* Allocate memory for UNDI ROM */
+	undirom = zalloc ( sizeof ( *undirom ) );
+	if ( ! undirom ) {
+		DBG ( "Could not allocate UNDI ROM structure\n" );
+		rc = -ENOMEM;
+		goto err;
+	}
+	DBGC ( undirom, "UNDIROM %p trying expansion ROM at %04x:0000 "
+	       "(%zdkB)\n", undirom, rom_segment, ( rom_len / 1024 ) );
+	undirom->rom_segment = rom_segment;
+
+	/* Check for and parse PXE ROM ID */
+	pxeromid = romheader.PXEROMID;
+	if ( ! pxeromid ) {
+		DBGC ( undirom, "UNDIROM %p has no PXE ROM ID\n", undirom );
+		rc = -EINVAL;
+		goto err;
+	}
+	if ( pxeromid > rom_len ) {
+		DBGC ( undirom, "UNDIROM %p PXE ROM ID outside ROM\n",
+		       undirom );
+		rc = -EINVAL;
+		goto err;
+	}
+	if ( ( rc = undirom_parse_pxeromid ( undirom, pxeromid ) ) != 0 )
+		goto err;
+
+	/* Parse PCIR header, if present */
+	pcirheader = romheader.PCIRHeader;
+	if ( pcirheader )
+		undirom_parse_pcirheader ( undirom, pcirheader );
+
+	/* Add to UNDI ROM list and return */
+	DBGC ( undirom, "UNDIROM %p registered\n", undirom );
+	list_add ( &undirom->list, &undiroms );
+	return 0;
+
+ err:
+	free ( undirom );
+	return rc;
+}
+
+/**
+ * Create UNDI ROMs for all possible expansion ROMs
+ *
+ * @ret 
+ */
+static void undirom_probe_all_roms ( void ) {
+	static int probed = 0;
+	unsigned int rom_segment;
+
+	/* Perform probe only once */
+	if ( probed )
+		return;
+
+	DBG ( "Scanning for PXE expansion ROMs\n" );
+
+	/* Scan through expansion ROM region at 512 byte intervals */
+	for ( rom_segment = 0xc000 ; rom_segment < 0x10000 ;
+	      rom_segment += 0x20 ) {
+		undirom_probe ( rom_segment );
+	}
+
+	probed = 1;
+}
+
+/**
+ * Find UNDI ROM for PCI device
+ *
+ * @v vendor_id		PCI vendor ID
+ * @v device_id		PCI device ID
+ * @v rombase		ROM base address, or 0 for any
+ * @ret undirom		UNDI ROM, or NULL
+ */
+struct undi_rom * undirom_find_pci ( unsigned int vendor_id,
+				     unsigned int device_id,
+				     unsigned int rombase ) {
+	struct undi_rom *undirom;
+
+	undirom_probe_all_roms();
+
+	list_for_each_entry ( undirom, &undiroms, list ) {
+		if ( undirom->bus_type != PCI_NIC )
+			continue;
+		if ( undirom->bus_id.pci.vendor_id != vendor_id )
+			continue;
+		if ( undirom->bus_id.pci.device_id != device_id )
+			continue;
+		if ( rombase && ( ( undirom->rom_segment << 4 ) != rombase ) )
+			continue;
+		DBGC ( undirom, "UNDIROM %p matched PCI %04x:%04x (%08x)\n",
+		       undirom, vendor_id, device_id, rombase );
+		return undirom;
+	}
+
+	DBG ( "No UNDI ROM matched PCI %04x:%04x (%08x)\n",
+	      vendor_id, device_id, rombase );
+	return NULL;
+}
diff --git a/gpxe/src/arch/i386/firmware/pcbios/basemem.c b/gpxe/src/arch/i386/firmware/pcbios/basemem.c
new file mode 100644
index 0000000..1ba7d1f
--- /dev/null
+++ b/gpxe/src/arch/i386/firmware/pcbios/basemem.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <realmode.h>
+#include <bios.h>
+#include <basemem.h>
+#include <gpxe/hidemem.h>
+
+/** @file
+ *
+ * Base memory allocation
+ *
+ */
+
+/**
+ * Set the BIOS free base memory counter
+ *
+ * @v new_fbms		New free base memory counter (in kB)
+ */
+void set_fbms ( unsigned int new_fbms ) {
+	uint16_t fbms = new_fbms;
+
+	/* Update the BIOS memory counter */
+	put_real ( fbms, BDA_SEG, BDA_FBMS );
+
+	/* Update our hidden memory region map */
+	hide_basemem();
+}
diff --git a/gpxe/src/arch/i386/firmware/pcbios/bios_console.c b/gpxe/src/arch/i386/firmware/pcbios/bios_console.c
new file mode 100644
index 0000000..1d18e54
--- /dev/null
+++ b/gpxe/src/arch/i386/firmware/pcbios/bios_console.c
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <assert.h>
+#include <realmode.h>
+#include <console.h>
+#include <gpxe/ansiesc.h>
+
+#define ATTR_BOLD		0x08
+
+#define ATTR_FCOL_MASK		0x07
+#define ATTR_FCOL_BLACK		0x00
+#define ATTR_FCOL_BLUE		0x01
+#define ATTR_FCOL_GREEN		0x02
+#define ATTR_FCOL_CYAN		0x03
+#define ATTR_FCOL_RED		0x04
+#define ATTR_FCOL_MAGENTA	0x05
+#define ATTR_FCOL_YELLOW	0x06
+#define ATTR_FCOL_WHITE		0x07
+
+#define ATTR_BCOL_MASK		0x70
+#define ATTR_BCOL_BLACK		0x00
+#define ATTR_BCOL_BLUE		0x10
+#define ATTR_BCOL_GREEN		0x20
+#define ATTR_BCOL_CYAN		0x30
+#define ATTR_BCOL_RED		0x40
+#define ATTR_BCOL_MAGENTA	0x50
+#define ATTR_BCOL_YELLOW	0x60
+#define ATTR_BCOL_WHITE		0x70
+
+#define ATTR_DEFAULT		ATTR_FCOL_WHITE
+
+/** Current character attribute */
+static unsigned int bios_attr = ATTR_DEFAULT;
+
+/**
+ * Handle ANSI CUP (cursor position)
+ *
+ * @v count		Parameter count
+ * @v params[0]		Row (1 is top)
+ * @v params[1]		Column (1 is left)
+ */
+static void bios_handle_cup ( unsigned int count __unused, int params[] ) {
+	int cx = ( params[1] - 1 );
+	int cy = ( params[0] - 1 );
+
+	if ( cx < 0 )
+		cx = 0;
+	if ( cy < 0 )
+		cy = 0;
+
+	__asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
+					   "int $0x10\n\t"
+					   "cli\n\t" )
+			       : : "a" ( 0x0200 ), "b" ( 1 ),
+			           "d" ( ( cy << 8 ) | cx ) );
+}
+
+/**
+ * Handle ANSI ED (erase in page)
+ *
+ * @v count		Parameter count
+ * @v params[0]		Region to erase
+ */
+static void bios_handle_ed ( unsigned int count __unused,
+			     int params[] __unused ) {
+	/* We assume that we always clear the whole screen */
+	assert ( params[0] == ANSIESC_ED_ALL );
+
+	__asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
+					   "int $0x10\n\t"
+					   "cli\n\t" )
+			       : : "a" ( 0x0600 ), "b" ( bios_attr << 8 ),
+			           "c" ( 0 ), "d" ( 0xffff ) );
+}
+
+/**
+ * Handle ANSI SGR (set graphics rendition)
+ *
+ * @v count		Parameter count
+ * @v params		List of graphic rendition aspects
+ */
+static void bios_handle_sgr ( unsigned int count, int params[] ) {
+	static const uint8_t bios_attr_fcols[10] = {
+		ATTR_FCOL_BLACK, ATTR_FCOL_RED, ATTR_FCOL_GREEN,
+		ATTR_FCOL_YELLOW, ATTR_FCOL_BLUE, ATTR_FCOL_MAGENTA,
+		ATTR_FCOL_CYAN, ATTR_FCOL_WHITE,
+		ATTR_FCOL_WHITE, ATTR_FCOL_WHITE /* defaults */
+	};
+	static const uint8_t bios_attr_bcols[10] = {
+		ATTR_BCOL_BLACK, ATTR_BCOL_RED, ATTR_BCOL_GREEN,
+		ATTR_BCOL_YELLOW, ATTR_BCOL_BLUE, ATTR_BCOL_MAGENTA,
+		ATTR_BCOL_CYAN, ATTR_BCOL_WHITE,
+		ATTR_BCOL_BLACK, ATTR_BCOL_BLACK /* defaults */
+	};
+	unsigned int i;
+	int aspect;
+
+	for ( i = 0 ; i < count ; i++ ) {
+		aspect = params[i];
+		if ( aspect == 0 ) {
+			bios_attr = ATTR_DEFAULT;
+		} else if ( aspect == 1 ) {
+			bios_attr |= ATTR_BOLD;
+		} else if ( aspect == 22 ) {
+			bios_attr &= ~ATTR_BOLD;
+		} else if ( ( aspect >= 30 ) && ( aspect <= 39 ) ) {
+			bios_attr &= ~ATTR_FCOL_MASK;
+			bios_attr |= bios_attr_fcols[ aspect - 30 ];
+		} else if ( ( aspect >= 40 ) && ( aspect <= 49 ) ) {
+			bios_attr &= ~ATTR_BCOL_MASK;
+			bios_attr |= bios_attr_bcols[ aspect - 40 ];
+		}
+	}
+}
+
+/** BIOS console ANSI escape sequence handlers */
+static struct ansiesc_handler bios_ansiesc_handlers[] = {
+	{ ANSIESC_CUP, bios_handle_cup },
+	{ ANSIESC_ED, bios_handle_ed },
+	{ ANSIESC_SGR, bios_handle_sgr },
+	{ 0, NULL }
+};
+
+/** BIOS console ANSI escape sequence context */
+static struct ansiesc_context bios_ansiesc_ctx = {
+	.handlers = bios_ansiesc_handlers,
+};
+
+/**
+ * Print a character to BIOS console
+ *
+ * @v character		Character to be printed
+ */
+static void bios_putchar ( int character ) {
+	int discard_a, discard_b, discard_c;
+
+	/* Intercept ANSI escape sequences */
+	character = ansiesc_process ( &bios_ansiesc_ctx, character );
+	if ( character < 0 )
+		return;
+
+	/* Print character with attribute */
+	__asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
+					   /* Skip non-printable characters */
+					   "cmpb $0x20, %%al\n\t"
+					   "jb 1f\n\t"
+					   /* Set attribute */
+					   "movw $0x0001, %%cx\n\t"
+					   "movb $0x09, %%ah\n\t"
+					   "int $0x10\n\t"
+					   "\n1:\n\t"
+					   /* Print character */
+					   "xorw %%bx, %%bx\n\t"
+					   "movb $0x0e, %%ah\n\t"
+					   "int $0x10\n\t"
+					   "cli\n\t" )
+			       : "=a" ( discard_a ), "=b" ( discard_b ),
+			         "=c" ( discard_c )
+			       : "a" ( character ), "b" ( bios_attr )
+			       : "ebp" );
+}
+
+/**
+ * Pointer to current ANSI output sequence
+ *
+ * While we are in the middle of returning an ANSI sequence for a
+ * special key, this will point to the next character to return.  When
+ * not in the middle of such a sequence, this will point to a NUL
+ * (note: not "will be NULL").
+ */
+static const char *ansi_input = "";
+
+/**
+ * Lowest BIOS scancode of interest
+ *
+ * Most of the BIOS key scancodes that we are interested in are in a
+ * dense range, so subtracting a constant and treating them as offsets
+ * into an array works efficiently.
+ */
+#define BIOS_KEY_MIN 0x42
+
+/** Offset into list of interesting BIOS scancodes */
+#define BIOS_KEY(scancode) ( (scancode) - BIOS_KEY_MIN )
+
+/** Mapping from BIOS scan codes to ANSI escape sequences */
+static const char *ansi_sequences[] = {
+	[ BIOS_KEY ( 0x42 ) ] = "[19~",	/* F8 (required for PXE) */
+	[ BIOS_KEY ( 0x47 ) ] = "[H",	/* Home */
+	[ BIOS_KEY ( 0x48 ) ] = "[A",	/* Up arrow */
+	[ BIOS_KEY ( 0x4b ) ] = "[D",	/* Left arrow */
+	[ BIOS_KEY ( 0x4d ) ] = "[C",	/* Right arrow */
+	[ BIOS_KEY ( 0x4f ) ] = "[F",	/* End */
+	[ BIOS_KEY ( 0x50 ) ] = "[B",	/* Down arrow */
+	[ BIOS_KEY ( 0x53 ) ] = "[3~",	/* Delete */
+};
+
+/**
+ * Get ANSI escape sequence corresponding to BIOS scancode
+ *
+ * @v scancode		BIOS scancode
+ * @ret ansi_seq	ANSI escape sequence, if any, otherwise NULL
+ */
+static const char * scancode_to_ansi_seq ( unsigned int scancode ) {
+	unsigned int bios_key = BIOS_KEY ( scancode );
+	
+	if ( bios_key < ( sizeof ( ansi_sequences ) /
+			  sizeof ( ansi_sequences[0] ) ) ) {
+		return ansi_sequences[bios_key];
+	}
+	DBG ( "Unrecognised BIOS scancode %02x\n", scancode );
+	return NULL;
+}
+
+/**
+ * Get character from BIOS console
+ *
+ * @ret character	Character read from console
+ */
+static int bios_getchar ( void ) {
+	uint16_t keypress;
+	unsigned int character;
+	const char *ansi_seq;
+
+	/* If we are mid-sequence, pass out the next byte */
+	if ( ( character = *ansi_input ) ) {
+		ansi_input++;
+		return character;
+	}
+
+	/* Read character from real BIOS console */
+	__asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
+					   "int $0x16\n\t"
+					   "cli\n\t" )
+			       : "=a" ( keypress ) : "a" ( 0x1000 ) );
+	character = ( keypress & 0xff );
+
+	/* If it's a normal character, just return it */
+	if ( character && ( character < 0x80 ) )
+		return character;
+
+	/* Otherwise, check for a special key that we know about */
+	if ( ( ansi_seq = scancode_to_ansi_seq ( keypress >> 8 ) ) ) {
+		/* Start of escape sequence: return ESC (0x1b) */
+		ansi_input = ansi_seq;
+		return 0x1b;
+	}
+
+	return 0;
+}
+
+/**
+ * Check for character ready to read from BIOS console
+ *
+ * @ret True		Character available to read
+ * @ret False		No character available to read
+ */
+static int bios_iskey ( void ) {
+	unsigned int discard_a;
+	unsigned int flags;
+
+	/* If we are mid-sequence, we are always ready */
+	if ( *ansi_input )
+		return 1;
+
+	/* Otherwise check the real BIOS console */
+	__asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
+					   "int $0x16\n\t"
+					   "pushfw\n\t"
+					   "popw %w0\n\t"
+					   "cli\n\t" )
+			       : "=r" ( flags ), "=a" ( discard_a )
+			       : "a" ( 0x0100 ) );
+	return ( ! ( flags & ZF ) );
+}
+
+struct console_driver bios_console __console_driver = {
+	.putchar = bios_putchar,
+	.getchar = bios_getchar,
+	.iskey = bios_iskey,
+};
diff --git a/gpxe/src/arch/i386/firmware/pcbios/e820mangler.S b/gpxe/src/arch/i386/firmware/pcbios/e820mangler.S
new file mode 100644
index 0000000..99ca519
--- /dev/null
+++ b/gpxe/src/arch/i386/firmware/pcbios/e820mangler.S
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+	.text
+	.arch i386
+	.code16
+
+#define SMAP 0x534d4150
+
+/* Most documentation refers to the E820 buffer as being 20 bytes, and
+ * the API makes it perfectly legitimate to pass only a 20-byte buffer
+ * and expect to get valid data.  However, some morons at ACPI decided
+ * to extend the data structure by adding an extra "extended
+ * attributes" field and by including critical information within this
+ * field, such as whether or not the region is enabled.  A caller who
+ * passes in only a 20-byte buffer therefore risks getting very, very
+ * misleading information.
+ *
+ * I have personally witnessed an HP BIOS that returns a value of
+ * 0x0009 in the extended attributes field.  If we don't pass this
+ * value through to the caller, 32-bit WinPE will die, usually with a
+ * PAGE_FAULT_IN_NONPAGED_AREA blue screen of death.
+ *
+ * Allow a ridiculously large maximum value (64 bytes) for the E820
+ * buffer as a guard against insufficiently creative idiots in the
+ * future.
+ */
+#define E820MAXSIZE	64
+
+/****************************************************************************
+ *
+ * Allowed memory windows
+ *
+ * There are two ways to view this list.  The first is as a list of
+ * (non-overlapping) allowed memory regions, sorted by increasing
+ * address.  The second is as a list of (non-overlapping) hidden
+ * memory regions, again sorted by increasing address.  The second
+ * view is offset by half an entry from the first: think about this
+ * for a moment and it should make sense.
+ *
+ * xxx_memory_window is used to indicate an "allowed region"
+ * structure, hidden_xxx_memory is used to indicate a "hidden region"
+ * structure.  Each structure is 16 bytes in length.
+ *
+ ****************************************************************************
+ */
+	.section ".data16", "aw", @progbits
+	.align 16
+	.globl hidemem_base
+	.globl hidemem_umalloc
+	.globl hidemem_textdata
+memory_windows:
+base_memory_window:	.long 0x00000000, 0x00000000 /* Start of memory */
+
+hidemem_base:		.long 0x000a0000, 0x00000000 /* Changes at runtime */
+ext_memory_window:	.long 0x000a0000, 0x00000000 /* 640kB mark */
+
+hidemem_umalloc:	.long 0xffffffff, 0xffffffff /* Changes at runtime */
+			.long 0xffffffff, 0xffffffff /* Changes at runtime */
+
+hidemem_textdata:	.long 0xffffffff, 0xffffffff /* Changes at runtime */
+			.long 0xffffffff, 0xffffffff /* Changes at runtime */
+
+			.long 0xffffffff, 0xffffffff /* End of memory */
+memory_windows_end:
+
+/****************************************************************************
+ * Truncate region to memory window
+ *
+ * Parameters:
+ *  %edx:%eax	Start of region
+ *  %ecx:%ebx	Length of region
+ *  %si		Memory window
+ * Returns:
+ *  %edx:%eax	Start of windowed region
+ *  %ecx:%ebx	Length of windowed region
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+window_region:
+	/* Convert (start,len) to (start, end) */
+	addl	%eax, %ebx
+	adcl	%edx, %ecx
+	/* Truncate to window start */
+	cmpl	4(%si), %edx
+	jne	1f
+	cmpl	0(%si), %eax
+1:	jae	2f
+	movl	4(%si), %edx
+	movl	0(%si), %eax
+2:	/* Truncate to window end */
+	cmpl	12(%si), %ecx
+	jne	1f
+	cmpl	8(%si), %ebx
+1:	jbe	2f
+	movl	12(%si), %ecx
+	movl	8(%si), %ebx
+2:	/* Convert (start, end) back to (start, len) */
+	subl	%eax, %ebx
+	sbbl	%edx, %ecx
+	/* If length is <0, set length to 0 */
+	jae	1f
+	xorl	%ebx, %ebx
+	xorl	%ecx, %ecx
+	ret
+	.size	window_region, . - window_region
+
+/****************************************************************************
+ * Patch "memory above 1MB" figure
+ *
+ * Parameters:
+ *  %ax		Memory above 1MB, in 1kB blocks
+ * Returns:
+ *  %ax		Modified memory above 1M in 1kB blocks
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+patch_1m:
+	pushal
+	/* Convert to (start,len) format and call truncate */
+	xorl	%ecx, %ecx
+	movzwl	%ax, %ebx
+	shll	$10, %ebx
+	xorl	%edx, %edx
+	movl	$0x100000, %eax
+	movw	$ext_memory_window, %si
+	call	window_region
+	/* Convert back to "memory above 1MB" format and return via %ax */
+	pushfw
+	shrl	$10, %ebx
+	popfw
+	movw	%sp, %bp
+	movw	%bx, 28(%bp)
+	popal
+	ret
+	.size patch_1m, . - patch_1m
+
+/****************************************************************************
+ * Patch "memory above 16MB" figure
+ *
+ * Parameters:
+ *  %bx		Memory above 16MB, in 64kB blocks
+ * Returns:
+ *  %bx		Modified memory above 16M in 64kB blocks
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+patch_16m:
+	pushal
+	/* Convert to (start,len) format and call truncate */
+	xorl	%ecx, %ecx
+	shll	$16, %ebx
+	xorl	%edx, %edx
+	movl	$0x1000000, %eax
+	movw	$ext_memory_window, %si
+	call	window_region
+	/* Convert back to "memory above 16MB" format and return via %bx */
+	pushfw
+	shrl	$16, %ebx
+	popfw
+	movw	%sp, %bp
+	movw	%bx, 16(%bp)
+	popal
+	ret
+	.size patch_16m, . - patch_16m
+
+/****************************************************************************
+ * Patch "memory between 1MB and 16MB" and "memory above 16MB" figures
+ *
+ * Parameters:
+ *  %ax		Memory between 1MB and 16MB, in 1kB blocks
+ *  %bx		Memory above 16MB, in 64kB blocks
+ * Returns:
+ *  %ax		Modified memory between 1MB and 16MB, in 1kB blocks
+ *  %bx		Modified memory above 16MB, in 64kB blocks
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+patch_1m_16m:
+	call	patch_1m
+	call	patch_16m
+	/* If 1M region is no longer full-length, kill off the 16M region */
+	cmpw	$( 15 * 1024 ), %ax
+	je	1f
+	xorw	%bx, %bx
+1:	ret
+	.size patch_1m_16m, . - patch_1m_16m
+
+/****************************************************************************
+ * Get underlying e820 memory region to underlying_e820 buffer
+ *
+ * Parameters:
+ *   As for INT 15,e820
+ * Returns:
+ *   As for INT 15,e820
+ *
+ * Wraps the underlying INT 15,e820 call so that the continuation
+ * value (%ebx) is a 16-bit simple sequence counter (with the high 16
+ * bits ignored), and termination is always via CF=1 rather than
+ * %ebx=0.
+ *
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+get_underlying_e820:
+
+	/* If the requested region is in the cache, return it */
+	cmpw	%bx, underlying_e820_index
+	jne	2f
+	pushw	%di
+	pushw	%si
+	movw	$underlying_e820_cache, %si
+	cmpl	underlying_e820_cache_size, %ecx
+	jbe	1f
+	movl	underlying_e820_cache_size, %ecx
+1:	pushl	%ecx
+	rep movsb
+	popl	%ecx
+	popw	%si
+	popw	%di
+	incw	%bx
+	movl	%edx, %eax
+	clc
+	ret
+2:	
+	/* If the requested region is earlier than the cached region,
+	 * invalidate the cache.
+	 */
+	cmpw	%bx, underlying_e820_index
+	jbe	1f
+	movw	$0xffff, underlying_e820_index
+1:
+	/* If the cache is invalid, reset the underlying %ebx */
+	cmpw	$0xffff, underlying_e820_index
+	jne	1f
+	andl	$0, underlying_e820_ebx
+1:	
+	/* If the cache is valid but the continuation value is zero,
+	 * this means that the previous underlying call returned with
+	 * %ebx=0.  Return with CF=1 in this case.
+	 */
+	cmpw	$0xffff, underlying_e820_index
+	je	1f
+	cmpl	$0, underlying_e820_ebx
+	jne	1f
+	stc
+	ret
+1:	
+	/* Get the next region into the cache */
+	pushl	%eax
+	pushl	%ebx
+	pushl	%ecx
+	pushl	%edx
+	pushl	%esi	/* Some implementations corrupt %esi, so we	*/
+	pushl	%edi	/* preserve %esi, %edi and %ebp to be paranoid	*/
+	pushl	%ebp
+	pushw	%es
+	pushw	%ds
+	popw	%es
+	movw	$underlying_e820_cache, %di
+	cmpl	$E820MAXSIZE, %ecx
+	jbe	1f
+	movl	$E820MAXSIZE, %ecx
+1:	movl	underlying_e820_ebx, %ebx
+	stc
+	pushfw
+	lcall	*%cs:int15_vector
+	popw	%es
+	popl	%ebp
+	popl	%edi
+	popl	%esi
+	/* Check for error return from underlying e820 call */
+	jc	2f /* CF set: error */
+	cmpl	$SMAP, %eax
+	je	3f /* 'SMAP' missing: error */
+2:	/* An error occurred: return values returned by underlying e820 call */
+	stc	/* Force CF set if SMAP was missing */
+	addr32 leal 16(%esp), %esp /* avoid changing other flags */
+	ret
+3:	/* No error occurred */
+	movl	%ebx, underlying_e820_ebx
+	movl	%ecx, underlying_e820_cache_size
+	popl	%edx
+	popl	%ecx
+	popl	%ebx
+	popl	%eax
+	/* Mark cache as containing this result */
+	incw	underlying_e820_index
+
+	/* Loop until found */
+	jmp	get_underlying_e820
+	.size	get_underlying_e820, . - get_underlying_e820
+
+	.section ".data16", "aw", @progbits
+underlying_e820_index:
+	.word	0xffff /* Initialise to an invalid value */
+	.size underlying_e820_index, . - underlying_e820_index
+
+	.section ".bss16", "aw", @nobits
+underlying_e820_ebx:
+	.long	0
+	.size underlying_e820_ebx, . - underlying_e820_ebx
+
+	.section ".bss16", "aw", @nobits
+underlying_e820_cache:
+	.space	E820MAXSIZE
+	.size underlying_e820_cache, . - underlying_e820_cache
+
+	.section ".bss16", "aw", @nobits
+underlying_e820_cache_size:
+	.long	0
+	.size	underlying_e820_cache_size, . - underlying_e820_cache_size
+
+/****************************************************************************
+ * Get windowed e820 region, without empty region stripping
+ *
+ * Parameters:
+ *   As for INT 15,e820
+ * Returns:
+ *   As for INT 15,e820
+ *
+ * Wraps the underlying INT 15,e820 call so that each underlying
+ * region is returned N times, windowed to fit within N visible-memory
+ * windows.  Termination is always via CF=1.
+ *
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+get_windowed_e820:
+
+	/* Preserve registers */
+	pushl	%esi
+	pushw	%bp
+
+	/* Split %ebx into %si:%bx, store original %bx in %bp */
+	pushl	%ebx
+	popw	%bp
+	popw	%si
+
+	/* %si == 0 => start of memory_windows list */
+	testw	%si, %si
+	jne	1f
+	movw	$memory_windows, %si
+1:	
+	/* Get (cached) underlying e820 region to buffer */
+	call	get_underlying_e820
+	jc	99f /* Abort on error */
+
+	/* Preserve registers */
+	pushal
+	/* start => %edx:%eax, len => %ecx:%ebx */
+	movl	%es:0(%di), %eax
+	movl	%es:4(%di), %edx
+	movl	%es:8(%di), %ebx
+	movl	%es:12(%di), %ecx
+	/* Truncate region to current window */
+	call	window_region
+1:	/* Store modified values in e820 map entry */
+	movl	%eax, %es:0(%di)
+	movl	%edx, %es:4(%di)
+	movl	%ebx, %es:8(%di)
+	movl	%ecx, %es:12(%di)
+	/* Restore registers */
+	popal
+
+	/* Derive continuation value for next call */
+	addw	$16, %si
+	cmpw	$memory_windows_end, %si
+	jne	1f
+	/* End of memory windows: reset %si and allow %bx to continue */
+	xorw	%si, %si
+	jmp	2f
+1:	/* More memory windows to go: restore original %bx */
+	movw	%bp, %bx
+2:	/* Construct %ebx from %si:%bx */
+	pushw	%si
+	pushw	%bx
+	popl	%ebx
+
+98:	/* Clear CF */
+	clc
+99:	/* Restore registers and return */
+	popw	%bp
+	popl	%esi
+	ret
+	.size get_windowed_e820, . - get_windowed_e820
+
+/****************************************************************************
+ * Get windowed e820 region, with empty region stripping
+ *
+ * Parameters:
+ *   As for INT 15,e820
+ * Returns:
+ *   As for INT 15,e820
+ *
+ * Wraps the underlying INT 15,e820 call so that each underlying
+ * region is returned up to N times, windowed to fit within N
+ * visible-memory windows.  Empty windows are never returned.
+ * Termination is always via CF=1.
+ *
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+get_nonempty_e820:
+
+	/* Record entry parameters */
+	pushl	%eax
+	pushl	%ecx
+	pushl	%edx
+
+	/* Get next windowed region */
+	call	get_windowed_e820
+	jc	99f /* abort on error */
+
+	/* If region is non-empty, finish here */
+	cmpl	$0, %es:8(%di)
+	jne	98f
+	cmpl	$0, %es:12(%di)
+	jne	98f
+
+	/* Region was empty: restore entry parameters and go to next region */
+	popl	%edx
+	popl	%ecx
+	popl	%eax
+	jmp	get_nonempty_e820
+
+98:	/* Clear CF */
+	clc
+99:	/* Return values from underlying call */
+	addr32 leal 12(%esp), %esp /* avoid changing flags */
+	ret
+	.size get_nonempty_e820, . - get_nonempty_e820
+
+/****************************************************************************
+ * Get mangled e820 region, with empty region stripping
+ *
+ * Parameters:
+ *   As for INT 15,e820
+ * Returns:
+ *   As for INT 15,e820
+ *
+ * Wraps the underlying INT 15,e820 call so that underlying regions
+ * are windowed to the allowed memory regions.  Empty regions are
+ * stripped from the map.  Termination is always via %ebx=0.
+ *
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+get_mangled_e820:
+
+	/* Get a nonempty region */
+	call	get_nonempty_e820
+	jc	99f /* Abort on error */
+
+	/* Peek ahead to see if there are any further nonempty regions */
+	pushal
+	pushw	%es
+	movw	%sp, %bp
+	subw	%cx, %sp
+	movl	$0xe820, %eax
+	movl	$SMAP, %edx
+	pushw	%ss
+	popw	%es
+	movw	%sp, %di
+	call	get_nonempty_e820
+	movw	%bp, %sp
+	popw	%es
+	popal
+	jnc	99f /* There are further nonempty regions */
+
+	/* No futher nonempty regions: zero %ebx and clear CF */
+	xorl	%ebx, %ebx
+	
+99:	/* Return */
+	ret
+	.size get_mangled_e820, . - get_mangled_e820
+
+/****************************************************************************
+ * Set/clear CF on the stack as appropriate, assumes stack is as it should
+ * be immediately before IRET
+ ****************************************************************************
+ */
+patch_cf:
+	pushw	%bp
+	movw	%sp, %bp
+	setc	8(%bp)	/* Set/reset CF; clears PF, AF, ZF, SF */
+	popw	%bp
+	ret
+
+/****************************************************************************
+ * INT 15,e820 handler
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+int15_e820:
+	pushw	%ds
+	pushw	%cs:rm_ds
+	popw	%ds
+	call	get_mangled_e820
+	popw	%ds
+	call	patch_cf
+	iret
+	.size int15_e820, . - int15_e820
+	
+/****************************************************************************
+ * INT 15,e801 handler
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+int15_e801:
+	/* Call previous handler */
+	pushfw
+	lcall	*%cs:int15_vector
+	call	patch_cf
+	/* Edit result */
+	pushw	%ds
+	pushw	%cs:rm_ds
+	popw	%ds
+	call	patch_1m_16m
+	xchgw	%ax, %cx
+	xchgw	%bx, %dx
+	call	patch_1m_16m
+	xchgw	%ax, %cx
+	xchgw	%bx, %dx
+	popw	%ds
+	iret
+	.size int15_e801, . - int15_e801
+	
+/****************************************************************************
+ * INT 15,88 handler
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+int15_88:
+	/* Call previous handler */
+	pushfw
+	lcall	*%cs:int15_vector
+	call	patch_cf
+	/* Edit result */
+	pushw	%ds
+	pushw	%cs:rm_ds
+	popw	%ds
+	call	patch_1m
+	popw	%ds
+	iret
+	.size int15_88, . - int15_88
+		
+/****************************************************************************
+ * INT 15 handler
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+	.globl int15
+int15:
+	/* See if we want to intercept this call */
+	pushfw
+	cmpw	$0xe820, %ax
+	jne	1f
+	cmpl	$SMAP, %edx
+	jne	1f
+	popfw
+	jmp	int15_e820
+1:	cmpw	$0xe801, %ax
+	jne	2f
+	popfw
+	jmp	int15_e801
+2:	cmpb	$0x88, %ah
+	jne	3f
+	popfw
+	jmp	int15_88
+3:	popfw
+	ljmp	*%cs:int15_vector
+	.size int15, . - int15
+	
+	.section ".text16.data", "aw", @progbits
+	.globl int15_vector
+int15_vector:
+	.long 0
+	.size int15_vector, . - int15_vector
diff --git a/gpxe/src/arch/i386/firmware/pcbios/fakee820.c b/gpxe/src/arch/i386/firmware/pcbios/fakee820.c
new file mode 100644
index 0000000..ea116fe
--- /dev/null
+++ b/gpxe/src/arch/i386/firmware/pcbios/fakee820.c
@@ -0,0 +1,93 @@
+/* Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <realmode.h>
+#include <biosint.h>
+
+/** Assembly routine in inline asm */
+extern void int15_fakee820();
+
+/** Original INT 15 handler */
+static struct segoff __text16 ( real_int15_vector );
+#define real_int15_vector __use_text16 ( real_int15_vector )
+
+/** An INT 15,e820 memory map entry */
+struct e820_entry {
+	/** Start of region */
+	uint64_t start;
+	/** Length of region */
+	uint64_t len;
+	/** Type of region */
+	uint32_t type;
+} __attribute__ (( packed ));
+
+#define E820_TYPE_RAM		1 /**< Normal memory */
+#define E820_TYPE_RSVD		2 /**< Reserved and unavailable */
+#define E820_TYPE_ACPI		3 /**< ACPI reclaim memory */
+#define E820_TYPE_NVS		4 /**< ACPI NVS memory */
+
+/** Fake e820 map */
+static struct e820_entry __text16_array ( e820map, [] ) __used = {
+	{ 0x00000000ULL, ( 0x000a0000ULL - 0x00000000ULL ), E820_TYPE_RAM },
+	{ 0x00100000ULL, ( 0xcfb50000ULL - 0x00100000ULL ), E820_TYPE_RAM },
+	{ 0xcfb50000ULL, ( 0xcfb64000ULL - 0xcfb50000ULL ), E820_TYPE_RSVD },
+	{ 0xcfb64000ULL, ( 0xcfb66000ULL - 0xcfb64000ULL ), E820_TYPE_RSVD },
+	{ 0xcfb66000ULL, ( 0xcfb85c00ULL - 0xcfb66000ULL ), E820_TYPE_ACPI },
+	{ 0xcfb85c00ULL, ( 0xd0000000ULL - 0xcfb85c00ULL ), E820_TYPE_RSVD },
+	{ 0xe0000000ULL, ( 0xf0000000ULL - 0xe0000000ULL ), E820_TYPE_RSVD },
+	{ 0xfe000000ULL, (0x100000000ULL - 0xfe000000ULL ), E820_TYPE_RSVD },
+	{0x100000000ULL, (0x230000000ULL -0x100000000ULL ), E820_TYPE_RAM },
+};
+#define e820map __use_text16 ( e820map )
+
+void fake_e820 ( void ) {
+	__asm__ __volatile__ (
+		TEXT16_CODE ( "\nint15_fakee820:\n\t"
+			      "pushfw\n\t"
+			      "cmpl $0xe820, %%eax\n\t"
+			      "jne 99f\n\t"
+			      "cmpl $0x534d4150, %%edx\n\t"
+			      "jne 99f\n\t"
+			      "pushaw\n\t"
+			      "movw %%sp, %%bp\n\t"
+			      "andb $~0x01, 22(%%bp)\n\t" /* Clear return CF */
+			      "leaw e820map(%%bx), %%si\n\t"
+			      "cs rep movsb\n\t"
+			      "popaw\n\t"
+			      "movl %%edx, %%eax\n\t"
+			      "addl $20, %%ebx\n\t"
+			      "cmpl %0, %%ebx\n\t"
+			      "jne 1f\n\t"
+			      "xorl %%ebx,%%ebx\n\t"
+			      "\n1:\n\t"
+			      "popfw\n\t"
+			      "iret\n\t"
+			      "\n99:\n\t"
+			      "popfw\n\t"
+			      "ljmp *%%cs:real_int15_vector\n\t" )
+		: : "i" ( sizeof ( e820map ) ) );
+
+	hook_bios_interrupt ( 0x15, ( unsigned int ) int15_fakee820,
+			      &real_int15_vector );
+}
+
+void unfake_e820 ( void ) {
+	unhook_bios_interrupt ( 0x15, ( unsigned int ) int15_fakee820,
+				&real_int15_vector );
+}
diff --git a/gpxe/src/arch/i386/firmware/pcbios/gateA20.c b/gpxe/src/arch/i386/firmware/pcbios/gateA20.c
new file mode 100644
index 0000000..1a71472
--- /dev/null
+++ b/gpxe/src/arch/i386/firmware/pcbios/gateA20.c
@@ -0,0 +1,176 @@
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdio.h>
+#include <realmode.h>
+#include <bios.h>
+#include <gpxe/io.h>
+#include <gpxe/timer.h>
+
+#define K_RDWR		0x60		/* keyboard data & cmds (read/write) */
+#define K_STATUS	0x64		/* keyboard status */
+#define K_CMD		0x64		/* keybd ctlr command (write-only) */
+
+#define K_OBUF_FUL	0x01		/* output buffer full */
+#define K_IBUF_FUL	0x02		/* input buffer full */
+
+#define KC_CMD_WIN	0xd0		/* read  output port */
+#define KC_CMD_WOUT	0xd1		/* write output port */
+#define KC_CMD_NULL	0xff		/* null command ("pulse nothing") */
+#define KB_SET_A20	0xdf		/* enable A20,
+					   enable output buffer full interrupt
+					   enable data line
+					   disable clock line */
+#define KB_UNSET_A20	0xdd		/* enable A20,
+					   enable output buffer full interrupt
+					   enable data line
+					   disable clock line */
+
+#define SCP_A		0x92		/* System Control Port A */
+
+enum { Disable_A20 = 0x2400, Enable_A20 = 0x2401, Query_A20_Status = 0x2402,
+	Query_A20_Support = 0x2403 };
+
+enum a20_methods {
+	A20_UNKNOWN = 0,
+	A20_INT15,
+	A20_KBC,
+	A20_SCPA,
+};
+
+#define A20_MAX_RETRIES		32
+#define A20_INT15_RETRIES	32
+#define A20_KBC_RETRIES		(2^21)
+#define A20_SCPA_RETRIES	(2^21)
+
+/**
+ * Drain keyboard controller
+ */
+static void empty_8042 ( void ) {
+	unsigned long time;
+
+	time = currticks() + TICKS_PER_SEC;	/* max wait of 1 second */
+	while ( ( inb ( K_CMD ) & ( K_IBUF_FUL | K_OBUF_FUL ) ) &&
+		currticks() < time ) {
+		iodelay();
+		( void ) inb_p ( K_RDWR );
+		iodelay();
+	}
+}
+
+/**
+ * Fast test to see if gate A20 is already set
+ *
+ * @v retries		Number of times to retry before giving up
+ * @ret set		Gate A20 is set
+ */
+static int gateA20_is_set ( int retries ) {
+	static uint32_t test_pattern = 0xdeadbeef;
+	physaddr_t test_pattern_phys = virt_to_phys ( &test_pattern );
+	physaddr_t verify_pattern_phys = ( test_pattern_phys ^ 0x100000 );
+	userptr_t verify_pattern_user = phys_to_user ( verify_pattern_phys );
+	uint32_t verify_pattern;
+
+	do {
+		/* Check for difference */
+		copy_from_user ( &verify_pattern, verify_pattern_user, 0,
+				 sizeof ( verify_pattern ) );
+		if ( verify_pattern != test_pattern )
+			return 1;
+
+		/* Avoid false negatives */
+		test_pattern++;
+
+		iodelay();
+
+		/* Always retry at least once, to avoid false negatives */
+	} while ( retries-- >= 0 );
+
+	/* Pattern matched every time; gate A20 is not set */
+	return 0;
+}
+
+/*
+ * Gate A20 for high memory
+ *
+ * Note that this function gets called as part of the return path from
+ * librm's real_call, which is used to make the int15 call if librm is
+ * being used.  To avoid an infinite recursion, we make gateA20_set
+ * return immediately if it is already part of the call stack.
+ */
+void gateA20_set ( void ) {
+	static char reentry_guard = 0;
+	static int a20_method = A20_UNKNOWN;
+	unsigned int discard_a;
+	unsigned int scp_a;
+	int retries = 0;
+
+	/* Avoid potential infinite recursion */
+	if ( reentry_guard )
+		return;
+	reentry_guard = 1;
+
+	/* Fast check to see if gate A20 is already enabled */
+	if ( gateA20_is_set ( 0 ) )
+		goto out;
+
+	for ( ; retries < A20_MAX_RETRIES ; retries++ ) {
+		switch ( a20_method ) {
+		case A20_UNKNOWN:
+		case A20_INT15:
+			/* Try INT 15 method */
+			__asm__ __volatile__ ( REAL_CODE ( "int $0x15" )
+					       : "=a" ( discard_a )
+					       : "a" ( Enable_A20 ) );
+			if ( gateA20_is_set ( A20_INT15_RETRIES ) ) {
+				DBG ( "Enabled gate A20 using BIOS\n" );
+				a20_method = A20_INT15;
+				goto out;
+			}
+			/* fall through */
+		case A20_KBC:
+			/* Try keyboard controller method */
+			empty_8042();
+			outb ( KC_CMD_WOUT, K_CMD );
+			empty_8042();
+			outb ( KB_SET_A20, K_RDWR );
+			empty_8042();
+			outb ( KC_CMD_NULL, K_CMD );
+			empty_8042();
+			if ( gateA20_is_set ( A20_KBC_RETRIES ) ) {
+				DBG ( "Enabled gate A20 using "
+				      "keyboard controller\n" );
+				a20_method = A20_KBC;
+				goto out;
+			}
+			/* fall through */
+		case A20_SCPA:
+			/* Try "Fast gate A20" method */
+			scp_a = inb ( SCP_A );
+			scp_a &= ~0x01; /* Avoid triggering a reset */
+			scp_a |= 0x02; /* Enable A20 */
+			iodelay();
+			outb ( scp_a, SCP_A );
+			iodelay();
+			if ( gateA20_is_set ( A20_SCPA_RETRIES ) ) {
+				DBG ( "Enabled gate A20 using "
+				      "Fast Gate A20\n" );
+				a20_method = A20_SCPA;
+				goto out;
+			}
+		}
+	}
+
+	/* Better to die now than corrupt memory later */
+	printf ( "FATAL: Gate A20 stuck\n" );
+	while ( 1 ) {}
+
+ out:
+	if ( retries )
+		DBG ( "%d attempts were required to enable A20\n",
+		      ( retries + 1 ) );
+	reentry_guard = 0;
+}
+
+void gateA20_unset ( void ) {
+	/* Not currently implemented */
+}
diff --git a/gpxe/src/arch/i386/firmware/pcbios/hidemem.c b/gpxe/src/arch/i386/firmware/pcbios/hidemem.c
new file mode 100644
index 0000000..17082c3
--- /dev/null
+++ b/gpxe/src/arch/i386/firmware/pcbios/hidemem.c
@@ -0,0 +1,220 @@
+/* Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <assert.h>
+#include <realmode.h>
+#include <biosint.h>
+#include <basemem.h>
+#include <fakee820.h>
+#include <gpxe/init.h>
+#include <gpxe/memmap.h>
+#include <gpxe/hidemem.h>
+
+/** Set to true if you want to test a fake E820 map */
+#define FAKE_E820 0
+
+/** Alignment for hidden memory regions */
+#define ALIGN_HIDDEN 4096   /* 4kB page alignment should be enough */
+
+/**
+ * A hidden region of gPXE
+ *
+ * This represents a region that will be edited out of the system's
+ * memory map.
+ *
+ * This structure is accessed by assembly code, so must not be
+ * changed.
+ */
+struct hidden_region {
+	/** Physical start address */
+	uint64_t start;
+	/** Physical end address */
+	uint64_t end;
+};
+
+/** Hidden base memory */
+extern struct hidden_region __data16 ( hidemem_base );
+#define hidemem_base __use_data16 ( hidemem_base )
+
+/** Hidden umalloc memory */
+extern struct hidden_region __data16 ( hidemem_umalloc );
+#define hidemem_umalloc __use_data16 ( hidemem_umalloc )
+
+/** Hidden text memory */
+extern struct hidden_region __data16 ( hidemem_textdata );
+#define hidemem_textdata __use_data16 ( hidemem_textdata )
+
+/** Assembly routine in e820mangler.S */
+extern void int15();
+
+/** Vector for storing original INT 15 handler */
+extern struct segoff __text16 ( int15_vector );
+#define int15_vector __use_text16 ( int15_vector )
+
+/* The linker defines these symbols for us */
+extern char _textdata[];
+extern char _etextdata[];
+extern char _text16_memsz[];
+#define _text16_memsz ( ( unsigned int ) _text16_memsz )
+extern char _data16_memsz[];
+#define _data16_memsz ( ( unsigned int ) _data16_memsz )
+
+/**
+ * Hide region of memory from system memory map
+ *
+ * @v region		Hidden memory region
+ * @v start		Start of region
+ * @v end		End of region
+ */
+static void hide_region ( struct hidden_region *region,
+			  physaddr_t start, physaddr_t end ) {
+
+	/* Some operating systems get a nasty shock if a region of the
+	 * E820 map seems to start on a non-page boundary.  Make life
+	 * safer by rounding out our edited region.
+	 */
+	region->start = ( start & ~( ALIGN_HIDDEN - 1 ) );
+	region->end = ( ( end + ALIGN_HIDDEN - 1 ) & ~( ALIGN_HIDDEN - 1 ) );
+
+	DBG ( "Hiding region [%llx,%llx)\n", region->start, region->end );
+}
+
+/**
+ * Hide used base memory
+ *
+ */
+void hide_basemem ( void ) {
+	/* Hide from the top of free base memory to 640kB.  Don't use
+	 * hide_region(), because we don't want this rounded to the
+	 * nearest page boundary.
+	 */
+	hidemem_base.start = ( get_fbms() * 1024 );
+}
+
+/**
+ * Hide umalloc() region
+ *
+ */
+void hide_umalloc ( physaddr_t start, physaddr_t end ) {
+	assert ( end <= virt_to_phys ( _textdata ) );
+	hide_region ( &hidemem_umalloc, start, end );
+}
+
+/**
+ * Hide .text and .data
+ *
+ */
+void hide_textdata ( void ) {
+	hide_region ( &hidemem_textdata, virt_to_phys ( _textdata ),
+		      virt_to_phys ( _etextdata ) );
+}
+
+/**
+ * Hide Etherboot
+ *
+ * Installs an INT 15 handler to edit Etherboot out of the memory map
+ * returned by the BIOS.
+ */
+static void hide_etherboot ( void ) {
+	struct memory_map memmap;
+	unsigned int rm_ds_top;
+	unsigned int rm_cs_top;
+	unsigned int fbms;
+
+	/* Dump memory map before mangling */
+	DBG ( "Hiding gPXE from system memory map\n" );
+	get_memmap ( &memmap );
+
+	/* Hook in fake E820 map, if we're testing one */
+	if ( FAKE_E820 ) {
+		DBG ( "Hooking in fake E820 map\n" );
+		fake_e820();
+		get_memmap ( &memmap );
+	}
+
+	/* Initialise the hidden regions */
+	hide_basemem();
+	hide_umalloc ( virt_to_phys ( _textdata ), virt_to_phys ( _textdata ) );
+	hide_textdata();
+
+	/* Some really moronic BIOSes bring up the PXE stack via the
+	 * UNDI loader entry point and then don't bother to unload it
+	 * before overwriting the code and data segments.  If this
+	 * happens, we really don't want to leave INT 15 hooked,
+	 * because that will cause any loaded OS to die horribly as
+	 * soon as it attempts to fetch the system memory map.
+	 *
+	 * We use a heuristic to guess whether or not we are being
+	 * loaded sensibly.
+	 */
+	rm_cs_top = ( ( ( rm_cs << 4 ) + _text16_memsz + 1024 - 1 ) >> 10 );
+	rm_ds_top = ( ( ( rm_ds << 4 ) + _data16_memsz + 1024 - 1 ) >> 10 );
+	fbms = get_fbms();
+	if ( ( rm_cs_top < fbms ) && ( rm_ds_top < fbms ) ) {
+		DBG ( "Detected potentially unsafe UNDI load at CS=%04x "
+		      "DS=%04x FBMS=%dkB\n", rm_cs, rm_ds, fbms );
+		DBG ( "Disabling INT 15 memory hiding\n" );
+		return;
+	}
+
+	/* Hook INT 15 */
+	hook_bios_interrupt ( 0x15, ( unsigned int ) int15,
+			      &int15_vector );
+
+	/* Dump memory map after mangling */
+	DBG ( "Hidden gPXE from system memory map\n" );
+	get_memmap ( &memmap );
+}
+
+/**
+ * Unhide Etherboot
+ *
+ * Uninstalls the INT 15 handler installed by hide_etherboot(), if
+ * possible.
+ */
+static void unhide_etherboot ( int flags __unused ) {
+
+	/* If we have more than one hooked interrupt at this point, it
+	 * means that some other vector is still hooked, in which case
+	 * we can't safely unhook INT 15 because we need to keep our
+	 * memory protected.  (We expect there to be at least one
+	 * hooked interrupt, because INT 15 itself is still hooked).
+	 */
+	if ( hooked_bios_interrupts > 1 ) {
+		DBG ( "Cannot unhide: %d interrupt vectors still hooked\n",
+		      hooked_bios_interrupts );
+		return;
+	}
+
+	/* Try to unhook INT 15.  If it fails, then just leave it
+	 * hooked; it takes care of protecting itself.  :)
+	 */
+	unhook_bios_interrupt ( 0x15, ( unsigned int ) int15,
+				&int15_vector );
+
+	/* Unhook fake E820 map, if used */
+	if ( FAKE_E820 )
+		unfake_e820();
+}
+
+/** Hide Etherboot startup function */
+struct startup_fn hide_etherboot_startup_fn __startup_fn ( STARTUP_EARLY ) = {
+	.startup = hide_etherboot,
+	.shutdown = unhide_etherboot,
+};
diff --git a/gpxe/src/arch/i386/firmware/pcbios/memmap.c b/gpxe/src/arch/i386/firmware/pcbios/memmap.c
new file mode 100644
index 0000000..8a30dba
--- /dev/null
+++ b/gpxe/src/arch/i386/firmware/pcbios/memmap.c
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <errno.h>
+#include <realmode.h>
+#include <bios.h>
+#include <memsizes.h>
+#include <gpxe/memmap.h>
+
+/**
+ * @file
+ *
+ * Memory mapping
+ *
+ */
+
+/** Magic value for INT 15,e820 calls */
+#define SMAP ( 0x534d4150 )
+
+/** An INT 15,e820 memory map entry */
+struct e820_entry {
+	/** Start of region */
+	uint64_t start;
+	/** Length of region */
+	uint64_t len;
+	/** Type of region */
+	uint32_t type;
+	/** Extended attributes (optional) */
+	uint32_t attrs;
+} __attribute__ (( packed ));
+
+#define E820_TYPE_RAM		1 /**< Normal memory */
+#define E820_TYPE_RESERVED	2 /**< Reserved and unavailable */
+#define E820_TYPE_ACPI		3 /**< ACPI reclaim memory */
+#define E820_TYPE_NVS		4 /**< ACPI NVS memory */
+
+#define E820_ATTR_ENABLED	0x00000001UL
+#define E820_ATTR_NONVOLATILE	0x00000002UL
+#define E820_ATTR_UNKNOWN	0xfffffffcUL
+
+#define E820_MIN_SIZE		20
+
+/** Buffer for INT 15,e820 calls */
+static struct e820_entry __bss16 ( e820buf );
+#define e820buf __use_data16 ( e820buf )
+
+/**
+ * Get size of extended memory via INT 15,e801
+ *
+ * @ret extmem		Extended memory size, in kB, or 0
+ */
+static unsigned int extmemsize_e801 ( void ) {
+	uint16_t extmem_1m_to_16m_k, extmem_16m_plus_64k;
+	uint16_t confmem_1m_to_16m_k, confmem_16m_plus_64k;
+	unsigned int flags;
+	unsigned int extmem;
+
+	__asm__ __volatile__ ( REAL_CODE ( "stc\n\t"
+					   "int $0x15\n\t"
+					   "pushfw\n\t"
+					   "popw %w0\n\t" )
+			       : "=r" ( flags ),
+				 "=a" ( extmem_1m_to_16m_k ),
+				 "=b" ( extmem_16m_plus_64k ),
+				 "=c" ( confmem_1m_to_16m_k ),
+				 "=d" ( confmem_16m_plus_64k )
+			       : "a" ( 0xe801 ) );
+
+	if ( flags & CF ) {
+		DBG ( "INT 15,e801 failed with CF set\n" );
+		return 0;
+	}
+
+	if ( ! ( extmem_1m_to_16m_k | extmem_16m_plus_64k ) ) {
+		DBG ( "INT 15,e801 extmem=0, using confmem\n" );
+		extmem_1m_to_16m_k = confmem_1m_to_16m_k;
+		extmem_16m_plus_64k = confmem_16m_plus_64k;
+	}
+
+	extmem = ( extmem_1m_to_16m_k + ( extmem_16m_plus_64k * 64 ) );
+	DBG ( "INT 15,e801 extended memory size %d+64*%d=%d kB "
+	      "[100000,%llx)\n", extmem_1m_to_16m_k, extmem_16m_plus_64k,
+	      extmem, ( 0x100000 + ( ( ( uint64_t ) extmem ) * 1024 ) ) );
+
+	/* Sanity check.  Some BIOSes report the entire 4GB address
+	 * space as available, which cannot be correct (since that
+	 * would leave no address space available for 32-bit PCI
+	 * BARs).
+	 */
+	if ( extmem == ( 0x400000 - 0x400 ) ) {
+		DBG ( "INT 15,e801 reported whole 4GB; assuming insane\n" );
+		return 0;
+	}
+
+	return extmem;
+}
+
+/**
+ * Get size of extended memory via INT 15,88
+ *
+ * @ret extmem		Extended memory size, in kB
+ */
+static unsigned int extmemsize_88 ( void ) {
+	uint16_t extmem;
+
+	/* Ignore CF; it is not reliable for this call */
+	__asm__ __volatile__ ( REAL_CODE ( "int $0x15" )
+			       : "=a" ( extmem ) : "a" ( 0x8800 ) );
+
+	DBG ( "INT 15,88 extended memory size %d kB [100000, %x)\n",
+	      extmem, ( 0x100000 + ( extmem * 1024 ) ) );
+	return extmem;
+}
+
+/**
+ * Get size of extended memory
+ *
+ * @ret extmem		Extended memory size, in kB
+ *
+ * Note that this is only an approximation; for an accurate picture,
+ * use the E820 memory map obtained via get_memmap();
+ */
+unsigned int extmemsize ( void ) {
+	unsigned int extmem;
+
+	/* Try INT 15,e801 first, then fall back to INT 15,88 */
+	extmem = extmemsize_e801();
+	if ( ! extmem )
+		extmem = extmemsize_88();
+	return extmem;
+}
+
+/**
+ * Get e820 memory map
+ *
+ * @v memmap		Memory map to fill in
+ * @ret rc		Return status code
+ */
+static int meme820 ( struct memory_map *memmap ) {
+	struct memory_region *region = memmap->regions;
+	uint32_t next = 0;
+	uint32_t smap;
+	size_t size;
+	unsigned int flags;
+	unsigned int discard_D;
+
+	/* Clear the E820 buffer.  Do this once before starting,
+	 * rather than on each call; some BIOSes rely on the contents
+	 * being preserved between calls.
+	 */
+	memset ( &e820buf, 0, sizeof ( e820buf ) );
+
+	do {
+		/* Some BIOSes corrupt %esi for fun. Guard against
+		 * this by telling gcc that all non-output registers
+		 * may be corrupted.
+		 */
+		__asm__ __volatile__ ( REAL_CODE ( "pushl %%ebp\n\t"
+						   "stc\n\t"
+						   "int $0x15\n\t"
+						   "pushfw\n\t"
+						   "popw %%dx\n\t"
+						   "popl %%ebp\n\t" )
+				       : "=a" ( smap ), "=b" ( next ),
+					 "=c" ( size ), "=d" ( flags ),
+					 "=D" ( discard_D )
+				       : "a" ( 0xe820 ), "b" ( next ),
+					 "D" ( __from_data16 ( &e820buf ) ),
+					 "c" ( sizeof ( e820buf ) ),
+					 "d" ( SMAP )
+				       : "esi", "memory" );
+
+		if ( smap != SMAP ) {
+			DBG ( "INT 15,e820 failed SMAP signature check\n" );
+			return -ENOTSUP;
+		}
+
+		if ( size < E820_MIN_SIZE ) {
+			DBG ( "INT 15,e820 returned only %zd bytes\n", size );
+			return -EINVAL;
+		}
+
+		if ( flags & CF ) {
+			DBG ( "INT 15,e820 terminated on CF set\n" );
+			break;
+		}
+
+		/* If first region is not RAM, assume map is invalid */
+		if ( ( memmap->count == 0 ) &&
+		     ( e820buf.type != E820_TYPE_RAM ) ) {
+		       DBG ( "INT 15,e820 failed, first entry not RAM\n" );
+		       return -EINVAL;
+		}
+
+		DBG ( "INT 15,e820 region [%llx,%llx) type %d",
+		      e820buf.start, ( e820buf.start + e820buf.len ),
+		      ( int ) e820buf.type );
+		if ( size > offsetof ( typeof ( e820buf ), attrs ) ) {
+			DBG ( " (%s", ( ( e820buf.attrs & E820_ATTR_ENABLED )
+					? "enabled" : "disabled" ) );
+			if ( e820buf.attrs & E820_ATTR_NONVOLATILE )
+				DBG ( ", non-volatile" );
+			if ( e820buf.attrs & E820_ATTR_UNKNOWN )
+				DBG ( ", other [%08x]", e820buf.attrs );
+			DBG ( ")" );
+		}
+		DBG ( "\n" );
+
+		/* Discard non-RAM regions */
+		if ( e820buf.type != E820_TYPE_RAM )
+			continue;
+
+		/* Check extended attributes, if present */
+		if ( size > offsetof ( typeof ( e820buf ), attrs ) ) {
+			if ( ! ( e820buf.attrs & E820_ATTR_ENABLED ) )
+				continue;
+			if ( e820buf.attrs & E820_ATTR_NONVOLATILE )
+				continue;
+		}
+
+		region->start = e820buf.start;
+		region->end = e820buf.start + e820buf.len;
+		region++;
+		memmap->count++;
+
+		if ( memmap->count >= ( sizeof ( memmap->regions ) /
+					sizeof ( memmap->regions[0] ) ) ) {
+			DBG ( "INT 15,e820 too many regions returned\n" );
+			/* Not a fatal error; what we've got so far at
+			 * least represents valid regions of memory,
+			 * even if we couldn't get them all.
+			 */
+			break;
+		}
+	} while ( next != 0 );
+
+	/* Sanity checks.  Some BIOSes report complete garbage via INT
+	 * 15,e820 (especially at POST time), despite passing the
+	 * signature checks.  We currently check for a base memory
+	 * region (starting at 0) and at least one high memory region
+	 * (starting at 0x100000).
+	 */
+	if ( memmap->count < 2 ) {
+		DBG ( "INT 15,e820 returned only %d regions; assuming "
+		      "insane\n", memmap->count );
+		return -EINVAL;
+	}
+	if ( memmap->regions[0].start != 0 ) {
+		DBG ( "INT 15,e820 region 0 starts at %llx (expected 0); "
+		      "assuming insane\n", memmap->regions[0].start );
+		return -EINVAL;
+	}
+	if ( memmap->regions[1].start != 0x100000 ) {
+		DBG ( "INT 15,e820 region 1 starts at %llx (expected 100000); "
+		      "assuming insane\n", memmap->regions[0].start );
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * Get memory map
+ *
+ * @v memmap		Memory map to fill in
+ */
+void get_memmap ( struct memory_map *memmap ) {
+	unsigned int basemem, extmem;
+	int rc;
+
+	DBG ( "Fetching system memory map\n" );
+
+	/* Clear memory map */
+	memset ( memmap, 0, sizeof ( *memmap ) );
+
+	/* Get base and extended memory sizes */
+	basemem = basememsize();
+	DBG ( "FBMS base memory size %d kB [0,%x)\n",
+	      basemem, ( basemem * 1024 ) );
+	extmem = extmemsize();
+	
+	/* Try INT 15,e820 first */
+	if ( ( rc = meme820 ( memmap ) ) == 0 ) {
+		DBG ( "Obtained system memory map via INT 15,e820\n" );
+		return;
+	}
+
+	/* Fall back to constructing a map from basemem and extmem sizes */
+	DBG ( "INT 15,e820 failed; constructing map\n" );
+	memmap->regions[0].end = ( basemem * 1024 );
+	memmap->regions[1].start = 0x100000;
+	memmap->regions[1].end = 0x100000 + ( extmem * 1024 );
+	memmap->count = 2;
+}
diff --git a/gpxe/src/arch/i386/firmware/pcbios/pnpbios.c b/gpxe/src/arch/i386/firmware/pcbios/pnpbios.c
new file mode 100644
index 0000000..c572914
--- /dev/null
+++ b/gpxe/src/arch/i386/firmware/pcbios/pnpbios.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <realmode.h>
+#include <pnpbios.h>
+
+/** @file
+ *
+ * PnP BIOS
+ *
+ */
+
+/** PnP BIOS structure */
+struct pnp_bios {
+	/** Signature
+	 *
+	 * Must be equal to @c PNP_BIOS_SIGNATURE
+	 */
+	uint32_t signature;
+	/** Version as BCD (e.g. 1.0 is 0x10) */
+	uint8_t version;
+	/** Length of this structure */
+	uint8_t length;
+	/** System capabilities */
+	uint16_t control;
+	/** Checksum */
+	uint8_t checksum;
+} __attribute__ (( packed ));
+
+/** Signature for a PnP BIOS structure */
+#define PNP_BIOS_SIGNATURE \
+	( ( '$' << 0 ) + ( 'P' << 8 ) + ( 'n' << 16 ) + ( 'P' << 24 ) )
+
+/**
+ * Test address for PnP BIOS structure
+ *
+ * @v offset		Offset within BIOS segment to test
+ * @ret rc		Return status code
+ */
+static int is_pnp_bios ( unsigned int offset ) {
+	union {
+		struct pnp_bios pnp_bios;
+		uint8_t bytes[256]; /* 256 is maximum length possible */
+	} u;
+	size_t len;
+	unsigned int i;
+	uint8_t sum = 0;
+
+	/* Read start of header and verify signature */
+	copy_from_real ( &u.pnp_bios, BIOS_SEG, offset, sizeof ( u.pnp_bios ));
+	if ( u.pnp_bios.signature != PNP_BIOS_SIGNATURE )
+		return -EINVAL;
+
+	/* Read whole header and verify checksum */
+	len = u.pnp_bios.length;
+	copy_from_real ( &u.bytes, BIOS_SEG, offset, len );
+	for ( i = 0 ; i < len ; i++ ) {
+		sum += u.bytes[i];
+	}
+	if ( sum != 0 )
+		return -EINVAL;
+
+	DBG ( "Found PnP BIOS at %04x:%04x\n", BIOS_SEG, offset );
+
+	return 0;
+}
+
+/**
+ * Locate Plug-and-Play BIOS
+ *
+ * @ret pnp_offset	Offset of PnP BIOS structure within BIOS segment
+ *
+ * The PnP BIOS structure will be at BIOS_SEG:pnp_offset.  If no PnP
+ * BIOS is found, -1 is returned.
+ */
+int find_pnp_bios ( void ) {
+	static int pnp_offset = 0;
+
+	if ( pnp_offset )
+		return pnp_offset;
+
+	for ( pnp_offset = 0 ; pnp_offset < 0x10000 ; pnp_offset += 0x10 ) {
+		if ( is_pnp_bios ( pnp_offset ) == 0 )
+			return pnp_offset;
+	}
+
+	pnp_offset = -1;
+	return pnp_offset;
+}
diff --git a/gpxe/src/arch/i386/hci/commands/pxe_cmd.c b/gpxe/src/arch/i386/hci/commands/pxe_cmd.c
new file mode 100644
index 0000000..b5df2d1
--- /dev/null
+++ b/gpxe/src/arch/i386/hci/commands/pxe_cmd.c
@@ -0,0 +1,33 @@
+#include <gpxe/netdevice.h>
+#include <gpxe/command.h>
+#include <hci/ifmgmt_cmd.h>
+#include <pxe_call.h>
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+static int startpxe_payload ( struct net_device *netdev ) {
+	if ( netdev->state & NETDEV_OPEN )
+		pxe_activate ( netdev );
+	return 0;
+}
+
+static int startpxe_exec ( int argc, char **argv ) {
+	return ifcommon_exec ( argc, argv, startpxe_payload,
+			       "Activate PXE on" );
+}
+
+static int stoppxe_exec ( int argc __unused, char **argv __unused ) {
+	pxe_deactivate();
+	return 0;
+}
+
+struct command pxe_commands[] __command = {
+	{
+		.name = "startpxe",
+		.exec = startpxe_exec,
+	},
+	{
+		.name = "stoppxe",
+		.exec = stoppxe_exec,
+	},
+};
diff --git a/gpxe/src/arch/i386/image/bootsector.c b/gpxe/src/arch/i386/image/bootsector.c
new file mode 100644
index 0000000..f96cf20
--- /dev/null
+++ b/gpxe/src/arch/i386/image/bootsector.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/**
+ * @file
+ *
+ * x86 bootsector image format
+ *
+ */
+
+#include <errno.h>
+#include <realmode.h>
+#include <biosint.h>
+#include <bootsector.h>
+
+/** Vector for storing original INT 18 handler
+ *
+ * We do not chain to this vector, so there is no need to place it in
+ * .text16.
+ */
+static struct segoff int18_vector;
+
+/** Vector for storing original INT 19 handler
+ *
+ * We do not chain to this vector, so there is no need to place it in
+ * .text16.
+ */
+static struct segoff int19_vector;
+
+/** Restart point for INT 18 or 19 */
+extern void bootsector_exec_fail ( void );
+
+/**
+ * Jump to preloaded bootsector
+ *
+ * @v segment		Real-mode segment
+ * @v offset		Real-mode offset
+ * @v drive		Drive number to pass to boot sector
+ * @ret rc		Return status code
+ */
+int call_bootsector ( unsigned int segment, unsigned int offset,
+		      unsigned int drive ) {
+	int discard_b, discard_D, discard_d;
+
+	DBG ( "Booting from boot sector at %04x:%04x\n", segment, offset );
+
+	/* Hook INTs 18 and 19 to capture failure paths */
+	hook_bios_interrupt ( 0x18, ( unsigned int ) bootsector_exec_fail,
+			      &int18_vector );
+	hook_bios_interrupt ( 0x19, ( unsigned int ) bootsector_exec_fail,
+			      &int19_vector );
+
+	/* Boot the loaded sector
+	 *
+	 * We assume that the boot sector may completely destroy our
+	 * real-mode stack, so we preserve everything we need in
+	 * static storage.
+	 */
+	__asm__ __volatile__ ( REAL_CODE ( /* Save return address off-stack */
+					   "popw %%cs:saved_retaddr\n\t"
+					   /* Save stack pointer */
+					   "movw %%ss, %%ax\n\t"
+					   "movw %%ax, %%cs:saved_ss\n\t"
+					   "movw %%sp, %%cs:saved_sp\n\t"
+					   /* Jump to boot sector */
+					   "pushw %%bx\n\t"
+					   "pushw %%di\n\t"
+					   "sti\n\t"
+					   "lret\n\t"
+					   /* Preserved variables */
+					   "\nsaved_ss: .word 0\n\t"
+					   "\nsaved_sp: .word 0\n\t"
+					   "\nsaved_retaddr: .word 0\n\t"
+					   /* Boot failure return point */
+					   "\nbootsector_exec_fail:\n\t"
+					   /* Restore stack pointer */
+					   "movw %%cs:saved_ss, %%ax\n\t"
+					   "movw %%ax, %%ss\n\t"
+					   "movw %%cs:saved_sp, %%sp\n\t"
+					   /* Return via saved address */
+					   "jmp *%%cs:saved_retaddr\n\t" )
+			       : "=b" ( discard_b ), "=D" ( discard_D ),
+			         "=d" ( discard_d )
+			       : "b" ( segment ), "D" ( offset ),
+			         "d" ( drive )
+			       : "eax", "ecx", "esi", "ebp" );
+
+	DBG ( "Booted disk returned via INT 18 or 19\n" );
+
+	/* Unhook INTs 18 and 19 */
+	unhook_bios_interrupt ( 0x18, ( unsigned int ) bootsector_exec_fail,
+				&int18_vector );
+	unhook_bios_interrupt ( 0x19, ( unsigned int ) bootsector_exec_fail,
+				&int19_vector );
+	
+	return -ECANCELED;
+}
diff --git a/gpxe/src/arch/i386/image/bzimage.c b/gpxe/src/arch/i386/image/bzimage.c
new file mode 100644
index 0000000..1945099
--- /dev/null
+++ b/gpxe/src/arch/i386/image/bzimage.c
@@ -0,0 +1,561 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/**
+ * @file
+ *
+ * Linux bzImage image format
+ *
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <realmode.h>
+#include <bzimage.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/image.h>
+#include <gpxe/segment.h>
+#include <gpxe/init.h>
+#include <gpxe/cpio.h>
+#include <gpxe/features.h>
+
+FEATURE ( FEATURE_IMAGE, "bzImage", DHCP_EB_FEATURE_BZIMAGE, 1 );
+
+struct image_type bzimage_image_type __image_type ( PROBE_NORMAL );
+
+/**
+ * bzImage context
+ */
+struct bzimage_context {
+	/** Boot protocol version */
+	unsigned int version;
+	/** Real-mode kernel portion load segment address */
+	unsigned int rm_kernel_seg;
+	/** Real-mode kernel portion load address */
+	userptr_t rm_kernel;
+	/** Real-mode kernel portion file size */
+	size_t rm_filesz;
+	/** Real-mode heap top (offset from rm_kernel) */
+	size_t rm_heap;
+	/** Command line (offset from rm_kernel) */
+	size_t rm_cmdline;
+	/** Command line maximum length */
+	size_t cmdline_size;
+	/** Real-mode kernel portion total memory size */
+	size_t rm_memsz;
+	/** Non-real-mode kernel portion load address */
+	userptr_t pm_kernel;
+	/** Non-real-mode kernel portion file and memory size */
+	size_t pm_sz;
+	/** Video mode */
+	unsigned int vid_mode;
+	/** Memory limit */
+	uint64_t mem_limit;
+	/** Initrd address */
+	physaddr_t ramdisk_image;
+	/** Initrd size */
+	physaddr_t ramdisk_size;
+
+	/** Command line magic block */
+	struct bzimage_cmdline cmdline_magic;
+	/** bzImage header */
+	struct bzimage_header bzhdr;
+};
+
+/**
+ * Parse bzImage header
+ *
+ * @v image		bzImage file
+ * @v bzimg		bzImage context
+ * @v src		bzImage to parse
+ * @ret rc		Return status code
+ */
+static int bzimage_parse_header ( struct image *image,
+				  struct bzimage_context *bzimg,
+				  userptr_t src ) {
+	unsigned int syssize;
+	int is_bzimage;
+
+	/* Sanity check */
+	if ( image->len < ( BZI_HDR_OFFSET + sizeof ( bzimg->bzhdr ) ) ) {
+		DBGC ( image, "bzImage %p too short for kernel header\n",
+		       image );
+		return -ENOEXEC;
+	}
+
+	/* Read in header structures */
+	memset ( bzimg, 0, sizeof ( *bzimg ) );
+	copy_from_user ( &bzimg->cmdline_magic, src, BZI_CMDLINE_OFFSET,
+			 sizeof ( bzimg->cmdline_magic ) );
+	copy_from_user ( &bzimg->bzhdr, src, BZI_HDR_OFFSET,
+			 sizeof ( bzimg->bzhdr ) );
+
+	/* Calculate size of real-mode portion */
+	bzimg->rm_filesz =
+		( ( bzimg->bzhdr.setup_sects ? bzimg->bzhdr.setup_sects : 4 ) + 1 ) << 9;
+	if ( bzimg->rm_filesz > image->len ) {
+		DBGC ( image, "bzImage %p too short for %zd byte of setup\n",
+		       image, bzimg->rm_filesz );
+		return -ENOEXEC;
+	}
+	bzimg->rm_memsz = BZI_ASSUMED_RM_SIZE;
+
+	/* Calculate size of protected-mode portion */
+	bzimg->pm_sz = ( image->len - bzimg->rm_filesz );
+	syssize = ( ( bzimg->pm_sz + 15 ) / 16 );
+
+	/* Check for signatures and determine version */
+	if ( bzimg->bzhdr.boot_flag != BZI_BOOT_FLAG ) {
+		DBGC ( image, "bzImage %p missing 55AA signature\n", image );
+		return -ENOEXEC;
+	}
+	if ( bzimg->bzhdr.header == BZI_SIGNATURE ) {
+		/* 2.00+ */
+		bzimg->version = bzimg->bzhdr.version;
+	} else {
+		/* Pre-2.00.  Check that the syssize field is correct,
+		 * as a guard against accepting arbitrary binary data,
+		 * since the 55AA check is pretty lax.  Note that the
+		 * syssize field is unreliable for protocols between
+		 * 2.00 and 2.03 inclusive, so we should not always
+		 * check this field.
+		 */
+		bzimg->version = 0x0100;
+		if ( bzimg->bzhdr.syssize != syssize ) {
+			DBGC ( image, "bzImage %p bad syssize %x (expected "
+			       "%x)\n", image, bzimg->bzhdr.syssize, syssize );
+			return -ENOEXEC;
+		}
+	}
+
+	/* Determine image type */
+	is_bzimage = ( ( bzimg->version >= 0x0200 ) ?
+		       ( bzimg->bzhdr.loadflags & BZI_LOAD_HIGH ) : 0 );
+
+	/* Calculate load address of real-mode portion */
+	bzimg->rm_kernel_seg = ( is_bzimage ? 0x1000 : 0x9000 );
+	bzimg->rm_kernel = real_to_user ( bzimg->rm_kernel_seg, 0 );
+
+	/* Allow space for the stack and heap */
+	bzimg->rm_memsz += BZI_STACK_SIZE;
+	bzimg->rm_heap = bzimg->rm_memsz;
+
+	/* Allow space for the command line */
+	bzimg->rm_cmdline = bzimg->rm_memsz;
+	bzimg->rm_memsz += BZI_CMDLINE_SIZE;
+
+	/* Calculate load address of protected-mode portion */
+	bzimg->pm_kernel = phys_to_user ( is_bzimage ? BZI_LOAD_HIGH_ADDR
+					: BZI_LOAD_LOW_ADDR );
+
+	/* Extract video mode */
+	bzimg->vid_mode = bzimg->bzhdr.vid_mode;
+
+	/* Extract memory limit */
+	bzimg->mem_limit = ( ( bzimg->version >= 0x0203 ) ?
+			     bzimg->bzhdr.initrd_addr_max : BZI_INITRD_MAX );
+
+	/* Extract command line size */
+	bzimg->cmdline_size = ( ( bzimg->version >= 0x0206 ) ?
+				bzimg->bzhdr.cmdline_size : BZI_CMDLINE_SIZE );
+
+	DBGC ( image, "bzImage %p version %04x RM %#lx+%#zx PM %#lx+%#zx "
+	       "cmdlen %zd\n", image, bzimg->version,
+	       user_to_phys ( bzimg->rm_kernel, 0 ), bzimg->rm_filesz,
+	       user_to_phys ( bzimg->pm_kernel, 0 ), bzimg->pm_sz,
+	       bzimg->cmdline_size );
+
+	return 0;
+}
+
+/**
+ * Update bzImage header in loaded kernel
+ *
+ * @v image		bzImage file
+ * @v bzimg		bzImage context
+ * @v dst		bzImage to update
+ */
+static void bzimage_update_header ( struct image *image,
+				    struct bzimage_context *bzimg,
+				    userptr_t dst ) {
+
+	/* Set loader type */
+	if ( bzimg->version >= 0x0200 )
+		bzimg->bzhdr.type_of_loader = BZI_LOADER_TYPE_GPXE;
+
+	/* Set heap end pointer */
+	if ( bzimg->version >= 0x0201 ) {
+		bzimg->bzhdr.heap_end_ptr = ( bzimg->rm_heap - 0x200 );
+		bzimg->bzhdr.loadflags |= BZI_CAN_USE_HEAP;
+	}
+
+	/* Set command line */
+	if ( bzimg->version >= 0x0202 ) {
+		bzimg->bzhdr.cmd_line_ptr = user_to_phys ( bzimg->rm_kernel,
+							   bzimg->rm_cmdline );
+	} else {
+		bzimg->cmdline_magic.magic = BZI_CMDLINE_MAGIC;
+		bzimg->cmdline_magic.offset = bzimg->rm_cmdline;
+		bzimg->bzhdr.setup_move_size = bzimg->rm_memsz;
+	}
+
+	/* Set video mode */
+	bzimg->bzhdr.vid_mode = bzimg->vid_mode;
+
+	/* Set initrd address */
+	if ( bzimg->version >= 0x0200 ) {
+		bzimg->bzhdr.ramdisk_image = bzimg->ramdisk_image;
+		bzimg->bzhdr.ramdisk_size = bzimg->ramdisk_size;
+	}
+
+	/* Write out header structures */
+	copy_to_user ( dst, BZI_CMDLINE_OFFSET, &bzimg->cmdline_magic,
+		       sizeof ( bzimg->cmdline_magic ) );
+	copy_to_user ( dst, BZI_HDR_OFFSET, &bzimg->bzhdr,
+		       sizeof ( bzimg->bzhdr ) );
+
+	DBGC ( image, "bzImage %p vidmode %d\n", image, bzimg->vid_mode );
+}
+
+/**
+ * Parse kernel command line for bootloader parameters
+ *
+ * @v image		bzImage file
+ * @v bzimg		bzImage context
+ * @v cmdline		Kernel command line
+ * @ret rc		Return status code
+ */
+static int bzimage_parse_cmdline ( struct image *image,
+				   struct bzimage_context *bzimg,
+				   const char *cmdline ) {
+	char *vga;
+	char *mem;
+
+	/* Look for "vga=" */
+	if ( ( vga = strstr ( cmdline, "vga=" ) ) ) {
+		vga += 4;
+		if ( strcmp ( vga, "normal" ) == 0 ) {
+			bzimg->vid_mode = BZI_VID_MODE_NORMAL;
+		} else if ( strcmp ( vga, "ext" ) == 0 ) {
+			bzimg->vid_mode = BZI_VID_MODE_EXT;
+		} else if ( strcmp ( vga, "ask" ) == 0 ) {
+			bzimg->vid_mode = BZI_VID_MODE_ASK;
+		} else {
+			bzimg->vid_mode = strtoul ( vga, &vga, 0 );
+			if ( *vga && ( *vga != ' ' ) ) {
+				DBGC ( image, "bzImage %p strange \"vga=\""
+				       "terminator '%c'\n", image, *vga );
+			}
+		}
+	}
+
+	/* Look for "mem=" */
+	if ( ( mem = strstr ( cmdline, "mem=" ) ) ) {
+		mem += 4;
+		bzimg->mem_limit = strtoul ( mem, &mem, 0 );
+		switch ( *mem ) {
+		case 'G':
+		case 'g':
+			bzimg->mem_limit <<= 10;
+		case 'M':
+		case 'm':
+			bzimg->mem_limit <<= 10;
+		case 'K':
+		case 'k':
+			bzimg->mem_limit <<= 10;
+			break;
+		case '\0':
+		case ' ':
+			break;
+		default:
+			DBGC ( image, "bzImage %p strange \"mem=\" "
+			       "terminator '%c'\n", image, *mem );
+			break;
+		}
+		bzimg->mem_limit -= 1;
+	}
+
+	return 0;
+}
+
+/**
+ * Set command line
+ *
+ * @v image		bzImage image
+ * @v bzimg		bzImage context
+ * @v cmdline		Kernel command line
+ * @ret rc		Return status code
+ */
+static int bzimage_set_cmdline ( struct image *image,
+				 struct bzimage_context *bzimg,
+				 const char *cmdline ) {
+	size_t cmdline_len;
+
+	/* Copy command line down to real-mode portion */
+	cmdline_len = ( strlen ( cmdline ) + 1 );
+	if ( cmdline_len > bzimg->cmdline_size )
+		cmdline_len = bzimg->cmdline_size;
+	copy_to_user ( bzimg->rm_kernel, bzimg->rm_cmdline,
+		       cmdline, cmdline_len );
+	DBGC ( image, "bzImage %p command line \"%s\"\n", image, cmdline );
+
+	return 0;
+}
+
+/**
+ * Load initrd
+ *
+ * @v image		bzImage image
+ * @v initrd		initrd image
+ * @v address		Address at which to load, or UNULL
+ * @ret len		Length of loaded image, rounded up to 4 bytes
+ */
+static size_t bzimage_load_initrd ( struct image *image,
+				    struct image *initrd,
+				    userptr_t address ) {
+	char *filename = initrd->cmdline;
+	struct cpio_header cpio;
+        size_t offset = 0;
+
+	/* Do not include kernel image itself as an initrd */
+	if ( initrd == image )
+		return 0;
+
+	/* Create cpio header before non-prebuilt images */
+	if ( filename && filename[0] ) {
+		size_t name_len = ( strlen ( filename ) + 1 );
+
+		DBGC ( image, "bzImage %p inserting initrd %p as %s\n",
+		       image, initrd, filename );
+		memset ( &cpio, '0', sizeof ( cpio ) );
+		memcpy ( cpio.c_magic, CPIO_MAGIC, sizeof ( cpio.c_magic ) );
+		cpio_set_field ( cpio.c_mode, 0100644 );
+		cpio_set_field ( cpio.c_nlink, 1 );
+		cpio_set_field ( cpio.c_filesize, initrd->len );
+		cpio_set_field ( cpio.c_namesize, name_len );
+		if ( address ) {
+			copy_to_user ( address, offset, &cpio,
+				       sizeof ( cpio ) );
+		}
+		offset += sizeof ( cpio );
+		if ( address ) {
+			copy_to_user ( address, offset, filename,
+				       name_len );
+		}
+		offset += name_len;
+		offset = ( ( offset + 0x03 ) & ~0x03 );
+	}
+
+	/* Copy in initrd image body */
+	if ( address )
+		memcpy_user ( address, offset, initrd->data, 0, initrd->len );
+	offset += initrd->len;
+	if ( address ) {
+		DBGC ( image, "bzImage %p has initrd %p at [%lx,%lx)\n",
+		       image, initrd, user_to_phys ( address, 0 ),
+		       user_to_phys ( address, offset ) );
+	}
+
+	/* Round up to 4-byte boundary */
+	offset = ( ( offset + 0x03 ) & ~0x03 );
+	return offset;
+}
+
+/**
+ * Load initrds, if any
+ *
+ * @v image		bzImage image
+ * @v bzimg		bzImage context
+ * @ret rc		Return status code
+ */
+static int bzimage_load_initrds ( struct image *image,
+				  struct bzimage_context *bzimg ) {
+	struct image *initrd;
+	size_t total_len = 0;
+	physaddr_t address;
+	int rc;
+
+	/* Add up length of all initrd images */
+	for_each_image ( initrd )
+		total_len += bzimage_load_initrd ( image, initrd, UNULL );
+
+	/* Give up if no initrd images found */
+	if ( ! total_len )
+		return 0;
+
+	/* Find a suitable start address.  Try 1MB boundaries,
+	 * starting from the downloaded kernel image itself and
+	 * working downwards until we hit an available region.
+	 */
+	for ( address = ( user_to_phys ( image->data, 0 ) & ~0xfffff ) ; ;
+	      address -= 0x100000 ) {
+		/* Check that we're not going to overwrite the
+		 * kernel itself.  This check isn't totally
+		 * accurate, but errs on the side of caution.
+		 */
+		if ( address <= ( BZI_LOAD_HIGH_ADDR + image->len ) ) {
+			DBGC ( image, "bzImage %p could not find a location "
+			       "for initrd\n", image );
+			return -ENOBUFS;
+		}
+		/* Check that we are within the kernel's range */
+		if ( ( address + total_len - 1 ) > bzimg->mem_limit )
+			continue;
+		/* Prepare and verify segment */
+		if ( ( rc = prep_segment ( phys_to_user ( address ), 0,
+					   total_len ) ) != 0 )
+			continue;
+		/* Use this address */
+		break;
+	}
+
+	/* Record initrd location */
+	bzimg->ramdisk_image = address;
+	bzimg->ramdisk_size = total_len;
+
+	/* Construct initrd */
+	DBGC ( image, "bzImage %p constructing initrd at [%lx,%lx)\n",
+	       image, address, ( address + total_len ) );
+	for_each_image ( initrd ) {
+		address += bzimage_load_initrd ( image, initrd,
+						 phys_to_user ( address ) );
+	}
+
+	return 0;
+}
+
+/**
+ * Execute bzImage image
+ *
+ * @v image		bzImage image
+ * @ret rc		Return status code
+ */
+static int bzimage_exec ( struct image *image ) {
+	struct bzimage_context bzimg;
+	const char *cmdline = ( image->cmdline ? image->cmdline : "" );
+	int rc;
+
+	/* Read and parse header from loaded kernel */
+	if ( ( rc = bzimage_parse_header ( image, &bzimg,
+					   image->priv.user ) ) != 0 )
+		return rc;
+	assert ( bzimg.rm_kernel == image->priv.user );
+
+	/* Parse command line for bootloader parameters */
+	if ( ( rc = bzimage_parse_cmdline ( image, &bzimg, cmdline ) ) != 0)
+		return rc;
+
+	/* Store command line */
+	if ( ( rc = bzimage_set_cmdline ( image, &bzimg, cmdline ) ) != 0 )
+		return rc;
+
+	/* Load any initrds */
+	if ( ( rc = bzimage_load_initrds ( image, &bzimg ) ) != 0 )
+		return rc;
+
+	/* Update kernel header */
+	bzimage_update_header ( image, &bzimg, bzimg.rm_kernel );
+
+	/* Prepare for exiting */
+	shutdown ( SHUTDOWN_BOOT );
+
+	DBGC ( image, "bzImage %p jumping to RM kernel at %04x:0000 "
+	       "(stack %04x:%04zx)\n", image, ( bzimg.rm_kernel_seg + 0x20 ),
+	       bzimg.rm_kernel_seg, bzimg.rm_heap );
+
+	/* Jump to the kernel */
+	__asm__ __volatile__ ( REAL_CODE ( "movw %w0, %%ds\n\t"
+					   "movw %w0, %%es\n\t"
+					   "movw %w0, %%fs\n\t"
+					   "movw %w0, %%gs\n\t"
+					   "movw %w0, %%ss\n\t"
+					   "movw %w1, %%sp\n\t"
+					   "pushw %w2\n\t"
+					   "pushw $0\n\t"
+					   "lret\n\t" )
+			       : : "r" ( bzimg.rm_kernel_seg ),
+			           "r" ( bzimg.rm_heap ),
+			           "r" ( bzimg.rm_kernel_seg + 0x20 ) );
+
+	/* There is no way for the image to return, since we provide
+	 * no return address.
+	 */
+	assert ( 0 );
+
+	return -ECANCELED; /* -EIMPOSSIBLE */
+}
+
+/**
+ * Load bzImage image into memory
+ *
+ * @v image		bzImage file
+ * @ret rc		Return status code
+ */
+int bzimage_load ( struct image *image ) {
+	struct bzimage_context bzimg;
+	int rc;
+
+	/* Read and parse header from image */
+	if ( ( rc = bzimage_parse_header ( image, &bzimg,
+					   image->data ) ) != 0 )
+		return rc;
+
+	/* This is a bzImage image, valid or otherwise */
+	if ( ! image->type )
+		image->type = &bzimage_image_type;
+
+	/* Prepare segments */
+	if ( ( rc = prep_segment ( bzimg.rm_kernel, bzimg.rm_filesz,
+				   bzimg.rm_memsz ) ) != 0 ) {
+		DBGC ( image, "bzImage %p could not prepare RM segment: %s\n",
+		       image, strerror ( rc ) );
+		return rc;
+	}
+	if ( ( rc = prep_segment ( bzimg.pm_kernel, bzimg.pm_sz,
+				   bzimg.pm_sz ) ) != 0 ) {
+		DBGC ( image, "bzImage %p could not prepare PM segment: %s\n",
+		       image, strerror ( rc ) );
+		return rc;
+	}
+
+	/* Load segments */
+	memcpy_user ( bzimg.rm_kernel, 0, image->data,
+		      0, bzimg.rm_filesz );
+	memcpy_user ( bzimg.pm_kernel, 0, image->data,
+		      bzimg.rm_filesz, bzimg.pm_sz );
+
+	/* Update and write out header */
+	bzimage_update_header ( image, &bzimg, bzimg.rm_kernel );
+
+	/* Record real-mode segment in image private data field */
+	image->priv.user = bzimg.rm_kernel;
+
+	return 0;
+}
+
+/** Linux bzImage image type */
+struct image_type bzimage_image_type __image_type ( PROBE_NORMAL ) = {
+	.name = "bzImage",
+	.load = bzimage_load,
+	.exec = bzimage_exec,
+};
diff --git a/gpxe/src/arch/i386/image/com32.c b/gpxe/src/arch/i386/image/com32.c
new file mode 100644
index 0000000..6ab347c
--- /dev/null
+++ b/gpxe/src/arch/i386/image/com32.c
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2008 Daniel Verkamp <daniel@drv.nu>.
+ *
+ * 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.
+ */
+
+/**
+ * @file
+ *
+ * SYSLINUX COM32 image format
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <assert.h>
+#include <realmode.h>
+#include <basemem.h>
+#include <comboot.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/image.h>
+#include <gpxe/segment.h>
+#include <gpxe/init.h>
+#include <gpxe/memmap.h>
+
+struct image_type com32_image_type __image_type ( PROBE_NORMAL );
+
+/**
+ * Execute COMBOOT image
+ *
+ * @v image		COM32 image
+ * @ret rc		Return status code
+ */
+static int com32_exec ( struct image *image ) {
+	struct memory_map memmap;
+	unsigned int i;
+	int state;
+	uint32_t avail_mem_top;
+
+	state = rmsetjmp ( comboot_return );
+
+	switch ( state ) {
+	case 0: /* First time through; invoke COM32 program */
+
+		/* Get memory map */
+		get_memmap ( &memmap );
+
+		/* Find end of block covering COM32 image loading area */
+		for ( i = 0, avail_mem_top = 0 ; i < memmap.count ; i++ ) {
+			if ( (memmap.regions[i].start <= COM32_START_PHYS) &&
+			     (memmap.regions[i].end > COM32_START_PHYS + image->len) ) {
+				avail_mem_top = memmap.regions[i].end;
+				break;
+			}
+		}
+
+		DBGC ( image, "COM32 %p: available memory top = 0x%x\n",
+		       image, avail_mem_top );
+
+		assert ( avail_mem_top != 0 );
+
+		com32_external_esp = phys_to_virt ( avail_mem_top );
+
+		/* Hook COMBOOT API interrupts */
+		hook_comboot_interrupts();
+
+		/* Unregister image, so that a "boot" command doesn't
+		 * throw us into an execution loop.  We never
+		 * reregister ourselves; COMBOOT images expect to be
+		 * removed on exit.
+		 */
+		unregister_image ( image );
+
+		__asm__ __volatile__ (
+			"movl %%esp, (com32_internal_esp)\n\t" /* Save internal virtual address space ESP */
+			"movl (com32_external_esp), %%esp\n\t" /* Switch to COM32 ESP (top of available memory) */
+			"call _virt_to_phys\n\t"               /* Switch to flat physical address space */
+			"pushl %0\n\t"                         /* Pointer to CDECL helper function */
+			"pushl %1\n\t"                         /* Pointer to FAR call helper function */
+			"pushl %2\n\t"                         /* Size of low memory bounce buffer */
+			"pushl %3\n\t"                         /* Pointer to low memory bounce buffer */
+			"pushl %4\n\t"                         /* Pointer to INT call helper function */
+			"pushl %5\n\t"                         /* Pointer to the command line arguments */
+			"pushl $6\n\t"                         /* Number of additional arguments */
+			"call *%6\n\t"                         /* Execute image */
+			"call _phys_to_virt\n\t"               /* Switch back to internal virtual address space */
+			"movl (com32_internal_esp), %%esp\n\t" /* Switch back to internal stack */
+		:
+		:
+			/* %0 */ "r" ( virt_to_phys ( com32_cfarcall_wrapper ) ),
+			/* %1 */ "r" ( virt_to_phys ( com32_farcall_wrapper ) ),
+			/* %2 */ "r" ( get_fbms() * 1024 - (COM32_BOUNCE_SEG << 4) ),
+			/* %3 */ "i" ( COM32_BOUNCE_SEG << 4 ),
+			/* %4 */ "r" ( virt_to_phys ( com32_intcall_wrapper ) ),
+			/* %5 */ "r" ( virt_to_phys ( image->cmdline ) ),
+			/* %6 */ "r" ( COM32_START_PHYS )
+		:
+			"memory" );
+		DBGC ( image, "COM32 %p: returned\n", image );
+		break;
+
+	case COMBOOT_EXIT:
+		DBGC ( image, "COM32 %p: exited\n", image );
+		break;
+
+	case COMBOOT_EXIT_RUN_KERNEL:
+		DBGC ( image, "COM32 %p: exited to run kernel %p\n",
+		       image, comboot_replacement_image );
+		image->replacement = comboot_replacement_image;
+		comboot_replacement_image = NULL;
+		image_autoload ( image->replacement );
+		break;
+
+	case COMBOOT_EXIT_COMMAND:
+		DBGC ( image, "COM32 %p: exited after executing command\n",
+		       image );
+		break;
+
+	default:
+		assert ( 0 );
+		break;
+	}
+
+	unhook_comboot_interrupts();
+	comboot_force_text_mode();
+
+	return 0;
+}
+
+/**
+ * Check image name extension
+ * 
+ * @v image		COM32 image
+ * @ret rc		Return status code
+ */
+static int com32_identify ( struct image *image ) {
+	const char *ext;
+	static const uint8_t magic[] = { 0xB8, 0xFF, 0x4C, 0xCD, 0x21 };
+	uint8_t buf[5];
+	
+	if ( image->len >= 5 ) {
+		/* Check for magic number
+		 * mov eax,21cd4cffh
+		 * B8 FF 4C CD 21
+		 */
+		copy_from_user ( buf, image->data, 0, sizeof(buf) );
+		if ( ! memcmp ( buf, magic, sizeof(buf) ) ) {
+			DBGC ( image, "COM32 %p: found magic number\n",
+			       image );
+			return 0;
+		}
+	}
+
+	/* Magic number not found; check filename extension */
+
+	ext = strrchr( image->name, '.' );
+
+	if ( ! ext ) {
+		DBGC ( image, "COM32 %p: no extension\n",
+		       image );
+		return -ENOEXEC;
+	}
+
+	++ext;
+
+	if ( strcasecmp( ext, "c32" ) ) {
+		DBGC ( image, "COM32 %p: unrecognized extension %s\n",
+		       image, ext );
+		return -ENOEXEC;
+	}
+
+	return 0;
+}
+
+
+/**
+ * Load COM32 image into memory
+ * @v image		COM32 image
+ * @ret rc		Return status code
+ */
+static int comboot_load_image ( struct image *image ) {
+	size_t filesz, memsz;
+	userptr_t buffer;
+	int rc;
+
+	filesz = image->len;
+	memsz = filesz;
+	buffer = phys_to_user ( COM32_START_PHYS );
+	if ( ( rc = prep_segment ( buffer, filesz, memsz ) ) != 0 ) {
+		DBGC ( image, "COM32 %p: could not prepare segment: %s\n",
+		       image, strerror ( rc ) );
+		return rc;
+	}
+
+	/* Copy image to segment */
+	memcpy_user ( buffer, 0, image->data, 0, filesz );
+
+	return 0;
+}
+
+/**
+ * Prepare COM32 low memory bounce buffer
+ * @v image		COM32 image
+ * @ret rc		Return status code
+ */
+static int comboot_prepare_bounce_buffer ( struct image * image ) {
+	unsigned int seg;
+	userptr_t seg_userptr;
+	size_t filesz, memsz;
+	int rc;
+
+	seg = COM32_BOUNCE_SEG;
+	seg_userptr = real_to_user ( seg, 0 );
+
+	/* Ensure the entire 64k segment is free */
+	memsz = 0xFFFF;
+	filesz = 0;
+
+	/* Prepare, verify, and load the real-mode segment */
+	if ( ( rc = prep_segment ( seg_userptr, filesz, memsz ) ) != 0 ) {
+		DBGC ( image, "COM32 %p: could not prepare bounce buffer segment: %s\n",
+		       image, strerror ( rc ) );
+		return rc;
+	}
+
+	return 0;
+}
+
+/**
+ * Load COM32 image into memory
+ *
+ * @v image		COM32 image
+ * @ret rc		Return status code
+ */
+static int com32_load ( struct image *image ) {
+	int rc;
+
+	DBGC ( image, "COM32 %p: name '%s', cmdline '%s'\n",
+	       image, image->name, image->cmdline );
+
+	/* Check if this is a COMBOOT image */
+	if ( ( rc = com32_identify ( image ) ) != 0 ) {
+		return rc;
+	}
+
+	/* This is a COM32 image, valid or otherwise */
+	if ( ! image->type )
+		image->type = &com32_image_type;
+
+	/* Load image */
+	if ( ( rc = comboot_load_image ( image ) ) != 0 ) {
+		return rc;
+	}
+
+	/* Prepare bounce buffer segment */
+	if ( ( rc = comboot_prepare_bounce_buffer ( image ) ) != 0 ) {
+		return rc;
+	}
+
+	return 0;
+}
+
+/** SYSLINUX COM32 image type */
+struct image_type com32_image_type __image_type ( PROBE_NORMAL ) = {
+	.name = "COM32",
+	.load = com32_load,
+	.exec = com32_exec,
+};
diff --git a/gpxe/src/arch/i386/image/comboot.c b/gpxe/src/arch/i386/image/comboot.c
new file mode 100644
index 0000000..a00b2b9
--- /dev/null
+++ b/gpxe/src/arch/i386/image/comboot.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2008 Daniel Verkamp <daniel@drv.nu>.
+ *
+ * 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.
+ */
+
+/**
+ * @file
+ *
+ * SYSLINUX COMBOOT (16-bit) image format
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <assert.h>
+#include <realmode.h>
+#include <basemem.h>
+#include <comboot.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/image.h>
+#include <gpxe/segment.h>
+#include <gpxe/init.h>
+#include <gpxe/features.h>
+
+FEATURE ( FEATURE_IMAGE, "COMBOOT", DHCP_EB_FEATURE_COMBOOT, 1 );
+
+struct image_type comboot_image_type __image_type ( PROBE_NORMAL );
+
+/**
+ * COMBOOT PSP, copied to offset 0 of code segment
+ */
+struct comboot_psp {
+	/** INT 20 instruction, executed if COMBOOT image returns with RET */
+	uint16_t int20;
+	/** Segment of first non-free paragraph of memory */
+	uint16_t first_non_free_para;
+};
+
+/** Offset in PSP of command line */
+#define COMBOOT_PSP_CMDLINE_OFFSET 0x81
+
+/** Maximum length of command line in PSP
+ * (127 bytes minus space and CR) */
+#define COMBOOT_MAX_CMDLINE_LEN    125
+
+
+/**
+ * Copy command line to PSP
+ * 
+ * @v image		COMBOOT image
+ */
+static void comboot_copy_cmdline ( struct image * image, userptr_t seg_userptr ) {
+	const char *cmdline = ( image->cmdline ? image->cmdline : "" );
+	int cmdline_len = strlen ( cmdline );
+	if( cmdline_len > COMBOOT_MAX_CMDLINE_LEN )
+		cmdline_len = COMBOOT_MAX_CMDLINE_LEN;
+	uint8_t len_byte = cmdline_len;
+	char spc = ' ', cr = '\r';
+
+	/* Copy length to byte before command line */
+	copy_to_user ( seg_userptr, COMBOOT_PSP_CMDLINE_OFFSET - 1,
+	               &len_byte, 1 );
+
+	/* Command line starts with space */
+	copy_to_user ( seg_userptr,
+	               COMBOOT_PSP_CMDLINE_OFFSET,
+	               &spc, 1 );
+
+	/* Copy command line */
+	copy_to_user ( seg_userptr,
+	               COMBOOT_PSP_CMDLINE_OFFSET + 1,
+	               cmdline, cmdline_len );
+
+	/* Command line ends with CR */
+	copy_to_user ( seg_userptr,
+	               COMBOOT_PSP_CMDLINE_OFFSET + cmdline_len + 1,
+	               &cr, 1 );
+}
+
+/**
+ * Initialize PSP
+ * 
+ * @v image		COMBOOT image
+ * @v seg_userptr	segment to initialize
+ */
+static void comboot_init_psp ( struct image * image, userptr_t seg_userptr ) {
+	struct comboot_psp psp;
+
+	/* Fill PSP */
+
+	/* INT 20h instruction, byte order reversed */
+	psp.int20 = 0x20CD;
+
+	/* get_fbms() returns BIOS free base memory counter, which is in
+	 * kilobytes; x * 1024 / 16 == x * 64 == x << 6 */
+	psp.first_non_free_para = get_fbms() << 6;
+
+	DBGC ( image, "COMBOOT %p: first non-free paragraph = 0x%x\n",
+	       image, psp.first_non_free_para );
+
+	/* Copy the PSP to offset 0 of segment.
+	 * The rest of the PSP was already zeroed by
+	 * comboot_prepare_segment. */
+	copy_to_user ( seg_userptr, 0, &psp, sizeof( psp ) );
+
+	/* Copy the command line to the PSP */
+	comboot_copy_cmdline ( image, seg_userptr );
+}
+
+/**
+ * Execute COMBOOT image
+ *
+ * @v image		COMBOOT image
+ * @ret rc		Return status code
+ */
+static int comboot_exec ( struct image *image ) {
+	userptr_t seg_userptr = real_to_user ( COMBOOT_PSP_SEG, 0 );
+	int state;
+
+	state = rmsetjmp ( comboot_return );
+
+	switch ( state ) {
+	case 0: /* First time through; invoke COMBOOT program */
+
+		/* Initialize PSP */
+		comboot_init_psp ( image, seg_userptr );
+
+		/* Hook COMBOOT API interrupts */
+		hook_comboot_interrupts();
+
+		DBGC ( image, "executing 16-bit COMBOOT image at %4x:0100\n",
+		       COMBOOT_PSP_SEG );
+
+		/* Unregister image, so that a "boot" command doesn't
+		 * throw us into an execution loop.  We never
+		 * reregister ourselves; COMBOOT images expect to be
+		 * removed on exit.
+		 */
+		unregister_image ( image );
+
+		/* Store stack segment at 0x38 and stack pointer at 0x3A
+		 * in the PSP and jump to the image */
+		__asm__ __volatile__ (
+		    REAL_CODE ( /* Save return address with segment on old stack */
+				    "popw %%ax\n\t"
+				    "pushw %%cs\n\t"
+				    "pushw %%ax\n\t"
+				    /* Set DS=ES=segment with image */
+				    "movw %w0, %%ds\n\t"
+				    "movw %w0, %%es\n\t"
+				    /* Set SS:SP to new stack (end of image segment) */
+				    "movw %w0, %%ss\n\t"
+				    "xor %%sp, %%sp\n\t"
+				    "pushw $0\n\t"
+				    "pushw %w0\n\t"
+				    "pushw $0x100\n\t"
+				    /* Zero registers (some COM files assume GP regs are 0) */
+				    "xorw %%ax, %%ax\n\t"
+				    "xorw %%bx, %%bx\n\t"
+				    "xorw %%cx, %%cx\n\t"
+				    "xorw %%dx, %%dx\n\t"
+				    "xorw %%si, %%si\n\t"
+				    "xorw %%di, %%di\n\t"
+				    "xorw %%bp, %%bp\n\t"
+				    "lret\n\t" )
+					 : : "r" ( COMBOOT_PSP_SEG ) : "eax" );
+		DBGC ( image, "COMBOOT %p: returned\n", image );
+		break;
+
+	case COMBOOT_EXIT:
+		DBGC ( image, "COMBOOT %p: exited\n", image );
+		break;
+
+	case COMBOOT_EXIT_RUN_KERNEL:
+		DBGC ( image, "COMBOOT %p: exited to run kernel %p\n",
+		       image, comboot_replacement_image );
+		image->replacement = comboot_replacement_image;
+		comboot_replacement_image = NULL;
+		image_autoload ( image->replacement );
+		break;
+
+	case COMBOOT_EXIT_COMMAND:
+		DBGC ( image, "COMBOOT %p: exited after executing command\n",
+		       image );
+		break;
+
+	default:
+		assert ( 0 );
+		break;
+	}
+
+	unhook_comboot_interrupts();
+	comboot_force_text_mode();
+
+	return 0;
+}
+
+/**
+ * Check image name extension
+ * 
+ * @v image		COMBOOT image
+ * @ret rc		Return status code
+ */
+static int comboot_identify ( struct image *image ) {
+	const char *ext;
+
+	ext = strrchr( image->name, '.' );
+
+	if ( ! ext ) {
+		DBGC ( image, "COMBOOT %p: no extension\n",
+		       image );
+		return -ENOEXEC;
+	}
+
+	++ext;
+
+	if ( strcasecmp( ext, "com" ) && strcasecmp( ext, "cbt" ) ) {
+		DBGC ( image, "COMBOOT %p: unrecognized extension %s\n",
+		       image, ext );
+		return -ENOEXEC;
+	}
+
+	return 0;
+}
+
+/**
+ * Load COMBOOT image into memory, preparing a segment and returning it
+ * @v image		COMBOOT image
+ * @ret rc		Return status code
+ */
+static int comboot_prepare_segment ( struct image *image )
+{
+	userptr_t seg_userptr;
+	size_t filesz, memsz;
+	int rc;
+
+	/* Load image in segment */
+	seg_userptr = real_to_user ( COMBOOT_PSP_SEG, 0 );
+
+	/* Allow etra 0x100 bytes before image for PSP */
+	filesz = image->len + 0x100; 
+
+	/* Ensure the entire 64k segment is free */
+	memsz = 0xFFFF;
+
+	/* Prepare, verify, and load the real-mode segment */
+	if ( ( rc = prep_segment ( seg_userptr, filesz, memsz ) ) != 0 ) {
+		DBGC ( image, "COMBOOT %p: could not prepare segment: %s\n",
+		       image, strerror ( rc ) );
+		return rc;
+	}
+
+	/* Zero PSP */
+	memset_user ( seg_userptr, 0, 0, 0x100 );
+
+	/* Copy image to segment:0100 */
+	memcpy_user ( seg_userptr, 0x100, image->data, 0, image->len );
+
+	return 0;
+}
+
+/**
+ * Load COMBOOT image into memory
+ *
+ * @v image		COMBOOT image
+ * @ret rc		Return status code
+ */
+static int comboot_load ( struct image *image ) {
+	int rc;
+
+	DBGC ( image, "COMBOOT %p: name '%s'\n",
+	       image, image->name );
+
+	/* Check if this is a COMBOOT image */
+	if ( ( rc = comboot_identify ( image ) ) != 0 ) {
+		
+		return rc;
+	}
+
+	/* This is a 16-bit COMBOOT image, valid or otherwise */
+	if ( ! image->type )
+		image->type = &comboot_image_type;
+	
+	/* Sanity check for filesize */
+	if( image->len >= 0xFF00 ) {
+		DBGC( image, "COMBOOT %p: image too large\n",
+		      image );
+		return -ENOEXEC;
+	}
+
+	/* Prepare segment and load image */
+	if ( ( rc = comboot_prepare_segment ( image ) ) != 0 ) {
+		return rc;
+	}
+
+	return 0;
+}
+
+/** SYSLINUX COMBOOT (16-bit) image type */
+struct image_type comboot_image_type __image_type ( PROBE_NORMAL ) = {
+	.name = "COMBOOT",
+	.load = comboot_load,
+	.exec = comboot_exec,
+};
diff --git a/gpxe/src/arch/i386/image/elfboot.c b/gpxe/src/arch/i386/image/elfboot.c
new file mode 100644
index 0000000..a41040e
--- /dev/null
+++ b/gpxe/src/arch/i386/image/elfboot.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <errno.h>
+#include <elf.h>
+#include <gpxe/image.h>
+#include <gpxe/elf.h>
+#include <gpxe/features.h>
+#include <gpxe/init.h>
+
+/**
+ * @file
+ *
+ * ELF bootable image
+ *
+ */
+
+FEATURE ( FEATURE_IMAGE, "ELF", DHCP_EB_FEATURE_ELF, 1 );
+
+struct image_type elfboot_image_type __image_type ( PROBE_NORMAL );
+
+/**
+ * Execute ELF image
+ *
+ * @v image		ELF image
+ * @ret rc		Return status code
+ */
+static int elfboot_exec ( struct image *image ) {
+	physaddr_t entry = image->priv.phys;
+
+	/* An ELF image has no callback interface, so we need to shut
+	 * down before invoking it.
+	 */
+	shutdown ( SHUTDOWN_BOOT );
+
+	/* Jump to OS with flat physical addressing */
+	DBGC ( image, "ELF %p starting execution at %lx\n", image, entry );
+	__asm__ __volatile__ ( PHYS_CODE ( "call *%%edi\n\t" )
+			       : : "D" ( entry )
+			       : "eax", "ebx", "ecx", "edx", "esi", "ebp",
+			         "memory" );
+
+	DBGC ( image, "ELF %p returned\n", image );
+
+	/* It isn't safe to continue after calling shutdown() */
+	while ( 1 ) {}
+
+	return -ECANCELED;  /* -EIMPOSSIBLE, anyone? */
+}
+
+/**
+ * Load ELF image into memory
+ *
+ * @v image		ELF file
+ * @ret rc		Return status code
+ */
+static int elfboot_load ( struct image *image ) {
+	Elf32_Ehdr ehdr;
+	static const uint8_t e_ident[] = {
+		[EI_MAG0]	= ELFMAG0,
+		[EI_MAG1]	= ELFMAG1,
+		[EI_MAG2]	= ELFMAG2,
+		[EI_MAG3]	= ELFMAG3,
+		[EI_CLASS]	= ELFCLASS32,
+		[EI_DATA]	= ELFDATA2LSB,
+		[EI_VERSION]	= EV_CURRENT,
+	};
+	int rc;
+
+	/* Read ELF header */
+	copy_from_user ( &ehdr, image->data, 0, sizeof ( ehdr ) );
+	if ( memcmp ( ehdr.e_ident, e_ident, sizeof ( e_ident ) ) != 0 ) {
+		DBG ( "Invalid ELF identifier\n" );
+		return -ENOEXEC;
+	}
+
+	/* This is an ELF image, valid or otherwise */
+	if ( ! image->type )
+		image->type = &elfboot_image_type;
+
+	/* Load the image using core ELF support */
+	if ( ( rc = elf_load ( image ) ) != 0 ) {
+		DBGC ( image, "ELF %p could not load: %s\n",
+		       image, strerror ( rc ) );
+		return rc;
+	}
+
+	return 0;
+}
+
+/** ELF image type */
+struct image_type elfboot_image_type __image_type ( PROBE_NORMAL ) = {
+	.name = "ELF",
+	.load = elfboot_load,
+	.exec = elfboot_exec,
+};
diff --git a/gpxe/src/arch/i386/image/eltorito.c b/gpxe/src/arch/i386/image/eltorito.c
new file mode 100644
index 0000000..53eb2c0
--- /dev/null
+++ b/gpxe/src/arch/i386/image/eltorito.c
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/**
+ * @file
+ *
+ * El Torito bootable ISO image format
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <realmode.h>
+#include <bootsector.h>
+#include <int13.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/image.h>
+#include <gpxe/segment.h>
+#include <gpxe/ramdisk.h>
+#include <gpxe/init.h>
+
+#define ISO9660_BLKSIZE 2048
+#define ELTORITO_VOL_DESC_OFFSET ( 17 * ISO9660_BLKSIZE )
+
+/** An El Torito Boot Record Volume Descriptor */
+struct eltorito_vol_desc {
+	/** Boot record indicator; must be 0 */
+	uint8_t record_indicator;
+	/** ISO-9660 identifier; must be "CD001" */
+	uint8_t iso9660_id[5];
+	/** Version, must be 1 */
+	uint8_t version;
+	/** Boot system indicator; must be "EL TORITO SPECIFICATION" */
+	uint8_t system_indicator[32];
+	/** Unused */
+	uint8_t unused[32];
+	/** Boot catalog sector */
+	uint32_t sector;
+} __attribute__ (( packed ));
+
+/** An El Torito Boot Catalog Validation Entry */
+struct eltorito_validation_entry {
+	/** Header ID; must be 1 */
+	uint8_t header_id;
+	/** Platform ID
+	 *
+	 * 0 = 80x86
+	 * 1 = PowerPC
+	 * 2 = Mac
+	 */
+	uint8_t platform_id;
+	/** Reserved */
+	uint16_t reserved;
+	/** ID string */
+	uint8_t id_string[24];
+	/** Checksum word */
+	uint16_t checksum;
+	/** Signature; must be 0xaa55 */
+	uint16_t signature;
+} __attribute__ (( packed ));
+
+/** A bootable entry in the El Torito Boot Catalog */
+struct eltorito_boot_entry {
+	/** Boot indicator
+	 *
+	 * Must be @c ELTORITO_BOOTABLE for a bootable ISO image
+	 */
+	uint8_t indicator;
+	/** Media type
+	 *
+	 */
+	uint8_t media_type;
+	/** Load segment */
+	uint16_t load_segment;
+	/** System type */
+	uint8_t filesystem;
+	/** Unused */
+	uint8_t reserved_a;
+	/** Sector count */
+	uint16_t length;
+	/** Starting sector */
+	uint32_t start;
+	/** Unused */
+	uint8_t reserved_b[20];
+} __attribute__ (( packed ));
+
+/** Boot indicator for a bootable ISO image */
+#define ELTORITO_BOOTABLE 0x88
+
+/** El Torito media types */
+enum eltorito_media_type {
+	/** No emulation */
+	ELTORITO_NO_EMULATION = 0,
+};
+
+struct image_type eltorito_image_type __image_type ( PROBE_NORMAL );
+
+/**
+ * Calculate 16-bit word checksum
+ *
+ * @v data		Data to checksum
+ * @v len		Length (in bytes, must be even)
+ * @ret sum		Checksum
+ */
+static unsigned int word_checksum ( void *data, size_t len ) {
+	uint16_t *words;
+	uint16_t sum = 0;
+
+	for ( words = data ; len ; words++, len -= 2 ) {
+		sum += *words;
+	}
+	return sum;
+}
+
+/**
+ * Execute El Torito image
+ *
+ * @v image		El Torito image
+ * @ret rc		Return status code
+ */
+static int eltorito_exec ( struct image *image ) {
+	struct ramdisk ramdisk;
+	struct int13_drive int13_drive;
+	unsigned int load_segment = image->priv.ul;
+	unsigned int load_offset = ( load_segment ? 0 : 0x7c00 );
+	int rc;
+
+	memset ( &ramdisk, 0, sizeof ( ramdisk ) );
+	init_ramdisk ( &ramdisk, image->data, image->len, ISO9660_BLKSIZE );
+	
+	memset ( &int13_drive, 0, sizeof ( int13_drive ) );
+	int13_drive.blockdev = &ramdisk.blockdev;
+	register_int13_drive ( &int13_drive );
+
+	if ( ( rc = call_bootsector ( load_segment, load_offset, 
+				      int13_drive.drive ) ) != 0 ) {
+		DBGC ( image, "ElTorito %p boot failed: %s\n",
+		       image, strerror ( rc ) );
+		goto err;
+	}
+	
+	rc = -ECANCELED; /* -EIMPOSSIBLE */
+ err:
+	unregister_int13_drive ( &int13_drive );
+	return rc;
+}
+
+/**
+ * Read and verify El Torito Boot Record Volume Descriptor
+ *
+ * @v image		El Torito file
+ * @ret catalog_offset	Offset of Boot Catalog
+ * @ret rc		Return status code
+ */
+static int eltorito_read_voldesc ( struct image *image,
+				   unsigned long *catalog_offset ) {
+	static const struct eltorito_vol_desc vol_desc_signature = {
+		.record_indicator = 0,
+		.iso9660_id = "CD001",
+		.version = 1,
+		.system_indicator = "EL TORITO SPECIFICATION",
+	};
+	struct eltorito_vol_desc vol_desc;
+
+	/* Sanity check */
+	if ( image->len < ( ELTORITO_VOL_DESC_OFFSET + ISO9660_BLKSIZE ) ) {
+		DBGC ( image, "ElTorito %p too short\n", image );
+		return -ENOEXEC;
+	}
+
+	/* Read and verify Boot Record Volume Descriptor */
+	copy_from_user ( &vol_desc, image->data, ELTORITO_VOL_DESC_OFFSET,
+			 sizeof ( vol_desc ) );
+	if ( memcmp ( &vol_desc, &vol_desc_signature,
+		      offsetof ( typeof ( vol_desc ), sector ) ) != 0 ) {
+		DBGC ( image, "ElTorito %p invalid Boot Record Volume "
+		       "Descriptor\n", image );
+		return -ENOEXEC;
+	}
+	*catalog_offset = ( vol_desc.sector * ISO9660_BLKSIZE );
+
+	DBGC ( image, "ElTorito %p boot catalog at offset %#lx\n",
+	       image, *catalog_offset );
+
+	return 0;
+}
+
+/**
+ * Read and verify El Torito Boot Catalog
+ *
+ * @v image		El Torito file
+ * @v catalog_offset	Offset of Boot Catalog
+ * @ret boot_entry	El Torito boot entry
+ * @ret rc		Return status code
+ */
+static int eltorito_read_catalog ( struct image *image,
+				   unsigned long catalog_offset,
+				   struct eltorito_boot_entry *boot_entry ) {
+	struct eltorito_validation_entry validation_entry;
+
+	/* Sanity check */
+	if ( image->len < ( catalog_offset + ISO9660_BLKSIZE ) ) {
+		DBGC ( image, "ElTorito %p bad boot catalog offset %#lx\n",
+		       image, catalog_offset );
+		return -ENOEXEC;
+	}
+
+	/* Read and verify the Validation Entry of the Boot Catalog */
+	copy_from_user ( &validation_entry, image->data, catalog_offset,
+			 sizeof ( validation_entry ) );
+	if ( word_checksum ( &validation_entry,
+			     sizeof ( validation_entry ) ) != 0 ) {
+		DBGC ( image, "ElTorito %p bad Validation Entry checksum\n",
+		       image );
+		return -ENOEXEC;
+	}
+
+	/* Read and verify the Initial/Default entry */
+	copy_from_user ( boot_entry, image->data,
+			 ( catalog_offset + sizeof ( validation_entry ) ),
+			 sizeof ( *boot_entry ) );
+	if ( boot_entry->indicator != ELTORITO_BOOTABLE ) {
+		DBGC ( image, "ElTorito %p not bootable\n", image );
+		return -ENOEXEC;
+	}
+	if ( boot_entry->media_type != ELTORITO_NO_EMULATION ) {
+		DBGC ( image, "ElTorito %p cannot support media type %d\n",
+		       image, boot_entry->media_type );
+		return -ENOTSUP;
+	}
+
+	DBGC ( image, "ElTorito %p media type %d segment %04x\n",
+	       image, boot_entry->media_type, boot_entry->load_segment );
+
+	return 0;
+}
+
+/**
+ * Load El Torito virtual disk image into memory
+ *
+ * @v image		El Torito file
+ * @v boot_entry	El Torito boot entry
+ * @ret rc		Return status code
+ */
+static int eltorito_load_disk ( struct image *image,
+				struct eltorito_boot_entry *boot_entry ) {
+	unsigned long start = ( boot_entry->start * ISO9660_BLKSIZE );
+	unsigned long length = ( boot_entry->length * ISO9660_BLKSIZE );
+	unsigned int load_segment;
+	userptr_t buffer;
+	int rc;
+
+	/* Sanity check */
+	if ( image->len < ( start + length ) ) {
+		DBGC ( image, "ElTorito %p virtual disk lies outside image\n",
+		       image );
+		return -ENOEXEC;
+	}
+	DBGC ( image, "ElTorito %p virtual disk at %#lx+%#lx\n",
+	       image, start, length );
+
+	/* Calculate load address */
+	load_segment = boot_entry->load_segment;
+	buffer = real_to_user ( load_segment, ( load_segment ? 0 : 0x7c00 ) );
+
+	/* Verify and prepare segment */
+	if ( ( rc = prep_segment ( buffer, length, length ) ) != 0 ) {
+		DBGC ( image, "ElTorito %p could not prepare segment: %s\n",
+		       image, strerror ( rc ) );
+		return rc;
+	}
+
+	/* Copy image to segment */
+	memcpy_user ( buffer, 0, image->data, start, length );
+
+	return 0;
+}
+
+/**
+ * Load El Torito image into memory
+ *
+ * @v image		El Torito file
+ * @ret rc		Return status code
+ */
+static int eltorito_load ( struct image *image ) {
+	struct eltorito_boot_entry boot_entry;
+	unsigned long bootcat_offset;
+	int rc;
+
+	/* Read Boot Record Volume Descriptor, if present */
+	if ( ( rc = eltorito_read_voldesc ( image, &bootcat_offset ) ) != 0 )
+		return rc;
+
+	/* This is an El Torito image, valid or otherwise */
+	if ( ! image->type )
+		image->type = &eltorito_image_type;
+
+	/* Read Boot Catalog */
+	if ( ( rc = eltorito_read_catalog ( image, bootcat_offset,
+					    &boot_entry ) ) != 0 )
+		return rc;
+
+	/* Load Virtual Disk image */
+	if ( ( rc = eltorito_load_disk ( image, &boot_entry ) ) != 0 )
+		return rc;
+
+	/* Record load segment in image private data field */
+	image->priv.ul = boot_entry.load_segment;
+
+	return 0;
+}
+
+/** El Torito image type */
+struct image_type eltorito_image_type __image_type ( PROBE_NORMAL ) = {
+	.name = "El Torito",
+	.load = eltorito_load,
+	.exec = eltorito_exec,
+};
diff --git a/gpxe/src/arch/i386/image/multiboot.c b/gpxe/src/arch/i386/image/multiboot.c
new file mode 100644
index 0000000..5b62095
--- /dev/null
+++ b/gpxe/src/arch/i386/image/multiboot.c
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/**
+ * @file
+ *
+ * Multiboot image format
+ *
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+#include <realmode.h>
+#include <multiboot.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/image.h>
+#include <gpxe/segment.h>
+#include <gpxe/memmap.h>
+#include <gpxe/elf.h>
+#include <gpxe/init.h>
+#include <gpxe/features.h>
+
+FEATURE ( FEATURE_IMAGE, "Multiboot", DHCP_EB_FEATURE_MULTIBOOT, 1 );
+
+struct image_type multiboot_image_type __image_type ( PROBE_MULTIBOOT );
+
+/**
+ * Maximum number of modules we will allow for
+ *
+ * If this has bitten you: sorry.  I did have a perfect scheme with a
+ * dynamically allocated list of modules on the protected-mode stack,
+ * but it was incompatible with some broken OSes that can only access
+ * low memory at boot time (even though we kindly set up 4GB flat
+ * physical addressing as per the multiboot specification.
+ *
+ */
+#define MAX_MODULES 8
+
+/**
+ * Maximum combined length of command lines
+ *
+ * Again; sorry.  Some broken OSes zero out any non-base memory that
+ * isn't part of the loaded module set, so we can't just use
+ * virt_to_phys(cmdline) to point to the command lines, even though
+ * this would comply with the Multiboot spec.
+ */
+#define MB_MAX_CMDLINE 512
+
+/** Multiboot flags that we support */
+#define MB_SUPPORTED_FLAGS ( MB_FLAG_PGALIGN | MB_FLAG_MEMMAP | \
+			     MB_FLAG_VIDMODE | MB_FLAG_RAW )
+
+/** Compulsory feature multiboot flags */
+#define MB_COMPULSORY_FLAGS 0x0000ffff
+
+/** Optional feature multiboot flags */
+#define MB_OPTIONAL_FLAGS 0xffff0000
+
+/**
+ * Multiboot flags that we don't support
+ *
+ * We only care about the compulsory feature flags (bits 0-15); we are
+ * allowed to ignore the optional feature flags.
+ */
+#define MB_UNSUPPORTED_FLAGS ( MB_COMPULSORY_FLAGS & ~MB_SUPPORTED_FLAGS )
+
+/** A multiboot header descriptor */
+struct multiboot_header_info {
+	/** The actual multiboot header */
+	struct multiboot_header mb;
+	/** Offset of header within the multiboot image */
+	size_t offset;
+};
+
+/** Multiboot module command lines */
+static char __bss16_array ( mb_cmdlines, [MB_MAX_CMDLINE] );
+#define mb_cmdlines __use_data16 ( mb_cmdlines )
+
+/** Offset within module command lines */
+static unsigned int mb_cmdline_offset;
+
+/**
+ * Build multiboot memory map
+ *
+ * @v image		Multiboot image
+ * @v mbinfo		Multiboot information structure
+ * @v mbmemmap		Multiboot memory map
+ * @v limit		Maxmimum number of memory map entries
+ */
+static void multiboot_build_memmap ( struct image *image,
+				     struct multiboot_info *mbinfo,
+				     struct multiboot_memory_map *mbmemmap,
+				     unsigned int limit ) {
+	struct memory_map memmap;
+	unsigned int i;
+
+	/* Get memory map */
+	get_memmap ( &memmap );
+
+	/* Translate into multiboot format */
+	memset ( mbmemmap, 0, sizeof ( *mbmemmap ) );
+	for ( i = 0 ; i < memmap.count ; i++ ) {
+		if ( i >= limit ) {
+			DBGC ( image, "MULTIBOOT %p limit of %d memmap "
+			       "entries reached\n", image, limit );
+			break;
+		}
+		mbmemmap[i].size = ( sizeof ( mbmemmap[i] ) -
+				     sizeof ( mbmemmap[i].size ) );
+		mbmemmap[i].base_addr = memmap.regions[i].start;
+		mbmemmap[i].length = ( memmap.regions[i].end -
+				       memmap.regions[i].start );
+		mbmemmap[i].type = MBMEM_RAM;
+		mbinfo->mmap_length += sizeof ( mbmemmap[i] );
+		if ( memmap.regions[i].start == 0 )
+			mbinfo->mem_lower = ( memmap.regions[i].end / 1024 );
+		if ( memmap.regions[i].start == 0x100000 )
+			mbinfo->mem_upper = ( ( memmap.regions[i].end -
+						0x100000 ) / 1024 );
+	}
+}
+
+/**
+ * Add command line in base memory
+ *
+ * @v imgname		Image name
+ * @v cmdline		Command line
+ * @ret physaddr	Physical address of command line
+ */
+physaddr_t multiboot_add_cmdline ( const char *imgname, const char *cmdline ) {
+	char *mb_cmdline;
+
+	if ( ! cmdline )
+		cmdline = "";
+
+	/* Copy command line to base memory buffer */
+	mb_cmdline = ( mb_cmdlines + mb_cmdline_offset );
+	mb_cmdline_offset +=
+		( snprintf ( mb_cmdline,
+			     ( sizeof ( mb_cmdlines ) - mb_cmdline_offset ),
+			     "%s %s", imgname, cmdline ) + 1 );
+
+	/* Truncate to terminating NUL in buffer if necessary */
+	if ( mb_cmdline_offset > sizeof ( mb_cmdlines ) )
+		mb_cmdline_offset = ( sizeof ( mb_cmdlines ) - 1 );
+
+	return virt_to_phys ( mb_cmdline );
+}
+
+/**
+ * Build multiboot module list
+ *
+ * @v image		Multiboot image
+ * @v modules		Module list to fill, or NULL
+ * @ret count		Number of modules
+ */
+static unsigned int
+multiboot_build_module_list ( struct image *image,
+			      struct multiboot_module *modules,
+			      unsigned int limit ) {
+	struct image *module_image;
+	struct multiboot_module *module;
+	unsigned int count = 0;
+	unsigned int insert;
+	physaddr_t start;
+	physaddr_t end;
+	unsigned int i;
+
+	/* Add each image as a multiboot module */
+	for_each_image ( module_image ) {
+
+		if ( count >= limit ) {
+			DBGC ( image, "MULTIBOOT %p limit of %d modules "
+			       "reached\n", image, limit );
+			break;
+		}
+
+		/* Do not include kernel image itself as a module */
+		if ( module_image == image )
+			continue;
+
+		/* At least some OSes expect the multiboot modules to
+		 * be in ascending order, so we have to support it.
+		 */
+		start = user_to_phys ( module_image->data, 0 );
+		end = user_to_phys ( module_image->data, module_image->len );
+		for ( insert = 0 ; insert < count ; insert++ ) {
+			if ( start < modules[insert].mod_start )
+				break;
+		}
+		module = &modules[insert];
+		memmove ( ( module + 1 ), module,
+			  ( ( count - insert ) * sizeof ( *module ) ) );
+		module->mod_start = start;
+		module->mod_end = end;
+		module->string = multiboot_add_cmdline ( module_image->name,
+						       module_image->cmdline );
+		module->reserved = 0;
+		
+		/* We promise to page-align modules */
+		assert ( ( module->mod_start & 0xfff ) == 0 );
+
+		count++;
+	}
+
+	/* Dump module configuration */
+	for ( i = 0 ; i < count ; i++ ) {
+		DBGC ( image, "MULTIBOOT %p module %d is [%x,%x)\n",
+		       image, i, modules[i].mod_start,
+		       modules[i].mod_end );
+	}
+
+	return count;
+}
+
+/**
+ * The multiboot information structure
+ *
+ * Kept in base memory because some OSes won't find it elsewhere,
+ * along with the other structures belonging to the Multiboot
+ * information table.
+ */
+static struct multiboot_info __bss16 ( mbinfo );
+#define mbinfo __use_data16 ( mbinfo )
+
+/** The multiboot bootloader name */
+static char __data16_array ( mb_bootloader_name, [] ) = "gPXE " VERSION;
+#define mb_bootloader_name __use_data16 ( mb_bootloader_name )
+
+/** The multiboot memory map */
+static struct multiboot_memory_map
+	__bss16_array ( mbmemmap, [MAX_MEMORY_REGIONS] );
+#define mbmemmap __use_data16 ( mbmemmap )
+
+/** The multiboot module list */
+static struct multiboot_module __bss16_array ( mbmodules, [MAX_MODULES] );
+#define mbmodules __use_data16 ( mbmodules )
+
+/**
+ * Execute multiboot image
+ *
+ * @v image		Multiboot image
+ * @ret rc		Return status code
+ */
+static int multiboot_exec ( struct image *image ) {
+	physaddr_t entry = image->priv.phys;
+
+	/* Populate multiboot information structure */
+	memset ( &mbinfo, 0, sizeof ( mbinfo ) );
+	mbinfo.flags = ( MBI_FLAG_LOADER | MBI_FLAG_MEM | MBI_FLAG_MMAP |
+			 MBI_FLAG_CMDLINE | MBI_FLAG_MODS );
+	mb_cmdline_offset = 0;
+	mbinfo.cmdline = multiboot_add_cmdline ( image->name, image->cmdline );
+	mbinfo.mods_count = multiboot_build_module_list ( image, mbmodules,
+				( sizeof(mbmodules) / sizeof(mbmodules[0]) ) );
+	mbinfo.mods_addr = virt_to_phys ( mbmodules );
+	mbinfo.mmap_addr = virt_to_phys ( mbmemmap );
+	mbinfo.boot_loader_name = virt_to_phys ( mb_bootloader_name );
+
+	/* Multiboot images may not return and have no callback
+	 * interface, so shut everything down prior to booting the OS.
+	 */
+	shutdown ( SHUTDOWN_BOOT );
+
+	/* Build memory map after unhiding bootloader memory regions as part of
+	 * shutting everything down.
+	 */
+	multiboot_build_memmap ( image, &mbinfo, mbmemmap,
+				 ( sizeof(mbmemmap) / sizeof(mbmemmap[0]) ) );
+
+	/* Jump to OS with flat physical addressing */
+	DBGC ( image, "MULTIBOOT %p starting execution at %lx\n",
+	       image, entry );
+	__asm__ __volatile__ ( PHYS_CODE ( "pushl %%ebp\n\t"
+					   "call *%%edi\n\t"
+					   "popl %%ebp\n\t" )
+			       : : "a" ( MULTIBOOT_BOOTLOADER_MAGIC ),
+			           "b" ( virt_to_phys ( &mbinfo ) ),
+			           "D" ( entry )
+			       : "ecx", "edx", "esi", "memory" );
+
+	DBGC ( image, "MULTIBOOT %p returned\n", image );
+
+	/* It isn't safe to continue after calling shutdown() */
+	while ( 1 ) {}
+
+	return -ECANCELED;  /* -EIMPOSSIBLE, anyone? */
+}
+
+/**
+ * Find multiboot header
+ *
+ * @v image		Multiboot file
+ * @v hdr		Multiboot header descriptor to fill in
+ * @ret rc		Return status code
+ */
+static int multiboot_find_header ( struct image *image,
+				   struct multiboot_header_info *hdr ) {
+	uint32_t buf[64];
+	size_t offset;
+	unsigned int buf_idx;
+	uint32_t checksum;
+
+	/* Scan through first 8kB of image file 256 bytes at a time.
+	 * (Use the buffering to avoid the overhead of a
+	 * copy_from_user() for every dword.)
+	 */
+	for ( offset = 0 ; offset < 8192 ; offset += sizeof ( buf[0] ) ) {
+		/* Check for end of image */
+		if ( offset > image->len )
+			break;
+		/* Refill buffer if applicable */
+		buf_idx = ( ( offset % sizeof ( buf ) ) / sizeof ( buf[0] ) );
+		if ( buf_idx == 0 ) {
+			copy_from_user ( buf, image->data, offset,
+					 sizeof ( buf ) );
+		}
+		/* Check signature */
+		if ( buf[buf_idx] != MULTIBOOT_HEADER_MAGIC )
+			continue;
+		/* Copy header and verify checksum */
+		copy_from_user ( &hdr->mb, image->data, offset,
+				 sizeof ( hdr->mb ) );
+		checksum = ( hdr->mb.magic + hdr->mb.flags +
+			     hdr->mb.checksum );
+		if ( checksum != 0 )
+			continue;
+		/* Record offset of multiboot header and return */
+		hdr->offset = offset;
+		return 0;
+	}
+
+	/* No multiboot header found */
+	return -ENOEXEC;
+}
+
+/**
+ * Load raw multiboot image into memory
+ *
+ * @v image		Multiboot file
+ * @v hdr		Multiboot header descriptor
+ * @ret rc		Return status code
+ */
+static int multiboot_load_raw ( struct image *image,
+				struct multiboot_header_info *hdr ) {
+	size_t offset;
+	size_t filesz;
+	size_t memsz;
+	userptr_t buffer;
+	int rc;
+
+	/* Sanity check */
+	if ( ! ( hdr->mb.flags & MB_FLAG_RAW ) ) {
+		DBGC ( image, "MULTIBOOT %p is not flagged as a raw image\n",
+		       image );
+		return -EINVAL;
+	}
+
+	/* Verify and prepare segment */
+	offset = ( hdr->offset - hdr->mb.header_addr + hdr->mb.load_addr );
+	filesz = ( hdr->mb.load_end_addr ?
+		   ( hdr->mb.load_end_addr - hdr->mb.load_addr ) :
+		   ( image->len - offset ) );
+	memsz = ( hdr->mb.bss_end_addr ?
+		  ( hdr->mb.bss_end_addr - hdr->mb.load_addr ) : filesz );
+	buffer = phys_to_user ( hdr->mb.load_addr );
+	if ( ( rc = prep_segment ( buffer, filesz, memsz ) ) != 0 ) {
+		DBGC ( image, "MULTIBOOT %p could not prepare segment: %s\n",
+		       image, strerror ( rc ) );
+		return rc;
+	}
+
+	/* Copy image to segment */
+	memcpy_user ( buffer, 0, image->data, offset, filesz );
+
+	/* Record execution entry point in image private data field */
+	image->priv.phys = hdr->mb.entry_addr;
+
+	return 0;
+}
+
+/**
+ * Load ELF multiboot image into memory
+ *
+ * @v image		Multiboot file
+ * @ret rc		Return status code
+ */
+static int multiboot_load_elf ( struct image *image ) {
+	int rc;
+
+	/* Load ELF image*/
+	if ( ( rc = elf_load ( image ) ) != 0 ) {
+		DBGC ( image, "MULTIBOOT %p ELF image failed to load: %s\n",
+		       image, strerror ( rc ) );
+		return rc;
+	}
+
+	return 0;
+}
+
+/**
+ * Load multiboot image into memory
+ *
+ * @v image		Multiboot file
+ * @ret rc		Return status code
+ */
+static int multiboot_load ( struct image *image ) {
+	struct multiboot_header_info hdr;
+	int rc;
+
+	/* Locate multiboot header, if present */
+	if ( ( rc = multiboot_find_header ( image, &hdr ) ) != 0 ) {
+		DBGC ( image, "MULTIBOOT %p has no multiboot header\n",
+		       image );
+		return rc;
+	}
+	DBGC ( image, "MULTIBOOT %p found header with flags %08x\n",
+	       image, hdr.mb.flags );
+
+	/* This is a multiboot image, valid or otherwise */
+	if ( ! image->type )
+		image->type = &multiboot_image_type;
+
+	/* Abort if we detect flags that we cannot support */
+	if ( hdr.mb.flags & MB_UNSUPPORTED_FLAGS ) {
+		DBGC ( image, "MULTIBOOT %p flags %08x not supported\n",
+		       image, ( hdr.mb.flags & MB_UNSUPPORTED_FLAGS ) );
+		return -ENOTSUP;
+	}
+
+	/* There is technically a bit MB_FLAG_RAW to indicate whether
+	 * this is an ELF or a raw image.  In practice, grub will use
+	 * the ELF header if present, and Solaris relies on this
+	 * behaviour.
+	 */
+	if ( ( ( rc = multiboot_load_elf ( image ) ) != 0 ) &&
+	     ( ( rc = multiboot_load_raw ( image, &hdr ) ) != 0 ) )
+		return rc;
+
+	return 0;
+}
+
+/** Multiboot image type */
+struct image_type multiboot_image_type __image_type ( PROBE_MULTIBOOT ) = {
+	.name = "Multiboot",
+	.load = multiboot_load,
+	.exec = multiboot_exec,
+};
diff --git a/gpxe/src/arch/i386/image/nbi.c b/gpxe/src/arch/i386/image/nbi.c
new file mode 100644
index 0000000..a4ee442
--- /dev/null
+++ b/gpxe/src/arch/i386/image/nbi.c
@@ -0,0 +1,442 @@
+#include <errno.h>
+#include <assert.h>
+#include <realmode.h>
+#include <gateA20.h>
+#include <memsizes.h>
+#include <basemem_packet.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/segment.h>
+#include <gpxe/init.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/fakedhcp.h>
+#include <gpxe/image.h>
+#include <gpxe/features.h>
+
+/** @file
+ *
+ * NBI image format.
+ *
+ * The Net Boot Image format is defined by the "Draft Net Boot Image
+ * Proposal 0.3" by Jamie Honan, Gero Kuhlmann and Ken Yap.  It is now
+ * considered to be a legacy format, but it still included because a
+ * large amount of software (e.g. nymph, LTSP) makes use of NBI files.
+ *
+ * Etherboot does not implement the INT 78 callback interface
+ * described by the NBI specification.  For a callback interface on
+ * x86 architecture, use PXE.
+ *
+ */
+
+FEATURE ( FEATURE_IMAGE, "NBI", DHCP_EB_FEATURE_NBI, 1 );
+
+struct image_type nbi_image_type __image_type ( PROBE_NORMAL );
+
+/**
+ * An NBI image header
+ *
+ * Note that the length field uses a peculiar encoding; use the
+ * NBI_LENGTH() macro to decode the actual header length.
+ *
+ */
+struct imgheader {
+	unsigned long magic;		/**< Magic number (NBI_MAGIC) */
+	union {
+		unsigned char length;	/**< Nibble-coded header length */
+		unsigned long flags;	/**< Image flags */
+	};
+	segoff_t location;		/**< 16-bit seg:off header location */
+	union {
+		segoff_t segoff;	/**< 16-bit seg:off entry point */
+		unsigned long linear;	/**< 32-bit entry point */
+	} execaddr;
+} __attribute__ (( packed ));
+
+/** NBI magic number */
+#define NBI_MAGIC 0x1B031336UL
+
+/* Interpretation of the "length" fields */
+#define NBI_NONVENDOR_LENGTH(len)	( ( (len) & 0x0f ) << 2 )
+#define NBI_VENDOR_LENGTH(len)		( ( (len) & 0xf0 ) >> 2 )
+#define NBI_LENGTH(len) ( NBI_NONVENDOR_LENGTH(len) + NBI_VENDOR_LENGTH(len) )
+
+/* Interpretation of the "flags" fields */
+#define	NBI_PROGRAM_RETURNS(flags)	( (flags) & ( 1 << 8 ) )
+#define	NBI_LINEAR_EXEC_ADDR(flags)	( (flags) & ( 1 << 31 ) )
+
+/** NBI header length */
+#define NBI_HEADER_LENGTH	512
+
+/**
+ * An NBI segment header
+ *
+ * Note that the length field uses a peculiar encoding; use the
+ * NBI_LENGTH() macro to decode the actual header length.
+ *
+ */
+struct segheader {
+	unsigned char length;		/**< Nibble-coded header length */
+	unsigned char vendortag;	/**< Vendor-defined private tag */
+	unsigned char reserved;
+	unsigned char flags;		/**< Segment flags */
+	unsigned long loadaddr;		/**< Load address */
+	unsigned long imglength;	/**< Segment length in NBI file */
+	unsigned long memlength;	/**< Segment length in memory */
+};
+
+/* Interpretation of the "flags" fields */
+#define NBI_LOADADDR_FLAGS(flags)	( (flags) & 0x03 )
+#define NBI_LOADADDR_ABS		0x00
+#define NBI_LOADADDR_AFTER		0x01
+#define NBI_LOADADDR_END		0x02
+#define NBI_LOADADDR_BEFORE		0x03
+#define NBI_LAST_SEGHEADER(flags)	( (flags) & ( 1 << 2 ) )
+
+/* Define a type for passing info to a loaded program */
+struct ebinfo {
+	uint8_t  major, minor;  /* Version */
+	uint16_t flags;         /* Bit flags */
+};
+
+/** Info passed to NBI image */
+static struct ebinfo loaderinfo = {
+	VERSION_MAJOR, VERSION_MINOR,
+	0
+};
+
+/**
+ * Prepare a segment for an NBI image
+ *
+ * @v image		NBI image
+ * @v offset		Offset within NBI image
+ * @v filesz		Length of initialised-data portion of the segment
+ * @v memsz		Total length of the segment
+ * @v src		Source for initialised data
+ * @ret rc		Return status code
+ */
+static int nbi_prepare_segment ( struct image *image, size_t offset __unused,
+				 userptr_t dest, size_t filesz, size_t memsz ){
+	int rc;
+
+	if ( ( rc = prep_segment ( dest, filesz, memsz ) ) != 0 ) {
+		DBGC ( image, "NBI %p could not prepare segment: %s\n",
+		       image, strerror ( rc ) );
+		return rc;
+	}
+
+	return 0;
+}
+
+/**
+ * Load a segment for an NBI image
+ *
+ * @v image		NBI image
+ * @v offset		Offset within NBI image
+ * @v filesz		Length of initialised-data portion of the segment
+ * @v memsz		Total length of the segment
+ * @v src		Source for initialised data
+ * @ret rc		Return status code
+ */
+static int nbi_load_segment ( struct image *image, size_t offset,
+			      userptr_t dest, size_t filesz,
+			      size_t memsz __unused ) {
+	memcpy_user ( dest, 0, image->data, offset, filesz );
+	return 0;
+}
+
+/**
+ * Process segments of an NBI image
+ *
+ * @v image		NBI image
+ * @v imgheader		Image header information
+ * @v process		Function to call for each segment
+ * @ret rc		Return status code
+ */
+static int nbi_process_segments ( struct image *image,
+				  struct imgheader *imgheader,
+				  int ( * process ) ( struct image *image,
+						      size_t offset,
+						      userptr_t dest,
+						      size_t filesz,
+						      size_t memsz ) ) {
+	struct segheader sh;
+	size_t offset = 0;
+	size_t sh_off;
+	userptr_t dest;
+	size_t filesz;
+	size_t memsz;
+	int rc;
+	
+	/* Copy image header to target location */
+	dest = real_to_user ( imgheader->location.segment,
+			      imgheader->location.offset );
+	filesz = memsz = NBI_HEADER_LENGTH;
+	if ( ( rc = process ( image, offset, dest, filesz, memsz ) ) != 0 )
+		return rc;
+	offset += filesz;
+
+	/* Process segments in turn */
+	sh_off = NBI_LENGTH ( imgheader->length );
+	do {
+		/* Read segment header */
+		copy_from_user ( &sh, image->data, sh_off, sizeof ( sh ) );
+		if ( sh.length == 0 ) {
+			/* Avoid infinite loop? */
+			DBGC ( image, "NBI %p invalid segheader length 0\n",
+			       image );
+			return -ENOEXEC;
+		}
+		
+		/* Calculate segment load address */
+		switch ( NBI_LOADADDR_FLAGS ( sh.flags ) ) {
+		case NBI_LOADADDR_ABS:
+			dest = phys_to_user ( sh.loadaddr );
+			break;
+		case NBI_LOADADDR_AFTER:
+			dest = userptr_add ( dest, memsz + sh.loadaddr );
+			break;
+		case NBI_LOADADDR_BEFORE:
+			dest = userptr_add ( dest, -sh.loadaddr );
+			break;
+		case NBI_LOADADDR_END:
+			/* Not correct according to the spec, but
+			 * maintains backwards compatibility with
+			 * previous versions of Etherboot.
+			 */
+			dest = phys_to_user ( ( extmemsize() + 1024 ) * 1024
+					      - sh.loadaddr );
+			break;
+		default:
+			/* Cannot be reached */
+			assert ( 0 );
+		}
+
+		/* Process this segment */
+		filesz = sh.imglength;
+		memsz = sh.memlength;
+		if ( ( offset + filesz ) > image->len ) {
+			DBGC ( image, "NBI %p segment outside file\n", image );
+			return -ENOEXEC;
+		}
+		if ( ( rc = process ( image, offset, dest,
+				      filesz, memsz ) ) != 0 ) {
+			return rc;
+		}
+		offset += filesz;
+
+		/* Next segheader */
+		sh_off += NBI_LENGTH ( sh.length );
+		if ( sh_off >= NBI_HEADER_LENGTH ) {
+			DBGC ( image, "NBI %p header overflow\n", image );
+			return -ENOEXEC;
+		}
+
+	} while ( ! NBI_LAST_SEGHEADER ( sh.flags ) );
+
+	if ( offset != image->len ) {
+		DBGC ( image, "NBI %p length wrong (file %zd, metadata %zd)\n",
+		       image, image->len, offset );
+		return -ENOEXEC;
+	}
+
+	return 0;
+}
+
+/**
+ * Load an NBI image into memory
+ *
+ * @v image		NBI image
+ * @ret rc		Return status code
+ */
+static int nbi_load ( struct image *image ) {
+	struct imgheader imgheader;
+	int rc;
+
+	/* If we don't have enough data give up */
+	if ( image->len < NBI_HEADER_LENGTH ) {
+		DBGC ( image, "NBI %p too short for an NBI image\n", image );
+		return -ENOEXEC;
+	}
+
+	/* Check image header */
+	copy_from_user ( &imgheader, image->data, 0, sizeof ( imgheader ) );
+	if ( imgheader.magic != NBI_MAGIC ) {
+		DBGC ( image, "NBI %p has no NBI signature\n", image );
+		return -ENOEXEC;
+	}
+
+	/* This is an NBI image, valid or otherwise */
+	if ( ! image->type )
+		image->type = &nbi_image_type;
+
+	DBGC ( image, "NBI %p placing header at %hx:%hx\n", image,
+	       imgheader.location.segment, imgheader.location.offset );
+
+	/* NBI files can have overlaps between segments; the bss of
+	 * one segment may overlap the initialised data of another.  I
+	 * assume this is a design flaw, but there are images out
+	 * there that we need to work with.  We therefore do two
+	 * passes: first to initialise the segments, then to copy the
+	 * data.  This avoids zeroing out already-copied data.
+	 */
+	if ( ( rc = nbi_process_segments ( image, &imgheader,
+					   nbi_prepare_segment ) ) != 0 )
+		return rc;
+	if ( ( rc = nbi_process_segments ( image, &imgheader,
+					   nbi_load_segment ) ) != 0 )
+		return rc;
+
+	/* Record header address in image private data field */
+	image->priv.user = real_to_user ( imgheader.location.segment,
+					  imgheader.location.offset );
+
+	return 0;
+}
+
+/**
+ * Boot a 16-bit NBI image
+ *
+ * @v imgheader		Image header information
+ * @ret rc		Return status code, if image returns
+ */
+static int nbi_boot16 ( struct image *image, struct imgheader *imgheader ) {
+	int discard_D, discard_S, discard_b;
+	int rc;
+
+	DBGC ( image, "NBI %p executing 16-bit image at %04x:%04x\n", image,
+	       imgheader->execaddr.segoff.segment,
+	       imgheader->execaddr.segoff.offset );
+
+	gateA20_unset();
+
+	__asm__ __volatile__ (
+		REAL_CODE ( "pushw %%ds\n\t"	/* far pointer to bootp data */
+			    "pushw %%bx\n\t"
+			    "pushl %%esi\n\t"	/* location */
+			    "pushw %%cs\n\t"	/* lcall execaddr */
+			    "call 1f\n\t"
+			    "jmp 2f\n\t"
+			    "\n1:\n\t"
+			    "pushl %%edi\n\t"
+			    "lret\n\t"
+			    "\n2:\n\t"
+			    "addw $8,%%sp\n\t"	/* clean up stack */ )
+		: "=a" ( rc ), "=D" ( discard_D ), "=S" ( discard_S ),
+		  "=b" ( discard_b )
+		: "D" ( imgheader->execaddr.segoff ),
+		  "S" ( imgheader->location ),
+		  "b" ( __from_data16 ( basemem_packet ) )
+		: "ecx", "edx", "ebp" );
+
+	gateA20_set();
+
+	return rc;
+}
+
+/**
+ * Boot a 32-bit NBI image
+ *
+ * @v imgheader		Image header information
+ * @ret rc		Return status code, if image returns
+ */
+static int nbi_boot32 ( struct image *image, struct imgheader *imgheader ) {
+	int discard_D, discard_S, discard_b;
+	int rc;
+
+	DBGC ( image, "NBI %p executing 32-bit image at %lx\n",
+	       image, imgheader->execaddr.linear );
+
+	/* no gateA20_unset for PM call */
+
+	/* Jump to OS with flat physical addressing */
+	__asm__ __volatile__ (
+		PHYS_CODE ( "pushl %%ebx\n\t" /* bootp data */
+			    "pushl %%esi\n\t" /* imgheader */
+			    "pushl %%eax\n\t" /* loaderinfo */
+			    "call *%%edi\n\t"
+			    "addl $12, %%esp\n\t" /* clean up stack */ )
+		: "=a" ( rc ), "=D" ( discard_D ), "=S" ( discard_S ),
+		  "=b" ( discard_b )
+		: "D" ( imgheader->execaddr.linear ),
+		  "S" ( ( imgheader->location.segment << 4 ) +
+			imgheader->location.offset ),
+		  "b" ( virt_to_phys ( basemem_packet ) ),
+		  "a" ( virt_to_phys ( &loaderinfo ) )
+		: "ecx", "edx", "ebp", "memory" );
+
+	return rc;
+}
+
+/**
+ * Prepare DHCP parameter block for NBI image
+ *
+ * @v image		NBI image
+ * @ret rc		Return status code
+ */
+static int nbi_prepare_dhcp ( struct image *image ) {
+	struct net_device *boot_netdev;
+	int rc;
+
+	boot_netdev = last_opened_netdev();
+	if ( ! boot_netdev ) {
+		DBGC ( image, "NBI %p could not identify a network device\n",
+		       image );
+		return -ENODEV;
+	}
+
+	if ( ( rc = create_fakedhcpack ( boot_netdev, basemem_packet,
+					 sizeof ( basemem_packet ) ) ) != 0 ) {
+		DBGC ( image, "NBI %p failed to build DHCP packet\n", image );
+		return rc;
+	}
+
+	return 0;
+}
+
+/**
+ * Execute a loaded NBI image
+ *
+ * @v image		NBI image
+ * @ret rc		Return status code
+ */
+static int nbi_exec ( struct image *image ) {
+	struct imgheader imgheader;
+	int may_return;
+	int rc;
+
+	copy_from_user ( &imgheader, image->priv.user, 0,
+			 sizeof ( imgheader ) );
+
+	/* Prepare DHCP option block */
+	if ( ( rc = nbi_prepare_dhcp ( image ) ) != 0 )
+		return rc;
+
+	/* Shut down now if NBI image will not return */
+	may_return = NBI_PROGRAM_RETURNS ( imgheader.flags );
+	if ( ! may_return )
+		shutdown ( SHUTDOWN_BOOT );
+
+	/* Execute NBI image */
+	if ( NBI_LINEAR_EXEC_ADDR ( imgheader.flags ) ) {
+		rc = nbi_boot32 ( image, &imgheader );
+	} else {
+	        rc = nbi_boot16 ( image, &imgheader );
+	}
+
+	if ( ! may_return ) {
+		/* Cannot continue after shutdown() called */
+		DBGC ( image, "NBI %p returned %d from non-returnable image\n",
+		       image, rc  );
+		while ( 1 ) {}
+	}
+
+	DBGC ( image, "NBI %p returned %d\n", image, rc );
+
+	return rc;
+}
+
+/** NBI image type */
+struct image_type nbi_image_type __image_type ( PROBE_NORMAL ) = {
+	.name = "NBI",
+	.load = nbi_load,
+	.exec = nbi_exec,
+};
diff --git a/gpxe/src/arch/i386/image/pxe_image.c b/gpxe/src/arch/i386/image/pxe_image.c
new file mode 100644
index 0000000..63429f8
--- /dev/null
+++ b/gpxe/src/arch/i386/image/pxe_image.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/**
+ * @file
+ *
+ * PXE image format
+ *
+ */
+
+#include <pxe.h>
+#include <pxe_call.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/image.h>
+#include <gpxe/segment.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/features.h>
+
+FEATURE ( FEATURE_IMAGE, "PXE", DHCP_EB_FEATURE_PXE, 1 );
+
+struct image_type pxe_image_type __image_type ( PROBE_PXE );
+
+/**
+ * Execute PXE image
+ *
+ * @v image		PXE image
+ * @ret rc		Return status code
+ */
+static int pxe_exec ( struct image *image ) {
+	struct net_device *netdev;
+	int rc;
+
+	/* Arbitrarily pick the most recently opened network device */
+	if ( ( netdev = last_opened_netdev() ) == NULL ) {
+		DBGC ( image, "IMAGE %p could not locate PXE net device\n",
+		       image );
+		return -ENODEV;
+	}
+
+	/* Activate PXE */
+	pxe_activate ( netdev );
+
+	/* Start PXE NBP */
+	rc = pxe_start_nbp();
+
+	/* Deactivate PXE */
+	pxe_deactivate();
+
+	return rc;
+}
+
+/**
+ * Load PXE image into memory
+ *
+ * @v image		PXE file
+ * @ret rc		Return status code
+ */
+int pxe_load ( struct image *image ) {
+	userptr_t buffer = real_to_user ( 0, 0x7c00 );
+	size_t filesz = image->len;
+	size_t memsz = image->len;
+	int rc;
+
+	/* Images too large to fit in base memory cannot be PXE
+	 * images.  We include this check to help prevent unrecognised
+	 * images from being marked as PXE images, since PXE images
+	 * have no signature we can check against.
+	 */
+	if ( filesz > ( 0xa0000 - 0x7c00 ) )
+		return -ENOEXEC;
+
+	/* Rejecting zero-length images is also useful, since these
+	 * end up looking to the user like bugs in gPXE.
+	 */
+	if ( ! filesz )
+		return -ENOEXEC;
+
+	/* There are no signature checks for PXE; we will accept anything */
+	if ( ! image->type )
+		image->type = &pxe_image_type;
+
+	/* Verify and prepare segment */
+	if ( ( rc = prep_segment ( buffer, filesz, memsz ) ) != 0 ) {
+		DBGC ( image, "IMAGE %p could not prepare segment: %s\n",
+		       image, strerror ( rc ) );
+		return rc;
+	}
+
+	/* Copy image to segment */
+	memcpy_user ( buffer, 0, image->data, 0, filesz );
+
+	return 0;
+}
+
+/** PXE image type */
+struct image_type pxe_image_type __image_type ( PROBE_PXE ) = {
+	.name = "PXE",
+	.load = pxe_load,
+	.exec = pxe_exec,
+};
diff --git a/gpxe/src/arch/i386/include/basemem.h b/gpxe/src/arch/i386/include/basemem.h
new file mode 100644
index 0000000..c477c7f
--- /dev/null
+++ b/gpxe/src/arch/i386/include/basemem.h
@@ -0,0 +1,35 @@
+#ifndef _BASEMEM_H
+#define _BASEMEM_H
+
+/** @file
+ *
+ * Base memory allocation
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <realmode.h>
+#include <bios.h>
+
+/**
+ * Read the BIOS free base memory counter
+ *
+ * @ret fbms		Free base memory counter (in kB)
+ */
+static inline unsigned int get_fbms ( void ) {
+	uint16_t fbms;
+
+	get_real ( fbms, BDA_SEG, BDA_FBMS );
+	return fbms;
+}
+
+extern void set_fbms ( unsigned int new_fbms );
+
+/* Actually in hidemem.c, but putting it here avoids polluting the
+ * architecture-independent include/hidemem.h.
+ */
+extern void hide_basemem ( void );
+
+#endif /* _BASEMEM_H */
diff --git a/gpxe/src/arch/i386/include/basemem_packet.h b/gpxe/src/arch/i386/include/basemem_packet.h
new file mode 100644
index 0000000..3cb4776
--- /dev/null
+++ b/gpxe/src/arch/i386/include/basemem_packet.h
@@ -0,0 +1,15 @@
+#ifndef BASEMEM_PACKET_H
+#define BASEMEM_PACKET_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <realmode.h>
+
+/** Maximum length of base memory packet buffer */
+#define BASEMEM_PACKET_LEN 1514
+
+/** Base memory packet buffer */
+extern char __bss16_array ( basemem_packet, [BASEMEM_PACKET_LEN] );
+#define basemem_packet __use_data16 ( basemem_packet )
+
+#endif /* BASEMEM_PACKET_H */
diff --git a/gpxe/src/arch/i386/include/bios.h b/gpxe/src/arch/i386/include/bios.h
new file mode 100644
index 0000000..70bb73d
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bios.h
@@ -0,0 +1,10 @@
+#ifndef BIOS_H
+#define BIOS_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#define BDA_SEG 0x0040
+#define BDA_FBMS 0x0013
+#define BDA_NUM_DRIVES 0x0075
+
+#endif /* BIOS_H */
diff --git a/gpxe/src/arch/i386/include/bios_disks.h b/gpxe/src/arch/i386/include/bios_disks.h
new file mode 100644
index 0000000..0dd7c4e
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bios_disks.h
@@ -0,0 +1,69 @@
+#ifndef BIOS_DISKS_H
+#define BIOS_DISKS_H
+
+#include "dev.h"
+
+/*
+ * Constants
+ *
+ */
+
+#define	BIOS_DISK_MAX_NAME_LEN	6
+
+struct bios_disk_sector {
+	char data[512];
+};
+
+/*
+ * The location of a BIOS disk
+ *
+ */
+struct bios_disk_loc {
+	uint8_t drive;
+};
+
+/*
+ * A physical BIOS disk device
+ *
+ */
+struct bios_disk_device {
+	char name[BIOS_DISK_MAX_NAME_LEN];
+	uint8_t drive;
+	uint8_t type;
+};
+
+/*
+ * A BIOS disk driver, with a valid device ID range and naming
+ * function.
+ *
+ */
+struct bios_disk_driver {
+	void ( *fill_drive_name ) ( char *buf, uint8_t drive );
+	uint8_t min_drive;
+	uint8_t max_drive;
+};
+
+/*
+ * Define a BIOS disk driver
+ *
+ */
+#define BIOS_DISK_DRIVER( _name, _fill_drive_name, _min_drive, _max_drive )   \
+	static struct bios_disk_driver _name = {			      \
+		.fill_drive_name = _fill_drive_name,			      \
+		.min_drive = _min_drive,				      \
+		.max_drive = _max_drive,				      \
+	}
+
+/*
+ * Functions in bios_disks.c
+ *
+ */
+
+
+/*
+ * bios_disk bus global definition
+ *
+ */
+extern struct bus_driver bios_disk_driver;
+
+#endif /* BIOS_DISKS_H */
diff --git a/gpxe/src/arch/i386/include/biosint.h b/gpxe/src/arch/i386/include/biosint.h
new file mode 100644
index 0000000..ab466af
--- /dev/null
+++ b/gpxe/src/arch/i386/include/biosint.h
@@ -0,0 +1,33 @@
+#ifndef BIOSINT_H
+#define BIOSINT_H
+
+/**
+ * @file BIOS interrupts
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <realmode.h>
+
+struct segoff;
+
+/**
+ * Hooked interrupt count
+ *
+ * At exit, after unhooking all possible interrupts, this counter
+ * should be examined.  If it is non-zero, it means that we failed to
+ * unhook at least one interrupt vector, and so must not free up the
+ * memory we are using.  (Note that this also implies that we should
+ * re-hook INT 15 in order to hide ourselves from the memory map).
+ */
+extern uint16_t __text16 ( hooked_bios_interrupts );
+#define hooked_bios_interrupts __use_text16 ( hooked_bios_interrupts )
+
+extern void hook_bios_interrupt ( unsigned int interrupt, unsigned int handler,
+				  struct segoff *chain_vector );
+extern int unhook_bios_interrupt ( unsigned int interrupt,
+				   unsigned int handler,
+				   struct segoff *chain_vector );
+
+#endif /* BIOSINT_H */
diff --git a/gpxe/src/arch/i386/include/bits/byteswap.h b/gpxe/src/arch/i386/include/bits/byteswap.h
new file mode 100644
index 0000000..ddbd40e
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bits/byteswap.h
@@ -0,0 +1,43 @@
+#ifndef ETHERBOOT_BITS_BYTESWAP_H
+#define ETHERBOOT_BITS_BYTESWAP_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+static inline __attribute__ ((always_inline, const)) uint16_t
+__bswap_variable_16(uint16_t x)
+{
+	__asm__("xchgb %b0,%h0\n\t"
+		: "=q" (x)
+		: "0" (x));
+	return x;
+}
+
+static inline __attribute__ ((always_inline, const)) uint32_t
+__bswap_variable_32(uint32_t x)
+{
+	__asm__("xchgb %b0,%h0\n\t"
+		"rorl $16,%0\n\t"
+		"xchgb %b0,%h0"
+		: "=q" (x)
+		: "0" (x));
+	return x;
+}
+
+static inline __attribute__ ((always_inline, const)) uint64_t
+__bswap_variable_64(uint64_t x)
+{
+	union {
+		uint64_t qword;
+		uint32_t dword[2]; 
+	} u;
+
+	u.qword = x;
+	u.dword[0] = __bswap_variable_32(u.dword[0]);
+	u.dword[1] = __bswap_variable_32(u.dword[1]);
+	__asm__("xchgl %0,%1"
+		: "=r" ( u.dword[0] ), "=r" ( u.dword[1] )
+		: "0" ( u.dword[0] ), "1" ( u.dword[1] ) );
+	return u.qword;
+}
+
+#endif /* ETHERBOOT_BITS_BYTESWAP_H */
diff --git a/gpxe/src/arch/i386/include/bits/compiler.h b/gpxe/src/arch/i386/include/bits/compiler.h
new file mode 100644
index 0000000..000db0c
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bits/compiler.h
@@ -0,0 +1,27 @@
+#ifndef _BITS_COMPILER_H
+#define _BITS_COMPILER_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#ifndef ASSEMBLY
+
+/** Declare a function with standard calling conventions */
+#define __asmcall __attribute__ (( cdecl, regparm(0) ))
+
+/**
+ * Declare a function with libgcc implicit linkage
+ *
+ * It seems as though gcc expects its implicit arithmetic functions to
+ * be cdecl, even if -mrtd is specified.  This is somewhat
+ * inconsistent; for example, if -mregparm=3 is used then the implicit
+ * functions do become regparm(3).
+ *
+ * The implicit calls to memcpy() and memset() which gcc can generate
+ * do not seem to have this inconsistency; -mregparm and -mrtd affect
+ * them in the same way as any other function.
+ */
+#define __libgcc __attribute__ (( cdecl ))
+
+#endif /* ASSEMBLY */
+
+#endif /* _BITS_COMPILER_H */
diff --git a/gpxe/src/arch/i386/include/bits/cpu.h b/gpxe/src/arch/i386/include/bits/cpu.h
new file mode 100644
index 0000000..83339dd
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bits/cpu.h
@@ -0,0 +1,86 @@
+#ifndef I386_BITS_CPU_H
+#define I386_BITS_CPU_H
+
+/* Intel-defined CPU features, CPUID level 0x00000001, word 0 */
+#define X86_FEATURE_FPU		0 /* Onboard FPU */
+#define X86_FEATURE_VME		1 /* Virtual Mode Extensions */
+#define X86_FEATURE_DE		2 /* Debugging Extensions */
+#define X86_FEATURE_PSE 	3 /* Page Size Extensions */
+#define X86_FEATURE_TSC		4 /* Time Stamp Counter */
+#define X86_FEATURE_MSR		5 /* Model-Specific Registers, RDMSR, WRMSR */
+#define X86_FEATURE_PAE		6 /* Physical Address Extensions */
+#define X86_FEATURE_MCE		7 /* Machine Check Architecture */
+#define X86_FEATURE_CX8		8 /* CMPXCHG8 instruction */
+#define X86_FEATURE_APIC	9 /* Onboard APIC */
+#define X86_FEATURE_SEP		11 /* SYSENTER/SYSEXIT */
+#define X86_FEATURE_MTRR	12 /* Memory Type Range Registers */
+#define X86_FEATURE_PGE		13 /* Page Global Enable */
+#define X86_FEATURE_MCA		14 /* Machine Check Architecture */
+#define X86_FEATURE_CMOV	15 /* CMOV instruction (FCMOVCC and FCOMI too if FPU present) */
+#define X86_FEATURE_PAT		16 /* Page Attribute Table */
+#define X86_FEATURE_PSE36	17 /* 36-bit PSEs */
+#define X86_FEATURE_PN		18 /* Processor serial number */
+#define X86_FEATURE_CLFLSH	19 /* Supports the CLFLUSH instruction */
+#define X86_FEATURE_DTES	21 /* Debug Trace Store */
+#define X86_FEATURE_ACPI	22 /* ACPI via MSR */
+#define X86_FEATURE_MMX		23 /* Multimedia Extensions */
+#define X86_FEATURE_FXSR	24 /* FXSAVE and FXRSTOR instructions (fast save and restore */
+				          /* of FPU context), and CR4.OSFXSR available */
+#define X86_FEATURE_XMM		25 /* Streaming SIMD Extensions */
+#define X86_FEATURE_XMM2	26 /* Streaming SIMD Extensions-2 */
+#define X86_FEATURE_SELFSNOOP	27 /* CPU self snoop */
+#define X86_FEATURE_HT		28 /* Hyper-Threading */
+#define X86_FEATURE_ACC		29 /* Automatic clock control */
+#define X86_FEATURE_IA64	30 /* IA-64 processor */
+
+/* AMD-defined CPU features, CPUID level 0x80000001, word 1 */
+/* Don't duplicate feature flags which are redundant with Intel! */
+#define X86_FEATURE_SYSCALL	11 /* SYSCALL/SYSRET */
+#define X86_FEATURE_MMXEXT	22 /* AMD MMX extensions */
+#define X86_FEATURE_LM		29 /* Long Mode (x86-64) */
+#define X86_FEATURE_3DNOWEXT	30 /* AMD 3DNow! extensions */
+#define X86_FEATURE_3DNOW	31 /* 3DNow! */
+
+/** x86 CPU information */
+struct cpuinfo_x86 {
+	/** CPU features */
+	unsigned int features;
+	/** 64-bit CPU features */
+	unsigned int amd_features;
+};
+
+/*
+ * EFLAGS bits
+ */
+#define X86_EFLAGS_CF	0x00000001 /* Carry Flag */
+#define X86_EFLAGS_PF	0x00000004 /* Parity Flag */
+#define X86_EFLAGS_AF	0x00000010 /* Auxillary carry Flag */
+#define X86_EFLAGS_ZF	0x00000040 /* Zero Flag */
+#define X86_EFLAGS_SF	0x00000080 /* Sign Flag */
+#define X86_EFLAGS_TF	0x00000100 /* Trap Flag */
+#define X86_EFLAGS_IF	0x00000200 /* Interrupt Flag */
+#define X86_EFLAGS_DF	0x00000400 /* Direction Flag */
+#define X86_EFLAGS_OF	0x00000800 /* Overflow Flag */
+#define X86_EFLAGS_IOPL	0x00003000 /* IOPL mask */
+#define X86_EFLAGS_NT	0x00004000 /* Nested Task */
+#define X86_EFLAGS_RF	0x00010000 /* Resume Flag */
+#define X86_EFLAGS_VM	0x00020000 /* Virtual Mode */
+#define X86_EFLAGS_AC	0x00040000 /* Alignment Check */
+#define X86_EFLAGS_VIF	0x00080000 /* Virtual Interrupt Flag */
+#define X86_EFLAGS_VIP	0x00100000 /* Virtual Interrupt Pending */
+#define X86_EFLAGS_ID	0x00200000 /* CPUID detection flag */
+
+/*
+ * Generic CPUID function
+ */
+static inline __attribute__ (( always_inline )) void
+cpuid ( int op, unsigned int *eax, unsigned int *ebx,
+	unsigned int *ecx, unsigned int *edx ) {
+	__asm__ ( "cpuid" :
+		  "=a" ( *eax ), "=b" ( *ebx ), "=c" ( *ecx ), "=d" ( *edx )
+		: "0" ( op ) );
+}
+
+extern void get_cpuinfo ( struct cpuinfo_x86 *cpu );
+
+#endif /* I386_BITS_CPU_H */
diff --git a/gpxe/src/arch/i386/include/bits/eltorito.h b/gpxe/src/arch/i386/include/bits/eltorito.h
new file mode 100644
index 0000000..d43e9aa
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bits/eltorito.h
@@ -0,0 +1,3 @@
+#ifndef ELTORITO_PLATFORM
+#define ELTORITO_PLATFORM ELTORITO_PLATFORM_X86
+#endif /* ELTORITO_PLATFORM */
diff --git a/gpxe/src/arch/i386/include/bits/endian.h b/gpxe/src/arch/i386/include/bits/endian.h
new file mode 100644
index 0000000..8418854
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bits/endian.h
@@ -0,0 +1,8 @@
+#ifndef ETHERBOOT_BITS_ENDIAN_H
+#define ETHERBOOT_BITS_ENDIAN_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#define __BYTE_ORDER __LITTLE_ENDIAN
+
+#endif /* ETHERBOOT_BITS_ENDIAN_H */
diff --git a/gpxe/src/arch/i386/include/bits/errfile.h b/gpxe/src/arch/i386/include/bits/errfile.h
new file mode 100644
index 0000000..32b8a08
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bits/errfile.h
@@ -0,0 +1,42 @@
+#ifndef _BITS_ERRFILE_H
+#define _BITS_ERRFILE_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/**
+ * @addtogroup errfile Error file identifiers
+ * @{
+ */
+
+#define ERRFILE_memtop_umalloc	( ERRFILE_ARCH | ERRFILE_CORE | 0x00000000 )
+#define ERRFILE_memmap		( ERRFILE_ARCH | ERRFILE_CORE | 0x00010000 )
+#define ERRFILE_pnpbios		( ERRFILE_ARCH | ERRFILE_CORE | 0x00020000 )
+#define ERRFILE_bios_smbios	( ERRFILE_ARCH | ERRFILE_CORE | 0x00030000 )
+#define ERRFILE_biosint		( ERRFILE_ARCH | ERRFILE_CORE | 0x00040000 )
+#define ERRFILE_int13		( ERRFILE_ARCH | ERRFILE_CORE | 0x00050000 )
+#define ERRFILE_pxeparent	( ERRFILE_ARCH | ERRFILE_CORE | 0x00060000 )
+
+#define ERRFILE_bootsector     ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00000000 )
+#define ERRFILE_bzimage	       ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00010000 )
+#define ERRFILE_eltorito       ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00020000 )
+#define ERRFILE_multiboot      ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00030000 )
+#define ERRFILE_nbi	       ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00040000 )
+#define ERRFILE_pxe_image      ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00050000 )
+#define ERRFILE_elfboot	       ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00060000 )
+#define ERRFILE_comboot        ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00070000 )
+#define ERRFILE_com32          ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00080000 )
+#define ERRFILE_comboot_resolv ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00090000 )
+#define ERRFILE_comboot_call   ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x000a0000 )
+
+#define ERRFILE_undi		 ( ERRFILE_ARCH | ERRFILE_NET | 0x00000000 )
+#define ERRFILE_undiload	 ( ERRFILE_ARCH | ERRFILE_NET | 0x00010000 )
+#define ERRFILE_undinet		 ( ERRFILE_ARCH | ERRFILE_NET | 0x00020000 )
+#define ERRFILE_undionly	 ( ERRFILE_ARCH | ERRFILE_NET | 0x00030000 )
+#define ERRFILE_undirom		 ( ERRFILE_ARCH | ERRFILE_NET | 0x00040000 )
+
+#define ERRFILE_timer_rdtsc    ( ERRFILE_ARCH | ERRFILE_DRIVER | 0x00000000 )
+#define ERRFILE_timer_bios     ( ERRFILE_ARCH | ERRFILE_DRIVER | 0x00010000 )
+
+/** @} */
+
+#endif /* _BITS_ERRFILE_H */
diff --git a/gpxe/src/arch/i386/include/bits/io.h b/gpxe/src/arch/i386/include/bits/io.h
new file mode 100644
index 0000000..eded977
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bits/io.h
@@ -0,0 +1,14 @@
+#ifndef _BITS_IO_H
+#define _BITS_IO_H
+
+/** @file
+ *
+ * i386-specific I/O API implementations
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <gpxe/x86_io.h>
+
+#endif /* _BITS_IO_H */
diff --git a/gpxe/src/arch/i386/include/bits/nap.h b/gpxe/src/arch/i386/include/bits/nap.h
new file mode 100644
index 0000000..1354f6b
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bits/nap.h
@@ -0,0 +1,15 @@
+#ifndef _BITS_NAP_H
+#define _BITS_NAP_H
+
+/** @file
+ *
+ * i386-specific CPU sleeping API implementations
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <gpxe/bios_nap.h>
+#include <gpxe/efi/efix86_nap.h>
+
+#endif /* _BITS_MAP_H */
diff --git a/gpxe/src/arch/i386/include/bits/smbios.h b/gpxe/src/arch/i386/include/bits/smbios.h
new file mode 100644
index 0000000..a68413a
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bits/smbios.h
@@ -0,0 +1,14 @@
+#ifndef _BITS_SMBIOS_H
+#define _BITS_SMBIOS_H
+
+/** @file
+ *
+ * i386-specific SMBIOS API implementations
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <gpxe/bios_smbios.h>
+
+#endif /* _BITS_SMBIOS_H */
diff --git a/gpxe/src/arch/i386/include/bits/stdint.h b/gpxe/src/arch/i386/include/bits/stdint.h
new file mode 100644
index 0000000..8edf131
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bits/stdint.h
@@ -0,0 +1,23 @@
+#ifndef _BITS_STDINT_H
+#define _BITS_STDINT_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+typedef __SIZE_TYPE__		size_t;
+typedef signed long		ssize_t;
+typedef signed long		off_t;
+
+typedef unsigned char		uint8_t;
+typedef unsigned short		uint16_t;
+typedef unsigned int		uint32_t;
+typedef unsigned long long	uint64_t;
+
+typedef signed char		int8_t;
+typedef signed short		int16_t;
+typedef signed int		int32_t;
+typedef signed long long	int64_t;
+
+typedef unsigned long		physaddr_t;
+typedef unsigned long		intptr_t;
+
+#endif /* _BITS_STDINT_H */
diff --git a/gpxe/src/arch/i386/include/bits/timer.h b/gpxe/src/arch/i386/include/bits/timer.h
new file mode 100644
index 0000000..32e6bd4
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bits/timer.h
@@ -0,0 +1,15 @@
+#ifndef _BITS_TIMER_H
+#define _BITS_TIMER_H
+
+/** @file
+ *
+ * i386-specific timer API implementations
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <gpxe/bios_timer.h>
+#include <gpxe/rdtsc_timer.h>
+
+#endif /* _BITS_TIMER_H */
diff --git a/gpxe/src/arch/i386/include/bits/uaccess.h b/gpxe/src/arch/i386/include/bits/uaccess.h
new file mode 100644
index 0000000..2bb52e0
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bits/uaccess.h
@@ -0,0 +1,14 @@
+#ifndef _BITS_UACCESS_H
+#define _BITS_UACCESS_H
+
+/** @file
+ *
+ * i386-specific user access API implementations
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <librm.h>
+
+#endif /* _BITS_UACCESS_H */
diff --git a/gpxe/src/arch/i386/include/bits/umalloc.h b/gpxe/src/arch/i386/include/bits/umalloc.h
new file mode 100644
index 0000000..17ba2cb
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bits/umalloc.h
@@ -0,0 +1,14 @@
+#ifndef _BITS_UMALLOC_H
+#define _BITS_UMALLOC_H
+
+/** @file
+ *
+ * i386-specific user memory allocation API implementations
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <gpxe/memtop_umalloc.h>
+
+#endif /* _BITS_UMALLOC_H */
diff --git a/gpxe/src/arch/i386/include/bochs.h b/gpxe/src/arch/i386/include/bochs.h
new file mode 100644
index 0000000..9d090fc
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bochs.h
@@ -0,0 +1,34 @@
+#ifndef BOCHS_H
+#define BOCHS_H
+
+/** @file
+ *
+ * bochs breakpoints
+ *
+ * This file defines @c bochsbp, the magic breakpoint instruction that
+ * is incredibly useful when debugging under bochs.  This file should
+ * never be included in production code.
+ *
+ * Use the pseudo-instruction @c bochsbp in assembly code, or the
+ * bochsbp() function in C code.
+ *
+ */
+
+#ifdef ASSEMBLY
+
+/* Breakpoint for when debugging under bochs */
+#define bochsbp xchgw %bx, %bx
+#define BOCHSBP bochsbp
+
+#else /* ASSEMBLY */
+
+/** Breakpoint for when debugging under bochs */
+static inline void bochsbp ( void ) {
+	__asm__ __volatile__ ( "xchgw %bx, %bx" );
+}
+
+#endif /* ASSEMBLY */
+
+#warning "bochs.h should not be included into production code"
+
+#endif /* BOCHS_H */
diff --git a/gpxe/src/arch/i386/include/bootsector.h b/gpxe/src/arch/i386/include/bootsector.h
new file mode 100644
index 0000000..8730fbf
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bootsector.h
@@ -0,0 +1,14 @@
+#ifndef _BOOTSECTOR_H
+#define _BOOTSECTOR_H
+
+/** @file
+ *
+ * x86 bootsector image format
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+extern int call_bootsector ( unsigned int segment, unsigned int offset,
+			     unsigned int drive );
+
+#endif /* _BOOTSECTOR_H */
diff --git a/gpxe/src/arch/i386/include/bzimage.h b/gpxe/src/arch/i386/include/bzimage.h
new file mode 100644
index 0000000..42b31fe
--- /dev/null
+++ b/gpxe/src/arch/i386/include/bzimage.h
@@ -0,0 +1,142 @@
+#ifndef _BZIMAGE_H
+#define _BZIMAGE_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+
+/**
+ * A bzImage header
+ *
+ * As documented in Documentation/i386/boot.txt
+ */
+struct bzimage_header {
+	/** The size of the setup in sectors
+	 *
+	 * If this field contains 0, assume it contains 4.
+	 */
+	uint8_t setup_sects;
+	/** If set, the root is mounted readonly */
+	uint16_t root_flags;
+	/** DO NOT USE - for bootsect.S use only */
+	uint16_t syssize;
+	/** DO NOT USE - obsolete */
+	uint16_t swap_dev;
+	/** DO NOT USE - for bootsect.S use only */
+	uint16_t ram_size;
+	/** Video mode control */
+	uint16_t vid_mode;
+	/** Default root device number */
+	uint16_t root_dev;
+	/** 0xAA55 magic number */
+	uint16_t boot_flag;
+	/** Jump instruction */
+	uint16_t jump;
+	/** Magic signature "HdrS" */
+	uint32_t header;
+	/** Boot protocol version supported */
+	uint16_t version;
+	/** Boot loader hook (see below) */
+	uint32_t realmode_swtch;
+	/** The load-low segment (0x1000) (obsolete) */
+	uint16_t start_sys;
+	/** Pointer to kernel version string */
+	uint16_t kernel_version;
+	/** Boot loader identifier */
+	uint8_t type_of_loader;
+	/** Boot protocol option flags */
+	uint8_t loadflags;
+	/** Move to high memory size (used with hooks) */
+	uint16_t setup_move_size;
+	/** Boot loader hook (see below) */
+	uint32_t code32_start;
+	/** initrd load address (set by boot loader) */
+	uint32_t ramdisk_image;
+	/** initrd size (set by boot loader) */
+	uint32_t ramdisk_size;
+	/** DO NOT USE - for bootsect.S use only */
+	uint32_t bootsect_kludge;
+	/** Free memory after setup end */
+	uint16_t heap_end_ptr;
+	/** Unused */
+	uint16_t pad1;
+	/** 32-bit pointer to the kernel command line */
+	uint32_t cmd_line_ptr;
+	/** Highest legal initrd address */
+	uint32_t initrd_addr_max;
+	/** Physical addr alignment required for kernel	*/
+	uint32_t kernel_alignment;
+	/** Whether kernel is relocatable or not */
+	uint8_t relocatable_kernel;
+	/** Unused */
+	uint8_t pad2[3];
+	/** Maximum size of the kernel command line */
+	uint32_t cmdline_size;
+} __attribute__ (( packed ));
+
+/** Offset of bzImage header within kernel image */
+#define BZI_HDR_OFFSET 0x1f1
+
+/** bzImage boot flag value */
+#define BZI_BOOT_FLAG 0xaa55
+
+/** bzImage magic signature value */
+#define BZI_SIGNATURE 0x53726448
+
+/** bzImage boot loader identifier for Etherboot */
+#define BZI_LOADER_TYPE_ETHERBOOT 0x40
+
+/** bzImage boot loader identifier for gPXE
+ *
+ * We advertise ourselves as Etherboot version 6.
+ */
+#define BZI_LOADER_TYPE_GPXE ( BZI_LOADER_TYPE_ETHERBOOT | 0x06 )
+
+/** bzImage "load high" flag */
+#define BZI_LOAD_HIGH 0x01
+
+/** Load address for high-loaded kernels */
+#define BZI_LOAD_HIGH_ADDR 0x100000
+
+/** Load address for low-loaded kernels */
+#define BZI_LOAD_LOW_ADDR 0x10000
+
+/** bzImage "kernel can use heap" flag */
+#define BZI_CAN_USE_HEAP 0x80
+
+/** bzImage special video mode "normal" */
+#define BZI_VID_MODE_NORMAL 0xffff
+
+/** bzImage special video mode "ext" */
+#define BZI_VID_MODE_EXT 0xfffe
+
+/** bzImage special video mode "ask" */
+#define BZI_VID_MODE_ASK 0xfffd
+
+/** bzImage maximum initrd address for versions < 2.03 */
+#define BZI_INITRD_MAX 0x37ffffff
+
+/** bzImage command-line structure used by older kernels */
+struct bzimage_cmdline {
+	/** Magic signature */
+	uint16_t magic;
+	/** Offset to command line */
+	uint16_t offset;
+} __attribute__ (( packed ));
+
+/** Offset of bzImage command-line structure within kernel image */
+#define BZI_CMDLINE_OFFSET 0x20
+
+/** bzImage command line present magic marker value */
+#define BZI_CMDLINE_MAGIC 0xa33f
+
+/** Assumed size of real-mode portion (including .bss) */
+#define BZI_ASSUMED_RM_SIZE 0x8000
+
+/** Amount of stack space to provide */
+#define BZI_STACK_SIZE 0x1000
+
+/** Maximum size of command line */
+#define BZI_CMDLINE_SIZE 0x100
+
+#endif /* _BZIMAGE_H */
diff --git a/gpxe/src/arch/i386/include/comboot.h b/gpxe/src/arch/i386/include/comboot.h
new file mode 100644
index 0000000..1232f0a
--- /dev/null
+++ b/gpxe/src/arch/i386/include/comboot.h
@@ -0,0 +1,135 @@
+#ifndef COMBOOT_H
+#define COMBOOT_H
+
+/**
+ * @file
+ *
+ * SYSLINUX COMBOOT
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <setjmp.h>
+#include <gpxe/in.h>
+
+/** Segment used for COMBOOT PSP and image */
+#define COMBOOT_PSP_SEG 0x07C0
+
+/** Entry point address of COM32 images */
+#define COM32_START_PHYS 0x101000
+
+/** COM32 bounce buffer segment */
+#define COM32_BOUNCE_SEG 0x07C0
+
+/** Size of SYSLINUX file block in bytes */
+#define COMBOOT_FILE_BLOCKSZ 512
+
+/** COMBOOT feature flags (INT 22h AX=15h) */
+#define COMBOOT_FEATURE_LOCAL_BOOT (1 << 0)
+#define COMBOOT_FEATURE_IDLE_LOOP  (1 << 1)
+
+/** Maximum number of shuffle descriptors for 
+ * shuffle and boot functions
+ * (INT 22h AX=0012h, 001Ah, 001Bh)
+ */
+#define COMBOOT_MAX_SHUFFLE_DESCRIPTORS 682
+
+typedef union {
+	uint32_t l;
+	uint16_t w[2];
+	uint8_t  b[4];
+} com32_reg32_t;
+
+typedef struct {
+	uint16_t gs;                /* Offset  0 */
+	uint16_t fs;                /* Offset  2 */
+	uint16_t es;                /* Offset  4 */
+	uint16_t ds;                /* Offset  6 */
+
+	com32_reg32_t edi;          /* Offset  8 */
+	com32_reg32_t esi;          /* Offset 12 */
+	com32_reg32_t ebp;          /* Offset 16 */
+	com32_reg32_t _unused_esp;  /* Offset 20 */
+	com32_reg32_t ebx;          /* Offset 24 */
+	com32_reg32_t edx;          /* Offset 28 */
+	com32_reg32_t ecx;          /* Offset 32 */
+	com32_reg32_t eax;          /* Offset 36 */
+
+	com32_reg32_t eflags;       /* Offset 40 */
+} com32sys_t;
+
+typedef struct {
+	uint32_t eax;               /* Offset  0 */
+	uint32_t ecx;               /* Offset  4 */
+	uint32_t edx;               /* Offset  8 */
+	uint32_t ebx;               /* Offset 12 */
+	uint32_t esp;               /* Offset 16 */
+	uint32_t ebp;               /* Offset 20 */
+	uint32_t esi;               /* Offset 24 */
+	uint32_t edi;               /* Offset 28 */
+
+	uint32_t eip;               /* Offset 32 */
+} syslinux_pm_regs;
+
+typedef struct {
+	uint16_t es;                /* Offset  0 */
+	uint16_t _unused_cs;        /* Offset  2 */
+	uint16_t ds;                /* Offset  4 */
+	uint16_t ss;                /* Offset  6 */
+	uint16_t fs;                /* Offset  8 */
+	uint16_t gs;                /* Offset 10 */
+
+	uint32_t eax;                /* Offset 12 */
+	uint32_t ecx;                /* Offset 16 */
+	uint32_t edx;                /* Offset 20 */
+	uint32_t ebx;                /* Offset 24 */
+	uint32_t esp;                /* Offset 28 */
+	uint32_t ebp;                /* Offset 32 */
+	uint32_t esi;                /* Offset 36 */
+	uint32_t edi;                /* Offset 40 */
+
+	uint16_t ip;                /* Offset 44 */
+	uint16_t cs;                /* Offset 46 */
+} syslinux_rm_regs;
+
+typedef struct {
+	uint32_t dest;
+	uint32_t src;
+	uint32_t len;
+} comboot_shuffle_descriptor;
+
+extern void hook_comboot_interrupts ( );
+extern void unhook_comboot_interrupts ( );
+
+/* These are not the correct prototypes, but it doens't matter, 
+ * as we only ever get the address of these functions;
+ * they are only called from COM32 code running in PHYS_CODE
+ */
+extern void com32_intcall_wrapper ( );
+extern void com32_farcall_wrapper ( );
+extern void com32_cfarcall_wrapper ( );
+
+/* Resolve a hostname to an (IPv4) address */
+extern int comboot_resolv ( const char *name, struct in_addr *address );
+
+/* setjmp/longjmp context buffer used to return after loading an image */
+extern rmjmp_buf comboot_return;
+
+/* Replacement image when exiting with COMBOOT_EXIT_RUN_KERNEL */
+extern struct image *comboot_replacement_image;
+
+extern void *com32_external_esp;
+
+#define COMBOOT_EXIT 1
+#define COMBOOT_EXIT_RUN_KERNEL 2
+#define COMBOOT_EXIT_COMMAND 3
+
+extern void comboot_force_text_mode ( void );
+
+#define COMBOOT_VIDEO_GRAPHICS    0x01
+#define COMBOOT_VIDEO_NONSTANDARD 0x02
+#define COMBOOT_VIDEO_VESA        0x04
+#define COMBOOT_VIDEO_NOTEXT      0x08
+
+#endif
diff --git a/gpxe/src/arch/i386/include/fakee820.h b/gpxe/src/arch/i386/include/fakee820.h
new file mode 100644
index 0000000..9d00fb6
--- /dev/null
+++ b/gpxe/src/arch/i386/include/fakee820.h
@@ -0,0 +1,9 @@
+#ifndef _FAKEE820_H
+#define _FAKEE820_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+extern void fake_e820 ( void );
+extern void unfake_e820 ( void );
+
+#endif /* _FAKEE820_H */
diff --git a/gpxe/src/arch/i386/include/gateA20.h b/gpxe/src/arch/i386/include/gateA20.h
new file mode 100644
index 0000000..297ad6f
--- /dev/null
+++ b/gpxe/src/arch/i386/include/gateA20.h
@@ -0,0 +1,7 @@
+#ifndef GATEA20_H
+#define GATEA20_H
+
+extern void gateA20_set ( void );
+extern void gateA20_unset ( void );
+
+#endif /* GATEA20_H */
diff --git a/gpxe/src/arch/i386/include/gdbmach.h b/gpxe/src/arch/i386/include/gdbmach.h
new file mode 100644
index 0000000..794dab1
--- /dev/null
+++ b/gpxe/src/arch/i386/include/gdbmach.h
@@ -0,0 +1,64 @@
+#ifndef GDBMACH_H
+#define GDBMACH_H
+
+/** @file
+ *
+ * GDB architecture specifics
+ *
+ * This file declares functions for manipulating the machine state and
+ * debugging context.
+ *
+ */
+
+#include <stdint.h>
+
+typedef unsigned long gdbreg_t;
+
+/* The register snapshot, this must be in sync with interrupt handler and the
+ * GDB protocol. */
+enum {
+	GDBMACH_EAX,
+	GDBMACH_ECX,
+	GDBMACH_EDX,
+	GDBMACH_EBX,
+	GDBMACH_ESP,
+	GDBMACH_EBP,
+	GDBMACH_ESI,
+	GDBMACH_EDI,
+	GDBMACH_EIP,
+	GDBMACH_EFLAGS,
+	GDBMACH_CS,
+	GDBMACH_SS,
+	GDBMACH_DS,
+	GDBMACH_ES,
+	GDBMACH_FS,
+	GDBMACH_GS,
+	GDBMACH_NREGS,
+	GDBMACH_SIZEOF_REGS = GDBMACH_NREGS * sizeof ( gdbreg_t )
+};
+
+/* Breakpoint types */
+enum {
+	GDBMACH_BPMEM,
+	GDBMACH_BPHW,
+	GDBMACH_WATCH,
+	GDBMACH_RWATCH,
+	GDBMACH_AWATCH,
+};
+
+static inline void gdbmach_set_pc ( gdbreg_t *regs, gdbreg_t pc ) {
+	regs [ GDBMACH_EIP ] = pc;
+}
+
+static inline void gdbmach_set_single_step ( gdbreg_t *regs, int step ) {
+	regs [ GDBMACH_EFLAGS ] &= ~( 1 << 8 ); /* Trace Flag (TF) */
+	regs [ GDBMACH_EFLAGS ] |= ( step << 8 );
+}
+
+static inline void gdbmach_breakpoint ( void ) {
+	__asm__ __volatile__ ( "int $3\n" );
+}
+
+extern int gdbmach_set_breakpoint ( int type, unsigned long addr, size_t len, int enable );
+
+#endif /* GDBMACH_H */
diff --git a/gpxe/src/arch/i386/include/gpxe/abft.h b/gpxe/src/arch/i386/include/gpxe/abft.h
new file mode 100644
index 0000000..9065e61
--- /dev/null
+++ b/gpxe/src/arch/i386/include/gpxe/abft.h
@@ -0,0 +1,37 @@
+#ifndef _GPXE_ABFT_H
+#define _GPXE_ABFT_H
+
+/** @file
+ *
+ * AoE boot firmware table
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <gpxe/acpi.h>
+#include <gpxe/if_ether.h>
+
+/** AoE boot firmware table signature */
+#define ABFT_SIG "aBFT"
+
+/**
+ * AoE Boot Firmware Table (aBFT)
+ */
+struct abft_table {
+	/** ACPI header */
+	struct acpi_description_header acpi;
+	/** AoE shelf */
+	uint16_t shelf;
+	/** AoE slot */
+	uint8_t slot;
+	/** Reserved */
+	uint8_t reserved_a;
+	/** MAC address */
+	uint8_t mac[ETH_ALEN];
+} __attribute__ (( packed ));
+
+extern void abft_fill_data ( struct aoe_session *aoe );
+
+#endif /* _GPXE_ABFT_H */
diff --git a/gpxe/src/arch/i386/include/gpxe/bios_nap.h b/gpxe/src/arch/i386/include/gpxe/bios_nap.h
new file mode 100644
index 0000000..c32429b
--- /dev/null
+++ b/gpxe/src/arch/i386/include/gpxe/bios_nap.h
@@ -0,0 +1,18 @@
+#ifndef _GPXE_BIOS_NAP_H
+#define _GPXE_BIOS_NAP_H
+
+/** @file
+ *
+ * BIOS CPU sleeping
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#ifdef NAP_PCBIOS
+#define NAP_PREFIX_pcbios
+#else
+#define NAP_PREFIX_pcbios __pcbios_
+#endif
+
+#endif /* _GPXE_BIOS_NAP_H */
diff --git a/gpxe/src/arch/i386/include/gpxe/bios_smbios.h b/gpxe/src/arch/i386/include/gpxe/bios_smbios.h
new file mode 100644
index 0000000..83726b1
--- /dev/null
+++ b/gpxe/src/arch/i386/include/gpxe/bios_smbios.h
@@ -0,0 +1,18 @@
+#ifndef _GPXE_BIOS_SMBIOS_H
+#define _GPXE_BIOS_SMBIOS_H
+
+/** @file
+ *
+ * Standard PC-BIOS SMBIOS interface
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#ifdef SMBIOS_PCBIOS
+#define SMBIOS_PREFIX_pcbios
+#else
+#define SMBIOS_PREFIX_pcbios __pcbios_
+#endif
+
+#endif /* _GPXE_BIOS_SMBIOS_H */
diff --git a/gpxe/src/arch/i386/include/gpxe/bios_timer.h b/gpxe/src/arch/i386/include/gpxe/bios_timer.h
new file mode 100644
index 0000000..ed9df52
--- /dev/null
+++ b/gpxe/src/arch/i386/include/gpxe/bios_timer.h
@@ -0,0 +1,44 @@
+#ifndef _GPXE_BIOS_TIMER_H
+#define _GPXE_BIOS_TIMER_H
+
+/** @file
+ *
+ * BIOS timer
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#ifdef TIMER_PCBIOS
+#define TIMER_PREFIX_pcbios
+#else
+#define TIMER_PREFIX_pcbios __pcbios_
+#endif
+
+#include <gpxe/timer2.h>
+
+/**
+ * Delay for a fixed number of microseconds
+ *
+ * @v usecs		Number of microseconds for which to delay
+ */
+static inline __always_inline void
+TIMER_INLINE ( pcbios, udelay ) ( unsigned long usecs ) {
+	/* BIOS timer is not high-resolution enough for udelay(), so
+	 * we use timer2
+	 */
+	timer2_udelay ( usecs );
+}
+
+/**
+ * Get number of ticks per second
+ *
+ * @ret ticks_per_sec	Number of ticks per second
+ */
+static inline __always_inline unsigned long
+TIMER_INLINE ( pcbios, ticks_per_sec ) ( void ) {
+	/* BIOS timer ticks over at 18.2 ticks per second */
+	return 18;
+}
+
+#endif /* _GPXE_BIOS_TIMER_H */
diff --git a/gpxe/src/arch/i386/include/gpxe/ibft.h b/gpxe/src/arch/i386/include/gpxe/ibft.h
new file mode 100644
index 0000000..c41e2e4
--- /dev/null
+++ b/gpxe/src/arch/i386/include/gpxe/ibft.h
@@ -0,0 +1,302 @@
+#ifndef _GPXE_IBFT_H
+#define _GPXE_IBFT_H
+
+/*
+ * Copyright Fen Systems Ltd. 2007.  Portions of this code are derived
+ * from IBM Corporation Sample Programs.  Copyright IBM Corporation
+ * 2004, 2007.  All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+FILE_LICENCE ( BSD2 );
+
+/** @file
+ *
+ * iSCSI boot firmware table
+ *
+ * The information in this file is derived from the document "iSCSI
+ * Boot Firmware Table (iBFT)" as published by IBM at
+ *
+ * ftp://ftp.software.ibm.com/systems/support/system_x_pdf/ibm_iscsi_boot_firmware_table_v1.02.pdf
+ *
+ */
+
+#include <stdint.h>
+#include <gpxe/acpi.h>
+#include <gpxe/in.h>
+
+/** iSCSI Boot Firmware Table signature */
+#define IBFT_SIG "iBFT"
+
+/** An offset from the start of the iBFT */
+typedef uint16_t ibft_off_t;
+
+/** Length of a string within the iBFT (excluding terminating NUL) */
+typedef uint16_t ibft_size_t;
+
+/** A string within the iBFT */
+struct ibft_string {
+	/** Length of string */
+	ibft_size_t length;
+	/** Offset to string */
+	ibft_off_t offset;
+} __attribute__ (( packed ));
+
+/** An IP address within the iBFT */
+struct ibft_ipaddr {
+	/** Reserved; must be zero */
+	uint16_t zeroes[5];
+	/** Must be 0xffff if IPv4 address is present, otherwise zero */
+	uint16_t ones;
+	/** The IPv4 address, or zero if not present */
+	struct in_addr in;
+} __attribute__ (( packed ));
+
+/**
+ * iBFT structure header
+ *
+ * This structure is common to several sections within the iBFT.
+ */
+struct ibft_header {
+	/** Structure ID
+	 *
+	 * This is an IBFT_STRUCTURE_ID_XXX constant
+	 */
+	uint8_t structure_id;
+	/** Version (always 1) */
+	uint8_t version;
+	/** Length, including this header */
+	uint16_t length;
+	/** Index 
+	 *
+	 * This is the number of the NIC or Target, when applicable.
+	 */
+	uint8_t index;
+	/** Flags */
+	uint8_t flags;
+} __attribute__ (( packed ));
+
+/**
+ * iBFT Control structure
+ *
+ */
+struct ibft_control {
+	/** Common header */
+	struct ibft_header header;
+	/** Extensions */
+	uint16_t extensions;
+	/** Offset to Initiator structure */
+	ibft_off_t initiator;
+	/** Offset to NIC structure for NIC 0 */
+	ibft_off_t nic_0;
+	/** Offset to Target structure for target 0 */
+	ibft_off_t target_0;
+	/** Offset to NIC structure for NIC 1 */
+	ibft_off_t nic_1;
+	/** Offset to Target structure for target 1 */
+	ibft_off_t target_1;
+} __attribute__ (( packed ));
+
+/** Structure ID for Control section */
+#define IBFT_STRUCTURE_ID_CONTROL 0x01
+
+/** Attempt login only to specified target
+ *
+ * If this flag is not set, all targets will be logged in to.
+ */
+#define IBFT_FL_CONTROL_SINGLE_LOGIN_ONLY 0x01
+
+/**
+ * iBFT Initiator structure
+ *
+ */
+struct ibft_initiator {
+	/** Common header */
+	struct ibft_header header;
+	/** iSNS server */
+	struct ibft_ipaddr isns_server;
+	/** SLP server */
+	struct ibft_ipaddr slp_server;
+	/** Primary and secondary Radius servers */
+	struct ibft_ipaddr radius[2];
+	/** Initiator name */
+	struct ibft_string initiator_name;
+} __attribute__ (( packed ));
+
+/** Structure ID for Initiator section */
+#define IBFT_STRUCTURE_ID_INITIATOR 0x02
+
+/** Initiator block valid */
+#define IBFT_FL_INITIATOR_BLOCK_VALID 0x01
+
+/** Initiator firmware boot selected */
+#define IBFT_FL_INITIATOR_FIRMWARE_BOOT_SELECTED 0x02
+
+/**
+ * iBFT NIC structure
+ *
+ */
+struct ibft_nic {
+	/** Common header */
+	struct ibft_header header;
+	/** IP address */
+	struct ibft_ipaddr ip_address;
+	/** Subnet mask
+	 *
+	 * This is the length of the subnet mask in bits (e.g. /24).
+	 */
+	uint8_t subnet_mask_prefix;
+	/** Origin */
+	uint8_t origin;
+	/** Default gateway */
+	struct ibft_ipaddr gateway;
+	/** Primary and secondary DNS servers */
+	struct ibft_ipaddr dns[2];
+	/** DHCP server */
+	struct ibft_ipaddr dhcp;
+	/** VLAN tag */
+	uint16_t vlan;
+	/** MAC address */
+	uint8_t mac_address[6];
+	/** PCI bus:dev:fn */
+	uint16_t pci_bus_dev_func;
+	/** Hostname */
+	struct ibft_string hostname;
+} __attribute__ (( packed ));
+
+/** Structure ID for NIC section */
+#define IBFT_STRUCTURE_ID_NIC 0x03
+
+/** NIC block valid */
+#define IBFT_FL_NIC_BLOCK_VALID 0x01
+
+/** NIC firmware boot selected */
+#define IBFT_FL_NIC_FIRMWARE_BOOT_SELECTED 0x02
+
+/** NIC global / link local */
+#define IBFT_FL_NIC_GLOBAL 0x04
+
+/**
+ * iBFT Target structure
+ *
+ */
+struct ibft_target {
+	/** Common header */
+	struct ibft_header header;
+	/** IP address */
+	struct ibft_ipaddr ip_address;
+	/** TCP port */
+	uint16_t socket;
+	/** Boot LUN */
+	uint64_t boot_lun;
+	/** CHAP type
+	 *
+	 * This is an IBFT_CHAP_XXX constant.
+	 */
+	uint8_t chap_type;
+	/** NIC association */
+	uint8_t nic_association;
+	/** Target name */
+	struct ibft_string target_name;
+	/** CHAP name */
+	struct ibft_string chap_name;
+	/** CHAP secret */
+	struct ibft_string chap_secret;
+	/** Reverse CHAP name */
+	struct ibft_string reverse_chap_name;
+	/** Reverse CHAP secret */
+	struct ibft_string reverse_chap_secret;
+} __attribute__ (( packed ));
+
+/** Structure ID for Target section */
+#define IBFT_STRUCTURE_ID_TARGET 0x04
+
+/** Target block valid */
+#define IBFT_FL_TARGET_BLOCK_VALID 0x01
+
+/** Target firmware boot selected */
+#define IBFT_FL_TARGET_FIRMWARE_BOOT_SELECTED 0x02
+
+/** Target use Radius CHAP */
+#define IBFT_FL_TARGET_USE_CHAP 0x04
+
+/** Target use Radius rCHAP */
+#define IBFT_FL_TARGET_USE_RCHAP 0x08
+
+/* Values for chap_type */
+#define IBFT_CHAP_NONE		0	/**< No CHAP authentication */
+#define IBFT_CHAP_ONE_WAY	1	/**< One-way CHAP */
+#define IBFT_CHAP_MUTUAL	2	/**< Mutual CHAP */
+
+/**
+ * iSCSI Boot Firmware Table (iBFT)
+ */
+struct ibft_table {
+	/** ACPI header */
+	struct acpi_description_header acpi;
+	/** Reserved */
+	uint8_t reserved[12];
+	/** Control structure */
+	struct ibft_control control;
+} __attribute__ (( packed ));
+
+/**
+ * iSCSI string block descriptor
+ *
+ * This is an internal structure that we use to keep track of the
+ * allocation of string data.
+ */
+struct ibft_string_block {
+	/** The iBFT containing these strings */
+	struct ibft_table *table;
+	/** Offset of first free byte within iBFT */
+	unsigned int offset;
+};
+
+/** Amount of space reserved for strings in a gPXE iBFT */
+#define IBFT_STRINGS_SIZE 384
+
+/**
+ * An iBFT created by gPXE
+ *
+ */
+struct gpxe_ibft {
+	/** The fixed section */
+	struct ibft_table table;
+	/** The Initiator section */
+	struct ibft_initiator initiator __attribute__ (( aligned ( 16 ) ));
+	/** The NIC section */
+	struct ibft_nic nic __attribute__ (( aligned ( 16 ) ));
+	/** The Target section */
+	struct ibft_target target __attribute__ (( aligned ( 16 ) ));
+	/** Strings block */
+	char strings[IBFT_STRINGS_SIZE];
+} __attribute__ (( packed, aligned ( 16 ) ));
+
+struct net_device;
+struct iscsi_session;
+
+extern int ibft_fill_data ( struct net_device *netdev,
+			    struct iscsi_session *iscsi );
+
+#endif /* _GPXE_IBFT_H */
diff --git a/gpxe/src/arch/i386/include/gpxe/memtop_umalloc.h b/gpxe/src/arch/i386/include/gpxe/memtop_umalloc.h
new file mode 100644
index 0000000..eaf7025
--- /dev/null
+++ b/gpxe/src/arch/i386/include/gpxe/memtop_umalloc.h
@@ -0,0 +1,18 @@
+#ifndef _GPXE_MEMTOP_UMALLOC_H
+#define _GPXE_MEMTOP_UMALLOC_H
+
+/** @file
+ *
+ * External memory allocation
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#ifdef UMALLOC_MEMTOP
+#define UMALLOC_PREFIX_memtop
+#else
+#define UMALLOC_PREFIX_memtop __memtop_
+#endif
+
+#endif /* _GPXE_MEMTOP_UMALLOC_H */
diff --git a/gpxe/src/arch/i386/include/gpxe/rdtsc_timer.h b/gpxe/src/arch/i386/include/gpxe/rdtsc_timer.h
new file mode 100644
index 0000000..67ba22f
--- /dev/null
+++ b/gpxe/src/arch/i386/include/gpxe/rdtsc_timer.h
@@ -0,0 +1,39 @@
+#ifndef _GPXE_RDTSC_TIMER_H
+#define _GPXE_RDTSC_TIMER_H
+
+/** @file
+ *
+ * RDTSC timer
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#ifdef TIMER_RDTSC
+#define TIMER_PREFIX_rdtsc
+#else
+#define TIMER_PREFIX_rdtsc __rdtsc_
+#endif
+
+/**
+ * RDTSC values can easily overflow an unsigned long.  We discard the
+ * low-order bits in order to obtain sensibly-scaled values.
+ */
+#define TSC_SHIFT 8
+
+/**
+ * Get current system time in ticks
+ *
+ * @ret ticks		Current time, in ticks
+ */
+static inline __always_inline unsigned long
+TIMER_INLINE ( rdtsc, currticks ) ( void ) {
+	unsigned long ticks;
+
+	__asm__ __volatile__ ( "rdtsc\n\t"
+			       "shrdl %1, %%edx, %%eax\n\t"
+			       : "=a" ( ticks ) : "i" ( TSC_SHIFT ) : "edx" );
+	return ticks;
+}
+
+#endif /* _GPXE_RDTSC_TIMER_H */
diff --git a/gpxe/src/arch/i386/include/gpxe/sbft.h b/gpxe/src/arch/i386/include/gpxe/sbft.h
new file mode 100644
index 0000000..3003843
--- /dev/null
+++ b/gpxe/src/arch/i386/include/gpxe/sbft.h
@@ -0,0 +1,125 @@
+#ifndef _GPXE_SBFT_H
+#define _GPXE_SBFT_H
+
+/*
+ * Copyright (C) 2009 Fen Systems Ltd <mbrown@fensystems.co.uk>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ *   Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+FILE_LICENCE ( BSD2 );
+
+/** @file
+ *
+ * SRP boot firmware table
+ *
+ * The working draft specification for the SRP boot firmware table can
+ * be found at
+ *
+ *   http://etherboot.org/wiki/srp/sbft
+ *
+ */
+
+#include <stdint.h>
+#include <gpxe/acpi.h>
+#include <gpxe/scsi.h>
+#include <gpxe/srp.h>
+#include <gpxe/ib_srp.h>
+
+/** SRP Boot Firmware Table signature */
+#define SBFT_SIG "sBFT"
+
+/** An offset from the start of the sBFT */
+typedef uint16_t sbft_off_t;
+
+/**
+ * SRP Boot Firmware Table
+ */
+struct sbft_table {
+	/** ACPI header */
+	struct acpi_description_header acpi;
+	/** Offset to SCSI subtable */
+	sbft_off_t scsi_offset;
+	/** Offset to SRP subtable */
+	sbft_off_t srp_offset;
+	/** Offset to IB subtable, if present */
+	sbft_off_t ib_offset;
+	/** Reserved */
+	uint8_t reserved[6];
+} __attribute__ (( packed ));
+
+/**
+ * sBFT SCSI subtable
+ */
+struct sbft_scsi_subtable {
+	/** LUN */
+	struct scsi_lun lun;
+} __attribute__ (( packed ));
+
+/**
+ * sBFT SRP subtable
+ */
+struct sbft_srp_subtable {
+	/** Initiator and target ports */
+	struct srp_port_ids port_ids;
+} __attribute__ (( packed ));
+
+/**
+ * sBFT IB subtable
+ */
+struct sbft_ib_subtable {
+	/** Source GID */
+	struct ib_gid sgid;
+	/** Destination GID */
+	struct ib_gid dgid;
+	/** Service ID */
+	struct ib_gid_half service_id;
+	/** Partition key */
+	uint16_t pkey;
+	/** Reserved */
+	uint8_t reserved[6];
+} __attribute__ (( packed ));
+
+/**
+ * An sBFT created by gPXE
+ */
+struct gpxe_sbft {
+	/** The table header */
+	struct sbft_table table;
+	/** The SCSI subtable */
+	struct sbft_scsi_subtable scsi;
+	/** The SRP subtable */
+	struct sbft_srp_subtable srp;
+	/** The IB subtable */
+	struct sbft_ib_subtable ib;
+} __attribute__ (( packed, aligned ( 16 ) ));
+
+struct srp_device;
+
+extern int sbft_fill_data ( struct srp_device *srp );
+
+#endif /* _GPXE_SBFT_H */
diff --git a/gpxe/src/arch/i386/include/gpxe/timer2.h b/gpxe/src/arch/i386/include/gpxe/timer2.h
new file mode 100644
index 0000000..8f11951
--- /dev/null
+++ b/gpxe/src/arch/i386/include/gpxe/timer2.h
@@ -0,0 +1,14 @@
+#ifndef _GPXE_TIMER2_H
+#define _GPXE_TIMER2_H
+
+/** @file
+ *
+ * Timer chip control
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+extern void timer2_udelay ( unsigned long usecs );
+
+#endif /* _GPXE_TIMER2_H */
diff --git a/gpxe/src/arch/i386/include/gpxe/x86_io.h b/gpxe/src/arch/i386/include/gpxe/x86_io.h
new file mode 100644
index 0000000..beb5b22
--- /dev/null
+++ b/gpxe/src/arch/i386/include/gpxe/x86_io.h
@@ -0,0 +1,153 @@
+#ifndef _GPXE_X86_IO_H
+#define _GPXE_X86_IO_H
+
+/** @file
+ *
+ * gPXE I/O API for x86
+ *
+ * i386 uses direct pointer dereferences for accesses to memory-mapped
+ * I/O space, and the inX/outX instructions for accesses to
+ * port-mapped I/O space.
+ *
+ * 64-bit atomic accesses (readq() and writeq()) use MMX instructions,
+ * and will crash original Pentium and earlier CPUs.  Fortunately, no
+ * hardware that requires atomic 64-bit accesses will physically fit
+ * into a machine with such an old CPU anyway.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#ifdef IOAPI_X86
+#define IOAPI_PREFIX_x86
+#else
+#define IOAPI_PREFIX_x86 __x86_
+#endif
+
+/*
+ * Memory space mappings
+ *
+ */
+
+/*
+ * Physical<->Bus and Bus<->I/O address mappings
+ *
+ */
+
+static inline __always_inline unsigned long
+IOAPI_INLINE ( x86, phys_to_bus ) ( unsigned long phys_addr ) {
+	return phys_addr;
+}
+
+static inline __always_inline unsigned long
+IOAPI_INLINE ( x86, bus_to_phys ) ( unsigned long bus_addr ) {
+	return bus_addr;
+}
+
+static inline __always_inline void *
+IOAPI_INLINE ( x86, ioremap ) ( unsigned long bus_addr, size_t len __unused ) {
+	return phys_to_virt ( bus_addr );
+}
+
+static inline __always_inline void
+IOAPI_INLINE ( x86, iounmap ) ( volatile const void *io_addr __unused ) {
+	/* Nothing to do */
+}
+
+static inline __always_inline unsigned long
+IOAPI_INLINE ( x86, io_to_bus ) ( volatile const void *io_addr ) {
+	return virt_to_phys ( io_addr );
+}
+
+/*
+ * MMIO reads and writes up to 32 bits
+ *
+ */
+
+#define X86_READX( _api_func, _type )					      \
+static inline __always_inline _type					      \
+IOAPI_INLINE ( x86, _api_func ) ( volatile _type *io_addr ) {		      \
+	return *io_addr;						      \
+}
+X86_READX ( readb, uint8_t );
+X86_READX ( readw, uint16_t );
+X86_READX ( readl, uint32_t );
+
+#define X86_WRITEX( _api_func, _type )					      \
+static inline __always_inline void					      \
+IOAPI_INLINE ( x86, _api_func ) ( _type data,				      \
+				  volatile _type *io_addr ) {		      \
+	*io_addr = data;						      \
+}
+X86_WRITEX ( writeb, uint8_t );
+X86_WRITEX ( writew, uint16_t );
+X86_WRITEX ( writel, uint32_t );
+
+/*
+ * PIO reads and writes up to 32 bits
+ *
+ */
+
+#define X86_INX( _insn_suffix, _type, _reg_prefix )			      \
+static inline __always_inline _type					      \
+IOAPI_INLINE ( x86, in ## _insn_suffix ) ( volatile _type *io_addr ) {	      \
+	_type data;							      \
+	__asm__ __volatile__ ( "in" #_insn_suffix " %w1, %" _reg_prefix "0"   \
+			       : "=a" ( data ) : "Nd" ( io_addr ) );	      \
+	return data;							      \
+}									      \
+static inline __always_inline void					      \
+IOAPI_INLINE ( x86, ins ## _insn_suffix ) ( volatile _type *io_addr,	      \
+					    _type *data,		      \
+					    unsigned int count ) {	      \
+	unsigned int discard_D;						      \
+	__asm__ __volatile__ ( "rep ins" #_insn_suffix			      \
+			       : "=D" ( discard_D )			      \
+			       : "d" ( io_addr ), "c" ( count ),	      \
+				 "0" ( data ) );			      \
+}
+X86_INX ( b, uint8_t, "b" );
+X86_INX ( w, uint16_t, "w" );
+X86_INX ( l, uint32_t, "k" );
+
+#define X86_OUTX( _insn_suffix, _type, _reg_prefix )			      \
+static inline __always_inline void					      \
+IOAPI_INLINE ( x86, out ## _insn_suffix ) ( _type data,			      \
+					    volatile _type *io_addr ) {	      \
+	__asm__ __volatile__ ( "out" #_insn_suffix " %" _reg_prefix "0, %w1"  \
+			       : : "a" ( data ), "Nd" ( io_addr ) );	      \
+}									      \
+static inline __always_inline void					      \
+IOAPI_INLINE ( x86, outs ## _insn_suffix ) ( volatile _type *io_addr,	      \
+					     const _type *data,		      \
+					     unsigned int count ) {	      \
+	unsigned int discard_S;						      \
+	__asm__ __volatile__ ( "rep outs" #_insn_suffix			      \
+			       : "=S" ( discard_S )			      \
+			       : "d" ( io_addr ), "c" ( count ),	      \
+				 "0" ( data ) );			      \
+}
+X86_OUTX ( b, uint8_t, "b" );
+X86_OUTX ( w, uint16_t, "w" );
+X86_OUTX ( l, uint32_t, "k" );
+
+/*
+ * Slow down I/O
+ *
+ */
+
+static inline __always_inline void
+IOAPI_INLINE ( x86, iodelay ) ( void ) {
+	__asm__ __volatile__ ( "outb %al, $0x80" );
+}
+
+/*
+ * Memory barrier
+ *
+ */
+
+static inline __always_inline void
+IOAPI_INLINE ( x86, mb ) ( void ) {
+	__asm__ __volatile__ ( "lock; addl $0, 0(%%esp)" : : : "memory" );
+}
+
+#endif /* _GPXE_X86_IO_H */
diff --git a/gpxe/src/arch/i386/include/int13.h b/gpxe/src/arch/i386/include/int13.h
new file mode 100644
index 0000000..e1884d9
--- /dev/null
+++ b/gpxe/src/arch/i386/include/int13.h
@@ -0,0 +1,292 @@
+#ifndef INT13_H
+#define INT13_H
+
+/** @file
+ *
+ * INT 13 emulation
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <gpxe/list.h>
+#include <realmode.h>
+
+struct block_device;
+
+/**
+ * @defgroup int13ops INT 13 operation codes
+ * @{
+ */
+
+/** Reset disk system */
+#define INT13_RESET			0x00
+/** Get status of last operation */
+#define INT13_GET_LAST_STATUS		0x01
+/** Read sectors */
+#define INT13_READ_SECTORS		0x02
+/** Write sectors */
+#define INT13_WRITE_SECTORS		0x03
+/** Get drive parameters */
+#define INT13_GET_PARAMETERS		0x08
+/** Get disk type */
+#define INT13_GET_DISK_TYPE		0x15
+/** Extensions installation check */
+#define INT13_EXTENSION_CHECK		0x41
+/** Extended read */
+#define INT13_EXTENDED_READ		0x42
+/** Extended write */
+#define INT13_EXTENDED_WRITE		0x43
+/** Get extended drive parameters */
+#define INT13_GET_EXTENDED_PARAMETERS	0x48
+/** Get CD-ROM status / terminate emulation */
+#define INT13_CDROM_STATUS_TERMINATE	0x4b
+
+/** @} */
+
+/**
+ * @defgroup int13status INT 13 status codes
+ * @{
+ */
+
+/** Operation completed successfully */
+#define INT13_STATUS_SUCCESS		0x00
+/** Invalid function or parameter */
+#define INT13_STATUS_INVALID		0x01
+/** Read error */
+#define INT13_STATUS_READ_ERROR		0x04
+/** Write error */
+#define INT13_STATUS_WRITE_ERROR	0xcc
+
+/** @} */
+
+/** Block size for non-extended INT 13 calls */
+#define INT13_BLKSIZE 512
+
+/** An INT 13 emulated drive */
+struct int13_drive {
+	/** List of all registered drives */
+	struct list_head list;
+
+	/** Underlying block device */
+	struct block_device *blockdev;
+
+	/** BIOS in-use drive number (0x80-0xff) */
+	unsigned int drive;
+	/** BIOS natural drive number (0x80-0xff)
+	 *
+	 * This is the drive number that would have been assigned by
+	 * 'naturally' appending the drive to the end of the BIOS
+	 * drive list.
+	 *
+	 * If the emulated drive replaces a preexisting drive, this is
+	 * the drive number that the preexisting drive gets remapped
+	 * to.
+	 */
+	unsigned int natural_drive;
+
+	/** Number of cylinders
+	 *
+	 * The cylinder number field in an INT 13 call is ten bits
+	 * wide, giving a maximum of 1024 cylinders.  Conventionally,
+	 * when the 7.8GB limit of a CHS address is exceeded, it is
+	 * the number of cylinders that is increased beyond the
+	 * addressable limit.
+	 */
+	unsigned int cylinders;
+	/** Number of heads
+	 *
+	 * The head number field in an INT 13 call is eight bits wide,
+	 * giving a maximum of 256 heads.  However, apparently all
+	 * versions of MS-DOS up to and including Win95 fail with 256
+	 * heads, so the maximum encountered in practice is 255.
+	 */
+	unsigned int heads;
+	/** Number of sectors per track
+	 *
+	 * The sector number field in an INT 13 call is six bits wide,
+	 * giving a maximum of 63 sectors, since sector numbering
+	 * (unlike head and cylinder numbering) starts at 1, not 0.
+	 */
+	unsigned int sectors_per_track;
+
+	/** Status of last operation */
+	int last_status;
+};
+
+/** An INT 13 disk address packet */
+struct int13_disk_address {
+	/** Size of the packet, in bytes */
+	uint8_t bufsize;
+	/** Reserved, must be zero */
+	uint8_t reserved;
+	/** Block count */
+	uint16_t count;
+	/** Data buffer */
+	struct segoff buffer;
+	/** Starting block number */
+	uint64_t lba;
+	/** Data buffer (EDD-3.0 only) */
+	uint64_t buffer_phys;
+} __attribute__ (( packed ));
+
+/** INT 13 disk parameters */
+struct int13_disk_parameters {
+	/** Size of this structure */
+	uint16_t bufsize;
+	/** Flags */
+	uint16_t flags;
+	/** Number of cylinders */
+	uint32_t cylinders;
+	/** Number of heads */
+	uint32_t heads;
+	/** Number of sectors per track */
+	uint32_t sectors_per_track;
+	/** Total number of sectors on drive */
+	uint64_t sectors;
+	/** Bytes per sector */
+	uint16_t sector_size;
+	
+} __attribute__ (( packed ));
+
+/**
+ * @defgroup int13types INT 13 disk types
+ * @{
+ */
+
+/** No such drive */
+#define INT13_DISK_TYPE_NONE	0x00
+/** Floppy without change-line support */
+#define INT13_DISK_TYPE_FDD	0x01
+/** Floppy with change-line support */
+#define INT13_DISK_TYPE_FDD_CL	0x02
+/** Hard disk */
+#define INT13_DISK_TYPE_HDD	0x03
+
+/** @} */
+
+/**
+ * @defgroup int13flags INT 13 disk parameter flags
+ * @{
+ */
+
+/** DMA boundary errors handled transparently */
+#define INT13_FL_DMA_TRANSPARENT 0x01
+/** CHS information is valid */
+#define INT13_FL_CHS_VALID	 0x02
+/** Removable drive */
+#define INT13_FL_REMOVABLE	 0x04
+/** Write with verify supported */
+#define INT13_FL_VERIFIABLE	 0x08
+/** Has change-line supported (valid only for removable drives) */
+#define INT13_FL_CHANGE_LINE	 0x10
+/** Drive can be locked (valid only for removable drives) */
+#define INT13_FL_LOCKABLE	 0x20
+/** CHS is max possible, not current media (valid only for removable drives) */
+#define INT13_FL_CHS_MAX	 0x40
+
+/** @} */
+
+/**
+ * @defgroup int13exts INT 13 extension flags
+ * @{
+ */
+
+/** Extended disk access functions supported */
+#define INT13_EXTENSION_LINEAR		0x01
+/** Removable drive functions supported */
+#define INT13_EXTENSION_REMOVABLE	0x02
+/** EDD functions supported */
+#define INT13_EXTENSION_EDD		0x04
+
+/** @} */
+
+/**
+ * @defgroup int13vers INT 13 extension versions
+ * @{
+ */
+
+/** INT13 extensions version 1.x */
+#define INT13_EXTENSION_VER_1_X		0x01
+/** INT13 extensions version 2.0 (EDD-1.0) */
+#define INT13_EXTENSION_VER_2_0		0x20
+/** INT13 extensions version 2.1 (EDD-1.1) */
+#define INT13_EXTENSION_VER_2_1		0x21
+/** INT13 extensions version 3.0 (EDD-3.0) */
+#define INT13_EXTENSION_VER_3_0		0x30
+
+/** @} */ 
+
+/** Bootable CD-ROM specification packet */
+struct int13_cdrom_specification {
+	/** Size of packet in bytes */
+	uint8_t size;
+	/** Boot media type */
+	uint8_t media_type;
+	/** Drive number */
+	uint8_t drive;
+	/** CD-ROM controller number */
+	uint8_t controller;
+	/** LBA of disk image to emulate */
+	uint32_t lba;
+	/** Device specification */
+	uint16_t device;
+	/** Segment of 3K buffer for caching CD-ROM reads */
+	uint16_t cache_segment;
+	/** Load segment for initial boot image */
+	uint16_t load_segment;
+	/** Number of 512-byte sectors to load */
+	uint16_t load_sectors;
+	/** Low 8 bits of cylinder number */
+	uint8_t cyl;
+	/** Sector number, plus high 2 bits of cylinder number */
+	uint8_t cyl_sector;
+	/** Head number */
+	uint8_t head;
+} __attribute__ (( packed ));
+
+/** A C/H/S address within a partition table entry */
+struct partition_chs {
+	/** Head number */
+	uint8_t head;
+	/** Sector number, plus high 2 bits of cylinder number */
+	uint8_t cyl_sector;
+	/** Low 8 bits of cylinder number */
+	uint8_t cyl;
+} __attribute__ (( packed ));
+
+#define PART_HEAD(chs) ( (chs).head )
+#define PART_SECTOR(chs) ( (chs).cyl_sector & 0x3f )
+#define PART_CYLINDER(chs) ( (chs).cyl | ( ( (chs).cyl_sector & 0xc0 ) << 2 ) )
+
+/** A partition table entry within the MBR */
+struct partition_table_entry {
+	/** Bootable flag */
+	uint8_t bootable;
+	/** C/H/S start address */
+	struct partition_chs chs_start;
+	/** System indicator (partition type) */
+	uint8_t type;
+	/** C/H/S end address */
+	struct partition_chs chs_end;
+	/** Linear start address */
+	uint32_t start;
+	/** Linear length */
+	uint32_t length;
+} __attribute__ (( packed ));
+
+/** A Master Boot Record */
+struct master_boot_record {
+	uint8_t pad[446];
+	/** Partition table */
+	struct partition_table_entry partitions[4];
+	/** 0x55aa MBR signature */
+	uint16_t signature;
+} __attribute__ (( packed ));
+
+extern void register_int13_drive ( struct int13_drive *drive );
+extern void unregister_int13_drive ( struct int13_drive *drive );
+extern int int13_boot ( unsigned int drive );
+
+#endif /* INT13_H */
diff --git a/gpxe/src/arch/i386/include/kir.h b/gpxe/src/arch/i386/include/kir.h
new file mode 100644
index 0000000..84633d2
--- /dev/null
+++ b/gpxe/src/arch/i386/include/kir.h
@@ -0,0 +1,18 @@
+#ifndef KIR_H
+#define KIR_H
+
+#ifndef KEEP_IT_REAL
+#error "kir.h can be used only with -DKEEP_IT_REAL"
+#endif
+
+#ifdef ASSEMBLY
+
+#define code32 code16gcc
+
+#else /* ASSEMBLY */
+
+__asm__ ( ".code16gcc" );
+
+#endif /* ASSEMBLY */
+
+#endif /* KIR_H */
diff --git a/gpxe/src/arch/i386/include/libkir.h b/gpxe/src/arch/i386/include/libkir.h
new file mode 100644
index 0000000..1f5b135
--- /dev/null
+++ b/gpxe/src/arch/i386/include/libkir.h
@@ -0,0 +1,233 @@
+#ifndef LIBKIR_H
+#define LIBKIR_H
+
+#include "realmode.h"
+
+#ifndef ASSEMBLY
+
+/*
+ * Full API documentation for these functions is in realmode.h.
+ *
+ */
+
+/* Access to variables in .data16 and .text16 in a way compatible with librm */
+#define __data16( variable ) variable
+#define __data16_array( variable, array ) variable array
+#define __bss16( variable ) variable
+#define __bss16_array( variable, array ) variable array
+#define __text16( variable ) variable
+#define __text16_array( variable,array ) variable array
+#define __use_data16( variable ) variable
+#define __use_text16( variable ) variable
+#define __from_data16( pointer ) pointer
+#define __from_text16( pointer ) pointer
+
+/* Real-mode data and code segments */
+static inline __attribute__ (( always_inline )) unsigned int _rm_cs ( void ) {
+	uint16_t cs;
+	__asm__ __volatile__ ( "movw %%cs, %w0" : "=r" ( cs ) );
+	return cs;
+}
+
+static inline __attribute__ (( always_inline )) unsigned int _rm_ds ( void ) {
+	uint16_t ds;
+	__asm__ __volatile__ ( "movw %%ds, %w0" : "=r" ( ds ) );
+	return ds;
+}
+
+#define rm_cs ( _rm_cs() )
+#define rm_ds ( _rm_ds() )
+
+/* Copy to/from base memory */
+
+static inline void copy_to_real_libkir ( unsigned int dest_seg,
+					 unsigned int dest_off,
+					 const void *src, size_t n ) {
+	unsigned int discard_D, discard_S, discard_c;
+
+	__asm__ __volatile__ ( "pushw %%es\n\t"
+			       "movw %3, %%es\n\t"
+			       "rep movsb\n\t"
+			       "popw %%es\n\t"
+			       : "=D" ( discard_D ), "=S" ( discard_S ),
+			         "=c" ( discard_c )
+			       : "r" ( dest_seg ), "D" ( dest_off ),
+			         "S" ( src ),
+			         "c" ( n )
+			       : "memory" );
+}
+
+static inline void copy_from_real_libkir ( void *dest,
+					   unsigned int src_seg,
+					   unsigned int src_off,
+					   size_t n ) {
+	unsigned int discard_D, discard_S, discard_c;
+
+	__asm__ __volatile__ ( "pushw %%ds\n\t"
+			       "movw %4, %%ds\n\t"
+			       "rep movsb\n\t"
+			       "popw %%ds\n\t"
+			       : "=D" ( discard_D ), "=S" ( discard_S ),
+			         "=c" ( discard_c )
+			       : "D" ( dest ),
+			         "r" ( src_seg ), "S" ( src_off ),
+			         "c" ( n )
+			       : "memory" );
+}
+
+#define copy_to_real copy_to_real_libkir
+#define copy_from_real copy_from_real_libkir
+
+/*
+ * Transfer individual values to/from base memory.  There may well be
+ * a neater way to do this.  We have two versions: one for constant
+ * offsets (where the mov instruction must be of the form "mov
+ * %es:123, %xx") and one for non-constant offsets (where the mov
+ * instruction must be of the form "mov %es:(%xx), %yx".  If it's
+ * possible to incorporate both forms into one __asm__ instruction, I
+ * don't know how to do it.
+ *
+ * Ideally, the mov instruction should be "mov%z0"; the "%z0" is meant
+ * to expand to either "b", "w" or "l" depending on the size of
+ * operand 0.  This would remove the (minor) ambiguity in the mov
+ * instruction.  However, gcc on at least my system barfs with an
+ * "internal compiler error" when confronted with %z0.
+ *
+ */
+
+#define put_real_kir_const_off( var, seg, off )		  		     \
+	__asm__ ( "movw %w1, %%es\n\t"					     \
+		  "mov %0, %%es:%c2\n\t"				     \
+		  "pushw %%ds\n\t" /* restore %es */			     \
+		  "popw %%es\n\t"					     \
+		  :							     \
+		  : "r,r" ( var ), "rm,rm" ( seg ), "i,!r" ( off )	     \
+		  )
+
+#define put_real_kir_nonconst_off( var, seg, off )	  		     \
+	__asm__ ( "movw %w1, %%es\n\t"					     \
+		  "mov %0, %%es:(%2)\n\t"				     \
+		  "pushw %%ds\n\t" /* restore %es */			     \
+		  "popw %%es\n\t"					     \
+		  :							     \
+		  : "r" ( var ), "rm" ( seg ), "r" ( off )		     \
+		  )
+
+#define put_real_kir( var, seg, off )					     \
+	do {								     \
+	  if ( __builtin_constant_p ( off ) )				     \
+		  put_real_kir_const_off ( var, seg, off );		     \
+	  else								     \
+		  put_real_kir_nonconst_off ( var, seg, off );		     \
+	} while ( 0 )
+
+#define get_real_kir_const_off( var, seg, off )		  		     \
+	__asm__ ( "movw %w1, %%es\n\t"					     \
+		  "mov %%es:%c2, %0\n\t"				     \
+		  "pushw %%ds\n\t" /* restore %es */			     \
+		  "popw %%es\n\t"					     \
+		  : "=r,r" ( var )					     \
+		  : "rm,rm" ( seg ), "i,!r" ( off )			     \
+		  )
+
+#define get_real_kir_nonconst_off( var, seg, off )			     \
+	__asm__ ( "movw %w1, %%es\n\t"					     \
+		  "mov %%es:(%2), %0\n\t"				     \
+		  "pushw %%ds\n\t" /* restore %es */			     \
+		  "popw %%es\n\t"					     \
+		  : "=r" ( var )					     \
+		  : "rm" ( seg ), "r" ( off )				     \
+		  )
+
+#define get_real_kir( var, seg, off )					     \
+	do {								     \
+	  if ( __builtin_constant_p ( off ) )				     \
+		  get_real_kir_const_off ( var, seg, off );		     \
+	  else								     \
+		  get_real_kir_nonconst_off ( var, seg, off );		     \
+	} while ( 0 )
+
+#define put_real put_real_kir
+#define get_real get_real_kir
+
+/**
+ * A pointer to a user buffer
+ *
+ * This is actually a struct segoff, but encoded as a uint32_t to
+ * ensure that gcc passes it around efficiently.
+ */
+typedef uint32_t userptr_t;
+
+/**
+ * Copy data to user buffer
+ *
+ * @v buffer	User buffer
+ * @v offset	Offset within user buffer
+ * @v src	Source
+ * @v len	Length
+ */
+static inline __attribute__ (( always_inline )) void
+copy_to_user ( userptr_t buffer, off_t offset, const void *src, size_t len ) {
+	copy_to_real ( ( buffer >> 16 ), ( ( buffer & 0xffff ) + offset ),
+		       src, len );
+}
+
+/**
+ * Copy data from user buffer
+ *
+ * @v dest	Destination
+ * @v buffer	User buffer
+ * @v offset	Offset within user buffer
+ * @v len	Length
+ */
+static inline __attribute__ (( always_inline )) void
+copy_from_user ( void *dest, userptr_t buffer, off_t offset, size_t len ) {
+	copy_from_real ( dest, ( buffer >> 16 ),
+			 ( ( buffer & 0xffff ) + offset ), len );
+}
+
+/**
+ * Convert segment:offset address to user buffer
+ *
+ * @v segment	Real-mode segment
+ * @v offset	Real-mode offset
+ * @ret buffer	User buffer
+ */
+static inline __attribute__ (( always_inline )) userptr_t
+real_to_user ( unsigned int segment, unsigned int offset ) {
+	return ( ( segment << 16 ) | offset );
+}
+
+/**
+ * Convert virtual address to user buffer
+ *
+ * @v virtual	Virtual address
+ * @ret buffer	User buffer
+ *
+ * This constructs a user buffer from an ordinary pointer.  Use it
+ * when you need to pass a pointer to an internal buffer to a function
+ * that expects a @c userptr_t.
+ */
+static inline __attribute__ (( always_inline )) userptr_t
+virt_to_user ( void * virtual ) {
+	return real_to_user ( rm_ds, ( intptr_t ) virtual );
+}
+
+/* TEXT16_CODE: declare a fragment of code that resides in .text16 */
+#define TEXT16_CODE( asm_code_str )			\
+	".section \".text16\", \"ax\", @progbits\n\t"	\
+	".code16\n\t"					\
+	".arch i386\n\t"				\
+	asm_code_str "\n\t"				\
+	".code16gcc\n\t"				\
+	".previous\n\t"
+
+/* REAL_CODE: declare a fragment of code that executes in real mode */
+#define REAL_CODE( asm_code_str )	\
+	".code16\n\t"			\
+	asm_code_str "\n\t"		\
+	".code16gcc\n\t"
+
+#endif /* ASSEMBLY */
+
+#endif /* LIBKIR_H */
diff --git a/gpxe/src/arch/i386/include/librm.h b/gpxe/src/arch/i386/include/librm.h
new file mode 100644
index 0000000..f193f5e
--- /dev/null
+++ b/gpxe/src/arch/i386/include/librm.h
@@ -0,0 +1,206 @@
+#ifndef LIBRM_H
+#define LIBRM_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/* Segment selectors as used in our protected-mode GDTs.
+ *
+ * Don't change these unless you really know what you're doing.
+ */
+
+#define VIRTUAL_CS 0x08
+#define VIRTUAL_DS 0x10
+#define PHYSICAL_CS 0x18
+#define PHYSICAL_DS 0x20
+#define REAL_CS 0x28
+#define REAL_DS 0x30
+#if 0
+#define LONG_CS 0x38
+#define LONG_DS 0x40
+#endif
+
+#ifndef ASSEMBLY
+
+#ifdef UACCESS_LIBRM
+#define UACCESS_PREFIX_librm
+#else
+#define UACCESS_PREFIX_librm __librm_
+#endif
+
+/* Variables in librm.S */
+extern unsigned long virt_offset;
+
+/**
+ * Convert physical address to user pointer
+ *
+ * @v phys_addr		Physical address
+ * @ret userptr		User pointer
+ */
+static inline __always_inline userptr_t
+UACCESS_INLINE ( librm, phys_to_user ) ( unsigned long phys_addr ) {
+	return ( phys_addr - virt_offset );
+}
+
+/**
+ * Convert user buffer to physical address
+ *
+ * @v userptr		User pointer
+ * @v offset		Offset from user pointer
+ * @ret phys_addr	Physical address
+ */
+static inline __always_inline unsigned long
+UACCESS_INLINE ( librm, user_to_phys ) ( userptr_t userptr, off_t offset ) {
+	return ( userptr + offset + virt_offset );
+}
+
+static inline __always_inline userptr_t
+UACCESS_INLINE ( librm, virt_to_user ) ( volatile const void *addr ) {
+	return trivial_virt_to_user ( addr );
+}
+
+static inline __always_inline void *
+UACCESS_INLINE ( librm, user_to_virt ) ( userptr_t userptr, off_t offset ) {
+	return trivial_user_to_virt ( userptr, offset );
+}
+
+static inline __always_inline userptr_t
+UACCESS_INLINE ( librm, userptr_add ) ( userptr_t userptr, off_t offset ) {
+	return trivial_userptr_add ( userptr, offset );
+}
+
+static inline __always_inline void
+UACCESS_INLINE ( librm, memcpy_user ) ( userptr_t dest, off_t dest_off,
+					userptr_t src, off_t src_off,
+					size_t len ) {
+	trivial_memcpy_user ( dest, dest_off, src, src_off, len );
+}
+
+static inline __always_inline void
+UACCESS_INLINE ( librm, memmove_user ) ( userptr_t dest, off_t dest_off,
+					 userptr_t src, off_t src_off,
+					 size_t len ) {
+	trivial_memmove_user ( dest, dest_off, src, src_off, len );
+}
+
+static inline __always_inline void
+UACCESS_INLINE ( librm, memset_user ) ( userptr_t buffer, off_t offset,
+					int c, size_t len ) {
+	trivial_memset_user ( buffer, offset, c, len );
+}
+
+static inline __always_inline size_t
+UACCESS_INLINE ( librm, strlen_user ) ( userptr_t buffer, off_t offset ) {
+	return trivial_strlen_user ( buffer, offset );
+}
+
+static inline __always_inline off_t
+UACCESS_INLINE ( librm, memchr_user ) ( userptr_t buffer, off_t offset,
+					int c, size_t len ) {
+	return trivial_memchr_user ( buffer, offset, c, len );
+}
+
+
+/******************************************************************************
+ *
+ * Access to variables in .data16 and .text16
+ *
+ */
+
+extern char *data16;
+extern char *text16;
+
+#define __data16( variable )						\
+	__attribute__ (( section ( ".data16" ) ))			\
+	_data16_ ## variable __asm__ ( #variable )
+
+#define __data16_array( variable, array )				\
+	__attribute__ (( section ( ".data16" ) ))			\
+	_data16_ ## variable array __asm__ ( #variable )
+
+#define __bss16( variable )						\
+	__attribute__ (( section ( ".bss16" ) ))			\
+	_data16_ ## variable __asm__ ( #variable )
+
+#define __bss16_array( variable, array )				\
+	__attribute__ (( section ( ".bss16" ) ))			\
+	_data16_ ## variable array __asm__ ( #variable )
+
+#define __text16( variable )						\
+	__attribute__ (( section ( ".text16.data" ) ))			\
+	_text16_ ## variable __asm__ ( #variable )
+
+#define __text16_array( variable, array )				\
+	__attribute__ (( section ( ".text16.data" ) ))			\
+	_text16_ ## variable array __asm__ ( #variable )
+
+#define __use_data16( variable )					\
+	( * ( ( typeof ( _data16_ ## variable ) * )			\
+	      & ( data16 [ ( size_t ) & ( _data16_ ## variable ) ] ) ) )
+
+#define __use_text16( variable )					\
+	( * ( ( typeof ( _text16_ ## variable ) * )			\
+	      & ( text16 [ ( size_t ) & ( _text16_ ## variable ) ] ) ) )
+
+#define __from_data16( pointer )					\
+	( ( unsigned int )						\
+	  ( ( ( void * ) (pointer) ) - ( ( void * ) data16 ) ) )
+
+#define __from_text16( pointer )					\
+	( ( unsigned int )						\
+	  ( ( ( void * ) (pointer) ) - ( ( void * ) text16 ) ) )
+
+/* Variables in librm.S, present in the normal data segment */
+extern uint16_t rm_sp;
+extern uint16_t rm_ss;
+extern uint16_t __data16 ( rm_cs );
+#define rm_cs __use_data16 ( rm_cs )
+extern uint16_t __text16 ( rm_ds );
+#define rm_ds __use_text16 ( rm_ds )
+
+/* Functions that librm expects to be able to link to.  Included here
+ * so that the compiler will catch prototype mismatches.
+ */
+extern void gateA20_set ( void );
+
+/**
+ * Convert segment:offset address to user buffer
+ *
+ * @v segment		Real-mode segment
+ * @v offset		Real-mode offset
+ * @ret buffer		User buffer
+ */
+static inline __always_inline userptr_t
+real_to_user ( unsigned int segment, unsigned int offset ) {
+	return ( phys_to_user ( ( segment << 4 ) + offset ) );
+}
+
+extern uint16_t copy_user_to_rm_stack ( userptr_t data, size_t size );
+extern void remove_user_from_rm_stack ( userptr_t data, size_t size );
+
+/* TEXT16_CODE: declare a fragment of code that resides in .text16 */
+#define TEXT16_CODE( asm_code_str )			\
+	".section \".text16\", \"ax\", @progbits\n\t"	\
+	".code16\n\t"					\
+	asm_code_str "\n\t"				\
+	".code32\n\t"					\
+	".previous\n\t"
+
+/* REAL_CODE: declare a fragment of code that executes in real mode */
+#define REAL_CODE( asm_code_str )			\
+	"pushl $1f\n\t"					\
+	"call real_call\n\t"				\
+	"addl $4, %%esp\n\t"				\
+	TEXT16_CODE ( "\n1:\n\t"			\
+		      asm_code_str			\
+		      "\n\t"				\
+		      "ret\n\t" )
+
+/* PHYS_CODE: declare a fragment of code that executes in flat physical mode */
+#define PHYS_CODE( asm_code_str )			\
+	"call _virt_to_phys\n\t"			\
+	asm_code_str					\
+	"call _phys_to_virt\n\t"
+
+#endif /* ASSEMBLY */
+
+#endif /* LIBRM_H */
diff --git a/gpxe/src/arch/i386/include/limits.h b/gpxe/src/arch/i386/include/limits.h
new file mode 100644
index 0000000..031b6c5
--- /dev/null
+++ b/gpxe/src/arch/i386/include/limits.h
@@ -0,0 +1,61 @@
+#ifndef LIMITS_H
+#define LIMITS_H	1
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/* Number of bits in a `char' */
+#define CHAR_BIT	8
+
+/* Minimum and maximum values a `signed char' can hold */
+#define SCHAR_MIN	(-128)
+#define SCHAR_MAX	127
+
+/* Maximum value an `unsigned char' can hold. (Minimum is 0.) */
+#define UCHAR_MAX	255
+
+/* Minimum and maximum values a `char' can hold */
+#define CHAR_MIN	SCHAR_MIN
+#define CHAR_MAX	SCHAR_MAX
+
+/* Minimum and maximum values a `signed short int' can hold */
+#define SHRT_MIN	(-32768)
+#define SHRT_MAX	32767
+
+/* Maximum value an `unsigned short' can hold. (Minimum is 0.) */
+#define USHRT_MAX	65535
+
+
+/* Minimum and maximum values a `signed int' can hold */
+#define INT_MIN		(-INT_MAX - 1)
+#define INT_MAX		2147483647
+
+/* Maximum value an `unsigned int' can hold. (Minimum is 0.) */
+#define UINT_MAX	4294967295U
+
+
+/* Minimum and maximum values a `signed int' can hold */
+#define INT_MAX		2147483647
+#define INT_MIN		(-INT_MAX - 1)
+
+
+/* Maximum value an `unsigned int' can hold. (Minimum is 0.) */
+#define UINT_MAX	4294967295U
+
+
+/* Minimum and maximum values a `signed long' can hold */
+#define LONG_MAX	2147483647
+#define LONG_MIN	(-LONG_MAX - 1L)
+
+/* Maximum value an `unsigned long' can hold. (Minimum is 0.) */
+#define ULONG_MAX	4294967295UL
+
+/* Minimum and maximum values a `signed long long' can hold */
+#define LLONG_MAX	9223372036854775807LL
+#define LLONG_MIN	(-LONG_MAX - 1LL)
+
+
+/* Maximum value an `unsigned long long' can hold. (Minimum is 0.) */
+#define ULLONG_MAX	18446744073709551615ULL
+
+
+#endif /* LIMITS_H */
diff --git a/gpxe/src/arch/i386/include/memsizes.h b/gpxe/src/arch/i386/include/memsizes.h
new file mode 100644
index 0000000..7b21749
--- /dev/null
+++ b/gpxe/src/arch/i386/include/memsizes.h
@@ -0,0 +1,19 @@
+#ifndef _MEMSIZES_H
+#define _MEMSIZES_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <basemem.h>
+
+/**
+ * Get size of base memory from BIOS free base memory counter
+ *
+ * @ret basemem		Base memory size, in kB
+ */
+static inline unsigned int basememsize ( void ) {
+	return get_fbms();
+}
+
+extern unsigned int extmemsize ( void );
+
+#endif /* _MEMSIZES_H */
diff --git a/gpxe/src/arch/i386/include/multiboot.h b/gpxe/src/arch/i386/include/multiboot.h
new file mode 100644
index 0000000..44614c7
--- /dev/null
+++ b/gpxe/src/arch/i386/include/multiboot.h
@@ -0,0 +1,149 @@
+#ifndef _MULTIBOOT_H
+#define _MULTIBOOT_H
+
+/**
+ * @file
+ *
+ * Multiboot operating systems
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+
+/** The magic number for the Multiboot header */
+#define MULTIBOOT_HEADER_MAGIC 0x1BADB002
+
+/** Boot modules must be page aligned */
+#define MB_FLAG_PGALIGN 0x00000001
+
+/** Memory map must be provided */
+#define MB_FLAG_MEMMAP 0x00000002
+
+/** Video mode information must be provided */
+#define MB_FLAG_VIDMODE 0x00000004
+
+/** Image is a raw multiboot image (not ELF) */
+#define MB_FLAG_RAW 0x00010000
+
+/**
+ * The magic number passed by a Multiboot-compliant boot loader
+ *
+ * Must be passed in register %eax when jumping to the Multiboot OS
+ * image.
+ */
+#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002
+
+/** Multiboot information structure mem_* fields are valid */
+#define MBI_FLAG_MEM 0x00000001
+
+/** Multiboot information structure boot_device field is valid */
+#define MBI_FLAG_BOOTDEV 0x00000002
+
+/** Multiboot information structure cmdline field is valid */
+#define MBI_FLAG_CMDLINE 0x00000004
+
+/** Multiboot information structure module fields are valid */
+#define MBI_FLAG_MODS 0x00000008
+
+/** Multiboot information structure a.out symbol table is valid */
+#define MBI_FLAG_AOUT 0x00000010
+
+/** Multiboot information struture ELF section header table is valid */
+#define MBI_FLAG_ELF 0x00000020
+
+/** Multiboot information structure memory map is valid */
+#define MBI_FLAG_MMAP 0x00000040
+
+/** Multiboot information structure drive list is valid */
+#define MBI_FLAG_DRIVES 0x00000080
+
+/** Multiboot information structure ROM configuration field is valid */
+#define MBI_FLAG_CFGTBL 0x00000100
+
+/** Multiboot information structure boot loader name field is valid */
+#define MBI_FLAG_LOADER 0x00000200
+
+/** Multiboot information structure APM table is valid */
+#define MBI_FLAG_APM 0x00000400
+
+/** Multiboot information structure video information is valid */
+#define MBI_FLAG_VBE 0x00000800
+
+/** A multiboot header */
+struct multiboot_header {
+	uint32_t magic;
+	uint32_t flags;
+	uint32_t checksum;
+	uint32_t header_addr;
+	uint32_t load_addr;
+	uint32_t load_end_addr;
+	uint32_t bss_end_addr;
+	uint32_t entry_addr;
+} __attribute__ (( packed, may_alias ));
+
+/** A multiboot a.out symbol table */
+struct multiboot_aout_symbol_table {
+	uint32_t tabsize;
+	uint32_t strsize;
+	uint32_t addr;
+	uint32_t reserved;
+} __attribute__ (( packed, may_alias ));
+
+/** A multiboot ELF section header table */
+struct multiboot_elf_section_header_table {
+	uint32_t num;
+	uint32_t size;
+	uint32_t addr;
+	uint32_t shndx;
+} __attribute__ (( packed, may_alias ));
+
+/** A multiboot information structure */
+struct multiboot_info {
+	uint32_t flags;
+	uint32_t mem_lower;
+	uint32_t mem_upper;
+	uint32_t boot_device;
+	uint32_t cmdline;
+	uint32_t mods_count;
+	uint32_t mods_addr;
+	union {
+		struct multiboot_aout_symbol_table aout_syms;
+		struct multiboot_elf_section_header_table elf_sections;
+	} syms;
+	uint32_t mmap_length;
+	uint32_t mmap_addr;
+	uint32_t drives_length;
+	uint32_t drives_addr;
+	uint32_t config_table;
+	uint32_t boot_loader_name;
+	uint32_t apm_table;
+	uint32_t vbe_control_info;
+	uint32_t vbe_mode_info;
+	uint16_t vbe_mode;
+	uint16_t vbe_interface_seg;
+	uint16_t vbe_interface_off;
+	uint16_t vbe_interface_len;
+} __attribute__ (( packed, may_alias ));
+
+/** A multiboot module structure */
+struct multiboot_module {
+	uint32_t mod_start;
+	uint32_t mod_end;
+	uint32_t string;
+	uint32_t reserved;
+} __attribute__ (( packed, may_alias ));
+
+/** A multiboot memory map entry */
+struct multiboot_memory_map {
+	uint32_t size;
+	uint64_t base_addr;
+	uint64_t length;
+	uint32_t type;
+} __attribute__ (( packed, may_alias ));
+
+/** Usable RAM */
+#define MBMEM_RAM 1
+
+#endif /* _MULTIBOOT_H */
diff --git a/gpxe/src/arch/i386/include/pic8259.h b/gpxe/src/arch/i386/include/pic8259.h
new file mode 100644
index 0000000..f8e20c4
--- /dev/null
+++ b/gpxe/src/arch/i386/include/pic8259.h
@@ -0,0 +1,71 @@
+/*
+ * Basic support for controlling the 8259 Programmable Interrupt Controllers.
+ *
+ * Initially written by Michael Brown (mcb30).
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#ifndef PIC8259_H
+#define PIC8259_H
+
+/* For segoff_t */
+#include "realmode.h"
+
+#define IRQ_PIC_CUTOFF 8
+
+/* 8259 register locations */
+#define PIC1_ICW1 0x20
+#define PIC1_OCW2 0x20
+#define PIC1_OCW3 0x20
+#define PIC1_ICR 0x20
+#define PIC1_IRR 0x20
+#define PIC1_ISR 0x20
+#define PIC1_ICW2 0x21
+#define PIC1_ICW3 0x21
+#define PIC1_ICW4 0x21
+#define PIC1_IMR 0x21
+#define PIC2_ICW1 0xa0
+#define PIC2_OCW2 0xa0
+#define PIC2_OCW3 0xa0
+#define PIC2_ICR 0xa0
+#define PIC2_IRR 0xa0
+#define PIC2_ISR 0xa0
+#define PIC2_ICW2 0xa1
+#define PIC2_ICW3 0xa1
+#define PIC2_ICW4 0xa1
+#define PIC2_IMR 0xa1
+
+/* Register command values */
+#define OCW3_ID 0x08
+#define OCW3_READ_IRR 0x03
+#define OCW3_READ_ISR 0x02
+#define ICR_EOI_NON_SPECIFIC 0x20
+#define ICR_EOI_NOP 0x40
+#define ICR_EOI_SPECIFIC 0x60
+#define ICR_EOI_SET_PRIORITY 0xc0
+
+/* Macros to enable/disable IRQs */
+#define IMR_REG(x) ( (x) < IRQ_PIC_CUTOFF ? PIC1_IMR : PIC2_IMR )
+#define IMR_BIT(x) ( 1 << ( (x) % IRQ_PIC_CUTOFF ) )
+#define irq_enabled(x) ( ( inb ( IMR_REG(x) ) & IMR_BIT(x) ) == 0 )
+#define enable_irq(x) outb ( inb( IMR_REG(x) ) & ~IMR_BIT(x), IMR_REG(x) )
+#define disable_irq(x) outb ( inb( IMR_REG(x) ) | IMR_BIT(x), IMR_REG(x) )
+
+/* Macros for acknowledging IRQs */
+#define ICR_REG( irq ) ( (irq) < IRQ_PIC_CUTOFF ? PIC1_ICR : PIC2_ICR )
+#define ICR_VALUE( irq ) ( (irq) % IRQ_PIC_CUTOFF )
+#define CHAINED_IRQ 2
+
+/* Utility macros to convert IRQ numbers to INT numbers and INT vectors  */
+#define IRQ_INT( irq ) ( ( ( (irq) - IRQ_PIC_CUTOFF ) ^ 0x70 ) & 0x7f )
+
+/* Other constants */
+#define IRQ_MAX 15
+#define IRQ_NONE -1U
+
+/* Function prototypes
+ */
+void send_eoi ( unsigned int irq );
+
+#endif /* PIC8259_H */
diff --git a/gpxe/src/arch/i386/include/pnpbios.h b/gpxe/src/arch/i386/include/pnpbios.h
new file mode 100644
index 0000000..4c20e73
--- /dev/null
+++ b/gpxe/src/arch/i386/include/pnpbios.h
@@ -0,0 +1,17 @@
+#ifndef _PNPBIOS_H
+#define _PNPBIOS_H
+
+/** @file
+ *
+ * PnP BIOS
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/* BIOS segment address */
+#define BIOS_SEG 0xf000
+
+extern int find_pnp_bios ( void );
+
+#endif /* _PNPBIOS_H */
diff --git a/gpxe/src/arch/i386/include/pxe.h b/gpxe/src/arch/i386/include/pxe.h
new file mode 100644
index 0000000..041ee7b
--- /dev/null
+++ b/gpxe/src/arch/i386/include/pxe.h
@@ -0,0 +1,154 @@
+#ifndef PXE_H
+#define PXE_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include "pxe_types.h"
+#include "pxe_api.h"
+#include <gpxe/device.h>
+
+/* Parameter block for pxenv_unknown() */
+struct s_PXENV_UNKNOWN {
+	PXENV_STATUS_t Status;			/**< PXE status code */
+} PACKED;
+
+typedef struct s_PXENV_UNKNOWN PXENV_UNKNOWN_t;
+
+/* Union used for PXE API calls; we don't know the type of the
+ * structure until we interpret the opcode.  Also, Status is available
+ * in the same location for any opcode, and it's convenient to have
+ * non-specific access to it.
+ */
+union u_PXENV_ANY {
+	/* Make it easy to read status for any operation */
+	PXENV_STATUS_t				Status;
+	struct s_PXENV_UNKNOWN			unknown;
+	struct s_PXENV_UNLOAD_STACK		unload_stack;
+	struct s_PXENV_GET_CACHED_INFO		get_cached_info;
+	struct s_PXENV_TFTP_READ_FILE		restart_tftp;
+	struct s_PXENV_START_UNDI		start_undi;
+	struct s_PXENV_STOP_UNDI		stop_undi;
+	struct s_PXENV_START_BASE		start_base;
+	struct s_PXENV_STOP_BASE		stop_base;
+	struct s_PXENV_TFTP_OPEN		tftp_open;
+	struct s_PXENV_TFTP_CLOSE		tftp_close;
+	struct s_PXENV_TFTP_READ		tftp_read;
+	struct s_PXENV_TFTP_READ_FILE		tftp_read_file;
+	struct s_PXENV_TFTP_GET_FSIZE		tftp_get_fsize;
+	struct s_PXENV_UDP_OPEN			udp_open;
+	struct s_PXENV_UDP_CLOSE		udp_close;
+	struct s_PXENV_UDP_WRITE		udp_write;
+	struct s_PXENV_UDP_READ			udp_read;
+	struct s_PXENV_UNDI_STARTUP		undi_startup;
+	struct s_PXENV_UNDI_CLEANUP		undi_cleanup;
+	struct s_PXENV_UNDI_INITIALIZE		undi_initialize;
+	struct s_PXENV_UNDI_RESET		undi_reset_adapter;
+	struct s_PXENV_UNDI_SHUTDOWN		undi_shutdown;
+	struct s_PXENV_UNDI_OPEN		undi_open;
+	struct s_PXENV_UNDI_CLOSE		undi_close;
+	struct s_PXENV_UNDI_TRANSMIT		undi_transmit;
+	struct s_PXENV_UNDI_SET_MCAST_ADDRESS	undi_set_mcast_address;
+	struct s_PXENV_UNDI_SET_STATION_ADDRESS undi_set_station_address;
+	struct s_PXENV_UNDI_SET_PACKET_FILTER	undi_set_packet_filter;
+	struct s_PXENV_UNDI_GET_INFORMATION	undi_get_information;
+	struct s_PXENV_UNDI_GET_STATISTICS	undi_get_statistics;
+	struct s_PXENV_UNDI_CLEAR_STATISTICS	undi_clear_statistics;
+	struct s_PXENV_UNDI_INITIATE_DIAGS	undi_initiate_diags;
+	struct s_PXENV_UNDI_FORCE_INTERRUPT	undi_force_interrupt;
+	struct s_PXENV_UNDI_GET_MCAST_ADDRESS	undi_get_mcast_address;
+	struct s_PXENV_UNDI_GET_NIC_TYPE	undi_get_nic_type;
+	struct s_PXENV_UNDI_GET_IFACE_INFO	undi_get_iface_info;
+	struct s_PXENV_UNDI_GET_STATE		undi_get_state;
+	struct s_PXENV_UNDI_ISR			undi_isr;
+	struct s_PXENV_FILE_OPEN		file_open;
+	struct s_PXENV_FILE_CLOSE		file_close;
+	struct s_PXENV_FILE_SELECT		file_select;
+	struct s_PXENV_FILE_READ		file_read;
+	struct s_PXENV_GET_FILE_SIZE		get_file_size;
+	struct s_PXENV_FILE_EXEC		file_exec;
+	struct s_PXENV_FILE_API_CHECK		file_api_check;
+	struct s_PXENV_FILE_EXIT_HOOK		file_exit_hook;
+};
+
+typedef union u_PXENV_ANY PXENV_ANY_t;
+
+/** An UNDI expansion ROM header */
+struct undi_rom_header {
+	/** Signature
+	 *
+	 * Must be equal to @c ROM_SIGNATURE
+	 */
+	UINT16_t Signature;
+	/** ROM length in 512-byte blocks */
+	UINT8_t ROMLength;
+	/** Unused */
+	UINT8_t unused[0x13];
+	/** Offset of the PXE ROM ID structure */
+	UINT16_t PXEROMID;
+	/** Offset of the PCI ROM structure */
+	UINT16_t PCIRHeader;
+} PACKED;
+
+/** Signature for an expansion ROM */
+#define ROM_SIGNATURE 0xaa55
+
+/** An UNDI ROM ID structure */
+struct undi_rom_id {
+	/** Signature
+	 *
+	 * Must be equal to @c UNDI_ROM_ID_SIGNATURE
+	 */
+	UINT32_t Signature;
+	/** Length of structure */
+	UINT8_t StructLength;
+	/** Checksum */
+	UINT8_t StructCksum;
+	/** Structure revision
+	 *
+	 * Must be zero.
+	 */
+	UINT8_t StructRev;
+	/** UNDI revision
+	 *
+	 * Version 2.1.0 is encoded as the byte sequence 0x00, 0x01, 0x02.
+	 */
+	UINT8_t UNDIRev[3];
+	/** Offset to UNDI loader */
+	UINT16_t UNDILoader;
+	/** Minimum required stack segment size */
+	UINT16_t StackSize;
+	/** Minimum required data segment size */
+	UINT16_t DataSize;
+	/** Minimum required code segment size */
+	UINT16_t CodeSize;
+} PACKED;
+
+/** Signature for an UNDI ROM ID structure */
+#define UNDI_ROM_ID_SIGNATURE \
+	( ( 'U' << 0 ) + ( 'N' << 8 ) + ( 'D' << 16 ) + ( 'I' << 24 ) )
+
+/** A PCI expansion header */
+struct pcir_header {
+	/** Signature
+	 *
+	 * Must be equal to @c PCIR_SIGNATURE
+	 */
+	uint32_t signature;
+	/** PCI vendor ID */
+	uint16_t vendor_id;
+	/** PCI device ID */
+	uint16_t device_id;
+} PACKED;
+
+/** Signature for an UNDI ROM ID structure */
+#define PCIR_SIGNATURE \
+	( ( 'P' << 0 ) + ( 'C' << 8 ) + ( 'I' << 16 ) + ( 'R' << 24 ) )
+
+
+extern struct net_device *pxe_netdev;
+
+extern void pxe_set_netdev ( struct net_device *netdev );
+
+extern void pxe_set_cached_filename ( const unsigned char *filename );
+
+#endif /* PXE_H */
diff --git a/gpxe/src/arch/i386/include/pxe_api.h b/gpxe/src/arch/i386/include/pxe_api.h
new file mode 100644
index 0000000..92f046f
--- /dev/null
+++ b/gpxe/src/arch/i386/include/pxe_api.h
@@ -0,0 +1,1909 @@
+#ifndef PXE_API_H
+#define PXE_API_H
+
+/*
+ * 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.
+ *
+ * As an alternative, at your option, you may use this file under the
+ * following terms, known as the "MIT license":
+ *
+ * Copyright (c) 2005-2009 Michael Brown <mbrown@fensystems.co.uk>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/** @file
+ *
+ * Preboot eXecution Environment (PXE) API
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include "pxe_types.h"
+
+/** @addtogroup pxe Preboot eXecution Environment (PXE) API
+ *  @{
+ */
+
+/** @defgroup pxe_api_call PXE entry points
+ *
+ * PXE entry points and calling conventions
+ *
+ *  @{
+ */
+
+/** The PXENV+ structure */
+struct s_PXENV {
+	/** Signature
+	 *
+	 * Contains the bytes 'P', 'X', 'E', 'N', 'V', '+'.
+	 */
+	UINT8_t		Signature[6];
+	/** PXE API version
+	 *
+	 * MSB is major version number, LSB is minor version number.
+	 * If the API version number is 0x0201 or greater, the !PXE
+	 * structure pointed to by #PXEPtr should be used instead of
+	 * this data structure.
+	 */
+	UINT16_t	Version;
+	UINT8_t		Length;		/**< Length of this structure */
+	/** Checksum
+	 *
+	 * The byte checksum of this structure (using the length in
+	 * #Length) must be zero.
+	 */
+	UINT8_t		Checksum;
+	SEGOFF16_t	RMEntry;	/**< Real-mode PXENV+ entry point */
+	/** Protected-mode PXENV+ entry point offset
+	 *
+	 * PXE 2.1 deprecates this entry point.  For protected-mode
+	 * API calls, use the !PXE structure pointed to by #PXEPtr
+	 * instead.
+	 */
+	UINT32_t	PMOffset;
+	/** Protected-mode PXENV+ entry point segment selector
+	 *
+	 * PXE 2.1 deprecates this entry point.  For protected-mode
+	 * API calls, use the !PXE structure pointed to by #PXEPtr
+	 * instead.
+	 */
+	SEGSEL_t	PMSelector;
+	SEGSEL_t	StackSeg;	/**< Stack segment selector */
+	UINT16_t	StackSize;	/**< Stack segment size */
+	SEGSEL_t	BC_CodeSeg;	/**< Base-code code segment selector */
+	UINT16_t	BC_CodeSize;	/**< Base-code code segment size */
+	SEGSEL_t	BC_DataSeg;	/**< Base-code data segment selector */
+	UINT16_t	BC_DataSize;	/**< Base-code data segment size */
+	SEGSEL_t	UNDIDataSeg;	/**< UNDI data segment selector */
+	UINT16_t	UNDIDataSize;	/**< UNDI data segment size */
+	SEGSEL_t	UNDICodeSeg;	/**< UNDI code segment selector */
+	UINT16_t	UNDICodeSize;	/**< UNDI code segment size */
+	/** Address of the !PXE structure
+	 *
+	 * This field is present only if #Version is 0x0201 or
+	 * greater.  If present, it points to a struct s_PXE.
+	 */
+	SEGOFF16_t	PXEPtr;
+} PACKED;
+
+typedef struct s_PXENV PXENV_t;
+
+/** The !PXE structure */
+struct s_PXE {
+	/** Signature
+	 *
+	 * Contains the bytes '!', 'P', 'X', 'E'.
+	 */
+	UINT8_t		Signature[4];
+	UINT8_t		StructLength;	/**< Length of this structure */
+	/** Checksum
+	 *
+	 * The byte checksum of this structure (using the length in
+	 * #StructLength) must be zero.
+	 */
+	UINT8_t		StructCksum;
+	/** Revision of this structure
+	 *
+	 * For PXE version 2.1, this field must be zero.
+	 */
+	UINT8_t		StructRev;
+	UINT8_t		reserved_1;	/**< Must be zero */
+	/** Address of the UNDI ROM ID structure
+	 *
+	 * This is a pointer to a struct s_UNDI_ROM_ID.
+	 */
+	SEGOFF16_t	UNDIROMID;
+	/** Address of the Base Code ROM ID structure
+	 *
+	 * This is a pointer to a struct s_BC_ROM_ID.
+	 */
+	SEGOFF16_t	BaseROMID;
+	/** 16-bit !PXE entry point
+	 *
+	 * This is the entry point for either real mode, or protected
+	 * mode with a 16-bit stack segment.
+	 */
+	SEGOFF16_t	EntryPointSP;
+	/** 32-bit !PXE entry point
+	 *
+	 * This is the entry point for protected mode with a 32-bit
+	 * stack segment.
+	 */
+	SEGOFF16_t	EntryPointESP;
+	/** Status call-out function
+	 *
+	 * @v 0		(if in a time-out loop)
+	 * @v n		Number of a received TFTP packet
+	 * @ret 0	Continue operation
+	 * @ret 1	Cancel operation
+	 *
+	 * This function will be called whenever the PXE stack is in
+	 * protected mode, is waiting for an event (e.g. a DHCP reply)
+	 * and wishes to allow the user to cancel the operation.
+	 * Parameters are passed in register %ax; the return value
+	 * must also be placed in register %ax.  All other registers
+	 * and flags @b must be preserved.
+	 *
+	 * In real mode, an internal function (that checks for a
+	 * keypress) will be used.
+	 *
+	 * If this field is set to -1, no status call-out function
+	 * will be used and consequently the user will not be allowed
+	 * to interrupt operations.
+	 *
+	 * @note The PXE specification version 2.1 defines the
+	 * StatusCallout field, mentions it 11 times, but nowhere
+	 * defines what it actually does or how it gets called.
+	 * Fortunately, the WfM specification version 1.1a deigns to
+	 * inform us of such petty details.
+	 */
+	SEGOFF16_t	StatusCallout;
+	UINT8_t		reserved_2;	/**< Must be zero */
+	/** Number of segment descriptors
+	 *
+	 * If this number is greater than 7, the remaining descriptors
+	 * follow immediately after #BC_CodeWrite.
+	 */
+	UINT8_t		SegDescCnt;
+	/** First protected-mode selector
+	 *
+	 * This is the segment selector value for the first segment
+	 * assigned to PXE.  Protected-mode selectors must be
+	 * consecutive, according to the PXE 2.1 specification, though
+	 * no reason is given.  Each #SEGDESC_t includes a field for
+	 * the segment selector, so this information is entirely
+	 * redundant.
+	 */
+	SEGSEL_t	FirstSelector;
+	/** Stack segment descriptor */
+	SEGDESC_t	Stack;
+	/** UNDI data segment descriptor */
+	SEGDESC_t	UNDIData;
+	/** UNDI code segment descriptor */
+	SEGDESC_t	UNDICode;
+	/** UNDI writable code segment descriptor */
+	SEGDESC_t	UNDICodeWrite;
+	/** Base-code data segment descriptor */
+	SEGDESC_t	BC_Data;
+	/** Base-code code segment descriptor */
+	SEGDESC_t	BC_Code;
+	/** Base-code writable code segment descriptor */
+	SEGDESC_t	BC_CodeWrite;
+} PACKED;
+
+typedef struct s_PXE PXE_t;
+
+/** @} */ /* pxe_api_call */
+
+/** @defgroup pxe_preboot_api PXE Preboot API
+ *
+ * General high-level functions: #PXENV_UNLOAD_STACK, #PXENV_START_UNDI etc.
+ *
+ * @{
+ */
+
+/** @defgroup pxenv_unload_stack PXENV_UNLOAD_STACK
+ *
+ *  UNLOAD BASE CODE STACK
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_unload_stack() */
+#define	PXENV_UNLOAD_STACK		0x0070
+
+/** Parameter block for pxenv_unload_stack() */
+struct s_PXENV_UNLOAD_STACK {
+	PXENV_STATUS_t Status;			/**< PXE status code */
+	UINT8_t reserved[10];			/**< Must be zero */
+} PACKED;
+
+typedef struct s_PXENV_UNLOAD_STACK PXENV_UNLOAD_STACK_t;
+
+extern PXENV_EXIT_t pxenv_unload_stack ( struct s_PXENV_UNLOAD_STACK
+					 *unload_stack );
+
+/** @} */ /* pxenv_unload_stack */
+
+/** @defgroup pxenv_get_cached_info PXENV_GET_CACHED_INFO
+ *
+ *  GET CACHED INFO
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_get_cached_info() */
+#define	PXENV_GET_CACHED_INFO		0x0071
+
+/** The client's DHCPDISCOVER packet */
+#define PXENV_PACKET_TYPE_DHCP_DISCOVER	1
+
+/** The DHCP server's DHCPACK packet */
+#define PXENV_PACKET_TYPE_DHCP_ACK	2
+
+/** The Boot Server's Discover Reply packet
+ *
+ * This packet contains DHCP option 60 set to "PXEClient", a valid
+ * boot file name, and may or may not contain MTFTP options.
+ */
+#define PXENV_PACKET_TYPE_CACHED_REPLY	3
+
+/** Parameter block for pxenv_get_cached_info() */
+struct s_PXENV_GET_CACHED_INFO {
+	PXENV_STATUS_t Status;			/**< PXE status code */
+	/** Packet type.
+	 *
+	 * Valid values are #PXENV_PACKET_TYPE_DHCP_DISCOVER,
+	 * #PXENV_PACKET_TYPE_DHCP_ACK or #PXENV_PACKET_TYPE_CACHED_REPLY
+	 */
+	UINT16_t PacketType;
+	UINT16_t BufferSize;			/**< Buffer size */
+	SEGOFF16_t Buffer;			/**< Buffer address */
+	UINT16_t BufferLimit;			/**< Maximum buffer size */
+} PACKED;
+
+typedef struct s_PXENV_GET_CACHED_INFO PXENV_GET_CACHED_INFO_t;
+
+#define BOOTP_REQ	1	/**< A BOOTP request packet */
+#define BOOTP_REP	2	/**< A BOOTP reply packet */
+
+/** DHCP broadcast flag
+ *
+ * Request a broadcast response (DHCPOFFER or DHCPACK) from the DHCP
+ * server.
+ */
+#define BOOTP_BCAST	0x8000
+
+#define VM_RFC1048	0x63825363L	/**< DHCP magic cookie */
+
+/** Maximum length of DHCP options */
+#define BOOTP_DHCPVEND	1024
+
+/** Format of buffer filled in by pxenv_get_cached_info()
+ *
+ * This somewhat convoluted data structure simply describes the layout
+ * of a DHCP packet.  Refer to RFC2131 section 2 for a full
+ * description.
+ */
+struct bootph {
+	/** Message opcode.
+	 *
+	 * Valid values are #BOOTP_REQ and #BOOTP_REP.
+	 */
+	UINT8_t opcode;
+	/** NIC hardware type.
+	 *
+	 * Valid values are as for s_PXENV_UNDI_GET_INFORMATION::HwType.
+	 */
+	UINT8_t Hardware;
+	UINT8_t Hardlen;		/**< MAC address length */
+	/** Gateway hops
+	 *
+	 * Zero in packets sent by the client.  May be non-zero in
+	 * replies from the DHCP server, if the reply comes via a DHCP
+	 * relay agent.
+	 */
+	UINT8_t Gatehops;
+	UINT32_t ident;			/**< DHCP transaction id (xid) */
+	/** Elapsed time
+	 *
+	 * Number of seconds since the client began the DHCP
+	 * transaction.
+	 */
+	UINT16_t seconds;
+	/** Flags
+	 *
+	 * This is the bitwise-OR of any of the following values:
+	 * #BOOTP_BCAST.
+	 */
+	UINT16_t Flags;
+	/** Client IP address
+	 *
+	 * Set only if the client already has an IP address.
+	 */
+	IP4_t cip;
+	/** Your IP address
+	 *
+	 * This is the IP address that the server assigns to the
+	 * client.
+	 */
+	IP4_t yip;
+	/** Server IP address
+	 *
+	 * This is the IP address of the BOOTP/DHCP server.
+	 */
+	IP4_t sip;
+	/** Gateway IP address
+	 *
+	 * This is the IP address of the BOOTP/DHCP relay agent, if
+	 * any.  It is @b not (necessarily) the address of the default
+	 * gateway for routing purposes.
+	 */
+	IP4_t gip;
+	MAC_ADDR_t CAddr;		/**< Client MAC address */
+	UINT8_t Sname[64];		/**< Server host name */
+	UINT8_t bootfile[128];		/**< Boot file name */
+	/** DHCP options
+	 *
+	 * Don't ask.  Just laugh.  Then burn a copy of the PXE
+	 * specification and send Intel an e-mail asking them if
+	 * they've figured out what a "union" does in C yet.
+	 */
+	union bootph_vendor {
+		UINT8_t d[BOOTP_DHCPVEND]; /**< DHCP options */
+		/** DHCP options */
+		struct bootph_vendor_v {
+			/** DHCP magic cookie
+			 *
+			 * Should have the value #VM_RFC1048.
+			 */
+			UINT8_t magic[4];
+			UINT32_t flags;	/**< BOOTP flags/opcodes */
+			/** "End of BOOTP vendor extensions"
+			 *
+			 * Abandon hope, all ye who consider the
+			 * purpose of this field.
+			 */
+			UINT8_t pad[56];
+		} v;
+	} vendor;
+} PACKED;
+
+typedef struct bootph BOOTPLAYER_t;
+
+extern PXENV_EXIT_t pxenv_get_cached_info ( struct s_PXENV_GET_CACHED_INFO
+					    *get_cached_info );
+
+/** @} */ /* pxenv_get_cached_info */
+
+/** @defgroup pxenv_restart_tftp PXENV_RESTART_TFTP
+ *
+ *  RESTART TFTP
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_restart_tftp() */
+#define	PXENV_RESTART_TFTP		0x0073
+
+/** Parameter block for pxenv_restart_tftp() */
+struct s_PXENV_TFTP_READ_FILE;
+
+typedef struct s_PXENV_RESTART_TFTP PXENV_RESTART_TFTP_t;
+
+extern PXENV_EXIT_t pxenv_restart_tftp ( struct s_PXENV_TFTP_READ_FILE
+					 *restart_tftp );
+
+/** @} */ /* pxenv_restart_tftp */
+
+/** @defgroup pxenv_start_undi PXENV_START_UNDI
+ *
+ *  START UNDI
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_start_undi() */
+#define	PXENV_START_UNDI		0x0000
+
+/** Parameter block for pxenv_start_undi() */
+struct s_PXENV_START_UNDI {
+	PXENV_STATUS_t Status;			/**< PXE status code */
+	/** %ax register as passed to the Option ROM initialisation routine.
+	 *
+	 * For a PCI device, this should contain the bus:dev:fn value
+	 * that uniquely identifies the PCI device in the system.  For
+	 * a non-PCI device, this field is not defined.
+	 */
+	UINT16_t AX;
+	/** %bx register as passed to the Option ROM initialisation routine.
+	 *
+	 * For an ISAPnP device, this should contain the Card Select
+	 * Number assigned to the ISAPnP card.  For non-ISAPnP
+	 * devices, this should contain 0xffff.
+	 */
+	UINT16_t BX;
+	/** %dx register as passed to the Option ROM initialisation routine.
+	 *
+	 * For an ISAPnP device, this should contain the ISAPnP Read
+	 * Port address as currently set in all ISAPnP cards.  If
+	 * there are no ISAPnP cards, this should contain 0xffff.  (If
+	 * this is a non-ISAPnP device, but there are ISAPnP cards in
+	 * the system, this value is not well defined.)
+	 */
+	UINT16_t DX;
+	/** %di register as passed to the Option ROM initialisation routine.
+	 *
+	 * This contains the #OFF16_t portion of a struct #s_SEGOFF16
+	 * that points to the System BIOS Plug and Play Installation
+	 * Check Structure.  (Refer to section 4.4 of the Plug and
+	 * Play BIOS specification for a description of this
+	 * structure.)
+	 *
+	 * @note The PXE specification defines the type of this field
+	 * as #UINT16_t.  For x86, #OFF16_t and #UINT16_t are
+	 * equivalent anyway; for other architectures #OFF16_t makes
+	 * more sense.
+	 */
+	OFF16_t DI;
+	/** %es register as passed to the Option ROM initialisation routine.
+	 *
+	 * This contains the #SEGSEL_t portion of a struct #s_SEGOFF16
+	 * that points to the System BIOS Plug and Play Installation
+	 * Check Structure.  (Refer to section 4.4 of the Plug and
+	 * Play BIOS specification for a description of this
+	 * structure.)
+	 *
+	 * @note The PXE specification defines the type of this field
+	 * as #UINT16_t.  For x86, #SEGSEL_t and #UINT16_t are
+	 * equivalent anyway; for other architectures #SEGSEL_t makes
+	 * more sense.
+	 */
+	SEGSEL_t ES;
+} PACKED;
+
+typedef struct s_PXENV_START_UNDI PXENV_START_UNDI_t;
+
+extern PXENV_EXIT_t pxenv_start_undi ( struct s_PXENV_START_UNDI *start_undi );
+
+/** @} */ /* pxenv_start_undi */
+
+/** @defgroup pxenv_stop_undi PXENV_STOP_UNDI
+ *
+ *  STOP UNDI
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_stop_undi() */
+#define	PXENV_STOP_UNDI			0x0015
+
+/** Parameter block for pxenv_stop_undi() */
+struct s_PXENV_STOP_UNDI {
+	PXENV_STATUS_t Status;			/**< PXE status code */
+} PACKED;
+
+typedef struct s_PXENV_STOP_UNDI PXENV_STOP_UNDI_t;
+
+extern PXENV_EXIT_t pxenv_stop_undi ( struct s_PXENV_STOP_UNDI *stop_undi );
+
+/** @} */ /* pxenv_stop_undi */
+
+/** @defgroup pxenv_start_base PXENV_START_BASE
+ *
+ *  START BASE
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_start_base() */
+#define	PXENV_START_BASE		0x0075
+
+/** Parameter block for pxenv_start_base() */
+struct s_PXENV_START_BASE {
+	PXENV_STATUS_t Status;			/**< PXE status code */
+} PACKED;
+
+typedef struct s_PXENV_START_BASE PXENV_START_BASE_t;
+
+extern PXENV_EXIT_t pxenv_start_base ( struct s_PXENV_START_BASE *start_base );
+
+/** @} */ /* pxenv_start_base */
+
+/** @defgroup pxenv_stop_base PXENV_STOP_BASE
+ *
+ *  STOP BASE
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_stop_base() */
+#define	PXENV_STOP_BASE			0x0076
+
+/** Parameter block for pxenv_stop_base() */
+struct s_PXENV_STOP_BASE {
+	PXENV_STATUS_t Status;			/**< PXE status code */
+} PACKED;
+
+typedef struct s_PXENV_STOP_BASE PXENV_STOP_BASE_t;
+
+extern PXENV_EXIT_t pxenv_stop_base ( struct s_PXENV_STOP_BASE *stop_base );
+
+/** @} */ /* pxenv_stop_base */
+
+/** @} */ /* pxe_preboot_api */
+
+/** @defgroup pxe_tftp_api PXE TFTP API
+ *
+ * Download files via TFTP or MTFTP
+ *
+ * @{
+ */
+
+/** @defgroup pxenv_tftp_open PXENV_TFTP_OPEN
+ *
+ *  TFTP OPEN
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_tftp_open() */
+#define	PXENV_TFTP_OPEN			0x0020
+
+/** Parameter block for pxenv_tftp_open() */
+struct s_PXENV_TFTP_OPEN {
+	PXENV_STATUS_t Status;			/**< PXE status code */
+	IP4_t ServerIPAddress;			/**< TFTP server IP address */
+	IP4_t GatewayIPAddress;			/**< Relay agent IP address */
+	UINT8_t FileName[128];			/**< File name */
+	UDP_PORT_t TFTPPort;			/**< TFTP server UDP port */
+	/** Requested size of TFTP packets
+	 *
+	 * This is the TFTP "blksize" option.  This must be at least
+	 * 512, since servers that do not support TFTP options cannot
+	 * negotiate blocksizes smaller than this.
+	 */
+	UINT16_t PacketSize;
+} PACKED;
+
+typedef struct s_PXENV_TFTP_OPEN PXENV_TFTP_OPEN_t;
+
+extern PXENV_EXIT_t pxenv_tftp_open ( struct s_PXENV_TFTP_OPEN *tftp_open );
+
+/** @} */ /* pxenv_tftp_open */
+
+/** @defgroup pxenv_tftp_close PXENV_TFTP_CLOSE
+ *
+ *  TFTP CLOSE
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_tftp_close() */
+#define	PXENV_TFTP_CLOSE		0x0021
+
+/** Parameter block for pxenv_tftp_close() */
+struct s_PXENV_TFTP_CLOSE {
+	PXENV_STATUS_t Status;			/**< PXE status code */
+} PACKED;
+
+typedef struct s_PXENV_TFTP_CLOSE PXENV_TFTP_CLOSE_t;
+
+extern PXENV_EXIT_t pxenv_tftp_close ( struct s_PXENV_TFTP_CLOSE *tftp_close );
+
+/** @} */ /* pxenv_tftp_close */
+
+/** @defgroup pxenv_tftp_read PXENV_TFTP_READ
+ *
+ *  TFTP READ
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_tftp_read() */
+#define	PXENV_TFTP_READ			0x0022
+
+/** Parameter block for pxenv_tftp_read() */
+struct s_PXENV_TFTP_READ {
+	PXENV_STATUS_t Status;			/**< PXE status code */
+	UINT16_t PacketNumber;			/**< TFTP packet number */
+	UINT16_t BufferSize;			/**< Size of data buffer */
+	SEGOFF16_t Buffer;			/**< Address of data buffer */
+} PACKED;
+
+typedef struct s_PXENV_TFTP_READ PXENV_TFTP_READ_t;
+
+extern PXENV_EXIT_t pxenv_tftp_read ( struct s_PXENV_TFTP_READ *tftp_read );
+
+/** @} */ /* pxenv_tftp_read */
+
+/** @defgroup pxenv_tftp_read_file PXENV_TFTP_READ_FILE
+ *
+ *  TFTP/MTFTP READ FILE
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_tftp_read_file() */
+#define	PXENV_TFTP_READ_FILE		0x0023
+
+/** Parameter block for pxenv_tftp_read_file() */
+struct s_PXENV_TFTP_READ_FILE {
+	PXENV_STATUS_t Status;			/**< PXE status code */
+	UINT8_t FileName[128];			/**< File name */
+	UINT32_t BufferSize;			/**< Size of data buffer */
+	ADDR32_t Buffer;			/**< Address of data buffer */
+	IP4_t ServerIPAddress;			/**< TFTP server IP address */
+	IP4_t GatewayIPAddress;			/**< Relay agent IP address */
+	/** File multicast IP address */
+	IP4_t McastIPAddress;
+	/** Client multicast listening port */
+	UDP_PORT_t TFTPClntPort;
+	/** Server multicast listening port */
+	UDP_PORT_t TFTPSrvPort;
+	/** TFTP open timeout.
+	 *
+	 * This is the timeout for receiving the first DATA or ACK
+	 * packets during the MTFTP Listen phase.
+	 */
+	UINT16_t TFTPOpenTimeOut;
+	/** TFTP reopen timeout.
+	 *
+	 * This is the timeout for receiving an ACK packet while in
+	 * the MTFTP Listen phase (when at least one ACK packet has
+	 * already been seen).
+	 */
+	UINT16_t TFTPReopenDelay;
+} PACKED;
+
+typedef struct s_PXENV_TFTP_READ_FILE PXENV_TFTP_READ_FILE_t;
+
+extern PXENV_EXIT_t pxenv_tftp_read_file ( struct s_PXENV_TFTP_READ_FILE
+					   *tftp_read_file );
+
+/** @} */ /* pxenv_tftp_read_file */
+
+/** @defgroup pxenv_tftp_get_fsize PXENV_TFTP_GET_FSIZE
+ *
+ *  TFTP GET FILE SIZE
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_tftp_get_fsize() */
+#define	PXENV_TFTP_GET_FSIZE		0x0025
+
+/** Parameter block for pxenv_tftp_get_fsize() */
+struct s_PXENV_TFTP_GET_FSIZE {
+	PXENV_STATUS_t Status;			/**< PXE status code */
+	IP4_t ServerIPAddress;			/**< TFTP server IP address */
+	IP4_t GatewayIPAddress;			/**< Relay agent IP address */
+	UINT8_t FileName[128];			/**< File name */
+	UINT32_t FileSize;			/**< Size of the file */
+} PACKED;
+
+typedef struct s_PXENV_TFTP_GET_FSIZE PXENV_TFTP_GET_FSIZE_t;
+
+extern PXENV_EXIT_t pxenv_tftp_get_fsize ( struct s_PXENV_TFTP_GET_FSIZE
+					   *get_fsize );
+
+/** @} */ /* pxenv_tftp_get_fsize */
+
+/** @} */ /* pxe_tftp_api */
+
+/** @defgroup pxe_udp_api PXE UDP API
+ *
+ * Transmit and receive UDP packets
+ *
+ * @{
+ */
+
+/** @defgroup pxenv_udp_open PXENV_UDP_OPEN
+ *
+ *  UDP OPEN
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_udp_open() */
+#define	PXENV_UDP_OPEN			0x0030
+
+/** Parameter block for pxenv_udp_open() */
+struct s_PXENV_UDP_OPEN {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	IP4_t		src_ip;		/**< IP address of this station */
+} PACKED;
+
+typedef struct s_PXENV_UDP_OPEN PXENV_UDP_OPEN_t;
+
+extern PXENV_EXIT_t pxenv_udp_open ( struct s_PXENV_UDP_OPEN *udp_open );
+
+/** @} */ /* pxenv_udp_open */
+
+/** @defgroup pxenv_udp_close PXENV_UDP_CLOSE
+ *
+ *  UDP CLOSE
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_udp_close() */
+#define	PXENV_UDP_CLOSE			0x0031
+
+/** Parameter block for pxenv_udp_close() */
+struct s_PXENV_UDP_CLOSE {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+} PACKED;
+
+typedef struct s_PXENV_UDP_CLOSE PXENV_UDP_CLOSE_t;
+
+extern PXENV_EXIT_t pxenv_udp_close ( struct s_PXENV_UDP_CLOSE *udp_close );
+
+/** @} */ /* pxenv_udp_close */
+
+/** @defgroup pxenv_udp_write PXENV_UDP_WRITE
+ *
+ *  UDP WRITE
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_udp_write() */
+#define	PXENV_UDP_WRITE			0x0033
+
+/** Parameter block for pxenv_udp_write() */
+struct s_PXENV_UDP_WRITE {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	IP4_t		ip;		/**< Destination IP address */
+	IP4_t		gw;		/**< Relay agent IP address */
+	UDP_PORT_t	src_port;	/**< Source UDP port */
+	UDP_PORT_t	dst_port;	/**< Destination UDP port */
+	UINT16_t	buffer_size;	/**< UDP payload buffer size */
+	SEGOFF16_t	buffer;		/**< UDP payload buffer address */
+} PACKED;
+
+typedef struct s_PXENV_UDP_WRITE PXENV_UDP_WRITE_t;
+
+extern PXENV_EXIT_t pxenv_udp_write ( struct s_PXENV_UDP_WRITE *udp_write );
+
+/** @} */ /* pxenv_udp_write */
+
+/** @defgroup pxenv_udp_read PXENV_UDP_READ
+ *
+ *  UDP READ
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_udp_read() */
+#define	PXENV_UDP_READ			0x0032
+
+/** Parameter block for pxenv_udp_read() */
+struct s_PXENV_UDP_READ {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	IP4_t		src_ip;		/**< Source IP address */
+	IP4_t		dest_ip;	/**< Destination IP address */
+	UDP_PORT_t	s_port;		/**< Source UDP port */
+	UDP_PORT_t	d_port;		/**< Destination UDP port */
+	UINT16_t	buffer_size;	/**< UDP payload buffer size */
+	SEGOFF16_t	buffer;		/**< UDP payload buffer address */
+} PACKED;
+
+typedef struct s_PXENV_UDP_READ PXENV_UDP_READ_t;
+
+extern PXENV_EXIT_t pxenv_udp_read ( struct s_PXENV_UDP_READ *udp_read );
+
+/** @} */ /* pxenv_udp_read */
+
+/** @} */ /* pxe_udp_api */
+
+/** @defgroup pxe_undi_api PXE UNDI API
+ *
+ * Direct control of the network interface card
+ *
+ * @{
+ */
+
+/** @defgroup pxenv_undi_startup PXENV_UNDI_STARTUP
+ *
+ *  UNDI STARTUP
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_startup() */
+#define	PXENV_UNDI_STARTUP		0x0001
+
+#define PXENV_BUS_ISA		0	/**< ISA bus type */
+#define PXENV_BUS_EISA		1	/**< EISA bus type */
+#define PXENV_BUS_MCA		2	/**< MCA bus type */
+#define PXENV_BUS_PCI		3	/**< PCI bus type */
+#define PXENV_BUS_VESA		4	/**< VESA bus type */
+#define PXENV_BUS_PCMCIA	5	/**< PCMCIA bus type */
+
+/** Parameter block for pxenv_undi_startup() */
+struct s_PXENV_UNDI_STARTUP {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_STARTUP PXENV_UNDI_STARTUP_t;
+
+extern PXENV_EXIT_t pxenv_undi_startup ( struct s_PXENV_UNDI_STARTUP
+					 *undi_startup );
+
+/** @} */ /* pxenv_undi_startup */
+
+/** @defgroup pxenv_undi_cleanup PXENV_UNDI_CLEANUP
+ *
+ *  UNDI CLEANUP
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_cleanup() */
+#define	PXENV_UNDI_CLEANUP		0x0002
+
+/** Parameter block for pxenv_undi_cleanup() */
+struct s_PXENV_UNDI_CLEANUP {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_CLEANUP PXENV_UNDI_CLEANUP_t;
+
+extern PXENV_EXIT_t pxenv_undi_cleanup ( struct s_PXENV_UNDI_CLEANUP
+					 *undi_cleanup );
+
+/** @} */ /* pxenv_undi_cleanup */
+
+/** @defgroup pxenv_undi_initialize PXENV_UNDI_INITIALIZE
+ *
+ *  UNDI INITIALIZE
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_initialize() */
+#define	PXENV_UNDI_INITIALIZE		0x0003
+
+/** Parameter block for pxenv_undi_initialize() */
+struct s_PXENV_UNDI_INITIALIZE {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	/** NDIS 2.0 configuration information, or NULL
+	 *
+	 * This is a pointer to the data structure returned by the
+	 * NDIS 2.0 GetProtocolManagerInfo() API call.  The data
+	 * structure is documented, in a rather haphazard way, in
+	 * section 4-17 of the NDIS 2.0 specification.
+	 */
+	ADDR32_t ProtocolIni;
+	UINT8_t reserved[8];		/**< Must be zero */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_INITIALIZE PXENV_UNDI_INITIALIZE_t;
+
+extern PXENV_EXIT_t pxenv_undi_initialize ( struct s_PXENV_UNDI_INITIALIZE
+					    *undi_initialize );
+
+/** @} */ /* pxenv_undi_initialize */
+
+/** @defgroup pxenv_undi_reset_adapter PXENV_UNDI_RESET_ADAPTER
+ *
+ *  UNDI RESET ADAPTER
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_reset_adapter() */
+#define	PXENV_UNDI_RESET_ADAPTER	0x0004
+
+/** Maximum number of multicast MAC addresses */
+#define MAXNUM_MCADDR	8
+
+/** List of multicast MAC addresses */
+struct s_PXENV_UNDI_MCAST_ADDRESS {
+	/** Number of multicast MAC addresses */
+	UINT16_t MCastAddrCount;
+	/** List of up to #MAXNUM_MCADDR multicast MAC addresses */
+	MAC_ADDR_t McastAddr[MAXNUM_MCADDR];
+} PACKED;
+
+typedef struct s_PXENV_UNDI_MCAST_ADDRESS PXENV_UNDI_MCAST_ADDRESS_t;
+
+/** Parameter block for pxenv_undi_reset_adapter() */
+struct s_PXENV_UNDI_RESET {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	/** Multicast MAC addresses */
+	struct s_PXENV_UNDI_MCAST_ADDRESS R_Mcast_Buf;
+} PACKED;
+
+typedef struct s_PXENV_UNDI_RESET PXENV_UNDI_RESET_t;
+
+extern PXENV_EXIT_t pxenv_undi_reset_adapter ( struct s_PXENV_UNDI_RESET
+					       *undi_reset_adapter );
+
+/** @} */ /* pxenv_undi_reset_adapter */
+
+/** @defgroup pxenv_undi_shutdown PXENV_UNDI_SHUTDOWN
+ *
+ *  UNDI SHUTDOWN
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_shutdown() */
+#define	PXENV_UNDI_SHUTDOWN		0x0005
+
+/** Parameter block for pxenv_undi_shutdown() */
+struct s_PXENV_UNDI_SHUTDOWN {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_SHUTDOWN PXENV_UNDI_SHUTDOWN_t;
+
+extern PXENV_EXIT_t pxenv_undi_shutdown ( struct s_PXENV_UNDI_SHUTDOWN
+					  *undi_shutdown );
+
+/** @} */ /* pxenv_undi_shutdown */
+
+/** @defgroup pxenv_undi_open PXENV_UNDI_OPEN
+ *
+ *  UNDI OPEN
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_open() */
+#define	PXENV_UNDI_OPEN			0x0006
+
+/** Accept "directed" packets
+ *
+ * These are packets addresses to either this adapter's MAC address or
+ * to any of the configured multicast MAC addresses (see
+ * #s_PXENV_UNDI_MCAST_ADDRESS).
+ */
+#define FLTR_DIRECTED	0x0001
+/** Accept broadcast packets */
+#define FLTR_BRDCST	0x0002
+/** Accept all packets; listen in promiscuous mode */
+#define FLTR_PRMSCS	0x0004
+/** Accept source-routed packets */
+#define FLTR_SRC_RTG	0x0008
+
+/** Parameter block for pxenv_undi_open() */
+struct s_PXENV_UNDI_OPEN {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	/** Open flags as defined in NDIS 2.0
+	 *
+	 * This is the OpenOptions field as passed to the NDIS 2.0
+	 * OpenAdapter() API call.  It is defined to be "adapter
+	 * specific", though 0 is guaranteed to be a valid value.
+	 */
+	UINT16_t OpenFlag;
+	/** Receive packet filter
+	 *
+	 * This is the bitwise-OR of any of the following flags:
+	 * #FLTR_DIRECTED, #FLTR_BRDCST, #FLTR_PRMSCS and
+	 * #FLTR_SRC_RTG.
+	 */
+	UINT16_t PktFilter;
+	/** Multicast MAC addresses */
+	struct s_PXENV_UNDI_MCAST_ADDRESS R_Mcast_Buf;
+} PACKED;
+
+typedef struct s_PXENV_UNDI_OPEN PXENV_UNDI_OPEN_t;
+
+extern PXENV_EXIT_t pxenv_undi_open ( struct s_PXENV_UNDI_OPEN *undi_open );
+
+/** @} */ /* pxenv_undi_open */
+
+/** @defgroup pxenv_undi_close PXENV_UNDI_CLOSE
+ *
+ *  UNDI CLOSE
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_close() */
+#define	PXENV_UNDI_CLOSE		0x0007
+
+/** Parameter block for pxenv_undi_close() */
+struct s_PXENV_UNDI_CLOSE {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_CLOSE PXENV_UNDI_CLOSE_t;
+
+extern PXENV_EXIT_t pxenv_undi_close ( struct s_PXENV_UNDI_CLOSE *undi_close );
+
+/** @} */ /* pxenv_undi_close */
+
+/** @defgroup pxenv_undi_transmit PXENV_UNDI_TRANSMIT
+ *
+ *  UNDI TRANSMIT PACKET
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_transmit() */
+#define	PXENV_UNDI_TRANSMIT		0x0008
+
+#define P_UNKNOWN	0		/**< Media header already filled in */
+#define P_IP		1		/**< IP protocol */
+#define P_ARP		2		/**< ARP protocol */
+#define P_RARP		3		/**< RARP protocol */
+#define P_OTHER		4		/**< Other protocol */
+
+#define XMT_DESTADDR	0x0000		/**< Unicast packet */
+#define XMT_BROADCAST	0x0001		/**< Broadcast packet */
+
+/** Maximum number of data blocks in a transmit buffer descriptor */
+#define MAX_DATA_BLKS	8
+
+/** A transmit buffer descriptor, as pointed to by s_PXENV_UNDI_TRANSMIT::TBD
+ */
+struct s_PXENV_UNDI_TBD {
+	UINT16_t ImmedLength;		/**< Length of the transmit buffer */
+	SEGOFF16_t Xmit;		/**< Address of the transmit buffer */
+	UINT16_t DataBlkCount;
+	/** Array of up to #MAX_DATA_BLKS additional transmit buffers */
+	struct DataBlk {
+		/** Always 1
+		 *
+		 * A value of 0 would indicate that #TDDataPtr were an
+		 * #ADDR32_t rather than a #SEGOFF16_t.  The PXE
+		 * specification version 2.1 explicitly states that
+		 * this is not supported; #TDDataPtr will always be a
+		 * #SEGOFF16_t.
+		 */
+		UINT8_t TDPtrType;
+		UINT8_t TDRsvdByte;	/**< Must be zero */
+		UINT16_t TDDataLen;	/**< Length of this transmit buffer */
+		SEGOFF16_t TDDataPtr;	/**< Address of this transmit buffer */
+	} DataBlock[MAX_DATA_BLKS];
+} PACKED;
+
+typedef struct s_PXENV_UNDI_TBD PXENV_UNDI_TBD_t;
+
+/** Parameter block for pxenv_undi_transmit() */
+struct s_PXENV_UNDI_TRANSMIT {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	/** Protocol
+	 *
+	 * Valid values are #P_UNKNOWN, #P_IP, #P_ARP or #P_RARP.  If
+	 * the caller has already filled in the media header, this
+	 * field must be set to #P_UNKNOWN.
+	 */
+	UINT8_t Protocol;
+	/** Unicast/broadcast flag
+	 *
+	 * Valid values are #XMT_DESTADDR or #XMT_BROADCAST.
+	 */
+	UINT8_t XmitFlag;
+	SEGOFF16_t DestAddr;		/**< Destination MAC address */
+	/** Address of the Transmit Buffer Descriptor
+	 *
+	 * This is a pointer to a struct s_PXENV_UNDI_TBD.
+	 */
+	SEGOFF16_t TBD;
+	UINT32_t Reserved[2];		/**< Must be zero */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_TRANSMIT PXENV_UNDI_TRANSMIT_t;
+
+extern PXENV_EXIT_t pxenv_undi_transmit ( struct s_PXENV_UNDI_TRANSMIT
+					  *undi_transmit );
+
+/** @} */ /* pxenv_undi_transmit */
+
+/** @defgroup pxenv_undi_set_mcast_address PXENV_UNDI_SET_MCAST_ADDRESS
+ *
+ *  UNDI SET MULTICAST ADDRESS
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_set_mcast_address() */
+#define	PXENV_UNDI_SET_MCAST_ADDRESS	0x0009
+
+/** Parameter block for pxenv_undi_set_mcast_address() */
+struct s_PXENV_UNDI_SET_MCAST_ADDRESS {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	/** List of multicast addresses */
+	struct s_PXENV_UNDI_MCAST_ADDRESS R_Mcast_Buf;
+} PACKED;
+
+typedef struct s_PXENV_UNDI_SET_MCAST_ADDRESS PXENV_UNDI_SET_MCAST_ADDRESS_t;
+
+extern PXENV_EXIT_t pxenv_undi_set_mcast_address (
+	       struct s_PXENV_UNDI_SET_MCAST_ADDRESS *undi_set_mcast_address );
+
+/** @} */ /* pxenv_undi_set_mcast_address */
+
+/** @defgroup pxenv_undi_set_station_address PXENV_UNDI_SET_STATION_ADDRESS
+ *
+ *  UNDI SET STATION ADDRESS
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_set_station_address() */
+#define	PXENV_UNDI_SET_STATION_ADDRESS	0x000a
+
+/** Parameter block for pxenv_undi_set_station_address() */
+struct s_PXENV_UNDI_SET_STATION_ADDRESS {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	MAC_ADDR_t StationAddress;	/**< Station MAC address */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_SET_STATION_ADDRESS PXENV_UNDI_SET_STATION_ADDRESS_t;
+
+extern PXENV_EXIT_t pxenv_undi_set_station_address (
+	   struct s_PXENV_UNDI_SET_STATION_ADDRESS *undi_set_station_address );
+
+/** @} */ /* pxenv_undi_set_station_address */
+
+/** @defgroup pxenv_undi_set_packet_filter PXENV_UNDI_SET_PACKET_FILTER
+ *
+ *  UNDI SET PACKET FILTER
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_set_packet_filter() */
+#define	PXENV_UNDI_SET_PACKET_FILTER	0x000b
+
+/** Parameter block for pxenv_undi_set_packet_filter() */
+struct s_PXENV_UNDI_SET_PACKET_FILTER {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	/** Receive packet filter
+	 *
+	 * This field takes the same values as
+	 * s_PXENV_UNDI_OPEN::PktFilter.
+	 *
+	 * @note Yes, this field is a different size to
+	 * s_PXENV_UNDI_OPEN::PktFilter.  Blame "the managers at Intel
+	 * who apparently let a consultant come up with the spec
+	 * without any kind of adult supervision" (quote from hpa).
+	 */
+	UINT8_t filter;
+} PACKED;
+
+typedef struct s_PXENV_UNDI_SET_PACKET_FILTER PXENV_UNDI_SET_PACKET_FILTER_t;
+
+extern PXENV_EXIT_t pxenv_undi_set_packet_filter (
+	       struct s_PXENV_UNDI_SET_PACKET_FILTER *undi_set_packet_filter );
+
+/** @} */ /* pxenv_undi_set_packet_filter */
+
+/** @defgroup pxenv_undi_get_information PXENV_UNDI_GET_INFORMATION
+ *
+ *  UNDI GET INFORMATION
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_get_information() */
+#define	PXENV_UNDI_GET_INFORMATION	0x000c
+
+#define ETHER_TYPE		1	/**< Ethernet (10Mb) */
+#define EXP_ETHER_TYPE		2	/**< Experimental Ethernet (3Mb) */
+#define AX25_TYPE		3	/**< Amateur Radio AX.25 */
+#define TOKEN_RING_TYPE		4	/**< Proteon ProNET Token Ring */
+#define CHAOS_TYPE		5	/**< Chaos */
+#define IEEE_TYPE		6	/**< IEEE 802 Networks */
+#define ARCNET_TYPE		7	/**< ARCNET */
+
+/** Parameter block for pxenv_undi_get_information() */
+struct s_PXENV_UNDI_GET_INFORMATION {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	UINT16_t BaseIo;		/**< I/O base address */
+	UINT16_t IntNumber;		/**< IRQ number */
+	UINT16_t MaxTranUnit;		/**< Adapter MTU */
+	/** Hardware type
+	 *
+	 * Valid values are defined in RFC1010 ("Assigned numbers"),
+	 * and are #ETHER_TYPE, #EXP_ETHER_TYPE, #AX25_TYPE,
+	 * #TOKEN_RING_TYPE, #CHAOS_TYPE, #IEEE_TYPE or #ARCNET_TYPE.
+	 */
+	UINT16_t HwType;
+	UINT16_t HwAddrLen;		/**< MAC address length */
+	MAC_ADDR_t CurrentNodeAddress;	/**< Current MAC address */
+	MAC_ADDR_t PermNodeAddress;	/**< Permanent (EEPROM) MAC address */
+	SEGSEL_t ROMAddress;		/**< Real-mode ROM segment address */
+	UINT16_t RxBufCt;		/**< Receive queue length */
+	UINT16_t TxBufCt;		/**< Transmit queue length */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_GET_INFORMATION PXENV_UNDI_GET_INFORMATION_t;
+
+extern PXENV_EXIT_t pxenv_undi_get_information (
+		   struct s_PXENV_UNDI_GET_INFORMATION *undi_get_information );
+
+/** @} */ /* pxenv_undi_get_information */
+
+/** @defgroup pxenv_undi_get_statistics PXENV_UNDI_GET_STATISTICS
+ *
+ *  UNDI GET STATISTICS
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_get_statistics() */
+#define	PXENV_UNDI_GET_STATISTICS	0x000d
+
+/** Parameter block for pxenv_undi_get_statistics() */
+struct s_PXENV_UNDI_GET_STATISTICS {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	UINT32_t XmtGoodFrames;		/**< Successful transmission count */
+	UINT32_t RcvGoodFrames;		/**< Successful reception count */
+	UINT32_t RcvCRCErrors;		/**< Receive CRC error count */
+	UINT32_t RcvResourceErrors;	/**< Receive queue overflow count */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_GET_STATISTICS PXENV_UNDI_GET_STATISTICS_t;
+
+extern PXENV_EXIT_t pxenv_undi_get_statistics (
+		     struct s_PXENV_UNDI_GET_STATISTICS *undi_get_statistics );
+
+/** @} */ /* pxenv_undi_get_statistics */
+
+/** @defgroup pxenv_undi_clear_statistics PXENV_UNDI_CLEAR_STATISTICS
+ *
+ *  UNDI CLEAR STATISTICS
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_clear_statistics() */
+#define	PXENV_UNDI_CLEAR_STATISTICS	0x000e
+
+/** Parameter block for pxenv_undi_clear_statistics() */
+struct s_PXENV_UNDI_CLEAR_STATISTICS {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_CLEAR_STATISTICS PXENV_UNDI_CLEAR_STATISTICS_t;
+
+extern PXENV_EXIT_t pxenv_undi_clear_statistics (
+		 struct s_PXENV_UNDI_CLEAR_STATISTICS *undi_clear_statistics );
+
+/** @} */ /* pxenv_undi_clear_statistics */
+
+/** @defgroup pxenv_undi_initiate_diags PXENV_UNDI_INITIATE_DIAGS
+ *
+ *  UNDI INITIATE DIAGS
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_initiate_diags() */
+#define	PXENV_UNDI_INITIATE_DIAGS	0x000f
+
+/** Parameter block for pxenv_undi_initiate_diags() */
+struct s_PXENV_UNDI_INITIATE_DIAGS {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_INITIATE_DIAGS PXENV_UNDI_INITIATE_DIAGS_t;
+
+extern PXENV_EXIT_t pxenv_undi_initiate_diags (
+		     struct s_PXENV_UNDI_INITIATE_DIAGS *undi_initiate_diags );
+
+/** @} */ /* pxenv_undi_initiate_diags */
+
+/** @defgroup pxenv_undi_force_interrupt PXENV_UNDI_FORCE_INTERRUPT
+ *
+ *  UNDI FORCE INTERRUPT
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_force_interrupt() */
+#define	PXENV_UNDI_FORCE_INTERRUPT	0x0010
+
+/** Parameter block for pxenv_undi_force_interrupt() */
+struct s_PXENV_UNDI_FORCE_INTERRUPT {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_FORCE_INTERRUPT PXENV_UNDI_FORCE_INTERRUPT_t;
+
+extern PXENV_EXIT_t pxenv_undi_force_interrupt (
+		   struct s_PXENV_UNDI_FORCE_INTERRUPT *undi_force_interrupt );
+
+/** @} */ /* pxenv_undi_force_interrupt */
+
+/** @defgroup pxenv_undi_get_mcast_address PXENV_UNDI_GET_MCAST_ADDRESS
+ *
+ *  UNDI GET MULTICAST ADDRESS
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_get_mcast_address() */
+#define	PXENV_UNDI_GET_MCAST_ADDRESS	0x0011
+
+/** Parameter block for pxenv_undi_get_mcast_address() */
+struct s_PXENV_UNDI_GET_MCAST_ADDRESS {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	IP4_t InetAddr;			/**< Multicast IP address */
+	MAC_ADDR_t MediaAddr;		/**< Multicast MAC address */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_GET_MCAST_ADDRESS PXENV_UNDI_GET_MCAST_ADDRESS_t;
+
+extern PXENV_EXIT_t pxenv_undi_get_mcast_address (
+	       struct s_PXENV_UNDI_GET_MCAST_ADDRESS *undi_get_mcast_address );
+
+/** @} */ /* pxenv_undi_get_mcast_address */
+
+/** @defgroup pxenv_undi_get_nic_type PXENV_UNDI_GET_NIC_TYPE
+ *
+ *  UNDI GET NIC TYPE
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_get_nic_type() */
+#define	PXENV_UNDI_GET_NIC_TYPE		0x0012
+
+#define PCI_NIC		2		/**< PCI network card */
+#define PnP_NIC		3		/**< ISAPnP network card */
+#define CardBus_NIC	4		/**< CardBus network card */
+
+/** Information for a PCI or equivalent NIC */
+struct pci_nic_info {
+	UINT16_t Vendor_ID;		/**< PCI vendor ID */
+	UINT16_t Dev_ID;		/**< PCI device ID */
+	UINT8_t Base_Class;		/**< PCI base class */
+	UINT8_t Sub_Class;		/**< PCI sub class */
+	UINT8_t Prog_Intf;		/**< PCI programming interface */
+	UINT8_t Rev;			/**< PCI revision */
+	UINT16_t BusDevFunc;		/**< PCI bus:dev:fn address */
+	UINT16_t SubVendor_ID;		/**< PCI subvendor ID */
+	UINT16_t SubDevice_ID;		/**< PCI subdevice ID */
+} PACKED;
+ 
+/** Information for an ISAPnP or equivalent NIC */
+struct pnp_nic_info {
+	UINT32_t EISA_Dev_ID;		/**< EISA device ID */
+	UINT8_t Base_Class;		/**< Base class */
+	UINT8_t Sub_Class;		/**< Sub class */
+	UINT8_t Prog_Intf;		/**< Programming interface */
+	/** Card Select Number assigned to card */
+	UINT16_t CardSelNum;
+} PACKED;
+
+/** Parameter block for pxenv_undi_get_nic_type() */
+struct s_PXENV_UNDI_GET_NIC_TYPE {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	/** NIC type
+	 *
+	 * Valid values are #PCI_NIC, #PnP_NIC or #CardBus_NIC.
+	 */
+	UINT8_t NicType;
+	/** NIC information */
+	union nic_type_info {
+		/** NIC information (if #NicType==#PCI_NIC) */
+		struct pci_nic_info pci;
+		/** NIC information (if #NicType==#CardBus_NIC) */
+		struct pci_nic_info cardbus;
+		/** NIC information (if #NicType==#PnP_NIC) */
+		struct pnp_nic_info pnp;
+	} info;
+} PACKED;
+
+typedef struct s_PXENV_UNDI_GET_NIC_TYPE PXENV_UNDI_GET_NIC_TYPE_t;
+
+extern PXENV_EXIT_t pxenv_undi_get_nic_type ( 
+			 struct s_PXENV_UNDI_GET_NIC_TYPE *undi_get_nic_type );
+
+/** @} */ /* pxenv_undi_get_nic_type */
+
+/** @defgroup pxenv_undi_get_iface_info PXENV_UNDI_GET_IFACE_INFO
+ *
+ *  UNDI GET IFACE INFO
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_get_iface_info() */
+#define	PXENV_UNDI_GET_IFACE_INFO	0x0013
+
+/** Broadcast supported */
+#define SUPPORTED_BROADCAST		0x0001
+/** Multicast supported */
+#define SUPPORTED_MULTICAST		0x0002
+/** Functional/group addressing supported */
+#define SUPPORTED_GROUP			0x0004
+/** Promiscuous mode supported */
+#define SUPPORTED_PROMISCUOUS		0x0008
+/** Software settable station address */
+#define SUPPORTED_SET_STATION_ADDRESS	0x0010
+/** InitiateDiagnostics supported */
+#define SUPPORTED_DIAGNOSTICS		0x0040
+/** Reset MAC supported */
+#define SUPPORTED_RESET			0x0400
+/** Open / Close Adapter supported */
+#define SUPPORTED_OPEN_CLOSE		0x0800
+/** Interrupt Request supported */
+#define SUPPORTED_IRQ			0x1000
+
+/** Parameter block for pxenv_undi_get_iface_info() */
+struct s_PXENV_UNDI_GET_IFACE_INFO {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	/** Interface type
+	 *
+	 * This is defined in the NDIS 2.0 specification to be one of
+	 * the strings "802.3", "802.4", "802.5", "802.6", "DIX",
+	 * "DIX+802.3", "APPLETALK", "ARCNET", "FDDI", "SDLC", "BSC",
+	 * "HDLC", or "ISDN".
+	 *
+	 * "Normal" Ethernet, for various historical reasons, is
+	 * "DIX+802.3".
+	 */
+	UINT8_t IfaceType[16];
+	UINT32_t LinkSpeed;		/**< Link speed, in bits per second */
+	/** Service flags
+	 *
+	 * These are the "service flags" defined in the "MAC
+	 * Service-Specific Characteristics" table in the NDIS 2.0
+	 * specification.  Almost all of them are irrelevant to PXE.
+	 */
+	UINT32_t ServiceFlags;
+	UINT32_t Reserved[4];		/**< Must be zero */
+} PACKED;
+
+typedef struct s_PXENV_UNDI_GET_IFACE_INFO PXENV_UNDI_GET_IFACE_INFO_t;
+
+extern PXENV_EXIT_t pxenv_undi_get_iface_info (
+		     struct s_PXENV_UNDI_GET_IFACE_INFO *undi_get_iface_info );
+
+/** @} */ /* pxenv_undi_get_iface_info */
+
+/** @defgroup pxenv_undi_get_state PXENV_UNDI_GET_STATE
+ *
+ *  UNDI GET STATE
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_get_state() */
+#define PXENV_UNDI_GET_STATE		0x0015
+
+/** pxenv_start_undi() has been called */
+#define PXE_UNDI_GET_STATE_STARTED	1
+/** pxenv_undi_initialize() has been called */
+#define PXE_UNDI_GET_STATE_INITIALIZED	2
+/** pxenv_undi_open() has been called */
+#define PXE_UNDI_GET_STATE_OPENED	3
+
+/** Parameter block for pxenv_undi_get_state() */
+struct s_PXENV_UNDI_GET_STATE {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	/** Current state of the UNDI driver
+	 *
+	 * Valid values are #PXE_UNDI_GET_STATE_STARTED,
+	 * #PXE_UNDI_GET_STATE_INITIALIZED or
+	 * #PXE_UNDI_GET_STATE_OPENED.
+	 */
+	UINT8_t UNDIstate;
+} PACKED;
+
+typedef struct s_PXENV_UNDI_GET_STATE PXENV_UNDI_GET_STATE_t;
+
+extern PXENV_EXIT_t pxenv_undi_get_state ( struct s_PXENV_UNDI_GET_STATE
+					   *undi_get_state );
+
+/** @} */ /* pxenv_undi_get_state */
+
+/** @defgroup pxenv_undi_isr PXENV_UNDI_ISR
+ *
+ *  UNDI ISR
+ *
+ *  @{
+ */
+
+/** PXE API function code for pxenv_undi_isr() */
+#define	PXENV_UNDI_ISR			0x0014
+
+/** Determine whether or not this is our interrupt */
+#define PXENV_UNDI_ISR_IN_START		1
+/** Start processing interrupt */
+#define PXENV_UNDI_ISR_IN_PROCESS	2
+/** Continue processing interrupt */
+#define PXENV_UNDI_ISR_IN_GET_NEXT	3
+/** This interrupt was ours */
+#define PXENV_UNDI_ISR_OUT_OURS		0
+/** This interrupt was not ours */
+#define PXENV_UNDI_ISR_OUT_NOT_OURS	1
+/** Finished processing interrupt */
+#define PXENV_UNDI_ISR_OUT_DONE		0
+/** A packet transmission has completed */
+#define PXENV_UNDI_ISR_OUT_TRANSMIT	2
+/** A packet has been received */
+#define PXENV_UNDI_ISR_OUT_RECEIVE	3
+/** We are already in the middle of processing an interrupt */
+#define PXENV_UNDI_ISR_OUT_BUSY		4
+
+/** Unicast packet (or packet captured in promiscuous mode) */
+#define P_DIRECTED	0
+/** Broadcast packet */
+#define P_BROADCAST	1
+/** Multicast packet */
+#define P_MULTICAST	2
+
+/** Parameter block for pxenv_undi_isr() */
+struct s_PXENV_UNDI_ISR {
+	PXENV_STATUS_t	Status;		/**< PXE status code */
+	/** Function flag
+	 *
+	 * Valid values are #PXENV_UNDI_ISR_IN_START,
+	 * #PXENV_UNDI_ISR_IN_PROCESS, #PXENV_UNDI_ISR_IN_GET_NEXT,
+	 * #PXENV_UNDI_ISR_OUT_OURS, #PXENV_UNDI_ISR_OUT_NOT_OURS,
+	 * #PXENV_UNDI_ISR_OUT_DONE, #PXENV_UNDI_ISR_OUT_TRANSMIT,
+	 * #PXENV_UNDI_ISR_OUT_RECEIVE or #PXENV_UNDI_ISR_OUT_BUSY.
+	 */
+	UINT16_t FuncFlag;
+	UINT16_t BufferLength;		/**< Data buffer length */
+	UINT16_t FrameLength;		/**< Total frame length */
+	UINT16_t FrameHeaderLength;	/**< Frame header length */
+	SEGOFF16_t Frame;		/**< Data buffer address */
+	/** Protocol type
+	 *
+	 * Valid values are #P_IP, #P_ARP, #P_RARP or #P_OTHER.
+	 */
+	UINT8_t ProtType;
+	/** Packet type
+	 *
+	 * Valid values are #P_DIRECTED, #P_BROADCAST or #P_MULTICAST.
+	 */
+	UINT8_t PktType;
+} PACKED;
+
+typedef struct s_PXENV_UNDI_ISR PXENV_UNDI_ISR_t;
+
+extern PXENV_EXIT_t pxenv_undi_isr ( struct s_PXENV_UNDI_ISR *undi_isr );
+
+/** @} */ /* pxenv_undi_isr */
+
+/** @} */ /* pxe_undi_api */
+
+/** @defgroup pxe_file_api PXE FILE API
+ *
+ * POSIX-like file operations
+ *
+ * @{
+ */
+
+/** @defgroup pxenv_file_open PXENV_FILE_OPEN
+ *
+ * FILE OPEN
+ *
+ * @{
+ */
+
+/** PXE API function code for pxenv_file_open() */
+#define PXENV_FILE_OPEN			0x00e0
+
+/** Parameter block for pxenv_file_open() */
+struct s_PXENV_FILE_OPEN {
+	PXENV_STATUS_t Status;		/**< PXE status code */
+	UINT16_t FileHandle;		/**< File handle */
+	SEGOFF16_t FileName;		/**< File URL */
+	UINT32_t Reserved;		/**< Reserved */
+} PACKED;
+
+typedef struct s_PXENV_FILE_OPEN PXENV_FILE_OPEN_t;
+
+extern PXENV_EXIT_t pxenv_file_open ( struct s_PXENV_FILE_OPEN *file_open );
+
+/** @} */ /* pxenv_file_open */
+
+/** @defgroup pxenv_file_close PXENV_FILE_CLOSE
+ *
+ * FILE CLOSE
+ *
+ * @{
+ */
+
+/** PXE API function code for pxenv_file_close() */
+#define PXENV_FILE_CLOSE		0x00e1
+
+/** Parameter block for pxenv_file_close() */
+struct s_PXENV_FILE_CLOSE {
+	PXENV_STATUS_t Status;		/**< PXE status code */
+	UINT16_t FileHandle;		/**< File handle */
+} PACKED;
+
+typedef struct s_PXENV_FILE_CLOSE PXENV_FILE_CLOSE_t;
+
+extern PXENV_EXIT_t pxenv_file_close ( struct s_PXENV_FILE_CLOSE
+				       *file_close );
+
+/** @} */ /* pxenv_file_close */
+
+/** @defgroup pxenv_file_select PXENV_FILE_SELECT
+ *
+ * FILE SELECT
+ *
+ * @{
+ */
+
+/** PXE API function code for pxenv_file_select() */
+#define PXENV_FILE_SELECT		0x00e2
+
+/** File is ready for reading */
+#define RDY_READ			0x0001
+
+/** Parameter block for pxenv_file_select() */
+struct s_PXENV_FILE_SELECT {
+	PXENV_STATUS_t Status;		/**< PXE status code */
+	UINT16_t FileHandle;		/**< File handle */
+	UINT16_t Ready;			/**< Indication of readiness */
+} PACKED;
+
+typedef struct s_PXENV_FILE_SELECT PXENV_FILE_SELECT_t;
+
+extern PXENV_EXIT_t pxenv_file_select ( struct s_PXENV_FILE_SELECT
+					*file_select );
+
+/** @} */ /* pxenv_file_select */
+
+/** @defgroup pxenv_file_read PXENV_FILE_READ
+ *
+ * FILE READ
+ *
+ * @{
+ */
+
+/** PXE API function code for pxenv_file_read() */
+#define PXENV_FILE_READ		0x00e3
+
+/** Parameter block for pxenv_file_read() */
+struct s_PXENV_FILE_READ {
+	PXENV_STATUS_t Status;		/**< PXE status code */
+	UINT16_t FileHandle;		/**< File handle */
+	UINT16_t BufferSize;		/**< Data buffer size */
+	SEGOFF16_t Buffer;		/**< Data buffer */
+} PACKED;
+
+typedef struct s_PXENV_FILE_READ PXENV_FILE_READ_t;
+
+extern PXENV_EXIT_t pxenv_file_read ( struct s_PXENV_FILE_READ *file_read );
+
+/** @} */ /* pxenv_file_read */
+
+/** @defgroup pxenv_get_file_size PXENV_GET_FILE_SIZE
+ *
+ * GET FILE SIZE
+ *
+ * @{
+ */
+
+/** PXE API function code for pxenv_get_file_size() */
+#define PXENV_GET_FILE_SIZE		0x00e4
+
+/** Parameter block for pxenv_get_file_size() */
+struct s_PXENV_GET_FILE_SIZE {
+	PXENV_STATUS_t Status;		/**< PXE status code */
+	UINT16_t FileHandle;		/**< File handle */
+	UINT32_t FileSize;		/**< File size */
+} PACKED;
+
+typedef struct s_PXENV_GET_FILE_SIZE PXENV_GET_FILE_SIZE_t;
+
+extern PXENV_EXIT_t pxenv_get_file_size ( struct s_PXENV_GET_FILE_SIZE
+					  *get_file_size );
+
+/** @} */ /* pxenv_get_file_size */
+
+/** @defgroup pxenv_file_exec PXENV_FILE_EXEC
+ *
+ * FILE EXEC
+ *
+ * @{
+ */
+
+/** PXE API function code for pxenv_file_exec() */
+#define PXENV_FILE_EXEC			0x00e5
+
+/** Parameter block for pxenv_file_exec() */
+struct s_PXENV_FILE_EXEC {
+	PXENV_STATUS_t Status;		/**< PXE status code */
+	SEGOFF16_t Command;		/**< Command to execute */
+} PACKED;
+
+typedef struct s_PXENV_FILE_EXEC PXENV_FILE_EXEC_t;
+
+extern PXENV_EXIT_t pxenv_file_exec ( struct s_PXENV_FILE_EXEC *file_exec );
+
+/** @} */ /* pxenv_file_exec */
+
+/** @defgroup pxenv_file_api_check PXENV_FILE_API_CHECK
+ *
+ * FILE API CHECK
+ *
+ * @{
+ */
+
+/** PXE API function code for pxenv_file_api_check() */
+#define PXENV_FILE_API_CHECK		0x00e6
+
+/** Parameter block for pxenv_file_api_check() */
+struct s_PXENV_FILE_API_CHECK {
+	PXENV_STATUS_t Status;		/**< PXE status code */
+	UINT16_t Size;			/**< Size of structure  */
+	UINT32_t Magic;			/**< Magic number */
+	UINT32_t Provider;		/**< Implementation identifier */
+	UINT32_t APIMask;		/**< Supported API functions */
+	UINT32_t Flags;			/**< Reserved for the future */
+} PACKED;
+
+typedef struct s_PXENV_FILE_API_CHECK PXENV_FILE_API_CHECK_t;
+
+extern PXENV_EXIT_t pxenv_file_api_check ( struct s_PXENV_FILE_API_CHECK *file_api_check );
+
+/** @} */ /* pxenv_file_api_check */
+
+/** @defgroup pxenv_file_exit_hook PXENV_FILE_EXIT_HOOK
+ *
+ * FILE EXIT HOOK
+ *
+ * @{
+ */
+
+/** PXE API function code for pxenv_file_exit_hook() */
+#define PXENV_FILE_EXIT_HOOK			0x00e7
+
+/** Parameter block for pxenv_file_exit_hook() */
+struct s_PXENV_FILE_EXIT_HOOK {
+	PXENV_STATUS_t Status;		/**< PXE status code */
+	SEGOFF16_t Hook;		/**< SEG16:OFF16 to jump to */
+} PACKED;
+
+typedef struct s_PXENV_FILE_EXIT_HOOK PXENV_FILE_EXIT_HOOK_t;
+
+extern PXENV_EXIT_t pxenv_file_exit_hook ( struct s_PXENV_FILE_EXIT_HOOK *file_exit_hook );
+
+/** @} */ /* pxenv_file_exit_hook */
+
+/** @} */ /* pxe_file_api */
+
+/** @defgroup pxe_loader_api PXE Loader API
+ *
+ * The UNDI ROM loader API
+ *
+ * @{
+ */
+
+/** Parameter block for undi_loader() */
+struct s_UNDI_LOADER {
+	/** PXE status code */
+	PXENV_STATUS_t Status;
+	/** %ax register as for PXENV_START_UNDI */
+	UINT16_t AX;
+	/** %bx register as for PXENV_START_UNDI */
+	UINT16_t BX;
+	/** %dx register as for PXENV_START_UNDI */
+	UINT16_t DX;
+	/** %di register as for PXENV_START_UNDI */
+	OFF16_t DI;
+	/** %es register as for PXENV_START_UNDI */
+	SEGSEL_t ES;
+	/** UNDI data segment
+	 *
+	 * @note The PXE specification defines the type of this field
+	 * as #UINT16_t.  For x86, #SEGSEL_t and #UINT16_t are
+	 * equivalent anyway; for other architectures #SEGSEL_t makes
+	 * more sense.
+	 */
+	SEGSEL_t UNDI_DS;
+	/** UNDI code segment
+	 *
+	 * @note The PXE specification defines the type of this field
+	 * as #UINT16_t.  For x86, #SEGSEL_t and #UINT16_t are
+	 * equivalent anyway; for other architectures #SEGSEL_t makes
+	 * more sense.
+	 */
+	SEGSEL_t UNDI_CS;
+	/** Address of the !PXE structure (a struct s_PXE) */
+	SEGOFF16_t PXEptr;
+	/** Address of the PXENV+ structure (a struct s_PXENV) */
+	SEGOFF16_t PXENVptr;
+} PACKED;
+
+typedef struct s_UNDI_LOADER UNDI_LOADER_t;
+
+extern PXENV_EXIT_t undi_loader ( struct s_UNDI_LOADER *undi_loader );
+
+/** @} */ /* pxe_loader_api */
+
+/** @} */ /* pxe */
+
+/** @page pxe_notes Etherboot PXE implementation notes
+
+@section pxe_routing IP routing
+
+Several PXE API calls (e.g. pxenv_tftp_open() and pxenv_udp_write())
+allow for the caller to specify a "relay agent IP address", often in a
+field called "gateway" or similar.  The PXE specification states that
+"The IP layer should provide space for a minimum of four routing
+entries obtained from the default router and static route DHCP option
+tags in the DHCPACK message, plus any non-zero giaddr field from the
+DHCPOFFER message(s) accepted by the client".
+
+The DHCP static route option ("option static-routes" in dhcpd.conf)
+works only for classed IP routing (i.e. it provides no way to specify
+a subnet mask).  Since virtually everything now uses classless IP
+routing, the DHCP static route option is almost totally useless, and
+is (according to the dhcp-options man page) not implemented by any of
+the popular DHCP clients.
+
+This leaves the caller-specified "relay agent IP address", the giaddr
+field from the DHCPOFFER message(s) and the default gateway(s)
+provided via the routers option ("option routers" in dhcpd.conf) in
+the DHCPACK message.  Each of these is a default gateway address.
+It's a fair bet that the routers option should take priority over the
+giaddr field, since the routers option has to be explicitly specified
+by the DHCP server operator.  Similarly, it's fair to assume that the
+caller-specified "relay agent IP address", if present, should take
+priority over any other routing table entries.
+
+@bug Etherboot currently ignores all potential sources of routing
+information other than the first router provided to it by a DHCP
+routers option.
+
+@section pxe_x86_modes x86 processor mode restrictions
+
+On the x86 platform, different PXE API calls have different
+restrictions on the processor modes (real or protected) that can be
+used.  See the individual API call descriptions for the restrictions
+that apply to any particular call.
+
+@subsection pxe_x86_pmode16 Real mode, or protected-mode with 16-bit stack
+
+The PXE specification states that the API function can be called in
+protected mode only if the s_PXE::StatusCallout field is set to a
+non-zero value, and that the API function cannot be called with a
+32-bit stack segment.
+
+Etherboot does not enforce either of these restrictions; they seem (as
+with so much of the PXE specification) to be artifacts of the Intel
+implementation.
+
+*/
+
+#endif /* PXE_API_H */
diff --git a/gpxe/src/arch/i386/include/pxe_call.h b/gpxe/src/arch/i386/include/pxe_call.h
new file mode 100644
index 0000000..4d24561
--- /dev/null
+++ b/gpxe/src/arch/i386/include/pxe_call.h
@@ -0,0 +1,57 @@
+#ifndef _PXE_CALL_H
+#define _PXE_CALL_H
+
+/** @file
+ *
+ * PXE API entry point
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <pxe_api.h>
+#include <realmode.h>
+
+struct net_device;
+
+/** PXE load address segment */
+#define PXE_LOAD_SEGMENT 0
+
+/** PXE load address offset */
+#define PXE_LOAD_OFFSET 0x7c00
+
+/** PXE physical load address */
+#define PXE_LOAD_PHYS ( ( PXE_LOAD_SEGMENT << 4 ) + PXE_LOAD_OFFSET )
+
+/** !PXE structure */
+extern struct s_PXE __text16 ( ppxe );
+#define ppxe __use_text16 ( ppxe )
+
+/** PXENV+ structure */
+extern struct s_PXENV __text16 ( pxenv );
+#define pxenv __use_text16 ( pxenv )
+
+extern void pxe_activate ( struct net_device *netdev );
+extern int pxe_deactivate ( void );
+extern int pxe_start_nbp ( void );
+extern __asmcall void pxe_api_call ( struct i386_all_regs *ix86 );
+extern int _pxe_api_call_weak ( struct i386_all_regs *ix86 )
+	__attribute__ (( weak ));
+
+/**
+ * Dispatch PXE API call weakly
+ *
+ * @v ix86		Registers for PXE call
+ * @ret present		Zero if the PXE stack is present, nonzero if not
+ *
+ * A successful return only indicates that the PXE stack was available
+ * for dispatching the call; it says nothing about the success of
+ * whatever the call asked for.
+ */
+static inline int pxe_api_call_weak ( struct i386_all_regs *ix86 )
+{
+	if ( _pxe_api_call_weak != NULL )
+		return _pxe_api_call_weak ( ix86 );
+	return -1;
+}
+
+#endif /* _PXE_CALL_H */
diff --git a/gpxe/src/arch/i386/include/pxe_types.h b/gpxe/src/arch/i386/include/pxe_types.h
new file mode 100644
index 0000000..a6516d2
--- /dev/null
+++ b/gpxe/src/arch/i386/include/pxe_types.h
@@ -0,0 +1,127 @@
+#ifndef PXE_TYPES_H
+#define PXE_TYPES_H
+
+/** @file
+ *
+ * PXE data types
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <errno.h> /* PXE status codes */
+
+/** @addtogroup pxe Preboot eXecution Environment (PXE) API
+ *  @{
+ */
+
+/** @defgroup pxe_types PXE data types
+ *
+ * Basic PXE data types such as #UINT16_t, #ADDR32_t, #SEGSEL_t etc.
+ *
+ * These definitions are based on Table 1-1 ("Data Type Definitions")
+ * in the Intel PXE specification version 2.1.  They have been
+ * generalised to non-x86 architectures where possible.
+ *
+ * @{
+ */
+
+/** An 8-bit unsigned integer */
+typedef uint8_t UINT8_t;
+
+/** A 16-bit unsigned integer */
+typedef uint16_t UINT16_t;
+
+/** A 32-bit unsigned integer */
+typedef uint32_t UINT32_t;
+
+/** A PXE exit code.
+ *
+ * Permitted values are #PXENV_EXIT_SUCCESS and #PXENV_EXIT_FAILURE.
+ *
+ */
+typedef UINT16_t PXENV_EXIT_t;
+#define PXENV_EXIT_SUCCESS	0x0000	/**< No error occurred */
+#define PXENV_EXIT_FAILURE	0x0001	/**< An error occurred */
+
+/** A PXE status code.
+ *
+ * Status codes are defined in errno.h.
+ *
+ */
+typedef UINT16_t PXENV_STATUS_t;
+
+/** An IPv4 address.
+ *
+ * @note This data type is in network (big-endian) byte order.
+ *
+ */
+typedef UINT32_t IP4_t;
+
+/** A UDP port.
+ *
+ * @note This data type is in network (big-endian) byte order.
+ *
+ */
+typedef UINT16_t UDP_PORT_t;
+
+/** Maximum length of a MAC address */
+#define MAC_ADDR_LEN 16
+
+/** A MAC address */
+typedef UINT8_t MAC_ADDR_t[MAC_ADDR_LEN];
+
+#ifndef HAVE_ARCH_ADDR32
+/** A physical address.
+ *
+ * For x86, this is a 32-bit physical address, and is therefore
+ * limited to the low 4GB.
+ *
+ */
+typedef UINT32_t ADDR32_t;
+#endif
+
+#ifndef HAVE_ARCH_SEGSEL
+/** A segment selector.
+ *
+ * For x86, this is a real mode segment (0x0000-0xffff), or a
+ * protected-mode segment selector, such as could be loaded into a
+ * segment register.
+ *
+ */
+typedef UINT16_t SEGSEL_t;
+#endif
+
+#ifndef HAVE_ARCH_OFF16
+/** An offset within a segment identified by #SEGSEL
+ *
+ * For x86, this is a 16-bit offset.
+ *
+ */
+typedef UINT16_t OFF16_t;
+#endif
+
+/** A segment:offset address
+ *
+ * For x86, this is a 16-bit real-mode or protected-mode
+ * segment:offset address.
+ *
+ */
+typedef struct s_SEGOFF16 {
+	OFF16_t		offset;		/**< Offset within the segment */
+	SEGSEL_t	segment;	/**< Segment selector */
+} PACKED SEGOFF16_t;
+
+/** A segment descriptor */
+typedef struct s_SEGDESC {
+	SEGSEL_t	segment_address;	/**< Segment selector */
+	ADDR32_t	Physical_address;	/**< Segment base address */
+	OFF16_t		Seg_size;		/**< Size of the segment */
+} PACKED SEGDESC_t;
+
+/** @} */ /* pxe_types */
+
+/** @} */ /* pxe */
+
+#endif /* PXE_TYPES_H */
diff --git a/gpxe/src/arch/i386/include/pxeparent.h b/gpxe/src/arch/i386/include/pxeparent.h
new file mode 100644
index 0000000..b31e24a
--- /dev/null
+++ b/gpxe/src/arch/i386/include/pxeparent.h
@@ -0,0 +1,11 @@
+#ifndef PXEPARENT_H
+#define PXEPARENT_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <pxe_types.h>
+
+extern int pxeparent_call ( SEGOFF16_t entry, unsigned int function,
+			    void *params, size_t params_len );
+
+#endif
diff --git a/gpxe/src/arch/i386/include/realmode.h b/gpxe/src/arch/i386/include/realmode.h
new file mode 100644
index 0000000..a0a830b
--- /dev/null
+++ b/gpxe/src/arch/i386/include/realmode.h
@@ -0,0 +1,127 @@
+#ifndef REALMODE_H
+#define REALMODE_H
+
+#include <stdint.h>
+#include <registers.h>
+#include <gpxe/uaccess.h>
+
+/*
+ * Data structures and type definitions
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/*
+ * Declaration of variables in .data16
+ *
+ * To place a variable in the .data16 segment, declare it using the
+ * pattern:
+ *
+ *   int __data16 ( foo );
+ *   #define foo __use_data16 ( foo );
+ *
+ *   extern uint32_t __data16 ( bar );
+ *   #define bar __use_data16 ( bar );
+ *
+ *   static long __data16 ( baz ) = 0xff000000UL;
+ *   #define baz __use_data16 ( baz );
+ *
+ * i.e. take a normal declaration, add __data16() around the variable
+ * name, and add a line saying "#define <name> __use_data16 ( <name> )
+ *
+ * You can then access them just like any other variable, for example
+ *
+ *   int x = foo + bar;
+ *
+ * This magic is achieved at a cost of only around 7 extra bytes per
+ * group of accesses to .data16 variables.  When using KEEP_IT_REAL,
+ * there is no extra cost.
+ *
+ * You should place variables in .data16 when they need to be accessed
+ * by real-mode code.  Real-mode assembly (e.g. as created by
+ * REAL_CODE()) can access these variables via the usual data segment.
+ * You can therefore write something like
+ *
+ *   static uint16_t __data16 ( foo );
+ *   #define foo __use_data16 ( foo )
+ *
+ *   int bar ( void ) {
+ *     __asm__ __volatile__ ( REAL_CODE ( "int $0xff\n\t"
+ *                                        "movw %ax, foo" )
+ *                            : : );
+ *     return foo;
+ *   }
+ *
+ * Variables may also be placed in .text16 using __text16 and
+ * __use_text16.  Some variables (e.g. chained interrupt vectors) fit
+ * most naturally in .text16; most should be in .data16.
+ *
+ * If you have only a pointer to a magic symbol within .data16 or
+ * .text16, rather than the symbol itself, you can attempt to extract
+ * the underlying symbol name using __from_data16() or
+ * __from_text16().  This is not for the faint-hearted; check the
+ * assembler output to make sure that it's doing the right thing.
+ */
+
+/**
+ * Copy data to base memory
+ *
+ * @v dest_seg		Destination segment
+ * @v dest_off		Destination offset
+ * @v src		Source
+ * @v len		Length
+ */
+static inline __always_inline void
+copy_to_real ( unsigned int dest_seg, unsigned int dest_off,
+	       void *src, size_t n ) {
+	copy_to_user ( real_to_user ( dest_seg, dest_off ), 0, src, n );
+}
+
+/**
+ * Copy data to base memory
+ *
+ * @v dest		Destination
+ * @v src_seg		Source segment
+ * @v src_off		Source offset
+ * @v len		Length
+ */
+static inline __always_inline void
+copy_from_real ( void *dest, unsigned int src_seg,
+		 unsigned int src_off, size_t n ) {
+	copy_from_user ( dest, real_to_user ( src_seg, src_off ), 0, n );
+}
+
+/**
+ * Write a single variable to base memory
+ *
+ * @v var		Variable to write
+ * @v dest_seg		Destination segment
+ * @v dest_off		Destination offset
+ */
+#define put_real( var, dest_seg, dest_off ) \
+	copy_to_real ( (dest_seg), (dest_off), &(var), sizeof (var) )
+
+/**
+ * Read a single variable from base memory
+ *
+ * @v var		Variable to read
+ * @v src_seg		Source segment
+ * @v src_off		Source offset
+ */
+#define get_real( var, src_seg, src_off ) \
+	copy_from_real ( &(var), (src_seg), (src_off), sizeof (var) )
+
+/*
+ * REAL_CODE ( asm_code_str )
+ *
+ * This can be used in inline assembly to create a fragment of code
+ * that will execute in real mode.  For example: to write a character
+ * to the BIOS console using INT 10, you would do something like:
+ *
+ *     __asm__ __volatile__ ( REAL_CODE ( "int $0x16" )
+ *			      : "=a" ( character ) : "a" ( 0x0000 ) );
+ *
+ */
+
+#endif /* REALMODE_H */
diff --git a/gpxe/src/arch/i386/include/registers.h b/gpxe/src/arch/i386/include/registers.h
new file mode 100644
index 0000000..2839e2b
--- /dev/null
+++ b/gpxe/src/arch/i386/include/registers.h
@@ -0,0 +1,198 @@
+#ifndef REGISTERS_H
+#define REGISTERS_H
+
+/** @file
+ *
+ * i386 registers.
+ *
+ * This file defines data structures that allow easy access to i386
+ * register dumps.
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+
+/**
+ * A 16-bit general register.
+ *
+ * This type encapsulates a 16-bit register such as %ax, %bx, %cx,
+ * %dx, %si, %di, %bp or %sp.
+ *
+ */
+typedef union {
+	struct {
+		union {
+			uint8_t l;
+			uint8_t byte;
+		};
+		uint8_t h;
+	} PACKED;
+	uint16_t word;
+} PACKED reg16_t;
+
+/**
+ * A 32-bit general register.
+ *
+ * This type encapsulates a 32-bit register such as %eax, %ebx, %ecx,
+ * %edx, %esi, %edi, %ebp or %esp.
+ *
+ */
+typedef union {
+	struct {
+		union {
+			uint8_t l;
+			uint8_t byte;
+		};
+		uint8_t h;
+	} PACKED;
+	uint16_t word;
+	uint32_t dword;
+} PACKED reg32_t;
+
+/**
+ * A 32-bit general register dump.
+ *
+ * This is the data structure that is created on the stack by the @c
+ * pushal instruction, and can be read back using the @c popal
+ * instruction.
+ *
+ */
+struct i386_regs {
+	union {
+		uint16_t di;
+		uint32_t edi;
+	};
+	union {
+		uint16_t si;
+		uint32_t esi;
+	};
+	union {
+		uint16_t bp;
+		uint32_t ebp;
+	};
+	union {
+		uint16_t sp;
+		uint32_t esp;
+	};
+	union {
+		struct {
+			uint8_t bl;
+			uint8_t bh;
+		} PACKED;
+		uint16_t bx;
+		uint32_t ebx;
+	};
+	union {
+		struct {
+			uint8_t dl;
+			uint8_t dh;
+		} PACKED;
+		uint16_t dx;
+		uint32_t edx;
+	};
+	union {
+		struct {
+			uint8_t cl;
+			uint8_t ch;
+		} PACKED;
+		uint16_t cx;
+		uint32_t ecx;
+	};
+	union {
+		struct {
+			uint8_t al;
+			uint8_t ah;
+		} PACKED;
+		uint16_t ax;
+		uint32_t eax;
+	};
+} PACKED;
+
+/**
+ * A segment register dump.
+ *
+ * The i386 has no equivalent of the @c pushal or @c popal
+ * instructions for the segment registers.  We adopt the convention of
+ * always using the sequences
+ *
+ * @code
+ *
+ *   pushw %gs ; pushw %fs ; pushw %es ; pushw %ds ; pushw %ss ; pushw %cs
+ *
+ * @endcode
+ *
+ * and
+ *
+ * @code
+ *
+ *   addw $4, %sp ; popw %ds ; popw %es ; popw %fs ; popw %gs
+ *
+ * @endcode
+ *
+ * This is the data structure that is created and read back by these
+ * instruction sequences.
+ *
+ */
+struct i386_seg_regs {
+	uint16_t cs;
+	uint16_t ss;
+	uint16_t ds;
+	uint16_t es;
+	uint16_t fs;
+	uint16_t gs;
+} PACKED;
+
+/**
+ * A full register dump.
+ *
+ * This data structure is created by the instructions
+ *
+ * @code
+ *
+ *   pushfl
+ *   pushal
+ *   pushw %gs ; pushw %fs ; pushw %es ; pushw %ds ; pushw %ss ; pushw %cs
+ *
+ * @endcode
+ *
+ * and can be read back using the instructions
+ *
+ * @code
+ *
+ *   addw $4, %sp ; popw %ds ; popw %es ; popw %fs ; popw %gs
+ *   popal
+ *   popfl
+ *
+ * @endcode
+ *
+ * prot_call() and kir_call() create this data structure on the stack
+ * and pass in a pointer to this structure.
+ *
+ */
+struct i386_all_regs {
+	struct i386_seg_regs segs;
+	struct i386_regs regs;
+	uint32_t flags;
+} PACKED;
+
+/* Flags */
+#define CF ( 1 <<  0 )
+#define PF ( 1 <<  2 )
+#define AF ( 1 <<  4 )
+#define ZF ( 1 <<  6 )
+#define SF ( 1 <<  7 )
+#define OF ( 1 << 11 )
+
+/* Segment:offset structure.  Note that the order within the structure
+ * is offset:segment.
+ */
+struct segoff {
+	uint16_t offset;
+	uint16_t segment;
+} PACKED;
+
+typedef struct segoff segoff_t;
+
+#endif /* REGISTERS_H */
diff --git a/gpxe/src/arch/i386/include/setjmp.h b/gpxe/src/arch/i386/include/setjmp.h
new file mode 100644
index 0000000..5d3c11b
--- /dev/null
+++ b/gpxe/src/arch/i386/include/setjmp.h
@@ -0,0 +1,40 @@
+#ifndef ETHERBOOT_SETJMP_H
+#define ETHERBOOT_SETJMP_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <realmode.h>
+
+/** A jump buffer */
+typedef struct {
+	uint32_t retaddr;
+	uint32_t ebx;
+	uint32_t esp;
+	uint32_t ebp;
+	uint32_t esi;
+	uint32_t edi;
+} jmp_buf[1];
+
+/** A real-mode-extended jump buffer */
+typedef struct {
+	jmp_buf env;
+	uint16_t rm_ss;
+	uint16_t rm_sp;
+} rmjmp_buf[1];
+
+extern int __asmcall setjmp ( jmp_buf env );
+extern void __asmcall longjmp ( jmp_buf env, int val );
+
+#define rmsetjmp( _env ) ( {			\
+	(_env)->rm_ss = rm_ss;			\
+	(_env)->rm_sp = rm_sp;			\
+	setjmp ( (_env)->env ); } )		\
+
+#define rmlongjmp( _env, _val ) do {		\
+	rm_ss = (_env)->rm_ss;			\
+	rm_sp = (_env)->rm_sp;			\
+	longjmp ( (_env)->env, (_val) );	\
+	} while ( 0 )
+
+#endif /* ETHERBOOT_SETJMP_H */
diff --git a/gpxe/src/arch/i386/include/undi.h b/gpxe/src/arch/i386/include/undi.h
new file mode 100644
index 0000000..de6925b
--- /dev/null
+++ b/gpxe/src/arch/i386/include/undi.h
@@ -0,0 +1,106 @@
+#ifndef _UNDI_H
+#define _UNDI_H
+
+/** @file
+ *
+ * UNDI driver
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#ifndef ASSEMBLY
+
+#include <gpxe/device.h>
+#include <pxe_types.h>
+
+/** An UNDI device
+ *
+ * This structure is used by assembly code as well as C; do not alter
+ * this structure without editing pxeprefix.S to match.
+ */
+struct undi_device {
+	/** PXENV+ structure address */
+	SEGOFF16_t pxenv;
+	/** !PXE structure address */
+	SEGOFF16_t ppxe;
+	/** Entry point */
+	SEGOFF16_t entry;
+	/** Free base memory after load */
+	UINT16_t fbms;
+	/** Free base memory prior to load */
+	UINT16_t restore_fbms;
+	/** PCI bus:dev.fn, or @c UNDI_NO_PCI_BUSDEVFN */
+	UINT16_t pci_busdevfn;
+	/** ISAPnP card select number, or @c UNDI_NO_ISAPNP_CSN */
+	UINT16_t isapnp_csn;
+	/** ISAPnP read port, or @c UNDI_NO_ISAPNP_READ_PORT */
+	UINT16_t isapnp_read_port;
+	/** PCI vendor ID
+	 *
+	 * Filled in only for the preloaded UNDI device by pxeprefix.S
+	 */
+	UINT16_t pci_vendor;
+	/** PCI device ID 
+	 *
+	 * Filled in only for the preloaded UNDI device by pxeprefix.S
+	 */
+	UINT16_t pci_device;
+	/** Flags
+	 *
+	 * This is the bitwise OR of zero or more UNDI_FL_XXX
+	 * constants.
+	 */
+	UINT16_t flags;
+
+	/** Generic device */
+	struct device dev;
+	/** Driver-private data
+	 *
+	 * Use undi_set_drvdata() and undi_get_drvdata() to access this
+	 * field.
+	 */
+	void *priv;
+} __attribute__ (( packed ));
+
+/**
+ * Set UNDI driver-private data
+ *
+ * @v undi		UNDI device
+ * @v priv		Private data
+ */
+static inline void undi_set_drvdata ( struct undi_device *undi, void *priv ) {
+	undi->priv = priv;
+}
+
+/**
+ * Get UNDI driver-private data
+ *
+ * @v undi		UNDI device
+ * @ret priv		Private data
+ */
+static inline void * undi_get_drvdata ( struct undi_device *undi ) {
+	return undi->priv;
+}
+
+#endif /* ASSEMBLY */
+
+/** PCI bus:dev.fn field is invalid */
+#define UNDI_NO_PCI_BUSDEVFN 0xffff
+
+/** ISAPnP card select number field is invalid */
+#define UNDI_NO_ISAPNP_CSN 0xffff
+
+/** ISAPnP read port field is invalid */
+#define UNDI_NO_ISAPNP_READ_PORT 0xffff
+
+/** UNDI flag: START_UNDI has been called */
+#define UNDI_FL_STARTED 0x0001
+
+/** UNDI flag: UNDI_STARTUP and UNDI_INITIALIZE have been called */
+#define UNDI_FL_INITIALIZED 0x0002
+
+/** UNDI flag: keep stack resident */
+#define UNDI_FL_KEEP_ALL 0x0004
+
+#endif /* _UNDI_H */
diff --git a/gpxe/src/arch/i386/include/undiload.h b/gpxe/src/arch/i386/include/undiload.h
new file mode 100644
index 0000000..426830e
--- /dev/null
+++ b/gpxe/src/arch/i386/include/undiload.h
@@ -0,0 +1,35 @@
+#ifndef _UNDILOAD_H
+#define _UNDILOAD_H
+
+/** @file
+ *
+ * UNDI load/unload
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+struct undi_device;
+struct undi_rom;
+
+extern int undi_load ( struct undi_device *undi, struct undi_rom *undirom );
+extern int undi_unload ( struct undi_device *undi );
+
+/**
+ * Call UNDI loader to create a pixie
+ *
+ * @v undi		UNDI device
+ * @v undirom		UNDI ROM
+ * @v pci_busdevfn	PCI bus:dev.fn
+ * @ret rc		Return status code
+ */
+static inline int undi_load_pci ( struct undi_device *undi,
+				  struct undi_rom *undirom,
+				  unsigned int pci_busdevfn ) {
+	undi->pci_busdevfn = pci_busdevfn;
+	undi->isapnp_csn = UNDI_NO_ISAPNP_CSN;
+	undi->isapnp_read_port = UNDI_NO_ISAPNP_READ_PORT;
+	return undi_load ( undi, undirom );
+}
+
+#endif /* _UNDILOAD_H */
diff --git a/gpxe/src/arch/i386/include/undinet.h b/gpxe/src/arch/i386/include/undinet.h
new file mode 100644
index 0000000..c3c17c1
--- /dev/null
+++ b/gpxe/src/arch/i386/include/undinet.h
@@ -0,0 +1,17 @@
+#ifndef _UNDINET_H
+#define _UNDINET_H
+
+/** @file
+ *
+ * UNDI network device driver
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+struct undi_device;
+
+extern int undinet_probe ( struct undi_device *undi );
+extern void undinet_remove ( struct undi_device *undi );
+
+#endif /* _UNDINET_H */
diff --git a/gpxe/src/arch/i386/include/undipreload.h b/gpxe/src/arch/i386/include/undipreload.h
new file mode 100644
index 0000000..de9b8fb
--- /dev/null
+++ b/gpxe/src/arch/i386/include/undipreload.h
@@ -0,0 +1,18 @@
+#ifndef _UNDIPRELOAD_H
+#define _UNDIPRELOAD_H
+
+/** @file
+ *
+ * Preloaded UNDI stack
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <realmode.h>
+#include <undi.h>
+
+extern struct undi_device __data16 ( preloaded_undi );
+#define preloaded_undi __use_data16 ( preloaded_undi )
+
+#endif /* _UNDIPRELOAD_H */
diff --git a/gpxe/src/arch/i386/include/undirom.h b/gpxe/src/arch/i386/include/undirom.h
new file mode 100644
index 0000000..86d7077
--- /dev/null
+++ b/gpxe/src/arch/i386/include/undirom.h
@@ -0,0 +1,53 @@
+#ifndef _UNDIROM_H
+#define _UNDIROM_H
+
+/** @file
+ *
+ * UNDI expansion ROMs
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <pxe_types.h>
+
+/** An UNDI PCI device ID */
+struct undi_pci_device_id {
+	/** PCI vendor ID */
+	unsigned int vendor_id;
+	/** PCI device ID */
+	unsigned int device_id;
+};
+
+/** An UNDI device ID */
+union undi_device_id {
+	/** PCI device ID */
+	struct undi_pci_device_id pci;
+};
+
+/** An UNDI ROM */
+struct undi_rom {
+	/** List of UNDI ROMs */
+	struct list_head list;
+	/** ROM segment address */
+	unsigned int rom_segment;
+	/** UNDI loader entry point */
+	SEGOFF16_t loader_entry;
+	/** Code segment size */
+	size_t code_size;
+	/** Data segment size */
+	size_t data_size;
+	/** Bus type
+	 *
+	 * Values are as used by @c PXENV_UNDI_GET_NIC_TYPE
+	 */
+	unsigned int bus_type;
+	/** Device ID */
+	union undi_device_id bus_id;
+};
+
+extern struct undi_rom * undirom_find_pci ( unsigned int vendor_id,
+					    unsigned int device_id,
+					    unsigned int rombase );
+
+#endif /* _UNDIROM_H */
diff --git a/gpxe/src/arch/i386/include/vga.h b/gpxe/src/arch/i386/include/vga.h
new file mode 100644
index 0000000..01fc39d
--- /dev/null
+++ b/gpxe/src/arch/i386/include/vga.h
@@ -0,0 +1,228 @@
+/*
+ *
+ * modified
+ * by Steve M. Gehlbach <steve@kesa.com>
+ *
+ * Originally  from linux/drivers/video/vga16.c by
+ * Ben Pfaff <pfaffben@debian.org> and Petr Vandrovec <VANDROVE@vc.cvut.cz>
+ * Copyright 1999 Ben Pfaff <pfaffben@debian.org> and Petr Vandrovec <VANDROVE@vc.cvut.cz>
+ * Based on VGA info at http://www.goodnet.com/~tinara/FreeVGA/home.htm
+ * Based on VESA framebuffer (c) 1998 Gerd Knorr <kraxel@goldbach.in-berlin.de>
+ *
+ */ 
+
+#ifndef VGA_H_INCL
+#define VGA_H_INCL 1
+
+//#include <cpu/p5/io.h>
+
+#define u8 unsigned char
+#define u16 unsigned short
+#define u32 unsigned int
+#define __u32 u32
+
+#define VERROR -1
+#define CHAR_HEIGHT 16
+#define LINES 25
+#define COLS 80
+
+// macros for writing to vga regs
+#define write_crtc(data,addr) outb(addr,CRT_IC); outb(data,CRT_DC)
+#define write_att(data,addr) inb(IS1_RC); inb(0x80); outb(addr,ATT_IW); inb(0x80); outb(data,ATT_IW); inb(0x80)
+#define write_seq(data,addr) outb(addr,SEQ_I); outb(data,SEQ_D)
+#define write_gra(data,addr) outb(addr,GRA_I); outb(data,GRA_D)
+u8 read_seq_b(u16 addr);
+u8 read_gra_b(u16 addr);
+u8 read_crtc_b(u16 addr);
+u8 read_att_b(u16 addr);
+
+
+#ifdef VGA_HARDWARE_FIXUP
+void vga_hardware_fixup(void);
+#else
+#define vga_hardware_fixup() do{} while(0)
+#endif
+
+#define SYNC_HOR_HIGH_ACT    1       /* horizontal sync high active  */
+#define SYNC_VERT_HIGH_ACT   2       /* vertical sync high active    */
+#define SYNC_EXT             4       /* external sync                */
+#define SYNC_COMP_HIGH_ACT   8       /* composite sync high active   */
+#define SYNC_BROADCAST       16      /* broadcast video timings      */
+                                        /* vtotal = 144d/288n/576i => PAL  */
+                                        /* vtotal = 121d/242n/484i => NTSC */
+
+#define SYNC_ON_GREEN        32      /* sync on green */
+
+#define VMODE_NONINTERLACED  0       /* non interlaced */
+#define VMODE_INTERLACED     1       /* interlaced   */
+#define VMODE_DOUBLE         2       /* double scan */
+#define VMODE_MASK           255
+
+#define VMODE_YWRAP          256     /* ywrap instead of panning     */
+#define VMODE_SMOOTH_XPAN    512     /* smooth xpan possible (internally used) */
+#define VMODE_CONUPDATE      512     /* don't update x/yoffset       */
+
+/* VGA data register ports */
+#define CRT_DC  0x3D5           /* CRT Controller Data Register - color emulation */
+#define CRT_DM  0x3B5           /* CRT Controller Data Register - mono emulation */
+#define ATT_R   0x3C1           /* Attribute Controller Data Read Register */
+#define GRA_D   0x3CF           /* Graphics Controller Data Register */
+#define SEQ_D   0x3C5           /* Sequencer Data Register */
+
+#define MIS_R   0x3CC           // Misc Output Read Register
+#define MIS_W   0x3C2           // Misc Output Write Register
+
+#define IS1_RC  0x3DA           /* Input Status Register 1 - color emulation */
+#define IS1_RM  0x3BA           /* Input Status Register 1 - mono emulation */
+#define PEL_D   0x3C9           /* PEL Data Register */
+#define PEL_MSK 0x3C6           /* PEL mask register */
+
+/* EGA-specific registers */
+#define GRA_E0  0x3CC           /* Graphics enable processor 0 */
+#define GRA_E1  0x3CA           /* Graphics enable processor 1 */
+
+
+/* VGA index register ports */
+#define CRT_IC  0x3D4           /* CRT Controller Index - color emulation */
+#define CRT_IM  0x3B4           /* CRT Controller Index - mono emulation */
+#define ATT_IW  0x3C0           /* Attribute Controller Index & Data Write Register */
+#define GRA_I   0x3CE           /* Graphics Controller Index */
+#define SEQ_I   0x3C4           /* Sequencer Index */
+#define PEL_IW  0x3C8           /* PEL Write Index */
+#define PEL_IR  0x3C7           /* PEL Read Index */
+
+/* standard VGA indexes max counts */
+#define CRTC_C   25              /* 25 CRT Controller Registers sequentially set*/
+								 // the remainder are not in the par array
+#define ATT_C   21              /* 21 Attribute Controller Registers */
+#define GRA_C   9               /* 9  Graphics Controller Registers */
+#define SEQ_C   5               /* 5  Sequencer Registers */
+#define MIS_C   1               /* 1  Misc Output Register */
+
+#define CRTC_H_TOTAL            0
+#define CRTC_H_DISP             1
+#define CRTC_H_BLANK_START      2
+#define CRTC_H_BLANK_END        3
+#define CRTC_H_SYNC_START       4
+#define CRTC_H_SYNC_END         5
+#define CRTC_V_TOTAL            6
+#define CRTC_OVERFLOW           7
+#define CRTC_PRESET_ROW         8
+#define CRTC_MAX_SCAN           9
+#define CRTC_CURSOR_START       0x0A
+#define CRTC_CURSOR_END         0x0B
+#define CRTC_START_HI           0x0C
+#define CRTC_START_LO           0x0D
+#define CRTC_CURSOR_HI          0x0E
+#define CRTC_CURSOR_LO          0x0F
+#define CRTC_V_SYNC_START       0x10
+#define CRTC_V_SYNC_END         0x11
+#define CRTC_V_DISP_END         0x12
+#define CRTC_OFFSET             0x13
+#define CRTC_UNDERLINE          0x14
+#define CRTC_V_BLANK_START      0x15
+#define CRTC_V_BLANK_END        0x16
+#define CRTC_MODE               0x17
+#define CRTC_LINE_COMPARE       0x18
+
+#define ATC_MODE                0x10
+#define ATC_OVERSCAN            0x11
+#define ATC_PLANE_ENABLE        0x12
+#define ATC_PEL                 0x13
+#define ATC_COLOR_PAGE          0x14
+
+#define SEQ_CLOCK_MODE          0x01
+#define SEQ_PLANE_WRITE         0x02
+#define SEQ_CHARACTER_MAP       0x03
+#define SEQ_MEMORY_MODE         0x04
+
+#define GDC_SR_VALUE            0x00
+#define GDC_SR_ENABLE           0x01
+#define GDC_COMPARE_VALUE       0x02
+#define GDC_DATA_ROTATE         0x03
+#define GDC_PLANE_READ          0x04
+#define GDC_MODE                0x05
+#define GDC_MISC                0x06
+#define GDC_COMPARE_MASK        0x07
+#define GDC_BIT_MASK            0x08
+
+// text attributes
+#define VGA_ATTR_CLR_RED 0x4
+#define VGA_ATTR_CLR_GRN 0x2
+#define VGA_ATTR_CLR_BLU 0x1
+#define VGA_ATTR_CLR_YEL (VGA_ATTR_CLR_RED | VGA_ATTR_CLR_GRN)
+#define VGA_ATTR_CLR_CYN (VGA_ATTR_CLR_GRN | VGA_ATTR_CLR_BLU)
+#define VGA_ATTR_CLR_MAG (VGA_ATTR_CLR_BLU | VGA_ATTR_CLR_RED)
+#define VGA_ATTR_CLR_BLK 0
+#define VGA_ATTR_CLR_WHT (VGA_ATTR_CLR_RED | VGA_ATTR_CLR_GRN | VGA_ATTR_CLR_BLU)
+#define VGA_ATTR_BNK     0x80
+#define VGA_ATTR_ITN     0x08
+
+/*
+ * vga register parameters
+ * these are copied to the 
+ * registers.
+ *
+ */
+struct vga_par {
+        u8 crtc[CRTC_C];
+        u8 atc[ATT_C];
+        u8 gdc[GRA_C];
+        u8 seq[SEQ_C];
+        u8 misc; // the misc register, MIS_W
+        u8 vss;
+};
+
+
+/* Interpretation of offset for color fields: All offsets are from the right,
+ * inside a "pixel" value, which is exactly 'bits_per_pixel' wide (means: you
+ * can use the offset as right argument to <<). A pixel afterwards is a bit
+ * stream and is written to video memory as that unmodified. This implies
+ * big-endian byte order if bits_per_pixel is greater than 8.
+ */
+struct fb_bitfield {
+        __u32 offset;                   /* beginning of bitfield        */
+        __u32 length;                   /* length of bitfield           */
+        __u32 msb_right;                /* != 0 : Most significant bit is */ 
+                                        /* right */ 
+};
+
+struct screeninfo {
+        __u32 xres;                     /* visible resolution           */
+        __u32 yres;
+        __u32 xres_virtual;             /* virtual resolution           */
+        __u32 yres_virtual;
+        __u32 xoffset;                  /* offset from virtual to visible */
+        __u32 yoffset;                  /* resolution                   */
+
+        __u32 bits_per_pixel;           /* guess what                   */
+        __u32 grayscale;                /* != 0 Graylevels instead of colors */
+
+        struct fb_bitfield red;         /* bitfield in fb mem if true color, */
+        struct fb_bitfield green;       /* else only length is significant */
+        struct fb_bitfield blue;
+        struct fb_bitfield transp;      /* transparency                 */      
+
+        __u32 nonstd;                   /* != 0 Non standard pixel format */
+
+        __u32 activate;                 /* see FB_ACTIVATE_*            */
+
+        __u32 height;                   /* height of picture in mm    */
+        __u32 width;                    /* width of picture in mm     */
+
+        __u32 accel_flags;              /* acceleration flags (hints)   */
+
+        /* Timing: All values in pixclocks, except pixclock (of course) */
+        __u32 pixclock;                 /* pixel clock in ps (pico seconds) */
+        __u32 left_margin;              /* time from sync to picture    */
+        __u32 right_margin;             /* time from picture to sync    */
+        __u32 upper_margin;             /* time from sync to picture    */
+        __u32 lower_margin;
+        __u32 hsync_len;                /* length of horizontal sync    */
+        __u32 vsync_len;                /* length of vertical sync      */
+        __u32 sync;                     /* sync polarity                */
+        __u32 vmode;                    /* interlaced etc				*/
+        __u32 reserved[6];              /* Reserved for future compatibility */
+};
+
+#endif
diff --git a/gpxe/src/arch/i386/interface/pcbios/abft.c b/gpxe/src/arch/i386/interface/pcbios/abft.c
new file mode 100644
index 0000000..8694172
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/abft.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <realmode.h>
+#include <gpxe/aoe.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/abft.h>
+
+/** @file
+ *
+ * AoE Boot Firmware Table
+ *
+ */
+
+#define abftab __use_data16 ( abftab )
+/** The aBFT used by gPXE */
+struct abft_table __data16 ( abftab ) __attribute__ (( aligned ( 16 ) )) = {
+	/* ACPI header */
+	.acpi = {
+		.signature = ABFT_SIG,
+		.length = sizeof ( abftab ),
+		.revision = 1,
+		.oem_id = "FENSYS",
+		.oem_table_id = "gPXE",
+	},
+};
+
+/**
+ * Fill in all variable portions of aBFT
+ *
+ * @v aoe		AoE session
+ */
+void abft_fill_data ( struct aoe_session *aoe ) {
+
+	/* Fill in boot parameters */
+	abftab.shelf = aoe->major;
+	abftab.slot = aoe->minor;
+	memcpy ( abftab.mac, aoe->netdev->ll_addr, sizeof ( abftab.mac ) );
+
+	/* Update checksum */
+	acpi_fix_checksum ( &abftab.acpi );
+
+	DBG ( "AoE boot firmware table:\n" );
+	DBG_HD ( &abftab, sizeof ( abftab ) );
+}
diff --git a/gpxe/src/arch/i386/interface/pcbios/aoeboot.c b/gpxe/src/arch/i386/interface/pcbios/aoeboot.c
new file mode 100644
index 0000000..2670b15
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/aoeboot.c
@@ -0,0 +1,78 @@
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <gpxe/aoe.h>
+#include <gpxe/ata.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/sanboot.h>
+#include <gpxe/abft.h>
+#include <int13.h>
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+static int aoeboot ( const char *root_path ) {
+	struct ata_device *ata;
+	struct int13_drive *drive;
+	int rc;
+
+	ata = zalloc ( sizeof ( *ata ) );
+	if ( ! ata ) {
+		rc = -ENOMEM;
+		goto err_alloc_ata;
+	}
+	drive = zalloc ( sizeof ( *drive ) );
+	if ( ! drive ) {
+		rc = -ENOMEM;
+		goto err_alloc_drive;
+	}
+
+	/* FIXME: ugly, ugly hack */
+	struct net_device *netdev = last_opened_netdev();
+
+	if ( ( rc = aoe_attach ( ata, netdev, root_path ) ) != 0 ) {
+		printf ( "Could not attach AoE device: %s\n",
+			 strerror ( rc ) );
+		goto err_attach;
+	}
+	if ( ( rc = init_atadev ( ata ) ) != 0 ) {
+		printf ( "Could not initialise AoE device: %s\n",
+			 strerror ( rc ) );
+		goto err_init;
+	}
+
+	/* FIXME: ugly, ugly hack */
+	struct aoe_session *aoe =
+		container_of ( ata->backend, struct aoe_session, refcnt );
+	abft_fill_data ( aoe );
+
+	drive->blockdev = &ata->blockdev;
+
+	register_int13_drive ( drive );
+	printf ( "Registered as BIOS drive %#02x\n", drive->drive );
+	printf ( "Booting from BIOS drive %#02x\n", drive->drive );
+	rc = int13_boot ( drive->drive );
+	printf ( "Boot failed\n" );
+
+	/* Leave drive registered, if instructed to do so */
+	if ( keep_san() )
+		return rc;
+
+	printf ( "Unregistering BIOS drive %#02x\n", drive->drive );
+	unregister_int13_drive ( drive );
+
+ err_init:
+	aoe_detach ( ata );
+ err_attach:
+	free ( drive );
+ err_alloc_drive:
+	free ( ata );
+ err_alloc_ata:
+	return rc;
+}
+
+struct sanboot_protocol aoe_sanboot_protocol __sanboot_protocol = {
+	.prefix = "aoe:",
+	.boot = aoeboot,
+};
diff --git a/gpxe/src/arch/i386/interface/pcbios/bios_nap.c b/gpxe/src/arch/i386/interface/pcbios/bios_nap.c
new file mode 100644
index 0000000..e38cac7
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/bios_nap.c
@@ -0,0 +1,16 @@
+#include <gpxe/nap.h>
+#include <realmode.h>
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/**
+ * Save power by halting the CPU until the next interrupt
+ *
+ */
+static void bios_cpu_nap ( void ) {
+	__asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
+					   "hlt\n\t"
+					   "cli\n\t" ) : : );
+}
+
+PROVIDE_NAP ( pcbios, cpu_nap, bios_cpu_nap );
diff --git a/gpxe/src/arch/i386/interface/pcbios/bios_smbios.c b/gpxe/src/arch/i386/interface/pcbios/bios_smbios.c
new file mode 100644
index 0000000..094214b
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/bios_smbios.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/smbios.h>
+#include <realmode.h>
+#include <pnpbios.h>
+
+/** @file
+ *
+ * System Management BIOS
+ *
+ */
+
+/**
+ * Find SMBIOS
+ *
+ * @v smbios		SMBIOS entry point descriptor structure to fill in
+ * @ret rc		Return status code
+ */
+static int bios_find_smbios ( struct smbios *smbios ) {
+	union {
+		struct smbios_entry entry;
+		uint8_t bytes[256]; /* 256 is maximum length possible */
+	} u;
+	static unsigned int offset = 0;
+	size_t len;
+	unsigned int i;
+	uint8_t sum;
+
+	/* Try to find SMBIOS */
+	for ( ; offset < 0x10000 ; offset += 0x10 ) {
+
+		/* Read start of header and verify signature */
+		copy_from_real ( &u.entry, BIOS_SEG, offset,
+				 sizeof ( u.entry ));
+		if ( u.entry.signature != SMBIOS_SIGNATURE )
+			continue;
+
+		/* Read whole header and verify checksum */
+		len = u.entry.len;
+		copy_from_real ( &u.bytes, BIOS_SEG, offset, len );
+		for ( i = 0 , sum = 0 ; i < len ; i++ ) {
+			sum += u.bytes[i];
+		}
+		if ( sum != 0 ) {
+			DBG ( "SMBIOS at %04x:%04x has bad checksum %02x\n",
+			      BIOS_SEG, offset, sum );
+			continue;
+		}
+
+		/* Fill result structure */
+		DBG ( "Found SMBIOS v%d.%d entry point at %04x:%04x\n",
+		      u.entry.major, u.entry.minor, BIOS_SEG, offset );
+		smbios->address = phys_to_user ( u.entry.smbios_address );
+		smbios->len = u.entry.smbios_len;
+		smbios->count = u.entry.smbios_count;
+		return 0;
+	}
+
+	DBG ( "No SMBIOS found\n" );
+	return -ENODEV;
+}
+
+PROVIDE_SMBIOS ( pcbios, find_smbios, bios_find_smbios );
diff --git a/gpxe/src/arch/i386/interface/pcbios/bios_timer.c b/gpxe/src/arch/i386/interface/pcbios/bios_timer.c
new file mode 100644
index 0000000..8ecf7c1
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/bios_timer.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/** @file
+ *
+ * BIOS timer
+ *
+ */
+
+#include <gpxe/timer.h>
+#include <realmode.h>
+#include <bios.h>
+
+/**
+ * Get current system time in ticks
+ *
+ * @ret ticks		Current time, in ticks
+ *
+ * Use direct memory access to BIOS variables, longword 0040:006C
+ * (ticks today) and byte 0040:0070 (midnight crossover flag) instead
+ * of calling timeofday BIOS interrupt.
+ */
+static unsigned long bios_currticks ( void ) {
+	static int days = 0;
+	uint32_t ticks;
+	uint8_t midnight;
+
+	/* Re-enable interrupts so that the timer interrupt can occur */
+	__asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
+					   "nop\n\t"
+					   "nop\n\t"
+					   "cli\n\t" ) : : );
+
+	get_real ( ticks, BDA_SEG, 0x006c );
+	get_real ( midnight, BDA_SEG, 0x0070 );
+
+	if ( midnight ) {
+		midnight = 0;
+		put_real ( midnight, BDA_SEG, 0x0070 );
+		days += 0x1800b0;
+	}
+
+	return ( days + ticks );
+}
+
+PROVIDE_TIMER_INLINE ( pcbios, udelay );
+PROVIDE_TIMER ( pcbios, currticks, bios_currticks );
+PROVIDE_TIMER_INLINE ( pcbios, ticks_per_sec );
diff --git a/gpxe/src/arch/i386/interface/pcbios/biosint.c b/gpxe/src/arch/i386/interface/pcbios/biosint.c
new file mode 100644
index 0000000..a193def
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/biosint.c
@@ -0,0 +1,92 @@
+#include <errno.h>
+#include <realmode.h>
+#include <biosint.h>
+
+/**
+ * @file BIOS interrupts
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/**
+ * Hook INT vector
+ *
+ * @v interrupt		INT number
+ * @v handler		Offset within .text16 to interrupt handler
+ * @v chain_vector	Vector for chaining to previous handler
+ *
+ * Hooks in an i386 INT handler.  The handler itself must reside
+ * within the .text16 segment.  @c chain_vector will be filled in with
+ * the address of the previously-installed handler for this interrupt;
+ * the handler should probably exit by ljmping via this vector.
+ */
+void hook_bios_interrupt ( unsigned int interrupt, unsigned int handler,
+			   struct segoff *chain_vector ) {
+	struct segoff vector = {
+		.segment = rm_cs,
+		.offset = handler,
+	};
+
+	DBG ( "Hooking INT %#02x to %04x:%04x\n",
+	      interrupt, rm_cs, handler );
+
+	if ( ( chain_vector->segment != 0 ) ||
+	     ( chain_vector->offset != 0 ) ) {
+		/* Already hooked; do nothing */
+		DBG ( "...already hooked\n" );
+		return;
+	}
+
+	copy_from_real ( chain_vector, 0, ( interrupt * 4 ),
+			 sizeof ( *chain_vector ) );
+	DBG ( "...chaining to %04x:%04x\n",
+	      chain_vector->segment, chain_vector->offset );
+	if ( DBG_LOG ) {
+		char code[64];
+		copy_from_real ( code, chain_vector->segment,
+				 chain_vector->offset, sizeof ( code ) );
+		DBG_HDA ( *chain_vector, code, sizeof ( code ) );
+	}
+
+	copy_to_real ( 0, ( interrupt * 4 ), &vector, sizeof ( vector ) );
+	hooked_bios_interrupts++;
+}
+
+/**
+ * Unhook INT vector
+ *
+ * @v interrupt		INT number
+ * @v handler		Offset within .text16 to interrupt handler
+ * @v chain_vector	Vector containing address of previous handler
+ *
+ * Unhooks an i386 interrupt handler hooked by hook_i386_vector().
+ * Note that this operation may fail, if some external code has hooked
+ * the vector since we hooked in our handler.  If it fails, it means
+ * that it is not possible to unhook our handler, and we must leave it
+ * (and its chaining vector) resident in memory.
+ */
+int unhook_bios_interrupt ( unsigned int interrupt, unsigned int handler,
+			    struct segoff *chain_vector ) {
+	struct segoff vector;
+
+	DBG ( "Unhooking INT %#02x from %04x:%04x\n",
+	      interrupt, rm_cs, handler );
+
+	copy_from_real ( &vector, 0, ( interrupt * 4 ), sizeof ( vector ) );
+	if ( ( vector.segment != rm_cs ) || ( vector.offset != handler ) ) {
+		DBG ( "...cannot unhook; vector points to %04x:%04x\n",
+		      vector.segment, vector.offset );
+		return -EBUSY;
+	}
+
+	DBG ( "...restoring to %04x:%04x\n",
+	      chain_vector->segment, chain_vector->offset );
+	copy_to_real ( 0, ( interrupt * 4 ), chain_vector,
+		       sizeof ( *chain_vector ) );
+
+	chain_vector->segment = 0;
+	chain_vector->offset = 0;
+	hooked_bios_interrupts--;
+	return 0;
+}
diff --git a/gpxe/src/arch/i386/interface/pcbios/ib_srpboot.c b/gpxe/src/arch/i386/interface/pcbios/ib_srpboot.c
new file mode 100644
index 0000000..b1cbc33
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/ib_srpboot.c
@@ -0,0 +1,73 @@
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <gpxe/sanboot.h>
+#include <int13.h>
+#include <gpxe/srp.h>
+#include <gpxe/sbft.h>
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+static int ib_srpboot ( const char *root_path ) {
+	struct scsi_device *scsi;
+	struct int13_drive *drive;
+	int rc;
+
+	scsi = zalloc ( sizeof ( *scsi ) );
+	if ( ! scsi ) {
+		rc = -ENOMEM;
+		goto err_alloc_scsi;
+	}
+	drive = zalloc ( sizeof ( *drive ) );
+	if ( ! drive ) {
+		rc = -ENOMEM;
+		goto err_alloc_drive;
+	}
+
+	if ( ( rc = srp_attach ( scsi, root_path ) ) != 0 ) {
+		printf ( "Could not attach IB_SRP device: %s\n",
+			 strerror ( rc ) );
+		goto err_attach;
+	}
+	if ( ( rc = init_scsidev ( scsi ) ) != 0 ) {
+		printf ( "Could not initialise IB_SRP device: %s\n",
+			 strerror ( rc ) );
+		goto err_init;
+	}
+
+	drive->blockdev = &scsi->blockdev;
+
+	/* FIXME: ugly, ugly hack */
+	struct srp_device *srp =
+		container_of ( scsi->backend, struct srp_device, refcnt );
+	sbft_fill_data ( srp );
+
+	register_int13_drive ( drive );
+	printf ( "Registered as BIOS drive %#02x\n", drive->drive );
+	printf ( "Booting from BIOS drive %#02x\n", drive->drive );
+	rc = int13_boot ( drive->drive );
+	printf ( "Boot failed\n" );
+
+	/* Leave drive registered, if instructed to do so */
+	if ( keep_san() )
+		return rc;
+
+	printf ( "Unregistering BIOS drive %#02x\n", drive->drive );
+	unregister_int13_drive ( drive );
+
+ err_init:
+	srp_detach ( scsi );
+ err_attach:
+	free ( drive );
+ err_alloc_drive:
+	free ( scsi );
+ err_alloc_scsi:
+	return rc;
+}
+
+struct sanboot_protocol ib_srp_sanboot_protocol __sanboot_protocol = {
+	.prefix = "ib_srp:",
+	.boot = ib_srpboot,
+};
diff --git a/gpxe/src/arch/i386/interface/pcbios/ibft.c b/gpxe/src/arch/i386/interface/pcbios/ibft.c
new file mode 100644
index 0000000..adf8e6b
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/ibft.c
@@ -0,0 +1,451 @@
+/*
+ * Copyright Fen Systems Ltd. 2007.  Portions of this code are derived
+ * from IBM Corporation Sample Programs.  Copyright IBM Corporation
+ * 2004, 2007.  All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+FILE_LICENCE ( BSD2 );
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <realmode.h>
+#include <gpxe/pci.h>
+#include <gpxe/acpi.h>
+#include <gpxe/in.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/ethernet.h>
+#include <gpxe/dhcp.h>
+#include <gpxe/iscsi.h>
+#include <gpxe/ibft.h>
+
+/** @file
+ *
+ * iSCSI boot firmware table
+ *
+ * The information in this file is derived from the document "iSCSI
+ * Boot Firmware Table (iBFT)" as published by IBM at
+ *
+ * ftp://ftp.software.ibm.com/systems/support/system_x_pdf/ibm_iscsi_boot_firmware_table_v1.02.pdf
+ *
+ */
+
+#define ibftab __use_data16 ( ibftab )
+/** The iBFT used by gPXE */
+struct gpxe_ibft __data16 ( ibftab ) = {
+	/* Table header */
+	.table = {
+		/* ACPI header */
+		.acpi = {
+			.signature = IBFT_SIG,
+			.length = sizeof ( ibftab ),
+			.revision = 1,
+			.oem_id = "FENSYS",
+			.oem_table_id = "gPXE",
+		},
+		/* Control block */
+		.control = {
+			.header = {
+				.structure_id = IBFT_STRUCTURE_ID_CONTROL,
+				.version = 1,
+				.length = sizeof ( ibftab.table.control ),
+				.flags = 0,
+			},
+			.initiator = offsetof ( typeof ( ibftab ), initiator ),
+			.nic_0 = offsetof ( typeof ( ibftab ), nic ),
+			.target_0 = offsetof ( typeof ( ibftab ), target ),
+		},
+	},
+	/* iSCSI initiator information */
+	.initiator = {
+		.header = {
+			.structure_id = IBFT_STRUCTURE_ID_INITIATOR,
+			.version = 1,
+			.length = sizeof ( ibftab.initiator ),
+			.flags = ( IBFT_FL_INITIATOR_BLOCK_VALID |
+				   IBFT_FL_INITIATOR_FIRMWARE_BOOT_SELECTED ),
+		},
+	},
+	/* NIC information */
+	.nic = {
+		.header = {
+			.structure_id = IBFT_STRUCTURE_ID_NIC,
+			.version = 1,
+			.length = sizeof ( ibftab.nic ),
+			.flags = ( IBFT_FL_NIC_BLOCK_VALID |
+				   IBFT_FL_NIC_FIRMWARE_BOOT_SELECTED ),
+		},
+	},
+	/* iSCSI target information */
+	.target = {
+		.header = {
+			.structure_id = IBFT_STRUCTURE_ID_TARGET,
+			.version = 1,
+			.length = sizeof ( ibftab.target ),
+			.flags = ( IBFT_FL_TARGET_BLOCK_VALID |
+				   IBFT_FL_TARGET_FIRMWARE_BOOT_SELECTED ),
+		},
+	},
+};
+
+/**
+ * Fill in an IP address field within iBFT
+ *
+ * @v ipaddr		IP address field
+ * @v in		IPv4 address
+ */
+static void ibft_set_ipaddr ( struct ibft_ipaddr *ipaddr, struct in_addr in ) {
+	memset ( ipaddr, 0, sizeof ( ipaddr ) );
+	if ( in.s_addr ) {
+		ipaddr->in = in;
+		ipaddr->ones = 0xffff;
+	}
+}
+
+/**
+ * Fill in an IP address within iBFT from configuration setting
+ *
+ * @v ipaddr		IP address field
+ * @v setting		Configuration setting
+ * @v tag		DHCP option tag
+ */
+static void ibft_set_ipaddr_option ( struct ibft_ipaddr *ipaddr,
+				     struct setting *setting ) {
+	struct in_addr in = { 0 };
+	fetch_ipv4_setting ( NULL, setting, &in );
+	ibft_set_ipaddr ( ipaddr, in );
+}
+
+/**
+ * Read IP address from iBFT (for debugging)
+ *
+ * @v strings		iBFT string block descriptor
+ * @v string		String field
+ * @ret ipaddr		IP address string
+ */
+static const char * ibft_ipaddr ( struct ibft_ipaddr *ipaddr ) {
+	return inet_ntoa ( ipaddr->in );
+}
+
+/**
+ * Allocate a string within iBFT
+ *
+ * @v strings		iBFT string block descriptor
+ * @v string		String field to fill in
+ * @v len		Length of string to allocate (excluding NUL)
+ * @ret rc		Return status code
+ */
+static int ibft_alloc_string ( struct ibft_string_block *strings,
+			       struct ibft_string *string, size_t len ) {
+	char *dest;
+	unsigned int remaining;
+
+	dest = ( ( ( char * ) strings->table ) + strings->offset );
+	remaining = ( strings->table->acpi.length - strings->offset );
+	if ( len >= remaining )
+		return -ENOMEM;
+
+	string->offset = strings->offset;
+	string->length = len;
+	strings->offset += ( len + 1 );
+	return 0;
+}
+
+/**
+ * Fill in a string field within iBFT
+ *
+ * @v strings		iBFT string block descriptor
+ * @v string		String field
+ * @v data		String to fill in, or NULL
+ * @ret rc		Return status code
+ */
+static int ibft_set_string ( struct ibft_string_block *strings,
+			     struct ibft_string *string, const char *data ) {
+	char *dest;
+	int rc;
+
+	if ( ! data )
+		return 0;
+
+	if ( ( rc = ibft_alloc_string ( strings, string,
+					strlen ( data ) ) ) != 0 )
+		return rc;
+	dest = ( ( ( char * ) strings->table ) + string->offset );
+	strcpy ( dest, data );
+
+	return 0;
+}
+
+/**
+ * Fill in a string field within iBFT from configuration setting
+ *
+ * @v strings		iBFT string block descriptor
+ * @v string		String field
+ * @v setting		Configuration setting
+ * @ret rc		Return status code
+ */
+static int ibft_set_string_option ( struct ibft_string_block *strings,
+				    struct ibft_string *string,
+				    struct setting *setting ) {
+	int len;
+	char *dest;
+	int rc;
+
+	len = fetch_setting_len ( NULL, setting );
+	if ( len < 0 ) {
+		string->offset = 0;
+		string->length = 0;
+		return 0;
+	}
+
+	if ( ( rc = ibft_alloc_string ( strings, string, len ) ) != 0 )
+		return rc;
+	dest = ( ( ( char * ) strings->table ) + string->offset );
+	fetch_string_setting ( NULL, setting, dest, ( len + 1 ) );
+	return 0;
+}
+
+/**
+ * Read string from iBFT (for debugging)
+ *
+ * @v strings		iBFT string block descriptor
+ * @v string		String field
+ * @ret data		String content (or "<empty>")
+ */
+static const char * ibft_string ( struct ibft_string_block *strings,
+				  struct ibft_string *string ) {
+	return ( string->offset ?
+		 ( ( ( char * ) strings->table ) + string->offset ) : NULL );
+}
+
+/**
+ * Fill in NIC portion of iBFT
+ *
+ * @v nic		NIC portion of iBFT
+ * @v strings		iBFT string block descriptor
+ * @v netdev		Network device
+ * @ret rc		Return status code
+ */
+static int ibft_fill_nic ( struct ibft_nic *nic,
+			   struct ibft_string_block *strings,
+			   struct net_device *netdev ) {
+	struct ll_protocol *ll_protocol = netdev->ll_protocol;
+	struct in_addr netmask_addr = { 0 };
+	unsigned int netmask_count = 0;
+	int rc;
+
+	/* Extract values from DHCP configuration */
+	ibft_set_ipaddr_option ( &nic->ip_address, &ip_setting );
+	DBG ( "iBFT NIC IP = %s\n", ibft_ipaddr ( &nic->ip_address ) );
+	ibft_set_ipaddr_option ( &nic->gateway, &gateway_setting );
+	DBG ( "iBFT NIC gateway = %s\n", ibft_ipaddr ( &nic->gateway ) );
+	ibft_set_ipaddr_option ( &nic->dns[0], &dns_setting );
+	DBG ( "iBFT NIC DNS = %s\n", ibft_ipaddr ( &nic->dns[0] ) );
+	if ( ( rc = ibft_set_string_option ( strings, &nic->hostname,
+					     &hostname_setting ) ) != 0 )
+		return rc;
+	DBG ( "iBFT NIC hostname = %s\n",
+	      ibft_string ( strings, &nic->hostname ) );
+
+	/* Derive subnet mask prefix from subnet mask */
+	fetch_ipv4_setting ( NULL, &netmask_setting, &netmask_addr );
+	while ( netmask_addr.s_addr ) {
+		if ( netmask_addr.s_addr & 0x1 )
+			netmask_count++;
+		netmask_addr.s_addr >>= 1;
+	}
+	nic->subnet_mask_prefix = netmask_count;
+	DBG ( "iBFT NIC subnet = /%d\n", nic->subnet_mask_prefix );
+
+	/* Extract values from net-device configuration */
+	if ( ( rc = ll_protocol->eth_addr ( netdev->ll_addr,
+					    nic->mac_address ) ) != 0 ) {
+		DBG ( "Could not determine iBFT MAC: %s\n", strerror ( rc ) );
+		return rc;
+	}
+	DBG ( "iBFT NIC MAC = %s\n", eth_ntoa ( nic->mac_address ) );
+	nic->pci_bus_dev_func = netdev->dev->desc.location;
+	DBG ( "iBFT NIC PCI = %04x\n", nic->pci_bus_dev_func );
+
+	return 0;
+}
+
+/**
+ * Fill in Initiator portion of iBFT
+ *
+ * @v initiator		Initiator portion of iBFT
+ * @v strings		iBFT string block descriptor
+ * @ret rc		Return status code
+ */
+static int ibft_fill_initiator ( struct ibft_initiator *initiator,
+				 struct ibft_string_block *strings ) {
+	const char *initiator_iqn = iscsi_initiator_iqn();
+	int rc;
+
+	if ( ( rc = ibft_set_string ( strings, &initiator->initiator_name,
+				      initiator_iqn ) ) != 0 )
+		return rc;
+	DBG ( "iBFT initiator hostname = %s\n",
+	      ibft_string ( strings, &initiator->initiator_name ) );
+
+	return 0;
+}
+
+/**
+ * Fill in Target CHAP portion of iBFT
+ *
+ * @v target		Target portion of iBFT
+ * @v strings		iBFT string block descriptor
+ * @v iscsi		iSCSI session
+ * @ret rc		Return status code
+ */
+static int ibft_fill_target_chap ( struct ibft_target *target,
+				   struct ibft_string_block *strings,
+				   struct iscsi_session *iscsi ) {
+	int rc;
+
+	if ( ! ( iscsi->status & ISCSI_STATUS_AUTH_FORWARD_REQUIRED ) )
+		return 0;
+
+	assert ( iscsi->initiator_username );
+	assert ( iscsi->initiator_password );
+
+	target->chap_type = IBFT_CHAP_ONE_WAY;
+	if ( ( rc = ibft_set_string ( strings, &target->chap_name,
+				      iscsi->initiator_username ) ) != 0 )
+		return rc;
+	DBG ( "iBFT target username = %s\n",
+	      ibft_string ( strings, &target->chap_name ) );
+	if ( ( rc = ibft_set_string ( strings, &target->chap_secret,
+				      iscsi->initiator_password ) ) != 0 )
+		return rc;
+	DBG ( "iBFT target password = <redacted>\n" );
+
+	return 0;
+}
+
+/**
+ * Fill in Target Reverse CHAP portion of iBFT
+ *
+ * @v target		Target portion of iBFT
+ * @v strings		iBFT string block descriptor
+ * @v iscsi		iSCSI session
+ * @ret rc		Return status code
+ */
+static int ibft_fill_target_reverse_chap ( struct ibft_target *target,
+					   struct ibft_string_block *strings,
+					   struct iscsi_session *iscsi ) {
+	int rc;
+
+	if ( ! ( iscsi->status & ISCSI_STATUS_AUTH_REVERSE_REQUIRED ) )
+		return 0;
+
+	assert ( iscsi->initiator_username );
+	assert ( iscsi->initiator_password );
+	assert ( iscsi->target_username );
+	assert ( iscsi->target_password );
+
+	target->chap_type = IBFT_CHAP_MUTUAL;
+	if ( ( rc = ibft_set_string ( strings, &target->reverse_chap_name,
+				      iscsi->target_username ) ) != 0 )
+		return rc;
+	DBG ( "iBFT target reverse username = %s\n",
+	      ibft_string ( strings, &target->chap_name ) );
+	if ( ( rc = ibft_set_string ( strings, &target->reverse_chap_secret,
+				      iscsi->target_password ) ) != 0 )
+		return rc;
+	DBG ( "iBFT target reverse password = <redacted>\n" );
+
+	return 0;
+}
+
+/**
+ * Fill in Target portion of iBFT
+ *
+ * @v target		Target portion of iBFT
+ * @v strings		iBFT string block descriptor
+ * @v iscsi		iSCSI session
+ * @ret rc		Return status code
+ */
+static int ibft_fill_target ( struct ibft_target *target,
+			      struct ibft_string_block *strings,
+			      struct iscsi_session *iscsi ) {
+	struct sockaddr_in *sin_target =
+		( struct sockaddr_in * ) &iscsi->target_sockaddr;
+	int rc;
+
+	/* Fill in Target values */
+	ibft_set_ipaddr ( &target->ip_address, sin_target->sin_addr );
+	DBG ( "iBFT target IP = %s\n", ibft_ipaddr ( &target->ip_address ) );
+	target->socket = ntohs ( sin_target->sin_port );
+	DBG ( "iBFT target port = %d\n", target->socket );
+	if ( ( rc = ibft_set_string ( strings, &target->target_name,
+				      iscsi->target_iqn ) ) != 0 )
+		return rc;
+	DBG ( "iBFT target name = %s\n",
+	      ibft_string ( strings, &target->target_name ) );
+	if ( ( rc = ibft_fill_target_chap ( target, strings, iscsi ) ) != 0 )
+		return rc;
+	if ( ( rc = ibft_fill_target_reverse_chap ( target, strings,
+						    iscsi ) ) != 0 )
+		return rc;
+
+	return 0;
+}
+
+/**
+ * Fill in all variable portions of iBFT
+ *
+ * @v netdev		Network device
+ * @v initiator_iqn	Initiator IQN
+ * @v st_target		Target socket address
+ * @v target_iqn	Target IQN
+ * @ret rc		Return status code
+ *
+ */
+int ibft_fill_data ( struct net_device *netdev,
+		     struct iscsi_session *iscsi ) {
+	struct ibft_string_block strings = {
+		.table = &ibftab.table,
+		.offset = offsetof ( typeof ( ibftab ), strings ),
+	};
+	int rc;
+
+	/* Fill in NIC, Initiator and Target portions */
+	if ( ( rc = ibft_fill_nic ( &ibftab.nic, &strings, netdev ) ) != 0 )
+		return rc;
+	if ( ( rc = ibft_fill_initiator ( &ibftab.initiator,
+					  &strings ) ) != 0 )
+		return rc;
+	if ( ( rc = ibft_fill_target ( &ibftab.target, &strings,
+				       iscsi ) ) != 0 )
+		return rc;
+
+	/* Update checksum */
+	acpi_fix_checksum ( &ibftab.table.acpi );
+
+	return 0;
+}
diff --git a/gpxe/src/arch/i386/interface/pcbios/int13.c b/gpxe/src/arch/i386/interface/pcbios/int13.c
new file mode 100644
index 0000000..87b613a
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/int13.c
@@ -0,0 +1,715 @@
+/*
+ * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <limits.h>
+#include <byteswap.h>
+#include <errno.h>
+#include <assert.h>
+#include <gpxe/list.h>
+#include <gpxe/blockdev.h>
+#include <gpxe/memmap.h>
+#include <realmode.h>
+#include <bios.h>
+#include <biosint.h>
+#include <bootsector.h>
+#include <int13.h>
+
+/** @file
+ *
+ * INT 13 emulation
+ *
+ * This module provides a mechanism for exporting block devices via
+ * the BIOS INT 13 disk interrupt interface.  
+ *
+ */
+
+/** Vector for chaining to other INT 13 handlers */
+static struct segoff __text16 ( int13_vector );
+#define int13_vector __use_text16 ( int13_vector )
+
+/** Assembly wrapper */
+extern void int13_wrapper ( void );
+
+/** List of registered emulated drives */
+static LIST_HEAD ( drives );
+
+/**
+ * Number of BIOS drives
+ *
+ * Note that this is the number of drives in the system as a whole
+ * (i.e. a mirror of the counter at 40:75), rather than a count of the
+ * number of emulated drives.
+ */
+static uint8_t num_drives;
+
+/**
+ * Update BIOS drive count
+ */
+static void int13_set_num_drives ( void ) {
+	struct int13_drive *drive;
+
+	/* Get current drive count */
+	get_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES );
+
+	/* Ensure count is large enough to cover all of our emulated drives */
+	list_for_each_entry ( drive, &drives, list ) {
+		if ( num_drives <= ( drive->drive & 0x7f ) )
+			num_drives = ( ( drive->drive & 0x7f ) + 1 );
+	}
+
+	/* Update current drive count */
+	put_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES );
+}
+
+/**
+ * Check number of drives
+ */
+static void int13_check_num_drives ( void ) {
+	uint8_t check_num_drives;
+
+	get_real ( check_num_drives, BDA_SEG, BDA_NUM_DRIVES );
+	if ( check_num_drives != num_drives ) {
+		int13_set_num_drives();
+		DBG ( "INT13 fixing up number of drives from %d to %d\n",
+		      check_num_drives, num_drives );
+	}
+}
+
+/**
+ * INT 13, 00 - Reset disk system
+ *
+ * @v drive		Emulated drive
+ * @ret status		Status code
+ */
+static int int13_reset ( struct int13_drive *drive __unused,
+			 struct i386_all_regs *ix86 __unused ) {
+	DBG ( "Reset drive\n" );
+	return 0;
+}
+
+/**
+ * INT 13, 01 - Get status of last operation
+ *
+ * @v drive		Emulated drive
+ * @ret status		Status code
+ */
+static int int13_get_last_status ( struct int13_drive *drive,
+				   struct i386_all_regs *ix86 __unused ) {
+	DBG ( "Get status of last operation\n" );
+	return drive->last_status;
+}
+
+/**
+ * Read / write sectors
+ *
+ * @v drive		Emulated drive
+ * @v al		Number of sectors to read or write (must be nonzero)
+ * @v ch		Low bits of cylinder number
+ * @v cl (bits 7:6)	High bits of cylinder number
+ * @v cl (bits 5:0)	Sector number
+ * @v dh		Head number
+ * @v es:bx		Data buffer
+ * @v io		Read / write method
+ * @ret status		Status code
+ * @ret al		Number of sectors read or written
+ */
+static int int13_rw_sectors ( struct int13_drive *drive,
+			      struct i386_all_regs *ix86,
+			      int ( * io ) ( struct block_device *blockdev,
+					     uint64_t block,
+					     unsigned long count,
+					     userptr_t buffer ) ) {
+	struct block_device *blockdev = drive->blockdev;
+	unsigned int cylinder, head, sector;
+	unsigned long lba;
+	unsigned int count;
+	userptr_t buffer;
+	int rc;
+
+	/* Validate blocksize */
+	if ( blockdev->blksize != INT13_BLKSIZE ) {
+		DBG ( "Invalid blocksize (%zd) for non-extended read/write\n",
+		      blockdev->blksize );
+		return -INT13_STATUS_INVALID;
+	}
+	
+	/* Calculate parameters */
+	cylinder = ( ( ( ix86->regs.cl & 0xc0 ) << 2 ) | ix86->regs.ch );
+	assert ( cylinder < drive->cylinders );
+	head = ix86->regs.dh;
+	assert ( head < drive->heads );
+	sector = ( ix86->regs.cl & 0x3f );
+	assert ( ( sector >= 1 ) && ( sector <= drive->sectors_per_track ) );
+	lba = ( ( ( ( cylinder * drive->heads ) + head )
+		  * drive->sectors_per_track ) + sector - 1 );
+	count = ix86->regs.al;
+	buffer = real_to_user ( ix86->segs.es, ix86->regs.bx );
+
+	DBG ( "C/H/S %d/%d/%d = LBA %#lx <-> %04x:%04x (count %d)\n", cylinder,
+	      head, sector, lba, ix86->segs.es, ix86->regs.bx, count );
+
+	/* Read from / write to block device */
+	if ( ( rc = io ( blockdev, lba, count, buffer ) ) != 0 ) {
+		DBG ( "INT 13 failed: %s\n", strerror ( rc ) );
+		return -INT13_STATUS_READ_ERROR;
+	}
+
+	return 0;
+}
+
+/**
+ * INT 13, 02 - Read sectors
+ *
+ * @v drive		Emulated drive
+ * @v al		Number of sectors to read (must be nonzero)
+ * @v ch		Low bits of cylinder number
+ * @v cl (bits 7:6)	High bits of cylinder number
+ * @v cl (bits 5:0)	Sector number
+ * @v dh		Head number
+ * @v es:bx		Data buffer
+ * @ret status		Status code
+ * @ret al		Number of sectors read
+ */
+static int int13_read_sectors ( struct int13_drive *drive,
+				struct i386_all_regs *ix86 ) {
+	DBG ( "Read: " );
+	return int13_rw_sectors ( drive, ix86, drive->blockdev->op->read );
+}
+
+/**
+ * INT 13, 03 - Write sectors
+ *
+ * @v drive		Emulated drive
+ * @v al		Number of sectors to write (must be nonzero)
+ * @v ch		Low bits of cylinder number
+ * @v cl (bits 7:6)	High bits of cylinder number
+ * @v cl (bits 5:0)	Sector number
+ * @v dh		Head number
+ * @v es:bx		Data buffer
+ * @ret status		Status code
+ * @ret al		Number of sectors written
+ */
+static int int13_write_sectors ( struct int13_drive *drive,
+				 struct i386_all_regs *ix86 ) {
+	DBG ( "Write: " );
+	return int13_rw_sectors ( drive, ix86, drive->blockdev->op->write );
+}
+
+/**
+ * INT 13, 08 - Get drive parameters
+ *
+ * @v drive		Emulated drive
+ * @ret status		Status code
+ * @ret ch		Low bits of maximum cylinder number
+ * @ret cl (bits 7:6)	High bits of maximum cylinder number
+ * @ret cl (bits 5:0)	Maximum sector number
+ * @ret dh		Maximum head number
+ * @ret dl		Number of drives
+ */
+static int int13_get_parameters ( struct int13_drive *drive,
+				  struct i386_all_regs *ix86 ) {
+	unsigned int max_cylinder = drive->cylinders - 1;
+	unsigned int max_head = drive->heads - 1;
+	unsigned int max_sector = drive->sectors_per_track; /* sic */
+
+	DBG ( "Get drive parameters\n" );
+
+	ix86->regs.ch = ( max_cylinder & 0xff );
+	ix86->regs.cl = ( ( ( max_cylinder >> 8 ) << 6 ) | max_sector );
+	ix86->regs.dh = max_head;
+	get_real ( ix86->regs.dl, BDA_SEG, BDA_NUM_DRIVES );
+	return 0;
+}
+
+/**
+ * INT 13, 15 - Get disk type
+ *
+ * @v drive		Emulated drive
+ * @ret ah		Type code
+ * @ret cx:dx		Sector count
+ * @ret status		Status code / disk type
+ */
+static int int13_get_disk_type ( struct int13_drive *drive,
+				 struct i386_all_regs *ix86 ) {
+	uint32_t blocks;
+
+	DBG ( "Get disk type\n" );
+	blocks = ( ( drive->blockdev->blocks <= 0xffffffffUL ) ?
+		   drive->blockdev->blocks : 0xffffffffUL );
+	ix86->regs.cx = ( blocks >> 16 );
+	ix86->regs.dx = ( blocks & 0xffff );
+	return INT13_DISK_TYPE_HDD;
+}
+
+/**
+ * INT 13, 41 - Extensions installation check
+ *
+ * @v drive		Emulated drive
+ * @v bx		0x55aa
+ * @ret bx		0xaa55
+ * @ret cx		Extensions API support bitmap
+ * @ret status		Status code / API version
+ */
+static int int13_extension_check ( struct int13_drive *drive __unused,
+				   struct i386_all_regs *ix86 ) {
+	if ( ix86->regs.bx == 0x55aa ) {
+		DBG ( "INT 13 extensions installation check\n" );
+		ix86->regs.bx = 0xaa55;
+		ix86->regs.cx = INT13_EXTENSION_LINEAR;
+		return INT13_EXTENSION_VER_1_X;
+	} else {
+		return -INT13_STATUS_INVALID;
+	}
+}
+
+/**
+ * Extended read / write
+ *
+ * @v drive		Emulated drive
+ * @v ds:si		Disk address packet
+ * @v io		Read / write method
+ * @ret status		Status code
+ */
+static int int13_extended_rw ( struct int13_drive *drive,
+			       struct i386_all_regs *ix86,
+			       int ( * io ) ( struct block_device *blockdev,
+					      uint64_t block,
+					      unsigned long count,
+					      userptr_t buffer ) ) {
+	struct block_device *blockdev = drive->blockdev;
+	struct int13_disk_address addr;
+	uint64_t lba;
+	unsigned long count;
+	userptr_t buffer;
+	int rc;
+
+	/* Read parameters from disk address structure */
+	copy_from_real ( &addr, ix86->segs.ds, ix86->regs.si, sizeof ( addr ));
+	lba = addr.lba;
+	count = addr.count;
+	buffer = real_to_user ( addr.buffer.segment, addr.buffer.offset );
+
+	DBG ( "LBA %#llx <-> %04x:%04x (count %ld)\n", (unsigned long long)lba,
+	      addr.buffer.segment, addr.buffer.offset, count );
+	
+	/* Read from / write to block device */
+	if ( ( rc = io ( blockdev, lba, count, buffer ) ) != 0 ) {
+		DBG ( "INT 13 failed: %s\n", strerror ( rc ) );
+		return -INT13_STATUS_READ_ERROR;
+	}
+
+	return 0;
+}
+
+/**
+ * INT 13, 42 - Extended read
+ *
+ * @v drive		Emulated drive
+ * @v ds:si		Disk address packet
+ * @ret status		Status code
+ */
+static int int13_extended_read ( struct int13_drive *drive,
+				 struct i386_all_regs *ix86 ) {
+	DBG ( "Extended read: " );
+	return int13_extended_rw ( drive, ix86, drive->blockdev->op->read );
+}
+
+/**
+ * INT 13, 43 - Extended write
+ *
+ * @v drive		Emulated drive
+ * @v ds:si		Disk address packet
+ * @ret status		Status code
+ */
+static int int13_extended_write ( struct int13_drive *drive,
+				  struct i386_all_regs *ix86 ) {
+	DBG ( "Extended write: " );
+	return int13_extended_rw ( drive, ix86, drive->blockdev->op->write );
+}
+
+/**
+ * INT 13, 48 - Get extended parameters
+ *
+ * @v drive		Emulated drive
+ * @v ds:si		Drive parameter table
+ * @ret status		Status code
+ */
+static int int13_get_extended_parameters ( struct int13_drive *drive,
+					   struct i386_all_regs *ix86 ) {
+	struct int13_disk_parameters params = {
+		.bufsize = sizeof ( params ),
+		.flags = INT13_FL_DMA_TRANSPARENT,
+		.cylinders = drive->cylinders,
+		.heads = drive->heads,
+		.sectors_per_track = drive->sectors_per_track,
+		.sectors = drive->blockdev->blocks,
+		.sector_size = drive->blockdev->blksize,
+	};
+	
+	DBG ( "Get extended drive parameters to %04x:%04x\n",
+	      ix86->segs.ds, ix86->regs.si );
+
+	copy_to_real ( ix86->segs.ds, ix86->regs.si, &params,
+		       sizeof ( params ) );
+	return 0;
+}
+
+/**
+ * INT 13 handler
+ *
+ */
+static __asmcall void int13 ( struct i386_all_regs *ix86 ) {
+	int command = ix86->regs.ah;
+	unsigned int bios_drive = ix86->regs.dl;
+	struct int13_drive *drive;
+	int status;
+
+	/* Check BIOS hasn't killed off our drive */
+	int13_check_num_drives();
+
+	list_for_each_entry ( drive, &drives, list ) {
+
+		if ( bios_drive != drive->drive ) {
+			/* Remap any accesses to this drive's natural number */
+			if ( bios_drive == drive->natural_drive ) {
+				DBG ( "INT 13,%04x (%02x) remapped to "
+				      "(%02x)\n", ix86->regs.ax,
+				      bios_drive, drive->drive );
+				ix86->regs.dl = drive->drive;
+				return;
+			}
+			continue;
+		}
+		
+		DBG ( "INT 13,%04x (%02x): ", ix86->regs.ax, drive->drive );
+
+		switch ( command ) {
+		case INT13_RESET:
+			status = int13_reset ( drive, ix86 );
+			break;
+		case INT13_GET_LAST_STATUS:
+			status = int13_get_last_status ( drive, ix86 );
+			break;
+		case INT13_READ_SECTORS:
+			status = int13_read_sectors ( drive, ix86 );
+			break;
+		case INT13_WRITE_SECTORS:
+			status = int13_write_sectors ( drive, ix86 );
+			break;
+		case INT13_GET_PARAMETERS:
+			status = int13_get_parameters ( drive, ix86 );
+			break;
+		case INT13_GET_DISK_TYPE:
+			status = int13_get_disk_type ( drive, ix86 );
+			break;
+		case INT13_EXTENSION_CHECK:
+			status = int13_extension_check ( drive, ix86 );
+			break;
+		case INT13_EXTENDED_READ:
+			status = int13_extended_read ( drive, ix86 );
+			break;
+		case INT13_EXTENDED_WRITE:
+			status = int13_extended_write ( drive, ix86 );
+			break;
+		case INT13_GET_EXTENDED_PARAMETERS:
+			status = int13_get_extended_parameters ( drive, ix86 );
+			break;
+		default:
+			DBG ( "*** Unrecognised INT 13 ***\n" );
+			status = -INT13_STATUS_INVALID;
+			break;
+		}
+
+		/* Store status for INT 13,01 */
+		drive->last_status = status;
+
+		/* Negative status indicates an error */
+		if ( status < 0 ) {
+			status = -status;
+			DBG ( "INT 13 returning failure status %x\n", status );
+		} else {
+			ix86->flags &= ~CF;
+		}
+		ix86->regs.ah = status;
+
+		/* Set OF to indicate to wrapper not to chain this call */
+		ix86->flags |= OF;
+
+		return;
+	}
+}
+
+/**
+ * Hook INT 13 handler
+ *
+ */
+static void hook_int13 ( void ) {
+	/* Assembly wrapper to call int13().  int13() sets OF if we
+	 * should not chain to the previous handler.  (The wrapper
+	 * clears CF and OF before calling int13()).
+	 */
+	__asm__  __volatile__ (
+	       TEXT16_CODE ( "\nint13_wrapper:\n\t"
+			     /* Preserve %ax and %dx for future reference */
+			     "pushw %%bp\n\t"
+			     "movw %%sp, %%bp\n\t"			     
+			     "pushw %%ax\n\t"
+			     "pushw %%dx\n\t"
+			     /* Clear OF, set CF, call int13() */
+			     "orb $0, %%al\n\t" 
+			     "stc\n\t"
+			     "pushl %0\n\t"
+			     "pushw %%cs\n\t"
+			     "call prot_call\n\t"
+			     /* Chain if OF not set */
+			     "jo 1f\n\t"
+			     "pushfw\n\t"
+			     "lcall *%%cs:int13_vector\n\t"
+			     "\n1:\n\t"
+			     /* Overwrite flags for iret */
+			     "pushfw\n\t"
+			     "popw 6(%%bp)\n\t"
+			     /* Fix up %dl:
+			      *
+			      * INT 13,15 : do nothing
+			      * INT 13,08 : load with number of drives
+			      * all others: restore original value
+			      */
+			     "cmpb $0x15, -1(%%bp)\n\t"
+			     "je 2f\n\t"
+			     "movb -4(%%bp), %%dl\n\t"
+			     "cmpb $0x08, -1(%%bp)\n\t"
+			     "jne 2f\n\t"
+			     "pushw %%ds\n\t"
+			     "pushw %1\n\t"
+			     "popw %%ds\n\t"
+			     "movb %c2, %%dl\n\t"
+			     "popw %%ds\n\t"
+			     /* Return */
+			     "\n2:\n\t"
+			     "movw %%bp, %%sp\n\t"
+			     "popw %%bp\n\t"
+			     "iret\n\t" )
+	       : : "i" ( int13 ), "i" ( BDA_SEG ), "i" ( BDA_NUM_DRIVES ) );
+
+	hook_bios_interrupt ( 0x13, ( unsigned int ) int13_wrapper,
+			      &int13_vector );
+}
+
+/**
+ * Unhook INT 13 handler
+ */
+static void unhook_int13 ( void ) {
+	unhook_bios_interrupt ( 0x13, ( unsigned int ) int13_wrapper,
+				&int13_vector );
+}
+
+/**
+ * Guess INT 13 drive geometry
+ *
+ * @v drive		Emulated drive
+ *
+ * Guesses the drive geometry by inspecting the partition table.
+ */
+static void guess_int13_geometry ( struct int13_drive *drive ) {
+	struct master_boot_record mbr;
+	struct partition_table_entry *partition;
+	unsigned int guessed_heads = 255;
+	unsigned int guessed_sectors_per_track = 63;
+	unsigned long blocks;
+	unsigned long blocks_per_cyl;
+	unsigned int i;
+
+	/* Don't even try when the blksize is invalid for C/H/S access */
+	if ( drive->blockdev->blksize != INT13_BLKSIZE )
+		return;
+
+	/* Scan through partition table and modify guesses for heads
+	 * and sectors_per_track if we find any used partitions.
+	 */
+	if ( drive->blockdev->op->read ( drive->blockdev, 0, 1,
+				         virt_to_user ( &mbr ) ) == 0 ) {
+		for ( i = 0 ; i < 4 ; i++ ) {
+			partition = &mbr.partitions[i];
+			if ( ! partition->type )
+				continue;
+			guessed_heads =
+				( PART_HEAD ( partition->chs_end ) + 1 );
+			guessed_sectors_per_track = 
+				PART_SECTOR ( partition->chs_end );
+			DBG ( "Guessing C/H/S xx/%d/%d based on partition "
+			      "%d\n", guessed_heads,
+			      guessed_sectors_per_track, ( i + 1 ) );
+		}
+	} else {
+		DBG ( "Could not read partition table to guess geometry\n" );
+	}
+
+	/* Apply guesses if no geometry already specified */
+	if ( ! drive->heads )
+		drive->heads = guessed_heads;
+	if ( ! drive->sectors_per_track )
+		drive->sectors_per_track = guessed_sectors_per_track;
+	if ( ! drive->cylinders ) {
+		/* Avoid attempting a 64-bit divide on a 32-bit system */
+		blocks = ( ( drive->blockdev->blocks <= ULONG_MAX ) ?
+			   drive->blockdev->blocks : ULONG_MAX );
+		blocks_per_cyl = ( drive->heads * drive->sectors_per_track );
+		assert ( blocks_per_cyl != 0 );
+		drive->cylinders = ( blocks / blocks_per_cyl );
+		if ( drive->cylinders > 1024 )
+			drive->cylinders = 1024;
+	}
+}
+
+/**
+ * Register INT 13 emulated drive
+ *
+ * @v drive		Emulated drive
+ *
+ * Registers the drive with the INT 13 emulation subsystem, and hooks
+ * the INT 13 interrupt vector (if not already hooked).
+ *
+ * The underlying block device must be valid.  A drive number and
+ * geometry will be assigned if left blank.
+ */
+void register_int13_drive ( struct int13_drive *drive ) {
+	uint8_t num_drives;
+
+	/* Give drive a default geometry if none specified */
+	guess_int13_geometry ( drive );
+
+	/* Assign natural drive number */
+	get_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES );
+	drive->natural_drive = ( num_drives | 0x80 );
+
+	/* Assign drive number */
+	if ( ( drive->drive & 0xff ) == 0xff ) {
+		/* Drive number == -1 => use natural drive number */
+		drive->drive = drive->natural_drive;
+	} else {
+		/* Use specified drive number (+0x80 if necessary) */
+		drive->drive |= 0x80;
+	}
+
+	DBG ( "Registered INT13 drive %02x (naturally %02x) with C/H/S "
+	      "geometry %d/%d/%d\n", drive->drive, drive->natural_drive,
+	      drive->cylinders, drive->heads, drive->sectors_per_track );
+
+	/* Hook INT 13 vector if not already hooked */
+	if ( list_empty ( &drives ) )
+		hook_int13();
+
+	/* Add to list of emulated drives */
+	list_add ( &drive->list, &drives );
+
+	/* Update BIOS drive count */
+	int13_set_num_drives();
+}
+
+/**
+ * Unregister INT 13 emulated drive
+ *
+ * @v drive		Emulated drive
+ *
+ * Unregisters the drive from the INT 13 emulation subsystem.  If this
+ * is the last emulated drive, the INT 13 vector is unhooked (if
+ * possible).
+ */
+void unregister_int13_drive ( struct int13_drive *drive ) {
+	/* Remove from list of emulated drives */
+	list_del ( &drive->list );
+
+	/* Should adjust BIOS drive count, but it's difficult to do so
+	 * reliably.
+	 */
+
+	DBG ( "Unregistered INT13 drive %02x\n", drive->drive );
+
+	/* Unhook INT 13 vector if no more drives */
+	if ( list_empty ( &drives ) )
+		unhook_int13();
+}
+
+/**
+ * Attempt to boot from an INT 13 drive
+ *
+ * @v drive		Drive number
+ * @ret rc		Return status code
+ *
+ * This boots from the specified INT 13 drive by loading the Master
+ * Boot Record to 0000:7c00 and jumping to it.  INT 18 is hooked to
+ * capture an attempt by the MBR to boot the next device.  (This is
+ * the closest thing to a return path from an MBR).
+ *
+ * Note that this function can never return success, by definition.
+ */
+int int13_boot ( unsigned int drive ) {
+	struct memory_map memmap;
+	int status, signature;
+	int discard_c, discard_d;
+	int rc;
+
+	DBG ( "Booting from INT 13 drive %02x\n", drive );
+
+	/* Use INT 13 to read the boot sector */
+	__asm__ __volatile__ ( REAL_CODE ( "pushw %%es\n\t"
+					   "pushw $0\n\t"
+					   "popw %%es\n\t"
+					   "stc\n\t"
+					   "sti\n\t"
+					   "int $0x13\n\t"
+					   "sti\n\t" /* BIOS bugs */
+					   "jc 1f\n\t"
+					   "xorl %%eax, %%eax\n\t"
+					   "\n1:\n\t"
+					   "movzwl %%es:0x7dfe, %%ebx\n\t"
+					   "popw %%es\n\t" )
+			       : "=a" ( status ), "=b" ( signature ),
+				 "=c" ( discard_c ), "=d" ( discard_d )
+			       : "a" ( 0x0201 ), "b" ( 0x7c00 ),
+				 "c" ( 1 ), "d" ( drive ) );
+	if ( status )
+		return -EIO;
+
+	/* Check signature is correct */
+	if ( signature != be16_to_cpu ( 0x55aa ) ) {
+		DBG ( "Invalid disk signature %#04x (should be 0x55aa)\n",
+		      cpu_to_be16 ( signature ) );
+		return -ENOEXEC;
+	}
+
+	/* Dump out memory map prior to boot, if memmap debugging is
+	 * enabled.  Not required for program flow, but we have so
+	 * many problems that turn out to be memory-map related that
+	 * it's worth doing.
+	 */
+	get_memmap ( &memmap );
+
+	/* Jump to boot sector */
+	if ( ( rc = call_bootsector ( 0x0, 0x7c00, drive ) ) != 0 ) {
+		DBG ( "INT 13 drive %02x boot returned: %s\n",
+		      drive, strerror ( rc ) );
+		return rc;
+	}
+
+	return -ECANCELED; /* -EIMPOSSIBLE */
+}
diff --git a/gpxe/src/arch/i386/interface/pcbios/iscsiboot.c b/gpxe/src/arch/i386/interface/pcbios/iscsiboot.c
new file mode 100644
index 0000000..00efd8f
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/iscsiboot.c
@@ -0,0 +1,75 @@
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <gpxe/iscsi.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/ibft.h>
+#include <gpxe/sanboot.h>
+#include <int13.h>
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+static int iscsiboot ( const char *root_path ) {
+	struct scsi_device *scsi;
+	struct int13_drive *drive;
+	int rc;
+
+	scsi = zalloc ( sizeof ( *scsi ) );
+	if ( ! scsi ) {
+		rc = -ENOMEM;
+		goto err_alloc_scsi;
+	}
+	drive = zalloc ( sizeof ( *drive ) );
+	if ( ! drive ) {
+		rc = -ENOMEM;
+		goto err_alloc_drive;
+	}
+
+	if ( ( rc = iscsi_attach ( scsi, root_path ) ) != 0 ) {
+		printf ( "Could not attach iSCSI device: %s\n",
+			 strerror ( rc ) );
+		goto err_attach;
+	}
+	if ( ( rc = init_scsidev ( scsi ) ) != 0 ) {
+		printf ( "Could not initialise iSCSI device: %s\n",
+			 strerror ( rc ) );
+		goto err_init;
+	}
+
+	drive->blockdev = &scsi->blockdev;
+
+	/* FIXME: ugly, ugly hack */
+	struct net_device *netdev = last_opened_netdev();
+	struct iscsi_session *iscsi =
+		container_of ( scsi->backend, struct iscsi_session, refcnt );
+	ibft_fill_data ( netdev, iscsi );
+
+	register_int13_drive ( drive );
+	printf ( "Registered as BIOS drive %#02x\n", drive->drive );
+	printf ( "Booting from BIOS drive %#02x\n", drive->drive );
+	rc = int13_boot ( drive->drive );
+	printf ( "Boot failed\n" );
+
+	/* Leave drive registered, if instructed to do so */
+	if ( keep_san() )
+		return rc;
+
+	printf ( "Unregistering BIOS drive %#02x\n", drive->drive );
+	unregister_int13_drive ( drive );
+
+ err_init:
+	iscsi_detach ( scsi );
+ err_attach:
+	free ( drive );
+ err_alloc_drive:
+	free ( scsi );
+ err_alloc_scsi:
+	return rc;
+}
+
+struct sanboot_protocol iscsi_sanboot_protocol __sanboot_protocol = {
+	.prefix = "iscsi:",
+	.boot = iscsiboot,
+};
diff --git a/gpxe/src/arch/i386/interface/pcbios/keepsan.c b/gpxe/src/arch/i386/interface/pcbios/keepsan.c
new file mode 100644
index 0000000..904e017
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/keepsan.c
@@ -0,0 +1,26 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <gpxe/settings.h>
+#include <gpxe/dhcp.h>
+#include <gpxe/init.h>
+#include <gpxe/sanboot.h>
+#include <usr/autoboot.h>
+
+struct setting keep_san_setting __setting = {
+	.name = "keep-san",
+	.description = "Preserve SAN connection",
+	.tag = DHCP_EB_KEEP_SAN,
+	.type = &setting_type_int8,
+};
+
+int keep_san ( void ) {
+	int keep_san;
+
+	keep_san = fetch_intz_setting ( NULL, &keep_san_setting );
+	if ( ! keep_san )
+		return 0;
+
+	printf ( "Preserving connection to SAN disk\n" );
+	shutdown_exit_flags |= SHUTDOWN_KEEP_DEVICES;
+	return 1;
+}
diff --git a/gpxe/src/arch/i386/interface/pcbios/memtop_umalloc.c b/gpxe/src/arch/i386/interface/pcbios/memtop_umalloc.c
new file mode 100644
index 0000000..0645fe6
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/memtop_umalloc.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/**
+ * @file
+ *
+ * External memory allocation
+ *
+ */
+
+#include <limits.h>
+#include <errno.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/hidemem.h>
+#include <gpxe/memmap.h>
+#include <gpxe/umalloc.h>
+
+/** Alignment of external allocated memory */
+#define EM_ALIGN ( 4 * 1024 )
+
+/** Equivalent of NOWHERE for user pointers */
+#define UNOWHERE ( ~UNULL )
+
+/** An external memory block */
+struct external_memory {
+	/** Size of this memory block (excluding this header) */
+	size_t size;
+	/** Block is currently in use */
+	int used;
+};
+
+/** Top of heap */
+static userptr_t top = UNULL;
+
+/** Bottom of heap (current lowest allocated block) */
+static userptr_t bottom = UNULL;
+
+/**
+ * Initialise external heap
+ *
+ * @ret rc		Return status code
+ */
+static int init_eheap ( void ) {
+	struct memory_map memmap;
+	unsigned long heap_size = 0;
+	unsigned int i;
+
+	DBG ( "Allocating external heap\n" );
+
+	get_memmap ( &memmap );
+	for ( i = 0 ; i < memmap.count ; i++ ) {
+		struct memory_region *region = &memmap.regions[i];
+		unsigned long r_start, r_end;
+		unsigned long r_size;
+
+		DBG ( "Considering [%llx,%llx)\n", region->start, region->end);
+
+		/* Truncate block to 4GB */
+		if ( region->start > UINT_MAX ) {
+			DBG ( "...starts after 4GB\n" );
+			continue;
+		}
+		r_start = region->start;
+		if ( region->end > UINT_MAX ) {
+			DBG ( "...end truncated to 4GB\n" );
+			r_end = 0; /* =4GB, given the wraparound */
+		} else {
+			r_end = region->end;
+		}
+
+		/* Use largest block */
+		r_size = ( r_end - r_start );
+		if ( r_size > heap_size ) {
+			DBG ( "...new best block found\n" );
+			top = bottom = phys_to_user ( r_end );
+			heap_size = r_size;
+		}
+	}
+
+	if ( ! heap_size ) {
+		DBG ( "No external heap available\n" );
+		return -ENOMEM;
+	}
+
+	DBG ( "External heap grows downwards from %lx\n",
+	      user_to_phys ( top, 0 ) );
+	return 0;
+}
+
+/**
+ * Collect free blocks
+ *
+ */
+static void ecollect_free ( void ) {
+	struct external_memory extmem;
+
+	/* Walk the free list and collect empty blocks */
+	while ( bottom != top ) {
+		copy_from_user ( &extmem, bottom, -sizeof ( extmem ),
+				 sizeof ( extmem ) );
+		if ( extmem.used )
+			break;
+		DBG ( "EXTMEM freeing [%lx,%lx)\n", user_to_phys ( bottom, 0 ),
+		      user_to_phys ( bottom, extmem.size ) );
+		bottom = userptr_add ( bottom,
+				       ( extmem.size + sizeof ( extmem ) ) );
+	}
+}
+
+/**
+ * Reallocate external memory
+ *
+ * @v old_ptr		Memory previously allocated by umalloc(), or UNULL
+ * @v new_size		Requested size
+ * @ret new_ptr		Allocated memory, or UNULL
+ *
+ * Calling realloc() with a new size of zero is a valid way to free a
+ * memory block.
+ */
+static userptr_t memtop_urealloc ( userptr_t ptr, size_t new_size ) {
+	struct external_memory extmem;
+	userptr_t new = ptr;
+	size_t align;
+	int rc;
+
+	/* Initialise external memory allocator if necessary */
+	if ( bottom == top ) {
+		if ( ( rc = init_eheap() ) != 0 )
+			return UNULL;
+	}
+
+	/* Get block properties into extmem */
+	if ( ptr && ( ptr != UNOWHERE ) ) {
+		/* Determine old size */
+		copy_from_user ( &extmem, ptr, -sizeof ( extmem ),
+				 sizeof ( extmem ) );
+	} else {
+		/* Create a zero-length block */
+		ptr = bottom = userptr_add ( bottom, -sizeof ( extmem ) );
+		DBG ( "EXTMEM allocating [%lx,%lx)\n",
+		      user_to_phys ( ptr, 0 ), user_to_phys ( ptr, 0 ) );
+		extmem.size = 0;
+	}
+	extmem.used = ( new_size > 0 );
+
+	/* Expand/shrink block if possible */
+	if ( ptr == bottom ) {
+		/* Update block */
+		new = userptr_add ( ptr, - ( new_size - extmem.size ) );
+		align = ( user_to_phys ( new, 0 ) & ( EM_ALIGN - 1 ) );
+		new_size += align;
+		new = userptr_add ( new, -align );
+		DBG ( "EXTMEM expanding [%lx,%lx) to [%lx,%lx)\n",
+		      user_to_phys ( ptr, 0 ),
+		      user_to_phys ( ptr, extmem.size ),
+		      user_to_phys ( new, 0 ),
+		      user_to_phys ( new, new_size ));
+		memmove_user ( new, 0, ptr, 0, ( ( extmem.size < new_size ) ?
+						 extmem.size : new_size ) );
+		extmem.size = new_size;
+		bottom = new;
+	} else {
+		/* Cannot expand; can only pretend to shrink */
+		if ( new_size > extmem.size ) {
+			/* Refuse to expand */
+			DBG ( "EXTMEM cannot expand [%lx,%lx)\n",
+			      user_to_phys ( ptr, 0 ),
+			      user_to_phys ( ptr, extmem.size ) );
+			return UNULL;
+		}
+	}
+
+	/* Write back block properties */
+	copy_to_user ( new, -sizeof ( extmem ), &extmem,
+		       sizeof ( extmem ) );
+
+	/* Collect any free blocks and update hidden memory region */
+	ecollect_free();
+	hide_umalloc ( user_to_phys ( bottom, -sizeof ( extmem ) ),
+		       user_to_phys ( top, 0 ) );
+
+	return ( new_size ? new : UNOWHERE );
+}
+
+PROVIDE_UMALLOC ( memtop, urealloc, memtop_urealloc );
diff --git a/gpxe/src/arch/i386/interface/pcbios/pcibios.c b/gpxe/src/arch/i386/interface/pcbios/pcibios.c
new file mode 100644
index 0000000..f2c3880
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/pcibios.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <gpxe/pci.h>
+#include <realmode.h>
+
+/** @file
+ *
+ * PCI configuration space access via PCI BIOS
+ *
+ */
+
+/**
+ * Determine maximum PCI bus number within system
+ *
+ * @ret max_bus		Maximum bus number
+ */
+static int pcibios_max_bus ( void ) {
+	int discard_a, discard_D;
+	uint8_t max_bus;
+
+	__asm__ __volatile__ ( REAL_CODE ( "stc\n\t"
+					   "int $0x1a\n\t"
+					   "jnc 1f\n\t"
+					   "xorw %%cx, %%cx\n\t"
+					   "\n1:\n\t" )
+			       : "=c" ( max_bus ), "=a" ( discard_a ),
+				 "=D" ( discard_D )
+			       : "a" ( PCIBIOS_INSTALLATION_CHECK >> 16 ),
+				 "D" ( 0 )
+			       : "ebx", "edx" );
+
+	return max_bus;
+}
+
+/**
+ * Read configuration space via PCI BIOS
+ *
+ * @v pci	PCI device
+ * @v command	PCI BIOS command
+ * @v value	Value read
+ * @ret rc	Return status code
+ */
+int pcibios_read ( struct pci_device *pci, uint32_t command, uint32_t *value ){
+	int discard_b, discard_D;
+	int status;
+
+	__asm__ __volatile__ ( REAL_CODE ( "stc\n\t"
+					   "int $0x1a\n\t"
+					   "jnc 1f\n\t"
+					   "xorl %%eax, %%eax\n\t"
+					   "decl %%eax\n\t"
+					   "movl %%eax, %%ecx\n\t"
+					   "\n1:\n\t" )
+			       : "=a" ( status ), "=b" ( discard_b ),
+				 "=c" ( *value ), "=D" ( discard_D )
+			       : "a" ( command >> 16 ), "D" ( command ),
+			         "b" ( PCI_BUSDEVFN ( pci->bus, pci->devfn ) )
+			       : "edx" );
+
+	return ( ( status >> 8 ) & 0xff );
+}
+
+/**
+ * Write configuration space via PCI BIOS
+ *
+ * @v pci	PCI device
+ * @v command	PCI BIOS command
+ * @v value	Value to be written
+ * @ret rc	Return status code
+ */
+int pcibios_write ( struct pci_device *pci, uint32_t command, uint32_t value ){
+	int discard_b, discard_c, discard_D;
+	int status;
+
+	__asm__ __volatile__ ( REAL_CODE ( "stc\n\t"
+					   "int $0x1a\n\t"
+					   "jnc 1f\n\t"
+					   "movb $0xff, %%ah\n\t"
+					   "\n1:\n\t" )
+			       : "=a" ( status ), "=b" ( discard_b ),
+				 "=c" ( discard_c ), "=D" ( discard_D )
+			       : "a" ( command >> 16 ),	"D" ( command ),
+			         "b" ( PCI_BUSDEVFN ( pci->bus, pci->devfn ) ),
+				 "c" ( value )
+			       : "edx" );
+	
+	return ( ( status >> 8 ) & 0xff );
+}
+
+PROVIDE_PCIAPI ( pcbios, pci_max_bus, pcibios_max_bus );
+PROVIDE_PCIAPI_INLINE ( pcbios, pci_read_config_byte );
+PROVIDE_PCIAPI_INLINE ( pcbios, pci_read_config_word );
+PROVIDE_PCIAPI_INLINE ( pcbios, pci_read_config_dword );
+PROVIDE_PCIAPI_INLINE ( pcbios, pci_write_config_byte );
+PROVIDE_PCIAPI_INLINE ( pcbios, pci_write_config_word );
+PROVIDE_PCIAPI_INLINE ( pcbios, pci_write_config_dword );
diff --git a/gpxe/src/arch/i386/interface/pcbios/sbft.c b/gpxe/src/arch/i386/interface/pcbios/sbft.c
new file mode 100644
index 0000000..12927c7
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pcbios/sbft.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2009 Fen Systems Ltd <mbrown@fensystems.co.uk>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ *   Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+FILE_LICENCE ( BSD2 );
+
+/** @file
+ *
+ * SRP boot firmware table
+ *
+ */
+
+#include <assert.h>
+#include <realmode.h>
+#include <gpxe/srp.h>
+#include <gpxe/ib_srp.h>
+#include <gpxe/acpi.h>
+#include <gpxe/sbft.h>
+
+#define sbftab __use_data16 ( sbftab )
+/** The sBFT used by gPXE */
+struct gpxe_sbft __data16 ( sbftab ) = {
+	/* Table header */
+	.table = {
+		/* ACPI header */
+		.acpi = {
+			.signature = SBFT_SIG,
+			.length = sizeof ( sbftab ),
+			.revision = 1,
+			.oem_id = "FENSYS",
+			.oem_table_id = "gPXE",
+		},
+		.scsi_offset = offsetof ( typeof ( sbftab ), scsi ),
+		.srp_offset = offsetof ( typeof ( sbftab ), srp ),
+		.ib_offset = offsetof ( typeof ( sbftab ), ib ),
+	},
+};
+
+/**
+ * Fill in all variable portions of sBFT
+ *
+ * @v srp		SRP device
+ * @ret rc		Return status code
+ */
+int sbft_fill_data ( struct srp_device *srp ) {
+	struct sbft_scsi_subtable *sbft_scsi = &sbftab.scsi;
+	struct sbft_srp_subtable *sbft_srp = &sbftab.srp;
+	struct sbft_ib_subtable *sbft_ib = &sbftab.ib;
+	struct ib_srp_parameters *ib_params;
+	struct segoff rm_sbftab = {
+		.segment = rm_ds,
+		.offset = __from_data16 ( &sbftab ),
+	};
+
+	/* Fill in the SCSI subtable */
+	memcpy ( &sbft_scsi->lun, &srp->lun, sizeof ( sbft_scsi->lun ) );
+
+	/* Fill in the SRP subtable */
+	memcpy ( &sbft_srp->port_ids, &srp->port_ids,
+		 sizeof ( sbft_srp->port_ids ) );
+
+	/* Fill in the IB subtable */
+	assert ( srp->transport == &ib_srp_transport );
+	ib_params = ib_srp_params ( srp );
+	memcpy ( &sbft_ib->sgid, &ib_params->sgid, sizeof ( sbft_ib->sgid ) );
+	memcpy ( &sbft_ib->dgid, &ib_params->dgid, sizeof ( sbft_ib->dgid ) );
+	memcpy ( &sbft_ib->service_id, &ib_params->service_id,
+		 sizeof ( sbft_ib->service_id ) );
+	sbft_ib->pkey = ib_params->pkey;
+
+	/* Update checksum */
+	acpi_fix_checksum ( &sbftab.table.acpi );
+
+	DBGC ( &sbftab, "SRP Boot Firmware Table at %04x:%04x:\n",
+	       rm_sbftab.segment, rm_sbftab.offset );
+	DBGC_HDA ( &sbftab, rm_sbftab, &sbftab, sizeof ( sbftab ) );
+
+	return 0;
+}
diff --git a/gpxe/src/arch/i386/interface/pxe/pxe_call.c b/gpxe/src/arch/i386/interface/pxe/pxe_call.c
new file mode 100644
index 0000000..66a9b1e
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pxe/pxe_call.c
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <gpxe/uaccess.h>
+#include <gpxe/init.h>
+#include <registers.h>
+#include <biosint.h>
+#include <pxe.h>
+#include <pxe_call.h>
+
+/** @file
+ *
+ * PXE API entry point
+ */
+
+/** Vector for chaining INT 1A */
+extern struct segoff __text16 ( pxe_int_1a_vector );
+#define pxe_int_1a_vector __use_text16 ( pxe_int_1a_vector )
+
+/** INT 1A handler */
+extern void pxe_int_1a ( void );
+
+/** INT 1A hooked flag */
+static int int_1a_hooked = 0;
+
+/** A function pointer to hold any PXE API call
+ *
+ * Used by pxe_api_call() to avoid large swathes of duplicated code.
+ */
+union pxenv_call {
+	PXENV_EXIT_t ( * any ) ( union u_PXENV_ANY * );
+	PXENV_EXIT_t ( * unknown ) ( struct s_PXENV_UNKNOWN * );
+	PXENV_EXIT_t ( * unload_stack ) ( struct s_PXENV_UNLOAD_STACK * );
+	PXENV_EXIT_t ( * get_cached_info )
+			( struct s_PXENV_GET_CACHED_INFO * );
+	PXENV_EXIT_t ( * restart_tftp ) ( struct s_PXENV_TFTP_READ_FILE * );
+	PXENV_EXIT_t ( * start_undi ) ( struct s_PXENV_START_UNDI * );
+	PXENV_EXIT_t ( * stop_undi ) ( struct s_PXENV_STOP_UNDI * );
+	PXENV_EXIT_t ( * start_base ) ( struct s_PXENV_START_BASE * );
+	PXENV_EXIT_t ( * stop_base ) ( struct s_PXENV_STOP_BASE * );
+	PXENV_EXIT_t ( * tftp_open ) ( struct s_PXENV_TFTP_OPEN * );
+	PXENV_EXIT_t ( * tftp_close ) ( struct s_PXENV_TFTP_CLOSE * );
+	PXENV_EXIT_t ( * tftp_read ) ( struct s_PXENV_TFTP_READ * );
+	PXENV_EXIT_t ( * tftp_read_file ) ( struct s_PXENV_TFTP_READ_FILE * );
+	PXENV_EXIT_t ( * tftp_get_fsize ) ( struct s_PXENV_TFTP_GET_FSIZE * );
+	PXENV_EXIT_t ( * udp_open ) ( struct s_PXENV_UDP_OPEN * );
+	PXENV_EXIT_t ( * udp_close ) ( struct s_PXENV_UDP_CLOSE * );
+	PXENV_EXIT_t ( * udp_write ) ( struct s_PXENV_UDP_WRITE * );
+	PXENV_EXIT_t ( * udp_read ) ( struct s_PXENV_UDP_READ * );
+	PXENV_EXIT_t ( * undi_startup ) ( struct s_PXENV_UNDI_STARTUP * );
+	PXENV_EXIT_t ( * undi_cleanup ) ( struct s_PXENV_UNDI_CLEANUP * );
+	PXENV_EXIT_t ( * undi_initialize )
+			( struct s_PXENV_UNDI_INITIALIZE * );
+	PXENV_EXIT_t ( * undi_reset_adapter ) ( struct s_PXENV_UNDI_RESET * );
+	PXENV_EXIT_t ( * undi_shutdown ) ( struct s_PXENV_UNDI_SHUTDOWN * );
+	PXENV_EXIT_t ( * undi_open ) ( struct s_PXENV_UNDI_OPEN * );
+	PXENV_EXIT_t ( * undi_close ) ( struct s_PXENV_UNDI_CLOSE * );
+	PXENV_EXIT_t ( * undi_transmit ) ( struct s_PXENV_UNDI_TRANSMIT * );
+	PXENV_EXIT_t ( * undi_set_mcast_address )
+			( struct s_PXENV_UNDI_SET_MCAST_ADDRESS * );
+	PXENV_EXIT_t ( * undi_set_station_address )
+			( struct s_PXENV_UNDI_SET_STATION_ADDRESS * );
+	PXENV_EXIT_t ( * undi_set_packet_filter )
+			( struct s_PXENV_UNDI_SET_PACKET_FILTER * );
+	PXENV_EXIT_t ( * undi_get_information )
+			( struct s_PXENV_UNDI_GET_INFORMATION * );
+	PXENV_EXIT_t ( * undi_get_statistics )
+			( struct s_PXENV_UNDI_GET_STATISTICS * );
+	PXENV_EXIT_t ( * undi_clear_statistics )
+			( struct s_PXENV_UNDI_CLEAR_STATISTICS * );
+	PXENV_EXIT_t ( * undi_initiate_diags )
+			( struct s_PXENV_UNDI_INITIATE_DIAGS * );
+	PXENV_EXIT_t ( * undi_force_interrupt )
+			( struct s_PXENV_UNDI_FORCE_INTERRUPT * );
+	PXENV_EXIT_t ( * undi_get_mcast_address )
+			( struct s_PXENV_UNDI_GET_MCAST_ADDRESS * );
+	PXENV_EXIT_t ( * undi_get_nic_type )
+			( struct s_PXENV_UNDI_GET_NIC_TYPE * );
+	PXENV_EXIT_t ( * undi_get_iface_info )
+			( struct s_PXENV_UNDI_GET_IFACE_INFO * );
+	PXENV_EXIT_t ( * undi_get_state ) ( struct s_PXENV_UNDI_GET_STATE * );
+	PXENV_EXIT_t ( * undi_isr ) ( struct s_PXENV_UNDI_ISR * );
+	PXENV_EXIT_t ( * file_open ) ( struct s_PXENV_FILE_OPEN * );
+	PXENV_EXIT_t ( * file_close ) ( struct s_PXENV_FILE_CLOSE * );
+	PXENV_EXIT_t ( * file_select ) ( struct s_PXENV_FILE_SELECT * );
+	PXENV_EXIT_t ( * file_read ) ( struct s_PXENV_FILE_READ * );
+	PXENV_EXIT_t ( * get_file_size ) ( struct s_PXENV_GET_FILE_SIZE * );
+	PXENV_EXIT_t ( * file_exec ) ( struct s_PXENV_FILE_EXEC * );
+	PXENV_EXIT_t ( * file_api_check ) ( struct s_PXENV_FILE_API_CHECK * );
+	PXENV_EXIT_t ( * file_exit_hook ) ( struct s_PXENV_FILE_EXIT_HOOK * );
+};
+
+/**
+ * Handle an unknown PXE API call
+ *
+ * @v pxenv_unknown 			Pointer to a struct s_PXENV_UNKNOWN
+ * @ret #PXENV_EXIT_FAILURE		Always
+ * @err #PXENV_STATUS_UNSUPPORTED	Always
+ */
+static PXENV_EXIT_t pxenv_unknown ( struct s_PXENV_UNKNOWN *pxenv_unknown ) {
+	pxenv_unknown->Status = PXENV_STATUS_UNSUPPORTED;
+	return PXENV_EXIT_FAILURE;
+}
+
+/**
+ * Dispatch PXE API call
+ *
+ * @v bx		PXE opcode
+ * @v es:di		Address of PXE parameter block
+ * @ret ax		PXE exit code
+ */
+__asmcall void pxe_api_call ( struct i386_all_regs *ix86 ) {
+	int opcode = ix86->regs.bx;
+	userptr_t parameters = real_to_user ( ix86->segs.es, ix86->regs.di );
+	size_t param_len;
+	union u_PXENV_ANY pxenv_any;
+	union pxenv_call pxenv_call;
+	PXENV_EXIT_t ret;
+
+	switch ( opcode ) {
+	case PXENV_UNLOAD_STACK:
+		pxenv_call.unload_stack = pxenv_unload_stack;
+		param_len = sizeof ( pxenv_any.unload_stack );
+		break;
+	case PXENV_GET_CACHED_INFO:
+		pxenv_call.get_cached_info = pxenv_get_cached_info;
+		param_len = sizeof ( pxenv_any.get_cached_info );
+		break;
+	case PXENV_RESTART_TFTP:
+		pxenv_call.restart_tftp = pxenv_restart_tftp;
+		param_len = sizeof ( pxenv_any.restart_tftp );
+		break;
+	case PXENV_START_UNDI:
+		pxenv_call.start_undi = pxenv_start_undi;
+		param_len = sizeof ( pxenv_any.start_undi );
+		break;
+	case PXENV_STOP_UNDI:
+		pxenv_call.stop_undi = pxenv_stop_undi;
+		param_len = sizeof ( pxenv_any.stop_undi );
+		break;
+	case PXENV_START_BASE:
+		pxenv_call.start_base = pxenv_start_base;
+		param_len = sizeof ( pxenv_any.start_base );
+		break;
+	case PXENV_STOP_BASE:
+		pxenv_call.stop_base = pxenv_stop_base;
+		param_len = sizeof ( pxenv_any.stop_base );
+		break;
+	case PXENV_TFTP_OPEN:
+		pxenv_call.tftp_open = pxenv_tftp_open;
+		param_len = sizeof ( pxenv_any.tftp_open );
+		break;
+	case PXENV_TFTP_CLOSE:
+		pxenv_call.tftp_close = pxenv_tftp_close;
+		param_len = sizeof ( pxenv_any.tftp_close );
+		break;
+	case PXENV_TFTP_READ:
+		pxenv_call.tftp_read = pxenv_tftp_read;
+		param_len = sizeof ( pxenv_any.tftp_read );
+		break;
+	case PXENV_TFTP_READ_FILE:
+		pxenv_call.tftp_read_file = pxenv_tftp_read_file;
+		param_len = sizeof ( pxenv_any.tftp_read_file );
+		break;
+	case PXENV_TFTP_GET_FSIZE:
+		pxenv_call.tftp_get_fsize = pxenv_tftp_get_fsize;
+		param_len = sizeof ( pxenv_any.tftp_get_fsize );
+		break;
+	case PXENV_UDP_OPEN:
+		pxenv_call.udp_open = pxenv_udp_open;
+		param_len = sizeof ( pxenv_any.udp_open );
+		break;
+	case PXENV_UDP_CLOSE:
+		pxenv_call.udp_close = pxenv_udp_close;
+		param_len = sizeof ( pxenv_any.udp_close );
+		break;
+	case PXENV_UDP_WRITE:
+		pxenv_call.udp_write = pxenv_udp_write;
+		param_len = sizeof ( pxenv_any.udp_write );
+		break;
+	case PXENV_UDP_READ:
+		pxenv_call.udp_read = pxenv_udp_read;
+		param_len = sizeof ( pxenv_any.udp_read );
+		break;
+	case PXENV_UNDI_STARTUP:
+		pxenv_call.undi_startup = pxenv_undi_startup;
+		param_len = sizeof ( pxenv_any.undi_startup );
+		break;
+	case PXENV_UNDI_CLEANUP:
+		pxenv_call.undi_cleanup = pxenv_undi_cleanup;
+		param_len = sizeof ( pxenv_any.undi_cleanup );
+		break;
+	case PXENV_UNDI_INITIALIZE:
+		pxenv_call.undi_initialize = pxenv_undi_initialize;
+		param_len = sizeof ( pxenv_any.undi_initialize );
+		break;
+	case PXENV_UNDI_RESET_ADAPTER:
+		pxenv_call.undi_reset_adapter = pxenv_undi_reset_adapter;
+		param_len = sizeof ( pxenv_any.undi_reset_adapter );
+		break;
+	case PXENV_UNDI_SHUTDOWN:
+		pxenv_call.undi_shutdown = pxenv_undi_shutdown;
+		param_len = sizeof ( pxenv_any.undi_shutdown );
+		break;
+	case PXENV_UNDI_OPEN:
+		pxenv_call.undi_open = pxenv_undi_open;
+		param_len = sizeof ( pxenv_any.undi_open );
+		break;
+	case PXENV_UNDI_CLOSE:
+		pxenv_call.undi_close = pxenv_undi_close;
+		param_len = sizeof ( pxenv_any.undi_close );
+		break;
+	case PXENV_UNDI_TRANSMIT:
+		pxenv_call.undi_transmit = pxenv_undi_transmit;
+		param_len = sizeof ( pxenv_any.undi_transmit );
+		break;
+	case PXENV_UNDI_SET_MCAST_ADDRESS:
+		pxenv_call.undi_set_mcast_address =
+			pxenv_undi_set_mcast_address;
+		param_len = sizeof ( pxenv_any.undi_set_mcast_address );
+		break;
+	case PXENV_UNDI_SET_STATION_ADDRESS:
+		pxenv_call.undi_set_station_address =
+			pxenv_undi_set_station_address;
+		param_len = sizeof ( pxenv_any.undi_set_station_address );
+		break;
+	case PXENV_UNDI_SET_PACKET_FILTER:
+		pxenv_call.undi_set_packet_filter =
+			pxenv_undi_set_packet_filter;
+		param_len = sizeof ( pxenv_any.undi_set_packet_filter );
+		break;
+	case PXENV_UNDI_GET_INFORMATION:
+		pxenv_call.undi_get_information = pxenv_undi_get_information;
+		param_len = sizeof ( pxenv_any.undi_get_information );
+		break;
+	case PXENV_UNDI_GET_STATISTICS:
+		pxenv_call.undi_get_statistics = pxenv_undi_get_statistics;
+		param_len = sizeof ( pxenv_any.undi_get_statistics );
+		break;
+	case PXENV_UNDI_CLEAR_STATISTICS:
+		pxenv_call.undi_clear_statistics = pxenv_undi_clear_statistics;
+		param_len = sizeof ( pxenv_any.undi_clear_statistics );
+		break;
+	case PXENV_UNDI_INITIATE_DIAGS:
+		pxenv_call.undi_initiate_diags = pxenv_undi_initiate_diags;
+		param_len = sizeof ( pxenv_any.undi_initiate_diags );
+		break;
+	case PXENV_UNDI_FORCE_INTERRUPT:
+		pxenv_call.undi_force_interrupt = pxenv_undi_force_interrupt;
+		param_len = sizeof ( pxenv_any.undi_force_interrupt );
+		break;
+	case PXENV_UNDI_GET_MCAST_ADDRESS:
+		pxenv_call.undi_get_mcast_address =
+			pxenv_undi_get_mcast_address;
+		param_len = sizeof ( pxenv_any.undi_get_mcast_address );
+		break;
+	case PXENV_UNDI_GET_NIC_TYPE:
+		pxenv_call.undi_get_nic_type = pxenv_undi_get_nic_type;
+		param_len = sizeof ( pxenv_any.undi_get_nic_type );
+		break;
+	case PXENV_UNDI_GET_IFACE_INFO:
+		pxenv_call.undi_get_iface_info = pxenv_undi_get_iface_info;
+		param_len = sizeof ( pxenv_any.undi_get_iface_info );
+		break;
+	case PXENV_UNDI_ISR:
+		pxenv_call.undi_isr = pxenv_undi_isr;
+		param_len = sizeof ( pxenv_any.undi_isr );
+		break;
+	case PXENV_FILE_OPEN:
+		pxenv_call.file_open = pxenv_file_open;
+		param_len = sizeof ( pxenv_any.file_open );
+		break;
+	case PXENV_FILE_CLOSE:
+		pxenv_call.file_close = pxenv_file_close;
+		param_len = sizeof ( pxenv_any.file_close );
+		break;
+	case PXENV_FILE_SELECT:
+		pxenv_call.file_select = pxenv_file_select;
+		param_len = sizeof ( pxenv_any.file_select );
+		break;
+	case PXENV_FILE_READ:
+		pxenv_call.file_read = pxenv_file_read;
+		param_len = sizeof ( pxenv_any.file_read );
+		break;
+	case PXENV_GET_FILE_SIZE:
+		pxenv_call.get_file_size = pxenv_get_file_size;
+		param_len = sizeof ( pxenv_any.get_file_size );
+		break;
+	case PXENV_FILE_EXEC:
+		pxenv_call.file_exec = pxenv_file_exec;
+		param_len = sizeof ( pxenv_any.file_exec );
+		break;
+	case PXENV_FILE_API_CHECK:
+		pxenv_call.file_api_check = pxenv_file_api_check;
+		param_len = sizeof ( pxenv_any.file_api_check );
+		break;
+	case PXENV_FILE_EXIT_HOOK:
+		pxenv_call.file_exit_hook = pxenv_file_exit_hook;
+		param_len = sizeof ( pxenv_any.file_exit_hook );
+		break;
+	default:
+		DBG ( "PXENV_UNKNOWN_%hx", opcode );
+		pxenv_call.unknown = pxenv_unknown;
+		param_len = sizeof ( pxenv_any.unknown );
+		break;
+	}
+
+	/* Copy parameter block from caller */
+	copy_from_user ( &pxenv_any, parameters, 0, param_len );
+
+	/* Set default status in case child routine fails to do so */
+	pxenv_any.Status = PXENV_STATUS_FAILURE;
+
+	/* Hand off to relevant API routine */
+	DBG ( "[" );
+	ret = pxenv_call.any ( &pxenv_any );
+	if ( pxenv_any.Status != PXENV_STATUS_SUCCESS ) {
+		DBG ( " %02x", pxenv_any.Status );
+	}
+	if ( ret != PXENV_EXIT_SUCCESS ) {
+		DBG ( ret == PXENV_EXIT_FAILURE ? " err" : " ??" );
+	}
+	DBG ( "]" );
+	
+	/* Copy modified parameter block back to caller and return */
+	copy_to_user ( parameters, 0, &pxenv_any, param_len );
+	ix86->regs.ax = ret;
+}
+
+/**
+ * Dispatch weak PXE API call with PXE stack available
+ *
+ * @v ix86		Registers for PXE call
+ * @ret present		Zero (PXE stack present)
+ */
+int _pxe_api_call_weak ( struct i386_all_regs *ix86 )
+{
+	pxe_api_call ( ix86 );
+	return 0;
+}
+
+/**
+ * Dispatch PXE loader call
+ *
+ * @v es:di		Address of PXE parameter block
+ * @ret ax		PXE exit code
+ */
+__asmcall void pxe_loader_call ( struct i386_all_regs *ix86 ) {
+	userptr_t uparams = real_to_user ( ix86->segs.es, ix86->regs.di );
+	struct s_UNDI_LOADER params;
+	PXENV_EXIT_t ret;
+
+	/* Copy parameter block from caller */
+	copy_from_user ( &params, uparams, 0, sizeof ( params ) );
+
+	/* Fill in ROM segment address */
+	ppxe.UNDIROMID.segment = ix86->segs.ds;
+
+	/* Set default status in case child routine fails to do so */
+	params.Status = PXENV_STATUS_FAILURE;
+
+	/* Call UNDI loader */
+	ret = undi_loader ( &params );
+
+	/* Copy modified parameter block back to caller and return */
+	copy_to_user ( uparams, 0, &params, sizeof ( params ) );
+	ix86->regs.ax = ret;
+}
+
+/**
+ * Calculate byte checksum as used by PXE
+ *
+ * @v data		Data
+ * @v size		Length of data
+ * @ret sum		Checksum
+ */
+static uint8_t pxe_checksum ( void *data, size_t size ) {
+	uint8_t *bytes = data;
+	uint8_t sum = 0;
+
+	while ( size-- ) {
+		sum += *bytes++;
+	}
+	return sum;
+}
+
+/**
+ * Initialise !PXE and PXENV+ structures
+ *
+ */
+static void pxe_init_structures ( void ) {
+	uint32_t rm_cs_phys = ( rm_cs << 4 );
+	uint32_t rm_ds_phys = ( rm_ds << 4 );
+
+	/* Fill in missing segment fields */
+	ppxe.EntryPointSP.segment = rm_cs;
+	ppxe.EntryPointESP.segment = rm_cs;
+	ppxe.Stack.segment_address = rm_ds;
+	ppxe.Stack.Physical_address = rm_ds_phys;
+	ppxe.UNDIData.segment_address = rm_ds;
+	ppxe.UNDIData.Physical_address = rm_ds_phys;
+	ppxe.UNDICode.segment_address = rm_cs;
+	ppxe.UNDICode.Physical_address = rm_cs_phys;
+	ppxe.UNDICodeWrite.segment_address = rm_cs;
+	ppxe.UNDICodeWrite.Physical_address = rm_cs_phys;
+	pxenv.RMEntry.segment = rm_cs;
+	pxenv.StackSeg = rm_ds;
+	pxenv.UNDIDataSeg = rm_ds;
+	pxenv.UNDICodeSeg = rm_cs;
+	pxenv.PXEPtr.segment = rm_cs;
+
+	/* Update checksums */
+	ppxe.StructCksum -= pxe_checksum ( &ppxe, sizeof ( ppxe ) );
+	pxenv.Checksum -= pxe_checksum ( &pxenv, sizeof ( pxenv ) );
+}
+
+/** PXE structure initialiser */
+struct init_fn pxe_init_fn __init_fn ( INIT_NORMAL ) = {
+	.initialise = pxe_init_structures,
+};
+
+/**
+ * Activate PXE stack
+ *
+ * @v netdev		Net device to use as PXE net device
+ */
+void pxe_activate ( struct net_device *netdev ) {
+
+	/* Ensure INT 1A is hooked */
+	if ( ! int_1a_hooked ) {
+		hook_bios_interrupt ( 0x1a, ( unsigned int ) pxe_int_1a,
+				      &pxe_int_1a_vector );
+		int_1a_hooked = 1;
+	}
+
+	/* Set PXE network device */
+	pxe_set_netdev ( netdev );
+}
+
+/**
+ * Deactivate PXE stack
+ *
+ * @ret rc		Return status code
+ */
+int pxe_deactivate ( void ) {
+	int rc;
+
+	/* Clear PXE network device */
+	pxe_set_netdev ( NULL );
+
+	/* Ensure INT 1A is unhooked, if possible */
+	if ( int_1a_hooked ) {
+		if ( ( rc = unhook_bios_interrupt ( 0x1a,
+						    (unsigned int) pxe_int_1a,
+						    &pxe_int_1a_vector ))!= 0){
+			DBG ( "Could not unhook INT 1A: %s\n",
+			      strerror ( rc ) );
+			return rc;
+		}
+		int_1a_hooked = 0;
+	}
+
+	return 0;
+}
+
+/**
+ * Start PXE NBP at 0000:7c00
+ *
+ * @ret rc		Return status code
+ */
+int pxe_start_nbp ( void ) {
+	int discard_b, discard_c, discard_d, discard_D;
+	uint16_t rc;
+
+	/* Far call to PXE NBP */
+	__asm__ __volatile__ ( REAL_CODE ( "movw %%cx, %%es\n\t"
+					   "pushw %%es\n\t"
+					   "pushw %%di\n\t"
+					   "sti\n\t"
+					   "lcall $0, $0x7c00\n\t"
+					   "addw $4, %%sp\n\t" )
+			       : "=a" ( rc ), "=b" ( discard_b ),
+				 "=c" ( discard_c ), "=d" ( discard_d ),
+				 "=D" ( discard_D )
+			       : "a" ( 0 ), "b" ( __from_text16 ( &pxenv ) ),
+			         "c" ( rm_cs ),
+			         "d" ( virt_to_phys ( &pxenv ) ),
+				 "D" ( __from_text16 ( &ppxe ) )
+			       : "esi", "ebp", "memory" );
+
+	return rc;
+}
diff --git a/gpxe/src/arch/i386/interface/pxe/pxe_entry.S b/gpxe/src/arch/i386/interface/pxe/pxe_entry.S
new file mode 100644
index 0000000..0d3a57c
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pxe/pxe_entry.S
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+	.arch i386
+
+/****************************************************************************
+ * !PXE structure
+ ****************************************************************************
+ */
+	.section ".text16.data", "aw", @progbits
+	.globl ppxe
+	.align 16
+ppxe:
+	.ascii "!PXE"			/* Signature */
+	.byte pxe_length		/* StructLength */
+	.byte 0				/* StructCksum */
+	.byte 0				/* StructRev */
+	.byte 0				/* reserved_1 */
+	.word undiheader, 0		/* UNDIROMID */
+	.word 0, 0			/* BaseROMID */
+	.word pxe_entry_sp, 0		/* EntryPointSP */
+	.word pxe_entry_esp, 0		/* EntryPointESP */
+	.word -1, -1			/* StatusCallout */
+	.byte 0				/* reserved_2 */
+	.byte SegDescCnt		/* SegDescCnt */
+	.word 0				/* FirstSelector */
+pxe_segments:
+	.word 0, 0, 0, _data16_memsz	/* Stack */
+	.word 0, 0, 0, _data16_memsz	/* UNDIData */
+	.word 0, 0, 0, _text16_memsz	/* UNDICode */
+	.word 0, 0, 0, _text16_memsz	/* UNDICodeWrite */
+	.word 0, 0, 0, 0		/* BC_Data */
+	.word 0, 0, 0, 0		/* BC_Code */
+	.word 0, 0, 0, 0		/* BC_CodeWrite */
+	.equ	SegDescCnt, ( ( . - pxe_segments ) / 8 )
+	.equ	pxe_length, . - ppxe
+	.size	ppxe, . - ppxe
+
+	/* Define undiheader=0 as a weak symbol for non-ROM builds */
+	.section ".weak", "a", @nobits
+	.weak	undiheader
+undiheader:
+
+/****************************************************************************
+ * PXENV+ structure
+ ****************************************************************************
+ */
+	.section ".text16.data", "aw", @progbits
+	.globl pxenv
+	.align 16
+pxenv:
+	.ascii "PXENV+"			/* Signature */
+	.word 0x0201			/* Version */
+	.byte pxenv_length		/* Length */
+	.byte 0				/* Checksum */
+	.word pxenv_entry, 0		/* RMEntry */
+	.long 0				/* PMEntry */
+	.word 0				/* PMSelector */
+	.word 0				/* StackSeg */
+	.word _data16_memsz		/* StackSize */
+	.word 0				/* BC_CodeSeg */
+	.word 0				/* BC_CodeSize */
+	.word 0				/* BC_DataSeg */
+	.word 0				/* BC_DataSize */
+	.word 0				/* UNDIDataSeg */
+	.word _data16_memsz		/* UNDIDataSize */
+	.word 0				/* UNDICodeSeg */
+	.word _text16_memsz		/* UNDICodeSize */
+	.word ppxe, 0			/* PXEPtr */
+	.equ	pxenv_length, . - pxenv
+	.size	pxenv, . - pxenv
+ 
+/****************************************************************************
+ * pxenv_entry (16-bit far call)
+ *
+ * PXE API call PXENV+ entry point
+ *
+ * Parameters:
+ *   %es:di : Far pointer to PXE parameter structure
+ *   %bx : PXE API call
+ * Returns:
+ *   %ax : PXE exit status
+ * Corrupts:
+ *   none
+ ****************************************************************************
+ */
+	/* Wyse Streaming Manager server (WLDRM13.BIN) assumes that
+	 * the PXENV+ entry point is at UNDI_CS:0000; apparently,
+	 * somebody at Wyse has difficulty distinguishing between the
+	 * words "may" and "must"...
+	 */
+	.section ".text16.null", "ax", @progbits
+	.code16
+pxenv_null_entry:
+	jmp	pxenv_entry
+
+	.section ".text16", "ax", @progbits
+	.code16
+pxenv_entry:
+	pushl	$pxe_api_call
+	pushw	%cs
+	call	prot_call
+	addl	$4, %esp
+	lret
+	.size	pxenv_entry, . - pxenv_entry
+
+/****************************************************************************
+ * pxe_entry
+ *
+ * PXE API call !PXE entry point
+ *
+ * Parameters:
+ *   stack : Far pointer to PXE parameter structure
+ *   stack : PXE API call
+ * Returns:
+ *   %ax : PXE exit status
+ * Corrupts:
+ *   none
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+	.code16
+pxe_entry:
+pxe_entry_sp:
+	/* Preserve original %esp */
+	pushl	%esp
+	/* Zero high word of %esp to allow use of common code */
+	movzwl	%sp, %esp
+	jmp	pxe_entry_common
+pxe_entry_esp:
+	/* Preserve %esp to match behaviour of pxe_entry_sp */
+	pushl	%esp
+pxe_entry_common:
+	/* Save PXENV+ API call registers */
+	pushw	%es
+	pushw	%di
+	pushw	%bx
+	/* Load !PXE parameters from stack into PXENV+ registers */
+	addr32 movw	18(%esp), %bx
+	movw	%bx, %es
+	addr32 movw	16(%esp), %di
+	addr32 movw	14(%esp), %bx
+	/* Make call as for PXENV+ */
+	pushw	%cs
+	call	pxenv_entry
+	/* Restore PXENV+ registers */
+	popw	%bx
+	popw	%di
+	popw	%es
+	/* Restore original %esp and return */
+	popl	%esp
+	lret
+	.size	pxe_entry, . - pxe_entry
+
+/****************************************************************************
+ * pxe_int_1a
+ *
+ * PXE INT 1A handler
+ *
+ * Parameters:
+ *   %ax : 0x5650
+ * Returns:
+ *   %ax : 0x564e
+ *   %es:bx : Far pointer to the PXENV+ structure
+ *   %edx : Physical address of the PXENV+ structure
+ *   CF cleared
+ * Corrupts:
+ *   none
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+	.code16
+	.globl	pxe_int_1a
+pxe_int_1a:
+	pushfw
+	cmpw	$0x5650, %ax
+	jne	1f
+	/* INT 1A,5650 - PXE installation check */
+	xorl	%edx, %edx
+	movw	%cs, %dx
+	movw	%dx, %es
+	movw	$pxenv, %bx
+	shll	$4, %edx
+	addl	$pxenv, %edx
+	movw	$0x564e, %ax
+	pushw	%bp
+	movw	%sp, %bp
+	andb	$~0x01, 8(%bp)	/* Clear CF on return */
+	popw	%bp
+	popfw
+	iret
+1:	/* INT 1A,other - pass through */
+	popfw
+	ljmp	*%cs:pxe_int_1a_vector
+
+	.section ".text16.data", "aw", @progbits
+	.globl	pxe_int_1a_vector
+pxe_int_1a_vector:	.long 0
diff --git a/gpxe/src/arch/i386/interface/pxe/pxe_errors.c b/gpxe/src/arch/i386/interface/pxe/pxe_errors.c
new file mode 100644
index 0000000..f884ef8
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pxe/pxe_errors.c
@@ -0,0 +1,103 @@
+#include <errno.h>
+#include <gpxe/errortab.h>
+
+/*
+ * This table was generated from the relevant section of errno.h using
+ *
+ * perl -ne 'if ( /(PXENV_STATUS_(\S+))/ ) {
+ *	$code = $1; $msg = $2;
+ *	$msg =~ s/_/ /g; $msg = ucfirst lc $msg;
+ *	$msg =~ s/(tftp|udp|arp|undi|bis|binl|pxenv|pxe|dhcp)/uc $1/ieg;
+ *	print "\t{ $code, \"$msg\" },\n";
+ *	}'
+ *
+ * followed by a little manual tweaking.
+ *
+ */
+struct errortab pxe_errortab[] __errortab = {
+	{ PXENV_STATUS_SUCCESS, "Success" },
+	{ PXENV_STATUS_FAILURE, "Failure" },
+	{ PXENV_STATUS_BAD_FUNC, "Bad function" },
+	{ PXENV_STATUS_UNSUPPORTED, "Unsupported function" },
+	{ PXENV_STATUS_KEEP_UNDI, "Keep UNDI" },
+	{ PXENV_STATUS_KEEP_ALL, "Keep all" },
+	{ PXENV_STATUS_OUT_OF_RESOURCES, "Out of resources" },
+	{ PXENV_STATUS_ARP_TIMEOUT, "ARP timeout" },
+	{ PXENV_STATUS_UDP_CLOSED, "UDP closed" },
+	{ PXENV_STATUS_UDP_OPEN, "UDP open" },
+	{ PXENV_STATUS_TFTP_CLOSED, "TFTP closed" },
+	{ PXENV_STATUS_TFTP_OPEN, "TFTP open" },
+	{ PXENV_STATUS_MCOPY_PROBLEM, "Memory copy problem" },
+	{ PXENV_STATUS_BIS_INTEGRITY_FAILURE, "BIS integrity failure" },
+	{ PXENV_STATUS_BIS_VALIDATE_FAILURE, "BIS validation failure" },
+	{ PXENV_STATUS_BIS_INIT_FAILURE, "BIS init failure" },
+	{ PXENV_STATUS_BIS_SHUTDOWN_FAILURE, "BIS shutdown failure" },
+	{ PXENV_STATUS_BIS_GBOA_FAILURE, "BIS GBOA failure" },
+	{ PXENV_STATUS_BIS_FREE_FAILURE, "BIS free failure" },
+	{ PXENV_STATUS_BIS_GSI_FAILURE, "BIS GSI failure" },
+	{ PXENV_STATUS_BIS_BAD_CKSUM, "BIS bad checksum" },
+	{ PXENV_STATUS_TFTP_CANNOT_ARP_ADDRESS, "TFTP cannot ARP address" },
+	{ PXENV_STATUS_TFTP_OPEN_TIMEOUT, "TFTP open timeout" },
+	{ PXENV_STATUS_TFTP_UNKNOWN_OPCODE, "TFTP unknown opcode" },
+	{ PXENV_STATUS_TFTP_READ_TIMEOUT, "TFTP read timeout" },
+	{ PXENV_STATUS_TFTP_ERROR_OPCODE, "TFTP error opcode" },
+	{ PXENV_STATUS_TFTP_CANNOT_OPEN_CONNECTION,
+	  "TFTP cannot open connection" },
+	{ PXENV_STATUS_TFTP_CANNOT_READ_FROM_CONNECTION,
+	  "TFTP cannot read from connection" },
+	{ PXENV_STATUS_TFTP_TOO_MANY_PACKAGES, "TFTP too many packages" },
+	{ PXENV_STATUS_TFTP_FILE_NOT_FOUND, "TFTP file not found" },
+	{ PXENV_STATUS_TFTP_ACCESS_VIOLATION, "TFTP access violation" },
+	{ PXENV_STATUS_TFTP_NO_MCAST_ADDRESS, "TFTP no mcast address" },
+	{ PXENV_STATUS_TFTP_NO_FILESIZE, "TFTP no filesize" },
+	{ PXENV_STATUS_TFTP_INVALID_PACKET_SIZE, "TFTP invalid packet size" },
+	{ PXENV_STATUS_DHCP_TIMEOUT, "DHCP timeout" },
+	{ PXENV_STATUS_DHCP_NO_IP_ADDRESS, "DHCP no ip address" },
+	{ PXENV_STATUS_DHCP_NO_BOOTFILE_NAME, "DHCP no bootfile name" },
+	{ PXENV_STATUS_DHCP_BAD_IP_ADDRESS, "DHCP bad ip address" },
+	{ PXENV_STATUS_UNDI_INVALID_FUNCTION, "UNDI invalid function" },
+	{ PXENV_STATUS_UNDI_MEDIATEST_FAILED, "UNDI mediatest failed" },
+	{ PXENV_STATUS_UNDI_CANNOT_INIT_NIC_FOR_MCAST,
+	  "UNDI cannot initialise NIC for multicast" },
+	{ PXENV_STATUS_UNDI_CANNOT_INITIALIZE_NIC,
+	  "UNDI cannot initialise NIC" },
+	{ PXENV_STATUS_UNDI_CANNOT_INITIALIZE_PHY,
+	  "UNDI cannot initialise PHY" },
+	{ PXENV_STATUS_UNDI_CANNOT_READ_CONFIG_DATA,
+	  "UNDI cannot read config data" },
+	{ PXENV_STATUS_UNDI_CANNOT_READ_INIT_DATA,
+	  "UNDI cannot read init data" },
+	{ PXENV_STATUS_UNDI_BAD_MAC_ADDRESS, "UNDI bad MAC address" },
+	{ PXENV_STATUS_UNDI_BAD_EEPROM_CHECKSUM, "UNDI bad EEPROM checksum" },
+	{ PXENV_STATUS_UNDI_ERROR_SETTING_ISR, "UNDI error setting ISR" },
+	{ PXENV_STATUS_UNDI_INVALID_STATE, "UNDI invalid state" },
+	{ PXENV_STATUS_UNDI_TRANSMIT_ERROR, "UNDI transmit error" },
+	{ PXENV_STATUS_UNDI_INVALID_PARAMETER, "UNDI invalid parameter" },
+	{ PXENV_STATUS_BSTRAP_PROMPT_MENU, "Bootstrap prompt menu" },
+	{ PXENV_STATUS_BSTRAP_MCAST_ADDR, "Bootstrap mcast addr" },
+	{ PXENV_STATUS_BSTRAP_MISSING_LIST, "Bootstrap missing list" },
+	{ PXENV_STATUS_BSTRAP_NO_RESPONSE, "Bootstrap no response" },
+	{ PXENV_STATUS_BSTRAP_FILE_TOO_BIG, "Bootstrap file too big" },
+	{ PXENV_STATUS_BINL_CANCELED_BY_KEYSTROKE,
+	  "BINL canceled by keystroke" },
+	{ PXENV_STATUS_BINL_NO_PXE_SERVER, "BINL no PXE server" },
+	{ PXENV_STATUS_NOT_AVAILABLE_IN_PMODE,
+	  "Not available in protected mode" },
+	{ PXENV_STATUS_NOT_AVAILABLE_IN_RMODE, "Not available in real mode" },
+	{ PXENV_STATUS_BUSD_DEVICE_NOT_SUPPORTED,
+	  "BUSD device not supported" },
+	{ PXENV_STATUS_LOADER_NO_FREE_BASE_MEMORY,
+	  "Loader no free base memory" },
+	{ PXENV_STATUS_LOADER_NO_BC_ROMID, "Loader no Base Code ROM ID" },
+	{ PXENV_STATUS_LOADER_BAD_BC_ROMID, "Loader bad Base Code ROM ID" },
+	{ PXENV_STATUS_LOADER_BAD_BC_RUNTIME_IMAGE,
+	  "Loader bad Base Code runtime image" },
+	{ PXENV_STATUS_LOADER_NO_UNDI_ROMID, "Loader no UNDI ROM ID" },
+	{ PXENV_STATUS_LOADER_BAD_UNDI_ROMID, "Loader bad UNDI ROM ID" },
+	{ PXENV_STATUS_LOADER_BAD_UNDI_DRIVER_IMAGE,
+	  "Loader bad UNDI driver image" },
+	{ PXENV_STATUS_LOADER_NO_PXE_STRUCT, "Loader no !PXE struct" },
+	{ PXENV_STATUS_LOADER_NO_PXENV_STRUCT, "Loader no PXENV+ struct" },
+	{ PXENV_STATUS_LOADER_UNDI_START, "Loader UNDI start" },
+	{ PXENV_STATUS_LOADER_BC_START, "Loader Base Code start" },
+};
diff --git a/gpxe/src/arch/i386/interface/pxe/pxe_file.c b/gpxe/src/arch/i386/interface/pxe/pxe_file.c
new file mode 100644
index 0000000..8d83212
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pxe/pxe_file.c
@@ -0,0 +1,306 @@
+/** @file
+ *
+ * PXE FILE API
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/posix_io.h>
+#include <gpxe/features.h>
+#include <pxe.h>
+#include <realmode.h>
+
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ * Portions (C) 2010 Shao Miller <shao.miller@yrdsb.edu.on.ca>.
+ *              [PXE exit hook logic]
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+FEATURE ( FEATURE_MISC, "PXEXT", DHCP_EB_FEATURE_PXE_EXT, 2 );
+
+/**
+ * FILE OPEN
+ *
+ * @v file_open				Pointer to a struct s_PXENV_FILE_OPEN
+ * @v s_PXENV_FILE_OPEN::FileName	URL of file to open
+ * @ret #PXENV_EXIT_SUCCESS		File was opened
+ * @ret #PXENV_EXIT_FAILURE		File was not opened
+ * @ret s_PXENV_FILE_OPEN::Status	PXE status code
+ * @ret s_PXENV_FILE_OPEN::FileHandle	Handle of opened file
+ *
+ */
+PXENV_EXIT_t pxenv_file_open ( struct s_PXENV_FILE_OPEN *file_open ) {
+	userptr_t filename;
+	size_t filename_len;
+	int fd;
+
+	DBG ( "PXENV_FILE_OPEN" );
+
+	/* Copy name from external program, and open it */
+	filename = real_to_user ( file_open->FileName.segment,
+			      file_open->FileName.offset );
+	filename_len = strlen_user ( filename, 0 );
+	{
+		char uri_string[ filename_len + 1 ];
+
+		copy_from_user ( uri_string, filename, 0,
+				 sizeof ( uri_string ) );
+		DBG ( " %s", uri_string );
+		fd = open ( uri_string );
+	}
+
+	if ( fd < 0 ) {
+		file_open->Status = PXENV_STATUS ( fd );
+		return PXENV_EXIT_FAILURE;
+	}
+
+	DBG ( " as file %d", fd );
+
+	file_open->FileHandle = fd;
+	file_open->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/**
+ * FILE CLOSE
+ *
+ * @v file_close			Pointer to a struct s_PXENV_FILE_CLOSE
+ * @v s_PXENV_FILE_CLOSE::FileHandle	File handle
+ * @ret #PXENV_EXIT_SUCCESS		File was closed
+ * @ret #PXENV_EXIT_FAILURE		File was not closed
+ * @ret s_PXENV_FILE_CLOSE::Status	PXE status code
+ *
+ */
+PXENV_EXIT_t pxenv_file_close ( struct s_PXENV_FILE_CLOSE *file_close ) {
+
+	DBG ( "PXENV_FILE_CLOSE %d", file_close->FileHandle );
+
+	close ( file_close->FileHandle );
+	file_close->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/**
+ * FILE SELECT
+ *
+ * @v file_select			Pointer to a struct s_PXENV_FILE_SELECT
+ * @v s_PXENV_FILE_SELECT::FileHandle	File handle
+ * @ret #PXENV_EXIT_SUCCESS		File has been checked for readiness
+ * @ret #PXENV_EXIT_FAILURE		File has not been checked for readiness
+ * @ret s_PXENV_FILE_SELECT::Status	PXE status code
+ * @ret s_PXENV_FILE_SELECT::Ready	Indication of readiness
+ *
+ */
+PXENV_EXIT_t pxenv_file_select ( struct s_PXENV_FILE_SELECT *file_select ) {
+	fd_set fdset;
+	int ready;
+
+	DBG ( "PXENV_FILE_SELECT %d", file_select->FileHandle );
+
+	FD_ZERO ( &fdset );
+	FD_SET ( file_select->FileHandle, &fdset );
+	if ( ( ready = select ( &fdset, 0 ) ) < 0 ) {
+		file_select->Status = PXENV_STATUS ( ready );
+		return PXENV_EXIT_FAILURE;
+	}
+
+	file_select->Ready = ( ready ? RDY_READ : 0 );
+	file_select->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/**
+ * FILE READ
+ *
+ * @v file_read				Pointer to a struct s_PXENV_FILE_READ
+ * @v s_PXENV_FILE_READ::FileHandle	File handle
+ * @v s_PXENV_FILE_READ::BufferSize	Size of data buffer
+ * @v s_PXENV_FILE_READ::Buffer		Data buffer
+ * @ret #PXENV_EXIT_SUCCESS		Data has been read from file
+ * @ret #PXENV_EXIT_FAILURE		Data has not been read from file
+ * @ret s_PXENV_FILE_READ::Status	PXE status code
+ * @ret s_PXENV_FILE_READ::Ready	Indication of readiness
+ * @ret s_PXENV_FILE_READ::BufferSize	Length of data read
+ *
+ */
+PXENV_EXIT_t pxenv_file_read ( struct s_PXENV_FILE_READ *file_read ) {
+	userptr_t buffer;
+	ssize_t len;
+
+	DBG ( "PXENV_FILE_READ %d to %04x:%04x+%04x", file_read->FileHandle,
+	      file_read->Buffer.segment, file_read->Buffer.offset,
+	      file_read->BufferSize );
+
+	buffer = real_to_user ( file_read->Buffer.segment,
+				file_read->Buffer.offset );
+	if ( ( len = read_user ( file_read->FileHandle, buffer, 0,
+				file_read->BufferSize ) ) < 0 ) {
+		file_read->Status = PXENV_STATUS ( len );
+		return PXENV_EXIT_FAILURE;
+	}
+
+	DBG ( " read %04zx", ( ( size_t ) len ) );
+
+	file_read->BufferSize = len;
+	file_read->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/**
+ * GET FILE SIZE
+ *
+ * @v get_file_size			Pointer to a struct s_PXENV_GET_FILE_SIZE
+ * @v s_PXENV_GET_FILE_SIZE::FileHandle	File handle
+ * @ret #PXENV_EXIT_SUCCESS		File size has been determined
+ * @ret #PXENV_EXIT_FAILURE		File size has not been determined
+ * @ret s_PXENV_GET_FILE_SIZE::Status	PXE status code
+ * @ret s_PXENV_GET_FILE_SIZE::FileSize	Size of file
+ */
+PXENV_EXIT_t pxenv_get_file_size ( struct s_PXENV_GET_FILE_SIZE
+				   *get_file_size ) {
+	ssize_t filesize;
+
+	DBG ( "PXENV_GET_FILE_SIZE %d", get_file_size->FileHandle );
+
+	filesize = fsize ( get_file_size->FileHandle );
+	if ( filesize < 0 ) {
+		get_file_size->Status = PXENV_STATUS ( filesize );
+		return PXENV_EXIT_FAILURE;
+	}
+
+	DBG ( " is %zd", ( ( size_t ) filesize ) );
+
+	get_file_size->FileSize = filesize;
+	get_file_size->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/**
+ * FILE EXEC
+ *
+ * @v file_exec				Pointer to a struct s_PXENV_FILE_EXEC
+ * @v s_PXENV_FILE_EXEC::Command	Command to execute
+ * @ret #PXENV_EXIT_SUCCESS		Command was executed successfully
+ * @ret #PXENV_EXIT_FAILURE		Command was not executed successfully
+ * @ret s_PXENV_FILE_EXEC::Status	PXE status code
+ *
+ */
+PXENV_EXIT_t pxenv_file_exec ( struct s_PXENV_FILE_EXEC *file_exec ) {
+	userptr_t command;
+	size_t command_len;
+	int rc;
+
+	DBG ( "PXENV_FILE_EXEC" );
+
+	/* Copy name from external program, and exec it */
+	command = real_to_user ( file_exec->Command.segment,
+				 file_exec->Command.offset );
+	command_len = strlen_user ( command, 0 );
+	{
+		char command_string[ command_len + 1 ];
+
+		copy_from_user ( command_string, command, 0,
+				 sizeof ( command_string ) );
+		DBG ( " %s", command_string );
+
+		if ( ( rc = system ( command_string ) ) != 0 ) {
+			file_exec->Status = PXENV_STATUS ( rc );
+			return PXENV_EXIT_FAILURE;
+		}
+	}
+
+	file_exec->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+segoff_t __data16 ( pxe_exit_hook ) = { 0, 0 };
+#define pxe_exit_hook __use_data16 ( pxe_exit_hook )
+
+/**
+ * FILE API CHECK
+ *
+ * @v file_exec				Pointer to a struct s_PXENV_FILE_API_CHECK
+ * @v s_PXENV_FILE_API_CHECK::Magic     Inbound magic number (0x91d447b2)
+ * @ret #PXENV_EXIT_SUCCESS		Command was executed successfully
+ * @ret #PXENV_EXIT_FAILURE		Command was not executed successfully
+ * @ret s_PXENV_FILE_API_CHECK::Status	PXE status code
+ * @ret s_PXENV_FILE_API_CHECK::Magic	Outbound magic number (0xe9c17b20)
+ * @ret s_PXENV_FILE_API_CHECK::Provider "gPXE" (0x45585067)
+ * @ret s_PXENV_FILE_API_CHECK::APIMask API function bitmask
+ * @ret s_PXENV_FILE_API_CHECK::Flags	Reserved
+ *
+ */
+PXENV_EXIT_t pxenv_file_api_check ( struct s_PXENV_FILE_API_CHECK *file_api_check ) {
+	DBG ( "PXENV_FILE_API_CHECK" );
+
+	if ( file_api_check->Magic != 0x91d447b2 ) {
+		file_api_check->Status = PXENV_STATUS_BAD_FUNC;
+		return PXENV_EXIT_FAILURE;
+	} else if ( file_api_check->Size <
+		    sizeof(struct s_PXENV_FILE_API_CHECK) ) {
+		file_api_check->Status = PXENV_STATUS_OUT_OF_RESOURCES;
+		return PXENV_EXIT_FAILURE;
+	} else {
+		file_api_check->Status   = PXENV_STATUS_SUCCESS;
+		file_api_check->Size     = sizeof(struct s_PXENV_FILE_API_CHECK);
+		file_api_check->Magic    = 0xe9c17b20;
+		file_api_check->Provider = 0x45585067; /* "gPXE" */
+		file_api_check->APIMask  = 0x0000007f; /* Functions e0-e6 */
+		/* Check to see if we have a PXE exit hook */
+		if ( pxe_exit_hook.segment | pxe_exit_hook.offset )
+			/* Function e7, also */
+			file_api_check->APIMask |= 0x00000080;
+		file_api_check->Flags    = 0;	       /* None defined */
+		return PXENV_EXIT_SUCCESS;
+	}
+}
+
+/**
+ * FILE EXIT HOOK
+ *
+ * @v file_exit_hook			Pointer to a struct
+ *					s_PXENV_FILE_EXIT_HOOK
+ * @v s_PXENV_FILE_EXIT_HOOK::Hook	SEG16:OFF16 to jump to
+ * @ret #PXENV_EXIT_SUCCESS		Successfully set hook
+ * @ret #PXENV_EXIT_FAILURE		We're not an NBP build
+ * @ret s_PXENV_FILE_EXIT_HOOK::Status	PXE status code
+ *
+ */
+PXENV_EXIT_t pxenv_file_exit_hook ( struct s_PXENV_FILE_EXIT_HOOK
+					*file_exit_hook ) {
+	DBG ( "PXENV_FILE_EXIT_HOOK" );
+
+	/* Check to see if we have a PXE exit hook */
+	if ( pxe_exit_hook.segment | pxe_exit_hook.offset ) {
+		/* We'll jump to the specified SEG16:OFF16 during exit */
+		pxe_exit_hook.segment = file_exit_hook->Hook.segment;
+		pxe_exit_hook.offset = file_exit_hook->Hook.offset;
+		file_exit_hook->Status = PXENV_STATUS_SUCCESS;
+		return PXENV_EXIT_SUCCESS;
+	}
+
+	DBG ( " not NBP" );
+	file_exit_hook->Status = PXENV_STATUS_UNSUPPORTED;
+	return PXENV_EXIT_FAILURE;
+}
+
diff --git a/gpxe/src/arch/i386/interface/pxe/pxe_loader.c b/gpxe/src/arch/i386/interface/pxe/pxe_loader.c
new file mode 100644
index 0000000..b35caf7
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pxe/pxe_loader.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <gpxe/init.h>
+#include "pxe.h"
+#include "pxe_call.h"
+
+/** @file
+ *
+ * PXE UNDI loader
+ *
+ */
+
+/* PXENV_UNDI_LOADER
+ *
+ */
+PXENV_EXIT_t undi_loader ( struct s_UNDI_LOADER *undi_loader ) {
+
+	/* Perform one-time initialisation (e.g. heap) */
+	initialise();
+
+	DBG ( "[PXENV_UNDI_LOADER to CS %04x DS %04x]",
+	      undi_loader->UNDI_CS, undi_loader->UNDI_DS );
+
+	/* Fill in UNDI loader structure */
+	undi_loader->PXEptr.segment = rm_cs;
+	undi_loader->PXEptr.offset = __from_text16 ( &ppxe );
+	undi_loader->PXENVptr.segment = rm_cs;
+	undi_loader->PXENVptr.offset = __from_text16 ( &pxenv );
+
+	undi_loader->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
diff --git a/gpxe/src/arch/i386/interface/pxe/pxe_preboot.c b/gpxe/src/arch/i386/interface/pxe/pxe_preboot.c
new file mode 100644
index 0000000..3939c7b
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pxe/pxe_preboot.c
@@ -0,0 +1,357 @@
+/** @file
+ *
+ * PXE Preboot API
+ *
+ */
+
+/* PXE API interface for Etherboot.
+ *
+ * Copyright (C) 2004 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/dhcp.h>
+#include <gpxe/fakedhcp.h>
+#include <gpxe/device.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/isapnp.h>
+#include <gpxe/init.h>
+#include <gpxe/if_ether.h>
+#include <basemem_packet.h>
+#include <biosint.h>
+#include "pxe.h"
+#include "pxe_call.h"
+
+/* Avoid dragging in isapnp.o unnecessarily */
+uint16_t isapnp_read_port;
+
+/** Zero-based versions of PXENV_GET_CACHED_INFO::PacketType */
+enum pxe_cached_info_indices {
+	CACHED_INFO_DHCPDISCOVER = ( PXENV_PACKET_TYPE_DHCP_DISCOVER - 1 ),
+	CACHED_INFO_DHCPACK = ( PXENV_PACKET_TYPE_DHCP_ACK - 1 ),
+	CACHED_INFO_BINL = ( PXENV_PACKET_TYPE_CACHED_REPLY - 1 ),
+	NUM_CACHED_INFOS
+};
+
+/** A cached DHCP packet */
+union pxe_cached_info {
+	struct dhcphdr dhcphdr;
+	/* This buffer must be *exactly* the size of a BOOTPLAYER_t
+	 * structure, otherwise WinPE will die horribly.  It takes the
+	 * size of *our* buffer and feeds it in to us as the size of
+	 * one of *its* buffers.  If our buffer is larger than it
+	 * expects, we therefore end up overwriting part of its data
+	 * segment, since it tells us to do so.  (D'oh!)
+	 *
+	 * Note that a BOOTPLAYER_t is not necessarily large enough to
+	 * hold a DHCP packet; this is a flaw in the PXE spec.
+	 */
+	BOOTPLAYER_t packet;
+} __attribute__ (( packed ));
+
+/** A PXE DHCP packet creator */
+struct pxe_dhcp_packet_creator {
+	/** Create DHCP packet
+	 *
+	 * @v netdev		Network device
+	 * @v data		Buffer for DHCP packet
+	 * @v max_len		Size of DHCP packet buffer
+	 * @ret rc		Return status code
+	 */
+	int ( * create ) ( struct net_device *netdev, void *data,
+			   size_t max_len );
+};
+
+/** PXE DHCP packet creators */
+static struct pxe_dhcp_packet_creator pxe_dhcp_packet_creators[] = {
+	[CACHED_INFO_DHCPDISCOVER] = { create_fakedhcpdiscover },
+	[CACHED_INFO_DHCPACK] = { create_fakedhcpack },
+	[CACHED_INFO_BINL] = { create_fakepxebsack },
+};
+
+/* The case in which the caller doesn't supply a buffer is really
+ * awkward to support given that we have multiple sources of options,
+ * and that we don't actually store the DHCP packets.  (We may not
+ * even have performed DHCP; we may have obtained all configuration
+ * from non-volatile stored options or from the command line.)
+ *
+ * Some NBPs rely on the buffers we provide being persistent, so we
+ * can't just use the temporary packet buffer.  4.5kB of base memory
+ * always wasted just because some clients are too lazy to provide
+ * their own buffers...
+ */
+static union pxe_cached_info __bss16_array ( cached_info, [NUM_CACHED_INFOS] );
+#define cached_info __use_data16 ( cached_info )
+
+/**
+ * Set PXE cached TFTP filename
+ *
+ * @v filename		TFTP filename
+ *
+ * This is a bug-for-bug compatibility hack needed in order to work
+ * with Microsoft Remote Installation Services (RIS).  The filename
+ * used in a call to PXENV_RESTART_TFTP or PXENV_TFTP_READ_FILE must
+ * be returned as the DHCP filename in subsequent calls to
+ * PXENV_GET_CACHED_INFO.
+ */
+void pxe_set_cached_filename ( const unsigned char *filename ) {
+	memcpy ( cached_info[CACHED_INFO_DHCPACK].dhcphdr.file, filename,
+		 sizeof ( cached_info[CACHED_INFO_DHCPACK].dhcphdr.file ) );
+	memcpy ( cached_info[CACHED_INFO_BINL].dhcphdr.file, filename,
+		 sizeof ( cached_info[CACHED_INFO_BINL].dhcphdr.file ) );
+}
+
+/**
+ * UNLOAD BASE CODE STACK
+ *
+ * @v None				-
+ * @ret ...
+ *
+ */
+PXENV_EXIT_t pxenv_unload_stack ( struct s_PXENV_UNLOAD_STACK *unload_stack ) {
+	DBG ( "PXENV_UNLOAD_STACK" );
+
+	unload_stack->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_GET_CACHED_INFO
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_get_cached_info ( struct s_PXENV_GET_CACHED_INFO
+				     *get_cached_info ) {
+	struct pxe_dhcp_packet_creator *creator;
+	union pxe_cached_info *info;
+	unsigned int idx;
+	size_t len;
+	userptr_t buffer;
+	int rc;
+
+	DBG ( "PXENV_GET_CACHED_INFO %d", get_cached_info->PacketType );
+
+	DBG ( " to %04x:%04x+%x", get_cached_info->Buffer.segment,
+	      get_cached_info->Buffer.offset, get_cached_info->BufferSize );
+
+	/* Sanity check */
+        idx = ( get_cached_info->PacketType - 1 );
+	if ( idx >= NUM_CACHED_INFOS ) {
+		DBG ( " bad PacketType" );
+		goto err;
+	}
+	info = &cached_info[idx];
+
+	/* Construct cached version of packet, if not already constructed. */
+	if ( ! info->dhcphdr.op ) {
+		/* Construct DHCP packet */
+		creator = &pxe_dhcp_packet_creators[idx];
+		if ( ( rc = creator->create ( pxe_netdev, info,
+					      sizeof ( *info ) ) ) != 0 ) {
+			DBG ( " failed to build packet" );
+			goto err;
+		}
+	}
+
+	len = get_cached_info->BufferSize;
+	if ( len == 0 ) {
+		/* Point client at our cached buffer.
+		 *
+		 * To add to the fun, Intel decided at some point in
+		 * the evolution of the PXE specification to add the
+		 * BufferLimit field, which we are meant to fill in
+		 * with the length of our packet buffer, so that the
+		 * caller can safely modify the boot server reply
+		 * packet stored therein.  However, this field was not
+		 * present in earlier versions of the PXE spec, and
+		 * there is at least one PXE NBP (Altiris) which
+		 * allocates only exactly enough space for this
+		 * earlier, shorter version of the structure.  If we
+		 * actually fill in the BufferLimit field, we
+		 * therefore risk trashing random areas of the
+		 * caller's memory.  If we *don't* fill it in, then
+		 * the caller is at liberty to assume that whatever
+		 * random value happened to be in that location
+		 * represents the length of the buffer we've just
+		 * passed back to it.
+		 *
+		 * Since older PXE stacks won't fill this field in
+		 * anyway, it's probably safe to assume that no
+		 * callers actually rely on it, so we choose to not
+		 * fill it in.
+		 */
+		get_cached_info->Buffer.segment = rm_ds;
+		get_cached_info->Buffer.offset = __from_data16 ( info );
+		get_cached_info->BufferSize = sizeof ( *info );
+		DBG ( " returning %04x:%04x+%04x['%x']",
+		      get_cached_info->Buffer.segment,
+		      get_cached_info->Buffer.offset,
+		      get_cached_info->BufferSize,
+		      get_cached_info->BufferLimit );
+	} else {
+		/* Copy packet to client buffer */
+		if ( len > sizeof ( *info ) )
+			len = sizeof ( *info );
+		if ( len < sizeof ( *info ) )
+			DBG ( " buffer may be too short" );
+		buffer = real_to_user ( get_cached_info->Buffer.segment,
+					get_cached_info->Buffer.offset );
+		copy_to_user ( buffer, 0, info, len );
+		get_cached_info->BufferSize = len;
+	}
+
+	get_cached_info->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+
+ err:
+	get_cached_info->Status = PXENV_STATUS_OUT_OF_RESOURCES;
+	return PXENV_EXIT_FAILURE;
+}
+
+/* PXENV_RESTART_TFTP
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_restart_tftp ( struct s_PXENV_TFTP_READ_FILE
+				  *restart_tftp ) {
+	PXENV_EXIT_t tftp_exit;
+
+	DBG ( "PXENV_RESTART_TFTP " );
+
+	/* Intel bug-for-bug hack */
+	pxe_set_cached_filename ( restart_tftp->FileName );
+
+	/* Words cannot describe the complete mismatch between the PXE
+	 * specification and any possible version of reality...
+	 */
+	restart_tftp->Buffer = PXE_LOAD_PHYS; /* Fixed by spec, apparently */
+	restart_tftp->BufferSize = ( 0xa0000 - PXE_LOAD_PHYS ); /* Near enough */
+	tftp_exit = pxenv_tftp_read_file ( restart_tftp );
+	if ( tftp_exit != PXENV_EXIT_SUCCESS )
+		return tftp_exit;
+
+	/* Fire up the new NBP */
+	restart_tftp->Status = pxe_start_nbp();
+
+	/* Not sure what "SUCCESS" actually means, since we can only
+	 * return if the new NBP failed to boot...
+	 */
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_START_UNDI
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_start_undi ( struct s_PXENV_START_UNDI *start_undi ) {
+	unsigned int bus_type;
+	unsigned int location;
+	struct net_device *netdev;
+
+	DBG ( "PXENV_START_UNDI %04x:%04x:%04x",
+	      start_undi->AX, start_undi->BX, start_undi->DX );
+
+	/* Determine bus type and location.  Use a heuristic to decide
+	 * whether we are PCI or ISAPnP
+	 */
+	if ( ( start_undi->DX >= ISAPNP_READ_PORT_MIN ) &&
+	     ( start_undi->DX <= ISAPNP_READ_PORT_MAX ) &&
+	     ( start_undi->BX >= ISAPNP_CSN_MIN ) &&
+	     ( start_undi->BX <= ISAPNP_CSN_MAX ) ) {
+		bus_type = BUS_TYPE_ISAPNP;
+		location = start_undi->BX;
+		/* Record ISAPnP read port for use by isapnp.c */
+		isapnp_read_port = start_undi->DX;
+	} else {
+		bus_type = BUS_TYPE_PCI;
+		location = start_undi->AX;
+	}
+
+	/* Probe for devices, etc. */
+	startup();
+
+	/* Look for a matching net device */
+	netdev = find_netdev_by_location ( bus_type, location );
+	if ( ! netdev ) {
+		DBG ( " no net device found" );
+		start_undi->Status = PXENV_STATUS_UNDI_CANNOT_INITIALIZE_NIC;
+		return PXENV_EXIT_FAILURE;
+	}
+	DBG ( " using netdev %s", netdev->name );
+
+	/* Activate PXE */
+	pxe_activate ( netdev );
+
+	start_undi->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_STOP_UNDI
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_stop_undi ( struct s_PXENV_STOP_UNDI *stop_undi ) {
+	DBG ( "PXENV_STOP_UNDI" );
+
+	/* Deactivate PXE */
+	pxe_deactivate();
+
+	/* Prepare for unload */
+	shutdown ( SHUTDOWN_BOOT );
+
+	/* Check to see if we still have any hooked interrupts */
+	if ( hooked_bios_interrupts != 0 ) {
+		DBG ( "PXENV_STOP_UNDI failed: %d interrupts still hooked\n",
+		      hooked_bios_interrupts );
+		stop_undi->Status = PXENV_STATUS_KEEP_UNDI;
+		return PXENV_EXIT_FAILURE;
+	}
+
+	stop_undi->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_START_BASE
+ *
+ * Status: won't implement (requires major structural changes)
+ */
+PXENV_EXIT_t pxenv_start_base ( struct s_PXENV_START_BASE *start_base ) {
+	DBG ( "PXENV_START_BASE" );
+
+	start_base->Status = PXENV_STATUS_UNSUPPORTED;
+	return PXENV_EXIT_FAILURE;
+}
+
+/* PXENV_STOP_BASE
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_stop_base ( struct s_PXENV_STOP_BASE *stop_base ) {
+	DBG ( "PXENV_STOP_BASE" );
+
+	/* The only time we will be called is when the NBP is trying
+	 * to shut down the PXE stack.  There's nothing we need to do
+	 * in this call.
+	 */
+
+	stop_base->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
diff --git a/gpxe/src/arch/i386/interface/pxe/pxe_tftp.c b/gpxe/src/arch/i386/interface/pxe/pxe_tftp.c
new file mode 100644
index 0000000..0e3ca3c
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pxe/pxe_tftp.c
@@ -0,0 +1,586 @@
+/** @file
+ *
+ * PXE TFTP API
+ *
+ */
+
+/*
+ * Copyright (C) 2004 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/in.h>
+#include <gpxe/tftp.h>
+#include <gpxe/xfer.h>
+#include <gpxe/open.h>
+#include <gpxe/process.h>
+#include <pxe.h>
+
+/** A PXE TFTP connection */
+struct pxe_tftp_connection {
+	/** Data transfer interface */
+	struct xfer_interface xfer;
+	/** Data buffer */
+	userptr_t buffer;
+	/** Size of data buffer */
+	size_t size;
+	/** Starting offset of data buffer */
+	size_t start;
+	/** File position */
+	size_t offset;
+	/** Maximum file position */
+	size_t max_offset;
+	/** Block size */
+	size_t blksize;
+	/** Block index */
+	unsigned int blkidx;
+	/** Overall return status code */
+	int rc;
+};
+
+/** The PXE TFTP connection */
+static struct pxe_tftp_connection pxe_tftp = {
+	.xfer = XFER_INIT ( &null_xfer_ops ),
+};
+
+/**
+ * Close PXE TFTP connection
+ *
+ * @v rc		Final status code
+ */
+static void pxe_tftp_close ( int rc ) {
+	xfer_nullify ( &pxe_tftp.xfer );
+	xfer_close ( &pxe_tftp.xfer, rc );
+	pxe_tftp.rc = rc;
+}
+
+/**
+ * Receive new data
+ *
+ * @v xfer		Data transfer interface
+ * @v iobuf		I/O buffer
+ * @v meta		Transfer metadata
+ * @ret rc		Return status code
+ */
+static int pxe_tftp_xfer_deliver_iob ( struct xfer_interface *xfer __unused,
+				       struct io_buffer *iobuf,
+				       struct xfer_metadata *meta ) {
+	size_t len = iob_len ( iobuf );
+	int rc = 0;
+
+	/* Calculate new buffer position */
+	if ( meta->whence != SEEK_CUR )
+		pxe_tftp.offset = 0;
+	pxe_tftp.offset += meta->offset;
+
+	/* Copy data block to buffer */
+	if ( len == 0 ) {
+		/* No data (pure seek); treat as success */
+	} else if ( pxe_tftp.offset < pxe_tftp.start ) {
+		DBG ( " buffer underrun at %zx (min %zx)",
+		      pxe_tftp.offset, pxe_tftp.start );
+		rc = -ENOBUFS;
+	} else if ( ( pxe_tftp.offset + len ) >
+		    ( pxe_tftp.start + pxe_tftp.size ) ) {
+		DBG ( " buffer overrun at %zx (max %zx)",
+		      ( pxe_tftp.offset + len ),
+		      ( pxe_tftp.start + pxe_tftp.size ) );
+		rc = -ENOBUFS;
+	} else {
+		copy_to_user ( pxe_tftp.buffer,
+			       ( pxe_tftp.offset - pxe_tftp.start ),
+			       iobuf->data, len );
+	}
+
+	/* Calculate new buffer position */
+	pxe_tftp.offset += len;
+
+	/* Record maximum offset as the file size */
+	if ( pxe_tftp.max_offset < pxe_tftp.offset )
+		pxe_tftp.max_offset = pxe_tftp.offset;
+
+	/* Terminate transfer on error */
+	if ( rc != 0 )
+		pxe_tftp_close ( rc );
+
+	free_iob ( iobuf );
+	return rc;
+}
+
+/**
+ * Handle close() event
+ *
+ * @v xfer		Data transfer interface
+ * @v rc		Reason for close
+ */
+static void pxe_tftp_xfer_close ( struct xfer_interface *xfer __unused,
+				  int rc ) {
+	pxe_tftp_close ( rc );
+}
+
+static struct xfer_interface_operations pxe_tftp_xfer_ops = {
+	.close		= pxe_tftp_xfer_close,
+	.vredirect	= xfer_vreopen,
+	.window		= unlimited_xfer_window,
+	.alloc_iob	= default_xfer_alloc_iob,
+	.deliver_iob	= pxe_tftp_xfer_deliver_iob,
+	.deliver_raw	= xfer_deliver_as_iob,
+};
+
+/**
+ * Maximum length of a PXE TFTP URI
+ *
+ * The PXE TFTP API provides 128 characters for the filename; the
+ * extra 128 bytes allow for the remainder of the URI.
+ */
+#define PXE_TFTP_URI_LEN 256
+
+/**
+ * Open PXE TFTP connection
+ *
+ * @v ipaddress		IP address
+ * @v port		TFTP server port
+ * @v filename		File name
+ * @v blksize		Requested block size
+ * @ret rc		Return status code
+ */
+static int pxe_tftp_open ( uint32_t ipaddress, unsigned int port,
+			   const unsigned char *filename, size_t blksize,
+			   int sizeonly ) {
+	char uri_string[PXE_TFTP_URI_LEN];
+	struct in_addr address;
+	int rc;
+
+	/* Intel bug-for-bug hack */
+	pxe_set_cached_filename ( filename );
+
+	/* Reset PXE TFTP connection structure */
+	memset ( &pxe_tftp, 0, sizeof ( pxe_tftp ) );
+	xfer_init ( &pxe_tftp.xfer, &pxe_tftp_xfer_ops, NULL );
+	pxe_tftp.rc = -EINPROGRESS;
+
+	/* Construct URI string */
+	address.s_addr = ipaddress;
+	if ( ! port )
+		port = htons ( TFTP_PORT );
+	if ( blksize < TFTP_DEFAULT_BLKSIZE )
+		blksize = TFTP_DEFAULT_BLKSIZE;
+	snprintf ( uri_string, sizeof ( uri_string ),
+		   "tftp%s://%s:%d%s%s?blksize=%zd",
+		   sizeonly ? "size" : "",
+		   inet_ntoa ( address ), ntohs ( port ),
+		   ( ( filename[0] == '/' ) ? "" : "/" ), filename, blksize );
+	DBG ( " %s", uri_string );
+
+	/* Open PXE TFTP connection */
+	if ( ( rc = xfer_open_uri_string ( &pxe_tftp.xfer,
+					   uri_string ) ) != 0 ) {
+		DBG ( " could not open (%s)\n", strerror ( rc ) );
+		return rc;
+	}
+
+	return 0;
+}
+
+/**
+ * TFTP OPEN
+ *
+ * @v tftp_open				Pointer to a struct s_PXENV_TFTP_OPEN
+ * @v s_PXENV_TFTP_OPEN::ServerIPAddress TFTP server IP address
+ * @v s_PXENV_TFTP_OPEN::GatewayIPAddress Relay agent IP address, or 0.0.0.0
+ * @v s_PXENV_TFTP_OPEN::FileName	Name of file to open
+ * @v s_PXENV_TFTP_OPEN::TFTPPort	TFTP server UDP port
+ * @v s_PXENV_TFTP_OPEN::PacketSize	TFTP blksize option to request
+ * @ret #PXENV_EXIT_SUCCESS		File was opened
+ * @ret #PXENV_EXIT_FAILURE		File was not opened
+ * @ret s_PXENV_TFTP_OPEN::Status	PXE status code
+ * @ret s_PXENV_TFTP_OPEN::PacketSize	Negotiated blksize
+ * @err #PXENV_STATUS_TFTP_INVALID_PACKET_SIZE Requested blksize too small
+ *
+ * Opens a TFTP connection for downloading a file a block at a time
+ * using pxenv_tftp_read().
+ *
+ * If s_PXENV_TFTP_OPEN::GatewayIPAddress is 0.0.0.0, normal IP
+ * routing will take place.  See the relevant
+ * @ref pxe_routing "implementation note" for more details.
+ *
+ * On x86, you must set the s_PXE::StatusCallout field to a nonzero
+ * value before calling this function in protected mode.  You cannot
+ * call this function with a 32-bit stack segment.  (See the relevant
+ * @ref pxe_x86_pmode16 "implementation note" for more details.)
+ * 
+ * @note According to the PXE specification version 2.1, this call
+ * "opens a file for reading/writing", though how writing is to be
+ * achieved without the existence of an API call %pxenv_tftp_write()
+ * is not made clear.
+ *
+ * @note Despite the existence of the numerous statements within the
+ * PXE specification of the form "...if a TFTP/MTFTP or UDP connection
+ * is active...", you cannot use pxenv_tftp_open() and
+ * pxenv_tftp_read() to read a file via MTFTP; only via plain old
+ * TFTP.  If you want to use MTFTP, use pxenv_tftp_read_file()
+ * instead.  Astute readers will note that, since
+ * pxenv_tftp_read_file() is an atomic operation from the point of
+ * view of the PXE API, it is conceptually impossible to issue any
+ * other PXE API call "if an MTFTP connection is active".
+ */
+PXENV_EXIT_t pxenv_tftp_open ( struct s_PXENV_TFTP_OPEN *tftp_open ) {
+	int rc;
+
+	DBG ( "PXENV_TFTP_OPEN" );
+
+	/* Guard against callers that fail to close before re-opening */
+	pxe_tftp_close ( 0 );
+
+	/* Open connection */
+	if ( ( rc = pxe_tftp_open ( tftp_open->ServerIPAddress,
+				    tftp_open->TFTPPort,
+				    tftp_open->FileName,
+				    tftp_open->PacketSize,
+				    0) ) != 0 ) {
+		tftp_open->Status = PXENV_STATUS ( rc );
+		return PXENV_EXIT_FAILURE;
+	}
+
+	/* Wait for OACK to arrive so that we have the block size */
+	while ( ( ( rc = pxe_tftp.rc ) == -EINPROGRESS ) &&
+		( pxe_tftp.max_offset == 0 ) ) {
+		step();
+	}
+	pxe_tftp.blksize = xfer_window ( &pxe_tftp.xfer );
+	tftp_open->PacketSize = pxe_tftp.blksize;
+	DBG ( " blksize=%d", tftp_open->PacketSize );
+
+	/* EINPROGRESS is normal; we don't wait for the whole transfer */
+	if ( rc == -EINPROGRESS )
+		rc = 0;
+
+	tftp_open->Status = PXENV_STATUS ( rc );
+	return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
+}
+
+/**
+ * TFTP CLOSE
+ *
+ * @v tftp_close			Pointer to a struct s_PXENV_TFTP_CLOSE
+ * @ret #PXENV_EXIT_SUCCESS		File was closed successfully
+ * @ret #PXENV_EXIT_FAILURE		File was not closed
+ * @ret s_PXENV_TFTP_CLOSE::Status	PXE status code
+ * @err None				-
+ *
+ * Close a connection previously opened with pxenv_tftp_open().  You
+ * must have previously opened a connection with pxenv_tftp_open().
+ *
+ * On x86, you must set the s_PXE::StatusCallout field to a nonzero
+ * value before calling this function in protected mode.  You cannot
+ * call this function with a 32-bit stack segment.  (See the relevant
+ * @ref pxe_x86_pmode16 "implementation note" for more details.)
+ */
+PXENV_EXIT_t pxenv_tftp_close ( struct s_PXENV_TFTP_CLOSE *tftp_close ) {
+	DBG ( "PXENV_TFTP_CLOSE" );
+
+	pxe_tftp_close ( 0 );
+	tftp_close->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/**
+ * TFTP READ
+ *
+ * @v tftp_read				Pointer to a struct s_PXENV_TFTP_READ
+ * @v s_PXENV_TFTP_READ::Buffer		Address of data buffer
+ * @ret #PXENV_EXIT_SUCCESS		Data was read successfully
+ * @ret #PXENV_EXIT_FAILURE		Data was not read
+ * @ret s_PXENV_TFTP_READ::Status	PXE status code
+ * @ret s_PXENV_TFTP_READ::PacketNumber	TFTP packet number
+ * @ret s_PXENV_TFTP_READ::BufferSize	Length of data written into buffer
+ *
+ * Reads a single packet from a connection previously opened with
+ * pxenv_tftp_open() into the data buffer pointed to by
+ * s_PXENV_TFTP_READ::Buffer.  You must have previously opened a
+ * connection with pxenv_tftp_open().  The data written into
+ * s_PXENV_TFTP_READ::Buffer is just the file data; the various
+ * network headers have already been removed.
+ *
+ * The buffer must be large enough to contain a packet of the size
+ * negotiated via the s_PXENV_TFTP_OPEN::PacketSize field in the
+ * pxenv_tftp_open() call.  It is worth noting that the PXE
+ * specification does @b not require the caller to fill in
+ * s_PXENV_TFTP_READ::BufferSize before calling pxenv_tftp_read(), so
+ * the PXE stack is free to ignore whatever value the caller might
+ * place there and just assume that the buffer is large enough.  That
+ * said, it may be worth the caller always filling in
+ * s_PXENV_TFTP_READ::BufferSize to guard against PXE stacks that
+ * mistake it for an input parameter.
+ *
+ * The length of the TFTP data packet will be returned via
+ * s_PXENV_TFTP_READ::BufferSize.  If this length is less than the
+ * blksize negotiated via s_PXENV_TFTP_OPEN::PacketSize in the call to
+ * pxenv_tftp_open(), this indicates that the block is the last block
+ * in the file.  Note that zero is a valid length for
+ * s_PXENV_TFTP_READ::BufferSize, and will occur when the length of
+ * the file is a multiple of the blksize.
+ *
+ * The PXE specification doesn't actually state that calls to
+ * pxenv_tftp_read() will return the data packets in strict sequential
+ * order, though most PXE stacks will probably do so.  The sequence
+ * number of the packet will be returned in
+ * s_PXENV_TFTP_READ::PacketNumber.  The first packet in the file has
+ * a sequence number of one, not zero.
+ *
+ * To guard against flawed PXE stacks, the caller should probably set
+ * s_PXENV_TFTP_READ::PacketNumber to one less than the expected
+ * returned value (i.e. set it to zero for the first call to
+ * pxenv_tftp_read() and then re-use the returned s_PXENV_TFTP_READ
+ * parameter block for subsequent calls without modifying
+ * s_PXENV_TFTP_READ::PacketNumber between calls).  The caller should
+ * also guard against potential problems caused by flawed
+ * implementations returning the occasional duplicate packet, by
+ * checking that the value returned in s_PXENV_TFTP_READ::PacketNumber
+ * is as expected (i.e. one greater than that returned from the
+ * previous call to pxenv_tftp_read()).
+ *
+ * On x86, you must set the s_PXE::StatusCallout field to a nonzero
+ * value before calling this function in protected mode.  You cannot
+ * call this function with a 32-bit stack segment.  (See the relevant
+ * @ref pxe_x86_pmode16 "implementation note" for more details.)
+ */
+PXENV_EXIT_t pxenv_tftp_read ( struct s_PXENV_TFTP_READ *tftp_read ) {
+	int rc;
+
+	DBG ( "PXENV_TFTP_READ to %04x:%04x",
+	      tftp_read->Buffer.segment, tftp_read->Buffer.offset );
+
+	/* Read single block into buffer */
+	pxe_tftp.buffer = real_to_user ( tftp_read->Buffer.segment,
+					 tftp_read->Buffer.offset );
+	pxe_tftp.size = pxe_tftp.blksize;
+	pxe_tftp.start = pxe_tftp.offset;
+	while ( ( ( rc = pxe_tftp.rc ) == -EINPROGRESS ) &&
+		( pxe_tftp.offset == pxe_tftp.start ) )
+		step();
+	pxe_tftp.buffer = UNULL;
+	tftp_read->BufferSize = ( pxe_tftp.offset - pxe_tftp.start );
+	tftp_read->PacketNumber = ++pxe_tftp.blkidx;
+
+	/* EINPROGRESS is normal if we haven't reached EOF yet */
+	if ( rc == -EINPROGRESS )
+		rc = 0;
+
+	tftp_read->Status = PXENV_STATUS ( rc );
+	return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
+}
+
+/**
+ * TFTP/MTFTP read file
+ *
+ * @v tftp_read_file		     Pointer to a struct s_PXENV_TFTP_READ_FILE
+ * @v s_PXENV_TFTP_READ_FILE::FileName		File name
+ * @v s_PXENV_TFTP_READ_FILE::BufferSize 	Size of the receive buffer
+ * @v s_PXENV_TFTP_READ_FILE::Buffer		Address of the receive buffer
+ * @v s_PXENV_TFTP_READ_FILE::ServerIPAddress	TFTP server IP address
+ * @v s_PXENV_TFTP_READ_FILE::GatewayIPAddress	Relay agent IP address
+ * @v s_PXENV_TFTP_READ_FILE::McastIPAddress	File's multicast IP address
+ * @v s_PXENV_TFTP_READ_FILE::TFTPClntPort	Client multicast UDP port
+ * @v s_PXENV_TFTP_READ_FILE::TFTPSrvPort	Server multicast UDP port
+ * @v s_PXENV_TFTP_READ_FILE::TFTPOpenTimeOut	Time to wait for first packet
+ * @v s_PXENV_TFTP_READ_FILE::TFTPReopenDelay	MTFTP inactivity timeout
+ * @ret #PXENV_EXIT_SUCCESS			File downloaded successfully
+ * @ret #PXENV_EXIT_FAILURE			File not downloaded
+ * @ret s_PXENV_TFTP_READ_FILE::Status		PXE status code
+ * @ret s_PXENV_TFTP_READ_FILE::BufferSize	Length of downloaded file
+ *
+ * Downloads an entire file via either TFTP or MTFTP into the buffer
+ * pointed to by s_PXENV_TFTP_READ_FILE::Buffer.
+ *
+ * The PXE specification does not make it clear how the caller
+ * requests that MTFTP be used rather than TFTP (or vice versa).  One
+ * reasonable guess is that setting
+ * s_PXENV_TFTP_READ_FILE::McastIPAddress to 0.0.0.0 would cause TFTP
+ * to be used instead of MTFTP, though it is conceivable that some PXE
+ * stacks would interpret that as "use the DHCP-provided multicast IP
+ * address" instead.  Some PXE stacks will not implement MTFTP at all,
+ * and will always use TFTP.
+ *
+ * It is not specified whether or not
+ * s_PXENV_TFTP_READ_FILE::TFTPSrvPort will be used as the TFTP server
+ * port for TFTP (rather than MTFTP) downloads.  Callers should assume
+ * that the only way to access a TFTP server on a non-standard port is
+ * to use pxenv_tftp_open() and pxenv_tftp_read().
+ *
+ * If s_PXENV_TFTP_READ_FILE::GatewayIPAddress is 0.0.0.0, normal IP
+ * routing will take place.  See the relevant
+ * @ref pxe_routing "implementation note" for more details.
+ *
+ * It is interesting to note that s_PXENV_TFTP_READ_FILE::Buffer is an
+ * #ADDR32_t type, i.e. nominally a flat physical address.  Some PXE
+ * NBPs (e.g. NTLDR) are known to call pxenv_tftp_read_file() in real
+ * mode with s_PXENV_TFTP_READ_FILE::Buffer set to an address above
+ * 1MB.  This means that PXE stacks must be prepared to write to areas
+ * outside base memory.  Exactly how this is to be achieved is not
+ * specified, though using INT 15,87 is as close to a standard method
+ * as any, and should probably be used.  Switching to protected-mode
+ * in order to access high memory will fail if pxenv_tftp_read_file()
+ * is called in V86 mode; it is reasonably to expect that a V86
+ * monitor would intercept the relatively well-defined INT 15,87 if it
+ * wants the PXE stack to be able to write to high memory.
+ *
+ * Things get even more interesting if pxenv_tftp_read_file() is
+ * called in protected mode, because there is then absolutely no way
+ * for the PXE stack to write to an absolute physical address.  You
+ * can't even get around the problem by creating a special "access
+ * everything" segment in the s_PXE data structure, because the
+ * #SEGDESC_t descriptors are limited to 64kB in size.
+ *
+ * Previous versions of the PXE specification (e.g. WfM 1.1a) provide
+ * a separate API call, %pxenv_tftp_read_file_pmode(), specifically to
+ * work around this problem.  The s_PXENV_TFTP_READ_FILE_PMODE
+ * parameter block splits s_PXENV_TFTP_READ_FILE::Buffer into
+ * s_PXENV_TFTP_READ_FILE_PMODE::BufferSelector and
+ * s_PXENV_TFTP_READ_FILE_PMODE::BufferOffset, i.e. it provides a
+ * protected-mode segment:offset address for the data buffer.  This
+ * API call is no longer present in version 2.1 of the PXE
+ * specification.
+ *
+ * Etherboot makes the assumption that s_PXENV_TFTP_READ_FILE::Buffer
+ * is an offset relative to the caller's data segment, when
+ * pxenv_tftp_read_file() is called in protected mode.
+ *
+ * On x86, you must set the s_PXE::StatusCallout field to a nonzero
+ * value before calling this function in protected mode.  You cannot
+ * call this function with a 32-bit stack segment.  (See the relevant
+ * @ref pxe_x86_pmode16 "implementation note" for more details.)
+ *
+ * @note Microsoft's NTLDR assumes that the filename passed in via
+ * s_PXENV_TFTP_READ_FILE::FileName will be stored in the "file" field
+ * of the stored DHCPACK packet, whence it will be returned via any
+ * subsequent calls to pxenv_get_cached_info().  Though this is
+ * essentially a bug in the Intel PXE implementation (not, for once,
+ * in the specification!), it is a bug that Microsoft relies upon, and
+ * so we implement this bug-for-bug compatibility by overwriting the
+ * filename stored DHCPACK packet with the filename passed in
+ * s_PXENV_TFTP_READ_FILE::FileName.
+ *
+ */
+PXENV_EXIT_t pxenv_tftp_read_file ( struct s_PXENV_TFTP_READ_FILE
+				    *tftp_read_file ) {
+	int rc;
+
+	DBG ( "PXENV_TFTP_READ_FILE to %08x+%x", tftp_read_file->Buffer,
+	      tftp_read_file->BufferSize );
+
+	/* Open TFTP file */
+	if ( ( rc = pxe_tftp_open ( tftp_read_file->ServerIPAddress, 0,
+				    tftp_read_file->FileName, 0, 0 ) ) != 0 ) {
+		tftp_read_file->Status = PXENV_STATUS ( rc );
+		return PXENV_EXIT_FAILURE;
+	}
+
+	/* Read entire file */
+	pxe_tftp.buffer = phys_to_user ( tftp_read_file->Buffer );
+	pxe_tftp.size = tftp_read_file->BufferSize;
+	while ( ( rc = pxe_tftp.rc ) == -EINPROGRESS )
+		step();
+	pxe_tftp.buffer = UNULL;
+	tftp_read_file->BufferSize = pxe_tftp.max_offset;
+
+	/* Close TFTP file */
+	pxe_tftp_close ( rc );
+
+	tftp_read_file->Status = PXENV_STATUS ( rc );
+	return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
+}
+
+/**
+ * TFTP GET FILE SIZE
+ *
+ * @v tftp_get_fsize		     Pointer to a struct s_PXENV_TFTP_GET_FSIZE
+ * @v s_PXENV_TFTP_GET_FSIZE::ServerIPAddress	TFTP server IP address
+ * @v s_PXENV_TFTP_GET_FSIZE::GatewayIPAddress	Relay agent IP address
+ * @v s_PXENV_TFTP_GET_FSIZE::FileName	File name
+ * @ret #PXENV_EXIT_SUCCESS		File size was determined successfully
+ * @ret #PXENV_EXIT_FAILURE		File size was not determined
+ * @ret s_PXENV_TFTP_GET_FSIZE::Status	PXE status code
+ * @ret s_PXENV_TFTP_GET_FSIZE::FileSize	File size
+ *
+ * Determine the size of a file on a TFTP server.  This uses the
+ * "tsize" TFTP option, and so will not work with a TFTP server that
+ * does not support TFTP options, or that does not support the "tsize"
+ * option.
+ *
+ * The PXE specification states that this API call will @b not open a
+ * TFTP connection for subsequent use with pxenv_tftp_read().  (This
+ * is somewhat daft, since the only way to obtain the file size via
+ * the "tsize" option involves issuing a TFTP open request, but that's
+ * life.)
+ *
+ * You cannot call pxenv_tftp_get_fsize() while a TFTP or UDP
+ * connection is open.
+ *
+ * If s_PXENV_TFTP_GET_FSIZE::GatewayIPAddress is 0.0.0.0, normal IP
+ * routing will take place.  See the relevant
+ * @ref pxe_routing "implementation note" for more details.
+ *
+ * On x86, you must set the s_PXE::StatusCallout field to a nonzero
+ * value before calling this function in protected mode.  You cannot
+ * call this function with a 32-bit stack segment.  (See the relevant
+ * @ref pxe_x86_pmode16 "implementation note" for more details.)
+ * 
+ * @note There is no way to specify the TFTP server port with this API
+ * call.  Though you can open a file using a non-standard TFTP server
+ * port (via s_PXENV_TFTP_OPEN::TFTPPort or, potentially,
+ * s_PXENV_TFTP_READ_FILE::TFTPSrvPort), you can only get the size of
+ * a file from a TFTP server listening on the standard TFTP port.
+ * "Consistency" is not a word in Intel's vocabulary.
+ */
+PXENV_EXIT_t pxenv_tftp_get_fsize ( struct s_PXENV_TFTP_GET_FSIZE
+				    *tftp_get_fsize ) {
+	int rc;
+
+	DBG ( "PXENV_TFTP_GET_FSIZE" );
+
+	/* Open TFTP file */
+	if ( ( rc = pxe_tftp_open ( tftp_get_fsize->ServerIPAddress, 0,
+				    tftp_get_fsize->FileName, 0, 1 ) ) != 0 ) {
+		tftp_get_fsize->Status = PXENV_STATUS ( rc );
+		return PXENV_EXIT_FAILURE;
+	}
+
+	/* Wait for initial seek to arrive, and record size */
+	while ( ( ( rc = pxe_tftp.rc ) == -EINPROGRESS ) &&
+		( pxe_tftp.max_offset == 0 ) ) {
+		step();
+	}
+	tftp_get_fsize->FileSize = pxe_tftp.max_offset;
+	DBG ( " fsize=%d", tftp_get_fsize->FileSize );
+
+	/* EINPROGRESS is normal; we don't wait for the whole transfer */
+	if ( rc == -EINPROGRESS )
+		rc = 0;
+
+	/* Close TFTP file */
+	pxe_tftp_close ( rc );
+
+	tftp_get_fsize->Status = PXENV_STATUS ( rc );
+	return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
+}
diff --git a/gpxe/src/arch/i386/interface/pxe/pxe_udp.c b/gpxe/src/arch/i386/interface/pxe/pxe_udp.c
new file mode 100644
index 0000000..f470220
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pxe/pxe_udp.c
@@ -0,0 +1,405 @@
+/** @file
+ *
+ * PXE UDP API
+ *
+ */
+
+#include <string.h>
+#include <byteswap.h>
+#include <gpxe/xfer.h>
+#include <gpxe/udp.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/process.h>
+#include <pxe.h>
+
+/*
+ * Copyright (C) 2004 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/** A PXE UDP connection */
+struct pxe_udp_connection {
+	/** Data transfer interface to UDP stack */
+	struct xfer_interface xfer;
+	/** Local address */
+	struct sockaddr_in local;
+	/** Current PXENV_UDP_READ parameter block */
+	struct s_PXENV_UDP_READ *pxenv_udp_read;
+};
+
+/**
+ * Receive PXE UDP data
+ *
+ * @v xfer			Data transfer interface
+ * @v iobuf			I/O buffer
+ * @v meta			Data transfer metadata
+ * @ret rc			Return status code
+ *
+ * Receives a packet as part of the current pxenv_udp_read()
+ * operation.
+ */
+static int pxe_udp_deliver_iob ( struct xfer_interface *xfer,
+				 struct io_buffer *iobuf,
+				 struct xfer_metadata *meta ) {
+	struct pxe_udp_connection *pxe_udp = 
+		container_of ( xfer, struct pxe_udp_connection, xfer );
+	struct s_PXENV_UDP_READ *pxenv_udp_read = pxe_udp->pxenv_udp_read;
+	struct sockaddr_in *sin_src;
+	struct sockaddr_in *sin_dest;
+	userptr_t buffer;
+	size_t len;
+	int rc = 0;
+
+	if ( ! pxenv_udp_read ) {
+		DBG ( "PXE discarded UDP packet\n" );
+		rc = -ENOBUFS;
+		goto done;
+	}
+
+	/* Copy packet to buffer and record length */
+	buffer = real_to_user ( pxenv_udp_read->buffer.segment,
+				pxenv_udp_read->buffer.offset );
+	len = iob_len ( iobuf );
+	if ( len > pxenv_udp_read->buffer_size )
+		len = pxenv_udp_read->buffer_size;
+	copy_to_user ( buffer, 0, iobuf->data, len );
+	pxenv_udp_read->buffer_size = len;
+
+	/* Fill in source/dest information */
+	assert ( meta );
+	sin_src = ( struct sockaddr_in * ) meta->src;
+	assert ( sin_src );
+	assert ( sin_src->sin_family == AF_INET );
+	pxenv_udp_read->src_ip = sin_src->sin_addr.s_addr;
+	pxenv_udp_read->s_port = sin_src->sin_port;
+	sin_dest = ( struct sockaddr_in * ) meta->dest;
+	assert ( sin_dest );
+	assert ( sin_dest->sin_family == AF_INET );
+	pxenv_udp_read->dest_ip = sin_dest->sin_addr.s_addr;
+	pxenv_udp_read->d_port = sin_dest->sin_port;
+
+	/* Mark as received */
+	pxe_udp->pxenv_udp_read = NULL;
+
+ done:
+	free_iob ( iobuf );
+	return rc;
+}
+
+/** PXE UDP data transfer interface operations */
+static struct xfer_interface_operations pxe_udp_xfer_operations = {
+	.close		= ignore_xfer_close,
+	.vredirect	= ignore_xfer_vredirect,
+	.window		= unlimited_xfer_window,
+	.alloc_iob	= default_xfer_alloc_iob,
+	.deliver_iob	= pxe_udp_deliver_iob,
+	.deliver_raw	= xfer_deliver_as_iob,
+};
+
+/** The PXE UDP connection */
+static struct pxe_udp_connection pxe_udp = {
+	.xfer = XFER_INIT ( &pxe_udp_xfer_operations ),
+	.local = {
+		.sin_family = AF_INET,
+	},
+};
+
+/**
+ * UDP OPEN
+ *
+ * @v pxenv_udp_open			Pointer to a struct s_PXENV_UDP_OPEN
+ * @v s_PXENV_UDP_OPEN::src_ip		IP address of this station, or 0.0.0.0
+ * @ret #PXENV_EXIT_SUCCESS		Always
+ * @ret s_PXENV_UDP_OPEN::Status	PXE status code
+ * @err #PXENV_STATUS_UDP_OPEN		UDP connection already open
+ * @err #PXENV_STATUS_OUT_OF_RESOURCES	Could not open connection
+ *
+ * Prepares the PXE stack for communication using pxenv_udp_write()
+ * and pxenv_udp_read().
+ *
+ * The IP address supplied in s_PXENV_UDP_OPEN::src_ip will be
+ * recorded and used as the local station's IP address for all further
+ * communication, including communication by means other than
+ * pxenv_udp_write() and pxenv_udp_read().  (If
+ * s_PXENV_UDP_OPEN::src_ip is 0.0.0.0, the local station's IP address
+ * will remain unchanged.)
+ *
+ * You can only have one open UDP connection at a time.  This is not a
+ * meaningful restriction, since pxenv_udp_write() and
+ * pxenv_udp_read() allow you to specify arbitrary local and remote
+ * ports and an arbitrary remote address for each packet.  According
+ * to the PXE specifiation, you cannot have a UDP connection open at
+ * the same time as a TFTP connection; this restriction does not apply
+ * to Etherboot.
+ *
+ * On x86, you must set the s_PXE::StatusCallout field to a nonzero
+ * value before calling this function in protected mode.  You cannot
+ * call this function with a 32-bit stack segment.  (See the relevant
+ * @ref pxe_x86_pmode16 "implementation note" for more details.)
+ *
+ * @note The PXE specification does not make it clear whether the IP
+ * address supplied in s_PXENV_UDP_OPEN::src_ip should be used only
+ * for this UDP connection, or retained for all future communication.
+ * The latter seems more consistent with typical PXE stack behaviour.
+ *
+ * @note Etherboot currently ignores the s_PXENV_UDP_OPEN::src_ip
+ * parameter.
+ *
+ */
+PXENV_EXIT_t pxenv_udp_open ( struct s_PXENV_UDP_OPEN *pxenv_udp_open ) {
+	int rc;
+
+	DBG ( "PXENV_UDP_OPEN" );
+
+	/* Record source IP address */
+	pxe_udp.local.sin_addr.s_addr = pxenv_udp_open->src_ip;
+	DBG ( " %s", inet_ntoa ( pxe_udp.local.sin_addr ) );
+
+	/* Open promiscuous UDP connection */
+	xfer_close ( &pxe_udp.xfer, 0 );
+	if ( ( rc = udp_open_promisc ( &pxe_udp.xfer ) ) != 0 ) {
+		pxenv_udp_open->Status = PXENV_STATUS ( rc );
+		return PXENV_EXIT_FAILURE;
+	}
+
+	pxenv_udp_open->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/**
+ * UDP CLOSE
+ *
+ * @v pxenv_udp_close			Pointer to a struct s_PXENV_UDP_CLOSE
+ * @ret #PXENV_EXIT_SUCCESS		Always
+ * @ret s_PXENV_UDP_CLOSE::Status	PXE status code
+ * @err None				-
+ *
+ * Closes a UDP connection opened with pxenv_udp_open().
+ *
+ * You can only have one open UDP connection at a time.  You cannot
+ * have a UDP connection open at the same time as a TFTP connection.
+ * You cannot use pxenv_udp_close() to close a TFTP connection; use
+ * pxenv_tftp_close() instead.
+ *
+ * On x86, you must set the s_PXE::StatusCallout field to a nonzero
+ * value before calling this function in protected mode.  You cannot
+ * call this function with a 32-bit stack segment.  (See the relevant
+ * @ref pxe_x86_pmode16 "implementation note" for more details.)
+ *
+ */
+PXENV_EXIT_t pxenv_udp_close ( struct s_PXENV_UDP_CLOSE *pxenv_udp_close ) {
+	DBG ( "PXENV_UDP_CLOSE" );
+
+	/* Close UDP connection */
+	xfer_close ( &pxe_udp.xfer, 0 );
+
+	pxenv_udp_close->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/**
+ * UDP WRITE
+ *
+ * @v pxenv_udp_write			Pointer to a struct s_PXENV_UDP_WRITE
+ * @v s_PXENV_UDP_WRITE::ip		Destination IP address
+ * @v s_PXENV_UDP_WRITE::gw		Relay agent IP address, or 0.0.0.0
+ * @v s_PXENV_UDP_WRITE::src_port	Source UDP port, or 0
+ * @v s_PXENV_UDP_WRITE::dst_port	Destination UDP port
+ * @v s_PXENV_UDP_WRITE::buffer_size	Length of the UDP payload
+ * @v s_PXENV_UDP_WRITE::buffer		Address of the UDP payload
+ * @ret #PXENV_EXIT_SUCCESS		Packet was transmitted successfully
+ * @ret #PXENV_EXIT_FAILURE		Packet could not be transmitted
+ * @ret s_PXENV_UDP_WRITE::Status	PXE status code
+ * @err #PXENV_STATUS_UDP_CLOSED	UDP connection is not open
+ * @err #PXENV_STATUS_UNDI_TRANSMIT_ERROR Could not transmit packet
+ *
+ * Transmits a single UDP packet.  A valid IP and UDP header will be
+ * prepended to the payload in s_PXENV_UDP_WRITE::buffer; the buffer
+ * should not contain precomputed IP and UDP headers, nor should it
+ * contain space allocated for these headers.  The first byte of the
+ * buffer will be transmitted as the first byte following the UDP
+ * header.
+ *
+ * If s_PXENV_UDP_WRITE::gw is 0.0.0.0, normal IP routing will take
+ * place.  See the relevant @ref pxe_routing "implementation note" for
+ * more details.
+ *
+ * If s_PXENV_UDP_WRITE::src_port is 0, port 2069 will be used.
+ *
+ * You must have opened a UDP connection with pxenv_udp_open() before
+ * calling pxenv_udp_write().
+ *
+ * On x86, you must set the s_PXE::StatusCallout field to a nonzero
+ * value before calling this function in protected mode.  You cannot
+ * call this function with a 32-bit stack segment.  (See the relevant
+ * @ref pxe_x86_pmode16 "implementation note" for more details.)
+ *
+ * @note Etherboot currently ignores the s_PXENV_UDP_WRITE::gw
+ * parameter.
+ *
+ */
+PXENV_EXIT_t pxenv_udp_write ( struct s_PXENV_UDP_WRITE *pxenv_udp_write ) {
+	struct sockaddr_in dest;
+	struct xfer_metadata meta = {
+		.src = ( struct sockaddr * ) &pxe_udp.local,
+		.dest = ( struct sockaddr * ) &dest,
+		.netdev = pxe_netdev,
+	};
+	size_t len;
+	struct io_buffer *iobuf;
+	userptr_t buffer;
+	int rc;
+
+	DBG ( "PXENV_UDP_WRITE" );
+
+	/* Construct destination socket address */
+	memset ( &dest, 0, sizeof ( dest ) );
+	dest.sin_family = AF_INET;
+	dest.sin_addr.s_addr = pxenv_udp_write->ip;
+	dest.sin_port = pxenv_udp_write->dst_port;
+
+	/* Set local (source) port.  PXE spec says source port is 2069
+	 * if not specified.  Really, this ought to be set at UDP open
+	 * time but hey, we didn't design this API.
+	 */
+	pxe_udp.local.sin_port = pxenv_udp_write->src_port;
+	if ( ! pxe_udp.local.sin_port )
+		pxe_udp.local.sin_port = htons ( 2069 );
+
+	/* FIXME: we ignore the gateway specified, since we're
+	 * confident of being able to do our own routing.  We should
+	 * probably allow for multiple gateways.
+	 */
+
+	/* Allocate and fill data buffer */
+	len = pxenv_udp_write->buffer_size;
+	iobuf = xfer_alloc_iob ( &pxe_udp.xfer, len );
+	if ( ! iobuf ) {
+		pxenv_udp_write->Status = PXENV_STATUS_OUT_OF_RESOURCES;
+		return PXENV_EXIT_FAILURE;
+	}
+	buffer = real_to_user ( pxenv_udp_write->buffer.segment,
+				pxenv_udp_write->buffer.offset );
+	copy_from_user ( iob_put ( iobuf, len ), buffer, 0, len );
+
+	DBG ( " %04x:%04x+%x %d->%s:%d", pxenv_udp_write->buffer.segment,
+	      pxenv_udp_write->buffer.offset, pxenv_udp_write->buffer_size,
+	      ntohs ( pxenv_udp_write->src_port ),
+	      inet_ntoa ( dest.sin_addr ),
+	      ntohs ( pxenv_udp_write->dst_port ) );
+	
+	/* Transmit packet */
+	if ( ( rc = xfer_deliver_iob_meta ( &pxe_udp.xfer, iobuf,
+					    &meta ) ) != 0 ) {
+		pxenv_udp_write->Status = PXENV_STATUS ( rc );
+		return PXENV_EXIT_FAILURE;
+	}
+
+	pxenv_udp_write->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/**
+ * UDP READ
+ *
+ * @v pxenv_udp_read			Pointer to a struct s_PXENV_UDP_READ
+ * @v s_PXENV_UDP_READ::dest_ip		Destination IP address, or 0.0.0.0
+ * @v s_PXENV_UDP_READ::d_port		Destination UDP port, or 0
+ * @v s_PXENV_UDP_READ::buffer_size	Size of the UDP payload buffer
+ * @v s_PXENV_UDP_READ::buffer		Address of the UDP payload buffer
+ * @ret #PXENV_EXIT_SUCCESS		A packet has been received
+ * @ret #PXENV_EXIT_FAILURE		No packet has been received
+ * @ret s_PXENV_UDP_READ::Status	PXE status code
+ * @ret s_PXENV_UDP_READ::src_ip	Source IP address
+ * @ret s_PXENV_UDP_READ::dest_ip	Destination IP address
+ * @ret s_PXENV_UDP_READ::s_port	Source UDP port
+ * @ret s_PXENV_UDP_READ::d_port	Destination UDP port
+ * @ret s_PXENV_UDP_READ::buffer_size	Length of UDP payload
+ * @err #PXENV_STATUS_UDP_CLOSED	UDP connection is not open
+ * @err #PXENV_STATUS_FAILURE		No packet was ready to read
+ *
+ * Receive a single UDP packet.  This is a non-blocking call; if no
+ * packet is ready to read, the call will return instantly with
+ * s_PXENV_UDP_READ::Status==PXENV_STATUS_FAILURE.
+ *
+ * If s_PXENV_UDP_READ::dest_ip is 0.0.0.0, UDP packets addressed to
+ * any IP address will be accepted and may be returned to the caller.
+ *
+ * If s_PXENV_UDP_READ::d_port is 0, UDP packets addressed to any UDP
+ * port will be accepted and may be returned to the caller.
+ *
+ * You must have opened a UDP connection with pxenv_udp_open() before
+ * calling pxenv_udp_read().
+ *
+ * On x86, you must set the s_PXE::StatusCallout field to a nonzero
+ * value before calling this function in protected mode.  You cannot
+ * call this function with a 32-bit stack segment.  (See the relevant
+ * @ref pxe_x86_pmode16 "implementation note" for more details.)
+ *
+ * @note The PXE specification (version 2.1) does not state that we
+ * should fill in s_PXENV_UDP_READ::dest_ip and
+ * s_PXENV_UDP_READ::d_port, but Microsoft Windows' NTLDR program
+ * expects us to do so, and will fail if we don't.
+ *
+ */
+PXENV_EXIT_t pxenv_udp_read ( struct s_PXENV_UDP_READ *pxenv_udp_read ) {
+	struct in_addr dest_ip_wanted = { .s_addr = pxenv_udp_read->dest_ip };
+	struct in_addr dest_ip;
+	uint16_t d_port_wanted = pxenv_udp_read->d_port;
+	uint16_t d_port;
+
+	DBG ( "PXENV_UDP_READ" );
+
+	/* Try receiving a packet */
+	pxe_udp.pxenv_udp_read = pxenv_udp_read;
+	step();
+	if ( pxe_udp.pxenv_udp_read ) {
+		/* No packet received */
+		pxe_udp.pxenv_udp_read = NULL;
+		goto no_packet;
+	}
+	dest_ip.s_addr = pxenv_udp_read->dest_ip;
+	d_port = pxenv_udp_read->d_port;
+
+	/* Filter on destination address and/or port */
+	if ( dest_ip_wanted.s_addr &&
+	     ( dest_ip_wanted.s_addr != dest_ip.s_addr ) ) {
+		DBG ( " wrong IP %s", inet_ntoa ( dest_ip ) );
+		DBG ( " (wanted %s)", inet_ntoa ( dest_ip_wanted ) );
+		goto no_packet;
+	}
+	if ( d_port_wanted && ( d_port_wanted != d_port ) ) {
+		DBG ( " wrong port %d ", htons ( d_port ) );
+		DBG ( " (wanted %d)", htons ( d_port_wanted ) );
+		goto no_packet;
+	}
+
+	DBG ( " %04x:%04x+%x %s:", pxenv_udp_read->buffer.segment,
+	      pxenv_udp_read->buffer.offset, pxenv_udp_read->buffer_size,
+	      inet_ntoa ( *( ( struct in_addr * ) &pxenv_udp_read->src_ip ) ));
+	DBG ( "%d<-%s:%d",  ntohs ( pxenv_udp_read->s_port ),
+	      inet_ntoa ( *( ( struct in_addr * ) &pxenv_udp_read->dest_ip ) ),
+	      ntohs ( pxenv_udp_read->d_port ) );
+
+	pxenv_udp_read->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+
+ no_packet:
+	pxenv_udp_read->Status = PXENV_STATUS_FAILURE;
+	return PXENV_EXIT_FAILURE;
+}
diff --git a/gpxe/src/arch/i386/interface/pxe/pxe_undi.c b/gpxe/src/arch/i386/interface/pxe/pxe_undi.c
new file mode 100644
index 0000000..c9b67c0
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pxe/pxe_undi.c
@@ -0,0 +1,791 @@
+/** @file
+ *
+ * PXE UNDI API
+ *
+ */
+
+/*
+ * Copyright (C) 2004 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <byteswap.h>
+#include <basemem_packet.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/iobuf.h>
+#include <gpxe/device.h>
+#include <gpxe/pci.h>
+#include <gpxe/if_ether.h>
+#include <gpxe/ip.h>
+#include <gpxe/arp.h>
+#include <gpxe/rarp.h>
+#include "pxe.h"
+
+/**
+ * Count of outstanding transmitted packets
+ *
+ * This is incremented each time PXENV_UNDI_TRANSMIT is called, and
+ * decremented each time that PXENV_UNDI_ISR is called with the TX
+ * queue empty, stopping when the count reaches zero.  This allows us
+ * to provide a pessimistic approximation of TX completion events to
+ * the PXE NBP simply by monitoring the netdev's TX queue.
+ */
+static int undi_tx_count = 0;
+
+struct net_device *pxe_netdev = NULL;
+
+/**
+ * Set network device as current PXE network device
+ *
+ * @v netdev		Network device, or NULL
+ */
+void pxe_set_netdev ( struct net_device *netdev ) {
+	if ( pxe_netdev )
+		netdev_put ( pxe_netdev );
+	pxe_netdev = NULL;
+	if ( netdev )
+		pxe_netdev = netdev_get ( netdev );
+}
+
+/**
+ * Open PXE network device
+ *
+ * @ret rc		Return status code
+ */
+static int pxe_netdev_open ( void ) {
+	int rc;
+
+	if ( ( rc = netdev_open ( pxe_netdev ) ) != 0 )
+		return rc;
+
+	netdev_irq ( pxe_netdev, 1 );
+	return 0;
+}
+
+/**
+ * Close PXE network device
+ *
+ */
+static void pxe_netdev_close ( void ) {
+	netdev_irq ( pxe_netdev, 0 );
+	netdev_close ( pxe_netdev );
+	undi_tx_count = 0;
+}
+
+/**
+ * Dump multicast address list
+ *
+ * @v mcast		PXE multicast address list
+ */
+static void pxe_dump_mcast_list ( struct s_PXENV_UNDI_MCAST_ADDRESS *mcast ) {
+	struct ll_protocol *ll_protocol = pxe_netdev->ll_protocol;
+	unsigned int i;
+
+	for ( i = 0 ; i < mcast->MCastAddrCount ; i++ ) {
+		DBG ( " %s", ll_protocol->ntoa ( mcast->McastAddr[i] ) );
+	}
+}
+
+/* PXENV_UNDI_STARTUP
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_startup ( struct s_PXENV_UNDI_STARTUP *undi_startup ) {
+	DBG ( "PXENV_UNDI_STARTUP\n" );
+
+	undi_startup->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_CLEANUP
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_cleanup ( struct s_PXENV_UNDI_CLEANUP *undi_cleanup ) {
+	DBG ( "PXENV_UNDI_CLEANUP\n" );
+
+	pxe_netdev_close();
+
+	undi_cleanup->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_INITIALIZE
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_initialize ( struct s_PXENV_UNDI_INITIALIZE
+				     *undi_initialize ) {
+	DBG ( "PXENV_UNDI_INITIALIZE protocolini %08x\n",
+	      undi_initialize->ProtocolIni );
+
+	undi_initialize->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_RESET_ADAPTER
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_reset_adapter ( struct s_PXENV_UNDI_RESET
+					*undi_reset_adapter ) {
+	int rc;
+
+	DBG ( "PXENV_UNDI_RESET_ADAPTER" );
+	pxe_dump_mcast_list ( &undi_reset_adapter->R_Mcast_Buf );
+	DBG ( "\n" );
+
+	pxe_netdev_close();
+	if ( ( rc = pxe_netdev_open() ) != 0 ) {
+		DBG ( "PXENV_UNDI_RESET_ADAPTER could not reopen %s: %s\n",
+		      pxe_netdev->name, strerror ( rc ) );
+		undi_reset_adapter->Status = PXENV_STATUS ( rc );
+		return PXENV_EXIT_FAILURE;
+	}
+
+	undi_reset_adapter->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_SHUTDOWN
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_shutdown ( struct s_PXENV_UNDI_SHUTDOWN
+				   *undi_shutdown ) {
+	DBG ( "PXENV_UNDI_SHUTDOWN\n" );
+
+	pxe_netdev_close();
+
+	undi_shutdown->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_OPEN
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_open ( struct s_PXENV_UNDI_OPEN *undi_open ) {
+	int rc;
+
+	DBG ( "PXENV_UNDI_OPEN flag %04x filter %04x",
+	      undi_open->OpenFlag, undi_open->PktFilter );
+	pxe_dump_mcast_list ( &undi_open->R_Mcast_Buf );
+	DBG ( "\n" );
+
+	if ( ( rc = pxe_netdev_open() ) != 0 ) {
+		DBG ( "PXENV_UNDI_OPEN could not open %s: %s\n",
+		      pxe_netdev->name, strerror ( rc ) );
+		undi_open->Status = PXENV_STATUS ( rc );
+		return PXENV_EXIT_FAILURE;
+	}
+
+	undi_open->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_CLOSE
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_close ( struct s_PXENV_UNDI_CLOSE *undi_close ) {
+	DBG ( "PXENV_UNDI_CLOSE\n" );
+
+	pxe_netdev_close();
+
+	undi_close->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_TRANSMIT
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_transmit ( struct s_PXENV_UNDI_TRANSMIT
+				   *undi_transmit ) {
+	struct s_PXENV_UNDI_TBD tbd;
+	struct DataBlk *datablk;
+	struct io_buffer *iobuf;
+	struct net_protocol *net_protocol;
+	struct ll_protocol *ll_protocol = pxe_netdev->ll_protocol;
+	char destaddr[MAX_LL_ADDR_LEN];
+	const void *ll_dest;
+	size_t ll_hlen = ll_protocol->ll_header_len;
+	size_t len;
+	unsigned int i;
+	int rc;
+
+	DBG2 ( "PXENV_UNDI_TRANSMIT" );
+
+	/* Forcibly enable interrupts at this point, to work around
+	 * callers that never call PXENV_UNDI_OPEN before attempting
+	 * to use the UNDI API.
+	 */
+	netdev_irq ( pxe_netdev, 1 );
+
+	/* Identify network-layer protocol */
+	switch ( undi_transmit->Protocol ) {
+	case P_IP:	net_protocol = &ipv4_protocol;	break;
+	case P_ARP:	net_protocol = &arp_protocol;	break;
+	case P_RARP:	net_protocol = &rarp_protocol;	break;
+	case P_UNKNOWN:
+		net_protocol = NULL;
+		ll_hlen = 0;
+		break;
+	default:
+		DBG2 ( " %02x invalid protocol\n", undi_transmit->Protocol );
+		undi_transmit->Status = PXENV_STATUS_UNDI_INVALID_PARAMETER;
+		return PXENV_EXIT_FAILURE;
+	}
+	DBG2 ( " %s", ( net_protocol ? net_protocol->name : "RAW" ) );
+
+	/* Calculate total packet length */
+	copy_from_real ( &tbd, undi_transmit->TBD.segment,
+			 undi_transmit->TBD.offset, sizeof ( tbd ) );
+	len = tbd.ImmedLength;
+	DBG2 ( " %04x:%04x+%x", tbd.Xmit.segment, tbd.Xmit.offset,
+	       tbd.ImmedLength );
+	for ( i = 0 ; i < tbd.DataBlkCount ; i++ ) {
+		datablk = &tbd.DataBlock[i];
+		len += datablk->TDDataLen;
+		DBG2 ( " %04x:%04x+%x", datablk->TDDataPtr.segment,
+		       datablk->TDDataPtr.offset, datablk->TDDataLen );
+	}
+
+	/* Allocate and fill I/O buffer */
+	iobuf = alloc_iob ( ll_hlen + len );
+	if ( ! iobuf ) {
+		DBG2 ( " could not allocate iobuf\n" );
+		undi_transmit->Status = PXENV_STATUS_OUT_OF_RESOURCES;
+		return PXENV_EXIT_FAILURE;
+	}
+	iob_reserve ( iobuf, ll_hlen );
+	copy_from_real ( iob_put ( iobuf, tbd.ImmedLength ), tbd.Xmit.segment,
+			 tbd.Xmit.offset, tbd.ImmedLength );
+	for ( i = 0 ; i < tbd.DataBlkCount ; i++ ) {
+		datablk = &tbd.DataBlock[i];
+		copy_from_real ( iob_put ( iobuf, datablk->TDDataLen ),
+				 datablk->TDDataPtr.segment,
+				 datablk->TDDataPtr.offset,
+				 datablk->TDDataLen );
+	}
+
+	/* Add link-layer header, if required to do so */
+	if ( net_protocol != NULL ) {
+
+		/* Calculate destination address */
+		if ( undi_transmit->XmitFlag == XMT_DESTADDR ) {
+			copy_from_real ( destaddr,
+					 undi_transmit->DestAddr.segment,
+					 undi_transmit->DestAddr.offset,
+					 ll_protocol->ll_addr_len );
+			ll_dest = destaddr;
+			DBG2 ( " DEST %s", ll_protocol->ntoa ( ll_dest ) );
+		} else {
+			ll_dest = pxe_netdev->ll_broadcast;
+			DBG2 ( " BCAST" );
+		}
+
+		/* Add link-layer header */
+		if ( ( rc = ll_protocol->push ( pxe_netdev, iobuf, ll_dest,
+						pxe_netdev->ll_addr,
+						net_protocol->net_proto ))!=0){
+			DBG2 ( " could not add link-layer header: %s\n",
+			       strerror ( rc ) );
+			free_iob ( iobuf );
+			undi_transmit->Status = PXENV_STATUS ( rc );
+			return PXENV_EXIT_FAILURE;
+		}
+	}
+
+	/* Flag transmission as in-progress.  Do this before starting
+	 * to transmit the packet, because the ISR may trigger before
+	 * we return from netdev_tx().
+	 */
+	undi_tx_count++;
+
+	/* Transmit packet */
+	DBG2 ( "\n" );
+	if ( ( rc = netdev_tx ( pxe_netdev, iobuf ) ) != 0 ) {
+		DBG2 ( "PXENV_UNDI_TRANSMIT could not transmit: %s\n",
+		       strerror ( rc ) );
+		undi_tx_count--;
+		undi_transmit->Status = PXENV_STATUS ( rc );
+		return PXENV_EXIT_FAILURE;
+	}
+
+	undi_transmit->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_SET_MCAST_ADDRESS
+ *
+ * Status: working (for NICs that support receive-all-multicast)
+ */
+PXENV_EXIT_t
+pxenv_undi_set_mcast_address ( struct s_PXENV_UNDI_SET_MCAST_ADDRESS
+			       *undi_set_mcast_address ) {
+	DBG ( "PXENV_UNDI_SET_MCAST_ADDRESS" );
+	pxe_dump_mcast_list ( &undi_set_mcast_address->R_Mcast_Buf );
+	DBG ( "\n" );
+
+	undi_set_mcast_address->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_SET_STATION_ADDRESS
+ *
+ * Status: working
+ */
+PXENV_EXIT_t 
+pxenv_undi_set_station_address ( struct s_PXENV_UNDI_SET_STATION_ADDRESS
+				 *undi_set_station_address ) {
+	struct ll_protocol *ll_protocol = pxe_netdev->ll_protocol;
+
+	DBG ( "PXENV_UNDI_SET_STATION_ADDRESS %s",
+	      ll_protocol->ntoa ( undi_set_station_address->StationAddress ) );
+
+	/* If adapter is open, the change will have no effect; return
+	 * an error
+	 */
+	if ( pxe_netdev->state & NETDEV_OPEN ) {
+		DBG ( " failed: netdev is open\n" );
+		undi_set_station_address->Status =
+			PXENV_STATUS_UNDI_INVALID_STATE;
+		return PXENV_EXIT_FAILURE;
+	}
+
+	/* Update MAC address */
+	memcpy ( pxe_netdev->ll_addr,
+		 &undi_set_station_address->StationAddress,
+		 ll_protocol->ll_addr_len );
+
+	DBG ( "\n" );
+	undi_set_station_address->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_SET_PACKET_FILTER
+ *
+ * Status: won't implement (would require driver API changes for no
+ * real benefit)
+ */
+PXENV_EXIT_t
+pxenv_undi_set_packet_filter ( struct s_PXENV_UNDI_SET_PACKET_FILTER
+			       *undi_set_packet_filter ) {
+
+	DBG ( "PXENV_UNDI_SET_PACKET_FILTER %02x\n",
+	      undi_set_packet_filter->filter );
+
+	/* Pretend that we succeeded, otherwise the 3Com DOS UNDI
+	 * driver refuses to load.  (We ignore the filter value in the
+	 * PXENV_UNDI_OPEN call anyway.)
+	 */
+	undi_set_packet_filter->Status = PXENV_STATUS_SUCCESS;
+
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_GET_INFORMATION
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_get_information ( struct s_PXENV_UNDI_GET_INFORMATION
+					  *undi_get_information ) {
+	struct device *dev = pxe_netdev->dev;
+	struct ll_protocol *ll_protocol = pxe_netdev->ll_protocol;
+	size_t ll_addr_len = ll_protocol->ll_addr_len;
+
+	DBG ( "PXENV_UNDI_GET_INFORMATION" );
+
+	undi_get_information->BaseIo = dev->desc.ioaddr;
+	undi_get_information->IntNumber = dev->desc.irq;
+	/* Cheat: assume all cards can cope with this */
+	undi_get_information->MaxTranUnit = ETH_MAX_MTU;
+	undi_get_information->HwType = ntohs ( ll_protocol->ll_proto );
+	undi_get_information->HwAddrLen = ll_addr_len;
+	assert ( ll_addr_len <=
+		 sizeof ( undi_get_information->CurrentNodeAddress ) );
+	memcpy ( &undi_get_information->CurrentNodeAddress,
+		 pxe_netdev->ll_addr,
+		 sizeof ( undi_get_information->CurrentNodeAddress ) );
+	ll_protocol->init_addr ( pxe_netdev->hw_addr,
+				 &undi_get_information->PermNodeAddress );
+	undi_get_information->ROMAddress = 0;
+		/* nic.rom_info->rom_segment; */
+	/* We only provide the ability to receive or transmit a single
+	 * packet at a time.  This is a bootloader, not an OS.
+	 */
+	undi_get_information->RxBufCt = 1;
+	undi_get_information->TxBufCt = 1;
+
+	DBG ( " io %04x irq %d mtu %d %s %s\n",
+	      undi_get_information->BaseIo, undi_get_information->IntNumber,
+	      undi_get_information->MaxTranUnit, ll_protocol->name,
+	      ll_protocol->ntoa ( &undi_get_information->CurrentNodeAddress ));
+	undi_get_information->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_GET_STATISTICS
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_get_statistics ( struct s_PXENV_UNDI_GET_STATISTICS
+					 *undi_get_statistics ) {
+	DBG ( "PXENV_UNDI_GET_STATISTICS" );
+
+	undi_get_statistics->XmtGoodFrames = pxe_netdev->tx_stats.good;
+	undi_get_statistics->RcvGoodFrames = pxe_netdev->rx_stats.good;
+	undi_get_statistics->RcvCRCErrors = pxe_netdev->rx_stats.bad;
+	undi_get_statistics->RcvResourceErrors = pxe_netdev->rx_stats.bad;
+
+	DBG ( " txok %d rxok %d rxcrc %d rxrsrc %d\n",
+	      undi_get_statistics->XmtGoodFrames,
+	      undi_get_statistics->RcvGoodFrames,
+	      undi_get_statistics->RcvCRCErrors,
+	      undi_get_statistics->RcvResourceErrors );
+	undi_get_statistics->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_CLEAR_STATISTICS
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_clear_statistics ( struct s_PXENV_UNDI_CLEAR_STATISTICS
+					   *undi_clear_statistics ) {
+	DBG ( "PXENV_UNDI_CLEAR_STATISTICS\n" );
+
+	memset ( &pxe_netdev->tx_stats, 0, sizeof ( pxe_netdev->tx_stats ) );
+	memset ( &pxe_netdev->rx_stats, 0, sizeof ( pxe_netdev->rx_stats ) );
+
+	undi_clear_statistics->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_INITIATE_DIAGS
+ *
+ * Status: won't implement (would require driver API changes for no
+ * real benefit)
+ */
+PXENV_EXIT_t pxenv_undi_initiate_diags ( struct s_PXENV_UNDI_INITIATE_DIAGS
+					 *undi_initiate_diags ) {
+	DBG ( "PXENV_UNDI_INITIATE_DIAGS failed: unsupported\n" );
+
+	undi_initiate_diags->Status = PXENV_STATUS_UNSUPPORTED;
+	return PXENV_EXIT_FAILURE;
+}
+
+/* PXENV_UNDI_FORCE_INTERRUPT
+ *
+ * Status: won't implement (would require driver API changes for no
+ * perceptible benefit)
+ */
+PXENV_EXIT_t pxenv_undi_force_interrupt ( struct s_PXENV_UNDI_FORCE_INTERRUPT
+					  *undi_force_interrupt ) {
+	DBG ( "PXENV_UNDI_FORCE_INTERRUPT failed: unsupported\n" );
+
+	undi_force_interrupt->Status = PXENV_STATUS_UNSUPPORTED;
+	return PXENV_EXIT_FAILURE;
+}
+
+/* PXENV_UNDI_GET_MCAST_ADDRESS
+ *
+ * Status: working
+ */
+PXENV_EXIT_t
+pxenv_undi_get_mcast_address ( struct s_PXENV_UNDI_GET_MCAST_ADDRESS
+			       *undi_get_mcast_address ) {
+	struct ll_protocol *ll_protocol = pxe_netdev->ll_protocol;
+	struct in_addr ip = { .s_addr = undi_get_mcast_address->InetAddr };
+	int rc;
+
+	DBG ( "PXENV_UNDI_GET_MCAST_ADDRESS %s", inet_ntoa ( ip ) );
+
+	if ( ( rc = ll_protocol->mc_hash ( AF_INET, &ip,
+				      undi_get_mcast_address->MediaAddr ))!=0){
+		DBG ( " failed: %s\n", strerror ( rc ) );
+		undi_get_mcast_address->Status = PXENV_STATUS ( rc );
+		return PXENV_EXIT_FAILURE;
+	}
+	DBG ( "=>%s\n",
+	      ll_protocol->ntoa ( undi_get_mcast_address->MediaAddr ) );
+
+	undi_get_mcast_address->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_GET_NIC_TYPE
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_get_nic_type ( struct s_PXENV_UNDI_GET_NIC_TYPE
+				       *undi_get_nic_type ) {
+	struct device *dev = pxe_netdev->dev;
+
+	DBG ( "PXENV_UNDI_GET_NIC_TYPE" );
+
+	memset ( &undi_get_nic_type->info, 0,
+		 sizeof ( undi_get_nic_type->info ) );
+
+	switch ( dev->desc.bus_type ) {
+	case BUS_TYPE_PCI: {
+		struct pci_nic_info *info = &undi_get_nic_type->info.pci;
+
+		undi_get_nic_type->NicType = PCI_NIC;
+		info->Vendor_ID = dev->desc.vendor;
+		info->Dev_ID = dev->desc.device;
+		info->Base_Class = PCI_BASE_CLASS ( dev->desc.class );
+		info->Sub_Class = PCI_SUB_CLASS ( dev->desc.class );
+		info->Prog_Intf = PCI_PROG_INTF ( dev->desc.class );
+		info->BusDevFunc = dev->desc.location;
+		/* Cheat: remaining fields are probably unnecessary,
+		 * and would require adding extra code to pci.c.
+		 */
+		undi_get_nic_type->info.pci.SubVendor_ID = 0xffff;
+		undi_get_nic_type->info.pci.SubDevice_ID = 0xffff;
+		DBG ( " PCI %02x:%02x.%x %04x:%04x (%04x:%04x) %02x%02x%02x "
+		      "rev %02x\n", PCI_BUS ( info->BusDevFunc ),
+		      PCI_SLOT ( info->BusDevFunc ),
+		      PCI_FUNC ( info->BusDevFunc ), info->Vendor_ID,
+		      info->Dev_ID, info->SubVendor_ID, info->SubDevice_ID,
+		      info->Base_Class, info->Sub_Class, info->Prog_Intf,
+		      info->Rev );
+		break; }
+	case BUS_TYPE_ISAPNP: {
+		struct pnp_nic_info *info = &undi_get_nic_type->info.pnp;
+
+		undi_get_nic_type->NicType = PnP_NIC;
+		info->EISA_Dev_ID = ( ( dev->desc.vendor << 16 ) |
+				      dev->desc.device );
+		info->CardSelNum = dev->desc.location;
+		/* Cheat: remaining fields are probably unnecessary,
+		 * and would require adding extra code to isapnp.c.
+		 */
+		DBG ( " ISAPnP CSN %04x %08x %02x%02x%02x\n",
+		      info->CardSelNum, info->EISA_Dev_ID,
+		      info->Base_Class, info->Sub_Class, info->Prog_Intf );
+		break; }
+	default:
+		DBG ( " failed: unknown bus type\n" );
+		undi_get_nic_type->Status = PXENV_STATUS_FAILURE;
+		return PXENV_EXIT_FAILURE;
+	}
+
+	undi_get_nic_type->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_GET_IFACE_INFO
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_get_iface_info ( struct s_PXENV_UNDI_GET_IFACE_INFO
+					 *undi_get_iface_info ) {
+	DBG ( "PXENV_UNDI_GET_IFACE_INFO" );
+
+	/* Just hand back some info, doesn't really matter what it is.
+	 * Most PXE stacks seem to take this approach.
+	 */
+	snprintf ( ( char * ) undi_get_iface_info->IfaceType,
+		   sizeof ( undi_get_iface_info->IfaceType ), "DIX+802.3" );
+	undi_get_iface_info->LinkSpeed = 10000000; /* 10 Mbps */
+	undi_get_iface_info->ServiceFlags =
+		( SUPPORTED_BROADCAST | SUPPORTED_MULTICAST |
+		  SUPPORTED_SET_STATION_ADDRESS | SUPPORTED_RESET |
+		  SUPPORTED_OPEN_CLOSE | SUPPORTED_IRQ );
+	memset ( undi_get_iface_info->Reserved, 0,
+		 sizeof(undi_get_iface_info->Reserved) );
+
+	DBG ( " %s %dbps flags %08x\n", undi_get_iface_info->IfaceType,
+	      undi_get_iface_info->LinkSpeed,
+	      undi_get_iface_info->ServiceFlags );
+	undi_get_iface_info->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
+
+/* PXENV_UNDI_GET_STATE
+ *
+ * Status: impossible
+ */
+PXENV_EXIT_t pxenv_undi_get_state ( struct s_PXENV_UNDI_GET_STATE
+				    *undi_get_state ) {
+	DBG ( "PXENV_UNDI_GET_STATE failed: unsupported\n" );
+
+	undi_get_state->Status = PXENV_STATUS_UNSUPPORTED;
+	return PXENV_EXIT_FAILURE;
+};
+
+/* PXENV_UNDI_ISR
+ *
+ * Status: working
+ */
+PXENV_EXIT_t pxenv_undi_isr ( struct s_PXENV_UNDI_ISR *undi_isr ) {
+	struct io_buffer *iobuf;
+	size_t len;
+	struct ll_protocol *ll_protocol;
+	const void *ll_dest;
+	const void *ll_source;
+	uint16_t net_proto;
+	size_t ll_hlen;
+	struct net_protocol *net_protocol;
+	unsigned int prottype;
+	int rc;
+
+	/* Use coloured debug, since UNDI ISR messages are likely to
+	 * be interspersed amongst other UNDI messages.
+	 */
+	DBGC2 ( &pxenv_undi_isr, "PXENV_UNDI_ISR" );
+
+	/* Just in case some idiot actually looks at these fields when
+	 * we weren't meant to fill them in...
+	 */
+	undi_isr->BufferLength = 0;
+	undi_isr->FrameLength = 0;
+	undi_isr->FrameHeaderLength = 0;
+	undi_isr->ProtType = 0;
+	undi_isr->PktType = 0;
+
+	switch ( undi_isr->FuncFlag ) {
+	case PXENV_UNDI_ISR_IN_START :
+		DBGC2 ( &pxenv_undi_isr, " START" );
+
+		/* Call poll().  This should acknowledge the device
+		 * interrupt and queue up any received packet.
+		 */
+		netdev_poll ( pxe_netdev );
+
+		/* Disable interrupts to avoid interrupt storm */
+		netdev_irq ( pxe_netdev, 0 );
+
+		/* Always say it was ours for the sake of simplicity */
+		DBGC2 ( &pxenv_undi_isr, " OURS" );
+		undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_OURS;
+		break;
+	case PXENV_UNDI_ISR_IN_PROCESS :
+	case PXENV_UNDI_ISR_IN_GET_NEXT :
+		DBGC2 ( &pxenv_undi_isr, " %s",
+			( ( undi_isr->FuncFlag == PXENV_UNDI_ISR_IN_PROCESS ) ?
+			  "PROCESS" : "GET_NEXT" ) );
+
+		/* Some dumb NBPs (e.g. emBoot's winBoot/i) never call
+		 * PXENV_UNDI_ISR with FuncFlag=PXENV_UNDI_ISR_START;
+		 * they just sit in a tight polling loop merrily
+		 * violating the PXE spec with repeated calls to
+		 * PXENV_UNDI_ISR_IN_PROCESS.  Force extra polls to
+		 * cope with these out-of-spec clients.
+		 */
+		netdev_poll ( pxe_netdev );
+
+		/* If we have not yet marked a TX as complete, and the
+		 * netdev TX queue is empty, report the TX completion.
+		 */
+		if ( undi_tx_count && list_empty ( &pxe_netdev->tx_queue ) ) {
+			DBGC2 ( &pxenv_undi_isr, " TXC" );
+			undi_tx_count--;
+			undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_TRANSMIT;
+			break;
+		}
+
+		/* Remove first packet from netdev RX queue */
+		iobuf = netdev_rx_dequeue ( pxe_netdev );
+		if ( ! iobuf ) {
+			DBGC2 ( &pxenv_undi_isr, " DONE" );
+			/* No more packets remaining */
+			undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_DONE;
+			/* Re-enable interrupts */
+			netdev_irq ( pxe_netdev, 1 );
+			break;
+		}
+
+		/* Copy packet to base memory buffer */
+		len = iob_len ( iobuf );
+		DBGC2 ( &pxenv_undi_isr, " RX" );
+		if ( len > sizeof ( basemem_packet ) ) {
+			/* Should never happen */
+			DBGC2 ( &pxenv_undi_isr, " overlength (%zx)", len );
+			len = sizeof ( basemem_packet );
+		}
+		memcpy ( basemem_packet, iobuf->data, len );
+
+		/* Strip link-layer header */
+		ll_protocol = pxe_netdev->ll_protocol;
+		if ( ( rc = ll_protocol->pull ( pxe_netdev, iobuf, &ll_dest,
+						&ll_source, &net_proto )) !=0){
+			/* Assume unknown net_proto and no ll_source */
+			net_proto = 0;
+			ll_source = NULL;
+		}
+		ll_hlen = ( len - iob_len ( iobuf ) );
+
+		/* Determine network-layer protocol */
+		switch ( net_proto ) {
+		case htons ( ETH_P_IP ):
+			net_protocol = &ipv4_protocol;
+			prottype = P_IP;
+			break;
+		case htons ( ETH_P_ARP ):
+			net_protocol = &arp_protocol;
+			prottype = P_ARP;
+			break;
+		case htons ( ETH_P_RARP ):
+			net_protocol = &rarp_protocol;
+			prottype = P_RARP;
+			break;
+		default:
+			net_protocol = NULL;
+			prottype = P_UNKNOWN;
+			break;
+		}
+
+		/* Fill in UNDI_ISR structure */
+		undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_RECEIVE;
+		undi_isr->BufferLength = len;
+		undi_isr->FrameLength = len;
+		undi_isr->FrameHeaderLength = ll_hlen;
+		undi_isr->Frame.segment = rm_ds;
+		undi_isr->Frame.offset = __from_data16 ( basemem_packet );
+		undi_isr->ProtType = prottype;
+		undi_isr->PktType = XMT_DESTADDR;
+		DBGC2 ( &pxenv_undi_isr, " %04x:%04x+%x(%x) %s hlen %d",
+			undi_isr->Frame.segment, undi_isr->Frame.offset,
+			undi_isr->BufferLength, undi_isr->FrameLength,
+			( net_protocol ? net_protocol->name : "RAW" ),
+			undi_isr->FrameHeaderLength );
+
+		/* Free packet */
+		free_iob ( iobuf );
+		break;
+	default :
+		DBGC2 ( &pxenv_undi_isr, " INVALID(%04x)\n",
+			undi_isr->FuncFlag );
+
+		/* Should never happen */
+		undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_DONE;
+		undi_isr->Status = PXENV_STATUS_UNDI_INVALID_PARAMETER;
+		return PXENV_EXIT_FAILURE;
+	}
+
+	DBGC2 ( &pxenv_undi_isr, "\n" );
+	undi_isr->Status = PXENV_STATUS_SUCCESS;
+	return PXENV_EXIT_SUCCESS;
+}
diff --git a/gpxe/src/arch/i386/interface/pxeparent/pxeparent.c b/gpxe/src/arch/i386/interface/pxeparent/pxeparent.c
new file mode 100644
index 0000000..582db5d
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pxeparent/pxeparent.c
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <gpxe/dhcp.h>
+#include <pxeparent.h>
+#include <pxe_api.h>
+#include <pxe_types.h>
+#include <pxe.h>
+
+/** @file
+ *
+ * Call interface to parent PXE stack
+ *
+ */
+
+/**
+ * Name PXE API call
+ *
+ * @v function		API call number
+ * @ret name		API call name
+ */
+static inline __attribute__ (( always_inline )) const char *
+pxeparent_function_name ( unsigned int function ) {
+	switch ( function ) {
+	case PXENV_START_UNDI:
+		return "PXENV_START_UNDI";
+	case PXENV_STOP_UNDI:
+		return "PXENV_STOP_UNDI";
+	case PXENV_UNDI_STARTUP:
+		return "PXENV_UNDI_STARTUP";
+	case PXENV_UNDI_CLEANUP:
+		return "PXENV_UNDI_CLEANUP";
+	case PXENV_UNDI_INITIALIZE:
+		return "PXENV_UNDI_INITIALIZE";
+	case PXENV_UNDI_RESET_ADAPTER:
+		return "PXENV_UNDI_RESET_ADAPTER";
+	case PXENV_UNDI_SHUTDOWN:
+		return "PXENV_UNDI_SHUTDOWN";
+	case PXENV_UNDI_OPEN:
+		return "PXENV_UNDI_OPEN";
+	case PXENV_UNDI_CLOSE:
+		return "PXENV_UNDI_CLOSE";
+	case PXENV_UNDI_TRANSMIT:
+		return "PXENV_UNDI_TRANSMIT";
+	case PXENV_UNDI_SET_MCAST_ADDRESS:
+		return "PXENV_UNDI_SET_MCAST_ADDRESS";
+	case PXENV_UNDI_SET_STATION_ADDRESS:
+		return "PXENV_UNDI_SET_STATION_ADDRESS";
+	case PXENV_UNDI_SET_PACKET_FILTER:
+		return "PXENV_UNDI_SET_PACKET_FILTER";
+	case PXENV_UNDI_GET_INFORMATION:
+		return "PXENV_UNDI_GET_INFORMATION";
+	case PXENV_UNDI_GET_STATISTICS:
+		return "PXENV_UNDI_GET_STATISTICS";
+	case PXENV_UNDI_CLEAR_STATISTICS:
+		return "PXENV_UNDI_CLEAR_STATISTICS";
+	case PXENV_UNDI_INITIATE_DIAGS:
+		return "PXENV_UNDI_INITIATE_DIAGS";
+	case PXENV_UNDI_FORCE_INTERRUPT:
+		return "PXENV_UNDI_FORCE_INTERRUPT";
+	case PXENV_UNDI_GET_MCAST_ADDRESS:
+		return "PXENV_UNDI_GET_MCAST_ADDRESS";
+	case PXENV_UNDI_GET_NIC_TYPE:
+		return "PXENV_UNDI_GET_NIC_TYPE";
+	case PXENV_UNDI_GET_IFACE_INFO:
+		return "PXENV_UNDI_GET_IFACE_INFO";
+	/*
+	 * Duplicate case value; this is a bug in the PXE specification.
+	 *
+	 *	case PXENV_UNDI_GET_STATE:
+	 *		return "PXENV_UNDI_GET_STATE";
+	 */
+	case PXENV_UNDI_ISR:
+		return "PXENV_UNDI_ISR";
+	case PXENV_GET_CACHED_INFO:
+		return "PXENV_GET_CACHED_INFO";
+	default:
+		return "UNKNOWN API CALL";
+	}
+}
+
+/**
+ * PXE parent parameter block
+ *
+ * Used as the paramter block for all parent PXE API calls.  Resides in base
+ * memory.
+ */
+static union u_PXENV_ANY __bss16 ( pxeparent_params );
+#define pxeparent_params __use_data16 ( pxeparent_params )
+
+/** PXE parent entry point
+ *
+ * Used as the indirection vector for all parent PXE API calls.  Resides in
+ * base memory.
+ */
+SEGOFF16_t __bss16 ( pxeparent_entry_point );
+#define pxeparent_entry_point __use_data16 ( pxeparent_entry_point )
+
+/**
+ * Issue parent PXE API call
+ *
+ * @v entry		Parent PXE stack entry point
+ * @v function		API call number
+ * @v params		PXE parameter block
+ * @v params_len	Length of PXE parameter block
+ * @ret rc		Return status code
+ */
+int pxeparent_call ( SEGOFF16_t entry, unsigned int function,
+		     void *params, size_t params_len ) {
+	PXENV_EXIT_t exit;
+	int discard_b, discard_D;
+	int rc;
+
+	/* Copy parameter block and entry point */
+	assert ( params_len <= sizeof ( pxeparent_params ) );
+	memcpy ( &pxeparent_params, params, params_len );
+	memcpy ( &pxeparent_entry_point, &entry, sizeof ( entry ) );
+
+	/* Call real-mode entry point.  This calling convention will
+	 * work with both the !PXE and the PXENV+ entry points.
+	 */
+	__asm__ __volatile__ ( REAL_CODE ( "pushw %%es\n\t"
+					   "pushw %%di\n\t"
+					   "pushw %%bx\n\t"
+					   "lcall *pxeparent_entry_point\n\t"
+					   "addw $6, %%sp\n\t" )
+			       : "=a" ( exit ), "=b" ( discard_b ),
+			         "=D" ( discard_D )
+			       : "b" ( function ),
+			         "D" ( __from_data16 ( &pxeparent_params ) )
+			       : "ecx", "edx", "esi", "ebp" );
+
+	/* PXE API calls may rudely change the status of A20 and not
+	 * bother to restore it afterwards.  Intel is known to be
+	 * guilty of this.
+	 *
+	 * Note that we will return to this point even if A20 gets
+	 * screwed up by the parent PXE stack, because Etherboot always
+	 * resides in an even megabyte of RAM.
+	 */
+	gateA20_set();
+
+	/* Determine return status code based on PXENV_EXIT and
+	 * PXENV_STATUS
+	 */
+	if ( exit == PXENV_EXIT_SUCCESS ) {
+		rc = 0;
+	} else {
+		rc = -pxeparent_params.Status;
+		/* Paranoia; don't return success for the combination
+		 * of PXENV_EXIT_FAILURE but PXENV_STATUS_SUCCESS
+		 */
+		if ( rc == 0 )
+			rc = -EIO;
+	}
+
+	/* If anything goes wrong, print as much debug information as
+	 * it's possible to give.
+	 */
+	if ( rc != 0 ) {
+		SEGOFF16_t rm_params = {
+			.segment = rm_ds,
+			.offset = __from_data16 ( &pxeparent_params ),
+		};
+
+		DBG ( "PXEPARENT %s failed: %s\n",
+		       pxeparent_function_name ( function ), strerror ( rc ) );
+		DBG ( "PXEPARENT parameters at %04x:%04x length "
+		       "%#02zx, entry point at %04x:%04x\n",
+		       rm_params.segment, rm_params.offset, params_len,
+		       pxeparent_entry_point.segment,
+		       pxeparent_entry_point.offset );
+		DBG ( "PXEPARENT parameters provided:\n" );
+		DBG_HDA ( rm_params, params, params_len );
+		DBG ( "PXEPARENT parameters returned:\n" );
+		DBG_HDA ( rm_params, &pxeparent_params, params_len );
+	}
+
+	/* Copy parameter block back */
+	memcpy ( params, &pxeparent_params, params_len );
+
+	return rc;
+}
+
diff --git a/gpxe/src/arch/i386/interface/pxeparent/pxeparent_dhcp.c b/gpxe/src/arch/i386/interface/pxeparent/pxeparent_dhcp.c
new file mode 100644
index 0000000..6605943
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/pxeparent/pxeparent_dhcp.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2009 Joshua Oreman <oremanj@rwcr.net>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <string.h>
+#include <gpxe/dhcp.h>
+#include <gpxe/netdevice.h>
+#include <undipreload.h>
+#include <pxeparent.h>
+#include <realmode.h>
+#include <pxe_api.h>
+
+/**
+ * Present cached DHCP packet if it exists
+ */
+void __weak_impl ( get_cached_dhcpack ) ( void ) {
+	struct undi_device *undi;
+	struct s_PXENV_GET_CACHED_INFO get_cached_info;
+	int rc;
+
+	/* Use preloaded UNDI device to get at PXE entry point */
+	undi = &preloaded_undi;
+	if ( ! undi->entry.segment ) {
+		DBG ( "PXEDHCP no preloaded UNDI device found\n" );
+		return;
+	}
+
+	/* Check that stack is available to get cached info */
+	if ( ! ( undi->flags & UNDI_FL_KEEP_ALL ) ) {
+		DBG ( "PXEDHCP stack was unloaded, no cache available\n" );
+		return;
+	}
+
+	/* Obtain cached DHCP packet */
+	memset ( &get_cached_info, 0, sizeof ( get_cached_info ) );
+	get_cached_info.PacketType = PXENV_PACKET_TYPE_DHCP_ACK;
+
+	if ( ( rc = pxeparent_call ( undi->entry, PXENV_GET_CACHED_INFO,
+				     &get_cached_info,
+				     sizeof ( get_cached_info ) ) ) != 0 ) {
+		DBG ( "PXEDHCP GET_CACHED_INFO failed: %s\n", strerror ( rc ) );
+		return;
+	}
+
+	DBG ( "PXEDHCP got cached info at %04x:%04x length %d\n",
+	      get_cached_info.Buffer.segment, get_cached_info.Buffer.offset,
+	      get_cached_info.BufferSize );
+
+	/* Present cached DHCP packet */
+	store_cached_dhcpack ( real_to_user ( get_cached_info.Buffer.segment,
+					      get_cached_info.Buffer.offset ),
+			       get_cached_info.BufferSize );
+}
diff --git a/gpxe/src/arch/i386/interface/syslinux/com32_call.c b/gpxe/src/arch/i386/interface/syslinux/com32_call.c
new file mode 100644
index 0000000..d2c3f91
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/syslinux/com32_call.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2008 Daniel Verkamp <daniel@drv.nu>.
+ *
+ * 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.
+ */
+
+/**
+ * @file SYSLINUX COM32 helpers
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <realmode.h>
+#include <comboot.h>
+#include <assert.h>
+#include <gpxe/uaccess.h>
+
+static com32sys_t __bss16 ( com32_regs );
+#define com32_regs __use_data16 ( com32_regs )
+
+static uint8_t __bss16 ( com32_int_vector );
+#define com32_int_vector __use_data16 ( com32_int_vector )
+
+static uint32_t __bss16 ( com32_farcall_proc );
+#define com32_farcall_proc __use_data16 ( com32_farcall_proc )
+
+uint16_t __bss16 ( com32_saved_sp );
+
+/**
+ * Interrupt call helper
+ */
+void __asmcall com32_intcall ( uint8_t interrupt, physaddr_t inregs_phys, physaddr_t outregs_phys ) {
+
+	memcpy_user ( virt_to_user( &com32_regs ), 0,
+	              phys_to_user ( inregs_phys ), 0,
+	              sizeof(com32sys_t) );
+
+	com32_int_vector = interrupt;
+
+	__asm__ __volatile__ (
+		REAL_CODE ( /* Save all registers */
+		            "pushal\n\t"
+		            "pushw %%ds\n\t"
+		            "pushw %%es\n\t"
+		            "pushw %%fs\n\t"
+		            "pushw %%gs\n\t"
+		            /* Mask off unsafe flags */
+		            "movl (com32_regs + 40), %%eax\n\t"
+		            "andl $0x200cd7, %%eax\n\t"
+		            "movl %%eax, (com32_regs + 40)\n\t"
+		            /* Load com32_regs into the actual registers */
+		            "movw %%sp, %%ss:(com32_saved_sp)\n\t"
+		            "movw $com32_regs, %%sp\n\t"
+		            "popw %%gs\n\t"
+		            "popw %%fs\n\t"
+		            "popw %%es\n\t"
+		            "popw %%ds\n\t"
+		            "popal\n\t"
+		            "popfl\n\t"
+		            "movw %%ss:(com32_saved_sp), %%sp\n\t"
+		            /* patch INT instruction */
+		            "pushw %%ax\n\t"
+		            "movb %%ss:(com32_int_vector), %%al\n\t"
+		            "movb %%al, %%cs:(com32_intcall_instr + 1)\n\t" 
+		            /* perform a jump to avoid problems with cache
+		             * consistency in self-modifying code on some CPUs (486)
+		             */
+		            "jmp 1f\n"
+		            "1:\n\t"
+		            "popw %%ax\n\t"
+		            "com32_intcall_instr:\n\t"
+		            /* INT instruction to be patched */
+		            "int $0xFF\n\t"
+		            /* Copy regs back to com32_regs */
+		            "movw %%sp, %%ss:(com32_saved_sp)\n\t"
+		            "movw $(com32_regs + 44), %%sp\n\t"
+		            "pushfl\n\t"
+		            "pushal\n\t"
+		            "pushw %%ds\n\t"
+		            "pushw %%es\n\t"
+		            "pushw %%fs\n\t"
+		            "pushw %%gs\n\t"
+		            "movw %%ss:(com32_saved_sp), %%sp\n\t"
+		            /* Restore registers */
+		            "popw %%gs\n\t"
+		            "popw %%fs\n\t"
+		            "popw %%es\n\t"
+		            "popw %%ds\n\t"
+		            "popal\n\t")
+		            : : );
+
+	if ( outregs_phys ) {
+		memcpy_user ( phys_to_user ( outregs_phys ), 0,
+		              virt_to_user( &com32_regs ), 0, 
+		              sizeof(com32sys_t) );
+	}
+}
+
+/**
+ * Farcall helper
+ */
+void __asmcall com32_farcall ( uint32_t proc, physaddr_t inregs_phys, physaddr_t outregs_phys ) {
+
+	memcpy_user ( virt_to_user( &com32_regs ), 0,
+	              phys_to_user ( inregs_phys ), 0,
+	              sizeof(com32sys_t) );
+
+	com32_farcall_proc = proc;
+
+	__asm__ __volatile__ (
+		REAL_CODE ( /* Save all registers */
+		            "pushal\n\t"
+		            "pushw %%ds\n\t"
+		            "pushw %%es\n\t"
+		            "pushw %%fs\n\t"
+		            "pushw %%gs\n\t"
+		            /* Mask off unsafe flags */
+		            "movl (com32_regs + 40), %%eax\n\t"
+		            "andl $0x200cd7, %%eax\n\t"
+		            "movl %%eax, (com32_regs + 40)\n\t"
+		            /* Load com32_regs into the actual registers */
+		            "movw %%sp, %%ss:(com32_saved_sp)\n\t"
+		            "movw $com32_regs, %%sp\n\t"
+		            "popw %%gs\n\t"
+		            "popw %%fs\n\t"
+		            "popw %%es\n\t"
+		            "popw %%ds\n\t"
+		            "popal\n\t"
+		            "popfl\n\t"
+		            "movw %%ss:(com32_saved_sp), %%sp\n\t"
+		            /* Call procedure */
+		            "lcall *%%ss:(com32_farcall_proc)\n\t"
+		            /* Copy regs back to com32_regs */
+		            "movw %%sp, %%ss:(com32_saved_sp)\n\t"
+		            "movw $(com32_regs + 44), %%sp\n\t"
+		            "pushfl\n\t"
+		            "pushal\n\t"
+		            "pushw %%ds\n\t"
+		            "pushw %%es\n\t"
+		            "pushw %%fs\n\t"
+		            "pushw %%gs\n\t"
+		            "movw %%ss:(com32_saved_sp), %%sp\n\t"
+		            /* Restore registers */
+		            "popw %%gs\n\t"
+		            "popw %%fs\n\t"
+		            "popw %%es\n\t"
+		            "popw %%ds\n\t"
+		            "popal\n\t")
+		            : : );
+
+	if ( outregs_phys ) {
+		memcpy_user ( phys_to_user ( outregs_phys ), 0,
+		              virt_to_user( &com32_regs ), 0, 
+		              sizeof(com32sys_t) );
+	}
+}
+
+/**
+ * CDECL farcall helper
+ */
+int __asmcall com32_cfarcall ( uint32_t proc, physaddr_t stack, size_t stacksz ) {
+	int32_t eax;
+
+	copy_user_to_rm_stack ( phys_to_user ( stack ), stacksz );
+	com32_farcall_proc = proc;
+
+	__asm__ __volatile__ (
+		REAL_CODE ( "lcall *%%ss:(com32_farcall_proc)\n\t" )
+		: "=a" (eax)
+		: 
+		: "ecx", "edx" );
+
+	remove_user_from_rm_stack ( 0, stacksz );
+
+	return eax;
+}
diff --git a/gpxe/src/arch/i386/interface/syslinux/com32_wrapper.S b/gpxe/src/arch/i386/interface/syslinux/com32_wrapper.S
new file mode 100644
index 0000000..5c5bd13
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/syslinux/com32_wrapper.S
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2008 Daniel Verkamp <daniel@drv.nu>.
+ *
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+	.text
+	.arch i386
+	.code32
+
+	.globl com32_farcall_wrapper
+com32_farcall_wrapper:
+
+	movl $com32_farcall, %eax
+	jmp com32_wrapper
+
+
+	.globl com32_cfarcall_wrapper
+com32_cfarcall_wrapper:
+
+	movl $com32_cfarcall, %eax
+	jmp com32_wrapper
+
+
+	.globl com32_intcall_wrapper
+com32_intcall_wrapper:
+
+	movl $com32_intcall, %eax
+	/*jmp com32_wrapper*/ /* fall through */
+
+com32_wrapper:
+
+	/* Switch to internal virtual address space */
+	call _phys_to_virt
+
+	mov %eax, (com32_helper_function)
+
+	/* Save external COM32 stack pointer */
+	movl %esp, (com32_external_esp)
+
+	/* Copy arguments to caller-save registers */
+	movl 12(%esp), %eax
+	movl 8(%esp), %ecx
+	movl 4(%esp), %edx
+
+	/* Switch to internal stack */
+	movl (com32_internal_esp), %esp
+
+	/* Copy arguments to internal stack */
+	pushl %eax
+	pushl %ecx
+	pushl %edx
+
+	call *(com32_helper_function)
+
+	/* Clean up stack */
+	addl $12, %esp
+
+	/* Save internal stack pointer and restore external stack pointer */
+	movl %esp, (com32_internal_esp)
+	movl (com32_external_esp), %esp
+
+	/* Switch to external flat physical address space */
+	call _virt_to_phys
+
+	ret
+
+
+	.data
+
+/* Internal gPXE virtual address space %esp */
+.globl com32_internal_esp
+.lcomm com32_internal_esp, 4
+
+/* External flat physical address space %esp */
+.globl com32_external_esp
+.lcomm com32_external_esp, 4
+
+/* Function pointer of helper to call */
+.lcomm com32_helper_function, 4
diff --git a/gpxe/src/arch/i386/interface/syslinux/comboot_call.c b/gpxe/src/arch/i386/interface/syslinux/comboot_call.c
new file mode 100644
index 0000000..0a17bf1
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/syslinux/comboot_call.c
@@ -0,0 +1,716 @@
+/*
+ * Copyright (C) 2008 Daniel Verkamp <daniel@drv.nu>.
+ *
+ * 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.
+ */
+
+/**
+ * @file SYSLINUX COMBOOT API
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <errno.h>
+#include <realmode.h>
+#include <biosint.h>
+#include <console.h>
+#include <stdlib.h>
+#include <comboot.h>
+#include <bzimage.h>
+#include <pxe_call.h>
+#include <setjmp.h>
+#include <string.h>
+#include <gpxe/posix_io.h>
+#include <gpxe/process.h>
+#include <gpxe/serial.h>
+#include <gpxe/init.h>
+#include <gpxe/image.h>
+#include <usr/imgmgmt.h>
+#include "config/console.h"
+#include "config/serial.h"
+
+/** The "SYSLINUX" version string */
+static char __data16_array ( syslinux_version, [] ) = "gPXE " VERSION;
+#define syslinux_version __use_data16 ( syslinux_version )
+
+/** The "SYSLINUX" copyright string */
+static char __data16_array ( syslinux_copyright, [] ) = "http://etherboot.org";
+#define syslinux_copyright __use_data16 ( syslinux_copyright )
+
+static char __data16_array ( syslinux_configuration_file, [] ) = "";
+#define syslinux_configuration_file __use_data16 ( syslinux_configuration_file )
+
+/** Feature flags */
+static uint8_t __data16 ( comboot_feature_flags ) = COMBOOT_FEATURE_IDLE_LOOP;
+#define comboot_feature_flags __use_data16 ( comboot_feature_flags )
+
+typedef union {
+	syslinux_pm_regs pm; syslinux_rm_regs rm;
+} syslinux_regs;
+
+/** Initial register values for INT 22h AX=1Ah and 1Bh */
+static syslinux_regs __text16 ( comboot_initial_regs );
+#define comboot_initial_regs __use_text16 ( comboot_initial_regs )
+
+static struct segoff __text16 ( int20_vector );
+#define int20_vector __use_text16 ( int20_vector )
+
+static struct segoff __text16 ( int21_vector );
+#define int21_vector __use_text16 ( int21_vector )
+
+static struct segoff __text16 ( int22_vector );
+#define int22_vector __use_text16 ( int22_vector )
+
+extern void int20_wrapper ( void );
+extern void int21_wrapper ( void );
+extern void int22_wrapper ( void );
+
+/* setjmp/longjmp context buffer used to return after loading an image */
+rmjmp_buf comboot_return;
+
+/* Replacement image when exiting with COMBOOT_EXIT_RUN_KERNEL */
+struct image *comboot_replacement_image;
+
+/* Mode flags set by INT 22h AX=0017h */
+static uint16_t comboot_graphics_mode = 0;
+
+
+/**
+ * Print a string with a particular terminator
+ */
+static void print_user_string ( unsigned int segment, unsigned int offset, char terminator ) {
+	int i = 0;
+	char c;
+	userptr_t str = real_to_user ( segment, offset );
+	for ( ; ; ) {
+		copy_from_user ( &c, str, i, 1 );
+		if ( c == terminator ) break;
+		putchar ( c );
+		i++;
+	}
+}
+
+
+/**
+ * Perform a series of memory copies from a list in low memory
+ */
+static void shuffle ( unsigned int list_segment, unsigned int list_offset, unsigned int count )
+{
+	comboot_shuffle_descriptor shuf[COMBOOT_MAX_SHUFFLE_DESCRIPTORS];
+	unsigned int i;
+
+	/* Copy shuffle descriptor list so it doesn't get overwritten */
+	copy_from_user ( shuf, real_to_user ( list_segment, list_offset ), 0,
+	                 count * sizeof( comboot_shuffle_descriptor ) );
+
+	/* Do the copies */
+	for ( i = 0; i < count; i++ ) {
+		userptr_t src_u = phys_to_user ( shuf[ i ].src );
+		userptr_t dest_u = phys_to_user ( shuf[ i ].dest );
+
+		if ( shuf[ i ].src == 0xFFFFFFFF ) {
+			/* Fill with 0 instead of copying */
+			memset_user ( dest_u, 0, 0, shuf[ i ].len );
+		} else if ( shuf[ i ].dest == 0xFFFFFFFF ) {
+			/* Copy new list of descriptors */
+			count = shuf[ i ].len / sizeof( comboot_shuffle_descriptor );
+			assert ( count <= COMBOOT_MAX_SHUFFLE_DESCRIPTORS );
+			copy_from_user ( shuf, src_u, 0, shuf[ i ].len );
+			i = -1;
+		} else {
+			/* Regular copy */
+			memmove_user ( dest_u, 0, src_u, 0, shuf[ i ].len );
+		}
+	}
+}
+
+
+/**
+ * Set default text mode
+ */
+void comboot_force_text_mode ( void ) {
+	if ( comboot_graphics_mode & COMBOOT_VIDEO_VESA ) {
+		/* Set VGA mode 3 via VESA VBE mode set */
+		__asm__ __volatile__ (
+			REAL_CODE (
+				"mov $0x4F02, %%ax\n\t"
+				"mov $0x03, %%bx\n\t"
+				"int $0x10\n\t"
+			)
+		: : );
+	} else if ( comboot_graphics_mode & COMBOOT_VIDEO_GRAPHICS ) {
+		/* Set VGA mode 3 via standard VGA mode set */
+		__asm__ __volatile__ (
+			REAL_CODE (
+				"mov $0x03, %%ax\n\t"
+				"int $0x10\n\t"
+			)
+		: : );
+	}
+
+	comboot_graphics_mode = 0;
+}
+
+
+/**
+ * Fetch kernel and optional initrd
+ */
+static int comboot_fetch_kernel ( char *kernel_file, char *cmdline ) {
+	struct image *kernel = NULL;
+	struct image *initrd = NULL;
+	char *initrd_file;
+	int rc;
+
+	/* Find initrd= parameter, if any */
+	if ( ( initrd_file = strstr ( cmdline, "initrd=" ) ) != NULL ) {
+		char *initrd_end;
+
+		/* skip "initrd=" */
+		initrd_file += 7;
+
+		/* Find terminating space, if any, and replace with NUL */
+		initrd_end = strchr ( initrd_file, ' ' );
+		if ( initrd_end )
+			*initrd_end = '\0';
+
+		DBG ( "COMBOOT: fetching initrd '%s'\n", initrd_file );
+
+		/* Allocate and fetch initrd */
+		initrd = alloc_image();
+		if ( ! initrd ) {
+			DBG ( "COMBOOT: could not allocate initrd\n" );
+			rc = -ENOMEM;
+			goto out;
+		}
+		if ( ( rc = imgfetch ( initrd, initrd_file,
+				       register_image ) ) != 0 ) {
+			DBG ( "COMBOOT: could not fetch initrd: %s\n",
+			      strerror ( rc ) );
+			goto out;
+		}
+
+		/* Restore space after initrd name, if applicable */
+		if ( initrd_end )
+			*initrd_end = ' ';
+	}
+
+	DBG ( "COMBOOT: fetching kernel '%s'\n", kernel_file );
+
+	/* Allocate and fetch kernel */
+	kernel = alloc_image();
+	if ( ! kernel ) {
+		DBG ( "COMBOOT: could not allocate kernel\n" );
+		rc = -ENOMEM;
+		goto out;
+	}
+	if ( ( rc = imgfetch ( kernel, kernel_file,
+			       register_image ) ) != 0 ) {
+		DBG ( "COMBOOT: could not fetch kernel: %s\n",
+		      strerror ( rc ) );
+		goto out;
+	}
+	if ( ( rc = image_set_cmdline ( kernel, cmdline ) ) != 0 ) {
+		DBG ( "COMBOOT: could not set kernel command line: %s\n",
+		      strerror ( rc ) );
+		goto out;
+	}
+
+	/* Store kernel as replacement image */
+	assert ( comboot_replacement_image == NULL );
+	comboot_replacement_image = image_get ( kernel );
+
+ out:
+	/* Drop image references unconditionally; either we want to
+	 * discard them, or they have been registered and we should
+	 * drop out local reference.
+	 */
+	image_put ( kernel );
+	image_put ( initrd );
+	return rc;
+}
+
+
+/**
+ * Terminate program interrupt handler
+ */
+static __asmcall void int20 ( struct i386_all_regs *ix86 __unused ) {
+	rmlongjmp ( comboot_return, COMBOOT_EXIT );
+}
+
+
+/**
+ * DOS-compatible API
+ */
+static __asmcall void int21 ( struct i386_all_regs *ix86 ) {
+	ix86->flags |= CF;
+
+	switch ( ix86->regs.ah ) {
+	case 0x00:
+	case 0x4C: /* Terminate program */
+		rmlongjmp ( comboot_return, COMBOOT_EXIT );
+		break;
+
+	case 0x01: /* Get Key with Echo */
+	case 0x08: /* Get Key without Echo */
+		/* TODO: handle extended characters? */
+		ix86->regs.al = getchar( );
+
+		/* Enter */
+		if ( ix86->regs.al == 0x0A )
+			ix86->regs.al = 0x0D;
+
+		if ( ix86->regs.ah == 0x01 )
+			putchar ( ix86->regs.al );
+
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x02: /* Write Character */
+		putchar ( ix86->regs.dl );
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x04: /* Write Character to Serial Port */
+		serial_putc ( ix86->regs.dl );
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x09: /* Write DOS String to Console */
+		print_user_string ( ix86->segs.ds, ix86->regs.dx, '$' );
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x0B: /* Check Keyboard */
+		if ( iskey() )
+			ix86->regs.al = 0xFF;
+		else
+			ix86->regs.al = 0x00;
+
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x30: /* Check DOS Version */
+		/* Bottom halves all 0; top halves spell "SYSLINUX" */
+		ix86->regs.eax = 0x59530000;
+		ix86->regs.ebx = 0x4C530000;
+		ix86->regs.ecx = 0x4E490000;
+		ix86->regs.edx = 0x58550000;
+		ix86->flags &= ~CF;
+		break;
+
+	default:
+		DBG ( "COMBOOT unknown int21 function %02x\n", ix86->regs.ah );
+		break;
+	}
+}
+
+
+/**
+ * SYSLINUX API
+ */
+static __asmcall void int22 ( struct i386_all_regs *ix86 ) {
+	ix86->flags |= CF;
+
+	switch ( ix86->regs.ax ) {
+	case 0x0001: /* Get Version */
+
+		/* Number of INT 22h API functions available */
+		ix86->regs.ax = 0x001D;
+
+		/* SYSLINUX version number */
+		ix86->regs.ch = 0; /* major */
+		ix86->regs.cl = 0; /* minor */
+
+		/* SYSLINUX derivative ID */
+		ix86->regs.dl = BZI_LOADER_TYPE_GPXE;
+
+		/* SYSLINUX version and copyright strings */
+		ix86->segs.es = rm_ds;
+		ix86->regs.si = ( ( unsigned ) __from_data16 ( syslinux_version ) );
+		ix86->regs.di = ( ( unsigned ) __from_data16 ( syslinux_copyright ) );
+
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x0002: /* Write String */
+		print_user_string ( ix86->segs.es, ix86->regs.bx, '\0' );
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x0003: /* Run command */
+		{
+			userptr_t cmd_u = real_to_user ( ix86->segs.es, ix86->regs.bx );
+			int len = strlen_user ( cmd_u, 0 );
+			char cmd[len + 1];
+			copy_from_user ( cmd, cmd_u, 0, len + 1 );
+			DBG ( "COMBOOT: executing command '%s'\n", cmd );
+			system ( cmd );
+			DBG ( "COMBOOT: exiting after executing command...\n" );
+			rmlongjmp ( comboot_return, COMBOOT_EXIT_COMMAND );
+		}
+		break;
+
+	case 0x0004: /* Run default command */
+		/* FIXME: just exit for now */
+		rmlongjmp ( comboot_return, COMBOOT_EXIT_COMMAND );
+		break;
+
+	case 0x0005: /* Force text mode */
+		comboot_force_text_mode ( );
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x0006: /* Open file */
+		{
+			int fd;
+			userptr_t file_u = real_to_user ( ix86->segs.es, ix86->regs.si );
+			int len = strlen_user ( file_u, 0 );
+			char file[len + 1];
+
+			copy_from_user ( file, file_u, 0, len + 1 );
+
+			if ( file[0] == '\0' ) {
+				DBG ( "COMBOOT: attempted open with empty file name\n" );
+				break;
+			}
+
+			DBG ( "COMBOOT: opening file '%s'\n", file );
+
+			fd = open ( file );
+
+			if ( fd < 0 ) {
+				DBG ( "COMBOOT: error opening file %s\n", file );
+				break;
+			}
+
+			/* This relies on the fact that a gPXE POSIX fd will
+			 * always fit in 16 bits.
+			 */
+#if (POSIX_FD_MAX > 65535)
+#error POSIX_FD_MAX too large
+#endif
+			ix86->regs.si = (uint16_t) fd;
+
+			ix86->regs.cx = COMBOOT_FILE_BLOCKSZ;
+			ix86->regs.eax = fsize ( fd );
+			ix86->flags &= ~CF;
+		}
+		break;
+
+	case 0x0007: /* Read file */
+		{
+			int fd = ix86->regs.si;
+			int len = ix86->regs.cx * COMBOOT_FILE_BLOCKSZ;
+			int rc;
+			fd_set fds;
+			userptr_t buf = real_to_user ( ix86->segs.es, ix86->regs.bx );
+
+			/* Wait for data ready to read */
+			FD_ZERO ( &fds );
+			FD_SET ( fd, &fds );
+
+			select ( &fds, 1 );
+
+			rc = read_user ( fd, buf, 0, len );
+			if ( rc < 0 ) {
+				DBG ( "COMBOOT: read failed\n" );
+				ix86->regs.si = 0;
+				break;
+			}
+
+			ix86->regs.ecx = rc;
+			ix86->flags &= ~CF;
+		}
+		break;
+
+	case 0x0008: /* Close file */
+		{
+			int fd = ix86->regs.si;
+			close ( fd );
+			ix86->flags &= ~CF;
+		}
+		break;
+
+	case 0x0009: /* Call PXE Stack */
+		if ( pxe_api_call_weak ( ix86 ) != 0 )
+			ix86->flags |= CF;
+		else
+			ix86->flags &= ~CF;
+		break;
+
+	case 0x000A: /* Get Derivative-Specific Information */
+
+		/* gPXE has its own derivative ID, so there is no defined
+		 * output here; just return AL for now */
+		ix86->regs.al = BZI_LOADER_TYPE_GPXE;
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x000B: /* Get Serial Console Configuration */
+#if defined(CONSOLE_SERIAL) && !defined(COMPRESERVE)
+		ix86->regs.dx = COMCONSOLE;
+		ix86->regs.cx = 115200 / COMSPEED;
+		ix86->regs.bx = 0;
+#else
+		ix86->regs.dx = 0;
+#endif
+
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x000E: /* Get configuration file name */
+		/* FIXME: stub */
+		ix86->segs.es = rm_ds;
+		ix86->regs.bx = ( ( unsigned ) __from_data16 ( syslinux_configuration_file ) );
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x000F: /* Get IPAPPEND strings */
+		/* FIXME: stub */
+		ix86->regs.cx = 0;
+		ix86->segs.es = 0;
+		ix86->regs.bx = 0;
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x0010: /* Resolve hostname */
+		{
+			userptr_t hostname_u = real_to_user ( ix86->segs.es, ix86->regs.bx );
+			int len = strlen_user ( hostname_u, 0 );
+			char hostname[len];
+			struct in_addr addr;
+
+			copy_from_user ( hostname, hostname_u, 0, len + 1 );
+			
+			/* TODO:
+			 * "If the hostname does not contain a dot (.), the
+			 * local domain name is automatically appended."
+			 */
+
+			comboot_resolv ( hostname, &addr );
+
+			ix86->regs.eax = addr.s_addr;
+			ix86->flags &= ~CF;
+		}
+		break;
+
+	case 0x0011: /* Maximum number of shuffle descriptors */
+		ix86->regs.cx = COMBOOT_MAX_SHUFFLE_DESCRIPTORS;
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x0012: /* Cleanup, shuffle and boot */
+		if ( ix86->regs.cx > COMBOOT_MAX_SHUFFLE_DESCRIPTORS )
+			break;
+
+		/* Perform final cleanup */
+		shutdown ( SHUTDOWN_BOOT );
+
+		/* Perform sequence of copies */
+		shuffle ( ix86->segs.es, ix86->regs.di, ix86->regs.cx );
+
+		/* Jump to real-mode entry point */
+		__asm__ __volatile__ (
+			REAL_CODE ( 
+				"pushw %0\n\t"
+				"popw %%ds\n\t"
+				"pushl %1\n\t"
+				"lret\n\t"
+			)
+			:
+			: "r" ( ix86->segs.ds ),
+			  "r" ( ix86->regs.ebp ),
+			  "d" ( ix86->regs.ebx ),
+			  "S" ( ix86->regs.esi ) );
+
+		assert ( 0 ); /* Execution should never reach this point */
+
+		break;
+
+	case 0x0013: /* Idle loop call */
+		step ( );
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x0015: /* Get feature flags */
+		ix86->segs.es = rm_ds;
+		ix86->regs.bx = ( ( unsigned ) __from_data16 ( &comboot_feature_flags ) );
+		ix86->regs.cx = 1; /* Number of feature flag bytes */
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x0016: /* Run kernel image */
+		{
+			userptr_t file_u = real_to_user ( ix86->segs.ds, ix86->regs.si );
+			userptr_t cmd_u = real_to_user ( ix86->segs.es, ix86->regs.bx );
+			int file_len = strlen_user ( file_u, 0 );
+			int cmd_len = strlen_user ( cmd_u, 0 );
+			char file[file_len + 1];
+			char cmd[cmd_len + 1];
+
+			copy_from_user ( file, file_u, 0, file_len + 1 );
+			copy_from_user ( cmd, cmd_u, 0, cmd_len + 1 );
+
+			DBG ( "COMBOOT: run kernel %s %s\n", file, cmd );
+			comboot_fetch_kernel ( file, cmd );
+			/* Technically, we should return if we
+			 * couldn't load the kernel, but it's not safe
+			 * to do that since we have just overwritten
+			 * part of the COMBOOT program's memory space.
+			 */
+			DBG ( "COMBOOT: exiting to run kernel...\n" );
+			rmlongjmp ( comboot_return, COMBOOT_EXIT_RUN_KERNEL );
+		}
+		break;
+
+	case 0x0017: /* Report video mode change */
+		comboot_graphics_mode = ix86->regs.bx;
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x0018: /* Query custom font */
+		/* FIXME: stub */
+		ix86->regs.al = 0;
+		ix86->segs.es = 0;
+		ix86->regs.bx = 0;
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x001B: /* Cleanup, shuffle and boot to real mode */
+		if ( ix86->regs.cx > COMBOOT_MAX_SHUFFLE_DESCRIPTORS )
+			break;
+
+		/* Perform final cleanup */
+		shutdown ( SHUTDOWN_BOOT );
+
+		/* Perform sequence of copies */
+		shuffle ( ix86->segs.es, ix86->regs.di, ix86->regs.cx );
+
+		/* Copy initial register values to .text16 */
+		memcpy_user ( real_to_user ( rm_cs, (unsigned) __from_text16 ( &comboot_initial_regs ) ), 0,
+		              real_to_user ( ix86->segs.ds, ix86->regs.si ), 0,
+		              sizeof(syslinux_rm_regs) );
+
+		/* Load initial register values */
+		__asm__ __volatile__ (
+			REAL_CODE (
+				/* Point SS:SP at the register value structure */
+				"pushw %%cs\n\t"
+				"popw %%ss\n\t"
+				"movw $comboot_initial_regs, %%sp\n\t"
+
+				/* Segment registers */
+				"popw %%es\n\t"
+				"popw %%ax\n\t" /* Skip CS */
+				"popw %%ds\n\t"
+				"popw %%ax\n\t" /* Skip SS for now */
+				"popw %%fs\n\t"
+				"popw %%gs\n\t"
+
+				/* GP registers */
+				"popl %%eax\n\t"
+				"popl %%ecx\n\t"
+				"popl %%edx\n\t"
+				"popl %%ebx\n\t"
+				"popl %%ebp\n\t" /* Skip ESP for now */
+				"popl %%ebp\n\t"
+				"popl %%esi\n\t"
+				"popl %%edi\n\t"
+
+				/* Load correct SS:ESP */
+				"movw $(comboot_initial_regs + 6), %%sp\n\t"
+				"popw %%ss\n\t"
+				"movl %%cs:(comboot_initial_regs + 28), %%esp\n\t"
+
+				"ljmp *%%cs:(comboot_initial_regs + 44)\n\t"
+			)
+			: : );
+
+		break;
+
+	case 0x001C: /* Get pointer to auxilliary data vector */
+		/* FIXME: stub */
+		ix86->regs.cx = 0; /* Size of the ADV */
+		ix86->flags &= ~CF;
+		break;
+
+	case 0x001D: /* Write auxilliary data vector */
+		/* FIXME: stub */
+		ix86->flags &= ~CF;
+		break;
+
+	default:
+		DBG ( "COMBOOT unknown int22 function %04x\n", ix86->regs.ax );
+		break;
+	}
+}
+
+/**
+ * Hook BIOS interrupts related to COMBOOT API (INT 20h, 21h, 22h)
+ */
+void hook_comboot_interrupts ( ) {
+
+	__asm__ __volatile__ (
+		TEXT16_CODE ( "\nint20_wrapper:\n\t"
+		              "pushl %0\n\t"
+		              "pushw %%cs\n\t"
+		              "call prot_call\n\t"
+		              "addw $4, %%sp\n\t"
+		              "iret\n\t" )
+		          : : "i" ( int20 ) );
+
+	hook_bios_interrupt ( 0x20, ( unsigned int ) int20_wrapper,
+		                      &int20_vector );
+
+	__asm__ __volatile__ (
+		TEXT16_CODE ( "\nint21_wrapper:\n\t"
+		              "pushl %0\n\t"
+		              "pushw %%cs\n\t"
+		              "call prot_call\n\t"
+		              "addw $4, %%sp\n\t"
+		              "iret\n\t" )
+		          : : "i" ( int21 ) );
+
+	hook_bios_interrupt ( 0x21, ( unsigned int ) int21_wrapper,
+	                      &int21_vector );
+
+	__asm__  __volatile__ (
+		TEXT16_CODE ( "\nint22_wrapper:\n\t"
+		              "pushl %0\n\t"
+		              "pushw %%cs\n\t"
+		              "call prot_call\n\t"
+		              "addw $4, %%sp\n\t"
+		              "iret\n\t" )
+		          : : "i" ( int22) );
+
+	hook_bios_interrupt ( 0x22, ( unsigned int ) int22_wrapper,
+	                      &int22_vector );
+}
+
+/**
+ * Unhook BIOS interrupts related to COMBOOT API (INT 20h, 21h, 22h)
+ */
+void unhook_comboot_interrupts ( ) {
+
+	unhook_bios_interrupt ( 0x20, ( unsigned int ) int20_wrapper,
+				&int20_vector );
+
+	unhook_bios_interrupt ( 0x21, ( unsigned int ) int21_wrapper,
+				&int21_vector );
+
+	unhook_bios_interrupt ( 0x22, ( unsigned int ) int22_wrapper,
+				&int22_vector );
+}
diff --git a/gpxe/src/arch/i386/interface/syslinux/comboot_resolv.c b/gpxe/src/arch/i386/interface/syslinux/comboot_resolv.c
new file mode 100644
index 0000000..30ac502
--- /dev/null
+++ b/gpxe/src/arch/i386/interface/syslinux/comboot_resolv.c
@@ -0,0 +1,60 @@
+#include <errno.h>
+#include <comboot.h>
+#include <gpxe/in.h>
+#include <gpxe/list.h>
+#include <gpxe/process.h>
+#include <gpxe/resolv.h>
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+static int comboot_resolv_rc;
+static struct in_addr comboot_resolv_addr;
+
+static void comboot_resolv_done ( struct resolv_interface *resolv,
+				  struct sockaddr *sa, int rc ) {
+	struct sockaddr_in *sin;
+
+	resolv_unplug ( resolv );
+
+	if ( rc != 0 ) {
+		comboot_resolv_rc = rc;
+		return;
+	}
+
+	if ( sa->sa_family != AF_INET ) {
+		comboot_resolv_rc = -EAFNOSUPPORT;
+		return;
+	}
+
+	sin = ( ( struct sockaddr_in * ) sa );
+	comboot_resolv_addr = sin->sin_addr;
+
+	comboot_resolv_rc = 0;
+}
+
+static struct resolv_interface_operations comboot_resolv_ops = {
+	.done = comboot_resolv_done,
+};
+
+static struct resolv_interface comboot_resolver = {
+	.intf = {
+		.dest = &null_resolv.intf,
+		.refcnt = NULL,
+	},
+	.op = &comboot_resolv_ops,
+};
+
+int comboot_resolv ( const char *name, struct in_addr *address ) {
+	int rc;
+
+	comboot_resolv_rc = -EINPROGRESS;
+
+	if ( ( rc = resolv ( &comboot_resolver, name, NULL ) ) != 0 )
+		return rc;
+
+	while ( comboot_resolv_rc == -EINPROGRESS )
+		step();
+
+	*address = comboot_resolv_addr;
+	return comboot_resolv_rc;
+}
diff --git a/gpxe/src/arch/i386/kir-Makefile b/gpxe/src/arch/i386/kir-Makefile
new file mode 100644
index 0000000..bbfc1a3
--- /dev/null
+++ b/gpxe/src/arch/i386/kir-Makefile
@@ -0,0 +1,26 @@
+# Makefile to build a KEEP_IT_REAL flavour
+# 
+# KEEP_IT_REAL, by its nature, requires a different build of every
+# single object file, since the inclusion of ".code16gcc" will
+# generate different machine code from the assembly.  Unlike the other
+# config options, there is no way that this global dependency can ever
+# be reduced, so it makes sense to be able to build both the normal
+# and the KIR versions without having to force a full rebuild each
+# time.
+
+# Add this Makefile to MAKEDEPS
+#
+MAKEDEPS	+= arch/i386/kir-Makefile
+
+# Place binaries in bin-kir
+#
+BIN		= bin-kir
+
+# Compile with -DKEEP_IT_REAL, forcibly include kir.h at the start of
+# each file to drag in ".code16gcc"
+#
+CFLAGS		+= -DKEEP_IT_REAL -include kir.h
+
+include Makefile
+
+LDSCRIPT	= arch/i386/scripts/i386-kir.lds
diff --git a/gpxe/src/arch/i386/prefix/bootpart.S b/gpxe/src/arch/i386/prefix/bootpart.S
new file mode 100644
index 0000000..968da1a
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/bootpart.S
@@ -0,0 +1,218 @@
+FILE_LICENCE ( GPL2_OR_LATER )
+
+#define BOOT_SEG	0x07c0
+#define EXEC_SEG	0x0100
+#define STACK_SEG 	0x0200
+#define STACK_SIZE	0x2000
+	
+	.text
+	.arch i386
+	.section ".prefix", "awx", @progbits
+	.code16
+
+/*
+ * Find active partition
+ *
+ * Parameters:
+ *   %dl	: BIOS drive number
+ *   %bp	: Active partition handler routine
+ */
+find_active_partition:
+	/* Set up stack at STACK_SEG:STACK_SIZE */
+	movw	$STACK_SEG, %ax
+	movw	%ax, %ss
+	movw	$STACK_SIZE, %sp
+
+	/* Relocate self to EXEC_SEG */
+	pushw	$BOOT_SEG
+	popw	%ds
+	pushw	$EXEC_SEG
+	popw	%es
+	xorw	%si, %si
+	xorw	%di, %di
+	movw	$0x200, %cx
+	rep movsb
+	ljmp	$EXEC_SEG, $1f
+1:	pushw	%ds
+	popw	%es
+	pushw	%cs
+	popw	%ds
+
+	/* Check for LBA extensions */
+	movb	$0x41, %ah
+	movw	$0x55aa, %bx
+	stc
+	int	$0x13
+	jc	1f
+	cmpw	$0xaa55, %bx
+	jne	1f
+	movw	$read_lba, read_sectors
+1:	
+	/* Read and process root partition table */
+	xorb	%dh, %dh
+	movw	$0x0001, %cx
+	xorl	%esi, %esi
+	xorl	%edi, %edi
+	call	process_table
+
+	/* Print failure message */
+	movw	$10f, %si
+	jmp	boot_error
+10:	.asciz	"Could not locate active partition\r\n"
+
+/*
+ * Print failure message and boot next device
+ *
+ * Parameters:
+ *   %si	: Failure string
+ */
+boot_error:
+	cld
+	movw	$0x0007, %bx
+	movb	$0x0e, %ah
+1:	lodsb
+	testb	%al, %al
+	je	99f
+	int	$0x10
+	jmp	1b
+99:	/* Boot next device */
+	int	$0x18
+
+/*
+ * Process partition table
+ *
+ * Parameters:
+ *   %dl	: BIOS drive number
+ *   %dh	: Head
+ *   %cl	: Sector (bits 0-5), high two bits of cylinder (bits 6-7)
+ *   %ch	: Low eight bits of cylinder
+ *   %esi:%edi	: LBA address
+ *   %bp	: Active partition handler routine
+ *
+ * Returns:
+ *   CF set on error
+ */
+process_table:
+	pushal
+	call	read_boot_sector
+	jc	99f
+	movw	$446, %bx
+1:	call	process_partition
+	addw	$16, %bx
+	cmpw	$510, %bx
+	jne	1b
+99:	popal
+	ret
+
+/*
+ * Process partition
+ *
+ * Parameters:
+ *   %dl	: BIOS drive number
+ *   %dh	: Head
+ *   %cl	: Sector (bits 0-5), high two bits of cylinder (bits 6-7)
+ *   %ch	: Low eight bits of cylinder
+ *   %esi:%edi	: LBA address
+ *   %bx	: Offset within partition table
+ *   %bp	: Active partition handler routine
+ */
+process_partition:
+	pushal
+	/* Load C/H/S values from partition entry */
+	movb	%es:1(%bx), %dh
+	movw	%es:2(%bx), %cx
+	/* Update LBA address from partition entry */
+	addl	%es:8(%bx), %edi
+	adcl	$0, %esi
+	/* Check active flag */
+	testb	$0x80, %es:(%bx)
+	jz	1f
+	call	read_boot_sector
+	jc	99f
+	jmp	*%bp
+1:	/* Check for extended partition */
+	movb	%es:4(%bx), %al
+	cmpb	$0x05, %al
+	je	2f
+	cmpb	$0x0f, %al
+	je	2f
+	cmpb	$0x85, %al
+	jne	99f
+2:	call	process_table
+99:	popal
+	/* Reload original partition table */
+	call	read_boot_sector
+	ret
+
+/*
+ * Read single sector to %es:0000 and verify 0x55aa signature
+ *
+ * Parameters:
+ *   %dl	: BIOS drive number
+ *   %dh	: Head
+ *   %cl	: Sector (bits 0-5), high two bits of cylinder (bits 6-7)
+ *   %ch	: Low eight bits of cylinder
+ *   %esi:%edi	: LBA address
+ *
+ * Returns:
+ *   CF set on error
+ */
+read_boot_sector:
+	pushw	%ax
+	movw	$1, %ax
+	call	*read_sectors
+	jc	99f
+	cmpw	$0xaa55, %es:(510)
+	je	99f
+	stc	
+99:	popw	%ax
+	ret
+	
+/*
+ * Read sectors to %es:0000
+ *
+ * Parameters:
+ *   %dl	: BIOS drive number
+ *   %dh	: Head
+ *   %cl	: Sector (bits 0-5), high two bits of cylinder (bits 6-7)
+ *   %ch	: Low eight bits of cylinder
+ *   %esi:%edi	: LBA address
+ *   %ax	: Number of sectors (max 127)
+ *
+ * Returns:
+ *   CF set on error
+ */
+read_sectors:	.word	read_chs
+
+read_chs:
+	/* Read sectors using C/H/S address */
+	pushal
+	xorw	%bx, %bx
+	movb	$0x02, %ah
+	stc
+	int	$0x13
+	sti
+	popal
+	ret
+
+read_lba:
+	/* Read sectors using LBA address */
+	pushal
+	movw	%ax, (lba_desc + 2)
+	pushw	%es
+	popw	(lba_desc + 6)
+	movl	%edi, (lba_desc + 8)
+	movl	%esi, (lba_desc + 12)
+	movw	$lba_desc, %si
+	movb	$0x42, %ah
+	int	$0x13
+	popal
+	ret
+
+lba_desc:
+	.byte	0x10
+	.byte	0
+	.word	1
+	.word	0x0000
+	.word	0x0000
+	.long	0, 0
diff --git a/gpxe/src/arch/i386/prefix/dskprefix.S b/gpxe/src/arch/i386/prefix/dskprefix.S
new file mode 100644
index 0000000..60d351f
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/dskprefix.S
@@ -0,0 +1,381 @@
+/* NOTE: this boot sector contains instructions that need at least an 80186.
+ * Yes, as86 has a bug somewhere in the valid instruction set checks.
+ *
+ */
+
+/*	floppyload.S Copyright (C) 1991, 1992 Linus Torvalds
+ *	modified by Drew Eckhardt
+ *	modified by Bruce Evans (bde)
+ *
+ * floppyprefix.S is loaded at 0x0000:0x7c00 by the bios-startup routines.
+ *
+ * It then loads the system at SYSSEG<<4, using BIOS interrupts.
+ *
+ * The loader has been made as simple as possible, and continuous read errors
+ * will result in a unbreakable loop. Reboot by hand. It loads pretty fast by
+ * getting whole tracks at a time whenever possible.
+ */
+
+FILE_LICENCE ( GPL2_ONLY )
+
+.equ	BOOTSEG, 0x07C0			/* original address of boot-sector */
+
+.equ	SYSSEG, 0x1000			/* system loaded at SYSSEG<<4 */
+
+	.org	0
+	.arch i386
+	.text
+	.section ".prefix", "ax", @progbits
+	.code16
+
+	jmp	$BOOTSEG, $go		/* reload cs:ip to match relocation addr */
+go: 
+	movw	$0x2000-12, %di		/* 0x2000 is arbitrary value >= length */
+					/* of bootsect + room for stack + 12 for */
+					/* saved disk parm block */
+
+	movw	$BOOTSEG, %ax
+	movw	%ax,%ds
+	movw	%ax,%es
+	movw	%ax,%ss			/* put stack at BOOTSEG:0x4000-12. */
+	movw	%di,%sp
+
+/* Many BIOS's default disk parameter tables will not recognize multi-sector
+ * reads beyond the maximum sector number specified in the default diskette
+ * parameter tables - this may mean 7 sectors in some cases.
+ *
+ * Since single sector reads are slow and out of the question, we must take care
+ * of this by creating new parameter tables (for the first disk) in RAM.  We
+ * will set the maximum sector count to 36 - the most we will encounter on an
+ * ED 2.88.  High doesn't hurt.	Low does.
+ *
+ * Segments are as follows: ds=es=ss=cs - BOOTSEG
+ */
+
+	xorw	%cx,%cx
+	movw	%cx,%es			/* access segment 0 */
+	movw	$0x78, %bx		/* 0:bx is parameter table address */
+	pushw	%ds			/* save ds */
+/* 0:bx is parameter table address */
+	ldsw	%es:(%bx),%si		/* loads ds and si */
+
+	movw	%ax,%es			/* ax is BOOTSECT (loaded above) */
+	movb	$6, %cl			/* copy 12 bytes */
+	cld
+	pushw	%di			/* keep a copy for later */
+	rep
+	movsw				/* ds:si is source, es:di is dest */
+	popw	%di
+
+	movb	$36,%es:4(%di)
+
+	movw	%cx,%ds			/* access segment 0 */
+	xchgw	%di,(%bx)
+	movw	%es,%si
+	xchgw	%si,2(%bx)
+	popw	%ds			/* restore ds */
+	movw	%di, dpoff		/* save old parameters */
+	movw	%si, dpseg		/* to restore just before finishing */
+	pushw	%ds
+	popw	%es			/* reload es */
+
+/* Note that es is already set up.  Also cx is 0 from rep movsw above. */
+
+	xorb	%ah,%ah			/* reset FDC */
+	xorb	%dl,%dl
+	int	$0x13
+
+/* Get disk drive parameters, specifically number of sectors/track.
+ *
+ * It seems that there is no BIOS call to get the number of sectors.  Guess
+ * 36 sectors if sector 36 can be read, 18 sectors if sector 18 can be read,
+ * 15 if sector 15 can be read.	Otherwise guess 9.
+ */
+
+	movw	$disksizes, %si		/* table of sizes to try */
+
+probe_loop: 
+	lodsb
+	cbtw				/* extend to word */
+	movw	%ax, sectors
+	cmpw	$disksizes+4, %si
+	jae	got_sectors		/* if all else fails, try 9 */
+	xchgw	%cx,%ax			/* cx = track and sector */
+	xorw	%dx,%dx			/* drive 0, head 0 */
+	movw	$0x0200, %bx		/* address after boot sector */
+					/*   (512 bytes from origin, es = cs) */
+	movw	$0x0201, %ax		/* service 2, 1 sector */
+	int	$0x13
+	jc	probe_loop		/* try next value */
+
+got_sectors: 
+	movw	$msg1end-msg1, %cx
+	movw	$msg1, %si
+	call	print_str
+
+/* ok, we've written the Loading... message, now we want to load the system */
+
+	movw	$SYSSEG, %ax
+	movw	%ax,%es			/* segment of SYSSEG<<4 */
+	pushw	%es
+	call	read_it
+
+/* This turns off the floppy drive motor, so that we enter the kernel in a
+ * known state, and don't have to worry about it later.
+ */
+	movw	$0x3f2, %dx
+	xorb	%al,%al
+	outb	%al,%dx
+
+	call	print_nl
+	pop	%es			/* = SYSSEG */
+
+/* Restore original disk parameters */
+	movw	$0x78, %bx
+	movw	dpoff, %di
+	movw	dpseg, %si
+	xorw	%ax,%ax
+	movw	%ax,%ds
+	movw	%di,(%bx)
+	movw	%si,2(%bx)
+
+	/* Everything now loaded.  %es = SYSSEG, so %es:0000 points to
+	 * start of loaded image.
+	 */
+
+	/* Jump to loaded copy */
+	ljmp	$SYSSEG, $start_runtime
+
+endseg:	.word SYSSEG
+	.section ".zinfo.fixup", "a", @progbits	/* Compressor fixups */
+	.ascii	"ADDW"
+	.long	endseg
+	.long	16
+	.long	0
+	.previous
+
+/* This routine loads the system at address SYSSEG<<4, making sure no 64kB
+ * boundaries are crossed. We try to load it as fast as possible, loading whole
+ * tracks whenever we can.
+ *
+ * in:	es - starting address segment (normally SYSSEG)
+ */
+read_it: 
+	movw	$0,sread		/* load whole image including prefix */
+	movw	%es,%ax
+	testw	$0x0fff, %ax
+die:	jne	die			/* es must be at 64kB boundary */
+	xorw	%bx,%bx			/* bx is starting address within segment */
+rp_read: 
+	movw	%es,%ax
+	movw	%bx,%dx
+	movb	$4, %cl
+	shrw	%cl,%dx			/* bx is always divisible by 16 */
+	addw	%dx,%ax
+	cmpw	endseg, %ax	/* have we loaded all yet? */
+	jb	ok1_read
+	ret
+ok1_read: 
+	movw	sectors, %ax
+	subw	sread, %ax
+	movw	%ax,%cx
+	shlw	$9, %cx
+	addw	%bx,%cx
+	jnc	ok2_read
+	je	ok2_read
+	xorw	%ax,%ax
+	subw	%bx,%ax
+	shrw	$9, %ax
+ok2_read: 
+	call	read_track
+	movw	%ax,%cx
+	addw	sread, %ax
+	cmpw	sectors, %ax
+	jne	ok3_read
+	movw	$1, %ax
+	subw	head, %ax
+	jne	ok4_read
+	incw	track
+ok4_read: 
+	movw	%ax, head
+	xorw	%ax,%ax
+ok3_read: 
+	movw	%ax, sread
+	shlw	$9, %cx
+	addw	%cx,%bx
+	jnc	rp_read
+	movw	%es,%ax
+	addb	$0x10, %ah
+	movw	%ax,%es
+	xorw	%bx,%bx
+	jmp	rp_read
+
+read_track: 
+	pusha
+	pushw	%ax
+	pushw	%bx
+	pushw	%bp			/* just in case the BIOS is buggy */
+	movw	$0x0e2e, %ax		/* 0x2e = . */
+	movw	$0x0007, %bx
+	int	$0x10
+	popw	%bp
+	popw	%bx
+	popw	%ax
+
+	movw	track, %dx
+	movw	sread, %cx
+	incw	%cx
+	movb	%dl,%ch
+	movw	head, %dx
+	movb	%dl,%dh
+	andw	$0x0100, %dx
+	movb	$2, %ah
+
+	pushw	%dx			/* save for error dump */
+	pushw	%cx
+	pushw	%bx
+	pushw	%ax
+
+	int	$0x13
+	jc	bad_rt
+	addw	$8, %sp
+	popa
+	ret
+
+bad_rt: pushw	%ax			/* save error code */
+	call	print_all		/* ah = error, al = read */
+
+	xorb	%ah,%ah
+	xorb	%dl,%dl
+	int	$0x13
+
+	addw	$10, %sp
+	popa
+	jmp	read_track
+
+/* print_all is for debugging purposes.	It will print out all of the registers.
+ * The assumption is that this is called from a routine, with a stack frame like
+ *	dx
+ *	cx
+ *	bx
+ *	ax
+ *	error
+ *	ret <- sp
+ */
+
+print_all: 
+	call	print_nl		/* nl for readability */
+	movw	$5, %cx			/* error code + 4 registers */
+	movw	%sp,%bp
+
+print_loop: 
+	pushw	%cx			/* save count left */
+
+	cmpb	$5, %cl
+	jae	no_reg			/* see if register name is needed */
+
+	movw	$0x0007, %bx		/* page 0, attribute 7 (normal) */
+	movw	$0xe05+0x41-1, %ax
+	subb	%cl,%al
+	int	$0x10
+
+	movb	$0x58, %al		/* 'X' */
+	int	$0x10
+
+	movb	$0x3A, %al		/* ':' */
+	int	$0x10
+
+no_reg: 
+	addw	$2, %bp			/* next register */
+	call	print_hex		/* print it */
+	movb	$0x20, %al		/* print a space */
+	int	$0x10
+	popw	%cx
+	loop	print_loop
+	call	print_nl		/* nl for readability */
+	ret
+
+print_str: 
+	movw	$0x0007, %bx		/* page 0, attribute 7 (normal) */
+	movb	$0x0e, %ah		/* write char, tty mode */
+prloop: 
+	lodsb
+	int	$0x10
+	loop	prloop
+	ret
+
+print_nl: 
+	movw	$0x0007, %bx		/* page 0, attribute 7 (normal) */
+	movw	$0xe0d, %ax		/* CR */
+	int	$0x10
+	movb	$0xa, %al		/* LF */
+	int	$0x10
+	ret
+
+/* print_hex prints the word pointed to by ss:bp in hexadecimal. */
+
+print_hex: 
+	movw	(%bp),%dx		/* load word into dx */
+	movb	$4, %cl
+	movb	$0x0e, %ah		/* write char, tty mode */
+	movw	$0x0007, %bx		/* page 0, attribute 7 (normal) */
+	call	print_digit
+	call	print_digit
+	call	print_digit
+/* fall through */
+print_digit: 
+	rol	%cl,%dx			/* rotate so that lowest 4 bits are used */
+	movb	$0x0f, %al		/* mask for nybble */
+	andb	%dl,%al
+	addb	$0x90, %al		/* convert al to ascii hex (four instructions) */
+	daa
+	adcb	$0x40, %al
+	daa
+	int	$0x10
+	ret
+
+sread:	.word 0				/* sectors read of current track */
+head:	.word 0				/* current head */
+track:	.word 0				/* current track */
+
+sectors: 
+	.word 0
+
+dpseg:	.word 0
+dpoff:	.word 0
+
+disksizes: 
+	.byte 36,18,15,9
+
+msg1: 
+	.ascii "Loading ROM image"
+msg1end: 
+
+	.org 510, 0
+	.word 0xAA55
+
+start_runtime:
+	/* Install gPXE */
+	call	install
+
+	/* Set up real-mode stack */
+	movw	%bx, %ss
+	movw	$_estack16, %sp
+
+	/* Jump to .text16 segment */
+	pushw	%ax
+	pushw	$1f
+	lret
+	.section ".text16", "awx", @progbits
+1:
+	pushl	$main
+	pushw	%cs
+	call	prot_call
+	popl	%ecx /* discard */
+
+	/* Uninstall gPXE */
+	call	uninstall
+
+	/* Boot next device */
+	int $0x18
+
diff --git a/gpxe/src/arch/i386/prefix/hdprefix.S b/gpxe/src/arch/i386/prefix/hdprefix.S
new file mode 100644
index 0000000..0576756
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/hdprefix.S
@@ -0,0 +1,109 @@
+FILE_LICENCE ( GPL2_OR_LATER )
+
+	.text
+	.arch i386
+	.section ".prefix", "awx", @progbits
+	.code16
+	.org 0
+
+	movw	$load_image, %bp
+	jmp	find_active_partition
+
+#include "bootpart.S"
+
+load_image:
+	/* Get disk geometry */
+	pushal
+	pushw	%es
+	movb	$0x08, %ah
+	int	$0x13
+	jc	load_failed
+	movb	%cl, max_sector
+	movb	%dh, max_head
+	popw	%es
+	popal
+	
+1:	/* Read to end of current track */
+	movb	%cl, %al
+	negb	%al
+	addb	max_sector, %al
+	incb	%al
+	andb	$0x3f, %al
+	movzbl	%al, %eax
+	call	*read_sectors
+	jc	load_failed
+	
+	/* Update %es */
+	movw	%es, %bx
+	shll	$5, %eax
+	addw	%ax, %bx
+	movw	%bx, %es
+	shrl	$5, %eax
+	
+	/* Update LBA address */
+	addl	%eax, %edi
+	adcl	$0, %esi
+	
+	/* Update CHS address */
+	andb	$0xc0, %cl
+	orb	$0x01, %cl
+	incb	%dh
+	cmpb	max_head, %dh
+	jbe	2f
+	xorb	%dh, %dh
+	incb	%ch
+	jnc	2f
+	addb	$0xc0, %cl
+2:
+	/* Loop until whole image is read */
+	subl	%eax, load_length
+	ja	1b
+	ljmp	$BOOT_SEG, $start_image
+
+max_sector:
+	.byte	0
+max_head:
+	.byte	0
+load_length:
+	.long	0
+	
+	.section ".zinfo.fixup", "a", @progbits	/* Compressor fixups */
+	.ascii	"ADDL"
+	.long	load_length
+	.long	512
+	.long	0
+	.previous
+
+
+load_failed:
+	movw	$10f, %si
+	jmp	boot_error
+10:	.asciz	"Could not load gPXE\r\n"
+
+	.org 510
+	.byte 0x55, 0xaa
+
+start_image:
+	/* Install gPXE */
+	call	install
+
+	/* Set up real-mode stack */
+	movw	%bx, %ss
+	movw	$_estack16, %sp
+
+	/* Jump to .text16 segment */
+	pushw	%ax
+	pushw	$1f
+	lret
+	.section ".text16", "awx", @progbits
+1:
+	pushl	$main
+	pushw	%cs
+	call	prot_call
+	popl	%ecx /* discard */
+
+	/* Uninstall gPXE */
+	call	uninstall
+
+	/* Boot next device */
+	int $0x18
diff --git a/gpxe/src/arch/i386/prefix/hromprefix.S b/gpxe/src/arch/i386/prefix/hromprefix.S
new file mode 100644
index 0000000..03acf1e
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/hromprefix.S
@@ -0,0 +1,12 @@
+/*****************************************************************************
+ * ROM prefix that relocates to HIGHMEM_LOADPOINT during POST if PMM allocation
+ * fails. Intended to be used, with caution, on BIOSes that support PCI3.00 but
+ * have limited PMM support, such as most AMI BIOSes.
+ *****************************************************************************
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+#define SHRINK_WITHOUT_PMM
+
+#include "romprefix.S"
diff --git a/gpxe/src/arch/i386/prefix/kkpxeprefix.S b/gpxe/src/arch/i386/prefix/kkpxeprefix.S
new file mode 100644
index 0000000..02cc6fe
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/kkpxeprefix.S
@@ -0,0 +1,13 @@
+/*****************************************************************************
+ * PXE prefix that keeps the whole PXE stack present
+ *****************************************************************************
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+/* Since we have the whole stack, we can use cached DHCP information */
+REQUEST_OBJECT ( pxeparent_dhcp )
+
+#define PXELOADER_KEEP_UNDI
+#define PXELOADER_KEEP_PXE
+#include "pxeprefix.S"
diff --git a/gpxe/src/arch/i386/prefix/kpxeprefix.S b/gpxe/src/arch/i386/prefix/kpxeprefix.S
new file mode 100644
index 0000000..923facc
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/kpxeprefix.S
@@ -0,0 +1,9 @@
+/*****************************************************************************
+ * PXE prefix that keep the UNDI portion of the PXE stack present
+ *****************************************************************************
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+#define PXELOADER_KEEP_UNDI
+#include "pxeprefix.S"
diff --git a/gpxe/src/arch/i386/prefix/libprefix.S b/gpxe/src/arch/i386/prefix/libprefix.S
new file mode 100644
index 0000000..9e6ba6f
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/libprefix.S
@@ -0,0 +1,819 @@
+/*
+ * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+	.arch i386
+
+/**
+ * High memory temporary load address
+ *
+ * Temporary buffer into which to copy (or decompress) our runtime
+ * image, prior to calling get_memmap() and relocate().  We don't
+ * actually leave anything here once install() has returned.
+ *
+ * We use the start of an even megabyte so that we don't have to worry
+ * about the current state of the A20 line.
+ *
+ * We use 4MB rather than 2MB because some PXE stack / PMM BIOS
+ * combinations are known to place data required by other UNDI ROMs
+ * loader around the 2MB mark.
+ */
+	.globl	HIGHMEM_LOADPOINT
+	.equ	HIGHMEM_LOADPOINT, ( 4 << 20 )
+
+/* Image compression enabled */
+#define COMPRESS 1
+
+#define CR0_PE 1
+
+/*****************************************************************************
+ * Utility function: print character (with LF -> LF,CR translation)
+ *
+ * Parameters:
+ *   %al : character to print
+ *   %ds:di : output buffer (or %di=0 to print to console)
+ * Returns:
+ *   %ds:di : next character in output buffer (if applicable)
+ *****************************************************************************
+ */
+	.section ".prefix.lib", "awx", @progbits
+	.code16
+	.globl	print_character
+print_character:
+	/* Preserve registers */
+	pushw	%ax
+	pushw	%bx
+	pushw	%bp
+	/* If %di is non-zero, write character to buffer and exit */
+	testw	%di, %di
+	jz	1f
+	movb	%al, %ds:(%di)
+	incw	%di
+	jmp	3f
+1:	/* Print character */
+	movw	$0x0007, %bx		/* page 0, attribute 7 (normal) */
+	movb	$0x0e, %ah		/* write char, tty mode */
+	cmpb	$0x0a, %al		/* '\n'? */
+	jne	2f
+	int	$0x10
+	movb	$0x0d, %al
+2:	int	$0x10
+	/* Restore registers and return */
+3:	popw	%bp
+	popw	%bx
+	popw	%ax
+	ret
+	.size	print_character, . - print_character
+
+/*****************************************************************************
+ * Utility function: print a NUL-terminated string
+ *
+ * Parameters:
+ *   %ds:si : string to print
+ *   %ds:di : output buffer (or %di=0 to print to console)
+ * Returns:
+ *   %ds:si : character after terminating NUL
+ *   %ds:di : next character in output buffer (if applicable)
+ *****************************************************************************
+ */
+	.section ".prefix.lib", "awx", @progbits
+	.code16
+	.globl	print_message
+print_message:
+	/* Preserve registers */
+	pushw	%ax
+	/* Print string */
+1: 	lodsb
+	testb	%al, %al
+	je	2f
+	call	print_character
+	jmp	1b
+2:	/* Restore registers and return */
+	popw	%ax
+	ret
+	.size	print_message, . - print_message
+
+/*****************************************************************************
+ * Utility functions: print hex digit/byte/word/dword
+ *
+ * Parameters:
+ *   %al (low nibble) : digit to print
+ *   %al : byte to print
+ *   %ax : word to print
+ *   %eax : dword to print
+ *   %ds:di : output buffer (or %di=0 to print to console)
+ * Returns:
+ *   %ds:di : next character in output buffer (if applicable)
+ *****************************************************************************
+ */
+	.section ".prefix.lib", "awx", @progbits
+	.code16
+	.globl	print_hex_dword
+print_hex_dword:
+	rorl	$16, %eax
+	call	print_hex_word
+	rorl	$16, %eax
+	/* Fall through */
+	.size	print_hex_dword, . - print_hex_dword
+	.globl	print_hex_word
+print_hex_word:
+	xchgb	%al, %ah
+	call	print_hex_byte
+	xchgb	%al, %ah
+	/* Fall through */
+	.size	print_hex_word, . - print_hex_word
+	.globl	print_hex_byte
+print_hex_byte:
+	rorb	$4, %al
+	call	print_hex_nibble
+	rorb	$4, %al
+	/* Fall through */
+	.size	print_hex_byte, . - print_hex_byte
+	.globl	print_hex_nibble
+print_hex_nibble:
+	/* Preserve registers */
+	pushw	%ax
+	/* Print digit (technique by Norbert Juffa <norbert.juffa@amd.com> */
+	andb	$0x0f, %al
+	cmpb	$10, %al
+	sbbb	$0x69, %al
+	das
+	call	print_character
+	/* Restore registers and return */
+	popw	%ax
+	ret
+	.size	print_hex_nibble, . - print_hex_nibble
+
+/*****************************************************************************
+ * Utility function: print PCI bus:dev.fn
+ *
+ * Parameters:
+ *   %ax : PCI bus:dev.fn to print
+ *   %ds:di : output buffer (or %di=0 to print to console)
+ * Returns:
+ *   %ds:di : next character in output buffer (if applicable)
+ *****************************************************************************
+ */
+	.section ".prefix.lib", "awx", @progbits
+	.code16
+	.globl	print_pci_busdevfn
+print_pci_busdevfn:
+	/* Preserve registers */
+	pushw	%ax
+	/* Print bus */
+	xchgb	%al, %ah
+	call	print_hex_byte
+	/* Print ":" */
+	movb	$( ':' ), %al
+	call	print_character
+	/* Print device */
+	movb	%ah, %al
+	shrb	$3, %al
+	call	print_hex_byte
+	/* Print "." */
+	movb	$( '.' ), %al
+	call	print_character
+	/* Print function */
+	movb	%ah, %al
+	andb	$0x07, %al
+	call	print_hex_nibble
+	/* Restore registers and return */
+	popw	%ax
+	ret
+	.size	print_pci_busdevfn, . - print_pci_busdevfn
+
+/*****************************************************************************
+ * Utility function: clear current line
+ *
+ * Parameters:
+ *   %ds:di : output buffer (or %di=0 to print to console)
+ * Returns:
+ *   %ds:di : next character in output buffer (if applicable)
+ *****************************************************************************
+ */
+	.section ".prefix.lib", "awx", @progbits
+	.code16
+	.globl	print_kill_line
+print_kill_line:
+	/* Preserve registers */
+	pushw	%ax
+	pushw	%cx
+	/* Print CR */
+	movb	$( '\r' ), %al
+	call	print_character
+	/* Print 79 spaces */
+	movb	$( ' ' ), %al
+	movw	$79, %cx
+1:	call	print_character
+	loop	1b
+	/* Print CR */
+	movb	$( '\r' ), %al
+	call	print_character
+	/* Restore registers and return */
+	popw	%cx
+	popw	%ax
+	ret
+	.size	print_kill_line, . - print_kill_line
+
+/****************************************************************************
+ * pm_call (real-mode near call)
+ *
+ * Call routine in 16-bit protected mode for access to extended memory
+ *
+ * Parameters:
+ *   %ax : address of routine to call in 16-bit protected mode
+ * Returns:
+ *   none
+ * Corrupts:
+ *   %ax
+ *
+ * The specified routine is called in 16-bit protected mode, with:
+ *
+ *   %cs : 16-bit code segment with base matching real-mode %cs
+ *   %ss : 16-bit data segment with base matching real-mode %ss
+ *   %ds,%es,%fs,%gs : 32-bit data segment with zero base and 4GB limit
+ *
+ ****************************************************************************
+ */
+
+#ifndef KEEP_IT_REAL
+
+	/* GDT for protected-mode calls */
+	.section ".prefix.lib", "awx", @progbits
+	.align 16
+pm_call_vars:
+gdt:
+gdt_limit:		.word gdt_length - 1
+gdt_base:		.long 0
+			.word 0 /* padding */
+pm_cs:		/* 16-bit protected-mode code segment */	
+	.equ    PM_CS, pm_cs - gdt
+	.word   0xffff, 0
+	.byte   0, 0x9b, 0x00, 0
+pm_ss:		/* 16-bit protected-mode stack segment */
+	.equ    PM_SS, pm_ss - gdt
+	.word   0xffff, 0
+	.byte   0, 0x93, 0x00, 0
+pm_ds:		/* 32-bit protected-mode flat data segment */
+	.equ    PM_DS, pm_ds - gdt
+	.word   0xffff, 0
+	.byte   0, 0x93, 0xcf, 0
+gdt_end:
+	.equ	gdt_length, . - gdt
+	.size	gdt, . - gdt
+
+	.section ".prefix.lib", "awx", @progbits
+	.align 16
+pm_saved_gdt:	
+	.long	0, 0
+	.size	pm_saved_gdt, . - pm_saved_gdt
+
+	.equ	pm_call_vars_size, . - pm_call_vars
+#define PM_CALL_VAR(x) ( -pm_call_vars_size + ( (x) - pm_call_vars ) )
+
+	.section ".prefix.lib", "awx", @progbits
+	.code16
+pm_call:
+	/* Preserve registers, flags, and RM return point */
+	pushw	%bp
+	movw	%sp, %bp
+	subw	$pm_call_vars_size, %sp
+	andw	$0xfff0, %sp
+	pushfl
+	pushw	%gs
+	pushw	%fs
+	pushw	%es
+	pushw	%ds
+	pushw	%ss
+	pushw	%cs
+	pushw	$99f
+
+	/* Set up local variable block, and preserve GDT */
+	pushw	%cx
+	pushw	%si
+	pushw	%di
+	pushw	%ss
+	popw	%es
+	movw	$pm_call_vars, %si
+	leaw	PM_CALL_VAR(pm_call_vars)(%bp), %di
+	movw	$pm_call_vars_size, %cx
+	cs rep movsb
+	popw	%di
+	popw	%si
+	popw	%cx
+	sgdt	PM_CALL_VAR(pm_saved_gdt)(%bp)
+
+	/* Set up GDT bases */
+	pushl	%eax
+	pushl	%edi
+	xorl	%eax, %eax
+	movw	%ss, %ax
+	shll	$4, %eax
+	movzwl	%bp, %edi
+	addr32 leal PM_CALL_VAR(gdt)(%eax, %edi), %eax
+	movl	%eax, PM_CALL_VAR(gdt_base)(%bp)
+	movw	%cs, %ax
+	movw	$PM_CALL_VAR(pm_cs), %di
+	call	set_seg_base
+	movw	%ss, %ax
+	movw	$PM_CALL_VAR(pm_ss), %di
+	call	set_seg_base
+	popl	%edi
+	popl	%eax
+
+	/* Switch CPU to protected mode and load up segment registers */
+	pushl	%eax
+	cli
+	data32 lgdt PM_CALL_VAR(gdt)(%bp)
+	movl	%cr0, %eax
+	orb	$CR0_PE, %al
+	movl	%eax, %cr0
+	ljmp	$PM_CS, $1f
+1:	movw	$PM_SS, %ax
+	movw	%ax, %ss
+	movw	$PM_DS, %ax
+	movw	%ax, %ds
+	movw	%ax, %es
+	movw	%ax, %fs
+	movw	%ax, %gs
+	popl	%eax
+
+	/* Call PM routine */
+	call	*%ax
+
+	/* Set real-mode segment limits on %ds, %es, %fs and %gs */
+	movw	%ss, %ax
+	movw	%ax, %ds
+	movw	%ax, %es
+	movw	%ax, %fs
+	movw	%ax, %gs
+
+	/* Return CPU to real mode */
+	movl	%cr0, %eax
+	andb	$0!CR0_PE, %al
+	movl	%eax, %cr0
+
+	/* Restore registers and flags */
+	lret	/* will ljmp to 99f */
+99:	popw	%ss
+	popw	%ds
+	popw	%es
+	popw	%fs
+	popw	%gs
+	data32 lgdt PM_CALL_VAR(pm_saved_gdt)(%bp)
+	popfl
+	movw	%bp, %sp
+	popw	%bp
+	ret
+	.size pm_call, . - pm_call
+
+set_seg_base:
+	rolw	$4, %ax
+	movw	%ax, 2(%bp,%di)
+	andw	$0xfff0, 2(%bp,%di)
+	movb	%al, 4(%bp,%di)
+	andb	$0x0f, 4(%bp,%di)
+	ret
+	.size set_seg_base, . - set_seg_base
+
+#endif /* KEEP_IT_REAL */
+
+/****************************************************************************
+ * copy_bytes (real-mode or 16-bit protected-mode near call)
+ *
+ * Copy bytes
+ *
+ * Parameters:
+ *   %ds:esi : source address
+ *   %es:edi : destination address
+ *   %ecx : length
+ * Returns:
+ *   %ds:esi : next source address
+ *   %es:edi : next destination address
+ * Corrupts:
+ *   None
+ ****************************************************************************
+ */
+	.section ".prefix.lib", "awx", @progbits
+	.code16
+copy_bytes:
+	pushl %ecx
+	rep addr32 movsb
+	popl %ecx
+	ret
+	.size copy_bytes, . - copy_bytes
+
+/****************************************************************************
+ * install_block (real-mode near call)
+ *
+ * Install block to specified address
+ *
+ * Parameters:
+ *   %esi : source physical address (must be a multiple of 16)
+ *   %edi : destination physical address (must be a multiple of 16)
+ *   %ecx : length of (decompressed) data
+ *   %edx : total length of block (including any uninitialised data portion)
+ * Returns:
+ *   %esi : next source physical address (will be a multiple of 16)
+ * Corrupts:
+ *   none
+ ****************************************************************************
+ */
+	.section ".prefix.lib", "awx", @progbits
+	.code16
+install_block:
+	
+#ifdef KEEP_IT_REAL
+
+	/* Preserve registers */
+	pushw	%ds
+	pushw	%es
+	pushl	%ecx
+	pushl	%edi
+	
+	/* Convert %esi and %edi to segment registers */
+	shrl	$4, %esi
+	movw	%si, %ds
+	xorw	%si, %si
+	shrl	$4, %edi
+	movw	%di, %es
+	xorw	%di, %di
+
+#else /* KEEP_IT_REAL */
+
+	/* Call self in protected mode */
+	pushw	%ax
+	movw	$1f, %ax
+	call	pm_call
+	popw	%ax
+	ret
+1:
+	/* Preserve registers */
+	pushl	%ecx
+	pushl	%edi
+	
+#endif /* KEEP_IT_REAL */
+
+	
+#if COMPRESS
+	/* Decompress source to destination */
+	call	decompress16
+#else
+	/* Copy source to destination */
+	call	copy_bytes
+#endif
+
+	/* Zero .bss portion */
+	negl	%ecx
+	addl	%edx, %ecx
+	pushw	%ax
+	xorw	%ax, %ax
+	rep addr32 stosb
+	popw	%ax
+
+	/* Round up %esi to start of next source block */
+	addl	$0xf, %esi
+	andl	$~0xf, %esi
+
+
+#ifdef KEEP_IT_REAL
+
+	/* Convert %ds:esi back to a physical address */
+	movzwl	%ds, %cx
+	shll	$4, %ecx
+	addl	%ecx, %esi
+
+	/* Restore registers */
+	popl	%edi
+	popl	%ecx
+	popw	%es
+	popw	%ds
+
+#else /* KEEP_IT_REAL */
+
+	/* Restore registers */
+	popl	%edi
+	popl	%ecx
+
+#endif
+
+	ret
+	.size install_block, . - install_block
+	
+/****************************************************************************
+ * alloc_basemem (real-mode near call)
+ *
+ * Allocate space for .text16 and .data16 from top of base memory.
+ * Memory is allocated using the BIOS free base memory counter at
+ * 0x40:13.
+ *
+ * Parameters: 
+ *   none
+ * Returns:
+ *   %ax : .text16 segment address
+ *   %bx : .data16 segment address
+ * Corrupts:
+ *   none
+ ****************************************************************************
+ */
+	.section ".prefix.lib", "awx", @progbits
+	.code16
+	.globl	alloc_basemem
+alloc_basemem:
+	/* Preserve registers */
+	pushw	%fs
+
+	/* FBMS => %ax as segment address */
+	pushw	$0x40
+	popw	%fs
+	movw	%fs:0x13, %ax
+	shlw	$6, %ax
+
+	/* Calculate .data16 segment address */
+	subw	$_data16_memsz_pgh, %ax
+	pushw	%ax
+
+	/* Calculate .text16 segment address */
+	subw	$_text16_memsz_pgh, %ax
+	pushw	%ax
+
+	/* Update FBMS */
+	shrw	$6, %ax
+	movw	%ax, %fs:0x13
+
+	/* Retrieve .text16 and .data16 segment addresses */
+	popw	%ax
+	popw	%bx
+
+	/* Restore registers and return */
+	popw	%fs
+	ret
+	.size alloc_basemem, . - alloc_basemem
+
+/****************************************************************************
+ * free_basemem (real-mode near call)
+ *
+ * Free space allocated with alloc_basemem.
+ *
+ * Parameters:
+ *   %ax : .text16 segment address
+ *   %bx : .data16 segment address
+ * Returns:
+ *   %ax : 0 if successfully freed
+ * Corrupts:
+ *   none
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+	.code16
+	.globl	free_basemem
+free_basemem:
+	/* Preserve registers */
+	pushw	%fs
+
+	/* Check FBMS counter */
+	pushw	%ax
+	shrw	$6, %ax
+	pushw	$0x40
+	popw	%fs
+	cmpw	%ax, %fs:0x13
+	popw	%ax
+	jne	1f
+
+	/* Check hooked interrupt count */
+	cmpw	$0, %cs:hooked_bios_interrupts
+	jne	1f
+
+	/* OK to free memory */
+	addw	$_text16_memsz_pgh, %ax
+	addw	$_data16_memsz_pgh, %ax
+	shrw	$6, %ax
+	movw	%ax, %fs:0x13
+	xorw	%ax, %ax
+
+1:	/* Restore registers and return */
+	popw	%fs
+	ret
+	.size free_basemem, . - free_basemem
+
+	.section ".text16.data", "aw", @progbits
+	.globl	hooked_bios_interrupts
+hooked_bios_interrupts:
+	.word	0
+	.size	hooked_bios_interrupts, . - hooked_bios_interrupts
+
+/****************************************************************************
+ * install (real-mode near call)
+ *
+ * Install all text and data segments.
+ *
+ * Parameters:
+ *   none
+ * Returns:
+ *   %ax  : .text16 segment address
+ *   %bx  : .data16 segment address
+ * Corrupts:
+ *   none
+ ****************************************************************************
+ */
+	.section ".prefix.lib", "awx", @progbits
+	.code16
+	.globl install
+install:
+	/* Preserve registers */
+	pushl	%esi
+	pushl	%edi
+	/* Allocate space for .text16 and .data16 */
+	call	alloc_basemem
+	/* Image source = %cs:0000 */
+	xorl	%esi, %esi
+	/* Image destination = HIGHMEM_LOADPOINT */
+	movl	$HIGHMEM_LOADPOINT, %edi
+	/* Install text and data segments */
+	call	install_prealloc
+	/* Restore registers and return */
+	popl	%edi
+	popl	%esi
+	ret
+	.size install, . - install
+
+/****************************************************************************
+ * install_prealloc (real-mode near call)
+ *
+ * Install all text and data segments.
+ *
+ * Parameters:
+ *   %ax  : .text16 segment address
+ *   %bx  : .data16 segment address
+ *   %esi : Image source physical address (or zero for %cs:0000)
+ *   %edi : Decompression temporary area physical address
+ * Corrupts:
+ *   none
+ ****************************************************************************
+ */
+	.section ".prefix.lib", "awx", @progbits
+	.code16
+	.globl install_prealloc
+install_prealloc:
+	/* Save registers */
+	pushal
+	pushw	%ds
+	pushw	%es
+
+	/* Sanity: clear the direction flag asap */
+	cld
+
+	/* Calculate physical address of payload (i.e. first source) */
+	testl	%esi, %esi
+	jnz	1f
+	movw	%cs, %si
+	shll	$4, %esi
+1:	addl	$_payload_lma, %esi
+
+	/* Install .text16 and .data16 */
+	pushl	%edi
+	movzwl	%ax, %edi
+	shll	$4, %edi
+	movl	$_text16_memsz, %ecx
+	movl	%ecx, %edx
+	call	install_block		/* .text16 */
+	movzwl	%bx, %edi
+	shll	$4, %edi
+	movl	$_data16_filesz, %ecx
+	movl	$_data16_memsz, %edx
+	call	install_block		/* .data16 */
+	popl	%edi
+
+	/* Set up %ds for access to .data16 */
+	movw	%bx, %ds
+
+#ifdef KEEP_IT_REAL
+	/* Initialise libkir */
+	movw	%ax, (init_libkir_vector+2)
+	lcall	*init_libkir_vector
+#else
+	/* Install .text and .data to temporary area in high memory,
+	 * prior to reading the E820 memory map and relocating
+	 * properly.
+	 */
+	movl	$_textdata_filesz, %ecx
+	movl	$_textdata_memsz, %edx
+	call	install_block
+
+	/* Initialise librm at current location */
+	movw	%ax, (init_librm_vector+2)
+	lcall	*init_librm_vector
+
+	/* Call relocate() to determine target address for relocation.
+	 * relocate() will return with %esi, %edi and %ecx set up
+	 * ready for the copy to the new location.
+	 */
+	movw	%ax, (prot_call_vector+2)
+	pushl	$relocate
+	lcall	*prot_call_vector
+	popl	%edx /* discard */
+
+	/* Copy code to new location */
+	pushl	%edi
+	pushw	%ax
+	movw	$copy_bytes, %ax
+	call	pm_call
+	popw	%ax
+	popl	%edi
+
+	/* Initialise librm at new location */
+	lcall	*init_librm_vector
+
+#endif
+	/* Restore registers */
+	popw	%es
+	popw	%ds
+	popal
+	ret
+	.size install_prealloc, . - install_prealloc
+
+	/* Vectors for far calls to .text16 functions */
+	.section ".data16", "aw", @progbits
+#ifdef KEEP_IT_REAL
+init_libkir_vector:
+	.word init_libkir
+	.word 0
+	.size init_libkir_vector, . - init_libkir_vector
+#else
+init_librm_vector:
+	.word init_librm
+	.word 0
+	.size init_librm_vector, . - init_librm_vector
+prot_call_vector:
+	.word prot_call
+	.word 0
+	.size prot_call_vector, . - prot_call_vector
+#endif
+
+/****************************************************************************
+ * uninstall (real-mode near call)
+ *
+ * Uninstall all text and data segments.
+ *
+ * Parameters:
+ *   %ax  : .text16 segment address
+ *   %bx  : .data16 segment address
+ * Returns:
+ *   none
+ * Corrupts:
+ *   none
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+	.code16
+	.globl uninstall
+uninstall:
+	call	free_basemem
+	ret
+	.size uninstall, . - uninstall
+
+
+
+	/* File split information for the compressor */
+#if COMPRESS
+	.section ".zinfo", "a", @progbits
+	.ascii	"COPY"
+	.long	_prefix_lma
+	.long	_prefix_filesz
+	.long	_max_align
+	.ascii	"PACK"
+	.long	_text16_lma
+	.long	_text16_filesz
+	.long	_max_align
+	.ascii	"PACK"
+	.long	_data16_lma
+	.long	_data16_filesz
+	.long	_max_align
+	.ascii	"PACK"
+	.long	_textdata_lma
+	.long	_textdata_filesz
+	.long	_max_align
+#else /* COMPRESS */
+	.section ".zinfo", "a", @progbits
+	.ascii	"COPY"
+	.long	_prefix_lma
+	.long	_filesz
+	.long	_max_align
+#endif /* COMPRESS */
diff --git a/gpxe/src/arch/i386/prefix/lkrnprefix.S b/gpxe/src/arch/i386/prefix/lkrnprefix.S
new file mode 100644
index 0000000..101d038
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/lkrnprefix.S
@@ -0,0 +1,216 @@
+/*
+	Copyright (C) 2000, Entity Cyber, Inc.
+
+	Authors: Gary Byers (gb@thinguin.org)
+		 Marty Connor (mdc@thinguin.org)
+
+	This software may be used and distributed according to the terms
+	of the GNU Public License (GPL), incorporated herein by reference.
+
+	Description:	
+
+	This is just a little bit of code and data that can get prepended
+	to a ROM image in order to allow bootloaders to load the result
+	as if it were a Linux kernel image.
+
+	A real Linux kernel image consists of a one-sector boot loader
+	(to load the image from a floppy disk), followed a few sectors
+	of setup code, followed by the kernel code itself.  There's
+	a table in the first sector (starting at offset 497) that indicates
+	how many sectors of setup code follow the first sector and which
+	contains some other parameters that aren't interesting in this
+	case.
+
+	When a bootloader loads the sectors that comprise a kernel image,
+	it doesn't execute the code in the first sector (since that code
+	would try to load the image from a floppy disk.)  The code in the
+	first sector below doesn't expect to get executed (and prints an
+	error message if it ever -is- executed.)
+
+	We don't require much in the way of setup code.  Historically, the
+	Linux kernel required at least 4 sectors of setup code.
+	Therefore, at least 4 sectors must be present even though we don't
+	use them.
+
+*/
+
+FILE_LICENCE ( GPL_ANY )
+
+#define	SETUPSECS 4		/* Minimal nr of setup-sectors */
+#define PREFIXSIZE ((SETUPSECS+1)*512)
+#define PREFIXPGH (PREFIXSIZE / 16 )
+#define	BOOTSEG  0x07C0		/* original address of boot-sector */
+#define	INITSEG  0x9000		/* we move boot here - out of the way */
+#define	SETUPSEG 0x9020		/* setup starts here */
+#define SYSSEG   0x1000		/* system loaded at 0x10000 (65536). */
+
+	.text
+	.code16
+	.arch i386
+	.org	0
+	.section ".prefix", "ax", @progbits
+/* 
+	This is a minimal boot sector.	If anyone tries to execute it (e.g., if
+	a .lilo file is dd'ed to a floppy), print an error message. 
+*/
+
+bootsector: 
+	jmp	$BOOTSEG, $1f	/* reload cs:ip to match relocation addr */
+1:
+	movw	$0x2000, %di		/*  0x2000 is arbitrary value >= length
+					    of bootsect + room for stack */
+
+	movw	$BOOTSEG, %ax
+	movw	%ax,%ds
+	movw	%ax,%es
+
+	cli
+	movw	%ax, %ss		/* put stack at BOOTSEG:0x2000. */
+	movw	%di,%sp
+	sti
+
+	movw	$why_end-why, %cx
+	movw	$why, %si
+
+	movw	$0x0007, %bx		/* page 0, attribute 7 (normal) */
+	movb	$0x0e, %ah		/* write char, tty mode */
+prloop: 
+	lodsb
+	int	$0x10
+	loop	prloop
+freeze: jmp	freeze
+
+why:	.ascii	"This image cannot be loaded from a floppy disk.\r\n"
+why_end: 
+
+
+/*
+	The following header is documented in the Linux source code at
+	Documentation/i386/boot.txt
+*/
+	.org	497
+setup_sects: 
+	.byte	SETUPSECS
+root_flags: 
+	.word	0
+syssize: 
+	.long	-PREFIXPGH
+
+	.section ".zinfo.fixup", "a", @progbits	/* Compressor fixups */
+	.ascii	"ADDL"
+	.long	syssize
+	.long	16
+	.long	0
+	.previous
+	
+ram_size: 
+	.word	0
+vid_mode: 
+	.word	0
+root_dev: 
+	.word	0
+boot_flag: 
+	.word	0xAA55
+jump:
+	/* Manually specify a two-byte jmp instruction here rather
+	 * than leaving it up to the assembler. */
+	.byte	0xeb
+	.byte	setup_code - header
+header:
+	.byte	'H', 'd', 'r', 'S'
+version:
+	.word	0x0207 /* 2.07 */
+realmode_swtch:
+	.long	0
+start_sys:
+	.word	0
+kernel_version:
+	.word	0
+type_of_loader:
+	.byte	0
+loadflags:
+	.byte	0
+setup_move_size:
+	.word	0
+code32_start:
+	.long	0
+ramdisk_image:
+	.long	0
+ramdisk_size:
+	.long	0
+bootsect_kludge:
+	.long	0
+heap_end_ptr:
+	.word	0
+pad1:
+	.word	0
+cmd_line_ptr:
+	.long	0
+initrd_addr_max:
+	/* We don't use an initrd but some bootloaders (e.g. SYSLINUX) have
+	 * been known to require this field.  Set the value to 2 GB.  This
+	 * value is also used by the Linux kernel. */
+	.long	0x7fffffff
+kernel_alignment:
+	.long	0
+relocatable_kernel:
+	.byte	0
+pad2:
+	.byte	0, 0, 0
+cmdline_size:
+	.long	0
+hardware_subarch:
+	.long	0
+hardware_subarch_data:
+	.byte	0, 0, 0, 0, 0, 0, 0, 0
+
+/*
+	We don't need to do too much setup.
+
+	This code gets loaded at SETUPSEG:0.  It wants to start
+	executing the image that's loaded at SYSSEG:0 and
+	whose entry point is SYSSEG:0.
+*/
+setup_code:
+	/* We expect to be contiguous in memory once loaded.  The Linux image
+	 * boot process requires that setup code is loaded separately from
+	 * "non-real code".  Since we don't need any information that's left
+	 * in the prefix, it doesn't matter: we just have to ensure that
+	 * %cs:0000 is where the start of the image *would* be.
+	 */
+	ljmp	$(SYSSEG-(PREFIXSIZE/16)), $run_gpxe
+
+
+	.org	PREFIXSIZE
+/*
+	We're now at the beginning of the kernel proper.
+ */
+run_gpxe:
+	/* Set up stack just below 0x7c00 */
+	xorw	%ax, %ax
+	movw	%ax, %ss
+	movw	$0x7c00, %sp
+
+	/* Install gPXE */
+	call	install
+
+	/* Set up real-mode stack */
+	movw	%bx, %ss
+	movw	$_estack16, %sp
+
+	/* Jump to .text16 segment */
+	pushw	%ax
+	pushw	$1f
+	lret
+	.section ".text16", "awx", @progbits
+1:
+	pushl	$main
+	pushw	%cs
+	call	prot_call
+	popl	%ecx /* discard */
+
+	/* Uninstall gPXE */
+	call	uninstall
+
+	/* Boot next device */
+	int $0x18
diff --git a/gpxe/src/arch/i386/prefix/mbr.S b/gpxe/src/arch/i386/prefix/mbr.S
new file mode 100644
index 0000000..adfe204
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/mbr.S
@@ -0,0 +1,13 @@
+	.text
+	.arch i386
+	.section ".prefix", "awx", @progbits
+	.code16
+	.org 0
+
+mbr:
+	movw	$exec_sector, %bp
+	jmp	find_active_partition
+exec_sector:
+	ljmp	$0x0000, $0x7c00
+
+#include "bootpart.S"
diff --git a/gpxe/src/arch/i386/prefix/nbiprefix.S b/gpxe/src/arch/i386/prefix/nbiprefix.S
new file mode 100644
index 0000000..607d80f
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/nbiprefix.S
@@ -0,0 +1,77 @@
+	.text
+	.arch i386
+	.code16
+	.section ".prefix", "ax", @progbits
+	.org 0
+
+nbi_header:
+	
+/*****************************************************************************
+ * NBI file header
+ *****************************************************************************
+ */
+file_header:
+	.long	0x1b031336	/* Signature */
+	.byte	0x04		/* 16 bytes header, no vendor info */
+	.byte	0
+	.byte	0
+	.byte	0		/* No flags */
+	.word	0x0000, 0x07c0	/* Load header to 0x07c0:0x0000 */
+	.word	entry, 0x07c0	/* Start execution at 0x07c0:entry */
+	.size	file_header, . - file_header
+
+/*****************************************************************************
+ * NBI segment header
+ *****************************************************************************
+ */
+segment_header:
+	.byte	0x04		/* 16 bytes header, no vendor info */
+	.byte	0
+	.byte	0
+	.byte	0x04		/* Last segment */
+	.long	0x00007e00
+imglen:	.long	-512
+memlen:	.long	-512
+	.size	segment_header, . - segment_header
+
+	.section ".zinfo.fixup", "a", @progbits	/* Compressor fixups */
+	.ascii	"ADDL"
+	.long	imglen
+	.long	1
+	.long	0
+	.ascii	"ADDL"
+	.long	memlen
+	.long	1
+	.long	0
+	.previous
+
+/*****************************************************************************
+ * NBI entry point
+ *****************************************************************************
+ */
+entry:
+	/* Install gPXE */
+	call	install
+
+	/* Jump to .text16 segment */
+	pushw	%ax
+	pushw	$1f
+	lret
+	.section ".text16", "awx", @progbits
+1:
+	pushl	$main
+	pushw	%cs
+	call	prot_call
+	popl	%ecx /* discard */
+
+	/* Uninstall gPXE */
+	call	uninstall
+
+	/* Reboot system */
+	int $0x19
+
+	.previous
+	.size	entry, . - entry
+
+nbi_header_end:
+	.org 512
diff --git a/gpxe/src/arch/i386/prefix/nullprefix.S b/gpxe/src/arch/i386/prefix/nullprefix.S
new file mode 100644
index 0000000..032d41e
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/nullprefix.S
@@ -0,0 +1,13 @@
+	.org	0
+	.text
+	.arch i386
+
+	.section ".prefix", "ax", @progbits
+	.code16
+_prefix:
+
+	.section ".text16", "ax", @progbits
+prefix_exit:
+
+prefix_exit_end:
+	.previous
diff --git a/gpxe/src/arch/i386/prefix/pxeprefix.S b/gpxe/src/arch/i386/prefix/pxeprefix.S
new file mode 100644
index 0000000..e728c48
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/pxeprefix.S
@@ -0,0 +1,761 @@
+FILE_LICENCE ( GPL2_OR_LATER )
+
+#define PXENV_UNDI_SHUTDOWN		0x0005
+#define	PXENV_UNDI_GET_NIC_TYPE		0x0012
+#define PXENV_UNDI_GET_IFACE_INFO	0x0013
+#define	PXENV_STOP_UNDI			0x0015
+#define PXENV_UNLOAD_STACK		0x0070
+
+#define PXE_HACK_EB54			0x0001
+
+	.text
+	.arch i386
+	.org 0
+	.code16
+
+#include <undi.h>
+
+#define STACK_MAGIC ( 'L' + ( 'R' << 8 ) + ( 'E' << 16 ) + ( 'T' << 24 ) )
+#define EB_MAGIC_1 ( 'E' + ( 't' << 8 ) + ( 'h' << 16 ) + ( 'e' << 24 ) )
+#define EB_MAGIC_2 ( 'r' + ( 'b' << 8 ) + ( 'o' << 16 ) + ( 'o' << 24 ) )
+
+/*****************************************************************************
+ * Entry point:	set operating context, print welcome message
+ *****************************************************************************
+ */
+	.section ".prefix", "ax", @progbits
+	jmp	$0x7c0, $1f
+1:
+	/* Preserve registers for possible return to PXE */
+	pushfl
+	pushal
+	pushw	%gs
+	pushw	%fs
+	pushw	%es
+	pushw	%ds
+
+	/* Store magic word on PXE stack and remember PXE %ss:esp */
+	pushl	$STACK_MAGIC
+	movw	%ss, %cs:pxe_ss
+	movl	%esp, %cs:pxe_esp
+
+	/* Set up segments */
+	movw	%cs, %ax
+	movw	%ax, %ds
+	movw	$0x40, %ax		/* BIOS data segment access */
+	movw	%ax, %fs
+	/* Set up stack just below 0x7c00 */
+	xorw	%ax, %ax
+	movw	%ax, %ss
+	movl	$0x7c00, %esp
+	/* Clear direction flag, for the sake of sanity */
+	cld
+	/* Print welcome message */
+	movw	$10f, %si
+	xorw	%di, %di
+	call	print_message
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	"PXE->EB:"
+	.previous
+
+/*****************************************************************************
+ * Find us a usable !PXE or PXENV+ entry point
+ *****************************************************************************
+ */
+detect_pxe:
+	/* Plan A: !PXE pointer from the stack */
+	lgsl	pxe_esp, %ebp		/* %gs:%bp -> original stack */
+	lesw	%gs:52(%bp), %bx
+	call	is_valid_ppxe
+	je	have_ppxe
+
+	/* Plan B: PXENV+ pointer from initial ES:BX */
+	movw	%gs:32(%bp),%bx
+	movw	%gs:8(%bp),%es
+	call	is_valid_pxenv
+	je	have_pxenv
+
+	/* Plan C: PXENV+ structure via INT 1Ah */
+	movw	$0x5650, %ax
+	int	$0x1a
+	jc	1f
+	cmpw	$0x564e, %ax
+	jne	1f
+	call	is_valid_pxenv
+	je	have_pxenv
+1:
+	/* Plan D: scan base memory for !PXE */
+	call	memory_scan_ppxe
+	je	have_ppxe
+
+	/* Plan E: scan base memory for PXENV+ */
+	call	memory_scan_pxenv
+	jne	stack_not_found
+	
+have_pxenv:
+	movw	%bx, pxenv_offset
+	movw	%es, pxenv_segment
+
+	cmpw	$0x201, %es:6(%bx)	/* API version >= 2.01 */
+	jb	1f
+	cmpb	$0x2c, %es:8(%bx)	/* ... and structure long enough */
+	jb	2f
+
+	lesw	%es:0x28(%bx), %bx	/* Find !PXE from PXENV+ */
+	call	is_valid_ppxe
+	je	have_ppxe
+2:
+	call	memory_scan_ppxe	/* We are *supposed* to have !PXE... */
+	je	have_ppxe
+1:
+	lesw	pxenv_segoff, %bx	/* Nope, we're stuck with PXENV+ */
+
+	/* Record entry point and UNDI segments */
+	pushl	%es:0x0a(%bx)		/* Entry point */
+	pushw	%es:0x24(%bx)		/* UNDI code segment */
+	pushw	%es:0x26(%bx)		/* UNDI code size */
+	pushw	%es:0x20(%bx)		/* UNDI data segment */
+	pushw	%es:0x22(%bx)		/* UNDI data size */
+
+	/* Print "PXENV+ at <address>" */
+	movw	$10f, %si
+	jmp	check_have_stack
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	" PXENV+ at "
+	.previous
+
+have_ppxe:
+	movw	%bx, ppxe_offset
+	movw	%es, ppxe_segment
+	
+	pushl	%es:0x10(%bx)		/* Entry point */
+	pushw	%es:0x30(%bx)		/* UNDI code segment */
+	pushw	%es:0x36(%bx)		/* UNDI code size */
+	pushw	%es:0x28(%bx)		/* UNDI data segment */
+	pushw	%es:0x2e(%bx)		/* UNDI data size */
+
+	/* Print "!PXE at <address>" */
+	movw	$10f, %si
+	jmp	check_have_stack
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	" !PXE at "
+	.previous
+
+is_valid_ppxe:
+	cmpl	$0x45585021, %es:(%bx)
+	jne	1f
+	movzbw	%es:4(%bx), %cx
+	cmpw	$0x58, %cx
+	jae	is_valid_checksum
+1:
+	ret
+	
+is_valid_pxenv:
+	cmpl	$0x4e455850, %es:(%bx)
+	jne	1b
+	cmpw	$0x2b56, %es:4(%bx)
+	jne	1b
+	movzbw	%es:8(%bx), %cx
+	cmpw	$0x28, %cx
+	jb	1b
+	
+is_valid_checksum:
+	pushw	%ax
+	movw	%bx, %si
+	xorw	%ax, %ax
+2:
+	es lodsb
+	addb	%al, %ah
+	loopw	2b
+	popw	%ax
+	ret
+
+memory_scan_ppxe:
+	movw	$is_valid_ppxe, %dx
+	jmp	memory_scan_common
+
+memory_scan_pxenv:
+	movw	$is_valid_pxenv, %dx
+
+memory_scan_common:
+	movw	%fs:(0x13), %ax
+	shlw	$6, %ax
+	decw	%ax
+1:	incw	%ax
+	cmpw	$( 0xa000 - 1 ), %ax
+	ja	2f
+	movw	%ax, %es
+	xorw	%bx, %bx
+	call	*%dx
+	jne	1b
+2:	ret
+	
+/*****************************************************************************
+ * Sanity check: we must have an entry point
+ *****************************************************************************
+ */
+check_have_stack:
+	/* Save common values pushed onto the stack */
+	popl	undi_data_segoff
+	popl	undi_code_segoff
+	popl	entry_segoff
+
+	/* Print have !PXE/PXENV+ message; structure pointer in %es:%bx */
+	call	print_message
+	call	print_segoff
+	movb	$( ',' ), %al
+	call	print_character
+
+	/* Check for entry point */
+	movl	entry_segoff, %eax
+	testl	%eax, %eax
+	jnz	99f
+	/* No entry point: print message and skip everything else */
+stack_not_found:
+	movw	$10f, %si
+	call	print_message
+	jmp	finished
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	" No PXE stack found!\n"
+	.previous
+99:	
+
+/*****************************************************************************
+ * Calculate base memory usage by UNDI
+ *****************************************************************************
+ */
+find_undi_basemem_usage:
+	movw	undi_code_segment, %ax
+	movw	undi_code_size, %bx
+	movw	undi_data_segment, %cx
+	movw	undi_data_size, %dx
+	cmpw	%ax, %cx
+	ja	1f
+	xchgw	%ax, %cx
+	xchgw	%bx, %dx
+1:	/* %ax:%bx now describes the lower region, %cx:%dx the higher */
+	shrw	$6, %ax			/* Round down to nearest kB */
+	movw	%ax, undi_fbms_start
+	addw	$0x0f, %dx		/* Round up to next segment */
+	shrw	$4, %dx
+	addw	%dx, %cx
+	addw	$((1024 / 16) - 1), %cx	/* Round up to next kB */
+	shrw	$6, %cx
+	movw	%cx, undi_fbms_end
+
+/*****************************************************************************
+ * Print information about detected PXE stack
+ *****************************************************************************
+ */
+print_structure_information:
+	/* Print entry point */
+	movw	$10f, %si
+	call	print_message
+	les	entry_segoff, %bx
+	call	print_segoff
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	" entry point at "
+	.previous
+	/* Print UNDI code segment */
+	movw	$10f, %si
+	call	print_message
+	les	undi_code_segoff, %bx
+	call	print_segoff
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	"\n         UNDI code segment "
+	.previous
+	/* Print UNDI data segment */
+	movw	$10f, %si
+	call	print_message
+	les	undi_data_segoff, %bx
+	call	print_segoff
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	", data segment "
+	.previous
+	/* Print UNDI memory usage */
+	movw	$10f, %si
+	call	print_message
+	movw	undi_fbms_start, %ax
+	call	print_word
+	movb	$( '-' ), %al
+	call	print_character
+	movw	undi_fbms_end, %ax
+	call	print_word
+	movw	$20f, %si
+	call	print_message
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	" ("
+20:	.asciz	"kB)\n"
+	.previous
+
+/*****************************************************************************
+ * Determine physical device
+ *****************************************************************************
+ */
+get_physical_device:
+	/* Issue PXENV_UNDI_GET_NIC_TYPE */
+	movw	$PXENV_UNDI_GET_NIC_TYPE, %bx
+	call	pxe_call
+	jnc	1f
+	call	print_pxe_error
+	jmp	no_physical_device
+1:	/* Determine physical device type */
+	movb	( pxe_parameter_structure + 0x02 ), %al
+	cmpb	$2, %al
+	je	pci_physical_device
+	jmp	no_physical_device
+
+pci_physical_device:
+	/* Record PCI bus:dev.fn and vendor/device IDs */
+	movl	( pxe_parameter_structure + 0x03 ), %eax
+	movl	%eax, pci_vendor
+	movw	( pxe_parameter_structure + 0x0b ), %ax
+	movw	%ax, pci_busdevfn
+	movw	$10f, %si
+	call	print_message
+	call	print_pci_busdevfn
+	jmp	99f
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	"         UNDI device is PCI "
+	.previous
+
+no_physical_device:
+	/* No device found, or device type not understood */
+	movw	$10f, %si
+	call	print_message
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	"         Unable to determine UNDI physical device"
+	.previous
+
+99:
+
+/*****************************************************************************
+ * Determine interface type
+ *****************************************************************************
+ */
+get_iface_type:
+	/* Issue PXENV_UNDI_GET_IFACE_INFO */
+	movw	$PXENV_UNDI_GET_IFACE_INFO, %bx
+	call	pxe_call
+	jnc	1f
+	call	print_pxe_error
+	jmp	99f
+1:	/* Print interface type */
+	movw	$10f, %si
+	call	print_message
+	leaw	( pxe_parameter_structure + 0x02 ), %si
+	call	print_message
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	", type "
+	.previous
+	/* Check for "Etherboot" interface type */
+	cmpl	$EB_MAGIC_1, ( pxe_parameter_structure + 0x02 )
+	jne	99f
+	cmpl	$EB_MAGIC_2, ( pxe_parameter_structure + 0x06 )
+	jne	99f
+	movw	$10f, %si
+	call	print_message
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	" (workaround enabled)"
+	.previous
+	/* Flag Etherboot workarounds as required */
+	orw	$PXE_HACK_EB54, pxe_hacks
+
+99:	movb	$0x0a, %al
+	call	print_character
+
+/*****************************************************************************
+ * Leave NIC in a safe state
+ *****************************************************************************
+ */
+#ifndef PXELOADER_KEEP_PXE
+shutdown_nic:
+	/* Issue PXENV_UNDI_SHUTDOWN */
+	movw	$PXENV_UNDI_SHUTDOWN, %bx
+	call	pxe_call
+	jnc	1f
+	call	print_pxe_error
+1:
+unload_base_code:
+	/* Etherboot treats PXENV_UNLOAD_STACK as PXENV_STOP_UNDI, so
+	 * we must not issue this call if the underlying stack is
+	 * Etherboot and we were not intending to issue a PXENV_STOP_UNDI.
+	 */
+#ifdef PXELOADER_KEEP_UNDI
+	testw	$PXE_HACK_EB54, pxe_hacks
+	jnz	99f
+#endif /* PXELOADER_KEEP_UNDI */
+	/* Issue PXENV_UNLOAD_STACK */
+	movw	$PXENV_UNLOAD_STACK, %bx
+	call	pxe_call
+	jnc	1f
+	call	print_pxe_error
+	jmp	99f
+1:	/* Free base memory used by PXE base code */
+	movw	undi_fbms_start, %ax
+	movw	%fs:(0x13), %bx
+	call	free_basemem
+99:
+	andw	$~( UNDI_FL_INITIALIZED | UNDI_FL_KEEP_ALL ), flags
+#endif /* PXELOADER_KEEP_PXE */
+
+/*****************************************************************************
+ * Unload UNDI driver
+ *****************************************************************************
+ */
+#ifndef PXELOADER_KEEP_UNDI
+unload_undi:
+	/* Issue PXENV_STOP_UNDI */
+	movw	$PXENV_STOP_UNDI, %bx
+	call	pxe_call
+	jnc	1f
+	call	print_pxe_error
+	jmp	99f
+1:	/* Free base memory used by UNDI */
+	movw	undi_fbms_end, %ax
+	movw	undi_fbms_start, %bx
+	call	free_basemem
+	/* Clear UNDI_FL_STARTED */
+	andw	$~UNDI_FL_STARTED, flags
+99:	
+#endif /* PXELOADER_KEEP_UNDI */
+
+/*****************************************************************************
+ * Print remaining free base memory
+ *****************************************************************************
+ */
+print_free_basemem:
+	movw	$10f, %si
+	call	print_message
+	movw	%fs:(0x13), %ax
+	call	print_word
+	movw	$20f, %si
+	call	print_message
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	"         "
+20:	.asciz	"kB free base memory after PXE unload\n"
+	.previous
+	
+/*****************************************************************************
+ * Exit point
+ *****************************************************************************
+ */	
+finished:
+	jmp	run_gpxe
+
+/*****************************************************************************
+ * Subroutine: print segment:offset address
+ *
+ * Parameters:
+ *   %es:%bx : segment:offset address to print
+ *   %ds:di : output buffer (or %di=0 to print to console)
+ * Returns:
+ *   %ds:di : next character in output buffer (if applicable)
+ *****************************************************************************
+ */
+print_segoff:
+	/* Preserve registers */
+	pushw	%ax
+	/* Print "<segment>:offset" */
+	movw	%es, %ax
+	call	print_hex_word
+	movb	$( ':' ), %al
+	call	print_character
+	movw	%bx, %ax
+	call	print_hex_word
+	/* Restore registers and return */
+	popw	%ax
+	ret
+
+/*****************************************************************************
+ * Subroutine: print decimal word
+ *
+ * Parameters:
+ *   %ax : word to print
+ *   %ds:di : output buffer (or %di=0 to print to console)
+ * Returns:
+ *   %ds:di : next character in output buffer (if applicable)
+ *****************************************************************************
+ */
+print_word:
+	/* Preserve registers */
+	pushw	%ax
+	pushw	%bx
+	pushw	%cx
+	pushw	%dx
+	/* Build up digit sequence on stack */
+	movw	$10, %bx
+	xorw	%cx, %cx
+1:	xorw	%dx, %dx
+	divw	%bx, %ax
+	pushw	%dx
+	incw	%cx
+	testw	%ax, %ax
+	jnz	1b
+	/* Print digit sequence */
+1:	popw	%ax
+	call	print_hex_nibble
+	loop	1b
+	/* Restore registers and return */
+	popw	%dx
+	popw	%cx
+	popw	%bx
+	popw	%ax
+	ret
+	
+/*****************************************************************************
+ * Subroutine: zero 1kB block of base memory
+ *
+ * Parameters:
+ *   %bx : block to zero (in kB)
+ * Returns:
+ *   Nothing
+ *****************************************************************************
+ */
+zero_kb:
+	/* Preserve registers */
+	pushw	%ax
+	pushw	%cx
+	pushw	%di
+	pushw	%es
+	/* Zero block */
+	movw	%bx, %ax
+	shlw	$6, %ax
+	movw	%ax, %es
+	movw	$0x400, %cx
+	xorw	%di, %di
+	xorw	%ax, %ax
+	rep stosb
+	/* Restore registers and return */
+	popw	%es
+	popw	%di
+	popw	%cx
+	popw	%ax
+	ret
+	
+/*****************************************************************************
+ * Subroutine: free and zero base memory
+ *
+ * Parameters:
+ *   %ax : Desired new free base memory counter (in kB)
+ *   %bx : Expected current free base memory counter (in kB)
+ *   %fs : BIOS data segment (0x40)
+ * Returns:
+ *   None
+ *
+ * The base memory from %bx kB to %ax kB is unconditionally zeroed.
+ * It will be freed if and only if the expected current free base
+ * memory counter (%bx) matches the actual current free base memory
+ * counter in 0x40:0x13; if this does not match then the memory will
+ * be leaked.
+ *****************************************************************************
+ */
+free_basemem:
+	/* Zero base memory */
+	pushw	%bx
+1:	cmpw	%bx, %ax
+	je	2f
+	call	zero_kb
+	incw	%bx
+	jmp	1b
+2:	popw	%bx
+	/* Free base memory */
+	cmpw	%fs:(0x13), %bx		/* Update FBMS only if "old" value  */
+	jne	1f			/* is correct			    */
+1:	movw	%ax, %fs:(0x13)
+	ret
+
+/*****************************************************************************
+ * Subroutine: make a PXE API call.  Works with either !PXE or PXENV+ API.
+ *
+ * Parameters:
+ *   %bx : PXE API call number
+ *   %ds:pxe_parameter_structure : Parameters for PXE API call
+ * Returns:
+ *   %ax : PXE status code (not exit code)
+ *   CF set if %ax is non-zero
+ *****************************************************************************
+ */
+pxe_call:
+	/* Preserve registers */
+	pushw	%di
+	pushw	%es
+	/* Set up registers for PXENV+ API.  %bx already set up */
+	pushw	%ds
+	popw	%es
+	movw	$pxe_parameter_structure, %di
+	/* Set up stack for !PXE API */
+	pushw   %es
+	pushw	%di
+	pushw	%bx
+	/* Make the API call */
+	lcall	*entry_segoff
+	/* Reset the stack */
+	addw	$6, %sp
+	movw	pxe_parameter_structure, %ax
+	clc
+	testw	%ax, %ax
+	jz	1f
+	stc
+1:	/* Clear direction flag, for the sake of sanity */
+	cld
+	/* Restore registers and return */
+	popw	%es
+	popw	%di
+	ret
+
+/*****************************************************************************
+ * Subroutine: print PXE API call error message
+ *
+ * Parameters:
+ *   %ax : PXE status code
+ *   %bx : PXE API call number
+ * Returns:
+ *   Nothing
+ *****************************************************************************
+ */
+print_pxe_error:
+	pushw	%si
+	movw	$10f, %si
+	call	print_message
+	xchgw	%ax, %bx
+	call	print_hex_word
+	movw	$20f, %si
+	call	print_message
+	xchgw	%ax, %bx
+	call	print_hex_word
+	movw	$30f, %si
+	call	print_message
+	popw	%si
+	ret
+	.section ".prefix.data", "aw", @progbits
+10:	.asciz	"         UNDI API call "
+20:	.asciz	" failed: status code "
+30:	.asciz	"\n"
+	.previous
+
+/*****************************************************************************
+ * PXE data structures
+ *****************************************************************************
+ */
+	.section ".prefix.data"
+
+pxe_esp:		.long 0
+pxe_ss:			.word 0
+
+pxe_parameter_structure: .fill 64
+
+undi_code_segoff:
+undi_code_size:		.word 0
+undi_code_segment:	.word 0
+
+undi_data_segoff:
+undi_data_size:		.word 0
+undi_data_segment:	.word 0
+
+pxe_hacks:		.word 0
+
+/* The following fields are part of a struct undi_device */
+
+undi_device:
+
+pxenv_segoff:
+pxenv_offset:		.word 0
+pxenv_segment:		.word 0
+
+ppxe_segoff:
+ppxe_offset:		.word 0
+ppxe_segment:		.word 0
+	
+entry_segoff:
+entry_offset:		.word 0
+entry_segment:		.word 0
+
+undi_fbms_start:	.word 0
+undi_fbms_end:		.word 0
+
+pci_busdevfn:		.word UNDI_NO_PCI_BUSDEVFN
+isapnp_csn:		.word UNDI_NO_ISAPNP_CSN
+isapnp_read_port:	.word UNDI_NO_ISAPNP_READ_PORT
+
+pci_vendor:		.word 0
+pci_device:		.word 0
+flags:
+	.word ( UNDI_FL_INITIALIZED | UNDI_FL_STARTED | UNDI_FL_KEEP_ALL )
+
+	.equ undi_device_size, ( . - undi_device )
+
+/*****************************************************************************
+ * Run gPXE main code
+ *****************************************************************************
+ */
+	.section ".prefix"
+run_gpxe:
+	/* Install gPXE */
+	call	install
+
+	/* Set up real-mode stack */
+	movw	%bx, %ss
+	movw	$_estack16, %sp
+
+#ifdef PXELOADER_KEEP_UNDI
+	/* Copy our undi_device structure to the preloaded_undi variable */
+	movw	%bx, %es
+	movw	$preloaded_undi, %di
+	movw	$undi_device, %si
+	movw	$undi_device_size, %cx
+	rep movsb
+#endif
+
+	/* Retrieve PXE %ss:esp */
+	movw	pxe_ss,	%di
+	movl	pxe_esp, %ebp
+
+	/* Jump to .text16 segment with %ds pointing to .data16 */
+	movw	%bx, %ds
+	pushw	%ax
+	pushw	$1f
+	lret
+	.section ".text16", "ax", @progbits
+1:
+	/* Update the exit hook */
+	movw	%cs,pxe_exit_hook+2
+	push	%ax
+	mov	$2f,%ax
+	mov	%ax,pxe_exit_hook
+	pop	%ax
+
+	/* Run main program */
+	pushl	$main
+	pushw	%cs
+	call	prot_call
+	popl	%ecx /* discard */
+
+	/* Uninstall gPXE */
+	call	uninstall
+
+	/* Restore PXE stack */
+	movw	%di, %ss
+	movl	%ebp, %esp
+
+	/* Jump to hook if applicable */
+	ljmpw	*pxe_exit_hook
+
+2:	/* Check PXE stack magic */
+	popl	%eax
+	cmpl	$STACK_MAGIC, %eax
+	jne	1f
+
+	/* PXE stack OK: return to caller */
+	popw	%ds
+	popw	%es
+	popw	%fs
+	popw	%gs
+	popal
+	popfl
+	xorw	%ax, %ax	/* Return success */
+	lret
+
+1:	/* PXE stack corrupt or removed: use INT 18 */
+	int	$0x18
+	.previous
diff --git a/gpxe/src/arch/i386/prefix/romprefix.S b/gpxe/src/arch/i386/prefix/romprefix.S
new file mode 100644
index 0000000..02e5497
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/romprefix.S
@@ -0,0 +1,1079 @@
+/* At entry, the processor is in 16 bit real mode and the code is being
+ * executed from an address it was not linked to. Code must be pic and
+ * 32 bit sensitive until things are fixed up.
+ *
+ * Also be very careful as the stack is at the rear end of the interrupt
+ * table so using a noticeable amount of stack space is a no-no.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+#include <config/general.h>
+
+#define PNP_SIGNATURE ( '$' + ( 'P' << 8 ) + ( 'n' << 16 ) + ( 'P' << 24 ) )
+#define PMM_SIGNATURE ( '$' + ( 'P' << 8 ) + ( 'M' << 16 ) + ( 'M' << 24 ) )
+#define PCI_SIGNATURE ( 'P' + ( 'C' << 8 ) + ( 'I' << 16 ) + ( ' ' << 24 ) )
+#define STACK_MAGIC ( 'L' + ( 'R' << 8 ) + ( 'E' << 16 ) + ( 'T' << 24 ) )
+#define PNP_GET_BBS_VERSION 0x60
+#define PMM_ALLOCATE 0x0000
+#define PMM_DEALLOCATE 0x0002
+
+/* ROM banner timeout.  Based on the configurable BANNER_TIMEOUT in
+ * config.h, but converted to a number of (18Hz) timer ticks, and
+ * doubled to allow for BIOSes that switch video modes immediately
+ * beforehand, so rendering the message almost invisible to the user.
+ */
+#define ROM_BANNER_TIMEOUT ( 2 * ( 18 * BANNER_TIMEOUT ) / 10 )
+
+/* We can load a ROM in two ways: have the BIOS load all of it (.rom prefix)
+ * or have the BIOS load a stub that loads the rest using PCI (.xrom prefix).
+ * The latter is not as widely supported, but allows the use of large ROMs
+ * on some systems with crowded option ROM space.
+ */
+
+#ifdef LOAD_ROM_FROM_PCI
+#define ROM_SIZE_VALUE	_prefix_filesz_sect /* Amount to load in BIOS */
+#else
+#define ROM_SIZE_VALUE	0		/* Load amount (before compr. fixup) */
+#endif
+
+
+	.text
+	.code16
+	.arch i386
+	.section ".prefix", "ax", @progbits
+	
+	.org	0x00
+romheader:
+	.word	0xAA55			/* BIOS extension signature */
+romheader_size:	.byte ROM_SIZE_VALUE	/* Size in 512-byte blocks */
+	jmp	init			/* Initialisation vector */
+checksum:
+	.byte	0, 0
+real_size:
+	.word	0
+	.org	0x16
+	.word	undiheader
+	.org	0x18
+	.word	pciheader
+	.org	0x1a
+	.word	pnpheader
+	.size romheader, . - romheader
+
+	.section ".zinfo.fixup", "a", @progbits	/* Compressor fixups */
+#ifndef LOAD_ROM_FROM_PCI
+	.ascii	"ADDB"
+	.long	romheader_size
+	.long	512
+	.long	0
+#endif
+	.ascii	"ADDB"
+	.long	real_size
+	.long	512
+	.long	0
+	.previous
+
+pciheader:
+	.ascii	"PCIR"			/* Signature */
+	.word	pci_vendor_id		/* Vendor identification */ 
+	.word	pci_device_id		/* Device identification */
+	.word	0x0000			/* Device list pointer */
+	.word	pciheader_len		/* PCI data structure length */
+	.byte	0x03			/* PCI data structure revision */
+	.byte	0x02, 0x00, 0x00	/* Class code */
+pciheader_image_length:
+	.word	ROM_SIZE_VALUE		/* Image length */
+	.word	0x0001			/* Revision level */
+	.byte	0x00			/* Code type */
+	.byte	0x80			/* Last image indicator */
+pciheader_runtime_length:
+	.word	ROM_SIZE_VALUE		/* Maximum run-time image length */
+	.word	0x0000			/* Configuration utility code header */
+	.word	0x0000			/* DMTF CLP entry point */
+	.equ pciheader_len, . - pciheader
+	.size pciheader, . - pciheader
+
+#ifndef LOAD_ROM_FROM_PCI
+	.section ".zinfo.fixup", "a", @progbits	/* Compressor fixups */
+	.ascii	"ADDW"
+	.long	pciheader_image_length
+	.long	512
+	.long	0
+	.ascii	"ADDW"
+	.long	pciheader_runtime_length
+	.long	512
+	.long	0
+	.previous
+#endif
+
+pnpheader:
+	.ascii	"$PnP"			/* Signature */
+	.byte	0x01			/* Structure revision */
+	.byte	( pnpheader_len	/ 16 )	/* Length (in 16 byte increments) */
+	.word	0x0000			/* Offset of next header */
+	.byte	0x00			/* Reserved */
+	.byte	0x00			/* Checksum */
+	.long	0x00000000		/* Device identifier */
+	.word	mfgstr			/* Manufacturer string */
+	.word	prodstr			/* Product name */
+	.byte	0x02			/* Device base type code */
+	.byte	0x00			/* Device sub-type code */
+	.byte	0x00			/* Device interface type code */
+	.byte	0xf4			/* Device indicator */
+	.word	0x0000			/* Boot connection vector */
+	.word	0x0000			/* Disconnect vector */
+	.word	bev_entry		/* Boot execution vector */
+	.word	0x0000			/* Reserved */
+	.word	0x0000			/* Static resource information vector*/
+	.equ pnpheader_len, . - pnpheader
+	.size pnpheader, . - pnpheader
+
+/* Manufacturer string */
+mfgstr:
+	.asciz	"http://etherboot.org"
+	.size mfgstr, . - mfgstr
+
+/* Product string
+ *
+ * Defaults to PRODUCT_SHORT_NAME.  If the ROM image is writable at
+ * initialisation time, it will be filled in to include the PCI
+ * bus:dev.fn number of the card as well.
+ */
+prodstr:
+	.ascii	PRODUCT_SHORT_NAME
+prodstr_separator:
+	.byte	0
+	.ascii	"(PCI "
+prodstr_pci_id:
+	.asciz	"xx:xx.x)"		/* Filled in by init code */
+	.size prodstr, . - prodstr
+
+	.globl	undiheader	
+	.weak	undiloader
+undiheader:
+	.ascii	"UNDI"			/* Signature */
+	.byte	undiheader_len		/* Length of structure */
+	.byte	0			/* Checksum */
+	.byte	0			/* Structure revision */
+	.byte	0,1,2			/* PXE version: 2.1.0 */
+	.word	undiloader		/* Offset to loader routine */
+	.word	_data16_memsz		/* Stack segment size */
+	.word	_data16_memsz		/* Data segment size */
+	.word	_text16_memsz		/* Code segment size */
+	.ascii	"PCIR"			/* Bus type */
+	.equ undiheader_len, . - undiheader
+	.size undiheader, . - undiheader
+
+/* Initialisation (called once during POST)
+ *
+ * Determine whether or not this is a PnP system via a signature
+ * check.  If it is PnP, return to the PnP BIOS indicating that we are
+ * a boot-capable device; the BIOS will call our boot execution vector
+ * if it wants to boot us.  If it is not PnP, hook INT 19.
+ */
+init:
+	/* Preserve registers, clear direction flag, set %ds=%cs */
+	pushaw
+	pushw	%ds
+	pushw	%es
+	pushw	%fs
+	pushw	%gs
+	cld
+	pushw	%cs
+	popw	%ds
+
+	/* Shuffle some registers around.  We need %di available for
+	 * the print_xxx functions, and in a register that's
+	 * addressable from %es, so shuffle as follows:
+	 *
+	 *    %di (pointer to PnP structure) => %bx
+	 *    %bx (runtime segment address, for PCI 3.0) => %gs
+	 */
+	movw	%bx, %gs
+	movw	%di, %bx
+
+	/* Print message as early as possible */
+	movw	$init_message, %si
+	xorw	%di, %di
+	call	print_message
+	call	print_pci_busdevfn
+
+#ifdef LOAD_ROM_FROM_PCI
+	/* Save PCI bus:dev.fn for later use */
+	movw	%ax, pci_busdevfn
+#endif
+
+	/* Fill in product name string, if possible */
+	movw	$prodstr_pci_id, %di
+	call	print_pci_busdevfn
+	movb	$( ' ' ), prodstr_separator
+
+	/* Print segment address */
+	movb	$( ' ' ), %al
+	xorw	%di, %di
+	call	print_character
+	movw	%cs, %ax
+	call	print_hex_word
+
+	/* Check for PCI BIOS version */
+	pushl	%ebx
+	pushl	%edx
+	pushl	%edi
+	stc
+	movw	$0xb101, %ax
+	int	$0x1a
+	jc	no_pci3
+	cmpl	$PCI_SIGNATURE, %edx
+	jne	no_pci3
+	testb	%ah, %ah
+	jnz	no_pci3
+#ifdef LOAD_ROM_FROM_PCI
+	incb	pcibios_present
+#endif
+	movw	$init_message_pci, %si
+	xorw	%di, %di
+	call	print_message
+	movb	%bh, %al
+	call	print_hex_nibble
+	movb	$( '.' ), %al
+	call	print_character
+	movb	%bl, %al
+	call	print_hex_byte
+	cmpb	$3, %bh
+	jb	no_pci3
+	/* PCI >=3.0: leave %gs as-is if sane */
+	movw	%gs, %ax
+	cmpw	$0xa000, %ax	/* Insane if %gs < 0xa000 */
+	jb	pci3_insane
+	movw	%cs, %bx	/* Sane if %cs == %gs */
+	cmpw	%bx, %ax
+	je	1f
+	movzbw	romheader_size, %cx /* Sane if %cs+len <= %gs */
+	shlw	$5, %cx
+	addw	%cx, %bx
+	cmpw	%bx, %ax
+	jae	1f
+	movw	%cs, %bx	/* Sane if %gs+len <= %cs */
+	addw	%cx, %ax
+	cmpw	%bx, %ax
+	jbe	1f
+pci3_insane: /* PCI 3.0 with insane %gs value: print error and ignore %gs */
+	movb	$( '!' ), %al
+	call	print_character
+	movw	%gs, %ax
+	call	print_hex_word
+no_pci3:
+	/* PCI <3.0: set %gs (runtime segment) = %cs (init-time segment) */
+	pushw	%cs
+	popw	%gs
+1:	popl	%edi
+	popl	%edx
+	popl	%ebx
+
+	/* Check for PnP BIOS.  Although %es:di should point to the
+	 * PnP BIOS signature on entry, some BIOSes fail to do this.
+	 */
+	movw	$( 0xf000 - 1 ), %bx
+pnp_scan:
+	incw	%bx
+	jz	no_pnp
+	movw	%bx, %es
+	cmpl	$PNP_SIGNATURE, %es:0
+	jne	pnp_scan
+	xorw	%dx, %dx
+	xorw	%si, %si
+	movzbw	%es:5, %cx
+1:	es lodsb
+	addb	%al, %dl
+	loop	1b
+	jnz	pnp_scan
+	/* Is PnP: print PnP message */
+	movw	$init_message_pnp, %si
+	xorw	%di, %di
+	call	print_message
+	/* Check for BBS */
+	pushw	%es:0x1b	/* Real-mode data segment */
+	pushw	%ds		/* &(bbs_version) */
+	pushw	$bbs_version
+	pushw	$PNP_GET_BBS_VERSION
+	lcall	*%es:0xd
+	addw	$8, %sp
+	testw	%ax, %ax
+	je	got_bbs
+no_pnp:	/* Not PnP-compliant - therefore cannot be BBS-compliant */
+no_bbs:	/* Not BBS-compliant - must hook INT 19 */
+	movw	$init_message_int19, %si
+	xorw	%di, %di
+	call	print_message
+	xorw	%ax, %ax
+	movw	%ax, %es
+	pushl	%es:( 0x19 * 4 )
+	popl	orig_int19
+	pushw	%gs /* %gs contains runtime %cs */
+	pushw	$int19_entry
+	popl	%es:( 0x19 * 4 )
+	jmp	bbs_done
+got_bbs: /* BBS compliant - no need to hook INT 19 */
+	movw	$init_message_bbs, %si
+	xorw	%di, %di
+	call	print_message
+bbs_done:
+
+	/* Check for PMM */
+	movw	$( 0xe000 - 1 ), %bx
+pmm_scan:
+	incw	%bx
+	jz	no_pmm
+	movw	%bx, %es
+	cmpl	$PMM_SIGNATURE, %es:0
+	jne	pmm_scan
+	xorw	%dx, %dx
+	xorw	%si, %si
+	movzbw	%es:5, %cx
+1:	es lodsb
+	addb	%al, %dl
+	loop	1b
+	jnz	pmm_scan
+	/* PMM found: print PMM message */
+	movw	$init_message_pmm, %si
+	xorw	%di, %di
+	call	print_message
+	/* We have PMM and so a 1kB stack: preserve upper register halves */
+	pushal
+	/* Calculate required allocation size in %esi */
+	movzwl	real_size, %eax
+	shll	$9, %eax
+	addl	$_textdata_memsz, %eax
+	orw	$0xffff, %ax	/* Ensure allocation size is at least 64kB */
+	bsrl	%eax, %ecx
+	subw	$15, %cx	/* Round up and convert to 64kB count */
+	movw	$1, %si
+	shlw	%cl, %si
+pmm_loop:
+	/* Try to allocate block via PMM */
+	pushw	$0x0006		/* Aligned, extended memory */
+	pushl	$0xffffffff	/* No handle */
+	movzwl	%si, %eax
+	shll	$12, %eax
+	pushl	%eax		/* Allocation size in paragraphs */
+	pushw	$PMM_ALLOCATE
+	lcall	*%es:7
+	addw	$12, %sp
+	/* Abort if allocation fails */
+	testw	%dx, %dx	/* %ax==0 even on success, since align>=64kB */
+	jz	pmm_fail
+	/* If block has A20==1, free block and try again with twice
+	 * the allocation size (and hence alignment).
+	 */
+	testw	$0x0010, %dx
+	jz	got_pmm
+	pushw	%dx
+	pushw	$0
+	pushw	$PMM_DEALLOCATE
+	lcall	*%es:7
+	addw	$6, %sp
+	addw	%si, %si
+	jmp	pmm_loop
+got_pmm: /* PMM allocation succeeded */
+	movw	%dx, ( image_source + 2 )
+	movw	%dx, %ax
+	xorw	%di, %di
+	call	print_hex_word
+	movb	$( '@' ), %al
+	call	print_character
+	movw	%si, %ax
+	call	print_hex_byte
+pmm_copy:
+	/* Copy ROM to PMM block */
+	xorw	%ax, %ax
+	movw	%ax, %es
+	movl	image_source, %edi
+	xorl	%esi, %esi
+	movzbl	romheader_size, %ecx
+	shll	$9, %ecx
+	addr32 rep movsb	/* PMM presence implies flat real mode */
+	movl	%edi, decompress_to
+	/* Shrink ROM */
+	movb	$_prefix_memsz_sect, romheader_size
+#if defined(SHRINK_WITHOUT_PMM) || defined(LOAD_ROM_FROM_PCI)
+	jmp	pmm_done
+pmm_fail:
+	/* Print marker and copy ourselves to high memory */
+	movl	$HIGHMEM_LOADPOINT, image_source
+	xorw	%di, %di
+	movb	$( '!' ), %al
+	call	print_character
+	jmp	pmm_copy
+pmm_done:
+#else
+pmm_fail:
+#endif
+	/* Restore upper register halves */
+	popal
+#if defined(LOAD_ROM_FROM_PCI)
+	call	load_from_pci
+	jc	load_err
+	jmp	load_ok
+no_pmm:
+	/* Cannot continue without PMM - print error message */
+	xorw	%di, %di
+	movw	$init_message_no_pmm, %si
+	call	print_message
+load_err:
+	/* Wait for five seconds to let user see message */
+	movw	$90, %cx
+1:	call	wait_for_tick
+	loop	1b
+	/* Mark environment as invalid and return */
+	movl	$0, decompress_to
+	jmp	out
+
+load_ok:
+#else
+no_pmm:
+#endif
+	/* Update checksum */
+	xorw	%bx, %bx
+	xorw	%si, %si
+	movzbw	romheader_size, %cx
+	shlw	$9, %cx
+1:	lodsb
+	addb	%al, %bl
+	loop	1b
+	subb	%bl, checksum
+
+	/* Copy self to option ROM space.  Required for PCI3.0, which
+	 * loads us to a temporary location in low memory.  Will be a
+	 * no-op for lower PCI versions.
+	 */
+	movb	$( ' ' ), %al
+	xorw	%di, %di
+	call	print_character
+	movw	%gs, %ax
+	call	print_hex_word
+	movzbw	romheader_size, %cx
+	shlw	$9, %cx
+	movw	%ax, %es
+	xorw	%si, %si
+	xorw	%di, %di
+	cs rep	movsb
+
+	/* Prompt for POST-time shell */
+	movw	$init_message_prompt, %si
+	xorw	%di, %di
+	call	print_message
+	movw	$prodstr, %si
+	call	print_message
+	movw	$init_message_dots, %si
+	call	print_message
+	/* Wait for Ctrl-B */
+	movw	$0xff02, %bx
+	call	wait_for_key
+	/* Clear prompt */
+	pushf
+	xorw	%di, %di
+	call	print_kill_line
+	movw	$init_message_done, %si
+	call	print_message
+	popf
+	jnz	out
+	/* Ctrl-B was pressed: invoke gPXE.  The keypress will be
+	 * picked up by the initial shell prompt, and we will drop
+	 * into a shell.
+	 */
+	pushw	%cs
+	call	exec
+out:
+	/* Restore registers */
+	popw	%gs
+	popw	%fs
+	popw	%es
+	popw	%ds
+	popaw
+
+	/* Indicate boot capability to PnP BIOS, if present */
+	movw	$0x20, %ax
+	lret
+	.size init, . - init
+
+/*
+ * Note to hardware vendors:
+ *
+ * If you wish to brand this boot ROM, please do so by defining the
+ * strings PRODUCT_NAME and PRODUCT_SHORT_NAME in config/general.h.
+ *
+ * While nothing in the GPL prevents you from removing all references
+ * to gPXE or http://etherboot.org, we prefer you not to do so.
+ *
+ * If you have an OEM-mandated branding requirement that cannot be
+ * satisfied simply by defining PRODUCT_NAME and PRODUCT_SHORT_NAME,
+ * please contact us.
+ *
+ * [ Including an ASCII NUL in PRODUCT_NAME is considered to be
+ *   bypassing the spirit of this request! ]
+ */
+init_message:
+	.ascii	"\n"
+	.ascii	PRODUCT_NAME
+	.ascii	"\n"
+	.asciz	"gPXE (http://etherboot.org) - "
+	.size	init_message, . - init_message
+init_message_pci:
+	.asciz	" PCI"
+	.size	init_message_pci, . - init_message_pci
+init_message_pnp:
+	.asciz	" PnP"
+	.size	init_message_pnp, . - init_message_pnp
+init_message_bbs:
+	.asciz	" BBS"
+	.size	init_message_bbs, . - init_message_bbs
+init_message_pmm:
+	.asciz	" PMM"
+	.size	init_message_pmm, . - init_message_pmm
+#ifdef LOAD_ROM_FROM_PCI
+init_message_no_pmm:
+	.asciz	"\nPMM required but not present!\n"
+	.size	init_message_no_pmm, . - init_message_no_pmm
+#endif
+init_message_int19:
+	.asciz	" INT19"
+	.size	init_message_int19, . - init_message_int19
+init_message_prompt:
+	.asciz	"\nPress Ctrl-B to configure "
+	.size	init_message_prompt, . - init_message_prompt
+init_message_dots:
+	.asciz	"..."
+	.size	init_message_dots, . - init_message_dots
+init_message_done:
+	.asciz	"\n\n"
+	.size	init_message_done, . - init_message_done
+
+/* ROM image location
+ *
+ * May be either within option ROM space, or within PMM-allocated block.
+ */
+	.globl	image_source
+image_source:
+	.long	0
+	.size	image_source, . - image_source
+
+/* Temporary decompression area
+ *
+ * May be either at HIGHMEM_LOADPOINT, or within PMM-allocated block.
+ * If a PCI ROM load fails, this will be set to zero.
+ */
+	.globl	decompress_to
+decompress_to:
+	.long	HIGHMEM_LOADPOINT
+	.size	decompress_to, . - decompress_to
+
+#ifdef LOAD_ROM_FROM_PCI
+
+/* Set if the PCI BIOS is present, even <3.0 */
+pcibios_present:
+	.byte	0
+	.byte	0		/* for alignment */
+	.size	pcibios_present, . - pcibios_present
+
+/* PCI bus:device.function word
+ *
+ * Filled in by init in the .xrom case, so the remainder of the ROM
+ * can be located.
+ */
+pci_busdevfn:
+	.word	0
+	.size	pci_busdevfn, . - pci_busdevfn
+
+#endif
+
+/* BBS version
+ *
+ * Filled in by BBS BIOS.  We ignore the value.
+ */
+bbs_version:
+	.word	0
+	.size	bbs_version, . - bbs_version
+
+/* Boot Execution Vector entry point
+ *
+ * Called by the PnP BIOS when it wants to boot us.
+ */
+bev_entry:
+	pushw	%cs
+	call	exec
+	lret
+	.size	bev_entry, . - bev_entry
+
+
+#ifdef LOAD_ROM_FROM_PCI
+
+#define PCI_ROM_ADDRESS		0x30	/* Bits 31:11 address, 10:1 reserved */
+#define PCI_ROM_ADDRESS_ENABLE	 0x00000001
+#define PCI_ROM_ADDRESS_MASK	 0xfffff800
+
+#define PCIBIOS_READ_WORD	0xb109
+#define PCIBIOS_READ_DWORD	0xb10a
+#define PCIBIOS_WRITE_WORD	0xb10c
+#define PCIBIOS_WRITE_DWORD	0xb10d
+
+/* Determine size of PCI BAR
+ *
+ *  %bx : PCI bus:dev.fn to probe
+ *  %di : Address of BAR to find size of
+ * %edx : Mask of address bits within BAR
+ *
+ * %ecx : Size for a memory resource,
+ *	  1 for an I/O resource (bit 0 set).
+ *   CF : Set on error or nonexistent device (all-ones read)
+ *
+ * All other registers saved.
+ */
+pci_bar_size:
+	/* Save registers */
+	pushw	%ax
+	pushl	%esi
+	pushl	%edx
+
+	/* Read current BAR value */
+	movw	$PCIBIOS_READ_DWORD, %ax
+	int	$0x1a
+
+	/* Check for device existence and save it */
+	testb	$1, %cl		/* I/O bit? */
+	jz	1f
+	andl	$1, %ecx	/* If so, exit with %ecx = 1 */
+	jmp	99f
+1:	notl	%ecx
+	testl	%ecx, %ecx	/* Set ZF iff %ecx was all-ones */
+	notl	%ecx
+	jnz	1f
+	stc			/* All ones - exit with CF set */
+	jmp	99f
+1:	movl	%ecx, %esi	/* Save in %esi */
+
+	/* Write all ones to BAR */
+	movl	%edx, %ecx
+	movw	$PCIBIOS_WRITE_DWORD, %ax
+	int	$0x1a
+
+	/* Read back BAR */
+	movw	$PCIBIOS_READ_DWORD, %ax
+	int	$0x1a
+
+	/* Find decode size from least set bit in mask BAR */
+	bsfl	%ecx, %ecx	/* Find least set bit, log2(decode size) */
+	jz	1f		/* Mask BAR should not be zero */
+	xorl	%edx, %edx
+	incl	%edx
+	shll	%cl, %edx	/* %edx = decode size */
+	jmp	2f
+1:	xorl	%edx, %edx	/* Return zero size for mask BAR zero */
+
+	/* Restore old BAR value */
+2:	movl	%esi, %ecx
+	movw	$PCIBIOS_WRITE_DWORD, %ax
+	int	$0x1a
+
+	movl	%edx, %ecx	/* Return size in %ecx */
+
+	/* Restore registers and return */
+99:	popl	%edx
+	popl	%esi
+	popw	%ax
+	ret
+
+	.size	pci_bar_size, . - pci_bar_size
+
+/* PCI ROM loader
+ *
+ * Called from init in the .xrom case to load the non-prefix code
+ * using the PCI ROM BAR.
+ *
+ * Returns with carry flag set on error. All registers saved.
+ */
+load_from_pci:
+	/*
+	 * Use PCI BIOS access to config space. The calls take
+	 *
+	 *   %ah : 0xb1		%al : function
+	 *   %bx : bus/dev/fn
+	 *   %di : config space address
+	 *  %ecx : value to write (for writes)
+	 *
+	 *  %ecx : value read (for reads)
+	 *   %ah : return code
+	 *    CF : error indication
+	 *
+	 * All registers not used for return are preserved.
+	 */
+
+	/* Save registers and set up %es for big real mode */
+	pushal
+	pushw	%es
+	xorw	%ax, %ax
+	movw	%ax, %es
+
+	/* Check PCI BIOS presence */
+	cmpb	$0, pcibios_present
+	jz	err_pcibios
+
+	/* Load existing PCI ROM BAR */
+	movw	$PCIBIOS_READ_DWORD, %ax
+	movw	pci_busdevfn, %bx
+	movw	$PCI_ROM_ADDRESS, %di
+	int	$0x1a
+
+	/* Maybe it's already enabled? */
+	testb	$PCI_ROM_ADDRESS_ENABLE, %cl
+	jz	1f
+	movb	$1, %dl		/* Flag indicating no deinit required */
+	movl	%ecx, %ebp
+	jmp	check_rom
+
+	/* Determine PCI BAR decode size */
+1:	movl	$PCI_ROM_ADDRESS_MASK, %edx
+	call	pci_bar_size	/* Returns decode size in %ecx */
+	jc	err_size_insane	/* CF => no ROM BAR, %ecx == ffffffff */
+
+	/* Check sanity of decode size */
+	xorl	%eax, %eax
+	movw	real_size, %ax
+	shll	$9, %eax	/* %eax = ROM size */
+	cmpl	%ecx, %eax
+	ja	err_size_insane	/* Insane if decode size < ROM size */
+	cmpl	$0x100000, %ecx
+	jae	err_size_insane	/* Insane if decode size >= 1MB */
+
+	/* Find a place to map the BAR
+	 * In theory we should examine e820 and all PCI BARs to find a
+	 * free region. However, we run at POST when e820 may not be
+	 * available, and memory reads of an unmapped location are
+	 * de facto standardized to return all-ones. Thus, we can get
+	 * away with searching high memory (0xf0000000 and up) on
+	 * multiples of the ROM BAR decode size for a sufficiently
+	 * large all-ones region.
+	 */
+	movl	%ecx, %edx	/* Save ROM BAR size in %edx */
+	movl	$0xf0000000, %ebp
+	xorl	%eax, %eax
+	notl	%eax		/* %eax = all ones */
+bar_search:
+	movl	%ebp, %edi
+	movl	%edx, %ecx
+	shrl	$2, %ecx
+	addr32 repe scasl	/* Scan %es:edi for anything not all-ones */
+	jz	bar_found
+	addl	%edx, %ebp
+	testl	$0x80000000, %ebp
+	jz	err_no_bar
+	jmp	bar_search
+
+bar_found:
+	movl	%edi, %ebp
+	/* Save current BAR value on stack to restore later */
+	movw	$PCIBIOS_READ_DWORD, %ax
+	movw	$PCI_ROM_ADDRESS, %di
+	int	$0x1a
+	pushl	%ecx
+
+	/* Map the ROM */
+	movw	$PCIBIOS_WRITE_DWORD, %ax
+	movl	%ebp, %ecx
+	orb	$PCI_ROM_ADDRESS_ENABLE, %cl
+	int	$0x1a
+
+	xorb	%dl, %dl	/* %dl = 0 : ROM was not already mapped */
+check_rom:
+	/* Check and copy ROM - enter with %dl set to skip unmapping,
+	 * %ebp set to mapped ROM BAR address.
+	 * We check up to prodstr_separator for equality, since anything past
+	 * that may have been modified. Since our check includes the checksum
+	 * byte over the whole ROM stub, that should be sufficient.
+	 */
+	xorb	%dh, %dh	/* %dh = 0 : ROM did not fail integrity check */
+
+	/* Verify ROM integrity */
+	xorl	%esi, %esi
+	movl	%ebp, %edi
+	movl	$prodstr_separator, %ecx
+	addr32 repe cmpsb
+	jz	copy_rom
+	incb	%dh		/* ROM failed integrity check */
+	movl	%ecx, %ebp	/* Save number of bytes left */
+	jmp	skip_load
+
+copy_rom:
+	/* Print BAR address and indicate whether we mapped it ourselves */
+	movb	$( ' ' ), %al
+	xorw	%di, %di
+	call	print_character
+	movl	%ebp, %eax
+	call	print_hex_dword
+	movb	$( '-' ), %al	/* '-' for self-mapped */
+	subb	%dl, %al
+	subb	%dl, %al	/* '+' = '-' - 2 for BIOS-mapped */
+	call	print_character
+
+	/* Copy ROM at %ebp to PMM or highmem block */
+	movl	%ebp, %esi
+	movl	image_source, %edi
+	movzwl	real_size, %ecx
+	shll	$9, %ecx
+	addr32 es rep movsb
+	movl	%edi, decompress_to
+skip_load:
+	testb	%dl, %dl	/* Was ROM already mapped? */
+	jnz	skip_unmap
+
+	/* Unmap the ROM by restoring old ROM BAR */
+	movw	$PCIBIOS_WRITE_DWORD, %ax
+	movw	$PCI_ROM_ADDRESS, %di
+	popl	%ecx
+	int	$0x1a
+
+skip_unmap:
+	/* Error handling */
+	testb	%dh, %dh
+	jnz	err_rom_invalid
+	clc
+	jmp	99f
+
+err_pcibios:			/* No PCI BIOS available */
+	movw	$load_message_no_pcibios, %si
+	xorl	%eax, %eax	/* "error code" is zero */
+	jmp	1f
+err_size_insane:		/* BAR has size (%ecx) that is insane */
+	movw	$load_message_size_insane, %si
+	movl	%ecx, %eax
+	jmp	1f
+err_no_bar:			/* No space of sufficient size (%edx) found */
+	movw	$load_message_no_bar, %si
+	movl	%edx, %eax
+	jmp	1f
+err_rom_invalid:		/* Loaded ROM does not match (%ebp bytes left) */
+	movw	$load_message_rom_invalid, %si
+	movzbl	romheader_size, %eax
+	shll	$9, %eax
+	subl	%ebp, %eax
+	decl	%eax		/* %eax is now byte index of failure */
+
+1:	/* Error handler - print message at %si and dword in %eax */
+	xorw	%di, %di
+	call	print_message
+	call	print_hex_dword
+	stc
+99:	popw	%es
+	popal
+	ret
+
+	.size	load_from_pci, . - load_from_pci
+
+load_message_no_pcibios:
+	.asciz	"\nNo PCI BIOS found! "
+	.size	load_message_no_pcibios, . - load_message_no_pcibios
+
+load_message_size_insane:
+	.asciz	"\nROM resource has invalid size "
+	.size	load_message_size_insane, . - load_message_size_insane
+
+load_message_no_bar:
+	.asciz	"\nNo memory hole of sufficient size "
+	.size	load_message_no_bar, . - load_message_no_bar
+
+load_message_rom_invalid:
+	.asciz	"\nLoaded ROM is invalid at "
+	.size	load_message_rom_invalid, . - load_message_rom_invalid
+
+#endif /* LOAD_ROM_FROM_PCI */
+
+
+/* INT19 entry point
+ *
+ * Called via the hooked INT 19 if we detected a non-PnP BIOS.  We
+ * attempt to return via the original INT 19 vector (if we were able
+ * to store it).
+ */
+int19_entry:
+	pushw	%cs
+	popw	%ds
+	/* Prompt user to press B to boot */
+	movw	$int19_message_prompt, %si
+	xorw	%di, %di
+	call	print_message
+	movw	$prodstr, %si
+	call	print_message
+	movw	$int19_message_dots, %si
+	call	print_message
+	movw	$0xdf4e, %bx
+	call	wait_for_key
+	pushf
+	xorw	%di, %di
+	call	print_kill_line
+	movw	$int19_message_done, %si
+	call	print_message
+	popf
+	jz	1f
+	/* Leave keypress in buffer and start gPXE.  The keypress will
+	 * cause the usual initial Ctrl-B prompt to be skipped.
+	 */
+	pushw	%cs
+	call	exec
+1:	/* Try to call original INT 19 vector */
+	movl	%cs:orig_int19, %eax
+	testl	%eax, %eax
+	je	2f
+	ljmp	*%cs:orig_int19
+2:	/* No chained vector: issue INT 18 as a last resort */
+	int	$0x18
+	.size	int19_entry, . - int19_entry
+orig_int19:
+	.long	0
+	.size	orig_int19, . - orig_int19
+
+int19_message_prompt:
+	.asciz	"Press N to skip booting from "
+	.size	int19_message_prompt, . - int19_message_prompt
+int19_message_dots:
+	.asciz	"..."
+	.size	int19_message_dots, . - int19_message_dots
+int19_message_done:
+	.asciz	"\n\n"
+	.size	int19_message_done, . - int19_message_done
+	
+/* Execute as a boot device
+ *
+ */
+exec:	/* Set %ds = %cs */
+	pushw	%cs
+	popw	%ds
+
+#ifdef LOAD_ROM_FROM_PCI
+	/* Don't execute if load was invalid */
+	cmpl	$0, decompress_to
+	jne	1f
+	lret
+1:
+#endif
+
+	/* Print message as soon as possible */
+	movw	$prodstr, %si
+	xorw	%di, %di
+	call	print_message
+	movw	$exec_message, %si
+	call	print_message
+
+	/* Store magic word on BIOS stack and remember BIOS %ss:sp */
+	pushl	$STACK_MAGIC
+	movw	%ss, %dx
+	movw	%sp, %bp
+
+	/* Obtain a reasonably-sized temporary stack */
+	xorw	%ax, %ax
+	movw	%ax, %ss
+	movw	$0x7c00, %sp
+
+	/* Install gPXE */
+	movl	image_source, %esi
+	movl	decompress_to, %edi
+	call	alloc_basemem
+	call	install_prealloc
+
+	/* Set up real-mode stack */
+	movw	%bx, %ss
+	movw	$_estack16, %sp
+
+	/* Jump to .text16 segment */
+	pushw	%ax
+	pushw	$1f
+	lret
+	.section ".text16", "awx", @progbits
+1:	/* Call main() */
+	pushl	$main
+	pushw	%cs
+	call	prot_call
+	popl	%ecx /* discard */
+
+	/* Uninstall gPXE */
+	call	uninstall
+
+	/* Restore BIOS stack */
+	movw	%dx, %ss
+	movw	%bp, %sp
+
+	/* Check magic word on BIOS stack */
+	popl	%eax
+	cmpl	$STACK_MAGIC, %eax
+	jne	1f
+	/* BIOS stack OK: return to caller */
+	lret
+1:	/* BIOS stack corrupt: use INT 18 */
+	int	$0x18
+	.previous
+
+exec_message:
+	.asciz	" starting execution\n"
+	.size exec_message, . - exec_message
+
+/* Wait for key press specified by %bl (masked by %bh)
+ *
+ * Used by init and INT19 code when prompting user.  If the specified
+ * key is pressed, it is left in the keyboard buffer.
+ *
+ * Returns with ZF set iff specified key is pressed.
+ */
+wait_for_key:
+	/* Preserve registers */
+	pushw	%cx
+	pushw	%ax
+1:	/* Empty the keyboard buffer before waiting for input */
+	movb	$0x01, %ah
+	int	$0x16
+	jz	2f
+	xorw	%ax, %ax
+	int	$0x16
+	jmp	1b
+2:	/* Wait for a key press */
+	movw	$ROM_BANNER_TIMEOUT, %cx
+3:	decw	%cx
+	js	99f		/* Exit with ZF clear */
+	/* Wait for timer tick to be updated */
+	call	wait_for_tick
+	/* Check to see if a key was pressed */
+	movb	$0x01, %ah
+	int	$0x16
+	jz	3b
+	/* Check to see if key was the specified key */
+	andb	%bh, %al
+	cmpb	%al, %bl
+	je	99f		/* Exit with ZF set */
+	/* Not the specified key: remove from buffer and stop waiting */
+	pushfw
+	xorw	%ax, %ax
+	int	$0x16
+	popfw			/* Exit with ZF clear */
+99:	/* Restore registers and return */
+	popw	%ax
+	popw	%cx
+	ret
+	.size wait_for_key, . - wait_for_key
+
+/* Wait for timer tick
+ *
+ * Used by wait_for_key
+ */
+wait_for_tick:
+	pushl	%eax
+	pushw	%fs
+	movw	$0x40, %ax
+	movw	%ax, %fs
+	movl	%fs:(0x6c), %eax
+1:	pushf
+	sti
+	hlt
+	popf
+	cmpl	%fs:(0x6c), %eax
+	je	1b
+	popw	%fs
+	popl	%eax
+	ret
+	.size wait_for_tick, . - wait_for_tick
diff --git a/gpxe/src/arch/i386/prefix/undiloader.S b/gpxe/src/arch/i386/prefix/undiloader.S
new file mode 100644
index 0000000..36c1bef
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/undiloader.S
@@ -0,0 +1,49 @@
+	.text
+	.code16
+	.arch i386
+	.section ".prefix", "ax", @progbits
+
+/* UNDI loader
+ *
+ * Called by an external program to load our PXE stack.
+ */
+	.globl	undiloader
+undiloader:
+	/* Save registers */
+	pushl	%esi
+	pushl	%edi
+	pushw	%ds
+	pushw	%es
+	pushw	%bx
+	/* ROM segment address to %ds */
+	pushw	%cs
+	popw	%ds
+	/* UNDI loader parameter structure address into %es:%di */
+	movw	%sp, %bx
+	movw	%ss:18(%bx), %di
+	movw	%ss:20(%bx), %es
+	/* Install to specified real-mode addresses */
+	pushw	%di
+	movw	%es:12(%di), %bx
+	movw	%es:14(%di), %ax
+	movl	image_source, %esi
+	movl	decompress_to, %edi
+	call	install_prealloc
+	popw	%di
+	/* Call UNDI loader C code */
+	pushl	$pxe_loader_call
+	pushw	%cs
+	pushw	$1f
+	pushw	%ax
+	pushw	$prot_call
+	lret
+1:	popw	%bx	/* discard */
+	popw	%bx	/* discard */
+	/* Restore registers and return */
+	popw	%bx
+	popw	%es
+	popw	%ds
+	popl	%edi
+	popl	%esi
+	lret
+	.size undiloader, . - undiloader
diff --git a/gpxe/src/arch/i386/prefix/unnrv2b.S b/gpxe/src/arch/i386/prefix/unnrv2b.S
new file mode 100644
index 0000000..f5724c1
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/unnrv2b.S
@@ -0,0 +1,184 @@
+/* 
+ * Copyright (C) 1996-2002 Markus Franz Xaver Johannes Oberhumer
+ *
+ * This file 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 (at your option) any later version.
+ *
+ * Originally this code was part of ucl the data compression library
+ * for upx the ``Ultimate Packer of eXecutables''.
+ *
+ * - Converted to gas assembly, and refitted to work with etherboot.
+ *   Eric Biederman 20 Aug 2002
+ *
+ * - Structure modified to be a subroutine call rather than an
+ *   executable prefix.
+ *   Michael Brown 30 Mar 2004
+ *
+ * - Modified to be compilable as either 16-bit or 32-bit code.
+ *   Michael Brown 9 Mar 2005
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+/****************************************************************************
+ * This file provides the decompress() and decompress16() functions
+ * which can be called in order to decompress an image compressed with
+ * the nrv2b utility in src/util.
+ *
+ * These functions are designed to be called by the prefix.  They are
+ * position-independent code.
+ *
+ * The same basic assembly code is used to compile both
+ * decompress() and decompress16().
+ ****************************************************************************
+ */
+
+	.text
+	.arch i386
+	.section ".prefix.lib", "ax", @progbits
+
+#ifdef CODE16
+/****************************************************************************
+ * decompress16 (real-mode near call, position independent)
+ *
+ * Decompress data in 16-bit mode
+ *
+ * Parameters (passed via registers):
+ *   %ds:%esi - Start of compressed input data
+ *   %es:%edi - Start of output buffer
+ * Returns:
+ *   %ds:%esi - End of compressed input data
+ *   %es:%edi - End of decompressed output data
+ *   All other registers are preserved
+ *
+ * NOTE: It would be possible to build a smaller version of the
+ * decompression code for -DKEEP_IT_REAL by using
+ *    #define REG(x) x
+ * to use 16-bit registers where possible.  This would impose limits
+ * that the compressed data size must be in the range [1,65533-%si]
+ * and the uncompressed data size must be in the range [1,65536-%di]
+ * (where %si and %di are the input values for those registers).  Note
+ * particularly that the lower limit is 1, not 0, and that the upper
+ * limit on the input (compressed) data really is 65533, since the
+ * algorithm may read up to three bytes beyond the end of the input
+ * data, since it reads dwords.
+ ****************************************************************************
+ */
+
+#define REG(x) e ## x
+#define ADDR32 addr32
+
+	.code16
+	.globl	decompress16
+decompress16:
+	
+#else /* CODE16 */
+
+/****************************************************************************
+ * decompress (32-bit protected-mode near call, position independent)
+ *
+ * Parameters (passed via registers):
+ *   %ds:%esi - Start of compressed input data
+ *   %es:%edi - Start of output buffer
+ * Returns:
+ *   %ds:%esi - End of compressed input data
+ *   %es:%edi - End of decompressed output data
+ *   All other registers are preserved
+ ****************************************************************************
+ */
+
+#define REG(x) e ## x
+#define ADDR32
+	
+	.code32
+	.globl	decompress
+decompress:
+
+#endif /* CODE16 */
+
+#define xAX	REG(ax)
+#define xCX	REG(cx)
+#define xBP	REG(bp)
+#define xSI	REG(si)
+#define xDI	REG(di)
+
+	/* Save registers */
+	push	%xAX
+	pushl	%ebx
+	push	%xCX
+	push	%xBP
+	/* Do the decompression */
+	cld
+	xor	%xBP, %xBP
+	dec	%xBP		/* last_m_off = -1 */
+	jmp	dcl1_n2b
+	
+decompr_literals_n2b:
+	ADDR32 movsb
+decompr_loop_n2b:
+	addl	%ebx, %ebx
+	jnz	dcl2_n2b
+dcl1_n2b:
+	call	getbit32
+dcl2_n2b:
+	jc	decompr_literals_n2b
+	xor	%xAX, %xAX
+	inc	%xAX		/* m_off = 1 */
+loop1_n2b:
+	call	getbit1
+	adc	%xAX, %xAX	/* m_off = m_off*2 + getbit() */
+	call	getbit1
+	jnc	loop1_n2b	/* while(!getbit()) */
+	sub	$3, %xAX
+	jb	decompr_ebpeax_n2b	/* if (m_off == 2) goto decompr_ebpeax_n2b ? */
+	shl	$8, %xAX	
+	ADDR32 movb (%xSI), %al	/* m_off = (m_off - 3)*256 + src[ilen++] */
+	inc	%xSI
+	xor	$-1, %xAX
+	jz	decompr_end_n2b	/* if (m_off == 0xffffffff) goto decomp_end_n2b */
+	mov	%xAX, %xBP	/* last_m_off = m_off ?*/
+decompr_ebpeax_n2b:
+	xor	%xCX, %xCX
+	call	getbit1
+	adc	%xCX, %xCX	/* m_len = getbit() */
+	call	getbit1
+	adc	%xCX, %xCX	/* m_len = m_len*2 + getbit()) */
+	jnz	decompr_got_mlen_n2b	/* if (m_len == 0) goto decompr_got_mlen_n2b */
+	inc	%xCX		/* m_len++ */
+loop2_n2b:
+	call	getbit1	
+	adc	%xCX, %xCX	/* m_len = m_len*2 + getbit() */
+	call	getbit1
+	jnc	loop2_n2b	/* while(!getbit()) */
+	inc	%xCX
+	inc	%xCX		/* m_len += 2 */
+decompr_got_mlen_n2b:
+	cmp	$-0xd00, %xBP
+	adc	$1, %xCX	/* m_len = m_len + 1 + (last_m_off > 0xd00) */
+	push	%xSI
+	ADDR32 lea (%xBP,%xDI), %xSI	/* m_pos = dst + olen + -m_off  */
+	rep
+	es ADDR32 movsb		/* dst[olen++] = *m_pos++ while(m_len > 0) */
+	pop	%xSI
+	jmp	decompr_loop_n2b
+
+
+getbit1:
+	addl	%ebx, %ebx
+	jnz	1f
+getbit32:
+	ADDR32 movl (%xSI), %ebx
+	sub	$-4, %xSI	/* sets carry flag */
+	adcl	%ebx, %ebx
+1:
+	ret
+
+decompr_end_n2b:
+	/* Restore registers and return */
+	pop	%xBP
+	pop	%xCX
+	popl	%ebx
+	pop	%xAX
+	ret
diff --git a/gpxe/src/arch/i386/prefix/unnrv2b16.S b/gpxe/src/arch/i386/prefix/unnrv2b16.S
new file mode 100644
index 0000000..b24c284
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/unnrv2b16.S
@@ -0,0 +1,9 @@
+/*
+ * 16-bit version of the decompressor
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+#define CODE16
+#include "unnrv2b.S"
diff --git a/gpxe/src/arch/i386/prefix/usbdisk.S b/gpxe/src/arch/i386/prefix/usbdisk.S
new file mode 100644
index 0000000..fa7d195
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/usbdisk.S
@@ -0,0 +1,23 @@
+	.text
+	.arch i386
+	.section ".prefix", "awx", @progbits
+	.code16
+	.org 0
+
+#include "mbr.S"
+
+/* Partition table: ZIP-compatible partition 4, 64 heads, 32 sectors/track */
+	.org 446
+	.space 16
+	.space 16
+	.space 16
+	.byte 0x80, 0x01, 0x01, 0x00
+	.byte 0xeb, 0x3f, 0x20, 0x01
+	.long 0x00000020
+	.long 0x00000fe0
+
+	.org 510
+	.byte 0x55, 0xaa
+
+/* Skip to start of partition */
+	.org 32 * 512
diff --git a/gpxe/src/arch/i386/prefix/xromprefix.S b/gpxe/src/arch/i386/prefix/xromprefix.S
new file mode 100644
index 0000000..d7c861f
--- /dev/null
+++ b/gpxe/src/arch/i386/prefix/xromprefix.S
@@ -0,0 +1,9 @@
+/*
+ * ROM prefix that loads the bulk of the ROM using direct PCI accesses,
+ * so as not to take up much option ROM space on PCI <3.0 systems.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+#define LOAD_ROM_FROM_PCI
+#include "romprefix.S"
diff --git a/gpxe/src/arch/i386/scripts/i386-kir.lds b/gpxe/src/arch/i386/scripts/i386-kir.lds
new file mode 100644
index 0000000..c19480f
--- /dev/null
+++ b/gpxe/src/arch/i386/scripts/i386-kir.lds
@@ -0,0 +1,197 @@
+/* -*- sh -*- */
+
+/*
+ * Linker script for i386 images
+ *
+ */
+
+OUTPUT_FORMAT ( "elf32-i386", "elf32-i386", "elf32-i386" )
+OUTPUT_ARCH ( i386 )
+ENTRY ( _entry )
+
+SECTIONS {
+
+    /* All sections in the resulting file have consecutive load
+     * addresses, but may have individual link addresses depending on
+     * the memory model being used.
+     *
+     * The linker symbols _prefix_link_addr, load_addr, and
+     * _max_align may be specified explicitly.  If not specified, they
+     * will default to:
+     *
+     *   _prefix_link_addr	= 0
+     *   _load_addr		= 0
+     *   _max_align		= 16
+     * 
+     * We guarantee alignment of virtual addresses to any alignment
+     * specified by the constituent object files (e.g. via
+     * __attribute__((aligned(x)))).  Load addresses are guaranteed
+     * only up to _max_align.  Provided that all loader and relocation
+     * code honours _max_align, this means that physical addresses are
+     * also guaranteed up to _max_align.
+     *
+     * Note that when using -DKEEP_IT_REAL, the UNDI segments are only
+     * guaranteed to be loaded on a paragraph boundary (i.e. 16-byte
+     * alignment).  Using _max_align>16 will therefore not guarantee
+     * >16-byte alignment of physical addresses when -DKEEP_IT_REAL is
+     * used (though virtual addresses will still be fully aligned).
+     *
+     */
+
+    /*
+     * The prefix
+     */
+
+    _prefix_link_addr = DEFINED ( _prefix_link_addr ) ? _prefix_link_addr : 0;
+    . = _prefix_link_addr;
+    _prefix = .;
+
+    .prefix : AT ( _prefix_load_offset + __prefix ) {
+	__prefix = .;
+	_entry = .;
+	*(.prefix)
+	*(.prefix.*)
+	_eprefix_progbits = .;
+    }
+    
+    _eprefix = .;
+
+    /*
+     * The 16-bit sections
+     */
+
+    _text16_link_addr = 0;
+    . = _text16_link_addr;
+    _text16 = .;
+
+    . += 1;			/* Prevent NULL being valid */
+
+    .text16 : AT ( _text16_load_offset + __text16 ) {
+	__text16 = .;
+	*(.text.null_trap)
+	*(.text16)
+	*(.text16.*)
+	*(.text)
+	*(.text.*)
+	_etext16_progbits = .;
+    } = 0x9090
+
+    _etext16 = .;
+
+    _data16_link_addr = 0;
+    . = _data16_link_addr;
+    _data16 = .;
+
+    . += 1;			/* Prevent NULL being valid */
+
+    .rodata16 : AT ( _data16_load_offset + __rodata16 ) {
+	__rodata16 = .;
+	*(.rodata16)
+	*(.rodata16.*)
+	*(.rodata)
+	*(.rodata.*)
+    }
+    .data16 : AT ( _data16_load_offset + __data16 ) {
+	__data16 = .;
+	*(.data16)
+	*(.data16.*)
+	*(.data)
+	*(.data.*)
+	*(SORT(.tbl.*))		/* Various tables.  See include/tables.h */
+	_edata16_progbits = .;
+    }
+    .bss16 : AT ( _data16_load_offset + __bss16 ) {
+	__bss16 = .;
+	_bss16 = .;
+	*(.bss16)
+	*(.bss16.*)
+	*(.bss)
+	*(.bss.*)
+	*(COMMON)
+	_ebss16 = .;
+    }
+    .stack16 : AT ( _data16_load_offset + __stack16 ) {
+	__stack16 = .;
+	*(.stack16)
+	*(.stack16.*)
+	*(.stack)
+	*(.stack.*)
+    }
+
+    _edata16 = .;
+
+    _end = .;
+
+    /*
+     * Dispose of the comment and note sections to make the link map
+     * easier to read
+     */
+
+    /DISCARD/ : {
+	*(.comment)
+	*(.note)
+	*(.discard)
+    }
+
+    /*
+     * Load address calculations.  The slightly obscure nature of the
+     * calculations is because ALIGN(x) can only operate on the
+     * location counter.
+     */
+
+    _max_align		    = DEFINED ( _max_align ) ? _max_align : 16;
+    _load_addr		    = DEFINED ( _load_addr ) ? _load_addr : 0;
+
+    .			    = _load_addr;
+
+    .			   -= _prefix_link_addr;
+    _prefix_load_offset	    = ALIGN ( _max_align );
+    _prefix_load_addr	    = _prefix_link_addr + _prefix_load_offset;
+    _prefix_size	    = _eprefix - _prefix;
+    _prefix_progbits_size   = _eprefix_progbits - _prefix;
+    .			    = _prefix_load_addr + _prefix_progbits_size;
+
+    .			   -= _text16_link_addr;
+    _text16_load_offset	    = ALIGN ( _max_align );
+    _text16_load_addr	    = _text16_link_addr + _text16_load_offset;
+    _text16_size	    = _etext16 - _text16;
+    _text16_progbits_size   = _etext16_progbits - _text16;
+    .			    = _text16_load_addr + _text16_progbits_size;
+
+    .			   -= _data16_link_addr;
+    _data16_load_offset	    = ALIGN ( _max_align );
+    _data16_load_addr	    = _data16_link_addr + _data16_load_offset;
+    _data16_size	    = _edata16 - _data16;
+    _data16_progbits_size   = _edata16_progbits - _data16;
+    .			    = _data16_load_addr + _data16_progbits_size;
+
+    .			    = ALIGN ( _max_align );
+
+    _load_size		    = . - _load_addr;
+
+    /*
+     * Alignment checks.  ALIGN() can only operate on the location
+     * counter, so we set the location counter to each value we want
+     * to check.
+     */
+
+    . = _prefix_load_addr - _prefix_link_addr;
+    _assert = ASSERT ( ( . == ALIGN ( _max_align ) ),
+		       "_prefix is badly aligned" );
+
+    . = _text16_load_addr - _text16_link_addr;
+    _assert = ASSERT ( ( . == ALIGN ( _max_align ) ),
+		       "_text16 is badly aligned" );
+
+    . = _data16_load_addr - _data16_link_addr;
+    _assert = ASSERT ( ( . == ALIGN ( _max_align ) ),
+		       "_data16 is badly aligned" );
+
+    /*
+     * Values calculated to save code from doing it
+     */
+    _text16_size_pgh	= ( ( _text16_size + 15 ) / 16 );
+    _data16_size_pgh	= ( ( _data16_size + 15 ) / 16 );
+    _load_size_pgh	= ( ( _load_size + 15 ) / 16 );
+    _load_size_sect	= ( ( _load_size + 511 ) / 512 );
+}
diff --git a/gpxe/src/arch/i386/scripts/i386.lds b/gpxe/src/arch/i386/scripts/i386.lds
new file mode 100644
index 0000000..33c75f9
--- /dev/null
+++ b/gpxe/src/arch/i386/scripts/i386.lds
@@ -0,0 +1,202 @@
+/* -*- sh -*- */
+
+/*
+ * Linker script for i386 images
+ *
+ */
+
+SECTIONS {
+
+    /* Each section starts at a virtual address of zero.
+     *
+     * We guarantee alignment of virtual addresses to any alignment
+     * specified by the constituent object files (e.g. via
+     * __attribute__((aligned(x)))).  Load addresses are guaranteed
+     * only up to _max_align.  Provided that all loader and relocation
+     * code honours _max_align, this means that physical addresses are
+     * also guaranteed up to _max_align.
+     *
+     * Note that when using -DKEEP_IT_REAL, the UNDI segments are only
+     * guaranteed to be loaded on a paragraph boundary (i.e. 16-byte
+     * alignment).  Using _max_align>16 will therefore not guarantee
+     * >16-byte alignment of physical addresses when -DKEEP_IT_REAL is
+     * used (though virtual addresses will still be fully aligned).
+     *
+     */
+
+    /*
+     * The prefix
+     *
+     */
+
+    .prefix 0x0 : AT ( _prefix_lma ) {
+	_prefix = .;
+	*(.prefix)
+	*(.prefix.*)
+	_mprefix = .;
+    } .bss.prefix (NOLOAD) : AT ( _end_lma ) {
+	_eprefix = .;
+    }
+    _prefix_filesz	= ABSOLUTE ( _mprefix - _prefix );
+    _prefix_memsz	= ABSOLUTE ( _eprefix - _prefix );
+
+    /*
+     * The 16-bit (real-mode) code section
+     *
+     */
+
+    .text16 0x0 : AT ( _text16_lma ) {
+	_text16 = .;
+	*(.text16.null)
+	. += 1;				/* Prevent NULL being valid */
+	*(.text16)
+	*(.text16.*)
+	_mtext16 = .;
+    } .bss.text16 (NOLOAD) : AT ( _end_lma ) {
+	_etext16 = .;
+    }
+    _text16_filesz	= ABSOLUTE ( _mtext16 - _text16 );
+    _text16_memsz	= ABSOLUTE ( _etext16 - _text16 );
+
+    /*
+     * The 16-bit (real-mode) data section
+     *
+     */
+
+    .data16 0x0 : AT ( _data16_lma ) {
+	_data16 = .;
+	. += 1;				/* Prevent NULL being valid */
+	*(.rodata16)
+	*(.rodata16.*)
+	*(.data16)
+	*(.data16.*)
+	_mdata16 = .;
+    } .bss.data16 (NOLOAD) : AT ( _end_lma ) {
+	*(.bss16)
+	*(.bss16.*)
+	*(.stack16)
+	*(.stack16.*)
+	_edata16 = .;
+    }
+    _data16_filesz	= ABSOLUTE ( _mdata16 - _data16 );
+    _data16_memsz	= ABSOLUTE ( _edata16 - _data16 );
+
+    /*
+     * The 32-bit sections
+     *
+     */
+
+    .textdata 0x0 : AT ( _textdata_lma ) {
+	_textdata = .;
+	*(.text.null_trap)
+	. += 1;				/* Prevent NULL being valid */
+	*(.text)
+	*(.text.*)
+	*(.rodata)
+	*(.rodata.*)
+	*(.data)
+	*(.data.*)
+	*(SORT(.tbl.*))		/* Various tables.  See include/tables.h */
+	_mtextdata = .;
+    } .bss.textdata (NOLOAD) : AT ( _end_lma ) {
+	*(.bss)
+	*(.bss.*)
+	*(COMMON)
+	*(.stack)
+	*(.stack.*)
+	_etextdata = .;
+    }
+    _textdata_filesz	= ABSOLUTE ( _mtextdata - _textdata );
+    _textdata_memsz	= ABSOLUTE ( _etextdata - _textdata );
+
+    /*
+     * Compressor information block
+     *
+     */
+
+    .zinfo 0x0 : AT ( _zinfo_lma ) {
+	_zinfo = .;
+	*(.zinfo)
+	*(.zinfo.*)
+	_mzinfo = .;
+    } .bss.zinfo (NOLOAD) : AT ( _end_lma ) {
+	_ezinfo = .;
+    }
+    _zinfo_filesz	= ABSOLUTE ( _mzinfo - _zinfo );
+    _zinfo_memsz	= ABSOLUTE ( _ezinfo - _zinfo );
+
+    /*
+     * Weak symbols that need zero values if not otherwise defined
+     *
+     */
+
+    .weak 0x0 : AT ( _end_lma ) {
+	_weak = .;
+	*(.weak)
+	_eweak = .;
+    }
+    _assert = ASSERT ( ( _weak == _eweak ), ".weak is non-zero length" );
+
+    /*
+     * Dispose of the comment and note sections to make the link map
+     * easier to read
+     *
+     */
+
+    /DISCARD/ : {
+	*(.comment)
+	*(.comment.*)
+	*(.note)
+	*(.note.*)
+	*(.eh_frame)
+	*(.eh_frame.*)
+	*(.rel)
+	*(.rel.*)
+	*(.discard)
+    }
+
+    /*
+     * Load address calculations.  In older versions of ld, ALIGN()
+     * can operate only on the location counter, so we use that.
+     *
+     */
+
+    PROVIDE ( _max_align = 16 );
+    .			= 0;
+
+    .			= ALIGN ( _max_align );
+    _prefix_lma		= .;
+    .			+= _prefix_filesz;
+
+    .			= ALIGN ( _max_align );
+    _payload_lma	= .;
+    _text16_lma		= .;
+    .			+= _text16_filesz;
+
+    .			= ALIGN ( _max_align );
+    _data16_lma		= .;
+    .			+= _data16_filesz;
+
+    .			= ALIGN ( _max_align );
+    _textdata_lma	= .;
+    .			+= _textdata_filesz;
+
+    _filesz		= .; /* Do not include zinfo block in file size */
+
+    .			= ALIGN ( _max_align );
+    _zinfo_lma		= .;
+    .			+= _zinfo_filesz;
+
+    .			= ALIGN ( _max_align );
+    _end_lma		= .;
+
+    /*
+     * Values calculated to save code from doing it
+     *
+     */
+    _prefix_filesz_sect = ( ( _prefix_filesz + 511 ) / 512 );
+    _prefix_memsz_pgh	= ( ( _prefix_memsz + 15 ) / 16 );
+    _prefix_memsz_sect	= ( ( _prefix_memsz + 511 ) / 512 );
+    _text16_memsz_pgh	= ( ( _text16_memsz + 15 ) / 16 );
+    _data16_memsz_pgh	= ( ( _data16_memsz + 15 ) / 16 );
+}
diff --git a/gpxe/src/arch/i386/transitions/libkir.S b/gpxe/src/arch/i386/transitions/libkir.S
new file mode 100644
index 0000000..1176fcc
--- /dev/null
+++ b/gpxe/src/arch/i386/transitions/libkir.S
@@ -0,0 +1,256 @@
+/*
+ * libkir: a transition library for -DKEEP_IT_REAL
+ *
+ * Michael Brown <mbrown@fensystems.co.uk>
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+/****************************************************************************
+ * This file defines libkir: an interface between external and
+ * internal environments when -DKEEP_IT_REAL is used, so that both
+ * internal and external environments are in real mode.  It deals with
+ * switching data segments and the stack.  It provides the following
+ * functions:
+ *
+ * ext_to_kir &		switch between external and internal (kir)
+ * kir_to_ext		environments, preserving all non-segment
+ *			registers
+ *
+ * kir_call		issue a call to an internal routine from external
+ *			code
+ *
+ * libkir is written to avoid assuming that segments are anything
+ * other than opaque data types, and also avoids assuming that the
+ * stack pointer is 16-bit.  This should enable it to run just as well
+ * in 16:16 or 16:32 protected mode as in real mode.
+ ****************************************************************************
+ */
+
+/* Breakpoint for when debugging under bochs */
+#define BOCHSBP xchgw %bx, %bx
+
+	.text
+	.arch i386
+	.section ".text16", "awx", @progbits
+	.code16
+	
+/****************************************************************************
+ * init_libkir (real-mode or 16:xx protected-mode far call)
+ *
+ * Initialise libkir ready for transitions to the kir environment
+ *
+ * Parameters:
+ *   %cs : .text16 segment
+ *   %ds : .data16 segment
+ ****************************************************************************
+ */
+	.globl	init_libkir
+init_libkir:
+	/* Record segment registers */
+	pushw	%ds
+	popw	%cs:kir_ds
+	lret
+	
+/****************************************************************************
+ * ext_to_kir (real-mode or 16:xx protected-mode near call)
+ *
+ * Switch from external stack and segment registers to internal stack
+ * and segment registers.  %ss:sp is restored from the saved kir_ds
+ * and kir_sp.  %ds, %es, %fs and %gs are all restored from the saved
+ * kir_ds.  All other registers are preserved.
+ *
+ * %cs:0000 must point to the start of the runtime image code segment
+ * on entry.
+ *
+ * Parameters: none
+ ****************************************************************************
+ */
+
+	.globl	ext_to_kir
+ext_to_kir:
+	/* Record external segment registers */
+	movw	%ds, %cs:ext_ds
+	pushw	%cs
+	popw	%ds	/* Set %ds = %cs for easier access to variables */
+	movw	%es, %ds:ext_es
+	movw	%fs, %ds:ext_fs
+	movw	%gs, %ds:ext_fs
+
+	/* Preserve registers */
+	movw	%ax, %ds:save_ax
+
+	/* Extract near return address from stack */
+	popw	%ds:save_retaddr
+
+	/* Record external %ss:esp */
+	movw	%ss, %ds:ext_ss
+	movl	%esp, %ds:ext_esp
+
+	/* Load internal segment registers and stack pointer */
+	movw	%ds:kir_ds, %ax
+	movw	%ax, %ss
+	movzwl	%ds:kir_sp, %esp
+	movw	%ax, %ds
+	movw	%ax, %es
+	movw	%ax, %fs
+	movw	%ax, %gs
+1:
+
+	/* Place return address on new stack */
+	pushw	%cs:save_retaddr
+	
+	/* Restore registers and return */
+	movw	%cs:save_ax, %ax
+	ret
+
+/****************************************************************************
+ * kir_to_ext (real-mode or 16:xx protected-mode near call)
+ *
+ * Switch from internal stack and segment registers to external stack
+ * and segment registers.  %ss:%esp is restored from the saved ext_ss
+ * and ext_esp.  Other segment registers are restored from the
+ * corresponding locations.  All other registers are preserved.
+ *
+ * Note that it is actually %ss that is recorded as kir_ds, on the
+ * assumption that %ss == %ds when kir_to_ext is called.
+ *
+ * Parameters: none
+ ****************************************************************************
+ */
+
+	.globl	kir_to_ext
+kir_to_ext:
+	/* Record near return address */
+	pushw	%cs
+	popw	%ds	/* Set %ds = %cs for easier access to variables */
+	popw	%ds:save_retaddr
+	
+	/* Record internal segment registers and %sp */
+	movw	%ss, %ds:kir_ds
+	movw	%sp, %ds:kir_sp
+
+	/* Load external segment registers and stack pointer */
+	movw	%ds:ext_ss, %ss
+	movl	%ds:ext_esp, %esp
+	movw	%ds:ext_gs, %gs
+	movw	%ds:ext_fs, %fs
+	movw	%ds:ext_es, %es
+	movw	%ds:ext_ds, %ds
+
+	/* Return */
+	pushw	%cs:save_retaddr
+	ret
+	
+/****************************************************************************
+ * kir_call (real-mode or 16:xx protected-mode far call)
+ *
+ * Call a specific C function in the internal code.  The prototype of
+ * the C function must be
+ *   void function ( struct i386_all_resg *ix86 ); 
+ * ix86 will point to a struct containing the real-mode registers
+ * at entry to kir_call.
+ *
+ * All registers will be preserved across kir_call(), unless the C
+ * function explicitly overwrites values in ix86.  Interrupt status
+ * will also be preserved.
+ *
+ * Parameters:
+ *   function : (32-bit) virtual address of C function to call
+ *
+ * Example usage:
+ *	pushl	$pxe_api_call
+ *	lcall	$UNDI_CS, $kir_call
+ *	addw	$4, %sp
+ * to call in to the C function
+ *      void pxe_api_call ( struct i386_all_regs *ix86 );
+ ****************************************************************************
+ */
+
+	.globl	kir_call
+kir_call:
+	/* Preserve flags.  Must do this before any operation that may
+	 * affect flags.
+	 */
+	pushfl
+	popl	%cs:save_flags
+
+	/* Disable interrupts.  We do funny things with the stack, and
+	 * we're not re-entrant.
+	 */
+	cli
+		
+	/* Extract address of internal routine from stack.  We must do
+	 * this without using (%bp), because we may be called with
+	 * either a 16-bit or a 32-bit stack segment.
+	 */
+	popl	%cs:save_retaddr	/* Scratch location */
+	popl	%cs:save_function
+	subl	$8, %esp		/* Restore %esp */
+	
+	/* Switch to internal stack.  Note that the external stack is
+	 * inaccessible once we're running internally (since we have
+	 * no concept of 48-bit far pointers)
+	 */
+	call	ext_to_kir
+	
+	/* Store external registers on internal stack */
+	pushl	%cs:save_flags
+	pushal
+	pushl	%cs:ext_fs_and_gs
+	pushl	%cs:ext_ds_and_es
+	pushl	%cs:ext_cs_and_ss
+
+	/* Push &ix86 on stack and call function */
+	sti
+	pushl	%esp
+	data32 call *%cs:save_function
+	popl	%eax /* discard */
+	
+	/* Restore external registers from internal stack */
+	popl	%cs:ext_cs_and_ss
+	popl	%cs:ext_ds_and_es
+	popl	%cs:ext_fs_and_gs
+	popal
+	popl	%cs:save_flags
+
+	/* Switch to external stack */
+	call	kir_to_ext
+
+	/* Restore flags */
+	pushl	%cs:save_flags
+	popfl
+
+	/* Return */
+	lret
+
+/****************************************************************************
+ * Stored internal and external stack and segment registers
+ ****************************************************************************
+ */
+	
+ext_cs_and_ss:	
+ext_cs:		.word 0
+ext_ss:		.word 0
+ext_ds_and_es:	
+ext_ds:		.word 0
+ext_es:		.word 0
+ext_fs_and_gs:	
+ext_fs:		.word 0
+ext_gs:		.word 0
+ext_esp:	.long 0
+
+		.globl kir_ds
+kir_ds:		.word 0
+		.globl kir_sp
+kir_sp:		.word _estack
+
+/****************************************************************************
+ * Temporary variables
+ ****************************************************************************
+ */
+save_ax:	.word 0
+save_retaddr:	.long 0
+save_flags:	.long 0
+save_function:	.long 0
diff --git a/gpxe/src/arch/i386/transitions/libpm.S b/gpxe/src/arch/i386/transitions/libpm.S
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gpxe/src/arch/i386/transitions/libpm.S
diff --git a/gpxe/src/arch/i386/transitions/librm.S b/gpxe/src/arch/i386/transitions/librm.S
new file mode 100644
index 0000000..cb27ef3
--- /dev/null
+++ b/gpxe/src/arch/i386/transitions/librm.S
@@ -0,0 +1,581 @@
+/*
+ * librm: a library for interfacing to real-mode code
+ *
+ * Michael Brown <mbrown@fensystems.co.uk>
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER )
+
+/* Drag in local definitions */
+#include "librm.h"
+
+/* For switches to/from protected mode */
+#define CR0_PE 1
+
+/* Size of various C data structures */
+#define SIZEOF_I386_SEG_REGS	12
+#define SIZEOF_I386_REGS	32
+#define SIZEOF_REAL_MODE_REGS	( SIZEOF_I386_SEG_REGS + SIZEOF_I386_REGS )
+#define SIZEOF_I386_FLAGS	4
+#define SIZEOF_I386_ALL_REGS	( SIZEOF_REAL_MODE_REGS + SIZEOF_I386_FLAGS )
+	
+	.arch i386
+
+/****************************************************************************
+ * Global descriptor table
+ *
+ * Call init_librm to set up the GDT before attempting to use any
+ * protected-mode code.
+ *
+ * Define FLATTEN_REAL_MODE if you want to use so-called "flat real
+ * mode" with 4GB limits instead.
+ *
+ * NOTE: This must be located before prot_to_real, otherwise gas
+ * throws a "can't handle non absolute segment in `ljmp'" error due to
+ * not knowing the value of REAL_CS when the ljmp is encountered.
+ *
+ * Note also that putting ".word gdt_end - gdt - 1" directly into
+ * gdt_limit, rather than going via gdt_length, will also produce the
+ * "non absolute segment" error.  This is most probably a bug in gas.
+ ****************************************************************************
+ */
+	
+#ifdef FLATTEN_REAL_MODE
+#define RM_LIMIT_16_19__AVL__SIZE__GRANULARITY 0x8f
+#else
+#define RM_LIMIT_16_19__AVL__SIZE__GRANULARITY 0x00
+#endif
+	.section ".data16", "aw", @progbits
+	.align 16
+gdt:
+gdtr:		/* The first GDT entry is unused, the GDTR can fit here. */
+gdt_limit:		.word gdt_length - 1
+gdt_base:		.long 0
+			.word 0 /* padding */
+
+	.org	gdt + VIRTUAL_CS, 0
+virtual_cs:	/* 32 bit protected mode code segment, virtual addresses */
+	.word	0xffff, 0
+	.byte	0, 0x9f, 0xcf, 0
+
+	.org	gdt + VIRTUAL_DS, 0
+virtual_ds:	/* 32 bit protected mode data segment, virtual addresses */
+	.word	0xffff, 0
+	.byte	0, 0x93, 0xcf, 0
+	
+	.org	gdt + PHYSICAL_CS, 0
+physical_cs:	/* 32 bit protected mode code segment, physical addresses */
+	.word	0xffff, 0
+	.byte	0, 0x9f, 0xcf, 0
+
+	.org	gdt + PHYSICAL_DS, 0
+physical_ds:	/* 32 bit protected mode data segment, physical addresses */
+	.word	0xffff, 0
+	.byte	0, 0x93, 0xcf, 0	
+
+	.org	gdt + REAL_CS, 0
+real_cs: 	/* 16 bit real mode code segment */
+	.word	0xffff, 0
+	.byte	0, 0x9b, RM_LIMIT_16_19__AVL__SIZE__GRANULARITY, 0
+
+	.org	gdt + REAL_DS	
+real_ds:	/* 16 bit real mode data segment */
+	.word	0xffff, 0
+	.byte	0, 0x93, RM_LIMIT_16_19__AVL__SIZE__GRANULARITY, 0
+	
+gdt_end:
+	.equ	gdt_length, gdt_end - gdt
+
+/****************************************************************************
+ * init_librm (real-mode far call, 16-bit real-mode far return address)
+ *
+ * Initialise the GDT ready for transitions to protected mode.
+ *
+ * Parameters:
+ *   %cs : .text16 segment
+ *   %ds : .data16 segment
+ *   %edi : Physical base of protected-mode code (virt_offset)
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+	.code16
+	.globl init_librm
+init_librm:
+	/* Preserve registers */
+	pushl	%eax
+	pushl	%ebx
+
+	/* Store _virt_offset and set up virtual_cs and virtual_ds segments */
+	movl	%edi, %eax
+	movw	$virtual_cs, %bx
+	call	set_seg_base
+	movw	$virtual_ds, %bx
+	call	set_seg_base	
+	movl	%edi, _virt_offset
+
+	/* Negate virt_offset */
+	negl	%edi
+		
+	/* Store rm_cs and _text16, set up real_cs segment */
+	xorl	%eax, %eax
+	movw	%cs, %ax
+	movw	%ax, rm_cs
+	shll	$4, %eax
+	movw	$real_cs, %bx
+	call	set_seg_base
+	addr32 leal	(%eax, %edi), %ebx
+	movl	%ebx, _text16
+
+	/* Store rm_ds and _data16, set up real_ds segment */
+	xorl	%eax, %eax
+	movw	%ds, %ax
+	movw	%ax, %cs:rm_ds
+	shll	$4, %eax
+	movw	$real_ds, %bx
+	call	set_seg_base
+	addr32 leal	(%eax, %edi), %ebx
+	movl	%ebx, _data16
+
+	/* Set GDT and IDT base */
+	movl	%eax, gdt_base
+	addl	$gdt, gdt_base
+	call	idt_init
+
+	/* Restore registers */
+	negl	%edi
+	popl	%ebx
+	popl	%eax
+	lret
+
+	.section ".text16", "ax", @progbits
+	.code16
+	.weak idt_init
+set_seg_base:
+1:	movw	%ax, 2(%bx)
+	rorl	$16, %eax
+	movb	%al, 4(%bx)
+	movb	%ah, 7(%bx)
+	roll	$16, %eax
+idt_init: /* Reuse the return opcode here */
+	ret
+
+/****************************************************************************
+ * real_to_prot (real-mode near call, 32-bit virtual return address)
+ *
+ * Switch from 16-bit real-mode to 32-bit protected mode with virtual
+ * addresses.  The real-mode %ss:sp is stored in rm_ss and rm_sp, and
+ * the protected-mode %esp is restored from the saved pm_esp.
+ * Interrupts are disabled.  All other registers may be destroyed.
+ *
+ * The return address for this function should be a 32-bit virtual
+ * address.
+ *
+ * Parameters: 
+ *   %ecx : number of bytes to move from RM stack to PM stack
+ *
+ ****************************************************************************
+ */
+	.section ".text16", "ax", @progbits
+	.code16
+real_to_prot:
+	/* Make sure we have our data segment available */
+	movw	%cs:rm_ds, %ax
+	movw	%ax, %ds
+	
+	/* Add _virt_offset, _text16 and _data16 to stack to be
+	 * copied, and also copy the return address.
+	 */
+	pushl	_virt_offset
+	pushl	_text16
+	pushl	_data16
+	addw	$16, %cx /* %ecx must be less than 64kB anyway */
+	
+	/* Real-mode %ss:%sp => %ebp:%edx and virtual address => %esi */
+	xorl	%ebp, %ebp
+	movw	%ss, %bp
+	movzwl	%sp, %edx
+	movl	%ebp, %eax
+	shll	$4, %eax
+	addr32 leal (%eax,%edx), %esi
+	subl	_virt_offset, %esi
+
+	/* Switch to protected mode */
+	cli
+	data32 lgdt gdtr
+	data32 lidt idtr
+	movl	%cr0, %eax
+	orb	$CR0_PE, %al
+	movl	%eax, %cr0
+	data32 ljmp	$VIRTUAL_CS, $1f
+	.section ".text", "ax", @progbits
+	.code32
+1:
+	/* Set up protected-mode data segments and stack pointer */
+	movw	$VIRTUAL_DS, %ax
+	movw	%ax, %ds
+	movw	%ax, %es
+	movw	%ax, %fs
+	movw	%ax, %gs
+	movw	%ax, %ss
+	movl	pm_esp, %esp
+
+	/* Record real-mode %ss:sp (after removal of data) */
+	movw	%bp, rm_ss
+	addl	%ecx, %edx
+	movw	%dx, rm_sp
+
+	/* Move data from RM stack to PM stack */
+	subl	%ecx, %esp
+	movl	%esp, %edi
+	rep movsb
+
+	/* Publish virt_offset, text16 and data16 for PM code to use */
+	popl	data16
+	popl	text16
+	popl	virt_offset
+
+	/* Return to virtual address */
+	ret
+
+	/* Default IDTR with no interrupts */
+	.section ".data16", "aw", @progbits
+	.weak idtr
+idtr:
+rm_idtr:
+	.word 0xffff /* limit */
+	.long 0 /* base */
+
+/****************************************************************************
+ * prot_to_real (protected-mode near call, 32-bit real-mode return address)
+ *
+ * Switch from 32-bit protected mode with virtual addresses to 16-bit
+ * real mode.  The protected-mode %esp is stored in pm_esp and the
+ * real-mode %ss:sp is restored from the saved rm_ss and rm_sp.  The
+ * high word of the real-mode %esp is set to zero.  All real-mode data
+ * segment registers are loaded from the saved rm_ds.  Interrupts are
+ * *not* enabled, since we want to be able to use prot_to_real in an
+ * ISR.  All other registers may be destroyed.
+ *
+ * The return address for this function should be a 32-bit (sic)
+ * real-mode offset within .code16.
+ *
+ * Parameters: 
+ *   %ecx : number of bytes to move from PM stack to RM stack
+ *
+ ****************************************************************************
+ */
+	.section ".text", "ax", @progbits
+	.code32
+prot_to_real:
+	/* Add return address to data to be moved to RM stack */
+	addl	$4, %ecx
+	
+	/* Real-mode %ss:sp => %ebp:edx and virtual address => %edi */
+	movzwl	rm_ss, %ebp
+	movzwl	rm_sp, %edx
+	subl	%ecx, %edx
+	movl	%ebp, %eax
+	shll	$4, %eax
+	leal	(%eax,%edx), %edi
+	subl	virt_offset, %edi
+	
+	/* Move data from PM stack to RM stack */
+	movl	%esp, %esi
+	rep movsb
+	
+	/* Record protected-mode %esp (after removal of data) */
+	movl	%esi, pm_esp
+
+	/* Load real-mode segment limits */
+	movw	$REAL_DS, %ax
+	movw	%ax, %ds
+	movw	%ax, %es
+	movw	%ax, %fs
+	movw	%ax, %gs
+	movw	%ax, %ss
+	ljmp	$REAL_CS, $1f
+	.section ".text16", "ax", @progbits
+	.code16
+1:
+	/* Switch to real mode */
+	movl	%cr0, %eax
+	andb	$0!CR0_PE, %al
+	movl	%eax, %cr0
+	ljmp	*p2r_jump_vector
+p2r_jump_target:
+
+	/* Set up real-mode data segments and stack pointer */
+	movw	%cs:rm_ds, %ax
+	movw	%ax, %ds
+	movw	%ax, %es
+	movw	%ax, %fs
+	movw	%ax, %gs
+	movw	%bp, %ss
+	movl	%edx, %esp
+
+	/* Reset IDTR to the real-mode defaults */
+	data32 lidt rm_idtr
+
+	/* Return to real-mode address */
+	data32 ret
+
+
+	/* Real-mode code and data segments.  Assigned by the call to
+	 * init_librm.  rm_cs doubles as the segment part of the jump
+	 * vector used by prot_to_real.  rm_ds is located in .text16
+	 * rather than .data16 because code needs to be able to locate
+	 * the data segment.
+	 */
+	.section ".data16", "aw", @progbits
+p2r_jump_vector:
+	.word	p2r_jump_target
+	.globl rm_cs
+rm_cs:	.word 0
+	.globl rm_ds
+	.section ".text16.data", "aw", @progbits
+rm_ds:	.word 0
+
+/****************************************************************************
+ * prot_call (real-mode far call, 16-bit real-mode far return address)
+ *
+ * Call a specific C function in the protected-mode code.  The
+ * prototype of the C function must be
+ *   void function ( struct i386_all_regs *ix86 ); 
+ * ix86 will point to a struct containing the real-mode registers
+ * at entry to prot_call.  
+ *
+ * All registers will be preserved across prot_call(), unless the C
+ * function explicitly overwrites values in ix86.  Interrupt status
+ * and GDT will also be preserved.  Gate A20 will be enabled.
+ *
+ * Note that prot_call() does not rely on the real-mode stack
+ * remaining intact in order to return, since everything relevant is
+ * copied to the protected-mode stack for the duration of the call.
+ * In particular, this means that a real-mode prefix can make a call
+ * to main() which will return correctly even if the prefix's stack
+ * gets vapourised during the Etherboot run.  (The prefix cannot rely
+ * on anything else on the stack being preserved, so should move any
+ * critical data to registers before calling main()).
+ *
+ * Parameters:
+ *   function : virtual address of protected-mode function to call
+ *
+ * Example usage:
+ *	pushl	$pxe_api_call
+ *	call	prot_call
+ *	addw	$4, %sp
+ * to call in to the C function
+ *      void pxe_api_call ( struct i386_all_regs *ix86 );
+ ****************************************************************************
+ */
+
+#define PC_OFFSET_GDT ( 0 )
+#define PC_OFFSET_IDT ( PC_OFFSET_GDT + 8 /* pad to 8 to keep alignment */ )
+#define PC_OFFSET_IX86 ( PC_OFFSET_IDT + 8 /* pad to 8 to keep alignment */ )
+#define PC_OFFSET_RETADDR ( PC_OFFSET_IX86 + SIZEOF_I386_ALL_REGS )
+#define PC_OFFSET_FUNCTION ( PC_OFFSET_RETADDR + 4 )
+#define PC_OFFSET_END ( PC_OFFSET_FUNCTION + 4 )
+
+	.section ".text16", "ax", @progbits
+	.code16
+	.globl prot_call
+prot_call:
+	/* Preserve registers, flags and GDT on external RM stack */
+	pushfl
+	pushal
+	pushw	%gs
+	pushw	%fs
+	pushw	%es
+	pushw	%ds
+	pushw	%ss
+	pushw	%cs
+	subw	$16, %sp
+	movw	%sp, %bp
+	sidt	8(%bp)
+	sgdt	(%bp)
+
+	/* For sanity's sake, clear the direction flag as soon as possible */
+	cld
+
+	/* Switch to protected mode and move register dump to PM stack */
+	movl	$PC_OFFSET_END, %ecx
+	pushl	$1f
+	jmp	real_to_prot
+	.section ".text", "ax", @progbits
+	.code32
+1:
+	/* Set up environment expected by C code */
+	call	gateA20_set
+
+	/* Call function */
+	leal	PC_OFFSET_IX86(%esp), %eax
+	pushl	%eax
+	call	*(PC_OFFSET_FUNCTION+4)(%esp)
+	popl	%eax /* discard */
+
+	/* Switch to real mode and move register dump back to RM stack */
+	movl	$PC_OFFSET_END, %ecx
+	pushl	$1f
+	jmp	prot_to_real
+	.section ".text16", "ax", @progbits
+	.code16
+1:	
+	/* Reload GDT and IDT, restore registers and flags and return */
+	movw	%sp, %bp
+	data32 lgdt (%bp)
+	data32 lidt 8(%bp)
+	addw	$20, %sp /* also skip %cs and %ss */
+	popw	%ds
+	popw	%es
+	popw	%fs
+	popw	%gs
+	popal
+	/* popal skips %esp.  We therefore want to do "movl -20(%sp),
+	 * %esp", but -20(%sp) is not a valid 80386 expression.
+	 * Fortunately, prot_to_real() zeroes the high word of %esp, so
+	 * we can just use -20(%esp) instead.
+	 */
+	addr32 movl -20(%esp), %esp
+	popfl
+	lret
+
+/****************************************************************************
+ * real_call (protected-mode near call, 32-bit virtual return address)
+ *
+ * Call a real-mode function from protected-mode code.
+ *
+ * The non-segment register values will be passed directly to the
+ * real-mode code.  The segment registers will be set as per
+ * prot_to_real.  The non-segment register values set by the real-mode
+ * function will be passed back to the protected-mode caller.  A
+ * result of this is that this routine cannot be called directly from
+ * C code, since it clobbers registers that the C ABI expects the
+ * callee to preserve.  Gate A20 will *not* be automatically
+ * re-enabled.  Since we always run from an even megabyte of memory,
+ * we are guaranteed to return successfully to the protected-mode
+ * code, which should then call gateA20_set() if it suspects that gate
+ * A20 may have been disabled.  Note that enabling gate A20 is a
+ * potentially slow operation that may also cause keyboard input to be
+ * lost; this is why it is not done automatically.
+ *
+ * librm.h defines a convenient macro REAL_CODE() for using real_call.
+ * See librm.h and realmode.h for details and examples.
+ *
+ * Parameters:
+ *   (32-bit) near pointer to real-mode function to call
+ *
+ * Returns: none
+ ****************************************************************************
+ */
+
+#define RC_OFFSET_PRESERVE_REGS ( 0 )
+#define RC_OFFSET_RETADDR ( RC_OFFSET_PRESERVE_REGS + SIZEOF_I386_REGS )
+#define RC_OFFSET_FUNCTION ( RC_OFFSET_RETADDR + 4 )
+#define RC_OFFSET_END ( RC_OFFSET_FUNCTION + 4 )
+
+	.section ".text", "ax", @progbits
+	.code32
+	.globl real_call
+real_call:
+	/* Create register dump and function pointer copy on PM stack */
+	pushal
+	pushl	RC_OFFSET_FUNCTION(%esp)
+
+	/* Switch to real mode and move register dump to RM stack  */
+	movl	$( RC_OFFSET_RETADDR + 4 /* function pointer copy */ ), %ecx
+	pushl	$1f
+	jmp	prot_to_real
+	.section ".text16", "ax", @progbits
+	.code16
+1:
+	/* Call real-mode function */
+	popl	rc_function
+	popal
+	call	*rc_function
+	pushal
+
+	/* For sanity's sake, clear the direction flag as soon as possible */
+	cld
+
+	/* Switch to protected mode and move register dump back to PM stack */
+	movl	$RC_OFFSET_RETADDR, %ecx
+	pushl	$1f
+	jmp	real_to_prot
+	.section ".text", "ax", @progbits
+	.code32
+1:
+	/* Restore registers and return */
+	popal
+	ret
+
+
+	/* Function vector, used because "call xx(%sp)" is not a valid
+	 * 16-bit expression.
+	 */
+	.section ".data16", "aw", @progbits
+rc_function:	.word 0, 0
+
+/****************************************************************************
+ * Stored real-mode and protected-mode stack pointers
+ *
+ * The real-mode stack pointer is stored here whenever real_to_prot
+ * is called and restored whenever prot_to_real is called.  The
+ * converse happens for the protected-mode stack pointer.
+ *
+ * Despite initial appearances this scheme is, in fact re-entrant,
+ * because program flow dictates that we always return via the point
+ * we left by.  For example:
+ *    PXE API call entry
+ *  1   real => prot
+ *        ...
+ *        Print a text string
+ *	    ...
+ *  2       prot => real
+ *            INT 10
+ *  3       real => prot
+ *	    ...
+ *        ...
+ *  4   prot => real
+ *    PXE API call exit
+ *
+ * At point 1, the RM mode stack value, say RPXE, is stored in
+ * rm_ss,sp.  We want this value to still be present in rm_ss,sp when
+ * we reach point 4.
+ *
+ * At point 2, the RM stack value is restored from RPXE.  At point 3,
+ * the RM stack value is again stored in rm_ss,sp.  This *does*
+ * overwrite the RPXE that we have stored there, but it's the same
+ * value, since the code between points 2 and 3 has managed to return
+ * to us.
+ ****************************************************************************
+ */
+	.section ".data", "aw", @progbits
+	.globl rm_sp
+rm_sp:	.word 0
+	.globl rm_ss
+rm_ss:	.word 0
+pm_esp:	.long _estack
+
+/****************************************************************************
+ * Virtual address offsets
+ *
+ * These are used by the protected-mode code to map between virtual
+ * and physical addresses, and to access variables in the .text16 or
+ * .data16 segments.
+ ****************************************************************************
+ */
+	/* Internal copies, created by init_librm (which runs in real mode) */
+	.section ".data16", "aw", @progbits
+_virt_offset:	.long 0
+_text16:	.long 0
+_data16:	.long 0
+
+	/* Externally-visible copies, created by real_to_prot */
+	.section ".data", "aw", @progbits
+	.globl virt_offset
+virt_offset:	.long 0	
+	.globl text16
+text16:		.long 0
+	.globl data16
+data16:		.long 0
diff --git a/gpxe/src/arch/i386/transitions/librm_mgmt.c b/gpxe/src/arch/i386/transitions/librm_mgmt.c
new file mode 100644
index 0000000..f00be81
--- /dev/null
+++ b/gpxe/src/arch/i386/transitions/librm_mgmt.c
@@ -0,0 +1,58 @@
+/*
+ * librm: a library for interfacing to real-mode code
+ *
+ * Michael Brown <mbrown@fensystems.co.uk>
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <realmode.h>
+
+/*
+ * This file provides functions for managing librm.
+ *
+ */
+
+/**
+ * Allocate space on the real-mode stack and copy data there from a
+ * user buffer
+ *
+ * @v data			User buffer
+ * @v size			Size of stack data
+ * @ret sp			New value of real-mode stack pointer
+ */
+uint16_t copy_user_to_rm_stack ( userptr_t data, size_t size ) {
+	userptr_t rm_stack;
+	rm_sp -= size;
+	rm_stack = real_to_user ( rm_ss, rm_sp );
+	memcpy_user ( rm_stack, 0, data, 0, size );
+	return rm_sp;
+};
+
+/**
+ * Deallocate space on the real-mode stack, optionally copying back
+ * data to a user buffer.
+ *
+ * @v data			User buffer
+ * @v size			Size of stack data
+ */
+void remove_user_from_rm_stack ( userptr_t data, size_t size ) {
+	if ( data ) {
+		userptr_t rm_stack = real_to_user ( rm_ss, rm_sp );
+		memcpy_user ( rm_stack, 0, data, 0, size );
+	}
+	rm_sp += size;
+};
+
+PROVIDE_UACCESS_INLINE ( librm, phys_to_user );
+PROVIDE_UACCESS_INLINE ( librm, user_to_phys );
+PROVIDE_UACCESS_INLINE ( librm, virt_to_user );
+PROVIDE_UACCESS_INLINE ( librm, user_to_virt );
+PROVIDE_UACCESS_INLINE ( librm, userptr_add );
+PROVIDE_UACCESS_INLINE ( librm, memcpy_user );
+PROVIDE_UACCESS_INLINE ( librm, memmove_user );
+PROVIDE_UACCESS_INLINE ( librm, memset_user );
+PROVIDE_UACCESS_INLINE ( librm, strlen_user );
+PROVIDE_UACCESS_INLINE ( librm, memchr_user );