blob: 7dd7c096f921d2ad125dbeabf92b79b8677df78b [file] [log] [blame]
// 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<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.
///
/// # 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)
);
}
}