Skip to main content

virt_support_aarch64emu/
emulate.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Wrapper around aarch64emu for emulating single instructions to handle VM exits.
5
6use crate::translate::TranslationRegisters;
7use aarch64defs::EsrEl2;
8use aarch64defs::FaultStatusCode;
9use aarch64defs::IssInstructionAbort;
10use aarch64emu::AccessCpuState;
11use aarch64emu::InterceptState;
12use cvm_tracing::CVM_ALLOWED;
13use cvm_tracing::CVM_CONFIDENTIAL;
14use guestmem::GuestMemory;
15use guestmem::GuestMemoryError;
16use hvdef::HV_PAGE_SIZE;
17use hvdef::HvAarch64PendingEvent;
18use hvdef::HvAarch64PendingEventType;
19use hvdef::HvInterceptAccessType;
20use hvdef::HvMapGpaFlags;
21use thiserror::Error;
22use virt::EmulatorMonitorSupport;
23use virt::VpHaltReason;
24use virt::io::CpuIo;
25use vm_topology::processor::VpIndex;
26use zerocopy::FromBytes;
27use zerocopy::IntoBytes;
28
29/// Support routines for the emulator.
30pub trait EmulatorSupport: AccessCpuState {
31    /// The current VP index.
32    fn vp_index(&self) -> VpIndex;
33
34    /// The physical address that caused the fault.
35    fn physical_address(&self) -> Option<u64>;
36
37    /// The gva translation included in the intercept message header, if valid.
38    fn initial_gva_translation(&mut self) -> Option<InitialTranslation>;
39
40    /// If interrupt pending is marked in the intercept message
41    fn interruption_pending(&self) -> bool;
42
43    /// Check that the current GPA is valid to access by the current VTL with the following access mode.
44    /// Returns true if valid to access.
45    fn check_vtl_access(
46        &mut self,
47        gpa: u64,
48        mode: TranslateMode,
49    ) -> Result<(), EmuCheckVtlAccessError>;
50
51    /// Translates a GVA to a GPA.
52    fn translate_gva(
53        &mut self,
54        gva: u64,
55        mode: TranslateMode,
56    ) -> Result<EmuTranslateResult, EmuTranslateError>;
57
58    /// Generates an event (exception, guest nested page fault, etc.) in the guest.
59    fn inject_pending_event(&mut self, event_info: HvAarch64PendingEvent);
60
61    /// Get access to monitor support for the emulator, if it supports it.
62    fn monitor_support(&self) -> Option<&dyn EmulatorMonitorSupport> {
63        None
64    }
65
66    /// Returns true if `gpa` is mapped for the specified permissions.
67    ///
68    /// If true, then the emulator will use [`GuestMemory`] to access the GPA,
69    /// and any failures will be fatal to the VM.
70    ///
71    /// If false, then the emulator will use [`CpuIo`] to access the GPA as
72    /// MMIO.
73    fn is_gpa_mapped(&self, gpa: u64, write: bool) -> bool;
74}
75
76pub trait TranslateGvaSupport {
77    type Error;
78
79    /// Gets the object used to access the guest memory.
80    fn guest_memory(&self) -> &GuestMemory;
81
82    /// Acquires the TLB lock for this processor.
83    fn acquire_tlb_lock(&mut self);
84
85    /// Returns the registers used to walk the page table.
86    fn registers(&mut self) -> Result<TranslationRegisters, Self::Error>;
87}
88
89/// The result of translate_gva on [`EmulatorSupport`].
90pub struct EmuTranslateResult {
91    /// The GPA result of the translation.
92    pub gpa: u64,
93    /// Whether the page is an overlay page.
94    /// Not all implementations return overlay page or event_info yet, so these values are optional
95    pub overlay_page: Option<bool>,
96}
97
98/// The translation, if any, provided in the intercept message and provided by [`EmulatorSupport`].
99#[derive(Debug)]
100pub struct InitialTranslation {
101    /// GVA for the translation
102    pub gva: u64,
103    /// Translated gpa for the gva
104    pub gpa: u64,
105    // Whether the translation has read, write, or execute permissions.
106    pub translate_mode: TranslateMode,
107}
108
109#[derive(Error, Debug)]
110pub enum EmuCheckVtlAccessError {
111    #[error("failed vtl permissions access for vtl {vtl:?} and access flags {denied_flags:?}")]
112    AccessDenied {
113        vtl: hvdef::Vtl,
114        denied_flags: HvMapGpaFlags,
115    },
116}
117
118#[derive(Error, Debug)]
119#[error("translate gva to gpa returned non-successful code {code:?}")]
120/// Error for a failed gva translation from [`EmulatorSupport`].
121pub struct EmuTranslateError {
122    /// Translate code of type hvdef::hypercall::TranslateGvaResultCode
123    /// Should != Success
124    pub code: hvdef::hypercall::TranslateGvaResultCode,
125    /// Pending event, if any, returned by hypervisor to go with the translate code.
126    pub event_info: Option<EsrEl2>,
127}
128
129/// The access type for a gva translation for [`EmulatorSupport`].
130#[derive(Debug, Copy, Clone, PartialEq, Eq)]
131pub enum TranslateMode {
132    /// A read operation.
133    Read,
134    /// A write operation.
135    Write,
136    /// An execute operation.
137    Execute,
138}
139
140/// The requested intercept access type isn't supported
141#[derive(Debug)]
142pub struct UnsupportedInterceptAccessType;
143
144impl TryFrom<HvInterceptAccessType> for TranslateMode {
145    type Error = UnsupportedInterceptAccessType;
146
147    fn try_from(access_type: HvInterceptAccessType) -> Result<Self, Self::Error> {
148        match access_type {
149            HvInterceptAccessType::READ => Ok(TranslateMode::Read),
150            HvInterceptAccessType::WRITE => Ok(TranslateMode::Write),
151            HvInterceptAccessType::EXECUTE => Ok(TranslateMode::Execute),
152            _ => Err(UnsupportedInterceptAccessType),
153        }
154    }
155}
156
157#[derive(Debug, Error)]
158enum EmulationError {
159    #[error("an interrupt caused the memory access exit")]
160    InterruptionPending,
161    #[error("emulator error (instruction {bytes:02x?})")]
162    Emulator {
163        bytes: Vec<u8>,
164        #[source]
165        error: aarch64emu::Error<Error>,
166    },
167}
168
169/// Emulates an instruction.
170pub async fn emulate<T: EmulatorSupport>(
171    support: &mut T,
172    intercept_state: &InterceptState,
173    emu_mem: &GuestMemory,
174    dev: &impl CpuIo,
175) -> Result<(), VpHaltReason> {
176    emulate_core(support, intercept_state, emu_mem, dev)
177        .await
178        .map_err(|e| {
179            let pc = support.pc();
180            let sp = support.sp();
181            let cpsr = support.cpsr();
182            let gpa = support.physical_address();
183            let initial_translation = support.initial_gva_translation();
184            let int_pend = support.interruption_pending();
185            let gpa_mapped = gpa.map(|a| support.is_gpa_mapped(a, false));
186            tracing::warn!(
187                CVM_ALLOWED,
188                pc,
189                sp,
190                ?cpsr,
191                gpa,
192                ?initial_translation,
193                int_pend,
194                gpa_mapped,
195                "emulation failed"
196            );
197            let xs = (0..=30).map(|i| (i, support.x(i))).collect::<Vec<_>>();
198            tracing::warn!(CVM_CONFIDENTIAL, ?xs, "emulation failed");
199            dev.fatal_error(e.into())
200        })
201}
202
203async fn emulate_core<T: EmulatorSupport>(
204    support: &mut T,
205    intercept_state: &InterceptState,
206    gm: &GuestMemory,
207    dev: &impl CpuIo,
208) -> Result<(), EmulationError> {
209    tracing::trace!(physical_address = support.physical_address(), "emulating");
210
211    if support.interruption_pending() {
212        // This means a fault or interruption *caused* the intercept
213        // (and only really applies to memory intercept handling).
214        // An example of how this could happen is if the
215        // interrupt vector table itself is in mmio space; taking an
216        // interrupt at that point requires that the processor reads the
217        // vector out of the table, which generates an mmio intercept,
218        // but not one associated with any particular instruction.
219        // Therefore, there is nothing to emulate.
220        //
221        // A fault can't be injected into the guest because that could
222        // cause an infinite loop (as the processor tries to get the trap
223        // vector out of the mmio-ed vector table).  Just give up.
224
225        return Err(EmulationError::InterruptionPending);
226    }
227
228    let mut cpu = EmulatorCpu::new(gm, dev, support, intercept_state.syndrome);
229    let pc = cpu.pc();
230    let result = {
231        let mut emu = aarch64emu::Emulator::new(&mut cpu, intercept_state);
232        emu.run().await
233    };
234
235    let instruction_bytes = if intercept_state.instruction_byte_count > 0 {
236        intercept_state.instruction_bytes.to_vec()
237    } else {
238        vec![0, 0, 0, 0]
239    };
240    cpu.commit();
241
242    if let Err(e) = result {
243        match *e {
244            aarch64emu::Error::MemoryAccess(addr, kind, err) => {
245                if inject_memory_access_fault(addr, &err, support, intercept_state.syndrome) {
246                    return Ok(());
247                } else {
248                    return Err(EmulationError::Emulator {
249                        bytes: instruction_bytes,
250                        error: aarch64emu::Error::MemoryAccess(addr, kind, err),
251                    });
252                };
253            }
254            err => {
255                tracing::error!(
256                    err = &err as &dyn std::error::Error,
257                    len = instruction_bytes.len(),
258                    physical_address = cpu.support.physical_address(),
259                    "failed to emulate instruction"
260                );
261                let syndrome: EsrEl2 = IssInstructionAbort::new().into();
262                cpu.support
263                    .inject_pending_event(make_exception_event(syndrome, pc));
264            }
265        }
266    }
267
268    Ok(())
269}
270
271/// For storing gva to gpa translations in a cache in [`EmulatorCpu`]
272struct GvaGpaCacheEntry {
273    gva_page: u64,
274    gpa_page: u64,
275    translate_mode: TranslateMode,
276}
277
278impl GvaGpaCacheEntry {
279    pub fn new(gva: u64, gpa: u64, translate_mode: TranslateMode) -> Self {
280        GvaGpaCacheEntry {
281            gva_page: gva >> hvdef::HV_PAGE_SHIFT,
282            gpa_page: gpa >> hvdef::HV_PAGE_SHIFT,
283            translate_mode,
284        }
285    }
286}
287
288struct EmulatorCpu<'a, T, U> {
289    gm: &'a GuestMemory,
290    support: &'a mut T,
291    dev: &'a U,
292    cached_translation: Option<GvaGpaCacheEntry>,
293    syndrome: EsrEl2,
294}
295
296#[derive(Debug, Error)]
297enum Error {
298    #[error("translation error")]
299    Translate(#[source] TranslateGvaError, Option<EsrEl2>),
300    #[error("vtl permissions denied access for gpa {gpa}")]
301    NoVtlAccess {
302        gpa: u64,
303        intercepting_vtl: hvdef::Vtl,
304        denied_flags: HvMapGpaFlags,
305    },
306    #[error("failed to access mapped memory")]
307    Memory(#[source] GuestMemoryError),
308}
309
310/// Result of a gva translation in [`EmulatorCpu`]
311#[derive(Error, Debug)]
312enum TranslateGvaError {
313    #[error("gpa access denied code {0:?}")]
314    AccessDenied(hvdef::hypercall::TranslateGvaResultCode),
315    #[error("write on overlay page")]
316    OverlayPageWrite,
317    #[error("translation failed with unknown code {0:?}")]
318    UnknownCode(hvdef::hypercall::TranslateGvaResultCode),
319    #[error("translation failed with an intercept code")]
320    Intercept,
321    #[error("translation failed with a page fault-related code {0:?}")]
322    PageFault(hvdef::hypercall::TranslateGvaResultCode),
323}
324
325impl<T: EmulatorSupport, U> EmulatorCpu<'_, T, U> {
326    pub fn new<'a>(
327        gm: &'a GuestMemory,
328        dev: &'a U,
329        support: &'a mut T,
330        syndrome: EsrEl2,
331    ) -> EmulatorCpu<'a, T, U> {
332        let init_cache = {
333            if let Some(InitialTranslation {
334                gva,
335                gpa,
336                translate_mode,
337            }) = support.initial_gva_translation()
338            {
339                tracing::trace!(
340                    ?gva,
341                    ?gpa,
342                    ?translate_mode,
343                    "adding initial translation to cache"
344                );
345                Some(GvaGpaCacheEntry::new(gva, gpa, translate_mode))
346            } else {
347                None
348            }
349        };
350
351        EmulatorCpu {
352            gm,
353            dev,
354            support,
355            cached_translation: init_cache,
356            syndrome,
357        }
358    }
359
360    pub fn translate_gva(&mut self, gva: u64, mode: TranslateMode) -> Result<u64, Error> {
361        type TranslateCode = hvdef::hypercall::TranslateGvaResultCode;
362
363        // Note about invalid accesses at user mode: the exception code will
364        // distinguish user vs kernel via _LOWER (e.g. kernel -> DATA_ABORT,
365        // user -> DATA_ABORT_LOWER). We don't track that here though because
366        // Hyper-V only takes the general version and will convert it depending
367        // on the last execution state it has recorded.
368
369        if let Some(GvaGpaCacheEntry {
370            gva_page: cached_gva_page,
371            gpa_page: cached_gpa_page,
372            translate_mode: cached_mode,
373        }) = self.cached_translation
374        {
375            if ((gva >> hvdef::HV_PAGE_SHIFT) == cached_gva_page) && (cached_mode == mode) {
376                tracing::trace!(
377                    ?gva,
378                    ?cached_gva_page,
379                    cached_gpa_page,
380                    ?cached_mode,
381                    "using cached entry"
382                );
383                return Ok((cached_gpa_page << hvdef::HV_PAGE_SHIFT) + (gva & (HV_PAGE_SIZE - 1)));
384            }
385        };
386
387        match self.support.translate_gva(gva, mode) {
388            Ok(EmuTranslateResult { gpa, overlay_page }) => {
389                if overlay_page.is_some()
390                    && overlay_page
391                        .expect("should've already checked that the overlay page has value")
392                    && (mode == TranslateMode::Write)
393                {
394                    // Parity: Reads of overlay pages are allowed for x64.
395                    let mut syndrome: EsrEl2 = crate::translate::Error::GpaUnmapped(3).into();
396                    syndrome.set_il(self.syndrome.il());
397                    return Err(Error::Translate(TranslateGvaError::OverlayPageWrite, None));
398                }
399
400                let new_cache_entry = GvaGpaCacheEntry::new(gva, gpa, mode);
401
402                self.cached_translation = Some(new_cache_entry);
403                Ok(gpa)
404            }
405            Err(EmuTranslateError { code, event_info }) => match code {
406                TranslateCode::INTERCEPT => {
407                    tracing::trace!("translate gva to gpa returned an intercept event");
408                    Err(Error::Translate(TranslateGvaError::Intercept, event_info))
409                }
410                TranslateCode::GPA_NO_READ_ACCESS
411                | TranslateCode::GPA_NO_WRITE_ACCESS
412                | TranslateCode::GPA_UNMAPPED
413                | TranslateCode::GPA_ILLEGAL_OVERLAY_ACCESS
414                | TranslateCode::GPA_UNACCEPTED => {
415                    tracing::trace!("translate gva to gpa returned no access to page {:?}", code);
416                    Err(Error::Translate(
417                        TranslateGvaError::AccessDenied(code),
418                        event_info,
419                    ))
420                }
421                TranslateCode::PAGE_NOT_PRESENT
422                | TranslateCode::PRIVILEGE_VIOLATION
423                | TranslateCode::INVALID_PAGE_TABLE_FLAGS => {
424                    tracing::trace!(gva, ?code, "translate gva to gpa returned");
425                    Err(Error::Translate(
426                        TranslateGvaError::PageFault(code),
427                        event_info,
428                    ))
429                }
430                TranslateCode::SUCCESS => unreachable!(),
431                _ => {
432                    tracing::trace!(
433                        "translate error: unknown translation result code {:?}",
434                        code
435                    );
436
437                    Err(Error::Translate(TranslateGvaError::UnknownCode(code), None))
438                }
439            },
440        }
441    }
442
443    pub fn check_vtl_access(&mut self, gpa: u64, mode: TranslateMode) -> Result<(), Error> {
444        self.support
445            .check_vtl_access(gpa, mode)
446            .map_err(|e| match e {
447                EmuCheckVtlAccessError::AccessDenied { vtl, denied_flags } => Error::NoVtlAccess {
448                    gpa,
449                    intercepting_vtl: vtl,
450                    denied_flags,
451                },
452            })
453    }
454
455    fn check_monitor_write(&self, gpa: u64, bytes: &[u8]) -> bool {
456        if let Some(monitor_support) = self.support.monitor_support() {
457            monitor_support.check_write(gpa, bytes)
458        } else {
459            false
460        }
461    }
462
463    fn check_monitor_read(&self, gpa: u64, bytes: &mut [u8]) -> bool {
464        if let Some(monitor_support) = self.support.monitor_support() {
465            monitor_support.check_read(gpa, bytes)
466        } else {
467            false
468        }
469    }
470}
471
472impl<T: EmulatorSupport, U: CpuIo> aarch64emu::Cpu for EmulatorCpu<'_, T, U> {
473    type Error = Error;
474
475    async fn read_instruction(&mut self, gva: u64, bytes: &mut [u8]) -> Result<(), Self::Error> {
476        let gpa = match self.translate_gva(gva, TranslateMode::Execute) {
477            Ok(g) => g,
478            Err(e) => return Err(e),
479        };
480        self.read_physical_memory(gpa, bytes, true).await
481    }
482
483    async fn read_memory(&mut self, gva: u64, bytes: &mut [u8]) -> Result<(), Self::Error> {
484        let gpa = match self.translate_gva(gva, TranslateMode::Read) {
485            Ok(g) => g,
486            Err(e) => return Err(e),
487        };
488        self.read_physical_memory(gpa, bytes, false).await
489    }
490
491    async fn read_physical_memory(
492        &mut self,
493        gpa: u64,
494        bytes: &mut [u8],
495        exec: bool,
496    ) -> Result<(), Self::Error> {
497        self.check_vtl_access(
498            gpa,
499            if exec {
500                TranslateMode::Execute
501            } else {
502                TranslateMode::Read
503            },
504        )?;
505
506        if self.check_monitor_read(gpa, bytes) {
507            Ok(())
508        } else if self.support.is_gpa_mapped(gpa, false) {
509            self.gm.read_at(gpa, bytes).map_err(Self::Error::Memory)
510        } else {
511            self.dev
512                .read_mmio(self.support.vp_index(), gpa, bytes)
513                .await;
514            Ok(())
515        }
516    }
517
518    async fn write_memory(&mut self, gva: u64, bytes: &[u8]) -> Result<(), Self::Error> {
519        let gpa = match self.translate_gva(gva, TranslateMode::Write) {
520            Ok(g) => g,
521            Err(e) => return Err(e),
522        };
523        self.write_physical_memory(gpa, bytes).await
524    }
525
526    async fn write_physical_memory(&mut self, gpa: u64, bytes: &[u8]) -> Result<(), Self::Error> {
527        self.check_vtl_access(gpa, TranslateMode::Write)?;
528
529        if self.support.is_gpa_mapped(gpa, true) {
530            self.gm.write_at(gpa, bytes).map_err(Self::Error::Memory)?;
531        } else {
532            self.dev
533                .write_mmio(self.support.vp_index(), gpa, bytes)
534                .await;
535        }
536        Ok(())
537    }
538
539    async fn compare_and_write_memory(
540        &mut self,
541        gva: u64,
542        current: &[u8],
543        new: &[u8],
544        success: &mut bool,
545    ) -> Result<(), Self::Error> {
546        let gpa = match self.translate_gva(gva, TranslateMode::Write) {
547            Ok(g) => g,
548            Err(e) => return Err(e),
549        };
550
551        self.check_vtl_access(gpa, TranslateMode::Write)?;
552
553        if self.check_monitor_write(gpa, new) {
554            *success = true;
555            Ok(())
556        } else if self.support.is_gpa_mapped(gpa, true) {
557            *success = match (current.len(), new.len()) {
558                (1, 1) => self
559                    .gm
560                    .compare_exchange(gpa, current[0], new[0])
561                    .map(|r| r.is_ok()),
562                (2, 2) => self
563                    .gm
564                    .compare_exchange(
565                        gpa,
566                        u16::from_ne_bytes(current.try_into().unwrap()),
567                        u16::from_ne_bytes(new.try_into().unwrap()),
568                    )
569                    .map(|r| r.is_ok()),
570                (4, 4) => self
571                    .gm
572                    .compare_exchange(
573                        gpa,
574                        u32::from_ne_bytes(current.try_into().unwrap()),
575                        u32::from_ne_bytes(new.try_into().unwrap()),
576                    )
577                    .map(|r| r.is_ok()),
578                (8, 8) => self
579                    .gm
580                    .compare_exchange(
581                        gpa,
582                        u64::from_ne_bytes(current.try_into().unwrap()),
583                        u64::from_ne_bytes(new.try_into().unwrap()),
584                    )
585                    .map(|r| r.is_ok()),
586                _ => panic!("unsupported size for compare and write memory"),
587            }
588            .map_err(Self::Error::Memory)?;
589            Ok(())
590        } else {
591            // Ignore the comparison aspect for device MMIO.
592            *success = true;
593            self.dev.write_mmio(self.support.vp_index(), gpa, new).await;
594            Ok(())
595        }
596    }
597}
598
599impl<T: AccessCpuState, U: CpuIo> AccessCpuState for EmulatorCpu<'_, T, U> {
600    fn commit(&mut self) {
601        self.support.commit()
602    }
603    fn x(&mut self, index: u8) -> u64 {
604        self.support.x(index)
605    }
606    fn update_x(&mut self, index: u8, data: u64) {
607        self.support.update_x(index, data)
608    }
609    fn q(&self, index: u8) -> u128 {
610        self.support.q(index)
611    }
612    fn update_q(&mut self, index: u8, data: u128) {
613        self.support.update_q(index, data)
614    }
615    fn d(&self, index: u8) -> u64 {
616        self.support.d(index)
617    }
618    fn update_d(&mut self, index: u8, data: u64) {
619        self.support.update_d(index, data)
620    }
621    fn h(&self, index: u8) -> u32 {
622        self.support.h(index)
623    }
624    fn update_h(&mut self, index: u8, data: u32) {
625        self.support.update_h(index, data)
626    }
627    fn s(&self, index: u8) -> u16 {
628        self.support.s(index)
629    }
630    fn update_s(&mut self, index: u8, data: u16) {
631        self.support.update_s(index, data)
632    }
633    fn b(&self, index: u8) -> u8 {
634        self.support.b(index)
635    }
636    fn update_b(&mut self, index: u8, data: u8) {
637        self.support.update_b(index, data)
638    }
639    fn sp(&mut self) -> u64 {
640        self.support.sp()
641    }
642    fn update_sp(&mut self, data: u64) {
643        self.support.update_sp(data)
644    }
645    fn fp(&mut self) -> u64 {
646        self.support.fp()
647    }
648    fn update_fp(&mut self, data: u64) {
649        self.support.update_fp(data)
650    }
651    fn lr(&mut self) -> u64 {
652        self.support.lr()
653    }
654    fn update_lr(&mut self, data: u64) {
655        self.support.update_lr(data)
656    }
657    fn pc(&mut self) -> u64 {
658        self.support.pc()
659    }
660    fn update_pc(&mut self, data: u64) {
661        self.support.update_pc(data)
662    }
663    fn cpsr(&mut self) -> aarch64defs::Cpsr64 {
664        self.support.cpsr()
665    }
666}
667
668/// Creates a pending event for the exception type
669pub fn make_exception_event(syndrome: EsrEl2, fault_address: u64) -> HvAarch64PendingEvent {
670    let exception_event = hvdef::HvAarch64PendingExceptionEvent {
671        header: hvdef::HvAarch64PendingEventHeader::new()
672            .with_event_pending(true)
673            .with_event_type(HvAarch64PendingEventType::EXCEPTION),
674        syndrome: syndrome.into(),
675        fault_address,
676        _padding: Default::default(),
677    };
678    let exception_event_bytes = exception_event.as_bytes();
679    let mut event = [0u8; 32];
680    event.as_mut_slice()[..exception_event_bytes.len()].copy_from_slice(exception_event_bytes);
681    HvAarch64PendingEvent::read_from_bytes(&event[..]).unwrap()
682}
683
684/// Injects an event into the guest if appropriate.
685///
686/// Returns true if an event was injected into the guest.
687/// In the case of false being returned, the caller can
688/// return the appropriate error code.
689#[must_use]
690fn inject_memory_access_fault<T: EmulatorSupport>(
691    gva: u64,
692    result: &Error,
693    support: &mut T,
694    syndrome: EsrEl2,
695) -> bool {
696    match result {
697        Error::Translate(e, event) => {
698            tracing::trace!(
699                error = e as &dyn std::error::Error,
700                "translation failed, injecting event"
701            );
702
703            if let Some(event_info) = event {
704                support.inject_pending_event(make_exception_event(*event_info, gva));
705
706                // The emulation did what it was supposed to do, which is throw a fault, so the emulation is done.
707                return true;
708            }
709            false
710        }
711        Error::NoVtlAccess {
712            gpa,
713            intercepting_vtl: _,
714            denied_flags,
715        } => {
716            tracing::trace!(
717                error = result as &dyn std::error::Error,
718                ?gva,
719                ?gpa,
720                "Vtl permissions checking failed"
721            );
722
723            let event = vtl_access_event(gva, *denied_flags, &syndrome);
724            support.inject_pending_event(event);
725            true
726        }
727        Error::Memory(_) => false,
728    }
729}
730
731/// Generates the appropriate event for a VTL access error based
732/// on the intercepting VTL
733fn vtl_access_event(
734    gva: u64,
735    denied_access: HvMapGpaFlags,
736    cur_syndrome: &EsrEl2,
737) -> HvAarch64PendingEvent {
738    assert!(denied_access.kernel_executable() || denied_access.user_executable());
739    let inst_abort = IssInstructionAbort::new().with_ifsc(FaultStatusCode::PERMISSION_FAULT_LEVEL2);
740    let mut syndrome: EsrEl2 = inst_abort.into();
741    syndrome.set_il(cur_syndrome.il());
742    make_exception_event(syndrome, gva)
743}
744
745/// Tries to emulate monitor page writes without taking the slower, full
746/// emulation path.
747///
748/// The caller must have already validated that the fault was due to a write to
749/// a monitor page GPA.
750///
751/// Returns the bit number being set within the monitor page.
752pub fn emulate_mnf_write_fast_path<T: EmulatorSupport>(
753    opcode: u32,
754    support: &mut T,
755    gm: &GuestMemory,
756    dev: &impl CpuIo,
757) -> Option<u64> {
758    if support.interruption_pending() {
759        return None;
760    }
761
762    // LDSETx / STSETx. A "fast path" is possible because we can assume the
763    // MNF page is always zero-filled.
764    if (opcode & 0x38203c00) == 0x38203000 {
765        let mut cpu = EmulatorCpu::new(gm, dev, support, EsrEl2::from_bits(0));
766        let size = (1 << (opcode >> 30)) * 8;
767        let rs = (opcode >> 16) as u8 & 0x1f;
768        let bitmask = if rs < 31 { cpu.x(rs) } else { 0 };
769        let bitmask = if size == 64 {
770            bitmask
771        } else {
772            bitmask & ((1 << size) - 1)
773        };
774        let rt = opcode as u8 & 0x1f;
775        if rt != 31 {
776            cpu.update_x(rt, 0);
777        }
778
779        let new_pc = cpu.pc().wrapping_add(4);
780        cpu.update_pc(new_pc);
781        cpu.commit();
782        Some(bitmask)
783    } else {
784        tracelimit::warn_ratelimited!(
785            opcode = format!("{:x}", opcode),
786            "MNF fast path unknown opcode"
787        );
788        None
789    }
790}