Skip to main content

obkrnl/uma/
zone.rs

1use super::{Alloc, BucketHdr, Slab, Uma, UmaBucket, UmaFlags, UmaKeg};
2use crate::context::{CpuLocal, config, current_thread};
3use crate::lock::Mutex;
4use crate::vm::Vm;
5use alloc::collections::VecDeque;
6use alloc::collections::linked_list::LinkedList;
7use alloc::string::String;
8use alloc::sync::Arc;
9use alloc::vec::Vec;
10use core::cell::RefCell;
11use core::cmp::min;
12use core::num::NonZero;
13use core::ops::DerefMut;
14use core::ptr::{NonNull, null_mut};
15use core::sync::atomic::{AtomicBool, Ordering};
16
17/// Implementation of `uma_zone` structure.
18pub struct UmaZone {
19    bucket_enable: Arc<AtomicBool>,
20    bucket_keys: Arc<Vec<usize>>,
21    bucket_zones: Arc<Vec<UmaZone>>,
22    ty: ZoneType,
23    size: NonZero<usize>,                                         // uz_size
24    slab: unsafe fn(&mut UmaKeg, Alloc) -> Option<NonNull<Slab>>, // uz_slab
25    init: Option<fn(*mut u8, NonZero<usize>, Alloc) -> bool>,     // uz_init
26    ctor: Option<fn(*mut u8, NonZero<usize>, Alloc) -> bool>,     // uz_ctor
27    caches: CpuLocal<RefCell<UmaCache>>,                          // uz_cpu
28    flags: UmaFlags,                                              // uz_flags
29    state: Mutex<ZoneState>,
30}
31
32impl UmaZone {
33    const ALIGN_CACHE: usize = 63; // uma_align_cache
34
35    /// See `zone_ctor` on Orbis for a reference.
36    ///
37    /// # Reference offsets
38    /// | Version | Offset |
39    /// |---------|--------|
40    /// |PS4 11.00|0x13D490|
41    #[allow(clippy::too_many_arguments)] // TODO: Find a better way.
42    pub(super) fn new(
43        vm: &'static Vm,
44        bucket_enable: Arc<AtomicBool>,
45        bucket_keys: Arc<Vec<usize>>,
46        bucket_zones: Arc<Vec<UmaZone>>,
47        name: impl Into<String>,
48        keg: Option<UmaKeg>,
49        size: NonZero<usize>,
50        align: Option<usize>,
51        init: Option<fn()>,
52        flags: impl Into<UmaFlags>,
53    ) -> Self {
54        let name = name.into();
55        let flags = flags.into();
56        let (keg, mut flags) = if flags.has_any(UmaFlags::Secondary) {
57            todo!()
58        } else {
59            // We use a different approach here to make it idiomatic to Rust. On Orbis it will
60            // construct a keg here if it is passed from the caller. If not it will allocate a new
61            // keg from masterzone_k.
62            let keg = match keg {
63                Some(v) => v,
64                None => UmaKeg::new(vm, size, align.unwrap_or(Self::ALIGN_CACHE), init, flags),
65            };
66
67            (keg, UmaFlags::zeroed())
68        };
69
70        // Get type and uz_count.
71        let mut ty = ZoneType::Other;
72        let mut count = 0;
73
74        if !keg.flags().has_any(UmaFlags::Internal) {
75            count = if !keg.flags().has_any(UmaFlags::MaxBucket) {
76                min(keg.item_per_slab(), Uma::BUCKET_MAX)
77            } else {
78                Uma::BUCKET_MAX
79            };
80
81            match name.as_str() {
82                "mbuf_packet" => {
83                    ty = ZoneType::MbufPacket;
84                    count = 4;
85                }
86                "mbuf_cluster_pack" => {
87                    ty = ZoneType::MbufClusterPack;
88                    count = Uma::BUCKET_MAX;
89                }
90                "mbuf_jumbo_page" => {
91                    ty = ZoneType::MbufJumboPage;
92                    count = 1;
93                }
94                "mbuf" => {
95                    ty = ZoneType::Mbuf;
96                    count = 16;
97                }
98                "mbuf_cluster" => {
99                    ty = ZoneType::MbufCluster;
100                    count = 1;
101                }
102                _ => (),
103            }
104        }
105
106        // Construct uma_zone.
107        let inherit = UmaFlags::Offpage
108            | UmaFlags::Malloc
109            | UmaFlags::Hash
110            | UmaFlags::VToSlab
111            | UmaFlags::Bucket
112            | UmaFlags::Internal
113            | UmaFlags::CacheOnly;
114
115        flags |= keg.flags() & inherit;
116
117        Self {
118            bucket_enable,
119            bucket_keys,
120            bucket_zones,
121            ty,
122            size: keg.size(),
123            slab: Self::fetch_slab,
124            init: None,
125            ctor: None,
126            caches: CpuLocal::new(|_| RefCell::default()),
127            flags,
128            state: Mutex::new(ZoneState {
129                kegs: LinkedList::from([keg]),
130                full_buckets: VecDeque::default(),
131                free_buckets: VecDeque::default(),
132                alloc_count: 0,
133                free_count: 0,
134                count,
135                fills: 0,
136            }),
137        }
138    }
139
140    pub fn size(&self) -> NonZero<usize> {
141        self.size
142    }
143
144    /// See `uma_zalloc_arg` on the Orbis for a reference.
145    ///
146    /// # Reference offsets
147    /// | Version | Offset |
148    /// |---------|--------|
149    /// |PS4 11.00|0x13E750|
150    pub fn alloc(&self, flags: Alloc) -> *mut u8 {
151        if flags.has_any(Alloc::Wait) {
152            // TODO: The Orbis also modify td_pflags on a certain condition.
153            let td = current_thread();
154
155            if !td.can_sleep() {
156                panic!("attempt to do waitable heap allocation in a non-sleeping context");
157            }
158        }
159
160        loop {
161            // Try allocate from per-CPU cache first so we don't need to acquire a mutex lock.
162            let caches = self.caches.lock();
163            let mem = Self::alloc_from_cache(caches.borrow_mut().deref_mut());
164
165            if !mem.is_null() {
166                return mem;
167            }
168
169            drop(caches); // Exit from non-sleeping context before acquire the mutex.
170
171            // Cache not found, allocate from the zone. We need to re-check the cache again because
172            // we may on a different CPU since we drop the CPU pinning on the above.
173            let mut state = self.state.lock();
174            let caches = self.caches.lock();
175            let mut cache = caches.borrow_mut();
176            let mem = Self::alloc_from_cache(&mut cache);
177
178            if !mem.is_null() {
179                return mem;
180            }
181
182            // TODO: What actually we are doing here?
183            state.alloc_count += core::mem::take(&mut cache.allocs);
184            state.free_count += core::mem::take(&mut cache.frees);
185
186            if let Some(b) = cache.alloc.take() {
187                state.free_buckets.push_front(b);
188            }
189
190            if let Some(b) = state.full_buckets.pop_front() {
191                cache.alloc = Some(b);
192
193                // Seems like this should never fail.
194                let m = Self::alloc_from_cache(&mut cache);
195
196                assert!(!m.is_null());
197
198                return m;
199            }
200
201            drop(cache);
202            drop(caches);
203
204            // TODO: What is this?
205            if matches!(
206                self.ty,
207                ZoneType::MbufPacket
208                    | ZoneType::MbufJumboPage
209                    | ZoneType::Mbuf
210                    | ZoneType::MbufCluster
211            ) {
212                if flags.has_any(Alloc::Wait) {
213                    todo!()
214                }
215
216                todo!()
217            }
218
219            // TODO: What is this?
220            if !matches!(
221                self.ty,
222                ZoneType::MbufCluster
223                    | ZoneType::Mbuf
224                    | ZoneType::MbufJumboPage
225                    | ZoneType::MbufPacket
226                    | ZoneType::MbufClusterPack
227            ) && state.count < Uma::BUCKET_MAX
228            {
229                state.count += 1;
230            }
231
232            if self.alloc_bucket(&mut state, flags) {
233                return self.alloc_item(&mut state, flags);
234            }
235        }
236    }
237
238    fn alloc_from_cache(c: &mut UmaCache) -> *mut u8 {
239        while let Some(b) = c.alloc.map(|v| v.as_ptr()) {
240            if unsafe { (*b).hdr.len != 0 } {
241                todo!()
242            }
243
244            if c.free
245                .map(|v| v.as_ptr())
246                .is_some_and(|b| unsafe { (*b).hdr.len != 0 })
247            {
248                core::mem::swap(&mut c.alloc, &mut c.free);
249                continue;
250            }
251
252            break;
253        }
254
255        null_mut()
256    }
257
258    /// See `zone_alloc_bucket` on the Orbis for a reference.
259    ///
260    /// # Reference offsets
261    /// | Version | Offset |
262    /// |---------|--------|
263    /// |PS4 11.00|0x13EBA0|
264    fn alloc_bucket(&self, state: &mut ZoneState, flags: Alloc) -> bool {
265        // Get bucket.
266        let b = match state.free_buckets.front() {
267            Some(_) => todo!(),
268            None => {
269                if self.bucket_enable.load(Ordering::Relaxed) {
270                    // Get allocation flags. On Orbis it will remove M_ZERO from the flags but we do
271                    // the opposite to eliminate the chance of dangling pointer in bucket items.
272                    let mut flags = flags | Alloc::Zero;
273
274                    if self.flags.has_any(UmaFlags::CacheOnly) {
275                        flags |= Alloc::NoVm;
276                    }
277
278                    // Alloc a bucket.
279                    let i = (state.count + 15) >> Uma::BUCKET_SHIFT;
280                    let k = self.bucket_keys[i];
281                    let b = &self.bucket_zones[k];
282                    let b = b.alloc_item(&mut b.state.lock(), flags);
283
284                    if b.is_null() {
285                        todo!()
286                    }
287
288                    // Initialize bucket.
289                    let h = BucketHdr { len: 0 };
290
291                    unsafe { core::ptr::write(b.cast(), h) };
292
293                    core::ptr::slice_from_raw_parts_mut(b, Uma::BUCKET_SIZES[k]) as *mut UmaBucket
294                } else {
295                    todo!()
296                }
297            }
298        };
299
300        // SAFETY: We have exclusive access to the bucket.
301        let b = unsafe { &mut *b };
302
303        if state.fills < config().cpu_count().get().into() {
304            let n = min(b.items.len(), state.count);
305            let k = state.kegs.front_mut().unwrap();
306            let mut f = flags;
307
308            state.fills += 1;
309
310            while b.hdr.len < n {
311                let s = match unsafe { (self.slab)(k, f) } {
312                    Some(v) => v.as_ptr(),
313                    None => todo!(),
314                };
315
316                while b.hdr.len < n {
317                    let i = unsafe { (*s).alloc_item(k) };
318
319                    if i.is_null() {
320                        break;
321                    }
322
323                    b.items[b.hdr.len] = i;
324                    b.hdr.len += 1;
325                }
326
327                f |= Alloc::NoWait;
328            }
329
330            if self.init.is_some() {
331                todo!()
332            }
333
334            state.fills -= 1;
335
336            if b.hdr.len != 0 {
337                state
338                    .full_buckets
339                    .push_front(unsafe { NonNull::new_unchecked(b) });
340
341                return true;
342            }
343
344            todo!()
345        }
346
347        todo!()
348    }
349
350    /// See `zone_alloc_item` on the Orbis for a reference.
351    ///
352    /// # Reference offsets
353    /// | Version | Offset |
354    /// |---------|--------|
355    /// |PS4 11.00|0x13DD50|
356    fn alloc_item(&self, state: &mut ZoneState, flags: Alloc) -> *mut u8 {
357        // Get a slab.
358        let keg = state.kegs.front_mut().unwrap();
359        let slab = unsafe { (self.slab)(keg, flags) };
360
361        if let Some(slab) = slab {
362            let item = unsafe { slab.as_ref().alloc_item(keg) };
363
364            state.alloc_count += 1;
365
366            if self.init.is_none_or(|f| f(item, self.size, flags)) {
367                if self.ctor.is_none_or(|f| f(item, self.size, flags)) {
368                    if flags.has_any(Alloc::Zero) {
369                        unsafe { item.write_bytes(0, self.size.get()) };
370                    }
371
372                    return item;
373                } else {
374                    todo!()
375                }
376            } else {
377                todo!()
378            }
379        }
380
381        todo!()
382    }
383
384    /// See `zone_fetch_slab` on the Orbis for a reference.
385    ///
386    /// # Reference offsets
387    /// | Version | Offset |
388    /// |---------|--------|
389    /// |PS4 11.00|0x141DB0|
390    unsafe fn fetch_slab(keg: &mut UmaKeg, flags: Alloc) -> Option<NonNull<Slab>> {
391        if !keg.flags().has_any(UmaFlags::Bucket) || keg.recurse() == 0 {
392            loop {
393                if let Some(v) = unsafe { keg.fetch_slab(flags) } {
394                    return Some(v);
395                }
396
397                if flags.has_any(Alloc::NoWait | Alloc::NoVm) {
398                    break;
399                }
400            }
401        }
402
403        None
404    }
405}
406
407/// Contains mutable data for [UmaZone].
408struct ZoneState {
409    kegs: LinkedList<UmaKeg>,                   // uz_kegs + uz_klink
410    full_buckets: VecDeque<NonNull<UmaBucket>>, // uz_full_bucket
411    free_buckets: VecDeque<NonNull<UmaBucket>>, // uz_free_bucket
412    alloc_count: u64,                           // uz_allocs
413    free_count: u64,                            // uz_frees
414    count: usize,                               // uz_count
415    fills: u16,                                 // uz_fills
416}
417
418unsafe impl Send for ZoneState {}
419
420/// Type of [UmaZone].
421#[derive(Clone, Copy)]
422enum ZoneType {
423    Other,
424    /// `zone_pack`.
425    MbufPacket,
426    /// `zone_jumbop`.
427    MbufJumboPage,
428    /// `zone_mbuf`.
429    Mbuf,
430    /// `zone_clust`.
431    MbufCluster,
432    /// `zone_clust_pack`.
433    MbufClusterPack,
434}
435
436/// Implementation of `uma_cache` structure.
437#[derive(Default)]
438struct UmaCache {
439    alloc: Option<NonNull<UmaBucket>>, // uc_allocbucket
440    free: Option<NonNull<UmaBucket>>,  // uc_freebucket
441    allocs: u64,                       // uc_allocs
442    frees: u64,                        // uc_frees
443}
444
445unsafe impl Send for UmaCache {}