Skip to content

Commit 61b2b2f

Browse files
committed
feat: device alloc: appropriate entire pages, lazily
1 parent 47594e0 commit 61b2b2f

2 files changed

Lines changed: 143 additions & 41 deletions

File tree

src/mm/device_alloc.rs

Lines changed: 140 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,21 @@ use core::alloc::{AllocError, Allocator, Layout};
22
use core::ptr::{self, NonNull};
33

44
use align_address::Align;
5+
#[cfg(target_arch = "x86_64")]
6+
use free_list::FreeList;
57
use free_list::{PageLayout, PageRange};
8+
#[cfg(target_arch = "x86_64")]
9+
use hermit_sync::InterruptTicketMutex;
610
use memory_addresses::{PhysAddr, VirtAddr};
11+
#[cfg(target_arch = "x86_64")]
12+
use x86_64::structures::paging::PhysFrame;
713

8-
use crate::arch::mm::paging::{BasePageSize, PageSize};
9-
use crate::mm::{FrameAlloc, PageRangeAllocator, virtualmem};
14+
use crate::arch::mm::paging;
15+
use crate::arch::mm::paging::{BasePageSize, HugePageSize, PageSize};
16+
#[cfg(target_arch = "x86_64")]
17+
use crate::arch::mm::paging::{LargePageSize, PageTableEntryFlags};
18+
use crate::env;
19+
use crate::mm::{FrameAlloc, PageRangeAllocator};
1020

1121
/// An [`Allocator`] for memory that is used to communicate with devices.
1222
///
@@ -19,7 +29,15 @@ unsafe impl Allocator for DeviceAlloc {
1929
let size = layout.size().align_up(BasePageSize::SIZE as usize);
2030
let frame_layout = PageLayout::from_size(size).unwrap();
2131

22-
let frame_range = FrameAlloc::allocate(frame_layout).map_err(|_| AllocError)?;
32+
let frame_range = if const { DeviceAlloc.phys_offset().is_null() } {
33+
FrameAlloc::allocate(frame_layout)
34+
} else {
35+
cfg_select! {
36+
target_arch = "x86_64" => DeviceFreeList::allocate(frame_layout),
37+
_ => unreachable!()
38+
}
39+
}
40+
.map_err(|_| AllocError)?;
2341

2442
let phys_addr = PhysAddr::from(frame_range.start());
2543
let ptr = self.ptr_from(phys_addr);
@@ -34,17 +52,22 @@ unsafe impl Allocator for DeviceAlloc {
3452
let phys_addr = self.phys_addr_from(ptr.as_ptr());
3553
let range = PageRange::from_start_len(phys_addr.as_usize(), size).unwrap();
3654

37-
unsafe {
38-
FrameAlloc::deallocate(range);
39-
}
55+
if const { DeviceAlloc.phys_offset().is_null() } {
56+
unsafe { FrameAlloc::deallocate(range) }
57+
} else {
58+
cfg_select! {
59+
target_arch = "x86_64" => unsafe { DeviceFreeList::deallocate(range) },
60+
_ => unreachable!()
61+
}
62+
};
4063
}
4164
}
4265

4366
impl DeviceAlloc {
4467
/// Returns a pointer corresponding to `phys_addr`.
4568
#[inline]
46-
pub fn ptr_from<T>(&self, phys_addr: PhysAddr) -> *mut T {
47-
let addr = phys_addr.as_usize() + self.phys_offset().as_usize();
69+
pub const fn ptr_from<T>(&self, phys_addr: PhysAddr) -> *mut T {
70+
let addr = phys_addr.as_usize() + const { Self.phys_offset().as_usize() };
4871
ptr::with_exposed_provenance_mut(addr)
4972
}
5073

@@ -53,19 +76,121 @@ impl DeviceAlloc {
5376
/// The address is only correct if `ptr` has been allocated by this allocator.
5477
#[inline]
5578
pub fn phys_addr_from<T: ?Sized>(&self, ptr: *mut T) -> PhysAddr {
56-
let addr = u64::try_from(ptr.expose_provenance()).unwrap() - self.phys_offset().as_u64();
79+
let addr =
80+
u64::try_from(ptr.expose_provenance()).unwrap() - const { Self.phys_offset().as_u64() };
5781
PhysAddr::new(addr)
5882
}
5983

6084
/// Returns the physical address offset.
6185
///
6286
/// This device allocator expects the complete physical memory to be mapped device-readable at this offset.
63-
#[inline]
64-
pub fn phys_offset(&self) -> VirtAddr {
65-
if cfg!(careful) {
66-
virtualmem::kernel_heap_end().as_u64().div_ceil(4).into()
67-
} else {
68-
0u64.into()
87+
#[inline(always)]
88+
const fn phys_offset(&self) -> VirtAddr {
89+
cfg_select! {
90+
all(target_arch = "x86_64", careful) => VirtAddr::new(crate::mm::virtualmem::kernel_heap_end().as_u64().div_ceil(4)),
91+
_ => VirtAddr::zero(),
92+
}
93+
}
94+
95+
pub fn init() {
96+
// Remove all mappings in the device allocator range
97+
if env::is_uefi() && DeviceAlloc.phys_offset() != VirtAddr::zero() {
98+
let start = DeviceAlloc.phys_offset();
99+
let count = DeviceAlloc.phys_offset().as_u64() / HugePageSize::SIZE;
100+
let count = usize::try_from(count).unwrap();
101+
paging::unmap::<HugePageSize>(start, count);
102+
}
103+
}
104+
}
105+
106+
#[cfg(target_arch = "x86_64")]
107+
static DEVICE_FREE_LIST: InterruptTicketMutex<FreeList<16>> =
108+
InterruptTicketMutex::new(FreeList::new());
109+
110+
#[cfg(target_arch = "x86_64")]
111+
struct DeviceFreeList;
112+
113+
#[cfg(target_arch = "x86_64")]
114+
type DeviceAllocIncrement = LargePageSize;
115+
116+
#[cfg(target_arch = "x86_64")]
117+
impl DeviceFreeList {
118+
fn allocate(layout: PageLayout) -> Result<PageRange, AllocError> {
119+
let allocation = DEVICE_FREE_LIST.lock().allocate(layout);
120+
121+
match allocation {
122+
Err(_) => {
123+
// Failed allocation: try to claim pages from the main FrameAllocator then retry
124+
let aligned_layout = PageLayout::from_size_align(
125+
layout.size().align_up(DeviceAllocIncrement::SIZE as usize),
126+
DeviceAllocIncrement::SIZE as usize,
127+
)
128+
.unwrap();
129+
130+
let frames = FrameAlloc::allocate(aligned_layout)?;
131+
let start = x86_64::PhysAddr::new(u64::try_from(frames.start()).unwrap());
132+
let start = PhysFrame::<DeviceAllocIncrement>::from_start_address(start).unwrap();
133+
let end = x86_64::PhysAddr::new(u64::try_from(frames.end()).unwrap());
134+
let end = PhysFrame::<DeviceAllocIncrement>::from_start_address(end).unwrap();
135+
136+
for frame in PhysFrame::range(start, end) {
137+
unsafe {
138+
Self::map_claim_frame(frame)?;
139+
}
140+
}
141+
142+
// Retry allocation
143+
DEVICE_FREE_LIST
144+
.lock()
145+
.allocate(layout)
146+
.map_err(|_| AllocError)
147+
}
148+
Ok(r) => Ok(r),
149+
}
150+
}
151+
152+
/// # Safety
153+
///
154+
/// See [PageRangeAllocator::deallocate]
155+
unsafe fn deallocate(range: PageRange) {
156+
// OPTIONAL: if we have too much memory in the list we may return it to the physical free list
157+
// In this case, we MUST unmap it/remap it as identity
158+
unsafe {
159+
// SAFETY: invariants match
160+
DEVICE_FREE_LIST.lock().deallocate(range).unwrap();
161+
}
162+
}
163+
164+
/// Adds the given frame to the device free list.
165+
///
166+
/// The identity mapping for the frame will be removed and a new mapping will be inserted at
167+
/// the offset for devices.
168+
///
169+
/// The frame will then be added to the free list.
170+
///
171+
/// # Safety
172+
///
173+
/// The frame should have been clained from the free list
174+
unsafe fn map_claim_frame(frame: PhysFrame<DeviceAllocIncrement>) -> Result<(), AllocError> {
175+
let identity_mapping = VirtAddr::new(frame.start_address().as_u64());
176+
177+
// Remove identity mapping
178+
paging::unmap::<DeviceAllocIncrement>(identity_mapping, 1);
179+
180+
// Add mapping at the device offset
181+
let flags = PageTableEntryFlags::WRITABLE
182+
| PageTableEntryFlags::NO_EXECUTE
183+
| PageTableEntryFlags::WRITE_THROUGH;
184+
185+
let phys_addr = frame.start_address().into();
186+
let virt_addr = VirtAddr::from_ptr(DeviceAlloc.ptr_from::<()>(phys_addr));
187+
paging::map::<DeviceAllocIncrement>(virt_addr, phys_addr, 1, flags);
188+
189+
unsafe {
190+
DEVICE_FREE_LIST
191+
.lock()
192+
.deallocate(frame.into())
193+
.map_err(|_| AllocError)
69194
}
70195
}
71196
}

src/mm/physicalmem.rs

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ use free_list::{FreeList, PageLayout, PageRange, PageRangeError};
77
use hermit_sync::InterruptTicketMutex;
88
use memory_addresses::{PhysAddr, VirtAddr};
99

10-
#[cfg(target_arch = "x86_64")]
11-
use crate::arch::mm::paging::PageTableEntryFlagsExt;
12-
use crate::arch::mm::paging::{self, HugePageSize, PageSize, PageTableEntryFlags};
10+
use crate::arch::mm::paging::{self, PageSize};
1311
use crate::env;
1412
use crate::mm::device_alloc::DeviceAlloc;
1513
use crate::mm::{PageRangeAllocator, PageRangeBox};
@@ -67,7 +65,7 @@ pub unsafe fn map_frame_range(frame_range: PageRange) {
6765
type IdentityPageSize = paging::BasePageSize;
6866
}
6967
target_arch = "riscv64" => {
70-
type IdentityPageSize = HugePageSize;
68+
type IdentityPageSize = paging::HugePageSize;
7169
}
7270
target_arch = "x86_64" => {
7371
type IdentityPageSize = paging::LargePageSize;
@@ -85,22 +83,6 @@ pub unsafe fn map_frame_range(frame_range: PageRange) {
8583
.step_by(IdentityPageSize::SIZE.try_into().unwrap())
8684
.map(|addr| PhysAddr::new(addr.try_into().unwrap()))
8785
.for_each(paging::identity_map::<IdentityPageSize>);
88-
89-
// Map the physical memory again if DeviceAlloc operates at an offset
90-
if DeviceAlloc.phys_offset() != VirtAddr::zero() {
91-
let flags = {
92-
let mut flags = PageTableEntryFlags::empty();
93-
flags.normal().writable().execute_disable();
94-
flags
95-
};
96-
(start..end)
97-
.step_by(IdentityPageSize::SIZE.try_into().unwrap())
98-
.for_each(|addr| {
99-
let phys_addr = PhysAddr::new(addr.try_into().unwrap());
100-
let virt_addr = VirtAddr::from_ptr(DeviceAlloc.ptr_from::<()>(phys_addr));
101-
paging::map::<IdentityPageSize>(virt_addr, phys_addr, 1, flags);
102-
});
103-
}
10486
}
10587

10688
unsafe fn detect_from_fdt() -> Result<(), ()> {
@@ -225,12 +207,7 @@ unsafe fn detect_from_limits() -> Result<(), ()> {
225207
}
226208

227209
unsafe fn init() {
228-
if env::is_uefi() && DeviceAlloc.phys_offset() != VirtAddr::zero() {
229-
let start = DeviceAlloc.phys_offset();
230-
let count = DeviceAlloc.phys_offset().as_u64() / HugePageSize::SIZE;
231-
let count = usize::try_from(count).unwrap();
232-
paging::unmap::<HugePageSize>(start, count);
233-
}
210+
DeviceAlloc::init();
234211

235212
if unsafe { detect_from_fdt().is_ok() } {
236213
return;

0 commit comments

Comments
 (0)