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