diff options
Diffstat (limited to 'gpxe/src/arch')
131 files changed, 19114 insertions, 0 deletions
diff --git a/gpxe/src/arch/i386/Config b/gpxe/src/arch/i386/Config new file mode 100644 index 00000000..1c086ecc --- /dev/null +++ b/gpxe/src/arch/i386/Config @@ -0,0 +1,148 @@ +# -*- makefile -*- + +############################################################################## +############################################################################## +# +# IMPORTANT! +# +# The use of this file to set options that affect only single object +# files is deprecated, because changing anything in this file results +# in a complete rebuild, which is slow. All options are gradually +# being migrated to config.h, which does not suffer from this problem. +# +# Only options that affect the entire build (e.g. overriding the $(CC) +# Makefile variable) should be placed in here. +# +############################################################################## +############################################################################## + + +# Config for i386 Etherboot +# +# Do not delete the tag OptionDescription and /OptionDescription +# It is used to automatically generate the documentation. +# +# @OptionDescrition@ +# +# BIOS interface options: +# +# -DPCBIOS +# Compile in support for the normal pcbios +# -DLINUXBIOS +# Compile in support for LinuxBIOS +# -DBBS_BUT_NOT_PNP_COMPLIANT +# Some BIOSes claim to be PNP but they don't conform +# to the BBS spec which specifies that ES:DI must +# point to the string $PnP on entry. This option +# works around those. This option must be added to +# LCONFIG. +# -DNO_DELAYED_INT +# Take control as soon as BIOS detects the ROM. +# Normally hooks onto INT18H or INT19H. Use only if you +# have a very non-conformant BIOS as it bypasses +# BIOS initialisation of devices. This only works for +# legacy ROMs, i.e. PCI_PNP_HEADER not defined. +# This option was formerly called NOINT19H. +# -DBOOT_INT18H +# Etherboot normally hooks onto INT19H for legacy ROMs. +# You can choose to hook onto INT18H (BASIC interpreter +# entry point) instead. This entry point is used when +# all boot devices have been exhausted. This option must +# be added to LCONFIG. +# -DCONFIG_PCI_DIRECT +# Define this for PCI BIOSes that do not implement +# BIOS32 or not correctly. Normally not needed. +# Only works for BIOSes of a certain era. +# -DCONFIG_TSC_CURRTICKS +# Uses the processor time stamp counter instead of reading +# the BIOS time counter. This allows Etherboot to work +# even without a BIOS. This only works on late model +# 486s and above. +# -DCONFIG_NO_TIMER2 +# Some systems do not have timer2 implemented. +# If you have a RTC this will allow you to roughly calibrate +# it using outb instructions. +# +# Extended cpu options + +# -DCONFIG_X86_64 +# Compile in support for booting x86_64 64bit binaries. +# +# PXE loader options: +# +# -DPXELOADER_KEEP_ALL +# Prevent PXE loader (prefix) from unloading the +# PXE stack. You will want to use this if, for +# example, you are booting via PXE-on-floppy. +# You may want to use it under certain +# circumstances when using the Etherboot UNDI +# driver; these are complex and best practice is +# not yet established. +# +# Obscure options you probably don't need to touch: +# +# -DIGNORE_E820_MAP +# Ignore the memory map returned by the E820 BIOS +# call. May be necessary on some buggy BIOSes. +# -DT503_AUI +# Use AUI by default on 3c503 cards. +# -DFLATTEN_REAL_MODE +# Use 4GB segment limits when calling out to or +# returning to real-mode code. This is necessary to +# work around some buggy code (e.g. OpenBSD's pxeboot) +# that uses flat real-mode without being sufficiently +# paranoid about the volatility of its segment limits. + +# +# @/OptionDescription@ + +# BIOS select don't change unless you know what you are doing +# CFLAGS+= -DPCBIOS + +# Compile in k8/hammer support +# CFLAGS+= -DCONFIG_X86_64 + +# Options to make a version of Etherboot that will work under linuxBIOS. +# CFLAGS+= -DLINUXBIOS -DCONFIG_TSC_CURRTICKS -DCONSOLE_SERIAL -DCOMCONSOLE=0x3f8 -DCOMPRESERVE -DCONFIG_PCI_DIRECT -DELF_IMAGE + +# These options affect the loader that is prepended to the Etherboot image +# LCONFIG+= -DBBS_BUT_NOT_PNP_COMPLIANT +# LCONFIG+= -DBOOT_INT18H + +# Produce code that will work with OpenBSD's pxeboot +# CFLAGS+= -DFLATTEN_REAL_MODE + +CFLAGS+= -fstrength-reduce -fomit-frame-pointer -march=i386 +# Squeeze the code in as little space as possible. +# gcc3 needs a different syntax to gcc2 if you want to avoid spurious warnings. +GCC_VERSION = $(subst ., ,$(shell $(CC) -dumpversion)) +GCC_MAJORVERSION = $(firstword $(GCC_VERSION)) +ifeq ($(GCC_MAJORVERSION),2) +CFLAGS+= -malign-jumps=1 -malign-loops=1 -malign-functions=1 +else +CFLAGS+= -falign-jumps=1 -falign-loops=1 -falign-functions=1 +endif + +# this is almost always a win. the kernel uses it, too. +CFLAGS+= -mpreferred-stack-boundary=2 + +# use regparm for all functions - C functions called from assembly (or +# vice versa) need __cdecl now +CFLAGS+= -mregparm=3 + +# use -mrtd (same __cdecl requirements as above) +CFLAGS+= -mrtd + +# 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 + +LDFLAGS+= -N --no-check-sections + +ifeq "$(shell uname -s)" "FreeBSD" +CFLAGS+= -DIMAGE_FREEBSD -DELF_IMAGE -DAOUT_IMAGE +endif + +# An alternate location for isolinux.bin can be set here +# ISOLINUX_BIN=/path/to/isolinux.bin diff --git a/gpxe/src/arch/i386/Makefile b/gpxe/src/arch/i386/Makefile new file mode 100644 index 00000000..da7976df --- /dev/null +++ b/gpxe/src/arch/i386/Makefile @@ -0,0 +1,107 @@ +# 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/bus +SRCDIRS += arch/i386/drivers/net +SRCDIRS += arch/i386/drivers/disk +SRCDIRS += arch/i386/interface/pcbios +SRCDIRS += arch/i386/interface/pxe + +# 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 + +# unnrv2b.S is used to generate a 16-bit as well as a 32-bit object. +# +OBJS_unnrv2b = unnrv2b unnrv2b16 +CFLAGS_unnrv2b16 = -DCODE16 + +# We need to undefine the default macro "i386" when compiling .S +# files, otherwise ".arch i386" translates to ".arch 1"... +# +CFLAGS_S += -Ui386 + +# The i386 linker script +# +LDSCRIPT = arch/i386/scripts/i386.lds + +# Media types. +# +MEDIA += rom +MEDIA += pxe +MEDIA += kpxe +MEDIA += elf +MEDIA += elfd +MEDIA += lmelf +MEDIA += lmelfd +MEDIA += lkrn +MEDIA += bImage +MEDIA += dsk +MEDIA += nbi +MEDIA += hd +MEDIA += raw +MEDIA += com +MEDIA += exe + +# Special target for building Master Boot Record binary +$(BIN)/mbr.bin : $(BIN)/mbr.o + $(OBJCOPY) -O binary $< $@ + +# 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 + dd if=$< bs=512 conv=sync of=/dev/fd0 + sync + +# rule to create padded disk images +NON_AUTO_MEDIA += pdsk +%pdsk : %dsk + cp $< $@ + $(PERL) ./util/dskpad.pl $@ + +# rule to make a non-emulation ISO boot image +NON_AUTO_MEDIA += iso +%iso: %lkrn util/geniso + ISOLINUX_BIN=$(ISOLINUX_BIN) bash util/geniso $@ $< + +# rule to make a floppy emulation ISO boot image +NON_AUTO_MEDIA += liso +%liso: %lkrn util/genliso + bash util/genliso $@ $< + +# rule to make a USB disk image +$(BIN)/usbdisk.bin : $(BIN)/usbdisk.o + $(OBJCOPY) -O binary $< $@ + +NON_AUTO_MEDIA += usb +%usb: $(BIN)/usbdisk.bin %hd + cat $^ > $@ + +# Add NON_AUTO_MEDIA to the media list, so that they show up in the +# output of "make" +# +MEDIA += $(NON_AUTO_MEDIA) + +# Shortcut to allow typing just +# make bin-kir/% +# rather than +# make -f arch/i386/kir-Makefile bin-kir/% +# for building a KEEP_IT_REAL flavour. +# +$(BIN)-kir/% : kir-target + $(MAKE) -f arch/i386/kir-Makefile $(MAKECMDGOALS) + +.PHONY : kir-target diff --git a/gpxe/src/arch/i386/README.i386 b/gpxe/src/arch/i386/README.i386 new file mode 100644 index 00000000..b9b79cc4 --- /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 00000000..f85620e9 --- /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 00000000..64e0bcc1 --- /dev/null +++ b/gpxe/src/arch/i386/core/basemem_packet.c @@ -0,0 +1,30 @@ +/* + * 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 + * + * 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 00000000..c24fa4e6 --- /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/etherboot.prefix.lds b/gpxe/src/arch/i386/core/etherboot.prefix.lds new file mode 100644 index 00000000..3550a2a3 --- /dev/null +++ b/gpxe/src/arch/i386/core/etherboot.prefix.lds @@ -0,0 +1,100 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) + +ENTRY(_prefix_start) +SECTIONS { + /* Prefix */ + .prefix : { + _verbatim_start = . ; + _prefix_start = . ; + *(.prefix) + . = ALIGN(16); + _prefix_end = . ; + } = 0x9090 + _prefix_size = _prefix_end - _prefix_start; + + .text.nocompress : { + *(.prefix.udata) + } = 0x9090 + + decompress_to = . ; + .prefix.zdata : { + _compressed = . ; + *(.prefix.zdata) + _compressed_end = . ; + } + _compressed_size = _compressed_end - _compressed; + + . = ALIGN(16); + _verbatim_end = . ; + + + /* Size of the core of etherboot in memory */ + _base_size = _end - _text; + + /* _prefix_size is the length of the non-core etherboot prefix */ + _prefix_size = _prefix_end - _prefix_start; + + /* _verbatim_size is the actual amount that has to be copied to base memory */ + _verbatim_size = _verbatim_end - _verbatim_start; + + /* _image_size is the amount of base memory needed to run */ + _image_size = _base_size + _prefix_size; + + /* Standard sizes rounded up to paragraphs */ + _prefix_size_pgh = (_prefix_size + 15) / 16; + _verbatim_size_pgh = (_verbatim_size + 15) / 16; + _image_size_pgh = (_image_size + 15) / 16 ; + + /* Standard sizes in sectors */ + _prefix_size_sct = (_prefix_size + 511) / 512; + _verbatim_size_sct = (_verbatim_size + 511) / 512; + _image_size_sct = (_image_size + 511) / 512; + + /* Symbol offsets and sizes for the exe prefix */ + _exe_hdr_size = 32; + _exe_size = _verbatim_size; /* Should this be - 32 to exclude the header? */ + _exe_size_tail = (_exe_size) % 512; + _exe_size_pages = ((_exe_size) + 511) / 512; + _exe_bss_size = ((_image_size - _verbatim_size) + 15) / 16; + _exe_ss_offset = (_stack_offset + _prefix_size - _exe_hdr_size + 15) / 16 ; + + /* This is where we copy the compressed image before decompression. + * Prepare to decompress in place. The end mark is about 8.25 bytes long, + * and the worst case symbol is about 16.5 bytes long. Therefore + * We need to reserve at least 25 bytes of slack here. + * Currently I reserve 2048 bytes of just slack to be safe :) + * 2048 bytes easily falls within the BSS (the defualt stack is 4096 bytes) + * so we really are decompressing in place. + * + * Hmm. I missed a trick. In the very worst case (no compression) + * the encoded data is 9/8 the size as it started out so to be completely + * safe I need to be 1/8 of the uncompressed code size past the end. + * This will still fit compfortably into our bss in any conceivable scenario. + */ + _compressed_copy = _edata + _prefix_size - _compressed_size + + /* The amount to overflow _edata */ + MAX( ((_edata - _text + 7) / 8) , 2016 ) + 32; + _assert = ASSERT( ( _compressed_copy - _prefix_size ) < _ebss , "Cannot decompress in place" ) ; + + decompress = DEFINED(decompress) ? decompress : 0; + /DISCARD/ : { + *(.comment) + *(.note) + } + + /* Symbols used by the prefixes whose addresses are inconvinient + * to compute, at runtime in the code. + */ + image_basemem_size = DEFINED(image_basemem_size)? image_basemem_size : 65536; + image_basemem = DEFINED(image_basemem)? image_basemem : 65536; + _prefix_real_to_prot = _real_to_prot + _prefix_size ; + _prefix_prot_to_real = _prot_to_real + _prefix_size ; + _prefix_image_basemem_size = image_basemem_size + _prefix_size ; + _prefix_image_basemem = image_basemem + _prefix_size ; + _prefix_rm_in_call = _rm_in_call + _prefix_size ; + _prefix_in_call = _in_call + _prefix_size ; + _prefix_rom = rom + _prefix_size ; + _prefix_rm_etherboot_location = rm_etherboot_location + _prefix_size ; + _prefix_stack_end = _stack_end + _prefix_size ; +} 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 00000000..464f6d93 --- /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/gdbsym.c b/gpxe/src/arch/i386/core/gdbsym.c new file mode 100644 index 00000000..2da1a1bd --- /dev/null +++ b/gpxe/src/arch/i386/core/gdbsym.c @@ -0,0 +1,33 @@ +#include <stdio.h> +#include <gpxe/init.h> +#include <console.h> +#include <realmode.h> + +extern char __text[]; +extern char __rodata[]; +extern char __data[]; +extern char __bss[]; +extern char __text16[]; +extern char __data16[]; + + +static void gdb_symbol_line ( void ) { + printf ( "Commands to start up gdb:\n\n" ); + printf ( "gdb\n" ); + printf ( "target remote localhost:1234\n" ); + printf ( "set confirm off\n" ); + printf ( "add-symbol-file symbols %#lx", virt_to_phys ( __text ) ); + printf ( " -s .rodata %#lx", virt_to_phys ( __rodata ) ); + printf ( " -s .data %#lx", virt_to_phys ( __data ) ); + printf ( " -s .bss %#lx", virt_to_phys ( __bss ) ); + printf ( " -s .text16 %#x", ( ( rm_cs << 4 ) + (int)__text16 ) ); + printf ( " -s .data16 %#x", ( ( rm_ds << 4 ) + (int)__data16 ) ); + printf ( "\n" ); + printf ( "add-symbol-file symbols 0\n" ); + printf ( "set confirm on\n" ); + getkey(); +} + +struct startup_fn gdb_startup_fn __startup_fn ( STARTUP_NORMAL ) = { + .startup = gdb_symbol_line, +}; diff --git a/gpxe/src/arch/i386/core/i386_string.c b/gpxe/src/arch/i386/core/i386_string.c new file mode 100644 index 00000000..9917363a --- /dev/null +++ b/gpxe/src/arch/i386/core/i386_string.c @@ -0,0 +1,63 @@ +/* + * 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 + * + * Optimised string operations + * + */ + +#include <string.h> + +/** + * Copy memory area + * + * @v dest Destination address + * @v src Source address + * @v len Length + * @ret dest Destination address + */ +__attribute__ (( regparm ( 3 ) )) void * __memcpy ( void *dest, + const void *src, + size_t len ) { + void *edi = dest; + const void *esi = src; + int discard_ecx; + + /* We often do large dword-aligned and dword-length block + * moves. Using movsl rather than movsb speeds these up by + * around 32%. + */ + if ( len >> 2 ) { + __asm__ __volatile__ ( "rep movsl" + : "=&D" ( edi ), "=&S" ( esi ), + "=&c" ( discard_ecx ) + : "0" ( edi ), "1" ( esi ), + "2" ( len >> 2 ) + : "memory" ); + } + if ( len & 0x02 ) { + __asm__ __volatile__ ( "movsw" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + } + if ( len & 0x01 ) { + __asm__ __volatile__ ( "movsb" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + } + return dest; +} diff --git a/gpxe/src/arch/i386/core/i386_timer.c b/gpxe/src/arch/i386/core/i386_timer.c new file mode 100644 index 00000000..8f90ae05 --- /dev/null +++ b/gpxe/src/arch/i386/core/i386_timer.c @@ -0,0 +1,89 @@ +/* + * 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. + */ + +#include <stddef.h> +#include <bits/timer2.h> +#include <gpxe/timer.h> +#include <io.h> + +/* Timers tick over at this rate */ +#define TIMER2_TICK_RATE 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 i386_timer2_udelay(unsigned int usecs) +{ + load_timer2((usecs * TIMER2_TICK_RATE)/USECS_IN_SEC); + while (timer2_running()) + ; +} + diff --git a/gpxe/src/arch/i386/core/nap.c b/gpxe/src/arch/i386/core/nap.c new file mode 100644 index 00000000..12bb5699 --- /dev/null +++ b/gpxe/src/arch/i386/core/nap.c @@ -0,0 +1,12 @@ + +#include <realmode.h> +#include <bios.h> + +/************************************************************************** + * Save power by halting the CPU until the next interrupt + **************************************************************************/ +void cpu_nap ( void ) { + __asm__ __volatile__ ( REAL_CODE ( "sti\n\t" + "hlt\n\t" + "cli\n\t" ) : : ); +} diff --git a/gpxe/src/arch/i386/core/nulltrap.c b/gpxe/src/arch/i386/core/nulltrap.c new file mode 100644 index 00000000..3046fbec --- /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/pcibios.c b/gpxe/src/arch/i386/core/pcibios.c new file mode 100644 index 00000000..1c93e4be --- /dev/null +++ b/gpxe/src/arch/i386/core/pcibios.c @@ -0,0 +1,106 @@ +/* + * 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. + */ + +#include <stdint.h> +#include <gpxe/pci.h> +#include <pcibios.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 + */ +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 ); +} diff --git a/gpxe/src/arch/i386/core/pcidirect.c b/gpxe/src/arch/i386/core/pcidirect.c new file mode 100644 index 00000000..2ed8c2ad --- /dev/null +++ b/gpxe/src/arch/i386/core/pcidirect.c @@ -0,0 +1,38 @@ +/* + * 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. + */ + +#include <gpxe/pci.h> +#include <pcidirect.h> + +/** @file + * + * PCI configuration space access via Type 1 accesses + * + */ + +/** + * Prepare for Type 1 PCI configuration space access + * + * @v pci PCI device + * @v where Location within PCI configuration space + */ +void pcidirect_prepare ( struct pci_device *pci, int where ) { + outl ( ( 0x80000000 | ( pci->bus << 16 ) | ( pci->devfn << 8 ) | + ( where & ~3 ) ), PCIDIRECT_CONFIG_ADDRESS ); +} + diff --git a/gpxe/src/arch/i386/core/pic8259.c b/gpxe/src/arch/i386/core/pic8259.c new file mode 100644 index 00000000..defe2e7d --- /dev/null +++ b/gpxe/src/arch/i386/core/pic8259.c @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#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/prefixudata.lds b/gpxe/src/arch/i386/core/prefixudata.lds new file mode 100644 index 00000000..1c76128e --- /dev/null +++ b/gpxe/src/arch/i386/core/prefixudata.lds @@ -0,0 +1,8 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS { + .prefix.udata : { + *(*) + } +} diff --git a/gpxe/src/arch/i386/core/prefixzdata.lds b/gpxe/src/arch/i386/core/prefixzdata.lds new file mode 100644 index 00000000..bf6ea977 --- /dev/null +++ b/gpxe/src/arch/i386/core/prefixzdata.lds @@ -0,0 +1,8 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS { + .prefix.zdata : { + *(*) + } +} diff --git a/gpxe/src/arch/i386/core/realmode.c b/gpxe/src/arch/i386/core/realmode.c new file mode 100644 index 00000000..9a77bd8a --- /dev/null +++ b/gpxe/src/arch/i386/core/realmode.c @@ -0,0 +1,23 @@ +/* Real-mode interface: C portions. + * + * Initial version by Michael Brown <mbrown@fensystems.co.uk>, January 2004. + */ + +#include "realmode.h" + +/* + * Copy data to/from base memory. + * + */ + +#ifdef KEEP_IT_REAL + +void memcpy_to_real ( segoff_t dest, void *src, size_t n ) { + +} + +void memcpy_from_real ( void *dest, segoff_t src, size_t n ) { + +} + +#endif /* KEEP_IT_REAL */ diff --git a/gpxe/src/arch/i386/core/relocate.c b/gpxe/src/arch/i386/core/relocate.c new file mode 100644 index 00000000..39d00b09 --- /dev/null +++ b/gpxe/src/arch/i386/core/relocate.c @@ -0,0 +1,163 @@ +#include <io.h> +#include <registers.h> +#include <gpxe/memmap.h> + +/* + * Originally by Eric Biederman + * + * Heavily modified by Michael Brown + * + */ + +/* + * 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 _text[]; +extern char _end[]; + +/* 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. + */ +__cdecl 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 ( _text ); + end = virt_to_phys ( _end ); + 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. + */ + 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 00000000..59a1b7cb --- /dev/null +++ b/gpxe/src/arch/i386/core/setjmp.S @@ -0,0 +1,40 @@ +/* setjmp and longjmp. Use of these functions is deprecated. */ + + .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 00000000..c2d138aa --- /dev/null +++ b/gpxe/src/arch/i386/core/stack.S @@ -0,0 +1,13 @@ + .arch i386 + +/**************************************************************************** + * Internal stack + **************************************************************************** + */ + .section ".stack" + .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 00000000..3380a083 --- /dev/null +++ b/gpxe/src/arch/i386/core/stack16.S @@ -0,0 +1,13 @@ + .arch i386 + +/**************************************************************************** + * Internal stack + **************************************************************************** + */ + .section ".stack16" + .align 8 + .globl _stack16 +_stack16: + .space 4096 + .globl _estack16 +_estack16: diff --git a/gpxe/src/arch/i386/core/start16.lds b/gpxe/src/arch/i386/core/start16.lds new file mode 100644 index 00000000..544fc78f --- /dev/null +++ b/gpxe/src/arch/i386/core/start16.lds @@ -0,0 +1,8 @@ +/* When linking with an uncompressed image, these symbols are not + * defined so we provide them here. + */ + +__decompressor_uncompressed = 0 ; +__decompressor__start = 0 ; + +INCLUDE arch/i386/core/start16z.lds diff --git a/gpxe/src/arch/i386/core/start16z.lds b/gpxe/src/arch/i386/core/start16z.lds new file mode 100644 index 00000000..711bcf7b --- /dev/null +++ b/gpxe/src/arch/i386/core/start16z.lds @@ -0,0 +1,65 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) + +/* Linker-generated symbols are prefixed with a double underscore. + * Decompressor symbols are prefixed with __decompressor_. All other + * symbols are the same as in the original object file, i.e. the + * runtime addresses. + */ + +ENTRY(_start16) + +SECTIONS { + .text : { + *(.text) + } + .payload : { + __payload_start = .; + *(.data) + __payload_end = .; + } + + /* _payload_size is the size of the binary image appended to + * start16, in bytes. + */ + __payload_size = __payload_end - __payload_start ; + + /* _size is the size of the runtime image + * (start32 + the C code), in bytes. + */ + __size = _end - _start ; + + /* _decompressor_size is the size of the decompressor, in + * bytes. For a non-compressed image, start16.lds sets + * _decompressor_uncompressed = _decompressor__start = 0. + */ + __decompressor_size = __decompressor_uncompressed - __decompressor__start ; + + /* image__size is the total size of the image, after + * decompression and including the decompressor if applicable. + * It is therefore the amount of memory that start16's payload + * needs in order to execute, in bytes. + */ + __image_size = __size + __decompressor_size ; + + /* Amount to add to runtime symbols to obtain the offset of + * that symbol within the image. + */ + __offset_adjust = __decompressor_size - _start ; + + /* Calculations for the stack + */ + __stack_size = _estack - _stack ; + __offset_stack = _stack + __offset_adjust ; + + /* Some symbols will be larger than 16 bits but guaranteed to + * be multiples of 16. We calculate them in paragraphs and + * export these symbols which can be used in 16-bit code + * without risk of overflow. + */ + __image_size_pgh = ( __image_size / 16 ); + __start_pgh = ( _start / 16 ); + __decompressor_size_pgh = ( __decompressor_size / 16 ); + __offset_stack_pgh = ( __offset_stack / 16 ); +} + diff --git a/gpxe/src/arch/i386/core/start32.S b/gpxe/src/arch/i386/core/start32.S new file mode 100644 index 00000000..37ef5eb9 --- /dev/null +++ b/gpxe/src/arch/i386/core/start32.S @@ -0,0 +1,325 @@ +#include "virtaddr.h" + + .equ MSR_K6_EFER, 0xC0000080 + .equ EFER_LME, 0x00000100 + .equ X86_CR4_PAE, 0x00000020 + .equ CR0_PG, 0x80000000 + +#ifdef GAS291 +#define DATA32 data32; +#define ADDR32 addr32; +#define LJMPI(x) ljmp x +#else +#define DATA32 data32 +#define ADDR32 addr32 +/* newer GAS295 require #define LJMPI(x) ljmp *x */ +#define LJMPI(x) ljmp x +#endif + +/* + * NOTE: if you write a subroutine that is called from C code (gcc/egcs), + * then you only have to take care of %ebx, %esi, %edi and %ebp. These + * registers must not be altered under any circumstance. All other registers + * may be clobbered without any negative side effects. If you don't follow + * this rule then you'll run into strange effects that only occur on some + * gcc versions (because the register allocator may use different registers). + * + * All the data32 prefixes for the ljmp instructions are necessary, because + * the assembler emits code with a relocation address of 0. This means that + * all destinations are initially negative, which the assembler doesn't grok, + * because for some reason negative numbers don't fit into 16 bits. The addr32 + * prefixes are there for the same reasons, because otherwise the memory + * references are only 16 bit wide. Theoretically they are all superfluous. + * One last note about prefixes: the data32 prefixes on all call _real_to_prot + * instructions could be removed if the _real_to_prot function is changed to + * deal correctly with 16 bit return addresses. I tried it, but failed. + */ + + .text + .arch i386 + .code32 + + /* This is a struct os_entry_regs */ + .globl os_regs +os_regs: .space 56 + +/************************************************************************** +XSTART32 - Transfer control to the kernel just loaded +**************************************************************************/ + .globl xstart32 +xstart32: + /* Save the callee save registers */ + movl %ebp, os_regs + 32 + movl %esi, os_regs + 36 + movl %edi, os_regs + 40 + movl %ebx, os_regs + 44 + + /* save the return address */ + popl %eax + movl %eax, os_regs + 48 + + /* save the stack pointer */ + movl %esp, os_regs + 52 + + /* Get the new destination address */ + popl %ecx + + /* Store the physical address of xend on the stack */ + movl $xend32, %ebx + addl virt_offset, %ebx + pushl %ebx + + /* Store the destination address on the stack */ + pushl $PHYSICAL_CS + pushl %ecx + + /* Cache virt_offset */ + movl virt_offset, %ebp + + /* Switch to using physical addresses */ + call _virt_to_phys + + /* Save the target stack pointer */ + movl %esp, os_regs + 12(%ebp) + leal os_regs(%ebp), %esp + + /* Store the pointer to os_regs */ + movl %esp, os_regs_ptr(%ebp) + + /* Load my new registers */ + popal + movl (-32 + 12)(%esp), %esp + + /* Jump to the new kernel + * The lret switches to a flat code segment + */ + lret + + .balign 4 + .globl xend32 +xend32: + /* Fixup %eflags */ + nop + cli + cld + + /* Load %esp with &os_regs + virt_offset */ + .byte 0xbc /* movl $0, %esp */ +os_regs_ptr: + .long 0 + + /* Save the result registers */ + addl $32, %esp + pushal + + /* Compute virt_offset */ + movl %esp, %ebp + subl $os_regs, %ebp + + /* Load the stack pointer and convert it to physical address */ + movl 52(%esp), %esp + addl %ebp, %esp + + /* Enable the virtual addresses */ + leal _phys_to_virt(%ebp), %eax + call *%eax + + /* Restore the callee save registers */ + movl os_regs + 32, %ebp + movl os_regs + 36, %esi + movl os_regs + 40, %edi + movl os_regs + 44, %ebx + movl os_regs + 48, %edx + movl os_regs + 52, %esp + + /* Get the C return value */ + movl os_regs + 28, %eax + + jmpl *%edx + +#ifdef CONFIG_X86_64 + .arch sledgehammer +/************************************************************************** +XSTART_lm - Transfer control to the kernel just loaded in long mode +**************************************************************************/ + .globl xstart_lm +xstart_lm: + /* Save the callee save registers */ + pushl %ebp + pushl %esi + pushl %edi + pushl %ebx + + /* Cache virt_offset && (virt_offset & 0xfffff000) */ + movl virt_offset, %ebp + movl %ebp, %ebx + andl $0xfffff000, %ebx + + /* Switch to using physical addresses */ + call _virt_to_phys + + /* Initialize the page tables */ + /* Level 4 */ + leal 0x23 + pgt_level3(%ebx), %eax + leal pgt_level4(%ebx), %edi + movl %eax, (%edi) + + /* Level 3 */ + leal 0x23 + pgt_level2(%ebx), %eax + leal pgt_level3(%ebx), %edi + movl %eax, 0x00(%edi) + addl $4096, %eax + movl %eax, 0x08(%edi) + addl $4096, %eax + movl %eax, 0x10(%edi) + addl $4096, %eax + movl %eax, 0x18(%edi) + + /* Level 2 */ + movl $0xe3, %eax + leal pgt_level2(%ebx), %edi + leal 16384(%edi), %esi +pgt_level2_loop: + movl %eax, (%edi) + addl $8, %edi + addl $0x200000, %eax + cmp %esi, %edi + jne pgt_level2_loop + + /* Point at the x86_64 page tables */ + leal pgt_level4(%ebx), %edi + movl %edi, %cr3 + + + /* Setup for the return from 64bit mode */ + /* 64bit align the stack */ + movl %esp, %ebx /* original stack pointer + 16 */ + andl $0xfffffff8, %esp + + /* Save original stack pointer + 16 */ + pushl %ebx + + /* Save virt_offset */ + pushl %ebp + + /* Setup for the jmp to 64bit long mode */ + leal start_lm(%ebp), %eax + movl %eax, 0x00 + start_lm_addr(%ebp) + movl $LM_CODE_SEG, %eax + movl %eax, 0x04 + start_lm_addr(%ebp) + + /* Setup for the jump out of 64bit long mode */ + leal end_lm(%ebp), %eax + movl %eax, 0x00 + end_lm_addr(%ebp) + movl $FLAT_CODE_SEG, %eax + movl %eax, 0x04 + end_lm_addr(%ebp) + + /* Enable PAE mode */ + movl %cr4, %eax + orl $X86_CR4_PAE, %eax + movl %eax, %cr4 + + /* Enable long mode */ + movl $MSR_K6_EFER, %ecx + rdmsr + orl $EFER_LME, %eax + wrmsr + + /* Start paging, entering 32bit compatiblity mode */ + movl %cr0, %eax + orl $CR0_PG, %eax + movl %eax, %cr0 + + /* Enter 64bit long mode */ + ljmp *start_lm_addr(%ebp) + .code64 +start_lm: + /* Load 64bit data segments */ + movl $LM_DATA_SEG, %eax + movl %eax, %ds + movl %eax, %es + movl %eax, %ss + + andq $0xffffffff, %rbx + /* Get the address to jump to */ + movl 20(%rbx), %edx + andq $0xffffffff, %rdx + + /* Get the argument pointer */ + movl 24(%rbx), %ebx + andq $0xffffffff, %rbx + + /* Jump to the 64bit code */ + call *%rdx + + /* Preserve the result */ + movl %eax, %edx + + /* Fixup %eflags */ + cli + cld + + /* Switch to 32bit compatibility mode */ + ljmp *end_lm_addr(%rip) + + .code32 +end_lm: + /* Disable paging */ + movl %cr0, %eax + andl $~CR0_PG, %eax + movl %eax, %cr0 + + /* Disable long mode */ + movl $MSR_K6_EFER, %ecx + rdmsr + andl $~EFER_LME, %eax + wrmsr + + /* Disable PAE */ + movl %cr4, %eax + andl $~X86_CR4_PAE, %eax + movl %eax, %cr4 + + /* Compute virt_offset */ + popl %ebp + + /* Compute the original stack pointer + 16 */ + popl %ebx + movl %ebx, %esp + + /* Enable the virtual addresses */ + leal _phys_to_virt(%ebp), %eax + call *%eax + + /* Restore the callee save registers */ + popl %ebx + popl %esi + popl %edi + popl %ebp + + /* Get the C return value */ + movl %edx, %eax + + /* Return */ + ret + + .arch i386 +#endif /* CONFIG_X86_64 */ + +#ifdef CONFIG_X86_64 + .section ".bss" + .p2align 12 + /* Include a dummy space in case we are loaded badly aligned */ + .space 4096 + /* Reserve enough space for a page table convering 4GB with 2MB pages */ +pgt_level4: + .space 4096 +pgt_level3: + .space 4096 +pgt_level2: + .space 16384 +start_lm_addr: + .space 8 +end_lm_addr: + .space 8 +#endif diff --git a/gpxe/src/arch/i386/core/umalloc.c b/gpxe/src/arch/i386/core/umalloc.c new file mode 100644 index 00000000..bfd62ef1 --- /dev/null +++ b/gpxe/src/arch/i386/core/umalloc.c @@ -0,0 +1,224 @@ +/* + * 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 + * + * 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 ) + +/** Start of Etherboot text, as defined by the linker */ +extern char _text[]; + +/** 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 ( ! top ) { + 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. + */ +userptr_t 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 ( ! top ) { + if ( ( rc = init_eheap() ) != 0 ) + return rc; + } + + /* 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_region ( EXTMEM, user_to_phys ( bottom, -sizeof ( extmem ) ), + user_to_phys ( top, 0 ) ); + + return ( new_size ? new : UNOWHERE ); +} + +/** + * Allocate external memory + * + * @v size Requested size + * @ret ptr Memory, or UNULL + * + * Memory is guaranteed to be aligned to a page boundary. + */ +userptr_t umalloc ( size_t size ) { + return urealloc ( UNULL, size ); +} + +/** + * Free external memory + * + * @v ptr Memory allocated by umalloc(), or UNULL + * + * If @c ptr is UNULL, no action is taken. + */ +void ufree ( userptr_t ptr ) { + urealloc ( ptr, 0 ); +} 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 00000000..bf82cc61 --- /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 "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 00000000..5d762375 --- /dev/null +++ b/gpxe/src/arch/i386/core/virtaddr.S @@ -0,0 +1,101 @@ +/* + * Functions to support the virtual addressing method of relocation + * that Etherboot uses. + * + */ + +#include "virtaddr.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 00000000..f452b659 --- /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/drivers/net/undi.c b/gpxe/src/arch/i386/drivers/net/undi.c new file mode 100644 index 00000000..1090cc94 --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undi.c @@ -0,0 +1,146 @@ +/* + * 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. + */ + +#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)" ), +}; + +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 00000000..a6c6c381 --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undiisr.S @@ -0,0 +1,87 @@ +#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 + .section ".text16", "ax", @progbits + .section ".data16", "aw", @progbits + .code16 + + .section ".text16" + .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, undinet_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 *undinet_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" +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 00000000..a3284f80 --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undiload.c @@ -0,0 +1,170 @@ +/* + * 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. + */ + +#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 *%c2\n\t" + "addw $4, %%sp\n\t" ) + : "=a" ( exit ) + : "a" ( & __from_data16 ( undi_loader ) ), + "p" ( & __from_data16 ( undi_loader_entry ) ) + : "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 00000000..e3b9f85a --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undinet.c @@ -0,0 +1,793 @@ +/* + * 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. + */ + +#include <string.h> +#include <pxe.h> +#include <realmode.h> +#include <pic8259.h> +#include <biosint.h> +#include <pnpbios.h> +#include <basemem_packet.h> +#include <gpxe/iobuf.h> +#include <gpxe/netdevice.h> +#include <gpxe/if_ether.h> +#include <gpxe/ethernet.h> +#include <undi.h> +#include <undinet.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 ); + +/***************************************************************************** + * + * UNDI API call + * + ***************************************************************************** + */ + +/** + * Name UNDI API call + * + * @v function API call number + * @ret name API call name + */ +static inline __attribute__ (( always_inline )) const char * +undinet_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"; + default: + return "UNKNOWN API CALL"; + } +} + +/** + * UNDI parameter block + * + * Used as the paramter block for all UNDI API calls. Resides in base + * memory. + */ +static union u_PXENV_ANY __bss16 ( undinet_params ); +#define undinet_params __use_data16 ( undinet_params ) + +/** UNDI entry point + * + * Used as the indirection vector for all UNDI API calls. Resides in + * base memory. + */ +SEGOFF16_t __bss16 ( undinet_entry_point ); +#define undinet_entry_point __use_data16 ( undinet_entry_point ) + +/** + * Issue UNDI API call + * + * @v undinic UNDI NIC + * @v function API call number + * @v params UNDI parameter block + * @v params_len Length of UNDI parameter block + * @ret rc Return status code + */ +static int undinet_call ( struct undi_nic *undinic, 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 ( undinet_params ) ); + memcpy ( &undinet_params, params, params_len ); + + /* 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 *%c3\n\t" + "addw $6, %%sp\n\t" ) + : "=a" ( exit ), "=b" ( discard_b ), + "=D" ( discard_D ) + : "p" ( &__from_data16 ( undinet_entry_point )), + "b" ( function ), + "D" ( &__from_data16 ( undinet_params ) ) + : "ecx", "edx", "esi", "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(); + + /* Determine return status code based on PXENV_EXIT and + * PXENV_STATUS + */ + if ( exit == PXENV_EXIT_SUCCESS ) { + rc = 0; + } else { + rc = -undinet_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 = (intptr_t) &__from_data16 ( undinet_params ), + }; + + DBGC ( undinic, "UNDINIC %p %s failed: %s\n", undinic, + undinet_function_name ( function ), strerror ( rc ) ); + DBGC ( undinic, "UNDINIC %p parameters at %04x:%04x length " + "%#02zx, entry point at %04x:%04x\n", undinic, + rm_params.segment, rm_params.offset, params_len, + undinet_entry_point.segment, + undinet_entry_point.offset ); + DBGC ( undinic, "UNDINIC %p parameters provided:\n", undinic ); + DBGC_HDA ( undinic, rm_params, params, params_len ); + DBGC ( undinic, "UNDINIC %p parameters returned:\n", undinic ); + DBGC_HDA ( undinic, rm_params, &undinet_params, params_len ); + } + + /* Copy parameter block back */ + memcpy ( params, &undinet_params, params_len ); + + return rc; +} + +/***************************************************************************** + * + * 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 undi_nic *undinic = netdev->priv; + 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 + = ( ( unsigned ) & __from_data16 ( undinet_tbd ) ); + undi_transmit.TBD.segment = rm_ds; + undi_transmit.TBD.offset + = ( ( unsigned ) & __from_data16 ( undinet_tbd ) ); + + /* Create PXENV_UNDI_TBD data structure */ + undinet_tbd.ImmedLength = len; + undinet_tbd.Xmit.segment = rm_ds; + undinet_tbd.Xmit.offset + = ( ( unsigned ) & __from_data16 ( basemem_packet ) ); + + /* Issue PXE API call */ + if ( ( rc = undinet_call ( undinic, 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 = undinet_call ( undinic, 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, iobuf ); + iobuf = NULL; + /* 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 ) ); + undinet_call ( undinic, PXENV_UNDI_SET_STATION_ADDRESS, + &undi_set_address, sizeof ( undi_set_address ) ); + + /* Open NIC */ + memset ( &undi_open, 0, sizeof ( undi_open ) ); + undi_open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST ); + if ( ( rc = undinet_call ( undinic, 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 = undinet_call ( undinic, 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 */ + undinet_call ( undinic, PXENV_UNDI_CLOSE, &undi_close, + sizeof ( undi_close ) ); + + /* Disable interrupt and unhook ISR */ + disable_irq ( undinic->irq ); + undinet_unhook_isr ( undinic->irq ); +#if 0 + enable_irq ( undinic->irq ); + send_eoi ( undinic->irq ); +#endif + + 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; +#if 0 + struct s_PXENV_STOP_UNDI stop_undi; +#endif + 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_point = 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 = undinet_call ( undinic, PXENV_START_UNDI, + &start_undi, + sizeof ( start_undi ) ) ) != 0 ) + goto err_start_undi; + /* Bring up UNDI stack */ + memset ( &undi_startup, 0, sizeof ( undi_startup ) ); + if ( ( rc = undinet_call ( undinic, PXENV_UNDI_STARTUP, + &undi_startup, + sizeof ( undi_startup ) ) ) != 0 ) + goto err_undi_startup; + + memset ( &undi_initialize, 0, sizeof ( undi_initialize ) ); + if ( ( rc = undinet_call ( undinic, PXENV_UNDI_INITIALIZE, + &undi_initialize, + sizeof ( undi_initialize ) ) ) != 0 ) + goto err_undi_initialize; + } + undi->flags |= UNDI_FL_STARTED; + + /* Get device information */ + memset ( &undi_info, 0, sizeof ( undi_info ) ); + if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_INFORMATION, + &undi_info, sizeof ( undi_info ) ) ) != 0 ) + goto err_undi_get_information; + memcpy ( netdev->ll_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->ll_addr ), undinic->irq ); + + /* Get interface information */ + memset ( &undi_iface, 0, sizeof ( undi_iface ) ); + if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_IFACE_INFO, + &undi_iface, + sizeof ( undi_iface ) ) ) != 0 ) + goto err_undi_get_iface_info; + DBGC ( undinic, "UNDINIC %p has type %s and link speed %ld\n", + undinic, undi_iface.IfaceType, undi_iface.LinkSpeed ); + 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; + } + + /* 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 ) ); + undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown, + sizeof ( undi_shutdown ) ); + memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) ); + undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup, + sizeof ( undi_cleanup ) ); + err_undi_startup: +#if 0 + /* Unhook UNDI stack */ + memset ( &stop_undi, 0, sizeof ( stop_undi ) ); + undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi, + sizeof ( stop_undi ) ); +#endif + 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; +#if 0 + struct s_PXENV_UNDI_SHUTDOWN undi_shutdown; + struct s_PXENV_UNDI_CLEANUP undi_cleanup; + struct s_PXENV_STOP_UNDI stop_undi; +#endif + + /* Unregister net device */ + unregister_netdev ( netdev ); + + /* Shut down UNDI stack */ +#if 0 + memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) ); + undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown, + sizeof ( undi_shutdown ) ); + memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) ); + undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup, + sizeof ( undi_cleanup ) ); + + /* Unhook UNDI stack */ + memset ( &stop_undi, 0, sizeof ( stop_undi ) ); + undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi, + sizeof ( stop_undi ) ); + undi->flags &= ~UNDI_FL_STARTED; +#endif + + /* Clear entry point */ + memset ( &undinet_entry_point, 0, sizeof ( undinet_entry_point ) ); + + /* 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 00000000..ee361493 --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undionly.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. + */ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <gpxe/device.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, +}; 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 00000000..e29d150a --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undipreload.c @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#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 00000000..f977a553 --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undirom.c @@ -0,0 +1,232 @@ +/* + * 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. + */ + +#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 " + "%08lx\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 %08lx\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 2kB intervals */ + for ( rom_segment = 0xc000 ; rom_segment < 0x10000 ; + rom_segment += 0x80 ) { + 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/drivers/timer_bios.c b/gpxe/src/arch/i386/drivers/timer_bios.c new file mode 100644 index 00000000..f9caf8d9 --- /dev/null +++ b/gpxe/src/arch/i386/drivers/timer_bios.c @@ -0,0 +1,57 @@ +/* + * Etherboot routines for PCBIOS firmware. + * + * Body of routines taken from old pcbios.S + */ + +#include <gpxe/init.h> +#include <gpxe/timer.h> +#include <stdio.h> +#include <realmode.h> +#include <bios.h> +#include <bits/timer2.h> + +/* A bit faster actually, but we don't care. */ +#define TIMER2_TICKS_PER_SEC 18 + +/* + * 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 tick_t 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) * (USECS_IN_SEC / TIMER2_TICKS_PER_SEC) ); +} + +static int bios_ts_init(void) +{ + DBG("BIOS timer installed\n"); + return 0; +} + +struct timer bios_ts __timer ( 02 ) = { + .init = bios_ts_init, + .udelay = i386_timer2_udelay, + .currticks = bios_currticks, +}; + diff --git a/gpxe/src/arch/i386/drivers/timer_rdtsc.c b/gpxe/src/arch/i386/drivers/timer_rdtsc.c new file mode 100644 index 00000000..09b7df2f --- /dev/null +++ b/gpxe/src/arch/i386/drivers/timer_rdtsc.c @@ -0,0 +1,69 @@ + +#include <gpxe/init.h> +#include <gpxe/timer.h> +#include <errno.h> +#include <stdio.h> +#include <bits/cpu.h> +#include <bits/timer2.h> +#include <io.h> + + +#define rdtsc(low,high) \ + __asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high)) + +#define rdtscll(val) \ + __asm__ __volatile__ ("rdtsc" : "=A" (val)) + + +/* Measure how many clocks we get in one microsecond */ +static inline uint64_t calibrate_tsc(void) +{ + + uint64_t rdtsc_start; + uint64_t rdtsc_end; + + rdtscll(rdtsc_start); + i386_timer2_udelay(USECS_IN_MSEC); + rdtscll(rdtsc_end); + + return (rdtsc_end - rdtsc_start) / USECS_IN_MSEC; +} + +static uint32_t clocks_per_usec = 0; + +/* We measure time in microseconds. */ +static tick_t rdtsc_currticks(void) +{ + uint64_t clocks; + + /* Read the Time Stamp Counter */ + rdtscll(clocks); + + return clocks / clocks_per_usec; +} + +static int rdtsc_ts_init(void) +{ + + struct cpuinfo_x86 cpu_info; + + get_cpuinfo(&cpu_info); + if (cpu_info.features & X86_FEATURE_TSC) { + clocks_per_usec= calibrate_tsc(); + if (clocks_per_usec) { + DBG("RDTSC ticksource installed. CPU running at %ld Mhz\n", + clocks_per_usec); + return 0; + } + } + + DBG("RDTSC ticksource not available on this machine.\n"); + return -ENODEV; +} + +struct timer rdtsc_ts __timer (01) = { + .init = rdtsc_ts_init, + .udelay = generic_currticks_udelay, + .currticks = rdtsc_currticks, +}; + 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 00000000..b126d2a7 --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/basemem.c @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#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 00000000..dcb0462a --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/bios_console.c @@ -0,0 +1,294 @@ +/* + * 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. + */ + +#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 0x47 + +/** 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 ( 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]; + } + 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 00000000..e9328041 --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/e820mangler.S @@ -0,0 +1,473 @@ +/* + * 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. + */ + + .text + .arch i386 + .section ".text16", "ax", @progbits + .section ".data16", "aw", @progbits + .section ".text16.data", "aw", @progbits + .code16 + +#define SMAP 0x534d4150 + +/**************************************************************************** + * Check for overlap + * + * Parameters: + * %edx:%eax Region start + * %ecx:%ebx Region end + * %si Pointer to hidden region descriptor + * Returns: + * CF set Region overlaps + * CF clear No overlap + **************************************************************************** + */ + .section ".text16" +check_overlap: + /* If start >= hidden_end, there is no overlap. */ + testl %edx, %edx + jnz no_overlap + cmpl 4(%si), %eax + jae no_overlap + /* If end <= hidden_start, there is no overlap; equivalently, + * if end > hidden_start, there is overlap. + */ + testl %ecx, %ecx + jnz overlap + cmpl 0(%si), %ebx + ja overlap +no_overlap: + clc + ret +overlap: + stc + ret + .size check_overlap, . - check_overlap + +/**************************************************************************** + * Check for overflow/underflow + * + * Parameters: + * %edx:%eax Region start + * %ecx:%ebx Region end + * Returns: + * CF set start < end + * CF clear start >= end + **************************************************************************** + */ + .section ".text16" +check_overflow: + pushl %ecx + pushl %ebx + subl %eax, %ebx + sbbl %edx, %ecx + popl %ebx + popl %ecx + ret + .size check_overflow, . - check_overflow + +/**************************************************************************** + * Truncate towards start of region + * + * Parameters: + * %edx:%eax Region start + * %ecx:%ebx Region end + * %si Pointer to hidden region descriptor + * Returns: + * %edx:%eax Modified region start + * %ecx:%ebx Modified region end + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +truncate_to_start: + /* If overlaps, set region end = hidden region start */ + call check_overlap + jnc 99f + movl 0(%si), %ebx + xorl %ecx, %ecx + /* If region end < region start, set region end = region start */ + call check_overflow + jnc 1f + movl %eax, %ebx + movl %edx, %ecx +1: stc +99: ret + .size truncate_to_start, . - truncate_to_start + +/**************************************************************************** + * Truncate towards end of region + * + * Parameters: + * %edx:%eax Region start + * %ecx:%ebx Region end + * %si Pointer to hidden region descriptor + * Returns: + * %edx:%eax Modified region start + * %ecx:%ebx Modified region end + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +truncate_to_end: + /* If overlaps, set region start = hidden region end */ + call check_overlap + jnc 99f + movl 4(%si), %eax + xorl %edx, %edx + /* If region start > region end, set region start = region end */ + call check_overflow + jnc 1f + movl %ebx, %eax + movl %ecx, %edx +1: stc +99: ret + .size truncate_to_end, . - truncate_to_end + +/**************************************************************************** + * Truncate region + * + * Parameters: + * %edx:%eax Region start + * %ecx:%ebx Region length (*not* region end) + * %bp truncate_to_start or truncate_to_end + * Returns: + * %edx:%eax Modified region start + * %ecx:%ebx Modified region length + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +truncate: + pushw %si + pushfw + /* Convert (start,len) to (start,end) */ + addl %eax, %ebx + adcl %edx, %ecx + /* Hide all hidden regions, truncating as directed */ + movw $hidden_regions, %si +1: call *%bp + jnc 2f + popfw /* If CF was set, set stored CF in flags word on stack */ + stc + pushfw +2: addw $8, %si + cmpl $0, 0(%si) + jne 1b + /* Convert modified (start,end) back to (start,len) */ + subl %eax, %ebx + sbbl %edx, %ecx + popfw + popw %si + ret + .size truncate, . - truncate + +/**************************************************************************** + * Patch "memory above 1MB" figure + * + * Parameters: + * %ax Memory above 1MB, in 1kB blocks + * Returns: + * %ax Modified memory above 1M in 1kB blocks + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +patch_1m: + pushal + /* Convert to (start,len) format and call truncate */ + movw $truncate_to_start, %bp + xorl %ecx, %ecx + movzwl %ax, %ebx + shll $10, %ebx + xorl %edx, %edx + movl $0x100000, %eax + call truncate + /* 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 + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +patch_16m: + pushal + /* Convert to (start,len) format and call truncate */ + movw $truncate_to_start, %bp + xorl %ecx, %ecx + shll $16, %ebx + xorl %edx, %edx + movl $0x1000000, %eax + call truncate + /* 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 + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +patch_1m_16m: + call patch_1m + jc 1f + call patch_16m + ret +1: /* 1m region was truncated; kill the 16m region */ + xorw %bx, %bx + ret + .size patch_1m_16m, . - patch_1m_16m + +/**************************************************************************** + * Patch E820 memory map entry + * + * Parameters: + * %es:di Pointer to E820 memory map descriptor + * %bp truncate_to_start or truncate_to_end + * Returns: + * %es:di Pointer to now-modified E820 memory map descriptor + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +patch_e820: + pushal + movl %es:0(%di), %eax + movl %es:4(%di), %edx + movl %es:8(%di), %ebx + movl %es:12(%di), %ecx + call truncate + movl %eax, %es:0(%di) + movl %edx, %es:4(%di) + movl %ebx, %es:8(%di) + movl %ecx, %es:12(%di) + popal + ret + .size patch_e820, . - patch_e820 + +/**************************************************************************** + * Split E820 memory map entry if necessary + * + * Parameters: + * As for INT 15,e820 + * Returns: + * As for INT 15,e820 + * + * Calls the underlying INT 15,e820 and returns a modified memory map. + * Regions will be split around any hidden regions. + **************************************************************************** + */ + .section ".text16" +split_e820: + pushw %si + pushw %bp + /* Caller's %bx => %si, real %ebx to %ebx, call previous handler */ + pushfw + movw %bx, %si + testl %ebx, %ebx + jnz 1f + movl %ebx, %cs:real_ebx +1: movl %cs:real_ebx, %ebx + lcall *%cs:int15_vector + pushfw + /* Edit result */ + pushw %ds + pushw %cs:rm_ds + popw %ds + movw $truncate_to_start, %bp + incw %si + jns 2f + movw $truncate_to_end, %bp +2: call patch_e820 + jnc 3f + xorw $0x8000, %si +3: testw %si, %si + js 4f + movl %ebx, %cs:real_ebx + testl %ebx, %ebx + jz 5f +4: movw %si, %bx +5: popw %ds + /* Restore flags returned by previous handler and return */ + popfw + popw %bp + popw %si + ret + .size split_e820, . - split_e820 + + .section ".text16.data" +real_ebx: + .long 0 + .size real_ebx, . - real_ebx + +/**************************************************************************** + * INT 15,e820 handler + **************************************************************************** + */ + .section ".text16" +int15_e820: + pushl %eax + pushl %ecx + pushl %edx + call split_e820 + pushfw + /* If we've hit an error, exit immediately */ + jc 99f + /* If region is non-empty, return this region */ + pushl %eax + movl %es:8(%di), %eax + orl %es:12(%di), %eax + popl %eax + jnz 99f + /* Region is empty. If this is not the end of the map, + * skip over this region. + */ + testl %ebx, %ebx + jz 1f + popfw + popl %edx + popl %ecx + popl %eax + jmp int15_e820 +1: /* Region is empty and this is the end of the map. Return + * with CF set to avoid placing an empty region at the end of + * the map. + */ + popfw + stc + pushfw +99: /* Restore flags from original INT 15,e820 call and return */ + popfw + addr32 leal 12(%esp), %esp /* avoid changing flags */ + lret $2 + .size int15_e820, . - int15_e820 + +/**************************************************************************** + * INT 15,e801 handler + **************************************************************************** + */ + .section ".text16" +int15_e801: + /* Call previous handler */ + pushfw + lcall *%cs:int15_vector + pushfw + /* 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 + /* Restore flags returned by previous handler and return */ + popfw + lret $2 + .size int15_e801, . - int15_e801 + +/**************************************************************************** + * INT 15,88 handler + **************************************************************************** + */ + .section ".text16" +int15_88: + /* Call previous handler */ + pushfw + lcall *%cs:int15_vector + pushfw + /* Edit result */ + pushw %ds + pushw %cs:rm_ds + popw %ds + call patch_1m + popw %ds + /* Restore flags returned by previous handler and return */ + popfw + lret $2 + .size int15_88, . - int15_88 + +/**************************************************************************** + * INT 15 handler + **************************************************************************** + */ + .section ".text16" + .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" + .globl int15_vector +int15_vector: + .long 0 + .size int15_vector, . - 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 00000000..2caac894 --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/gateA20.c @@ -0,0 +1,170 @@ +#include <stdio.h> +#include <realmode.h> +#include <bios.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 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 ) { + SLOW_DOWN_IO; + ( void ) inb ( K_RDWR ); + SLOW_DOWN_IO; + } +} + +/** + * 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++; + + SLOW_DOWN_IO; + + /* 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(); + 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 */ + SLOW_DOWN_IO; + outb ( scp_a, SCP_A ); + SLOW_DOWN_IO; + 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 00000000..eba94007 --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/hidemem.c @@ -0,0 +1,156 @@ +/* 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. + */ + +#include <realmode.h> +#include <biosint.h> +#include <basemem.h> +#include <gpxe/init.h> +#include <gpxe/hidemem.h> + +/** Alignment for hidden memory regions */ +#define ALIGN_HIDDEN 4096 /* 4kB page alignment should be enough */ + +/** + * A hidden region of Etherboot + * + * 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 */ + physaddr_t start; + /* Physical end address */ + physaddr_t end; +}; + +/** + * List of hidden regions + * + * Must be terminated by a zero entry. + */ +struct hidden_region __data16_array ( hidden_regions, [] ) = { + [TEXT] = { 0, 0 }, + [BASEMEM] = { ( 640 * 1024 ), ( 640 * 1024 ) }, + [EXTMEM] = { 0, 0 }, + { 0, 0, } /* Terminator */ +}; +#define hidden_regions __use_data16 ( hidden_regions ) + +/** 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 ) + +/** + * Hide region of memory from system memory map + * + * @v start Start of region + * @v end End of region + */ +void hide_region ( unsigned int region_id, physaddr_t start, physaddr_t end ) { + struct hidden_region *region = &hidden_regions[region_id]; + + /* 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 %d [%lx,%lx)\n", + region_id, region->start, region->end ); +} + +/** + * Hide Etherboot text + * + */ +static void hide_text ( void ) { + + /* The linker defines these symbols for us */ + extern char _text[]; + extern char _end[]; + + hide_region ( TEXT, virt_to_phys ( _text ), virt_to_phys ( _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. + */ + hidden_regions[BASEMEM].start = ( get_fbms() * 1024 ); +} + +/** + * Hide Etherboot + * + * Installs an INT 15 handler to edit Etherboot out of the memory map + * returned by the BIOS. + */ +static void hide_etherboot ( void ) { + + /* Initialise the hidden regions */ + hide_text(); + hide_basemem(); + + /* Hook INT 15 */ + hook_bios_interrupt ( 0x15, ( unsigned int ) int15, + &int15_vector ); +} + +/** + * Unhide Etherboot + * + * Uninstalls the INT 15 handler installed by hide_etherboot(), if + * possible. + */ +static void unhide_etherboot ( void ) { + + /* 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 ); +} + +/** 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 00000000..b6a8ca3c --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/memmap.c @@ -0,0 +1,221 @@ +/* + * 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. + */ + +#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; +} __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 */ + +/** 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\n", + extmem_1m_to_16m_k, extmem_16m_plus_64k, extmem ); + 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\n", extmem ); + 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; + unsigned int flags; + unsigned int discard_c, discard_d, discard_D; + + do { + __asm__ __volatile__ ( REAL_CODE ( "stc\n\t" + "int $0x15\n\t" + "pushfw\n\t" + "popw %w0\n\t" ) + : "=r" ( flags ), "=a" ( smap ), + "=b" ( next ), "=D" ( discard_D ), + "=c" ( discard_c ), "=d" ( discard_d ) + : "a" ( 0xe820 ), "b" ( next ), + "D" ( &__from_data16 ( e820buf ) ), + "c" ( sizeof ( e820buf ) ), + "d" ( SMAP ) + : "memory" ); + + if ( smap != SMAP ) { + DBG ( "INT 15,e820 failed SMAP signature check\n" ); + return -ENOTSUP; + } + + if ( flags & CF ) { + DBG ( "INT 15,e820 terminated on CF set\n" ); + break; + } + + DBG ( "INT 15,e820 region [%llx,%llx) type %d\n", + e820buf.start, ( e820buf.start + e820buf.len ), + ( int ) e820buf.type ); + if ( e820buf.type != E820_TYPE_RAM ) + 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 ); + + 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\n", basemem ); + 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 00000000..420d2ae8 --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/pnpbios.c @@ -0,0 +1,107 @@ +/* + * 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. + */ + +#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/firmware/pcbios/smbios.c b/gpxe/src/arch/i386/firmware/pcbios/smbios.c new file mode 100644 index 00000000..4d710d68 --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/smbios.c @@ -0,0 +1,323 @@ +/* + * 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. + */ + +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <gpxe/uaccess.h> +#include <gpxe/uuid.h> +#include <realmode.h> +#include <pnpbios.h> +#include <smbios.h> + +/** @file + * + * System Management BIOS + * + */ + +/** Signature for SMBIOS entry point */ +#define SMBIOS_SIGNATURE \ + ( ( '_' << 0 ) + ( 'S' << 8 ) + ( 'M' << 16 ) + ( '_' << 24 ) ) + +/** + * SMBIOS entry point + * + * This is the single table which describes the list of SMBIOS + * structures. It is located by scanning through the BIOS segment. + */ +struct smbios_entry { + /** Signature + * + * Must be equal to SMBIOS_SIGNATURE + */ + uint32_t signature; + /** Checksum */ + uint8_t checksum; + /** Length */ + uint8_t length; + /** Major version */ + uint8_t major; + /** Minor version */ + uint8_t minor; + /** Maximum structure size */ + uint16_t max; + /** Entry point revision */ + uint8_t revision; + /** Formatted area */ + uint8_t formatted[5]; + /** DMI Signature */ + uint8_t dmi_signature[5]; + /** DMI checksum */ + uint8_t dmi_checksum; + /** Structure table length */ + uint16_t smbios_length; + /** Structure table address */ + physaddr_t smbios_address; + /** Number of SMBIOS structures */ + uint16_t smbios_count; + /** BCD revision */ + uint8_t bcd_revision; +} __attribute__ (( packed )); + +/** + * SMBIOS entry point descriptor + * + * This contains the information from the SMBIOS entry point that we + * care about. + */ +struct smbios { + /** Start of SMBIOS structures */ + userptr_t address; + /** Length of SMBIOS structures */ + size_t length; + /** Number of SMBIOS structures */ + unsigned int count; +}; + +/** + * SMBIOS strings descriptor + * + * This is returned as part of the search for an SMBIOS structure, and + * contains the information needed for extracting the strings within + * the "unformatted" portion of the structure. + */ +struct smbios_strings { + /** Start of strings data */ + userptr_t data; + /** Length of strings data */ + size_t length; +}; + +/** + * Find SMBIOS + * + * @ret smbios SMBIOS entry point descriptor, or NULL if not found + */ +static struct smbios * find_smbios ( void ) { + static struct smbios smbios = { + .address = UNULL, + }; + union { + struct smbios_entry entry; + uint8_t bytes[256]; /* 256 is maximum length possible */ + } u; + unsigned int offset; + size_t len; + unsigned int i; + uint8_t sum; + + /* Return cached result if available */ + if ( smbios.address != UNULL ) + return &smbios; + + /* Try to find SMBIOS */ + for ( offset = 0 ; 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.length; + 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 entry point at %04x:%04x\n", + BIOS_SEG, offset ); + smbios.address = phys_to_user ( u.entry.smbios_address ); + smbios.length = u.entry.smbios_length; + smbios.count = u.entry.smbios_count; + return &smbios; + } + + DBG ( "No SMBIOS found\n" ); + return NULL; +} + +/** + * Find SMBIOS strings terminator + * + * @v smbios SMBIOS entry point descriptor + * @v offset Offset to start of strings + * @ret offset Offset to strings terminator, or 0 if not found + */ +static size_t find_strings_terminator ( struct smbios *smbios, + size_t offset ) { + size_t max_offset = ( smbios->length - 2 ); + uint16_t nulnul; + + for ( ; offset <= max_offset ; offset++ ) { + copy_from_user ( &nulnul, smbios->address, offset, 2 ); + if ( nulnul == 0 ) + return ( offset + 1 ); + } + return 0; +} + +/** + * Find specific structure type within SMBIOS + * + * @v type Structure type to search for + * @v structure Buffer to fill in with structure + * @v length Length of buffer + * @v strings Strings descriptor to fill in, or NULL + * @ret rc Return status code + */ +int find_smbios_structure ( unsigned int type, void *structure, + size_t length, struct smbios_strings *strings ) { + struct smbios *smbios; + struct smbios_header header; + struct smbios_strings temp_strings; + unsigned int count = 0; + size_t offset = 0; + size_t strings_offset; + size_t terminator_offset; + + /* Locate SMBIOS entry point */ + if ( ! ( smbios = find_smbios() ) ) + return -ENOENT; + + /* Ensure that we have a usable strings descriptor buffer */ + if ( ! strings ) + strings = &temp_strings; + + /* Scan through list of structures */ + while ( ( ( offset + sizeof ( header ) ) < smbios->length ) && + ( count < smbios->count ) ) { + + /* Read next SMBIOS structure header */ + copy_from_user ( &header, smbios->address, offset, + sizeof ( header ) ); + + /* Determine start and extent of strings block */ + strings_offset = ( offset + header.length ); + if ( strings_offset > smbios->length ) { + DBG ( "SMBIOS structure at offset %zx with length " + "%x extends beyond SMBIOS\n", offset, + header.length ); + return -ENOENT; + } + terminator_offset = + find_strings_terminator ( smbios, strings_offset ); + if ( ! terminator_offset ) { + DBG ( "SMBIOS structure at offset %zx has " + "unterminated strings section\n", offset ); + return -ENOENT; + } + strings->data = userptr_add ( smbios->address, + strings_offset ); + strings->length = ( terminator_offset - strings_offset ); + + DBG ( "SMBIOS structure at offset %zx has type %d, " + "length %x, strings length %zx\n", + offset, header.type, header.length, strings->length ); + + /* If this is the structure we want, return */ + if ( header.type == type ) { + if ( length > header.length ) + length = header.length; + copy_from_user ( structure, smbios->address, + offset, length ); + return 0; + } + + /* Move to next SMBIOS structure */ + offset = ( terminator_offset + 1 ); + count++; + } + + DBG ( "SMBIOS structure type %d not found\n", type ); + return -ENOENT; +} + +/** + * Find indexed string within SMBIOS structure + * + * @v strings SMBIOS strings descriptor + * @v index String index + * @v buffer Buffer for string + * @v length Length of string buffer + * @ret rc Return status code + */ +int find_smbios_string ( struct smbios_strings *strings, unsigned int index, + char *buffer, size_t length ) { + size_t offset = 0; + size_t string_len; + + /* Zero buffer. This ensures that a valid NUL terminator is + * always present (unless length==0). + */ + memset ( buffer, 0, length ); + + /* String numbers start at 1 (0 is used to indicate "no string") */ + if ( ! index ) + return 0; + + while ( offset < strings->length ) { + /* Get string length. This is known safe, since the + * smbios_strings struct is constructed so as to + * always end on a string boundary. + */ + string_len = strlen_user ( strings->data, offset ); + if ( --index == 0 ) { + /* Copy string, truncating as necessary. */ + if ( string_len >= length ) + string_len = ( length - 1 ); + copy_from_user ( buffer, strings->data, + offset, string_len ); + return 0; + } + offset += ( string_len + 1 ); + } + + DBG ( "SMBIOS string index %d not found\n", index ); + return -ENOENT; +} + +/** + * Get UUID from SMBIOS + * + * @v uuid UUID to fill in + * @ret rc Return status code + */ +int smbios_get_uuid ( union uuid *uuid ) { + struct smbios_system_information sysinfo; + int rc; + + if ( ( rc = find_smbios_structure ( SMBIOS_TYPE_SYSTEM_INFORMATION, + &sysinfo, sizeof ( sysinfo ), + NULL ) ) != 0 ) + return rc; + + memcpy ( uuid, sysinfo.uuid, sizeof ( *uuid ) ); + DBG ( "SMBIOS found UUID %s\n", uuid_ntoa ( uuid ) ); + + return 0; +} diff --git a/gpxe/src/arch/i386/image/bootsector.c b/gpxe/src/arch/i386/image/bootsector.c new file mode 100644 index 00000000..0f297a26 --- /dev/null +++ b/gpxe/src/arch/i386/image/bootsector.c @@ -0,0 +1,112 @@ +/* + * 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 + * + * 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 00000000..ed9d286d --- /dev/null +++ b/gpxe/src/arch/i386/image/bzimage.c @@ -0,0 +1,567 @@ +/* + * 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 + * + * 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/initrd.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 load context + */ +struct bzimage_load_context { + /** 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; + /** 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; +}; + +/** + * bzImage execution context + */ +struct bzimage_exec_context { + /** Real-mode kernel portion load segment address */ + unsigned int rm_kernel_seg; + /** Real-mode kernel portion load address */ + userptr_t rm_kernel; + /** Real-mode heap top (offset from rm_kernel) */ + size_t rm_heap; + /** Command line (offset from rm_kernel) */ + size_t rm_cmdline; + /** Video mode */ + unsigned int vid_mode; + /** Memory limit */ + uint64_t mem_limit; + /** Initrd address */ + physaddr_t ramdisk_image; + /** Initrd size */ + physaddr_t ramdisk_size; +}; + +/** + * Parse kernel command line for bootloader parameters + * + * @v image bzImage file + * @v exec_ctx Execution context + * @v cmdline Kernel command line + * @ret rc Return status code + */ +static int bzimage_parse_cmdline ( struct image *image, + struct bzimage_exec_context *exec_ctx, + const char *cmdline ) { + char *vga; + char *mem; + + /* Look for "vga=" */ + if ( ( vga = strstr ( cmdline, "vga=" ) ) ) { + vga += 4; + if ( strcmp ( vga, "normal" ) == 0 ) { + exec_ctx->vid_mode = BZI_VID_MODE_NORMAL; + } else if ( strcmp ( vga, "ext" ) == 0 ) { + exec_ctx->vid_mode = BZI_VID_MODE_EXT; + } else if ( strcmp ( vga, "ask" ) == 0 ) { + exec_ctx->vid_mode = BZI_VID_MODE_ASK; + } else { + exec_ctx->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; + exec_ctx->mem_limit = strtoul ( mem, &mem, 0 ); + switch ( *mem ) { + case 'G': + case 'g': + exec_ctx->mem_limit <<= 10; + case 'M': + case 'm': + exec_ctx->mem_limit <<= 10; + case 'K': + case 'k': + exec_ctx->mem_limit <<= 10; + break; + case '\0': + case ' ': + break; + default: + DBGC ( image, "bzImage %p strange \"mem=\" " + "terminator '%c'\n", image, *mem ); + break; + } + exec_ctx->mem_limit -= 1; + } + + return 0; +} + +/** + * Set command line + * + * @v image bzImage image + * @v exec_ctx Execution context + * @v cmdline Kernel command line + * @ret rc Return status code + */ +static int bzimage_set_cmdline ( struct image *image, + struct bzimage_exec_context *exec_ctx, + const char *cmdline ) { + size_t cmdline_len; + + /* Copy command line down to real-mode portion */ + cmdline_len = ( strlen ( cmdline ) + 1 ); + if ( cmdline_len > BZI_CMDLINE_SIZE ) + cmdline_len = BZI_CMDLINE_SIZE; + copy_to_user ( exec_ctx->rm_kernel, exec_ctx->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; + + /* Ignore images which aren't initrds */ + if ( initrd->type != &initrd_image_type ) + 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 ) { + DBGC ( image, "bzImage %p has initrd %p at [%lx,%lx)\n", + image, initrd, address, ( address + offset ) ); + memcpy_user ( address, offset, initrd->data, 0, + initrd->len ); + } + offset += initrd->len; + + offset = ( ( offset + 0x03 ) & ~0x03 ); + return offset; +} + +/** + * Load initrds, if any + * + * @v image bzImage image + * @v exec_ctx Execution context + * @ret rc Return status code + */ +static int bzimage_load_initrds ( struct image *image, + struct bzimage_exec_context *exec_ctx ) { + 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 ) > exec_ctx->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 */ + exec_ctx->ramdisk_image = address; + exec_ctx->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_exec_context exec_ctx; + struct bzimage_header bzhdr; + const char *cmdline = ( image->cmdline ? image->cmdline : "" ); + int rc; + + /* Initialise context */ + memset ( &exec_ctx, 0, sizeof ( exec_ctx ) ); + + /* Retrieve kernel header */ + exec_ctx.rm_kernel_seg = image->priv.ul; + exec_ctx.rm_kernel = real_to_user ( exec_ctx.rm_kernel_seg, 0 ); + copy_from_user ( &bzhdr, exec_ctx.rm_kernel, BZI_HDR_OFFSET, + sizeof ( bzhdr ) ); + exec_ctx.rm_cmdline = exec_ctx.rm_heap = + ( bzhdr.heap_end_ptr + 0x200 ); + exec_ctx.vid_mode = bzhdr.vid_mode; + if ( bzhdr.version >= 0x0203 ) { + exec_ctx.mem_limit = bzhdr.initrd_addr_max; + } else { + exec_ctx.mem_limit = BZI_INITRD_MAX; + } + + /* Parse command line for bootloader parameters */ + if ( ( rc = bzimage_parse_cmdline ( image, &exec_ctx, cmdline ) ) != 0) + return rc; + + /* Store command line */ + if ( ( rc = bzimage_set_cmdline ( image, &exec_ctx, cmdline ) ) != 0 ) + return rc; + + /* Load any initrds */ + if ( ( rc = bzimage_load_initrds ( image, &exec_ctx ) ) != 0 ) + return rc; + + /* Update and store kernel header */ + bzhdr.vid_mode = exec_ctx.vid_mode; + bzhdr.ramdisk_image = exec_ctx.ramdisk_image; + bzhdr.ramdisk_size = exec_ctx.ramdisk_size; + copy_to_user ( exec_ctx.rm_kernel, BZI_HDR_OFFSET, &bzhdr, + sizeof ( bzhdr ) ); + + /* Prepare for exiting */ + shutdown(); + + DBGC ( image, "bzImage %p jumping to RM kernel at %04x:0000 " + "(stack %04x:%04zx)\n", image, + ( exec_ctx.rm_kernel_seg + 0x20 ), + exec_ctx.rm_kernel_seg, exec_ctx.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" ( exec_ctx.rm_kernel_seg ), + "r" ( exec_ctx.rm_heap ), + "r" ( exec_ctx.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 and parse bzImage header + * + * @v image bzImage file + * @v load_ctx Load context + * @v bzhdr Buffer for bzImage header + * @ret rc Return status code + */ +static int bzimage_load_header ( struct image *image, + struct bzimage_load_context *load_ctx, + struct bzimage_header *bzhdr ) { + + /* Sanity check */ + if ( image->len < ( BZI_HDR_OFFSET + sizeof ( *bzhdr ) ) ) { + DBGC ( image, "bzImage %p too short for kernel header\n", + image ); + return -ENOEXEC; + } + + /* Read and verify header */ + copy_from_user ( bzhdr, image->data, BZI_HDR_OFFSET, + sizeof ( *bzhdr ) ); + if ( bzhdr->header != BZI_SIGNATURE ) { + DBGC ( image, "bzImage %p bad signature %08lx\n", + image, bzhdr->header ); + return -ENOEXEC; + } + + /* We don't support ancient kernels */ + if ( bzhdr->version < 0x0200 ) { + DBGC ( image, "bzImage %p version %04x not supported\n", + image, bzhdr->version ); + return -ENOTSUP; + } + + /* Calculate load address and size of real-mode portion */ + load_ctx->rm_kernel_seg = 0x1000; /* place RM kernel at 1000:0000 */ + load_ctx->rm_kernel = real_to_user ( load_ctx->rm_kernel_seg, 0 ); + load_ctx->rm_filesz = + ( ( bzhdr->setup_sects ? bzhdr->setup_sects : 4 ) + 1 ) << 9; + load_ctx->rm_memsz = BZI_ASSUMED_RM_SIZE; + if ( load_ctx->rm_filesz > image->len ) { + DBGC ( image, "bzImage %p too short for %zd byte of setup\n", + image, load_ctx->rm_filesz ); + return -ENOEXEC; + } + + /* Calculate load address and size of non-real-mode portion */ + load_ctx->pm_kernel = ( ( bzhdr->loadflags & BZI_LOAD_HIGH ) ? + phys_to_user ( BZI_LOAD_HIGH_ADDR ) : + phys_to_user ( BZI_LOAD_LOW_ADDR ) ); + load_ctx->pm_sz = ( image->len - load_ctx->rm_filesz ); + + DBGC ( image, "bzImage %p version %04x RM %#zx bytes PM %#zx bytes\n", + image, bzhdr->version, load_ctx->rm_filesz, load_ctx->pm_sz ); + return 0; +} + +/** + * Load real-mode portion of bzImage + * + * @v image bzImage file + * @v load_ctx Load context + * @ret rc Return status code + */ +static int bzimage_load_real ( struct image *image, + struct bzimage_load_context *load_ctx ) { + int rc; + + /* Allow space for the stack and heap */ + load_ctx->rm_memsz += BZI_STACK_SIZE; + load_ctx->rm_heap = load_ctx->rm_memsz; + + /* Allow space for the command line */ + load_ctx->rm_cmdline = load_ctx->rm_memsz; + load_ctx->rm_memsz += BZI_CMDLINE_SIZE; + + /* Prepare, verify, and load the real-mode segment */ + if ( ( rc = prep_segment ( load_ctx->rm_kernel, load_ctx->rm_filesz, + load_ctx->rm_memsz ) ) != 0 ) { + DBGC ( image, "bzImage %p could not prepare RM segment: %s\n", + image, strerror ( rc ) ); + return rc; + } + memcpy_user ( load_ctx->rm_kernel, 0, image->data, 0, + load_ctx->rm_filesz ); + + return 0; +} + +/** + * Load non-real-mode portion of bzImage + * + * @v image bzImage file + * @v load_ctx Load context + * @ret rc Return status code + */ +static int bzimage_load_non_real ( struct image *image, + struct bzimage_load_context *load_ctx ) { + int rc; + + /* Prepare, verify and load the non-real-mode segment */ + if ( ( rc = prep_segment ( load_ctx->pm_kernel, load_ctx->pm_sz, + load_ctx->pm_sz ) ) != 0 ) { + DBGC ( image, "bzImage %p could not prepare PM segment: %s\n", + image, strerror ( rc ) ); + return rc; + } + memcpy_user ( load_ctx->pm_kernel, 0, image->data, load_ctx->rm_filesz, + load_ctx->pm_sz ); + + return 0; +} + +/** + * Update and store bzImage header + * + * @v image bzImage file + * @v load_ctx Load context + * @v bzhdr Original bzImage header + * @ret rc Return status code + */ +static int bzimage_write_header ( struct image *image __unused, + struct bzimage_load_context *load_ctx, + struct bzimage_header *bzhdr ) { + struct bzimage_cmdline cmdline; + + /* Update the header and copy it into the loaded kernel */ + bzhdr->type_of_loader = BZI_LOADER_TYPE_GPXE; + if ( bzhdr->version >= 0x0201 ) { + bzhdr->heap_end_ptr = ( load_ctx->rm_heap - 0x200 ); + bzhdr->loadflags |= BZI_CAN_USE_HEAP; + } + if ( bzhdr->version >= 0x0202 ) { + bzhdr->cmd_line_ptr = user_to_phys ( load_ctx->rm_kernel, + load_ctx->rm_cmdline ); + } else { + cmdline.magic = BZI_CMDLINE_MAGIC; + cmdline.offset = load_ctx->rm_cmdline; + copy_to_user ( load_ctx->rm_kernel, BZI_CMDLINE_OFFSET, + &cmdline, sizeof ( cmdline ) ); + bzhdr->setup_move_size = load_ctx->rm_memsz; + } + copy_to_user ( load_ctx->rm_kernel, BZI_HDR_OFFSET, + bzhdr, sizeof ( *bzhdr ) ); + + return 0; +} + +/** + * Load bzImage image into memory + * + * @v image bzImage file + * @ret rc Return status code + */ +int bzimage_load ( struct image *image ) { + struct bzimage_load_context load_ctx; + struct bzimage_header bzhdr; + int rc; + + /* Initialise context */ + memset ( &load_ctx, 0, sizeof ( load_ctx ) ); + + /* Load and verify header */ + if ( ( rc = bzimage_load_header ( image, &load_ctx, &bzhdr ) ) != 0 ) + return rc; + + /* This is a bzImage image, valid or otherwise */ + if ( ! image->type ) + image->type = &bzimage_image_type; + + /* Load real-mode portion */ + if ( ( rc = bzimage_load_real ( image, &load_ctx ) ) != 0 ) + return rc; + + /* Load non-real-mode portion */ + if ( ( rc = bzimage_load_non_real ( image, &load_ctx ) ) != 0 ) + return rc; + + /* Update and write out header */ + if ( ( rc = bzimage_write_header ( image, &load_ctx, &bzhdr ) ) != 0 ) + return rc; + + /* Record real-mode segment in image private data field */ + image->priv.ul = load_ctx.rm_kernel_seg; + + 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/eltorito.c b/gpxe/src/arch/i386/image/eltorito.c new file mode 100644 index 00000000..9d573106 --- /dev/null +++ b/gpxe/src/arch/i386/image/eltorito.c @@ -0,0 +1,334 @@ +/* + * 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 + * + * 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 00000000..fbaebd5c --- /dev/null +++ b/gpxe/src/arch/i386/image/multiboot.c @@ -0,0 +1,445 @@ +/* + * 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 + * + * 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 cmdline Command line + * @ret physaddr Physical address of command line + */ +physaddr_t multiboot_add_cmdline ( 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", 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->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 [%lx,%lx)\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 ); + multiboot_build_memmap ( image, &mbinfo, mbmemmap, + ( sizeof(mbmemmap) / sizeof(mbmemmap[0]) ) ); + mb_cmdline_offset = 0; + mbinfo.cmdline = multiboot_add_cmdline ( 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(); + + /* Jump to OS with flat physical addressing */ + __asm__ __volatile__ ( PHYS_CODE ( "call *%%edi\n\t" ) + : : "a" ( MULTIBOOT_BOOTLOADER_MAGIC ), + "b" ( virt_to_phys ( &mbinfo ) ), + "D" ( entry ) + : "ecx", "edx", "esi", "ebp", "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; + + /* Verify and prepare segment */ + offset = ( hdr->offset - hdr->mb.header_addr + hdr->mb.load_addr ); + filesz = ( hdr->mb.load_end_addr - hdr->mb.load_addr ); + memsz = ( hdr->mb.bss_end_addr - hdr->mb.load_addr ); + 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 %08lx\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 %08lx not supported\n", + image, ( hdr.mb.flags & MB_UNSUPPORTED_FLAGS ) ); + return -ENOTSUP; + } + + /* Load the actual image */ + if ( hdr.mb.flags & MB_FLAG_RAW ) { + if ( ( rc = multiboot_load_raw ( image, &hdr ) ) != 0 ) + return rc; + } else { + if ( ( rc = multiboot_load_elf ( image ) ) != 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 00000000..73791be9 --- /dev/null +++ b/gpxe/src/arch/i386/image/nbi.c @@ -0,0 +1,458 @@ +#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; +} + +/** + * Guess boot network device + * + * @ret netdev Boot network device + */ +static struct net_device * guess_boot_netdev ( void ) { + struct net_device *boot_netdev; + + /* Just use the first network device */ + for_each_netdev ( boot_netdev ) { + return boot_netdev; + } + + return NULL; +} + +/** + * 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 = guess_boot_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(); + + /* 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 00000000..9e634f14 --- /dev/null +++ b/gpxe/src/arch/i386/image/pxe_image.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 + * + * 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; + + /* Ensure that PXE stack is ready to use */ + pxe_init_structures(); + pxe_hook_int1a(); + + /* Arbitrarily pick the first open network device to use for PXE */ + for_each_netdev ( netdev ) { + pxe_set_netdev ( netdev ); + break; + } + + /* Many things will break if pxe_netdev is NULL */ + if ( ! pxe_netdev ) { + DBGC ( image, "IMAGE %p could not locate PXE net device\n", + image ); + return -ENODEV; + } + + /* Start PXE NBP */ + rc = pxe_start_nbp(); + + /* Deactivate PXE */ + pxe_set_netdev ( NULL ); + pxe_unhook_int1a(); + + 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; + + /* 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 00000000..cd5668e0 --- /dev/null +++ b/gpxe/src/arch/i386/include/basemem.h @@ -0,0 +1,33 @@ +#ifndef _BASEMEM_H +#define _BASEMEM_H + +/** @file + * + * Base memory allocation + * + */ + +#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 00000000..e4d4f49c --- /dev/null +++ b/gpxe/src/arch/i386/include/basemem_packet.h @@ -0,0 +1,13 @@ +#ifndef BASEMEM_PACKET_H +#define BASEMEM_PACKET_H + +#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 00000000..630a898b --- /dev/null +++ b/gpxe/src/arch/i386/include/bios.h @@ -0,0 +1,11 @@ +#ifndef BIOS_H +#define BIOS_H + +#define BDA_SEG 0x0040 +#define BDA_FBMS 0x0013 +#define BDA_NUM_DRIVES 0x0075 + +extern unsigned long currticks ( void ); +extern void cpu_nap ( void ); + +#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 00000000..0dd7c4eb --- /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 00000000..d4e34963 --- /dev/null +++ b/gpxe/src/arch/i386/include/biosint.h @@ -0,0 +1,18 @@ +#ifndef BIOSINT_H +#define BIOSINT_H + +/** + * @file BIOS interrupts + * + */ + +struct segoff; + +extern int 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 00000000..54b93ab9 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/byteswap.h @@ -0,0 +1,76 @@ +#ifndef ETHERBOOT_BITS_BYTESWAP_H +#define ETHERBOOT_BITS_BYTESWAP_H + +static inline __attribute__ ((always_inline, const)) uint16_t +__i386_bswap_16(uint16_t x) +{ + __asm__("xchgb %b0,%h0\n\t" + : "=q" (x) + : "0" (x)); + return x; +} + +static inline __attribute__ ((always_inline, const)) uint32_t +__i386_bswap_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 +__i386_bswap_64(uint64_t x) +{ + union { + uint64_t qword; + uint32_t dword[2]; + } u; + + u.qword = x; + u.dword[0] = __i386_bswap_32(u.dword[0]); + u.dword[1] = __i386_bswap_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; +} + +#define __bswap_constant_16(x) \ + ((uint16_t)((((uint16_t)(x) & 0x00ff) << 8) | \ + (((uint16_t)(x) & 0xff00) >> 8))) + +#define __bswap_constant_32(x) \ + ((uint32_t)((((uint32_t)(x) & 0x000000ffU) << 24) | \ + (((uint32_t)(x) & 0x0000ff00U) << 8) | \ + (((uint32_t)(x) & 0x00ff0000U) >> 8) | \ + (((uint32_t)(x) & 0xff000000U) >> 24))) + +#define __bswap_constant_64(x) \ + ((uint64_t)((((uint64_t)(x) & 0x00000000000000ffULL) << 56) | \ + (((uint64_t)(x) & 0x000000000000ff00ULL) << 40) | \ + (((uint64_t)(x) & 0x0000000000ff0000ULL) << 24) | \ + (((uint64_t)(x) & 0x00000000ff000000ULL) << 8) | \ + (((uint64_t)(x) & 0x000000ff00000000ULL) >> 8) | \ + (((uint64_t)(x) & 0x0000ff0000000000ULL) >> 24) | \ + (((uint64_t)(x) & 0x00ff000000000000ULL) >> 40) | \ + (((uint64_t)(x) & 0xff00000000000000ULL) >> 56))) + +#define __bswap_16(x) \ + ((uint16_t)(__builtin_constant_p(x) ? \ + __bswap_constant_16(x) : \ + __i386_bswap_16(x))) + +#define __bswap_32(x) \ + ((uint32_t)(__builtin_constant_p(x) ? \ + __bswap_constant_32(x) : \ + __i386_bswap_32(x))) + +#define __bswap_64(x) \ + ((uint64_t)(__builtin_constant_p(x) ? \ + __bswap_constant_64(x) : \ + __i386_bswap_64(x))) + +#endif /* ETHERBOOT_BITS_BYTESWAP_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 00000000..83339ddd --- /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/elf.h b/gpxe/src/arch/i386/include/bits/elf.h new file mode 100644 index 00000000..dad9c7b8 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/elf.h @@ -0,0 +1,91 @@ +#ifndef I386_BITS_ELF_H +#define I386_BITS_ELF_H + +#include "cpu.h" + +#ifdef CONFIG_X86_64 +/* ELF Defines for the 64bit version of the current architecture */ +#define EM_CURRENT_64 EM_X86_64 +#define EM_CURRENT_64_PRESENT ( \ + CPU_FEATURE_P(cpu_info.x86_capability, LM) && \ + CPU_FEATURE_P(cpu_info.x86_capability, PAE) && \ + CPU_FEATURE_P(cpu_info.x86_capability, PSE)) + +#define ELF_CHECK_X86_64_ARCH(x) \ + (EM_CURRENT_64_PRESENT && ((x).e_machine == EM_X86_64)) +#define __unused_i386 +#else +#define ELF_CHECK_X86_64_ARCH(x) 0 +#define __unused_i386 __unused +#endif + + +/* ELF Defines for the current architecture */ +#define EM_CURRENT EM_386 +#define ELFDATA_CURRENT ELFDATA2LSB + +#define ELF_CHECK_I386_ARCH(x) \ + (((x).e_machine == EM_386) || ((x).e_machine == EM_486)) + +#define ELF_CHECK_ARCH(x) \ + ((ELF_CHECK_I386_ARCH(x) || ELF_CHECK_X86_64_ARCH(x)) && \ + ((x).e_entry <= 0xffffffffUL)) + +#ifdef IMAGE_FREEBSD +/* + * FreeBSD has this rather strange "feature" of its design. + * At some point in its evolution, FreeBSD started to rely + * externally on private/static/debug internal symbol information. + * That is, some of the interfaces that software uses to access + * and work with the FreeBSD kernel are made available not + * via the shared library symbol information (the .DYNAMIC section) + * but rather the debug symbols. This means that any symbol, not + * just publicly defined symbols can be (and are) used by system + * tools to make the system work. (such as top, swapinfo, swapon, + * etc) + * + * Even worse, however, is the fact that standard ELF loaders do + * not know how to load the symbols since they are not within + * an ELF PT_LOAD section. The kernel needs these symbols to + * operate so the following changes/additions to the boot + * loading of EtherBoot have been made to get the kernel to load. + * All of the changes are within IMAGE_FREEBSD such that the + * extra/changed code only compiles when FREEBSD support is + * enabled. + */ + +/* + * Section header for FreeBSD (debug symbol kludge!) support + */ +typedef struct { + Elf32_Word sh_name; /* Section name (index into the + section header string table). */ + Elf32_Word sh_type; /* Section type. */ + Elf32_Word sh_flags; /* Section flags. */ + Elf32_Addr sh_addr; /* Address in memory image. */ + Elf32_Off sh_offset; /* Offset in file. */ + Elf32_Size sh_size; /* Size in bytes. */ + Elf32_Word sh_link; /* Index of a related section. */ + Elf32_Word sh_info; /* Depends on section type. */ + Elf32_Size sh_addralign; /* Alignment in bytes. */ + Elf32_Size sh_entsize; /* Size of each entry in section. */ +} Elf32_Shdr; + +/* sh_type */ +#define SHT_SYMTAB 2 /* symbol table section */ +#define SHT_STRTAB 3 /* string table section */ + +/* + * Module information subtypes (for the metadata that we need to build) + */ +#define MODINFO_END 0x0000 /* End of list */ +#define MODINFO_NAME 0x0001 /* Name of module (string) */ +#define MODINFO_TYPE 0x0002 /* Type of module (string) */ +#define MODINFO_METADATA 0x8000 /* Module-specfic */ + +#define MODINFOMD_SSYM 0x0003 /* start of symbols */ +#define MODINFOMD_ESYM 0x0004 /* end of symbols */ + +#endif /* IMAGE_FREEBSD */ + +#endif /* I386_BITS_ELF_H */ diff --git a/gpxe/src/arch/i386/include/bits/elf_x.h b/gpxe/src/arch/i386/include/bits/elf_x.h new file mode 100644 index 00000000..86c67250 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/elf_x.h @@ -0,0 +1,5 @@ +#define ARCH_ELF_CLASS ELFCLASS32 +#define ARCH_ELF_DATA ELFDATA2LSB +#define ARCH_ELF_MACHINE_OK(x) ((x)==EM_386 || (x)==EM_486) +typedef Elf32_Ehdr Elf_ehdr; +typedef Elf32_Phdr Elf_phdr; 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 00000000..d43e9aac --- /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 00000000..b23b233a --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/endian.h @@ -0,0 +1,9 @@ +#ifndef ETHERBOOT_BITS_ENDIAN_H +#define ETHERBOOT_BITS_ENDIAN_H + +#define __BYTE_ORDER __LITTLE_ENDIAN + +#define le32_to_cpup(x) (*(uint32_t *)(x)) +#define cpu_to_le16p(x) (*(uint16_t*)(x)) + +#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 00000000..0f140214 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/errfile.h @@ -0,0 +1,34 @@ +#ifndef _BITS_ERRFILE_H +#define _BITS_ERRFILE_H + +/** + * @addtogroup errfile Error file identifiers + * @{ + */ + +#define ERRFILE_umalloc ( ERRFILE_ARCH | ERRFILE_CORE | 0x00000000 ) +#define ERRFILE_memmap ( ERRFILE_ARCH | ERRFILE_CORE | 0x00010000 ) +#define ERRFILE_pnpbios ( ERRFILE_ARCH | ERRFILE_CORE | 0x00020000 ) +#define ERRFILE_smbios ( ERRFILE_ARCH | ERRFILE_CORE | 0x00030000 ) +#define ERRFILE_biosint ( ERRFILE_ARCH | ERRFILE_CORE | 0x00040000 ) +#define ERRFILE_int13 ( ERRFILE_ARCH | ERRFILE_CORE | 0x00050000 ) + +#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_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/stdint.h b/gpxe/src/arch/i386/include/bits/stdint.h new file mode 100644 index 00000000..a2947cda --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/stdint.h @@ -0,0 +1,21 @@ +#ifndef _BITS_STDINT_H +#define _BITS_STDINT_H + +typedef typeof(sizeof(int)) size_t; +typedef signed long ssize_t; +typedef signed long off_t; + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned long uint32_t; +typedef unsigned long long uint64_t; + +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed long 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/string.h b/gpxe/src/arch/i386/include/bits/string.h new file mode 100644 index 00000000..c05a7df8 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/string.h @@ -0,0 +1,252 @@ +#ifndef ETHERBOOT_BITS_STRING_H +#define ETHERBOOT_BITS_STRING_H +/* + * Taken from Linux /usr/include/asm/string.h + * All except memcpy, memmove, memset and memcmp removed. + * + * Non-standard memswap() function added because it saves quite a bit + * of code (mbrown@fensystems.co.uk). + */ + +/* + * This string-include defines all string functions as inline + * functions. Use gcc. It also assumes ds=es=data space, this should be + * normal. Most of the string-functions are rather heavily hand-optimized, + * see especially strtok,strstr,str[c]spn. They should work, but are not + * very easy to understand. Everything is done entirely within the register + * set, making the functions fast and clean. String instructions have been + * used through-out, making for "slightly" unclear code :-) + * + * NO Copyright (C) 1991, 1992 Linus Torvalds, + * consider these trivial functions to be PD. + */ + +#define __HAVE_ARCH_MEMCPY + +extern __attribute__ (( regparm ( 3 ) )) void * __memcpy ( void *dest, + const void *src, + size_t len ); + +#if 0 +static inline __attribute__ (( always_inline )) void * +__memcpy ( void *dest, const void *src, size_t len ) { + int d0, d1, d2; + __asm__ __volatile__ ( "rep ; movsb" + : "=&c" ( d0 ), "=&S" ( d1 ), "=&D" ( d2 ) + : "0" ( len ), "1" ( src ), "2" ( dest ) + : "memory" ); + return dest; +} +#endif + +static inline __attribute__ (( always_inline )) void * +__constant_memcpy ( void *dest, const void *src, size_t len ) { + union { + uint32_t u32[2]; + uint16_t u16[4]; + uint8_t u8[8]; + } __attribute__ (( __may_alias__ )) *dest_u = dest; + const union { + uint32_t u32[2]; + uint16_t u16[4]; + uint8_t u8[8]; + } __attribute__ (( __may_alias__ )) *src_u = src; + const void *esi; + void *edi; + + switch ( len ) { + case 0 : /* 0 bytes */ + return dest; + /* + * Single-register moves; these are always better than a + * string operation. We can clobber an arbitrary two + * registers (data, source, dest can re-use source register) + * instead of being restricted to esi and edi. There's also a + * much greater potential for optimising with nearby code. + * + */ + case 1 : /* 4 bytes */ + dest_u->u8[0] = src_u->u8[0]; + return dest; + case 2 : /* 6 bytes */ + dest_u->u16[0] = src_u->u16[0]; + return dest; + case 4 : /* 4 bytes */ + dest_u->u32[0] = src_u->u32[0]; + return dest; + /* + * Double-register moves; these are probably still a win. + * + */ + case 3 : /* 12 bytes */ + dest_u->u16[0] = src_u->u16[0]; + dest_u->u8[2] = src_u->u8[2]; + return dest; + case 5 : /* 10 bytes */ + dest_u->u32[0] = src_u->u32[0]; + dest_u->u8[4] = src_u->u8[4]; + return dest; + case 6 : /* 12 bytes */ + dest_u->u32[0] = src_u->u32[0]; + dest_u->u16[2] = src_u->u16[2]; + return dest; + case 8 : /* 10 bytes */ + dest_u->u32[0] = src_u->u32[0]; + dest_u->u32[1] = src_u->u32[1]; + return dest; + } + + /* Even if we have to load up esi and edi ready for a string + * operation, we can sometimes save space by using multiple + * single-byte "movs" operations instead of loading up ecx and + * using "rep movsb". + * + * "load ecx, rep movsb" is 7 bytes, plus an average of 1 byte + * to allow for saving/restoring ecx 50% of the time. + * + * "movsl" and "movsb" are 1 byte each, "movsw" is two bytes. + * (In 16-bit mode, "movsl" is 2 bytes and "movsw" is 1 byte, + * but "movsl" moves twice as much data, so it balances out). + * + * The cutoff point therefore occurs around 26 bytes; the byte + * requirements for each method are: + * + * len 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 + * #bytes (ecx) 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 + * #bytes (no ecx) 4 5 6 7 5 6 7 8 6 7 8 9 7 8 9 10 + */ + + esi = src; + edi = dest; + + if ( len >= 26 ) + return __memcpy ( dest, src, len ); + + if ( len >= 6*4 ) + __asm__ __volatile__ ( "movsl" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( len >= 5*4 ) + __asm__ __volatile__ ( "movsl" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( len >= 4*4 ) + __asm__ __volatile__ ( "movsl" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( len >= 3*4 ) + __asm__ __volatile__ ( "movsl" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( len >= 2*4 ) + __asm__ __volatile__ ( "movsl" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( len >= 1*4 ) + __asm__ __volatile__ ( "movsl" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( ( len % 4 ) >= 2 ) + __asm__ __volatile__ ( "movsw" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( ( len % 2 ) >= 1 ) + __asm__ __volatile__ ( "movsb" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + + return dest; +} + +#define memcpy( dest, src, len ) \ + ( __builtin_constant_p ( (len) ) ? \ + __constant_memcpy ( (dest), (src), (len) ) : \ + __memcpy ( (dest), (src), (len) ) ) + +#define __HAVE_ARCH_MEMMOVE +static inline void * memmove(void * dest,const void * src, size_t n) +{ +int d0, d1, d2; +if (dest<src) +__asm__ __volatile__( + "cld\n\t" + "rep\n\t" + "movsb" + : "=&c" (d0), "=&S" (d1), "=&D" (d2) + :"0" (n),"1" (src),"2" (dest) + : "memory"); +else +__asm__ __volatile__( + "std\n\t" + "rep\n\t" + "movsb\n\t" + "cld" + : "=&c" (d0), "=&S" (d1), "=&D" (d2) + :"0" (n), + "1" (n-1+(const char *)src), + "2" (n-1+(char *)dest) + :"memory"); +return dest; +} + +#define __HAVE_ARCH_MEMSET +static inline void * memset(void *s, int c,size_t count) +{ +int d0, d1; +__asm__ __volatile__( + "cld\n\t" + "rep\n\t" + "stosb" + : "=&c" (d0), "=&D" (d1) + :"a" (c),"1" (s),"0" (count) + :"memory"); +return s; +} + +#define __HAVE_ARCH_MEMSWAP +static inline void * memswap(void *dest, void *src, size_t n) +{ +int d0, d1, d2, d3; +__asm__ __volatile__( + "\n1:\t" + "movb (%%edi),%%al\n\t" + "xchgb (%%esi),%%al\n\t" + "incl %%esi\n\t" + "stosb\n\t" + "loop 1b" + : "=&c" (d0), "=&S" (d1), "=&D" (d2), "=&a" (d3) + : "0" (n), "1" (src), "2" (dest) + : "memory" ); +return dest; +} + +#define __HAVE_ARCH_STRNCMP +static inline int strncmp(const char * cs,const char * ct,size_t count) +{ +register int __res; +int d0, d1, d2; +__asm__ __volatile__( + "1:\tdecl %3\n\t" + "js 2f\n\t" + "lodsb\n\t" + "scasb\n\t" + "jne 3f\n\t" + "testb %%al,%%al\n\t" + "jne 1b\n" + "2:\txorl %%eax,%%eax\n\t" + "jmp 4f\n" + "3:\tsbbl %%eax,%%eax\n\t" + "orb $1,%%al\n" + "4:" + :"=a" (__res), "=&S" (d0), "=&D" (d1), "=&c" (d2) + :"1" (cs),"2" (ct),"3" (count)); +return __res; +} + +#define __HAVE_ARCH_STRLEN +static inline size_t strlen(const char * s) +{ +int d0; +register int __res; +__asm__ __volatile__( + "repne\n\t" + "scasb\n\t" + "notl %0\n\t" + "decl %0" + :"=c" (__res), "=&D" (d0) :"1" (s),"a" (0), "0" (0xffffffff)); +return __res; +} + +#endif /* ETHERBOOT_BITS_STRING_H */ diff --git a/gpxe/src/arch/i386/include/bits/timer2.h b/gpxe/src/arch/i386/include/bits/timer2.h new file mode 100644 index 00000000..83923b29 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/timer2.h @@ -0,0 +1,8 @@ +#ifndef BITS_TIMER2_H +#define BITS_TIMER2_H + +#include <stddef.h> + +void i386_timer2_udelay(unsigned int usecs); + +#endif 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 00000000..9c6d0c21 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/uaccess.h @@ -0,0 +1,6 @@ +#ifndef _BITS_UACCESS_H +#define _BITS_UACCESS_H + +#include <realmode.h> + +#endif /* _BITS_UACCESS_H */ diff --git a/gpxe/src/arch/i386/include/bits/uuid.h b/gpxe/src/arch/i386/include/bits/uuid.h new file mode 100644 index 00000000..0cbd320a --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/uuid.h @@ -0,0 +1,10 @@ +#ifndef _I386_UUID_H +#define _I386_UUID_H + +#include <smbios.h> + +static inline int get_uuid ( union uuid *uuid ) { + return smbios_get_uuid ( uuid ); +} + +#endif /* _I386_UUID_H */ diff --git a/gpxe/src/arch/i386/include/bochs.h b/gpxe/src/arch/i386/include/bochs.h new file mode 100644 index 00000000..9d090fc1 --- /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 00000000..e9071052 --- /dev/null +++ b/gpxe/src/arch/i386/include/bootsector.h @@ -0,0 +1,12 @@ +#ifndef _BOOTSECTOR_H +#define _BOOTSECTOR_H + +/** @file + * + * x86 bootsector image format + */ + +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 00000000..609e8362 --- /dev/null +++ b/gpxe/src/arch/i386/include/bzimage.h @@ -0,0 +1,129 @@ +#ifndef _BZIMAGE_H +#define _BZIMAGE_H + +#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; +} __attribute__ (( packed )); + +/** Offset of bzImage header within kernel image */ +#define BZI_HDR_OFFSET 0x1f1 + +/** 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/callbacks_arch.h b/gpxe/src/arch/i386/include/callbacks_arch.h new file mode 100644 index 00000000..f9cba488 --- /dev/null +++ b/gpxe/src/arch/i386/include/callbacks_arch.h @@ -0,0 +1,243 @@ +/* Callout/callback interface for Etherboot + * + * This file provides the mechanisms for making calls from Etherboot + * to external programs and vice-versa. + * + * Initial version by Michael Brown <mbrown@fensystems.co.uk>, January 2004. + * + * $Id$ + */ + +#ifndef CALLBACKS_ARCH_H +#define CALLBACKS_ARCH_H + +/* Skip the definitions that won't make sense to the assembler */ +#ifndef ASSEMBLY + +/* Struct to hold general-purpose register values. PUSHAL and POPAL + * can work directly with this structure; do not change the order of + * registers. + */ +typedef struct { + 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; + }; +} regs_t; + +/* Struct to hold segment register values. Don't change the order; + * many bits of assembly code rely on it. + */ +typedef struct { + uint16_t cs; + uint16_t ss; + uint16_t ds; + uint16_t es; + uint16_t fs; + uint16_t gs; +} PACKED seg_regs_t; + +/* Struct for a GDT descriptor */ +typedef struct { + uint16_t limit; + uint32_t address; + uint16_t padding; +} PACKED gdt_descriptor_t; + +/* Struct for a GDT entry. Use GDT_SEGMENT() to fill it in. + */ +typedef struct { + uint16_t limit_0_15; + uint16_t base_0_15; + uint8_t base_16_23; + uint8_t accessed__type__sflag__dpl__present; + uint8_t limit_16_19__avl__size__granularity; + uint8_t base_24_31; +} PACKED gdt_segment_t; + +#define GDT_SEGMENT(base,limit,type,sflag,dpl,avl,size,granularity) \ + ( (gdt_segment_t) { \ + ( (limit) & 0xffff ), \ + ( (base) & 0xffff ), \ + ( ( (base) >> 16 ) & 0xff ), \ + ( ( 1 << 0 ) | ( (type) << 1 ) | \ + ( (sflag) << 4 ) | ( (dpl) << 5 ) | ( 1 << 7 ) ), \ + ( ( (limit) >> 16 ) | \ + ( (avl) << 4 ) | ( (size) << 5 ) | ( (granularity) << 7 ) ),\ + ( (base) >> 24 ) \ + } ) +#define GDT_SEGMENT_BASE(gdt_segment) \ + ( (gdt_segment)->base_0_15 | \ + (gdt_segment)->base_16_23 << 16 | \ + (gdt_segment)->base_24_31 << 24 ) +#define GDT_SEGMENT_LIMIT(gdt_segment) \ + ( (gdt_segment)->limit_0_15 | \ + ( ( (gdt_segment)->limit_16_19__avl__size__granularity \ + & 0xf ) << 16 ) ) +#define GDT_SEGMENT_GRANULARITY(gdt_segment) \ + ( ( (gdt_segment)->limit_16_19__avl__size__granularity \ + & 0x80 ) >> 7 ) +#define GDT_SEGMENT_TYPE(gdt_segment) \ + ( ( (gdt_segment)->accessed__type__sflag__dpl__present & 0x0e ) >> 1 ) +#define GDT_SEGMENT_SIZE(gdt_segment) \ + ( ( (gdt_segment)->limit_16_19__avl__size__granularity \ + & 0x60 ) >> 5 ) + +#define GDT_TYPE_DATA (0x0) +#define GDT_TYPE_STACK (0x2) +#define GDT_TYPE_WRITEABLE (0x1) +#define GDT_TYPE_CODE (0x6) +#define GDT_TYPE_EXEC_ONLY_CODE (0x4) +#define GDT_TYPE_CONFORMING (0x1) +#define GDT_SFLAG_SYSTEM (0) +#define GDT_SFLAG_NORMAL (1) +#define GDT_AVL_NORMAL (0) +#define GDT_SIZE_16BIT (0x0) +#define GDT_SIZE_32BIT (0x2) +#define GDT_SIZE_64BIT (0x1) +#define GDT_SIZE_UNKNOWN (0x3) +#define GDT_GRANULARITY_SMALL (0) +#define GDT_GRANULARITY_LARGE (1) +#define GDT_SEGMENT_NORMAL(base,limit,type,size,granularity) \ + GDT_SEGMENT ( base, limit, type, \ + GDT_SFLAG_NORMAL, 0, GDT_AVL_NORMAL, \ + size, granularity ) + +/* Protected mode code segment */ +#define GDT_SEGMENT_PMCS(base) GDT_SEGMENT_NORMAL ( \ + base, 0xfffff, GDT_TYPE_CODE | GDT_TYPE_CONFORMING, \ + GDT_SIZE_32BIT, GDT_GRANULARITY_LARGE ) +#define GDT_SEGMENT_PMCS_PHYS GDT_SEGMENT_PMCS(0) +/* Protected mode data segment */ +#define GDT_SEGMENT_PMDS(base) GDT_SEGMENT_NORMAL ( \ + base, 0xfffff, GDT_TYPE_DATA | GDT_TYPE_WRITEABLE, \ + GDT_SIZE_32BIT, GDT_GRANULARITY_LARGE ) +#define GDT_SEGMENT_PMDS_PHYS GDT_SEGMENT_PMDS(0) +/* Real mode code segment */ +/* Not sure if there's any reason to use GDT_TYPE_EXEC_ONLY_CODE + * instead of just GDT_TYPE_CODE, but that's what our old GDT did and + * it worked, so I'm not changing it. + */ +#define GDT_SEGMENT_RMCS(base) GDT_SEGMENT_NORMAL ( \ + base, 0xffff, GDT_TYPE_EXEC_ONLY_CODE | GDT_TYPE_CONFORMING, \ + GDT_SIZE_16BIT, GDT_GRANULARITY_SMALL ) +/* Real mode data segment */ +#define GDT_SEGMENT_RMDS(base) GDT_SEGMENT_NORMAL ( \ + base, 0xffff, GDT_TYPE_DATA | GDT_TYPE_WRITEABLE, \ + GDT_SIZE_16BIT, GDT_GRANULARITY_SMALL ) +/* Long mode code segment */ +#define GDT_SEGMENT_LMCS(base) GDT_SEGMENT_NORMAL ( \ + base, 0xfffff, GDT_TYPE_CODE | GDT_TYPE_CONFORMING, \ + GDT_SIZE_64BIT, GDT_GRANULARITY_LARGE ) +#define GDT_SEGMENT_LMCS_PHYS GDT_SEGMENT_LMCS(0) +/* Long mode data segment */ +/* AFIACT, GDT_SIZE_64BIT applies only to code segments */ +#define GDT_SEGMENT_LMDS(base) GDT_SEGMENT_NORMAL ( \ + base, 0xfffff, GDT_TYPE_DATA | GDT_TYPE_WRITEABLE, \ + GDT_SIZE_32BIT, GDT_GRANULARITY_LARGE ) +#define GDT_SEGMENT_LMDS_PHYS GDT_SEGMENT_LMDS(0) + +/* Template for creating GDT structures (including segment register + * lists), suitable for passing as parameters to external_call(). + */ +#define GDT_STRUCT_t(num_segments) \ + struct { \ + gdt_descriptor_t descriptor; \ + gdt_segment_t segments[num_segments]; \ + } PACKED +/* And utility function for filling it in */ +#define GDT_ADJUST(structure) { \ + (structure)->descriptor.address = \ + virt_to_phys(&((structure)->descriptor.limit)); \ + (structure)->descriptor.limit = \ + sizeof((structure)->segments) + 8 - 1; \ + (structure)->descriptor.padding = 0; \ +} + +/* Data passed in to in_call() by assembly wrapper. + */ +typedef struct { + regs_t regs; + seg_regs_t seg_regs; + gdt_descriptor_t gdt_desc; + uint32_t flags; + struct { + uint32_t offset; + uint32_t segment; + } ret_addr; +} PACKED i386_pm_in_call_data_t; + +typedef struct { + seg_regs_t seg_regs; + union { + uint16_t pad; + uint16_t prefix_sp; + }; + uint16_t flags; + struct { + uint16_t offset; + uint16_t segment; + } ret_addr; + uint32_t orig_opcode; +} PACKED i386_rm_in_call_data_t; + +typedef struct { + i386_pm_in_call_data_t *pm; + i386_rm_in_call_data_t *rm; +} i386_in_call_data_t; +#define in_call_data_t i386_in_call_data_t + +/* Function prototypes + */ +extern int install_rm_callback_interface ( void *address, size_t available ); + +#endif /* ASSEMBLY */ + +#define RM_IN_CALL (0) +#define RM_IN_CALL_FAR (2) + +#endif /* CALLBACKS_ARCH_H */ diff --git a/gpxe/src/arch/i386/include/gateA20.h b/gpxe/src/arch/i386/include/gateA20.h new file mode 100644 index 00000000..297ad6f2 --- /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/int13.h b/gpxe/src/arch/i386/include/int13.h new file mode 100644 index 00000000..2a193831 --- /dev/null +++ b/gpxe/src/arch/i386/include/int13.h @@ -0,0 +1,277 @@ +#ifndef INT13_H +#define INT13_H + +/** @file + * + * INT 13 emulation + * + */ + +#include <stdint.h> +#include <gpxe/list.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 drive number (0x80-0xff) */ + unsigned int 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/io.h b/gpxe/src/arch/i386/include/io.h new file mode 100644 index 00000000..c26fdf7e --- /dev/null +++ b/gpxe/src/arch/i386/include/io.h @@ -0,0 +1,265 @@ +#ifndef ETHERBOOT_IO_H +#define ETHERBOOT_IO_H + +#include <stdint.h> +#include "virtaddr.h" + +/* virt_to_bus converts an addresss inside of etherboot [_start, _end] + * into a memory access cards can use. + */ +#define virt_to_bus virt_to_phys + + +/* bus_to_virt reverses virt_to_bus, the address must be output + * from virt_to_bus to be valid. This function does not work on + * all bus addresses. + */ +#define bus_to_virt phys_to_virt + +/* ioremap converts a random 32bit bus address into something + * etherboot can access. + */ +static inline void *ioremap(unsigned long bus_addr, unsigned long length __unused) +{ + return bus_to_virt(bus_addr); +} + +/* iounmap cleans up anything ioremap had to setup */ +static inline void iounmap(void *virt_addr __unused) +{ + return; +} + +/* + * This file contains the definitions for the x86 IO instructions + * inb/inw/inl/outb/outw/outl and the "string versions" of the same + * (insb/insw/insl/outsb/outsw/outsl). You can also use "pausing" + * versions of the single-IO instructions (inb_p/inw_p/..). + * + * This file is not meant to be obfuscating: it's just complicated + * to (a) handle it all in a way that makes gcc able to optimize it + * as well as possible and (b) trying to avoid writing the same thing + * over and over again with slight variations and possibly making a + * mistake somewhere. + */ + +/* + * Thanks to James van Artsdalen for a better timing-fix than + * the two short jumps: using outb's to a nonexistent port seems + * to guarantee better timings even on fast machines. + * + * On the other hand, I'd like to be sure of a non-existent port: + * I feel a bit unsafe about using 0x80 (should be safe, though) + * + * Linus + */ + +#ifdef SLOW_IO_BY_JUMPING +#define __SLOW_DOWN_IO __asm__ __volatile__("jmp 1f\n1:\tjmp 1f\n1:") +#else +#define __SLOW_DOWN_IO __asm__ __volatile__("outb %al,$0x80") +#endif + +#ifdef REALLY_SLOW_IO +#define SLOW_DOWN_IO { __SLOW_DOWN_IO; __SLOW_DOWN_IO; __SLOW_DOWN_IO; __SLOW_DOWN_IO; } +#else +#define SLOW_DOWN_IO __SLOW_DOWN_IO +#endif + +/* + * readX/writeX() are used to access memory mapped devices. On some + * architectures the memory mapped IO stuff needs to be accessed + * differently. On the x86 architecture, we just read/write the + * memory location directly. + */ +static inline __attribute__ (( always_inline )) unsigned long +_readb ( volatile uint8_t *addr ) { + unsigned long data = *addr; + DBGIO ( "[%08lx] => %02lx\n", virt_to_phys ( addr ), data ); + return data; +} +static inline __attribute__ (( always_inline )) unsigned long +_readw ( volatile uint16_t *addr ) { + unsigned long data = *addr; + DBGIO ( "[%08lx] => %04lx\n", virt_to_phys ( addr ), data ); + return data; +} +static inline __attribute__ (( always_inline )) unsigned long +_readl ( volatile uint32_t *addr ) { + unsigned long data = *addr; + DBGIO ( "[%08lx] => %08lx\n", virt_to_phys ( addr ), data ); + return data; +} +#define readb( addr ) _readb ( ( volatile uint8_t * ) (addr) ) +#define readw( addr ) _readw ( ( volatile uint16_t * ) (addr) ) +#define readl( addr ) _readl ( ( volatile uint32_t * ) (addr) ) + +static inline __attribute__ (( always_inline )) void +_writeb ( unsigned long data, volatile uint8_t *addr ) { + DBGIO ( "[%08lx] <= %02lx\n", virt_to_phys ( addr ), data ); + *addr = data; +} +static inline __attribute__ (( always_inline )) void +_writew ( unsigned long data, volatile uint16_t *addr ) { + DBGIO ( "[%08lx] <= %04lx\n", virt_to_phys ( addr ), data ); + *addr = data; +} +static inline __attribute__ (( always_inline )) void +_writel ( unsigned long data, volatile uint32_t *addr ) { + DBGIO ( "[%08lx] <= %08lx\n", virt_to_phys ( addr ), data ); + *addr = data; +} +#define writeb( b, addr ) _writeb ( (b), ( volatile uint8_t * ) (addr) ) +#define writew( b, addr ) _writew ( (b), ( volatile uint16_t * ) (addr) ) +#define writel( b, addr ) _writel ( (b), ( volatile uint32_t * ) (addr) ) + +#define memcpy_fromio(a,b,c) memcpy((a),(void *)(b),(c)) +#define memcpy_toio(a,b,c) memcpy((void *)(a),(b),(c)) + +/* + * Force strict CPU ordering. + * And yes, this is required on UP too when we're talking + * to devices. + * + * For now, "wmb()" doesn't actually do anything, as all + * Intel CPU's follow what Intel calls a *Processor Order*, + * in which all writes are seen in the program order even + * outside the CPU. + * + * I expect future Intel CPU's to have a weaker ordering, + * but I'd also expect them to finally get their act together + * and add some real memory barriers if so. + * + * Some non intel clones support out of order store. wmb() ceases to be a + * nop for these. + */ + +#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory") +#define rmb() mb() +#define wmb() mb(); + + +/* + * Talk about misusing macros.. + */ + +#define __OUT1(s,x) \ +extern void __out##s(unsigned x value, unsigned short port); \ +extern inline void __out##s(unsigned x value, unsigned short port) { + +#define __OUT2(s,s1,s2) \ +__asm__ __volatile__ ("out" #s " %" s1 "0,%" s2 "1" + +#define __OUT(s,s1,x) \ +__OUT1(s,x) __OUT2(s,s1,"w") : : "a" (value), "d" (port)); } \ +__OUT1(s##c,x) __OUT2(s,s1,"") : : "a" (value), "id" (port)); } \ +__OUT1(s##_p,x) __OUT2(s,s1,"w") : : "a" (value), "d" (port)); SLOW_DOWN_IO; } \ +__OUT1(s##c_p,x) __OUT2(s,s1,"") : : "a" (value), "id" (port)); SLOW_DOWN_IO; } + +#define __IN1(s,x) \ +extern unsigned x __in##s(unsigned short port); \ +extern inline unsigned x __in##s(unsigned short port) { unsigned x _v; + +#define __IN2(s,s1,s2) \ +__asm__ __volatile__ ("in" #s " %" s2 "1,%" s1 "0" + +#define __IN(s,s1,x,i...) \ +__IN1(s,x) __IN2(s,s1,"w") : "=a" (_v) : "d" (port) ,##i ); return _v; } \ +__IN1(s##c,x) __IN2(s,s1,"") : "=a" (_v) : "id" (port) ,##i ); return _v; } \ +__IN1(s##_p,x) __IN2(s,s1,"w") : "=a" (_v) : "d" (port) ,##i ); SLOW_DOWN_IO; return _v; } \ +__IN1(s##c_p,x) __IN2(s,s1,"") : "=a" (_v) : "id" (port) ,##i ); SLOW_DOWN_IO; return _v; } + +#define __INS(s) \ +extern void ins##s(unsigned short port, void * addr, unsigned long count); \ +extern inline void ins##s(unsigned short port, void * addr, unsigned long count) \ +{ __asm__ __volatile__ ("cld ; rep ; ins" #s \ +: "=D" (addr), "=c" (count) : "d" (port),"0" (addr),"1" (count)); } + +#define __OUTS(s) \ +extern void outs##s(unsigned short port, const void * addr, unsigned long count); \ +extern inline void outs##s(unsigned short port, const void * addr, unsigned long count) \ +{ __asm__ __volatile__ ("cld ; rep ; outs" #s \ +: "=S" (addr), "=c" (count) : "d" (port),"0" (addr),"1" (count)); } + +__IN(b,"", char) +__IN(w,"",short) +__IN(l,"", long) + +__OUT(b,"b",char) +__OUT(w,"w",short) +__OUT(l,,int) + +__INS(b) +__INS(w) +__INS(l) + +__OUTS(b) +__OUTS(w) +__OUTS(l) + +/* + * Note that due to the way __builtin_constant_p() works, you + * - can't use it inside a inline function (it will never be true) + * - you don't have to worry about side effects within the __builtin.. + */ +#define outb(val,port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __outbc((val),(port)) : \ + __outb((val),(port))) + +#define inb(port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __inbc(port) : \ + __inb(port)) + +#define outb_p(val,port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __outbc_p((val),(port)) : \ + __outb_p((val),(port))) + +#define inb_p(port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __inbc_p(port) : \ + __inb_p(port)) + +#define outw(val,port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __outwc((val),(port)) : \ + __outw((val),(port))) + +#define inw(port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __inwc(port) : \ + __inw(port)) + +#define outw_p(val,port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __outwc_p((val),(port)) : \ + __outw_p((val),(port))) + +#define inw_p(port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __inwc_p(port) : \ + __inw_p(port)) + +#define outl(val,port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __outlc((val),(port)) : \ + __outl((val),(port))) + +#define inl(port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __inlc(port) : \ + __inl(port)) + +#define outl_p(val,port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __outlc_p((val),(port)) : \ + __outl_p((val),(port))) + +#define inl_p(port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __inlc_p(port) : \ + __inl_p(port)) + +#endif /* ETHERBOOT_IO_H */ diff --git a/gpxe/src/arch/i386/include/kir.h b/gpxe/src/arch/i386/include/kir.h new file mode 100644 index 00000000..84633d26 --- /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 00000000..5f67a56d --- /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( variable ) variable +#define __from_text16( variable ) variable + +/* 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 00000000..32dceed6 --- /dev/null +++ b/gpxe/src/arch/i386/include/librm.h @@ -0,0 +1,289 @@ +#ifndef LIBRM_H +#define LIBRM_H + +/* Drag in protected-mode segment selector values */ +#include "virtaddr.h" +#include "realmode.h" + +#ifndef ASSEMBLY + +#include "stddef.h" +#include "string.h" + +/* + * Data structures and type definitions + * + */ + +/* 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( variable ) \ + ( * ( ( typeof ( variable ) * ) \ + ( ( ( void * ) &(variable) ) - ( ( void * ) data16 ) ) ) ) + +#define __from_text16( variable ) \ + ( * ( ( typeof ( variable ) * ) \ + ( ( ( void * ) &(variable) ) - ( ( void * ) text16 ) ) ) ) + +/* Variables in librm.S, present in the normal data segment */ +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 ); + +/* + * librm_mgmt: functions for manipulating base memory and executing + * real-mode code. + * + * Full API documentation for these functions is in realmode.h. + * + */ + +/* Macro for obtaining a physical address from a segment:offset pair. */ +#define VIRTUAL(x,y) ( phys_to_virt ( ( ( x ) << 4 ) + ( y ) ) ) + +/* Copy to/from base memory */ +static inline __attribute__ (( always_inline )) void +copy_to_real_librm ( unsigned int dest_seg, unsigned int dest_off, + void *src, size_t n ) { + memcpy ( VIRTUAL ( dest_seg, dest_off ), src, n ); +} +static inline __attribute__ (( always_inline )) void +copy_from_real_librm ( void *dest, unsigned int src_seg, + unsigned int src_off, size_t n ) { + memcpy ( dest, VIRTUAL ( src_seg, src_off ), n ); +} +#define put_real_librm( var, dest_seg, dest_off ) \ + do { \ + * ( ( typeof(var) * ) VIRTUAL ( dest_seg, dest_off ) ) = var; \ + } while ( 0 ) +#define get_real_librm( var, src_seg, src_off ) \ + do { \ + var = * ( ( typeof(var) * ) VIRTUAL ( src_seg, src_off ) ); \ + } while ( 0 ) +#define copy_to_real copy_to_real_librm +#define copy_from_real copy_from_real_librm +#define put_real put_real_librm +#define get_real get_real_librm + +/** + * A pointer to a user buffer + * + * Even though we could just use a void *, we use an intptr_t so that + * attempts to use normal pointers show up as compiler warnings. Such + * code is actually valid for librm, but not for libkir (i.e. under + * KEEP_IT_REAL), so it's good to have the warnings even under librm. + */ +typedef intptr_t userptr_t; + +/** + * Add offset to user pointer + * + * @v ptr User pointer + * @v offset Offset + * @ret new_ptr New pointer value + */ +static inline __attribute__ (( always_inline )) userptr_t +userptr_add ( userptr_t ptr, off_t offset ) { + return ( ptr + offset ); +} + +/** + * 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 ) { + memcpy ( ( ( void * ) buffer + 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 ) { + memcpy ( dest, ( ( void * ) buffer + offset ), len ); +} + +/** + * Copy data between user buffers + * + * @v dest Destination user buffer + * @v dest_off Offset within destination buffer + * @v src Source user buffer + * @v src_off Offset within source buffer + * @v len Length + */ +static inline __attribute__ (( always_inline )) void +memcpy_user ( userptr_t dest, off_t dest_off, userptr_t src, off_t src_off, + size_t len ) { + memcpy ( ( ( void * ) dest + dest_off ), ( ( void * ) src + src_off ), + len ); +} + +/** + * Copy data between user buffers, allowing for overlap + * + * @v dest Destination user buffer + * @v dest_off Offset within destination buffer + * @v src Source user buffer + * @v src_off Offset within source buffer + * @v len Length + */ +static inline __attribute__ (( always_inline )) void +memmove_user ( userptr_t dest, off_t dest_off, userptr_t src, off_t src_off, + size_t len ) { + memmove ( ( ( void * ) dest + dest_off ), ( ( void * ) src + src_off ), + len ); +} + +/** + * Fill user buffer with a constant byte + * + * @v buffer User buffer + * @v offset Offset within buffer + * @v c Constant byte with which to fill + * @v len Length + */ +static inline __attribute__ (( always_inline )) void +memset_user ( userptr_t buffer, off_t offset, int c, size_t len ) { + memset ( ( ( void * ) buffer + offset ), c, len ); +} + +/** + * Find length of NUL-terminated string in user buffer + * + * @v buffer User buffer + * @v offset Offset within buffer + * @ret len Length of string (excluding NUL) + */ +static inline __attribute__ (( always_inline )) size_t +strlen_user ( userptr_t buffer, off_t offset ) { + return strlen ( ( void * ) buffer + 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 ( ( intptr_t ) virtual ); +} + +/** + * 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 virt_to_user ( VIRTUAL ( segment, offset ) ); +} + +/** + * Convert physical address to user buffer + * + * @v physical Physical address + * @ret buffer User buffer + */ +static inline __attribute__ (( always_inline )) userptr_t +phys_to_user ( physaddr_t physical ) { + return virt_to_user ( phys_to_virt ( physical ) ); +} + +/** + * Convert user buffer to physical address + * + * @v buffer User buffer + * @v offset Offset within user buffer + * @ret physical Physical address + */ +static inline __attribute__ (( always_inline )) physaddr_t +user_to_phys ( userptr_t buffer, off_t offset ) { + return virt_to_phys ( ( void * ) buffer + offset ); +} + +/* 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 00000000..f13db267 --- /dev/null +++ b/gpxe/src/arch/i386/include/limits.h @@ -0,0 +1,59 @@ +#ifndef LIMITS_H +#define LIMITS_H 1 + +/* 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 00000000..6222fd66 --- /dev/null +++ b/gpxe/src/arch/i386/include/memsizes.h @@ -0,0 +1,17 @@ +#ifndef _MEMSIZES_H +#define _MEMSIZES_H + +#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 00000000..4ca7089b --- /dev/null +++ b/gpxe/src/arch/i386/include/multiboot.h @@ -0,0 +1,147 @@ +#ifndef _MULTIBOOT_H +#define _MULTIBOOT_H + +/** + * @file + * + * Multiboot operating systems + * + */ + +#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/pci_io.h b/gpxe/src/arch/i386/include/pci_io.h new file mode 100644 index 00000000..4888d557 --- /dev/null +++ b/gpxe/src/arch/i386/include/pci_io.h @@ -0,0 +1,35 @@ +#ifndef _PCI_IO_H +#define _PCI_IO_H + +#include <pcibios.h> +#include <pcidirect.h> + +/** @file + * + * i386 PCI configuration space access + * + * We have two methods of PCI configuration space access: the PCI BIOS + * and direct Type 1 accesses. Selecting between them is via the + * compile-time switch -DCONFIG_PCI_DIRECT. + * + */ + +#if CONFIG_PCI_DIRECT +#define pci_max_bus pcidirect_max_bus +#define pci_read_config_byte pcidirect_read_config_byte +#define pci_read_config_word pcidirect_read_config_word +#define pci_read_config_dword pcidirect_read_config_dword +#define pci_write_config_byte pcidirect_write_config_byte +#define pci_write_config_word pcidirect_write_config_word +#define pci_write_config_dword pcidirect_write_config_dword +#else /* CONFIG_PCI_DIRECT */ +#define pci_max_bus pcibios_max_bus +#define pci_read_config_byte pcibios_read_config_byte +#define pci_read_config_word pcibios_read_config_word +#define pci_read_config_dword pcibios_read_config_dword +#define pci_write_config_byte pcibios_write_config_byte +#define pci_write_config_word pcibios_write_config_word +#define pci_write_config_dword pcibios_write_config_dword +#endif /* CONFIG_PCI_DIRECT */ + +#endif /* _PCI_IO_H */ diff --git a/gpxe/src/arch/i386/include/pcibios.h b/gpxe/src/arch/i386/include/pcibios.h new file mode 100644 index 00000000..3d08d135 --- /dev/null +++ b/gpxe/src/arch/i386/include/pcibios.h @@ -0,0 +1,122 @@ +#ifndef _PCIBIOS_H +#define _PCIBIOS_H + +#include <stdint.h> + +/** @file + * + * PCI configuration space access via PCI BIOS + * + */ + +struct pci_device; + +#define PCIBIOS_INSTALLATION_CHECK 0xb1010000 +#define PCIBIOS_READ_CONFIG_BYTE 0xb1080000 +#define PCIBIOS_READ_CONFIG_WORD 0xb1090000 +#define PCIBIOS_READ_CONFIG_DWORD 0xb10a0000 +#define PCIBIOS_WRITE_CONFIG_BYTE 0xb10b0000 +#define PCIBIOS_WRITE_CONFIG_WORD 0xb10c0000 +#define PCIBIOS_WRITE_CONFIG_DWORD 0xb10d0000 + +extern int pcibios_max_bus ( void ); +extern int pcibios_read ( struct pci_device *pci, uint32_t command, + uint32_t *value ); +extern int pcibios_write ( struct pci_device *pci, uint32_t command, + uint32_t value ); + +/** + * Read byte from PCI configuration space via PCI BIOS + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcibios_read_config_byte ( struct pci_device *pci, unsigned int where, + uint8_t *value ) { + uint32_t tmp; + int rc; + + rc = pcibios_read ( pci, PCIBIOS_READ_CONFIG_BYTE | where, &tmp ); + *value = tmp; + return rc; +} + +/** + * Read word from PCI configuration space via PCI BIOS + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcibios_read_config_word ( struct pci_device *pci, unsigned int where, + uint16_t *value ) { + uint32_t tmp; + int rc; + + rc = pcibios_read ( pci, PCIBIOS_READ_CONFIG_WORD | where, &tmp ); + *value = tmp; + return rc; +} + +/** + * Read dword from PCI configuration space via PCI BIOS + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcibios_read_config_dword ( struct pci_device *pci, unsigned int where, + uint32_t *value ) { + return pcibios_read ( pci, PCIBIOS_READ_CONFIG_DWORD | where, value ); +} + +/** + * Write byte to PCI configuration space via PCI BIOS + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcibios_write_config_byte ( struct pci_device *pci, unsigned int where, + uint8_t value ) { + return pcibios_write ( pci, PCIBIOS_WRITE_CONFIG_BYTE | where, value ); +} + +/** + * Write word to PCI configuration space via PCI BIOS + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcibios_write_config_word ( struct pci_device *pci, unsigned int where, + uint16_t value ) { + return pcibios_write ( pci, PCIBIOS_WRITE_CONFIG_WORD | where, value ); +} + +/** + * Write dword to PCI configuration space via PCI BIOS + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcibios_write_config_dword ( struct pci_device *pci, unsigned int where, + uint32_t value ) { + return pcibios_write ( pci, PCIBIOS_WRITE_CONFIG_DWORD | where, value); +} + +#endif /* _PCIBIOS_H */ diff --git a/gpxe/src/arch/i386/include/pcidirect.h b/gpxe/src/arch/i386/include/pcidirect.h new file mode 100644 index 00000000..4e2e9d12 --- /dev/null +++ b/gpxe/src/arch/i386/include/pcidirect.h @@ -0,0 +1,126 @@ +#ifndef _PCIDIRECT_H +#define _PCIDIRECT_H + +#include <stdint.h> +#include <io.h> + +/** @file + * + * PCI configuration space access via Type 1 accesses + * + */ + +#define PCIDIRECT_CONFIG_ADDRESS 0xcf8 +#define PCIDIRECT_CONFIG_DATA 0xcfc + +struct pci_device; + +extern void pcidirect_prepare ( struct pci_device *pci, int where ); + +/** + * Determine maximum PCI bus number within system + * + * @ret max_bus Maximum bus number + */ +static inline int pcidirect_max_bus ( void ) { + /* No way to work this out via Type 1 accesses */ + return 0xff; +} + +/** + * Read byte from PCI configuration space via Type 1 access + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcidirect_read_config_byte ( struct pci_device *pci, unsigned int where, + uint8_t *value ) { + pcidirect_prepare ( pci, where ); + *value = inb ( PCIDIRECT_CONFIG_DATA + ( where & 3 ) ); + return 0; +} + +/** + * Read word from PCI configuration space via Type 1 access + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcidirect_read_config_word ( struct pci_device *pci, unsigned int where, + uint16_t *value ) { + pcidirect_prepare ( pci, where ); + *value = inw ( PCIDIRECT_CONFIG_DATA + ( where & 2 ) ); + return 0; +} + +/** + * Read dword from PCI configuration space via Type 1 access + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcidirect_read_config_dword ( struct pci_device *pci, unsigned int where, + uint32_t *value ) { + pcidirect_prepare ( pci, where ); + *value = inl ( PCIDIRECT_CONFIG_DATA ); + return 0; +} + +/** + * Write byte to PCI configuration space via Type 1 access + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcidirect_write_config_byte ( struct pci_device *pci, unsigned int where, + uint8_t value ) { + pcidirect_prepare ( pci, where ); + outb ( value, PCIDIRECT_CONFIG_DATA + ( where & 3 ) ); + return 0; +} + +/** + * Write word to PCI configuration space via Type 1 access + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcidirect_write_config_word ( struct pci_device *pci, unsigned int where, + uint16_t value ) { + pcidirect_prepare ( pci, where ); + outw ( value, PCIDIRECT_CONFIG_DATA + ( where & 2 ) ); + return 0; +} + +/** + * Write dword to PCI configuration space via Type 1 access + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcidirect_write_config_dword ( struct pci_device *pci, unsigned int where, + uint32_t value ) { + pcidirect_prepare ( pci, where ); + outl ( value, PCIDIRECT_CONFIG_DATA ); + return 0; +} + +#endif /* _PCIDIRECT_H */ diff --git a/gpxe/src/arch/i386/include/pic8259.h b/gpxe/src/arch/i386/include/pic8259.h new file mode 100644 index 00000000..0c501a9c --- /dev/null +++ b/gpxe/src/arch/i386/include/pic8259.h @@ -0,0 +1,69 @@ +/* + * Basic support for controlling the 8259 Programmable Interrupt Controllers. + * + * Initially written by Michael Brown (mcb30). + */ + +#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 00000000..ab31c699 --- /dev/null +++ b/gpxe/src/arch/i386/include/pnpbios.h @@ -0,0 +1,15 @@ +#ifndef _PNPBIOS_H +#define _PNPBIOS_H + +/** @file + * + * PnP BIOS + * + */ + +/* 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_addr.h b/gpxe/src/arch/i386/include/pxe_addr.h new file mode 100644 index 00000000..954551e8 --- /dev/null +++ b/gpxe/src/arch/i386/include/pxe_addr.h @@ -0,0 +1,17 @@ +/* + * Architecture-specific portion of pxe.h for Etherboot + * + * This file has to define the types SEGOFF16_t, SEGDESC_t and + * SEGSEL_t for use in other PXE structures. See pxe.h for details. + */ + +#ifndef PXE_ADDR_H +#define PXE_ADDR_H + +#define IS_NULL_SEGOFF16(x) ( ( (x).segment == 0 ) && ( (x).offset == 0 ) ) +#define SEGOFF16_TO_PTR(x) ( VIRTUAL( (x).segment, (x).offset ) ) +#define PTR_TO_SEGOFF16(ptr,segoff16) \ + (segoff16).segment = SEGMENT(ptr); \ + (segoff16).offset = OFFSET(ptr); + +#endif /* PXE_ADDR_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 00000000..dc585310 --- /dev/null +++ b/gpxe/src/arch/i386/include/pxe_call.h @@ -0,0 +1,34 @@ +#ifndef _PXE_CALL_H +#define _PXE_CALL_H + +/** @file + * + * PXE API entry point + */ + +#include <pxe_api.h> +#include <realmode.h> + +/** 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_hook_int1a ( void ); +extern int pxe_unhook_int1a ( void ); +extern void pxe_init_structures ( void ); +extern int pxe_start_nbp ( void ); + +#endif /* _PXE_CALL_H */ diff --git a/gpxe/src/arch/i386/include/realmode.h b/gpxe/src/arch/i386/include/realmode.h new file mode 100644 index 00000000..5d3ddf50 --- /dev/null +++ b/gpxe/src/arch/i386/include/realmode.h @@ -0,0 +1,128 @@ +#ifndef REALMODE_H +#define REALMODE_H + +#ifndef ASSEMBLY + +#include "stdint.h" +#include "registers.h" +#include "io.h" + +/* + * Data structures and type definitions + * + */ + +/* Segment:offset structure. Note that the order within the structure + * is offset:segment. + */ +struct segoff { + uint16_t offset; + uint16_t segment; +} __attribute__ (( packed )); + +typedef struct segoff segoff_t; + +/* Macro hackery needed to stringify bits of inline assembly */ +#define RM_XSTR(x) #x +#define RM_STR(x) RM_XSTR(x) + +/* Drag in the selected real-mode transition library header */ +#ifdef KEEP_IT_REAL +#include "libkir.h" +#else +#include "librm.h" +#endif + +/* + * The API to some functions is identical between librm and libkir, so + * they are documented here, even though the prototypes are in librm.h + * and libkir.h. + * + */ + +/* + * 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. + */ + +/* + * void copy_to_real ( uint16_t dest_seg, uint16_t dest_off, + * void *src, size_t n ) + * void copy_from_real ( void *dest, uint16_t src_seg, uint16_t src_off, + * size_t n ) + * + * These functions can be used to copy data to and from arbitrary + * locations in base memory. + */ + +/* + * put_real ( variable, uint16_t dest_seg, uint16_t dest_off ) + * get_real ( variable, uint16_t src_seg, uint16_t src_off ) + * + * These macros can be used to read or write single variables to and + * from arbitrary locations in base memory. "variable" must be a + * variable of either 1, 2 or 4 bytes in length. + */ + +/* + * 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 /* ASSEMBLY */ + +#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 00000000..2b9b2b43 --- /dev/null +++ b/gpxe/src/arch/i386/include/registers.h @@ -0,0 +1,187 @@ +#ifndef REGISTERS_H +#define REGISTERS_H + +/** @file + * + * i386 registers. + * + * This file defines data structures that allow easy access to i386 + * register dumps. + * + */ + +#include "compiler.h" /* for doxygen */ +#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 ) + +#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 00000000..ed2be270 --- /dev/null +++ b/gpxe/src/arch/i386/include/setjmp.h @@ -0,0 +1,12 @@ +#ifndef ETHERBOOT_SETJMP_H +#define ETHERBOOT_SETJMP_H + + +/* Define a type for use by setjmp and longjmp */ +#define JBLEN 6 +typedef unsigned long jmp_buf[JBLEN]; + +extern int setjmp (jmp_buf env); +extern void longjmp (jmp_buf env, int val); + +#endif /* ETHERBOOT_SETJMP_H */ diff --git a/gpxe/src/arch/i386/include/smbios.h b/gpxe/src/arch/i386/include/smbios.h new file mode 100644 index 00000000..821eda17 --- /dev/null +++ b/gpxe/src/arch/i386/include/smbios.h @@ -0,0 +1,51 @@ +#ifndef _SMBIOS_H +#define _SMBIOS_H + +/** @file + * + * System Management BIOS + */ + +#include <stdint.h> + +/** An SMBIOS structure header */ +struct smbios_header { + /** Type */ + uint8_t type; + /** Length */ + uint8_t length; + /** Handle */ + uint16_t handle; +} __attribute__ (( packed )); + +/** SMBIOS system information structure */ +struct smbios_system_information { + /** SMBIOS structure header */ + struct smbios_header header; + /** Manufacturer string */ + uint8_t manufacturer; + /** Product string */ + uint8_t product; + /** Version string */ + uint8_t version; + /** Serial number string */ + uint8_t serial; + /** UUID */ + uint8_t uuid[16]; + /** Wake-up type */ + uint8_t wakeup; +} __attribute__ (( packed )); + +/** SMBIOS system information structure type */ +#define SMBIOS_TYPE_SYSTEM_INFORMATION 1 + +struct smbios_strings; +extern int find_smbios_structure ( unsigned int type, + void *structure, size_t length, + struct smbios_strings *strings ); +extern int find_smbios_string ( struct smbios_strings *strings, + unsigned int index, + char *buffer, size_t length ); +extern int smbios_get_uuid ( union uuid *uuid ); + +#endif /* _SMBIOS_H */ diff --git a/gpxe/src/arch/i386/include/undi.h b/gpxe/src/arch/i386/include/undi.h new file mode 100644 index 00000000..9936e17f --- /dev/null +++ b/gpxe/src/arch/i386/include/undi.h @@ -0,0 +1,102 @@ +#ifndef _UNDI_H +#define _UNDI_H + +/** @file + * + * UNDI driver + * + */ + +#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; + /** Return stack */ + UINT16_t return_stack[3]; + /** Return type */ + UINT16_t return_type; + /** 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 + +#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 00000000..bfc11874 --- /dev/null +++ b/gpxe/src/arch/i386/include/undiload.h @@ -0,0 +1,33 @@ +#ifndef _UNDILOAD_H +#define _UNDILOAD_H + +/** @file + * + * UNDI load/unload + * + */ + +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 00000000..1a4a385e --- /dev/null +++ b/gpxe/src/arch/i386/include/undinet.h @@ -0,0 +1,15 @@ +#ifndef _UNDINET_H +#define _UNDINET_H + +/** @file + * + * UNDI network device driver + * + */ + +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 00000000..d9bc8cb9 --- /dev/null +++ b/gpxe/src/arch/i386/include/undipreload.h @@ -0,0 +1,16 @@ +#ifndef _UNDIPRELOAD_H +#define _UNDIPRELOAD_H + +/** @file + * + * Preloaded UNDI stack + * + */ + +#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 00000000..a2636007 --- /dev/null +++ b/gpxe/src/arch/i386/include/undirom.h @@ -0,0 +1,51 @@ +#ifndef _UNDIROM_H +#define _UNDIROM_H + +/** @file + * + * UNDI expansion ROMs + * + */ + +#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 00000000..01fc39d8 --- /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/include/virtaddr.h b/gpxe/src/arch/i386/include/virtaddr.h new file mode 100644 index 00000000..f2ffa2a1 --- /dev/null +++ b/gpxe/src/arch/i386/include/virtaddr.h @@ -0,0 +1,105 @@ +#ifndef VIRTADDR_H +#define VIRTADDR_H + +/* 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 + +#include "stdint.h" +#include "string.h" + +#ifndef KEEP_IT_REAL + +/* + * Without -DKEEP_IT_REAL, we are in 32-bit protected mode with a + * fixed link address but an unknown physical start address. Our GDT + * sets up code and data segments with an offset of virt_offset, so + * that link-time addresses can still work. + * + */ + +/* C-callable function prototypes */ + +extern void relocate_to ( uint32_t new_phys_addr ); + +/* Variables in virtaddr.S */ +extern unsigned long virt_offset; + +/* + * Convert between virtual and physical addresses + * + */ +static inline unsigned long virt_to_phys ( volatile const void *virt_addr ) { + return ( ( unsigned long ) virt_addr ) + virt_offset; +} + +static inline void * phys_to_virt ( unsigned long phys_addr ) { + return ( void * ) ( phys_addr - virt_offset ); +} + +static inline void copy_to_phys ( physaddr_t dest, const void *src, + size_t len ) { + memcpy ( phys_to_virt ( dest ), src, len ); +} + +static inline void copy_from_phys ( void *dest, physaddr_t src, size_t len ) { + memcpy ( dest, phys_to_virt ( src ), len ); +} + +static inline void copy_phys_to_phys ( physaddr_t dest, physaddr_t src, + size_t len ) { + memcpy ( phys_to_virt ( dest ), phys_to_virt ( src ), len ); +} + +#else /* KEEP_IT_REAL */ + +/* + * With -DKEEP_IT_REAL, we are in 16-bit real mode with fixed link + * addresses and a segmented memory model. We have separate code and + * data segments. + * + * Because we may be called in 16-bit protected mode (damn PXE spec), + * we cannot simply assume that physical = segment * 16 + offset. + * Instead, we have to look up the physical start address of the + * segment in the !PXE structure. We have to assume that + * virt_to_phys() is called only on pointers within the data segment, + * because nothing passes segment information to us. + * + * We don't implement phys_to_virt at all, because there will be many + * addresses that simply cannot be reached via a virtual address when + * the virtual address space is limited to 64kB! + */ + +static inline unsigned long virt_to_phys ( volatile const void *virt_addr ) { + /* Cheat: just for now, do the segment*16+offset calculation */ + uint16_t ds; + + __asm__ ( "movw %%ds, %%ax" : "=a" ( ds ) : ); + return ( 16 * ds + ( ( unsigned long ) virt_addr ) ); +} + +/* Define it as a deprecated function so that we get compile-time + * warnings, rather than just the link-time errors. + */ +extern void * phys_to_virt ( unsigned long phys_addr ) + __attribute__ ((deprecated)); + +#endif /* KEEP_IT_REAL */ + +#endif /* ASSEMBLY */ + +#endif /* VIRTADDR_H */ 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 00000000..8ef2d7ab --- /dev/null +++ b/gpxe/src/arch/i386/interface/pcbios/biosint.c @@ -0,0 +1,101 @@ +#include <errno.h> +#include <realmode.h> +#include <biosint.h> + +/** + * @file BIOS interrupts + * + */ + +/** + * 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). + */ +int hooked_bios_interrupts = 0; + +/** + * 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/int13.c b/gpxe/src/arch/i386/interface/pcbios/int13.c new file mode 100644 index 00000000..7e09fb5f --- /dev/null +++ b/gpxe/src/arch/i386/interface/pcbios/int13.c @@ -0,0 +1,653 @@ +/* + * 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. + */ + +#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 ); + +/** + * 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; + + /* 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 ( io ( blockdev, lba, count, buffer ) != 0 ) + 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->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->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 ) { + DBG ( "Get disk type\n" ); + ix86->regs.cx = ( drive->cylinders >> 16 ); + ix86->regs.dx = ( drive->cylinders & 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; + + /* 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 ( io ( blockdev, lba, count, buffer ) != 0 ) + 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->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->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, ¶ms, + sizeof ( params ) ); + return 0; +} + +/** + * INT 13 handler + * + */ +static __cdecl void int13 ( struct i386_all_regs *ix86 ) { + int command = ix86->regs.ah; + unsigned int bios_drive = ix86->regs.dl; + unsigned int original_bios_drive = bios_drive; + struct int13_drive *drive; + int status; + + list_for_each_entry ( drive, &drives, list ) { + if ( drive->drive > bios_drive ) + continue; + if ( drive->drive < bios_drive ) { + original_bios_drive--; + 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 ( "INT13 failed with 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; + } + + /* Remap BIOS drive */ + if ( bios_drive != original_bios_drive ) { + DBG ( "INT 13,%04x (%02x) remapped to (%02x)\n", + ix86->regs.ax, bios_drive, original_bios_drive ); + } + ix86->regs.dl = original_bios_drive; +} + +/** + * 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->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 drive number if none specified, update BIOS drive count */ + get_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES ); + if ( ( drive->drive & 0xff ) == 0xff ) + drive->drive = num_drives; + drive->drive |= 0x80; + num_drives++; + if ( num_drives <= ( drive->drive & 0x7f ) ) + num_drives = ( ( drive->drive & 0x7f ) + 1 ); + put_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES ); + + DBG ( "Registered INT13 drive %02x with C/H/S geometry %d/%d/%d\n", + drive->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 ); +} + +/** + * 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\n", drive ); + return rc; + } + + return -ECANCELED; /* -EIMPOSSIBLE */ +} 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 00000000..5b307d40 --- /dev/null +++ b/gpxe/src/arch/i386/interface/pxe/pxe_call.c @@ -0,0 +1,450 @@ +/* + * 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. + */ + +#include <gpxe/uaccess.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 ); + +/** 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 * ); +}; + +/** + * 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 + */ +__cdecl 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; + 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 PXE loader call + * + * @v es:di Address of PXE parameter block + * @ret ax PXE exit code + */ +__cdecl 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 ( ¶ms, uparams, 0, sizeof ( params ) ); + + /* Set default status in case child routine fails to do so */ + params.Status = PXENV_STATUS_FAILURE; + + /* Call UNDI loader */ + ret = undi_loader ( ¶ms ); + + /* Copy modified parameter block back to caller and return */ + copy_to_user ( uparams, 0, ¶ms, sizeof ( params ) ); + ix86->regs.ax = ret; +} + +/** + * Hook INT 1A for PXE + * + */ +void pxe_hook_int1a ( void ) { + hook_bios_interrupt ( 0x1a, ( unsigned int ) pxe_int_1a, + &pxe_int_1a_vector ); +} + +/** + * Unhook INT 1A for PXE + * + * @ret rc Return status code + */ +int pxe_unhook_int1a ( void ) { + return unhook_bios_interrupt ( 0x1a, ( unsigned int ) pxe_int_1a, + &pxe_int_1a_vector ); +} + +/** + * 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 + * + */ +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 ) ); +} + +/** + * Start PXE NBP at 0000:7c00 + * + * @ret rc Return status code + */ +int pxe_start_nbp ( void ) { + int discard_b, discard_c; + uint16_t rc; + + /* Far call to PXE NBP */ + __asm__ __volatile__ ( REAL_CODE ( "pushw %%cx\n\t" + "pushw %%ax\n\t" + "movw %%cx, %%es\n\t" + "lcall $0, $0x7c00\n\t" + "addw $4, %%sp\n\t" ) + : "=a" ( rc ), "=b" ( discard_b ), + "=c" ( discard_c ) + : "a" ( & __from_text16 ( ppxe ) ), + "b" ( & __from_text16 ( pxenv ) ), + "c" ( rm_cs ) + : "edx", "esi", "edi", "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 00000000..204894ef --- /dev/null +++ b/gpxe/src/arch/i386/interface/pxe/pxe_entry.S @@ -0,0 +1,195 @@ +/* + * 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. + * + */ + + .arch i386 + .section ".text16", "awx", @progbits + .section ".text16.data", "aw", @progbits + .section ".data16", "aw", @progbits + +/**************************************************************************** + * !PXE structure + **************************************************************************** + */ + .section ".text16.data" + .globl ppxe + .align 16 +ppxe: + .ascii "!PXE" /* Signature */ + .byte pxe_length /* StructLength */ + .byte 0 /* StructCksum */ + .byte 0 /* StructRev */ + .byte 0 /* reserved_1 */ + .word 0, 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_size /* Stack */ + .word 0, 0, 0, _data16_size /* UNDIData */ + .word 0, 0, 0, _text16_size /* UNDICode */ + .word 0, 0, 0, _text16_size /* 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 + +/**************************************************************************** + * PXENV+ structure + **************************************************************************** + */ + .section ".text16.data" + .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_size /* StackSize */ + .word 0 /* BC_CodeSeg */ + .word 0 /* BC_CodeSize */ + .word 0 /* BC_DataSeg */ + .word 0 /* BC_DataSize */ + .word 0 /* UNDIDataSeg */ + .word _data16_size /* UNDIDataSize */ + .word 0 /* UNDICodeSeg */ + .word _text16_size /* 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 + **************************************************************************** + */ + .section ".text16" + .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" + .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 + * CF cleared + * Corrupts: + * none + **************************************************************************** + */ + .section ".text16" + .code16 + .globl pxe_int_1a +pxe_int_1a: + pushfw + cmpw $0x5650, %ax + jne 1f + /* INT 1A,5650 - PXE installation check */ + pushw %cs + popw %es + movw $pxenv, %bx + movw $0x564e, %ax + popfw + clc + lret $2 +1: /* INT 1A,other - pass through */ + popfw + ljmp *%cs:pxe_int_1a_vector + + .section ".text16.data" + .globl pxe_int_1a_vector +pxe_int_1a_vector: .long 0 diff --git a/gpxe/src/arch/i386/kir-Makefile b/gpxe/src/arch/i386/kir-Makefile new file mode 100644 index 00000000..bbfc1a3a --- /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/bImageprefix.S b/gpxe/src/arch/i386/prefix/bImageprefix.S new file mode 100644 index 00000000..30020f73 --- /dev/null +++ b/gpxe/src/arch/i386/prefix/bImageprefix.S @@ -0,0 +1,611 @@ +/* + Copyright (C) 2000, Entity Cyber, Inc. + + Authors: Gary Byers (gb@thinguin.org) + Marty Connor (mdc@thinguin.org) + Eric Biederman (ebiederman@lnxi.com) + + This code also derives a lot from arch/i386/boot/setup.S in + the linux kernel. + + 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 an Etherboot ROM image in order to allow LILO 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 LILO 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.) LILO's only interested in knowing the + number of setup sectors advertised in the table (at offset 497 in + the first sector.) + + Etherboot doesn't require much in the way of setup code. + Historically, the Linux kernel required at least 4 sectors of + setup code. Current versions of LILO look at the byte at + offset 497 in the first sector to indicate how many sectors + of setup code are contained in the image. + + The setup code that is present here does a lot of things + exactly the way the linux kernel does them instead of in + ways more typical of etherboot. Generally this is so + the code can be strongly compatible with the linux kernel. + In addition the general etherboot technique of enabling the a20 + after we switch into protected mode does not work if etherboot + is being loaded at 1MB. +*/ + + .equ CR0_PE,1 + +#ifdef GAS291 +#define DATA32 data32; +#define ADDR32 addr32; +#define LJMPI(x) ljmp x +#else +#define DATA32 data32 +#define ADDR32 addr32 +/* newer GAS295 require #define LJMPI(x) ljmp *x */ +#define LJMPI(x) ljmp x +#endif + +/* Simple and small GDT entries for booting only */ +#define GDT_ENTRY_BOOT_CS 2 +#define GDT_ENTRY_BOOT_DS (GDT_ENTRY_BOOT_CS + 1) +#define __BOOT_CS (GDT_ENTRY_BOOT_CS * 8) +#define __BOOT_DS (GDT_ENTRY_BOOT_DS * 8) + + +#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). */ + +#define DELTA_INITSEG (SETUPSEG - INITSEG) /* 0x0020 */ + +/* Signature words to ensure LILO loaded us right */ +#define SIG1 0xAA55 +#define SIG2 0x5A5A + + .text + .code16 + .arch i386 + .org 0 + .section ".prefix", "ax", @progbits +_prefix: + +/* + This is a minimal boot sector. If anyone tries to execute it (e.g., if + a .lkrn file is dd'ed to a floppy), print an error message. +*/ + +bootsector: + jmp $BOOTSEG, $go - _prefix /* reload cs:ip to match relocation addr */ +go: + 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 - _prefix, %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: + + + .org 497 +setup_sects: + .byte SETUPSECS +root_flags: + .word 0 +syssize: + .word _verbatim_size_pgh - PREFIXPGH +swap_dev: + .word 0 +ram_size: + .word 0 +vid_mode: + .word 0 +root_dev: + .word 0 +boot_flag: + .word 0xAA55 + +/* + We're now at the beginning of the second sector of the image - + where the setup code goes. + + We don't need to do too much setup for Etherboot. + + This code gets loaded at SETUPSEG:0. It wants to start + executing the Etherboot image that's loaded at SYSSEG:0 and + whose entry point is SYSSEG:0. +*/ +setup_code: + jmp trampoline +# This is the setup header, and it must start at %cs:2 (old 0x9020:2) + + .ascii "HdrS" # header signature + .word 0x0203 # header version number (>= 0x0105) + # or else old loadlin-1.5 will fail) +realmode_swtch: .word 0, 0 # default_switch, SETUPSEG +start_sys_seg: .word SYSSEG # low load segment (obsolete) + .word kernel_version - setup_code + # pointing to kernel version string + # above section of header is compatible + # with loadlin-1.5 (header v1.5). Don't + # change it. + +type_of_loader: .byte 0 # = 0, old one (LILO, Loadlin, + # Bootlin, SYSLX, bootsect...) + # See Documentation/i386/boot.txt for + # assigned ids + +# flags, unused bits must be zero (RFU) bit within loadflags +loadflags: +LOADED_HIGH = 1 # If set, the kernel is loaded high +CAN_USE_HEAP = 0x80 # If set, the loader also has set + # heap_end_ptr to tell how much + # space behind setup.S can be used for + # heap purposes. + # Only the loader knows what is free + .byte LOADED_HIGH + +setup_move_size: .word 0x8000 # size to move, when setup is not + # loaded at 0x90000. We will move setup + # to 0x90000 then just before jumping + # into the kernel. However, only the + # loader knows how much data behind + # us also needs to be loaded. + +code32_start: # here loaders can put a different + # start address for 32-bit code. + .long 0x100000 # 0x100000 = default for big kernel + +ramdisk_image: .long 0 # address of loaded ramdisk image + # Here the loader puts the 32-bit + # address where it loaded the image. + # This only will be read by the kernel. + +ramdisk_size: .long 0 # its size in bytes + +bootsect_kludge: + .long 0 # obsolete + +heap_end_ptr: .word 0 # (Header version 0x0201 or later) + # space from here (exclusive) down to + # end of setup code can be used by setup + # for local heap purposes. + +pad1: .word 0 +cmd_line_ptr: .long 0 # (Header version 0x0202 or later) + # If nonzero, a 32-bit pointer + # to the kernel command line. + # The command line should be + # located between the start of + # setup and the end of low + # memory (0xa0000), or it may + # get overwritten before it + # gets read. If this field is + # used, there is no longer + # anything magical about the + # 0x90000 segment; the setup + # can be located anywhere in + # low memory 0x10000 or higher. + +ramdisk_max: .long 0 # (Header version 0x0203 or later) + # The highest safe address for + # the contents of an initrd + +trampoline: call start_of_setup +trampoline_end: + .space 1024 +# End of setup header ##################################################### + +start_of_setup: +# Set %ds = %cs, we know that SETUPSEG = %cs at this point + movw %cs, %ax # aka SETUPSEG + movw %ax, %ds +# Check signature at end of setup + cmpw $SIG1, (setup_sig1 - setup_code) + jne bad_sig + + cmpw $SIG2, (setup_sig2 - setup_code) + jne bad_sig + + jmp good_sig1 + +# Routine to print asciiz string at ds:si +prtstr: + lodsb + andb %al, %al + jz fin + + call prtchr + jmp prtstr + +fin: ret + +# Part of above routine, this one just prints ascii al +prtchr: pushw %ax + pushw %cx + movw $7,%bx + movw $0x01, %cx + movb $0x0e, %ah + int $0x10 + popw %cx + popw %ax + ret + +no_sig_mess: .string "No setup signature found ..." + +good_sig1: + jmp good_sig + +# We now have to find the rest of the setup code/data +bad_sig: + movw %cs, %ax # SETUPSEG + subw $DELTA_INITSEG, %ax # INITSEG + movw %ax, %ds + xorb %bh, %bh + movb (497), %bl # get setup sect from bootsect + subw $4, %bx # LILO loads 4 sectors of setup + shlw $8, %bx # convert to words (1sect=2^8 words) + movw %bx, %cx + shrw $3, %bx # convert to segment + addw $SYSSEG, %bx + movw %bx, %cs:(start_sys_seg - setup_code) +# Move rest of setup code/data to here + movw $2048, %di # four sectors loaded by LILO + subw %si, %si + pushw %cs + popw %es + movw $SYSSEG, %ax + movw %ax, %ds + rep + movsw + movw %cs, %ax # aka SETUPSEG + movw %ax, %ds + cmpw $SIG1, (setup_sig1 - setup_code) + jne no_sig + + cmpw $SIG2, (setup_sig2 - setup_code) + jne no_sig + + jmp good_sig + +no_sig: + lea (no_sig_mess - setup_code), %si + call prtstr + +no_sig_loop: + hlt + jmp no_sig_loop + +good_sig: + cmpw $0, %cs:(realmode_swtch - setup_code) + jz rmodeswtch_normal + + lcall *%cs:(realmode_swtch - setup_code) + jmp rmodeswtch_end + +rmodeswtch_normal: + pushw %cs + call default_switch + +rmodeswtch_end: +# we get the code32 start address and modify the below 'jmpi' +# (loader may have changed it) + movl %cs:(code32_start - setup_code), %eax + movl %eax, %cs:(code32 - setup_code) + +# then we load the segment descriptors + movw %cs, %ax # aka SETUPSEG + movw %ax, %ds + +# +# Enable A20. This is at the very best an annoying procedure. +# A20 code ported from SYSLINUX 1.52-1.63 by H. Peter Anvin. +# + +A20_TEST_LOOPS = 32 # Iterations per wait +A20_ENABLE_LOOPS = 255 # Total loops to try + +a20_try_loop: + + # First, see if we are on a system with no A20 gate. +a20_none: + call a20_test + jnz a20_done + + # Next, try the BIOS (INT 0x15, AX=0x2401) +a20_bios: + movw $0x2401, %ax + pushfl # Be paranoid about flags + int $0x15 + popfl + + call a20_test + jnz a20_done + + # Try enabling A20 through the keyboard controller +a20_kbc: + call empty_8042 + + call a20_test # Just in case the BIOS worked + jnz a20_done # but had a delayed reaction. + + movb $0xD1, %al # command write + outb %al, $0x64 + call empty_8042 + + movb $0xDF, %al # A20 on + outb %al, $0x60 + call empty_8042 + + # Wait until a20 really *is* enabled; it can take a fair amount of + # time on certain systems; Toshiba Tecras are known to have this + # problem. +a20_kbc_wait: + xorw %cx, %cx +a20_kbc_wait_loop: + call a20_test + jnz a20_done + loop a20_kbc_wait_loop + + # Final attempt: use "configuration port A" +a20_fast: + inb $0x92, %al # Configuration Port A + orb $0x02, %al # "fast A20" version + andb $0xFE, %al # don't accidentally reset + outb %al, $0x92 + + # Wait for configuration port A to take effect +a20_fast_wait: + xorw %cx, %cx +a20_fast_wait_loop: + call a20_test + jnz a20_done + loop a20_fast_wait_loop + + # A20 is still not responding. Try frobbing it again. + # + decb (a20_tries - setup_code) + jnz a20_try_loop + + movw $(a20_err_msg - setup_code), %si + call prtstr + +a20_die: + hlt + jmp a20_die + +a20_tries: + .byte A20_ENABLE_LOOPS + +a20_err_msg: + .ascii "linux: fatal error: A20 gate not responding!" + .byte 13, 10, 0 + + # If we get here, all is good +a20_done: + # Leave the idt alone + + # set up gdt + xorl %eax, %eax # Compute gdt_base + movw %ds, %ax # (Convert %ds:gdt to a linear ptr) + shll $4, %eax + addl $(bImage_gdt - setup_code), %eax + movl %eax, (bImage_gdt_48+2 - setup_code) + DATA32 lgdt %ds:(bImage_gdt_48 - setup_code) # load gdt with whatever is + # appropriate + + # Switch to protected mode + movl %cr0, %eax + orb $CR0_PE, %al + movl %eax, %cr0 + + DATA32 ljmp *%ds:(code32 - setup_code) +code32: + .long 0x100000 + .word __BOOT_CS, 0 + +# Here's a bunch of information about your current kernel.. +kernel_version: .ascii "Etherboot " + .ascii VERSION + .byte 0 + +# This is the default real mode switch routine. +# to be called just before protected mode transition +default_switch: + cli # no interrupts allowed ! + movb $0x80, %al # disable NMI for bootup + # sequence + outb %al, $0x70 + lret + +# This routine tests whether or not A20 is enabled. If so, it +# exits with zf = 0. +# +# The memory address used, 0x200, is the int $0x80 vector, which +# should be safe. + +A20_TEST_ADDR = 4*0x80 + +a20_test: + pushw %cx + pushw %ax + xorw %cx, %cx + movw %cx, %fs # Low memory + decw %cx + movw %cx, %gs # High memory area + movw $A20_TEST_LOOPS, %cx + movw %fs:(A20_TEST_ADDR), %ax + pushw %ax +a20_test_wait: + incw %ax + movw %ax, %fs:(A20_TEST_ADDR) + call delay # Serialize and make delay constant + cmpw %gs:(A20_TEST_ADDR+0x10), %ax + loope a20_test_wait + + popw %fs:(A20_TEST_ADDR) + popw %ax + popw %cx + ret + + +# This routine checks that the keyboard command queue is empty +# (after emptying the output buffers) +# +# Some machines have delusions that the keyboard buffer is always full +# with no keyboard attached... +# +# If there is no keyboard controller, we will usually get 0xff +# to all the reads. With each IO taking a microsecond and +# a timeout of 100,000 iterations, this can take about half a +# second ("delay" == outb to port 0x80). That should be ok, +# and should also be plenty of time for a real keyboard controller +# to empty. +# + +empty_8042: + pushl %ecx + movl $100000, %ecx + +empty_8042_loop: + decl %ecx + jz empty_8042_end_loop + + call delay + + inb $0x64, %al # 8042 status port + testb $1, %al # output buffer? + jz no_output + + call delay + inb $0x60, %al # read it + jmp empty_8042_loop + +no_output: + testb $2, %al # is input buffer full? + jnz empty_8042_loop # yes - loop +empty_8042_end_loop: + popl %ecx + + +# Delay is needed after doing I/O +delay: + outb %al,$0x80 + ret + +# Descriptor tables +# +# NOTE: The intel manual says gdt should be sixteen bytes aligned for +# efficiency reasons. However, there are machines which are known not +# to boot with misaligned GDTs, so alter this at your peril! If you alter +# GDT_ENTRY_BOOT_CS (in asm/segment.h) remember to leave at least two +# empty GDT entries (one for NULL and one reserved). +# +# NOTE: On some CPUs, the GDT must be 8 byte aligned. This is +# true for the Voyager Quad CPU card which will not boot without +# This directive. 16 byte aligment is recommended by intel. +# + .balign 16 +bImage_gdt: + .fill GDT_ENTRY_BOOT_CS,8,0 + + .word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb) + .word 0 # base address = 0 + .word 0x9A00 # code read/exec + .word 0x00CF # granularity = 4096, 386 + # (+5th nibble of limit) + + .word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb) + .word 0 # base address = 0 + .word 0x9200 # data read/write + .word 0x00CF # granularity = 4096, 386 + # (+5th nibble of limit) +bImage_gdt_end: + .balign 4 + + .word 0 # alignment byte +bImage_idt_48: + .word 0 # idt limit = 0 + .long 0 # idt base = 0L + + .word 0 # alignment byte +bImage_gdt_48: + .word bImage_gdt_end - bImage_gdt - 1 # gdt limit + .long bImage_gdt_48 - setup_code # gdt base (filled in later) + + .section ".text16", "ax", @progbits +prefix_exit: + int $0x19 /* should try to boot machine */ +prefix_exit_end: + .previous + + + .org (PREFIXSIZE - 4) +# Setup signature -- must be last +setup_sig1: .word SIG1 +setup_sig2: .word SIG2 + /* Etherboot expects to be contiguous in memory once loaded. + * The linux bImage protocol does not do this, but since we + * don't need any information that's left in the prefix, it + * doesn't matter: we just have to ensure that we make it to _start + * + * protected_start will live at 0x100000 and it will be the + * the first code called as we enter protected mode. + */ + .code32 +protected_start: + /* Load segment registers */ + movw $__BOOT_DS, %ax + movw %ax, %ss + movw %ax, %ds + movw %ax, %es + movw %ax, %fs + movw %ax, %gs + + /* Use the internal etherboot stack */ + movl $(_prefix_stack_end - protected_start + 0x100000), %esp + + pushl $0 /* No parameters to preserve for exit path */ + pushl $0 /* Use prefix exit path mechanism */ + + jmp _start +/* + That's about it. +*/ diff --git a/gpxe/src/arch/i386/prefix/bootpart.S b/gpxe/src/arch/i386/prefix/bootpart.S new file mode 100644 index 00000000..d60fe9bc --- /dev/null +++ b/gpxe/src/arch/i386/prefix/bootpart.S @@ -0,0 +1,216 @@ +#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/comprefix.S b/gpxe/src/arch/i386/prefix/comprefix.S new file mode 100644 index 00000000..2cbbca50 --- /dev/null +++ b/gpxe/src/arch/i386/prefix/comprefix.S @@ -0,0 +1,46 @@ +/* We need a real mode stack that won't be stomped on by Etherboot + which starts at 0x20000. Choose something that's sufficiently high, + but not in DOC territory. Note that we couldn't do this in a real + .com program since stack variables are in the same segment as the + code and data, but this isn't really a .com program, it just looks + like one to make DOS load it into memory. It still has the 64kB + limitation of .com files though. */ +#define STACK_SEG 0x7000 +#define STACK_SIZE 0x4000 + + .text + .code16 + .arch i386 + .section ".prefix", "ax", @progbits + +/* Cheat a little with the relocations: .COM files are loaded at 0x100 */ +_prefix: + /* Set up temporary stack */ + movw $STACK_SEG, %ax + movw %ax, %ss + movw $STACK_SIZE, %sp + + pushl $0 /* No parameters to preserve for exit path */ + pushw $0 /* Dummy return address - use prefix_exit */ + + /* Calculate segment address of image start */ + pushw %cs + popw %ax + addw $(0x100/16), %ax + pushw %ax + pushw $_start + /* Calculated lcall to _start with %cs:0000 = image start */ + lret + + .section ".text16", "ax", @progbits +prefix_exit: + movw $0x4c00,%ax /* return to DOS */ + int $0x21 /* reach this on Quit */ +prefix_exit_end: + .previous + +/* The body of etherboot is attached here at build time. + * Force 16 byte alignment + */ + .align 16,0 +_body: diff --git a/gpxe/src/arch/i386/prefix/dskprefix.S b/gpxe/src/arch/i386/prefix/dskprefix.S new file mode 100644 index 00000000..cdc43b37 --- /dev/null +++ b/gpxe/src/arch/i386/prefix/dskprefix.S @@ -0,0 +1,375 @@ +/* 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. + */ + +.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 + _load_size_pgh + .section ".zinfo.fixup", "a" /* Compressor fixup information */ + .ascii "SUBW" + .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: + 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 %eax /* discard */ + + /* Boot next device */ + int $0x18 + diff --git a/gpxe/src/arch/i386/prefix/elf_dprefix.S b/gpxe/src/arch/i386/prefix/elf_dprefix.S new file mode 100644 index 00000000..0eac77e0 --- /dev/null +++ b/gpxe/src/arch/i386/prefix/elf_dprefix.S @@ -0,0 +1,94 @@ +#include "elf.h" + + .arch i386 + .section ".prefix", "a", @progbits + +#define LOAD_ADDR 0x10000 + + /* ELF Header */ + .globl elf_header +elf_header: +e_ident: .byte 0x7f, 'E', 'L', 'F', 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 +e_type: .short ET_DYN +e_machine: .short EM_386 +e_version: .long 1 +e_entry: .long LOAD_ADDR + _start - elf_header +e_phoff: .long elf_program_header - elf_header +e_shoff: .long 0 +e_flags: .long 0 +e_ehsize: .short elf_header_end - elf_header +e_phentsize: .short ELF32_PHDR_SIZE +e_phnum: .short (elf_program_header_end - elf_program_header)/ELF32_PHDR_SIZE +e_shentsize: .short 0 +e_shnum: .short 0 +e_shstrndx: .short 0 +elf_header_end: + +elf_program_header: +phdr1_p_type: .long PT_NOTE +phdr1_p_offset: .long elf_note - elf_header +phdr1_p_vaddr: .long elf_note +phdr1_p_paddr: .long elf_note +phdr1_p_filesz: .long elf_note_end - elf_note +phdr1_p_memsz: .long elf_note_end - elf_note +phdr1_p_flags: .long PF_R | PF_W | PF_X +phdr1_p_align: .long 0 + +/* The decompressor */ +phdr2_p_type: .long PT_LOAD +phdr2_p_offset: .long 0 +phdr2_p_vaddr: .long elf_header +phdr2_p_paddr: .long LOAD_ADDR +phdr2_p_filesz: .long _verbatim_size +phdr2_p_memsz: .long _image_size +phdr2_p_flags: .long PF_R | PF_W | PF_X +phdr2_p_align: .long 16 + +elf_program_header_end: + + .globl elf_note +elf_note: + .balign 4 + .int 2f - 1f + .int 4f - 3f + .int EIN_PROGRAM_NAME +1: .asciz "ELFBoot" +2: + .balign 4 +3: + .asciz "Etherboot" +4: + + + .balign 4 + .int 2f - 1f + .int 4f - 3f + .int EIN_PROGRAM_VERSION +1: .asciz "ELFBoot" +2: + .balign 4 +3: + .asciz VERSION +4: + +#if 0 + .balign 4 + .int 2f - 1f + .int 4f - 3f + .int EIN_PROGRAM_CHECKSUM +1: .asciz "ELFBoot" +2: + .balign 4 +3: + .word 0 +4: +#endif + .balign 4 +elf_note_end: + + /* Dummy routines to satisfy the build */ + .section ".text16", "ax", @progbits +prefix_exit: + +prefix_exit_end: + .previous diff --git a/gpxe/src/arch/i386/prefix/elfprefix.S b/gpxe/src/arch/i386/prefix/elfprefix.S new file mode 100644 index 00000000..d712753a --- /dev/null +++ b/gpxe/src/arch/i386/prefix/elfprefix.S @@ -0,0 +1,94 @@ +#include "elf.h" + + .arch i386 + .section ".prefix", "a", @progbits + +#define LOAD_ADDR 0x10000 + + /* ELF Header */ + .globl elf_header +elf_header: +e_ident: .byte 0x7f, 'E', 'L', 'F', 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 +e_type: .short ET_EXEC +e_machine: .short EM_386 +e_version: .long 1 +e_entry: .long LOAD_ADDR + _start - elf_header +e_phoff: .long elf_program_header - elf_header +e_shoff: .long 0 +e_flags: .long 0 +e_ehsize: .short elf_header_end - elf_header +e_phentsize: .short ELF32_PHDR_SIZE +e_phnum: .short (elf_program_header_end - elf_program_header)/ELF32_PHDR_SIZE +e_shentsize: .short 0 +e_shnum: .short 0 +e_shstrndx: .short 0 +elf_header_end: + +elf_program_header: +phdr1_p_type: .long PT_NOTE +phdr1_p_offset: .long elf_note - elf_header +phdr1_p_vaddr: .long elf_note +phdr1_p_paddr: .long elf_note +phdr1_p_filesz: .long elf_note_end - elf_note +phdr1_p_memsz: .long elf_note_end - elf_note +phdr1_p_flags: .long PF_R | PF_W | PF_X +phdr1_p_align: .long 0 + +/* The decompressor */ +phdr2_p_type: .long PT_LOAD +phdr2_p_offset: .long 0 +phdr2_p_vaddr: .long elf_header +phdr2_p_paddr: .long LOAD_ADDR +phdr2_p_filesz: .long _verbatim_size +phdr2_p_memsz: .long _image_size +phdr2_p_flags: .long PF_R | PF_W | PF_X +phdr2_p_align: .long 16 + +elf_program_header_end: + + .globl elf_note +elf_note: + .balign 4 + .int 2f - 1f + .int 4f - 3f + .int EIN_PROGRAM_NAME +1: .asciz "ELFBoot" +2: + .balign 4 +3: + .asciz "Etherboot" +4: + + + .balign 4 + .int 2f - 1f + .int 4f - 3f + .int EIN_PROGRAM_VERSION +1: .asciz "ELFBoot" +2: + .balign 4 +3: + .asciz VERSION +4: + +#if 0 + .balign 4 + .int 2f - 1f + .int 4f - 3f + .int EIN_PROGRAM_CHECKSUM +1: .asciz "ELFBoot" +2: + .balign 4 +3: + .word 0 +4: +#endif + .balign 4 +elf_note_end: + + /* Dummy routines to satisfy the build */ + .section ".text16", "ax", @progbits +prefix_exit: + +prefix_exit_end: + .previous diff --git a/gpxe/src/arch/i386/prefix/exeprefix.S b/gpxe/src/arch/i386/prefix/exeprefix.S new file mode 100755 index 00000000..f1b402b7 --- /dev/null +++ b/gpxe/src/arch/i386/prefix/exeprefix.S @@ -0,0 +1,41 @@ +/* + Prefix for .exe images + Doesn't work yet, even though it starts off the same as a .com + image as shown by DOS debug. +*/ + + .text + .code16 + .arch i386 + .section ".prefix", "ax", @progbits + +_prefix: + .byte 'M', 'Z' + .short _exe_size_tail /* tail */ + .short _exe_size_pages /* pages */ + .short 0 /* relocations */ + .short 2 /* header paras */ + .short _exe_bss_size /* min */ + .short 0xFFFF /* max paras */ + .short _exe_ss_offset /* SS */ + .short _stack_size /* SP */ + .short 0 /* checksum */ + .short 0 /* IP */ + .short 0 /* CS */ + .short 0x1C /* reloc offset */ + .short 0 /* overlay number */ + .short 0 /* fill */ + .short 0 /* fill */ + + .section ".text16", "ax", @progbits +prefix_exit: + movw $0x4c00,%ax /* return to DOS */ + int $0x21 /* reach this on Quit */ +prefix_exit_end: + .previous + +/* The body of etherboot is attached here at build time. + * Force 16 byte alignment + */ + .align 16,0 +_body: diff --git a/gpxe/src/arch/i386/prefix/hdprefix.S b/gpxe/src/arch/i386/prefix/hdprefix.S new file mode 100644 index 00000000..56fcb36d --- /dev/null +++ b/gpxe/src/arch/i386/prefix/hdprefix.S @@ -0,0 +1,103 @@ + .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 _load_size_sect + + .section ".zinfo.fixup", "a" /* Compressor fixup information */ + .ascii "SUBL" + .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: + 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 %eax /* discard */ + + /* Boot next device */ + int $0x18 diff --git a/gpxe/src/arch/i386/prefix/kpxeprefix.S b/gpxe/src/arch/i386/prefix/kpxeprefix.S new file mode 100644 index 00000000..d708604b --- /dev/null +++ b/gpxe/src/arch/i386/prefix/kpxeprefix.S @@ -0,0 +1,7 @@ +/***************************************************************************** + * PXE prefix that keep the UNDI portion of the PXE stack present + ***************************************************************************** + */ + +#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 00000000..deea5ab3 --- /dev/null +++ b/gpxe/src/arch/i386/prefix/libprefix.S @@ -0,0 +1,657 @@ +/* + * 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. + * + */ + + .arch i386 + .section ".prefix.lib", "awx", @progbits + .section ".data16", "aw", @progbits + +/** + * 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 + * Returns: + * Nothing + * Corrupts: + * %ax + ***************************************************************************** + */ + .section ".prefix.lib" + .code16 + .globl print_character +print_character: + /* Preserve registers */ + pushw %bx + pushw %bp + /* Print character */ + movw $0x0007, %bx /* page 0, attribute 7 (normal) */ + movb $0x0e, %ah /* write char, tty mode */ + cmpb $0x0a, %al /* '\n'? */ + jne 1f + int $0x10 + movb $0x0d, %al +1: int $0x10 + /* Restore registers and return */ + popw %bp + popw %bx + ret + .size print_character, . - print_character + +/***************************************************************************** + * Utility function: print a NUL-terminated string + * + * Parameters: + * %ds:si : string to print + * Returns: + * %ds:si : character after terminating NUL + ***************************************************************************** + */ + .section ".prefix.lib" + .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 + * Returns: + * Nothing + ***************************************************************************** + */ + .section ".prefix.lib" + .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 + +/**************************************************************************** + * 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" + .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" + .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" + .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 + 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 + 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 + 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" + .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" + .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" + .code16 + .globl alloc_basemem +alloc_basemem: + /* FBMS => %ax as segment address */ + movw $0x40, %ax + movw %ax, %fs + movw %fs:0x13, %ax + shlw $6, %ax + + /* .data16 segment address */ + subw $_data16_size_pgh, %ax + pushw %ax + + /* .text16 segment address */ + subw $_text16_size_pgh, %ax + pushw %ax + + /* Update FBMS */ + shrw $6, %ax + movw %ax, %fs:0x13 + + /* Return */ + popw %ax + popw %bx + ret + .size alloc_basemem, . - alloc_basemem + +/**************************************************************************** + * 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" + .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" + .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_offset, %esi + + /* Install .text16 and .data16 */ + pushl %edi + movzwl %ax, %edi + shll $4, %edi + movl $_text16_size, %ecx + movl %ecx, %edx + call install_block /* .text16 */ + movzwl %bx, %edi + shll $4, %edi + movl $_data16_progbits_size, %ecx + movl $_data16_size, %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_progbits_size, %ecx + movl $_textdata_size, %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" +#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 + + + /* File split information for the compressor */ +#if COMPRESS + .section ".zinfo", "a" + .ascii "COPY" + .long _prefix_load_offset + .long _prefix_progbits_size + .long _max_align + .ascii "PACK" + .long _text16_load_offset + .long _text16_progbits_size + .long _max_align + .ascii "PACK" + .long _data16_load_offset + .long _data16_progbits_size + .long _max_align + .ascii "PACK" + .long _textdata_load_offset + .long _textdata_progbits_size + .long _max_align +#else /* COMPRESS */ + .section ".zinfo", "a" + .ascii "COPY" + .long _prefix_load_offset + .long _load_size + .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 00000000..a3774d1a --- /dev/null +++ b/gpxe/src/arch/i386/prefix/lkrnprefix.S @@ -0,0 +1,155 @@ +/* + 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 an Etherboot ROM image in order to allow LILO 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 LILO 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.) LILO's only interested in knowing the + number of setup sectors advertised in the table (at offset 497 in + the first sector.) + + Etherboot doesn't require much in the way of setup code. + Historically, the Linux kernel required at least 4 sectors of + setup code. Current versions of LILO look at the byte at + offset 497 in the first sector to indicate how many sectors + of setup code are contained in the image. + +*/ + +#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: + + + .org 497 +setup_sects: + .byte SETUPSECS +root_flags: + .word 0 +syssize: + .word _load_size_pgh - PREFIXPGH +swap_dev: + .word 0 +ram_size: + .word 0 +vid_mode: + .word 0 +root_dev: + .word 0 +boot_flag: + .word 0xAA55 + + + .org 512 + + .section ".zinfo.fixup", "a" /* Compressor fixup information */ + .ascii "SUBW" + .long syssize + .long 16 + .long 0 + .previous + +/* + We're now at the beginning of the second sector of the image - + where the setup code goes. + + We don't need to do too much setup for Etherboot. + + This code gets loaded at SETUPSEG:0. It wants to start + executing the Etherboot image that's loaded at SYSSEG:0 and + whose entry point is SYSSEG:0. +*/ +setup_code: + /* Etherboot expects to be contiguous in memory once loaded. + * LILO doesn't do this, but 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 Etherboot image *would* be. + */ + ljmp $(SYSSEG-(PREFIXSIZE/16)), $run_etherboot + + + .org PREFIXSIZE +/* + We're now at the beginning of the kernel proper. + */ +run_etherboot: + call install + + /* Jump to .text16 segment */ + pushw %ax + pushw $1f + lret + .section ".text16", "awx", @progbits +1: + pushl $main + pushw %cs + call prot_call + popl %eax /* discard */ + + /* Boot next device */ + int $0x18 diff --git a/gpxe/src/arch/i386/prefix/lmelf_dprefix.S b/gpxe/src/arch/i386/prefix/lmelf_dprefix.S new file mode 100644 index 00000000..93534df5 --- /dev/null +++ b/gpxe/src/arch/i386/prefix/lmelf_dprefix.S @@ -0,0 +1,161 @@ +#include "elf.h" + .arch sledgehammer + .code32 + .equ FLAT_CODE_SEG,_pmcs-_gdt + .equ FLAT_DATA_SEG,_pmds-_gdt + .equ MSR_K6_EFER, 0xC0000080 + .equ EFER_LME, 0x00000100 + .equ X86_CR4_PAE, 0x00000020 + .equ CR0_PG, 0x80000000 + + .section ".prefix", "ax", @progbits + +#define LOAD_ADDR 0x10000 + + /* ELF Header */ + .globl elf_header +elf_header: +e_ident: .byte 0x7f, 'E', 'L', 'F', 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 +e_type: .short ET_DYN +e_machine: .short EM_X86_64 +e_version: .long 1 +e_entry: .long LOAD_ADDR + elf_start - elf_header +e_phoff: .long elf_program_header - elf_header +e_shoff: .long 0 +e_flags: .long 0 +e_ehsize: .short elf_header_end - elf_header +e_phentsize: .short ELF32_PHDR_SIZE +e_phnum: .short (elf_program_header_end - elf_program_header)/ELF32_PHDR_SIZE +e_shentsize: .short 0 +e_shnum: .short 0 +e_shstrndx: .short 0 +elf_header_end: + +elf_program_header: +phdr1_p_type: .long PT_NOTE +phdr1_p_offset: .long elf_note - elf_header +phdr1_p_vaddr: .long elf_note +phdr1_p_paddr: .long elf_note +phdr1_p_filesz: .long elf_note_end - elf_note +phdr1_p_memsz: .long elf_note_end - elf_note +phdr1_p_flags: .long PF_R | PF_W | PF_X +phdr1_p_align: .long 0 + +/* The decompressor */ +phdr2_p_type: .long PT_LOAD +phdr2_p_offset: .long 0 +phdr2_p_vaddr: .long elf_header +phdr2_p_paddr: .long LOAD_ADDR +phdr2_p_filesz: .long _verbatim_size +phdr2_p_memsz: .long _image_size +phdr2_p_flags: .long PF_R | PF_W | PF_X +phdr2_p_align: .long 16 + +elf_program_header_end: + + .globl elf_note +elf_note: + .balign 4 + .int 2f - 1f + .int 4f - 3f + .int EIN_PROGRAM_NAME +1: .asciz "ELFBoot" +2: + .balign 4 +3: + .asciz "Etherboot" +4: + + + .balign 4 + .int 2f - 1f + .int 4f - 3f + .int EIN_PROGRAM_VERSION +1: .asciz "ELFBoot" +2: + .balign 4 +3: + .asciz VERSION +4: + +#if 0 + .balign 4 + .int 2f - 1f + .int 4f - 3f + .int EIN_PROGRAM_CHECKSUM +1: .asciz "ELFBoot" +2: + .balign 4 +3: + .word 0 +4: +#endif + .balign 4 +elf_note_end: + +elf_start: + .code64 + /* Reload the gdt to something I know */ + leaq _gdt(%rip), %rax + movq %rax, 0x02 + gdtptr(%rip) + lgdt gdtptr(%rip) + + /* Enter 32bit compatibility mode */ + leaq elf_start32(%rip), %rax + movl %eax, 0x00 + elf_start32_addr(%rip) + ljmp *elf_start32_addr(%rip) + +elf_start32: + .code32 + /* Reload the data segments */ + movl $FLAT_DATA_SEG, %eax + movl %eax, %ds + movl %eax, %es + movl %eax, %ss + + /* Disable paging */ + movl %cr0, %eax + andl $~CR0_PG, %eax + movl %eax, %cr0 + + /* Disable long mode */ + movl $MSR_K6_EFER, %ecx + rdmsr + andl $~EFER_LME, %eax + wrmsr + + /* Disable PAE */ + movl %cr4, %eax + andl $~X86_CR4_PAE, %eax + movl %eax, %cr4 + + /* Save the first argument */ + pushl %ebx + jmp _start + +gdtptr: + .word _gdt_end - _gdt -1 + .long _gdt + .long 0 +_gdt: +elf_start32_addr: + .long elf_start32 + .long FLAT_CODE_SEG +_pmcs: + /* 32 bit protected mode code segment, base 0 */ + .word 0xffff,0 + .byte 0,0x9f,0xcf,0 + +_pmds: + /* 32 bit protected mode data segment, base 0 */ + .word 0xffff,0 + .byte 0,0x93,0xcf,0 +_gdt_end: + + + /* Dummy routines to satisfy the build */ + .section ".text16", "ax", @progbits +prefix_exit: + +prefix_exit_end: + .previous diff --git a/gpxe/src/arch/i386/prefix/lmelf_prefix.S b/gpxe/src/arch/i386/prefix/lmelf_prefix.S new file mode 100644 index 00000000..3c1e7251 --- /dev/null +++ b/gpxe/src/arch/i386/prefix/lmelf_prefix.S @@ -0,0 +1,161 @@ +#include "elf.h" + .arch sledgehammer + .code32 + .equ FLAT_CODE_SEG,_pmcs-_gdt + .equ FLAT_DATA_SEG,_pmds-_gdt + .equ MSR_K6_EFER, 0xC0000080 + .equ EFER_LME, 0x00000100 + .equ X86_CR4_PAE, 0x00000020 + .equ CR0_PG, 0x80000000 + + .section ".prefix", "ax", @progbits + +#define LOAD_ADDR 0x10000 + + /* ELF Header */ + .globl elf_header +elf_header: +e_ident: .byte 0x7f, 'E', 'L', 'F', 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 +e_type: .short ET_EXEC +e_machine: .short EM_X86_64 +e_version: .long 1 +e_entry: .long LOAD_ADDR + elf_start - elf_header +e_phoff: .long elf_program_header - elf_header +e_shoff: .long 0 +e_flags: .long 0 +e_ehsize: .short elf_header_end - elf_header +e_phentsize: .short ELF32_PHDR_SIZE +e_phnum: .short (elf_program_header_end - elf_program_header)/ELF32_PHDR_SIZE +e_shentsize: .short 0 +e_shnum: .short 0 +e_shstrndx: .short 0 +elf_header_end: + +elf_program_header: +phdr1_p_type: .long PT_NOTE +phdr1_p_offset: .long elf_note - elf_header +phdr1_p_vaddr: .long elf_note +phdr1_p_paddr: .long elf_note +phdr1_p_filesz: .long elf_note_end - elf_note +phdr1_p_memsz: .long elf_note_end - elf_note +phdr1_p_flags: .long PF_R | PF_W | PF_X +phdr1_p_align: .long 0 + +/* The decompressor */ +phdr2_p_type: .long PT_LOAD +phdr2_p_offset: .long 0 +phdr2_p_vaddr: .long elf_header +phdr2_p_paddr: .long LOAD_ADDR +phdr2_p_filesz: .long _verbatim_size +phdr2_p_memsz: .long _image_size +phdr2_p_flags: .long PF_R | PF_W | PF_X +phdr2_p_align: .long 16 + +elf_program_header_end: + + .globl elf_note +elf_note: + .balign 4 + .int 2f - 1f + .int 4f - 3f + .int EIN_PROGRAM_NAME +1: .asciz "ELFBoot" +2: + .balign 4 +3: + .asciz "Etherboot" +4: + + + .balign 4 + .int 2f - 1f + .int 4f - 3f + .int EIN_PROGRAM_VERSION +1: .asciz "ELFBoot" +2: + .balign 4 +3: + .asciz VERSION +4: + +#if 0 + .balign 4 + .int 2f - 1f + .int 4f - 3f + .int EIN_PROGRAM_CHECKSUM +1: .asciz "ELFBoot" +2: + .balign 4 +3: + .word 0 +4: +#endif + .balign 4 +elf_note_end: + +elf_start: + .code64 + /* Reload the gdt to something I know */ + leaq _gdt(%rip), %rax + movq %rax, 0x02 + gdtptr(%rip) + lgdt gdtptr(%rip) + + /* Enter 32bit compatibility mode */ + leaq elf_start32(%rip), %rax + movl %eax, 0x00 + elf_start32_addr(%rip) + ljmp *elf_start32_addr(%rip) + +elf_start32: + .code32 + /* Reload the data segments */ + movl $FLAT_DATA_SEG, %eax + movl %eax, %ds + movl %eax, %es + movl %eax, %ss + + /* Disable paging */ + movl %cr0, %eax + andl $~CR0_PG, %eax + movl %eax, %cr0 + + /* Disable long mode */ + movl $MSR_K6_EFER, %ecx + rdmsr + andl $~EFER_LME, %eax + wrmsr + + /* Disable PAE */ + movl %cr4, %eax + andl $~X86_CR4_PAE, %eax + movl %eax, %cr4 + + /* Save the first argument */ + pushl %ebx + jmp _start + +gdtptr: + .word _gdt_end - _gdt -1 + .long _gdt + .long 0 +_gdt: +elf_start32_addr: + .long elf_start32 + .long FLAT_CODE_SEG +_pmcs: + /* 32 bit protected mode code segment, base 0 */ + .word 0xffff,0 + .byte 0,0x9f,0xcf,0 + +_pmds: + /* 32 bit protected mode data segment, base 0 */ + .word 0xffff,0 + .byte 0,0x93,0xcf,0 +_gdt_end: + + + /* Dummy routines to satisfy the build */ + .section ".text16", "ax", @progbits +prefix_exit: + +prefix_exit_end: + .previous diff --git a/gpxe/src/arch/i386/prefix/mbr.S b/gpxe/src/arch/i386/prefix/mbr.S new file mode 100644 index 00000000..adfe2041 --- /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 00000000..d4904b73 --- /dev/null +++ b/gpxe/src/arch/i386/prefix/nbiprefix.S @@ -0,0 +1,76 @@ + .text + .arch i386 + .section ".prefix", "ax", @progbits + .section ".prefix.data", "aw", @progbits + .code16 + .section ".prefix" + .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 _load_size - 512 +memlen: .long _load_size - 512 + .size segment_header, . - segment_header + + .section ".zinfo.fixup", "a" /* Compressor fixup information */ + .ascii "SUBL" + .long imglen + .long 1 + .long 0 + .ascii "SUBL" + .long memlen + .long 1 + .long 0 + .previous + +/***************************************************************************** + * NBI entry point + ***************************************************************************** + */ +entry: + /* Install low and high memory regions */ + call install + + /* Jump to .text16 segment */ + pushw %ax + pushw $1f + lret + .section ".text16", "awx", @progbits +1: + pushl $main + pushw %cs + call prot_call + popl %eax /* discard */ + + /* 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 00000000..032d41e0 --- /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 00000000..d7125b61 --- /dev/null +++ b/gpxe/src/arch/i386/prefix/pxeprefix.S @@ -0,0 +1,666 @@ +#define PXENV_UNDI_SHUTDOWN 0x0005 +#define PXENV_UNDI_GET_NIC_TYPE 0x0012 +#define PXENV_STOP_UNDI 0x0015 +#define PXENV_UNLOAD_STACK 0x0070 + + .text + .arch i386 + .org 0 + .section ".prefix", "ax", @progbits + .section ".prefix.data", "aw", @progbits + .code16 + +#include <undi.h> + +/***************************************************************************** + * Entry point: set operating context, print welcome message + ***************************************************************************** + */ + .section ".prefix" + /* Set up our non-stack segment registers */ + jmp $0x7c0, $1f +1: pushfl + /* %ax here is the default return type... */ + movw $5, %ax /* Keep PXE+UNDI */ + pushal + pushw %ds + pushw %es + pushw %fs + pushw %gs + movw %cs, %ax + movw %ax, %ds + + movw $0x40, %ax /* BIOS data segment access */ + movw %ax, %fs + + pushw %fs:0x13 + /* Record PXENV+ and !PXE nominal addresses */ + movw %es, pxenv_segment + movw %bx, pxenv_offset + movw %sp, %bp + movw %ss, return_stack_segment + movl %esp, return_stack_offset + movl 50(%bp), %eax + movl %eax, ppxe_segoff /* !PXE address */ + /* Set up stack just below 0x7c00 */ + xorw %ax, %ax + movw %ax, %ss + movw $0x7c00, %sp + /* Clear direction flag, for the sake of sanity */ + cld + /* Print welcome message */ + movw $10f, %si + call print_message + .section ".prefix.data" +10: .asciz "PXE->EB:" + .previous + +/***************************************************************************** + * Verify PXENV+ structure and record parameters of interest + ***************************************************************************** + */ +detect_pxenv: + /* Signature check */ + les pxenv_segoff, %di + cmpl $0x4e455850, %es:(%di) /* 'PXEN' signature */ + jne no_pxenv + cmpw $0x2b56, %es:4(%di) /* 'V+' signature */ + jne no_pxenv + /* Record entry point and UNDI segments */ + pushl %es:0x0a(%di) /* Entry point */ + popl entry_segoff + pushw %es:0x24(%di) /* UNDI code segment */ + pushw %es:0x26(%di) /* UNDI code size */ + popl undi_code_segoff + pushw %es:0x20(%di) /* UNDI data segment */ + pushw %es:0x22(%di) /* UNDI data size */ + popl undi_data_segoff + /* Print "PXENV+ at <address>" */ + movw $10f, %si + call print_message + movw %bx, %di + call print_segoff + movb $',', %al + call print_character + jmp 99f + .section ".prefix.data" +10: .asciz " PXENV+ at " + .previous + +no_pxenv: + xorl %eax, %eax + movl %eax, pxenv_segoff + +99: + +/***************************************************************************** + * Verify !PXE structure and record parameters of interest + ***************************************************************************** + */ +detect_ppxe: + /* Signature check */ + les ppxe_segoff, %di + cmpl $0x45585021, %es:(%di) /* '!PXE' signature */ + jne no_ppxe + /* Record structure address, entry point, and UNDI segments */ + pushw %es + popw ppxe_segment + movw %di, ppxe_offset + pushl %es:0x10(%di) /* Entry point */ + popl entry_segoff + pushw %es:0x30(%di) /* UNDI code segment */ + pushw %es:0x36(%di) /* UNDI code size */ + popl undi_code_segoff + pushw %es:0x28(%di) /* UNDI data segment */ + pushw %es:0x2e(%di) /* UNDI data size */ + popl undi_data_segoff + /* Print "!PXE at <address>" */ + movw $10f, %si + call print_message + call print_segoff + movb $',', %al + call print_character + jmp 99f + .section ".prefix.data" +10: .asciz " !PXE at " + .previous + +no_ppxe: + xorl %eax, %eax + movl %eax, ppxe_segoff + +99: + +/***************************************************************************** + * Sanity check: we must have an entry point + ***************************************************************************** + */ +check_have_stack: + /* Check for entry point */ + movl entry_segoff, %eax + testl %eax, %eax + jnz 99f + /* No entry point: print message and skip everything else */ + movw $10f, %si + call print_message + jmp finished + .section ".prefix.data" +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, %di + call print_segoff + .section ".prefix.data" +10: .asciz " entry point at " + .previous + /* Print UNDI code segment */ + movw $10f, %si + call print_message + les undi_code_segoff, %di + call print_segoff + .section ".prefix.data" +10: .asciz "\n UNDI code segment " + .previous + /* Print UNDI data segment */ + movw $10f, %si + call print_message + les undi_data_segoff, %di + call print_segoff + .section ".prefix.data" +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" +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 + movb $0x0a, %al + call print_character + jmp 99f + .section ".prefix.data" +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" +10: .asciz " Unable to determine UNDI physical device\n" + .previous + +99: + +/***************************************************************************** + * Leave NIC in a safe state + ***************************************************************************** + */ +#ifndef PXELOADER_KEEP_UNDI +shutdown_nic: + /* Issue PXENV_UNDI_SHUTDOWN */ + movw $PXENV_UNDI_SHUTDOWN, %bx + call pxe_call + jnc 1f + call print_pxe_error +1: + +/***************************************************************************** + * Unload PXE base code + ***************************************************************************** + */ +unload_base_code: + /* 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 %fs:(0x13), %si + movw undi_fbms_start, %di + call free_basemem +99: + +/***************************************************************************** + * Unload UNDI driver + ***************************************************************************** + */ + +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_start, %si + movw undi_fbms_end, %di + call free_basemem + /* Clear UNDI_FL_STARTED */ + andw $~UNDI_FL_STARTED, flags +99: + +/***************************************************************************** + * 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" +10: .asciz " " +20: .asciz "kB free base memory after PXE unload\n" + .previous +#endif /* PXELOADER_KEEP_UNDI */ + +/***************************************************************************** + * Exit point + ***************************************************************************** + */ +finished: + jmp run_etherboot + +/***************************************************************************** + * Subroutine: print segment:offset address + * + * Parameters: + * %es:%di : segment:offset address to print + * Returns: + * Nothing + ***************************************************************************** + */ +print_segoff: + /* Preserve registers */ + pushw %ax + /* Print "<segment>:offset" */ + movw %es, %ax + call print_hex_word + movb $':', %al + call print_character + movw %di, %ax + call print_hex_word + /* Restore registers and return */ + popw %ax + ret + +/***************************************************************************** + * Subroutine: print decimal word + * + * Parameters: + * %ax : word to print + * Returns: + * Nothing + ***************************************************************************** + */ +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: print PCI bus:dev.fn + * + * Parameters: + * %ax : PCI bus:dev.fn to print + * Returns: + * Nothing + ***************************************************************************** + */ +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 + +/***************************************************************************** + * Subroutine: zero 1kB block of base memory + * + * Parameters: + * %si : block to zero (in kB) + * Returns: + * Nothing + ***************************************************************************** + */ +zero_kb: + /* Preserve registers */ + pushw %ax + pushw %cx + pushw %di + pushw %es + /* Zero block */ + movw %si, %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: + * %si : Expected current free base memory counter (in kB) + * %di : Desired new free base memory counter (in kB) + * %fs : BIOS data segment (0x40) + * Returns: + * %ax : Actual new free base memory counter (in kB) + * + * The base memory from %si kB to %di kB is unconditionally zeroed. + * It will be freed if and only if the expected current free base + * memory counter (%si) 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 %si +1: cmpw %si, %di + je 2f + call zero_kb + incw %si + jmp 1b +2: popw %si + /* Free base memory */ + movw %fs:(0x13), %ax /* Current FBMS to %ax */ + cmpw %ax, %si /* Update FBMS only if "old" value */ + jne 1f /* is correct */ + movw %di, %ax +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: /* 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" +10: .asciz " UNDI API call " +20: .asciz " failed: status code " +30: .asciz "\n" + .previous + +/***************************************************************************** + * PXE data structures + ***************************************************************************** + */ + +pxe_parameter_structure: .fill 20 + +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 + +/* 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 + +return_stack_segoff: +return_stack_offset: .long 0 +return_stack_segment: .word 0 + +return_type: .word 0 /* Default: unload PXE and boot next */ + +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_STARTED + + .equ undi_device_size, ( . - undi_device ) + +/***************************************************************************** + * Run Etherboot main code + ***************************************************************************** + */ +run_etherboot: + /* Install Etherboot */ + 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 + + /* Jump to .text16 segment with %ds pointing to .data16 */ + movw %bx, %ds + pushw %ax + pushw $1f + lret + .section ".text16", "ax", @progbits +1: + /* Run main program */ + pushl $main + pushw %cs + call prot_call + popl %eax /* discard */ + +#ifdef PXELOADER_KEEP_UNDI + /* Boot next device */ + movw $0x40, %ax + movw %ax, %fs + movw $preloaded_undi,%bx + + cli + movw %ss:return_type-undi_device(%bx),%ax + lssl %ss:return_stack_segoff-undi_device(%bx), %esp + movw %sp,%bp + movw %ax,38(%bp) /* Overwrite return AX value */ + popw %fs:0x13 /* 0 */ + popw %gs /* 2 */ + popw %fs /* 4 */ + popw %es /* 6 */ + popw %ds /* 8 */ + popal /* 10, 14, 18, 22, 26, 30, 34, 38 */ + popfl /* 42 */ + lret /* 46 */ +#else + int $0x18 +#endif + + .previous diff --git a/gpxe/src/arch/i386/prefix/romprefix.S b/gpxe/src/arch/i386/prefix/romprefix.S new file mode 100644 index 00000000..d37cce94 --- /dev/null +++ b/gpxe/src/arch/i386/prefix/romprefix.S @@ -0,0 +1,383 @@ +/* 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. + */ + +#define PNP_SIGNATURE ( '$' + ( 'P' << 8 ) + ( 'n' << 16 ) + ( 'P' << 24 ) ) +#define PMM_SIGNATURE ( '$' + ( 'P' << 8 ) + ( 'M' << 16 ) + ( 'M' << 24 ) ) +#define STACK_MAGIC ( 'L' + ( 'R' << 8 ) + ( 'E' << 16 ) + ( 'T' << 24 ) ) +#define PNP_GET_BBS_VERSION 0x60 + + .text + .code16 + .arch i386 + .section ".prefix", "ax", @progbits + + .org 0x00 +romheader: + .word 0xAA55 /* BIOS extension signature */ +romheader_size: .byte _load_size_sect /* Size in 512-byte blocks */ + jmp init /* Initialisation vector */ +checksum: + .byte 0 + .org 0x16 + .word undiheader + .org 0x18 + .word pciheader + .org 0x1a + .word pnpheader + .size romheader, . - romheader + + .section ".zinfo.fixup", "a" /* Compressor fixup information */ + .ascii "SUBB" + .long romheader_size + .long 512 + .long 0 + .previous + +pciheader: + .ascii "PCIR" /* Signature */ + .word pci_vendor_id /* Vendor ID */ + .word pci_device_id /* Device ID */ + .word 0x0000 /* pointer to vital product data */ + .word pciheader_len /* PCI data structure length */ + .byte 0x00 /* PCI data structure revision */ + .byte 0x02 /* Device Base Type code */ + .byte 0x00 /* Device Sub-Type code */ + .byte 0x00 /* Device Interface Type code */ +pciheader_size: .word _load_size_sect /* Image length same as offset 02h */ + .word 0x0001 /* revision level of code/data */ + .byte 0x00 /* code type */ + .byte 0x80 /* Flags (last PCI data structure) */ + .word 0x0000 /* reserved */ + .equ pciheader_len, . - pciheader + .size pciheader, . - pciheader + + .section ".zinfo.fixup", "a" /* Compressor fixup information */ + .ascii "SUBW" + .long pciheader_size + .long 512 + .long 0 + .previous + +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 0x54 /* 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 + +mfgstr: + .asciz "http://etherboot.org" + .size mfgstr, . - mfgstr +prodstr: + .asciz "gPXE" + .size prodstr, . - prodstr + +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_size /* Stack segment size */ + .word _data16_size /* Data segment size */ + .word _text16_size /* Code segment size */ + .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 + cld + pushw %cs + popw %ds + /* Print message as early as possible */ + movw $init_message, %si + call print_message + /* Check for PnP BIOS */ + testw $0x0f, %di /* PnP signature must be aligned - bochs */ + jnz hook_int19 /* uses unalignment to indicate 'fake' PnP. */ + cmpl $PNP_SIGNATURE, %es:0(%di) + jne hook_int19 + /* Is PnP: print PnP message */ + movw $init_message_pnp, %si + call print_message + xchgw %bx, %bx + /* Check for BBS */ + pushw %es:0x1b(%di) /* Real-mode data segment */ + pushw %ds /* &(bbs_version) */ + pushw $bbs_version + pushw $PNP_GET_BBS_VERSION + lcall *%es:0xd(%di) + addw $8, %sp + testw %ax, %ax + jne hook_int19 + movw $init_message_bbs, %si + call print_message + jmp hook_bbs + /* Not BBS-compliant - must hook INT 19 */ +hook_int19: + movw $init_message_int19, %si + call print_message + xorw %ax, %ax + movw %ax, %es + pushw %cs + pushw $int19_entry + popl %es:( 0x19 * 4 ) +hook_bbs: + /* Check for PMM */ + movw $( 0xe000 - 1 ), %di +pmm_scan: + incw %di + jz no_pmm + movw %di, %es + cmpl $PMM_SIGNATURE, %es:0 + jne pmm_scan + xorw %bx, %bx + xorw %si, %si + movzbw %es:5, %cx +1: es lodsb + addb %al, %bl + loop 1b + jnz pmm_scan + /* PMM found: print PMM message */ + movw $init_message_pmm, %si + call print_message + /* Try to allocate 2MB block via PMM */ + pushw $0x0006 /* Aligned, extended memory */ + pushl $0xffffffff /* No handle */ + pushl $( 0x00200000 / 16 ) /* 2MB in paragraphs */ + pushw $0x0000 /* pmmAllocate */ + lcall *%es:7 + addw $12, %sp + testw %dx, %dx /* %ax==0 even on success, since align=2MB */ + jnz gotpmm + movw $init_message_pmm_failed, %si + call print_message + jmp no_pmm +gotpmm: /* PMM allocation succeeded: copy ROM to PMM block */ + pushal /* PMM presence implies 1kB stack */ + movw %ax, %es /* %ax=0 already - see above */ + pushw %dx + pushw %ax + popl %edi + movl %edi, image_source + 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 and update checksum */ + xorw %bx, %bx + xorw %si, %si + movw $_prefix_size_sect, %cx + movb %cl, romheader_size + shlw $9, %cx +1: lodsb + addb %al, %bl + loop 1b + subb %bl, checksum + popal +no_pmm: + /* Print CRLF to terminate messages */ + movw $'\n', %ax + call print_character + /* Restore registers */ + popw %es + popw %ds + popaw + /* Indicate boot capability to PnP BIOS, if present */ + movw $0x20, %ax + lret + .size init, . - init + +init_message: + .asciz "gPXE (http://etherboot.org) -" + .size init_message, . - init_message +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 +init_message_pmm_failed: + .asciz "(failed)" + .size init_message_pmm_failed, . - init_message_pmm_failed +init_message_int19: + .asciz " INT19" + .size init_message_int19, . - init_message_int19 + +/* ROM image location + * + * May be either within option ROM space, or within PMM-allocated block. + */ +image_source: + .long 0 + .size image_source, . - image_source + +/* Temporary decompression area + * + * May be either at HIGHMEM_LOADPOINT, or within PMM-allocated block. + */ +decompress_to: + .long HIGHMEM_LOADPOINT + .size decompress_to, . - decompress_to + +/* BBS version + * + * Filled in by BBS BIOS. We ignore the value. + */ +bbs_version: + .word 0 + +/* 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 + +/* INT19 entry point + * + * Called via the hooked INT 19 if we detected a non-PnP BIOS. + */ +int19_entry: + pushw %cs + call exec + /* No real way to return from INT19 */ + int $0x18 + .size int19_entry, . - int19_entry + +/* Execute as a boot device + * + */ +exec: /* Set %ds = %cs */ + pushw %cs + popw %ds + + /* Print message as soon as possible */ + 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 + /* No need to clean up stack; we are about to reload %ss:sp */ + + /* 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 "gPXE starting boot\n" + .size exec_message, . - exec_message + +/* UNDI loader + * + * Called by an external program to load our PXE stack. + */ +undiloader: + /* Save registers */ + pushl %esi + pushl %edi + pushw %es + pushw %bx + /* UNDI loader parameter structure address into %es:%di */ + movw %sp, %bx + movw %ss:12(%bx), %di + movw %ss:14(%bx), %es + /* Install to specified real-mode addresses */ + pushw %di + movw %es:12(%di), %bx + movw %es:14(%di), %ax + movl %cs:image_source, %esi + movl %cs: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 + 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 00000000..70167a14 --- /dev/null +++ b/gpxe/src/arch/i386/prefix/unnrv2b.S @@ -0,0 +1,182 @@ +/* + * 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 + */ + +/**************************************************************************** + * 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/usbdisk.S b/gpxe/src/arch/i386/prefix/usbdisk.S new file mode 100644 index 00000000..fa7d1956 --- /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/scripts/i386-kir.lds b/gpxe/src/arch/i386/scripts/i386-kir.lds new file mode 100644 index 00000000..b213c605 --- /dev/null +++ b/gpxe/src/arch/i386/scripts/i386-kir.lds @@ -0,0 +1,196 @@ +/* -*- 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) + } + + /* + * 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 00000000..a5a01056 --- /dev/null +++ b/gpxe/src/arch/i386/scripts/i386.lds @@ -0,0 +1,272 @@ +/* -*- 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. + * + * 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 = 0; + . = _prefix_link_addr; + _prefix = .; + + .prefix : AT ( _prefix_load_offset + __prefix ) { + __prefix = .; + _entry = .; + *(.prefix) + *(.prefix.*) + _eprefix_progbits = .; + } + + _eprefix = .; + + /* + * The 16-bit sections, if present + */ + + _text16_link_addr = 0; + . = _text16_link_addr; + _text16 = .; + + . += 1; /* Prevent NULL being valid */ + + .text16 : AT ( _text16_load_offset + __text16 ) { + __text16 = .; + *(.text16) + *(.text16.*) + _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.*) + } + .data16 : AT ( _data16_load_offset + __data16 ) { + __data16 = .; + *(.data16) + *(.data16.*) + _edata16_progbits = .; + } + .bss16 : AT ( _data16_load_offset + __bss16 ) { + __bss16 = .; + _bss16 = .; + *(.bss16) + *(.bss16.*) + _ebss16 = .; + } + .stack16 : AT ( _data16_load_offset + __stack16 ) { + __stack16 = .; + *(.stack16) + *(.stack16.*) + } + + _edata16 = .; + + /* + * The 32-bit sections + */ + + _textdata_link_addr = 0; + . = _textdata_link_addr; + _textdata = .; + + _text = .; + + . += 1; /* Prevent NULL being valid */ + + .text : AT ( _textdata_load_offset + __text ) { + __text = .; + *(.text.null_trap) + *(.text) + *(.text.*) + } = 0x9090 + + _etext = .; + + _data = .; + + .rodata : AT ( _textdata_load_offset + __rodata ) { + __rodata = .; + *(.rodata) + *(.rodata.*) + } + .data : AT ( _textdata_load_offset + __data ) { + __data = .; + *(.data) + *(.data.*) + *(SORT(.tbl.*)) /* Various tables. See include/tables.h */ + _etextdata_progbits = .; + } + .bss : AT ( _textdata_load_offset + __bss ) { + __bss = .; + _bss = .; + *(.bss) + *(.bss.*) + *(COMMON) + _ebss = .; + } + .stack : AT ( _textdata_load_offset + __stack ) { + __stack = .; + *(.stack) + *(.stack.*) + } + + _edata = .; + + _etextdata = .; + + _end = .; + + /* + * Compressor information block + */ + + _zinfo_link_addr = 0; + . = _zinfo_link_addr; + _zinfo = .; + + .zinfo : AT ( _zinfo_load_offset + __zinfo ) { + __zinfo = .; + _entry = .; + *(.zinfo) + *(.zinfo.*) + _ezinfo_progbits = .; + } + + _ezinfo = .; + + /* + * Dispose of the comment and note sections to make the link map + * easier to read + */ + + /DISCARD/ : { + *(.comment) + *(.note) + } + + /* + * Load address calculations. The slightly obscure nature of the + * calculations is because ALIGN(x) can only operate on the + * location counter. + */ + + _max_align = 16; + _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; + + . -= _textdata_link_addr; + _textdata_load_offset = ALIGN ( _max_align ); + _textdata_load_addr = _textdata_link_addr + _textdata_load_offset; + _textdata_size = _etextdata - _textdata; + _textdata_progbits_size = _etextdata_progbits - _textdata; + . = _textdata_load_addr + _textdata_progbits_size; + + _load_size = . - _load_addr; + + . -= _zinfo_link_addr; + _zinfo_load_offset = ALIGN ( _max_align ); + _zinfo_load_addr = _zinfo_link_addr + _zinfo_load_offset; + _zinfo_size = _ezinfo - _zinfo; + _zinfo_progbits_size = _ezinfo_progbits - _zinfo; + . = _zinfo_load_addr + _zinfo_progbits_size; + + _payload_offset = _text16_load_offset; + + /* + * 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" ); + + . = _textdata_load_addr - _textdata_link_addr; + _assert = ASSERT ( ( . == ALIGN ( _max_align ) ), + "_text is badly aligned" ); + + /* + * Values calculated to save code from doing it + */ + _prefix_size_pgh = ( ( _prefix_size + 15 ) / 16 ); + _prefix_size_sect = ( ( _prefix_size + 511 ) / 512 ); + _text16_size_pgh = ( ( _text16_size + 15 ) / 16 ); + _data16_size_pgh = ( ( _data16_size + 15 ) / 16 ); + + /* + * Load sizes in paragraphs and sectors. Note that wherever the + * _load_size variables are used, there must be a corresponding + * .zinfo.fixup section. + */ + _load_size_pgh = ( ( _load_size + 15 ) / 16 ); + _load_size_sect = ( ( _load_size + 511 ) / 512 ); +} diff --git a/gpxe/src/arch/i386/transitions/libkir.S b/gpxe/src/arch/i386/transitions/libkir.S new file mode 100644 index 00000000..1023ddd0 --- /dev/null +++ b/gpxe/src/arch/i386/transitions/libkir.S @@ -0,0 +1,254 @@ +/* + * libkir: a transition library for -DKEEP_IT_REAL + * + * Michael Brown <mbrown@fensystems.co.uk> + * + */ + +/**************************************************************************** + * 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 00000000..e69de29b --- /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 00000000..b1f9dd59 --- /dev/null +++ b/gpxe/src/arch/i386/transitions/librm.S @@ -0,0 +1,559 @@ +/* + * librm: a library for interfacing to real-mode code + * + * Michael Brown <mbrown@fensystems.co.uk> + * + */ + +/* 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 + .section ".text16", "ax", @progbits + .section ".text16.data", "aw", @progbits + .section ".data16", "aw", @progbits + +/**************************************************************************** + * 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" + .align 16 +gdt: +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" + .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 and set GDT base */ + 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 + addl $gdt, %eax + movl %eax, gdt_base + + /* Restore registers */ + negl %edi + popl %ebx + popl %eax + lret + + .section ".text16" + .code16 +set_seg_base: +1: movw %ax, 2(%bx) + rorl $16, %eax + movb %al, 4(%bx) + movb %ah, 7(%bx) + roll $16, %eax + 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" + .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 gdt + movl %cr0, %eax + orb $CR0_PE, %al + movl %eax, %cr0 + data32 ljmp $VIRTUAL_CS, $1f + .section ".text" + .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 + +/**************************************************************************** + * 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" + .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" + .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 + + /* 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" +p2r_jump_vector: + .word p2r_jump_target + .globl rm_cs +rm_cs: .word 0 + .globl rm_ds + .section ".text16.data" +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_IX86 ( PC_OFFSET_GDT + 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" + .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 $8, %sp + movw %sp, %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" + .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" + .code16 +1: + /* Reload GDT, restore registers and flags and return */ + movw %sp, %bp + lgdt (%bp) + addw $12, %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" + .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" + .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" + .code32 +1: + /* Restore registers and return */ + popal + ret + + + /* Function vector, used because "call xx(%sp)" is not a valid + * 16-bit expression. + */ + .section ".data16" +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" +rm_sp: .word 0 +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" +_virt_offset: .long 0 +_text16: .long 0 +_data16: .long 0 + + /* Externally-visible copies, created by real_to_prot */ + .section ".data" + .globl virt_offset +virt_offset: .long 0 + .globl text16 +text16: .long 0 + .globl data16 +data16: .long 0 |