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