Skip to main content

obkrnl/uma/
keg.rs

1use super::{Alloc, Slab, SlabFlags, SlabHdr, Uma, UmaFlags, small_alloc};
2use crate::config::{PAGE_MASK, PAGE_SHIFT, PAGE_SIZE};
3use crate::vm::{PageObj, Vm, kaddr_to_phys};
4use alloc::collections::vec_deque::VecDeque;
5use core::alloc::Layout;
6use core::cmp::{max, min};
7use core::num::NonZero;
8use core::ptr::NonNull;
9
10/// Implementation of `uma_keg` structure.
11pub struct UmaKeg {
12    vm: &'static Vm,
13    size: NonZero<usize>,                                  // uk_size
14    rsize: usize,                                          // uk_rsize
15    pgoff: usize,                                          // uk_pgoff
16    ppera: usize,                                          // uk_ppera
17    ipers: usize,                                          // uk_ipers
18    alloc: fn(&'static Vm, Alloc) -> (*mut u8, SlabFlags), // uk_allocf
19    init: Option<fn()>,                                    // uk_init
20    max_pages: usize,                                      // uk_maxpages
21    pages: usize,                                          // uk_pages
22    pub(super) free: usize,                                // uk_free
23    recurse: u32,                                          // uk_recurse
24    partial_slabs: VecDeque<NonNull<Slab>>,                // uk_part_slab
25    flags: UmaFlags,                                       // uk_flags
26}
27
28impl UmaKeg {
29    /// `align` is the actual alignment **minus** one, which mean if you want each item to be 8
30    /// bytes alignment this value will be 7.
31    ///
32    /// See `keg_ctor` on the Orbis for a reference.
33    ///
34    /// # Reference offsets
35    /// | Version | Offset |
36    /// |---------|--------|
37    /// |PS4 11.00|0x13CF40|
38    pub(super) fn new(
39        vm: &'static Vm,
40        size: NonZero<usize>,
41        align: usize,
42        init: Option<fn()>,
43        mut flags: UmaFlags,
44    ) -> Self {
45        if flags.has_any(UmaFlags::Vm) {
46            todo!()
47        }
48
49        if flags.has_any(UmaFlags::ZInit) {
50            todo!()
51        }
52
53        if flags.has_any(UmaFlags::Malloc) {
54            flags |= UmaFlags::VToSlab;
55        }
56
57        // Get header layout.
58        let hdr = Layout::new::<SlabHdr>();
59        let (mut hdr, off) = hdr.extend(Layout::new::<u8>()).unwrap();
60
61        hdr = hdr.pad_to_align();
62
63        // Get UMA_FRITM_SZ and UMA_FRITMREF_SZ.
64        let free_item = hdr.size() - off;
65        let available = PAGE_SIZE.get() - hdr.size();
66
67        // Get uk_rsize, uk_ppera and uk_ipers.
68        let (rsize, ppera, ipers) = if flags.has_any(UmaFlags::CacheSpread) {
69            // Get uk_rsize.
70            let rsize = size.get().next_multiple_of(align + 1);
71            let align = align + 1;
72            let rsize = if (rsize & align) == 0 {
73                // TODO: What is this?
74                rsize + align
75            } else {
76                rsize
77            };
78
79            // Get uk_ppera.
80            let pages = (PAGE_SIZE.get() / align * rsize) >> PAGE_SHIFT;
81            let ppera = min(pages, (128 * 1024) / PAGE_SIZE);
82
83            // TODO: Why we need to add the differences to the calculation?
84            let ipers = (ppera * PAGE_SIZE.get() + (rsize - size.get())) / rsize;
85
86            (rsize, ppera, ipers)
87        } else {
88            // TODO: Not sure why we need space at least for 2 free item?
89            if (size.get() + free_item) > available {
90                // TODO: Set uk_ppera and uk_rsize.
91                if !flags.has_any(UmaFlags::Internal) {
92                    flags |= UmaFlags::Offpage;
93
94                    if !flags.has_any(UmaFlags::VToSlab) {
95                        flags |= UmaFlags::Hash;
96                    }
97                }
98
99                // Get uk_ppera.
100                let mut ppera = size.get() >> PAGE_SHIFT;
101
102                if size.get() > (size.get() & !PAGE_MASK.get()) {
103                    ppera += 1;
104                }
105
106                (size.get(), ppera, 1)
107            } else {
108                // Get uk_rsize.
109                let rsize = max(size, Uma::SMALLEST_UNIT);
110                let rsize = rsize.get().next_multiple_of(align + 1);
111
112                // Get uk_ipers.
113                let mut ipers = available / (rsize + free_item);
114
115                // TODO: Verify if this valid for PAGE_SIZE < 0x4000.
116                if !flags.has_any(UmaFlags::Internal | UmaFlags::CacheOnly)
117                    && (available % (rsize + free_item)) >= Uma::MAX_WASTE.get()
118                    && (PAGE_SIZE.get() / rsize) > ipers
119                {
120                    ipers = PAGE_SIZE.get() / rsize;
121
122                    if flags.has_any(UmaFlags::VToSlab) {
123                        flags |= UmaFlags::Offpage;
124                    } else {
125                        flags |= UmaFlags::Offpage | UmaFlags::Hash;
126                    }
127                }
128
129                (rsize, 1, ipers)
130            }
131        };
132
133        if flags.has_any(UmaFlags::Offpage) {
134            // TODO: Set uk_slabzone.
135        }
136
137        // Get allocator.
138        let alloc = if ppera == 1 {
139            // TODO: Get uk_freef.
140            small_alloc
141        } else {
142            Self::page_alloc
143        };
144
145        if flags.has_any(UmaFlags::MtxClass) {
146            todo!()
147        }
148
149        // Get uk_pgoff.
150        let mut pgoff = 0;
151
152        if !flags.has_any(UmaFlags::Offpage) {
153            let space = ppera * PAGE_SIZE.get();
154
155            // TODO: This can cause a pointer to slab unaligned.
156            pgoff = (space - hdr.size()) - ipers * free_item;
157
158            // TODO: What is this?
159            if space < pgoff + hdr.size() + ipers * free_item {
160                panic!("UMA slab won't fit");
161            }
162        }
163
164        if flags.has_any(UmaFlags::Hash) {
165            todo!()
166        }
167
168        // TODO: Add uk_zones.
169        // TODO: Add uma_kegs.
170        Self {
171            vm,
172            size,
173            rsize,
174            pgoff,
175            ppera,
176            ipers,
177            alloc,
178            init,
179            max_pages: 0,
180            pages: 0,
181            free: 0,
182            recurse: 0,
183            partial_slabs: VecDeque::new(),
184            flags,
185        }
186    }
187
188    pub fn size(&self) -> NonZero<usize> {
189        self.size
190    }
191
192    pub fn allocated_size(&self) -> usize {
193        self.rsize
194    }
195
196    pub fn item_per_slab(&self) -> usize {
197        self.ipers
198    }
199
200    pub fn recurse(&self) -> u32 {
201        self.recurse
202    }
203
204    pub fn flags(&self) -> UmaFlags {
205        self.flags
206    }
207
208    /// See `page_alloc` on the Orbis for a reference.
209    ///
210    /// # Reference offsets
211    /// | Version | Offset |
212    /// |---------|--------|
213    /// |PS4 11.00|0x1402F0|
214    fn page_alloc(_: &'static Vm, _: Alloc) -> (*mut u8, SlabFlags) {
215        todo!()
216    }
217
218    /// See `keg_fetch_slab` on the Orbis for a reference.
219    ///
220    /// # Reference offsets
221    /// | Version | Offset |
222    /// |---------|--------|
223    /// |PS4 11.00|0x141E20|
224    pub unsafe fn fetch_slab(&mut self, mut flags: Alloc) -> Option<NonNull<Slab>> {
225        while self.free == 0 {
226            if flags.has_any(Alloc::NoVm) {
227                return None;
228            }
229
230            #[allow(clippy::while_immutable_condition)] // TODO: Remove this.
231            while self.max_pages != 0 && self.max_pages <= self.pages {
232                todo!()
233            }
234
235            self.recurse += 1;
236            let slab = self.alloc_slab(flags);
237            self.recurse -= 1;
238
239            if let Some(slab) = slab {
240                self.partial_slabs.push_front(slab);
241                return Some(slab);
242            }
243
244            flags |= Alloc::NoVm;
245        }
246
247        if let Some(v) = self.partial_slabs.front().copied() {
248            return Some(v);
249        }
250
251        todo!()
252    }
253
254    /// See `keg_alloc_slab` on the Orbis for a reference.
255    ///
256    /// # Reference offsets
257    /// | Version | Offset |
258    /// |---------|--------|
259    /// |PS4 11.00|0x13FBA0|
260    fn alloc_slab(&mut self, flags: Alloc) -> Option<NonNull<Slab>> {
261        if self.flags.has_any(UmaFlags::Offpage) {
262            todo!()
263        } else {
264            // Get allocation flags.
265            let flags = if self.flags.has_any(UmaFlags::Malloc) {
266                flags & !Alloc::Zero
267            } else {
268                flags | Alloc::Zero
269            };
270
271            // Allocate.
272            let (mem, slab_flags) = (self.alloc)(self.vm, flags);
273
274            if !mem.is_null() {
275                // The Orbis also check if uk_flags does not contains UMA_ZONE_OFFPAGE, which seems
276                // to be useless since we only be here when it does not contains UMA_ZONE_OFFPAGE.
277                let hdr = unsafe { mem.byte_add(self.pgoff).cast::<SlabHdr>() };
278
279                // TODO: I'm not confident about the memory layout here. The variables calculation
280                // during keg construction is very complicated and I don't fully understand it. If
281                // we encounter some memory corruptions then this is likely to be the root of
282                // problem.
283                unsafe { hdr.write(SlabHdr::new(slab_flags, mem, self.ipers)) };
284
285                // Initialize free items. The offset calculation here should be optimized away.
286                let (_, off) = Layout::new::<SlabHdr>()
287                    .extend(Layout::new::<u8>())
288                    .unwrap();
289                let free = unsafe { hdr.byte_add(off).cast::<u8>() };
290
291                for i in 0..self.ipers {
292                    let item = (i + 1).try_into().unwrap();
293
294                    unsafe { free.add(i).write(item) };
295                }
296
297                if self.init.is_some() {
298                    todo!()
299                }
300
301                if self.flags.has_any(UmaFlags::Hash) {
302                    todo!()
303                }
304
305                self.pages += self.ppera;
306                self.free += self.ipers;
307
308                // The Orbis do this before initialize the slab but we move it after initialization
309                // instead.
310                let slab = core::ptr::slice_from_raw_parts_mut(hdr, self.ipers) as *mut Slab;
311                let slab = unsafe { NonNull::new_unchecked(slab) };
312
313                if self.flags.has_any(UmaFlags::VToSlab) {
314                    let mut next = mem as usize;
315
316                    for _ in 0..self.ppera {
317                        let p = unsafe { kaddr_to_phys(next) };
318                        let p = self.vm.phys_to_page(p).unwrap(); // Orbis assume non-null.
319                        let mut s = p.state.lock();
320
321                        // The Orbis also set PG_SLAB to vm_page::flags here. AFAIK this flag only
322                        // used to identify the vm_page::object, which mean we don't need this flag
323                        // because our vm_page::object is a Rust enum.
324                        s.object = Some(PageObj::Slab(slab));
325
326                        drop(s);
327
328                        next += PAGE_SIZE.get();
329                    }
330                }
331
332                return Some(slab);
333            }
334
335            todo!()
336        }
337    }
338}