// Copyright 2022 The aarch64-paging Authors. // This project is dual-licensed under Apache 2.0 and MIT terms. // See LICENSE-APACHE and LICENSE-MIT for details. //! Functionality for managing page tables with linear mapping. //! //! See [`LinearMap`] for details on how to use it. use crate::{ paging::{ deallocate, is_aligned, Attributes, MemoryRegion, PageTable, PhysicalAddress, Translation, VaRange, VirtualAddress, PAGE_SIZE, }, MapError, Mapping, }; use core::ptr::NonNull; /// Linear mapping, where every virtual address is either unmapped or mapped to an IPA with a fixed /// offset. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct LinearTranslation { /// The offset from a virtual address to the corresponding (intermediate) physical address. offset: isize, } impl LinearTranslation { /// Constructs a new linear translation, which will map a virtual address `va` to the /// (intermediate) physical address `va + offset`. /// /// The `offset` must be a multiple of [`PAGE_SIZE`]; if not this will panic. pub fn new(offset: isize) -> Self { if !is_aligned(offset.unsigned_abs(), PAGE_SIZE) { panic!( "Invalid offset {}, must be a multiple of page size {}.", offset, PAGE_SIZE, ); } Self { offset } } fn virtual_to_physical(&self, va: VirtualAddress) -> Result { if let Some(pa) = checked_add_to_unsigned(va.0 as isize, self.offset) { Ok(PhysicalAddress(pa)) } else { Err(MapError::InvalidVirtualAddress(va)) } } } impl Translation for LinearTranslation { fn allocate_table(&self) -> (NonNull, PhysicalAddress) { let table = PageTable::new(); // Assume that the same linear mapping is used everywhere. let va = VirtualAddress(table.as_ptr() as usize); let pa = self.virtual_to_physical(va).expect( "Allocated subtable with virtual address which doesn't correspond to any physical address." ); (table, pa) } unsafe fn deallocate_table(&self, page_table: NonNull) { deallocate(page_table); } fn physical_to_virtual(&self, pa: PhysicalAddress) -> NonNull { let signed_pa = pa.0 as isize; if signed_pa < 0 { panic!("Invalid physical address {} for pagetable", pa); } if let Some(va) = signed_pa.checked_sub(self.offset) { if let Some(ptr) = NonNull::new(va as *mut PageTable) { ptr } else { panic!( "Invalid physical address {} for pagetable (translated to virtual address 0)", pa ) } } else { panic!("Invalid physical address {} for pagetable", pa); } } } /// Adds two signed values, returning an unsigned value or `None` if it would overflow. fn checked_add_to_unsigned(a: isize, b: isize) -> Option { a.checked_add(b)?.try_into().ok() } /// Manages a level 1 page table using linear mapping, where every virtual address is either /// unmapped or mapped to an IPA with a fixed offset. /// /// This assumes that the same linear mapping is used both for the page table being managed, and for /// code that is managing it. #[derive(Debug)] pub struct LinearMap { mapping: Mapping, } impl LinearMap { /// Creates a new identity-mapping page table with the given ASID, root level and offset, for /// use in the given TTBR. /// /// This will map any virtual address `va` which is added to the table to the physical address /// `va + offset`. /// /// The `offset` must be a multiple of [`PAGE_SIZE`]; if not this will panic. pub fn new(asid: usize, rootlevel: usize, offset: isize, va_range: VaRange) -> Self { Self { mapping: Mapping::new(LinearTranslation::new(offset), asid, rootlevel, va_range), } } /// Activates the page table by setting `TTBR0_EL1` to point to it, and saves the previous value /// of `TTBR0_EL1` so that it may later be restored by [`deactivate`](Self::deactivate). /// /// Panics if a previous value of `TTBR0_EL1` is already saved and not yet used by a call to /// `deactivate`. #[cfg(target_arch = "aarch64")] pub fn activate(&mut self) { self.mapping.activate() } /// Deactivates the page table, by setting `TTBR0_EL1` back to the value it had before /// [`activate`](Self::activate) was called, and invalidating the TLB for this page table's /// configured ASID. /// /// Panics if there is no saved `TTRB0_EL1` value because `activate` has not previously been /// called. #[cfg(target_arch = "aarch64")] pub fn deactivate(&mut self) { self.mapping.deactivate() } /// Maps the given range of virtual addresses to the corresponding physical addresses with the /// given flags. /// /// This should generally only be called while the page table is not active. In particular, any /// change that may require break-before-make per the architecture must be made while the page /// table is inactive. Mapping a previously unmapped memory range may be done while the page /// table is active. /// /// # Errors /// /// Returns [`MapError::InvalidVirtualAddress`] if adding the configured offset to any virtual /// address within the `range` would result in overflow. /// /// Returns [`MapError::AddressRange`] if the largest address in the `range` is greater than the /// largest virtual address covered by the page table given its root level. pub fn map_range(&mut self, range: &MemoryRegion, flags: Attributes) -> Result<(), MapError> { let pa = self .mapping .root .translation() .virtual_to_physical(range.start())?; self.mapping.map_range(range, pa, flags) } } #[cfg(test)] mod tests { use super::*; use crate::{ paging::{Attributes, MemoryRegion, BITS_PER_LEVEL, PAGE_SIZE}, MapError, }; const MAX_ADDRESS_FOR_ROOT_LEVEL_1: usize = 1 << 39; const GIB_512_S: isize = 512 * 1024 * 1024 * 1024; const GIB_512: usize = 512 * 1024 * 1024 * 1024; #[test] fn map_valid() { // A single byte at the start of the address space. let mut pagetable = LinearMap::new(1, 1, 4096, VaRange::Lower); assert_eq!( pagetable.map_range(&MemoryRegion::new(0, 1), Attributes::NORMAL), Ok(()) ); // Two pages at the start of the address space. let mut pagetable = LinearMap::new(1, 1, 4096, VaRange::Lower); assert_eq!( pagetable.map_range(&MemoryRegion::new(0, PAGE_SIZE * 2), Attributes::NORMAL), Ok(()) ); // A single byte at the end of the address space. let mut pagetable = LinearMap::new(1, 1, 4096, VaRange::Lower); assert_eq!( pagetable.map_range( &MemoryRegion::new( MAX_ADDRESS_FOR_ROOT_LEVEL_1 - 1, MAX_ADDRESS_FOR_ROOT_LEVEL_1 ), Attributes::NORMAL ), Ok(()) ); // The entire valid address space. Use an offset that is a multiple of the level 2 block // size to avoid mapping everything as pages as that is really slow. const LEVEL_2_BLOCK_SIZE: usize = PAGE_SIZE << BITS_PER_LEVEL; let mut pagetable = LinearMap::new(1, 1, LEVEL_2_BLOCK_SIZE as isize, VaRange::Lower); assert_eq!( pagetable.map_range( &MemoryRegion::new(0, MAX_ADDRESS_FOR_ROOT_LEVEL_1), Attributes::NORMAL ), Ok(()) ); } #[test] fn map_valid_negative_offset() { // A single byte which maps to IPA 0. let mut pagetable = LinearMap::new(1, 1, -(PAGE_SIZE as isize), VaRange::Lower); assert_eq!( pagetable.map_range( &MemoryRegion::new(PAGE_SIZE, PAGE_SIZE + 1), Attributes::NORMAL ), Ok(()) ); // Two pages at the start of the address space. let mut pagetable = LinearMap::new(1, 1, -(PAGE_SIZE as isize), VaRange::Lower); assert_eq!( pagetable.map_range( &MemoryRegion::new(PAGE_SIZE, PAGE_SIZE * 3), Attributes::NORMAL ), Ok(()) ); // A single byte at the end of the address space. let mut pagetable = LinearMap::new(1, 1, -(PAGE_SIZE as isize), VaRange::Lower); assert_eq!( pagetable.map_range( &MemoryRegion::new( MAX_ADDRESS_FOR_ROOT_LEVEL_1 - 1, MAX_ADDRESS_FOR_ROOT_LEVEL_1 ), Attributes::NORMAL ), Ok(()) ); // The entire valid address space. Use an offset that is a multiple of the level 2 block // size to avoid mapping everything as pages as that is really slow. const LEVEL_2_BLOCK_SIZE: usize = PAGE_SIZE << BITS_PER_LEVEL; let mut pagetable = LinearMap::new(1, 1, -(LEVEL_2_BLOCK_SIZE as isize), VaRange::Lower); assert_eq!( pagetable.map_range( &MemoryRegion::new(LEVEL_2_BLOCK_SIZE, MAX_ADDRESS_FOR_ROOT_LEVEL_1), Attributes::NORMAL ), Ok(()) ); } #[test] fn map_out_of_range() { let mut pagetable = LinearMap::new(1, 1, 4096, VaRange::Lower); // One byte, just past the edge of the valid range. assert_eq!( pagetable.map_range( &MemoryRegion::new( MAX_ADDRESS_FOR_ROOT_LEVEL_1, MAX_ADDRESS_FOR_ROOT_LEVEL_1 + 1, ), Attributes::NORMAL ), Err(MapError::AddressRange(VirtualAddress( MAX_ADDRESS_FOR_ROOT_LEVEL_1 + PAGE_SIZE ))) ); // From 0 to just past the valid range. assert_eq!( pagetable.map_range( &MemoryRegion::new(0, MAX_ADDRESS_FOR_ROOT_LEVEL_1 + 1), Attributes::NORMAL ), Err(MapError::AddressRange(VirtualAddress( MAX_ADDRESS_FOR_ROOT_LEVEL_1 + PAGE_SIZE ))) ); } #[test] fn map_invalid_offset() { let mut pagetable = LinearMap::new(1, 1, -4096, VaRange::Lower); // One byte, with an offset which would map it to a negative IPA. assert_eq!( pagetable.map_range(&MemoryRegion::new(0, 1), Attributes::NORMAL), Err(MapError::InvalidVirtualAddress(VirtualAddress(0))) ); } #[test] fn physical_address_in_range_ttbr0() { let translation = LinearTranslation::new(4096); assert_eq!( translation.physical_to_virtual(PhysicalAddress(8192)), NonNull::new(4096 as *mut PageTable).unwrap(), ); assert_eq!( translation.physical_to_virtual(PhysicalAddress(GIB_512 + 4096)), NonNull::new(GIB_512 as *mut PageTable).unwrap(), ); } #[test] #[should_panic] fn physical_address_to_zero_ttbr0() { let translation = LinearTranslation::new(4096); translation.physical_to_virtual(PhysicalAddress(4096)); } #[test] #[should_panic] fn physical_address_out_of_range_ttbr0() { let translation = LinearTranslation::new(4096); translation.physical_to_virtual(PhysicalAddress(-4096_isize as usize)); } #[test] fn physical_address_in_range_ttbr1() { // Map the 512 GiB region at the top of virtual address space to one page above the bottom // of physical address space. let translation = LinearTranslation::new(GIB_512_S + 4096); assert_eq!( translation.physical_to_virtual(PhysicalAddress(8192)), NonNull::new((4096 - GIB_512_S) as *mut PageTable).unwrap(), ); assert_eq!( translation.physical_to_virtual(PhysicalAddress(GIB_512)), NonNull::new(-4096_isize as *mut PageTable).unwrap(), ); } #[test] #[should_panic] fn physical_address_to_zero_ttbr1() { // Map the 512 GiB region at the top of virtual address space to the bottom of physical // address space. let translation = LinearTranslation::new(GIB_512_S); translation.physical_to_virtual(PhysicalAddress(GIB_512)); } #[test] #[should_panic] fn physical_address_out_of_range_ttbr1() { // Map the 512 GiB region at the top of virtual address space to the bottom of physical // address space. let translation = LinearTranslation::new(GIB_512_S); translation.physical_to_virtual(PhysicalAddress(-4096_isize as usize)); } #[test] fn virtual_address_out_of_range() { let translation = LinearTranslation::new(-4096); let va = VirtualAddress(1024); assert_eq!( translation.virtual_to_physical(va), Err(MapError::InvalidVirtualAddress(va)) ) } #[test] fn virtual_address_range_ttbr1() { // Map the 512 GiB region at the top of virtual address space to the bottom of physical // address space. let translation = LinearTranslation::new(GIB_512_S); // The first page in the region covered by TTBR1. assert_eq!( translation.virtual_to_physical(VirtualAddress(0xffff_ff80_0000_0000)), Ok(PhysicalAddress(0)) ); // The last page in the region covered by TTBR1. assert_eq!( translation.virtual_to_physical(VirtualAddress(0xffff_ffff_ffff_f000)), Ok(PhysicalAddress(0x7f_ffff_f000)) ); } #[test] fn block_mapping() { // Test that block mapping is used when the PA is appropriately aligned... let mut pagetable = LinearMap::new(1, 1, 1 << 30, VaRange::Lower); pagetable .map_range(&MemoryRegion::new(0, 1 << 30), Attributes::NORMAL) .unwrap(); assert_eq!( pagetable.mapping.root.mapping_level(VirtualAddress(0)), Some(1) ); // ...but not when it is not. let mut pagetable = LinearMap::new(1, 1, 1 << 29, VaRange::Lower); pagetable .map_range(&MemoryRegion::new(0, 1 << 30), Attributes::NORMAL) .unwrap(); assert_eq!( pagetable.mapping.root.mapping_level(VirtualAddress(0)), Some(2) ); } }