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 = {
98			let mut flags = PageTableEntryFlags::empty();
99			flags.normal().writable().execute_disable();
100			flags
101		};
102		(start..end)
103			.step_by(IdentityPageSize::SIZE.try_into().unwrap())
104			.for_each(|addr| {
105				let phys_addr = PhysAddr::new(addr.try_into().unwrap());
106				let virt_addr = VirtAddr::from_ptr(DeviceAlloc.ptr_from::<()>(phys_addr));
107				paging::map::<IdentityPageSize>(virt_addr, phys_addr, 1, flags);
108			});
109	}
110}
111
112unsafe fn detect_from_start_info() {
113	for memmap_entry in env::start_info().memmap() {
114		if memmap_entry.ty != MemmapType::Ram {
115			continue;
116		}
117
118		let mut start_addr = memmap_entry.phys_addr;
119		let mut end_addr = start_addr + memmap_entry.len;
120
121		// Do not free any address in the real mode addressable range.
122		//
123		// When claiming physical memory, ignore all addresses below this one. This ensures we
124		// don't accidentally clash with hardcoded low addresses, such as `SMP_BOOT_CODE_ADDRESS`
125		// in x86_64 with SMP enabled. We use a 2MIB size for now, but this is arbitrary, and could
126		// likely be lowered.
127		start_addr = start_addr.max(LargePageSize::SIZE as usize);
128
129		#[cfg(all(target_arch = "x86_64", feature = "hermit-entry"))]
130		if paging::is_recursive() {
131			start_addr = start_addr.max(elf_symbols::executable_end().addr());
132		}
133
134		if cfg!(target_arch = "aarch64") || cfg!(target_arch = "riscv64") {
135			start_addr = start_addr.max(elf_symbols::executable_end().addr());
136		}
137
138		start_addr = start_addr.align_up(0x1000);
139		end_addr = end_addr.align_down(0x1000);
140
141		if start_addr > end_addr {
142			continue;
143		}
144
145		let range = PageRange::new(start_addr, end_addr).unwrap();
146		unsafe {
147			FrameAlloc::deallocate(range);
148		}
149	}
150
151	let reserve = |reservation: PageRange| {
152		debug!("Memory reservation: {reservation:#x?}");
153		// While there are still overlaps between this reservation and any available ranges,
154		// allocate that overlap to mark it as not available.
155		while let Ok(reserved) = PHYSICAL_FREE_LIST
156			.lock()
157			.allocate_with(|range| reservation.and(range))
158		{
159			debug!("Reserved {reserved:#x?}");
160		}
161	};
162
163	let kernel_start = elf_symbols::executable_start().addr();
164	let kernel_end = elf_symbols::executable_end().addr();
165	let kernel_region = PageRange::containing(kernel_start, kernel_end).unwrap();
166	reserve(kernel_region);
167
168	for module in env::start_info().modules() {
169		reserve(module.phys_frame_range());
170	}
171
172	#[cfg(feature = "hermit-entry")]
173	{
174		use crate::env::FdtStartInfo;
175
176		let fdt = env::start_info().fdt().unwrap();
177
178		for reservation in fdt.memory_reservations() {
179			let start = reservation.address().addr();
180			let end = start + reservation.size();
181			let reservation = PageRange::new(start, end).unwrap();
182			reserve(reservation);
183		}
184
185		let fdt_start = env::start_info().fdt_addr().unwrap().get();
186		let fdt_end = fdt_start + fdt.total_size();
187		let fdt_region = PageRange::containing(fdt_start, fdt_end).unwrap();
188		reserve(fdt_region);
189	}
190
191	let frame_ranges = PHYSICAL_FREE_LIST.lock().iter().collect::<Vec<_>>();
192
193	for frame_range in frame_ranges {
194		#[cfg(feature = "hermit-entry")]
195		unsafe {
196			map_frame_range(frame_range);
197		}
198		debug!("Claimed physical memory: {frame_range:#x?}");
199	}
200
201	TOTAL_MEMORY.store(PHYSICAL_FREE_LIST.lock().free_space(), Ordering::Relaxed);
202}
203
204unsafe fn init() {
205	if cfg!(target_arch = "x86_64") && DeviceAlloc.phys_offset() != VirtAddr::zero() {
206		let start = DeviceAlloc.phys_offset();
207		let count = DeviceAlloc.phys_offset().as_u64() / HugePageSize::SIZE;
208		let count = usize::try_from(count).unwrap();
209		paging::unmap::<HugePageSize>(start, count);
210	}
211
212	unsafe {
213		detect_from_start_info();
214	}
215}