From cb69c59fa38cad13bf3f9a6ca292b62d5ada3513 Mon Sep 17 00:00:00 2001 From: Benjamin Lamowski Date: Wed, 21 Dec 2022 00:13:19 +0100 Subject: [PATCH] base-hw: implement VMCB data structure for AMD SVM Ref #4826 --- .../base-hw/lib/mk/spec/x86_64/core-hw-pc.mk | 1 + .../core/spec/x86_64/virtualization/board.h | 8 +- .../spec/x86_64/virtualization/kernel/svm.cc | 156 ++++++++ .../spec/x86_64/virtualization/kernel/vm.cc | 15 +- .../src/core/spec/x86_64/virtualization/svm.h | 333 ++++++++++++++++++ 5 files changed, 511 insertions(+), 2 deletions(-) create mode 100644 repos/base-hw/src/core/spec/x86_64/virtualization/kernel/svm.cc create mode 100644 repos/base-hw/src/core/spec/x86_64/virtualization/svm.h diff --git a/repos/base-hw/lib/mk/spec/x86_64/core-hw-pc.mk b/repos/base-hw/lib/mk/spec/x86_64/core-hw-pc.mk index 89f5ddb86d..0856786fbb 100644 --- a/repos/base-hw/lib/mk/spec/x86_64/core-hw-pc.mk +++ b/repos/base-hw/lib/mk/spec/x86_64/core-hw-pc.mk @@ -20,6 +20,7 @@ SRC_S += spec/x86_64/exception_vector.s SRC_CC += kernel/cpu_mp.cc SRC_CC += kernel/vm_thread_on.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 += vm_session_common.cc SRC_CC += vm_session_component.cc diff --git a/repos/base-hw/src/core/spec/x86_64/virtualization/board.h b/repos/base-hw/src/core/spec/x86_64/virtualization/board.h index 822b21f260..47c928ea7a 100644 --- a/repos/base-hw/src/core/spec/x86_64/virtualization/board.h +++ b/repos/base-hw/src/core/spec/x86_64/virtualization/board.h @@ -18,8 +18,10 @@ #include #include -#include #include +#include +#include +#include namespace Board { @@ -46,6 +48,10 @@ namespace Kernel { struct Board::Vcpu_context { Vcpu_context(Kernel::Cpu & cpu); + void initialize_svm(Kernel::Cpu &cpu, void *table); + + Vmcb vmcb; + Genode::Align_at regs; }; #endif /* _CORE__SPEC__PC__VIRTUALIZATION__BOARD_H_ */ diff --git a/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/svm.cc b/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/svm.cc new file mode 100644 index 0000000000..c021de210e --- /dev/null +++ b/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/svm.cc @@ -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 +#include +#include +#include +#include +#include + +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(id); + write(dummy_msrpm()); + write(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 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(1); + write((Genode::addr_t) table_ptr); + + write(1); /* See 15.2 */ + write(17); /* AC */ + + enforce_intercepts(); +} + + +/* + * Enforce SVM intercepts + */ +void Vmcb::enforce_intercepts(Genode::uint32_t desired_primary, Genode::uint32_t desired_secondary) +{ + write( + 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( + 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 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 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); +} diff --git a/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/vm.cc b/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/vm.cc index 21e3623e88..8f615c8b1a 100644 --- a/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/vm.cc +++ b/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/vm.cc @@ -14,19 +14,24 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include +#include +#include using Genode::addr_t; using Kernel::Cpu; using Kernel::Vm; +using Board::Vmcb; Vm::Vm(Irq::Pool & user_irq_pool, @@ -53,11 +58,19 @@ Vm::~Vm() } +void Vm::proceed(Cpu &) +{ +} + + void Vm::exception(Cpu &) { } -void Vm::proceed(Cpu &) +Board::Vcpu_context::Vcpu_context(Cpu &) +: + vmcb(0), + regs(1) { } diff --git a/repos/base-hw/src/core/spec/x86_64/virtualization/svm.h b/repos/base-hw/src/core/spec/x86_64/virtualization/svm.h new file mode 100644 index 0000000000..157687a5f1 --- /dev/null +++ b/repos/base-hw/src/core/spec/x86_64/virtualization/svm.h @@ -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 +#include +#include +#include + +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_ */