diff options
author | Michael Brown | 2005-03-08 19:53:11 +0100 |
---|---|---|
committer | Michael Brown | 2005-03-08 19:53:11 +0100 |
commit | 3d6123e69ab879c72ff489afc5bf93ef0b7a94ce (patch) | |
tree | 9f3277569153a550fa8d81ebd61bd88f266eb8da /src/arch/i386 | |
download | ipxe-3d6123e69ab879c72ff489afc5bf93ef0b7a94ce.tar.gz ipxe-3d6123e69ab879c72ff489afc5bf93ef0b7a94ce.tar.xz ipxe-3d6123e69ab879c72ff489afc5bf93ef0b7a94ce.zip |
Initial revision
Diffstat (limited to 'src/arch/i386')
75 files changed, 13904 insertions, 0 deletions
diff --git a/src/arch/i386/Config b/src/arch/i386/Config new file mode 100644 index 00000000..e8ac91b8 --- /dev/null +++ b/src/arch/i386/Config @@ -0,0 +1,131 @@ +# 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. +# -DIBM_L40 +# This option uses the 0x92 method of controlling +# A20 instead of the traditional method of using the +# keyboard controller. An explanation of A20 is here: +# http://www.win.tue.nl/~aeb/linux/kbd/A20.html +# This occurs on MCA, EISA and some embedded boards, +# and sometimes with the Fast Gate A20 option on some +# BIOSes. +# Enable this only if you are sure of what you are doing. +# +# 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 inside the Bochs emulator. The pnic +# driver is probably the best one to try. +# CFLAGS+= -DCONFIG_PCI_DIRECT + +# 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 +GCC_MINORVERSION = $(word 2, $(GCC_VERSION)) +ifneq ($(GCC_MINORVERSION),4) +CFLAGS+= -mcpu=i386 +endif + +LDFLAGS+= -N + +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/src/arch/i386/Makefile b/src/arch/i386/Makefile new file mode 100644 index 00000000..cded46c3 --- /dev/null +++ b/src/arch/i386/Makefile @@ -0,0 +1,373 @@ +ARCH_FORMAT= elf32-i386 + +# For debugging, don't delete intermediates +#.SECONDARY: + +LDSCRIPT= arch/i386/core/etherboot.lds +PLDSCRIPT= arch/i386/core/etherboot.prefix.lds + +LCONFIG+= -Ui386 + +ROMLIMIT= 524288 +CHECKSIZE= { read d1; read d1 d2 d3 size d4; [ $$size -gt $(ROMLIMIT) ] &&\ + { $(RM) $@; echo "ERROR: code size exceeds limit!"; exit 1; }; exit 0; } + +START= $(BIN)/start32.o $(BIN)/linuxbios.o \ + $(BIN)/bios.o $(BIN)/console.o $(BIN)/memsizes.o $(BIN)/basemem.o \ + $(BIN)/hidemem.o $(BIN)/e820mangler.o \ + $(BIN)/realmode.o $(BIN)/realmode_asm.o \ + $(BIN)/callbacks.o $(BIN)/pxe_callbacks.o + +SRCS+= arch/i386/prefix/floppyprefix.S +SRCS+= arch/i386/prefix/unhuf.S +SRCS+= arch/i386/prefix/unnrv2b.S +SRCS+= arch/i386/firmware/pcbios/bios.c +SRCS+= arch/i386/firmware/pcbios/console.c +SRCS+= arch/i386/firmware/pcbios/memsizes.c +SRCS+= arch/i386/firmware/pcbios/basemem.c +SRCS+= arch/i386/firmware/pcbios/hidemem.c +SRCS+= arch/i386/firmware/pcbios/e820mangler.S +SRCS+= arch/i386/prefix/liloprefix.S +SRCS+= arch/i386/prefix/elfprefix.S +SRCS+= arch/i386/prefix/lmelf_prefix.S +SRCS+= arch/i386/prefix/elf_dprefix.S +SRCS+= arch/i386/prefix/lmelf_dprefix.S +SRCS+= arch/i386/prefix/comprefix.S +SRCS+= arch/i386/prefix/exeprefix.S +SRCS+= arch/i386/prefix/pxeprefix.S +SRCS+= arch/i386/prefix/romprefix.S + +SRCS+= arch/i386/core/init.S +SRCS+= arch/i386/core/start32.S +SRCS+= arch/i386/core/pci_io.c +SRCS+= arch/i386/core/i386_timer.c +SRCS+= arch/i386/core/elf.c +SRCS+= arch/i386/core/cpu.c +SRCS+= arch/i386/core/video_subr.c +SRCS+= arch/i386/core/pic8259.c +SRCS+= arch/i386/core/hooks.c +SRCS+= arch/i386/core/callbacks.c +SRCS+= arch/i386/core/realmode.c +SRCS+= arch/i386/core/realmode_asm.S +SRCS+= arch/i386/core/pxe_callbacks.c + +# ROM loaders: ISA and PCI versions +ISAPREFIX= $(BIN)/isaprefix.o +ISAENTRY= $(BIN)/isaprefix.entry.o +ISAEXIT= $(BIN)/isaprefix.exit.o +PCIPREFIX= $(BIN)/pciprefix.o +PCIENTRY= $(BIN)/pciprefix.entry.o +PCIEXIT= $(BIN)/pciprefix.exit.o +# Variables xxx_ROMTYPE are defined by genrules.pl. ROMENTRY and +# ROMEXIT will evaluate to give the correct objects to use. +TARGETBASE=$(patsubst $(BIN)/%,%,$(firstword $(subst ., ,$@))) +ROMCARD=$(firstword $(subst --, ,$(TARGETBASE))) +ROMTYPE=$(firstword $(ROMTYPE_$(ROMCARD)) ISA) +romENTRY=$($(ROMTYPE)ENTRY) +romEXIT=$($(ROMTYPE)EXIT) + +# Target type for generic prf rules +TARGETTYPE=$(patsubst .%,%, $(suffix $(basename $@))) +TARGETENTRY=$($(TARGETTYPE)ENTRY) +TARGETEXIT=$($(TARGETTYPE)EXIT) + +# Other real-mode entry loaders +dskPREFIX= $(BIN)/floppyprefix.o +dskENTRY= $(BIN)/floppyprefix.entry.o +dskEXIT= $(BIN)/floppyprefix.exit.o +comPREFIX= $(BIN)/comprefix.o +comENTRY= $(BIN)/comprefix.entry.o +comEXIT= $(BIN)/comprefix.exit.o +exePREFIX= $(BIN)/exeprefix.o +exeENTRY= $(BIN)/exeprefix.entry.o +exeEXIT= $(BIN)/exeprefix.exit.o +liloPREFIX= $(BIN)/liloprefix.o +liloENTRY= $(BIN)/liloprefix.entry.o +liloEXIT= $(BIN)/liloprefix.exit.o +bImagePREFIX= $(BIN)/bImageprefix.o +bImageENTRY= $(BIN)/bImageprefix.entry.o +bImageEXIT= $(BIN)/bImageprefix.exit.o +pxePREFIX= $(BIN)/pxeprefix.o +pxeENTRY= $(BIN)/pxeprefix.entry.o +pxeEXIT= $(BIN)/pxeprefix.exit.o +rawPREFIX= $(BIN)/nullprefix.o +rawENTRY= $(BIN)/nullprefix.entry.o +rawEXIT= $(BIN)/nullprefix.exit.o + +# Protected mode entry loaders +elfPREFIX= $(BIN)/elfprefix.o +elfENTRY= $(BIN)/elfprefix.entry.o +elfEXIT= $(BIN)/elfprefix.exit.o +lmelfPREFIX= $(BIN)/lmelf_prefix.o +lmelfENTRY= $(BIN)/lmelf_prefix.entry.o +lmelfEXIT= $(BIN)/lmelf_prefix.exit.o +elfdPREFIX= $(BIN)/elf_dprefix.o +elfdENTRY= $(BIN)/elf_dprefix.entry.o +elfdEXIT= $(BIN)/elf_dprefix.exit.o +lmelfdPREFIX= $(BIN)/lmelf_dprefix.o +lmelfdENTRY= $(BIN)/lmelf_dprefix.entry.o +lmelfdEXIT= $(BIN)/lmelf_dprefix.exit.o + +include $(BIN)/Roms + +all: $(ROMS) +allroms: $(ROMS) +allzroms: $(ROMS) +alldsks: $(EB_DSKS) +allzdsks: $(EB_ZDSKS) +alllilos: $(EB_LILOS) +allzlilos: $(EB_ZLILOS) +allbImages: $(EB_BIMAGES) +allbzImages: $(EB_BZIMAGES) +allpxes: $(EB_PXES) +allzpxes: $(EB_ZPXES) +allelfs: $(EB_ELFS) +allzelfs: $(EB_ZELFS) +alllmelfs: $(EB_LMELFS) +allzlmelfs: $(EB_ZLMELFS) +allelfds: $(EB_ELFDS) +allzelfds: $(EB_ZELFDS) +alllmelfds: $(EB_LMELFDS) +allzlmelfds: $(EB_ZLMELFDS) +allcoms: $(EB_COMS) +allexes: $(EB_EXES) +allisos: $(EB_ISOS) +alllisos: $(EB_LISOS) + +BOBJS+= $(BIN)/pci_io.o $(BIN)/i386_timer.o +BOBJS+= $(BIN)/elf.o $(BIN)/cpu.o $(BIN)/video_subr.o +BOBJS+= $(BIN)/pic8259.o $(BIN)/hooks.o + +# ROM loaders + +$(ISAPREFIX): arch/i386/prefix/romprefix.S $(MAKEDEPS) + $(CPP) $(CFLAGS) $(LCONFIG) -Ui386 -D ASSEMBLY $< \ + | $(AS) $(ASFLAGS) -o $@ + +$(PCIPREFIX): arch/i386/prefix/romprefix.S $(MAKEDEPS) + $(CPP) -DPCI_PNP_HEADER $(CFLAGS) $(LCONFIG) -Ui386 -D ASSEMBLY $< \ + | $(AS) $(ASFLAGS) -o $@ + +# Prefix splitters +$(BIN)/%prefix.entry.o: $(BIN)/%prefix.o $(MAKEDEPS) + $(OBJCOPY) -R .text16 $< $@ + +$(BIN)/%prefix.exit.o: $(BIN)/%prefix.o $(MAKEDEPS) + $(OBJCOPY) -R .prefix $< $@ + +# Generic prefix objects +PREFIXOBJS = $(BIN)/init.o +ZPREFIXOBJS = $(BIN)/init.o $(BIN)/unnrv2b.o + +# Utilities +$(BIN)/nrv2b: util/nrv2b.c + $(HOST_CC) -O2 -DENCODE -DDECODE -DMAIN -DVERBOSE -DNDEBUG -DBITSIZE=32 -DENDIAN=0 -o $@ $< + +ZFILELEN = perl util/zfilelen.pl + +# Pattern Rules + +# General for compiling/assembly source files + +$(BIN)/%.o: arch/i386/core/%.c $(MAKEDEPS) + $(CC) $(CFLAGS) -o $@ -c $< + +$(BIN)/%.o: arch/i386/core/%.S $(MAKEDEPS) + $(CPP) $(CFLAGS) -Ui386 -D ASSEMBLY $< | $(AS) $(ASFLAGS) -o $@ + +$(BIN)/%.o: arch/i386/firmware/pcbios/%.c $(MAKEDEPS) + $(CC) $(CFLAGS) -o $@ -c $< + +$(BIN)/%.o: arch/i386/firmware/pcbios/%.S $(MAKEDEPS) + $(CPP) $(CFLAGS) -Ui386 -D ASSEMBLY $< | $(AS) $(ASFLAGS) -o $@ + +$(BIN)/%.o: arch/i386/prefix/%.S $(MAKEDEPS) + $(CPP) $(CFLAGS) -Ui386 -D ASSEMBLY $< | $(AS) $(ASFLAGS) -o $@ + +# general rule for 16bit .o, may be overridden +$(BIN)/%.o: $(BIN)/%.s + $(AS) $(ASFLAGS) -o $@ $< + +# general rule for .bin (plain binary loader code), may be overridden +$(BIN)/%.bin: $(BIN)/%.o + $(OBJCOPY) -O binary $< $@ + +# general rule for .z (compressed binary code), may be overridden +# rule for .z is in top level Makefile +# Give the directory name, e.g. use $(BIN)/rtl8139.com as the target. + +$(BIN)/%.zo: $(BIN)/%.zbin arch/i386/core/prefixzdata.lds $(MAKEDEPS) + $(LD) -T arch/i386/core/prefixzdata.lds -b binary $< -o $@ + +$(BIN)/%.uo: $(BIN)/%.bin arch/i386/core/prefixudata.lds $(MAKEDEPS) + $(LD) -T arch/i386/core/prefixudata.lds -b binary $< -o $@ + +# Intermediate prf rules + +%.prf: %.rt $(PREFIXOBJS) %.rt1.uo %.rt2.uo $(MAKEDEPS) + $(MAKE) $(TARGETENTRY) + $(LD) $(LDFLAGS) -T $(PLDSCRIPT) $(TARGETENTRY) -R $(subst $(MAKEDEPS),,$^) -o $@ + +%.zprf: %.rt $(ZPREFIXOBJS) %.rt1.uo %.rt2.zo $(MAKEDEPS) + $(MAKE) $(TARGETENTRY) + $(LD) $(LDFLAGS) -T $(PLDSCRIPT) $(TARGETENTRY) -R $(subst $(MAKEDEPS),,$^) -o $@ + +# general rules for normal/compressed ROM images, may be overridden +SUFFIXES += rom zrom + +$(BIN)/%.rom.rt: $(BIN)/%.rt.o $(ISAENTRY) $(PCIENTRY) $(ISAEXIT) $(PCIEXIT) $(LDSCRIPT) $(MAKEDEPS) + $(LD) $(LDFLAGS) -T $(LDSCRIPT) -o $@ $(romEXIT) $< + @$(SIZE) $@ | $(CHECKSIZE) + +$(BIN)/%.rom: $(BIN)/%.rom.prf + $(OBJCOPY) -O binary $< $@ + $(MAKEROM) $(MAKEROM_FLAGS) $(MAKEROM_$(ROMCARD)) $(MAKEROM_ID_$(ROMCARD)) -i$(IDENT) $@ + +$(BIN)/%.zrom: $(BIN)/%.rom.zprf + $(OBJCOPY) -O binary $< $@ + $(MAKEROM) $(MAKEROM_FLAGS) $(MAKEROM_$(ROMCARD)) $(MAKEROM_ID_$(ROMCARD)) -i$(IDENT) $@ + +# general rules for ELF images +SUFFIXES += elf zelf +$(BIN)/%.elf.rt: $(BIN)/%.rt.o $(elfENTRY) $(elfEXIT) $(LDSCRIPT) $(MAKEDEPS) + $(LD) $(LDFLAGS) -T $(LDSCRIPT) -o $@ $(elfEXIT) $< + +$(BIN)/%.elf: $(BIN)/%.elf.prf + $(OBJCOPY) -O binary $< $@ + +$(BIN)/%.zelf: $(BIN)/%.elf.zprf + $(OBJCOPY) -O binary $< $@ + +# general rules for Long Mode ELF images +SUFFIXES += lmelf zlmelf +$(BIN)/%.lmelf.rt: $(BIN)/%.rt.o $(lmelfENTRY) $(lmelfEXIT) $(LDSCRIPT) $(MAKEDEPS) + $(LD) $(LDFLAGS) -T $(LDSCRIPT) -o $@ $(lmelfEXIT) $< + +$(BIN)/%.lmelf: $(BIN)/%.lmelf.prf + $(OBJCOPY) -O binary $< $@ + +$(BIN)/%.zlmelf: $(BIN)/%.lmelf.zprf + $(OBJCOPY) -O binary $< $@ + +# general rules for ELF dynamic images +SUFFIXES += elfd zelfd +$(BIN)/%.elfd.rt: $(BIN)/%.rt.o $(elfdENTRY) $(elfdEXIT) $(LDSCRIPT) $(MAKEDEPS) + $(LD) $(LDFLAGS) -T $(LDSCRIPT) -o $@ $(elfdEXIT) $< + +$(BIN)/%.elfd: $(BIN)/%.elfd.prf + $(OBJCOPY) -O binary $< $@ + +$(BIN)/%.zelfd: $(BIN)/%.elfd.zprf + $(OBJCOPY) -O binary $< $@ + +# general rules for Long Mode ELF dynamic images +SUFFIXES += lmelfd zlmelfd +$(BIN)/%.lmelfd.rt: $(BIN)/%.rt.o $(lmelfdENTRY) $(lmelfdEXIT) $(LDSCRIPT) $(MAKEDEPS) + $(LD) $(LDFLAGS) -T $(LDSCRIPT) -o $@ $(lmelfdEXIT) $< + +$(BIN)/%.lmelfd: $(BIN)/%.lmelfd.prf + $(OBJCOPY) -O binary $< $@ + +$(BIN)/%.zlmelfd: $(BIN)/%.lmelfd.zprf + $(OBJCOPY) -O binary $< $@ + +# rules to generate a DOS loadable .com executable +SUFFIXES += com +$(BIN)/%.com.rt: $(BIN)/%.rt.o $(comENTRY) $(comEXIT) $(LDSCRIPT) $(MAKEDEPS) + $(LD) $(LDFLAGS) -T $(LDSCRIPT) -o $@ $< $(comEXIT) + +$(BIN)/%.com: $(BIN)/%.com.zprf + $(OBJCOPY) -O binary $< $@ + +# rules to generate a DOS loadable .exe executable +SUFFIXES += exe +$(BIN)/%.exe.rt: $(BIN)/%.rt.o $(exeENTRY) $(exeEXIT) $(LDSCRIPT) $(MAKEDEPS) + $(LD) $(LDFLAGS) -T $(LDSCRIPT) -o $@ $< $(exeEXIT) + @$(SIZE) $@ | $(CHECKSIZE) + +$(BIN)/%.exe: $(BIN)/%.exe.prf + $(OBJCOPY) -O binary $< $@ + +# rules to make a LILO loadable image +SUFFIXES += lilo zlilo + +$(BIN)/%.lilo.rt: $(BIN)/%.rt.o $(liloENTRY) $(liloEXIT) $(LDSCRIPT) $(MAKEDEPS) + $(LD) $(LDFLAGS) -T $(LDSCRIPT) -o $@ $< $(liloEXIT) + @$(SIZE) $@ | $(CHECKSIZE) + +$(BIN)/%.lilo: $(BIN)/%.lilo.prf + $(OBJCOPY) -O binary $< $@ + +$(BIN)/%.zlilo: $(BIN)/%.lilo.zprf + $(OBJCOPY) -O binary $< $@ + +# rules to make big linux boot protocol image +SUFFIXES += bImage bzImage + +$(BIN)/%.bImage.rt: $(BIN)/%.rt.o $(bImageENTRY) $(bImageEXIT) $(LDSCRIPT) $(MAKEDEPS) + $(LD) $(LDFLAGS) -T $(LDSCRIPT) -o $@ $< $(bImageEXIT) + +$(BIN)/%.bImage: $(BIN)/%.bImage.prf + $(OBJCOPY) -O binary $< $@ + +$(BIN)/%.bzImage: $(BIN)/%.bImage.zprf + $(OBJCOPY) -O binary $< $@ + + +# rules to generate a PXE loadable image +SUFFIXES += pxe zpxe + +$(BIN)/%.pxe.rt: $(BIN)/%.rt.o $(pxeENTRY) $(pxeEXIT) $(LDSCRIPT) $(MAKEDEPS) + $(LD) $(LDFLAGS) -T $(LDSCRIPT) -o $@ $< $(pxeEXIT) + @$(SIZE) $@ | $(CHECKSIZE) + +$(BIN)/%.pxe: $(BIN)/%.pxe.prf + $(OBJCOPY) -O binary $< $@ + +$(BIN)/%.zpxe: $(BIN)/%.pxe.zprf + $(OBJCOPY) -O binary $< $@ + +# rules to generate the .dsk/.zdsk floppy images +SUFFIXES += dsk zdsk + +$(BIN)/%.dsk.rt: $(BIN)/%.rt.o $(dskENTRY) $(dskEXIT) $(LDSCRIPT) $(MAKEDEPS) + $(LD) $(LDFLAGS) -T $(LDSCRIPT) -o $@ $< $(dskEXIT) + @$(SIZE) $@ | $(CHECKSIZE) + +$(BIN)/%.dsk: $(BIN)/%.dsk.prf + $(OBJCOPY) -O binary $< $@ + +$(BIN)/%.zdsk: $(BIN)/%.dsk.zprf + $(OBJCOPY) -O binary $< $@ + +# rules to write the .dsk/.zdsk image onto a blank floppy +SUFFIXES += fd0 zfd0 +%.fd0: %.dsk + dd if=$< bs=512 conv=sync of=/dev/fd0 + sync + +%.zfd0: %.zdsk + dd if=$< bs=512 conv=sync of=/dev/fd0 + sync + +# rules to create raw executable images +SUFFIXES += raw zraw +$(BIN)/%.raw.rt: $(BIN)/%.rt.o $(rawENTRY) $(rawEXIT) $(LDSCRIPT) $(MAKEDEPS) + $(LD) $(LDFLAGS) -T $(LDSCRIPT) -o $@ $< $(rawEXIT) + +$(BIN)/%.raw: $(BIN)/%.raw.prf + $(OBJCOPY) -O binary $< $@ + +$(BIN)/%.zraw: $(BIN)/%.raw.zprf + $(OBJCOPY) -O binary $< $@ + +# rule to make a non-emulation ISO boot image +SUFFIXES += iso +%.iso: util/geniso %.zlilo + ISOLINUX_BIN=${ISOLINUX_BIN} bash util/geniso $*.iso $*.zlilo + +# rule to make a floppy emulation ISO boot image +SUFFIXES += liso +%.liso: util/genliso %.zlilo + bash util/genliso $*.liso $*.zlilo + diff --git a/src/arch/i386/core/aout_loader.c b/src/arch/i386/core/aout_loader.c new file mode 100644 index 00000000..f85620e9 --- /dev/null +++ b/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/src/arch/i386/core/callbacks.c b/src/arch/i386/core/callbacks.c new file mode 100644 index 00000000..d45e63e2 --- /dev/null +++ b/src/arch/i386/core/callbacks.c @@ -0,0 +1,107 @@ +/* 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. + */ + +#include "etherboot.h" +#include "callbacks.h" +#include "realmode.h" +#include "segoff.h" +#include <stdarg.h> + +/* Maximum amount of stack data that prefix may request to be passed + * to its exit routine + */ +#define MAX_PREFIX_STACK_DATA 16 + +/* Prefix exit routine is defined in prefix object */ +extern void prefix_exit ( void ); +extern void prefix_exit_end ( void ); + +/***************************************************************************** + * + * IN_CALL INTERFACE + * + ***************************************************************************** + */ + +/* in_call(): entry point for calls in to Etherboot from external code. + * + * Parameters: some set up by assembly code _in_call(), others as + * passed from external code. + */ +uint32_t i386_in_call ( va_list ap, i386_pm_in_call_data_t pm_data, + uint32_t opcode ) { + uint32_t ret; + i386_rm_in_call_data_t rm_data; + in_call_data_t in_call_data = { &pm_data, NULL }; + struct { + int data[MAX_PREFIX_STACK_DATA/4]; + } in_stack; + + /* Fill out rm_data if we were called from real mode */ + if ( opcode & EB_CALL_FROM_REAL_MODE ) { + in_call_data.rm = &rm_data; + rm_data = va_arg ( ap, typeof(rm_data) ); + /* Null return address indicates to use the special + * prefix exit mechanism, and that there are + * parameters on the stack that the prefix wants + * handed to its exit routine. + */ + if ( rm_data.ret_addr.offset == 0 ) { + int n = va_arg ( ap, int ) / 4; + int i; + for ( i = 0; i < n; i++ ) { + in_stack.data[i] = va_arg ( ap, int ); + } + } + } + + /* Hand off to main in_call() routine */ + ret = in_call ( &in_call_data, opcode, ap ); + + /* If real-mode return address is null, it means that we + * should exit via the prefix's exit path, which is part of + * our image. (This arrangement is necessary since the prefix + * code itself may have been vapourised by the time we want to + * return.) + */ + if ( ( opcode & EB_CALL_FROM_REAL_MODE ) && + ( rm_data.ret_addr.offset == 0 ) ) { + real_call ( prefix_exit, &in_stack, NULL ); + /* Should never return */ + } + + return ret; +} + +#ifdef CODE16 + +/* install_rm_callback_interface(): install real-mode callback + * interface at specified address. + * + * Real-mode code may then call to this address (or lcall to this + * address plus RM_IN_CALL_FAR) in order to make an in_call() to + * Etherboot. + * + * Returns the size of the installed code, or 0 if the code could not + * be installed. + */ +int install_rm_callback_interface ( void *address, size_t available ) { + if ( available && + ( available < rm_callback_interface_size ) ) return 0; + + /* Inform RM code where to find Etherboot */ + rm_etherboot_location = virt_to_phys(_text); + + /* Install callback interface */ + memcpy ( address, &rm_callback_interface, + rm_callback_interface_size ); + + return rm_callback_interface_size; +} + +#endif /* CODE16 */ diff --git a/src/arch/i386/core/cpu.c b/src/arch/i386/core/cpu.c new file mode 100644 index 00000000..8a0f7333 --- /dev/null +++ b/src/arch/i386/core/cpu.c @@ -0,0 +1,86 @@ +#ifdef CONFIG_X86_64 +#include "stdint.h" +#include "string.h" +#include "bits/cpu.h" + + +/* Standard macro to see if a specific flag is changeable */ +static inline int flag_is_changeable_p(uint32_t 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; +} + + +/* Probe for the CPUID instruction */ +static inline int have_cpuid_p(void) +{ + return flag_is_changeable_p(X86_EFLAGS_ID); +} + +static void identify_cpu(struct cpuinfo_x86 *c) +{ + unsigned xlvl; + + c->cpuid_level = -1; /* CPUID not detected */ + c->x86_model = c->x86_mask = 0; /* So far unknown... */ + c->x86_vendor_id[0] = '\0'; /* Unset */ + memset(&c->x86_capability, 0, sizeof c->x86_capability); + + if (!have_cpuid_p()) { + /* CPU doesn'thave CPUID */ + + /* If there are any capabilities, they'r vendor-specific */ + /* enable_cpuid() would have set c->x86 for us. */ + } + else { + /* CPU does have CPUID */ + + /* Get vendor name */ + cpuid(0x00000000, &c->cpuid_level, + (int *)&c->x86_vendor_id[0], + (int *)&c->x86_vendor_id[8], + (int *)&c->x86_vendor_id[4]); + + /* Initialize the standard set of capabilities */ + /* Note that the vendor-specific code below might override */ + + /* Intel-defined flags: level 0x00000001 */ + if ( c->cpuid_level >= 0x00000001 ) { + unsigned tfms, junk; + cpuid(0x00000001, &tfms, &junk, &junk, + &c->x86_capability[0]); + c->x86 = (tfms >> 8) & 15; + c->x86_model = (tfms >> 4) & 15; + c->x86_mask = tfms & 15; + } + + /* AMD-defined flags: level 0x80000001 */ + xlvl = cpuid_eax(0x80000000); + if ( (xlvl & 0xffff0000) == 0x80000000 ) { + if ( xlvl >= 0x80000001 ) + c->x86_capability[1] = cpuid_edx(0x80000001); + } + } +} + +struct cpuinfo_x86 cpu_info; +void cpu_setup(void) +{ + identify_cpu(&cpu_info); +} +#endif /* CONFIG_X86_64 */ diff --git a/src/arch/i386/core/elf.c b/src/arch/i386/core/elf.c new file mode 100644 index 00000000..6ef78c71 --- /dev/null +++ b/src/arch/i386/core/elf.c @@ -0,0 +1,135 @@ +#include "etherboot.h" +#include "elf.h" + + +#define NAME "Etherboot" + +#if defined(PCBIOS) +#define FIRMWARE "PCBIOS" +#endif +#if defined(LINUXBIOS) +#define FIRMWARE "LinuxBIOS" +#endif +#if !defined(FIRMWARE) +#error "No BIOS selected" +#endif + +#define SZ(X) ((sizeof(X)+3) & ~3) +#define CP(D,S) (memcpy(&(D), &(S), sizeof(S))) + +struct elf_notes { + /* The note header */ + struct Elf_Bhdr hdr; + + /* First the Fixed sized entries that must be well aligned */ + + /* Pointer to bootp data */ + Elf_Nhdr nf1; + char nf1_name[SZ(EB_PARAM_NOTE)]; + uint32_t nf1_bootp_data; + + /* Pointer to ELF header */ + Elf_Nhdr nf2; + char nf2_name[SZ(EB_PARAM_NOTE)]; + uint32_t nf2_header; + + /* A copy of the i386 memory map */ + Elf_Nhdr nf3; + char nf3_name[SZ(EB_PARAM_NOTE)]; + struct meminfo nf3_meminfo; + + /* Then the variable sized data string data where alignment does not matter */ + + /* The bootloader name */ + Elf_Nhdr nv1; + char nv1_desc[SZ(NAME)]; + /* The bootloader version */ + Elf_Nhdr nv2; + char nv2_desc[SZ(VERSION)]; + /* The firmware type */ + Elf_Nhdr nv3; + char nv3_desc[SZ(FIRMWARE)]; + /* Name of the loaded image */ + Elf_Nhdr nv4; + char nv4_loaded_image[128]; + /* An empty command line */ + Elf_Nhdr nv5; + char nv5_cmdline[SZ("")]; +}; + +#define ELF_NOTE_COUNT (3 + 5) + +static struct elf_notes notes; +struct Elf_Bhdr *prepare_boot_params(void *header) +{ + memset(¬es, 0, sizeof(notes)); + notes.hdr.b_signature = ELF_BHDR_MAGIC; + notes.hdr.b_size = sizeof(notes); + notes.hdr.b_checksum = 0; + notes.hdr.b_records = ELF_NOTE_COUNT; + + /* Initialize the fixed length entries. */ + notes.nf1.n_namesz = sizeof(EB_PARAM_NOTE); + notes.nf1.n_descsz = sizeof(notes.nf1_bootp_data); + notes.nf1.n_type = EB_BOOTP_DATA; + CP(notes.nf1_name, EB_PARAM_NOTE); + notes.nf1_bootp_data = virt_to_phys(BOOTP_DATA_ADDR); + + notes.nf2.n_namesz = sizeof(EB_PARAM_NOTE); + notes.nf2.n_descsz = sizeof(notes.nf2_header); + notes.nf2.n_type = EB_HEADER; + CP(notes.nf2_name, EB_PARAM_NOTE); + notes.nf2_header = virt_to_phys(header); + + notes.nf3.n_namesz = sizeof(EB_PARAM_NOTE); + notes.nf3.n_descsz = sizeof(notes.nf3_meminfo); + notes.nf3.n_type = EB_I386_MEMMAP; + CP(notes.nf3_name, EB_PARAM_NOTE); + memcpy(¬es.nf3_meminfo, &meminfo, sizeof(meminfo)); + + /* Initialize the variable length entries */ + notes.nv1.n_namesz = 0; + notes.nv1.n_descsz = sizeof(NAME); + notes.nv1.n_type = EBN_BOOTLOADER_NAME; + CP(notes.nv1_desc, NAME); + + notes.nv2.n_namesz = 0; + notes.nv2.n_descsz = sizeof(VERSION); + notes.nv2.n_type = EBN_BOOTLOADER_VERSION; + CP(notes.nv2_desc, VERSION); + + notes.nv3.n_namesz = 0; + notes.nv3.n_descsz = sizeof(FIRMWARE); + notes.nv3.n_type = EBN_FIRMWARE_TYPE; + CP(notes.nv3_desc, FIRMWARE); + + /* Attempt to pass the name of the loaded image */ + notes.nv4.n_namesz = 0; + notes.nv4.n_descsz = sizeof(notes.nv4_loaded_image); + notes.nv4.n_type = EBN_LOADED_IMAGE; + memcpy(¬es.nv4_loaded_image, KERNEL_BUF, sizeof(notes.nv4_loaded_image)); + + /* Pass an empty command line for now */ + notes.nv5.n_namesz = 0; + notes.nv5.n_descsz = sizeof(""); + notes.nv5.n_type = EBN_COMMAND_LINE; + CP(notes.nv5_cmdline, ""); + + + notes.hdr.b_checksum = ipchksum(¬es, sizeof(notes)); + /* Like UDP invert a 0 checksum to show that a checksum is present */ + if (notes.hdr.b_checksum == 0) { + notes.hdr.b_checksum = 0xffff; + } + return ¬es.hdr; +} + +int elf_start(unsigned long machine __unused_i386, unsigned long entry, unsigned long params) +{ +#if defined(CONFIG_X86_64) + if (machine == EM_X86_64) { + return xstart_lm(entry, params); + } +#endif + return xstart32(entry, params); +} diff --git a/src/arch/i386/core/etherboot.lds b/src/arch/i386/core/etherboot.lds new file mode 100644 index 00000000..6f406329 --- /dev/null +++ b/src/arch/i386/core/etherboot.lds @@ -0,0 +1,90 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) + +ENTRY(_text) +SECTIONS { + . = ALIGN(16); + /* Start address of Etherboot in the virtual address space */ + _virt_start = 0; + _text = . ; + .text.nocompress : { + *(.text*.nocompress) + . = ALIGN(16); + } = 0x9090 + + .text16 : { + _text16 = .; + *(.text16) + *(.text16.*) + _etext16 = . ; + } + .text.compress : { + *(.text) + *(.text.*) + } = 0x9090 + .rodata : { + . = ALIGN(4); + *(.rodata) + *(.rodata.*) + } + . = ALIGN(4); + .drivers.pci : { + pci_drivers = . ; + *(.drivers.pci); + pci_drivers_end = . ; + } + . = ALIGN(4); + .drivers.isa : { + isa_drivers = . ; + *(.drivers.isa); + isa_drivers_end = .; + } + _etext = . ; + _data = . ; + .data : { + *(.data) + *(.data.*) + } + _edata = . ; + _uncompressed_verbatim_end = . ; + . = ALIGN(16); + .bss.preserve : { + *(.bss.preserve) + *(.bss.preserve.*) + } + _bss = . ; + .bss : { + *(.bss) + *(.bss.*) + } + . = ALIGN(16); + _ebss = .; + _stack = . ; + .stack : { + _stack_start = . ; + *(.stack) + *(.stack.*) + _stack_end = . ; + } + _bss_size = _ebss - _bss; + _stack_offset = _stack - _text ; + _stack_offset_pgh = _stack_offset / 16 ; + _stack_size = _stack_end - _stack_start ; + . = ALIGN(16); + _end = . ; + + /DISCARD/ : { + *(.comment) + *(.note) + } + + /* PXE-specific symbol calculations. The results of these are + * needed in romprefix.S, which is why they must be calculated + * here. + */ + _pxe_stack_size = _pxe_stack_t_size + + _pxe_callback_interface_size + + _rm_callback_interface_size + + _e820mangler_size + 15 ; + +} diff --git a/src/arch/i386/core/etherboot.prefix.lds b/src/arch/i386/core/etherboot.prefix.lds new file mode 100644 index 00000000..3550a2a3 --- /dev/null +++ b/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/src/arch/i386/core/freebsd_loader.c b/src/arch/i386/core/freebsd_loader.c new file mode 100644 index 00000000..4e820e8e --- /dev/null +++ b/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 = phys_to_virt(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 = phys_to_virt(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 *)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/src/arch/i386/core/hooks.c b/src/arch/i386/core/hooks.c new file mode 100644 index 00000000..9ca6f480 --- /dev/null +++ b/src/arch/i386/core/hooks.c @@ -0,0 +1,35 @@ +#include "etherboot.h" +#include "callbacks.h" +#include <stdarg.h> + +void arch_main ( in_call_data_t *data __unused, va_list params __unused ) +{ +#ifdef PCBIOS + /* Deallocate base memory used for the prefix, if applicable + */ + forget_prefix_base_memory(); +#endif + +} + +void arch_relocated_from (unsigned long old_addr ) +{ + +#ifdef PCBIOS + /* Deallocate base memory used for the Etherboot runtime, + * if applicable + */ + forget_runtime_base_memory( old_addr ); +#endif + +} + +void arch_on_exit ( int exit_status __unused ) +{ +#ifdef PCBIOS + /* Deallocate the real-mode stack now. We will reallocate + * the stack if are going to use it after this point. + */ + forget_real_mode_stack(); +#endif +} diff --git a/src/arch/i386/core/i386_timer.c b/src/arch/i386/core/i386_timer.c new file mode 100644 index 00000000..531183d4 --- /dev/null +++ b/src/arch/i386/core/i386_timer.c @@ -0,0 +1,191 @@ +/* A couple of routines to implement a low-overhead timer for drivers */ + + /* + * 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 "etherboot.h" +#include "timer.h" +#include "latch.h" + +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); +} + +#if !defined(CONFIG_TSC_CURRTICKS) +void setup_timers(void) +{ + return; +} + +void load_timer2(unsigned int ticks) +{ + return __load_timer2(ticks); +} + +int timer2_running(void) +{ + return __timer2_running(); +} + +void ndelay(unsigned int nsecs) +{ + waiton_timer2((nsecs * CLOCK_TICK_RATE)/1000000000); +} +void udelay(unsigned int usecs) +{ + waiton_timer2((usecs * TICKS_PER_MS)/1000); +} +#endif /* !defined(CONFIG_TSC_CURRTICKS) */ + +#if defined(CONFIG_TSC_CURRTICKS) + +#define rdtsc(low,high) \ + __asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high)) + +#define rdtscll(val) \ + __asm__ __volatile__ ("rdtsc" : "=A" (val)) + + +/* Number of clock ticks to time with the rtc */ +#define LATCH 0xFF + +#define LATCHES_PER_SEC ((CLOCK_TICK_RATE + (LATCH/2))/LATCH) +#define TICKS_PER_LATCH ((LATCHES_PER_SEC + (TICKS_PER_SEC/2))/TICKS_PER_SEC) + +static void sleep_latch(void) +{ + __load_timer2(LATCH); + while(__timer2_running()); +} + +/* ------ Calibrate the TSC ------- + * Time how long it takes to excute a loop that runs in known time. + * And find the convertion needed to get to CLOCK_TICK_RATE + */ + + +static unsigned long long calibrate_tsc(void) +{ + unsigned long startlow, starthigh; + unsigned long endlow, endhigh; + + rdtsc(startlow,starthigh); + sleep_latch(); + rdtsc(endlow,endhigh); + + /* 64-bit subtract - gcc just messes up with long longs */ + __asm__("subl %2,%0\n\t" + "sbbl %3,%1" + :"=a" (endlow), "=d" (endhigh) + :"g" (startlow), "g" (starthigh), + "0" (endlow), "1" (endhigh)); + + /* Error: ECPUTOOFAST */ + if (endhigh) + goto bad_ctc; + + endlow *= TICKS_PER_LATCH; + return endlow; + + /* + * The CTC wasn't reliable: we got a hit on the very first read, + * or the CPU was so fast/slow that the quotient wouldn't fit in + * 32 bits.. + */ +bad_ctc: + printf("bad_ctc\n"); + return 0; +} + +static unsigned long clocks_per_tick; +void setup_timers(void) +{ + if (!clocks_per_tick) { + clocks_per_tick = calibrate_tsc(); + /* Display the CPU Mhz to easily test if the calibration was bad */ + printf("CPU %ld Mhz\n", (clocks_per_tick/1000 * TICKS_PER_SEC)/1000); + } +} + +unsigned long currticks(void) +{ + unsigned long clocks_high, clocks_low; + unsigned long currticks; + /* Read the Time Stamp Counter */ + rdtsc(clocks_low, clocks_high); + + /* currticks = clocks / clocks_per_tick; */ + __asm__("divl %1" + :"=a" (currticks) + :"r" (clocks_per_tick), "0" (clocks_low), "d" (clocks_high)); + + + return currticks; +} + +static unsigned long long timer_timeout; +static int __timer_running(void) +{ + unsigned long long now; + rdtscll(now); + return now < timer_timeout; +} + +void udelay(unsigned int usecs) +{ + unsigned long long now; + rdtscll(now); + timer_timeout = now + usecs * ((clocks_per_tick * TICKS_PER_SEC)/(1000*1000)); + while(__timer_running()); +} +void ndelay(unsigned int nsecs) +{ + unsigned long long now; + rdtscll(now); + timer_timeout = now + nsecs * ((clocks_per_tick * TICKS_PER_SEC)/(1000*1000*1000)); + while(__timer_running()); +} + +void load_timer2(unsigned int timer2_ticks) +{ + unsigned long long now; + unsigned long clocks; + rdtscll(now); + clocks = timer2_ticks * ((clocks_per_tick * TICKS_PER_SEC)/CLOCK_TICK_RATE); + timer_timeout = now + clocks; +} + +int timer2_running(void) +{ + return __timer_running(); +} + +#endif /* RTC_CURRTICKS */ diff --git a/src/arch/i386/core/init.S b/src/arch/i386/core/init.S new file mode 100644 index 00000000..71717c25 --- /dev/null +++ b/src/arch/i386/core/init.S @@ -0,0 +1,305 @@ +#include "callbacks.h" + .equ CR0_PE, 1 + + .text + .arch i386 + .section ".prefix", "ax", @progbits + +#undef CODE16 +#if defined(PCBIOS) +#define CODE16 +#endif + +/* We have two entry points: "conventional" (at the start of the file) + * and "callback" (at _entry, 2 bytes in). The "callback" entry + * should be used if the caller wishes to provide a specific opcode. + * It is equivalent to a call to in_call. Using the "conventional" + * entry point is equivalent to using the "callback" entry point with + * an opcode of EB_OPCODE_MAIN. + * + * Both entry points can be called in either 16-bit real or 32-bit + * protected mode with flat physical addresses. We detect which mode + * the processor is in and call either in_call or rm_in_call as + * appropriate. Note that the mode detection code must therefore be + * capable of doing the same thing in either mode, even though the + * machine code instructions will be interpreted differently. + * + * The decompressor will be invoked if necessary to decompress + * Etherboot before attempting to jump to it. + */ + +/****************************************************************************** + * Entry points and mode detection code + ****************************************************************************** + */ + + .code32 +/* "Conventional" entry point: caller provides no opcode */ + .globl _start +_start: + /* Set flag to indicate conventional entry point used */ + pushl $0 /* "pushw $0" in 16-bit code */ + /* Fall through to "callback" entry point */ + +/* "Callback" entry point */ + .globl _entry +_entry: + +#ifdef CODE16 + /* CPU mode detection code */ + pushl %eax /* "pushw %ax" in 16-bit code */ + pushw %ax /* "pushl %eax" in 16-bit code */ + movl %cr0, %eax /* Test protected mode bit */ + testb $CR0_PE, %al + popw %ax /* "popl %eax" in 16-bit code */ + popl %eax /* "popw %eax" in 16-bit code */ + jz rmode +#endif /* CODE16 */ + +/****************************************************************************** + * Entered in protected mode + ****************************************************************************** + */ + + .code32 +pmode: + cmpl $0, 0(%esp) /* Conventional entry point used? */ + jne 1f + /* Entered via conventional entry point: set up stack */ + xchgl %eax, 4(%esp) /* %eax = return addr, store %eax */ + movl %eax, 0(%esp) /* 0(%esp) = return address */ + movl $(EB_OPCODE_MAIN|EB_USE_INTERNAL_STACK|EB_SKIP_OPCODE), %eax + xchgl %eax, 4(%esp) /* 4(%esp) = opcode, restore %eax */ +1: + /* Run decompressor if necessary */ + pushl %eax + movl $decompress, %eax + testl %eax, %eax + jz 1f + call decompress +1: popl %eax + + /* Make in_call to Etherboot */ + jmp _prefix_in_call + +/****************************************************************************** + * Entered in real mode + ****************************************************************************** + */ + +#ifdef CODE16 + .code16 +rmode: + pushw %ax /* Padding */ + pushw %bp + movw %sp, %bp + cmpw $0, 6(%bp) /* Conventional entry point used? */ + jne 1f + /* Entered via conventional entry point: set up stack */ + pushw %ax + movw 6(%bp), %ax + movw %ax, 2(%bp) /* Move return address down */ + movl $(EB_OPCODE_MAIN|EB_USE_INTERNAL_STACK|EB_SKIP_OPCODE), 4(%bp) + popw %ax + popw %bp + jmp 2f +1: /* Entered via callback entry point: do nothing */ + popw %bp + popw %ax +2: + /* Preserve registers */ + pushw %ds + pushl %eax + + /* Run decompressor if necessary. Decompressor is 32-bit + * code, so we must switch to pmode first. Save and restore + * GDT over transition to pmode. + */ + movl $decompress, %eax + testl %eax, %eax + jz 1f + pushw %ds + pushw %es + pushw %fs + pushw %gs + subw $8, %sp + pushw %bp + movw %sp, %bp + sgdt 2(%bp) + pushw %ss /* Store params for _prot_to_real */ + pushw %cs + call _prefix_real_to_prot + .code32 + call decompress + call _prefix_prot_to_real + .code16 + popw %ax /* skip */ + popw %ax /* skip */ + lgdt 2(%bp) + popw %bp + addw $8, %sp + popw %gs + popw %fs + popw %es + popw %ds +1: + + /* Set rm_etherboot_location */ + xorl %eax, %eax + movw %cs, %ax + movw %ax, %ds + shll $4, %eax + addl $_prefix_size, %eax + movl %eax, _prefix_rm_etherboot_location + + /* Restore registers */ + popl %eax + popw %ds + + /* Make real-mode in_call to Etherboot */ + jmp _prefix_rm_in_call +#endif /* CODE16 */ + +/****************************************************************************** + * Utility routines that can be called by the "prefix". + ****************************************************************************** + */ + +#ifdef CODE16 + +/* Prelocate code: either to an area at the top of free base memory. + * Switch stacks to use the stack within the resulting + * Etherboot image. + * + * On entry, %cs:0000 must be the start of the prefix: this is used to + * locate the code to be copied. + * + * This routine takes a single word parameter: the number of bytes to + * be transferred from the old stack to the new stack (excluding the + * return address and this parameter itself, which will always be + * copied). If this value is negative, the stacks will not be + * switched. + * + * Control will "return" to the appropriate point in the relocated + * image. + */ + +#define PRELOC_PRESERVE ( 20 ) +#define PRELOC_OFFSET_RETADDR ( PRELOC_PRESERVE ) +#define PRELOC_OFFSET_RETADDR_E ( PRELOC_OFFSET_RETADDR + 4 ) +#define PRELOC_OFFSET_COPY ( PRELOC_OFFSET_RETADDR_E ) +#define PRELOC_OFFSET_COPY_E ( PRELOC_OFFSET_COPY + 2 ) + +#define PRELOC_ALWAYS_COPY ( PRELOC_OFFSET_COPY_E ) + + .code16 + .globl prelocate +prelocate: + /* Pad to allow for expansion of return address */ + pushw %ax + + /* Preserve registers */ + pushaw + pushw %ds + pushw %es + + /* Claim an area of base memory from the BIOS and put the + * payload there. + */ + movw $0x40, %bx + movw %bx, %es + movw %es:(0x13), %bx /* FBMS in kb to %ax */ + shlw $6, %bx /* ... in paragraphs */ + subw $_image_size_pgh, %bx /* Subtract space for image */ + shrw $6, %bx /* Round down to nearest kb */ + movw %bx, %es:(0x13) /* ...and claim memory from BIOS */ + shlw $6, %bx + + /* At this point %bx contains the segment address for the + * start of the image (image = prefix + runtime). + */ + + /* Switch stacks */ + movw %ss, %ax + movw %ax, %ds + movw %sp, %si /* %ds:si = current %ss:sp */ + movw %ss:PRELOC_OFFSET_COPY(%si), %cx + testw %cx, %cx + js 1f + leaw _stack_offset_pgh(%bx), %ax /* %ax = new %ss */ + movw %ax, %es + movw $_stack_size, %di + addw $PRELOC_ALWAYS_COPY, %cx + subw %cx, %di /* %es:di = new %ss:sp */ + movw %ax, %ss /* Set new %ss:sp */ + movw %di, %sp + cld + rep movsb /* Copy stack contents */ +1: + + /* Do the image copy backwards, since if there's overlap with + * a forward copy then it means we're going to get trashed + * during the copy anyway... + */ + pushal /* Preserve 32-bit registers */ + movw %bx, %es /* Destination base for copy */ + pushw %cs + popw %ds /* Source base for copy */ + movl $_verbatim_size-1, %ecx /* Offset to last byte */ + movl %ecx, %esi + movl %ecx, %edi + incl %ecx /* Length */ + std /* Backwards copy of binary */ + ADDR32 rep movsb + cld + popal /* Restore 32-bit registers */ + + /* Store (%bx<<4) as image_basemem to be picked up by + * basemem.c. Also store image_size, since there's no other + * way that we can later know how much memory we allocated. + * (_zfile_size is unavailable when rt2 is linked). + */ + pushl %eax + xorl %eax, %eax + movw %bx, %ax + shll $4, %eax + movl %eax, %es:_prefix_image_basemem + movl $_image_size, %es:_prefix_image_basemem_size + popl %eax + + /* Expand original near return address into far return to new + * code location. + */ + movw %sp, %bp + xchgw %bx, (PRELOC_OFFSET_RETADDR+2)(%bp) + movw %bx, (PRELOC_OFFSET_RETADDR+0)(%bp) + + /* Restore registers and return */ + popw %es + popw %ds + popaw + lret /* Jump to relocated code */ + + /* Utility routine to free base memory allocated by prelocate. + * Ensure that said memory is not in use (e.g. for the CPU + * stack) before calling this routine. + */ + .globl deprelocate +deprelocate: + /* Claim an area of base memory from the BIOS and put the + * payload there. + */ + pushw %ax + pushw %es + movw $0x40, %ax + movw %ax, %es + movw %es:(0x13), %ax /* FBMS in kb to %ax */ + shlw $6, %ax /* ... in paragraphs */ + addw $_image_size_pgh+0x40-1, %ax /* Add space for image and... */ + shrw $6, %ax /* ...round up to nearest kb */ + movw %ax, %es:(0x13) /* Give memory back to BIOS */ + popw %es + popw %ax + ret + +#endif /* CODE16 */ diff --git a/src/arch/i386/core/multiboot_loader.c b/src/arch/i386/core/multiboot_loader.c new file mode 100644 index 00000000..e9785f1c --- /dev/null +++ b/src/arch/i386/core/multiboot_loader.c @@ -0,0 +1,143 @@ +/* Multiboot support + * + * 2003-07-02 mmap fix and header probe by SONE Takeshi + */ + +struct multiboot_mods { + unsigned mod_start; + unsigned mod_end; + unsigned char *string; + unsigned reserved; +}; + +struct multiboot_mmap { + unsigned int size; + unsigned int base_addr_low; + unsigned int base_addr_high; + unsigned int length_low; + unsigned int length_high; + unsigned int type; +}; + +/* The structure of a Multiboot 0.6 parameter block. */ +struct multiboot_info { + unsigned int flags; +#define MULTIBOOT_MEM_VALID 0x01 +#define MULTIBOOT_BOOT_DEV_VALID 0x02 +#define MULTIBOOT_CMDLINE_VALID 0x04 +#define MULTIBOOT_MODS_VALID 0x08 +#define MULTIBOOT_AOUT_SYMS_VALID 0x10 +#define MULTIBOOT_ELF_SYMS_VALID 0x20 +#define MULTIBOOT_MMAP_VALID 0x40 + unsigned int memlower; + unsigned int memupper; + unsigned int bootdev; + unsigned int cmdline; /* physical address of the command line */ + unsigned mods_count; + struct multiboot_mods *mods_addr; + unsigned syms_num; + unsigned syms_size; + unsigned syms_addr; + unsigned syms_shndx; + unsigned mmap_length; + unsigned mmap_addr; + /* The structure actually ends here, so I might as well put + * the ugly e820 parameters here... + */ + struct multiboot_mmap mmap[E820MAX]; +}; + +/* Multiboot image header (minimal part) */ +struct multiboot_header { + unsigned int magic; +#define MULTIBOOT_HEADER_MAGIC 0x1BADB002 + unsigned int flags; + unsigned int checksum; +}; + +static struct multiboot_header *mbheader; + +static struct multiboot_info mbinfo; + +static void multiboot_probe(unsigned char *data, int len) +{ + int offset; + struct multiboot_header *h; + + /* Multiboot spec requires the header to be in first 8KB of the image */ + if (len > 8192) + len = 8192; + + for (offset = 0; offset < len; offset += 4) { + h = (struct multiboot_header *) (data + offset); + if (h->magic == MULTIBOOT_HEADER_MAGIC + && h->magic + h->flags + h->checksum == 0) { + printf("/Multiboot"); + mbheader = h; + return; + } + } + mbheader = 0; +} + +static inline void multiboot_boot(unsigned long entry) +{ + unsigned char cmdline[512], *c; + int i; + if (!mbheader) + return; + /* Etherboot limits the command line to the kernel name, + * default parameters and user prompted parameters. All of + * them are shorter than 256 bytes. As the kernel name and + * the default parameters come from the same BOOTP/DHCP entry + * (or if they don't, the parameters are empty), only two + * strings of the maximum size are possible. Note this buffer + * can overrun if a stupid file name is chosen. Oh well. */ + c = cmdline; + for (i = 0; KERNEL_BUF[i] != 0; i++) { + switch (KERNEL_BUF[i]) { + case ' ': + case '\\': + case '"': + *c++ = '\\'; + break; + default: + break; + } + *c++ = KERNEL_BUF[i]; + } + (void)sprintf(c, " -retaddr %#lX", virt_to_phys(xend32)); + + mbinfo.flags = MULTIBOOT_MMAP_VALID | MULTIBOOT_MEM_VALID |MULTIBOOT_CMDLINE_VALID; + mbinfo.memlower = meminfo.basememsize; + mbinfo.memupper = meminfo.memsize; + mbinfo.bootdev = 0; /* not booted from disk */ + mbinfo.cmdline = virt_to_phys(cmdline); + for (i = 0; i < (int) meminfo.map_count; i++) { + mbinfo.mmap[i].size = sizeof(struct multiboot_mmap) + - sizeof(unsigned int); + mbinfo.mmap[i].base_addr_low = + (unsigned int) meminfo.map[i].addr; + mbinfo.mmap[i].base_addr_high = + (unsigned int) (meminfo.map[i].addr >> 32); + mbinfo.mmap[i].length_low = + (unsigned int) meminfo.map[i].size; + mbinfo.mmap[i].length_high = + (unsigned int) (meminfo.map[i].size >> 32); + mbinfo.mmap[i].type = meminfo.map[i].type; + } + mbinfo.mmap_length = meminfo.map_count * sizeof(struct multiboot_mmap); + mbinfo.mmap_addr = virt_to_phys(mbinfo.mmap); + + /* The Multiboot 0.6 spec requires all segment registers to be + * loaded with an unrestricted, writeable segment. + * xstart32 does this for us. + */ + + /* Start the kernel, passing the Multiboot information record + * and the magic number. */ + os_regs.eax = 0x2BADB002; + os_regs.ebx = virt_to_phys(&mbinfo); + xstart32(entry); + longjmp(restart_etherboot, -2); +} diff --git a/src/arch/i386/core/pci_io.c b/src/arch/i386/core/pci_io.c new file mode 100644 index 00000000..e89561e4 --- /dev/null +++ b/src/arch/i386/core/pci_io.c @@ -0,0 +1,352 @@ +/* +** Support for NE2000 PCI clones added David Monro June 1997 +** Generalised to other NICs by Ken Yap July 1997 +** +** Most of this is taken from: +** +** /usr/src/linux/drivers/pci/pci.c +** /usr/src/linux/include/linux/pci.h +** /usr/src/linux/arch/i386/bios32.c +** /usr/src/linux/include/linux/bios32.h +** /usr/src/linux/drivers/net/ne.c +*/ +#ifdef CONFIG_PCI +#include "etherboot.h" +#include "pci.h" + +#ifdef CONFIG_PCI_DIRECT +#define PCIBIOS_SUCCESSFUL 0x00 + +#define DEBUG 0 + +/* + * Functions for accessing PCI configuration space with type 1 accesses + */ + +#define CONFIG_CMD(bus, device_fn, where) (0x80000000 | (bus << 16) | (device_fn << 8) | (where & ~3)) + +int pcibios_read_config_byte(unsigned int bus, unsigned int device_fn, + unsigned int where, uint8_t *value) +{ + outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); + *value = inb(0xCFC + (where&3)); + return PCIBIOS_SUCCESSFUL; +} + +int pcibios_read_config_word (unsigned int bus, + unsigned int device_fn, unsigned int where, uint16_t *value) +{ + outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); + *value = inw(0xCFC + (where&2)); + return PCIBIOS_SUCCESSFUL; +} + +int pcibios_read_config_dword (unsigned int bus, unsigned int device_fn, + unsigned int where, uint32_t *value) +{ + outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); + *value = inl(0xCFC); + return PCIBIOS_SUCCESSFUL; +} + +int pcibios_write_config_byte (unsigned int bus, unsigned int device_fn, + unsigned int where, uint8_t value) +{ + outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); + outb(value, 0xCFC + (where&3)); + return PCIBIOS_SUCCESSFUL; +} + +int pcibios_write_config_word (unsigned int bus, unsigned int device_fn, + unsigned int where, uint16_t value) +{ + outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); + outw(value, 0xCFC + (where&2)); + return PCIBIOS_SUCCESSFUL; +} + +int pcibios_write_config_dword (unsigned int bus, unsigned int device_fn, unsigned int where, uint32_t value) +{ + outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); + outl(value, 0xCFC); + return PCIBIOS_SUCCESSFUL; +} + +#undef CONFIG_CMD + +#else /* CONFIG_PCI_DIRECT not defined */ + +#if !defined(PCBIOS) +#error "The pcibios can only be used when the PCBIOS support is compiled in" +#endif + +/* Macro for calling the BIOS32 service. This replaces the old + * bios32_call function. Use in a statement such as + * __asm__ ( BIOS32_CALL, + * : <output registers> + * : "S" ( bios32_entry ), <other input registers> ); + */ +#define BIOS32_CALL "call _virt_to_phys\n\t" \ + "pushl %%cs\n\t" \ + "call *%%esi\n\t" \ + "cli\n\t" \ + "cld\n\t" \ + "call _phys_to_virt\n\t" + +static unsigned long bios32_entry; +static unsigned long pcibios_entry; + +static unsigned long bios32_service(unsigned long service) +{ + unsigned char return_code; /* %al */ + unsigned long address; /* %ebx */ + unsigned long length; /* %ecx */ + unsigned long entry; /* %edx */ + + __asm__(BIOS32_CALL + : "=a" (return_code), + "=b" (address), + "=c" (length), + "=d" (entry) + : "0" (service), + "1" (0), + "S" (bios32_entry)); + + switch (return_code) { + case 0: + return address + entry; + case 0x80: /* Not present */ + printf("bios32_service(%d) : not present\n", service); + return 0; + default: /* Shouldn't happen */ + printf("bios32_service(%d) : returned %#X????\n", + service, return_code); + return 0; + } +} + +int pcibios_read_config_byte(unsigned int bus, + unsigned int device_fn, unsigned int where, uint8_t *value) +{ + unsigned long ret; + unsigned long bx = (bus << 8) | device_fn; + + __asm__(BIOS32_CALL + "jc 1f\n\t" + "xor %%ah, %%ah\n" + "1:" + : "=c" (*value), + "=a" (ret) + : "1" (PCIBIOS_READ_CONFIG_BYTE), + "b" (bx), + "D" ((long) where), + "S" (pcibios_entry)); + return (int) (ret & 0xff00) >> 8; +} + +int pcibios_read_config_word(unsigned int bus, + unsigned int device_fn, unsigned int where, uint16_t *value) +{ + unsigned long ret; + unsigned long bx = (bus << 8) | device_fn; + + __asm__(BIOS32_CALL + "jc 1f\n\t" + "xor %%ah, %%ah\n" + "1:" + : "=c" (*value), + "=a" (ret) + : "1" (PCIBIOS_READ_CONFIG_WORD), + "b" (bx), + "D" ((long) where), + "S" (pcibios_entry)); + return (int) (ret & 0xff00) >> 8; +} + +int pcibios_read_config_dword(unsigned int bus, + unsigned int device_fn, unsigned int where, uint32_t *value) +{ + unsigned long ret; + unsigned long bx = (bus << 8) | device_fn; + + __asm__(BIOS32_CALL + "jc 1f\n\t" + "xor %%ah, %%ah\n" + "1:" + : "=c" (*value), + "=a" (ret) + : "1" (PCIBIOS_READ_CONFIG_DWORD), + "b" (bx), + "D" ((long) where), + "S" (pcibios_entry)); + return (int) (ret & 0xff00) >> 8; +} + +int pcibios_write_config_byte (unsigned int bus, + unsigned int device_fn, unsigned int where, uint8_t value) +{ + unsigned long ret; + unsigned long bx = (bus << 8) | device_fn; + + __asm__(BIOS32_CALL + "jc 1f\n\t" + "xor %%ah, %%ah\n" + "1:" + : "=a" (ret) + : "0" (PCIBIOS_WRITE_CONFIG_BYTE), + "c" (value), + "b" (bx), + "D" ((long) where), + "S" (pcibios_entry)); + return (int) (ret & 0xff00) >> 8; +} + +int pcibios_write_config_word (unsigned int bus, + unsigned int device_fn, unsigned int where, uint16_t value) +{ + unsigned long ret; + unsigned long bx = (bus << 8) | device_fn; + + __asm__(BIOS32_CALL + "jc 1f\n\t" + "xor %%ah, %%ah\n" + "1:" + : "=a" (ret) + : "0" (PCIBIOS_WRITE_CONFIG_WORD), + "c" (value), + "b" (bx), + "D" ((long) where), + "S" (pcibios_entry)); + return (int) (ret & 0xff00) >> 8; +} + +int pcibios_write_config_dword (unsigned int bus, + unsigned int device_fn, unsigned int where, uint32_t value) +{ + unsigned long ret; + unsigned long bx = (bus << 8) | device_fn; + + __asm__(BIOS32_CALL + "jc 1f\n\t" + "xor %%ah, %%ah\n" + "1:" + : "=a" (ret) + : "0" (PCIBIOS_WRITE_CONFIG_DWORD), + "c" (value), + "b" (bx), + "D" ((long) where), + "S" (pcibios_entry)); + return (int) (ret & 0xff00) >> 8; +} + +static void check_pcibios(void) +{ + unsigned long signature; + unsigned char present_status; + unsigned char major_revision; + unsigned char minor_revision; + int pack; + + if ((pcibios_entry = bios32_service(PCI_SERVICE))) { + __asm__(BIOS32_CALL + "jc 1f\n\t" + "xor %%ah, %%ah\n" + "1:\tshl $8, %%eax\n\t" + "movw %%bx, %%ax" + : "=d" (signature), + "=a" (pack) + : "1" (PCIBIOS_PCI_BIOS_PRESENT), + "S" (pcibios_entry) + : "bx", "cx"); + + present_status = (pack >> 16) & 0xff; + major_revision = (pack >> 8) & 0xff; + minor_revision = pack & 0xff; + if (present_status || (signature != PCI_SIGNATURE)) { + printf("ERROR: BIOS32 says PCI BIOS, but no PCI " + "BIOS????\n"); + pcibios_entry = 0; + } +#if DEBUG + if (pcibios_entry) { + printf ("pcibios_init : PCI BIOS revision %hhX.%hhX" + " entry at %#X\n", major_revision, + minor_revision, pcibios_entry); + } +#endif + } +} + +static void pcibios_init(void) +{ + union bios32 *check; + unsigned char sum; + int i, length; + bios32_entry = 0; + + /* + * Follow the standard procedure for locating the BIOS32 Service + * directory by scanning the permissible address range from + * 0xe0000 through 0xfffff for a valid BIOS32 structure. + * + */ + + for (check = phys_to_virt(0xe0000); (void *)check <= phys_to_virt(0xffff0); ++check) { + if (check->fields.signature != BIOS32_SIGNATURE) + continue; + length = check->fields.length * 16; + if (!length) + continue; + sum = 0; + for (i = 0; i < length ; ++i) + sum += check->chars[i]; + if (sum != 0) + continue; + if (check->fields.revision != 0) { + printf("pcibios_init : unsupported revision %d at %#X, mail drew@colorado.edu\n", + check->fields.revision, check); + continue; + } +#if DEBUG + printf("pcibios_init : BIOS32 Service Directory " + "structure at %#X\n", check); +#endif + if (!bios32_entry) { + if (check->fields.entry >= 0x100000) { + printf("pcibios_init: entry in high " + "memory, giving up\n"); + return; + } else { + bios32_entry = check->fields.entry; +#if DEBUG + printf("pcibios_init : BIOS32 Service Directory" + " entry at %#X\n", bios32_entry); +#endif + } + } + } + if (bios32_entry) + check_pcibios(); +} +#endif /* CONFIG_PCI_DIRECT not defined*/ + +unsigned long pcibios_bus_base(unsigned int bus __unused) +{ + /* architecturally this must be 0 */ + return 0; +} + +void find_pci(int type, struct pci_device *dev) +{ +#ifndef CONFIG_PCI_DIRECT + if (!pcibios_entry) { + pcibios_init(); + } + if (!pcibios_entry) { + printf("pci_init: no BIOS32 detected\n"); + return; + } +#endif + return scan_pci_bus(type, dev); +} +#endif /* CONFIG_PCI */ diff --git a/src/arch/i386/core/pic8259.c b/src/arch/i386/core/pic8259.c new file mode 100644 index 00000000..cc2071fc --- /dev/null +++ b/src/arch/i386/core/pic8259.c @@ -0,0 +1,331 @@ +/* + * Basic support for controlling the 8259 Programmable Interrupt Controllers. + * + * Initially written by Michael Brown (mcb30). + */ + +#include <etherboot.h> +#include "pic8259.h" +#include "realmode.h" + +#ifdef DEBUG_IRQ +#define DBG(...) printf ( __VA_ARGS__ ) +#else +#define DBG(...) +#endif + +/* State of trivial IRQ handler */ +irq_t trivial_irq_installed_on = IRQ_NONE; +static uint16_t trivial_irq_previous_trigger_count = 0; + +/* The actual trivial IRQ handler + * + * Note: we depend on the C compiler not realising that we're putting + * variables in the ".text16" section and therefore not forcing them + * back to the ".data" section. I don't see any reason to expect this + * behaviour to change. + * + * These must *not* be the first variables to appear in this file; the + * first variable to appear gets the ".data" directive. + */ +RM_FRAGMENT(_trivial_irq_handler, + "pushw %bx\n\t" + "call 1f\n1:\tpopw %bx\n\t" /* PIC access to variables */ + "incw %cs:(_trivial_irq_trigger_count-1b)(%bx)\n\t" + "popw %bx\n\t" + "iret\n\t" + "\n\t" + ".globl _trivial_irq_trigger_count\n\t" + "_trivial_irq_trigger_count: .short 0\n\t" + "\n\t" + ".globl _trivial_irq_chain_to\n\t" + "_trivial_irq_chain_to: .short 0,0\n\t" + "\n\t" + ".globl _trivial_irq_chain\n\t" + "_trivial_irq_chain: .byte 0\n\t" + ); +extern volatile uint16_t _trivial_irq_trigger_count; +extern segoff_t _trivial_irq_chain_to; +extern int8_t _trivial_irq_chain; + +/* Current locations of trivial IRQ handler. These will change at + * runtime when relocation is used; the handler needs to be copied to + * base memory before being installed. + */ +void (*trivial_irq_handler)P((void)) = _trivial_irq_handler; +uint16_t volatile *trivial_irq_trigger_count = &_trivial_irq_trigger_count; +segoff_t *trivial_irq_chain_to = &_trivial_irq_chain_to; +uint8_t *trivial_irq_chain = &_trivial_irq_chain; + +/* Install a handler for the specified IRQ. Address of previous + * handler will be stored in previous_handler. Enabled/disabled state + * of IRQ will be preserved across call, therefore if the handler does + * chaining, ensure that either (a) IRQ is disabled before call, or + * (b) previous_handler points directly to the place that the handler + * picks up its chain-to address. + */ + +int install_irq_handler ( irq_t irq, segoff_t *handler, + uint8_t *previously_enabled, + segoff_t *previous_handler ) { + segoff_t *irq_vector = IRQ_VECTOR ( irq ); + *previously_enabled = irq_enabled ( irq ); + + if ( irq > IRQ_MAX ) { + DBG ( "Invalid IRQ number %d\n" ); + return 0; + } + + previous_handler->segment = irq_vector->segment; + previous_handler->offset = irq_vector->offset; + if ( *previously_enabled ) disable_irq ( irq ); + DBG ( "Installing handler at %hx:%hx for IRQ %d (vector 0000:%hx)," + " leaving %s\n", + handler->segment, handler->offset, irq, virt_to_phys(irq_vector), + ( *previously_enabled ? "enabled" : "disabled" ) ); + DBG ( "...(previous handler at %hx:%hx)\n", + previous_handler->segment, previous_handler->offset ); + irq_vector->segment = handler->segment; + irq_vector->offset = handler->offset; + if ( *previously_enabled ) enable_irq ( irq ); + return 1; +} + +/* Remove handler for the specified IRQ. Routine checks that another + * handler has not been installed that chains to handler before + * uninstalling handler. Enabled/disabled state of the IRQ will be + * restored to that specified by previously_enabled. + */ + +int remove_irq_handler ( irq_t irq, segoff_t *handler, + uint8_t *previously_enabled, + segoff_t *previous_handler ) { + segoff_t *irq_vector = IRQ_VECTOR ( irq ); + + if ( irq > IRQ_MAX ) { + DBG ( "Invalid IRQ number %d\n" ); + return 0; + } + if ( ( irq_vector->segment != handler->segment ) || + ( irq_vector->offset != handler->offset ) ) { + DBG ( "Cannot remove handler for IRQ %d\n" ); + return 0; + } + + DBG ( "Removing handler for IRQ %d\n", irq ); + disable_irq ( irq ); + irq_vector->segment = previous_handler->segment; + irq_vector->offset = previous_handler->offset; + if ( *previously_enabled ) enable_irq ( irq ); + return 1; +} + +/* Install the trivial IRQ handler. This routine installs the + * handler, tests it and enables the IRQ. + */ + +int install_trivial_irq_handler ( irq_t irq ) { + segoff_t trivial_irq_handler_segoff = SEGOFF(trivial_irq_handler); + + if ( trivial_irq_installed_on != IRQ_NONE ) { + DBG ( "Can install trivial IRQ handler only once\n" ); + return 0; + } + if ( SEGMENT(trivial_irq_handler) > 0xffff ) { + DBG ( "Trivial IRQ handler not in base memory\n" ); + return 0; + } + + DBG ( "Installing trivial IRQ handler on IRQ %d\n", irq ); + if ( ! install_irq_handler ( irq, &trivial_irq_handler_segoff, + trivial_irq_chain, + trivial_irq_chain_to ) ) + return 0; + trivial_irq_installed_on = irq; + + DBG ( "Testing trivial IRQ handler\n" ); + disable_irq ( irq ); + *trivial_irq_trigger_count = 0; + trivial_irq_previous_trigger_count = 0; + fake_irq ( irq ); + if ( ! trivial_irq_triggered ( irq ) ) { + DBG ( "Installation of trivial IRQ handler failed\n" ); + remove_trivial_irq_handler ( irq ); + return 0; + } + /* Send EOI just in case there was a leftover interrupt */ + send_specific_eoi ( irq ); + DBG ( "Trivial IRQ handler installed successfully\n" ); + enable_irq ( irq ); + return 1; +} + +/* Remove the trivial IRQ handler. + */ + +int remove_trivial_irq_handler ( irq_t irq ) { + segoff_t trivial_irq_handler_segoff = SEGOFF(trivial_irq_handler); + + if ( trivial_irq_installed_on == IRQ_NONE ) return 1; + if ( irq != trivial_irq_installed_on ) { + DBG ( "Cannot uninstall trivial IRQ handler from IRQ %d; " + "is installed on IRQ %d\n", irq, + trivial_irq_installed_on ); + return 0; + } + + if ( ! remove_irq_handler ( irq, &trivial_irq_handler_segoff, + trivial_irq_chain, + trivial_irq_chain_to ) ) + return 0; + + if ( trivial_irq_triggered ( trivial_irq_installed_on ) ) { + DBG ( "Sending EOI for unwanted trivial IRQ\n" ); + send_specific_eoi ( trivial_irq_installed_on ); + } + + trivial_irq_installed_on = IRQ_NONE; + return 1; +} + +/* Safe method to detect whether or not trivial IRQ has been + * triggered. Using this call avoids potential race conditions. This + * call will return success only once per trigger. + */ + +int trivial_irq_triggered ( irq_t irq ) { + uint16_t trivial_irq_this_trigger_count = *trivial_irq_trigger_count; + int triggered = ( trivial_irq_this_trigger_count - + trivial_irq_previous_trigger_count ); + + /* irq is not used at present, but we have it in the API for + * future-proofing; in case we want the facility to have + * multiple trivial IRQ handlers installed simultaneously. + * + * Avoid compiler warning about unused variable. + */ + if ( irq == IRQ_NONE ) {}; + + trivial_irq_previous_trigger_count = trivial_irq_this_trigger_count; + return triggered ? 1 : 0; +} + +/* Copy trivial IRQ handler to a new location. Typically used to copy + * the handler into base memory; when relocation is being used we need + * to do this before installing the handler. + * + * Call with target=NULL in order to restore the handler to its + * original location. + */ + +int copy_trivial_irq_handler ( void *target, size_t target_size ) { + irq_t currently_installed_on = trivial_irq_installed_on; + uint32_t offset = ( target == NULL ? 0 : + target - (void*)_trivial_irq_handler ); + + if (( target != NULL ) && ( target_size < TRIVIAL_IRQ_HANDLER_SIZE )) { + DBG ( "Insufficient space to copy trivial IRQ handler\n" ); + return 0; + } + + if ( currently_installed_on != IRQ_NONE ) { + DBG ("WARNING: relocating trivial IRQ handler while in use\n"); + if ( ! remove_trivial_irq_handler ( currently_installed_on ) ) + return 0; + } + + /* Do the actual copy */ + if ( target != NULL ) { + DBG ( "Copying trivial IRQ handler to %hx:%hx\n", + SEGMENT(target), OFFSET(target) ); + memcpy ( target, _trivial_irq_handler, + TRIVIAL_IRQ_HANDLER_SIZE ); + } else { + DBG ( "Restoring trivial IRQ handler to original location\n" ); + } + /* Update all the pointers to structures within the handler */ + trivial_irq_handler = ( void (*)P((void)) ) + ( (void*)_trivial_irq_handler + offset ); + trivial_irq_trigger_count = (uint16_t*) + ( (void*)&_trivial_irq_trigger_count + offset ); + trivial_irq_chain_to = (segoff_t*) + ( (void*)&_trivial_irq_chain_to + offset ); + trivial_irq_chain = (uint8_t*) + ( (void*)&_trivial_irq_chain + offset ); + + if ( currently_installed_on != IRQ_NONE ) { + if ( ! install_trivial_irq_handler ( currently_installed_on ) ) + return 0; + } + return 1; +} + +/* Send non-specific EOI(s). This seems to be inherently unsafe. + */ + +void send_nonspecific_eoi ( irq_t 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). + */ + +void send_specific_eoi ( irq_t irq ) { + DBG ( "Sending specific EOI for IRQ %d\n", irq ); + outb ( ICR_EOI_SPECIFIC | ICR_VALUE(irq), ICR_REG(irq) ); + if ( irq >= IRQ_PIC_CUTOFF ) { + outb ( ICR_EOI_SPECIFIC | ICR_VALUE(CHAINED_IRQ), + ICR_REG(CHAINED_IRQ) ); + } +} + +/* Fake an IRQ + */ + +void fake_irq ( irq_t irq ) { + struct { + uint16_t int_number; + } PACKED in_stack; + + /* Convert IRQ to INT number: + * + * subb $0x08,%cl Invert bit 3, set bits 4-7 iff irq < 8 + * xorb $0x70,%cl Invert bits 4-6 + * andb $0x7f,%cl Clear bit 7 + * + * No, it's not the most intuitive method, but I was proud to + * get it down to three lines of assembler when this routine + * was originally implemented in pcbios.S. + */ + in_stack.int_number = ( ( irq - 8 ) ^ 0x70 ) & 0x7f; + + RM_FRAGMENT(rm_fake_irq, + "popw %ax\n\t" /* %ax = INT number */ + "call 1f\n1:\tpop %bx\n\t" + "movb %al, %cs:(2f-1b+1)(%bx)\n\t" /* Overwrite INT number..*/ + "\n2:\tint $0x00\n\t" /* ..in this instruction */ + ); + + real_call ( rm_fake_irq, &in_stack, NULL ); +} + +/* Dump current 8259 status: enabled IRQs and handler addresses. + */ + +#ifdef DEBUG_IRQ +void dump_irq_status ( void ) { + int irq = 0; + + for ( irq = 0; irq < 16; irq++ ) { + if ( irq_enabled ( irq ) ) { + printf ( "IRQ%d enabled, ISR at %hx:%hx\n", irq, + IRQ_VECTOR(irq)->segment, + IRQ_VECTOR(irq)->offset ); + } + } +} +#endif diff --git a/src/arch/i386/core/prefixudata.lds b/src/arch/i386/core/prefixudata.lds new file mode 100644 index 00000000..1c76128e --- /dev/null +++ b/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/src/arch/i386/core/prefixzdata.lds b/src/arch/i386/core/prefixzdata.lds new file mode 100644 index 00000000..bf6ea977 --- /dev/null +++ b/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/src/arch/i386/core/pxe_callbacks.c b/src/arch/i386/core/pxe_callbacks.c new file mode 100644 index 00000000..344d34fe --- /dev/null +++ b/src/arch/i386/core/pxe_callbacks.c @@ -0,0 +1,364 @@ +/* PXE callback mechanisms. This file contains only the portions + * specific to i386: i.e. the low-level mechanisms for calling in from + * an NBP to the PXE stack and for starting an NBP from the PXE stack. + */ + +#ifdef PXE_EXPORT + +#include "etherboot.h" +#include "callbacks.h" +#include "realmode.h" +#include "pxe.h" +#include "pxe_callbacks.h" +#include "pxe_export.h" +#include "hidemem.h" +#include <stdarg.h> + +#define INSTALLED(x) ( (typeof(&x)) ( (void*)(&x) \ + - &pxe_callback_interface \ + + (void*)&pxe_stack->arch_data ) ) +#define pxe_intercept_int1a INSTALLED(_pxe_intercept_int1a) +#define pxe_intercepted_int1a INSTALLED(_pxe_intercepted_int1a) +#define pxe_pxenv_location INSTALLED(_pxe_pxenv_location) +#define INT1A_VECTOR ( (segoff_t*) ( phys_to_virt( 4 * 0x1a ) ) ) + +/* The overall size of the PXE stack is ( sizeof(pxe_stack_t) + + * pxe_callback_interface_size + rm_callback_interface_size ). + * Unfortunately, this isn't a compile-time constant, since + * {pxe,rm}_callback_interface_size depend on the length of the + * assembly code in these interfaces. + * + * We used to have a function pxe_stack_size() which returned this + * value. However, it actually needs to be a link-time constant, so + * that it can appear in the UNDIROMID structure in romprefix.S. We + * therefore export the three component sizes as absolute linker + * symbols, get the linker to add them together and generate a new + * absolute symbol _pxe_stack_size. We then import this value into a + * C variable pxe_stack_size, for access from C code. + */ + +/* gcc won't let us use extended asm outside a function (compiler + * bug), ao we have to put these asm statements inside a dummy + * function. + */ +static void work_around_gcc_bug ( void ) __attribute__ ((used)); +static void work_around_gcc_bug ( void ) { + /* Export sizeof(pxe_stack_t) as absolute linker symbol */ + __asm__ ( ".globl _pxe_stack_t_size" ); + __asm__ ( ".equ _pxe_stack_t_size, %c0" + : : "i" (sizeof(pxe_stack_t)) ); +} +/* Import _pxe_stack_size absolute linker symbol into C variable */ +extern int pxe_stack_size; +__asm__ ( "pxe_stack_size: .long _pxe_stack_size" ); + +/* Utility routine: byte checksum + */ +uint8_t byte_checksum ( void *address, size_t size ) { + unsigned int i, sum = 0; + + for ( i = 0; i < size; i++ ) { + sum += ((uint8_t*)address)[i]; + } + return (uint8_t)sum; +} + +/* install_pxe_stack(): install PXE stack. + * + * Use base = NULL for auto-allocation of base memory + * + * IMPORTANT: no further allocation of base memory should take place + * before the PXE stack is removed. This is to work around a small + * but important deficiency in the PXE specification. + */ +pxe_stack_t * install_pxe_stack ( void *base ) { + pxe_t *pxe; + pxenv_t *pxenv; + void *pxe_callback_code; + void (*pxe_in_call_far)(void); + void (*pxenv_in_call_far)(void); + void *rm_callback_code; + void *e820mangler_code; + void *end; + + /* If already installed, just return */ + if ( pxe_stack != NULL ) return pxe_stack; + + /* Allocate base memory if requested to do so + */ + if ( base == NULL ) { + base = allot_base_memory ( pxe_stack_size ); + if ( base == NULL ) return NULL; + } + + /* Round address up to 16-byte physical alignment */ + pxe_stack = (pxe_stack_t *) + ( phys_to_virt ( ( virt_to_phys(base) + 0xf ) & ~0xf ) ); + /* Zero out allocated stack */ + memset ( pxe_stack, 0, sizeof(*pxe_stack) ); + + /* Calculate addresses for portions of the stack */ + pxe = &(pxe_stack->pxe); + pxenv = &(pxe_stack->pxenv); + pxe_callback_code = &(pxe_stack->arch_data); + pxe_in_call_far = _pxe_in_call_far + + ( pxe_callback_code - &pxe_callback_interface ); + pxenv_in_call_far = _pxenv_in_call_far + + ( pxe_callback_code - &pxe_callback_interface ); + rm_callback_code = pxe_callback_code + pxe_callback_interface_size; + + e820mangler_code = (void*)(((int)rm_callback_code + + rm_callback_interface_size + 0xf ) & ~0xf); + end = e820mangler_code + e820mangler_size; + + /* Initialise !PXE data structures */ + memcpy ( pxe->Signature, "!PXE", 4 ); + pxe->StructLength = sizeof(*pxe); + pxe->StructRev = 0; + pxe->reserved_1 = 0; + /* We don't yet have an UNDI ROM ID structure */ + pxe->UNDIROMID.segment = 0; + pxe->UNDIROMID.offset = 0; + /* or a BC ROM ID structure */ + pxe->BaseROMID.segment = 0; + pxe->BaseROMID.offset = 0; + pxe->EntryPointSP.segment = SEGMENT(pxe_stack); + pxe->EntryPointSP.offset = (void*)pxe_in_call_far - (void*)pxe_stack; + /* No %esp-compatible entry point yet */ + pxe->EntryPointESP.segment = 0; + pxe->EntryPointESP.offset = 0; + pxe->StatusCallout.segment = -1; + pxe->StatusCallout.offset = -1; + pxe->reserved_2 = 0; + pxe->SegDescCn = 7; + pxe->FirstSelector = 0; + /* PXE specification doesn't say anything about when the stack + * space should get freed. We work around this by claiming it + * as our data segment as well. + */ + pxe->Stack.Seg_Addr = pxe->UNDIData.Seg_Addr = real_mode_stack >> 4; + pxe->Stack.Phy_Addr = pxe->UNDIData.Phy_Addr = real_mode_stack; + pxe->Stack.Seg_Size = pxe->UNDIData.Seg_Size = real_mode_stack_size; + /* Code segment has to be the one containing the data structures... */ + pxe->UNDICode.Seg_Addr = SEGMENT(pxe_stack); + pxe->UNDICode.Phy_Addr = virt_to_phys(pxe_stack); + pxe->UNDICode.Seg_Size = end - (void*)pxe_stack; + /* No base code loaded */ + pxe->BC_Data.Seg_Addr = 0; + pxe->BC_Data.Phy_Addr = 0; + pxe->BC_Data.Seg_Size = 0; + pxe->BC_Code.Seg_Addr = 0; + pxe->BC_Code.Phy_Addr = 0; + pxe->BC_Code.Seg_Size = 0; + pxe->BC_CodeWrite.Seg_Addr = 0; + pxe->BC_CodeWrite.Phy_Addr = 0; + pxe->BC_CodeWrite.Seg_Size = 0; + pxe->StructCksum -= byte_checksum ( pxe, sizeof(*pxe) ); + + /* Initialise PXENV+ data structures */ + memcpy ( pxenv->Signature, "PXENV+", 6 ); + pxenv->Version = 0x201; + pxenv->Length = sizeof(*pxenv); + pxenv->RMEntry.segment = SEGMENT(pxe_stack); + pxenv->RMEntry.offset = (void*)pxenv_in_call_far - (void*)pxe_stack; + pxenv->PMOffset = 0; /* "Do not use" says the PXE spec */ + pxenv->PMSelector = 0; /* "Do not use" says the PXE spec */ + pxenv->StackSeg = pxenv->UNDIDataSeg = real_mode_stack >> 4; + pxenv->StackSize = pxenv->UNDIDataSize = real_mode_stack_size; + pxenv->BC_CodeSeg = 0; + pxenv->BC_CodeSize = 0; + pxenv->BC_DataSeg = 0; + pxenv->BC_DataSize = 0; + /* UNDIData{Seg,Size} set above */ + pxenv->UNDICodeSeg = SEGMENT(pxe_stack); + pxenv->UNDICodeSize = end - (void*)pxe_stack; + pxenv->PXEPtr.segment = SEGMENT(pxe); + pxenv->PXEPtr.offset = OFFSET(pxe); + pxenv->Checksum -= byte_checksum ( pxenv, sizeof(*pxenv) ); + + /* Mark stack as inactive */ + pxe_stack->state = CAN_UNLOAD; + + /* Install PXE and RM callback code and E820 mangler */ + memcpy ( pxe_callback_code, &pxe_callback_interface, + pxe_callback_interface_size ); + install_rm_callback_interface ( rm_callback_code, 0 ); + install_e820mangler ( e820mangler_code ); + + return pxe_stack; +} + +/* Use the UNDI data segment as our real-mode stack. This is for when + * we have been loaded via the UNDI loader + */ +void use_undi_ds_for_rm_stack ( uint16_t ds ) { + forget_real_mode_stack(); + real_mode_stack = virt_to_phys ( VIRTUAL ( ds, 0 ) ); + lock_real_mode_stack = 1; +} + +/* Activate PXE stack (i.e. hook interrupt vectors). The PXE stack + * *can* be used before it is activated, but it really shoudln't. + */ +int hook_pxe_stack ( void ) { + if ( pxe_stack == NULL ) return 0; + if ( pxe_stack->state >= MIDWAY ) return 1; + + /* Hook INT15 handler */ + hide_etherboot(); + + /* Hook INT1A handler */ + *pxe_intercepted_int1a = *INT1A_VECTOR; + pxe_pxenv_location->segment = SEGMENT(pxe_stack); + pxe_pxenv_location->offset = (void*)&pxe_stack->pxenv + - (void*)pxe_stack; + INT1A_VECTOR->segment = SEGMENT(&pxe_stack->arch_data); + INT1A_VECTOR->offset = (void*)pxe_intercept_int1a + - (void*)&pxe_stack->arch_data; + + /* Mark stack as active */ + pxe_stack->state = MIDWAY; + return 1; +} + +/* Deactivate the PXE stack (i.e. unhook interrupt vectors). + */ +int unhook_pxe_stack ( void ) { + if ( pxe_stack == NULL ) return 0; + if ( pxe_stack->state <= CAN_UNLOAD ) return 1; + + /* Restore original INT15 and INT1A handlers */ + *INT1A_VECTOR = *pxe_intercepted_int1a; + if ( !unhide_etherboot() ) { + /* Cannot unhook INT15. We're up the creek without + * even a suitable log out of which to fashion a + * paddle. There are some very badly behaved NBPs + * that will ignore plaintive pleas such as + * PXENV_KEEP_UNDI and just zero out our code anyway. + * This means they end up vapourising an active INT15 + * handler, which is generally not a good thing to do. + */ + return 0; + } + + /* Mark stack as inactive */ + pxe_stack->state = CAN_UNLOAD; + return 1; +} + +/* remove_pxe_stack(): remove PXE stack installed by install_pxe_stack() + */ +void remove_pxe_stack ( void ) { + /* Ensure stack is deactivated, then free up the memory */ + if ( ensure_pxe_state ( CAN_UNLOAD ) ) { + forget_base_memory ( pxe_stack, pxe_stack_size ); + pxe_stack = NULL; + } else { + printf ( "Cannot remove PXE stack!\n" ); + } +} + +/* xstartpxe(): start up a PXE image + */ +int xstartpxe ( void ) { + int nbp_exit; + struct { + reg16_t bx; + reg16_t es; + segoff_t pxe; + } PACKED in_stack; + + /* Set up registers and stack parameters to pass to PXE NBP */ + in_stack.es.word = SEGMENT(&(pxe_stack->pxenv)); + in_stack.bx.word = OFFSET(&(pxe_stack->pxenv)); + in_stack.pxe.segment = SEGMENT(&(pxe_stack->pxe)); + in_stack.pxe.offset = OFFSET(&(pxe_stack->pxe)); + + /* Real-mode trampoline fragment used to jump to PXE NBP + */ + RM_FRAGMENT(jump_to_pxe_nbp, + "popw %bx\n\t" + "popw %es\n\t" + "lcall $" RM_STR(PXE_LOAD_SEGMENT) ", $" RM_STR(PXE_LOAD_OFFSET) "\n\t" + ); + + /* Call to PXE image */ + gateA20_unset(); + nbp_exit = real_call ( jump_to_pxe_nbp, &in_stack, NULL ); + gateA20_set(); + + return nbp_exit; +} + +int pxe_in_call ( in_call_data_t *in_call_data, va_list params ) { + /* i386 calling conventions; the only two defined by Intel's + * PXE spec. + * + * Assembly code must pass a long containing the PXE version + * code (i.e. 0x201 for !PXE, 0x200 for PXENV+) as the first + * parameter after the in_call opcode. This is used to decide + * whether to take parameters from the stack (!PXE) or from + * registers (PXENV+). + */ + uint32_t api_version = va_arg ( params, typeof(api_version) ); + uint16_t opcode; + segoff_t segoff; + t_PXENV_ANY *structure; + + if ( api_version >= 0x201 ) { + /* !PXE calling convention */ + pxe_call_params_t pxe_params + = va_arg ( params, typeof(pxe_params) ); + opcode = pxe_params.opcode; + segoff = pxe_params.segoff; + } else { + /* PXENV+ calling convention */ + opcode = in_call_data->pm->regs.bx; + segoff.segment = in_call_data->rm->seg_regs.es; + segoff.offset = in_call_data->pm->regs.di; + } + structure = VIRTUAL ( segoff.segment, segoff.offset ); + return pxe_api_call ( opcode, structure ); +} + +#ifdef TEST_EXCLUDE_ALGORITHM +/* This code retained because it's a difficult algorithm to tweak with + * confidence + */ +int ___test_exclude ( int start, int len, int estart, int elen, int fixbase ); +void __test_exclude ( int start, int len, int estart, int elen, int fixbase ) { + int newrange = ___test_exclude ( start, len, estart, elen, fixbase ); + int newstart = ( newrange >> 16 ) & 0xffff; + int newlen = ( newrange & 0xffff ); + + printf ( "[%x,%x): excluding [%x,%x) %s gives [%x,%x)\n", + start, start + len, + estart, estart + elen, + ( fixbase == 0 ) ? " " : "fb", + newstart, newstart + newlen ); +} +void _test_exclude ( int start, int len, int estart, int elen ) { + __test_exclude ( start, len, estart, elen, 0 ); + __test_exclude ( start, len, estart, elen, 1 ); +} +void test_exclude ( void ) { + _test_exclude ( 0x8000, 0x1000, 0x0400, 0x200 ); /* before */ + _test_exclude ( 0x8000, 0x1000, 0x9000, 0x200 ); /* after */ + _test_exclude ( 0x8000, 0x1000, 0x7f00, 0x200 ); /* before overlap */ + _test_exclude ( 0x8000, 0x1000, 0x8f00, 0x200 ); /* after overlap */ + _test_exclude ( 0x8000, 0x1000, 0x8000, 0x200 ); /* align start */ + _test_exclude ( 0x8000, 0x1000, 0x8e00, 0x200 ); /* align end */ + _test_exclude ( 0x8000, 0x1000, 0x8100, 0x200 ); /* early overlap */ + _test_exclude ( 0x8000, 0x1000, 0x8d00, 0x200 ); /* late overlap */ + _test_exclude ( 0x8000, 0x1000, 0x7000, 0x3000 ); /* total overlap */ + _test_exclude ( 0x8000, 0x1000, 0x8000, 0x1000 ); /* exact overlap */ +} +#endif /* TEST_EXCLUDE_ALGORITHM */ + +#else /* PXE_EXPORT */ + +/* Define symbols used by the linker scripts, to prevent link errors */ +__asm__ ( ".globl _pxe_stack_t_size" ); +__asm__ ( ".equ _pxe_stack_t_size, 0" ); + +#endif /* PXE_EXPORT */ diff --git a/src/arch/i386/core/pxe_loader.c b/src/arch/i386/core/pxe_loader.c new file mode 100644 index 00000000..1b611891 --- /dev/null +++ b/src/arch/i386/core/pxe_loader.c @@ -0,0 +1,94 @@ +/* + * PXE image loader for Etherboot. + * + * Note: There is no signature check for PXE images because there is + * no signature. Well done, Intel! Consequently, pxe_probe() must be + * called last of all the image_probe() routines, because it will + * *always* claim the image. + */ + +#ifndef PXE_EXPORT +#error PXE_IMAGE requires PXE_EXPORT +#endif + +#include "etherboot.h" +#include "pxe_callbacks.h" +#include "pxe_export.h" +#include "pxe.h" + +unsigned long pxe_load_offset; + +static sector_t pxe_download ( unsigned char *data, + unsigned int len, int eof ); + +static inline os_download_t pxe_probe ( unsigned char *data __unused, + unsigned int len __unused ) { + printf("(PXE)"); + pxe_load_offset = 0; + return pxe_download; +} + +static sector_t pxe_download ( unsigned char *data, + unsigned int len, int eof ) { + unsigned long block_address = PXE_LOAD_ADDRESS + pxe_load_offset; + PXENV_STATUS_t nbp_exit; + + /* Check segment will fit. We can't do this in probe() + * because there's nothing in the non-existent header to tell + * us how long the image is. + */ + if ( ! prep_segment ( block_address, block_address + len, + block_address + len, + pxe_load_offset, pxe_load_offset + len ) ) { + longjmp ( restart_etherboot, -2 ); + } + + /* Load block into memory, continue loading until eof */ + memcpy ( phys_to_virt ( block_address ), data, len ); + pxe_load_offset += len; + if ( ! eof ) { + return 0; + } + + /* Start up PXE NBP */ + done ( 0 ); + + /* Install and activate a PXE stack */ + pxe_stack = install_pxe_stack ( NULL ); + if ( ensure_pxe_state ( READY ) ) { + /* Invoke the NBP */ + nbp_exit = xstartpxe(); + } else { + /* Fake success so we tear down the stack */ + nbp_exit = PXENV_STATUS_SUCCESS; + } + + /* NBP has three exit codes: + * PXENV_STATUS_KEEP_UNDI : keep UNDI and boot next device + * PXENV_STATUS_KEEP_ALL : keep all and boot next device + * anything else : remove all and boot next device + * + * Strictly, we're meant to hand back to the BIOS, but this + * would prevent the useful combination of "PXE NBP fails, so + * let Etherboot try to boot its next device". We therefore + * take liberties. + */ + if ( nbp_exit != PXENV_STATUS_KEEP_UNDI && + nbp_exit != PXENV_STATUS_KEEP_ALL ) { + /* Tear down PXE stack */ + remove_pxe_stack(); + } + + /* Boot next device. Under strict PXE compliance, exit back + * to the BIOS, otherwise let Etherboot move to the next + * device. + */ +#ifdef PXE_STRICT + longjmp ( restart_etherboot, 255 ); +#else + longjmp ( restart_etherboot, 4 ); +#endif + + /* Never reached; avoid compiler warning */ + return ( 0 ); +} diff --git a/src/arch/i386/core/realmode.c b/src/arch/i386/core/realmode.c new file mode 100644 index 00000000..ef4ede86 --- /dev/null +++ b/src/arch/i386/core/realmode.c @@ -0,0 +1,148 @@ +/* Real-mode interface: C portions. + * + * Initial version by Michael Brown <mbrown@fensystems.co.uk>, January 2004. + */ + +#include "etherboot.h" +#include "realmode.h" +#include "segoff.h" + +#define RM_STACK_SIZE ( 0x1000 ) + +/* gcc won't let us use extended asm outside a function (compiler + * bug), ao we have to put these asm statements inside a dummy + * function. + */ +static void work_around_gcc_bug ( void ) __attribute__ ((used)); +static void work_around_gcc_bug ( void ) { + /* Export _real_mode_stack_size as absolute linker symbol */ + __asm__ ( ".globl _real_mode_stack_size" ); + __asm__ ( ".equ _real_mode_stack_size, %c0" : : "i" (RM_STACK_SIZE) ); +} + +/* While Etherboot remains in base memory the real-mode stack is + * placed in the Etherboot main stack. The first allocation or + * deallocation of base memory will cause a 'proper' real-mode stack + * to be allocated. This will happen before Etherboot is relocated to + * high memory. + */ +uint32_t real_mode_stack = 0; +size_t real_mode_stack_size = RM_STACK_SIZE; +int lock_real_mode_stack = 0; /* Set to make stack immobile */ + +/* Make a call to a real-mode code block. + */ + +/* These is the structure that exists on the stack as the paramters + * passed in to _real_call. We pass a pointer to this struct to + * prepare_real_call(), to save stack space. + */ +typedef struct { + void *fragment; + int fragment_len; + void *in_stack; + int in_stack_len; + void *out_stack; + int out_stack_len; +} real_call_params_t; + +uint32_t prepare_real_call ( real_call_params_t *p, + int local_stack_len, char *local_stack ) { + char *stack_base; + char *stack_end; + char *stack; + char *s; + prot_to_real_params_t *p2r_params; + real_to_prot_params_t *r2p_params; + + /* Work out where we're putting the stack */ + if ( virt_to_phys(local_stack) < 0xa0000 ) { + /* Current stack is in base memory. We can therefore + * use it directly, with a constraint on the size that + * we don't know; assume that we can use up to + * real_mode_stack_size. (Not a valid assumption, but + * it will do). + */ + stack_end = local_stack + local_stack_len; + stack_base = stack_end - real_mode_stack_size; + } else { + if (!real_mode_stack) { + allot_real_mode_stack(); + } + /* Use the allocated real-mode stack in base memory. + * This has fixed start and end points. + */ + stack_base = phys_to_virt(real_mode_stack); + stack_end = stack_base + real_mode_stack_size; + } + s = stack = stack_end - local_stack_len; + + /* Compile input stack and trampoline code to stack */ + if ( p->in_stack_len ) { + memcpy ( s, p->in_stack, p->in_stack_len ); + s += p->in_stack_len; + } + memcpy ( s, _prot_to_real_prefix, prot_to_real_prefix_size ); + s += prot_to_real_prefix_size; + p2r_params = (prot_to_real_params_t*) ( s - sizeof(*p2r_params) ); + memcpy ( s, p->fragment, p->fragment_len ); + s += p->fragment_len; + memcpy ( s, _real_to_prot_suffix, real_to_prot_suffix_size ); + s += real_to_prot_suffix_size; + r2p_params = (real_to_prot_params_t*) ( s - sizeof(*r2p_params) ); + + /* Set parameters within compiled stack */ + p2r_params->ss = p2r_params->cs = SEGMENT ( stack_base ); + p2r_params->esp = virt_to_phys ( stack ); + p2r_params->r2p_params = virt_to_phys ( r2p_params ); + r2p_params->out_stack = ( p->out_stack == NULL ) ? + 0 : virt_to_phys ( p->out_stack ); + r2p_params->out_stack_len = p->out_stack_len; + + return virt_to_phys ( stack + p->in_stack_len ); +} + + +/* Parameters are not genuinely unused; they are passed to + * prepare_real_call() as part of a real_call_params_t struct. + */ +uint16_t _real_call ( void *fragment, int fragment_len, + void *in_stack __unused, int in_stack_len, + void *out_stack __unused, int out_stack_len __unused ) { + uint16_t retval; + + /* This code is basically equivalent to + * + * uint32_t trampoline; + * char local_stack[ in_stack_len + prot_to_real_prefix_size + + * fragment_len + real_to_prot_suffix_size ]; + * trampoline = prepare_real_call ( &fragment, local_stack ); + * __asm__ ( "call _virt_to_phys\n\t" + * "call %%eax\n\t" + * "call _phys_to_virt\n\t" + * : "=a" (retval) : "0" (trampoline) ); + * + * but implemented in assembly to avoid problems with not + * being certain exactly how gcc handles %esp. + */ + + __asm__ ( "pushl %%ebp\n\t" + "movl %%esp, %%ebp\n\t" /* %esp preserved via %ebp */ + "subl %%ecx, %%esp\n\t" /* space for inline RM stack */ + "pushl %%esp\n\t" /* set up RM stack */ + "pushl %%ecx\n\t" + "pushl %%eax\n\t" + "call prepare_real_call\n\t" /* %eax = trampoline addr */ + "addl $12, %%esp\n\t" + "call _virt_to_phys\n\t" /* switch to phys addr */ + "call *%%eax\n\t" /* call to trampoline */ + "call _phys_to_virt\n\t" /* switch to virt addr */ + "movl %%ebp, %%esp\n\t" /* restore %esp & %ebp */ + "popl %%ebp\n\t" + : "=a" ( retval ) + : "0" ( &fragment ) + , "c" ( ( ( in_stack_len + prot_to_real_prefix_size + + fragment_len + real_to_prot_suffix_size ) + + 0x3 ) & ~0x3 ) ); + return retval; +} diff --git a/src/arch/i386/core/realmode_asm.S b/src/arch/i386/core/realmode_asm.S new file mode 100644 index 00000000..28a5bfed --- /dev/null +++ b/src/arch/i386/core/realmode_asm.S @@ -0,0 +1,695 @@ +/* Real-mode interface: assembly-language portions. + * + * Initial version by Michael Brown <mbrown@fensystems.co.uk>, January 2004. + */ + +#include "realmode.h" +#include "callbacks.h" + +#if 1 /* CODE16 */ + +#define BOCHSBP xchgw %bx,%bx + +#define NUM_PUSHA_REGS (8) +#define NUM_SEG_REGS (6) + + .text + .arch i386 + .section ".text16.nocompress", "ax", @progbits + .code16 + + .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 + +/**************************************************************************** + * REAL-MODE CALLBACK INTERFACE + * + * This must be copied down to base memory in order for external + * programs to be able to make calls in to Etherboot. Store the + * current physical address of Etherboot (i.e. virt_to_phys(_text)) in + * (uint32_t)rm_etherboot_location, then copy + * (uint16_t)rm_callback_interface_size bytes starting at + * &((void)rm_callback_interface). + * + * There are two defined entry points: + * Offset RM_IN_CALL = 0 Near call entry point + * Offset RM_IN_CALL_FAR = 2 Far call entry point + * + * Note that the routines _prot_to_real and _real_to_prot double as + * trampoline fragments for external calls (calls from Etherboot to + * real-mode code). _prot_to_real does not automatically re-enable + * interrupts; this is to allow for the potential of using Etherboot + * code as an ISR. _real_to_prot does automatically disable + * interrupts, since we don't have a protected-mode IDT. + **************************************************************************** + */ + + .globl rm_callback_interface + .code16 +rm_callback_interface: + .globl _rm_in_call +_rm_in_call: + jmp _real_in_call + .globl _rm_in_call_far +_rm_in_call_far: + jmp _real_in_call_far + +/**************************************************************************** + * _real_in_call + * + * Parameters: + * 16-bit real-mode near/far return address (implicit from [l]call + * to routine) Other parameters as for _in_call_far(). + * + * This routine will convert the 16-bit real-mode far return address + * to a 32-bit real-mode far return address, switch to protected mode + * using _real_to_prot and call in to _in_call_far. + **************************************************************************** + */ + +#define RIC_PRESERVE ( 8 ) +#define RIC_OFFSET_CALLADDR ( RIC_PRESERVE ) +#define RIC_OFFSET_CALLADDR_E ( RIC_OFFSET_CALLADDR + 4 ) +#define RIC_OFFSET_CONTADDR ( RIC_OFFSET_CALLADDR_E ) +#define RIC_OFFSET_CONTADDR_E ( RIC_OFFSET_CONTADDR + 4 ) +#define RIC_OFFSET_OPCODE ( RIC_OFFSET_CONTADDR_E ) +#define RIC_OFFSET_OPCODE_E ( RIC_OFFSET_OPCODE + 4 ) +#define RIC_OFFSET_SEG_REGS ( RIC_OFFSET_OPCODE_E ) +#define RIC_OFFSET_SEG_REGS_E ( RIC_OFFSET_SEG_REGS + ( NUM_SEG_REGS * 2 ) ) +#define RIC_OFFSET_PAD ( RIC_OFFSET_SEG_REGS_E ) +#define RIC_OFFSET_PAD_E ( RIC_OFFSET_PAD + 2 ) +#define RIC_OFFSET_FLAGS ( RIC_OFFSET_PAD_E ) +#define RIC_OFFSET_FLAGS_E ( RIC_OFFSET_FLAGS + 2 ) +#define RIC_OFFSET_RETADDR ( RIC_OFFSET_FLAGS_E ) +#define RIC_OFFSET_RETADDR_E ( RIC_OFFSET_RETADDR + 4 ) +#define RIC_OFFSET_ORIG_OPCODE ( RIC_OFFSET_RETADDR_E ) +#define RIC_INSERT_LENGTH ( RIC_OFFSET_OPCODE_E - RIC_OFFSET_CALLADDR ) + + .code16 +_real_in_call: + /* Expand near return address to far return address + */ + pushw %ax /* Extend stack, store %ax */ + pushfw + pushw %bp + movw %sp, %bp + movw %cs, %ax + xchgw %ax, 6(%bp) + xchgw %ax, 4(%bp) /* also restores %ax */ + popw %bp + popfw + /* Fall through to _real_in_call_far */ + +_real_in_call_far: + /* Store flags and pad */ + pushfw + pushw %ax + + /* Store segment registers. Order matches that of seg_regs_t */ + pushw %gs + pushw %fs + pushw %es + pushw %ds + pushw %ss + pushw %cs + + /* Switch to protected mode */ + call _real_to_prot + .code32 + + /* Allow space for expanded stack */ + subl $RIC_INSERT_LENGTH, %esp + + /* Store temporary registers */ + pushl %ebp + pushl %eax + + /* Copy opcode, set EB_CALL_FROM_REAL_MODE and EP_SKIP_OPCODE. + * Copy it because _in_call() and i386_in_call() expect it at + * a fixed position, not as part of the va_list. + */ + movl RIC_OFFSET_ORIG_OPCODE(%esp), %eax + orl $(EB_CALL_FROM_REAL_MODE|EB_SKIP_OPCODE), %eax + movl %eax, RIC_OFFSET_OPCODE(%esp) + + /* Set up call and return addresses */ + call 1f +1: popl %ebp + subl $1b, %ebp /* %ebp = offset */ + movl rm_etherboot_location(%ebp), %eax /* Etherboot phys addr */ + subl $_text, %eax + addl $_in_call, %eax /* _in_call phys addr */ + movl %eax, RIC_OFFSET_CALLADDR(%esp) + leal 2f(%ebp), %eax /* continuation address */ + movl %eax, RIC_OFFSET_CONTADDR(%esp) + + /* Restore temporary registers */ + popl %eax + popl %ebp + + /* Call to _in_call */ + ret + /* opcode will be popped automatically thanks to EB_SKIP_OPCODE */ + +2: /* Continuation point */ + call _prot_to_real /* Return to real mode */ + /* Note: the first two words of our segment register store + * happens to be exactly what we need to pass as parameters to + * _prot_to_real. + */ + .code16 + popw %ds /* Restore segment registers */ + popw %ds /* (skip cs&ss since these */ + popw %ds /* have already been set by */ + popw %es /* _prot_to_real */ + popw %fs + popw %gs + addw $2, %sp /* skip pad */ + + /* Check for EB_SKIP_OPCODE */ + pushw %bp + movw %sp, %bp + testl $EB_SKIP_OPCODE, 6(%bp) + popw %bp + jnz 1f + /* Normal return */ + popfw /* Restore interrupt status */ + lret /* Back to caller */ +1: /* Return and skip opcode */ + popfw + lret $4 + +/**************************************************************************** + * rm_etherboot_location: the current physical location of Etherboot. + * Needed so that real-mode callback routines can locate Etherboot. + **************************************************************************** + */ + .globl rm_etherboot_location +rm_etherboot_location: .long 0 + +/**************************************************************************** + * _prot_to_real_prefix + * + * Trampoline fragment. Switch from 32-bit protected mode with flat + * physical addresses to 16-bit real mode. Store registers in the + * trampoline for restoration by _real_to_prot_suffix. Switch to + * stack in base memory. + **************************************************************************** + */ + + .globl _prot_to_real_prefix + .code32 +_prot_to_real_prefix: + /* Registers to preserve */ + pushl %ebx + pushl %esi + pushl %edi + pushl %ebp + + /* Calculate offset */ + call 1f +1: popl %ebp + subl $1b, %ebp /* %ebp = offset for labels in p2r*/ + + /* Preserve registers and return address in r2p_params */ + movl p2r_r2p_params(%ebp), %ebx + subl $r2p_params, %ebx /* %ebx = offset for labels in r2p */ + popl r2p_ebp(%ebx) + popl r2p_edi(%ebx) + popl r2p_esi(%ebx) + popl r2p_ebx(%ebx) + popl r2p_ret_addr(%ebx) + movl %esp, r2p_esp(%ebx) + + /* Switch stacks */ + movl p2r_esp(%ebp), %esp + + /* Switch to real mode */ + pushl p2r_segments(%ebp) + call _prot_to_real + .code16 + addw $4, %sp + + /* Fall through to next trampoline fragment */ + jmp _prot_to_real_prefix_end + +/**************************************************************************** + * _prot_to_real + * + * Switch from 32-bit protected mode with flat physical addresses to + * 16-bit real mode. Stack and code must be in base memory when + * called. %cs, %ss, %eip, %esp are changed to real-mode values, + * other segment registers are destroyed, all other registers are + * preserved. Interrupts are *not* enabled. + * + * Parameters: + * %cs Real-mode code segment (word) + * %ss Real-mode stack segment (word) + **************************************************************************** + */ + +#define P2R_PRESERVE ( 12 ) +#define P2R_OFFSET_RETADDR ( P2R_PRESERVE ) +#define P2R_OFFSET_RETADDR_E ( P2R_OFFSET_RETADDR + 4 ) +#define P2R_OFFSET_CS ( P2R_OFFSET_RETADDR_E ) +#define P2R_OFFSET_CS_E ( P2R_OFFSET_CS + 2 ) +#define P2R_OFFSET_SS ( P2R_OFFSET_CS_E ) +#define P2R_OFFSET_SS_E ( P2R_OFFSET_SS + 2 ) + + .globl _prot_to_real + .code32 +_prot_to_real: + /* Preserve registers */ + pushl %ebp + pushl %ebx + pushl %eax + + /* Calculate offset */ + call 1f +1: popl %ebp + subl $1b, %ebp /* %ebp = offset for labels in p2r*/ + + /* Set up GDT with real-mode limits and appropriate bases for + * real-mode %cs and %ss. Set up protected-mode continuation + * point on stack. + */ + /* Fixup GDT */ + leal p2r_gdt(%ebp), %eax + movl %eax, p2r_gdt_addr(%ebp) + + /* Calculate CS base address: set GDT code segment, adjust + * return address, set up continuation address on stack. + */ + movzwl P2R_OFFSET_CS(%esp), %eax + shll $4, %eax + /* Change return address to real-mode far address */ + subl %eax, P2R_OFFSET_RETADDR(%esp) + movl %eax, %ebx + shrl $4, %ebx + movw %bx, (P2R_OFFSET_RETADDR+2)(%esp) + /* First real mode address */ + movl %eax, %ebx + shrl $4, %ebx + pushw %bx + leal 3f(%ebp), %ebx + subl %eax, %ebx + pushw %bx + /* Continuation address */ + pushl $(p2r_rmcs - p2r_gdt) + leal 2f(%ebp), %ebx + subl %eax, %ebx + pushl %ebx + /* Code segment in GDT */ + movw %ax, (p2r_rmcs+2)(%ebp) + shrl $16, %eax /* Remainder of cs base addr */ + movb %al, (p2r_rmcs+4)(%ebp) + movb %ah, (p2r_rmcs+7)(%ebp) + + /* Calculate SS base address: set GDT data segment, retain to + * use for adjusting %esp. + */ + movzwl (12+P2R_OFFSET_SS)(%esp), %eax /* Set ss base address */ + shll $4, %eax + movw %ax, (p2r_rmds+2)(%ebp) + movl %eax, %ebx + shrl $16, %ebx + movb %bl, (p2r_rmds+4)(%ebp) + movb %bh, (p2r_rmds+7)(%ebp) + + /* Load GDT */ + lgdt p2r_gdt(%ebp) + /* Reload all segment registers and adjust %esp */ + movw $(p2r_rmds - p2r_gdt), %bx /* Pmode DS */ + movw %bx, %ss + subl %eax, %esp /* %esp now less than 0x10000 */ + movw %bx, %ds + movw %bx, %es + movw %bx, %fs + movw %bx, %gs + lret /* %cs:eip */ +2: /* Segment registers now have 16-bit limits. */ + .code16 + + /* Switch to real mode */ + movl %cr0, %ebx + andb $0!CR0_PE, %bl + movl %ebx, %cr0 + + /* Make intersegment jmp to flush the processor pipeline + * and reload %cs:%eip (to clear upper 16 bits of %eip). + */ + lret +3: + + /* Load real-mode segment value to %ss. %sp already OK */ + shrl $4, %eax + movw %ax, %ss + + /* Restore registers */ + popl %eax + popl %ebx + popl %ebp + + /* Return to caller in real-mode */ + lret + +#ifdef FLATTEN_REAL_MODE +#define RM_LIMIT_16_19__AVL__SIZE__GRANULARITY 0x8f +#else +#define RM_LIMIT_16_19__AVL__SIZE__GRANULARITY 0x00 +#endif + +p2r_gdt: +p2r_gdtarg: +p2r_gdt_limit: .word p2r_gdt_end - p2r_gdt - 1 +p2r_gdt_addr: .long 0 +p2r_gdt_padding: .word 0 +p2r_rmcs: + /* 16 bit real mode code segment */ + .word 0xffff,(0&0xffff) + .byte (0>>16),0x9b,RM_LIMIT_16_19__AVL__SIZE__GRANULARITY,(0>>24) +p2r_rmds: + /* 16 bit real mode data segment */ + .word 0xffff,(0&0xffff) + .byte (0>>16),0x93,RM_LIMIT_16_19__AVL__SIZE__GRANULARITY,(0>>24) +p2r_gdt_end: + + /* This is the end of the trampoline prefix code. When used + * as a prefix, fall through to the following code in the + * trampoline. + */ +p2r_params: /* Structure must match prot_to_real_params_t in realmode.h */ +p2r_esp: .long 0 +p2r_segments: +p2r_cs: .word 0 +p2r_ss: .word 0 +p2r_r2p_params: .long 0 + .globl _prot_to_real_prefix_end +_prot_to_real_prefix_end: + + .globl _prot_to_real_prefix_size + .equ _prot_to_real_prefix_size, _prot_to_real_prefix_end - _prot_to_real_prefix + .globl prot_to_real_prefix_size +prot_to_real_prefix_size: + .word _prot_to_real_prefix_size + +/**************************************************************************** + * _real_to_prot_suffix + * + * Trampoline fragment. Switch from 16-bit real-mode to 32-bit + * protected mode with flat physical addresses. Copy returned stack + * parameters to output_stack. Restore registers preserved by + * _prot_to_real_prefix. Restore stack to previous location. + **************************************************************************** + */ + + .globl _real_to_prot_suffix + .code16 +_real_to_prot_suffix: + + /* Switch to protected mode */ + call _real_to_prot + .code32 + + /* Calculate offset */ + call 1f +1: popl %ebp + subl $1b, %ebp /* %ebp = offset for labels in r2p */ + + /* Copy stack to out_stack */ + movl r2p_out_stack(%ebp), %edi + movl r2p_out_stack_len(%ebp), %ecx + movl %esp, %esi + cld + rep movsb + + /* Switch back to original stack */ + movl r2p_esp(%ebp), %esp + + /* Restore registers and return */ + pushl r2p_ret_addr(%ebp) /* Set up return address on stack */ + movl r2p_ebx(%ebp), %ebx + movl r2p_esi(%ebp), %esi + movl r2p_edi(%ebp), %edi + movl r2p_ebp(%ebp), %ebp + ret + +/**************************************************************************** + * _real_to_prot + * + * Switch from 16-bit real-mode to 32-bit protected mode with flat + * physical addresses. All segment registers are destroyed, %eip and + * %esp are changed to flat physical values, all other registers are + * preserved. Interrupts are disabled. + * + * Parameters: none + **************************************************************************** + */ + +#define R2P_PRESERVE ( 12 ) +#define R2P_OFFSET_RETADDR ( R2P_PRESERVE ) +#define R2P_OFFSET_ORIG_RETADDR ( R2P_OFFSET_RETADDR + 2 ) + + .globl _real_to_prot + .code16 +_real_to_prot: + /* Disable interrupts */ + cli + /* zero extend the return address */ + pushw $0 + + /* Preserve registers */ + pushl %ebp + pushl %ebx + pushl %eax + + /* Convert 16-bit real-mode near return address to + * 32-bit pmode physical near return address + */ + movw %sp, %bp + xorl %ebx, %ebx + push %cs + popw %bx + movw %bx, %ds + shll $4, %ebx + movzwl %ss:R2P_OFFSET_ORIG_RETADDR(%bp), %eax + addl %ebx, %eax + movl %eax, %ss:(R2P_OFFSET_RETADDR)(%bp) + + /* Store the code segment physical base address in %ebp */ + movl %ebx, %ebp + + /* Find the offset within the code segment that I am running at */ + xorl %ebx, %ebx + call 1f +1: popw %bx + + /* Set up GDT */ + leal (r2p_gdt-1b)(%bx), %eax /* %ds:ebx = %ds:bx = &(r2p_gdt) */ + addl %ebp, %eax /* %eax = &r2p_gdt (physical) */ + movl %eax, %ds:(r2p_gdt-1b+2)(%bx) /* Set phys. addr. in r2p_gdt */ + + /* Compute the first protected mode physical address */ + leal (2f-1b)(%bx), %eax + addl %ebp, %eax + movl %eax, %ds:(r2p_paddr-1b)(%bx) + + /* Calculate new %esp */ + xorl %eax, %eax + push %ss + popw %ax + shll $4, %eax + movzwl %sp, %ebp + addl %eax, %ebp /* %ebp = new %esp */ + + /* Load GDT */ + DATA32 lgdt %ds:(r2p_gdt-1b)(%bx) /* Load GDT */ + + /* Switch to protected mode */ + movl %cr0, %eax + orb $CR0_PE, %al + movl %eax, %cr0 + + /* flush prefetch queue, and reload %cs:%eip */ + DATA32 ljmp %ds:(r2p_paddr-1b)(%bx) + .code32 +2: + + /* Load segment registers, adjust %esp */ + movw $(r2p_pmds-r2p_gdt), %ax + movw %ax, %ss + movl %ebp, %esp + movw %ax, %ds + movw %ax, %es + movw %ax, %fs + movw %ax, %gs + + /* Restore registers */ + popl %eax + popl %ebx + popl %ebp + + /* return to caller */ + ret + +r2p_gdt: + .word r2p_gdt_end - r2p_gdt - 1 /* limit */ + .long 0 /* addr */ + .word 0 +r2p_pmcs: + /* 32 bit protected mode code segment, physical addresses */ + .word 0xffff, 0 + .byte 0, 0x9f, 0xcf, 0 +r2p_pmds: + /* 32 bit protected mode data segment, physical addresses */ + .word 0xffff,0 + .byte 0,0x93,0xcf,0 +r2p_gdt_end: + +r2p_paddr: + .long 2b + .word r2p_pmcs - r2p_gdt, 0 + + + /* This is the end of the trampoline suffix code. + */ +r2p_params: /* Structure must match real_to_prot_params_t in realmode.h */ +r2p_ret_addr: .long 0 +r2p_esp: .long 0 +r2p_ebx: .long 0 +r2p_esi: .long 0 +r2p_edi: .long 0 +r2p_ebp: .long 0 +r2p_out_stack: .long 0 +r2p_out_stack_len: .long 0 + .globl _real_to_prot_suffix_end +_real_to_prot_suffix_end: + + .globl _real_to_prot_suffix_size + .equ _real_to_prot_suffix_size, _real_to_prot_suffix_end - _real_to_prot_suffix + .globl real_to_prot_suffix_size +real_to_prot_suffix_size: + .word _real_to_prot_suffix_size + +rm_callback_interface_end: + + .globl _rm_callback_interface_size + .equ _rm_callback_interface_size, rm_callback_interface_end - rm_callback_interface + .globl rm_callback_interface_size +rm_callback_interface_size: + .word _rm_callback_interface_size + +/**************************************************************************** + * END OF REAL-MODE CALLBACK INTERFACE + **************************************************************************** + */ + + +#ifdef PXE_EXPORT +/**************************************************************************** + * PXE CALLBACK INTERFACE + * + * Prepend this to rm_callback_interface to create a real-mode PXE + * callback interface. + **************************************************************************** + */ + .section ".text16", "ax", @progbits + .globl pxe_callback_interface + .code16 +pxe_callback_interface: + +/* Macro to calculate offset of labels within code segment in + * installed copy of code. + */ +#define INSTALLED(x) ( (x) - pxe_callback_interface ) + +/**************************************************************************** + * PXE entry points (!PXE and PXENV+ APIs) + **************************************************************************** + */ + /* in_call mechanism for !PXE API calls */ + .globl _pxe_in_call_far +_pxe_in_call_far: + /* Prepend "PXE API call" and "API version 0x201" to stack */ + pushl $0x201 + jmp 1f + /* in_call mechanism for PXENV+ API calls */ + .globl _pxenv_in_call_far +_pxenv_in_call_far: + /* Prepend "PXE API call" and "API version 0x200" to stack */ + pushl $0x200 +1: pushl $EB_OPCODE_PXE + /* Perform real-mode in_call */ + call pxe_rm_in_call + /* Return */ + addw $8, %sp + lret + +/**************************************************************************** + * PXE installation check (INT 1A) code + **************************************************************************** + */ + .globl _pxe_intercept_int1a +_pxe_intercept_int1a: + pushfw + cmpw $0x5650, %ax + jne 2f +1: /* INT 1A,5650 - Intercept */ + popfw + /* Set up return values according to PXE spec: */ + movw $0x564e, %ax /* AX := 564Eh (VN) */ + pushw %cs:INSTALLED(_pxe_pxenv_segment) + popw %es /* ES:BX := &(PXENV+ structure) */ + movw %cs:INSTALLED(_pxe_pxenv_offset), %bx + clc /* CF is cleared */ + lret $2 /* 'iret' without reloading flags */ +2: /* INT 1A,other - Do not intercept */ + popfw + ljmp %cs:*INSTALLED(_pxe_intercepted_int1a) + + .globl _pxe_intercepted_int1a +_pxe_intercepted_int1a: .word 0,0 + .globl _pxe_pxenv_location +_pxe_pxenv_location: +_pxe_pxenv_offset: .word 0 +_pxe_pxenv_segment: .word 0 + +pxe_rm_in_call: +pxe_attach_rm: + /* rm_callback_interface must be appended here */ + +pxe_callback_interface_end: + + .globl _pxe_callback_interface_size + .equ _pxe_callback_interface_size, pxe_callback_interface_end - pxe_callback_interface + .globl pxe_callback_interface_size +pxe_callback_interface_size: + .word _pxe_callback_interface_size + +#else /* PXE_EXPORT */ + +/* Define symbols used by the linker scripts, to prevent link errors */ + .globl _pxe_callback_interface_size + .equ _pxe_callback_interface_size, 0 + +#endif /* PXE_EXPORT */ + +#else /* CODE16 */ + +/* Define symbols used by the linker scripts, to prevent link errors */ + .globl _rm_callback_interface_size + .equ _rm_callback_interface_size, 0 + .globl _pxe_callback_interface_size + .equ _pxe_callback_interface_size, 0 + +#endif /* CODE16 */ diff --git a/src/arch/i386/core/start16.S b/src/arch/i386/core/start16.S new file mode 100644 index 00000000..72fcfbfb --- /dev/null +++ b/src/arch/i386/core/start16.S @@ -0,0 +1,285 @@ +/***************************************************************************** + * + * THIS FILE IS NOW OBSOLETE. + * + * The functions of this file are now placed in init.S. + * + ***************************************************************************** + */ + +#ifndef PCBIOS +#error "16bit code is only supported with the PCBIOS" +#endif + +#define CODE_SEG 0x08 +#define DATA_SEG 0x10 + +#define EXEC_IN_SITU_MAGIC 0x45524548 /* 'HERE' */ + + .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 + +/***************************************************************************** + * + * start16 : move payload to desired area of memory, set up for exit + * back to prefix, set up for 32-bit code. + * + * Enter (from prefix) with es:di = 0x4552:0x4548 if you want to + * prevent start16 from moving the payload. There are three + * motivations for moving the payload: + * + * 1. It may be in ROM, in which case we need to move it to RAM. + * 2. Whatever loaded us probably didn't know about our memory usage + * beyond the end of the image file. We should claim this memory + * before using it. + * + * Unless the prefix instructs us otherwise we will move the payload to: + * + * An area of memory claimed from the BIOS via 40:13. + * + * We use the main Etherboot stack (within the image target) as our + * stack; we don't rely on the prefix passing us a stack usable for + * anything other than the prefix's return address. The (first 512 + * bytes of the) prefix code segment is copied to a safe archive + * location. + * + * When we return to the prefix (from start32), we copy this code back + * to a new area of memory, restore the prefix's ss:sp and ljmp back + * to the copy of the prefix. The prefix will see a return from + * start16 *but* may be executing at a new location. Code following + * the lcall to start16 must therefore be position-independent and + * must also be within [cs:0000,cs:01ff]. We make absolutely no + * guarantees about the stack contents when the prefix regains + * control. + * + * Trashes just about all registers, including all the segment + * registers. + * + ***************************************************************************** + */ + + .text + .code16 + .arch i386 + .org 0 + .globl _start16 +_start16: + +/***************************************************************************** + * Work out where we are going to place our image (image = optional + * decompressor + runtime). Exit this stage with %ax containing the + * runtime target address divided by 16 (i.e. a real-mode segment + * address). + ***************************************************************************** + */ + movw %es, %ax + cmpw $(EXEC_IN_SITU_MAGIC >> 16), %ax + jne exec_moved + cmpw $(EXEC_IN_SITU_MAGIC & 0xffff), %di + jne exec_moved +exec_in_situ: + /* Prefix has warned us not to move the payload. Simply + * calculate where the image is going to end up, so we can + * work out where to put our stack. + */ + movw %cs, %ax + addw $((payload-_start16)/16), %ax + jmp 99f +exec_moved: + /* Claim an area of base memory from the BIOS and put the + * payload there. arch_relocated_to() will deal with freeing + * up this memory once we've relocated to high memory. + */ + movw $0x40, %ax + movw %ax, %es + movw %es:(0x13), %ax /* FBMS in kb to %ax */ + shlw $6, %ax /* ... in paragraphs */ + subw $__image_size_pgh, %ax /* Subtract space for image */ + shrw $6, %ax /* Round down to nearest kb */ + movw %ax, %es:(0x13) /* ...and claim memory from BIOS */ + shlw $6, %ax +99: + /* At this point %ax contains the segment address for the + * start of the image (image = optional decompressor + runtime). + */ + +/***************************************************************************** + * Set up stack in start32's stack space within the place we're going + * to copy Etherboot to, reserve space for GDT, copy return address + * from prefix stack, store prefix stack address + ***************************************************************************** + */ + popl %esi /* Return address */ + mov %ss, %bx /* %es:di = prefix stack address */ + mov %bx, %es /* (*after* pop of return address) */ + movw %sp, %di + movw $__offset_stack_pgh, %bx /* Set up Etherboot stack */ + addw %ax, %bx + movw %bx, %ss + movw $__stack_size, %sp + subw $(_gdt_end - _gdt), %sp /* Reserve space for GDT */ + movw %sp, %bp /* Record GDT location */ + /* Set up i386_rm_in_call_data_t structure on stack. This is + * the same structure as is set up by rm_in_call. + */ + pushl $0 /* Dummy opcode */ + pushl %esi /* Prefix return address */ + pushfw /* Flags */ + pushw %di /* Prefix %sp */ + pushw %gs /* Segment registers */ + pushw %fs + pushw %es + pushw %ds + pushw %es /* Prefix %ss */ + pushw %cs + /* Stack is now 32-bit aligned */ + + /* %ax still contains image target segment address */ + +/***************************************************************************** + * Calculate image target and prefix code physical addresses, store on stack + * for use in copy routine. + ***************************************************************************** + */ + movzwl %es:-2(%di), %ebx /* Prefix code segment */ + shll $4, %ebx + pushl %ebx /* Prefix code physical address */ + movzwl %ax, %edi /* Image target segment */ + shll $4, %edi + pushl %edi /* Image target physical address */ + +/***************************************************************************** + * Transition to 32-bit protected mode. Set up all segment + * descriptors to use flat physical addresses. + ***************************************************************************** + */ + /* Copy gdt to area reserved on stack + */ + push %cs /* GDT source location -> %ds:%si */ + pop %ds + mov $(_gdt - _start16), %si + push %ss /* GDT target location -> %es:%di */ + pop %es + mov %bp, %di + mov $(_gdt_end - _gdt), %cx + cld + rep movsb /* Copy GDT to stack */ + movl %ss, %eax + shll $4, %eax + movzwl %bp, %ebx + addl %eax, %ebx /* Physical addr of GDT copy -> %ebx */ + movl %ebx, 2(%bp) /* Fill in addr field in GDT */ + + /* Compute the offset I am running at. + */ + movl %cs, %ebx + shll $4, %ebx /* %ebx = offset for start16 symbols */ + + /* Switch to 32bit protected mode. + */ + cli /* Disable interrupts */ + lgdt (%bp) /* Load GDT from stack */ + movl %cr0, %eax /* Set protected mode bit */ + orb $CR0_PE, %al + movl %eax, %cr0 + movl %ss, %eax /* Convert stack pointer to 32bit */ + shll $4, %eax + movzwl %sp, %esp + addl %eax, %esp + movl $DATA_SEG, %eax /* Reload the segment registers */ + movl %eax, %ds + movl %eax, %es + movl %eax, %ss + movl %eax, %fs + movl %eax, %gs + /* Flush prefetch queue, and reload %cs:%eip by effectively ljmping + * to code32_start. Do the jump via pushl and lret because the text + * may not be writable/ + */ + pushl $CODE_SEG + ADDR32 leal (code32_start-_start16)(%ebx), %eax + pushl %eax + DATA32 lret /* DATA32 needed, because we're still in 16-bit mode */ + +_gdt: +gdtarg: + .word _gdt_end - _gdt - 1 /* limit */ + .long 0 /* addr */ + .word 0 +_pmcs: + /* 32 bit protected mode code segment */ + .word 0xffff, 0 + .byte 0, 0x9f, 0xcf, 0 +_pmds: + /* 32 bit protected mode data segment */ + .word 0xffff,0 + .byte 0,0x93,0xcf,0 +_gdt_end: + + .code32 +code32_start: + +/***************************************************************************** + * Copy payload to target location. Do the copy backwards, since if + * there's overlap with a forward copy then it means start16 is going + * to get trashed during the copy anyway... + ***************************************************************************** + */ + popl %edi /* Image target physical address */ + pushl %edi + leal (payload-_start16)(%ebx), %esi /* Image source physical addr */ + movl $__payload_size, %ecx /* Payload size (not image size) */ + addl %ecx, %edi /* Start at last byte (length - 1) */ + decl %edi + addl %ecx, %esi + decl %esi + std /* Backward copy of image */ + rep movsb + cld + popl %edi /* Restore image target physical address */ + leal __decompressor_uncompressed(%edi), %ebx + subl $_text, %ebx /* %ebx = offset for runtime symbols */ + +/***************************************************************************** + * Copy prefix to storage area within Etherboot image. + ***************************************************************************** + */ + popl %esi /* Prefix source physical address */ + pushl %edi + leal _prefix_copy(%ebx), %edi /* Prefix copy phys. addr. */ + leal _eprefix_copy(%ebx), %ecx + subl %edi, %ecx /* Prefix copy size */ + rep movsb /* Forward copy of prefix */ + popl %edi /* Restore image target physical address */ + +/***************************************************************************** + * Record base memory used by Etherboot image + ***************************************************************************** + */ + movl %edi, _prefix_image_basemem (%ebx) + +/***************************************************************************** + * Jump to start of the image (i.e. the decompressor, or start32 if + * non-compressed). + ***************************************************************************** + */ + pushl $0 /* Inform start32 that exit path is 16-bit */ + jmpl *%edi /* Jump to image */ + + .balign 16 + /* Etherboot needs to be 16byte aligned or data that + * is virtually aligned is no longer physically aligned + * which is just nasty in general. 16byte alignment + * should be sufficient though. + */ +payload: diff --git a/src/arch/i386/core/start16.lds b/src/arch/i386/core/start16.lds new file mode 100644 index 00000000..544fc78f --- /dev/null +++ b/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/src/arch/i386/core/start16z.lds b/src/arch/i386/core/start16z.lds new file mode 100644 index 00000000..711bcf7b --- /dev/null +++ b/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/src/arch/i386/core/start32.S b/src/arch/i386/core/start32.S new file mode 100644 index 00000000..6dc3f203 --- /dev/null +++ b/src/arch/i386/core/start32.S @@ -0,0 +1,767 @@ +/* #defines because ljmp wants a number, probably gas bug */ +/* .equ KERN_CODE_SEG,_pmcs-_gdt */ +#define KERN_CODE_SEG 0x08 + .equ KERN_DATA_SEG,_pmds-_gdt +/* .equ REAL_CODE_SEG,_rmcs-_gdt */ +#define REAL_CODE_SEG 0x18 + .equ REAL_DATA_SEG,_rmds-_gdt + .equ FLAT_CODE_SEG,_pmcs2-_gdt + .equ FLAT_DATA_SEG,_pmds2-_gdt + .equ CR0_PE,1 +#ifdef CONFIG_X86_64 + .equ LM_CODE_SEG, _lmcs-_gdt + .equ LM_DATA_SEG, _lmds-_gdt +#endif + + .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 + +#define BOCHSBP xchgw %bx, %bx + +#include "callbacks.h" +#define NUM_PUSHA_REGS (8) +#define NUM_SEG_REGS (6) + +/* + * 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. + */ + +/************************************************************************** + * START + * + * This file is no longer enterered from the top. init.S will jump to + * either _in_call or _rm_in_call, depending on the processor mode + * when init.S was entered. + **************************************************************************/ + .text + .arch i386 + .code32 + +/************************************************************************** +_IN_CALL - make a call in to Etherboot. +**************************************************************************/ + +/* There are two 32-bit entry points: _in_call and _in_call_far, for + * near calls and far calls respectively. Both should be called with + * flat physical addresses. They will result in a call to the C + * routine in_call(); see there for API details. + * + * Note that this routine makes fairly heavy use of the stack and no + * use of fixed data areas. This is because it must be re-entrant; + * there may be more than one concurrent call in to Etherboot. + */ + +#define IC_OFFSET_VA_LIST_PTR ( 0 ) +#define IC_OFFSET_VA_LIST_PTR_E ( IC_OFFSET_VA_LIST_PTR + 4 ) +#define IC_OFFSET_REGISTERS ( IC_OFFSET_VA_LIST_PTR_E ) +#define IC_OFFSET_REGISTERS_E ( IC_OFFSET_REGISTERS + ( NUM_PUSHA_REGS * 4 ) ) +#define IC_OFFSET_SEG_REGS ( IC_OFFSET_REGISTERS_E ) +#define IC_OFFSET_SEG_REGS_E ( IC_OFFSET_SEG_REGS + ( NUM_SEG_REGS * 2 ) ) +#define IC_OFFSET_GDT ( IC_OFFSET_SEG_REGS_E ) +#define IC_OFFSET_GDT_E ( IC_OFFSET_GDT + 8 ) +#define IC_OFFSET_FLAGS ( IC_OFFSET_GDT_E ) +#define IC_OFFSET_FLAGS_E ( IC_OFFSET_FLAGS + 4 ) +#define IC_OFFSET_RETADDR ( IC_OFFSET_FLAGS_E ) +#define IC_OFFSET_RETADDR_E ( IC_OFFSET_RETADDR + 8 ) +#define IC_OFFSET_ORIG_STACK ( IC_OFFSET_RETADDR ) +#define IC_OFFSET_OPCODE ( IC_OFFSET_ORIG_STACK + 8 ) +#define IC_OFFSET_OPCODE_E ( IC_OFFSET_OPCODE + 4 ) +#define IC_OFFSET_VA_LIST ( IC_OFFSET_OPCODE_E ) + + .code32 + .globl _in_call + .globl _in_call_far +_in_call: + /* Expand to far return address */ + pushl %eax /* Store %eax */ + xorl %eax, %eax + movw %cs, %ax + xchgl %eax, 4(%esp) /* 4(%esp) = %cs, %eax = ret addr */ + xchgl %eax, 0(%esp) /* 0(%esp) = ret addr, restore %eax */ +_in_call_far: + /* Store flags */ + pushfl + /* Store the GDT */ + subl $8, %esp + sgdt 0(%esp) + /* Store segment register values */ + pushw %gs + pushw %fs + pushw %es + pushw %ds + pushw %ss + pushw %cs + /* Store general-purpose register values */ + pushal + /* Replace %esp in store with physical %esp value on entry */ + leal (IC_OFFSET_ORIG_STACK - IC_OFFSET_REGISTERS)(%esp), %eax + movl %eax, (IC_OFFSET_REGISTERS - IC_OFFSET_REGISTERS + 12)(%esp) + /* Store va_list pointer (physical address) */ + leal (IC_OFFSET_VA_LIST - IC_OFFSET_VA_LIST_PTR_E)(%esp), %eax + pushl %eax + /* IC_OFFSET_*(%esp) are now valid */ + + /* Switch to virtual addresses */ + call _phys_to_virt + + /* Fixup the va_list pointer */ + movl virt_offset, %ebp + subl %ebp, IC_OFFSET_VA_LIST_PTR(%esp) + + /* Check opcode for EB_USE_INTERNAL_STACK flag */ + movl IC_OFFSET_OPCODE(%esp), %eax + testl $EB_USE_INTERNAL_STACK, %eax + je 2f + /* Use internal stack flag set */ + /* Check %esp is not already in internal stack range */ + leal _stack, %esi /* %esi = bottom of internal stack */ + leal _estack, %edi /* %edi = top of internal stack */ + cmpl %esi, %esp + jb 1f + cmpl %edi, %esp + jbe 2f +1: /* %esp not currently in internal stack range */ + movl %esp, %esi /* %esi = original stack */ + movl $IC_OFFSET_OPCODE_E, %ecx /* %ecx = length to transfer */ + subl %ecx, %edi /* %edi = internal stack pos */ + movl %edi, %esp /* = new %esp */ + rep movsb /* Copy data to internal stack */ +2: + + /* Call to C code */ + call i386_in_call + + /* Set %eax (return code from C) in registers structure on + * stack, so that we return it to the caller. + */ + movl %eax, (IC_OFFSET_REGISTERS + 28)(%esp) + + /* Calculate physical continuation address */ + movl virt_offset, %ebp + movzwl (IC_OFFSET_SEG_REGS + 0)(%esp), %eax /* %cs */ + movzwl (IC_OFFSET_SEG_REGS + 2)(%esp), %ebx /* %ss */ + pushl %eax /* Continuation segment */ + leal 1f(%ebp), %eax + pushl %eax /* Continuation offset */ + + /* Restore caller's GDT */ + cli /* Temporarily disable interrupts */ + lgdt (8+IC_OFFSET_GDT)(%esp) + /* Reset %ss and adjust %esp */ + movw %bx, %ss + addl %ebp, %esp + lret /* Reload %cs:eip, flush prefetch */ +1: + + /* Skip va_list ptr */ + popl %eax + /* Reload general-purpose registers to be returned */ + popal + /* Reload segment registers as passed in from caller */ + popw %gs + popw %fs + popw %es + popw %ds + addl $(4+8), %esp /* Skip %cs, %ss and GDT (already reloaded) */ + /* Restore flags (including revert of interrupt status) */ + popfl + + /* Restore physical %esp from entry. It will only be + * different if EB_USE_INTERNAL_STACK was specified. + */ + movl ( 12 + IC_OFFSET_REGISTERS - IC_OFFSET_RETADDR )(%esp), %esp + + /* Check for EB_SKIP_OPCODE */ + pushfl + testl $EB_SKIP_OPCODE, 12(%esp) + jnz 1f + /* Normal return */ + popfl + lret +1: /* Return and skip opcode */ + popfl + lret $4 + +/************************************************************************** +RELOCATE_TO - relocate etherboot to the specified address +**************************************************************************/ + .globl relocate_to +relocate_to: + /* Save the callee save registers */ + pushl %ebp + pushl %esi + pushl %edi + + /* Compute the virtual destination address */ + movl 16(%esp), %edi # dest + subl virt_offset, %edi + + + /* Compute the new value of virt_offset */ + movl 16(%esp), %ebp # virt_offset + subl $_text, %ebp + + /* Fixup the gdt */ + pushl $_pmcs + pushl %ebp # virt_offset + call set_seg_base + addl $8, %esp + + /* Fixup gdtarg */ + leal _gdt(%ebp), %eax + movl %eax, gdtarg +2 + + /* Fixup virt_offset */ + movl %ebp, virt_offset + + /* Load the move parameters */ + movl $_text, %esi + movl $_end, %ecx + subl %esi, %ecx + + /* Move etherboot uses %esi, %edi, %ecx */ + rep + movsb + + /* Reload the gdt */ + cs + lgdt gdtarg + + /* Reload %cs */ + ljmp $KERN_CODE_SEG, $1f +1: + /* reload other segment registers */ + movl $KERN_DATA_SEG, %eax + movl %eax,%ds + movl %eax,%es + movl %eax,%ss + movl %eax,%fs + movl %eax,%gs + + /* Restore the callee save registers */ + popl %edi + popl %esi + popl %ebp + + /* return */ + ret + +/************************************************************************** +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 $FLAT_CODE_SEG + 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 */ + movl 52(%esp), %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 */ + +/************************************************************************** +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 + +/************************************************************************** +_VIRT_TO_PHYS - Transition from virtual to physical addresses + Preserves all preservable registers and flags +**************************************************************************/ + .globl _virt_to_phys +_virt_to_phys: + pushfl + pushl %ebp + pushl %eax + movl virt_offset, %ebp /* Load virt_offset */ + addl %ebp, 12(%esp) /* Adjust the return address */ + + /* reload the code segment */ + pushl $FLAT_CODE_SEG + leal 1f(%ebp), %eax + pushl %eax + lret + +1: + /* reload other segment registers */ + movl $FLAT_DATA_SEG, %eax + movl %eax, %ds + movl %eax, %es + movl %eax, %ss + addl %ebp, %esp /* Adjust the stack pointer */ + movl %eax, %fs + movl %eax, %gs + + popl %eax + popl %ebp + popfl + ret + + +/************************************************************************** +_PHYS_TO_VIRT - Transition from using physical to virtual addresses + Preserves all preservable registers and flags +**************************************************************************/ + .globl _phys_to_virt +_phys_to_virt: + pushfl + pushl %ebp + pushl %eax + + call 1f +1: popl %ebp + subl $1b, %ebp + movl %ebp, virt_offset(%ebp) + + /* Fixup the gdt */ + leal _pmcs(%ebp), %eax + pushl %eax + pushl %ebp + call set_seg_base + addl $8, %esp + + /* Fixup gdtarg */ + leal _gdt(%ebp), %eax + movl %eax, (gdtarg+2)(%ebp) + + /* Load the global descriptor table */ + cli + lgdt %cs:gdtarg(%ebp) + ljmp $KERN_CODE_SEG, $1f +1: + /* reload other segment regsters */ + movl $KERN_DATA_SEG, %eax + movl %eax, %ds + movl %eax, %es + movl %eax, %ss + subl %ebp, %esp /* Adjust the stack pointer */ + movl %eax, %fs + movl %eax, %gs + + subl %ebp, 12(%esp) /* Adjust the return address */ + popl %eax + popl %ebp + popfl + ret + + +/************************************************************************** +SET_SEG_BASE - Set the base address of a segment register +**************************************************************************/ + .globl set_seg_base +set_seg_base: + pushl %eax + pushl %ebx + movl 12(%esp), %eax /* %eax = base address */ + movl 16(%esp), %ebx /* %ebx = &code_descriptor */ + movw %ax, (0+2)(%ebx) /* CS base bits 0-15 */ + movw %ax, (8+2)(%ebx) /* DS base bits 0-15 */ + shrl $16, %eax + movb %al, (0+4)(%ebx) /* CS base bits 16-23 */ + movb %al, (8+4)(%ebx) /* DS base bits 16-23 */ + movb %ah, (0+7)(%ebx) /* CS base bits 24-31 */ + movb %ah, (8+7)(%ebx) /* DS base bits 24-31 */ + popl %ebx + popl %eax + ret + +/************************************************************************** +GLOBAL DESCRIPTOR TABLE +**************************************************************************/ + .data + .align 4 + + .globl _gdt + .globl gdtarg +_gdt: +gdtarg: + .word _gdt_end - _gdt - 1 /* limit */ + .long _gdt /* addr */ + .word 0 + + .globl _pmcs +_pmcs: + /* 32 bit protected mode code segment */ + .word 0xffff,0 + .byte 0,0x9f,0xcf,0 + +_pmds: + /* 32 bit protected mode data segment */ + .word 0xffff,0 + .byte 0,0x93,0xcf,0 + +_rmcs: + /* 16 bit real mode code segment */ + .word 0xffff,(0&0xffff) + .byte (0>>16),0x9b,0x00,(0>>24) + +_rmds: + /* 16 bit real mode data segment */ + .word 0xffff,(0&0xffff) + .byte (0>>16),0x93,0x00,(0>>24) + +_pmcs2: + /* 32 bit protected mode code segment, base 0 */ + .word 0xffff,0 + .byte 0,0x9f,0xcf,0 + +_pmds2: + /* 32 bit protected mode data segment, base 0 */ + .word 0xffff,0 + .byte 0,0x93,0xcf,0 + +#ifdef CONFIG_X86_64 +_lmcs: + /* 64bit long mode code segment, base 0 */ + .word 0xffff, 0 + .byte 0x00, 0x9f, 0xaf , 0x00 +_lmds: + /* 64bit long mode data segment, base 0 */ + .word 0xffff, 0 + .byte 0x00, 0x93, 0xcf, 0x00 +#endif +_gdt_end: + + /* The initial register contents */ + .balign 4 + .globl initial_regs +initial_regs: + .fill 8, 4, 0 + + /* The virtual address offset */ + .globl virt_offset +virt_offset: + .long 0 + + .section ".stack" + .p2align 3 + /* allocate a 4K stack in the stack segment */ + .globl _stack +_stack: + .space 4096 + .globl _estack +_estack: +#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/src/arch/i386/core/tagged_loader.c b/src/arch/i386/core/tagged_loader.c new file mode 100644 index 00000000..9c6d6c25 --- /dev/null +++ b/src/arch/i386/core/tagged_loader.c @@ -0,0 +1,201 @@ +#include "realmode.h" +#include "segoff.h" + +struct segheader +{ + unsigned char length; + unsigned char vendortag; + unsigned char reserved; + unsigned char flags; + unsigned long loadaddr; + unsigned long imglength; + unsigned long memlength; +}; + +struct imgheader +{ + unsigned long magic; + unsigned long length; /* and flags */ + union + { + segoff_t segoff; + unsigned long location; + } u; + unsigned long execaddr; +}; + +/* Keep all context about loaded image in one place */ +static struct tagged_context +{ + struct imgheader img; /* copy of header */ + unsigned long linlocation; /* addr of header */ + unsigned long last0, last1; /* of prev segment */ + unsigned long segaddr, seglen; /* of current segment */ + unsigned char segflags; + unsigned char first; + unsigned long curaddr; +} tctx; + +#define TAGGED_PROGRAM_RETURNS (tctx.img.length & 0x00000100) /* bit 8 */ +#define LINEAR_EXEC_ADDR (tctx.img.length & 0x80000000) /* bit 31 */ + +static sector_t tagged_download(unsigned char *data, unsigned int len, int eof); +void xstart16 (unsigned long execaddr, segoff_t location, + void *bootp); + +static inline os_download_t tagged_probe(unsigned char *data, unsigned int len) +{ + struct segheader *sh; + unsigned long loc; + if (*((uint32_t *)data) != 0x1B031336L) { + return 0; + } + printf("(NBI)"); + /* If we don't have enough data give up */ + if (len < 512) + return dead_download; + /* Zero all context info */ + memset(&tctx, 0, sizeof(tctx)); + /* Copy first 4 longwords */ + memcpy(&tctx.img, data, sizeof(tctx.img)); + /* Memory location where we are supposed to save it */ + tctx.segaddr = tctx.linlocation = + ((tctx.img.u.segoff.segment) << 4) + tctx.img.u.segoff.offset; + if (!prep_segment(tctx.segaddr, tctx.segaddr + 512, tctx.segaddr + 512, + 0, 512)) { + return dead_download; + } + /* Now verify the segments we are about to load */ + loc = 512; + for(sh = (struct segheader *)(data + + ((tctx.img.length & 0x0F) << 2) + + ((tctx.img.length & 0xF0) >> 2) ); + (sh->length > 0) && ((unsigned char *)sh < data + 512); + sh = (struct segheader *)((unsigned char *)sh + + ((sh->length & 0x0f) << 2) + ((sh->length & 0xf0) >> 2)) ) { + if (!prep_segment( + sh->loadaddr, + sh->loadaddr + sh->imglength, + sh->loadaddr + sh->imglength, + loc, loc + sh->imglength)) { + return dead_download; + } + loc = loc + sh->imglength; + if (sh->flags & 0x04) + break; + } + if (!(sh->flags & 0x04)) + return dead_download; + /* Grab a copy */ + memcpy(phys_to_virt(tctx.segaddr), data, 512); + /* Advance to first segment descriptor */ + tctx.segaddr += ((tctx.img.length & 0x0F) << 2) + + ((tctx.img.length & 0xF0) >> 2); + /* Remember to skip the first 512 data bytes */ + tctx.first = 1; + + return tagged_download; + +} +static sector_t tagged_download(unsigned char *data, unsigned int len, int eof) +{ + int i; + + if (tctx.first) { + tctx.first = 0; + if (len > 512) { + len -= 512; + data += 512; + /* and fall through to deal with rest of block */ + } else + return 0; + } + for (;;) { + if (len == 0) /* Detect truncated files */ + eof = 0; + while (tctx.seglen == 0) { + struct segheader sh; + if (tctx.segflags & 0x04) { + done(1); + if (LINEAR_EXEC_ADDR) { + int result; + /* no gateA20_unset for PM call */ + result = xstart32(tctx.img.execaddr, + virt_to_phys(&loaderinfo), + tctx.linlocation, + virt_to_phys(BOOTP_DATA_ADDR)); + printf("Secondary program returned %d\n", + result); + if (!TAGGED_PROGRAM_RETURNS) { + /* We shouldn't have returned */ + result = -2; + } + if (result == 0) + result = -2; + longjmp(restart_etherboot, result); + + } else { + gateA20_unset(); + xstart16(tctx.img.execaddr, + tctx.img.u.segoff, + BOOTP_DATA_ADDR); + longjmp(restart_etherboot, -2); + } + } + sh = *((struct segheader *)phys_to_virt(tctx.segaddr)); + tctx.seglen = sh.imglength; + if ((tctx.segflags = sh.flags & 0x03) == 0) + tctx.curaddr = sh.loadaddr; + else if (tctx.segflags == 0x01) + tctx.curaddr = tctx.last1 + sh.loadaddr; + else if (tctx.segflags == 0x02) + tctx.curaddr = (Address)(meminfo.memsize * 1024L + + 0x100000L) + - sh.loadaddr; + else + tctx.curaddr = tctx.last0 - sh.loadaddr; + tctx.last1 = (tctx.last0 = tctx.curaddr) + sh.memlength; + tctx.segflags = sh.flags; + tctx.segaddr += ((sh.length & 0x0F) << 2) + + ((sh.length & 0xF0) >> 2); + /* Avoid lock-up */ + if ( sh.length == 0 ) longjmp(restart_etherboot, -2); + } + if ((len <= 0) && !eof) + break; + i = (tctx.seglen > len) ? len : tctx.seglen; + memcpy(phys_to_virt(tctx.curaddr), data, i); + tctx.seglen -= i; + tctx.curaddr += i; + len -= i; + data += i; + } + return 0; +} + +void xstart16 (unsigned long execaddr, segoff_t location, + void *bootp) { + struct { + segoff_t execaddr; + segoff_t location; + segoff_t bootp; + } PACKED in_stack; + + /* AFAICT, execaddr is actually already a segment:offset */ + *((unsigned long *)&in_stack.execaddr) = execaddr; + in_stack.location = location; + in_stack.bootp.segment = SEGMENT(bootp); + in_stack.bootp.offset = OFFSET(bootp); + + RM_FRAGMENT(rm_xstart16, + "popl %eax\n\t" /* Calculated lcall */ + "pushw %cs\n\t" + "call 1f\n1:\tpopw %bp\n\t" + "leaw (2f-1b)(%bp), %bx\n\t" + "pushw %bx\n\t" + "pushl %eax\n\t" + "lret\n2:\n\t" + ); + + real_call ( rm_xstart16, &in_stack, NULL ); +} diff --git a/src/arch/i386/core/video_subr.c b/src/arch/i386/core/video_subr.c new file mode 100644 index 00000000..dccdd97c --- /dev/null +++ b/src/arch/i386/core/video_subr.c @@ -0,0 +1,94 @@ +/* + * + * modified from linuxbios code + * by Cai Qiang <rimy2000@hotmail.com> + * + */ + +#ifdef CONSOLE_DIRECT_VGA + +#include <etherboot.h> +#include <vga.h> + +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) +{ + int i; + u16 *ss = (u16 *) s; + + for (i = 0; i < n; i++) { + ss[i] = ( u16 ) c; + } +} + +void video_init(void) +{ + static int inited=0; + + vidmem = (unsigned 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] = ' '; +} + +void vga_putc(unsigned char 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); +} + +#endif + diff --git a/src/arch/i386/core/wince_loader.c b/src/arch/i386/core/wince_loader.c new file mode 100644 index 00000000..f452b659 --- /dev/null +++ b/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/src/arch/i386/drivers/net/undi.c b/src/arch/i386/drivers/net/undi.c new file mode 100644 index 00000000..084fc18a --- /dev/null +++ b/src/arch/i386/drivers/net/undi.c @@ -0,0 +1,1458 @@ +/************************************************************************** +Etherboot - BOOTP/TFTP Bootstrap Program +UNDI NIC driver for Etherboot + +This file Copyright (C) 2003 Michael Brown <mbrown@fensystems.co.uk> +of Fen Systems Ltd. (http://www.fensystems.co.uk/). All rights +reserved. + +$Id$ +***************************************************************************/ + +/* + * 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. + */ + +#ifdef PCBIOS + +/* to get some global routines like printf */ +#include "etherboot.h" +/* to get the interface to the body of the program */ +#include "nic.h" +/* to get the PCI support functions, if this is a PCI NIC */ +#include "pci.h" +/* UNDI and PXE defines. Includes pxe.h. */ +#include "undi.h" +/* 8259 PIC defines */ +#include "pic8259.h" +/* Real-mode calls */ +#include "realmode.h" +/* E820 map mangler */ +#include "hidemem.h" + +/* NIC specific static variables go here */ +static undi_t undi = { + .pnp_bios = NULL, + .rom = NULL, + .undi_rom_id = NULL, + .pxe = NULL, + .pxs = NULL, + .xmit_data = NULL, + .base_mem_data = NULL, + .driver_code = NULL, + .driver_code_size = 0, + .driver_data = NULL, + .driver_data_size = 0, + .xmit_buffer = NULL, + .prestarted = 0, + .started = 0, + .initialized = 0, + .opened = 0, + .pci = { 0, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, + .irq = IRQ_NONE +}; + +/* Function prototypes */ +static int allocate_base_mem_data ( void ); +static int free_base_mem_data ( void ); +static int eb_pxenv_undi_shutdown ( void ); +static int eb_pxenv_stop_undi ( void ); +static int undi_unload_base_code ( void ); +static int undi_full_shutdown ( void ); + +/* Trivial/nontrivial IRQ handler selection */ +#ifdef UNDI_NONTRIVIAL_IRQ +static void nontrivial_irq_handler ( void ); +static void nontrivial_irq_handler_end ( void ); +static int install_nontrivial_irq_handler ( irq_t irq ); +static int remove_nontrivial_irq_handler ( irq_t irq ); +static int nontrivial_irq_triggered ( irq_t irq ); +static int copy_nontrivial_irq_handler ( void *target, size_t target_size ); +#define NONTRIVIAL_IRQ_HANDLER_SIZE FRAGMENT_SIZE(nontrivial_irq_handler) +#define install_undi_irq_handler(irq) install_nontrivial_irq_handler(irq) +#define remove_undi_irq_handler(irq) remove_nontrivial_irq_handler(irq) +#define undi_irq_triggered(irq) nontrivial_irq_triggered(irq) +#define UNDI_IRQ_HANDLER_SIZE NONTRIVIAL_IRQ_HANDLER_SIZE +#define copy_undi_irq_handler(dest,size) copy_nontrivial_irq_handler(dest,size) +#else +#define install_undi_irq_handler(irq) install_trivial_irq_handler(irq) +#define remove_undi_irq_handler(irq) remove_trivial_irq_handler(irq) +#define undi_irq_triggered(irq) trivial_irq_triggered(irq) +#define UNDI_IRQ_HANDLER_SIZE TRIVIAL_IRQ_HANDLER_SIZE +#define copy_undi_irq_handler(dest,size) copy_trivial_irq_handler(dest,size) +#endif /* UNDI_NONTRIVIAL_IRQ */ + +/* Size of variable-length data in base_mem_data */ +#define BASE_MEM_VARDATA_SIZE ( UNDI_IRQ_HANDLER_SIZE > e820mangler_size ? \ + UNDI_IRQ_HANDLER_SIZE : e820mangler_size ) + +/************************************************************************** + * Utility functions + **************************************************************************/ + +/* Checksum a block. + */ + +static uint8_t checksum ( void *block, size_t size ) { + uint8_t sum = 0; + uint16_t i = 0; + for ( i = 0; i < size; i++ ) { + sum += ( ( uint8_t * ) block )[i]; + } + return sum; +} + +/* Print the status of a !PXE structure + */ + +static void pxe_dump ( void ) { + printf ( "API %hx:%hx St %hx:%hx UD %hx:%hx UC %hx:%hx " + "BD %hx:%hx BC %hx:%hx\n", + undi.pxe->EntryPointSP.segment, undi.pxe->EntryPointSP.offset, + undi.pxe->Stack.Seg_Addr, undi.pxe->Stack.Seg_Size, + undi.pxe->UNDIData.Seg_Addr, undi.pxe->UNDIData.Seg_Size, + undi.pxe->UNDICode.Seg_Addr, undi.pxe->UNDICode.Seg_Size, + undi.pxe->BC_Data.Seg_Addr, undi.pxe->BC_Data.Seg_Size, + undi.pxe->BC_Code.Seg_Addr, undi.pxe->BC_Code.Seg_Size ); +} + +/* Allocate/free space for structures that must reside in base memory + */ + +static int allocate_base_mem_data ( void ) { + /* Allocate space in base memory. + * Initialise pointers to base memory structures. + */ + if ( undi.base_mem_data == NULL ) { + undi.base_mem_data = + allot_base_memory ( sizeof(undi_base_mem_data_t) + + BASE_MEM_VARDATA_SIZE ); + if ( undi.base_mem_data == NULL ) { + printf ( "Failed to allocate base memory\n" ); + free_base_mem_data(); + return 0; + } + memset ( undi.base_mem_data, 0, sizeof(undi_base_mem_data_t) ); + undi.pxs = &undi.base_mem_data->pxs; + undi.xmit_data = &undi.base_mem_data->xmit_data; + undi.xmit_buffer = undi.base_mem_data->xmit_buffer; + } + return 1; +} + +static int free_base_mem_data ( void ) { + if ( undi.base_mem_data != NULL ) { + forget_base_memory ( undi.base_mem_data, + sizeof(undi_base_mem_data_t) + + BASE_MEM_VARDATA_SIZE ); + undi.base_mem_data = NULL; + undi.pxs = NULL; + undi.xmit_data = NULL; + undi.xmit_buffer = NULL; + copy_undi_irq_handler ( NULL, 0 ); + } + return 1; +} + +static void assemble_firing_squad ( firing_squad_lineup_t *lineup, + void *start, size_t size, + firing_squad_shoot_t shoot ) { + int target; + int index; + int bit; + int start_kb = virt_to_phys(start) >> 10; + int end_kb = ( virt_to_phys(start+size) + (1<<10) - 1 ) >> 10; + + for ( target = start_kb; target <= end_kb; target++ ) { + index = FIRING_SQUAD_TARGET_INDEX ( target ); + bit = FIRING_SQUAD_TARGET_BIT ( target ); + lineup->targets[index] = ( shoot << bit ) | + ( lineup->targets[index] & ~( 1 << bit ) ); + } +} + +static void shoot_targets ( firing_squad_lineup_t *lineup ) { + int shoot_this_target = 0; + int shoot_last_target = 0; + int start_target = 0; + int target; + + for ( target = 0; target <= 640; target++ ) { + shoot_this_target = ( target == 640 ? 0 : + ( 1 << FIRING_SQUAD_TARGET_BIT(target) ) & + lineup->targets[FIRING_SQUAD_TARGET_INDEX(target)] ); + if ( shoot_this_target && !shoot_last_target ) { + start_target = target; + } else if ( shoot_last_target && !shoot_this_target ) { + size_t range_size = ( target - start_target ) << 10; + forget_base_memory ( phys_to_virt( start_target<<10 ), + range_size ); + } + shoot_last_target = shoot_this_target; + } +} + +/* Debug macros + */ + +#ifdef TRACE_UNDI +#define DBG(...) printf ( __VA_ARGS__ ) +#else +#define DBG(...) +#endif + +#define UNDI_STATUS(pxs) ( (pxs)->Status == PXENV_EXIT_SUCCESS ? \ + "SUCCESS" : \ + ( (pxs)->Status == PXENV_EXIT_FAILURE ? \ + "FAILURE" : "UNKNOWN" ) ) + +/************************************************************************** + * Base memory scanning functions + **************************************************************************/ + +/* Locate the $PnP structure indicating a PnP BIOS. + */ + +static int hunt_pnp_bios ( void ) { + uint32_t off = 0x10000; + + printf ( "Hunting for PnP BIOS..." ); + while ( off > 0 ) { + off -= 16; + undi.pnp_bios = (pnp_bios_t *) phys_to_virt ( 0xf0000 + off ); + if ( undi.pnp_bios->signature == PNP_BIOS_SIGNATURE ) { + printf ( "found $PnP at f000:%hx...", off ); + if ( checksum(undi.pnp_bios,sizeof(pnp_bios_t)) !=0) { + printf ( "invalid checksum\n..." ); + continue; + } + printf ( "ok\n" ); + return 1; + } + } + printf ( "none found\n" ); + undi.pnp_bios = NULL; + return 0; +} + +/* Locate the !PXE structure indicating a loaded UNDI driver. + */ + +static int hunt_pixie ( void ) { + static uint32_t ptr = 0; + pxe_t *pxe = NULL; + + printf ( "Hunting for pixies..." ); + if ( ptr == 0 ) ptr = 0xa0000; + while ( ptr > 0x10000 ) { + ptr -= 16; + pxe = (pxe_t *) phys_to_virt ( ptr ); + if ( memcmp ( pxe->Signature, "!PXE", 4 ) == 0 ) { + printf ( "found !PXE at %x...", ptr ); + if ( checksum ( pxe, sizeof(pxe_t) ) != 0 ) { + printf ( "invalid checksum\n..." ); + continue; + } + if ( ptr < get_free_base_memory() ) { + printf ( "in free base memory!\n\n" + "WARNING: a valid !PXE structure was " + "found in an area of memory marked " + "as free!\n\n" ); + undi.pxe = pxe; + pxe_dump(); + undi.pxe = NULL; + printf ( "\nIgnoring and continuing, but this " + "may cause problems later!\n\n" ); + continue; + } + printf ( "ok\n" ); + undi.pxe = pxe; + pxe_dump(); + printf ( "Resetting pixie...\n" ); + undi_unload_base_code(); + eb_pxenv_stop_undi(); + pxe_dump(); + return 1; + } + } + printf ( "none found\n" ); + ptr = 0; + return 0; +} + +/* Locate PCI PnP ROMs. + */ + +static int hunt_rom ( void ) { + static uint32_t ptr = 0; + + /* If we are not a PCI device, we cannot search for a ROM that + * matches us (?) + */ + if ( ! undi.pci.vendor ) + return 0; + + printf ( "Hunting for ROMs..." ); + if ( ptr == 0 ) ptr = 0x100000; + while ( ptr > 0x0c0000 ) { + ptr -= 0x800; + undi.rom = ( rom_t * ) phys_to_virt ( ptr ); + if ( undi.rom->signature == ROM_SIGNATURE ) { + pcir_header_t *pcir_header = NULL; + pnp_header_t *pnp_header = NULL; + + printf ( "found 55AA at %x...", ptr ); + if ( undi.rom->pcir_off == 0 ) { + printf ( "not a PCI ROM\n..." ); + continue; + } + pcir_header = (pcir_header_t*)( ( void * ) undi.rom + + undi.rom->pcir_off ); + if ( pcir_header->signature != PCIR_SIGNATURE ) { + printf ( "invalid PCI signature\n..." ); + continue; + } + printf ( "PCI:%hx:%hx...", pcir_header->vendor_id, + pcir_header->device_id ); + if ( ( pcir_header->vendor_id != undi.pci.vendor ) || + ( pcir_header->device_id != undi.pci.dev_id ) ) { + printf ( "not me (%hx:%hx)\n...", + undi.pci.vendor, + undi.pci.dev_id ); + continue; + } + if ( undi.rom->pnp_off == 0 ) { + printf ( "not a PnP ROM\n..." ); + continue; + } + pnp_header = (pnp_header_t*)( ( void * ) undi.rom + + undi.rom->pnp_off ); + if ( pnp_header->signature != PNP_SIGNATURE ) { + printf ( "invalid $PnP signature\n..." ); + continue; + } + if ( checksum(pnp_header,sizeof(pnp_header_t)) != 0 ) { + printf ( "invalid PnP checksum\n..." ); + continue; + } + printf ( "ok\nROM contains %s by %s\n", + pnp_header->product_str_off==0 ? "(unknown)" : + (void*)undi.rom+pnp_header->product_str_off, + pnp_header->manuf_str_off==0 ? "(unknown)" : + (void*)undi.rom+pnp_header->manuf_str_off ); + return 1; + } + } + printf ( "none found\n" ); + ptr = 0; + undi.rom = NULL; + return 0; +} + +/* Locate ROMs containing UNDI drivers. + */ + +static int hunt_undi_rom ( void ) { + while ( hunt_rom() ) { + if ( undi.rom->undi_rom_id_off == 0 ) { + printf ( "Not a PXE ROM\n" ); + continue; + } + undi.undi_rom_id = (undi_rom_id_t *) + ( (void *)undi.rom + undi.rom->undi_rom_id_off ); + if ( undi.undi_rom_id->signature != UNDI_SIGNATURE ) { + printf ( "Invalid UNDI signature\n" ); + continue; + } + if ( checksum ( undi.undi_rom_id, + undi.undi_rom_id->struct_length ) != 0 ) { + printf ( "Invalid checksum\n" ); + continue; + } + printf ( "Located UNDI ROM supporting revision %d.%d.%d\n", + undi.undi_rom_id->undi_rev[2], + undi.undi_rom_id->undi_rev[1], + undi.undi_rom_id->undi_rev[0] ); + return 1; + } + return 0; +} + +/************************************************************************** + * Low-level UNDI API call wrappers + **************************************************************************/ + +/* Make a real-mode UNDI API call to the UNDI routine at + * routine_seg:routine_off, passing in three uint16 parameters on the + * real-mode stack. + */ + +static PXENV_EXIT_t _undi_call ( uint16_t routine_seg, + uint16_t routine_off, uint16_t st0, + uint16_t st1, uint16_t st2 ) { + PXENV_EXIT_t ret = PXENV_EXIT_FAILURE; + struct { + segoff_t routine; + uint16_t st0; + uint16_t st1; + uint16_t st2; + } PACKED in_stack = { + { routine_off, routine_seg }, st0, st1, st2 + }; + + RM_FRAGMENT(rm_undi_call, + "popw %di\n\t" /* %es:di = routine */ + "popw %es\n\t" + "pushw %cs\n\t" /* set up return address */ + "call 1f\n\t1:popw %bx\n\t" + "leaw (2f-1b)(%bx), %ax\n\t" + "pushw %ax\n\t" + "pushw %es\n\t" /* routine address to stack */ + "pushw %di\n\t" + "lret\n\t" /* calculated lcall */ + "\n2:\n\t" /* continuation point */ + ); + + /* Parameters are left on stack: set out_stack = in_stack */ + ret = real_call ( rm_undi_call, &in_stack, &in_stack ); + + /* 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(); + + return ret; +} + +/* Make a real-mode call to the UNDI loader routine at + * routine_seg:routine_off, passing in the seg:off address of a + * pxenv_structure on the real-mode stack. + */ + +static int undi_call_loader ( void ) { + PXENV_EXIT_t pxenv_exit = PXENV_EXIT_FAILURE; + + /* Hide Etherboot around the loader, so that the PXE stack + * doesn't trash our memory areas + */ + install_e820mangler ( undi.base_mem_data->e820mangler ); + hide_etherboot(); + pxenv_exit = _undi_call ( SEGMENT( undi.rom ), + undi.undi_rom_id->undi_loader_off, + OFFSET( undi.pxs ), + SEGMENT( undi.pxs ), + 0 /* Unused for UNDI loader API */ ); + if ( !unhide_etherboot() ) { + printf ( "FATAL: corrupt INT15\n" ); + return 0; + } + + /* Return 1 for success, to be consistent with other routines */ + if ( pxenv_exit == PXENV_EXIT_SUCCESS ) return 1; + printf ( "UNDI loader call failed with status %#hx\n", + undi.pxs->Status ); + return 0; +} + +/* Make a real-mode UNDI API call, passing in the opcode and the + * seg:off address of a pxenv_structure on the real-mode stack. + * + * Two versions: undi_call() will automatically report any failure + * codes, undi_call_silent() will not. + */ + +static int undi_call_silent ( uint16_t opcode ) { + PXENV_EXIT_t pxenv_exit = PXENV_EXIT_FAILURE; + + pxenv_exit = _undi_call ( undi.pxe->EntryPointSP.segment, + undi.pxe->EntryPointSP.offset, + opcode, + OFFSET( undi.pxs ), + SEGMENT( undi.pxs ) ); + /* Return 1 for success, to be consistent with other routines */ + return pxenv_exit == PXENV_EXIT_SUCCESS ? 1 : 0; +} + +static int undi_call ( uint16_t opcode ) { + if ( undi_call_silent ( opcode ) ) return 1; + printf ( "UNDI API call %#hx failed with status %#hx\n", + opcode, undi.pxs->Status ); + return 0; +} + +#ifdef UNDI_NONTRIVIAL_IRQ +/* IRQ handler that actually calls PXENV_UNDI_ISR. It's probably + * better to use the trivial IRQ handler, since this seems to work for + * just about all known NICs and doesn't involve making a PXE API call + * in interrupt context. + * + * This routine is mainly used for testing the Etherboot PXE stack's + * ability to be called in interrupt context. It is not compiled in + * by default. + * + * This code has fewer safety checks than those in the + * trivial_irq_handler routines. These are omitted because this code + * is not intended for mainstream use. + */ + +uint16_t nontrivial_irq_previous_trigger_count = 0; + +static int copy_nontrivial_irq_handler ( void *target, + size_t target_size __unused ) { + RM_FRAGMENT(nontrivial_irq_handler, + /* Will be installed on a paragraph boundary, so access variables + * using %cs:(xxx-irqstart) + */ + "\n\t" + "irqstart:\n\t" + /* Fields here must match those in undi_irq_handler_t */ + "chain_to:\t.word 0,0\n\t" + "irq_chain:\t.byte 0,0,0,0\n\t" + "entry:\t.word 0,0\n\t" + "count_all:\t.word 0\n\t" + "count_ours:\t.word 0\n\t" + "undi_isr:\n\t" + "undi_isr_Status:\t.word 0\n\t" + "undi_isr_FuncFlag:\t.word 0\n\t" + "undi_isr_others:\t.word 0,0,0,0,0,0\n\t" + "handler:\n\t" + /* Assume that PXE stack will corrupt everything */ + "pushal\n\t" + "push %ds\n\t" + "push %es\n\t" + "push %fs\n\t" + "push %gs\n\t" + /* Set DS == CS */ + "pushw %cs\n\t" + "popw %ds\n\t" + /* Set up parameters for call */ + "movw $" RM_STR(PXENV_UNDI_ISR_IN_START) ", %ds:(undi_isr_FuncFlag-irqstart)\n\t" + "pushw %cs\n\t" + "popw %es\n\t" + "movw $(undi_isr-irqstart), %di\n\t" + "movw $" RM_STR(PXENV_UNDI_ISR) ", %bx\n\t" + "pushw %es\n\t" /* Registers for PXENV+, stack for !PXE */ + "pushw %di\n\t" + "pushw %bx\n\t" + /* Make PXE API call */ + "lcall *%ds:(entry-irqstart)\n\t" + "addw $6, %sp\n\t" + /* Set DS == CS */ + "pushw %cs\n\t" + "popw %ds\n\t" + /* Check return status to see if it's one of our interrupts */ + "cmpw $" RM_STR(PXENV_STATUS_SUCCESS) ", %cs:(undi_isr_Status-irqstart)\n\t" + "jne 1f\n\t" + "cmpw $" RM_STR(PXENV_UNDI_ISR_OUT_OURS) ", %cs:(undi_isr_FuncFlag-irqstart)\n\t" + "jne 1f\n\t" + /* Increment count_ours if so */ + "incw %ds:(count_ours-irqstart)\n\t" + "1:\n\t" + /* Increment count_all anyway */ + "incw %ds:(count_all-irqstart)\n\t" + /* Restore registers and return */ + "popw %gs\n\t" + "popw %fs\n\t" + "popw %es\n\t" + "popw %ds\n\t" + "popal\n\t" + "\n\t" + /* Chain to acknowledge the interrupt */ + "cmpb $0, %cs:(irq_chain-irqstart)\n\t" + "jz 2f\n\t" + "ljmp %cs:(chain_to-irqstart)\n\t" + "2:\n\t" + "\n\t" + "iret\n\t" + "\n\t" + ); + + /* Copy handler */ + memcpy ( target, nontrivial_irq_handler, NONTRIVIAL_IRQ_HANDLER_SIZE ); + + return 1; +} + +static int install_nontrivial_irq_handler ( irq_t irq ) { + undi_irq_handler_t *handler = + &undi.base_mem_data->nontrivial_irq_handler; + segoff_t isr_segoff; + + printf ( "WARNING: using non-trivial IRQ handler [EXPERIMENTAL]\n" ); + /* + * This code is deliberately quick and dirty. The whole + * nontrivial IRQ stuff is only present in order to test out + * calling our PXE stack in interrupt context. Do NOT use + * this in production code. + */ + + disable_irq ( irq ); + handler->count_all = 0; + handler->count_ours = 0; + handler->entry = undi.pxe->EntryPointSP; + nontrivial_irq_previous_trigger_count = 0; + isr_segoff.segment = SEGMENT(handler); + isr_segoff.offset = (void*)&handler->code - (void*)handler; + install_irq_handler( irq, &isr_segoff, + &handler->irq_chain, &handler->chain_to); + enable_irq ( irq ); + + return 1; +} + +static int remove_nontrivial_irq_handler ( irq_t irq ) { + undi_irq_handler_t *handler = + &undi.base_mem_data->nontrivial_irq_handler; + segoff_t isr_segoff; + + isr_segoff.segment = SEGMENT(handler); + isr_segoff.offset = (char*)&handler->code - (char*)handler; + remove_irq_handler( irq, &isr_segoff, + &handler->irq_chain, &handler->chain_to); + return 1; +} + +static int nontrivial_irq_triggered ( irq_t irq __unused ) { + undi_irq_handler_t *handler = + &undi.base_mem_data->nontrivial_irq_handler; + uint16_t nontrivial_irq_this_trigger_count = handler->count_ours; + int triggered = ( nontrivial_irq_this_trigger_count - + nontrivial_irq_previous_trigger_count ); + + nontrivial_irq_previous_trigger_count = + nontrivial_irq_this_trigger_count; + return triggered ? 1 : 0; +} + +static void nontrivial_irq_debug ( irq_t irq ) { + undi_irq_handler_t *handler = + &undi.base_mem_data->nontrivial_irq_handler; + + printf ( "IRQ %d triggered %d times (%d of which were ours)\n", + irq, handler->count_all, handler->count_ours ); +} +#endif /* UNDI_NONTRIVIAL_IRQ */ + +/************************************************************************** + * High-level UNDI API call wrappers + **************************************************************************/ + +/* Install the UNDI driver from a located UNDI ROM. + */ + +static int undi_loader ( void ) { + pxe_t *pxe = NULL; + + if ( ! undi.pci.vendor ) { + printf ( "ERROR: attempted to call loader of an ISA ROM?\n" ); + return 0; + } + + /* AX contains PCI bus:devfn (PCI specification) */ + undi.pxs->loader.ax = ( undi.pci.bus << 8 ) | undi.pci.devfn; + /* BX and DX set to 0xffff for non-ISAPnP devices + * (BIOS boot specification) + */ + undi.pxs->loader.bx = 0xffff; + undi.pxs->loader.dx = 0xffff; + /* ES:DI points to PnP BIOS' $PnP structure + * (BIOS boot specification) + */ + if ( undi.pnp_bios ) { + undi.pxs->loader.es = 0xf000; + undi.pxs->loader.di = virt_to_phys ( undi.pnp_bios ) - 0xf0000; + } else { + /* Set to a NULL pointer and hope that we don't need it */ + undi.pxs->loader.es = 0x0000; + undi.pxs->loader.di = 0x0000; + } + + /* Allocate space for UNDI driver's code and data segments */ + undi.driver_code_size = undi.undi_rom_id->code_size; + undi.driver_code = allot_base_memory ( undi.driver_code_size ); + if ( undi.driver_code == NULL ) { + printf ( "Could not allocate %d bytes for UNDI code segment\n", + undi.driver_code_size ); + return 0; + } + undi.pxs->loader.undi_cs = SEGMENT( undi.driver_code ); + + undi.driver_data_size = undi.undi_rom_id->data_size; + undi.driver_data = allot_base_memory ( undi.driver_data_size ); + if ( undi.driver_data == NULL ) { + printf ( "Could not allocate %d bytes for UNDI code segment\n", + undi.driver_data_size ); + return 0; + } + undi.pxs->loader.undi_ds = SEGMENT( undi.driver_data ); + + printf ( "Installing UNDI driver code to %hx:0000, data at %hx:0000\n", + undi.pxs->loader.undi_cs, undi.pxs->loader.undi_ds ); + + /* Do the API call to install the loader */ + if ( ! undi_call_loader () ) return 0; + + pxe = VIRTUAL( undi.pxs->loader.undi_cs, + undi.pxs->loader.pxe_ptr.offset ); + printf ( "UNDI driver created a pixie at %hx:%hx...", + undi.pxs->loader.undi_cs, undi.pxs->loader.pxe_ptr.offset ); + if ( memcmp ( pxe->Signature, "!PXE", 4 ) != 0 ) { + printf ( "invalid signature\n" ); + return 0; + } + if ( checksum ( pxe, sizeof(pxe_t) ) != 0 ) { + printf ( "invalid checksum\n" ); + return 0; + } + printf ( "ok\n" ); + undi.pxe = pxe; + pxe_dump(); + return 1; +} + +/* Start the UNDI driver. + */ + +static int eb_pxenv_start_undi ( void ) { + int success = 0; + + /* AX contains PCI bus:devfn (PCI specification) */ + undi.pxs->start_undi.ax = ( undi.pci.bus << 8 ) | undi.pci.devfn; + + /* BX and DX set to 0xffff for non-ISAPnP devices + * (BIOS boot specification) + */ + undi.pxs->start_undi.bx = 0xffff; + undi.pxs->start_undi.dx = 0xffff; + /* ES:DI points to PnP BIOS' $PnP structure + * (BIOS boot specification) + */ + if ( undi.pnp_bios ) { + undi.pxs->start_undi.es = 0xf000; + undi.pxs->start_undi.di = + virt_to_phys ( undi.pnp_bios ) - 0xf0000; + } else { + /* Set to a NULL pointer and hope that we don't need it */ + undi.pxs->start_undi.es = 0x0000; + undi.pxs->start_undi.di = 0x0000; + } + + DBG ( "PXENV_START_UNDI => AX=%hx BX=%hx DX=%hx ES:DI=%hx:%hx\n", + undi.pxs->start_undi.ax, + undi.pxs->start_undi.bx, undi.pxs->start_undi.dx, + undi.pxs->start_undi.es, undi.pxs->start_undi.di ); + success = undi_call ( PXENV_START_UNDI ); + DBG ( "PXENV_START_UNDI <= Status=%s\n", UNDI_STATUS(undi.pxs) ); + if ( success ) undi.prestarted = 1; + return success; +} + +static int eb_pxenv_undi_startup ( void ) { + int success = 0; + + DBG ( "PXENV_UNDI_STARTUP => (void)\n" ); + success = undi_call ( PXENV_UNDI_STARTUP ); + DBG ( "PXENV_UNDI_STARTUP <= Status=%s\n", UNDI_STATUS(undi.pxs) ); + if ( success ) undi.started = 1; + return success; +} + +static int eb_pxenv_undi_cleanup ( void ) { + int success = 0; + + DBG ( "PXENV_UNDI_CLEANUP => (void)\n" ); + success = undi_call ( PXENV_UNDI_CLEANUP ); + DBG ( "PXENV_UNDI_CLEANUP <= Status=%s\n", UNDI_STATUS(undi.pxs) ); + return success; +} + +static int eb_pxenv_undi_initialize ( void ) { + int success = 0; + + undi.pxs->undi_initialize.ProtocolIni = 0; + memset ( &undi.pxs->undi_initialize.reserved, 0, + sizeof ( undi.pxs->undi_initialize.reserved ) ); + DBG ( "PXENV_UNDI_INITIALIZE => ProtocolIni=%x\n" ); + success = undi_call ( PXENV_UNDI_INITIALIZE ); + DBG ( "PXENV_UNDI_INITIALIZE <= Status=%s\n", UNDI_STATUS(undi.pxs) ); + if ( success ) undi.initialized = 1; + return success; +} + +static int eb_pxenv_undi_shutdown ( void ) { + int success = 0; + + DBG ( "PXENV_UNDI_SHUTDOWN => (void)\n" ); + success = undi_call ( PXENV_UNDI_SHUTDOWN ); + DBG ( "PXENV_UNDI_SHUTDOWN <= Status=%s\n", UNDI_STATUS(undi.pxs) ); + if ( success ) { + undi.initialized = 0; + undi.started = 0; + } + return success; +} + +static int eb_pxenv_undi_open ( void ) { + int success = 0; + + undi.pxs->undi_open.OpenFlag = 0; + undi.pxs->undi_open.PktFilter = FLTR_DIRECTED | FLTR_BRDCST; + + /* Multicast support not yet implemented */ + undi.pxs->undi_open.R_Mcast_Buf.MCastAddrCount = 0; + DBG ( "PXENV_UNDI_OPEN => OpenFlag=%hx PktFilter=%hx " + "MCastAddrCount=%hx\n", + undi.pxs->undi_open.OpenFlag, undi.pxs->undi_open.PktFilter, + undi.pxs->undi_open.R_Mcast_Buf.MCastAddrCount ); + success = undi_call ( PXENV_UNDI_OPEN ); + DBG ( "PXENV_UNDI_OPEN <= Status=%s\n", UNDI_STATUS(undi.pxs) ); + if ( success ) undi.opened = 1; + return success; +} + +static int eb_pxenv_undi_close ( void ) { + int success = 0; + + DBG ( "PXENV_UNDI_CLOSE => (void)\n" ); + success = undi_call ( PXENV_UNDI_CLOSE ); + DBG ( "PXENV_UNDI_CLOSE <= Status=%s\n", UNDI_STATUS(undi.pxs) ); + if ( success ) undi.opened = 0; + return success; +} + +static int eb_pxenv_undi_transmit_packet ( void ) { + int success = 0; + static const uint8_t broadcast[] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }; + + /* XMitFlag selects unicast / broadcast */ + if ( memcmp ( undi.xmit_data->destaddr, broadcast, + sizeof(broadcast) ) == 0 ) { + undi.pxs->undi_transmit.XmitFlag = XMT_BROADCAST; + } else { + undi.pxs->undi_transmit.XmitFlag = XMT_DESTADDR; + } + + /* Zero reserved dwords */ + undi.pxs->undi_transmit.Reserved[0] = 0; + undi.pxs->undi_transmit.Reserved[1] = 0; + + /* Segment:offset pointer to DestAddr in base memory */ + undi.pxs->undi_transmit.DestAddr.segment = + SEGMENT( undi.xmit_data->destaddr ); + undi.pxs->undi_transmit.DestAddr.offset = + OFFSET( undi.xmit_data->destaddr ); + + /* Segment:offset pointer to TBD in base memory */ + undi.pxs->undi_transmit.TBD.segment = SEGMENT( &undi.xmit_data->tbd ); + undi.pxs->undi_transmit.TBD.offset = OFFSET( &undi.xmit_data->tbd ); + + /* Use only the "immediate" part of the TBD */ + undi.xmit_data->tbd.DataBlkCount = 0; + + DBG ( "PXENV_UNDI_TRANSMIT_PACKET => Protocol=%hx XmitFlag=%hx ...\n" + "... DestAddr=%hx:%hx TBD=%hx:%hx ...\n", + undi.pxs->undi_transmit.Protocol, + undi.pxs->undi_transmit.XmitFlag, + undi.pxs->undi_transmit.DestAddr.segment, + undi.pxs->undi_transmit.DestAddr.offset, + undi.pxs->undi_transmit.TBD.segment, + undi.pxs->undi_transmit.TBD.offset ); + DBG ( "... TBD { ImmedLength=%hx Xmit=%hx:%hx DataBlkCount=%hx }\n", + undi.xmit_data->tbd.ImmedLength, + undi.xmit_data->tbd.Xmit.segment, + undi.xmit_data->tbd.Xmit.offset, + undi.xmit_data->tbd.DataBlkCount ); + success = undi_call ( PXENV_UNDI_TRANSMIT ); + DBG ( "PXENV_UNDI_TRANSMIT_PACKET <= Status=%s\n", + UNDI_STATUS(undi.pxs) ); + return success; +} + +static int eb_pxenv_undi_set_station_address ( void ) { + /* This will spuriously fail on some cards. Ignore failures. + * We only ever use it to set the MAC address to the card's + * permanent value anyway, so it's a useless call (although we + * make it because PXE spec says we should). + */ + DBG ( "PXENV_UNDI_SET_STATION_ADDRESS => " + "StationAddress=%!\n", + undi.pxs->undi_set_station_address.StationAddress ); + undi_call_silent ( PXENV_UNDI_SET_STATION_ADDRESS ); + DBG ( "PXENV_UNDI_SET_STATION_ADDRESS <= Status=%s\n", + UNDI_STATUS(undi.pxs) ); + return 1; +} + +static int eb_pxenv_undi_get_information ( void ) { + int success = 0; + memset ( undi.pxs, 0, sizeof ( undi.pxs ) ); + DBG ( "PXENV_UNDI_GET_INFORMATION => (void)\n" ); + success = undi_call ( PXENV_UNDI_GET_INFORMATION ); + DBG ( "PXENV_UNDI_GET_INFORMATION <= Status=%s " + "BaseIO=%hx IntNumber=%hx ...\n" + "... MaxTranUnit=%hx HwType=%hx HwAddrlen=%hx ...\n" + "... CurrentNodeAddress=%! PermNodeAddress=%! ...\n" + "... ROMAddress=%hx RxBufCt=%hx TxBufCt=%hx\n", + UNDI_STATUS(undi.pxs), + undi.pxs->undi_get_information.BaseIo, + undi.pxs->undi_get_information.IntNumber, + undi.pxs->undi_get_information.MaxTranUnit, + undi.pxs->undi_get_information.HwType, + undi.pxs->undi_get_information.HwAddrLen, + undi.pxs->undi_get_information.CurrentNodeAddress, + undi.pxs->undi_get_information.PermNodeAddress, + undi.pxs->undi_get_information.ROMAddress, + undi.pxs->undi_get_information.RxBufCt, + undi.pxs->undi_get_information.TxBufCt ); + return success; +} + +static int eb_pxenv_undi_get_iface_info ( void ) { + int success = 0; + + DBG ( "PXENV_UNDI_GET_IFACE_INFO => (void)\n" ); + success = undi_call ( PXENV_UNDI_GET_IFACE_INFO ); + DBG ( "PXENV_UNDI_GET_IFACE_INFO <= Status=%s IfaceType=%s ...\n" + "... LinkSpeed=%x ServiceFlags=%x\n", + UNDI_STATUS(undi.pxs), + undi.pxs->undi_get_iface_info.IfaceType, + undi.pxs->undi_get_iface_info.LinkSpeed, + undi.pxs->undi_get_iface_info.ServiceFlags ); + return success; +} + +static int eb_pxenv_undi_isr ( void ) { + int success = 0; + + DBG ( "PXENV_UNDI_ISR => FuncFlag=%hx\n", + undi.pxs->undi_isr.FuncFlag ); + success = undi_call ( PXENV_UNDI_ISR ); + DBG ( "PXENV_UNDI_ISR <= Status=%s FuncFlag=%hx BufferLength=%hx ...\n" + "... FrameLength=%hx FrameHeaderLength=%hx Frame=%hx:%hx " + "ProtType=%hhx ...\n... PktType=%hhx\n", + UNDI_STATUS(undi.pxs), undi.pxs->undi_isr.FuncFlag, + undi.pxs->undi_isr.BufferLength, + undi.pxs->undi_isr.FrameLength, + undi.pxs->undi_isr.FrameHeaderLength, + undi.pxs->undi_isr.Frame.segment, + undi.pxs->undi_isr.Frame.offset, + undi.pxs->undi_isr.ProtType, + undi.pxs->undi_isr.PktType ); + return success; +} + +static int eb_pxenv_stop_undi ( void ) { + int success = 0; + + DBG ( "PXENV_STOP_UNDI => (void)\n" ); + success = undi_call ( PXENV_STOP_UNDI ); + DBG ( "PXENV_STOP_UNDI <= Status=%s\n", UNDI_STATUS(undi.pxs) ); + if ( success ) undi.prestarted = 0; + return success; +} + +static int eb_pxenv_unload_stack ( void ) { + int success = 0; + + memset ( undi.pxs, 0, sizeof ( undi.pxs ) ); + DBG ( "PXENV_UNLOAD_STACK => (void)\n" ); + success = undi_call_silent ( PXENV_UNLOAD_STACK ); + DBG ( "PXENV_UNLOAD_STACK <= Status=%s ...\n... (%s)\n", + UNDI_STATUS(undi.pxs), + ( undi.pxs->Status == PXENV_STATUS_SUCCESS ? + "base-code is ready to be removed" : + ( undi.pxs->Status == PXENV_STATUS_FAILURE ? + "the size of free base memory has been changed" : + ( undi.pxs->Status == PXENV_STATUS_KEEP_ALL ? + "the NIC interrupt vector has been changed" : + "UNEXPECTED STATUS CODE" ) ) ) ); + return success; +} + +static int eb_pxenv_stop_base ( void ) { + int success = 0; + + DBG ( "PXENV_STOP_BASE => (void)\n" ); + success = undi_call ( PXENV_STOP_BASE ); + DBG ( "PXENV_STOP_BASE <= Status=%s\n", UNDI_STATUS(undi.pxs) ); + return success; +} + +/* Unload UNDI base code (if any present) and free memory. + */ +static int undi_unload_base_code ( void ) { + void *bc_code = VIRTUAL( undi.pxe->BC_Code.Seg_Addr, 0 ); + size_t bc_code_size = undi.pxe->BC_Code.Seg_Size; + void *bc_data = VIRTUAL( undi.pxe->BC_Data.Seg_Addr, 0 ); + size_t bc_data_size = undi.pxe->BC_Data.Seg_Size; + void *bc_stck = VIRTUAL( undi.pxe->Stack.Seg_Addr, 0 ); + size_t bc_stck_size = undi.pxe->Stack.Seg_Size; + firing_squad_lineup_t lineup; + + /* Since we never start the base code, the only time we should + * reach this is if we were loaded via PXE. There are many + * different and conflicting versions of the "correct" way to + * unload the PXE base code, several of which appear within + * the PXE specification itself. This one seems to work for + * our purposes. + * + * We always call PXENV_STOP_BASE and PXENV_UNLOAD_STACK even + * if the !PXE structure indicates that no base code is + * present. We do this for the case that there is a + * base-code-less UNDI driver loaded that has hooked some + * interrupts. If the base code really is absent, then these + * calls will fail, we will ignore the failure, and our + * subsequent memory-freeing code is robust enough to handle + * whatever's thrown at it. + */ + eb_pxenv_stop_base(); + eb_pxenv_unload_stack(); + if ( ( undi.pxs->unload_stack.Status != PXENV_STATUS_SUCCESS ) && + ( undi.pxs->unload_stack.Status != PXENV_STATUS_FAILURE ) && + ( undi.pxe->BC_Code.Seg_Addr != 0 ) ) + { + printf ( "Could not free memory allocated to PXE base code: " + "possible memory leak\n" ); + return 0; + } + /* Free data structures. Forget what the PXE specification + * says about how to calculate the new size of base memory; + * basemem.c takes care of all that for us. Note that we also + * have to free the stack (even though PXE spec doesn't say + * anything about it) because nothing else is going to do so. + * + * Structures will almost certainly not be kB-aligned and + * there's a reasonable chance that the UNDI code or data + * portions will lie in the same kB as the base code. Since + * forget_base_memory works only in 1kB increments, this means + * we have to do some arcane trickery. + */ + memset ( &lineup, 0, sizeof(lineup) ); + if ( SEGMENT(bc_code) != 0 ) + assemble_firing_squad( &lineup, bc_code, bc_code_size, SHOOT ); + if ( SEGMENT(bc_data) != 0 ) + assemble_firing_squad( &lineup, bc_data, bc_data_size, SHOOT ); + if ( SEGMENT(bc_stck) != 0 ) + assemble_firing_squad( &lineup, bc_stck, bc_stck_size, SHOOT ); + /* Don't shoot any bits of the UNDI driver code or data */ + assemble_firing_squad ( &lineup, + VIRTUAL(undi.pxe->UNDICode.Seg_Addr, 0), + undi.pxe->UNDICode.Seg_Size, DONTSHOOT ); + assemble_firing_squad ( &lineup, + VIRTUAL(undi.pxe->UNDIData.Seg_Addr, 0), + undi.pxe->UNDIData.Seg_Size, DONTSHOOT ); + shoot_targets ( &lineup ); + undi.pxe->BC_Code.Seg_Addr = 0; + undi.pxe->BC_Data.Seg_Addr = 0; + undi.pxe->Stack.Seg_Addr = 0; + + /* Free and reallocate our own base memory data structures, to + * allow the freed base-code blocks to be fully released. + */ + free_base_mem_data(); + if ( ! allocate_base_mem_data() ) { + printf ( "FATAL: memory unaccountably lost\n" ); + while ( 1 ) {}; + } + + return 1; +} + +/* UNDI full initialization + * + * This calls all the various UNDI initialization routines in sequence. + */ + +static int undi_full_startup ( void ) { + if ( ! eb_pxenv_start_undi() ) return 0; + if ( ! eb_pxenv_undi_startup() ) return 0; + if ( ! eb_pxenv_undi_initialize() ) return 0; + if ( ! eb_pxenv_undi_get_information() ) return 0; + undi.irq = undi.pxs->undi_get_information.IntNumber; + copy_undi_irq_handler ( undi.base_mem_data->irq_handler, + UNDI_IRQ_HANDLER_SIZE ); + if ( ! install_undi_irq_handler ( undi.irq ) ) { + undi.irq = IRQ_NONE; + return 0; + } + memmove ( &undi.pxs->undi_set_station_address.StationAddress, + &undi.pxs->undi_get_information.PermNodeAddress, + sizeof (undi.pxs->undi_set_station_address.StationAddress) ); + if ( ! eb_pxenv_undi_set_station_address() ) return 0; + if ( ! eb_pxenv_undi_open() ) return 0; + return 1; +} + +/* UNDI full shutdown + * + * This calls all the various UNDI shutdown routines in sequence and + * also frees any memory that it can. + */ + +static int undi_full_shutdown ( void ) { + if ( undi.pxe != NULL ) { + /* In case we didn't allocate the driver's memory in the first + * place, try to grab the code and data segments and sizes + * from the !PXE structure. + */ + if ( undi.driver_code == NULL ) { + undi.driver_code = VIRTUAL(undi.pxe->UNDICode.Seg_Addr, + 0 ); + undi.driver_code_size = undi.pxe->UNDICode.Seg_Size; + } + if ( undi.driver_data == NULL ) { + undi.driver_data = VIRTUAL(undi.pxe->UNDIData.Seg_Addr, + 0 ); + undi.driver_data_size = undi.pxe->UNDIData.Seg_Size; + } + + /* Ignore errors and continue in the hope of shutting + * down anyway + */ + if ( undi.opened ) eb_pxenv_undi_close(); + if ( undi.started ) { + eb_pxenv_undi_cleanup(); + /* We may get spurious UNDI API errors at this + * point. If startup() succeeded but + * initialize() failed then according to the + * spec, we should call shutdown(). However, + * some NICS will fail with a status code + * 0x006a (INVALID_STATE). + */ + eb_pxenv_undi_shutdown(); + } + if ( undi.irq != IRQ_NONE ) { + remove_undi_irq_handler ( undi.irq ); + undi.irq = IRQ_NONE; + } + undi_unload_base_code(); + if ( undi.prestarted ) { + eb_pxenv_stop_undi(); + /* Success OR Failure indicates that memory + * can be freed. Any other status code means + * that it can't. + */ + if (( undi.pxs->Status == PXENV_STATUS_KEEP_UNDI ) || + ( undi.pxs->Status == PXENV_STATUS_KEEP_ALL ) ) { + printf ("Could not free memory allocated to " + "UNDI driver: possible memory leak\n"); + return 0; + } + } + } + /* Free memory allocated to UNDI driver */ + if ( undi.driver_code != NULL ) { + /* Clear contents in order to eliminate !PXE and PXENV + * signatures to prevent spurious detection via base + * memory scan. + */ + memset ( undi.driver_code, 0, undi.driver_code_size ); + forget_base_memory ( undi.driver_code, undi.driver_code_size ); + undi.driver_code = NULL; + undi.driver_code_size = 0; + } + if ( undi.driver_data != NULL ) { + forget_base_memory ( undi.driver_data, undi.driver_data_size ); + undi.driver_data = NULL; + undi.driver_data_size = 0; + } + /* !PXE structure now gone; memory freed */ + undi.pxe = NULL; + return 1; +} + +/************************************************************************** +POLL - Wait for a frame +***************************************************************************/ +static int undi_poll(struct nic *nic, int retrieve) +{ + /* Fun, fun, fun. UNDI drivers don't use polling; they use + * interrupts. We therefore cheat and pretend that an + * interrupt has occurred every time undi_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 + * undi_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. + */ + + /* See if a hardware interrupt has occurred since the last poll(). + */ + if ( ! undi_irq_triggered ( undi.irq ) ) return 0; + + /* Given the frailty of PXE stacks, it's probably not safe to + * risk calling PXENV_UNDI_ISR with + * FuncFlag=PXENV_UNDI_ISR_START twice for the same interrupt, + * so we cheat slightly and assume that there is something + * ready to retrieve as long as an interrupt has occurred. + */ + if ( ! retrieve ) return 1; + +#ifdef UNDI_NONTRIVIAL_IRQ + /* With the nontrivial IRQ handler, we have already called + * PXENV_UNDI_ISR with PXENV_UNDI_ISR_IN_START and determined + * that it is one of ours. + */ +#else + /* Ask the UNDI driver if this is "our" interrupt. + */ + undi.pxs->undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_START; + if ( ! eb_pxenv_undi_isr() ) return 0; + if ( undi.pxs->undi_isr.FuncFlag == PXENV_UNDI_ISR_OUT_NOT_OURS ) { + /* "Not our interrupt" translates to "no packet ready + * to read". + */ + /* FIXME: Technically, we shouldn't be the one sending + * EOI. However, since our IRQ handlers don't yet + * support chaining, nothing else gets the chance to. + * One nice side-effect of doing this is that it means + * we can cheat and claim the timer interrupt as our + * NIC interrupt; it will be inefficient but will + * work. + */ + send_specific_eoi ( undi.irq ); + return 0; + } +#endif + + /* At this stage, the device should have cleared its interrupt + * line so we can send EOI to the 8259. + */ + send_specific_eoi ( undi.irq ); + + /* We might have received a packet, or this might be a + * "transmit completed" interrupt. Zero nic->packetlen, + * increment whenever we receive a bit of a packet, test + * nic->packetlen when we're done to see whether or not we + * actually received anything. + */ + nic->packetlen = 0; + undi.pxs->undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS; + if ( ! eb_pxenv_undi_isr() ) return 0; + while ( undi.pxs->undi_isr.FuncFlag != PXENV_UNDI_ISR_OUT_DONE ) { + switch ( undi.pxs->undi_isr.FuncFlag ) { + case PXENV_UNDI_ISR_OUT_TRANSMIT: + /* We really don't care about transmission complete + * interrupts. + */ + break; + case PXENV_UNDI_ISR_OUT_BUSY: + /* This should never happen. + */ + printf ( "UNDI ISR thinks it's being re-entered!\n" + "Aborting receive\n" ); + return 0; + case PXENV_UNDI_ISR_OUT_RECEIVE: + /* Copy data to receive buffer */ + memcpy ( nic->packet + nic->packetlen, + VIRTUAL( undi.pxs->undi_isr.Frame.segment, + undi.pxs->undi_isr.Frame.offset ), + undi.pxs->undi_isr.BufferLength ); + nic->packetlen += undi.pxs->undi_isr.BufferLength; + break; + default: + printf ( "UNDI ISR returned bizzare status code %d\n", + undi.pxs->undi_isr.FuncFlag ); + } + undi.pxs->undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT; + if ( ! eb_pxenv_undi_isr() ) return 0; + } + return nic->packetlen > 0 ? 1 : 0; +} + +/************************************************************************** +TRANSMIT - Transmit a frame +***************************************************************************/ +static void undi_transmit( + struct nic *nic __unused, + const char *d, /* Destination */ + unsigned int t, /* Type */ + unsigned int s, /* size */ + const char *p) /* Packet */ +{ + /* Copy destination to buffer in base memory */ + memcpy ( undi.xmit_data->destaddr, d, sizeof(MAC_ADDR) ); + + /* Translate packet type to UNDI packet type */ + switch ( t ) { + case IP : undi.pxs->undi_transmit.Protocol = P_IP; break; + case ARP: undi.pxs->undi_transmit.Protocol = P_ARP; break; + case RARP: undi.pxs->undi_transmit.Protocol = P_RARP; break; + default: printf ( "Unknown packet type %hx\n", t ); + return; + } + + /* Store packet length in TBD */ + undi.xmit_data->tbd.ImmedLength = s; + + /* Check to see if data to be transmitted is currently in base + * memory. If not, allocate temporary storage in base memory + * and copy it there. + */ + if ( SEGMENT( p ) <= 0xffff ) { + undi.xmit_data->tbd.Xmit.segment = SEGMENT( p ); + undi.xmit_data->tbd.Xmit.offset = OFFSET( p ); + } else { + memcpy ( undi.xmit_buffer, p, s ); + undi.xmit_data->tbd.Xmit.segment = SEGMENT( undi.xmit_buffer ); + undi.xmit_data->tbd.Xmit.offset = OFFSET( undi.xmit_buffer ); + } + + eb_pxenv_undi_transmit_packet(); +} + +/************************************************************************** +DISABLE - Turn off ethernet interface +***************************************************************************/ +static void undi_disable ( struct dev *dev __unused ) { + undi_full_shutdown(); + free_base_mem_data(); +} + +/************************************************************************** +PROBE - Look for an adapter, this routine's visible to the outside +***************************************************************************/ + +/* Locate an UNDI driver by first scanning through base memory for an + * installed driver and then by scanning for UNDI ROMs and attempting + * to install their drivers. + */ + +static int hunt_pixies_and_undi_roms ( void ) { + static uint8_t hunt_type = HUNT_FOR_PIXIES; + + if ( hunt_type == HUNT_FOR_PIXIES ) { + if ( hunt_pixie() ) { + return 1; + } + } + hunt_type = HUNT_FOR_UNDI_ROMS; + while ( hunt_undi_rom() ) { + if ( undi_loader() ) { + return 1; + } + undi_full_shutdown(); /* Free any allocated memory */ + } + hunt_type = HUNT_FOR_PIXIES; + return 0; +} + +/* The actual Etherboot probe routine. + */ + +static int undi_probe(struct dev *dev, struct pci_device *pci) +{ + struct nic *nic = (struct nic *)dev; + + /* Zero out global undi structure */ + memset ( &undi, 0, sizeof(undi) ); + + /* Store PCI parameters; we will need them to initialize the + * UNDI driver later. If not a PCI device, leave as 0. + */ + if ( pci ) { + memcpy ( &undi.pci, pci, sizeof(undi.pci) ); + } + + /* Find the BIOS' $PnP structure */ + if ( ! hunt_pnp_bios() ) { + /* Not all PXE stacks actually insist on a PnP BIOS. + * In particular, an Etherboot PXE stack will work + * just fine without one. + * + * We used to make this a fatal error, but now we just + * warn and continue. Note that this is necessary in + * order to be able to debug the Etherboot PXE stack + * under Bochs, since Bochs' BIOS is non-PnP. + */ + printf ( "WARNING: No PnP BIOS found\n" ); + } + + /* Allocate base memory data structures */ + if ( ! allocate_base_mem_data() ) return 0; + + /* Search thoroughly for UNDI drivers */ + for ( ; hunt_pixies_and_undi_roms(); undi_full_shutdown() ) { + /* Try to initialise UNDI driver */ + printf ( "Initializing UNDI driver. Please wait...\n" ); + if ( ! undi_full_startup() ) { + if ( undi.pxs->Status == + PXENV_STATUS_UNDI_MEDIATEST_FAILED ) { + printf ( "Cable not connected (code %#hx)\n", + PXENV_STATUS_UNDI_MEDIATEST_FAILED ); + } + continue; + } + /* Basic information: MAC, IO addr, IRQ */ + if ( ! eb_pxenv_undi_get_information() ) continue; + printf ( "Initialized UNDI NIC with IO %#hx, IRQ %d, MAC %!\n", + undi.pxs->undi_get_information.BaseIo, + undi.pxs->undi_get_information.IntNumber, + undi.pxs->undi_get_information.CurrentNodeAddress ); + /* Fill out MAC address in nic structure */ + memcpy ( nic->node_addr, + undi.pxs->undi_get_information.CurrentNodeAddress, + ETH_ALEN ); + /* More diagnostic information including link speed */ + if ( ! eb_pxenv_undi_get_iface_info() ) continue; + printf ( "NDIS type %s interface at %d Mbps\n", + undi.pxs->undi_get_iface_info.IfaceType, + undi.pxs->undi_get_iface_info.LinkSpeed / 1000000 ); + dev->disable = undi_disable; + nic->poll = undi_poll; + nic->transmit = undi_transmit; + return 1; + } + undi_disable ( dev ); /* To free base memory structures */ + return 0; +} + +static int undi_isa_probe ( struct dev *dev, + unsigned short *probe_addrs __unused ) { + return undi_probe ( dev, NULL ); +} + + +/* UNDI driver states that it is suitable for any PCI NIC (i.e. any + * PCI device of class PCI_CLASS_NETWORK_ETHERNET). If there are any + * obscure UNDI NICs that have the incorrect PCI class, add them to + * this list. + */ +static struct pci_id undi_nics[] = { + /* PCI_ROM(0x0000, 0x0000, "undi", "UNDI adaptor"), */ +}; + +static struct pci_driver undi_driver __pci_driver = { + .type = NIC_DRIVER, + .name = "UNDI", + .probe = undi_probe, + .ids = undi_nics, + .id_count = sizeof(undi_nics)/sizeof(undi_nics[0]), + .class = PCI_CLASS_NETWORK_ETHERNET, +}; + +static struct isa_driver undi_isa_driver __isa_driver = { + .type = NIC_DRIVER, + .name = "UNDI", + .probe = undi_isa_probe, + .ioaddrs = 0, +}; + +#endif /* PCBIOS */ diff --git a/src/arch/i386/drivers/net/undi.h b/src/arch/i386/drivers/net/undi.h new file mode 100644 index 00000000..3ba76c35 --- /dev/null +++ b/src/arch/i386/drivers/net/undi.h @@ -0,0 +1,178 @@ +/************************************************************************** +Etherboot - BOOTP/TFTP Bootstrap Program +UNDI NIC driver for Etherboot - header file + +This file Copyright (C) 2003 Michael Brown <mbrown@fensystems.co.uk> +of Fen Systems Ltd. (http://www.fensystems.co.uk/). All rights +reserved. + +$Id$ +***************************************************************************/ + +/* + * 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 "pxe.h" +#include "pic8259.h" + +/* A union that can function as the parameter block for any UNDI API call. + */ +typedef t_PXENV_ANY pxenv_structure_t; + +/* BIOS PnP parameter block. We scan for this so that we can pass it + * to the UNDI driver. + */ + +#define PNP_BIOS_SIGNATURE ( ('$'<<0) + ('P'<<8) + ('n'<<16) + ('P'<<24) ) +typedef struct pnp_bios { + uint32_t signature; + uint8_t version; + uint8_t length; + uint16_t control; + uint8_t checksum; + uint8_t dontcare[24]; +} PACKED pnp_bios_t; + +/* Structures within the PXE ROM. + */ + +#define ROM_SIGNATURE 0xaa55 +typedef struct rom { + uint16_t signature; + uint8_t unused[0x14]; + uint16_t undi_rom_id_off; + uint16_t pcir_off; + uint16_t pnp_off; +} PACKED rom_t; + +#define PCIR_SIGNATURE ( ('P'<<0) + ('C'<<8) + ('I'<<16) + ('R'<<24) ) +typedef struct pcir_header { + uint32_t signature; + uint16_t vendor_id; + uint16_t device_id; +} PACKED pcir_header_t; + +#define PNP_SIGNATURE ( ('$'<<0) + ('P'<<8) + ('n'<<16) + ('P'<<24) ) +typedef struct pnp_header { + uint32_t signature; + uint8_t struct_revision; + uint8_t length; + uint16_t next; + uint8_t reserved; + uint8_t checksum; + uint16_t id[2]; + uint16_t manuf_str_off; + uint16_t product_str_off; + uint8_t base_type; + uint8_t sub_type; + uint8_t interface_type; + uint8_t indicator; + uint16_t boot_connect_off; + uint16_t disconnect_off; + uint16_t initialise_off; + uint16_t reserved2; + uint16_t info; +} PACKED pnp_header_t; + +#define UNDI_SIGNATURE ( ('U'<<0) + ('N'<<8) + ('D'<<16) + ('I'<<24) ) +typedef struct undi_rom_id { + uint32_t signature; + uint8_t struct_length; + uint8_t struct_cksum; + uint8_t struct_rev; + uint8_t undi_rev[3]; + uint16_t undi_loader_off; + uint16_t stack_size; + uint16_t data_size; + uint16_t code_size; +} PACKED undi_rom_id_t; + +/* Nontrivial IRQ handler structure */ +typedef struct { + segoff_t chain_to; + uint8_t irq_chain, pad1, pad2, pad3; + segoff_t entry; + uint16_t count_all; + uint16_t count_ours; + t_PXENV_UNDI_ISR undi_isr; + char code[0]; +} PACKED undi_irq_handler_t ; + +/* Storage buffers that we need in base memory. We collect these into + * a single structure to make allocation simpler. + */ + +typedef struct undi_base_mem_xmit_data { + MAC_ADDR destaddr; + t_PXENV_UNDI_TBD tbd; +} undi_base_mem_xmit_data_t; + +typedef struct undi_base_mem_data { + pxenv_structure_t pxs; + undi_base_mem_xmit_data_t xmit_data; + char xmit_buffer[ETH_FRAME_LEN]; + /* Must be last in structure and paragraph-aligned */ + union { + char e820mangler[0]; + char irq_handler[0]; + undi_irq_handler_t nontrivial_irq_handler; + } __attribute__ ((aligned(16))); +} undi_base_mem_data_t; + +/* Macros and data structures used when freeing bits of base memory + * used by the UNDI driver. + */ + +#define FIRING_SQUAD_TARGET_SIZE 8 +#define FIRING_SQUAD_TARGET_INDEX(x) ( (x) / FIRING_SQUAD_TARGET_SIZE ) +#define FIRING_SQUAD_TARGET_BIT(x) ( (x) % FIRING_SQUAD_TARGET_SIZE ) +typedef struct firing_squad_lineup { + uint8_t targets[ 640 / FIRING_SQUAD_TARGET_SIZE ]; +} firing_squad_lineup_t; +typedef enum firing_squad_shoot { + DONTSHOOT = 0, + SHOOT = 1 +} firing_squad_shoot_t; + +/* Driver private data structure. + */ + +typedef struct undi { + /* Pointers to various data structures */ + pnp_bios_t *pnp_bios; + rom_t *rom; + undi_rom_id_t *undi_rom_id; + pxe_t *pxe; + pxenv_structure_t *pxs; + undi_base_mem_xmit_data_t *xmit_data; + /* Pointers and sizes to keep track of allocated base memory */ + undi_base_mem_data_t *base_mem_data; + void *driver_code; + size_t driver_code_size; + void *driver_data; + size_t driver_data_size; + char *xmit_buffer; + /* Flags. We keep our own instead of trusting the UNDI driver + * to have implemented PXENV_UNDI_GET_STATE correctly. Plus + * there's the small issue of PXENV_UNDI_GET_STATE being the + * same API call as PXENV_STOP_UNDI... + */ + uint8_t prestarted; /* pxenv_start_undi() has been called */ + uint8_t started; /* pxenv_undi_startup() has been called */ + uint8_t initialized; /* pxenv_undi_initialize() has been called */ + uint8_t opened; /* pxenv_undi_open() has been called */ + /* Parameters that we need to store for future reference + */ + struct pci_device pci; + irq_t irq; +} undi_t; + +/* Constants + */ + +#define HUNT_FOR_PIXIES 0 +#define HUNT_FOR_UNDI_ROMS 1 diff --git a/src/arch/i386/firmware/pcbios/basemem.c b/src/arch/i386/firmware/pcbios/basemem.c new file mode 100644 index 00000000..c93b19e9 --- /dev/null +++ b/src/arch/i386/firmware/pcbios/basemem.c @@ -0,0 +1,317 @@ +#ifdef PCBIOS + +#include "etherboot.h" +#include "realmode.h" /* for real_mode_stack */ + +/* Routines to allocate base memory in a BIOS-compatible way, by + * updating the Free Base Memory Size counter at 40:13h. + * + * Michael Brown <mbrown@fensystems.co.uk> (mcb30) + * $Id$ + */ + +#define fbms ( ( uint16_t * ) phys_to_virt ( 0x413 ) ) +#define BASE_MEMORY_MAX ( 640 ) +#define FREE_BLOCK_MAGIC ( ('!'<<0) + ('F'<<8) + ('R'<<16) + ('E'<<24) ) +#define FREE_BASE_MEMORY ( (uint32_t) ( *fbms << 10 ) ) + +/* Prototypes */ +void * _allot_base_memory ( size_t size ); +void _forget_base_memory ( void *ptr, size_t size ); + +typedef struct free_base_memory_block { + uint32_t magic; + uint16_t size_kb; +} free_base_memory_block_t; + +/* Return amount of free base memory in bytes + */ + +uint32_t get_free_base_memory ( void ) { + return FREE_BASE_MEMORY; +} + +/* Start of our image in base memory. + */ +#define __text16_nocompress __attribute__ ((section (".text16.nocompress"))) +uint32_t image_basemem __text16_nocompress = 0; +uint32_t image_basemem_size __text16_nocompress = 0; + +/* Allot/free the real-mode stack + */ + +void allot_real_mode_stack ( void ) +{ + void *new_real_mode_stack; + + if ( lock_real_mode_stack ) + return; + + /* This is evil hack. + * Until we have a real_mode stack use 0x7c00. + * Except for 0 - 0x600 membory below 0x7c00 is hardly every used. + * This stack should never be used unless the stack allocation fails, + * or if someone has placed a print statement in a dangerous location. + */ + if (!real_mode_stack) { + real_mode_stack = 0x7c00; + } + new_real_mode_stack = _allot_base_memory ( real_mode_stack_size ); + if ( ! new_real_mode_stack ) { + printf ( "FATAL: No real-mode stack\n" ); + while ( 1 ) {}; + } + real_mode_stack = virt_to_phys ( new_real_mode_stack ); + get_memsizes(); +} + +void forget_real_mode_stack ( void ) +{ + if ( lock_real_mode_stack ) + return; + + if ( real_mode_stack) { + _forget_base_memory ( phys_to_virt(real_mode_stack), + real_mode_stack_size ); + /* get_memsizes() uses the real_mode stack we just freed + * for it's BIOS calls. + */ + get_memsizes(); + real_mode_stack = 0; + } +} + +/* Allocate N bytes of base memory. Amount allocated will be rounded + * up to the nearest kB, since that's the granularity of the BIOS FBMS + * counter. Returns NULL if memory cannot be allocated. + */ + +static void * _allot_base_memory ( size_t size ) +{ + uint16_t size_kb = ( size + 1023 ) >> 10; + void *ptr = NULL; + +#ifdef DEBUG_BASEMEM + printf ( "Trying to allocate %d kB of base memory from %d kB free\n", + size_kb, *fbms ); +#endif + + /* Free up any unused memory before we start */ + free_unused_base_memory(); + + /* Check available base memory */ + if ( size_kb > *fbms ) { return NULL; } + + /* Reduce available base memory */ + *fbms -= size_kb; + + /* Calculate address of memory allocated */ + ptr = phys_to_virt ( FREE_BASE_MEMORY ); + + /* Zero out memory. We do this so that allocation of + * already-used space will show up in the form of a crash as + * soon as possible. + * + * Update: there's another reason for doing this. If we don't + * zero the contents, then they could still retain our "free + * block" markers and be liable to being freed whenever a + * base-memory allocation routine is next called. + */ + memset ( ptr, 0, size_kb << 10 ); + +#ifdef DEBUG_BASEMEM + printf ( "Allocated %d kB at [%x,%x)\n", size_kb, + virt_to_phys ( ptr ), + virt_to_phys ( ptr ) + size_kb * 1024 ); +#endif + + return ptr; +} + +void * allot_base_memory ( size_t size ) +{ + void *ptr; + + /* Free real-mode stack, allocate memory, reallocate real-mode + * stack. + */ + forget_real_mode_stack(); + ptr = _allot_base_memory ( size ); + get_memsizes(); + return ptr; +} + +/* Free base memory allocated by allot_base_memory. The BIOS provides + * nothing better than a LIFO mechanism for freeing memory (i.e. it + * just has the single "total free memory" counter), but we improve + * upon this slightly; as long as you free all the allotted blocks, it + * doesn't matter what order you free them in. (This will only work + * for blocks that are freed via forget_base_memory()). + * + * Yes, it's annoying that you have to remember the size of the blocks + * you've allotted. However, since our granularity of allocation is + * 1K, the alternative is to risk wasting the occasional kB of base + * memory, which is a Bad Thing. Really, you should be using as + * little base memory as possible, so consider the awkwardness of the + * API to be a feature! :-) + */ + +static void _forget_base_memory ( void *ptr, size_t size ) +{ + uint16_t remainder = virt_to_phys(ptr) & 1023; + uint16_t size_kb = ( size + remainder + 1023 ) >> 10; + free_base_memory_block_t *free_block = + ( free_base_memory_block_t * ) ( ptr - remainder ); + + if ( ( ptr == NULL ) || ( size == 0 ) ) { + return; + } + +#ifdef DEBUG_BASEMEM + printf ( "Trying to free %d bytes base memory at 0x%x\n", + size, virt_to_phys ( ptr ) ); + if ( remainder > 0 ) { + printf ( "WARNING: destructively expanding free block " + "downwards to 0x%x\n", + virt_to_phys ( ptr - remainder ) ); + } +#endif + + /* Mark every kilobyte within this block as free. This is + * overkill for normal purposes, but helps when something has + * allocated base memory with a granularity finer than the + * BIOS granularity of 1kB. PXE ROMs tend to do this when + * they allocate their own memory. This method allows us to + * free their blocks (admittedly in a rather dangerous, + * tread-on-anything-either-side sort of way, but there's no + * other way to do it). + * + * Since we're marking every kB as free, there's actually no + * need for recording the size of the blocks. However, we + * keep this in so that debug messages are friendlier. It + * probably adds around 8 bytes to the overall code size. + */ + while ( size_kb > 0 ) { + /* Mark this block as unused */ + free_block->magic = FREE_BLOCK_MAGIC; + free_block->size_kb = size_kb; + /* Move up by 1 kB */ + free_block = (void *)(((char *)free_block) + (1 << 10)); + size_kb--; + } + + /* Free up unused base memory */ + free_unused_base_memory(); +} + +void forget_base_memory ( void *ptr, size_t size ) +{ + /* Free memory, free real-mode stack, re-allocate real-mode + * stack. Do this so that we don't end up wasting a huge + * block of memory trapped behind the real-mode stack. + */ + _forget_base_memory ( ptr, size ); + forget_real_mode_stack(); + get_memsizes(); +} + +/* Do the actual freeing of memory. This is split out from + * forget_base_memory() so that it may be called separately. It + * should be called whenever base memory is deallocated by an external + * entity (if we can detect that it has done so) so that we get the + * chance to free up our own blocks. + */ +static void free_unused_base_memory ( void ) { + free_base_memory_block_t *free_block = NULL; + + /* Try to release memory back to the BIOS. Free all + * consecutive blocks marked as free. + */ + while ( 1 ) { + /* Calculate address of next potential free block */ + free_block = ( free_base_memory_block_t * ) + phys_to_virt ( FREE_BASE_MEMORY ); + + /* Stop processing if we're all the way up to 640K or + * if this is not a free block + */ + if ( ( *fbms == BASE_MEMORY_MAX ) || + ( free_block->magic != FREE_BLOCK_MAGIC ) ) { + break; + } + + /* Return memory to BIOS */ + *fbms += free_block->size_kb; + +#ifdef DEBUG_BASEMEM + printf ( "Freed %d kB base memory, %d kB now free\n", + free_block->size_kb, *fbms ); +#endif + + /* Zero out freed block. We do this in case + * the block contained any structures that + * might be located by scanning through + * memory. + */ + memset ( free_block, 0, free_block->size_kb << 10 ); + } +} + +/* Free base memory used by the prefix. Called once at start of + * Etherboot by arch_main(). + */ +void forget_prefix_base_memory ( void ) +{ + /* runtime_start_kb is _text rounded down to a physical kB boundary */ + uint32_t runtime_start_kb = virt_to_phys(_text) & ~0x3ff; + /* prefix_size_kb is the prefix size excluding any portion + * that overlaps into the first kB used by the runtime image + */ + uint32_t prefix_size_kb = runtime_start_kb - image_basemem; + +#ifdef DEBUG_BASEMEM + printf ( "Attempting to free base memory used by prefix\n" ); +#endif + + /* If the decompressor is in allocated base memory + * *and* the Etherboot text is in base + * memory, then free the decompressor. + */ + if ( ( image_basemem >= FREE_BASE_MEMORY ) && + ( runtime_start_kb >= FREE_BASE_MEMORY ) && + ( runtime_start_kb <= ( BASE_MEMORY_MAX << 10 ) ) ) + { + forget_base_memory ( phys_to_virt ( image_basemem ), + prefix_size_kb ); + /* Update image_basemem and image_basemem_size to + * indicate that our allocation now starts with _text + */ + image_basemem = runtime_start_kb; + image_basemem_size -= prefix_size_kb; + } +} + +/* Free base memory used by the runtime image. Called after + * relocation by arch_relocated_from(). + */ +void forget_runtime_base_memory ( unsigned long old_addr ) +{ + /* text_start_kb is old _text rounded down to a physical KB boundary */ + uint32_t old_text_start_kb = old_addr & ~0x3ff; + +#ifdef DEBUG_BASEMEM + printf ( "Attempting to free base memory used by runtime image\n" ); +#endif + + if ( ( image_basemem >= FREE_BASE_MEMORY ) && + ( image_basemem == old_text_start_kb ) ) + { + forget_base_memory ( phys_to_virt ( image_basemem ), + image_basemem_size ); + /* Update image_basemem to show no longer in use */ + image_basemem = 0; + image_basemem_size = 0; + } +} + +#endif /* PCBIOS */ diff --git a/src/arch/i386/firmware/pcbios/bios.c b/src/arch/i386/firmware/pcbios/bios.c new file mode 100644 index 00000000..70f26703 --- /dev/null +++ b/src/arch/i386/firmware/pcbios/bios.c @@ -0,0 +1,155 @@ +/* Etherboot routines for PCBIOS firmware. + * + * Body of routines taken from old pcbios.S + */ + +#ifdef PCBIOS + +#include "etherboot.h" +#include "realmode.h" +#include "segoff.h" + +#define CF ( 1 << 0 ) + +/************************************************************************** +CURRTICKS - Get Time +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. +**************************************************************************/ +#if defined(CONFIG_TSC_CURRTICKS) +#undef CONFIG_BIOS_CURRTICKS +#else +#define CONFIG_BIOS_CURRTICKS 1 +#endif +#if defined(CONFIG_BIOS_CURRTICKS) +unsigned long currticks (void) +{ + static uint32_t days = 0; + uint32_t *ticks = VIRTUAL(0x0040,0x006c); + uint8_t *midnight = VIRTUAL(0x0040,0x0070); + + /* Re-enable interrupts so that the timer interrupt can occur + */ + RM_FRAGMENT(rm_currticks, + "sti\n\t" + "nop\n\t" + "nop\n\t" + "cli\n\t" + ); + + real_call ( rm_currticks, NULL, NULL ); + + if ( *midnight ) { + *midnight = 0; + days += 0x1800b0; + } + return ( days + *ticks ); +} +#endif /* CONFIG_BIOS_CURRTICKS */ + +/************************************************************************** +INT15 - Call Interrupt 0x15 +**************************************************************************/ +int int15 ( int ax ) +{ + struct { + reg16_t ax; + } PACKED in_stack; + struct { + reg16_t flags; + } PACKED out_stack; + reg16_t ret_ax; + + RM_FRAGMENT(rm_int15, + "sti\n\t" + "popw %ax\n\t" + "stc\n\t" + "int $0x15\n\t" + "pushf\n\t" + "cli\n\t" + ); + + in_stack.ax.word = ax; + ret_ax.word = real_call ( rm_int15, &in_stack, &out_stack ); + + /* Carry flag clear indicates function not supported */ + if ( ! ( out_stack.flags.word & CF ) ) return 0; + return ret_ax.h; +} + +#ifdef POWERSAVE +/************************************************************************** +CPU_NAP - Save power by halting the CPU until the next interrupt +**************************************************************************/ +void cpu_nap ( void ) +{ + RM_FRAGMENT(rm_cpu_nap, + "sti\n\t" + "hlt\n\t" + "cli\n\t" + ); + + real_call ( rm_cpu_nap, NULL, NULL ); +} +#endif /* POWERSAVE */ + +#if (TRY_FLOPPY_FIRST > 0) +/************************************************************************** +DISK_INIT - Initialize the disk system +**************************************************************************/ +void disk_init ( void ) +{ + RM_FRAGMENT(rm_disk_init, + "sti\n\t" + "xorw %ax,%ax\n\t" + "movb $0x80,%dl\n\t" + "int $0x13\n\t" + "cli\n\t" + ); + + real_call ( rm_disk_init, NULL, NULL ); +} + +/************************************************************************** +DISK_READ - Read a sector from disk +**************************************************************************/ +unsigned int pcbios_disk_read ( int drive, int cylinder, int head, int sector, + char *buf ) { + struct { + reg16_t ax; + reg16_t cx; + reg16_t dx; + segoff_t buffer; + } PACKED in_stack; + struct { + reg16_t flags; + } PACKED out_stack; + reg16_t ret_ax; + + RM_FRAGMENT(rm_pcbios_disk_read, + "sti\n\t" + "popw %ax\n\t" + "popw %cx\n\t" + "popw %dx\n\t" + "popw %bx\n\t" + "popw %es\n\t" + "int $0x13\n\t" + "pushfw\n\t" + "cli\n\t" + ); + + in_stack.ax.h = 2; /* INT 13,2 - Read disk sector */ + in_stack.ax.l = 1; /* Read one sector */ + in_stack.cx.h = cylinder & 0xff; + in_stack.cx.l = ( ( cylinder >> 8 ) & 0x3 ) | sector; + in_stack.dx.h = head; + in_stack.dx.l = drive; + in_stack.buffer.segment = SEGMENT ( buf ); + in_stack.buffer.offset = OFFSET ( buf ); + ret_ax.word = real_call ( rm_pcbios_disk_read, &in_stack, &out_stack ); + return ( out_stack.flags.word & CF ) ? ret_ax.word : 0; +} +#endif /* TRY_FLOPPY_FIRST */ + +#endif /* PCBIOS */ diff --git a/src/arch/i386/firmware/pcbios/console.c b/src/arch/i386/firmware/pcbios/console.c new file mode 100644 index 00000000..f994f06e --- /dev/null +++ b/src/arch/i386/firmware/pcbios/console.c @@ -0,0 +1,85 @@ +/* Etherboot routines for PCBIOS firmware. + * + * Body of routines taken from old pcbios.S + */ + +#ifdef PCBIOS + +#include "etherboot.h" +#include "realmode.h" +#include "segoff.h" + +#define ZF ( 1 << 6 ) + +/************************************************************************** +CONSOLE_PUTC - Print a character on console +**************************************************************************/ +void console_putc ( int character ) +{ + struct { + reg16_t ax; + } PACKED in_stack; + + RM_FRAGMENT(rm_console_putc, + "sti\n\t" + "popw %ax\n\t" + "movb $0x0e, %ah\n\t" + "movl $1, %ebx\n\t" + "int $0x10\n\t" + "cli\n\t" + ); + + in_stack.ax.l = character; + real_call ( rm_console_putc, &in_stack, NULL ); +} + +/************************************************************************** +CONSOLE_GETC - Get a character from console +**************************************************************************/ +int console_getc ( void ) +{ + RM_FRAGMENT(rm_console_getc, + "sti\n\t" + "xorw %ax, %ax\n\t" + "int $0x16\n\t" + "xorb %ah, %ah\n\t" + "cli\n\t" + ); + + return real_call ( rm_console_getc, NULL, NULL ); +} + +/************************************************************************** +CONSOLE_ISCHAR - Check for keyboard interrupt +**************************************************************************/ +int console_ischar ( void ) +{ + RM_FRAGMENT(rm_console_ischar, + "sti\n\t" + "movb $1, %ah\n\t" + "int $0x16\n\t" + "pushfw\n\t" + "popw %ax\n\t" + "cli\n\t" + ); + + return ( ( real_call ( rm_console_ischar, NULL, NULL ) & ZF ) == 0 ); +} + +/************************************************************************** +GETSHIFT - Get keyboard shift state +**************************************************************************/ +int getshift ( void ) +{ + RM_FRAGMENT(rm_getshift, + "sti\n\t" + "movb $2, %ah\n\t" + "int $0x16\n\t" + "andw $0x3, %ax\n\t" + "cli\n\t" + ); + + return real_call ( rm_getshift, NULL, NULL ); +} + +#endif /* PCBIOS */ diff --git a/src/arch/i386/firmware/pcbios/e820mangler.S b/src/arch/i386/firmware/pcbios/e820mangler.S new file mode 100644 index 00000000..9349cf2b --- /dev/null +++ b/src/arch/i386/firmware/pcbios/e820mangler.S @@ -0,0 +1,296 @@ +#undef CODE16 +#if defined(PCBIOS) +#define CODE16 +#endif + +#ifdef CODE16 + +#define BOCHSBP xchgw %bx,%bx + + .text + .arch i386 + .section ".text16", "ax", @progbits + .code16 + +/**************************************************************************** + * Memory map mangling code + **************************************************************************** + */ + + .globl e820mangler +e820mangler: + +/* Macro to calculate offset of labels within code segment in + * installed copy of code. + */ +#define INSTALLED(x) ( (x) - e820mangler ) + +/**************************************************************************** + * Intercept INT 15 memory calls and remove the hidden memory ranges + * from the resulting memory map. + **************************************************************************** + */ + .globl _intercept_int15 +_intercept_int15: + /* Preserve registers */ + pushw %bp + /* Store %ax for future reference */ + pushw %ax + /* Make INT-style call to old INT15 routine */ + pushfw + lcall %cs:*INSTALLED(_intercepted_int15) + /* Preserve flags returned by original E820 routine */ + pushfw + /* Check for valid INT15 routine */ + jc intercept_int15_exit + /* Check for a routine we want to intercept */ + movw %sp, %bp + cmpw $0xe820, 2(%bp) + je intercept_e820 + cmpw $0xe801, 2(%bp) + je intercept_e801 + cmpb $0x88, 3(%bp) + je intercept_88 +intercept_int15_exit: + /* Restore registers and return */ + popfw + popw %bp /* discard original %ax */ + popw %bp + lret $2 /* 'iret' - flags already loaded */ + + .globl _intercepted_int15 +_intercepted_int15: .word 0,0 + +/**************************************************************************** + * Exclude an address range from a potentially overlapping address range + * + * Note: this *can* be called even if the range doesn't overlap; it + * will simply return the range unaltered. It copes with all the + * possible cases of overlap, including total overlap (which will + * modify the range to length zero). If the to-be-excluded range is + * in the middle of the target range, then the larger remaining + * portion will be returned. If %di is nonzero on entry then the + * range will only be truncated from the high end, i.e. the base + * address will never be altered. All this in less than 30 + * instructions. :) + * + * Parameters: + * %eax Base address of memory range + * %ecx Length of memory range + * %ebx Base address of memory range to exclude + * %edx Length of memory range to exclude + * %di 0 => truncate either end, 1 => truncate high end only + * Returns: + * %eax Updated base address of range + * %ecx Updated length of range + * %ebx,%edx Undefined + * All other registers (including %di) preserved + * + * Note: "ja" is used rather than "jg" because we are comparing + * unsigned ints + **************************************************************************** + */ +#ifdef TEST_EXCLUDE_ALGORITHM + .code32 +#endif /* TEST_EXCLUDE_ALGORITHM */ +exclude_memory_range: + /* Convert (start,length) to (start,end) */ + addl %eax, %ecx + addl %ebx, %edx + /* Calculate "prefix" length */ + subl %eax, %ebx /* %ebx = "prefix" length */ + ja 1f + xorl %ebx, %ebx /* Truncate to zero if negative */ +1: /* %di == 0 => truncate either end + * %di != 0 => truncate only high end + */ + testw %di, %di + je use_either + cmpl %eax, %edx + jbe 99f /* excl. range is below target range */ +use_prefix: /* Use prefix, discard suffix */ + addl %eax, %ebx /* %ebx = candidate end address */ + cmpl %ecx, %ebx /* %ecx = min ( %ebx, %ecx ) */ + ja 1f + movl %ebx, %ecx +1: jmp 99f +use_either: + /* Calculate "suffix" length */ + subl %ecx, %edx /* %edx = -( "suffix" length ) */ + jb 1f + xorl %edx, %edx /* Truncate to zero if negative */ +1: negl %edx /* %edx = "suffix" length */ + /* Use whichever is longest of "prefix" and "suffix" */ + cmpl %ebx, %edx + jbe use_prefix +use_suffix: /* Use suffix, discard prefix */ + negl %edx + addl %ecx, %edx /* %edx = candidate start address */ + cmpl %eax, %edx /* %eax = max ( %eax, %edx ) */ + jb 1f + movl %edx, %eax +1: +99: subl %eax, %ecx /* Convert back to (start,length) */ + ret + +#ifdef TEST_EXCLUDE_ALGORITHM + .globl __test_exclude +__test_exclude: + pushl %ebx + pushl %edi + movl 12(%esp), %eax + movl 16(%esp), %ecx + movl 20(%esp), %ebx + movl 24(%esp), %edx + movl 28(%esp), %edi + call exclude_memory_range + shll $16, %eax + orl %ecx, %eax + popl %edi + popl %ebx + ret + .code16 +#endif /* TEST_EXCLUDE_ALGORITHM */ + +/**************************************************************************** + * Exclude Etherboot-reserved address ranges from a potentially + * overlapping address range + * + * Parameters: + * %eax Base address of memory range + * %ecx Length of memory range + * %di 0 => truncate either end, 1 => truncate high end only + * Returns: + * %eax Updated base address of range + * %ecx Updated length of range + * All other registers (including %di) preserved + **************************************************************************** + */ +exclude_hidden_memory_ranges: + pushw %si + pushl %ebx + pushl %edx + movw $INSTALLED(_hide_memory), %si +2: movl %cs:0(%si), %ebx + movl %cs:4(%si), %edx + call exclude_memory_range + addw $8, %si + cmpw $INSTALLED(_hide_memory_end), %si + jl 2b + popl %edx + popl %ebx + popw %si + ret + + .globl _hide_memory +_hide_memory: + .long 0,0 /* Etherboot text (base,length) */ + .long 0,0 /* Heap (base,length) */ +_hide_memory_end: + +/**************************************************************************** + * Intercept INT 15,E820 calls and remove the hidden memory ranges + * from the resulting memory map. + **************************************************************************** + */ +#define SMAP ( 0x534d4150 ) +intercept_e820: + /* Check for valid E820 routine */ + cmpl $SMAP, %eax + jne intercept_int15_exit + /* If base address isn't in the low 4GB, return unaltered + * (since we never claim memory above 4GB). WARNING: we cheat + * by assuming that no E820 region will straddle the 4GB + * boundary: if this is not a valid assumption then things + * will probably break. + */ + cmpl $0, %es:4(%di) + jne intercept_int15_exit + /* Preserve registers */ + pushl %eax + pushl %ecx + /* Update returned memory range */ + movl %es:0(%di), %eax /* Base */ + movl %es:8(%di), %ecx /* Length */ + pushw %di + xorw %di, %di /* "truncate either end" flag */ + call exclude_hidden_memory_ranges + popw %di + movl %eax, %es:0(%di) /* Store updated base */ + movl %ecx, %es:8(%di) /* Store updated length */ + /* Restore registers and return */ + popl %ecx + popl %eax + jmp intercept_int15_exit + +/**************************************************************************** + * Intercept INT 15,E801 calls and remove the hidden memory ranges + * from the resulting memory map. + **************************************************************************** + */ +intercept_e801: + /* Adjust return values */ + call e801_adjust + xchgw %ax, %cx + xchgw %bx, %dx + call e801_adjust + xchgw %ax, %cx + xchgw %bx, %dx + jmp intercept_int15_exit + + /* %ax = #KB from 1MB+, %bx = #64KB from 16MB+ + * Return with modified values in %ax, %bx. Preserver other regs. + */ +e801_adjust: + pushw %di + pushl %ecx + pushl %eax + movw $1, %di /* "truncate only high end" flag */ + + /* Truncate #64KB from 16MB+ as appropriate */ + movw %bx, %cx /* (no need to zero high word) */ + shll $16, %ecx /* %ecx = length in bytes */ + movl $(1<<24), %eax /* 16MB start address */ + call exclude_hidden_memory_ranges + shrl $16, %ecx /* %cx = updated length in 64KB */ + movw %cx, %bx /* Return in %bx */ + + /* Truncate #KB from 1MB+ as appropriate */ + popw %cx /* Orig. %ax (high word already 0) */ + shll $10, %ecx /* %ecx = length in bytes */ + shrl $4, %eax /* 1MB start address */ + call exclude_hidden_memory_ranges + shrl $10, %ecx /* %cx = updated length in KB */ + pushw %cx /* Will be picked up in %eax */ + + popl %eax + popl %ecx + popw %di + ret + +/**************************************************************************** + * Intercept INT 15,88 calls and remove the hidden memory ranges + * from the resulting memory map. + **************************************************************************** + */ +intercept_88: + pushw %bx /* E801 adjust, ignore %bx */ + call e801_adjust + popw %bx + jmp intercept_int15_exit + + .globl e820mangler_end +e820mangler_end: + + .globl _e820mangler_size + .equ _e820mangler_size, e820mangler_end - e820mangler + .globl e820mangler_size +e820mangler_size: + .word _e820mangler_size + +#else + + .globl _e820mangler_size + .equ _e820mangler_size, 0 + +#endif /* CODE16 */ diff --git a/src/arch/i386/firmware/pcbios/hidemem.c b/src/arch/i386/firmware/pcbios/hidemem.c new file mode 100644 index 00000000..a9ae001e --- /dev/null +++ b/src/arch/i386/firmware/pcbios/hidemem.c @@ -0,0 +1,94 @@ +/* Utility functions to hide Etherboot by manipulating the E820 memory + * map. These could go in memsizes.c, but are placed here because not + * all images will need them. + */ + +#include "etherboot.h" +#include "hidemem.h" + +#ifdef CODE16 + +static int mangling = 0; +static void *mangler = NULL; + +#define INSTALLED(x) ( (typeof(&x)) ( (void*)(&x) - (void*)e820mangler \ + + mangler ) ) +#define intercept_int15 INSTALLED(_intercept_int15) +#define intercepted_int15 INSTALLED(_intercepted_int15) +#define hide_memory INSTALLED(_hide_memory) +#define INT15_VECTOR ( (segoff_t*) ( phys_to_virt( 4 * 0x15 ) ) ) + +int install_e820mangler ( void *new_mangler ) { + if ( mangling ) return 0; + memcpy ( new_mangler, &e820mangler, e820mangler_size ); + mangler = new_mangler; + return 1; +} + +/* Intercept INT15 calls and pass them through the mangler. The + * mangler must have been copied to base memory before making this + * call, and "mangler" must point to the base memory copy, which must + * be 16-byte aligned. + */ +int hide_etherboot ( void ) { + if ( mangling ) return 1; + if ( !mangler ) return 0; + + /* Hook INT15 handler */ + *intercepted_int15 = *INT15_VECTOR; + (*hide_memory)[0].start = virt_to_phys(_text); + (*hide_memory)[0].length = _end - _text; + /* IMPORTANT, possibly even FIXME: + * + * Etherboot has a tendency to claim a very large area of + * memory as possible heap; enough to make it impossible to + * load an OS if we hide all of it. We hide only the portion + * that's currently in use. This means that we MUST NOT + * perform further allocations from the heap while the mangler + * is active. + */ + (*hide_memory)[1].start = heap_ptr; + (*hide_memory)[1].length = heap_bot - heap_ptr; + INT15_VECTOR->segment = SEGMENT(mangler); + INT15_VECTOR->offset = 0; + + mangling = 1; + return 1; +} + +int unhide_etherboot ( void ) { + if ( !mangling ) return 1; + + /* Restore original INT15 handler + */ + if ( VIRTUAL(INT15_VECTOR->segment,INT15_VECTOR->offset) != mangler ) { + /* Oh dear... */ + +#ifdef WORK_AROUND_BPBATCH_BUG + /* BpBatch intercepts INT15, so can't unhook it, and + * then proceeds to ignore our PXENV_KEEP_UNDI return + * status, which means that it ends up zeroing out the + * INT15 handler routine. + * + * This rather ugly hack involves poking into + * BpBatch's code and changing it's stored value for + * the "next handler" in the INT15 chain. + */ + segoff_t *bp_chain = VIRTUAL ( 0x0060, 0x8254 ); + + if ( ( bp_chain->segment == SEGMENT(mangler) ) && + ( bp_chain->offset == 0 ) ) { + printf ( "\nBPBATCH bug workaround enabled\n" ); + *bp_chain = *intercepted_int15; + } +#endif /* WORK_AROUND_BPBATCH_BUG */ + + return 0; + } + *INT15_VECTOR = *intercepted_int15; + + mangling = 0; + return 1; +} + +#endif /* CODE16 */ diff --git a/src/arch/i386/firmware/pcbios/memsizes.c b/src/arch/i386/firmware/pcbios/memsizes.c new file mode 100644 index 00000000..18ae86da --- /dev/null +++ b/src/arch/i386/firmware/pcbios/memsizes.c @@ -0,0 +1,201 @@ +#ifdef PCBIOS + +#include "etherboot.h" +#include "realmode.h" + +#define CF ( 1 << 0 ) + +#ifndef MEMSIZES_DEBUG +#define MEMSIZES_DEBUG 0 +#endif + +/* by Eric Biederman */ + +struct meminfo meminfo; + +/************************************************************************** +BASEMEMSIZE - Get size of the conventional (base) memory +**************************************************************************/ +unsigned short basememsize ( void ) +{ + RM_FRAGMENT(rm_basememsize, + "int $0x12\n\t" + ); + return real_call ( rm_basememsize, NULL, NULL ); +} + +/************************************************************************** +MEMSIZE - Determine size of extended memory +**************************************************************************/ +unsigned int memsize ( void ) +{ + struct { + reg16_t ax; + } PACKED in_stack; + struct { + reg16_t ax; + reg16_t bx; + reg16_t cx; + reg16_t dx; + reg16_t flags; + } PACKED out_stack; + int memsize; + + RM_FRAGMENT(rm_memsize, + /* Some buggy BIOSes don't clear/set carry on pass/error of + * e801h memory size call or merely pass cx,dx through without + * changing them, so we set carry and zero cx,dx before call. + */ + "stc\n\t" + "xorw %cx,%cx\n\t" + "xorw %dx,%dx\n\t" + "popw %ax\n\t" + "int $0x15\n\t" + "pushfw\n\t" + "pushw %dx\n\t" + "pushw %cx\n\t" + "pushw %bx\n\t" + "pushw %ax\n\t" + ); + + /* Try INT 15,e801 first */ + in_stack.ax.word = 0xe801; + real_call ( rm_memsize, &in_stack, &out_stack ); + if ( out_stack.flags.word & CF ) { + /* INT 15,e801 not supported: try INT 15,88 */ + in_stack.ax.word = 0x8800; + memsize = real_call ( rm_memsize, &in_stack, &out_stack ); + } else { + /* Some BIOSes report extended memory via ax,bx rather + * than cx,dx + */ + if ( (out_stack.cx.word==0) && (out_stack.dx.word==0) ) { + /* Use ax,bx */ + memsize = ( out_stack.bx.word<<6 ) + out_stack.ax.word; + } else { + /* Use cx,dx */ + memsize = ( out_stack.dx.word<<6 ) + out_stack.cx.word; + } + } + return memsize; +} + +#define SMAP ( 0x534d4150 ) +int meme820 ( struct e820entry *buf, int count ) +{ + struct { + reg16_t flags; + reg32_t eax; + reg32_t ebx; + struct e820entry entry; + } PACKED stack; + int index = 0; + + RM_FRAGMENT(rm_meme820, + "addw $6, %sp\n\t" /* skip flags, eax */ + "popl %ebx\n\t" + "pushw %ss\n\t" /* es:di = ss:sp */ + "popw %es\n\t" + "movw %sp, %di\n\t" + "movl $0xe820, %eax\n\t" + "movl $" RM_STR(SMAP) ", %edx\n\t" + "movl $" RM_STR(E820ENTRY_SIZE) ", %ecx\n\t" + "int $0x15\n\t" + "pushl %ebx\n\t" + "pushl %eax\n\t" + "pushfw\n\t" + ); + + stack.ebx.dword = 0; /* 'EOF' marker */ + while ( ( index < count ) && + ( ( index == 0 ) || ( stack.ebx.dword != 0 ) ) ) { + real_call ( rm_meme820, &stack, &stack ); + if ( stack.eax.dword != SMAP ) return 0; + if ( stack.flags.word & CF ) return 0; + buf[index++] = stack.entry; + } + return index; +} + +void get_memsizes(void) +{ + /* Ensure we don't stomp bios data structutres. + * the interrupt table: 0x000 - 0x3ff + * the bios data area: 0x400 - 0x502 + * Dos variables: 0x502 - 0x5ff + */ + static const unsigned min_addr = 0x600; + unsigned i; + unsigned basemem; + basemem = get_free_base_memory(); + meminfo.basememsize = basememsize(); + meminfo.memsize = memsize(); +#ifndef IGNORE_E820_MAP + meminfo.map_count = meme820(meminfo.map, E820MAX); +#else + meminfo.map_count = 0; +#endif + if (meminfo.map_count == 0) { + /* If we don't have an e820 memory map fake it */ + meminfo.map_count = 2; + meminfo.map[0].addr = 0; + meminfo.map[0].size = meminfo.basememsize << 10; + meminfo.map[0].type = E820_RAM; + meminfo.map[1].addr = 1024*1024; + meminfo.map[1].size = meminfo.memsize << 10; + meminfo.map[1].type = E820_RAM; + } + /* Scrub the e820 map */ + for(i = 0; i < meminfo.map_count; i++) { + if (meminfo.map[i].type != E820_RAM) { + continue; + } + /* Reserve the bios data structures */ + if (meminfo.map[i].addr < min_addr) { + unsigned long delta; + delta = min_addr - meminfo.map[i].addr; + if (delta > meminfo.map[i].size) { + delta = meminfo.map[i].size; + } + meminfo.map[i].addr = min_addr; + meminfo.map[i].size -= delta; + } + /* Ensure the returned e820 map is in sync + * with the actual memory state + */ + if ((meminfo.map[i].addr < 0xa0000) && + ((meminfo.map[i].addr + meminfo.map[i].size) > basemem)) + { + if (meminfo.map[i].addr <= basemem) { + meminfo.map[i].size = basemem - meminfo.map[i].addr; + } else { + meminfo.map[i].addr = basemem; + meminfo.map[i].size = 0; + } + } + } +#if MEMSIZES_DEBUG +{ + int i; + printf("basememsize %d\n", meminfo.basememsize); + printf("memsize %d\n", meminfo.memsize); + printf("Memory regions(%d):\n", meminfo.map_count); + for(i = 0; i < meminfo.map_count; i++) { + unsigned long long r_start, r_end; + r_start = meminfo.map[i].addr; + r_end = r_start + meminfo.map[i].size; + printf("[%X%X, %X%X) type %d\n", + (unsigned long)(r_start >> 32), + (unsigned long)r_start, + (unsigned long)(r_end >> 32), + (unsigned long)r_end, + meminfo.map[i].type); +#if defined(CONSOLE_FIRMWARE) + sleep(1); /* No way to see 32 entries on a standard 80x25 screen... */ +#endif + } +} +#endif +} + +#endif /* PCBIOS */ diff --git a/src/arch/i386/include/bits/byteswap.h b/src/arch/i386/include/bits/byteswap.h new file mode 100644 index 00000000..1bc84fda --- /dev/null +++ b/src/arch/i386/include/bits/byteswap.h @@ -0,0 +1,45 @@ +#ifndef ETHERBOOT_BITS_BYTESWAP_H +#define ETHERBOOT_BITS_BYTESWAP_H + +static inline uint16_t __i386_bswap_16(uint16_t x) +{ + __asm__("xchgb %b0,%h0\n\t" + : "=q" (x) + : "0" (x)); + return x; +} + +static inline 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; +} + + +#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_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))) + + +#endif /* ETHERBOOT_BITS_BYTESWAP_H */ diff --git a/src/arch/i386/include/bits/cpu.h b/src/arch/i386/include/bits/cpu.h new file mode 100644 index 00000000..2e9b27ba --- /dev/null +++ b/src/arch/i386/include/bits/cpu.h @@ -0,0 +1,243 @@ +#ifndef I386_BITS_CPU_H +#define I386_BITS_CPU_H + + +/* Sample usage: CPU_FEATURE_P(cpu.x86_capability, FPU) */ +#define CPU_FEATURE_P(CAP, FEATURE) \ + (!!(CAP[(X86_FEATURE_##FEATURE)/32] & ((X86_FEATURE_##FEATURE) & 0x1f))) + +#define NCAPINTS 4 /* Currently we have 4 32-bit words worth of info */ + +/* Intel-defined CPU features, CPUID level 0x00000001, word 0 */ +#define X86_FEATURE_FPU (0*32+ 0) /* Onboard FPU */ +#define X86_FEATURE_VME (0*32+ 1) /* Virtual Mode Extensions */ +#define X86_FEATURE_DE (0*32+ 2) /* Debugging Extensions */ +#define X86_FEATURE_PSE (0*32+ 3) /* Page Size Extensions */ +#define X86_FEATURE_TSC (0*32+ 4) /* Time Stamp Counter */ +#define X86_FEATURE_MSR (0*32+ 5) /* Model-Specific Registers, RDMSR, WRMSR */ +#define X86_FEATURE_PAE (0*32+ 6) /* Physical Address Extensions */ +#define X86_FEATURE_MCE (0*32+ 7) /* Machine Check Architecture */ +#define X86_FEATURE_CX8 (0*32+ 8) /* CMPXCHG8 instruction */ +#define X86_FEATURE_APIC (0*32+ 9) /* Onboard APIC */ +#define X86_FEATURE_SEP (0*32+11) /* SYSENTER/SYSEXIT */ +#define X86_FEATURE_MTRR (0*32+12) /* Memory Type Range Registers */ +#define X86_FEATURE_PGE (0*32+13) /* Page Global Enable */ +#define X86_FEATURE_MCA (0*32+14) /* Machine Check Architecture */ +#define X86_FEATURE_CMOV (0*32+15) /* CMOV instruction (FCMOVCC and FCOMI too if FPU present) */ +#define X86_FEATURE_PAT (0*32+16) /* Page Attribute Table */ +#define X86_FEATURE_PSE36 (0*32+17) /* 36-bit PSEs */ +#define X86_FEATURE_PN (0*32+18) /* Processor serial number */ +#define X86_FEATURE_CLFLSH (0*32+19) /* Supports the CLFLUSH instruction */ +#define X86_FEATURE_DTES (0*32+21) /* Debug Trace Store */ +#define X86_FEATURE_ACPI (0*32+22) /* ACPI via MSR */ +#define X86_FEATURE_MMX (0*32+23) /* Multimedia Extensions */ +#define X86_FEATURE_FXSR (0*32+24) /* FXSAVE and FXRSTOR instructions (fast save and restore */ + /* of FPU context), and CR4.OSFXSR available */ +#define X86_FEATURE_XMM (0*32+25) /* Streaming SIMD Extensions */ +#define X86_FEATURE_XMM2 (0*32+26) /* Streaming SIMD Extensions-2 */ +#define X86_FEATURE_SELFSNOOP (0*32+27) /* CPU self snoop */ +#define X86_FEATURE_HT (0*32+28) /* Hyper-Threading */ +#define X86_FEATURE_ACC (0*32+29) /* Automatic clock control */ +#define X86_FEATURE_IA64 (0*32+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 (1*32+11) /* SYSCALL/SYSRET */ +#define X86_FEATURE_MMXEXT (1*32+22) /* AMD MMX extensions */ +#define X86_FEATURE_LM (1*32+29) /* Long Mode (x86-64) */ +#define X86_FEATURE_3DNOWEXT (1*32+30) /* AMD 3DNow! extensions */ +#define X86_FEATURE_3DNOW (1*32+31) /* 3DNow! */ + +/* Transmeta-defined CPU features, CPUID level 0x80860001, word 2 */ +#define X86_FEATURE_RECOVERY (2*32+ 0) /* CPU in recovery mode */ +#define X86_FEATURE_LONGRUN (2*32+ 1) /* Longrun power control */ +#define X86_FEATURE_LRTI (2*32+ 3) /* LongRun table interface */ + +/* Other features, Linux-defined mapping, word 3 */ +/* This range is used for feature bits which conflict or are synthesized */ +#define X86_FEATURE_CXMMX (3*32+ 0) /* Cyrix MMX extensions */ +#define X86_FEATURE_K6_MTRR (3*32+ 1) /* AMD K6 nonstandard MTRRs */ +#define X86_FEATURE_CYRIX_ARR (3*32+ 2) /* Cyrix ARRs (= MTRRs) */ +#define X86_FEATURE_CENTAUR_MCR (3*32+ 3) /* Centaur MCRs (= MTRRs) */ + +#define MAX_X86_VENDOR_ID 16 +struct cpuinfo_x86 { + uint8_t x86; /* CPU family */ + uint8_t x86_model; + uint8_t x86_mask; + + int cpuid_level; /* Maximum supported CPUID level, -1=no CPUID */ + unsigned x86_capability[NCAPINTS]; + char x86_vendor_id[MAX_X86_VENDOR_ID]; +}; + + +#define X86_VENDOR_INTEL 0 +#define X86_VENDOR_CYRIX 1 +#define X86_VENDOR_AMD 2 +#define X86_VENDOR_UMC 3 +#define X86_VENDOR_NEXGEN 4 +#define X86_VENDOR_CENTAUR 5 +#define X86_VENDOR_RISE 6 +#define X86_VENDOR_TRANSMETA 7 +#define X86_VENDOR_NSC 8 +#define X86_VENDOR_UNKNOWN 0xff + +/* + * 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 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)); +} + +/* + * CPUID functions returning a single datum + */ +static inline unsigned int cpuid_eax(unsigned int op) +{ + unsigned int eax; + + __asm__("cpuid" + : "=a" (eax) + : "0" (op) + : "bx", "cx", "dx"); + return eax; +} +static inline unsigned int cpuid_ebx(unsigned int op) +{ + unsigned int eax, ebx; + + __asm__("cpuid" + : "=a" (eax), "=b" (ebx) + : "0" (op) + : "cx", "dx" ); + return ebx; +} +static inline unsigned int cpuid_ecx(unsigned int op) +{ + unsigned int eax, ecx; + + __asm__("cpuid" + : "=a" (eax), "=c" (ecx) + : "0" (op) + : "bx", "dx" ); + return ecx; +} +static inline unsigned int cpuid_edx(unsigned int op) +{ + unsigned int eax, edx; + + __asm__("cpuid" + : "=a" (eax), "=d" (edx) + : "0" (op) + : "bx", "cx"); + return edx; +} + +/* + * Intel CPU features in CR4 + */ +#define X86_CR4_VME 0x0001 /* enable vm86 extensions */ +#define X86_CR4_PVI 0x0002 /* virtual interrupts flag enable */ +#define X86_CR4_TSD 0x0004 /* disable time stamp at ipl 3 */ +#define X86_CR4_DE 0x0008 /* enable debugging extensions */ +#define X86_CR4_PSE 0x0010 /* enable page size extensions */ +#define X86_CR4_PAE 0x0020 /* enable physical address extensions */ +#define X86_CR4_MCE 0x0040 /* Machine check enable */ +#define X86_CR4_PGE 0x0080 /* enable global pages */ +#define X86_CR4_PCE 0x0100 /* enable performance counters at ipl 3 */ +#define X86_CR4_OSFXSR 0x0200 /* enable fast FPU save and restore */ +#define X86_CR4_OSXMMEXCPT 0x0400 /* enable unmasked SSE exceptions */ + + +#define MSR_K6_EFER 0xC0000080 +/* EFER bits: */ +#define _EFER_SCE 0 /* SYSCALL/SYSRET */ +#define _EFER_LME 8 /* Long mode enable */ +#define _EFER_LMA 10 /* Long mode active (read-only) */ +#define _EFER_NX 11 /* No execute enable */ + +#define EFER_SCE (1<<_EFER_SCE) +#define EFER_LME (1<<EFER_LME) +#define EFER_LMA (1<<EFER_LMA) +#define EFER_NX (1<<_EFER_NX) + +#define rdmsr(msr,val1,val2) \ + __asm__ __volatile__("rdmsr" \ + : "=a" (val1), "=d" (val2) \ + : "c" (msr)) + +#define wrmsr(msr,val1,val2) \ + __asm__ __volatile__("wrmsr" \ + : /* no outputs */ \ + : "c" (msr), "a" (val1), "d" (val2)) + + +#define read_cr0() ({ \ + unsigned int __dummy; \ + __asm__( \ + "movl %%cr0, %0\n\t" \ + :"=r" (__dummy)); \ + __dummy; \ +}) +#define write_cr0(x) \ + __asm__("movl %0,%%cr0": :"r" (x)); + +#define read_cr3() ({ \ + unsigned int __dummy; \ + __asm__( \ + "movl %%cr3, %0\n\t" \ + :"=r" (__dummy)); \ + __dummy; \ +}) +#define write_cr3x(x) \ + __asm__("movl %0,%%cr3": :"r" (x)); + + +#define read_cr4() ({ \ + unsigned int __dummy; \ + __asm__( \ + "movl %%cr4, %0\n\t" \ + :"=r" (__dummy)); \ + __dummy; \ +}) +#define write_cr4x(x) \ + __asm__("movl %0,%%cr4": :"r" (x)); + + +extern struct cpuinfo_x86 cpu_info; +#ifdef CONFIG_X86_64 +extern void cpu_setup(void); +#else +#define cpu_setup() do {} while(0) +#endif + +#endif /* I386_BITS_CPU_H */ diff --git a/src/arch/i386/include/bits/elf.h b/src/arch/i386/include/bits/elf.h new file mode 100644 index 00000000..dad9c7b8 --- /dev/null +++ b/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/src/arch/i386/include/bits/elf_x.h b/src/arch/i386/include/bits/elf_x.h new file mode 100644 index 00000000..86c67250 --- /dev/null +++ b/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/src/arch/i386/include/bits/eltorito.h b/src/arch/i386/include/bits/eltorito.h new file mode 100644 index 00000000..d43e9aac --- /dev/null +++ b/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/src/arch/i386/include/bits/endian.h b/src/arch/i386/include/bits/endian.h new file mode 100644 index 00000000..b23b233a --- /dev/null +++ b/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/src/arch/i386/include/bits/string.h b/src/arch/i386/include/bits/string.h new file mode 100644 index 00000000..3743d011 --- /dev/null +++ b/src/arch/i386/include/bits/string.h @@ -0,0 +1,99 @@ +#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. + */ + +/* + * 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_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_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/src/arch/i386/include/callbacks_arch.h b/src/arch/i386/include/callbacks_arch.h new file mode 100644 index 00000000..f9cba488 --- /dev/null +++ b/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/src/arch/i386/include/hidemem.h b/src/arch/i386/include/hidemem.h new file mode 100644 index 00000000..600a8692 --- /dev/null +++ b/src/arch/i386/include/hidemem.h @@ -0,0 +1,21 @@ +#ifndef HIDEMEM_H +#define HIDEMEM_H + +#include "segoff.h" + +extern int install_e820mangler ( void *new_mangler ); +extern int hide_etherboot ( void ); +extern int unhide_etherboot ( void ); + +/* Symbols in e820mangler.S */ +extern void e820mangler ( void ); +extern void _intercept_int15 ( void ); +extern segoff_t _intercepted_int15; +typedef struct { + uint32_t start; + uint32_t length; +} exclude_range_t; +extern exclude_range_t _hide_memory[2]; +extern uint16_t e820mangler_size; + +#endif /* HIDEMEM_H */ diff --git a/src/arch/i386/include/hooks.h b/src/arch/i386/include/hooks.h new file mode 100644 index 00000000..0764edaf --- /dev/null +++ b/src/arch/i386/include/hooks.h @@ -0,0 +1,9 @@ +#ifndef ETHERBOOT_I386_HOOKS_H +#define ETHERBOOT_I386_HOOKS_H + +void arch_main(in_call_data_t *data, va_list params); +void arch_on_exit(int status); +#define arch_relocate_to(addr) +void arch_relocated_from ( uint32_t old_addr ); + +#endif /* ETHERBOOT_I386_HOOKS_H */ diff --git a/src/arch/i386/include/io.h b/src/arch/i386/include/io.h new file mode 100644 index 00000000..e351a0c1 --- /dev/null +++ b/src/arch/i386/include/io.h @@ -0,0 +1,246 @@ +#ifndef ETHERBOOT_IO_H +#define ETHERBOOT_IO_H + + +/* Amount of relocation etherboot is experiencing */ +extern unsigned long virt_offset; + +/* Don't require identity mapped physical memory, + * osloader.c is the only valid user at the moment. + */ +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); +} + +/* 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. + */ +#define readb(addr) (*(volatile unsigned char *) (addr)) +#define readw(addr) (*(volatile unsigned short *) (addr)) +#define readl(addr) (*(volatile unsigned int *) (addr)) + +#define writeb(b,addr) ((*(volatile unsigned char *) (addr)) = (b)) +#define writew(b,addr) ((*(volatile unsigned short *) (addr)) = (b)) +#define writel(b,addr) ((*(volatile unsigned int *) (addr)) = (b)) + +#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/src/arch/i386/include/latch.h b/src/arch/i386/include/latch.h new file mode 100644 index 00000000..38a8bd21 --- /dev/null +++ b/src/arch/i386/include/latch.h @@ -0,0 +1,10 @@ +#ifndef LATCH_H +#define LATCH_H + +#define TICKS_PER_SEC 18 + +/* For different calibrators of the TSC move the declaration of + * sleep_latch and the definitions of it's length here... + */ + +#endif /* LATCH_H */ diff --git a/src/arch/i386/include/limits.h b/src/arch/i386/include/limits.h new file mode 100644 index 00000000..f13db267 --- /dev/null +++ b/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/src/arch/i386/include/pic8259.h b/src/arch/i386/include/pic8259.h new file mode 100644 index 00000000..694e9d13 --- /dev/null +++ b/src/arch/i386/include/pic8259.h @@ -0,0 +1,96 @@ +/* + * 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 "segoff.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(x) ( (x) < IRQ_PIC_CUTOFF ? PIC1_ICR : PIC2_ICR ) +#define ICR_VALUE(x) ( (x) % IRQ_PIC_CUTOFF ) +#define CHAINED_IRQ 2 + +/* Utility macros to convert IRQ numbers to INT numbers and INT vectors */ +#define IRQ_INT(x) ( (x)<IRQ_PIC_CUTOFF ? (x)+0x08 : (x)-IRQ_PIC_CUTOFF+0x70 ) +#define INT_VECTOR(x) ( (segoff_t*) phys_to_virt( 4 * (x) ) ) +#define IRQ_VECTOR(x) ( INT_VECTOR ( IRQ_INT(x) ) ) + +/* Other constants */ +typedef uint8_t irq_t; +#define IRQ_MAX (15) +#define IRQ_NONE (0xff) + +/* Function prototypes + */ +int install_irq_handler ( irq_t irq, segoff_t *handler, + uint8_t *previously_enabled, + segoff_t *previous_handler ); +int remove_irq_handler ( irq_t irq, segoff_t *handler, + uint8_t *previously_enabled, + segoff_t *previous_handler ); +int install_trivial_irq_handler ( irq_t irq ); +int remove_trivial_irq_handler ( irq_t irq ); +int trivial_irq_triggered ( irq_t irq ); +int copy_trivial_irq_handler ( void *target, size_t target_size ); +void send_non_specific_eoi ( irq_t irq ); +void send_specific_eoi ( irq_t irq ); +void fake_irq ( irq_t irq ); +#ifdef DEBUG_IRQ +void dump_irq_status ( void ); +#else +#define dump_irq_status() +#endif + +/* Other code (e.g. undi.c) needs to know the size of the trivial IRQ + * handler code, so we put prototypes and the size macro here. + */ +extern void _trivial_irq_handler ( void ); +extern void _trivial_irq_handler_end ( void ); +#define TRIVIAL_IRQ_HANDLER_SIZE FRAGMENT_SIZE(_trivial_irq_handler) + +#endif /* PIC8259_H */ diff --git a/src/arch/i386/include/pxe_callbacks.h b/src/arch/i386/include/pxe_callbacks.h new file mode 100644 index 00000000..9b941931 --- /dev/null +++ b/src/arch/i386/include/pxe_callbacks.h @@ -0,0 +1,33 @@ +/* Header for pxe_callbacks.c. + */ + +#ifndef PXE_CALLBACKS_H +#define PXE_CALLBACKS_H + +#include "etherboot.h" +#include "segoff.h" +#include "pxe.h" + +typedef struct { + segoff_t orig_retaddr; + uint16_t opcode; + segoff_t segoff; +} PACKED pxe_call_params_t; + +/* + * These values are hard-coded into the PXE spec + */ +#define PXE_LOAD_SEGMENT (0x0000) +#define PXE_LOAD_OFFSET (0x7c00) +#define PXE_LOAD_ADDRESS ( ( PXE_LOAD_SEGMENT << 4 ) + PXE_LOAD_OFFSET ) + +/* Function prototypes + */ +extern pxe_stack_t * install_pxe_stack ( void *base ); +extern void use_undi_ds_for_rm_stack ( uint16_t ds ); +extern int hook_pxe_stack ( void ); +extern int unhook_pxe_stack ( void ); +extern void remove_pxe_stack ( void ); +extern int xstartpxe ( void ); + +#endif /* PXE_CALLBACKS_H */ diff --git a/src/arch/i386/include/pxe_types.h b/src/arch/i386/include/pxe_types.h new file mode 100644 index 00000000..7b093e6a --- /dev/null +++ b/src/arch/i386/include/pxe_types.h @@ -0,0 +1,35 @@ +/* + * 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_TYPES_H +#define PXE_TYPES_H + +/* SEGOFF16_t defined in separate header + */ +#include "segoff.h" +typedef segoff_t I386_SEGOFF16_t; +#define SEGOFF16_t I386_SEGOFF16_t + +#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); + +typedef struct { + uint16_t Seg_Addr; + uint32_t Phy_Addr; + uint16_t Seg_Size; +} PACKED I386_SEGDESC_t; /* PACKED is required, otherwise gcc pads + * this out to 12 bytes - + * mbrown@fensystems.co.uk (mcb30) 17/5/03 */ +#define SEGDESC_t I386_SEGDESC_t + +typedef uint16_t I386_SEGSEL_t; +#define SEGSEL_t I386_SEGSEL_t + +#endif /* PXE_TYPES_H */ diff --git a/src/arch/i386/include/realmode.h b/src/arch/i386/include/realmode.h new file mode 100644 index 00000000..eca92b9b --- /dev/null +++ b/src/arch/i386/include/realmode.h @@ -0,0 +1,124 @@ +/* Real-mode interface + */ + +#ifndef ASSEMBLY + +#include "etherboot.h" +#include "segoff.h" + +typedef union { + struct { + union { + uint8_t l; + uint8_t byte; + }; + uint8_t h; + } PACKED; + uint16_t word; +} PACKED reg16_t; + +typedef union { + reg16_t w; + uint32_t dword; +} PACKED reg32_t; + +/* Macros to help with defining inline real-mode trampoline fragments. + */ +#define RM_XSTR(x) #x /* Macro hackery needed to stringify */ +#define RM_STR(x) RM_XSTR(x) +#define RM_FRAGMENT(name, asm_code_str) \ + extern void name ( void ); \ + extern void name ## _end (void); \ + __asm__( \ + ".section \".text16\"\n\t" \ + ".code16\n\t" \ + ".arch i386\n\t" \ + ".globl " #name " \n\t" \ + #name ":\n\t" \ + asm_code_str "\n\t" \ + ".globl " #name "_end\n\t" \ + #name "_end:\n\t" \ + ".code32\n\t" \ + ".previous\n\t" \ + ) + +#define FRAGMENT_SIZE(fragment) ( (size_t) ( ( (void*) fragment ## _end )\ + - ( (void*) (fragment) ) ) ) + +/* Data structures in _prot_to_real and _real_to_prot. These + * structures are accessed by assembly code as well as C code. + */ +typedef struct { + uint32_t esp; + uint16_t cs; + uint16_t ss; + uint32_t r2p_params; +} PACKED prot_to_real_params_t; + +typedef struct { + uint32_t ret_addr; + uint32_t esp; + uint32_t ebx; + uint32_t esi; + uint32_t edi; + uint32_t ebp; + uint32_t out_stack; + uint32_t out_stack_len; +} PACKED real_to_prot_params_t; + +/* Function prototypes: realmode.c + */ +#define real_call( fragment, in_stack, out_stack ) \ + _real_call ( fragment, FRAGMENT_SIZE(fragment), \ + (void*)(in_stack), \ + ( (in_stack) == NULL ? 0 : sizeof(*(in_stack)) ), \ + (void*)(out_stack), \ + ( (out_stack) == NULL ? 0 : sizeof(*(out_stack)) ) ) +extern uint16_t _real_call ( void *fragment, int fragment_len, + void *in_stack, int in_stack_len, + void *out_stack, int out_stack_len ); +/* Function prototypes: realmode_asm.S + */ +extern void rm_callback_interface; +extern uint16_t rm_callback_interface_size; +extern uint32_t rm_etherboot_location; +extern void _rm_in_call ( void ); +extern void _rm_in_call_far ( void ); + +extern void _prot_to_real_prefix ( void ); +extern void _prot_to_real_prefix_end ( void ); +extern uint16_t prot_to_real_prefix_size; + +extern void _real_to_prot_suffix ( void ); +extern void _real_to_prot_suffix_end ( void ); +extern uint16_t real_to_prot_suffix_size; + +/* PXE assembler bits */ +extern void pxe_callback_interface; +extern uint16_t pxe_callback_interface_size; +extern void _pxe_in_call_far ( void ); +extern void _pxenv_in_call_far ( void ); +extern void _pxe_intercept_int1a ( void ); +extern segoff_t _pxe_intercepted_int1a; +extern segoff_t _pxe_pxenv_location; + +/* Global variables + */ +extern uint32_t real_mode_stack; +extern size_t real_mode_stack_size; +extern int lock_real_mode_stack; + + +/* Function prototypes from basemem.c + */ +#ifdef LINUXBIOS +/* A silly hard code that let's the code compile and work. + * When this becomes a problem feel free to implement + * something better. + */ +static inline void allot_real_mode_stack(void) { real_mode_stack = 0x7c00; } +#else +void allot_real_mode_stack(void); +#endif + +#endif /* ASSEMBLY */ diff --git a/src/arch/i386/include/segoff.h b/src/arch/i386/include/segoff.h new file mode 100644 index 00000000..822ddd33 --- /dev/null +++ b/src/arch/i386/include/segoff.h @@ -0,0 +1,41 @@ +/* + * Segment:offset types and macros + * + * Initially written by Michael Brown (mcb30). + */ + +#ifndef SEGOFF_H +#define SEGOFF_H + +#include <stdint.h> +#include <osdep.h> +#include <io.h> + +/* Segment:offset structure. Note that the order within the structure + * is offset:segment. + */ +typedef struct { + uint16_t offset; + uint16_t segment; +} segoff_t; + +/* Macros for converting from virtual to segment:offset addresses, + * when we don't actually care which of the many isomorphic results we + * get. + */ +#ifdef DEBUG_SEGMENT +uint16_t SEGMENT ( const void * const ptr ) { + uint32_t phys = virt_to_phys ( ptr ); + if ( phys > 0xfffff ) { + printf ( "FATAL ERROR: segment address out of range\n" ); + } + return phys >> 4; +} +#else +#define SEGMENT(x) ( virt_to_phys ( x ) >> 4 ) +#endif +#define OFFSET(x) ( virt_to_phys ( x ) & 0xf ) +#define SEGOFF(x) { OFFSET(x), SEGMENT(x) } +#define VIRTUAL(x,y) ( phys_to_virt ( ( ( x ) << 4 ) + ( y ) ) ) + +#endif /* SEGOFF_H */ diff --git a/src/arch/i386/include/setjmp.h b/src/arch/i386/include/setjmp.h new file mode 100644 index 00000000..ed2be270 --- /dev/null +++ b/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/src/arch/i386/include/stdint.h b/src/arch/i386/include/stdint.h new file mode 100644 index 00000000..42b04830 --- /dev/null +++ b/src/arch/i386/include/stdint.h @@ -0,0 +1,16 @@ +#ifndef STDINT_H +#define STDINT_H + +typedef unsigned size_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; + +#endif /* STDINT_H */ diff --git a/src/arch/i386/include/vga.h b/src/arch/i386/include/vga.h new file mode 100644 index 00000000..01fc39d8 --- /dev/null +++ b/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/src/arch/i386/prefix/bImageprefix.S b/src/arch/i386/prefix/bImageprefix.S new file mode 100644 index 00000000..a478713d --- /dev/null +++ b/src/arch/i386/prefix/bImageprefix.S @@ -0,0 +1,614 @@ +/* + 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 + .globl _prefix +_prefix: + +/* + 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, $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 + .globl prefix_exit +prefix_exit: + int $0x19 /* should try to boot machine */ + .globl prefix_exit_end +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/src/arch/i386/prefix/boot1a.s b/src/arch/i386/prefix/boot1a.s new file mode 100644 index 00000000..557462f1 --- /dev/null +++ b/src/arch/i386/prefix/boot1a.s @@ -0,0 +1,410 @@ +# This code is no longer used in Etherboot. It is not maintained and +# may not work. + + +# +# Copyright (c) 1998 Robert Nordier +# All rights reserved. +# Very small bootrom changes by Luigi Rizzo +# <comment author="Luigi Rizzo"> +# I recently had the problem of downloading the etherboot code +# from a hard disk partition instead of a floppy, and noticed that +# floppyload.S does not do the job. With a bit of hacking to +# the FreeBSD's boot1.s code, I managed to obtain a boot sector +# which works both for floppies and hard disks -- basically you +# do something like +# +# cat boot1a bin32/<yourcard>.lzrom > /dev/ad0s4 +# +# (or whatever is the HD partition you are using, I am using slice +# 4 on FreeBSD) and you are up and running. +# Then with "fdisk" you have to mark your partition as having type "1" +# (which is listed as DOS-- but basically it must be something matching +# the variable PRT_BSD in the assembly source below). +# </comment> +# +# Redistribution and use in source and binary forms are freely +# permitted provided that the above copyright notice and this +# paragraph and the following disclaimer are duplicated in all +# such forms. +# +# This software is provided "AS IS" and without any express or +# implied warranties, including, without limitation, the implied +# warranties of merchantability and fitness for a particular +# purpose. +# +# Makefile: +#boot1a: boot1a.out +# objcopy -S -O binary boot1a.out boot1a +# +#boot1a.out: boot1a.o +# ld -nostdlib -static -N -e start -Ttext 0x7c00 -o boot1a.out boot1a.o +# +#boot1a.o: boot1a.s +# as --defsym FLAGS=0x80 boot1a.s -o boot1a.o +# +# + +# $FreeBSD: src/sys/boot/i386/boot2/boot1.s,v 1.10.2.2 2000/07/07 21:12:32 jhb Exp $ + +# Memory Locations + .set MEM_REL,0x700 # Relocation address + .set MEM_ARG,0x900 # Arguments + .set MEM_ORG,0x7c00 # Origin + .set MEM_BUF,0x8c00 # Load area + .set MEM_BTX,0x9000 # BTX start + .set MEM_JMP,0x9010 # BTX entry point + .set MEM_USR,0xa000 # Client start + .set BDA_BOOT,0x472 # Boot howto flag + +# Partition Constants + .set PRT_OFF,0x1be # Partition offset + .set PRT_NUM,0x4 # Partitions + .set PRT_BSD,0x1 # Partition type + +# Flag Bits + .set FL_PACKET,0x80 # Packet mode + +# Misc. Constants + .set SIZ_PAG,0x1000 # Page size + .set SIZ_SEC,0x200 # Sector size + + .globl start + .globl xread + .code16 + +start: jmp main # Start recognizably + + .org 0x4,0x90 +# +# Trampoline used by boot2 to call read to read data from the disk via +# the BIOS. Call with: +# +# %cx:%ax - long - LBA to read in +# %es:(%bx) - caddr_t - buffer to read data into +# %dl - byte - drive to read from +# %dh - byte - num sectors to read +# + +xread: push %ss # Address + pop %ds # data +# +# Setup an EDD disk packet and pass it to read +# +xread.1: # Starting + pushl $0x0 # absolute + push %cx # block + push %ax # number + push %es # Address of + push %bx # transfer buffer + xor %ax,%ax # Number of + movb %dh,%al # blocks to + push %ax # transfer + push $0x10 # Size of packet + mov %sp,%bp # Packet pointer + callw read # Read from disk + lea 0x10(%bp),%sp # Clear stack + lret # To far caller +# +# Load the rest of boot2 and BTX up, copy the parts to the right locations, +# and start it all up. +# + +# +# Setup the segment registers to flat addressing (segment 0) and setup the +# stack to end just below the start of our code. +# +main: cld # String ops inc + xor %cx,%cx # Zero + mov %cx,%es # Address + mov %cx,%ds # data + mov %cx,%ss # Set up + mov $start,%sp # stack +# +# Relocate ourself to MEM_REL. Since %cx == 0, the inc %ch sets +# %cx == 0x100. +# + mov %sp,%si # Source + mov $MEM_REL,%di # Destination + incb %ch # Word count + rep # Copy + movsw # code +# +# If we are on a hard drive, then load the MBR and look for the first +# FreeBSD slice. We use the fake partition entry below that points to +# the MBR when we call nread. The first pass looks for the first active +# FreeBSD slice. The second pass looks for the first non-active FreeBSD +# slice if the first one fails. +# + mov $part4,%si # Partition + cmpb $0x80,%dl # Hard drive? + jb main.4 # No + movb $0x1,%dh # Block count + callw nread # Read MBR + mov $0x1,%cx # Two passes +main.1: mov $MEM_BUF+PRT_OFF,%si # Partition table + movb $0x1,%dh # Partition +main.2: cmpb $PRT_BSD,0x4(%si) # Our partition type? + jne main.3 # No + jcxz main.5 # If second pass + testb $0x80,(%si) # Active? + jnz main.5 # Yes +main.3: add $0x10,%si # Next entry + incb %dh # Partition + cmpb $0x1+PRT_NUM,%dh # In table? + jb main.2 # Yes + dec %cx # Do two + jcxz main.1 # passes +# +# If we get here, we didn't find any FreeBSD slices at all, so print an +# error message and die. +# +booterror: mov $msg_part,%si # Message + jmp error # Error +# +# Floppies use partition 0 of drive 0. +# +main.4: xor %dx,%dx # Partition:drive +# +# Ok, we have a slice and drive in %dx now, so use that to locate and load +# boot2. %si references the start of the slice we are looking for, so go +# ahead and load up the first 16 sectors (boot1 + boot2) from that. When +# we read it in, we conveniently use 0x8c00 as our transfer buffer. Thus, +# boot1 ends up at 0x8c00, and boot2 starts at 0x8c00 + 0x200 = 0x8e00. +# The first part of boot2 is the disklabel, which is 0x200 bytes long. +# The second part is BTX, which is thus loaded into 0x9000, which is where +# it also runs from. The boot2.bin binary starts right after the end of +# BTX, so we have to figure out where the start of it is and then move the +# binary to 0xb000. Normally, BTX clients start at MEM_USR, or 0xa000, but +# when we use btxld create boot2, we use an entry point of 0x1000. That +# entry point is relative to MEM_USR; thus boot2.bin starts at 0xb000. +# +main.5: mov %dx,MEM_ARG # Save args + movb $0x2,%dh # Sector count + mov $0x7e00, %bx + callw nreadbx # Read disk + movb $0x40,%dh # Sector count + movb %dh, %al + callw puthex + mov $0x7e00, %bx + callw nreadbx # Read disk + push %si + mov $msg_r1,%si + callw putstr + pop %si + lcall $0x800,$0 # enter the rom code + int $0x19 + +msg_r1: .asciz " done\r\n" + +.if 0 + mov $MEM_BTX,%bx # BTX + mov 0xa(%bx),%si # Get BTX length and set + add %bx,%si # %si to start of boot2.bin + mov $MEM_USR+SIZ_PAG,%di # Client page 1 + mov $MEM_BTX+0xe*SIZ_SEC,%cx # Byte + sub %si,%cx # count + rep # Relocate + movsb # client + sub %di,%cx # Byte count + xorb %al,%al # Zero assumed bss from + rep # the end of boot2.bin + stosb # up to 0x10000 + callw seta20 # Enable A20 + jmp start+MEM_JMP-MEM_ORG # Start BTX +# +# Enable A20 so we can access memory above 1 meg. +# +seta20: cli # Disable interrupts +seta20.1: inb $0x64,%al # Get status + testb $0x2,%al # Busy? + jnz seta20.1 # Yes + movb $0xd1,%al # Command: Write + outb %al,$0x64 # output port +seta20.2: inb $0x64,%al # Get status + testb $0x2,%al # Busy? + jnz seta20.2 # Yes + movb $0xdf,%al # Enable + outb %al,$0x60 # A20 + sti # Enable interrupts + retw # To caller +.endif +# +# Trampoline used to call read from within boot1. +# +nread: mov $MEM_BUF,%bx # Transfer buffer +nreadbx: # same but address is in bx + mov 0x8(%si),%ax # Get + mov 0xa(%si),%cx # LBA + push %bx + push %ax + callw putword + pop %ax + pop %bx + push %cs # Read from + callw xread.1 # disk + jnc return # If success, return + mov $msg_read,%si # Otherwise, set the error + # message and fall through to + # the error routine +# +# Print out the error message pointed to by %ds:(%si) followed +# by a prompt, wait for a keypress, and then reboot the machine. +# +error: callw putstr # Display message + mov $prompt,%si # Display + callw putstr # prompt + xorb %ah,%ah # BIOS: Get + int $0x16 # keypress + movw $0x1234, BDA_BOOT # Do a warm boot + ljmp $0xffff,$0x0 # reboot the machine +# +# Display a null-terminated string using the BIOS output. +# +putstr.0: call putchar +putstr: lodsb # Get char + testb %al,%al # End of string? + jne putstr.0 # No + retw + +putword: push %ax + movb $'.', %al + callw putchar + movb %ah, %al + callw puthex + pop %ax +puthex: push %ax + shr $4, %al + callw putdigit + pop %ax +putdigit: + andb $0xf, %al + addb $0x30, %al + cmpb $0x39, %al + jbe putchar + addb $7, %al +putchar: push %ax + mov $0x7,%bx + movb $0xe,%ah + int $0x10 + pop %ax + retw + +# +# Overused return code. ereturn is used to return an error from the +# read function. Since we assume putstr succeeds, we (ab)use the +# same code when we return from putstr. +# +ereturn: movb $0x1,%ah # Invalid + stc # argument +return: retw # To caller +# +# Reads sectors from the disk. If EDD is enabled, then check if it is +# installed and use it if it is. If it is not installed or not enabled, then +# fall back to using CHS. Since we use a LBA, if we are using CHS, we have to +# fetch the drive parameters from the BIOS and divide it out ourselves. +# Call with: +# +# %dl - byte - drive number +# stack - 10 bytes - EDD Packet +# +read: push %dx # Save + movb $0x8,%ah # BIOS: Get drive + int $0x13 # parameters + movb %dh,%ch # Max head number + pop %dx # Restore + jc return # If error + andb $0x3f,%cl # Sectors per track + jz ereturn # If zero + cli # Disable interrupts + mov 0x8(%bp),%eax # Get LBA + push %dx # Save + movzbl %cl,%ebx # Divide by + xor %edx,%edx # sectors + div %ebx # per track + movb %ch,%bl # Max head number + movb %dl,%ch # Sector number + inc %bx # Divide by + xorb %dl,%dl # number + div %ebx # of heads + movb %dl,%bh # Head number + pop %dx # Restore + cmpl $0x3ff,%eax # Cylinder number supportable? + sti # Enable interrupts + ja read.7 # No, try EDD + xchgb %al,%ah # Set up cylinder + rorb $0x2,%al # number + orb %ch,%al # Merge + inc %ax # sector + xchg %ax,%cx # number + movb %bh,%dh # Head number + subb %ah,%al # Sectors this track + mov 0x2(%bp),%ah # Blocks to read + cmpb %ah,%al # To read + jb read.2 # this + movb %ah,%al # track +read.2: mov $0x5,%di # Try count +read.3: les 0x4(%bp),%bx # Transfer buffer + push %ax # Save + movb $0x2,%ah # BIOS: Read + int $0x13 # from disk + pop %bx # Restore + jnc read.4 # If success + dec %di # Retry? + jz read.6 # No + xorb %ah,%ah # BIOS: Reset + int $0x13 # disk system + xchg %bx,%ax # Block count + jmp read.3 # Continue +read.4: movzbw %bl,%ax # Sectors read + add %ax,0x8(%bp) # Adjust + jnc read.5 # LBA, + incw 0xa(%bp) # transfer +read.5: shlb %bl # buffer + add %bl,0x5(%bp) # pointer, + sub %al,0x2(%bp) # block count + ja read # If not done +read.6: retw # To caller +read.7: testb $FL_PACKET,%cs:MEM_REL+flags-start # LBA support enabled? + jz ereturn # No, so return an error + mov $0x55aa,%bx # Magic + push %dx # Save + movb $0x41,%ah # BIOS: Check + int $0x13 # extensions present + pop %dx # Restore + jc return # If error, return an error + cmp $0xaa55,%bx # Magic? + jne ereturn # No, so return an error + testb $0x1,%cl # Packet interface? + jz ereturn # No, so return an error + mov %bp,%si # Disk packet + movb $0x42,%ah # BIOS: Extended + int $0x13 # read + retw # To caller + +# Messages + +msg_read: .asciz "Rd" +msg_part: .asciz "Boot" + +prompt: .asciz " err\r\n" + +flags: .byte FLAGS # Flags + + .org PRT_OFF,0x90 + +# Partition table + + .fill 0x30,0x1,0x0 +part4: .byte 0x80 + .byte 0x00 # start head + .byte 0x01 # start sector (6 bits) + start cyl (2 bit) + .byte 0x00 # start cyl (low 8 bits) + .byte 0x1 # part.type + .byte 0xff # end head + .byte 0xff # end sect (6) + end_cyl(2) + .byte 0xff # end cyl + .byte 0x00, 0x00, 0x00, 0x00 # explicit start + .byte 0x50, 0xc3, 0x00, 0x00 # 50000 sectors long, bleh + + .word 0xaa55 # Magic number diff --git a/src/arch/i386/prefix/comprefix.S b/src/arch/i386/prefix/comprefix.S new file mode 100644 index 00000000..8be8db77 --- /dev/null +++ b/src/arch/i386/prefix/comprefix.S @@ -0,0 +1,49 @@ +/* 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 + .globl _prefix + +/* 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 + .globl prefix_exit +prefix_exit: + movw $0x4c00,%ax /* return to DOS */ + int $0x21 /* reach this on Quit */ + .globl prefix_exit_end +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/src/arch/i386/prefix/elf_dprefix.S b/src/arch/i386/prefix/elf_dprefix.S new file mode 100644 index 00000000..d2453be4 --- /dev/null +++ b/src/arch/i386/prefix/elf_dprefix.S @@ -0,0 +1,96 @@ +#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 + .globl prefix_exit +prefix_exit: + + .globl prefix_exit_end +prefix_exit_end: + .previous diff --git a/src/arch/i386/prefix/elfprefix.S b/src/arch/i386/prefix/elfprefix.S new file mode 100644 index 00000000..c5200678 --- /dev/null +++ b/src/arch/i386/prefix/elfprefix.S @@ -0,0 +1,96 @@ +#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 + .globl prefix_exit +prefix_exit: + + .globl prefix_exit_end +prefix_exit_end: + .previous diff --git a/src/arch/i386/prefix/exeprefix.S b/src/arch/i386/prefix/exeprefix.S new file mode 100755 index 00000000..d169563e --- /dev/null +++ b/src/arch/i386/prefix/exeprefix.S @@ -0,0 +1,44 @@ +/* + 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 + .globl _prefix + +_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 + .globl prefix_exit +prefix_exit: + movw $0x4c00,%ax /* return to DOS */ + int $0x21 /* reach this on Quit */ + .globl prefix_exit_end +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/src/arch/i386/prefix/floppyprefix.S b/src/arch/i386/prefix/floppyprefix.S new file mode 100644 index 00000000..18bed4c8 --- /dev/null +++ b/src/arch/i386/prefix/floppyprefix.S @@ -0,0 +1,359 @@ +/* NOTE: this boot sector contains instructions that need at least an 80186. + * Yes, as86 has a bug somewhere in the valid instruction set checks. + * + * SYS_SIZE is the number of clicks (16 bytes) to be loaded. + */ +.equ SYSSIZE, 8192 # 8192 * 16 bytes = 128kB maximum size of .ROM file + +/* 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 */ + + pushw %es /* = ds */ + 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 */ + pop %es /* balance push/pop es */ +sigok: + +/* 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) + +/* after that (everything loaded), we call to the .ROM file loaded. */ + + pushl $0 /* No parameters to preserve for exit path */ + pushw $0 /* Use prefix exit path mechanism */ + ljmp $SYSSEG, $_start + + .section ".text16", "ax", @progbits + .globl prefix_exit +prefix_exit: + xchgw %bx, %bx + int $0x19 /* should try to boot machine */ + .globl prefix_exit_end +prefix_exit_end: + .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 /* read whole image incl boot sector */ + 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 $SYSSEG+SYSSIZE, %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 + diff --git a/src/arch/i386/prefix/huf.lds b/src/arch/i386/prefix/huf.lds new file mode 100644 index 00000000..1e8da162 --- /dev/null +++ b/src/arch/i386/prefix/huf.lds @@ -0,0 +1,6 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS { + .huf : { *(*) } +} diff --git a/src/arch/i386/prefix/img.lds b/src/arch/i386/prefix/img.lds new file mode 100644 index 00000000..e383d89e --- /dev/null +++ b/src/arch/i386/prefix/img.lds @@ -0,0 +1,6 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS { + .img : { *(*) } +} diff --git a/src/arch/i386/prefix/liloprefix.S b/src/arch/i386/prefix/liloprefix.S new file mode 100644 index 00000000..1aeb071f --- /dev/null +++ b/src/arch/i386/prefix/liloprefix.S @@ -0,0 +1,144 @@ +/* + 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 + .globl _prefix +_prefix: + +/* + 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, $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: + pushl $0 /* No parameters to preserve for exit path */ + pushw $0 /* Use prefix exit path mechanism */ + /* 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)), $_start + + .section ".text16", "ax", @progbits + .globl prefix_exit +prefix_exit: + int $0x19 /* should try to boot machine */ + .globl prefix_exit_end +prefix_exit_end: + .previous + + .org (PREFIXSIZE-1) + .byte 0 +prefix_end: +/* + That's about it. +*/ + diff --git a/src/arch/i386/prefix/lmelf_dprefix.S b/src/arch/i386/prefix/lmelf_dprefix.S new file mode 100644 index 00000000..65fe1356 --- /dev/null +++ b/src/arch/i386/prefix/lmelf_dprefix.S @@ -0,0 +1,163 @@ +#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 + .globl prefix_exit +prefix_exit: + + .globl prefix_exit_end +prefix_exit_end: + .previous diff --git a/src/arch/i386/prefix/lmelf_prefix.S b/src/arch/i386/prefix/lmelf_prefix.S new file mode 100644 index 00000000..7488f452 --- /dev/null +++ b/src/arch/i386/prefix/lmelf_prefix.S @@ -0,0 +1,163 @@ +#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 + .globl prefix_exit +prefix_exit: + + .globl prefix_exit_end +prefix_exit_end: + .previous diff --git a/src/arch/i386/prefix/nullprefix.S b/src/arch/i386/prefix/nullprefix.S new file mode 100644 index 00000000..dbee141b --- /dev/null +++ b/src/arch/i386/prefix/nullprefix.S @@ -0,0 +1,16 @@ + .org 0 + .text + .arch i386 + + .section ".prefix", "ax", @progbits + .code16 + .globl _prefix +_prefix: + + .section ".text16", "ax", @progbits + .globl prefix_exit +prefix_exit: + + .globl prefix_exit_end +prefix_exit_end: + .previous diff --git a/src/arch/i386/prefix/pxeprefix.S b/src/arch/i386/prefix/pxeprefix.S new file mode 100644 index 00000000..246e22b2 --- /dev/null +++ b/src/arch/i386/prefix/pxeprefix.S @@ -0,0 +1,398 @@ +/* Offsets of words containing ROM's CS and size (in 512 byte blocks) + * from start of floppy boot block at 0x7c00 + * Offsets must match those in etherboot.h + */ +#define FLOPPY_SEGMENT 0x7c0 + +#define PXENV_UNDI_CLEANUP 0x02 +#define PXENV_UNDI_SHUTDOWN 0x05 +#define PXENV_STOP_UNDI 0x15 +#define PXENV_UNLOAD_STACK 0x70 +#define PXENV_STOP_BASE 0x76 + +#define PUSHA_SIZE 16 +#define PXE_STACK_MAGIC 0x57ac /* 'STac' */ + + .text + .code16 + .arch i386 + .org 0 + .section ".prefix", "ax", @progbits + .globl _prefix +/***************************************************************************** + * Entry point: set cs, ds, bp, print welcome message + ***************************************************************************** + */ +_prefix: + jmp $FLOPPY_SEGMENT, $code_start-_prefix +10: .asciz "PXE->EB " +code_start: + pusha /* Preserve all registers */ + push %ds + movw %sp, %bp /* %bp must be preserved, hence do + * this after the pusha */ + push $PXE_STACK_MAGIC /* PXE stack magic marker */ + + push %cs /* Set up data segment */ + pop %ds + mov $0x40, %cx /* Set up %fs for access to 40:13 */ + mov %cx, %fs + movw $10b-_prefix, %si /* Print welcome message */ + call print_message + +/***************************************************************************** + * Detect type of PXE available (!PXE, PXENV+ or none) + ***************************************************************************** + */ +detect_pxe: + les 4+PUSHA_SIZE+2(%bp), %di /* !PXE structure */ + cmpl $0x45585021, %es:(%di) /* '!PXE' signature */ + je detected_pxe + mov $0x5650, %ax + int $0x1a + cmp $0x564e, %ax + jne detected_nothing + cmpl $0x4e455850, %es:(%bx) /* 'PXEN' signature */ + jne detected_nothing + cmpw $0x2b56, %es:4(%bx) /* 'V+' signature */ + je detected_pxenv + +detected_nothing: + movw $10f-_prefix, %si + call print_message + jmp finished_with_error +10: .asciz "No PXE " + +detected_pxenv: /* es:bx points to PXENV+ structure */ + push %es + push %bx + push %es:0x24(%bx) /* UNDI code segment */ + push %es:0x26(%bx) /* UNDI code size */ + push %es:0x20(%bx) /* UNDI data segment */ + push %es:0x22(%bx) /* UNDI data size */ + les %es:0x0a(%bx), %di /* Entry point to %es:%di */ + movw $10f-_prefix, %si + jmp pxe_setup_done +10: .asciz "PXENV+ " + +detected_pxe: /* es:di points to !PXE structure */ + push %es + push %di + push %es:0x30(%di) /* UNDI code segment */ + push %es:0x36(%di) /* UNDI code size */ + push %es:0x28(%di) /* UNDI data segment */ + push %es:0x2e(%di) /* UNDI data size */ + les %es:0x10(%di), %di /* Entry point to %es:%di */ + movw $10f-_prefix, %si + jmp pxe_setup_done +10: .asciz "!PXE " + +pxe_setup_done: + mov %es, pxe_entry_segment - _prefix + mov %di, pxe_entry_offset - _prefix + pop %ax + mov %ax, undi_data_size - _prefix + pop %ax + mov %ax, undi_data_segment - _prefix + pop %ax + mov %ax, undi_code_size - _prefix + pop %ax + mov %ax, undi_code_segment - _prefix + call print_message + pop %di + pop %es /* Exit with %es:%di containing structure address */ + +/***************************************************************************** + * Print information about located structure + ***************************************************************************** + */ +print_structure_information: + call print_segoff /* %es:%di contains address of structure */ + les %ds:(pxe_entry_segoff - _prefix), %di + call print_segoff + les %ds:(undi_code_segoff - _prefix), %di + call print_segoff + les %ds:(undi_data_segoff - _prefix), %di + call print_segoff + +/***************************************************************************** + * Unload PXE base code and UNDI driver + ***************************************************************************** + */ +#ifdef PXELOADER_KEEP_ALL + xor %ax, %ax /* Force zero flag to show success */ + jmp do_not_free_base_mem /* Skip the unloading */ +#endif /* PXELOADER_KEEP_ALL */ + +unload_pxe: + mov $PXENV_UNLOAD_STACK, %bx + call pxe_call + mov $PXENV_STOP_UNDI, %bx + call pxe_call + pushfw /* Ignore PXENV_UNDI_CLEANUP errors */ + mov $PXENV_UNDI_CLEANUP, %bx + call pxe_call + popfw + /* On exit, zero flag is set iff all calls were successful */ + +/***************************************************************************** + * Free base memory + ***************************************************************************** + */ +free_base_mem: + jnz do_not_free_base_mem /* Using zero flag from unload_pxe */ + + mov undi_code_segment - _prefix, %bx + mov undi_data_segment - _prefix, %cx + mov undi_code_size - _prefix, %ax + cmp %bx, %cx + jb 1f + mov %cx, %bx + mov undi_data_size - _prefix, %ax +1: add $0x0f, %ax /* Round up to next segment */ + shr $4, %ax + add %bx, %ax /* Highest segment address into %ax */ + add $(1024 / 16 - 1), %ax /* Round up to next kb */ + shr $6, %ax /* New free basemem size in %ax */ + mov %fs:(0x13), %bx /* Old free base memory in %bx */ + mov %ax, %fs:(0x13) /* Store new free base memory size */ + + /* Note that zero_mem_loop will also zero out our stack, so make + * sure the stack is empty at this point. + */ + mov %ax, %dx + sub %bx, %dx /* numberof kb to zero in %dx */ + shl $6, %bx /* Segment address into %bx */ +zero_mem_loop: + mov %bx, %es /* kB boundary into %es:00 */ + xor %ax, %ax + xor %di, %di + mov $0x400, %cx + rep stosb /* fill kB with zeroes */ + add $(1024 / 16), %bx + dec %dx + jnz zero_mem_loop + /* Will exit here with zero flag set, so no need to set it explicitly + * in order to indicate success. + */ + +do_not_free_base_mem: + pushf /* Save success (zero) flag status */ + mov %fs:(0x13), %ax /* Free base memory in %ax */ + call print_hex_word /* Print free base memory */ + popf /* Restore success (zero) flag */ + +/***************************************************************************** + * Exit point + * Jump to finished with the zero flag set to indicate success, or to + * finished_with_error to always report an error + ***************************************************************************** + */ +finished: + movw $10f-_prefix, %si + jz 1f +finished_with_error: + movw $20f-_prefix, %si +1: + call print_message + jmp 99f +10: .asciz " ok\n" +20: .asciz " err\n" + + + /* We place a stack here. It doesn't get used until after all + * the above code is finished, so we can happily write all + * over it. Putting the stack here ensures that it doesn't + * accidentally go over the 512 byte threshold, which would + * cause problems when returning via start32's prefix + * relocation mechanism. + */ +_estack: +99: + +/***************************************************************************** + * Run Etherboot main code + ***************************************************************************** + */ +run_etherboot: + /* Very temporarily switch stacks to one internal to the + * prefix. Do this because the stack provided by the PXE ROM + * could be absolutely anywhere, including in an area of + * memory that the call to prelocate will vapourise... + */ + pushw %ss /* PXE stack pointer to ES:DI */ + popw %es + movw %sp, %di + pushw %ds /* Set up stack in "safe" area */ + popw %ss + movw $_estack-_prefix, %sp + pushw %es /* Record PXE stack pointer */ + pushw %di + /* Relocate payload and stack to claimed base memory */ + pushw $4 /* Preserve old PXE stack pointer */ + call prelocate + popw %ax /* Remove parameter */ + pushl $4 /* Preserve old PXE stack pointer */ + pushw $0 /* Indicate prefix exit mechanism */ + jmp _start /* Run Etherboot */ + + .section ".text16", "ax", @progbits + .globl prefix_exit +prefix_exit: + pushw %cs /* Set %ds, %bp for access to text */ + popw %ds + call 1f +1: popw %bp + popw %di /* Old PXE stack to %es:di */ + popw %es + cmpw $PXE_STACK_MAGIC, %es:0(%di) /* See if PXE stack intact */ + jne exit_via_int18 +exit_via_pxe: /* Stack OK, return to PXE */ + push %es /* Restore PXE stack pointer */ + pop %ss + mov %di, %sp + pop %ax /* Discard PXE_STACK_MAGIC marker */ + leaw (10f-1b)(%bp), %si + call print_exit_message + pop %ds /* Restore PXE's DS */ + popa /* Restore PXE's other registers */ + movw $0, %ax /* Return PXENV_STATUS_SUCCESS */ + lret /* Return control to PXE ROM */ +10: .asciz "EB->PXE\r\n" +exit_via_int18: /* Stack damaged, do int 18 */ + leaw (10f-1b)(%bp), %si + call print_exit_message + int $0x18 +10: .asciz "EB->BIOS\r\n" +print_exit_message: + movw $0x0007, %bx /* page 0, attribute 7 (normal) */ + movb $0x0e, %ah /* write char, tty mode */ +1: lodsb + testb %al, %al + je 2f + int $0x10 + jmp 1b +2: ret + .globl prefix_exit_end +prefix_exit_end: + .previous + +/***************************************************************************** + * Subroutine: print character in %al (with LF -> LF,CR translation) + ***************************************************************************** + */ +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 + ret + +/***************************************************************************** + * Subroutine: print a zero-terminated message starting at %si + ***************************************************************************** + */ +print_message: +1: lodsb + testb %al, %al + je 2f + call print_character + jmp 1b +2: ret + +/***************************************************************************** + * Subroutine: print hex word in %ax + ***************************************************************************** + */ +print_hex_word: + mov $4, %cx +1: + push %ax + shr $12, %ax + /* Courtesy of Norbert Juffa <norbert.juffa@amd.com> */ + cmp $10, %al + sbb $0x69, %al + das + call print_character + pop %ax + shl $4, %ax + loop 1b + ret + +/***************************************************************************** + * Subroutine: print segment:offset address in %es:%di + ***************************************************************************** + */ +print_segoff: + push %di + push %es + pop %ax + call print_hex_word + movb $0x3a,%al /* ':' */ + call print_character + pop %ax + call print_hex_word + mov $0x20, %al /* ' ' */ + call print_character + ret + +/***************************************************************************** + * Make a PXE API call. Works with either !PXE or PXENV+ API. + * Opcode in %bx. pxe_parameter_structure always used. + * Returns status code (not exit code) in %bx and prints it. + * ORs status code with overall status code in pxe_overall_status, returns + * with zero flag set iff all PXE API calls have been successful. + ***************************************************************************** + */ +pxe_call: + /* Set up registers for PXENV+ API. %bx already set up */ + push %ds + pop %es + mov $pxe_parameter_structure - _prefix, %di + /* Set up stack for !PXE API */ + pushw %cs + pushw %di + pushw %bx + /* Make the API call */ + lcall *(pxe_entry_segoff - _prefix) + /* Reset the stack */ + add $6, %sp + mov pxe_parameter_structure - _prefix, %ax + push %ax + call print_hex_word + mov $0x20, %ax /* ' ' */ + call print_character + pop %bx + or %bx, pxe_overall_status - _prefix + ret + +/***************************************************************************** + * PXE data structures + ***************************************************************************** + */ + +pxe_overall_status: .word 0 + +pxe_entry_segoff: +pxe_entry_offset: .word 0 +pxe_entry_segment: .word 0 + +undi_code_segoff: +undi_code_size: .word 0 +undi_code_segment: .word 0 + +undi_data_segoff: +undi_data_size: .word 0 +undi_data_segment: .word 0 + +pxe_parameter_structure: + .word 0 + .word 0,0,0,0,0 + +end_of_pxeloader: + + .balign 16, 0 +payload: diff --git a/src/arch/i386/prefix/romprefix.S b/src/arch/i386/prefix/romprefix.S new file mode 100644 index 00000000..c6c83489 --- /dev/null +++ b/src/arch/i386/prefix/romprefix.S @@ -0,0 +1,416 @@ +/* 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 DELAYED_INT when NO_DELAYED_INT is not defined. + * This allows positive tests instead of tests that contain + * double negatives, and become confusing. + */ +#ifndef NO_DELAYED_INT +#define DELAYED_INT +#endif + +/* We need some unique magic ID, if we defer startup thru the INT18H or INT19H + * handler. This way, we can check if we have already been installed. + */ +#ifndef MAGIC +#define MAGIC 0xE44C +#endif + +/* Hook into INT18H or INT19H handler */ +#ifdef BOOT_INT18H +#define BOOT_INT 0x18 +#else +#define BOOT_INT 0x19 +#endif + +#define BOOT_INT_VEC BOOT_INT*4 +#define SCRATCHVEC 0x300 + +/* Prefix exit codes. We store these on the stack so that we will + * know how to return control to the BIOS when Etherboot exits. + */ +#define EXIT_VIA_LRET 0x0 +#define EXIT_VIA_INT_18 0x1 +#define EXIT_VIA_BOOT_INT 0x2 + + .text + .code16 + .arch i386 + .org 0 + .section ".prefix", "ax", @progbits + .globl _prefix +_prefix: + .word 0xAA55 /* BIOS extension signature */ +size: .byte 0 /* number of 512 byte blocks */ + /* = number of 256 word blocks */ + /* filled in by makerom program */ + jmp over /* skip over checksum */ + .byte 0 /* checksum */ + jmp legacyentry /* alternate entry point +6 */ + /* used by mknbi-rom */ + +#ifdef PCI_PNP_HEADER +mfgstr: + .asciz "Etherboot" + +#ifdef PXE_EXPORT + .org 0x16 + .word UNDIROMID - _prefix +#endif /* PXE_EXPORT */ + + .org 0x18 + .word PCI - _prefix + .word PnP - _prefix + +PCI: + .ascii "PCIR" + .word 0x0000 /* vendor ID, filled in by makerom */ + .word 0x0000 /* device ID, filled in by makerom */ + .word 0x0000 /* pointer to vital product data */ + .word 0x0018 /* 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 */ + .word 0x0000 /* Image length same as offset 02h */ + .word 0x0001 /* revision level of code/data */ + .byte 0x00 /* code type */ + .byte 0x80 /* indicator (last PCI data structure) */ + .word 0x0000 /* reserved */ + +PnP: + .ascii "$PnP" + .byte 0x01 /* structure revision */ + .byte 0x02 /* length (in 16 byte increments) */ + .word 0x0000 /* offset of next header */ + .byte 0x00 /* Reserved */ + .byte 0x00 /* checksum filled by makerom */ + .long 0x00000000 /* Device identifier */ + .word mfgstr - _prefix + .word 0x0 /* pointer to product name */ + /* filled by makerom */ + .byte 0x02 /* Device Base Type code */ + .byte 0x00 /* Device Sub-Type code */ + .byte 0x00 /* Device Interface Type code */ + .byte 0x14 /* device indicator */ + .word 0x0000 /* boot connection vector */ + .word 0x0000 /* disconnect vector */ + .word pnpentry - _prefix + .word 0x0000 /* reserved */ + .word 0x0000 /* static resource information vector */ +#ifdef PXE_EXPORT +UNDIROMID: + .ascii "UNDI" + .byte UNDIROMID_end - UNDIROMID /* length of structure */ + .byte 0 /* Checksum */ + .byte 0 /* Structure revision */ + .byte 0,1,2 /* PXE version 2.1.0 */ + .word UNDILoader - _prefix /* Offset to loader routine */ + .word UNDIStackSize /* Stack segment size */ + .word UNDIDataSize /* Data segment size */ + .word UNDICodeSize /* Code segment size */ + .ascii "PCIR" + + /* The code segment contains our pxe_stack_t plus the PXE and + * RM callback interfaces. We don't actually use a data + * segment, but we put a nonzero value here to avoid confusing + * things. 16k of stack space should be enough. + * + * When we claim our own memory, we fill out the data segment + * with the address and size of the real-mode stack, so that + * NBPs will free that area of memory for us. When the UNDI + * loader is used to initialise us, we will never need a + * real-mode stack because we will only ever be called via the + * PXE API, hence our stack is already in base memory. + */ + .equ UNDICodeSize, _pxe_stack_size + .equ UNDIDataSize, _real_mode_stack_size + .equ UNDIStackSize, _real_mode_stack_size +UNDIROMID_end: +#endif /* PXE_EXPORT */ + +#endif /* PCI_PNP_HEADER */ + +/* + * Explicitly specify DI is wrt ES to avoid problems with some BIOSes + * Discovered by Eric Biederman + * In addition, some BIOSes don't point DI to the string $PnP so + * we need another #define to take care of that. + */ +over: +#ifdef DEBUG_ROMPREFIX + call print_bcv +#endif +/* Omit this test for ISA cards anyway */ +#ifdef PCI_PNP_HEADER +/* Accept old name too for backward compatibility */ +#if !defined(BBS_BUT_NOT_PNP_COMPLIANT) && !defined(PNP_BUT_NOT_BBS_COMPLIANT) + cmpw $'$'+'P'*256,%es:0(%di) + jne notpnp + cmpw $'n'+'P'*256,%es:2(%di) + jne notpnp +#endif /* BBS_BUT_NOT_PNP_COMPLIANT */ + movw $0x20,%ax + lret +#endif /* PCI_PNP_HEADER */ +notpnp: +#ifdef DEBUG_ROMPREFIX + call print_notpnp +#endif +#ifdef DELAYED_INT + pushw %ax + pushw %ds + xorw %ax,%ax + movw %ax,%ds /* access first 64kB segment */ + movw SCRATCHVEC+4, %ax /* check if already installed */ + cmpw $MAGIC, %ax /* check magic word */ + jz installed + movw BOOT_INT_VEC, %ax /* hook into INT18H or INT19H */ + movw %ax, SCRATCHVEC + movw BOOT_INT_VEC+2, %ax + movw %ax, SCRATCHVEC+2 + movw $start_int - _prefix, %ax + movw %ax, BOOT_INT_VEC + movw %cs,%ax + movw %ax, BOOT_INT_VEC+2 + movw $MAGIC, %ax /* set magic word */ + movw %ax, SCRATCHVEC+4 +#ifdef DEBUG_ROMPREFIX + call print_installed +#endif +installed: + popw %ds + popw %ax + movw $0x20,%ax + lret + +start_int: /* clobber magic id, so that we will */ +#ifdef DEBUG_ROMPREFIX + call print_start_int +#endif + xorw %ax,%ax /* not inadvertendly end up in an */ + movw %ax,%ds /* endless loop */ + movw %ax, SCRATCHVEC+4 + movw SCRATCHVEC+2, %ax /* restore original INT19h handler */ + movw %ax, BOOT_INT_VEC+2 + movw SCRATCHVEC, %ax + movw %ax, BOOT_INT_VEC + pushl %eax /* padding */ + pushw $EXIT_VIA_BOOT_INT + jmp invoke +#endif /* DELAYED_INT */ + + + + +legacyentry: +#ifdef DEBUG_ROMPREFIX + call print_legacyentry +#endif + pushw $EXIT_VIA_LRET + jmp invoke + + + +#ifdef PCI_PNP_HEADER +pnpentry: +#ifdef DEBUG_ROMPREFIX + call print_bev +#endif + pushl %eax /* padding */ + pushw $EXIT_VIA_INT_18 + jmp invoke +#endif /* PCI_PNP_HEADER */ + + +invoke: + /* Store ROM segment and size on stack */ + pushw %ax + pushw %ds + pushw %cs + movzbw %cs:(size-_prefix), %ax + shlw $9, %ax /* 512-byte blocks */ + pushw %ax + /* Relocate to free base memory, switch stacks */ + pushw $12 /* Preserve exit code & far ret addr */ + call prelocate + /* We are now running in RAM */ + popw %ax /* padding */ + movw %cs, %ax + movw %ax, %ds + popw %ds:(_prefix_rom+2) /* ROM size */ + popw %ds:(_prefix_rom+0) /* ROM segment */ + popw %ds /* Original %ds */ + popw %ax /* Original %ax */ + pushw %ax /* 4-byte alignment */ + pushl $8 /* Preserve exit code & far ret addr */ + pushw $0 /* Set null return address */ + jmp _start + + + .section ".text16", "ax", @progbits + .globl prefix_exit +prefix_exit: + popw %ax /* padding */ + popw %ax /* %ax = exit code */ + cmpw $EXIT_VIA_LRET, %ax + jne 1f + /* Exit via LRET */ + lret +1: addw $4, %sp /* Strip padding */ + cmpw $EXIT_VIA_BOOT_INT, %ax + jne 2f + /* Exit via int BOOT_INT */ + int $BOOT_INT /* Try original vector */ +2: /* Exit via int $0x18 */ + int $0x18 /* As per BIOS Boot Spec, next dev */ + .globl prefix_exit_end +prefix_exit_end: + .previous + + + +#ifdef PXE_EXPORT + +#include "callbacks.h" +#define PXENV_UNDI_LOADER 0x104d + + .section ".prefix" +UNDILoader: + /* Loader API is different to the usual PXE API; there is no + * opcode on the stack. We arrange the stack to look like a + * normal PXE API call; this makes the Etherboot internals + * cleaner and avoids adding an extra API type just for the + * PXE loader. + */ + pushw %bx + movw %sp, %ax /* Store original %ss:sp */ + pushw %ss + pushw %ax + pushl %eax /* Space for loader structure ptr */ + pushw %bp + movw %sp, %bp + movw 16(%bp), %ax /* Copy loader structure ptr */ + movw %ax, 2(%bp) + movw 18(%bp), %ax + movw %ax, 4(%bp) + popw %bp + pushw $PXENV_UNDI_LOADER /* PXE 'opcode' */ + pushl %eax /* dummy return address */ + /* Stack now looks like a normal PXE API call */ + /* Store ROM segment and size on stack */ + pushw %ax + pushw %cs + movzbw %cs:(size-_prefix), %ax + shlw $9, %ax /* 512-byte blocks */ + pushw %ax + /* Unpack Etherboot into temporarily claimed base memory */ + pushw $20 /* Dummy ret, PXE params, orig ss:sp */ + call prelocate + popw %ax /* discard */ + popw %cs:(_prefix_rom+2) /* ROM size */ + popw %cs:(_prefix_rom+0) /* ROM segment */ + popw %ax /* Original %ax */ + /* Inhibit automatic deallocation of base memory */ + movl $0, %cs:_prefix_image_basemem + /* Make PXE API call to Etherboot */ + pushl $0x201 /* PXE API version */ + /* Need to USE_INTERNAL_STACK, since we will call relocate() */ + pushl $(EB_OPCODE_PXE|EB_USE_INTERNAL_STACK) /* PXE API call type */ + call _entry + addw $18, %sp /* discard */ + popw %bx /* Restore original %ss:sp */ + popw %ss + movw %bx, %sp + popw %bx + call deprelocate + lret $2 /* Skip our PXE 'opcode' */ +#endif /* PXE_EXPORT */ + +#ifdef DEBUG_ROMPREFIX + .section ".prefix" + +print_bcv: + pushw %si + movw $1f-_prefix, %si + call print_message + popw %si + ret +1: .asciz "ROM detected\r\n" + +print_bev: + pushw %si + movw $1f-_prefix, %si + call print_message + popw %si + ret +1: .asciz "booting\r\n" + +print_notpnp: + pushw %si + movw $1f-_prefix, %si + call print_message + popw %si + ret +1: .asciz ": Non-PnP BIOS detected!\r\n" + +print_legacyentry: + pushw %si + movw $1f-_prefix, %si + call print_message + popw %si + ret +1: .asciz "ROM using legacy boot mechanism\r\n" + +print_installed: + pushw %si + movw $1f-_prefix, %si + call print_message + popw %si + ret +1: .ascii "hooked boot via INT" +#ifdef BOOT_INT18H + .asciz "18\r\n" +#else + .asciz "19\r\n" +#endif + +print_start_int: + pushw %si + movw $1f-_prefix, %si + call print_message + popw %si + ret +1: .asciz "booting via hooked interrupt\r\n" + +print_message: + pushaw + pushw %ds + pushw %cs + popw %ds + pushw %si + movw $1f-_prefix, %si + call print_string + popw %si + call print_string + popw %ds + popaw + ret +1: .asciz "Etherboot " + +print_string: +1: lodsb + testb %al,%al + je 2f + movw $0x0007, %bx /* page 0, attribute 7 (normal) */ + movb $0x0e, %ah /* write char, tty mode */ + int $0x10 + jmp 1b +2: ret + +#endif diff --git a/src/arch/i386/prefix/unhuf.S b/src/arch/i386/prefix/unhuf.S new file mode 100644 index 00000000..390556e3 --- /dev/null +++ b/src/arch/i386/prefix/unhuf.S @@ -0,0 +1,400 @@ +/***************************************************************************** + * NOTE: This code is no longer used in Etherboot. The obsolete + * Makefile target .lzrom refers to it, but it is no longer being + * maintained and may no longer work. Use .zrom instead (which uses + * the unnrv2b decompressor). + ***************************************************************************** + */ + +/* 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. + */ + + +/* LZHuf (LZSS) Decompressing boot loader for ROM images + * + * this code is based on the work of Haruyasu Yoshizaki and Haruhiko Okumura + * who implemented the original compressor and decompressor in C code + * + * Converted to 32bit assembly 16 July 2002 Eric Biederman <ebiederman@lnxi.com> + * Made PIC 10 Aug 2002 Eric Biederman <ebiederman@lnxi.com> + * + * Copyright 1997 by M. Gutschke <gutschk@math.uni-muenster.de> + * + * Compression pays off, as soon as the uncompressed image is bigger than + * about 1.5kB. This assumes an average compressibility of about 60%. + */ + + +/* Do not change these values unless you really know what you are doing + * the pre-computed lookup tables rely on the buffer size being 4kB or + * smaller. The buffer size must be a power of two. The lookahead size has + * to fit into 6 bits. If you change any of these numbers, you will also + * have to adjust the compressor accordingly. + */ +#define BUFSZ 4096 +#define LOOKAHEAD 60 +#define THRESHOLD 2 +#define NCHAR (256+LOOKAHEAD-THRESHOLD) +#define TABLESZ (NCHAR+NCHAR-1) +#define ROOT (TABLESZ-1) + + .text + .arch i386 + .globl _start +_start: + cli + + /* Save the initial register values */ + pushal + + /* + * See where I am running, and compute %ebp + */ + call 1f +1: pop %ebp + subl $1b, %ebp + +/* + * INIT -- initializes all data structures + * ==== + */ + +init: + cld + leal dcodrle(%ebp), %esi /* uncompress run length encoded */ + leal dcode(%ebp), %edi /* lookup table for codes */ + movb $6, %dl + movb $0x20, %dh + xorb %bh,%bh +init0: + lodsb + movb %al,%bl +init1: + xorl %ecx, %ecx + movb %dh,%cl + movb %bh,%al + rep + stosb + incb %bh + decb %bl + jnz init1 + shrb %dh + decb %dl + jnz init0 + movb $1, %bl /* uncompress run length encoded */ + movb $6, %bh /* lookup table for code lengths */ +init2: + lodsb + xorl %ecx, %ecx + movb %al,%cl + movb %bl,%al + rep + stosb + incb %bl + decb %bh + jnz init2 + + movl $NCHAR, %ecx /* set all frequencies of leaf nodes */ + movw $1, %ax /* to one */ + rep + stosw + leal freq(%ebp), %esi + movl $ROOT+1-NCHAR, %ecx +init3: + lodsw /* update frequencies of non-leaf nodes */ + movw %ax,%bx + lodsw + addw %bx,%ax + stosw + loop init3 + movw $0xFFFF, %ax + stosw /* sentinel with infinite frequency */ + movl $NCHAR, %ecx + movw $TABLESZ, %ax +init4: + stosw /* update son pointers for leaf nodes */ + incw %ax + loop init4 + movl $ROOT+1-NCHAR, %ecx + xorw %ax,%ax +init5: + stosw /* update son ptrs for non-leaf nodes */ + addw $2, %ax + loop init5 + movl $ROOT+1-NCHAR, %ecx + movw $NCHAR, %ax +init6: + stosw /* update parent ptrs for non-leaf nd. */ + stosw + incw %ax + loop init6 + movl $NCHAR, %ecx + xorw %ax,%ax + stosw /* root node has no parent */ +init7: + stosw /* update parent ptrs for leaf nodes */ + incw %ax + loop init7 + xorw %ax,%ax + stosb /* clear getlen */ + stosw /* clear getbuf */ + movb $0x20, %al /* fill text buffer with spaces */ + leal spaces(%ebp), %edi + movl $BUFSZ-LOOKAHEAD, %ecx + rep + + stosb + /* fall thru */ + +/* + * MAIN -- reads compressed codes and writes decompressed data + * ==== + */ + + leal _payload(%ebp), %esi /* get length of compressed data stream */ + leal uncompressed(%ebp), %edi + + lodsl + movl %eax, %ecx +main1: + pushl %ecx + call dcdchr /* decode one code symbol */ + orb %ah,%ah /* test if 8bit character */ + jnz main2 + stosb /* store verbatim */ + popl %ecx + loop main1 /* proceed with next compressed code */ + jmp done /* until end of input is detected */ +main2: + pushl %eax + call dcdpos /* compute position in output buffer */ + movl %esi, %eax + subl %edi, %ebx + notl %ebx + movl %ebx, %esi /* si := di - dcdpos() - 1 */ + popl %ecx + subl $255-THRESHOLD, %ecx /* compute length of code sequence */ + movl %ecx, %edx + rep + movsb + movl %eax,%esi + popl %ecx + subl %edx, %ecx /* check end of input condition */ + jnz main1 /* proceed with next compressed code */ +done: + /* Start Etherboot */ + popal + jmp uncompressed +/* + * GETBIT -- gets one bit pointed to by DS:ESI + * ====== + * + * changes: AX,CX,DL + */ + +getbit: + movb $8, %cl + movb getlen(%ebp), %dl /* compute number of bits required */ + subb %dl,%cl /* to fill read buffer */ + jae getbit1 + movw getbuf(%ebp), %ax /* there is still enough read ahead data */ + jmp getbit2 +getbit1: + lodsb /* get next byte from input stream */ + xorb %ah,%ah + shlw %cl,%ax /* shift, so that it will fit into */ + movw getbuf(%ebp), %cx /* read ahead buffer */ + orw %cx,%ax + addb $8, %dl /* update number of bits in buffer */ +getbit2: + movw %ax,%cx + shlw %cx /* extract one bit from buffer */ + movw %cx, getbuf(%ebp) + decb %dl + movb %dl, getlen(%ebp) /* and update number of bits */ + shlw %ax /* return in carry flag */ + ret + + +/* + * DCDPOS -- decodes position in textbuffer as pointed to by DS:SI, result in BX + * ====== + * + * changes: AX,EBX,ECX,DX + */ + +dcdpos: + movl $0x0800, %ebx +dcdpos1: + shlb %bl /* read one byte */ + call getbit + jnc dcdpos2 + incb %bl +dcdpos2: + decb %bh + jnz dcdpos1 + movb %bl,%dh /* read length of code from table */ + xorb %bh,%bh + xorl %ecx, %ecx + movb dlen(%ebx, %ebp),%cl + movb dcode(%ebx, %ebp),%bl /* get top six bits from table */ + shll $6, %ebx +dcdpos3: + pushl %ecx /* read the rest from the input stream */ + shlb %dh + call getbit + jnc dcdpos4 + incb %dh +dcdpos4: + popl %ecx + loop dcdpos3 + andb $0x3f, %dh /* combine upper and lower half of code */ + orb %dh,%bl + ret + +/* + * DCDCHR -- decodes one compressed character pointed to by DS:SI + * ====== + * + * changes: AX,BX,CX,DX + */ + +dcdchr: + movl $ROOT, %ebx /* start at root entry */ + shll %ebx + movzwl son(%ebx, %ebp),%ebx +dcdchr1: + call getbit /* get a single bit */ + jnc dcdchr2 + incl %ebx /* travel left or right */ +dcdchr2: + shll %ebx + movzwl son(%ebx, %ebp), %ebx + cmpl $TABLESZ, %ebx /* until we come to a leaf node */ + jb dcdchr1 + movl %ebx, %eax + subl $TABLESZ, %eax + /* fall thru */ + +/* + * UPDATE -- updates huffman tree after incrementing frequency for code in BX + * ====== + * + * changes: BX,CX,DX + */ + +update: + /* we do not check whether the frequency count has overrun. + * this will cause problems for large files, but be should be fine + * as long as the compressed size does not exceed 32kB and we + * cannot do more than this anyways, because we load into the + * upper 32kB of conventional memory + */ + pushl %esi + pushl %eax + shll %ebx + movzwl parent(%ebx, %ebp),%ebx +update1: + shll %ebx + movzwl freq(%ebx, %ebp), %edx + incl %edx /* increment frequency count by one */ + movw %dx, freq(%ebx, %ebp) + leal 2+freq(%ebx, %ebp), %esi + lodsw /* check if nodes need reordering */ + cmpw %ax, %dx + jbe update5 +update2: + lodsw + cmpw %dx, %ax + jb update2 + movzwl -4(%esi), %ecx + movw %cx, freq(%ebx, %ebp) /* swap frequency of entries */ + movw %dx, -4(%esi) + + movl %esi, %eax /* compute index of new entry */ + subl $freq+4, %eax + subl %ebp, %eax + + movl %eax, %edx + shrl %eax + movzwl son(%ebx, %ebp), %ecx /* get son of old entry */ + movl %ecx, %esi + addl %esi, %esi + movw %ax, parent(%esi, %ebp) /* and update the ptr to new parent */ + cmpl $TABLESZ, %ecx + jae update3 /* do this for both branches */ + movw %ax, parent+2(%esi, %ebp) /* if not a leaf node */ +update3: + movl %edx, %esi + movzwl son(%esi, %ebp), %edx /* get son of new entry */ + movw %cx, son(%esi, %ebp) /* update its contents */ + movl %edx, %esi + addl %esi, %esi + movl %ebx, %ecx + shrl %ecx + movw %cx, parent(%esi, %ebp) /* and update the ptr to new paren */ + cmpl $TABLESZ, %edx + jae update4 /* do this for both branches */ + movw %cx, parent+2(%esi, %ebp) /* if not a leaf node */ +update4: + movw %dx, son(%ebx, %ebp) /* update son of old entry */ + movl %eax, %ebx /* continue with new entry */ + shll %ebx +update5: + movzwl parent(%ebx, %ebp), %ebx /* continue with parent */ + orl %ebx, %ebx + jnz update1 /* until we found the root entry */ + popl %eax + popl %esi + ret + +/* + * constant data. this part of the program resides in ROM and cannot be + * changed + * + * run length encoded tables will be uncompressed into the bss segment + * take care with any symbols here for .com files to add 0x100 to address + */ + +dcodrle: .byte 0x01,0x03,0x08,0x0C,0x18,0x10 +dlenrle: .byte 0x20,0x30,0x40,0x30,0x30,0x10 + +/* + * variable data segment (bss) + * this segment will always be found at 0x90000 (i.e. at RELOC - SCRATCH) + * + * do not change the order or the sizes of any of the following tables + * the initialization code makes assumptions on the exact layout of the + * data structures... + */ + +.bss +/* lookup table for index into buffer of recently output characters */ +dcode: .skip 256 + +/* lookup table for length of code sequence from buffer of recent characters */ +dlen: .skip 256 + +/* table with frequency counts for all codes */ +freq: .skip 2*(TABLESZ+1) + +/* pointer to child nodes */ +son: .skip 2*(TABLESZ) + +/* the first part of this table contains all the codes (0..TABLESZ-1) */ +/* the second part contains all leaf nodes (TABLESZ..) */ +parent: .skip 2*(TABLESZ+NCHAR) + +/* temporary storage for extracting bits from compressed data stream */ +getlen: .skip 1 +getbuf: .skip 1 + + /* the initial buffer has to be filled with spaces */ + .balign 4 +spaces: + .skip BUFSZ - LOOKAHEAD + /* uncompressed data will be written here */ +uncompressed: + diff --git a/src/arch/i386/prefix/unhuf.lds b/src/arch/i386/prefix/unhuf.lds new file mode 100644 index 00000000..00d6b55b --- /dev/null +++ b/src/arch/i386/prefix/unhuf.lds @@ -0,0 +1,33 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS +{ + . = 0; + .text : { + _text = .; + *(.head) + *(.text) + } = 0x9090 + .rodata : { + *(.rodata) + } + _etext = . ; + .data : { + *(.data) + /* Force 4 byte alignment */ + . = ALIGN(4); + _payload = . ; + *(.huf) + _epayload = . ; + } + _edata = . ; + _data_size = _edata - _start; + /* Etherboot needs to be 16 byte aligned */ + . = ALIGN(16); + .bss : { + *(.bss) + } + _end = . ; + _image_size = _end - _start; +} diff --git a/src/arch/i386/prefix/unnrv2b.S b/src/arch/i386/prefix/unnrv2b.S new file mode 100644 index 00000000..1836fa71 --- /dev/null +++ b/src/arch/i386/prefix/unnrv2b.S @@ -0,0 +1,129 @@ +/* + * 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 + */ + + + .text + .arch i386 + .section ".prefix", "ax", @progbits + .code32 + + .globl decompress +decompress: + /* Save the initial register values */ + pushal + + /* + * See where I am running, and compute %ebp + * %ebp holds delta between physical and virtual addresses. + */ + call 1f +1: popl %ebp + subl $1b, %ebp + + /* "compressed" and "decompress_to" defined by linker script */ + /* move compressed image up to temporary area before decompressing */ + std + movl $_compressed_size, %ecx + leal _compressed+4-1(%ebp, %ecx), %esi + leal _compressed_copy-1(%ebp, %ecx), %edi + rep movsb + /* Setup to run the decompressor */ + cld + leal _compressed_copy(%ebp), %esi + leal decompress_to(%ebp), %edi + movl $-1, %ebp /* last_m_off = -1 */ + jmp dcl1_n2b + +/* ------------- DECOMPRESSION ------------- + + Input: + %esi - source + %edi - dest + %ebp - -1 + cld + + Output: + %eax - 0 + %ecx - 0 +*/ + +.macro getbit bits +.if \bits == 1 + addl %ebx, %ebx + jnz 1f +.endif + movl (%esi), %ebx + subl $-4, %esi /* sets carry flag */ + adcl %ebx, %ebx +1: +.endm + +decompr_literals_n2b: + movsb + +decompr_loop_n2b: + addl %ebx, %ebx + jnz dcl2_n2b +dcl1_n2b: + getbit 32 +dcl2_n2b: + jc decompr_literals_n2b + xorl %eax, %eax + incl %eax /* m_off = 1 */ +loop1_n2b: + getbit 1 + adcl %eax, %eax /* m_off = m_off*2 + getbit() */ + getbit 1 + jnc loop1_n2b /* while(!getbit()) */ + xorl %ecx, %ecx + subl $3, %eax + jb decompr_ebpeax_n2b /* if (m_off == 2) goto decompr_ebpeax_n2b ? */ + shll $8, %eax + movb (%esi), %al /* m_off = (m_off - 3)*256 + src[ilen++] */ + incl %esi + xorl $-1, %eax + jz decompr_end_n2b /* if (m_off == 0xffffffff) goto decomp_end_n2b */ + movl %eax, %ebp /* last_m_off = m_off ?*/ +decompr_ebpeax_n2b: + getbit 1 + adcl %ecx, %ecx /* m_len = getbit() */ + getbit 1 + adcl %ecx, %ecx /* m_len = m_len*2 + getbit()) */ + jnz decompr_got_mlen_n2b /* if (m_len == 0) goto decompr_got_mlen_n2b */ + incl %ecx /* m_len++ */ +loop2_n2b: + getbit 1 + adcl %ecx, %ecx /* m_len = m_len*2 + getbit() */ + getbit 1 + jnc loop2_n2b /* while(!getbit()) */ + incl %ecx + incl %ecx /* m_len += 2 */ +decompr_got_mlen_n2b: + cmpl $-0xd00, %ebp + adcl $1, %ecx /* m_len = m_len + 1 + (last_m_off > 0xd00) */ + pushl %esi + leal (%edi,%ebp), %esi /* m_pos = dst + olen + -m_off */ + rep + movsb /* dst[olen++] = *m_pos++ while(m_len > 0) */ + popl %esi + jmp decompr_loop_n2b +decompr_end_n2b: + /* Restore the initial register values */ + popal + ret |