Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
- The mappings of a `MappedPageTable` can now be displayed.
- [Increase the Minimum Supported Rust Version to 1.98](https://github.com/rust-osdev/x86_64/pull/604)
- [make memory encryption bit an upper limit for physical address bits](https://github.com/rust-osdev/x86_64/pull/603)
- [add 57-bit and runtime-validated virtual address types](https://github.com/rust-osdev/x86_64/pull/605)
- `VirtAddr57` and `VirtAddrRT` are available through the `virt_addr_57` and `virt_addr_rt` features.
- [add `VirtAddr57/RT` support for `Page`](https://github.com/rust-osdev/x86_64/pull/619) Generalize `Page` and page ranges over virtual-address validity policies, including five-level page indices.
- TLB invalidation now accepts fixed-width and runtime virtual addresses.

# 0.15.5 – 2026-07-11

Expand Down
20 changes: 13 additions & 7 deletions src/addr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,19 +274,25 @@ where
where
U: Into<u64>,
{
self.align_down_u64(align.into())
Self::new_truncate(align_down(self.0, align.into()))
}
}

impl<V: VirtAddrValidity> VirtAddrGeneric<V> {
/// Aligns the virtual address downwards to the given alignment.
///
/// This variant accepts the alignment as a `u64` for internal users.
/// This internal variant accepts the alignment as a `u64` and preserves the
/// validity policy of the address.
///
/// # Safety
///
/// The caller must ensure that the alignment is not greater than the half
/// of the size of the address space.
#[inline]
pub(crate) const fn align_down_u64(self, align: u64) -> Self {
Self::new_truncate(align_down(self.0, align))
pub(crate) const unsafe fn align_down_u64(self, align: u64) -> Self {
// Clearing low address bits cannot change the canonical sign extension.
VirtAddrGeneric(align_down(self.0, align), PhantomData)
}
}

impl<V: VirtAddrValidity> VirtAddrGeneric<V> {
/// Creates a new virtual address, without any checks.
///
/// ## Safety
Expand Down
103 changes: 94 additions & 9 deletions src/addr/rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,7 @@ impl VirtAddrGeneric<RuntimeValidity> {
where
U: Into<u64>,
{
self.align_down_u64(align.into())
}

/// Aligns the virtual address downwards to the given alignment.
///
/// This variant accepts the alignment as a `u64` for internal users.
#[inline]
pub(crate) fn align_down_u64(self, align: u64) -> Self {
Self::new_truncate(align_down(self.0, align))
Self::new_truncate(align_down(self.0, align.into()))
}

/// Refetches the virtual-address width from the active address-space mode and updates the
Expand Down Expand Up @@ -219,6 +211,99 @@ mod tests {

use super::*;

#[test]
fn runtime_l5_index_constructors_preserve_indices_or_reject() {
use crate::structures::paging::{Page, PageTableIndex, Size1GiB, Size2MiB, Size4KiB};

const TEST_WIDTH: &str = "X86_64_TEST_L5_ADDRESS_BITS";
let Ok(bits) = std::env::var(TEST_WIDTH) else {
// This test deliberately launches two child test processes. That is unusual, but
// necessary: each process must run only this test so its global
// `CURRENT_VIRTUAL_ADDRESS_BITS` cache cannot be shared with, or affected by, any
// other test that uses the cache. The environment variable seeds the cache without
// requiring a privileged CR4 read. If a better way to isolate this process-global
// state becomes available, this subprocess-based test should be replaced.
for bits in [48, 57] {
let output = std::process::Command::new(std::env::current_exe().unwrap())
.args([
"--exact",
"addr::rt::tests::runtime_l5_index_constructors_preserve_indices_or_reject",
"--nocapture",
])
.env(TEST_WIDTH, bits.to_string())
.output()
.unwrap();
assert!(
output.status.success()
&& std::string::String::from_utf8_lossy(&output.stdout)
.contains("1 passed; 0 failed"),
"runtime width {bits}:\n{}\n{}",
std::string::String::from_utf8_lossy(&output.stdout),
std::string::String::from_utf8_lossy(&output.stderr),
);
}
return;
};
let bits = bits.parse::<u8>().unwrap();
assert!(matches!(bits, 48 | 57));
CURRENT_VIRTUAL_ADDRESS_BITS.store(bits, Ordering::Relaxed);

for p5 in [0, 1, 255, 256, 510, 511] {
for p4 in [0, 255, 256, 511] {
let p5_index = PageTableIndex::new(p5);
let p4_index = PageTableIndex::new(p4);
let results = [
std::panic::catch_unwind(|| {
Page::<Size1GiB, RuntimeValidity>::from_page_table_indices_1gib_l5(
p5_index,
p4_index,
PageTableIndex::new(3),
)
.start_address()
.as_u64()
}),
std::panic::catch_unwind(|| {
Page::<Size2MiB, RuntimeValidity>::from_page_table_indices_2mib_l5(
p5_index,
p4_index,
PageTableIndex::new(3),
PageTableIndex::new(4),
)
.start_address()
.as_u64()
}),
std::panic::catch_unwind(|| {
Page::<Size4KiB, RuntimeValidity>::from_page_table_indices_l5(
p5_index,
p4_index,
PageTableIndex::new(3),
PageTableIndex::new(4),
PageTableIndex::new(5),
)
.start_address()
.as_u64()
}),
];
for (result, low_bits) in results.into_iter().zip([
3 << 30,
(3 << 30) | (4 << 21),
(3 << 30) | (4 << 21) | (5 << 12),
]) {
let fits = bits == 57 || (p5 == 0 && p4 < 256) || (p5 == 511 && p4 >= 256);
if !fits {
assert!(result.is_err(), "P5={p5}, P4={p4} must be rejected");
continue;
}
let address = result.unwrap();
assert_eq!((address >> 48) & 511, u64::from(p5));
assert_eq!((address >> 39) & 511, u64::from(p4));
assert_eq!(address & ((1 << 39) - 1), low_bits);
assert_eq!(address >> 57, if p5 < 256 { 0 } else { 127 });
}
}
}
}

#[test]
fn runtime_virtaddr_arithmetic_traits_are_available() {
fn assert_arithmetic<T>()
Expand Down
3 changes: 2 additions & 1 deletion src/instructions/tlb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use bit_field::BitField;

use crate::{
PrivilegeLevel, VirtAddr,
addr::{VirtAddrGeneric, VirtAddrValidity},
instructions::segmentation::{CS, Segment},
structures::paging::{
Page, PageSize, Size2MiB, Size4KiB,
Expand All @@ -14,7 +15,7 @@ use core::{arch::asm, cmp, convert::TryFrom, fmt};

/// Invalidate the given address in the TLB using the `invlpg` instruction.
#[inline]
pub fn flush(addr: VirtAddr) {
pub fn flush<V: VirtAddrValidity>(addr: VirtAddrGeneric<V>) {
unsafe {
asm!("invlpg [{}]", in(reg) addr.as_u64(), options(nostack, preserves_flags));
}
Expand Down
10 changes: 6 additions & 4 deletions src/structures/paging/mapper/mapped_page_table/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,10 @@ impl<P: PageTableFrameMapping> MappedPageTableIter<'_, P> {
}
Err(PageTableWalkError::MappedToHugePage) => {
// We have found a 1GiB page.
let page =
Page::from_page_table_indices_1gib(self.p4_index()?, self.p3_index()?);
let page = Page::<Size1GiB>::from_page_table_indices_1gib(
self.p4_index()?,
self.p3_index()?,
);
let entry = &p3[self.p3_index()?];
let frame = PhysFrame::containing_address(entry.addr());
let flags = entry.flags();
Expand All @@ -262,7 +264,7 @@ impl<P: PageTableFrameMapping> MappedPageTableIter<'_, P> {
}
Err(PageTableWalkError::MappedToHugePage) => {
// We have found a 2MiB page.
let page = Page::from_page_table_indices_2mib(
let page = Page::<Size2MiB>::from_page_table_indices_2mib(
self.p4_index()?,
self.p3_index()?,
self.p2_index()?,
Expand All @@ -287,7 +289,7 @@ impl<P: PageTableFrameMapping> MappedPageTableIter<'_, P> {
}

// We have found a 4KiB page.
let page = Page::from_page_table_indices(
let page = Page::<Size4KiB>::from_page_table_indices(
self.p4_index()?,
self.p3_index()?,
self.p2_index()?,
Expand Down
6 changes: 3 additions & 3 deletions src/structures/paging/mapper/recursive_page_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1037,7 +1037,7 @@ fn p3_ptr<S: PageSize>(page: Page<S>, recursive_index: PageTableIndex) -> *mut P

#[inline]
fn p3_page<S: PageSize>(page: Page<S>, recursive_index: PageTableIndex) -> Page {
Page::from_page_table_indices(
Page::<Size4KiB>::from_page_table_indices(
recursive_index,
recursive_index,
recursive_index,
Expand All @@ -1052,7 +1052,7 @@ fn p2_ptr<S: NotGiantPageSize>(page: Page<S>, recursive_index: PageTableIndex) -

#[inline]
fn p2_page<S: NotGiantPageSize>(page: Page<S>, recursive_index: PageTableIndex) -> Page {
Page::from_page_table_indices(
Page::<Size4KiB>::from_page_table_indices(
recursive_index,
recursive_index,
page.p4_index(),
Expand All @@ -1067,7 +1067,7 @@ fn p1_ptr(page: Page<Size4KiB>, recursive_index: PageTableIndex) -> *mut PageTab

#[inline]
fn p1_page(page: Page<Size4KiB>, recursive_index: PageTableIndex) -> Page {
Page::from_page_table_indices(
Page::<Size4KiB>::from_page_table_indices(
recursive_index,
page.p4_index(),
page.p3_index(),
Expand Down
Loading
Loading