Skip to main content

obkrnl/uma/
slab.rs

1use super::UmaKeg;
2use crate::lock::Mutex;
3use crate::mem::{RefCnt, too_many_refs};
4use alloc::sync::Arc;
5use core::marker::PhantomPinned;
6use core::ptr::null_mut;
7use core::sync::atomic::{AtomicUsize, Ordering};
8use macros::bitflag;
9
10/// Implementation of `uma_slab`.
11///
12/// Unlike Orbis, we don't support `uma_slab_refcnt`. We use [alloc::sync::Arc] for that job instead
13/// so you need to wrap the item to allocate from the slab with [alloc::sync::Arc] for any zones
14/// that going to create with `UMA_ZONE_REFCNT`.
15///
16/// We use slightly different mechanism here but has the same memory layout (except we don't support
17/// `uma_slab_refcnt` as stated on the above).
18///
19/// # Safety
20/// Adding more fields into this struct without knowing how it work can cause undefined behavior in
21/// some places.
22#[repr(C)]
23pub struct Slab {
24    pub(super) pin: PhantomPinned,
25    pub(super) hdr: SlabHdr, // us_head
26    pub(super) free: [u8],   // us_freelist
27}
28
29impl Slab {
30    pub fn flags(&self) -> SlabFlags {
31        self.hdr.flags
32    }
33
34    /// Each allocated item keep a strong reference to the slab, which mean all allocated item need
35    /// to free manually otherwise the slab (and its keg) will be leak.
36    ///
37    /// Unlike Orbis, this method will return null if the slab already full instead of trigger a UB.
38    ///
39    /// See `slab_alloc_item` on the Orbis for a reference.
40    ///
41    /// # Reference offsets
42    /// | Version | Offset |
43    /// |---------|--------|
44    /// |PS4 11.00|0x141FE0|
45    pub fn alloc_item(&self) -> *mut u8 {
46        // Check if full.
47        let mut k = self.hdr.keg.state().lock();
48        let mut s = self.hdr.state.lock();
49
50        if s.free_count == 0 {
51            return null_mut();
52        }
53
54        // Allocate.
55        let f = usize::from(s.first_free);
56
57        s.first_free = self.free[f];
58        s.free_count -= 1;
59        k.free -= 1;
60
61        if s.free_count == 0 {
62            todo!()
63        }
64
65        self.hdr.refs.fetch_add(1, Ordering::Relaxed);
66
67        unsafe { self.hdr.items.add(f * self.hdr.keg.allocated_size()) }
68    }
69}
70
71impl Drop for Slab {
72    #[inline(never)]
73    fn drop(&mut self) {
74        core::sync::atomic::fence(Ordering::Acquire);
75
76        todo!()
77    }
78}
79
80unsafe impl RefCnt for Slab {
81    fn increase_ref(&self) {
82        let p = self.hdr.refs.fetch_add(1, Ordering::Relaxed);
83
84        if p == usize::MAX {
85            too_many_refs();
86        }
87    }
88
89    fn decrease_ref(&self) -> usize {
90        self.hdr.refs.fetch_sub(1, Ordering::Release)
91    }
92}
93
94unsafe impl Send for Slab {}
95unsafe impl Sync for Slab {}
96
97/// Implementation of `uma_slab_head`.
98pub(super) struct SlabHdr {
99    keg: Arc<UmaKeg>, // us_keg
100    items: *mut u8,   // us_data
101    flags: SlabFlags, // us_flags
102    /// This **MUST** be locked after everything else (e.g. keg state and zone state) otherwise it
103    /// will cause a deadlock.
104    state: Mutex<SlabState>,
105    refs: AtomicUsize,
106}
107
108impl SlabHdr {
109    /// # Safety
110    /// - `items` cannot be null.
111    /// - `len` must be a number of elements of the array at `items`.
112    pub unsafe fn new(keg: Arc<UmaKeg>, flags: SlabFlags, items: *mut u8, len: usize) -> Self {
113        Self {
114            keg,
115            items,
116            flags,
117            state: Mutex::new(SlabState {
118                free_count: len,
119                first_free: 0,
120            }),
121            refs: AtomicUsize::new(0),
122        }
123    }
124}
125
126/// Flags for [Slab].
127#[bitflag(u8)]
128pub enum SlabFlags {
129    /// `UMA_SLAB_PRIV`.
130    Private = 0x08,
131    /// `UMA_SLAB_MALLOC`.
132    Malloc = 0x20,
133}
134
135/// Contains mutable data for [SlabHdr].
136struct SlabState {
137    free_count: usize, // us_freecount
138    first_free: u8,    // us_firstfree
139}