diff options
Diffstat (limited to 'qemu/pc-bios/optionrom/linuxboot.S')
-rw-r--r-- | qemu/pc-bios/optionrom/linuxboot.S | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/qemu/pc-bios/optionrom/linuxboot.S b/qemu/pc-bios/optionrom/linuxboot.S new file mode 100644 index 000000000..ba821ab92 --- /dev/null +++ b/qemu/pc-bios/optionrom/linuxboot.S @@ -0,0 +1,195 @@ +/* + * Linux Boot Option ROM + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Copyright Novell Inc, 2009 + * Authors: Alexander Graf <agraf@suse.de> + * + * Based on code in hw/pc.c. + */ + +#include "optionrom.h" + +#define BOOT_ROM_PRODUCT "Linux loader" + +BOOT_ROM_START + +run_linuxboot: + + cli + cld + + jmp copy_kernel +boot_kernel: + + read_fw FW_CFG_SETUP_ADDR + + mov %eax, %ebx + shr $4, %ebx + + /* All segments contain real_addr */ + mov %bx, %ds + mov %bx, %es + mov %bx, %fs + mov %bx, %gs + mov %bx, %ss + + /* CX = CS we want to jump to */ + add $0x20, %bx + mov %bx, %cx + + /* SP = cmdline_addr-real_addr-16 */ + read_fw FW_CFG_CMDLINE_ADDR + mov %eax, %ebx + read_fw FW_CFG_SETUP_ADDR + sub %eax, %ebx + sub $16, %ebx + mov %ebx, %esp + + /* Build indirect lret descriptor */ + pushw %cx /* CS */ + xor %ax, %ax + pushw %ax /* IP = 0 */ + + /* Clear registers */ + xor %eax, %eax + xor %ebx, %ebx + xor %ecx, %ecx + xor %edx, %edx + xor %edi, %edi + xor %ebp, %ebp + + /* Jump to Linux */ + lret + + +copy_kernel: + /* Read info block in low memory (0x10000 or 0x90000) */ + read_fw FW_CFG_SETUP_ADDR + shr $4, %eax + mov %eax, %es + xor %edi, %edi + read_fw_blob_addr32_edi(FW_CFG_SETUP) + + cmpw $0x203, %es:0x206 // if protocol >= 0x203 + jae 1f // have initrd_max + movl $0x37ffffff, %es:0x22c // else assume 0x37ffffff +1: + + /* Check if using kernel-specified initrd address */ + read_fw FW_CFG_INITRD_ADDR + mov %eax, %edi // (load_kernel wants it in %edi) + read_fw FW_CFG_INITRD_SIZE // find end of initrd + add %edi, %eax + xor %es:0x22c, %eax // if it matches es:0x22c + and $-4096, %eax // (apart from padding for page) + jz load_kernel // then initrd is not at top + // of memory + + /* pc.c placed the initrd at end of memory. Compute a better + * initrd address based on e801 data. + */ + mov $0xe801, %ax + xor %cx, %cx + xor %dx, %dx + int $0x15 + + /* Output could be in AX/BX or CX/DX */ + or %cx, %cx + jnz 1f + or %dx, %dx + jnz 1f + mov %ax, %cx + mov %bx, %dx +1: + + or %dx, %dx + jnz 2f + addw $1024, %cx /* add 1 MB */ + movzwl %cx, %edi + shll $10, %edi /* convert to bytes */ + jmp 3f + +2: + addw $16777216 >> 16, %dx /* add 16 MB */ + movzwl %dx, %edi + shll $16, %edi /* convert to bytes */ + +3: + read_fw FW_CFG_INITRD_SIZE + subl %eax, %edi + andl $-4096, %edi /* EDI = start of initrd */ + movl %edi, %es:0x218 /* put it in the header */ + +load_kernel: + /* We need to load the kernel into memory we can't access in 16 bit + mode, so let's get into 32 bit mode, write the kernel and jump + back again. */ + + /* Reserve space on the stack for our GDT descriptor. */ + mov %esp, %ebp + sub $16, %esp + + /* Now create the GDT descriptor */ + movw $((3 * 8) - 1), -16(%bp) + mov %cs, %eax + movzwl %ax, %eax + shl $4, %eax + addl $gdt, %eax + movl %eax, -14(%bp) + + /* And load the GDT */ + data32 lgdt -16(%bp) + mov %ebp, %esp + + /* Get us to protected mode now */ + mov $1, %eax + mov %eax, %cr0 + + /* So we can set ES to a 32-bit segment */ + mov $0x10, %eax + mov %eax, %es + + /* We're now running in 16-bit CS, but 32-bit ES! */ + + /* Load kernel and initrd */ + read_fw_blob_addr32_edi(FW_CFG_INITRD) + read_fw_blob_addr32(FW_CFG_KERNEL) + read_fw_blob_addr32(FW_CFG_CMDLINE) + + /* And now jump into Linux! */ + mov $0, %eax + mov %eax, %cr0 + + /* ES = CS */ + mov %cs, %ax + mov %ax, %es + + jmp boot_kernel + +/* Variables */ + +.align 4, 0 +gdt: + /* 0x00 */ +.byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + + /* 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k) */ +.byte 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00 + + /* 0x10: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k) */ +.byte 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00 + +BOOT_ROM_END |