#[cfg(target_arch = "aarch64")]
use alloc::sync::{Arc, Weak};
use core::any::Any;
#[cfg(target_arch = "aarch64")]
use core::sync::atomic::{AtomicBool, Ordering};
#[cfg(target_arch = "aarch64")]
use ax_alloc::GlobalPage;
use ax_errno::{AxError, AxResult};
#[cfg(target_arch = "aarch64")]
use ax_hal::mem::virt_to_phys;
#[cfg(target_arch = "aarch64")]
use ax_memory_addr::PhysAddr;
#[cfg(target_arch = "aarch64")]
use ax_task::IrqNotify;
#[cfg(target_arch = "aarch64")]
use axpoll::PollSet;
use axpoll::{IoEvents, Pollable};
use kbpf_basic::linux_bpf::perf_event_attr;
#[cfg(target_arch = "aarch64")]
use kbpf_basic::linux_bpf::perf_event_mmap_page;
#[cfg(target_arch = "aarch64")]
use kbpf_basic::linux_bpf::{perf_hw_id, perf_type_id};
use super::PerfEventOps;
#[cfg(target_arch = "aarch64")]
use super::PerfReadValues;
#[cfg(target_arch = "aarch64")]
use super::sampling::{self, SampleSlot};
pub const ARMV8_PMUV3_PERF_TYPE: u32 = 8;
#[cfg(target_arch = "aarch64")]
const PERF_SAMPLE_IP: u64 = 1;
#[cfg(target_arch = "aarch64")]
const RING_DATA_OFFSET: usize = ax_memory_addr::PAGE_SIZE_4K;
#[cfg(target_arch = "aarch64")]
#[derive(Debug, Clone, Copy)]
enum Counter {
Cycle,
Programmable(usize),
}
#[cfg(target_arch = "aarch64")]
struct HwAlloc {
num_counters: usize,
used: u32,
cycle_used: bool,
}
#[cfg(target_arch = "aarch64")]
impl HwAlloc {
const fn new() -> Self {
HwAlloc {
num_counters: 0,
used: 0,
cycle_used: false,
}
}
fn alloc_cycle(&mut self) -> Option<Counter> {
if self.cycle_used {
return None;
}
self.cycle_used = true;
Some(Counter::Cycle)
}
fn alloc_counter(&mut self) -> Option<Counter> {
for n in 0..self.num_counters.min(32) {
if self.used & (1 << n) == 0 {
self.used |= 1 << n;
return Some(Counter::Programmable(n));
}
}
None
}
fn free(&mut self, counter: Counter) {
match counter {
Counter::Cycle => self.cycle_used = false,
Counter::Programmable(n) => {
if n < 32 {
self.used &= !(1 << n);
}
}
}
}
}
#[cfg(target_arch = "aarch64")]
static ALLOC: ax_kspin::SpinNoPreempt<HwAlloc> = ax_kspin::SpinNoPreempt::new(HwAlloc::new());
#[cfg(target_arch = "aarch64")]
pub(crate) fn alloc_programmable_counter() -> Option<usize> {
match ALLOC.lock().alloc_counter() {
Some(Counter::Programmable(n)) => Some(n),
_ => None,
}
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn free_programmable_counter(n: usize) {
ALLOC.lock().free(Counter::Programmable(n));
}
#[cfg(target_arch = "aarch64")]
#[derive(Debug)]
struct RingState {
pages: Weak<GlobalPage>,
ring_vaddr: usize,
ring_len: usize,
}
#[cfg(target_arch = "aarch64")]
impl RingState {
fn is_mapped(&self) -> bool {
self.pages.strong_count() > 0
}
}
#[cfg(target_arch = "aarch64")]
struct SamplingState {
period: u32,
freq: bool,
target_freq: u32,
sample_type: u64,
poll_ready: Arc<PollSet>,
notify: Arc<IrqNotify>,
poll_alive: Arc<AtomicBool>,
ring: Option<RingState>,
redirect: Option<(usize, usize, Arc<dyn Any + Send + Sync>)>,
}
#[cfg(target_arch = "aarch64")]
impl core::fmt::Debug for SamplingState {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SamplingState")
.field("period", &self.period)
.field("sample_type", &self.sample_type)
.field("ring", &self.ring)
.finish()
}
}
#[cfg(target_arch = "aarch64")]
fn start_sampling_notify_worker(
poll_ready: Arc<PollSet>,
notify: Arc<IrqNotify>,
poll_alive: Arc<AtomicBool>,
) {
ax_task::spawn_with_name(
move || loop {
notify.wait();
if !poll_alive.load(Ordering::Acquire) {
break;
}
unsafe { poll_ready.wake(IoEvents::IN) };
},
"hw-perf-sample-notify".into(),
);
}
#[cfg(target_arch = "aarch64")]
fn alloc_sampling_ring(len: usize) -> AxResult<(Arc<GlobalPage>, usize, PhysAddr)> {
if len == 0 || !len.is_multiple_of(ax_memory_addr::PAGE_SIZE_4K) {
return Err(AxError::InvalidInput);
}
let num_pages = len / ax_memory_addr::PAGE_SIZE_4K;
if num_pages < 2 || !(num_pages - 1).is_power_of_two() {
return Err(AxError::InvalidInput);
}
let mut pages = GlobalPage::alloc_contiguous(num_pages, ax_memory_addr::PAGE_SIZE_4K)?;
pages.zero();
let kvirt = pages.start_vaddr();
let paddr = virt_to_phys(kvirt);
let header = kvirt.as_usize() as *mut perf_event_mmap_page;
let data_size = (len - RING_DATA_OFFSET) as u64;
unsafe {
core::ptr::addr_of_mut!((*header).version).write(1); core::ptr::addr_of_mut!((*header).compat_version).write(0);
core::ptr::addr_of_mut!((*header).data_offset).write(RING_DATA_OFFSET as u64);
core::ptr::addr_of_mut!((*header).data_size).write(data_size);
core::ptr::addr_of_mut!((*header).data_head).write(0);
core::ptr::addr_of_mut!((*header).data_tail).write(0);
}
Ok((Arc::new(pages), kvirt.as_usize(), paddr))
}
#[cfg(target_arch = "aarch64")]
#[derive(Debug)]
pub struct HwPerfEvent {
counter: Counter,
sample_id: u64,
read_format: u64,
enabled_since: Option<u64>,
time_enabled: u64,
time_running: u64,
sampling: Option<SamplingState>,
per_task: Option<Arc<super::task::PerTaskCounter>>,
}
#[cfg(target_arch = "aarch64")]
impl HwPerfEvent {
fn raw_value(&self) -> u64 {
match self.counter {
Counter::Cycle => ax_cpu::pmu::cycles::read(),
Counter::Programmable(n) => ax_cpu::pmu::counter::read(n),
}
}
fn programmable_index(&self) -> Option<usize> {
match self.counter {
Counter::Programmable(n) => Some(n),
Counter::Cycle => None,
}
}
fn teardown_sampling_irq(&self) {
if self.sampling.is_none() {
return;
}
if let Some(n) = self.programmable_index() {
ax_cpu::pmu::overflow::disable_irq(n);
ax_cpu::pmu::counter::disable(n);
sampling::unregister(n);
}
}
fn device_mmap_rdpmc(&self, len: usize) -> AxResult<(PhysAddr, Arc<dyn Any + Send + Sync>)> {
if len < ax_memory_addr::PAGE_SIZE_4K {
return Err(AxError::InvalidInput);
}
let mut pages = GlobalPage::alloc_contiguous(1, ax_memory_addr::PAGE_SIZE_4K)?;
pages.zero();
let kvirt = pages.start_vaddr();
let paddr = virt_to_phys(kvirt);
let (index, pmc_width): (u32, u16) = match self.counter {
Counter::Cycle => (32, 64),
Counter::Programmable(n) => (n as u32 + 1, 32),
};
let header = kvirt.as_usize() as *mut perf_event_mmap_page;
unsafe {
core::ptr::addr_of_mut!((*header).version).write(1);
core::ptr::addr_of_mut!((*header).compat_version).write(0);
core::ptr::addr_of_mut!((*header).index).write(index);
core::ptr::addr_of_mut!((*header).offset).write(0);
core::ptr::addr_of_mut!((*header).pmc_width).write(pmc_width);
core::ptr::addr_of_mut!((*header).__bindgen_anon_1.capabilities).write(1u64 << 2);
}
let anchor: Arc<dyn Any + Send + Sync> = Arc::new(pages);
Ok((paddr, anchor))
}
}
#[cfg(target_arch = "aarch64")]
impl Drop for HwPerfEvent {
fn drop(&mut self) {
if let Some(ptc) = &self.per_task {
super::task::free_hw(ptc);
return;
}
self.teardown_sampling_irq();
match self.counter {
Counter::Cycle => ax_cpu::pmu::cycles::disable(),
Counter::Programmable(n) => ax_cpu::pmu::counter::disable(n),
}
ALLOC.lock().free(self.counter);
if let Some(sampling) = &self.sampling {
sampling.poll_alive.store(false, Ordering::Release);
sampling.notify.notify();
}
}
}
#[cfg(target_arch = "aarch64")]
impl Pollable for HwPerfEvent {
fn poll(&self) -> IoEvents {
if let Some(ptc) = &self.per_task {
if ptc.is_sampling() {
return if ptc.ring_has_data() {
IoEvents::IN
} else {
IoEvents::empty()
};
}
return IoEvents::IN;
}
match &self.sampling {
Some(sampling) => {
if sampling.ring.as_ref().is_some_and(ring_has_data) {
IoEvents::IN
} else {
IoEvents::empty()
}
}
None => IoEvents::IN,
}
}
fn register(&self, context: &mut core::task::Context<'_>, events: IoEvents) {
if let Some(ptc) = &self.per_task {
if ptc.is_sampling() && events.contains(IoEvents::IN) {
ptc.register_poll(context);
}
return;
}
if let Some(sampling) = &self.sampling
&& events.contains(IoEvents::IN)
{
unsafe { sampling.poll_ready.register(context.waker(), IoEvents::IN) };
}
}
}
#[cfg(target_arch = "aarch64")]
fn ring_has_data(ring: &RingState) -> bool {
if !ring.is_mapped() {
return false;
}
let header = ring.ring_vaddr as *const perf_event_mmap_page;
let (head, tail) = unsafe {
(
core::ptr::addr_of!((*header).data_head).read_volatile(),
core::ptr::addr_of!((*header).data_tail).read_volatile(),
)
};
head != tail
}
#[cfg(target_arch = "aarch64")]
impl PerfEventOps for HwPerfEvent {
fn enable(&mut self) -> AxResult<()> {
if let Some(ptc) = &self.per_task {
ptc.set_enabled();
return Ok(());
}
if self.enabled_since.is_none() {
self.enabled_since = Some(ax_runtime::hal::time::monotonic_time_nanos());
}
if let Some(sampling) = &self.sampling {
let Counter::Programmable(n) = self.counter else {
return Err(AxError::Unsupported);
};
let period = sampling.period;
let sample_type = sampling.sample_type;
let freq = sampling.freq;
let target_freq = sampling.target_freq;
let (ring_vaddr, ring_len) = if let Some((rv, rl, _anchor)) = &sampling.redirect {
(*rv, *rl)
} else {
match sampling.ring.as_ref() {
Some(r) => (r.ring_vaddr, r.ring_len),
None => (0, 0),
}
};
let notify_ptr = Arc::as_ptr(&sampling.notify) as *const ();
sampling::ensure_pmu_irq_registered();
ax_cpu::pmu::counter::preload(n, period);
sampling::register(
n,
SampleSlot {
ring_vaddr,
ring_len,
period,
sample_type,
id: self.sample_id,
notify: notify_ptr,
freq,
target_freq,
last_time: 0,
},
);
ax_cpu::pmu::overflow::enable_irq(n);
ax_cpu::pmu::counter::enable(n);
return Ok(());
}
match self.counter {
Counter::Cycle => ax_cpu::pmu::cycles::enable(),
Counter::Programmable(n) => ax_cpu::pmu::counter::enable(n),
}
Ok(())
}
fn disable(&mut self) -> AxResult<()> {
if let Some(ptc) = &self.per_task {
ptc.set_disabled();
return Ok(());
}
if self.sampling.is_some() {
self.teardown_sampling_irq();
} else {
match self.counter {
Counter::Cycle => ax_cpu::pmu::cycles::disable(),
Counter::Programmable(n) => ax_cpu::pmu::counter::disable(n),
}
}
if let Some(since) = self.enabled_since.take() {
let now = ax_runtime::hal::time::monotonic_time_nanos();
let elapsed = now.saturating_sub(since);
self.time_enabled += elapsed;
self.time_running += elapsed;
}
Ok(())
}
fn reset(&mut self) -> AxResult<()> {
if let Some(ptc) = &self.per_task {
ptc.reset();
return Ok(());
}
match self.counter {
Counter::Cycle => ax_cpu::pmu::cycles::reset(),
Counter::Programmable(n) => ax_cpu::pmu::counter::reset(n),
}
Ok(())
}
fn read_values(&mut self) -> AxResult<PerfReadValues> {
if let Some(ptc) = &self.per_task {
let (value, time_enabled, time_running) = super::task::read_values(ptc);
return Ok(PerfReadValues {
value,
time_enabled,
time_running,
read_format: ptc.read_format(),
});
}
let (mut time_enabled, mut time_running) = (self.time_enabled, self.time_running);
if let Some(since) = self.enabled_since {
let now = ax_runtime::hal::time::monotonic_time_nanos();
let elapsed = now.saturating_sub(since);
time_enabled += elapsed;
time_running += elapsed;
}
Ok(PerfReadValues {
value: self.raw_value(),
time_enabled,
time_running,
read_format: self.read_format,
})
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn set_sample_id(&mut self, id: u64) {
self.sample_id = id;
if let Some(ptc) = &self.per_task {
ptc.set_sample_id(id);
}
}
fn output_ring(&self) -> Option<(usize, usize, Arc<dyn Any + Send + Sync>)> {
if let Some(ptc) = &self.per_task {
return ptc.output_ring();
}
let ring = self.sampling.as_ref()?.ring.as_ref()?;
let pages = ring.pages.upgrade()?;
let anchor: Arc<dyn Any + Send + Sync> = pages;
Some((ring.ring_vaddr, ring.ring_len, anchor))
}
fn redirect_output(
&mut self,
ring_vaddr: usize,
ring_len: usize,
anchor: Arc<dyn Any + Send + Sync>,
) -> AxResult<()> {
if let Some(ptc) = &self.per_task {
ptc.set_redirect_ring(ring_vaddr, ring_len, anchor);
return Ok(());
}
if let Some(sampling) = &mut self.sampling {
sampling.redirect = Some((ring_vaddr, ring_len, anchor));
}
Ok(())
}
fn device_mmap(&mut self, len: usize) -> AxResult<(PhysAddr, Arc<dyn Any + Send + Sync>)> {
if let Some(ptc) = &self.per_task {
return device_mmap_per_task(ptc, len);
}
let Some(sampling) = &mut self.sampling else {
return self.device_mmap_rdpmc(len);
};
if sampling.ring.as_ref().is_some_and(RingState::is_mapped) {
return Err(AxError::ResourceBusy);
}
let (pages, ring_vaddr, paddr) = alloc_sampling_ring(len)?;
sampling.ring = Some(RingState {
pages: Arc::downgrade(&pages),
ring_vaddr,
ring_len: len,
});
let anchor: Arc<dyn Any + Send + Sync> = pages;
Ok((paddr, anchor))
}
}
#[cfg(target_arch = "aarch64")]
fn device_mmap_per_task(
ptc: &Arc<super::task::PerTaskCounter>,
len: usize,
) -> AxResult<(PhysAddr, Arc<dyn Any + Send + Sync>)> {
if !ptc.is_sampling() {
return Err(AxError::Unsupported);
}
if ptc.ring_mapped() {
return Err(AxError::ResourceBusy);
}
let (pages, ring_vaddr, paddr) = alloc_sampling_ring(len)?;
let poll_ready = Arc::new(PollSet::new());
let notify = Arc::new(IrqNotify::new());
let poll_alive = Arc::new(AtomicBool::new(true));
start_sampling_notify_worker(poll_ready.clone(), notify.clone(), poll_alive.clone());
ptc.set_ring(
pages.clone(),
ring_vaddr,
len,
notify,
poll_ready,
poll_alive,
);
let anchor: Arc<dyn Any + Send + Sync> = pages;
Ok((paddr, anchor))
}
#[cfg(target_arch = "aarch64")]
fn resolve_sampling(raw: u64, is_freq: bool) -> (u32, u32) {
if is_freq {
let freq = raw.clamp(1, sampling::MAX_TARGET_FREQ as u64) as u32;
(sampling::initial_period_for_freq(freq), freq)
} else {
(raw.min(u32::MAX as u64) as u32, 0)
}
}
#[cfg(target_arch = "aarch64")]
pub fn perf_event_open_hw(attr: &perf_event_attr, pid: i32) -> AxResult<HwPerfEvent> {
let Some(info) = ax_hal::pmu::info() else {
return Err(AxError::Unsupported);
};
ax_cpu::pmu::init_cpu();
ALLOC.lock().num_counters = info.num_counters;
if pid > 0 {
return perf_event_open_hw_per_task(attr, pid);
}
let exclude_user = attr.exclude_user() != 0;
let exclude_kernel = attr.exclude_kernel() != 0;
let raw = unsafe { attr.__bindgen_anon_1.sample_period };
let is_freq = attr.freq() != 0;
let is_sampling = raw > 0;
if is_sampling {
if attr.sample_type & PERF_SAMPLE_IP == 0
|| attr.sample_type & !super::sampling::SUPPORTED_SAMPLE_TYPE != 0
{
warn!(
"perf_event_open: sampling sample_type {:#x} unsupported (need PERF_SAMPLE_IP and \
only scalar fields)",
attr.sample_type
);
return Err(AxError::Unsupported);
}
if !is_freq && raw > u32::MAX as u64 {
warn!("perf_event_open: sample_period {raw} exceeds 32-bit counter");
return Err(AxError::InvalidInput);
}
}
let (sample_period, target_freq) = resolve_sampling(raw, is_freq);
let counter = if attr.type_ == perf_type_id::PERF_TYPE_HARDWARE as u32 {
if attr.config == perf_hw_id::PERF_COUNT_HW_CPU_CYCLES as u64 && !is_sampling {
let Some(counter) = ALLOC.lock().alloc_cycle() else {
return Err(AxError::NoMemory);
};
ax_cpu::pmu::cycles::configure(exclude_user, exclude_kernel);
counter
} else {
let Some(event) = ax_cpu::pmu::hw_event_to_arm(attr.config as u32) else {
warn!(
"perf_event_open: unsupported hardware config {:#x}",
attr.config
);
return Err(AxError::Unsupported);
};
alloc_programmable(event, exclude_user, exclude_kernel)?
}
} else if attr.type_ == perf_type_id::PERF_TYPE_RAW as u32
|| attr.type_ == ARMV8_PMUV3_PERF_TYPE
{
let event = (attr.config & 0xFFFF) as u16;
alloc_programmable(event, exclude_user, exclude_kernel)?
} else {
warn!(
"perf_event_open: unsupported hardware type {:#x}",
attr.type_
);
return Err(AxError::Unsupported);
};
let sampling = if is_sampling {
let poll_ready = Arc::new(PollSet::new());
let notify = Arc::new(IrqNotify::new());
let poll_alive = Arc::new(AtomicBool::new(true));
start_sampling_notify_worker(poll_ready.clone(), notify.clone(), poll_alive.clone());
Some(SamplingState {
period: sample_period,
freq: is_freq,
target_freq,
sample_type: attr.sample_type,
poll_ready,
notify,
poll_alive,
ring: None,
redirect: None,
})
} else {
None
};
Ok(HwPerfEvent {
counter,
sample_id: 0,
read_format: attr.read_format,
enabled_since: None,
time_enabled: 0,
time_running: 0,
sampling,
per_task: None,
})
}
#[cfg(target_arch = "aarch64")]
fn perf_event_open_hw_per_task(attr: &perf_event_attr, pid: i32) -> AxResult<HwPerfEvent> {
use crate::task::AsThread;
let task = crate::task::get_task(pid as u32)?;
let thr = task.try_as_thread().ok_or(AxError::NoSuchProcess)?;
let exclude_user = attr.exclude_user() != 0;
let exclude_kernel = attr.exclude_kernel() != 0;
let raw = unsafe { attr.__bindgen_anon_1.sample_period };
let is_freq = attr.freq() != 0;
let is_sampling = raw > 0;
if is_sampling {
if attr.sample_type & PERF_SAMPLE_IP == 0
|| attr.sample_type & !super::sampling::SUPPORTED_SAMPLE_TYPE != 0
{
warn!(
"perf_event_open: per-task sampling sample_type {:#x} unsupported (need \
PERF_SAMPLE_IP and only scalar fields)",
attr.sample_type
);
return Err(AxError::Unsupported);
}
if !is_freq && raw > u32::MAX as u64 {
warn!("perf_event_open: per-task sample_period {raw} exceeds 32-bit");
return Err(AxError::InvalidInput);
}
}
let (sample_period, target_freq) = resolve_sampling(raw, is_freq);
let event = if attr.type_ == perf_type_id::PERF_TYPE_HARDWARE as u32 {
match ax_cpu::pmu::hw_event_to_arm(attr.config as u32) {
Some(event) => event,
None => {
warn!(
"perf_event_open: unsupported per-task hardware config {:#x}",
attr.config
);
return Err(AxError::Unsupported);
}
}
} else if attr.type_ == perf_type_id::PERF_TYPE_RAW as u32
|| attr.type_ == ARMV8_PMUV3_PERF_TYPE
{
(attr.config & 0xFFFF) as u16
} else {
warn!(
"perf_event_open: unsupported per-task hardware type {:#x}",
attr.type_
);
return Err(AxError::Unsupported);
};
if !ax_cpu::pmu::event_supported(event) {
warn!(
"perf_event_open: per-task ARM event {:#x} not implemented on this CPU",
event
);
return Err(AxError::Unsupported);
}
let Some(n) = alloc_programmable_counter() else {
return Err(AxError::NoMemory);
};
let enabled = attr.disabled() == 0;
let enable_on_exec = attr.enable_on_exec() != 0;
let ptc = Arc::new(super::task::PerTaskCounter::new(
super::task::PerTaskConfig {
n,
event,
exclude_user,
exclude_kernel,
read_format: attr.read_format,
enabled,
enable_on_exec,
sample_period,
sample_type: attr.sample_type,
freq: is_freq,
target_freq,
want_comm: attr.comm() != 0,
want_mmap2: attr.mmap2() != 0,
want_task: attr.task() != 0,
sample_id_all: attr.sample_id_all() != 0,
inherit: attr.inherit() != 0,
},
));
super::task::attach(thr, ptc.clone());
Ok(HwPerfEvent {
counter: Counter::Programmable(n),
sample_id: 0,
read_format: attr.read_format,
enabled_since: None,
time_enabled: 0,
time_running: 0,
sampling: None,
per_task: Some(ptc),
})
}
#[cfg(target_arch = "aarch64")]
fn alloc_programmable(event: u16, exclude_user: bool, exclude_kernel: bool) -> AxResult<Counter> {
if !ax_cpu::pmu::event_supported(event) {
warn!(
"perf_event_open: ARM event {:#x} not implemented on this CPU",
event
);
return Err(AxError::Unsupported);
}
let Some(Counter::Programmable(n)) = ALLOC.lock().alloc_counter() else {
return Err(AxError::NoMemory);
};
ax_cpu::pmu::counter::configure(n, event, exclude_user, exclude_kernel);
Ok(Counter::Programmable(n))
}
#[cfg(not(target_arch = "aarch64"))]
#[derive(Debug)]
pub struct HwPerfEvent;
#[cfg(not(target_arch = "aarch64"))]
impl Pollable for HwPerfEvent {
fn poll(&self) -> IoEvents {
IoEvents::IN
}
fn register(&self, _context: &mut core::task::Context<'_>, _events: IoEvents) {}
}
#[cfg(not(target_arch = "aarch64"))]
impl PerfEventOps for HwPerfEvent {
fn enable(&mut self) -> AxResult<()> {
Err(AxError::Unsupported)
}
fn disable(&mut self) -> AxResult<()> {
Err(AxError::Unsupported)
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[cfg(not(target_arch = "aarch64"))]
pub fn perf_event_open_hw(_attr: &perf_event_attr, _pid: i32) -> AxResult<HwPerfEvent> {
Err(AxError::Unsupported)
}