blob: 407087289bd85b5ed868eaae4de11693295ee658 [file]
// Copyright 2025 The Fuchsia Authors
//
// 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
//! Kernel address space allocator for `percpu_writer` buffers.
mod allocator_ffi;
pub mod buffer;
pub use buffer::{
Buffer, DroppedRecordDurationEvent, DroppedRecordStats, Reservation, serialize_drop_stats,
};
use crate::vm::arch_vm_aspace::{ARCH_MMU_FLAG_PERM_READ, ARCH_MMU_FLAG_PERM_WRITE};
use crate::vm::pmm::{ALLOC_FLAG_ANY, ALLOC_FLAG_CAN_WAIT};
use crate::vm::vm_aspace::VmAspace;
use crate::vm::vm_object_paged::VmObjectPaged;
use core::ptr::{NonNull, with_exposed_provenance_mut};
/// `SpscBuffer` requires us to specify how the underlying memory is managed -- it abstracts over a
/// buffer provided by userspace or the kernel. Since we're writing to buffers in the kernel, we
/// provide an allocator backed by a `VmAspace`.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct KernelAspaceAllocator;
impl KernelAspaceAllocator {
/// Allocates and maps a pinned region of `size` bytes (rounded up to a page multiple) in the
/// kernel address space named `buffer_name`.
pub fn allocate(size: u32, buffer_name: &[u8]) -> Option<NonNull<u8>> {
let rounded_size = page::round_up(size as usize);
if size == 0 {
return None;
}
let Ok(vmo) =
VmObjectPaged::create(ALLOC_FLAG_ANY | ALLOC_FLAG_CAN_WAIT, 0, rounded_size as u64)
else {
return None;
};
if vmo.commit_range_pinned(0, rounded_size as u64, true).is_err() {
return None;
}
let kaspace = VmAspace::kernel_aspace();
let Ok(r) = kaspace
.root_vmar()
.expect("kernel aspace root VMAR must be initialized")
.create_vm_mapping(
0,
rounded_size,
0,
0,
VmObjectPaged::into_vm_object(vmo.clone()),
0,
ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE,
buffer_name,
)
else {
vmo.unpin(0, rounded_size as u64);
return None;
};
if r.mapping.map_range(0, rounded_size, true, true).is_err() {
let _ = r.mapping.destroy();
vmo.unpin(0, rounded_size as u64);
return None;
}
NonNull::new(with_exposed_provenance_mut(r.base))
}
/// Frees a kernel address space region previously allocated by [`Self::allocate`].
///
/// # Safety
///
/// If `ptr` is `Some`, it must be the base address of a live region allocated by
/// [`Self::allocate`] in the kernel address space that is no longer in use.
pub unsafe fn free(ptr: Option<NonNull<u8>>) {
if let Some(ptr) = ptr {
let kaspace = VmAspace::kernel_aspace();
// SAFETY: The caller guarantees `ptr` is the base address of a live region in the
// kernel address space that is no longer in use.
let _ = unsafe { kaspace.free_region(ptr.addr().get()) };
}
}
}