Skip to main content

hermit/mm/
physicalmem.rs

1use core::alloc::AllocError;
2use core::fmt;
3use core::sync::atomic::{AtomicUsize, Ordering};
4
5use align_address::Align;
6use free_list::{FreeList, PageLayout, PageRange};
7use hermit_sync::InterruptTicketMutex;
8use memory_addresses::VirtAddr;
9
10#[cfg(all(target_arch = "x86_64", feature = "hermit-entry"))]
11use crate::arch::mm::paging::PageTableEntryFlagsExt;
12use crate::arch::mm::paging::{self, HugePageSize, LargePageSize, PageSize};
13use crate::env::{self, MemmapType, StartInfo};
14use crate::mm::device_alloc::DeviceAlloc;
15use crate::mm::{PageRangeAllocator, PageRangeBox};
16use crate::page_range_ext::PageRangeExt;
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		#[cfg(feature = "hermit-entry")]
150		unsafe {
151			map_frame_range(range);
152		}
153		TOTAL_MEMORY.fetch_add(range.len().get(), Ordering::Relaxed);
154		debug!("Claimed physical memory: {range:#x?}");
155	}
156
157	let reserve = |reservation: PageRange| {
158		debug!("Memory reservation: {reservation:#x?}");
159		// While there are still overlaps between this reservation and any available ranges,
160		// allocate that overlap to mark it as not available.
161		while let Ok(reserved) = PHYSICAL_FREE_LIST
162			.lock()
163			.allocate_with(|range| reservation.and(range))
164		{
165			debug!("Reserved {reserved:#x?}");
166		}
167	};
168
169	let kernel_start = elf_symbols::executable_start().addr();
170	let kernel_end = elf_symbols::executable_end().addr();
171	let kernel_region = PageRange::containing(kernel_start, kernel_end).unwrap();
172	reserve(kernel_region);
173
174	for module in env::start_info().modules() {
175		reserve(module.phys_frame_range());
176	}
177
178	#[cfg(feature = "hermit-entry")]
179	{
180		use crate::env::FdtStartInfo;
181
182		let fdt = env::start_info().fdt().unwrap();
183
184		for reservation in fdt.memory_reservations() {
185			let start = reservation.address().addr();
186			let end = start + reservation.size();
187			let reservation = PageRange::new(start, end).unwrap();
188			reserve(reservation);
189		}
190
191		let fdt_start = env::start_info().fdt_addr().unwrap().get();
192		let fdt_end = fdt_start + fdt.total_size();
193		let fdt_region = PageRange::containing(fdt_start, fdt_end).unwrap();
194		reserve(fdt_region);
195	}
196}
197
198unsafe fn init() {
199	if cfg!(target_arch = "x86_64") && DeviceAlloc.phys_offset() != VirtAddr::zero() {
200		let start = DeviceAlloc.phys_offset();
201		let count = DeviceAlloc.phys_offset().as_u64() / HugePageSize::SIZE;
202		let count = usize::try_from(count).unwrap();
203		paging::unmap::<HugePageSize>(start, count);
204	}
205
206	unsafe {
207		detect_from_start_info();
208	}
209}