File
Blob: firmware/crates/esp32-radio/src/platform/metrics.rs
| 1 | use super::ffi; |
| 2 | use crate::error::{Error, Result, check}; |
| 3 | use radio_core::metrics::Memory; |
| 4 | use std::{ffi::c_void, marker::PhantomData, ptr::NonNull, rc::Rc}; |
| 5 | |
| 6 | pub(crate) struct Sample { |
| 7 | pub now_us: u64, |
| 8 | pub idle_us: [u64; 2], |
| 9 | pub temperature_mc: Option<i32>, |
| 10 | pub rssi: Option<i32>, |
| 11 | pub internal: Memory, |
| 12 | pub psram: Memory, |
| 13 | } |
| 14 | |
| 15 | /// Single-task sensor owner that keeps temperature conversion on core 1. |
| 16 | pub(crate) struct Metrics { |
| 17 | handle: NonNull<c_void>, |
| 18 | _task: PhantomData<Rc<()>>, |
| 19 | } |
| 20 | |
| 21 | impl Metrics { |
| 22 | pub(crate) fn open() -> Result<Self> { |
| 23 | // SAFETY: scalar query of this task before any sensor floating-point work. |
| 24 | if unsafe { ffi::radio_current_core() } != 1 { |
| 25 | return Err(Error::new("metrics must run on core 1")); |
| 26 | } |
| 27 | // SAFETY: C owns the driver and context. It borrows no Rust memory and |
| 28 | // registers no callbacks; a missing temperature sensor remains optional. |
| 29 | let handle = NonNull::new(unsafe { ffi::radio_metrics_open() }) |
| 30 | .ok_or(Error::new("metrics initialization failed"))?; |
| 31 | Ok(Self { |
| 32 | handle, |
| 33 | _task: PhantomData, |
| 34 | }) |
| 35 | } |
| 36 | |
| 37 | pub(crate) fn sample(&mut self, detailed: bool) -> Result<Sample> { |
| 38 | let mut clocks = [0; 3]; |
| 39 | let mut memory = [0; 6]; |
| 40 | let mut sensors = [i32::MIN; 2]; |
| 41 | // SAFETY: live same-task handle and initialized output arrays of exactly |
| 42 | // the ABI's 3/6/2 elements. C writes synchronously and retains no pointers. |
| 43 | let status = unsafe { |
| 44 | ffi::radio_metrics_sample( |
| 45 | self.handle.as_ptr(), |
| 46 | clocks.as_mut_ptr(), |
| 47 | memory.as_mut_ptr(), |
| 48 | sensors.as_mut_ptr(), |
| 49 | i32::from(detailed), |
| 50 | ) |
| 51 | }; |
| 52 | check(status, "metrics sampling failed")?; |
| 53 | let pool = |offset| Memory { |
| 54 | free: memory[offset], |
| 55 | minimum_free: memory[offset + 1], |
| 56 | largest_block: memory[offset + 2], |
| 57 | }; |
| 58 | Ok(Sample { |
| 59 | now_us: clocks[0], |
| 60 | idle_us: [clocks[1], clocks[2]], |
| 61 | temperature_mc: (sensors[0] != i32::MIN).then_some(sensors[0]), |
| 62 | rssi: (sensors[1] != i32::MIN).then_some(sensors[1]), |
| 63 | internal: pool(0), |
| 64 | psram: pool(3), |
| 65 | }) |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | impl Drop for Metrics { |
| 70 | fn drop(&mut self) { |
| 71 | // SAFETY: sole task-affine owner; native sensor APIs have no callbacks. |
| 72 | unsafe { ffi::radio_metrics_free(self.handle.as_ptr()) }; |
| 73 | } |
| 74 | } |