#![warn(missing_docs)]
use crate::types::{
AccessType, PageEntry, PagePermissions, SecurityState, TranslationData, TranslationError, TranslationResult, IOVA,
PA, PAGE_SIZE,
};
use dashmap::DashMap;
use smallvec::SmallVec;
use std::sync::atomic::{AtomicU64, Ordering};
use thiserror::Error;
const MAX_VIRTUAL_ADDRESS: u64 = (1u64 << 52) - 1;
const MAX_PHYSICAL_ADDRESS: u64 = (1u64 << 52) - 1;
const PAGE_MASK: u64 = PAGE_SIZE - 1;
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum AddressSpaceError {
#[error("Invalid address: 0x{address:x}")]
InvalidAddress {
address: u64,
},
#[error("Invalid permissions - at least one permission must be set")]
InvalidPermissions,
#[error("Invalid security state")]
InvalidSecurityState,
#[error("Page not mapped")]
PageNotMapped,
#[error("Internal error")]
InternalError,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AddressRange {
pub start: IOVA,
pub end: IOVA,
}
impl AddressRange {
#[must_use]
pub const fn new(start: IOVA, end: IOVA) -> Self {
Self { start, end }
}
}
#[derive(Debug)]
pub struct AddressRangeIterator {
current: u64,
end: u64,
}
impl Iterator for AddressRangeIterator {
type Item = PageInfo;
fn next(&mut self) -> Option<Self::Item> {
if self.current <= self.end {
let iova = IOVA::new(self.current).ok()?;
let page_number = self.current >> 12;
self.current += PAGE_SIZE;
Some(PageInfo { iova, page_number })
} else {
None
}
}
}
impl IntoIterator for AddressRange {
type Item = PageInfo;
type IntoIter = AddressRangeIterator;
fn into_iter(self) -> Self::IntoIter {
AddressRangeIterator {
current: self.start.as_u64() & !(PAGE_SIZE - 1),
end: self.end.as_u64(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PageInfo {
iova: IOVA,
page_number: u64,
}
impl PageInfo {
#[must_use]
pub const fn iova(&self) -> IOVA {
self.iova
}
#[must_use]
pub const fn page_number(&self) -> u64 {
self.page_number
}
}
#[derive(Debug, Clone)]
pub struct PageEntryRef {
iova: IOVA,
entry: PageEntry,
}
impl PageEntryRef {
#[must_use]
pub const fn iova(&self) -> IOVA {
self.iova
}
#[must_use]
pub const fn entry(&self) -> &PageEntry {
&self.entry
}
#[must_use]
pub const fn physical_address(&self) -> PA {
self.entry.physical_address()
}
#[must_use]
pub const fn permissions(&self) -> PagePermissions {
self.entry.permissions()
}
#[must_use]
pub const fn security_state(&self) -> SecurityState {
self.entry.security_state()
}
}
#[derive(Debug)]
pub struct PageEntryMutRef<'a> {
iova: IOVA,
entry: &'a mut PageEntry,
}
impl<'a> PageEntryMutRef<'a> {
#[must_use]
pub const fn iova(&self) -> IOVA {
self.iova
}
pub fn set_permissions(&mut self, perms: PagePermissions) {
*self.entry = self.entry.clone().with_permissions(perms);
}
#[must_use]
pub fn permissions(&self) -> PagePermissions {
self.entry.permissions()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct RangeStats {
pub total_pages: usize,
pub readable_pages: usize,
pub writable_pages: usize,
pub executable_pages: usize,
}
#[derive(Debug)]
pub struct AddressSpaceQuery<'a> {
addr_space: &'a AddressSpace,
}
impl<'a> AddressSpaceQuery<'a> {
#[must_use]
pub fn page_count(&self) -> usize {
self.addr_space.page_table.len()
}
#[must_use]
pub fn is_mapped(&self, iova: IOVA) -> bool {
let page_num = self.addr_space.page_number(iova);
self.addr_space.page_table.contains_key(&page_num)
}
pub fn get_all_entries(&self) -> Vec<PageEntry> {
self.addr_space.page_table.iter().map(|entry| entry.value().clone()).collect()
}
#[must_use]
pub fn range_statistics(&self, start: IOVA, end: IOVA) -> RangeStats {
let start_page = self.addr_space.page_number(start);
let end_page = self.addr_space.page_number(end);
let mut stats = RangeStats::default();
for page_num in start_page..=end_page {
if let Some(entry_ref) = self.addr_space.page_table.get(&page_num) {
let entry = entry_ref.value();
stats.total_pages += 1;
if entry.permissions().read() {
stats.readable_pages += 1;
}
if entry.permissions().write() {
stats.writable_pages += 1;
}
if entry.permissions().execute() {
stats.executable_pages += 1;
}
}
}
stats
}
}
#[derive(Debug)]
pub struct AddressSpace {
page_table: DashMap<u64, PageEntry>,
invalidation_generation: AtomicU64,
invalidation_map: DashMap<u64, AtomicU64>,
}
impl Clone for AddressSpace {
fn clone(&self) -> Self {
let new_invalidation_map = DashMap::new();
for entry in &self.invalidation_map {
new_invalidation_map.insert(*entry.key(), AtomicU64::new(entry.value().load(Ordering::Acquire)));
}
Self {
page_table: self.page_table.clone(),
invalidation_generation: AtomicU64::new(self.invalidation_generation.load(Ordering::Acquire)),
invalidation_map: new_invalidation_map,
}
}
}
impl AddressSpace {
#[must_use]
pub fn new() -> Self {
Self {
page_table: DashMap::new(),
invalidation_generation: AtomicU64::new(0),
invalidation_map: DashMap::new(),
}
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
page_table: DashMap::with_capacity(capacity),
invalidation_generation: AtomicU64::new(0),
invalidation_map: DashMap::with_capacity(capacity),
}
}
pub fn map_page(
&self,
iova: IOVA,
pa: PA,
permissions: PagePermissions,
security_state: SecurityState,
) -> Result<(), AddressSpaceError> {
if iova.as_u64() > MAX_VIRTUAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: iova.as_u64() });
}
if pa.as_u64() > MAX_PHYSICAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: pa.as_u64() });
}
if !permissions.read() && !permissions.write() && !permissions.execute() {
return Err(AddressSpaceError::InvalidPermissions);
}
let page_num = self.page_number(iova);
let aligned_pa = PA::new(pa.as_u64() & !PAGE_MASK).unwrap();
let entry = PageEntry::with_security_state(aligned_pa, permissions, security_state)
.with_access_flag(true);
self.page_table.insert(page_num, entry);
Ok(())
}
pub fn unmap_page(&self, iova: IOVA) -> Result<(), AddressSpaceError> {
if iova.as_u64() > MAX_VIRTUAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: iova.as_u64() });
}
let page_num = self.page_number(iova);
if !self.page_table.contains_key(&page_num) {
return Err(AddressSpaceError::PageNotMapped);
}
self.page_table.remove(&page_num);
Ok(())
}
pub fn map_page_unaccessed(
&self,
iova: IOVA,
pa: PA,
permissions: PagePermissions,
security_state: SecurityState,
) -> Result<(), AddressSpaceError> {
self.map_page(iova, pa, permissions, security_state)?;
let page_num = self.page_number(iova);
if let Some(mut entry) = self.page_table.get_mut(&page_num) {
*entry = entry.clone().with_access_flag(false);
}
Ok(())
}
pub fn map_page_device(
&self,
iova: IOVA,
pa: PA,
permissions: PagePermissions,
security_state: SecurityState,
) -> Result<(), AddressSpaceError> {
self.map_page(iova, pa, permissions, security_state)?;
let page_num = self.page_number(iova);
if let Some(mut entry) = self.page_table.get_mut(&page_num) {
*entry = entry.clone().with_device_memory(true);
}
Ok(())
}
pub fn get_page_device_memory(&self, iova: IOVA) -> bool {
let page_num = self.page_number(iova);
self.page_table.get(&page_num).map_or(false, |e| e.is_device_memory())
}
pub fn translate_page(
&self,
iova: IOVA,
access_type: AccessType,
security_state: SecurityState,
) -> TranslationResult {
let page_num = self.page_number(iova);
let entry = self.page_table.get(&page_num).ok_or(TranslationError::PageNotMapped)?;
if !entry.is_valid() {
return Err(TranslationError::PageNotMapped);
}
if entry.security_state() != security_state {
return Err(TranslationError::SecurityViolation);
}
if !self.check_permissions(entry.permissions(), access_type) {
return Err(TranslationError::PermissionViolation { access: access_type });
}
let page_offset = iova.as_u64() & PAGE_MASK;
let translated_pa = PA::new(entry.physical_address().as_u64() + page_offset).unwrap();
let mut data = TranslationData::new(translated_pa, entry.permissions(), entry.security_state());
if entry.is_device_memory() {
data.page_attr = 0x00; }
Ok(data)
}
pub fn is_page_mapped(&self, iova: IOVA) -> Result<bool, AddressSpaceError> {
if iova.as_u64() > MAX_VIRTUAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: iova.as_u64() });
}
let page_num = self.page_number(iova);
Ok(self.page_table.contains_key(&page_num))
}
pub fn get_page_permissions(&self, iova: IOVA) -> Result<PagePermissions, AddressSpaceError> {
if iova.as_u64() > MAX_VIRTUAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: iova.as_u64() });
}
let page_num = self.page_number(iova);
self.page_table
.get(&page_num)
.map(|entry| entry.permissions())
.ok_or(AddressSpaceError::PageNotMapped)
}
pub fn get_page_count(&self) -> Result<usize, AddressSpaceError> {
Ok(self.page_table.len())
}
pub fn clear(&mut self) -> Result<(), AddressSpaceError> {
self.page_table.clear();
Ok(())
}
pub fn map_range(
&mut self,
start_iova: IOVA,
end_iova: IOVA,
start_pa: PA,
permissions: PagePermissions,
security_state: SecurityState,
) -> Result<(), AddressSpaceError> {
if end_iova.as_u64() < start_iova.as_u64() {
return Err(AddressSpaceError::InvalidAddress { address: end_iova.as_u64() });
}
if start_iova.as_u64() > MAX_VIRTUAL_ADDRESS || end_iova.as_u64() > MAX_VIRTUAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: start_iova.as_u64() });
}
if start_pa.as_u64() > MAX_PHYSICAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: start_pa.as_u64() });
}
if !permissions.read() && !permissions.write() && !permissions.execute() {
return Err(AddressSpaceError::InvalidPermissions);
}
let aligned_start_iova = start_iova.as_u64() & !PAGE_MASK;
let aligned_start_pa = start_pa.as_u64() & !PAGE_MASK;
let start_page_num = self.page_number(IOVA::new(aligned_start_iova).unwrap());
let end_page_num = self.page_number(end_iova);
let num_pages = end_page_num.saturating_sub(start_page_num).saturating_add(1);
let last_pa = aligned_start_pa
.checked_add(num_pages.saturating_sub(1).saturating_mul(PAGE_SIZE))
.ok_or(AddressSpaceError::InvalidAddress { address: aligned_start_pa })?;
if last_pa > MAX_PHYSICAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: last_pa });
}
let mut current_pa = aligned_start_pa;
for page_num in start_page_num..=end_page_num {
let entry = PageEntry::with_security_state(PA::new(current_pa).unwrap(), permissions, security_state)
.with_access_flag(true);
self.page_table.insert(page_num, entry);
current_pa = current_pa
.checked_add(PAGE_SIZE)
.ok_or(AddressSpaceError::InvalidAddress { address: current_pa })?;
}
Ok(())
}
pub fn unmap_range(&mut self, start_iova: IOVA, end_iova: IOVA) -> Result<(), AddressSpaceError> {
if end_iova.as_u64() < start_iova.as_u64() {
return Err(AddressSpaceError::InvalidAddress { address: end_iova.as_u64() });
}
if start_iova.as_u64() > MAX_VIRTUAL_ADDRESS || end_iova.as_u64() > MAX_VIRTUAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: start_iova.as_u64() });
}
let start_page_num = self.page_number(start_iova);
let end_page_num = self.page_number(end_iova);
let any_mapped = (start_page_num..=end_page_num).any(|page_num| self.page_table.contains_key(&page_num));
if !any_mapped {
return Err(AddressSpaceError::PageNotMapped);
}
for page_num in start_page_num..=end_page_num {
self.page_table.remove(&page_num);
}
Ok(())
}
pub fn map_pages(
&self,
mappings: &[(IOVA, PA)],
permissions: PagePermissions,
security_state: SecurityState,
) -> Result<(), AddressSpaceError> {
if !permissions.read() && !permissions.write() && !permissions.execute() {
return Err(AddressSpaceError::InvalidPermissions);
}
for &(iova, pa) in mappings {
if iova.as_u64() > MAX_VIRTUAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: iova.as_u64() });
}
if pa.as_u64() > MAX_PHYSICAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: pa.as_u64() });
}
}
for &(iova, pa) in mappings {
let page_num = self.page_number(iova);
let aligned_pa = PA::new(pa.as_u64() & !PAGE_MASK).unwrap();
let entry = PageEntry::with_security_state(aligned_pa, permissions, security_state)
.with_access_flag(true);
self.page_table.insert(page_num, entry);
}
Ok(())
}
pub fn unmap_pages(&self, iovas: &[IOVA]) -> Result<(), AddressSpaceError> {
for &iova in iovas {
if iova.as_u64() > MAX_VIRTUAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: iova.as_u64() });
}
}
let any_mapped = iovas.iter().any(|&iova| self.page_table.contains_key(&self.page_number(iova)));
if !any_mapped {
return Err(AddressSpaceError::PageNotMapped);
}
for &iova in iovas {
let page_num = self.page_number(iova);
self.page_table.remove(&page_num);
}
Ok(())
}
#[must_use]
pub fn get_mapped_ranges(&self) -> Vec<AddressRange> {
let mut ranges = Vec::new();
if self.page_table.is_empty() {
return ranges;
}
let mut page_nums: Vec<u64> = self.page_table.iter().map(|entry| *entry.key()).collect();
page_nums.sort_unstable();
let mut range_start = page_nums[0] << 12;
let mut range_end = range_start + PAGE_SIZE - 1;
for &page_num in &page_nums[1..] {
let current_page_addr = page_num << 12;
if current_page_addr == range_end + 1 {
range_end = current_page_addr + PAGE_SIZE - 1;
} else {
ranges.push(AddressRange::new(
IOVA::new(range_start).unwrap(),
IOVA::new(range_end).unwrap(),
));
range_start = current_page_addr;
range_end = range_start + PAGE_SIZE - 1;
}
}
ranges.push(AddressRange::new(
IOVA::new(range_start).unwrap(),
IOVA::new(range_end).unwrap(),
));
ranges
}
#[must_use]
pub fn get_address_space_size(&self) -> u64 {
if self.page_table.is_empty() {
return 0;
}
let min_page_num = self.page_table.iter().map(|entry| *entry.key()).min().unwrap();
let max_page_num = self.page_table.iter().map(|entry| *entry.key()).max().unwrap();
let min_address = min_page_num << 12;
let max_address = (max_page_num << 12) + PAGE_SIZE - 1;
max_address - min_address + 1
}
#[must_use]
pub fn has_overlapping_mappings(&self, start_iova: IOVA, end_iova: IOVA) -> bool {
if end_iova.as_u64() < start_iova.as_u64() {
return false;
}
let start_page_num = self.page_number(start_iova);
let end_page_num = self.page_number(end_iova);
(start_page_num..=end_page_num).any(|page_num| self.page_table.contains_key(&page_num))
}
pub fn iter(&self) -> impl Iterator<Item = PageEntryRef> + '_ {
self.page_table.iter().map(|entry_ref| {
let page_num = *entry_ref.key();
let iova = IOVA::new(page_num << 12).unwrap();
PageEntryRef { iova, entry: entry_ref.value().clone() }
})
}
pub fn iter_mut(&self) -> impl Iterator<Item = PageEntryRef> + '_ {
self.iter()
}
#[must_use]
pub const fn query(&self) -> AddressSpaceQuery<'_> {
AddressSpaceQuery { addr_space: self }
}
#[must_use]
pub fn query_page(&self, iova: IOVA) -> Option<PageEntry> {
let page_num = self.page_number(iova);
self.page_table.get(&page_num).map(|entry_ref| entry_ref.value().clone())
}
pub fn map_pages_batched(
&self,
mappings: &[(IOVA, PA)],
permissions: PagePermissions,
security_state: SecurityState,
) -> Result<(), AddressSpaceError> {
if !permissions.read() && !permissions.write() && !permissions.execute() {
return Err(AddressSpaceError::InvalidPermissions);
}
let mut batch: SmallVec<[(u64, PageEntry); 16]> = SmallVec::with_capacity(mappings.len());
for &(iova, pa) in mappings {
if iova.as_u64() > MAX_VIRTUAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: iova.as_u64() });
}
if pa.as_u64() > MAX_PHYSICAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: pa.as_u64() });
}
let page_num = self.page_number(iova);
let aligned_pa = PA::new(pa.as_u64() & !PAGE_MASK).unwrap();
let entry = PageEntry::with_security_state(aligned_pa, permissions, security_state)
.with_access_flag(true);
batch.push((page_num, entry));
}
for (page_num, entry) in batch {
self.page_table.insert(page_num, entry);
}
Ok(())
}
pub fn unmap_pages_batched(&self, iovas: &[IOVA]) -> Result<(), AddressSpaceError> {
for &iova in iovas {
if iova.as_u64() > MAX_VIRTUAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: iova.as_u64() });
}
}
let any_mapped = iovas.iter().any(|&iova| self.page_table.contains_key(&self.page_number(iova)));
if !any_mapped {
return Err(AddressSpaceError::PageNotMapped);
}
for &iova in iovas {
let page_num = self.page_number(iova);
self.page_table.remove(&page_num);
}
Ok(())
}
pub fn update_permissions_batched(
&mut self,
iovas: &[IOVA],
permissions: PagePermissions,
) -> Result<(), AddressSpaceError> {
if !permissions.read() && !permissions.write() && !permissions.execute() {
return Err(AddressSpaceError::InvalidPermissions);
}
for &iova in iovas {
if iova.as_u64() > MAX_VIRTUAL_ADDRESS {
return Err(AddressSpaceError::InvalidAddress { address: iova.as_u64() });
}
}
for &iova in iovas {
let page_num = self.page_number(iova);
if let Some(mut entry) = self.page_table.get_mut(&page_num) {
*entry = entry.clone().with_permissions(permissions);
}
}
Ok(())
}
pub fn invalidate_page_atomic(&mut self, iova: IOVA) {
let page_num = self.page_number(iova);
self.invalidation_generation.fetch_add(1, Ordering::Release);
self.invalidation_map
.entry(page_num)
.or_insert_with(|| AtomicU64::new(0))
.fetch_add(1, Ordering::Release);
}
pub fn invalidate_range_atomic(&mut self, start: IOVA, end: IOVA) -> usize {
let start_page = self.page_number(start);
let end_page = self.page_number(end);
let mut count = 0;
for page_num in start_page..=end_page {
if self.page_table.contains_key(&page_num) {
self.invalidation_map
.entry(page_num)
.or_insert_with(|| AtomicU64::new(0))
.fetch_add(1, Ordering::Release);
count += 1;
}
}
if count > 0 {
self.invalidation_generation.fetch_add(count as u64, Ordering::Release);
}
count
}
pub fn invalidate_page_with_ordering(&mut self, iova: IOVA, ordering: Ordering) {
let page_num = self.page_number(iova);
self.invalidation_generation.fetch_add(1, ordering);
self.invalidation_map
.entry(page_num)
.or_insert_with(|| AtomicU64::new(0))
.fetch_add(1, ordering);
}
#[must_use]
pub fn is_invalidated(&self, iova: IOVA) -> bool {
let page_num = self.page_number(iova);
self.invalidation_map
.get(&page_num)
.map_or(false, |gen| gen.load(Ordering::Acquire) > 0)
}
#[must_use]
pub fn is_invalidated_with_ordering(&self, iova: IOVA, ordering: Ordering) -> bool {
let page_num = self.page_number(iova);
self.invalidation_map.get(&page_num).map_or(false, |gen| gen.load(ordering) > 0)
}
#[must_use]
pub fn invalidation_generation(&self) -> u64 {
self.invalidation_generation.load(Ordering::Acquire)
}
pub fn compare_exchange_invalidate(&mut self, iova: IOVA, current: bool, new: bool, ordering: Ordering) -> bool {
let page_num = self.page_number(iova);
let current_val = if current { 1u64 } else { 0u64 };
let new_val = if new { 1u64 } else { 0u64 };
let entry = self.invalidation_map.entry(page_num).or_insert_with(|| AtomicU64::new(0));
entry
.compare_exchange(current_val, new_val, ordering, Ordering::Relaxed)
.is_ok()
}
pub fn invalidate_page(&mut self, iova: IOVA) {
self.invalidate_page_atomic(iova);
}
pub fn invalidate_range(&mut self, _start_iova: IOVA, _end_iova: IOVA) {
}
pub fn invalidate_all(&mut self) {
}
pub fn update_access_flags(&self, iova: IOVA, ha: bool, hd: bool, access_type: AccessType) -> bool {
if !ha && !hd {
return false;
}
let page_num = self.page_number(iova);
if let Some(mut entry) = self.page_table.get_mut(&page_num) {
let af_changed = if ha && !entry.is_access_flag_set() {
*entry = entry.clone().with_access_flag(true);
true
} else {
false
};
let is_write = access_type.can_write();
let dirty_changed = if hd && is_write && !entry.is_dirty() {
*entry = entry.clone().with_dirty(true);
true
} else {
false
};
af_changed || dirty_changed
} else {
false
}
}
#[must_use]
pub fn get_page_access_flag(&self, iova: IOVA) -> Option<bool> {
let page_num = self.page_number(iova);
self.page_table.get(&page_num).map(|e| e.is_access_flag_set())
}
#[must_use]
pub fn get_page_dirty(&self, iova: IOVA) -> Option<bool> {
let page_num = self.page_number(iova);
self.page_table.get(&page_num).map(|e| e.is_dirty())
}
#[inline]
fn page_number(&self, iova: IOVA) -> u64 {
iova.as_u64() >> 12 }
#[inline]
fn check_permissions(&self, perms: PagePermissions, access_type: AccessType) -> bool {
perms.allows(access_type)
}
}
impl Default for AddressSpace {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_address_space() {
let addr_space = AddressSpace::new();
assert_eq!(addr_space.get_page_count().unwrap(), 0);
}
#[test]
fn test_map_single_page() {
let addr_space = AddressSpace::new();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
addr_space
.map_page(iova, pa, PagePermissions::read_write(), SecurityState::NonSecure)
.unwrap();
assert!(addr_space.is_page_mapped(iova).unwrap());
assert_eq!(addr_space.get_page_count().unwrap(), 1);
}
#[test]
fn test_translate_page() {
let addr_space = AddressSpace::new();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
addr_space
.map_page(iova, pa, PagePermissions::read_write(), SecurityState::NonSecure)
.unwrap();
let result = addr_space
.translate_page(iova, AccessType::Read, SecurityState::NonSecure)
.unwrap();
assert_eq!(result.physical_address().as_u64(), 0x2000);
}
#[test]
fn test_permission_violation() {
let addr_space = AddressSpace::new();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
addr_space
.map_page(iova, pa, PagePermissions::read_only(), SecurityState::NonSecure)
.unwrap();
let result = addr_space.translate_page(iova, AccessType::Write, SecurityState::NonSecure);
assert!(result.is_err());
}
#[test]
fn test_update_access_flags_ha_sets_af_on_read() {
let addr_space = AddressSpace::new();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
addr_space.map_page_unaccessed(iova, pa, PagePermissions::read_write(), SecurityState::NonSecure).unwrap();
assert_eq!(addr_space.get_page_access_flag(iova), Some(false));
let changed = addr_space.update_access_flags(iova, true, false, AccessType::Read);
assert!(changed);
assert_eq!(addr_space.get_page_access_flag(iova), Some(true));
}
#[test]
fn test_update_access_flags_hd_sets_dirty_on_write() {
let addr_space = AddressSpace::new();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
addr_space.map_page_unaccessed(iova, pa, PagePermissions::read_write(), SecurityState::NonSecure).unwrap();
assert_eq!(addr_space.get_page_dirty(iova), Some(false));
let changed = addr_space.update_access_flags(iova, false, true, AccessType::Write);
assert!(changed);
assert_eq!(addr_space.get_page_dirty(iova), Some(true));
}
#[test]
fn test_update_access_flags_hd_does_not_set_dirty_on_read() {
let addr_space = AddressSpace::new();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
addr_space.map_page_unaccessed(iova, pa, PagePermissions::read_write(), SecurityState::NonSecure).unwrap();
addr_space.update_access_flags(iova, false, true, AccessType::Read);
assert_eq!(addr_space.get_page_dirty(iova), Some(false));
}
#[test]
fn test_update_access_flags_no_change_when_already_set() {
let addr_space = AddressSpace::new();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
addr_space.map_page(iova, pa, PagePermissions::read_write(), SecurityState::NonSecure).unwrap();
let changed = addr_space.update_access_flags(iova, true, false, AccessType::Read);
assert!(!changed);
}
#[test]
fn test_update_access_flags_no_flags_enabled_returns_false() {
let addr_space = AddressSpace::new();
let iova = IOVA::new(0x1000).unwrap();
let pa = PA::new(0x2000).unwrap();
addr_space.map_page(iova, pa, PagePermissions::read_write(), SecurityState::NonSecure).unwrap();
let changed = addr_space.update_access_flags(iova, false, false, AccessType::Write);
assert!(!changed);
}
}