smmu 1.7.1

ARM SMMU v3 (System Memory Management Unit) implementation - Production-grade translation engine
Documentation
//! Fault type definitions for ARM SMMU v3
//!
//! This module defines all 15 ARM SMMU v3 fault types with detailed classification and severity.

use crate::types::{StreamID, ValidationError, IOVA, PASID};
use core::fmt;

/// Fault severity level
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FaultSeverity {
    /// Warning - recoverable fault
    Warning,
    /// Error - non-recoverable but operation can be terminated cleanly
    Error,
    /// Critical - configuration or system error
    Critical,
}

/// Translation step where fault occurred
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TranslationStep {
    /// Stage 1 translation
    Stage1,
    /// Stage 2 translation
    Stage2,
}

/// Address type
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AddressType {
    /// Input/Output Virtual Address
    IOVA,
    /// Intermediate Physical Address
    IPA,
    /// Physical Address
    PA,
}

/// ARM SMMU v3 Fault Type
///
/// Represents all 15 fault types defined in the ARM SMMU v3 specification.
///
/// # Fault Codes
///
/// Each fault type has a unique code (0x01-0x0F) as defined in the ARM SMMU v3 spec.
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FaultType {
    /// Translation fault - no valid translation found
    TranslationFault = 0x01,

    /// Address size fault - address exceeds supported size
    AddressSizeFault = 0x02,

    /// Access flag fault - access flag not set in descriptor
    AccessFlagFault = 0x03,

    /// Permission fault - requested access not permitted
    PermissionFault = 0x04,

    /// External abort on translation table walk or descriptor fetch
    ExternalAbort = 0x05,

    /// TLB conflict abort
    TLBConflictAbort = 0x06,

    /// Unsupported atomic update to translation table
    UnsupportedAtomicUpdate = 0x07,

    /// Alignment fault
    AlignmentFault = 0x08,

    /// Output address range fault
    OutputAddressRangeFault = 0x09,

    /// Bad StreamID
    BadStreamID = 0x0A,

    /// Context Descriptor fetch fault
    CDFetchFault = 0x0B,

    /// Bad Context Descriptor
    BadCD = 0x0C,

    /// Page table walk external abort
    WalkEABT = 0x0D,

    /// Bad Stream Table Entry
    BadSTE = 0x0E,

    /// Stream Table Entry fetch fault
    STEFetchFault = 0x0F,

    /// Stream disabled — STE.Config indicates disabled/abort stream (§7.3.7)
    ///
    /// Generates F_STREAM_DISABLED (event code 0x06) per ARM IHI0070G.b §7.3.7.
    StreamDisabled = 0x10,

    /// Bad SubstreamID — non-zero PASID to stage-2-only or bypass stream (§3.9, §7.3.9)
    ///
    /// Always generates `C_BAD_SUBSTREAMID` (event code 0x08); never stalled.
    BadSubstreamId = 0x11,

    /// Security fault — transaction violates security policy (ARM IHI0070G.b §7.3)
    ///
    /// Distinct from `PermissionFault`: raised when a transaction crosses a
    /// security boundary (e.g. NonSecure transaction targeting Secure memory).
    /// Maps to `EventType::FPermission` (0x13) in the event queue per §7.3.16.
    SecurityFault = 0x12,
}

impl FaultType {
    /// Get the ARM SMMU v3 fault code
    #[inline]
    #[must_use]
    pub const fn code(self) -> u8 {
        self as u8
    }

    /// Get the fault name
    #[must_use]
    pub const fn name(self) -> &'static str {
        match self {
            Self::TranslationFault => "Translation Fault",
            Self::AddressSizeFault => "Address Size Fault",
            Self::AccessFlagFault => "Access Flag Fault",
            Self::PermissionFault => "Permission Fault",
            Self::ExternalAbort => "External Abort",
            Self::TLBConflictAbort => "TLB Conflict Abort",
            Self::UnsupportedAtomicUpdate => "Unsupported Atomic Update",
            Self::AlignmentFault => "Alignment Fault",
            Self::OutputAddressRangeFault => "Output Address Range Fault",
            Self::BadStreamID => "Bad StreamID",
            Self::CDFetchFault => "Context Descriptor Fetch Fault",
            Self::BadCD => "Bad Context Descriptor",
            Self::WalkEABT => "Page Table Walk External Abort",
            Self::BadSTE => "Bad Stream Table Entry",
            Self::STEFetchFault => "Stream Table Entry Fetch Fault",
            Self::StreamDisabled => "Stream Disabled",
            Self::BadSubstreamId => "Bad SubstreamID",
            Self::SecurityFault => "Security Fault",
        }
    }

    /// Get fault description
    #[must_use]
    pub const fn description(self) -> &'static str {
        match self {
            Self::TranslationFault => "No valid translation found for the input address",
            Self::AddressSizeFault => "Input address exceeds the supported address size",
            Self::AccessFlagFault => "Access flag not set in translation descriptor",
            Self::PermissionFault => "Requested access type not permitted by page permissions",
            Self::ExternalAbort => "External abort during descriptor fetch",
            Self::TLBConflictAbort => "Conflicting TLB entries detected",
            Self::UnsupportedAtomicUpdate => "Atomic update to translation table not supported",
            Self::AlignmentFault => "Address is not properly aligned",
            Self::OutputAddressRangeFault => "Output address exceeds addressable range",
            Self::BadStreamID => "Invalid or misconfigured StreamID",
            Self::CDFetchFault => "Error fetching Context Descriptor",
            Self::BadCD => "Invalid or misconfigured Context Descriptor",
            Self::WalkEABT => "External abort during page table walk",
            Self::BadSTE => "Invalid or misconfigured Stream Table Entry",
            Self::STEFetchFault => "Error fetching Stream Table Entry",
            Self::StreamDisabled => "Transaction on a disabled/abort stream (STE.Config=disabled)",
            Self::BadSubstreamId => "Non-zero PASID (SubstreamID) supplied to a stage-2-only or bypass stream",
            Self::SecurityFault => "Transaction violates security policy (crosses security boundary)",
        }
    }

    /// Check if this is a translation fault
    #[inline]
    #[must_use]
    pub const fn is_translation_fault(self) -> bool {
        matches!(self, Self::TranslationFault)
    }

    /// Check if this is a permission-related fault
    #[inline]
    #[must_use]
    pub const fn is_permission_fault(self) -> bool {
        matches!(self, Self::PermissionFault | Self::AccessFlagFault)
    }

    /// Check if this is a configuration fault
    #[inline]
    #[must_use]
    pub const fn is_configuration_fault(self) -> bool {
        matches!(
            self,
            Self::BadStreamID
                | Self::CDFetchFault
                | Self::BadCD
                | Self::BadSTE
                | Self::STEFetchFault
                | Self::StreamDisabled
                | Self::BadSubstreamId
        )
    }

    /// Check if this is an address-related fault
    #[inline]
    #[must_use]
    pub const fn is_address_fault(self) -> bool {
        matches!(
            self,
            Self::AddressSizeFault | Self::AlignmentFault | Self::OutputAddressRangeFault
        )
    }

    /// Check if this is an external fault
    #[inline]
    #[must_use]
    pub const fn is_external_fault(self) -> bool {
        matches!(self, Self::ExternalAbort | Self::WalkEABT)
    }

    /// Get fault severity
    #[must_use]
    pub const fn severity(self) -> FaultSeverity {
        match self {
            Self::BadSTE | Self::BadCD | Self::BadStreamID | Self::StreamDisabled | Self::BadSubstreamId => FaultSeverity::Critical,
            Self::TranslationFault
            | Self::PermissionFault
            | Self::SecurityFault
            | Self::ExternalAbort
            | Self::AddressSizeFault
            | Self::AlignmentFault
            | Self::OutputAddressRangeFault
            | Self::CDFetchFault
            | Self::STEFetchFault
            | Self::WalkEABT
            | Self::UnsupportedAtomicUpdate => FaultSeverity::Error,
            Self::AccessFlagFault | Self::TLBConflictAbort => FaultSeverity::Warning,
        }
    }

    /// Check if fault is potentially recoverable
    #[must_use]
    pub const fn is_recoverable(self) -> bool {
        matches!(self, Self::AccessFlagFault | Self::TLBConflictAbort)
    }

    /// Check if fault can occur in Stage 1
    #[must_use]
    pub const fn can_occur_in_stage1(self) -> bool {
        !matches!(self, Self::BadSTE | Self::STEFetchFault | Self::BadSubstreamId)
    }

    /// Check if fault can occur in Stage 2
    #[must_use]
    pub const fn can_occur_in_stage2(self) -> bool {
        !matches!(
            self,
            Self::BadStreamID
                | Self::BadSTE
                | Self::STEFetchFault
                | Self::BadCD
                | Self::CDFetchFault
                | Self::BadSubstreamId
        )
    }

    /// Check if fault is stage-agnostic
    #[must_use]
    pub const fn is_stage_agnostic(self) -> bool {
        matches!(
            self,
            Self::BadStreamID | Self::BadSTE | Self::ExternalAbort | Self::STEFetchFault | Self::BadSubstreamId
        )
    }

    /// Create from fault code
    pub const fn from_code(code: u8) -> Result<Self, ValidationError> {
        match code {
            0x01 => Ok(Self::TranslationFault),
            0x02 => Ok(Self::AddressSizeFault),
            0x03 => Ok(Self::AccessFlagFault),
            0x04 => Ok(Self::PermissionFault),
            0x05 => Ok(Self::ExternalAbort),
            0x06 => Ok(Self::TLBConflictAbort),
            0x07 => Ok(Self::UnsupportedAtomicUpdate),
            0x08 => Ok(Self::AlignmentFault),
            0x09 => Ok(Self::OutputAddressRangeFault),
            0x0A => Ok(Self::BadStreamID),
            0x0B => Ok(Self::CDFetchFault),
            0x0C => Ok(Self::BadCD),
            0x0D => Ok(Self::WalkEABT),
            0x0E => Ok(Self::BadSTE),
            0x0F => Ok(Self::STEFetchFault),
            0x10 => Ok(Self::StreamDisabled),
            0x11 => Ok(Self::BadSubstreamId),
            0x12 => Ok(Self::SecurityFault),
            _ => Err(ValidationError::InvalidFaultType { code }),
        }
    }
}

impl fmt::Display for FaultType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.name())
    }
}

/// Fault context information
#[derive(Debug, Clone)]
pub struct FaultContext {
    fault_type: FaultType,
    stream_id: StreamID,
    pasid: PASID,
    address: IOVA,
}

impl FaultContext {
    /// Create a new fault context
    #[must_use]
    pub const fn new(fault_type: FaultType, stream_id: StreamID, pasid: PASID, address: IOVA) -> Self {
        Self { fault_type, stream_id, pasid, address }
    }

    /// Get the fault type
    #[must_use]
    pub const fn fault_type(&self) -> FaultType {
        self.fault_type
    }

    /// Get the stream ID
    #[must_use]
    pub const fn stream_id(&self) -> StreamID {
        self.stream_id
    }

    /// Get the PASID
    #[must_use]
    pub const fn pasid(&self) -> PASID {
        self.pasid
    }

    /// Get the address
    #[must_use]
    pub const fn address(&self) -> IOVA {
        self.address
    }
}