;; -----------------------------------------------------------------------
;;
;;   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.
;;
;; -----------------------------------------------------------------------

;
; 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:
		test byte [KbdFlags],5Bh	; Shift Alt Caps Scroll
		jnz enter_command
		cmp word [ForcePrompt],0	; Force prompt?
		jz auto_boot
		cmp word [DefaultLevel],1	; Active UI statement?
		ja auto_boot

enter_command:
		cmp word [NoEscape],0		; If NOESCAPE, no prompt,
		jne auto_boot			; always run default cmd

		mov si,boot_prompt
		call writestr

		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,09h			; Tab
		je display_labels
		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 writestr
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

display_labels:
		cmp word [NoComplete],0		; Label completion enabled?
		jne get_char_2
		push di				; Save pointer
		mov cx,di
		sub cx,command_line
		call crlf
		mov esi,[HighMemSize]		; Start from top of memory
.scan:
		cmp esi,[VKernelEnd]
		jbe .not_vk

		push cx				; save command line size

		mov edi,VKernelBuf
		call rllunpack
		; ESI updated on return

		sub di,cx			; Return to beginning of buf
		pop cx				; restore command line size
		push si				; save SI
		cmp cx,0
		jz .print
		push di
		push cx
		mov si,command_line
		es repe cmpsb
		pop cx
		pop di
		jne .next
.print:
		mov al,' '
		call writechr

		mov si,di
		call writestr
.next:
		pop si				; restore SI
		jmp .scan
.not_vk:
		call crlf
		jmp fk_wrcmd

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 writestr
%ifdef HAVE_BIOSNAME
		mov si,[BIOSName]
		call writestr
%endif
		mov si,copyright_str
		call writestr

		; ... 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 writestr
		pop di				; Command line write pointer
		push di
		mov byte [di],0			; Null-terminate command line
		mov si,command_line
		call writestr			; 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 writestr
		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 edi,VKernelBuf
		call rllunpack
		; ESI updated on return

		sub di,cx			; Return to beginning of buf
		push si
		mov si,command_line
.loop:
		lodsb
		cmp al,' '
		jbe .done
		scasb
		je .loop
.nomatch:
		pop si
		jmp .scan
.done:
		cmp byte [di],0			; Must match end of string
		jne .nomatch
		pop si

;
; 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?
		cmp al,VK_LOCALBOOT		; al == KernelType
		mov ax,[VKernelBuf+vk_rname]	; Possible localboot type
		je local_boot
%endif
		jmp get_kernel

.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
		cmp byte [di],' '
		jbe bad_kernel			; Missing kernel name
		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 writestr
		pop si				; KernelCName
                call writestr
                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
		;
		; Common initialization for all kernel types
		;
		xor ax,ax
		mov [InitRDPtr],ax
		mov [QuietBoot],al
%if IS_PXELINUX
		mov [KeepPXE],al
%endif

		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 writestr
		jmp enter_command

%if IS_SYSLINUX
		; 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


		alignz 2
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"
