Skip to main content

hermit/mm/
physicalmem.rs

1use alloc::vec::Vec;
2use core::alloc::AllocError;
3use core::fmt;
4use core::sync::atomic::{AtomicUsize, Ordering};
5
6use align_address::Align;
7use free_list::{FreeList, PageLayout, PageRange};
8use hermit_sync::InterruptTicketMutex;
9use memory_addresses::VirtAddr;
10
11#[cfg(all(target_arch = "x86_64", feature = "hermit-entry"))]
12use crate::arch::mm::paging::PageTableEntryFlagsExt;
13use crate::arch::mm::paging::{self, HugePageSize, LargePageSize, PageSize};
14use crate::env::{self, MemmapType, StartInfo};
15use crate::mm::device_alloc::DeviceAlloc;
16use crate::mm::{PageRangeAllocator, PageRangeBox};
17
18static PHYSICAL_FREE_LIST: InterruptTicketMutex<FreeList<16>> =
19	InterruptTicketMutex::new(FreeList::new());
20pub static TOTAL_MEMORY: AtomicUsize = AtomicUsize::new(0);
21
22pub struct FrameAlloc;
23
24impl PageRangeAllocator for FrameAlloc {
25	unsafe fn init() {
26		unsafe {
27			init();
28		}
29	}
30
31	fn allocate(layout: PageLayout) -> Result<PageRange, AllocError> {
32		PHYSICAL_FREE_LIST
33			.lock()
34			.allocate(layout)
35			.map_err(|_| AllocError)
36	}
37
38	fn allocate_at(range: PageRange) -> Result<(), AllocError> {
39		PHYSICAL_FREE_LIST
40			.lock()
41			.allocate_at(range)
42			.map_err(|_| AllocError)
43	}
44
45	unsafe fn deallocate(range: PageRange) {
46		unsafe {
47			PHYSICAL_FREE_LIST.lock().deallocate(range).unwrap();
48		}
49	}
50}
51
52impl fmt::Display for FrameAlloc {
53	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54		let free_list = PHYSICAL_FREE_LIST.lock();
55		write!(f, "FrameAlloc free list:\n{free_list}")
56	}
57}
58
59pub type FrameBox = PageRangeBox<FrameAlloc>;
60
61pub fn total_memory_size() -> usize {
62	TOTAL_MEMORY.load(Ordering::Relaxed)
63}
64
65#[cfg(feature = "hermit-entry")]
66pub unsafe fn map_frame_range(frame_range: PageRange) {
67	use memory_addresses::PhysAddr;
68
69	use crate::arch::mm::paging::PageTableEntryFlags;
70
71	cfg_select! {
72		target_arch = "aarch64" => {
73			type IdentityPageSize = paging::BasePageSize;
74		}
75		target_arch = "riscv64" => {
76			type IdentityPageSize = HugePageSize;
77		}
78		target_arch = "x86_64" => {
79			type IdentityPageSize = LargePageSize;
80		}
81	}
82
83	let start = frame_range
84		.start()
85		.align_down(IdentityPageSize::SIZE.try_into().unwrap());
86	let end = frame_range
87		.end()
88		.align_up(IdentityPageSize::SIZE.try_into().unwrap());
89
90	(start..end)
91		.step_by(IdentityPageSize::SIZE.try_into().unwrap())
92		.map(|addr| PhysAddr::new(addr.try_into().unwrap()))
93		.for_each(paging::identity_map::<IdentityPageSize>);
94
95	// Map the physical memory again if DeviceAlloc operates at an offset
96	if DeviceAlloc.phys_offset() != VirtAddr::zero() {
97		let flags = PageTableEntryFlags::normal();
98		(start..end)
99			.step_by(IdentityPageSize::SIZE.try_into().unwrap())
100			.for_each(|addr| {
101				let phys_addr = PhysAddr::new(addr.try_into().unwrap());
102				let virt_addr = VirtAddr::from_ptr(DeviceAlloc.ptr_from::<()>(phys_addr));
103				paging::map::<IdentityPageSize>(virt_addr, phys_addr, 1, flags);
104			});
105	}
106}
107
108unsafe fn detect_from_start_info() {
109	for memmap_entry in env::start_info().memmap() {
110		if memmap_entry.ty != MemmapType::Ram {
111			continue;
112		}
113
114		let mut start_addr = memmap_entry.phys_addr;
115		let mut end_addr = start_addr + memmap_entry.len;
116
117		// Do not free any address in the real mode addressable range.
118		//
119		// When claiming physical memory, ignore all addresses below this one. This ensures we
120		// don't accidentally clash with hardcoded low addresses, such as `SMP_BOOT_CODE_ADDRESS`
121		// in x86_64 with SMP enabled. We use a 2MIB size for now, but this is arbitrary, and could
122		// likely be lowered.
123		start_addr = start_addr.max(LargePageSize::SIZE as usize);
124
125		#[cfg(all(target_arch = "x86_64", feature = "hermit-entry"))]
126		if paging::is_recursive() {
127			start_addr = start_addr.max(elf_symbols::executable_end().addr());
128		}
129
130		if cfg!(target_arch = "aarch64") || cfg!(target_arch = "riscv64") {
131			start_addr = start_addr.max(elf_symbols::executable_end().addr());
132		}
133
134		start_addr = start_addr.align_up(0x1000);
135		end_addr = end_addr.align_down(0x1000);
136
137		if start_addr > end_addr {
138			continue;
139		}
140
141		let range = PageRange::new(start_addr, end_addr).unwrap();
142		unsafe {
143			FrameAlloc::deallocate(range);
144		}
145	}
146
147	let reserve = |reservation: PageRange| {
148		debug!("Memory reservation: {reservation:#x?}");
149		// While there are still overlaps between this reservation and any available ranges,
150		// allocate that overlap to mark it as not available.
151		while let Ok(reserved) = PHYSICAL_FREE_LIST
152			.lock()
153			.allocate_with(|range| reservation.and(range))
154		{
155			debug!("Reserved {reserved:#x?}");
156		}
157	};
158
159	let kernel_start = elf_symbols::executable_start().addr();
160	let kernel_end = elf_symbols::executable_end().addr();
161	let kernel_region = PageRange::containing(kernel_start, kernel_end).unwrap();
162	reserve(kernel_region);
163
164	for module in env::start_info().modules() {
165		reserve(module.phys_frame_range());
166	}
167
168	#[cfg(feature = "hermit-entry")]
169	{
170		use crate::env::FdtStartInfo;
171
172		let fdt = env::start_info().fdt().unwrap();
173
174		for reservation in fdt.memory_reservations() {
175			let start = reservation.address().addr();
176			let end = start + reservation.size();
177			let reservation = PageRange::new(start, end).unwrap();
178			reserve(reservation);
179		}
180
181		let fdt_start = env::start_info().fdt_addr().unwrap().get();
182		let fdt_end = fdt_start + fdt.total_size();
183		let fdt_region = PageRange::containing(fdt_start, fdt_end).unwrap();
184		reserve(fdt_region);
185	}
186
187	let frame_ranges = PHYSICAL_FREE_LIST.lock().iter().collect::<Vec<_>>();
188
189	for frame_range in frame_ranges {
190		#[cfg(feature = "hermit-entry")]
191		unsafe {
192			map_frame_range(frame_range);
193		}
194		debug!("Claimed physical memory: {frame_range:#x?}");
195	}
196
197	TOTAL_MEMORY.store(PHYSICAL_FREE_LIST.lock().free_space(), Ordering::Relaxed);
198}
199
200unsafe fn init() {
201	if cfg!(target_arch = "x86_64") && DeviceAlloc.phys_offset() != VirtAddr::zero() {
202		let start = DeviceAlloc.phys_offset();
203		let count = DeviceAlloc.phys_offset().as_u64() / HugePageSize::SIZE;
204		let count = usize::try_from(count).unwrap();
205		paging::unmap::<HugePageSize>(start, count);
206	}
207
208	unsafe {
209		detect_from_start_info();
210	}
211}