Skip to content

Commit

Permalink
Merge pull request #54 from google/static
Browse files Browse the repository at this point in the history
Add Translation for pregenerating a static pagetable for some target
  • Loading branch information
qwandor authored Jul 29, 2024
2 parents 697b229 + ce79c30 commit 4982f6a
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 2 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## Unreleased

### New features

- Added `Mapping::translation` method.
- Derived zerocopy traits for `VirtualAddress`, `PhysicalAddress`, `PageTable` and `Descriptor`.
This is guarded behind the `zerocopy` feature so the dependency can be avoided if not desired.
- Added `TargetAllocator` for pregenerating a static pagetable for a target device.

## 0.7.0

### Breaking changes
Expand Down
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ categories = ["embedded", "no-std", "hardware-support"]

[dependencies]
bitflags = "2.6.0"
zerocopy = { version = "0.7.35", features = ["derive"], optional = true }

[features]
default = ["alloc"]
default = ["alloc", "zerocopy"]
alloc = []
zerocopy = ["dep:zerocopy"]

[package.metadata.docs.rs]
all-features = true
Expand Down
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ pub mod idmap;
#[cfg(feature = "alloc")]
pub mod linearmap;
pub mod paging;
#[cfg(feature = "alloc")]
pub mod target;

#[cfg(feature = "alloc")]
extern crate alloc;
Expand Down Expand Up @@ -138,6 +140,11 @@ impl<T: Translation> Mapping<T> {
}
}

/// Returns a reference to the translation used for this page table.
pub fn translation(&self) -> &T {
self.root.translation()
}

/// Returns whether this mapping is currently active.
pub fn active(&self) -> bool {
self.previous_ttbr.is_some()
Expand Down
1 change: 0 additions & 1 deletion src/linearmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ impl LinearMap {
) -> Result<(), MapError> {
let pa = self
.mapping
.root
.translation()
.virtual_to_physical(range.start())?;
self.mapping.map_range(range, pa, flags, constraints)
Expand Down
8 changes: 8 additions & 0 deletions src/paging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use core::fmt::{self, Debug, Display, Formatter};
use core::marker::PhantomData;
use core::ops::{Add, Range, Sub};
use core::ptr::NonNull;
#[cfg(feature = "zerocopy")]
use zerocopy::{AsBytes, FromBytes, FromZeroes};

const PAGE_SHIFT: usize = 12;

Expand Down Expand Up @@ -62,7 +64,9 @@ impl TranslationRegime {
}

/// An aarch64 virtual address, the input type of a stage 1 page table.
#[cfg_attr(feature = "zerocopy", derive(AsBytes, FromBytes, FromZeroes))]
#[derive(Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct VirtualAddress(pub usize);

impl Display for VirtualAddress {
Expand Down Expand Up @@ -107,7 +111,9 @@ pub struct MemoryRegion(Range<VirtualAddress>);

/// An aarch64 physical address or intermediate physical address, the output type of a stage 1 page
/// table.
#[cfg_attr(feature = "zerocopy", derive(AsBytes, FromBytes, FromZeroes))]
#[derive(Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct PhysicalAddress(pub usize);

impl Display for PhysicalAddress {
Expand Down Expand Up @@ -828,6 +834,7 @@ impl<T: Translation> PageTableWithLevel<T> {

/// A single level of a page table.
#[repr(C, align(4096))]
#[cfg_attr(feature = "zerocopy", derive(AsBytes, FromZeroes))]
pub struct PageTable {
entries: [Descriptor; 1 << BITS_PER_LEVEL],
}
Expand All @@ -849,6 +856,7 @@ impl PageTable {
/// - A page mapping, if it is in the lowest level page table.
/// - A block mapping, if it is not in the lowest level page table.
/// - A pointer to a lower level pagetable, if it is not in the lowest level page table.
#[cfg_attr(feature = "zerocopy", derive(AsBytes, FromZeroes))]
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct Descriptor(usize);
Expand Down
196 changes: 196 additions & 0 deletions src/target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// Copyright 2024 The aarch64-paging Authors.
// This project is dual-licensed under Apache 2.0 and MIT terms.
// See LICENSE-APACHE and LICENSE-MIT for details.

//! Types for building a static pagetable for some separate target device.
//!
//! See [`TargetAllocator`] for details on how to use it.
use crate::paging::{deallocate, PageTable, PhysicalAddress, Translation};
use alloc::{vec, vec::Vec};
use core::{mem::size_of, ptr::NonNull};
#[cfg(feature = "zerocopy")]
use zerocopy::AsBytes;

/// An implementation of `Translation` which builds a static pagetable to be built into a binary for
/// some target device.
///
/// # Example
///
/// ```
/// use aarch64_paging::{
/// paging::{
/// Attributes, Constraints, MemoryRegion, PhysicalAddress, RootTable, TranslationRegime,
/// VaRange,
/// },
/// target::TargetAllocator,
/// };
///
/// const ROOT_LEVEL: usize = 1;
///
/// let mut map = RootTable::new(
/// TargetAllocator::new(0x1_0000),
/// ROOT_LEVEL,
/// TranslationRegime::El1And0,
/// VaRange::Lower,
/// );
/// map.map_range(
/// &MemoryRegion::new(0x0, 0x1000),
/// PhysicalAddress(0x4_2000),
/// Attributes::VALID
/// | Attributes::ATTRIBUTE_INDEX_0
/// | Attributes::INNER_SHAREABLE
/// | Attributes::UXN,
/// Constraints::empty(),
/// )
/// .unwrap();
///
/// let bytes = map.translation().as_bytes();
/// // Build the bytes into a binary image for the target device...
/// ```
#[derive(Debug)]
pub struct TargetAllocator {
base_address: u64,
allocations: Vec<Option<NonNull<PageTable>>>,
}

impl TargetAllocator {
/// Creates a new `TargetAllocator` for a page table which will be loaded on the target in a
/// contiguous block of memory starting at the given address.
pub fn new(base_address: u64) -> Self {
Self {
base_address,
allocations: vec![],
}
}

fn add_allocation(&mut self, page_table: NonNull<PageTable>) -> usize {
for (i, allocation) in self.allocations.iter_mut().enumerate() {
if allocation.is_none() {
*allocation = Some(page_table);
return i;
}
}
self.allocations.push(Some(page_table));
self.allocations.len() - 1
}

fn remove_allocation(&mut self, page_table: NonNull<PageTable>) -> bool {
for allocation in &mut self.allocations {
if *allocation == Some(page_table) {
*allocation = None;
return true;
}
}
false
}

/// Returns the full page table as bytes to be loaded into the target device's memory.
///
/// This could be embedded in a binary image for the target.
#[cfg(feature = "zerocopy")]
pub fn as_bytes(&self) -> Vec<u8> {
let mut bytes = vec![0; self.allocations.len() * size_of::<PageTable>()];
for (chunk, allocation) in bytes
.chunks_exact_mut(size_of::<PageTable>())
.zip(self.allocations.iter())
{
if let Some(page_table) = allocation {
// SAFETY: The pointer is valid because we allocated it in `allocate_table`, and has
// no aliases for this block.
let page_table = unsafe { page_table.as_ref() };
page_table.write_to(chunk).unwrap();
}
}
bytes
}
}

impl Translation for TargetAllocator {
fn allocate_table(&mut self) -> (NonNull<PageTable>, PhysicalAddress) {
let page_table = PageTable::new();
let index = self.add_allocation(page_table);
let address = PhysicalAddress(
usize::try_from(self.base_address).unwrap() + index * size_of::<PageTable>(),
);
(page_table, address)
}

unsafe fn deallocate_table(&mut self, page_table: NonNull<PageTable>) {
if !self.remove_allocation(page_table) {
panic!(
"dealloc_table called for page table {:?} which isn't in allocations.",
page_table
);
}
// SAFETY: Our caller promises that the memory was allocated by `allocate_table` on this
// `TargetAllocator` and not yet deallocated. `allocate_table` used the global allocator
// and appropriate layout by calling `PageTable::new()`.
unsafe {
deallocate(page_table);
}
}

fn physical_to_virtual(&self, pa: PhysicalAddress) -> NonNull<PageTable> {
self.allocations
[(pa.0 - usize::try_from(self.base_address).unwrap()) / size_of::<PageTable>()]
.unwrap()
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::paging::{
Attributes, Constraints, MemoryRegion, RootTable, TranslationRegime, VaRange,
};

const ROOT_LEVEL: usize = 1;

#[test]
fn map_one_page() {
let mut map = RootTable::new(
TargetAllocator::new(0x1_0000),
ROOT_LEVEL,
TranslationRegime::El1And0,
VaRange::Lower,
);
map.map_range(
&MemoryRegion::new(0x0, 0x1000),
PhysicalAddress(0x4_2000),
Attributes::VALID
| Attributes::ATTRIBUTE_INDEX_0
| Attributes::INNER_SHAREABLE
| Attributes::UXN,
Constraints::empty(),
)
.unwrap();

let bytes = map.translation().as_bytes();
assert_eq!(bytes.len(), 3 * size_of::<PageTable>());
// Table mapping for table at 0x01_1000
assert_eq!(
bytes[0..8],
[0x03, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]
);
for byte in &bytes[8..size_of::<PageTable>()] {
assert_eq!(*byte, 0);
}
// Table mapping for table at 0x01_2000
assert_eq!(
bytes[size_of::<PageTable>()..size_of::<PageTable>() + 8],
[0x03, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]
);
for byte in &bytes[size_of::<PageTable>() + 8..2 * size_of::<PageTable>()] {
assert_eq!(*byte, 0);
}
// Page mapping for 0x04_2000 with the attributes given above.
assert_eq!(
bytes[2 * size_of::<PageTable>()..2 * size_of::<PageTable>() + 8],
[0x03, 0x23, 0x04, 0x00, 0x00, 0x00, 0x40, 0x00]
);
for byte in &bytes[2 * size_of::<PageTable>() + 8..3 * size_of::<PageTable>()] {
assert_eq!(*byte, 0);
}
}
}

0 comments on commit 4982f6a

Please sign in to comment.