Skip to main content

counters_rs/
lib.rs

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