Skip to main content

hermit/mm/
mod.rs

1//! Memory management.
2//!
3//! This is an overview of Hermit's memory layout:
4//!
5//! - `DeviceAlloc.device_offset` is 0 if `!cfg!(careful)`
6//! - User space virtual memory is only used if `!cfg!(feature = "common-os")`
7//! - On x86-64, PCI BARs, I/O APICs, and local APICs may be in `0xc0000000..0xffffffff`, which could be inside of `MEM`.
8//!
9//! ```text
10//!                               Virtual address
11//!                                    space
12//!
13//!                                 ...┌───┬──► 00000000
14//!           Physical address   ...   │   │
15//!                space      ...      │   │ Identity map
16//!                        ...         │   │
17//!    00000000 ◄──┬───┐...         ...├───┼──► mem_size
18//!                │   │   ...   ...   │   │
19//!     FrameAlloc │MEM│      ...      │   │ Unused
20//!                │   │   ...   ...   │   │
21//!    mem_size ◄──┼───┤...         ...├───┼──► DeviceAlloc.phys_offset
22//!                │   │   ...         │   │
23//!                │   │      ...      │   │ DeviceAlloc
24//!                │   │         ...   │   │
25//!          Empty │   │            ...├───┼──► DeviceAlloc.phys_offset + mem_size
26//!                │   │               │   │
27//!                │   │               │   │
28//!                │   │               │   │ Unused
29//!     Unknown ◄──┼───┤               │   │
30//!                │   │               │   │
31//!            PCI │   │               ├───┼──► kernel_virt_start
32//!                │   │               │   │
33//!     Unknown ◄──┼───┤               │   │ PageAlloc
34//!                │   │               │   │
35//!                │   │               ├───┼──► kernel_virt_end
36//!                │   │               │   │
37//!          Empty │   │               │   │
38//!                │   │               │   │ User space
39//!                │   │               │   │
40//!                │   │               │   │
41//! ```
42
43pub(crate) mod device_alloc;
44mod page_range_alloc;
45mod physicalmem;
46mod virtualmem;
47
48use core::alloc::Layout;
49use core::mem::MaybeUninit;
50use core::ops::Range;
51
52use align_address::Align;
53use free_list::{PageLayout, PageRange};
54use hermit_sync::{Lazy, RawInterruptTicketMutex};
55pub use memory_addresses::{PhysAddr, VirtAddr};
56#[cfg(target_os = "none")]
57use talc::TalcLock;
58#[cfg(target_os = "none")]
59use talc::source::Manual;
60
61pub use self::page_range_alloc::{PageRangeAllocator, PageRangeBox};
62pub use self::physicalmem::{FrameAlloc, FrameBox};
63pub use self::virtualmem::{PageAlloc, PageBox};
64#[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))]
65use crate::arch::mm::paging::HugePageSize;
66pub use crate::arch::mm::paging::virtual_to_physical;
67use crate::arch::mm::paging::{BasePageSize, LargePageSize, PageSize};
68use crate::{arch, env};
69
70#[cfg(target_os = "none")]
71#[global_allocator]
72pub(crate) static ALLOCATOR: TalcLock<RawInterruptTicketMutex, Manual> = TalcLock::new(Manual);
73
74/// Physical and virtual address range of the 2 MiB pages that map the kernel.
75static KERNEL_ADDR_RANGE: Lazy<Range<VirtAddr>> = Lazy::new(|| {
76	if cfg!(target_os = "none") {
77		// Calculate the start and end addresses of the 2 MiB page(s) that map the kernel.
78		let start = VirtAddr::from_ptr(elf_symbols::executable_start());
79		let end = VirtAddr::from_ptr(elf_symbols::executable_end());
80		start.align_down(LargePageSize::SIZE)..end.align_up(LargePageSize::SIZE)
81	} else {
82		VirtAddr::zero()..VirtAddr::zero()
83	}
84});
85
86pub(crate) fn kernel_start_address() -> VirtAddr {
87	KERNEL_ADDR_RANGE.start
88}
89
90pub(crate) fn kernel_end_address() -> VirtAddr {
91	KERNEL_ADDR_RANGE.end
92}
93
94#[cfg(target_os = "none")]
95pub(crate) fn claim_initial_heap() {
96	#[repr(C, align(0x1000))]
97	struct InitialHeap([MaybeUninit<u8>; 0x1000]);
98
99	debug_assert_eq!(
100		Layout::new::<InitialHeap>(),
101		Layout::from_size_align(0x1000, 0x1000).unwrap()
102	);
103
104	static mut INITIAL_HEAP: InitialHeap = InitialHeap([MaybeUninit::uninit(); _]);
105
106	let base = (&raw mut INITIAL_HEAP).cast::<u8>();
107	let size = size_of::<InitialHeap>();
108	unsafe {
109		ALLOCATOR.lock().claim(base, size).unwrap();
110	}
111}
112
113#[cfg(target_os = "none")]
114pub(crate) fn init() {
115	use crate::arch::mm::paging;
116
117	Lazy::force(&KERNEL_ADDR_RANGE);
118
119	unsafe {
120		arch::mm::init();
121	}
122
123	let total_mem = physicalmem::total_memory_size();
124	let kernel_addr_range = KERNEL_ADDR_RANGE.clone();
125	info!("Total memory size: {} MiB", total_mem >> 20);
126	info!(
127		"Kernel region: {:p}..{:p}",
128		kernel_addr_range.start, kernel_addr_range.end
129	);
130
131	// we reserve physical memory for the required page tables
132	// In worst case, we use page size of BasePageSize::SIZE
133	let npages = total_mem / BasePageSize::SIZE as usize;
134	let npage_div = BasePageSize::SIZE as usize / align_of::<usize>();
135	let npage_3tables = npages / npage_div + 1;
136	let npage_2tables = npage_3tables / npage_div + 1;
137	let npage_1tables = npage_2tables / npage_div + 1;
138	let reserved_space = (npage_3tables + npage_2tables + npage_1tables)
139		* BasePageSize::SIZE as usize
140		+ 2 * LargePageSize::SIZE as usize;
141	#[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))]
142	let has_1gib_pages = arch::kernel::processor::supports_1gib_pages();
143	let has_2mib_pages = arch::kernel::processor::supports_2mib_pages();
144
145	let min_mem = if env::is_uefi() {
146		// On UEFI, the given memory is guaranteed free memory and the kernel is located before the given memory
147		reserved_space
148	} else {
149		(kernel_addr_range.end.as_u64() - env::get_ram_address().unwrap().as_u64()
150			+ reserved_space as u64) as usize
151	};
152	info!("Minimum memory size: {} MiB", min_mem >> 20);
153	let avail_mem = total_mem
154		.checked_sub(min_mem)
155		.unwrap_or_else(|| panic!("Not enough memory available!"))
156		.align_down(LargePageSize::SIZE as usize);
157
158	let mut map_addr;
159	let mut map_size;
160	let heap_start_addr;
161
162	#[cfg(feature = "common-os")]
163	{
164		info!("Using Hermit as common OS!");
165
166		// we reserve at least 75% of the memory for the user space
167		let reserve: usize = (avail_mem * 75) / 100;
168		// 64 MB is enough as kernel heap
169		let reserve = core::cmp::min(reserve, 0x0400_0000);
170
171		let virt_size: usize = reserve.align_down(LargePageSize::SIZE as usize);
172		let layout = PageLayout::from_size_align(virt_size, LargePageSize::SIZE as usize).unwrap();
173		let page_range = PageAlloc::allocate(layout).unwrap();
174		let virt_addr = VirtAddr::from(page_range.start());
175		heap_start_addr = virt_addr;
176
177		info!(
178			"Heap: size {} MB, start address {:p}",
179			virt_size >> 20,
180			virt_addr
181		);
182
183		#[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))]
184		if has_1gib_pages && virt_size > HugePageSize::SIZE as usize {
185			// Mount large pages to the next huge page boundary
186			let npages = (virt_addr.align_up(HugePageSize::SIZE) - virt_addr) as usize
187				/ LargePageSize::SIZE as usize;
188			if let Err(n) = paging::map_heap::<LargePageSize>(virt_addr, npages) {
189				map_addr = virt_addr + n as u64 * LargePageSize::SIZE;
190				map_size = virt_size - (map_addr - virt_addr) as usize;
191			} else {
192				map_addr = virt_addr.align_up(HugePageSize::SIZE);
193				map_size = virt_size - (map_addr - virt_addr) as usize;
194			}
195		} else {
196			map_addr = virt_addr;
197			map_size = virt_size;
198		}
199
200		#[cfg(not(any(target_arch = "x86_64", target_arch = "riscv64")))]
201		{
202			map_addr = virt_addr;
203			map_size = virt_size;
204		}
205	}
206
207	#[cfg(not(feature = "common-os"))]
208	{
209		// we reserve 10% of the memory for stack allocations
210		#[cfg(not(feature = "mman"))]
211		let stack_reserve: usize = (avail_mem * 10) / 100;
212
213		// At first, we map only a small part into the heap.
214		// Afterwards, we already use the heap and map the rest into
215		// the virtual address space.
216
217		#[cfg(not(feature = "mman"))]
218		let virt_size: usize = (avail_mem - stack_reserve).align_down(LargePageSize::SIZE as usize);
219		#[cfg(feature = "mman")]
220		let virt_size: usize = ((avail_mem * 75) / 100).align_down(LargePageSize::SIZE as usize);
221
222		let layout = PageLayout::from_size_align(virt_size, LargePageSize::SIZE as usize).unwrap();
223		let page_range = PageAlloc::allocate(layout).unwrap();
224		let virt_addr = VirtAddr::from(page_range.start());
225		heap_start_addr = virt_addr;
226
227		info!(
228			"Heap: size {} MB, start address {:p}",
229			virt_size >> 20,
230			virt_addr
231		);
232
233		#[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))]
234		if has_1gib_pages && virt_size > HugePageSize::SIZE as usize {
235			// Mount large pages to the next huge page boundary
236			let npages = (virt_addr.align_up(HugePageSize::SIZE) - virt_addr) / LargePageSize::SIZE;
237			if let Err(n) = paging::map_heap::<LargePageSize>(virt_addr, npages as usize) {
238				map_addr = virt_addr + n as u64 * LargePageSize::SIZE;
239				map_size = virt_size - (map_addr - virt_addr) as usize;
240			} else {
241				map_addr = virt_addr.align_up(HugePageSize::SIZE);
242				map_size = virt_size - (map_addr - virt_addr) as usize;
243			}
244		} else {
245			map_addr = virt_addr;
246			map_size = virt_size;
247		}
248
249		#[cfg(not(any(target_arch = "x86_64", target_arch = "riscv64")))]
250		{
251			map_addr = virt_addr;
252			map_size = virt_size;
253		}
254	}
255
256	#[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))]
257	if has_1gib_pages
258		&& map_size > HugePageSize::SIZE as usize
259		&& map_addr.is_aligned_to(HugePageSize::SIZE)
260	{
261		let size = map_size.align_down(HugePageSize::SIZE as usize);
262		if let Err(num_pages) =
263			paging::map_heap::<HugePageSize>(map_addr, size / HugePageSize::SIZE as usize)
264		{
265			map_size -= num_pages * HugePageSize::SIZE as usize;
266			map_addr += num_pages as u64 * HugePageSize::SIZE;
267		} else {
268			map_size -= size;
269			map_addr += size;
270		}
271	}
272
273	if has_2mib_pages
274		&& map_size > LargePageSize::SIZE as usize
275		&& map_addr.is_aligned_to(LargePageSize::SIZE)
276	{
277		let size = map_size.align_down(LargePageSize::SIZE as usize);
278		if let Err(num_pages) =
279			paging::map_heap::<LargePageSize>(map_addr, size / LargePageSize::SIZE as usize)
280		{
281			map_size -= num_pages * LargePageSize::SIZE as usize;
282			map_addr += num_pages as u64 * LargePageSize::SIZE;
283		} else {
284			map_size -= size;
285			map_addr += size;
286		}
287	}
288
289	if map_size > BasePageSize::SIZE as usize && map_addr.is_aligned_to(BasePageSize::SIZE) {
290		let size = map_size.align_down(BasePageSize::SIZE as usize);
291		if let Err(num_pages) =
292			paging::map_heap::<BasePageSize>(map_addr, size / BasePageSize::SIZE as usize)
293		{
294			map_size -= num_pages * BasePageSize::SIZE as usize;
295			map_addr += num_pages as u64 * BasePageSize::SIZE;
296		} else {
297			map_size -= size;
298			map_addr += size;
299		}
300	}
301
302	let heap_end_addr = map_addr;
303
304	let size = heap_end_addr.as_usize() - heap_start_addr.as_usize();
305	unsafe {
306		ALLOCATOR
307			.lock()
308			.claim(heap_start_addr.as_mut_ptr(), size)
309			.unwrap();
310	}
311
312	info!("Heap is located at {heap_start_addr:p}..{heap_end_addr:p} ({map_size} Bytes unmapped)");
313}
314
315pub(crate) fn print_information() {
316	info!("{FrameAlloc}");
317	info!("{PageAlloc}");
318}
319
320/// Maps a given physical address and size in virtual space and returns address.
321#[cfg(feature = "pci")]
322pub(crate) fn map(
323	physical_address: PhysAddr,
324	size: usize,
325	writable: bool,
326	no_execution: bool,
327	no_cache: bool,
328) -> VirtAddr {
329	use crate::arch::mm::paging::PageTableEntryFlags;
330	#[cfg(target_arch = "x86_64")]
331	use crate::arch::mm::paging::PageTableEntryFlagsExt;
332
333	let size = size.align_up(BasePageSize::SIZE as usize);
334	let count = size / BasePageSize::SIZE as usize;
335
336	let mut flags = PageTableEntryFlags::empty();
337	flags.normal();
338	if writable {
339		flags.writable();
340	}
341	if no_execution {
342		flags.execute_disable();
343	}
344	if no_cache {
345		flags.device();
346	}
347
348	let layout = PageLayout::from_size(size).unwrap();
349	let page_range = PageAlloc::allocate(layout).unwrap();
350	let virtual_address = VirtAddr::from(page_range.start());
351	arch::mm::paging::map::<BasePageSize>(virtual_address, physical_address, count, flags);
352
353	virtual_address
354}
355
356#[allow(dead_code)]
357/// Unmaps virtual address, without 'freeing' physical memory it is mapped to!
358pub(crate) fn unmap(virtual_address: VirtAddr, size: usize) {
359	let size = size.align_up(BasePageSize::SIZE as usize);
360
361	if virtual_to_physical(virtual_address).is_some() {
362		arch::mm::paging::unmap::<BasePageSize>(
363			virtual_address,
364			size / BasePageSize::SIZE as usize,
365		);
366
367		let range = PageRange::from_start_len(virtual_address.as_usize(), size).unwrap();
368		unsafe {
369			PageAlloc::deallocate(range);
370		}
371	} else {
372		panic!("No page table entry for virtual address {virtual_address:p}");
373	}
374}