mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 18:21:39 +01:00
Do not embed IPXE into Rombios anymore. Instead, it is loaded by the toolstack from a file as a separate module. Ability to let user specify an IPXE blob will come later. No user visible change. Signed-off-by: Wei Liu <wei.liu2@citrix.com> Acked-by: Jan Beulich <jbeulich@suse.com>
451 lines
14 KiB
C
451 lines
14 KiB
C
/*
|
|
* hvmloader.c: HVM bootloader.
|
|
*
|
|
* Leendert van Doorn, leendert@watson.ibm.com
|
|
* Copyright (c) 2005, International Business Machines Corporation.
|
|
*
|
|
* Copyright (c) 2006, Keir Fraser, XenSource Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "util.h"
|
|
#include "hypercall.h"
|
|
#include "config.h"
|
|
#include "pci_regs.h"
|
|
#include "apic_regs.h"
|
|
#include "vnuma.h"
|
|
#include <acpi2_0.h>
|
|
#include <xen/version.h>
|
|
#include <xen/hvm/params.h>
|
|
#include <xen/arch-x86/hvm/start_info.h>
|
|
|
|
const struct hvm_start_info *hvm_start_info;
|
|
|
|
asm (
|
|
" .text \n"
|
|
" .globl _start \n"
|
|
"_start: \n"
|
|
/* C runtime kickoff. */
|
|
" cld \n"
|
|
" cli \n"
|
|
" lgdt gdt_desr \n"
|
|
" mov $"STR(SEL_DATA32)",%ax \n"
|
|
" mov %ax,%ds \n"
|
|
" mov %ax,%es \n"
|
|
" mov %ax,%fs \n"
|
|
" mov %ax,%gs \n"
|
|
" mov %ax,%ss \n"
|
|
" ljmp $"STR(SEL_CODE32)",$1f \n"
|
|
"1: movl $stack_top,%esp \n"
|
|
" movl %esp,%ebp \n"
|
|
/* store HVM start info ptr */
|
|
" mov %ebx, hvm_start_info \n"
|
|
" call main \n"
|
|
/* Relocate real-mode trampoline to 0x0. */
|
|
" mov $trampoline_start,%esi \n"
|
|
" xor %edi,%edi \n"
|
|
" mov $trampoline_end,%ecx \n"
|
|
" sub %esi,%ecx \n"
|
|
" rep movsb \n"
|
|
/* Load real-mode compatible segment state (base 0x0000, limit 0xffff). */
|
|
" mov $"STR(SEL_DATA16)",%ax \n"
|
|
" mov %ax,%ds \n"
|
|
" mov %ax,%es \n"
|
|
" mov %ax,%fs \n"
|
|
" mov %ax,%gs \n"
|
|
" mov %ax,%ss \n"
|
|
/* Initialise all 32-bit GPRs to zero. */
|
|
" xor %eax,%eax \n"
|
|
" xor %ebx,%ebx \n"
|
|
" xor %ecx,%ecx \n"
|
|
" xor %edx,%edx \n"
|
|
" xor %esp,%esp \n"
|
|
" xor %ebp,%ebp \n"
|
|
" xor %esi,%esi \n"
|
|
" xor %edi,%edi \n"
|
|
/* Enter real mode, reload all segment registers and IDT. */
|
|
" ljmp $"STR(SEL_CODE16)",$0x0\n"
|
|
"trampoline_start: .code16 \n"
|
|
" mov %eax,%cr0 \n"
|
|
" ljmp $0,$1f-trampoline_start\n"
|
|
"1: mov %ax,%ds \n"
|
|
" mov %ax,%es \n"
|
|
" mov %ax,%fs \n"
|
|
" mov %ax,%gs \n"
|
|
" mov %ax,%ss \n"
|
|
" lidt 1f-trampoline_start \n"
|
|
" ljmp $0xf000,$0xfff0 \n"
|
|
"1: .word 0x3ff,0,0 \n"
|
|
"trampoline_end: .code32 \n"
|
|
" \n"
|
|
"gdt_desr: \n"
|
|
" .word gdt_end - gdt - 1 \n"
|
|
" .long gdt \n"
|
|
" \n"
|
|
" .align 8 \n"
|
|
"gdt: \n"
|
|
" .quad 0x0000000000000000 \n"
|
|
" .quad 0x008f9a000000ffff \n" /* Ring 0 16b code, base 0 limit 4G */
|
|
" .quad 0x008f92000000ffff \n" /* Ring 0 16b data, base 0 limit 4G */
|
|
" .quad 0x00cf9a000000ffff \n" /* Ring 0 32b code, base 0 limit 4G */
|
|
" .quad 0x00cf92000000ffff \n" /* Ring 0 32b data, base 0 limit 4G */
|
|
" .quad 0x00af9a000000ffff \n" /* Ring 0 64b code */
|
|
"gdt_end: \n"
|
|
" \n"
|
|
" .bss \n"
|
|
" .align 8 \n"
|
|
"stack: \n"
|
|
" .skip 0x4000 \n"
|
|
"stack_top: \n"
|
|
" .text \n"
|
|
);
|
|
|
|
unsigned long scratch_start = SCRATCH_PHYSICAL_ADDRESS;
|
|
|
|
uint32_t ioapic_base_address = 0xfec00000;
|
|
uint8_t ioapic_version;
|
|
|
|
static void init_hypercalls(void)
|
|
{
|
|
uint32_t eax, ebx, ecx, edx;
|
|
unsigned long i;
|
|
char signature[13];
|
|
xen_extraversion_t extraversion;
|
|
uint32_t base;
|
|
|
|
for ( base = 0x40000000; base < 0x40010000; base += 0x100 )
|
|
{
|
|
cpuid(base, &eax, &ebx, &ecx, &edx);
|
|
|
|
*(uint32_t *)(signature + 0) = ebx;
|
|
*(uint32_t *)(signature + 4) = ecx;
|
|
*(uint32_t *)(signature + 8) = edx;
|
|
signature[12] = '\0';
|
|
|
|
if ( !strcmp("XenVMMXenVMM", signature) )
|
|
break;
|
|
}
|
|
|
|
BUG_ON(strcmp("XenVMMXenVMM", signature) || ((eax - base) < 2));
|
|
|
|
/* Fill in hypercall transfer pages. */
|
|
cpuid(base + 2, &eax, &ebx, &ecx, &edx);
|
|
for ( i = 0; i < eax; i++ )
|
|
wrmsr(ebx, HYPERCALL_PHYSICAL_ADDRESS + (i << 12) + i);
|
|
|
|
/* Print version information. */
|
|
cpuid(base + 1, &eax, &ebx, &ecx, &edx);
|
|
hypercall_xen_version(XENVER_extraversion, extraversion);
|
|
printf("Detected Xen v%u.%u%s\n", eax >> 16, eax & 0xffff, extraversion);
|
|
}
|
|
|
|
/* Replace possibly erroneous memory-size CMOS fields with correct values. */
|
|
static void cmos_write_memory_size(void)
|
|
{
|
|
uint32_t base_mem = 640, ext_mem, alt_mem;
|
|
|
|
alt_mem = ext_mem = hvm_info->low_mem_pgend << PAGE_SHIFT;
|
|
ext_mem = (ext_mem > 0x0100000) ? (ext_mem - 0x0100000) >> 10 : 0;
|
|
if ( ext_mem > 0xffff )
|
|
ext_mem = 0xffff;
|
|
alt_mem = (alt_mem > 0x1000000) ? (alt_mem - 0x1000000) >> 16 : 0;
|
|
|
|
/* All BIOSes: conventional memory (CMOS *always* reports 640kB). */
|
|
cmos_outb(0x15, (uint8_t)(base_mem >> 0));
|
|
cmos_outb(0x16, (uint8_t)(base_mem >> 8));
|
|
|
|
/* All BIOSes: extended memory (1kB chunks above 1MB). */
|
|
cmos_outb(0x17, (uint8_t)( ext_mem >> 0));
|
|
cmos_outb(0x18, (uint8_t)( ext_mem >> 8));
|
|
cmos_outb(0x30, (uint8_t)( ext_mem >> 0));
|
|
cmos_outb(0x31, (uint8_t)( ext_mem >> 8));
|
|
|
|
/* Some BIOSes: alternative extended memory (64kB chunks above 16MB). */
|
|
cmos_outb(0x34, (uint8_t)( alt_mem >> 0));
|
|
cmos_outb(0x35, (uint8_t)( alt_mem >> 8));
|
|
}
|
|
|
|
/*
|
|
* Set up an empty TSS area for virtual 8086 mode to use. Its content is
|
|
* going to be managed by Xen, but zero fill it just in case.
|
|
*/
|
|
static void init_vm86_tss(void)
|
|
{
|
|
/*
|
|
* Have the TSS cover the ISA port range, which makes it
|
|
* - 104 bytes base structure
|
|
* - 32 bytes interrupt redirection bitmap
|
|
* - 128 bytes I/O bitmap
|
|
* - one trailing byte
|
|
* or a total of to 265 bytes. As it needs to be a multiple of the requested
|
|
* alignment, this ends up requiring 384 bytes.
|
|
*/
|
|
#define TSS_SIZE (3 * 128)
|
|
void *tss;
|
|
|
|
tss = mem_alloc(TSS_SIZE, 128);
|
|
memset(tss, 0, TSS_SIZE);
|
|
hvm_param_set(HVM_PARAM_VM86_TSS_SIZED,
|
|
((uint64_t)TSS_SIZE << 32) | virt_to_phys(tss));
|
|
printf("vm86 TSS at %08lx\n", virt_to_phys(tss));
|
|
#undef TSS_SIZE
|
|
}
|
|
|
|
static void apic_setup(void)
|
|
{
|
|
/*
|
|
* This would the The Right Thing To Do (tm), if only qemu negotiated
|
|
* with Xen where the IO-APIC actually sits (which is currently hard
|
|
* coded in Xen and can't be controlled externally). Uncomment this code
|
|
* once that changed.
|
|
ioapic_base_address |= (pci_readb(PCI_ISA_DEVFN, 0x80) & 0x3f) << 10;
|
|
*/
|
|
ioapic_version = ioapic_read(0x01) & 0xff;
|
|
|
|
/* Set the IOAPIC ID to the static value used in the MP/ACPI tables. */
|
|
ioapic_write(0x00, IOAPIC_ID);
|
|
|
|
/* NMIs are delivered direct to the BSP. */
|
|
lapic_write(APIC_SPIV, APIC_SPIV_APIC_ENABLED | 0xFF);
|
|
lapic_write(APIC_LVT0, (APIC_MODE_EXTINT << 8) | APIC_LVT_MASKED);
|
|
lapic_write(APIC_LVT1, APIC_MODE_NMI << 8);
|
|
|
|
/* 8259A ExtInts are delivered through IOAPIC pin 0 (Virtual Wire Mode). */
|
|
ioapic_write(0x10, APIC_DM_EXTINT);
|
|
ioapic_write(0x11, SET_APIC_ID(LAPIC_ID(0)));
|
|
}
|
|
|
|
struct bios_info {
|
|
const char *key;
|
|
const struct bios_config *bios;
|
|
} bios_configs[] = {
|
|
#ifdef ENABLE_ROMBIOS
|
|
{ "rombios", &rombios_config, },
|
|
#endif
|
|
{ "seabios", &seabios_config, },
|
|
{ "ovmf", &ovmf_config, },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
static const struct bios_config *detect_bios(void)
|
|
{
|
|
const struct bios_info *b;
|
|
const char *bios;
|
|
|
|
bios = xenstore_read("hvmloader/bios", "rombios");
|
|
|
|
for ( b = &bios_configs[0]; b->key != NULL; b++ )
|
|
if ( !strcmp(bios, b->key) )
|
|
return b->bios;
|
|
|
|
printf("Unknown BIOS %s, no ROM image found\n", bios);
|
|
BUG();
|
|
return NULL;
|
|
}
|
|
|
|
static void acpi_enable_sci(void)
|
|
{
|
|
uint8_t pm1a_cnt_val;
|
|
|
|
#define PIIX4_SMI_CMD_IOPORT 0xb2
|
|
#define PIIX4_ACPI_ENABLE 0xf1
|
|
|
|
/*
|
|
* PIIX4 emulation in QEMU has SCI_EN=0 by default. We have no legacy
|
|
* SMM implementation, so give ACPI control to the OSPM immediately.
|
|
*/
|
|
pm1a_cnt_val = inb(ACPI_PM1A_CNT_BLK_ADDRESS_V1);
|
|
if ( !(pm1a_cnt_val & ACPI_PM1C_SCI_EN) )
|
|
outb(PIIX4_SMI_CMD_IOPORT, PIIX4_ACPI_ENABLE);
|
|
|
|
pm1a_cnt_val = inb(ACPI_PM1A_CNT_BLK_ADDRESS_V1);
|
|
BUG_ON(!(pm1a_cnt_val & ACPI_PM1C_SCI_EN));
|
|
}
|
|
|
|
const struct hvm_modlist_entry *get_module_entry(
|
|
const struct hvm_start_info *info,
|
|
const char *name)
|
|
{
|
|
const struct hvm_modlist_entry *modlist =
|
|
(struct hvm_modlist_entry *)(uintptr_t)info->modlist_paddr;
|
|
unsigned int i;
|
|
|
|
if ( !modlist ||
|
|
info->modlist_paddr > UINTPTR_MAX ||
|
|
(UINTPTR_MAX - (uintptr_t)info->modlist_paddr) / sizeof(*modlist)
|
|
< info->nr_modules )
|
|
return NULL;
|
|
|
|
for ( i = 0; i < info->nr_modules; i++ )
|
|
{
|
|
char *module_name = (char*)(uintptr_t)modlist[i].cmdline_paddr;
|
|
|
|
/* Skip if the module or its cmdline is missing. */
|
|
if ( !module_name || !modlist[i].paddr )
|
|
continue;
|
|
|
|
/* Skip if the cmdline cannot be read. */
|
|
if ( modlist[i].cmdline_paddr > UINTPTR_MAX ||
|
|
(modlist[i].cmdline_paddr + strlen(name)) > UINTPTR_MAX )
|
|
continue;
|
|
|
|
if ( !strcmp(name, module_name) )
|
|
{
|
|
if ( modlist[i].paddr > UINTPTR_MAX ||
|
|
modlist[i].size > UINTPTR_MAX ||
|
|
(modlist[i].paddr + modlist[i].size - 1) > UINTPTR_MAX )
|
|
{
|
|
printf("Cannot load \"%s\" from 0x"PRIllx" (0x"PRIllx")\n",
|
|
name, PRIllx_arg(modlist[i].paddr),
|
|
PRIllx_arg(modlist[i].size));
|
|
BUG();
|
|
}
|
|
return &modlist[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
const struct bios_config *bios;
|
|
int acpi_enabled;
|
|
const struct hvm_modlist_entry *bios_module;
|
|
|
|
/* Initialise hypercall stubs with RET, rendering them no-ops. */
|
|
memset((void *)HYPERCALL_PHYSICAL_ADDRESS, 0xc3 /* RET */, PAGE_SIZE);
|
|
|
|
printf("HVM Loader\n");
|
|
BUG_ON(hvm_start_info->magic != XEN_HVM_START_MAGIC_VALUE);
|
|
|
|
init_hypercalls();
|
|
|
|
memory_map_setup();
|
|
|
|
xenbus_setup();
|
|
|
|
bios = detect_bios();
|
|
printf("System requested %s\n", bios->name);
|
|
|
|
printf("CPU speed is %u MHz\n", get_cpu_mhz());
|
|
|
|
apic_setup();
|
|
pci_setup();
|
|
|
|
smp_initialise();
|
|
|
|
perform_tests();
|
|
|
|
if ( bios->bios_info_setup )
|
|
bios->bios_info_setup();
|
|
|
|
if ( bios->create_smbios_tables )
|
|
{
|
|
printf("Writing SMBIOS tables ...\n");
|
|
bios->create_smbios_tables();
|
|
}
|
|
|
|
printf("Loading %s ...\n", bios->name);
|
|
bios_module = get_module_entry(hvm_start_info, "firmware");
|
|
if ( bios_module )
|
|
{
|
|
uint32_t paddr = bios_module->paddr;
|
|
|
|
bios->bios_load(bios, (void *)paddr, bios_module->size, NULL);
|
|
}
|
|
#ifdef ENABLE_ROMBIOS
|
|
else if ( bios == &rombios_config )
|
|
{
|
|
const struct hvm_modlist_entry *ipxe;
|
|
uint32_t paddr = 0;
|
|
|
|
ipxe = get_module_entry(hvm_start_info, "ipxe");
|
|
if ( ipxe )
|
|
paddr = ipxe->paddr;
|
|
bios->bios_load(bios, NULL, 0, (void *)paddr);
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
/*
|
|
* If there is no BIOS module supplied and if there is no embeded BIOS
|
|
* image, then we failed. Only rombios might have an embedded bios blob.
|
|
*/
|
|
printf("no BIOS ROM image found\n");
|
|
BUG();
|
|
}
|
|
|
|
if ( (hvm_info->nr_vcpus > 1) || hvm_info->apic_mode )
|
|
{
|
|
if ( bios->create_mp_tables )
|
|
bios->create_mp_tables();
|
|
if ( bios->create_pir_tables )
|
|
bios->create_pir_tables();
|
|
}
|
|
|
|
if ( bios->load_roms )
|
|
bios->load_roms();
|
|
|
|
acpi_enabled = !strncmp(xenstore_read("platform/acpi", "1"), "1", 1);
|
|
|
|
if ( acpi_enabled )
|
|
{
|
|
init_vnuma_info();
|
|
|
|
if ( bios->acpi_build_tables )
|
|
{
|
|
printf("Loading ACPI ...\n");
|
|
bios->acpi_build_tables();
|
|
}
|
|
|
|
acpi_enable_sci();
|
|
|
|
hvm_param_set(HVM_PARAM_ACPI_IOPORTS_LOCATION, 1);
|
|
}
|
|
|
|
init_vm86_tss();
|
|
|
|
cmos_write_memory_size();
|
|
|
|
printf("BIOS map:\n");
|
|
if ( SCRATCH_PHYSICAL_ADDRESS != scratch_start )
|
|
printf(" %05x-%05lx: Scratch space\n",
|
|
SCRATCH_PHYSICAL_ADDRESS, scratch_start);
|
|
printf(" %05x-%05x: Main BIOS\n",
|
|
bios->bios_address,
|
|
bios->bios_address + bios->image_size - 1);
|
|
|
|
if ( bios->e820_setup )
|
|
bios->e820_setup();
|
|
|
|
if ( bios->bios_info_finish )
|
|
bios->bios_info_finish();
|
|
|
|
xenbus_shutdown();
|
|
|
|
printf("Invoking %s ...\n", bios->name);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Local variables:
|
|
* mode: C
|
|
* c-file-style: "BSD"
|
|
* c-basic-offset: 4
|
|
* tab-width: 4
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*/
|