Skip to main content

pow2_range_allocator/
lib.rs

1// Copyright 2016 The Fuchsia Authors
2// Copyright (c) 2016, Google, Inc. All rights reserved
3//
4// Use of this source code is governed by a MIT-style
5// license that can be found in the LICENSE file or at
6// https://opensource.org/licenses/MIT
7//
8// Ported from zircon/kernel/lib/pow2_range_allocator/pow2_range_allocator.cc and
9// zircon/kernel/lib/pow2_range_allocator/include/lib/pow2_range_allocator.h
10
11#![no_std]
12
13use core::convert::Infallible;
14use core::pin::Pin;
15use debug::{ltracef, tracef};
16use fbl::{Array, DoublyLinkedList, DoublyLinkedListContainable, DoublyLinkedListNode};
17use pin_init::{PinInit, pin_data, pin_init, pinned_drop};
18use zx_status::Status;
19
20const LOCAL_TRACE: u32 = 0;
21
22/// Bookkeeping for a single power of 2 sized, power of 2 aligned sub-range.
23#[derive(DoublyLinkedListContainable)]
24struct Block {
25    #[dll_node]
26    node: DoublyLinkedListNode<Block>,
27    bucket: u32,
28    start: u32,
29}
30
31impl Block {
32    const fn new() -> Self {
33        Self { node: DoublyLinkedListNode::new(), bucket: 0, start: 0 }
34    }
35}
36
37/// Bookkeeping for a range of `u32`s which was handed to the allocator via `add_range`.
38#[derive(DoublyLinkedListContainable)]
39struct Range {
40    #[dll_node]
41    node: DoublyLinkedListNode<Range>,
42    start: u32,
43    len: u32,
44}
45
46impl Range {
47    const fn new() -> Self {
48        Self { node: DoublyLinkedListNode::new(), start: 0, len: 0 }
49    }
50}
51
52/// Frees every `Block` remaining in `list`.
53///
54/// The lists hold raw pointers and perform no lifecycle management, so ownership of each element
55/// has to be reclaimed explicitly.
56fn free_block_list(list: &mut DoublyLinkedList<*mut Block>) {
57    while let Some(block) = list.pop_front() {
58        // SAFETY: Every `Block` in one of the allocator's lists was allocated with
59        // `kalloc::Box::try_new` and leaked with `kalloc::Box::into_raw`.  It has just been removed
60        // from `list`, so this is the only pointer to it.
61        drop(unsafe { kalloc::Box::from_raw(block) });
62    }
63}
64
65/// Frees every `Range` remaining in `list`.
66fn free_range_list(list: &mut DoublyLinkedList<*mut Range>) {
67    while let Some(range) = list.pop_front() {
68        // SAFETY: Every `Range` in the allocator's range list was allocated with
69        // `kalloc::Box::try_new` and leaked with `kalloc::Box::into_raw`.  It has just been removed
70        // from `list`, so this is the only pointer to it.
71        drop(unsafe { kalloc::Box::from_raw(range) });
72    }
73}
74
75/// All of the mutable state of a `Pow2RangeAllocator`, protected by the allocator's mutex.
76#[pin_data(PinnedDrop)]
77struct Inner {
78    #[pin]
79    ranges: DoublyLinkedList<*mut Range>,
80    #[pin]
81    unused_blocks: DoublyLinkedList<*mut Block>,
82    #[pin]
83    allocated_blocks: DoublyLinkedList<*mut Block>,
84    free_block_buckets: Array<DoublyLinkedList<*mut Block>>,
85    bucket_count: u32,
86}
87
88// SAFETY: The raw pointers held by `Inner`'s lists are exclusively owned by the allocator; they
89// point at `kalloc::Box` allocations which are never handed out and which are only ever reachable
90// with the allocator's mutex held.  Ownership of the whole state can therefore be moved between
91// threads, which is what the C++ implementation does by placing a `Pow2RangeAllocator` in shared
92// storage and serializing all access on its `DECLARE_MUTEX`.
93unsafe impl Send for Inner {}
94
95#[pinned_drop]
96impl PinnedDrop for Inner {
97    fn drop(self: Pin<&mut Self>) {
98        // The C++ implementation has no destructor at all; a `Pow2RangeAllocator` which is
99        // destroyed without calling `Free()` simply leaks its bookkeeping.  Rust's lists hold raw
100        // pointers and debug assert that they are empty when dropped, so the bookkeeping is
101        // released here instead.
102        // SAFETY: We are being dropped, so nothing is moved out of the pinned `Inner`.
103        let this = unsafe { self.get_unchecked_mut() };
104        this.release();
105    }
106}
107
108impl Inner {
109    fn new() -> impl PinInit<Self, Infallible> {
110        pin_init!(Self {
111            ranges <- DoublyLinkedList::<*mut Range>::new(),
112            unused_blocks <- DoublyLinkedList::<*mut Block>::new(),
113            allocated_blocks <- DoublyLinkedList::<*mut Block>::new(),
114            free_block_buckets: Array::new(),
115            bucket_count: 0,
116        })
117    }
118
119    fn init(&mut self, bucket_count: u32) -> Result<(), Status> {
120        let mut uninit = kalloc::Box::<[DoublyLinkedList<*mut Block>]>::try_new_uninit_slice(
121            bucket_count as usize,
122        )
123        .map_err(|_| {
124            tracef!("Failed to allocate storage for {} free bucket lists!\n", bucket_count);
125            Status::NO_MEMORY
126        })?;
127        for item in uninit.iter_mut() {
128            let init = DoublyLinkedList::<*mut Block>::new();
129            // SAFETY: `item.as_mut_ptr()` is a valid, writable pointer to uninitialized memory
130            // in the newly allocated slice.
131            unsafe {
132                let _ = init.__pinned_init(item.as_mut_ptr());
133            }
134        }
135        // SAFETY: All elements in `uninit` were initialized in the loop above.
136        let buf = unsafe { uninit.assume_init() };
137        self.free_block_buckets = Array::from_box(buf);
138        self.bucket_count = bucket_count;
139        Ok(())
140    }
141
142    /// Releases every piece of bookkeeping still held by the allocator.
143    fn release(&mut self) {
144        free_range_list(&mut self.ranges);
145        free_block_list(&mut self.unused_blocks);
146        free_block_list(&mut self.allocated_blocks);
147        for bucket in self.free_block_buckets.iter_mut() {
148            free_block_list(bucket);
149        }
150    }
151
152    /// Returns a block of bookkeeping, recycling one from the unused list if possible.
153    ///
154    /// Returns `None` if a new block had to be allocated and the allocation failed.
155    fn get_unused_block(&mut self) -> Option<*mut Block> {
156        if !self.unused_blocks.is_empty() {
157            return self.unused_blocks.pop_front();
158        }
159
160        match kalloc::Box::try_new(Block::new()) {
161            Ok(block) => Some(kalloc::Box::into_raw(block)),
162            Err(_) => None,
163        }
164    }
165
166    /// Returns `block` to its proper free bucket, merging with its buddy as many times as possible.
167    ///
168    /// # Safety
169    ///
170    /// `block` must be a valid pointer to a `Block` owned by this allocator which is not currently
171    /// a member of any list.
172    unsafe fn return_free_block(&mut self, mut block: *mut Block) {
173        // The C++ implementation recurses into `ReturnFreeBlock` after a successful merge.  This is
174        // the same algorithm written as a loop.
175        loop {
176            debug_assert!(!block.is_null());
177            // SAFETY: The caller guarantees that `block` points at a valid `Block` which is not in
178            // any container.  On subsequent loop iterations `block` was just erased from a bucket.
179            let block_ref = unsafe { &*block };
180            debug_assert!(block_ref.bucket < self.bucket_count);
181            debug_assert!(!block_ref.node.in_container());
182
183            let bucket = block_ref.bucket;
184            let block_start = block_ref.start;
185            let block_len = 1u32 << bucket;
186            debug_assert_eq!(block_start & (block_len - 1), 0);
187
188            // Return the block to its proper free bucket, sorted by base ID.  Start by
189            // finding the block which should come after this block in the list.
190            let list = &mut self.free_block_buckets[bucket as usize];
191            let mut inserted = false;
192            {
193                let mut cursor = list.cursor_front_mut();
194                while let Some(after) = cursor.get() {
195                    // We do not allow ranges to overlap.
196                    let after_len = 1u32 << after.bucket;
197                    let after_start = after.start;
198                    debug_assert!(
199                        (block_start >= after_start.wrapping_add(after_len))
200                            || (after_start >= block_start.wrapping_add(block_len))
201                    );
202
203                    if after_start > block_start {
204                        // SAFETY: `block` is a valid pointer to a `Block` which is not in any
205                        // container, and it is owned by this allocator so it outlives its
206                        // membership in the list.
207                        unsafe { cursor.insert_before_raw(block) };
208                        inserted = true;
209                        break;
210                    }
211                    cursor.move_next();
212                }
213            }
214
215            // If no block comes after this one, it goes on the end of the list.
216            if !inserted {
217                // SAFETY: See the `insert_before_raw` call above.
218                unsafe { list.push_back_raw(block) };
219            }
220
221            // After this point, the bucket list owns |block|.
222
223            // Don't merge blocks in the largest bucket.
224            if bucket + 1 == self.bucket_count {
225                return;
226            }
227
228            // Check to see if we should be merging this block into a larger aligned block.
229            let (first, second) = if (block_start & ((block_len << 1) - 1)) != 0 {
230                // Odd alignment.  This might be the second block of a merge pair.
231                // SAFETY: `block` was just inserted into `list`.
232                let mut cursor = unsafe { list.cursor_at(&*block) };
233                cursor.move_prev();
234                let first = cursor.get().map(|b| core::ptr::from_ref(b).cast_mut());
235                (first, Some(block))
236            } else {
237                // Even alignment.  This might be the first block of a merge pair.
238                // SAFETY: `block` was just inserted into `list`.
239                let mut cursor = unsafe { list.cursor_at(&*block) };
240                cursor.move_next();
241                let second = cursor.get().map(|b| core::ptr::from_ref(b).cast_mut());
242                (Some(block), second)
243            };
244
245            // Do these chunks fit together?
246            let (Some(first), Some(second)) = (first, second) else {
247                return;
248            };
249
250            // SAFETY: `first` and `second` are both members of `list` and therefore valid.
251            let (first_bucket, first_start, second_bucket, second_start) =
252                unsafe { ((*first).bucket, (*first).start, (*second).bucket, (*second).start) };
253            let first_len = 1u32 << first_bucket;
254            if first_start.wrapping_add(first_len) != second_start {
255                return;
256            }
257            debug_assert_eq!(first_bucket, second_bucket);
258
259            // Remove the two blocks' bookkeeping from their bucket.
260            // SAFETY: Both `first` and `second` are valid and are currently members of `list`.
261            unsafe {
262                let _ = list.erase(&*first);
263                let _ = list.erase(&*second);
264            }
265
266            // Place one half of the bookkeeping back on the unused list.
267            // SAFETY: `second` was just erased from `list`, so it is in no container.
268            unsafe { self.unused_blocks.push_back_raw(second) };
269
270            // Reuse the other half to track the newly merged block, and place
271            // it in the next bucket size up.
272            // SAFETY: `first` was just erased from `list` and is still owned by this allocator.
273            unsafe { (*first).bucket += 1 };
274            block = first;
275        }
276    }
277
278    fn add_range(&mut self, mut range_start: u32, mut range_len: u32) -> Result<(), Status> {
279        for range in self.ranges.iter() {
280            if ((range.start >= range_start) && (range.start < range_start.wrapping_add(range_len)))
281                || ((range_start >= range.start)
282                    && (range_start < range.start.wrapping_add(range.len)))
283            {
284                tracef!(
285                    "Range [{}, {}] overlaps with existing range [{}, {}].\n",
286                    range_start,
287                    range_start.wrapping_add(range_len).wrapping_sub(1),
288                    range.start,
289                    range.start.wrapping_add(range.len).wrapping_sub(1)
290                );
291                return Err(Status::ALREADY_EXISTS);
292            }
293        }
294
295        // Allocate our range state.
296        let Ok(mut new_range) = kalloc::Box::try_new(Range::new()) else {
297            return Err(Status::NO_MEMORY);
298        };
299        new_range.start = range_start;
300        new_range.len = range_len;
301
302        // Break the range we were given into power of two aligned chunks, and place
303        // them on the new blocks list to be added to the free-blocks buckets.
304        debug_assert!(self.bucket_count != 0);
305        debug_assert!(!self.free_block_buckets.is_empty());
306        pin_init::stack_pin_init!(let new_blocks = DoublyLinkedList::<*mut Block>::new());
307        // SAFETY: `new_blocks` is pinned on the stack and is never moved out of.
308        let new_blocks = unsafe { new_blocks.get_unchecked_mut() };
309
310        let mut bucket = self.bucket_count - 1;
311        let mut csize = 1u32 << bucket;
312        let max_csize = csize;
313        while range_len != 0 {
314            // Shrink the chunk size until it is aligned with the start of the
315            // range, and not larger than the number of irqs we have left.
316            let mut shrunk = false;
317            while ((range_start & (csize - 1)) != 0) || (range_len < csize) {
318                csize >>= 1;
319                bucket -= 1;
320                shrunk = true;
321            }
322
323            // If we didn't need to shrink the chunk size, perhaps we can grow it
324            // instead.
325            if !shrunk {
326                let mut tmp = csize << 1;
327                while (tmp <= max_csize) && (tmp <= range_len) && ((range_start & (tmp - 1)) == 0) {
328                    bucket += 1;
329                    csize = tmp;
330                    tmp <<= 1;
331                    debug_assert!(bucket < self.bucket_count);
332                }
333            }
334
335            // Break off a chunk of the range.
336            debug_assert_eq!(1u32 << bucket, csize);
337            debug_assert!(bucket < self.bucket_count);
338            debug_assert_eq!(range_start & (csize - 1), 0);
339            debug_assert!(csize <= range_len);
340            debug_assert!(csize != 0);
341
342            let Some(block) = self.get_unused_block() else {
343                tracef!(
344                    "WARNING! Failed to allocate block bookkeeping with sub-range [{}, {}] still left to track.\n",
345                    range_start,
346                    range_start.wrapping_add(range_len).wrapping_sub(1)
347                );
348                free_block_list(new_blocks);
349                return Err(Status::NO_MEMORY);
350            };
351
352            // SAFETY: `block` was just handed to us by `get_unused_block`, so it is valid and is
353            // not a member of any list.
354            unsafe {
355                (*block).bucket = bucket;
356                (*block).start = range_start;
357                new_blocks.push_back_raw(block);
358            }
359
360            range_start += csize;
361            range_len -= csize;
362        }
363
364        // Looks like we managed to allocate everything we needed to.  Go ahead and
365        // add all of our newly allocated bookkeeping to the state.
366        // SAFETY: `new_range` was just allocated and is a member of no list.
367        unsafe { self.ranges.push_back_raw(kalloc::Box::into_raw(new_range)) };
368
369        while let Some(block) = new_blocks.pop_front() {
370            // SAFETY: `block` was just popped from `new_blocks` and is owned by this allocator.
371            unsafe { self.return_free_block(block) };
372        }
373
374        Ok(())
375    }
376
377    fn allocate_range(&mut self, orig_bucket: u32) -> Result<u32, Status> {
378        // Find the smallest sized chunk which can hold the allocation and is
379        // compatible with the requested addressing capabilities.
380        let mut bucket = orig_bucket;
381        let mut block: Option<*mut Block> = None;
382        while bucket < self.bucket_count {
383            block = self.free_block_buckets[bucket as usize].pop_front();
384            if block.is_some() {
385                break;
386            }
387            bucket += 1;
388        }
389
390        // Nothing found, unlock and get out.
391        let Some(block) = block else {
392            return Err(Status::NO_RESOURCES);
393        };
394
395        // Looks like we have a chunk which can satisfy this allocation request.
396        // Split it as many times as needed to match the requested size.
397        // SAFETY: `block` was just popped off of a free bucket, so it is valid and in no list.
398        debug_assert_eq!(unsafe { (*block).bucket }, bucket);
399        debug_assert!(bucket >= orig_bucket);
400
401        while bucket > orig_bucket {
402            // If we failed to allocate bookkeeping for the split block, put the block
403            // we failed to split back into the free list (merging if required),
404            // then fail the allocation.
405            let Some(split_block) = self.get_unused_block() else {
406                tracef!(
407                    "Failed to allocate free bookkeeping block when attempting to split for allocation\n"
408                );
409                // SAFETY: `block` is valid and is a member of no list.
410                unsafe { self.return_free_block(block) };
411                return Err(Status::NO_MEMORY);
412            };
413
414            debug_assert!(bucket != 0);
415            bucket -= 1;
416
417            // SAFETY: Both `block` and `split_block` are valid and are members of no list.
418            unsafe {
419                // Cut the first chunk in half.
420                (*block).bucket = bucket;
421
422                // Fill out the bookkeeping for the second half of the chunk.
423                (*split_block).start = (*block).start + (1u32 << (*block).bucket);
424                (*split_block).bucket = bucket;
425
426                // Return the second half of the chunk to the free pool.
427                self.return_free_block(split_block);
428            }
429        }
430
431        // Success! Mark the block as allocated and return the block to the user.
432        // SAFETY: `block` is valid and is a member of no list.
433        let range_start = unsafe { (*block).start };
434        // SAFETY: `block` is valid and is a member of no list.
435        unsafe { self.allocated_blocks.push_front_raw(block) };
436
437        Ok(range_start)
438    }
439
440    fn free_range(&mut self, range_start: u32, bucket: u32) {
441        // In a debug build, find the specific block being returned in the list of
442        // allocated blocks and use it as the bookkeeping for returning to the free
443        // bucket.  Because this is an O(n) operation, and serves only as an integrity
444        // check, we only do this in debug builds.  In release builds, we just grab
445        // any piece of bookkeeping memory off the allocated_blocks list and use
446        // that instead.
447        //
448        // The C++ implementation selects between the two arms with
449        // `#if DEBUG_ASSERT_IMPLEMENTED`.  Rust's `debug_assertions` gates `debug_assert!` in
450        // exactly the same way that `DEBUG_ASSERT_IMPLEMENTED` gates `DEBUG_ASSERT`, so it is used
451        // here to keep the port self consistent.
452        let block: Option<*mut Block> = if cfg!(debug_assertions) {
453            self.allocated_blocks.erase_if(|candidate| {
454                (candidate.start == range_start) && (candidate.bucket == bucket)
455            })
456        } else {
457            let block = self.allocated_blocks.pop_front();
458            if let Some(block) = block {
459                // SAFETY: `block` was just popped off of `allocated_blocks`.
460                unsafe {
461                    (*block).start = range_start;
462                    (*block).bucket = bucket;
463                }
464            }
465            block
466        };
467
468        let block = block.expect("no matching allocated block to free");
469
470        // Return the block to the free buckets (merging as needed) and we are done.
471        // SAFETY: `block` was just removed from `allocated_blocks`, so it is valid and is a member
472        // of no list.
473        unsafe { self.return_free_block(block) };
474    }
475}
476
477/// `Pow2RangeAllocator` is a small utility class which partitions a set of
478/// ranges of integers into sub-ranges which are power of 2 in length and power
479/// of 2 aligned and then manages allocating and freeing the subranges for
480/// clients.  It is responsible for breaking larger sub-regions into smaller ones
481/// as needed for allocation, and for merging sub-regions into larger sub-regions
482/// as needed during free operations.
483///
484/// Its primary use is as a utility library for platforms who need to manage
485/// allocating blocks MSI IRQ IDs on behalf of the PCI bus driver, but could (in
486/// theory) be used for other things).
487#[ksync::guarded]
488pub struct Pow2RangeAllocator {
489    #[guarded_by(lock)]
490    #[pin]
491    inner: Inner,
492
493    #[mutex]
494    lock: ksync::KMutex,
495}
496
497// Like the C++ implementation, a `Pow2RangeAllocator` is meant to live in shared storage and to
498// serialize all of its access on its own mutex.
499const _: fn() = || {
500    fn assert_sync<T: Sync + ?Sized>() {}
501    assert_sync::<Pow2RangeAllocator>();
502};
503
504impl Pow2RangeAllocator {
505    /// Creates a new, uninitialized `Pow2RangeAllocator`.
506    ///
507    /// `init` must be called before any range can be added or allocated.
508    pub fn new() -> impl PinInit<Self, Infallible> {
509        pin_init!(Self {
510            inner <- ksync::kcell_init(Inner::new()),
511            lock <- ksync::KMutex::init(),
512        })
513    }
514
515    /// Initialize the state of a pow2 range allocator.
516    ///
517    /// `max_alloc_size` is the maximum size of a single contiguous allocation.  It must be a power
518    /// of 2.
519    ///
520    /// Returns a status code indicating the success or failure of the operation.
521    /// Possible return values include
522    /// ++ `ZX_ERR_INVALID_ARGS` `max_alloc_size` is zero or not a power of 2.
523    /// ++ `ZX_ERR_NO_MEMORY` Not enough memory to allocate the storage for free bucket lists.
524    pub fn init(&self, max_alloc_size: u32) -> Result<(), Status> {
525        if (max_alloc_size == 0) || !max_alloc_size.is_power_of_two() {
526            tracef!("max_alloc_size ({}) is not an integer power of two!\n", max_alloc_size);
527            return Err(Status::INVALID_ARGS);
528        }
529
530        let bucket_count = max_alloc_size.ilog2() + 1;
531
532        ksync::lock!(let mut guard = self.lock_lock());
533        // SAFETY: `inner` is structurally pinned inside the pinned allocator; taking a `&mut`
534        // reference to it never moves it.
535        let inner = unsafe { guard.as_mut().inner_mut().get_unchecked_mut() };
536        inner.init(bucket_count)
537    }
538
539    /// Free all of the state associated with a previously initialized pow2 range allocator.
540    pub fn free(&self) {
541        ksync::lock!(let mut guard = self.lock_lock());
542        // SAFETY: `inner` is structurally pinned inside the pinned allocator; taking a `&mut`
543        // reference to it never moves it.
544        let inner = unsafe { guard.as_mut().inner_mut().get_unchecked_mut() };
545
546        debug_assert!(inner.bucket_count != 0);
547        debug_assert!(!inner.free_block_buckets.is_empty());
548        debug_assert!(inner.allocated_blocks.is_empty());
549
550        inner.release();
551    }
552
553    /// Add a range of `u32`s to the pool of ranges to be allocated.
554    ///
555    /// `range_start` is the start of the `u32` range and `range_len` is its length.
556    ///
557    /// Returns a status code indicating the success or failure of the operation.
558    /// Possible return values include
559    /// ++ `ZX_ERR_INVALID_ARGS` range_len is zero, or would cause the range to wrap the
560    ///    maximum range of a `u32`.
561    /// ++ `ZX_ERR_ALREADY_EXISTS` the specified range overlaps with a range already added
562    ///    to the allocator.
563    /// ++ `ZX_ERR_NO_MEMORY` Not enough memory to allocate the bookkeeping required for
564    ///    managing the range.
565    pub fn add_range(&self, range_start: u32, range_len: u32) -> Result<(), Status> {
566        ltracef!(
567            "Adding range [{}, {}]\n",
568            range_start,
569            range_start.wrapping_add(range_len).wrapping_sub(1)
570        );
571
572        if (range_len == 0) || (range_start.wrapping_add(range_len) < range_start) {
573            return Err(Status::INVALID_ARGS);
574        }
575
576        // Enter the lock and check for overlap with pre-existing ranges.
577        ksync::lock!(let mut guard = self.lock_lock());
578        // SAFETY: `inner` is structurally pinned inside the pinned allocator; taking a `&mut`
579        // reference to it never moves it.
580        let inner = unsafe { guard.as_mut().inner_mut().get_unchecked_mut() };
581        inner.add_range(range_start, range_len)
582    }
583
584    /// Attempt to allocate a range of `u32`s from the available sub-ranges.  The
585    /// size of the allocated range must be a power of 2, and if the allocation
586    /// succeeds, it is guaranteed to be aligned on a power of 2 boundary matching its
587    /// size.
588    ///
589    /// `size` is the requested size of the region.  On success, the start of the allocated range is
590    /// returned.
591    ///
592    /// Possible error values include
593    /// ++ `ZX_ERR_INVALID_ARGS` Multiple reasons, including...
594    ///    ++ size is zero.
595    ///    ++ size is not a power of two.
596    /// ++ `ZX_ERR_NO_RESOURCES` No contiguous, aligned region could be found to satisfy
597    ///    the allocation request.
598    /// ++ `ZX_ERR_NO_MEMORY` A region could be found, but memory required for bookkeeping
599    ///    could not be allocated.
600    pub fn allocate_range(&self, size: u32) -> Result<u32, Status> {
601        if (size == 0) || !size.is_power_of_two() {
602            tracef!("Size ({}) is not an integer power of 2.\n", size);
603            return Err(Status::INVALID_ARGS);
604        }
605
606        let orig_bucket = size.ilog2();
607
608        // Lock state during allocation.
609        ksync::lock!(let mut guard = self.lock_lock());
610        // SAFETY: `inner` is structurally pinned inside the pinned allocator; taking a `&mut`
611        // reference to it never moves it.
612        let inner = unsafe { guard.as_mut().inner_mut().get_unchecked_mut() };
613
614        if orig_bucket >= inner.bucket_count {
615            tracef!(
616                "Invalid size ({}).  Valid sizes are integer powers of 2 from [1, {}]\n",
617                size,
618                1u32.checked_shl(inner.bucket_count.wrapping_sub(1)).unwrap_or(0)
619            );
620            return Err(Status::INVALID_ARGS);
621        }
622
623        inner.allocate_range(orig_bucket)
624    }
625
626    /// Free a range previously allocated using `allocate_range`.
627    ///
628    /// `range_start` is the start of the previously allocated range and `size` is its size.
629    pub fn free_range(&self, range_start: u32, size: u32) {
630        debug_assert!((size != 0) && size.is_power_of_two());
631
632        let bucket = size.ilog2();
633
634        ksync::lock!(let mut guard = self.lock_lock());
635        // SAFETY: `inner` is structurally pinned inside the pinned allocator; taking a `&mut`
636        // reference to it never moves it.
637        let inner = unsafe { guard.as_mut().inner_mut().get_unchecked_mut() };
638        inner.free_range(range_start, bucket);
639    }
640}