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}