ostd/mm/kspace/kvirt_area.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
// SPDX-License-Identifier: MPL-2.0
//! Kernel virtual memory allocation
use core::ops::Range;
use safety::safety;
use super::{KERNEL_PAGE_TABLE, VMALLOC_VADDR_RANGE};
use crate::{
mm::{
frame::{meta::AnyFrameMeta, Frame},
kspace::{KernelPtConfig, MappedItem},
page_prop::PageProperty,
page_table::largest_pages,
Paddr, Vaddr, PAGE_SIZE,
},
task::disable_preempt,
util::range_alloc::RangeAllocator,
};
static KVIRT_AREA_ALLOCATOR: RangeAllocator = RangeAllocator::new(VMALLOC_VADDR_RANGE);
/// Kernel virtual area.
///
/// A kernel virtual area manages a range of memory in [`VMALLOC_VADDR_RANGE`].
/// It can map a portion or the entirety of its virtual memory pages to
/// physical memory, whether tracked with metadata or not.
///
/// It is the caller's responsibility to ensure TLB coherence before using the
/// mapped virtual address on a certain CPU.
//
// FIXME: This caller-ensured design is very error-prone. A good option is to
// use a guard the pins the CPU and ensures TLB coherence while accessing the
// `KVirtArea`. However, `IoMem` need some non trivial refactoring to support
// being implemented on a `!Send` and `!Sync` guard.
#[derive(Debug)]
pub struct KVirtArea {
range: Range<Vaddr>,
}
impl KVirtArea {
pub fn start(&self) -> Vaddr {
self.range.start
}
pub fn end(&self) -> Vaddr {
self.range.end
}
pub fn range(&self) -> Range<Vaddr> {
self.range.start..self.range.end
}
#[cfg(ktest)]
pub fn len(&self) -> usize {
self.range.len()
}
#[cfg(ktest)]
pub fn query(&self, addr: Vaddr) -> Option<super::MappedItem> {
use align_ext::AlignExt;
assert!(self.start() <= addr && self.end() >= addr);
let start = addr.align_down(PAGE_SIZE);
let vaddr = start..start + PAGE_SIZE;
let page_table = KERNEL_PAGE_TABLE.get().unwrap();
let preempt_guard = disable_preempt();
let mut cursor = page_table.cursor(&preempt_guard, &vaddr).unwrap();
cursor.query().unwrap().1
}
/// Create a kernel virtual area and map tracked pages into it.
///
/// The created virtual area will have a size of `area_size`, and the pages
/// will be mapped starting from `map_offset` in the area.
///
/// # Panics
///
/// This function panics if
/// - the area size is not a multiple of [`PAGE_SIZE`];
/// - the map offset is not aligned to [`PAGE_SIZE`];
/// - the map offset plus the size of the pages exceeds the area size.
pub fn map_frames<T: AnyFrameMeta>(
area_size: usize,
map_offset: usize,
frames: impl Iterator<Item = Frame<T>>,
prop: PageProperty,
) -> Self {
assert!(area_size % PAGE_SIZE == 0);
assert!(map_offset % PAGE_SIZE == 0);
let range = KVIRT_AREA_ALLOCATOR.alloc(area_size).unwrap();
let cursor_range = range.start + map_offset..range.end;
let page_table = KERNEL_PAGE_TABLE.get().unwrap();
let preempt_guard = disable_preempt();
let mut cursor = page_table
.cursor_mut(&preempt_guard, &cursor_range)
.unwrap();
for frame in frames.into_iter() {
// SAFETY: The constructor of the `KVirtArea` has already ensured
// that this mapping does not affect kernel's memory safety.
unsafe { cursor.map(MappedItem::Tracked(frame.into(), prop)) }
.expect("Failed to map frame in a new `KVirtArea`");
}
Self { range }
}
/// Creates a kernel virtual area and maps untracked frames into it.
///
/// The created virtual area will have a size of `area_size`, and the
/// physical addresses will be mapped starting from `map_offset` in
/// the area.
///
/// You can provide a `0..0` physical range to create a virtual area without
/// mapping any physical memory.
///
/// # Panics
///
/// This function panics if
/// - the area size is not a multiple of [`PAGE_SIZE`];
/// - the map offset is not aligned to [`PAGE_SIZE`];
/// - the provided physical range is not aligned to [`PAGE_SIZE`];
/// - the map offset plus the length of the physical range exceeds the
/// area size;
/// - the provided physical range contains tracked physical addresses.
#[safety {
Untracked("The physical addresses within pa_range")
}]
pub unsafe fn map_untracked_frames(
area_size: usize,
map_offset: usize,
pa_range: Range<Paddr>,
prop: PageProperty,
) -> Self {
assert!(pa_range.start % PAGE_SIZE == 0);
assert!(pa_range.end % PAGE_SIZE == 0);
assert!(area_size % PAGE_SIZE == 0);
assert!(map_offset % PAGE_SIZE == 0);
assert!(map_offset + pa_range.len() <= area_size);
let range = KVIRT_AREA_ALLOCATOR.alloc(area_size).unwrap();
if !pa_range.is_empty() {
let len = pa_range.len();
let va_range = range.start + map_offset..range.start + map_offset + len;
let page_table = KERNEL_PAGE_TABLE.get().unwrap();
let preempt_guard = disable_preempt();
let mut cursor = page_table.cursor_mut(&preempt_guard, &va_range).unwrap();
for (pa, level) in largest_pages::<KernelPtConfig>(va_range.start, pa_range.start, len)
{
// SAFETY: The caller of `map_untracked_frames` has ensured the safety of this mapping.
let _ = unsafe { cursor.map(MappedItem::Untracked(pa, level, prop)) };
}
}
Self { range }
}
}
impl Drop for KVirtArea {
fn drop(&mut self) {
// 1. unmap all mapped pages.
let page_table = KERNEL_PAGE_TABLE.get().unwrap();
let range = self.start()..self.end();
let preempt_guard = disable_preempt();
let mut cursor = page_table.cursor_mut(&preempt_guard, &range).unwrap();
loop {
// SAFETY: The range is under `KVirtArea` so it is safe to unmap.
let Some(frag) = (unsafe { cursor.take_next(self.end() - cursor.virt_addr()) }) else {
break;
};
drop(frag);
}
// 2. free the virtual block
KVIRT_AREA_ALLOCATOR.free(range);
}
}