obkrnl/vm/
mod.rs

1pub use self::object::*;
2pub use self::page::*;
3
4use self::stats::VmStats;
5use crate::config::{Dipsw, PAGE_MASK, PAGE_SHIFT, PAGE_SIZE};
6use crate::context::{current_arch, current_config, current_thread};
7use crate::lock::GutexGroup;
8use crate::proc::Proc;
9use alloc::sync::{Arc, Weak};
10use config::{BootEnv, MapType};
11use core::cmp::{max, min};
12use core::fmt::Debug;
13use core::sync::atomic::{AtomicUsize, Ordering};
14use krt::{boot_env, warn};
15use macros::bitflag;
16use thiserror::Error;
17
18mod object;
19mod page;
20mod stats;
21
22/// Implementation of Virtual Memory system.
23pub struct Vm {
24    boot_area: u64,           // basemem
25    boot_addr: u64,           // boot_address
26    boot_tables: u64,         // mptramp_pagetables
27    initial_memory_size: u64, // initial_memory_size
28    end_page: u64,            // Maxmem
29    stats: [VmStats; 2],
30    pagers: [Weak<Proc>; 2],         // pageproc
31    pages_deficit: [AtomicUsize; 2], // vm_pageout_deficit
32}
33
34impl Vm {
35    /// See `vm_page_startup` on the Orbis for a reference.
36    ///
37    /// # Reference offsets
38    /// | Version | Offset |
39    /// |---------|--------|
40    /// |PS4 11.00|0x029200|
41    pub fn new() -> Result<Arc<Self>, VmError> {
42        // Initializes stats. The Orbis initialize these data in vm_pageout function but it is
43        // possible for data race so we do it here instead.
44        let pageout_page_count = 0x10; // TODO: Figure out where this value come from.
45        let gg = GutexGroup::new();
46        let stats = [
47            VmStats {
48                free_reserved: pageout_page_count + 100 + 10, // TODO: Same here.
49                cache_count: gg.clone().spawn_default(),
50                free_count: gg.clone().spawn_default(),
51                interrupt_free_min: gg.clone().spawn(2),
52            },
53            VmStats {
54                #[allow(clippy::identity_op)]
55                free_reserved: pageout_page_count + 0, // TODO: Same here.
56                cache_count: gg.clone().spawn_default(),
57                free_count: gg.clone().spawn_default(),
58                interrupt_free_min: gg.clone().spawn(2),
59            },
60        ];
61
62        // The Orbis invoke this in hammer_time but we do it here instead to keep it in the VM
63        // subsystem.
64        let mut vm = Self {
65            boot_area: 0,
66            boot_addr: 0,
67            boot_tables: 0,
68            initial_memory_size: 0,
69            end_page: 0,
70            stats,
71            pagers: Default::default(),
72            pages_deficit: [AtomicUsize::new(0), AtomicUsize::new(0)],
73        };
74
75        vm.load_memory_map()?;
76
77        // Spawn page daemons. The Orbis do this in a separated sysinit but we do it here instead to
78        // keep it in the VM subsystem.
79        vm.spawn_pagers();
80
81        Ok(Arc::new(vm))
82    }
83
84    pub fn boot_area(&self) -> u64 {
85        self.boot_area
86    }
87
88    pub fn initial_memory_size(&self) -> u64 {
89        self.initial_memory_size
90    }
91
92    /// See `vm_page_alloc` on the Orbis for a reference.
93    ///
94    /// # Reference offsets
95    /// | Version | Offset |
96    /// |---------|--------|
97    /// |PS4 11.00|0x02B030|
98    pub fn alloc_page(&self, obj: Option<VmObject>, flags: VmAlloc) -> Option<VmPage> {
99        let vm = obj.as_ref().map_or(0, |v| v.vm());
100        let td = current_thread();
101        let stats = &self.stats[vm];
102        let cache_count = stats.cache_count.read();
103        let free_count = stats.free_count.read();
104        let available = *free_count + *cache_count;
105
106        if available <= stats.free_reserved {
107            let p = td.proc();
108            let mut flags = if Arc::as_ptr(p) == self.pagers[p.pager()].as_ptr() {
109                VmAlloc::System.into()
110            } else {
111                flags & (VmAlloc::Interrupt | VmAlloc::System)
112            };
113
114            if (flags & (VmAlloc::Interrupt | VmAlloc::System)) == VmAlloc::Interrupt {
115                flags = VmAlloc::Interrupt.into();
116            }
117
118            if flags == VmAlloc::Interrupt {
119                todo!()
120            } else if flags == VmAlloc::System {
121                if available <= *stats.interrupt_free_min.read() {
122                    let deficit = max(1, flags.get(VmAlloc::Count));
123
124                    drop(free_count);
125                    drop(cache_count);
126
127                    self.pages_deficit[vm].fetch_add(deficit.into(), Ordering::Relaxed);
128                    self.wake_pager(vm);
129
130                    return None;
131                }
132            } else {
133                todo!()
134            }
135        }
136
137        todo!()
138    }
139
140    /// See `getmemsize` on the Orbis for a reference.
141    ///
142    /// # Reference offsets
143    /// | Version | Offset |
144    /// |---------|--------|
145    /// |PS4 11.00|0x25CF00|
146    fn load_memory_map(&mut self) -> Result<(), VmError> {
147        // TODO: Some of the logic around here are very hard to understand.
148        let mut physmap = [0u64; 60];
149        let mut last = 0usize;
150        let map = match boot_env() {
151            BootEnv::Vm(v) => v.memory_map.as_slice(),
152        };
153
154        'top: for m in map {
155            // We only interested in RAM.
156            match m.ty {
157                MapType::None => break,
158                MapType::Ram => (),
159                MapType::Reserved => continue,
160            }
161
162            // TODO: This should be possible only when booting from BIOS.
163            if m.len == 0 {
164                break;
165            }
166
167            // Check if we need to insert before the previous entries.
168            let mut insert_idx = last + 2;
169            let mut j = 0usize;
170
171            while j <= last {
172                if m.base < physmap[j + 1] {
173                    // Check if end address overlapped.
174                    if m.base + m.len > physmap[j] {
175                        warn!("Overlapping memory regions, ignoring second region.");
176                        continue 'top;
177                    }
178
179                    insert_idx = j;
180                    break;
181                }
182
183                j += 2;
184            }
185
186            // Check if end address is the start address of the next entry. If yes we just change
187            // base address of it to increase its size.
188            if insert_idx <= last && m.base + m.len == physmap[insert_idx] {
189                physmap[insert_idx] = m.base;
190                continue;
191            }
192
193            // Check if start address is the end address of the previous entry. If yes we just
194            // increase the size of previous entry.
195            if insert_idx > 0 && m.base == physmap[insert_idx - 1] {
196                physmap[insert_idx - 1] = m.base + m.len;
197                continue;
198            }
199
200            last += 2;
201
202            if last == physmap.len() {
203                warn!("Too many segments in the physical address map, giving up.");
204                break;
205            }
206
207            // This loop does not make sense on the Orbis. It seems like if this loop once
208            // entered it will never exit.
209            #[allow(clippy::while_immutable_condition)]
210            while insert_idx < last {
211                todo!()
212            }
213
214            physmap[insert_idx] = m.base;
215            physmap[insert_idx + 1] = m.base + m.len;
216        }
217
218        // Check if bootloader provide us a memory map. The Orbis will check if
219        // preload_search_info() return null but we can't do that since we use a static size array
220        // to pass this information.
221        if physmap[1] == 0 {
222            return Err(VmError::NoMemoryMap);
223        }
224
225        // Get initial memory size and BIOS boot area.
226        let page_size = PAGE_SIZE.get().try_into().unwrap();
227        let page_mask = !u64::try_from(PAGE_MASK.get()).unwrap();
228
229        for i in (0..=last).step_by(2) {
230            // Check if BIOS boot area.
231            if physmap[i] == 0 {
232                // TODO: Why 1024?
233                self.boot_area = physmap[i + 1] / 1024;
234            }
235
236            // Add to initial memory size.
237            let start = physmap[i].next_multiple_of(page_size);
238            let end = physmap[i + 1] & page_mask;
239
240            self.initial_memory_size += end.saturating_sub(start);
241        }
242
243        if self.boot_area == 0 {
244            return Err(VmError::NoBootArea);
245        }
246
247        // TODO: This seems like it is assume the first physmap always a boot area. The problem is
248        // what is the point of the logic on the above to find boot_area?
249        physmap[1] = self.adjust_boot_area(physmap[1] / 1024);
250
251        // Get end page.
252        self.end_page = physmap[last + 1] >> PAGE_SHIFT;
253
254        if let Some(v) = current_config().env("hw.physmem") {
255            self.end_page = min(v.parse::<u64>().unwrap() >> PAGE_SHIFT, self.end_page);
256        }
257
258        // TODO: There is some unknown calls here.
259        self.load_pmap();
260
261        Ok(())
262    }
263
264    /// See `kick_pagedaemons` on the Orbis for a reference.
265    ///
266    /// # Reference offsets
267    /// | Version | Offset |
268    /// |---------|--------|
269    /// |PS4 11.00|0x3E0E40|
270    fn spawn_pagers(&mut self) {
271        // TODO: This requires v_page_count that populated by vm_page_startup. In order to populate
272        // this we need phys_avail that populated by getmemsize.
273    }
274
275    /// See `pagedaemon_wakeup` on the Orbis for a reference.
276    ///
277    /// # Reference offsets
278    /// | Version | Offset |
279    /// |---------|--------|
280    /// |PS4 11.00|0x3E0690|
281    fn wake_pager(&self, _: usize) {
282        todo!()
283    }
284
285    /// See `mp_bootaddress` on the Orbis for a reference.
286    ///
287    /// # Reference offsets
288    /// | Version | Offset |
289    /// |---------|--------|
290    /// |PS4 11.00|0x1B9D20|
291    fn adjust_boot_area(&mut self, original: u64) -> u64 {
292        // TODO: Most logic here does not make sense.
293        let page_size = u64::try_from(PAGE_SIZE.get()).unwrap();
294        let page_mask = !u64::try_from(PAGE_MASK.get()).unwrap();
295        let need = u64::try_from(current_arch().secondary_start.len()).unwrap();
296        let addr = (original * 1024) & page_mask;
297
298        // TODO: What is this?
299        self.boot_addr = if need <= ((original * 1024) & 0xC00) {
300            addr
301        } else {
302            addr - page_size
303        };
304
305        self.boot_tables = self.boot_addr - (page_size * 3);
306        self.boot_tables
307    }
308
309    /// See `pmap_bootstrap` on the Orbis for a reference.
310    ///
311    /// # Reference offsets
312    /// | Version | Offset |
313    /// |---------|--------|
314    /// |PS4 11.00|0x1127C0|
315    fn load_pmap(&mut self) {
316        let config = current_config();
317
318        if config.is_allow_disabling_aslr() && config.dipsw(Dipsw::DisabledKaslr) {
319            todo!()
320        } else {
321            // TODO: There are a lot of unknown variables here so we skip implementing this until we
322            // run into the code that using them.
323        }
324    }
325}
326
327/// Flags for [`Vm::alloc_page()`].
328#[bitflag(u32)]
329pub enum VmAlloc {
330    /// `VM_ALLOC_INTERRUPT`.
331    Interrupt = 0x00000001,
332    /// `VM_ALLOC_SYSTEM`.
333    System = 0x00000002,
334    /// `VM_ALLOC_COUNT`.
335    Count(u16) = 0xFFFF0000,
336}
337
338/// Represents an error when [`Vm::new()`] fails.
339#[derive(Debug, Error)]
340pub enum VmError {
341    #[error("no memory map provided to the kernel")]
342    NoMemoryMap,
343
344    #[error("no boot area provided to the kernel")]
345    NoBootArea,
346}