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