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::mem::Strong;
5use crate::vm::Vm;
6use alloc::collections::VecDeque;
7use alloc::collections::linked_list::LinkedList;
8use alloc::string::String;
9use alloc::sync::Arc;
10use alloc::vec::Vec;
11use core::cell::RefCell;
12use core::cmp::min;
13use core::num::NonZero;
14use core::ops::DerefMut;
15use core::pin::Pin;
16use core::ptr::{NonNull, null_mut};
17use core::sync::atomic::{AtomicBool, Ordering};
18
19/// Implementation of `uma_zone` structure.
20pub struct UmaZone {
21    bucket_enable: Arc<AtomicBool>,
22    bucket_keys: Arc<Vec<usize>>,
23    bucket_zones: Arc<Vec<UmaZone>>,
24    ty: ZoneType,
25    size: NonZero<usize>,                                              // uz_size
26    slab: unsafe fn(&Arc<UmaKeg>, Alloc) -> Option<Pin<Strong<Slab>>>, // uz_slab
27    init: Option<fn(*mut u8, NonZero<usize>, Alloc) -> bool>,          // uz_init
28    ctor: Option<fn(*mut u8, NonZero<usize>, Alloc) -> bool>,          // uz_ctor
29    dtor: Option<fn()>,                                                // uz_dtor
30    caches: CpuLocal<RefCell<UmaCache>>,                               // uz_cpu
31    state: Mutex<ZoneState>,
32}
33
34impl UmaZone {
35    const ALIGN_CACHE: usize = 63; // uma_align_cache
36
37    /// See `zone_ctor` on Orbis for a reference.
38    ///
39    /// # Reference offsets
40    /// | Version | Offset |
41    /// |---------|--------|
42    /// |PS4 11.00|0x13D490|
43    pub(super) fn new(
44        vm: &'static Vm,
45        bucket_enable: Arc<AtomicBool>,
46        bucket_keys: Arc<Vec<usize>>,
47        bucket_zones: Arc<Vec<UmaZone>>,
48        args: ZoneArgs,
49    ) -> Self {
50        let name = args.name;
51        let flags = args.flags;
52        let (keg, mut flags) = if flags.has_any(UmaFlags::Secondary) {
53            todo!()
54        } else {
55            // We use a different approach here to make it idiomatic to Rust. On Orbis it will
56            // construct a keg here if it is passed from the caller. If not it will allocate a new
57            // keg from masterzone_k.
58            let keg = match args.keg {
59                Some(v) => v,
60                None => UmaKeg::new(
61                    vm,
62                    args.size,
63                    args.align.unwrap_or(Self::ALIGN_CACHE),
64                    args.init,
65                    flags,
66                ),
67            };
68
69            (keg, UmaFlags::zeroed())
70        };
71
72        // Get type and uz_count.
73        let mut ty = ZoneType::Other;
74        let mut count = 0;
75
76        if !keg.flags().has_any(UmaFlags::Internal) {
77            count = if !keg.flags().has_any(UmaFlags::MaxBucket) {
78                min(keg.item_per_slab(), Uma::BUCKET_MAX)
79            } else {
80                Uma::BUCKET_MAX
81            };
82
83            match name.as_str() {
84                "mbuf_packet" => {
85                    ty = ZoneType::MbufPacket;
86                    count = 4;
87                }
88                "mbuf_cluster_pack" => {
89                    ty = ZoneType::MbufClusterPack;
90                    count = Uma::BUCKET_MAX;
91                }
92                "mbuf_jumbo_page" => {
93                    ty = ZoneType::MbufJumboPage;
94                    count = 1;
95                }
96                "mbuf" => {
97                    ty = ZoneType::Mbuf;
98                    count = 16;
99                }
100                "mbuf_cluster" => {
101                    ty = ZoneType::MbufCluster;
102                    count = 1;
103                }
104                _ => (),
105            }
106        }
107
108        // Construct uma_zone.
109        let inherit = UmaFlags::Offpage
110            | UmaFlags::Malloc
111            | UmaFlags::Hash
112            | UmaFlags::VToSlab
113            | UmaFlags::Bucket
114            | UmaFlags::Internal
115            | UmaFlags::CacheOnly;
116
117        flags |= keg.flags() & inherit;
118
119        Self {
120            bucket_enable,
121            bucket_keys,
122            bucket_zones,
123            ty,
124            size: keg.size(),
125            slab: Self::fetch_slab,
126            init: None,
127            ctor: args.ctor,
128            dtor: args.dtor,
129            caches: CpuLocal::new(|_| RefCell::default()),
130            state: Mutex::new(ZoneState {
131                kegs: LinkedList::from([keg]),
132                full_buckets: VecDeque::default(),
133                free_buckets: VecDeque::default(),
134                alloc_count: 0,
135                free_count: 0,
136                count,
137                fills: 0,
138                flags,
139            }),
140        }
141    }
142
143    pub fn size(&self) -> NonZero<usize> {
144        self.size
145    }
146
147    /// See `uma_zalloc_arg` on the Orbis for a reference.
148    ///
149    /// # Reference offsets
150    /// | Version | Offset |
151    /// |---------|--------|
152    /// |PS4 11.00|0x13E750|
153    pub fn alloc(&self, flags: Alloc) -> *mut u8 {
154        if flags.has_any(Alloc::Wait) {
155            // TODO: The Orbis also modify td_pflags on a certain condition.
156            let td = current_thread();
157
158            if !td.can_sleep() {
159                panic!("attempt to do waitable heap allocation in a non-sleeping context");
160            }
161        }
162
163        let m = loop {
164            // Try allocate from per-CPU cache first so we don't need to acquire a mutex lock.
165            let caches = self.caches.lock();
166            let mem = Self::alloc_from_cache(caches.borrow_mut().deref_mut());
167
168            if !mem.is_null() {
169                break mem;
170            }
171
172            drop(caches); // Exit from non-sleeping context before acquire the mutex.
173
174            // Cache not found, allocate from the zone. We need to re-check the cache again because
175            // we may on a different CPU since we drop the CPU pinning on the above.
176            let mut state = self.state.lock();
177            let caches = self.caches.lock();
178            let mut cache = caches.borrow_mut();
179            let mem = Self::alloc_from_cache(&mut cache);
180
181            if !mem.is_null() {
182                break mem;
183            }
184
185            // TODO: What actually we are doing here?
186            state.alloc_count += core::mem::take(&mut cache.allocs);
187            state.free_count += core::mem::take(&mut cache.frees);
188
189            if let Some(b) = cache.alloc.take() {
190                state.free_buckets.push_front(b);
191            }
192
193            if let Some(b) = state.full_buckets.pop_front() {
194                cache.alloc = Some(b);
195
196                // Seems like this should never fail.
197                let m = Self::alloc_from_cache(&mut cache);
198
199                debug_assert!(!m.is_null());
200
201                break m;
202            }
203
204            drop(cache);
205            drop(caches);
206
207            // TODO: What is this?
208            if matches!(
209                self.ty,
210                ZoneType::MbufPacket
211                    | ZoneType::MbufJumboPage
212                    | ZoneType::Mbuf
213                    | ZoneType::MbufCluster
214            ) {
215                if flags.has_any(Alloc::Wait) {
216                    todo!()
217                }
218
219                todo!()
220            }
221
222            // TODO: What is this?
223            if !matches!(
224                self.ty,
225                ZoneType::MbufCluster
226                    | ZoneType::Mbuf
227                    | ZoneType::MbufJumboPage
228                    | ZoneType::MbufPacket
229                    | ZoneType::MbufClusterPack
230            ) && state.count < Uma::BUCKET_MAX
231            {
232                state.count += 1;
233            }
234
235            if self.alloc_bucket(&mut state, flags) {
236                return self.alloc_item(&mut state, flags);
237            }
238        };
239
240        // The Orbis apply M_ZERO after calling uz_ctor, which seems like a bug.
241        if flags.has_any(Alloc::Zero) {
242            unsafe { m.write_bytes(0, self.size.get()) };
243        }
244
245        if self.ctor.is_none_or(move |f| f(m, self.size, flags)) {
246            m
247        } else {
248            todo!()
249        }
250    }
251
252    /// See `uma_zfree_arg` on the Orbis for a reference.
253    ///
254    /// # Safety
255    /// `item` either allocated from [Self::alloc()] or null.
256    ///
257    /// # Reference offsets
258    /// | Version | Offset |
259    /// |---------|--------|
260    /// |PS4 11.00|0x13EFC0|
261    pub unsafe fn free(&self, item: *mut u8) {
262        if item.is_null() {
263            return;
264        } else if self.dtor.is_some() {
265            todo!()
266        }
267
268        // Check zone type.
269        let mut state = self.state.lock();
270
271        if self.ty != ZoneType::MbufPacket
272            && self.ty != ZoneType::MbufClusterPack
273            && self.ty != ZoneType::MbufJumboPage
274            && self.ty != ZoneType::Mbuf
275            && self.ty != ZoneType::MbufCluster
276            && state.flags.has_any(UmaFlags::Full)
277        {
278            todo!()
279        }
280
281        // TODO: The uz_flags check on above defeat below optimization. On Orbis they did not put
282        // uz_flags behind a uz_lock.
283        let mut caches = self.caches.lock();
284        let mut cache = caches.borrow_mut();
285
286        loop {
287            while let Some(b) = cache.free.map(|mut v| unsafe { v.as_mut() }) {
288                loop {
289                    // Check if bucket has available space.
290                    let n = if matches!(
291                        self.ty,
292                        ZoneType::MbufCluster
293                            | ZoneType::Mbuf
294                            | ZoneType::MbufJumboPage
295                            | ZoneType::MbufClusterPack
296                            | ZoneType::MbufPacket
297                    ) {
298                        todo!()
299                    } else {
300                        b.items.len()
301                    };
302
303                    if let i = b.hdr.len
304                        && i < n
305                    {
306                        b.items[i] = item;
307                        b.hdr.len = i + 1;
308
309                        cache.frees += 1;
310
311                        if self.ty != ZoneType::MbufPacket
312                            && self.ty != ZoneType::MbufJumboPage
313                            && self.ty != ZoneType::Mbuf
314                            && self.ty != ZoneType::MbufCluster
315                        {
316                            return;
317                        }
318
319                        todo!()
320                    }
321
322                    todo!()
323                }
324            }
325
326            state.alloc_count += core::mem::take(&mut cache.allocs);
327            state.free_count += core::mem::take(&mut cache.frees);
328
329            if cache.free.take().is_some() {
330                todo!()
331            }
332
333            if let Some(b) = state.free_buckets.pop_front() {
334                cache.free = Some(b);
335                continue;
336            }
337
338            drop(cache);
339            drop(caches);
340
341            if !self.bucket_enable.load(Ordering::Relaxed) {
342                todo!()
343            }
344
345            // Get bucket zone.
346            let i = (state.count + 15) >> Uma::BUCKET_SHIFT;
347            let k = self.bucket_keys[i];
348            let b = &self.bucket_zones[k];
349            let f = Alloc::from((u32::from(state.flags) >> 31) << 9); // TODO: Refactor this.
350
351            drop(state);
352
353            // Alloc a bucket. The Orbis does not force M_ZERO here but we do the opposite to
354            // eliminate the chance of dangling pointer in bucket items.
355            let b = b.alloc_item(&mut b.state.lock(), f | Alloc::Zero | Alloc::NoWait);
356
357            if b.is_null() {
358                todo!()
359            }
360
361            // Initialize bucket.
362            let h = BucketHdr { len: 0 };
363            let b = unsafe {
364                core::ptr::write(b.cast(), h);
365                core::ptr::slice_from_raw_parts_mut(b, Uma::BUCKET_SIZES[k]) as *mut UmaBucket
366            };
367
368            // Add to free list.
369            state = self.state.lock();
370            state
371                .free_buckets
372                .push_front(unsafe { NonNull::new_unchecked(b) });
373
374            caches = self.caches.lock();
375            cache = caches.borrow_mut();
376        }
377    }
378
379    fn alloc_from_cache(c: &mut UmaCache) -> *mut u8 {
380        while let Some(b) = c.alloc.map(|v| v.as_ptr()) {
381            if let Some(v) = unsafe { (*b).hdr.len.checked_sub(1) } {
382                unsafe { (*b).hdr.len = v };
383
384                c.allocs += 1;
385
386                return unsafe { (*b).items[v] };
387            }
388
389            if c.free
390                .map(|v| v.as_ptr())
391                .is_some_and(|b| unsafe { (*b).hdr.len != 0 })
392            {
393                core::mem::swap(&mut c.alloc, &mut c.free);
394                continue;
395            }
396
397            break;
398        }
399
400        null_mut()
401    }
402
403    /// See `zone_alloc_bucket` on the Orbis for a reference.
404    ///
405    /// # Reference offsets
406    /// | Version | Offset |
407    /// |---------|--------|
408    /// |PS4 11.00|0x13EBA0|
409    fn alloc_bucket(&self, state: &mut ZoneState, flags: Alloc) -> bool {
410        // Get bucket.
411        let b = match state.free_buckets.front() {
412            Some(_) => todo!(),
413            None => {
414                if self.bucket_enable.load(Ordering::Relaxed) {
415                    // Get allocation flags. On Orbis it will remove M_ZERO from the flags but we do
416                    // the opposite to eliminate the chance of dangling pointer in bucket items.
417                    let mut flags = flags | Alloc::Zero;
418
419                    if state.flags.has_any(UmaFlags::CacheOnly) {
420                        flags |= Alloc::NoVm;
421                    }
422
423                    // Alloc a bucket.
424                    let i = (state.count + 15) >> Uma::BUCKET_SHIFT;
425                    let k = self.bucket_keys[i];
426                    let b = &self.bucket_zones[k];
427                    let b = b.alloc_item(&mut b.state.lock(), flags);
428
429                    if b.is_null() {
430                        todo!()
431                    }
432
433                    // Initialize bucket.
434                    let h = BucketHdr { len: 0 };
435                    let s = Uma::BUCKET_SIZES[k];
436
437                    unsafe {
438                        core::ptr::write(b.cast(), h);
439                        core::ptr::slice_from_raw_parts_mut(b, s) as *mut UmaBucket
440                    }
441                } else {
442                    todo!()
443                }
444            }
445        };
446
447        // SAFETY: We have exclusive access to the bucket.
448        let b = unsafe { &mut *b };
449
450        if state.fills < config().cpu_count().get().into() {
451            let n = min(b.items.len(), state.count);
452            let k = state.kegs.front().unwrap();
453            let mut f = flags;
454
455            state.fills += 1;
456
457            while b.hdr.len < n {
458                let s = match unsafe { (self.slab)(k, f) } {
459                    Some(v) => v,
460                    None => todo!(),
461                };
462
463                while b.hdr.len < n {
464                    let i = s.alloc_item();
465
466                    if i.is_null() {
467                        break;
468                    }
469
470                    b.items[b.hdr.len] = i;
471                    b.hdr.len += 1;
472                }
473
474                f |= Alloc::NoWait;
475            }
476
477            if self.init.is_some() {
478                todo!()
479            }
480
481            state.fills -= 1;
482
483            if b.hdr.len != 0 {
484                state
485                    .full_buckets
486                    .push_front(unsafe { NonNull::new_unchecked(b) });
487
488                return true;
489            }
490
491            todo!()
492        }
493
494        todo!()
495    }
496
497    /// See `zone_alloc_item` on the Orbis for a reference.
498    ///
499    /// # Reference offsets
500    /// | Version | Offset |
501    /// |---------|--------|
502    /// |PS4 11.00|0x13DD50|
503    fn alloc_item(&self, state: &mut ZoneState, flags: Alloc) -> *mut u8 {
504        // Get a slab.
505        let keg = state.kegs.front().unwrap();
506        let slab = unsafe { (self.slab)(keg, flags) };
507
508        if let Some(slab) = slab {
509            let item = slab.alloc_item();
510
511            state.alloc_count += 1;
512
513            if self.init.is_none_or(|f| f(item, self.size, flags)) {
514                if self.ctor.is_none_or(|f| f(item, self.size, flags)) {
515                    if flags.has_any(Alloc::Zero) {
516                        unsafe { item.write_bytes(0, self.size.get()) };
517                    }
518
519                    return item;
520                } else {
521                    todo!()
522                }
523            } else {
524                todo!()
525            }
526        }
527
528        todo!()
529    }
530
531    /// See `zone_fetch_slab` on the Orbis for a reference.
532    ///
533    /// # Reference offsets
534    /// | Version | Offset |
535    /// |---------|--------|
536    /// |PS4 11.00|0x141DB0|
537    unsafe fn fetch_slab(keg: &Arc<UmaKeg>, flags: Alloc) -> Option<Pin<Strong<Slab>>> {
538        if !keg.flags().has_any(UmaFlags::Bucket) || keg.recurse() == 0 {
539            loop {
540                if let Some(v) = unsafe { keg.fetch_slab(flags) } {
541                    return Some(v);
542                }
543
544                if flags.has_any(Alloc::NoWait | Alloc::NoVm) {
545                    break;
546                }
547            }
548        }
549
550        None
551    }
552}
553
554/// Contains mutable data for [UmaZone].
555struct ZoneState {
556    kegs: LinkedList<Arc<UmaKeg>>,              // uz_kegs + uz_klink
557    full_buckets: VecDeque<NonNull<UmaBucket>>, // uz_full_bucket
558    free_buckets: VecDeque<NonNull<UmaBucket>>, // uz_free_bucket
559    alloc_count: u64,                           // uz_allocs
560    free_count: u64,                            // uz_frees
561    count: usize,                               // uz_count
562    fills: u16,                                 // uz_fills
563    flags: UmaFlags,                            // uz_flags
564}
565
566unsafe impl Send for ZoneState {}
567
568/// Type of [UmaZone].
569#[derive(Clone, Copy, PartialEq, Eq)]
570enum ZoneType {
571    Other,
572    /// `zone_pack`.
573    MbufPacket,
574    /// `zone_jumbop`.
575    MbufJumboPage,
576    /// `zone_mbuf`.
577    Mbuf,
578    /// `zone_clust`.
579    MbufCluster,
580    /// `zone_clust_pack`.
581    MbufClusterPack,
582}
583
584/// Implementation of `uma_cache` structure.
585#[derive(Default)]
586struct UmaCache {
587    alloc: Option<NonNull<UmaBucket>>, // uc_allocbucket
588    /// The pointer must be unique.
589    free: Option<NonNull<UmaBucket>>, // uc_freebucket
590    allocs: u64,                       // uc_allocs
591    frees: u64,                        // uc_frees
592}
593
594unsafe impl Send for UmaCache {}
595
596/// Implementation of `uma_zctor_args` structure.
597pub struct ZoneArgs {
598    pub name: String,                                             // name
599    pub keg: Option<Arc<UmaKeg>>,                                 // keg
600    pub size: NonZero<usize>,                                     // size
601    pub align: Option<usize>,                                     // align
602    pub init: Option<fn()>,                                       // uminit
603    pub ctor: Option<fn(*mut u8, NonZero<usize>, Alloc) -> bool>, // ctor
604    pub dtor: Option<fn()>,                                       // dtor
605    pub flags: UmaFlags,                                          // flags
606}