obkrnl/vm/
mod.rs

1pub use self::object::*;
2pub use self::page::*;
3
4use self::stats::VmStats;
5use crate::config::PAGE_SIZE;
6use crate::context::{config, current_thread};
7use crate::dmem::Dmem;
8use crate::lock::GutexGroup;
9use crate::proc::Proc;
10use alloc::sync::{Arc, Weak};
11use core::cmp::max;
12use core::fmt::Debug;
13use core::sync::atomic::{AtomicUsize, Ordering};
14use krt::info;
15use macros::bitflag;
16use thiserror::Error;
17
18mod object;
19mod page;
20mod stats;
21
22/// Implementation of Virtual Memory system.
23pub struct Vm {
24    stats: [VmStats; 2],
25    pagers: [Weak<Proc>; 2],         // pageproc
26    pages_deficit: [AtomicUsize; 2], // vm_pageout_deficit
27}
28
29impl Vm {
30    /// See `vm_page_startup` on the Orbis for a reference.
31    ///
32    /// # Reference offsets
33    /// | Version | Offset |
34    /// |---------|--------|
35    /// |PS4 11.00|0x029200|
36    pub fn new(phys_avail: [u64; 61], dmem: &Dmem) -> Result<Arc<Self>, VmError> {
37        // Get initial v_page_count and v_free_count.
38        let page_size = u64::try_from(PAGE_SIZE.get()).unwrap();
39        let config = config();
40        let blocked = config.env("vm.blacklist");
41        let unk = dmem.game_end() - dmem.config().fmem_max.get();
42        let mut page_count = [0; 2];
43        let mut free_count = [0; 2];
44
45        for i in (0..).step_by(2) {
46            // Check if end entry.
47            let mut addr = phys_avail[i];
48            let end = phys_avail[i + 1];
49
50            if end == 0 {
51                break;
52            }
53
54            while addr < end {
55                if blocked.is_some() {
56                    todo!();
57                }
58
59                if addr < unk || dmem.game_end() <= addr {
60                    // TODO: Update vm_phys_segs.
61                    page_count[0] += 1;
62                    free_count[0] += 1;
63                } else {
64                    // TODO: Update vm_phys_segs.
65                    page_count[1] += 1;
66                }
67
68                addr += page_size;
69            }
70        }
71
72        info!(
73            concat!(
74                "VM stats initialized.\n",
75                "v_page_count[0]: {}\n",
76                "v_free_count[0]: {}\n",
77                "v_page_count[1]: {}"
78            ),
79            page_count[0], free_count[0], page_count[1]
80        );
81
82        // Initializes stats. The Orbis initialize these data in vm_pageout function but it is
83        // possible for data race so we do it here instead.
84        let pageout_page_count = 0x10; // TODO: Figure out where this value come from.
85        let gg = GutexGroup::new();
86        let stats = [
87            VmStats {
88                free_reserved: pageout_page_count + 100 + 10,
89                cache_count: gg.clone().spawn_default(),
90                free_count: gg.clone().spawn(free_count[0]),
91                interrupt_free_min: gg.clone().spawn(2),
92            },
93            VmStats {
94                free_reserved: pageout_page_count,
95                cache_count: gg.clone().spawn_default(),
96                free_count: gg.clone().spawn(free_count[1]),
97                interrupt_free_min: gg.clone().spawn(2),
98            },
99        ];
100
101        // Spawn page daemons. The Orbis do this in a separated sysinit but we do it here instead to
102        // keep it in the VM subsystem.
103        let mut vm = Self {
104            stats,
105            pagers: Default::default(),
106            pages_deficit: [AtomicUsize::new(0), AtomicUsize::new(0)],
107        };
108
109        vm.spawn_pagers();
110
111        Ok(Arc::new(vm))
112    }
113
114    /// See `vm_page_alloc` on the Orbis for a reference.
115    ///
116    /// # Reference offsets
117    /// | Version | Offset |
118    /// |---------|--------|
119    /// |PS4 11.00|0x02B030|
120    pub fn alloc_page(&self, obj: Option<VmObject>, flags: VmAlloc) -> Option<VmPage> {
121        let vm = obj.as_ref().map_or(0, |v| v.vm());
122        let td = current_thread();
123        let stats = &self.stats[vm];
124        let cache_count = stats.cache_count.read();
125        let free_count = stats.free_count.read();
126        let available = *free_count + *cache_count;
127
128        if available <= stats.free_reserved {
129            let p = td.proc();
130            let mut flags = if Arc::as_ptr(p) == self.pagers[p.pager()].as_ptr() {
131                VmAlloc::System.into()
132            } else {
133                flags & (VmAlloc::Interrupt | VmAlloc::System)
134            };
135
136            if (flags & (VmAlloc::Interrupt | VmAlloc::System)) == VmAlloc::Interrupt {
137                flags = VmAlloc::Interrupt.into();
138            }
139
140            if flags == VmAlloc::Interrupt {
141                todo!()
142            } else if flags == VmAlloc::System {
143                if available <= *stats.interrupt_free_min.read() {
144                    let deficit = max(1, flags.get(VmAlloc::Count));
145
146                    drop(free_count);
147                    drop(cache_count);
148
149                    self.pages_deficit[vm].fetch_add(deficit.into(), Ordering::Relaxed);
150                    self.wake_pager(vm);
151
152                    return None;
153                }
154            } else {
155                todo!()
156            }
157        }
158
159        // Allocate VmPage.
160        let page = match obj {
161            Some(_) => todo!(),
162            None => {
163                if flags.has_any(VmAlloc::Cached) {
164                    return None;
165                }
166
167                self.alloc_phys()
168            }
169        };
170
171        match page.flags().has_any(PageFlags::Cached) {
172            true => todo!(),
173            false => todo!(),
174        }
175    }
176
177    /// See `vm_phys_alloc_pages` on the Orbis for a reference.
178    ///
179    /// # Reference offsets
180    /// | Version | Offset |
181    /// |---------|--------|
182    /// |PS4 11.00|0x160520|
183    fn alloc_phys(&self) -> VmPage {
184        todo!()
185    }
186
187    /// See `kick_pagedaemons` on the Orbis for a reference.
188    ///
189    /// # Reference offsets
190    /// | Version | Offset |
191    /// |---------|--------|
192    /// |PS4 11.00|0x3E0E40|
193    fn spawn_pagers(&mut self) {
194        // TODO: This requires v_page_count that populated by vm_page_startup. In order to populate
195        // this we need phys_avail that populated by getmemsize.
196    }
197
198    /// See `pagedaemon_wakeup` on the Orbis for a reference.
199    ///
200    /// # Reference offsets
201    /// | Version | Offset |
202    /// |---------|--------|
203    /// |PS4 11.00|0x3E0690|
204    fn wake_pager(&self, _: usize) {
205        todo!()
206    }
207}
208
209/// Flags for [`Vm::alloc_page()`].
210#[bitflag(u32)]
211pub enum VmAlloc {
212    /// `VM_ALLOC_INTERRUPT`.
213    Interrupt = 0x00000001,
214    /// `VM_ALLOC_SYSTEM`.
215    System = 0x00000002,
216    /// `VM_ALLOC_IFCACHED`.
217    Cached = 0x00000400,
218    /// `VM_ALLOC_COUNT`.
219    Count(u16) = 0xFFFF0000,
220}
221
222/// Represents an error when [`Vm::new()`] fails.
223#[derive(Debug, Error)]
224pub enum VmError {}