Add translation table library
Add AArch64 MMU handler component.
Signed-off-by: Imre Kis <imre.kis@arm.com>
Change-Id: Ief463cb783e1b8f825d8be37bb42988992879e68
diff --git a/src/region.rs b/src/region.rs
new file mode 100644
index 0000000..fb13db9
--- /dev/null
+++ b/src/region.rs
@@ -0,0 +1,632 @@
+// SPDX-FileCopyrightText: Copyright 2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
+// SPDX-License-Identifier: MIT OR Apache-2.0
+
+//! Module for handling physical and virtual memory regions
+//!
+//! A region is a continuous memory area in the given memory space.
+
+use alloc::vec::Vec;
+use log::debug;
+
+use super::{
+ page_pool::{PagePool, Pages},
+ region_pool::Region,
+};
+
+/// Physical region
+///
+/// A physical memory region can be in three different state
+/// * Unused
+/// * Points to a page pool allocated address
+/// * Points to a physical address without allocation
+pub enum PhysicalRegion {
+ Unused,
+ Allocated(PagePool, Pages),
+ PhysicalAddress(usize),
+}
+
+impl PhysicalRegion {
+ /// Get physical memory address
+ fn get_pa(&self) -> usize {
+ match self {
+ PhysicalRegion::Unused => panic!("Unused area has no PA"),
+ PhysicalRegion::Allocated(_page_pool, pages) => pages.get_pa(),
+ PhysicalRegion::PhysicalAddress(pa) => *pa,
+ }
+ }
+}
+
+/// Virtual region
+///
+/// A virtual memory region has a virtual address, a length and a physical region.
+pub struct VirtualRegion {
+ va: usize,
+ length: usize,
+ physical_region: PhysicalRegion,
+}
+
+impl VirtualRegion {
+ /// Create new virtual memory region without a physical region
+ pub fn new(va: usize, length: usize) -> Self {
+ Self::new_from_fields(va, length, PhysicalRegion::Unused)
+ }
+
+ /// Create virtual region with points to a given physical address
+ pub fn new_with_pa(pa: usize, va: usize, length: usize) -> Self {
+ Self::new_from_fields(va, length, PhysicalRegion::PhysicalAddress(pa))
+ }
+
+ /// Create virtual region by defining all the fields of the object
+ fn new_from_fields(va: usize, length: usize, physical_region: PhysicalRegion) -> Self {
+ Self {
+ va,
+ length,
+ physical_region,
+ }
+ }
+
+ /// Get the base address of the linked physical region
+ pub fn get_pa(&self) -> usize {
+ self.physical_region.get_pa()
+ }
+
+ /// Get physical address for a virtual address
+ pub fn get_pa_for_va(&self, va: usize) -> usize {
+ let offset = va.checked_sub(self.va).unwrap();
+
+ assert!(offset < self.length);
+ self.get_pa().checked_add(offset).unwrap()
+ }
+}
+
+impl Region for VirtualRegion {
+ type Resource = PhysicalRegion;
+
+ fn base(&self) -> usize {
+ self.va
+ }
+
+ fn length(&self) -> usize {
+ self.length
+ }
+
+ fn used(&self) -> bool {
+ !matches!(self.physical_region, PhysicalRegion::Unused)
+ }
+
+ fn contains(&self, base: usize, length: usize) -> bool {
+ if let (Some(end), Some(self_end)) =
+ (base.checked_add(length), self.va.checked_add(self.length))
+ {
+ self.va <= base && end <= self_end
+ } else {
+ false
+ }
+ }
+
+ fn try_append(&mut self, other: &Self) -> bool {
+ if let (Some(self_end), Some(new_length)) = (
+ self.va.checked_add(self.length),
+ self.length.checked_add(other.length),
+ ) {
+ if self_end == other.va {
+ // VA is consecutive
+ match (&self.physical_region, &other.physical_region) {
+ (PhysicalRegion::Unused, PhysicalRegion::Unused) => {
+ // Unused range can be merged without further conditions
+ self.length = new_length;
+ return true;
+ }
+ (
+ PhysicalRegion::PhysicalAddress(self_pa),
+ PhysicalRegion::PhysicalAddress(other_pa),
+ ) => {
+ // Used ranges can be only merged if the PA doesn't overflow and it is
+ // consecutive
+ if let Some(self_end_pa) = self_pa.checked_add(self.length) {
+ if self_end_pa == *other_pa {
+ self.length = new_length;
+ return true;
+ }
+ }
+ }
+
+ // PhyisicalRegion::Allocated instances cannot be merged at the moment. Not sure
+ // if it's a valid use case. If needed the pages has to be merged which might
+ // require tricks to invalidate the pages of 'other'.
+ _ => {}
+ }
+ }
+ }
+
+ false
+ }
+
+ fn create_split(
+ &self,
+ base: usize,
+ length: usize,
+ resource: Option<Self::Resource>,
+ ) -> (Self, Vec<Self>) {
+ assert!(self.contains(base, length));
+ assert!(self.used() != resource.is_some());
+
+ if let Some(physical_region) = resource {
+ // Self is unused, setting part of it to used
+ let pa = physical_region.get_pa();
+
+ let mut res = Vec::new();
+ if self.va != base {
+ res.push(Self::new(self.va, base.checked_sub(self.va).unwrap()));
+ }
+
+ res.push(Self::new_from_fields(base, length, physical_region));
+
+ let end = base.checked_add(length).unwrap();
+ let self_end = self.va.checked_add(self.length).unwrap();
+ if end != self_end {
+ res.push(Self::new(end, self_end.checked_sub(end).unwrap()));
+ }
+
+ (
+ Self::new_from_fields(base, length, PhysicalRegion::PhysicalAddress(pa)),
+ res,
+ )
+ } else {
+ // Self is used, mark part of it unused
+ let mut res = Vec::new();
+ if self.va != base {
+ let physical_region = match &self.physical_region {
+ PhysicalRegion::Allocated(_page_pool, _pages) => {
+ todo!("Implement Pages::split");
+ }
+ PhysicalRegion::PhysicalAddress(pa) => PhysicalRegion::PhysicalAddress(*pa),
+ _ => {
+ panic!("Splitting unused region by other unused")
+ }
+ };
+
+ res.push(Self::new_from_fields(
+ self.va,
+ base.checked_sub(self.va).unwrap(),
+ physical_region,
+ ));
+ }
+
+ res.push(Self::new(base, length));
+
+ let end = base.checked_add(length).unwrap();
+ let self_end = self.va.checked_add(self.length).unwrap();
+ if end != self_end {
+ let physical_region = match &self.physical_region {
+ PhysicalRegion::Allocated(_page_pool, _pages) => {
+ todo!("Implement Pages::split");
+ }
+ PhysicalRegion::PhysicalAddress(pa) => {
+ let offset = end.checked_sub(self.va).unwrap();
+ PhysicalRegion::PhysicalAddress(pa.checked_add(offset).unwrap())
+ }
+ _ => {
+ panic!("Splitting unused region by other unused")
+ }
+ };
+
+ res.push(Self::new_from_fields(
+ end,
+ self_end.checked_sub(end).unwrap(),
+ physical_region,
+ ));
+ }
+
+ (Self::new(base, length), res)
+ }
+ }
+}
+
+impl Drop for VirtualRegion {
+ /// If the virtual region has a linked physical region which was allocated then release the
+ /// allocated pages.
+ fn drop(&mut self) {
+ let mut physical_region = PhysicalRegion::Unused;
+
+ core::mem::swap(&mut self.physical_region, &mut physical_region);
+
+ if let PhysicalRegion::Allocated(page_pool, pages) = physical_region {
+ debug!(
+ "Dropping physical region with pages: PA={:#010x} VA={:#010x}",
+ pages.get_pa(),
+ self.base(),
+ );
+
+ page_pool.release_pages(pages).unwrap();
+ }
+ }
+}
+
+#[cfg(test)]
+use super::page_pool::PagePoolArea;
+
+#[test]
+#[should_panic]
+fn test_physical_region_unused() {
+ let region = PhysicalRegion::Unused;
+ region.get_pa();
+}
+
+#[test]
+fn test_physical_region() {
+ const PA: usize = 0x0123_4567_89ab_cdef;
+ const LENGTH: usize = 0x8000_0000_0000;
+
+ static PAGE_POOL_AREA: PagePoolArea<16> = PagePoolArea::new();
+ let region =
+ PhysicalRegion::Allocated(PagePool::new(&PAGE_POOL_AREA), Pages::new(PA, LENGTH, true));
+ assert_eq!(PA, region.get_pa());
+
+ let region = PhysicalRegion::PhysicalAddress(PA);
+ assert_eq!(PA, region.get_pa());
+}
+
+#[test]
+fn test_virtual_region() {
+ const VA: usize = 0x0123_4567_89ab_cdef;
+ const PA: usize = 0xfedc_ba98_7654_3210;
+ const LENGTH: usize = 0x8000_0000_0000;
+
+ let region = VirtualRegion::new(VA, LENGTH);
+ assert_eq!(VA, region.va);
+ assert_eq!(VA, region.base());
+ assert_eq!(LENGTH, region.length);
+ assert_eq!(LENGTH, region.length());
+ assert!(matches!(region.physical_region, PhysicalRegion::Unused));
+ assert!(!region.used());
+
+ let region = VirtualRegion::new_with_pa(PA, VA, LENGTH);
+ assert_eq!(VA, region.va);
+ assert_eq!(VA, region.base());
+ assert_eq!(LENGTH, region.length);
+ assert_eq!(LENGTH, region.length());
+ assert!(matches!(
+ region.physical_region,
+ PhysicalRegion::PhysicalAddress(_)
+ ));
+ assert_eq!(PA, region.get_pa());
+ assert!(region.used());
+}
+
+#[test]
+fn test_virtual_region_get_pa_for_va() {
+ let region = VirtualRegion::new_with_pa(0x8000_0000_0000_0000, 0x4000_0000_0000_0000, 0x1000);
+ assert_eq!(
+ 0x8000_0000_0000_0000,
+ region.get_pa_for_va(0x4000_0000_0000_0000)
+ );
+ assert_eq!(
+ 0x8000_0000_0000_0001,
+ region.get_pa_for_va(0x4000_0000_0000_0001)
+ );
+ assert_eq!(
+ 0x8000_0000_0000_0fff,
+ region.get_pa_for_va(0x4000_0000_0000_0fff)
+ );
+}
+
+#[test]
+#[should_panic]
+fn test_virtual_region_get_pa_for_va_low_va() {
+ let region = VirtualRegion::new_with_pa(0x8000_0000_0000_0000, 0x4000_0000_0000_0000, 0x1000);
+ region.get_pa_for_va(0x3fff_ffff_ffff_ffff);
+}
+
+#[test]
+#[should_panic]
+fn test_virtual_region_get_pa_for_va_high_va() {
+ let region = VirtualRegion::new_with_pa(0x8000_0000_0000_0000, 0x4000_0000_0000_0000, 0x1000);
+ region.get_pa_for_va(0x4000_0000_0000_1000);
+}
+
+#[test]
+fn test_virtual_region_contains() {
+ const VA: usize = 0x8000_0000_0000_0000;
+ const LENGTH: usize = 0x8000_0000_0000;
+
+ let region_overflow_end = VirtualRegion::new(0x8000_0000_0000_0000, 0x8000_0000_0000_0000);
+ assert!(!region_overflow_end.contains(0x8000_0000_0000_0000, 1));
+
+ let region = VirtualRegion::new(0x4000_0000_0000_0000, 0x8000_0000_0000_0000);
+ assert!(!region.contains(0x8000_0000_0000_0000, 0x8000_0000_0000_0000));
+
+ assert!(!region.contains(0x4000_0000_0000_0000, 0x8000_0000_0000_0001));
+ assert!(!region.contains(0x3fff_ffff_ffff_ffff, 0x8000_0000_0000_0000));
+ assert!(region.contains(0x4000_0000_0000_0000, 0x8000_0000_0000_0000));
+ assert!(region.contains(0x4000_0000_0000_0000, 0x7fff_ffff_ffff_ffff));
+ assert!(region.contains(0x4000_0000_0000_0001, 0x7fff_ffff_ffff_ffff));
+}
+
+#[test]
+fn test_virtual_region_try_append() {
+ // Both unused
+ let mut region_unused0 = VirtualRegion::new(0x4000_0000, 0x1000);
+ let mut region_unused1 = VirtualRegion::new(0x4000_1000, 0x1000);
+
+ assert!(!region_unused1.try_append(®ion_unused0));
+ assert_eq!(0x4000_0000, region_unused0.va);
+ assert_eq!(0x1000, region_unused0.length);
+ assert_eq!(0x4000_1000, region_unused1.va);
+ assert_eq!(0x1000, region_unused1.length);
+
+ assert!(region_unused0.try_append(®ion_unused1));
+ assert_eq!(0x4000_0000, region_unused0.va);
+ assert_eq!(0x2000, region_unused0.length);
+ assert_eq!(0x4000_1000, region_unused1.va);
+ assert_eq!(0x1000, region_unused1.length);
+
+ // Unused and PA region
+ let mut region_unused = VirtualRegion::new(0x4000_0000, 0x1000);
+ let region_physical = VirtualRegion::new_with_pa(0x8000_0000, 0x4000_1000, 0x1000);
+ assert!(!region_unused.try_append(®ion_physical));
+ assert_eq!(0x4000_0000, region_unused.va);
+ assert_eq!(0x1000, region_unused.length);
+ assert_eq!(0x4000_1000, region_physical.va);
+ assert_eq!(0x1000, region_physical.length);
+
+ // Both PA regions but non-consecutive PA ranges
+ let mut region_physical0 = VirtualRegion::new_with_pa(0x8000_0000, 0x4000_0000, 0x1000);
+ let region_physical1 = VirtualRegion::new_with_pa(0x9000_0000, 0x4000_1000, 0x1000);
+ assert!(!region_physical0.try_append(®ion_physical1));
+ assert_eq!(0x4000_0000, region_physical0.va);
+ assert_eq!(0x1000, region_physical0.length);
+ assert_eq!(0x4000_1000, region_physical1.va);
+ assert_eq!(0x1000, region_physical1.length);
+
+ // Both PA regions with consecutive PA ranges
+ let mut region_physical0 = VirtualRegion::new_with_pa(0x8000_0000, 0x4000_0000, 0x1000);
+ let region_physical1 = VirtualRegion::new_with_pa(0x8000_1000, 0x4000_1000, 0x1000);
+ assert!(region_physical0.try_append(®ion_physical1));
+ assert_eq!(0x4000_0000, region_physical0.va);
+ assert_eq!(0x2000, region_physical0.length);
+ assert_eq!(0x4000_1000, region_physical1.va);
+ assert_eq!(0x1000, region_physical1.length);
+
+ // VA overflow
+ let mut region_unused0 = VirtualRegion::new(0x8000_0000_0000_0000, 0x8000_0000_0000_0000);
+ let mut region_unused1 = VirtualRegion::new(0x4000_1000, 0x1000);
+
+ assert!(!region_unused0.try_append(®ion_unused1));
+ assert_eq!(0x8000_0000_0000_0000, region_unused0.va);
+ assert_eq!(0x8000_0000_0000_0000, region_unused0.length);
+ assert_eq!(0x4000_1000, region_unused1.va);
+ assert_eq!(0x1000, region_unused1.length);
+
+ assert!(!region_unused1.try_append(®ion_unused0));
+ assert_eq!(0x8000_0000_0000_0000, region_unused0.va);
+ assert_eq!(0x8000_0000_0000_0000, region_unused0.length);
+ assert_eq!(0x4000_1000, region_unused1.va);
+ assert_eq!(0x1000, region_unused1.length);
+
+ // PA overflow
+ let mut region_physical0 =
+ VirtualRegion::new_with_pa(0x8000_0000_0000_0000, 0x4000_0000, 0x8000_0000_0000_0000);
+ let region_physical1 = VirtualRegion::new_with_pa(0x9000_0000, 0x8000_0000_4000_0000, 0x1000);
+ assert!(!region_physical0.try_append(®ion_physical1));
+ assert_eq!(0x4000_0000, region_physical0.va);
+ assert_eq!(0x8000_0000_0000_0000, region_physical0.length);
+ assert_eq!(0x8000_0000_4000_0000, region_physical1.va);
+ assert_eq!(0x1000, region_physical1.length);
+}
+
+#[test]
+fn test_virtual_region_create_split_by_used() {
+ let region_unused = VirtualRegion::new(0x4000_0000, 0x4000);
+
+ // New region at the start
+ let (new_region, splitted_regions) = region_unused.create_split(
+ 0x4000_0000,
+ 0x1000,
+ Some(PhysicalRegion::PhysicalAddress(0x8000_0000)),
+ );
+
+ assert_eq!(0x4000_0000, new_region.va);
+ assert_eq!(0x1000, new_region.length);
+ assert_eq!(0x8000_0000, new_region.get_pa());
+ assert!(matches!(
+ new_region.physical_region,
+ PhysicalRegion::PhysicalAddress(_)
+ ));
+
+ assert_eq!(0x4000_0000, splitted_regions[0].va);
+ assert_eq!(0x1000, splitted_regions[0].length);
+ assert_eq!(0x8000_0000, splitted_regions[0].get_pa());
+ assert!(matches!(
+ splitted_regions[0].physical_region,
+ PhysicalRegion::PhysicalAddress(_)
+ ));
+
+ assert_eq!(0x4000_1000, splitted_regions[1].va);
+ assert_eq!(0x3000, splitted_regions[1].length);
+ assert!(matches!(
+ splitted_regions[1].physical_region,
+ PhysicalRegion::Unused
+ ));
+
+ // New region in the middle
+ let (new_region, splitted_regions) = region_unused.create_split(
+ 0x4000_1000,
+ 0x1000,
+ Some(PhysicalRegion::PhysicalAddress(0x8000_0000)),
+ );
+
+ assert_eq!(0x4000_1000, new_region.va);
+ assert_eq!(0x1000, new_region.length);
+ assert_eq!(0x8000_0000, new_region.get_pa());
+ assert!(matches!(
+ new_region.physical_region,
+ PhysicalRegion::PhysicalAddress(_)
+ ));
+
+ assert_eq!(0x4000_0000, splitted_regions[0].va);
+ assert_eq!(0x1000, splitted_regions[0].length);
+ assert!(matches!(
+ splitted_regions[0].physical_region,
+ PhysicalRegion::Unused
+ ));
+
+ assert_eq!(0x4000_1000, splitted_regions[1].va);
+ assert_eq!(0x1000, splitted_regions[1].length);
+ assert_eq!(0x8000_0000, splitted_regions[1].get_pa());
+ assert!(matches!(
+ splitted_regions[1].physical_region,
+ PhysicalRegion::PhysicalAddress(_)
+ ));
+
+ assert_eq!(0x4000_2000, splitted_regions[2].va);
+ assert_eq!(0x2000, splitted_regions[2].length);
+ assert!(matches!(
+ splitted_regions[2].physical_region,
+ PhysicalRegion::Unused
+ ));
+
+ // New region at the end
+ let (new_region, splitted_regions) = region_unused.create_split(
+ 0x4000_3000,
+ 0x1000,
+ Some(PhysicalRegion::PhysicalAddress(0x8000_0000)),
+ );
+
+ assert_eq!(0x4000_3000, new_region.va);
+ assert_eq!(0x1000, new_region.length);
+ assert_eq!(0x8000_0000, new_region.get_pa());
+ assert!(matches!(
+ new_region.physical_region,
+ PhysicalRegion::PhysicalAddress(_)
+ ));
+
+ assert_eq!(0x4000_0000, splitted_regions[0].va);
+ assert_eq!(0x3000, splitted_regions[0].length);
+ assert!(matches!(
+ splitted_regions[0].physical_region,
+ PhysicalRegion::Unused
+ ));
+
+ assert_eq!(0x4000_3000, splitted_regions[1].va);
+ assert_eq!(0x1000, splitted_regions[1].length);
+ assert_eq!(0x8000_0000, splitted_regions[1].get_pa());
+ assert!(matches!(
+ splitted_regions[1].physical_region,
+ PhysicalRegion::PhysicalAddress(_)
+ ));
+}
+
+#[test]
+fn test_virtual_region_create_split_by_unused() {
+ let region_unused = VirtualRegion::new_with_pa(0x8000_0000, 0x4000_0000, 0x4000);
+
+ // New region at the start
+ let (new_region, splitted_regions) = region_unused.create_split(0x4000_0000, 0x1000, None);
+
+ assert_eq!(0x4000_0000, new_region.va);
+ assert_eq!(0x1000, new_region.length);
+ assert!(matches!(new_region.physical_region, PhysicalRegion::Unused));
+
+ assert_eq!(0x4000_0000, splitted_regions[0].va);
+ assert_eq!(0x1000, splitted_regions[0].length);
+ assert!(matches!(
+ splitted_regions[0].physical_region,
+ PhysicalRegion::Unused
+ ));
+
+ assert_eq!(0x4000_1000, splitted_regions[1].va);
+ assert_eq!(0x3000, splitted_regions[1].length);
+ assert_eq!(0x8000_1000, splitted_regions[1].get_pa());
+ assert!(matches!(
+ splitted_regions[1].physical_region,
+ PhysicalRegion::PhysicalAddress(_)
+ ));
+
+ // New region in the middle
+ let (new_region, splitted_regions) = region_unused.create_split(0x4000_1000, 0x1000, None);
+
+ assert_eq!(0x4000_1000, new_region.va);
+ assert_eq!(0x1000, new_region.length);
+ assert!(matches!(new_region.physical_region, PhysicalRegion::Unused));
+
+ assert_eq!(0x4000_0000, splitted_regions[0].va);
+ assert_eq!(0x1000, splitted_regions[0].length);
+ assert_eq!(0x8000_0000, splitted_regions[0].get_pa());
+ assert!(matches!(
+ splitted_regions[0].physical_region,
+ PhysicalRegion::PhysicalAddress(_)
+ ));
+
+ assert_eq!(0x4000_1000, splitted_regions[1].va);
+ assert_eq!(0x1000, splitted_regions[1].length);
+ assert!(matches!(
+ splitted_regions[1].physical_region,
+ PhysicalRegion::Unused
+ ));
+
+ assert_eq!(0x4000_2000, splitted_regions[2].va);
+ assert_eq!(0x2000, splitted_regions[2].length);
+ assert_eq!(0x8000_2000, splitted_regions[2].get_pa());
+ assert!(matches!(
+ splitted_regions[2].physical_region,
+ PhysicalRegion::PhysicalAddress(_)
+ ));
+
+ // New region at the end
+ let (new_region, splitted_regions) = region_unused.create_split(0x4000_3000, 0x1000, None);
+
+ assert_eq!(0x4000_3000, new_region.va);
+ assert_eq!(0x1000, new_region.length);
+ assert!(matches!(new_region.physical_region, PhysicalRegion::Unused));
+
+ assert_eq!(0x4000_0000, splitted_regions[0].va);
+ assert_eq!(0x3000, splitted_regions[0].length);
+ assert_eq!(0x8000_0000, splitted_regions[0].get_pa());
+ assert!(matches!(
+ splitted_regions[0].physical_region,
+ PhysicalRegion::PhysicalAddress(_)
+ ));
+
+ assert_eq!(0x4000_3000, splitted_regions[1].va);
+ assert_eq!(0x1000, splitted_regions[1].length);
+
+ assert!(matches!(
+ splitted_regions[1].physical_region,
+ PhysicalRegion::Unused
+ ));
+}
+
+#[test]
+#[should_panic]
+fn test_virtual_region_does_not_contain() {
+ let region = VirtualRegion::new(0x4000_0000, 0x1000);
+ region.create_split(
+ 0x8000_0000,
+ 0x1000,
+ Some(PhysicalRegion::PhysicalAddress(0xc000_0000)),
+ );
+}
+
+#[test]
+#[should_panic]
+fn test_virtual_region_create_split_same_used() {
+ let region = VirtualRegion::new(0x4000_0000, 0x1000);
+ region.create_split(0x4000_0000, 0x1000, Some(PhysicalRegion::Unused));
+}
+
+#[test]
+fn test_virtual_region_drop() {
+ const PA: usize = 0x0123_4567_89ab_cdef;
+ const LENGTH: usize = 0x8000_0000_0000;
+
+ static PAGE_POOL_AREA: PagePoolArea<8192> = PagePoolArea::new();
+ let page_pool = PagePool::new(&PAGE_POOL_AREA);
+ let page = page_pool.allocate_pages(4096).unwrap();
+
+ let physical_region = PhysicalRegion::Allocated(page_pool, page);
+
+ // Testing physical region drop through virtualregion
+ let virtual_region = VirtualRegion::new_from_fields(0x4000_0000, 1000, physical_region);
+ drop(virtual_region);
+}