pvh: Boot uncompressed kernel using direct boot ABI

These changes (along with corresponding Linux kernel and qboot changes)
enable a guest to be booted using the x86/HVM direct boot ABI.

This commit adds a load_elfboot() routine to pass the size and
location of the kernel entry point to qboot (which will fill in
the start_info struct information needed to to boot the guest).
Having loaded the ELF binary, load_linux() will run qboot
which continues the boot.

The address for the kernel entry point is read from an ELF Note
in the uncompressed kernel binary by a helper routine passed
to load_elf().

Co-developed-by: George Kennedy <George.Kennedy@oracle.com>
Signed-off-by: George Kennedy <George.Kennedy@oracle.com>
Signed-off-by: Liam Merwick <liam.merwick@oracle.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Liam Merwick 2019-01-15 12:18:06 +00:00 committed by Paolo Bonzini
parent 20a965067f
commit ab969087da
2 changed files with 145 additions and 0 deletions

View File

@ -54,6 +54,7 @@
#include "sysemu/qtest.h" #include "sysemu/qtest.h"
#include "kvm_i386.h" #include "kvm_i386.h"
#include "hw/xen/xen.h" #include "hw/xen/xen.h"
#include "hw/xen/start_info.h"
#include "ui/qemu-spice.h" #include "ui/qemu-spice.h"
#include "exec/memory.h" #include "exec/memory.h"
#include "exec/address-spaces.h" #include "exec/address-spaces.h"
@ -110,6 +111,9 @@ static struct e820_entry *e820_table;
static unsigned e820_entries; static unsigned e820_entries;
struct hpet_fw_config hpet_cfg = {.count = UINT8_MAX}; struct hpet_fw_config hpet_cfg = {.count = UINT8_MAX};
/* Physical Address of PVH entry point read from kernel ELF NOTE */
static size_t pvh_start_addr;
GlobalProperty pc_compat_3_1[] = { GlobalProperty pc_compat_3_1[] = {
{ "intel-iommu", "dma-drain", "off" }, { "intel-iommu", "dma-drain", "off" },
{ "Opteron_G3" "-" TYPE_X86_CPU, "rdtscp", "off" }, { "Opteron_G3" "-" TYPE_X86_CPU, "rdtscp", "off" },
@ -1069,6 +1073,109 @@ struct setup_data {
uint8_t data[0]; uint8_t data[0];
} __attribute__((packed)); } __attribute__((packed));
/*
* The entry point into the kernel for PVH boot is different from
* the native entry point. The PVH entry is defined by the x86/HVM
* direct boot ABI and is available in an ELFNOTE in the kernel binary.
*
* This function is passed to load_elf() when it is called from
* load_elfboot() which then additionally checks for an ELF Note of
* type XEN_ELFNOTE_PHYS32_ENTRY and passes it to this function to
* parse the PVH entry address from the ELF Note.
*
* Due to trickery in elf_opts.h, load_elf() is actually available as
* load_elf32() or load_elf64() and this routine needs to be able
* to deal with being called as 32 or 64 bit.
*
* The address of the PVH entry point is saved to the 'pvh_start_addr'
* global variable. (although the entry point is 32-bit, the kernel
* binary can be either 32-bit or 64-bit).
*/
static uint64_t read_pvh_start_addr(void *arg1, void *arg2, bool is64)
{
size_t *elf_note_data_addr;
/* Check if ELF Note header passed in is valid */
if (arg1 == NULL) {
return 0;
}
if (is64) {
struct elf64_note *nhdr64 = (struct elf64_note *)arg1;
uint64_t nhdr_size64 = sizeof(struct elf64_note);
uint64_t phdr_align = *(uint64_t *)arg2;
uint64_t nhdr_namesz = nhdr64->n_namesz;
elf_note_data_addr =
((void *)nhdr64) + nhdr_size64 +
QEMU_ALIGN_UP(nhdr_namesz, phdr_align);
} else {
struct elf32_note *nhdr32 = (struct elf32_note *)arg1;
uint32_t nhdr_size32 = sizeof(struct elf32_note);
uint32_t phdr_align = *(uint32_t *)arg2;
uint32_t nhdr_namesz = nhdr32->n_namesz;
elf_note_data_addr =
((void *)nhdr32) + nhdr_size32 +
QEMU_ALIGN_UP(nhdr_namesz, phdr_align);
}
pvh_start_addr = *elf_note_data_addr;
return pvh_start_addr;
}
static bool load_elfboot(const char *kernel_filename,
int kernel_file_size,
uint8_t *header,
size_t pvh_xen_start_addr,
FWCfgState *fw_cfg)
{
uint32_t flags = 0;
uint32_t mh_load_addr = 0;
uint32_t elf_kernel_size = 0;
uint64_t elf_entry;
uint64_t elf_low, elf_high;
int kernel_size;
if (ldl_p(header) != 0x464c457f) {
return false; /* no elfboot */
}
bool elf_is64 = header[EI_CLASS] == ELFCLASS64;
flags = elf_is64 ?
((Elf64_Ehdr *)header)->e_flags : ((Elf32_Ehdr *)header)->e_flags;
if (flags & 0x00010004) { /* LOAD_ELF_HEADER_HAS_ADDR */
error_report("elfboot unsupported flags = %x", flags);
exit(1);
}
uint64_t elf_note_type = XEN_ELFNOTE_PHYS32_ENTRY;
kernel_size = load_elf(kernel_filename, read_pvh_start_addr,
NULL, &elf_note_type, &elf_entry,
&elf_low, &elf_high, 0, I386_ELF_MACHINE,
0, 0);
if (kernel_size < 0) {
error_report("Error while loading elf kernel");
exit(1);
}
mh_load_addr = elf_low;
elf_kernel_size = elf_high - elf_low;
if (pvh_start_addr == 0) {
error_report("Error loading uncompressed kernel without PVH ELF Note");
exit(1);
}
fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_ENTRY, pvh_start_addr);
fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_ADDR, mh_load_addr);
fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_SIZE, elf_kernel_size);
return true;
}
static void load_linux(PCMachineState *pcms, static void load_linux(PCMachineState *pcms,
FWCfgState *fw_cfg) FWCfgState *fw_cfg)
{ {
@ -1108,6 +1215,34 @@ static void load_linux(PCMachineState *pcms,
if (ldl_p(header+0x202) == 0x53726448) { if (ldl_p(header+0x202) == 0x53726448) {
protocol = lduw_p(header+0x206); protocol = lduw_p(header+0x206);
} else { } else {
/*
* Check if the file is an uncompressed kernel file (ELF) and load it,
* saving the PVH entry point used by the x86/HVM direct boot ABI.
* If load_elfboot() is successful, populate the fw_cfg info.
*/
if (load_elfboot(kernel_filename, kernel_size,
header, pvh_start_addr, fw_cfg)) {
struct hvm_modlist_entry ramdisk_mod = { 0 };
fclose(f);
fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE,
strlen(kernel_cmdline) + 1);
fw_cfg_add_string(fw_cfg, FW_CFG_CMDLINE_DATA, kernel_cmdline);
assert(machine->device_memory != NULL);
ramdisk_mod.paddr = machine->device_memory->base;
ramdisk_mod.size =
memory_region_size(&machine->device_memory->mr);
fw_cfg_add_bytes(fw_cfg, FW_CFG_KERNEL_DATA, &ramdisk_mod,
sizeof(ramdisk_mod));
fw_cfg_add_i32(fw_cfg, FW_CFG_SETUP_SIZE, sizeof(header));
fw_cfg_add_bytes(fw_cfg, FW_CFG_SETUP_DATA,
header, sizeof(header));
return;
}
/* This looks like a multiboot kernel. If it is, let's stop /* This looks like a multiboot kernel. If it is, let's stop
treating it like a Linux kernel. */ treating it like a Linux kernel. */
if (load_multiboot(fw_cfg, f, kernel_filename, initrd_filename, if (load_multiboot(fw_cfg, f, kernel_filename, initrd_filename,

View File

@ -1640,6 +1640,16 @@ typedef struct elf64_shdr {
#define NT_ARM_HW_WATCH 0x403 /* ARM hardware watchpoint registers */ #define NT_ARM_HW_WATCH 0x403 /* ARM hardware watchpoint registers */
#define NT_ARM_SYSTEM_CALL 0x404 /* ARM system call number */ #define NT_ARM_SYSTEM_CALL 0x404 /* ARM system call number */
/*
* Physical entry point into the kernel.
*
* 32bit entry point into the kernel. When requested to launch the
* guest kernel, use this entry point to launch the guest in 32-bit
* protected mode with paging disabled.
*
* [ Corresponding definition in Linux kernel: include/xen/interface/elfnote.h ]
*/
#define XEN_ELFNOTE_PHYS32_ENTRY 18 /* 0x12 */
/* Note header in a PT_NOTE section */ /* Note header in a PT_NOTE section */
typedef struct elf32_note { typedef struct elf32_note {