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