use core::{arch::asm, marker::PhantomData};
mod capability;
mod registers;
pub use capability::{EventSupport, PmuInfo};
macro_rules! read_reg {
($register:literal) => {{
let value: u64;
unsafe { asm!(concat!("mrs {}, ", $register), out(reg) value, options(nomem, nostack)); }
value
}};
}
macro_rules! write_reg {
($register:literal, $value:expr) => {{
unsafe { asm!(concat!("msr ", $register, ", {}"), in(reg) $value as u64, options(nostack)); }
}};
}
mod access;
mod counter;
mod overflow;
fn isb() {
unsafe {
asm!("isb", options(nostack));
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum PmuError {
#[error("PMUv3 is unavailable on this CPU")]
Unavailable,
#[error("counter is not implemented on this CPU")]
InvalidCounter,
#[error("event is reported as unsupported")]
UnsupportedEvent,
#[error("configuration does not apply to this counter")]
InvalidConfiguration,
#[error("period is zero or exceeds the counter width")]
InvalidPeriod,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CounterId(u8);
impl CounterId {
pub const CYCLE: Self = Self(31);
pub const INSTRUCTIONS: Self = Self(32);
pub const fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EventConfig {
pub event: u16,
pub exclude_user: bool,
pub exclude_kernel: bool,
pub include_hypervisor: bool,
}
pub struct Pmu {
info: PmuInfo,
_not_send_sync: PhantomData<*mut ()>,
}
impl Pmu {
pub unsafe fn current() -> Result<Self, PmuError> {
let version = ((read_reg!("ID_AA64DFR0_EL1") >> 8) & 15) as u8;
if version == 0 || version == 15 {
return Err(PmuError::Unavailable);
}
let pmcr = read_reg!("PMCR_EL0");
Ok(Self {
info: PmuInfo {
version,
num_counters: ((pmcr >> 11) & 31) as usize,
has_instruction_counter: (read_reg!("ID_AA64DFR1_EL1") >> 36) & 15 != 0,
counter_width: if version >= 6 && pmcr & (1 << 7) != 0 {
64
} else {
32
},
cycle_counter_width: if pmcr & (1 << 6) != 0 { 64 } else { 32 },
pmceid0: read_reg!("PMCEID0_EL0"),
pmceid1: read_reg!("PMCEID1_EL0"),
},
_not_send_sync: PhantomData,
})
}
pub const fn info(&self) -> PmuInfo {
self.info
}
pub fn counter(&self, index: usize) -> Result<CounterId, PmuError> {
if index < self.info.num_counters {
Ok(CounterId(index as u8))
} else {
Err(PmuError::InvalidCounter)
}
}
fn validate(&self, id: CounterId) -> Result<(), PmuError> {
if id == CounterId::CYCLE
|| (id == CounterId::INSTRUCTIONS && self.info.has_instruction_counter)
|| id.index() < self.info.num_counters
{
Ok(())
} else {
Err(PmuError::InvalidCounter)
}
}
pub fn width(&self, id: CounterId) -> Result<u8, PmuError> {
self.validate(id)?;
Ok(if id == CounterId::INSTRUCTIONS {
64
} else if id == CounterId::CYCLE {
self.info.cycle_counter_width
} else {
self.info.counter_width
})
}
fn mask(&self, id: CounterId) -> Result<u64, PmuError> {
Ok(u64::MAX >> (64 - self.width(id)?))
}
fn implemented_mask(&self) -> u64 {
((1u64 << self.info.num_counters) - 1)
| (1u64 << 31)
| (u64::from(self.info.has_instruction_counter) << 32)
}
pub fn is_running(&self) -> bool {
read_reg!("PMCR_EL0") & 1 != 0
}
pub unsafe fn with_counting_paused<R>(&mut self, operation: impl FnOnce(&mut Self) -> R) -> R {
struct Restore<'a> {
pmu: &'a mut Pmu,
running: bool,
}
impl Drop for Restore<'_> {
fn drop(&mut self) {
if self.running {
self.pmu.start();
} else {
self.pmu.stop();
}
}
}
let running = self.is_running();
self.stop();
let restore = Restore { pmu: self, running };
operation(restore.pmu)
}
pub fn start(&mut self) {
let value = (read_reg!("PMCR_EL0") & 0xf9) | 1;
isb();
write_reg!("PMCR_EL0", value);
}
pub fn stop(&mut self) {
let value = read_reg!("PMCR_EL0") & 0xf8;
isb();
write_reg!("PMCR_EL0", value);
isb();
}
pub unsafe fn reset(&mut self) {
let mask = self.implemented_mask();
write_reg!("PMCNTENCLR_EL0", mask);
isb();
write_reg!("PMINTENCLR_EL1", mask);
isb();
write_reg!("PMOVSCLR_EL0", mask);
isb();
write_reg!("PMUSERENR_EL0", 0);
let value = 2
| 4
| 64
| if self.info.has_long_counters() {
128
} else {
0
};
isb();
write_reg!("PMCR_EL0", value);
isb();
if self.info.has_instruction_counter {
write_reg!("S3_3_C9_C4_0", 0); }
self.info.counter_width = if self.info.has_long_counters() {
64
} else {
32
};
self.info.cycle_counter_width = 64;
}
}