diff options
author | H. Peter Anvin <hpa@zytor.com> | 2008-03-26 16:25:35 -0700 |
---|---|---|
committer | H. Peter Anvin <hpa@zytor.com> | 2008-03-26 16:25:35 -0700 |
commit | 9eddd22a7b53b1d02fbae0d987df8af122924248 (patch) | |
tree | 882f5152880b0b1aa2d7a0619d30065acc69fb16 /gpxe/src | |
parent | bbb8f15936b851e6a0ef6f7bb2c95197bff35994 (diff) | |
download | syslinux.git-9eddd22a7b53b1d02fbae0d987df8af122924248.tar.gz syslinux.git-9eddd22a7b53b1d02fbae0d987df8af122924248.tar.xz syslinux.git-9eddd22a7b53b1d02fbae0d987df8af122924248.zip |
Add gPXE into the source tree; build unified imagesyslinux-3.70-pre7
Diffstat (limited to 'gpxe/src')
619 files changed, 198529 insertions, 0 deletions
diff --git a/gpxe/src/.gitignore b/gpxe/src/.gitignore new file mode 100644 index 00000000..cc8e33e2 --- /dev/null +++ b/gpxe/src/.gitignore @@ -0,0 +1,4 @@ +.toolcheck +.echocheck +TAGS* +bin* diff --git a/gpxe/src/Config b/gpxe/src/Config new file mode 100644 index 00000000..210718d4 --- /dev/null +++ b/gpxe/src/Config @@ -0,0 +1,386 @@ +############################################################################## +############################################################################## +# +# IMPORTANT! +# +# The use of this file to set options that affect only single object +# files is deprecated, because changing anything in this file results +# in a complete rebuild, which is slow. All options are gradually +# being migrated to config.h, which does not suffer from this problem. +# +# Only options that affect the entire build (e.g. overriding the $(CC) +# Makefile variable) should be placed in here. +# +############################################################################## +############################################################################## + + +# +# Config for Etherboot/32 +# +# +# Do not delete the tag OptionDescription and /OptionDescription +# It is used to automatically generate the documentation. +# +# @OptionDescription@ +# User interaction options: +# +# -DASK_BOOT=n +# Ask "Boot from (N)etwork ... or (Q)uit? " +# at startup, timeout after n seconds (0 = no timeout). +# If unset or negative, don't ask and boot immediately +# using the default. +# -DBOOT_FIRST +# -DBOOT_SECOND +# -DBOOT_THIRD +# On timeout or Return key from previous +# question, selects the order to try to boot from +# various devices. +# (alternatives: BOOT_NIC, BOOT_DISK, +# BOOT_FLOPPY, BOOT_NOTHING) +# See etherboot.h for prompt and answer strings. +# BOOT_DISK and BOOT_FLOPPY work only where a driver +# exists, e.g. in LinuxBIOS. +# They have no effect on PCBIOS. +# -DBOOT_INDEX The device to boot from 0 == any device. +# 1 == The first nic found. +# 2 == The second nic found +# ... +# BOOT_INDEX only applies to the BOOT_FIRST. BOOT_SECOND +# and BOOT_THIRD search through all of the boot devices. +# -DBAR_PROGRESS +# Use rotating bar instead of sequential dots +# to indicate an IP packet transmitted. +# +# Boot order options: +# +# -DBOOT_CLASS_FIRST +# -DBOOT_CLASS_SECOND +# -DBOOT_CLASS_THIRD +# Select the priority of the boot classes +# Valid values are: +# BOOT_NIC +# BOOT_DISK +# BOOT_FLOPPY +# BOOT_DISK and BOOT_FLOPPY work only where a driver exists, +# e.g. in LinuxBIOS. They have no effect on PCBIOS. +# +# Boot autoconfiguration protocol options: +# +# -DALTERNATE_DHCP_PORTS_1067_1068 +# Use ports 1067 and 1068 for DHCP instead of 67 and 68. +# As these ports are non-standard, you need to configure +# your DHCP server to use them. This option gets around +# existing DHCP servers which cannot be touched, for +# one reason or another, at the cost of non-standard +# boot images. +# -DNO_DHCP_SUPPORT +# Use BOOTP instead of DHCP. +# -DRARP_NOT_BOOTP +# Use RARP instead of BOOTP/DHCP. +# -DREQUIRE_VCI_ETHERBOOT +# Require an encapsulated Vendor Class Identifier +# of "Etherboot" in the DHCP reply +# Requires DHCP support. +# -DDHCP_CLIENT_ID=\"Identifier\" +# -DDHCP_CLIENT_ID_LEN=<Client ID length in octets> +# -DDHCP_CLIENT_ID_TYPE=<Client ID type> +# Specify a RFC2132 Client Identifier option, length and type. +# Requires DHCP support. +# -DDHCP_USER_CLASS=\"UserClass\" +# -DDHCP_USER_CLASS_LEN=<User Class length in octets> +# Specify a RFC3004 User Class option and length. Use this +# option to set a UC (or multiple UCs) rather than munge the +# client Vendor Class ID. +# Requires DHCP support. +# -DALLOW_ONLY_ENCAPSULATED +# Ignore Etherboot-specific options that are not within +# the Etherboot encapsulated options field. This option +# should be enabled unless you have a legacy DHCP server +# configuration from the bad old days before the use of +# encapsulated Etherboot options. +# -DDEFAULT_BOOTFILE=\"default_bootfile_name\" +# Define a default bootfile for the case where your DHCP +# server does not provide the information. Example: +# -DDEFAULT_BOOTFILE="tftp:///tftpboot/kernel" +# If you do not specify this option, then DHCP offers that +# do not specify bootfiles will be ignored. +# +# NIC tuning parameters: +# +# -DALLMULTI +# Turns on multicast reception in the NICs. +# +# Boot tuning parameters: +# +# -DCONGESTED +# Turns on packet retransmission. Use it on a +# congested network, where the normal operation +# can't boot the image. +# -DBACKOFF_LIMIT +# Sets the maximum RFC951 backoff exponent to n. +# Do not set this unreasonably low, because on networks +# with many machines they can saturate the link +# (the delay corresponding to the exponent is a random +# time in the range 0..3.5*2^n seconds). Use 5 for a +# VERY small network (max. 2 minutes delay), 7 for a +# medium sized network (max. 7.5 minutes delay) or 10 +# for a really huge network with many clients, frequent +# congestions (max. 1 hour delay). On average the +# delay time will be half the maximum value. If in +# doubt about the consequences, use a larger value. +# Also keep in mind that the number of retransmissions +# is not changed by this setting, so the default of 20 +# may no longer be appropriate. You might need to set +# MAX_ARP_RETRIES, MAX_BOOTP_RETRIES, MAX_TFTP_RETRIES +# and MAX_RPC_RETRIES to a larger value. +# -DTIMEOUT=n +# Use with care!! See above. +# Sets the base of RFC2131 sleep interval to n. +# This can be used with -DBACKOFF_LIMIT=0 to get a small +# and constant (predictable) retry interval for embedded +# devices. This is to achieve short boot delays if both +# the DHCP Server and the embedded device will be powered +# on the same time. Otherwise if the DHCP server is ready +# the client could sleep the next exponentially timeout, +# e.g. 70 seconds or more. This is not what you want. +# n should be a multiple of TICKS_PER_SEC (18). +# +# Boot device options: +# +# -DTRY_FLOPPY_FIRST +# If > 0, tries that many times to read the boot +# sector from a floppy drive before booting from +# ROM. If successful, does a local boot. +# It assumes the floppy is bootable. +# -DEXIT_IF_NO_OFFER +# If no IP offer is obtained, exit and +# let the BIOS continue. +# The accessibility of the TFTP server has no effect, +# so configure your DHCP/BOOTP server properly. +# You should probably reduce MAX_BOOTP_RETRIES +# to a small number like 3. +# +# Boot image options: +# +# -DFREEBSD_KERNEL_ENV +# Pass in FreeBSD kernel environment +# -DAOUT_LYNX_KDI +# Add Lynx a.out KDI support +# -DMULTICAST_LEVEL1 +# Support for sending multicast packets +# -DMULTICAST_LEVEL2 +# Support for receiving multicast packets +# +# Interface export options: +# +# -DPXE_EXPORT +# Export a PXE API interface. This is work in +# progress. Note that you won't be able to load +# PXE NBPs unless you also use -DPXE_IMAGE. +# -DPXE_STRICT +# Strict(er) compliance with the PXE +# specification as published by Intel. This may +# or may not be a good thing depending on your +# view of the spec... +# -DPXE_DHCP_STRICT +# Strict compliance of the DHCP request packets +# with the PXE specification as published by +# Intel. This may or may not be a good thing +# depending on your view of whether requesting +# vendor options which don't actually exist is +# pointless or not. You probably want this +# option if you intend to use Windows RIS or +# similar. +# +# Obscure options you probably don't need to touch: +# +# -DZPXE_SUFFIX_STRIP +# If the last 5 characters of the filename passed to Etherboot is +# ".zpxe" then strip it off. This is useful in cases where a DHCP server +# is not able to be configured to support conditionals. The way it works +# is that the DHCP server is configured with a filename like +# "foo.nbi.zpxe" so that when PXE asks for a filename it gets that, and +# loads Etherboot from that file. Etherboot then starts up and once +# again asks the DHCP server for a filename and once again gets +# foo.nbi.zpxe, but with this option turned on loads "foo.nbi" instead. +# This allows people to use Etherboot who might not otherwise be able to +# because their DHCP servers won't let them. +# +# -DPOWERSAVE +# Halt the processor when waiting for keyboard input +# which saves power while waiting for user interaction. +# Good for compute clusters and VMware emulation. +# But may not work for all CPUs. +# +# @/OptionDescription@ + +# These default settings compile Etherboot with a small number of options. +# You may wish to enable more of the features if the size of your ROM allows. + + +# For prompting and default on timeout +# CFLAGS+= -DASK_BOOT=3 -DBOOT_FIRST=BOOT_NIC +# If you would like to attempt to boot from other devices as well as the network. +# CFLAGS+= -DBOOT_SECOND=BOOT_FLOPPY +# CFLAGS+= -DBOOT_THIRD=BOOT_DISK +# CFLAGS+= -DBOOT_INDEX=0 + +# If you prefer the old style rotating bar progress display +# CFLAGS+= -DBAR_PROGRESS + +# Show size indicator +# CFLAGS+= -DSIZEINDICATOR + +# Enabling this creates non-standard images which use ports 1067 and 1068 +# for DHCP/BOOTP +# CFLAGS+= -DALTERNATE_DHCP_PORTS_1067_1068 + +# Enabling this makes the boot ROM require a Vendor Class Identifier +# of "Etherboot" in the Vendor Encapsulated Options +# This can be used to reject replies from servers other than the one +# we want to give out addresses to us, but it will prevent Etherboot +# from getting an IP lease until you have configured DHCPD correctly +# CFLAGS+= -DREQUIRE_VCI_ETHERBOOT + +# EXPERIMENTAL! Set DHCP_CLIENT_ID to create a Client Identifier (DHCP +# option 61, see RFC2132 section 9.14) when Etherboot sends the DHCP +# DISCOVER and REQUEST packets. This ID must UNIQUELY identify each +# client on your local network. Set DHCP_CLIENT_ID_TYPE to the +# appropriate hardware type as described in RFC2132 / RFC1700; this +# almost certainly means using '1' if the Client ID is an Ethernet MAC +# address and '0' otherwise. Set DHCP_CLIENT_ID_LEN to the length of +# the Client ID in octets (this is not a null terminated C string, do +# NOT add 1 for a terminator and do NOT add an extra 1 for the +# hardware type octet). Note that to identify your client using the +# normal default MAC address of your NIC, you do NOT need to set this +# option, as the MAC address is automatically used in the +# hwtype/chaddr field; note also that this field only sets the DHCP +# option: it does NOT change the MAC address used by the client. + +# CFLAGS+= -DDHCP_CLIENT_ID="'C','L','I','E','N','T','0','0','1'" \ +# -DDHCP_CLIENT_ID_LEN=9 -DDHCP_CLIENT_ID_TYPE=0 + +# CFLAGS+= -DDHCP_CLIENT_ID="0xDE,0xAD,0xBE,0xEF,0xDE,0xAD" \ +# -DDHCP_CLIENT_ID_LEN=6 -DDHCP_CLIENT_ID_TYPE=1 + +# EXPERIMENTAL! Set DHCP_USER_CLASS to create a User Class option (see +# RFC3004) when Etherboot sends the DHCP DISCOVER and REQUEST packets. +# This can be used for classification of clients, typically so that a +# DHCP server can send an appropriately tailored reply. Normally, a +# string identifies a class of to which this client instance belongs +# which is useful in your network, such as a department ('FINANCE' or +# 'MARKETING') or hardware type ('THINCLIENT' or 'KIOSK'). Set +# DHCP_USER_CLASS_LEN to the length of DHCP_USER_CLASS in octets. +# This is NOT a null terminated C string, do NOT add 1 for a +# terminator. RFC3004 advises how to lay out multiple User Class +# options by using an octet for the length of each string, as in this +# example. It is, of course, up to the server to parse this. + +# CFLAGS+= -DDHCP_USER_CLASS="'T','E','S','T','C','L','A','S','S'" \ +# -DDHCP_USER_CLASS_LEN=9 + +# CFLAGS+= -DDHCP_USER_CLASS="5,'A','L','P','H','A',4,'B','E','T','A'" \ +# -DDHCP_USER_CLASS_LEN=11 + +# Enabling this causes Etherboot to ignore Etherboot-specific options +# that are not within an Etherboot encapsulated options field. +# This option should be enabled unless you have a legacy DHCP server +# configuration from the bad old days before the use of +# encapsulated Etherboot options. +# CFLAGS+= -DALLOW_ONLY_ENCAPSULATED + +# Disable DHCP support +# CFLAGS+= -DNO_DHCP_SUPPORT + +# Specify a default bootfile to be used if the DHCP server does not +# provide the information. If you do not specify this option, then +# DHCP offers that do not contain bootfiles will be ignored. +# CFLAGS+= -DDEFAULT_BOOTFILE=\"tftp:///tftpboot/kernel\" + +# Limit the delay on packet loss/congestion to a more bearable value. See +# description above. If unset, do not limit the delay between resend. +# CFLAGS+= -DBACKOFF_LIMIT=5 -DCONGESTED + +# More optional features +# CFLAGS+= -DTRY_FLOPPY_FIRST=4 +# CFLAGS+= -DEXIT_IF_NO_OFFER + + +# Multicast Support +# CFLAGS+= -DALLMULTI -DMULTICAST_LEVEL1 -DMULTICAST_LEVEL2 + +# Etherboot as a PXE network protocol ROM +# CFLAGS+= -DPXE_IMAGE -DPXE_EXPORT +# Etherboot stricter as a PXE network protocol ROM +# CFLAGS+= -DPXE_DHCP_STRICT + +# Support for PXE emulation. Works only with FreeBSD to load the kernel +# via pxeboot, use only with DOWNLOAD_PROTO_NFS +# CFLAGS+= -DFREEBSD_PXEEMU + + + +# Garbage from Makefile.main temporarily placed here until a home can +# be found for it. + +# NS8390 options: +# -DINCLUDE_NE - Include NE1000/NE2000 support +# -DNE_SCAN=list - Probe for NE base address using list of +# comma separated hex addresses +# -DINCLUDE_3C503 - Include 3c503 support +# -DT503_SHMEM - Use 3c503 shared memory mode (off by default) +# -DINCLUDE_WD - Include Western Digital/SMC support +# -DWD_DEFAULT_MEM- Default memory location for WD/SMC cards +# -DWD_790_PIO - Read/write to WD/SMC 790 cards in PIO mode (default +# is to use shared memory) Try this if you get "Bogus +# packet, ignoring" messages, common on ISA/PCI hybrid +# systems. +# -DCOMPEX_RL2000_FIX +# +# If you have a Compex RL2000 PCI 32-bit (11F6:1401), +# and the bootrom hangs in "Probing...[NE*000/PCI]", +# try enabling this fix... it worked for me :). +# In the first packet write somehow it somehow doesn't +# get back the expected data so it is stuck in a loop. +# I didn't bother to investigate what or why because it works +# when I interrupt the loop if it takes more then COMPEX_RL2000_TRIES. +# The code will notify if it does a abort. +# SomniOne - somnione@gmx.net +# +# 3C90X options: +# Warning Warning Warning +# If you use any of the XCVR options below, please do not complain about +# the behaviour with Linux drivers to the kernel developers. You are +# on your own if you do this. Please read 3c90x.txt to understand +# what they do. If you don't understand them, ask for help on the +# Etherboot mailing list. And please document what you did to the NIC +# on the NIC so that people after you won't get nasty surprises. +# +# -DCFG_3C90X_PRESERVE_XCVR - Reset the transceiver type to the value it +# had initially just before the loaded code is started. +# -DCFG_3C90X_XCVR - Hardcode the tranceiver type Etherboot uses. +# -DCFG_3C90X_BOOTROM_FIX - If you have a 3c905B with buggy ROM +# interface, setting this option might "fix" it. Use +# with caution and read the docs in 3c90x.txt! +# +# See the documentation file 3c90x.txt for more details. +# +# CS89X0 (optional) options: +# -DISA_PROBE_ADDRS=list +# Probe for CS89x0 base address using list of +# comma separated hex addresses; increasing the +# address by one (0x300 -> 0x301) will force a +# more aggressive probing algorithm. This might +# be neccessary after a soft-reset of the NIC. + + +CFLAGS_3c503 = -DINCLUDE_3C503 # -DT503_SHMEM +CFLAGS_ne = -DINCLUDE_NE -DNE_SCAN=0x300,0x280,0x320,0x340,0x380 +CFLAGS_ns8390 = -DINCLUDE_NS8390 # NE2000/PCI! +CFLAGS_wd = -DINCLUDE_WD -DWD_DEFAULT_MEM=0xCC000 + +# +# SYSLINUX: Local addition to build PXELINUX combined image +# +EMBEDDED_IMAGE = ../../pxelinux.0 diff --git a/gpxe/src/Makefile b/gpxe/src/Makefile new file mode 100644 index 00000000..0591bb01 --- /dev/null +++ b/gpxe/src/Makefile @@ -0,0 +1,191 @@ +# Location to place generated files +# +BIN := bin + +# Initialise variables that get added to throughout the various Makefiles +# +MAKEDEPS := Makefile .toolcheck .echocheck +SRCDIRS := +SRCS := +NON_AUTO_SRCS := +DRIVERS := +ROMS := +MEDIA := +NON_AUTO_MEDIA := + +# Locations of utilities +# +HOST_CC := gcc +RM := rm -f +TOUCH := touch +MKDIR := mkdir +CP := cp +ECHO := echo +PRINTF := printf +PERL := /usr/bin/perl +CC := $(CROSS_COMPILE)gcc +CPP := $(CROSS_COMPILE)gcc -E -Wp,-Wall +AS := $(CROSS_COMPILE)as +LD := $(CROSS_COMPILE)ld +SIZE := $(CROSS_COMPILE)size +AR := $(CROSS_COMPILE)ar +RANLIB := $(CROSS_COMPILE)ranlib +OBJCOPY := $(CROSS_COMPILE)objcopy +NM := $(CROSS_COMPILE)nm +OBJDUMP := $(CROSS_COMPILE)objdump +PARSEROM := $(PERL) ./util/parserom.pl +MAKEROM := $(PERL) ./util/makerom.pl +MKCONFIG := $(PERL) ./util/mkconfig.pl +SYMCHECK := $(PERL) ./util/symcheck.pl +SORTOBJDUMP := $(PERL) ./util/sortobjdump.pl +NRV2B := ./util/nrv2b +ZBIN := ./util/zbin +DOXYGEN := doxygen + +# If invoked with no build target, print out a helpfully suggestive +# message. +# +noargs : blib $(BIN)/NIC $(BIN)/gpxe.dsk $(BIN)/gpxe.iso $(BIN)/gpxe.usb + @$(ECHO) '===========================================================' + @$(ECHO) + @$(ECHO) 'To create a bootable floppy, type' + @$(ECHO) ' cat $(BIN)/gpxe.dsk > /dev/fd0' + @$(ECHO) 'where /dev/fd0 is your floppy drive. This will erase any' + @$(ECHO) 'data already on the disk.' + @$(ECHO) + @$(ECHO) 'To create a bootable USB key, type' + @$(ECHO) ' cat $(BIN)/gpxe.usb > /dev/sdX' + @$(ECHO) 'where /dev/sdX is your USB key, and is *not* a real hard' + @$(ECHO) 'disk on your system. This will erase any data already on' + @$(ECHO) 'the USB key.' + @$(ECHO) + @$(ECHO) 'To create a bootable CD-ROM, burn the ISO image ' + @$(ECHO) '$(BIN)/gpxe.iso to a blank CD-ROM.' + @$(ECHO) + @$(ECHO) 'These images contain drivers for all supported cards. You' + @$(ECHO) 'can build more customised images, and ROM images, using' + @$(ECHO) ' make bin/<rom-name>.<output-format>' + @$(ECHO) + @$(ECHO) '===========================================================' + +# Grab the central Config file. +# +MAKEDEPS += Config +include Config + +# If no architecture is specified in Config or on the command-line, +# use that of the build machine. +# +ARCH ?= $(shell uname -m | sed -e s,i[3456789]86,i386,) + +# handle x86_64 like i386, but set -m32 option for 32bit code only +ifeq ($(ARCH),x86_64) +ARCH := i386 +CFLAGS += -m32 +ASFLAGS += --32 +LDFLAGS += -m elf_i386 +endif + +# Drag in architecture-specific Config +# +MAKEDEPS += arch/$(ARCH)/Config +include arch/$(ARCH)/Config + +# Common flags +# +CFLAGS += -I include -I arch/$(ARCH)/include -I . -DARCH=$(ARCH) +CFLAGS += -Os -ffreestanding +CFLAGS += -Wall -W +CFLAGS += -g +CFLAGS += $(EXTRA_CFLAGS) +ASFLAGS += $(EXTRA_ASFLAGS) +LDFLAGS += $(EXTRA_LDFLAGS) + +# Embedded image, if present +# +EMBEDDED_IMAGE ?= /dev/null + +ifneq ($(NO_WERROR),1) +CFLAGS += -Werror +endif + +# CFLAGS for specific object types +# +CFLAGS_c += +CFLAGS_S += -DASSEMBLY + +# Base object name of the current target +# +OBJECT = $(firstword $(subst ., ,$(@F))) + +# CFLAGS for specific object files. You can define +# e.g. CFLAGS_rtl8139, and have those flags automatically used when +# compiling bin/rtl8139.o. +# +OBJ_CFLAGS = $(CFLAGS_$(OBJECT)) -DOBJECT=$(subst -,_,$(OBJECT)) +$(BIN)/%.flags : + @$(ECHO) $(OBJ_CFLAGS) + +# Rules for specific object types. +# +COMPILE_c = $(CC) $(CFLAGS) $(CFLAGS_c) $(OBJ_CFLAGS) +RULE_c = $(Q)$(COMPILE_c) -c $< -o $@ +RULE_c_to_dbg%.o = $(Q)$(COMPILE_c) -Ddebug_$(OBJECT)=$* -c $< -o $@ +RULE_c_to_c = $(Q)$(COMPILE_c) -E -c $< > $@ +RULE_c_to_s = $(Q)$(COMPILE_c) -S -g0 -c $< -o $@ + +PREPROCESS_S = $(CPP) $(CFLAGS) $(CFLAGS_S) $(OBJ_CFLAGS) +ASSEMBLE_S = $(AS) $(ASFLAGS) +RULE_S = $(Q)$(PREPROCESS_S) $< | $(ASSEMBLE_S) -o $@ +RULE_S_to_s = $(Q)$(PREPROCESS_S) $< > $@ + +DEBUG_TARGETS += dbg%.o c s + +# SRCDIRS lists all directories containing source files. +# +SRCDIRS += libgcc +SRCDIRS += core +SRCDIRS += proto +SRCDIRS += net net/tcp net/udp +SRCDIRS += image +SRCDIRS += drivers/bus +SRCDIRS += drivers/net +SRCDIRS += drivers/net/e1000 +SRCDIRS += drivers/block +SRCDIRS += drivers/scsi +SRCDIRS += drivers/ata +SRCDIRS += drivers/nvs +SRCDIRS += drivers/bitbash +SRCDIRS += drivers/infiniband +SRCDIRS += interface/pxe +SRCDIRS += tests +SRCDIRS += crypto crypto/axtls crypto/matrixssl +SRCDIRS += hci hci/commands hci/tui +SRCDIRS += hci/mucurses hci/mucurses/widgets +SRCDIRS += usr + +# NON_AUTO_SRCS lists files that are excluded from the normal +# automatic build system. +# +NON_AUTO_SRCS += core/elf_loader.c +NON_AUTO_SRCS += drivers/net/prism2.c + +# Rules for finalising files. TGT_MAKEROM_FLAGS is defined as part of +# the automatic build system and varies by target; it includes the +# "-p 0x1234,0x5678" string to set the PCI IDs. +# +FINALISE_rom = $(MAKEROM) $(MAKEROM_FLAGS) $(TGT_MAKEROM_FLAGS) \ + -i$(IDENT) -s 0 $@ + +# Some ROMs require specific flags to be passed to makerom.pl +# +MAKEROM_FLAGS_3c503 = -3 + +# Drag in architecture-specific Makefile +# +MAKEDEPS += arch/$(ARCH)/Makefile +include arch/$(ARCH)/Makefile + +# Drag in the automatic build system and other housekeeping functions +MAKEDEPS += Makefile.housekeeping +include Makefile.housekeeping diff --git a/gpxe/src/Makefile.housekeeping b/gpxe/src/Makefile.housekeeping new file mode 100644 index 00000000..fe3addc9 --- /dev/null +++ b/gpxe/src/Makefile.housekeeping @@ -0,0 +1,603 @@ +# -*- makefile -*- : Force emacs to use Makefile mode + +# This file contains various boring housekeeping functions that would +# otherwise seriously clutter up the main Makefile. + +# Objects to be removed by "make clean" +# +CLEANUP := $(BIN)/*.* # *.* to avoid catching the "CVS" directory + +# Version number calculations +# +VERSION_MAJOR = 0 +VERSION_MINOR = 9 +VERSION_PATCH = 3 +EXTRAVERSION = +MM_VERSION = $(VERSION_MAJOR).$(VERSION_MINOR) +VERSION = $(MM_VERSION).$(VERSION_PATCH)$(EXTRAVERSION) +CFLAGS += -DVERSION_MAJOR=$(VERSION_MAJOR) \ + -DVERSION_MINOR=$(VERSION_MINOR) \ + -DVERSION=\"$(VERSION)\" +IDENT = '$(@F) $(VERSION) (GPL) etherboot.org' +version : + @$(ECHO) $(VERSION) + +configure : + @$(ECHO) "No configuration needed." + +install : + @$(ECHO) "No installation required. Generated images will be placed in the" $(BIN) "directory." + +# Check for tools that can cause failed builds +# +.toolcheck : Makefile Config + @if $(CC) -v 2>&1 | grep -is 'gcc version 2\.96' > /dev/null; then \ + $(ECHO) 'gcc 2.96 is unsuitable for compiling Etherboot'; \ + $(ECHO) 'Use gcc 2.95 or gcc 3.x instead'; \ + exit 1; \ + fi + @if [ `perl -e 'use bytes; print chr(255)' | wc -c` = 2 ]; then \ + $(ECHO) 'Your Perl version has a Unicode handling bug'; \ + $(ECHO) 'Execute this command before compiling Etherboot:'; \ + $(ECHO) 'export LANG=$${LANG%.UTF-8}'; \ + exit 1; \ + fi + @$(TOUCH) $@ +VERYCLEANUP += .toolcheck + +# Find a usable "echo -e" substitute. +# +TAB := $(shell $(PRINTF) '\t') +ECHO_E_ECHO := $(ECHO) +ECHO_E_ECHO_E := $(ECHO) -e +ECHO_E_BIN_ECHO := /bin/echo +ECHO_E_BIN_ECHO_E := /bin/echo -e +ECHO_E_ECHO_TAB := $(shell $(ECHO_E_ECHO) '\t' | cat) +ECHO_E_ECHO_E_TAB := $(shell $(ECHO_E_ECHO_E) '\t' | cat) +ECHO_E_BIN_ECHO_TAB := $(shell $(ECHO_E_BIN_ECHO) '\t') +ECHO_E_BIN_ECHO_E_TAB := $(shell $(ECHO_E_BIN_ECHO_E) '\t') + +ifeq ($(ECHO_E_ECHO_TAB),$(TAB)) +ECHO_E ?= $(ECHO_E_ECHO) +endif +ifeq ($(ECHO_E_ECHO_E_TAB),$(TAB)) +ECHO_E ?= $(ECHO_E_ECHO_E) +endif +ifeq ($(ECHO_E_BIN_ECHO_TAB),$(TAB)) +ECHO_E ?= $(ECHO_E_BIN_ECHO) +endif +ifeq ($(ECHO_E_BIN_ECHO_E_TAB),$(TAB)) +ECHO_E ?= $(ECHO_E_BIN_ECHO_E) +endif + +.echocheck : +ifdef ECHO_E + @$(TOUCH) $@ +else + @$(PRINTF) '%24s : x%sx\n' 'tab' '$(TAB)' + @$(PRINTF) '%24s : x%sx\n' '"$(ECHO_E_ECHO) \t"' \ + '$(ECHO_E_ECHO_TAB)' + @$(PRINTF) '%24s : x%sx\n' '"$(ECHO_E_ECHO_E) \t"' \ + '$(ECHO_E_ECHO_E_TAB)' + @$(PRINTF) '%24s : x%sx\n' '"$(ECHO_E_BIN_ECHO) \t"' \ + '$(ECHO_E_BIN_ECHO_TAB)' + @$(PRINTF) '%24s : x%sx\n' '"$(ECHO_E_BIN_ECHO_E) \t"' \ + '$(ECHO_E_BIN_ECHO_E_TAB)' + @$(ECHO) "No usable \"echo -e\" substitute found" + @exit 1 +endif +VERYCLEANUP += .echocheck + +echo : + @$(ECHO) "Using \"$(ECHO_E)\" for \"echo -e\"" + +# Build verbosity +# +ifeq ($(V),1) +Q = +QM = @\# +else +Q = @ +QM = @ +endif + +# Check for an old version of gas (binutils 2.9.1) +# +OLDGAS := $(shell $(AS) --version | grep -q '2\.9\.1' && $(ECHO) -DGAS291) +CFLAGS += $(OLDGAS) +oldgas : + @$(ECHO) $(oldgas) + +# Some widespread patched versions of gcc include -fstack-protector by +# default, even when -ffreestanding is specified. We therefore need +# to disable -fstack-protector if the compiler supports it. +# +SP_TEST = $(CC) -fno-stack-protector -x c -E - < /dev/null >/dev/null 2>&1 +SP_FLAGS := $(shell $(SP_TEST) && $(ECHO) '-fno-stack-protector') +CFLAGS += $(SP_FLAGS) + +# compiler.h is needed for our linking and debugging system +# +CFLAGS += -include compiler.h + +# config/%.h files are generated from config.h using mkconfig.pl +config/%.h : config.h + $(MKCONFIG) $< +CLEANUP += config/*.h + +# SRCDIRS lists all directories containing source files. +srcdirs : + @$(ECHO) $(SRCDIRS) + +# SRCS lists all .c or .S files found in any SRCDIR +# +SRCS += $(wildcard $(patsubst %,%/*.c,$(SRCDIRS))) +SRCS += $(wildcard $(patsubst %,%/*.S,$(SRCDIRS))) +srcs : + @$(ECHO) $(SRCS) + +# AUTO_SRCS lists all files in SRCS that are not mentioned in +# NON_AUTO_SRCS. Files should be added to NON_AUTO_SRCS if they +# cannot be built using the standard build template. +# +AUTO_SRCS = $(filter-out $(NON_AUTO_SRCS),$(SRCS)) +autosrcs : + @$(ECHO) $(AUTO_SRCS) + +# We automatically generate rules for any file mentioned in AUTO_SRCS +# using the following set of templates. It would be cleaner to use +# $(eval ...), but this function exists only in GNU make >= 3.80. + +# src_template : generate Makefile rules for a given source file +# +# $(1) is the full path to the source file (e.g. "drivers/net/rtl8139.c") +# $(2) is the full path to the .d file (e.g. "bin/deps/drivers/net/rtl8139.d") +# $(3) is the source type (e.g. "c") +# $(4) is the source base name (e.g. "rtl8139") +# +define src_template + + @$(ECHO) "Generating Makefile rules for $(1)" + @$(MKDIR) -p $(dir $(2)) + @$(RM) $(2) + @$(TOUCH) $(2) + $(foreach OBJ,$(if $(OBJS_$(4)),$(OBJS_$(4)),$(4)), \ + $(call obj_template,$(1),$(2),$(3),$(OBJ))) + @$(PARSEROM) $(1) >> $(2) + +endef + +# obj_template : generate Makefile rules for a given resultant object +# of a particular source file. (We can have multiple objects per +# source file via the OBJS_xxx list.) +# +# $(1) is the full path to the source file (e.g. "drivers/net/rtl8139.c") +# $(2) is the full path to the .d file (e.g. "bin/deps/drivers/net/rtl8139.d") +# $(3) is the source type (e.g. "c") +# $(4) is the object name (e.g. "rtl8139") +# +define obj_template + + @$(CPP) $(CFLAGS) $(CFLAGS_$(3)) $(CFLAGS_$(4)) -DOBJECT=$(4) \ + -Wno-error -MM $(1) -MT "$(4)_DEPS" -MG -MP | \ + sed 's/_DEPS\s*:/_DEPS =/' >> $(2) + @$(ECHO_E) '\n$$(BIN)/$(4).o : $(1) $$(MAKEDEPS) $$($(4)_DEPS)' \ + '\n\t$$(QM)$(ECHO) " [BUILD] $$@"\n' \ + '\n\t$$(RULE_$(3))\n' \ + '\nBOBJS += $$(BIN)/$(4).o\n' \ + $(foreach TGT,$(DEBUG_TARGETS), \ + $(if $(RULE_$(3)_to_$(TGT)), \ + '\n$$(BIN)/$(4).$(TGT) : $(1) $$(MAKEDEPS) $$($(4)_DEPS)' \ + '\n\t$$(QM)$(ECHO) " [BUILD] $$@"\n' \ + '\n\t$$(RULE_$(3)_to_$(TGT))\n' \ + '\n$(TGT)_OBJS += $$(BIN)/$(4).$(TGT)\n' ) ) \ + '\n$(2) : $$($(4)_DEPS)\n' \ + '\nTAGS : $$($(4)_DEPS)\n' \ + >> $(2) + +endef + +# Rule to generate the Makefile rules files to be included +# +$(BIN)/deps/%.d : % $(MAKEDEPS) $(PARSEROM) + $(if $(filter $(AUTO_SRCS),$<),$(call src_template,$<,$@,$(subst .,,$(suffix $<)),$(basename $(notdir $<))),@$(ECHO) 'ERROR: $< is not an AUTO_SRC' ; exit 1) + +# Calculate and include the list of Makefile rules files +# +AUTO_DEPS = $(patsubst %,$(BIN)/deps/%.d,$(AUTO_SRCS)) +include $(AUTO_DEPS) +autodeps : + @$(ECHO) $(AUTO_DEPS) +VERYCLEANUP += $(BIN)/deps + +# The following variables are created by the Makefile rules files +# +bobjs : + @$(ECHO) $(BOBJS) +drivers : + @$(ECHO) $(DRIVERS) +.PHONY : drivers +roms : + @$(ECHO) $(ROMS) + +# Embedded binary +$(BIN)/embedimg.bin: $(EMBEDDED_IMAGE) + $(QM)$(ECHO) " [COPY] $@" + $(Q)$(CP) -f $(EMBEDDED_IMAGE) $@ + +$(BIN)/embed.o: $(BIN)/embedimg.bin +CFLAGS_embed = -DEMBEDIMG=\"$(BIN)/embedimg.bin\" + +# Generate the NIC file from the parsed source files. The NIC file is +# only for rom-o-matic. +# +$(BIN)/NIC : $(AUTO_DEPS) + @$(ECHO) '# This is an automatically generated file, do not edit' > $@ + @$(ECHO) '# It does not affect anything in the build, ' \ + 'it is only for rom-o-matic' >> $@ + @$(ECHO) >> $@ + @perl -ne 'chomp; print "$$1\n" if /\# NIC\t(.*)$$/' $^ >> $@ +CLEANUP += $(BIN)/NIC + +# Analyse a target name (e.g. "bin/dfe538--prism2_pci.zrom.tmp") and +# derive the variables: +# +# TGT_ELEMENTS : the elements of the target (e.g. "dfe538 prism2_pci") +# TGT_PREFIX : the prefix type (e.g. "zrom") +# TGT_DRIVERS : the driver for each element (e.g. "rtl8139 prism2_pci") +# TGT_ROM_NAME : the ROM name (e.g. "dfe538") +# TGT_MEDIA : the media type (e.g. "rom") +# +DRIVERS_gpxe = $(DRIVERS) +CARD_DRIVER = $(firstword $(DRIVER_$(1)) $(1)) +TGT_ELEMENTS = $(subst --, ,$(firstword $(subst ., ,$(notdir $@)))) +TGT_PREFIX = $(word 2,$(subst ., ,$(notdir $@))) +TGT_ROM_NAME = $(firstword $(TGT_ELEMENTS)) +TGT_DRIVERS = $(strip $(if $(DRIVERS_$(TGT_ROM_NAME)), \ + $(DRIVERS_$(TGT_ROM_NAME)), \ + $(foreach TGT_ELEMENT,$(TGT_ELEMENTS), \ + $(call CARD_DRIVER,$(TGT_ELEMENT))) )) +TGT_MEDIA = $(subst z,,$(TGT_PREFIX)) + +# Look up ROM IDs for the current target +# (e.g. "bin/dfe538--prism2_pci.zrom.tmp") and derive the variables: +# +# TGT_PCI_VENDOR : the PCI vendor ID (e.g. "0x1186") +# TGT_PCI_DEVICE : the PCI device ID (e.g. "0x1300") +# +TGT_PCI_VENDOR = $(PCI_VENDOR_$(TGT_ROM_NAME)) +TGT_PCI_DEVICE = $(PCI_DEVICE_$(TGT_ROM_NAME)) + +# Calculate link-time options for the current target +# (e.g. "bin/dfe538--prism2_pci.zrom.tmp") and derive the variables: +# +# TGT_LD_DRIVERS : symbols to require in order to drag in the relevant drivers +# (e.g. "obj_rtl8139 obj_prism2_pci") +# TGT_LD_IDS : symbols to define in order to fill in ID structures in the +# ROM header (e.g."pci_vendor_id=0x1186 pci_device_id=0x1300") +# +TGT_LD_DRIVERS = $(subst -,_,$(patsubst %,obj_%,$(TGT_DRIVERS))) +TGT_LD_PREFIX = obj_$(TGT_PREFIX)prefix +TGT_LD_IDS = $(if $(TGT_PCI_VENDOR),pci_vendor_id=$(TGT_PCI_VENDOR)) \ + $(if $(TGT_PCI_DEVICE),pci_device_id=$(TGT_PCI_DEVICE)) + +# Calculate linker flags based on link-time options for the current +# target type (e.g. "bin/dfe538--prism2_pci.zrom.tmp") and derive the +# variables: +# +# TGT_LD_FLAGS : target-specific flags to pass to linker (e.g. +# "-u obj_zpciprefix -u obj_rtl8139 -u obj_prism2_pci +# --defsym pci_vendor=0x1186 --defsym pci_device=0x1300") +# +TGT_LD_FLAGS = $(foreach SYM,$(TGT_LD_PREFIX) $(TGT_LD_DRIVERS) obj_config,\ + -u $(SYM) --defsym check_$(SYM)=$(SYM) ) \ + $(patsubst %,--defsym %,$(TGT_LD_IDS)) + +# Calculate makerom flags for the specific target +# (e.g. "bin/dfe538--prism2_pci.zrom.tmp") and derive the variables: +# +# TGT_MAKEROM_FLAGS : target-specific flags for makerom (e.g. +# "-p 0x1186,0x1300") +# +TGT_MAKEROM_FLAGS = $(strip $(MAKEROM_FLAGS_$(TGT_ROM_NAME)) \ + $(if $(TGT_PCI_VENDOR),$(strip -p $(TGT_PCI_VENDOR),$(TGT_PCI_DEVICE)))) + +# Calculate list of debugging versions of objects to be included in +# the target. +# +COMMA := , +DEBUG_LIST = $(subst $(COMMA), ,$(DEBUG)) +DEBUG_OBJ_LEVEL = $(firstword $(word 2,$(subst :, ,$(1))) 1) +DEBUG_OBJ_BASE = $(word 1,$(subst :, ,$(1))).dbg$(call DEBUG_OBJ_LEVEL,$(1)) +DEBUG_OBJ = $(BIN)/$(call DEBUG_OBJ_BASE,$(1)).o +DEBUG_ORIG_OBJ = $(BIN)/$(word 1,$(subst :, ,$(1))).o +DEBUG_OBJS = $(foreach D,$(DEBUG_LIST),$(call DEBUG_OBJ,$(D))) +DEBUG_ORIG_OBJS = $(foreach D,$(DEBUG_LIST),$(call DEBUG_ORIG_OBJ,$(D))) +BLIB_OBJS = $(DEBUG_OBJS) $(filter-out $(DEBUG_ORIG_OBJS),$(BOBJS)) + +# Print out all derived information for a given target. +# +$(BIN)/%.info : + @$(ECHO) 'Elements : $(TGT_ELEMENTS)' + @$(ECHO) 'Prefix : $(TGT_PREFIX)' + @$(ECHO) 'Drivers : $(TGT_DRIVERS)' + @$(ECHO) 'ROM name : $(TGT_ROM_NAME)' + @$(ECHO) 'Media : $(TGT_MEDIA)' + @$(ECHO) + @$(ECHO) 'PCI vendor : $(TGT_PCI_VENDOR)' + @$(ECHO) 'PCI device : $(TGT_PCI_DEVICE)' + @$(ECHO) + @$(ECHO) 'LD driver symbols : $(TGT_LD_DRIVERS)' + @$(ECHO) 'LD prefix symbols : $(TGT_LD_PREFIX)' + @$(ECHO) 'LD ID symbols : $(TGT_LD_IDS)' + @$(ECHO) + @$(ECHO) 'LD target flags : $(TGT_LD_FLAGS)' + @$(ECHO) + @$(ECHO) 'makerom target flags : $(TGT_MAKEROM_FLAGS)' + @$(ECHO) + @$(ECHO) 'Debugging objects : $(DEBUG_OBJS)' + @$(ECHO) 'Replaced objects : $(DEBUG_ORIG_OBJS)' + +# List of objects included in the last build of blib. This is needed +# in order to correctly rebuild blib whenever the list of objects +# changes. +# +BLIB_LIST = $(BIN)/.blib.list +ifneq ($(shell cat $(BLIB_LIST)),$(BLIB_OBJS)) +$(shell $(ECHO) "$(BLIB_OBJS)" > $(BLIB_LIST)) +endif + +$(BLIB_LIST) : + +VERYCLEANUP += $(BLIB_LIST) + +# Library of all objects +# +BLIB = $(BIN)/blib.a +$(BLIB) : $(BLIB_OBJS) $(BLIB_LIST) $(MAKEDEPS) + $(Q)$(RM) $(BLIB) + $(QM)$(ECHO) " [AR] $@" + $(Q)$(AR) r $@ $(BLIB_OBJS) + $(Q)$(RANLIB) $@ +blib : $(BLIB) + +# Build an intermediate object file from the objects required for the +# specified target. +# +$(BIN)/%.tmp : $(BLIB) $(MAKEDEPS) $(LDSCRIPT) + $(QM)$(ECHO) " [LD] $@" + $(Q)$(LD) $(LDFLAGS) -T $(LDSCRIPT) $(TGT_LD_FLAGS) $(BLIB) -o $@ \ + -Map $(BIN)/$*.tmp.map + $(Q)$(OBJDUMP) -ht $@ | $(SORTOBJDUMP) >> $(BIN)/$*.tmp.map + +# Keep intermediate object file (useful for debugging) +.SECONDARY : $(BIN)/%.tmp + +# Show a linker map for the specified target +# +$(BIN)/%.map : $(BIN)/%.tmp + @less $(BIN)/$*.tmp.map + +# Extract compression information from intermediate object file +# +$(BIN)/%.zinfo : $(BIN)/%.tmp + $(QM)$(ECHO) " [ZINFO] $@" + $(Q)$(OBJCOPY) -O binary -j .zinfo $< $@ + +# Build raw binary file from intermediate object file +# +$(BIN)/%.bin : $(BIN)/%.tmp + $(QM)$(ECHO) " [BIN] $@" + $(Q)$(OBJCOPY) -O binary -R .zinfo $< $@ + +# Compress raw binary file +# +$(BIN)/%.zbin : $(BIN)/%.bin $(BIN)/%.zinfo $(ZBIN) + $(QM)$(ECHO) " [ZBIN] $@" + $(Q)$(ZBIN) $(BIN)/$*.bin $(BIN)/$*.zinfo > $@ + +# Build bochs symbol table +$(BIN)/%.bxs : $(BIN)/%.tmp + $(NM) $< | cut -d" " -f1,3 > $@ + +# Rules for each media format. These are generated and placed in an +# external Makefile fragment. We could do this via $(eval ...), but +# that would require make >= 3.80. +# +# Note that there's an alternative way to generate most .rom images: +# they can be copied from their 'master' ROM image using cp and +# reprocessed with makerom to add the PCI IDs and ident string. The +# relevant rule would look something like: +# +# $(BIN)/dfe538%rom : $(BIN)/rtl8139%rom +# cat $< $@ +# $(FINALISE_rom) +# +# You can derive the ROM/driver relationships using the variables +# DRIVER_<rom> and/or ROMS_<driver>. +# +# We don't currently do this, because (a) it would require generating +# yet more Makefile fragments (since you need a rule for each ROM in +# ROMS), and (b) the linker is so fast that it probably wouldn't make +# much difference to the overall build time. + +media : + @$(ECHO) $(MEDIA) + +AUTO_MEDIA = $(filter-out $(NON_AUTO_MEDIA),$(MEDIA)) +automedia : + @$(ECHO) $(AUTO_MEDIA) + +# media_template : create Makefile rules for specified media +# +# $(1) is the media name (e.g. "rom") +# $(2) is the full path to the .d file (e.g. "bin/deps/rom.media.d") +# +define media_template + + @$(ECHO) "Generating Makefile rules for $(1) media" + @$(MKDIR) -p $(dir $(2)) + @$(RM) $(2) + @$(TOUCH) $(2) + @$(ECHO_E) '$$(BIN)/%.$(1) : $$(BIN)/%.$(1).zbin' \ + '\n\t$$(QM)$(ECHO) " [FINISH] $$@"' \ + '\n\t$$(Q)$$(CP) $$< $$@' \ + '\n\t$$(Q)$$(FINALISE_$(1))' \ + > $(2) + +endef + +# Rule to generate the Makefile rules to be included +# +$(BIN)/deps/%.media.d : $(MAKEDEPS) + $(if $(filter $(AUTO_MEDIA),$*), \ + $(call media_template,$*,$@), \ + @$(ECHO) 'ERROR: $* is not an AUTO_MEDIA' ; exit 1) + +# Calculate and include the list of Makefile rules files +# +MEDIA_DEPS = $(patsubst %,$(BIN)/deps/%.media.d,$(AUTO_MEDIA)) +mediadeps : + @$(ECHO) $(MEDIA_DEPS) +include $(MEDIA_DEPS) + +# The "allXXXs" targets for each suffix +# +allall: allroms allzroms allpxes allisos alldsks +allroms allzroms : all%s : $(foreach ROM,$(ROMS),$(BIN)/$(ROM).%) +allpxes allisos alldsks : all%s : $(foreach DRIVER,$(DRIVERS),$(BIN)/$(DRIVER).%) + +# Alias for gpxe.% +# +$(BIN)/etherboot.% : $(BIN)/gpxe.% + ln -sf $(notdir $<) $@ + +# Wrap up binary blobs +# +$(BIN)/%.o : payload/%.img + $(QM)echo " [WRAP] $@" + $(Q)$(LD) -b binary -r -o $@ $< --undefined obj_payload \ + --defsym obj_$*=0 + +BOBJS += $(patsubst payload/%.img,$(BIN)/%.o,$(wildcard payload/*.img)) + +# The compression utilities +# +$(NRV2B) : util/nrv2b.c $(MAKEDEPS) + $(QM)$(ECHO) " [HOSTCC] $@" + $(Q)$(HOST_CC) -O2 -DENCODE -DDECODE -DMAIN -DVERBOSE -DNDEBUG \ + -DBITSIZE=32 -DENDIAN=0 -o $@ $< +CLEANUP += $(NRV2B) + +$(ZBIN) : util/zbin.c util/nrv2b.c $(MAKEDEPS) + $(QM)$(ECHO) " [HOSTCC] $@" + $(Q)$(HOST_CC) -O2 -o $@ $< +CLEANUP += $(ZBIN) + +# Auto-incrementing build serial number. Append "bs" to your list of +# build targets to get a serial number printed at the end of the +# build. Enable -DBUILD_SERIAL in order to see it when the code runs. +# +BUILDSERIAL_H = config/.buildserial.h +BUILDSERIAL_NOW = config/.buildserial.now +BUILDSERIAL_NEXT = config/.buildserial.next + +$(BUILDSERIAL_NOW) $(BUILDSERIAL_NEXT) : + $(ECHO) 1 > $@ + +$(BUILDSERIAL_H) : $(BUILDSERIAL_NOW) $(BUILDSERIAL_NEXT) + $(ECHO) '#define BUILD_SERIAL_NUM $(shell cat $<)' > $@ + +ifeq ($(filter bs,$(MAKECMDGOALS)),bs) +$(shell diff -q $(BUILDSERIAL_NOW) $(BUILDSERIAL_NEXT) > /dev/null || \ + cp -f $(BUILDSERIAL_NEXT) $(BUILDSERIAL_NOW)) +endif + +bs : $(BUILDSERIAL_NOW) + @$(ECHO) $$(( $(shell cat $<) + 1 )) > $(BUILDSERIAL_NEXT) + @$(ECHO) "Build serial number is $(shell cat $<)" + +# List of available architectures +# +ARCHS = $(filter-out CVS,$(patsubst arch/%,%,$(wildcard arch/*))) +archs : + @$(ECHO) $(ARCHS) + +OTHER_ARCHS = $(filter-out $(ARCH),$(ARCHS)) +otherarchs : + @$(ECHO) $(OTHER_ARCHS) + +# Build the TAGS file for emacs +# +TAGS : TAGS.$(ARCH) + +TAGS.$(ARCH) : + ctags -e -R -f $@ --exclude=bin \ + $(foreach ARCH,$(OTHER_ARCHS),--exclude=arch/$(ARCH)) +CLEANUP += TAGS* + +# Symbol table checks +# +SYMTAB = $(BIN)/symtab +$(SYMTAB) : $(BLIB) + $(OBJDUMP) -w -t $< > $@ + +CLEANUP += $(BIN)/symtab + +symcheck : $(SYMTAB) + $(SYMCHECK) $< + +# Force rebuild for any given target +# +$(BIN)/%.rebuild : + rm -f $(BIN)/$* + $(MAKE) $(MAKEFLAGS) $(BIN)/$* + +# Documentation +# +$(BIN)/doxygen.cfg : doxygen.cfg $(MAKEDEPS) + $(PERL) -pe 's{\@SRCDIRS\@}{$(SRCDIRS)}; ' \ + -e 's{\@BIN\@}{$(BIN)}; ' \ + -e 's{\@ARCH\@}{$(ARCH)}; ' \ + $< > $@ + +$(BIN)/doc : $(BIN)/doxygen.cfg + $(DOXYGEN) $< + +.PHONY : $(BIN)/doc + +VERYCLEANUP += $(BIN)/doc + +doc : $(BIN)/doc + +docview : + @[ -f $(BIN)/doc/html/index.html ] || $(MAKE) $(BIN)/doc + @if [ -n "$$BROWSER" ] ; then \ + ( $$BROWSER $(BIN)/doc/html/index.html & ) ; \ + else \ + $(ECHO) "Documentation index in $(BIN)/doc/html/index.html" ; \ + fi + +# Clean-up +# +clean : + $(RM) $(CLEANUP) + +veryclean : clean + $(RM) -r $(VERYCLEANUP) + +# Make clean tarballs for release + +tarball : ../VERSION + ($(ECHO) -n $(VERSION) ''; date -u +'%Y-%m-%d') > ../VERSION + $(RM) -r /tmp/$(USER)/gpxe-$(VERSION) + mkdir -p /tmp/$(USER)/gpxe-$(VERSION) + cp -rP .. /tmp/$(USER)/gpxe-$(VERSION) + ( cd /tmp/$(USER)/gpxe-$(VERSION)/src ; $(MAKE) veryclean ; $(RM) -r bin/deps ) + ( cd /tmp/$(USER); tar cf /tmp/$(USER)/gpxe-$(VERSION).tar --exclude ".git*" --exclude "#*" \ + --exclude "*~" gpxe-$(VERSION) ) + bzip2 -9 < /tmp/$(USER)/gpxe-$(VERSION).tar > /tmp/$(USER)/gpxe-$(VERSION).tar.bz2 + gzip -9 < /tmp/$(USER)/gpxe-$(VERSION).tar > /tmp/$(USER)/gpxe-$(VERSION).tar.gz + $(RM) -r /tmp/$(USER)/gpxe-$(VERSION) + $(RM) /tmp/$(USER)/gpxe-$(VERSION).tar + ( cd /tmp/$(USER) ; tar -zxf /tmp/$(USER)/gpxe-$(VERSION).tar.gz ) diff --git a/gpxe/src/README.cvs b/gpxe/src/README.cvs new file mode 100644 index 00000000..56a24f9f --- /dev/null +++ b/gpxe/src/README.cvs @@ -0,0 +1,65 @@ +Changes should be committed to the CVS HEAD only when they are in a +working state. The definition of "working" is somewhat liquid; a good +guiding principle is that anyone checking out HEAD should receive a +checkout of working software. + +When you want to work on changes that are likely to temporarily break +large swathes of code, you should probably work on a private branch. +Since CVS branching and merging is something of a black art, here are +some simple step-by-step instructions for creating and using a branch. + +To create your private branch: + + # Get most up-to-date tree before branching + cvs update + # Create a branch called "my-branch" + cvs tag -b my-branch + # Switch working copy to the "my-branch" branch + cvs update -r my-branch + +At this point you'll be on a branch called "my-branch". Any changes +you make will not affect people working on HEAD, or on other branches. + +Use name for your branch that is both descriptive and unique. +Starting the branch name with your SourceForge username +(e.g. "mcb30-realmode-redesign") is a good idea. + +When you want to merge the changes on your branch back into HEAD, do +the following: + + # Ensure there are no not-yet-checked-in modifications in your tree) + cvs -q update + # Tag the merge point in the "my-branch" branch + cvs tag -c my-branch-merge-1 + # Switch working copy back to HEAD + cvs update -A + # Merge changes from the branch + cvs update -j my-branch + # Commit merged changes to HEAD + cvs commit + +If you then want to continue working further on the "my-branch" branch, +do the following + + # Switch working copy back to the "my-branch" branch + cvs update -r my-branch + +and then when you want to merge some more changes back to HEAD: + + # Ensure there are no not-yet-checked-in modifications in your tree) + cvs -q update + # Tag the merge point in the "my-branch" branch + cvs tag -c my-branch-merge-2 + # Switch working copy back to HEAD + cvs update -A + # Merge changes from the branch + cvs update -j my-branch-merge-1 -j my-branch + # Commit merged changes to HEAD + cvs commit + +Note that the format of the "merge changes from the branch" command has +changed, because this time you need to only merge changes since the last +merge point. + +When you have finished with your branch and merged all the changes +back to HEAD, simply stop using the branch. diff --git a/gpxe/src/README.pixify b/gpxe/src/README.pixify new file mode 100644 index 00000000..9aef25d9 --- /dev/null +++ b/gpxe/src/README.pixify @@ -0,0 +1,90 @@ +This file documents the driver changes needed to support use as part +of a PXE stack. + +PROPER WAY +========== + +1. The probe() routine. + +There are three additional fields that need to be filled in the nic +structure: ioaddr, irqno and irq. + + ioaddr is the base I/O address and seems to be for information only; + no use will be made of this value other than displaying it on the + screen. + + irqno must be the IRQ number for the NIC. For PCI NICs this can + simply be copied from pci->irq. + + irq is a function pointer, like poll and transmit. It must point to + the driver's irq() function. + +2. The poll() routine. + +This must take an additional parameter: "int retrieve". Calling +poll() with retrieve!=0 should function exactly as before. Calling +poll() with retrieve==0 indicates that poll() should check for the +presence of a packet to read, but must *not* read the packet. The +packet will be read by a subsequent call to poll() with retrieve!=0. + +The easiest way to implement this is to insert the line + if ( ! retrieve ) return 1; +between the "is there a packet ready" and the "fetch packet" parts of +the existing poll() routine. + +Care must be taken that a call to poll() with retrieve==0 does not +clear the NIC's "packet ready" status indicator, otherwise the +subsequent call to poll() with retrieve!=0 will fail because it will +think that there is no packet to read. + +poll() should also acknowledge and clear the NIC's "packet received" +interrupt. It does not need to worry about enabling/disabling +interrupts; this is taken care of by calls to the driver's irq() +routine. + +Etherboot will forcibly regenerate an interrupt if a packet remains +pending after all interrupts have been acknowledged. You can +therefore get away with having poll() just acknolwedge and clear all +NIC interrupts, without particularly worrying about exactly when this +should be done. + +3. The irq() routine. + +This is a new routine, with prototype + void DRIVER_irq ( struct nic *nic, irq_action_t action ); +"action" takes one of three possible values: ENABLE, DISABLE or FORCE. +ENABLE and DISABLE mean to enable/disable the NIC's "packet received" +interrupt. FORCE means that the NIC should be forced to generate a +fake "packet received" interrupt. + +If you are unable to implement FORCE, your NIC will not work when +being driven via the UNDI interface under heavy network traffic +conditions. Since Etherboot's UNDI driver (make bin/undi.zpxe) is the +only program known to use this interface, it probably doesn't really +matter. + + +QUICK AND DIRTY WAY +=================== + +It is possible to use the system timer interrupt (IRQ 0) rather than a +genuine NIC interrupt. Since there is a constant stream of timer +interrupts, the net upshot is a whole load of spurious "NIC" +interrupts that have no effect other than to cause unnecessary PXE API +calls. It's inefficient but it works. + +To achieve this, simply set nic->irqno=0 in probe() and point nic->irq +to a dummy routine that does nothing. Add the line + if ( ! retrieve ) return 1; +at the beginning of poll(), to prevent the packet being read (and +discarded) when poll() is called with retrieve==0; + + +UNCONVERTED DRIVERS +=================== + +Drivers that have not yet been converted should continue to function +when not used as part of a PXE stack, although there will be a +harmless compile-time warning about assignment from an incompatible +pointer type in the probe() function, since the prototype for the +poll() function is missing the "int retrieve" parameter. diff --git a/gpxe/src/arch/i386/Config b/gpxe/src/arch/i386/Config new file mode 100644 index 00000000..1c086ecc --- /dev/null +++ b/gpxe/src/arch/i386/Config @@ -0,0 +1,148 @@ +# -*- makefile -*- + +############################################################################## +############################################################################## +# +# IMPORTANT! +# +# The use of this file to set options that affect only single object +# files is deprecated, because changing anything in this file results +# in a complete rebuild, which is slow. All options are gradually +# being migrated to config.h, which does not suffer from this problem. +# +# Only options that affect the entire build (e.g. overriding the $(CC) +# Makefile variable) should be placed in here. +# +############################################################################## +############################################################################## + + +# Config for i386 Etherboot +# +# Do not delete the tag OptionDescription and /OptionDescription +# It is used to automatically generate the documentation. +# +# @OptionDescrition@ +# +# BIOS interface options: +# +# -DPCBIOS +# Compile in support for the normal pcbios +# -DLINUXBIOS +# Compile in support for LinuxBIOS +# -DBBS_BUT_NOT_PNP_COMPLIANT +# Some BIOSes claim to be PNP but they don't conform +# to the BBS spec which specifies that ES:DI must +# point to the string $PnP on entry. This option +# works around those. This option must be added to +# LCONFIG. +# -DNO_DELAYED_INT +# Take control as soon as BIOS detects the ROM. +# Normally hooks onto INT18H or INT19H. Use only if you +# have a very non-conformant BIOS as it bypasses +# BIOS initialisation of devices. This only works for +# legacy ROMs, i.e. PCI_PNP_HEADER not defined. +# This option was formerly called NOINT19H. +# -DBOOT_INT18H +# Etherboot normally hooks onto INT19H for legacy ROMs. +# You can choose to hook onto INT18H (BASIC interpreter +# entry point) instead. This entry point is used when +# all boot devices have been exhausted. This option must +# be added to LCONFIG. +# -DCONFIG_PCI_DIRECT +# Define this for PCI BIOSes that do not implement +# BIOS32 or not correctly. Normally not needed. +# Only works for BIOSes of a certain era. +# -DCONFIG_TSC_CURRTICKS +# Uses the processor time stamp counter instead of reading +# the BIOS time counter. This allows Etherboot to work +# even without a BIOS. This only works on late model +# 486s and above. +# -DCONFIG_NO_TIMER2 +# Some systems do not have timer2 implemented. +# If you have a RTC this will allow you to roughly calibrate +# it using outb instructions. +# +# Extended cpu options + +# -DCONFIG_X86_64 +# Compile in support for booting x86_64 64bit binaries. +# +# PXE loader options: +# +# -DPXELOADER_KEEP_ALL +# Prevent PXE loader (prefix) from unloading the +# PXE stack. You will want to use this if, for +# example, you are booting via PXE-on-floppy. +# You may want to use it under certain +# circumstances when using the Etherboot UNDI +# driver; these are complex and best practice is +# not yet established. +# +# Obscure options you probably don't need to touch: +# +# -DIGNORE_E820_MAP +# Ignore the memory map returned by the E820 BIOS +# call. May be necessary on some buggy BIOSes. +# -DT503_AUI +# Use AUI by default on 3c503 cards. +# -DFLATTEN_REAL_MODE +# Use 4GB segment limits when calling out to or +# returning to real-mode code. This is necessary to +# work around some buggy code (e.g. OpenBSD's pxeboot) +# that uses flat real-mode without being sufficiently +# paranoid about the volatility of its segment limits. + +# +# @/OptionDescription@ + +# BIOS select don't change unless you know what you are doing +# CFLAGS+= -DPCBIOS + +# Compile in k8/hammer support +# CFLAGS+= -DCONFIG_X86_64 + +# Options to make a version of Etherboot that will work under linuxBIOS. +# CFLAGS+= -DLINUXBIOS -DCONFIG_TSC_CURRTICKS -DCONSOLE_SERIAL -DCOMCONSOLE=0x3f8 -DCOMPRESERVE -DCONFIG_PCI_DIRECT -DELF_IMAGE + +# These options affect the loader that is prepended to the Etherboot image +# LCONFIG+= -DBBS_BUT_NOT_PNP_COMPLIANT +# LCONFIG+= -DBOOT_INT18H + +# Produce code that will work with OpenBSD's pxeboot +# CFLAGS+= -DFLATTEN_REAL_MODE + +CFLAGS+= -fstrength-reduce -fomit-frame-pointer -march=i386 +# Squeeze the code in as little space as possible. +# gcc3 needs a different syntax to gcc2 if you want to avoid spurious warnings. +GCC_VERSION = $(subst ., ,$(shell $(CC) -dumpversion)) +GCC_MAJORVERSION = $(firstword $(GCC_VERSION)) +ifeq ($(GCC_MAJORVERSION),2) +CFLAGS+= -malign-jumps=1 -malign-loops=1 -malign-functions=1 +else +CFLAGS+= -falign-jumps=1 -falign-loops=1 -falign-functions=1 +endif + +# this is almost always a win. the kernel uses it, too. +CFLAGS+= -mpreferred-stack-boundary=2 + +# use regparm for all functions - C functions called from assembly (or +# vice versa) need __cdecl now +CFLAGS+= -mregparm=3 + +# use -mrtd (same __cdecl requirements as above) +CFLAGS+= -mrtd + +# this is the logical complement to -mregparm=3. +# it doesn't currently buy us anything, but if anything ever tries +# to return small structures, let's be prepared +CFLAGS+= -freg-struct-return + +LDFLAGS+= -N --no-check-sections + +ifeq "$(shell uname -s)" "FreeBSD" +CFLAGS+= -DIMAGE_FREEBSD -DELF_IMAGE -DAOUT_IMAGE +endif + +# An alternate location for isolinux.bin can be set here +# ISOLINUX_BIN=/path/to/isolinux.bin diff --git a/gpxe/src/arch/i386/Makefile b/gpxe/src/arch/i386/Makefile new file mode 100644 index 00000000..da7976df --- /dev/null +++ b/gpxe/src/arch/i386/Makefile @@ -0,0 +1,107 @@ +# Locations of utilities +# +ISOLINUX_BIN = /usr/lib/syslinux/isolinux.bin + +# i386-specific directories containing source files +# +SRCDIRS += arch/i386/core arch/i386/transitions arch/i386/prefix +SRCDIRS += arch/i386/firmware/pcbios +SRCDIRS += arch/i386/image +SRCDIRS += arch/i386/drivers +SRCDIRS += arch/i386/drivers/bus +SRCDIRS += arch/i386/drivers/net +SRCDIRS += arch/i386/drivers/disk +SRCDIRS += arch/i386/interface/pcbios +SRCDIRS += arch/i386/interface/pxe + +# The various xxx_loader.c files are #included into core/loader.c and +# should not be compiled directly. +# +NON_AUTO_SRCS += arch/i386/core/aout_loader.c +NON_AUTO_SRCS += arch/i386/core/freebsd_loader.c +NON_AUTO_SRCS += arch/i386/core/wince_loader.c + +# unnrv2b.S is used to generate a 16-bit as well as a 32-bit object. +# +OBJS_unnrv2b = unnrv2b unnrv2b16 +CFLAGS_unnrv2b16 = -DCODE16 + +# We need to undefine the default macro "i386" when compiling .S +# files, otherwise ".arch i386" translates to ".arch 1"... +# +CFLAGS_S += -Ui386 + +# The i386 linker script +# +LDSCRIPT = arch/i386/scripts/i386.lds + +# Media types. +# +MEDIA += rom +MEDIA += pxe +MEDIA += kpxe +MEDIA += elf +MEDIA += elfd +MEDIA += lmelf +MEDIA += lmelfd +MEDIA += lkrn +MEDIA += bImage +MEDIA += dsk +MEDIA += nbi +MEDIA += hd +MEDIA += raw +MEDIA += com +MEDIA += exe + +# Special target for building Master Boot Record binary +$(BIN)/mbr.bin : $(BIN)/mbr.o + $(OBJCOPY) -O binary $< $@ + +# Some suffixes (e.g. %.fd0) are generated directly from other +# finished files (e.g. %.dsk), rather than having their own prefix. + +# rule to write disk images to /dev/fd0 +NON_AUTO_MEDIA += fd0 +%fd0 : %dsk + dd if=$< bs=512 conv=sync of=/dev/fd0 + sync + +# rule to create padded disk images +NON_AUTO_MEDIA += pdsk +%pdsk : %dsk + cp $< $@ + $(PERL) ./util/dskpad.pl $@ + +# rule to make a non-emulation ISO boot image +NON_AUTO_MEDIA += iso +%iso: %lkrn util/geniso + ISOLINUX_BIN=$(ISOLINUX_BIN) bash util/geniso $@ $< + +# rule to make a floppy emulation ISO boot image +NON_AUTO_MEDIA += liso +%liso: %lkrn util/genliso + bash util/genliso $@ $< + +# rule to make a USB disk image +$(BIN)/usbdisk.bin : $(BIN)/usbdisk.o + $(OBJCOPY) -O binary $< $@ + +NON_AUTO_MEDIA += usb +%usb: $(BIN)/usbdisk.bin %hd + cat $^ > $@ + +# Add NON_AUTO_MEDIA to the media list, so that they show up in the +# output of "make" +# +MEDIA += $(NON_AUTO_MEDIA) + +# Shortcut to allow typing just +# make bin-kir/% +# rather than +# make -f arch/i386/kir-Makefile bin-kir/% +# for building a KEEP_IT_REAL flavour. +# +$(BIN)-kir/% : kir-target + $(MAKE) -f arch/i386/kir-Makefile $(MAKECMDGOALS) + +.PHONY : kir-target diff --git a/gpxe/src/arch/i386/README.i386 b/gpxe/src/arch/i386/README.i386 new file mode 100644 index 00000000..b9b79cc4 --- /dev/null +++ b/gpxe/src/arch/i386/README.i386 @@ -0,0 +1,197 @@ +Etherboot/NILO i386 initialisation path and external call interface +=================================================================== + +1. Background + +GCC compiles 32-bit code. It is capable of producing +position-independent code, but the resulting binary is about 25% +bigger than the corresponding fixed-position code. Since one main use +of Etherboot is as firmware to be burned into an EPROM, code size must +be kept as small as possible. + +This means that we want to compile fixed-position code with GCC, and +link it to have a predetermined start address. The problem then is +that we must know the address that the code will be loaded to when it +runs. There are several ways to solve this: + +1. Pick an address, link the code with this start address, then make + sure that the code gets loaded at that location. This is + problematic, because we may pick an address that we later end up + wanting to use to load the operating system that we're booting. + +2. Pick an address, link the code with this start address, then set up + virtual addressing so that the virtual addresses match the + link-time addresses regardless of the real physical address that + the code is loaded to. This enables us to relocate Etherboot to + the top of high memory, where it will be out of the way of any + loading operating system. + +3. Link the code with a text start address of zero and a data start + address also of zero. Use 16-bit real mode and the + quasi-position-independence it gives you via segment addressing. + Doing this requires that we generate 16-bit code, rather than + 32-bit code, and restricts us to a maximum of 64kB in each segment. + +There are other possible approaches (e.g. including a relocation table +and code that performs standard dynamic relocation), but the three +options listed above are probably the best available. + +Etherboot can be invoked in a variety of ways (ROM, floppy, as a PXE +NBP, etc). Several of these ways involve control being passed to +Etherboot with the CPU in 16-bit real mode. Some will involve the CPU +being in 32-bit protected mode, and there's an outside chance that +some may involve the CPU being in 16-bit protected mode. We will +almost certainly have to effect a CPU mode change in order to reach +the mode we want to be in to execute the C code. + +Additionally, Etherboot may wish to call external routines, such as +BIOS interrupts, which must be called in 16-bit real mode. When +providing a PXE API, Etherboot must provide a mechanism for external +code to call it from 16-bit real mode. + +Not all i386 builds of Etherboot will want to make real-mode calls. +For example, when built for LinuxBIOS rather than the standard PCBIOS, +no real-mode calls are necessary. + +For the ultimate in PXE compatibility, we may want to build Etherboot +to run permanently in real mode. + +There is a wide variety of potential combinations of mode switches +that we may wish to implement. There are additional complications, +such as the inability to access a high-memory stack when running in +real mode. + +2. Transition libraries + +To handle all these various combinations of mode switches, we have +several "transition" libraries in Etherboot. We also have the concept +of an "internal" and an "external" environment. The internal +environment is the environment within which we can execute C code. +The external environment is the environment of whatever external code +we're trying to interface to, such as the system BIOS or a PXE NBP. + +As well as having a separate addressing scheme, the internal +environment also has a separate stack. + +The transition libraries are: + +a) librm + +librm handles transitions between an external 16-bit real-mode +environment and an internal 32-bit protected-mode environment with +virtual addresses. + +b) libkir + +libkir handles transitions between an external 16-bit real-mode (or +16:16 or 16:32 protected-mode) environment and an internal 16-bit +real-mode (or 16:16 protected-mode) environment. + +c) libpm + +libpm handles transitions between an external 32-bit protected-mode +environment with flat physical addresses and an internal 32-bit +protected-mode environment with virtual addresses. + +The transition libraries handle the transitions required when +Etherboot is started up for the first time, the transitions required +to execute any external code, and the transitions required when +Etherboot exits (if it exits). When Etherboot provides a PXE API, +they also handle the transitions required when a PXE client makes a +PXE API call to Etherboot. + +Etherboot may use multiple transition libraries. For example, an +Etherboot ELF image does not require librm for its initial transitions +from prefix to runtime, but may require librm for calling external +real-mode functions. + +3. Setup and initialisation + +Etherboot is conceptually divided into the prefix, the decompressor, +and the runtime image. (For non-compressed images, the decompressor +is a no-op.) The complete image comprises all three parts and is +distinct from the runtime image, which exclude the prefix and the +decompressor. + +The prefix does several tasks: + + Load the complete image into memory. (For example, the floppy + prefix issues BIOS calls to load the remainder of the complete image + from the floppy disk into RAM, and the ISA ROM prefix copies the ROM + contents into RAM for faster access.) + + Call the decompressor, if the runtime image is compressed. This + decompresses the runtime image. + + Call the runtime image's setup() routine. This is a routine + implemented in assembly code which sets up the internal environment + so that C code can execute. + + Call the runtime image's arch_initialise() routine. This is a + routine implemented in C which does some basic startup tasks, such + as initialising the console device, obtaining a memory map and + relocating the runtime image to high memory. + + Call the runtime image's arch_main() routine. This records the exit + mechanism requested by the prefix and calls main(). (The prefix + needs to register an exit mechanism because by the time main() + returns, the memory occupied by the prefix has most likely been + overwritten.) + +When acting as a PXE ROM, the ROM prefix contains an UNDI loader +routine in addition to its usual code. The UNDI loader performs a +similar sequence of steps: + + Load the complete image into memory. + + Call the decompressor. + + Call the runtime image's setup() routine. + + Call the runtime image's arch_initialise() routine. + + Call the runtime image's install_pxe_stack() routine. + + Return to caller. + +The runtime image's setup() routine will perform the following steps: + + Switch to the internal environment using an appropriate transition + library. This will record the parameters of the external + environment. + + Set up the internal environment: load a stack, and set up a GDT for + virtual addressing if virtual addressing is to be used. + + Switch back to the external environment using the transition + library. This will record the parameters of the internal + environment. + +Once the setup() routine has returned, the internal environment has been +set up ready for C code to run. The prefix can call C routines using +a function from the transition library. + +The runtime image's arch_initialise() routine will perform the +following steps: + + Zero the bss + + Initialise the console device(s) and print a welcome message. + + Obtain a memory map via the INT 15,E820 BIOS call or suitable + fallback mechanism. [not done if libkir is being used] + + Relocate the runtime image to the top of high memory. [not done if + libkir is being used] + + Install librm to base memory. [done only if librm is being used] + + Call initialise(). + + Return to the prefix, setting registers to indicate to the prefix + the new location of the transition library, if applicable. Which + registers these are is specific to the transition library being + used. + +Once the arch_initialise() routine has returned, the prefix will +probably call arch_main(). diff --git a/gpxe/src/arch/i386/core/aout_loader.c b/gpxe/src/arch/i386/core/aout_loader.c new file mode 100644 index 00000000..f85620e9 --- /dev/null +++ b/gpxe/src/arch/i386/core/aout_loader.c @@ -0,0 +1,144 @@ +/* a.out */ +struct exec { + unsigned long a_midmag; /* flags<<26 | mid<<16 | magic */ + unsigned long a_text; /* text segment size */ + unsigned long a_data; /* initialized data size */ + unsigned long a_bss; /* uninitialized data size */ + unsigned long a_syms; /* symbol table size */ + unsigned long a_entry; /* entry point */ + unsigned long a_trsize; /* text relocation size */ + unsigned long a_drsize; /* data relocation size */ +}; + +struct aout_state { + struct exec head; + unsigned long curaddr; + int segment; /* current segment number, -1 for none */ + unsigned long loc; /* start offset of current block */ + unsigned long skip; /* padding to be skipped to current segment */ + unsigned long toread; /* remaining data to be read in the segment */ +}; + +static struct aout_state astate; + +static sector_t aout_download(unsigned char *data, unsigned int len, int eof); +static inline os_download_t aout_probe(unsigned char *data, unsigned int len) +{ + unsigned long start, mid, end, istart, iend; + if (len < sizeof(astate.head)) { + return 0; + } + memcpy(&astate.head, data, sizeof(astate.head)); + if ((astate.head.a_midmag & 0xffff) != 0x010BL) { + return 0; + } + + printf("(a.out"); + aout_freebsd_probe(); + printf(")... "); + /* Check the aout image */ + start = astate.head.a_entry; + mid = (((start + astate.head.a_text) + 4095) & ~4095) + astate.head.a_data; + end = ((mid + 4095) & ~4095) + astate.head.a_bss; + istart = 4096; + iend = istart + (mid - start); + if (!prep_segment(start, mid, end, istart, iend)) + return dead_download; + astate.segment = -1; + astate.loc = 0; + astate.skip = 0; + astate.toread = 0; + return aout_download; +} + +static sector_t aout_download(unsigned char *data, unsigned int len, int eof) +{ + unsigned int offset; /* working offset in the current data block */ + + offset = 0; + +#ifdef AOUT_LYNX_KDI + astate.segment++; + if (astate.segment == 0) { + astate.curaddr = 0x100000; + astate.head.a_entry = astate.curaddr + 0x20; + } + memcpy(phys_to_virt(astate.curaddr), data, len); + astate.curaddr += len; + return 0; +#endif + + do { + if (astate.segment != -1) { + if (astate.skip) { + if (astate.skip >= len - offset) { + astate.skip -= len - offset; + break; + } + offset += astate.skip; + astate.skip = 0; + } + + if (astate.toread) { + if (astate.toread >= len - offset) { + memcpy(phys_to_virt(astate.curaddr), data+offset, + len - offset); + astate.curaddr += len - offset; + astate.toread -= len - offset; + break; + } + memcpy(phys_to_virt(astate.curaddr), data+offset, astate.toread); + offset += astate.toread; + astate.toread = 0; + } + } + + /* Data left, but current segment finished - look for the next + * segment. This is quite simple for a.out files. */ + astate.segment++; + switch (astate.segment) { + case 0: + /* read text */ + astate.curaddr = astate.head.a_entry; + astate.skip = 4096; + astate.toread = astate.head.a_text; + break; + case 1: + /* read data */ + /* skip and curaddr may be wrong, but I couldn't find + * examples where this failed. There is no reasonable + * documentation for a.out available. */ + astate.skip = ((astate.curaddr + 4095) & ~4095) - astate.curaddr; + astate.curaddr = (astate.curaddr + 4095) & ~4095; + astate.toread = astate.head.a_data; + break; + case 2: + /* initialize bss and start kernel */ + astate.curaddr = (astate.curaddr + 4095) & ~4095; + astate.skip = 0; + astate.toread = 0; + memset(phys_to_virt(astate.curaddr), '\0', astate.head.a_bss); + goto aout_startkernel; + default: + break; + } + } while (offset < len); + + astate.loc += len; + + if (eof) { + unsigned long entry; + +aout_startkernel: + entry = astate.head.a_entry; + done(1); + + aout_freebsd_boot(); +#ifdef AOUT_LYNX_KDI + xstart32(entry); +#endif + printf("unexpected a.out variant\n"); + longjmp(restart_etherboot, -2); + } + return 0; +} diff --git a/gpxe/src/arch/i386/core/basemem_packet.c b/gpxe/src/arch/i386/core/basemem_packet.c new file mode 100644 index 00000000..64e0bcc1 --- /dev/null +++ b/gpxe/src/arch/i386/core/basemem_packet.c @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** + * @file + * + * Packet buffer in base memory. Used by various components which + * need to pass packets to and from external real-mode code. + * + */ + +#include <basemem_packet.h> + +#undef basemem_packet +char __bss16_array ( basemem_packet, [BASEMEM_PACKET_LEN] ); diff --git a/gpxe/src/arch/i386/core/cpu.c b/gpxe/src/arch/i386/core/cpu.c new file mode 100644 index 00000000..c24fa4e6 --- /dev/null +++ b/gpxe/src/arch/i386/core/cpu.c @@ -0,0 +1,73 @@ +#include <stdint.h> +#include <string.h> +#include <cpu.h> + +/** @file + * + * CPU identification + * + */ + +/** + * Test to see if CPU flag is changeable + * + * @v flag Flag to test + * @ret can_change Flag is changeable + */ +static inline int flag_is_changeable ( unsigned int flag ) { + uint32_t f1, f2; + + __asm__ ( "pushfl\n\t" + "pushfl\n\t" + "popl %0\n\t" + "movl %0,%1\n\t" + "xorl %2,%0\n\t" + "pushl %0\n\t" + "popfl\n\t" + "pushfl\n\t" + "popl %0\n\t" + "popfl\n\t" + : "=&r" ( f1 ), "=&r" ( f2 ) + : "ir" ( flag ) ); + + return ( ( ( f1 ^ f2 ) & flag ) != 0 ); +} + +/** + * Get CPU information + * + * @v cpu CPU information structure to fill in + */ +void get_cpuinfo ( struct cpuinfo_x86 *cpu ) { + unsigned int cpuid_level; + unsigned int cpuid_extlevel; + unsigned int discard_1, discard_2, discard_3; + + memset ( cpu, 0, sizeof ( *cpu ) ); + + /* Check for CPUID instruction */ + if ( ! flag_is_changeable ( X86_EFLAGS_ID ) ) { + DBG ( "CPUID not supported\n" ); + return; + } + + /* Get features, if present */ + cpuid ( 0x00000000, &cpuid_level, &discard_1, + &discard_2, &discard_3 ); + if ( cpuid_level >= 0x00000001 ) { + cpuid ( 0x00000001, &discard_1, &discard_2, + &discard_3, &cpu->features ); + } else { + DBG ( "CPUID cannot return capabilities\n" ); + } + + /* Get 64-bit features, if present */ + cpuid ( 0x80000000, &cpuid_extlevel, &discard_1, + &discard_2, &discard_3 ); + if ( ( cpuid_extlevel & 0xffff0000 ) == 0x80000000 ) { + if ( cpuid_extlevel >= 0x80000001 ) { + cpuid ( 0x80000001, &discard_1, &discard_2, + &discard_3, &cpu->amd_features ); + } + } +} diff --git a/gpxe/src/arch/i386/core/etherboot.prefix.lds b/gpxe/src/arch/i386/core/etherboot.prefix.lds new file mode 100644 index 00000000..3550a2a3 --- /dev/null +++ b/gpxe/src/arch/i386/core/etherboot.prefix.lds @@ -0,0 +1,100 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) + +ENTRY(_prefix_start) +SECTIONS { + /* Prefix */ + .prefix : { + _verbatim_start = . ; + _prefix_start = . ; + *(.prefix) + . = ALIGN(16); + _prefix_end = . ; + } = 0x9090 + _prefix_size = _prefix_end - _prefix_start; + + .text.nocompress : { + *(.prefix.udata) + } = 0x9090 + + decompress_to = . ; + .prefix.zdata : { + _compressed = . ; + *(.prefix.zdata) + _compressed_end = . ; + } + _compressed_size = _compressed_end - _compressed; + + . = ALIGN(16); + _verbatim_end = . ; + + + /* Size of the core of etherboot in memory */ + _base_size = _end - _text; + + /* _prefix_size is the length of the non-core etherboot prefix */ + _prefix_size = _prefix_end - _prefix_start; + + /* _verbatim_size is the actual amount that has to be copied to base memory */ + _verbatim_size = _verbatim_end - _verbatim_start; + + /* _image_size is the amount of base memory needed to run */ + _image_size = _base_size + _prefix_size; + + /* Standard sizes rounded up to paragraphs */ + _prefix_size_pgh = (_prefix_size + 15) / 16; + _verbatim_size_pgh = (_verbatim_size + 15) / 16; + _image_size_pgh = (_image_size + 15) / 16 ; + + /* Standard sizes in sectors */ + _prefix_size_sct = (_prefix_size + 511) / 512; + _verbatim_size_sct = (_verbatim_size + 511) / 512; + _image_size_sct = (_image_size + 511) / 512; + + /* Symbol offsets and sizes for the exe prefix */ + _exe_hdr_size = 32; + _exe_size = _verbatim_size; /* Should this be - 32 to exclude the header? */ + _exe_size_tail = (_exe_size) % 512; + _exe_size_pages = ((_exe_size) + 511) / 512; + _exe_bss_size = ((_image_size - _verbatim_size) + 15) / 16; + _exe_ss_offset = (_stack_offset + _prefix_size - _exe_hdr_size + 15) / 16 ; + + /* This is where we copy the compressed image before decompression. + * Prepare to decompress in place. The end mark is about 8.25 bytes long, + * and the worst case symbol is about 16.5 bytes long. Therefore + * We need to reserve at least 25 bytes of slack here. + * Currently I reserve 2048 bytes of just slack to be safe :) + * 2048 bytes easily falls within the BSS (the defualt stack is 4096 bytes) + * so we really are decompressing in place. + * + * Hmm. I missed a trick. In the very worst case (no compression) + * the encoded data is 9/8 the size as it started out so to be completely + * safe I need to be 1/8 of the uncompressed code size past the end. + * This will still fit compfortably into our bss in any conceivable scenario. + */ + _compressed_copy = _edata + _prefix_size - _compressed_size + + /* The amount to overflow _edata */ + MAX( ((_edata - _text + 7) / 8) , 2016 ) + 32; + _assert = ASSERT( ( _compressed_copy - _prefix_size ) < _ebss , "Cannot decompress in place" ) ; + + decompress = DEFINED(decompress) ? decompress : 0; + /DISCARD/ : { + *(.comment) + *(.note) + } + + /* Symbols used by the prefixes whose addresses are inconvinient + * to compute, at runtime in the code. + */ + image_basemem_size = DEFINED(image_basemem_size)? image_basemem_size : 65536; + image_basemem = DEFINED(image_basemem)? image_basemem : 65536; + _prefix_real_to_prot = _real_to_prot + _prefix_size ; + _prefix_prot_to_real = _prot_to_real + _prefix_size ; + _prefix_image_basemem_size = image_basemem_size + _prefix_size ; + _prefix_image_basemem = image_basemem + _prefix_size ; + _prefix_rm_in_call = _rm_in_call + _prefix_size ; + _prefix_in_call = _in_call + _prefix_size ; + _prefix_rom = rom + _prefix_size ; + _prefix_rm_etherboot_location = rm_etherboot_location + _prefix_size ; + _prefix_stack_end = _stack_end + _prefix_size ; +} diff --git a/gpxe/src/arch/i386/core/freebsd_loader.c b/gpxe/src/arch/i386/core/freebsd_loader.c new file mode 100644 index 00000000..464f6d93 --- /dev/null +++ b/gpxe/src/arch/i386/core/freebsd_loader.c @@ -0,0 +1,377 @@ +/* bootinfo */ +#define BOOTINFO_VERSION 1 +#define NODEV (-1) /* non-existent device */ +#define PAGE_SHIFT 12 /* LOG2(PAGE_SIZE) */ +#define PAGE_SIZE (1<<PAGE_SHIFT) /* bytes/page */ +#define PAGE_MASK (PAGE_SIZE-1) +#define N_BIOS_GEOM 8 + +struct bootinfo { + unsigned int bi_version; + const unsigned char *bi_kernelname; + struct nfs_diskless *bi_nfs_diskless; + /* End of fields that are always present. */ +#define bi_endcommon bi_n_bios_used + unsigned int bi_n_bios_used; + unsigned long bi_bios_geom[N_BIOS_GEOM]; + unsigned int bi_size; + unsigned char bi_memsizes_valid; + unsigned char bi_pad[3]; + unsigned long bi_basemem; + unsigned long bi_extmem; + unsigned long bi_symtab; + unsigned long bi_esymtab; + /* Note that these are in the FreeBSD headers but were not here... */ + unsigned long bi_kernend; /* end of kernel space */ + unsigned long bi_envp; /* environment */ + unsigned long bi_modulep; /* preloaded modules */ +}; + +static struct bootinfo bsdinfo; + +#ifdef ELF_IMAGE +static Elf32_Shdr *shdr; /* To support the FreeBSD kludge! */ +static Address symtab_load; +static Address symstr_load; +static int symtabindex; +static int symstrindex; +#endif + +static enum { + Unknown, Tagged, Aout, Elf, Aout_FreeBSD, Elf_FreeBSD, +} image_type = Unknown; + +static unsigned int off; + + +#ifdef ELF_IMAGE +static void elf_freebsd_probe(void) +{ + image_type = Elf; + if ( (estate.e.elf32.e_entry & 0xf0000000) && + (estate.e.elf32.e_type == ET_EXEC)) + { + image_type = Elf_FreeBSD; + printf("/FreeBSD"); + off = -(estate.e.elf32.e_entry & 0xff000000); + estate.e.elf32.e_entry += off; + } + /* Make sure we have a null to start with... */ + shdr = 0; + + /* Clear the symbol index values... */ + symtabindex = -1; + symstrindex = -1; + + /* ...and the load addresses of the symbols */ + symtab_load = 0; + symstr_load = 0; +} + +static void elf_freebsd_fixup_segment(void) +{ + if (image_type == Elf_FreeBSD) { + estate.p.phdr32[estate.segment].p_paddr += off; + } +} + +static void elf_freebsd_find_segment_end(void) +{ + /* Count the bytes read even for the last block + * as we will need to know where the last block + * ends in order to load the symbols correctly. + * (plus it could be useful elsewhere...) + * Note that we need to count the actual size, + * not just the end of the disk image size. + */ + estate.curaddr += + (estate.p.phdr32[estate.segment].p_memsz - + estate.p.phdr32[estate.segment].p_filesz); +} + +static int elf_freebsd_debug_loader(unsigned int offset) +{ + /* No more segments to be loaded - time to start the + * nasty state machine to support the loading of + * FreeBSD debug symbols due to the fact that FreeBSD + * uses/exports the kernel's debug symbols in order + * to make much of the system work! Amazing (arg!) + * + * We depend on the fact that for the FreeBSD kernel, + * there is only one section of debug symbols and that + * the section is after all of the loaded sections in + * the file. This assumes a lot but is somewhat required + * to make this code not be too annoying. (Where do you + * load symbols when the code has not loaded yet?) + * Since this function is actually just a callback from + * the network data transfer code, we need to be able to + * work with the data as it comes in. There is no chance + * for doing a seek other than forwards. + * + * The process we use is to first load the section + * headers. Once they are loaded (shdr != 0) we then + * look for where the symbol table and symbol table + * strings are and setup some state that we found + * them and fall into processing the first one (which + * is the symbol table) and after that has been loaded, + * we try the symbol strings. Note that the order is + * actually required as the memory image depends on + * the symbol strings being loaded starting at the + * end of the symbol table. The kernel assumes this + * layout of the image. + * + * At any point, if we get to the end of the load file + * or the section requested is earlier in the file than + * the current file pointer, we just end up falling + * out of this and booting the kernel without this + * information. + */ + + /* Make sure that the next address is long aligned... */ + /* Assumes size of long is a power of 2... */ + estate.curaddr = (estate.curaddr + sizeof(long) - 1) & ~(sizeof(long) - 1); + + /* If we have not yet gotten the shdr loaded, try that */ + if (shdr == 0) + { + estate.toread = estate.e.elf32.e_shnum * estate.e.elf32.e_shentsize; + estate.skip = estate.e.elf32.e_shoff - (estate.loc + offset); + if (estate.toread) + { +#if ELF_DEBUG + printf("shdr *, size %lX, curaddr %lX\n", + estate.toread, estate.curaddr); +#endif + + /* Start reading at the curaddr and make that the shdr */ + shdr = (Elf32_Shdr *)phys_to_virt(estate.curaddr); + + /* Start to read... */ + return 1; + } + } + else + { + /* We have the shdr loaded, check if we have found + * the indexs where the symbols are supposed to be */ + if ((symtabindex == -1) && (symstrindex == -1)) + { + int i; + /* Make sure that the address is page aligned... */ + /* Symbols need to start in their own page(s)... */ + estate.curaddr = (estate.curaddr + 4095) & ~4095; + + /* Need to make new indexes... */ + for (i=0; i < estate.e.elf32.e_shnum; i++) + { + if (shdr[i].sh_type == SHT_SYMTAB) + { + int j; + for (j=0; j < estate.e.elf32.e_phnum; j++) + { + /* Check only for loaded sections */ + if ((estate.p.phdr32[j].p_type | 0x80) == (PT_LOAD | 0x80)) + { + /* Only the extra symbols */ + if ((shdr[i].sh_offset >= estate.p.phdr32[j].p_offset) && + ((shdr[i].sh_offset + shdr[i].sh_size) <= + (estate.p.phdr32[j].p_offset + estate.p.phdr32[j].p_filesz))) + { + shdr[i].sh_offset=0; + shdr[i].sh_size=0; + break; + } + } + } + if ((shdr[i].sh_offset != 0) && (shdr[i].sh_size != 0)) + { + symtabindex = i; + symstrindex = shdr[i].sh_link; + } + } + } + } + + /* Check if we have a symbol table index and have not loaded it */ + if ((symtab_load == 0) && (symtabindex >= 0)) + { + /* No symbol table yet? Load it first... */ + + /* This happens to work out in a strange way. + * If we are past the point in the file already, + * we will skip a *large* number of bytes which + * ends up bringing us to the end of the file and + * an old (default) boot. Less code and lets + * the state machine work in a cleaner way but this + * is a nasty side-effect trick... */ + estate.skip = shdr[symtabindex].sh_offset - (estate.loc + offset); + + /* And we need to read this many bytes... */ + estate.toread = shdr[symtabindex].sh_size; + + if (estate.toread) + { +#if ELF_DEBUG + printf("db sym, size %lX, curaddr %lX\n", + estate.toread, estate.curaddr); +#endif + /* Save where we are loading this... */ + symtab_load = estate.curaddr; + + *((long *)phys_to_virt(estate.curaddr)) = estate.toread; + estate.curaddr += sizeof(long); + + /* Start to read... */ + return 1; + } + } + else if ((symstr_load == 0) && (symstrindex >= 0)) + { + /* We have already loaded the symbol table, so + * now on to the symbol strings... */ + + + /* Same nasty trick as above... */ + estate.skip = shdr[symstrindex].sh_offset - (estate.loc + offset); + + /* And we need to read this many bytes... */ + estate.toread = shdr[symstrindex].sh_size; + + if (estate.toread) + { +#if ELF_DEBUG + printf("db str, size %lX, curaddr %lX\n", + estate.toread, estate.curaddr); +#endif + /* Save where we are loading this... */ + symstr_load = estate.curaddr; + + *((long *)phys_to_virt(estate.curaddr)) = estate.toread; + estate.curaddr += sizeof(long); + + /* Start to read... */ + return 1; + } + } + } + /* all done */ + return 0; +} + +static void elf_freebsd_boot(unsigned long entry) +{ + if (image_type != Elf_FreeBSD) + return; + + memset(&bsdinfo, 0, sizeof(bsdinfo)); + bsdinfo.bi_basemem = meminfo.basememsize; + bsdinfo.bi_extmem = meminfo.memsize; + bsdinfo.bi_memsizes_valid = 1; + bsdinfo.bi_version = BOOTINFO_VERSION; + bsdinfo.bi_kernelname = virt_to_phys(KERNEL_BUF); + bsdinfo.bi_nfs_diskless = NULL; + bsdinfo.bi_size = sizeof(bsdinfo); +#define RB_BOOTINFO 0x80000000 /* have `struct bootinfo *' arg */ + if(freebsd_kernel_env[0] != '\0'){ + freebsd_howto |= RB_BOOTINFO; + bsdinfo.bi_envp = (unsigned long)freebsd_kernel_env; + } + + /* Check if we have symbols loaded, and if so, + * made the meta_data needed to pass those to + * the kernel. */ + if ((symtab_load !=0) && (symstr_load != 0)) + { + unsigned long *t; + + bsdinfo.bi_symtab = symtab_load; + + /* End of symbols (long aligned...) */ + /* Assumes size of long is a power of 2... */ + bsdinfo.bi_esymtab = (symstr_load + + sizeof(long) + + *((long *)phys_to_virt(symstr_load)) + + sizeof(long) - 1) & ~(sizeof(long) - 1); + + /* Where we will build the meta data... */ + t = phys_to_virt(bsdinfo.bi_esymtab); + +#if ELF_DEBUG + printf("Metadata at %lX\n",t); +#endif + + /* Set up the pointer to the memory... */ + bsdinfo.bi_modulep = virt_to_phys(t); + + /* The metadata structure is an array of 32-bit + * words where we store some information about the + * system. This is critical, as FreeBSD now looks + * only for the metadata for the extended symbol + * information rather than in the bootinfo. + */ + /* First, do the kernel name and the kernel type */ + /* Note that this assumed x86 byte order... */ + + /* 'kernel\0\0' */ + *t++=MODINFO_NAME; *t++= 7; *t++=0x6E72656B; *t++=0x00006C65; + + /* 'elf kernel\0\0' */ + *t++=MODINFO_TYPE; *t++=11; *t++=0x20666C65; *t++=0x6E72656B; *t++ = 0x00006C65; + + /* Now the symbol start/end - note that they are + * here in local/physical address - the Kernel + * boot process will relocate the addresses. */ + *t++=MODINFOMD_SSYM | MODINFO_METADATA; *t++=sizeof(*t); *t++=bsdinfo.bi_symtab; + *t++=MODINFOMD_ESYM | MODINFO_METADATA; *t++=sizeof(*t); *t++=bsdinfo.bi_esymtab; + + *t++=MODINFO_END; *t++=0; /* end of metadata */ + + /* Since we have symbols we need to make + * sure that the kernel knows its own end + * of memory... It is not _end but after + * the symbols and the metadata... */ + bsdinfo.bi_kernend = virt_to_phys(t); + + /* Signal locore.s that we have a valid bootinfo + * structure that was completely filled in. */ + freebsd_howto |= 0x80000000; + } + + xstart32(entry, freebsd_howto, NODEV, 0, 0, 0, + virt_to_phys(&bsdinfo), 0, 0, 0); + longjmp(restart_etherboot, -2); +} +#endif + +#ifdef AOUT_IMAGE +static void aout_freebsd_probe(void) +{ + image_type = Aout; + if (((astate.head.a_midmag >> 16) & 0xffff) == 0) { + /* Some other a.out variants have a different + * value, and use other alignments (e.g. 1K), + * not the 4K used by FreeBSD. */ + image_type = Aout_FreeBSD; + printf("/FreeBSD"); + off = -(astate.head.a_entry & 0xff000000); + astate.head.a_entry += off; + } +} + +static void aout_freebsd_boot(void) +{ + if (image_type == Aout_FreeBSD) { + memset(&bsdinfo, 0, sizeof(bsdinfo)); + bsdinfo.bi_basemem = meminfo.basememsize; + bsdinfo.bi_extmem = meminfo.memsize; + bsdinfo.bi_memsizes_valid = 1; + bsdinfo.bi_version = BOOTINFO_VERSION; + bsdinfo.bi_kernelname = virt_to_phys(KERNEL_BUF); + bsdinfo.bi_nfs_diskless = NULL; + bsdinfo.bi_size = sizeof(bsdinfo); + xstart32(astate.head.a_entry, freebsd_howto, NODEV, 0, 0, 0, + virt_to_phys(&bsdinfo), 0, 0, 0); + longjmp(restart_etherboot, -2); + } +} +#endif diff --git a/gpxe/src/arch/i386/core/gdbsym.c b/gpxe/src/arch/i386/core/gdbsym.c new file mode 100644 index 00000000..2da1a1bd --- /dev/null +++ b/gpxe/src/arch/i386/core/gdbsym.c @@ -0,0 +1,33 @@ +#include <stdio.h> +#include <gpxe/init.h> +#include <console.h> +#include <realmode.h> + +extern char __text[]; +extern char __rodata[]; +extern char __data[]; +extern char __bss[]; +extern char __text16[]; +extern char __data16[]; + + +static void gdb_symbol_line ( void ) { + printf ( "Commands to start up gdb:\n\n" ); + printf ( "gdb\n" ); + printf ( "target remote localhost:1234\n" ); + printf ( "set confirm off\n" ); + printf ( "add-symbol-file symbols %#lx", virt_to_phys ( __text ) ); + printf ( " -s .rodata %#lx", virt_to_phys ( __rodata ) ); + printf ( " -s .data %#lx", virt_to_phys ( __data ) ); + printf ( " -s .bss %#lx", virt_to_phys ( __bss ) ); + printf ( " -s .text16 %#x", ( ( rm_cs << 4 ) + (int)__text16 ) ); + printf ( " -s .data16 %#x", ( ( rm_ds << 4 ) + (int)__data16 ) ); + printf ( "\n" ); + printf ( "add-symbol-file symbols 0\n" ); + printf ( "set confirm on\n" ); + getkey(); +} + +struct startup_fn gdb_startup_fn __startup_fn ( STARTUP_NORMAL ) = { + .startup = gdb_symbol_line, +}; diff --git a/gpxe/src/arch/i386/core/i386_string.c b/gpxe/src/arch/i386/core/i386_string.c new file mode 100644 index 00000000..9917363a --- /dev/null +++ b/gpxe/src/arch/i386/core/i386_string.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** @file + * + * Optimised string operations + * + */ + +#include <string.h> + +/** + * Copy memory area + * + * @v dest Destination address + * @v src Source address + * @v len Length + * @ret dest Destination address + */ +__attribute__ (( regparm ( 3 ) )) void * __memcpy ( void *dest, + const void *src, + size_t len ) { + void *edi = dest; + const void *esi = src; + int discard_ecx; + + /* We often do large dword-aligned and dword-length block + * moves. Using movsl rather than movsb speeds these up by + * around 32%. + */ + if ( len >> 2 ) { + __asm__ __volatile__ ( "rep movsl" + : "=&D" ( edi ), "=&S" ( esi ), + "=&c" ( discard_ecx ) + : "0" ( edi ), "1" ( esi ), + "2" ( len >> 2 ) + : "memory" ); + } + if ( len & 0x02 ) { + __asm__ __volatile__ ( "movsw" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + } + if ( len & 0x01 ) { + __asm__ __volatile__ ( "movsb" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + } + return dest; +} diff --git a/gpxe/src/arch/i386/core/i386_timer.c b/gpxe/src/arch/i386/core/i386_timer.c new file mode 100644 index 00000000..8f90ae05 --- /dev/null +++ b/gpxe/src/arch/i386/core/i386_timer.c @@ -0,0 +1,89 @@ +/* + * arch/i386/core/i386_timer.c + * + * Use the "System Timer 2" to implement the udelay callback in + * the BIOS timer driver. Also used to calibrate the clock rate + * in the RTDSC timer driver. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, or (at + * your option) any later version. + */ + +#include <stddef.h> +#include <bits/timer2.h> +#include <gpxe/timer.h> +#include <io.h> + +/* Timers tick over at this rate */ +#define TIMER2_TICK_RATE 1193180U + +/* Parallel Peripheral Controller Port B */ +#define PPC_PORTB 0x61 + +/* Meaning of the port bits */ +#define PPCB_T2OUT 0x20 /* Bit 5 */ +#define PPCB_SPKR 0x02 /* Bit 1 */ +#define PPCB_T2GATE 0x01 /* Bit 0 */ + +/* Ports for the 8254 timer chip */ +#define TIMER2_PORT 0x42 +#define TIMER_MODE_PORT 0x43 + +/* Meaning of the mode bits */ +#define TIMER0_SEL 0x00 +#define TIMER1_SEL 0x40 +#define TIMER2_SEL 0x80 +#define READBACK_SEL 0xC0 + +#define LATCH_COUNT 0x00 +#define LOBYTE_ACCESS 0x10 +#define HIBYTE_ACCESS 0x20 +#define WORD_ACCESS 0x30 + +#define MODE0 0x00 +#define MODE1 0x02 +#define MODE2 0x04 +#define MODE3 0x06 +#define MODE4 0x08 +#define MODE5 0x0A + +#define BINARY_COUNT 0x00 +#define BCD_COUNT 0x01 + +static void load_timer2(unsigned int ticks) +{ + /* + * Now let's take care of PPC channel 2 + * + * Set the Gate high, program PPC channel 2 for mode 0, + * (interrupt on terminal count mode), binary count, + * load 5 * LATCH count, (LSB and MSB) to begin countdown. + * + * Note some implementations have a bug where the high bits byte + * of channel 2 is ignored. + */ + /* Set up the timer gate, turn off the speaker */ + /* Set the Gate high, disable speaker */ + outb((inb(PPC_PORTB) & ~PPCB_SPKR) | PPCB_T2GATE, PPC_PORTB); + /* binary, mode 0, LSB/MSB, Ch 2 */ + outb(TIMER2_SEL|WORD_ACCESS|MODE0|BINARY_COUNT, TIMER_MODE_PORT); + /* LSB of ticks */ + outb(ticks & 0xFF, TIMER2_PORT); + /* MSB of ticks */ + outb(ticks >> 8, TIMER2_PORT); +} + +static int timer2_running(void) +{ + return ((inb(PPC_PORTB) & PPCB_T2OUT) == 0); +} + +void i386_timer2_udelay(unsigned int usecs) +{ + load_timer2((usecs * TIMER2_TICK_RATE)/USECS_IN_SEC); + while (timer2_running()) + ; +} + diff --git a/gpxe/src/arch/i386/core/nap.c b/gpxe/src/arch/i386/core/nap.c new file mode 100644 index 00000000..12bb5699 --- /dev/null +++ b/gpxe/src/arch/i386/core/nap.c @@ -0,0 +1,12 @@ + +#include <realmode.h> +#include <bios.h> + +/************************************************************************** + * Save power by halting the CPU until the next interrupt + **************************************************************************/ +void cpu_nap ( void ) { + __asm__ __volatile__ ( REAL_CODE ( "sti\n\t" + "hlt\n\t" + "cli\n\t" ) : : ); +} diff --git a/gpxe/src/arch/i386/core/nulltrap.c b/gpxe/src/arch/i386/core/nulltrap.c new file mode 100644 index 00000000..3046fbec --- /dev/null +++ b/gpxe/src/arch/i386/core/nulltrap.c @@ -0,0 +1,51 @@ +#include <stdint.h> +#include <stdio.h> + +__attribute__ (( noreturn, section ( ".text.null_trap" ) )) +void null_function_trap ( void ) { + void *stack; + + /* 128 bytes of NOPs; the idea of this is that if something + * dereferences a NULL pointer and overwrites us, we at least + * have some chance of still getting to execute the printf() + * statement. + */ + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + __asm__ __volatile__ ( "nop ; nop ; nop ; nop" ); + + __asm__ __volatile__ ( "movl %%esp, %0" : "=r" ( stack ) ); + printf ( "NULL method called from %p (stack %p)\n", + __builtin_return_address ( 0 ), stack ); + DBG_HD ( stack, 256 ); + while ( 1 ) {} +} diff --git a/gpxe/src/arch/i386/core/pcibios.c b/gpxe/src/arch/i386/core/pcibios.c new file mode 100644 index 00000000..1c93e4be --- /dev/null +++ b/gpxe/src/arch/i386/core/pcibios.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <gpxe/pci.h> +#include <pcibios.h> +#include <realmode.h> + +/** @file + * + * PCI configuration space access via PCI BIOS + * + */ + +/** + * Determine maximum PCI bus number within system + * + * @ret max_bus Maximum bus number + */ +int pcibios_max_bus ( void ) { + int discard_a, discard_D; + uint8_t max_bus; + + __asm__ __volatile__ ( REAL_CODE ( "stc\n\t" + "int $0x1a\n\t" + "jnc 1f\n\t" + "xorw %%cx, %%cx\n\t" + "\n1:\n\t" ) + : "=c" ( max_bus ), "=a" ( discard_a ), + "=D" ( discard_D ) + : "a" ( PCIBIOS_INSTALLATION_CHECK >> 16 ), + "D" ( 0 ) + : "ebx", "edx" ); + + return max_bus; +} + +/** + * Read configuration space via PCI BIOS + * + * @v pci PCI device + * @v command PCI BIOS command + * @v value Value read + * @ret rc Return status code + */ +int pcibios_read ( struct pci_device *pci, uint32_t command, uint32_t *value ){ + int discard_b, discard_D; + int status; + + __asm__ __volatile__ ( REAL_CODE ( "stc\n\t" + "int $0x1a\n\t" + "jnc 1f\n\t" + "xorl %%eax, %%eax\n\t" + "decl %%eax\n\t" + "movl %%eax, %%ecx\n\t" + "\n1:\n\t" ) + : "=a" ( status ), "=b" ( discard_b ), + "=c" ( *value ), "=D" ( discard_D ) + : "a" ( command >> 16 ), "D" ( command ), + "b" ( PCI_BUSDEVFN ( pci->bus, pci->devfn ) ) + : "edx" ); + + return ( ( status >> 8 ) & 0xff ); +} + +/** + * Write configuration space via PCI BIOS + * + * @v pci PCI device + * @v command PCI BIOS command + * @v value Value to be written + * @ret rc Return status code + */ +int pcibios_write ( struct pci_device *pci, uint32_t command, uint32_t value ){ + int discard_b, discard_c, discard_D; + int status; + + __asm__ __volatile__ ( REAL_CODE ( "stc\n\t" + "int $0x1a\n\t" + "jnc 1f\n\t" + "movb $0xff, %%ah\n\t" + "\n1:\n\t" ) + : "=a" ( status ), "=b" ( discard_b ), + "=c" ( discard_c ), "=D" ( discard_D ) + : "a" ( command >> 16 ), "D" ( command ), + "b" ( PCI_BUSDEVFN ( pci->bus, pci->devfn ) ), + "c" ( value ) + : "edx" ); + + return ( ( status >> 8 ) & 0xff ); +} diff --git a/gpxe/src/arch/i386/core/pcidirect.c b/gpxe/src/arch/i386/core/pcidirect.c new file mode 100644 index 00000000..2ed8c2ad --- /dev/null +++ b/gpxe/src/arch/i386/core/pcidirect.c @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <gpxe/pci.h> +#include <pcidirect.h> + +/** @file + * + * PCI configuration space access via Type 1 accesses + * + */ + +/** + * Prepare for Type 1 PCI configuration space access + * + * @v pci PCI device + * @v where Location within PCI configuration space + */ +void pcidirect_prepare ( struct pci_device *pci, int where ) { + outl ( ( 0x80000000 | ( pci->bus << 16 ) | ( pci->devfn << 8 ) | + ( where & ~3 ) ), PCIDIRECT_CONFIG_ADDRESS ); +} + diff --git a/gpxe/src/arch/i386/core/pic8259.c b/gpxe/src/arch/i386/core/pic8259.c new file mode 100644 index 00000000..defe2e7d --- /dev/null +++ b/gpxe/src/arch/i386/core/pic8259.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <pic8259.h> + +/** @file + * + * Minimal support for the 8259 Programmable Interrupt Controller + * + */ + +/** + * Send non-specific EOI(s) + * + * @v irq IRQ number + * + * This seems to be inherently unsafe. + */ +static inline void send_nonspecific_eoi ( unsigned int irq ) { + DBG ( "Sending non-specific EOI for IRQ %d\n", irq ); + if ( irq >= IRQ_PIC_CUTOFF ) { + outb ( ICR_EOI_NON_SPECIFIC, PIC2_ICR ); + } + outb ( ICR_EOI_NON_SPECIFIC, PIC1_ICR ); +} + +/** + * Send specific EOI(s) + * + * @v irq IRQ number + */ +static inline void send_specific_eoi ( unsigned int irq ) { + DBG ( "Sending specific EOI for IRQ %d\n", irq ); + if ( irq >= IRQ_PIC_CUTOFF ) { + outb ( ( ICR_EOI_SPECIFIC | ICR_VALUE ( CHAINED_IRQ ) ), + ICR_REG ( CHAINED_IRQ ) ); + } + outb ( ( ICR_EOI_SPECIFIC | ICR_VALUE ( irq ) ), ICR_REG ( irq ) ); +} + +/** + * Send End-Of-Interrupt to the PIC + * + * @v irq IRQ number + */ +void send_eoi ( unsigned int irq ) { + send_specific_eoi ( irq ); +} diff --git a/gpxe/src/arch/i386/core/prefixudata.lds b/gpxe/src/arch/i386/core/prefixudata.lds new file mode 100644 index 00000000..1c76128e --- /dev/null +++ b/gpxe/src/arch/i386/core/prefixudata.lds @@ -0,0 +1,8 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS { + .prefix.udata : { + *(*) + } +} diff --git a/gpxe/src/arch/i386/core/prefixzdata.lds b/gpxe/src/arch/i386/core/prefixzdata.lds new file mode 100644 index 00000000..bf6ea977 --- /dev/null +++ b/gpxe/src/arch/i386/core/prefixzdata.lds @@ -0,0 +1,8 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS { + .prefix.zdata : { + *(*) + } +} diff --git a/gpxe/src/arch/i386/core/realmode.c b/gpxe/src/arch/i386/core/realmode.c new file mode 100644 index 00000000..9a77bd8a --- /dev/null +++ b/gpxe/src/arch/i386/core/realmode.c @@ -0,0 +1,23 @@ +/* Real-mode interface: C portions. + * + * Initial version by Michael Brown <mbrown@fensystems.co.uk>, January 2004. + */ + +#include "realmode.h" + +/* + * Copy data to/from base memory. + * + */ + +#ifdef KEEP_IT_REAL + +void memcpy_to_real ( segoff_t dest, void *src, size_t n ) { + +} + +void memcpy_from_real ( void *dest, segoff_t src, size_t n ) { + +} + +#endif /* KEEP_IT_REAL */ diff --git a/gpxe/src/arch/i386/core/relocate.c b/gpxe/src/arch/i386/core/relocate.c new file mode 100644 index 00000000..39d00b09 --- /dev/null +++ b/gpxe/src/arch/i386/core/relocate.c @@ -0,0 +1,163 @@ +#include <io.h> +#include <registers.h> +#include <gpxe/memmap.h> + +/* + * Originally by Eric Biederman + * + * Heavily modified by Michael Brown + * + */ + +/* + * The linker passes in the symbol _max_align, which is the alignment + * that we must preserve, in bytes. + * + */ +extern char _max_align[]; +#define max_align ( ( unsigned int ) _max_align ) + +/* Linker symbols */ +extern char _text[]; +extern char _end[]; + +/* within 1MB of 4GB is too close. + * MAX_ADDR is the maximum address we can easily do DMA to. + * + * Not sure where this constraint comes from, but kept it from Eric's + * old code - mcb30 + */ +#define MAX_ADDR (0xfff00000UL) + +/** + * Relocate Etherboot + * + * @v ix86 x86 register dump from prefix + * @ret ix86 x86 registers to return to prefix + * + * This finds a suitable location for Etherboot near the top of 32-bit + * address space, and returns the physical address of the new location + * to the prefix in %edi. + */ +__cdecl void relocate ( struct i386_all_regs *ix86 ) { + struct memory_map memmap; + unsigned long start, end, size, padded_size; + unsigned long new_start, new_end; + unsigned i; + + /* Get memory map and current location */ + get_memmap ( &memmap ); + start = virt_to_phys ( _text ); + end = virt_to_phys ( _end ); + size = ( end - start ); + padded_size = ( size + max_align - 1 ); + + DBG ( "Relocate: currently at [%lx,%lx)\n" + "...need %lx bytes for %d-byte alignment\n", + start, end, padded_size, max_align ); + + /* Walk through the memory map and find the highest address + * below 4GB that etherboot will fit into. Ensure etherboot + * lies entirely within a range with A20=0. This means that + * even if something screws up the state of the A20 line, the + * etherboot code is still visible and we have a chance to + * diagnose the problem. + */ + new_end = end; + for ( i = 0 ; i < memmap.count ; i++ ) { + struct memory_region *region = &memmap.regions[i]; + unsigned long r_start, r_end; + + DBG ( "Considering [%llx,%llx)\n", region->start, region->end); + + /* Truncate block to MAX_ADDR. This will be less than + * 4GB, which means that we can get away with using + * just 32-bit arithmetic after this stage. + */ + if ( region->start > MAX_ADDR ) { + DBG ( "...starts after MAX_ADDR=%lx\n", MAX_ADDR ); + continue; + } + r_start = region->start; + if ( region->end > MAX_ADDR ) { + DBG ( "...end truncated to MAX_ADDR=%lx\n", MAX_ADDR ); + r_end = MAX_ADDR; + } else { + r_end = region->end; + } + + /* Shrink the range down to use only even megabytes + * (i.e. A20=0). + */ + if ( ( r_end - 1 ) & 0x100000 ) { + /* If last byte that might be used (r_end-1) + * is in an odd megabyte, round down r_end to + * the top of the next even megabyte. + */ + r_end = ( r_end - 1 ) & ~0xfffff; + DBG ( "...end truncated to %lx " + "(avoid ending in odd megabyte)\n", + r_end ); + } else if ( ( r_end - size ) & 0x100000 ) { + /* If the last byte that might be used + * (r_end-1) is in an even megabyte, but the + * first byte that might be used (r_end-size) + * is an odd megabyte, round down to the top + * of the next even megabyte. + * + * Make sure that we don't accidentally wrap + * r_end below 0. + */ + if ( r_end > 0x100000 ) { + r_end = ( r_end - 0x100000 ) & ~0xfffff; + DBG ( "...end truncated to %lx " + "(avoid starting in odd megabyte)\n", + r_end ); + } + } + + DBG ( "...usable portion is [%lx,%lx)\n", r_start, r_end ); + + /* If we have rounded down r_end below r_ start, skip + * this block. + */ + if ( r_end < r_start ) { + DBG ( "...truncated to negative size\n" ); + continue; + } + + /* Check that there is enough space to fit in Etherboot */ + if ( ( r_end - r_start ) < size ) { + DBG ( "...too small (need %lx bytes)\n", size ); + continue; + } + + /* If the start address of the Etherboot we would + * place in this block is higher than the end address + * of the current highest block, use this block. + * + * Note that this avoids overlaps with the current + * Etherboot, as well as choosing the highest of all + * viable blocks. + */ + if ( ( r_end - size ) > new_end ) { + new_end = r_end; + DBG ( "...new best block found.\n" ); + } + } + + /* Calculate new location of Etherboot, and align it to the + * required alignemnt. + */ + new_start = new_end - padded_size; + new_start += ( start - new_start ) & ( max_align - 1 ); + new_end = new_start + size; + + DBG ( "Relocating from [%lx,%lx) to [%lx,%lx)\n", + start, end, new_start, new_end ); + + /* Let prefix know what to copy */ + ix86->regs.esi = start; + ix86->regs.edi = new_start; + ix86->regs.ecx = size; +} diff --git a/gpxe/src/arch/i386/core/setjmp.S b/gpxe/src/arch/i386/core/setjmp.S new file mode 100644 index 00000000..59a1b7cb --- /dev/null +++ b/gpxe/src/arch/i386/core/setjmp.S @@ -0,0 +1,40 @@ +/* setjmp and longjmp. Use of these functions is deprecated. */ + + .text + .arch i386 + .code32 + +/************************************************************************** +SETJMP - Save stack context for non-local goto +**************************************************************************/ + .globl setjmp +setjmp: + movl 4(%esp),%ecx /* jmpbuf */ + movl 0(%esp),%edx /* return address */ + movl %edx,0(%ecx) + movl %ebx,4(%ecx) + movl %esp,8(%ecx) + movl %ebp,12(%ecx) + movl %esi,16(%ecx) + movl %edi,20(%ecx) + movl $0,%eax + ret + +/************************************************************************** +LONGJMP - Non-local jump to a saved stack context +**************************************************************************/ + .globl longjmp +longjmp: + movl 4(%esp),%edx /* jumpbuf */ + movl 8(%esp),%eax /* result */ + movl 0(%edx),%ecx + movl 4(%edx),%ebx + movl 8(%edx),%esp + movl 12(%edx),%ebp + movl 16(%edx),%esi + movl 20(%edx),%edi + cmpl $0,%eax + jne 1f + movl $1,%eax +1: movl %ecx,0(%esp) + ret diff --git a/gpxe/src/arch/i386/core/stack.S b/gpxe/src/arch/i386/core/stack.S new file mode 100644 index 00000000..c2d138aa --- /dev/null +++ b/gpxe/src/arch/i386/core/stack.S @@ -0,0 +1,13 @@ + .arch i386 + +/**************************************************************************** + * Internal stack + **************************************************************************** + */ + .section ".stack" + .align 8 + .globl _stack +_stack: + .space 4096 + .globl _estack +_estack: diff --git a/gpxe/src/arch/i386/core/stack16.S b/gpxe/src/arch/i386/core/stack16.S new file mode 100644 index 00000000..3380a083 --- /dev/null +++ b/gpxe/src/arch/i386/core/stack16.S @@ -0,0 +1,13 @@ + .arch i386 + +/**************************************************************************** + * Internal stack + **************************************************************************** + */ + .section ".stack16" + .align 8 + .globl _stack16 +_stack16: + .space 4096 + .globl _estack16 +_estack16: diff --git a/gpxe/src/arch/i386/core/start16.lds b/gpxe/src/arch/i386/core/start16.lds new file mode 100644 index 00000000..544fc78f --- /dev/null +++ b/gpxe/src/arch/i386/core/start16.lds @@ -0,0 +1,8 @@ +/* When linking with an uncompressed image, these symbols are not + * defined so we provide them here. + */ + +__decompressor_uncompressed = 0 ; +__decompressor__start = 0 ; + +INCLUDE arch/i386/core/start16z.lds diff --git a/gpxe/src/arch/i386/core/start16z.lds b/gpxe/src/arch/i386/core/start16z.lds new file mode 100644 index 00000000..711bcf7b --- /dev/null +++ b/gpxe/src/arch/i386/core/start16z.lds @@ -0,0 +1,65 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) + +/* Linker-generated symbols are prefixed with a double underscore. + * Decompressor symbols are prefixed with __decompressor_. All other + * symbols are the same as in the original object file, i.e. the + * runtime addresses. + */ + +ENTRY(_start16) + +SECTIONS { + .text : { + *(.text) + } + .payload : { + __payload_start = .; + *(.data) + __payload_end = .; + } + + /* _payload_size is the size of the binary image appended to + * start16, in bytes. + */ + __payload_size = __payload_end - __payload_start ; + + /* _size is the size of the runtime image + * (start32 + the C code), in bytes. + */ + __size = _end - _start ; + + /* _decompressor_size is the size of the decompressor, in + * bytes. For a non-compressed image, start16.lds sets + * _decompressor_uncompressed = _decompressor__start = 0. + */ + __decompressor_size = __decompressor_uncompressed - __decompressor__start ; + + /* image__size is the total size of the image, after + * decompression and including the decompressor if applicable. + * It is therefore the amount of memory that start16's payload + * needs in order to execute, in bytes. + */ + __image_size = __size + __decompressor_size ; + + /* Amount to add to runtime symbols to obtain the offset of + * that symbol within the image. + */ + __offset_adjust = __decompressor_size - _start ; + + /* Calculations for the stack + */ + __stack_size = _estack - _stack ; + __offset_stack = _stack + __offset_adjust ; + + /* Some symbols will be larger than 16 bits but guaranteed to + * be multiples of 16. We calculate them in paragraphs and + * export these symbols which can be used in 16-bit code + * without risk of overflow. + */ + __image_size_pgh = ( __image_size / 16 ); + __start_pgh = ( _start / 16 ); + __decompressor_size_pgh = ( __decompressor_size / 16 ); + __offset_stack_pgh = ( __offset_stack / 16 ); +} + diff --git a/gpxe/src/arch/i386/core/start32.S b/gpxe/src/arch/i386/core/start32.S new file mode 100644 index 00000000..37ef5eb9 --- /dev/null +++ b/gpxe/src/arch/i386/core/start32.S @@ -0,0 +1,325 @@ +#include "virtaddr.h" + + .equ MSR_K6_EFER, 0xC0000080 + .equ EFER_LME, 0x00000100 + .equ X86_CR4_PAE, 0x00000020 + .equ CR0_PG, 0x80000000 + +#ifdef GAS291 +#define DATA32 data32; +#define ADDR32 addr32; +#define LJMPI(x) ljmp x +#else +#define DATA32 data32 +#define ADDR32 addr32 +/* newer GAS295 require #define LJMPI(x) ljmp *x */ +#define LJMPI(x) ljmp x +#endif + +/* + * NOTE: if you write a subroutine that is called from C code (gcc/egcs), + * then you only have to take care of %ebx, %esi, %edi and %ebp. These + * registers must not be altered under any circumstance. All other registers + * may be clobbered without any negative side effects. If you don't follow + * this rule then you'll run into strange effects that only occur on some + * gcc versions (because the register allocator may use different registers). + * + * All the data32 prefixes for the ljmp instructions are necessary, because + * the assembler emits code with a relocation address of 0. This means that + * all destinations are initially negative, which the assembler doesn't grok, + * because for some reason negative numbers don't fit into 16 bits. The addr32 + * prefixes are there for the same reasons, because otherwise the memory + * references are only 16 bit wide. Theoretically they are all superfluous. + * One last note about prefixes: the data32 prefixes on all call _real_to_prot + * instructions could be removed if the _real_to_prot function is changed to + * deal correctly with 16 bit return addresses. I tried it, but failed. + */ + + .text + .arch i386 + .code32 + + /* This is a struct os_entry_regs */ + .globl os_regs +os_regs: .space 56 + +/************************************************************************** +XSTART32 - Transfer control to the kernel just loaded +**************************************************************************/ + .globl xstart32 +xstart32: + /* Save the callee save registers */ + movl %ebp, os_regs + 32 + movl %esi, os_regs + 36 + movl %edi, os_regs + 40 + movl %ebx, os_regs + 44 + + /* save the return address */ + popl %eax + movl %eax, os_regs + 48 + + /* save the stack pointer */ + movl %esp, os_regs + 52 + + /* Get the new destination address */ + popl %ecx + + /* Store the physical address of xend on the stack */ + movl $xend32, %ebx + addl virt_offset, %ebx + pushl %ebx + + /* Store the destination address on the stack */ + pushl $PHYSICAL_CS + pushl %ecx + + /* Cache virt_offset */ + movl virt_offset, %ebp + + /* Switch to using physical addresses */ + call _virt_to_phys + + /* Save the target stack pointer */ + movl %esp, os_regs + 12(%ebp) + leal os_regs(%ebp), %esp + + /* Store the pointer to os_regs */ + movl %esp, os_regs_ptr(%ebp) + + /* Load my new registers */ + popal + movl (-32 + 12)(%esp), %esp + + /* Jump to the new kernel + * The lret switches to a flat code segment + */ + lret + + .balign 4 + .globl xend32 +xend32: + /* Fixup %eflags */ + nop + cli + cld + + /* Load %esp with &os_regs + virt_offset */ + .byte 0xbc /* movl $0, %esp */ +os_regs_ptr: + .long 0 + + /* Save the result registers */ + addl $32, %esp + pushal + + /* Compute virt_offset */ + movl %esp, %ebp + subl $os_regs, %ebp + + /* Load the stack pointer and convert it to physical address */ + movl 52(%esp), %esp + addl %ebp, %esp + + /* Enable the virtual addresses */ + leal _phys_to_virt(%ebp), %eax + call *%eax + + /* Restore the callee save registers */ + movl os_regs + 32, %ebp + movl os_regs + 36, %esi + movl os_regs + 40, %edi + movl os_regs + 44, %ebx + movl os_regs + 48, %edx + movl os_regs + 52, %esp + + /* Get the C return value */ + movl os_regs + 28, %eax + + jmpl *%edx + +#ifdef CONFIG_X86_64 + .arch sledgehammer +/************************************************************************** +XSTART_lm - Transfer control to the kernel just loaded in long mode +**************************************************************************/ + .globl xstart_lm +xstart_lm: + /* Save the callee save registers */ + pushl %ebp + pushl %esi + pushl %edi + pushl %ebx + + /* Cache virt_offset && (virt_offset & 0xfffff000) */ + movl virt_offset, %ebp + movl %ebp, %ebx + andl $0xfffff000, %ebx + + /* Switch to using physical addresses */ + call _virt_to_phys + + /* Initialize the page tables */ + /* Level 4 */ + leal 0x23 + pgt_level3(%ebx), %eax + leal pgt_level4(%ebx), %edi + movl %eax, (%edi) + + /* Level 3 */ + leal 0x23 + pgt_level2(%ebx), %eax + leal pgt_level3(%ebx), %edi + movl %eax, 0x00(%edi) + addl $4096, %eax + movl %eax, 0x08(%edi) + addl $4096, %eax + movl %eax, 0x10(%edi) + addl $4096, %eax + movl %eax, 0x18(%edi) + + /* Level 2 */ + movl $0xe3, %eax + leal pgt_level2(%ebx), %edi + leal 16384(%edi), %esi +pgt_level2_loop: + movl %eax, (%edi) + addl $8, %edi + addl $0x200000, %eax + cmp %esi, %edi + jne pgt_level2_loop + + /* Point at the x86_64 page tables */ + leal pgt_level4(%ebx), %edi + movl %edi, %cr3 + + + /* Setup for the return from 64bit mode */ + /* 64bit align the stack */ + movl %esp, %ebx /* original stack pointer + 16 */ + andl $0xfffffff8, %esp + + /* Save original stack pointer + 16 */ + pushl %ebx + + /* Save virt_offset */ + pushl %ebp + + /* Setup for the jmp to 64bit long mode */ + leal start_lm(%ebp), %eax + movl %eax, 0x00 + start_lm_addr(%ebp) + movl $LM_CODE_SEG, %eax + movl %eax, 0x04 + start_lm_addr(%ebp) + + /* Setup for the jump out of 64bit long mode */ + leal end_lm(%ebp), %eax + movl %eax, 0x00 + end_lm_addr(%ebp) + movl $FLAT_CODE_SEG, %eax + movl %eax, 0x04 + end_lm_addr(%ebp) + + /* Enable PAE mode */ + movl %cr4, %eax + orl $X86_CR4_PAE, %eax + movl %eax, %cr4 + + /* Enable long mode */ + movl $MSR_K6_EFER, %ecx + rdmsr + orl $EFER_LME, %eax + wrmsr + + /* Start paging, entering 32bit compatiblity mode */ + movl %cr0, %eax + orl $CR0_PG, %eax + movl %eax, %cr0 + + /* Enter 64bit long mode */ + ljmp *start_lm_addr(%ebp) + .code64 +start_lm: + /* Load 64bit data segments */ + movl $LM_DATA_SEG, %eax + movl %eax, %ds + movl %eax, %es + movl %eax, %ss + + andq $0xffffffff, %rbx + /* Get the address to jump to */ + movl 20(%rbx), %edx + andq $0xffffffff, %rdx + + /* Get the argument pointer */ + movl 24(%rbx), %ebx + andq $0xffffffff, %rbx + + /* Jump to the 64bit code */ + call *%rdx + + /* Preserve the result */ + movl %eax, %edx + + /* Fixup %eflags */ + cli + cld + + /* Switch to 32bit compatibility mode */ + ljmp *end_lm_addr(%rip) + + .code32 +end_lm: + /* Disable paging */ + movl %cr0, %eax + andl $~CR0_PG, %eax + movl %eax, %cr0 + + /* Disable long mode */ + movl $MSR_K6_EFER, %ecx + rdmsr + andl $~EFER_LME, %eax + wrmsr + + /* Disable PAE */ + movl %cr4, %eax + andl $~X86_CR4_PAE, %eax + movl %eax, %cr4 + + /* Compute virt_offset */ + popl %ebp + + /* Compute the original stack pointer + 16 */ + popl %ebx + movl %ebx, %esp + + /* Enable the virtual addresses */ + leal _phys_to_virt(%ebp), %eax + call *%eax + + /* Restore the callee save registers */ + popl %ebx + popl %esi + popl %edi + popl %ebp + + /* Get the C return value */ + movl %edx, %eax + + /* Return */ + ret + + .arch i386 +#endif /* CONFIG_X86_64 */ + +#ifdef CONFIG_X86_64 + .section ".bss" + .p2align 12 + /* Include a dummy space in case we are loaded badly aligned */ + .space 4096 + /* Reserve enough space for a page table convering 4GB with 2MB pages */ +pgt_level4: + .space 4096 +pgt_level3: + .space 4096 +pgt_level2: + .space 16384 +start_lm_addr: + .space 8 +end_lm_addr: + .space 8 +#endif diff --git a/gpxe/src/arch/i386/core/umalloc.c b/gpxe/src/arch/i386/core/umalloc.c new file mode 100644 index 00000000..bfd62ef1 --- /dev/null +++ b/gpxe/src/arch/i386/core/umalloc.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** + * @file + * + * External memory allocation + * + */ + +#include <limits.h> +#include <errno.h> +#include <gpxe/uaccess.h> +#include <gpxe/hidemem.h> +#include <gpxe/memmap.h> +#include <gpxe/umalloc.h> + +/** Alignment of external allocated memory */ +#define EM_ALIGN ( 4 * 1024 ) + +/** Equivalent of NOWHERE for user pointers */ +#define UNOWHERE ( ~UNULL ) + +/** Start of Etherboot text, as defined by the linker */ +extern char _text[]; + +/** An external memory block */ +struct external_memory { + /** Size of this memory block (excluding this header) */ + size_t size; + /** Block is currently in use */ + int used; +}; + +/** Top of heap */ +static userptr_t top = UNULL; + +/** Bottom of heap (current lowest allocated block) */ +static userptr_t bottom = UNULL; + +/** + * Initialise external heap + * + * @ret rc Return status code + */ +static int init_eheap ( void ) { + struct memory_map memmap; + unsigned long heap_size = 0; + unsigned int i; + + DBG ( "Allocating external heap\n" ); + + get_memmap ( &memmap ); + for ( i = 0 ; i < memmap.count ; i++ ) { + struct memory_region *region = &memmap.regions[i]; + unsigned long r_start, r_end; + unsigned long r_size; + + DBG ( "Considering [%llx,%llx)\n", region->start, region->end); + + /* Truncate block to 4GB */ + if ( region->start > UINT_MAX ) { + DBG ( "...starts after 4GB\n" ); + continue; + } + r_start = region->start; + if ( region->end > UINT_MAX ) { + DBG ( "...end truncated to 4GB\n" ); + r_end = 0; /* =4GB, given the wraparound */ + } else { + r_end = region->end; + } + + /* Use largest block */ + r_size = ( r_end - r_start ); + if ( r_size > heap_size ) { + DBG ( "...new best block found\n" ); + top = bottom = phys_to_user ( r_end ); + heap_size = r_size; + } + } + + if ( ! top ) { + DBG ( "No external heap available\n" ); + return -ENOMEM; + } + + DBG ( "External heap grows downwards from %lx\n", + user_to_phys ( top, 0 ) ); + return 0; +} + +/** + * Collect free blocks + * + */ +static void ecollect_free ( void ) { + struct external_memory extmem; + + /* Walk the free list and collect empty blocks */ + while ( bottom != top ) { + copy_from_user ( &extmem, bottom, -sizeof ( extmem ), + sizeof ( extmem ) ); + if ( extmem.used ) + break; + DBG ( "EXTMEM freeing [%lx,%lx)\n", user_to_phys ( bottom, 0 ), + user_to_phys ( bottom, extmem.size ) ); + bottom = userptr_add ( bottom, + ( extmem.size + sizeof ( extmem ) ) ); + } +} + +/** + * Reallocate external memory + * + * @v old_ptr Memory previously allocated by umalloc(), or UNULL + * @v new_size Requested size + * @ret new_ptr Allocated memory, or UNULL + * + * Calling realloc() with a new size of zero is a valid way to free a + * memory block. + */ +userptr_t urealloc ( userptr_t ptr, size_t new_size ) { + struct external_memory extmem; + userptr_t new = ptr; + size_t align; + int rc; + + /* Initialise external memory allocator if necessary */ + if ( ! top ) { + if ( ( rc = init_eheap() ) != 0 ) + return rc; + } + + /* Get block properties into extmem */ + if ( ptr && ( ptr != UNOWHERE ) ) { + /* Determine old size */ + copy_from_user ( &extmem, ptr, -sizeof ( extmem ), + sizeof ( extmem ) ); + } else { + /* Create a zero-length block */ + ptr = bottom = userptr_add ( bottom, -sizeof ( extmem ) ); + DBG ( "EXTMEM allocating [%lx,%lx)\n", + user_to_phys ( ptr, 0 ), user_to_phys ( ptr, 0 ) ); + extmem.size = 0; + } + extmem.used = ( new_size > 0 ); + + /* Expand/shrink block if possible */ + if ( ptr == bottom ) { + /* Update block */ + new = userptr_add ( ptr, - ( new_size - extmem.size ) ); + align = ( user_to_phys ( new, 0 ) & ( EM_ALIGN - 1 ) ); + new_size += align; + new = userptr_add ( new, -align ); + DBG ( "EXTMEM expanding [%lx,%lx) to [%lx,%lx)\n", + user_to_phys ( ptr, 0 ), + user_to_phys ( ptr, extmem.size ), + user_to_phys ( new, 0 ), + user_to_phys ( new, new_size )); + memmove_user ( new, 0, ptr, 0, ( ( extmem.size < new_size ) ? + extmem.size : new_size ) ); + extmem.size = new_size; + bottom = new; + } else { + /* Cannot expand; can only pretend to shrink */ + if ( new_size > extmem.size ) { + /* Refuse to expand */ + DBG ( "EXTMEM cannot expand [%lx,%lx)\n", + user_to_phys ( ptr, 0 ), + user_to_phys ( ptr, extmem.size ) ); + return UNULL; + } + } + + /* Write back block properties */ + copy_to_user ( new, -sizeof ( extmem ), &extmem, + sizeof ( extmem ) ); + + /* Collect any free blocks and update hidden memory region */ + ecollect_free(); + hide_region ( EXTMEM, user_to_phys ( bottom, -sizeof ( extmem ) ), + user_to_phys ( top, 0 ) ); + + return ( new_size ? new : UNOWHERE ); +} + +/** + * Allocate external memory + * + * @v size Requested size + * @ret ptr Memory, or UNULL + * + * Memory is guaranteed to be aligned to a page boundary. + */ +userptr_t umalloc ( size_t size ) { + return urealloc ( UNULL, size ); +} + +/** + * Free external memory + * + * @v ptr Memory allocated by umalloc(), or UNULL + * + * If @c ptr is UNULL, no action is taken. + */ +void ufree ( userptr_t ptr ) { + urealloc ( ptr, 0 ); +} diff --git a/gpxe/src/arch/i386/core/video_subr.c b/gpxe/src/arch/i386/core/video_subr.c new file mode 100644 index 00000000..bf82cc61 --- /dev/null +++ b/gpxe/src/arch/i386/core/video_subr.c @@ -0,0 +1,104 @@ +/* + * + * modified from linuxbios code + * by Cai Qiang <rimy2000@hotmail.com> + * + */ + +#include "stddef.h" +#include "string.h" +#include "io.h" +#include "console.h" +#include <gpxe/init.h> +#include "vga.h" + +struct console_driver vga_console; + +static char *vidmem; /* The video buffer */ +static int video_line, video_col; + +#define VIDBUFFER 0xB8000 + +static void memsetw(void *s, int c, unsigned int n) +{ + unsigned int i; + u16 *ss = (u16 *) s; + + for (i = 0; i < n; i++) { + ss[i] = ( u16 ) c; + } +} + +static void video_init(void) +{ + static int inited=0; + + vidmem = (char *)phys_to_virt(VIDBUFFER); + + if (!inited) { + video_line = 0; + video_col = 0; + + memsetw(vidmem, VGA_ATTR_CLR_WHT, 2*1024); // + + inited=1; + } +} + +static void video_scroll(void) +{ + int i; + + memcpy(vidmem, vidmem + COLS * 2, (LINES - 1) * COLS * 2); + for (i = (LINES - 1) * COLS * 2; i < LINES * COLS * 2; i += 2) + vidmem[i] = ' '; +} + +static void vga_putc(int byte) +{ + if (byte == '\n') { + video_line++; + video_col = 0; + + } else if (byte == '\r') { + video_col = 0; + + } else if (byte == '\b') { + video_col--; + + } else if (byte == '\t') { + video_col += 4; + + } else if (byte == '\a') { + //beep + //beep(500); + + } else { + vidmem[((video_col + (video_line *COLS)) * 2)] = byte; + vidmem[((video_col + (video_line *COLS)) * 2) +1] = VGA_ATTR_CLR_WHT; + video_col++; + } + if (video_col < 0) { + video_col = 0; + } + if (video_col >= COLS) { + video_line++; + video_col = 0; + } + if (video_line >= LINES) { + video_scroll(); + video_line--; + } + // move the cursor + write_crtc((video_col + (video_line *COLS)) >> 8, CRTC_CURSOR_HI); + write_crtc((video_col + (video_line *COLS)) & 0x0ff, CRTC_CURSOR_LO); +} + +struct console_driver vga_console __console_driver = { + .putchar = vga_putc, + .disabled = 1, +}; + +struct init_fn video_init_fn __init_fn ( INIT_EARLY ) = { + .initialise = video_init, +}; diff --git a/gpxe/src/arch/i386/core/virtaddr.S b/gpxe/src/arch/i386/core/virtaddr.S new file mode 100644 index 00000000..5d762375 --- /dev/null +++ b/gpxe/src/arch/i386/core/virtaddr.S @@ -0,0 +1,101 @@ +/* + * Functions to support the virtual addressing method of relocation + * that Etherboot uses. + * + */ + +#include "virtaddr.h" + + .arch i386 + .text + .code32 + +/**************************************************************************** + * _virt_to_phys (virtual addressing) + * + * Switch from virtual to flat physical addresses. %esp is adjusted + * to a physical value. Segment registers are set to flat physical + * selectors. All other registers are preserved. Flags are + * preserved. + * + * Parameters: none + * Returns: none + **************************************************************************** + */ + .globl _virt_to_phys +_virt_to_phys: + /* Preserve registers and flags */ + pushfl + pushl %eax + pushl %ebp + + /* Change return address to a physical address */ + movl virt_offset, %ebp + addl %ebp, 12(%esp) + + /* Switch to physical code segment */ + pushl $PHYSICAL_CS + leal 1f(%ebp), %eax + pushl %eax + lret +1: + /* Reload other segment registers and adjust %esp */ + movl $PHYSICAL_DS, %eax + movl %eax, %ds + movl %eax, %es + movl %eax, %fs + movl %eax, %gs + movl %eax, %ss + addl %ebp, %esp + + /* Restore registers and flags, and return */ + popl %ebp + popl %eax + popfl + ret + +/**************************************************************************** + * _phys_to_virt (flat physical addressing) + * + * Switch from flat physical to virtual addresses. %esp is adjusted + * to a virtual value. Segment registers are set to virtual + * selectors. All other registers are preserved. Flags are + * preserved. + * + * Note that this depends on the GDT already being correctly set up + * (e.g. by a call to run_here()). + * + * Parameters: none + * Returns: none + **************************************************************************** + */ + .globl _phys_to_virt +_phys_to_virt: + /* Preserve registers and flags */ + pushfl + pushl %eax + pushl %ebp + + /* Switch to virtual code segment */ + ljmp $VIRTUAL_CS, $1f +1: + /* Reload data segment registers */ + movl $VIRTUAL_DS, %eax + movl %eax, %ds + movl %eax, %es + movl %eax, %fs + movl %eax, %gs + + /* Reload stack segment and adjust %esp */ + movl virt_offset, %ebp + movl %eax, %ss + subl %ebp, %esp + + /* Change the return address to a virtual address */ + subl %ebp, 12(%esp) + + /* Restore registers and flags, and return */ + popl %ebp + popl %eax + popfl + ret diff --git a/gpxe/src/arch/i386/core/wince_loader.c b/gpxe/src/arch/i386/core/wince_loader.c new file mode 100644 index 00000000..f452b659 --- /dev/null +++ b/gpxe/src/arch/i386/core/wince_loader.c @@ -0,0 +1,273 @@ +#define LOAD_DEBUG 0 + +static int get_x_header(unsigned char *data, unsigned long now); +static void jump_2ep(); +static unsigned char ce_signature[] = {'B', '0', '0', '0', 'F', 'F', '\n',}; +static char ** ep; + +#define BOOT_ARG_PTR_LOCATION 0x001FFFFC + +typedef struct _BOOT_ARGS{ + unsigned char ucVideoMode; + unsigned char ucComPort; + unsigned char ucBaudDivisor; + unsigned char ucPCIConfigType; + + unsigned long dwSig; + #define BOOTARG_SIG 0x544F4F42 + unsigned long dwLen; + + unsigned char ucLoaderFlags; + unsigned char ucEshellFlags; + unsigned char ucEdbgAdapterType; + unsigned char ucEdbgIRQ; + + unsigned long dwEdbgBaseAddr; + unsigned long dwEdbgDebugZone; + unsigned long dwDHCPLeaseTime; + unsigned long dwEdbgFlags; + + unsigned long dwEBootFlag; + unsigned long dwEBootAddr; + unsigned long dwLaunchAddr; + + unsigned long pvFlatFrameBuffer; + unsigned short vesaMode; + unsigned short cxDisplayScreen; + unsigned short cyDisplayScreen; + unsigned short cxPhysicalScreen; + unsigned short cyPhysicalScreen; + unsigned short cbScanLineLength; + unsigned short bppScreen; + + unsigned char RedMaskSize; + unsigned char REdMaskPosition; + unsigned char GreenMaskSize; + unsigned char GreenMaskPosition; + unsigned char BlueMaskSize; + unsigned char BlueMaskPosition; +} BOOT_ARGS; + +BOOT_ARGS BootArgs; + +static struct segment_info{ + unsigned long addr; // Section Address + unsigned long size; // Section Size + unsigned long checksum; // Section CheckSum +} X; + +#define PSIZE (1500) //Max Packet Size +#define DSIZE (PSIZE+12) +static unsigned long dbuffer_available =0; +static unsigned long not_loadin =0; +static unsigned long d_now =0; + +unsigned long entry; +static unsigned long ce_curaddr; + + +static sector_t ce_loader(unsigned char *data, unsigned int len, int eof); +static os_download_t wince_probe(unsigned char *data, unsigned int len) +{ + if (strncmp(ce_signature, data, sizeof(ce_signature)) != 0) { + return 0; + } + printf("(WINCE)"); + return ce_loader; +} + +static sector_t ce_loader(unsigned char *data, unsigned int len, int eof) +{ + static unsigned char dbuffer[DSIZE]; + int this_write = 0; + static int firsttime = 1; + + /* + * new packet in, we have to + * [1] copy data to dbuffer, + * + * update... + * [2] dbuffer_available + */ + memcpy( (dbuffer+dbuffer_available), data, len); //[1] + dbuffer_available += len; // [2] + len = 0; + + d_now = 0; + +#if 0 + printf("dbuffer_available =%ld \n", dbuffer_available); +#endif + + if (firsttime) + { + d_now = sizeof(ce_signature); + printf("String Physical Address = %lx \n", + *(unsigned long *)(dbuffer+d_now)); + + d_now += sizeof(unsigned long); + printf("Image Size = %ld [%lx]\n", + *(unsigned long *)(dbuffer+d_now), + *(unsigned long *)(dbuffer+d_now)); + + d_now += sizeof(unsigned long); + dbuffer_available -= d_now; + + d_now = (unsigned long)get_x_header(dbuffer, d_now); + firsttime = 0; + } + + if (not_loadin == 0) + { + d_now = get_x_header(dbuffer, d_now); + } + + while ( not_loadin > 0 ) + { + /* dbuffer do not have enough data to loading, copy all */ +#if LOAD_DEBUG + printf("[0] not_loadin = [%ld], dbuffer_available = [%ld] \n", + not_loadin, dbuffer_available); + printf("[0] d_now = [%ld] \n", d_now); +#endif + + if( dbuffer_available <= not_loadin) + { + this_write = dbuffer_available ; + memcpy(phys_to_virt(ce_curaddr), (dbuffer+d_now), this_write ); + ce_curaddr += this_write; + not_loadin -= this_write; + + /* reset index and available in the dbuffer */ + dbuffer_available = 0; + d_now = 0; +#if LOAD_DEBUG + printf("[1] not_loadin = [%ld], dbuffer_available = [%ld] \n", + not_loadin, dbuffer_available); + printf("[1] d_now = [%ld], this_write = [%d] \n", + d_now, this_write); +#endif + + // get the next packet... + return (0); + } + + /* dbuffer have more data then loading ... , copy partital.... */ + else + { + this_write = not_loadin; + memcpy(phys_to_virt(ce_curaddr), (dbuffer+d_now), this_write); + ce_curaddr += this_write; + not_loadin = 0; + + /* reset index and available in the dbuffer */ + dbuffer_available -= this_write; + d_now += this_write; +#if LOAD_DEBUG + printf("[2] not_loadin = [%ld], dbuffer_available = [%ld] \n", + not_loadin, dbuffer_available); + printf("[2] d_now = [%ld], this_write = [%d] \n\n", + d_now, this_write); +#endif + + /* dbuffer not empty, proceed processing... */ + + // don't have enough data to get_x_header.. + if ( dbuffer_available < (sizeof(unsigned long) * 3) ) + { +// printf("we don't have enough data remaining to call get_x. \n"); + memcpy( (dbuffer+0), (dbuffer+d_now), dbuffer_available); + return (0); + } + else + { +#if LOAD_DEBUG + printf("with remaining data to call get_x \n"); + printf("dbuffer available = %ld , d_now = %ld\n", + dbuffer_available, d_now); +#endif + d_now = get_x_header(dbuffer, d_now); + } + } + } + return (0); +} + +static int get_x_header(unsigned char *dbuffer, unsigned long now) +{ + X.addr = *(unsigned long *)(dbuffer + now); + X.size = *(unsigned long *)(dbuffer + now + sizeof(unsigned long)); + X.checksum = *(unsigned long *)(dbuffer + now + sizeof(unsigned long)*2); + + if (X.addr == 0) + { + entry = X.size; + done(1); + printf("Entry Point Address = [%lx] \n", entry); + jump_2ep(); + } + + if (!prep_segment(X.addr, X.addr + X.size, X.addr + X.size, 0, 0)) { + longjmp(restart_etherboot, -2); + } + + ce_curaddr = X.addr; + now += sizeof(unsigned long)*3; + + /* re-calculate dbuffer available... */ + dbuffer_available -= sizeof(unsigned long)*3; + + /* reset index of this section */ + not_loadin = X.size; + +#if 1 + printf("\n"); + printf("\t Section Address = [%lx] \n", X.addr); + printf("\t Size = %d [%lx]\n", X.size, X.size); + printf("\t Checksum = %ld [%lx]\n", X.checksum, X.checksum); +#endif +#if LOAD_DEBUG + printf("____________________________________________\n"); + printf("\t dbuffer_now = %ld \n", now); + printf("\t dbuffer available = %ld \n", dbuffer_available); + printf("\t not_loadin = %ld \n", not_loadin); +#endif + + return now; +} + +static void jump_2ep() +{ + BootArgs.ucVideoMode = 1; + BootArgs.ucComPort = 1; + BootArgs.ucBaudDivisor = 1; + BootArgs.ucPCIConfigType = 1; // do not fill with 0 + + BootArgs.dwSig = BOOTARG_SIG; + BootArgs.dwLen = sizeof(BootArgs); + + if(BootArgs.ucVideoMode == 0) + { + BootArgs.cxDisplayScreen = 640; + BootArgs.cyDisplayScreen = 480; + BootArgs.cxPhysicalScreen = 640; + BootArgs.cyPhysicalScreen = 480; + BootArgs.bppScreen = 16; + BootArgs.cbScanLineLength = 1024; + BootArgs.pvFlatFrameBuffer = 0x800a0000; // ollie say 0x98000000 + } + else if(BootArgs.ucVideoMode != 0xFF) + { + BootArgs.cxDisplayScreen = 0; + BootArgs.cyDisplayScreen = 0; + BootArgs.cxPhysicalScreen = 0; + BootArgs.cyPhysicalScreen = 0; + BootArgs.bppScreen = 0; + BootArgs.cbScanLineLength = 0; + BootArgs.pvFlatFrameBuffer = 0; + } + + ep = phys_to_virt(BOOT_ARG_PTR_LOCATION); + *ep= virt_to_phys(&BootArgs); + xstart32(entry); +} diff --git a/gpxe/src/arch/i386/drivers/net/undi.c b/gpxe/src/arch/i386/drivers/net/undi.c new file mode 100644 index 00000000..1090cc94 --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undi.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <gpxe/pci.h> +#include <undi.h> +#include <undirom.h> +#include <undiload.h> +#include <undinet.h> +#include <undipreload.h> + +/** @file + * + * UNDI PCI driver + * + */ + +/** + * Find UNDI ROM for PCI device + * + * @v pci PCI device + * @ret undirom UNDI ROM, or NULL + * + * Try to find a driver for this device. Try an exact match on the + * ROM address first, then fall back to a vendor/device ID match only + */ +static struct undi_rom * undipci_find_rom ( struct pci_device *pci ) { + struct undi_rom *undirom; + unsigned long rombase; + + rombase = pci_bar_start ( pci, PCI_ROM_ADDRESS ); + undirom = undirom_find_pci ( pci->vendor, pci->device, rombase ); + if ( ! undirom ) + undirom = undirom_find_pci ( pci->vendor, pci->device, 0 ); + return undirom; +} + +/** + * Probe PCI device + * + * @v pci PCI device + * @v id PCI ID + * @ret rc Return status code + */ +static int undipci_probe ( struct pci_device *pci, + const struct pci_device_id *id __unused ) { + struct undi_device *undi; + struct undi_rom *undirom; + unsigned int busdevfn = PCI_BUSDEVFN ( pci->bus, pci->devfn ); + int rc; + + /* Ignore non-network devices */ + if ( PCI_BASE_CLASS ( pci->class ) != PCI_BASE_CLASS_NETWORK ) + return -ENOTTY; + + /* Allocate UNDI device structure */ + undi = zalloc ( sizeof ( *undi ) ); + if ( ! undi ) + return -ENOMEM; + pci_set_drvdata ( pci, undi ); + + /* Find/create our pixie */ + if ( preloaded_undi.pci_busdevfn == busdevfn ) { + /* Claim preloaded UNDI device */ + DBGC ( undi, "UNDI %p using preloaded UNDI device\n", undi ); + memcpy ( undi, &preloaded_undi, sizeof ( *undi ) ); + memset ( &preloaded_undi, 0, sizeof ( preloaded_undi ) ); + } else { + /* Find UNDI ROM for PCI device */ + if ( ! ( undirom = undipci_find_rom ( pci ) ) ) { + rc = -ENODEV; + goto err_find_rom; + } + + /* Call UNDI ROM loader to create pixie */ + if ( ( rc = undi_load_pci ( undi, undirom, busdevfn ) ) != 0 ) + goto err_load_pci; + } + + /* Add to device hierarchy */ + snprintf ( undi->dev.name, sizeof ( undi->dev.name ), + "UNDI-%s", pci->dev.name ); + memcpy ( &undi->dev.desc, &pci->dev.desc, sizeof ( undi->dev.desc ) ); + undi->dev.parent = &pci->dev; + INIT_LIST_HEAD ( &undi->dev.children ); + list_add ( &undi->dev.siblings, &pci->dev.children ); + + /* Create network device */ + if ( ( rc = undinet_probe ( undi ) ) != 0 ) + goto err_undinet_probe; + + return 0; + + err_undinet_probe: + undi_unload ( undi ); + list_del ( &undi->dev.siblings ); + err_find_rom: + err_load_pci: + free ( undi ); + pci_set_drvdata ( pci, NULL ); + return rc; +} + +/** + * Remove PCI device + * + * @v pci PCI device + */ +static void undipci_remove ( struct pci_device *pci ) { + struct undi_device *undi = pci_get_drvdata ( pci ); + + undinet_remove ( undi ); + undi_unload ( undi ); + list_del ( &undi->dev.siblings ); + free ( undi ); + pci_set_drvdata ( pci, NULL ); +} + +static struct pci_device_id undipci_nics[] = { +PCI_ROM ( 0xffff, 0xffff, "undipci", "UNDI (PCI)" ), +}; + +struct pci_driver undipci_driver __pci_driver = { + .ids = undipci_nics, + .id_count = ( sizeof ( undipci_nics ) / sizeof ( undipci_nics[0] ) ), + .probe = undipci_probe, + .remove = undipci_remove, +}; diff --git a/gpxe/src/arch/i386/drivers/net/undiisr.S b/gpxe/src/arch/i386/drivers/net/undiisr.S new file mode 100644 index 00000000..a6c6c381 --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undiisr.S @@ -0,0 +1,87 @@ +#define PXENV_UNDI_ISR 0x0014 +#define PXENV_UNDI_ISR_IN_START 1 +#define PXENV_UNDI_ISR_OUT_OURS 0 +#define PXENV_UNDI_ISR_OUT_NOT_OURS 1 + +#define IRQ_PIC_CUTOFF 8 +#define ICR_EOI_NON_SPECIFIC 0x20 +#define PIC1_ICR 0x20 +#define PIC2_ICR 0xa0 + + .text + .arch i386 + .section ".text16", "ax", @progbits + .section ".data16", "aw", @progbits + .code16 + + .section ".text16" + .globl undiisr +undiisr: + + /* Preserve registers */ + pushw %ds + pushw %es + pushw %fs + pushw %gs + pushfl + pushal + + /* Set up our segment registers */ + movw %cs:rm_ds, %ax + movw %ax, %ds + + /* Check that we have an UNDI entry point */ + cmpw $0, undinet_entry_point + je chain + + /* Issue UNDI API call */ + movw %ax, %es + movw $undinet_params, %di + movw $PXENV_UNDI_ISR, %bx + movw $PXENV_UNDI_ISR_IN_START, funcflag + pushw %es + pushw %di + pushw %bx + lcall *undinet_entry_point + cli /* Just in case */ + addw $6, %sp + cmpw $PXENV_UNDI_ISR_OUT_OURS, funcflag + jne eoi + +trig: /* Record interrupt occurence */ + incb undiisr_trigger_count + +eoi: /* Send EOI */ + movb $ICR_EOI_NON_SPECIFIC, %al + cmpb $IRQ_PIC_CUTOFF, undiisr_irq + jb 1f + outb %al, $PIC2_ICR +1: outb %al, $PIC1_ICR + jmp exit + +chain: /* Chain to next handler */ + pushfw + lcall *undiisr_next_handler + +exit: /* Restore registers and return */ + cli + popal + movzwl %sp, %esp + addr32 movl -20(%esp), %esp /* %esp isn't restored by popal */ + popfl + popw %gs + popw %fs + popw %es + popw %ds + iret + + .section ".data16" +undinet_params: +status: .word 0 +funcflag: .word 0 +bufferlength: .word 0 +framelength: .word 0 +frameheaderlength: .word 0 +frame: .word 0, 0 +prottype: .byte 0 +pkttype: .byte 0 diff --git a/gpxe/src/arch/i386/drivers/net/undiload.c b/gpxe/src/arch/i386/drivers/net/undiload.c new file mode 100644 index 00000000..a3284f80 --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undiload.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <pxe.h> +#include <realmode.h> +#include <bios.h> +#include <pnpbios.h> +#include <basemem.h> +#include <gpxe/pci.h> +#include <undi.h> +#include <undirom.h> +#include <undiload.h> + +/** @file + * + * UNDI load/unload + * + */ + +/** Parameter block for calling UNDI loader */ +static struct s_UNDI_LOADER __bss16 ( undi_loader ); +#define undi_loader __use_data16 ( undi_loader ) + +/** UNDI loader entry point */ +static SEGOFF16_t __bss16 ( undi_loader_entry ); +#define undi_loader_entry __use_data16 ( undi_loader_entry ) + +/** + * Call UNDI loader to create a pixie + * + * @v undi UNDI device + * @v undirom UNDI ROM + * @ret rc Return status code + */ +int undi_load ( struct undi_device *undi, struct undi_rom *undirom ) { + struct s_PXE ppxe; + unsigned int fbms_seg; + uint16_t exit; + int rc; + + /* Set up START_UNDI parameters */ + memset ( &undi_loader, 0, sizeof ( undi_loader ) ); + undi_loader.AX = undi->pci_busdevfn; + undi_loader.BX = undi->isapnp_csn; + undi_loader.DX = undi->isapnp_read_port; + undi_loader.ES = BIOS_SEG; + undi_loader.DI = find_pnp_bios(); + + /* Allocate base memory for PXE stack */ + undi->restore_fbms = get_fbms(); + fbms_seg = ( undi->restore_fbms << 6 ); + fbms_seg -= ( ( undirom->code_size + 0x0f ) >> 4 ); + undi_loader.UNDI_CS = fbms_seg; + fbms_seg -= ( ( undirom->data_size + 0x0f ) >> 4 ); + undi_loader.UNDI_DS = fbms_seg; + + /* Debug info */ + DBGC ( undi, "UNDI %p loading UNDI ROM %p to CS %04x DS %04x for ", + undi, undirom, undi_loader.UNDI_CS, undi_loader.UNDI_DS ); + if ( undi->pci_busdevfn != UNDI_NO_PCI_BUSDEVFN ) { + unsigned int bus = ( undi->pci_busdevfn >> 8 ); + unsigned int devfn = ( undi->pci_busdevfn & 0xff ); + DBGC ( undi, "PCI %02x:%02x.%x\n", + bus, PCI_SLOT ( devfn ), PCI_FUNC ( devfn ) ); + } + if ( undi->isapnp_csn != UNDI_NO_ISAPNP_CSN ) { + DBGC ( undi, "ISAPnP(%04x) CSN %04x\n", + undi->isapnp_read_port, undi->isapnp_csn ); + } + + /* Call loader */ + undi_loader_entry = undirom->loader_entry; + __asm__ __volatile__ ( REAL_CODE ( "pushw %%ds\n\t" + "pushw %%ax\n\t" + "lcall *%c2\n\t" + "addw $4, %%sp\n\t" ) + : "=a" ( exit ) + : "a" ( & __from_data16 ( undi_loader ) ), + "p" ( & __from_data16 ( undi_loader_entry ) ) + : "ebx", "ecx", "edx", "esi", "edi", "ebp" ); + + /* UNDI API calls may rudely change the status of A20 and not + * bother to restore it afterwards. Intel is known to be + * guilty of this. + * + * Note that we will return to this point even if A20 gets + * screwed up by the UNDI driver, because Etherboot always + * resides in an even megabyte of RAM. + */ + gateA20_set(); + + if ( exit != PXENV_EXIT_SUCCESS ) { + rc = -undi_loader.Status; + if ( rc == 0 ) /* Paranoia */ + rc = -EIO; + DBGC ( undi, "UNDI %p loader failed: %s\n", + undi, strerror ( rc ) ); + return rc; + } + + /* Populate PXE device structure */ + undi->pxenv = undi_loader.PXENVptr; + undi->ppxe = undi_loader.PXEptr; + copy_from_real ( &ppxe, undi->ppxe.segment, undi->ppxe.offset, + sizeof ( ppxe ) ); + undi->entry = ppxe.EntryPointSP; + DBGC ( undi, "UNDI %p loaded PXENV+ %04x:%04x !PXE %04x:%04x " + "entry %04x:%04x\n", undi, undi->pxenv.segment, + undi->pxenv.offset, undi->ppxe.segment, undi->ppxe.offset, + undi->entry.segment, undi->entry.offset ); + + /* Update free base memory counter */ + undi->fbms = ( fbms_seg >> 6 ); + set_fbms ( undi->fbms ); + DBGC ( undi, "UNDI %p using [%d,%d) kB of base memory\n", + undi, undi->fbms, undi->restore_fbms ); + + return 0; +} + +/** + * Unload a pixie + * + * @v undi UNDI device + * @ret rc Return status code + * + * Erases the PXENV+ and !PXE signatures, and frees the used base + * memory (if possible). + */ +int undi_unload ( struct undi_device *undi ) { + static uint32_t dead = 0xdeaddead; + + DBGC ( undi, "UNDI %p unloading\n", undi ); + + /* Erase signatures */ + if ( undi->pxenv.segment ) + put_real ( dead, undi->pxenv.segment, undi->pxenv.offset ); + if ( undi->ppxe.segment ) + put_real ( dead, undi->ppxe.segment, undi->ppxe.offset ); + + /* Free base memory, if possible */ + if ( undi->fbms == get_fbms() ) { + DBGC ( undi, "UNDI %p freeing [%d,%d) kB of base memory\n", + undi, undi->fbms, undi->restore_fbms ); + set_fbms ( undi->restore_fbms ); + return 0; + } else { + DBGC ( undi, "UNDI %p leaking [%d,%d) kB of base memory\n", + undi, undi->fbms, undi->restore_fbms ); + return -EBUSY; + } +} diff --git a/gpxe/src/arch/i386/drivers/net/undinet.c b/gpxe/src/arch/i386/drivers/net/undinet.c new file mode 100644 index 00000000..e3b9f85a --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undinet.c @@ -0,0 +1,793 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <string.h> +#include <pxe.h> +#include <realmode.h> +#include <pic8259.h> +#include <biosint.h> +#include <pnpbios.h> +#include <basemem_packet.h> +#include <gpxe/iobuf.h> +#include <gpxe/netdevice.h> +#include <gpxe/if_ether.h> +#include <gpxe/ethernet.h> +#include <undi.h> +#include <undinet.h> + + +/** @file + * + * UNDI network device driver + * + */ + +/** An UNDI NIC */ +struct undi_nic { + /** Assigned IRQ number */ + unsigned int irq; + /** Currently processing ISR */ + int isr_processing; + /** Bug workarounds */ + int hacks; +}; + +/** + * @defgroup undi_hacks UNDI workarounds + * @{ + */ + +/** Work around Etherboot 5.4 bugs */ +#define UNDI_HACK_EB54 0x0001 + +/** @} */ + +static void undinet_close ( struct net_device *netdev ); + +/***************************************************************************** + * + * UNDI API call + * + ***************************************************************************** + */ + +/** + * Name UNDI API call + * + * @v function API call number + * @ret name API call name + */ +static inline __attribute__ (( always_inline )) const char * +undinet_function_name ( unsigned int function ) { + switch ( function ) { + case PXENV_START_UNDI: + return "PXENV_START_UNDI"; + case PXENV_STOP_UNDI: + return "PXENV_STOP_UNDI"; + case PXENV_UNDI_STARTUP: + return "PXENV_UNDI_STARTUP"; + case PXENV_UNDI_CLEANUP: + return "PXENV_UNDI_CLEANUP"; + case PXENV_UNDI_INITIALIZE: + return "PXENV_UNDI_INITIALIZE"; + case PXENV_UNDI_RESET_ADAPTER: + return "PXENV_UNDI_RESET_ADAPTER"; + case PXENV_UNDI_SHUTDOWN: + return "PXENV_UNDI_SHUTDOWN"; + case PXENV_UNDI_OPEN: + return "PXENV_UNDI_OPEN"; + case PXENV_UNDI_CLOSE: + return "PXENV_UNDI_CLOSE"; + case PXENV_UNDI_TRANSMIT: + return "PXENV_UNDI_TRANSMIT"; + case PXENV_UNDI_SET_MCAST_ADDRESS: + return "PXENV_UNDI_SET_MCAST_ADDRESS"; + case PXENV_UNDI_SET_STATION_ADDRESS: + return "PXENV_UNDI_SET_STATION_ADDRESS"; + case PXENV_UNDI_SET_PACKET_FILTER: + return "PXENV_UNDI_SET_PACKET_FILTER"; + case PXENV_UNDI_GET_INFORMATION: + return "PXENV_UNDI_GET_INFORMATION"; + case PXENV_UNDI_GET_STATISTICS: + return "PXENV_UNDI_GET_STATISTICS"; + case PXENV_UNDI_CLEAR_STATISTICS: + return "PXENV_UNDI_CLEAR_STATISTICS"; + case PXENV_UNDI_INITIATE_DIAGS: + return "PXENV_UNDI_INITIATE_DIAGS"; + case PXENV_UNDI_FORCE_INTERRUPT: + return "PXENV_UNDI_FORCE_INTERRUPT"; + case PXENV_UNDI_GET_MCAST_ADDRESS: + return "PXENV_UNDI_GET_MCAST_ADDRESS"; + case PXENV_UNDI_GET_NIC_TYPE: + return "PXENV_UNDI_GET_NIC_TYPE"; + case PXENV_UNDI_GET_IFACE_INFO: + return "PXENV_UNDI_GET_IFACE_INFO"; + /* + * Duplicate case value; this is a bug in the PXE specification. + * + * case PXENV_UNDI_GET_STATE: + * return "PXENV_UNDI_GET_STATE"; + */ + case PXENV_UNDI_ISR: + return "PXENV_UNDI_ISR"; + default: + return "UNKNOWN API CALL"; + } +} + +/** + * UNDI parameter block + * + * Used as the paramter block for all UNDI API calls. Resides in base + * memory. + */ +static union u_PXENV_ANY __bss16 ( undinet_params ); +#define undinet_params __use_data16 ( undinet_params ) + +/** UNDI entry point + * + * Used as the indirection vector for all UNDI API calls. Resides in + * base memory. + */ +SEGOFF16_t __bss16 ( undinet_entry_point ); +#define undinet_entry_point __use_data16 ( undinet_entry_point ) + +/** + * Issue UNDI API call + * + * @v undinic UNDI NIC + * @v function API call number + * @v params UNDI parameter block + * @v params_len Length of UNDI parameter block + * @ret rc Return status code + */ +static int undinet_call ( struct undi_nic *undinic, unsigned int function, + void *params, size_t params_len ) { + PXENV_EXIT_t exit; + int discard_b, discard_D; + int rc; + + /* Copy parameter block and entry point */ + assert ( params_len <= sizeof ( undinet_params ) ); + memcpy ( &undinet_params, params, params_len ); + + /* Call real-mode entry point. This calling convention will + * work with both the !PXE and the PXENV+ entry points. + */ + __asm__ __volatile__ ( REAL_CODE ( "pushw %%es\n\t" + "pushw %%di\n\t" + "pushw %%bx\n\t" + "lcall *%c3\n\t" + "addw $6, %%sp\n\t" ) + : "=a" ( exit ), "=b" ( discard_b ), + "=D" ( discard_D ) + : "p" ( &__from_data16 ( undinet_entry_point )), + "b" ( function ), + "D" ( &__from_data16 ( undinet_params ) ) + : "ecx", "edx", "esi", "ebp" ); + + /* UNDI API calls may rudely change the status of A20 and not + * bother to restore it afterwards. Intel is known to be + * guilty of this. + * + * Note that we will return to this point even if A20 gets + * screwed up by the UNDI driver, because Etherboot always + * resides in an even megabyte of RAM. + */ + gateA20_set(); + + /* Determine return status code based on PXENV_EXIT and + * PXENV_STATUS + */ + if ( exit == PXENV_EXIT_SUCCESS ) { + rc = 0; + } else { + rc = -undinet_params.Status; + /* Paranoia; don't return success for the combination + * of PXENV_EXIT_FAILURE but PXENV_STATUS_SUCCESS + */ + if ( rc == 0 ) + rc = -EIO; + } + + /* If anything goes wrong, print as much debug information as + * it's possible to give. + */ + if ( rc != 0 ) { + SEGOFF16_t rm_params = { + .segment = rm_ds, + .offset = (intptr_t) &__from_data16 ( undinet_params ), + }; + + DBGC ( undinic, "UNDINIC %p %s failed: %s\n", undinic, + undinet_function_name ( function ), strerror ( rc ) ); + DBGC ( undinic, "UNDINIC %p parameters at %04x:%04x length " + "%#02zx, entry point at %04x:%04x\n", undinic, + rm_params.segment, rm_params.offset, params_len, + undinet_entry_point.segment, + undinet_entry_point.offset ); + DBGC ( undinic, "UNDINIC %p parameters provided:\n", undinic ); + DBGC_HDA ( undinic, rm_params, params, params_len ); + DBGC ( undinic, "UNDINIC %p parameters returned:\n", undinic ); + DBGC_HDA ( undinic, rm_params, &undinet_params, params_len ); + } + + /* Copy parameter block back */ + memcpy ( params, &undinet_params, params_len ); + + return rc; +} + +/***************************************************************************** + * + * UNDI interrupt service routine + * + ***************************************************************************** + */ + +/** + * UNDI interrupt service routine + * + * The UNDI ISR increments a counter (@c trigger_count) and exits. + */ +extern void undiisr ( void ); + +/** IRQ number */ +uint8_t __data16 ( undiisr_irq ); +#define undiisr_irq __use_data16 ( undiisr_irq ) + +/** IRQ chain vector */ +struct segoff __data16 ( undiisr_next_handler ); +#define undiisr_next_handler __use_data16 ( undiisr_next_handler ) + +/** IRQ trigger count */ +volatile uint8_t __data16 ( undiisr_trigger_count ) = 0; +#define undiisr_trigger_count __use_data16 ( undiisr_trigger_count ) + +/** Last observed trigger count */ +static unsigned int last_trigger_count = 0; + +/** + * Hook UNDI interrupt service routine + * + * @v irq IRQ number + */ +static void undinet_hook_isr ( unsigned int irq ) { + + assert ( irq <= IRQ_MAX ); + assert ( undiisr_irq == 0 ); + + undiisr_irq = irq; + hook_bios_interrupt ( IRQ_INT ( irq ), + ( ( unsigned int ) undiisr ), + &undiisr_next_handler ); +} + +/** + * Unhook UNDI interrupt service routine + * + * @v irq IRQ number + */ +static void undinet_unhook_isr ( unsigned int irq ) { + + assert ( irq <= IRQ_MAX ); + + unhook_bios_interrupt ( IRQ_INT ( irq ), + ( ( unsigned int ) undiisr ), + &undiisr_next_handler ); + undiisr_irq = 0; +} + +/** + * Test to see if UNDI ISR has been triggered + * + * @ret triggered ISR has been triggered since last check + */ +static int undinet_isr_triggered ( void ) { + unsigned int this_trigger_count; + + /* Read trigger_count. Do this only once; it is volatile */ + this_trigger_count = undiisr_trigger_count; + + if ( this_trigger_count == last_trigger_count ) { + /* Not triggered */ + return 0; + } else { + /* Triggered */ + last_trigger_count = this_trigger_count; + return 1; + } +} + +/***************************************************************************** + * + * UNDI network device interface + * + ***************************************************************************** + */ + +/** UNDI transmit buffer descriptor */ +static struct s_PXENV_UNDI_TBD __data16 ( undinet_tbd ); +#define undinet_tbd __use_data16 ( undinet_tbd ) + +/** + * Transmit packet + * + * @v netdev Network device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int undinet_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ) { + struct undi_nic *undinic = netdev->priv; + struct s_PXENV_UNDI_TRANSMIT undi_transmit; + size_t len = iob_len ( iobuf ); + int rc; + + /* Technically, we ought to make sure that the previous + * transmission has completed before we re-use the buffer. + * However, many PXE stacks (including at least some Intel PXE + * stacks and Etherboot 5.4) fail to generate TX completions. + * In practice this won't be a problem, since our TX datapath + * has a very low packet volume and we can get away with + * assuming that a TX will be complete by the time we want to + * transmit the next packet. + */ + + /* Copy packet to UNDI I/O buffer */ + if ( len > sizeof ( basemem_packet ) ) + len = sizeof ( basemem_packet ); + memcpy ( &basemem_packet, iobuf->data, len ); + + /* Create PXENV_UNDI_TRANSMIT data structure */ + memset ( &undi_transmit, 0, sizeof ( undi_transmit ) ); + undi_transmit.DestAddr.segment = rm_ds; + undi_transmit.DestAddr.offset + = ( ( unsigned ) & __from_data16 ( undinet_tbd ) ); + undi_transmit.TBD.segment = rm_ds; + undi_transmit.TBD.offset + = ( ( unsigned ) & __from_data16 ( undinet_tbd ) ); + + /* Create PXENV_UNDI_TBD data structure */ + undinet_tbd.ImmedLength = len; + undinet_tbd.Xmit.segment = rm_ds; + undinet_tbd.Xmit.offset + = ( ( unsigned ) & __from_data16 ( basemem_packet ) ); + + /* Issue PXE API call */ + if ( ( rc = undinet_call ( undinic, PXENV_UNDI_TRANSMIT, + &undi_transmit, + sizeof ( undi_transmit ) ) ) != 0 ) + goto done; + + /* Free I/O buffer */ + netdev_tx_complete ( netdev, iobuf ); + + done: + return rc; +} + +/** + * Poll for received packets + * + * @v netdev Network device + * + * Fun, fun, fun. UNDI drivers don't use polling; they use + * interrupts. We therefore cheat and pretend that an interrupt has + * occurred every time undinet_poll() is called. This isn't too much + * of a hack; PCI devices share IRQs and so the first thing that a + * proper ISR should do is call PXENV_UNDI_ISR to determine whether or + * not the UNDI NIC generated the interrupt; there is no harm done by + * spurious calls to PXENV_UNDI_ISR. Similarly, we wouldn't be + * handling them any more rapidly than the usual rate of + * undinet_poll() being called even if we did implement a full ISR. + * So it should work. Ha! + * + * Addendum (21/10/03). Some cards don't play nicely with this trick, + * so instead of doing it the easy way we have to go to all the hassle + * of installing a genuine interrupt service routine and dealing with + * the wonderful 8259 Programmable Interrupt Controller. Joy. + * + * Addendum (10/07/07). When doing things such as iSCSI boot, in + * which we have to co-operate with a running OS, we can't get away + * with the "ISR-just-increments-a-counter-and-returns" trick at all, + * because it involves tying up the PIC for far too long, and other + * interrupt-dependent components (e.g. local disks) start breaking. + * We therefore implement a "proper" ISR which calls PXENV_UNDI_ISR + * from within interrupt context in order to deassert the device + * interrupt, and sends EOI if applicable. + */ +static void undinet_poll ( struct net_device *netdev ) { + struct undi_nic *undinic = netdev->priv; + struct s_PXENV_UNDI_ISR undi_isr; + struct io_buffer *iobuf = NULL; + size_t len; + size_t frag_len; + size_t max_frag_len; + int rc; + + if ( ! undinic->isr_processing ) { + /* Do nothing unless ISR has been triggered */ + if ( ! undinet_isr_triggered() ) { + /* Allow interrupt to occur */ + __asm__ __volatile__ ( REAL_CODE ( "sti\n\t" + "nop\n\t" + "nop\n\t" + "cli\n\t" ) : : ); + return; + } + + /* Start ISR processing */ + undinic->isr_processing = 1; + undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS; + } else { + /* Continue ISR processing */ + undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT; + } + + /* Run through the ISR loop */ + while ( 1 ) { + if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr, + sizeof ( undi_isr ) ) ) != 0 ) + break; + switch ( undi_isr.FuncFlag ) { + case PXENV_UNDI_ISR_OUT_TRANSMIT: + /* We don't care about transmit completions */ + break; + case PXENV_UNDI_ISR_OUT_RECEIVE: + /* Packet fragment received */ + len = undi_isr.FrameLength; + frag_len = undi_isr.BufferLength; + if ( ( len == 0 ) || ( len < frag_len ) ) { + /* Don't laugh. VMWare does it. */ + DBGC ( undinic, "UNDINIC %p reported insane " + "fragment (%zd of %zd bytes)\n", + undinic, frag_len, len ); + netdev_rx_err ( netdev, NULL, -EINVAL ); + break; + } + if ( ! iobuf ) + iobuf = alloc_iob ( len ); + if ( ! iobuf ) { + DBGC ( undinic, "UNDINIC %p could not " + "allocate %zd bytes for RX buffer\n", + undinic, len ); + /* Fragment will be dropped */ + netdev_rx_err ( netdev, NULL, -ENOMEM ); + goto done; + } + max_frag_len = iob_tailroom ( iobuf ); + if ( frag_len > max_frag_len ) { + DBGC ( undinic, "UNDINIC %p fragment too big " + "(%zd+%zd does not fit into %zd)\n", + undinic, iob_len ( iobuf ), frag_len, + ( iob_len ( iobuf ) + max_frag_len ) ); + frag_len = max_frag_len; + } + copy_from_real ( iob_put ( iobuf, frag_len ), + undi_isr.Frame.segment, + undi_isr.Frame.offset, frag_len ); + if ( iob_len ( iobuf ) == len ) { + /* Whole packet received; deliver it */ + netdev_rx ( netdev, iobuf ); + iobuf = NULL; + /* Etherboot 5.4 fails to return all packets + * under mild load; pretend it retriggered. + */ + if ( undinic->hacks & UNDI_HACK_EB54 ) + --last_trigger_count; + } + break; + case PXENV_UNDI_ISR_OUT_DONE: + /* Processing complete */ + undinic->isr_processing = 0; + goto done; + default: + /* Should never happen. VMWare does it routinely. */ + DBGC ( undinic, "UNDINIC %p ISR returned invalid " + "FuncFlag %04x\n", undinic, undi_isr.FuncFlag ); + undinic->isr_processing = 0; + goto done; + } + undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT; + } + + done: + if ( iobuf ) { + DBGC ( undinic, "UNDINIC %p returned incomplete packet " + "(%zd of %zd)\n", undinic, iob_len ( iobuf ), + ( iob_len ( iobuf ) + iob_tailroom ( iobuf ) ) ); + netdev_rx_err ( netdev, iobuf, -EINVAL ); + } +} + +/** + * Open NIC + * + * @v netdev Net device + * @ret rc Return status code + */ +static int undinet_open ( struct net_device *netdev ) { + struct undi_nic *undinic = netdev->priv; + struct s_PXENV_UNDI_SET_STATION_ADDRESS undi_set_address; + struct s_PXENV_UNDI_OPEN undi_open; + int rc; + + /* Hook interrupt service routine and enable interrupt */ + undinet_hook_isr ( undinic->irq ); + enable_irq ( undinic->irq ); + send_eoi ( undinic->irq ); + + /* Set station address. Required for some PXE stacks; will + * spuriously fail on others. Ignore failures. We only ever + * use it to set the MAC address to the card's permanent value + * anyway. + */ + memcpy ( undi_set_address.StationAddress, netdev->ll_addr, + sizeof ( undi_set_address.StationAddress ) ); + undinet_call ( undinic, PXENV_UNDI_SET_STATION_ADDRESS, + &undi_set_address, sizeof ( undi_set_address ) ); + + /* Open NIC */ + memset ( &undi_open, 0, sizeof ( undi_open ) ); + undi_open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST ); + if ( ( rc = undinet_call ( undinic, PXENV_UNDI_OPEN, &undi_open, + sizeof ( undi_open ) ) ) != 0 ) + goto err; + + DBGC ( undinic, "UNDINIC %p opened\n", undinic ); + return 0; + +err: + undinet_close ( netdev ); + return rc; +} + +/** + * Close NIC + * + * @v netdev Net device + */ +static void undinet_close ( struct net_device *netdev ) { + struct undi_nic *undinic = netdev->priv; + struct s_PXENV_UNDI_ISR undi_isr; + struct s_PXENV_UNDI_CLOSE undi_close; + int rc; + + /* Ensure ISR has exited cleanly */ + while ( undinic->isr_processing ) { + undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT; + if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr, + sizeof ( undi_isr ) ) ) != 0 ) + break; + switch ( undi_isr.FuncFlag ) { + case PXENV_UNDI_ISR_OUT_TRANSMIT: + case PXENV_UNDI_ISR_OUT_RECEIVE: + /* Continue draining */ + break; + default: + /* Stop processing */ + undinic->isr_processing = 0; + break; + } + } + + /* Close NIC */ + undinet_call ( undinic, PXENV_UNDI_CLOSE, &undi_close, + sizeof ( undi_close ) ); + + /* Disable interrupt and unhook ISR */ + disable_irq ( undinic->irq ); + undinet_unhook_isr ( undinic->irq ); +#if 0 + enable_irq ( undinic->irq ); + send_eoi ( undinic->irq ); +#endif + + DBGC ( undinic, "UNDINIC %p closed\n", undinic ); +} + +/** + * Enable/disable interrupts + * + * @v netdev Net device + * @v enable Interrupts should be enabled + */ +static void undinet_irq ( struct net_device *netdev, int enable ) { + struct undi_nic *undinic = netdev->priv; + + /* Cannot support interrupts yet */ + DBGC ( undinic, "UNDINIC %p cannot %s interrupts\n", + undinic, ( enable ? "enable" : "disable" ) ); +} + +/** UNDI network device operations */ +static struct net_device_operations undinet_operations = { + .open = undinet_open, + .close = undinet_close, + .transmit = undinet_transmit, + .poll = undinet_poll, + .irq = undinet_irq, +}; + +/** + * Probe UNDI device + * + * @v undi UNDI device + * @ret rc Return status code + */ +int undinet_probe ( struct undi_device *undi ) { + struct net_device *netdev; + struct undi_nic *undinic; + struct s_PXENV_START_UNDI start_undi; + struct s_PXENV_UNDI_STARTUP undi_startup; + struct s_PXENV_UNDI_INITIALIZE undi_initialize; + struct s_PXENV_UNDI_GET_INFORMATION undi_info; + struct s_PXENV_UNDI_GET_IFACE_INFO undi_iface; + struct s_PXENV_UNDI_SHUTDOWN undi_shutdown; + struct s_PXENV_UNDI_CLEANUP undi_cleanup; +#if 0 + struct s_PXENV_STOP_UNDI stop_undi; +#endif + int rc; + + /* Allocate net device */ + netdev = alloc_etherdev ( sizeof ( *undinic ) ); + if ( ! netdev ) + return -ENOMEM; + netdev_init ( netdev, &undinet_operations ); + undinic = netdev->priv; + undi_set_drvdata ( undi, netdev ); + netdev->dev = &undi->dev; + memset ( undinic, 0, sizeof ( *undinic ) ); + undinet_entry_point = undi->entry; + DBGC ( undinic, "UNDINIC %p using UNDI %p\n", undinic, undi ); + + /* Hook in UNDI stack */ + if ( ! ( undi->flags & UNDI_FL_STARTED ) ) { + memset ( &start_undi, 0, sizeof ( start_undi ) ); + start_undi.AX = undi->pci_busdevfn; + start_undi.BX = undi->isapnp_csn; + start_undi.DX = undi->isapnp_read_port; + start_undi.ES = BIOS_SEG; + start_undi.DI = find_pnp_bios(); + if ( ( rc = undinet_call ( undinic, PXENV_START_UNDI, + &start_undi, + sizeof ( start_undi ) ) ) != 0 ) + goto err_start_undi; + /* Bring up UNDI stack */ + memset ( &undi_startup, 0, sizeof ( undi_startup ) ); + if ( ( rc = undinet_call ( undinic, PXENV_UNDI_STARTUP, + &undi_startup, + sizeof ( undi_startup ) ) ) != 0 ) + goto err_undi_startup; + + memset ( &undi_initialize, 0, sizeof ( undi_initialize ) ); + if ( ( rc = undinet_call ( undinic, PXENV_UNDI_INITIALIZE, + &undi_initialize, + sizeof ( undi_initialize ) ) ) != 0 ) + goto err_undi_initialize; + } + undi->flags |= UNDI_FL_STARTED; + + /* Get device information */ + memset ( &undi_info, 0, sizeof ( undi_info ) ); + if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_INFORMATION, + &undi_info, sizeof ( undi_info ) ) ) != 0 ) + goto err_undi_get_information; + memcpy ( netdev->ll_addr, undi_info.PermNodeAddress, ETH_ALEN ); + undinic->irq = undi_info.IntNumber; + if ( undinic->irq > IRQ_MAX ) { + DBGC ( undinic, "UNDINIC %p invalid IRQ %d\n", + undinic, undinic->irq ); + goto err_bad_irq; + } + DBGC ( undinic, "UNDINIC %p is %s on IRQ %d\n", + undinic, eth_ntoa ( netdev->ll_addr ), undinic->irq ); + + /* Get interface information */ + memset ( &undi_iface, 0, sizeof ( undi_iface ) ); + if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_IFACE_INFO, + &undi_iface, + sizeof ( undi_iface ) ) ) != 0 ) + goto err_undi_get_iface_info; + DBGC ( undinic, "UNDINIC %p has type %s and link speed %ld\n", + undinic, undi_iface.IfaceType, undi_iface.LinkSpeed ); + if ( strncmp ( ( ( char * ) undi_iface.IfaceType ), "Etherboot", + sizeof ( undi_iface.IfaceType ) ) == 0 ) { + DBGC ( undinic, "UNDINIC %p Etherboot 5.4 workaround enabled\n", + undinic ); + undinic->hacks |= UNDI_HACK_EB54; + } + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) + goto err_register; + + DBGC ( undinic, "UNDINIC %p added\n", undinic ); + return 0; + + err_register: + err_undi_get_iface_info: + err_bad_irq: + err_undi_get_information: + err_undi_initialize: + + /* Shut down UNDI stack */ + memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) ); + undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown, + sizeof ( undi_shutdown ) ); + memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) ); + undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup, + sizeof ( undi_cleanup ) ); + err_undi_startup: +#if 0 + /* Unhook UNDI stack */ + memset ( &stop_undi, 0, sizeof ( stop_undi ) ); + undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi, + sizeof ( stop_undi ) ); +#endif + err_start_undi: + netdev_nullify ( netdev ); + netdev_put ( netdev ); + undi_set_drvdata ( undi, NULL ); + return rc; +} + +/** + * Remove UNDI device + * + * @v undi UNDI device + */ +void undinet_remove ( struct undi_device *undi ) { + struct net_device *netdev = undi_get_drvdata ( undi ); + struct undi_nic *undinic = netdev->priv; +#if 0 + struct s_PXENV_UNDI_SHUTDOWN undi_shutdown; + struct s_PXENV_UNDI_CLEANUP undi_cleanup; + struct s_PXENV_STOP_UNDI stop_undi; +#endif + + /* Unregister net device */ + unregister_netdev ( netdev ); + + /* Shut down UNDI stack */ +#if 0 + memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) ); + undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown, + sizeof ( undi_shutdown ) ); + memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) ); + undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup, + sizeof ( undi_cleanup ) ); + + /* Unhook UNDI stack */ + memset ( &stop_undi, 0, sizeof ( stop_undi ) ); + undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi, + sizeof ( stop_undi ) ); + undi->flags &= ~UNDI_FL_STARTED; +#endif + + /* Clear entry point */ + memset ( &undinet_entry_point, 0, sizeof ( undinet_entry_point ) ); + + /* Free network device */ + netdev_nullify ( netdev ); + netdev_put ( netdev ); + + DBGC ( undinic, "UNDINIC %p removed\n", undinic ); +} diff --git a/gpxe/src/arch/i386/drivers/net/undionly.c b/gpxe/src/arch/i386/drivers/net/undionly.c new file mode 100644 index 00000000..ee361493 --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undionly.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <gpxe/device.h> +#include <undi.h> +#include <undinet.h> +#include <undipreload.h> + +/** @file + * + * "Pure" UNDI driver + * + * This is the UNDI driver without explicit support for PCI or any + * other bus type. It is capable only of using the preloaded UNDI + * device. It must not be combined in an image with any other + * drivers. + * + * If you want a PXE-loadable image that contains only the UNDI + * driver, build "bin/undionly.kpxe". + * + * If you want any other image format, or any other drivers in + * addition to the UNDI driver, build e.g. "bin/undi.dsk". + */ + +/** + * Probe UNDI root bus + * + * @v rootdev UNDI bus root device + * + * Scans the UNDI bus for devices and registers all devices it can + * find. + */ +static int undibus_probe ( struct root_device *rootdev ) { + struct undi_device *undi = &preloaded_undi; + int rc; + + /* Check for a valie preloaded UNDI device */ + if ( ! undi->entry.segment ) { + DBG ( "No preloaded UNDI device found!\n" ); + return -ENODEV; + } + + /* Add to device hierarchy */ + strncpy ( undi->dev.name, "UNDI", + ( sizeof ( undi->dev.name ) - 1 ) ); + if ( undi->pci_busdevfn != UNDI_NO_PCI_BUSDEVFN ) { + undi->dev.desc.bus_type = BUS_TYPE_PCI; + undi->dev.desc.location = undi->pci_busdevfn; + undi->dev.desc.vendor = undi->pci_vendor; + undi->dev.desc.device = undi->pci_device; + } else if ( undi->isapnp_csn != UNDI_NO_ISAPNP_CSN ) { + undi->dev.desc.bus_type = BUS_TYPE_ISAPNP; + } + undi->dev.parent = &rootdev->dev; + list_add ( &undi->dev.siblings, &rootdev->dev.children); + INIT_LIST_HEAD ( &undi->dev.children ); + + /* Create network device */ + if ( ( rc = undinet_probe ( undi ) ) != 0 ) + goto err; + + return 0; + + err: + list_del ( &undi->dev.siblings ); + return rc; +} + +/** + * Remove UNDI root bus + * + * @v rootdev UNDI bus root device + */ +static void undibus_remove ( struct root_device *rootdev __unused ) { + struct undi_device *undi = &preloaded_undi; + + undinet_remove ( undi ); + list_del ( &undi->dev.siblings ); +} + +/** UNDI bus root device driver */ +static struct root_driver undi_root_driver = { + .probe = undibus_probe, + .remove = undibus_remove, +}; + +/** UNDI bus root device */ +struct root_device undi_root_device __root_device = { + .dev = { .name = "UNDI" }, + .driver = &undi_root_driver, +}; diff --git a/gpxe/src/arch/i386/drivers/net/undipreload.c b/gpxe/src/arch/i386/drivers/net/undipreload.c new file mode 100644 index 00000000..e29d150a --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undipreload.c @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <realmode.h> +#include <undipreload.h> + +/** @file + * + * Preloaded UNDI stack + * + */ + +/** + * Preloaded UNDI device + * + * This is the UNDI device that was present when Etherboot started + * execution (i.e. when loading a .kpxe image). The first driver to + * claim this device must zero out this data structure. + */ +struct undi_device __data16 ( preloaded_undi ); diff --git a/gpxe/src/arch/i386/drivers/net/undirom.c b/gpxe/src/arch/i386/drivers/net/undirom.c new file mode 100644 index 00000000..f977a553 --- /dev/null +++ b/gpxe/src/arch/i386/drivers/net/undirom.c @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <pxe.h> +#include <realmode.h> +#include <undirom.h> + +/** @file + * + * UNDI expansion ROMs + * + */ + +/** List of all UNDI ROMs */ +static LIST_HEAD ( undiroms ); + +/** + * Parse PXE ROM ID structure + * + * @v undirom UNDI ROM + * @v pxeromid Offset within ROM to PXE ROM ID structure + * @ret rc Return status code + */ +static int undirom_parse_pxeromid ( struct undi_rom *undirom, + unsigned int pxeromid ) { + struct undi_rom_id undi_rom_id; + unsigned int undiloader; + + DBGC ( undirom, "UNDIROM %p has PXE ROM ID at %04x:%04x\n", undirom, + undirom->rom_segment, pxeromid ); + + /* Read PXE ROM ID structure and verify */ + copy_from_real ( &undi_rom_id, undirom->rom_segment, pxeromid, + sizeof ( undi_rom_id ) ); + if ( undi_rom_id.Signature != UNDI_ROM_ID_SIGNATURE ) { + DBGC ( undirom, "UNDIROM %p has bad PXE ROM ID signature " + "%08lx\n", undirom, undi_rom_id.Signature ); + return -EINVAL; + } + + /* Check for UNDI loader */ + undiloader = undi_rom_id.UNDILoader; + if ( ! undiloader ) { + DBGC ( undirom, "UNDIROM %p has no UNDI loader\n", undirom ); + return -EINVAL; + } + + /* Fill in UNDI ROM loader fields */ + undirom->loader_entry.segment = undirom->rom_segment; + undirom->loader_entry.offset = undiloader; + undirom->code_size = undi_rom_id.CodeSize; + undirom->data_size = undi_rom_id.DataSize; + + DBGC ( undirom, "UNDIROM %p has UNDI loader at %04x:%04x " + "(code %04zx data %04zx)\n", undirom, + undirom->loader_entry.segment, undirom->loader_entry.offset, + undirom->code_size, undirom->data_size ); + return 0; +} + +/** + * Parse PCI expansion header + * + * @v undirom UNDI ROM + * @v pcirheader Offset within ROM to PCI expansion header + */ +static int undirom_parse_pcirheader ( struct undi_rom *undirom, + unsigned int pcirheader ) { + struct pcir_header pcir_header; + + DBGC ( undirom, "UNDIROM %p has PCI expansion header at %04x:%04x\n", + undirom, undirom->rom_segment, pcirheader ); + + /* Read PCI expansion header and verify */ + copy_from_real ( &pcir_header, undirom->rom_segment, pcirheader, + sizeof ( pcir_header ) ); + if ( pcir_header.signature != PCIR_SIGNATURE ) { + DBGC ( undirom, "UNDIROM %p has bad PCI expansion header " + "signature %08lx\n", undirom, pcir_header.signature ); + return -EINVAL; + } + + /* Fill in UNDI ROM PCI device fields */ + undirom->bus_type = PCI_NIC; + undirom->bus_id.pci.vendor_id = pcir_header.vendor_id; + undirom->bus_id.pci.device_id = pcir_header.device_id; + + DBGC ( undirom, "UNDIROM %p is for PCI devices %04x:%04x\n", undirom, + undirom->bus_id.pci.vendor_id, undirom->bus_id.pci.device_id ); + return 0; + +} + +/** + * Probe UNDI ROM + * + * @v rom_segment ROM segment address + * @ret rc Return status code + */ +static int undirom_probe ( unsigned int rom_segment ) { + struct undi_rom *undirom = NULL; + struct undi_rom_header romheader; + size_t rom_len; + unsigned int pxeromid; + unsigned int pcirheader; + int rc; + + /* Read expansion ROM header and verify */ + copy_from_real ( &romheader, rom_segment, 0, sizeof ( romheader ) ); + if ( romheader.Signature != ROM_SIGNATURE ) { + rc = -EINVAL; + goto err; + } + rom_len = ( romheader.ROMLength * 512 ); + + /* Allocate memory for UNDI ROM */ + undirom = zalloc ( sizeof ( *undirom ) ); + if ( ! undirom ) { + DBG ( "Could not allocate UNDI ROM structure\n" ); + rc = -ENOMEM; + goto err; + } + DBGC ( undirom, "UNDIROM %p trying expansion ROM at %04x:0000 " + "(%zdkB)\n", undirom, rom_segment, ( rom_len / 1024 ) ); + undirom->rom_segment = rom_segment; + + /* Check for and parse PXE ROM ID */ + pxeromid = romheader.PXEROMID; + if ( ! pxeromid ) { + DBGC ( undirom, "UNDIROM %p has no PXE ROM ID\n", undirom ); + rc = -EINVAL; + goto err; + } + if ( pxeromid > rom_len ) { + DBGC ( undirom, "UNDIROM %p PXE ROM ID outside ROM\n", + undirom ); + rc = -EINVAL; + goto err; + } + if ( ( rc = undirom_parse_pxeromid ( undirom, pxeromid ) ) != 0 ) + goto err; + + /* Parse PCIR header, if present */ + pcirheader = romheader.PCIRHeader; + if ( pcirheader ) + undirom_parse_pcirheader ( undirom, pcirheader ); + + /* Add to UNDI ROM list and return */ + DBGC ( undirom, "UNDIROM %p registered\n", undirom ); + list_add ( &undirom->list, &undiroms ); + return 0; + + err: + free ( undirom ); + return rc; +} + +/** + * Create UNDI ROMs for all possible expansion ROMs + * + * @ret + */ +static void undirom_probe_all_roms ( void ) { + static int probed = 0; + unsigned int rom_segment; + + /* Perform probe only once */ + if ( probed ) + return; + + DBG ( "Scanning for PXE expansion ROMs\n" ); + + /* Scan through expansion ROM region at 2kB intervals */ + for ( rom_segment = 0xc000 ; rom_segment < 0x10000 ; + rom_segment += 0x80 ) { + undirom_probe ( rom_segment ); + } + + probed = 1; +} + +/** + * Find UNDI ROM for PCI device + * + * @v vendor_id PCI vendor ID + * @v device_id PCI device ID + * @v rombase ROM base address, or 0 for any + * @ret undirom UNDI ROM, or NULL + */ +struct undi_rom * undirom_find_pci ( unsigned int vendor_id, + unsigned int device_id, + unsigned int rombase ) { + struct undi_rom *undirom; + + undirom_probe_all_roms(); + + list_for_each_entry ( undirom, &undiroms, list ) { + if ( undirom->bus_type != PCI_NIC ) + continue; + if ( undirom->bus_id.pci.vendor_id != vendor_id ) + continue; + if ( undirom->bus_id.pci.device_id != device_id ) + continue; + if ( rombase && ( ( undirom->rom_segment << 4 ) != rombase ) ) + continue; + DBGC ( undirom, "UNDIROM %p matched PCI %04x:%04x (%08x)\n", + undirom, vendor_id, device_id, rombase ); + return undirom; + } + + DBG ( "No UNDI ROM matched PCI %04x:%04x (%08x)\n", + vendor_id, device_id, rombase ); + return NULL; +} diff --git a/gpxe/src/arch/i386/drivers/timer_bios.c b/gpxe/src/arch/i386/drivers/timer_bios.c new file mode 100644 index 00000000..f9caf8d9 --- /dev/null +++ b/gpxe/src/arch/i386/drivers/timer_bios.c @@ -0,0 +1,57 @@ +/* + * Etherboot routines for PCBIOS firmware. + * + * Body of routines taken from old pcbios.S + */ + +#include <gpxe/init.h> +#include <gpxe/timer.h> +#include <stdio.h> +#include <realmode.h> +#include <bios.h> +#include <bits/timer2.h> + +/* A bit faster actually, but we don't care. */ +#define TIMER2_TICKS_PER_SEC 18 + +/* + * Use direct memory access to BIOS variables, longword 0040:006C (ticks + * today) and byte 0040:0070 (midnight crossover flag) instead of calling + * timeofday BIOS interrupt. + */ + +static tick_t bios_currticks ( void ) { + static int days = 0; + uint32_t ticks; + uint8_t midnight; + + /* Re-enable interrupts so that the timer interrupt can occur */ + __asm__ __volatile__ ( REAL_CODE ( "sti\n\t" + "nop\n\t" + "nop\n\t" + "cli\n\t" ) : : ); + + get_real ( ticks, BDA_SEG, 0x006c ); + get_real ( midnight, BDA_SEG, 0x0070 ); + + if ( midnight ) { + midnight = 0; + put_real ( midnight, BDA_SEG, 0x0070 ); + days += 0x1800b0; + } + + return ( (days + ticks) * (USECS_IN_SEC / TIMER2_TICKS_PER_SEC) ); +} + +static int bios_ts_init(void) +{ + DBG("BIOS timer installed\n"); + return 0; +} + +struct timer bios_ts __timer ( 02 ) = { + .init = bios_ts_init, + .udelay = i386_timer2_udelay, + .currticks = bios_currticks, +}; + diff --git a/gpxe/src/arch/i386/drivers/timer_rdtsc.c b/gpxe/src/arch/i386/drivers/timer_rdtsc.c new file mode 100644 index 00000000..09b7df2f --- /dev/null +++ b/gpxe/src/arch/i386/drivers/timer_rdtsc.c @@ -0,0 +1,69 @@ + +#include <gpxe/init.h> +#include <gpxe/timer.h> +#include <errno.h> +#include <stdio.h> +#include <bits/cpu.h> +#include <bits/timer2.h> +#include <io.h> + + +#define rdtsc(low,high) \ + __asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high)) + +#define rdtscll(val) \ + __asm__ __volatile__ ("rdtsc" : "=A" (val)) + + +/* Measure how many clocks we get in one microsecond */ +static inline uint64_t calibrate_tsc(void) +{ + + uint64_t rdtsc_start; + uint64_t rdtsc_end; + + rdtscll(rdtsc_start); + i386_timer2_udelay(USECS_IN_MSEC); + rdtscll(rdtsc_end); + + return (rdtsc_end - rdtsc_start) / USECS_IN_MSEC; +} + +static uint32_t clocks_per_usec = 0; + +/* We measure time in microseconds. */ +static tick_t rdtsc_currticks(void) +{ + uint64_t clocks; + + /* Read the Time Stamp Counter */ + rdtscll(clocks); + + return clocks / clocks_per_usec; +} + +static int rdtsc_ts_init(void) +{ + + struct cpuinfo_x86 cpu_info; + + get_cpuinfo(&cpu_info); + if (cpu_info.features & X86_FEATURE_TSC) { + clocks_per_usec= calibrate_tsc(); + if (clocks_per_usec) { + DBG("RDTSC ticksource installed. CPU running at %ld Mhz\n", + clocks_per_usec); + return 0; + } + } + + DBG("RDTSC ticksource not available on this machine.\n"); + return -ENODEV; +} + +struct timer rdtsc_ts __timer (01) = { + .init = rdtsc_ts_init, + .udelay = generic_currticks_udelay, + .currticks = rdtsc_currticks, +}; + diff --git a/gpxe/src/arch/i386/firmware/pcbios/basemem.c b/gpxe/src/arch/i386/firmware/pcbios/basemem.c new file mode 100644 index 00000000..b126d2a7 --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/basemem.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <realmode.h> +#include <bios.h> +#include <basemem.h> +#include <gpxe/hidemem.h> + +/** @file + * + * Base memory allocation + * + */ + +/** + * Set the BIOS free base memory counter + * + * @v new_fbms New free base memory counter (in kB) + */ +void set_fbms ( unsigned int new_fbms ) { + uint16_t fbms = new_fbms; + + /* Update the BIOS memory counter */ + put_real ( fbms, BDA_SEG, BDA_FBMS ); + + /* Update our hidden memory region map */ + hide_basemem(); +} diff --git a/gpxe/src/arch/i386/firmware/pcbios/bios_console.c b/gpxe/src/arch/i386/firmware/pcbios/bios_console.c new file mode 100644 index 00000000..dcb0462a --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/bios_console.c @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <assert.h> +#include <realmode.h> +#include <console.h> +#include <gpxe/ansiesc.h> + +#define ATTR_BOLD 0x08 + +#define ATTR_FCOL_MASK 0x07 +#define ATTR_FCOL_BLACK 0x00 +#define ATTR_FCOL_BLUE 0x01 +#define ATTR_FCOL_GREEN 0x02 +#define ATTR_FCOL_CYAN 0x03 +#define ATTR_FCOL_RED 0x04 +#define ATTR_FCOL_MAGENTA 0x05 +#define ATTR_FCOL_YELLOW 0x06 +#define ATTR_FCOL_WHITE 0x07 + +#define ATTR_BCOL_MASK 0x70 +#define ATTR_BCOL_BLACK 0x00 +#define ATTR_BCOL_BLUE 0x10 +#define ATTR_BCOL_GREEN 0x20 +#define ATTR_BCOL_CYAN 0x30 +#define ATTR_BCOL_RED 0x40 +#define ATTR_BCOL_MAGENTA 0x50 +#define ATTR_BCOL_YELLOW 0x60 +#define ATTR_BCOL_WHITE 0x70 + +#define ATTR_DEFAULT ATTR_FCOL_WHITE + +/** Current character attribute */ +static unsigned int bios_attr = ATTR_DEFAULT; + +/** + * Handle ANSI CUP (cursor position) + * + * @v count Parameter count + * @v params[0] Row (1 is top) + * @v params[1] Column (1 is left) + */ +static void bios_handle_cup ( unsigned int count __unused, int params[] ) { + int cx = ( params[1] - 1 ); + int cy = ( params[0] - 1 ); + + if ( cx < 0 ) + cx = 0; + if ( cy < 0 ) + cy = 0; + + __asm__ __volatile__ ( REAL_CODE ( "sti\n\t" + "int $0x10\n\t" + "cli\n\t" ) + : : "a" ( 0x0200 ), "b" ( 1 ), + "d" ( ( cy << 8 ) | cx ) ); +} + +/** + * Handle ANSI ED (erase in page) + * + * @v count Parameter count + * @v params[0] Region to erase + */ +static void bios_handle_ed ( unsigned int count __unused, + int params[] __unused ) { + /* We assume that we always clear the whole screen */ + assert ( params[0] == ANSIESC_ED_ALL ); + + __asm__ __volatile__ ( REAL_CODE ( "sti\n\t" + "int $0x10\n\t" + "cli\n\t" ) + : : "a" ( 0x0600 ), "b" ( bios_attr << 8 ), + "c" ( 0 ), "d" ( 0xffff ) ); +} + +/** + * Handle ANSI SGR (set graphics rendition) + * + * @v count Parameter count + * @v params List of graphic rendition aspects + */ +static void bios_handle_sgr ( unsigned int count, int params[] ) { + static const uint8_t bios_attr_fcols[10] = { + ATTR_FCOL_BLACK, ATTR_FCOL_RED, ATTR_FCOL_GREEN, + ATTR_FCOL_YELLOW, ATTR_FCOL_BLUE, ATTR_FCOL_MAGENTA, + ATTR_FCOL_CYAN, ATTR_FCOL_WHITE, + ATTR_FCOL_WHITE, ATTR_FCOL_WHITE /* defaults */ + }; + static const uint8_t bios_attr_bcols[10] = { + ATTR_BCOL_BLACK, ATTR_BCOL_RED, ATTR_BCOL_GREEN, + ATTR_BCOL_YELLOW, ATTR_BCOL_BLUE, ATTR_BCOL_MAGENTA, + ATTR_BCOL_CYAN, ATTR_BCOL_WHITE, + ATTR_BCOL_BLACK, ATTR_BCOL_BLACK /* defaults */ + }; + unsigned int i; + int aspect; + + for ( i = 0 ; i < count ; i++ ) { + aspect = params[i]; + if ( aspect == 0 ) { + bios_attr = ATTR_DEFAULT; + } else if ( aspect == 1 ) { + bios_attr |= ATTR_BOLD; + } else if ( aspect == 22 ) { + bios_attr &= ~ATTR_BOLD; + } else if ( ( aspect >= 30 ) && ( aspect <= 39 ) ) { + bios_attr &= ~ATTR_FCOL_MASK; + bios_attr |= bios_attr_fcols[ aspect - 30 ]; + } else if ( ( aspect >= 40 ) && ( aspect <= 49 ) ) { + bios_attr &= ~ATTR_BCOL_MASK; + bios_attr |= bios_attr_bcols[ aspect - 40 ]; + } + } +} + +/** BIOS console ANSI escape sequence handlers */ +static struct ansiesc_handler bios_ansiesc_handlers[] = { + { ANSIESC_CUP, bios_handle_cup }, + { ANSIESC_ED, bios_handle_ed }, + { ANSIESC_SGR, bios_handle_sgr }, + { 0, NULL } +}; + +/** BIOS console ANSI escape sequence context */ +static struct ansiesc_context bios_ansiesc_ctx = { + .handlers = bios_ansiesc_handlers, +}; + +/** + * Print a character to BIOS console + * + * @v character Character to be printed + */ +static void bios_putchar ( int character ) { + int discard_a, discard_b, discard_c; + + /* Intercept ANSI escape sequences */ + character = ansiesc_process ( &bios_ansiesc_ctx, character ); + if ( character < 0 ) + return; + + /* Print character with attribute */ + __asm__ __volatile__ ( REAL_CODE ( "sti\n\t" + /* Skip non-printable characters */ + "cmpb $0x20, %%al\n\t" + "jb 1f\n\t" + /* Set attribute */ + "movw $0x0001, %%cx\n\t" + "movb $0x09, %%ah\n\t" + "int $0x10\n\t" + "\n1:\n\t" + /* Print character */ + "xorw %%bx, %%bx\n\t" + "movb $0x0e, %%ah\n\t" + "int $0x10\n\t" + "cli\n\t" ) + : "=a" ( discard_a ), "=b" ( discard_b ), + "=c" ( discard_c ) + : "a" ( character ), "b" ( bios_attr ) + : "ebp" ); +} + +/** + * Pointer to current ANSI output sequence + * + * While we are in the middle of returning an ANSI sequence for a + * special key, this will point to the next character to return. When + * not in the middle of such a sequence, this will point to a NUL + * (note: not "will be NULL"). + */ +static const char *ansi_input = ""; + +/** + * Lowest BIOS scancode of interest + * + * Most of the BIOS key scancodes that we are interested in are in a + * dense range, so subtracting a constant and treating them as offsets + * into an array works efficiently. + */ +#define BIOS_KEY_MIN 0x47 + +/** Offset into list of interesting BIOS scancodes */ +#define BIOS_KEY(scancode) ( (scancode) - BIOS_KEY_MIN ) + +/** Mapping from BIOS scan codes to ANSI escape sequences */ +static const char *ansi_sequences[] = { + [ BIOS_KEY ( 0x47 ) ] = "[H", /* Home */ + [ BIOS_KEY ( 0x48 ) ] = "[A", /* Up arrow */ + [ BIOS_KEY ( 0x4b ) ] = "[D", /* Left arrow */ + [ BIOS_KEY ( 0x4d ) ] = "[C", /* Right arrow */ + [ BIOS_KEY ( 0x4f ) ] = "[F", /* End */ + [ BIOS_KEY ( 0x50 ) ] = "[B", /* Down arrow */ + [ BIOS_KEY ( 0x53 ) ] = "[3~", /* Delete */ +}; + +/** + * Get ANSI escape sequence corresponding to BIOS scancode + * + * @v scancode BIOS scancode + * @ret ansi_seq ANSI escape sequence, if any, otherwise NULL + */ +static const char * scancode_to_ansi_seq ( unsigned int scancode ) { + unsigned int bios_key = BIOS_KEY ( scancode ); + + if ( bios_key < ( sizeof ( ansi_sequences ) / + sizeof ( ansi_sequences[0] ) ) ) { + return ansi_sequences[bios_key]; + } + return NULL; +} + +/** + * Get character from BIOS console + * + * @ret character Character read from console + */ +static int bios_getchar ( void ) { + uint16_t keypress; + unsigned int character; + const char *ansi_seq; + + /* If we are mid-sequence, pass out the next byte */ + if ( ( character = *ansi_input ) ) { + ansi_input++; + return character; + } + + /* Read character from real BIOS console */ + __asm__ __volatile__ ( REAL_CODE ( "sti\n\t" + "int $0x16\n\t" + "cli\n\t" ) + : "=a" ( keypress ) : "a" ( 0x1000 ) ); + character = ( keypress & 0xff ); + + /* If it's a normal character, just return it */ + if ( character && ( character < 0x80 ) ) + return character; + + /* Otherwise, check for a special key that we know about */ + if ( ( ansi_seq = scancode_to_ansi_seq ( keypress >> 8 ) ) ) { + /* Start of escape sequence: return ESC (0x1b) */ + ansi_input = ansi_seq; + return 0x1b; + } + + return 0; +} + +/** + * Check for character ready to read from BIOS console + * + * @ret True Character available to read + * @ret False No character available to read + */ +static int bios_iskey ( void ) { + unsigned int discard_a; + unsigned int flags; + + /* If we are mid-sequence, we are always ready */ + if ( *ansi_input ) + return 1; + + /* Otherwise check the real BIOS console */ + __asm__ __volatile__ ( REAL_CODE ( "sti\n\t" + "int $0x16\n\t" + "pushfw\n\t" + "popw %w0\n\t" + "cli\n\t" ) + : "=r" ( flags ), "=a" ( discard_a ) + : "a" ( 0x0100 ) ); + return ( ! ( flags & ZF ) ); +} + +struct console_driver bios_console __console_driver = { + .putchar = bios_putchar, + .getchar = bios_getchar, + .iskey = bios_iskey, +}; diff --git a/gpxe/src/arch/i386/firmware/pcbios/e820mangler.S b/gpxe/src/arch/i386/firmware/pcbios/e820mangler.S new file mode 100644 index 00000000..e9328041 --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/e820mangler.S @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + .text + .arch i386 + .section ".text16", "ax", @progbits + .section ".data16", "aw", @progbits + .section ".text16.data", "aw", @progbits + .code16 + +#define SMAP 0x534d4150 + +/**************************************************************************** + * Check for overlap + * + * Parameters: + * %edx:%eax Region start + * %ecx:%ebx Region end + * %si Pointer to hidden region descriptor + * Returns: + * CF set Region overlaps + * CF clear No overlap + **************************************************************************** + */ + .section ".text16" +check_overlap: + /* If start >= hidden_end, there is no overlap. */ + testl %edx, %edx + jnz no_overlap + cmpl 4(%si), %eax + jae no_overlap + /* If end <= hidden_start, there is no overlap; equivalently, + * if end > hidden_start, there is overlap. + */ + testl %ecx, %ecx + jnz overlap + cmpl 0(%si), %ebx + ja overlap +no_overlap: + clc + ret +overlap: + stc + ret + .size check_overlap, . - check_overlap + +/**************************************************************************** + * Check for overflow/underflow + * + * Parameters: + * %edx:%eax Region start + * %ecx:%ebx Region end + * Returns: + * CF set start < end + * CF clear start >= end + **************************************************************************** + */ + .section ".text16" +check_overflow: + pushl %ecx + pushl %ebx + subl %eax, %ebx + sbbl %edx, %ecx + popl %ebx + popl %ecx + ret + .size check_overflow, . - check_overflow + +/**************************************************************************** + * Truncate towards start of region + * + * Parameters: + * %edx:%eax Region start + * %ecx:%ebx Region end + * %si Pointer to hidden region descriptor + * Returns: + * %edx:%eax Modified region start + * %ecx:%ebx Modified region end + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +truncate_to_start: + /* If overlaps, set region end = hidden region start */ + call check_overlap + jnc 99f + movl 0(%si), %ebx + xorl %ecx, %ecx + /* If region end < region start, set region end = region start */ + call check_overflow + jnc 1f + movl %eax, %ebx + movl %edx, %ecx +1: stc +99: ret + .size truncate_to_start, . - truncate_to_start + +/**************************************************************************** + * Truncate towards end of region + * + * Parameters: + * %edx:%eax Region start + * %ecx:%ebx Region end + * %si Pointer to hidden region descriptor + * Returns: + * %edx:%eax Modified region start + * %ecx:%ebx Modified region end + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +truncate_to_end: + /* If overlaps, set region start = hidden region end */ + call check_overlap + jnc 99f + movl 4(%si), %eax + xorl %edx, %edx + /* If region start > region end, set region start = region end */ + call check_overflow + jnc 1f + movl %ebx, %eax + movl %ecx, %edx +1: stc +99: ret + .size truncate_to_end, . - truncate_to_end + +/**************************************************************************** + * Truncate region + * + * Parameters: + * %edx:%eax Region start + * %ecx:%ebx Region length (*not* region end) + * %bp truncate_to_start or truncate_to_end + * Returns: + * %edx:%eax Modified region start + * %ecx:%ebx Modified region length + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +truncate: + pushw %si + pushfw + /* Convert (start,len) to (start,end) */ + addl %eax, %ebx + adcl %edx, %ecx + /* Hide all hidden regions, truncating as directed */ + movw $hidden_regions, %si +1: call *%bp + jnc 2f + popfw /* If CF was set, set stored CF in flags word on stack */ + stc + pushfw +2: addw $8, %si + cmpl $0, 0(%si) + jne 1b + /* Convert modified (start,end) back to (start,len) */ + subl %eax, %ebx + sbbl %edx, %ecx + popfw + popw %si + ret + .size truncate, . - truncate + +/**************************************************************************** + * Patch "memory above 1MB" figure + * + * Parameters: + * %ax Memory above 1MB, in 1kB blocks + * Returns: + * %ax Modified memory above 1M in 1kB blocks + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +patch_1m: + pushal + /* Convert to (start,len) format and call truncate */ + movw $truncate_to_start, %bp + xorl %ecx, %ecx + movzwl %ax, %ebx + shll $10, %ebx + xorl %edx, %edx + movl $0x100000, %eax + call truncate + /* Convert back to "memory above 1MB" format and return via %ax */ + pushfw + shrl $10, %ebx + popfw + movw %sp, %bp + movw %bx, 28(%bp) + popal + ret + .size patch_1m, . - patch_1m + +/**************************************************************************** + * Patch "memory above 16MB" figure + * + * Parameters: + * %bx Memory above 16MB, in 64kB blocks + * Returns: + * %bx Modified memory above 16M in 64kB blocks + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +patch_16m: + pushal + /* Convert to (start,len) format and call truncate */ + movw $truncate_to_start, %bp + xorl %ecx, %ecx + shll $16, %ebx + xorl %edx, %edx + movl $0x1000000, %eax + call truncate + /* Convert back to "memory above 16MB" format and return via %bx */ + pushfw + shrl $16, %ebx + popfw + movw %sp, %bp + movw %bx, 16(%bp) + popal + ret + .size patch_16m, . - patch_16m + +/**************************************************************************** + * Patch "memory between 1MB and 16MB" and "memory above 16MB" figures + * + * Parameters: + * %ax Memory between 1MB and 16MB, in 1kB blocks + * %bx Memory above 16MB, in 64kB blocks + * Returns: + * %ax Modified memory between 1MB and 16MB, in 1kB blocks + * %bx Modified memory above 16MB, in 64kB blocks + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +patch_1m_16m: + call patch_1m + jc 1f + call patch_16m + ret +1: /* 1m region was truncated; kill the 16m region */ + xorw %bx, %bx + ret + .size patch_1m_16m, . - patch_1m_16m + +/**************************************************************************** + * Patch E820 memory map entry + * + * Parameters: + * %es:di Pointer to E820 memory map descriptor + * %bp truncate_to_start or truncate_to_end + * Returns: + * %es:di Pointer to now-modified E820 memory map descriptor + * CF set Region was truncated + * CF clear Region was not truncated + **************************************************************************** + */ + .section ".text16" +patch_e820: + pushal + movl %es:0(%di), %eax + movl %es:4(%di), %edx + movl %es:8(%di), %ebx + movl %es:12(%di), %ecx + call truncate + movl %eax, %es:0(%di) + movl %edx, %es:4(%di) + movl %ebx, %es:8(%di) + movl %ecx, %es:12(%di) + popal + ret + .size patch_e820, . - patch_e820 + +/**************************************************************************** + * Split E820 memory map entry if necessary + * + * Parameters: + * As for INT 15,e820 + * Returns: + * As for INT 15,e820 + * + * Calls the underlying INT 15,e820 and returns a modified memory map. + * Regions will be split around any hidden regions. + **************************************************************************** + */ + .section ".text16" +split_e820: + pushw %si + pushw %bp + /* Caller's %bx => %si, real %ebx to %ebx, call previous handler */ + pushfw + movw %bx, %si + testl %ebx, %ebx + jnz 1f + movl %ebx, %cs:real_ebx +1: movl %cs:real_ebx, %ebx + lcall *%cs:int15_vector + pushfw + /* Edit result */ + pushw %ds + pushw %cs:rm_ds + popw %ds + movw $truncate_to_start, %bp + incw %si + jns 2f + movw $truncate_to_end, %bp +2: call patch_e820 + jnc 3f + xorw $0x8000, %si +3: testw %si, %si + js 4f + movl %ebx, %cs:real_ebx + testl %ebx, %ebx + jz 5f +4: movw %si, %bx +5: popw %ds + /* Restore flags returned by previous handler and return */ + popfw + popw %bp + popw %si + ret + .size split_e820, . - split_e820 + + .section ".text16.data" +real_ebx: + .long 0 + .size real_ebx, . - real_ebx + +/**************************************************************************** + * INT 15,e820 handler + **************************************************************************** + */ + .section ".text16" +int15_e820: + pushl %eax + pushl %ecx + pushl %edx + call split_e820 + pushfw + /* If we've hit an error, exit immediately */ + jc 99f + /* If region is non-empty, return this region */ + pushl %eax + movl %es:8(%di), %eax + orl %es:12(%di), %eax + popl %eax + jnz 99f + /* Region is empty. If this is not the end of the map, + * skip over this region. + */ + testl %ebx, %ebx + jz 1f + popfw + popl %edx + popl %ecx + popl %eax + jmp int15_e820 +1: /* Region is empty and this is the end of the map. Return + * with CF set to avoid placing an empty region at the end of + * the map. + */ + popfw + stc + pushfw +99: /* Restore flags from original INT 15,e820 call and return */ + popfw + addr32 leal 12(%esp), %esp /* avoid changing flags */ + lret $2 + .size int15_e820, . - int15_e820 + +/**************************************************************************** + * INT 15,e801 handler + **************************************************************************** + */ + .section ".text16" +int15_e801: + /* Call previous handler */ + pushfw + lcall *%cs:int15_vector + pushfw + /* Edit result */ + pushw %ds + pushw %cs:rm_ds + popw %ds + call patch_1m_16m + xchgw %ax, %cx + xchgw %bx, %dx + call patch_1m_16m + xchgw %ax, %cx + xchgw %bx, %dx + popw %ds + /* Restore flags returned by previous handler and return */ + popfw + lret $2 + .size int15_e801, . - int15_e801 + +/**************************************************************************** + * INT 15,88 handler + **************************************************************************** + */ + .section ".text16" +int15_88: + /* Call previous handler */ + pushfw + lcall *%cs:int15_vector + pushfw + /* Edit result */ + pushw %ds + pushw %cs:rm_ds + popw %ds + call patch_1m + popw %ds + /* Restore flags returned by previous handler and return */ + popfw + lret $2 + .size int15_88, . - int15_88 + +/**************************************************************************** + * INT 15 handler + **************************************************************************** + */ + .section ".text16" + .globl int15 +int15: + /* See if we want to intercept this call */ + pushfw + cmpw $0xe820, %ax + jne 1f + cmpl $SMAP, %edx + jne 1f + popfw + jmp int15_e820 +1: cmpw $0xe801, %ax + jne 2f + popfw + jmp int15_e801 +2: cmpb $0x88, %ah + jne 3f + popfw + jmp int15_88 +3: popfw + ljmp *%cs:int15_vector + .size int15, . - int15 + + .section ".text16.data" + .globl int15_vector +int15_vector: + .long 0 + .size int15_vector, . - int15_vector diff --git a/gpxe/src/arch/i386/firmware/pcbios/gateA20.c b/gpxe/src/arch/i386/firmware/pcbios/gateA20.c new file mode 100644 index 00000000..2caac894 --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/gateA20.c @@ -0,0 +1,170 @@ +#include <stdio.h> +#include <realmode.h> +#include <bios.h> +#include <gpxe/timer.h> + +#define K_RDWR 0x60 /* keyboard data & cmds (read/write) */ +#define K_STATUS 0x64 /* keyboard status */ +#define K_CMD 0x64 /* keybd ctlr command (write-only) */ + +#define K_OBUF_FUL 0x01 /* output buffer full */ +#define K_IBUF_FUL 0x02 /* input buffer full */ + +#define KC_CMD_WIN 0xd0 /* read output port */ +#define KC_CMD_WOUT 0xd1 /* write output port */ +#define KB_SET_A20 0xdf /* enable A20, + enable output buffer full interrupt + enable data line + disable clock line */ +#define KB_UNSET_A20 0xdd /* enable A20, + enable output buffer full interrupt + enable data line + disable clock line */ + +#define SCP_A 0x92 /* System Control Port A */ + +enum { Disable_A20 = 0x2400, Enable_A20 = 0x2401, Query_A20_Status = 0x2402, + Query_A20_Support = 0x2403 }; + +enum a20_methods { + A20_UNKNOWN = 0, + A20_INT15, + A20_KBC, + A20_SCPA, +}; + +#define A20_MAX_RETRIES 32 +#define A20_INT15_RETRIES 32 +#define A20_KBC_RETRIES (2^21) +#define A20_SCPA_RETRIES (2^21) + +/** + * Drain keyboard controller + */ +static void empty_8042 ( void ) { + unsigned long time; + + time = currticks() + TICKS_PER_SEC; /* max wait of 1 second */ + while ( ( inb ( K_CMD ) & ( K_IBUF_FUL | K_OBUF_FUL ) ) && + currticks() < time ) { + SLOW_DOWN_IO; + ( void ) inb ( K_RDWR ); + SLOW_DOWN_IO; + } +} + +/** + * Fast test to see if gate A20 is already set + * + * @v retries Number of times to retry before giving up + * @ret set Gate A20 is set + */ +static int gateA20_is_set ( int retries ) { + static uint32_t test_pattern = 0xdeadbeef; + physaddr_t test_pattern_phys = virt_to_phys ( &test_pattern ); + physaddr_t verify_pattern_phys = ( test_pattern_phys ^ 0x100000 ); + userptr_t verify_pattern_user = phys_to_user ( verify_pattern_phys ); + uint32_t verify_pattern; + + do { + /* Check for difference */ + copy_from_user ( &verify_pattern, verify_pattern_user, 0, + sizeof ( verify_pattern ) ); + if ( verify_pattern != test_pattern ) + return 1; + + /* Avoid false negatives */ + test_pattern++; + + SLOW_DOWN_IO; + + /* Always retry at least once, to avoid false negatives */ + } while ( retries-- >= 0 ); + + /* Pattern matched every time; gate A20 is not set */ + return 0; +} + +/* + * Gate A20 for high memory + * + * Note that this function gets called as part of the return path from + * librm's real_call, which is used to make the int15 call if librm is + * being used. To avoid an infinite recursion, we make gateA20_set + * return immediately if it is already part of the call stack. + */ +void gateA20_set ( void ) { + static char reentry_guard = 0; + static int a20_method = A20_UNKNOWN; + unsigned int discard_a; + unsigned int scp_a; + int retries = 0; + + /* Avoid potential infinite recursion */ + if ( reentry_guard ) + return; + reentry_guard = 1; + + /* Fast check to see if gate A20 is already enabled */ + if ( gateA20_is_set ( 0 ) ) + goto out; + + for ( ; retries < A20_MAX_RETRIES ; retries++ ) { + switch ( a20_method ) { + case A20_UNKNOWN: + case A20_INT15: + /* Try INT 15 method */ + __asm__ __volatile__ ( REAL_CODE ( "int $0x15" ) + : "=a" ( discard_a ) + : "a" ( Enable_A20 ) ); + if ( gateA20_is_set ( A20_INT15_RETRIES ) ) { + DBG ( "Enabled gate A20 using BIOS\n" ); + a20_method = A20_INT15; + goto out; + } + /* fall through */ + case A20_KBC: + /* Try keyboard controller method */ + empty_8042(); + outb ( KC_CMD_WOUT, K_CMD ); + empty_8042(); + outb ( KB_SET_A20, K_RDWR ); + empty_8042(); + if ( gateA20_is_set ( A20_KBC_RETRIES ) ) { + DBG ( "Enabled gate A20 using " + "keyboard controller\n" ); + a20_method = A20_KBC; + goto out; + } + /* fall through */ + case A20_SCPA: + /* Try "Fast gate A20" method */ + scp_a = inb ( SCP_A ); + scp_a &= ~0x01; /* Avoid triggering a reset */ + scp_a |= 0x02; /* Enable A20 */ + SLOW_DOWN_IO; + outb ( scp_a, SCP_A ); + SLOW_DOWN_IO; + if ( gateA20_is_set ( A20_SCPA_RETRIES ) ) { + DBG ( "Enabled gate A20 using " + "Fast Gate A20\n" ); + a20_method = A20_SCPA; + goto out; + } + } + } + + /* Better to die now than corrupt memory later */ + printf ( "FATAL: Gate A20 stuck\n" ); + while ( 1 ) {} + + out: + if ( retries ) + DBG ( "%d attempts were required to enable A20\n", + ( retries + 1 ) ); + reentry_guard = 0; +} + +void gateA20_unset ( void ) { + /* Not currently implemented */ +} diff --git a/gpxe/src/arch/i386/firmware/pcbios/hidemem.c b/gpxe/src/arch/i386/firmware/pcbios/hidemem.c new file mode 100644 index 00000000..eba94007 --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/hidemem.c @@ -0,0 +1,156 @@ +/* Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <realmode.h> +#include <biosint.h> +#include <basemem.h> +#include <gpxe/init.h> +#include <gpxe/hidemem.h> + +/** Alignment for hidden memory regions */ +#define ALIGN_HIDDEN 4096 /* 4kB page alignment should be enough */ + +/** + * A hidden region of Etherboot + * + * This represents a region that will be edited out of the system's + * memory map. + * + * This structure is accessed by assembly code, so must not be + * changed. + */ +struct hidden_region { + /* Physical start address */ + physaddr_t start; + /* Physical end address */ + physaddr_t end; +}; + +/** + * List of hidden regions + * + * Must be terminated by a zero entry. + */ +struct hidden_region __data16_array ( hidden_regions, [] ) = { + [TEXT] = { 0, 0 }, + [BASEMEM] = { ( 640 * 1024 ), ( 640 * 1024 ) }, + [EXTMEM] = { 0, 0 }, + { 0, 0, } /* Terminator */ +}; +#define hidden_regions __use_data16 ( hidden_regions ) + +/** Assembly routine in e820mangler.S */ +extern void int15(); + +/** Vector for storing original INT 15 handler */ +extern struct segoff __text16 ( int15_vector ); +#define int15_vector __use_text16 ( int15_vector ) + +/** + * Hide region of memory from system memory map + * + * @v start Start of region + * @v end End of region + */ +void hide_region ( unsigned int region_id, physaddr_t start, physaddr_t end ) { + struct hidden_region *region = &hidden_regions[region_id]; + + /* Some operating systems get a nasty shock if a region of the + * E820 map seems to start on a non-page boundary. Make life + * safer by rounding out our edited region. + */ + region->start = ( start & ~( ALIGN_HIDDEN - 1 ) ); + region->end = ( ( end + ALIGN_HIDDEN - 1 ) & ~( ALIGN_HIDDEN - 1 ) ); + + DBG ( "Hiding region %d [%lx,%lx)\n", + region_id, region->start, region->end ); +} + +/** + * Hide Etherboot text + * + */ +static void hide_text ( void ) { + + /* The linker defines these symbols for us */ + extern char _text[]; + extern char _end[]; + + hide_region ( TEXT, virt_to_phys ( _text ), virt_to_phys ( _end ) ); +} + +/** + * Hide used base memory + * + */ +void hide_basemem ( void ) { + /* Hide from the top of free base memory to 640kB. Don't use + * hide_region(), because we don't want this rounded to the + * nearest page boundary. + */ + hidden_regions[BASEMEM].start = ( get_fbms() * 1024 ); +} + +/** + * Hide Etherboot + * + * Installs an INT 15 handler to edit Etherboot out of the memory map + * returned by the BIOS. + */ +static void hide_etherboot ( void ) { + + /* Initialise the hidden regions */ + hide_text(); + hide_basemem(); + + /* Hook INT 15 */ + hook_bios_interrupt ( 0x15, ( unsigned int ) int15, + &int15_vector ); +} + +/** + * Unhide Etherboot + * + * Uninstalls the INT 15 handler installed by hide_etherboot(), if + * possible. + */ +static void unhide_etherboot ( void ) { + + /* If we have more than one hooked interrupt at this point, it + * means that some other vector is still hooked, in which case + * we can't safely unhook INT 15 because we need to keep our + * memory protected. (We expect there to be at least one + * hooked interrupt, because INT 15 itself is still hooked). + */ + if ( hooked_bios_interrupts > 1 ) { + DBG ( "Cannot unhide: %d interrupt vectors still hooked\n", + hooked_bios_interrupts ); + return; + } + + /* Try to unhook INT 15. If it fails, then just leave it + * hooked; it takes care of protecting itself. :) + */ + unhook_bios_interrupt ( 0x15, ( unsigned int ) int15, + &int15_vector ); +} + +/** Hide Etherboot startup function */ +struct startup_fn hide_etherboot_startup_fn __startup_fn ( STARTUP_EARLY ) = { + .startup = hide_etherboot, + .shutdown = unhide_etherboot, +}; diff --git a/gpxe/src/arch/i386/firmware/pcbios/memmap.c b/gpxe/src/arch/i386/firmware/pcbios/memmap.c new file mode 100644 index 00000000..b6a8ca3c --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/memmap.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <errno.h> +#include <realmode.h> +#include <bios.h> +#include <memsizes.h> +#include <gpxe/memmap.h> + +/** + * @file + * + * Memory mapping + * + */ + +/** Magic value for INT 15,e820 calls */ +#define SMAP ( 0x534d4150 ) + +/** An INT 15,e820 memory map entry */ +struct e820_entry { + /** Start of region */ + uint64_t start; + /** Length of region */ + uint64_t len; + /** Type of region */ + uint32_t type; +} __attribute__ (( packed )); + +#define E820_TYPE_RAM 1 /**< Normal memory */ +#define E820_TYPE_RESERVED 2 /**< Reserved and unavailable */ +#define E820_TYPE_ACPI 3 /**< ACPI reclaim memory */ +#define E820_TYPE_NVS 4 /**< ACPI NVS memory */ + +/** Buffer for INT 15,e820 calls */ +static struct e820_entry __bss16 ( e820buf ); +#define e820buf __use_data16 ( e820buf ) + +/** + * Get size of extended memory via INT 15,e801 + * + * @ret extmem Extended memory size, in kB, or 0 + */ +static unsigned int extmemsize_e801 ( void ) { + uint16_t extmem_1m_to_16m_k, extmem_16m_plus_64k; + uint16_t confmem_1m_to_16m_k, confmem_16m_plus_64k; + unsigned int flags; + unsigned int extmem; + + __asm__ __volatile__ ( REAL_CODE ( "stc\n\t" + "int $0x15\n\t" + "pushfw\n\t" + "popw %w0\n\t" ) + : "=r" ( flags ), + "=a" ( extmem_1m_to_16m_k ), + "=b" ( extmem_16m_plus_64k ), + "=c" ( confmem_1m_to_16m_k ), + "=d" ( confmem_16m_plus_64k ) + : "a" ( 0xe801 ) ); + + if ( flags & CF ) { + DBG ( "INT 15,e801 failed with CF set\n" ); + return 0; + } + + if ( ! ( extmem_1m_to_16m_k | extmem_16m_plus_64k ) ) { + DBG ( "INT 15,e801 extmem=0, using confmem\n" ); + extmem_1m_to_16m_k = confmem_1m_to_16m_k; + extmem_16m_plus_64k = confmem_16m_plus_64k; + } + + extmem = ( extmem_1m_to_16m_k + ( extmem_16m_plus_64k * 64 ) ); + DBG ( "INT 15,e801 extended memory size %d+64*%d=%d kB\n", + extmem_1m_to_16m_k, extmem_16m_plus_64k, extmem ); + return extmem; +} + +/** + * Get size of extended memory via INT 15,88 + * + * @ret extmem Extended memory size, in kB + */ +static unsigned int extmemsize_88 ( void ) { + uint16_t extmem; + + /* Ignore CF; it is not reliable for this call */ + __asm__ __volatile__ ( REAL_CODE ( "int $0x15" ) + : "=a" ( extmem ) : "a" ( 0x8800 ) ); + + DBG ( "INT 15,88 extended memory size %d kB\n", extmem ); + return extmem; +} + +/** + * Get size of extended memory + * + * @ret extmem Extended memory size, in kB + * + * Note that this is only an approximation; for an accurate picture, + * use the E820 memory map obtained via get_memmap(); + */ +unsigned int extmemsize ( void ) { + unsigned int extmem; + + /* Try INT 15,e801 first, then fall back to INT 15,88 */ + extmem = extmemsize_e801(); + if ( ! extmem ) + extmem = extmemsize_88(); + return extmem; +} + +/** + * Get e820 memory map + * + * @v memmap Memory map to fill in + * @ret rc Return status code + */ +static int meme820 ( struct memory_map *memmap ) { + struct memory_region *region = memmap->regions; + uint32_t next = 0; + uint32_t smap; + unsigned int flags; + unsigned int discard_c, discard_d, discard_D; + + do { + __asm__ __volatile__ ( REAL_CODE ( "stc\n\t" + "int $0x15\n\t" + "pushfw\n\t" + "popw %w0\n\t" ) + : "=r" ( flags ), "=a" ( smap ), + "=b" ( next ), "=D" ( discard_D ), + "=c" ( discard_c ), "=d" ( discard_d ) + : "a" ( 0xe820 ), "b" ( next ), + "D" ( &__from_data16 ( e820buf ) ), + "c" ( sizeof ( e820buf ) ), + "d" ( SMAP ) + : "memory" ); + + if ( smap != SMAP ) { + DBG ( "INT 15,e820 failed SMAP signature check\n" ); + return -ENOTSUP; + } + + if ( flags & CF ) { + DBG ( "INT 15,e820 terminated on CF set\n" ); + break; + } + + DBG ( "INT 15,e820 region [%llx,%llx) type %d\n", + e820buf.start, ( e820buf.start + e820buf.len ), + ( int ) e820buf.type ); + if ( e820buf.type != E820_TYPE_RAM ) + continue; + + region->start = e820buf.start; + region->end = e820buf.start + e820buf.len; + region++; + memmap->count++; + + if ( memmap->count >= ( sizeof ( memmap->regions ) / + sizeof ( memmap->regions[0] ) ) ) { + DBG ( "INT 15,e820 too many regions returned\n" ); + /* Not a fatal error; what we've got so far at + * least represents valid regions of memory, + * even if we couldn't get them all. + */ + break; + } + } while ( next != 0 ); + + return 0; +} + +/** + * Get memory map + * + * @v memmap Memory map to fill in + */ +void get_memmap ( struct memory_map *memmap ) { + unsigned int basemem, extmem; + int rc; + + DBG ( "Fetching system memory map\n" ); + + /* Clear memory map */ + memset ( memmap, 0, sizeof ( *memmap ) ); + + /* Get base and extended memory sizes */ + basemem = basememsize(); + DBG ( "FBMS base memory size %d kB\n", basemem ); + extmem = extmemsize(); + + /* Try INT 15,e820 first */ + if ( ( rc = meme820 ( memmap ) ) == 0 ) { + DBG ( "Obtained system memory map via INT 15,e820\n" ); + return; + } + + /* Fall back to constructing a map from basemem and extmem sizes */ + DBG ( "INT 15,e820 failed; constructing map\n" ); + memmap->regions[0].end = ( basemem * 1024 ); + memmap->regions[1].start = 0x100000; + memmap->regions[1].end = 0x100000 + ( extmem * 1024 ); + memmap->count = 2; +} diff --git a/gpxe/src/arch/i386/firmware/pcbios/pnpbios.c b/gpxe/src/arch/i386/firmware/pcbios/pnpbios.c new file mode 100644 index 00000000..420d2ae8 --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/pnpbios.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <realmode.h> +#include <pnpbios.h> + +/** @file + * + * PnP BIOS + * + */ + +/** PnP BIOS structure */ +struct pnp_bios { + /** Signature + * + * Must be equal to @c PNP_BIOS_SIGNATURE + */ + uint32_t signature; + /** Version as BCD (e.g. 1.0 is 0x10) */ + uint8_t version; + /** Length of this structure */ + uint8_t length; + /** System capabilities */ + uint16_t control; + /** Checksum */ + uint8_t checksum; +} __attribute__ (( packed )); + +/** Signature for a PnP BIOS structure */ +#define PNP_BIOS_SIGNATURE \ + ( ( '$' << 0 ) + ( 'P' << 8 ) + ( 'n' << 16 ) + ( 'P' << 24 ) ) + +/** + * Test address for PnP BIOS structure + * + * @v offset Offset within BIOS segment to test + * @ret rc Return status code + */ +static int is_pnp_bios ( unsigned int offset ) { + union { + struct pnp_bios pnp_bios; + uint8_t bytes[256]; /* 256 is maximum length possible */ + } u; + size_t len; + unsigned int i; + uint8_t sum = 0; + + /* Read start of header and verify signature */ + copy_from_real ( &u.pnp_bios, BIOS_SEG, offset, sizeof ( u.pnp_bios )); + if ( u.pnp_bios.signature != PNP_BIOS_SIGNATURE ) + return -EINVAL; + + /* Read whole header and verify checksum */ + len = u.pnp_bios.length; + copy_from_real ( &u.bytes, BIOS_SEG, offset, len ); + for ( i = 0 ; i < len ; i++ ) { + sum += u.bytes[i]; + } + if ( sum != 0 ) + return -EINVAL; + + DBG ( "Found PnP BIOS at %04x:%04x\n", BIOS_SEG, offset ); + + return 0; +} + +/** + * Locate Plug-and-Play BIOS + * + * @ret pnp_offset Offset of PnP BIOS structure within BIOS segment + * + * The PnP BIOS structure will be at BIOS_SEG:pnp_offset. If no PnP + * BIOS is found, -1 is returned. + */ +int find_pnp_bios ( void ) { + static int pnp_offset = 0; + + if ( pnp_offset ) + return pnp_offset; + + for ( pnp_offset = 0 ; pnp_offset < 0x10000 ; pnp_offset += 0x10 ) { + if ( is_pnp_bios ( pnp_offset ) == 0 ) + return pnp_offset; + } + + pnp_offset = -1; + return pnp_offset; +} diff --git a/gpxe/src/arch/i386/firmware/pcbios/smbios.c b/gpxe/src/arch/i386/firmware/pcbios/smbios.c new file mode 100644 index 00000000..4d710d68 --- /dev/null +++ b/gpxe/src/arch/i386/firmware/pcbios/smbios.c @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <gpxe/uaccess.h> +#include <gpxe/uuid.h> +#include <realmode.h> +#include <pnpbios.h> +#include <smbios.h> + +/** @file + * + * System Management BIOS + * + */ + +/** Signature for SMBIOS entry point */ +#define SMBIOS_SIGNATURE \ + ( ( '_' << 0 ) + ( 'S' << 8 ) + ( 'M' << 16 ) + ( '_' << 24 ) ) + +/** + * SMBIOS entry point + * + * This is the single table which describes the list of SMBIOS + * structures. It is located by scanning through the BIOS segment. + */ +struct smbios_entry { + /** Signature + * + * Must be equal to SMBIOS_SIGNATURE + */ + uint32_t signature; + /** Checksum */ + uint8_t checksum; + /** Length */ + uint8_t length; + /** Major version */ + uint8_t major; + /** Minor version */ + uint8_t minor; + /** Maximum structure size */ + uint16_t max; + /** Entry point revision */ + uint8_t revision; + /** Formatted area */ + uint8_t formatted[5]; + /** DMI Signature */ + uint8_t dmi_signature[5]; + /** DMI checksum */ + uint8_t dmi_checksum; + /** Structure table length */ + uint16_t smbios_length; + /** Structure table address */ + physaddr_t smbios_address; + /** Number of SMBIOS structures */ + uint16_t smbios_count; + /** BCD revision */ + uint8_t bcd_revision; +} __attribute__ (( packed )); + +/** + * SMBIOS entry point descriptor + * + * This contains the information from the SMBIOS entry point that we + * care about. + */ +struct smbios { + /** Start of SMBIOS structures */ + userptr_t address; + /** Length of SMBIOS structures */ + size_t length; + /** Number of SMBIOS structures */ + unsigned int count; +}; + +/** + * SMBIOS strings descriptor + * + * This is returned as part of the search for an SMBIOS structure, and + * contains the information needed for extracting the strings within + * the "unformatted" portion of the structure. + */ +struct smbios_strings { + /** Start of strings data */ + userptr_t data; + /** Length of strings data */ + size_t length; +}; + +/** + * Find SMBIOS + * + * @ret smbios SMBIOS entry point descriptor, or NULL if not found + */ +static struct smbios * find_smbios ( void ) { + static struct smbios smbios = { + .address = UNULL, + }; + union { + struct smbios_entry entry; + uint8_t bytes[256]; /* 256 is maximum length possible */ + } u; + unsigned int offset; + size_t len; + unsigned int i; + uint8_t sum; + + /* Return cached result if available */ + if ( smbios.address != UNULL ) + return &smbios; + + /* Try to find SMBIOS */ + for ( offset = 0 ; offset < 0x10000 ; offset += 0x10 ) { + + /* Read start of header and verify signature */ + copy_from_real ( &u.entry, BIOS_SEG, offset, + sizeof ( u.entry )); + if ( u.entry.signature != SMBIOS_SIGNATURE ) + continue; + + /* Read whole header and verify checksum */ + len = u.entry.length; + copy_from_real ( &u.bytes, BIOS_SEG, offset, len ); + for ( i = 0 , sum = 0 ; i < len ; i++ ) { + sum += u.bytes[i]; + } + if ( sum != 0 ) { + DBG ( "SMBIOS at %04x:%04x has bad checksum %02x\n", + BIOS_SEG, offset, sum ); + continue; + } + + /* Fill result structure */ + DBG ( "Found SMBIOS entry point at %04x:%04x\n", + BIOS_SEG, offset ); + smbios.address = phys_to_user ( u.entry.smbios_address ); + smbios.length = u.entry.smbios_length; + smbios.count = u.entry.smbios_count; + return &smbios; + } + + DBG ( "No SMBIOS found\n" ); + return NULL; +} + +/** + * Find SMBIOS strings terminator + * + * @v smbios SMBIOS entry point descriptor + * @v offset Offset to start of strings + * @ret offset Offset to strings terminator, or 0 if not found + */ +static size_t find_strings_terminator ( struct smbios *smbios, + size_t offset ) { + size_t max_offset = ( smbios->length - 2 ); + uint16_t nulnul; + + for ( ; offset <= max_offset ; offset++ ) { + copy_from_user ( &nulnul, smbios->address, offset, 2 ); + if ( nulnul == 0 ) + return ( offset + 1 ); + } + return 0; +} + +/** + * Find specific structure type within SMBIOS + * + * @v type Structure type to search for + * @v structure Buffer to fill in with structure + * @v length Length of buffer + * @v strings Strings descriptor to fill in, or NULL + * @ret rc Return status code + */ +int find_smbios_structure ( unsigned int type, void *structure, + size_t length, struct smbios_strings *strings ) { + struct smbios *smbios; + struct smbios_header header; + struct smbios_strings temp_strings; + unsigned int count = 0; + size_t offset = 0; + size_t strings_offset; + size_t terminator_offset; + + /* Locate SMBIOS entry point */ + if ( ! ( smbios = find_smbios() ) ) + return -ENOENT; + + /* Ensure that we have a usable strings descriptor buffer */ + if ( ! strings ) + strings = &temp_strings; + + /* Scan through list of structures */ + while ( ( ( offset + sizeof ( header ) ) < smbios->length ) && + ( count < smbios->count ) ) { + + /* Read next SMBIOS structure header */ + copy_from_user ( &header, smbios->address, offset, + sizeof ( header ) ); + + /* Determine start and extent of strings block */ + strings_offset = ( offset + header.length ); + if ( strings_offset > smbios->length ) { + DBG ( "SMBIOS structure at offset %zx with length " + "%x extends beyond SMBIOS\n", offset, + header.length ); + return -ENOENT; + } + terminator_offset = + find_strings_terminator ( smbios, strings_offset ); + if ( ! terminator_offset ) { + DBG ( "SMBIOS structure at offset %zx has " + "unterminated strings section\n", offset ); + return -ENOENT; + } + strings->data = userptr_add ( smbios->address, + strings_offset ); + strings->length = ( terminator_offset - strings_offset ); + + DBG ( "SMBIOS structure at offset %zx has type %d, " + "length %x, strings length %zx\n", + offset, header.type, header.length, strings->length ); + + /* If this is the structure we want, return */ + if ( header.type == type ) { + if ( length > header.length ) + length = header.length; + copy_from_user ( structure, smbios->address, + offset, length ); + return 0; + } + + /* Move to next SMBIOS structure */ + offset = ( terminator_offset + 1 ); + count++; + } + + DBG ( "SMBIOS structure type %d not found\n", type ); + return -ENOENT; +} + +/** + * Find indexed string within SMBIOS structure + * + * @v strings SMBIOS strings descriptor + * @v index String index + * @v buffer Buffer for string + * @v length Length of string buffer + * @ret rc Return status code + */ +int find_smbios_string ( struct smbios_strings *strings, unsigned int index, + char *buffer, size_t length ) { + size_t offset = 0; + size_t string_len; + + /* Zero buffer. This ensures that a valid NUL terminator is + * always present (unless length==0). + */ + memset ( buffer, 0, length ); + + /* String numbers start at 1 (0 is used to indicate "no string") */ + if ( ! index ) + return 0; + + while ( offset < strings->length ) { + /* Get string length. This is known safe, since the + * smbios_strings struct is constructed so as to + * always end on a string boundary. + */ + string_len = strlen_user ( strings->data, offset ); + if ( --index == 0 ) { + /* Copy string, truncating as necessary. */ + if ( string_len >= length ) + string_len = ( length - 1 ); + copy_from_user ( buffer, strings->data, + offset, string_len ); + return 0; + } + offset += ( string_len + 1 ); + } + + DBG ( "SMBIOS string index %d not found\n", index ); + return -ENOENT; +} + +/** + * Get UUID from SMBIOS + * + * @v uuid UUID to fill in + * @ret rc Return status code + */ +int smbios_get_uuid ( union uuid *uuid ) { + struct smbios_system_information sysinfo; + int rc; + + if ( ( rc = find_smbios_structure ( SMBIOS_TYPE_SYSTEM_INFORMATION, + &sysinfo, sizeof ( sysinfo ), + NULL ) ) != 0 ) + return rc; + + memcpy ( uuid, sysinfo.uuid, sizeof ( *uuid ) ); + DBG ( "SMBIOS found UUID %s\n", uuid_ntoa ( uuid ) ); + + return 0; +} diff --git a/gpxe/src/arch/i386/image/bootsector.c b/gpxe/src/arch/i386/image/bootsector.c new file mode 100644 index 00000000..0f297a26 --- /dev/null +++ b/gpxe/src/arch/i386/image/bootsector.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** + * @file + * + * x86 bootsector image format + * + */ + +#include <errno.h> +#include <realmode.h> +#include <biosint.h> +#include <bootsector.h> + +/** Vector for storing original INT 18 handler + * + * We do not chain to this vector, so there is no need to place it in + * .text16. + */ +static struct segoff int18_vector; + +/** Vector for storing original INT 19 handler + * + * We do not chain to this vector, so there is no need to place it in + * .text16. + */ +static struct segoff int19_vector; + +/** Restart point for INT 18 or 19 */ +extern void bootsector_exec_fail ( void ); + +/** + * Jump to preloaded bootsector + * + * @v segment Real-mode segment + * @v offset Real-mode offset + * @v drive Drive number to pass to boot sector + * @ret rc Return status code + */ +int call_bootsector ( unsigned int segment, unsigned int offset, + unsigned int drive ) { + int discard_b, discard_D, discard_d; + + DBG ( "Booting from boot sector at %04x:%04x\n", segment, offset ); + + /* Hook INTs 18 and 19 to capture failure paths */ + hook_bios_interrupt ( 0x18, ( unsigned int ) bootsector_exec_fail, + &int18_vector ); + hook_bios_interrupt ( 0x19, ( unsigned int ) bootsector_exec_fail, + &int19_vector ); + + /* Boot the loaded sector + * + * We assume that the boot sector may completely destroy our + * real-mode stack, so we preserve everything we need in + * static storage. + */ + __asm__ __volatile__ ( REAL_CODE ( /* Save return address off-stack */ + "popw %%cs:saved_retaddr\n\t" + /* Save stack pointer */ + "movw %%ss, %%ax\n\t" + "movw %%ax, %%cs:saved_ss\n\t" + "movw %%sp, %%cs:saved_sp\n\t" + /* Jump to boot sector */ + "pushw %%bx\n\t" + "pushw %%di\n\t" + "sti\n\t" + "lret\n\t" + /* Preserved variables */ + "\nsaved_ss: .word 0\n\t" + "\nsaved_sp: .word 0\n\t" + "\nsaved_retaddr: .word 0\n\t" + /* Boot failure return point */ + "\nbootsector_exec_fail:\n\t" + /* Restore stack pointer */ + "movw %%cs:saved_ss, %%ax\n\t" + "movw %%ax, %%ss\n\t" + "movw %%cs:saved_sp, %%sp\n\t" + /* Return via saved address */ + "jmp *%%cs:saved_retaddr\n\t" ) + : "=b" ( discard_b ), "=D" ( discard_D ), + "=d" ( discard_d ) + : "b" ( segment ), "D" ( offset ), + "d" ( drive ) + : "eax", "ecx", "esi", "ebp" ); + + DBG ( "Booted disk returned via INT 18 or 19\n" ); + + /* Unhook INTs 18 and 19 */ + unhook_bios_interrupt ( 0x18, ( unsigned int ) bootsector_exec_fail, + &int18_vector ); + unhook_bios_interrupt ( 0x19, ( unsigned int ) bootsector_exec_fail, + &int19_vector ); + + return -ECANCELED; +} diff --git a/gpxe/src/arch/i386/image/bzimage.c b/gpxe/src/arch/i386/image/bzimage.c new file mode 100644 index 00000000..ed9d286d --- /dev/null +++ b/gpxe/src/arch/i386/image/bzimage.c @@ -0,0 +1,567 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** + * @file + * + * Linux bzImage image format + * + */ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <realmode.h> +#include <bzimage.h> +#include <gpxe/uaccess.h> +#include <gpxe/image.h> +#include <gpxe/segment.h> +#include <gpxe/init.h> +#include <gpxe/initrd.h> +#include <gpxe/cpio.h> +#include <gpxe/features.h> + +FEATURE ( FEATURE_IMAGE, "bzImage", DHCP_EB_FEATURE_BZIMAGE, 1 ); + +struct image_type bzimage_image_type __image_type ( PROBE_NORMAL ); + +/** + * bzImage load context + */ +struct bzimage_load_context { + /** Real-mode kernel portion load segment address */ + unsigned int rm_kernel_seg; + /** Real-mode kernel portion load address */ + userptr_t rm_kernel; + /** Real-mode kernel portion file size */ + size_t rm_filesz; + /** Real-mode heap top (offset from rm_kernel) */ + size_t rm_heap; + /** Command line (offset from rm_kernel) */ + size_t rm_cmdline; + /** Real-mode kernel portion total memory size */ + size_t rm_memsz; + /** Non-real-mode kernel portion load address */ + userptr_t pm_kernel; + /** Non-real-mode kernel portion file and memory size */ + size_t pm_sz; +}; + +/** + * bzImage execution context + */ +struct bzimage_exec_context { + /** Real-mode kernel portion load segment address */ + unsigned int rm_kernel_seg; + /** Real-mode kernel portion load address */ + userptr_t rm_kernel; + /** Real-mode heap top (offset from rm_kernel) */ + size_t rm_heap; + /** Command line (offset from rm_kernel) */ + size_t rm_cmdline; + /** Video mode */ + unsigned int vid_mode; + /** Memory limit */ + uint64_t mem_limit; + /** Initrd address */ + physaddr_t ramdisk_image; + /** Initrd size */ + physaddr_t ramdisk_size; +}; + +/** + * Parse kernel command line for bootloader parameters + * + * @v image bzImage file + * @v exec_ctx Execution context + * @v cmdline Kernel command line + * @ret rc Return status code + */ +static int bzimage_parse_cmdline ( struct image *image, + struct bzimage_exec_context *exec_ctx, + const char *cmdline ) { + char *vga; + char *mem; + + /* Look for "vga=" */ + if ( ( vga = strstr ( cmdline, "vga=" ) ) ) { + vga += 4; + if ( strcmp ( vga, "normal" ) == 0 ) { + exec_ctx->vid_mode = BZI_VID_MODE_NORMAL; + } else if ( strcmp ( vga, "ext" ) == 0 ) { + exec_ctx->vid_mode = BZI_VID_MODE_EXT; + } else if ( strcmp ( vga, "ask" ) == 0 ) { + exec_ctx->vid_mode = BZI_VID_MODE_ASK; + } else { + exec_ctx->vid_mode = strtoul ( vga, &vga, 0 ); + if ( *vga && ( *vga != ' ' ) ) { + DBGC ( image, "bzImage %p strange \"vga=\"" + "terminator '%c'\n", image, *vga ); + } + } + } + + /* Look for "mem=" */ + if ( ( mem = strstr ( cmdline, "mem=" ) ) ) { + mem += 4; + exec_ctx->mem_limit = strtoul ( mem, &mem, 0 ); + switch ( *mem ) { + case 'G': + case 'g': + exec_ctx->mem_limit <<= 10; + case 'M': + case 'm': + exec_ctx->mem_limit <<= 10; + case 'K': + case 'k': + exec_ctx->mem_limit <<= 10; + break; + case '\0': + case ' ': + break; + default: + DBGC ( image, "bzImage %p strange \"mem=\" " + "terminator '%c'\n", image, *mem ); + break; + } + exec_ctx->mem_limit -= 1; + } + + return 0; +} + +/** + * Set command line + * + * @v image bzImage image + * @v exec_ctx Execution context + * @v cmdline Kernel command line + * @ret rc Return status code + */ +static int bzimage_set_cmdline ( struct image *image, + struct bzimage_exec_context *exec_ctx, + const char *cmdline ) { + size_t cmdline_len; + + /* Copy command line down to real-mode portion */ + cmdline_len = ( strlen ( cmdline ) + 1 ); + if ( cmdline_len > BZI_CMDLINE_SIZE ) + cmdline_len = BZI_CMDLINE_SIZE; + copy_to_user ( exec_ctx->rm_kernel, exec_ctx->rm_cmdline, + cmdline, cmdline_len ); + DBGC ( image, "bzImage %p command line \"%s\"\n", image, cmdline ); + + return 0; +} + +/** + * Load initrd + * + * @v image bzImage image + * @v initrd initrd image + * @v address Address at which to load, or UNULL + * @ret len Length of loaded image, rounded up to 4 bytes + */ +static size_t bzimage_load_initrd ( struct image *image, + struct image *initrd, + userptr_t address ) { + char *filename = initrd->cmdline; + struct cpio_header cpio; + size_t offset = 0; + + /* Ignore images which aren't initrds */ + if ( initrd->type != &initrd_image_type ) + return 0; + + /* Create cpio header before non-prebuilt images */ + if ( filename && filename[0] ) { + size_t name_len = ( strlen ( filename ) + 1 ); + + DBGC ( image, "bzImage %p inserting initrd %p as %s\n", + image, initrd, filename ); + memset ( &cpio, '0', sizeof ( cpio ) ); + memcpy ( cpio.c_magic, CPIO_MAGIC, sizeof ( cpio.c_magic ) ); + cpio_set_field ( cpio.c_mode, 0100644 ); + cpio_set_field ( cpio.c_nlink, 1 ); + cpio_set_field ( cpio.c_filesize, initrd->len ); + cpio_set_field ( cpio.c_namesize, name_len ); + if ( address ) { + copy_to_user ( address, offset, &cpio, + sizeof ( cpio ) ); + } + offset += sizeof ( cpio ); + if ( address ) { + copy_to_user ( address, offset, filename, + name_len ); + } + offset += name_len; + offset = ( ( offset + 0x03 ) & ~0x03 ); + } + + /* Copy in initrd image body */ + if ( address ) { + DBGC ( image, "bzImage %p has initrd %p at [%lx,%lx)\n", + image, initrd, address, ( address + offset ) ); + memcpy_user ( address, offset, initrd->data, 0, + initrd->len ); + } + offset += initrd->len; + + offset = ( ( offset + 0x03 ) & ~0x03 ); + return offset; +} + +/** + * Load initrds, if any + * + * @v image bzImage image + * @v exec_ctx Execution context + * @ret rc Return status code + */ +static int bzimage_load_initrds ( struct image *image, + struct bzimage_exec_context *exec_ctx ) { + struct image *initrd; + size_t total_len = 0; + physaddr_t address; + int rc; + + /* Add up length of all initrd images */ + for_each_image ( initrd ) { + total_len += bzimage_load_initrd ( image, initrd, UNULL ); + } + + /* Give up if no initrd images found */ + if ( ! total_len ) + return 0; + + /* Find a suitable start address. Try 1MB boundaries, + * starting from the downloaded kernel image itself and + * working downwards until we hit an available region. + */ + for ( address = ( user_to_phys ( image->data, 0 ) & ~0xfffff ) ; ; + address -= 0x100000 ) { + /* Check that we're not going to overwrite the + * kernel itself. This check isn't totally + * accurate, but errs on the side of caution. + */ + if ( address <= ( BZI_LOAD_HIGH_ADDR + image->len ) ) { + DBGC ( image, "bzImage %p could not find a location " + "for initrd\n", image ); + return -ENOBUFS; + } + /* Check that we are within the kernel's range */ + if ( ( address + total_len - 1 ) > exec_ctx->mem_limit ) + continue; + /* Prepare and verify segment */ + if ( ( rc = prep_segment ( phys_to_user ( address ), 0, + total_len ) ) != 0 ) + continue; + /* Use this address */ + break; + } + + /* Record initrd location */ + exec_ctx->ramdisk_image = address; + exec_ctx->ramdisk_size = total_len; + + /* Construct initrd */ + DBGC ( image, "bzImage %p constructing initrd at [%lx,%lx)\n", + image, address, ( address + total_len ) ); + for_each_image ( initrd ) { + address += bzimage_load_initrd ( image, initrd, + phys_to_user ( address ) ); + } + + return 0; +} + +/** + * Execute bzImage image + * + * @v image bzImage image + * @ret rc Return status code + */ +static int bzimage_exec ( struct image *image ) { + struct bzimage_exec_context exec_ctx; + struct bzimage_header bzhdr; + const char *cmdline = ( image->cmdline ? image->cmdline : "" ); + int rc; + + /* Initialise context */ + memset ( &exec_ctx, 0, sizeof ( exec_ctx ) ); + + /* Retrieve kernel header */ + exec_ctx.rm_kernel_seg = image->priv.ul; + exec_ctx.rm_kernel = real_to_user ( exec_ctx.rm_kernel_seg, 0 ); + copy_from_user ( &bzhdr, exec_ctx.rm_kernel, BZI_HDR_OFFSET, + sizeof ( bzhdr ) ); + exec_ctx.rm_cmdline = exec_ctx.rm_heap = + ( bzhdr.heap_end_ptr + 0x200 ); + exec_ctx.vid_mode = bzhdr.vid_mode; + if ( bzhdr.version >= 0x0203 ) { + exec_ctx.mem_limit = bzhdr.initrd_addr_max; + } else { + exec_ctx.mem_limit = BZI_INITRD_MAX; + } + + /* Parse command line for bootloader parameters */ + if ( ( rc = bzimage_parse_cmdline ( image, &exec_ctx, cmdline ) ) != 0) + return rc; + + /* Store command line */ + if ( ( rc = bzimage_set_cmdline ( image, &exec_ctx, cmdline ) ) != 0 ) + return rc; + + /* Load any initrds */ + if ( ( rc = bzimage_load_initrds ( image, &exec_ctx ) ) != 0 ) + return rc; + + /* Update and store kernel header */ + bzhdr.vid_mode = exec_ctx.vid_mode; + bzhdr.ramdisk_image = exec_ctx.ramdisk_image; + bzhdr.ramdisk_size = exec_ctx.ramdisk_size; + copy_to_user ( exec_ctx.rm_kernel, BZI_HDR_OFFSET, &bzhdr, + sizeof ( bzhdr ) ); + + /* Prepare for exiting */ + shutdown(); + + DBGC ( image, "bzImage %p jumping to RM kernel at %04x:0000 " + "(stack %04x:%04zx)\n", image, + ( exec_ctx.rm_kernel_seg + 0x20 ), + exec_ctx.rm_kernel_seg, exec_ctx.rm_heap ); + + /* Jump to the kernel */ + __asm__ __volatile__ ( REAL_CODE ( "movw %w0, %%ds\n\t" + "movw %w0, %%es\n\t" + "movw %w0, %%fs\n\t" + "movw %w0, %%gs\n\t" + "movw %w0, %%ss\n\t" + "movw %w1, %%sp\n\t" + "pushw %w2\n\t" + "pushw $0\n\t" + "lret\n\t" ) + : : "r" ( exec_ctx.rm_kernel_seg ), + "r" ( exec_ctx.rm_heap ), + "r" ( exec_ctx.rm_kernel_seg + 0x20 ) ); + + /* There is no way for the image to return, since we provide + * no return address. + */ + assert ( 0 ); + + return -ECANCELED; /* -EIMPOSSIBLE */ +} + +/** + * Load and parse bzImage header + * + * @v image bzImage file + * @v load_ctx Load context + * @v bzhdr Buffer for bzImage header + * @ret rc Return status code + */ +static int bzimage_load_header ( struct image *image, + struct bzimage_load_context *load_ctx, + struct bzimage_header *bzhdr ) { + + /* Sanity check */ + if ( image->len < ( BZI_HDR_OFFSET + sizeof ( *bzhdr ) ) ) { + DBGC ( image, "bzImage %p too short for kernel header\n", + image ); + return -ENOEXEC; + } + + /* Read and verify header */ + copy_from_user ( bzhdr, image->data, BZI_HDR_OFFSET, + sizeof ( *bzhdr ) ); + if ( bzhdr->header != BZI_SIGNATURE ) { + DBGC ( image, "bzImage %p bad signature %08lx\n", + image, bzhdr->header ); + return -ENOEXEC; + } + + /* We don't support ancient kernels */ + if ( bzhdr->version < 0x0200 ) { + DBGC ( image, "bzImage %p version %04x not supported\n", + image, bzhdr->version ); + return -ENOTSUP; + } + + /* Calculate load address and size of real-mode portion */ + load_ctx->rm_kernel_seg = 0x1000; /* place RM kernel at 1000:0000 */ + load_ctx->rm_kernel = real_to_user ( load_ctx->rm_kernel_seg, 0 ); + load_ctx->rm_filesz = + ( ( bzhdr->setup_sects ? bzhdr->setup_sects : 4 ) + 1 ) << 9; + load_ctx->rm_memsz = BZI_ASSUMED_RM_SIZE; + if ( load_ctx->rm_filesz > image->len ) { + DBGC ( image, "bzImage %p too short for %zd byte of setup\n", + image, load_ctx->rm_filesz ); + return -ENOEXEC; + } + + /* Calculate load address and size of non-real-mode portion */ + load_ctx->pm_kernel = ( ( bzhdr->loadflags & BZI_LOAD_HIGH ) ? + phys_to_user ( BZI_LOAD_HIGH_ADDR ) : + phys_to_user ( BZI_LOAD_LOW_ADDR ) ); + load_ctx->pm_sz = ( image->len - load_ctx->rm_filesz ); + + DBGC ( image, "bzImage %p version %04x RM %#zx bytes PM %#zx bytes\n", + image, bzhdr->version, load_ctx->rm_filesz, load_ctx->pm_sz ); + return 0; +} + +/** + * Load real-mode portion of bzImage + * + * @v image bzImage file + * @v load_ctx Load context + * @ret rc Return status code + */ +static int bzimage_load_real ( struct image *image, + struct bzimage_load_context *load_ctx ) { + int rc; + + /* Allow space for the stack and heap */ + load_ctx->rm_memsz += BZI_STACK_SIZE; + load_ctx->rm_heap = load_ctx->rm_memsz; + + /* Allow space for the command line */ + load_ctx->rm_cmdline = load_ctx->rm_memsz; + load_ctx->rm_memsz += BZI_CMDLINE_SIZE; + + /* Prepare, verify, and load the real-mode segment */ + if ( ( rc = prep_segment ( load_ctx->rm_kernel, load_ctx->rm_filesz, + load_ctx->rm_memsz ) ) != 0 ) { + DBGC ( image, "bzImage %p could not prepare RM segment: %s\n", + image, strerror ( rc ) ); + return rc; + } + memcpy_user ( load_ctx->rm_kernel, 0, image->data, 0, + load_ctx->rm_filesz ); + + return 0; +} + +/** + * Load non-real-mode portion of bzImage + * + * @v image bzImage file + * @v load_ctx Load context + * @ret rc Return status code + */ +static int bzimage_load_non_real ( struct image *image, + struct bzimage_load_context *load_ctx ) { + int rc; + + /* Prepare, verify and load the non-real-mode segment */ + if ( ( rc = prep_segment ( load_ctx->pm_kernel, load_ctx->pm_sz, + load_ctx->pm_sz ) ) != 0 ) { + DBGC ( image, "bzImage %p could not prepare PM segment: %s\n", + image, strerror ( rc ) ); + return rc; + } + memcpy_user ( load_ctx->pm_kernel, 0, image->data, load_ctx->rm_filesz, + load_ctx->pm_sz ); + + return 0; +} + +/** + * Update and store bzImage header + * + * @v image bzImage file + * @v load_ctx Load context + * @v bzhdr Original bzImage header + * @ret rc Return status code + */ +static int bzimage_write_header ( struct image *image __unused, + struct bzimage_load_context *load_ctx, + struct bzimage_header *bzhdr ) { + struct bzimage_cmdline cmdline; + + /* Update the header and copy it into the loaded kernel */ + bzhdr->type_of_loader = BZI_LOADER_TYPE_GPXE; + if ( bzhdr->version >= 0x0201 ) { + bzhdr->heap_end_ptr = ( load_ctx->rm_heap - 0x200 ); + bzhdr->loadflags |= BZI_CAN_USE_HEAP; + } + if ( bzhdr->version >= 0x0202 ) { + bzhdr->cmd_line_ptr = user_to_phys ( load_ctx->rm_kernel, + load_ctx->rm_cmdline ); + } else { + cmdline.magic = BZI_CMDLINE_MAGIC; + cmdline.offset = load_ctx->rm_cmdline; + copy_to_user ( load_ctx->rm_kernel, BZI_CMDLINE_OFFSET, + &cmdline, sizeof ( cmdline ) ); + bzhdr->setup_move_size = load_ctx->rm_memsz; + } + copy_to_user ( load_ctx->rm_kernel, BZI_HDR_OFFSET, + bzhdr, sizeof ( *bzhdr ) ); + + return 0; +} + +/** + * Load bzImage image into memory + * + * @v image bzImage file + * @ret rc Return status code + */ +int bzimage_load ( struct image *image ) { + struct bzimage_load_context load_ctx; + struct bzimage_header bzhdr; + int rc; + + /* Initialise context */ + memset ( &load_ctx, 0, sizeof ( load_ctx ) ); + + /* Load and verify header */ + if ( ( rc = bzimage_load_header ( image, &load_ctx, &bzhdr ) ) != 0 ) + return rc; + + /* This is a bzImage image, valid or otherwise */ + if ( ! image->type ) + image->type = &bzimage_image_type; + + /* Load real-mode portion */ + if ( ( rc = bzimage_load_real ( image, &load_ctx ) ) != 0 ) + return rc; + + /* Load non-real-mode portion */ + if ( ( rc = bzimage_load_non_real ( image, &load_ctx ) ) != 0 ) + return rc; + + /* Update and write out header */ + if ( ( rc = bzimage_write_header ( image, &load_ctx, &bzhdr ) ) != 0 ) + return rc; + + /* Record real-mode segment in image private data field */ + image->priv.ul = load_ctx.rm_kernel_seg; + + return 0; +} + +/** Linux bzImage image type */ +struct image_type bzimage_image_type __image_type ( PROBE_NORMAL ) = { + .name = "bzImage", + .load = bzimage_load, + .exec = bzimage_exec, +}; diff --git a/gpxe/src/arch/i386/image/eltorito.c b/gpxe/src/arch/i386/image/eltorito.c new file mode 100644 index 00000000..9d573106 --- /dev/null +++ b/gpxe/src/arch/i386/image/eltorito.c @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** + * @file + * + * El Torito bootable ISO image format + * + */ + +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <realmode.h> +#include <bootsector.h> +#include <int13.h> +#include <gpxe/uaccess.h> +#include <gpxe/image.h> +#include <gpxe/segment.h> +#include <gpxe/ramdisk.h> +#include <gpxe/init.h> + +#define ISO9660_BLKSIZE 2048 +#define ELTORITO_VOL_DESC_OFFSET ( 17 * ISO9660_BLKSIZE ) + +/** An El Torito Boot Record Volume Descriptor */ +struct eltorito_vol_desc { + /** Boot record indicator; must be 0 */ + uint8_t record_indicator; + /** ISO-9660 identifier; must be "CD001" */ + uint8_t iso9660_id[5]; + /** Version, must be 1 */ + uint8_t version; + /** Boot system indicator; must be "EL TORITO SPECIFICATION" */ + uint8_t system_indicator[32]; + /** Unused */ + uint8_t unused[32]; + /** Boot catalog sector */ + uint32_t sector; +} __attribute__ (( packed )); + +/** An El Torito Boot Catalog Validation Entry */ +struct eltorito_validation_entry { + /** Header ID; must be 1 */ + uint8_t header_id; + /** Platform ID + * + * 0 = 80x86 + * 1 = PowerPC + * 2 = Mac + */ + uint8_t platform_id; + /** Reserved */ + uint16_t reserved; + /** ID string */ + uint8_t id_string[24]; + /** Checksum word */ + uint16_t checksum; + /** Signature; must be 0xaa55 */ + uint16_t signature; +} __attribute__ (( packed )); + +/** A bootable entry in the El Torito Boot Catalog */ +struct eltorito_boot_entry { + /** Boot indicator + * + * Must be @c ELTORITO_BOOTABLE for a bootable ISO image + */ + uint8_t indicator; + /** Media type + * + */ + uint8_t media_type; + /** Load segment */ + uint16_t load_segment; + /** System type */ + uint8_t filesystem; + /** Unused */ + uint8_t reserved_a; + /** Sector count */ + uint16_t length; + /** Starting sector */ + uint32_t start; + /** Unused */ + uint8_t reserved_b[20]; +} __attribute__ (( packed )); + +/** Boot indicator for a bootable ISO image */ +#define ELTORITO_BOOTABLE 0x88 + +/** El Torito media types */ +enum eltorito_media_type { + /** No emulation */ + ELTORITO_NO_EMULATION = 0, +}; + +struct image_type eltorito_image_type __image_type ( PROBE_NORMAL ); + +/** + * Calculate 16-bit word checksum + * + * @v data Data to checksum + * @v len Length (in bytes, must be even) + * @ret sum Checksum + */ +static unsigned int word_checksum ( void *data, size_t len ) { + uint16_t *words; + uint16_t sum = 0; + + for ( words = data ; len ; words++, len -= 2 ) { + sum += *words; + } + return sum; +} + +/** + * Execute El Torito image + * + * @v image El Torito image + * @ret rc Return status code + */ +static int eltorito_exec ( struct image *image ) { + struct ramdisk ramdisk; + struct int13_drive int13_drive; + unsigned int load_segment = image->priv.ul; + unsigned int load_offset = ( load_segment ? 0 : 0x7c00 ); + int rc; + + memset ( &ramdisk, 0, sizeof ( ramdisk ) ); + init_ramdisk ( &ramdisk, image->data, image->len, ISO9660_BLKSIZE ); + + memset ( &int13_drive, 0, sizeof ( int13_drive ) ); + int13_drive.blockdev = &ramdisk.blockdev; + register_int13_drive ( &int13_drive ); + + if ( ( rc = call_bootsector ( load_segment, load_offset, + int13_drive.drive ) ) != 0 ) { + DBGC ( image, "ElTorito %p boot failed: %s\n", + image, strerror ( rc ) ); + goto err; + } + + rc = -ECANCELED; /* -EIMPOSSIBLE */ + err: + unregister_int13_drive ( &int13_drive ); + return rc; +} + +/** + * Read and verify El Torito Boot Record Volume Descriptor + * + * @v image El Torito file + * @ret catalog_offset Offset of Boot Catalog + * @ret rc Return status code + */ +static int eltorito_read_voldesc ( struct image *image, + unsigned long *catalog_offset ) { + static const struct eltorito_vol_desc vol_desc_signature = { + .record_indicator = 0, + .iso9660_id = "CD001", + .version = 1, + .system_indicator = "EL TORITO SPECIFICATION", + }; + struct eltorito_vol_desc vol_desc; + + /* Sanity check */ + if ( image->len < ( ELTORITO_VOL_DESC_OFFSET + ISO9660_BLKSIZE ) ) { + DBGC ( image, "ElTorito %p too short\n", image ); + return -ENOEXEC; + } + + /* Read and verify Boot Record Volume Descriptor */ + copy_from_user ( &vol_desc, image->data, ELTORITO_VOL_DESC_OFFSET, + sizeof ( vol_desc ) ); + if ( memcmp ( &vol_desc, &vol_desc_signature, + offsetof ( typeof ( vol_desc ), sector ) ) != 0 ) { + DBGC ( image, "ElTorito %p invalid Boot Record Volume " + "Descriptor\n", image ); + return -ENOEXEC; + } + *catalog_offset = ( vol_desc.sector * ISO9660_BLKSIZE ); + + DBGC ( image, "ElTorito %p boot catalog at offset %#lx\n", + image, *catalog_offset ); + + return 0; +} + +/** + * Read and verify El Torito Boot Catalog + * + * @v image El Torito file + * @v catalog_offset Offset of Boot Catalog + * @ret boot_entry El Torito boot entry + * @ret rc Return status code + */ +static int eltorito_read_catalog ( struct image *image, + unsigned long catalog_offset, + struct eltorito_boot_entry *boot_entry ) { + struct eltorito_validation_entry validation_entry; + + /* Sanity check */ + if ( image->len < ( catalog_offset + ISO9660_BLKSIZE ) ) { + DBGC ( image, "ElTorito %p bad boot catalog offset %#lx\n", + image, catalog_offset ); + return -ENOEXEC; + } + + /* Read and verify the Validation Entry of the Boot Catalog */ + copy_from_user ( &validation_entry, image->data, catalog_offset, + sizeof ( validation_entry ) ); + if ( word_checksum ( &validation_entry, + sizeof ( validation_entry ) ) != 0 ) { + DBGC ( image, "ElTorito %p bad Validation Entry checksum\n", + image ); + return -ENOEXEC; + } + + /* Read and verify the Initial/Default entry */ + copy_from_user ( boot_entry, image->data, + ( catalog_offset + sizeof ( validation_entry ) ), + sizeof ( *boot_entry ) ); + if ( boot_entry->indicator != ELTORITO_BOOTABLE ) { + DBGC ( image, "ElTorito %p not bootable\n", image ); + return -ENOEXEC; + } + if ( boot_entry->media_type != ELTORITO_NO_EMULATION ) { + DBGC ( image, "ElTorito %p cannot support media type %d\n", + image, boot_entry->media_type ); + return -ENOTSUP; + } + + DBGC ( image, "ElTorito %p media type %d segment %04x\n", + image, boot_entry->media_type, boot_entry->load_segment ); + + return 0; +} + +/** + * Load El Torito virtual disk image into memory + * + * @v image El Torito file + * @v boot_entry El Torito boot entry + * @ret rc Return status code + */ +static int eltorito_load_disk ( struct image *image, + struct eltorito_boot_entry *boot_entry ) { + unsigned long start = ( boot_entry->start * ISO9660_BLKSIZE ); + unsigned long length = ( boot_entry->length * ISO9660_BLKSIZE ); + unsigned int load_segment; + userptr_t buffer; + int rc; + + /* Sanity check */ + if ( image->len < ( start + length ) ) { + DBGC ( image, "ElTorito %p virtual disk lies outside image\n", + image ); + return -ENOEXEC; + } + DBGC ( image, "ElTorito %p virtual disk at %#lx+%#lx\n", + image, start, length ); + + /* Calculate load address */ + load_segment = boot_entry->load_segment; + buffer = real_to_user ( load_segment, ( load_segment ? 0 : 0x7c00 ) ); + + /* Verify and prepare segment */ + if ( ( rc = prep_segment ( buffer, length, length ) ) != 0 ) { + DBGC ( image, "ElTorito %p could not prepare segment: %s\n", + image, strerror ( rc ) ); + return rc; + } + + /* Copy image to segment */ + memcpy_user ( buffer, 0, image->data, start, length ); + + return 0; +} + +/** + * Load El Torito image into memory + * + * @v image El Torito file + * @ret rc Return status code + */ +static int eltorito_load ( struct image *image ) { + struct eltorito_boot_entry boot_entry; + unsigned long bootcat_offset; + int rc; + + /* Read Boot Record Volume Descriptor, if present */ + if ( ( rc = eltorito_read_voldesc ( image, &bootcat_offset ) ) != 0 ) + return rc; + + /* This is an El Torito image, valid or otherwise */ + if ( ! image->type ) + image->type = &eltorito_image_type; + + /* Read Boot Catalog */ + if ( ( rc = eltorito_read_catalog ( image, bootcat_offset, + &boot_entry ) ) != 0 ) + return rc; + + /* Load Virtual Disk image */ + if ( ( rc = eltorito_load_disk ( image, &boot_entry ) ) != 0 ) + return rc; + + /* Record load segment in image private data field */ + image->priv.ul = boot_entry.load_segment; + + return 0; +} + +/** El Torito image type */ +struct image_type eltorito_image_type __image_type ( PROBE_NORMAL ) = { + .name = "El Torito", + .load = eltorito_load, + .exec = eltorito_exec, +}; diff --git a/gpxe/src/arch/i386/image/multiboot.c b/gpxe/src/arch/i386/image/multiboot.c new file mode 100644 index 00000000..fbaebd5c --- /dev/null +++ b/gpxe/src/arch/i386/image/multiboot.c @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** + * @file + * + * Multiboot image format + * + */ + +#include <stdio.h> +#include <errno.h> +#include <assert.h> +#include <realmode.h> +#include <multiboot.h> +#include <gpxe/uaccess.h> +#include <gpxe/image.h> +#include <gpxe/segment.h> +#include <gpxe/memmap.h> +#include <gpxe/elf.h> +#include <gpxe/init.h> +#include <gpxe/features.h> + +FEATURE ( FEATURE_IMAGE, "Multiboot", DHCP_EB_FEATURE_MULTIBOOT, 1 ); + +struct image_type multiboot_image_type __image_type ( PROBE_MULTIBOOT ); + +/** + * Maximum number of modules we will allow for + * + * If this has bitten you: sorry. I did have a perfect scheme with a + * dynamically allocated list of modules on the protected-mode stack, + * but it was incompatible with some broken OSes that can only access + * low memory at boot time (even though we kindly set up 4GB flat + * physical addressing as per the multiboot specification. + * + */ +#define MAX_MODULES 8 + +/** + * Maximum combined length of command lines + * + * Again; sorry. Some broken OSes zero out any non-base memory that + * isn't part of the loaded module set, so we can't just use + * virt_to_phys(cmdline) to point to the command lines, even though + * this would comply with the Multiboot spec. + */ +#define MB_MAX_CMDLINE 512 + +/** Multiboot flags that we support */ +#define MB_SUPPORTED_FLAGS ( MB_FLAG_PGALIGN | MB_FLAG_MEMMAP | \ + MB_FLAG_VIDMODE | MB_FLAG_RAW ) + +/** Compulsory feature multiboot flags */ +#define MB_COMPULSORY_FLAGS 0x0000ffff + +/** Optional feature multiboot flags */ +#define MB_OPTIONAL_FLAGS 0xffff0000 + +/** + * Multiboot flags that we don't support + * + * We only care about the compulsory feature flags (bits 0-15); we are + * allowed to ignore the optional feature flags. + */ +#define MB_UNSUPPORTED_FLAGS ( MB_COMPULSORY_FLAGS & ~MB_SUPPORTED_FLAGS ) + +/** A multiboot header descriptor */ +struct multiboot_header_info { + /** The actual multiboot header */ + struct multiboot_header mb; + /** Offset of header within the multiboot image */ + size_t offset; +}; + +/** Multiboot module command lines */ +static char __bss16_array ( mb_cmdlines, [MB_MAX_CMDLINE] ); +#define mb_cmdlines __use_data16 ( mb_cmdlines ) + +/** Offset within module command lines */ +static unsigned int mb_cmdline_offset; + +/** + * Build multiboot memory map + * + * @v image Multiboot image + * @v mbinfo Multiboot information structure + * @v mbmemmap Multiboot memory map + * @v limit Maxmimum number of memory map entries + */ +static void multiboot_build_memmap ( struct image *image, + struct multiboot_info *mbinfo, + struct multiboot_memory_map *mbmemmap, + unsigned int limit ) { + struct memory_map memmap; + unsigned int i; + + /* Get memory map */ + get_memmap ( &memmap ); + + /* Translate into multiboot format */ + memset ( mbmemmap, 0, sizeof ( *mbmemmap ) ); + for ( i = 0 ; i < memmap.count ; i++ ) { + if ( i >= limit ) { + DBGC ( image, "MULTIBOOT %p limit of %d memmap " + "entries reached\n", image, limit ); + break; + } + mbmemmap[i].size = ( sizeof ( mbmemmap[i] ) - + sizeof ( mbmemmap[i].size ) ); + mbmemmap[i].base_addr = memmap.regions[i].start; + mbmemmap[i].length = ( memmap.regions[i].end - + memmap.regions[i].start ); + mbmemmap[i].type = MBMEM_RAM; + mbinfo->mmap_length += sizeof ( mbmemmap[i] ); + if ( memmap.regions[i].start == 0 ) + mbinfo->mem_lower = ( memmap.regions[i].end / 1024 ); + if ( memmap.regions[i].start == 0x100000 ) + mbinfo->mem_upper = ( ( memmap.regions[i].end - + 0x100000 ) / 1024 ); + } +} + +/** + * Add command line in base memory + * + * @v cmdline Command line + * @ret physaddr Physical address of command line + */ +physaddr_t multiboot_add_cmdline ( const char *cmdline ) { + char *mb_cmdline; + + if ( ! cmdline ) + cmdline = ""; + + /* Copy command line to base memory buffer */ + mb_cmdline = ( mb_cmdlines + mb_cmdline_offset ); + mb_cmdline_offset += + ( snprintf ( mb_cmdline, + ( sizeof ( mb_cmdlines ) - mb_cmdline_offset ), + "%s", cmdline ) + 1 ); + + /* Truncate to terminating NUL in buffer if necessary */ + if ( mb_cmdline_offset > sizeof ( mb_cmdlines ) ) + mb_cmdline_offset = ( sizeof ( mb_cmdlines ) - 1 ); + + return virt_to_phys ( mb_cmdline ); +} + +/** + * Build multiboot module list + * + * @v image Multiboot image + * @v modules Module list to fill, or NULL + * @ret count Number of modules + */ +static unsigned int +multiboot_build_module_list ( struct image *image, + struct multiboot_module *modules, + unsigned int limit ) { + struct image *module_image; + struct multiboot_module *module; + unsigned int count = 0; + unsigned int insert; + physaddr_t start; + physaddr_t end; + unsigned int i; + + /* Add each image as a multiboot module */ + for_each_image ( module_image ) { + + if ( count >= limit ) { + DBGC ( image, "MULTIBOOT %p limit of %d modules " + "reached\n", image, limit ); + break; + } + + /* Do not include kernel image itself as a module */ + if ( module_image == image ) + continue; + + /* At least some OSes expect the multiboot modules to + * be in ascending order, so we have to support it. + */ + start = user_to_phys ( module_image->data, 0 ); + end = user_to_phys ( module_image->data, module_image->len ); + for ( insert = 0 ; insert < count ; insert++ ) { + if ( start < modules[insert].mod_start ) + break; + } + module = &modules[insert]; + memmove ( ( module + 1 ), module, + ( ( count - insert ) * sizeof ( *module ) ) ); + module->mod_start = start; + module->mod_end = end; + module->string = + multiboot_add_cmdline ( module_image->cmdline ); + module->reserved = 0; + + /* We promise to page-align modules */ + assert ( ( module->mod_start & 0xfff ) == 0 ); + + count++; + } + + /* Dump module configuration */ + for ( i = 0 ; i < count ; i++ ) { + DBGC ( image, "MULTIBOOT %p module %d is [%lx,%lx)\n", + image, i, modules[i].mod_start, + modules[i].mod_end ); + } + + return count; +} + +/** + * The multiboot information structure + * + * Kept in base memory because some OSes won't find it elsewhere, + * along with the other structures belonging to the Multiboot + * information table. + */ +static struct multiboot_info __bss16 ( mbinfo ); +#define mbinfo __use_data16 ( mbinfo ) + +/** The multiboot bootloader name */ +static char __data16_array ( mb_bootloader_name, [] ) = "gPXE " VERSION; +#define mb_bootloader_name __use_data16 ( mb_bootloader_name ) + +/** The multiboot memory map */ +static struct multiboot_memory_map + __bss16_array ( mbmemmap, [MAX_MEMORY_REGIONS] ); +#define mbmemmap __use_data16 ( mbmemmap ) + +/** The multiboot module list */ +static struct multiboot_module __bss16_array ( mbmodules, [MAX_MODULES] ); +#define mbmodules __use_data16 ( mbmodules ) + +/** + * Execute multiboot image + * + * @v image Multiboot image + * @ret rc Return status code + */ +static int multiboot_exec ( struct image *image ) { + physaddr_t entry = image->priv.phys; + + /* Populate multiboot information structure */ + memset ( &mbinfo, 0, sizeof ( mbinfo ) ); + mbinfo.flags = ( MBI_FLAG_LOADER | MBI_FLAG_MEM | MBI_FLAG_MMAP | + MBI_FLAG_CMDLINE | MBI_FLAG_MODS ); + multiboot_build_memmap ( image, &mbinfo, mbmemmap, + ( sizeof(mbmemmap) / sizeof(mbmemmap[0]) ) ); + mb_cmdline_offset = 0; + mbinfo.cmdline = multiboot_add_cmdline ( image->cmdline ); + mbinfo.mods_count = multiboot_build_module_list ( image, mbmodules, + ( sizeof(mbmodules) / sizeof(mbmodules[0]) ) ); + mbinfo.mods_addr = virt_to_phys ( mbmodules ); + mbinfo.mmap_addr = virt_to_phys ( mbmemmap ); + mbinfo.boot_loader_name = virt_to_phys ( mb_bootloader_name ); + + /* Multiboot images may not return and have no callback + * interface, so shut everything down prior to booting the OS. + */ + shutdown(); + + /* Jump to OS with flat physical addressing */ + __asm__ __volatile__ ( PHYS_CODE ( "call *%%edi\n\t" ) + : : "a" ( MULTIBOOT_BOOTLOADER_MAGIC ), + "b" ( virt_to_phys ( &mbinfo ) ), + "D" ( entry ) + : "ecx", "edx", "esi", "ebp", "memory" ); + + DBGC ( image, "MULTIBOOT %p returned\n", image ); + + /* It isn't safe to continue after calling shutdown() */ + while ( 1 ) {} + + return -ECANCELED; /* -EIMPOSSIBLE, anyone? */ +} + +/** + * Find multiboot header + * + * @v image Multiboot file + * @v hdr Multiboot header descriptor to fill in + * @ret rc Return status code + */ +static int multiboot_find_header ( struct image *image, + struct multiboot_header_info *hdr ) { + uint32_t buf[64]; + size_t offset; + unsigned int buf_idx; + uint32_t checksum; + + /* Scan through first 8kB of image file 256 bytes at a time. + * (Use the buffering to avoid the overhead of a + * copy_from_user() for every dword.) + */ + for ( offset = 0 ; offset < 8192 ; offset += sizeof ( buf[0] ) ) { + /* Check for end of image */ + if ( offset > image->len ) + break; + /* Refill buffer if applicable */ + buf_idx = ( ( offset % sizeof ( buf ) ) / sizeof ( buf[0] ) ); + if ( buf_idx == 0 ) { + copy_from_user ( buf, image->data, offset, + sizeof ( buf ) ); + } + /* Check signature */ + if ( buf[buf_idx] != MULTIBOOT_HEADER_MAGIC ) + continue; + /* Copy header and verify checksum */ + copy_from_user ( &hdr->mb, image->data, offset, + sizeof ( hdr->mb ) ); + checksum = ( hdr->mb.magic + hdr->mb.flags + + hdr->mb.checksum ); + if ( checksum != 0 ) + continue; + /* Record offset of multiboot header and return */ + hdr->offset = offset; + return 0; + } + + /* No multiboot header found */ + return -ENOEXEC; +} + +/** + * Load raw multiboot image into memory + * + * @v image Multiboot file + * @v hdr Multiboot header descriptor + * @ret rc Return status code + */ +static int multiboot_load_raw ( struct image *image, + struct multiboot_header_info *hdr ) { + size_t offset; + size_t filesz; + size_t memsz; + userptr_t buffer; + int rc; + + /* Verify and prepare segment */ + offset = ( hdr->offset - hdr->mb.header_addr + hdr->mb.load_addr ); + filesz = ( hdr->mb.load_end_addr - hdr->mb.load_addr ); + memsz = ( hdr->mb.bss_end_addr - hdr->mb.load_addr ); + buffer = phys_to_user ( hdr->mb.load_addr ); + if ( ( rc = prep_segment ( buffer, filesz, memsz ) ) != 0 ) { + DBGC ( image, "MULTIBOOT %p could not prepare segment: %s\n", + image, strerror ( rc ) ); + return rc; + } + + /* Copy image to segment */ + memcpy_user ( buffer, 0, image->data, offset, filesz ); + + /* Record execution entry point in image private data field */ + image->priv.phys = hdr->mb.entry_addr; + + return 0; +} + +/** + * Load ELF multiboot image into memory + * + * @v image Multiboot file + * @ret rc Return status code + */ +static int multiboot_load_elf ( struct image *image ) { + int rc; + + /* Load ELF image*/ + if ( ( rc = elf_load ( image ) ) != 0 ) { + DBGC ( image, "MULTIBOOT %p ELF image failed to load: %s\n", + image, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Load multiboot image into memory + * + * @v image Multiboot file + * @ret rc Return status code + */ +static int multiboot_load ( struct image *image ) { + struct multiboot_header_info hdr; + int rc; + + /* Locate multiboot header, if present */ + if ( ( rc = multiboot_find_header ( image, &hdr ) ) != 0 ) { + DBGC ( image, "MULTIBOOT %p has no multiboot header\n", + image ); + return rc; + } + DBGC ( image, "MULTIBOOT %p found header with flags %08lx\n", + image, hdr.mb.flags ); + + /* This is a multiboot image, valid or otherwise */ + if ( ! image->type ) + image->type = &multiboot_image_type; + + /* Abort if we detect flags that we cannot support */ + if ( hdr.mb.flags & MB_UNSUPPORTED_FLAGS ) { + DBGC ( image, "MULTIBOOT %p flags %08lx not supported\n", + image, ( hdr.mb.flags & MB_UNSUPPORTED_FLAGS ) ); + return -ENOTSUP; + } + + /* Load the actual image */ + if ( hdr.mb.flags & MB_FLAG_RAW ) { + if ( ( rc = multiboot_load_raw ( image, &hdr ) ) != 0 ) + return rc; + } else { + if ( ( rc = multiboot_load_elf ( image ) ) != 0 ) + return rc; + } + + return 0; +} + +/** Multiboot image type */ +struct image_type multiboot_image_type __image_type ( PROBE_MULTIBOOT ) = { + .name = "Multiboot", + .load = multiboot_load, + .exec = multiboot_exec, +}; diff --git a/gpxe/src/arch/i386/image/nbi.c b/gpxe/src/arch/i386/image/nbi.c new file mode 100644 index 00000000..73791be9 --- /dev/null +++ b/gpxe/src/arch/i386/image/nbi.c @@ -0,0 +1,458 @@ +#include <errno.h> +#include <assert.h> +#include <realmode.h> +#include <gateA20.h> +#include <memsizes.h> +#include <basemem_packet.h> +#include <gpxe/uaccess.h> +#include <gpxe/segment.h> +#include <gpxe/init.h> +#include <gpxe/netdevice.h> +#include <gpxe/fakedhcp.h> +#include <gpxe/image.h> +#include <gpxe/features.h> + +/** @file + * + * NBI image format. + * + * The Net Boot Image format is defined by the "Draft Net Boot Image + * Proposal 0.3" by Jamie Honan, Gero Kuhlmann and Ken Yap. It is now + * considered to be a legacy format, but it still included because a + * large amount of software (e.g. nymph, LTSP) makes use of NBI files. + * + * Etherboot does not implement the INT 78 callback interface + * described by the NBI specification. For a callback interface on + * x86 architecture, use PXE. + * + */ + +FEATURE ( FEATURE_IMAGE, "NBI", DHCP_EB_FEATURE_NBI, 1 ); + +struct image_type nbi_image_type __image_type ( PROBE_NORMAL ); + +/** + * An NBI image header + * + * Note that the length field uses a peculiar encoding; use the + * NBI_LENGTH() macro to decode the actual header length. + * + */ +struct imgheader { + unsigned long magic; /**< Magic number (NBI_MAGIC) */ + union { + unsigned char length; /**< Nibble-coded header length */ + unsigned long flags; /**< Image flags */ + }; + segoff_t location; /**< 16-bit seg:off header location */ + union { + segoff_t segoff; /**< 16-bit seg:off entry point */ + unsigned long linear; /**< 32-bit entry point */ + } execaddr; +} __attribute__ (( packed )); + +/** NBI magic number */ +#define NBI_MAGIC 0x1B031336UL + +/* Interpretation of the "length" fields */ +#define NBI_NONVENDOR_LENGTH(len) ( ( (len) & 0x0f ) << 2 ) +#define NBI_VENDOR_LENGTH(len) ( ( (len) & 0xf0 ) >> 2 ) +#define NBI_LENGTH(len) ( NBI_NONVENDOR_LENGTH(len) + NBI_VENDOR_LENGTH(len) ) + +/* Interpretation of the "flags" fields */ +#define NBI_PROGRAM_RETURNS(flags) ( (flags) & ( 1 << 8 ) ) +#define NBI_LINEAR_EXEC_ADDR(flags) ( (flags) & ( 1 << 31 ) ) + +/** NBI header length */ +#define NBI_HEADER_LENGTH 512 + +/** + * An NBI segment header + * + * Note that the length field uses a peculiar encoding; use the + * NBI_LENGTH() macro to decode the actual header length. + * + */ +struct segheader { + unsigned char length; /**< Nibble-coded header length */ + unsigned char vendortag; /**< Vendor-defined private tag */ + unsigned char reserved; + unsigned char flags; /**< Segment flags */ + unsigned long loadaddr; /**< Load address */ + unsigned long imglength; /**< Segment length in NBI file */ + unsigned long memlength; /**< Segment length in memory */ +}; + +/* Interpretation of the "flags" fields */ +#define NBI_LOADADDR_FLAGS(flags) ( (flags) & 0x03 ) +#define NBI_LOADADDR_ABS 0x00 +#define NBI_LOADADDR_AFTER 0x01 +#define NBI_LOADADDR_END 0x02 +#define NBI_LOADADDR_BEFORE 0x03 +#define NBI_LAST_SEGHEADER(flags) ( (flags) & ( 1 << 2 ) ) + +/* Define a type for passing info to a loaded program */ +struct ebinfo { + uint8_t major, minor; /* Version */ + uint16_t flags; /* Bit flags */ +}; + +/** Info passed to NBI image */ +static struct ebinfo loaderinfo = { + VERSION_MAJOR, VERSION_MINOR, + 0 +}; + +/** + * Prepare a segment for an NBI image + * + * @v image NBI image + * @v offset Offset within NBI image + * @v filesz Length of initialised-data portion of the segment + * @v memsz Total length of the segment + * @v src Source for initialised data + * @ret rc Return status code + */ +static int nbi_prepare_segment ( struct image *image, size_t offset __unused, + userptr_t dest, size_t filesz, size_t memsz ){ + int rc; + + if ( ( rc = prep_segment ( dest, filesz, memsz ) ) != 0 ) { + DBGC ( image, "NBI %p could not prepare segment: %s\n", + image, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Load a segment for an NBI image + * + * @v image NBI image + * @v offset Offset within NBI image + * @v filesz Length of initialised-data portion of the segment + * @v memsz Total length of the segment + * @v src Source for initialised data + * @ret rc Return status code + */ +static int nbi_load_segment ( struct image *image, size_t offset, + userptr_t dest, size_t filesz, + size_t memsz __unused ) { + memcpy_user ( dest, 0, image->data, offset, filesz ); + return 0; +} + +/** + * Process segments of an NBI image + * + * @v image NBI image + * @v imgheader Image header information + * @v process Function to call for each segment + * @ret rc Return status code + */ +static int nbi_process_segments ( struct image *image, + struct imgheader *imgheader, + int ( * process ) ( struct image *image, + size_t offset, + userptr_t dest, + size_t filesz, + size_t memsz ) ) { + struct segheader sh; + size_t offset = 0; + size_t sh_off; + userptr_t dest; + size_t filesz; + size_t memsz; + int rc; + + /* Copy image header to target location */ + dest = real_to_user ( imgheader->location.segment, + imgheader->location.offset ); + filesz = memsz = NBI_HEADER_LENGTH; + if ( ( rc = process ( image, offset, dest, filesz, memsz ) ) != 0 ) + return rc; + offset += filesz; + + /* Process segments in turn */ + sh_off = NBI_LENGTH ( imgheader->length ); + do { + /* Read segment header */ + copy_from_user ( &sh, image->data, sh_off, sizeof ( sh ) ); + if ( sh.length == 0 ) { + /* Avoid infinite loop? */ + DBGC ( image, "NBI %p invalid segheader length 0\n", + image ); + return -ENOEXEC; + } + + /* Calculate segment load address */ + switch ( NBI_LOADADDR_FLAGS ( sh.flags ) ) { + case NBI_LOADADDR_ABS: + dest = phys_to_user ( sh.loadaddr ); + break; + case NBI_LOADADDR_AFTER: + dest = userptr_add ( dest, memsz + sh.loadaddr ); + break; + case NBI_LOADADDR_BEFORE: + dest = userptr_add ( dest, -sh.loadaddr ); + break; + case NBI_LOADADDR_END: + /* Not correct according to the spec, but + * maintains backwards compatibility with + * previous versions of Etherboot. + */ + dest = phys_to_user ( ( extmemsize() + 1024 ) * 1024 + - sh.loadaddr ); + break; + default: + /* Cannot be reached */ + assert ( 0 ); + } + + /* Process this segment */ + filesz = sh.imglength; + memsz = sh.memlength; + if ( ( offset + filesz ) > image->len ) { + DBGC ( image, "NBI %p segment outside file\n", image ); + return -ENOEXEC; + } + if ( ( rc = process ( image, offset, dest, + filesz, memsz ) ) != 0 ) { + return rc; + } + offset += filesz; + + /* Next segheader */ + sh_off += NBI_LENGTH ( sh.length ); + if ( sh_off >= NBI_HEADER_LENGTH ) { + DBGC ( image, "NBI %p header overflow\n", image ); + return -ENOEXEC; + } + + } while ( ! NBI_LAST_SEGHEADER ( sh.flags ) ); + + if ( offset != image->len ) { + DBGC ( image, "NBI %p length wrong (file %zd, metadata %zd)\n", + image, image->len, offset ); + return -ENOEXEC; + } + + return 0; +} + +/** + * Load an NBI image into memory + * + * @v image NBI image + * @ret rc Return status code + */ +static int nbi_load ( struct image *image ) { + struct imgheader imgheader; + int rc; + + /* If we don't have enough data give up */ + if ( image->len < NBI_HEADER_LENGTH ) { + DBGC ( image, "NBI %p too short for an NBI image\n", image ); + return -ENOEXEC; + } + + /* Check image header */ + copy_from_user ( &imgheader, image->data, 0, sizeof ( imgheader ) ); + if ( imgheader.magic != NBI_MAGIC ) { + DBGC ( image, "NBI %p has no NBI signature\n", image ); + return -ENOEXEC; + } + + /* This is an NBI image, valid or otherwise */ + if ( ! image->type ) + image->type = &nbi_image_type; + + DBGC ( image, "NBI %p placing header at %hx:%hx\n", image, + imgheader.location.segment, imgheader.location.offset ); + + /* NBI files can have overlaps between segments; the bss of + * one segment may overlap the initialised data of another. I + * assume this is a design flaw, but there are images out + * there that we need to work with. We therefore do two + * passes: first to initialise the segments, then to copy the + * data. This avoids zeroing out already-copied data. + */ + if ( ( rc = nbi_process_segments ( image, &imgheader, + nbi_prepare_segment ) ) != 0 ) + return rc; + if ( ( rc = nbi_process_segments ( image, &imgheader, + nbi_load_segment ) ) != 0 ) + return rc; + + /* Record header address in image private data field */ + image->priv.user = real_to_user ( imgheader.location.segment, + imgheader.location.offset ); + + return 0; +} + +/** + * Boot a 16-bit NBI image + * + * @v imgheader Image header information + * @ret rc Return status code, if image returns + */ +static int nbi_boot16 ( struct image *image, struct imgheader *imgheader ) { + int discard_D, discard_S, discard_b; + int rc; + + DBGC ( image, "NBI %p executing 16-bit image at %04x:%04x\n", image, + imgheader->execaddr.segoff.segment, + imgheader->execaddr.segoff.offset ); + + gateA20_unset(); + + __asm__ __volatile__ ( + REAL_CODE ( "pushw %%ds\n\t" /* far pointer to bootp data */ + "pushw %%bx\n\t" + "pushl %%esi\n\t" /* location */ + "pushw %%cs\n\t" /* lcall execaddr */ + "call 1f\n\t" + "jmp 2f\n\t" + "\n1:\n\t" + "pushl %%edi\n\t" + "lret\n\t" + "\n2:\n\t" + "addw $8,%%sp\n\t" /* clean up stack */ ) + : "=a" ( rc ), "=D" ( discard_D ), "=S" ( discard_S ), + "=b" ( discard_b ) + : "D" ( imgheader->execaddr.segoff ), + "S" ( imgheader->location ), + "b" ( __from_data16 ( basemem_packet ) ) + : "ecx", "edx", "ebp" ); + + gateA20_set(); + + return rc; +} + +/** + * Boot a 32-bit NBI image + * + * @v imgheader Image header information + * @ret rc Return status code, if image returns + */ +static int nbi_boot32 ( struct image *image, struct imgheader *imgheader ) { + int discard_D, discard_S, discard_b; + int rc; + + DBGC ( image, "NBI %p executing 32-bit image at %lx\n", + image, imgheader->execaddr.linear ); + + /* no gateA20_unset for PM call */ + + /* Jump to OS with flat physical addressing */ + __asm__ __volatile__ ( + PHYS_CODE ( "pushl %%ebx\n\t" /* bootp data */ + "pushl %%esi\n\t" /* imgheader */ + "pushl %%eax\n\t" /* loaderinfo */ + "call *%%edi\n\t" + "addl $12, %%esp\n\t" /* clean up stack */ ) + : "=a" ( rc ), "=D" ( discard_D ), "=S" ( discard_S ), + "=b" ( discard_b ) + : "D" ( imgheader->execaddr.linear ), + "S" ( ( imgheader->location.segment << 4 ) + + imgheader->location.offset ), + "b" ( virt_to_phys ( basemem_packet ) ), + "a" ( virt_to_phys ( &loaderinfo ) ) + : "ecx", "edx", "ebp", "memory" ); + + return rc; +} + +/** + * Guess boot network device + * + * @ret netdev Boot network device + */ +static struct net_device * guess_boot_netdev ( void ) { + struct net_device *boot_netdev; + + /* Just use the first network device */ + for_each_netdev ( boot_netdev ) { + return boot_netdev; + } + + return NULL; +} + +/** + * Prepare DHCP parameter block for NBI image + * + * @v image NBI image + * @ret rc Return status code + */ +static int nbi_prepare_dhcp ( struct image *image ) { + struct net_device *boot_netdev; + int rc; + + boot_netdev = guess_boot_netdev(); + if ( ! boot_netdev ) { + DBGC ( image, "NBI %p could not identify a network device\n", + image ); + return -ENODEV; + } + + if ( ( rc = create_fakedhcpack ( boot_netdev, basemem_packet, + sizeof ( basemem_packet ) ) ) != 0 ) { + DBGC ( image, "NBI %p failed to build DHCP packet\n", image ); + return rc; + } + + return 0; +} + +/** + * Execute a loaded NBI image + * + * @v image NBI image + * @ret rc Return status code + */ +static int nbi_exec ( struct image *image ) { + struct imgheader imgheader; + int may_return; + int rc; + + copy_from_user ( &imgheader, image->priv.user, 0, + sizeof ( imgheader ) ); + + /* Prepare DHCP option block */ + if ( ( rc = nbi_prepare_dhcp ( image ) ) != 0 ) + return rc; + + /* Shut down now if NBI image will not return */ + may_return = NBI_PROGRAM_RETURNS ( imgheader.flags ); + if ( ! may_return ) + shutdown(); + + /* Execute NBI image */ + if ( NBI_LINEAR_EXEC_ADDR ( imgheader.flags ) ) { + rc = nbi_boot32 ( image, &imgheader ); + } else { + rc = nbi_boot16 ( image, &imgheader ); + } + + if ( ! may_return ) { + /* Cannot continue after shutdown() called */ + DBGC ( image, "NBI %p returned %d from non-returnable image\n", + image, rc ); + while ( 1 ) {} + } + + DBGC ( image, "NBI %p returned %d\n", image, rc ); + + return rc; +} + +/** NBI image type */ +struct image_type nbi_image_type __image_type ( PROBE_NORMAL ) = { + .name = "NBI", + .load = nbi_load, + .exec = nbi_exec, +}; diff --git a/gpxe/src/arch/i386/image/pxe_image.c b/gpxe/src/arch/i386/image/pxe_image.c new file mode 100644 index 00000000..9e634f14 --- /dev/null +++ b/gpxe/src/arch/i386/image/pxe_image.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** + * @file + * + * PXE image format + * + */ + +#include <pxe.h> +#include <pxe_call.h> +#include <gpxe/uaccess.h> +#include <gpxe/image.h> +#include <gpxe/segment.h> +#include <gpxe/netdevice.h> +#include <gpxe/features.h> + +FEATURE ( FEATURE_IMAGE, "PXE", DHCP_EB_FEATURE_PXE, 1 ); + +struct image_type pxe_image_type __image_type ( PROBE_PXE ); + +/** + * Execute PXE image + * + * @v image PXE image + * @ret rc Return status code + */ +static int pxe_exec ( struct image *image ) { + struct net_device *netdev; + int rc; + + /* Ensure that PXE stack is ready to use */ + pxe_init_structures(); + pxe_hook_int1a(); + + /* Arbitrarily pick the first open network device to use for PXE */ + for_each_netdev ( netdev ) { + pxe_set_netdev ( netdev ); + break; + } + + /* Many things will break if pxe_netdev is NULL */ + if ( ! pxe_netdev ) { + DBGC ( image, "IMAGE %p could not locate PXE net device\n", + image ); + return -ENODEV; + } + + /* Start PXE NBP */ + rc = pxe_start_nbp(); + + /* Deactivate PXE */ + pxe_set_netdev ( NULL ); + pxe_unhook_int1a(); + + return rc; +} + +/** + * Load PXE image into memory + * + * @v image PXE file + * @ret rc Return status code + */ +int pxe_load ( struct image *image ) { + userptr_t buffer = real_to_user ( 0, 0x7c00 ); + size_t filesz = image->len; + size_t memsz = image->len; + int rc; + + /* There are no signature checks for PXE; we will accept anything */ + if ( ! image->type ) + image->type = &pxe_image_type; + + /* Verify and prepare segment */ + if ( ( rc = prep_segment ( buffer, filesz, memsz ) ) != 0 ) { + DBGC ( image, "IMAGE %p could not prepare segment: %s\n", + image, strerror ( rc ) ); + return rc; + } + + /* Copy image to segment */ + memcpy_user ( buffer, 0, image->data, 0, filesz ); + + return 0; +} + +/** PXE image type */ +struct image_type pxe_image_type __image_type ( PROBE_PXE ) = { + .name = "PXE", + .load = pxe_load, + .exec = pxe_exec, +}; diff --git a/gpxe/src/arch/i386/include/basemem.h b/gpxe/src/arch/i386/include/basemem.h new file mode 100644 index 00000000..cd5668e0 --- /dev/null +++ b/gpxe/src/arch/i386/include/basemem.h @@ -0,0 +1,33 @@ +#ifndef _BASEMEM_H +#define _BASEMEM_H + +/** @file + * + * Base memory allocation + * + */ + +#include <stdint.h> +#include <realmode.h> +#include <bios.h> + +/** + * Read the BIOS free base memory counter + * + * @ret fbms Free base memory counter (in kB) + */ +static inline unsigned int get_fbms ( void ) { + uint16_t fbms; + + get_real ( fbms, BDA_SEG, BDA_FBMS ); + return fbms; +} + +extern void set_fbms ( unsigned int new_fbms ); + +/* Actually in hidemem.c, but putting it here avoids polluting the + * architecture-independent include/hidemem.h. + */ +extern void hide_basemem ( void ); + +#endif /* _BASEMEM_H */ diff --git a/gpxe/src/arch/i386/include/basemem_packet.h b/gpxe/src/arch/i386/include/basemem_packet.h new file mode 100644 index 00000000..e4d4f49c --- /dev/null +++ b/gpxe/src/arch/i386/include/basemem_packet.h @@ -0,0 +1,13 @@ +#ifndef BASEMEM_PACKET_H +#define BASEMEM_PACKET_H + +#include <realmode.h> + +/** Maximum length of base memory packet buffer */ +#define BASEMEM_PACKET_LEN 1514 + +/** Base memory packet buffer */ +extern char __bss16_array ( basemem_packet, [BASEMEM_PACKET_LEN] ); +#define basemem_packet __use_data16 ( basemem_packet ) + +#endif /* BASEMEM_PACKET_H */ diff --git a/gpxe/src/arch/i386/include/bios.h b/gpxe/src/arch/i386/include/bios.h new file mode 100644 index 00000000..630a898b --- /dev/null +++ b/gpxe/src/arch/i386/include/bios.h @@ -0,0 +1,11 @@ +#ifndef BIOS_H +#define BIOS_H + +#define BDA_SEG 0x0040 +#define BDA_FBMS 0x0013 +#define BDA_NUM_DRIVES 0x0075 + +extern unsigned long currticks ( void ); +extern void cpu_nap ( void ); + +#endif /* BIOS_H */ diff --git a/gpxe/src/arch/i386/include/bios_disks.h b/gpxe/src/arch/i386/include/bios_disks.h new file mode 100644 index 00000000..0dd7c4eb --- /dev/null +++ b/gpxe/src/arch/i386/include/bios_disks.h @@ -0,0 +1,69 @@ +#ifndef BIOS_DISKS_H +#define BIOS_DISKS_H + +#include "dev.h" + +/* + * Constants + * + */ + +#define BIOS_DISK_MAX_NAME_LEN 6 + +struct bios_disk_sector { + char data[512]; +}; + +/* + * The location of a BIOS disk + * + */ +struct bios_disk_loc { + uint8_t drive; +}; + +/* + * A physical BIOS disk device + * + */ +struct bios_disk_device { + char name[BIOS_DISK_MAX_NAME_LEN]; + uint8_t drive; + uint8_t type; +}; + +/* + * A BIOS disk driver, with a valid device ID range and naming + * function. + * + */ +struct bios_disk_driver { + void ( *fill_drive_name ) ( char *buf, uint8_t drive ); + uint8_t min_drive; + uint8_t max_drive; +}; + +/* + * Define a BIOS disk driver + * + */ +#define BIOS_DISK_DRIVER( _name, _fill_drive_name, _min_drive, _max_drive ) \ + static struct bios_disk_driver _name = { \ + .fill_drive_name = _fill_drive_name, \ + .min_drive = _min_drive, \ + .max_drive = _max_drive, \ + } + +/* + * Functions in bios_disks.c + * + */ + + +/* + * bios_disk bus global definition + * + */ +extern struct bus_driver bios_disk_driver; + +#endif /* BIOS_DISKS_H */ diff --git a/gpxe/src/arch/i386/include/biosint.h b/gpxe/src/arch/i386/include/biosint.h new file mode 100644 index 00000000..d4e34963 --- /dev/null +++ b/gpxe/src/arch/i386/include/biosint.h @@ -0,0 +1,18 @@ +#ifndef BIOSINT_H +#define BIOSINT_H + +/** + * @file BIOS interrupts + * + */ + +struct segoff; + +extern int hooked_bios_interrupts; +extern void hook_bios_interrupt ( unsigned int interrupt, unsigned int handler, + struct segoff *chain_vector ); +extern int unhook_bios_interrupt ( unsigned int interrupt, + unsigned int handler, + struct segoff *chain_vector ); + +#endif /* BIOSINT_H */ diff --git a/gpxe/src/arch/i386/include/bits/byteswap.h b/gpxe/src/arch/i386/include/bits/byteswap.h new file mode 100644 index 00000000..54b93ab9 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/byteswap.h @@ -0,0 +1,76 @@ +#ifndef ETHERBOOT_BITS_BYTESWAP_H +#define ETHERBOOT_BITS_BYTESWAP_H + +static inline __attribute__ ((always_inline, const)) uint16_t +__i386_bswap_16(uint16_t x) +{ + __asm__("xchgb %b0,%h0\n\t" + : "=q" (x) + : "0" (x)); + return x; +} + +static inline __attribute__ ((always_inline, const)) uint32_t +__i386_bswap_32(uint32_t x) +{ + __asm__("xchgb %b0,%h0\n\t" + "rorl $16,%0\n\t" + "xchgb %b0,%h0" + : "=q" (x) + : "0" (x)); + return x; +} + +static inline __attribute__ ((always_inline, const)) uint64_t +__i386_bswap_64(uint64_t x) +{ + union { + uint64_t qword; + uint32_t dword[2]; + } u; + + u.qword = x; + u.dword[0] = __i386_bswap_32(u.dword[0]); + u.dword[1] = __i386_bswap_32(u.dword[1]); + __asm__("xchgl %0,%1" + : "=r" ( u.dword[0] ), "=r" ( u.dword[1] ) + : "0" ( u.dword[0] ), "1" ( u.dword[1] ) ); + return u.qword; +} + +#define __bswap_constant_16(x) \ + ((uint16_t)((((uint16_t)(x) & 0x00ff) << 8) | \ + (((uint16_t)(x) & 0xff00) >> 8))) + +#define __bswap_constant_32(x) \ + ((uint32_t)((((uint32_t)(x) & 0x000000ffU) << 24) | \ + (((uint32_t)(x) & 0x0000ff00U) << 8) | \ + (((uint32_t)(x) & 0x00ff0000U) >> 8) | \ + (((uint32_t)(x) & 0xff000000U) >> 24))) + +#define __bswap_constant_64(x) \ + ((uint64_t)((((uint64_t)(x) & 0x00000000000000ffULL) << 56) | \ + (((uint64_t)(x) & 0x000000000000ff00ULL) << 40) | \ + (((uint64_t)(x) & 0x0000000000ff0000ULL) << 24) | \ + (((uint64_t)(x) & 0x00000000ff000000ULL) << 8) | \ + (((uint64_t)(x) & 0x000000ff00000000ULL) >> 8) | \ + (((uint64_t)(x) & 0x0000ff0000000000ULL) >> 24) | \ + (((uint64_t)(x) & 0x00ff000000000000ULL) >> 40) | \ + (((uint64_t)(x) & 0xff00000000000000ULL) >> 56))) + +#define __bswap_16(x) \ + ((uint16_t)(__builtin_constant_p(x) ? \ + __bswap_constant_16(x) : \ + __i386_bswap_16(x))) + +#define __bswap_32(x) \ + ((uint32_t)(__builtin_constant_p(x) ? \ + __bswap_constant_32(x) : \ + __i386_bswap_32(x))) + +#define __bswap_64(x) \ + ((uint64_t)(__builtin_constant_p(x) ? \ + __bswap_constant_64(x) : \ + __i386_bswap_64(x))) + +#endif /* ETHERBOOT_BITS_BYTESWAP_H */ diff --git a/gpxe/src/arch/i386/include/bits/cpu.h b/gpxe/src/arch/i386/include/bits/cpu.h new file mode 100644 index 00000000..83339ddd --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/cpu.h @@ -0,0 +1,86 @@ +#ifndef I386_BITS_CPU_H +#define I386_BITS_CPU_H + +/* Intel-defined CPU features, CPUID level 0x00000001, word 0 */ +#define X86_FEATURE_FPU 0 /* Onboard FPU */ +#define X86_FEATURE_VME 1 /* Virtual Mode Extensions */ +#define X86_FEATURE_DE 2 /* Debugging Extensions */ +#define X86_FEATURE_PSE 3 /* Page Size Extensions */ +#define X86_FEATURE_TSC 4 /* Time Stamp Counter */ +#define X86_FEATURE_MSR 5 /* Model-Specific Registers, RDMSR, WRMSR */ +#define X86_FEATURE_PAE 6 /* Physical Address Extensions */ +#define X86_FEATURE_MCE 7 /* Machine Check Architecture */ +#define X86_FEATURE_CX8 8 /* CMPXCHG8 instruction */ +#define X86_FEATURE_APIC 9 /* Onboard APIC */ +#define X86_FEATURE_SEP 11 /* SYSENTER/SYSEXIT */ +#define X86_FEATURE_MTRR 12 /* Memory Type Range Registers */ +#define X86_FEATURE_PGE 13 /* Page Global Enable */ +#define X86_FEATURE_MCA 14 /* Machine Check Architecture */ +#define X86_FEATURE_CMOV 15 /* CMOV instruction (FCMOVCC and FCOMI too if FPU present) */ +#define X86_FEATURE_PAT 16 /* Page Attribute Table */ +#define X86_FEATURE_PSE36 17 /* 36-bit PSEs */ +#define X86_FEATURE_PN 18 /* Processor serial number */ +#define X86_FEATURE_CLFLSH 19 /* Supports the CLFLUSH instruction */ +#define X86_FEATURE_DTES 21 /* Debug Trace Store */ +#define X86_FEATURE_ACPI 22 /* ACPI via MSR */ +#define X86_FEATURE_MMX 23 /* Multimedia Extensions */ +#define X86_FEATURE_FXSR 24 /* FXSAVE and FXRSTOR instructions (fast save and restore */ + /* of FPU context), and CR4.OSFXSR available */ +#define X86_FEATURE_XMM 25 /* Streaming SIMD Extensions */ +#define X86_FEATURE_XMM2 26 /* Streaming SIMD Extensions-2 */ +#define X86_FEATURE_SELFSNOOP 27 /* CPU self snoop */ +#define X86_FEATURE_HT 28 /* Hyper-Threading */ +#define X86_FEATURE_ACC 29 /* Automatic clock control */ +#define X86_FEATURE_IA64 30 /* IA-64 processor */ + +/* AMD-defined CPU features, CPUID level 0x80000001, word 1 */ +/* Don't duplicate feature flags which are redundant with Intel! */ +#define X86_FEATURE_SYSCALL 11 /* SYSCALL/SYSRET */ +#define X86_FEATURE_MMXEXT 22 /* AMD MMX extensions */ +#define X86_FEATURE_LM 29 /* Long Mode (x86-64) */ +#define X86_FEATURE_3DNOWEXT 30 /* AMD 3DNow! extensions */ +#define X86_FEATURE_3DNOW 31 /* 3DNow! */ + +/** x86 CPU information */ +struct cpuinfo_x86 { + /** CPU features */ + unsigned int features; + /** 64-bit CPU features */ + unsigned int amd_features; +}; + +/* + * EFLAGS bits + */ +#define X86_EFLAGS_CF 0x00000001 /* Carry Flag */ +#define X86_EFLAGS_PF 0x00000004 /* Parity Flag */ +#define X86_EFLAGS_AF 0x00000010 /* Auxillary carry Flag */ +#define X86_EFLAGS_ZF 0x00000040 /* Zero Flag */ +#define X86_EFLAGS_SF 0x00000080 /* Sign Flag */ +#define X86_EFLAGS_TF 0x00000100 /* Trap Flag */ +#define X86_EFLAGS_IF 0x00000200 /* Interrupt Flag */ +#define X86_EFLAGS_DF 0x00000400 /* Direction Flag */ +#define X86_EFLAGS_OF 0x00000800 /* Overflow Flag */ +#define X86_EFLAGS_IOPL 0x00003000 /* IOPL mask */ +#define X86_EFLAGS_NT 0x00004000 /* Nested Task */ +#define X86_EFLAGS_RF 0x00010000 /* Resume Flag */ +#define X86_EFLAGS_VM 0x00020000 /* Virtual Mode */ +#define X86_EFLAGS_AC 0x00040000 /* Alignment Check */ +#define X86_EFLAGS_VIF 0x00080000 /* Virtual Interrupt Flag */ +#define X86_EFLAGS_VIP 0x00100000 /* Virtual Interrupt Pending */ +#define X86_EFLAGS_ID 0x00200000 /* CPUID detection flag */ + +/* + * Generic CPUID function + */ +static inline __attribute__ (( always_inline )) void +cpuid ( int op, unsigned int *eax, unsigned int *ebx, + unsigned int *ecx, unsigned int *edx ) { + __asm__ ( "cpuid" : + "=a" ( *eax ), "=b" ( *ebx ), "=c" ( *ecx ), "=d" ( *edx ) + : "0" ( op ) ); +} + +extern void get_cpuinfo ( struct cpuinfo_x86 *cpu ); + +#endif /* I386_BITS_CPU_H */ diff --git a/gpxe/src/arch/i386/include/bits/elf.h b/gpxe/src/arch/i386/include/bits/elf.h new file mode 100644 index 00000000..dad9c7b8 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/elf.h @@ -0,0 +1,91 @@ +#ifndef I386_BITS_ELF_H +#define I386_BITS_ELF_H + +#include "cpu.h" + +#ifdef CONFIG_X86_64 +/* ELF Defines for the 64bit version of the current architecture */ +#define EM_CURRENT_64 EM_X86_64 +#define EM_CURRENT_64_PRESENT ( \ + CPU_FEATURE_P(cpu_info.x86_capability, LM) && \ + CPU_FEATURE_P(cpu_info.x86_capability, PAE) && \ + CPU_FEATURE_P(cpu_info.x86_capability, PSE)) + +#define ELF_CHECK_X86_64_ARCH(x) \ + (EM_CURRENT_64_PRESENT && ((x).e_machine == EM_X86_64)) +#define __unused_i386 +#else +#define ELF_CHECK_X86_64_ARCH(x) 0 +#define __unused_i386 __unused +#endif + + +/* ELF Defines for the current architecture */ +#define EM_CURRENT EM_386 +#define ELFDATA_CURRENT ELFDATA2LSB + +#define ELF_CHECK_I386_ARCH(x) \ + (((x).e_machine == EM_386) || ((x).e_machine == EM_486)) + +#define ELF_CHECK_ARCH(x) \ + ((ELF_CHECK_I386_ARCH(x) || ELF_CHECK_X86_64_ARCH(x)) && \ + ((x).e_entry <= 0xffffffffUL)) + +#ifdef IMAGE_FREEBSD +/* + * FreeBSD has this rather strange "feature" of its design. + * At some point in its evolution, FreeBSD started to rely + * externally on private/static/debug internal symbol information. + * That is, some of the interfaces that software uses to access + * and work with the FreeBSD kernel are made available not + * via the shared library symbol information (the .DYNAMIC section) + * but rather the debug symbols. This means that any symbol, not + * just publicly defined symbols can be (and are) used by system + * tools to make the system work. (such as top, swapinfo, swapon, + * etc) + * + * Even worse, however, is the fact that standard ELF loaders do + * not know how to load the symbols since they are not within + * an ELF PT_LOAD section. The kernel needs these symbols to + * operate so the following changes/additions to the boot + * loading of EtherBoot have been made to get the kernel to load. + * All of the changes are within IMAGE_FREEBSD such that the + * extra/changed code only compiles when FREEBSD support is + * enabled. + */ + +/* + * Section header for FreeBSD (debug symbol kludge!) support + */ +typedef struct { + Elf32_Word sh_name; /* Section name (index into the + section header string table). */ + Elf32_Word sh_type; /* Section type. */ + Elf32_Word sh_flags; /* Section flags. */ + Elf32_Addr sh_addr; /* Address in memory image. */ + Elf32_Off sh_offset; /* Offset in file. */ + Elf32_Size sh_size; /* Size in bytes. */ + Elf32_Word sh_link; /* Index of a related section. */ + Elf32_Word sh_info; /* Depends on section type. */ + Elf32_Size sh_addralign; /* Alignment in bytes. */ + Elf32_Size sh_entsize; /* Size of each entry in section. */ +} Elf32_Shdr; + +/* sh_type */ +#define SHT_SYMTAB 2 /* symbol table section */ +#define SHT_STRTAB 3 /* string table section */ + +/* + * Module information subtypes (for the metadata that we need to build) + */ +#define MODINFO_END 0x0000 /* End of list */ +#define MODINFO_NAME 0x0001 /* Name of module (string) */ +#define MODINFO_TYPE 0x0002 /* Type of module (string) */ +#define MODINFO_METADATA 0x8000 /* Module-specfic */ + +#define MODINFOMD_SSYM 0x0003 /* start of symbols */ +#define MODINFOMD_ESYM 0x0004 /* end of symbols */ + +#endif /* IMAGE_FREEBSD */ + +#endif /* I386_BITS_ELF_H */ diff --git a/gpxe/src/arch/i386/include/bits/elf_x.h b/gpxe/src/arch/i386/include/bits/elf_x.h new file mode 100644 index 00000000..86c67250 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/elf_x.h @@ -0,0 +1,5 @@ +#define ARCH_ELF_CLASS ELFCLASS32 +#define ARCH_ELF_DATA ELFDATA2LSB +#define ARCH_ELF_MACHINE_OK(x) ((x)==EM_386 || (x)==EM_486) +typedef Elf32_Ehdr Elf_ehdr; +typedef Elf32_Phdr Elf_phdr; diff --git a/gpxe/src/arch/i386/include/bits/eltorito.h b/gpxe/src/arch/i386/include/bits/eltorito.h new file mode 100644 index 00000000..d43e9aac --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/eltorito.h @@ -0,0 +1,3 @@ +#ifndef ELTORITO_PLATFORM +#define ELTORITO_PLATFORM ELTORITO_PLATFORM_X86 +#endif /* ELTORITO_PLATFORM */ diff --git a/gpxe/src/arch/i386/include/bits/endian.h b/gpxe/src/arch/i386/include/bits/endian.h new file mode 100644 index 00000000..b23b233a --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/endian.h @@ -0,0 +1,9 @@ +#ifndef ETHERBOOT_BITS_ENDIAN_H +#define ETHERBOOT_BITS_ENDIAN_H + +#define __BYTE_ORDER __LITTLE_ENDIAN + +#define le32_to_cpup(x) (*(uint32_t *)(x)) +#define cpu_to_le16p(x) (*(uint16_t*)(x)) + +#endif /* ETHERBOOT_BITS_ENDIAN_H */ diff --git a/gpxe/src/arch/i386/include/bits/errfile.h b/gpxe/src/arch/i386/include/bits/errfile.h new file mode 100644 index 00000000..0f140214 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/errfile.h @@ -0,0 +1,34 @@ +#ifndef _BITS_ERRFILE_H +#define _BITS_ERRFILE_H + +/** + * @addtogroup errfile Error file identifiers + * @{ + */ + +#define ERRFILE_umalloc ( ERRFILE_ARCH | ERRFILE_CORE | 0x00000000 ) +#define ERRFILE_memmap ( ERRFILE_ARCH | ERRFILE_CORE | 0x00010000 ) +#define ERRFILE_pnpbios ( ERRFILE_ARCH | ERRFILE_CORE | 0x00020000 ) +#define ERRFILE_smbios ( ERRFILE_ARCH | ERRFILE_CORE | 0x00030000 ) +#define ERRFILE_biosint ( ERRFILE_ARCH | ERRFILE_CORE | 0x00040000 ) +#define ERRFILE_int13 ( ERRFILE_ARCH | ERRFILE_CORE | 0x00050000 ) + +#define ERRFILE_bootsector ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00000000 ) +#define ERRFILE_bzimage ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00010000 ) +#define ERRFILE_eltorito ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00020000 ) +#define ERRFILE_multiboot ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00030000 ) +#define ERRFILE_nbi ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00040000 ) +#define ERRFILE_pxe_image ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00050000 ) + +#define ERRFILE_undi ( ERRFILE_ARCH | ERRFILE_NET | 0x00000000 ) +#define ERRFILE_undiload ( ERRFILE_ARCH | ERRFILE_NET | 0x00010000 ) +#define ERRFILE_undinet ( ERRFILE_ARCH | ERRFILE_NET | 0x00020000 ) +#define ERRFILE_undionly ( ERRFILE_ARCH | ERRFILE_NET | 0x00030000 ) +#define ERRFILE_undirom ( ERRFILE_ARCH | ERRFILE_NET | 0x00040000 ) + +#define ERRFILE_timer_rdtsc ( ERRFILE_ARCH | ERRFILE_DRIVER | 0x00000000 ) +#define ERRFILE_timer_bios ( ERRFILE_ARCH | ERRFILE_DRIVER | 0x00010000 ) + +/** @} */ + +#endif /* _BITS_ERRFILE_H */ diff --git a/gpxe/src/arch/i386/include/bits/stdint.h b/gpxe/src/arch/i386/include/bits/stdint.h new file mode 100644 index 00000000..a2947cda --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/stdint.h @@ -0,0 +1,21 @@ +#ifndef _BITS_STDINT_H +#define _BITS_STDINT_H + +typedef typeof(sizeof(int)) size_t; +typedef signed long ssize_t; +typedef signed long off_t; + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned long uint32_t; +typedef unsigned long long uint64_t; + +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed long int32_t; +typedef signed long long int64_t; + +typedef unsigned long physaddr_t; +typedef unsigned long intptr_t; + +#endif /* _BITS_STDINT_H */ diff --git a/gpxe/src/arch/i386/include/bits/string.h b/gpxe/src/arch/i386/include/bits/string.h new file mode 100644 index 00000000..c05a7df8 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/string.h @@ -0,0 +1,252 @@ +#ifndef ETHERBOOT_BITS_STRING_H +#define ETHERBOOT_BITS_STRING_H +/* + * Taken from Linux /usr/include/asm/string.h + * All except memcpy, memmove, memset and memcmp removed. + * + * Non-standard memswap() function added because it saves quite a bit + * of code (mbrown@fensystems.co.uk). + */ + +/* + * This string-include defines all string functions as inline + * functions. Use gcc. It also assumes ds=es=data space, this should be + * normal. Most of the string-functions are rather heavily hand-optimized, + * see especially strtok,strstr,str[c]spn. They should work, but are not + * very easy to understand. Everything is done entirely within the register + * set, making the functions fast and clean. String instructions have been + * used through-out, making for "slightly" unclear code :-) + * + * NO Copyright (C) 1991, 1992 Linus Torvalds, + * consider these trivial functions to be PD. + */ + +#define __HAVE_ARCH_MEMCPY + +extern __attribute__ (( regparm ( 3 ) )) void * __memcpy ( void *dest, + const void *src, + size_t len ); + +#if 0 +static inline __attribute__ (( always_inline )) void * +__memcpy ( void *dest, const void *src, size_t len ) { + int d0, d1, d2; + __asm__ __volatile__ ( "rep ; movsb" + : "=&c" ( d0 ), "=&S" ( d1 ), "=&D" ( d2 ) + : "0" ( len ), "1" ( src ), "2" ( dest ) + : "memory" ); + return dest; +} +#endif + +static inline __attribute__ (( always_inline )) void * +__constant_memcpy ( void *dest, const void *src, size_t len ) { + union { + uint32_t u32[2]; + uint16_t u16[4]; + uint8_t u8[8]; + } __attribute__ (( __may_alias__ )) *dest_u = dest; + const union { + uint32_t u32[2]; + uint16_t u16[4]; + uint8_t u8[8]; + } __attribute__ (( __may_alias__ )) *src_u = src; + const void *esi; + void *edi; + + switch ( len ) { + case 0 : /* 0 bytes */ + return dest; + /* + * Single-register moves; these are always better than a + * string operation. We can clobber an arbitrary two + * registers (data, source, dest can re-use source register) + * instead of being restricted to esi and edi. There's also a + * much greater potential for optimising with nearby code. + * + */ + case 1 : /* 4 bytes */ + dest_u->u8[0] = src_u->u8[0]; + return dest; + case 2 : /* 6 bytes */ + dest_u->u16[0] = src_u->u16[0]; + return dest; + case 4 : /* 4 bytes */ + dest_u->u32[0] = src_u->u32[0]; + return dest; + /* + * Double-register moves; these are probably still a win. + * + */ + case 3 : /* 12 bytes */ + dest_u->u16[0] = src_u->u16[0]; + dest_u->u8[2] = src_u->u8[2]; + return dest; + case 5 : /* 10 bytes */ + dest_u->u32[0] = src_u->u32[0]; + dest_u->u8[4] = src_u->u8[4]; + return dest; + case 6 : /* 12 bytes */ + dest_u->u32[0] = src_u->u32[0]; + dest_u->u16[2] = src_u->u16[2]; + return dest; + case 8 : /* 10 bytes */ + dest_u->u32[0] = src_u->u32[0]; + dest_u->u32[1] = src_u->u32[1]; + return dest; + } + + /* Even if we have to load up esi and edi ready for a string + * operation, we can sometimes save space by using multiple + * single-byte "movs" operations instead of loading up ecx and + * using "rep movsb". + * + * "load ecx, rep movsb" is 7 bytes, plus an average of 1 byte + * to allow for saving/restoring ecx 50% of the time. + * + * "movsl" and "movsb" are 1 byte each, "movsw" is two bytes. + * (In 16-bit mode, "movsl" is 2 bytes and "movsw" is 1 byte, + * but "movsl" moves twice as much data, so it balances out). + * + * The cutoff point therefore occurs around 26 bytes; the byte + * requirements for each method are: + * + * len 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 + * #bytes (ecx) 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 + * #bytes (no ecx) 4 5 6 7 5 6 7 8 6 7 8 9 7 8 9 10 + */ + + esi = src; + edi = dest; + + if ( len >= 26 ) + return __memcpy ( dest, src, len ); + + if ( len >= 6*4 ) + __asm__ __volatile__ ( "movsl" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( len >= 5*4 ) + __asm__ __volatile__ ( "movsl" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( len >= 4*4 ) + __asm__ __volatile__ ( "movsl" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( len >= 3*4 ) + __asm__ __volatile__ ( "movsl" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( len >= 2*4 ) + __asm__ __volatile__ ( "movsl" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( len >= 1*4 ) + __asm__ __volatile__ ( "movsl" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( ( len % 4 ) >= 2 ) + __asm__ __volatile__ ( "movsw" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + if ( ( len % 2 ) >= 1 ) + __asm__ __volatile__ ( "movsb" : "=&D" ( edi ), "=&S" ( esi ) + : "0" ( edi ), "1" ( esi ) : "memory" ); + + return dest; +} + +#define memcpy( dest, src, len ) \ + ( __builtin_constant_p ( (len) ) ? \ + __constant_memcpy ( (dest), (src), (len) ) : \ + __memcpy ( (dest), (src), (len) ) ) + +#define __HAVE_ARCH_MEMMOVE +static inline void * memmove(void * dest,const void * src, size_t n) +{ +int d0, d1, d2; +if (dest<src) +__asm__ __volatile__( + "cld\n\t" + "rep\n\t" + "movsb" + : "=&c" (d0), "=&S" (d1), "=&D" (d2) + :"0" (n),"1" (src),"2" (dest) + : "memory"); +else +__asm__ __volatile__( + "std\n\t" + "rep\n\t" + "movsb\n\t" + "cld" + : "=&c" (d0), "=&S" (d1), "=&D" (d2) + :"0" (n), + "1" (n-1+(const char *)src), + "2" (n-1+(char *)dest) + :"memory"); +return dest; +} + +#define __HAVE_ARCH_MEMSET +static inline void * memset(void *s, int c,size_t count) +{ +int d0, d1; +__asm__ __volatile__( + "cld\n\t" + "rep\n\t" + "stosb" + : "=&c" (d0), "=&D" (d1) + :"a" (c),"1" (s),"0" (count) + :"memory"); +return s; +} + +#define __HAVE_ARCH_MEMSWAP +static inline void * memswap(void *dest, void *src, size_t n) +{ +int d0, d1, d2, d3; +__asm__ __volatile__( + "\n1:\t" + "movb (%%edi),%%al\n\t" + "xchgb (%%esi),%%al\n\t" + "incl %%esi\n\t" + "stosb\n\t" + "loop 1b" + : "=&c" (d0), "=&S" (d1), "=&D" (d2), "=&a" (d3) + : "0" (n), "1" (src), "2" (dest) + : "memory" ); +return dest; +} + +#define __HAVE_ARCH_STRNCMP +static inline int strncmp(const char * cs,const char * ct,size_t count) +{ +register int __res; +int d0, d1, d2; +__asm__ __volatile__( + "1:\tdecl %3\n\t" + "js 2f\n\t" + "lodsb\n\t" + "scasb\n\t" + "jne 3f\n\t" + "testb %%al,%%al\n\t" + "jne 1b\n" + "2:\txorl %%eax,%%eax\n\t" + "jmp 4f\n" + "3:\tsbbl %%eax,%%eax\n\t" + "orb $1,%%al\n" + "4:" + :"=a" (__res), "=&S" (d0), "=&D" (d1), "=&c" (d2) + :"1" (cs),"2" (ct),"3" (count)); +return __res; +} + +#define __HAVE_ARCH_STRLEN +static inline size_t strlen(const char * s) +{ +int d0; +register int __res; +__asm__ __volatile__( + "repne\n\t" + "scasb\n\t" + "notl %0\n\t" + "decl %0" + :"=c" (__res), "=&D" (d0) :"1" (s),"a" (0), "0" (0xffffffff)); +return __res; +} + +#endif /* ETHERBOOT_BITS_STRING_H */ diff --git a/gpxe/src/arch/i386/include/bits/timer2.h b/gpxe/src/arch/i386/include/bits/timer2.h new file mode 100644 index 00000000..83923b29 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/timer2.h @@ -0,0 +1,8 @@ +#ifndef BITS_TIMER2_H +#define BITS_TIMER2_H + +#include <stddef.h> + +void i386_timer2_udelay(unsigned int usecs); + +#endif diff --git a/gpxe/src/arch/i386/include/bits/uaccess.h b/gpxe/src/arch/i386/include/bits/uaccess.h new file mode 100644 index 00000000..9c6d0c21 --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/uaccess.h @@ -0,0 +1,6 @@ +#ifndef _BITS_UACCESS_H +#define _BITS_UACCESS_H + +#include <realmode.h> + +#endif /* _BITS_UACCESS_H */ diff --git a/gpxe/src/arch/i386/include/bits/uuid.h b/gpxe/src/arch/i386/include/bits/uuid.h new file mode 100644 index 00000000..0cbd320a --- /dev/null +++ b/gpxe/src/arch/i386/include/bits/uuid.h @@ -0,0 +1,10 @@ +#ifndef _I386_UUID_H +#define _I386_UUID_H + +#include <smbios.h> + +static inline int get_uuid ( union uuid *uuid ) { + return smbios_get_uuid ( uuid ); +} + +#endif /* _I386_UUID_H */ diff --git a/gpxe/src/arch/i386/include/bochs.h b/gpxe/src/arch/i386/include/bochs.h new file mode 100644 index 00000000..9d090fc1 --- /dev/null +++ b/gpxe/src/arch/i386/include/bochs.h @@ -0,0 +1,34 @@ +#ifndef BOCHS_H +#define BOCHS_H + +/** @file + * + * bochs breakpoints + * + * This file defines @c bochsbp, the magic breakpoint instruction that + * is incredibly useful when debugging under bochs. This file should + * never be included in production code. + * + * Use the pseudo-instruction @c bochsbp in assembly code, or the + * bochsbp() function in C code. + * + */ + +#ifdef ASSEMBLY + +/* Breakpoint for when debugging under bochs */ +#define bochsbp xchgw %bx, %bx +#define BOCHSBP bochsbp + +#else /* ASSEMBLY */ + +/** Breakpoint for when debugging under bochs */ +static inline void bochsbp ( void ) { + __asm__ __volatile__ ( "xchgw %bx, %bx" ); +} + +#endif /* ASSEMBLY */ + +#warning "bochs.h should not be included into production code" + +#endif /* BOCHS_H */ diff --git a/gpxe/src/arch/i386/include/bootsector.h b/gpxe/src/arch/i386/include/bootsector.h new file mode 100644 index 00000000..e9071052 --- /dev/null +++ b/gpxe/src/arch/i386/include/bootsector.h @@ -0,0 +1,12 @@ +#ifndef _BOOTSECTOR_H +#define _BOOTSECTOR_H + +/** @file + * + * x86 bootsector image format + */ + +extern int call_bootsector ( unsigned int segment, unsigned int offset, + unsigned int drive ); + +#endif /* _BOOTSECTOR_H */ diff --git a/gpxe/src/arch/i386/include/bzimage.h b/gpxe/src/arch/i386/include/bzimage.h new file mode 100644 index 00000000..609e8362 --- /dev/null +++ b/gpxe/src/arch/i386/include/bzimage.h @@ -0,0 +1,129 @@ +#ifndef _BZIMAGE_H +#define _BZIMAGE_H + +#include <stdint.h> + +/** + * A bzImage header + * + * As documented in Documentation/i386/boot.txt + */ +struct bzimage_header { + /** The size of the setup in sectors + * + * If this field contains 0, assume it contains 4. + */ + uint8_t setup_sects; + /** If set, the root is mounted readonly */ + uint16_t root_flags; + /** DO NOT USE - for bootsect.S use only */ + uint16_t syssize; + /** DO NOT USE - obsolete */ + uint16_t swap_dev; + /** DO NOT USE - for bootsect.S use only */ + uint16_t ram_size; + /** Video mode control */ + uint16_t vid_mode; + /** Default root device number */ + uint16_t root_dev; + /** 0xAA55 magic number */ + uint16_t boot_flag; + /** Jump instruction */ + uint16_t jump; + /** Magic signature "HdrS" */ + uint32_t header; + /** Boot protocol version supported */ + uint16_t version; + /** Boot loader hook (see below) */ + uint32_t realmode_swtch; + /** The load-low segment (0x1000) (obsolete) */ + uint16_t start_sys; + /** Pointer to kernel version string */ + uint16_t kernel_version; + /** Boot loader identifier */ + uint8_t type_of_loader; + /** Boot protocol option flags */ + uint8_t loadflags; + /** Move to high memory size (used with hooks) */ + uint16_t setup_move_size; + /** Boot loader hook (see below) */ + uint32_t code32_start; + /** initrd load address (set by boot loader) */ + uint32_t ramdisk_image; + /** initrd size (set by boot loader) */ + uint32_t ramdisk_size; + /** DO NOT USE - for bootsect.S use only */ + uint32_t bootsect_kludge; + /** Free memory after setup end */ + uint16_t heap_end_ptr; + /** Unused */ + uint16_t pad1; + /** 32-bit pointer to the kernel command line */ + uint32_t cmd_line_ptr; + /** Highest legal initrd address */ + uint32_t initrd_addr_max; +} __attribute__ (( packed )); + +/** Offset of bzImage header within kernel image */ +#define BZI_HDR_OFFSET 0x1f1 + +/** bzImage magic signature value */ +#define BZI_SIGNATURE 0x53726448 + +/** bzImage boot loader identifier for Etherboot */ +#define BZI_LOADER_TYPE_ETHERBOOT 0x40 + +/** bzImage boot loader identifier for gPXE + * + * We advertise ourselves as Etherboot version 6. + */ +#define BZI_LOADER_TYPE_GPXE ( BZI_LOADER_TYPE_ETHERBOOT | 0x06 ) + +/** bzImage "load high" flag */ +#define BZI_LOAD_HIGH 0x01 + +/** Load address for high-loaded kernels */ +#define BZI_LOAD_HIGH_ADDR 0x100000 + +/** Load address for low-loaded kernels */ +#define BZI_LOAD_LOW_ADDR 0x10000 + +/** bzImage "kernel can use heap" flag */ +#define BZI_CAN_USE_HEAP 0x80 + +/** bzImage special video mode "normal" */ +#define BZI_VID_MODE_NORMAL 0xffff + +/** bzImage special video mode "ext" */ +#define BZI_VID_MODE_EXT 0xfffe + +/** bzImage special video mode "ask" */ +#define BZI_VID_MODE_ASK 0xfffd + +/** bzImage maximum initrd address for versions < 2.03 */ +#define BZI_INITRD_MAX 0x37ffffff + +/** bzImage command-line structure used by older kernels */ +struct bzimage_cmdline { + /** Magic signature */ + uint16_t magic; + /** Offset to command line */ + uint16_t offset; +} __attribute__ (( packed )); + +/** Offset of bzImage command-line structure within kernel image */ +#define BZI_CMDLINE_OFFSET 0x20 + +/** bzImage command line present magic marker value */ +#define BZI_CMDLINE_MAGIC 0xa33f + +/** Assumed size of real-mode portion (including .bss) */ +#define BZI_ASSUMED_RM_SIZE 0x8000 + +/** Amount of stack space to provide */ +#define BZI_STACK_SIZE 0x1000 + +/** Maximum size of command line */ +#define BZI_CMDLINE_SIZE 0x100 + +#endif /* _BZIMAGE_H */ diff --git a/gpxe/src/arch/i386/include/callbacks_arch.h b/gpxe/src/arch/i386/include/callbacks_arch.h new file mode 100644 index 00000000..f9cba488 --- /dev/null +++ b/gpxe/src/arch/i386/include/callbacks_arch.h @@ -0,0 +1,243 @@ +/* Callout/callback interface for Etherboot + * + * This file provides the mechanisms for making calls from Etherboot + * to external programs and vice-versa. + * + * Initial version by Michael Brown <mbrown@fensystems.co.uk>, January 2004. + * + * $Id$ + */ + +#ifndef CALLBACKS_ARCH_H +#define CALLBACKS_ARCH_H + +/* Skip the definitions that won't make sense to the assembler */ +#ifndef ASSEMBLY + +/* Struct to hold general-purpose register values. PUSHAL and POPAL + * can work directly with this structure; do not change the order of + * registers. + */ +typedef struct { + union { + uint16_t di; + uint32_t edi; + }; + union { + uint16_t si; + uint32_t esi; + }; + union { + uint16_t bp; + uint32_t ebp; + }; + union { + uint16_t sp; + uint32_t esp; + }; + union { + struct { + uint8_t bl; + uint8_t bh; + } PACKED; + uint16_t bx; + uint32_t ebx; + }; + union { + struct { + uint8_t dl; + uint8_t dh; + } PACKED; + uint16_t dx; + uint32_t edx; + }; + union { + struct { + uint8_t cl; + uint8_t ch; + } PACKED; + uint16_t cx; + uint32_t ecx; + }; + union { + struct { + uint8_t al; + uint8_t ah; + } PACKED; + uint16_t ax; + uint32_t eax; + }; +} regs_t; + +/* Struct to hold segment register values. Don't change the order; + * many bits of assembly code rely on it. + */ +typedef struct { + uint16_t cs; + uint16_t ss; + uint16_t ds; + uint16_t es; + uint16_t fs; + uint16_t gs; +} PACKED seg_regs_t; + +/* Struct for a GDT descriptor */ +typedef struct { + uint16_t limit; + uint32_t address; + uint16_t padding; +} PACKED gdt_descriptor_t; + +/* Struct for a GDT entry. Use GDT_SEGMENT() to fill it in. + */ +typedef struct { + uint16_t limit_0_15; + uint16_t base_0_15; + uint8_t base_16_23; + uint8_t accessed__type__sflag__dpl__present; + uint8_t limit_16_19__avl__size__granularity; + uint8_t base_24_31; +} PACKED gdt_segment_t; + +#define GDT_SEGMENT(base,limit,type,sflag,dpl,avl,size,granularity) \ + ( (gdt_segment_t) { \ + ( (limit) & 0xffff ), \ + ( (base) & 0xffff ), \ + ( ( (base) >> 16 ) & 0xff ), \ + ( ( 1 << 0 ) | ( (type) << 1 ) | \ + ( (sflag) << 4 ) | ( (dpl) << 5 ) | ( 1 << 7 ) ), \ + ( ( (limit) >> 16 ) | \ + ( (avl) << 4 ) | ( (size) << 5 ) | ( (granularity) << 7 ) ),\ + ( (base) >> 24 ) \ + } ) +#define GDT_SEGMENT_BASE(gdt_segment) \ + ( (gdt_segment)->base_0_15 | \ + (gdt_segment)->base_16_23 << 16 | \ + (gdt_segment)->base_24_31 << 24 ) +#define GDT_SEGMENT_LIMIT(gdt_segment) \ + ( (gdt_segment)->limit_0_15 | \ + ( ( (gdt_segment)->limit_16_19__avl__size__granularity \ + & 0xf ) << 16 ) ) +#define GDT_SEGMENT_GRANULARITY(gdt_segment) \ + ( ( (gdt_segment)->limit_16_19__avl__size__granularity \ + & 0x80 ) >> 7 ) +#define GDT_SEGMENT_TYPE(gdt_segment) \ + ( ( (gdt_segment)->accessed__type__sflag__dpl__present & 0x0e ) >> 1 ) +#define GDT_SEGMENT_SIZE(gdt_segment) \ + ( ( (gdt_segment)->limit_16_19__avl__size__granularity \ + & 0x60 ) >> 5 ) + +#define GDT_TYPE_DATA (0x0) +#define GDT_TYPE_STACK (0x2) +#define GDT_TYPE_WRITEABLE (0x1) +#define GDT_TYPE_CODE (0x6) +#define GDT_TYPE_EXEC_ONLY_CODE (0x4) +#define GDT_TYPE_CONFORMING (0x1) +#define GDT_SFLAG_SYSTEM (0) +#define GDT_SFLAG_NORMAL (1) +#define GDT_AVL_NORMAL (0) +#define GDT_SIZE_16BIT (0x0) +#define GDT_SIZE_32BIT (0x2) +#define GDT_SIZE_64BIT (0x1) +#define GDT_SIZE_UNKNOWN (0x3) +#define GDT_GRANULARITY_SMALL (0) +#define GDT_GRANULARITY_LARGE (1) +#define GDT_SEGMENT_NORMAL(base,limit,type,size,granularity) \ + GDT_SEGMENT ( base, limit, type, \ + GDT_SFLAG_NORMAL, 0, GDT_AVL_NORMAL, \ + size, granularity ) + +/* Protected mode code segment */ +#define GDT_SEGMENT_PMCS(base) GDT_SEGMENT_NORMAL ( \ + base, 0xfffff, GDT_TYPE_CODE | GDT_TYPE_CONFORMING, \ + GDT_SIZE_32BIT, GDT_GRANULARITY_LARGE ) +#define GDT_SEGMENT_PMCS_PHYS GDT_SEGMENT_PMCS(0) +/* Protected mode data segment */ +#define GDT_SEGMENT_PMDS(base) GDT_SEGMENT_NORMAL ( \ + base, 0xfffff, GDT_TYPE_DATA | GDT_TYPE_WRITEABLE, \ + GDT_SIZE_32BIT, GDT_GRANULARITY_LARGE ) +#define GDT_SEGMENT_PMDS_PHYS GDT_SEGMENT_PMDS(0) +/* Real mode code segment */ +/* Not sure if there's any reason to use GDT_TYPE_EXEC_ONLY_CODE + * instead of just GDT_TYPE_CODE, but that's what our old GDT did and + * it worked, so I'm not changing it. + */ +#define GDT_SEGMENT_RMCS(base) GDT_SEGMENT_NORMAL ( \ + base, 0xffff, GDT_TYPE_EXEC_ONLY_CODE | GDT_TYPE_CONFORMING, \ + GDT_SIZE_16BIT, GDT_GRANULARITY_SMALL ) +/* Real mode data segment */ +#define GDT_SEGMENT_RMDS(base) GDT_SEGMENT_NORMAL ( \ + base, 0xffff, GDT_TYPE_DATA | GDT_TYPE_WRITEABLE, \ + GDT_SIZE_16BIT, GDT_GRANULARITY_SMALL ) +/* Long mode code segment */ +#define GDT_SEGMENT_LMCS(base) GDT_SEGMENT_NORMAL ( \ + base, 0xfffff, GDT_TYPE_CODE | GDT_TYPE_CONFORMING, \ + GDT_SIZE_64BIT, GDT_GRANULARITY_LARGE ) +#define GDT_SEGMENT_LMCS_PHYS GDT_SEGMENT_LMCS(0) +/* Long mode data segment */ +/* AFIACT, GDT_SIZE_64BIT applies only to code segments */ +#define GDT_SEGMENT_LMDS(base) GDT_SEGMENT_NORMAL ( \ + base, 0xfffff, GDT_TYPE_DATA | GDT_TYPE_WRITEABLE, \ + GDT_SIZE_32BIT, GDT_GRANULARITY_LARGE ) +#define GDT_SEGMENT_LMDS_PHYS GDT_SEGMENT_LMDS(0) + +/* Template for creating GDT structures (including segment register + * lists), suitable for passing as parameters to external_call(). + */ +#define GDT_STRUCT_t(num_segments) \ + struct { \ + gdt_descriptor_t descriptor; \ + gdt_segment_t segments[num_segments]; \ + } PACKED +/* And utility function for filling it in */ +#define GDT_ADJUST(structure) { \ + (structure)->descriptor.address = \ + virt_to_phys(&((structure)->descriptor.limit)); \ + (structure)->descriptor.limit = \ + sizeof((structure)->segments) + 8 - 1; \ + (structure)->descriptor.padding = 0; \ +} + +/* Data passed in to in_call() by assembly wrapper. + */ +typedef struct { + regs_t regs; + seg_regs_t seg_regs; + gdt_descriptor_t gdt_desc; + uint32_t flags; + struct { + uint32_t offset; + uint32_t segment; + } ret_addr; +} PACKED i386_pm_in_call_data_t; + +typedef struct { + seg_regs_t seg_regs; + union { + uint16_t pad; + uint16_t prefix_sp; + }; + uint16_t flags; + struct { + uint16_t offset; + uint16_t segment; + } ret_addr; + uint32_t orig_opcode; +} PACKED i386_rm_in_call_data_t; + +typedef struct { + i386_pm_in_call_data_t *pm; + i386_rm_in_call_data_t *rm; +} i386_in_call_data_t; +#define in_call_data_t i386_in_call_data_t + +/* Function prototypes + */ +extern int install_rm_callback_interface ( void *address, size_t available ); + +#endif /* ASSEMBLY */ + +#define RM_IN_CALL (0) +#define RM_IN_CALL_FAR (2) + +#endif /* CALLBACKS_ARCH_H */ diff --git a/gpxe/src/arch/i386/include/gateA20.h b/gpxe/src/arch/i386/include/gateA20.h new file mode 100644 index 00000000..297ad6f2 --- /dev/null +++ b/gpxe/src/arch/i386/include/gateA20.h @@ -0,0 +1,7 @@ +#ifndef GATEA20_H +#define GATEA20_H + +extern void gateA20_set ( void ); +extern void gateA20_unset ( void ); + +#endif /* GATEA20_H */ diff --git a/gpxe/src/arch/i386/include/int13.h b/gpxe/src/arch/i386/include/int13.h new file mode 100644 index 00000000..2a193831 --- /dev/null +++ b/gpxe/src/arch/i386/include/int13.h @@ -0,0 +1,277 @@ +#ifndef INT13_H +#define INT13_H + +/** @file + * + * INT 13 emulation + * + */ + +#include <stdint.h> +#include <gpxe/list.h> + +struct block_device; + +/** + * @defgroup int13ops INT 13 operation codes + * @{ + */ + +/** Reset disk system */ +#define INT13_RESET 0x00 +/** Get status of last operation */ +#define INT13_GET_LAST_STATUS 0x01 +/** Read sectors */ +#define INT13_READ_SECTORS 0x02 +/** Write sectors */ +#define INT13_WRITE_SECTORS 0x03 +/** Get drive parameters */ +#define INT13_GET_PARAMETERS 0x08 +/** Get disk type */ +#define INT13_GET_DISK_TYPE 0x15 +/** Extensions installation check */ +#define INT13_EXTENSION_CHECK 0x41 +/** Extended read */ +#define INT13_EXTENDED_READ 0x42 +/** Extended write */ +#define INT13_EXTENDED_WRITE 0x43 +/** Get extended drive parameters */ +#define INT13_GET_EXTENDED_PARAMETERS 0x48 +/** Get CD-ROM status / terminate emulation */ +#define INT13_CDROM_STATUS_TERMINATE 0x4b + +/** @} */ + +/** + * @defgroup int13status INT 13 status codes + * @{ + */ + +/** Operation completed successfully */ +#define INT13_STATUS_SUCCESS 0x00 +/** Invalid function or parameter */ +#define INT13_STATUS_INVALID 0x01 +/** Read error */ +#define INT13_STATUS_READ_ERROR 0x04 +/** Write error */ +#define INT13_STATUS_WRITE_ERROR 0xcc + +/** @} */ + +/** Block size for non-extended INT 13 calls */ +#define INT13_BLKSIZE 512 + +/** An INT 13 emulated drive */ +struct int13_drive { + /** List of all registered drives */ + struct list_head list; + + /** Underlying block device */ + struct block_device *blockdev; + + /** BIOS drive number (0x80-0xff) */ + unsigned int drive; + /** Number of cylinders + * + * The cylinder number field in an INT 13 call is ten bits + * wide, giving a maximum of 1024 cylinders. Conventionally, + * when the 7.8GB limit of a CHS address is exceeded, it is + * the number of cylinders that is increased beyond the + * addressable limit. + */ + unsigned int cylinders; + /** Number of heads + * + * The head number field in an INT 13 call is eight bits wide, + * giving a maximum of 256 heads. However, apparently all + * versions of MS-DOS up to and including Win95 fail with 256 + * heads, so the maximum encountered in practice is 255. + */ + unsigned int heads; + /** Number of sectors per track + * + * The sector number field in an INT 13 call is six bits wide, + * giving a maximum of 63 sectors, since sector numbering + * (unlike head and cylinder numbering) starts at 1, not 0. + */ + unsigned int sectors_per_track; + + /** Status of last operation */ + int last_status; +}; + +/** An INT 13 disk address packet */ +struct int13_disk_address { + /** Size of the packet, in bytes */ + uint8_t bufsize; + /** Reserved, must be zero */ + uint8_t reserved; + /** Block count */ + uint16_t count; + /** Data buffer */ + struct segoff buffer; + /** Starting block number */ + uint64_t lba; + /** Data buffer (EDD-3.0 only) */ + uint64_t buffer_phys; +} __attribute__ (( packed )); + +/** INT 13 disk parameters */ +struct int13_disk_parameters { + /** Size of this structure */ + uint16_t bufsize; + /** Flags */ + uint16_t flags; + /** Number of cylinders */ + uint32_t cylinders; + /** Number of heads */ + uint32_t heads; + /** Number of sectors per track */ + uint32_t sectors_per_track; + /** Total number of sectors on drive */ + uint64_t sectors; + /** Bytes per sector */ + uint16_t sector_size; + +} __attribute__ (( packed )); + +/** + * @defgroup int13types INT 13 disk types + * @{ + */ + +/** No such drive */ +#define INT13_DISK_TYPE_NONE 0x00 +/** Floppy without change-line support */ +#define INT13_DISK_TYPE_FDD 0x01 +/** Floppy with change-line support */ +#define INT13_DISK_TYPE_FDD_CL 0x02 +/** Hard disk */ +#define INT13_DISK_TYPE_HDD 0x03 + +/** @} */ + +/** + * @defgroup int13flags INT 13 disk parameter flags + * @{ + */ + +/** DMA boundary errors handled transparently */ +#define INT13_FL_DMA_TRANSPARENT 0x01 +/** CHS information is valid */ +#define INT13_FL_CHS_VALID 0x02 +/** Removable drive */ +#define INT13_FL_REMOVABLE 0x04 +/** Write with verify supported */ +#define INT13_FL_VERIFIABLE 0x08 +/** Has change-line supported (valid only for removable drives) */ +#define INT13_FL_CHANGE_LINE 0x10 +/** Drive can be locked (valid only for removable drives) */ +#define INT13_FL_LOCKABLE 0x20 +/** CHS is max possible, not current media (valid only for removable drives) */ +#define INT13_FL_CHS_MAX 0x40 + +/** @} */ + +/** + * @defgroup int13exts INT 13 extension flags + * @{ + */ + +/** Extended disk access functions supported */ +#define INT13_EXTENSION_LINEAR 0x01 +/** Removable drive functions supported */ +#define INT13_EXTENSION_REMOVABLE 0x02 +/** EDD functions supported */ +#define INT13_EXTENSION_EDD 0x04 + +/** @} */ + +/** + * @defgroup int13vers INT 13 extension versions + * @{ + */ + +/** INT13 extensions version 1.x */ +#define INT13_EXTENSION_VER_1_X 0x01 +/** INT13 extensions version 2.0 (EDD-1.0) */ +#define INT13_EXTENSION_VER_2_0 0x20 +/** INT13 extensions version 2.1 (EDD-1.1) */ +#define INT13_EXTENSION_VER_2_1 0x21 +/** INT13 extensions version 3.0 (EDD-3.0) */ +#define INT13_EXTENSION_VER_3_0 0x30 + +/** @} */ + +/** Bootable CD-ROM specification packet */ +struct int13_cdrom_specification { + /** Size of packet in bytes */ + uint8_t size; + /** Boot media type */ + uint8_t media_type; + /** Drive number */ + uint8_t drive; + /** CD-ROM controller number */ + uint8_t controller; + /** LBA of disk image to emulate */ + uint32_t lba; + /** Device specification */ + uint16_t device; + /** Segment of 3K buffer for caching CD-ROM reads */ + uint16_t cache_segment; + /** Load segment for initial boot image */ + uint16_t load_segment; + /** Number of 512-byte sectors to load */ + uint16_t load_sectors; + /** Low 8 bits of cylinder number */ + uint8_t cyl; + /** Sector number, plus high 2 bits of cylinder number */ + uint8_t cyl_sector; + /** Head number */ + uint8_t head; +} __attribute__ (( packed )); + +/** A C/H/S address within a partition table entry */ +struct partition_chs { + /** Head number */ + uint8_t head; + /** Sector number, plus high 2 bits of cylinder number */ + uint8_t cyl_sector; + /** Low 8 bits of cylinder number */ + uint8_t cyl; +} __attribute__ (( packed )); + +#define PART_HEAD(chs) ( (chs).head ) +#define PART_SECTOR(chs) ( (chs).cyl_sector & 0x3f ) +#define PART_CYLINDER(chs) ( (chs).cyl | ( ( (chs).cyl_sector & 0xc0 ) << 2 ) ) + +/** A partition table entry within the MBR */ +struct partition_table_entry { + /** Bootable flag */ + uint8_t bootable; + /** C/H/S start address */ + struct partition_chs chs_start; + /** System indicator (partition type) */ + uint8_t type; + /** C/H/S end address */ + struct partition_chs chs_end; + /** Linear start address */ + uint32_t start; + /** Linear length */ + uint32_t length; +} __attribute__ (( packed )); + +/** A Master Boot Record */ +struct master_boot_record { + uint8_t pad[446]; + /** Partition table */ + struct partition_table_entry partitions[4]; + /** 0x55aa MBR signature */ + uint16_t signature; +} __attribute__ (( packed )); + +extern void register_int13_drive ( struct int13_drive *drive ); +extern void unregister_int13_drive ( struct int13_drive *drive ); +extern int int13_boot ( unsigned int drive ); + +#endif /* INT13_H */ diff --git a/gpxe/src/arch/i386/include/io.h b/gpxe/src/arch/i386/include/io.h new file mode 100644 index 00000000..c26fdf7e --- /dev/null +++ b/gpxe/src/arch/i386/include/io.h @@ -0,0 +1,265 @@ +#ifndef ETHERBOOT_IO_H +#define ETHERBOOT_IO_H + +#include <stdint.h> +#include "virtaddr.h" + +/* virt_to_bus converts an addresss inside of etherboot [_start, _end] + * into a memory access cards can use. + */ +#define virt_to_bus virt_to_phys + + +/* bus_to_virt reverses virt_to_bus, the address must be output + * from virt_to_bus to be valid. This function does not work on + * all bus addresses. + */ +#define bus_to_virt phys_to_virt + +/* ioremap converts a random 32bit bus address into something + * etherboot can access. + */ +static inline void *ioremap(unsigned long bus_addr, unsigned long length __unused) +{ + return bus_to_virt(bus_addr); +} + +/* iounmap cleans up anything ioremap had to setup */ +static inline void iounmap(void *virt_addr __unused) +{ + return; +} + +/* + * This file contains the definitions for the x86 IO instructions + * inb/inw/inl/outb/outw/outl and the "string versions" of the same + * (insb/insw/insl/outsb/outsw/outsl). You can also use "pausing" + * versions of the single-IO instructions (inb_p/inw_p/..). + * + * This file is not meant to be obfuscating: it's just complicated + * to (a) handle it all in a way that makes gcc able to optimize it + * as well as possible and (b) trying to avoid writing the same thing + * over and over again with slight variations and possibly making a + * mistake somewhere. + */ + +/* + * Thanks to James van Artsdalen for a better timing-fix than + * the two short jumps: using outb's to a nonexistent port seems + * to guarantee better timings even on fast machines. + * + * On the other hand, I'd like to be sure of a non-existent port: + * I feel a bit unsafe about using 0x80 (should be safe, though) + * + * Linus + */ + +#ifdef SLOW_IO_BY_JUMPING +#define __SLOW_DOWN_IO __asm__ __volatile__("jmp 1f\n1:\tjmp 1f\n1:") +#else +#define __SLOW_DOWN_IO __asm__ __volatile__("outb %al,$0x80") +#endif + +#ifdef REALLY_SLOW_IO +#define SLOW_DOWN_IO { __SLOW_DOWN_IO; __SLOW_DOWN_IO; __SLOW_DOWN_IO; __SLOW_DOWN_IO; } +#else +#define SLOW_DOWN_IO __SLOW_DOWN_IO +#endif + +/* + * readX/writeX() are used to access memory mapped devices. On some + * architectures the memory mapped IO stuff needs to be accessed + * differently. On the x86 architecture, we just read/write the + * memory location directly. + */ +static inline __attribute__ (( always_inline )) unsigned long +_readb ( volatile uint8_t *addr ) { + unsigned long data = *addr; + DBGIO ( "[%08lx] => %02lx\n", virt_to_phys ( addr ), data ); + return data; +} +static inline __attribute__ (( always_inline )) unsigned long +_readw ( volatile uint16_t *addr ) { + unsigned long data = *addr; + DBGIO ( "[%08lx] => %04lx\n", virt_to_phys ( addr ), data ); + return data; +} +static inline __attribute__ (( always_inline )) unsigned long +_readl ( volatile uint32_t *addr ) { + unsigned long data = *addr; + DBGIO ( "[%08lx] => %08lx\n", virt_to_phys ( addr ), data ); + return data; +} +#define readb( addr ) _readb ( ( volatile uint8_t * ) (addr) ) +#define readw( addr ) _readw ( ( volatile uint16_t * ) (addr) ) +#define readl( addr ) _readl ( ( volatile uint32_t * ) (addr) ) + +static inline __attribute__ (( always_inline )) void +_writeb ( unsigned long data, volatile uint8_t *addr ) { + DBGIO ( "[%08lx] <= %02lx\n", virt_to_phys ( addr ), data ); + *addr = data; +} +static inline __attribute__ (( always_inline )) void +_writew ( unsigned long data, volatile uint16_t *addr ) { + DBGIO ( "[%08lx] <= %04lx\n", virt_to_phys ( addr ), data ); + *addr = data; +} +static inline __attribute__ (( always_inline )) void +_writel ( unsigned long data, volatile uint32_t *addr ) { + DBGIO ( "[%08lx] <= %08lx\n", virt_to_phys ( addr ), data ); + *addr = data; +} +#define writeb( b, addr ) _writeb ( (b), ( volatile uint8_t * ) (addr) ) +#define writew( b, addr ) _writew ( (b), ( volatile uint16_t * ) (addr) ) +#define writel( b, addr ) _writel ( (b), ( volatile uint32_t * ) (addr) ) + +#define memcpy_fromio(a,b,c) memcpy((a),(void *)(b),(c)) +#define memcpy_toio(a,b,c) memcpy((void *)(a),(b),(c)) + +/* + * Force strict CPU ordering. + * And yes, this is required on UP too when we're talking + * to devices. + * + * For now, "wmb()" doesn't actually do anything, as all + * Intel CPU's follow what Intel calls a *Processor Order*, + * in which all writes are seen in the program order even + * outside the CPU. + * + * I expect future Intel CPU's to have a weaker ordering, + * but I'd also expect them to finally get their act together + * and add some real memory barriers if so. + * + * Some non intel clones support out of order store. wmb() ceases to be a + * nop for these. + */ + +#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory") +#define rmb() mb() +#define wmb() mb(); + + +/* + * Talk about misusing macros.. + */ + +#define __OUT1(s,x) \ +extern void __out##s(unsigned x value, unsigned short port); \ +extern inline void __out##s(unsigned x value, unsigned short port) { + +#define __OUT2(s,s1,s2) \ +__asm__ __volatile__ ("out" #s " %" s1 "0,%" s2 "1" + +#define __OUT(s,s1,x) \ +__OUT1(s,x) __OUT2(s,s1,"w") : : "a" (value), "d" (port)); } \ +__OUT1(s##c,x) __OUT2(s,s1,"") : : "a" (value), "id" (port)); } \ +__OUT1(s##_p,x) __OUT2(s,s1,"w") : : "a" (value), "d" (port)); SLOW_DOWN_IO; } \ +__OUT1(s##c_p,x) __OUT2(s,s1,"") : : "a" (value), "id" (port)); SLOW_DOWN_IO; } + +#define __IN1(s,x) \ +extern unsigned x __in##s(unsigned short port); \ +extern inline unsigned x __in##s(unsigned short port) { unsigned x _v; + +#define __IN2(s,s1,s2) \ +__asm__ __volatile__ ("in" #s " %" s2 "1,%" s1 "0" + +#define __IN(s,s1,x,i...) \ +__IN1(s,x) __IN2(s,s1,"w") : "=a" (_v) : "d" (port) ,##i ); return _v; } \ +__IN1(s##c,x) __IN2(s,s1,"") : "=a" (_v) : "id" (port) ,##i ); return _v; } \ +__IN1(s##_p,x) __IN2(s,s1,"w") : "=a" (_v) : "d" (port) ,##i ); SLOW_DOWN_IO; return _v; } \ +__IN1(s##c_p,x) __IN2(s,s1,"") : "=a" (_v) : "id" (port) ,##i ); SLOW_DOWN_IO; return _v; } + +#define __INS(s) \ +extern void ins##s(unsigned short port, void * addr, unsigned long count); \ +extern inline void ins##s(unsigned short port, void * addr, unsigned long count) \ +{ __asm__ __volatile__ ("cld ; rep ; ins" #s \ +: "=D" (addr), "=c" (count) : "d" (port),"0" (addr),"1" (count)); } + +#define __OUTS(s) \ +extern void outs##s(unsigned short port, const void * addr, unsigned long count); \ +extern inline void outs##s(unsigned short port, const void * addr, unsigned long count) \ +{ __asm__ __volatile__ ("cld ; rep ; outs" #s \ +: "=S" (addr), "=c" (count) : "d" (port),"0" (addr),"1" (count)); } + +__IN(b,"", char) +__IN(w,"",short) +__IN(l,"", long) + +__OUT(b,"b",char) +__OUT(w,"w",short) +__OUT(l,,int) + +__INS(b) +__INS(w) +__INS(l) + +__OUTS(b) +__OUTS(w) +__OUTS(l) + +/* + * Note that due to the way __builtin_constant_p() works, you + * - can't use it inside a inline function (it will never be true) + * - you don't have to worry about side effects within the __builtin.. + */ +#define outb(val,port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __outbc((val),(port)) : \ + __outb((val),(port))) + +#define inb(port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __inbc(port) : \ + __inb(port)) + +#define outb_p(val,port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __outbc_p((val),(port)) : \ + __outb_p((val),(port))) + +#define inb_p(port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __inbc_p(port) : \ + __inb_p(port)) + +#define outw(val,port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __outwc((val),(port)) : \ + __outw((val),(port))) + +#define inw(port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __inwc(port) : \ + __inw(port)) + +#define outw_p(val,port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __outwc_p((val),(port)) : \ + __outw_p((val),(port))) + +#define inw_p(port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __inwc_p(port) : \ + __inw_p(port)) + +#define outl(val,port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __outlc((val),(port)) : \ + __outl((val),(port))) + +#define inl(port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __inlc(port) : \ + __inl(port)) + +#define outl_p(val,port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __outlc_p((val),(port)) : \ + __outl_p((val),(port))) + +#define inl_p(port) \ +((__builtin_constant_p((port)) && (port) < 256) ? \ + __inlc_p(port) : \ + __inl_p(port)) + +#endif /* ETHERBOOT_IO_H */ diff --git a/gpxe/src/arch/i386/include/kir.h b/gpxe/src/arch/i386/include/kir.h new file mode 100644 index 00000000..84633d26 --- /dev/null +++ b/gpxe/src/arch/i386/include/kir.h @@ -0,0 +1,18 @@ +#ifndef KIR_H +#define KIR_H + +#ifndef KEEP_IT_REAL +#error "kir.h can be used only with -DKEEP_IT_REAL" +#endif + +#ifdef ASSEMBLY + +#define code32 code16gcc + +#else /* ASSEMBLY */ + +__asm__ ( ".code16gcc" ); + +#endif /* ASSEMBLY */ + +#endif /* KIR_H */ diff --git a/gpxe/src/arch/i386/include/libkir.h b/gpxe/src/arch/i386/include/libkir.h new file mode 100644 index 00000000..5f67a56d --- /dev/null +++ b/gpxe/src/arch/i386/include/libkir.h @@ -0,0 +1,233 @@ +#ifndef LIBKIR_H +#define LIBKIR_H + +#include "realmode.h" + +#ifndef ASSEMBLY + +/* + * Full API documentation for these functions is in realmode.h. + * + */ + +/* Access to variables in .data16 and .text16 in a way compatible with librm */ +#define __data16( variable ) variable +#define __data16_array( variable, array ) variable array +#define __bss16( variable ) variable +#define __bss16_array( variable, array ) variable array +#define __text16( variable ) variable +#define __text16_array( variable,array ) variable array +#define __use_data16( variable ) variable +#define __use_text16( variable ) variable +#define __from_data16( variable ) variable +#define __from_text16( variable ) variable + +/* Real-mode data and code segments */ +static inline __attribute__ (( always_inline )) unsigned int _rm_cs ( void ) { + uint16_t cs; + __asm__ __volatile__ ( "movw %%cs, %w0" : "=r" ( cs ) ); + return cs; +} + +static inline __attribute__ (( always_inline )) unsigned int _rm_ds ( void ) { + uint16_t ds; + __asm__ __volatile__ ( "movw %%ds, %w0" : "=r" ( ds ) ); + return ds; +} + +#define rm_cs ( _rm_cs() ) +#define rm_ds ( _rm_ds() ) + +/* Copy to/from base memory */ + +static inline void copy_to_real_libkir ( unsigned int dest_seg, + unsigned int dest_off, + const void *src, size_t n ) { + unsigned int discard_D, discard_S, discard_c; + + __asm__ __volatile__ ( "pushw %%es\n\t" + "movw %3, %%es\n\t" + "rep movsb\n\t" + "popw %%es\n\t" + : "=D" ( discard_D ), "=S" ( discard_S ), + "=c" ( discard_c ) + : "r" ( dest_seg ), "D" ( dest_off ), + "S" ( src ), + "c" ( n ) + : "memory" ); +} + +static inline void copy_from_real_libkir ( void *dest, + unsigned int src_seg, + unsigned int src_off, + size_t n ) { + unsigned int discard_D, discard_S, discard_c; + + __asm__ __volatile__ ( "pushw %%ds\n\t" + "movw %4, %%ds\n\t" + "rep movsb\n\t" + "popw %%ds\n\t" + : "=D" ( discard_D ), "=S" ( discard_S ), + "=c" ( discard_c ) + : "D" ( dest ), + "r" ( src_seg ), "S" ( src_off ), + "c" ( n ) + : "memory" ); +} + +#define copy_to_real copy_to_real_libkir +#define copy_from_real copy_from_real_libkir + +/* + * Transfer individual values to/from base memory. There may well be + * a neater way to do this. We have two versions: one for constant + * offsets (where the mov instruction must be of the form "mov + * %es:123, %xx") and one for non-constant offsets (where the mov + * instruction must be of the form "mov %es:(%xx), %yx". If it's + * possible to incorporate both forms into one __asm__ instruction, I + * don't know how to do it. + * + * Ideally, the mov instruction should be "mov%z0"; the "%z0" is meant + * to expand to either "b", "w" or "l" depending on the size of + * operand 0. This would remove the (minor) ambiguity in the mov + * instruction. However, gcc on at least my system barfs with an + * "internal compiler error" when confronted with %z0. + * + */ + +#define put_real_kir_const_off( var, seg, off ) \ + __asm__ ( "movw %w1, %%es\n\t" \ + "mov %0, %%es:%c2\n\t" \ + "pushw %%ds\n\t" /* restore %es */ \ + "popw %%es\n\t" \ + : \ + : "r,r" ( var ), "rm,rm" ( seg ), "i,!r" ( off ) \ + ) + +#define put_real_kir_nonconst_off( var, seg, off ) \ + __asm__ ( "movw %w1, %%es\n\t" \ + "mov %0, %%es:(%2)\n\t" \ + "pushw %%ds\n\t" /* restore %es */ \ + "popw %%es\n\t" \ + : \ + : "r" ( var ), "rm" ( seg ), "r" ( off ) \ + ) + +#define put_real_kir( var, seg, off ) \ + do { \ + if ( __builtin_constant_p ( off ) ) \ + put_real_kir_const_off ( var, seg, off ); \ + else \ + put_real_kir_nonconst_off ( var, seg, off ); \ + } while ( 0 ) + +#define get_real_kir_const_off( var, seg, off ) \ + __asm__ ( "movw %w1, %%es\n\t" \ + "mov %%es:%c2, %0\n\t" \ + "pushw %%ds\n\t" /* restore %es */ \ + "popw %%es\n\t" \ + : "=r,r" ( var ) \ + : "rm,rm" ( seg ), "i,!r" ( off ) \ + ) + +#define get_real_kir_nonconst_off( var, seg, off ) \ + __asm__ ( "movw %w1, %%es\n\t" \ + "mov %%es:(%2), %0\n\t" \ + "pushw %%ds\n\t" /* restore %es */ \ + "popw %%es\n\t" \ + : "=r" ( var ) \ + : "rm" ( seg ), "r" ( off ) \ + ) + +#define get_real_kir( var, seg, off ) \ + do { \ + if ( __builtin_constant_p ( off ) ) \ + get_real_kir_const_off ( var, seg, off ); \ + else \ + get_real_kir_nonconst_off ( var, seg, off ); \ + } while ( 0 ) + +#define put_real put_real_kir +#define get_real get_real_kir + +/** + * A pointer to a user buffer + * + * This is actually a struct segoff, but encoded as a uint32_t to + * ensure that gcc passes it around efficiently. + */ +typedef uint32_t userptr_t; + +/** + * Copy data to user buffer + * + * @v buffer User buffer + * @v offset Offset within user buffer + * @v src Source + * @v len Length + */ +static inline __attribute__ (( always_inline )) void +copy_to_user ( userptr_t buffer, off_t offset, const void *src, size_t len ) { + copy_to_real ( ( buffer >> 16 ), ( ( buffer & 0xffff ) + offset ), + src, len ); +} + +/** + * Copy data from user buffer + * + * @v dest Destination + * @v buffer User buffer + * @v offset Offset within user buffer + * @v len Length + */ +static inline __attribute__ (( always_inline )) void +copy_from_user ( void *dest, userptr_t buffer, off_t offset, size_t len ) { + copy_from_real ( dest, ( buffer >> 16 ), + ( ( buffer & 0xffff ) + offset ), len ); +} + +/** + * Convert segment:offset address to user buffer + * + * @v segment Real-mode segment + * @v offset Real-mode offset + * @ret buffer User buffer + */ +static inline __attribute__ (( always_inline )) userptr_t +real_to_user ( unsigned int segment, unsigned int offset ) { + return ( ( segment << 16 ) | offset ); +} + +/** + * Convert virtual address to user buffer + * + * @v virtual Virtual address + * @ret buffer User buffer + * + * This constructs a user buffer from an ordinary pointer. Use it + * when you need to pass a pointer to an internal buffer to a function + * that expects a @c userptr_t. + */ +static inline __attribute__ (( always_inline )) userptr_t +virt_to_user ( void * virtual ) { + return real_to_user ( rm_ds, ( intptr_t ) virtual ); +} + +/* TEXT16_CODE: declare a fragment of code that resides in .text16 */ +#define TEXT16_CODE( asm_code_str ) \ + ".section \".text16\", \"ax\", @progbits\n\t" \ + ".code16\n\t" \ + ".arch i386\n\t" \ + asm_code_str "\n\t" \ + ".code16gcc\n\t" \ + ".previous\n\t" + +/* REAL_CODE: declare a fragment of code that executes in real mode */ +#define REAL_CODE( asm_code_str ) \ + ".code16\n\t" \ + asm_code_str "\n\t" \ + ".code16gcc\n\t" + +#endif /* ASSEMBLY */ + +#endif /* LIBKIR_H */ diff --git a/gpxe/src/arch/i386/include/librm.h b/gpxe/src/arch/i386/include/librm.h new file mode 100644 index 00000000..32dceed6 --- /dev/null +++ b/gpxe/src/arch/i386/include/librm.h @@ -0,0 +1,289 @@ +#ifndef LIBRM_H +#define LIBRM_H + +/* Drag in protected-mode segment selector values */ +#include "virtaddr.h" +#include "realmode.h" + +#ifndef ASSEMBLY + +#include "stddef.h" +#include "string.h" + +/* + * Data structures and type definitions + * + */ + +/* Access to variables in .data16 and .text16 */ +extern char *data16; +extern char *text16; + +#define __data16( variable ) \ + __attribute__ (( section ( ".data16" ) )) \ + _data16_ ## variable __asm__ ( #variable ) + +#define __data16_array( variable, array ) \ + __attribute__ (( section ( ".data16" ) )) \ + _data16_ ## variable array __asm__ ( #variable ) + +#define __bss16( variable ) \ + __attribute__ (( section ( ".bss16" ) )) \ + _data16_ ## variable __asm__ ( #variable ) + +#define __bss16_array( variable, array ) \ + __attribute__ (( section ( ".bss16" ) )) \ + _data16_ ## variable array __asm__ ( #variable ) + +#define __text16( variable ) \ + __attribute__ (( section ( ".text16.data" ) )) \ + _text16_ ## variable __asm__ ( #variable ) + +#define __text16_array( variable, array ) \ + __attribute__ (( section ( ".text16.data" ) )) \ + _text16_ ## variable array __asm__ ( #variable ) + +#define __use_data16( variable ) \ + ( * ( ( typeof ( _data16_ ## variable ) * ) \ + & ( data16 [ ( size_t ) & ( _data16_ ## variable ) ] ) ) ) + +#define __use_text16( variable ) \ + ( * ( ( typeof ( _text16_ ## variable ) * ) \ + & ( text16 [ ( size_t ) & ( _text16_ ## variable ) ] ) ) ) + +#define __from_data16( variable ) \ + ( * ( ( typeof ( variable ) * ) \ + ( ( ( void * ) &(variable) ) - ( ( void * ) data16 ) ) ) ) + +#define __from_text16( variable ) \ + ( * ( ( typeof ( variable ) * ) \ + ( ( ( void * ) &(variable) ) - ( ( void * ) text16 ) ) ) ) + +/* Variables in librm.S, present in the normal data segment */ +extern uint16_t __data16 ( rm_cs ); +#define rm_cs __use_data16 ( rm_cs ) +extern uint16_t __text16 ( rm_ds ); +#define rm_ds __use_text16 ( rm_ds ) + +/* Functions that librm expects to be able to link to. Included here + * so that the compiler will catch prototype mismatches. + */ +extern void gateA20_set ( void ); + +/* + * librm_mgmt: functions for manipulating base memory and executing + * real-mode code. + * + * Full API documentation for these functions is in realmode.h. + * + */ + +/* Macro for obtaining a physical address from a segment:offset pair. */ +#define VIRTUAL(x,y) ( phys_to_virt ( ( ( x ) << 4 ) + ( y ) ) ) + +/* Copy to/from base memory */ +static inline __attribute__ (( always_inline )) void +copy_to_real_librm ( unsigned int dest_seg, unsigned int dest_off, + void *src, size_t n ) { + memcpy ( VIRTUAL ( dest_seg, dest_off ), src, n ); +} +static inline __attribute__ (( always_inline )) void +copy_from_real_librm ( void *dest, unsigned int src_seg, + unsigned int src_off, size_t n ) { + memcpy ( dest, VIRTUAL ( src_seg, src_off ), n ); +} +#define put_real_librm( var, dest_seg, dest_off ) \ + do { \ + * ( ( typeof(var) * ) VIRTUAL ( dest_seg, dest_off ) ) = var; \ + } while ( 0 ) +#define get_real_librm( var, src_seg, src_off ) \ + do { \ + var = * ( ( typeof(var) * ) VIRTUAL ( src_seg, src_off ) ); \ + } while ( 0 ) +#define copy_to_real copy_to_real_librm +#define copy_from_real copy_from_real_librm +#define put_real put_real_librm +#define get_real get_real_librm + +/** + * A pointer to a user buffer + * + * Even though we could just use a void *, we use an intptr_t so that + * attempts to use normal pointers show up as compiler warnings. Such + * code is actually valid for librm, but not for libkir (i.e. under + * KEEP_IT_REAL), so it's good to have the warnings even under librm. + */ +typedef intptr_t userptr_t; + +/** + * Add offset to user pointer + * + * @v ptr User pointer + * @v offset Offset + * @ret new_ptr New pointer value + */ +static inline __attribute__ (( always_inline )) userptr_t +userptr_add ( userptr_t ptr, off_t offset ) { + return ( ptr + offset ); +} + +/** + * Copy data to user buffer + * + * @v buffer User buffer + * @v offset Offset within user buffer + * @v src Source + * @v len Length + */ +static inline __attribute__ (( always_inline )) void +copy_to_user ( userptr_t buffer, off_t offset, const void *src, size_t len ) { + memcpy ( ( ( void * ) buffer + offset ), src, len ); +} + +/** + * Copy data from user buffer + * + * @v dest Destination + * @v buffer User buffer + * @v offset Offset within user buffer + * @v len Length + */ +static inline __attribute__ (( always_inline )) void +copy_from_user ( void *dest, userptr_t buffer, off_t offset, size_t len ) { + memcpy ( dest, ( ( void * ) buffer + offset ), len ); +} + +/** + * Copy data between user buffers + * + * @v dest Destination user buffer + * @v dest_off Offset within destination buffer + * @v src Source user buffer + * @v src_off Offset within source buffer + * @v len Length + */ +static inline __attribute__ (( always_inline )) void +memcpy_user ( userptr_t dest, off_t dest_off, userptr_t src, off_t src_off, + size_t len ) { + memcpy ( ( ( void * ) dest + dest_off ), ( ( void * ) src + src_off ), + len ); +} + +/** + * Copy data between user buffers, allowing for overlap + * + * @v dest Destination user buffer + * @v dest_off Offset within destination buffer + * @v src Source user buffer + * @v src_off Offset within source buffer + * @v len Length + */ +static inline __attribute__ (( always_inline )) void +memmove_user ( userptr_t dest, off_t dest_off, userptr_t src, off_t src_off, + size_t len ) { + memmove ( ( ( void * ) dest + dest_off ), ( ( void * ) src + src_off ), + len ); +} + +/** + * Fill user buffer with a constant byte + * + * @v buffer User buffer + * @v offset Offset within buffer + * @v c Constant byte with which to fill + * @v len Length + */ +static inline __attribute__ (( always_inline )) void +memset_user ( userptr_t buffer, off_t offset, int c, size_t len ) { + memset ( ( ( void * ) buffer + offset ), c, len ); +} + +/** + * Find length of NUL-terminated string in user buffer + * + * @v buffer User buffer + * @v offset Offset within buffer + * @ret len Length of string (excluding NUL) + */ +static inline __attribute__ (( always_inline )) size_t +strlen_user ( userptr_t buffer, off_t offset ) { + return strlen ( ( void * ) buffer + offset ); +} + +/** + * Convert virtual address to user buffer + * + * @v virtual Virtual address + * @ret buffer User buffer + * + * This constructs a user buffer from an ordinary pointer. Use it + * when you need to pass a pointer to an internal buffer to a function + * that expects a @c userptr_t. + */ +static inline __attribute__ (( always_inline )) userptr_t +virt_to_user ( void * virtual ) { + return ( ( intptr_t ) virtual ); +} + +/** + * Convert segment:offset address to user buffer + * + * @v segment Real-mode segment + * @v offset Real-mode offset + * @ret buffer User buffer + */ +static inline __attribute__ (( always_inline )) userptr_t +real_to_user ( unsigned int segment, unsigned int offset ) { + return virt_to_user ( VIRTUAL ( segment, offset ) ); +} + +/** + * Convert physical address to user buffer + * + * @v physical Physical address + * @ret buffer User buffer + */ +static inline __attribute__ (( always_inline )) userptr_t +phys_to_user ( physaddr_t physical ) { + return virt_to_user ( phys_to_virt ( physical ) ); +} + +/** + * Convert user buffer to physical address + * + * @v buffer User buffer + * @v offset Offset within user buffer + * @ret physical Physical address + */ +static inline __attribute__ (( always_inline )) physaddr_t +user_to_phys ( userptr_t buffer, off_t offset ) { + return virt_to_phys ( ( void * ) buffer + offset ); +} + +/* TEXT16_CODE: declare a fragment of code that resides in .text16 */ +#define TEXT16_CODE( asm_code_str ) \ + ".section \".text16\", \"ax\", @progbits\n\t" \ + ".code16\n\t" \ + asm_code_str "\n\t" \ + ".code32\n\t" \ + ".previous\n\t" + +/* REAL_CODE: declare a fragment of code that executes in real mode */ +#define REAL_CODE( asm_code_str ) \ + "pushl $1f\n\t" \ + "call real_call\n\t" \ + "addl $4, %%esp\n\t" \ + TEXT16_CODE ( "\n1:\n\t" \ + asm_code_str \ + "\n\t" \ + "ret\n\t" ) + +/* PHYS_CODE: declare a fragment of code that executes in flat physical mode */ +#define PHYS_CODE( asm_code_str ) \ + "call _virt_to_phys\n\t" \ + asm_code_str \ + "call _phys_to_virt\n\t" + +#endif /* ASSEMBLY */ + +#endif /* LIBRM_H */ diff --git a/gpxe/src/arch/i386/include/limits.h b/gpxe/src/arch/i386/include/limits.h new file mode 100644 index 00000000..f13db267 --- /dev/null +++ b/gpxe/src/arch/i386/include/limits.h @@ -0,0 +1,59 @@ +#ifndef LIMITS_H +#define LIMITS_H 1 + +/* Number of bits in a `char' */ +#define CHAR_BIT 8 + +/* Minimum and maximum values a `signed char' can hold */ +#define SCHAR_MIN (-128) +#define SCHAR_MAX 127 + +/* Maximum value an `unsigned char' can hold. (Minimum is 0.) */ +#define UCHAR_MAX 255 + +/* Minimum and maximum values a `char' can hold */ +#define CHAR_MIN SCHAR_MIN +#define CHAR_MAX SCHAR_MAX + +/* Minimum and maximum values a `signed short int' can hold */ +#define SHRT_MIN (-32768) +#define SHRT_MAX 32767 + +/* Maximum value an `unsigned short' can hold. (Minimum is 0.) */ +#define USHRT_MAX 65535 + + +/* Minimum and maximum values a `signed int' can hold */ +#define INT_MIN (-INT_MAX - 1) +#define INT_MAX 2147483647 + +/* Maximum value an `unsigned int' can hold. (Minimum is 0.) */ +#define UINT_MAX 4294967295U + + +/* Minimum and maximum values a `signed int' can hold */ +#define INT_MAX 2147483647 +#define INT_MIN (-INT_MAX - 1) + + +/* Maximum value an `unsigned int' can hold. (Minimum is 0.) */ +#define UINT_MAX 4294967295U + + +/* Minimum and maximum values a `signed long' can hold */ +#define LONG_MAX 2147483647 +#define LONG_MIN (-LONG_MAX - 1L) + +/* Maximum value an `unsigned long' can hold. (Minimum is 0.) */ +#define ULONG_MAX 4294967295UL + +/* Minimum and maximum values a `signed long long' can hold */ +#define LLONG_MAX 9223372036854775807LL +#define LLONG_MIN (-LONG_MAX - 1LL) + + +/* Maximum value an `unsigned long long' can hold. (Minimum is 0.) */ +#define ULLONG_MAX 18446744073709551615ULL + + +#endif /* LIMITS_H */ diff --git a/gpxe/src/arch/i386/include/memsizes.h b/gpxe/src/arch/i386/include/memsizes.h new file mode 100644 index 00000000..6222fd66 --- /dev/null +++ b/gpxe/src/arch/i386/include/memsizes.h @@ -0,0 +1,17 @@ +#ifndef _MEMSIZES_H +#define _MEMSIZES_H + +#include <basemem.h> + +/** + * Get size of base memory from BIOS free base memory counter + * + * @ret basemem Base memory size, in kB + */ +static inline unsigned int basememsize ( void ) { + return get_fbms(); +} + +extern unsigned int extmemsize ( void ); + +#endif /* _MEMSIZES_H */ diff --git a/gpxe/src/arch/i386/include/multiboot.h b/gpxe/src/arch/i386/include/multiboot.h new file mode 100644 index 00000000..4ca7089b --- /dev/null +++ b/gpxe/src/arch/i386/include/multiboot.h @@ -0,0 +1,147 @@ +#ifndef _MULTIBOOT_H +#define _MULTIBOOT_H + +/** + * @file + * + * Multiboot operating systems + * + */ + +#include <stdint.h> + +/** The magic number for the Multiboot header */ +#define MULTIBOOT_HEADER_MAGIC 0x1BADB002 + +/** Boot modules must be page aligned */ +#define MB_FLAG_PGALIGN 0x00000001 + +/** Memory map must be provided */ +#define MB_FLAG_MEMMAP 0x00000002 + +/** Video mode information must be provided */ +#define MB_FLAG_VIDMODE 0x00000004 + +/** Image is a raw multiboot image (not ELF) */ +#define MB_FLAG_RAW 0x00010000 + +/** + * The magic number passed by a Multiboot-compliant boot loader + * + * Must be passed in register %eax when jumping to the Multiboot OS + * image. + */ +#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002 + +/** Multiboot information structure mem_* fields are valid */ +#define MBI_FLAG_MEM 0x00000001 + +/** Multiboot information structure boot_device field is valid */ +#define MBI_FLAG_BOOTDEV 0x00000002 + +/** Multiboot information structure cmdline field is valid */ +#define MBI_FLAG_CMDLINE 0x00000004 + +/** Multiboot information structure module fields are valid */ +#define MBI_FLAG_MODS 0x00000008 + +/** Multiboot information structure a.out symbol table is valid */ +#define MBI_FLAG_AOUT 0x00000010 + +/** Multiboot information struture ELF section header table is valid */ +#define MBI_FLAG_ELF 0x00000020 + +/** Multiboot information structure memory map is valid */ +#define MBI_FLAG_MMAP 0x00000040 + +/** Multiboot information structure drive list is valid */ +#define MBI_FLAG_DRIVES 0x00000080 + +/** Multiboot information structure ROM configuration field is valid */ +#define MBI_FLAG_CFGTBL 0x00000100 + +/** Multiboot information structure boot loader name field is valid */ +#define MBI_FLAG_LOADER 0x00000200 + +/** Multiboot information structure APM table is valid */ +#define MBI_FLAG_APM 0x00000400 + +/** Multiboot information structure video information is valid */ +#define MBI_FLAG_VBE 0x00000800 + +/** A multiboot header */ +struct multiboot_header { + uint32_t magic; + uint32_t flags; + uint32_t checksum; + uint32_t header_addr; + uint32_t load_addr; + uint32_t load_end_addr; + uint32_t bss_end_addr; + uint32_t entry_addr; +} __attribute__ (( packed, may_alias )); + +/** A multiboot a.out symbol table */ +struct multiboot_aout_symbol_table { + uint32_t tabsize; + uint32_t strsize; + uint32_t addr; + uint32_t reserved; +} __attribute__ (( packed, may_alias )); + +/** A multiboot ELF section header table */ +struct multiboot_elf_section_header_table { + uint32_t num; + uint32_t size; + uint32_t addr; + uint32_t shndx; +} __attribute__ (( packed, may_alias )); + +/** A multiboot information structure */ +struct multiboot_info { + uint32_t flags; + uint32_t mem_lower; + uint32_t mem_upper; + uint32_t boot_device; + uint32_t cmdline; + uint32_t mods_count; + uint32_t mods_addr; + union { + struct multiboot_aout_symbol_table aout_syms; + struct multiboot_elf_section_header_table elf_sections; + } syms; + uint32_t mmap_length; + uint32_t mmap_addr; + uint32_t drives_length; + uint32_t drives_addr; + uint32_t config_table; + uint32_t boot_loader_name; + uint32_t apm_table; + uint32_t vbe_control_info; + uint32_t vbe_mode_info; + uint16_t vbe_mode; + uint16_t vbe_interface_seg; + uint16_t vbe_interface_off; + uint16_t vbe_interface_len; +} __attribute__ (( packed, may_alias )); + +/** A multiboot module structure */ +struct multiboot_module { + uint32_t mod_start; + uint32_t mod_end; + uint32_t string; + uint32_t reserved; +} __attribute__ (( packed, may_alias )); + +/** A multiboot memory map entry */ +struct multiboot_memory_map { + uint32_t size; + uint64_t base_addr; + uint64_t length; + uint32_t type; +} __attribute__ (( packed, may_alias )); + +/** Usable RAM */ +#define MBMEM_RAM 1 + +#endif /* _MULTIBOOT_H */ diff --git a/gpxe/src/arch/i386/include/pci_io.h b/gpxe/src/arch/i386/include/pci_io.h new file mode 100644 index 00000000..4888d557 --- /dev/null +++ b/gpxe/src/arch/i386/include/pci_io.h @@ -0,0 +1,35 @@ +#ifndef _PCI_IO_H +#define _PCI_IO_H + +#include <pcibios.h> +#include <pcidirect.h> + +/** @file + * + * i386 PCI configuration space access + * + * We have two methods of PCI configuration space access: the PCI BIOS + * and direct Type 1 accesses. Selecting between them is via the + * compile-time switch -DCONFIG_PCI_DIRECT. + * + */ + +#if CONFIG_PCI_DIRECT +#define pci_max_bus pcidirect_max_bus +#define pci_read_config_byte pcidirect_read_config_byte +#define pci_read_config_word pcidirect_read_config_word +#define pci_read_config_dword pcidirect_read_config_dword +#define pci_write_config_byte pcidirect_write_config_byte +#define pci_write_config_word pcidirect_write_config_word +#define pci_write_config_dword pcidirect_write_config_dword +#else /* CONFIG_PCI_DIRECT */ +#define pci_max_bus pcibios_max_bus +#define pci_read_config_byte pcibios_read_config_byte +#define pci_read_config_word pcibios_read_config_word +#define pci_read_config_dword pcibios_read_config_dword +#define pci_write_config_byte pcibios_write_config_byte +#define pci_write_config_word pcibios_write_config_word +#define pci_write_config_dword pcibios_write_config_dword +#endif /* CONFIG_PCI_DIRECT */ + +#endif /* _PCI_IO_H */ diff --git a/gpxe/src/arch/i386/include/pcibios.h b/gpxe/src/arch/i386/include/pcibios.h new file mode 100644 index 00000000..3d08d135 --- /dev/null +++ b/gpxe/src/arch/i386/include/pcibios.h @@ -0,0 +1,122 @@ +#ifndef _PCIBIOS_H +#define _PCIBIOS_H + +#include <stdint.h> + +/** @file + * + * PCI configuration space access via PCI BIOS + * + */ + +struct pci_device; + +#define PCIBIOS_INSTALLATION_CHECK 0xb1010000 +#define PCIBIOS_READ_CONFIG_BYTE 0xb1080000 +#define PCIBIOS_READ_CONFIG_WORD 0xb1090000 +#define PCIBIOS_READ_CONFIG_DWORD 0xb10a0000 +#define PCIBIOS_WRITE_CONFIG_BYTE 0xb10b0000 +#define PCIBIOS_WRITE_CONFIG_WORD 0xb10c0000 +#define PCIBIOS_WRITE_CONFIG_DWORD 0xb10d0000 + +extern int pcibios_max_bus ( void ); +extern int pcibios_read ( struct pci_device *pci, uint32_t command, + uint32_t *value ); +extern int pcibios_write ( struct pci_device *pci, uint32_t command, + uint32_t value ); + +/** + * Read byte from PCI configuration space via PCI BIOS + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcibios_read_config_byte ( struct pci_device *pci, unsigned int where, + uint8_t *value ) { + uint32_t tmp; + int rc; + + rc = pcibios_read ( pci, PCIBIOS_READ_CONFIG_BYTE | where, &tmp ); + *value = tmp; + return rc; +} + +/** + * Read word from PCI configuration space via PCI BIOS + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcibios_read_config_word ( struct pci_device *pci, unsigned int where, + uint16_t *value ) { + uint32_t tmp; + int rc; + + rc = pcibios_read ( pci, PCIBIOS_READ_CONFIG_WORD | where, &tmp ); + *value = tmp; + return rc; +} + +/** + * Read dword from PCI configuration space via PCI BIOS + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcibios_read_config_dword ( struct pci_device *pci, unsigned int where, + uint32_t *value ) { + return pcibios_read ( pci, PCIBIOS_READ_CONFIG_DWORD | where, value ); +} + +/** + * Write byte to PCI configuration space via PCI BIOS + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcibios_write_config_byte ( struct pci_device *pci, unsigned int where, + uint8_t value ) { + return pcibios_write ( pci, PCIBIOS_WRITE_CONFIG_BYTE | where, value ); +} + +/** + * Write word to PCI configuration space via PCI BIOS + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcibios_write_config_word ( struct pci_device *pci, unsigned int where, + uint16_t value ) { + return pcibios_write ( pci, PCIBIOS_WRITE_CONFIG_WORD | where, value ); +} + +/** + * Write dword to PCI configuration space via PCI BIOS + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcibios_write_config_dword ( struct pci_device *pci, unsigned int where, + uint32_t value ) { + return pcibios_write ( pci, PCIBIOS_WRITE_CONFIG_DWORD | where, value); +} + +#endif /* _PCIBIOS_H */ diff --git a/gpxe/src/arch/i386/include/pcidirect.h b/gpxe/src/arch/i386/include/pcidirect.h new file mode 100644 index 00000000..4e2e9d12 --- /dev/null +++ b/gpxe/src/arch/i386/include/pcidirect.h @@ -0,0 +1,126 @@ +#ifndef _PCIDIRECT_H +#define _PCIDIRECT_H + +#include <stdint.h> +#include <io.h> + +/** @file + * + * PCI configuration space access via Type 1 accesses + * + */ + +#define PCIDIRECT_CONFIG_ADDRESS 0xcf8 +#define PCIDIRECT_CONFIG_DATA 0xcfc + +struct pci_device; + +extern void pcidirect_prepare ( struct pci_device *pci, int where ); + +/** + * Determine maximum PCI bus number within system + * + * @ret max_bus Maximum bus number + */ +static inline int pcidirect_max_bus ( void ) { + /* No way to work this out via Type 1 accesses */ + return 0xff; +} + +/** + * Read byte from PCI configuration space via Type 1 access + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcidirect_read_config_byte ( struct pci_device *pci, unsigned int where, + uint8_t *value ) { + pcidirect_prepare ( pci, where ); + *value = inb ( PCIDIRECT_CONFIG_DATA + ( where & 3 ) ); + return 0; +} + +/** + * Read word from PCI configuration space via Type 1 access + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcidirect_read_config_word ( struct pci_device *pci, unsigned int where, + uint16_t *value ) { + pcidirect_prepare ( pci, where ); + *value = inw ( PCIDIRECT_CONFIG_DATA + ( where & 2 ) ); + return 0; +} + +/** + * Read dword from PCI configuration space via Type 1 access + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcidirect_read_config_dword ( struct pci_device *pci, unsigned int where, + uint32_t *value ) { + pcidirect_prepare ( pci, where ); + *value = inl ( PCIDIRECT_CONFIG_DATA ); + return 0; +} + +/** + * Write byte to PCI configuration space via Type 1 access + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcidirect_write_config_byte ( struct pci_device *pci, unsigned int where, + uint8_t value ) { + pcidirect_prepare ( pci, where ); + outb ( value, PCIDIRECT_CONFIG_DATA + ( where & 3 ) ); + return 0; +} + +/** + * Write word to PCI configuration space via Type 1 access + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcidirect_write_config_word ( struct pci_device *pci, unsigned int where, + uint16_t value ) { + pcidirect_prepare ( pci, where ); + outw ( value, PCIDIRECT_CONFIG_DATA + ( where & 2 ) ); + return 0; +} + +/** + * Write dword to PCI configuration space via Type 1 access + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +pcidirect_write_config_dword ( struct pci_device *pci, unsigned int where, + uint32_t value ) { + pcidirect_prepare ( pci, where ); + outl ( value, PCIDIRECT_CONFIG_DATA ); + return 0; +} + +#endif /* _PCIDIRECT_H */ diff --git a/gpxe/src/arch/i386/include/pic8259.h b/gpxe/src/arch/i386/include/pic8259.h new file mode 100644 index 00000000..0c501a9c --- /dev/null +++ b/gpxe/src/arch/i386/include/pic8259.h @@ -0,0 +1,69 @@ +/* + * Basic support for controlling the 8259 Programmable Interrupt Controllers. + * + * Initially written by Michael Brown (mcb30). + */ + +#ifndef PIC8259_H +#define PIC8259_H + +/* For segoff_t */ +#include "realmode.h" + +#define IRQ_PIC_CUTOFF 8 + +/* 8259 register locations */ +#define PIC1_ICW1 0x20 +#define PIC1_OCW2 0x20 +#define PIC1_OCW3 0x20 +#define PIC1_ICR 0x20 +#define PIC1_IRR 0x20 +#define PIC1_ISR 0x20 +#define PIC1_ICW2 0x21 +#define PIC1_ICW3 0x21 +#define PIC1_ICW4 0x21 +#define PIC1_IMR 0x21 +#define PIC2_ICW1 0xa0 +#define PIC2_OCW2 0xa0 +#define PIC2_OCW3 0xa0 +#define PIC2_ICR 0xa0 +#define PIC2_IRR 0xa0 +#define PIC2_ISR 0xa0 +#define PIC2_ICW2 0xa1 +#define PIC2_ICW3 0xa1 +#define PIC2_ICW4 0xa1 +#define PIC2_IMR 0xa1 + +/* Register command values */ +#define OCW3_ID 0x08 +#define OCW3_READ_IRR 0x03 +#define OCW3_READ_ISR 0x02 +#define ICR_EOI_NON_SPECIFIC 0x20 +#define ICR_EOI_NOP 0x40 +#define ICR_EOI_SPECIFIC 0x60 +#define ICR_EOI_SET_PRIORITY 0xc0 + +/* Macros to enable/disable IRQs */ +#define IMR_REG(x) ( (x) < IRQ_PIC_CUTOFF ? PIC1_IMR : PIC2_IMR ) +#define IMR_BIT(x) ( 1 << ( (x) % IRQ_PIC_CUTOFF ) ) +#define irq_enabled(x) ( ( inb ( IMR_REG(x) ) & IMR_BIT(x) ) == 0 ) +#define enable_irq(x) outb ( inb( IMR_REG(x) ) & ~IMR_BIT(x), IMR_REG(x) ) +#define disable_irq(x) outb ( inb( IMR_REG(x) ) | IMR_BIT(x), IMR_REG(x) ) + +/* Macros for acknowledging IRQs */ +#define ICR_REG( irq ) ( (irq) < IRQ_PIC_CUTOFF ? PIC1_ICR : PIC2_ICR ) +#define ICR_VALUE( irq ) ( (irq) % IRQ_PIC_CUTOFF ) +#define CHAINED_IRQ 2 + +/* Utility macros to convert IRQ numbers to INT numbers and INT vectors */ +#define IRQ_INT( irq ) ( ( ( (irq) - IRQ_PIC_CUTOFF ) ^ 0x70 ) & 0x7f ) + +/* Other constants */ +#define IRQ_MAX 15 +#define IRQ_NONE -1U + +/* Function prototypes + */ +void send_eoi ( unsigned int irq ); + +#endif /* PIC8259_H */ diff --git a/gpxe/src/arch/i386/include/pnpbios.h b/gpxe/src/arch/i386/include/pnpbios.h new file mode 100644 index 00000000..ab31c699 --- /dev/null +++ b/gpxe/src/arch/i386/include/pnpbios.h @@ -0,0 +1,15 @@ +#ifndef _PNPBIOS_H +#define _PNPBIOS_H + +/** @file + * + * PnP BIOS + * + */ + +/* BIOS segment address */ +#define BIOS_SEG 0xf000 + +extern int find_pnp_bios ( void ); + +#endif /* _PNPBIOS_H */ diff --git a/gpxe/src/arch/i386/include/pxe_addr.h b/gpxe/src/arch/i386/include/pxe_addr.h new file mode 100644 index 00000000..954551e8 --- /dev/null +++ b/gpxe/src/arch/i386/include/pxe_addr.h @@ -0,0 +1,17 @@ +/* + * Architecture-specific portion of pxe.h for Etherboot + * + * This file has to define the types SEGOFF16_t, SEGDESC_t and + * SEGSEL_t for use in other PXE structures. See pxe.h for details. + */ + +#ifndef PXE_ADDR_H +#define PXE_ADDR_H + +#define IS_NULL_SEGOFF16(x) ( ( (x).segment == 0 ) && ( (x).offset == 0 ) ) +#define SEGOFF16_TO_PTR(x) ( VIRTUAL( (x).segment, (x).offset ) ) +#define PTR_TO_SEGOFF16(ptr,segoff16) \ + (segoff16).segment = SEGMENT(ptr); \ + (segoff16).offset = OFFSET(ptr); + +#endif /* PXE_ADDR_H */ diff --git a/gpxe/src/arch/i386/include/pxe_call.h b/gpxe/src/arch/i386/include/pxe_call.h new file mode 100644 index 00000000..dc585310 --- /dev/null +++ b/gpxe/src/arch/i386/include/pxe_call.h @@ -0,0 +1,34 @@ +#ifndef _PXE_CALL_H +#define _PXE_CALL_H + +/** @file + * + * PXE API entry point + */ + +#include <pxe_api.h> +#include <realmode.h> + +/** PXE load address segment */ +#define PXE_LOAD_SEGMENT 0 + +/** PXE load address offset */ +#define PXE_LOAD_OFFSET 0x7c00 + +/** PXE physical load address */ +#define PXE_LOAD_PHYS ( ( PXE_LOAD_SEGMENT << 4 ) + PXE_LOAD_OFFSET ) + +/** !PXE structure */ +extern struct s_PXE __text16 ( ppxe ); +#define ppxe __use_text16 ( ppxe ) + +/** PXENV+ structure */ +extern struct s_PXENV __text16 ( pxenv ); +#define pxenv __use_text16 ( pxenv ) + +extern void pxe_hook_int1a ( void ); +extern int pxe_unhook_int1a ( void ); +extern void pxe_init_structures ( void ); +extern int pxe_start_nbp ( void ); + +#endif /* _PXE_CALL_H */ diff --git a/gpxe/src/arch/i386/include/realmode.h b/gpxe/src/arch/i386/include/realmode.h new file mode 100644 index 00000000..5d3ddf50 --- /dev/null +++ b/gpxe/src/arch/i386/include/realmode.h @@ -0,0 +1,128 @@ +#ifndef REALMODE_H +#define REALMODE_H + +#ifndef ASSEMBLY + +#include "stdint.h" +#include "registers.h" +#include "io.h" + +/* + * Data structures and type definitions + * + */ + +/* Segment:offset structure. Note that the order within the structure + * is offset:segment. + */ +struct segoff { + uint16_t offset; + uint16_t segment; +} __attribute__ (( packed )); + +typedef struct segoff segoff_t; + +/* Macro hackery needed to stringify bits of inline assembly */ +#define RM_XSTR(x) #x +#define RM_STR(x) RM_XSTR(x) + +/* Drag in the selected real-mode transition library header */ +#ifdef KEEP_IT_REAL +#include "libkir.h" +#else +#include "librm.h" +#endif + +/* + * The API to some functions is identical between librm and libkir, so + * they are documented here, even though the prototypes are in librm.h + * and libkir.h. + * + */ + +/* + * Declaration of variables in .data16 + * + * To place a variable in the .data16 segment, declare it using the + * pattern: + * + * int __data16 ( foo ); + * #define foo __use_data16 ( foo ); + * + * extern uint32_t __data16 ( bar ); + * #define bar __use_data16 ( bar ); + * + * static long __data16 ( baz ) = 0xff000000UL; + * #define baz __use_data16 ( baz ); + * + * i.e. take a normal declaration, add __data16() around the variable + * name, and add a line saying "#define <name> __use_data16 ( <name> ) + * + * You can then access them just like any other variable, for example + * + * int x = foo + bar; + * + * This magic is achieved at a cost of only around 7 extra bytes per + * group of accesses to .data16 variables. When using KEEP_IT_REAL, + * there is no extra cost. + * + * You should place variables in .data16 when they need to be accessed + * by real-mode code. Real-mode assembly (e.g. as created by + * REAL_CODE()) can access these variables via the usual data segment. + * You can therefore write something like + * + * static uint16_t __data16 ( foo ); + * #define foo __use_data16 ( foo ) + * + * int bar ( void ) { + * __asm__ __volatile__ ( REAL_CODE ( "int $0xff\n\t" + * "movw %ax, foo" ) + * : : ); + * return foo; + * } + * + * Variables may also be placed in .text16 using __text16 and + * __use_text16. Some variables (e.g. chained interrupt vectors) fit + * most naturally in .text16; most should be in .data16. + * + * If you have only a pointer to a magic symbol within .data16 or + * .text16, rather than the symbol itself, you can attempt to extract + * the underlying symbol name using __from_data16() or + * __from_text16(). This is not for the faint-hearted; check the + * assembler output to make sure that it's doing the right thing. + */ + +/* + * void copy_to_real ( uint16_t dest_seg, uint16_t dest_off, + * void *src, size_t n ) + * void copy_from_real ( void *dest, uint16_t src_seg, uint16_t src_off, + * size_t n ) + * + * These functions can be used to copy data to and from arbitrary + * locations in base memory. + */ + +/* + * put_real ( variable, uint16_t dest_seg, uint16_t dest_off ) + * get_real ( variable, uint16_t src_seg, uint16_t src_off ) + * + * These macros can be used to read or write single variables to and + * from arbitrary locations in base memory. "variable" must be a + * variable of either 1, 2 or 4 bytes in length. + */ + +/* + * REAL_CODE ( asm_code_str ) + * + * This can be used in inline assembly to create a fragment of code + * that will execute in real mode. For example: to write a character + * to the BIOS console using INT 10, you would do something like: + * + * __asm__ __volatile__ ( REAL_CODE ( "int $0x16" ) + * : "=a" ( character ) : "a" ( 0x0000 ) ); + * + */ + +#endif /* ASSEMBLY */ + +#endif /* REALMODE_H */ diff --git a/gpxe/src/arch/i386/include/registers.h b/gpxe/src/arch/i386/include/registers.h new file mode 100644 index 00000000..2b9b2b43 --- /dev/null +++ b/gpxe/src/arch/i386/include/registers.h @@ -0,0 +1,187 @@ +#ifndef REGISTERS_H +#define REGISTERS_H + +/** @file + * + * i386 registers. + * + * This file defines data structures that allow easy access to i386 + * register dumps. + * + */ + +#include "compiler.h" /* for doxygen */ +#include "stdint.h" + +/** + * A 16-bit general register. + * + * This type encapsulates a 16-bit register such as %ax, %bx, %cx, + * %dx, %si, %di, %bp or %sp. + * + */ +typedef union { + struct { + union { + uint8_t l; + uint8_t byte; + }; + uint8_t h; + } PACKED; + uint16_t word; +} PACKED reg16_t; + +/** + * A 32-bit general register. + * + * This type encapsulates a 32-bit register such as %eax, %ebx, %ecx, + * %edx, %esi, %edi, %ebp or %esp. + * + */ +typedef union { + struct { + union { + uint8_t l; + uint8_t byte; + }; + uint8_t h; + } PACKED; + uint16_t word; + uint32_t dword; +} PACKED reg32_t; + +/** + * A 32-bit general register dump. + * + * This is the data structure that is created on the stack by the @c + * pushal instruction, and can be read back using the @c popal + * instruction. + * + */ +struct i386_regs { + union { + uint16_t di; + uint32_t edi; + }; + union { + uint16_t si; + uint32_t esi; + }; + union { + uint16_t bp; + uint32_t ebp; + }; + union { + uint16_t sp; + uint32_t esp; + }; + union { + struct { + uint8_t bl; + uint8_t bh; + } PACKED; + uint16_t bx; + uint32_t ebx; + }; + union { + struct { + uint8_t dl; + uint8_t dh; + } PACKED; + uint16_t dx; + uint32_t edx; + }; + union { + struct { + uint8_t cl; + uint8_t ch; + } PACKED; + uint16_t cx; + uint32_t ecx; + }; + union { + struct { + uint8_t al; + uint8_t ah; + } PACKED; + uint16_t ax; + uint32_t eax; + }; +} PACKED; + +/** + * A segment register dump. + * + * The i386 has no equivalent of the @c pushal or @c popal + * instructions for the segment registers. We adopt the convention of + * always using the sequences + * + * @code + * + * pushw %gs ; pushw %fs ; pushw %es ; pushw %ds ; pushw %ss ; pushw %cs + * + * @endcode + * + * and + * + * @code + * + * addw $4, %sp ; popw %ds ; popw %es ; popw %fs ; popw %gs + * + * @endcode + * + * This is the data structure that is created and read back by these + * instruction sequences. + * + */ +struct i386_seg_regs { + uint16_t cs; + uint16_t ss; + uint16_t ds; + uint16_t es; + uint16_t fs; + uint16_t gs; +} PACKED; + +/** + * A full register dump. + * + * This data structure is created by the instructions + * + * @code + * + * pushfl + * pushal + * pushw %gs ; pushw %fs ; pushw %es ; pushw %ds ; pushw %ss ; pushw %cs + * + * @endcode + * + * and can be read back using the instructions + * + * @code + * + * addw $4, %sp ; popw %ds ; popw %es ; popw %fs ; popw %gs + * popal + * popfl + * + * @endcode + * + * prot_call() and kir_call() create this data structure on the stack + * and pass in a pointer to this structure. + * + */ +struct i386_all_regs { + struct i386_seg_regs segs; + struct i386_regs regs; + uint32_t flags; +} PACKED; + +/* Flags */ +#define CF ( 1 << 0 ) +#define PF ( 1 << 2 ) +#define AF ( 1 << 4 ) +#define ZF ( 1 << 6 ) +#define SF ( 1 << 7 ) +#define OF ( 1 << 11 ) + +#endif /* REGISTERS_H */ diff --git a/gpxe/src/arch/i386/include/setjmp.h b/gpxe/src/arch/i386/include/setjmp.h new file mode 100644 index 00000000..ed2be270 --- /dev/null +++ b/gpxe/src/arch/i386/include/setjmp.h @@ -0,0 +1,12 @@ +#ifndef ETHERBOOT_SETJMP_H +#define ETHERBOOT_SETJMP_H + + +/* Define a type for use by setjmp and longjmp */ +#define JBLEN 6 +typedef unsigned long jmp_buf[JBLEN]; + +extern int setjmp (jmp_buf env); +extern void longjmp (jmp_buf env, int val); + +#endif /* ETHERBOOT_SETJMP_H */ diff --git a/gpxe/src/arch/i386/include/smbios.h b/gpxe/src/arch/i386/include/smbios.h new file mode 100644 index 00000000..821eda17 --- /dev/null +++ b/gpxe/src/arch/i386/include/smbios.h @@ -0,0 +1,51 @@ +#ifndef _SMBIOS_H +#define _SMBIOS_H + +/** @file + * + * System Management BIOS + */ + +#include <stdint.h> + +/** An SMBIOS structure header */ +struct smbios_header { + /** Type */ + uint8_t type; + /** Length */ + uint8_t length; + /** Handle */ + uint16_t handle; +} __attribute__ (( packed )); + +/** SMBIOS system information structure */ +struct smbios_system_information { + /** SMBIOS structure header */ + struct smbios_header header; + /** Manufacturer string */ + uint8_t manufacturer; + /** Product string */ + uint8_t product; + /** Version string */ + uint8_t version; + /** Serial number string */ + uint8_t serial; + /** UUID */ + uint8_t uuid[16]; + /** Wake-up type */ + uint8_t wakeup; +} __attribute__ (( packed )); + +/** SMBIOS system information structure type */ +#define SMBIOS_TYPE_SYSTEM_INFORMATION 1 + +struct smbios_strings; +extern int find_smbios_structure ( unsigned int type, + void *structure, size_t length, + struct smbios_strings *strings ); +extern int find_smbios_string ( struct smbios_strings *strings, + unsigned int index, + char *buffer, size_t length ); +extern int smbios_get_uuid ( union uuid *uuid ); + +#endif /* _SMBIOS_H */ diff --git a/gpxe/src/arch/i386/include/undi.h b/gpxe/src/arch/i386/include/undi.h new file mode 100644 index 00000000..9936e17f --- /dev/null +++ b/gpxe/src/arch/i386/include/undi.h @@ -0,0 +1,102 @@ +#ifndef _UNDI_H +#define _UNDI_H + +/** @file + * + * UNDI driver + * + */ + +#ifndef ASSEMBLY + +#include <gpxe/device.h> +#include <pxe_types.h> + +/** An UNDI device + * + * This structure is used by assembly code as well as C; do not alter + * this structure without editing pxeprefix.S to match. + */ +struct undi_device { + /** PXENV+ structure address */ + SEGOFF16_t pxenv; + /** !PXE structure address */ + SEGOFF16_t ppxe; + /** Entry point */ + SEGOFF16_t entry; + /** Return stack */ + UINT16_t return_stack[3]; + /** Return type */ + UINT16_t return_type; + /** Free base memory after load */ + UINT16_t fbms; + /** Free base memory prior to load */ + UINT16_t restore_fbms; + /** PCI bus:dev.fn, or @c UNDI_NO_PCI_BUSDEVFN */ + UINT16_t pci_busdevfn; + /** ISAPnP card select number, or @c UNDI_NO_ISAPNP_CSN */ + UINT16_t isapnp_csn; + /** ISAPnP read port, or @c UNDI_NO_ISAPNP_READ_PORT */ + UINT16_t isapnp_read_port; + /** PCI vendor ID + * + * Filled in only for the preloaded UNDI device by pxeprefix.S + */ + UINT16_t pci_vendor; + /** PCI device ID + * + * Filled in only for the preloaded UNDI device by pxeprefix.S + */ + UINT16_t pci_device; + /** Flags + * + * This is the bitwise OR of zero or more UNDI_FL_XXX + * constants. + */ + UINT16_t flags; + + /** Generic device */ + struct device dev; + /** Driver-private data + * + * Use undi_set_drvdata() and undi_get_drvdata() to access this + * field. + */ + void *priv; +} __attribute__ (( packed )); + +/** + * Set UNDI driver-private data + * + * @v undi UNDI device + * @v priv Private data + */ +static inline void undi_set_drvdata ( struct undi_device *undi, void *priv ) { + undi->priv = priv; +} + +/** + * Get UNDI driver-private data + * + * @v undi UNDI device + * @ret priv Private data + */ +static inline void * undi_get_drvdata ( struct undi_device *undi ) { + return undi->priv; +} + +#endif /* ASSEMBLY */ + +/** PCI bus:dev.fn field is invalid */ +#define UNDI_NO_PCI_BUSDEVFN 0xffff + +/** ISAPnP card select number field is invalid */ +#define UNDI_NO_ISAPNP_CSN 0xffff + +/** ISAPnP read port field is invalid */ +#define UNDI_NO_ISAPNP_READ_PORT 0xffff + +/** UNDI flag: START_UNDI has been called */ +#define UNDI_FL_STARTED 0x0001 + +#endif /* _UNDI_H */ diff --git a/gpxe/src/arch/i386/include/undiload.h b/gpxe/src/arch/i386/include/undiload.h new file mode 100644 index 00000000..bfc11874 --- /dev/null +++ b/gpxe/src/arch/i386/include/undiload.h @@ -0,0 +1,33 @@ +#ifndef _UNDILOAD_H +#define _UNDILOAD_H + +/** @file + * + * UNDI load/unload + * + */ + +struct undi_device; +struct undi_rom; + +extern int undi_load ( struct undi_device *undi, struct undi_rom *undirom ); +extern int undi_unload ( struct undi_device *undi ); + +/** + * Call UNDI loader to create a pixie + * + * @v undi UNDI device + * @v undirom UNDI ROM + * @v pci_busdevfn PCI bus:dev.fn + * @ret rc Return status code + */ +static inline int undi_load_pci ( struct undi_device *undi, + struct undi_rom *undirom, + unsigned int pci_busdevfn ) { + undi->pci_busdevfn = pci_busdevfn; + undi->isapnp_csn = UNDI_NO_ISAPNP_CSN; + undi->isapnp_read_port = UNDI_NO_ISAPNP_READ_PORT; + return undi_load ( undi, undirom ); +} + +#endif /* _UNDILOAD_H */ diff --git a/gpxe/src/arch/i386/include/undinet.h b/gpxe/src/arch/i386/include/undinet.h new file mode 100644 index 00000000..1a4a385e --- /dev/null +++ b/gpxe/src/arch/i386/include/undinet.h @@ -0,0 +1,15 @@ +#ifndef _UNDINET_H +#define _UNDINET_H + +/** @file + * + * UNDI network device driver + * + */ + +struct undi_device; + +extern int undinet_probe ( struct undi_device *undi ); +extern void undinet_remove ( struct undi_device *undi ); + +#endif /* _UNDINET_H */ diff --git a/gpxe/src/arch/i386/include/undipreload.h b/gpxe/src/arch/i386/include/undipreload.h new file mode 100644 index 00000000..d9bc8cb9 --- /dev/null +++ b/gpxe/src/arch/i386/include/undipreload.h @@ -0,0 +1,16 @@ +#ifndef _UNDIPRELOAD_H +#define _UNDIPRELOAD_H + +/** @file + * + * Preloaded UNDI stack + * + */ + +#include <realmode.h> +#include <undi.h> + +extern struct undi_device __data16 ( preloaded_undi ); +#define preloaded_undi __use_data16 ( preloaded_undi ) + +#endif /* _UNDIPRELOAD_H */ diff --git a/gpxe/src/arch/i386/include/undirom.h b/gpxe/src/arch/i386/include/undirom.h new file mode 100644 index 00000000..a2636007 --- /dev/null +++ b/gpxe/src/arch/i386/include/undirom.h @@ -0,0 +1,51 @@ +#ifndef _UNDIROM_H +#define _UNDIROM_H + +/** @file + * + * UNDI expansion ROMs + * + */ + +#include <pxe_types.h> + +/** An UNDI PCI device ID */ +struct undi_pci_device_id { + /** PCI vendor ID */ + unsigned int vendor_id; + /** PCI device ID */ + unsigned int device_id; +}; + +/** An UNDI device ID */ +union undi_device_id { + /** PCI device ID */ + struct undi_pci_device_id pci; +}; + +/** An UNDI ROM */ +struct undi_rom { + /** List of UNDI ROMs */ + struct list_head list; + /** ROM segment address */ + unsigned int rom_segment; + /** UNDI loader entry point */ + SEGOFF16_t loader_entry; + /** Code segment size */ + size_t code_size; + /** Data segment size */ + size_t data_size; + /** Bus type + * + * Values are as used by @c PXENV_UNDI_GET_NIC_TYPE + */ + unsigned int bus_type; + /** Device ID */ + union undi_device_id bus_id; +}; + +extern struct undi_rom * undirom_find_pci ( unsigned int vendor_id, + unsigned int device_id, + unsigned int rombase ); + +#endif /* _UNDIROM_H */ diff --git a/gpxe/src/arch/i386/include/vga.h b/gpxe/src/arch/i386/include/vga.h new file mode 100644 index 00000000..01fc39d8 --- /dev/null +++ b/gpxe/src/arch/i386/include/vga.h @@ -0,0 +1,228 @@ +/* + * + * modified + * by Steve M. Gehlbach <steve@kesa.com> + * + * Originally from linux/drivers/video/vga16.c by + * Ben Pfaff <pfaffben@debian.org> and Petr Vandrovec <VANDROVE@vc.cvut.cz> + * Copyright 1999 Ben Pfaff <pfaffben@debian.org> and Petr Vandrovec <VANDROVE@vc.cvut.cz> + * Based on VGA info at http://www.goodnet.com/~tinara/FreeVGA/home.htm + * Based on VESA framebuffer (c) 1998 Gerd Knorr <kraxel@goldbach.in-berlin.de> + * + */ + +#ifndef VGA_H_INCL +#define VGA_H_INCL 1 + +//#include <cpu/p5/io.h> + +#define u8 unsigned char +#define u16 unsigned short +#define u32 unsigned int +#define __u32 u32 + +#define VERROR -1 +#define CHAR_HEIGHT 16 +#define LINES 25 +#define COLS 80 + +// macros for writing to vga regs +#define write_crtc(data,addr) outb(addr,CRT_IC); outb(data,CRT_DC) +#define write_att(data,addr) inb(IS1_RC); inb(0x80); outb(addr,ATT_IW); inb(0x80); outb(data,ATT_IW); inb(0x80) +#define write_seq(data,addr) outb(addr,SEQ_I); outb(data,SEQ_D) +#define write_gra(data,addr) outb(addr,GRA_I); outb(data,GRA_D) +u8 read_seq_b(u16 addr); +u8 read_gra_b(u16 addr); +u8 read_crtc_b(u16 addr); +u8 read_att_b(u16 addr); + + +#ifdef VGA_HARDWARE_FIXUP +void vga_hardware_fixup(void); +#else +#define vga_hardware_fixup() do{} while(0) +#endif + +#define SYNC_HOR_HIGH_ACT 1 /* horizontal sync high active */ +#define SYNC_VERT_HIGH_ACT 2 /* vertical sync high active */ +#define SYNC_EXT 4 /* external sync */ +#define SYNC_COMP_HIGH_ACT 8 /* composite sync high active */ +#define SYNC_BROADCAST 16 /* broadcast video timings */ + /* vtotal = 144d/288n/576i => PAL */ + /* vtotal = 121d/242n/484i => NTSC */ + +#define SYNC_ON_GREEN 32 /* sync on green */ + +#define VMODE_NONINTERLACED 0 /* non interlaced */ +#define VMODE_INTERLACED 1 /* interlaced */ +#define VMODE_DOUBLE 2 /* double scan */ +#define VMODE_MASK 255 + +#define VMODE_YWRAP 256 /* ywrap instead of panning */ +#define VMODE_SMOOTH_XPAN 512 /* smooth xpan possible (internally used) */ +#define VMODE_CONUPDATE 512 /* don't update x/yoffset */ + +/* VGA data register ports */ +#define CRT_DC 0x3D5 /* CRT Controller Data Register - color emulation */ +#define CRT_DM 0x3B5 /* CRT Controller Data Register - mono emulation */ +#define ATT_R 0x3C1 /* Attribute Controller Data Read Register */ +#define GRA_D 0x3CF /* Graphics Controller Data Register */ +#define SEQ_D 0x3C5 /* Sequencer Data Register */ + +#define MIS_R 0x3CC // Misc Output Read Register +#define MIS_W 0x3C2 // Misc Output Write Register + +#define IS1_RC 0x3DA /* Input Status Register 1 - color emulation */ +#define IS1_RM 0x3BA /* Input Status Register 1 - mono emulation */ +#define PEL_D 0x3C9 /* PEL Data Register */ +#define PEL_MSK 0x3C6 /* PEL mask register */ + +/* EGA-specific registers */ +#define GRA_E0 0x3CC /* Graphics enable processor 0 */ +#define GRA_E1 0x3CA /* Graphics enable processor 1 */ + + +/* VGA index register ports */ +#define CRT_IC 0x3D4 /* CRT Controller Index - color emulation */ +#define CRT_IM 0x3B4 /* CRT Controller Index - mono emulation */ +#define ATT_IW 0x3C0 /* Attribute Controller Index & Data Write Register */ +#define GRA_I 0x3CE /* Graphics Controller Index */ +#define SEQ_I 0x3C4 /* Sequencer Index */ +#define PEL_IW 0x3C8 /* PEL Write Index */ +#define PEL_IR 0x3C7 /* PEL Read Index */ + +/* standard VGA indexes max counts */ +#define CRTC_C 25 /* 25 CRT Controller Registers sequentially set*/ + // the remainder are not in the par array +#define ATT_C 21 /* 21 Attribute Controller Registers */ +#define GRA_C 9 /* 9 Graphics Controller Registers */ +#define SEQ_C 5 /* 5 Sequencer Registers */ +#define MIS_C 1 /* 1 Misc Output Register */ + +#define CRTC_H_TOTAL 0 +#define CRTC_H_DISP 1 +#define CRTC_H_BLANK_START 2 +#define CRTC_H_BLANK_END 3 +#define CRTC_H_SYNC_START 4 +#define CRTC_H_SYNC_END 5 +#define CRTC_V_TOTAL 6 +#define CRTC_OVERFLOW 7 +#define CRTC_PRESET_ROW 8 +#define CRTC_MAX_SCAN 9 +#define CRTC_CURSOR_START 0x0A +#define CRTC_CURSOR_END 0x0B +#define CRTC_START_HI 0x0C +#define CRTC_START_LO 0x0D +#define CRTC_CURSOR_HI 0x0E +#define CRTC_CURSOR_LO 0x0F +#define CRTC_V_SYNC_START 0x10 +#define CRTC_V_SYNC_END 0x11 +#define CRTC_V_DISP_END 0x12 +#define CRTC_OFFSET 0x13 +#define CRTC_UNDERLINE 0x14 +#define CRTC_V_BLANK_START 0x15 +#define CRTC_V_BLANK_END 0x16 +#define CRTC_MODE 0x17 +#define CRTC_LINE_COMPARE 0x18 + +#define ATC_MODE 0x10 +#define ATC_OVERSCAN 0x11 +#define ATC_PLANE_ENABLE 0x12 +#define ATC_PEL 0x13 +#define ATC_COLOR_PAGE 0x14 + +#define SEQ_CLOCK_MODE 0x01 +#define SEQ_PLANE_WRITE 0x02 +#define SEQ_CHARACTER_MAP 0x03 +#define SEQ_MEMORY_MODE 0x04 + +#define GDC_SR_VALUE 0x00 +#define GDC_SR_ENABLE 0x01 +#define GDC_COMPARE_VALUE 0x02 +#define GDC_DATA_ROTATE 0x03 +#define GDC_PLANE_READ 0x04 +#define GDC_MODE 0x05 +#define GDC_MISC 0x06 +#define GDC_COMPARE_MASK 0x07 +#define GDC_BIT_MASK 0x08 + +// text attributes +#define VGA_ATTR_CLR_RED 0x4 +#define VGA_ATTR_CLR_GRN 0x2 +#define VGA_ATTR_CLR_BLU 0x1 +#define VGA_ATTR_CLR_YEL (VGA_ATTR_CLR_RED | VGA_ATTR_CLR_GRN) +#define VGA_ATTR_CLR_CYN (VGA_ATTR_CLR_GRN | VGA_ATTR_CLR_BLU) +#define VGA_ATTR_CLR_MAG (VGA_ATTR_CLR_BLU | VGA_ATTR_CLR_RED) +#define VGA_ATTR_CLR_BLK 0 +#define VGA_ATTR_CLR_WHT (VGA_ATTR_CLR_RED | VGA_ATTR_CLR_GRN | VGA_ATTR_CLR_BLU) +#define VGA_ATTR_BNK 0x80 +#define VGA_ATTR_ITN 0x08 + +/* + * vga register parameters + * these are copied to the + * registers. + * + */ +struct vga_par { + u8 crtc[CRTC_C]; + u8 atc[ATT_C]; + u8 gdc[GRA_C]; + u8 seq[SEQ_C]; + u8 misc; // the misc register, MIS_W + u8 vss; +}; + + +/* Interpretation of offset for color fields: All offsets are from the right, + * inside a "pixel" value, which is exactly 'bits_per_pixel' wide (means: you + * can use the offset as right argument to <<). A pixel afterwards is a bit + * stream and is written to video memory as that unmodified. This implies + * big-endian byte order if bits_per_pixel is greater than 8. + */ +struct fb_bitfield { + __u32 offset; /* beginning of bitfield */ + __u32 length; /* length of bitfield */ + __u32 msb_right; /* != 0 : Most significant bit is */ + /* right */ +}; + +struct screeninfo { + __u32 xres; /* visible resolution */ + __u32 yres; + __u32 xres_virtual; /* virtual resolution */ + __u32 yres_virtual; + __u32 xoffset; /* offset from virtual to visible */ + __u32 yoffset; /* resolution */ + + __u32 bits_per_pixel; /* guess what */ + __u32 grayscale; /* != 0 Graylevels instead of colors */ + + struct fb_bitfield red; /* bitfield in fb mem if true color, */ + struct fb_bitfield green; /* else only length is significant */ + struct fb_bitfield blue; + struct fb_bitfield transp; /* transparency */ + + __u32 nonstd; /* != 0 Non standard pixel format */ + + __u32 activate; /* see FB_ACTIVATE_* */ + + __u32 height; /* height of picture in mm */ + __u32 width; /* width of picture in mm */ + + __u32 accel_flags; /* acceleration flags (hints) */ + + /* Timing: All values in pixclocks, except pixclock (of course) */ + __u32 pixclock; /* pixel clock in ps (pico seconds) */ + __u32 left_margin; /* time from sync to picture */ + __u32 right_margin; /* time from picture to sync */ + __u32 upper_margin; /* time from sync to picture */ + __u32 lower_margin; + __u32 hsync_len; /* length of horizontal sync */ + __u32 vsync_len; /* length of vertical sync */ + __u32 sync; /* sync polarity */ + __u32 vmode; /* interlaced etc */ + __u32 reserved[6]; /* Reserved for future compatibility */ +}; + +#endif diff --git a/gpxe/src/arch/i386/include/virtaddr.h b/gpxe/src/arch/i386/include/virtaddr.h new file mode 100644 index 00000000..f2ffa2a1 --- /dev/null +++ b/gpxe/src/arch/i386/include/virtaddr.h @@ -0,0 +1,105 @@ +#ifndef VIRTADDR_H +#define VIRTADDR_H + +/* Segment selectors as used in our protected-mode GDTs. + * + * Don't change these unless you really know what you're doing. + */ + +#define VIRTUAL_CS 0x08 +#define VIRTUAL_DS 0x10 +#define PHYSICAL_CS 0x18 +#define PHYSICAL_DS 0x20 +#define REAL_CS 0x28 +#define REAL_DS 0x30 +#if 0 +#define LONG_CS 0x38 +#define LONG_DS 0x40 +#endif + +#ifndef ASSEMBLY + +#include "stdint.h" +#include "string.h" + +#ifndef KEEP_IT_REAL + +/* + * Without -DKEEP_IT_REAL, we are in 32-bit protected mode with a + * fixed link address but an unknown physical start address. Our GDT + * sets up code and data segments with an offset of virt_offset, so + * that link-time addresses can still work. + * + */ + +/* C-callable function prototypes */ + +extern void relocate_to ( uint32_t new_phys_addr ); + +/* Variables in virtaddr.S */ +extern unsigned long virt_offset; + +/* + * Convert between virtual and physical addresses + * + */ +static inline unsigned long virt_to_phys ( volatile const void *virt_addr ) { + return ( ( unsigned long ) virt_addr ) + virt_offset; +} + +static inline void * phys_to_virt ( unsigned long phys_addr ) { + return ( void * ) ( phys_addr - virt_offset ); +} + +static inline void copy_to_phys ( physaddr_t dest, const void *src, + size_t len ) { + memcpy ( phys_to_virt ( dest ), src, len ); +} + +static inline void copy_from_phys ( void *dest, physaddr_t src, size_t len ) { + memcpy ( dest, phys_to_virt ( src ), len ); +} + +static inline void copy_phys_to_phys ( physaddr_t dest, physaddr_t src, + size_t len ) { + memcpy ( phys_to_virt ( dest ), phys_to_virt ( src ), len ); +} + +#else /* KEEP_IT_REAL */ + +/* + * With -DKEEP_IT_REAL, we are in 16-bit real mode with fixed link + * addresses and a segmented memory model. We have separate code and + * data segments. + * + * Because we may be called in 16-bit protected mode (damn PXE spec), + * we cannot simply assume that physical = segment * 16 + offset. + * Instead, we have to look up the physical start address of the + * segment in the !PXE structure. We have to assume that + * virt_to_phys() is called only on pointers within the data segment, + * because nothing passes segment information to us. + * + * We don't implement phys_to_virt at all, because there will be many + * addresses that simply cannot be reached via a virtual address when + * the virtual address space is limited to 64kB! + */ + +static inline unsigned long virt_to_phys ( volatile const void *virt_addr ) { + /* Cheat: just for now, do the segment*16+offset calculation */ + uint16_t ds; + + __asm__ ( "movw %%ds, %%ax" : "=a" ( ds ) : ); + return ( 16 * ds + ( ( unsigned long ) virt_addr ) ); +} + +/* Define it as a deprecated function so that we get compile-time + * warnings, rather than just the link-time errors. + */ +extern void * phys_to_virt ( unsigned long phys_addr ) + __attribute__ ((deprecated)); + +#endif /* KEEP_IT_REAL */ + +#endif /* ASSEMBLY */ + +#endif /* VIRTADDR_H */ diff --git a/gpxe/src/arch/i386/interface/pcbios/biosint.c b/gpxe/src/arch/i386/interface/pcbios/biosint.c new file mode 100644 index 00000000..8ef2d7ab --- /dev/null +++ b/gpxe/src/arch/i386/interface/pcbios/biosint.c @@ -0,0 +1,101 @@ +#include <errno.h> +#include <realmode.h> +#include <biosint.h> + +/** + * @file BIOS interrupts + * + */ + +/** + * Hooked interrupt count + * + * At exit, after unhooking all possible interrupts, this counter + * should be examined. If it is non-zero, it means that we failed to + * unhook at least one interrupt vector, and so must not free up the + * memory we are using. (Note that this also implies that we should + * re-hook INT 15 in order to hide ourselves from the memory map). + */ +int hooked_bios_interrupts = 0; + +/** + * Hook INT vector + * + * @v interrupt INT number + * @v handler Offset within .text16 to interrupt handler + * @v chain_vector Vector for chaining to previous handler + * + * Hooks in an i386 INT handler. The handler itself must reside + * within the .text16 segment. @c chain_vector will be filled in with + * the address of the previously-installed handler for this interrupt; + * the handler should probably exit by ljmping via this vector. + */ +void hook_bios_interrupt ( unsigned int interrupt, unsigned int handler, + struct segoff *chain_vector ) { + struct segoff vector = { + .segment = rm_cs, + .offset = handler, + }; + + DBG ( "Hooking INT %#02x to %04x:%04x\n", + interrupt, rm_cs, handler ); + + if ( ( chain_vector->segment != 0 ) || + ( chain_vector->offset != 0 ) ) { + /* Already hooked; do nothing */ + DBG ( "...already hooked\n" ); + return; + } + + copy_from_real ( chain_vector, 0, ( interrupt * 4 ), + sizeof ( *chain_vector ) ); + DBG ( "...chaining to %04x:%04x\n", + chain_vector->segment, chain_vector->offset ); + if ( DBG_LOG ) { + char code[64]; + copy_from_real ( code, chain_vector->segment, + chain_vector->offset, sizeof ( code ) ); + DBG_HDA ( *chain_vector, code, sizeof ( code ) ); + } + + copy_to_real ( 0, ( interrupt * 4 ), &vector, sizeof ( vector ) ); + hooked_bios_interrupts++; +} + +/** + * Unhook INT vector + * + * @v interrupt INT number + * @v handler Offset within .text16 to interrupt handler + * @v chain_vector Vector containing address of previous handler + * + * Unhooks an i386 interrupt handler hooked by hook_i386_vector(). + * Note that this operation may fail, if some external code has hooked + * the vector since we hooked in our handler. If it fails, it means + * that it is not possible to unhook our handler, and we must leave it + * (and its chaining vector) resident in memory. + */ +int unhook_bios_interrupt ( unsigned int interrupt, unsigned int handler, + struct segoff *chain_vector ) { + struct segoff vector; + + DBG ( "Unhooking INT %#02x from %04x:%04x\n", + interrupt, rm_cs, handler ); + + copy_from_real ( &vector, 0, ( interrupt * 4 ), sizeof ( vector ) ); + if ( ( vector.segment != rm_cs ) || ( vector.offset != handler ) ) { + DBG ( "...cannot unhook; vector points to %04x:%04x\n", + vector.segment, vector.offset ); + return -EBUSY; + } + + DBG ( "...restoring to %04x:%04x\n", + chain_vector->segment, chain_vector->offset ); + copy_to_real ( 0, ( interrupt * 4 ), chain_vector, + sizeof ( *chain_vector ) ); + + chain_vector->segment = 0; + chain_vector->offset = 0; + hooked_bios_interrupts--; + return 0; +} diff --git a/gpxe/src/arch/i386/interface/pcbios/int13.c b/gpxe/src/arch/i386/interface/pcbios/int13.c new file mode 100644 index 00000000..7e09fb5f --- /dev/null +++ b/gpxe/src/arch/i386/interface/pcbios/int13.c @@ -0,0 +1,653 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <limits.h> +#include <byteswap.h> +#include <errno.h> +#include <assert.h> +#include <gpxe/list.h> +#include <gpxe/blockdev.h> +#include <gpxe/memmap.h> +#include <realmode.h> +#include <bios.h> +#include <biosint.h> +#include <bootsector.h> +#include <int13.h> + +/** @file + * + * INT 13 emulation + * + * This module provides a mechanism for exporting block devices via + * the BIOS INT 13 disk interrupt interface. + * + */ + +/** Vector for chaining to other INT 13 handlers */ +static struct segoff __text16 ( int13_vector ); +#define int13_vector __use_text16 ( int13_vector ) + +/** Assembly wrapper */ +extern void int13_wrapper ( void ); + +/** List of registered emulated drives */ +static LIST_HEAD ( drives ); + +/** + * INT 13, 00 - Reset disk system + * + * @v drive Emulated drive + * @ret status Status code + */ +static int int13_reset ( struct int13_drive *drive __unused, + struct i386_all_regs *ix86 __unused ) { + DBG ( "Reset drive\n" ); + return 0; +} + +/** + * INT 13, 01 - Get status of last operation + * + * @v drive Emulated drive + * @ret status Status code + */ +static int int13_get_last_status ( struct int13_drive *drive, + struct i386_all_regs *ix86 __unused ) { + DBG ( "Get status of last operation\n" ); + return drive->last_status; +} + +/** + * Read / write sectors + * + * @v drive Emulated drive + * @v al Number of sectors to read or write (must be nonzero) + * @v ch Low bits of cylinder number + * @v cl (bits 7:6) High bits of cylinder number + * @v cl (bits 5:0) Sector number + * @v dh Head number + * @v es:bx Data buffer + * @v io Read / write method + * @ret status Status code + * @ret al Number of sectors read or written + */ +static int int13_rw_sectors ( struct int13_drive *drive, + struct i386_all_regs *ix86, + int ( * io ) ( struct block_device *blockdev, + uint64_t block, + unsigned long count, + userptr_t buffer ) ) { + struct block_device *blockdev = drive->blockdev; + unsigned int cylinder, head, sector; + unsigned long lba; + unsigned int count; + userptr_t buffer; + + /* Validate blocksize */ + if ( blockdev->blksize != INT13_BLKSIZE ) { + DBG ( "Invalid blocksize (%zd) for non-extended read/write\n", + blockdev->blksize ); + return -INT13_STATUS_INVALID; + } + + /* Calculate parameters */ + cylinder = ( ( ( ix86->regs.cl & 0xc0 ) << 2 ) | ix86->regs.ch ); + assert ( cylinder < drive->cylinders ); + head = ix86->regs.dh; + assert ( head < drive->heads ); + sector = ( ix86->regs.cl & 0x3f ); + assert ( ( sector >= 1 ) && ( sector <= drive->sectors_per_track ) ); + lba = ( ( ( ( cylinder * drive->heads ) + head ) + * drive->sectors_per_track ) + sector - 1 ); + count = ix86->regs.al; + buffer = real_to_user ( ix86->segs.es, ix86->regs.bx ); + + DBG ( "C/H/S %d/%d/%d = LBA %#lx <-> %04x:%04x (count %d)\n", cylinder, + head, sector, lba, ix86->segs.es, ix86->regs.bx, count ); + + /* Read from / write to block device */ + if ( io ( blockdev, lba, count, buffer ) != 0 ) + return -INT13_STATUS_READ_ERROR; + + return 0; +} + +/** + * INT 13, 02 - Read sectors + * + * @v drive Emulated drive + * @v al Number of sectors to read (must be nonzero) + * @v ch Low bits of cylinder number + * @v cl (bits 7:6) High bits of cylinder number + * @v cl (bits 5:0) Sector number + * @v dh Head number + * @v es:bx Data buffer + * @ret status Status code + * @ret al Number of sectors read + */ +static int int13_read_sectors ( struct int13_drive *drive, + struct i386_all_regs *ix86 ) { + DBG ( "Read: " ); + return int13_rw_sectors ( drive, ix86, drive->blockdev->read ); +} + +/** + * INT 13, 03 - Write sectors + * + * @v drive Emulated drive + * @v al Number of sectors to write (must be nonzero) + * @v ch Low bits of cylinder number + * @v cl (bits 7:6) High bits of cylinder number + * @v cl (bits 5:0) Sector number + * @v dh Head number + * @v es:bx Data buffer + * @ret status Status code + * @ret al Number of sectors written + */ +static int int13_write_sectors ( struct int13_drive *drive, + struct i386_all_regs *ix86 ) { + DBG ( "Write: " ); + return int13_rw_sectors ( drive, ix86, drive->blockdev->write ); +} + +/** + * INT 13, 08 - Get drive parameters + * + * @v drive Emulated drive + * @ret status Status code + * @ret ch Low bits of maximum cylinder number + * @ret cl (bits 7:6) High bits of maximum cylinder number + * @ret cl (bits 5:0) Maximum sector number + * @ret dh Maximum head number + * @ret dl Number of drives + */ +static int int13_get_parameters ( struct int13_drive *drive, + struct i386_all_regs *ix86 ) { + unsigned int max_cylinder = drive->cylinders - 1; + unsigned int max_head = drive->heads - 1; + unsigned int max_sector = drive->sectors_per_track; /* sic */ + + DBG ( "Get drive parameters\n" ); + + ix86->regs.ch = ( max_cylinder & 0xff ); + ix86->regs.cl = ( ( ( max_cylinder >> 8 ) << 6 ) | max_sector ); + ix86->regs.dh = max_head; + get_real ( ix86->regs.dl, BDA_SEG, BDA_NUM_DRIVES ); + return 0; +} + +/** + * INT 13, 15 - Get disk type + * + * @v drive Emulated drive + * @ret ah Type code + * @ret cx:dx Sector count + * @ret status Status code / disk type + */ +static int int13_get_disk_type ( struct int13_drive *drive, + struct i386_all_regs *ix86 ) { + DBG ( "Get disk type\n" ); + ix86->regs.cx = ( drive->cylinders >> 16 ); + ix86->regs.dx = ( drive->cylinders & 0xffff ); + return INT13_DISK_TYPE_HDD; +} + +/** + * INT 13, 41 - Extensions installation check + * + * @v drive Emulated drive + * @v bx 0x55aa + * @ret bx 0xaa55 + * @ret cx Extensions API support bitmap + * @ret status Status code / API version + */ +static int int13_extension_check ( struct int13_drive *drive __unused, + struct i386_all_regs *ix86 ) { + if ( ix86->regs.bx == 0x55aa ) { + DBG ( "INT 13 extensions installation check\n" ); + ix86->regs.bx = 0xaa55; + ix86->regs.cx = INT13_EXTENSION_LINEAR; + return INT13_EXTENSION_VER_1_X; + } else { + return -INT13_STATUS_INVALID; + } +} + +/** + * Extended read / write + * + * @v drive Emulated drive + * @v ds:si Disk address packet + * @v io Read / write method + * @ret status Status code + */ +static int int13_extended_rw ( struct int13_drive *drive, + struct i386_all_regs *ix86, + int ( * io ) ( struct block_device *blockdev, + uint64_t block, + unsigned long count, + userptr_t buffer ) ) { + struct block_device *blockdev = drive->blockdev; + struct int13_disk_address addr; + uint64_t lba; + unsigned long count; + userptr_t buffer; + + /* Read parameters from disk address structure */ + copy_from_real ( &addr, ix86->segs.ds, ix86->regs.si, sizeof ( addr )); + lba = addr.lba; + count = addr.count; + buffer = real_to_user ( addr.buffer.segment, addr.buffer.offset ); + + DBG ( "LBA %#llx <-> %04x:%04x (count %ld)\n", (unsigned long long)lba, + addr.buffer.segment, addr.buffe |