blob: c2233e7a98d75342f4caf20b95a66ba72f30a5ef [file]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![no_std]
/// The maximum number of CPUs that this counter descriptor supports.
/// This value is read from the `SMP_MAX_CPUS` environment variable at build time.
pub const SMP_MAX_CPUS: usize =
zr::parse_usize(env!("SMP_MAX_CPUS")).expect("SMP_MAX_CPUS invalid");
pub use zr::to_array;
/// The aggregation type of a kernel counter.
///
/// This specifies how the diagnostic tools should combine the per-CPU slot values
/// of the counter to produce a single diagnostic value.
#[repr(u64)]
pub enum Type {
/// Padding element (unused).
Padding = 0,
/// Standard summation counter (aggregates the sum across all CPUs).
Sum = 1,
/// Minimum tracker counter (finds the minimum value across all CPUs).
Min = 2,
/// Maximum tracker counter (finds the maximum value across all CPUs).
Max = 3,
}
/// Binary-stable C-compatible representation of a kernel counter descriptor.
///
/// The memory layout of this structure matches Zircon's `counters::Descriptor` exactly,
/// enabling the linker and userspace diagnostic tools to parse Rust-declared counters
/// seamlessly from the kernel's binary segments.
// TODO(https://fxbug.dev/517301686): Use bindgen or another systematic way to avoid duplicating
// this structure and causing drift.
#[repr(C, align(8))]
pub struct Descriptor {
name: [u8; 56],
type_: u64,
}
const _: () = {
assert!(core::mem::size_of::<Descriptor>() == 64, "Descriptor size must be exactly 64 bytes");
assert!(
core::mem::align_of::<Descriptor>() == 8,
"Descriptor alignment must be exactly 8 bytes"
);
};
impl Descriptor {
/// Create a new raw `Descriptor` instance with the given packed name and type value.
pub const fn new(name: [u8; 56], type_: u64) -> Self {
Self { name, type_ }
}
}
unsafe extern "C" {
fn kcounter_add_ffi(desc: *const Descriptor, delta: i64);
fn kcounter_min_ffi(desc: *const Descriptor, value: i64);
fn kcounter_max_ffi(desc: *const Descriptor, value: i64);
}
/// A thread-safe diagnostic handle representing a self-declared kernel counter.
///
/// This structure contains a pointer to the counter's static `Descriptor` layout in memory,
/// and delegates increment, minimum, and maximum operations to highly optimized C++ FFI
/// handlers with zero runtime overhead under ThinLTO.
pub struct Counter {
descriptor: *const Descriptor,
}
unsafe impl Sync for Counter {}
unsafe impl Send for Counter {}
impl Counter {
/// Create a new Counter handle using the direct descriptor pointer address.
///
/// # Safety
/// This should only be called with a pointer to a valid, linker-defined
/// static descriptor variable.
pub const unsafe fn new_with_ptr(descriptor: *const Descriptor) -> Self {
Self { descriptor }
}
/// Add the given delta value to the calling CPU's counter slot.
#[inline]
pub fn add(&self, delta: i64) {
unsafe {
kcounter_add_ffi(self.descriptor, delta);
}
}
/// Update the calling CPU's counter slot to the minimum of its current value and the given
/// value.
#[inline]
pub fn min(&self, value: i64) {
unsafe {
kcounter_min_ffi(self.descriptor, value);
}
}
/// Update the calling CPU's counter slot to the maximum of its current value and the given
/// value.
#[inline]
pub fn max(&self, value: i64) {
unsafe {
kcounter_max_ffi(self.descriptor, value);
}
}
}
/// Macro to safely define a new Counter in Rust that is visible to the kernel.
///
/// # Example
/// ```rust
/// define_kcounter!(MY_COUNTER, "my.custom.counter", Sum);
///
/// fn some_kernel_code() {
/// MY_COUNTER.add(1);
/// }
/// ```
#[macro_export]
macro_rules! define_kcounter {
($rust_var:ident, $name:expr, $type:ident) => {
pub static $rust_var: $crate::Counter = {
#[unsafe(link_section = concat!(".bss.kcounter.", $name))]
#[used]
static mut ARENA: [i64; $crate::SMP_MAX_CPUS] = [0; $crate::SMP_MAX_CPUS];
#[unsafe(link_section = concat!("kcountdesc.", $name))]
#[used]
static DESC: $crate::Descriptor =
$crate::Descriptor::new($crate::to_array::<56>($name), $crate::Type::$type as u64);
unsafe { $crate::Counter::new_with_ptr(&DESC as *const $crate::Descriptor) }
};
};
}