diff options
author | H. Peter Anvin <hpa@zytor.com> | 2008-05-29 15:11:38 -0700 |
---|---|---|
committer | H. Peter Anvin <hpa@zytor.com> | 2008-05-29 15:11:38 -0700 |
commit | b536209dfb7bd50c37061735fe10d2c19a97d26d (patch) | |
tree | 9d8ca6882fc5d9721fb0efea1abfd6dc09886814 /core | |
parent | 3ec40a0119587f63411475c76c69f9db24c7598e (diff) | |
download | syslinux-b536209dfb7bd50c37061735fe10d2c19a97d26d.tar.gz |
Move files out of root into core, dos, and utils
Move source files out of the root directory; the root is a mess and
has become virtually unmaintainable. The Syslinux core now lives in
core/; the Linux and generic utilities has moved into utils/, and
copybs.com has moved into dos/; it had to go somewhere, and it seemed
as good a place as any.
Diffstat (limited to 'core')
55 files changed, 16600 insertions, 0 deletions
diff --git a/core/Makefile b/core/Makefile new file mode 100644 index 00000000..de39acad --- /dev/null +++ b/core/Makefile @@ -0,0 +1,174 @@ +## ----------------------------------------------------------------------- +## +## Copyright 1998-2008 H. Peter Anvin - All Rights Reserved +## +## 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, Inc., 53 Temple Place Ste 330, +## Boston MA 02111-1307, USA; either version 2 of the License, or +## (at your option) any later version; incorporated herein by reference. +## +## ----------------------------------------------------------------------- + +# +# Makefile for the SYSLINUX core +# + +# No builtin rules +MAKEFLAGS += -r +MAKE += -r + +CC = gcc + +TMPFILE = $(shell mktemp /tmp/gcc_ok.XXXXXX) +gcc_ok = $(shell tmpf=$(TMPFILE); if $(CC) $(1) dummy.c -o $$tmpf 2>/dev/null; \ + then echo '$(1)'; else echo '$(2)'; fi; rm -f $$tmpf) + +M32 := $(call gcc_ok,-m32,) $(call gcc_ok,-ffreestanding,) \ + $(call gcc_ok,-fno-stack-protector,) \ + $(call gcc_ok,-fno-top-level-reorder,$(call gcc_ok,-fno-unit-at-a-time)) + +LD = ld +LDFLAGS = -m elf_i386 +OBJCOPY = objcopy +OBJDUMP = objdump + +OPTFLAGS = -g -Os -march=i386 -falign-functions=0 -falign-jumps=0 -falign-loops=0 -fomit-frame-pointer +INCLUDES = +CFLAGS = $(M32) -mregparm=3 -DREGPARM=3 -W -Wall -msoft-float $(OPTFLAGS) $(INCLUDES) + +NASM = nasm +NASMOPT = -O9999 +NINCLUDE = + +PERL = perl + +VERSION := $(shell cat ../version) + +# _bin.c files required by both BTARGET and ITARGET installers +BINFILES = bootsect_bin.c ldlinux_bin.c \ + extlinux_bss_bin.c extlinux_sys_bin.c + +# syslinux.exe is BTARGET so as to not require everyone to have the +# mingw suite installed +BTARGET = kwdhash.gen \ + ldlinux.bss ldlinux.sys ldlinux.bin \ + pxelinux.0 isolinux.bin isolinux-debug.bin \ + extlinux.bin extlinux.bss extlinux.sys +ITARGET = + +# All primary source files for the main syslinux files +NASMSRC = $(wildcard *.asm) +NASMHDR = $(wildcard *.inc) +CSRC = $(wildcard *.c) +CHDR = $(wildcard *.h) +OTHERSRC = keywords +ALLSRC = $(NASMSRC) $(NASMHDR) $(CSRC) $(CHDR) $(OTHERSRC) + +# The DATE is set on the make command line when building binaries for +# official release. Otherwise, substitute a hex string that is pretty much +# guaranteed to be unique to be unique from build to build. +ifndef HEXDATE +HEXDATE := $(shell $(PERL) now.pl $(SRCS)) +endif +ifndef DATE +DATE := $(HEXDATE) +endif + +# +# NOTE: If you don't have the mingw compiler suite installed, you probably +# want to remove win32 from this list; otherwise you're going to get an +# error every time you try to build. +# + +all: all-local + +all-local: $(BTARGET) $(BINFILES) + +installer: installer-local + +installer-local: $(ITARGET) $(BINFILES) + +kwdhash.gen: keywords genhash.pl + $(PERL) genhash.pl < keywords > kwdhash.gen + +.PRECIOUS: %.elf + +# Standard rule for {isolinux,isolinux-debug}.bin +iso%.bin: iso%.elf + $(OBJCOPY) -O binary $< $@ + $(PERL) checksumiso.pl $@ + +# Standard rule for {ldlinux,pxelinux,extlinux}.bin +%.bin: %.elf + $(OBJCOPY) -O binary $< $@ + +%.o: %.asm kwdhash.gen ../version.gen + $(NASM) $(NASMOPT) -f elf -F stabs -DDATE_STR="'$(DATE)'" \ + -DHEXDATE="$(HEXDATE)" \ + -l $(@:.o=.lsr) -o $@ $< + +%.elf: %.o syslinux.ld + $(LD) $(LDFLAGS) -T syslinux.ld -M -o $@ $< > $(@:.elf=.map) + $(OBJDUMP) -h $@ > $(@:.elf=.sec) + $(PERL) lstadjust.pl $(@:.elf=.lsr) $(@:.elf=.sec) $(@:.elf=.lst) + +pxelinux.0: pxelinux.bin + cp -f $< $@ + +ldlinux.bss: ldlinux.bin + dd if=$< of=$@ bs=512 count=1 + +ldlinux.sys: ldlinux.bin + dd if=$< of=$@ bs=512 skip=1 + +extlinux.bss: extlinux.bin + dd if=$< of=$@ bs=512 count=1 + +extlinux.sys: extlinux.bin + dd if=$< of=$@ bs=512 skip=1 + +bootsect_bin.c: ldlinux.bss ../bin2c.pl + $(PERL) ../bin2c.pl syslinux_bootsect < $< > $@ + +ldlinux_bin.c: ldlinux.sys ../bin2c.pl + $(PERL) ../bin2c.pl syslinux_ldlinux < $< > $@ + +extlinux_bss_bin.c: extlinux.bss ../bin2c.pl + $(PERL) ../bin2c.pl extlinux_bootsect < $< > $@ + +extlinux_sys_bin.c: extlinux.sys ../bin2c.pl + $(PERL) ../bin2c.pl extlinux_image 512 < $< > $@ + +install: installer + +install-lib: installer + +install-all: install install-lib + +netinstall: installer + +tidy dist: + rm -f *.o *.elf *_bin.c stupid.* patch.offset + rm -f *.lsr *.lst *.map *.sec + rm -f $(OBSOLETE) + +clean: tidy + rm -f $(ITARGET) + +spotless: clean + rm -f $(BTARGET) .depend *.so.* + +.depend: + rm -f .depend + for csrc in $(CSRC) ; do $(CC) $(INCLUDE) -MM $$csrc >> .depend ; done + for nsrc in $(NASMSRC) ; do $(NASM) -DDEPEND $(NINCLUDE) -o `echo $$nsrc | sed -e 's/\.asm/\.o/'` -M $$nsrc >> .depend ; done + +local-depend: + rm -f .depend + $(MAKE) .depend + +depend: local-depend + +# Include dependencies file +include .depend diff --git a/core/abort.inc b/core/abort.inc new file mode 100644 index 00000000..0f443829 --- /dev/null +++ b/core/abort.inc @@ -0,0 +1,70 @@ +; ----------------------------------------------------------------------- +; +; Copyright 2005-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 53 Temple Place Ste 330, +; Boston MA 02111-1307, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; ----------------------------------------------------------------------- + +; +; abort.inc +; +; Code to terminate a kernel load +; + + section .text +; +; abort_check: let the user abort with <ESC> or <Ctrl-C> +; +abort_check: + call pollchar + jz .ret1 + pusha + call getchar + cmp al,27 ; <ESC> + je .kill + cmp al,3 ; <Ctrl-C> + je .kill +.ret2: popa +.ret1: ret + +.kill: mov si,aborted_msg + mov bx,enter_command + jmp abort_load_chain + +; +; abort_load: Called by various routines which wants to print a fatal +; error message and return to the command prompt. Since this +; may happen at just about any stage of the boot process, assume +; our state is messed up, and just reset the segment registers +; and the stack forcibly. +; +; SI = offset (in _text) of error message to print +; BX = future entry point (abort_load_chain) +; +abort_load: + mov bx,error_or_command +abort_load_chain: + RESET_STACK_AND_SEGS AX + call cwritestr ; Expects SI -> error msg + + ; Return to the command prompt + jmp bx + +; +; error_or_command: Execute ONERROR if appropriate, otherwise enter_command +; +error_or_command: + mov cx,[OnerrorLen] + and cx,cx + jnz on_error + jmp enter_command + + section .data +aborted_msg db ' aborted.', CR, LF, 0 + + section .text diff --git a/core/adv.inc b/core/adv.inc new file mode 100644 index 00000000..c5e8270c --- /dev/null +++ b/core/adv.inc @@ -0,0 +1,489 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 2007-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 51 Franklin St, Fifth Floor, +;; Boston MA 02110-1301, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; adv.inc +;; +;; The auxillary data vector and its routines +;; +;; The auxillary data vector is a 512-byte aligned block that on the +;; disk-based derivatives can be part of the syslinux file itself. It +;; exists in two copies; when written, both copies are written (with a +;; sync in between, if from the operating system.) The first two +;; dwords are magic number and inverse checksum, then follows the data +;; area as a tagged array similar to BOOTP/DHCP, finally a tail +;; signature. +;; +;; Note that unlike BOOTP/DHCP, zero terminates the chain, and FF +;; has no special meaning. +;; + +;; +;; List of ADV tags... +;; +ADV_BOOTONCE equ 1 + +;; +;; Other ADV data... +;; +ADV_MAGIC1 equ 0x5a2d2fa5 ; Head signature +ADV_MAGIC2 equ 0xa3041767 ; Total checksum +ADV_MAGIC3 equ 0xdd28bf64 ; Tail signature + +ADV_LEN equ 500 ; Data bytes + +adv_retries equ 6 ; Disk retries + + section .adv + ; Introduce the ADVs to valid but blank +adv0: +.head resd 1 +.csum resd 1 +.data resb ADV_LEN +.tail resd 1 +.end equ $ +adv1: +.head resd 1 +.csum resd 1 +.data resb ADV_LEN +.tail resd 1 +.end equ $ + section .text + + ; + ; This is called after config file parsing, so we know + ; the intended location of the ADV + ; +adv_init: + cmp byte [ADVDrive],-1 + jne adv_read + +;%if IS_SYSLINUX || IS_MDSLINUX || IS_EXTLINUX +%if IS_EXTLINUX ; Not yet implemented for the other derivatives + ; + ; Update pointers to default ADVs... + ; + mov bx,[LDLSectors] + shl bx,2 + mov ecx,[bsHidden] + mov eax,[bx+SectorPtrs-8] + mov edx,[bx+SectorPtrs-4] + add eax,ecx + add edx,ecx + mov [ADVSec0],eax + mov [ADVSec1],edx + mov al,[DriveNumber] + mov [ADVDrive],al +%endif + ; ** fall through to adv_verify ** + + ; + ; Initialize the ADV data structure in memory + ; +adv_verify: + cmp byte [ADVDrive],-1 ; No ADV configured, still? + je .reset ; Then unconditionally reset + + mov si,adv0 + call .check_adv + jz .ok ; Primary ADV okay + mov si,adv1 + call .check_adv + jz .adv1ok + + ; Neither ADV is usable; initialize to blank +.reset: + mov di,adv0 + mov eax,ADV_MAGIC1 + stosd + mov eax,ADV_MAGIC2 + stosd + xor eax,eax + mov cx,ADV_LEN/4 + rep stosd + mov eax,ADV_MAGIC3 + stosd + +.ok: + ret + + ; The primary ADV is bad, but the backup is OK +.adv1ok: + mov di,adv0 + mov cx,512/4 + rep movsd + ret + + + ; SI points to the putative ADV; unchanged by routine + ; ZF=1 on return if good +.check_adv: + push si + lodsd + cmp eax,ADV_MAGIC1 + jne .done ; ZF=0, i.e. bad + xor edx,edx + mov cx,ADV_LEN/4+1 ; Remaining dwords +.csum: + lodsd + add edx,eax + loop .csum + cmp edx,ADV_MAGIC2 + jne .done + lodsd + cmp eax,ADV_MAGIC3 +.done: + pop si + ret + +; +; adv_get: find an ADV string if present +; +; Input: DL = ADV ID +; Output: CX = byte count (zero on not found) +; SI = pointer to data +; DL = unchanged +; +; Assumes CS == DS. +; + +adv_get: + push ax + mov si,adv0.data + xor ax,ax ; Keep AH=0 at all times +.loop: + lodsb ; Read ID + cmp al,dl + je .found + and al,al + jz .end + lodsb ; Read length + add si,ax + cmp si,adv0.tail + jb .loop + jmp .end + +.found: + lodsb + mov cx,ax + add ax,si ; Make sure it fits + cmp ax,adv0.tail + jbe .ok +.end: + xor cx,cx +.ok: + pop ax + ret + +; +; adv_set: insert a string into the ADV in memory +; +; Input: DL = ADV ID +; FS:BX = input buffer +; CX = byte count (max = 255!) +; Output: CF=1 on error +; CX clobbered +; +; Assumes CS == DS == ES. +; +adv_set: + push ax + push si + push di + and ch,ch + jnz .overflow + + push cx + mov si,adv0.data + xor ax,ax +.loop: + lodsb + cmp al,dl + je .found + and al,al + jz .endz + lodsb + add si,ax + cmp si,adv0.tail + jb .loop + jmp .end + +.found: ; Found, need to delete old copy + lodsb + lea di,[si-2] + push di + add si,ax + mov cx,adv0.tail + sub cx,si + jb .nukeit + rep movsb ; Remove the old one + mov [di],ah ; Termination zero + pop si + jmp .loop +.nukeit: + pop si + jmp .end +.endz: + dec si +.end: + ; Now SI points to where we want to put our data + pop cx + mov di,si + jcxz .empty + add si,cx + cmp si,adv0.tail-2 + jae .overflow ; CF=0 + + mov si,bx + mov al,dl + stosb + mov al,cl + stosb + fs rep movsb + +.empty: + mov cx,adv0.tail + sub cx,di + xor ax,ax + rep stosb ; Zero-fill remainder + + clc +.done: + pop di + pop si + pop ax + ret +.overflow: + stc + jmp .done + +; +; adv_cleanup: checksum adv0 and copy to adv1 +; Assumes CS == DS == ES. +; +adv_cleanup: + pushad + mov si,adv0.data + mov cx,ADV_LEN/4 + xor edx,edx +.loop: + lodsd + add edx,eax + loop .loop + mov eax,ADV_MAGIC2 + sub eax,edx + lea di,[si+4] ; adv1 + mov si,adv0 + mov [si+4],eax ; Store checksum + mov cx,(ADV_LEN+12)/4 + rep movsd + popad + ret + +; +; adv_write: write the ADV to disk. +; +; Location is in memory variables. +; Assumes CS == DS == ES. +; +; Returns CF=1 if the ADV cannot be written. +; +adv_write: + cmp dword [ADVSec0],0 + je .bad + cmp dword [ADVSec1],0 + je .bad + cmp byte [ADVDrive],-1 + je .bad + + push ax + call adv_cleanup + mov ah,3 ; Write + call adv_read_write + pop ax + + clc + ret +.bad: ; No location for ADV set + stc + ret + +; +; adv_read: read the ADV from disk +; +; Location is in memory variables. +; Assumes CS == DS == ES. +; +adv_read: + push ax + mov ah,2 ; Read + call adv_read_write + call adv_verify + pop ax + ret + +; +; adv_read_write: disk I/O for the ADV +; +; On input, AH=2 for read, AH=3 for write. +; Assumes CS == DS == ES. +; +adv_read_write: + mov [ADVOp],ah + pushad + + mov dl,[ADVDrive] + and dl,dl + ; Floppies: can't trust INT 13h 08h, we better know + ; the geometry a priori, which means it better be our + ; boot device. Handle that later. + ; jns .floppy ; Floppy drive... urk + + mov ah,08h ; Get disk parameters + int 13h + jc .noparm + and ah,ah + jnz .noparm + shr dx,8 + inc dx + mov [ADVHeads],dx + and cx,3fh + mov [ADVSecPerTrack],cx + +.noparm: + ; Check for EDD + mov bx,55AAh + mov ah,41h ; EDD existence query + mov dl,[ADVDrive] + int 13h + mov si,.cbios + jc .noedd + mov si,.ebios +.noedd: + + mov eax,[ADVSec0] + mov bx,adv0 + call .doone + + mov eax,[ADVSec1] + mov bx,adv1 + call .doone + + popad + ret + +.doone: + xor edx,edx ; Zero-extend LBA + push si + jmp si + +.ebios: + mov cx,adv_retries +.eb_retry: + ; Form DAPA on stack + push edx + push eax + push es + push bx + push word 1 ; Sector count + push word 16 ; DAPA size + mov si,sp + pushad + mov dl,[ADVDrive] + mov ax,4080h + or ah,[ADVOp] + push ds + push ss + pop ds + int 13h + pop ds + popad + lea sp,[si+16] ; Remove DAPA + jc .eb_error + pop si + ret +.eb_error: + loop .eb_retry + stc + pop si + ret + +.cbios: + push edx + push eax + push bp + + movzx esi,word [ADVSecPerTrack] + movzx edi,word [ADVHeads] + ; + ; Dividing by sectors to get (track,sector): we may have + ; up to 2^18 tracks, so we need to use 32-bit arithmetric. + ; + div esi + xor cx,cx + xchg cx,dx ; CX <- sector index (0-based) + ; EDX <- 0 + ; eax = track # + div edi ; Convert track to head/cyl + + ; Watch out for overflow, we might be writing! + cmp eax,1023 + ja .cb_overflow + + ; + ; Now we have AX = cyl, DX = head, CX = sector (0-based), + ; BP = sectors to transfer, SI = bsSecPerTrack, + ; ES:BX = data target + ; + + shl ah,6 ; Because IBM was STOOPID + ; and thought 8 bits were enough + ; then thought 10 bits were enough... + inc cx ; Sector numbers are 1-based, sigh + or cl,ah + mov ch,al + mov dh,dl + mov dl,[ADVDrive] + xchg ax,bp ; Sector to transfer count + mov ah,[ADVOp] ; Operation + + mov bp,adv_retries +.cb_retry: + pushad + int 13h + popad + jc .cb_error + +.cb_done: + pop bp + pop eax + pop edx + ret + +.cb_error: + dec bp + jnz .cb_retry +.cb_overflow: + stc + jmp .cb_done + + section .data + align 4, db 0 +ADVSec0 dd 0 ; Not specified +ADVSec1 dd 0 ; Not specified +ADVDrive db -1 ; No ADV defined + + section .bss + alignb 4 +ADVSecPerTrack resw 1 +ADVHeads resw 1 +ADVOp resb 1 diff --git a/core/bcopy32.inc b/core/bcopy32.inc new file mode 100644 index 00000000..fd14409f --- /dev/null +++ b/core/bcopy32.inc @@ -0,0 +1,633 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; bcopy32.inc +;; +;; 32-bit bcopy routine for real mode +;; + +; +; 32-bit bcopy routine for real mode +; +; We enter protected mode, set up a flat 32-bit environment, run rep movsd +; and then exit. IMPORTANT: This code assumes cs == 0. +; +; This code is probably excessively anal-retentive in its handling of +; segments, but this stuff is painful enough as it is without having to rely +; on everything happening "as it ought to." +; +; NOTE: this code is relocated into low memory, just after the .earlybss +; segment, in order to support to "bcopy over self" operation. +; + + section .bcopy32 + align 8 +__bcopy_start: + + ; This is in the .text segment since it needs to be + ; contiguous with the rest of the bcopy stuff + +; GDT descriptor entry +%macro desc 1 +bcopy_gdt.%1: +PM_%1 equ bcopy_gdt.%1-bcopy_gdt +%endmacro + +bcopy_gdt: + dw bcopy_gdt_size-1 ; Null descriptor - contains GDT + dd bcopy_gdt ; pointer for LGDT instruction + dw 0 + + desc CS16 + dd 0000ffffh ; 08h Code segment, use16, readable, + dd 00009b00h ; present, dpl 0, cover 64K + desc DS16_4G + dd 0000ffffh ; 10h Data segment, use16, read/write, + dd 008f9300h ; present, dpl 0, cover all 4G + desc DS16_RM + dd 0000ffffh ; 18h Data segment, use16, read/write, + dd 00009300h ; present, dpl 0, cover 64K + ; The next two segments are used for COM32 only + desc CS32 + dd 0000ffffh ; 20h Code segment, use32, readable, + dd 00cf9b00h ; present, dpl 0, cover all 4G + desc DS32 + dd 0000ffffh ; 28h Data segment, use32, read/write, + dd 00cf9300h ; present, dpl 0, cover all 4G + + ; TSS segment to keep Intel VT happy. Intel VT is + ; unhappy about anything that doesn't smell like a + ; full-blown 32-bit OS. + desc TSS + dw 104-1, DummyTSS ; 30h 32-bit task state segment + dd 00008900h ; present, dpl 0, 104 bytes @DummyTSS + + ; 16-bit stack segment, which may have a different + ; base from DS16 (e.g. if we're booted from PXELINUX) + desc SS16 + dd 0000ffffh ; 38h Data segment, use16, read/write, + dd 00009300h ; present, dpl 0, cover 64K + +bcopy_gdt_size: equ $-bcopy_gdt + +; +; bcopy: +; 32-bit copy, overlap safe +; +; Inputs: +; ESI - source pointer (-1 means do bzero rather than bcopy) +; EDI - target pointer +; ECX - byte count +; DF - zero +; +; Outputs: +; ESI - first byte after source (garbage if ESI == -1 on entry) +; EDI - first byte after target +; +bcopy: pushad + push word pm_bcopy + call simple_pm_call + popad + add edi,ecx + add esi,ecx + ret + +; +; This routine is used to invoke a simple routine in 16-bit protected +; mode (with 32-bit DS and ES, and working 16-bit stack.) +; Note that all segment registers including CS, except possibly SS, +; are zero-based in the protected-mode routine. +; +; No interrupt thunking services are provided; interrupts are disabled +; for the duration of the routine. Don't run for too long at a time. +; +; Inputs: +; On stack - pm entrypoint +; EAX, EBP preserved until real-mode exit +; EBX, ECX, EDX, ESI and EDI passed to the called routine +; +; Outputs: +; EAX, EBP restored from real-mode entry +; All other registers as returned from called function +; PM entrypoint cleaned off stack +; +simple_pm_call: + push eax + push ebp + mov bp,sp + pushfd ; Saves, among others, the IF flag + push ds + push es + push fs + push gs + + cli + call enable_a20 + + mov byte [cs:bcopy_gdt.TSS+5],89h ; Mark TSS unbusy + + ; Convert the stack segment to a base + xor eax,eax + mov ax,ss + shl eax,4 + or eax,93000000h + mov [cs:bcopy_gdt.SS16+2],eax + + push ss ; Save real-mode SS selector + + o32 lgdt [cs:bcopy_gdt] + mov eax,cr0 + or al,1 + mov cr0,eax ; Enter protected mode + jmp PM_CS16:.in_pm +.in_pm: + mov ax,PM_SS16 ; Make stack usable + mov ss,ax + + mov al,PM_DS16_4G ; Data segment selector + mov es,ax + mov ds,ax + + ; Set fs, gs, tr, and ldtr in case we're on a virtual + ; machine running on Intel VT hardware -- it can't + ; deal with a partial transition, for no good reason. + + mov al,PM_DS16_RM ; Real-mode-like segment + mov fs,ax + mov gs,ax + mov al,PM_TSS ; Intel VT really doesn't want + ltr ax ; an invalid TR and LDTR, so give + xor ax,ax ; it something that it can use... + lldt ax ; (sigh) + + call [bp+2*4+2] ; Call actual routine + +.exit: + mov ax,PM_DS16_RM ; "Real-mode-like" data segment + mov es,ax + mov ds,ax + + pop bp ; Previous value for ss + + mov eax,cr0 + and al,~1 + mov cr0,eax ; Disable protected mode + jmp 0:.in_rm + +.in_rm: ; Back in real mode + mov ss,bp + pop gs + pop fs + pop es + pop ds +%if DISABLE_A20 + call disable_a20 +%endif + + popfd ; Re-enables interrupts + pop ebp + pop eax + ret 2 ; Drops the pm entry + +; +; pm_bcopy: +; +; This is the protected-mode core of the "bcopy" routine. +; +pm_bcopy: + cmp esi,-1 + je .bzero + + cmp esi,edi ; If source < destination, we might + jb .reverse ; have to copy backwards + +.forward: + mov al,cl ; Save low bits + and al,3 + shr ecx,2 ; Convert to dwords + a32 rep movsd ; Do our business + ; At this point ecx == 0 + + mov cl,al ; Copy any fractional dword + a32 rep movsb + ret + +.reverse: + std ; Reverse copy + lea esi,[esi+ecx-1] ; Point to final byte + lea edi,[edi+ecx-1] + mov eax,ecx + and ecx,3 + shr eax,2 + a32 rep movsb + + ; Change ESI/EDI to point to the last dword, instead + ; of the last byte. + sub esi,3 + sub edi,3 + mov ecx,eax + a32 rep movsd + + cld + ret + +.bzero: + xor eax,eax + mov si,cx ; Save low bits + and si,3 + shr ecx,2 + a32 rep stosd + + mov cx,si ; Write fractional dword + a32 rep stosb + ret + +; +; Routines to enable and disable (yuck) A20. These routines are gathered +; from tips from a couple of sources, including the Linux kernel and +; http://www.x86.org/. The need for the delay to be as large as given here +; is indicated by Donnie Barnes of RedHat, the problematic system being an +; IBM ThinkPad 760EL. +; +; We typically toggle A20 twice for every 64K transferred. +; +%define io_delay call _io_delay +%define IO_DELAY_PORT 80h ; Invalid port (we hope!) +%define disable_wait 32 ; How long to wait for a disable + +; Note the skip of 2 here +%define A20_DUNNO 0 ; A20 type unknown +%define A20_NONE 2 ; A20 always on? +%define A20_BIOS 4 ; A20 BIOS enable +%define A20_KBC 6 ; A20 through KBC +%define A20_FAST 8 ; A20 through port 92h + +slow_out: out dx, al ; Fall through + +_io_delay: out IO_DELAY_PORT,al + out IO_DELAY_PORT,al + ret + +enable_a20: + pushad + mov byte [cs:A20Tries],255 ; Times to try to make this work + +try_enable_a20: +; +; Flush the caches +; +%if DO_WBINVD + call try_wbinvd +%endif + +; +; If the A20 type is known, jump straight to type +; + mov bp,[cs:A20Type] + jmp word [cs:bp+A20List] + +; +; First, see if we are on a system with no A20 gate +; +a20_dunno: +a20_none: + mov byte [cs:A20Type], A20_NONE + call a20_test + jnz a20_done + +; +; Next, try the BIOS (INT 15h AX=2401h) +; +a20_bios: + mov byte [cs:A20Type], A20_BIOS + mov ax,2401h + pushf ; Some BIOSes muck with IF + int 15h + popf + + call a20_test + jnz a20_done + +; +; Enable the keyboard controller A20 gate +; +a20_kbc: + mov dl, 1 ; Allow early exit + call empty_8042 + jnz a20_done ; A20 live, no need to use KBC + + mov byte [cs:A20Type], A20_KBC ; Starting KBC command sequence + + mov al,0D1h ; Command write + out 064h, al + call empty_8042_uncond + + mov al,0DFh ; A20 on + out 060h, al + call empty_8042_uncond + + ; Verify that A20 actually is enabled. Do that by + ; observing a word in low memory and the same word in + ; the HMA until they are no longer coherent. Note that + ; we don't do the same check in the disable case, because + ; we don't want to *require* A20 masking (SYSLINUX should + ; work fine without it, if the BIOS does.) +.kbc_wait: push cx + xor cx,cx +.kbc_wait_loop: + call a20_test + jnz a20_done_pop + loop .kbc_wait_loop + + pop cx +; +; Running out of options here. Final attempt: enable the "fast A20 gate" +; +a20_fast: + mov byte [cs:A20Type], A20_FAST ; Haven't used the KBC yet + in al, 092h + or al,02h + and al,~01h ; Don't accidentally reset the machine! + out 092h, al + +.fast_wait: push cx + xor cx,cx +.fast_wait_loop: + call a20_test + jnz a20_done_pop + loop .fast_wait_loop + + pop cx + +; +; Oh bugger. A20 is not responding. Try frobbing it again; eventually give up +; and report failure to the user. +; + + + dec byte [cs:A20Tries] + jnz try_enable_a20 + + mov si, err_a20 + jmp abort_load + + section .data +err_a20 db CR, LF, 'A20 gate not responding!', CR, LF, 0 + section .bcopy32 + +; +; A20 unmasked, proceed... +; +a20_done_pop: pop cx +a20_done: popad + ret + +; +; This routine tests if A20 is enabled (ZF = 0). This routine +; must not destroy any register contents. +; +a20_test: + push es + push cx + push ax + mov cx,0FFFFh ; HMA = segment 0FFFFh + mov es,cx + mov cx,32 ; Loop count + mov ax,[cs:A20Test] +.a20_wait: inc ax + mov [cs:A20Test],ax + io_delay ; Serialize, and fix delay + cmp ax,[es:A20Test+10h] + loopz .a20_wait +.a20_done: pop ax + pop cx + pop es + ret + +%if DISABLE_A20 + +disable_a20: + pushad +; +; Flush the caches +; +%if DO_WBINVD + call try_wbinvd +%endif + + mov bp,[cs:A20Type] + jmp word [cs:bp+A20DList] + +a20d_bios: + mov ax,2400h + pushf ; Some BIOSes muck with IF + int 15h + popf + jmp short a20d_snooze + +; +; Disable the "fast A20 gate" +; +a20d_fast: + in al, 092h + and al,~03h + out 092h, al + jmp short a20d_snooze + +; +; Disable the keyboard controller A20 gate +; +a20d_kbc: + call empty_8042_uncond + mov al,0D1h + out 064h, al ; Command write + call empty_8042_uncond + mov al,0DDh ; A20 off + out 060h, al + call empty_8042_uncond + ; Wait a bit for it to take effect +a20d_snooze: + push cx + mov cx, disable_wait +.delayloop: call a20_test + jz .disabled + loop .delayloop +.disabled: pop cx +a20d_dunno: +a20d_none: + popad + ret + +%endif + +; +; Routine to empty the 8042 KBC controller. If dl != 0 +; then we will test A20 in the loop and exit if A20 is +; suddenly enabled. +; +empty_8042_uncond: + xor dl,dl +empty_8042: + call a20_test + jz .a20_on + and dl,dl + jnz .done +.a20_on: io_delay + in al, 064h ; Status port + test al,1 + jz .no_output + io_delay + in al, 060h ; Read input + jmp short empty_8042 +.no_output: + test al,2 + jnz empty_8042 + io_delay +.done: ret + +; +; Execute a WBINVD instruction if possible on this CPU +; +%if DO_WBINVD +try_wbinvd: + wbinvd + ret +%endif + +; +; shuffle_and_boot: +; +; This routine is used to shuffle memory around, followed by +; invoking an entry point somewhere in low memory. This routine +; can clobber any memory above 7C00h, we therefore have to move +; necessary code into the trackbuf area before doing the copy, +; and do adjustments to anything except BSS area references. +; +; NOTE: Since PXELINUX relocates itself, put all these +; references in the ".earlybss" segment. +; +; After performing the copy, this routine resets the stack and +; jumps to the specified entrypoint. +; +; IMPORTANT: This routine does not canonicalize the stack or the +; SS register. That is the responsibility of the caller. +; +; Inputs: +; DS:BX -> Pointer to list of (dst, src, len) pairs(*) +; AX -> Number of list entries +; [CS:EntryPoint] -> CS:IP to jump to +; On stack - initial state (fd, ad, ds, es, fs, gs) +; +; (*) If dst == -1, then (src, len) entry refers to a set of new +; descriptors to load. +; If src == -1, then the memory pointed to by (dst, len) is bzeroed; +; this is handled inside the bcopy routine. +; +shuffle_and_boot: +.restart: + and ax,ax + jz .done +.loop: + mov edi,[bx] + mov esi,[bx+4] + mov ecx,[bx+8] + cmp edi, -1 + je .reload + call bcopy + add bx,12 + dec ax + jnz .loop + +.done: + pop gs + pop fs + pop es + pop ds + popad + popfd + jmp far [cs:EntryPoint] + +.reload: + mov bx, trackbuf ; Next descriptor + movzx edi,bx + push ecx ; Save byte count + call bcopy + pop eax ; Byte count + xor edx,edx + mov ecx,12 + div ecx ; Convert to descriptor count + jmp .restart + +; +; trampoline_to_pm: +; +; This routine is chained to from shuffle_and_boot to invoke a +; flat 32-bit protected mode operating system. +; +trampoline_to_pm: + cli + call enable_a20 + mov byte [cs:bcopy_gdt.TSS+5],89h ; Mark TSS unbusy + o32 lgdt [cs:bcopy_gdt] + mov eax,cr0 + or al,1 + mov cr0,eax ; Enter protected mode + jmp PM_CS32:.next ; Synchronize and go to 32-bit mode + + bits 32 +.next: xor eax,eax + lldt ax ; TR <- 0 to be nice to Intel VT + mov al,PM_TSS + ltr ax ; Bogus TSS to be nice to Intel VT + mov al,PM_DS32 + mov es,ax ; 32-bit data segment selector + mov ds,ax + mov ss,ax + mov fs,ax + mov gs,ax + jmp word TrampolineBuf + bits 16 + + align 2 +A20List dw a20_dunno, a20_none, a20_bios, a20_kbc, a20_fast +%if DISABLE_A20 +A20DList dw a20d_dunno, a20d_none, a20d_bios, a20d_kbc, a20d_fast +%endif + +A20Type dw A20_NONE ; A20 type + + ; Total size of .bcopy32 section + alignb 4, db 0 ; Even number of dwords +__bcopy_size equ $-__bcopy_start + + section .earlybss + alignb 2 +EntryPoint resd 1 ; CS:IP for shuffle_and_boot +A20Test resw 1 ; Counter for testing status of A20 +A20Tries resb 1 ; Times until giving up on A20 + +; +; This buffer contains synthesized code for shuffle-and-boot. +; For the PM case, it is 9*5 = 45 bytes long; for the RM case it is +; 8*6 to set the GPRs, 6*5 to set the segment registers (including a dummy +; setting of CS), 5 bytes to set CS:IP, for a total of 83 bytes. +; +TrampolineBuf resb 83 ; Shuffle and boot trampoline + +; +; Space for a dummy task state segment. It should never be actually +; accessed, but just in case it is, point to a chunk of memory not used +; for anything real. +; + alignb 4 +DummyTSS resb 104 diff --git a/core/bios.inc b/core/bios.inc new file mode 100644 index 00000000..987a2166 --- /dev/null +++ b/core/bios.inc @@ -0,0 +1,39 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; bios.inc +;; +;; Header file for the BIOS data structures etc. +;; + +%ifndef _BIOS_INC +%define _BIOS_INC + + absolute 4*1Eh ; In the interrupt table +fdctab equ $ +fdctab1 resw 1 +fdctab2 resw 1 + absolute 0400h +serial_base resw 4 ; Base addresses for 4 serial ports + absolute 0413h +BIOS_fbm resw 1 ; Free Base Memory (kilobytes) + absolute 0462h +BIOS_page resb 1 ; Current video page + absolute 046Ch +BIOS_timer resw 1 ; Timer ticks + absolute 0472h +BIOS_magic resw 1 ; BIOS reset magic + absolute 0484h +BIOS_vidrows resb 1 ; Number of screen rows + +%endif ; _BIOS_INC diff --git a/core/bootsect.inc b/core/bootsect.inc new file mode 100644 index 00000000..7e8f416d --- /dev/null +++ b/core/bootsect.inc @@ -0,0 +1,158 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; bootsect.inc +;; +;; Load a boot sector (or other bootstrap program.) +;; +;; Unlike previous versions of this software, this doesn't require that +;; the length is 512 bytes. This allows PXE bootstraps and WinNT +;; "CD boot sectors" to be invoked. +;; + +; +; Load a boot sector +; +is_bootsector: +%if IS_SYSLINUX || IS_MDSLINUX + ; Transfer zero bytes + mov byte [CopySuper],0 + jmp short load_bootsec + +is_bss_sector: + ; Transfer the superblock + mov byte [CopySuper],superblock_len +%endif +load_bootsec: + mov edi, 100000h + mov [trackbuf+4],edi ; Copy from this address + push edi ; Save load address + xor dx,dx ; No padding + mov bx,abort_check ; Don't print dots, but allow abort + call load_high + + sub edi,100000h + mov [trackbuf+8],edi ; Save length + + mov eax,7C00h ; Entry point + mov [trackbuf],eax ; Copy to this address + mov [EntryPoint],eax ; Jump to this address when done + +%if IS_SYSLINUX || IS_MDSLINUX + movzx ecx,byte [CopySuper] + jcxz .not_bss + + ; For a BSS boot sector we have to patch. + mov esi,superblock + mov edi,100000h+(superblock-bootsec) + call bcopy + +.not_bss: +%endif + + xor edx,edx + xor esi,esi +%if IS_SYSLINUX || IS_MDSLINUX || IS_EXTLINUX + ; Restore original FDC table + mov eax,[OrigFDCTabPtr] + mov [fdctab],eax + + mov dl,[DriveNumber] + mov si,PartInfo ; Partition info buffer + mov di,800h-18 ; Put partition info here + push di + mov cx,8 ; 16 bytes + xor ax,ax + rep movsw + pop si ; DS:SI points to partition info +%elif IS_ISOLINUX + mov dl,[DriveNumber] +%elif IS_PXELINUX + mov byte [KeepPXE],1 ; Chainloading another NBP + call reset_pxe +%endif + xor bx,bx + +; +; replace_bootstrap for the special case where we have exactly one +; descriptor, and it's the first entry in the trackbuf +; + +replace_bootstrap_one: + push word 1 ; Length of descriptor list + push word trackbuf ; Address of descriptor list + ; Fall through + +; +; Entrypoint for "shut down and replace bootstrap" -- also invoked by +; the COMBOOT API. This routine expects two words on the stack: +; address of the copy list (versus DS) and count. Additionally, +; the values of ESI and EDX are passed on to the new bootstrap; +; the value of BX becomes the new DS. +; +replace_bootstrap: + ; + ; Prepare for shutting down + ; + call vgaclearmode + call cleanup_hardware + + ; + ; Set up initial stack frame (not used by PXE if keeppxe is + ; set - we use the PXE stack then.) + ; AFTER THIS POINT ONLY .earlybss IS AVAILABLE, NOT .bss + ; + xor ax,ax + mov ds,ax + mov es,ax + +%if IS_PXELINUX + test byte [KeepPXE],01h + jz .stdstack + les di,[InitStack] ; Reset stack to PXE original + jmp .stackok +%endif +.stdstack: + mov di,7C00h-44 + push di + mov cx,22 ; 44 bytes + rep stosw + pop di +.stackok: + + mov [es:di+28],edx ; New EDX + mov [es:di+12],esi ; New ESI + mov [es:di+6],bx ; New DS + +%if IS_PXELINUX == 0 + ; DON'T DO THIS FOR PXELINUX... + ; For PXE, ES:BX -> PXENV+, and this would corrupt + ; that use. + + ; Restore ES:DI -> $PnP (if we were ourselves called + ; that way...) + mov ax,[OrigESDI] + mov bx,[OrigESDI+2] + + mov [es:di+8],ax ; New DI + mov [es:di+4],bx ; New ES +%endif + pop bx ; Copy from... + pop ax ; Copy list count + + cli + mov cx,es + mov ss,cx + movzx esp,di + + jmp shuffle_and_boot diff --git a/core/cache.inc b/core/cache.inc new file mode 100644 index 00000000..59755576 --- /dev/null +++ b/core/cache.inc @@ -0,0 +1,114 @@ +; -*- fundamental -*- --------------------------------------------------- +; +; Copyright 2004-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 53 Temple Place Ste 330, +; Boston MA 02111-1307, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; ----------------------------------------------------------------------- + + section .text + + struc cptr +.sector: resd 1 ; Sector number +.prev: resw 1 ; LRU pointer to previous (less recent) +.next: resw 1 ; LRU pointer to next (more recent) + endstruc +cptr_size_lg2 equ 3 + +NCacheEntries equ 65536/SECTOR_SIZE + +; +; initcache: Initialize the cache data structures +; +initcache: + xor eax,eax ; We don't care about sector 0 + mov di,CachePtrs + mov cx,NCacheEntries+1 + mov bx,CachePtrs+NCacheEntries*cptr_size ; "prev" pointer +.loop: + mov [di+cptr.sector],eax ; Zero sector number + mov [di+cptr.prev],bx ; Previous pointer + mov [bx+cptr.next],di ; Previous entry's next pointer + mov bx,di + add di,cptr_size + loop .loop + ret + +; +; getcachesector: Check for a particular sector (EAX) in the sector cache, +; and if it is already there, return a pointer in GS:SI +; otherwise load it and return said pointer. +; +; Assumes CS == DS. +; +getcachesector: + push cx + push bx + push di + mov si,cache_seg + mov gs,si + mov si,CachePtrs+cptr_size ; Real sector cache pointers + mov cx,NCacheEntries +.search: + cmp eax,[si] + jz .hit + add si,cptr_size + loop .search + +.miss: + TRACER 'M' + ; Need to load it. + push es + push gs + pop es + mov bx,[CachePtrs+cptr.next] ; "Next most recent than head node" + mov [bx+cptr.sector],eax + mov si,bx + sub bx,CachePtrs+cptr_size + shl bx,SECTOR_SHIFT-cptr_size_lg2 ; Buffer address + pushad +%if IS_EXTLINUX + call getonesec_ext +%else + call getonesec +%endif + popad + pop es +.hit: + ; Update LRU, then compute buffer address + TRACER 'H' + + ; Remove from current position in the list + mov bx,[si+cptr.prev] + mov di,[si+cptr.next] + mov [bx+cptr.next],di + mov [di+cptr.prev],bx + + ; Add to just before head node + mov bx,[CachePtrs+cptr.prev] + mov [si+cptr.prev],bx + mov [bx+cptr.next],si + mov [CachePtrs+cptr.prev],si + mov word [si+cptr.next],CachePtrs + + sub si,CachePtrs+cptr_size + shl si,SECTOR_SHIFT-cptr_size_lg2 ; Buffer address + + pop di + pop bx + pop cx + ret + + section .bss + + ; Each CachePtr contains: + ; - Block pointer + ; - LRU previous pointer + ; - LRU next pointer + ; The first entry is the head node of the list + alignb 4 +CachePtrs resb (NCacheEntries+1)*cptr_size diff --git a/core/checksumiso.pl b/core/checksumiso.pl new file mode 100755 index 00000000..b5527428 --- /dev/null +++ b/core/checksumiso.pl @@ -0,0 +1,36 @@ +#!/usr/bin/perl +# +# Construct a checksum for isolinux*.bin, compatible +# with an mkisofs boot-info-table +# + +use bytes; +use integer; + +($file) = @ARGV; + +open(FILE, '+<', $file) or die "$0: Cannot open $file: $!\n"; +binmode FILE; + +if ( !seek(FILE,64,0) ) { + die "$0: Cannot seek past header\n"; +} + +$csum = 0; +$bytes = 64; +while ( ($n = read(FILE, $dw, 4)) > 0 ) { + $dw .= "\0\0\0\0"; # Pad to at least 32 bits + ($v) = unpack("V", $dw); + $csum = ($csum + $v) & 0xffffffff; + $bytes += $n; +} + +if ( !seek(FILE,16,0) ) { + die "$0: Cannot seek to header\n"; +} + +print FILE pack("VV", $bytes, $csum); + +close(FILE); + +exit 0; diff --git a/core/cleanup.inc b/core/cleanup.inc new file mode 100644 index 00000000..dca6491a --- /dev/null +++ b/core/cleanup.inc @@ -0,0 +1,54 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 2007-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; cleanup.inc +;; +;; Some final tidying before jumping to a kernel or bootsector +;; + + section .text +; +; cleanup_hardware: +; +; Shut down anything transient. *No segment assumptions*. +; Can trash any registers. +; +cleanup_hardware: + pushad +; +; Linux wants the floppy motor shut off before starting the kernel, +; at least bootsect.S seems to imply so. If we don't load the floppy +; driver, this is *definitely* so! +; + xor ax,ax + xor dx,dx + int 13h + +%if 0 ; This bug report has not been substantiated! +; Vmware crashes if we scroll in the decompressor! Try to detect vmware +; and if it is Vmware, clear the screen... + mov eax,'VMXh' + xor ebx, ebx + mov ecx, 10 ; Get version + mov dx, 'VX' + in eax, dx + cmp ebx, 'VMXh' + jne .no_vmware + + mov ax,0x0003 ; Set mode (clear screen/home cursor) + int 10h +.no_vmware: +%endif + + popad + ret diff --git a/core/cmdline.inc b/core/cmdline.inc new file mode 100644 index 00000000..5d5b3c22 --- /dev/null +++ b/core/cmdline.inc @@ -0,0 +1,38 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 2003-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; cmdline.inc +;; +;; Common routine to assemble [null-terminated] command line into +;; real_mode_seg:cmd_line_here. +;; Not used by plain kernel due to BOOT_IMAGE= etc. +;; + +; +; Assumes DS == CS +make_plain_cmdline: + push es + ; ui.inc has already copied any APPEND options + mov ax,real_mode_seg + mov es,ax + + mov si,[CmdOptPtr] + mov di,[CmdLinePtr] + + call strcpy + + dec di + mov [CmdLinePtr],di + + pop es + ret diff --git a/core/com32.inc b/core/com32.inc new file mode 100644 index 00000000..99954206 --- /dev/null +++ b/core/com32.inc @@ -0,0 +1,421 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; com32.inc +;; +;; Common code for running a COM32 image +;; + +; +; Load a COM32 image. A COM32 image is the 32-bit analogue to a DOS +; .com file. A COM32 image is loaded at address 0x101000, with %esp +; set to the high end of usable memory. +; +; A COM32 image should begin with the magic bytes: +; B8 FF 4C CD 21, which is "mov eax,0x21cd4cff" in 32-bit mode and +; "mov ax,0x4cff; int 0x21" in 16-bit mode. This will abort the +; program with an error if run in 16-bit mode. +; +pm_idt: equ 0x100000 +pm_entry: equ 0x101000 + + bits 16 + section .data + align 2, db 0 +com32_pmidt: + dw 8*256 ; Limit + dd pm_idt ; Address + +com32_rmidt: + dw 0ffffh ; Limit + dd 0 ; Address + + section .text +is_com32_image: + push si ; Save file handle + push eax ; Save file length + + call make_plain_cmdline + ; Copy the command line into the low cmdline buffer + mov ax,real_mode_seg + mov fs,ax + mov si,cmd_line_here + mov di,command_line + mov cx,[CmdLinePtr] + inc cx ; Include final null + sub cx,si + fs rep movsb + + call comboot_setup_api ; Set up the COMBOOT-style API + + mov edi,pm_entry ; Load address + pop eax ; File length + pop si ; File handle + xor dx,dx ; No padding + mov bx,abort_check ; Don't print dots, but allow abort + call load_high + +com32_start: + mov ebx,com32_call_start ; Where to go in PM + +com32_enter_pm: + cli + mov ax,cs + mov ds,ax + mov [RealModeSSSP],sp + mov [RealModeSSSP+2],ss + cld + call a20_test + jnz .a20ok + call enable_a20 + +.a20ok: + mov byte [bcopy_gdt.TSS+5],89h ; Mark TSS unbusy + + lgdt [bcopy_gdt] ; We can use the same GDT just fine + lidt [com32_pmidt] ; Set up the IDT + mov eax,cr0 + or al,1 + mov cr0,eax ; Enter protected mode + jmp PM_CS32:.in_pm + + bits 32 +.in_pm: + xor eax,eax ; Available for future use... + mov fs,eax + mov gs,eax + lldt ax + + mov al,PM_DS32 ; Set up data segments + mov es,eax + mov ds,eax + mov ss,eax + + mov al,PM_TSS ; Be nice to Intel's VT by + ltr ax ; giving it a valid TR + + mov esp,[PMESP] ; Load protmode %esp if available + jmp ebx ; Go to where we need to go + +; +; This is invoked right before the actually starting the COM32 +; progam, in 32-bit mode... +; +com32_call_start: + ; + ; Point the stack to the end of (permitted) high memory + ; + mov esp,[word HighMemRsvd] + xor sp,sp ; Align to a 64K boundary + + ; + ; Set up the protmode IDT and the interrupt jump buffers + ; We set these up in the system area at 0x100000, + ; but we could also put them beyond the stack. + ; + mov edi,pm_idt + + ; Form an interrupt gate descriptor + mov eax,0x00200000+((pm_idt+8*256)&0x0000ffff) + mov ebx,0x0000ee00+((pm_idt+8*256)&0xffff0000) + xor ecx,ecx + inc ch ; ecx <- 256 + + push ecx +.make_idt: + stosd + add eax,8 + xchg eax,ebx + stosd + xchg eax,ebx + loop .make_idt + + pop ecx + + ; Each entry in the interrupt jump buffer contains + ; the following instructions: + ; + ; 00000000 60 pushad + ; 00000001 B0xx mov al,<interrupt#> + ; 00000003 E9xxxxxxxx jmp com32_handle_interrupt + + mov eax,0e900b060h + mov ebx,com32_handle_interrupt-(pm_idt+8*256+8) + +.make_ijb: + stosd + sub [edi-2],cl ; Interrupt # + xchg eax,ebx + stosd + sub eax,8 + xchg eax,ebx + loop .make_ijb + + ; Now everything is set up for interrupts... + + push dword com32_cfarcall ; Cfarcall entry point + push dword com32_farcall ; Farcall entry point + push dword (1 << 16) ; 64K bounce buffer + push dword (comboot_seg << 4) ; Bounce buffer address + push dword com32_intcall ; Intcall entry point + push dword command_line ; Command line pointer + push dword 6 ; Argument count + sti ; Interrupts OK now + call pm_entry ; Run the program... + ; ... on return, fall through to com32_exit ... + +com32_exit: + mov bx,com32_done ; Return to command loop + +com32_enter_rm: + cli + cld + mov [PMESP],esp ; Save exit %esp + xor esp,esp ; Make sure the high bits are zero + jmp PM_CS16:.in_pm16 ; Return to 16-bit mode first + + bits 16 +.in_pm16: + mov ax,PM_DS16_RM ; Real-mode-like segment + mov es,ax + mov ds,ax + mov ss,ax + mov fs,ax + mov gs,ax + + lidt [com32_rmidt] ; Real-mode IDT (rm needs no GDT) + mov eax,cr0 + and al,~1 + mov cr0,eax + jmp 0:.in_rm + +.in_rm: ; Back in real mode + mov ax,cs ; Set up sane segments + mov ds,ax + mov es,ax + mov fs,ax + mov gs,ax + lss sp,[RealModeSSSP] ; Restore stack + jmp bx ; Go to whereever we need to go... + +com32_done: +%if DISABLE_A20 + call disable_a20 +%endif + sti + jmp enter_command + +; +; 16-bit support code +; + bits 16 + +; +; 16-bit interrupt-handling code +; +com32_int_rm: + pushf ; Flags on stack + push cs ; Return segment + push word .cont ; Return address + push dword edx ; Segment:offset of IVT entry + retf ; Invoke IVT routine +.cont: ; ... on resume ... + mov ebx,com32_int_resume + jmp com32_enter_pm ; Go back to PM + +; +; 16-bit intcall/farcall handling code +; +com32_sys_rm: + pop gs + pop fs + pop es + pop ds + popad + popfd + mov [cs:Com32SysSP],sp + retf ; Invoke routine +.return: + ; We clean up SP here because we don't know if the + ; routine returned with RET, RETF or IRET + mov sp,[cs:Com32SysSP] + pushfd + pushad + push ds + push es + push fs + push gs + mov ebx,com32_syscall.resume + jmp com32_enter_pm + +; +; 16-bit cfarcall handing code +; +com32_cfar_rm: + retf +.return: + mov sp,[cs:Com32SysSP] + mov [cs:RealModeEAX],eax + mov ebx,com32_cfarcall.resume + jmp com32_enter_pm + +; +; 32-bit support code +; + bits 32 + +; +; This is invoked on getting an interrupt in protected mode. At +; this point, we need to context-switch to real mode and invoke +; the interrupt routine. +; +; When this gets invoked, the registers are saved on the stack and +; AL contains the register number. +; +com32_handle_interrupt: + movzx eax,al + xor ebx,ebx ; Actually makes the code smaller + mov edx,[ebx+eax*4] ; Get the segment:offset of the routine + mov bx,com32_int_rm + jmp com32_enter_rm ; Go to real mode + +com32_int_resume: + popad + iret + +; +; Intcall/farcall invocation. We manifest a structure on the real-mode stack, +; containing the com32sys_t structure from <com32.h> as well as +; the following entries (from low to high address): +; - Target offset +; - Target segment +; - Return offset +; - Return segment (== real mode cs == 0) +; - Return flags +; +com32_farcall: + pushfd ; Save IF among other things... + pushad ; We only need to save some, but... + + mov eax,[esp+10*4] ; CS:IP + jmp com32_syscall + + +com32_intcall: + pushfd ; Save IF among other things... + pushad ; We only need to save some, but... + + movzx eax,byte [esp+10*4] ; INT number + mov eax,[eax*4] ; Get CS:IP from low memory + +com32_syscall: + cld + + movzx edi,word [word RealModeSSSP] + movzx ebx,word [word RealModeSSSP+2] + sub edi,54 ; Allocate 54 bytes + mov [word RealModeSSSP],di + shl ebx,4 + add edi,ebx ; Create linear address + + mov esi,[esp+11*4] ; Source regs + xor ecx,ecx + mov cl,11 ; 44 bytes to copy + rep movsd + + ; EAX is already set up to be CS:IP + stosd ; Save in stack frame + mov eax,com32_sys_rm.return ; Return seg:offs + stosd ; Save in stack frame + mov eax,[edi-12] ; Return flags + and eax,0x200cd7 ; Mask (potentially) unsafe flags + mov [edi-12],eax ; Primary flags entry + stosw ; Return flags + + mov bx,com32_sys_rm + jmp com32_enter_rm ; Go to real mode + + ; On return, the 44-byte return structure is on the + ; real-mode stack, plus the 10 additional bytes used + ; by the target address (see above.) +.resume: + movzx esi,word [word RealModeSSSP] + movzx eax,word [word RealModeSSSP+2] + mov edi,[esp+12*4] ; Dest regs + shl eax,4 + add esi,eax ; Create linear address + and edi,edi ; NULL pointer? + jnz .do_copy +.no_copy: mov edi,esi ; Do a dummy copy-to-self +.do_copy: xor ecx,ecx + mov cl,11 ; 44 bytes + rep movsd ; Copy register block + + add dword [word RealModeSSSP],54 ; Remove from stack + + popad + popfd + ret ; Return to 32-bit program + +; +; Cfarcall invocation. We copy the stack frame to the real-mode stack, +; followed by the return CS:IP and the CS:IP of the target function. +; +com32_cfarcall: + pushfd + pushad + + cld + mov ecx,[esp+12*4] ; Size of stack frame + + movzx edi,word [word RealModeSSSP] + movzx ebx,word [word RealModeSSSP+2] + mov [word Com32SysSP],di + sub edi,ecx ; Allocate space for stack frame + and edi,~3 ; Round + sub edi,4*2 ; Return pointer, return value + mov [word RealModeSSSP],di + shl ebx,4 + add edi,ebx ; Create linear address + + mov eax,[esp+10*4] ; CS:IP + stosd ; Save to stack frame + mov eax,com32_cfar_rm.return ; Return seg:off + stosd + mov esi,[esp+11*4] ; Stack frame + mov eax,ecx ; Copy the stack frame + shr ecx,2 + rep movsd + mov ecx,eax + and ecx,3 + rep movsb + + mov bx,com32_cfar_rm + jmp com32_enter_rm + +.resume: + popad + mov eax,[word RealModeEAX] + popfd + ret + + bits 16 + + section .bss1 + alignb 4 +RealModeSSSP resd 1 ; Real-mode SS:SP +RealModeEAX resd 1 ; Real mode EAX +PMESP resd 1 ; Protected-mode ESP +Com32SysSP resw 1 ; SP saved during COM32 syscall + + section .text diff --git a/core/comboot.inc b/core/comboot.inc new file mode 100644 index 00000000..df9bb001 --- /dev/null +++ b/core/comboot.inc @@ -0,0 +1,988 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; comboot.inc +;; +;; Common code for running a COMBOOT image +;; + + section .text + +; Parameter registers definition; this is the definition +; of the stack frame used by INT 21h and INT 22h. +%define P_FLAGS word [bp+44] +%define P_FLAGSL byte [bp+44] +%define P_FLAGSH byte [bp+45] +%define P_CS word [bp+42] +%define P_IP word [bp+40] +%define P_DS word [bp+38] +%define P_ES word [bp+36] +%define P_FS word [bp+34] +%define P_GS word [bp+32] +%define P_EAX dword [bp+28] +%define P_AX word [bp+28] +%define P_HAX word [bp+30] +%define P_AL byte [bp+28] +%define P_AH byte [bp+29] +%define P_ECX dword [bp+24] +%define P_CX word [bp+24] +%define P_HCX word [bp+26] +%define P_CL byte [bp+24] +%define P_CH byte [bp+25] +%define P_EDX dword [bp+20] +%define P_DX word [bp+20] +%define P_HDX word [bp+22] +%define P_DL byte [bp+20] +%define P_DH byte [bp+21] +%define P_EBX dword [bp+16] +%define P_BX word [bp+16] +%define P_HBX word [bp+18] +%define P_BL byte [bp+16] +%define P_BH byte [bp+17] +%define P_EBP dword [bp+8] +%define P_BP word [bp+8] +%define P_HBP word [bp+10] +%define P_ESI dword [bp+4] +%define P_SI word [bp+4] +%define P_HSI word [bp+6] +%define P_EDI dword [bp] +%define P_DI word [bp] +%define P_HDI word [bp+2] + +; Looks like a COMBOOT image but too large +comboot_too_large: + call close_file + mov si,err_comlarge + call cwritestr + jmp enter_command + +; +; Load a COMBOOT image. A COMBOOT image is basically a DOS .COM file, +; except that it may, of course, not contain any DOS system calls. We +; do, however, allow the execution of INT 20h to return to SYSLINUX. +; +is_comboot_image: + push si ; Save file handle + + call make_plain_cmdline + + call comboot_setup_api + + mov cx,comboot_seg + mov es,cx + + xor di,di + mov cx,64 ; 256 bytes (size of PSP) + xor eax,eax ; Clear PSP + rep stosd + + mov word [es:0], 020CDh ; INT 20h instruction + ; First non-free paragraph + ; This is valid because comboot_seg == real_mode_seg + ; == the highest segment used by all derivatives + int 12h ; Get DOS memory size + shl ax,6 ; Kilobytes -> paragraphs + mov word [es:02h],ax + +%ifndef DEPEND +%if real_mode_seg != comboot_seg +%error "This code assumes real_mode_seg == comboot_seg" +%endif +%endif + ; Copy the command line from high memory + mov si,cmd_line_here + mov cx,125 ; Max cmdline len (minus space and CR) + mov di,081h ; Offset in PSP for command line + mov al,' ' ; DOS command lines begin with a space + stosb + +.loop: es lodsb + and al,al + jz .done + stosb + loop .loop +.done: + + mov al,0Dh ; CR after last character + stosb + mov ax,di + sub al,82h ; Include space but not CR + mov [es:80h],al ; Store command line length + + ; Now actually load the file... + pop si ; File handle + mov bx,100h ; Load at <seg>:0100h + mov cx,10000h >> SECTOR_SHIFT + ; Absolute maximum # of sectors + call getfssec + cmp ecx,65536-256-2 ; Maximum size + ja comboot_too_large + + ; And invoke the program... + mov ax,es + mov ds,ax + mov ss,ax + xor sp,sp + push word 0 ; Return to address 0 -> exit + + jmp comboot_seg:100h ; Run it + +; Proper return vector +comboot_return: cli ; Don't trust anyone + push enter_command ; Normal return to command prompt + jmp comboot_exit + +; +; Set up the COMBOOT API interrupt vectors. This is also used +; by the COM32 code. +; +comboot_setup_api: + mov di,4*0x20 ; DOS interrupt vectors + mov eax,comboot_return ; INT 20h = exit + stosd + mov ax,comboot_int21 ; INT 21h = DOS-compatible syscalls + stosd + mov ax,comboot_int22 ; INT 22h = proprietary syscalls + stosd + mov ax,comboot_bogus + mov cx,29 ; All remaining DOS vectors + rep stosd + ret + +; INT 21h: generic DOS system call +comboot_int21: cli + push ds + push es + push fs + push gs + pushad + cld + mov bp,cs + mov ds,bp + mov es,bp + mov bp,sp ; Set up stack frame + + call adjust_screen ; The COMBOOT program might have changed the screen + + mov cx,int21_count + mov si,int21_table +.again: lodsb + cmp al,P_AH + lodsw + loopne .again + ; The last function in the list is the + ; "no such function" function + clc + call ax ; Call the invoked function +comboot_resume: + mov bp,sp ; In case the function clobbers BP + setc P_FLAGSL ; Propagate CF->error + popad + pop gs + pop fs + pop es + pop ds + iret + +; Attempted to execute non-21h DOS system call +comboot_bogus: cli ; Don't trust anyone + mov cx,err_notdos + push enter_command + jmp comboot_exit_msg + +; +; Generic COMBOOT return to command line code +; stack -> where to go next +; CX -> message (for _msg version) +; +comboot_exit: + xor cx,cx +comboot_exit_msg: + pop bx ; Return address + RESET_STACK_AND_SEGS AX + call adjust_screen ; The COMBOOT program might have changed the screen + jcxz .nomsg + mov si,KernelCName + call cwritestr + mov si,cx + call cwritestr +.nomsg: + jmp bx + +; +; INT 21h system calls +; +comboot_getkey: ; 01 = get key with echo + call vgashowcursor + call comboot_getchar + call vgahidecursor + call writechr + clc + ret + +comboot_writechr: ; 02 = writechr + mov al,P_DL + call writechr + clc + ret + +comboot_writeserial: ; 04 = write serial port + mov al,P_DL + call write_serial + clc + ret + +comboot_getkeynoecho: ; 08 = get key w/o echo + call comboot_getchar + clc + ret + +comboot_writestr: ; 09 = write DOS string + mov es,P_DS + mov si,P_DX +.loop: es lodsb + cmp al,'$' ; End string with $ - bizarre + je .done + call writechr + jmp short .loop +.done: clc + ret + +comboot_checkkey: ; 0B = check keyboard status + cmp byte [APIKeyFlag],00h + jnz .waiting + call pollchar +.waiting: setz al + dec al ; AL = 0FFh if present, 0 if not + mov P_AL,al + clc + ret + +comboot_checkver: ; 30 = check DOS version + ; We return 0 in all DOS-compatible version registers, + ; but the high part of eax-ebx-ecx-edx spell "SYSLINUX" + mov P_EAX,'SY' << 16 + mov P_EBX,'SL' << 16 + mov P_ECX,'IN' << 16 + mov P_EDX,'UX' << 16 + ret + +comboot_getchar: + cmp byte [APIKeyFlag],00h + jne .queued + call getchar ; If not queued get input + and al,al ; Function key? (CF <- 0) + jnz .done + mov [APIKeyWait],ah ; High part of key + inc byte [APIKeyFlag] ; Set flag +.done: mov P_AL,al + ret +.queued: mov al,[APIKeyWait] + dec byte [APIKeyFlag] + jmp .done + +; +; INT 22h - SYSLINUX-specific system calls +; System call number in ax +; +comboot_int22: + cli + push ds + push es + push fs + push gs + pushad + cld + mov bp,cs + mov ds,bp + mov es,bp + mov bp,sp ; Set up stack frame + + call adjust_screen ; The COMBOOT program might have changed the screen + + cmp ax,int22_count + jb .ok + xor ax,ax ; Function 0 -> unimplemented +.ok: + xchg ax,bx + add bx,bx ; CF <- 0 + call [bx+int22_table] + jmp comboot_resume ; On return + +; +; INT 22h AX=0000h Unimplemented call +; +comapi_err: + stc + ret + +; +; INT 22h AX=0001h Get SYSLINUX version +; +comapi_get_version: + ; Number of API functions supported + mov P_AX,int22_count + ; SYSLINUX version + mov P_CX,(VER_MAJOR << 8)+VER_MINOR + ; SYSLINUX derivative ID byte + mov P_DX,my_id + ; For future use + mov P_BX,cs ; cs == 0 + + mov P_ES,ds + ; ES:SI -> version banner + mov P_SI,syslinux_banner + ; ES:DI -> copyright string + mov P_DI,copyright_str + +comapi_nop: + clc + ret + +; +; INT 22h AX=0002h Write string +; +; Write null-terminated string in ES:BX +; +comapi_writestr: + mov ds,P_ES + mov si,P_BX + call writestr + clc + ret + +; +; INT 22h AX=0003h Run command +; +; Terminates the COMBOOT program and executes the command line in +; ES:BX as if it had been entered by the user. +; +comapi_run: + mov ds,P_ES + mov si,P_BX + mov di,command_line + call strcpy + push load_kernel ; Run a new kernel + jmp comboot_exit ; Terminate task, clean up + +; +; INT 22h AX=0004h Run default command +; +; Terminates the COMBOOT program and executes the default command line +; as if a timeout had happened or the user pressed <Enter>. +; +comapi_run_default: + push auto_boot + jmp comboot_exit + +; +; INT 22h AX=0005h Force text mode +; +; Puts the video in standard text mode +; +comapi_textmode: + call vgaclearmode + clc + ret + +; +; INT 22h AX=0006h Open file +; +comapi_open: + push ds + mov ds,P_ES + mov si,P_SI + mov di,InitRD + call mangle_name + pop ds + call searchdir + jz comapi_err + mov P_EAX,eax + mov P_CX,SECTOR_SIZE + mov P_SI,si + clc + ret + +; +; INT 22h AX=0007h Read file +; +comapi_read: + mov es,P_ES + mov bx,P_BX + mov si,P_SI + mov cx,P_CX + call getfssec + jnc .noteof + xor si,si ; SI <- 0 on EOF, CF <- 0 +.noteof: mov P_SI,si + mov P_ECX,ecx + ret + +; +; INT 22h AX=0008h Close file +; +comapi_close: + mov si,P_SI + call close_file + clc + ret + +; +; INT 22h AX=0009h Call PXE stack +; +%if IS_PXELINUX +comapi_pxecall: + mov bx,P_BX + mov es,P_ES + mov di,P_DI + call pxenv + mov ax,[PXEStatus] + mov P_AX,ax + ret +%else +comapi_pxecall equ comapi_err ; Not available +%endif + +; +; INT 22h AX=000Ah Get Derivative-Specific Info +; +comapi_derinfo: + mov P_AL,my_id +%if IS_PXELINUX + mov ax,[APIVer] + mov P_DX,ax + mov ax,[StrucPtr] + mov P_BX,ax + mov ax,[StrucPtr+2] + mov P_ES,ax + mov ax,[InitStack] + mov P_SI,ax + mov ax,[InitStack+2] + mov P_FS,ax +%else + ; Physical medium... + + mov P_CL,SECTOR_SHIFT + mov al,[DriveNumber] + mov P_DL,al + mov P_FS,cs + mov P_SI,OrigESDI +%if IS_SYSLINUX || IS_MDSLINUX || IS_EXTLINUX + mov P_ES,cs + mov P_BX,PartInfo +%elif IS_ISOLINUX + mov P_ES,cs + mov P_BX,spec_packet +%endif +%endif + clc + ret + +; +; INT 22h AX=000Bh Get Serial Console Configuration +; +comapi_serialcfg: + mov ax,[SerialPort] + mov P_DX,ax + mov ax,[BaudDivisor] + mov P_CX,ax + mov ax,[FlowControl] + or al,ah + mov ah,[FlowIgnore] + shr ah,4 + test byte [DisplayCon],01h + jnz .normalconsole + or ah,80h +.normalconsole: + mov P_BX,ax + clc + ret + +; +; INT 22h AX=000Ch Perform final cleanup +; +comapi_cleanup: +%if IS_PXELINUX + ; Unload PXE if requested + test dl,3 + setnz [KeepPXE] + sub bp,sp ; unload_pxe may move the stack around + call unload_pxe + add bp,sp ; restore frame pointer... +%elif IS_SYSLINUX || IS_MDSLINUX || IS_EXTLINUX + ; Restore original FDC table + mov eax,[OrigFDCTabPtr] + mov [fdctab],eax +%endif + ; Reset the floppy disk subsystem + xor ax,ax + xor dx,dx + int 13h + clc + ret + +; +; INT 22h AX=000Dh Clean up then replace bootstrap +; +comapi_chainboot: + call comapi_cleanup + mov eax,P_EDI + mov [trackbuf+4],eax ; Copy from + mov eax,P_ECX + mov [trackbuf+8],eax ; Total bytes + mov eax,7C00h + mov [trackbuf],eax ; Copy to + mov [EntryPoint],eax ; CS:IP entry point + mov esi,P_ESI + mov edx,P_EBX + mov bx,P_DS + jmp replace_bootstrap_one + + +; +; INT 22h AX=000Eh Get configuration file name +; +comapi_configfile: + mov P_ES,cs + mov P_BX,ConfigName + clc + ret + +; +; INT 22h AX=000Fh Get IPAPPEND strings +; +%if IS_PXELINUX +comapi_ipappend: + mov P_ES,cs + mov P_CX,numIPAppends + mov P_BX,IPAppends + clc + ret + + section .data + alignb 2, db 0 +IPAppends dw IPOption + dw BOOTIFStr +numIPAppends equ ($-IPAppends)/2 + +%else +comapi_ipappend equ comapi_err +%endif + + section .text + +; +; INT 22h AX=0010h Resolve hostname +; +%if IS_PXELINUX +comapi_dnsresolv: + mov ds,P_ES + mov si,P_BX + call dns_resolv + mov P_EAX,eax + clc + ret +%else +comapi_dnsresolv equ comapi_err +%endif + + section .text + +; +; INT 22h AX=0011h Maximum number of shuffle descriptors +; +comapi_maxshuffle: + mov P_CX,trackbufsize/12 + ret + +; +; INT 22h AX=0012h Cleanup, shuffle and boot +; +comapi_shuffle: + cmp P_CX,(2*trackbufsize)/12 + ja .error + + call comapi_cleanup + + mov cx, P_CX + push cx ; On stack: descriptor count + + lea cx,[ecx+ecx*2] ; CX *= 3 + + mov fs,P_ES + mov si,P_DI + mov di,trackbuf + push di ; On stack: descriptor list address + fs rep movsd ; Copy the list + + mov eax,P_EBP + mov [EntryPoint],eax ; CS:IP entry point + mov esi,P_ESI + mov edx,P_EBX + mov bx,P_DS + jmp replace_bootstrap +.error: + stc + ret + +; +; INT 22h AX=0013h Idle call +; +; +; *** FIX THIS *** +; The idle call seems to have detrimental effects on some machines when +; called from a COM32 context (WHY?) -- disable it for now. +; +%if 0 ; def HAVE_IDLE + +comapi_idle: + DO_IDLE + clc + ret + +%else + +comapi_idle equ comapi_err + +%endif + +; +; INT 22h AX=0014h Local boot +; +%if HAS_LOCALBOOT +comapi_localboot: + mov ax,P_DX + jmp local_boot +%else +comapi_localboot equ comapi_err +%endif ; HAS_LOCALBOOT + +; +; INT 22h AX=0015h Feature flags +; +comapi_features: + mov P_ES,cs + mov P_BX,feature_flags + mov P_CX,feature_flags_len + clc + ret + +; +; INT 22h AX=0016h Run kernel image +; +comapi_runkernel: + mov al,P_DL + cmp al,VK_TYPES-1 + ja .error + mov [KernelType],al + push ds + mov ds,P_DS + mov si,P_SI + mov di,KernelName + call mangle_name + pop ds + call searchdir + jz comapi_err + + ; The kernel image was found, so we can load it... + mov [Kernel_SI],si + mov [Kernel_EAX],eax + + ; It's not just possible, but quite likely, that ES:BX + ; points into real_mode_seg, so we need to exercise some + ; special care here... use xfer_buf_seg as an intermediary + push ds + push es + mov ax,xfer_buf_seg + mov ds,P_ES + mov si,P_BX + mov es,ax + xor di,di + call strcpy + pop es + pop ds + +%if IS_PXELINUX + mov al,P_CL + mov [IPAppend],al +%endif + + call comboot_exit + +.finish: + ; Copy the command line into its proper place + push ds + push es + mov ax,xfer_buf_seg + mov dx,real_mode_seg + mov ds,ax + mov es,dx + xor si,si + mov di,cmd_line_here + call strcpy + mov byte [es:di-1],' ' ; Simulate APPEND + pop es + pop ds + mov [CmdLinePtr],di + mov word [CmdOptPtr],zero_string + jmp kernel_good_saved + +.error equ comapi_shuffle.error + +; +; INT 22h AX=0017h Report video mode change +; +comapi_usingvga: + mov ax,P_BX + cmp ax,0Fh ; Unknown flags = failure + ja .error + mov [UsingVGA],al + mov cx,P_CX + mov dx,P_DX + mov [GXPixCols],cx + mov [GXPixRows],dx + test al,08h + jnz .notext + call adjust_screen +.notext: + clc + ret +.error: + stc + ret + +; +; INT 22h AX=0018h Query custom font +; +comapi_userfont: + mov al,[UserFont] + and al,al + jz .done + mov al,[VGAFontSize] + mov P_ES,ds + mov P_BX,vgafontbuf + +.done: ; CF=0 here + mov P_AL,al + ret + +; +; INT 22h AX=0019h Read disk +; +%if IS_SYSLINUX || IS_MDSLINUX || IS_ISOLINUX || IS_EXTLINUX +comapi_readdisk: + mov esi,P_ESI ; Enforce ESI == EDI == 0, these + or esi,P_EDI ; are reserved for future expansion + jnz .err + mov eax,P_EDX + mov es,P_ES + mov bx,P_BX + mov bp,P_CX ; WE CANNOT use P_* after touching bp! + call getlinsec + clc + ret +.err: + stc + ret +%else +comapi_readdisk equ comapi_err +%endif + +; +; INT 22h AX=001Ah Cleanup, shuffle and boot to flat protected mode +; +comapi_shufflepm: + cmp P_CX,(2*trackbufsize)/12 + ja .error + + call comapi_cleanup + + mov cx, P_CX + push cx ; On stack: descriptor count + + lea cx,[ecx+ecx*2] ; CX *= 3 + + mov fs,P_ES + mov si,P_DI + mov di,trackbuf + push di ; On stack: descriptor list address + fs rep movsd ; Copy the list + + mov fs,P_DS + mov si,P_SI + mov edi,TrampolineBuf + mov al,0B8h ; MOV EAX opcode + mov cl,9 +.maketramp: + stosb ; MOV opcode + inc ax ; Next register opcode + fs movsd ; immediate value + loop .maketramp + mov byte [di-5],0E9h ; Last opcode is JMP + sub [di-4],edi ; Make JMP target relative + + mov dword [EntryPoint],trampoline_to_pm + xor bx,bx ; DS on entry + jmp replace_bootstrap +.error: + stc + ret + +; +; INT 22h AX=001Bh Cleanup, shuffle and boot with register setting +; +comapi_shufflerm: + cmp P_CX,(2*trackbufsize)/12 + ja .error + + call comapi_cleanup + + mov cx, P_CX + push cx ; On stack: descriptor count + + lea cx,[ecx+ecx*2] ; CX *= 3 + + mov fs,P_ES + mov si,P_DI + mov di,trackbuf + push di ; On stack: descriptor list address + fs rep movsd ; Copy the list + + mov fs,P_DS + mov si,P_SI + mov di,TrampolineBuf + + ; Generate segment-loading instructions + mov bx,0C08Eh ; MOV ES,AX + mov cl,6 ; 6 segment registers (incl CS) +.segtramp: + mov al,0B8h + stosb ; MOV AX,imm16 opcode + fs movsw ; imm16 + mov ax,bx + add bh,8 + stosw ; MOV xS,AX + loop .segtramp + + ; Clobber the MOV CS,AX instruction. + mov word [di-22], 9090h ; NOP NOP + + ; Generate GPR-loading instructions + mov ax,0B866h ; MOV EAX,imm32 + mov cl,8 ; 8 GPRs +.gprtramp: + stosw ; MOV ExX,imm32 opcode + fs movsd ; imm32 + inc ah + loop .gprtramp + + mov al,0EAh ; JMP FAR imm16:imm16 opcode + stosb + fs movsd ; CS:IP + + mov dword [EntryPoint],TrampolineBuf + jmp replace_bootstrap +.error: + stc + ret + +; +; INT 22h AX=001Ch Get pointer to auxillary data vector +; +comapi_getadv: + mov P_ES,ds + mov P_BX,adv0.data + mov P_CX,ADV_LEN + ret + +; +; INT 22h AX=001Dh Write auxillary data vector +; +comapi_writeadv equ adv_write + + section .data + +%macro int21 2 + db %1 + dw %2 +%endmacro + +int21_table: + int21 00h, comboot_return + int21 01h, comboot_getkey + int21 02h, comboot_writechr + int21 04h, comboot_writeserial + int21 08h, comboot_getkeynoecho + int21 09h, comboot_writestr + int21 0Bh, comboot_checkkey + int21 30h, comboot_checkver + int21 4Ch, comboot_return + int21 -1, comboot_bogus +int21_count equ ($-int21_table)/3 + + align 2, db 0 +int22_table: + dw comapi_err ; 0000 unimplemented syscall + dw comapi_get_version ; 0001 get SYSLINUX version + dw comapi_writestr ; 0002 write string + dw comapi_run ; 0003 run specified command + dw comapi_run_default ; 0004 run default command + dw comapi_textmode ; 0005 force text mode + dw comapi_open ; 0006 open file + dw comapi_read ; 0007 read file + dw comapi_close ; 0008 close file + dw comapi_pxecall ; 0009 call PXE stack + dw comapi_derinfo ; 000A derivative-specific info + dw comapi_serialcfg ; 000B get serial port config + dw comapi_cleanup ; 000C perform final cleanup + dw comapi_chainboot ; 000D clean up then bootstrap + dw comapi_configfile ; 000E get name of config file + dw comapi_ipappend ; 000F get ipappend strings + dw comapi_dnsresolv ; 0010 resolve hostname + dw comapi_maxshuffle ; 0011 maximum shuffle descriptors + dw comapi_shuffle ; 0012 cleanup, shuffle and boot + dw comapi_idle ; 0013 idle call + dw comapi_localboot ; 0014 local boot + dw comapi_features ; 0015 feature flags + dw comapi_runkernel ; 0016 run kernel image + dw comapi_usingvga ; 0017 report video mode change + dw comapi_userfont ; 0018 query custom font + dw comapi_readdisk ; 0019 read disk + dw comapi_shufflepm ; 001A cleanup, shuffle and boot to pm + dw comapi_shufflerm ; 001B cleanup, shuffle and boot to rm + dw comapi_getadv ; 001C get pointer to ADV + dw comapi_writeadv ; 001D write ADV to disk +int22_count equ ($-int22_table)/2 + +APIKeyWait db 0 +APIKeyFlag db 0 + +zero_string db 0 ; Empty, null-terminated string + +; +; This is the feature flag array for INT 22h AX=0015h +feature_flags: +%if IS_PXELINUX + db 1 ; Have local boot, idle not noop +%elif IS_ISOLINUX + db 3 ; Have local boot, idle is noop +%else + db 2 ; No local boot, idle is noop +%endif +feature_flags_len equ ($-feature_flags) + +err_notdos db ': attempted DOS system call', CR, LF, 0 +err_comlarge db 'COMBOOT image too large.', CR, LF, 0 + + section .bss1 +ConfigName resb FILENAME_MAX diff --git a/core/config.inc b/core/config.inc new file mode 100644 index 00000000..f696f291 --- /dev/null +++ b/core/config.inc @@ -0,0 +1,60 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 2002-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; config.inc +;; +;; Common configuration options. Some of these are imposed by the kernel. +;; + +%ifndef _CONFIG_INC +%define _CONFIG_INC + +max_cmd_len equ 2047 ; Must be &3; 2047 is the kernel limit +HIGHMEM_MAX equ 037FFFFFFh ; DEFAULT highest address for an initrd +DEFAULT_BAUD equ 9600 ; Default baud rate for serial port +BAUD_DIVISOR equ 115200 ; Serial port parameter +MAX_FKEYS equ 12 ; Number of F-key help files + +%assign DO_WBINVD 0 ; Should we use WBINVD or not? + +; +; Local boot supported +; +%assign HAS_LOCALBOOT 1 + +; +; Set this to return the A20 gate to its previous state, instead of +; leaving it open. This has caused problems, because there appear +; to be a race condition between disabling the A20 gate and trying to +; re-enter protected mode, causing the A20 gate disable to take effect +; after we have already done the A20 enabled check, with disastrous +; consequences. Plus, there seems to be little or no demand for it. +; +%assign DISABLE_A20 0 + + +; +; Version number definitinons +; +%ifndef DEPEND ; Generated file +%include "../version.gen" +%endif + +; +; Should be updated with every release to avoid bootsector/SYS file mismatch +; +%define version_str VERSION ; Must be 4 characters long! +%define date DATE_STR ; Defined from the Makefile +%define year '2008' + +%endif ; _CONFIG_INC diff --git a/core/configinit.inc b/core/configinit.inc new file mode 100644 index 00000000..cf6a814c --- /dev/null +++ b/core/configinit.inc @@ -0,0 +1,59 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; configinit.inc +;; +;; Initialize the configuration section +;; + + section .text + +reset_config: + call highmemsize + + ; Initialize the .config section + xor eax,eax + mov si,__config_lma + mov di,__config_start + mov cx,__config_dwords + rep movsd + +%ifndef DEPEND +%if NULLFILE != 0 + mov al,NULLFILE + mov di,FKeyName + mov cx,MAX_FKEYS*(1 << FILENAME_MAX_LG2) + rep stosb +%endif +%endif + + mov si,linuxauto_cmd ; Default command: "linux auto" + mov di,default_cmd + mov cx,linuxauto_len + rep movsb + + mov di,KbdMap ; Default keymap 1:1 + xor al,al + inc ch ; CX <- 256 +mkkeymap: stosb + inc al + loop mkkeymap + + mov eax,[HighMemSize] + mov [VKernelEnd],eax + + ret + + section .data +linuxauto_cmd db 'linux auto',0 +linuxauto_len equ $-linuxauto_cmd diff --git a/core/conio.inc b/core/conio.inc new file mode 100644 index 00000000..47005961 --- /dev/null +++ b/core/conio.inc @@ -0,0 +1,391 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; conio.inc +;; +;; Console I/O code, except: +;; writechr, writestr - module-dependent +;; cwritestr, crlf - writestr.inc +;; writehex* - writehex.inc +;; + +; +; loadkeys: Load a LILO-style keymap; SI and DX:AX set by searchdir +; + section .text + +loadkeys: + and dx,dx ; Should be 256 bytes exactly + jne loadkeys_ret + cmp ax,256 + jne loadkeys_ret + + mov bx,trackbuf + mov cx,1 ; 1 cluster should be >= 256 bytes + call getfssec + + mov si,trackbuf + mov di,KbdMap + mov cx,256 >> 2 + rep movsd + +loadkeys_ret: ret + +; +; get_msg_file: Load a text file and write its contents to the screen, +; interpreting color codes. Call with the file already +; on the top of the open/getc stack. +; +; Assumes CS == DS == ES. +; +get_msg_file: + mov byte [TextAttribute],07h ; Default grey on white + mov byte [DisplayMask],07h ; Display text in all modes + call msg_initvars + +print_msg_file: +.getc: + call getc + jc .done + cmp al,1Ah ; DOS EOF? + je .done + movzx cx,byte [UsingVGA] + and cl,01h + inc cx ; CL <- 01h = text mode, + ; 02h = graphics mode + call [NextCharJump] ; Do what shall be done + jmp .getc +.done: + jmp close ; Tailcall! + +msg_putchar: ; Normal character + cmp al,0Fh ; ^O = color code follows + je msg_ctrl_o + cmp al,0Dh ; Ignore <CR> + je msg_ignore + cmp al,0Ah ; <LF> = newline + je msg_newline + cmp al,0Ch ; <FF> = clear screen + je msg_formfeed + cmp al,07h ; <BEL> = beep + je msg_beep + cmp al,19h ; <EM> = return to text mode + je msg_novga + cmp al,18h ; <CAN> = VGA filename follows + je msg_vga + jnb .not_modectl + cmp al,10h ; 10h to 17h are mode controls + jae msg_modectl +.not_modectl: + +msg_normal: call write_serial_displaymask ; Write to serial port + test [DisplayMask],cl + jz msg_ignore ; Not screen + test byte [DisplayCon],01h + jz msg_ignore + mov bl,[TextAttribute] + mov bh,[BIOS_page] + mov ah,09h ; Write character/attribute + mov cx,1 ; One character only + int 10h ; Write to screen + mov al,[CursorCol] + inc ax + cmp al,[VidCols] + ja msg_line_wrap ; Screen wraparound + mov [CursorCol],al + +msg_gotoxy: mov bh,[BIOS_page] + mov dx,[CursorDX] + mov ah,02h ; Set cursor position + int 10h +msg_ignore: ret + +msg_beep: mov ax,0E07h ; Beep + xor bx,bx + int 10h + ret + +msg_ctrl_o: ; ^O = color code follows + mov word [NextCharJump],msg_setbg + ret +msg_newline: ; Newline char or end of line + mov si,crlf_msg + call write_serial_str_displaymask +msg_line_wrap: ; Screen wraparound + test [DisplayMask],cl + jz msg_ignore + mov byte [CursorCol],0 + mov al,[CursorRow] + inc ax + cmp al,[VidRows] + ja msg_scroll + mov [CursorRow],al + jmp short msg_gotoxy +msg_scroll: xor cx,cx ; Upper left hand corner + mov dx,[ScreenSize] + mov [CursorRow],dh ; New cursor at the bottom + mov bh,[ScrollAttribute] + mov ax,0601h ; Scroll up one line + int 10h + jmp short msg_gotoxy +msg_formfeed: ; Form feed character + mov si,crff_msg + call write_serial_str_displaymask + test [DisplayMask],cl + jz msg_ignore + xor cx,cx + mov [CursorDX],cx ; Upper lefthand corner + mov dx,[ScreenSize] + mov bh,[TextAttribute] + mov ax,0600h ; Clear screen region + int 10h + jmp msg_gotoxy +msg_setbg: ; Color background character + call unhexchar + jc msg_color_bad + shl al,4 + test [DisplayMask],cl + jz .dontset + mov [TextAttribute],al +.dontset: + mov word [NextCharJump],msg_setfg + ret +msg_setfg: ; Color foreground character + call unhexchar + jc msg_color_bad + test [DisplayMask],cl + jz .dontset + or [TextAttribute],al ; setbg set foreground to 0 +.dontset: + jmp short msg_putcharnext +msg_vga: + mov word [NextCharJump],msg_filename + mov di, VGAFileBuf + jmp short msg_setvgafileptr + +msg_color_bad: + mov byte [TextAttribute],07h ; Default attribute +msg_putcharnext: + mov word [NextCharJump],msg_putchar + ret + +msg_filename: ; Getting VGA filename + cmp al,0Ah ; <LF> = end of filename + je msg_viewimage + cmp al,' ' + jbe msg_ret ; Ignore space/control char + mov di,[VGAFilePtr] + cmp di,VGAFileBufEnd + jnb msg_ret + mov [di],al ; Can't use stosb (DS:) + inc di +msg_setvgafileptr: + mov [VGAFilePtr],di +msg_ret: ret + +msg_novga: + call vgaclearmode + jmp short msg_initvars + +msg_viewimage: + mov si,[VGAFilePtr] + mov byte [si],0 ; Zero-terminate filename + mov si,VGAFileBuf + mov di,VGAFileMBuf + call mangle_name + call open + jz msg_putcharnext ; Not there + call vgadisplayfile + ; Fall through + + ; Subroutine to initialize variables, also needed + ; after loading a graphics file +msg_initvars: + pusha + mov bh,[BIOS_page] + mov ah,03h ; Read cursor position + int 10h + mov [CursorDX],dx + popa + jmp short msg_putcharnext ; Initialize state machine + +msg_modectl: + and al,07h + mov [DisplayMask],al + jmp short msg_putcharnext + +; +; write_serial: If serial output is enabled, write character on serial port +; write_serial_displaymask: d:o, but ignore if DisplayMask & 04h == 0 +; +write_serial_displaymask: + test byte [DisplayMask], 04h + jz write_serial.end +write_serial: + pushfd + pushad + mov bx,[SerialPort] + and bx,bx + je .noserial + push ax + mov ah,[FlowInput] +.waitspace: + ; Wait for space in transmit register + lea dx,[bx+5] ; DX -> LSR + in al,dx + test al,20h + jz .waitspace + + ; Wait for input flow control + inc dx ; DX -> MSR + in al,dx + and al,ah + cmp al,ah + jne .waitspace +.no_flow: + + xchg dx,bx ; DX -> THR + pop ax + call slow_out ; Send data +.noserial: popad + popfd +.end: ret + +; +; write_serial_str: write_serial for strings +; write_serial_str_displaymask: d:o, but ignore if DisplayMask & 04h == 0 +; +write_serial_str_displaymask: + test byte [DisplayMask], 04h + jz write_serial_str.end + +write_serial_str: +.loop lodsb + and al,al + jz .end + call write_serial + jmp short .loop +.end: ret + +; +; pollchar: check if we have an input character pending (ZF = 0) +; +pollchar: + pushad + mov ah,11h ; Poll keyboard + int 16h + jnz .done ; Keyboard response + mov dx,[SerialPort] + and dx,dx + jz .done ; No serial port -> no input + add dx,byte 5 ; DX -> LSR + in al,dx + test al,1 ; ZF = 0 if data pending + jz .done + inc dx ; DX -> MSR + mov ah,[FlowIgnore] ; Required status bits + in al,dx + and al,ah + cmp al,ah + setne al + dec al ; Set ZF = 0 if equal +.done: popad + ret + +; +; getchar: Read a character from keyboard or serial port +; +getchar: + RESET_IDLE +.again: + DO_IDLE + mov ah,11h ; Poll keyboard + int 16h + jnz .kbd ; Keyboard input? + mov bx,[SerialPort] + and bx,bx + jz .again + lea dx,[bx+5] ; DX -> LSR + in al,dx + test al,1 + jz .again + inc dx ; DX -> MSR + mov ah,[FlowIgnore] + in al,dx + and al,ah + cmp al,ah + jne .again +.serial: xor ah,ah ; Avoid confusion + xchg dx,bx ; Data port + in al,dx + ret +.kbd: mov ah,10h ; Get keyboard input + int 16h + cmp al,0E0h + jnz .not_ext + xor al,al +.not_ext: + and al,al + jz .func_key + mov bx,KbdMap ; Convert character sets + xlatb +.func_key: ret + +%ifdef DEBUG_TRACERS +; +; debug hack to print a character with minimal code impact +; +debug_tracer: pushad + pushfd + mov bp,sp + mov bx,[bp+9*4] ; Get return address + mov al,[cs:bx] ; Get data byte + inc word [bp+9*4] ; Return to after data byte + call writechr + popfd + popad + ret +%endif ; DEBUG_TRACERS + + section .data +%if IS_ISOLINUX == 0 ; Defined elsewhere for ISOLINUX +crlf_msg db CR, LF +null_msg db 0 +%endif +crff_msg db CR, FF, 0 + + section .config + ; This is a word to pc_setint16 can set it +DisplayCon dw 01h ; Console display enabled + +ScrollAttribute db 07h ; Grey on white (normal text color) + + section .bss + alignb 2 +NextCharJump resw 1 ; Routine to interpret next print char +CursorDX equ $ +CursorCol resb 1 ; Cursor column for message file +CursorRow resb 1 ; Cursor row for message file +ScreenSize equ $ +VidCols resb 1 ; Columns on screen-1 +VidRows resb 1 ; Rows on screen-1 + +; Serial console stuff... +BaudDivisor resw 1 ; Baud rate divisor +FlowControl equ $ +FlowOutput resb 1 ; Outputs to assert for serial flow +FlowInput resb 1 ; Input bits for serial flow +FlowIgnore resb 1 ; Ignore input unless these bits set + +TextAttribute resb 1 ; Text attribute for message file +DisplayMask resb 1 ; Display modes mask diff --git a/core/cpuinit.inc b/core/cpuinit.inc new file mode 100644 index 00000000..fd62cc77 --- /dev/null +++ b/core/cpuinit.inc @@ -0,0 +1,86 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; cpuinit.inc +;; +;; CPU-dependent initialization and related checks. +;; + +check_escapes: + mov ah,02h ; Check keyboard flags + int 16h + mov [KbdFlags],al ; Save for boot prompt check + test al,04h ; Ctrl->skip 386 check + jnz skip_checks + +; +; Now check that there is sufficient low (DOS) memory +; +; NOTE: Linux doesn't use all of real_mode_seg, but we use the same +; segment for COMBOOT images, which can use all 64K +; +dosram_k equ (real_mode_seg+0x1000) >> 6 ; Minimum DOS memory (K) + int 12h + cmp ax,dosram_k + jae enough_ram + mov si,err_noram + call writestr + jmp kaboom +enough_ram: +skip_checks: + +; +; Initialize the bcopy32 code in low memory +; + mov si,__bcopy32_lma + mov di,__bcopy32_start + mov cx,__bcopy32_dwords + rep movsd + +; +; Check if we're 386 (as opposed to 486+); if so we need to blank out +; the WBINVD instruction +; +; We check for 486 by setting EFLAGS.AC +; +%if DO_WBINVD + pushfd ; Save the good flags + pushfd + pop eax + mov ebx,eax + xor eax,(1 << 18) ; AC bit + push eax + popfd + pushfd + pop eax + popfd ; Restore the original flags + xor eax,ebx + jnz is_486 +; +; 386 - Looks like we better blot out the WBINVD instruction +; + mov byte [try_wbinvd],0c3h ; Near RET +is_486: +%endif ; DO_WBINVD + + section .data +err_noram db 'It appears your computer has less than ' + asciidec dosram_k + db 'K of low ("DOS")' + db CR, LF + db 'RAM. Linux needs at least this amount to boot. If you get' + db CR, LF + db 'this message in error, hold down the Ctrl key while' + db CR, LF + db 'booting, and I will take your word for it.', CR, LF, 0 + section .text diff --git a/core/dnsresolv.inc b/core/dnsresolv.inc new file mode 100644 index 00000000..f31c578b --- /dev/null +++ b/core/dnsresolv.inc @@ -0,0 +1,380 @@ +; -*- fundamental -*- +; ----------------------------------------------------------------------- +; +; Copyright 2004-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 53 Temple Place Ste 330, +; Bostom MA 02111-1307, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; ----------------------------------------------------------------------- + +; +; dnsresolv.inc +; +; Very simple DNS resolver (assumes recursion-enabled DNS server; +; this should be the normal thing for client-serving DNS servers.) +; + +DNS_PORT equ htons(53) ; Default DNS port +DNS_MAX_PACKET equ 512 ; Defined by protocol +; TFTP uses the range 49152-57343 +DNS_LOCAL_PORT equ htons(60053) ; All local DNS queries come from this port # +DNS_MAX_SERVERS equ 4 ; Max no of DNS servers + + section .text + +; +; Turn a string in DS:SI into a DNS "label set" in ES:DI. +; On return, DI points to the first byte after the label set, +; and SI to the terminating byte. +; +; On return, DX contains the number of dots encountered. +; +dns_mangle: + push ax + push bx + xor dx,dx +.isdot: + inc dx + xor al,al + mov bx,di + stosb +.getbyte: + lodsb + and al,al + jz .endstring + cmp al,':' + jz .endstring + cmp al,'.' + je .isdot + inc byte [es:bx] + stosb + jmp .getbyte +.endstring: + dec si + dec dx ; We always counted one high + cmp byte [es:bx],0 + jz .done + xor al,al + stosb +.done: + pop bx + pop ax + ret + +; +; Compare two sets of DNS labels, in DS:SI and ES:DI; the one in SI +; is allowed pointers relative to a packet in DNSRecvBuf. +; +; Assumes DS == ES. ZF = 1 if same; no registers changed. +; (Note: change reference to [di] to [es:di] to remove DS == ES assumption) +; +dns_compare: + pusha +%if 0 + +.label: + lodsb + cmp al,0C0h + jb .noptr + and al,03Fh ; Get pointer value + mov ah,al ; ... in network byte order! + lodsb + mov si,DNSRecvBuf + add si,ax + jmp .label +.noptr: + cmp al,[di] + jne .done ; Mismatch + inc di + movzx cx,al ; End label? + and cx,cx ; ZF = 1 if match + jz .done + + ; We have a string of bytes that need to match now + repe cmpsb + je .label + +.done: +%else + xor ax,ax +%endif + popa + ret + +; +; Skip past a DNS label set in DS:SI. +; +dns_skiplabel: + push ax + xor ax,ax ; AH == 0 +.loop: + lodsb + cmp al,0C0h ; Pointer? + jae .ptr + and al,al + jz .done + add si,ax + jmp .loop +.ptr: + inc si ; Pointer is two bytes +.done: + pop ax + ret + + ; DNS header format + struc dnshdr +.id: resw 1 +.flags: resw 1 +.qdcount: resw 1 +.ancount: resw 1 +.nscount: resw 1 +.arcount: resw 1 + endstruc + + ; DNS query + struc dnsquery +.qtype: resw 1 +.qclass: resw 1 + endstruc + + ; DNS RR + struc dnsrr +.type: resw 1 +.class: resw 1 +.ttl: resd 1 +.rdlength: resw 1 +.rdata: equ $ + endstruc + + section .bss2 + alignb 2 +DNSSendBuf resb DNS_MAX_PACKET +DNSRecvBuf resb DNS_MAX_PACKET +LocalDomain resb 256 ; Max possible length +DNSServers resd DNS_MAX_SERVERS + + section .data +pxe_udp_write_pkt_dns: +.status: dw 0 ; Status +.sip: dd 0 ; Server IP +.gip: dd 0 ; Gateway IP +.lport: dw DNS_LOCAL_PORT ; Local port +.rport: dw DNS_PORT ; Remote port +.buffersize: dw 0 ; Size of packet +.buffer: dw DNSSendBuf, 0 ; off, seg of buffer + +pxe_udp_read_pkt_dns: +.status: dw 0 ; Status +.sip: dd 0 ; Source IP +.dip: dd 0 ; Destination (our) IP +.rport: dw DNS_PORT ; Remote port +.lport: dw DNS_LOCAL_PORT ; Local port +.buffersize: dw DNS_MAX_PACKET ; Max packet size +.buffer: dw DNSRecvBuf, 0 ; off, seg of buffer + +LastDNSServer dw DNSServers + +; Actual resolver function +; Points to a null-terminated or :-terminated string in DS:SI +; and returns the name in EAX if it exists and can be found. +; If EAX = 0 on exit, the lookup failed. +; +; No segment assumptions permitted. +; + section .text +dns_resolv: + push ds + push es + push di + push cx + push dx + + push cs + pop es ; ES <- CS + + ; First, build query packet + mov di,DNSSendBuf+dnshdr.flags + inc word [es:di-2] ; New query ID + mov ax,htons(0100h) ; Recursion requested + stosw + mov ax,htons(1) ; One question + stosw + xor ax,ax ; No answers, NS or ARs + stosw + stosw + stosw + + call dns_mangle ; Convert name to DNS labels + + push cs ; DS <- CS + pop ds + + push si ; Save pointer to after DNS string + + ; Initialize... + mov eax,[MyIP] + mov [pxe_udp_read_pkt_dns.dip],eax + + and dx,dx + jnz .fqdn ; If we have dots, assume it's FQDN + dec di ; Remove final null + mov si,LocalDomain + call strcpy ; Uncompressed DNS label set so it ends in null +.fqdn: + + mov ax,htons(1) + stosw ; QTYPE = 1 = A + stosw ; QCLASS = 1 = IN + + sub di,DNSSendBuf + mov [pxe_udp_write_pkt_dns.buffersize],di + + ; Now, send it to the nameserver(s) + ; Outer loop: exponential backoff + ; Inner loop: scan the various DNS servers + + mov dx,PKT_TIMEOUT + mov cx,PKT_RETRY +.backoff: + mov si,DNSServers +.servers: + cmp si,[LastDNSServer] + jb .moreservers + +.nomoreservers: + add dx,dx ; Exponential backoff + loop .backoff + + xor eax,eax ; Nothing... +.done: + pop si + pop dx + pop cx + pop di + pop es + pop ds + ret + +.moreservers: + lodsd ; EAX <- next server + push si + push cx + push dx + + mov word [pxe_udp_write_pkt_dns.status],0 + + mov [pxe_udp_write_pkt_dns.sip],eax + mov [pxe_udp_read_pkt_dns.sip],eax + xor eax,[MyIP] + and eax,[Netmask] + jz .nogw + mov eax,[Gateway] +.nogw: + mov [pxe_udp_write_pkt_dns.gip],eax + + mov di,pxe_udp_write_pkt_dns + mov bx,PXENV_UDP_WRITE + call pxenv + jc .timeout ; Treat failed transmit as timeout + cmp word [pxe_udp_write_pkt_dns.status],0 + jne .timeout + + mov cx,[BIOS_timer] +.waitrecv: + mov ax,[BIOS_timer] + sub ax,cx + cmp ax,dx + jae .timeout + + mov word [pxe_udp_read_pkt_dns.status],0 + mov word [pxe_udp_read_pkt_dns.buffersize],DNS_MAX_PACKET + mov di,pxe_udp_read_pkt_dns + mov bx,PXENV_UDP_READ + call pxenv + and ax,ax + jnz .waitrecv + cmp [pxe_udp_read_pkt_dns.status],ax + jnz .waitrecv + + ; Got a packet, deal with it... + mov si,DNSRecvBuf + lodsw + cmp ax,[DNSSendBuf] ; ID + jne .waitrecv ; Not ours + + lodsw ; flags + xor al,80h ; Query#/Answer bit + test ax,htons(0F80Fh) + jnz .badness + + lodsw + xchg ah,al ; ntohs + mov cx,ax ; Questions echoed + lodsw + xchg ah,al ; ntohs + push ax ; Replies + lodsw ; NS records + lodsw ; Authority records + + jcxz .qskipped +.skipq: + call dns_skiplabel ; Skip name + add si,4 ; Skip question trailer + loop .skipq + +.qskipped: + pop cx ; Number of replies + jcxz .badness + +.parseanswer: + mov di,DNSSendBuf+dnshdr_size + call dns_compare + pushf + call dns_skiplabel + mov ax,[si+8] ; RDLENGTH + xchg ah,al ; ntohs + popf + jnz .notsame + cmp dword [si],htons(1)*0x10001 ; TYPE = A, CLASS = IN? + jne .notsame + cmp ax,4 ; RDLENGTH = 4? + jne .notsame + ; + ; We hit paydirt here... + ; + mov eax,[si+10] +.gotresult: + add sp,6 ; Drop timeout information + jmp .done + +.notsame: + add si,10 + add si,ax + loop .parseanswer + +.badness: + ; We got back no data from this server. Unfortunately, for a recursive, + ; non-authoritative query there is no such thing as an NXDOMAIN reply, + ; which technically means we can't draw any conclusions. However, + ; in practice that means the domain doesn't exist. If this turns out + ; to be a problem, we may want to add code to go through all the servers + ; before giving up. + + ; If the DNS server wasn't capable of recursion, and isn't capable + ; of giving us an authoritative reply (i.e. neither AA or RA set), + ; then at least try a different setver... + test word [DNSRecvBuf+dnshdr.flags],htons(0480h) + jz .timeout + + xor eax,eax + jmp .gotresult + +.timeout: + pop dx + pop cx + pop si + jmp .servers diff --git a/core/ext2_fs.inc b/core/ext2_fs.inc new file mode 100644 index 00000000..e84efb14 --- /dev/null +++ b/core/ext2_fs.inc @@ -0,0 +1,183 @@ +; ----------------------------------------------------------------------- +; +; Copyright 1998-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 675 Mass Ave, Cambridge MA 02139, +; USA; either version 2 of the License, or (at your option) any later +; version; incorporated herein by reference. +; +; ----------------------------------------------------------------------- + +; +; ext2_fs.inc +; +; NASM include file for ext2fs data structures +; + +%define EXT2_SUPER_MAGIC 0xEF53 + +%define EXT2_GOOD_OLD_REV 0 ; The good old (original) format +%define EXT2_DYNAMIC_REV 1 ; V2 format w/ dynamic inode sizes +%define EXT2_GOOD_OLD_INODE_SIZE 128 + +; Special inode numbers +%define EXT2_BAD_INO 1 ; Bad blocks inode +%define EXT2_ROOT_INO 2 ; Root inode +%define EXT2_BOOT_LOADER_INO 5 ; Boot loader inode +%define EXT2_UNDEL_DIR_INO 6 ; Undelete directory inode +%define EXT3_RESIZE_INO 7 ; Reserved group descriptors inode +%define EXT3_JOURNAL_INO 8 ; Journal inode + +; We're readonly, so we only care about incompat features. +%define EXT2_FEATURE_INCOMPAT_COMPRESSION 0x0001 +%define EXT2_FEATURE_INCOMPAT_FILETYPE 0x0002 +%define EXT3_FEATURE_INCOMPAT_RECOVER 0x0004 +%define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV 0x0008 +%define EXT2_FEATURE_INCOMPAT_META_BG 0x0010 +%define EXT2_FEATURE_INCOMPAT_ANY 0xffffffff + +%define EXT2_NDIR_BLOCKS 12 +%define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS +%define EXT2_DIND_BLOCK (EXT2_IND_BLOCK+1) +%define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK+1) +%define EXT2_N_BLOCKS (EXT2_TIND_BLOCK+1) + +; +; File types and file modes +; +%define S_IFDIR 0040000o ; Directory +%define S_IFCHR 0020000o ; Character device +%define S_IFBLK 0060000o ; Block device +%define S_IFREG 0100000o ; Regular file +%define S_IFIFO 0010000o ; FIFO +%define S_IFLNK 0120000o ; Symbolic link +%define S_IFSOCK 0140000o ; Socket + +%define S_IFSHIFT 12 + +%define T_IFDIR (S_IFDIR >> S_IFSHIFT) +%define T_IFCHR (S_IFCHR >> S_IFSHIFT) +%define T_IFBLK (S_IFBLK >> S_IFSHIFT) +%define T_IFREG (S_IFREG >> S_IFSHIFT) +%define T_IFIFO (S_IFIFO >> S_IFSHIFT) +%define T_IFLNK (S_IFLNK >> S_IFSHIFT) +%define T_IFSOCK (S_IFSOCK >> S_IFSHIFT) + +; +; Structure definition for the ext2 superblock +; + struc ext2_super_block +s_inodes_count resd 1 ; Inodes count +s_blocks_count resd 1 ; Blocks count +s_r_blocks_count resd 1 ; Reserved blocks count +s_free_blocks_count resd 1 ; Free blocks count +s_free_inodes_count resd 1 ; Free inodes count +s_first_data_block resd 1 ; First Data Block +s_log_block_size resd 1 ; Block size +s_log_frag_size resd 1 ; Fragment size +s_blocks_per_group resd 1 ; # Blocks per group +s_frags_per_group resd 1 ; # Fragments per group +s_inodes_per_group resd 1 ; # Inodes per group +s_mtime resd 1 ; Mount time +s_wtime resd 1 ; Write time +s_mnt_count resw 1 ; Mount count +s_max_mnt_count resw 1 ; Maximal mount count +s_magic resw 1 ; Magic signature +s_state resw 1 ; File system state +s_errors resw 1 ; Behaviour when detecting errors +s_minor_rev_level resw 1 ; minor revision level +s_lastcheck resd 1 ; time of last check +s_checkinterval resd 1 ; max. time between checks +s_creator_os resd 1 ; OS +s_rev_level resd 1 ; Revision level +s_def_resuid resw 1 ; Default uid for reserved blocks +s_def_resgid resw 1 ; Default gid for reserved blocks +s_first_ino resd 1 ; First non-reserved inode +s_inode_size resw 1 ; size of inode structure +s_block_group_nr resw 1 ; block group # of this superblock +s_feature_compat resd 1 ; compatible feature set +s_feature_incompat resd 1 ; incompatible feature set +s_feature_ro_compat resd 1 ; readonly-compatible feature set +s_uuid resb 16 ; 128-bit uuid for volume +s_volume_name resb 16 ; volume name +s_last_mounted resb 64 ; directory where last mounted +s_algorithm_usage_bitmap resd 1 ; For compression +s_prealloc_blocks resb 1 ; Nr of blocks to try to preallocate +s_prealloc_dir_blocks resb 1 ; Nr to preallocate for dirs +s_padding1 resw 1 +s_reserved resd 204 ; Padding to the end of the block + endstruc + +%ifndef DEPEND +%if ext2_super_block_size != 1024 +%error "ext2_super_block definition bogus" +%endif +%endif + +; +; Structure definition for the ext2 inode +; + struc ext2_inode +i_mode resw 1 ; File mode +i_uid resw 1 ; Owner Uid +i_size resd 1 ; Size in bytes +i_atime resd 1 ; Access time +i_ctime resd 1 ; Creation time +i_mtime resd 1 ; Modification time +i_dtime resd 1 ; Deletion Time +i_gid resw 1 ; Group Id +i_links_count resw 1 ; Links count +i_blocks resd 1 ; Blocks count +i_flags resd 1 ; File flags +l_i_reserved1 resd 1 +i_block resd EXT2_N_BLOCKS ; Pointer to blocks +i_version resd 1 ; File version (for NFS) +i_file_acl resd 1 ; File ACL +i_dir_acl resd 1 ; Directory ACL +i_faddr resd 1 ; Fragment address +l_i_frag resb 1 ; Fragment number +l_i_fsize resb 1 ; Fragment size +i_pad1 resw 1 +l_i_reserved2 resd 2 + endstruc + +%ifndef DEPEND +%if ext2_inode_size != 128 +%error "ext2_inode definition bogus" +%endif +%endif + +; +; Structure definition for ext2 block group descriptor +; + struc ext2_group_desc +bg_block_bitmap resd 1 ; Block bitmap block +bg_inode_bitmap resd 1 ; Inode bitmap block +bg_inode_table resd 1 ; Inode table block +bg_free_blocks_count resw 1 ; Free blocks count +bg_free_inodes_count resw 1 ; Free inodes count +bg_used_dirs_count resw 1 ; Used inodes count +bg_pad resw 1 +bg_reserved resd 3 + endstruc + +%ifndef DEPEND +%if ext2_group_desc_size != 32 +%error "ext2_group_desc definition bogus" +%endif +%endif + +%define ext2_group_desc_lg2size 5 + +; +; Structure definition for ext2 directory entry +; + struc ext2_dir_entry +d_inode resd 1 ; Inode number +d_rec_len resw 1 ; Directory entry length +d_name_len resb 1 ; Name length +d_file_type resb 1 ; File type +d_name equ $ + endstruc diff --git a/core/extlinux.asm b/core/extlinux.asm new file mode 100644 index 00000000..6c2946c2 --- /dev/null +++ b/core/extlinux.asm @@ -0,0 +1,1588 @@ +; -*- fundamental -*- (asm-mode sucks) +; **************************************************************************** +; +; extlinux.asm +; +; A program to boot Linux kernels off an ext2/ext3 filesystem. +; +; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 53 Temple Place Ste 330, +; Boston MA 02111-1307, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; **************************************************************************** + +%define IS_EXTLINUX 1 +%include "head.inc" +%include "ext2_fs.inc" + +; +; Some semi-configurable constants... change on your own risk. +; +my_id equ extlinux_id +; NASM 0.98.38 croaks if these are equ's rather than macros... +FILENAME_MAX_LG2 equ 8 ; log2(Max filename size Including final null) +FILENAME_MAX equ (1 << FILENAME_MAX_LG2) ; Max mangled filename size +NULLFILE equ 0 ; Null character == empty filename +NULLOFFSET equ 0 ; Position in which to look +retry_count equ 16 ; How patient are we with the disk? +%assign HIGHMEM_SLOP 0 ; Avoid this much memory near the top +LDLINUX_MAGIC equ 0x3eb202fe ; A random number to identify ourselves with + +MAX_OPEN_LG2 equ 6 ; log2(Max number of open files) +MAX_OPEN equ (1 << MAX_OPEN_LG2) + +SECTOR_SHIFT equ 9 +SECTOR_SIZE equ (1 << SECTOR_SHIFT) + +MAX_SYMLINKS equ 64 ; Maximum number of symlinks per lookup +SYMLINK_SECTORS equ 2 ; Max number of sectors in a symlink + ; (should be >= FILENAME_MAX) + +; +; This is what we need to do when idle +; +%macro RESET_IDLE 0 + ; Nothing +%endmacro +%macro DO_IDLE 0 + ; Nothing +%endmacro + +; +; The following structure is used for "virtual kernels"; i.e. LILO-style +; option labels. The options we permit here are `kernel' and `append +; Since there is no room in the bottom 64K for all of these, we +; stick them in high memory and copy them down before we need them. +; + struc vkernel +vk_vname: resb FILENAME_MAX ; Virtual name **MUST BE FIRST!** +vk_rname: resb FILENAME_MAX ; Real name +vk_appendlen: resw 1 +vk_type: resb 1 ; Type of file + alignb 4 +vk_append: resb max_cmd_len+1 ; Command line + alignb 4 +vk_end: equ $ ; Should be <= vk_size + endstruc + +; +; Segment assignments in the bottom 640K +; Stick to the low 512K in case we're using something like M-systems flash +; which load a driver into low RAM (evil!!) +; +; 0000h - main code/data segment (and BIOS segment) +; +real_mode_seg equ 3000h +cache_seg equ 2000h ; 64K area for metadata cache +xfer_buf_seg equ 1000h ; Bounce buffer for I/O to high mem +comboot_seg equ real_mode_seg ; COMBOOT image loading zone + +; +; File structure. This holds the information for each currently open file. +; + struc open_file_t +file_bytesleft resd 1 ; Number of bytes left (0 = free) +file_sector resd 1 ; Next linear sector to read +file_in_sec resd 1 ; Sector where inode lives +file_in_off resw 1 +file_mode resw 1 + endstruc + +%ifndef DEPEND +%if (open_file_t_size & (open_file_t_size-1)) +%error "open_file_t is not a power of 2" +%endif +%endif + +; --------------------------------------------------------------------------- +; BEGIN CODE +; --------------------------------------------------------------------------- + +; +; Memory below this point is reserved for the BIOS and the MBR +; + section .earlybss +trackbufsize equ 8192 +trackbuf resb trackbufsize ; Track buffer goes here + ; ends at 2800h + + section .bss +SuperBlock resb 1024 ; ext2 superblock +SuperInfo resq 16 ; DOS superblock expanded +ClustSize resd 1 ; Bytes/cluster ("block") +SecPerClust resd 1 ; Sectors/cluster +ClustMask resd 1 ; Sectors/cluster - 1 +PtrsPerBlock1 resd 1 ; Pointers/cluster +PtrsPerBlock2 resd 1 ; (Pointers/cluster)^2 +DriveNumber resb 1 ; BIOS drive number +ClustShift resb 1 ; Shift count for sectors/cluster +ClustByteShift resb 1 ; Shift count for bytes/cluster + + alignb open_file_t_size +Files resb MAX_OPEN*open_file_t_size + + section .text +; +; Some of the things that have to be saved very early are saved +; "close" to the initial stack pointer offset, in order to +; reduce the code size... +; +StackBuf equ $-44-32 ; Start the stack here (grow down - 4K) +PartInfo equ StackBuf ; Saved partition table entry +FloppyTable equ PartInfo+16 ; Floppy info table (must follow PartInfo) +OrigFDCTabPtr equ StackBuf-8 ; The 2nd high dword on the stack +OrigESDI equ StackBuf-4 ; The high dword on the stack + +; +; Primary entry point. Tempting as though it may be, we can't put the +; initial "cli" here; the jmp opcode in the first byte is part of the +; "magic number" (using the term very loosely) for the DOS superblock. +; +bootsec equ $ +_start: jmp short start ; 2 bytes + nop ; 1 byte +; +; "Superblock" follows -- it's in the boot sector, so it's already +; loaded and ready for us +; +bsOemName db 'EXTLINUX' ; The SYS command sets this, so... +; +; These are the fields we actually care about. We end up expanding them +; all to dword size early in the code, so generate labels for both +; the expanded and unexpanded versions. +; +%macro superb 1 +bx %+ %1 equ SuperInfo+($-superblock)*8+4 +bs %+ %1 equ $ + zb 1 +%endmacro +%macro superw 1 +bx %+ %1 equ SuperInfo+($-superblock)*8 +bs %+ %1 equ $ + zw 1 +%endmacro +%macro superd 1 +bx %+ %1 equ $ ; no expansion for dwords +bs %+ %1 equ $ + zd 1 +%endmacro +superblock equ $ + superw BytesPerSec + superb SecPerClust + superw ResSectors + superb FATs + superw RootDirEnts + superw Sectors + superb Media + superw FATsecs + superw SecPerTrack + superw Heads +superinfo_size equ ($-superblock)-1 ; How much to expand + superd Hidden + superd HugeSectors + ; + ; This is as far as FAT12/16 and FAT32 are consistent + ; + zb 54 ; FAT12/16 need 26 more bytes, + ; FAT32 need 54 more bytes +superblock_len equ $-superblock + +; +; Note we don't check the constraints above now; we did that at install +; time (we hope!) +; +start: + cli ; No interrupts yet, please + cld ; Copy upwards +; +; Set up the stack +; + xor ax,ax + mov ss,ax + mov sp,StackBuf ; Just below BSS + push es ; Save initial ES:DI -> $PnP pointer + push di + mov es,ax +; +; DS:SI may contain a partition table entry. Preserve it for us. +; + mov cx,8 ; Save partition info + mov di,PartInfo + rep movsw + + mov ds,ax ; Now we can initialize DS... + +; +; Now sautee the BIOS floppy info block to that it will support decent- +; size transfers; the floppy block is 11 bytes and is stored in the +; INT 1Eh vector (brilliant waste of resources, eh?) +; +; Of course, if BIOSes had been properly programmed, we wouldn't have +; had to waste precious space with this code. +; + mov bx,fdctab + lfs si,[bx] ; FS:SI -> original fdctab + push fs ; Save on stack in case we need to bail + push si + + ; Save the old fdctab even if hard disk so the stack layout + ; is the same. The instructions above do not change the flags + mov [DriveNumber],dl ; Save drive number in DL + and dl,dl ; If floppy disk (00-7F), assume no + ; partition table + js harddisk + +floppy: + mov cl,6 ; 12 bytes (CX == 0) + ; es:di -> FloppyTable already + ; This should be safe to do now, interrupts are off... + mov [bx],di ; FloppyTable + mov [bx+2],ax ; Segment 0 + fs rep movsw ; Faster to move words + mov cl,[bsSecPerTrack] ; Patch the sector count + mov [di-8],cl + ; AX == 0 here + int 13h ; Some BIOSes need this + + jmp short not_harddisk +; +; The drive number and possibly partition information was passed to us +; by the BIOS or previous boot loader (MBR). Current "best practice" is to +; trust that rather than what the superblock contains. +; +; Would it be better to zero out bsHidden if we don't have a partition table? +; +; Note: di points to beyond the end of PartInfo +; +harddisk: + test byte [di-16],7Fh ; Sanity check: "active flag" should + jnz no_partition ; be 00 or 80 + mov eax,[di-8] ; Partition offset (dword) + mov [bsHidden],eax +no_partition: +; +; Get disk drive parameters (don't trust the superblock.) Don't do this for +; floppy drives -- INT 13:08 on floppy drives will (may?) return info about +; what the *drive* supports, not about the *media*. Fortunately floppy disks +; tend to have a fixed, well-defined geometry which is stored in the superblock. +; + ; DL == drive # still + mov ah,08h + int 13h + jc no_driveparm + and ah,ah + jnz no_driveparm + shr dx,8 + inc dx ; Contains # of heads - 1 + mov [bsHeads],dx + and cx,3fh + mov [bsSecPerTrack],cx +no_driveparm: +not_harddisk: +; +; Ready to enable interrupts, captain +; + sti + +; +; Do we have EBIOS (EDD)? +; +eddcheck: + mov bx,55AAh + mov ah,41h ; EDD existence query + mov dl,[DriveNumber] + int 13h + jc .noedd + cmp bx,0AA55h + jne .noedd + test cl,1 ; Extended disk access functionality set + jz .noedd + ; + ; We have EDD support... + ; + mov byte [getlinsec.jmp+1],(getlinsec_ebios-(getlinsec.jmp+2)) +.noedd: + +; +; Load the first sector of LDLINUX.SYS; this used to be all proper +; with parsing the superblock and root directory; it doesn't fit +; together with EBIOS support, unfortunately. +; + mov eax,[FirstSector] ; Sector start + mov bx,ldlinux_sys ; Where to load it + call getonesec + + ; Some modicum of integrity checking + cmp dword [ldlinux_magic+4],LDLINUX_MAGIC^HEXDATE + jne kaboom + + ; Go for it... + jmp ldlinux_ent + +; +; getonesec: get one disk sector +; +getonesec: + mov bp,1 ; One sector + ; Fall through + +; +; getlinsec: load a sequence of BP floppy sector given by the linear sector +; number in EAX into the buffer at ES:BX. We try to optimize +; by loading up to a whole track at a time, but the user +; is responsible for not crossing a 64K boundary. +; (Yes, BP is weird for a count, but it was available...) +; +; On return, BX points to the first byte after the transferred +; block. +; +; This routine assumes CS == DS, and trashes most registers. +; +; Stylistic note: use "xchg" instead of "mov" when the source is a register +; that is dead from that point; this saves space. However, please keep +; the order to dst,src to keep things sane. +; +getlinsec: + add eax,[bsHidden] ; Add partition offset + xor edx,edx ; Zero-extend LBA (eventually allow 64 bits) + +.jmp: jmp strict short getlinsec_cbios + +; +; getlinsec_ebios: +; +; getlinsec implementation for EBIOS (EDD) +; +getlinsec_ebios: +.loop: + push bp ; Sectors left +.retry2: + call maxtrans ; Enforce maximum transfer size + movzx edi,bp ; Sectors we are about to read + mov cx,retry_count +.retry: + + ; Form DAPA on stack + push edx + push eax + push es + push bx + push di + push word 16 + mov si,sp + pushad + mov dl,[DriveNumber] + push ds + push ss + pop ds ; DS <- SS + mov ah,42h ; Extended Read + int 13h + pop ds + popad + lea sp,[si+16] ; Remove DAPA + jc .error + pop bp + add eax,edi ; Advance sector pointer + sub bp,di ; Sectors left + shl di,SECTOR_SHIFT ; 512-byte sectors + add bx,di ; Advance buffer pointer + and bp,bp + jnz .loop + + ret + +.error: + ; Some systems seem to get "stuck" in an error state when + ; using EBIOS. Doesn't happen when using CBIOS, which is + ; good, since some other systems get timeout failures + ; waiting for the floppy disk to spin up. + + pushad ; Try resetting the device + xor ax,ax + mov dl,[DriveNumber] + int 13h + popad + loop .retry ; CX-- and jump if not zero + + ;shr word [MaxTransfer],1 ; Reduce the transfer size + ;jnz .retry2 + + ; Total failure. Try falling back to CBIOS. + mov byte [getlinsec.jmp+1],(getlinsec_cbios-(getlinsec.jmp+2)) + ;mov byte [MaxTransfer],63 ; Max possibe CBIOS transfer + + pop bp + ; ... fall through ... + +; +; getlinsec_cbios: +; +; getlinsec implementation for legacy CBIOS +; +getlinsec_cbios: +.loop: + push edx + push eax + push bp + push bx + + movzx esi,word [bsSecPerTrack] + movzx edi,word [bsHeads] + ; + ; Dividing by sectors to get (track,sector): we may have + ; up to 2^18 tracks, so we need to use 32-bit arithmetric. + ; + div esi + xor cx,cx + xchg cx,dx ; CX <- sector index (0-based) + ; EDX <- 0 + ; eax = track # + div edi ; Convert track to head/cyl + + ; We should test this, but it doesn't fit... + ; cmp eax,1023 + ; ja .error + + ; + ; Now we have AX = cyl, DX = head, CX = sector (0-based), + ; BP = sectors to transfer, SI = bsSecPerTrack, + ; ES:BX = data target + ; + + call maxtrans ; Enforce maximum transfer size + + ; Must not cross track boundaries, so BP <= SI-CX + sub si,cx + cmp bp,si + jna .bp_ok + mov bp,si +.bp_ok: + + shl ah,6 ; Because IBM was STOOPID + ; and thought 8 bits were enough + ; then thought 10 bits were enough... + inc cx ; Sector numbers are 1-based, sigh + or cl,ah + mov ch,al + mov dh,dl + mov dl,[DriveNumber] + xchg ax,bp ; Sector to transfer count + mov ah,02h ; Read sectors + mov bp,retry_count +.retry: + pushad + int 13h + popad + jc .error +.resume: + movzx ecx,al ; ECX <- sectors transferred + shl ax,SECTOR_SHIFT ; Convert sectors in AL to bytes in AX + pop bx + add bx,ax + pop bp + pop eax + pop edx + add eax,ecx + sub bp,cx + jnz .loop + ret + +.error: + dec bp + jnz .retry + + xchg ax,bp ; Sectors transferred <- 0 + shr word [MaxTransfer],1 + jnz .resume + ; Fall through to disk_error + +; +; kaboom: write a message and bail out. +; +disk_error: +kaboom: + xor si,si + mov ss,si + mov sp,StackBuf-4 ; Reset stack + mov ds,si ; Reset data segment + pop dword [fdctab] ; Restore FDC table +.patch: ; When we have full code, intercept here + mov si,bailmsg + + ; Write error message, this assumes screen page 0 +.loop: lodsb + and al,al + jz .done + mov ah,0Eh ; Write to screen as TTY + mov bx,0007h ; Attribute + int 10h + jmp short .loop +.done: + cbw ; AH <- 0 +.again: int 16h ; Wait for keypress + ; NB: replaced by int 18h if + ; chosen at install time.. + int 19h ; And try once more to boot... +.norge: jmp short .norge ; If int 19h returned; this is the end + +; +; Truncate BP to MaxTransfer +; +maxtrans: + cmp bp,[MaxTransfer] + jna .ok + mov bp,[MaxTransfer] +.ok: ret + +; +; Error message on failure +; +bailmsg: db 'Boot error', 0Dh, 0Ah, 0 + + ; This fails if the boot sector overflows + zb 1F8h-($-$$) + +FirstSector dd 0xDEADBEEF ; Location of sector 1 +MaxTransfer dw 0x007F ; Max transfer size + +; This field will be filled in 0xAA55 by the installer, but we abuse it +; to house a pointer to the INT 16h instruction at +; kaboom.again, which gets patched to INT 18h in RAID mode. +bootsignature dw kaboom.again-bootsec + +; +; =========================================================================== +; End of boot sector +; =========================================================================== +; Start of LDLINUX.SYS +; =========================================================================== + +ldlinux_sys: + +syslinux_banner db 0Dh, 0Ah + db 'EXTLINUX ' + db version_str, ' ', date, ' ', 0 + db 0Dh, 0Ah, 1Ah ; EOF if we "type" this in DOS + + align 8, db 0 +ldlinux_magic dd LDLINUX_MAGIC + dd LDLINUX_MAGIC^HEXDATE + +; +; This area is patched by the installer. It is found by looking for +; LDLINUX_MAGIC, plus 8 bytes. +; +patch_area: +LDLDwords dw 0 ; Total dwords starting at ldlinux_sys, + ; not including ADVs +LDLSectors dw 0 ; Number of sectors, not including + ; bootsec & this sec, but including the two ADVs +CheckSum dd 0 ; Checksum starting at ldlinux_sys + ; value = LDLINUX_MAGIC - [sum of dwords] +CurrentDir dd 2 ; "Current" directory inode number + +; Space for up to 64 sectors, the theoretical maximum +SectorPtrs times 64 dd 0 + +ldlinux_ent: +; +; Note that some BIOSes are buggy and run the boot sector at 07C0:0000 +; instead of 0000:7C00 and the like. We don't want to add anything +; more to the boot sector, so it is written to not assume a fixed +; value in CS, but we don't want to deal with that anymore from now +; on. +; + jmp 0:.next +.next: + +; +; Tell the user we got this far +; + mov si,syslinux_banner + call writestr + +; +; Tell the user if we're using EBIOS or CBIOS +; +print_bios: + mov si,cbios_name + cmp byte [getlinsec.jmp+1],(getlinsec_ebios-(getlinsec.jmp+2)) + jne .cbios + mov si,ebios_name +.cbios: + mov [BIOSName],si + call writestr + + section .bss +%define HAVE_BIOSNAME 1 +BIOSName resw 1 + + section .text +; +; Now we read the rest of LDLINUX.SYS. Don't bother loading the first +; sector again, though. +; +load_rest: + mov si,SectorPtrs + mov bx,7C00h+2*SECTOR_SIZE ; Where we start loading + mov cx,[LDLSectors] + +.get_chunk: + jcxz .done + xor bp,bp + lodsd ; First sector of this chunk + + mov edx,eax + +.make_chunk: + inc bp + dec cx + jz .chunk_ready + inc edx ; Next linear sector + cmp [si],edx ; Does it match + jnz .chunk_ready ; If not, this is it + add si,4 ; If so, add sector to chunk + jmp short .make_chunk + +.chunk_ready: + call getlinsecsr + shl bp,SECTOR_SHIFT + add bx,bp + jmp .get_chunk + +.done: + +; +; All loaded up, verify that we got what we needed. +; Note: the checksum field is embedded in the checksum region, so +; by the time we get to the end it should all cancel out. +; +verify_checksum: + mov si,ldlinux_sys + mov cx,[LDLDwords] + mov edx,-LDLINUX_MAGIC +.checksum: + lodsd + add edx,eax + loop .checksum + + and edx,edx ; Should be zero + jz all_read ; We're cool, go for it! + +; +; Uh-oh, something went bad... +; + mov si,checksumerr_msg + call writestr + jmp kaboom + +; +; ----------------------------------------------------------------------------- +; Subroutines that have to be in the first sector +; ----------------------------------------------------------------------------- + +; +; +; writestr: write a null-terminated string to the console +; This assumes we're on page 0. This is only used for early +; messages, so it should be OK. +; +writestr: +.loop: lodsb + and al,al + jz .return + mov ah,0Eh ; Write to screen as TTY + mov bx,0007h ; Attribute + int 10h + jmp short .loop +.return: ret + + +; getlinsecsr: save registers, call getlinsec, restore registers +; +getlinsecsr: pushad + call getlinsec + popad + ret + +; +; Checksum error message +; +checksumerr_msg db ' Load error - ', 0 ; Boot failed appended + +; +; BIOS type string +; +cbios_name db 'CBIOS', 0 +ebios_name db 'EBIOS', 0 + +; +; Debug routine +; +%ifdef debug +safedumpregs: + cmp word [Debug_Magic],0D00Dh + jnz nc_return + jmp dumpregs +%endif + +rl_checkpt equ $ ; Must be <= 8000h + +rl_checkpt_off equ ($-$$) +%ifndef DEPEND +%if rl_checkpt_off > 400h +%error "Sector 1 overflow" +%endif +%endif + +; ---------------------------------------------------------------------------- +; End of code and data that have to be in the first sector +; ---------------------------------------------------------------------------- + +all_read: +; +; Let the user (and programmer!) know we got this far. This used to be +; in Sector 1, but makes a lot more sense here. +; + mov si,copyright_str + call writestr + +; +; Insane hack to expand the DOS superblock to dwords +; +expand_super: + xor eax,eax + mov si,superblock + mov di,SuperInfo + mov cx,superinfo_size +.loop: + lodsw + dec si + stosd ; Store expanded word + xor ah,ah + stosd ; Store expanded byte + loop .loop + +; +; Load the real (ext2) superblock; 1024 bytes long at offset 1024 +; + mov bx,SuperBlock + mov eax,1024 >> SECTOR_SHIFT + mov bp,ax + call getlinsec + +; +; Compute some values... +; + xor edx,edx + inc edx + + ; s_log_block_size = log2(blocksize) - 10 + mov cl,[SuperBlock+s_log_block_size] + add cl,10 + mov [ClustByteShift],cl + mov eax,edx + shl eax,cl + mov [ClustSize],eax + + sub cl,SECTOR_SHIFT + mov [ClustShift],cl + shr eax,SECTOR_SHIFT + mov [SecPerClust],eax + dec eax + mov [ClustMask],eax + + add cl,SECTOR_SHIFT-2 ; 4 bytes/pointer + shl edx,cl + mov [PtrsPerBlock1],edx + shl edx,cl + mov [PtrsPerBlock2],edx + +; +; Common initialization code +; +%include "init.inc" +%include "cpuinit.inc" + +; +; Initialize the metadata cache +; + call initcache + +; +; Now, everything is "up and running"... patch kaboom for more +; verbosity and using the full screen system +; + ; E9 = JMP NEAR + mov dword [kaboom.patch],0e9h+((kaboom2-(kaboom.patch+3)) << 8) + +; +; Now we're all set to start with our *real* business. First load the +; configuration file (if any) and parse it. +; +; In previous versions I avoided using 32-bit registers because of a +; rumour some BIOSes clobbered the upper half of 32-bit registers at +; random. I figure, though, that if there are any of those still left +; they probably won't be trying to install Linux on them... +; +; The code is still ripe with 16-bitisms, though. Not worth the hassle +; to take'm out. In fact, we may want to put them back if we're going +; to boot ELKS at some point. +; + +; +; Load configuration file +; +load_config: + mov si,config_name ; Save config file name + mov di,ConfigName + call strcpy + + mov di,ConfigName + call open + jz no_config_file + +; +; Now we have the config file open. Parse the config file and +; run the user interface. +; +%include "ui.inc" + +; +; getlinsec_ext: same as getlinsec, except load any sector from the zero +; block as all zeros; use to load any data derived +; from an ext2 block pointer, i.e. anything *except the +; superblock.* +; +getonesec_ext: + mov bp,1 + +getlinsec_ext: + cmp eax,[SecPerClust] + jae getlinsec ; Nothing fancy + + ; If we get here, at least part of what we want is in the + ; zero block. Zero one sector at a time and loop. + push eax + push cx + xchg di,bx + xor eax,eax + mov cx,SECTOR_SIZE >> 2 + rep stosd + xchg di,bx + pop cx + pop eax + inc eax + dec bp + jnz getlinsec_ext + ret + +; +; allocate_file: Allocate a file structure +; +; If successful: +; ZF set +; BX = file pointer +; In unsuccessful: +; ZF clear +; +allocate_file: + TRACER 'a' + push cx + mov bx,Files + mov cx,MAX_OPEN +.check: cmp dword [bx], byte 0 + je .found + add bx,open_file_t_size ; ZF = 0 + loop .check + ; ZF = 0 if we fell out of the loop +.found: pop cx + ret +; +; open_inode: +; Open a file indicated by an inode number in EAX +; +; NOTE: This file considers finding a zero-length file an +; error. This is so we don't have to deal with that special +; case elsewhere in the program (most loops have the test +; at the end). +; +; If successful: +; ZF clear +; SI = file pointer +; EAX = file length in bytes +; ThisInode = the first 128 bytes of the inode +; If unsuccessful +; ZF set +; +; Assumes CS == DS == ES. +; +open_inode.allocate_failure: + xor eax,eax + pop bx + pop di + ret + +open_inode: + push di + push bx + call allocate_file + jnz .allocate_failure + + push cx + push gs + ; First, get the appropriate inode group and index + dec eax ; There is no inode 0 + xor edx,edx + mov [bx+file_sector],edx + div dword [SuperBlock+s_inodes_per_group] + ; EAX = inode group; EDX = inode within group + push edx + + ; Now, we need the block group descriptor. + ; To get that, we first need the relevant descriptor block. + + shl eax, ext2_group_desc_lg2size ; Get byte offset in desc table + xor edx,edx + div dword [ClustSize] + ; eax = block #, edx = offset in block + add eax,dword [SuperBlock+s_first_data_block] + inc eax ; s_first_data_block+1 + mov cl,[ClustShift] + shl eax,cl + push edx + shr edx,SECTOR_SHIFT + add eax,edx + pop edx + and dx,SECTOR_SIZE-1 + call getcachesector ; Get the group descriptor + add si,dx + mov esi,[gs:si+bg_inode_table] ; Get inode table block # + pop eax ; Get inode within group + movzx edx, word [SuperBlock+s_inode_size] + mul edx + ; edx:eax = byte offset in inode table + div dword [ClustSize] + ; eax = block # versus inode table, edx = offset in block + add eax,esi + shl eax,cl ; Turn into sector + push dx + shr edx,SECTOR_SHIFT + add eax,edx + mov [bx+file_in_sec],eax + pop dx + and dx,SECTOR_SIZE-1 + mov [bx+file_in_off],dx + + call getcachesector + add si,dx + mov cx,EXT2_GOOD_OLD_INODE_SIZE >> 2 + mov di,ThisInode + gs rep movsd + + mov ax,[ThisInode+i_mode] + mov [bx+file_mode],ax + mov eax,[ThisInode+i_size] + mov [bx+file_bytesleft],eax + mov si,bx + and eax,eax ; ZF clear unless zero-length file + pop gs + pop cx + pop bx + pop di + ret + + section .bss + alignb 4 +ThisInode resb EXT2_GOOD_OLD_INODE_SIZE ; The most recently opened inode + + section .text +; +; close_file: +; Deallocates a file structure (pointer in SI) +; Assumes CS == DS. +; +close_file: + and si,si + jz .closed + mov dword [si],0 ; First dword == file_bytesleft + xor si,si +.closed: ret + +; +; searchdir: +; Search the root directory for a pre-mangled filename in DS:DI. +; +; NOTE: This file considers finding a zero-length file an +; error. This is so we don't have to deal with that special +; case elsewhere in the program (most loops have the test +; at the end). +; +; If successful: +; ZF clear +; SI = file pointer +; DX:AX = EAX = file length in bytes +; If unsuccessful +; ZF set +; +; Assumes CS == DS == ES; *** IS THIS CORRECT ***? +; +searchdir: + push bx + push cx + push bp + mov byte [SymlinkCtr],MAX_SYMLINKS + + mov eax,[CurrentDir] +.begin_path: +.leadingslash: + cmp byte [di],'/' ; Absolute filename? + jne .gotdir + mov eax,EXT2_ROOT_INO + inc di ; Skip slash + jmp .leadingslash +.gotdir: + + ; At this point, EAX contains the directory inode, + ; and DS:DI contains a pathname tail. +.open: + push eax ; Save directory inode + + call open_inode + jz .missing ; If error, done + + mov cx,[si+file_mode] + shr cx,S_IFSHIFT ; Get file type + + cmp cx,T_IFDIR + je .directory + + add sp,4 ; Drop directory inode + + cmp cx,T_IFREG + je .file + cmp cx,T_IFLNK + je .symlink + + ; Otherwise, something bad... +.err: + call close_file +.err_noclose: + xor eax,eax + xor si,si + cwd ; DX <- 0 + +.done: + and eax,eax ; Set/clear ZF + pop bp + pop cx + pop bx + ret + +.missing: + add sp,4 ; Drop directory inode + jmp .done + + ; + ; It's a file. + ; +.file: + cmp byte [di],0 ; End of path? + je .done ; If so, done + jmp .err ; Otherwise, error + + ; + ; It's a directory. + ; +.directory: + pop dword [ThisDir] ; Remember what directory we're searching + + cmp byte [di],0 ; More path? + je .err ; If not, bad + +.skipslash: ; Skip redundant slashes + cmp byte [di],'/' + jne .readdir + inc di + jmp .skipslash + +.readdir: + mov cx,[SecPerClust] + push cx + shl cx,SECTOR_SHIFT + mov bx,trackbuf + add cx,bx + mov [EndBlock],cx + pop cx + push bx + call getfssec + pop bx + pushf ; Save EOF flag + push si ; Save filesystem pointer +.getent: + cmp bx,[EndBlock] + jae .endblock + + push di + cmp dword [bx+d_inode],0 ; Zero inode = void entry + je .nope + + movzx cx,byte [bx+d_name_len] + lea si,[bx+d_name] + repe cmpsb + je .maybe +.nope: + pop di + add bx,[bx+d_rec_len] + jmp .getent + +.endblock: + pop si + popf + jnc .readdir ; There is more + jmp .err ; Otherwise badness... + +.maybe: + mov eax,[bx+d_inode] + + ; Does this match the end of the requested filename? + cmp byte [di],0 + je .finish + cmp byte [di],'/' + jne .nope + + ; We found something; now we need to open the file +.finish: + pop bx ; Adjust stack (di) + pop si + call close_file ; Close directory + pop bx ; Adjust stack (flags) + jmp .open + + ; + ; It's a symlink. We have to determine if it's a fast symlink + ; (data stored in the inode) or not (data stored as a regular + ; file.) Either which way, we start from the directory + ; which we just visited if relative, or from the root directory + ; if absolute, and append any remaining part of the path. + ; +.symlink: + dec byte [SymlinkCtr] + jz .err ; Too many symlink references + + cmp eax,SYMLINK_SECTORS*SECTOR_SIZE + jae .err ; Symlink too long + + ; Computation for fast symlink, as defined by ext2/3 spec + xor ecx,ecx + cmp [ThisInode+i_file_acl],ecx + setne cl ; ECX <- i_file_acl ? 1 : 0 + cmp [ThisInode+i_blocks],ecx + jne .slow_symlink + + ; It's a fast symlink +.fast_symlink: + call close_file ; We've got all we need + mov si,ThisInode+i_block + + push di + mov di,SymlinkTmpBuf + mov ecx,eax + rep movsb + pop si + +.symlink_finish: + cmp byte [si],0 + je .no_slash + mov al,'/' + stosb +.no_slash: + mov bp,SymlinkTmpBufEnd + call strecpy + jc .err_noclose ; Buffer overflow + + ; Now copy it to the "real" buffer; we need to have + ; two buffers so we avoid overwriting the tail on the + ; next copy + mov si,SymlinkTmpBuf + mov di,SymlinkBuf + push di + call strcpy + pop di + mov eax,[ThisDir] ; Resume searching previous directory + jmp .begin_path + +.slow_symlink: + mov bx,SymlinkTmpBuf + mov cx,SYMLINK_SECTORS + call getfssec + ; The EOF closed the file + + mov si,di ; SI = filename tail + mov di,SymlinkTmpBuf + add di,ax ; AX = file length + jmp .symlink_finish + + + section .bss + alignb 4 +SymlinkBuf resb SYMLINK_SECTORS*SECTOR_SIZE+64 +SymlinkTmpBuf equ trackbuf +SymlinkTmpBufEnd equ trackbuf+SYMLINK_SECTORS*SECTOR_SIZE+64 +ThisDir resd 1 +EndBlock resw 1 +SymlinkCtr resb 1 + + section .text +; +; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed +; to by ES:DI; ends on encountering any whitespace. +; DI is preserved. +; +; This verifies that a filename is < FILENAME_MAX characters, +; doesn't contain whitespace, zero-pads the output buffer, +; and removes redundant slashes, +; so "repe cmpsb" can do a compare, and the +; path-searching routine gets a bit of an easier job. +; +; FIX: we may want to support \-escapes here (and this would +; be the place.) +; +mangle_name: + push di + push bx + xor ax,ax + mov cx,FILENAME_MAX-1 + mov bx,di + +.mn_loop: + lodsb + cmp al,' ' ; If control or space, end + jna .mn_end + cmp al,ah ; Repeated slash? + je .mn_skip + xor ah,ah + cmp al,'/' + jne .mn_ok + mov ah,al +.mn_ok stosb +.mn_skip: loop .mn_loop +.mn_end: + cmp bx,di ; At the beginning of the buffer? + jbe .mn_zero + cmp byte [di-1],'/' ; Terminal slash? + jne .mn_zero +.mn_kill: dec di ; If so, remove it + inc cx + jmp short .mn_end +.mn_zero: + inc cx ; At least one null byte + xor ax,ax ; Zero-fill name + rep stosb + pop bx + pop di + ret ; Done + +; +; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled +; filename to the conventional representation. This is needed +; for the BOOT_IMAGE= parameter for the kernel. +; +; DS:SI -> input mangled file name +; ES:DI -> output buffer +; +; On return, DI points to the first byte after the output name, +; which is set to a null byte. +; +unmangle_name: call strcpy + dec di ; Point to final null byte + ret + +; +; +; kaboom2: once everything is loaded, replace the part of kaboom +; starting with "kaboom.patch" with this part + +kaboom2: + mov si,err_bootfailed + call cwritestr + cmp byte [kaboom.again+1],18h ; INT 18h version? + je .int18 + call getchar + call vgaclearmode + int 19h ; And try once more to boot... +.norge: jmp short .norge ; If int 19h returned; this is the end +.int18: + call vgaclearmode + int 18h +.noreg: jmp short .noreg ; Nynorsk + + +; +; linsector: Convert a linear sector index in a file to a linear sector number +; EAX -> linear sector number +; DS:SI -> open_file_t +; +; Returns next sector number in EAX; CF on EOF (not an error!) +; +linsector: + push gs + push ebx + push esi + push edi + push ecx + push edx + push ebp + + push eax ; Save sector index + mov cl,[ClustShift] + shr eax,cl ; Convert to block number + push eax + mov eax,[si+file_in_sec] + mov bx,si + call getcachesector ; Get inode + add si,[bx+file_in_off] ; Get *our* inode + pop eax + lea ebx,[i_block+4*eax] + cmp eax,EXT2_NDIR_BLOCKS + jb .direct + mov ebx,i_block+4*EXT2_IND_BLOCK + sub eax,EXT2_NDIR_BLOCKS + mov ebp,[PtrsPerBlock1] + cmp eax,ebp + jb .ind1 + mov ebx,i_block+4*EXT2_DIND_BLOCK + sub eax,ebp + mov ebp,[PtrsPerBlock2] + cmp eax,ebp + jb .ind2 + mov ebx,i_block+4*EXT2_TIND_BLOCK + sub eax,ebp + +.ind3: + ; Triple indirect; eax contains the block no + ; with respect to the start of the tind area; + ; ebx contains the pointer to the tind block. + xor edx,edx + div dword [PtrsPerBlock2] + ; EAX = which dind block, EDX = pointer within dind block + push ax + shr eax,SECTOR_SHIFT-2 + mov ebp,[gs:si+bx] + shl ebp,cl + add eax,ebp + call getcachesector + pop bx + and bx,(SECTOR_SIZE >> 2)-1 + shl bx,2 + mov eax,edx ; The ind2 code wants the remainder... + +.ind2: + ; Double indirect; eax contains the block no + ; with respect to the start of the dind area; + ; ebx contains the pointer to the dind block. + xor edx,edx + div dword [PtrsPerBlock1] + ; EAX = which ind block, EDX = pointer within ind block + push ax + shr eax,SECTOR_SHIFT-2 + mov ebp,[gs:si+bx] + shl ebp,cl + add eax,ebp + call getcachesector + pop bx + and bx,(SECTOR_SIZE >> 2)-1 + shl bx,2 + mov eax,edx ; The int1 code wants the remainder... + +.ind1: + ; Single indirect; eax contains the block no + ; with respect to the start of the ind area; + ; ebx contains the pointer to the ind block. + push ax + shr eax,SECTOR_SHIFT-2 + mov ebp,[gs:si+bx] + shl ebp,cl + add eax,ebp + call getcachesector + pop bx + and bx,(SECTOR_SIZE >> 2)-1 + shl bx,2 + +.direct: + mov ebx,[gs:bx+si] ; Get the pointer + + pop eax ; Get the sector index again + shl ebx,cl ; Convert block number to sector + and eax,[ClustMask] ; Add offset within block + add eax,ebx + + pop ebp + pop edx + pop ecx + pop edi + pop esi + pop ebx + pop gs + ret + +; +; getfssec: Get multiple sectors from a file +; +; Same as above, except SI is a pointer to a open_file_t +; +; ES:BX -> Buffer +; DS:SI -> Pointer to open_file_t +; CX -> Sector count (0FFFFh = until end of file) +; Must not exceed the ES segment +; Returns CF=1 on EOF (not necessarily error) +; On return ECX = number of bytes read +; All arguments are advanced to reflect data read. +; +getfssec: + push ebp + push eax + push edx + push edi + + movzx ecx,cx + push ecx ; Sectors requested read + mov eax,[si+file_bytesleft] + add eax,SECTOR_SIZE-1 + shr eax,SECTOR_SHIFT + cmp ecx,eax ; Number of sectors left + jbe .lenok + mov cx,ax +.lenok: +.getfragment: + mov eax,[si+file_sector] ; Current start index + mov edi,eax + call linsector + push eax ; Fragment start sector + mov edx,eax + xor ebp,ebp ; Fragment sector count +.getseccnt: + inc bp + dec cx + jz .do_read + xor eax,eax + mov ax,es + shl ax,4 + add ax,bx ; Now DI = how far into 64K block we are + not ax ; Bytes left in 64K block + inc eax + shr eax,SECTOR_SHIFT ; Sectors left in 64K block + cmp bp,ax + jnb .do_read ; Unless there is at least 1 more sector room... + inc edi ; Sector index + inc edx ; Linearly next sector + mov eax,edi + call linsector + ; jc .do_read + cmp edx,eax + je .getseccnt +.do_read: + pop eax ; Linear start sector + pushad + call getlinsec_ext + popad + push bp + shl bp,9 + add bx,bp ; Adjust buffer pointer + pop bp + add [si+file_sector],ebp ; Next sector index + jcxz .done + jnz .getfragment + ; Fall through +.done: + pop ecx ; Sectors requested read + shl ecx,SECTOR_SHIFT + cmp ecx,[si+file_bytesleft] + jb .noteof + mov ecx,[si+file_bytesleft] +.noteof: sub [si+file_bytesleft],ecx + ; Did we run out of file? + cmp dword [si+file_bytesleft],1 + ; CF set if [SI] < 1, i.e. == 0 + pop edi + pop edx + pop eax + pop ebp + ret + +; ----------------------------------------------------------------------------- +; Common modules +; ----------------------------------------------------------------------------- + +%include "getc.inc" ; getc et al +%include "conio.inc" ; Console I/O +%include "plaincon.inc" ; writechr +%include "writestr.inc" ; String output +%include "configinit.inc" ; Initialize configuration +%include "parseconfig.inc" ; High-level config file handling +%include "parsecmd.inc" ; Low-level config file handling +%include "bcopy32.inc" ; 32-bit bcopy +%include "loadhigh.inc" ; Load a file into high memory +%include "font.inc" ; VGA font stuff +%include "graphics.inc" ; VGA graphics +%include "highmem.inc" ; High memory sizing +%include "strcpy.inc" ; strcpy() +%include "strecpy.inc" ; strcpy with end pointer check +%include "cache.inc" ; Metadata disk cache +%include "adv.inc" ; Auxillary Data Vector +%include "localboot.inc" ; Disk-based local boot + +; ----------------------------------------------------------------------------- +; Begin data section +; ----------------------------------------------------------------------------- + + section .data +copyright_str db ' Copyright (C) 1994-', year, ' H. Peter Anvin' + db CR, LF, 0 +err_bootfailed db CR, LF, 'Boot failed: please change disks and press ' + db 'a key to continue.', CR, LF, 0 +config_name db 'extlinux.conf',0 ; Unmangled form + +; +; Command line options we'd like to take a look at +; +; mem= and vga= are handled as normal 32-bit integer values +initrd_cmd db 'initrd=' +initrd_cmd_len equ 7 + +; +; Config file keyword table +; +%include "keywords.inc" + +; +; Extensions to search for (in *forward* order). +; + align 4, db 0 +exten_table: db '.cbt' ; COMBOOT (specific) + db '.img' ; Disk image + db '.bs', 0 ; Boot sector + db '.com' ; COMBOOT (same as DOS) + db '.c32' ; COM32 +exten_table_end: + dd 0, 0 ; Need 8 null bytes here + +; +; Misc initialized (data) variables +; +%ifdef debug ; This code for debugging only +debug_magic dw 0D00Dh ; Debug code sentinel +%endif + + alignb 4, db 0 +BufSafe dw trackbufsize/SECTOR_SIZE ; Clusters we can load into trackbuf +BufSafeBytes dw trackbufsize ; = how many bytes? +%ifndef DEPEND +%if ( trackbufsize % SECTOR_SIZE ) != 0 +%error trackbufsize must be a multiple of SECTOR_SIZE +%endif +%endif diff --git a/core/font.inc b/core/font.inc new file mode 100644 index 00000000..be9a365c --- /dev/null +++ b/core/font.inc @@ -0,0 +1,139 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; font.inc +;; +;; VGA font handling code +;; + + section .text + +; +; loadfont: Load a .psf font file and install it onto the VGA console +; (if we're not on a VGA screen then ignore.) It is called with +; SI and EAX set by routine searchdir +; +loadfont: + ; XXX: This can be 8K+4 bytes and the trackbuf is only + ; guaranteed to be 8K in size... + mov bx,trackbuf + mov cx,[BufSafe] + call getfssec + + mov ax,[trackbuf] ; Magic number + cmp ax,0436h + jne lf_ret + + mov al,[trackbuf+2] ; File mode + cmp al,5 ; Font modes 0-5 supported + ja lf_ret + + mov bh,byte [trackbuf+3] ; Height of font + cmp bh,2 ; VGA minimum + jb lf_ret + cmp bh,32 ; VGA maximum + ja lf_ret + + ; Copy to font buffer + mov si,trackbuf+4 ; Start of font data + mov [VGAFontSize],bh + mov di,vgafontbuf + mov cx,(32*256) >> 2 ; Maximum size + rep movsd + + mov [UserFont], byte 1 ; Set font flag + + ; Fall through to use_font + +; +; use_font: +; This routine activates whatever font happens to be in the +; vgafontbuf, and updates the adjust_screen data. +; Must be called with CS = DS = ES +; +use_font: + test byte [UsingVGA], ~03h ; Nonstandard mode? + jz .modeok + call vgaclearmode + +.modeok: + test [UserFont], byte 1 ; Are we using a user-specified font? + jz adjust_screen ; If not, just do the normal stuff + + mov bp,vgafontbuf + mov bh,[VGAFontSize] + + xor bl,bl ; Needed by both INT 10h calls + + test byte [UsingVGA], 01h ; Are we in graphics mode? + jz .text + +.graphics: + xor cx,cx + mov cl,bh ; CX = bytes/character + mov ax,[GXPixRows] + div cl ; Compute char rows per screen + mov dl,al + dec ax + mov [VidRows],al + mov ax,1121h ; Set user character table + int 10h + mov ax,[GXPixCols] + shr ax,3 ; 8 pixels/character + dec ax + mov [VidCols],al +.lf_ret: ret ; No need to call adjust_screen + +.text: + mov cx,256 + xor dx,dx + mov ax,1110h + int 10h ; Load into VGA RAM + + xor bl,bl + mov ax,1103h ; Select page 0 + int 10h + +lf_ret equ use_font.lf_ret + +; +; adjust_screen: Set the internal variables associated with the screen size. +; This is a subroutine in case we're loading a custom font. +; +adjust_screen: + pusha + mov al,[BIOS_vidrows] + and al,al + jnz vidrows_ok + mov al,24 ; No vidrows in BIOS, assume 25 + ; (Remember: vidrows == rows-1) +vidrows_ok: mov [VidRows],al + mov ah,0fh + int 10h ; Read video state + dec ah ; Store count-1 (same as rows) + mov [VidCols],ah + popa + ret + + section .bss +vgafontbuf resb 8192 + + section .data + align 2, db 0 +VGAFontSize dw 16 ; Defaults to 16 byte font +UserFont db 0 ; Using a user-specified font + + section .bss1 + alignb 4 +GXPixCols resw 1 ; Graphics mode pixel columns +GXPixRows resw 1 ; Graphics mode pixel rows diff --git a/core/genhash.pl b/core/genhash.pl new file mode 100755 index 00000000..c79139fd --- /dev/null +++ b/core/genhash.pl @@ -0,0 +1,26 @@ +#!/usr/bin/perl +# +# Generate hash values for keywords +# + +eval { use bytes; }; + +while ( defined($keywd = <STDIN>) ) { + chomp $keywd; + + ($keywd,$keywdname) = split(/\s+/, $keywd); + $keywdname = $keywd unless ( $keywdname ); + + $l = length($keywd); + $h = 0; + for ( $i = 0 ; $i < $l ; $i++ ) { + $c = ord(substr($keywd,$i,1)) | 0x20; + $h = ((($h << 5)|($h >> 27)) ^ $c) & 0xFFFFFFFF; + } + if ( $seenhash{$h} ) { + printf STDERR "$0: hash collision (0x%08x) %s %s\n", + $h, $keywd, $seenhash{$h}; + } + $seenhash{$h} = $keywd; + printf("%-23s equ 0x%08x\n", "hash_${keywdname}", $h); +} diff --git a/core/getc.inc b/core/getc.inc new file mode 100644 index 00000000..b36115ca --- /dev/null +++ b/core/getc.inc @@ -0,0 +1,381 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; getc.inc +;; +;; Simple file handling library (open, getc, ungetc) +;; +;; WARNING: This interface uses the real_mode_seg/comboot_seg. +;; + +MAX_GETC_LG2 equ 4 ; Max number of file nesting +MAX_GETC equ (1 << MAX_GETC_LG2) +bytes_per_getc_lg2 equ 16-MAX_GETC_LG2 +bytes_per_getc equ (1 << bytes_per_getc_lg2) +secs_per_getc equ bytes_per_getc/SECTOR_SIZE +MAX_UNGET equ 9 ; Max bytes that can be pushed back + + struc getc_file +gc_file resw 1 ; File pointer +gc_bufbytes resw 1 ; Bytes left in buffer +gc_bufdata resw 1 ; Pointer to data in buffer +gc_unget_cnt resb 1 ; Character pushed back count +gc_unget_buf resb MAX_UNGET ; Character pushed back buffer + endstruc +getc_file_lg2 equ 4 ; Size of getc_file as a power of 2 + +%ifndef DEPEND +%if (getc_file_size != (1 << getc_file_lg2)) +%error "getc_file_size != (1 << getc_file_lg2)" +%endif +%endif + +; +; open,getc: Load a file a character at a time for parsing in a manner +; similar to the C library getc routine. +; Up to MAX_GETC files can be open at the same time, +; they are accessed in a stack-like fashion. +; +; All routines assume CS == DS. +; +; open: Input: mangled filename in DS:DI +; Output: ZF set on file not found or zero length +; +; openfd: Input: file handle in SI, file size in EAX +; Output: ZF set on getc stack overflow +; +; getc: Output: CF set on end of file +; Character loaded in AL +; +; close: Output: CF set if nothing open +; +open: + call searchdir + jz openfd.ret +openfd: + push bx + + mov bx,[CurrentGetC] + sub bx,getc_file_size + cmp bx,GetCStack + jb .stack_full ; Excessive nesting + mov [CurrentGetC],bx + + mov [bx+gc_file],si ; File pointer + xor ax,ax + mov [bx+gc_bufbytes],ax ; Buffer empty + mov [bx+gc_unget_cnt],al ; ungetc buffer empty + + inc ax ; ZF <- 0 + pop bx +.ret: ret + +.stack_full: + call close_file + xor ax,ax ; ZF <- 1 + pop bx + ret + +getc: + push bx + push si + push di + push es + + mov di,[CurrentGetC] + movzx bx,byte [di+gc_unget_cnt] + and bx,bx + jnz .have_unget + + mov si,real_mode_seg ; Borrow the real_mode_seg + mov es,si + +.got_data: + sub word [di+gc_bufbytes],1 + jc .get_data ; Was it zero already? + mov si,[di+gc_bufdata] + mov al,[es:si] + inc si + mov [di+gc_bufdata],si +.done: + clc +.ret: + pop es + pop di + pop si + pop bx + ret +.have_unget: + dec bx + mov al,[di+bx+gc_unget_buf] + mov [di+gc_unget_cnt],bl + jmp .done + +.get_data: + pushad + ; Compute start of buffer + mov bx,di + sub bx,GetCStack + shl bx,bytes_per_getc_lg2-getc_file_lg2 + + mov [di+gc_bufdata],bx + mov si,[di+gc_file] + and si,si + mov [di+gc_bufbytes],si ; In case SI == 0 + jz .empty + mov cx,bytes_per_getc >> SECTOR_SHIFT + call getfssec + mov [di+gc_bufbytes],cx + mov [di+gc_file],si + jcxz .empty + popad + TRACER 'd' + jmp .got_data + +.empty: + TRACER 'e' + ; [di+gc_bufbytes] is zero already, thus we will continue + ; to get EOF on any further attempts to read the file. + popad + xor al,al ; Return a predictable zero + stc + jmp .ret + +close: + push bx + push si + mov bx,[CurrentGetC] + mov si,[bx+gc_file] + call close_file + add bx,getc_file_size + mov [CurrentGetC],bx + pop si + pop bx + ret + +; +; ungetc: Push a character (in AL) back into the getc buffer +; Note: if more than MAX_UNGET bytes are pushed back, all +; hell will break loose. +; +ungetc: + push di + push bx + mov di,[CurrentGetC] + movzx bx,[di+gc_unget_cnt] + mov [bx+di+gc_unget_buf],al + inc bx + mov [di+gc_unget_cnt],bl + pop bx + pop di + ret + +; +; skipspace: Skip leading whitespace using "getc". If we hit end-of-line +; or end-of-file, return with carry set; ZF = true of EOF +; ZF = false for EOLN; otherwise CF = ZF = 0. +; +; Otherwise AL = first character after whitespace +; +skipspace: +.loop: call getc + jc .eof + cmp al,1Ah ; DOS EOF + je .eof + cmp al,0Ah + je .eoln + cmp al,' ' + jbe .loop + ret ; CF = ZF = 0 +.eof: cmp al,al ; Set ZF + stc ; Set CF + ret +.eoln: add al,0FFh ; Set CF, clear ZF + ret + +; +; getint: Load an integer from the getc file. +; Return CF if error; otherwise return integer in EBX +; +getint: + mov di,NumBuf +.getnum: cmp di,NumBufEnd ; Last byte in NumBuf + jae .loaded + push di + call getc + pop di + jc .loaded + stosb + cmp al,'-' + jnb .getnum + call ungetc ; Unget non-numeric +.loaded: mov byte [di],0 + mov si,NumBuf + ; Fall through to parseint + +; +; parseint: Convert an integer to a number in EBX +; Get characters from string in DS:SI +; Return CF on error +; DS:SI points to first character after number +; +; Syntaxes accepted: [-]dec, [-]0+oct, [-]0x+hex, val+[KMG] +; +parseint: + push eax + push ecx + push bp + xor eax,eax ; Current digit (keep eax == al) + mov ebx,eax ; Accumulator + mov ecx,ebx ; Base + xor bp,bp ; Used for negative flag +.begin: lodsb + cmp al,'-' + jne .not_minus + xor bp,1 ; Set unary minus flag + jmp short .begin +.not_minus: + cmp al,'0' + jb .err + je .octhex + cmp al,'9' + ja .err + mov cl,10 ; Base = decimal + jmp short .foundbase +.octhex: + lodsb + cmp al,'0' + jb .km ; Value is zero + or al,20h ; Downcase + cmp al,'x' + je .ishex + cmp al,'7' + ja .err + mov cl,8 ; Base = octal + jmp short .foundbase +.ishex: + mov al,'0' ; No numeric value accrued yet + mov cl,16 ; Base = hex +.foundbase: + call unhexchar + jc .km ; Not a (hex) digit + cmp al,cl + jae .km ; Invalid for base + imul ebx,ecx ; Multiply accumulated by base + add ebx,eax ; Add current digit + lodsb + jmp short .foundbase +.km: + dec si ; Back up to last non-numeric + lodsb + or al,20h + cmp al,'k' + je .isk + cmp al,'m' + je .ism + cmp al,'g' + je .isg + dec si ; Back up +.fini: and bp,bp + jz .ret ; CF=0! + neg ebx ; Value was negative +.done: clc +.ret: pop bp + pop ecx + pop eax + ret +.err: stc + jmp short .ret +.isg: shl ebx,10 ; * 2^30 +.ism: shl ebx,10 ; * 2^20 +.isk: shl ebx,10 ; * 2^10 + jmp .fini + + section .bss1 + alignb 4 +NumBuf resb 15 ; Buffer to load number +NumBufEnd resb 1 ; Last byte in NumBuf + +GetCStack resb getc_file_size*MAX_GETC +.end equ $ + + section .data +CurrentGetC dw GetCStack.end ; GetCStack empty + +; +; unhexchar: Convert a hexadecimal digit in AL to the equivalent number; +; return CF=1 if not a hex digit +; + section .text +unhexchar: + cmp al,'0' + jb .ret ; If failure, CF == 1 already + cmp al,'9' + ja .notdigit + sub al,'0' ; CF <- 0 + ret +.notdigit: or al,20h ; upper case -> lower case + cmp al,'a' + jb .ret ; If failure, CF == 1 already + cmp al,'f' + ja .err + sub al,'a'-10 ; CF <- 0 + ret +.err: stc +.ret: ret + +; +; +; getline: Get a command line, converting control characters to spaces +; and collapsing streches to one; a space is appended to the +; end of the string, unless the line is empty. +; The line is terminated by ^J, ^Z or EOF and is written +; to ES:DI. On return, DI points to first char after string. +; CF is set if we hit EOF. +; +getline: + call skipspace + mov dl,1 ; Empty line -> empty string. + jz .eof ; eof + jc .eoln ; eoln + call ungetc +.fillloop: push dx + push di + call getc + pop di + pop dx + jc .ret ; CF set! + cmp al,' ' + jna .ctrl + xor dx,dx +.store: stosb + jmp short .fillloop +.ctrl: cmp al,10 + je .ret ; CF clear! + cmp al,26 + je .eof + and dl,dl + jnz .fillloop ; Ignore multiple spaces + mov al,' ' ; Ctrl -> space + inc dx + jmp short .store +.eoln: clc ; End of line is not end of file + jmp short .ret +.eof: stc +.ret: pushf ; We want the last char to be space! + and dl,dl + jnz .xret + mov al,' ' + stosb +.xret: popf + ret diff --git a/core/graphics.inc b/core/graphics.inc new file mode 100644 index 00000000..2b8290fc --- /dev/null +++ b/core/graphics.inc @@ -0,0 +1,331 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +; ---------------------------------------------------------------------------- +; VGA splash screen code +; ---------------------------------------------------------------------------- + +; +; vgadisplayfile: +; Display a graphical splash screen. +; The file is already opened on the top of the getc stack. +; +; Assumes CS == DS == ES. +; + section .text + +vgadisplayfile: + ; This is a cheap and easy way to make sure the screen is + ; cleared in case we were in graphics mode already + call vgaclearmode + call vgasetmode + jnz .error_nz + +.graphalready: + ; Load the header. + mov cx,4+2*2+16*3 + mov di,LSSHeader +.gethdr: + call getc + stosb + loop .gethdr + jc .error + + ; The header WILL be in the first chunk. + cmp dword [LSSMagic],0x1413f33d ; Magic number +.error_nz: jne .error + + mov dx,GraphColorMap ; Color map offset + mov ax,1012h ; Set RGB registers + xor bx,bx ; First register number + mov cx,16 ; 16 registers + int 10h + +.movecursor: + mov ax,[GraphYSize] ; Number of pixel rows + mov dx,[VGAFontSize] + add ax,dx + dec ax + div dl + xor dx,dx ; Set column to 0 + cmp al,[VidRows] + jb .rowsok + mov al,[VidRows] + dec al +.rowsok: + mov dh,al + mov ah,2 + xor bx,bx + int 10h ; Set cursor below image + + mov cx,[GraphYSize] ; Number of graphics rows + mov word [VGAPos],0 + +.drawpixelrow: + push cx + mov di,VGARowBuffer + ; Pre-clear the row buffer + push di + mov cx,640/4 + xor eax,eax + rep stosd + pop di + push di + mov cx,[GraphXSize] + call rledecode ; Decode one row + pop si + push es + mov di,0A000h ; VGA segment + mov es,di + mov di,[VGAPos] + mov bp,640 + call packedpixel2vga + add word [VGAPos],80 + pop es + pop cx + loop .drawpixelrow + +.error: + jmp close ; Tailcall! + +; +; rledecode: +; Decode a pixel row in RLE16 format. +; +; getc stack -> input +; CX -> pixel count +; ES:DI -> output (packed pixel) +; +rledecode: + xor dx,dx ; DL = last pixel, DH = nybble buffer +.loop: + call .getnybble + cmp al,dl + je .run ; Start of run sequence + stosb + mov dl,al + dec cx + jnz .loop +.done: + ret +.run: + xor bx,bx + call .getnybble + or bl,al + jz .longrun +.dorun: + push cx + mov cx,bx + mov al,dl + rep stosb + pop cx + sub cx,bx + ja .loop + jmp short .done +.longrun: + call .getnybble + mov bl,al + call .getnybble + shl al,4 + or bl,al + add bx,16 + jmp short .dorun + +.getnybble: + test dh,10h + jz .low + and dh,0Fh + mov al,dh + ret +.low: + call getc + mov dh,al + shr dh,4 + or dh,10h ; Nybble already read + and al,0Fh + ret + +; +; packedpixel2vga: +; Convert packed-pixel to VGA bitplanes +; +; DS:SI -> packed pixel string +; BP -> pixel count (multiple of 8) +; ES:DI -> output +; +packedpixel2vga: + mov dx,3C4h ; VGA Sequencer Register select port + mov al,2 ; Sequencer mask + out dx,al ; Select the sequencer mask + inc dx ; VGA Sequencer Register data port + mov al,1 + mov bl,al +.planeloop: + pusha + out dx,al +.loop1: + mov cx,8 +.loop2: + xchg cx,bx + lodsb + shr al,cl + rcl ch,1 ; VGA is bigendian. Sigh. + xchg cx,bx + loop .loop2 + mov al,bh + stosb + sub bp,byte 8 + ja .loop1 + popa + inc bl + shl al,1 + cmp bl,4 + jbe .planeloop + ret + +; +; vgasetmode: +; Enable VGA graphics, if possible; return ZF=1 on success +; DS must be set to the base segment; ES is set to DS. +; +vgasetmode: + push ds + pop es + mov al,[UsingVGA] + cmp al,01h + je .success ; Nothing to do... + test al,04h + jz .notvesa + ; We're in a VESA mode, which means VGA; use VESA call + ; to revert the mode, and then call the conventional + ; mode-setting for good measure... + mov ax,4F02h + mov bx,0012h + int 10h + jmp .setmode +.notvesa: + mov ax,1A00h ; Get video card and monitor + xor bx,bx + int 10h + sub bl, 7 ; BL=07h and BL=08h OK + cmp bl, 1 + ja .error ; ZF=0 +; mov bx,TextColorReg +; mov dx,1009h ; Read color registers +; int 10h +.setmode: + mov ax,0012h ; Set mode = 640x480 VGA 16 colors + int 10h + mov dx,linear_color + mov ax,1002h ; Write color registers + int 10h + mov [UsingVGA], byte 1 + + ; Set GXPixCols and GXPixRows + mov dword [GXPixCols],640+(480 << 16) + + call use_font ; Set graphics font/data + mov byte [ScrollAttribute], 00h + +.success: + xor ax,ax ; Set ZF +.error: + ret + +; +; vgaclearmode: +; Disable VGA graphics. It is not safe to assume any value +; for DS or ES. +; +vgaclearmode: + push ds + push es + pushad + mov ax,cs + mov ds,ax + mov es,ax + mov al,[UsingVGA] + and al,al ; Already in text mode? + jz .done + test al,04h + jz .notvesa + mov ax,4F02h ; VESA return to normal video mode + mov bx,0003h + int 10h +.notvesa: + mov ax,0003h ; Return to normal video mode + int 10h +; mov dx,TextColorReg ; Restore color registers +; mov ax,1002h +; int 10h + mov [UsingVGA], byte 0 + + mov byte [ScrollAttribute], 07h + call use_font ; Restore text font/data +.done: + popad + pop es + pop ds + ret + +; +; vgashowcursor/vgahidecursor: +; If VGA graphics is enabled, draw a cursor/clear a cursor +; +vgashowcursor: + pushad + mov al,'_' + jmp short vgacursorcommon +vgahidecursor: + pushad + mov al,' ' +vgacursorcommon: + cmp [UsingVGA], byte 1 + jne .done + mov ah,09h + mov bx,0007h + mov cx,1 + int 10h +.done: + popad + ret + + + section .data + ; Map colors to consecutive DAC registers +linear_color db 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0 + + ; See comboot.doc, INT 22h AX=0017h for the semantics + ; of this byte. +UsingVGA db 0 + + section .bss1 + alignb 4 +LSSHeader equ $ +LSSMagic resd 1 ; Magic number +GraphXSize resw 1 ; Width of splash screen file +GraphYSize resw 1 ; Height of splash screen file +GraphColorMap resb 3*16 +VGAPos resw 1 ; Pointer into VGA memory +VGAFilePtr resw 1 ; Pointer into VGAFileBuf +; TextColorReg resb 17 ; VGA color registers for text mode +%if IS_SYSLINUX +VGAFileBuf resb FILENAME_MAX+2 ; Unmangled VGA image name +%else +VGAFileBuf resb FILENAME_MAX ; Unmangled VGA image name +%endif +VGAFileBufEnd equ $ +VGAFileMBuf resb FILENAME_MAX ; Mangled VGA image name + +; We need a buffer of 640+80 bytes. At this point, command_line should +; not be in use, so use that buffer. +VGARowBuffer equ command_line diff --git a/core/head.inc b/core/head.inc new file mode 100644 index 00000000..37f1b36f --- /dev/null +++ b/core/head.inc @@ -0,0 +1,31 @@ +; -*- fundamental -*- (asm-mode sucks) +; ----------------------------------------------------------------------- +; +; Copyright 2006-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 53 Temple Place Ste 330, +; Boston MA 02111-1307, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; ----------------------------------------------------------------------- + +; +; head.inc +; +; Common header includes +; + +%ifndef _HEAD_INC +%define _HEAD_INC + +%include "macros.inc" +%include "config.inc" +%include "layout.inc" +%include "kernel.inc" +%include "bios.inc" +%include "tracers.inc" +%include "stack.inc" + +%endif ; _HEAD_INC diff --git a/core/highmem.inc b/core/highmem.inc new file mode 100644 index 00000000..1cd46dd9 --- /dev/null +++ b/core/highmem.inc @@ -0,0 +1,148 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; highmem.inc +;; +;; Probe for the size of high memory. This can be overridden by a +;; mem= command on the command line while booting a new kernel. +;; + + section .text + +; +; This is set up as a subroutine; it will set up the global variable +; HighMemSize. All registers are preserved. +; +highmemsize: + push es + pushad + + push cs + pop es + +; +; First, try INT 15:E820 (get BIOS memory map) +; +; Note: we may have to scan this multiple times, because some (daft) BIOSes +; report main memory as multiple contiguous ranges... +; +get_e820: + mov dword [E820Max],-(1 << 20) ; Max amount of high memory + mov dword [E820Mem],(1 << 20) ; End of detected high memory +.start_over: + xor ebx,ebx ; Start with first record + jmp short .do_e820 ; Skip "at end" check first time! +.int_loop: and ebx,ebx ; If we're back at beginning... + jz .e820_done ; ... we're done +.do_e820: mov eax,0000E820h + mov edx,534D4150h ; "SMAP" backwards + xor ecx,ecx + mov cl,20 ; ECX <- 20 + mov di,E820Buf + int 15h + jnc .no_carry + ; If carry, ebx == 0 means error, ebx != 0 means we're done + and ebx,ebx + jnz .e820_done + jmp no_e820 +.no_carry: + cmp eax,534D4150h + jne no_e820 +; +; Look for a memory block starting at <= 1 MB and continuing upward +; + cmp dword [E820Buf+4], byte 0 + ja .int_loop ; Start >= 4 GB? + mov eax, [E820Buf] + cmp dword [E820Buf+16],1 + je .is_ram ; Is it memory? + ; + ; Non-memory range. Remember this as a limit; some BIOSes get the length + ; of primary RAM incorrect! + ; + cmp eax, (1 << 20) + jb .int_loop ; Starts in lowmem region + cmp eax,[E820Max] + jae .int_loop ; Already above limit + mov [E820Max],eax ; Set limit + jmp .int_loop + +.is_ram: + cmp eax,[E820Mem] + ja .int_loop ; Not contiguous with our starting point + add eax,[E820Buf+8] + jc .overflow + cmp dword [E820Buf+12],0 + je .nooverflow +.overflow: + or eax,-1 +.nooverflow: + cmp eax,[E820Mem] + jbe .int_loop ; All is below our baseline + mov [E820Mem],eax + jmp .start_over ; Start over in case we find an adjacent range + +.e820_done: + mov eax,[E820Mem] + cmp eax,[E820Max] + jna .not_limited + mov eax,[E820Max] +.not_limited: + cmp eax,(1 << 20) + ja got_highmem ; Did we actually find memory? + ; otherwise fall through + +; +; INT 15:E820 failed. Try INT 15:E801. +; +no_e820: + mov ax,0e801h ; Query high memory (semi-recent) + int 15h + jc no_e801 + cmp ax,3c00h + ja no_e801 ; > 3C00h something's wrong with this call + jb e801_hole ; If memory hole we can only use low part + + mov ax,bx + shl eax,16 ; 64K chunks + add eax,(16 << 20) ; Add first 16M + jmp short got_highmem + +; +; INT 15:E801 failed. Try INT 15:88. +; +no_e801: + mov ah,88h ; Query high memory (oldest) + int 15h + cmp ax,14*1024 ; Don't trust memory >15M + jna e801_hole + mov ax,14*1024 +e801_hole: + and eax,0ffffh + shl eax,10 ; Convert from kilobytes + add eax,(1 << 20) ; First megabyte +got_highmem: +%if HIGHMEM_SLOP != 0 + sub eax,HIGHMEM_SLOP +%endif + mov [HighMemSize],eax + popad + pop es + ret ; Done! + + section .bss + alignb 4 +E820Buf resd 5 ; INT 15:E820 data buffer +E820Mem resd 1 ; Memory detected by E820 +E820Max resd 1 ; Is E820 memory capped? +HighMemSize resd 1 ; End of memory pointer (bytes) diff --git a/core/init.inc b/core/init.inc new file mode 100644 index 00000000..0b213ace --- /dev/null +++ b/core/init.inc @@ -0,0 +1,47 @@ +; -*- fundamental -*- +; ----------------------------------------------------------------------- +; +; Copyright 2004-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 53 Temple Place Ste 330, +; Boston MA 02111-1307, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; ----------------------------------------------------------------------- + +; +; init.inc +; +; Common initialization code (inline) +; + + section .text +common_init: + ; Now set up screen parameters + call adjust_screen + +; +; Initialize configuration information +; + call reset_config + +; +; Clear Files structures +; + mov di,Files + mov cx,(MAX_OPEN*open_file_t_size)/4 + xor eax,eax + rep stosd + +%if IS_PXELINUX + mov di,Files+tftp_pktbuf + mov cx,MAX_OPEN +.setbufptr: + mov [di],ax + add di,open_file_t_size + add ax,PKTBUF_SIZE + loop .setbufptr +%endif + section .text ; This is an inline file... diff --git a/core/isolinux-debug.asm b/core/isolinux-debug.asm new file mode 100644 index 00000000..9c74b7cd --- /dev/null +++ b/core/isolinux-debug.asm @@ -0,0 +1,2 @@ +%define DEBUG_MESSAGES 1 +%include "isolinux.asm" diff --git a/core/isolinux.asm b/core/isolinux.asm new file mode 100644 index 00000000..52d426f9 --- /dev/null +++ b/core/isolinux.asm @@ -0,0 +1,1549 @@ +; -*- fundamental -*- (asm-mode sucks) +; **************************************************************************** +; +; isolinux.asm +; +; A program to boot Linux kernels off a CD-ROM using the El Torito +; boot standard in "no emulation" mode, making the entire filesystem +; available. It is based on the SYSLINUX boot loader for MS-DOS +; floppies. +; +; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 53 Temple Place Ste 330, +; Boston MA 02111-1307, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; **************************************************************************** + +%define IS_ISOLINUX 1 +%include "head.inc" + +; +; Some semi-configurable constants... change on your own risk. +; +my_id equ isolinux_id +FILENAME_MAX_LG2 equ 8 ; log2(Max filename size Including final null) +FILENAME_MAX equ (1 << FILENAME_MAX_LG2) +NULLFILE equ 0 ; Zero byte == null file name +NULLOFFSET equ 0 ; Position in which to look +retry_count equ 6 ; How patient are we with the BIOS? +%assign HIGHMEM_SLOP 128*1024 ; Avoid this much memory near the top +MAX_OPEN_LG2 equ 6 ; log2(Max number of open files) +MAX_OPEN equ (1 << MAX_OPEN_LG2) +SECTOR_SHIFT equ 11 ; 2048 bytes/sector (El Torito requirement) +SECTOR_SIZE equ (1 << SECTOR_SHIFT) + +; +; This is what we need to do when idle +; +%macro RESET_IDLE 0 + ; Nothing +%endmacro +%macro DO_IDLE 0 + ; Nothing +%endmacro + +; +; The following structure is used for "virtual kernels"; i.e. LILO-style +; option labels. The options we permit here are `kernel' and `append +; Since there is no room in the bottom 64K for all of these, we +; stick them in high memory and copy them down before we need them. +; + struc vkernel +vk_vname: resb FILENAME_MAX ; Virtual name **MUST BE FIRST!** +vk_rname: resb FILENAME_MAX ; Real name +vk_appendlen: resw 1 +vk_type: resb 1 ; Type of file + alignb 4 +vk_append: resb max_cmd_len+1 ; Command line + alignb 4 +vk_end: equ $ ; Should be <= vk_size + endstruc + +; +; Segment assignments in the bottom 640K +; 0000h - main code/data segment (and BIOS segment) +; +real_mode_seg equ 2000h +xfer_buf_seg equ 1000h ; Bounce buffer for I/O to high mem +comboot_seg equ real_mode_seg ; COMBOOT image loading zone + +; +; File structure. This holds the information for each currently open file. +; + struc open_file_t +file_sector resd 1 ; Sector pointer (0 = structure free) +file_bytesleft resd 1 ; Number of bytes left +file_left resd 1 ; Number of sectors left + resd 1 ; Unused + endstruc + +%ifndef DEPEND +%if (open_file_t_size & (open_file_t_size-1)) +%error "open_file_t is not a power of 2" +%endif +%endif + + struc dir_t +dir_lba resd 1 ; Directory start (LBA) +dir_len resd 1 ; Length in bytes +dir_clust resd 1 ; Length in clusters + endstruc + +; --------------------------------------------------------------------------- +; BEGIN CODE +; --------------------------------------------------------------------------- + +; +; Memory below this point is reserved for the BIOS and the MBR +; + section .earlybss +trackbufsize equ 8192 +trackbuf resb trackbufsize ; Track buffer goes here +; ends at 2800h + + ; Some of these are touched before the whole image + ; is loaded. DO NOT move this to .uibss. + section .bss2 + alignb 4 +ISOFileName resb 64 ; ISO filename canonicalization buffer +ISOFileNameEnd equ $ +CurDir resb dir_t_size ; Current directory +RootDir resb dir_t_size ; Root directory +FirstSecSum resd 1 ; Checksum of bytes 64-2048 +ImageDwords resd 1 ; isolinux.bin size, dwords +InitStack resd 1 ; Initial stack pointer (SS:SP) +DiskSys resw 1 ; Last INT 13h call +ImageSectors resw 1 ; isolinux.bin size, sectors +DiskError resb 1 ; Error code for disk I/O +DriveNumber resb 1 ; CD-ROM BIOS drive number +ISOFlags resb 1 ; Flags for ISO directory search +RetryCount resb 1 ; Used for disk access retries + +_spec_start equ $ + +; +; El Torito spec packet +; + + alignb 8 +spec_packet: resb 1 ; Size of packet +sp_media: resb 1 ; Media type +sp_drive: resb 1 ; Drive number +sp_controller: resb 1 ; Controller index +sp_lba: resd 1 ; LBA for emulated disk image +sp_devspec: resw 1 ; IDE/SCSI information +sp_buffer: resw 1 ; User-provided buffer +sp_loadseg: resw 1 ; Load segment +sp_sectors: resw 1 ; Sector count +sp_chs: resb 3 ; Simulated CHS geometry +sp_dummy: resb 1 ; Scratch, safe to overwrite + +; +; EBIOS drive parameter packet +; + alignb 8 +drive_params: resw 1 ; Buffer size +dp_flags: resw 1 ; Information flags +dp_cyl: resd 1 ; Physical cylinders +dp_head: resd 1 ; Physical heads +dp_sec: resd 1 ; Physical sectors/track +dp_totalsec: resd 2 ; Total sectors +dp_secsize: resw 1 ; Bytes per sector +dp_dpte: resd 1 ; Device Parameter Table +dp_dpi_key: resw 1 ; 0BEDDh if rest valid +dp_dpi_len: resb 1 ; DPI len + resb 1 + resw 1 +dp_bus: resb 4 ; Host bus type +dp_interface: resb 8 ; Interface type +db_i_path: resd 2 ; Interface path +db_d_path: resd 2 ; Device path + resb 1 +db_dpi_csum: resb 1 ; Checksum for DPI info + +; +; EBIOS disk address packet +; + alignb 8 +dapa: resw 1 ; Packet size +.count: resw 1 ; Block count +.off: resw 1 ; Offset of buffer +.seg: resw 1 ; Segment of buffer +.lba: resd 2 ; LBA (LSW, MSW) + +; +; Spec packet for disk image emulation +; + alignb 8 +dspec_packet: resb 1 ; Size of packet +dsp_media: resb 1 ; Media type +dsp_drive: resb 1 ; Drive number +dsp_controller: resb 1 ; Controller index +dsp_lba: resd 1 ; LBA for emulated disk image +dsp_devspec: resw 1 ; IDE/SCSI information +dsp_buffer: resw 1 ; User-provided buffer +dsp_loadseg: resw 1 ; Load segment +dsp_sectors: resw 1 ; Sector count +dsp_chs: resb 3 ; Simulated CHS geometry +dsp_dummy: resb 1 ; Scratch, safe to overwrite + + alignb 4 +_spec_end equ $ +_spec_len equ _spec_end - _spec_start + + alignb open_file_t_size +Files resb MAX_OPEN*open_file_t_size + + section .text +;; +;; Primary entry point. Because BIOSes are buggy, we only load the first +;; CD-ROM sector (2K) of the file, so the number one priority is actually +;; loading the rest. +;; +StackBuf equ $-44 ; 44 bytes needed for + ; the bootsector chainloading + ; code! +OrigESDI equ StackBuf-4 ; The high dword on the stack + +bootsec equ $ + +_start: ; Far jump makes sure we canonicalize the address + cli + jmp 0:_start1 + times 8-($-$$) nop ; Pad to file offset 8 + + ; This table hopefully gets filled in by mkisofs using the + ; -boot-info-table option. If not, the values in this + ; table are default values that we can use to get us what + ; we need, at least under a certain set of assumptions. +bi_pvd: dd 16 ; LBA of primary volume descriptor +bi_file: dd 0 ; LBA of boot file +bi_length: dd 0xdeadbeef ; Length of boot file +bi_csum: dd 0xdeadbeef ; Checksum of boot file +bi_reserved: times 10 dd 0xdeadbeef ; Reserved + +_start1: mov [cs:InitStack],sp ; Save initial stack pointer + mov [cs:InitStack+2],ss + xor ax,ax + mov ss,ax + mov sp,StackBuf ; Set up stack + push es ; Save initial ES:DI -> $PnP pointer + push di + mov ds,ax + mov es,ax + mov fs,ax + mov gs,ax + sti + + cld + ; Show signs of life + mov si,syslinux_banner + call writestr +%ifdef DEBUG_MESSAGES + mov si,copyright_str + call writestr +%endif + + ; + ; Before modifying any memory, get the checksum of bytes + ; 64-2048 + ; +initial_csum: xor edi,edi + mov si,_start1 + mov cx,(SECTOR_SIZE-64) >> 2 +.loop: lodsd + add edi,eax + loop .loop + mov [FirstSecSum],edi + + mov [DriveNumber],dl +%ifdef DEBUG_MESSAGES + mov si,startup_msg + call writemsg + mov al,dl + call writehex2 + call crlf +%endif + ; + ; Initialize spec packet buffers + ; + mov di,_spec_start + mov cx,_spec_len >> 2 + xor eax,eax + rep stosd + + ; Initialize length field of the various packets + mov byte [spec_packet],13h + mov byte [drive_params],30 + mov byte [dapa],16 + mov byte [dspec_packet],13h + + ; Other nonzero fields + inc word [dsp_sectors] + + ; Now figure out what we're actually doing + ; Note: use passed-in DL value rather than 7Fh because + ; at least some BIOSes will get the wrong value otherwise + mov ax,4B01h ; Get disk emulation status + mov dl,[DriveNumber] + mov si,spec_packet + call int13 + jc award_hack ; changed for BrokenAwardHack + mov dl,[DriveNumber] + cmp [sp_drive],dl ; Should contain the drive number + jne spec_query_failed + +%ifdef DEBUG_MESSAGES + mov si,spec_ok_msg + call writemsg + mov al,byte [sp_drive] + call writehex2 + call crlf +%endif + +found_drive: + ; Alright, we have found the drive. Now, try to find the + ; boot file itself. If we have a boot info table, life is + ; good; if not, we have to make some assumptions, and try + ; to figure things out ourselves. In particular, the + ; assumptions we have to make are: + ; - single session only + ; - only one boot entry (no menu or other alternatives) + + cmp dword [bi_file],0 ; Address of code to load + jne found_file ; Boot info table present :) + +%ifdef DEBUG_MESSAGES + mov si,noinfotable_msg + call writemsg +%endif + + ; No such luck. See if the spec packet contained one. + mov eax,[sp_lba] + and eax,eax + jz set_file ; Good enough + +%ifdef DEBUG_MESSAGES + mov si,noinfoinspec_msg + call writemsg +%endif + + ; No such luck. Get the Boot Record Volume, assuming single + ; session disk, and that we're the first entry in the chain + mov eax,17 ; Assumed address of BRV + mov bx,trackbuf + call getonesec + + mov eax,[trackbuf+47h] ; Get boot catalog address + mov bx,trackbuf + call getonesec ; Get boot catalog + + mov eax,[trackbuf+28h] ; First boot entry + ; And hope and pray this is us... + + ; Some BIOSes apparently have limitations on the size + ; that may be loaded (despite the El Torito spec being very + ; clear on the fact that it must all be loaded.) Therefore, + ; we load it ourselves, and *bleep* the BIOS. + +set_file: + mov [bi_file],eax + +found_file: + ; Set up boot file sizes + mov eax,[bi_length] + sub eax,SECTOR_SIZE-3 + shr eax,2 ; bytes->dwords + mov [ImageDwords],eax ; boot file dwords + add eax,(2047 >> 2) + shr eax,9 ; dwords->sectors + mov [ImageSectors],ax ; boot file sectors + + mov eax,[bi_file] ; Address of code to load + inc eax ; Don't reload bootstrap code +%ifdef DEBUG_MESSAGES + mov si,offset_msg + call writemsg + call writehex8 + call crlf +%endif + + ; Just in case some BIOSes have problems with + ; segment wraparound, use the normalized address + mov bx,((7C00h+2048) >> 4) + mov es,bx + xor bx,bx + mov bp,[ImageSectors] +%ifdef DEBUG_MESSAGES + push ax + mov si,size_msg + call writemsg + mov ax,bp + call writehex4 + call crlf + pop ax +%endif + call getlinsec + + push ds + pop es + +%ifdef DEBUG_MESSAGES + mov si,loaded_msg + call writemsg +%endif + + ; Verify the checksum on the loaded image. +verify_image: + mov si,7C00h+2048 + mov bx,es + mov ecx,[ImageDwords] + mov edi,[FirstSecSum] ; First sector checksum +.loop es lodsd + add edi,eax + dec ecx + jz .done + and si,si + jnz .loop + ; SI wrapped around, advance ES + add bx,1000h + mov es,bx + jmp short .loop +.done: mov ax,ds + mov es,ax + cmp [bi_csum],edi + je integrity_ok + + mov si,checkerr_msg + call writemsg + jmp kaboom + +integrity_ok: +%ifdef DEBUG_MESSAGES + mov si,allread_msg + call writemsg +%endif + jmp all_read ; Jump to main code + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Start of BrokenAwardHack --- 10-nov-2002 Knut_Petersen@t-online.de +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; There is a problem with certain versions of the AWARD BIOS ... +;; the boot sector will be loaded and executed correctly, but, because the +;; int 13 vector points to the wrong code in the BIOS, every attempt to +;; load the spec packet will fail. We scan for the equivalent of +;; +;; mov ax,0201h +;; mov bx,7c00h +;; mov cx,0006h +;; mov dx,0180h +;; pushf +;; call <direct far> +;; +;; and use <direct far> as the new vector for int 13. The code above is +;; used to load the boot code into ram, and there should be no reason +;; for anybody to change it now or in the future. There are no opcodes +;; that use encodings relativ to IP, so scanning is easy. If we find the +;; code above in the BIOS code we can be pretty sure to run on a machine +;; with an broken AWARD BIOS ... +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; +%ifdef DEBUG_MESSAGES ;; + ;; +award_notice db "Trying BrokenAwardHack first ...",CR,LF,0 ;; +award_not_orig db "BAH: Original Int 13 vector : ",0 ;; +award_not_new db "BAH: Int 13 vector changed to : ",0 ;; +award_not_succ db "BAH: SUCCESS",CR,LF,0 ;; +award_not_fail db "BAH: FAILURE" ;; +award_not_crlf db CR,LF,0 ;; + ;; +%endif ;; + ;; +award_oldint13 dd 0 ;; +award_string db 0b8h,1,2,0bbh,0,7ch,0b9h,6,0,0bah,80h,1,09ch,09ah ;; + ;; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +award_hack: mov si,spec_err_msg ; Moved to this place from + call writemsg ; spec_query_faild + ; +%ifdef DEBUG_MESSAGES ; + ; + mov si,award_notice ; display our plan + call writemsg ; + mov si,award_not_orig ; display original int 13 + call writemsg ; vector +%endif ; + mov eax,[13h*4] ; + mov [award_oldint13],eax ; + ; +%ifdef DEBUG_MESSAGES ; + ; + call writehex8 ; + mov si,award_not_crlf ; + call writestr ; +%endif ; + push es ; save ES + mov ax,0f000h ; ES = BIOS Seg + mov es,ax ; + cld ; + xor di,di ; start at ES:DI = f000:0 +award_loop: push di ; save DI + mov si,award_string ; scan for award_string + mov cx,7 ; length of award_string = 7dw + repz cmpsw ; compare + pop di ; restore DI + jcxz award_found ; jmp if found + inc di ; not found, inc di + jno award_loop ; + ; +award_failed: pop es ; No, not this way :-(( +award_fail2: ; + ; +%ifdef DEBUG_MESSAGES ; + ; + mov si,award_not_fail ; display failure ... + call writemsg ; +%endif ; + mov eax,[award_oldint13] ; restore the original int + or eax,eax ; 13 vector if there is one + jz spec_query_failed ; and try other workarounds + mov [13h*4],eax ; + jmp spec_query_failed ; + ; +award_found: mov eax,[es:di+0eh] ; load possible int 13 addr + pop es ; restore ES + ; + cmp eax,[award_oldint13] ; give up if this is the + jz award_failed ; active int 13 vector, + mov [13h*4],eax ; otherwise change 0:13h*4 + ; + ; +%ifdef DEBUG_MESSAGES ; + ; + push eax ; display message and + mov si,award_not_new ; new vector address + call writemsg ; + pop eax ; + call writehex8 ; + mov si,award_not_crlf ; + call writestr ; +%endif ; + mov ax,4B01h ; try to read the spec packet + mov dl,[DriveNumber] ; now ... it should not fail + mov si,spec_packet ; any longer + int 13h ; + jc award_fail2 ; + ; +%ifdef DEBUG_MESSAGES ; + ; + mov si,award_not_succ ; display our SUCCESS + call writemsg ; +%endif ; + jmp found_drive ; and leave error recovery code + ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; End of BrokenAwardHack ---- 10-nov-2002 Knut_Petersen@t-online.de +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + + ; INT 13h, AX=4B01h, DL=<passed in value> failed. + ; Try to scan the entire 80h-FFh from the end. + +spec_query_failed: + + ; some code moved to BrokenAwardHack + + mov dl,0FFh +.test_loop: pusha + mov ax,4B01h + mov si,spec_packet + mov byte [si],13h ; Size of buffer + call int13 + popa + jc .still_broken + + mov si,maybe_msg + call writemsg + mov al,dl + call writehex2 + call crlf + + cmp byte [sp_drive],dl + jne .maybe_broken + + ; Okay, good enough... + mov si,alright_msg + call writemsg +.found_drive0: mov [DriveNumber],dl +.found_drive: jmp found_drive + + ; Award BIOS 4.51 apparently passes garbage in sp_drive, + ; but if this was the drive number originally passed in + ; DL then consider it "good enough" +.maybe_broken: + mov al,[DriveNumber] + cmp al,dl + je .found_drive + + ; Intel Classic R+ computer with Adaptec 1542CP BIOS 1.02 + ; passes garbage in sp_drive, and the drive number originally + ; passed in DL does not have 80h bit set. + or al,80h + cmp al,dl + je .found_drive0 + +.still_broken: dec dx + cmp dl, 80h + jnb .test_loop + + ; No spec packet anywhere. Some particularly pathetic + ; BIOSes apparently don't even implement function + ; 4B01h, so we can't query a spec packet no matter + ; what. If we got a drive number in DL, then try to + ; use it, and if it works, then well... + mov dl,[DriveNumber] + cmp dl,81h ; Should be 81-FF at least + jb fatal_error ; If not, it's hopeless + + ; Write a warning to indicate we're on *very* thin ice now + mov si,nospec_msg + call writemsg + mov al,dl + call writehex2 + call crlf + mov si,trysbm_msg + call writemsg + jmp .found_drive ; Pray that this works... + +fatal_error: + mov si,nothing_msg + call writemsg + +.norge: jmp short .norge + + ; Information message (DS:SI) output + ; Prefix with "isolinux: " + ; +writemsg: push ax + push si + mov si,isolinux_str + call writestr + pop si + call writestr + pop ax + ret + +; +; Write a character to the screen. There is a more "sophisticated" +; version of this in the subsequent code, so we patch the pointer +; when appropriate. +; + +writechr: + jmp near writechr_simple ; 3-byte jump + +writechr_simple: + pushfd + pushad + mov ah,0Eh + xor bx,bx + int 10h + popad + popfd + ret + +; +; int13: save all the segment registers and call INT 13h +; Some CD-ROM BIOSes have been found to corrupt segment registers. +; +int13: + + push ds + push es + push fs + push gs + int 13h + pop gs + pop fs + pop es + pop ds + ret + +; +; Get one sector. Convenience entry point. +; +getonesec: + mov bp,1 + ; Fall through to getlinsec + +; +; Get linear sectors - EBIOS LBA addressing, 2048-byte sectors. +; +; Note that we can't always do this as a single request, because at least +; Phoenix BIOSes has a 127-sector limit. To be on the safe side, stick +; to 32 sectors (64K) per request. +; +; Input: +; EAX - Linear sector number +; ES:BX - Target buffer +; BP - Sector count +; +getlinsec: + mov si,dapa ; Load up the DAPA + mov [si+4],bx + mov bx,es + mov [si+6],bx + mov [si+8],eax +.loop: + push bp ; Sectors left + cmp bp,[MaxTransfer] + jbe .bp_ok + mov bp,[MaxTransfer] +.bp_ok: + mov [si+2],bp + push si + mov dl,[DriveNumber] + mov ah,42h ; Extended Read + call xint13 + pop si + pop bp + movzx eax,word [si+2] ; Sectors we read + add [si+8],eax ; Advance sector pointer + sub bp,ax ; Sectors left + shl ax,SECTOR_SHIFT-4 ; 2048-byte sectors -> segment + add [si+6],ax ; Advance buffer pointer + and bp,bp + jnz .loop + mov eax,[si+8] ; Next sector + ret + + ; INT 13h with retry +xint13: mov byte [RetryCount],retry_count +.try: pushad + call int13 + jc .error + add sp,byte 8*4 ; Clean up stack + ret +.error: + mov [DiskError],ah ; Save error code + popad + mov [DiskSys],ax ; Save system call number + dec byte [RetryCount] + jz .real_error + push ax + mov al,[RetryCount] + mov ah,[dapa+2] ; Sector transfer count + cmp al,2 ; Only 2 attempts left + ja .nodanger + mov ah,1 ; Drop transfer size to 1 + jmp short .setsize +.nodanger: + cmp al,retry_count-2 + ja .again ; First time, just try again + shr ah,1 ; Otherwise, try to reduce + adc ah,0 ; the max transfer size, but not to 0 +.setsize: + mov [MaxTransfer],ah + mov [dapa+2],ah +.again: + pop ax + jmp .try + +.real_error: mov si,diskerr_msg + call writemsg + mov al,[DiskError] + call writehex2 + mov si,oncall_str + call writestr + mov ax,[DiskSys] + call writehex4 + mov si,ondrive_str + call writestr + mov al,dl + call writehex2 + call crlf + ; Fall through to kaboom + +; +; kaboom: write a message and bail out. Wait for a user keypress, +; then do a hard reboot. +; +kaboom: + RESET_STACK_AND_SEGS AX + mov si,err_bootfailed + call cwritestr + call getchar + cli + mov word [BIOS_magic],0 ; Cold reboot + jmp 0F000h:0FFF0h ; Reset vector address + +; ----------------------------------------------------------------------------- +; Common modules needed in the first sector +; ----------------------------------------------------------------------------- + +%include "writestr.inc" ; String output +writestr equ cwritestr +%include "writehex.inc" ; Hexadecimal output + +; ----------------------------------------------------------------------------- +; Data that needs to be in the first sector +; ----------------------------------------------------------------------------- + +syslinux_banner db CR, LF, 'ISOLINUX ', version_str, ' ', date, ' ', 0 +copyright_str db ' Copyright (C) 1994-', year, ' H. Peter Anvin' + db CR, LF, 0 +isolinux_str db 'isolinux: ', 0 +%ifdef DEBUG_MESSAGES +startup_msg: db 'Starting up, DL = ', 0 +spec_ok_msg: db 'Loaded spec packet OK, drive = ', 0 +secsize_msg: db 'Sector size appears to be ', 0 +offset_msg: db 'Loading main image from LBA = ', 0 +size_msg: db 'Sectors to load = ', 0 +loaded_msg: db 'Loaded boot image, verifying...', CR, LF, 0 +verify_msg: db 'Image checksum verified.', CR, LF, 0 +allread_msg db 'Main image read, jumping to main code...', CR, LF, 0 +%endif +noinfotable_msg db 'No boot info table, assuming single session disk...', CR, LF, 0 +noinfoinspec_msg db 'Spec packet missing LBA information, trying to wing it...', CR, LF, 0 +spec_err_msg: db 'Loading spec packet failed, trying to wing it...', CR, LF, 0 +maybe_msg: db 'Found something at drive = ', 0 +alright_msg: db 'Looks like it might be right, continuing...', CR, LF, 0 +nospec_msg db 'Extremely broken BIOS detected, last ditch attempt with drive = ', 0 +nothing_msg: db 'Failed to locate CD-ROM device; boot failed.', CR, LF +trysbm_msg db 'See http://syslinux.zytor.com/sbm for more information.', CR, LF, 0 +diskerr_msg: db 'Disk error ', 0 +oncall_str: db ', AX = ',0 +ondrive_str: db ', drive ', 0 +checkerr_msg: db 'Image checksum error, sorry...', CR, LF, 0 + +err_bootfailed db CR, LF, 'Boot failed: press a key to retry...' +bailmsg equ err_bootfailed +crlf_msg db CR, LF +null_msg db 0 + + alignb 4, db 0 +MaxTransfer dw 32 ; Max sectors per transfer + +rl_checkpt equ $ ; Must be <= 800h + +rl_checkpt_off equ ($-$$) +;%ifndef DEPEND +;%if rl_checkpt_off > 0x800 +;%error "Sector 0 overflow" +;%endif +;%endif + +; ---------------------------------------------------------------------------- +; End of code and data that have to be in the first sector +; ---------------------------------------------------------------------------- + +all_read: + +; Test tracers + TRACER 'T' + TRACER '>' + +; +; Common initialization code +; +%include "init.inc" +%include "cpuinit.inc" + + ; Patch the writechr routine to point to the full code + mov word [writechr+1], writechr_full-(writechr+3) + +; Tell the user we got this far... +%ifndef DEBUG_MESSAGES ; Gets messy with debugging on + mov si,copyright_str + call writestr +%endif + +; +; Now we're all set to start with our *real* business. First load the +; configuration file (if any) and parse it. +; +; In previous versions I avoided using 32-bit registers because of a +; rumour some BIOSes clobbered the upper half of 32-bit registers at +; random. I figure, though, that if there are any of those still left +; they probably won't be trying to install Linux on them... +; +; The code is still ripe with 16-bitisms, though. Not worth the hassle +; to take'm out. In fact, we may want to put them back if we're going +; to boot ELKS at some point. +; + +; +; Now, we need to sniff out the actual filesystem data structures. +; mkisofs gave us a pointer to the primary volume descriptor +; (which will be at 16 only for a single-session disk!); from the PVD +; we should be able to find the rest of what we need to know. +; +get_fs_structures: + mov eax,[bi_pvd] + mov bx,trackbuf + call getonesec + + mov eax,[trackbuf+156+2] + mov [RootDir+dir_lba],eax + mov [CurDir+dir_lba],eax +%ifdef DEBUG_MESSAGES + mov si,dbg_rootdir_msg + call writemsg + call writehex8 + call crlf +%endif + mov eax,[trackbuf+156+10] + mov [RootDir+dir_len],eax + mov [CurDir+dir_len],eax + add eax,SECTOR_SIZE-1 + shr eax,SECTOR_SHIFT + mov [RootDir+dir_clust],eax + mov [CurDir+dir_clust],eax + + ; Look for an isolinux directory, and if found, + ; make it the current directory instead of the root + ; directory. + mov di,boot_dir ; Search for /boot/isolinux + mov al,02h + call searchdir_iso + jnz .found_dir + mov di,isolinux_dir + mov al,02h ; Search for /isolinux + call searchdir_iso + jz .no_isolinux_dir +.found_dir: + mov [CurDir+dir_len],eax + mov eax,[si+file_left] + mov [CurDir+dir_clust],eax + xor eax,eax ; Free this file pointer entry + xchg eax,[si+file_sector] + mov [CurDir+dir_lba],eax +%ifdef DEBUG_MESSAGES + push si + mov si,dbg_isodir_msg + call writemsg + pop si + call writehex8 + call crlf +%endif +.no_isolinux_dir: + +; +; Locate the configuration file +; +load_config: +%ifdef DEBUG_MESSAGES + mov si,dbg_config_msg + call writemsg +%endif + + mov si,config_name + mov di,ConfigName + call strcpy + + mov di,ConfigName + call open + jz no_config_file ; Not found or empty + +%ifdef DEBUG_MESSAGES + mov si,dbg_configok_msg + call writemsg +%endif + +; +; Now we have the config file open. Parse the config file and +; run the user interface. +; +%include "ui.inc" + +; +; Enable disk emulation. The kind of disk we emulate is dependent on the +; size of the file: 1200K, 1440K or 2880K floppy, otherwise harddisk. +; +is_disk_image: + TRACER CR + TRACER LF + TRACER 'D' + TRACER ':' + + mov edx,eax ; File size + mov di,img_table + mov cx,img_table_count + mov eax,[si+file_sector] ; Starting LBA of file + mov [dsp_lba],eax ; Location of file + mov byte [dsp_drive], 0 ; 00h floppy, 80h hard disk +.search_table: + TRACER 't' + mov eax,[di+4] + cmp edx,[di] + je .type_found + add di,8 + loop .search_table + + ; Hard disk image. Need to examine the partition table + ; in order to deduce the C/H/S geometry. Sigh. +.hard_disk_image: + TRACER 'h' + cmp edx,512 + jb .bad_image + + mov bx,trackbuf + mov cx,1 ; Load 1 sector + call getfssec + + cmp word [trackbuf+510],0aa55h ; Boot signature + jne .bad_image ; Image not bootable + + mov cx,4 ; 4 partition entries + mov di,trackbuf+446 ; Start of partition table + + xor ax,ax ; Highest sector(al) head(ah) + +.part_scan: + cmp byte [di+4], 0 + jz .part_loop + lea si,[di+1] + call .hs_check + add si,byte 4 + call .hs_check +.part_loop: + add di,byte 16 + loop .part_scan + + push eax ; H/S + push edx ; File size + mov bl,ah + xor bh,bh + inc bx ; # of heads in BX + xor ah,ah ; # of sectors in AX + cwde ; EAX[31:16] <- 0 + mul bx + shl eax,9 ; Convert to bytes + ; Now eax contains the number of bytes per cylinder + pop ebx ; File size + xor edx,edx + div ebx + and edx,edx + jz .no_remainder + inc eax ; Fractional cylinder... + ; Now (e)ax contains the number of cylinders +.no_remainder: cmp eax,1024 + jna .ok_cyl + mov ax,1024 ; Max possible # +.ok_cyl: dec ax ; Convert to max cylinder no + pop ebx ; S(bl) H(bh) + shl ah,6 + or bl,ah + xchg ax,bx + shl eax,16 + mov ah,bl + mov al,4 ; Hard disk boot + mov byte [dsp_drive], 80h ; Drive 80h = hard disk + +.type_found: + TRACER 'T' + mov bl,[sp_media] + and bl,0F0h ; Copy controller info bits + or al,bl + mov [dsp_media],al ; Emulation type + shr eax,8 + mov [dsp_chs],eax ; C/H/S geometry + mov ax,[sp_devspec] ; Copy device spec + mov [dsp_devspec],ax + mov al,[sp_controller] ; Copy controller index + mov [dsp_controller],al + + TRACER 'V' + call vgaclearmode ; Reset video + + mov ax,4C00h ; Enable emulation and boot + mov si,dspec_packet + mov dl,[DriveNumber] + lss sp,[InitStack] + TRACER 'X' + + call int13 + + ; If this returns, we have problems +.bad_image: + mov si,err_disk_image + call cwritestr + jmp enter_command + +; +; Look for the highest seen H/S geometry +; We compute cylinders separately +; +.hs_check: + mov bl,[si] ; Head # + cmp bl,ah + jna .done_track + mov ah,bl ; New highest head # +.done_track: mov bl,[si+1] + and bl,3Fh ; Sector # + cmp bl,al + jna .done_sector + mov al,bl +.done_sector: ret + +; +; close_file: +; Deallocates a file structure (pointer in SI) +; Assumes CS == DS. +; +close_file: + and si,si + jz .closed + mov dword [si],0 ; First dword == file_left + xor si,si +.closed: ret + +; +; searchdir: +; +; Open a file +; +; On entry: +; DS:DI = filename +; If successful: +; ZF clear +; SI = file pointer +; EAX = file length in bytes +; If unsuccessful +; ZF set +; +; Assumes CS == DS == ES, and trashes BX and CX. +; +; searchdir_iso is a special entry point for ISOLINUX only. In addition +; to the above, searchdir_iso passes a file flag mask in AL. This is useful +; for searching for directories. +; +alloc_failure: + xor ax,ax ; ZF <- 1 + ret + +searchdir: + xor al,al +searchdir_iso: + mov [ISOFlags],al + TRACER 'S' + call allocate_file ; Temporary file structure for directory + jnz alloc_failure + push es + push ds + pop es ; ES = DS + mov si,CurDir + cmp byte [di],'/' ; If filename begins with slash + jne .not_rooted + inc di ; Skip leading slash + mov si,RootDir ; Reference root directory instead +.not_rooted: + mov eax,[si+dir_clust] + mov [bx+file_left],eax + mov eax,[si+dir_lba] + mov [bx+file_sector],eax + mov edx,[si+dir_len] + +.look_for_slash: + mov ax,di +.scan: + mov cl,[di] + inc di + and cl,cl + jz .isfile + cmp cl,'/' + jne .scan + mov [di-1],byte 0 ; Terminate at directory name + mov cl,02h ; Search for directory + xchg cl,[ISOFlags] + + push di ; Save these... + push cx + + ; Create recursion stack frame... + push word .resume ; Where to "return" to + push es +.isfile: xchg ax,di + +.getsome: + ; Get a chunk of the directory + ; This relies on the fact that ISOLINUX doesn't change SI + mov si,trackbuf + TRACER 'g' + pushad + xchg bx,si + mov cx,[BufSafe] + call getfssec + popad + +.compare: + movzx eax,byte [si] ; Length of directory entry + cmp al,33 + jb .next_sector + TRACER 'c' + mov cl,[si+25] + xor cl,[ISOFlags] + test cl, byte 8Eh ; Unwanted file attributes! + jnz .not_file + pusha + movzx cx,byte [si+32] ; File identifier length + add si,byte 33 ; File identifier offset + TRACER 'i' + call iso_compare_names + popa + je .success +.not_file: + sub edx,eax ; Decrease bytes left + jbe .failure + add si,ax ; Advance pointer + +.check_overrun: + ; Did we finish the buffer? + cmp si,trackbuf+trackbufsize + jb .compare ; No, keep going + + jmp short .getsome ; Get some more directory + +.next_sector: + ; Advance to the beginning of next sector + lea ax,[si+SECTOR_SIZE-1] + and ax,~(SECTOR_SIZE-1) + sub ax,si + jmp short .not_file ; We still need to do length checks + +.failure: xor eax,eax ; ZF = 1 + mov [bx+file_sector],eax + pop es + ret + +.success: + mov eax,[si+2] ; Location of extent + mov [bx+file_sector],eax + mov eax,[si+10] ; Data length + mov [bx+file_bytesleft],eax + push eax + add eax,SECTOR_SIZE-1 + shr eax,SECTOR_SHIFT + mov [bx+file_left],eax + pop eax + and bx,bx ; ZF = 0 + mov si,bx + pop es + ret + +.resume: ; We get here if we were only doing part of a lookup + ; This relies on the fact that .success returns bx == si + xchg edx,eax ; Directory length in edx + pop cx ; Old ISOFlags + pop di ; Next filename pointer + mov byte [di-1], '/' ; Restore slash + mov [ISOFlags],cl ; Restore the flags + jz .failure ; Did we fail? If so fail for real! + jmp .look_for_slash ; Otherwise, next level + +; +; allocate_file: Allocate a file structure +; +; If successful: +; ZF set +; BX = file pointer +; In unsuccessful: +; ZF clear +; +allocate_file: + TRACER 'a' + push cx + mov bx,Files + mov cx,MAX_OPEN +.check: cmp dword [bx], byte 0 + je .found + add bx,open_file_t_size ; ZF = 0 + loop .check + ; ZF = 0 if we fell out of the loop +.found: pop cx + ret + +; +; iso_compare_names: +; Compare the names DS:SI and DS:DI and report if they are +; equal from an ISO 9660 perspective. SI is the name from +; the filesystem; CX indicates its length, and ';' terminates. +; DI is expected to end with a null. +; +; Note: clobbers AX, CX, SI, DI; assumes DS == ES == base segment +; + +iso_compare_names: + ; First, terminate and canonicalize input filename + push di + mov di,ISOFileName +.canon_loop: jcxz .canon_end + lodsb + dec cx + cmp al,';' + je .canon_end + and al,al + je .canon_end + stosb + cmp di,ISOFileNameEnd-1 ; Guard against buffer overrun + jb .canon_loop +.canon_end: + cmp di,ISOFileName + jbe .canon_done + cmp byte [di-1],'.' ; Remove terminal dots + jne .canon_done + dec di + jmp short .canon_end +.canon_done: + mov [di],byte 0 ; Null-terminate string + pop di + mov si,ISOFileName +.compare: + lodsb + mov ah,[di] + inc di + and ax,ax + jz .success ; End of string for both + and al,al ; Is either one end of string? + jz .failure ; If so, failure + and ah,ah + jz .failure + or ax,2020h ; Convert to lower case + cmp al,ah + je .compare +.failure: and ax,ax ; ZF = 0 (at least one will be nonzero) +.success: ret + +; +; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed +; to by ES:DI; ends on encountering any whitespace. +; DI is preserved. +; +; This verifies that a filename is < FILENAME_MAX characters, +; doesn't contain whitespace, zero-pads the output buffer, +; and removes trailing dots and redundant slashes, +; so "repe cmpsb" can do a compare, and the +; path-searching routine gets a bit of an easier job. +; +mangle_name: + push di + push bx + xor ax,ax + mov cx,FILENAME_MAX-1 + mov bx,di + +.mn_loop: + lodsb + cmp al,' ' ; If control or space, end + jna .mn_end + cmp al,ah ; Repeated slash? + je .mn_skip + xor ah,ah + cmp al,'/' + jne .mn_ok + mov ah,al +.mn_ok stosb +.mn_skip: loop .mn_loop +.mn_end: + cmp bx,di ; At the beginning of the buffer? + jbe .mn_zero + cmp byte [es:di-1],'.' ; Terminal dot? + je .mn_kill + cmp byte [es:di-1],'/' ; Terminal slash? + jne .mn_zero +.mn_kill: dec di ; If so, remove it + inc cx + jmp short .mn_end +.mn_zero: + inc cx ; At least one null byte + xor ax,ax ; Zero-fill name + rep stosb + pop bx + pop di + ret ; Done + +; +; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled +; filename to the conventional representation. This is needed +; for the BOOT_IMAGE= parameter for the kernel. +; +; DS:SI -> input mangled file name +; ES:DI -> output buffer +; +; On return, DI points to the first byte after the output name, +; which is set to a null byte. +; +unmangle_name: call strcpy + dec di ; Point to final null byte + ret + +; +; getfssec: Get multiple clusters from a file, given the file pointer. +; +; On entry: +; ES:BX -> Buffer +; SI -> File pointer +; CX -> Cluster count +; On exit: +; SI -> File pointer (or 0 on EOF) +; CF = 1 -> Hit EOF +; ECX -> Bytes actually read +; +getfssec: + TRACER 'F' + + push ds + push cs + pop ds ; DS <- CS + + movzx ecx,cx + cmp ecx,[si+file_left] + jna .ok_size + mov ecx,[si+file_left] + +.ok_size: + mov bp,cx + push cx + push si + mov eax,[si+file_sector] + TRACER 'l' + call getlinsec + xor ecx,ecx + pop si + pop cx + + add [si+file_sector],ecx + sub [si+file_left],ecx + ja .not_eof ; CF = 0 + stc + +.not_eof: + pushf + shl ecx,SECTOR_SHIFT ; Convert to bytes + cmp ecx,[si+file_bytesleft] + jb .not_all + mov ecx,[si+file_bytesleft] +.not_all: sub [si+file_bytesleft],ecx + popf + jnc .ret + push eax + xor eax,eax + mov [si+file_sector],eax ; Unused + mov si,ax + pop eax + stc +.ret: + pop ds + TRACER 'f' + ret + +; ----------------------------------------------------------------------------- +; Common modules +; ----------------------------------------------------------------------------- + +%include "getc.inc" ; getc et al +%include "conio.inc" ; Console I/O +%include "configinit.inc" ; Initialize configuration +%include "parseconfig.inc" ; High-level config file handling +%include "parsecmd.inc" ; Low-level config file handling +%include "bcopy32.inc" ; 32-bit bcopy +%include "loadhigh.inc" ; Load a file into high memory +%include "font.inc" ; VGA font stuff +%include "graphics.inc" ; VGA graphics +%include "highmem.inc" ; High memory sizing +%include "strcpy.inc" ; strcpy() +%include "rawcon.inc" ; Console I/O w/o using the console functions +%include "adv.inc" ; Auxillary Data Vector +%include "localboot.inc" ; Disk-based local boot + +; ----------------------------------------------------------------------------- +; Begin data section +; ----------------------------------------------------------------------------- + + section .data + +default_str db 'default', 0 +default_len equ ($-default_str) +boot_dir db '/boot' ; /boot/isolinux +isolinux_dir db '/isolinux', 0 +config_name db 'isolinux.cfg', 0 +err_disk_image db 'Cannot load disk image (invalid file)?', CR, LF, 0 + +%ifdef DEBUG_MESSAGES +dbg_rootdir_msg db 'Root directory at LBA = ', 0 +dbg_isodir_msg db 'isolinux directory at LBA = ', 0 +dbg_config_msg db 'About to load config file...', CR, LF, 0 +dbg_configok_msg db 'Configuration file opened...', CR, LF, 0 +%endif +; +; Command line options we'd like to take a look at +; +; mem= and vga= are handled as normal 32-bit integer values +initrd_cmd db 'initrd=' +initrd_cmd_len equ 7 + +; +; Config file keyword table +; +%include "keywords.inc" + +; +; Extensions to search for (in *forward* order). +; + align 4, db 0 +exten_table: db '.cbt' ; COMBOOT (specific) + db '.img' ; Disk image + db '.bin' ; CD boot sector + db '.com' ; COMBOOT (same as DOS) + db '.c32' ; COM32 +exten_table_end: + dd 0, 0 ; Need 8 null bytes here + +; +; Floppy image table +; + align 4, db 0 +img_table_count equ 3 +img_table: + dd 1200*1024 ; 1200K floppy + db 1 ; Emulation type + db 80-1 ; Max cylinder + db 15 ; Max sector + db 2-1 ; Max head + + dd 1440*1024 ; 1440K floppy + db 2 ; Emulation type + db 80-1 ; Max cylinder + db 18 ; Max sector + db 2-1 ; Max head + + dd 2880*1024 ; 2880K floppy + db 3 ; Emulation type + db 80-1 ; Max cylinder + db 36 ; Max sector + db 2-1 ; Max head + +; +; Misc initialized (data) variables +; + +; +; Variables that are uninitialized in SYSLINUX but initialized here +; +; **** ISOLINUX:: We may have to make this flexible, based on what the +; **** BIOS expects our "sector size" to be. +; + alignb 4, db 0 +BufSafe dw trackbufsize/SECTOR_SIZE ; Clusters we can load into trackbuf +BufSafeBytes dw trackbufsize ; = how many bytes? +%ifndef DEPEND +%if ( trackbufsize % SECTOR_SIZE ) != 0 +%error trackbufsize must be a multiple of SECTOR_SIZE +%endif +%endif diff --git a/core/kernel.inc b/core/kernel.inc new file mode 100644 index 00000000..0da03638 --- /dev/null +++ b/core/kernel.inc @@ -0,0 +1,112 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; kernel.inc +;; +;; Header file for the kernel interface definitions +;; + +%ifndef _KERNEL_INC +%define _KERNEL_INC + +;; +;; Structure of the real_mode_seg +;; + + struc real_mode_seg_t + resb 20h-($-$$) ; org 20h +kern_cmd_magic resw 1 ; 0020 Magic # for command line +kern_cmd_offset resw 1 ; 0022 Offset for kernel command line + resb 497-($-$$) ; org 497d +bs_setupsecs resb 1 ; 01F1 Sectors for setup code (0 -> 4) +bs_rootflags resw 1 ; 01F2 Root readonly flag +bs_syssize resw 1 ; 01F4 +bs_swapdev resw 1 ; 01F6 Swap device (obsolete) +bs_ramsize resw 1 ; 01F8 Ramdisk flags, formerly ramdisk size +bs_vidmode resw 1 ; 01FA Video mode +bs_rootdev resw 1 ; 01FC Root device +bs_bootsign resw 1 ; 01FE Boot sector signature (0AA55h) +su_jump resb 1 ; 0200 0EBh +su_jump2 resb 1 ; 0201 Size of following header +su_header resd 1 ; 0202 New setup code: header +su_version resw 1 ; 0206 See linux/arch/i386/boot/setup.S +su_switch resw 1 ; 0208 +su_setupseg resw 1 ; 020A +su_startsys resw 1 ; 020C +su_kver resw 1 ; 020E Kernel version pointer +su_loader resb 1 ; 0210 Loader ID +su_loadflags resb 1 ; 0211 Load high flag +su_movesize resw 1 ; 0212 +su_code32start resd 1 ; 0214 Start of code loaded high +su_ramdiskat resd 1 ; 0218 Start of initial ramdisk +su_ramdisklen resd 1 ; 021C Length of initial ramdisk +su_bsklugeoffs resw 1 ; 0220 +su_bsklugeseg resw 1 ; 0222 +su_heapend resw 1 ; 0224 +su_pad1 resw 1 ; 0226 +su_cmd_line_ptr resd 1 ; 0228 +su_ramdisk_max resd 1 ; 022C + resb (0f800h-12)-($-$$) +linux_stack equ $ ; F7F4 +linux_fdctab resb 12 +cmd_line_here equ $ ; F800 Should be out of the way + endstruc + +; +; Old kernel command line signature +; +CMD_MAGIC equ 0A33Fh ; Command line magic + +; +; If we're loading the command line old-style, we need a smaller +; heap. +; +old_cmd_line_here equ 9800h +old_max_cmd_len equ 2047 +old_linux_fdctab equ old_cmd_line_here-12 +old_linux_stack equ old_linux_fdctab + +; +; Magic number of su_header field +; +HEADER_ID equ 'HdrS' ; HdrS (in littleendian hex) + +; +; Flags for the su_loadflags field +; +LOAD_HIGH equ 01h ; Large kernel, load high +CAN_USE_HEAP equ 80h ; Boot loader reports heap size + +; +; ID codes for various modules +; +syslinux_id equ 031h ; 3 = SYSLINUX family; 1 = SYSLINUX +pxelinux_id equ 032h ; 3 = SYSLINUX family; 2 = PXELINUX +isolinux_id equ 033h ; 3 = SYSLINUX family; 3 = ISOLINUX +extlinux_id equ 034h ; 3 = SYSLINUX family; 4 = EXTLINUX + +; +; Types of vkernels +; +VK_KERNEL equ 0 ; Choose by filename +VK_LINUX equ 1 ; Linux kernel image +VK_BOOT equ 2 ; Boot sector +VK_BSS equ 3 ; BSS boot sector +VK_PXE equ 4 ; PXE NBP +VK_FDIMAGE equ 5 ; Floppy disk image +VK_COMBOOT equ 6 ; COMBOOT image +VK_COM32 equ 7 ; COM32 image +VK_CONFIG equ 8 ; Configuration file +VK_TYPES equ 9 ; Number of VK types + +%endif ; _KERNEL_INC diff --git a/core/keywords b/core/keywords new file mode 100644 index 00000000..d7d8fa65 --- /dev/null +++ b/core/keywords @@ -0,0 +1,44 @@ +menu +text +include +append +config +default +display +font +implicit +ipappend +kbdmap +kernel +linux +boot +bss +pxe +fdimage +comboot +com32 +label +localboot +prompt +say +serial +console +timeout +totaltimeout +allowoptions +ontimeout +onerror +noescape +f0 +f1 +f2 +f3 +f4 +f5 +f6 +f7 +f8 +f9 +f10 +f11 +f12 diff --git a/core/keywords.inc b/core/keywords.inc new file mode 100644 index 00000000..b6a701bb --- /dev/null +++ b/core/keywords.inc @@ -0,0 +1,97 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; keywords.inc +;; +;; Common header file for the handling of keyword hash and macros +;; + +%ifndef DEPEND ; Generated file +%include "kwdhash.gen" +%endif + +%macro keyword 2 + dd hash_%1 ; Hash value + dw 0 ; No argument + dw %2 ; Entrypoint +%endmacro + +%macro keyword 3 + dd hash_%1 ; Hash value + dw %3 ; 16-bit argument + dw %2 ; Entrypoint +%endmacro + +%macro keyword 4 + dd hash_%1 ; Hash value + db %3, %4 ; 2 8-bit arguments + dw %2 ; Entrypoint +%endmacro + +keywd_size equ 8 ; Bytes per keyword + + align 4, db 0 + +%define FKeyN(n) (FKeyName+(((n)-1) << FILENAME_MAX_LG2)) + +keywd_table: + keyword menu, pc_comment + keyword text, pc_text + keyword include, pc_opencmd, pc_include + keyword append, pc_append + keyword default, pc_default + keyword display, pc_opencmd, get_msg_file + keyword font, pc_filecmd, loadfont + keyword implicit, pc_setint16, AllowImplicit + keyword kbdmap, pc_filecmd, loadkeys + keyword kernel, pc_kernel, VK_KERNEL + keyword linux, pc_kernel, VK_LINUX + keyword boot, pc_kernel, VK_BOOT + keyword bss, pc_kernel, VK_BSS + keyword pxe, pc_kernel, VK_PXE + keyword fdimage, pc_kernel, VK_FDIMAGE + keyword comboot, pc_kernel, VK_COMBOOT + keyword com32, pc_kernel, VK_COM32 + keyword config, pc_kernel, VK_CONFIG + keyword label, pc_label + keyword prompt, pc_setint16, ForcePrompt + keyword say, pc_say + keyword serial, pc_serial + keyword console, pc_setint16, DisplayCon + keyword timeout, pc_timeout, KbdTimeout + keyword totaltimeout, pc_timeout, TotalTimeout + keyword ontimeout, pc_ontimeout + keyword onerror, pc_onerror + keyword allowoptions, pc_setint16, AllowOptions + keyword noescape, pc_setint16, NoEscape + keyword f1, pc_fkey, FKeyN(1) + keyword f2, pc_fkey, FKeyN(2) + keyword f3, pc_fkey, FKeyN(3) + keyword f4, pc_fkey, FKeyN(4) + keyword f5, pc_fkey, FKeyN(5) + keyword f6, pc_fkey, FKeyN(6) + keyword f7, pc_fkey, FKeyN(7) + keyword f8, pc_fkey, FKeyN(8) + keyword f9, pc_fkey, FKeyN(9) + keyword f10, pc_fkey, FKeyN(10) + keyword f0, pc_fkey, FKeyN(10) + keyword f11, pc_fkey, FKeyN(11) + keyword f12, pc_fkey, FKeyN(12) +%if IS_PXELINUX + keyword ipappend, pc_ipappend +%endif +%if HAS_LOCALBOOT + keyword localboot, pc_localboot +%endif + +keywd_count equ ($-keywd_table)/keywd_size diff --git a/core/layout.inc b/core/layout.inc new file mode 100644 index 00000000..51460e14 --- /dev/null +++ b/core/layout.inc @@ -0,0 +1,92 @@ +; ----------------------------------------------------------------------- +; +; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 53 Temple Place Ste 330, +; Bostom MA 02111-1307, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; ----------------------------------------------------------------------- + +; +; layout.inc +; +; Memory layout of segments +; + + ; Default to 16-bit code + bits 16 + +; Memory below 0800h is reserved for the BIOS and the MBR. +BSS_START equ 0800h + +; Text starts at the load address of 07C00h. +TEXT_START equ 7C00h + +; The secondary BSS section, above the text; we really wish we could +; just make it follow .bcopy32 or hang off the end, +; but it doesn't seem to work that way. +LATEBSS_START equ 0B800h + +%ifdef MAP + [map all MAP] +%endif + +; +; The various sections and their relationship +; + ; Use .earlybss for things that MUST be in low memory. + section .earlybss nobits start=BSS_START + section .bcopy32 exec nowrite progbits align=4 + section .config write progbits align=4 + section .config.end write nobits align=4 + + ; Use .bss for things that doesn't have to be in low memory; + ; with .bss1 and .bss2 to offload. .earlybss should be used + ; for things that absolutely have to be below 0x7c00. + section .bss write nobits align=16 + + ; Warning here: RBFG build 22 randomly overwrites + ; memory location [0x5680,0x576c), possibly more. It + ; seems that it gets confused and screws up the + ; pointer to its own internal packet buffer and starts + ; writing a received ARP packet into low memory. +%if IS_PXELINUX + section .rbfg write nobits +RBFG_brainfuck: resb 2048 ; Bigger than an Ethernet packet... +%endif + + ; For section following .rbfg +%if IS_PXELINUX + section .bss2 write nobits align=16 +%else + section .bss2 write nobits align=16 +%endif + + section .text exec write progbits align=16 + section .data write progbits align=16 + + section .adv write nobits align=512 + + ; .uibss contains bss data which is guaranteed to be + ; safe to clobber during the loading of the image. This + ; is because while loading the primary image we will clobber + ; the spillover from the last fractional sector load. + section .uibss write nobits align=16 + + ; Normal bss... + section .bss1 write nobits align=16 + + ; Symbols from linker script +%macro SECINFO 1 + extern __%1_start, __%1_lma, __%1_end + extern __%1_len, __%1_dwords +%endmacro + SECINFO bcopy32 + SECINFO config + + global _start + + section .text diff --git a/core/ldlinux.asm b/core/ldlinux.asm new file mode 100644 index 00000000..86de4588 --- /dev/null +++ b/core/ldlinux.asm @@ -0,0 +1,1605 @@ +; -*- fundamental -*- (asm-mode sucks) +; **************************************************************************** +; +; ldlinux.asm +; +; A program to boot Linux kernels off an MS-DOS formatted floppy disk. This +; functionality is good to have for installation floppies, where it may +; be hard to find a functional Linux system to run LILO off. +; +; This program allows manipulation of the disk to take place entirely +; from MS-LOSS, and can be especially useful in conjunction with the +; umsdos filesystem. +; +; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 53 Temple Place Ste 330, +; Boston MA 02111-1307, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; **************************************************************************** + +%ifndef IS_MDSLINUX +%define IS_SYSLINUX 1 +%endif +%include "head.inc" + +; +; Some semi-configurable constants... change on your own risk. +; +my_id equ syslinux_id +FILENAME_MAX_LG2 equ 6 ; log2(Max filename size Including final null) +FILENAME_MAX equ (1<<FILENAME_MAX_LG2) ; Max mangled filename size +NULLFILE equ 0 ; First char space == null filename +NULLOFFSET equ 0 ; Position in which to look +retry_count equ 16 ; How patient are we with the disk? +%assign HIGHMEM_SLOP 0 ; Avoid this much memory near the top +LDLINUX_MAGIC equ 0x3eb202fe ; A random number to identify ourselves with + +MAX_OPEN_LG2 equ 6 ; log2(Max number of open files) +MAX_OPEN equ (1 << MAX_OPEN_LG2) + +SECTOR_SHIFT equ 9 +SECTOR_SIZE equ (1 << SECTOR_SHIFT) + +; +; This is what we need to do when idle +; +%macro RESET_IDLE 0 + ; Nothing +%endmacro +%macro DO_IDLE 0 + ; Nothing +%endmacro + +; +; The following structure is used for "virtual kernels"; i.e. LILO-style +; option labels. The options we permit here are `kernel' and `append +; Since there is no room in the bottom 64K for all of these, we +; stick them in high memory and copy them down before we need them. +; + struc vkernel +vk_vname: resb FILENAME_MAX ; Virtual name **MUST BE FIRST!** +vk_rname: resb FILENAME_MAX ; Real name +vk_appendlen: resw 1 +vk_type: resb 1 ; Type of file + alignb 4 +vk_append: resb max_cmd_len+1 ; Command line + alignb 4 +vk_end: equ $ ; Should be <= vk_size + endstruc + +; +; Segment assignments in the bottom 640K +; Stick to the low 512K in case we're using something like M-systems flash +; which load a driver into low RAM (evil!!) +; +; 0000h - main code/data segment (and BIOS segment) +; +real_mode_seg equ 3000h +cache_seg equ 2000h ; 64K area for metadata cache +xfer_buf_seg equ 1000h ; Bounce buffer for I/O to high mem +comboot_seg equ real_mode_seg ; COMBOOT image loading zone + +; +; File structure. This holds the information for each currently open file. +; + struc open_file_t +file_sector resd 1 ; Sector pointer (0 = structure free) +file_bytesleft resd 1 ; Number of bytes left +file_left resd 1 ; Number of sectors left + resd 1 ; Unused + endstruc + +%ifndef DEPEND +%if (open_file_t_size & (open_file_t_size-1)) +%error "open_file_t is not a power of 2" +%endif +%endif + +; --------------------------------------------------------------------------- +; BEGIN CODE +; --------------------------------------------------------------------------- + +; +; Memory below this point is reserved for the BIOS and the MBR +; + section .earlybss +trackbufsize equ 8192 +trackbuf resb trackbufsize ; Track buffer goes here + ; ends at 2800h + + section .bss + alignb 8 + + ; Expanded superblock +SuperInfo equ $ + resq 16 ; The first 16 bytes expanded 8 times +FAT resd 1 ; Location of (first) FAT +RootDirArea resd 1 ; Location of root directory area +RootDir resd 1 ; Location of root directory proper +DataArea resd 1 ; Location of data area +RootDirSize resd 1 ; Root dir size in sectors +TotalSectors resd 1 ; Total number of sectors +ClustSize resd 1 ; Bytes/cluster +ClustMask resd 1 ; Sectors/cluster - 1 +CopySuper resb 1 ; Distinguish .bs versus .bss +DriveNumber resb 1 ; BIOS drive number +ClustShift resb 1 ; Shift count for sectors/cluster +ClustByteShift resb 1 ; Shift count for bytes/cluster + + alignb open_file_t_size +Files resb MAX_OPEN*open_file_t_size + + section .text +; +; Some of the things that have to be saved very early are saved +; "close" to the initial stack pointer offset, in order to +; reduce the code size... +; +StackBuf equ $-44-32 ; Start the stack here (grow down - 4K) +PartInfo equ StackBuf ; Saved partition table entry +FloppyTable equ PartInfo+16 ; Floppy info table (must follow PartInfo) +OrigFDCTabPtr equ StackBuf-8 ; The 2nd high dword on the stack +OrigESDI equ StackBuf-4 ; The high dword on the stack + +; +; Primary entry point. Tempting as though it may be, we can't put the +; initial "cli" here; the jmp opcode in the first byte is part of the +; "magic number" (using the term very loosely) for the DOS superblock. +; +bootsec equ $ +_start: jmp short start ; 2 bytes + nop ; 1 byte +; +; "Superblock" follows -- it's in the boot sector, so it's already +; loaded and ready for us +; +bsOemName db 'SYSLINUX' ; The SYS command sets this, so... +; +; These are the fields we actually care about. We end up expanding them +; all to dword size early in the code, so generate labels for both +; the expanded and unexpanded versions. +; +%macro superb 1 +bx %+ %1 equ SuperInfo+($-superblock)*8+4 +bs %+ %1 equ $ + zb 1 +%endmacro +%macro superw 1 +bx %+ %1 equ SuperInfo+($-superblock)*8 +bs %+ %1 equ $ + zw 1 +%endmacro +%macro superd 1 +bx %+ %1 equ $ ; no expansion for dwords +bs %+ %1 equ $ + zd 1 +%endmacro +superblock equ $ + superw BytesPerSec + superb SecPerClust + superw ResSectors + superb FATs + superw RootDirEnts + superw Sectors + superb Media + superw FATsecs + superw SecPerTrack + superw Heads +superinfo_size equ ($-superblock)-1 ; How much to expand + superd Hidden + superd HugeSectors + ; + ; This is as far as FAT12/16 and FAT32 are consistent + ; + zb 54 ; FAT12/16 need 26 more bytes, + ; FAT32 need 54 more bytes +superblock_len equ $-superblock + +SecPerClust equ bxSecPerClust +; +; Note we don't check the constraints above now; we did that at install +; time (we hope!) +; +start: + cli ; No interrupts yet, please + cld ; Copy upwards +; +; Set up the stack +; + xor ax,ax + mov ss,ax + mov sp,StackBuf ; Just below BSS + push es ; Save initial ES:DI -> $PnP pointer + push di + mov es,ax +; +; DS:SI may contain a partition table entry. Preserve it for us. +; + mov cx,8 ; Save partition info + mov di,PartInfo + rep movsw + + mov ds,ax ; Now we can initialize DS... + +; +; Now sautee the BIOS floppy info block to that it will support decent- +; size transfers; the floppy block is 11 bytes and is stored in the +; INT 1Eh vector (brilliant waste of resources, eh?) +; +; Of course, if BIOSes had been properly programmed, we wouldn't have +; had to waste precious space with this code. +; + mov bx,fdctab + lfs si,[bx] ; FS:SI -> original fdctab + push fs ; Save on stack in case we need to bail + push si + + ; Save the old fdctab even if hard disk so the stack layout + ; is the same. The instructions above do not change the flags + mov [DriveNumber],dl ; Save drive number in DL + and dl,dl ; If floppy disk (00-7F), assume no + ; partition table + js harddisk + +floppy: + mov cl,6 ; 12 bytes (CX == 0) + ; es:di -> FloppyTable already + ; This should be safe to do now, interrupts are off... + mov [bx],di ; FloppyTable + mov [bx+2],ax ; Segment 0 + fs rep movsw ; Faster to move words + mov cl,[bsSecPerTrack] ; Patch the sector count + mov [di-8],cl + ; AX == 0 here + int 13h ; Some BIOSes need this + + jmp short not_harddisk +; +; The drive number and possibly partition information was passed to us +; by the BIOS or previous boot loader (MBR). Current "best practice" is to +; trust that rather than what the superblock contains. +; +; Would it be better to zero out bsHidden if we don't have a partition table? +; +; Note: di points to beyond the end of PartInfo +; +harddisk: + test byte [di-16],7Fh ; Sanity check: "active flag" should + jnz no_partition ; be 00 or 80 + mov eax,[di-8] ; Partition offset (dword) + mov [bsHidden],eax +no_partition: +; +; Get disk drive parameters (don't trust the superblock.) Don't do this for +; floppy drives -- INT 13:08 on floppy drives will (may?) return info about +; what the *drive* supports, not about the *media*. Fortunately floppy disks +; tend to have a fixed, well-defined geometry which is stored in the superblock. +; + ; DL == drive # still + mov ah,08h + int 13h + jc no_driveparm + and ah,ah + jnz no_driveparm + shr dx,8 + inc dx ; Contains # of heads - 1 + mov [bsHeads],dx + and cx,3fh + mov [bsSecPerTrack],cx +no_driveparm: +not_harddisk: +; +; Ready to enable interrupts, captain +; + sti + +; +; Do we have EBIOS (EDD)? +; +eddcheck: + mov bx,55AAh + mov ah,41h ; EDD existence query + mov dl,[DriveNumber] + int 13h + jc .noedd + cmp bx,0AA55h + jne .noedd + test cl,1 ; Extended disk access functionality set + jz .noedd + ; + ; We have EDD support... + ; + mov byte [getlinsec.jmp+1],(getlinsec_ebios-(getlinsec.jmp+2)) +.noedd: + +; +; Load the first sector of LDLINUX.SYS; this used to be all proper +; with parsing the superblock and root directory; it doesn't fit +; together with EBIOS support, unfortunately. +; + mov eax,[FirstSector] ; Sector start + mov bx,ldlinux_sys ; Where to load it + call getonesec + + ; Some modicum of integrity checking + cmp dword [ldlinux_magic+4],LDLINUX_MAGIC^HEXDATE + jne kaboom + + ; Go for it... + jmp ldlinux_ent + +; +; getonesec: get one disk sector +; +getonesec: + mov bp,1 ; One sector + ; Fall through + +; +; getlinsec: load a sequence of BP floppy sector given by the linear sector +; number in EAX into the buffer at ES:BX. We try to optimize +; by loading up to a whole track at a time, but the user +; is responsible for not crossing a 64K boundary. +; (Yes, BP is weird for a count, but it was available...) +; +; On return, BX points to the first byte after the transferred +; block. +; +; This routine assumes CS == DS, and trashes most registers. +; +; Stylistic note: use "xchg" instead of "mov" when the source is a register +; that is dead from that point; this saves space. However, please keep +; the order to dst,src to keep things sane. +; +getlinsec: + add eax,[bsHidden] ; Add partition offset + xor edx,edx ; Zero-extend LBA (eventually allow 64 bits) + +.jmp: jmp strict short getlinsec_cbios + +; +; getlinsec_ebios: +; +; getlinsec implementation for EBIOS (EDD) +; +getlinsec_ebios: +.loop: + push bp ; Sectors left +.retry2: + call maxtrans ; Enforce maximum transfer size + movzx edi,bp ; Sectors we are about to read + mov cx,retry_count +.retry: + + ; Form DAPA on stack + push edx + push eax + push es + push bx + push di + push word 16 + mov si,sp + pushad + mov dl,[DriveNumber] + push ds + push ss + pop ds ; DS <- SS + mov ah,42h ; Extended Read + int 13h + pop ds + popad + lea sp,[si+16] ; Remove DAPA + jc .error + pop bp + add eax,edi ; Advance sector pointer + sub bp,di ; Sectors left + shl di,SECTOR_SHIFT ; 512-byte sectors + add bx,di ; Advance buffer pointer + and bp,bp + jnz .loop + + ret + +.error: + ; Some systems seem to get "stuck" in an error state when + ; using EBIOS. Doesn't happen when using CBIOS, which is + ; good, since some other systems get timeout failures + ; waiting for the floppy disk to spin up. + + pushad ; Try resetting the device + xor ax,ax + mov dl,[DriveNumber] + int 13h + popad + loop .retry ; CX-- and jump if not zero + + ;shr word [MaxTransfer],1 ; Reduce the transfer size + ;jnz .retry2 + + ; Total failure. Try falling back to CBIOS. + mov byte [getlinsec.jmp+1],(getlinsec_cbios-(getlinsec.jmp+2)) + ;mov byte [MaxTransfer],63 ; Max possibe CBIOS transfer + + pop bp + ; ... fall through ... + +; +; getlinsec_cbios: +; +; getlinsec implementation for legacy CBIOS +; +getlinsec_cbios: +.loop: + push edx + push eax + push bp + push bx + + movzx esi,word [bsSecPerTrack] + movzx edi,word [bsHeads] + ; + ; Dividing by sectors to get (track,sector): we may have + ; up to 2^18 tracks, so we need to use 32-bit arithmetric. + ; + div esi + xor cx,cx + xchg cx,dx ; CX <- sector index (0-based) + ; EDX <- 0 + ; eax = track # + div edi ; Convert track to head/cyl + + ; We should test this, but it doesn't fit... + ; cmp eax,1023 + ; ja .error + + ; + ; Now we have AX = cyl, DX = head, CX = sector (0-based), + ; BP = sectors to transfer, SI = bsSecPerTrack, + ; ES:BX = data target + ; + + call maxtrans ; Enforce maximum transfer size + + ; Must not cross track boundaries, so BP <= SI-CX + sub si,cx + cmp bp,si + jna .bp_ok + mov bp,si +.bp_ok: + + shl ah,6 ; Because IBM was STOOPID + ; and thought 8 bits were enough + ; then thought 10 bits were enough... + inc cx ; Sector numbers are 1-based, sigh + or cl,ah + mov ch,al + mov dh,dl + mov dl,[DriveNumber] + xchg ax,bp ; Sector to transfer count + mov ah,02h ; Read sectors + mov bp,retry_count +.retry: + pushad + int 13h + popad + jc .error +.resume: + movzx ecx,al ; ECX <- sectors transferred + shl ax,SECTOR_SHIFT ; Convert sectors in AL to bytes in AX + pop bx + add bx,ax + pop bp + pop eax + pop edx + add eax,ecx + sub bp,cx + jnz .loop + ret + +.error: + dec bp + jnz .retry + + xchg ax,bp ; Sectors transferred <- 0 + shr word [MaxTransfer],1 + jnz .resume + ; Fall through to disk_error + +; +; kaboom: write a message and bail out. +; +disk_error: +kaboom: + xor si,si + mov ss,si + mov sp,StackBuf-4 ; Reset stack + mov ds,si ; Reset data segment + pop dword [fdctab] ; Restore FDC table +.patch: ; When we have full code, intercept here + mov si,bailmsg + + ; Write error message, this assumes screen page 0 +.loop: lodsb + and al,al + jz .done + mov ah,0Eh ; Write to screen as TTY + mov bx,0007h ; Attribute + int 10h + jmp short .loop +.done: + cbw ; AH <- 0 +.again: int 16h ; Wait for keypress + ; NB: replaced by int 18h if + ; chosen at install time.. + int 19h ; And try once more to boot... +.norge: jmp short .norge ; If int 19h returned; this is the end + +; +; Truncate BP to MaxTransfer +; +maxtrans: + cmp bp,[MaxTransfer] + jna .ok + mov bp,[MaxTransfer] +.ok: ret + +; +; Error message on failure +; +bailmsg: db 'Boot error', 0Dh, 0Ah, 0 + + ; This fails if the boot sector overflows + zb 1F8h-($-$$) + +FirstSector dd 0xDEADBEEF ; Location of sector 1 +MaxTransfer dw 0x007F ; Max transfer size + +; This field will be filled in 0xAA55 by the installer, but we abuse it +; to house a pointer to the INT 16h instruction at +; kaboom.again, which gets patched to INT 18h in RAID mode. +bootsignature dw kaboom.again-bootsec + +; +; =========================================================================== +; End of boot sector +; =========================================================================== +; Start of LDLINUX.SYS +; =========================================================================== + +ldlinux_sys: + +syslinux_banner db 0Dh, 0Ah +%if IS_MDSLINUX + db 'MDSLINUX ' +%else + db 'SYSLINUX ' +%endif + db version_str, ' ', date, ' ', 0 + db 0Dh, 0Ah, 1Ah ; EOF if we "type" this in DOS + + align 8, db 0 +ldlinux_magic dd LDLINUX_MAGIC + dd LDLINUX_MAGIC^HEXDATE + +; +; This area is patched by the installer. It is found by looking for +; LDLINUX_MAGIC, plus 8 bytes. +; +patch_area: +LDLDwords dw 0 ; Total dwords starting at ldlinux_sys +LDLSectors dw 0 ; Number of sectors - (bootsec+this sec) +CheckSum dd 0 ; Checksum starting at ldlinux_sys + ; value = LDLINUX_MAGIC - [sum of dwords] + +; Space for up to 64 sectors, the theoretical maximum +SectorPtrs times 64 dd 0 + +ldlinux_ent: +; +; Note that some BIOSes are buggy and run the boot sector at 07C0:0000 +; instead of 0000:7C00 and the like. We don't want to add anything +; more to the boot sector, so it is written to not assume a fixed +; value in CS, but we don't want to deal with that anymore from now +; on. +; + jmp 0:.next +.next: + +; +; Tell the user we got this far +; + mov si,syslinux_banner + call writestr + +; +; Tell the user if we're using EBIOS or CBIOS +; +print_bios: + mov si,cbios_name + cmp byte [getlinsec.jmp+1],(getlinsec_ebios-(getlinsec.jmp+2)) + jne .cbios + mov si,ebios_name +.cbios: + mov [BIOSName],si + call writestr + + section .bss +%define HAVE_BIOSNAME 1 +BIOSName resw 1 + + section .text +; +; Now we read the rest of LDLINUX.SYS. Don't bother loading the first +; sector again, though. +; +load_rest: + mov si,SectorPtrs + mov bx,7C00h+2*SECTOR_SIZE ; Where we start loading + mov cx,[LDLSectors] + +.get_chunk: + jcxz .done + xor bp,bp + lodsd ; First sector of this chunk + + mov edx,eax + +.make_chunk: + inc bp + dec cx + jz .chunk_ready + inc edx ; Next linear sector + cmp [si],edx ; Does it match + jnz .chunk_ready ; If not, this is it + add si,4 ; If so, add sector to chunk + jmp short .make_chunk + +.chunk_ready: + call getlinsecsr + shl bp,SECTOR_SHIFT + add bx,bp + jmp .get_chunk + +.done: + +; +; All loaded up, verify that we got what we needed. +; Note: the checksum field is embedded in the checksum region, so +; by the time we get to the end it should all cancel out. +; +verify_checksum: + mov si,ldlinux_sys + mov cx,[LDLDwords] + mov edx,-LDLINUX_MAGIC +.checksum: + lodsd + add edx,eax + loop .checksum + + and edx,edx ; Should be zero + jz all_read ; We're cool, go for it! + +; +; Uh-oh, something went bad... +; + mov si,checksumerr_msg + call writestr + jmp kaboom + +; +; ----------------------------------------------------------------------------- +; Subroutines that have to be in the first sector +; ----------------------------------------------------------------------------- + +; +; +; writestr: write a null-terminated string to the console +; This assumes we're on page 0. This is only used for early +; messages, so it should be OK. +; +writestr: +.loop: lodsb + and al,al + jz .return + mov ah,0Eh ; Write to screen as TTY + mov bx,0007h ; Attribute + int 10h + jmp short .loop +.return: ret + + +; getlinsecsr: save registers, call getlinsec, restore registers +; +getlinsecsr: pushad + call getlinsec + popad + ret + +; +; Checksum error message +; +checksumerr_msg db ' Load error - ', 0 ; Boot failed appended + +; +; BIOS type string +; +cbios_name db 'CBIOS', 0 +ebios_name db 'EBIOS', 0 + +; +; Debug routine +; +%ifdef debug +safedumpregs: + cmp word [Debug_Magic],0D00Dh + jnz nc_return + jmp dumpregs +%endif + +rl_checkpt equ $ ; Must be <= 8000h + +rl_checkpt_off equ ($-$$) +%ifndef DEPEND +%if rl_checkpt_off > 400h +%error "Sector 1 overflow" +%endif +%endif + +; ---------------------------------------------------------------------------- +; End of code and data that have to be in the first sector +; ---------------------------------------------------------------------------- + +all_read: +; +; Let the user (and programmer!) know we got this far. This used to be +; in Sector 1, but makes a lot more sense here. +; + mov si,copyright_str + call writestr + + +; +; Insane hack to expand the superblock to dwords +; +expand_super: + xor eax,eax + mov si,superblock + mov di,SuperInfo + mov cx,superinfo_size +.loop: + lodsw + dec si + stosd ; Store expanded word + xor ah,ah + stosd ; Store expanded byte + loop .loop + +; +; Compute some information about this filesystem. +; + +; First, generate the map of regions +genfatinfo: + mov edx,[bxSectors] + and dx,dx + jnz .have_secs + mov edx,[bsHugeSectors] +.have_secs: + mov [TotalSectors],edx + + mov eax,[bxResSectors] + mov [FAT],eax ; Beginning of FAT + mov edx,[bxFATsecs] + and dx,dx + jnz .have_fatsecs + mov edx,[bootsec+36] ; FAT32 BPB_FATsz32 +.have_fatsecs: + imul edx,[bxFATs] + add eax,edx + mov [RootDirArea],eax ; Beginning of root directory + mov [RootDir],eax ; For FAT12/16 == root dir location + + mov edx,[bxRootDirEnts] + add dx,SECTOR_SIZE/32-1 + shr dx,SECTOR_SHIFT-5 + mov [RootDirSize],edx + add eax,edx + mov [DataArea],eax ; Beginning of data area + +; Next, generate a cluster size shift count and mask + mov eax,[bxSecPerClust] + bsr cx,ax + mov [ClustShift],cl + push cx + add cl,SECTOR_SHIFT + mov [ClustByteShift],cl + pop cx + dec ax + mov [ClustMask],eax + inc ax + shl eax,SECTOR_SHIFT + mov [ClustSize],eax + +; +; FAT12, FAT16 or FAT28^H^H32? This computation is fscking ridiculous. +; +getfattype: + mov eax,[TotalSectors] + sub eax,[DataArea] + shr eax,cl ; cl == ClustShift + mov cl,nextcluster_fat12-(nextcluster+2) + cmp eax,4085 ; FAT12 limit + jb .setsize + mov cl,nextcluster_fat16-(nextcluster+2) + cmp eax,65525 ; FAT16 limit + jb .setsize + ; + ; FAT32, root directory is a cluster chain + ; + mov cl,[ClustShift] + mov eax,[bootsec+44] ; Root directory cluster + sub eax,2 + shl eax,cl + add eax,[DataArea] + mov [RootDir],eax + mov cl,nextcluster_fat28-(nextcluster+2) +.setsize: + mov byte [nextcluster+1],cl + +; +; Common initialization code +; +%include "cpuinit.inc" +%include "init.inc" + +; +; Initialize the metadata cache +; + call initcache + +; +; Now, everything is "up and running"... patch kaboom for more +; verbosity and using the full screen system +; + ; E9 = JMP NEAR + mov dword [kaboom.patch],0e9h+((kaboom2-(kaboom.patch+3)) << 8) + +; +; Now we're all set to start with our *real* business. First load the +; configuration file (if any) and parse it. +; +; In previous versions I avoided using 32-bit registers because of a +; rumour some BIOSes clobbered the upper half of 32-bit registers at +; random. I figure, though, that if there are any of those still left +; they probably won't be trying to install Linux on them... +; +; The code is still ripe with 16-bitisms, though. Not worth the hassle +; to take'm out. In fact, we may want to put them back if we're going +; to boot ELKS at some point. +; + +; +; Load configuration file +; + mov si,config_name ; Save configuration file name + mov di,ConfigName + call strcpy + + mov di,syslinux_cfg1 + call open + jnz .config_open + mov di,syslinux_cfg2 + call open + jnz .config_open + mov di,syslinux_cfg3 + call open + jz no_config_file +.config_open: + mov eax,[PrevDir] ; Make the directory with syslinux.cfg ... + mov [CurrentDir],eax ; ... the current directory + +; +; Now we have the config file open. Parse the config file and +; run the user interface. +; +%include "ui.inc" + +; +; allocate_file: Allocate a file structure +; +; If successful: +; ZF set +; BX = file pointer +; In unsuccessful: +; ZF clear +; +allocate_file: + TRACER 'a' + push cx + mov bx,Files + mov cx,MAX_OPEN +.check: cmp dword [bx], byte 0 + je .found + add bx,open_file_t_size ; ZF = 0 + loop .check + ; ZF = 0 if we fell out of the loop +.found: pop cx + ret + +; +; search_dos_dir: +; Search a specific directory for a pre-mangled filename in +; MangledBuf, in the directory starting in sector EAX. +; +; NOTE: This file considers finding a zero-length file an +; error. This is so we don't have to deal with that special +; case elsewhere in the program (most loops have the test +; at the end). +; +; Assumes DS == ES == CS. +; +; If successful: +; ZF clear +; SI = file pointer +; EAX = file length (MAY BE ZERO!) +; DL = file attributes +; If unsuccessful +; ZF set +; + +search_dos_dir: + push bx + call allocate_file + jnz .alloc_failure + + push cx + push gs + push es + push ds + pop es ; ES = DS + +.scansector: + ; EAX <- directory sector to scan + call getcachesector + ; GS:SI now points to this sector + + mov cx,SECTOR_SIZE/32 ; 32 == directory entry size +.scanentry: + cmp byte [gs:si],0 + jz .failure ; Hit directory high water mark + test byte [gs:si+11],8 ; Ignore volume labels and + ; VFAT long filename entries + jnz .nomatch + push cx + push si + push di + mov di,MangledBuf + mov cx,11 + gs repe cmpsb + pop di + pop si + pop cx + jz .found +.nomatch: + add si,32 + loop .scanentry + + call nextsector + jnc .scansector ; CF is set if we're at end + + ; If we get here, we failed +.failure: + pop es + pop gs + pop cx +.alloc_failure: + pop bx + xor eax,eax ; ZF <- 1 + ret +.found: + mov eax,[gs:si+28] ; File size + add eax,SECTOR_SIZE-1 + shr eax,SECTOR_SHIFT + mov [bx+4],eax ; Sector count + + mov cl,[ClustShift] + mov dx,[gs:si+20] ; High cluster word + shl edx,16 + mov dx,[gs:si+26] ; Low cluster word + sub edx,2 + shl edx,cl + add edx,[DataArea] + mov [bx],edx ; Starting sector + + mov eax,[gs:si+28] ; File length again + mov dl,[gs:si+11] ; File attribute + mov si,bx ; File pointer... + and si,si ; ZF <- 0 + + pop es + pop gs + pop cx + pop bx + ret + +; +; close_file: +; Deallocates a file structure (pointer in SI) +; Assumes CS == DS. +; +close_file: + and si,si + jz .closed + mov dword [si],0 ; First dword == file_sector + xor si,si +.closed: ret + +; +; searchdir: +; +; Open a file +; +; On entry: +; DS:DI = filename +; If successful: +; ZF clear +; SI = file pointer +; EAX = file length in bytes +; If unsuccessful +; ZF set +; +; Assumes CS == DS == ES, and trashes BX and CX. +; +searchdir: + mov eax,[CurrentDir] + cmp byte [di],'/' ; Root directory? + jne .notroot + mov eax,[RootDir] + inc di +.notroot: + +.pathwalk: + push eax ; <A> Current directory sector + mov si,di +.findend: + lodsb + cmp al,' ' + jbe .endpath + cmp al,'/' + jne .findend +.endpath: + xchg si,di + pop eax ; <A> Current directory sector + + mov [PrevDir],eax ; Remember last directory searched + + push di + call mangle_dos_name ; MangledBuf <- component + call search_dos_dir + pop di + jz .notfound ; Pathname component missing + + cmp byte [di-1],'/' ; Do we expect a directory + je .isdir + + ; Otherwise, it should be a file +.isfile: + test dl,18h ; Subdirectory|Volume Label + jnz .badfile ; If not a file, it's a bad thing + + ; SI and EAX are already set + mov [si+file_bytesleft],eax + push eax + add eax,SECTOR_SIZE-1 + shr eax,SECTOR_SHIFT + mov [si+file_left],eax ; Sectors left + pop eax + and eax,eax ; EAX != 0 + jz .badfile + ret ; Done! + + ; If we expected a directory, it better be one... +.isdir: + test dl,10h ; Subdirectory + jz .badfile + + xor eax,eax + xchg eax,[si+file_sector] ; Get sector number and free file structure + jmp .pathwalk ; Walk the next bit of the path + +.badfile: + xor eax,eax + mov [si],eax ; Free file structure + +.notfound: + xor eax,eax + ret + + section .bss + alignb 4 +CurrentDir resd 1 ; Current directory +PrevDir resd 1 ; Last scanned directory + + section .text + +; +; +; kaboom2: once everything is loaded, replace the part of kaboom +; starting with "kaboom.patch" with this part + +kaboom2: + mov si,err_bootfailed + call cwritestr + cmp byte [kaboom.again+1],18h ; INT 18h version? + je .int18 + call getchar + call vgaclearmode + int 19h ; And try once more to boot... +.norge: jmp short .norge ; If int 19h returned; this is the end +.int18: + call vgaclearmode + int 18h +.noreg: jmp short .noreg ; Nynorsk + +; +; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed +; to by ES:DI; ends on encountering any whitespace. +; DI is preserved. +; +; This verifies that a filename is < FILENAME_MAX characters, +; doesn't contain whitespace, zero-pads the output buffer, +; and removes trailing dots and redundant slashes, plus changes +; backslashes to forward slashes, +; so "repe cmpsb" can do a compare, and the path-searching routine +; gets a bit of an easier job. +; +; +mangle_name: + push di + push bx + xor ax,ax + mov cx,FILENAME_MAX-1 + mov bx,di + +.mn_loop: + lodsb + cmp al,' ' ; If control or space, end + jna .mn_end + cmp al,'\' ; Backslash? + jne .mn_not_bs + mov al,'/' ; Change to forward slash +.mn_not_bs: + cmp al,ah ; Repeated slash? + je .mn_skip + xor ah,ah + cmp al,'/' + jne .mn_ok + mov ah,al +.mn_ok stosb +.mn_skip: loop .mn_loop +.mn_end: + cmp bx,di ; At the beginning of the buffer? + jbe .mn_zero + cmp byte [es:di-1],'.' ; Terminal dot? + je .mn_kill + cmp byte [es:di-1],'/' ; Terminal slash? + jne .mn_zero +.mn_kill: dec di ; If so, remove it + inc cx + jmp short .mn_end +.mn_zero: + inc cx ; At least one null byte + xor ax,ax ; Zero-fill name + rep stosb + pop bx + pop di + ret ; Done + +; +; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled +; filename to the conventional representation. This is needed +; for the BOOT_IMAGE= parameter for the kernel. +; NOTE: A 13-byte buffer is mandatory, even if the string is +; known to be shorter. +; +; DS:SI -> input mangled file name +; ES:DI -> output buffer +; +; On return, DI points to the first byte after the output name, +; which is set to a null byte. +; +unmangle_name: call strcpy + dec di ; Point to final null byte + ret + +; +; mangle_dos_name: +; Mangle a DOS filename component pointed to by DS:SI +; into [MangledBuf]; ends on encountering any whitespace or slash. +; Assumes CS == DS == ES. +; + +mangle_dos_name: + pusha + mov di,MangledBuf + + mov cx,11 ; # of bytes to write +.loop: + lodsb + cmp al,' ' ; If control or space, end + jna .end + cmp al,'/' ; Slash, too + je .end + cmp al,'.' ; Period -> space-fill + je .is_period + cmp al,'a' + jb .not_lower + cmp al,'z' + ja .not_uslower + sub al,020h + jmp short .not_lower +.is_period: mov al,' ' ; We need to space-fill +.period_loop: cmp cx,3 ; If <= 3 characters left + jbe .loop ; Just ignore it + stosb ; Otherwise, write a period + loop .period_loop ; Dec CX and (always) jump +.not_uslower: cmp al,ucase_low + jb .not_lower + cmp al,ucase_high + ja .not_lower + mov bx,ucase_tab-ucase_low + xlatb +.not_lower: stosb + loop .loop ; Don't continue if too long +.end: + mov al,' ' ; Space-fill name + rep stosb ; Doesn't do anything if CX=0 + popa + ret ; Done + + section .bss +MangledBuf resb 11 + + section .text +; +; Case tables for extended characters; this is technically code page 865, +; but code page 437 users will probably not miss not being able to use the +; cent sign in kernel images too much :-) +; +; The table only covers the range 129 to 164; the rest we can deal with. +; + section .data + +ucase_low equ 129 +ucase_high equ 164 +ucase_tab db 154, 144, 'A', 142, 'A', 143, 128, 'EEEIII' + db 142, 143, 144, 146, 146, 'O', 153, 'OUUY', 153, 154 + db 157, 156, 157, 158, 159, 'AIOU', 165 + + section .text +; +; getfssec_edx: Get multiple sectors from a file +; +; This routine makes sure the subtransfers do not cross a 64K boundary, +; and will correct the situation if it does, UNLESS *sectors* cross +; 64K boundaries. +; +; ES:BX -> Buffer +; EDX -> Current sector number +; CX -> Sector count (0FFFFh = until end of file) +; Must not exceed the ES segment +; Returns EDX=0, CF=1 on EOF (not necessarily error) +; All arguments are advanced to reflect data read. +; +getfssec_edx: + push ebp + push eax +.getfragment: + xor ebp,ebp ; Fragment sector count + push edx ; Starting sector pointer +.getseccnt: + inc bp + dec cx + jz .do_read + xor eax,eax + mov ax,es + shl ax,4 + add ax,bx ; Now AX = how far into 64K block we are + not ax ; Bytes left in 64K block + inc eax + shr eax,SECTOR_SHIFT ; Sectors left in 64K block + cmp bp,ax + jnb .do_read ; Unless there is at least 1 more sector room... + mov eax,edx ; Current sector + inc edx ; Predict it's the linearly next sector + call nextsector + jc .do_read + cmp edx,eax ; Did it match? + jz .getseccnt +.do_read: + pop eax ; Starting sector pointer + call getlinsecsr + lea eax,[eax+ebp-1] ; This is the last sector actually read + shl bp,9 + add bx,bp ; Adjust buffer pointer + call nextsector + jc .eof + mov edx,eax + and cx,cx + jnz .getfragment +.done: + pop eax + pop ebp + ret +.eof: + xor edx,edx + stc + jmp .done + +; +; getfssec: Get multiple sectors from a file +; +; Same as above, except SI is a pointer to a open_file_t +; +; ES:BX -> Buffer +; DS:SI -> Pointer to open_file_t +; CX -> Sector count (0FFFFh = until end of file) +; Must not exceed the ES segment +; Returns CF=1 on EOF (not necessarily error) +; ECX returns number of bytes read. +; All arguments are advanced to reflect data read. +; +getfssec: + push edx + movzx edx,cx + push edx ; Zero-extended CX + cmp edx,[si+file_left] + jbe .sizeok + mov edx,[si+file_left] + mov cx,dx +.sizeok: + sub [si+file_left],edx + mov edx,[si+file_sector] + call getfssec_edx + mov [si+file_sector],edx + pop ecx ; Sectors requested read + shl ecx,SECTOR_SHIFT + cmp ecx,[si+file_bytesleft] + ja .eof +.noteof: + sub [si+file_bytesleft],ecx ; CF <- 0 + pop edx + ret +.eof: + mov ecx,[si+file_bytesleft] + call close_file + pop edx + stc + ret + +; +; nextcluster: Advance a cluster pointer in EDI to the next cluster +; pointed at in the FAT tables. CF=0 on return if end of file. +; +nextcluster: + jmp strict short nextcluster_fat28 ; This gets patched + +nextcluster_fat12: + push eax + push edx + push bx + push cx + push si + mov edx,edi + shr edi,1 + pushf ; Save the shifted-out LSB (=CF) + add edx,edi + mov eax,edx + shr eax,9 + call getfatsector + mov bx,dx + and bx,1FFh + mov cl,[gs:si+bx] + inc edx + mov eax,edx + shr eax,9 + call getfatsector + mov bx,dx + and bx,1FFh + mov ch,[gs:si+bx] + popf + jnc .even + shr cx,4 +.even: and cx,0FFFh + movzx edi,cx + cmp di,0FF0h + pop si + pop cx + pop bx + pop edx + pop eax + ret + +; +; FAT16 decoding routine. +; +nextcluster_fat16: + push eax + push si + push bx + mov eax,edi + shr eax,SECTOR_SHIFT-1 + call getfatsector + mov bx,di + add bx,bx + and bx,1FEh + movzx edi,word [gs:si+bx] + cmp di,0FFF0h + pop bx + pop si + pop eax + ret +; +; FAT28 ("FAT32") decoding routine. +; +nextcluster_fat28: + push eax + push si + push bx + mov eax,edi + shr eax,SECTOR_SHIFT-2 + call getfatsector + mov bx,di + add bx,bx + add bx,bx + and bx,1FCh + mov edi,dword [gs:si+bx] + and edi,0FFFFFFFh ; 28 bits only + cmp edi,0FFFFFF0h + pop bx + pop si + pop eax + ret + +; +; nextsector: Given a sector in EAX on input, return the next sector +; of the same filesystem object, which may be the root +; directory or a cluster chain. Returns EOF. +; +; Assumes CS == DS. +; +nextsector: + push edi + push edx + mov edx,[DataArea] + mov edi,eax + sub edi,edx + jae .isdata + + ; Root directory + inc eax + cmp eax,edx + cmc + jmp .done + +.isdata: + not edi + test edi,[ClustMask] + jz .endcluster + + ; It's not the final sector in a cluster + inc eax + jmp .done + +.endcluster: + push gs ; nextcluster trashes gs + push cx + not edi + mov cl,[ClustShift] + shr edi,cl + add edi,2 + + ; Now EDI contains the cluster number + call nextcluster + cmc + jc .exit ; There isn't anything else... + + ; New cluster number now in EDI + sub edi,2 + shl edi,cl ; CF <- 0, unless something is very wrong + lea eax,[edi+edx] +.exit: + pop cx + pop gs +.done: + pop edx + pop edi + ret + +; +; getfatsector: Check for a particular sector (in EAX) in the FAT cache, +; and return a pointer in GS:SI, loading it if needed. +; +; Assumes CS == DS. +; +getfatsector: + add eax,[FAT] ; FAT starting address + jmp getcachesector + +; ----------------------------------------------------------------------------- +; Common modules +; ----------------------------------------------------------------------------- + +%include "getc.inc" ; getc et al +%include "conio.inc" ; Console I/O +%include "plaincon.inc" ; writechr +%include "writestr.inc" ; String output +%include "configinit.inc" ; Initialize configuration +%include "parseconfig.inc" ; High-level config file handling +%include "parsecmd.inc" ; Low-level config file handling +%include "bcopy32.inc" ; 32-bit bcopy +%include "loadhigh.inc" ; Load a file into high memory +%include "font.inc" ; VGA font stuff +%include "graphics.inc" ; VGA graphics +%include "highmem.inc" ; High memory sizing +%include "strcpy.inc" ; strcpy() +%include "cache.inc" ; Metadata disk cache +%include "adv.inc" ; Auxillary Data Vector +%include "localboot.inc" ; Disk-based local boot + +; ----------------------------------------------------------------------------- +; Begin data section +; ----------------------------------------------------------------------------- + + section .data +copyright_str db ' Copyright (C) 1994-', year, ' H. Peter Anvin' + db CR, LF, 0 +err_bootfailed db CR, LF, 'Boot failed: please change disks and press ' + db 'a key to continue.', CR, LF, 0 +syslinux_cfg1 db '/boot' ; /boot/syslinux/syslinux.cfg +syslinux_cfg2 db '/syslinux' ; /syslinux/syslinux.cfg +syslinux_cfg3 db '/' ; /syslinux.cfg +config_name db 'syslinux.cfg', 0 ; syslinux.cfg + +; +; Command line options we'd like to take a look at +; +; mem= and vga= are handled as normal 32-bit integer values +initrd_cmd db 'initrd=' +initrd_cmd_len equ 7 + +; +; Config file keyword table +; +%include "keywords.inc" + +; +; Extensions to search for (in *forward* order). +; +exten_table: db '.cbt' ; COMBOOT (specific) + db '.bss' ; Boot Sector (add superblock) + db '.bs', 0 ; Boot Sector + db '.com' ; COMBOOT (same as DOS) + db '.c32' ; COM32 +exten_table_end: + dd 0, 0 ; Need 8 null bytes here + +; +; Misc initialized (data) variables +; +%ifdef debug ; This code for debugging only +debug_magic dw 0D00Dh ; Debug code sentinel +%endif + + alignb 4, db 0 +BufSafe dw trackbufsize/SECTOR_SIZE ; Clusters we can load into trackbuf +BufSafeBytes dw trackbufsize ; = how many bytes? +%ifndef DEPEND +%if ( trackbufsize % SECTOR_SIZE ) != 0 +%error trackbufsize must be a multiple of SECTOR_SIZE +%endif +%endif diff --git a/core/loadhigh.inc b/core/loadhigh.inc new file mode 100644 index 00000000..63041483 --- /dev/null +++ b/core/loadhigh.inc @@ -0,0 +1,120 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; loadhigh.inc +;; +;; Load a file into high memory +;; + + section .text + +; +; load_high: loads (the remainder of) a file into high memory. +; This routine prints dots for each 64K transferred, and +; calls abort_check periodically. +; +; The xfer_buf_seg is used as a bounce buffer. +; +; Assumes CS == DS. +; +; The input address (EDI) should be dword aligned, and the final +; stretch is padded with zeroes if necessary. +; +; Inputs: SI = file handle/cluster pointer +; EDI = target address in high memory +; EAX = maximum number of bytes to load +; DX = zero-padding mask (e.g. 0003h for pad to dword) +; BX = subroutine to call at the top of each loop +; (to print status and check for abort) +; MyHighMemSize = maximum load address +; +; Outputs: SI = file handle/cluster pointer +; EBX = first untouched address (not including padding) +; EDI = first untouched address (including padding) +; CF = reached EOF +; +load_high: + push es ; <AAA> ES + + mov cx,xfer_buf_seg + mov es,cx + mov [PauseBird],bx + +.read_loop: + and si,si ; If SI == 0 then we have end of file + jz .eof + call [PauseBird] + + push eax ; <A> Total bytes to transfer + cmp eax,(1 << 16) ; Max 64K in one transfer + jna .size_ok + mov eax,(1 << 16) +.size_ok: + push eax ; <B> Bytes transferred this chunk + add eax,SECTOR_SIZE-1 + shr eax,SECTOR_SHIFT ; Convert to sectors + + ; Now (e)ax contains the number of sectors to get + push edi ; <C> Target buffer + mov cx,ax + xor bx,bx ; ES:0 + call getfssec ; Load the data into xfer_buf_seg + pop edi ; <C> Target buffer + pushf ; <C> EOF status + lea ebx,[edi+ecx] ; End of data +.fix_slop: + test cx,dx + jz .noslop + ; The last dword fractional - pad with zeroes + ; Zero-padding is critical for multi-file initramfs. + mov byte [es:ecx],0 + inc cx + jmp short .fix_slop +.noslop: + lea eax,[edi+ecx] + cmp eax,[MyHighMemSize] + ja .overflow + + push esi ; <D> File handle/cluster pointer + mov esi,(xfer_buf_seg << 4) ; Source address + call bcopy ; Copy to high memory + pop esi ; <D> File handle/cluster pointer + popf ; <C> EOF status + pop ecx ; <B> Byte count this round + pop eax ; <A> Total bytes to transfer + jc .eof + sub eax,ecx + jnz .read_loop ; More to read... (if ZF=1 then CF=0) +.eof: + pop es ; <AAA> ES + ret + +.overflow: mov si,err_nohighmem + jmp abort_load + +dot_pause: + push ax + mov al,'.' + call writechr + pop ax + jmp abort_check ; Handles the return + + section .data +err_nohighmem db CR, LF + db 'Not enough memory to load specified image.', CR, LF, 0 + + section .bss + alignb 2 +PauseBird resw 1 + + section .text diff --git a/core/localboot.inc b/core/localboot.inc new file mode 100644 index 00000000..b6b31deb --- /dev/null +++ b/core/localboot.inc @@ -0,0 +1,75 @@ +; ----------------------------------------------------------------------- +; +; Copyright 1999-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 51 Franklin St, Fifth Floor, +; Boston MA 02110-1301, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; ----------------------------------------------------------------------- + +; +; localboot.inc +; +; Boot from a local disk, or invoke INT 18h. +; + +%if HAS_LOCALBOOT + +; +; Boot a specified local disk. AX specifies the BIOS disk number; or +; -1 in case we should execute INT 18h ("next device.") +; + section .text + +local_boot: + call vgaclearmode + RESET_STACK_AND_SEGS dx ; dx <- 0 + mov fs,dx + mov gs,dx + mov si,localboot_msg + call writestr + cmp ax,-1 + je .int18 + + ; Load boot sector from the specified BIOS device and jump to it. + mov dl,al + xor dh,dh + push dx + xor ax,ax ; Reset drive + int 13h + mov ax,0201h ; Read one sector + mov cx,0001h ; C/H/S = 0/0/1 (first sector) + mov bx,trackbuf + mov bp,retry_count +.again: + pusha + int 13h + popa + jnc .ok + dec bp + jnz .again + jmp kaboom ; Failure... +.ok: + pop dx + cli ; Abandon hope, ye who enter here + mov si,trackbuf + mov di,07C00h + mov cx,512 ; Probably overkill, but should be safe + rep movsd + mov ss,cx ; SS <- 0 + mov sp,7C00h + jmp 0:07C00h ; Jump to new boot sector + +.int18: + int 18h ; Hope this does the right thing... + jmp kaboom ; If we returned, oh boy... + + section .data +localboot_msg db 'Booting from local disk...', CR, LF, 0 + + section .text + +%endif ; HAS_LOCALBOOT diff --git a/core/lstadjust.pl b/core/lstadjust.pl new file mode 100755 index 00000000..cec54029 --- /dev/null +++ b/core/lstadjust.pl @@ -0,0 +1,57 @@ +#!/usr/bin/perl +# +# Take a NASM list and map file and make the offsets in the list file +# absolute. This makes debugging a lot easier. +# +# Usage: +# +# lstadjust.pl listfile mapfile outfile +# + +($listfile, $mapfile, $outfile) = @ARGV; + +open(LST, "< $listfile\0") + or die "$0: cannot open: $listfile: $!\n"; +open(MAP, "< $mapfile\0") + or die "$0: cannot open: $mapfile: $!\n"; +open(OUT, "> $outfile\0") + or die "$0: cannot create: $outfile: $!\n"; + +%vstart = (); + +while (defined($line = <MAP>)) { + if ($line =~ /^\s*([0-9]+)\s+(\S+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+2\*\*([0-9]+)/i) { + $vstart{$2} = hex $4; + } +} +close(MAP); + +$offset = 0; +@ostack = (); + +while (defined($line = <LST>)) { + chomp $line; + + $source = substr($line, 40); + if ($source =~ /^([^;]*);/) { + $source = $1; + } + + ($label, $op, $arg, $tail) = split(/\s+/, $source); + if ($op =~ /^(|\[)section$/i) { + $offset = $vstart{$arg}; + } elsif ($op =~ /^(absolute|\[absolute)$/i) { + $offset = 0; + } elsif ($op =~ /^struc$/i) { + push(@ostack, $offset); + $offset = 0; + } elsif ($op =~ /^endstruc$/i) { + $offset = pop(@ostack); + } + + if ($line =~ /^(\s*[0-9]+ )([0-9A-F]{8})(\s.*)$/) { + $line = sprintf("%s%08X%s", $1, (hex $2)+$offset, $3); + } + + print OUT $line, "\n"; +} diff --git a/core/macros.inc b/core/macros.inc new file mode 100644 index 00000000..f5e2c924 --- /dev/null +++ b/core/macros.inc @@ -0,0 +1,110 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; macros.inc +;; +;; Convenient macros +;; + +%ifndef _MACROS_INC +%define _MACROS_INC + +; +; Identify the module we're compiling; the "correct" should be defined +; in the module itself to 1 +; +%ifndef IS_SYSLINUX +%define IS_SYSLINUX 0 +%endif +%ifndef IS_MDSLINUX +%define IS_MDSLINUX 0 +%endif +%ifndef IS_PXELINUX +%define IS_PXELINUX 0 +%endif +%ifndef IS_ISOLINUX +%define IS_ISOLINUX 0 +%endif +%ifndef IS_EXTLINUX +%define IS_EXTLINUX 0 +%endif + +; +; Macros similar to res[bwd], but which works in the code segment (after +; section .text) or the data segment (section .data) +; +%macro zb 1.nolist + times %1 db 0 +%endmacro + +%macro zw 1.nolist + times %1 dw 0 +%endmacro + +%macro zd 1.nolist + times %1 dd 0 +%endmacro + +; +; Macro to emit an unsigned decimal number as a string +; +%macro asciidec 1.nolist +%ifndef DEPEND ; Not safe for "depend" +%if %1 >= 1000000000 + db ((%1/1000000000) % 10) + '0' +%endif +%if %1 >= 100000000 + db ((%1/100000000) % 10) + '0' +%endif +%if %1 >= 10000000 + db ((%1/10000000) % 10) + '0' +%endif +%if %1 >= 1000000 + db ((%1/1000000) % 10) + '0' +%endif +%if %1 >= 100000 + db ((%1/100000) % 10) + '0' +%endif +%if %1 >= 10000 + db ((%1/10000) % 10) + '0' +%endif +%if %1 >= 1000 + db ((%1/1000) % 10) + '0' +%endif +%if %1 >= 100 + db ((%1/100) % 10) + '0' +%endif +%if %1 >= 10 + db ((%1/10) % 10) + '0' +%endif + db (%1 % 10) + '0' +%endif +%endmacro + +; +; Macros for network byte order of constants +; +%define htons(x) ( ( ((x) & 0FFh) << 8 ) + ( ((x) & 0FF00h) >> 8 ) ) +%define ntohs(x) htons(x) +%define htonl(x) ( ( ((x) & 0FFh) << 24) + ( ((x) & 0FF00h) << 8 ) + ( ((x) & 0FF0000h) >> 8 ) + ( ((x) & 0FF000000h) >> 24) ) +%define ntohl(x) htonl(x) + +; +; ASCII +; +CR equ 13 ; Carriage Return +LF equ 10 ; Line Feed +FF equ 12 ; Form Feed +BS equ 8 ; Backspace + +%endif ; _MACROS_INC diff --git a/core/now.pl b/core/now.pl new file mode 100644 index 00000000..a3b5a798 --- /dev/null +++ b/core/now.pl @@ -0,0 +1,21 @@ +#!/usr/bin/perl +# +# Print the time (possibly the mtime of a file) as a hexadecimal integer +# If more than one file, print the mtime of the *newest* file. +# + +undef $now; + +foreach $file ( @ARGV ) { + ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime, + $ctime,$blksize,$blocks) = stat($file); + if ( !defined($now) || $now < $mtime ) { + $now = $mtime; + } +} + +if ( !defined($now) ) { + $now = time; +} + +printf "0x%08x\n", $now; diff --git a/core/parsecmd.inc b/core/parsecmd.inc new file mode 100644 index 00000000..8e648699 --- /dev/null +++ b/core/parsecmd.inc @@ -0,0 +1,120 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; parsecmd.inc +;; +;; Command line parser code +;; + + section .text + +; ------------------------------------------------------------------------- +; getcommand: Get a keyword from the current "getc" file and match it +; against a list of keywords (keywd_table). Each entry in +; that table should have the following form: +; <32 bit hash value> <16 bit handler offset> +; +; The handler is called, and upon return this function +; returns with CF = 0. On EOF, this function returns +; with CF = 1. +; ------------------------------------------------------------------------- + +getcommand: +.find: + call skipspace ; Skip leading whitespace + jz .eof ; End of file + jc .find ; End of line: try again + + ; Do this explicitly so #foo is treated as a comment + cmp al,'#' ; Leading hash mark -> comment + je .skipline + + or al,20h ; Convert to lower case + movzx ebx,al ; Hash for a one-char keyword +.read_loop: + push ebx + call getc + pop ebx + jc .eof + cmp al,' ' ; Whitespace + jbe .done + or al,20h + rol ebx,5 + xor bl,al + jmp short .read_loop +.done: call ungetc + call skipspace + jz .eof + jc .noparm + call ungetc ; Return nonwhitespace char to buf + mov si,keywd_table + mov cx,keywd_count +.table_search: + lodsd + cmp ebx,eax + je .found_keywd + lodsd ; Skip entrypoint/argument + loop .table_search + + ; Otherwise unrecognized keyword + mov si,err_badcfg + jmp short .error + + ; No parameter +.noparm: + mov si,err_noparm + mov al,10 ; Already at EOL +.error: + call cwritestr + jmp short .skipline + +.found_keywd: lodsw ; Load argument into ax + call [si] + clc + ret + +.eof: stc + ret + +.skipline: cmp al,10 ; Search for LF + je .find + call getc + jc .eof + jmp short .skipline + + section .data +err_badcfg db 'Unknown keyword in configuration file.', CR, LF, 0 +err_noparm db 'Missing parameter in configuration file.', CR, LF, 0 + + section .uibss + alignb 4 +vk_size equ (vk_end + 3) & ~3 +VKernelBuf: resb vk_size ; "Current" vkernel +AppendBuf resb max_cmd_len+1 ; append= +Ontimeout resb max_cmd_len+1 ; ontimeout +Onerror resb max_cmd_len+1 ; onerror +KbdMap resb 256 ; Keyboard map +FKeyName resb MAX_FKEYS*FILENAME_MAX ; File names for F-key help +KernelCNameLen resw 1 ; Length of unmangled kernel name +InitRDCNameLen resw 1 ; Length of unmangled initrd name +%if IS_SYSLINUX +KernelName resb FILENAME_MAX+1 ; Mangled name for kernel +KernelCName resb FILENAME_MAX+2 ; Unmangled kernel name +InitRDCName resb FILENAME_MAX+2 ; Unmangled initrd name +%else +KernelName resb FILENAME_MAX ; Mangled name for kernel +KernelCName resb FILENAME_MAX ; Unmangled kernel name +InitRDCName resb FILENAME_MAX ; Unmangled initrd name +%endif +MNameBuf resb FILENAME_MAX +InitRD resb FILENAME_MAX diff --git a/core/parseconfig.inc b/core/parseconfig.inc new file mode 100644 index 00000000..2ef9c3a2 --- /dev/null +++ b/core/parseconfig.inc @@ -0,0 +1,454 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; parseconfig.inc +;; +;; Configuration file operations +;; + + section .text +; +; "default" command +; +pc_default: mov di,default_cmd + call getline + mov byte [di-1],0 ; null-terminate + ret + +; +; "ontimeout" command +; +pc_ontimeout: mov di,Ontimeout + call getline + sub di,Ontimeout+1 ; Don't need final space + mov [OntimeoutLen],di + ret + +; +; "onerror" command +; +pc_onerror: mov di,Onerror + call getline + sub di,Onerror + mov [OnerrorLen],di + ret + +; +; "append" command +; +pc_append: cmp byte [VKernel],0 + ja .vk + mov di,AppendBuf + call getline + sub di,AppendBuf +.app1: mov [AppendLen],di + ret +.vk: mov di,VKernelBuf+vk_append ; "append" command (vkernel) + call getline + sub di,VKernelBuf+vk_append + cmp di,byte 2 + jne .app2 + cmp byte [VKernelBuf+vk_append],'-' + jne .app2 + xor di,di ; If "append -" -> null string +.app2: mov [VKernelBuf+vk_appendlen],di + ret + +; +; "ipappend" command (PXELINUX only) +; +%if IS_PXELINUX +pc_ipappend: call getint + jc .err + cmp byte [VKernel],0 + jne .vk + mov [IPAppend],bl +.err: ret +.vk: mov [VKernelBuf+vk_ipappend],bl + ret +%endif + +; +; "localboot" command (PXELINUX, ISOLINUX) +; +%if HAS_LOCALBOOT + +pc_localboot: call getint + cmp byte [VKernel],0 ; ("label" section only) + je .err + mov di,VKernelBuf+vk_rname + xor ax,ax + mov cx,FILENAME_MAX + rep stosb ; Null kernel name +%if IS_PXELINUX + ; PXELINUX uses the first 4 bytes of vk_rname for the + ; mangled IP address + mov [VKernelBuf+vk_rname+5], bx ; Return type +%else + mov [VKernelBuf+vk_rname+1], bx ; Return type +%endif +.err: ret + +%endif ; HAS_LOCALBOOT + +; +; "kernel", "config", ... command +; +pc_kernel: cmp byte [VKernel],0 + je .err ; ("label" section only) + mov [VKernelBuf+vk_type],al + call pc_getline + mov di,VKernelBuf+vk_rname + call mangle_name +.err: ret + +; +; "timeout", "totaltimeout" command +; +; N.B. 1/10 s ~ 1.D2162AABh clock ticks +; +pc_timeout: push ax + call getint + pop si + jc .err + mov eax,0D2162AABh + mul ebx ; clock ticks per 1/10 s + add ebx,edx + mov [si],ebx +.err: ret + + +; +; "totaltimeout" command +; +pc_totaltimeout: + +; +; Generic integer variable setting commands: +; "prompt", "implicit" +; +pc_setint16: + push ax + call getint + pop si + jc .err + mov [si],bx +.err: ret + +; +; Generic file-processing commands: +; "font", "kbdmap", +; +pc_filecmd: push ax ; Function to tailcall + call pc_getline + mov di,MNameBuf + call mangle_name + call searchdir + jnz .ok + pop ax ; Drop the successor function +.ok: ret ; Tailcall if OK, error return + +; +; Commands that expect the file to be opened on top of the getc stack. +; "display", "include" +; +pc_opencmd: push ax ; Function to tailcall + call pc_getline + mov di,MNameBuf + call mangle_name + call open + jnz .ok + pop ax ; Drop the successor function +.ok: ret ; Tailcall if OK, error return + +; +; "include" command (invoked from pc_opencmd) +; +pc_include: inc word [IncludeLevel] +.err: ret + +; +; "serial" command +; +pc_serial: call getint + jc .err + push bx ; Serial port # + call skipspace + jnc .ok + pop bx +.err: ret +.ok: + call ungetc + call getint + mov [FlowControl], word 0 ; Default to no flow control + jc .nobaud +.valid_baud: + push ebx + call skipspace + jc .no_flow + call ungetc + call getint ; Hardware flow control? + jnc .valid_flow +.no_flow: + xor bx,bx ; Default -> no flow control +.valid_flow: + and bh,0Fh ; FlowIgnore + shl bh,4 + mov [FlowIgnore],bh + mov bh,bl + and bx,0F003h ; Valid bits + mov [FlowControl],bx + pop ebx ; Baud rate + jmp short .parse_baud +.nobaud: + mov ebx,DEFAULT_BAUD ; No baud rate given +.parse_baud: + pop di ; Serial port # + cmp ebx,byte 75 + jb .err ; < 75 baud == bogus + mov eax,BAUD_DIVISOR + cdq + div ebx + mov [BaudDivisor],ax + push ax ; Baud rate divisor + cmp di,3 + ja .port_is_io ; If port > 3 then port is I/O addr + shl di,1 + mov di,[di+serial_base] ; Get the I/O port from the BIOS +.port_is_io: + mov [SerialPort],di + + ; + ; Begin code to actually set up the serial port + ; + lea dx,[di+3] ; DX -> LCR + mov al,83h ; Enable DLAB + call slow_out + + pop ax ; Divisor + mov dx,di ; DX -> LS + call slow_out + + inc dx ; DX -> MS + mov al,ah + call slow_out + + mov al,03h ; Disable DLAB + inc dx ; DX -> LCR + inc dx + call slow_out + + in al,dx ; Read back LCR (detect missing hw) + cmp al,03h ; If nothing here we'll read 00 or FF + jne .serial_port_bad ; Assume serial port busted + dec dx + dec dx ; DX -> IER + xor al,al ; IRQ disable + call slow_out + + inc dx ; DX -> FCR/IIR + mov al,01h + call slow_out ; Enable FIFOs if present + in al,dx + cmp al,0C0h ; FIFOs enabled and usable? + jae .fifo_ok + xor ax,ax ; Disable FIFO if unusable + call slow_out +.fifo_ok: + + inc dx + inc dx ; DX -> MCR + in al,dx + or al,[FlowOutput] ; Assert bits + call slow_out + + ; Show some life + cmp byte [SerialNotice],0 + je .notfirst + mov byte [SerialNotice],0 + + mov si,syslinux_banner + call write_serial_str + mov si,copyright_str + call write_serial_str +.notfirst: + ret + +.serial_port_bad: + mov [SerialPort], word 0 + ret + +; +; "F"-key command +; +pc_fkey: push ax + call pc_getline + pop di + call mangle_name ; Mangle file name + ret + +; +; "label" command +; +pc_label: call commit_vk ; Commit any current vkernel + mov di,VKernelBuf ; Erase the vkernelbuf for better compression + mov cx,(vk_size >> 1) + xor ax,ax + rep stosw + call pc_getline + mov di,VKernelBuf+vk_vname + call mangle_name ; Mangle virtual name + mov byte [VKernel],1 ; We've seen a "label" statement + mov si,VKernelBuf+vk_vname ; By default, rname == vname + ; mov di,VKernelBuf+vk_rname ; -- already set + mov cx,FILENAME_MAX + rep movsb + mov si,AppendBuf ; Default append==global append + mov di,VKernelBuf+vk_append + mov cx,[AppendLen] + mov [VKernelBuf+vk_appendlen],cx + rep movsb +%if IS_PXELINUX ; PXELINUX only + mov al,[IPAppend] ; Default ipappend==global ipappend + mov [VKernelBuf+vk_ipappend],al +%endif + ret + +; +; "say" command +; +pc_say: call pc_getline ; "say" command + call writestr + jmp crlf ; tailcall + +; +; "text" command; ignore everything until we get an "endtext" line +; +pc_text: call pc_getline ; Ignore rest of line +.loop: + call pc_getline + jc .eof + + ; Leading spaces are already removed... + lodsd + and eax,0xdfdfdfdf ; Upper case + cmp eax,'ENDT' + jne .loop + lodsd + and eax,0x00dfdfdf ; Upper case and mask + cmp eax,'EXT' + jne .loop + ; If we get here we hit ENDTEXT +.eof: + ret + +; +; Comment line +; +pc_comment: ; Fall into pc_getline + +; +; Common subroutine: load line into trackbuf; returns with SI -> trackbuf +; CF is set on EOF. +; +pc_getline: mov di,trackbuf + push di + call getline + mov byte [di],0 ; Null-terminate + pop si + ret + +; +; Main loop for configuration file parsing +; +parse_config: + mov di,VKernelBuf ; Clear VKernelBuf at start + xor ax,ax + mov cx,vk_size + rep stosb + +.again: + call getcommand ; Parse one command + jnc .again ; If not EOF... + call close + dec word [IncludeLevel] ; Still parsing? + jnz .again + + ; + ; The fall through to commit_vk to commit any final + ; VKernel being read + ; +; +; commit_vk: Store the current VKernelBuf into buffer segment +; +commit_vk: + ; For better compression, clean up the append field + mov ax,[VKernelBuf+vk_appendlen] + mov di,VKernelBuf+vk_append + add di,ax + mov cx,max_cmd_len+1 + sub cx,ax + xor ax,ax + rep stosb + + ; Pack into high memory + mov si,VKernelBuf + mov edi,[VKernelEnd] + mov cx,vk_size + call rllpack + mov [VKernelEnd],edi + ret +.overflow: + mov si,vk_overflow_msg + call writestr + ret + + section .data +vk_overflow_msg db 'Out of memory parsing config file', CR, LF, 0 +SerialNotice db 1 ; Only print this once + + section .bss + alignb 4 +VKernelEnd resd 1 ; Lowest high memory address used + + ; This symbol should be used by loaders to indicate + ; the highest address *they* are allowed to use. +HighMemRsvd equ VKernelEnd + ; by vkernels + section .config + align 4, db 0 +KbdTimeout dd 0 ; Keyboard timeout (if any) +TotalTimeout dd 0 ; Total timeout (if any) +AppendLen dw 0 ; Bytes in append= command +OntimeoutLen dw 0 ; Bytes in ontimeout command +OnerrorLen dw 0 ; Bytes in onerror command +CmdLinePtr dw cmd_line_here ; Command line advancing pointer +ForcePrompt dw 0 ; Force prompt +NoEscape dw 0 ; No escape +AllowImplicit dw 1 ; Allow implicit kernels +AllowOptions dw 1 ; User-specified options allowed +IncludeLevel dw 1 ; Nesting level +SerialPort dw 0 ; Serial port base (or 0 for no serial port) +VKernel db 0 ; Have we seen any "label" statements? + +%if IS_PXELINUX +IPAppend db 0 ; Default IPAPPEND option +%endif + + section .uibss + alignb 4 ; For the good of REP MOVSD +command_line resb max_cmd_len+2 ; Command line buffer + alignb 4 +default_cmd resb max_cmd_len+1 ; "default" command line + +%include "rllpack.inc" diff --git a/core/plaincon.inc b/core/plaincon.inc new file mode 100644 index 00000000..59b2cbb5 --- /dev/null +++ b/core/plaincon.inc @@ -0,0 +1,24 @@ +; +; writechr: Write a single character in AL to the console without +; mangling any registers; handle video pages correctly. +; + section .text + +writechr: + call write_serial ; write to serial port if needed + pushfd + test byte [cs:UsingVGA], 08h + jz .videook + call vgaclearmode +.videook: + test byte [cs:DisplayCon], 01h + jz .nothing + pushad + mov ah,0Eh + mov bl,07h ; attribute + mov bh,[cs:BIOS_page] ; current page + int 10h + popad +.nothing: + popfd + ret diff --git a/core/pxe.inc b/core/pxe.inc new file mode 100644 index 00000000..7471c4f0 --- /dev/null +++ b/core/pxe.inc @@ -0,0 +1,154 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1999-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; pxe.inc +;; +;; PXE opcodes +;; + +%ifndef _PXE_INC +%define _PXE_INC 1 + +%define PXENV_TFTP_OPEN 0x0020 +%define PXENV_TFTP_CLOSE 0x0021 +%define PXENV_TFTP_READ 0x0022 +%define PXENV_TFTP_READ_FILE 0x0023 +%define PXENV_TFTP_READ_FILE_PMODE 0x0024 +%define PXENV_TFTP_GET_FSIZE 0x0025 + +%define PXENV_UDP_OPEN 0x0030 +%define PXENV_UDP_CLOSE 0x0031 +%define PXENV_UDP_READ 0x0032 +%define PXENV_UDP_WRITE 0x0033 + +%define PXENV_START_UNDI 0x0000 +%define PXENV_UNDI_STARTUP 0x0001 +%define PXENV_UNDI_CLEANUP 0x0002 +%define PXENV_UNDI_INITIALIZE 0x0003 +%define PXENV_UNDI_RESET_NIC 0x0004 +%define PXENV_UNDI_SHUTDOWN 0x0005 +%define PXENV_UNDI_OPEN 0x0006 +%define PXENV_UNDI_CLOSE 0x0007 +%define PXENV_UNDI_TRANSMIT 0x0008 +%define PXENV_UNDI_SET_MCAST_ADDR 0x0009 +%define PXENV_UNDI_SET_STATION_ADDR 0x000A +%define PXENV_UNDI_SET_PACKET_FILTER 0x000B +%define PXENV_UNDI_GET_INFORMATION 0x000C +%define PXENV_UNDI_GET_STATISTICS 0x000D +%define PXENV_UNDI_CLEAR_STATISTICS 0x000E +%define PXENV_UNDI_INITIATE_DIAGS 0x000F +%define PXENV_UNDI_FORCE_INTERRUPT 0x0010 +%define PXENV_UNDI_GET_MCAST_ADDR 0x0011 +%define PXENV_UNDI_GET_NIC_TYPE 0x0012 +%define PXENV_UNDI_GET_IFACE_INFO 0x0013 +%define PXENV_UNDI_ISR 0x0014 +%define PXENV_STOP_UNDI 0x0015 ; Overlap...? +%define PXENV_UNDI_GET_STATE 0x0015 ; Overlap...? + +%define PXENV_UNLOAD_STACK 0x0070 +%define PXENV_GET_CACHED_INFO 0x0071 +%define PXENV_RESTART_DHCP 0x0072 +%define PXENV_RESTART_TFTP 0x0073 +%define PXENV_MODE_SWITCH 0x0074 +%define PXENV_START_BASE 0x0075 +%define PXENV_STOP_BASE 0x0076 + +; gPXE extensions... +%define PXENV_FILE_OPEN 0x00e0 +%define PXENV_FILE_CLOSE 0x00e1 +%define PXENV_FILE_SELECT 0x00e2 +%define PXENV_FILE_READ 0x00e3 +%define PXENV_GET_FILE_SIZE 0x00e4 +%define PXENV_FILE_EXEC 0x00e5 +%define PXENV_FILE_API_CHECK 0x00e6 + +; Exit codes +%define PXENV_EXIT_SUCCESS 0x0000 +%define PXENV_EXIT_FAILURE 0x0001 + +; Status codes +%define PXENV_STATUS_SUCCESS 0x00 +%define PXENV_STATUS_FAILURE 0x01 +%define PXENV_STATUS_BAD_FUNC 0x02 +%define PXENV_STATUS_UNSUPPORTED 0x03 +%define PXENV_STATUS_KEEP_UNDI 0x04 +%define PXENV_STATUS_KEEP_ALL 0x05 +%define PXENV_STATUS_OUT_OF_RESOURCES 0x06 +%define PXENV_STATUS_ARP_TIMEOUT 0x11 +%define PXENV_STATUS_UDP_CLOSED 0x18 +%define PXENV_STATUS_UDP_OPEN 0x19 +%define PXENV_STATUS_TFTP_CLOSED 0x1a +%define PXENV_STATUS_TFTP_OPEN 0x1b +%define PXENV_STATUS_MCOPY_PROBLEM 0x20 +%define PXENV_STATUS_BIS_INTEGRITY_FAILURE 0x21 +%define PXENV_STATUS_BIS_VALIDATE_FAILURE 0x22 +%define PXENV_STATUS_BIS_INIT_FAILURE 0x23 +%define PXENV_STATUS_BIS_SHUTDOWN_FAILURE 0x24 +%define PXENV_STATUS_BIS_GBOA_FAILURE 0x25 +%define PXENV_STATUS_BIS_FREE_FAILURE 0x26 +%define PXENV_STATUS_BIS_GSI_FAILURE 0x27 +%define PXENV_STATUS_BIS_BAD_CKSUM 0x28 +%define PXENV_STATUS_TFTP_CANNOT_ARP_ADDRESS 0x30 +%define PXENV_STATUS_TFTP_OPEN_TIMEOUT 0x32 + +%define PXENV_STATUS_TFTP_UNKNOWN_OPCODE 0x33 +%define PXENV_STATUS_TFTP_READ_TIMEOUT 0x35 +%define PXENV_STATUS_TFTP_ERROR_OPCODE 0x36 +%define PXENV_STATUS_TFTP_CANNOT_OPEN_CONNECTION 0x38 +%define PXENV_STATUS_TFTP_CANNOT_READ_FROM_CONNECTION 0x39 +%define PXENV_STATUS_TFTP_TOO_MANY_PACKAGES 0x3a +%define PXENV_STATUS_TFTP_FILE_NOT_FOUND 0x3b +%define PXENV_STATUS_TFTP_ACCESS_VIOLATION 0x3c +%define PXENV_STATUS_TFTP_NO_MCAST_ADDRESS 0x3d +%define PXENV_STATUS_TFTP_NO_FILESIZE 0x3e +%define PXENV_STATUS_TFTP_INVALID_PACKET_SIZE 0x3f +%define PXENV_STATUS_DHCP_TIMEOUT 0x51 +%define PXENV_STATUS_DHCP_NO_IP_ADDRESS 0x52 +%define PXENV_STATUS_DHCP_NO_BOOTFILE_NAME 0x53 +%define PXENV_STATUS_DHCP_BAD_IP_ADDRESS 0x54 +%define PXENV_STATUS_UNDI_INVALID_FUNCTION 0x60 +%define PXENV_STATUS_UNDI_MEDIATEST_FAILED 0x61 +%define PXENV_STATUS_UNDI_CANNOT_INIT_NIC_FOR_MCAST 0x62 +%define PXENV_STATUS_UNDI_CANNOT_INITIALIZE_NIC 0x63 +%define PXENV_STATUS_UNDI_CANNOT_INITIALIZE_PHY 0x64 +%define PXENV_STATUS_UNDI_CANNOT_READ_CONFIG_DATA 0x65 +%define PXENV_STATUS_UNDI_CANNOT_READ_INIT_DATA 0x66 +%define PXENV_STATUS_UNDI_BAD_MAC_ADDRESS 0x67 +%define PXENV_STATUS_UNDI_BAD_EEPROM_CHECKSUM 0x68 +%define PXENV_STATUS_UNDI_ERROR_SETTING_ISR 0x69 +%define PXENV_STATUS_UNDI_INVALID_STATE 0x6a +%define PXENV_STATUS_UNDI_TRANSMIT_ERROR 0x6b +%define PXENV_STATUS_UNDI_INVALID_PARAMETER 0x6c +%define PXENV_STATUS_BSTRAP_PROMPT_MENU 0x74 +%define PXENV_STATUS_BSTRAP_MCAST_ADDR 0x76 +%define PXENV_STATUS_BSTRAP_MISSING_LIST 0x77 +%define PXENV_STATUS_BSTRAP_NO_RESPONSE 0x78 +%define PXENV_STATUS_BSTRAP_FILE_TOO_BIG 0x79 +%define PXENV_STATUS_BINL_CANCELED_BY_KEYSTROKE 0xa0 +%define PXENV_STATUS_BINL_NO_PXE_SERVER 0xa1 +%define PXENV_STATUS_NOT_AVAILABLE_IN_PMODE 0xa2 +%define PXENV_STATUS_NOT_AVAILABLE_IN_RMODE 0xa3 +%define PXENV_STATUS_BUSD_DEVICE_NOT_SUPPORTED 0xb0 +%define PXENV_STATUS_LOADER_NO_FREE_BASE_MEMORY 0xc0 +%define PXENV_STATUS_LOADER_NO_BC_ROMID 0xc1 +%define PXENV_STATUS_LOADER_BAD_BC_ROMID 0xc2 +%define PXENV_STATUS_LOADER_BAD_BC_RUNTIME_IMAGE 0xc3 +%define PXENV_STATUS_LOADER_NO_UNDI_ROMID 0xc4 +%define PXENV_STATUS_LOADER_BAD_UNDI_ROMID 0xc5 +%define PXENV_STATUS_LOADER_BAD_UNDI_DRIVER_IMAGE 0xc6 +%define PXENV_STATUS_LOADER_NO_PXE_STRUCT 0xc8 +%define PXENV_STATUS_LOADER_NO_PXENV_STRUCT 0xc9 +%define PXENV_STATUS_LOADER_UNDI_START 0xca +%define PXENV_STATUS_LOADER_BC_START 0xcb + +%endif ; _PXE_INC diff --git a/core/pxelinux.asm b/core/pxelinux.asm new file mode 100644 index 00000000..ce3250bc --- /dev/null +++ b/core/pxelinux.asm @@ -0,0 +1,2897 @@ +; -*- fundamental -*- (asm-mode sucks) +; **************************************************************************** +; +; pxelinux.asm +; +; A program to boot Linux kernels off a TFTP server using the Intel PXE +; network booting API. It is based on the SYSLINUX boot loader for +; MS-DOS floppies. +; +; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 53 Temple Place Ste 330, +; Boston MA 02111-1307, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; **************************************************************************** + +%define IS_PXELINUX 1 +%include "head.inc" +%include "pxe.inc" + +; gPXE extensions support +%define GPXE 1 + +; +; Some semi-configurable constants... change on your own risk. +; +my_id equ pxelinux_id +FILENAME_MAX_LG2 equ 7 ; log2(Max filename size Including final null) +FILENAME_MAX equ (1 << FILENAME_MAX_LG2) +NULLFILE equ 0 ; Zero byte == null file name +NULLOFFSET equ 4 ; Position in which to look +REBOOT_TIME equ 5*60 ; If failure, time until full reset +%assign HIGHMEM_SLOP 128*1024 ; Avoid this much memory near the top +MAX_OPEN_LG2 equ 5 ; log2(Max number of open sockets) +MAX_OPEN equ (1 << MAX_OPEN_LG2) +PKTBUF_SIZE equ (65536/MAX_OPEN) ; Per-socket packet buffer size +TFTP_PORT equ htons(69) ; Default TFTP port +PKT_RETRY equ 6 ; Packet transmit retry count +PKT_TIMEOUT equ 12 ; Initial timeout, timer ticks @ 55 ms +; Desired TFTP block size +; For Ethernet MTU is normally 1500. Unfortunately there seems to +; be a fair number of networks with "substandard" MTUs which break. +; The code assumes TFTP_LARGEBLK <= 2K. +TFTP_MTU equ 1440 +TFTP_LARGEBLK equ (TFTP_MTU-20-8-4) ; MTU - IP hdr - UDP hdr - TFTP hdr +; Standard TFTP block size +TFTP_BLOCKSIZE_LG2 equ 9 ; log2(bytes/block) +TFTP_BLOCKSIZE equ (1 << TFTP_BLOCKSIZE_LG2) +%assign USE_PXE_PROVIDED_STACK 1 ; Use stack provided by PXE? + +SECTOR_SHIFT equ TFTP_BLOCKSIZE_LG2 +SECTOR_SIZE equ TFTP_BLOCKSIZE + +; +; This is what we need to do when idle +; *** This is disabled because some PXE stacks wait for unacceptably +; *** long if there are no packets receivable. + +%define HAVE_IDLE 0 ; idle is not a noop + +%if HAVE_IDLE +%macro RESET_IDLE 0 + call reset_idle +%endmacro +%macro DO_IDLE 0 + call check_for_arp +%endmacro +%else +%macro RESET_IDLE 0 + ; Nothing +%endmacro +%macro DO_IDLE 0 + ; Nothing +%endmacro +%endif + +; +; TFTP operation codes +; +TFTP_RRQ equ htons(1) ; Read request +TFTP_WRQ equ htons(2) ; Write request +TFTP_DATA equ htons(3) ; Data packet +TFTP_ACK equ htons(4) ; ACK packet +TFTP_ERROR equ htons(5) ; ERROR packet +TFTP_OACK equ htons(6) ; OACK packet + +; +; TFTP error codes +; +TFTP_EUNDEF equ htons(0) ; Unspecified error +TFTP_ENOTFOUND equ htons(1) ; File not found +TFTP_EACCESS equ htons(2) ; Access violation +TFTP_ENOSPACE equ htons(3) ; Disk full +TFTP_EBADOP equ htons(4) ; Invalid TFTP operation +TFTP_EBADID equ htons(5) ; Unknown transfer +TFTP_EEXISTS equ htons(6) ; File exists +TFTP_ENOUSER equ htons(7) ; No such user +TFTP_EOPTNEG equ htons(8) ; Option negotiation failure + +; +; The following structure is used for "virtual kernels"; i.e. LILO-style +; option labels. The options we permit here are `kernel' and `append +; Since there is no room in the bottom 64K for all of these, we +; stick them in high memory and copy them down before we need them. +; + struc vkernel +vk_vname: resb FILENAME_MAX ; Virtual name **MUST BE FIRST!** +vk_rname: resb FILENAME_MAX ; Real name +vk_ipappend: resb 1 ; "IPAPPEND" flag +vk_type: resb 1 ; Type of file +vk_appendlen: resw 1 + alignb 4 +vk_append: resb max_cmd_len+1 ; Command line + alignb 4 +vk_end: equ $ ; Should be <= vk_size + endstruc + +; +; Segment assignments in the bottom 640K +; 0000h - main code/data segment (and BIOS segment) +; +real_mode_seg equ 3000h +pktbuf_seg equ 2000h ; Packet buffers segments +xfer_buf_seg equ 1000h ; Bounce buffer for I/O to high mem +comboot_seg equ real_mode_seg ; COMBOOT image loading zone + +; +; BOOTP/DHCP packet pattern +; + struc bootp_t +bootp: +.opcode resb 1 ; BOOTP/DHCP "opcode" +.hardware resb 1 ; ARP hardware type +.hardlen resb 1 ; Hardware address length +.gatehops resb 1 ; Used by forwarders +.ident resd 1 ; Transaction ID +.seconds resw 1 ; Seconds elapsed +.flags resw 1 ; Broadcast flags +.cip resd 1 ; Client IP +.yip resd 1 ; "Your" IP +.sip resd 1 ; Next server IP +.gip resd 1 ; Relay agent IP +.macaddr resb 16 ; Client MAC address +.sname resb 64 ; Server name (optional) +.bootfile resb 128 ; Boot file name +.option_magic resd 1 ; Vendor option magic cookie +.options resb 1260 ; Vendor options + endstruc + +BOOTP_OPTION_MAGIC equ htonl(0x63825363) ; See RFC 2132 + +; +; TFTP connection data structure. Each one of these corresponds to a local +; UDP port. The size of this structure must be a power of 2. +; HBO = host byte order; NBO = network byte order +; (*) = written by options negotiation code, must be dword sized +; +; For a gPXE connection, we set the local port number to -1 and the +; remote port number contains the gPXE file handle. +; + struc open_file_t +tftp_localport resw 1 ; Local port number (0 = not in use) +tftp_remoteport resw 1 ; Remote port number +tftp_remoteip resd 1 ; Remote IP address +tftp_filepos resd 1 ; Bytes downloaded (including buffer) +tftp_filesize resd 1 ; Total file size(*) +tftp_blksize resd 1 ; Block size for this connection(*) +tftp_bytesleft resw 1 ; Unclaimed data bytes +tftp_lastpkt resw 1 ; Sequence number of last packet (NBO) +tftp_dataptr resw 1 ; Pointer to available data +tftp_goteof resb 1 ; 1 if the EOF packet received + resb 3 ; Currently unusued + ; At end since it should not be zeroed on socked close +tftp_pktbuf resw 1 ; Packet buffer offset + endstruc +%ifndef DEPEND +%if (open_file_t_size & (open_file_t_size-1)) +%error "open_file_t is not a power of 2" +%endif +%endif + +; --------------------------------------------------------------------------- +; BEGIN CODE +; --------------------------------------------------------------------------- + +; +; Memory below this point is reserved for the BIOS and the MBR +; + section .earlybss +trackbufsize equ 8192 +trackbuf resb trackbufsize ; Track buffer goes here + ; ends at 2800h + + alignb open_file_t_size +Files resb MAX_OPEN*open_file_t_size + + alignb FILENAME_MAX +BootFile resb 256 ; Boot file from DHCP packet +PathPrefix resb 256 ; Path prefix derived from boot file +DotQuadBuf resb 16 ; Buffer for dotted-quad IP address +IPOption resb 80 ; ip= option buffer +InitStack resd 1 ; Pointer to reset stack (SS:SP) +PXEStack resd 1 ; Saved stack during PXE call + + section .bss + alignb 4 +RebootTime resd 1 ; Reboot timeout, if set by option +StrucPtr resd 1 ; Pointer to PXENV+ or !PXE structure +APIVer resw 1 ; PXE API version found +IPOptionLen resw 1 ; Length of IPOption +IdleTimer resw 1 ; Time to check for ARP? +LocalBootType resw 1 ; Local boot return code +PktTimeout resw 1 ; Timeout for current packet +RealBaseMem resw 1 ; Amount of DOS memory after freeing +OverLoad resb 1 ; Set if DHCP packet uses "overloading" +DHCPMagic resb 1 ; PXELINUX magic flags + +; The relative position of these fields matter! +MAC_MAX equ 32 ; Handle hardware addresses this long +MACLen resb 1 ; MAC address len +MACType resb 1 ; MAC address type +MAC resb MAC_MAX+1 ; Actual MAC address +BOOTIFStr resb 7 ; Space for "BOOTIF=" +MACStr resb 3*(MAC_MAX+1) ; MAC address as a string + +; The relative position of these fields matter! +UUIDType resb 1 ; Type byte from DHCP option +UUID resb 16 ; UUID, from the PXE stack +UUIDNull resb 1 ; dhcp_copyoption zero-terminates + +; +; PXE packets which don't need static initialization +; + alignb 4 +pxe_unload_stack_pkt: +.status: resw 1 ; Status +.reserved: resw 10 ; Reserved +pxe_unload_stack_pkt_len equ $-pxe_unload_stack_pkt + + alignb 16 + ; BOOTP/DHCP packet buffer + + section .bss2 + alignb 16 +packet_buf resb 2048 ; Transfer packet +packet_buf_size equ $-packet_buf + + section .text + ; + ; PXELINUX needs more BSS than the other derivatives; + ; therefore we relocate it from 7C00h on startup. + ; +StackBuf equ $ ; Base of stack if we use our own + +; +; Primary entry point. +; +bootsec equ $ +_start: + pushfd ; Paranoia... in case of return to PXE + pushad ; ... save as much state as possible + push ds + push es + push fs + push gs + + xor ax,ax + mov ds,ax + mov es,ax + + jmp 0:_start1 ; Canonicalize address +_start1: + mov bp,sp + les bx,[bp+48] ; ES:BX -> !PXE or PXENV+ structure + + ; That is all pushed onto the PXE stack. Save the pointer + ; to it and switch to an internal stack. + mov [InitStack],sp + mov [InitStack+2],ss + +%if USE_PXE_PROVIDED_STACK + ; Apparently some platforms go bonkers if we + ; set up our own stack... + mov [BaseStack],sp + mov [BaseStack+4],ss +%endif + + cli ; Paranoia + lss esp,[BaseStack] + + sti ; Stack set up and ready + cld ; Copy upwards + +; +; Initialize screen (if we're using one) +; + push es ; Save ES -> PXE entry structure + push ds + pop es ; ES <- DS +%include "init.inc" + pop es ; Restore ES -> PXE entry structure +; +; Tell the user we got this far +; + mov si,syslinux_banner + call writestr + + mov si,copyright_str + call writestr + +; +; Assume API version 2.1, in case we find the !PXE structure without +; finding the PXENV+ structure. This should really look at the Base +; Code ROM ID structure in have_pxe, but this is adequate for now -- +; if we have !PXE, we have to be 2.1 or higher, and we don't care +; about higher versions than that. +; + mov word [APIVer],0201h + +; +; Now we need to find the !PXE structure. It's *supposed* to be pointed +; to by SS:[SP+4], but support INT 1Ah, AX=5650h method as well. +; FIX: ES:BX should point to the PXENV+ structure on entry as well. +; We should make that the second test, and not trash ES:BX... +; + cmp dword [es:bx], '!PXE' + je have_pxe + + ; Uh-oh, not there... try plan B + mov ax, 5650h +%if USE_PXE_PROVIDED_STACK == 0 + lss sp,[InitStack] +%endif + int 1Ah ; May trash regs +%if USE_PXE_PROVIDED_STACK == 0 + lss esp,[BaseStack] +%endif + + jc no_pxe + cmp ax,564Eh + jne no_pxe + + ; Okay, that gave us the PXENV+ structure, find !PXE + ; structure from that (if available) + cmp dword [es:bx], 'PXEN' + jne no_pxe + cmp word [es:bx+4], 'V+' + je have_pxenv + + ; Nothing there either. Last-ditch: scan memory + call memory_scan_for_pxe_struct ; !PXE scan + jnc have_pxe + call memory_scan_for_pxenv_struct ; PXENV+ scan + jnc have_pxenv + +no_pxe: mov si,err_nopxe + call writestr + jmp kaboom + +have_pxenv: + mov [StrucPtr],bx + mov [StrucPtr+2],es + + mov si,found_pxenv + call writestr + + mov si,apiver_str + call writestr + mov ax,[es:bx+6] + mov [APIVer],ax + call writehex4 + call crlf + + cmp ax,0201h ; API version 2.1 or higher + jb old_api + mov si,bx + mov ax,es + les bx,[es:bx+28h] ; !PXE structure pointer + cmp dword [es:bx],'!PXE' + je have_pxe + + ; Nope, !PXE structure missing despite API 2.1+, or at least + ; the pointer is missing. Do a last-ditch attempt to find it. + call memory_scan_for_pxe_struct + jnc have_pxe + + ; Otherwise, no dice, use PXENV+ structure + mov bx,si + mov es,ax + +old_api: ; Need to use a PXENV+ structure + mov si,using_pxenv_msg + call writestr + + mov eax,[es:bx+0Ah] ; PXE RM API + mov [PXEEntry],eax + + mov si,undi_data_msg + call writestr + mov ax,[es:bx+20h] + call writehex4 + call crlf + mov si,undi_data_len_msg + call writestr + mov ax,[es:bx+22h] + call writehex4 + call crlf + mov si,undi_code_msg + call writestr + mov ax,[es:bx+24h] + call writehex4 + call crlf + mov si,undi_code_len_msg + call writestr + mov ax,[es:bx+26h] + call writehex4 + call crlf + + ; Compute base memory size from PXENV+ structure + xor esi,esi + movzx eax,word [es:bx+20h] ; UNDI data seg + cmp ax,[es:bx+24h] ; UNDI code seg + ja .use_data + mov ax,[es:bx+24h] + mov si,[es:bx+26h] + jmp short .combine +.use_data: + mov si,[es:bx+22h] +.combine: + shl eax,4 + add eax,esi + shr eax,10 ; Convert to kilobytes + mov [RealBaseMem],ax + + mov si,pxenventry_msg + call writestr + mov ax,[PXEEntry+2] + call writehex4 + mov al,':' + call writechr + mov ax,[PXEEntry] + call writehex4 + call crlf + jmp have_entrypoint + +have_pxe: + mov [StrucPtr],bx + mov [StrucPtr+2],es + + mov eax,[es:bx+10h] + mov [PXEEntry],eax + + mov si,undi_data_msg + call writestr + mov eax,[es:bx+2Ah] + call writehex8 + call crlf + mov si,undi_data_len_msg + call writestr + mov ax,[es:bx+2Eh] + call writehex4 + call crlf + mov si,undi_code_msg + call writestr + mov ax,[es:bx+32h] + call writehex8 + call crlf + mov si,undi_code_len_msg + call writestr + mov ax,[es:bx+36h] + call writehex4 + call crlf + + ; Compute base memory size from !PXE structure + xor esi,esi + mov eax,[es:bx+2Ah] + cmp eax,[es:bx+32h] + ja .use_data + mov eax,[es:bx+32h] + mov si,[es:bx+36h] + jmp short .combine +.use_data: + mov si,[es:bx+2Eh] +.combine: + add eax,esi + shr eax,10 + mov [RealBaseMem],ax + + mov si,pxeentry_msg + call writestr + mov ax,[PXEEntry+2] + call writehex4 + mov al,':' + call writechr + mov ax,[PXEEntry] + call writehex4 + call crlf + +have_entrypoint: + push cs + pop es ; Restore CS == DS == ES + +; +; Network-specific initialization +; + xor ax,ax + mov [LocalDomain],al ; No LocalDomain received + +; +; The DHCP client identifiers are best gotten from the DHCPREQUEST +; packet (query info 1). +; +query_bootp_1: + mov dl,1 + call pxe_get_cached_info + call parse_dhcp + + ; We don't use flags from the request packet, so + ; this is a good time to initialize DHCPMagic... + ; Initialize it to 1 meaning we will accept options found; + ; in earlier versions of PXELINUX bit 0 was used to indicate + ; we have found option 208 with the appropriate magic number; + ; we no longer require that, but MAY want to re-introduce + ; it in the future for vendor encapsulated options. + mov byte [DHCPMagic],1 + +; +; Now attempt to get the BOOTP/DHCP packet that brought us life (and an IP +; address). This lives in the DHCPACK packet (query info 2). +; +query_bootp_2: + mov dl,2 + call pxe_get_cached_info + call parse_dhcp ; Parse DHCP packet +; +; Save away MAC address (assume this is in query info 2. If this +; turns out to be problematic it might be better getting it from +; the query info 1 packet.) +; +.save_mac: + movzx cx,byte [trackbuf+bootp.hardlen] + cmp cx,16 + jna .mac_ok + xor cx,cx ; Bad hardware address length +.mac_ok: + mov [MACLen],cl + mov al,[trackbuf+bootp.hardware] + mov [MACType],al + mov si,trackbuf+bootp.macaddr + mov di,MAC + rep movsb + +; Enable this if we really need to zero-pad this field... +; mov cx,MAC+MAC_MAX+1 +; sub cx,di +; xor ax,ax +; rep stosb + +; +; Now, get the boot file and other info. This lives in the CACHED_REPLY +; packet (query info 3). +; + mov dl,3 + call pxe_get_cached_info + call parse_dhcp ; Parse DHCP packet + +; +; Generate the bootif string, and the hardware-based config string. +; +make_bootif_string: + mov si,bootif_str + mov di,BOOTIFStr + mov cx,bootif_str_len + rep movsb + + movzx cx,byte [MACLen] + mov si,MACType + inc cx +.hexify_mac: + push cx + mov cl,1 ; CH == 0 already + call lchexbytes + mov al,'-' + stosb + pop cx + loop .hexify_mac + mov [di-1],cl ; Null-terminate and strip final dash +; +; Generate ip= option +; + call genipopt + +; +; Print IP address +; + mov eax,[MyIP] + mov di,DotQuadBuf + push di + call gendotquad ; This takes network byte order input + + xchg ah,al ; Convert to host byte order + ror eax,16 ; (BSWAP doesn't work on 386) + xchg ah,al + + mov si,myipaddr_msg + call writestr + call writehex8 + mov al,' ' + call writechr + pop si ; DotQuadBuf + call writestr + call crlf + + mov si,IPOption + call writestr + call crlf + +; +; Check to see if we got any PXELINUX-specific DHCP options; in particular, +; if we didn't get the magic enable, do not recognize any other options. +; +check_dhcp_magic: + test byte [DHCPMagic], 1 ; If we didn't get the magic enable... + jnz .got_magic + mov byte [DHCPMagic], 0 ; If not, kill all other options +.got_magic: + + +; +; Initialize UDP stack +; +udp_init: + mov eax,[MyIP] + mov [pxe_udp_open_pkt.sip],eax + mov di,pxe_udp_open_pkt + mov bx,PXENV_UDP_OPEN + call pxenv + jc .failed + cmp word [pxe_udp_open_pkt.status], byte 0 + je .success +.failed: mov si,err_udpinit + call writestr + jmp kaboom +.success: + +; +; Common initialization code +; +%include "cpuinit.inc" + +; +; Now we're all set to start with our *real* business. First load the +; configuration file (if any) and parse it. +; +; In previous versions I avoided using 32-bit registers because of a +; rumour some BIOSes clobbered the upper half of 32-bit registers at +; random. I figure, though, that if there are any of those still left +; they probably won't be trying to install Linux on them... +; +; The code is still ripe with 16-bitisms, though. Not worth the hassle +; to take'm out. In fact, we may want to put them back if we're going +; to boot ELKS at some point. +; + +; +; Store standard filename prefix +; +prefix: test byte [DHCPMagic], 04h ; Did we get a path prefix option + jnz .got_prefix + mov si,BootFile + mov di,PathPrefix + cld + call strcpy + mov cx,di + sub cx,PathPrefix+1 + std + lea si,[di-2] ; Skip final null! +.find_alnum: lodsb + or al,20h + cmp al,'.' ; Count . or - as alphanum + je .alnum + cmp al,'-' + je .alnum + cmp al,'0' + jb .notalnum + cmp al,'9' + jbe .alnum + cmp al,'a' + jb .notalnum + cmp al,'z' + ja .notalnum +.alnum: loop .find_alnum + dec si +.notalnum: mov byte [si+2],0 ; Zero-terminate after delimiter + cld +.got_prefix: + mov si,tftpprefix_msg + call writestr + mov si,PathPrefix + call writestr + call crlf + +; +; Load configuration file +; +find_config: + +; +; Begin looking for configuration file +; +config_scan: + test byte [DHCPMagic], 02h + jz .no_option + + ; We got a DHCP option, try it first + call .try + jnz .success + +.no_option: + mov di,ConfigName + mov si,cfgprefix + mov cx,cfgprefix_len + rep movsb + + ; Have to guess config file name... + + ; Try loading by UUID. + cmp byte [HaveUUID],0 + je .no_uuid + + push di + mov bx,uuid_dashes + mov si,UUID +.gen_uuid: + movzx cx,byte [bx] + jcxz .done_uuid + inc bx + call lchexbytes + mov al,'-' + stosb + jmp .gen_uuid +.done_uuid: + mov [di-1],cl ; Remove last dash and zero-terminate + pop di + call .try + jnz .success +.no_uuid: + + ; Try loading by MAC address + push di + mov si,MACStr + call strcpy + pop di + call .try + jnz .success + + ; Nope, try hexadecimal IP prefixes... +.scan_ip: + mov cx,4 + mov si,MyIP + call uchexbytes ; Convert to hex string + + mov cx,8 ; Up to 8 attempts +.tryagain: + mov byte [di],0 ; Zero-terminate string + call .try + jnz .success + dec di ; Drop one character + loop .tryagain + + ; Final attempt: "default" string + mov si,default_str ; "default" string + call strcpy + call .try + jnz .success + + mov si,err_noconfig + call writestr + jmp kaboom + +.try: + pusha + mov si,trying_msg + call writestr + mov di,ConfigName + mov si,di + call writestr + call crlf + mov si,di + mov di,KernelName ; Borrow this buffer for mangled name + call mangle_name + call open + popa + ret + + +.success: + +; +; Linux kernel loading code is common. However, we need to define +; a couple of helper macros... +; + +; Handle "ipappend" option +%define HAVE_SPECIAL_APPEND +%macro SPECIAL_APPEND 0 + test byte [IPAppend],01h ; ip= + jz .noipappend1 + mov si,IPOption + mov cx,[IPOptionLen] + rep movsb + mov al,' ' + stosb +.noipappend1: + test byte [IPAppend],02h + jz .noipappend2 + mov si,BOOTIFStr + call strcpy + mov byte [es:di-1],' ' ; Replace null with space +.noipappend2: +%endmacro + +; Unload PXE stack +%define HAVE_UNLOAD_PREP +%macro UNLOAD_PREP 0 + call unload_pxe +%endmacro + +; +; Now we have the config file open. Parse the config file and +; run the user interface. +; +%include "ui.inc" + +; +; Boot to the local disk by returning the appropriate PXE magic. +; AX contains the appropriate return code. +; +%if HAS_LOCALBOOT + +local_boot: + push cs + pop ds + mov [LocalBootType],ax + call vgaclearmode + mov si,localboot_msg + call writestr + ; Restore the environment we were called with + lss sp,[InitStack] + pop gs + pop fs + pop es + pop ds + popad + mov ax,[cs:LocalBootType] + popfd + retf ; Return to PXE + +%endif + +; +; kaboom: write a message and bail out. Wait for quite a while, +; or a user keypress, then do a hard reboot. +; +kaboom: + RESET_STACK_AND_SEGS AX +.patch: mov si,bailmsg + call writestr ; Returns with AL = 0 +.drain: call pollchar + jz .drained + call getchar + jmp short .drain +.drained: + mov edi,[RebootTime] + mov al,[DHCPMagic] + and al,09h ; Magic+Timeout + cmp al,09h + je .time_set + mov edi,REBOOT_TIME +.time_set: + mov cx,18 +.wait1: push cx + mov ecx,edi +.wait2: mov dx,[BIOS_timer] +.wait3: call pollchar + jnz .keypress + cmp dx,[BIOS_timer] + je .wait3 + loop .wait2,ecx + mov al,'.' + call writechr + pop cx + loop .wait1 +.keypress: + call crlf + mov word [BIOS_magic],0 ; Cold reboot + jmp 0F000h:0FFF0h ; Reset vector address + +; +; memory_scan_for_pxe_struct: +; +; If none of the standard methods find the !PXE structure, look for it +; by scanning memory. +; +; On exit, if found: +; CF = 0, ES:BX -> !PXE structure +; Otherwise CF = 1, all registers saved +; +memory_scan_for_pxe_struct: + push ds + pusha + mov ax,cs + mov ds,ax + mov si,trymempxe_msg + call writestr + mov ax,[BIOS_fbm] ; Starting segment + shl ax,(10-4) ; Kilobytes -> paragraphs +; mov ax,01000h ; Start to look here + dec ax ; To skip inc ax +.mismatch: + inc ax + cmp ax,0A000h ; End of memory + jae .not_found + call writehex4 + mov si,fourbs_msg + call writestr + mov es,ax + mov edx,[es:0] + cmp edx,'!PXE' + jne .mismatch + movzx cx,byte [es:4] ; Length of structure + cmp cl,08h ; Minimum length + jb .mismatch + push ax + xor ax,ax + xor si,si +.checksum: es lodsb + add ah,al + loop .checksum + pop ax + jnz .mismatch ; Checksum must == 0 +.found: mov bp,sp + xor bx,bx + mov [bp+8],bx ; Save BX into stack frame (will be == 0) + mov ax,es + call writehex4 + call crlf + popa + pop ds + clc + ret +.not_found: mov si,notfound_msg + call writestr + popa + pop ds + stc + ret + +; +; memory_scan_for_pxenv_struct: +; +; If none of the standard methods find the PXENV+ structure, look for it +; by scanning memory. +; +; On exit, if found: +; CF = 0, ES:BX -> PXENV+ structure +; Otherwise CF = 1, all registers saved +; +memory_scan_for_pxenv_struct: + pusha + mov si,trymempxenv_msg + call writestr +; mov ax,[BIOS_fbm] ; Starting segment +; shl ax,(10-4) ; Kilobytes -> paragraphs + mov ax,01000h ; Start to look here + dec ax ; To skip inc ax +.mismatch: + inc ax + cmp ax,0A000h ; End of memory + jae .not_found + mov es,ax + mov edx,[es:0] + cmp edx,'PXEN' + jne .mismatch + mov dx,[es:4] + cmp dx,'V+' + jne .mismatch + movzx cx,byte [es:8] ; Length of structure + cmp cl,26h ; Minimum length + jb .mismatch + xor ax,ax + xor si,si +.checksum: es lodsb + add ah,al + loop .checksum + and ah,ah + jnz .mismatch ; Checksum must == 0 +.found: mov bp,sp + mov [bp+8],bx ; Save BX into stack frame + mov ax,bx + call writehex4 + call crlf + clc + ret +.not_found: mov si,notfound_msg + call writestr + popad + stc + ret + +; +; close_file: +; Deallocates a file structure (pointer in SI) +; Assumes CS == DS. +; +; XXX: We should check to see if this file is still open on the server +; side and send a courtesy ERROR packet to the server. +; +close_file: + and si,si + jz .closed + mov word [si],0 ; Not in use +.closed: ret + +; +; searchdir: +; +; Open a TFTP connection to the server +; +; On entry: +; DS:DI = mangled filename +; If successful: +; ZF clear +; SI = socket pointer +; EAX = file length in bytes, or -1 if unknown +; If unsuccessful +; ZF set +; + +searchdir: + push es + push bx + push cx + mov ax,ds + mov es,ax + mov si,di + push bp + mov bp,sp + + call allocate_socket + jz .ret + + mov ax,PKT_RETRY ; Retry counter + mov word [PktTimeout],PKT_TIMEOUT ; Initial timeout + +.sendreq: push ax ; [bp-2] - Retry counter + push si ; [bp-4] - File name + + mov di,packet_buf + mov [pxe_udp_write_pkt.buffer],di + + mov ax,TFTP_RRQ ; TFTP opcode + stosw + + lodsd ; EAX <- server override (if any) + and eax,eax + jnz .noprefix ; No prefix, and we have the server + + push si ; Add common prefix + mov si,PathPrefix + call strcpy + dec di + pop si + + mov eax,[ServerIP] ; Get default server + +.noprefix: + call strcpy ; Filename +%if GPXE + mov si,packet_buf+2 + call is_gpxe + jnc .gpxe +%endif + + mov [bx+tftp_remoteip],eax + + push bx ; [bp-6] - TFTP block + mov bx,[bx] + push bx ; [bp-8] - TID (local port no) + + mov [pxe_udp_write_pkt.status],byte 0 + mov [pxe_udp_write_pkt.sip],eax + ; Now figure out the gateway + xor eax,[MyIP] + and eax,[Netmask] + jz .nogwneeded + mov eax,[Gateway] +.nogwneeded: + mov [pxe_udp_write_pkt.gip],eax + mov [pxe_udp_write_pkt.lport],bx + mov ax,[ServerPort] + mov [pxe_udp_write_pkt.rport],ax + mov si,tftp_tail + mov cx,tftp_tail_len + rep movsb + sub di,packet_buf ; Get packet size + mov [pxe_udp_write_pkt.buffersize],di + + mov di,pxe_udp_write_pkt + mov bx,PXENV_UDP_WRITE + call pxenv + jc .failure + cmp word [pxe_udp_write_pkt.status],byte 0 + jne .failure + + ; + ; Danger, Will Robinson! We need to support timeout + ; and retry lest we just lost a packet... + ; + + ; Packet transmitted OK, now we need to receive +.getpacket: push word [PktTimeout] ; [bp-10] + push word [BIOS_timer] ; [bp-12] + +.pkt_loop: mov bx,[bp-8] ; TID + mov di,packet_buf + mov word [pxe_udp_read_pkt.status],0 + mov [pxe_udp_read_pkt.buffer],di + mov [pxe_udp_read_pkt.buffer+2],ds + mov word [pxe_udp_read_pkt.buffersize],packet_buf_size + mov eax,[MyIP] + mov [pxe_udp_read_pkt.dip],eax + mov [pxe_udp_read_pkt.lport],bx + mov di,pxe_udp_read_pkt + mov bx,PXENV_UDP_READ + call pxenv + jnc .got_packet ; Wait for packet +.no_packet: + mov dx,[BIOS_timer] + cmp dx,[bp-12] + je .pkt_loop + mov [bp-12],dx + dec word [bp-10] ; Timeout + jnz .pkt_loop + pop ax ; Adjust stack + pop ax + shl word [PktTimeout],1 ; Exponential backoff + jmp .failure + +.got_packet: + mov si,[bp-6] ; TFTP pointer + mov bx,[bp-8] ; TID + + ; Make sure the packet actually came from the server + ; This is technically not to the TFTP spec? + mov eax,[si+tftp_remoteip] + cmp [pxe_udp_read_pkt.sip],eax + jne .no_packet + + ; Got packet - reset timeout + mov word [PktTimeout],PKT_TIMEOUT + + pop ax ; Adjust stack + pop ax + + mov ax,[pxe_udp_read_pkt.rport] + mov [si+tftp_remoteport],ax + + ; filesize <- -1 == unknown + mov dword [si+tftp_filesize], -1 + ; Default blksize unless blksize option negotiated + mov word [si+tftp_blksize], TFTP_BLOCKSIZE + + movzx ecx,word [pxe_udp_read_pkt.buffersize] + sub cx,2 ; CX <- bytes after opcode + jb .failure ; Garbled reply + + mov si,packet_buf + lodsw + + cmp ax, TFTP_ERROR + je .bailnow ; ERROR reply: don't try again + + ; If the server doesn't support any options, we'll get + ; a DATA reply instead of OACK. Stash the data in + ; the file buffer and go with the default value for + ; all options... + cmp ax, TFTP_DATA + je .no_oack + + cmp ax, TFTP_OACK + jne .err_reply ; Unknown packet type + + ; Now we need to parse the OACK packet to get the transfer + ; and packet sizes. + ; SI -> first byte of options; [E]CX -> byte count +.parse_oack: + jcxz .done_pkt ; No options acked +.get_opt_name: + mov di,si + mov bx,si +.opt_name_loop: lodsb + and al,al + jz .got_opt_name + or al,20h ; Convert to lowercase + stosb + loop .opt_name_loop + ; We ran out, and no final null + jmp .err_reply +.got_opt_name: ; si -> option value + dec cx ; bytes left in pkt + jz .err_reply ; Option w/o value + + ; Parse option pointed to by bx; guaranteed to be + ; null-terminated. + push cx + push si + mov si,bx ; -> option name + mov bx,tftp_opt_table + mov cx,tftp_opts +.opt_loop: + push cx + push si + mov di,[bx] ; Option pointer + mov cx,[bx+2] ; Option len + repe cmpsb + pop si + pop cx + je .get_value ; OK, known option + add bx,6 + loop .opt_loop + + pop si + pop cx + jmp .err_reply ; Non-negotiated option returned + +.get_value: pop si ; si -> option value + pop cx ; cx -> bytes left in pkt + mov bx,[bx+4] ; Pointer to data target + add bx,[bp-6] ; TFTP socket pointer + xor eax,eax + xor edx,edx +.value_loop: lodsb + and al,al + jz .got_value + sub al,'0' + cmp al, 9 + ja .err_reply ; Not a decimal digit + imul edx,10 + add edx,eax + mov [bx],edx + loop .value_loop + ; Ran out before final null, accept anyway + jmp short .done_pkt + +.got_value: + dec cx + jnz .get_opt_name ; Not end of packet + + ; ZF == 1 + + ; Success, done! +.done_pkt: + pop si ; Junk + pop si ; We want the packet ptr in SI + + mov eax,[si+tftp_filesize] +.got_file: ; SI->socket structure, EAX = size + and eax,eax ; Set ZF depending on file size + jz .error_si ; ZF = 1 need to free the socket +.ret: + leave ; SP <- BP, POP BP + pop cx + pop bx + pop es + ret + + +.no_oack: ; We got a DATA packet, meaning no options are + ; suported. Save the data away and consider the length + ; undefined, *unless* this is the only data packet... + mov bx,[bp-6] ; File pointer + sub cx,2 ; Too short? + jb .failure + lodsw ; Block number + cmp ax,htons(1) + jne .failure + mov [bx+tftp_lastpkt],ax + cmp cx,TFTP_BLOCKSIZE + ja .err_reply ; Corrupt... + je .not_eof + ; This was the final EOF packet, already... + ; We know the filesize, but we also want to ack the + ; packet and set the EOF flag. + mov [bx+tftp_filesize],ecx + mov byte [bx+tftp_goteof],1 + push si + mov si,bx + ; AX = htons(1) already + call ack_packet + pop si +.not_eof: + mov [bx+tftp_bytesleft],cx + mov ax,pktbuf_seg + push es + mov es,ax + mov di,tftp_pktbuf + mov [bx+tftp_dataptr],di + add cx,3 + shr cx,2 + rep movsd + pop es + jmp .done_pkt + +.err_reply: ; Option negotiation error. Send ERROR reply. + ; ServerIP and gateway are already programmed in + mov si,[bp-6] + mov ax,[si+tftp_remoteport] + mov word [pxe_udp_write_pkt.rport],ax + mov word [pxe_udp_write_pkt.buffer],tftp_opt_err + mov word [pxe_udp_write_pkt.buffersize],tftp_opt_err_len + mov di,pxe_udp_write_pkt + mov bx,PXENV_UDP_WRITE + call pxenv + + ; Write an error message and explode + mov si,err_damage + call writestr + jmp kaboom + +.bailnow: mov word [bp-2],1 ; Immediate error - no retry + +.failure: pop bx ; Junk + pop bx + pop si + pop ax + dec ax ; Retry counter + jnz .sendreq ; Try again + +.error: mov si,bx ; Socket pointer +.error_si: ; Socket pointer already in SI + call free_socket ; ZF <- 1, SI <- 0 + jmp .ret + + +%if GPXE +.gpxe: + push bx ; Socket pointer + mov di,gpxe_file_open + mov word [di],2 ; PXENV_STATUS_BAD_FUNC + mov word [di+4],packet_buf+2 ; Completed URL + mov [di+6],ds + mov bx,PXENV_FILE_OPEN + call pxenv + pop si ; Socket pointer in SI + jc .error_si + + mov ax,[di+2] + mov word [si+tftp_localport],-1 ; gPXE URL + mov [si+tftp_remoteport],ax + mov di,gpxe_get_file_size + mov [di+2],ax + +%if 0 + ; Disable this for now since gPXE doesn't always + ; return valid information in PXENV_GET_FILE_SIZE + mov bx,PXENV_GET_FILE_SIZE + call pxenv + mov eax,[di+4] ; File size + jnc .oksize +%endif + or eax,-1 ; Size unknown +.oksize: + mov [si+tftp_filesize],eax + jmp .got_file +%endif ; GPXE + +; +; allocate_socket: Allocate a local UDP port structure +; +; If successful: +; ZF set +; BX = socket pointer +; If unsuccessful: +; ZF clear +; +allocate_socket: + push cx + mov bx,Files + mov cx,MAX_OPEN +.check: cmp word [bx], byte 0 + je .found + add bx,open_file_t_size + loop .check + xor cx,cx ; ZF = 1 + pop cx + ret + ; Allocate a socket number. Socket numbers are made + ; guaranteed unique by including the socket slot number + ; (inverted, because we use the loop counter cx); add a + ; counter value to keep the numbers from being likely to + ; get immediately reused. + ; + ; The NextSocket variable also contains the top two bits + ; set. This generates a value in the range 49152 to + ; 57343. +.found: + dec cx + push ax + mov ax,[NextSocket] + inc ax + and ax,((1 << (13-MAX_OPEN_LG2))-1) | 0xC000 + mov [NextSocket],ax + shl cx,13-MAX_OPEN_LG2 + add cx,ax ; ZF = 0 + xchg ch,cl ; Convert to network byte order + mov [bx],cx ; Socket in use + pop ax + pop cx + ret + +; +; Free socket: socket in SI; return SI = 0, ZF = 1 for convenience +; +free_socket: + push es + pusha + xor ax,ax + mov es,ax + mov di,si + mov cx,tftp_pktbuf >> 1 ; tftp_pktbuf is not cleared + rep stosw + popa + pop es + xor si,si + ret + +; +; parse_dotquad: +; Read a dot-quad pathname in DS:SI and output an IP +; address in EAX, with SI pointing to the first +; nonmatching character. +; +; Return CF=1 on error. +; +; No segment assumptions permitted. +; +parse_dotquad: + push cx + mov cx,4 + xor eax,eax +.parseloop: + mov ch,ah + mov ah,al + lodsb + sub al,'0' + jb .notnumeric + cmp al,9 + ja .notnumeric + aad ; AL += 10 * AH; AH = 0; + xchg ah,ch + jmp .parseloop +.notnumeric: + cmp al,'.'-'0' + pushf + mov al,ah + mov ah,ch + xor ch,ch + ror eax,8 + popf + jne .error + loop .parseloop + jmp .done +.error: + loop .realerror ; If CX := 1 then we're done + clc + jmp .done +.realerror: + stc +.done: + dec si ; CF unchanged! + pop cx + ret + +; +; is_url: Return CF=0 if and only if the buffer pointed to by +; DS:SI is a URL (contains ://). No registers modified. +; +%if GPXE +is_url: + push si + push eax +.loop: + mov eax,[si] + inc si + and al,al + jz .not_url + and eax,0FFFFFFh + cmp eax,'://' + jne .loop +.done: + ; CF=0 here + pop eax + pop si + ret +.not_url: + stc + jmp .done + +; +; is_gpxe: Return CF=0 if and only if the buffer pointed to by +; DS:SI is a URL (contains ://) *and* the gPXE extensions +; API is available. No registers modified. +; +is_gpxe: + call is_url + jc .ret ; Not a URL, don't bother +.again: + cmp byte [HasGPXE],1 + ja .unknown + ; CF=1 if not available (0), + ; CF=0 if known available (1). +.ret: ret + +.unknown: + ; If we get here, the gPXE status is unknown. + push es + pushad + push ds + pop es + mov di,gpxe_file_api_check + mov bx,PXENV_FILE_API_CHECK ; BH = 0 + call pxenv + jc .nogood + cmp dword [di+4],0xe9c17b20 + jne .nogood + mov ax,[di+12] ; Don't care about the upper half... + not ax ; Set bits of *missing* functions... + and ax,01001011b ; The functions we care about + setz bh + jz .done +.nogood: + mov si,gpxe_warning_msg + call writestr +.done: + mov [HasGPXE],bh + popad + pop es + jmp .again + + section .data +gpxe_warning_msg + db 'URL syntax, but gPXE extensions not detected, ' + db 'trying plain TFTP...', CR, LF, 0 +HasGPXE db -1 ; Unknown + section .text + +%endif + +; +; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed +; to by ES:DI; ends on encountering any whitespace. +; DI is preserved. +; +; This verifies that a filename is < FILENAME_MAX characters +; and doesn't contain whitespace, and zero-pads the output buffer, +; so "repe cmpsb" can do a compare. +; +; The first four bytes of the manged name is the IP address of +; the download host, 0 for no host, or -1 for a gPXE URL. +; +; No segment assumptions permitted. +; +mangle_name: + push di +%if GPXE + call is_url + jc .not_url + or eax,-1 ; It's a URL + jmp .prefix_done +.not_url: +%endif ; GPXE + push si + mov eax,[cs:ServerIP] + cmp byte [si],0 + je .noip ; Null filename?!?! + cmp word [si],'::' ; Leading ::? + je .gotprefix + +.more: + inc si + cmp byte [si],0 + je .noip + cmp word [si],'::' + jne .more + + ; We have a :: prefix of some sort, it could be either + ; a DNS name or a dot-quad IP address. Try the dot-quad + ; first... +.here: + pop si + push si + call parse_dotquad + jc .notdq + cmp word [si],'::' + je .gotprefix +.notdq: + pop si + push si + call dns_resolv + cmp word [si],'::' + jne .noip + and eax,eax + jnz .gotprefix + +.noip: + pop si + xor eax,eax + jmp .prefix_done + +.gotprefix: + pop cx ; Adjust stack + inc si ; Skip double colon + inc si + +.prefix_done: + stosd ; Save IP address prefix + mov cx,FILENAME_MAX-5 + +.mn_loop: + lodsb + cmp al,' ' ; If control or space, end + jna .mn_end + stosb + loop .mn_loop +.mn_end: + inc cx ; At least one null byte + xor ax,ax ; Zero-fill name + rep stosb ; Doesn't do anything if CX=0 + pop di + ret ; Done + +; +; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled +; filename to the conventional representation. This is needed +; for the BOOT_IMAGE= parameter for the kernel. +; +; NOTE: The output buffer needs to be able to hold an +; expanded IP address. +; +; DS:SI -> input mangled file name +; ES:DI -> output buffer +; +; On return, DI points to the first byte after the output name, +; which is set to a null byte. +; +unmangle_name: + push eax + lodsd + and eax,eax + jz .noip + cmp eax,-1 + jz .noip ; URL + call gendotquad + mov ax,'::' + stosw +.noip: + call strcpy + dec di ; Point to final null byte + pop eax + ret + +; +; pxenv +; +; This is the main PXENV+/!PXE entry point, using the PXENV+ +; calling convention. This is a separate local routine so +; we can hook special things from it if necessary. In particular, +; some PXE stacks seem to not like being invoked from anything but +; the initial stack, so humour it. +; +; While we're at it, save and restore all registers. +; +pxenv: + pushad +%if USE_PXE_PROVIDED_STACK == 0 + mov [cs:PXEStack],sp + mov [cs:PXEStack+2],ss + lss sp,[cs:InitStack] +%endif + ; This works either for the PXENV+ or the !PXE calling + ; convention, as long as we ignore CF (which is redundant + ; with AX anyway.) + push es + push di + push bx +.jump: call 0:0 + add sp,6 + mov [cs:PXEStatus],ax + add ax,-1 ; Set CF unless AX was 0 + +%if USE_PXE_PROVIDED_STACK == 0 + lss sp,[cs:PXEStack] +%endif + + ; This clobbers the AX return, but we don't use it + ; except for testing it against zero (and setting CF), + ; which we did above. For anything else, + ; use the Status field in the reply. + ; For the COMBOOT function, the value is saved in + ; the PXEStatus variable. + popad + cld ; Make sure DF <- 0 + ret + +; Must be after function def due to NASM bug +PXEEntry equ pxenv.jump+1 + + section .bss + alignb 2 +PXEStatus resb 2 + + section .text + +; +; getfssec: Get multiple clusters from a file, given the starting cluster. +; +; In this case, get multiple blocks from a specific TCP connection. +; +; On entry: +; ES:BX -> Buffer +; SI -> TFTP socket pointer +; CX -> 512-byte block count; 0FFFFh = until end of file +; On exit: +; SI -> TFTP socket pointer (or 0 on EOF) +; CF = 1 -> Hit EOF +; ECX -> number of bytes actually read +; +getfssec: + push eax + push edi + push bx + push si + push fs + mov di,bx + mov ax,pktbuf_seg + mov fs,ax + + xor eax,eax + movzx ecx,cx + shl ecx,TFTP_BLOCKSIZE_LG2 ; Convert to bytes + push ecx ; Initial request size + jz .hit_eof ; Nothing to do? + +.need_more: + call fill_buffer + movzx eax,word [si+tftp_bytesleft] + and ax,ax + jz .hit_eof + + push ecx + cmp ecx,eax + jna .ok_size + mov ecx,eax +.ok_size: + mov ax,cx ; EAX<31:16> == ECX<31:16> == 0 + mov bx,[si+tftp_dataptr] + sub [si+tftp_bytesleft],cx + xchg si,bx + fs rep movsb ; Copy from packet buffer + xchg si,bx + mov [si+tftp_dataptr],bx + + pop ecx + sub ecx,eax + jnz .need_more + +.hit_eof: + call fill_buffer + + pop eax ; Initial request amount + xchg eax,ecx + sub ecx,eax ; ... minus anything not gotten + + pop fs + pop si + + ; Is there anything left of this? + mov eax,[si+tftp_filesize] + sub eax,[si+tftp_filepos] + jnz .bytes_left + + cmp [si+tftp_bytesleft],ax ; AX == 0 + jne .bytes_left + + cmp byte [si+tftp_goteof],0 + je .done + ; I'm 99% sure this can't happen, but... + call fill_buffer ; Receive/ACK the EOF packet +.done: + ; The socket is closed and the buffer drained + ; Close socket structure and re-init for next user + call free_socket + stc + jmp .ret +.bytes_left: + clc +.ret: + pop bx + pop edi + pop eax + ret + +; +; Get a fresh packet if the buffer is drained, and we haven't hit +; EOF yet. The buffer should be filled immediately after draining! +; +; expects fs -> pktbuf_seg and ds:si -> socket structure +; +fill_buffer: + cmp word [si+tftp_bytesleft],0 + je .empty + ret ; Otherwise, nothing to do + +.empty: + push es + pushad + mov ax,ds + mov es,ax + + ; Note: getting the EOF packet is not the same thing + ; as tftp_filepos == tftp_filesize; if the EOF packet + ; is empty the latter condition can be true without + ; having gotten the official EOF. + cmp byte [si+tftp_goteof],0 + jne .ret ; Already EOF + +%if GPXE + cmp word [si+tftp_localport], -1 + jne .get_packet_tftp + call get_packet_gpxe + jmp .ret +.get_packet_tftp: +%endif ; GPXE + + ; TFTP code... +.packet_loop: + ; Start by ACKing the previous packet; this should cause the + ; next packet to be sent. + mov cx,PKT_RETRY + mov word [PktTimeout],PKT_TIMEOUT + +.send_ack: push cx ; <D> Retry count + + mov ax,[si+tftp_lastpkt] + call ack_packet ; Send ACK + + ; We used to test the error code here, but sometimes + ; PXE would return negative status even though we really + ; did send the ACK. Now, just treat a failed send as + ; a normally lost packet, and let it time out in due + ; course of events. + +.send_ok: ; Now wait for packet. + mov dx,[BIOS_timer] ; Get current time + + mov cx,[PktTimeout] +.wait_data: push cx ; <E> Timeout + push dx ; <F> Old time + + mov bx,[si+tftp_pktbuf] + mov [pxe_udp_read_pkt.buffer],bx + mov [pxe_udp_read_pkt.buffer+2],fs + mov [pxe_udp_read_pkt.buffersize],word PKTBUF_SIZE + mov eax,[si+tftp_remoteip] + mov [pxe_udp_read_pkt.sip],eax + mov eax,[MyIP] + mov [pxe_udp_read_pkt.dip],eax + mov ax,[si+tftp_remoteport] + mov [pxe_udp_read_pkt.rport],ax + mov ax,[si+tftp_localport] + mov [pxe_udp_read_pkt.lport],ax + mov di,pxe_udp_read_pkt + mov bx,PXENV_UDP_READ + call pxenv + jnc .recv_ok + + ; No packet, or receive failure + mov dx,[BIOS_timer] + pop ax ; <F> Old time + pop cx ; <E> Timeout + cmp ax,dx ; Same time -> don't advance timeout + je .wait_data ; Same clock tick + loop .wait_data ; Decrease timeout + + pop cx ; <D> Didn't get any, send another ACK + shl word [PktTimeout],1 ; Exponential backoff + loop .send_ack + jmp kaboom ; Forget it... + +.recv_ok: pop dx ; <F> + pop cx ; <E> + + cmp word [pxe_udp_read_pkt.buffersize],byte 4 + jb .wait_data ; Bad size for a DATA packet + + mov bx,[si+tftp_pktbuf] + cmp word [fs:bx],TFTP_DATA ; Not a data packet? + jne .wait_data ; Then wait for something else + + mov ax,[si+tftp_lastpkt] + xchg ah,al ; Host byte order + inc ax ; Which packet are we waiting for? + xchg ah,al ; Network byte order + cmp [fs:bx+2],ax + je .right_packet + + ; Wrong packet, ACK the packet and then try again + ; This is presumably because the ACK got lost, + ; so the server just resent the previous packet + mov ax,[fs:bx+2] + call ack_packet + jmp .send_ok ; Reset timeout + +.right_packet: ; It's the packet we want. We're also EOF if the + ; size < blocksize + + pop cx ; <D> Don't need the retry count anymore + + mov [si+tftp_lastpkt],ax ; Update last packet number + + movzx ecx,word [pxe_udp_read_pkt.buffersize] + sub cx,byte 4 ; Skip TFTP header + + ; Set pointer to data block + lea ax,[bx+4] ; Data past TFTP header + mov [si+tftp_dataptr],ax + + add [si+tftp_filepos],ecx + mov [si+tftp_bytesleft],cx + + cmp cx,[si+tftp_blksize] ; Is it a full block? + jb .last_block ; If not, it's EOF + +.ret: + popad + pop es + ret + + +.last_block: ; Last block - ACK packet immediately + mov ax,[fs:bx+2] + call ack_packet + + ; Make sure we know we are at end of file + mov eax,[si+tftp_filepos] + mov [si+tftp_filesize],eax + mov byte [si+tftp_goteof],1 + + jmp .ret + +; +; ack_packet: +; +; Send ACK packet. This is a common operation and so is worth canning. +; +; Entry: +; SI = TFTP block +; AX = Packet # to ack (network byte order) +; Exit: +; All registers preserved +; +; This function uses the pxe_udp_write_pkt but not the packet_buf. +; +ack_packet: + pushad + mov [ack_packet_buf+2],ax ; Packet number to ack + mov ax,[si] + mov [pxe_udp_write_pkt.lport],ax + mov ax,[si+tftp_remoteport] + mov [pxe_udp_write_pkt.rport],ax + mov eax,[si+tftp_remoteip] + mov [pxe_udp_write_pkt.sip],eax + xor eax,[MyIP] + and eax,[Netmask] + jz .nogw + mov eax,[Gateway] +.nogw: + mov [pxe_udp_write_pkt.gip],eax + mov [pxe_udp_write_pkt.buffer],word ack_packet_buf + mov [pxe_udp_write_pkt.buffersize], word 4 + mov di,pxe_udp_write_pkt + mov bx,PXENV_UDP_WRITE + call pxenv + popad + ret + +%if GPXE +; +; Get a fresh packet from a gPXE socket; expects fs -> pktbuf_seg +; and ds:si -> socket structure +; +; Assumes CS == DS == ES. +; +get_packet_gpxe: + mov di,gpxe_file_read + + mov ax,[si+tftp_remoteport] ; gPXE filehandle + mov [di+2],ax + mov ax,[si+tftp_pktbuf] + mov [di+6],ax + mov [si+tftp_dataptr],ax + mov [di+8],fs + +.again: + mov word [di+4],PKTBUF_SIZE + mov bx,PXENV_FILE_READ + call pxenv + jnc .ok ; Got data or EOF + cmp word [di],PXENV_STATUS_TFTP_OPEN ; == EWOULDBLOCK + je .again + jmp kaboom ; Otherwise error... + +.ok: + movzx eax,word [di+4] ; Bytes read + mov [si+tftp_bytesleft],ax ; Bytes in buffer + add [si+tftp_filepos],eax ; Position in file + + and ax,ax ; EOF? + mov eax,[si+tftp_filepos] + + jnz .got_stuff + + ; We got EOF here, make sure the upper layers know + mov [si+tftp_filesize],eax + +.got_stuff: + ; If we're done here, close the file + cmp [si+tftp_filesize],eax + ja .done ; Not EOF, there is still data... + + ; Reuse the previous [es:di] structure since the + ; relevant fields are all the same + mov byte [si+tftp_goteof],1 + + mov bx,PXENV_FILE_CLOSE + call pxenv + ; Ignore return... +.done: + ret +%endif ; GPXE + +; +; unload_pxe: +; +; This function unloads the PXE and UNDI stacks and unclaims +; the memory. +; +unload_pxe: + test byte [KeepPXE],01h ; Should we keep PXE around? + jnz reset_pxe + + push ds + push es + + mov ax,cs + mov ds,ax + mov es,ax + + mov si,new_api_unload + cmp byte [APIVer+1],2 ; Major API version >= 2? + jae .new_api + mov si,old_api_unload +.new_api: + +.call_loop: xor ax,ax + lodsb + and ax,ax + jz .call_done + xchg bx,ax + mov di,pxe_unload_stack_pkt + push di + xor ax,ax + mov cx,pxe_unload_stack_pkt_len >> 1 + rep stosw + pop di + call pxenv + jc .cant_free + mov ax,word [pxe_unload_stack_pkt.status] + cmp ax,PXENV_STATUS_SUCCESS + jne .cant_free + jmp .call_loop + +.call_done: + mov bx,0FF00h + + mov dx,[RealBaseMem] + cmp dx,[BIOS_fbm] ; Sanity check + jna .cant_free + inc bx + + ; Check that PXE actually unhooked the INT 1Ah chain + movzx eax,word [4*0x1a] + movzx ecx,word [4*0x1a+2] + shl ecx,4 + add eax,ecx + shr eax,10 + cmp ax,dx ; Not in range + jae .ok + cmp ax,[BIOS_fbm] + jae .cant_free + ; inc bx + +.ok: + mov [BIOS_fbm],dx +.pop_ret: + pop es + pop ds + ret + +.cant_free: + mov si,cant_free_msg + call writestr + push ax + xchg bx,ax + call writehex4 + mov al,'-' + call writechr + pop ax + call writehex4 + mov al,'-' + call writechr + mov eax,[4*0x1a] + call writehex8 + call crlf + jmp .pop_ret + + ; We want to keep PXE around, but still we should reset + ; it to the standard bootup configuration +reset_pxe: + push es + push cs + pop es + mov bx,PXENV_UDP_CLOSE + mov di,pxe_udp_close_pkt + call pxenv + pop es + ret + +; +; gendotquad +; +; Take an IP address (in network byte order) in EAX and +; output a dotted quad string to ES:DI. +; DI points to terminal null at end of string on exit. +; +gendotquad: + push eax + push cx + mov cx,4 +.genchar: + push eax + cmp al,10 ; < 10? + jb .lt10 ; If so, skip first 2 digits + + cmp al,100 ; < 100 + jb .lt100 ; If so, skip first digit + + aam 100 + ; Now AH = 100-digit; AL = remainder + add ah,'0' + mov [es:di],ah + inc di + +.lt100: + aam 10 + ; Now AH = 10-digit; AL = remainder + add ah,'0' + mov [es:di],ah + inc di + +.lt10: + add al,'0' + stosb + mov al,'.' + stosb + pop eax + ror eax,8 ; Move next char into LSB + loop .genchar + dec di + mov [es:di], byte 0 + pop cx + pop eax + ret +; +; uchexbytes/lchexbytes +; +; Take a number of bytes in memory and convert to upper/lower-case +; hexadecimal +; +; Input: +; DS:SI = input bytes +; ES:DI = output buffer +; CX = number of bytes +; Output: +; DS:SI = first byte after +; ES:DI = first byte after +; CX = 0 +; +; Trashes AX, DX +; + +lchexbytes: + mov dl,'a'-'9'-1 + jmp xchexbytes +uchexbytes: + mov dl,'A'-'9'-1 +xchexbytes: +.loop: + lodsb + mov ah,al + shr al,4 + call .outchar + mov al,ah + call .outchar + loop .loop + ret +.outchar: + and al,0Fh + add al,'0' + cmp al,'9' + jna .done + add al,dl +.done: + stosb + ret + +; +; pxe_get_cached_info +; +; Get a DHCP packet from the PXE stack into the trackbuf. +; +; Input: +; DL = packet type +; Output: +; CX = buffer size +; +; Assumes CS == DS == ES. +; +pxe_get_cached_info: + pushad + mov si,get_packet_msg + call writestr + mov al,dl + call writehex2 + call crlf + mov di,pxe_bootp_query_pkt + push di + xor ax,ax + stosw ; Status + movzx ax,dl + stosw ; Packet type + mov ax,trackbufsize + stosw ; Buffer size + mov ax,trackbuf + stosw ; Buffer offset + xor ax,ax + stosw ; Buffer segment + + pop di ; DI -> parameter set + mov bx,PXENV_GET_CACHED_INFO + call pxenv + jc .err + and ax,ax + jnz .err + + popad + mov cx,[pxe_bootp_query_pkt.buffersize] + ret + +.err: + mov si,err_pxefailed + call writestr + call writehex4 + call crlf + jmp kaboom + + section .data +get_packet_msg db 'Getting cached packet ', 0 + + section .text +; +; ip_ok +; +; Tests an IP address in EAX for validity; return with ZF=1 for bad. +; We used to refuse class E, but class E addresses are likely to become +; assignable unicast addresses in the near future. +; +ip_ok: + push ax + cmp eax,-1 ; Refuse the all-ones address + jz .out + and al,al ; Refuse network zero + jz .out + cmp al,127 ; Refuse loopback + jz .out + and al,0F0h + cmp al,224 ; Refuse class D +.out: + pop ax + ret + +; +; parse_dhcp +; +; Parse a DHCP packet. This includes dealing with "overloaded" +; option fields (see RFC 2132, section 9.3) +; +; This should fill in the following global variables, if the +; information is present: +; +; MyIP - client IP address +; ServerIP - boot server IP address +; Netmask - network mask +; Gateway - default gateway router IP +; BootFile - boot file name +; DNSServers - DNS server IPs +; LocalDomain - Local domain name +; MACLen, MAC - Client identifier, if MACLen == 0 +; +; This assumes the DHCP packet is in "trackbuf" and the length +; of the packet in in CX on entry. +; + +parse_dhcp: + mov byte [OverLoad],0 ; Assume no overload + mov eax, [trackbuf+bootp.yip] + call ip_ok + jz .noyip + mov [MyIP], eax +.noyip: + mov eax, [trackbuf+bootp.sip] + and eax, eax + call ip_ok + jz .nosip + mov [ServerIP], eax +.nosip: + sub cx, bootp.options + jbe .nooptions + mov si, trackbuf+bootp.option_magic + lodsd + cmp eax, BOOTP_OPTION_MAGIC + jne .nooptions + call parse_dhcp_options +.nooptions: + mov si, trackbuf+bootp.bootfile + test byte [OverLoad],1 + jz .nofileoverload + mov cx,128 + call parse_dhcp_options + jmp short .parsed_file +.nofileoverload: + cmp byte [si], 0 + jz .parsed_file ; No bootfile name + mov di,BootFile + mov cx,32 + rep movsd + xor al,al + stosb ; Null-terminate +.parsed_file: + mov si, trackbuf+bootp.sname + test byte [OverLoad],2 + jz .nosnameoverload + mov cx,64 + call parse_dhcp_options +.nosnameoverload: + ret + +; +; Parse a sequence of DHCP options, pointed to by DS:SI; the field +; size is CX -- some DHCP servers leave option fields unterminated +; in violation of the spec. +; +; For parse_some_dhcp_options, DH contains the minimum value for +; the option to recognize -- this is used to restrict parsing to +; PXELINUX-specific options only. +; +parse_dhcp_options: + xor dx,dx + +parse_some_dhcp_options: +.loop: + and cx,cx + jz .done + + lodsb + dec cx + jz .done ; Last byte; must be PAD, END or malformed + cmp al, 0 ; PAD option + je .loop + cmp al,255 ; END option + je .done + + ; Anything else will have a length field + mov dl,al ; DL <- option number + xor ax,ax + lodsb ; AX <- option length + dec cx + sub cx,ax ; Decrement bytes left counter + jb .done ; Malformed option: length > field size + + cmp dl,dh ; Is the option value valid? + jb .opt_done + + mov bx,dhcp_option_list +.find_option: + cmp bx,dhcp_option_list_end + jae .opt_done + cmp dl,[bx] + je .found_option + add bx,3 + jmp .find_option +.found_option: + pushad + call [bx+1] + popad + +; Fall through + ; Unknown option. Skip to the next one. +.opt_done: + add si,ax + jmp .loop +.done: + ret + + section .data +dhcp_option_list: + section .text + +%macro dopt 2 + section .data + db %1 + dw dopt_%2 + section .text +dopt_%2: +%endmacro + +; +; Parse individual DHCP options. SI points to the option data and +; AX to the option length. DL contains the option number. +; All registers are saved around the routine. +; + dopt 1, subnet_mask + mov ebx,[si] + mov [Netmask],ebx + ret + + dopt 3, router + mov ebx,[si] + mov [Gateway],ebx + ret + + dopt 6, dns_servers + mov cx,ax + shr cx,2 + cmp cl,DNS_MAX_SERVERS + jna .oklen + mov cl,DNS_MAX_SERVERS +.oklen: + mov di,DNSServers + rep movsd + mov [LastDNSServer],di + ret + + dopt 16, local_domain + mov bx,si + add bx,ax + xor ax,ax + xchg [bx],al ; Zero-terminate option + mov di,LocalDomain + call dns_mangle ; Convert to DNS label set + mov [bx],al ; Restore ending byte + ret + + dopt 43, vendor_encaps + mov dh,208 ; Only recognize PXELINUX options + mov cx,ax ; Length of option = max bytes to parse + call parse_some_dhcp_options ; Parse recursive structure + ret + + dopt 52, option_overload + mov bl,[si] + mov [OverLoad],bl + ret + + dopt 54, server + mov eax,[si] + cmp dword [ServerIP],0 + jne .skip ; Already have a next server IP + call ip_ok + jz .skip + mov [ServerIP],eax +.skip: ret + + dopt 61, client_identifier + cmp ax,MAC_MAX ; Too long? + ja .skip + cmp ax,2 ; Too short? + jb .skip + cmp [MACLen],ah ; Only do this if MACLen == 0 + jne .skip + push ax + lodsb ; Client identifier type + cmp al,[MACType] + pop ax + jne .skip ; Client identifier is not a MAC + dec ax + mov [MACLen],al + mov di,MAC + jmp dhcp_copyoption +.skip: ret + + dopt 67, bootfile_name + mov di,BootFile + jmp dhcp_copyoption + + dopt 97, uuid_client_identifier + cmp ax,17 ; type byte + 16 bytes UUID + jne .skip + mov dl,[si] ; Must have type 0 == UUID + or dl,[HaveUUID] ; Capture only the first instance + jnz .skip + mov byte [HaveUUID],1 ; Got UUID + mov di,UUIDType + jmp dhcp_copyoption +.skip: ret + + dopt 209, pxelinux_configfile + mov di,ConfigName + or byte [DHCPMagic],2 ; Got config file + jmp dhcp_copyoption + + dopt 210, pxelinux_pathprefix + mov di,PathPrefix + or byte [DHCPMagic],4 ; Got path prefix + jmp dhcp_copyoption + + dopt 211, pxelinux_reboottime + cmp al,4 + jne .done + mov ebx,[si] + xchg bl,bh ; Convert to host byte order + rol ebx,16 + xchg bl,bh + mov [RebootTime],ebx + or byte [DHCPMagic],8 ; Got RebootTime +.done: ret + + ; Common code for copying an option verbatim + ; Copies the option into ES:DI and null-terminates it. + ; Returns with AX=0 and SI past the option. +dhcp_copyoption: + xchg cx,ax ; CX <- option length + rep movsb + xchg cx,ax ; AX <- 0 + stosb ; Null-terminate + ret + + section .data +dhcp_option_list_end: + section .text + + section .data +HaveUUID db 0 +uuid_dashes db 4,2,2,2,6,0 ; Bytes per UUID dashed section + section .text + +; +; genipopt +; +; Generate an ip=<client-ip>:<boot-server-ip>:<gw-ip>:<netmask> +; option into IPOption based on a DHCP packet in trackbuf. +; Assumes CS == DS == ES. +; +genipopt: + pushad + mov di,IPOption + mov eax,'ip=' + stosd + dec di + mov eax,[MyIP] + call gendotquad + mov al,':' + stosb + mov eax,[ServerIP] + call gendotquad + mov al,':' + stosb + mov eax,[Gateway] + call gendotquad + mov al,':' + stosb + mov eax,[Netmask] + call gendotquad ; Zero-terminates its output + sub di,IPOption + mov [IPOptionLen],di + popad + ret + +; +; Call the receive loop while idle. This is done mostly so we can respond to +; ARP messages, but perhaps in the future this can be used to do network +; console. +; +; hpa sez: people using automatic control on the serial port get very +; unhappy if we poll for ARP too often (the PXE stack is pretty slow, +; typically.) Therefore, only poll if at least 4 BIOS timer ticks have +; passed since the last poll, and reset this when a character is +; received (RESET_IDLE). +; +%if HAVE_IDLE + +reset_idle: + push ax + mov ax,[cs:BIOS_timer] + mov [cs:IdleTimer],ax + pop ax + ret + +check_for_arp: + push ax + mov ax,[cs:BIOS_timer] + sub ax,[cs:IdleTimer] + cmp ax,4 + pop ax + jae .need_poll + ret +.need_poll: pushad + push ds + push es + mov ax,cs + mov ds,ax + mov es,ax + mov di,packet_buf + mov [pxe_udp_read_pkt.status],al ; 0 + mov [pxe_udp_read_pkt.buffer],di + mov [pxe_udp_read_pkt.buffer+2],ds + mov word [pxe_udp_read_pkt.buffersize],packet_buf_size + mov eax,[MyIP] + mov [pxe_udp_read_pkt.dip],eax + mov word [pxe_udp_read_pkt.lport],htons(9) ; discard port + mov di,pxe_udp_read_pkt + mov bx,PXENV_UDP_READ + call pxenv + ; Ignore result... + pop es + pop ds + popad + RESET_IDLE + ret + +%endif ; HAVE_IDLE + +; ----------------------------------------------------------------------------- +; Common modules +; ----------------------------------------------------------------------------- + +%include "getc.inc" ; getc et al +%include "conio.inc" ; Console I/O +%include "writestr.inc" ; String output +writestr equ cwritestr +%include "writehex.inc" ; Hexadecimal output +%include "configinit.inc" ; Initialize configuration +%include "parseconfig.inc" ; High-level config file handling +%include "parsecmd.inc" ; Low-level config file handling +%include "bcopy32.inc" ; 32-bit bcopy +%include "loadhigh.inc" ; Load a file into high memory +%include "font.inc" ; VGA font stuff +%include "graphics.inc" ; VGA graphics +%include "highmem.inc" ; High memory sizing +%include "strcpy.inc" ; strcpy() +%include "rawcon.inc" ; Console I/O w/o using the console functions +%include "dnsresolv.inc" ; DNS resolver +%include "adv.inc" ; Auxillary Data Vector + +; ----------------------------------------------------------------------------- +; Begin data section +; ----------------------------------------------------------------------------- + + section .data + +copyright_str db ' Copyright (C) 1994-', year, ' H. Peter Anvin' + db CR, LF, 0 +err_bootfailed db CR, LF, 'Boot failed: press a key to retry, or wait for reset...', CR, LF, 0 +bailmsg equ err_bootfailed +err_nopxe db "No !PXE or PXENV+ API found; we're dead...", CR, LF, 0 +err_pxefailed db 'PXE API call failed, error ', 0 +err_udpinit db 'Failed to initialize UDP stack', CR, LF, 0 +err_noconfig db 'Unable to locate configuration file', CR, LF, 0 +err_damage db 'TFTP server sent an incomprehesible reply', CR, LF, 0 +found_pxenv db 'Found PXENV+ structure', CR, LF, 0 +using_pxenv_msg db 'Old PXE API detected, using PXENV+ structure', CR, LF, 0 +apiver_str db 'PXE API version is ',0 +pxeentry_msg db 'PXE entry point found (we hope) at ', 0 +pxenventry_msg db 'PXENV entry point found (we hope) at ', 0 +trymempxe_msg db 'Scanning memory for !PXE structure... ', 0 +trymempxenv_msg db 'Scanning memory for PXENV+ structure... ', 0 +undi_data_msg db 'UNDI data segment at: ',0 +undi_data_len_msg db 'UNDI data segment size: ',0 +undi_code_msg db 'UNDI code segment at: ',0 +undi_code_len_msg db 'UNDI code segment size: ',0 +cant_free_msg db 'Failed to free base memory, error ', 0 +notfound_msg db 'not found', CR, LF, 0 +myipaddr_msg db 'My IP address seems to be ',0 +tftpprefix_msg db 'TFTP prefix: ', 0 +localboot_msg db 'Booting from local disk...', CR, LF, 0 +trying_msg db 'Trying to load: ', 0 +fourbs_msg db BS, BS, BS, BS, 0 +default_str db 'default', 0 +syslinux_banner db CR, LF, 'PXELINUX ', version_str, ' ', date, ' ', 0 +cfgprefix db 'pxelinux.cfg/' ; No final null! +cfgprefix_len equ ($-cfgprefix) + +; +; Command line options we'd like to take a look at +; +; mem= and vga= are handled as normal 32-bit integer values +initrd_cmd db 'initrd=' +initrd_cmd_len equ $-initrd_cmd + +; This one we make ourselves +bootif_str db 'BOOTIF=' +bootif_str_len equ $-bootif_str +; +; Config file keyword table +; +%include "keywords.inc" + +; +; Extensions to search for (in *forward* order). +; (.bs and .bss are disabled for PXELINUX, since they are not supported) +; + align 4, db 0 +exten_table: db '.cbt' ; COMBOOT (specific) + db '.0', 0, 0 ; PXE bootstrap program + db '.com' ; COMBOOT (same as DOS) + db '.c32' ; COM32 +exten_table_end: + dd 0, 0 ; Need 8 null bytes here + +; +; PXE unload sequences +; +new_api_unload: + db PXENV_UDP_CLOSE + db PXENV_UNDI_SHUTDOWN + db PXENV_UNLOAD_STACK + db PXENV_STOP_UNDI + db 0 +old_api_unload: + db PXENV_UDP_CLOSE + db PXENV_UNDI_SHUTDOWN + db PXENV_UNLOAD_STACK + db PXENV_UNDI_CLEANUP + db 0 + +; +; PXE query packets partially filled in +; + section .bss +pxe_bootp_query_pkt: +.status: resw 1 ; Status +.packettype: resw 1 ; Boot server packet type +.buffersize: resw 1 ; Packet size +.buffer: resw 2 ; seg:off of buffer +.bufferlimit: resw 1 ; Unused + + section .data +pxe_udp_open_pkt: +.status: dw 0 ; Status +.sip: dd 0 ; Source (our) IP + +pxe_udp_close_pkt: +.status: dw 0 ; Status + +pxe_udp_write_pkt: +.status: dw 0 ; Status +.sip: dd 0 ; Server IP +.gip: dd 0 ; Gateway IP +.lport: dw 0 ; Local port +.rport: dw 0 ; Remote port +.buffersize: dw 0 ; Size of packet +.buffer: dw 0, 0 ; seg:off of buffer + +pxe_udp_read_pkt: +.status: dw 0 ; Status +.sip: dd 0 ; Source IP +.dip: dd 0 ; Destination (our) IP +.rport: dw 0 ; Remote port +.lport: dw 0 ; Local port +.buffersize: dw 0 ; Max packet size +.buffer: dw 0, 0 ; seg:off of buffer + +%if GPXE + +gpxe_file_api_check: +.status: dw 0 ; Status +.size: dw 20 ; Size in bytes +.magic: dd 0x91d447b2 ; Magic number +.provider: dd 0 +.apimask: dd 0 +.flags: dd 0 + +gpxe_file_open: +.status: dw 0 ; Status +.filehandle: dw 0 ; FileHandle +.filename: dd 0 ; seg:off of FileName +.reserved: dd 0 + +gpxe_get_file_size: +.status: dw 0 ; Status +.filehandle: dw 0 ; FileHandle +.filesize: dd 0 ; FileSize + +gpxe_file_read: +.status: dw 0 ; Status +.filehandle: dw 0 ; FileHandle +.buffersize: dw 0 ; BufferSize +.buffer: dd 0 ; seg:off of buffer + +%endif ; GPXE + +; +; Misc initialized (data) variables +; + alignb 4, db 0 +BaseStack dd StackBuf ; ESP of base stack + dw 0 ; SS of base stack +NextSocket dw 49152 ; Counter for allocating socket numbers +KeepPXE db 0 ; Should PXE be kept around? + +; +; TFTP commands +; +tftp_tail db 'octet', 0 ; Octet mode +tsize_str db 'tsize' ,0 ; Request size +tsize_len equ ($-tsize_str) + db '0', 0 +blksize_str db 'blksize', 0 ; Request large blocks +blksize_len equ ($-blksize_str) + asciidec TFTP_LARGEBLK + db 0 +tftp_tail_len equ ($-tftp_tail) + + alignb 2, db 0 +; +; Options negotiation parsing table (string pointer, string len, offset +; into socket structure) +; +tftp_opt_table: + dw tsize_str, tsize_len, tftp_filesize + dw blksize_str, blksize_len, tftp_blksize +tftp_opts equ ($-tftp_opt_table)/6 + +; +; Error packet to return on options negotiation error +; +tftp_opt_err dw TFTP_ERROR ; ERROR packet + dw TFTP_EOPTNEG ; ERROR 8: bad options + db 'tsize option required', 0 ; Error message +tftp_opt_err_len equ ($-tftp_opt_err) + + alignb 4, db 0 +ack_packet_buf: dw TFTP_ACK, 0 ; TFTP ACK packet + +; +; IP information (initialized to "unknown" values) +MyIP dd 0 ; My IP address +ServerIP dd 0 ; IP address of boot server +Netmask dd 0 ; Netmask of this subnet +Gateway dd 0 ; Default router +ServerPort dw TFTP_PORT ; TFTP server port + +; +; Variables that are uninitialized in SYSLINUX but initialized here +; + alignb 4, db 0 +BufSafe dw trackbufsize/TFTP_BLOCKSIZE ; Clusters we can load into trackbuf +BufSafeBytes dw trackbufsize ; = how many bytes? +%ifndef DEPEND +%if ( trackbufsize % TFTP_BLOCKSIZE ) != 0 +%error trackbufsize must be a multiple of TFTP_BLOCKSIZE +%endif +%endif diff --git a/core/rawcon.inc b/core/rawcon.inc new file mode 100644 index 00000000..10d7a764 --- /dev/null +++ b/core/rawcon.inc @@ -0,0 +1,75 @@ +; +; writechr: Write a single character in AL to the console without +; mangling any registers. This does raw console writes, +; since some PXE BIOSes seem to interfere regular console I/O. +; +%if IS_ISOLINUX +writechr_full: +%else +writechr: +%endif + push ds + push cs + pop ds + test byte [UsingVGA], 08h + jz .videook + call vgaclearmode +.videook: + call write_serial ; write to serial port if needed + pushfd + test byte [DisplayCon],01h ; Write to screen? + jz .nothing + + pushad + mov bh,[BIOS_page] + push ax + mov ah,03h ; Read cursor position + int 10h + pop ax + cmp al,8 + je .bs + cmp al,13 + je .cr + cmp al,10 + je .lf + push dx + mov bh,[BIOS_page] + mov bl,07h ; White on black + mov cx,1 ; One only + mov ah,09h ; Write char and attribute + int 10h + pop dx + inc dl + cmp dl,[VidCols] + jna .curxyok + xor dl,dl +.lf: inc dh + cmp dh,[VidRows] + ja .scroll +.curxyok: mov bh,[BIOS_page] + mov ah,02h ; Set cursor position + int 10h +.ret: popad +.nothing: + popfd + pop ds + ret +.scroll: dec dh + mov bh,[BIOS_page] + mov ah,02h + int 10h + mov ax,0601h ; Scroll up one line + mov bh,[ScrollAttribute] + xor cx,cx + mov dx,[ScreenSize] ; The whole screen + int 10h + jmp short .ret +.cr: xor dl,dl + jmp short .curxyok +.bs: sub dl,1 + jnc .curxyok + mov dl,[VidCols] + sub dh,1 + jnc .curxyok + xor dh,dh + jmp short .curxyok diff --git a/core/regdump.inc b/core/regdump.inc new file mode 100644 index 00000000..59a48c09 --- /dev/null +++ b/core/regdump.inc @@ -0,0 +1,108 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 2003-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; regdump.inc +;; +;; Dump as much as possible of the register state; for debugging +;; + +disk_dumpregs: + mov ah,02h + call dumpregs + int 13h + ret + +dumpregs: + push gs + push fs + push es + push ds + push ss + push cs + pushad + pushfd + + push cs + pop ds + + mov bp,sp + mov di,regnames + + mov cx,9 ; 9 32-bit registers +.reg8: + mov si,[di] + inc di + inc di + call cwritestr + mov eax,[bp] + add bp,4 + call writehex8 + loop .reg8 + + mov cx,7 ; 6 16-bit registers +.reg4: + mov si,[di] + inc di + inc di + call cwritestr + mov eax,[bp] + inc bp + inc bp + call writehex4 + loop .reg4 + + call crlf + + popfd + popad + add sp,4 ; Skip CS, SS + pop ds + pop es + pop fs + pop gs + ret + +regnames: + dw .eflags + dw .edi + dw .esi + dw .ebp + dw .esp + dw .ebx + dw .edx + dw .ecx + dw .eax + dw .cs + dw .ss + dw .ds + dw .es + dw .fs + dw .gs + dw .ip + +.eflags db 'EFL: ', 0 +.edi db 13,10,'EDI: ', 0 +.esi db ' ESI: ', 0 +.ebp db ' EBP: ', 0 +.esp db ' ESP: ', 0 +.ebx db 13,10,'EBX: ', 0 +.edx db ' EDX: ', 0 +.ecx db ' ECX: ', 0 +.eax db ' EAX: ', 0 +.cs db 13,10,'CS: ',0 +.ss db ' SS: ',0 +.ds db ' DS: ',0 +.es db ' ES: ',0 +.fs db ' FS: ',0 +.gs db ' GS: ',0 +.ip db ' IP: ',0 diff --git a/core/rllpack.inc b/core/rllpack.inc new file mode 100644 index 00000000..a556e00a --- /dev/null +++ b/core/rllpack.inc @@ -0,0 +1,156 @@ +; -*- fundamental -*- --------------------------------------------------- +; +; Copyright 2004-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 53 Temple Place Ste 330, +; Boston MA 02111-1307, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; ----------------------------------------------------------------------- + +; +; rllpack.inc +; +; Very simple RLL compressor/decompressor, used to pack binary structures +; together. +; +; Format of leading byte +; 1-128 = x verbatim bytes follow +; 129-223 = (x-126) times subsequent byte +; 224-255 = (x-224)*256+(next byte) times the following byte +; 0 = end of data +; +; These structures are stored *in reverse order* in high memory. +; High memory pointers point to one byte beyond the end. +; + + section .text + +; +; rllpack: +; Pack CX bytes from SI into EDI. +; Returns updated SI and EDI. +; +rllpack: + push word .pmentry + call simple_pm_call + ret + +.pmentry: + push cx + push ebx + push edx +.startseq: + xor ax,ax ; Zero byte + xor ebx,ebx ; Run length zero + dec edi + mov edx,edi ; Pointer to header byte + mov [edi],al ; Create header byte + jcxz .done ; If done, this was the terminator +.stdbyte: + lodsb + dec edi + mov [edi],al + dec cx + cmp ah,al + je .same +.diff: + mov ah,al + xor bx,bx +.plainbyte: + inc bx + inc byte [edx] + jcxz .startseq + jns .stdbyte + jmp .startseq +.same: + cmp bl,2 + jb .plainbyte + ; 3 bytes or more in a row, time to convert sequence + sub [edx],bl + jnz .normal + inc edi ; We killed a whole stretch, + ; drop start byte +.normal: + inc bx + add edi,ebx ; Remove the stored run bytes +.getrun: + jcxz .nomatch + lodsb + cmp al,ah + jne .nomatch + cmp bx,(256-224)*256-1 ; Maximum run size + jae .nomatch + inc bx + dec cx + jmp .getrun +.nomatch: + cmp bx,224-126 + jae .twobyte +.onebyte: + add bl,126 + dec edi + mov [edi],bl + jmp .storebyte +.twobyte: + add bh,224 + sub edi,2 + mov [edi],bx +.storebyte: + dec edi + mov [edi],ah + dec si ; Reload subsequent byte + jmp .startseq +.done: + pop edx + pop ebx + pop cx + ret +; +; rllunpack: +; Unpack bytes from ESI into DI +; On return ESI, DI are updated and CX contains number of bytes output. +; +rllunpack: + push word .pmentry + call simple_pm_call + ret + +.pmentry: + push di + xor cx,cx +.header: + dec esi + mov cl,[esi] + jcxz .done + cmp cl,129 + jae .isrun + ; Not a run +.copy: + dec esi + mov al,[esi] + stosb + loop .copy + jmp .header +.isrun: + cmp cl,224 + jae .longrun + sub cl,126 +.dorun: + dec esi + mov al,[esi] + rep stosb + jmp .header +.longrun: + sub cl,224 + mov ch,cl + dec esi + mov cl,[esi] + jmp .dorun +.done: + pop cx + sub cx,di + neg cx + ret diff --git a/core/runkernel.inc b/core/runkernel.inc new file mode 100644 index 00000000..abd23782 --- /dev/null +++ b/core/runkernel.inc @@ -0,0 +1,634 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; runkernel.inc +;; +;; Common code for running a Linux kernel +;; + +; +; Hook macros, that may or may not be defined +; +%ifndef HAVE_SPECIAL_APPEND +%macro SPECIAL_APPEND 0 +%endmacro +%endif + +%ifndef HAVE_UNLOAD_PREP +%macro UNLOAD_PREP 0 +%endmacro +%endif + +; +; A Linux kernel consists of three parts: boot sector, setup code, and +; kernel code. The boot sector is never executed when using an external +; booting utility, but it contains some status bytes that are necessary. +; +; First check that our kernel is at least 1K, or else it isn't long +; enough to have the appropriate headers. +; +; We used to require the kernel to be 64K or larger, but it has gotten +; popular to use the Linux kernel format for other things, which may +; not be so large. +; +; Additionally, we used to have a test for 8 MB or smaller. Equally +; obsolete. +; +is_linux_kernel: + push si ; <A> file pointer + mov si,loading_msg + call cwritestr + mov si,KernelCName ; Print kernel name part of + call cwritestr ; "Loading" message + + +; +; Now start transferring the kernel +; + push word real_mode_seg + pop es + +; +; Start by loading the bootsector/setup code, to see if we need to +; do something funky. It should fit in the first 32K (loading 64K won't +; work since we might have funny stuff up near the end of memory). +; + call dot_pause ; Check for abort key + mov cx,8000h >> SECTOR_SHIFT ; Half a moby (32K) + xor bx,bx + pop si ; <A> file pointer + call getfssec + cmp cx,1024 + jb kernel_corrupt + cmp word [es:bs_bootsign],0AA55h + jne kernel_corrupt ; Boot sec signature missing + +; +; Save the file pointer for later... +; + push si ; <A> file pointer + +; +; Construct the command line (append options have already been copied) +; +construct_cmdline: + mov di,[CmdLinePtr] + mov si,boot_image ; BOOT_IMAGE= + mov cx,boot_image_len + rep movsb + mov si,KernelCName ; Unmangled kernel name + mov cx,[KernelCNameLen] + rep movsb + mov al,' ' ; Space + stosb + + SPECIAL_APPEND ; Module-specific hook + + mov si,[CmdOptPtr] ; Options from user input + call strcpy + +; +; Scan through the command line for anything that looks like we might be +; interested in. The original version of this code automatically assumed +; the first option was BOOT_IMAGE=, but that is no longer certain. +; + mov si,cmd_line_here + xor ax,ax + mov [InitRDPtr],ax ; No initrd= option (yet) + push es ; Set DS <- real_mode_seg + pop ds +get_next_opt: lodsb + and al,al + jz cmdline_end + cmp al,' ' + jbe get_next_opt + dec si + mov eax,[si] + cmp eax,'vga=' + je is_vga_cmd + cmp eax,'mem=' + je is_mem_cmd +%if IS_PXELINUX + cmp eax,'keep' ; Is it "keeppxe"? + jne .notkeep + cmp dword [si+3],'ppxe' + jne .notkeep + cmp byte [si+7],' ' ; Must be whitespace or EOS + ja .notkeep + or byte [cs:KeepPXE],1 +.notkeep: +%endif + push es ; <B> ES -> real_mode_seg + push cs + pop es ; Set ES <- normal DS + mov di,initrd_cmd + mov cx,initrd_cmd_len + repe cmpsb + jne .not_initrd + + cmp al,' ' + jbe .noramdisk + mov [cs:InitRDPtr],si + jmp .not_initrd +.noramdisk: + xor ax,ax + mov [cs:InitRDPtr],ax +.not_initrd: pop es ; <B> ES -> real_mode_seg +skip_this_opt: lodsb ; Load from command line + cmp al,' ' + ja skip_this_opt + dec si + jmp short get_next_opt +is_vga_cmd: + add si,4 + mov eax,[si-1] + mov bx,-1 + cmp eax,'=nor' ; vga=normal + je vc0 + dec bx ; bx <- -2 + cmp eax,'=ext' ; vga=ext + je vc0 + dec bx ; bx <- -3 + cmp eax,'=ask' ; vga=ask + je vc0 + call parseint ; vga=<number> + jc skip_this_opt ; Not an integer +vc0: mov [bs_vidmode],bx ; Set video mode + jmp short skip_this_opt +is_mem_cmd: + add si,4 + call parseint + jc skip_this_opt ; Not an integer +%if HIGHMEM_SLOP != 0 + sub ebx,HIGHMEM_SLOP +%endif + mov [cs:MyHighMemSize],ebx + jmp short skip_this_opt +cmdline_end: + push cs ; Restore standard DS + pop ds + sub si,cmd_line_here + mov [CmdLineLen],si ; Length including final null +; +; Now check if we have a large kernel, which needs to be loaded high +; +prepare_header: + mov dword [RamdiskMax], HIGHMEM_MAX ; Default initrd limit + cmp dword [es:su_header],HEADER_ID ; New setup code ID + jne old_kernel ; Old kernel, load low + mov ax,[es:su_version] + mov [KernelVersion],ax + cmp ax,0200h ; Setup code version 2.0 + jb old_kernel ; Old kernel, load low + cmp ax,0201h ; Version 2.01+? + jb new_kernel ; If 2.00, skip this step + ; Set up the heap (assuming loading high for now) + mov word [es:su_heapend],linux_stack-512 + or byte [es:su_loadflags],80h ; Let the kernel know we care + cmp ax,0203h ; Version 2.03+? + jb new_kernel ; Not 2.03+ + mov eax,[es:su_ramdisk_max] + mov [RamdiskMax],eax ; Set the ramdisk limit + +; +; We definitely have a new-style kernel. Let the kernel know who we are, +; and that we are clueful +; +new_kernel: + mov byte [es:su_loader],my_id ; Show some ID + xor eax,eax + mov [es:su_ramdisklen],eax ; No initrd loaded yet + +; +; About to load the kernel. This is a modern kernel, so use the boot flags +; we were provided. +; + mov al,[es:su_loadflags] + mov [LoadFlags],al + + ; Cap the ramdisk memory range if appropriate + mov eax,[RamdiskMax] + cmp eax,[MyHighMemSize] + ja .ok + mov [MyHighMemSize],eax +.ok: + +any_kernel: + +; +; Load the kernel. We always load it at 100000h even if we're supposed to +; load it "low"; for a "low" load we copy it down to low memory right before +; jumping to it. +; +read_kernel: + movzx ax,byte [es:bs_setupsecs] ; Setup sectors + and ax,ax + jnz .sects_ok + mov al,4 ; 0 = 4 setup sectors +.sects_ok: + inc ax ; Including the boot sector + mov [SetupSecs],ax + + call dot_pause + +; +; Move the stuff beyond the setup code to high memory at 100000h +; + movzx esi,word [SetupSecs] ; Setup sectors + shl si,9 ; Convert to bytes + mov ecx,8000h ; 32K + sub ecx,esi ; Number of bytes to copy + add esi,(real_mode_seg << 4) ; Pointer to source + mov edi,100000h ; Copy to address 100000h + + call bcopy ; Transfer to high memory + + pop si ; <A> File pointer + and si,si ; EOF already? + jz high_load_done + + ; On exit EDI -> where to load the rest + + mov bx,dot_pause + or eax,-1 ; Load the whole file + mov dx,3 ; Pad to dword + call load_high + +high_load_done: + mov [KernelEnd],edi + mov ax,real_mode_seg ; Set to real mode seg + mov es,ax + + mov si,dot_msg + call cwritestr +; +; Some older kernels (1.2 era) would have more than 4 setup sectors, but +; would not rely on the boot protocol to manage that. These kernels fail +; if they see protected-mode kernel data after the setup sectors, so +; clear that memory. +; + mov di,[SetupSecs] + shl di,9 + xor eax,eax + mov cx,cmd_line_here + sub cx,di + shr cx,2 + rep stosd + +; +; Now see if we have an initial RAMdisk; if so, do requisite computation +; We know we have a new kernel; the old_kernel code already will have objected +; if we tried to load initrd using an old kernel +; +load_initrd: + xor eax,eax + cmp [InitRDPtr],ax + jz .noinitrd + call parse_load_initrd +.noinitrd: + +; +; Abandon hope, ye that enter here! We do no longer permit aborts. +; + call abort_check ; Last chance!! + + mov si,ready_msg + call cwritestr + + UNLOAD_PREP ; Module-specific hook + +; +; Now, if we were supposed to load "low", copy the kernel down to 10000h +; and the real mode stuff to 90000h. We assume that all bzImage kernels are +; capable of starting their setup from a different address. +; + mov ax,real_mode_seg + mov es,ax + mov fs,ax + +; +; If the default root device is set to FLOPPY (0000h), change to +; /dev/fd0 (0200h) +; + cmp word [es:bs_rootdev],byte 0 + jne root_not_floppy + mov word [es:bs_rootdev],0200h +root_not_floppy: + +; +; Copy command line. Unfortunately, the old kernel boot protocol requires +; the command line to exist in the 9xxxxh range even if the rest of the +; setup doesn't. +; +setup_command_line: + mov dx,[KernelVersion] + test byte [LoadFlags],LOAD_HIGH + jz .need_high_cmdline + cmp dx,0202h ; Support new cmdline protocol? + jb .need_high_cmdline + ; New cmdline protocol + ; Store 32-bit (flat) pointer to command line + ; This is the "high" location, since we have bzImage + mov dword [fs:su_cmd_line_ptr],(real_mode_seg << 4)+cmd_line_here + mov word [HeapEnd],linux_stack + mov word [fs:su_heapend],linux_stack-512 + jmp .setup_done + +.need_high_cmdline: +; +; Copy command line down to fit in high conventional memory +; -- this happens if we have a zImage kernel or the protocol +; is less than 2.02. +; + mov si,cmd_line_here + mov di,old_cmd_line_here + mov [fs:kern_cmd_magic],word CMD_MAGIC ; Store magic + mov [fs:kern_cmd_offset],di ; Store pointer + mov word [HeapEnd],old_linux_stack + mov ax,255 ; Max cmdline limit + cmp dx,0201h + jb .adjusted + ; Protocol 2.01+ + mov word [fs:su_heapend],old_linux_stack-512 + jbe .adjusted + ; Protocol 2.02+ + ; Note that the only reason we would end up here is + ; because we have a zImage, so we anticipate the move + ; to 90000h already... + mov dword [fs:su_cmd_line_ptr],0x90000+old_cmd_line_here + mov ax,old_max_cmd_len ; 2.02+ allow a higher limit +.adjusted: + + mov cx,[CmdLineLen] + cmp cx,ax + jna .len_ok + mov cx,ax ; Truncate the command line +.len_ok: + fs rep movsb + stosb ; Final null, note AL=0 already + mov [CmdLineEnd],di + cmp dx,0200h + jb .nomovesize + mov [es:su_movesize],di ; Tell the kernel what to move +.nomovesize: +.setup_done: + +; +; Time to start setting up move descriptors +; +setup_move: + mov di,trackbuf + xor cx,cx ; Number of descriptors + + mov bx,es ; real_mode_seg + mov fs,bx + push ds ; We need DS == ES == CS here + pop es + + test byte [LoadFlags],LOAD_HIGH + jnz .loading_high + +; Loading low: move real_mode stuff to 90000h, then move the kernel down + mov eax,90000h + stosd + mov eax,real_mode_seg << 4 + stosd + movzx eax,word [CmdLineEnd] + stosd + inc cx + + mov eax,10000h ; Target address of low kernel + stosd + mov eax,100000h ; Where currently loaded + stosd + neg eax + add eax,[KernelEnd] + stosd + inc cx + + mov bx,9000h ; Revised real mode segment + +.loading_high: + + cmp word [InitRDPtr],0 ; Did we have an initrd? + je .no_initrd + + mov eax,[fs:su_ramdiskat] + stosd + mov eax,[InitRDStart] + stosd + mov eax,[fs:su_ramdisklen] + stosd + inc cx + +.no_initrd: + push cx ; Length of descriptor list + push word trackbuf + + mov dword [EntryPoint],run_linux_kernel + ; BX points to the final real mode segment, and will be loaded + ; into DS. + jmp replace_bootstrap + + +run_linux_kernel: +; +; Set up segment registers and the Linux real-mode stack +; Note: ds == the real mode segment +; + cli + mov ax,ds + mov ss,ax + mov sp,strict word linux_stack + ; Point HeapEnd to the immediate of the instruction above +HeapEnd equ $-2 ; Self-modifying code! Fun! + mov es,ax + mov fs,ax + mov gs,ax + +; +; We're done... now RUN THAT KERNEL!!!! +; Setup segment == real mode segment + 020h; we need to jump to offset +; zero in the real mode segment. +; + add ax,020h + push ax + push word 0h + retf + +; +; Load an older kernel. Older kernels always have 4 setup sectors, can't have +; initrd, and are always loaded low. +; +old_kernel: + xor ax,ax + cmp word [InitRDPtr],ax ; Old kernel can't have initrd + je .load + mov si,err_oldkernel + jmp abort_load +.load: + mov byte [LoadFlags],al ; Always low + mov word [KernelVersion],ax ; Version 0.00 + jmp any_kernel + +; +; parse_load_initrd +; +; Parse an initrd= option and load the initrds. This sets +; InitRDStart and InitRDEnd with dword padding between; we then +; do a global memory shuffle to move it to the end of memory. +; +; On entry, EDI points to where to start loading. +; +parse_load_initrd: + push es + push ds + mov ax,real_mode_seg + mov ds,ax + push cs + pop es ; DS == real_mode_seg, ES == CS + + mov [cs:InitRDStart],edi + mov [cs:InitRDEnd],edi + + mov si,[cs:InitRDPtr] + +.get_chunk: + ; DS:SI points to the start of a name + + mov bx,si +.find_end: + lodsb + cmp al,',' + je .got_end + cmp al,' ' + jbe .got_end + jmp .find_end + +.got_end: + push ax ; Terminating character + push si ; Next filename (if any) + mov byte [si-1],0 ; Zero-terminate + mov si,bx ; Current filename + + push di + mov di,InitRD ; Target buffer for mangled name + call mangle_name + pop di + call loadinitrd + + pop si + pop ax + mov [si-1],al ; Restore ending byte + + cmp al,',' + je .get_chunk + + ; Compute the initrd target location + mov edx,[cs:InitRDEnd] + sub edx,[cs:InitRDStart] + mov [su_ramdisklen],edx + mov eax,[cs:MyHighMemSize] + sub eax,edx + and ax,0F000h ; Round to a page boundary + mov [su_ramdiskat],eax + + pop ds + pop es + ret + +; +; Load RAM disk into high memory +; +; Input: InitRD - set to the mangled name of the initrd +; EDI - location to load +; Output: EDI - location for next initrd +; InitRDEnd - updated +; +loadinitrd: + push ds + push es + mov ax,cs ; CS == DS == ES + mov ds,ax + mov es,ax + push edi + mov si,InitRD + mov di,InitRDCName + call unmangle_name ; Create human-readable name + sub di,InitRDCName + mov [InitRDCNameLen],di + mov di,InitRD + call searchdir ; Look for it in directory + pop edi + jz .notthere + + push si + mov si,crlfloading_msg ; Write "Loading " + call cwritestr + mov si,InitRDCName ; Write ramdisk name + call cwritestr + mov si,dotdot_msg ; Write dots + call cwritestr + pop si + + mov dx,3 + mov bx,dot_pause + call load_high + mov [InitRDEnd],ebx + + pop es + pop ds + ret + +.notthere: + mov si,err_noinitrd + call cwritestr + mov si,InitRDCName + call cwritestr + mov si,crlf_msg + jmp abort_load + +no_high_mem: ; Error routine + mov si,err_nohighmem + jmp abort_load + + ret + + section .data +crlfloading_msg db CR, LF +loading_msg db 'Loading ', 0 +dotdot_msg db '.' +dot_msg db '.', 0 +ready_msg db 'ready.', CR, LF, 0 +err_oldkernel db 'Cannot load a ramdisk with an old kernel image.' + db CR, LF, 0 +err_noinitrd db CR, LF, 'Could not find ramdisk image: ', 0 + +boot_image db 'BOOT_IMAGE=' +boot_image_len equ $-boot_image + + section .bss + alignb 4 +MyHighMemSize resd 1 ; Possibly adjusted highmem size +RamdiskMax resd 1 ; Highest address for ramdisk +KernelSize resd 1 ; Size of kernel in bytes +KernelSects resd 1 ; Size of kernel in sectors +KernelEnd resd 1 ; Ending address of the kernel image +InitRDStart resd 1 ; Start of initrd (pre-relocation) +InitRDEnd resd 1 ; End of initrd (pre-relocation) +CmdLineLen resw 1 ; Length of command line including null +CmdLineEnd resw 1 ; End of the command line in real_mode_seg +SetupSecs resw 1 ; Number of setup sectors (+bootsect) +InitRDPtr resw 1 ; Pointer to initrd= option in command line +KernelVersion resw 1 ; Kernel protocol version +LoadFlags resb 1 ; Loadflags from kernel diff --git a/core/stack.inc b/core/stack.inc new file mode 100644 index 00000000..f670dec0 --- /dev/null +++ b/core/stack.inc @@ -0,0 +1,47 @@ +; ----------------------------------------------------------------------- +; +; Copyright 2005-2008 H. Peter Anvin - All Rights Reserved +; +; 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, Inc., 53 Temple Place Ste 330, +; Boston MA 02111-1307, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; ----------------------------------------------------------------------- + +; +; stack.inc +; +; How to reset the stack pointer +; + +%ifndef _STACK_INC +%define _STACK_INC + +; +; This macro resets the stack pointer (including SS), and sets +; DS == ES == 0, interrupts on, DF = 0. +; +; It takes a 16-bit register that can be safely clobbered as parameter. +; +%macro RESET_STACK_AND_SEGS 1 + xor %1,%1 + mov ds,%1 + mov es,%1 +%if IS_SYSLINUX || IS_EXTLINUX + mov ss,%1 ; Just in case... + mov sp,StackBuf-2*5 ; Reset stack +%elif IS_PXELINUX + lss esp,[BaseStack] +%elif IS_ISOLINUX + mov ss,%1 + mov sp,StackBuf-2*2 +%else + NEED TO KNOW HOW TO RESET STACK +%endif + sti + cld +%endmacro + +%endif ; _STACK_INC diff --git a/core/strcpy.inc b/core/strcpy.inc new file mode 100644 index 00000000..2bafa7fe --- /dev/null +++ b/core/strcpy.inc @@ -0,0 +1,13 @@ +; +; strcpy: Copy DS:SI -> ES:DI up to and including a null byte; +; on exit SI and DI point to the byte *after* the null byte +; + section .text + +strcpy: push ax +.loop: lodsb + stosb + and al,al + jnz .loop + pop ax + ret diff --git a/core/strecpy.inc b/core/strecpy.inc new file mode 100644 index 00000000..1fc53e96 --- /dev/null +++ b/core/strecpy.inc @@ -0,0 +1,28 @@ +; +; strecpy: Copy DS:SI -> ES:DI up to and including a null byte; +; on exit SI and DI point to the byte *after* the null byte. +; BP holds a pointer to the first byte beyond the end of the +; target buffer; return with CF=1 if target buffer overflows; +; the output is still zero-terminated. +; + section .text + +strecpy: + push ax + push bp + dec bp + dec bp +.loop: lodsb + stosb + and al,al ; CF=0 + jz .done + cmp bp,di ; CF set if BP < DI + jnc .loop + + ; Zero-terminate overflow string + mov al,0 ; Avoid changing flags + stosb +.done: + pop bp + pop ax + ret diff --git a/core/syslinux.ld b/core/syslinux.ld new file mode 100644 index 00000000..3c55820f --- /dev/null +++ b/core/syslinux.ld @@ -0,0 +1,119 @@ +/* + * Linker script for the SYSLINUX core + */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +EXTERN(_start) +ENTRY(_start) + +STACK_LEN = 4096; + +SECTIONS +{ + /* "Early" sections (before the load) */ + . = 0x0800; + + .earlybss : { + __earlybss_start = .; + *(.earlybss) + __earlybss_end = .; + } + __earlybss_len = __earlybss_end - __earlybss_start; + __earlybss_dwords = (__earlybss_len + 3) >> 2; + + .bcopy32 : AT (__bcopy32_lma) { + FILL(0x90909090) + __bcopy32_start = .; + *(.bcopy32) + __bcopy32_end = .; + } + __bcopy32_len = __bcopy32_end - __bcopy32_start; + __bcopy32_dwords = (__bcopy32_len + 3) >> 2; + + .config : AT (__config_lma) { + __config_start = .; + *(.config) + __config_end = .; + } + __config_len = __config_end - __config_start; + __config_dwords = (__config_len + 3) >> 2; + + .bss : AT(__bss_start) { + __bss_start = .; + *(.bss) + *(.bss2) + __bss_end = .; + } + __bss_len = __bss_end - __bss_start; + __bss_dwords = (__bss_len + 3) >> 2; + + /* Stack */ + + . = 0x7c00 - STACK_LEN; + .stack : { + __stack_start = .; + . += STACK_LEN; + __stack_end = .; + } + __stack_len = __stack_end - __stack_start; + __stack_dwords = (__stack_len + 3) >> 2; + + /* Initialized sections */ + + . = 0x7c00; + .text : { + FILL(0x90909090) + __text_start = .; + *(.text) + __text_end = .; + } + __text_len = __text_end - __text_start; + __text_dwords = (__text_len + 3) >> 2; + + . = ALIGN(4); + __bcopy32_lma = .; + . += SIZEOF(.bcopy32); + + . = ALIGN(4); + .data : { + __data_start = .; + *(.data) + __data_end = .; + } + __data_len = __data_end - __data_start; + __data_dwords = (__data_len + 3) >> 2; + + . = ALIGN(4); + __config_lma = .; + . += SIZEOF(.config); + + /* ADV, must be the last intialized section */ + + . = ALIGN(512); + .adv : { + __adv_start = .; + *(.adv) + __adv_end = .; + } + __adv_len = __adv_end - __adv_start; + __adv_dwords = (__adv_len + 3) >> 2; + + /* Late uninitialized sections */ + + .uibss : { + __uibss_start = .; + *(.uibss) + __uibss_end = .; + } + __uibss_len = __uibss_end - __uibss_start; + __uibss_dwords = (__uibss_len + 3) >> 2; + + .bss1 : { + __bss1_start = .; + *(.bss1) + __bss1_end = .; + } + __bss1_len = __bss1_end - __bss1_start; + __bss1_dwords = (__bss1_len + 3) >> 2; +} diff --git a/core/tracers.inc b/core/tracers.inc new file mode 100644 index 00000000..a51209fa --- /dev/null +++ b/core/tracers.inc @@ -0,0 +1,40 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; tracers.inc +;; +;; Debugging tracers +;; + +%ifndef _TRACERS_INC +%define _TRACERS_INC + +; Note: The Makefile builds one version with DEBUG_MESSAGES automatically. +; %define DEBUG_TRACERS 1 ; Uncomment to get debugging tracers +; %define DEBUG_MESSAGES ; Uncomment to get debugging messages + +%ifdef DEBUG_TRACERS + +%macro TRACER 1 + call debug_tracer + db %1 +%endmacro + +%else ; DEBUG_TRACERS + +%macro TRACER 1 +%endmacro + +%endif ; DEBUG_TRACERS + +%endif ; _TRACERS_INC diff --git a/core/ui.inc b/core/ui.inc new file mode 100644 index 00000000..3fc3e639 --- /dev/null +++ b/core/ui.inc @@ -0,0 +1,683 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +; +; This file should be entered with the config file open (for getc) +; +load_config_file: + call parse_config ; Parse configuration file +no_config_file: + + call adv_init +; +; Check for an ADV boot-once entry +; + mov dl,ADV_BOOTONCE + call adv_get + jcxz .no_bootonce + +.have_bootone: + ; We apparently have a boot-once set; clear it and + ; then execute the boot-once... + + ; Save the boot-once data; SI = data, CX = length + mov di,command_line + rep movsb + xor ax,ax + stosb + + ; Clear the boot-once data from the ADV + xor cx,cx ; Set to zero = delete + call adv_set + jc .err + call adv_write +.err: jmp load_kernel + +.no_bootonce: + +; +; Check whether or not we are supposed to display the boot prompt. +; +check_for_key: + cmp word [ForcePrompt],0 ; Force prompt? + jnz enter_command + test byte [KbdFlags],5Bh ; Shift Alt Caps Scroll + jz auto_boot ; If neither, default boot + +enter_command: + cmp word [NoEscape],0 ; If NOESCAPE, no prompt, + jne auto_boot ; always run default cmd + + mov si,boot_prompt + call cwritestr + + mov byte [FuncFlag],0 ; <Ctrl-F> not pressed + mov di,command_line + +; +; get the very first character -- we can either time +; out, or receive a character press at this time. Some dorky BIOSes stuff +; a return in the buffer on bootup, so wipe the keyboard buffer first. +; +clear_buffer: mov ah,11h ; Check for pending char + int 16h + jz get_char_time + mov ah,10h ; Get char + int 16h + jmp short clear_buffer + + ; For the first character, both KbdTimeout and + ; TotalTimeout apply; after that, only TotalTimeout. + +get_char_time: + mov eax,[TotalTimeout] + mov [ThisTotalTo],eax + mov eax,[KbdTimeout] + mov [ThisKbdTo],eax + +get_char: + call getchar_timeout + and dword [ThisKbdTo],0 ; For the next time... + + and al,al + jz func_key + +got_ascii: cmp al,7Fh ; <DEL> == <BS> + je backspace + cmp al,' ' ; ASCII? + jb not_ascii + ja enter_char + cmp di,command_line ; Space must not be first + je short get_char +enter_char: test byte [FuncFlag],1 + jnz ctrl_f ; Keystroke after <Ctrl-F> + cmp di,max_cmd_len+command_line ; Check there's space + jnb short get_char + stosb ; Save it + call writechr ; Echo to screen + jmp short get_char +not_ascii: + cmp al,0Dh ; Enter + je command_done + cmp al,'F' & 1Fh ; <Ctrl-F> + je set_func_flag +%if IS_PXELINUX + cmp al,'N' & 1Fh ; <Ctrl-N> + je show_network_info +%endif + cmp al,'U' & 1Fh ; <Ctrl-U> + je kill_command ; Kill input line + cmp al,'V' & 1Fh ; <Ctrl-V> + je print_version + cmp al,'X' & 1Fh ; <Ctrl-X> + je force_text_mode + cmp al,08h ; Backspace + jne get_char +backspace: cmp di,command_line ; Make sure there is anything + je get_char ; to erase + dec di ; Unstore one character + mov si,wipe_char ; and erase it from the screen + call cwritestr +get_char_2: + jmp short get_char + +kill_command: + call crlf + jmp enter_command + +force_text_mode: + call vgaclearmode + jmp enter_command + +set_func_flag: + mov byte [FuncFlag],1 + jmp short get_char_2 + +ctrl_f: + xor ah,ah + mov [FuncFlag],ah + cmp al,'0' + jb get_char_2 + je .zero ; <Ctrl-F>0 = F10 + or al,20h ; Lower case + cmp al,'9' + jna .digit + cmp al,'a' ; F10-F12 = <Ctrl-F>A, B, C + jb get_char_2 + cmp al,'c' + ja get_char_2 + sub al,'a'-10 + jmp show_help +.zero: + mov al,10 + jmp show_help +.digit: + sub al,'1' + jmp show_help + +func_key: + ; AL = 0 if we get here + xchg al,ah + cmp al,44h ; F10 + ja .f11_f12 + sub al,3Bh ; F1 + jb get_char_2 + jmp show_help +.f11_f12: + cmp al,85h ; F11 + jb get_char_2 + cmp al,86h ; F12 + ja get_char_2 + sub al,85h-10 + +show_help: ; AX = func key # (0 = F1, 9 = F10, 11 = F12) + push di ; Save end-of-cmdline pointer + shl ax,FILENAME_MAX_LG2 ; Convert to pointer + add ax,FKeyName + xchg di,ax + cmp byte [di+NULLOFFSET],NULLFILE + je short fk_nofile ; Undefined F-key + call open + jz short fk_nofile ; File not found + call crlf + call get_msg_file + jmp short fk_wrcmd + +print_version: + push di ; Command line write pointer + mov si,syslinux_banner + call cwritestr +%ifdef HAVE_BIOSNAME + mov si,[BIOSName] + call cwritestr +%endif + mov si,copyright_str + call cwritestr + + ; ... fall through ... + + ; Write the boot prompt and command line again and + ; wait for input. Note that this expects the cursor + ; to already have been CRLF'd, and that the old value + ; of DI (the command line write pointer) is on the stack. +fk_wrcmd: + mov si,boot_prompt + call cwritestr + pop di ; Command line write pointer + push di + mov byte [di],0 ; Null-terminate command line + mov si,command_line + call cwritestr ; Write command line so far +fk_nofile: pop di + jmp get_char + +; +; Show network info (in the form of the ipappend strings) +; +%if IS_PXELINUX +show_network_info: + push di ; Command line write pointer + call crlf + mov si,IPAppends ; See comboot.doc + mov cx,numIPAppends +.loop: + lodsw + push si + mov si,ax + call cwritestr + call crlf + pop si + loop .loop + jmp fk_wrcmd +%endif + +; +; Jump here to run the default command line +; +auto_boot: + mov si,default_cmd + mov di,command_line + mov cx,(max_cmd_len+4) >> 2 + rep movsd + jmp short load_kernel + +; +; Jump here when the command line is completed +; +command_done: + call crlf + cmp di,command_line ; Did we just hit return? + je auto_boot + xor al,al ; Store a final null + stosb + +load_kernel: ; Load the kernel now +; +; First we need to mangle the kernel name the way DOS would... +; + mov si,command_line + mov di,KernelName + push si + call mangle_name + pop si +; +; Fast-forward to first option (we start over from the beginning, since +; mangle_name doesn't necessarily return a consistent ending state.) +; +clin_non_wsp: lodsb + cmp al,' ' + ja clin_non_wsp +clin_is_wsp: and al,al + jz clin_opt_ptr + lodsb + cmp al,' ' + jbe clin_is_wsp +clin_opt_ptr: dec si ; Point to first nonblank + mov [CmdOptPtr],si ; Save ptr to first option +; +; If "allowoptions 0", put a null character here in order to ignore any +; user-specified options. +; + mov ax,[AllowOptions] + and ax,ax + jnz clin_opt_ok + mov [si],al +clin_opt_ok: + +; +; Now check if it is a "virtual kernel" +; +vk_check: + mov esi,[HighMemSize] ; Start from top of memory +.scan: + cmp esi,[VKernelEnd] + jbe .not_vk + + mov di,VKernelBuf + call rllunpack + ; ESI updated on return + + sub di,cx ; Return to beginning of buf + push si + mov si,KernelName + mov cx,FILENAME_MAX + es repe cmpsb + pop si + je .found + jmp .scan + +; +; We *are* using a "virtual kernel" +; +.found: + push es + push word real_mode_seg + pop es + mov di,cmd_line_here + mov si,VKernelBuf+vk_append + mov cx,[VKernelBuf+vk_appendlen] + rep movsb + mov [CmdLinePtr],di ; Where to add rest of cmd + pop es + mov di,KernelName + push di + mov si,VKernelBuf+vk_rname + mov cx,FILENAME_MAX ; We need ECX == CX later + rep movsb + pop di +%if IS_PXELINUX + mov al,[VKernelBuf+vk_ipappend] + mov [IPAppend],al +%endif + xor bx,bx ; Try only one version + + mov al, [VKernelBuf+vk_type] + mov [KernelType], al + +%if HAS_LOCALBOOT + ; Is this a "localboot" pseudo-kernel? +%if IS_PXELINUX + cmp byte [VKernelBuf+vk_rname+4], 0 +%else + cmp byte [VKernelBuf+vk_rname], 0 +%endif + jne get_kernel ; No, it's real, go get it + + mov ax, [VKernelBuf+vk_rname+1] + jmp local_boot +%else + jmp get_kernel +%endif + +.not_vk: + +; +; Not a "virtual kernel" - check that's OK and construct the command line +; + cmp word [AllowImplicit],byte 0 + je bad_implicit + push es + push si + push di + mov di,real_mode_seg + mov es,di + mov si,AppendBuf + mov di,cmd_line_here + mov cx,[AppendLen] + rep movsb + mov [CmdLinePtr],di + pop di + pop si + pop es + + mov [KernelType], cl ; CL == 0 here + +; +; Find the kernel on disk +; +get_kernel: mov byte [KernelName+FILENAME_MAX],0 ; Zero-terminate filename/extension + mov di,KernelName+4*IS_PXELINUX + xor al,al + mov cx,FILENAME_MAX-5 ; Need 4 chars + null + repne scasb ; Scan for final null + jne .no_skip + dec di ; Point to final null +.no_skip: mov [KernelExtPtr],di + mov bx,exten_table +.search_loop: push bx + mov di,KernelName ; Search on disk + call searchdir + pop bx + jnz kernel_good + mov eax,[bx] ; Try a different extension + mov si,[KernelExtPtr] + mov [si],eax + mov byte [si+4],0 + add bx,byte 4 + cmp bx,exten_table_end + jna .search_loop ; allow == case (final case) + ; Fall into bad_kernel +; +; bad_kernel: Kernel image not found +; bad_implicit: The user entered a nonvirtual kernel name, with "implicit 0" +; +bad_implicit: +bad_kernel: + mov cx,[OnerrorLen] + and cx,cx + jnz on_error +.really: + mov si,KernelName + mov di,KernelCName + push di + call unmangle_name ; Get human form + mov si,err_notfound ; Complain about missing kernel + call cwritestr + pop si ; KernelCName + call cwritestr + mov si,crlf_msg + jmp abort_load ; Ask user for clue + +; +; on_error: bad kernel, but we have onerror set; CX = OnerrorLen +; +on_error: + mov si,Onerror + mov di,command_line + push si ; <A> + push di ; <B> + push cx ; <C> + push cx ; <D> + push di ; <E> + repe cmpsb + pop di ; <E> di == command_line + pop bx ; <D> bx == [OnerrorLen] + je bad_kernel.really ; Onerror matches command_line already + neg bx ; bx == -[OnerrorLen] + lea cx,[max_cmd_len+bx] + ; CX == max_cmd_len-[OnerrorLen] + mov di,command_line+max_cmd_len-1 + mov byte [di+1],0 ; Enforce null-termination + lea si,[di+bx] + std + rep movsb ; Make space in command_line + cld + pop cx ; <C> cx == [OnerrorLen] + pop di ; <B> di == command_line + pop si ; <A> si == Onerror + rep movsb + jmp load_kernel + +; +; kernel_corrupt: Called if the kernel file does not seem healthy +; +kernel_corrupt: mov si,err_notkernel + jmp abort_load + +; +; Get a key, observing ThisKbdTO and ThisTotalTO -- those are timeouts +; which can be adjusted by the caller based on the corresponding +; master variables; on return they're updated. +; +; This cheats. If we say "no timeout" we actually get a timeout of +; 7.5 years. +; +getchar_timeout: + call vgashowcursor + RESET_IDLE + +.loop: + push word [BIOS_timer] + call pollchar + jnz .got_char + pop ax + cmp ax,[BIOS_timer] ; Has the timer advanced? + je .loop + DO_IDLE + + dec dword [ThisKbdTo] + jz .timeout + dec dword [ThisTotalTo] + jnz .loop + +.timeout: + ; Timeout!!!! + pop cx ; Discard return address + call vgahidecursor + mov si,Ontimeout ; Copy ontimeout command + mov di,command_line + mov cx,[OntimeoutLen] ; if we have one... + rep movsb + jmp command_done + +.got_char: + pop cx ; Discard + call getchar + call vgahidecursor + ret + +; +; This is it! We have a name (and location on the disk)... let's load +; that sucker!! First we have to decide what kind of file this is; base +; that decision on the file extension. The following extensions are +; recognized; case insensitive: +; +; .com - COMBOOT image +; .cbt - COMBOOT image +; .c32 - COM32 image +; .bs - Boot sector +; .0 - PXE bootstrap program (PXELINUX only) +; .bin - Boot sector +; .bss - Boot sector, but transfer over DOS superblock (SYSLINUX only) +; .img - Floppy image (ISOLINUX only) +; +; Anything else is assumed to be a Linux kernel. +; + section .bss + alignb 4 +Kernel_EAX resd 1 +Kernel_SI resw 1 + + section .text +kernel_good_saved: + ; Alternate entry point for which the return from + ; searchdir is stored in memory. This is used for + ; COMBOOT function INT 22h, AX=0016h. + mov si,[Kernel_SI] + mov eax,[Kernel_EAX] + +kernel_good: + pushad + + mov si,KernelName + mov di,KernelCName + call unmangle_name + sub di,KernelCName + mov [KernelCNameLen],di + + ; Default memory limit, can be overridden by image loaders + mov eax,[HighMemRsvd] + mov [MyHighMemSize],eax + + popad + + push di + push ax + mov di,KernelName+4*IS_PXELINUX + xor al,al + mov cx,FILENAME_MAX + repne scasb + jne .one_step + dec di +.one_step: mov ecx,[di-4] ; 4 bytes before end + pop ax + pop di + +; +; At this point, EAX contains the size of the kernel, SI contains +; the file handle/cluster pointer, and ECX contains the extension (if any.) +; + movzx di,byte [KernelType] + add di,di + jmp [kerneltype_table+di] + +is_unknown_filetype: + or ecx,20202000h ; Force lower case (except dot) + + cmp ecx,'.com' + je is_comboot_image + cmp ecx,'.cbt' + je is_comboot_image + cmp ecx,'.c32' + je is_com32_image +%if IS_ISOLINUX + cmp ecx,'.img' + je is_disk_image +%endif + cmp ecx,'.bss' + je is_bss_sector + cmp ecx,'.bin' + je is_bootsector + shr ecx,8 + cmp ecx,'.bs' + je is_bootsector + shr ecx,8 + cmp cx,'.0' + je is_bootsector + + ; Otherwise Linux kernel + jmp is_linux_kernel + +is_config_file: + pusha + mov si,KernelCName ; Save the config file name, for posterity + mov di,ConfigName + call strcpy + popa + call openfd + call reset_config + jmp load_config_file + +; This is an image type we can't deal with +is_bad_image: + mov si,err_badimage + call cwritestr + jmp enter_command + +%if IS_SYSLINUX || IS_MDSLINUX + ; ok +%else +is_bss_sector equ is_bad_image +%endif +%if IS_ISOLINUX + ; ok +%else +is_disk_image equ is_bad_image +%endif + + section .data +boot_prompt db 'boot: ', 0 +wipe_char db BS, ' ', BS, 0 +err_badimage db 'Invalid image type for this media type!', CR, LF, 0 +err_notfound db 'Could not find kernel image: ',0 +err_notkernel db CR, LF, 'Invalid or corrupt kernel image.', CR, LF, 0 + + + align 2, db 0 +kerneltype_table: + dw is_unknown_filetype ; VK_KERNEL + dw is_linux_kernel ; VK_LINUX + dw is_bootsector ; VK_BOOT + dw is_bss_sector ; VK_BSS + dw is_bootsector ; VK_PXE + dw is_disk_image ; VK_FDIMAGE + dw is_comboot_image ; VK_COMBOOT + dw is_com32_image ; VK_COM32 + dw is_config_file ; VK_CONFIG + + section .bss + alignb 4 +ThisKbdTo resd 1 ; Temporary holder for KbdTimeout +ThisTotalTo resd 1 ; Temporary holder for TotalTimeout +KernelExtPtr resw 1 ; During search, final null pointer +CmdOptPtr resw 1 ; Pointer to first option on cmd line +KbdFlags resb 1 ; Check for keyboard escapes +FuncFlag resb 1 ; Escape sequences received from keyboard +KernelType resb 1 ; Kernel type, from vkernel, if known + + section .text +; +; Linux kernel loading code is common. +; +%include "runkernel.inc" + +; +; COMBOOT-loading code +; +%include "comboot.inc" +%include "com32.inc" +%include "cmdline.inc" + +; +; Boot sector loading code +; +%include "bootsect.inc" + +; +; Abort loading code +; +%include "abort.inc" + +; +; Hardware cleanup common code +; +%include "cleanup.inc" diff --git a/core/writehex.inc b/core/writehex.inc new file mode 100644 index 00000000..1dbe4ab3 --- /dev/null +++ b/core/writehex.inc @@ -0,0 +1,53 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; writehex.inc +;; +;; Write hexadecimal numbers to the console +;; + + section .text +; +; writehex[248]: Write a hex number in (AL, AX, EAX) to the console +; +writehex2: + pushfd + pushad + rol eax,24 + mov cx,2 + jmp short writehex_common +writehex4: + pushfd + pushad + rol eax,16 + mov cx,4 + jmp short writehex_common +writehex8: + pushfd + pushad + mov cx,8 +writehex_common: +.loop: rol eax,4 + push eax + and al,0Fh + cmp al,10 + jae .high +.low: add al,'0' + jmp short .ischar +.high: add al,'A'-10 +.ischar: call writechr + pop eax + loop .loop + popad + popfd + ret diff --git a/core/writestr.inc b/core/writestr.inc new file mode 100644 index 00000000..04ad67a6 --- /dev/null +++ b/core/writestr.inc @@ -0,0 +1,47 @@ +;; ----------------------------------------------------------------------- +;; +;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +;; +;; 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, Inc., 53 Temple Place Ste 330, +;; Boston MA 02111-1307, USA; either version 2 of the License, or +;; (at your option) any later version; incorporated herein by reference. +;; +;; ----------------------------------------------------------------------- + +;; +;; writestr.inc +;; +;; Code to write a simple string. +;; + + section .text +; +; crlf: Print a newline +; +crlf: push ax + mov al,CR + call writechr + mov al,LF + call writechr + pop ax + ret + +; +; cwritestr: write a null-terminated string to the console, saving +; registers on entry. +; +; Note: writestr and cwritestr are distinct in SYSLINUX (only) +; +cwritestr: + pushfd + pushad +.top: lodsb + and al,al + jz .end + call writechr + jmp short .top +.end: popad + popfd + ret |