Skip to main content

counters_rs/
lib.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![no_std]
6
7mod bindings;
8
9/// The maximum number of CPUs that this counter descriptor supports.
10/// This value is read from the `SMP_MAX_CPUS` environment variable at build time.
11pub const SMP_MAX_CPUS: usize =
12    zr::parse_usize(env!("SMP_MAX_CPUS")).expect("SMP_MAX_CPUS invalid");
13
14pub use zr::to_array;
15
16/// The aggregation type of a kernel counter.
17///
18/// This specifies how the diagnostic tools should combine the per-CPU slot values
19/// of the counter to produce a single diagnostic value.
20#[repr(u64)]
21pub enum Type {
22    /// Padding element (unused).
23    Padding = 0,
24    /// Standard summation counter (aggregates the sum across all CPUs).
25    Sum = 1,
26    /// Minimum tracker counter (finds the minimum value across all CPUs).
27    Min = 2,
28    /// Maximum tracker counter (finds the maximum value across all CPUs).
29    Max = 3,
30}
31
32/// Binary-stable C-compatible representation of a kernel counter descriptor.
33///
34/// The memory layout of this structure matches Zircon's `counters::Descriptor` exactly,
35/// enabling the linker and userspace diagnostic tools to parse Rust-declared counters
36/// seamlessly from the kernel's binary segments.
37#[repr(C, align(8))]
38pub struct Descriptor {
39    name: [u8; 56],
40    type_: u64,
41}
42
43zr::static_assert!(
44    core::mem::size_of::<Descriptor>() == core::mem::size_of::<bindings::counters_Descriptor>()
45);
46zr::static_assert!(
47    core::mem::align_of::<Descriptor>() == core::mem::align_of::<bindings::counters_Descriptor>()
48);
49zr::static_assert!(
50    core::mem::offset_of!(Descriptor, name)
51        == core::mem::offset_of!(bindings::counters_Descriptor, name)
52);
53zr::static_assert!(
54    core::mem::offset_of!(Descriptor, type_)
55        == core::mem::offset_of!(bindings::counters_Descriptor, type_)
56);
57zr::static_assert!(Type::Padding as u64 == bindings::counters_Type_kPadding as u64);
58zr::static_assert!(Type::Sum as u64 == bindings::counters_Type_kSum as u64);
59zr::static_assert!(Type::Min as u64 == bindings::counters_Type_kMin as u64);
60zr::static_assert!(Type::Max as u64 == bindings::counters_Type_kMax as u64);
61
62impl Descriptor {
63    /// Create a new raw `Descriptor` instance with the given packed name and type value.
64    pub const fn new(name: [u8; 56], type_: u64) -> Self {
65        Self { name, type_ }
66    }
67}
68
69unsafe extern "C" {
70    fn kcounter_add_ffi(desc: *const Descriptor, delta: i64);
71    fn kcounter_min_ffi(desc: *const Descriptor, value: i64);
72    fn kcounter_max_ffi(desc: *const Descriptor, value: i64);
73}
74
75/// A thread-safe diagnostic handle representing a self-declared kernel counter.
76///
77/// This structure contains a pointer to the counter's static `Descriptor` layout in memory,
78/// and delegates increment, minimum, and maximum operations to highly optimized C++ FFI
79/// handlers with zero runtime overhead under ThinLTO.
80pub struct Counter {
81    descriptor: *const Descriptor,
82}
83
84unsafe impl Sync for Counter {}
85unsafe impl Send for Counter {}
86
87impl Counter {
88    /// Create a new Counter handle using the direct descriptor pointer address.
89    ///
90    /// # Safety
91    /// This should only be called with a pointer to a valid, linker-defined
92    /// static descriptor variable.
93    pub const unsafe fn new_with_ptr(descriptor: *const Descriptor) -> Self {
94        Self { descriptor }
95    }
96
97    /// Add the given delta value to the calling CPU's counter slot.
98    #[inline]
99    pub fn add(&self, delta: i64) {
100        unsafe {
101            kcounter_add_ffi(self.descriptor, delta);
102        }
103    }
104
105    /// Update the calling CPU's counter slot to the minimum of its current value and the given
106    /// value.
107    #[inline]
108    pub fn min(&self, value: i64) {
109        unsafe {
110            kcounter_min_ffi(self.descriptor, value);
111        }
112    }
113
114    /// Update the calling CPU's counter slot to the maximum of its current value and the given
115    /// value.
116    #[inline]
117    pub fn max(&self, value: i64) {
118        unsafe {
119            kcounter_max_ffi(self.descriptor, value);
120        }
121    }
122}
123
124/// Macro to safely define a new Counter in Rust that is visible to the kernel.
125///
126/// # Example
127/// ```rust
128/// define_kcounter!(MY_COUNTER, "my.custom.counter", Sum);
129///
130/// fn some_kernel_code() {
131///     MY_COUNTER.add(1);
132/// }
133/// ```
134#[macro_export]
135macro_rules! define_kcounter {
136    ($rust_var:ident, $name:expr, $type:ident) => {
137        pub static $rust_var: $crate::Counter = {
138            #[unsafe(link_section = concat!(".bss.kcounter.", $name))]
139            #[used]
140            static mut ARENA: [i64; $crate::SMP_MAX_CPUS] = [0; $crate::SMP_MAX_CPUS];
141
142            #[unsafe(link_section = concat!("kcountdesc.", $name))]
143            #[used]
144            static DESC: $crate::Descriptor =
145                $crate::Descriptor::new($crate::to_array::<56>($name), $crate::Type::$type as u64);
146
147            unsafe { $crate::Counter::new_with_ptr(&DESC as *const $crate::Descriptor) }
148        };
149    };
150}