;; -----------------------------------------------------------------------
;;
;; Copyright 1994-2009 H. Peter Anvin - All Rights Reserved
;; Copyright 2009 Intel Corporation; author: H. Peter Anvin
;;
;; 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_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 ; file pointer
;
; 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 abort_check ; Check for abort key
mov cx,8000h >> SECTOR_SHIFT ; Half a moby (32K)
xor bx,bx
pop si ; 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 ; 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
call do_ip_append ; Handle IPAppend
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.
;
parse_cmdline:
mov di,cmd_line_here
.skipspace: mov al,[es:di]
inc di
.skipspace_loaded:
and al,al
jz cmdline_end
cmp al,' '
jbe .skipspace
dec di
; ES:DI now points to the beginning of an option
mov si,options_list
.next_opt:
movzx cx,byte [si]
jcxz .skip_opt
push di
inc si
repe cmpsb
jne .no_match
; This either needs to have been an option with parameter,
; or be followed by EOL/whitespace
mov ax,[es:di-1] ; AL = last chr; AH = following
cmp al,'='
je .is_match
cmp ah,' '
ja .no_match
.is_match:
pop ax ; Drop option pointer on stack
call [si]
.skip_opt:
mov al,[es:di]
inc di
cmp al,' '
ja .skip_opt
jmp .skipspace_loaded
.no_match:
pop di
add si,cx ; Skip remaining bytes
inc si ; Skip function pointer
inc si
jmp .next_opt
opt_vga:
mov eax,[es:di-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_esdi ; vga=
jc .skip ; Not an integer
.vc0: mov [es:bs_vidmode],bx ; Set video mode
.skip:
ret
opt_mem:
call parseint_esdi
jc .skip
%if HIGHMEM_SLOP != 0
sub ebx,HIGHMEM_SLOP
%endif
mov [MyHighMemSize],ebx
.skip:
ret
opt_quiet:
mov byte [QuietBoot],1
ret
%if IS_PXELINUX
opt_keeppxe:
or byte [KeepPXE],1 ; KeepPXE set by command line
ret
%endif
opt_initrd:
mov ax,di
cmp byte [es:di],' '
ja .have_initrd
xor ax,ax
.have_initrd:
mov [InitRDPtr],ax
ret
;
; After command line parsing...
;
cmdline_end:
sub di,cmd_line_here
mov [CmdLineLen],di ; 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
any_kernel:
mov si,loading_msg
call writestr_qchk
mov si,KernelCName ; Print kernel name part of
call writestr_qchk ; "Loading" message
;
; 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 ; 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 writestr_qchk
;
; 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.
;
push di
mov di,[SetupSecs]
shl di,9
xor eax,eax
mov cx,cmd_line_here
sub cx,di
shr cx,2
rep stosd
pop di
;
; 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:
; Cap the ramdisk memory range if appropriate
mov eax,[RamdiskMax]
cmp eax,[MyHighMemSize]
ja .ok
mov [MyHighMemSize],eax
.ok:
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 writestr_qchk
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 dword run_linux_kernel
push cx ; Length of descriptor list
; 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
; Note: we round to a page boundary twice here. The first
; time it is to make sure we don't use any fractional page
; which may be valid RAM but which will be ignored by the
; kernel (and therefore is inaccessible.) The second time
; it is to make sure we start out on page boundary.
mov edx,[cs:InitRDEnd]
sub edx,[cs:InitRDStart]
mov [su_ramdisklen],edx
mov eax,[cs:MyHighMemSize]
and ax,0F000h ; Round to a page boundary
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 writestr_qchk
mov si,InitRDCName ; Write ramdisk name
call writestr_qchk
mov si,dotdot_msg ; Write dots
call writestr_qchk
pop si
.li_skip_echo:
mov dx,3
mov bx,dot_pause
call load_high
mov [InitRDEnd],ebx
pop es
pop ds
ret
.notthere:
mov si,err_noinitrd
call writestr
mov si,InitRDCName
call writestr
mov si,crlf_msg
jmp abort_load
;
; writestr_qchk: writestr, except allows output to be suppressed
; assumes CS == DS
;
writestr_qchk:
test byte [QuietBoot],01h
jz writestr
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
;
; Command line options we'd like to take a look at
;
%macro cmd_opt 2
%strlen cmd_opt_len %1
db cmd_opt_len
db %1
dw %2
%endmacro
options_list:
cmd_opt "vga=", opt_vga
cmd_opt "mem=", opt_mem
cmd_opt "quiet", opt_quiet
str_initrd equ $+1 ; Pointer to "initrd=" in memory
cmd_opt "initrd=", opt_initrd
%if IS_PXELINUX
cmd_opt "keeppxe", opt_keeppxe
%endif
db 0
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)
KernelVersion resw 1 ; Kernel protocol version
;
; These are derived from the command-line parser
;
InitRDPtr resw 1 ; Pointer to initrd= option in command line
LoadFlags resb 1 ; Loadflags from kernel
QuietBoot resb 1 ; Set if a quiet boot is requested