base-hw: implement VMCB data structure for AMD SVM

Ref #4826
This commit is contained in:
Benjamin Lamowski 2022-12-21 00:13:19 +01:00 committed by Christian Helmuth
parent fa6e819f9a
commit cb69c59fa3
5 changed files with 511 additions and 2 deletions

View File

@ -20,6 +20,7 @@ SRC_S += spec/x86_64/exception_vector.s
SRC_CC += kernel/cpu_mp.cc SRC_CC += kernel/cpu_mp.cc
SRC_CC += kernel/vm_thread_on.cc SRC_CC += kernel/vm_thread_on.cc
SRC_CC += spec/x86_64/virtualization/kernel/vm.cc SRC_CC += spec/x86_64/virtualization/kernel/vm.cc
SRC_CC += spec/x86_64/virtualization/kernel/svm.cc
SRC_CC += spec/x86_64/virtualization/vm_session_component.cc SRC_CC += spec/x86_64/virtualization/vm_session_component.cc
SRC_CC += vm_session_common.cc SRC_CC += vm_session_common.cc
SRC_CC += vm_session_component.cc SRC_CC += vm_session_component.cc

View File

@ -18,8 +18,10 @@
#include <kernel/configuration.h> #include <kernel/configuration.h>
#include <kernel/irq.h> #include <kernel/irq.h>
#include <hw/spec/x86_64/page_table.h>
#include <cpu/vm_state_virtualization.h> #include <cpu/vm_state_virtualization.h>
#include <hw/spec/x86_64/page_table.h>
#include <spec/x86_64/virtualization/svm.h>
#include <cpu.h>
namespace Board { namespace Board {
@ -46,6 +48,10 @@ namespace Kernel {
struct Board::Vcpu_context struct Board::Vcpu_context
{ {
Vcpu_context(Kernel::Cpu & cpu); Vcpu_context(Kernel::Cpu & cpu);
void initialize_svm(Kernel::Cpu &cpu, void *table);
Vmcb vmcb;
Genode::Align_at<Core::Cpu::Context> regs;
}; };
#endif /* _CORE__SPEC__PC__VIRTUALIZATION__BOARD_H_ */ #endif /* _CORE__SPEC__PC__VIRTUALIZATION__BOARD_H_ */

View File

@ -0,0 +1,156 @@
/*
* \brief SVM specific implementations
* \author Benjamin Lamowski
* \date 2022-10-14
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#include <base/log.h>
#include <hw/spec/x86_64/x86_64.h>
#include <kernel/cpu.h>
#include <platform.h>
#include <spec/x86_64/virtualization/svm.h>
#include <util/mmio.h>
using Genode::addr_t;
using Kernel::Cpu;
using Kernel::Vm;
using Board::Vmcb;
Vmcb::Vmcb(Genode::uint32_t id, Genode::addr_t addr)
:
Mmio((Genode::addr_t)this)
{
write<Guest_asid>(id);
write<Msrpm_base_pa>(dummy_msrpm());
write<Iopm_base_pa>(dummy_iopm());
phys_addr = addr;
/*
* Set the guest PAT register to the default value.
* See: AMD Vol.2 7.8 Page-Attribute Table Mechanism
*/
g_pat = 0x0007040600070406ULL;
}
Vmcb & Vmcb::host_vmcb(Genode::size_t cpu_id)
{
static Genode::Constructible<Vmcb> host_vmcb[NR_OF_CPUS];
if (!host_vmcb[cpu_id].constructed()) {
host_vmcb[cpu_id].construct(Vmcb::Asid_host);
}
return *host_vmcb[cpu_id];
}
void Vmcb::init(Genode::size_t cpu_id, void * table_ptr)
{
using Cpu = Hw::X86_64_cpu;
root_vmcb_phys = Core::Platform::core_phys_addr((addr_t)
&host_vmcb(cpu_id));
asm volatile ("vmsave" : : "a" (root_vmcb_phys) : "memory");
Cpu::Amd_vm_hsavepa::write((Cpu::Amd_vm_hsavepa::access_t) root_vmcb_phys);
/*
* enable nested paging
*/
write<Npt_control::Np_enable>(1);
write<N_cr3>((Genode::addr_t) table_ptr);
write<Int_control::V_intr_mask>(1); /* See 15.2 */
write<Intercept_ex::Vectors>(17); /* AC */
enforce_intercepts();
}
/*
* Enforce SVM intercepts
*/
void Vmcb::enforce_intercepts(Genode::uint32_t desired_primary, Genode::uint32_t desired_secondary)
{
write<Vmcb::Intercept_misc1>(
desired_primary |
Vmcb::Intercept_misc1::Intr::bits(1) |
Vmcb::Intercept_misc1::Nmi::bits(1) |
Vmcb::Intercept_misc1::Init::bits(1) |
Vmcb::Intercept_misc1::Invd::bits(1) |
Vmcb::Intercept_misc1::Hlt::bits(1) |
Vmcb::Intercept_misc1::Ioio_prot::bits(1) |
Vmcb::Intercept_misc1::Msr_prot::bits(1) |
Vmcb::Intercept_misc1::Shutdown::bits(1)
);
write<Vmcb::Intercept_misc2>(
desired_secondary |
Vmcb::Intercept_misc2::Vmload::bits(1) |
Vmcb::Intercept_misc2::Vmsave::bits(1) |
Vmcb::Intercept_misc2::Clgi::bits(1) |
Vmcb::Intercept_misc2::Skinit::bits(1)
);
}
/*
* AMD Vol.2 15.11: MSR Permissions Map
* All set to 1 since we want all MSRs to be intercepted.
*/
Genode::addr_t Vmcb::dummy_msrpm()
{
static Genode::Constructible<Board::Msrpm> msrpm;
if (!msrpm.constructed())
msrpm.construct();
return Core::Platform::core_phys_addr((addr_t) & *msrpm);
}
/*
* AMD Vol.2 15.10.1 I/O Permissions Map
* All set to 1 since we want all IO port accesses to be intercepted.
*/
Genode::addr_t Vmcb::dummy_iopm()
{
static Genode::Constructible<Board::Iopm> iopm;
if (!iopm.constructed())
iopm.construct();
return Core::Platform::core_phys_addr((addr_t) &*iopm);
}
Board::Msrpm::Msrpm()
{
Genode::memset(this, 0xFF, sizeof(*this));
}
Board::Iopm::Iopm()
{
Genode::memset(this, 0xFF, sizeof(*this));
}
void Board::Vcpu_context::initialize_svm(Kernel::Cpu & cpu, void * table)
{
using Cpu = Hw::X86_64_cpu;
Cpu::Ia32_efer::access_t ia32_efer_msr = Cpu::Ia32_efer::read();
Cpu::Ia32_efer::Svme::set(ia32_efer_msr, 1);
Cpu::Ia32_efer::write(ia32_efer_msr);
Cpu::Amd_vm_syscvg::access_t amd_vm_syscvg_msr = Cpu::Amd_vm_syscvg::read();
Cpu::Amd_vm_syscvg::Nested_paging::set(amd_vm_syscvg_msr, 1);
Cpu::Amd_vm_syscvg::write(amd_vm_syscvg_msr);
vmcb.init(cpu.id(), table);
}

View File

@ -14,19 +14,24 @@
#include <base/log.h> #include <base/log.h>
#include <cpu/vm_state_virtualization.h> #include <cpu/vm_state_virtualization.h>
#include <util/mmio.h> #include <util/mmio.h>
#include <cpu/string.h>
#include <hw/assert.h> #include <hw/assert.h>
#include <map_local.h> #include <map_local.h>
#include <platform_pd.h> #include <platform_pd.h>
#include <platform.h>
#include <kernel/cpu.h> #include <kernel/cpu.h>
#include <kernel/vm.h> #include <kernel/vm.h>
#include <kernel/main.h> #include <kernel/main.h>
#include <spec/x86_64/virtualization/hypervisor.h> #include <spec/x86_64/virtualization/hypervisor.h>
#include <spec/x86_64/virtualization/svm.h>
#include <hw/spec/x86_64/x86_64.h>
using Genode::addr_t; using Genode::addr_t;
using Kernel::Cpu; using Kernel::Cpu;
using Kernel::Vm; using Kernel::Vm;
using Board::Vmcb;
Vm::Vm(Irq::Pool & user_irq_pool, Vm::Vm(Irq::Pool & user_irq_pool,
@ -53,11 +58,19 @@ Vm::~Vm()
} }
void Vm::proceed(Cpu &)
{
}
void Vm::exception(Cpu &) void Vm::exception(Cpu &)
{ {
} }
void Vm::proceed(Cpu &) Board::Vcpu_context::Vcpu_context(Cpu &)
:
vmcb(0),
regs(1)
{ {
} }

View File

@ -0,0 +1,333 @@
/*
* \brief VMCB data structure
* \author Benjamin Lamowski
* \date 2022-12-21
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _INCLUDE__SPEC__PC__SVM_H_
#define _INCLUDE__SPEC__PC__SVM_H_
#include <base/internal/page_size.h>
#include <base/stdint.h>
#include <cpu/vcpu_state.h>
#include <util/mmio.h>
namespace Board
{
struct Msrpm;
struct Iopm;
struct Vmcb_control_area;
struct Vmcb_reserved_for_host;
struct Vmcb_state_save_area;
struct Vmcb;
}
struct alignas(Genode::get_page_size()) Board::Msrpm
{
Genode::uint8_t pad[8192];
Msrpm();
};
struct
alignas(Genode::get_page_size())
Board::Iopm
{
Genode::uint8_t pad[12288];
Iopm();
};
/*
* VMCB Control area, excluding the reserved for host part
*/
struct Board::Vmcb_control_area
{
enum : Genode::size_t {
total_size = 1024U,
used_guest_size = 0x3E0U
};
/* The control area is padded and used via Mmio-like accesses. */
Genode::uint8_t control_area[used_guest_size];
Vmcb_control_area()
{
Genode::memset((void *) this, 0, sizeof(Vmcb_control_area));
}
};
/*
* Part of the VMCB control area that is reserved for host data.
* This uses 16 bytes less to accomodate for the size of the Mmio class.
*/
struct Board::Vmcb_reserved_for_host
{
/* 64bit used by the inherited Mmio class here */
Genode::addr_t phys_addr = 0U;
Genode::addr_t root_vmcb_phys = 0U;
};
static_assert(Board::Vmcb_control_area::total_size -
sizeof(Board::Vmcb_control_area) - sizeof(Genode::Mmio) -
sizeof(Board::Vmcb_reserved_for_host) ==
0);
/*
* AMD Manual Vol. 2, Table B-2: VMCB Layout, State Save Area
*/
struct Board::Vmcb_state_save_area
{
typedef Genode::Vcpu_state::Segment Segment;
Segment es, cs, ss, ds, fs, gs, gdtr, ldtr, idtr, tr;
Genode::uint8_t reserved1[43];
Genode::uint8_t cpl;
Genode::uint8_t reserved2[4];
Genode::uint64_t efer;
Genode::uint8_t reserved3[112];
Genode::uint64_t cr4, cr3, cr0, dr7, dr6, rflags, rip;
Genode::uint8_t reserved4[88];
Genode::uint64_t rsp;
Genode::uint64_t s_cet, ssp, isst_addr;
Genode::uint64_t rax, star, lstar, cstar, sfmask, kernel_gs_base;
Genode::uint64_t sysenter_cs, sysenter_esp, sysenter_eip, cr2;
Genode::uint8_t reserved5[32];
Genode::uint64_t g_pat;
Genode::uint64_t dbgctl;
Genode::uint64_t br_from;
Genode::uint64_t br_to;
Genode::uint64_t lastexcpfrom;
Genode::uint8_t reserved6[72];
Genode::uint64_t spec_ctrl;
} __attribute__((packed));
/*
* VMCB data structure
* See: AMD Manual Vol. 2, Appendix B Layout of VMCB
*
* We construct the VMCB by inheriting from its components. Inheritance is used
* instead of making the components members of the overall structure in order to
* present the interface on the top level. Order of inheritance is important!
*
* The Mmio interface is inherited from on this level to achieve the desired
* placement of the Mmio data member address in the host data part and after the
* VMCB control area data.
* The remaining part of the VMCB control area that is reserved for host data
* is then inherited from after the Mmio interface.
* Lastly, the VMCB state save area is inherited from to make its members
* directly available in the VCMB structure.
* In total, this allows Register type access to the VMCB control area and easy
* direct access to the VMCB state save area.
*/
struct alignas(Genode::get_page_size()) Board::Vmcb
:
Board::Vmcb_control_area,
public Genode::Mmio,
Board::Vmcb_reserved_for_host,
Board::Vmcb_state_save_area
{
enum {
Asid_host = 0,
};
Vmcb(Genode::uint32_t id, Genode::addr_t addr = 0);
void init(Genode::size_t cpu_id, void * table_ptr);
static Vmcb & host_vmcb(Genode::size_t cpu_id);
static Genode::addr_t dummy_msrpm();
void enforce_intercepts(Genode::uint32_t desired_primary = 0U, Genode::uint32_t desired_secondary = 0U);
static Genode::addr_t dummy_iopm();
Genode::uint8_t reserved[Genode::get_page_size() -
sizeof(Board::Vmcb_state_save_area) -
Board::Vmcb_control_area::total_size];
/*
* AMD Manual Vol. 2, Table B-1: VMCB Layout, Control Area
*/
struct Intercept_cr : Register<0x000, 32> {
struct Reads : Bitfield< 0,16> { };
struct Writes : Bitfield<16,16> { };
};
struct Intercept_dr : Register<0x004, 32> {
struct Reads : Bitfield< 0,16> { };
struct Writes : Bitfield<16,16> { };
};
struct Intercept_ex : Register<0x008, 32> {
struct Vectors : Bitfield<0,32> { };
};
struct Intercept_misc1 : Register<0x00C, 32> {
struct Intr : Bitfield< 0, 1> { };
struct Nmi : Bitfield< 1, 1> { };
struct Smi : Bitfield< 2, 1> { };
struct Init : Bitfield< 3, 1> { };
struct Vintr : Bitfield< 4, 1> { };
struct Cr0 : Bitfield< 5, 1> { };
struct Read_Idtr : Bitfield< 6, 1> { };
struct Read_Gdtr : Bitfield< 7, 1> { };
struct Read_Ldtr : Bitfield< 8, 1> { };
struct Read_Tr : Bitfield< 9, 1> { };
struct Write_Idtr : Bitfield<10, 1> { };
struct Write_Gdtr : Bitfield<11, 1> { };
struct Write_Ldtr : Bitfield<12, 1> { };
struct Write_Tr : Bitfield<13, 1> { };
struct Rdtsc : Bitfield<14, 1> { };
struct Rdpmc : Bitfield<15, 1> { };
struct Pushf : Bitfield<16, 1> { };
struct Popf : Bitfield<17, 1> { };
struct Cpuid : Bitfield<18, 1> { };
struct Rsm : Bitfield<19, 1> { };
struct Iret : Bitfield<20, 1> { };
struct Int : Bitfield<21, 1> { };
struct Invd : Bitfield<22, 1> { };
struct Pause : Bitfield<23, 1> { };
struct Hlt : Bitfield<24, 1> { };
struct Invlpg : Bitfield<25, 1> { };
struct INvlpga : Bitfield<26, 1> { };
struct Ioio_prot : Bitfield<27, 1> { };
struct Msr_prot : Bitfield<28, 1> { };
struct Task_switch : Bitfield<29, 1> { };
struct Ferr_freeze : Bitfield<30, 1> { };
struct Shutdown : Bitfield<31, 1> { };
};
struct Intercept_misc2 : Register<0x010, 32> {
struct Vmrun : Bitfield< 0, 1> { };
struct Vmcall : Bitfield< 1, 1> { };
struct Vmload : Bitfield< 2, 1> { };
struct Vmsave : Bitfield< 3, 1> { };
struct Stgi : Bitfield< 4, 1> { };
struct Clgi : Bitfield< 5, 1> { };
struct Skinit : Bitfield< 6, 1> { };
struct Rdtscp : Bitfield< 7, 1> { };
struct Icebp : Bitfield< 8, 1> { };
struct Wbinvd : Bitfield< 9, 1> { };
struct Monitor : Bitfield<10, 1> { };
struct Mwait_uncon : Bitfield<11, 1> { };
struct Mwait_armed : Bitfield<12, 1> { };
struct Xsetbv : Bitfield<13, 1> { };
struct Rdpru : Bitfield<14, 1> { };
struct Efer : Bitfield<15, 1> { };
struct Cr : Bitfield<16,16> { };
};
struct Intercept_misc3 : Register<0x014, 32> {
struct Invlpgb_all : Bitfield< 0, 1> { };
struct Invlpgb_inv : Bitfield< 1, 1> { };
struct Invpcid : Bitfield< 2, 1> { };
struct Mcommit : Bitfield< 3, 1> { };
struct Stgi : Bitfield< 4, 1> { };
};
struct Pause_filter_thres : Register<0x03C,16> { };
struct Pause_filter_count : Register<0x03E,16> { };
struct Iopm_base_pa : Register<0x040,64> { };
struct Msrpm_base_pa : Register<0x048,64> { };
struct Tsc_offset : Register<0x050,64> { };
/*
* The following two registers are documented as one 64bit register in the
* manual but since this is rather tedious to work with, just split them
* into two.
*/
struct Guest_asid : Register<0x058,32> { };
struct Tlb : Register<0x05C,32> {
struct Tlb_control : Bitfield< 0, 8> {};
};
struct Int_control : Register<0x060, 64> {
struct V_tpr : Bitfield< 0, 8> { };
struct V_irq : Bitfield< 8, 1> { };
struct Vgif : Bitfield< 9, 1> { };
struct V_intr_prio : Bitfield<16, 4> { };
struct V_ign_tpr : Bitfield<20, 1> { };
struct V_intr_mask : Bitfield<24, 1> { };
struct Amd_virt_gif : Bitfield<25, 1> { };
struct Avic_enable : Bitfield<31, 1> { };
struct V_intr_vector : Bitfield<33, 8> { };
};
struct Int_control_ext : Register<0x068, 64> {
struct Int_shadow : Bitfield< 0, 1> { };
struct Guest_int_mask : Bitfield< 1, 1> { };
};
struct Exitcode : Register<0x070,64> { };
struct Exitinfo1 : Register<0x078,64> { };
struct Exitinfo2 : Register<0x080,64> { };
struct Exitintinfo : Register<0x088,64> { };
struct Npt_control : Register<0x090, 64> {
struct Np_enable : Bitfield< 0, 1> { };
struct Enable_sev : Bitfield< 1, 1> { };
struct Sev_enc_state : Bitfield< 2, 1> { };
struct Guest_md_ex_tr : Bitfield< 3, 1> { };
struct Sss_check_en : Bitfield< 4, 1> { };
struct Virt_trans_enc : Bitfield< 5, 1> { };
struct Enable_invlpgb : Bitfield< 7, 1> { };
};
struct Avic : Register<0x098, 64> {
struct Avic_apic_bar : Bitfield< 0,52> { };
};
struct Ghcb_gpe : Register<0x0A0,64> { };
struct Eventinj : Register<0x0A8,64> { };
struct N_cr3 : Register<0x0B0,64> { };
struct Virt_extra : Register<0x0B8, 64> {
struct Lbr_virt : Bitfield< 0, 1> { };
struct Virt_vmload : Bitfield< 1, 1> { };
};
struct Vmcb_clean : Register<0x0C0, 64> {
struct Clean_bits : Bitfield< 0,32> { };
};
struct Nrip : Register<0x0C8,64> { };
/*
* This is a 128bit field in the documentation.
* Split it up into two parts for the time being.
*/
struct Fetch_part_1 : Register<0x0D0,64> {
struct Nr_bytes : Bitfield< 0, 8> { };
struct Guest_inst_lo : Bitfield< 8,56> { };
};
struct Fetch_part_2 : Register<0x0D8,64> {
struct Guest_inst_hi : Bitfield< 0,64> { };
};
struct Avic_1 : Register<0x0E0,64> {
struct Apic_page_ptr : Bitfield< 0,52> { };
};
struct Avic_2 : Register<0x0F0,64> {
struct Avic_log_table : Bitfield<12,52> { };
};
struct Avic_3 : Register<0x0F8,64> {
struct Avic_max_idx : Bitfield< 0, 8> { };
struct Avic_phys_ptr : Bitfield<12,52> { };
};
struct Vmsa : Register<0x108,64> {
struct Vmsa_ptr : Bitfield<12,52> { };
};
} __attribute__((packed));
#endif /* _INCLUDE__SPEC__PC__SVM_H_ */