xen: implement early init helper for PVHv2

In order to setup an initial environment and jump into the generic
hammer_time initialization function. Some of the code is shared with
PVHv1, while other code is PVHv2 specific.

This allows booting FreeBSD as a PVHv2 DomU and Dom0.

Sponsored by:	Citrix Systems R&D
This commit is contained in:
Roger Pau Monné 2018-07-19 08:44:52 +00:00
parent 07c2711fbf
commit b0663c33c2
4 changed files with 227 additions and 49 deletions

View File

@ -87,7 +87,7 @@ NON_GPROF_ENTRY(xen_start)
xorl %ebp, %ebp
/* u_int64_t hammer_time_xen(start_info_t *si, u_int64_t xenstack); */
call hammer_time_xen
call hammer_time_xen_legacy
movq %rax, %rsp /* set up kstack for mi_startup() */
call mi_startup /* autoconfiguration, mountroot etc */

View File

@ -82,6 +82,12 @@ static MALLOC_DEFINE(M_XENHVM, "xen_hvm", "Xen HVM PV Support");
*/
int xen_vector_callback_enabled;
/**
* Start info flags. ATM this only used to store the initial domain flag for
* PVHv2, and it's always empty for HVM guests.
*/
uint32_t hvm_start_flags;
/*------------------------------- Per-CPU Data -------------------------------*/
DPCPU_DEFINE(struct vcpu_info, vcpu_local_info);
DPCPU_DEFINE(struct vcpu_info *, vcpu_info);
@ -469,7 +475,7 @@ static uint32_t
hvm_get_start_flags(void)
{
return (0);
return (hvm_start_flags);
}
struct hypervisor_info hypervisor_info = {

View File

@ -58,6 +58,7 @@ __FBSDID("$FreeBSD$");
#include <vm/vm_pager.h>
#include <vm/vm_param.h>
#include <machine/_inttypes.h>
#include <machine/intr_machdep.h>
#include <x86/apicvar.h>
#include <x86/init.h>
@ -67,11 +68,13 @@ __FBSDID("$FreeBSD$");
#include <machine/metadata.h>
#include <xen/xen-os.h>
#include <xen/hvm.h>
#include <xen/hypervisor.h>
#include <xen/xenstore/xenstorevar.h>
#include <xen/xen_pv.h>
#include <xen/xen_msi.h>
#include <xen/interface/arch-x86/hvm/start_info.h>
#include <xen/interface/vcpu.h>
#include <dev/xen/timer/timer.h>
@ -83,13 +86,15 @@ __FBSDID("$FreeBSD$");
/* Native initial function */
extern u_int64_t hammer_time(u_int64_t, u_int64_t);
/* Xen initial function */
uint64_t hammer_time_xen(start_info_t *, uint64_t);
uint64_t hammer_time_xen_legacy(start_info_t *, uint64_t);
uint64_t hammer_time_xen(vm_paddr_t);
#define MAX_E820_ENTRIES 128
/*--------------------------- Forward Declarations ---------------------------*/
static caddr_t xen_pv_parse_preload_data(u_int64_t);
static void xen_pv_parse_memmap(caddr_t, vm_paddr_t *, int *);
static caddr_t xen_legacy_pvh_parse_preload_data(uint64_t);
static caddr_t xen_pvh_parse_preload_data(uint64_t);
static void xen_pvh_parse_memmap(caddr_t, vm_paddr_t *, int *);
#ifdef SMP
static int xen_pv_start_all_aps(void);
@ -112,20 +117,33 @@ extern uint32_t end;
/*-------------------------------- Global Data -------------------------------*/
/* Xen init_ops implementation. */
struct init_ops xen_init_ops = {
.parse_preload_data = xen_pv_parse_preload_data,
struct init_ops xen_legacy_init_ops = {
.parse_preload_data = xen_legacy_pvh_parse_preload_data,
.early_clock_source_init = xen_clock_init,
.early_delay = xen_delay,
.parse_memmap = xen_pv_parse_memmap,
.parse_memmap = xen_pvh_parse_memmap,
#ifdef SMP
.start_all_aps = xen_pv_start_all_aps,
#endif
.msi_init = xen_msi_init,
};
struct init_ops xen_pvh_init_ops = {
.parse_preload_data = xen_pvh_parse_preload_data,
.early_clock_source_init = xen_clock_init,
.early_delay = xen_delay,
.parse_memmap = xen_pvh_parse_memmap,
#ifdef SMP
.mp_bootaddress = mp_bootaddress,
.start_all_aps = native_start_all_aps,
#endif
.msi_init = msi_init,
};
static struct bios_smap xen_smap[MAX_E820_ENTRIES];
static start_info_t *legacy_start_info;
static struct hvm_start_info *start_info;
/*----------------------- Legacy PVH start_info accessors --------------------*/
static vm_paddr_t
@ -179,7 +197,7 @@ struct hypervisor_info legacy_info = {
* as similar as possible to what native FreeBSD init function expects.
*/
uint64_t
hammer_time_xen(start_info_t *si, uint64_t xenstack)
hammer_time_xen_legacy(start_info_t *si, uint64_t xenstack)
{
uint64_t physfree;
uint64_t *PT4 = (u_int64_t *)xenstack;
@ -235,7 +253,7 @@ hammer_time_xen(start_info_t *si, uint64_t xenstack)
load_cr3(((uint64_t)&PT4[0]) - KERNBASE);
/* Set the hooks for early functions that diverge from bare metal */
init_ops = xen_init_ops;
init_ops = xen_legacy_init_ops;
apic_ops = xen_apic_ops;
hypervisor_info = legacy_info;
@ -243,6 +261,85 @@ hammer_time_xen(start_info_t *si, uint64_t xenstack)
return (hammer_time(0, physfree));
}
uint64_t
hammer_time_xen(vm_paddr_t start_info_paddr)
{
struct hvm_modlist_entry *mod;
struct xen_add_to_physmap xatp;
uint64_t physfree;
char *kenv;
int rc;
xen_domain_type = XEN_HVM_DOMAIN;
vm_guest = VM_GUEST_XEN;
rc = xen_hvm_init_hypercall_stubs(XEN_HVM_INIT_EARLY);
if (rc) {
xc_printf("ERROR: failed to initialize hypercall page: %d\n",
rc);
HYPERVISOR_shutdown(SHUTDOWN_crash);
}
start_info = (struct hvm_start_info *)(start_info_paddr + KERNBASE);
if (start_info->magic != XEN_HVM_START_MAGIC_VALUE) {
xc_printf("Unknown magic value in start_info struct: %#x\n",
start_info->magic);
HYPERVISOR_shutdown(SHUTDOWN_crash);
}
/*
* The hvm_start_into structure is always appended after loading
* the kernel and modules.
*/
physfree = roundup2(start_info_paddr + PAGE_SIZE, PAGE_SIZE);
xatp.domid = DOMID_SELF;
xatp.idx = 0;
xatp.space = XENMAPSPACE_shared_info;
xatp.gpfn = atop(physfree);
if (HYPERVISOR_memory_op(XENMEM_add_to_physmap, &xatp)) {
xc_printf("ERROR: failed to setup shared_info page\n");
HYPERVISOR_shutdown(SHUTDOWN_crash);
}
HYPERVISOR_shared_info = (shared_info_t *)(physfree + KERNBASE);
physfree += PAGE_SIZE;
/*
* Init a static kenv using a free page. The contents will be filled
* from the parse_preload_data hook.
*/
kenv = (void *)(physfree + KERNBASE);
physfree += PAGE_SIZE;
bzero(kenv, PAGE_SIZE);
init_static_kenv(kenv, PAGE_SIZE);
if (start_info->modlist_paddr != 0) {
if (start_info->modlist_paddr >= physfree) {
xc_printf(
"ERROR: unexpected module list memory address\n");
HYPERVISOR_shutdown(SHUTDOWN_crash);
}
if (start_info->nr_modules == 0) {
xc_printf(
"ERROR: modlist_paddr != 0 but nr_modules == 0\n");
HYPERVISOR_shutdown(SHUTDOWN_crash);
}
mod = (struct hvm_modlist_entry *)
(vm_paddr_t)start_info->modlist_paddr + KERNBASE;
if (mod[0].paddr >= physfree) {
xc_printf("ERROR: unexpected module memory address\n");
HYPERVISOR_shutdown(SHUTDOWN_crash);
}
}
/* Set the hooks for early functions that diverge from bare metal */
init_ops = xen_pvh_init_ops;
hvm_start_flags = start_info->flags;
/* Now we can jump into the native init function */
return (hammer_time(0, physfree));
}
/*-------------------------------- PV specific -------------------------------*/
#ifdef SMP
static bool
@ -318,27 +415,49 @@ xen_pv_start_all_aps(void)
#endif /* SMP */
/*
* Functions to convert the "extra" parameters passed by Xen
* into FreeBSD boot options.
* When booted as a PVH guest FreeBSD needs to avoid using the RSDP address
* hint provided by the loader because it points to the native set of ACPI
* tables instead of the ones crafted by Xen. The acpi.rsdp env variable is
* removed from kenv if present, and a new acpi.rsdp is added to kenv that
* points to the address of the Xen crafted RSDP.
*/
static void
xen_pv_set_env(void)
static bool reject_option(const char *option)
{
char *cmd_line_next, *cmd_line;
size_t env_size;
static const char *reject[] = {
"acpi.rsdp",
};
unsigned int i;
cmd_line = legacy_start_info->cmd_line;
env_size = sizeof(legacy_start_info->cmd_line);
for (i = 0; i < nitems(reject); i++)
if (strncmp(option, reject[i], strlen(reject[i])) == 0)
return (true);
/* Skip leading spaces */
for (; isspace(*cmd_line) && (env_size != 0); cmd_line++)
env_size--;
return (false);
}
/* Replace ',' with '\0' */
for (cmd_line_next = cmd_line; strsep(&cmd_line_next, ",") != NULL;)
;
static void
xen_pvh_set_env(char *env, bool (*filter)(const char *))
{
char *option;
init_static_kenv(cmd_line, 0);
if (env == NULL)
return;
option = env;
while (*option != 0) {
char *value;
if (filter != NULL && filter(option)) {
option += strlen(option) + 1;
continue;
}
value = option;
option = strsep(&value, "=");
if (kern_setenv(option, value) != 0)
xc_printf("unable to add kenv %s=%s\n", option, value);
option = value + strlen(value) + 1;
}
}
#ifdef DDB
@ -349,26 +468,14 @@ xen_pv_set_env(void)
* sys/kern/kern_ksyms.c CVS Revision 1.71.
*/
static void
xen_pv_parse_symtab(void)
xen_pvh_parse_symtab(void)
{
Elf_Ehdr *ehdr;
Elf_Shdr *shdr;
vm_offset_t sym_end;
uint32_t size;
int i, j;
size = end;
sym_end = legacy_start_info->mod_start != 0 ?
legacy_start_info->mod_start : legacy_start_info->mfn_list;
/*
* Make sure the size is right headed, sym_end is just a
* high boundary, but at least allows us to fail earlier.
*/
if ((vm_offset_t)&end + size > sym_end) {
xc_printf("Unable to load ELF symtab: size mismatch\n");
return;
}
ehdr = (Elf_Ehdr *)(&end + 1);
if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) ||
@ -394,16 +501,14 @@ xen_pv_parse_symtab(void)
break;
}
if (ksymtab == 0 || kstrtab == 0) {
if (ksymtab == 0 || kstrtab == 0)
xc_printf(
"Unable to load ELF symtab: could not find symtab or strtab\n");
return;
}
}
#endif
static caddr_t
xen_pv_parse_preload_data(u_int64_t modulep)
xen_legacy_pvh_parse_preload_data(uint64_t modulep)
{
caddr_t kmdp;
vm_ooffset_t off;
@ -434,22 +539,82 @@ xen_pv_parse_preload_data(u_int64_t modulep)
envp = MD_FETCH(kmdp, MODINFOMD_ENVP, char *);
if (envp != NULL)
envp += off;
init_static_kenv(envp, 0);
xen_pvh_set_env(envp, NULL);
} else {
/* Parse the extra boot information given by Xen */
xen_pv_set_env();
boothowto |= boot_env_to_howto();
boot_parse_cmdline_delim(legacy_start_info->cmd_line, ",");
kmdp = NULL;
}
boothowto |= boot_env_to_howto();
#ifdef DDB
xen_pv_parse_symtab();
xen_pvh_parse_symtab();
#endif
return (kmdp);
}
static caddr_t
xen_pvh_parse_preload_data(uint64_t modulep)
{
caddr_t kmdp;
vm_ooffset_t off;
vm_paddr_t metadata;
char *envp;
char acpi_rsdp[19];
if (start_info->modlist_paddr != 0) {
struct hvm_modlist_entry *mod;
mod = (struct hvm_modlist_entry *)
(start_info->modlist_paddr + KERNBASE);
preload_metadata = (caddr_t)(mod[0].paddr + KERNBASE);
kmdp = preload_search_by_type("elf kernel");
if (kmdp == NULL)
kmdp = preload_search_by_type("elf64 kernel");
KASSERT(kmdp != NULL, ("unable to find kernel"));
/*
* Xen has relocated the metadata and the modules,
* so we need to recalculate it's position. This is
* done by saving the original modulep address and
* then calculating the offset with mod_start,
* which contains the relocated modulep address.
*/
metadata = MD_FETCH(kmdp, MODINFOMD_MODULEP, vm_paddr_t);
off = mod[0].paddr + KERNBASE - metadata;
preload_bootstrap_relocate(off);
boothowto = MD_FETCH(kmdp, MODINFOMD_HOWTO, int);
envp = MD_FETCH(kmdp, MODINFOMD_ENVP, char *);
if (envp != NULL)
envp += off;
xen_pvh_set_env(envp, reject_option);
} else {
/* Parse the extra boot information given by Xen */
if (start_info->cmdline_paddr != 0)
boot_parse_cmdline_delim(
(char *)(start_info->cmdline_paddr + KERNBASE),
",");
kmdp = NULL;
}
boothowto |= boot_env_to_howto();
snprintf(acpi_rsdp, sizeof(acpi_rsdp), "%#" PRIx64,
start_info->rsdp_paddr);
kern_setenv("acpi.rsdp", acpi_rsdp);
#ifdef DDB
xen_pvh_parse_symtab();
#endif
return (kmdp);
}
static void
xen_pv_parse_memmap(caddr_t kmdp, vm_paddr_t *physmap, int *physmap_idx)
xen_pvh_parse_memmap(caddr_t kmdp, vm_paddr_t *physmap, int *physmap_idx)
{
struct xen_memory_map memmap;
u_int32_t size;
@ -459,8 +624,12 @@ xen_pv_parse_memmap(caddr_t kmdp, vm_paddr_t *physmap, int *physmap_idx)
memmap.nr_entries = MAX_E820_ENTRIES;
set_xen_guest_handle(memmap.buffer, xen_smap);
rc = HYPERVISOR_memory_op(XENMEM_memory_map, &memmap);
if (rc)
panic("unable to fetch Xen E820 memory map");
if (rc) {
xc_printf("ERROR: unable to fetch Xen E820 memory map: %d\n",
rc);
HYPERVISOR_shutdown(SHUTDOWN_crash);
}
size = memmap.nr_entries * sizeof(xen_smap[0]);
bios_add_smap_entries(xen_smap, size, physmap, physmap_idx);

View File

@ -102,4 +102,7 @@ int xen_hvm_init_hypercall_stubs(enum xen_hvm_init_type);
void xen_hvm_set_callback(device_t);
void xen_hvm_suspend(void);
void xen_hvm_resume(bool suspend_cancelled);
extern uint32_t hvm_start_flags;
#endif /* __XEN_HVM_H__ */