use core::arch::asm;
pub struct PmuInfo {
pub num_counters: usize,
}
#[inline]
fn read_id_aa64dfr0_el1() -> u64 {
let value;
unsafe {
asm!("mrs {}, ID_AA64DFR0_EL1", out(reg) value);
}
value
}
#[inline]
fn read_pmcr_el0() -> u64 {
let value;
unsafe {
asm!("mrs {}, PMCR_EL0", out(reg) value);
}
value
}
#[inline]
fn write_pmcr_el0(value: u64) {
unsafe {
asm!("msr PMCR_EL0, {}", in(reg) value);
}
}
#[inline]
fn write_pmuserenr_el0(value: u64) {
unsafe {
asm!("msr PMUSERENR_EL0, {}", in(reg) value);
}
}
#[inline]
fn pmu_version() -> u64 {
(read_id_aa64dfr0_el1() >> 8) & 0xf
}
#[inline]
fn pmu_present() -> bool {
let v = pmu_version();
v >= 1 && v != 0xF
}
pub fn probe() -> Option<PmuInfo> {
if !pmu_present() {
return None;
}
let num_counters = ((read_pmcr_el0() >> 11) & 0x1f) as usize;
Some(PmuInfo { num_counters })
}
pub fn init_cpu() {
if !pmu_present() {
return;
}
let pmcr = read_pmcr_el0();
write_pmcr_el0(pmcr | (1 << 0) | (1 << 1));
write_pmuserenr_el0((1 << 3) | (1 << 2));
}
pub fn read_midr_el1() -> u64 {
let value;
unsafe {
asm!("mrs {}, MIDR_EL1", out(reg) value);
}
value
}
pub fn self_check() -> bool {
cycles::configure(false, false);
cycles::enable();
let a = cycles::read();
for _ in 0..100_000u32 {
core::hint::black_box(());
}
let b = cycles::read();
b > a
}
pub mod cycles {
use core::arch::asm;
const CYCLE_COUNTER_BIT: u64 = 1 << 31;
#[inline]
fn read_pmccfiltr_el0() -> u64 {
let value;
unsafe {
asm!("mrs {}, PMCCFILTR_EL0", out(reg) value);
}
value
}
#[inline]
fn write_pmccfiltr_el0(value: u64) {
unsafe {
asm!("msr PMCCFILTR_EL0, {}", in(reg) value);
}
}
pub fn configure(exclude_el0: bool, exclude_el1: bool) {
let mut filter = read_pmccfiltr_el0();
filter &= !((1 << 30) | (1 << 31));
if exclude_el0 {
filter |= 1 << 30;
}
if exclude_el1 {
filter |= 1 << 31;
}
write_pmccfiltr_el0(filter);
reset();
}
pub fn reset() {
unsafe {
asm!("msr PMCCNTR_EL0, {}", in(reg) 0u64);
}
}
pub fn enable() {
unsafe {
asm!("msr PMCNTENSET_EL0, {}", in(reg) CYCLE_COUNTER_BIT);
}
}
pub fn disable() {
unsafe {
asm!("msr PMCNTENCLR_EL0, {}", in(reg) CYCLE_COUNTER_BIT);
}
}
pub fn read() -> u64 {
let value;
unsafe {
asm!("mrs {}, PMCCNTR_EL0", out(reg) value);
}
value
}
}
#[inline]
fn read_pmceid0_el0() -> u64 {
let value;
unsafe {
asm!("mrs {}, PMCEID0_EL0", out(reg) value);
}
value
}
#[inline]
fn read_pmceid1_el0() -> u64 {
let value;
unsafe {
asm!("mrs {}, PMCEID1_EL0", out(reg) value);
}
value
}
pub fn event_supported(event: u16) -> bool {
match event {
0x00..=0x1F => (read_pmceid0_el0() >> event) & 1 != 0,
0x20..=0x3F => (read_pmceid1_el0() >> (event - 0x20)) & 1 != 0,
_ => true,
}
}
pub fn hw_event_to_arm(hw_id: u32) -> Option<u16> {
match hw_id {
0 => Some(0x11),
1 => Some(0x08),
2 => Some(0x04),
3 => Some(0x03),
4 => Some(0x21),
5 => Some(0x10),
6 => Some(0x1D),
7 => Some(0x23),
8 => Some(0x24),
_ => None,
}
}
pub mod counter {
use core::arch::asm;
const MAX_COUNTER: usize = 30;
const EVTYPER_P_EXCLUDE_EL1: u64 = 1 << 31;
const EVTYPER_U_EXCLUDE_EL0: u64 = 1 << 30;
const EVTYPER_EVENT_MASK: u64 = 0xFFFF;
macro_rules! pmev_switch {
(read $n:expr, $reg:literal) => {{
macro_rules! arm {
($idx:literal) => {{
let value: u64;
unsafe {
asm!(concat!("mrs {}, ", $reg, $idx, "_EL0"), out(reg) value);
}
value
}};
}
match $n {
0 => arm!("0"),
1 => arm!("1"),
2 => arm!("2"),
3 => arm!("3"),
4 => arm!("4"),
5 => arm!("5"),
6 => arm!("6"),
7 => arm!("7"),
8 => arm!("8"),
9 => arm!("9"),
10 => arm!("10"),
11 => arm!("11"),
12 => arm!("12"),
13 => arm!("13"),
14 => arm!("14"),
15 => arm!("15"),
16 => arm!("16"),
17 => arm!("17"),
18 => arm!("18"),
19 => arm!("19"),
20 => arm!("20"),
21 => arm!("21"),
22 => arm!("22"),
23 => arm!("23"),
24 => arm!("24"),
25 => arm!("25"),
26 => arm!("26"),
27 => arm!("27"),
28 => arm!("28"),
29 => arm!("29"),
30 => arm!("30"),
_ => 0u64,
}
}};
(write $n:expr, $reg:literal, $value:expr) => {{
let v: u64 = $value;
macro_rules! arm {
($idx:literal) => {{
unsafe {
asm!(concat!("msr ", $reg, $idx, "_EL0, {}"), in(reg) v);
}
}};
}
match $n {
0 => arm!("0"),
1 => arm!("1"),
2 => arm!("2"),
3 => arm!("3"),
4 => arm!("4"),
5 => arm!("5"),
6 => arm!("6"),
7 => arm!("7"),
8 => arm!("8"),
9 => arm!("9"),
10 => arm!("10"),
11 => arm!("11"),
12 => arm!("12"),
13 => arm!("13"),
14 => arm!("14"),
15 => arm!("15"),
16 => arm!("16"),
17 => arm!("17"),
18 => arm!("18"),
19 => arm!("19"),
20 => arm!("20"),
21 => arm!("21"),
22 => arm!("22"),
23 => arm!("23"),
24 => arm!("24"),
25 => arm!("25"),
26 => arm!("26"),
27 => arm!("27"),
28 => arm!("28"),
29 => arm!("29"),
30 => arm!("30"),
_ => {}
}
}};
}
pub fn configure(n: usize, event: u16, exclude_el0: bool, exclude_el1: bool) {
debug_assert!(n <= MAX_COUNTER);
let mut evtyper = read_typer(n);
evtyper &= !(EVTYPER_EVENT_MASK | EVTYPER_U_EXCLUDE_EL0 | EVTYPER_P_EXCLUDE_EL1);
evtyper |= (event as u64) & EVTYPER_EVENT_MASK;
if exclude_el0 {
evtyper |= EVTYPER_U_EXCLUDE_EL0;
}
if exclude_el1 {
evtyper |= EVTYPER_P_EXCLUDE_EL1;
}
write_typer(n, evtyper);
reset(n);
}
pub fn enable(n: usize) {
debug_assert!(n <= MAX_COUNTER);
if n > MAX_COUNTER {
return;
}
unsafe {
asm!("msr PMCNTENSET_EL0, {}", in(reg) 1u64 << n);
}
}
pub fn disable(n: usize) {
debug_assert!(n <= MAX_COUNTER);
if n > MAX_COUNTER {
return;
}
unsafe {
asm!("msr PMCNTENCLR_EL0, {}", in(reg) 1u64 << n);
}
}
pub fn reset(n: usize) {
write(n, 0);
}
pub fn read(n: usize) -> u64 {
debug_assert!(n <= MAX_COUNTER);
pmev_switch!(read n, "PMEVCNTR") & 0xFFFF_FFFF
}
pub fn write(n: usize, value: u64) {
debug_assert!(n <= MAX_COUNTER);
pmev_switch!(write n, "PMEVCNTR", value);
}
pub fn preload(n: usize, period: u32) {
write(n, (0u32).wrapping_sub(period) as u64);
}
fn read_typer(n: usize) -> u64 {
pmev_switch!(read n, "PMEVTYPER")
}
fn write_typer(n: usize, value: u64) {
pmev_switch!(write n, "PMEVTYPER", value);
}
}
pub mod overflow {
use core::arch::asm;
const MAX_COUNTER: usize = 30;
pub fn status() -> u32 {
let value: u64;
unsafe {
asm!("mrs {}, PMOVSCLR_EL0", out(reg) value);
}
value as u32
}
pub fn clear(mask: u32) {
unsafe {
asm!("msr PMOVSCLR_EL0, {}", in(reg) mask as u64);
}
}
pub fn enable_irq(n: usize) {
debug_assert!(n <= MAX_COUNTER);
if n > MAX_COUNTER {
return;
}
unsafe {
asm!("msr PMINTENSET_EL1, {}", in(reg) 1u64 << n);
}
}
pub fn disable_irq(n: usize) {
debug_assert!(n <= MAX_COUNTER);
if n > MAX_COUNTER {
return;
}
unsafe {
asm!("msr PMINTENCLR_EL1, {}", in(reg) 1u64 << n);
}
}
}
pub fn interrupted_pc() -> u64 {
let value;
unsafe {
asm!("mrs {}, ELR_EL1", out(reg) value);
}
value
}
pub fn interrupted_is_user() -> bool {
let spsr: u64;
unsafe {
asm!("mrs {}, SPSR_EL1", out(reg) spsr);
}
(spsr & 0xf) == 0
}