visor/src/vintel.c

300 lines
8.9 KiB
C
Raw Normal View History

2025-03-10 20:24:32 +01:00
#include <efi.h>
#include <efilib.h>
#include <efiapi.h>
2025-03-10 19:19:53 +01:00
#include <cpuid.h>
#include <x86/msr.h>
#include <x86/cr.h>
2025-03-11 02:06:12 +01:00
#include <x86/idt.h>
#include <x86/gdt.h>
2025-03-10 20:24:32 +01:00
#include <x86/vmx.h>
2025-03-10 19:19:53 +01:00
#include "virt.h"
#include "std.h"
2025-03-11 20:59:39 +01:00
#include "procvisor.h"
2025-03-10 19:19:53 +01:00
2025-03-10 20:24:32 +01:00
static struct VMXON *vmxon_region;
2025-03-10 20:41:09 +01:00
static struct VMCS *vmcs_region;
2025-03-10 20:24:32 +01:00
2025-03-10 19:19:53 +01:00
static bool
vintel_has_bios_support(void)
{
2025-03-11 01:40:28 +01:00
uint64_t value = readmsr64(IA32_FEATURE_CONTROL);
2025-03-10 19:19:53 +01:00
return (value & 0x5) == 0x5;
}
static bool
vintel_has_cpu_support(void)
{
unsigned info[4];
__get_cpuid(0x1, &info[0], &info[1], &info[2], &info[3]);
return (info[2] & (1 << 5)) != 0;
}
bool
vintel_has_support(char *err, size_t errmax)
{
if (!vintel_has_cpu_support()) {
strlcpy(err, "CPU does not support VT-x.", errmax);
return false;
}
if (!vintel_has_bios_support()) {
strlcpy(err, "VT-x support is not enabled in BIOS.", errmax);
return false;
}
return true;
}
2025-03-11 01:40:28 +01:00
static void
vintel_fix_cr_bits(void)
{
uint64_t cr0_fixed0 = readmsr64(IA32_VMX_CR0_FIXED0);
uint64_t cr0_fixed1 = readmsr64(IA32_VMX_CR0_FIXED1);
uint64_t cr4_fixed0 = readmsr64(IA32_VMX_CR4_FIXED0);
uint64_t cr4_fixed1 = readmsr64(IA32_VMX_CR4_FIXED1);
writecr0((readcr0() | cr0_fixed0) & cr0_fixed1);
writecr4((readcr4() | cr4_fixed0) & cr4_fixed1);
}
2025-03-12 17:20:39 +01:00
static void
checked_vmwrite(uint64_t field, uint64_t value)
{
uint64_t status = vmwrite(field, value);
if (status & (1 << 0)) {
AsciiPrint("vmwrite(%lx): Invalid VMCS Pointer\n", field);
}
if (status & (1 << 6)) {
AsciiPrint("vmwrite(%lx): %a\n", vmcs_describe_error());
}
}
static uint64_t
vmx_adjust_controls(uint64_t value, uint32_t msr)
{
uint64_t msrValue = readmsr64(msr);
value = ((msrValue & 0xffffffff) | value) & (msrValue >> 32);
return value;
}
static void
vmx_init_execution_controls(void)
{
uint64_t vmxBasic = readmsr64(IA32_VMX_BASIC);
uint64_t pinBasedMsr = ((vmxBasic >> 55) & 1) ? IA32_VMX_TRUE_PINBASED_CTLS : IA32_VMX_PINBASED_CTLS;
uint64_t procBasedMsr = ((vmxBasic >> 55) & 1) ? IA32_VMX_TRUE_PROCBASED_CTLS : IA32_VMX_PROCBASED_CTLS;
2025-03-12 17:20:39 +01:00
uint64_t procBasedMsr2 = IA32_VMX_PROCBASED_CTLS2;
uint64_t pinBasedControls = vmx_adjust_controls(0, pinBasedMsr);
uint64_t procBasedControls = vmx_adjust_controls(0, procBasedMsr);
2025-03-12 17:20:39 +01:00
uint64_t procBasedControls2 = vmx_adjust_controls(0, procBasedMsr2);
checked_vmwrite(PIN_BASED_VM_EXEC_CONTROL, pinBasedControls);
checked_vmwrite(CPU_BASED_VM_EXEC_CONTROL, procBasedControls);
checked_vmwrite(CPU_BASED_VM_EXEC_CONTROL2, procBasedControls2);
2025-03-12 17:20:39 +01:00
AsciiPrint("PIN_BASED_VM_EXEC_CONTROL: %lx\n", pinBasedControls);
AsciiPrint("CPU_BASED_VM_EXEC_CONTROL: %lx\n", procBasedControls);
AsciiPrint("CPU_BASED_VM_EXEC_CONTROL2: %lx\n", procBasedControls2);
}
2025-03-11 20:59:39 +01:00
// HACK
char guest_stack[4096];
void
guest_main(void)
{
AsciiPrint("Hello from guest!\n");
for (;;) {}
}
2025-03-12 02:33:53 +01:00
static void
vintel_write_guest_segment(struct GDTR gdtr, int reg, int ss, uint32_t hibase)
{
struct segdescr *descr = &gdtr.base[ss >> 3];
2025-03-12 17:20:39 +01:00
checked_vmwrite(GUEST_ES_SELECTOR + 2 * reg, ss & 0xfff8);
checked_vmwrite(GUEST_ES_BASE + 2 * reg, getsegbase(descr) | ((uint64_t)hibase << 32));
checked_vmwrite(GUEST_ES_LIMIT + 2 * reg, getseglimit(descr));
checked_vmwrite(GUEST_ES_ACCESS + 2 * reg, getsegaccess(descr, false));
2025-03-12 02:33:53 +01:00
}
2025-03-11 01:40:28 +01:00
static void
vintel_init_guest(void)
{
2025-03-12 02:33:53 +01:00
struct GDTR gdtr = storegdt();
struct IDTR idtr = storeidt();
2025-03-11 20:59:39 +01:00
uint64_t guestSP = (uintptr_t)guest_stack + 4096;
uint64_t guestIP = (uintptr_t)(void *)guest_main;
// Set segment selectors
2025-03-12 02:33:53 +01:00
vintel_write_guest_segment(gdtr, 0, reades(), 0);
vintel_write_guest_segment(gdtr, 1, readcs(), 0);
vintel_write_guest_segment(gdtr, 2, readss(), 0);
vintel_write_guest_segment(gdtr, 3, readds(), 0);
vintel_write_guest_segment(gdtr, 4, readfs(), 0);
vintel_write_guest_segment(gdtr, 5, readgs(), 0);
/* VMX requires a usable task register.
* UEFI requires us to use their GDT, which does not include a task register.
* So we try something stupid: load temporary but valid values into the TR shadow registers.
* Then later reload the GDT to make the shadow registers consistent again.
* Sigh.
*/
2025-03-12 17:20:39 +01:00
checked_vmwrite(GUEST_ES_SELECTOR + 2 * 7, readtr() & 0xfff8);
checked_vmwrite(GUEST_ES_BASE + 2 * 7, (uintptr_t)myprocvisor()->host_tss); // just abuse the host's TSS for this
checked_vmwrite(GUEST_ES_LIMIT + 2 * 7, sizeof (struct TSS) - 1);
checked_vmwrite(GUEST_ES_ACCESS + 2 * 7, 0x00000089);
2025-03-11 20:59:39 +01:00
// Set control registers
2025-03-12 17:20:39 +01:00
checked_vmwrite(GUEST_CR0, readcr0());
checked_vmwrite(GUEST_CR3, readcr3());
checked_vmwrite(GUEST_CR4, readcr4());
checked_vmwrite(GUEST_DR7, readdr7());
checked_vmwrite(GUEST_RFLAGS, readflags());
checked_vmwrite(GUEST_RSP, guestSP);
checked_vmwrite(GUEST_RIP, guestIP);
checked_vmwrite(VMCS_LINK_POINTER, -1LL);
checked_vmwrite(GUEST_IA32_DEBUGCTL, readmsr64(IA32_DEBUGCTL));
checked_vmwrite(GUEST_IA32_PAT, readmsr64(IA32_PAT));
checked_vmwrite(GUEST_IA32_EFER, readmsr64(IA32_EFER));
checked_vmwrite(GUEST_FS_BASE, readmsr64(IA32_FS_BASE));
checked_vmwrite(GUEST_GS_BASE, readmsr64(IA32_GS_BASE));
checked_vmwrite(GUEST_SYSENTER_CS, readmsr64(0x174));
checked_vmwrite(GUEST_SYSENTER_ESP, readmsr64(0x175));
checked_vmwrite(GUEST_SYSENTER_EIP, readmsr64(0x176));
2025-03-11 20:59:39 +01:00
// Set GDT and IDT
2025-03-12 17:20:39 +01:00
checked_vmwrite(GUEST_GDTR_BASE, (uintptr_t)gdtr.base);
checked_vmwrite(GUEST_IDTR_BASE, (uintptr_t)idtr.base);
2025-03-11 01:40:28 +01:00
}
static void
vintel_init_host(void)
{
2025-03-11 20:59:39 +01:00
struct procvisor *pv = myprocvisor();
2025-03-11 02:06:12 +01:00
2025-03-11 20:59:39 +01:00
uint64_t hostSP = (uintptr_t)pv->host_stack;
2025-03-11 02:06:12 +01:00
uint64_t hostIP = 0x0;
2025-03-11 01:40:28 +01:00
// Set segment selectors
2025-03-12 17:20:39 +01:00
checked_vmwrite(HOST_ES_SELECTOR, 0x10);
checked_vmwrite(HOST_CS_SELECTOR, 0x08);
checked_vmwrite(HOST_SS_SELECTOR, 0x10);
checked_vmwrite(HOST_DS_SELECTOR, 0x10);
checked_vmwrite(HOST_FS_SELECTOR, 0x00);
checked_vmwrite(HOST_GS_SELECTOR, 0x00);
checked_vmwrite(HOST_TR_SELECTOR, 0x18);
2025-03-11 01:40:28 +01:00
// Set control registers
2025-03-12 17:20:39 +01:00
checked_vmwrite(HOST_CR0, readcr0());
checked_vmwrite(HOST_CR3, readcr3());
checked_vmwrite(HOST_CR4, readcr4());
2025-03-11 01:40:28 +01:00
// Set RSP and RIP
2025-03-12 17:20:39 +01:00
checked_vmwrite(HOST_RSP, hostSP);
checked_vmwrite(HOST_RIP, hostIP);
2025-03-11 01:40:28 +01:00
// Set MSRs
2025-03-12 17:20:39 +01:00
checked_vmwrite(HOST_IA32_PAT, readmsr64(IA32_PAT));
checked_vmwrite(HOST_IA32_EFER, readmsr64(IA32_EFER));
2025-03-11 01:40:28 +01:00
// Set GDT and IDT
2025-03-12 17:20:39 +01:00
checked_vmwrite(HOST_GDTR_BASE, (uintptr_t)pv->host_gdtr.base);
checked_vmwrite(HOST_IDTR_BASE, (uintptr_t)pv->host_idtr.base);
2025-03-15 17:46:14 +01:00
// Dummy sysenter & sysexit values
checked_vmwrite(HOST_SYSENTER_ESP, 0x4000);
checked_vmwrite(HOST_SYSENTER_EIP, 0x4000);
2025-03-11 01:40:28 +01:00
}
2025-03-10 20:24:32 +01:00
void
vintel_enable(void)
{
Print(L"Set CR4 Bit\n");
writecr4(readcr4() | CR4_VMXE);
#if 0
Print(L"Enable in Feature Control\n");
uint64_t fctl = rdmsr64(IA32_FEATURE_CONTROL);
fctl |= (1 << 0);
wrmsr64(IA32_FEATURE_CONTROL, fctl);
#endif
2025-03-11 01:40:28 +01:00
vintel_fix_cr_bits();
2025-03-11 20:59:39 +01:00
AsciiPrint("VMX_BASIC = %lx\n", readmsr64(IA32_VMX_BASIC));
2025-03-10 20:24:32 +01:00
Print(L"Allocating VMXON Page\n");
uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, EfiRuntimeServicesData, 1, &vmxon_region);
2025-03-10 20:41:09 +01:00
Print(L"VMXON: %p\n", vmxon_region);
2025-03-11 20:59:39 +01:00
vmxon_region->revisionID = readmsr64(IA32_VMX_BASIC) & 0xffffffff;
2025-03-10 20:24:32 +01:00
uint64_t status = vmxon(vmxon_region);
2025-03-11 20:59:39 +01:00
Print(L"VMXON Status: %lx\n", status);
2025-03-10 20:24:32 +01:00
if (status & (1 << 0)) {
2025-03-11 20:59:39 +01:00
Print(L"Invalid VMXON Pointer\n");
2025-03-10 20:24:32 +01:00
}
if (status & (1 << 6)) {
Print(L"Extended VMX Error\n");
}
2025-03-10 20:41:09 +01:00
2025-03-11 18:46:10 +01:00
// FIXME Allocate the correct amount of space!
2025-03-10 20:41:09 +01:00
Print(L"Allocating VMCS Page\n");
uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, EfiRuntimeServicesData, 1, &vmcs_region);
Print(L"VMCS: %p\n", vmcs_region);
2025-03-11 20:59:39 +01:00
vmcs_region->revisionID = readmsr64(IA32_VMX_BASIC) & 0xffffffff;
2025-03-10 20:41:09 +01:00
status = vmclear(vmcs_region);
2025-03-11 20:59:39 +01:00
Print(L"VMCLEAR Status: %lx\n", status);
2025-03-10 20:41:09 +01:00
status = vmptrld(vmcs_region);
2025-03-11 20:59:39 +01:00
Print(L"VMPTRLD Status: %lx\n", status);
2025-03-11 01:40:28 +01:00
vmx_init_execution_controls();
2025-03-11 01:40:28 +01:00
vintel_init_guest();
2025-03-11 02:06:12 +01:00
vintel_init_host();
2025-03-11 17:16:03 +01:00
2025-03-11 20:59:39 +01:00
{
AsciiPrint("* Initializing VMX Entry Controls\n");
uint64_t vmxBasic = readmsr64(IA32_VMX_BASIC);
uint32_t msr = ((vmxBasic >> 55) & 1) ? IA32_VMX_TRUE_ENTRY_CTLS : IA32_VMX_ENTRY_CTLS;
uint64_t entryControls = vmx_adjust_controls(0x200, msr); // 0x200 enables IA-32e mode
2025-03-12 17:20:39 +01:00
checked_vmwrite(VM_ENTRY_CONTROLS, entryControls);
AsciiPrint("VM_ENTRY_CONTROLS: %lx\n", entryControls);
uint32_t exitMsr = ((vmxBasic >> 55) & 1) ? IA32_VMX_TRUE_EXIT_CTLS : IA32_VMX_EXIT_CTLS;
uint64_t exitControls = vmx_adjust_controls(0x0, exitMsr);
checked_vmwrite(VM_EXIT_CONTROLS, exitControls);
AsciiPrint("VM_EXIT_CONTROLS: %lx\n", exitControls);
checked_vmwrite(VM_ENTRY_MSR_LOAD_COUNT, 0);
checked_vmwrite(VM_ENTRY_INTR_INFO_FIELD, 0);
2025-03-15 17:46:14 +01:00
checked_vmwrite(VM_EXIT_MSR_STORE_COUNT, 0);
2025-03-12 17:20:39 +01:00
checked_vmwrite(CR3_TARGET_COUNT, 0);
2025-03-12 17:20:39 +01:00
checked_vmwrite(EXCEPTION_BITMAP, 0);
2025-03-11 20:59:39 +01:00
}
2025-03-11 17:16:03 +01:00
status = vmlaunch();
Print(L"VMLAUNCH Status: %p\n", (void *)status);
if (status & (1 << 0)) {
Print(L"Invalid VMCS Pointer\n");
}
if (status & (1 << 6)) {
AsciiPrint("%a\n", vmcs_describe_error());
}
2025-03-10 20:24:32 +01:00
}
2025-03-10 19:19:53 +01:00
struct virt_vtable virt_vtable_intel = {
.has_support = vintel_has_support,
2025-03-10 20:24:32 +01:00
.enable = vintel_enable,
2025-03-10 19:19:53 +01:00
};