Files
Roger Pau Monne cfd5232431 x86/hvmloader: fix order of PCI vs MTRR initialization
After some recent change the order of MTRR vs PCI initialization is
inverted.  MTRR will get initialization ahead of PCI scanning and sizing of
MMIO regions.  As a result when setting up MTRRs the MMIO window below 4GB
will always have the same size, and there will be no window above 4GB.
This results in malformed and incomplete MTRRs being setup.

Fix by making sure PCI is initialized ahead of MTRR, also add a comment to
notice the ordering dependency.

Fixes: 2c3dffbaa3 ('tools/hvmloader: Replace LAPIC_ID() with cpu_to_apicid[]')
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
Acked-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Anthoine Bourgeois <anthoine.bourgeois@gmail.com>
2025-05-28 10:23:35 +02:00

457 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;
uint8_t ioapic_version;
bool acpi_enabled;
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(cpu_to_apicid[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;
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());
/*
* PCI setup must be done before SMP initialization, as the later also does
* the MTRR setup and so the size of the PCI MMIO windows must be known at
* that point.
*/
pci_setup();
smp_initialise();
apic_setup();
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:
*/