Multiboot support v5

This patch implements support for Multiboot on x86 for -kernel.
Multiboot is a "new" approach to get rid of different bootloaders, providing
a unified interface for the kernel. It supports command line options and
kernel modules.

The two probably best known projects using multiboot are Xen and GNU Hurd.

This implementation should be mostly feature-complete. It is missing VBE
extensions, but as no system uses them currently it does not really hurt.

To use multiboot, specify the kernel as -kernel option. Modules should be given
as -initrd options, seperated by a comma (,). -append also works.

Please bear in mind that grub also does gzip decompression, which qemu does
not do yet. To run existing images, please ungzip them first.

The guest multiboot loader code is implemented as option rom using int 19.
Parts of the work are based on efforts by Rene Rebe, who originally ported
my code to int 19.

Signed-off-by: Alexander Graf <agraf@suse.de>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
This commit is contained in:
Alexander Graf 2009-06-29 15:37:39 +02:00 committed by Anthony Liguori
parent bf483392e6
commit f16408dfb0
2 changed files with 447 additions and 8 deletions

246
hw/pc.c
View file

@ -40,6 +40,9 @@
/* output Bochs bios info messages */
//#define DEBUG_BIOS
/* Show multiboot debug output */
//#define DEBUG_MULTIBOOT
#define BIOS_FILENAME "bios.bin"
#define VGABIOS_FILENAME "vgabios.bin"
#define VGABIOS_CIRRUS_FILENAME "vgabios-cirrus.bin"
@ -596,7 +599,218 @@ static long get_file_size(FILE *f)
return size;
}
static void load_linux(target_phys_addr_t option_rom,
#define MULTIBOOT_STRUCT_ADDR 0x9000
#if MULTIBOOT_STRUCT_ADDR > 0xf0000
#error multiboot struct needs to fit in 16 bit real mode
#endif
static int load_multiboot(void *fw_cfg,
FILE *f,
const char *kernel_filename,
const char *initrd_filename,
const char *kernel_cmdline,
uint8_t *header)
{
int i, t, is_multiboot = 0;
uint32_t flags = 0;
uint32_t mh_entry_addr;
uint32_t mh_load_addr;
uint32_t mb_kernel_size;
uint32_t mmap_addr = MULTIBOOT_STRUCT_ADDR;
uint32_t mb_bootinfo = MULTIBOOT_STRUCT_ADDR + 0x500;
uint32_t mb_cmdline = mb_bootinfo + 0x200;
uint32_t mb_mod_end;
/* Ok, let's see if it is a multiboot image.
The header is 12x32bit long, so the latest entry may be 8192 - 48. */
for (i = 0; i < (8192 - 48); i += 4) {
if (ldl_p(header+i) == 0x1BADB002) {
uint32_t checksum = ldl_p(header+i+8);
flags = ldl_p(header+i+4);
checksum += flags;
checksum += (uint32_t)0x1BADB002;
if (!checksum) {
is_multiboot = 1;
break;
}
}
}
if (!is_multiboot)
return 0; /* no multiboot */
#ifdef DEBUG_MULTIBOOT
fprintf(stderr, "qemu: I believe we found a multiboot image!\n");
#endif
if (flags & 0x00000004) { /* MULTIBOOT_HEADER_HAS_VBE */
fprintf(stderr, "qemu: multiboot knows VBE. we don't.\n");
}
if (!(flags & 0x00010000)) { /* MULTIBOOT_HEADER_HAS_ADDR */
uint64_t elf_entry;
int kernel_size;
fclose(f);
kernel_size = load_elf(kernel_filename, 0, &elf_entry, NULL, NULL);
if (kernel_size < 0) {
fprintf(stderr, "Error while loading elf kernel\n");
exit(1);
}
mh_load_addr = mh_entry_addr = elf_entry;
mb_kernel_size = kernel_size;
#ifdef DEBUG_MULTIBOOT
fprintf(stderr, "qemu: loading multiboot-elf kernel (%#x bytes) with entry %#zx\n",
mb_kernel_size, (size_t)mh_entry_addr);
#endif
} else {
/* Valid if mh_flags sets MULTIBOOT_HEADER_HAS_ADDR. */
uint32_t mh_header_addr = ldl_p(header+i+12);
mh_load_addr = ldl_p(header+i+16);
#ifdef DEBUG_MULTIBOOT
uint32_t mh_load_end_addr = ldl_p(header+i+20);
uint32_t mh_bss_end_addr = ldl_p(header+i+24);
#endif
uint32_t mb_kernel_text_offset = i - (mh_header_addr - mh_load_addr);
mh_entry_addr = ldl_p(header+i+28);
mb_kernel_size = get_file_size(f) - mb_kernel_text_offset;
/* Valid if mh_flags sets MULTIBOOT_HEADER_HAS_VBE.
uint32_t mh_mode_type = ldl_p(header+i+32);
uint32_t mh_width = ldl_p(header+i+36);
uint32_t mh_height = ldl_p(header+i+40);
uint32_t mh_depth = ldl_p(header+i+44); */
#ifdef DEBUG_MULTIBOOT
fprintf(stderr, "multiboot: mh_header_addr = %#x\n", mh_header_addr);
fprintf(stderr, "multiboot: mh_load_addr = %#x\n", mh_load_addr);
fprintf(stderr, "multiboot: mh_load_end_addr = %#x\n", mh_load_end_addr);
fprintf(stderr, "multiboot: mh_bss_end_addr = %#x\n", mh_bss_end_addr);
#endif
fseek(f, mb_kernel_text_offset, SEEK_SET);
#ifdef DEBUG_MULTIBOOT
fprintf(stderr, "qemu: loading multiboot kernel (%#x bytes) at %#x\n",
mb_kernel_size, mh_load_addr);
#endif
if (!fread_targphys_ok(mh_load_addr, mb_kernel_size, f)) {
fprintf(stderr, "qemu: read error on multiboot kernel '%s' (%#x)\n",
kernel_filename, mb_kernel_size);
exit(1);
}
fclose(f);
}
/* blob size is only the kernel for now */
mb_mod_end = mh_load_addr + mb_kernel_size;
/* load modules */
stl_phys(mb_bootinfo + 20, 0x0); /* mods_count */
if (initrd_filename) {
uint32_t mb_mod_info = mb_bootinfo + 0x100;
uint32_t mb_mod_cmdline = mb_bootinfo + 0x300;
uint32_t mb_mod_start = mh_load_addr;
uint32_t mb_mod_length = mb_kernel_size;
char *next_initrd;
char *next_space;
int mb_mod_count = 0;
do {
next_initrd = strchr(initrd_filename, ',');
if (next_initrd)
*next_initrd = '\0';
/* if a space comes after the module filename, treat everything
after that as parameters */
cpu_physical_memory_write(mb_mod_cmdline, (uint8_t*)initrd_filename,
strlen(initrd_filename) + 1);
stl_phys(mb_mod_info + 8, mb_mod_cmdline); /* string */
mb_mod_cmdline += strlen(initrd_filename) + 1;
if ((next_space = strchr(initrd_filename, ' ')))
*next_space = '\0';
#ifdef DEBUG_MULTIBOOT
printf("multiboot loading module: %s\n", initrd_filename);
#endif
f = fopen(initrd_filename, "rb");
if (f) {
mb_mod_start = (mb_mod_start + mb_mod_length + (TARGET_PAGE_SIZE - 1))
& (TARGET_PAGE_MASK);
mb_mod_length = get_file_size(f);
mb_mod_end = mb_mod_start + mb_mod_length;
if (!fread_targphys_ok(mb_mod_start, mb_mod_length, f)) {
fprintf(stderr, "qemu: read error on multiboot module '%s' (%#x)\n",
initrd_filename, mb_mod_length);
exit(1);
}
mb_mod_count++;
stl_phys(mb_mod_info + 0, mb_mod_start);
stl_phys(mb_mod_info + 4, mb_mod_start + mb_mod_length);
#ifdef DEBUG_MULTIBOOT
printf("mod_start: %#x\nmod_end: %#x\n", mb_mod_start,
mb_mod_start + mb_mod_length);
#endif
stl_phys(mb_mod_info + 12, 0x0); /* reserved */
}
initrd_filename = next_initrd+1;
mb_mod_info += 16;
} while (next_initrd);
stl_phys(mb_bootinfo + 20, mb_mod_count); /* mods_count */
stl_phys(mb_bootinfo + 24, mb_bootinfo + 0x100); /* mods_addr */
}
/* Make sure we're getting kernel + modules back after reset */
option_rom_setup_reset(mh_load_addr, mb_mod_end - mh_load_addr);
/* Commandline support */
stl_phys(mb_bootinfo + 16, mb_cmdline);
t = strlen(kernel_filename);
cpu_physical_memory_write(mb_cmdline, (uint8_t*)kernel_filename, t);
mb_cmdline += t;
stb_phys(mb_cmdline++, ' ');
t = strlen(kernel_cmdline) + 1;
cpu_physical_memory_write(mb_cmdline, (uint8_t*)kernel_cmdline, t);
/* the kernel is where we want it to be now */
#define MULTIBOOT_FLAGS_MEMORY (1 << 0)
#define MULTIBOOT_FLAGS_BOOT_DEVICE (1 << 1)
#define MULTIBOOT_FLAGS_CMDLINE (1 << 2)
#define MULTIBOOT_FLAGS_MODULES (1 << 3)
#define MULTIBOOT_FLAGS_MMAP (1 << 6)
stl_phys(mb_bootinfo, MULTIBOOT_FLAGS_MEMORY
| MULTIBOOT_FLAGS_BOOT_DEVICE
| MULTIBOOT_FLAGS_CMDLINE
| MULTIBOOT_FLAGS_MODULES
| MULTIBOOT_FLAGS_MMAP);
stl_phys(mb_bootinfo + 4, 640); /* mem_lower */
stl_phys(mb_bootinfo + 8, ram_size / 1024); /* mem_upper */
stl_phys(mb_bootinfo + 12, 0x8001ffff); /* XXX: use the -boot switch? */
stl_phys(mb_bootinfo + 48, mmap_addr); /* mmap_addr */
#ifdef DEBUG_MULTIBOOT
fprintf(stderr, "multiboot: mh_entry_addr = %#x\n", mh_entry_addr);
#endif
/* Pass variables to option rom */
fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_ADDR, mh_entry_addr);
fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_ADDR, mb_bootinfo);
fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_SIZE, mmap_addr);
/* Make sure we're getting the config space back after reset */
option_rom_setup_reset(mb_bootinfo, 0x500);
option_rom[nb_option_roms] = "multiboot.bin";
nb_option_roms++;
return 1; /* yes, we are multiboot */
}
static void load_linux(void *fw_cfg,
target_phys_addr_t option_rom,
const char *kernel_filename,
const char *initrd_filename,
const char *kernel_cmdline,
@ -608,7 +822,7 @@ static void load_linux(target_phys_addr_t option_rom,
uint16_t real_seg;
int setup_size, kernel_size, initrd_size = 0, cmdline_size;
uint32_t initrd_max;
uint8_t header[1024];
uint8_t header[8192];
target_phys_addr_t real_addr, prot_addr, cmdline_addr, initrd_addr = 0;
FILE *f, *fi;
@ -618,7 +832,8 @@ static void load_linux(target_phys_addr_t option_rom,
/* load the kernel header */
f = fopen(kernel_filename, "rb");
if (!f || !(kernel_size = get_file_size(f)) ||
fread(header, 1, 1024, f) != 1024) {
fread(header, 1, MIN(ARRAY_SIZE(header), kernel_size), f) !=
MIN(ARRAY_SIZE(header), kernel_size)) {
fprintf(stderr, "qemu: could not load kernel '%s'\n",
kernel_filename);
exit(1);
@ -630,8 +845,14 @@ static void load_linux(target_phys_addr_t option_rom,
#endif
if (ldl_p(header+0x202) == 0x53726448)
protocol = lduw_p(header+0x206);
else
else {
/* This looks like a multiboot kernel. If it is, let's stop
treating it like a Linux kernel. */
if (load_multiboot(fw_cfg, f, kernel_filename,
initrd_filename, kernel_cmdline, header))
return;
protocol = 0;
}
if (protocol < 0x200 || !(header[0x211] & 0x01)) {
/* Low kernel */
@ -721,16 +942,25 @@ static void load_linux(target_phys_addr_t option_rom,
}
/* store the finalized header and load the rest of the kernel */
cpu_physical_memory_write(real_addr, header, 1024);
cpu_physical_memory_write(real_addr, header, ARRAY_SIZE(header));
setup_size = header[0x1f1];
if (setup_size == 0)
setup_size = 4;
setup_size = (setup_size+1)*512;
kernel_size -= setup_size; /* Size of protected-mode code */
/* Size of protected-mode code */
kernel_size -= (setup_size > ARRAY_SIZE(header)) ? setup_size : ARRAY_SIZE(header);
if (!fread_targphys_ok(real_addr+1024, setup_size-1024, f) ||
/* In case we have read too much already, copy that over */
if (setup_size < ARRAY_SIZE(header)) {
cpu_physical_memory_write(prot_addr, header + setup_size, ARRAY_SIZE(header) - setup_size);
prot_addr += (ARRAY_SIZE(header) - setup_size);
setup_size = ARRAY_SIZE(header);
}
if (!fread_targphys_ok(real_addr + ARRAY_SIZE(header),
setup_size - ARRAY_SIZE(header), f) ||
!fread_targphys_ok(prot_addr, kernel_size, f)) {
fprintf(stderr, "qemu: read error on kernel '%s'\n",
kernel_filename);
@ -978,7 +1208,7 @@ static void pc_init1(ram_addr_t ram_size,
fw_cfg = bochs_bios_init();
if (linux_boot) {
load_linux(0xc0000 + oprom_area_size,
load_linux(fw_cfg, 0xc0000 + oprom_area_size,
kernel_filename, initrd_filename, kernel_cmdline, below_4g_mem_size);
oprom_area_size += 2048;
}

View file

@ -0,0 +1,209 @@
/*
* Multiboot 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, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright Novell Inc, 2009
* Authors: Alexander Graf <agraf@suse.de>
*/
#define NO_QEMU_PROTOS
#include "../../hw/fw_cfg.h"
#define BIOS_CFG_IOPORT_CFG 0x510
#define BIOS_CFG_IOPORT_DATA 0x511
#define MULTIBOOT_MAGIC 0x2badb002
/* Read a variable from the fw_cfg device.
Clobbers: %edx
Out: %eax */
.macro read_fw VAR
mov $\VAR, %ax
mov $BIOS_CFG_IOPORT_CFG, %dx
outw %ax, (%dx)
mov $BIOS_CFG_IOPORT_DATA, %dx
inb (%dx), %al
shl $8, %eax
inb (%dx), %al
shl $8, %eax
inb (%dx), %al
shl $8, %eax
inb (%dx), %al
bswap %eax
.endm
.code16
.text
.global _start
_start:
.short 0xaa55
.byte (_end - _start) / 512
push %eax
push %ds
/* setup ds so we can access the IVT */
xor %ax, %ax
mov %ax, %ds
/* save old int 19 */
mov (0x19*4), %eax
mov %eax, %cs:old_int19
/* install our int 19 handler */
movw $int19_handler, (0x19*4)
mov %cs, (0x19*4+2)
pop %ds
pop %eax
lret
int19_handler:
/* DS = CS */
movw %cs, %ax
movw %ax, %ds
/* fall through */
run_multiboot:
cli
cld
mov %cs, %eax
shl $0x4, %eax
/* fix the gdt descriptor to be PC relative */
mov (gdt_desc+2), %ebx
add %eax, %ebx
mov %ebx, (gdt_desc+2)
/* fix the prot mode indirect jump to be PC relative */
mov (prot_jump), %ebx
add %eax, %ebx
mov %ebx, (prot_jump)
/* FS = bootinfo_struct */
read_fw FW_CFG_INITRD_ADDR
shr $4, %eax
mov %ax, %fs
/* ES = mmap_addr */
read_fw FW_CFG_INITRD_SIZE
shr $4, %eax
mov %ax, %es
/* Initialize multiboot mmap structs using int 0x15(e820) */
xor %ebx, %ebx
/* mmap start after first size */
movl $4, %edi
mmap_loop:
/* entry size (mmap struct) & max buffer size (int15) */
movl $20, %ecx
/* store entry size */
movl %ecx, %es:-4(%edi)
/* e820 */
movl $0x0000e820, %eax
/* 'SMAP' magic */
movl $0x534d4150, %edx
int $0x15
mmap_check_entry:
/* last entry? then we're done */
jb mmap_done
and %bx, %bx
jz mmap_done
/* valid entry, so let's loop on */
mmap_store_entry:
/* %ax = entry_number * 24 */
mov $24, %ax
mul %bx
mov %ax, %di
movw %di, %fs:0x2c
/* %di = 4 + (entry_number * 24) */
add $4, %di
jmp mmap_loop
mmap_done:
real_to_prot:
/* Load the GDT before going into protected mode */
lgdt:
data32 lgdt %cs:gdt_desc
/* get us to protected mode now */
movl $1, %eax
movl %eax, %cr0
/* the LJMP sets CS for us and gets us to 32-bit */
ljmp:
data32 ljmp *%cs:prot_jump
prot_mode:
.code32
/* initialize all other segments */
movl $0x10, %eax
movl %eax, %ss
movl %eax, %ds
movl %eax, %es
movl %eax, %fs
movl %eax, %gs
/* Jump off to the kernel */
read_fw FW_CFG_KERNEL_ADDR
mov %eax, %ecx
/* EBX contains a pointer to the bootinfo struct */
read_fw FW_CFG_INITRD_ADDR
movl %eax, %ebx
/* EAX has to contain the magic */
movl $MULTIBOOT_MAGIC, %eax
ljmp2:
jmp *%ecx
/* Variables */
.align 4, 0
old_int19: .long 0
prot_jump: .long prot_mode
.short 8
.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
/* 0x18: code segment (base=0, limit=0x0ffff, type=16bit code exec/read/conf, DPL=0, 1b) */
.byte 0xff, 0xff, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00
/* 0x20: data segment (base=0, limit=0x0ffff, type=16bit data read/write, DPL=0, 1b) */
.byte 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00
gdt_desc:
.short (5 * 8) - 1
.long gdt
.align 512, 0
_end: