obkrnl/malloc/
vm.rs

1use crate::config::PAGE_SIZE;
2use crate::context::{CpuLocal, current_thread, uma};
3use crate::uma::{Alloc, UmaFlags, UmaZone};
4use alloc::string::ToString;
5use alloc::sync::Arc;
6use alloc::vec::Vec;
7use core::alloc::Layout;
8use core::cell::RefCell;
9use core::num::NonZero;
10
11/// Kernel heap that allocate a memory from a virtual memory management system. This struct is a
12/// merge of `malloc_type` and `malloc_type_internal` structure.
13pub struct VmHeap {
14    zones: [Vec<Arc<UmaZone>>; (usize::BITS - 1) as usize], // kmemsize + kmemzones
15    stats: CpuLocal<RefCell<Stats>>,                        // mti_stats
16}
17
18impl VmHeap {
19    const KMEM_ZSHIFT: usize = 4;
20    const KMEM_ZBASE: usize = 16;
21    const KMEM_ZMASK: usize = Self::KMEM_ZBASE - 1;
22    const KMEM_ZSIZE: usize = PAGE_SIZE.get() >> Self::KMEM_ZSHIFT;
23
24    /// See `kmeminit` on the Orbis for a reference.
25    ///
26    /// # Reference offsets
27    /// | Version | Offset |
28    /// |---------|--------|
29    /// |PS4 11.00|0x1A4B80|
30    pub fn new() -> Self {
31        // The possible of maximum alignment that Layout allowed is a bit before the most
32        // significant bit of isize (e.g. 0x4000000000000000 on 64 bit system). So we can use
33        // "size_of::<usize>() * 8 - 1" to get the size of array for all possible alignment.
34        let uma = uma().unwrap();
35        let zones = core::array::from_fn(|align| {
36            let mut zones = Vec::with_capacity(Self::KMEM_ZSIZE + 1);
37            let mut last = 0;
38            let align = align
39                .try_into()
40                .ok()
41                .and_then(|align| 1usize.checked_shl(align))
42                .unwrap();
43
44            for i in Self::KMEM_ZSHIFT.. {
45                // Stop if size larger than page size.
46                let size = NonZero::new(1usize << i).unwrap();
47
48                if size > PAGE_SIZE {
49                    break;
50                }
51
52                // Create zone.
53                let zone = Arc::new(uma.into_owned().create_zone(
54                    size.to_string(),
55                    size,
56                    Some(align - 1),
57                    None,
58                    UmaFlags::Malloc,
59                ));
60
61                while last <= size.get() {
62                    zones.push(zone.clone());
63                    last += Self::KMEM_ZBASE;
64                }
65            }
66
67            zones
68        });
69
70        Self {
71            zones,
72            stats: CpuLocal::new(|_| RefCell::default()),
73        }
74    }
75
76    /// Returns null on failure.
77    ///
78    /// See `malloc` on the Orbis for a reference.
79    ///
80    /// # Safety
81    /// `layout` must be nonzero.
82    ///
83    /// # Reference offsets
84    /// | Version | Offset |
85    /// |---------|--------|
86    /// |PS4 11.00|0x1A4220|
87    pub unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
88        // Our implementation imply M_WAITOK.
89        let td = current_thread();
90
91        if !td.can_sleep() {
92            panic!("heap allocation in a non-sleeping context is not supported");
93        }
94
95        // Determine how to allocate.
96        let lock = td.disable_vm_heap();
97        let size = layout.size();
98        let mem = if size <= PAGE_SIZE.get() {
99            // Get zone to allocate from.
100            let align = layout.align().trailing_zeros() as usize;
101            let size = if (size & Self::KMEM_ZMASK) != 0 {
102                // TODO: Refactor this for readability.
103                (size + Self::KMEM_ZBASE) & !Self::KMEM_ZMASK
104            } else {
105                size
106            };
107
108            // Allocate a memory from UMA zone.
109            let zone = &self.zones[align][size >> Self::KMEM_ZSHIFT];
110            let mem = zone.alloc(Alloc::Wait | Alloc::Zero);
111
112            // Update stats.
113            let stats = self.stats.lock();
114            let mut stats = stats.borrow_mut();
115            let size = if mem.is_null() { 0 } else { zone.size().get() };
116
117            if size != 0 {
118                stats.alloc_bytes = stats
119                    .alloc_bytes
120                    .checked_add(size.try_into().unwrap())
121                    .unwrap();
122                stats.alloc_count += 1;
123            }
124
125            // TODO: How to update mts_size here since our zone table also indexed by alignment?
126            mem
127        } else {
128            todo!()
129        };
130
131        drop(lock);
132
133        mem
134    }
135
136    /// # Safety
137    /// `ptr` must be obtained with [`Self::alloc()`] and `layout` must be the same one that was
138    /// passed to that method.
139    pub unsafe fn dealloc(&self, _: *mut u8, _: Layout) {
140        todo!()
141    }
142}
143
144/// Implementation of `malloc_type_stats` structure.
145#[derive(Default)]
146struct Stats {
147    alloc_bytes: u64, // mts_memalloced
148    alloc_count: u64, // mts_numallocs
149}