Skip to main content

rustubs/
mm.rs

1//! memory management unit
2
3pub mod allocator;
4pub mod vmm;
5
6use crate::arch::x86_64::paging::{get_root, Pagetable};
7use crate::defs::*;
8use crate::machine::multiboot;
9use crate::mm::allocator::buddy::BuddyAllocator;
10use crate::mm::allocator::IRQLockedHeap;
11use crate::proc::sync::spin::SpinMutexIRQ;
12use alloc::alloc::{alloc, alloc_zeroed, dealloc, Layout};
13use alloc::vec::Vec;
14use core::arch::asm;
15use core::ops::{Deref, Range};
16use lazy_static::lazy_static;
17
18#[global_allocator]
19static ALLOCATOR: IRQLockedHeap = IRQLockedHeap::empty();
20
21// ZONE is made global only for the ease of initialization. One must NOT modify
22// the zone (or its members) via the global reference. Use a locked Buddy
23// instance only!
24// static ZONE: Ether<allocator::buddy::Zone> =
25// 	Ether::new(allocator::buddy::Zone::default());
26
27// PFA MUST BE INITIALIZED BEFORE USE!
28static PFA: SpinMutexIRQ<BuddyAllocator> =
29	SpinMutexIRQ::new(BuddyAllocator::default());
30
31lazy_static! {
32	pub static ref KSTACK_ALLOCATOR: SpinMutexIRQ<KStackAllocator> =
33		SpinMutexIRQ::new(KStackAllocator::new());
34}
35
36/// half measure: simply initialize the linkedlist allocator
37pub fn init() {
38	let mbi = multiboot::get_mb_info().unwrap();
39	let mmapinfo = unsafe { mbi.get_mmap() }.unwrap();
40	let buf_start = mmapinfo.mmap_addr;
41	let buf_len = mmapinfo.mmap_length;
42	let buf_end = buf_start + buf_len;
43	let mut curr = buf_start as u64;
44	// initialize the heap allocator with the largest physical memory block
45	let mut largest_phy_range: Option<Range<u64>> = None;
46	loop {
47		if curr >= buf_end as u64 {
48			break;
49		}
50		let mblock = unsafe { &*(curr as *const multiboot::MultibootMmap) };
51		curr += mblock.size as u64;
52		curr += 4;
53		if mblock.mtype != multiboot::MultibootMmap::MTYPE_RAM {
54			continue;
55		}
56		if mblock.get_end() <= ExternSyms::KERNEL_PM_START as u64 {
57			continue;
58		}
59		let mut r = mblock.get_range();
60		if r.contains(&(ExternSyms::KERNEL_PM_END as u64)) {
61			assert!(
62				r.contains(&(ExternSyms::KERNEL_PM_START as u64)),
63				"FATAL: kernel physical map cross physical blocks, how?"
64			);
65			r.start = ExternSyms::KERNEL_PM_END as u64;
66		}
67		// TODO this is pretty ugly...
68		match largest_phy_range {
69			None => largest_phy_range = Some(r),
70			Some(ref lr) => {
71				if (r.end - r.start) > (lr.end - lr.start) {
72					largest_phy_range = Some(r);
73				}
74			}
75		}
76	}
77
78	// HALF MEASURE: we currently require MIN_PHY_MEM (aligned) for both heap
79	// allocator and frame allocator. This will be gone as soon as the
80	// two-layered kernel memory management is complete (TODO)
81	let pr = &largest_phy_range.expect("Can't find usable physical block");
82	let prange_pfa = pr.start..pr.start + 2 * Mem::MIN_PHY_MEM;
83	let prange_heap = pr.start + Mem::MIN_PHY_MEM..pr.end;
84	// initialize the page frame allocator
85	unsafe { PFA.lock().init(prange_pfa.start, prange_pfa.end) };
86	println!("[mm] PFA initialized: {:X?}", PFA.lock().deref());
87
88	// init heap allocator on id map
89	unsafe {
90		ALLOCATOR.lock().init(
91			P2V(prange_heap.start).unwrap() as *mut u8,
92			(prange_heap.end - prange_heap.start) as usize,
93		);
94	}
95	println!(
96		"[init] mm: heap alloc initialized @ {:#X} - {:#X}",
97		P2V(prange_heap.start).unwrap(),
98		P2V(prange_heap.end).unwrap()
99	);
100}
101
102/// wrapper around the global allocator with caching
103pub struct KStackAllocator {
104	pool: Vec<u64>,
105}
106
107/// TODO: the heap allocator is primitive atm and it may fail to allocate new
108/// kernel stack (64K here) due to fragmentation. It may be a good idea to
109/// reserve some memory during system init to guarantee that we can at least
110impl KStackAllocator {
111	const KSTACK_ALLOC_POOL_CAP: usize = 16;
112	const KSTACK_LAYOUT: Layout = unsafe {
113		Layout::from_size_align_unchecked(
114			Mem::KERNEL_STACK_SIZE as usize,
115			Mem::KERNEL_STACK_SIZE as usize,
116		)
117	};
118
119	pub fn new() -> Self {
120		let p = Vec::with_capacity(Self::KSTACK_ALLOC_POOL_CAP);
121		Self { pool: p }
122	}
123
124	/// unsafe because this may fail (same as populate)
125	pub unsafe fn allocate(&mut self) -> u64 {
126		if let Some(addr) = self.pool.pop() {
127			return addr;
128		} else {
129			return alloc(Self::KSTACK_LAYOUT) as u64;
130		}
131	}
132
133	/// unsafe because you must make sure you give back something the allocator gave
134	/// you. Otherwise you break the kernel heap allocator.
135	pub unsafe fn free(&mut self, addr: u64) {
136		if self.pool.len() < Self::KSTACK_ALLOC_POOL_CAP {
137			self.pool.push(addr);
138		} else {
139			dealloc(addr as *mut u8, Self::KSTACK_LAYOUT);
140		}
141	}
142
143	/// unsafe because this could OOM if you stress the allocator too much
144	/// (although unlikely)
145	pub unsafe fn populate(&mut self) {
146		for _ in 0..Self::KSTACK_ALLOC_POOL_CAP {
147			self.pool.push(alloc(Self::KSTACK_LAYOUT) as u64);
148		}
149	}
150}
151
152const LAYOUT_4K_ALIGNED: Layout =
153	unsafe { Layout::from_size_align_unchecked(0x1000, 0x1000) };
154
155/// allocate 4k aligned (virtual) memory.
156/// TODO create a buffer (like in KStackAllocator) for performance.
157pub fn allocate_4k() -> u64 {
158	return unsafe { alloc(LAYOUT_4K_ALIGNED) } as u64;
159}
160pub fn allocate_4k_zeroed() -> u64 {
161	return unsafe { alloc_zeroed(LAYOUT_4K_ALIGNED) } as u64;
162}
163
164// frame allocations always return physical address
165pub fn allocate_frame_4k() -> Option<u64> {
166	let f = PFA.lock().alloc_frame(0)?;
167	Some(f.get_paddr())
168}
169
170// frame allocations always return physical address
171pub fn allocate_frame_4k_zeroed() -> Option<u64> {
172	if let Some(pa) = allocate_frame_4k() {
173		let va = P2V(pa).unwrap();
174		unsafe {
175			core::slice::from_raw_parts_mut(
176				va as *mut u8,
177				Mem::PAGE_SIZE as usize,
178			)
179			.fill(0)
180		};
181		Some(pa)
182	} else {
183		None
184	}
185}
186
187// one must make sure that the to-be-freed frame is valid, which was acquired
188// via the same frame allocator. Otherwise it may corrupt the PFA's internal
189// structure.
190pub unsafe fn free_frame_4k(paddr: u64) {
191	PFA.lock().free_frame_by_pa(paddr, 0);
192}
193
194pub fn buddy_info() { PFA.lock().print_debug_info(); }
195
196/// invalidate a single page mapping in tlb
197pub fn invlpg(va: u64) { unsafe { asm!("invlpg [{0}]", in(reg) va) }; }
198
199/// flush the whole tlb
200pub fn flush_tlb() {
201	unsafe {
202		asm!(
203			"
204		push rax;
205		mov rax, cr3;
206		mov cr3, rax;
207		pop rax;
208		"
209		)
210	}
211}
212
213/// drop the low memory mapping from the current pagetable by removing the first
214/// entry from pml4 table (which mapps to 0~512G). The PDP table is unchanged,
215/// wasting 4K of memory but there is nothing we can do now since the heap
216/// allocator doesn't manage this address.
217///
218/// after calling this function, the system can no longer directly access memory
219/// by physical address
220pub unsafe fn drop_init_mapping() {
221	let pt: &mut Pagetable = unsafe { &mut *(get_root() as *mut Pagetable) };
222	pt.entries[0].set_unused();
223	flush_tlb();
224}