boot the kernel

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
diff --git a/Makefile b/Makefile
index fa43371..4e41822 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,7 @@
 obj16-y = e820.o int10.o int15.o
 obj-y = $(obj16-y) entry.o main.o string.o printf.o cstart.o fw_cfg.o
+obj-y += linuxboot.o
+
 all-y = bios.bin
 
 CFLAGS := -O2 -Wall -g
diff --git a/cstart.S b/cstart.S
index 5ca61c8..cef3950 100644
--- a/cstart.S
+++ b/cstart.S
@@ -25,6 +25,7 @@
         .quad 0
         .quad 0x00cf9b000000ffff // flat 32-bit code segment
         .quad 0x00cf93000000ffff // flat 32-bit data segment
+        .quad 0x008f9b0f0000ffff // 16-bit code segment at 0xF0000
 gdt32_end:
 
 gdt32_descr:
diff --git a/include/bios.h b/include/bios.h
index bd7f77b..dfc20af 100644
--- a/include/bios.h
+++ b/include/bios.h
@@ -36,6 +36,8 @@
 extern void bios_int10(void);
 extern void bios_int15(void);
 
+extern void boot_linux(void);
+
 extern struct e820map e820;
 
 #define ARRAY_SIZE(x)	(sizeof(x) / sizeof(x[0]))
diff --git a/linuxboot.c b/linuxboot.c
new file mode 100644
index 0000000..51bfba3
--- /dev/null
+++ b/linuxboot.c
@@ -0,0 +1,57 @@
+#include "bios.h"
+#include "ioport.h"
+#include "fw_cfg.h"
+
+static void *_fw_cfg_read_blob(int faddr, int fsize, int fdata)
+{
+	void *addr;
+	int length;
+
+	fw_cfg_select(faddr);
+	addr = (void *)fw_cfg_readl_le();
+	fw_cfg_select(fsize);
+	length = fw_cfg_readl_le();
+	fw_cfg_select(fdata);
+	fw_cfg_read(addr, length);
+	return addr;
+}
+
+/* BX = address of data block
+ * DX = cmdline_addr-setup_addr-16
+ */
+asm("pm16_boot_linux:"
+	    ".code16;"
+	    "mov $0, %eax; mov %eax, %cr0;"
+	    "ljmpl $0xf000, $(1f - 0xf0000); 1:"
+	    "mov %bx, %ds; mov %bx, %es;"
+	    "mov %bx, %fs; mov %bx, %gs; mov %bx, %ss;"
+	    "mov %dx, %sp;"
+	    "add $0x20, %bx; pushw %bx;"    // push CS
+	    "xor %eax, %eax; pushw %ax;"    // push IP
+	    "xor %ebx, %ebx;"
+	    "xor %ecx, %ecx;"
+	    "xor %edx, %edx;"
+	    "xor %edi, %edi;"
+	    "xor %ebp, %ebp;"
+	    "lret;"
+	    ".code32");
+
+void boot_linux(void)
+{
+	void *setup_addr, *cmdline_addr;
+
+#define fw_cfg_read_blob(f) \
+	_fw_cfg_read_blob(f##_ADDR, f##_SIZE, f##_DATA)
+
+	setup_addr = fw_cfg_read_blob(FW_CFG_SETUP);
+	cmdline_addr = fw_cfg_read_blob(FW_CFG_CMDLINE);
+	fw_cfg_read_blob(FW_CFG_INITRD);
+	fw_cfg_read_blob(FW_CFG_KERNEL);
+
+	asm volatile(
+	    "ljmp $0x18, $pm16_boot_linux - 0xf0000"
+	    : :
+	    "b" (((uintptr_t) setup_addr) >> 4),
+	    "d" (cmdline_addr - setup_addr - 16));
+        panic();
+}
diff --git a/main.c b/main.c
index 2378abb..e954d75 100644
--- a/main.c
+++ b/main.c
@@ -104,6 +104,6 @@
 	// extract_smbios();
 	// extract_kernel();
 	// make_bios_readonly();
-	// boot_linux();
+	boot_linux();
 	panic();
 }