use super::{AccessType, PagePermissions, SecurityState, PA};
use thiserror::Error;
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum TranslationError {
#[error("Page not mapped in address space")]
PageNotMapped,
#[error("Permission violation for access type: {access:?}")]
PermissionViolation {
access: AccessType,
},
#[error("Invalid address: 0x{address:x}")]
InvalidAddress {
address: u64,
},
#[error("Invalid StreamID")]
InvalidStreamID,
#[error("Invalid PASID")]
InvalidPASID,
#[error("PASID not found")]
PASIDNotFound,
#[error("Stream not configured")]
StreamNotConfigured,
#[error("Stream disabled")]
StreamDisabled,
#[error("STE abort mode (STE.Config==0b000) — transaction silently terminated")]
AbortMode,
#[error("Address size error - address exceeds supported size")]
AddressSizeError,
#[error("Address alignment error")]
AlignmentError,
#[error("Security state violation")]
SecurityViolation,
#[error("External abort during translation")]
ExternalAbort,
#[error("TLB conflict detected")]
TlbConflict,
#[error("Transaction stalled (STAG={stag:#06x})")]
Stalled {
stag: u16,
},
#[error("Transaction aborted by GBPA.ABORT (SMMUEN=0, GBPA.ABORT=1)")]
GbpaAbort,
#[error("Non-zero PASID on stage-2-only or bypass stream (§3.9 C_BAD_SUBSTREAMID)")]
BadSubstreamId,
#[error("Invalid context descriptor (§7.3.11 C_BAD_CD)")]
BadCD,
#[error("Stall queue exhausted — all STAG slots occupied (§3.12.2)")]
StallQueueFull,
#[error("IOVA exceeds T0SZ VA range limit (§3.4.1 F_TRANSLATION)")]
VaRangeExceeded,
#[error("Access flag fault — AF=0 and HTTU disabled (§3.13.2 F_ACCESS)")]
AccessFlagFault,
#[error("WXN/UWXN permission fault — execute on writable page denied (§5.4 F_PERMISSION)")]
WxnFault,
#[error("S2PTW fault — device-memory stage-2 page during table walk (§5.2 F_PERMISSION)")]
S2PtwFault,
#[error("SMMU Service Failure Mode active (§12.3 GERROR.SFM_ERR) — transaction terminated")]
ServiceFailureMode,
#[error("Stage-1 terminate fault: RAZ/WI (CD.A=0, §3.12.1)")]
RazWi,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TranslationData {
physical_address: PA,
permissions: PagePermissions,
security_state: SecurityState,
mem_type: u8,
shareability: u8,
alloc_hint: u8,
inst_cfg: u8,
priv_cfg: u8,
ns_cfg_out: u8,
pub page_attr: u8,
}
impl TranslationData {
#[must_use]
#[inline]
pub const fn new(physical_address: PA, permissions: PagePermissions, security_state: SecurityState) -> Self {
Self {
physical_address,
permissions,
security_state,
mem_type: 0,
shareability: 0,
alloc_hint: 0,
inst_cfg: 0,
priv_cfg: 0,
ns_cfg_out: 0,
page_attr: 0xFF, }
}
#[must_use]
#[inline]
pub const fn with_pa(physical_address: PA) -> Self {
Self {
physical_address,
permissions: PagePermissions::none(),
security_state: SecurityState::NonSecure,
mem_type: 0,
shareability: 0,
alloc_hint: 0,
inst_cfg: 0,
priv_cfg: 0,
ns_cfg_out: 0,
page_attr: 0xFF, }
}
#[must_use]
#[inline]
pub const fn mem_type(&self) -> u8 {
self.mem_type
}
#[must_use]
#[inline]
pub const fn shareability(&self) -> u8 {
self.shareability
}
#[must_use]
#[inline]
pub const fn alloc_hint(&self) -> u8 {
self.alloc_hint
}
#[must_use]
#[inline]
pub const fn inst_cfg(&self) -> u8 {
self.inst_cfg
}
#[must_use]
#[inline]
pub const fn priv_cfg(&self) -> u8 {
self.priv_cfg
}
#[must_use]
#[inline]
pub const fn ns_cfg_out(&self) -> u8 {
self.ns_cfg_out
}
#[must_use]
pub const fn with_output_attrs(
mut self,
mem_type: u8,
shareability: u8,
alloc_hint: u8,
inst_cfg: u8,
priv_cfg: u8,
ns_cfg_out: u8,
) -> Self {
self.mem_type = mem_type;
self.shareability = shareability;
self.alloc_hint = alloc_hint;
self.inst_cfg = inst_cfg;
self.priv_cfg = priv_cfg;
self.ns_cfg_out = ns_cfg_out;
self
}
#[must_use]
#[inline]
pub const fn builder() -> TranslationDataBuilder {
TranslationDataBuilder::new()
}
#[must_use]
#[inline]
pub const fn physical_address(&self) -> PA {
self.physical_address
}
#[must_use]
#[inline]
pub const fn permissions(&self) -> PagePermissions {
self.permissions
}
#[must_use]
#[inline]
pub const fn security_state(&self) -> SecurityState {
self.security_state
}
}
impl Default for TranslationData {
fn default() -> Self {
Self {
physical_address: PA::new(0).unwrap_or_else(|_| unreachable!()),
permissions: PagePermissions::default(),
security_state: SecurityState::NonSecure,
mem_type: 0,
shareability: 0,
alloc_hint: 0,
inst_cfg: 0,
priv_cfg: 0,
ns_cfg_out: 0,
page_attr: 0xFF, }
}
}
#[derive(Debug, Clone, Copy)]
pub struct TranslationDataBuilder {
physical_address: Option<PA>,
permissions: PagePermissions,
security_state: SecurityState,
mem_type: u8,
shareability: u8,
alloc_hint: u8,
inst_cfg: u8,
priv_cfg: u8,
ns_cfg_out: u8,
page_attr: u8,
}
impl TranslationDataBuilder {
#[must_use]
const fn new() -> Self {
Self {
physical_address: None,
permissions: PagePermissions::none(),
security_state: SecurityState::NonSecure,
mem_type: 0,
shareability: 0,
alloc_hint: 0,
inst_cfg: 0,
priv_cfg: 0,
ns_cfg_out: 0,
page_attr: 0xFF, }
}
#[must_use]
pub const fn physical_address(mut self, pa: PA) -> Self {
self.physical_address = Some(pa);
self
}
#[must_use]
pub const fn permissions(mut self, perms: PagePermissions) -> Self {
self.permissions = perms;
self
}
#[must_use]
pub const fn security_state(mut self, state: SecurityState) -> Self {
self.security_state = state;
self
}
#[must_use]
pub const fn output_attrs(
mut self,
mem_type: u8,
shareability: u8,
alloc_hint: u8,
inst_cfg: u8,
priv_cfg: u8,
ns_cfg_out: u8,
) -> Self {
self.mem_type = mem_type;
self.shareability = shareability;
self.alloc_hint = alloc_hint;
self.inst_cfg = inst_cfg;
self.priv_cfg = priv_cfg;
self.ns_cfg_out = ns_cfg_out;
self
}
#[must_use]
pub fn build(self) -> TranslationData {
TranslationData {
physical_address: self.physical_address.expect("Physical address must be set"),
permissions: self.permissions,
security_state: self.security_state,
mem_type: self.mem_type,
shareability: self.shareability,
alloc_hint: self.alloc_hint,
inst_cfg: self.inst_cfg,
priv_cfg: self.priv_cfg,
ns_cfg_out: self.ns_cfg_out,
page_attr: self.page_attr,
}
}
}
pub type TranslationResult = Result<TranslationData, TranslationError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_translation_data_basic() {
let pa = PA::new(0x1000).unwrap();
let perms = PagePermissions::read_write();
let data = TranslationData::new(pa, perms, SecurityState::NonSecure);
assert_eq!(data.physical_address(), pa);
assert_eq!(data.permissions(), perms);
assert_eq!(data.security_state(), SecurityState::NonSecure);
}
#[test]
fn test_translation_result_ok() {
let pa = PA::new(0x2000).unwrap();
let data = TranslationData::with_pa(pa);
let result: TranslationResult = Ok(data);
assert!(result.is_ok());
}
#[test]
fn test_translation_result_err() {
let result: TranslationResult = Err(TranslationError::PageNotMapped);
assert!(result.is_err());
}
#[test]
fn test_translation_error_display() {
let error = TranslationError::PageNotMapped;
let msg = format!("{error}");
assert!(!msg.is_empty());
}
}