blob: 98d826f0d915a4191921990225cd7a29cb26c317 [file] [log] [blame]
;; -----------------------------------------------------------------------
;;
;; 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:
and dx,dx
jnz kernel_sane
cmp ax,1024 ; Bootsect + 1 setup sect
jb kernel_corrupt
kernel_sane: push ax
push dx
push si
mov si,loading_msg
call cwritestr
;
; Now start transferring the kernel
;
push word real_mode_seg
pop es
movzx eax,ax ; Fix this by using a 32-bit
shl edx,16 ; register for the kernel size
or eax,edx
mov [KernelSize],eax
add eax,SECTOR_SIZE-1
shr eax,SECTOR_SHIFT
mov [KernelSects],eax ; Total sectors in kernel
;
; Now, if we transfer these straight, we'll hit 64K boundaries. Hence we
; have to see if we're loading more than 64K, and if so, load it step by
; step.
;
;
; 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).
; If we have larger than 32K clusters, yes, we're hosed.
;
call abort_check ; Check for abort key
mov ecx,8000h >> SECTOR_SHIFT ; Half a moby (32K)
cmp ecx,[KernelSects]
jna .normalkernel
mov ecx,[KernelSects]
.normalkernel:
sub [KernelSects],ecx
xor bx,bx
pop si ; Cluster pointer on stack
call getfssec
cmp word [es:bs_bootsign],0AA55h
jne kernel_corrupt ; Boot sec signature missing
;
; Save the cluster pointer for later...
;
push si
;
; Initialize our end of memory pointer
;
mov eax,[HighMemRsvd]
xor ax,ax ; Align to a 64K boundary
mov [MyHighMemSize],eax
;
; 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 ; Save 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 ; Restore 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
;
; 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:
mov [SetupSecs],ax
mov si,KernelCName ; Print kernel name part of
call cwritestr ; "Loading" message
mov si,dotdot_msg ; Print dots
call cwritestr
mov eax,[MyHighMemSize]
sub eax,100000h ; Load address
cmp eax,[KernelSize]
jb no_high_mem ; Not enough high memory
;
; Move the stuff beyond the setup code to high memory at 100000h
;
movzx esi,word [SetupSecs] ; Setup sectors
inc si ; plus 1 boot sector
shl si,9 ; Convert to bytes
mov ecx,8000h ; 32K
sub ecx,esi ; Number of bytes to copy
push ecx
add esi,(real_mode_seg << 4) ; Pointer to source
mov edi,100000h ; Copy to address 100000h
call bcopy ; Transfer to high memory
; On exit EDI -> where to load the rest
mov si,dot_msg ; Progress report
call cwritestr
call abort_check
pop ecx ; Number of bytes in the initial portion
pop si ; Restore file handle/cluster pointer
mov eax,[KernelSize]
sub eax,8000h ; Amount of kernel not yet loaded
jbe high_load_done ; Zero left (tiny kernel)
xor dx,dx ; No padding needed
mov bx,dot_pause ; Print dots...
call load_high ; Copy the file
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
;
; 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:
cmp word [InitRDPtr],0
jz nk_noinitrd
call parse_load_initrd
nk_noinitrd:
;
; Abandon hope, ye that enter here! We do no longer permit aborts.
;
call abort_check ; Last chance!!
mov si,ready_msg
call cwritestr
call vgaclearmode ; We can't trust ourselves after this
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
;
; 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:
cli ; In case of hooked interrupts
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
jmp in_proper_place
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,4095 ; 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
cmp dx,0200h
jb .nomovesize
mov [es:su_movesize],di ; Tell the kernel what to move
.nomovesize:
test byte [LoadFlags],LOAD_HIGH
jnz in_proper_place ; If high load, we're done
;
; Loading low; we can't assume it's safe to run in place.
;
; Copy real_mode stuff up to 90000h
;
mov ax,9000h
mov es,ax
mov cx,di ; == su_movesize (from above)
add cx,3 ; Round up
shr cx,2 ; Convert to dwords
xor si,si
xor di,di
fs rep movsd ; Copy setup + boot sector
;
; Some kernels in the 1.2 ballpark but pre-bzImage have more than 4
; setup sectors, but the boot protocol had not yet been defined. They
; rely on a signature to figure out if they need to copy stuff from
; the "protected mode" kernel area. Unfortunately, we used that area
; as a transfer buffer, so it's going to find the signature there.
; Hence, zero the low 32K beyond the setup area.
;
mov di,[SetupSecs]
inc di ; Setup + boot sector
mov cx,32768/512 ; Sectors/32K
sub cx,di ; Remaining sectors
shl di,9 ; Sectors -> bytes
shl cx,7 ; Sectors -> dwords
xor eax,eax
rep stosd ; Clear region
;
; Copy the kernel down to the "low" location (the kernel will then
; move itself again, sigh.)
;
mov ecx,[KernelSize]
mov esi,100000h
mov edi,10000h
call bcopy
;
; Now everything is where it needs to be...
;
; When we get here, es points to the final segment, either
; 9000h or real_mode_seg
;
in_proper_place:
;
; 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 the disk table to high memory, then re-initialize the floppy
; controller
;
%if IS_SYSLINUX || IS_MDSLINUX
lgs si,[cs:fdctab]
mov di,[cs:HeapEnd]
mov cx,6
gs rep movsw
mov [cs:fdctab],word linux_fdctab ; Save new floppy tab pos
mov [cs:fdctab+2],es
%endif
call cleanup_hardware
;
; If we're debugging, wait for a keypress so we can read any debug messages
;
%ifdef debug
xor ax,ax
int 16h
%endif
;
; Set up segment registers and the Linux real-mode stack
; Note: es == the real mode segment
;
cli
mov bx,es
mov ds,bx
mov fs,bx
mov gs,bx
mov ss,bx
mov sp,strict word linux_stack
; Point HeapEnd to the immediate of the instruction above
HeapEnd equ $-2 ; Self-modifying code! Fun!
;
; 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 bx,020h
push bx
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 read_kernel
;
; parse_load_initrd
;
; Parse an initrd= option and load the initrds. Note that we load
; from the high end of memory first, so we parse this option from
; left to right.
;
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 si,[cs:InitRDPtr]
.find_end:
lodsb
cmp al,' '
ja .find_end
; Now SI points to one character beyond the
; byte that ended this option.
.get_chunk:
dec si
; DS:SI points to a termination byte
xor ax,ax
xchg al,[si] ; Zero-terminate
push si ; Save ending byte address
push ax ; Save ending byte
.find_start:
dec si
cmp si,[cs:InitRDPtr]
je .got_start
cmp byte [si],','
jne .find_start
; It's a comma byte
inc si
.got_start:
push si
mov di,InitRD ; Target buffer for mangled name
call mangle_name
call loadinitrd
pop si
pop ax
pop di
mov [di],al ; Restore ending byte
cmp si,[cs:InitRDPtr]
ja .get_chunk
pop ds
pop es
ret
;
; Load RAM disk into high memory
;
; Input: InitRD - set to the mangled name of the initrd
;
loadinitrd:
push ds
push es
mov ax,cs ; CS == DS == ES
mov ds,ax
mov es,ax
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
jz .notthere
mov cx,dx
shl ecx,16
mov cx,ax ; ECX <- ram disk length
mov ax,real_mode_seg
mov es,ax
push ecx ; Bytes to load
mov edx,[MyHighMemSize] ; End of memory
dec edx
mov eax,[RamdiskMax] ; Highest address allowed by kernel
cmp edx,eax
jna .memsize_ok
mov edx,eax ; Adjust to fit inside limit
.memsize_ok:
inc edx
and dx,0F000h ; Round down to 4K boundary
sub edx,ecx ; Subtract size of ramdisk
and dx,0F000h ; Round down to 4K boundary
cmp edx,[KernelEnd] ; Are we hitting the kernel image?
jb no_high_mem
cmp dword [es:su_ramdisklen],0
je .highest
; The total length has to include the padding between
; different ramdisk files, so consider "the length" the
; total amount we're about to adjust the base pointer.
mov ecx,[es:su_ramdiskat]
sub ecx,edx
.highest:
add [es:su_ramdisklen],ecx
mov [es:su_ramdiskat],edx ; Load address
mov edi,edx ; initrd load address
dec edx ; Note: RamdiskMax is addr-1
mov [RamdiskMax],edx ; Next initrd loaded here
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
pop eax ; Bytes to load
mov dx,0FFFh ; Pad to page
mov bx,dot_pause ; Print dots...
call load_high ; Load the file
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
err_nohighmem db 'Not enough memory to load specified kernel.', CR, LF, 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
CmdLineLen resw 1 ; Length of command line including null
SetupSecs resw 1 ; Number of setup sectors
InitRDPtr resw 1 ; Pointer to initrd= option in command line
KernelVersion resw 1 ; Kernel protocol version
LoadFlags resb 1 ; Loadflags from kernel