diff --git a/Cargo.lock b/Cargo.lock index 7df3e07147..90911576a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7431,6 +7431,7 @@ dependencies = [ "minimal_rt_build", "tmk_core", "tmk_macros", + "tmk_protocol", "x86defs", ] @@ -8017,6 +8018,7 @@ dependencies = [ name = "tmk_protocol" version = "0.0.0" dependencies = [ + "bitfield-struct 0.11.0", "zerocopy", ] diff --git a/openhcl/hcl/src/ioctl.rs b/openhcl/hcl/src/ioctl.rs index f46d919bfa..3043e00fc2 100755 --- a/openhcl/hcl/src/ioctl.rs +++ b/openhcl/hcl/src/ioctl.rs @@ -376,6 +376,7 @@ pub(crate) mod ioctls { const MSHV_VTL_RSI_SYSREG_READ: u16 = 0x42; const MSHV_VTL_RSI_SYSREG_WRITE: u16 = 0x43; const MSHV_VTL_RSI_SET_MEM_PERM: u16 = 0x44; + const MSHV_VTL_RSI_GET_IPA_STATE: u16 = 0x45; #[repr(C)] #[derive(Copy, Clone)] @@ -617,6 +618,14 @@ pub(crate) mod ioctls { cca::mshv_rsi_sysreg_rw ); + // CCA: Get the RIPAS state of an ipa + ioctl_readwrite!( + hcl_rsi_ipa_state_read, + MSHV_IOCTL, + MSHV_VTL_RSI_GET_IPA_STATE, + cca::mshv_rsi_get_ipa_state + ); + // CCA: Assign the address described by `mshv_rsi_set_mem_perm` // to a plane. // Note: This is a simplification of the memory access configuration. diff --git a/openhcl/hcl/src/ioctl/cca.rs b/openhcl/hcl/src/ioctl/cca.rs index 1c896da47c..d68c46ed23 100755 --- a/openhcl/hcl/src/ioctl/cca.rs +++ b/openhcl/hcl/src/ioctl/cca.rs @@ -16,6 +16,7 @@ use crate::ioctl::GetRegError; use crate::ioctl::HvError; use crate::ioctl::SetRegError; use crate::ioctl::ioctls::hcl_realm_config; +use crate::ioctl::ioctls::hcl_rsi_ipa_state_read; use crate::ioctl::ioctls::hcl_rsi_set_mem_perm; use crate::ioctl::ioctls::hcl_rsi_sysreg_read; use crate::ioctl::ioctls::hcl_rsi_sysreg_write; @@ -70,6 +71,15 @@ pub struct mshv_rsi_set_mem_perm { pub top_addr: u64, } +/// CCA: +#[repr(C)] +#[derive(Clone, Copy, Default)] +#[expect(missing_docs)] +pub struct mshv_rsi_get_ipa_state { + pub fipa: u64, + pub state: u64, +} + /// SystemReg is following encoding used by MSR/MRS which is different with /// the encoding RSI is using. The latter doesn't left shift the register /// number. @@ -204,6 +214,17 @@ impl ProcessorRunner<'_, Cca> { .rsi_sysreg_read(vtl, encode_rsi_sysreg(name), value) } + /// Get the ipa ripas state from the RMM + pub fn cca_ipa_state_read( + &self, + vtl: GuestVtl, + state: &mut mshv_rsi_get_ipa_state, + ) -> Result<(), Error> { + self.hcl + .rsi_get_ipa_state(vtl, state) + .map_err(|_| Error::InvalidRegisterValue) + } + /// Update the address of the `plane_run` structure in `mshv_vtl_run.context`. pub fn cca_set_plane_enter(&mut self) { // SAFETY: the run page is exclusively accessed through `&mut self` while @@ -469,6 +490,26 @@ impl MshvVtl { } Ok(()) } + + /// Get the ipa RIPAS state + pub fn rsi_get_ipa_state( + &self, + vtl: GuestVtl, + plane_state: &mut mshv_rsi_get_ipa_state, + ) -> Result<(), HvError> { + let _plane = match vtl { + GuestVtl::Vtl0 => 1, + _ => return Err(HvError::InvalidVtlState), + }; + + // SAFETY: Calling hcl_rsi_ipa_state_read ioctl with the correct arguments. + unsafe { + hcl_rsi_ipa_state_read(self.file.as_raw_fd(), plane_state) + .map_err(|_| HvError::InvalidVpState)?; + } + + Ok(()) + } } impl Hcl { @@ -501,4 +542,13 @@ impl Hcl { pub fn rsi_set_mem_perm(&self, vtl: GuestVtl, range: MemoryRange) -> Result<(), HvError> { self.mshv_vtl.rsi_set_mem_perm(vtl, &range) } + + /// getting ipa RIPAS state + pub fn rsi_get_ipa_state( + &self, + vtl: GuestVtl, + plane_state: &mut mshv_rsi_get_ipa_state, + ) -> Result<(), HvError> { + self.mshv_vtl.rsi_get_ipa_state(vtl, plane_state) + } } diff --git a/openhcl/virt_mshv_vtl/src/processor/cca/mod.rs b/openhcl/virt_mshv_vtl/src/processor/cca/mod.rs index af67368d3d..c655190b00 100644 --- a/openhcl/virt_mshv_vtl/src/processor/cca/mod.rs +++ b/openhcl/virt_mshv_vtl/src/processor/cca/mod.rs @@ -17,11 +17,15 @@ use crate::UhCvmVpState; use crate::UhPartitionInner; use crate::processor::InterceptMessageState; use aarch64defs::EsrEl2; +use aarch64defs::HpfarEl2; +use aarch64defs::InstructionAbortReason; use aarch64defs::IssDataAbort; +use aarch64defs::IssInstructionAbort; use aarch64defs::SystemReg; use aarch64defs::rsi::cca_rsi_plane_exit; use hcl::GuestVtl; use hcl::ioctl::cca::Cca; +use hcl::ioctl::cca::mshv_rsi_get_ipa_state; use hcl::ioctl::register; use hv1_emulator::hv::ProcessorVtlHv; use hv1_emulator::synic::ProcessorSynic; @@ -49,6 +53,20 @@ enum CcaUnsupportedExit { ExceptionClass { exception_class: u8, esr_el2: u64 }, #[error("CCA data abort with invalid instruction syndrome in ESR_EL2 {0:#x}")] InvalidDataAbortIss(u64), + #[error( + "CCA instruction abort: ESR_EL2 {esr_el2:#x}, ELR_EL2 {elr_el2:#x}, FAR_EL2 {far_el2:#x}, + FIPA {fipa:#x}, FIPA RIPAS state {fipa_state:#x}, IFSC {ifsc:#x}, reason {reason:?}, FNV {far_not_valid}" + )] + InstructionAbort { + esr_el2: u64, + elr_el2: u64, + far_el2: u64, + fipa: u64, + fipa_state: u8, + ifsc: u8, + reason: InstructionAbortReason, + far_not_valid: bool, + }, } const AARCH64_ZERO_REGISTER_INDEX: u8 = 31; @@ -169,6 +187,14 @@ impl<'a> CcaExit<'a> { self.0.far_el2 } + fn hpfar_el2(&self) -> HpfarEl2 { + self.0.hpfar_el2.into() + } + + fn elr_el2(&self) -> u64 { + self.0.elr_el2 + } + fn gpr_or_zero_register(&self, index: u8) -> Option { match index { AARCH64_ZERO_REGISTER_INDEX => Some(0), @@ -345,7 +371,56 @@ impl BackingPrivate for CcaBacked { } ExceptionClass::InstructionAbort => { // Handle instruction abort - todo!(); + let iss = IssInstructionAbort::from_bits(esr_el2.iss()); + + if iss.fnv() { + tracing::warn!("CCA InstructionAbort: FAR_EL2 is not valid"); + + return Err(dev.fatal_error( + CcaUnsupportedExit::InstructionAbort { + esr_el2: cca_exit.0.esr_el2, + elr_el2: cca_exit.elr_el2(), + far_el2: cca_exit.far_el2(), + fipa: 0, + fipa_state: u8::MAX, + ifsc: iss.ifsc().0, + reason: InstructionAbortReason::Unknown, + far_not_valid: iss.fnv(), + } + .into(), + )); + } + + let far = cca_exit.far_el2(); + let hpfar = cca_exit.hpfar_el2(); + let fipa = (hpfar.fipa() << 12) | (far & 0xfff); + let mut plane_state = mshv_rsi_get_ipa_state { + fipa, + state: u64::MAX, + }; + if let Err(e) = this.ipa_state_read(GuestVtl::Vtl0, &mut plane_state) { + tracing::warn!( + error = ?e, + fipa, + "failed to read IPA state; state will be u8::MAX which is unavailable" + ); + } + + let reason = InstructionAbortReason::from(iss.ifsc()); + + return Err(dev.fatal_error( + CcaUnsupportedExit::InstructionAbort { + esr_el2: cca_exit.0.esr_el2, + elr_el2: cca_exit.elr_el2(), + far_el2: cca_exit.far_el2(), + fipa, + fipa_state: plane_state.state as u8, + ifsc: iss.ifsc().0, + reason, + far_not_valid: iss.fnv(), + } + .into(), + )); } ExceptionClass::SimdAccess => { this.runner.cca_plane_no_trap_simd(); @@ -448,6 +523,16 @@ impl UhProcessor<'_, CcaBacked> { self.runner.cca_sysreg_read(vtl, reg, val) } + fn ipa_state_read( + &self, + vtl: GuestVtl, + state: &mut mshv_rsi_get_ipa_state, + ) -> Result<(), Error> { + self.runner + .cca_ipa_state_read(vtl, state) + .map_err(Error::Hcl) + } + fn set_plane_enter(&mut self) { self.runner.cca_set_plane_enter(); } diff --git a/tmk/simple_tmk/Cargo.toml b/tmk/simple_tmk/Cargo.toml index bf1d3e4b38..82666302ec 100644 --- a/tmk/simple_tmk/Cargo.toml +++ b/tmk/simple_tmk/Cargo.toml @@ -8,6 +8,7 @@ edition.workspace = true [dependencies] tmk_core.workspace = true +tmk_protocol.workspace = true tmk_macros.workspace = true [target.'cfg(target_arch = "x86_64")'.dependencies] diff --git a/tmk/simple_tmk/src/aarch64/mod.rs b/tmk/simple_tmk/src/aarch64/mod.rs new file mode 100755 index 0000000000..da8f5ba590 --- /dev/null +++ b/tmk/simple_tmk/src/aarch64/mod.rs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![cfg(target_arch = "aarch64")] +#![allow( + unsafe_code, + reason = "global_asm! required for AArch64 trampoline code" +)] + +use crate::prelude::*; +use tmk_protocol as _; + +core::arch::global_asm! { + ".global instruction_abort_outside_par_entry", + "instruction_abort_outside_par_entry:", + "movz x16, #0x0000", + "movk x16, #0x0000, lsl #16", + "movk x16, #0xffff, lsl #32", + "movk x16, #0x0000, lsl #48", + "br x16", +} + +unsafe extern "C" { + fn instruction_abort_outside_par_entry() -> !; +} + +#[tmk_test(expected_failure, linux_only)] +fn instruction_abort_outside_par(_: TestContext<'_>) { + log!("instruction_abort_outside_par"); + + // SAFETY: This test intentionally jumps to an assembly entry point that + // triggers an instruction abort. The symbol is defined in this module via + // `global_asm!` and is declared `-> !`, so it is not expected to return. + unsafe { + instruction_abort_outside_par_entry(); + } +} + +core::arch::global_asm! { + ".global instruction_abort_ripas_empty_entry", + "instruction_abort_ripas_empty_entry:", + "movz x16, #0x0000", + "br x16", +} + +unsafe extern "C" { + fn instruction_abort_ripas_empty_entry() -> !; +} + +#[tmk_test(expected_failure, linux_only)] +fn instruction_abort_ripas_empty(_: TestContext<'_>) { + log!("instruction_abort_ripas_empty"); + + // SAFETY: This test intentionally transfers control to an assembly entry + // point that executes from an address chosen to provoke the expected + // instruction abort. The entry point is defined above and never returns. + unsafe { + instruction_abort_ripas_empty_entry(); + } +} + +core::arch::global_asm! { + ".global instruction_abort_permissions_enabled_entry", + "instruction_abort_permissions_enabled_entry:", + "movz x16, #0xf000", + "movk x16, #0x847f, lsl #16", + "br x16", +} + +unsafe extern "C" { + fn instruction_abort_permissions_enabled_entry() -> !; +} + +#[tmk_test(expected_failure, linux_only)] +fn instruction_abort_permissions_enabled(_: TestContext<'_>) { + log!("instruction_abort_permissions_enabled"); + + // SAFETY: This test intentionally calls an assembly entry point that jumps + // to an address expected to fault under the configured permissions. The + // entry point is defined in this module and is declared `-> !`. + unsafe { + instruction_abort_permissions_enabled_entry(); + } +} diff --git a/tmk/simple_tmk/src/main.rs b/tmk/simple_tmk/src/main.rs index bd84cb0b72..c8f9f5d7d4 100644 --- a/tmk/simple_tmk/src/main.rs +++ b/tmk/simple_tmk/src/main.rs @@ -9,6 +9,7 @@ mod prelude; +mod aarch64; mod common; mod x86_64; diff --git a/tmk/tmk_core/src/lib.rs b/tmk/tmk_core/src/lib.rs index 048a68fbbc..8d5932b070 100644 --- a/tmk/tmk_core/src/lib.rs +++ b/tmk/tmk_core/src/lib.rs @@ -12,6 +12,7 @@ pub mod x86_64; #[cfg(target_arch = "aarch64")] use aarch64 as arch; +use tmk_protocol::TestFlags64; #[cfg(target_arch = "x86_64")] use x86_64 as arch; @@ -143,7 +144,7 @@ unsafe extern "C" { #[doc(hidden)] #[macro_export] macro_rules! define_tmk_test { - ($name:expr, $func:ident) => { + ($name:expr, $func:ident, $flags:expr) => { const _: () = { // Strip the crate name from the module path. const NAME: &[u8] = const { @@ -162,6 +163,7 @@ macro_rules! define_tmk_test { static TEST: $crate::TestDescriptor = $crate::TestDescriptor { name: NAME, entrypoint: $func, + flags: $flags, }; }; }; @@ -176,6 +178,8 @@ pub struct TestDescriptor { pub name: &'static [u8], /// The test entry point. pub entrypoint: for<'scope> fn(TestContext<'scope>), + /// Test flags + pub flags: TestFlags64, } #[cfg_attr(minimal_rt, panic_handler)] diff --git a/tmk/tmk_macros/src/lib.rs b/tmk/tmk_macros/src/lib.rs index 370bf7b3f3..938f942cdb 100644 --- a/tmk/tmk_macros/src/lib.rs +++ b/tmk/tmk_macros/src/lib.rs @@ -8,17 +8,54 @@ use proc_macro::TokenStream; use quote::ToTokens; use quote::quote; +use syn::parse::Parser; /// `tmk_test` procedural attribute macro. /// /// This macro is used to define a test in the TMK. #[proc_macro_attribute] -pub fn tmk_test(_attr: TokenStream, item: TokenStream) -> TokenStream { +pub fn tmk_test(attr: TokenStream, item: TokenStream) -> TokenStream { let item = syn::parse_macro_input!(item as syn::ItemFn); let name = item.sig.ident.to_string(); let func = &item.sig.ident; + + let mut expected_failure = false; + let mut linux_only = false; + + let parser = syn::meta::parser(|meta| { + if meta.path.is_ident("expected_failure") { + expected_failure = true; + Ok(()) + } else if meta.path.is_ident("linux_only") { + linux_only = true; + Ok(()) + } else { + let ident = meta + .path + .get_ident() + .map_or_else(|| "".to_string(), |exp| exp.to_string()); + + Err(meta.error(format!("unsupported tmk_test option: {ident}"))) + } + }); + + if let Err(err) = parser.parse(attr) { + let compile_err = err.to_compile_error(); + return quote! { + #compile_err + #item + } + .into(); + } + + let flags = quote! { + ::tmk_protocol::TestFlags64::new() + .with_expected_failure(#expected_failure) + .with_linux_only(#linux_only) + }; + quote! { - ::tmk_core::define_tmk_test!(#name, #func); + ::tmk_core::define_tmk_test!(#name, #func, #flags); #item } .into_token_stream() diff --git a/tmk/tmk_protocol/Cargo.toml b/tmk/tmk_protocol/Cargo.toml index afabe02f02..fa7faefc32 100644 --- a/tmk/tmk_protocol/Cargo.toml +++ b/tmk/tmk_protocol/Cargo.toml @@ -8,6 +8,7 @@ edition.workspace = true [dependencies] zerocopy.workspace = true +bitfield-struct.workspace = true [lints] workspace = true diff --git a/tmk/tmk_protocol/src/lib.rs b/tmk/tmk_protocol/src/lib.rs index bd9f98457d..0e84156880 100644 --- a/tmk/tmk_protocol/src/lib.rs +++ b/tmk/tmk_protocol/src/lib.rs @@ -6,9 +6,11 @@ #![no_std] #![forbid(unsafe_code)] +use bitfield_struct::bitfield; use zerocopy::FromBytes; use zerocopy::Immutable; use zerocopy::IntoBytes; +use zerocopy::KnownLayout; use zerocopy::TryFromBytes; /// Start input from the VMM to the TMK. @@ -21,6 +23,18 @@ pub struct StartInput { pub test_index: u64, } +/// Test metadata flags. +#[bitfield(u64)] +#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)] +pub struct TestFlags64 { + #[bits(1)] + pub expected_failure: bool, + #[bits(1)] + pub linux_only: bool, + #[bits(62)] + reserved: u64, +} + /// A 64-bit TMK test descriptor. #[repr(C)] #[derive(IntoBytes, FromBytes, Immutable)] @@ -31,6 +45,8 @@ pub struct TestDescriptor64 { pub name_len: u64, /// The test entry point. pub entrypoint: u64, + /// Test metadata flags. + pub flags: TestFlags64, } /// TMK command. diff --git a/tmk/tmk_vmm/src/load.rs b/tmk/tmk_vmm/src/load.rs index 3a4440b8d3..ddb3961de4 100644 --- a/tmk/tmk_vmm/src/load.rs +++ b/tmk/tmk_vmm/src/load.rs @@ -176,6 +176,8 @@ struct LoadInfo { pub struct TestInfo { pub name: String, pub index: u64, + pub expected_failure: bool, + pub linux_only: bool, } /// Enumerate the tests from a TMK binary. @@ -228,6 +230,8 @@ pub fn enumerate_tests(tmk: &File) -> anyhow::Result> { tests.push(TestInfo { name: name.to_string(), index: i as u64, + expected_failure: t.flags.expected_failure(), + linux_only: t.flags.linux_only(), }); } diff --git a/tmk/tmk_vmm/src/run.rs b/tmk/tmk_vmm/src/run.rs index 477547d601..790e1c5ffb 100644 --- a/tmk/tmk_vmm/src/run.rs +++ b/tmk/tmk_vmm/src/run.rs @@ -276,6 +276,11 @@ impl CommonState { for test in &tests { tracing::info!(target: "test", name = test.name, "test started"); + if test.linux_only && !cfg!(target_os = "linux") { + tracing::info!(target: "test", name = test.name, "test skipped, incompatible os"); + continue; + } + let mut vmtime_keeper = VmTimeKeeper::new(&self.driver, VmTime::from_100ns(0)); let vmtime_source = vmtime_keeper.builder().build(&self.driver).await.unwrap(); let mut ctx = RunContext { @@ -291,19 +296,40 @@ impl CommonState { vmtime_keeper.stop().await; - match r { - TestResult::Passed => { + match (r, test.expected_failure) { + (TestResult::Passed, false) => { tracing::info!(target: "test", name = test.name, "test passed"); } - TestResult::Failed => { + (TestResult::Passed, true) => { + tracing::error!( + target: "test", + name = test.name, + expected_failure = true, + "test unexpectedly passed" + ); + success = false; + } + (TestResult::Failed, false) => { tracing::error!(target: "test", name = test.name, reason = "explicit failure", "test failed"); success = false; } - TestResult::Faulted { - vp_index, - reason, - regs, - } => { + (TestResult::Failed, true) => { + tracing::info!( + target: "test", + name = test.name, + expected_failure = true, + reason = "explicit failure", + "test passed" + ); + } + ( + TestResult::Faulted { + vp_index, + reason, + regs, + }, + false, + ) => { tracing::error!( target: "test", name = test.name, @@ -314,6 +340,24 @@ impl CommonState { ); success = false; } + ( + TestResult::Faulted { + vp_index, + reason, + regs, + }, + true, + ) => { + tracing::info!( + target: "test", + name = test.name, + expected_failure = true, + vp_index = vp_index.index(), + reason, + regs = format_args!("{:#x?}", regs), + "test passed" + ); + } } } if !success { diff --git a/vm/aarch64/aarch64defs/src/lib.rs b/vm/aarch64/aarch64defs/src/lib.rs index c77fc957da..2e5066626f 100644 --- a/vm/aarch64/aarch64defs/src/lib.rs +++ b/vm/aarch64/aarch64defs/src/lib.rs @@ -181,6 +181,19 @@ pub struct SctlrEl1 { pub tidcp: bool, } +/// aarch64 HPFAR_EL2 +#[bitfield(u64)] +#[derive(PartialEq, Eq)] +pub struct HpfarEl2 { + #[bits(4)] + pub res0: u8, + #[bits(44)] + pub fipa: u64, + #[bits(15)] + pub res1: u32, + pub ns: bool, +} + open_enum! { pub enum ExceptionClass: u8 { UNKNOWN = 0b000000, @@ -322,7 +335,7 @@ open_enum! { /// Valid only for instruction fault. GRANULE_PROTECTION_FAULT_LEVEL2 = 0b100110, /// Valid only for instruction fault. - GRANULE_PROTECTION_FAULT_LEVE3 = 0b100111, + GRANULE_PROTECTION_FAULT_LEVEL3 = 0b100111, ADDRESS_SIZE_FAULT_LEVEL_NEG1 = 0b101001, TRANSLATION_FAULT_LEVEL_NEG1 = 0b101011, TLB_CONFLICT_ABORT = 0b110000, @@ -948,3 +961,96 @@ impl Display for Vendor { } } } + +#[derive(Debug, Clone, Copy)] +pub enum InstructionAbortReason { + AddressSizeFaultLevel0, + AddressSizeFaultLevel1, + AddressSizeFaultLevel2, + AddressSizeFaultLevel3, + TranslationFaultLevel0, + TranslationFaultLevel1, + TranslationFaultLevel2, + TranslationFaultLevel3, + AccessFlagFaultLevel0, + AccessFlagFaultLevel1, + AccessFlagFaultLevel2, + AccessFlagFaultLevel3, + PermissionFaultLevel0, + PermissionFaultLevel1, + PermissionFaultLevel2, + PermissionFaultLevel3, + SynchronousExternalAbort, + SyncTagCheckFault, + SynchronousExternalAbortOnTableWalkLevelNeg1, + SynchronousExternalAbortOnTableWalkLevel0, + SynchronousExternalAbortOnTableWalkLevel1, + SynchronousExternalAbortOnTableWalkLevel2, + SynchronousExternalAbortOnTableWalkLevel3, + EccParity, + EccParityOnTableWalkLevelNeg1, + EccParityOnTableWalkLevel0, + EccParityOnTableWalkLevel1, + EccParityOnTableWalkLevel2, + EccParityOnTableWalkLevel3, + GranuleProtectionFaultLevelNeg1, + GranuleProtectionFaultLevel0, + GranuleProtectionFaultLevel1, + GranuleProtectionFaultLevel2, + GranuleProtectionFaultLevel3, + AddressSizeFaultLevelNeg1, + TranslationFaultLevelNeg1, + TlbConflictAbort, + UnsupportedHardwareUpdateFault, + Unknown, +} + +impl From for InstructionAbortReason { + fn from(value: FaultStatusCode) -> Self { + match value { + FaultStatusCode::ADDRESS_SIZE_FAULT_LEVEL0 => Self::AddressSizeFaultLevel0, + FaultStatusCode::ADDRESS_SIZE_FAULT_LEVEL1 => Self::AddressSizeFaultLevel1, + FaultStatusCode::ADDRESS_SIZE_FAULT_LEVEL2 => Self::AddressSizeFaultLevel2, + FaultStatusCode::ADDRESS_SIZE_FAULT_LEVEL3 => Self::AddressSizeFaultLevel3, + FaultStatusCode::TRANSLATION_FAULT_LEVEL0 => Self::TranslationFaultLevel0, + FaultStatusCode::TRANSLATION_FAULT_LEVEL1 => Self::TranslationFaultLevel1, + FaultStatusCode::TRANSLATION_FAULT_LEVEL2 => Self::TranslationFaultLevel2, + FaultStatusCode::TRANSLATION_FAULT_LEVEL3 => Self::TranslationFaultLevel3, + FaultStatusCode::ACCESS_FLAG_FAULT_LEVEL0 => Self::AccessFlagFaultLevel0, + FaultStatusCode::ACCESS_FLAG_FAULT_LEVEL1 => Self::AccessFlagFaultLevel1, + FaultStatusCode::ACCESS_FLAG_FAULT_LEVEL2 => Self::AccessFlagFaultLevel2, + FaultStatusCode::ACCESS_FLAG_FAULT_LEVEL3 => Self::AccessFlagFaultLevel3, + FaultStatusCode::PERMISSION_FAULT_LEVEL0 => Self::PermissionFaultLevel0, + FaultStatusCode::PERMISSION_FAULT_LEVEL1 => Self::PermissionFaultLevel1, + FaultStatusCode::PERMISSION_FAULT_LEVEL2 => Self::PermissionFaultLevel2, + FaultStatusCode::PERMISSION_FAULT_LEVEL3 => Self::PermissionFaultLevel3, + FaultStatusCode::SYNCHRONOUS_EXTERNAL_ABORT => Self::SynchronousExternalAbort, + FaultStatusCode::SYNC_TAG_CHECK_FAULT => Self::SyncTagCheckFault, + FaultStatusCode::SEA_TTW_LEVEL_NEG1 => { + Self::SynchronousExternalAbortOnTableWalkLevelNeg1 + } + FaultStatusCode::SEA_TTW_LEVEL0 => Self::SynchronousExternalAbortOnTableWalkLevel0, + FaultStatusCode::SEA_TTW_LEVEL1 => Self::SynchronousExternalAbortOnTableWalkLevel1, + FaultStatusCode::SEA_TTW_LEVEL2 => Self::SynchronousExternalAbortOnTableWalkLevel2, + FaultStatusCode::SEA_TTW_LEVEL3 => Self::SynchronousExternalAbortOnTableWalkLevel3, + FaultStatusCode::ECC_PARITY => Self::EccParity, + FaultStatusCode::ECC_PARITY_TTW_LEVEL_NEG1 => Self::EccParityOnTableWalkLevelNeg1, + FaultStatusCode::ECC_PARITY_TTW_LEVEL0 => Self::EccParityOnTableWalkLevel0, + FaultStatusCode::ECC_PARITY_TTW_LEVEL1 => Self::EccParityOnTableWalkLevel1, + FaultStatusCode::ECC_PARITY_TTW_LEVEL2 => Self::EccParityOnTableWalkLevel2, + FaultStatusCode::ECC_PARITY_TTW_LEVEL3 => Self::EccParityOnTableWalkLevel3, + FaultStatusCode::GRANULE_PROTECTION_FAULT_LEVEL_NEG => { + Self::GranuleProtectionFaultLevelNeg1 + } + FaultStatusCode::GRANULE_PROTECTION_FAULT_LEVEL0 => Self::GranuleProtectionFaultLevel0, + FaultStatusCode::GRANULE_PROTECTION_FAULT_LEVEL1 => Self::GranuleProtectionFaultLevel1, + FaultStatusCode::GRANULE_PROTECTION_FAULT_LEVEL2 => Self::GranuleProtectionFaultLevel2, + FaultStatusCode::GRANULE_PROTECTION_FAULT_LEVEL3 => Self::GranuleProtectionFaultLevel3, + FaultStatusCode::ADDRESS_SIZE_FAULT_LEVEL_NEG1 => Self::AddressSizeFaultLevelNeg1, + FaultStatusCode::TRANSLATION_FAULT_LEVEL_NEG1 => Self::TranslationFaultLevelNeg1, + FaultStatusCode::TLB_CONFLICT_ABORT => Self::TlbConflictAbort, + FaultStatusCode::UNSUPPORTED_HW_UPDATE_FAULT => Self::UnsupportedHardwareUpdateFault, + _ => Self::Unknown, + } + } +}