blob: cd170a48e769fe7b20ed1733a8fdc691a90b3681 [file]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2016, Google, Inc. All rights reserved
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
//
// Ported from zircon/kernel/lib/pow2_range_allocator/pow2_range_allocator.cc and
// zircon/kernel/lib/pow2_range_allocator/include/lib/pow2_range_allocator.h
#![no_std]
use core::convert::Infallible;
use core::pin::Pin;
use debug::{ltracef, tracef};
use fbl::{Array, DoublyLinkedList, DoublyLinkedListContainable, DoublyLinkedListNode};
use pin_init::{PinInit, pin_data, pin_init, pinned_drop};
use zx_status::Status;
const LOCAL_TRACE: u32 = 0;
/// Bookkeeping for a single power of 2 sized, power of 2 aligned sub-range.
#[derive(DoublyLinkedListContainable)]
struct Block {
#[dll_node]
node: DoublyLinkedListNode<Block>,
bucket: u32,
start: u32,
}
impl Block {
const fn new() -> Self {
Self { node: DoublyLinkedListNode::new(), bucket: 0, start: 0 }
}
}
/// Bookkeeping for a range of `u32`s which was handed to the allocator via `add_range`.
#[derive(DoublyLinkedListContainable)]
struct Range {
#[dll_node]
node: DoublyLinkedListNode<Range>,
start: u32,
len: u32,
}
impl Range {
const fn new() -> Self {
Self { node: DoublyLinkedListNode::new(), start: 0, len: 0 }
}
}
/// Frees every `Block` remaining in `list`.
///
/// The lists hold raw pointers and perform no lifecycle management, so ownership of each element
/// has to be reclaimed explicitly.
fn free_block_list(list: &mut DoublyLinkedList<*mut Block>) {
while let Some(block) = list.pop_front() {
// SAFETY: Every `Block` in one of the allocator's lists was allocated with
// `kalloc::Box::try_new` and leaked with `kalloc::Box::into_raw`. It has just been removed
// from `list`, so this is the only pointer to it.
drop(unsafe { kalloc::Box::from_raw(block) });
}
}
/// Frees every `Range` remaining in `list`.
fn free_range_list(list: &mut DoublyLinkedList<*mut Range>) {
while let Some(range) = list.pop_front() {
// SAFETY: Every `Range` in the allocator's range list was allocated with
// `kalloc::Box::try_new` and leaked with `kalloc::Box::into_raw`. It has just been removed
// from `list`, so this is the only pointer to it.
drop(unsafe { kalloc::Box::from_raw(range) });
}
}
/// All of the mutable state of a `Pow2RangeAllocator`, protected by the allocator's mutex.
#[pin_data(PinnedDrop)]
struct Inner {
#[pin]
ranges: DoublyLinkedList<*mut Range>,
#[pin]
unused_blocks: DoublyLinkedList<*mut Block>,
#[pin]
allocated_blocks: DoublyLinkedList<*mut Block>,
free_block_buckets: Array<DoublyLinkedList<*mut Block>>,
bucket_count: u32,
}
// SAFETY: The raw pointers held by `Inner`'s lists are exclusively owned by the allocator; they
// point at `kalloc::Box` allocations which are never handed out and which are only ever reachable
// with the allocator's mutex held. Ownership of the whole state can therefore be moved between
// threads, which is what the C++ implementation does by placing a `Pow2RangeAllocator` in shared
// storage and serializing all access on its `DECLARE_MUTEX`.
unsafe impl Send for Inner {}
#[pinned_drop]
impl PinnedDrop for Inner {
fn drop(self: Pin<&mut Self>) {
// The C++ implementation has no destructor at all; a `Pow2RangeAllocator` which is
// destroyed without calling `Free()` simply leaks its bookkeeping. Rust's lists hold raw
// pointers and debug assert that they are empty when dropped, so the bookkeeping is
// released here instead.
// SAFETY: We are being dropped, so nothing is moved out of the pinned `Inner`.
let this = unsafe { self.get_unchecked_mut() };
this.release();
}
}
impl Inner {
fn new() -> impl PinInit<Self, Infallible> {
pin_init!(Self {
ranges <- DoublyLinkedList::<*mut Range>::new(),
unused_blocks <- DoublyLinkedList::<*mut Block>::new(),
allocated_blocks <- DoublyLinkedList::<*mut Block>::new(),
free_block_buckets: Array::new(),
bucket_count: 0,
})
}
fn init(&mut self, bucket_count: u32) -> Result<(), Status> {
let mut uninit = kalloc::Box::<[DoublyLinkedList<*mut Block>]>::try_new_uninit_slice(
bucket_count as usize,
)
.map_err(|_| {
tracef!("Failed to allocate storage for {} free bucket lists!\n", bucket_count);
Status::NO_MEMORY
})?;
for item in uninit.iter_mut() {
let init = DoublyLinkedList::<*mut Block>::new();
// SAFETY: `item.as_mut_ptr()` is a valid, writable pointer to uninitialized memory
// in the newly allocated slice.
unsafe {
let _ = init.__pinned_init(item.as_mut_ptr());
}
}
// SAFETY: All elements in `uninit` were initialized in the loop above.
let buf = unsafe { uninit.assume_init() };
self.free_block_buckets = Array::from_box(buf);
self.bucket_count = bucket_count;
Ok(())
}
/// Releases every piece of bookkeeping still held by the allocator.
fn release(&mut self) {
free_range_list(&mut self.ranges);
free_block_list(&mut self.unused_blocks);
free_block_list(&mut self.allocated_blocks);
for bucket in self.free_block_buckets.iter_mut() {
free_block_list(bucket);
}
}
/// Returns a block of bookkeeping, recycling one from the unused list if possible.
///
/// Returns `None` if a new block had to be allocated and the allocation failed.
fn get_unused_block(&mut self) -> Option<*mut Block> {
if !self.unused_blocks.is_empty() {
return self.unused_blocks.pop_front();
}
match kalloc::Box::try_new(Block::new()) {
Ok(block) => Some(kalloc::Box::into_raw(block)),
Err(_) => None,
}
}
/// Returns `block` to its proper free bucket, merging with its buddy as many times as possible.
///
/// # Safety
///
/// `block` must be a valid pointer to a `Block` owned by this allocator which is not currently
/// a member of any list.
unsafe fn return_free_block(&mut self, mut block: *mut Block) {
// The C++ implementation recurses into `ReturnFreeBlock` after a successful merge. This is
// the same algorithm written as a loop.
loop {
debug_assert!(!block.is_null());
// SAFETY: The caller guarantees that `block` points at a valid `Block` which is not in
// any container. On subsequent loop iterations `block` was just erased from a bucket.
let block_ref = unsafe { &*block };
debug_assert!(block_ref.bucket < self.bucket_count);
debug_assert!(!block_ref.node.in_container());
let bucket = block_ref.bucket;
let block_start = block_ref.start;
let block_len = 1u32 << bucket;
debug_assert_eq!(block_start & (block_len - 1), 0);
// Return the block to its proper free bucket, sorted by base ID. Start by
// finding the block which should come after this block in the list.
let list = &mut self.free_block_buckets[bucket as usize];
let mut inserted = false;
{
let mut cursor = list.cursor_front_mut();
while let Some(after) = cursor.get() {
// We do not allow ranges to overlap.
let after_len = 1u32 << after.bucket;
let after_start = after.start;
debug_assert!(
(block_start >= after_start.wrapping_add(after_len))
|| (after_start >= block_start.wrapping_add(block_len))
);
if after_start > block_start {
// SAFETY: `block` is a valid pointer to a `Block` which is not in any
// container, and it is owned by this allocator so it outlives its
// membership in the list.
unsafe { cursor.insert_before_raw(block) };
inserted = true;
break;
}
cursor.move_next();
}
}
// If no block comes after this one, it goes on the end of the list.
if !inserted {
// SAFETY: See the `insert_before_raw` call above.
unsafe { list.push_back_raw(block) };
}
// After this point, the bucket list owns |block|.
// Don't merge blocks in the largest bucket.
if bucket + 1 == self.bucket_count {
return;
}
// Check to see if we should be merging this block into a larger aligned block.
let (first, second) = if (block_start & ((block_len << 1) - 1)) != 0 {
// Odd alignment. This might be the second block of a merge pair.
// SAFETY: `block` was just inserted into `list`.
let mut cursor = unsafe { list.cursor_at(&*block) };
cursor.move_prev();
let first = cursor.get().map(|b| core::ptr::from_ref(b).cast_mut());
(first, Some(block))
} else {
// Even alignment. This might be the first block of a merge pair.
// SAFETY: `block` was just inserted into `list`.
let mut cursor = unsafe { list.cursor_at(&*block) };
cursor.move_next();
let second = cursor.get().map(|b| core::ptr::from_ref(b).cast_mut());
(Some(block), second)
};
// Do these chunks fit together?
let (Some(first), Some(second)) = (first, second) else {
return;
};
// SAFETY: `first` and `second` are both members of `list` and therefore valid.
let (first_bucket, first_start, second_bucket, second_start) =
unsafe { ((*first).bucket, (*first).start, (*second).bucket, (*second).start) };
let first_len = 1u32 << first_bucket;
if first_start.wrapping_add(first_len) != second_start {
return;
}
debug_assert_eq!(first_bucket, second_bucket);
// Remove the two blocks' bookkeeping from their bucket.
// SAFETY: Both `first` and `second` are valid and are currently members of `list`.
unsafe {
let _ = list.erase(&*first);
let _ = list.erase(&*second);
}
// Place one half of the bookkeeping back on the unused list.
// SAFETY: `second` was just erased from `list`, so it is in no container.
unsafe { self.unused_blocks.push_back_raw(second) };
// Reuse the other half to track the newly merged block, and place
// it in the next bucket size up.
// SAFETY: `first` was just erased from `list` and is still owned by this allocator.
unsafe { (*first).bucket += 1 };
block = first;
}
}
fn add_range(&mut self, mut range_start: u32, mut range_len: u32) -> Result<(), Status> {
for range in self.ranges.iter() {
if ((range.start >= range_start) && (range.start < range_start.wrapping_add(range_len)))
|| ((range_start >= range.start)
&& (range_start < range.start.wrapping_add(range.len)))
{
tracef!(
"Range [{}, {}] overlaps with existing range [{}, {}].\n",
range_start,
range_start.wrapping_add(range_len).wrapping_sub(1),
range.start,
range.start.wrapping_add(range.len).wrapping_sub(1)
);
return Err(Status::ALREADY_EXISTS);
}
}
// Allocate our range state.
let Ok(mut new_range) = kalloc::Box::try_new(Range::new()) else {
return Err(Status::NO_MEMORY);
};
new_range.start = range_start;
new_range.len = range_len;
// Break the range we were given into power of two aligned chunks, and place
// them on the new blocks list to be added to the free-blocks buckets.
debug_assert!(self.bucket_count != 0);
debug_assert!(!self.free_block_buckets.is_empty());
pin_init::stack_pin_init!(let new_blocks = DoublyLinkedList::<*mut Block>::new());
// SAFETY: `new_blocks` is pinned on the stack and is never moved out of.
let new_blocks = unsafe { new_blocks.get_unchecked_mut() };
let mut bucket = self.bucket_count - 1;
let mut csize = 1u32 << bucket;
let max_csize = csize;
while range_len != 0 {
// Shrink the chunk size until it is aligned with the start of the
// range, and not larger than the number of irqs we have left.
let mut shrunk = false;
while ((range_start & (csize - 1)) != 0) || (range_len < csize) {
csize >>= 1;
bucket -= 1;
shrunk = true;
}
// If we didn't need to shrink the chunk size, perhaps we can grow it
// instead.
if !shrunk {
let mut tmp = csize << 1;
while (tmp <= max_csize) && (tmp <= range_len) && ((range_start & (tmp - 1)) == 0) {
bucket += 1;
csize = tmp;
tmp <<= 1;
debug_assert!(bucket < self.bucket_count);
}
}
// Break off a chunk of the range.
debug_assert_eq!(1u32 << bucket, csize);
debug_assert!(bucket < self.bucket_count);
debug_assert_eq!(range_start & (csize - 1), 0);
debug_assert!(csize <= range_len);
debug_assert!(csize != 0);
let Some(block) = self.get_unused_block() else {
tracef!(
"WARNING! Failed to allocate block bookkeeping with sub-range [{}, {}] still left to track.\n",
range_start,
range_start.wrapping_add(range_len).wrapping_sub(1)
);
free_block_list(new_blocks);
return Err(Status::NO_MEMORY);
};
// SAFETY: `block` was just handed to us by `get_unused_block`, so it is valid and is
// not a member of any list.
unsafe {
(*block).bucket = bucket;
(*block).start = range_start;
new_blocks.push_back_raw(block);
}
range_start += csize;
range_len -= csize;
}
// Looks like we managed to allocate everything we needed to. Go ahead and
// add all of our newly allocated bookkeeping to the state.
// SAFETY: `new_range` was just allocated and is a member of no list.
unsafe { self.ranges.push_back_raw(kalloc::Box::into_raw(new_range)) };
while let Some(block) = new_blocks.pop_front() {
// SAFETY: `block` was just popped from `new_blocks` and is owned by this allocator.
unsafe { self.return_free_block(block) };
}
Ok(())
}
fn allocate_range(&mut self, orig_bucket: u32) -> Result<u32, Status> {
// Find the smallest sized chunk which can hold the allocation and is
// compatible with the requested addressing capabilities.
let mut bucket = orig_bucket;
let mut block: Option<*mut Block> = None;
while bucket < self.bucket_count {
block = self.free_block_buckets[bucket as usize].pop_front();
if block.is_some() {
break;
}
bucket += 1;
}
// Nothing found, unlock and get out.
let Some(block) = block else {
return Err(Status::NO_RESOURCES);
};
// Looks like we have a chunk which can satisfy this allocation request.
// Split it as many times as needed to match the requested size.
// SAFETY: `block` was just popped off of a free bucket, so it is valid and in no list.
debug_assert_eq!(unsafe { (*block).bucket }, bucket);
debug_assert!(bucket >= orig_bucket);
while bucket > orig_bucket {
// If we failed to allocate bookkeeping for the split block, put the block
// we failed to split back into the free list (merging if required),
// then fail the allocation.
let Some(split_block) = self.get_unused_block() else {
tracef!(
"Failed to allocate free bookkeeping block when attempting to split for allocation\n"
);
// SAFETY: `block` is valid and is a member of no list.
unsafe { self.return_free_block(block) };
return Err(Status::NO_MEMORY);
};
debug_assert!(bucket != 0);
bucket -= 1;
// SAFETY: Both `block` and `split_block` are valid and are members of no list.
unsafe {
// Cut the first chunk in half.
(*block).bucket = bucket;
// Fill out the bookkeeping for the second half of the chunk.
(*split_block).start = (*block).start + (1u32 << (*block).bucket);
(*split_block).bucket = bucket;
// Return the second half of the chunk to the free pool.
self.return_free_block(split_block);
}
}
// Success! Mark the block as allocated and return the block to the user.
// SAFETY: `block` is valid and is a member of no list.
let range_start = unsafe { (*block).start };
// SAFETY: `block` is valid and is a member of no list.
unsafe { self.allocated_blocks.push_front_raw(block) };
Ok(range_start)
}
fn free_range(&mut self, range_start: u32, bucket: u32) {
// In a debug build, find the specific block being returned in the list of
// allocated blocks and use it as the bookkeeping for returning to the free
// bucket. Because this is an O(n) operation, and serves only as an integrity
// check, we only do this in debug builds. In release builds, we just grab
// any piece of bookkeeping memory off the allocated_blocks list and use
// that instead.
//
// The C++ implementation selects between the two arms with
// `#if DEBUG_ASSERT_IMPLEMENTED`. Rust's `debug_assertions` gates `debug_assert!` in
// exactly the same way that `DEBUG_ASSERT_IMPLEMENTED` gates `DEBUG_ASSERT`, so it is used
// here to keep the port self consistent.
let block: Option<*mut Block> = if cfg!(debug_assertions) {
self.allocated_blocks.erase_if(|candidate| {
(candidate.start == range_start) && (candidate.bucket == bucket)
})
} else {
let block = self.allocated_blocks.pop_front();
if let Some(block) = block {
// SAFETY: `block` was just popped off of `allocated_blocks`.
unsafe {
(*block).start = range_start;
(*block).bucket = bucket;
}
}
block
};
let block = block.expect("no matching allocated block to free");
// Return the block to the free buckets (merging as needed) and we are done.
// SAFETY: `block` was just removed from `allocated_blocks`, so it is valid and is a member
// of no list.
unsafe { self.return_free_block(block) };
}
}
/// `Pow2RangeAllocator` is a small utility class which partitions a set of
/// ranges of integers into sub-ranges which are power of 2 in length and power
/// of 2 aligned and then manages allocating and freeing the subranges for
/// clients. It is responsible for breaking larger sub-regions into smaller ones
/// as needed for allocation, and for merging sub-regions into larger sub-regions
/// as needed during free operations.
///
/// Its primary use is as a utility library for platforms who need to manage
/// allocating blocks MSI IRQ IDs on behalf of the PCI bus driver, but could (in
/// theory) be used for other things).
#[ksync::guarded]
pub struct Pow2RangeAllocator {
#[guarded_by(lock)]
#[pin]
inner: Inner,
#[mutex]
lock: ksync::KMutex,
}
// Like the C++ implementation, a `Pow2RangeAllocator` is meant to live in shared storage and to
// serialize all of its access on its own mutex.
const _: fn() = || {
fn assert_sync<T: Sync + ?Sized>() {}
assert_sync::<Pow2RangeAllocator>();
};
impl Pow2RangeAllocator {
/// Creates a new, uninitialized `Pow2RangeAllocator`.
///
/// `init` must be called before any range can be added or allocated.
pub fn new() -> impl PinInit<Self, Infallible> {
pin_init!(Self {
inner <- ksync::kcell_init(Inner::new()),
lock <- ksync::KMutex::init(),
})
}
/// Initialize the state of a pow2 range allocator.
///
/// `max_alloc_size` is the maximum size of a single contiguous allocation. It must be a power
/// of 2.
///
/// Returns a status code indicating the success or failure of the operation.
/// Possible return values include
/// ++ `ZX_ERR_INVALID_ARGS` `max_alloc_size` is zero or not a power of 2.
/// ++ `ZX_ERR_NO_MEMORY` Not enough memory to allocate the storage for free bucket lists.
pub fn init(&self, max_alloc_size: u32) -> Result<(), Status> {
if (max_alloc_size == 0) || !max_alloc_size.is_power_of_two() {
tracef!("max_alloc_size ({}) is not an integer power of two!\n", max_alloc_size);
return Err(Status::INVALID_ARGS);
}
let bucket_count = max_alloc_size.ilog2() + 1;
ksync::lock!(let mut guard = self.lock_lock());
// SAFETY: `inner` is structurally pinned inside the pinned allocator; taking a `&mut`
// reference to it never moves it.
let inner = unsafe { guard.as_mut().inner_mut().get_unchecked_mut() };
inner.init(bucket_count)
}
/// Free all of the state associated with a previously initialized pow2 range allocator.
pub fn free(&self) {
ksync::lock!(let mut guard = self.lock_lock());
// SAFETY: `inner` is structurally pinned inside the pinned allocator; taking a `&mut`
// reference to it never moves it.
let inner = unsafe { guard.as_mut().inner_mut().get_unchecked_mut() };
debug_assert!(inner.bucket_count != 0);
debug_assert!(!inner.free_block_buckets.is_empty());
debug_assert!(inner.allocated_blocks.is_empty());
inner.release();
}
/// Add a range of `u32`s to the pool of ranges to be allocated.
///
/// `range_start` is the start of the `u32` range and `range_len` is its length.
///
/// Returns a status code indicating the success or failure of the operation.
/// Possible return values include
/// ++ `ZX_ERR_INVALID_ARGS` range_len is zero, or would cause the range to wrap the
/// maximum range of a `u32`.
/// ++ `ZX_ERR_ALREADY_EXISTS` the specified range overlaps with a range already added
/// to the allocator.
/// ++ `ZX_ERR_NO_MEMORY` Not enough memory to allocate the bookkeeping required for
/// managing the range.
pub fn add_range(&self, range_start: u32, range_len: u32) -> Result<(), Status> {
ltracef!(
"Adding range [{}, {}]\n",
range_start,
range_start.wrapping_add(range_len).wrapping_sub(1)
);
if (range_len == 0) || (range_start.wrapping_add(range_len) < range_start) {
return Err(Status::INVALID_ARGS);
}
// Enter the lock and check for overlap with pre-existing ranges.
ksync::lock!(let mut guard = self.lock_lock());
// SAFETY: `inner` is structurally pinned inside the pinned allocator; taking a `&mut`
// reference to it never moves it.
let inner = unsafe { guard.as_mut().inner_mut().get_unchecked_mut() };
inner.add_range(range_start, range_len)
}
/// Attempt to allocate a range of `u32`s from the available sub-ranges. The
/// size of the allocated range must be a power of 2, and if the allocation
/// succeeds, it is guaranteed to be aligned on a power of 2 boundary matching its
/// size.
///
/// `size` is the requested size of the region. On success, the start of the allocated range is
/// returned.
///
/// Possible error values include
/// ++ `ZX_ERR_INVALID_ARGS` Multiple reasons, including...
/// ++ size is zero.
/// ++ size is not a power of two.
/// ++ `ZX_ERR_NO_RESOURCES` No contiguous, aligned region could be found to satisfy
/// the allocation request.
/// ++ `ZX_ERR_NO_MEMORY` A region could be found, but memory required for bookkeeping
/// could not be allocated.
pub fn allocate_range(&self, size: u32) -> Result<u32, Status> {
if (size == 0) || !size.is_power_of_two() {
tracef!("Size ({}) is not an integer power of 2.\n", size);
return Err(Status::INVALID_ARGS);
}
let orig_bucket = size.ilog2();
// Lock state during allocation.
ksync::lock!(let mut guard = self.lock_lock());
// SAFETY: `inner` is structurally pinned inside the pinned allocator; taking a `&mut`
// reference to it never moves it.
let inner = unsafe { guard.as_mut().inner_mut().get_unchecked_mut() };
if orig_bucket >= inner.bucket_count {
tracef!(
"Invalid size ({}). Valid sizes are integer powers of 2 from [1, {}]\n",
size,
1u32.checked_shl(inner.bucket_count.wrapping_sub(1)).unwrap_or(0)
);
return Err(Status::INVALID_ARGS);
}
inner.allocate_range(orig_bucket)
}
/// Free a range previously allocated using `allocate_range`.
///
/// `range_start` is the start of the previously allocated range and `size` is its size.
pub fn free_range(&self, range_start: u32, size: u32) {
debug_assert!((size != 0) && size.is_power_of_two());
let bucket = size.ilog2();
ksync::lock!(let mut guard = self.lock_lock());
// SAFETY: `inner` is structurally pinned inside the pinned allocator; taking a `&mut`
// reference to it never moves it.
let inner = unsafe { guard.as_mut().inner_mut().get_unchecked_mut() };
inner.free_range(range_start, bucket);
}
}