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