;; -----------------------------------------------------------------------
;;
;;   Copyright 1994-2007 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
;
; Get the BIOS' idea of what the size of high memory is.
;
		call highmemsize
;
; 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:HighMemSize],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,[HighMemSize]
		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,[HighMemSize]		; 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
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
