| // 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, PteUpdater, |
| 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<PhysicalAddress, MapError> { |
| 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<PageTable>, 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<PageTable>) { |
| deallocate(page_table); |
| } |
| |
| fn physical_to_virtual(&self, pa: PhysicalAddress) -> NonNull<PageTable> { |
| 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<usize> { |
| 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<LinearTranslation>, |
| } |
| |
| 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. This function writes block and page entries, but only maps them if `flags` |
| /// contains `Attributes::VALID`, otherwise the entries remain invalid. |
| /// |
| /// # Errors |
| /// |
| /// Returns [`MapError::InvalidVirtualAddress`] if adding the configured offset to any virtual |
| /// address within the `range` would result in overflow. |
| /// |
| /// Returns [`MapError::RegionBackwards`] if the range is backwards. |
| /// |
| /// 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) |
| } |
| |
| /// Applies the provided updater function to a number of PTEs corresponding to a given memory range. |
| /// |
| /// The virtual address range passed to the updater function may be expanded compared to the |
| /// `range` parameter, due to alignment to block boundaries. |
| /// |
| /// 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::PteUpdateFault`] if the updater function returns an error. |
| /// |
| /// Returns [`MapError::RegionBackwards`] if the range is backwards. |
| /// |
| /// 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 modify_range(&mut self, range: &MemoryRegion, f: &PteUpdater) -> Result<(), MapError> { |
| self.mapping.modify_range(range, f) |
| } |
| } |
| |
| #[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 | Attributes::VALID |
| ), |
| 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 | Attributes::VALID |
| ), |
| 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 | Attributes::VALID |
| ), |
| 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 | Attributes::VALID |
| ), |
| 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 | Attributes::VALID |
| ), |
| 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 | Attributes::VALID |
| ), |
| 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 | Attributes::VALID |
| ), |
| 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 | Attributes::VALID |
| ), |
| 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 | Attributes::VALID |
| ), |
| 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 | Attributes::VALID |
| ), |
| 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 | Attributes::VALID, |
| ) |
| .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 | Attributes::VALID, |
| ) |
| .unwrap(); |
| assert_eq!( |
| pagetable.mapping.root.mapping_level(VirtualAddress(0)), |
| Some(2) |
| ); |
| } |
| |
| fn make_map() -> LinearMap { |
| let mut lmap = LinearMap::new(1, 1, 4096, VaRange::Lower); |
| // Mapping VA range 0x0 - 0x2000 to PA range 0x1000 - 0x3000 |
| lmap.map_range(&MemoryRegion::new(0, PAGE_SIZE * 2), Attributes::NORMAL) |
| .unwrap(); |
| lmap |
| } |
| |
| #[test] |
| fn update_backwards_range() { |
| let mut lmap = make_map(); |
| assert!(lmap |
| .modify_range( |
| &MemoryRegion::new(PAGE_SIZE * 2, 1), |
| &|_range, entry, _level| { |
| entry |
| .modify_flags(Attributes::SWFLAG_0, Attributes::from_bits(0usize).unwrap()); |
| Ok(()) |
| }, |
| ) |
| .is_err()); |
| } |
| |
| #[test] |
| fn update_range() { |
| let mut lmap = make_map(); |
| lmap.modify_range(&MemoryRegion::new(1, PAGE_SIZE), &|_range, entry, level| { |
| if level == 3 || !entry.is_table_or_page() { |
| entry.modify_flags(Attributes::SWFLAG_0, Attributes::from_bits(0usize).unwrap()); |
| } |
| Ok(()) |
| }) |
| .unwrap(); |
| lmap.modify_range(&MemoryRegion::new(1, PAGE_SIZE), &|range, entry, level| { |
| if level == 3 || !entry.is_table_or_page() { |
| assert!(entry.flags().unwrap().contains(Attributes::SWFLAG_0)); |
| assert_eq!(range.end() - range.start(), PAGE_SIZE); |
| } |
| Ok(()) |
| }) |
| .unwrap(); |
| } |
| |
| #[test] |
| fn breakup_invalid_block() { |
| const BLOCK_RANGE: usize = 0x200000; |
| |
| let mut lmap = LinearMap::new(1, 1, 0x1000, VaRange::Lower); |
| lmap.map_range( |
| &MemoryRegion::new(0, BLOCK_RANGE), |
| Attributes::NORMAL | Attributes::NON_GLOBAL | Attributes::SWFLAG_0, |
| ) |
| .unwrap(); |
| lmap.map_range( |
| &MemoryRegion::new(0, PAGE_SIZE), |
| Attributes::NORMAL | Attributes::NON_GLOBAL | Attributes::VALID, |
| ) |
| .unwrap(); |
| lmap.modify_range( |
| &MemoryRegion::new(0, BLOCK_RANGE), |
| &|range, entry, level| { |
| if level == 3 { |
| let has_swflag = entry.flags().unwrap().contains(Attributes::SWFLAG_0); |
| let is_first_page = range.start().0 == 0usize; |
| assert!(has_swflag != is_first_page); |
| } |
| Ok(()) |
| }, |
| ) |
| .unwrap(); |
| } |
| } |