use super::security_state::SecurityState;
use crate::types::ValidationError;
use core::fmt;
#[cfg(feature = "std")]
use std::time::Duration;
#[cfg(feature = "std")]
use std::collections::HashMap;
#[cfg(feature = "std")]
fn parse_numeric<T: std::str::FromStr>(value: &str, field_name: &str) -> Result<T, ValidationError> {
value.replace('_', "").parse().map_err(|_| ValidationError::InvalidConfiguration {
reason: format!("invalid {field_name}"),
})
}
#[repr(u8)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum StreamWorld {
#[allow(clippy::upper_case_acronyms)]
El1El0 = 0x00,
El2 = 0x01,
El2E2h = 0x02,
El3 = 0x03,
}
impl Default for StreamWorld {
fn default() -> Self {
Self::El1El0
}
}
impl fmt::Display for StreamWorld {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::El1El0 => write!(f, "El1El0"),
Self::El2 => write!(f, "El2"),
Self::El2E2h => write!(f, "El2E2h"),
Self::El3 => write!(f, "El3"),
}
}
}
#[repr(u8)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum FaultMode {
Terminate = 0,
Stall = 1,
}
impl Default for FaultMode {
fn default() -> Self {
Self::Terminate
}
}
impl fmt::Display for FaultMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Terminate => write!(f, "Terminate"),
Self::Stall => write!(f, "Stall"),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StreamConfig {
pub translation_enabled: bool,
pub stage1_enabled: bool,
pub stage2_enabled: bool,
pub disabled: bool,
pub pasid_enabled: bool,
pub max_pasid: u32,
pub fault_mode: FaultMode,
pub security_enforced: bool,
pub security_state: SecurityState,
pub vmid: u16,
pub ha: bool,
pub hd: bool,
pub s1dss: u8,
pub s1cd_max: u8,
pub strw: StreamWorld,
pub ns_cfg: u8,
pub sh_cfg: u8,
pub alloc_cfg: u8,
pub mem_attr: u8,
pub inst_cfg: u8,
pub priv_cfg: u8,
pub mt_cfg: bool,
pub s2_t0sz: u8,
pub s2_tg: u8,
pub s2_sl0: u8,
pub s2_aa64: bool,
pub s2_ps: u8,
pub s2_ttb: u64,
pub aa64: bool,
pub t0sz: u8,
pub t1sz: u8,
pub mev: bool,
pub epd0: bool,
pub epd1: bool,
pub tbi: bool,
pub ips: u8,
pub eats: u8,
pub s1_stalld: bool,
pub affd: bool,
pub s2affd: bool,
pub s2ha: bool,
pub s2hd: bool,
pub wxn: bool,
pub uwxn: bool,
pub s2ptw: bool,
pub s2_stall: bool,
pub s2_record: bool,
pub endi: bool,
pub s2endi: bool,
pub ttb0: u64,
pub ttb1: u64,
}
impl StreamConfig {
pub const MIN_PASID: u32 = 0;
pub const MAX_PASID: u32 = (1 << 20) - 1;
pub const S1CD_MAX_LIMIT: u8 = 20;
#[must_use]
pub fn builder() -> StreamConfigBuilder {
StreamConfigBuilder::new()
}
#[must_use]
pub fn bypass() -> Self {
Self {
translation_enabled: false,
stage1_enabled: false,
stage2_enabled: false,
disabled: false,
pasid_enabled: false,
max_pasid: 0,
fault_mode: FaultMode::Terminate,
security_enforced: false,
security_state: SecurityState::NonSecure,
vmid: 0,
ha: false,
hd: false,
s1dss: 2,
s1cd_max: 0,
strw: StreamWorld::El1El0,
ns_cfg: 0,
sh_cfg: 0,
alloc_cfg: 0,
mem_attr: 0,
inst_cfg: 0,
priv_cfg: 0,
mt_cfg: false,
s2_t0sz: 25,
s2_tg: 0,
s2_sl0: 1,
s2_aa64: true,
s2_ps: 5,
s2_ttb: 0,
aa64: true,
t0sz: 16,
t1sz: 16,
mev: false,
epd0: false,
epd1: false,
tbi: false,
ips: 5,
eats: 0,
s1_stalld: false,
affd: false,
s2affd: false,
s2ha: false,
s2hd: false,
wxn: false,
uwxn: false,
s2ptw: false,
s2_stall: false,
s2_record: true,
endi: false,
s2endi: false,
ttb0: 0,
ttb1: 0,
}
}
#[must_use]
pub fn stage1_only() -> Self {
Self {
translation_enabled: true,
stage1_enabled: true,
stage2_enabled: false,
disabled: false,
pasid_enabled: false,
max_pasid: 0,
fault_mode: FaultMode::Terminate,
security_enforced: true,
security_state: SecurityState::NonSecure,
vmid: 0,
ha: false,
hd: false,
s1dss: 2,
s1cd_max: 0,
strw: StreamWorld::El1El0,
ns_cfg: 0,
sh_cfg: 0,
alloc_cfg: 0,
mem_attr: 0,
inst_cfg: 0,
priv_cfg: 0,
mt_cfg: false,
s2_t0sz: 25,
s2_tg: 0,
s2_sl0: 1,
s2_aa64: true,
s2_ps: 5,
s2_ttb: 0,
aa64: true,
t0sz: 16,
t1sz: 16,
mev: false,
epd0: false,
epd1: false,
tbi: false,
ips: 5,
eats: 0,
s1_stalld: false,
affd: false,
s2affd: false,
s2ha: false,
s2hd: false,
wxn: false,
uwxn: false,
s2ptw: false,
s2_stall: false,
s2_record: true,
endi: false,
s2endi: false,
ttb0: 0,
ttb1: 0,
}
}
#[must_use]
pub fn stage2_only() -> Self {
Self {
translation_enabled: true,
stage1_enabled: false,
stage2_enabled: true,
disabled: false,
pasid_enabled: false,
max_pasid: 0,
fault_mode: FaultMode::Terminate,
security_enforced: true,
security_state: SecurityState::NonSecure,
vmid: 0,
ha: false,
hd: false,
s1dss: 2,
s1cd_max: 0,
strw: StreamWorld::El1El0,
ns_cfg: 0,
sh_cfg: 0,
alloc_cfg: 0,
mem_attr: 0,
inst_cfg: 0,
priv_cfg: 0,
mt_cfg: false,
s2_t0sz: 25,
s2_tg: 0,
s2_sl0: 1,
s2_aa64: true,
s2_ps: 5,
s2_ttb: 0,
aa64: true,
t0sz: 16,
t1sz: 16,
mev: false,
epd0: false,
epd1: false,
tbi: false,
ips: 5,
eats: 0,
s1_stalld: false,
affd: false,
s2affd: false,
s2ha: false,
s2hd: false,
wxn: false,
uwxn: false,
s2ptw: false,
s2_stall: false,
s2_record: true,
endi: false,
s2endi: false,
ttb0: 0,
ttb1: 0,
}
}
#[must_use]
pub fn two_stage() -> Self {
Self {
translation_enabled: true,
stage1_enabled: true,
stage2_enabled: true,
disabled: false,
pasid_enabled: true,
max_pasid: Self::MAX_PASID,
fault_mode: FaultMode::Terminate,
security_enforced: true,
security_state: SecurityState::NonSecure,
vmid: 0,
ha: false,
hd: false,
s1dss: 2,
s1cd_max: 0,
strw: StreamWorld::El1El0,
ns_cfg: 0,
sh_cfg: 0,
alloc_cfg: 0,
mem_attr: 0,
inst_cfg: 0,
priv_cfg: 0,
mt_cfg: false,
s2_t0sz: 25,
s2_tg: 0,
s2_sl0: 1,
s2_aa64: true,
s2_ps: 5,
s2_ttb: 0,
aa64: true,
t0sz: 16,
t1sz: 16,
mev: false,
epd0: false,
epd1: false,
tbi: false,
ips: 5,
eats: 0,
s1_stalld: false,
affd: false,
s2affd: false,
s2ha: false,
s2hd: false,
wxn: false,
uwxn: false,
s2ptw: false,
s2_stall: false,
s2_record: true,
endi: false,
s2endi: false,
ttb0: 0,
ttb1: 0,
}
}
pub fn validate(&self) -> Result<(), ValidationError> {
if !self.translation_enabled && (self.stage1_enabled || self.stage2_enabled) {
return Err(ValidationError::InvalidConfiguration {
reason: "stages enabled without translation".into(),
});
}
if self.translation_enabled && !self.stage1_enabled && !self.stage2_enabled {
return Err(ValidationError::InvalidConfiguration {
reason: "translation enabled but no stages active".into(),
});
}
if self.pasid_enabled && !self.stage1_enabled {
return Err(ValidationError::InvalidConfiguration {
reason: "PASID enabled without Stage 1".into(),
});
}
if self.pasid_enabled && self.max_pasid > Self::MAX_PASID {
return Err(ValidationError::InvalidPASID { value: self.max_pasid });
}
if !self.pasid_enabled && self.max_pasid != 0 {
return Err(ValidationError::InvalidConfiguration {
reason: "max_pasid set without PASID enabled".into(),
});
}
if self.s1cd_max > Self::S1CD_MAX_LIMIT {
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"s1cd_max={} exceeds SMMU_IDR1.SSIDSIZE maximum of {} (ARM §5.2)",
self.s1cd_max, Self::S1CD_MAX_LIMIT
),
});
}
if self.s2_t0sz > 48 {
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"s2_t0sz={} exceeds absolute maximum of 48 (ARM §5.2 S2T0SZ)",
self.s2_t0sz
),
});
}
if self.hd && !self.ha {
return Err(ValidationError::InvalidConfiguration {
reason: "HD=1 requires HA=1 (§3.13.4: dirty-state HTTU implies access-flag HTTU)".into(),
});
}
if self.s2hd && !self.s2ha {
return Err(ValidationError::InvalidConfiguration {
reason: "S2HD=1 requires S2HA=1 (§3.13.4: stage-2 dirty-state HTTU implies stage-2 access-flag HTTU)".into(),
});
}
Ok(())
}
#[inline]
#[must_use]
pub const fn is_bypass(&self) -> bool {
!self.translation_enabled && !self.disabled
}
#[inline]
#[must_use]
pub const fn is_abort_mode(&self) -> bool {
self.disabled && !self.translation_enabled
}
#[inline]
#[must_use]
pub const fn is_two_stage(&self) -> bool {
self.stage1_enabled && self.stage2_enabled
}
}
impl Default for StreamConfig {
fn default() -> Self {
Self::bypass()
}
}
#[derive(Clone, Debug)]
pub struct StreamConfigBuilder {
translation_enabled: bool,
stage1_enabled: bool,
stage2_enabled: bool,
disabled: bool,
pasid_enabled: bool,
max_pasid: u32,
fault_mode: FaultMode,
security_enforced: bool,
security_state: SecurityState,
vmid: u16,
ha: bool,
hd: bool,
s1dss: u8,
s1cd_max: u8,
strw: StreamWorld,
ns_cfg: u8,
sh_cfg: u8,
alloc_cfg: u8,
mem_attr: u8,
inst_cfg: u8,
priv_cfg: u8,
mt_cfg: bool,
s2_t0sz: u8,
s2_tg: u8,
s2_sl0: u8,
s2_aa64: bool,
s2_ps: u8,
s2_ttb: u64,
aa64: bool,
t0sz: u8,
t1sz: u8,
mev: bool,
epd0: bool,
epd1: bool,
tbi: bool,
ips: u8,
eats: u8,
s1_stalld: bool,
affd: bool,
s2affd: bool,
s2ha: bool,
s2hd: bool,
wxn: bool,
uwxn: bool,
s2ptw: bool,
s2_stall: bool,
s2_record: bool,
endi: bool,
s2endi: bool,
ttb0: u64,
ttb1: u64,
}
impl StreamConfigBuilder {
#[must_use]
pub fn new() -> Self {
Self {
translation_enabled: false,
stage1_enabled: false,
stage2_enabled: false,
disabled: false,
pasid_enabled: false,
max_pasid: 0,
fault_mode: FaultMode::Terminate,
security_enforced: false,
security_state: SecurityState::NonSecure,
vmid: 0,
ha: false,
hd: false,
s1dss: 2,
s1cd_max: 0,
strw: StreamWorld::El1El0,
ns_cfg: 0,
sh_cfg: 0,
alloc_cfg: 0,
mem_attr: 0,
inst_cfg: 0,
priv_cfg: 0,
mt_cfg: false,
s2_t0sz: 25,
s2_tg: 0,
s2_sl0: 1,
s2_aa64: true,
s2_ps: 5,
s2_ttb: 0,
aa64: true,
t0sz: 16,
t1sz: 16,
mev: false,
epd0: false,
epd1: false,
tbi: false,
ips: 5,
eats: 0,
s1_stalld: false,
affd: false,
s2affd: false,
s2ha: false,
s2hd: false,
wxn: false,
uwxn: false,
s2ptw: false,
s2_stall: false,
s2_record: true,
endi: false,
s2endi: false,
ttb0: 0,
ttb1: 0,
}
}
#[must_use]
pub fn s1_stalld(mut self, s1_stalld: bool) -> Self {
self.s1_stalld = s1_stalld;
self
}
#[must_use]
pub fn affd(mut self, affd: bool) -> Self {
self.affd = affd;
self
}
#[must_use]
pub fn s2affd(mut self, s2affd: bool) -> Self {
self.s2affd = s2affd;
self
}
#[must_use]
pub fn s2ha(mut self, s2ha: bool) -> Self {
self.s2ha = s2ha;
self
}
#[must_use]
pub fn s2hd(mut self, s2hd: bool) -> Self {
self.s2hd = s2hd;
self
}
#[must_use]
pub fn wxn(mut self, wxn: bool) -> Self {
self.wxn = wxn;
self
}
#[must_use]
pub fn uwxn(mut self, uwxn: bool) -> Self {
self.uwxn = uwxn;
self
}
#[must_use]
pub fn s2ptw(mut self, s2ptw: bool) -> Self {
self.s2ptw = s2ptw;
self
}
#[must_use]
pub fn s2_stall(mut self, val: bool) -> Self {
self.s2_stall = val;
self
}
#[must_use]
pub fn s2_record(mut self, val: bool) -> Self {
self.s2_record = val;
self
}
#[must_use]
pub fn mev(mut self, mev: bool) -> Self {
self.mev = mev;
self
}
#[must_use]
pub fn translation_enabled(mut self, enabled: bool) -> Self {
self.translation_enabled = enabled;
if !enabled {
self.disabled = true;
}
self
}
#[must_use]
pub fn stage1_enabled(mut self, enabled: bool) -> Self {
self.stage1_enabled = enabled;
self
}
#[must_use]
pub fn stage2_enabled(mut self, enabled: bool) -> Self {
self.stage2_enabled = enabled;
self
}
#[must_use]
pub fn pasid_enabled(mut self, enabled: bool) -> Self {
self.pasid_enabled = enabled;
self
}
#[must_use]
pub fn max_pasid(mut self, max: u32) -> Self {
self.max_pasid = max;
self
}
#[must_use]
pub fn fault_mode(mut self, mode: FaultMode) -> Self {
self.fault_mode = mode;
self
}
#[must_use]
pub fn security_enforced(mut self, enforced: bool) -> Self {
self.security_enforced = enforced;
self
}
#[must_use]
pub fn security_state(mut self, state: SecurityState) -> Self {
self.security_state = state;
self
}
#[must_use]
pub fn vmid(mut self, vmid: u16) -> Self {
self.vmid = vmid;
self
}
#[must_use]
pub fn ha(mut self, ha: bool) -> Self {
self.ha = ha;
self
}
#[must_use]
pub fn hd(mut self, hd: bool) -> Self {
self.hd = hd;
self
}
#[must_use]
pub fn s1dss(mut self, s1dss: u8) -> Self {
self.s1dss = s1dss;
self
}
#[must_use]
pub fn s1cd_max(mut self, s1cd_max: u8) -> Self {
self.s1cd_max = s1cd_max;
self
}
#[must_use]
pub fn strw(mut self, strw: StreamWorld) -> Self {
self.strw = strw;
self
}
#[must_use]
pub fn ns_cfg(mut self, ns_cfg: u8) -> Self {
self.ns_cfg = ns_cfg;
self
}
#[must_use]
pub fn sh_cfg(mut self, sh_cfg: u8) -> Self {
self.sh_cfg = sh_cfg;
self
}
#[must_use]
pub fn alloc_cfg(mut self, alloc_cfg: u8) -> Self {
self.alloc_cfg = alloc_cfg;
self
}
#[must_use]
pub fn mem_attr(mut self, mem_attr: u8) -> Self {
self.mem_attr = mem_attr;
self
}
#[must_use]
pub fn inst_cfg(mut self, inst_cfg: u8) -> Self {
self.inst_cfg = inst_cfg;
self
}
#[must_use]
pub fn priv_cfg(mut self, priv_cfg: u8) -> Self {
self.priv_cfg = priv_cfg;
self
}
#[must_use]
pub fn mt_cfg(mut self, mt_cfg: bool) -> Self {
self.mt_cfg = mt_cfg;
self
}
#[must_use]
pub fn s2_t0sz(mut self, s2_t0sz: u8) -> Self {
self.s2_t0sz = s2_t0sz;
self
}
#[must_use]
pub fn s2_tg(mut self, s2_tg: u8) -> Self {
self.s2_tg = s2_tg;
self
}
#[must_use]
pub fn s2_sl0(mut self, s2_sl0: u8) -> Self {
self.s2_sl0 = s2_sl0;
self
}
#[must_use]
pub fn s2_aa64(mut self, s2_aa64: bool) -> Self {
self.s2_aa64 = s2_aa64;
self
}
#[must_use]
pub fn s2_ps(mut self, s2_ps: u8) -> Self {
self.s2_ps = s2_ps;
self
}
#[must_use]
pub fn s2_ttb(mut self, s2_ttb: u64) -> Self {
self.s2_ttb = s2_ttb;
self
}
#[must_use]
pub fn aa64(mut self, aa64: bool) -> Self {
self.aa64 = aa64;
self
}
#[must_use]
pub fn t0sz(mut self, t0sz: u8) -> Self {
self.t0sz = t0sz;
self
}
#[must_use]
pub fn t1sz(mut self, t1sz: u8) -> Self {
self.t1sz = t1sz;
self
}
#[must_use]
pub fn epd0(mut self, epd0: bool) -> Self {
self.epd0 = epd0;
self
}
#[must_use]
pub fn epd1(mut self, epd1: bool) -> Self {
self.epd1 = epd1;
self
}
#[must_use]
pub fn tbi(mut self, tbi: bool) -> Self {
self.tbi = tbi;
self
}
#[must_use]
pub fn ips(mut self, ips: u8) -> Self {
self.ips = ips;
self
}
#[must_use]
pub fn eats(mut self, eats: u8) -> Self {
self.eats = eats;
self
}
#[must_use]
pub fn ttb0(mut self, v: u64) -> Self {
self.ttb0 = v;
self
}
#[must_use]
pub fn ttb1(mut self, v: u64) -> Self {
self.ttb1 = v;
self
}
#[must_use]
pub fn build(self) -> Result<StreamConfig, ValidationError> {
let config = StreamConfig {
translation_enabled: self.translation_enabled,
stage1_enabled: self.stage1_enabled,
stage2_enabled: self.stage2_enabled,
disabled: self.disabled,
pasid_enabled: self.pasid_enabled,
max_pasid: self.max_pasid,
fault_mode: self.fault_mode,
security_enforced: self.security_enforced,
security_state: self.security_state,
vmid: self.vmid,
ha: self.ha,
hd: self.hd,
s1dss: self.s1dss,
s1cd_max: self.s1cd_max,
strw: self.strw,
ns_cfg: self.ns_cfg,
sh_cfg: self.sh_cfg,
alloc_cfg: self.alloc_cfg,
mem_attr: self.mem_attr,
inst_cfg: self.inst_cfg,
priv_cfg: self.priv_cfg,
mt_cfg: self.mt_cfg,
s2_t0sz: self.s2_t0sz,
s2_tg: self.s2_tg,
s2_sl0: self.s2_sl0,
s2_aa64: self.s2_aa64,
s2_ps: self.s2_ps,
s2_ttb: self.s2_ttb,
aa64: self.aa64,
t0sz: self.t0sz,
t1sz: self.t1sz,
mev: self.mev,
epd0: self.epd0,
epd1: self.epd1,
tbi: self.tbi,
ips: self.ips,
eats: self.eats,
s1_stalld: self.s1_stalld,
affd: self.affd,
s2affd: self.s2affd,
s2ha: self.s2ha,
s2hd: self.s2hd,
wxn: self.wxn,
uwxn: self.uwxn,
s2ptw: self.s2ptw,
s2_stall: self.s2_stall,
s2_record: self.s2_record,
endi: self.endi,
s2endi: self.s2endi,
ttb0: self.ttb0,
ttb1: self.ttb1,
};
config.validate()?;
Ok(config)
}
}
impl Default for StreamConfigBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct QueueConfig {
pub event_queue_size: usize,
pub command_queue_size: usize,
pub pri_queue_size: usize,
}
impl QueueConfig {
pub const MIN_QUEUE_SIZE: usize = 16;
pub const MAX_QUEUE_SIZE: usize = 65_536;
pub const DEFAULT_EVENT_QUEUE_SIZE: usize = 512;
pub const DEFAULT_COMMAND_QUEUE_SIZE: usize = 256;
pub const DEFAULT_PRI_QUEUE_SIZE: usize = 128;
#[must_use]
pub fn builder() -> QueueConfigBuilder {
QueueConfigBuilder::new()
}
#[inline]
#[must_use]
pub const fn event_queue_size(&self) -> usize {
self.event_queue_size
}
#[inline]
#[must_use]
pub const fn command_queue_size(&self) -> usize {
self.command_queue_size
}
#[inline]
#[must_use]
pub const fn pri_queue_size(&self) -> usize {
self.pri_queue_size
}
pub fn validate(&self) -> Result<(), ValidationError> {
let is_overflow_test_size = self.event_queue_size == 4;
if !is_overflow_test_size
&& (self.event_queue_size < Self::MIN_QUEUE_SIZE || self.event_queue_size > Self::MAX_QUEUE_SIZE)
{
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"event queue size {} out of range [{}, {}]",
self.event_queue_size,
Self::MIN_QUEUE_SIZE,
Self::MAX_QUEUE_SIZE
),
});
}
let is_overflow_test_size = self.command_queue_size == 4;
if !is_overflow_test_size
&& (self.command_queue_size < Self::MIN_QUEUE_SIZE || self.command_queue_size > Self::MAX_QUEUE_SIZE)
{
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"command queue size {} out of range [{}, {}]",
self.command_queue_size,
Self::MIN_QUEUE_SIZE,
Self::MAX_QUEUE_SIZE
),
});
}
let is_overflow_test_size = self.pri_queue_size == 4;
if !is_overflow_test_size
&& (self.pri_queue_size < Self::MIN_QUEUE_SIZE || self.pri_queue_size > Self::MAX_QUEUE_SIZE)
{
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"PRI queue size {} out of range [{}, {}]",
self.pri_queue_size,
Self::MIN_QUEUE_SIZE,
Self::MAX_QUEUE_SIZE
),
});
}
Ok(())
}
#[must_use]
pub fn with_event_queue_size(mut self, size: usize) -> Self {
self.event_queue_size = size;
self
}
#[must_use]
pub fn with_command_queue_size(mut self, size: usize) -> Self {
self.command_queue_size = size;
self
}
#[must_use]
pub fn with_pri_queue_size(mut self, size: usize) -> Self {
self.pri_queue_size = size;
self
}
}
impl Default for QueueConfig {
fn default() -> Self {
Self {
event_queue_size: Self::DEFAULT_EVENT_QUEUE_SIZE,
command_queue_size: Self::DEFAULT_COMMAND_QUEUE_SIZE,
pri_queue_size: Self::DEFAULT_PRI_QUEUE_SIZE,
}
}
}
#[derive(Clone, Debug)]
pub struct QueueConfigBuilder {
event_queue_size: usize,
command_queue_size: usize,
pri_queue_size: usize,
}
impl QueueConfigBuilder {
#[must_use]
pub fn new() -> Self {
Self {
event_queue_size: QueueConfig::DEFAULT_EVENT_QUEUE_SIZE,
command_queue_size: QueueConfig::DEFAULT_COMMAND_QUEUE_SIZE,
pri_queue_size: QueueConfig::DEFAULT_PRI_QUEUE_SIZE,
}
}
#[must_use]
pub fn event_queue_size(mut self, size: usize) -> Self {
self.event_queue_size = size;
self
}
#[must_use]
pub fn command_queue_size(mut self, size: usize) -> Self {
self.command_queue_size = size;
self
}
#[must_use]
pub fn pri_queue_size(mut self, size: usize) -> Self {
self.pri_queue_size = size;
self
}
#[must_use]
pub fn build(self) -> Result<QueueConfig, ValidationError> {
let config = QueueConfig {
event_queue_size: self.event_queue_size,
command_queue_size: self.command_queue_size,
pri_queue_size: self.pri_queue_size,
};
config.validate()?;
Ok(config)
}
}
impl Default for QueueConfigBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CacheConfig {
pub tlb_cache_size: usize,
pub cache_max_age_ms: u32,
pub enable_caching: bool,
}
impl CacheConfig {
pub const MIN_CACHE_SIZE: usize = 64;
pub const MAX_CACHE_SIZE: usize = 1_048_576;
pub const MIN_CACHE_AGE_MS: u32 = 100;
pub const MAX_CACHE_AGE_MS: u32 = 3_600_000;
pub const DEFAULT_TLB_CACHE_SIZE: usize = 1024;
pub const DEFAULT_CACHE_MAX_AGE_MS: u32 = 5000;
#[must_use]
pub fn builder() -> CacheConfigBuilder {
CacheConfigBuilder::new()
}
pub fn validate(&self) -> Result<(), ValidationError> {
if self.tlb_cache_size < Self::MIN_CACHE_SIZE || self.tlb_cache_size > Self::MAX_CACHE_SIZE {
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"TLB cache size {} out of range [{}, {}]",
self.tlb_cache_size,
Self::MIN_CACHE_SIZE,
Self::MAX_CACHE_SIZE
),
});
}
if self.cache_max_age_ms < Self::MIN_CACHE_AGE_MS || self.cache_max_age_ms > Self::MAX_CACHE_AGE_MS {
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"cache max age {} out of range [{}, {}]",
self.cache_max_age_ms,
Self::MIN_CACHE_AGE_MS,
Self::MAX_CACHE_AGE_MS
),
});
}
Ok(())
}
#[cfg(feature = "std")]
#[must_use]
pub fn cache_max_age(&self) -> Duration {
Duration::from_millis(u64::from(self.cache_max_age_ms))
}
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
tlb_cache_size: Self::DEFAULT_TLB_CACHE_SIZE,
cache_max_age_ms: Self::DEFAULT_CACHE_MAX_AGE_MS,
enable_caching: true,
}
}
}
#[derive(Clone, Debug)]
pub struct CacheConfigBuilder {
tlb_cache_size: usize,
cache_max_age_ms: u32,
enable_caching: bool,
}
impl CacheConfigBuilder {
#[must_use]
pub fn new() -> Self {
Self {
tlb_cache_size: CacheConfig::DEFAULT_TLB_CACHE_SIZE,
cache_max_age_ms: CacheConfig::DEFAULT_CACHE_MAX_AGE_MS,
enable_caching: true,
}
}
#[must_use]
pub fn tlb_cache_size(mut self, size: usize) -> Self {
self.tlb_cache_size = size;
self
}
#[must_use]
pub fn cache_max_age_ms(mut self, age_ms: u32) -> Self {
self.cache_max_age_ms = age_ms;
self
}
#[must_use]
pub fn enable_caching(mut self, enable: bool) -> Self {
self.enable_caching = enable;
self
}
#[must_use]
pub fn build(self) -> Result<CacheConfig, ValidationError> {
let config = CacheConfig {
tlb_cache_size: self.tlb_cache_size,
cache_max_age_ms: self.cache_max_age_ms,
enable_caching: self.enable_caching,
};
config.validate()?;
Ok(config)
}
}
impl Default for CacheConfigBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AddressConfig {
pub max_iova_bits: u8,
pub max_pa_bits: u8,
pub max_stream_count: u32,
pub max_pasid_count: u32,
}
impl AddressConfig {
pub const MIN_IOVA_BITS: u8 = 32;
pub const MAX_IOVA_BITS: u8 = 52;
pub const MIN_PA_BITS: u8 = 32;
pub const MAX_PA_BITS: u8 = 52;
pub const MIN_STREAM_COUNT: u32 = 1;
pub const MAX_STREAM_COUNT: u32 = 1_048_576;
pub const MIN_PASID_COUNT: u32 = 1;
pub const MAX_PASID_COUNT: u32 = 1_048_576;
pub const DEFAULT_IOVA_BITS: u8 = 48;
pub const DEFAULT_PA_BITS: u8 = 52;
pub const DEFAULT_STREAM_COUNT: u32 = 65_536;
pub const DEFAULT_PASID_COUNT: u32 = 1_048_576;
#[must_use]
pub fn builder() -> AddressConfigBuilder {
AddressConfigBuilder::new()
}
pub fn validate(&self) -> Result<(), ValidationError> {
if self.max_iova_bits < Self::MIN_IOVA_BITS || self.max_iova_bits > Self::MAX_IOVA_BITS {
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"max IOVA bits {} out of range [{}, {}]",
self.max_iova_bits,
Self::MIN_IOVA_BITS,
Self::MAX_IOVA_BITS
),
});
}
if self.max_pa_bits < Self::MIN_PA_BITS || self.max_pa_bits > Self::MAX_PA_BITS {
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"max PA bits {} out of range [{}, {}]",
self.max_pa_bits,
Self::MIN_PA_BITS,
Self::MAX_PA_BITS
),
});
}
if self.max_stream_count < Self::MIN_STREAM_COUNT || self.max_stream_count > Self::MAX_STREAM_COUNT {
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"max stream count {} out of range [{}, {}]",
self.max_stream_count,
Self::MIN_STREAM_COUNT,
Self::MAX_STREAM_COUNT
),
});
}
if self.max_pasid_count < Self::MIN_PASID_COUNT || self.max_pasid_count > Self::MAX_PASID_COUNT {
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"max PASID count {} out of range [{}, {}]",
self.max_pasid_count,
Self::MIN_PASID_COUNT,
Self::MAX_PASID_COUNT
),
});
}
Ok(())
}
}
impl Default for AddressConfig {
fn default() -> Self {
Self {
max_iova_bits: Self::DEFAULT_IOVA_BITS,
max_pa_bits: Self::DEFAULT_PA_BITS,
max_stream_count: Self::DEFAULT_STREAM_COUNT,
max_pasid_count: Self::DEFAULT_PASID_COUNT,
}
}
}
#[derive(Clone, Debug)]
pub struct AddressConfigBuilder {
max_iova_bits: u8,
max_pa_bits: u8,
max_stream_count: u32,
max_pasid_count: u32,
}
impl AddressConfigBuilder {
#[must_use]
pub fn new() -> Self {
Self {
max_iova_bits: AddressConfig::DEFAULT_IOVA_BITS,
max_pa_bits: AddressConfig::DEFAULT_PA_BITS,
max_stream_count: AddressConfig::DEFAULT_STREAM_COUNT,
max_pasid_count: AddressConfig::DEFAULT_PASID_COUNT,
}
}
#[must_use]
pub fn max_iova_bits(mut self, bits: u8) -> Self {
self.max_iova_bits = bits;
self
}
#[must_use]
pub fn max_pa_bits(mut self, bits: u8) -> Self {
self.max_pa_bits = bits;
self
}
#[must_use]
pub fn max_stream_count(mut self, count: u32) -> Self {
self.max_stream_count = count;
self
}
#[must_use]
pub fn max_pasid_count(mut self, count: u32) -> Self {
self.max_pasid_count = count;
self
}
#[must_use]
pub fn build(self) -> Result<AddressConfig, ValidationError> {
let config = AddressConfig {
max_iova_bits: self.max_iova_bits,
max_pa_bits: self.max_pa_bits,
max_stream_count: self.max_stream_count,
max_pasid_count: self.max_pasid_count,
};
config.validate()?;
Ok(config)
}
}
impl Default for AddressConfigBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResourceLimits {
pub max_memory_usage: u64,
pub max_thread_count: u32,
pub timeout_ms: u32,
pub enable_resource_tracking: bool,
}
impl ResourceLimits {
pub const MIN_MEMORY_USAGE: u64 = 1024 * 1024;
pub const MAX_MEMORY_USAGE: u64 = 64 * 1024 * 1024 * 1024;
pub const MIN_THREAD_COUNT: u32 = 1;
pub const MAX_THREAD_COUNT: u32 = 256;
pub const MIN_TIMEOUT_MS: u32 = 10;
pub const MAX_TIMEOUT_MS: u32 = 300_000;
pub const DEFAULT_MAX_MEMORY_USAGE: u64 = 1024 * 1024 * 1024;
pub const DEFAULT_MAX_THREAD_COUNT: u32 = 8;
pub const DEFAULT_TIMEOUT_MS: u32 = 1000;
#[must_use]
pub fn builder() -> ResourceLimitsBuilder {
ResourceLimitsBuilder::new()
}
pub fn validate(&self) -> Result<(), ValidationError> {
if self.max_memory_usage < Self::MIN_MEMORY_USAGE || self.max_memory_usage > Self::MAX_MEMORY_USAGE {
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"max memory usage {} out of range [{}, {}]",
self.max_memory_usage,
Self::MIN_MEMORY_USAGE,
Self::MAX_MEMORY_USAGE
),
});
}
if self.max_thread_count < Self::MIN_THREAD_COUNT || self.max_thread_count > Self::MAX_THREAD_COUNT {
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"max thread count {} out of range [{}, {}]",
self.max_thread_count,
Self::MIN_THREAD_COUNT,
Self::MAX_THREAD_COUNT
),
});
}
if self.timeout_ms < Self::MIN_TIMEOUT_MS || self.timeout_ms > Self::MAX_TIMEOUT_MS {
return Err(ValidationError::InvalidConfiguration {
reason: format!(
"timeout {} out of range [{}, {}]",
self.timeout_ms,
Self::MIN_TIMEOUT_MS,
Self::MAX_TIMEOUT_MS
),
});
}
Ok(())
}
#[cfg(feature = "std")]
#[must_use]
pub fn timeout(&self) -> Duration {
Duration::from_millis(u64::from(self.timeout_ms))
}
#[must_use]
pub const fn max_memory_bytes(&self) -> u64 {
self.max_memory_usage
}
#[must_use]
pub const fn max_memory_kb(&self) -> u64 {
self.max_memory_usage / 1024
}
#[must_use]
pub const fn max_memory_mb(&self) -> u64 {
self.max_memory_usage / (1024 * 1024)
}
#[must_use]
pub const fn max_memory_gb(&self) -> u64 {
self.max_memory_usage / (1024 * 1024 * 1024)
}
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
max_memory_usage: Self::DEFAULT_MAX_MEMORY_USAGE,
max_thread_count: Self::DEFAULT_MAX_THREAD_COUNT,
timeout_ms: Self::DEFAULT_TIMEOUT_MS,
enable_resource_tracking: true,
}
}
}
#[derive(Clone, Debug)]
pub struct ResourceLimitsBuilder {
max_memory_usage: u64,
max_thread_count: u32,
timeout_ms: u32,
enable_resource_tracking: bool,
}
impl ResourceLimitsBuilder {
#[must_use]
pub fn new() -> Self {
Self {
max_memory_usage: ResourceLimits::DEFAULT_MAX_MEMORY_USAGE,
max_thread_count: ResourceLimits::DEFAULT_MAX_THREAD_COUNT,
timeout_ms: ResourceLimits::DEFAULT_TIMEOUT_MS,
enable_resource_tracking: true,
}
}
#[must_use]
pub fn max_memory_usage(mut self, usage: u64) -> Self {
self.max_memory_usage = usage;
self
}
#[must_use]
pub fn max_thread_count(mut self, count: u32) -> Self {
self.max_thread_count = count;
self
}
#[must_use]
pub fn timeout_ms(mut self, timeout: u32) -> Self {
self.timeout_ms = timeout;
self
}
#[must_use]
pub fn enable_resource_tracking(mut self, enable: bool) -> Self {
self.enable_resource_tracking = enable;
self
}
#[must_use]
pub fn build(self) -> Result<ResourceLimits, ValidationError> {
let limits = ResourceLimits {
max_memory_usage: self.max_memory_usage,
max_thread_count: self.max_thread_count,
timeout_ms: self.timeout_ms,
enable_resource_tracking: self.enable_resource_tracking,
};
limits.validate()?;
Ok(limits)
}
}
impl Default for ResourceLimitsBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ConfigurationErrorType {
InvalidQueueSize,
InvalidCacheSize,
InvalidAddressSize,
InvalidResourceLimit,
InvalidFormat,
MissingRequired,
OutOfRange,
}
impl fmt::Display for ConfigurationErrorType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidQueueSize => write!(f, "Invalid queue size"),
Self::InvalidCacheSize => write!(f, "Invalid cache size"),
Self::InvalidAddressSize => write!(f, "Invalid address size"),
Self::InvalidResourceLimit => write!(f, "Invalid resource limit"),
Self::InvalidFormat => write!(f, "Invalid format"),
Self::MissingRequired => write!(f, "Missing required field"),
Self::OutOfRange => write!(f, "Value out of range"),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConfigurationError {
pub error_type: ConfigurationErrorType,
pub field: String,
pub message: String,
}
impl ConfigurationError {
#[must_use]
pub fn new(error_type: ConfigurationErrorType, field: String, message: String) -> Self {
Self { error_type, field, message }
}
}
impl fmt::Display for ConfigurationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {} - {}", self.error_type, self.field, self.message)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ConfigurationError {}
impl From<ValidationError> for ConfigurationError {
fn from(error: ValidationError) -> Self {
match error {
ValidationError::InvalidConfiguration { reason } => {
Self::new(ConfigurationErrorType::InvalidFormat, "unknown".to_string(), reason)
},
ValidationError::InvalidPASID { value } => Self::new(
ConfigurationErrorType::OutOfRange,
"pasid".to_string(),
format!("invalid PASID: {value}"),
),
_ => Self::new(
ConfigurationErrorType::InvalidFormat,
"unknown".to_string(),
format!("{error:?}"),
),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ValidationResult {
pub is_valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl ValidationResult {
#[must_use]
pub fn success() -> Self {
Self {
is_valid: true,
errors: Vec::new(),
warnings: Vec::new(),
}
}
#[must_use]
pub fn with_error(error: String) -> Self {
Self {
is_valid: false,
errors: vec![error],
warnings: Vec::new(),
}
}
pub fn add_error(&mut self, error: String) {
self.is_valid = false;
self.errors.push(error);
}
pub fn add_warning(&mut self, warning: String) {
self.warnings.push(warning);
}
pub fn merge(&mut self, other: ValidationResult) {
if !other.is_valid {
self.is_valid = false;
}
self.errors.extend(other.errors);
self.warnings.extend(other.warnings);
}
}
impl Default for ValidationResult {
fn default() -> Self {
Self {
is_valid: false,
errors: Vec::new(),
warnings: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ConfigConstants;
impl ConfigConstants {
pub const DEFAULT_CONFIG_FILE: &'static str = "smmu_config.conf";
pub const BACKUP_CONFIG_FILE: &'static str = "smmu_config.conf.bak";
pub const CONFIG_VERSION: &'static str = "v1.0.0";
pub const ENV_CONFIG_FILE: &'static str = "SMMU_CONFIG_FILE";
pub const ENV_QUEUE_SIZE: &'static str = "SMMU_QUEUE_SIZE";
pub const ENV_CACHE_SIZE: &'static str = "SMMU_CACHE_SIZE";
pub const ENV_MEMORY_LIMIT: &'static str = "SMMU_MEMORY_LIMIT";
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SMMUConfig {
pub queue_config: QueueConfig,
pub cache_config: CacheConfig,
pub address_config: AddressConfig,
pub resource_limits: ResourceLimits,
}
impl SMMUConfig {
#[must_use]
pub fn builder() -> SMMUConfigBuilder {
SMMUConfigBuilder::new()
}
#[must_use]
pub fn default_config() -> Self {
Self::default()
}
#[must_use]
pub fn high_performance() -> Self {
Self {
queue_config: QueueConfig {
event_queue_size: 2048,
command_queue_size: 1024,
pri_queue_size: 512,
},
cache_config: CacheConfig {
tlb_cache_size: 16_384,
cache_max_age_ms: 10_000,
enable_caching: true,
},
address_config: AddressConfig::default(),
resource_limits: ResourceLimits::default(),
}
}
#[must_use]
pub fn low_memory() -> Self {
Self {
queue_config: QueueConfig {
event_queue_size: 64,
command_queue_size: 32,
pri_queue_size: 16,
},
cache_config: CacheConfig {
tlb_cache_size: 128,
cache_max_age_ms: 2000,
enable_caching: true,
},
address_config: AddressConfig {
max_iova_bits: 32,
max_pa_bits: 40,
max_stream_count: 256,
max_pasid_count: 1024,
},
resource_limits: ResourceLimits {
max_memory_usage: 128 * 1024 * 1024, max_thread_count: 2,
timeout_ms: 500,
enable_resource_tracking: true,
},
}
}
#[must_use]
pub fn minimal() -> Self {
Self {
queue_config: QueueConfig {
event_queue_size: QueueConfig::MIN_QUEUE_SIZE,
command_queue_size: QueueConfig::MIN_QUEUE_SIZE,
pri_queue_size: QueueConfig::MIN_QUEUE_SIZE,
},
cache_config: CacheConfig {
tlb_cache_size: CacheConfig::MIN_CACHE_SIZE,
cache_max_age_ms: CacheConfig::MIN_CACHE_AGE_MS,
enable_caching: false,
},
address_config: AddressConfig {
max_iova_bits: 32,
max_pa_bits: 32,
max_stream_count: 1,
max_pasid_count: 1,
},
resource_limits: ResourceLimits {
max_memory_usage: ResourceLimits::MIN_MEMORY_USAGE,
max_thread_count: ResourceLimits::MIN_THREAD_COUNT,
timeout_ms: ResourceLimits::MIN_TIMEOUT_MS,
enable_resource_tracking: false,
},
}
}
#[must_use]
pub fn server_profile() -> Self {
Self {
queue_config: QueueConfig {
event_queue_size: 4096,
command_queue_size: 2048,
pri_queue_size: 1024,
},
cache_config: CacheConfig {
tlb_cache_size: 32_768,
cache_max_age_ms: 15_000,
enable_caching: true,
},
address_config: AddressConfig::default(),
resource_limits: ResourceLimits {
max_memory_usage: 8 * 1024 * 1024 * 1024, max_thread_count: 32,
timeout_ms: 5000,
enable_resource_tracking: true,
},
}
}
#[must_use]
pub fn embedded_profile() -> Self {
Self {
queue_config: QueueConfig {
event_queue_size: 64,
command_queue_size: 32,
pri_queue_size: 16,
},
cache_config: CacheConfig {
tlb_cache_size: 256,
cache_max_age_ms: 2000,
enable_caching: true,
},
address_config: AddressConfig {
max_iova_bits: 32,
max_pa_bits: 40,
max_stream_count: 128,
max_pasid_count: 256,
},
resource_limits: ResourceLimits {
max_memory_usage: 64 * 1024 * 1024, max_thread_count: 2,
timeout_ms: 500,
enable_resource_tracking: false,
},
}
}
#[must_use]
pub fn development_profile() -> Self {
Self {
queue_config: QueueConfig::default(),
cache_config: CacheConfig::default(),
address_config: AddressConfig::default(),
resource_limits: ResourceLimits {
max_memory_usage: 2 * 1024 * 1024 * 1024, max_thread_count: 8,
timeout_ms: 10_000, enable_resource_tracking: true,
},
}
}
pub fn update_queue_sizes(
&mut self,
event_size: usize,
command_size: usize,
pri_size: usize,
) -> Result<(), ValidationError> {
let new_config = QueueConfig {
event_queue_size: event_size,
command_queue_size: command_size,
pri_queue_size: pri_size,
};
new_config.validate()?;
self.queue_config = new_config;
Ok(())
}
pub fn update_cache_settings(
&mut self,
cache_size: usize,
max_age: u32,
enable: bool,
) -> Result<(), ValidationError> {
let new_config = CacheConfig {
tlb_cache_size: cache_size,
cache_max_age_ms: max_age,
enable_caching: enable,
};
new_config.validate()?;
self.cache_config = new_config;
Ok(())
}
pub fn update_address_limits(
&mut self,
iova_bits: u8,
pa_bits: u8,
stream_count: u32,
pasid_count: u32,
) -> Result<(), ValidationError> {
let new_config = AddressConfig {
max_iova_bits: iova_bits,
max_pa_bits: pa_bits,
max_stream_count: stream_count,
max_pasid_count: pasid_count,
};
new_config.validate()?;
self.address_config = new_config;
Ok(())
}
pub fn update_resource_limits(
&mut self,
memory_usage: u64,
thread_count: u32,
timeout: u32,
) -> Result<(), ValidationError> {
let new_limits = ResourceLimits {
max_memory_usage: memory_usage,
max_thread_count: thread_count,
timeout_ms: timeout,
enable_resource_tracking: self.resource_limits.enable_resource_tracking,
};
new_limits.validate()?;
self.resource_limits = new_limits;
Ok(())
}
pub fn merge(&mut self, other: &SMMUConfig) -> Result<(), ValidationError> {
other.validate()?;
self.queue_config = other.queue_config.clone();
self.cache_config = other.cache_config.clone();
self.address_config = other.address_config.clone();
self.resource_limits = other.resource_limits.clone();
Ok(())
}
pub fn reset(&mut self) {
*self = Self::default();
}
pub fn validate(&self) -> Result<(), ValidationError> {
self.queue_config.validate()?;
self.cache_config.validate()?;
self.address_config.validate()?;
self.resource_limits.validate()?;
Ok(())
}
#[must_use]
pub fn with_max_streams(mut self, max_streams: usize) -> Self {
self.address_config.max_stream_count = max_streams as u32;
self
}
#[inline]
#[must_use]
pub const fn max_streams(&self) -> usize {
self.address_config.max_stream_count as usize
}
#[inline]
#[must_use]
pub const fn queue_config(&self) -> &QueueConfig {
&self.queue_config
}
#[must_use]
pub fn validate_detailed(&self) -> ValidationResult {
let mut result = ValidationResult::success();
if let Err(e) = self.queue_config.validate() {
result.add_error(format!("Queue config: {e:?}"));
}
if let Err(e) = self.cache_config.validate() {
result.add_error(format!("Cache config: {e:?}"));
}
if let Err(e) = self.address_config.validate() {
result.add_error(format!("Address config: {e:?}"));
}
if let Err(e) = self.resource_limits.validate() {
result.add_error(format!("Resource limits: {e:?}"));
}
if self.queue_config.event_queue_size < 128 {
result.add_warning("Event queue size is very small".to_string());
}
if self.cache_config.tlb_cache_size < 256 {
result.add_warning("TLB cache size is very small".to_string());
}
result
}
#[cfg(feature = "std")]
#[must_use]
pub fn to_string(&self) -> String {
format!(
"event_queue_size={}\ncommand_queue_size={}\npri_queue_size={}\n\
tlb_cache_size={}\ncache_max_age_ms={}\nenable_caching={}\n\
max_iova_bits={}\nmax_pa_bits={}\nmax_stream_count={}\nmax_pasid_count={}\n\
max_memory_usage={}\nmax_thread_count={}\ntimeout_ms={}\nenable_resource_tracking={}",
self.queue_config.event_queue_size,
self.queue_config.command_queue_size,
self.queue_config.pri_queue_size,
self.cache_config.tlb_cache_size,
self.cache_config.cache_max_age_ms,
self.cache_config.enable_caching,
self.address_config.max_iova_bits,
self.address_config.max_pa_bits,
self.address_config.max_stream_count,
self.address_config.max_pasid_count,
self.resource_limits.max_memory_usage,
self.resource_limits.max_thread_count,
self.resource_limits.timeout_ms,
self.resource_limits.enable_resource_tracking,
)
}
#[cfg(feature = "std")]
pub fn from_string(s: &str) -> Result<Self, ValidationError> {
let mut config = Self::default();
let mut map = HashMap::new();
for line in s.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some(pos) = line.find('=') {
let key = line[..pos].trim();
let value = line[pos + 1..].trim();
map.insert(key.to_string(), value.to_string());
}
}
if let Some(v) = map.get("event_queue_size") {
config.queue_config.event_queue_size = parse_numeric(v, "event_queue_size")?;
}
if let Some(v) = map.get("command_queue_size") {
config.queue_config.command_queue_size = parse_numeric(v, "command_queue_size")?;
}
if let Some(v) = map.get("pri_queue_size") {
config.queue_config.pri_queue_size = parse_numeric(v, "pri_queue_size")?;
}
if let Some(v) = map.get("tlb_cache_size") {
config.cache_config.tlb_cache_size = parse_numeric(v, "tlb_cache_size")?;
}
if let Some(v) = map.get("cache_max_age_ms") {
config.cache_config.cache_max_age_ms = parse_numeric(v, "cache_max_age_ms")?;
}
if let Some(v) = map.get("enable_caching") {
config.cache_config.enable_caching = v
.parse()
.map_err(|_| ValidationError::InvalidConfiguration { reason: "invalid enable_caching".into() })?;
}
if let Some(v) = map.get("max_iova_bits") {
config.address_config.max_iova_bits = parse_numeric(v, "max_iova_bits")?;
}
if let Some(v) = map.get("max_pa_bits") {
config.address_config.max_pa_bits = parse_numeric(v, "max_pa_bits")?;
}
if let Some(v) = map.get("max_stream_count") {
config.address_config.max_stream_count = parse_numeric(v, "max_stream_count")?;
}
if let Some(v) = map.get("max_pasid_count") {
config.address_config.max_pasid_count = parse_numeric(v, "max_pasid_count")?;
}
if let Some(v) = map.get("max_memory_usage") {
config.resource_limits.max_memory_usage = parse_numeric(v, "max_memory_usage")?;
}
if let Some(v) = map.get("max_thread_count") {
config.resource_limits.max_thread_count = parse_numeric(v, "max_thread_count")?;
}
if let Some(v) = map.get("timeout_ms") {
config.resource_limits.timeout_ms = parse_numeric(v, "timeout_ms")?;
}
if let Some(v) = map.get("enable_resource_tracking") {
config.resource_limits.enable_resource_tracking =
v.parse().map_err(|_| ValidationError::InvalidConfiguration {
reason: "invalid enable_resource_tracking".into(),
})?;
}
config.validate()?;
Ok(config)
}
}
impl Default for SMMUConfig {
fn default() -> Self {
Self {
queue_config: QueueConfig::default(),
cache_config: CacheConfig::default(),
address_config: AddressConfig::default(),
resource_limits: ResourceLimits::default(),
}
}
}
#[derive(Clone, Debug)]
pub struct SMMUConfigBuilder {
queue_config: QueueConfig,
cache_config: CacheConfig,
address_config: AddressConfig,
resource_limits: ResourceLimits,
}
impl SMMUConfigBuilder {
#[must_use]
pub fn new() -> Self {
Self {
queue_config: QueueConfig::default(),
cache_config: CacheConfig::default(),
address_config: AddressConfig::default(),
resource_limits: ResourceLimits::default(),
}
}
#[must_use]
pub fn queue_config(mut self, config: QueueConfig) -> Self {
self.queue_config = config;
self
}
#[must_use]
pub fn cache_config(mut self, config: CacheConfig) -> Self {
self.cache_config = config;
self
}
#[must_use]
pub fn address_config(mut self, config: AddressConfig) -> Self {
self.address_config = config;
self
}
#[must_use]
pub fn resource_limits(mut self, limits: ResourceLimits) -> Self {
self.resource_limits = limits;
self
}
#[must_use]
pub fn build(self) -> Result<SMMUConfig, ValidationError> {
let config = SMMUConfig {
queue_config: self.queue_config,
cache_config: self.cache_config,
address_config: self.address_config,
resource_limits: self.resource_limits,
};
config.validate()?;
Ok(config)
}
}
impl Default for SMMUConfigBuilder {
fn default() -> Self {
Self::new()
}
}
impl From<QueueConfig> for SMMUConfig {
fn from(queue_config: QueueConfig) -> Self {
SMMUConfig {
queue_config,
cache_config: CacheConfig::default(),
address_config: AddressConfig::default(),
resource_limits: ResourceLimits::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_queue_config_validation() {
let config = QueueConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_cache_config_validation() {
let config = CacheConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_address_config_validation() {
let config = AddressConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_smmu_config_validation() {
let config = SMMUConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_resource_limits_default_construction() {
let _limits = ResourceLimits::default();
}
#[test]
fn test_resource_limits_builder() {
let _limits = ResourceLimits::builder()
.max_memory_usage(1024 * 1024 * 1024)
.max_thread_count(8)
.timeout_ms(1000)
.enable_resource_tracking(true)
.build();
}
#[test]
fn test_resource_limits_validation() {
let limits = ResourceLimits::default();
assert!(limits.validate().is_ok());
}
#[test]
fn test_resource_limits_constants() {
assert_eq!(ResourceLimits::MIN_MEMORY_USAGE, 1024 * 1024);
assert_eq!(ResourceLimits::MAX_MEMORY_USAGE, 64 * 1024 * 1024 * 1024);
}
#[test]
fn test_smmu_config_server_profile() {
let _config = SMMUConfig::server_profile();
}
#[test]
fn test_smmu_config_embedded_profile() {
let _config = SMMUConfig::embedded_profile();
}
#[test]
fn test_smmu_config_development_profile() {
let _config = SMMUConfig::development_profile();
}
#[test]
fn test_smmu_config_update_queue_sizes() {
let mut config = SMMUConfig::default();
assert!(config.update_queue_sizes(1024, 512, 256).is_ok());
}
#[test]
fn test_smmu_config_update_cache_settings() {
let mut config = SMMUConfig::default();
assert!(config.update_cache_settings(2048, 10_000, true).is_ok());
}
#[test]
fn test_smmu_config_update_address_limits() {
let mut config = SMMUConfig::default();
assert!(config.update_address_limits(40, 44, 1024, 2048).is_ok());
}
#[test]
fn test_smmu_config_update_resource_limits() {
let mut config = SMMUConfig::default();
assert!(config.update_resource_limits(2 * 1024 * 1024 * 1024, 16, 2000).is_ok());
}
#[test]
fn test_smmu_config_merge() {
let mut base = SMMUConfig::default();
let overlay = SMMUConfig::high_performance();
assert!(base.merge(&overlay).is_ok());
}
#[test]
fn test_smmu_config_reset() {
let mut config = SMMUConfig::high_performance();
config.reset();
assert_eq!(config, SMMUConfig::default());
}
#[test]
fn test_smmu_config_to_string() {
let config = SMMUConfig::default();
let _config_str = config.to_string();
}
#[test]
fn test_smmu_config_from_string() {
let config_str = "event_queue_size=1024\ntlb_cache_size=2048";
let _config = SMMUConfig::from_string(config_str);
}
#[test]
fn test_smmu_config_validate_detailed() {
let config = SMMUConfig::default();
let result = config.validate_detailed();
assert!(result.is_valid);
}
#[test]
fn test_configuration_error_construction() {
let _error = ConfigurationError::new(
ConfigurationErrorType::InvalidQueueSize,
"event_queue_size".to_string(),
"value out of range".to_string(),
);
}
#[test]
fn test_configuration_error_types_exist() {
let _types = [
ConfigurationErrorType::InvalidQueueSize,
ConfigurationErrorType::InvalidCacheSize,
ConfigurationErrorType::InvalidAddressSize,
ConfigurationErrorType::InvalidResourceLimit,
ConfigurationErrorType::InvalidFormat,
ConfigurationErrorType::MissingRequired,
ConfigurationErrorType::OutOfRange,
];
}
#[test]
fn test_validation_result_success() {
let result = ValidationResult::success();
assert!(result.is_valid);
assert!(result.errors.is_empty());
}
#[test]
fn test_validation_result_with_error() {
let result = ValidationResult::with_error("test error".to_string());
assert!(!result.is_valid);
assert_eq!(result.errors.len(), 1);
}
#[test]
fn test_validation_result_add_warning() {
let mut result = ValidationResult::success();
result.add_warning("test warning".to_string());
assert!(result.is_valid);
assert_eq!(result.warnings.len(), 1);
}
#[test]
fn test_config_constants_default_file() {
assert_eq!(ConfigConstants::DEFAULT_CONFIG_FILE, "smmu_config.conf");
}
#[test]
fn test_config_constants_env_vars() {
assert_eq!(ConfigConstants::ENV_CONFIG_FILE, "SMMU_CONFIG_FILE");
assert_eq!(ConfigConstants::ENV_QUEUE_SIZE, "SMMU_QUEUE_SIZE");
}
#[test]
fn test_config_constants_version() {
assert!(!ConfigConstants::CONFIG_VERSION.is_empty());
}
}