keos/mm/
mod.rs

1//! Memory Management.
2//!
3//! This module implements functionality for memory management operations such
4//! as allocating and deallocating memory. The core abstraction is the [`Page`],
5//! which represents a single memory page.
6//!
7//! Memory allocation and deallocation in KeOS is closely tied to Rust's
8//! ownership and lifetime system: A page is allocated by creating an instance
9//! of the [`Page`] struct. Once the [`Page`] instance is dropped, the page is
10//! automatically freed, ensuring proper memory management and preventing memory
11//! leaks.
12pub mod page_table;
13pub mod tlb;
14
15use crate::addressing::{Kva, PAGE_MASK, PAGE_SHIFT, Pa};
16use abyss::{boot::Regions, spinlock::SpinLock};
17use alloc::vec::Vec;
18use core::{
19    ops::Range,
20    sync::atomic::{AtomicU64, Ordering},
21};
22
23/// A reference of a memory page.
24///
25/// `PageRef` represents a borrowed reference to a kernel virtual address
26/// (`Kva`) that maps to a memory.
27///
28/// # Usage
29/// This struct is useful for safely accessing mapped kernel pages without
30/// requiring ownership transfers. The lifetime parameter `'a` ensures that
31/// the reference does not outlive the memory it points to.
32pub struct PageRef<'a> {
33    kva: Kva,
34    _lt: core::marker::PhantomData<&'a ()>,
35}
36
37impl PageRef<'_> {
38    /// Build a page refernce from physical address.
39    ///
40    /// # Safety
41    /// [`Pa`] must be held by a other object.
42    pub unsafe fn from_pa(pa: Pa) -> Self {
43        PageRef {
44            kva: pa.into_kva(),
45            _lt: core::marker::PhantomData,
46        }
47    }
48
49    ///  Increase the page reference count corresponding to.
50    pub fn into_page(&self) -> Page {
51        let page = unsafe { Page::from_pa(self.kva.into_pa()) };
52        Page::into_raw(page.clone());
53        page
54    }
55
56    /// Get the kernel virtual address of this page.
57    ///
58    /// # Returns
59    /// - The kernel virtual address ([`Kva`]) of the page.
60    ///
61    /// ## Example:
62    /// ```rust
63    /// let kva = page.kva(); // Get the kernel virtual address
64    /// ```
65    #[inline]
66    pub fn kva(&self) -> Kva {
67        self.kva
68    }
69
70    /// Get the physical address of this page.
71    ///
72    /// # Returns
73    /// - The physical address ([`Pa`]) of the page.
74    ///
75    /// ## Example:
76    /// ```rust
77    /// let pa = page.pa(); // Get the physical address
78    /// ```
79    #[inline]
80    pub fn pa(&self) -> Pa {
81        self.kva.into_pa()
82    }
83
84    /// Get a reference to the underlying slice of the page (read-only).
85    ///
86    /// This method allows access to the contents of the page as a byte slice.
87    /// The caller can read from the page's memory, but cannot modify it.
88    ///
89    /// # Returns
90    /// - A reference to the byte slice representing the contents of the page.
91    ///
92    /// ## Example:
93    /// ```rust
94    /// let slice = page.inner(); // Get read-only access to the page's content
95    /// ```
96    pub fn inner(&self) -> &[u8] {
97        unsafe { core::slice::from_raw_parts(self.kva().into_usize() as *const u8, 4096) }
98    }
99
100    /// Get a mutable reference to the underlying slice of the page.
101    ///
102    /// This method allows modification of the contents of the page as a byte
103    /// slice. The caller can read from and write to the page's memory.
104    ///
105    /// # Returns
106    /// - A mutable reference to the byte slice representing the contents of the
107    ///   page.
108    ///
109    /// ## Example:
110    /// ```rust
111    /// let slice = page.inner_mut();
112    /// ```
113    pub fn inner_mut(&mut self) -> &mut [u8] {
114        unsafe { core::slice::from_raw_parts_mut(self.kva().into_usize() as *mut u8, 4096) }
115    }
116}
117
118/// A representation of a memory page.
119///
120/// The [`Page`] struct encapsulates a single memory page, providing methods to
121/// allocate, access, and manipulate the underlying page's contents.
122///
123/// This page internally holds the reference counts. This counter increases on a
124/// calling of [`Page::clone`], and decreases when the page instance is dropped.
125///
126/// ## Example:
127/// ```
128/// let page = Page::new().unwrap();
129/// let va = page.va();  // Get the virtual address of the page.
130/// let pa = page.pa();  // Get the physical address of the page.
131/// ```
132#[derive(Clone)]
133pub struct Page {
134    inner: ContigPages,
135}
136
137impl Default for Page {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143impl Page {
144    /// Allocate a new page.
145    ///
146    /// This function allocates a new memory page.
147    #[inline]
148    #[track_caller]
149    pub fn new() -> Self {
150        let loc = core::panic::Location::caller();
151        ContigPages::new(0x1000)
152            .map(|inner| Self { inner })
153            .inspect(|pg| {
154                crate::thread::with_current(|th| {
155                    let mut guard = th.allocations.lock();
156                    if let Some(alloc) = &mut *guard {
157                        assert!(alloc.insert(pg.kva(), loc).is_none())
158                    }
159                    guard.unlock();
160                });
161            })
162            .expect("Failed to allocate page.")
163    }
164
165    /// Get the kernel virtual address of this page.
166    ///
167    /// # Returns
168    /// - The kernel virtual address ([`Kva`]) of the page.
169    #[inline]
170    pub fn kva(&self) -> Kva {
171        self.inner.kva
172    }
173
174    /// Get the physical address of this page.
175    ///
176    /// # Returns
177    /// - The physical address ([`Pa`]) of the page.
178    #[inline]
179    pub fn pa(&self) -> Pa {
180        self.inner.kva.into_pa()
181    }
182
183    /// Consumes the page, returning its physical address.
184    ///
185    /// This method "consumes" the [`Page`] and returns its physical address.
186    /// After calling this function, the caller is responsible for managing
187    /// the memory previously associated with the page. It is important to
188    /// properly release the page, which can be done using [`Page::from_pa`].
189    ///
190    /// # Returns
191    /// - The physical address ([`Pa`]) of the page.
192    #[inline]
193    pub fn into_raw(self) -> Pa {
194        core::mem::ManuallyDrop::new(self).pa()
195    }
196
197    /// Constructs a page from a given physical address.
198    ///
199    /// This method reconstructs a [`Page`] from a physical address ([`Pa`]). It
200    /// should be used only after consuming a [`Page`] with
201    /// [`Page::into_raw`]. The physical address passed must be valid.
202    ///
203    /// # Safety
204    /// - This function is unsafe because incorrect usage could result in memory
205    ///   issues such as a double-free.
206    /// - Ensure that the physical address passed is valid and is being used
207    ///   correctly.
208    ///
209    /// # Arguments
210    /// - `pa`: The physical address of the page.
211    ///
212    /// # Returns
213    /// - A [`Page`] reconstructed from the physical address.
214    #[inline]
215    pub unsafe fn from_pa(pa: Pa) -> Self {
216        Page {
217            inner: unsafe { ContigPages::from_va(pa.into_kva(), 0x1000) },
218        }
219    }
220
221    /// Get a reference to the underlying slice of the page (read-only).
222    ///
223    /// This method allows access to the contents of the page as a byte slice.
224    /// The caller can read from the page's memory, but cannot modify it.
225    ///
226    /// # Returns
227    /// - A reference to the byte slice representing the contents of the page.
228    pub fn inner(&self) -> &[u8] {
229        unsafe { core::slice::from_raw_parts(self.kva().into_usize() as *const u8, 4096) }
230    }
231
232    /// Get a mutable reference to the underlying slice of the page.
233    ///
234    /// This method allows modification of the contents of the page as a byte
235    /// slice. The caller can read from and write to the page's memory.
236    ///
237    /// # Returns
238    /// - A mutable reference to the byte slice representing the contents of the
239    ///   page.
240    pub fn inner_mut(&mut self) -> &mut [u8] {
241        unsafe { core::slice::from_raw_parts_mut(self.kva().into_usize() as *mut u8, 4096) }
242    }
243}
244
245impl Drop for Page {
246    fn drop(&mut self) {
247        crate::thread::with_current(|th| {
248            let mut guard = th.allocations.lock();
249            if let Some(alloc) = &mut *guard {
250                assert!(alloc.remove(&self.kva()).is_some());
251            }
252            guard.unlock();
253        });
254    }
255}
256
257struct BytePP(usize);
258impl core::fmt::Display for BytePP {
259    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
260        if self.0 > 16 * 1024 * 1024 * 1024 {
261            write!(f, "{} GiB", self.0 / 1024 / 1024 / 1024)
262        } else if self.0 > 16 * 1024 * 1024 {
263            write!(f, "{} MiB", self.0 / 1024 / 1024)
264        } else if self.0 > 16 * 1024 {
265            write!(f, "{} KiB", self.0 / 1024)
266        } else {
267            write!(f, "{} B", self.0)
268        }
269    }
270}
271
272/// Initialize the physical memory allocator.
273#[doc(hidden)]
274pub unsafe fn init_mm(regions: Regions) {
275    unsafe {
276        unsafe extern "C" {
277            static __edata_end: u64;
278        }
279
280        let edata_end = Kva::new(&__edata_end as *const _ as usize).unwrap();
281
282        for region in regions.iter() {
283            if region.usable {
284                let Range { start, end } = region.addr;
285                let (start, end) = (start.into_kva().max(edata_end), end.into_kva());
286                if start < end {
287                    info!(
288                        "    Usable: 0x{:016x}~0x{:016x} ({})",
289                        start.into_usize(),
290                        end.into_usize(),
291                        BytePP(end.into_usize() - start.into_usize())
292                    );
293                    let mut allocator = PALLOC.lock();
294                    allocator.foster(start, end);
295                    allocator.unlock();
296                }
297            }
298        }
299    }
300}
301
302// Physical memory allocators.
303struct Arena {
304    start: Kva,
305    end: Kva,
306    // 0: used, 1: unused
307    bitmap: &'static mut [u64],
308    ref_cnts: &'static [AtomicU64],
309}
310
311impl Arena {
312    const EMPTY: Option<Self> = None;
313    fn set_used(&mut self, index: usize) {
314        let (pos, ofs) = (index / 64, index % 64);
315        debug_assert_ne!(self.bitmap[pos] & (1 << ofs), 0);
316        self.bitmap[pos] &= !(1 << ofs);
317        debug_assert_eq!(self.bitmap[pos] & (1 << ofs), 0);
318    }
319    fn set_unused(&mut self, index: usize) {
320        let (pos, ofs) = (index / 64, index % 64);
321        debug_assert_eq!(self.bitmap[pos] & (1 << ofs), 0);
322        self.bitmap[pos] |= 1 << ofs;
323        debug_assert_ne!(self.bitmap[pos] & (1 << ofs), 0);
324    }
325    fn alloc(&mut self, cnt: usize, align: usize) -> Option<(Kva, &'static AtomicU64)> {
326        let mut search = 0;
327        while search < self.bitmap.len() * 64 {
328            let (mut pos, ofs) = (search / 64, search % 64);
329            // search first qword that contains one.
330            if ofs % 64 == 0 {
331                while self.bitmap[pos] == 0 {
332                    pos += 1;
333                }
334                search = pos * 64;
335            }
336
337            let mut cont = 0;
338            if align != 0
339                && !((self.start.into_usize() >> PAGE_SHIFT) + search).is_multiple_of(align)
340            {
341                search += 1;
342            } else {
343                let start = search;
344                loop {
345                    // Found!
346                    if cont == cnt {
347                        for i in start..start + cnt {
348                            self.set_used(i);
349                        }
350                        let ref_cnt = &self.ref_cnts[start];
351                        debug_assert_eq!(
352                            ref_cnt.fetch_add(1, core::sync::atomic::Ordering::SeqCst),
353                            0
354                        );
355                        return Some((self.start + (start << PAGE_SHIFT), ref_cnt));
356                    }
357
358                    let (pos, ofs) = (search / 64, search % 64);
359                    search += 1;
360                    if self.bitmap[pos] & (1 << ofs) != 0 {
361                        // usable
362                        cont += 1;
363                    } else {
364                        break;
365                    }
366                }
367            }
368        }
369        None
370    }
371    fn dealloc(&mut self, va: Kva, cnt: usize) {
372        let ofs = (va.into_usize() - self.start.into_usize()) >> PAGE_SHIFT;
373        for i in ofs..ofs + cnt {
374            self.set_unused(i);
375        }
376    }
377    fn ref_cnt_for_va(&self, va: Kva) -> &'static AtomicU64 {
378        &self.ref_cnts[(va - self.start) >> PAGE_SHIFT]
379    }
380}
381
382struct PhysicalAllocator {
383    inner: [Option<Arena>; 8],
384    max_idx: usize,
385}
386
387static PALLOC: SpinLock<PhysicalAllocator> = SpinLock::new(PhysicalAllocator {
388    inner: [Arena::EMPTY; 8],
389    max_idx: 0,
390});
391
392impl PhysicalAllocator {
393    unsafe fn foster(&mut self, start: Kva, end: Kva) {
394        unsafe {
395            // Calculate usable page of this region.
396            let usable_pages = (end.into_usize() - start.into_usize()) >> PAGE_SHIFT;
397            let mut meta_end = start;
398            // Each region has alloc bitmap on first N pages.
399            let bitmap = core::slice::from_raw_parts_mut(
400                start.into_usize() as *mut u64,
401                usable_pages.div_ceil(64),
402            );
403            bitmap.fill(u64::MAX);
404            meta_end += 8 * bitmap.len();
405            // Array for reference counts are following to the bitmap.
406            core::slice::from_raw_parts_mut(meta_end.into_usize() as *mut u64, usable_pages)
407                .fill(0);
408            let ref_cnts = core::slice::from_raw_parts(
409                meta_end.into_usize() as *const AtomicU64,
410                usable_pages,
411            );
412            meta_end += 8 * ref_cnts.len();
413            meta_end = (meta_end + PAGE_MASK) & !PAGE_MASK;
414
415            let mut arena = Arena {
416                bitmap,
417                start,
418                end,
419                ref_cnts,
420            };
421            // Pad front.
422            for i in 0..(meta_end - start) >> PAGE_SHIFT {
423                arena.set_used(i);
424            }
425            // Pad back.
426            for i in usable_pages..((usable_pages + 63) & !63) {
427                arena.set_used(i);
428            }
429            self.inner[self.max_idx] = Some(arena);
430            self.max_idx += 1;
431        }
432    }
433}
434
435/// A contiguous pages representation.
436pub struct ContigPages {
437    arena_idx: usize,
438    kva: Kva,
439    cnt: usize,
440    ref_cnt: &'static AtomicU64,
441}
442
443impl Clone for ContigPages {
444    fn clone(&self) -> Self {
445        self.ref_cnt.fetch_add(1, Ordering::SeqCst);
446
447        Self {
448            arena_idx: self.arena_idx,
449            kva: self.kva,
450            cnt: self.cnt,
451            ref_cnt: self.ref_cnt,
452        }
453    }
454}
455
456impl ContigPages {
457    /// Allocate a page.
458    #[inline]
459    pub fn new(size: usize) -> Option<Self> {
460        Self::new_with_align(size, 0x1000)
461    }
462
463    /// Allocate a page with align
464    #[inline]
465    pub fn new_with_align(size: usize, align: usize) -> Option<Self> {
466        if size != 0 {
467            // align up to page size.
468            let cnt = (size + PAGE_MASK) >> PAGE_SHIFT;
469            let mut allocator = PALLOC.lock();
470            let max_idx = allocator.max_idx;
471            for (arena_idx, arena) in allocator.inner.iter_mut().take(max_idx).enumerate() {
472                if let Some((kva, ref_cnt)) =
473                    arena.as_mut().unwrap().alloc(cnt, align >> PAGE_SHIFT)
474                {
475                    unsafe {
476                        core::slice::from_raw_parts_mut(
477                            kva.into_usize() as *mut u64,
478                            cnt * 0x1000 / core::mem::size_of::<u64>(),
479                        )
480                        .fill(0);
481                    }
482                    allocator.unlock();
483                    return Some(Self {
484                        arena_idx,
485                        kva,
486                        cnt,
487                        ref_cnt,
488                    });
489                }
490            }
491        }
492        None
493    }
494
495    /// Get virtual address of this page.
496    #[inline]
497    pub fn kva(&self) -> Kva {
498        self.kva
499    }
500
501    /// Constructs a page from a kva.
502    ///
503    /// ## Safety
504    /// This function is unsafe because improper use may lead to memory
505    /// problems. For example, a double-free may occur if the function is called
506    /// twice on the same raw pointer.
507    #[inline]
508    pub unsafe fn from_va(kva: Kva, size: usize) -> Self {
509        let allocator = PALLOC.lock();
510        let arena_idx = allocator
511            .inner
512            .iter()
513            .take(allocator.max_idx)
514            .enumerate()
515            .find_map(|(idx, arena)| {
516                let Arena { start, end, .. } = arena.as_ref().unwrap();
517                if (*start..*end).contains(&kva) {
518                    Some(idx)
519                } else {
520                    None
521                }
522            })
523            .expect("Failed to find arena index.");
524        let page = ContigPages {
525            arena_idx,
526            kva,
527            cnt: size / 4096,
528            ref_cnt: allocator.inner[arena_idx]
529                .as_ref()
530                .unwrap()
531                .ref_cnt_for_va(kva),
532        };
533        allocator.unlock();
534        page
535    }
536
537    /// Split the ContigPages into multiple pages.
538    pub fn split(self) -> Vec<Page> {
539        let mut out = Vec::new();
540        assert_eq!(self.ref_cnt.load(Ordering::SeqCst), 1);
541        let this = core::mem::ManuallyDrop::new(self);
542        for i in 0..this.cnt {
543            let ref_cnt = unsafe {
544                &core::slice::from_raw_parts(this.ref_cnt as *const AtomicU64, this.cnt)[i]
545            };
546            if i != 0 {
547                assert_eq!(ref_cnt.fetch_add(1, Ordering::SeqCst), 1);
548            }
549            out.push(Page {
550                inner: ContigPages {
551                    arena_idx: this.arena_idx,
552                    kva: this.kva + i * 0x1000,
553                    cnt: 1,
554                    ref_cnt,
555                },
556            })
557        }
558        out
559    }
560}
561
562impl Drop for ContigPages {
563    fn drop(&mut self) {
564        if self.ref_cnt.fetch_sub(1, Ordering::SeqCst) == 1 {
565            let mut allocator = PALLOC.lock();
566            allocator.inner[self.arena_idx]
567                .as_mut()
568                .unwrap()
569                .dealloc(self.kva, self.cnt);
570            allocator.unlock();
571        }
572    }
573}