#![warn(missing_docs)]
use crate::types::{PagePermissions, SecurityState, StreamID, IOVA, PA, PASID};
use crate::types::config::StreamWorld;
use smallvec::SmallVec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CacheEntry {
pub iova: IOVA,
pub physical_address: PA,
pub permissions: PagePermissions,
pub security_state: SecurityState,
pub timestamp: u64,
pub asid: u16,
pub vmid: u16,
pub ipa: u64,
pub strw: StreamWorld,
}
impl CacheEntry {
#[inline]
pub const fn new(iova: IOVA, physical_address: PA, permissions: PagePermissions, timestamp: u64) -> Self {
Self {
iova,
physical_address,
permissions,
security_state: SecurityState::NonSecure,
timestamp,
asid: 0,
vmid: 0,
ipa: 0,
strw: StreamWorld::El1El0,
}
}
#[inline]
pub const fn new_with_security(
iova: IOVA,
physical_address: PA,
permissions: PagePermissions,
security_state: SecurityState,
timestamp: u64,
) -> Self {
Self {
iova,
physical_address,
permissions,
security_state,
timestamp,
asid: 0,
vmid: 0,
ipa: 0,
strw: StreamWorld::El1El0,
}
}
#[inline]
pub const fn new_with_asid(
iova: IOVA,
physical_address: PA,
permissions: PagePermissions,
security_state: SecurityState,
asid: u16,
timestamp: u64,
) -> Self {
Self {
iova,
physical_address,
permissions,
security_state,
timestamp,
asid,
vmid: 0,
ipa: 0,
strw: StreamWorld::El1El0,
}
}
#[inline]
pub const fn new_with_tags(
iova: IOVA,
physical_address: PA,
permissions: PagePermissions,
security_state: SecurityState,
asid: u16,
vmid: u16,
timestamp: u64,
) -> Self {
Self {
iova,
physical_address,
permissions,
security_state,
timestamp,
asid,
vmid,
ipa: 0,
strw: StreamWorld::El1El0,
}
}
}
impl Default for CacheEntry {
fn default() -> Self {
Self {
iova: IOVA::const_new(0),
physical_address: PA::const_new(0),
permissions: PagePermissions::none(),
security_state: SecurityState::NonSecure,
timestamp: 0,
asid: 0,
vmid: 0,
ipa: 0,
strw: StreamWorld::El1El0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CacheKey {
pub stream_id: StreamID,
pub pasid: PASID,
pub iova: IOVA,
pub security_state: SecurityState,
}
impl CacheKey {
#[inline]
pub const fn new(stream_id: StreamID, pasid: PASID, iova: IOVA, security_state: SecurityState) -> Self {
Self { stream_id, pasid, iova, security_state }
}
}
#[derive(Debug)]
pub struct CacheKeyHash;
impl CacheKeyHash {
#[inline(always)]
pub fn hash(key: &CacheKey) -> u64 {
let stream_u64 = u64::from(key.stream_id.as_u32());
let stream = stream_u64.wrapping_mul(0x9e37_79b9_7f4a_7c15_u64);
let pasid_u64 = u64::from(key.pasid.as_u32());
let pasid = pasid_u64.wrapping_mul(0x6c62_272e_07bb_0142_u64);
let security = u64::from(key.security_state as u8) & 0x3;
let page = (key.iova.as_u64() >> 12).wrapping_mul(0x517c_c1b7_2722_0a95_u64);
let mut hash = stream
.wrapping_add(pasid)
.wrapping_add(security)
.wrapping_add(page)
^ 0xcbf2_9ce4_8422_2325_u64;
hash ^= hash >> 33;
hash = hash.wrapping_mul(0xff51_afd7_ed55_8ccd);
hash ^= hash >> 33;
hash = hash.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
hash ^= hash >> 33;
hash
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StreamPASIDKey {
pub stream_id: StreamID,
pub pasid: PASID,
}
impl StreamPASIDKey {
#[inline]
pub const fn new(stream_id: StreamID, pasid: PASID) -> Self {
Self { stream_id, pasid }
}
}
#[derive(Debug)]
pub struct StreamPASIDKeyHash;
impl StreamPASIDKeyHash {
#[inline(always)]
pub fn hash(key: &StreamPASIDKey) -> u64 {
let combined = (u64::from(key.stream_id.as_u32()) << 32) | u64::from(key.pasid.as_u32());
let mut hash = combined.wrapping_add(0xdead_beef);
hash ^= hash >> 33;
hash = hash.wrapping_mul(0xff51_afd7_ed55_8ccd);
hash ^= hash >> 33;
hash
}
}
use std::hash::{BuildHasher, Hasher};
#[derive(Debug, Clone, Default)]
pub struct FxBuildHasher;
#[derive(Debug, Default)]
pub struct FxHasher {
hash: u64,
}
impl Hasher for FxHasher {
#[inline]
fn finish(&self) -> u64 {
self.hash
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
let mut chunks = bytes.chunks_exact(8);
for chunk in chunks.by_ref() {
let word = u64::from_ne_bytes(chunk.try_into().expect("chunk is exactly 8 bytes"));
self.write_u64(word);
}
let tail = chunks.remainder();
if !tail.is_empty() {
let mut word_bytes = [0u8; 8];
word_bytes[..tail.len()].copy_from_slice(tail);
let word = u64::from_ne_bytes(word_bytes);
self.write_u64(word);
}
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.hash ^= i;
self.hash = self.hash.wrapping_mul(0xff51_afd7_ed55_8ccd);
self.hash ^= self.hash >> 33;
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(u64::from(i));
}
}
impl BuildHasher for FxBuildHasher {
type Hasher = FxHasher;
#[inline]
fn build_hasher(&self) -> FxHasher {
FxHasher { hash: 0 }
}
}
use dashmap::DashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReplacementPolicy {
Lru,
Fifo,
}
impl Default for ReplacementPolicy {
fn default() -> Self {
Self::Lru
}
}
#[derive(Debug)]
pub struct CacheStatistics {
pub lookups: AtomicU64,
pub hits: AtomicU64,
pub misses: AtomicU64,
pub evictions: AtomicU64,
pub insertions: AtomicU64,
pub invalidations: AtomicU64,
}
impl CacheStatistics {
#[inline]
pub const fn new() -> Self {
Self {
lookups: AtomicU64::new(0),
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
evictions: AtomicU64::new(0),
insertions: AtomicU64::new(0),
invalidations: AtomicU64::new(0),
}
}
#[inline]
pub fn reset(&self) {
self.lookups.store(0, Ordering::Relaxed);
self.hits.store(0, Ordering::Relaxed);
self.misses.store(0, Ordering::Relaxed);
self.evictions.store(0, Ordering::Relaxed);
self.insertions.store(0, Ordering::Relaxed);
self.invalidations.store(0, Ordering::Relaxed);
}
#[inline]
pub fn get_lookups(&self) -> u64 {
self.lookups.load(Ordering::Relaxed)
}
#[inline]
pub fn get_hits(&self) -> u64 {
self.hits.load(Ordering::Relaxed)
}
#[inline]
pub fn get_misses(&self) -> u64 {
self.misses.load(Ordering::Relaxed)
}
#[inline]
pub fn get_evictions(&self) -> u64 {
self.evictions.load(Ordering::Relaxed)
}
#[inline]
pub fn get_insertions(&self) -> u64 {
self.insertions.load(Ordering::Relaxed)
}
#[inline]
pub fn get_invalidations(&self) -> u64 {
self.invalidations.load(Ordering::Relaxed)
}
#[inline]
pub fn hit_rate(&self) -> f64 {
let hits = self.get_hits();
let lookups = self.get_lookups();
if lookups == 0 {
0.0
} else {
(hits as f64 / lookups as f64) * 100.0
}
}
#[inline]
pub fn miss_rate(&self) -> f64 {
let misses = self.get_misses();
let lookups = self.get_lookups();
if lookups == 0 {
0.0
} else {
(misses as f64 / lookups as f64) * 100.0
}
}
}
impl Default for CacheStatistics {
fn default() -> Self {
Self::new()
}
}
pub struct TlbCache {
entries: Arc<DashMap<CacheKey, CacheEntry, FxBuildHasher>>,
capacity: usize,
policy: ReplacementPolicy,
timestamp: AtomicU64,
statistics: Arc<CacheStatistics>,
}
impl TlbCache {
pub fn new(capacity: usize, policy: ReplacementPolicy) -> Self {
assert!(capacity > 0, "TlbCache capacity must be greater than 0");
Self {
entries: Arc::new(DashMap::with_capacity_and_hasher(capacity, FxBuildHasher)),
capacity,
policy,
timestamp: AtomicU64::new(0),
statistics: Arc::new(CacheStatistics::new()),
}
}
#[inline(always)]
pub fn lookup(&self, key: &CacheKey) -> Option<CacheEntry> {
self.statistics.lookups.fetch_add(1, Ordering::Relaxed);
if self.policy == ReplacementPolicy::Lru {
if let Some(mut entry_ref) = self.entries.get_mut(key) {
self.statistics.hits.fetch_add(1, Ordering::Relaxed);
let ts = self.timestamp.fetch_add(1, Ordering::Relaxed);
entry_ref.timestamp = ts;
return Some(*entry_ref);
}
self.statistics.misses.fetch_add(1, Ordering::Relaxed);
None
} else {
if let Some(entry_ref) = self.entries.get(key) {
self.statistics.hits.fetch_add(1, Ordering::Relaxed);
return Some(*entry_ref);
}
self.statistics.misses.fetch_add(1, Ordering::Relaxed);
None
}
}
#[inline]
pub fn insert(&self, key: CacheKey, mut entry: CacheEntry) {
let timestamp = self.timestamp.fetch_add(1, Ordering::Relaxed);
entry.timestamp = timestamp;
self.entries.insert(key, entry);
while self.entries.len() > self.capacity {
self.evict_one();
}
self.statistics.insertions.fetch_add(1, Ordering::Relaxed);
}
#[allow(dead_code)]
#[inline(always)]
fn evict_one_fast(&self) {
for entry_ref in self.entries.iter().take(1) {
let key_to_remove = *entry_ref.key();
drop(entry_ref);
if self.entries.remove(&key_to_remove).is_some() {
self.statistics.evictions.fetch_add(1, Ordering::Relaxed);
break;
}
}
}
fn evict_one(&self) {
let key_to_evict = match self.policy {
ReplacementPolicy::Lru => {
self.entries
.iter()
.min_by_key(|entry| entry.value().timestamp)
.map(|entry| *entry.key())
},
ReplacementPolicy::Fifo => {
self.entries.iter().next().map(|entry| *entry.key())
},
};
if let Some(key) = key_to_evict {
self.remove_entry(&key);
self.statistics.evictions.fetch_add(1, Ordering::Relaxed);
}
}
#[inline]
fn remove_entry(&self, key: &CacheKey) {
self.entries.remove(key);
}
pub fn invalidate_all(&self) {
let count = self.entries.len();
self.entries.clear();
self.statistics.invalidations.fetch_add(count as u64, Ordering::Relaxed);
}
pub fn invalidate_nsnh_all(&self) {
let mut count = 0usize;
self.entries.retain(|_key, entry| {
if entry.strw == StreamWorld::El1El0
&& entry.security_state == SecurityState::NonSecure
{
count += 1;
false } else {
true }
});
self.statistics.invalidations.fetch_add(count as u64, Ordering::Relaxed);
}
pub fn invalidate_el2_all(&self) {
let mut count = 0usize;
self.entries.retain(|_key, entry| {
if entry.strw == StreamWorld::El2 || entry.strw == StreamWorld::El2E2h {
count += 1;
false } else {
true }
});
self.statistics.invalidations.fetch_add(count as u64, Ordering::Relaxed);
}
pub fn invalidate_nh_by_vmid(&self, vmid: u16) {
let mut count = 0usize;
self.entries.retain(|_key, entry| {
if entry.strw == StreamWorld::El1El0 && entry.vmid == vmid {
count += 1;
false } else {
true }
});
self.statistics.invalidations.fetch_add(count as u64, Ordering::Relaxed);
}
pub fn invalidate_el2_e2h_by_asid(&self, target_asid: u16) {
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry in self.entries.iter() {
let e = entry.value();
if e.strw == StreamWorld::El2E2h && e.asid == target_asid {
keys_to_remove.push(*entry.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_el2_by_va_and_asid(&self, va: u64, target_asid: u16) {
const PAGE_MASK: u64 = 0xFFFF_FFFF_FFFF_F000;
let page_va = va & PAGE_MASK;
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry_ref in self.entries.iter() {
let e = entry_ref.value();
if (e.strw == StreamWorld::El2 || e.strw == StreamWorld::El2E2h)
&& e.asid == target_asid
&& (e.iova.as_u64() & PAGE_MASK) == page_va
{
keys_to_remove.push(*entry_ref.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_el2_by_va_range_and_asid(&self, start: u64, end: u64, target_asid: u16) {
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry_ref in self.entries.iter() {
let e = entry_ref.value();
let iova = e.iova.as_u64();
if (e.strw == StreamWorld::El2 || e.strw == StreamWorld::El2E2h)
&& e.asid == target_asid
&& iova >= start
&& iova <= end
{
keys_to_remove.push(*entry_ref.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_el2_by_va(&self, va: u64) {
const PAGE_MASK: u64 = 0xFFFF_FFFF_FFFF_F000;
let page_va = va & PAGE_MASK;
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry_ref in self.entries.iter() {
let e = entry_ref.value();
if (e.strw == StreamWorld::El2 || e.strw == StreamWorld::El2E2h)
&& (e.iova.as_u64() & PAGE_MASK) == page_va
{
keys_to_remove.push(*entry_ref.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_stream(&self, stream_id: StreamID) {
let mut removed_count = 0;
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry in self.entries.iter() {
if entry.key().stream_id == stream_id {
keys_to_remove.push(*entry.key());
}
}
for key in keys_to_remove {
self.remove_entry(&key);
removed_count += 1;
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_pasid(&self, pasid: PASID) {
let mut removed_count = 0;
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry in self.entries.iter() {
if entry.key().pasid == pasid {
keys_to_remove.push(*entry.key());
}
}
for key in keys_to_remove {
self.remove_entry(&key);
removed_count += 1;
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_stream_pasid(&self, stream_id: StreamID, pasid: PASID) {
let mut removed_count = 0;
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry in self.entries.iter() {
let key = entry.key();
if key.stream_id == stream_id && key.pasid == pasid {
keys_to_remove.push(*key);
}
}
for key in keys_to_remove {
self.remove_entry(&key);
removed_count += 1;
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_va_range(&self, stream_id: StreamID, pasid: PASID, start: IOVA, end: IOVA) {
let mut removed_count = 0;
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry in self.entries.iter() {
let key = entry.key();
if key.stream_id == stream_id
&& key.pasid == pasid
&& key.iova.as_u64() >= start.as_u64()
&& key.iova.as_u64() <= end.as_u64()
{
keys_to_remove.push(*key);
}
}
for key in keys_to_remove {
self.remove_entry(&key);
removed_count += 1;
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_asid(&self, target_asid: u16) {
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry in self.entries.iter() {
if entry.value().asid == target_asid {
keys_to_remove.push(*entry.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_vmid(&self, target_vmid: u16) {
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry in self.entries.iter() {
if entry.value().vmid == target_vmid {
keys_to_remove.push(*entry.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_vmid_and_asid(&self, target_vmid: u16, target_asid: u16) {
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry in self.entries.iter() {
let e = entry.value();
if e.vmid == target_vmid && e.asid == target_asid {
keys_to_remove.push(*entry.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_va_and_asid(&self, va: u64, target_asid: u16) {
const PAGE_MASK: u64 = 0xFFFF_FFFF_FFFF_F000;
let page_va = va & PAGE_MASK;
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry_ref in self.entries.iter() {
let e = entry_ref.value();
if e.asid == target_asid && (e.iova.as_u64() & PAGE_MASK) == page_va {
keys_to_remove.push(*entry_ref.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_va(&self, va: u64) {
const PAGE_MASK: u64 = 0xFFFF_FFFF_FFFF_F000;
let page_va = va & PAGE_MASK;
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry_ref in self.entries.iter() {
if (entry_ref.value().iova.as_u64() & PAGE_MASK) == page_va {
keys_to_remove.push(*entry_ref.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_va_range_and_asid(&self, start: u64, end: u64, target_asid: u16) {
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry_ref in self.entries.iter() {
let e = entry_ref.value();
let iova = e.iova.as_u64();
if e.asid == target_asid && iova >= start && iova <= end {
keys_to_remove.push(*entry_ref.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_vmid_and_va_and_asid(&self, target_vmid: u16, va: u64, target_asid: u16) {
const PAGE_MASK: u64 = 0xFFFF_FFFF_FFFF_F000;
let page_va = va & PAGE_MASK;
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry_ref in self.entries.iter() {
let e = entry_ref.value();
if e.vmid == target_vmid
&& e.asid == target_asid
&& (e.iova.as_u64() & PAGE_MASK) == page_va
{
keys_to_remove.push(*entry_ref.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_vmid_and_va_range_and_asid(
&self,
target_vmid: u16,
start: u64,
end: u64,
target_asid: u16,
) {
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry_ref in self.entries.iter() {
let e = entry_ref.value();
let iova = e.iova.as_u64();
if e.vmid == target_vmid && e.asid == target_asid && iova >= start && iova <= end {
keys_to_remove.push(*entry_ref.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_vmid_and_va(&self, target_vmid: u16, va: u64) {
const PAGE_MASK: u64 = 0xFFFF_FFFF_FFFF_F000;
let page_va = va & PAGE_MASK;
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry_ref in self.entries.iter() {
let e = entry_ref.value();
if e.vmid == target_vmid && (e.iova.as_u64() & PAGE_MASK) == page_va {
keys_to_remove.push(*entry_ref.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_vmid_with_mask(&self, target_vmid: u16, vmid_mask: u16) {
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry_ref in self.entries.iter() {
let e = entry_ref.value();
if (e.vmid & vmid_mask) == (target_vmid & vmid_mask) {
keys_to_remove.push(*entry_ref.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_by_vmid_and_ipa(&self, target_vmid: u16, vmid_mask: u16, ipa_start: u64, ipa_end: u64) {
let mut keys_to_remove: SmallVec<[CacheKey; 32]> = SmallVec::new();
for entry_ref in self.entries.iter() {
let e = entry_ref.value();
if e.ipa != 0
&& (e.vmid & vmid_mask) == (target_vmid & vmid_mask)
&& e.ipa >= ipa_start
&& e.ipa <= ipa_end
{
keys_to_remove.push(*entry_ref.key());
}
}
let removed_count = keys_to_remove.len() as u64;
for key in keys_to_remove {
self.remove_entry(&key);
}
self.statistics.invalidations.fetch_add(removed_count, Ordering::Relaxed);
}
pub fn invalidate_entry(&self, key: &CacheKey) -> bool {
if self.entries.remove(key).is_some() {
self.statistics.invalidations.fetch_add(1, Ordering::Relaxed);
true
} else {
false
}
}
#[inline]
pub fn statistics(&self) -> &CacheStatistics {
&self.statistics
}
#[inline]
pub fn clear_statistics(&self) {
self.statistics.reset();
}
#[inline]
pub fn len(&self) -> usize {
self.entries.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline]
pub fn policy(&self) -> ReplacementPolicy {
self.policy
}
}
impl std::fmt::Debug for TlbCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TlbCache")
.field("capacity", &self.capacity)
.field("policy", &self.policy)
.field("len", &self.len())
.field("statistics", &self.statistics)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_entry_default_construction() {
let entry = CacheEntry::default();
assert_eq!(entry.iova.as_u64(), 0);
assert_eq!(entry.physical_address.as_u64(), 0);
assert_eq!(entry.permissions, PagePermissions::none());
assert_eq!(entry.security_state, SecurityState::NonSecure);
assert_eq!(entry.timestamp, 0);
}
#[test]
fn test_cache_entry_new_default_security() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::read_only();
let entry = CacheEntry::new(iova, pa, perms, 42);
assert_eq!(entry.iova, iova);
assert_eq!(entry.physical_address, pa);
assert_eq!(entry.permissions, perms);
assert_eq!(entry.security_state, SecurityState::NonSecure);
assert_eq!(entry.timestamp, 42);
}
#[test]
fn test_cache_entry_new_with_security_nonsecure() {
let iova = IOVA::new(0x3000).unwrap();
let pa = PA::new(0x4000).unwrap();
let perms = PagePermissions::read_write();
let entry = CacheEntry::new_with_security(iova, pa, perms, SecurityState::NonSecure, 100);
assert_eq!(entry.security_state, SecurityState::NonSecure);
assert_eq!(entry.timestamp, 100);
}
#[test]
fn test_cache_entry_new_with_security_secure() {
let iova = IOVA::new(0x5000).unwrap();
let pa = PA::new(0x6000).unwrap();
let perms = PagePermissions::all();
let entry = CacheEntry::new_with_security(iova, pa, perms, SecurityState::Secure, 200);
assert_eq!(entry.security_state, SecurityState::Secure);
assert_eq!(entry.timestamp, 200);
}
#[test]
fn test_cache_entry_new_with_security_realm() {
let iova = IOVA::new(0x7000).unwrap();
let pa = PA::new(0x8000).unwrap();
let perms = PagePermissions::read_execute();
let entry = CacheEntry::new_with_security(iova, pa, perms, SecurityState::Realm, 300);
assert_eq!(entry.security_state, SecurityState::Realm);
assert_eq!(entry.timestamp, 300);
}
#[test]
fn test_cache_entry_copy_semantics() {
let entry1 = CacheEntry::default();
let entry2 = entry1;
assert_eq!(entry1.timestamp, 0);
assert_eq!(entry2.timestamp, 0);
}
#[test]
fn test_cache_entry_clone_semantics() {
let entry1 = CacheEntry::default();
let entry2 = entry1.clone();
assert_eq!(entry1, entry2);
}
#[test]
fn test_cache_entry_equality() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::read_only();
let entry1 = CacheEntry::new(iova, pa, perms, 42);
let entry2 = CacheEntry::new(iova, pa, perms, 42);
assert_eq!(entry1, entry2);
}
#[test]
fn test_cache_entry_inequality_different_iova() {
let iova1 = IOVA::new(0x1000).unwrap();
let iova2 = IOVA::new(0x2000).unwrap();
let pa = PA::new(0x3000).unwrap();
let perms = PagePermissions::read_only();
let entry1 = CacheEntry::new(iova1, pa, perms, 42);
let entry2 = CacheEntry::new(iova2, pa, perms, 42);
assert_ne!(entry1, entry2);
}
#[test]
fn test_cache_entry_inequality_different_pa() {
let iova = IOVA::new(0x1000).unwrap();
let pa1 = PA::new(0x2000).unwrap();
let pa2 = PA::new(0x3000).unwrap();
let perms = PagePermissions::read_only();
let entry1 = CacheEntry::new(iova, pa1, perms, 42);
let entry2 = CacheEntry::new(iova, pa2, perms, 42);
assert_ne!(entry1, entry2);
}
#[test]
fn test_cache_entry_inequality_different_permissions() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms1 = PagePermissions::read_only();
let perms2 = PagePermissions::read_write();
let entry1 = CacheEntry::new(iova, pa, perms1, 42);
let entry2 = CacheEntry::new(iova, pa, perms2, 42);
assert_ne!(entry1, entry2);
}
#[test]
fn test_cache_entry_inequality_different_security_state() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::read_only();
let entry1 = CacheEntry::new_with_security(iova, pa, perms, SecurityState::NonSecure, 42);
let entry2 = CacheEntry::new_with_security(iova, pa, perms, SecurityState::Secure, 42);
assert_ne!(entry1, entry2);
}
#[test]
fn test_cache_entry_inequality_different_timestamp() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::read_only();
let entry1 = CacheEntry::new(iova, pa, perms, 42);
let entry2 = CacheEntry::new(iova, pa, perms, 100);
assert_ne!(entry1, entry2);
}
#[test]
fn test_cache_entry_debug_format() {
let entry = CacheEntry::default();
let debug_str = format!("{entry:?}");
assert!(debug_str.contains("CacheEntry"));
}
#[test]
fn test_cache_entry_large_addresses() {
let iova = IOVA::new(0xFFFF_FFFF_F000).unwrap();
let pa = PA::new(0xFFFF_FFFF_E000).unwrap();
let perms = PagePermissions::read_write();
let entry = CacheEntry::new(iova, pa, perms, u64::MAX);
assert_eq!(entry.iova.as_u64(), 0xFFFF_FFFF_F000);
assert_eq!(entry.physical_address.as_u64(), 0xFFFF_FFFF_E000);
assert_eq!(entry.timestamp, u64::MAX);
}
#[test]
fn test_cache_entry_zero_timestamp() {
let entry = CacheEntry::default();
assert_eq!(entry.timestamp, 0);
}
#[test]
fn test_cache_entry_max_timestamp() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::read_only();
let entry = CacheEntry::new(iova, pa, perms, u64::MAX);
assert_eq!(entry.timestamp, u64::MAX);
}
#[test]
fn test_cache_entry_permissions_none() {
let entry = CacheEntry::default();
assert_eq!(entry.permissions, PagePermissions::none());
}
#[test]
fn test_cache_entry_permissions_read_only() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::read_only();
let entry = CacheEntry::new(iova, pa, perms, 0);
assert_eq!(entry.permissions, PagePermissions::read_only());
}
#[test]
fn test_cache_entry_permissions_write_only() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::write_only();
let entry = CacheEntry::new(iova, pa, perms, 0);
assert_eq!(entry.permissions, PagePermissions::write_only());
}
#[test]
fn test_cache_entry_permissions_execute_only() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::new(false, false, true);
let entry = CacheEntry::new(iova, pa, perms, 0);
assert!(!entry.permissions.read());
assert!(!entry.permissions.write());
assert!(entry.permissions.execute());
}
#[test]
fn test_cache_entry_permissions_read_write() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::read_write();
let entry = CacheEntry::new(iova, pa, perms, 0);
assert_eq!(entry.permissions, PagePermissions::read_write());
}
#[test]
fn test_cache_entry_permissions_read_execute() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::read_execute();
let entry = CacheEntry::new(iova, pa, perms, 0);
assert_eq!(entry.permissions, PagePermissions::read_execute());
}
#[test]
fn test_cache_entry_permissions_all() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::all();
let entry = CacheEntry::new(iova, pa, perms, 0);
assert_eq!(entry.permissions, PagePermissions::all());
}
#[test]
fn test_cache_entry_permissions_write_execute() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::new(false, true, true);
let entry = CacheEntry::new(iova, pa, perms, 0);
assert!(!entry.permissions.read());
assert!(entry.permissions.write());
assert!(entry.permissions.execute());
}
#[test]
fn test_cache_entry_const_new() {
const ENTRY: CacheEntry =
CacheEntry::new(IOVA::const_new(0x1000), PA::const_new(0x2000), PagePermissions::none(), 42);
assert_eq!(ENTRY.iova.as_u64(), 0x1000);
assert_eq!(ENTRY.physical_address.as_u64(), 0x2000);
assert_eq!(ENTRY.timestamp, 42);
}
#[test]
fn test_cache_entry_const_new_with_security() {
const ENTRY: CacheEntry = CacheEntry::new_with_security(
IOVA::const_new(0x1000),
PA::const_new(0x2000),
PagePermissions::none(),
SecurityState::Secure,
100,
);
assert_eq!(ENTRY.security_state, SecurityState::Secure);
assert_eq!(ENTRY.timestamp, 100);
}
#[test]
fn test_cache_entry_multiple_copies() {
let entry1 = CacheEntry::default();
let entry2 = entry1;
let entry3 = entry2;
let entry4 = entry3;
assert_eq!(entry1, entry2);
assert_eq!(entry2, entry3);
assert_eq!(entry3, entry4);
}
#[test]
fn test_cache_entry_page_aligned_addresses() {
let iova = IOVA::new_page_aligned(0x1000).unwrap();
let pa = PA::new_page_aligned(0x2000).unwrap();
let perms = PagePermissions::read_only();
let entry = CacheEntry::new(iova, pa, perms, 0);
assert!(entry.iova.is_page_aligned());
assert!(entry.physical_address.is_page_aligned());
}
#[test]
fn test_cache_entry_non_page_aligned_addresses() {
let iova = IOVA::new(0x1234).unwrap();
let pa = PA::new(0x5678).unwrap();
let perms = PagePermissions::read_only();
let entry = CacheEntry::new(iova, pa, perms, 0);
assert_eq!(entry.iova.as_u64(), 0x1234);
assert_eq!(entry.physical_address.as_u64(), 0x5678);
}
#[test]
fn test_cache_entry_security_state_values() {
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::read_only();
let entry_nonsecure = CacheEntry::new_with_security(iova, pa, perms, SecurityState::NonSecure, 0);
let entry_secure = CacheEntry::new_with_security(iova, pa, perms, SecurityState::Secure, 0);
let entry_realm = CacheEntry::new_with_security(iova, pa, perms, SecurityState::Realm, 0);
assert_eq!(entry_nonsecure.security_state, SecurityState::NonSecure);
assert_eq!(entry_secure.security_state, SecurityState::Secure);
assert_eq!(entry_realm.security_state, SecurityState::Realm);
}
#[test]
fn test_cache_key_new() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
assert_eq!(key.stream_id, stream_id);
assert_eq!(key.pasid, pasid);
assert_eq!(key.iova, iova);
assert_eq!(key.security_state, SecurityState::NonSecure);
}
#[test]
fn test_cache_key_equality_same_values() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key1 = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let key2 = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
assert_eq!(key1, key2);
}
#[test]
fn test_cache_key_inequality_different_stream_id() {
let stream1 = StreamID::new(100).unwrap();
let stream2 = StreamID::new(200).unwrap();
let pasid = PASID::new(300).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key1 = CacheKey::new(stream1, pasid, iova, SecurityState::NonSecure);
let key2 = CacheKey::new(stream2, pasid, iova, SecurityState::NonSecure);
assert_ne!(key1, key2);
}
#[test]
fn test_cache_key_inequality_different_pasid() {
let stream_id = StreamID::new(100).unwrap();
let pasid1 = PASID::new(200).unwrap();
let pasid2 = PASID::new(300).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key1 = CacheKey::new(stream_id, pasid1, iova, SecurityState::NonSecure);
let key2 = CacheKey::new(stream_id, pasid2, iova, SecurityState::NonSecure);
assert_ne!(key1, key2);
}
#[test]
fn test_cache_key_inequality_different_iova() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova1 = IOVA::new(0x1000).unwrap();
let iova2 = IOVA::new(0x2000).unwrap();
let key1 = CacheKey::new(stream_id, pasid, iova1, SecurityState::NonSecure);
let key2 = CacheKey::new(stream_id, pasid, iova2, SecurityState::NonSecure);
assert_ne!(key1, key2);
}
#[test]
fn test_cache_key_inequality_different_security_state() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key1 = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let key2 = CacheKey::new(stream_id, pasid, iova, SecurityState::Secure);
assert_ne!(key1, key2);
}
#[test]
fn test_cache_key_copy_semantics() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key1 = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let key2 = key1;
assert_eq!(key1, key2);
}
#[test]
fn test_cache_key_clone_semantics() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key1 = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let key2 = key1.clone();
assert_eq!(key1, key2);
}
#[test]
fn test_cache_key_debug_format() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let debug_str = format!("{key:?}");
assert!(debug_str.contains("CacheKey"));
}
#[test]
fn test_cache_key_const_construction() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova = IOVA::const_new(0x1000);
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
assert_eq!(key.stream_id.as_u32(), 100);
assert_eq!(key.pasid.as_u32(), 200);
assert_eq!(key.iova.as_u64(), 0x1000);
}
#[test]
fn test_cache_key_max_values() {
let stream_id = StreamID::new(u32::from(u16::MAX)).unwrap();
let pasid = PASID::new(0xF_FFFF).unwrap(); let iova = IOVA::new(u64::MAX).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::Realm);
assert_eq!(key.stream_id.as_u32(), u32::from(u16::MAX));
assert_eq!(key.pasid.as_u32(), 0xF_FFFF);
assert_eq!(key.iova.as_u64(), u64::MAX);
}
#[test]
fn test_cache_key_min_values() {
let stream_id = StreamID::new(0).unwrap();
let pasid = PASID::new(0).unwrap();
let iova = IOVA::new(0).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
assert_eq!(key.stream_id.as_u32(), 0);
assert_eq!(key.pasid.as_u32(), 0);
assert_eq!(key.iova.as_u64(), 0);
}
#[test]
fn test_cache_key_page_aligned_iova() {
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let iova = IOVA::new_page_aligned(0x1000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
assert!(key.iova.is_page_aligned());
}
#[test]
fn test_cache_key_all_security_states() {
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key_nonsecure = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let key_secure = CacheKey::new(stream_id, pasid, iova, SecurityState::Secure);
let key_realm = CacheKey::new(stream_id, pasid, iova, SecurityState::Realm);
assert_ne!(key_nonsecure, key_secure);
assert_ne!(key_secure, key_realm);
assert_ne!(key_nonsecure, key_realm);
}
#[test]
fn test_cache_key_hashability() {
use std::collections::HashMap;
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let mut map = HashMap::new();
map.insert(key, 42);
assert_eq!(map.get(&key), Some(&42));
}
#[test]
fn test_cache_key_hash_uses_murmur_constants() {
const MIX_CONSTANT_1: u64 = 0xff51_afd7_ed55_8ccd;
const MIX_CONSTANT_2: u64 = 0xc4ce_b9fe_1a85_ec53;
assert_eq!(MIX_CONSTANT_1, 0xff51_afd7_ed55_8ccd);
assert_eq!(MIX_CONSTANT_2, 0xc4ce_b9fe_1a85_ec53);
}
#[test]
fn test_cache_key_hash_deterministic() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let hash1 = CacheKeyHash::hash(&key);
let hash2 = CacheKeyHash::hash(&key);
assert_eq!(hash1, hash2);
}
#[test]
fn test_cache_key_hash_different_stream_id() {
let stream1 = StreamID::new(100).unwrap();
let stream2 = StreamID::new(101).unwrap();
let pasid = PASID::new(200).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key1 = CacheKey::new(stream1, pasid, iova, SecurityState::NonSecure);
let key2 = CacheKey::new(stream2, pasid, iova, SecurityState::NonSecure);
let hash1 = CacheKeyHash::hash(&key1);
let hash2 = CacheKeyHash::hash(&key2);
assert_ne!(hash1, hash2);
}
#[test]
fn test_cache_key_hash_different_pasid() {
let stream_id = StreamID::new(100).unwrap();
let pasid1 = PASID::new(200).unwrap();
let pasid2 = PASID::new(201).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key1 = CacheKey::new(stream_id, pasid1, iova, SecurityState::NonSecure);
let key2 = CacheKey::new(stream_id, pasid2, iova, SecurityState::NonSecure);
let hash1 = CacheKeyHash::hash(&key1);
let hash2 = CacheKeyHash::hash(&key2);
assert_ne!(hash1, hash2);
}
#[test]
fn test_cache_key_hash_different_iova_page() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova1 = IOVA::new(0x1000).unwrap();
let iova2 = IOVA::new(0x2000).unwrap();
let key1 = CacheKey::new(stream_id, pasid, iova1, SecurityState::NonSecure);
let key2 = CacheKey::new(stream_id, pasid, iova2, SecurityState::NonSecure);
let hash1 = CacheKeyHash::hash(&key1);
let hash2 = CacheKeyHash::hash(&key2);
assert_ne!(hash1, hash2);
}
#[test]
fn test_cache_key_hash_page_offset_ignored() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova1 = IOVA::new(0x1000).unwrap(); let iova2 = IOVA::new(0x1FFF).unwrap();
let key1 = CacheKey::new(stream_id, pasid, iova1, SecurityState::NonSecure);
let key2 = CacheKey::new(stream_id, pasid, iova2, SecurityState::NonSecure);
let hash1 = CacheKeyHash::hash(&key1);
let hash2 = CacheKeyHash::hash(&key2);
assert_eq!(hash1, hash2); }
#[test]
fn test_cache_key_hash_different_security_state() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key1 = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let key2 = CacheKey::new(stream_id, pasid, iova, SecurityState::Secure);
let hash1 = CacheKeyHash::hash(&key1);
let hash2 = CacheKeyHash::hash(&key2);
assert_ne!(hash1, hash2);
}
#[test]
fn test_cache_key_hash_max_values() {
let stream_id = StreamID::new(u32::from(u16::MAX)).unwrap();
let pasid = PASID::new(0xF_FFFF).unwrap();
let iova = IOVA::new(u64::MAX).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::Realm);
let _hash = CacheKeyHash::hash(&key);
}
#[test]
fn test_cache_key_hash_min_values() {
let stream_id = StreamID::new(0).unwrap();
let pasid = PASID::new(0).unwrap();
let iova = IOVA::new(0).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let hash = CacheKeyHash::hash(&key);
assert_ne!(hash, 0);
}
#[test]
fn test_cache_key_hash_distribution_different_streams() {
let pasid = PASID::new(200).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let mut hashes = Vec::new();
for stream in 0..100 {
let stream_id = StreamID::new(stream).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
hashes.push(CacheKeyHash::hash(&key));
}
hashes.sort_unstable();
hashes.dedup();
assert_eq!(hashes.len(), 100);
}
#[test]
fn test_cache_key_hash_distribution_different_pasids() {
let stream_id = StreamID::new(100).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let mut hashes = Vec::new();
for pasid_val in 0..100 {
let pasid = PASID::new(pasid_val).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
hashes.push(CacheKeyHash::hash(&key));
}
hashes.sort_unstable();
hashes.dedup();
assert_eq!(hashes.len(), 100);
}
#[test]
fn test_cache_key_hash_distribution_different_pages() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let mut hashes = Vec::new();
for page in 0..100 {
let iova = IOVA::new(page * 0x1000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
hashes.push(CacheKeyHash::hash(&key));
}
hashes.sort_unstable();
hashes.dedup();
assert_eq!(hashes.len(), 100);
}
#[test]
fn test_cache_key_hash_distribution_security_states() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key_nonsecure = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let key_secure = CacheKey::new(stream_id, pasid, iova, SecurityState::Secure);
let key_realm = CacheKey::new(stream_id, pasid, iova, SecurityState::Realm);
let hash_nonsecure = CacheKeyHash::hash(&key_nonsecure);
let hash_secure = CacheKeyHash::hash(&key_secure);
let hash_realm = CacheKeyHash::hash(&key_realm);
assert_ne!(hash_nonsecure, hash_secure);
assert_ne!(hash_secure, hash_realm);
assert_ne!(hash_nonsecure, hash_realm);
}
#[test]
fn test_cache_key_hash_large_iova() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova = IOVA::new(0xFFFF_FFFF_FFFF_F000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let _hash = CacheKeyHash::hash(&key);
}
#[test]
fn test_cache_key_hash_page_number_upper_bits() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova1 = IOVA::new(0x0000_0001_0000).unwrap(); let iova2 = IOVA::new(0x1000_0000_0000).unwrap();
let key1 = CacheKey::new(stream_id, pasid, iova1, SecurityState::NonSecure);
let key2 = CacheKey::new(stream_id, pasid, iova2, SecurityState::NonSecure);
let hash1 = CacheKeyHash::hash(&key1);
let hash2 = CacheKeyHash::hash(&key2);
assert_ne!(hash1, hash2);
}
#[test]
fn test_cache_key_hash_avalanche_effect() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let iova1 = IOVA::new(0x1000).unwrap();
let iova2 = IOVA::new(0x2000).unwrap();
let key1 = CacheKey::new(stream_id, pasid, iova1, SecurityState::NonSecure);
let key2 = CacheKey::new(stream_id, pasid, iova2, SecurityState::NonSecure);
let hash1 = CacheKeyHash::hash(&key1);
let hash2 = CacheKeyHash::hash(&key2);
let xor = hash1 ^ hash2;
let bit_diff = xor.count_ones();
assert!(bit_diff > 10, "Expected avalanche effect, got {} bits different", bit_diff);
}
#[test]
fn test_cache_key_hash_collision_resistance() {
use std::collections::HashSet;
let mut hash_set = HashSet::new();
for stream in 0..50 {
for pasid_val in 0..50 {
let stream_id = StreamID::new(stream).unwrap();
let pasid = PASID::new(pasid_val).unwrap();
let iova = IOVA::new(u64::from(stream) * 0x1000 + u64::from(pasid_val) * 0x1_0000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let hash = CacheKeyHash::hash(&key);
assert!(hash_set.insert(hash), "Hash collision detected");
}
}
assert_eq!(hash_set.len(), 50 * 50);
}
#[test]
fn test_cache_key_hash_wrapping_mul() {
let stream_id = StreamID::new(u32::from(u16::MAX)).unwrap();
let pasid = PASID::new(0xF_FFFF).unwrap();
let iova = IOVA::new(u64::MAX).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::Realm);
let hash = CacheKeyHash::hash(&key);
let hash2 = CacheKeyHash::hash(&key);
assert_eq!(hash, hash2);
}
#[test]
fn test_cache_key_hash_fnv_algorithm() {
let stream_id = StreamID::new(1).unwrap();
let pasid_val = PASID::new(2).unwrap();
let iova = IOVA::new(0x3000).unwrap();
let key = CacheKey::new(stream_id, pasid_val, iova, SecurityState::NonSecure);
let stream = 1u64.wrapping_mul(0x9e37_79b9_7f4a_7c15_u64);
let pasid = 2u64.wrapping_mul(0x6c62_272e_07bb_0142_u64);
let security = u64::from(SecurityState::NonSecure as u8) & 0x3;
let page = 3u64.wrapping_mul(0x517c_c1b7_2722_0a95_u64);
let mut expected = stream
.wrapping_add(pasid)
.wrapping_add(security)
.wrapping_add(page)
^ 0xcbf2_9ce4_8422_2325_u64;
expected ^= expected >> 33;
expected = expected.wrapping_mul(0xff51_afd7_ed55_8ccd);
expected ^= expected >> 33;
expected = expected.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
expected ^= expected >> 33;
let actual = CacheKeyHash::hash(&key);
assert_eq!(actual, expected);
}
#[test]
fn test_stream_pasid_key_new() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let key = StreamPASIDKey::new(stream_id, pasid);
assert_eq!(key.stream_id, stream_id);
assert_eq!(key.pasid, pasid);
}
#[test]
fn test_stream_pasid_key_equality() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let key1 = StreamPASIDKey::new(stream_id, pasid);
let key2 = StreamPASIDKey::new(stream_id, pasid);
assert_eq!(key1, key2);
}
#[test]
fn test_stream_pasid_key_inequality_different_stream() {
let stream1 = StreamID::new(100).unwrap();
let stream2 = StreamID::new(101).unwrap();
let pasid = PASID::new(200).unwrap();
let key1 = StreamPASIDKey::new(stream1, pasid);
let key2 = StreamPASIDKey::new(stream2, pasid);
assert_ne!(key1, key2);
}
#[test]
fn test_stream_pasid_key_inequality_different_pasid() {
let stream_id = StreamID::new(100).unwrap();
let pasid1 = PASID::new(200).unwrap();
let pasid2 = PASID::new(201).unwrap();
let key1 = StreamPASIDKey::new(stream_id, pasid1);
let key2 = StreamPASIDKey::new(stream_id, pasid2);
assert_ne!(key1, key2);
}
#[test]
fn test_stream_pasid_key_copy_semantics() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let key1 = StreamPASIDKey::new(stream_id, pasid);
let key2 = key1;
assert_eq!(key1, key2);
}
#[test]
fn test_stream_pasid_key_clone_semantics() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let key1 = StreamPASIDKey::new(stream_id, pasid);
let key2 = key1.clone();
assert_eq!(key1, key2);
}
#[test]
fn test_stream_pasid_key_debug_format() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let key = StreamPASIDKey::new(stream_id, pasid);
let debug_str = format!("{key:?}");
assert!(debug_str.contains("StreamPASIDKey"));
}
#[test]
fn test_stream_pasid_key_const_construction() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let key = StreamPASIDKey::new(stream_id, pasid);
assert_eq!(key.stream_id.as_u32(), 100);
assert_eq!(key.pasid.as_u32(), 200);
}
#[test]
fn test_stream_pasid_key_max_values() {
let stream_id = StreamID::new(u32::from(u16::MAX)).unwrap();
let pasid = PASID::new(0xF_FFFF).unwrap();
let key = StreamPASIDKey::new(stream_id, pasid);
assert_eq!(key.stream_id.as_u32(), u32::from(u16::MAX));
assert_eq!(key.pasid.as_u32(), 0xF_FFFF);
}
#[test]
fn test_stream_pasid_key_min_values() {
let stream_id = StreamID::new(0).unwrap();
let pasid = PASID::new(0).unwrap();
let key = StreamPASIDKey::new(stream_id, pasid);
assert_eq!(key.stream_id.as_u32(), 0);
assert_eq!(key.pasid.as_u32(), 0);
}
#[test]
fn test_stream_pasid_key_hashability() {
use std::collections::HashMap;
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let key = StreamPASIDKey::new(stream_id, pasid);
let mut map = HashMap::new();
map.insert(key, 42);
assert_eq!(map.get(&key), Some(&42));
}
#[test]
fn test_stream_pasid_key_hash_uses_fast_mixing() {
const MIX_CONSTANT: u64 = 0xff51_afd7_ed55_8ccd;
assert_eq!(MIX_CONSTANT, 0xff51_afd7_ed55_8ccd);
}
#[test]
fn test_stream_pasid_key_hash_deterministic() {
let stream_id = StreamID::new(100).unwrap();
let pasid = PASID::new(200).unwrap();
let key = StreamPASIDKey::new(stream_id, pasid);
let hash1 = StreamPASIDKeyHash::hash(&key);
let hash2 = StreamPASIDKeyHash::hash(&key);
assert_eq!(hash1, hash2);
}
#[test]
fn test_stream_pasid_key_hash_different_stream() {
let stream1 = StreamID::new(100).unwrap();
let stream2 = StreamID::new(101).unwrap();
let pasid = PASID::new(200).unwrap();
let key1 = StreamPASIDKey::new(stream1, pasid);
let key2 = StreamPASIDKey::new(stream2, pasid);
let hash1 = StreamPASIDKeyHash::hash(&key1);
let hash2 = StreamPASIDKeyHash::hash(&key2);
assert_ne!(hash1, hash2);
}
#[test]
fn test_stream_pasid_key_hash_different_pasid() {
let stream_id = StreamID::new(100).unwrap();
let pasid1 = PASID::new(200).unwrap();
let pasid2 = PASID::new(201).unwrap();
let key1 = StreamPASIDKey::new(stream_id, pasid1);
let key2 = StreamPASIDKey::new(stream_id, pasid2);
let hash1 = StreamPASIDKeyHash::hash(&key1);
let hash2 = StreamPASIDKeyHash::hash(&key2);
assert_ne!(hash1, hash2);
}
#[test]
fn test_stream_pasid_key_hash_max_values() {
let stream_id = StreamID::new(u32::from(u16::MAX)).unwrap();
let pasid = PASID::new(0xF_FFFF).unwrap();
let key = StreamPASIDKey::new(stream_id, pasid);
let _hash = StreamPASIDKeyHash::hash(&key);
}
#[test]
fn test_stream_pasid_key_hash_min_values() {
let stream_id = StreamID::new(0).unwrap();
let pasid = PASID::new(0).unwrap();
let key = StreamPASIDKey::new(stream_id, pasid);
let hash = StreamPASIDKeyHash::hash(&key);
assert_ne!(hash, 0);
}
#[test]
fn test_stream_pasid_key_hash_distribution_streams() {
let pasid = PASID::new(200).unwrap();
let mut hashes = Vec::new();
for stream in 0..100 {
let stream_id = StreamID::new(stream).unwrap();
let key = StreamPASIDKey::new(stream_id, pasid);
hashes.push(StreamPASIDKeyHash::hash(&key));
}
hashes.sort_unstable();
hashes.dedup();
assert_eq!(hashes.len(), 100);
}
#[test]
fn test_stream_pasid_key_hash_distribution_pasids() {
let stream_id = StreamID::new(100).unwrap();
let mut hashes = Vec::new();
for pasid_val in 0..100 {
let pasid = PASID::new(pasid_val).unwrap();
let key = StreamPASIDKey::new(stream_id, pasid);
hashes.push(StreamPASIDKeyHash::hash(&key));
}
hashes.sort_unstable();
hashes.dedup();
assert_eq!(hashes.len(), 100);
}
#[test]
fn test_stream_pasid_key_hash_collision_resistance() {
use std::collections::HashSet;
let mut hash_set = HashSet::new();
for stream in 0..100 {
for pasid_val in 0..100 {
let stream_id = StreamID::new(stream).unwrap();
let pasid = PASID::new(pasid_val).unwrap();
let key = StreamPASIDKey::new(stream_id, pasid);
let hash = StreamPASIDKeyHash::hash(&key);
assert!(hash_set.insert(hash), "Hash collision detected");
}
}
assert_eq!(hash_set.len(), 100 * 100);
}
#[test]
fn test_stream_pasid_key_hash_fnv_algorithm() {
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let key = StreamPASIDKey::new(stream_id, pasid);
let combined = ((1u64) << 32) | 2u64;
let mut expected = combined.wrapping_add(0xdead_beef);
expected ^= expected >> 33;
expected = expected.wrapping_mul(0xff51_afd7_ed55_8ccd);
expected ^= expected >> 33;
let actual = StreamPASIDKeyHash::hash(&key);
assert_eq!(actual, expected);
}
#[test]
fn test_tlb_cache_new_lru() {
let cache = TlbCache::new(1024, ReplacementPolicy::Lru);
assert_eq!(cache.capacity(), 1024);
assert_eq!(cache.policy(), ReplacementPolicy::Lru);
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
}
#[test]
fn test_tlb_cache_new_fifo() {
let cache = TlbCache::new(512, ReplacementPolicy::Fifo);
assert_eq!(cache.capacity(), 512);
assert_eq!(cache.policy(), ReplacementPolicy::Fifo);
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
}
#[test]
#[should_panic(expected = "TlbCache capacity must be greater than 0")]
fn test_tlb_cache_new_zero_capacity() {
let _cache = TlbCache::new(0, ReplacementPolicy::Lru);
}
#[test]
fn test_tlb_cache_insert_single() {
let cache = TlbCache::new(10, ReplacementPolicy::Lru);
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
assert_eq!(cache.len(), 1);
assert!(!cache.is_empty());
assert_eq!(cache.statistics().get_insertions(), 1);
}
#[test]
fn test_tlb_cache_lookup_hit() {
let cache = TlbCache::new(10, ReplacementPolicy::Lru);
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
let result = cache.lookup(&key);
assert!(result.is_some());
let found_entry = result.unwrap();
assert_eq!(found_entry.iova, iova);
assert_eq!(found_entry.physical_address, pa);
assert_eq!(cache.statistics().get_lookups(), 1);
assert_eq!(cache.statistics().get_hits(), 1);
assert_eq!(cache.statistics().get_misses(), 0);
}
#[test]
fn test_tlb_cache_lookup_miss() {
let cache = TlbCache::new(10, ReplacementPolicy::Lru);
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let result = cache.lookup(&key);
assert!(result.is_none());
assert_eq!(cache.statistics().get_lookups(), 1);
assert_eq!(cache.statistics().get_hits(), 0);
assert_eq!(cache.statistics().get_misses(), 1);
}
#[test]
fn test_tlb_cache_multiple_inserts() {
let cache = TlbCache::new(100, ReplacementPolicy::Lru);
for i in 0..10 {
let stream_id = StreamID::new(i).unwrap();
let pasid = PASID::new(i + 100).unwrap();
let iova = IOVA::new((i as u64) * 0x1000).unwrap();
let pa = PA::new((i as u64) * 0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_write(), 0);
cache.insert(key, entry);
}
assert_eq!(cache.len(), 10);
assert_eq!(cache.statistics().get_insertions(), 10);
}
#[test]
fn test_tlb_cache_eviction_lru() {
let cache = TlbCache::new(3, ReplacementPolicy::Lru);
for i in 0..3 {
let stream_id = StreamID::new(i).unwrap();
let pasid = PASID::new(0).unwrap();
let iova = IOVA::new((i as u64) * 0x1000).unwrap();
let pa = PA::new((i as u64) * 0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
}
assert_eq!(cache.len(), 3);
let stream_id = StreamID::new(3).unwrap();
let pasid = PASID::new(0).unwrap();
let iova = IOVA::new(0x3000).unwrap();
let pa = PA::new(0x6000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
assert_eq!(cache.len(), 3);
assert_eq!(cache.statistics().get_evictions(), 1);
assert_eq!(cache.statistics().get_insertions(), 4);
}
#[test]
fn test_tlb_cache_eviction_fifo() {
let cache = TlbCache::new(3, ReplacementPolicy::Fifo);
for i in 0..3 {
let stream_id = StreamID::new(i).unwrap();
let pasid = PASID::new(0).unwrap();
let iova = IOVA::new((i as u64) * 0x1000).unwrap();
let pa = PA::new((i as u64) * 0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
}
assert_eq!(cache.len(), 3);
let stream_id = StreamID::new(3).unwrap();
let pasid = PASID::new(0).unwrap();
let iova = IOVA::new(0x3000).unwrap();
let pa = PA::new(0x6000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
assert_eq!(cache.len(), 3);
assert_eq!(cache.statistics().get_evictions(), 1);
}
#[test]
fn test_tlb_cache_invalidate_all() {
let cache = TlbCache::new(100, ReplacementPolicy::Lru);
for i in 0..10 {
let stream_id = StreamID::new(i).unwrap();
let pasid = PASID::new(0).unwrap();
let iova = IOVA::new((i as u64) * 0x1000).unwrap();
let pa = PA::new((i as u64) * 0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
}
assert_eq!(cache.len(), 10);
cache.invalidate_all();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
assert_eq!(cache.statistics().get_invalidations(), 10);
}
#[test]
fn test_tlb_cache_invalidate_by_stream() {
let cache = TlbCache::new(100, ReplacementPolicy::Lru);
let target_stream = StreamID::new(5).unwrap();
for i in 0..10 {
let stream_id = StreamID::new(i).unwrap();
let pasid = PASID::new(0).unwrap();
let iova = IOVA::new((i as u64) * 0x1000).unwrap();
let pa = PA::new((i as u64) * 0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
}
assert_eq!(cache.len(), 10);
cache.invalidate_by_stream(target_stream);
assert_eq!(cache.len(), 9);
assert_eq!(cache.statistics().get_invalidations(), 1);
let key = CacheKey::new(
target_stream,
PASID::new(0).unwrap(),
IOVA::new(5 * 0x1000).unwrap(),
SecurityState::NonSecure,
);
assert!(cache.lookup(&key).is_none());
}
#[test]
fn test_tlb_cache_invalidate_by_pasid() {
let cache = TlbCache::new(100, ReplacementPolicy::Lru);
let target_pasid = PASID::new(5).unwrap();
for i in 0..10 {
let stream_id = StreamID::new(0).unwrap();
let pasid = PASID::new(i).unwrap();
let iova = IOVA::new((i as u64) * 0x1000).unwrap();
let pa = PA::new((i as u64) * 0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
}
assert_eq!(cache.len(), 10);
cache.invalidate_by_pasid(target_pasid);
assert_eq!(cache.len(), 9);
assert_eq!(cache.statistics().get_invalidations(), 1);
let key = CacheKey::new(
StreamID::new(0).unwrap(),
target_pasid,
IOVA::new(5 * 0x1000).unwrap(),
SecurityState::NonSecure,
);
assert!(cache.lookup(&key).is_none());
}
#[test]
fn test_tlb_cache_invalidate_by_stream_pasid() {
let cache = TlbCache::new(100, ReplacementPolicy::Lru);
let target_stream = StreamID::new(5).unwrap();
let target_pasid = PASID::new(7).unwrap();
for i in 0..10 {
for j in 0..10 {
let stream_id = StreamID::new(i).unwrap();
let pasid = PASID::new(j).unwrap();
let iova = IOVA::new((i as u64) * 0x0010_0000 + u64::from(j) * 0x1000).unwrap();
let pa = PA::new((i as u64) * 0x0020_0000 + u64::from(j) * 0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
}
}
assert_eq!(cache.len(), 100);
let target_key = CacheKey::new(
target_stream,
target_pasid,
IOVA::new(5 * 0x0010_0000 + 7 * 0x1000).unwrap(),
SecurityState::NonSecure,
);
assert!(cache.lookup(&target_key).is_some());
cache.invalidate_by_stream_pasid(target_stream, target_pasid);
assert_eq!(cache.len(), 99);
assert!(cache.lookup(&target_key).is_none());
}
#[test]
fn test_tlb_cache_invalidate_by_va_range() {
let cache = TlbCache::new(100, ReplacementPolicy::Lru);
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
for i in 0..10 {
let iova = IOVA::new((i as u64) * 0x1000).unwrap();
let pa = PA::new((i as u64) * 0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
}
assert_eq!(cache.len(), 10);
let start = IOVA::new(0x2000).unwrap();
let end = IOVA::new(0x5000).unwrap();
cache.invalidate_by_va_range(stream_id, pasid, start, end);
assert_eq!(cache.len(), 6);
assert_eq!(cache.statistics().get_invalidations(), 4);
}
#[test]
fn test_tlb_cache_invalidate_entry() {
let cache = TlbCache::new(10, ReplacementPolicy::Lru);
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
assert_eq!(cache.len(), 1);
let removed = cache.invalidate_entry(&key);
assert!(removed);
assert_eq!(cache.len(), 0);
assert_eq!(cache.statistics().get_invalidations(), 1);
let removed_again = cache.invalidate_entry(&key);
assert!(!removed_again);
}
#[test]
fn test_tlb_cache_statistics_hit_rate() {
let cache = TlbCache::new(10, ReplacementPolicy::Lru);
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
cache.lookup(&key); cache.lookup(&key); cache.lookup(&key);
let other_key = CacheKey::new(StreamID::new(99).unwrap(), pasid, iova, SecurityState::NonSecure);
cache.lookup(&other_key); cache.lookup(&other_key);
let stats = cache.statistics();
assert_eq!(stats.get_lookups(), 5);
assert_eq!(stats.get_hits(), 3);
assert_eq!(stats.get_misses(), 2);
assert!((stats.hit_rate() - 60.0).abs() < 0.01);
assert!((stats.miss_rate() - 40.0).abs() < 0.01);
}
#[test]
fn test_tlb_cache_statistics_clear() {
let cache = TlbCache::new(10, ReplacementPolicy::Lru);
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
cache.lookup(&key);
assert_eq!(cache.statistics().get_insertions(), 1);
assert_eq!(cache.statistics().get_lookups(), 1);
cache.clear_statistics();
assert_eq!(cache.statistics().get_insertions(), 0);
assert_eq!(cache.statistics().get_lookups(), 0);
assert_eq!(cache.statistics().get_hits(), 0);
assert_eq!(cache.statistics().get_misses(), 0);
assert_eq!(cache.len(), 1);
}
#[test]
fn test_tlb_cache_replacement_policy_default() {
let policy = ReplacementPolicy::default();
assert_eq!(policy, ReplacementPolicy::Lru);
}
#[test]
fn test_tlb_cache_concurrent_inserts() {
use std::sync::Arc;
use std::thread;
let cache = Arc::new(TlbCache::new(1000, ReplacementPolicy::Lru));
let mut handles = vec![];
for thread_id in 0..10 {
let cache_clone = Arc::clone(&cache);
let handle = thread::spawn(move || {
for i in 0..10 {
let stream_id = StreamID::new(thread_id).unwrap();
let pasid = PASID::new(i).unwrap();
let iova = IOVA::new((thread_id as u64) * 0x1_0000 + (i as u64) * 0x1000).unwrap();
let pa = PA::new((thread_id as u64) * 0x2_0000 + (i as u64) * 0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_write(), 0);
cache_clone.insert(key, entry);
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(cache.len(), 100);
assert_eq!(cache.statistics().get_insertions(), 100);
}
#[test]
fn test_tlb_cache_concurrent_lookups() {
use std::sync::Arc;
use std::thread;
let cache = Arc::new(TlbCache::new(100, ReplacementPolicy::Lru));
for i in 0..10 {
let stream_id = StreamID::new(i).unwrap();
let pasid = PASID::new(0).unwrap();
let iova = IOVA::new((i as u64) * 0x1000).unwrap();
let pa = PA::new((i as u64) * 0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
}
let mut handles = vec![];
for _thread_id in 0..10 {
let cache_clone = Arc::clone(&cache);
let handle = thread::spawn(move || {
for i in 0..10 {
let stream_id = StreamID::new(i).unwrap();
let pasid = PASID::new(0).unwrap();
let iova = IOVA::new((i as u64) * 0x1000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let _result = cache_clone.lookup(&key);
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(cache.statistics().get_lookups(), 100);
assert_eq!(cache.statistics().get_hits(), 100);
assert_eq!(cache.statistics().get_misses(), 0);
}
#[test]
fn test_tlb_cache_security_state_isolation() {
let cache = TlbCache::new(10, ReplacementPolicy::Lru);
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let pa_nonsecure = PA::new(0x2000).unwrap();
let pa_secure = PA::new(0x3000).unwrap();
let key_nonsecure = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry_nonsecure = CacheEntry::new_with_security(
iova,
pa_nonsecure,
PagePermissions::read_only(),
SecurityState::NonSecure,
0,
);
let key_secure = CacheKey::new(stream_id, pasid, iova, SecurityState::Secure);
let entry_secure =
CacheEntry::new_with_security(iova, pa_secure, PagePermissions::read_only(), SecurityState::Secure, 0);
cache.insert(key_nonsecure, entry_nonsecure);
cache.insert(key_secure, entry_secure);
assert_eq!(cache.len(), 2);
let result_nonsecure = cache.lookup(&key_nonsecure).unwrap();
let result_secure = cache.lookup(&key_secure).unwrap();
assert_eq!(result_nonsecure.physical_address, pa_nonsecure);
assert_eq!(result_secure.physical_address, pa_secure);
assert_eq!(result_nonsecure.security_state, SecurityState::NonSecure);
assert_eq!(result_secure.security_state, SecurityState::Secure);
}
#[test]
fn test_tlb_cache_large_capacity() {
let cache = TlbCache::new(10_000, ReplacementPolicy::Lru);
for i in 0..1000 {
let stream_id = StreamID::new(i % 100).unwrap();
let pasid = PASID::new(i % 50).unwrap();
let iova = IOVA::new((i as u64) * 0x1000).unwrap();
let pa = PA::new((i as u64) * 0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_write(), 0);
cache.insert(key, entry);
}
assert_eq!(cache.len(), 1000);
assert_eq!(cache.statistics().get_insertions(), 1000);
assert_eq!(cache.statistics().get_evictions(), 0); }
#[test]
fn test_tlb_cache_debug_format() {
let cache = TlbCache::new(100, ReplacementPolicy::Lru);
let debug_str = format!("{cache:?}");
assert!(debug_str.contains("TlbCache"));
assert!(debug_str.contains("capacity"));
assert!(debug_str.contains("policy"));
}
#[test]
fn test_tlb_cache_empty_operations() {
let cache = TlbCache::new(10, ReplacementPolicy::Lru);
cache.invalidate_all();
cache.invalidate_by_stream(StreamID::new(1).unwrap());
cache.invalidate_by_pasid(PASID::new(1).unwrap());
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
}
#[test]
fn test_tlb_cache_permissions_preserved() {
let cache = TlbCache::new(10, ReplacementPolicy::Lru);
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let perms = PagePermissions::read_execute();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, perms, 0);
cache.insert(key, entry);
let result = cache.lookup(&key).unwrap();
assert_eq!(result.permissions, perms);
assert!(result.permissions.read());
assert!(!result.permissions.write());
assert!(result.permissions.execute());
}
#[test]
fn test_tlb_cache_lru_timestamp_update() {
let cache = TlbCache::new(10, ReplacementPolicy::Lru);
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
let entry1 = cache.lookup(&key).unwrap();
let timestamp1 = entry1.timestamp;
let entry2 = cache.lookup(&key).unwrap();
let timestamp2 = entry2.timestamp;
assert!(timestamp2 > timestamp1);
}
#[test]
fn test_tlb_cache_fifo_no_timestamp_update() {
let cache = TlbCache::new(10, ReplacementPolicy::Fifo);
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
let entry1 = cache.lookup(&key).unwrap();
let timestamp1 = entry1.timestamp;
let entry2 = cache.lookup(&key).unwrap();
let timestamp2 = entry2.timestamp;
assert_eq!(timestamp1, timestamp2);
}
#[test]
fn test_tlb_cache_statistics_zero_lookups() {
let stats = CacheStatistics::new();
assert_eq!(stats.hit_rate(), 0.0);
assert_eq!(stats.miss_rate(), 0.0);
}
#[test]
fn test_tlb_cache_invalidate_nonexistent_stream() {
let cache = TlbCache::new(10, ReplacementPolicy::Lru);
let stream_id = StreamID::new(1).unwrap();
let pasid = PASID::new(2).unwrap();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
cache.invalidate_by_stream(StreamID::new(99).unwrap());
assert_eq!(cache.len(), 1);
assert!(cache.lookup(&key).is_some());
}
#[test]
fn test_tlb_cache_multiple_streams_same_pasid() {
let cache = TlbCache::new(100, ReplacementPolicy::Lru);
let pasid = PASID::new(1).unwrap();
for i in 0..10 {
let stream_id = StreamID::new(i).unwrap();
let iova = IOVA::new((i as u64) * 0x1000).unwrap();
let pa = PA::new((i as u64) * 0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
}
assert_eq!(cache.len(), 10);
cache.invalidate_by_pasid(pasid);
assert_eq!(cache.len(), 0);
}
#[test]
fn test_tlb_cache_same_stream_multiple_pasids() {
let cache = TlbCache::new(100, ReplacementPolicy::Lru);
let stream_id = StreamID::new(1).unwrap();
for i in 0..10 {
let pasid = PASID::new(i).unwrap();
let iova = IOVA::new((i as u64) * 0x1000).unwrap();
let pa = PA::new((i as u64) * 0x2000).unwrap();
let key = CacheKey::new(stream_id, pasid, iova, SecurityState::NonSecure);
let entry = CacheEntry::new(iova, pa, PagePermissions::read_only(), 0);
cache.insert(key, entry);
}
assert_eq!(cache.len(), 10);
cache.invalidate_by_stream(stream_id);
assert_eq!(cache.len(), 0);
}
}