Skip to main content

obkrnl/vm/
mod.rs

1pub use self::arch::*;
2pub use self::object::*;
3pub use self::page::*;
4
5use self::phys::PhysAllocator;
6use self::stats::VmStats;
7use crate::config::{PAGE_SHIFT, PAGE_SIZE};
8use crate::context::{config, current_thread};
9use crate::dmem::Dmem;
10use crate::lock::Mutex;
11use crate::proc::Proc;
12use alloc::sync::{Arc, Weak};
13use alloc::vec::Vec;
14use core::cmp::max;
15use core::fmt::Debug;
16use core::sync::atomic::{AtomicUsize, Ordering};
17use krt::info;
18use macros::bitflag;
19use thiserror::Error;
20
21#[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")]
22#[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")]
23mod arch;
24mod object;
25mod page;
26mod phys;
27mod stats;
28
29/// Implementation of Virtual Memory system.
30pub struct Vm {
31    phys: PhysAllocator,
32    pages: Vec<Arc<VmPage>>, // vm_page_array + vm_page_array_size
33    stats: [Mutex<VmStats>; 2],
34    pagers: [Weak<Proc>; 2],         // pageproc
35    pages_deficit: [AtomicUsize; 2], // vm_pageout_deficit
36}
37
38impl Vm {
39    /// See `vm_page_startup` on the Orbis for a reference.
40    ///
41    /// # Reference offsets
42    /// | Version | Offset |
43    /// |---------|--------|
44    /// |PS4 11.00|0x029200|
45    pub fn new(
46        phys_avail: [usize; 61],
47        ma: Option<&MemAffinity>,
48        dmem: &Dmem,
49    ) -> Result<Arc<Self>, VmError> {
50        let phys = PhysAllocator::new(&phys_avail, ma);
51
52        // Populate vm_page_array. We do a bit different than Orbis here to be able to make segind
53        // immutable.
54        let config = config();
55        let blocked = config.env("vm.blacklist");
56        let unk = dmem.game_end() - dmem.config().fmem_max.get();
57        let mut pages = Vec::new();
58        let mut free_pages = Vec::new();
59        let mut page_count = [0; 2];
60        let mut free_count = [0; 2];
61
62        for i in (0..).step_by(2) {
63            // Check if end entry.
64            let addr = phys_avail[i];
65            let end = phys_avail[i + 1];
66
67            if end == 0 {
68                break;
69            }
70
71            for addr in (addr..end).step_by(PAGE_SIZE.get()) {
72                // Check if blocked address.
73                if blocked.is_some() {
74                    // TODO: We probably want to use None for segment index here. The problem is
75                    // Orbis use zero here.
76                    let pi = pages.len();
77
78                    pages.push(Arc::new(VmPage::new(pi, 0, 0, addr, 0)));
79
80                    todo!();
81                }
82
83                // Check if free page.
84                let vm;
85                let free = if addr < unk || addr >= dmem.game_end() {
86                    // We inline a call to vm_phys_add_page() here.
87                    vm = 0;
88
89                    page_count[0] += 1;
90                    free_count[0] += 1;
91
92                    true
93                } else {
94                    // We inline a call to unknown function here.
95                    vm = 1;
96
97                    page_count[1] += 1;
98
99                    false
100                };
101
102                // Add to list.
103                let pi = pages.len();
104                let seg = phys.segment_index(addr).unwrap();
105                let page = Arc::new(VmPage::new(pi, vm, 0, addr, seg));
106
107                if free {
108                    free_pages.push(page.clone());
109                }
110
111                pages.push(page);
112            }
113        }
114
115        info!(
116            concat!(
117                "VM stats initialized.\n",
118                "v_page_count[0]: {}\n",
119                "v_free_count[0]: {}\n",
120                "v_page_count[1]: {}"
121            ),
122            page_count[0], free_count[0], page_count[1]
123        );
124
125        // Initializes stats. The Orbis initialize these data in vm_pageout function but it is
126        // possible for data race so we do it here instead.
127        let pageout_page_count = 0x10; // TODO: Figure out where this value come from.
128        let free_reserved = [pageout_page_count + 100 + 10, pageout_page_count];
129        let free_min = [free_reserved[0] + 325, free_reserved[1] + 64];
130        let stats = [
131            Mutex::new(VmStats {
132                free_reserved: free_reserved[0],
133                cache_min: if free_count[0] < 2049 {
134                    // TODO: Figure out where 2049 value come from.
135                    0
136                } else if free_count[0] < 6145 {
137                    // TODO: Figure out where 6145 value come from.
138                    free_reserved[0] + free_min[0] * 2
139                } else {
140                    free_reserved[0] + free_min[0] * 4
141                },
142                cache_count: 0,
143                free_count: free_count[0],
144                interrupt_free_min: 2,
145                wire_count: 0,
146            }),
147            Mutex::new(VmStats {
148                free_reserved: free_reserved[1],
149                cache_min: if free_count[1] < 2049 {
150                    // TODO: Figure out where 2049 value come from.
151                    0
152                } else if free_count[1] < 6145 {
153                    // TODO: Figure out where 6145 value come from.
154                    free_reserved[1] + free_min[1] * 2
155                } else {
156                    free_reserved[1] + free_min[1] * 4
157                },
158                cache_count: 0,
159                free_count: free_count[1],
160                interrupt_free_min: 2,
161                wire_count: 0,
162            }),
163        ];
164
165        // Add free pages. The Orbis do this on the above loop but that is not possible for us since
166        // we use that loop to populate vm_page_array.
167        let mut vm = Self {
168            phys,
169            pages,
170            stats,
171            pagers: Default::default(),
172            pages_deficit: [AtomicUsize::new(0), AtomicUsize::new(0)],
173        };
174
175        for page in free_pages {
176            vm.free_page(&page, 0);
177        }
178
179        // Spawn page daemons. The Orbis do this in a separated sysinit but we do it here instead to
180        // keep it in the VM subsystem.
181        vm.spawn_pagers();
182
183        Ok(Arc::new(vm))
184    }
185
186    pub fn phys_to_page(&self, pa: usize) -> Option<&Arc<VmPage>> {
187        self.phys.phys_to_page(&self.pages, pa)
188    }
189
190    /// See `vm_page_alloc` on the Orbis for a reference.
191    ///
192    /// # Reference offsets
193    /// | Version | Offset |
194    /// |---------|--------|
195    /// |PS4 11.00|0x02B030|
196    pub fn alloc_page(
197        &self,
198        obj: Option<VmObject>,
199        pindex: usize,
200        flags: VmAlloc,
201    ) -> Option<Arc<VmPage>> {
202        let vm = obj.as_ref().map_or(0, |v| v.vm());
203        let td = current_thread();
204        let mut stats = self.stats[vm].lock();
205        let available = stats.free_count + stats.cache_count;
206
207        if available <= stats.free_reserved {
208            let p = td.proc();
209            let mut flags = if Arc::as_ptr(p) == self.pagers[p.pager()].as_ptr() {
210                VmAlloc::System.into()
211            } else {
212                flags & (VmAlloc::Interrupt | VmAlloc::System)
213            };
214
215            if (flags & (VmAlloc::Interrupt | VmAlloc::System)) == VmAlloc::Interrupt {
216                flags = VmAlloc::Interrupt.into();
217            }
218
219            if flags == VmAlloc::Interrupt {
220                todo!()
221            } else if flags == VmAlloc::System {
222                if available <= stats.interrupt_free_min {
223                    let deficit = max(1, flags.get(VmAlloc::Count));
224
225                    drop(stats);
226
227                    self.pages_deficit[vm].fetch_add(deficit.into(), Ordering::Relaxed);
228                    self.wake_pager(vm);
229
230                    return None;
231                }
232            } else {
233                todo!()
234            }
235        }
236
237        // Allocate VmPage.
238        let page = match &obj {
239            Some(_) => todo!(),
240            None => {
241                if flags.has_any(VmAlloc::Cached) {
242                    return None;
243                }
244
245                self.phys
246                    .alloc_page(&self.pages, vm, obj.is_none().into(), 0)
247            }
248        };
249
250        // The Orbis assume page is never null here.
251        let page = page.unwrap();
252        let mut ps = page.state.lock();
253
254        match ps.flags.has_any(PageFlags::Cached) {
255            true => todo!(),
256            false => stats.free_count -= 1,
257        }
258
259        match ps.flags.has_any(PageFlags::Zero) {
260            true => todo!(),
261            false => ps.flags = PageFlags::zeroed(),
262        }
263
264        ps.access = PageAccess::zeroed();
265
266        // Set oflags.
267        let mut oflags = PageExtFlags::zeroed();
268
269        match &obj {
270            Some(_) => todo!(),
271            None => oflags |= PageExtFlags::Unmanaged,
272        }
273
274        if !flags.has_any(VmAlloc::NoBusy | VmAlloc::NoObj) {
275            oflags |= PageExtFlags::Busy;
276        }
277
278        ps.extended_flags = oflags;
279
280        if flags.has_any(VmAlloc::Wired) {
281            stats.wire_count += 1;
282            ps.wire_count = 1;
283        }
284
285        ps.act_count = 0;
286
287        match &obj {
288            Some(_) => todo!(),
289            None => ps.pindex = pindex,
290        }
291
292        // TODO: Call vdrop.
293        if (stats.cache_count + stats.free_count) < (stats.cache_min + stats.free_reserved) {
294            todo!()
295        }
296
297        // TODO: Set unknown field.
298        drop(ps);
299
300        Some(page)
301    }
302
303    /// `page` must not have active lock on any fields.
304    ///
305    /// See `vm_phys_free_pages` on the Orbis for a reference.
306    ///
307    /// # Reference offsets
308    /// | Version | Offset |
309    /// |---------|--------|
310    /// |PS4 11.00|0x15FCB0|
311    fn free_page(&self, page: &Arc<VmPage>, mut order: usize) {
312        // Get segment the page belong to.
313        let mut page = page; // For scoped lifetime.
314        let vm = page.vm;
315        let mut pa = page.addr;
316        let seg = if (page.unk1 & 1) == 0 {
317            self.phys.segment(page.segment)
318        } else {
319            todo!()
320        };
321
322        // TODO: What is this?
323        let mut queues = seg.free_queues.lock();
324        let mut ps = page.state.lock();
325
326        while order < 12 {
327            let start = seg.start;
328            let buddy_pa = pa ^ (1usize << (order + PAGE_SHIFT)); // TODO: What is this?
329
330            if buddy_pa < start || buddy_pa >= seg.end {
331                break;
332            }
333
334            // Get buddy page index.
335            let buddy = &self.pages[seg.first_page + ((buddy_pa - start) >> PAGE_SHIFT)];
336            let mut bs = buddy.state.lock();
337
338            if bs.order != order || buddy.vm != vm || ((page.unk1 ^ buddy.unk1) & 1) != 0 {
339                break;
340            }
341
342            // TODO: Check if we really need to preserve page order here. If not we need to replace
343            // IndexMap with HashMap otherwise we need to find a better solution than IndexMap.
344            queues[vm][bs.pool][bs.order].shift_remove(buddy);
345            bs.order = VmPage::FREE_ORDER;
346
347            if bs.pool != ps.pool {
348                todo!()
349            }
350
351            drop(bs);
352
353            order += 1;
354            pa &= !((1usize << (order + PAGE_SHIFT)) - 1);
355            page = &self.pages[seg.first_page + ((pa - start) >> PAGE_SHIFT)];
356            ps = page.state.lock();
357        }
358
359        // Add to free queue.
360        ps.order = order;
361        queues[vm][ps.pool][order].insert(page.clone());
362    }
363
364    /// See `kick_pagedaemons` on the Orbis for a reference.
365    ///
366    /// # Reference offsets
367    /// | Version | Offset |
368    /// |---------|--------|
369    /// |PS4 11.00|0x3E0E40|
370    fn spawn_pagers(&mut self) {
371        // TODO: This requires v_page_count that populated by vm_page_startup. In order to populate
372        // this we need phys_avail that populated by getmemsize.
373    }
374
375    /// See `pagedaemon_wakeup` on the Orbis for a reference.
376    ///
377    /// # Reference offsets
378    /// | Version | Offset |
379    /// |---------|--------|
380    /// |PS4 11.00|0x3E0690|
381    fn wake_pager(&self, _: usize) {
382        todo!()
383    }
384}
385
386/// Implementation of `mem_affinity` structure.
387pub struct MemAffinity {}
388
389/// Flags for [Vm::alloc_page()].
390#[bitflag(u32)]
391pub enum VmAlloc {
392    /// `VM_ALLOC_INTERRUPT`.
393    Interrupt = 0x00000001,
394    /// `VM_ALLOC_SYSTEM`.
395    System = 0x00000002,
396    /// `VM_ALLOC_WIRED`.
397    Wired = 0x00000020,
398    /// `VM_ALLOC_NOOBJ`.
399    NoObj = 0x00000100,
400    /// `VM_ALLOC_NOBUSY`.
401    NoBusy = 0x00000200,
402    /// `VM_ALLOC_IFCACHED`.
403    Cached = 0x00000400,
404    /// `VM_ALLOC_COUNT`.
405    Count(u16) = 0xFFFF0000,
406}
407
408/// Represents an error when [`Vm::new()`] fails.
409#[derive(Debug, Error)]
410pub enum VmError {}