obkrnl/vm/
phys.rs

1use super::{MemAffinity, VmPage};
2use crate::config::PAGE_SHIFT;
3use crate::lock::Mutex;
4use alloc::sync::Arc;
5use alloc::vec::Vec;
6use indexmap::IndexSet;
7use rustc_hash::FxBuildHasher;
8
9/// Provides methods to allocate physical memory.
10pub struct PhysAllocator {
11    segs: Vec<PhysSeg>, // vm_phys_segs + vm_phys_nsegs
12    nfree: usize,       // vm_nfreelists
13    lookup_lists: [Arc<Mutex<[[[IndexSet<Arc<VmPage>, FxBuildHasher>; 13]; 3]; 2]>>; 2], // vm_phys_lookup_lists
14}
15
16impl PhysAllocator {
17    /// See `vm_phys_init` on the Orbis for a reference.
18    ///
19    /// # Reference offsets
20    /// | Version | Offset |
21    /// |---------|--------|
22    /// |PS4 11.00|0x15F410|
23    pub fn new(phys_avail: &[u64; 61], ma: Option<&MemAffinity>) -> Self {
24        // Populate vm_phys_free_queues. Do not use Clone to construct the array here since it will
25        // refer to the same object. The Orbis do this after segments creation but we do it before
26        // instead.
27        let free_queues = [
28            Arc::<Mutex<[[[IndexSet<Arc<VmPage>, FxBuildHasher>; 13]; 3]; 2]>>::default(),
29            Arc::<Mutex<[[[IndexSet<Arc<VmPage>, FxBuildHasher>; 13]; 3]; 2]>>::default(),
30        ];
31
32        // Create segments.
33        let mut segs = Vec::new();
34        let mut nfree = 0;
35
36        for i in (0..).step_by(2) {
37            // Check if end entry.
38            let mut addr = phys_avail[i];
39            let end = phys_avail[i + 1];
40
41            if end == 0 {
42                break;
43            }
44
45            // TODO: Why Orbis need to create 16MB segment here?
46            if addr < 16777216 {
47                let unk = end < 0x1000001;
48
49                if !unk {
50                    Self::create_seg(&mut segs, ma, &free_queues, addr, 0x1000000, 1);
51
52                    // The Orbis also update end address here but it seems like the value is always
53                    // the same as current value.
54                    addr = 0x1000000;
55                }
56
57                Self::create_seg(&mut segs, ma, &free_queues, addr, end, unk.into());
58
59                nfree = 1;
60            } else {
61                Self::create_seg(&mut segs, ma, &free_queues, addr, end, 0);
62            }
63        }
64
65        // Populate vm_phys_lookup_lists.
66        let lookup_lists = [free_queues[0].clone(), free_queues[1].clone()];
67
68        Self {
69            segs,
70            nfree,
71            lookup_lists,
72        }
73    }
74
75    /// # Panics
76    /// If `i` is not valid.
77    pub fn segment(&self, i: usize) -> &PhysSeg {
78        &self.segs[i]
79    }
80
81    /// See `vm_phys_paddr_to_segind` on the Orbis for a reference. Our implementation is a bit
82    /// differences here. Orbis will panic if segment not found but we return [None] instead.
83    ///
84    /// # Reference offsets
85    /// | Version | Offset |
86    /// |---------|--------|
87    /// |PS4 11.00|0x15FC40|
88    pub fn segment_index(&self, pa: u64) -> Option<usize> {
89        for (i, s) in self.segs.iter().enumerate() {
90            if pa < s.start || pa >= s.end {
91                continue;
92            }
93
94            return Some(i);
95        }
96
97        None
98    }
99
100    /// See `vm_phys_alloc_pages` on the Orbis for a reference.
101    ///
102    /// # Reference offsets
103    /// | Version | Offset |
104    /// |---------|--------|
105    /// |PS4 11.00|0x160520|
106    pub fn alloc_page(
107        &self,
108        pages: &[Arc<VmPage>],
109        vm: usize,
110        pool: usize,
111        order: usize,
112    ) -> Option<Arc<VmPage>> {
113        // TODO: There is an increasement on unknown variable here.
114        let mut flind = 0;
115
116        loop {
117            let mut l = self.lookup_lists[flind].lock();
118
119            if let Some(v) = self.alloc_freelist(pages, &mut l[vm], pool, order) {
120                return Some(v);
121            }
122
123            flind += 1;
124
125            if flind >= (self.nfree + 1) {
126                break;
127            }
128        }
129
130        None
131    }
132
133    /// See `vm_phys_alloc_freelist_pages` on the Orbis for a reference.
134    ///
135    /// # Reference offsets
136    /// | Version | Offset |
137    /// |---------|--------|
138    /// |PS4 11.00|0x1605D0|
139    fn alloc_freelist(
140        &self,
141        pages: &[Arc<VmPage>],
142        list: &mut [[IndexSet<Arc<VmPage>, FxBuildHasher>; 13]; 3],
143        pool: usize,
144        order: usize,
145    ) -> Option<Arc<VmPage>> {
146        // Beware for deadlock here since we currently own a lock to free queue.
147        if order >= 13 {
148            return None;
149        }
150
151        let mut i = 0;
152
153        loop {
154            match list[pool][order + i].first() {
155                Some(v) => v,
156                None => match (order + i) < 12 {
157                    true => {
158                        i += 1;
159                        continue;
160                    }
161                    false => break,
162                },
163            };
164
165            todo!()
166        }
167
168        let mut next = 11;
169
170        loop {
171            for f in list.iter_mut() {
172                let mut i = next + 1;
173
174                if let Some(p) = f[i].first().cloned() {
175                    // Remove from queue.
176                    let mut ps = p.state.lock();
177
178                    f[i].shift_remove(&p);
179
180                    ps.pool = pool;
181                    ps.order = VmPage::FREE_ORDER;
182
183                    drop(ps);
184
185                    // Set buddy pool.
186                    let end = &pages[p.index + (1 << i)];
187
188                    for p in &pages[(p.index + 1)..end.index] {
189                        p.state.lock().pool = pool;
190                    }
191
192                    while i > order {
193                        i -= 1;
194
195                        // TODO: What is this?
196                        let buddy = &pages[p.index + (1 << i)];
197                        let mut ps = buddy.state.lock();
198
199                        ps.order = i;
200
201                        assert!(list[pool][i].shift_insert(0, buddy.clone()));
202                    }
203
204                    return Some(p);
205                }
206            }
207
208            if next < order || next == 0 {
209                break;
210            }
211
212            next -= 1;
213        }
214
215        None
216    }
217
218    /// See `vm_phys_create_seg` on the Orbis for a reference.
219    ///
220    /// # Reference offsets
221    /// | Version | Offset |
222    /// |---------|--------|
223    /// |PS4 11.00|0x15F8A0|
224    fn create_seg(
225        segs: &mut Vec<PhysSeg>,
226        ma: Option<&MemAffinity>,
227        queues: &[Arc<Mutex<[[[IndexSet<Arc<VmPage>, FxBuildHasher>; 13]; 3]; 2]>>; 2],
228        start: u64,
229        end: u64,
230        flind: usize,
231    ) {
232        match ma {
233            Some(_) => todo!(),
234            None => {
235                let mut first_page = 0;
236
237                for s in segs.iter() {
238                    first_page += (s.end - s.start) >> PAGE_SHIFT;
239                }
240
241                segs.push(PhysSeg {
242                    start,
243                    end,
244                    first_page: first_page.try_into().unwrap(),
245                    free_queues: queues[flind].clone(),
246                });
247            }
248        }
249    }
250}
251
252/// Implementation of `vm_phys_seg` structure.
253pub struct PhysSeg {
254    pub start: u64,        // start
255    pub end: u64,          // end
256    pub first_page: usize, // first_page
257    pub free_queues: Arc<Mutex<[[[IndexSet<Arc<VmPage>, FxBuildHasher>; 13]; 3]; 2]>>, // free_queues
258}