Add qemu 2.4.0
[kvmfornfv.git] / qemu / pc-bios / optionrom / linuxboot.S
diff --git a/qemu/pc-bios/optionrom/linuxboot.S b/qemu/pc-bios/optionrom/linuxboot.S
new file mode 100644 (file)
index 0000000..ba821ab
--- /dev/null
@@ -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