]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
kvm, bios: Rework BIOS setup
authorCyrill Gorcunov <gorcunov@gmail.com>
Thu, 8 Jul 2010 15:00:07 +0000 (19:00 +0400)
committerPekka Enberg <penberg@cs.helsinki.fi>
Thu, 8 Jul 2010 15:47:55 +0000 (18:47 +0300)
This is a really huge rework of our primitive BIOS emulator.

The initial idea was to implement stack management in BIOS
irq handlers. In turn it ends up in re-factoring of all BIOS
code.

Some code details
-----------------

1) BIOS stack is placed at predefined constant memory address.
2) BIOS stack is only 64 bytes deep. It must be enough even for
   future irq handlers.
3) To be able to place irq handlers at almost arbitrary place of
   guest memory their start address must be 16 byte aligned.
   This makes easy to compute irq routines local variables addresses.
4) To eliminate address relocations in irq handlers code we use a
   special loader script.

TODO
----

- Still needs e820 map implements, int15 is dummy at moment

P.S. Thanks H. Peter Anvin for advices.

[ penberg@cs.helsinki.fi: cleanups ]
Signed-off-by: Cyrill Gorcunov <gorcunov@gmail.com>
Signed-off-by: Pekka Enberg <penberg@cs.helsinki.fi>
15 files changed:
tools/kvm/Makefile
tools/kvm/bios.c [new file with mode: 0644]
tools/kvm/bios/bios-strip.ld.S [new file with mode: 0644]
tools/kvm/bios/int10-real.S
tools/kvm/bios/int10.S
tools/kvm/bios/int15-real.S [new file with mode: 0644]
tools/kvm/bios/int15.S [new file with mode: 0644]
tools/kvm/bios/intfake.S
tools/kvm/bios/local.S [new file with mode: 0644]
tools/kvm/bios/macro.S [new file with mode: 0644]
tools/kvm/include/kvm/bios-export.h
tools/kvm/include/kvm/bios.h
tools/kvm/include/kvm/interrupt.h
tools/kvm/include/kvm/kvm.h
tools/kvm/kvm.c

index ab825c6b07d06a646404308ad8144f4e7a85f4fd..a6f73f888bb5c1d3246516dd79dda37745eb6752 100644 (file)
@@ -17,13 +17,16 @@ OBJS        += kvm.o
 OBJS   += main.o
 OBJS   += mmio.o
 OBJS   += util.o
+OBJS   += bios.o
 OBJS   += bios/intfake.o
+OBJS   += bios/int10.o
+OBJS   += bios/int15.o
 
 uname_M      := $(shell uname -m | sed -e s/i.86/i386/)
 ifeq ($(uname_M),i386)
-DEFINES      += -DCONFIG_X86_32
+       DEFINES      += -DCONFIG_X86_32
 ifeq ($(uname_M),x86_64)
-DEFINES      += -DCONFIG_X86_64
+       DEFINES      += -DCONFIG_X86_64
 endif
 endif
 
@@ -62,13 +65,36 @@ $(OBJS):
 #
 # BIOS assembly weirdness
 #
+BIOS_CFLAGS += -m32
+BIOS_CFLAGS += -march=i386
+BIOS_CFLAGS += -mregparm=3
 bios/intfake.o: bios/intfake.S bios/intfake-real.S
        $(E) "  CC      " $@
-       $(Q) $(CC) $(CFLAGS) -c bios/intfake-real.S -o bios/intfake-real.o
+       $(Q) $(CC) $(CFLAGS) $(BIOS_CFLAGS) -c -s bios/intfake-real.S -o bios/intfake-real.o
+       $(E) "  LD      " $@
+       $(Q) ld -T bios/bios-strip.ld.S -o bios/intfake-real.bin.elf bios/intfake-real.o
        $(E) "  OBJCOPY " $@
-       $(Q) objcopy -O binary -j .text bios/intfake-real.o bios/intfake-real.bin
+       $(Q) objcopy -O binary -j .text bios/intfake-real.bin.elf bios/intfake-real.bin
        $(Q) $(CC) $(CFLAGS) -c bios/intfake.S -o bios/intfake.o
 
+bios/int10.o: bios/int10.S bios/int10-real.S
+       $(E) "  CC      " $@
+       $(Q) $(CC) $(CFLAGS) $(BIOS_CFLAGS) -c -s bios/int10-real.S -o bios/int10-real.o
+       $(E) "  LD      " $@
+       $(Q) ld -T bios/bios-strip.ld.S -o bios/int10-real.bin.elf bios/int10-real.o
+       $(E) "  OBJCOPY " $@
+       $(Q) objcopy -O binary -j .text bios/int10-real.bin.elf bios/int10-real.bin
+       $(Q) $(CC) $(CFLAGS) -c bios/int10.S -o bios/int10.o
+
+bios/int15.o: bios/int10.S bios/int15-real.S
+       $(E) "  CC      " $@
+       $(Q) $(CC) $(CFLAGS) $(BIOS_CFLAGS) -c -s bios/int15-real.S -o bios/int15-real.o
+       $(E) "  LD      " $@
+       $(Q) ld -T bios/bios-strip.ld.S -o bios/int15-real.bin.elf bios/int15-real.o
+       $(E) "  OBJCOPY " $@
+       $(Q) objcopy -O binary -j .text bios/int15-real.bin.elf bios/int15-real.bin
+       $(Q) $(CC) $(CFLAGS) -c bios/int15.S -o bios/int15.o
+
 check: $(PROGRAM)
        $(MAKE) -C tests
        ./$(PROGRAM) tests/pit/tick.bin
@@ -77,6 +103,7 @@ check: $(PROGRAM)
 clean:
        $(E) "  CLEAN"
        $(Q) rm -f bios/*.bin
+       $(Q) rm -f bios/*.elf
        $(Q) rm -f bios/*.o
        $(Q) rm -f $(OBJS) $(PROGRAM)
 .PHONY: clean
diff --git a/tools/kvm/bios.c b/tools/kvm/bios.c
new file mode 100644 (file)
index 0000000..aef4402
--- /dev/null
@@ -0,0 +1,58 @@
+#include "kvm/kvm.h"
+#include "kvm/interrupt.h"
+#include "kvm/util.h"
+
+#include <string.h>
+
+static void bios_setup_irq_handler(struct kvm *kvm, unsigned int address,
+                               unsigned int irq, void *handler, unsigned int size)
+{
+       struct real_intr_desc intr_desc;
+       void *p;
+
+       p = guest_flat_to_host(kvm, address);
+       memcpy(p, handler, size);
+       intr_desc = (struct real_intr_desc) {
+               .segment        = REAL_SEGMENT(address),
+               .offset         = REAL_OFFSET(address),
+       };
+       interrupt_table__set(&kvm->interrupt_table, &intr_desc, irq);
+}
+
+void setup_bios(struct kvm *kvm)
+{
+       unsigned long address = MB_BIOS_BEGIN;
+       struct real_intr_desc intr_desc;
+       void *p;
+
+       /*
+        * Setup a *fake* real mode vector table, it has only
+        * one real hadler which does just iret
+        */
+       address = BIOS_NEXT_IRQ_ADDR(address, 0);
+       p = guest_flat_to_host(kvm, address);
+       memcpy(p, bios_intfake, bios_intfake_size);
+       intr_desc = (struct real_intr_desc) {
+               .segment        = REAL_SEGMENT(address),
+               .offset         = REAL_OFFSET(address),
+       };
+       interrupt_table__setup(&kvm->interrupt_table, &intr_desc);
+
+       /*
+        * int 0x10
+        */
+       address = BIOS_NEXT_IRQ_ADDR(address, bios_intfake_size);
+       bios_setup_irq_handler(kvm, address, 0x10, bios_int10, bios_int10_size);
+
+       /*
+        * We don't have valid BIOS yet so we put one single memory
+        * region in e820 memory map
+        *
+        * int 0x15
+        */
+       address = BIOS_NEXT_IRQ_ADDR(address, bios_int10_size);
+       bios_setup_irq_handler(kvm, address, 0x15, bios_int15, bios_int15_size);
+
+       p = guest_flat_to_host(kvm, 0);
+       interrupt_table__copy(&kvm->interrupt_table, p, REAL_INTR_SIZE);
+}
diff --git a/tools/kvm/bios/bios-strip.ld.S b/tools/kvm/bios/bios-strip.ld.S
new file mode 100644 (file)
index 0000000..aed8a65
--- /dev/null
@@ -0,0 +1,17 @@
+OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
+OUTPUT_ARCH(i386)
+
+PHDRS {
+       text PT_LOAD FLAGS(5);          /* R_E */
+       data PT_LOAD FLAGS(7);          /* RWE */
+       user PT_LOAD FLAGS(5);          /* R_E */
+       percpu PT_LOAD FLAGS(6);        /* RW_ */
+       init PT_LOAD FLAGS(7);          /* RWE */
+       note PT_NOTE FLAGS(0);          /* ___ */
+}
+
+SECTIONS {
+       . = 0;
+       .text : { *(.text) } :text = 0x9090
+}
+
index 279392074b7affced68384025371580dd96ed45b..58c3c09be404387a4f4b0999dde15a6423e41060 100644 (file)
@@ -1,44 +1,14 @@
 /*
- * IRQ 0x10 handler
+ * IRQ 0x10 handler - output in video memory
  */
 
 #include <kvm/bios.h>
 #include <kvm/assembly.h>
 
-/*
- * NOTES:
- * ======
- * 1) The code is supposed to enter with CS somewhere in BDA_START area so prepare real mode
- *    interrupt table properly (if you screw it up -- there is no way to restore
- *    anything, the code is NOT PORTABLE by any means, it's BIOS after all)
- *
- * 2) We switch to own stack which is BIOS_STACK_SIZE bytes long saving all caller's
- *    data we clobber. The stack is not that deep so mask interrupts at entry and dont
- *    use 'push' too much
- */
-
        .org 0
        .code16gcc
 
-.macro stack_bios seg
-       mov %ss, %\seg:(ss_old)
-       mov %sp, %\seg:(sp_old)
-       mov %si, %\seg:(stack_clobber)
-       mov $BIOS_STACK_SEG, %si
-       mov %si, %ss
-       mov $BIOS_STACK_SIZE, %sp
-       mov %\seg:(stack_clobber), %si
-.endm
-
-.macro stack_norm seg
-       mov %\seg:(ss_old), %ss
-       mov %\seg:(sp_old), %sp
-.endm
-
-.macro opcode_switch ocode, label
-       test \label, %ah
-       je \label
-.endm
+#include "macro.S"
 
 /*
  * int 10 - video - write character and advance cursor (tty write)
  */
 ENTRY(___int10)
        cli
-       opcode_switch $0x0e, putchar
-       jmp out
+       test $0x0e, %ah
+       jne out
 
 /*
  * put char in AL at current cursor and
  * increment cursor position
  */
 putchar:
-       stack_bios cs
+       stack_swap
 
        push %fs
        push %bx
 
-       mov $VIDEO_BASE_SEG, %bx
+       mov $VGA_RAM_SEG, %bx
        mov %bx, %fs
        mov %cs:(cursor), %bx
        mov %al, %fs:(%bx)
        inc %bx
-       test $VIDEO_SIZE, %bx
+       test $VGA_PAGE_SIZE, %bx
        jb putchar_new
        xor %bx, %bx
 putchar_new:
@@ -78,20 +48,17 @@ putchar_new:
        pop %bx
        pop %fs
 
-       stack_norm cs
+       stack_restore
 out:
        sti
        IRET
 /*
  * private IRQ data
  */
-cursor:                .word 0
+cursor:                .long 0
 
 /*
  * must be last in this file
  */
-       __ALIGN
-ss_old:                .word 0
-sp_old:                .word 0
-stack_clobber: .word 0
+#include "local.S"
 ENTRY_END(___int10_end)
index 5cec37ca5913b10ff6ac255af057d8b8b42ee5df..06d5b66918ddea22a4f6bbea31051cdee6968fea 100644 (file)
@@ -7,6 +7,6 @@
 #else
        .code32
 #endif
-GLOBAL(int10)
+GLOBAL(bios_int10)
        .incbin "bios/int10-real.bin"
-GLOBAL(int10_end)
+GLOBAL(bios_int10_end)
diff --git a/tools/kvm/bios/int15-real.S b/tools/kvm/bios/int15-real.S
new file mode 100644 (file)
index 0000000..dc4f734
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * IRQ 0x15 handler - e820 memory map
+ */
+
+#include <kvm/bios.h>
+#include <kvm/assembly.h>
+
+       .org 0
+       .code16gcc
+
+#include "macro.S"
+
+ENTRY(___int15)
+       jmp out
+
+#      movl %cs, %eax
+#      movl %eax, %es
+#      movl $0x534D4150, %eax  # 'SMAP'
+#      movl $0, %ebx           # end of map
+#      movl $20, %ecx
+#      mov e820entry_start, %di
+#      mov $(e820entry_end - e820entry_start), %ecx
+
+out:
+       sti
+       IRET
+/*
+ * private IRQ data
+ */
+GLOBAL(e820entry_start)
+       e820entry_addr: .quad 0
+       e820entry_size: .quad 0
+       e820entry_type: .long 0
+GLOBAL(e820entry_end)
+
+/*
+ * must be last in this file
+ */
+#include "local.S"
+ENTRY_END(___int15_end)
diff --git a/tools/kvm/bios/int15.S b/tools/kvm/bios/int15.S
new file mode 100644 (file)
index 0000000..c7e17ff
--- /dev/null
@@ -0,0 +1,12 @@
+#include <kvm/bios.h>
+#include <kvm/assembly.h>
+
+       .org 0
+#ifdef CONFIG_X86_64
+       .code64
+#else
+       .code32
+#endif
+GLOBAL(bios_int15)
+       .incbin "bios/int15-real.bin"
+GLOBAL(bios_int15_end)
index d8c570a615e477777553b8c448a2deb54e5c2e4d..8be50b683250d7e0180eb3dd700a69ce0872dee4 100644 (file)
@@ -7,6 +7,6 @@
 #else
        .code32
 #endif
-GLOBAL(intfake)
+GLOBAL(bios_intfake)
        .incbin "bios/intfake-real.bin"
-GLOBAL(intfake_end)
+GLOBAL(bios_intfake_end)
diff --git a/tools/kvm/bios/local.S b/tools/kvm/bios/local.S
new file mode 100644 (file)
index 0000000..82a71f3
--- /dev/null
@@ -0,0 +1,7 @@
+/*
+ * Local variables for almost every BIOS irq handler
+ * Must be put somewhere inside irq handler body
+ */
+__CALLER_SS:           .int 0
+__CALLER_SP:           .int 0
+__CALLER_CLOBBER:      .int 0
diff --git a/tools/kvm/bios/macro.S b/tools/kvm/bios/macro.S
new file mode 100644 (file)
index 0000000..842a9c3
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * handy BIOS macros
+ */
+
+/*
+ * switch to BIOS stack
+ */
+.macro stack_swap
+       mov %ss, %cs:(__CALLER_SS)
+       mov %sp, %cs:(__CALLER_SP)
+       mov %dx, %cs:(__CALLER_CLOBBER)
+       mov $MB_BIOS_SS, %dx
+       mov %dx, %ss
+       mov $MB_BIOS_SP, %sp
+       mov %cs:(__CALLER_CLOBBER), %dx
+.endm
+
+/*
+ * restore the original stack
+ */
+.macro stack_restore
+       mov %cs:(__CALLER_SP), %sp
+       mov %cs:(__CALLER_SS), %ss
+.endm
+
index f76a5de7be5bc014b17020f06bf89fd093e28718..c312c5c9e0f32a70fa282db8144ec15b3f3f6850 100644 (file)
@@ -1,7 +1,21 @@
 #ifndef BIOS_EXPORT_H_
 #define BIOS_EXPORT_H_
 
-extern char intfake[0];
-extern char intfake_end[0];
+struct kvm;
+
+extern char bios_intfake[0];
+extern char bios_intfake_end[0];
+
+extern char bios_int10[0];
+extern char bios_int10_end[0];
+
+extern char bios_int15[0];
+extern char bios_int15_end[0];
+
+#define bios_intfake_size      (bios_intfake_end - bios_intfake)
+#define bios_int10_size                (bios_int10_end - bios_int10)
+#define bios_int15_size                (bios_int15_end - bios_int15)
+
+extern void setup_bios(struct kvm *kvm);
 
 #endif /* BIOS_EXPORT_H_ */
index b6bffb28cda70676c304a929aeb0f549ac8e2703..6767bd980f0590d2a3de8ea9079b5042c5b62b2f 100644 (file)
@@ -2,14 +2,60 @@
 #define BIOS_H_
 
 /*
- * Motherboard BIOS
+ * X86-32 Memory Map (typical)
+ *                                     start      end
+ * Real Mode Interrupt Vector Table    0x00000000 0x000003FF
+ * BDA area                            0x00000400 0x000004FF
+ * Conventional Low Memory             0x00000500 0x0009FBFF
+ * EBDA area                           0x0009FC00 0x0009FFFF
+ * VIDEO RAM                           0x000A0000 0x000BFFFF
+ * VIDEO ROM (BIOS)                    0x000C0000 0x000C7FFF
+ * Motherboard BIOS                    0x000F0000 0x000FFFFF
+ * Extended Memory                     0x00100000 0xFEBFFFFF
+ * Reserved (configs, ACPI, PnP, etc)  0xFEC00000 0xFFFFFFFF
  */
-#define KVM_BIOS_START         0xf0000
-#define KVM_BIOS_END           0xfffff
 
-#define REAL_INTR_BASE         0x0000
-#define REAL_INTR_VECTORS      256
+#define REAL_MODE_IVT_BEGIN            0x00000000
+#define REAL_MODE_IVT_END              0x000003ff
 
-#define ALIGN(x, a)            (((x)+((a)-1))&~((a)-1))
+#define MB_BIOS_BEGIN                  0x000f0000
+#define MB_BIOS_END                    0x000fffff
+
+#define VGA_RAM_BEGIN                  0x000a0000
+#define VGA_RAM_END                    0x000bffff
+
+/* we handle one page only */
+#define VGA_RAM_SEG                    (VGA_RAM_BEGIN >> 4)
+#define VGA_PAGE_SIZE                  0x007d0 /* 80x25 */
+
+/* real mode interrupt vector table */
+#define REAL_INTR_BASE                 REAL_MODE_IVT_BEGIN
+#define REAL_INTR_VECTORS              256
+
+/*
+ * BIOS stack must be at absolute predefined memory address
+ * We reserve 64 bytes for BIOS stack
+ */
+#define MB_BIOS_SS                     0xFFF7
+#define MB_BIOS_SP                     0x40
+
+#define E820_RAM       1
+#define E820_RESERVED  2
+#define E820_ACPI      3
+#define E820_NVS       4
+#define E820_UNUSABLE  5
+
+#define E820_MAP_OFFSET        64
+
+#define ALIGN(x, a)    \
+       (((x) + ((a) - 1)) & ~((a) - 1))
+
+/*
+ * note we use 16 bytes alignment which makes segment based
+ * addressing easy to compute, dont change it otherwise you
+ * may break local variables offsets in BIOS irq routines
+ */
+#define BIOS_NEXT_IRQ_ADDR(addr, size) \
+       ALIGN((addr + size + 1), 16)
 
 #endif /* BIOS_H_ */
index dbfdd926319f91e946799b164cc2bad56e04891b..0169bba3ef62c04564ad8d29bcff78210aa74629 100644 (file)
@@ -12,6 +12,7 @@ struct real_intr_desc {
 
 #define REAL_SEGMENT_SHIFT     4
 #define REAL_SEGMENT(addr)     ((addr) >> REAL_SEGMENT_SHIFT)
+#define REAL_OFFSET(addr)      ((addr) & ((1 << REAL_SEGMENT_SHIFT) - 1))
 #define REAL_INTR_SIZE         (REAL_INTR_VECTORS * sizeof(struct real_intr_desc))
 
 struct interrupt_table {
index 1ba844cc19879530163dbc2f02110bad3a9a666c..d7f4685bf7776a4b670a3fb03d2e823cdc24cb7b 100644 (file)
@@ -51,4 +51,26 @@ void kvm__dump_mem(struct kvm *self, unsigned long addr, unsigned long size);
 
 extern const char *kvm_exit_reasons[];
 
+static inline bool host_ptr_in_ram(struct kvm *self, void *p)
+{
+       return self->ram_start <= p && p < (self->ram_start + self->ram_size);
+}
+
+static inline uint32_t segment_to_flat(uint16_t selector, uint16_t offset)
+{
+       return ((uint32_t)selector << 4) + (uint32_t) offset;
+}
+
+static inline void *guest_flat_to_host(struct kvm *self, unsigned long offset)
+{
+       return self->ram_start + offset;
+}
+
+static inline void *guest_real_to_host(struct kvm *self, uint16_t selector, uint16_t offset)
+{
+       unsigned long flat = segment_to_flat(selector, offset);
+
+       return guest_flat_to_host(self, flat);
+}
+
 #endif /* KVM__KVM_H */
index 1b2dcd8b9dee8c1cf7872796ecb97a374a9713b7..a2bedcaf449f3d8590529aa3fe8fe25312304610 100644 (file)
@@ -70,28 +70,6 @@ struct {
        { DEFINE_KVM_EXT(KVM_CAP_EXT_CPUID) },
 };
 
-static inline bool host_ptr_in_ram(struct kvm *self, void *p)
-{
-       return self->ram_start <= p && p < (self->ram_start + self->ram_size);
-}
-
-static inline uint32_t segment_to_flat(uint16_t selector, uint16_t offset)
-{
-       return ((uint32_t)selector << 4) + (uint32_t) offset;
-}
-
-static inline void *guest_flat_to_host(struct kvm *self, unsigned long offset)
-{
-       return self->ram_start + offset;
-}
-
-static inline void *guest_real_to_host(struct kvm *self, uint16_t selector, uint16_t offset)
-{
-       unsigned long flat = segment_to_flat(selector, offset);
-
-       return guest_flat_to_host(self, flat);
-}
-
 static bool kvm__supports_extension(struct kvm *self, unsigned int extension)
 {
        int ret;
@@ -300,10 +278,8 @@ static const char *BZIMAGE_MAGIC   = "HdrS";
 static bool load_bzimage(struct kvm *self, int fd, const char *kernel_cmdline)
 {
        struct boot_params *kern_boot;
-       struct real_intr_desc intr;
        unsigned long setup_sects;
        struct boot_params boot;
-       unsigned int intr_addr;
        size_t cmdline_size;
        ssize_t setup_size;
        void *p;
@@ -371,20 +347,9 @@ static bool load_bzimage(struct kvm *self, int fd, const char *kernel_cmdline)
        self->boot_sp           = BOOT_LOADER_SP;
 
        /*
-        * Setup a *fake* real mode vector table, it has only
-        * one real hadler which does just iret
+        * Drum roll, BIOS is coming to live, oh dear...
         */
-       intr_addr = KVM_BIOS_START;
-       p = guest_flat_to_host(self, intr_addr);
-       memcpy(p, intfake, intfake_end - intfake);
-       intr = (struct real_intr_desc) {
-               .segment        = REAL_SEGMENT(intr_addr),
-               .offset         = 0,
-       };
-       interrupt_table__setup(&self->interrupt_table, &intr);
-
-       p = guest_flat_to_host(self, 0);
-       interrupt_table__copy(&self->interrupt_table, p, REAL_INTR_SIZE);
+       setup_bios(self);
 
        return true;
 }