Skip to content

Commit 57c5608

Browse files
committed
different impl
1 parent aa3f9e6 commit 57c5608

File tree

3 files changed

+142
-91
lines changed

3 files changed

+142
-91
lines changed

openhcl/virt_mshv_vtl/src/processor/snp/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ use virt::x86::SegmentRegister;
6161
use virt::x86::TableRegister;
6262
use virt_support_apic::ApicClient;
6363
use virt_support_x86emu::emulate::EmulatorSupport as X86EmulatorSupport;
64+
use virt_support_x86emu::emulate::ProbeResult;
6465
use virt_support_x86emu::emulate::emulate_io;
6566
use virt_support_x86emu::emulate::emulate_translate_gva;
6667
use virt_support_x86emu::translate::TranslationRegisters;
@@ -1737,9 +1738,17 @@ impl<T: CpuIo> X86EmulatorSupport for UhEmulationState<'_, '_, T, SnpBacked> {
17371738
}
17381739

17391740
fn physical_address(&self) -> Option<u64> {
1741+
// TODO SNP: Is this correct? Should this only be valid on NPFs?
17401742
Some(self.vp.runner.vmsa(self.vtl).exit_info2())
17411743
}
17421744

1745+
fn probe_gpa(&self, gpa: u64, gm: &guestmem::GuestMemory) -> ProbeResult {
1746+
// TODO SNP: Implement this correctly. See TDX's implementation for an
1747+
// example.
1748+
let _ = (gpa, gm);
1749+
ProbeResult::Emulate
1750+
}
1751+
17431752
fn initial_gva_translation(&self) -> Option<virt_support_x86emu::emulate::InitialTranslation> {
17441753
None
17451754
}

openhcl/virt_mshv_vtl/src/processor/tdx/mod.rs

Lines changed: 99 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ use virt_support_apic::ApicClient;
6969
use virt_support_apic::OffloadNotSupported;
7070
use virt_support_x86emu::emulate::EmulatedMemoryOperation;
7171
use virt_support_x86emu::emulate::EmulatorSupport as X86EmulatorSupport;
72+
use virt_support_x86emu::emulate::ProbeResult;
7273
use virt_support_x86emu::emulate::TranslateMode;
7374
use virt_support_x86emu::emulate::emulate_insn_memory_op;
7475
use virt_support_x86emu::emulate::emulate_io;
@@ -1998,7 +1999,6 @@ impl UhProcessor<'_, TdxBacked> {
19981999
&mut self.backing.vtls[intercepted_vtl].exit_stats.wbinvd
19992000
}
20002001
VmxExitBasic::EPT_VIOLATION => {
2001-
let gpa = exit_info.gpa();
20022002
let ept_info = VmxEptExitQualification::from(exit_info.qualification());
20032003
// If this was an EPT violation while handling an iret, and
20042004
// that iret cleared the NMI blocking state, restore it.
@@ -2016,7 +2016,16 @@ impl UhProcessor<'_, TdxBacked> {
20162016
.into();
20172017
assert!(!old_interruptibility.blocked_by_nmi());
20182018
} else {
2019-
self.handle_ept(intercepted_vtl, dev, gpa, ept_info).await?;
2019+
// Emulate the access.
2020+
self.emulate(
2021+
dev,
2022+
self.backing.vtls[intercepted_vtl]
2023+
.interruption_information
2024+
.valid(),
2025+
intercepted_vtl,
2026+
TdxEmulationCache::default(),
2027+
)
2028+
.await?;
20202029
}
20212030

20222031
&mut self.backing.vtls[intercepted_vtl].exit_stats.ept_violation
@@ -2361,94 +2370,6 @@ impl UhProcessor<'_, TdxBacked> {
23612370
self.backing.vtls[vtl].exception_error_code = 0;
23622371
}
23632372

2364-
fn inject_mc(&mut self, vtl: GuestVtl) {
2365-
self.backing.vtls[vtl].interruption_information = InterruptionInformation::new()
2366-
.with_valid(true)
2367-
.with_vector(x86defs::Exception::MACHINE_CHECK.0)
2368-
.with_interruption_type(INTERRUPT_TYPE_HARDWARE_EXCEPTION);
2369-
}
2370-
2371-
async fn handle_ept(
2372-
&mut self,
2373-
intercepted_vtl: GuestVtl,
2374-
dev: &impl CpuIo,
2375-
gpa: u64,
2376-
ept_info: VmxEptExitQualification,
2377-
) -> Result<(), VpHaltReason<UhRunVpError>> {
2378-
// vtom may be 0 if we are hiding isolation
2379-
let vtom = self.partition.caps.vtom.unwrap_or(0);
2380-
let is_shared = (gpa & vtom) == vtom && vtom != 0;
2381-
let canonical_gpa = gpa & !vtom;
2382-
2383-
// Only emulate the access if the gpa is expected to be accessible. This
2384-
// means, the gpa was described to VTL0 in some form as memory or mmio,
2385-
// and the hardware did not generate an exit for a private/shared
2386-
// violation.
2387-
let address_type = self
2388-
.partition
2389-
.lower_vtl_memory_layout
2390-
.probe_address(canonical_gpa);
2391-
2392-
match address_type {
2393-
Some(AddressType::Mmio) => {
2394-
// Emulate the access.
2395-
self.emulate(
2396-
dev,
2397-
self.backing.vtls[intercepted_vtl]
2398-
.interruption_information
2399-
.valid(),
2400-
intercepted_vtl,
2401-
TdxEmulationCache::default(),
2402-
)
2403-
.await?;
2404-
}
2405-
Some(AddressType::Ram) => {
2406-
// TODO TDX: This path changes when we support VTL page
2407-
// protections and MNF. That will require injecting events to
2408-
// VTL1 or other handling.
2409-
//
2410-
// For now, we just check if the exit was suprious or if we
2411-
// should inject a machine check. An exit is considered spurious
2412-
// if the gpa is accessible.
2413-
if self.partition.gm[intercepted_vtl].check_gpa_readable(gpa) {
2414-
tracelimit::warn_ratelimited!(gpa, "possible spurious EPT violation, ignoring");
2415-
} else {
2416-
// TODO: It would be better to show what exact bitmap check
2417-
// failed, but that requires some refactoring of how the
2418-
// different bitmaps are stored. Do this when we support VTL
2419-
// protections or MNF.
2420-
//
2421-
// If we entered this path, it means the bitmap check on
2422-
// `check_gpa_readable` failed so we can assume that if the
2423-
// address is shared, the actual state of the page is
2424-
// private, and vice versa. This is because the address
2425-
// should have already been checked to be valid memory
2426-
// described to the guest or not.
2427-
tracelimit::warn_ratelimited!(
2428-
gpa,
2429-
is_shared,
2430-
?ept_info,
2431-
"guest accessed inaccessible gpa, injecting MC"
2432-
);
2433-
self.inject_mc(intercepted_vtl);
2434-
}
2435-
}
2436-
None => {
2437-
// The guest should never attempt to access address that are not
2438-
// described in mmio or ram. Inject a machine check.
2439-
tracelimit::warn_ratelimited!(
2440-
gpa,
2441-
is_shared,
2442-
?ept_info,
2443-
"guest accessed gpa not described in memory layout, injecting MC"
2444-
);
2445-
self.inject_mc(intercepted_vtl);
2446-
}
2447-
}
2448-
2449-
Ok(())
2450-
}
2451-
24522373
fn handle_tdvmcall(&mut self, dev: &impl CpuIo, intercepted_vtl: GuestVtl) {
24532374
let regs = self.runner.tdx_enter_guest_gps();
24542375
if regs[TdxGp::R10] == 0 {
@@ -2852,7 +2773,94 @@ impl<T: CpuIo> X86EmulatorSupport for UhEmulationState<'_, '_, T, TdxBacked> {
28522773
}
28532774

28542775
fn physical_address(&self) -> Option<u64> {
2855-
Some(TdxExit(self.vp.runner.tdx_vp_enter_exit_info()).gpa())
2776+
let exit_info = TdxExit(self.vp.runner.tdx_vp_enter_exit_info());
2777+
2778+
// The gpa field on exit_info is only valid for EPT violations.
2779+
if exit_info.code().vmx_exit().basic_reason() == VmxExitBasic::EPT_VIOLATION {
2780+
Some(exit_info.gpa())
2781+
} else {
2782+
None
2783+
}
2784+
}
2785+
2786+
fn probe_gpa(&self, gpa: u64, gm: &guestmem::GuestMemory) -> ProbeResult {
2787+
let vtom = self.vp.partition.caps.vtom.unwrap_or(0);
2788+
let hide_isolation = self.vp.cvm_partition().hide_isolation;
2789+
let is_shared = (gpa & vtom) == vtom && vtom != 0;
2790+
let canonical_gpa = gpa & !vtom;
2791+
2792+
// Only emulate the access if the gpa is expected to be accessible. This
2793+
// means, the gpa was described to VTL0 in some form as memory or mmio,
2794+
// and the hardware did not generate an exit for a private/shared
2795+
// violation.
2796+
let address_type = self
2797+
.vp
2798+
.partition
2799+
.lower_vtl_memory_layout
2800+
.probe_address(canonical_gpa);
2801+
2802+
match address_type {
2803+
Some(AddressType::Mmio) => {
2804+
// Emulate the access.
2805+
ProbeResult::Emulate
2806+
}
2807+
Some(AddressType::Ram) => {
2808+
// TODO TDX: This path changes when we support VTL page
2809+
// protections and MNF. That will require injecting events to
2810+
// VTL1 or other handling.
2811+
//
2812+
// For now, we just check if the exit was suprious or if we
2813+
// should inject a machine check. An exit is considered spurious
2814+
// if the gpa is accessible.
2815+
if gm.check_gpa_readable(gpa) {
2816+
tracelimit::warn_ratelimited!(gpa, "possible spurious EPT violation, ignoring");
2817+
ProbeResult::EarlyReturn
2818+
} else {
2819+
// TODO: It would be better to show what exact bitmap check
2820+
// failed, but that requires some refactoring of how the
2821+
// different bitmaps are stored. Do this when we support VTL
2822+
// protections or MNF.
2823+
//
2824+
// If we entered this path, it means the bitmap check on
2825+
// `check_gpa_readable` failed so we can assume that if the
2826+
// address is shared, the actual state of the page is
2827+
// private, and vice versa. This is because the address
2828+
// should have already been checked to be valid memory
2829+
// described to the guest or not.
2830+
tracelimit::warn_ratelimited!(
2831+
gpa,
2832+
is_shared,
2833+
"guest accessed inaccessible gpa, injecting MC"
2834+
);
2835+
ProbeResult::InjectMachineCheck
2836+
}
2837+
}
2838+
None => {
2839+
if hide_isolation {
2840+
// If isolation is being hidden, the guest is not modifying
2841+
// visibility that would result in the need to inject
2842+
// machine checks and instead all RAM should be accessible.
2843+
// There may be other devices that are being emulated by the
2844+
// paravisor (such as xapic) that do not exist in memory
2845+
// layout, so allow all emulation.
2846+
//
2847+
// TODO: If we update the memory layout to be more precise
2848+
// and include all devices, then we could revisit this
2849+
// check.
2850+
ProbeResult::Emulate
2851+
} else {
2852+
// A guest which is told about isolation should never
2853+
// attempt to access address that are not described in mmio
2854+
// or ram. Inject a machine check.
2855+
tracelimit::warn_ratelimited!(
2856+
gpa,
2857+
is_shared,
2858+
"guest accessed gpa not described in memory layout, injecting MC"
2859+
);
2860+
ProbeResult::InjectMachineCheck
2861+
}
2862+
}
2863+
}
28562864
}
28572865

28582866
fn initial_gva_translation(&self) -> Option<virt_support_x86emu::emulate::InitialTranslation> {

vmm_core/virt_support_x86emu/src/emulate.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ use x86emu::Segment;
2525
use zerocopy::FromBytes;
2626
use zerocopy::IntoBytes;
2727

28+
/// What the emulator should do after probing a gp. See
29+
/// [`EmulatorSupport::probe_gpa`].
30+
pub enum ProbeResult {
31+
/// The emulator should continue attempting to emulate the instruction.
32+
Emulate,
33+
/// The emulator should return early, without doing anything else.
34+
EarlyReturn,
35+
/// The emulator should inject a machine check.
36+
InjectMachineCheck,
37+
}
38+
2839
/// Support routines for the emulator.
2940
pub trait EmulatorSupport {
3041
/// The hypervisor error type.
@@ -78,6 +89,13 @@ pub trait EmulatorSupport {
7889
/// The physical address that caused the fault.
7990
fn physical_address(&self) -> Option<u64>;
8091

92+
/// Probe the gpa of the physical address that caused the fault. The action
93+
/// returned is what the emulator should do next.
94+
fn probe_gpa(&self, gpa: u64, gm: &GuestMemory) -> ProbeResult {
95+
let _ = (gpa, gm);
96+
ProbeResult::Emulate
97+
}
98+
8199
/// The gva translation included in the intercept message header, if valid.
82100
fn initial_gva_translation(&self) -> Option<InitialTranslation>;
83101

@@ -277,6 +295,17 @@ pub async fn emulate<T: EmulatorSupport>(
277295
gm: &GuestMemory,
278296
dev: &impl CpuIo,
279297
) -> Result<(), VpHaltReason<T::Error>> {
298+
if let Some(gpa) = support.physical_address() {
299+
match support.probe_gpa(gpa, gm) {
300+
ProbeResult::Emulate => {}
301+
ProbeResult::EarlyReturn => return Ok(()),
302+
ProbeResult::InjectMachineCheck => {
303+
support.inject_pending_event(mc_event());
304+
return Ok(());
305+
}
306+
}
307+
}
308+
280309
let vendor = support.vendor();
281310

282311
let mut bytes = [0; 16];
@@ -1013,6 +1042,11 @@ fn gpf_event() -> hvdef::HvX64PendingEvent {
10131042
make_exception_event(Exception::GENERAL_PROTECTION_FAULT, Some(0), None)
10141043
}
10151044

1045+
/// Generates a machine check pending event
1046+
fn mc_event() -> hvdef::HvX64PendingEvent {
1047+
make_exception_event(Exception::MACHINE_CHECK, None, None)
1048+
}
1049+
10161050
/// Generates the appropriate event for a VTL access error based
10171051
/// on the intercepting VTL
10181052
fn vtl_access_event(

0 commit comments

Comments
 (0)