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>
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
#
# 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
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
--- /dev/null
+#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);
+}
--- /dev/null
+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
+}
+
/*
- * 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:
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)
#else
.code32
#endif
-GLOBAL(int10)
+GLOBAL(bios_int10)
.incbin "bios/int10-real.bin"
-GLOBAL(int10_end)
+GLOBAL(bios_int10_end)
--- /dev/null
+/*
+ * 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)
--- /dev/null
+#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)
#else
.code32
#endif
-GLOBAL(intfake)
+GLOBAL(bios_intfake)
.incbin "bios/intfake-real.bin"
-GLOBAL(intfake_end)
+GLOBAL(bios_intfake_end)
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
+
#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_ */
#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_ */
#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 {
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 */
{ 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;
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;
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;
}