use core::sync::atomic::Ordering;
use ax_hal::irq::{IrqContext, IrqId, IrqReturn};
use ax_task::IrqNotify;
use kbpf_basic::linux_bpf::perf_event_mmap_page;
use crate::{
sync::PreemptIrqSaveGuard,
task::{AsThread, PidNamespaceId, TgidNumber, TidNumber},
};
fn pmu_irq() -> Result<IrqId, ax_hal::irq::IrqError> {
ax_hal::pmu::irq()
}
const MAX_COUNTER: usize = 30;
const MIN_FREQ_PERIOD: u32 = 1;
const MAX_SAMPLE_PERIOD: u32 = u32::MAX;
pub const MAX_TARGET_FREQ: u32 = 100_000;
pub fn initial_period_for_freq(freq: u32) -> u32 {
(1_000_000_000u64 / freq.max(1) as u64).clamp(MIN_FREQ_PERIOD as u64, MAX_SAMPLE_PERIOD as u64)
as u32
}
fn next_freq_period(cur: u32, target_freq: u32, delta_ns: u64) -> u32 {
if delta_ns == 0 || target_freq == 0 {
return cur;
}
let ideal = (cur as u128 * 1_000_000_000u128) / (delta_ns as u128 * target_freq as u128);
let ideal = ideal.clamp(MIN_FREQ_PERIOD as u128, MAX_SAMPLE_PERIOD as u128) as i64;
let delta = (ideal - cur as i64 + 7) / 8;
(cur as i64 + delta).clamp(MIN_FREQ_PERIOD as i64, MAX_SAMPLE_PERIOD as i64) as u32
}
const PERF_RECORD_SAMPLE: u32 = 9;
const PERF_RECORD_MISC_KERNEL: u16 = 1;
const PERF_RECORD_MISC_USER: u16 = 2;
const SAMPLE_RECORD_MAX_LEN: usize = 8 + 9 * 8;
const PERF_SAMPLE_IP: u64 = 1 << 0;
const PERF_SAMPLE_TID: u64 = 1 << 1;
const PERF_SAMPLE_TIME: u64 = 1 << 2;
const PERF_SAMPLE_ADDR: u64 = 1 << 3;
const PERF_SAMPLE_ID: u64 = 1 << 6;
const PERF_SAMPLE_CPU: u64 = 1 << 7;
const PERF_SAMPLE_PERIOD: u64 = 1 << 8;
const PERF_SAMPLE_STREAM_ID: u64 = 1 << 9;
const PERF_SAMPLE_IDENTIFIER: u64 = 1 << 16;
pub const SUPPORTED_SAMPLE_TYPE: u64 = PERF_SAMPLE_IP
| PERF_SAMPLE_TID
| PERF_SAMPLE_TIME
| PERF_SAMPLE_ADDR
| PERF_SAMPLE_ID
| PERF_SAMPLE_CPU
| PERF_SAMPLE_PERIOD
| PERF_SAMPLE_STREAM_ID
| PERF_SAMPLE_IDENTIFIER;
#[derive(Clone, Copy)]
pub struct SampleSlot {
pub ring_vaddr: usize,
pub ring_len: usize,
pub period: u32,
pub sample_type: u64,
pub id: u64,
pub observer: PidNamespaceId,
pub notify: *const (),
pub freq: bool,
pub target_freq: u32,
pub last_time: u64,
}
unsafe impl Send for SampleSlot {}
#[ax_percpu::def_percpu]
static REGISTRY: [Option<SampleSlot>; 32] = [None; 32];
static REGISTERED: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
unsafe fn with_registry_mut<R>(
operation: impl for<'value> FnOnce(&'value mut [Option<SampleSlot>; 32]) -> R,
) -> R {
unsafe {
ax_percpu::with_cpu_pin(|pin| {
ax_percpu::with_exclusive_cpu(pin, |exclusive| {
REGISTRY.with_current_mut(exclusive, operation)
})
})
}
.unwrap_or_else(|error| panic!("perf sampling CPU-local state is invalid: {error}"))
}
pub fn register(n: usize, slot: SampleSlot) {
if n > MAX_COUNTER {
return;
}
let _guard = PreemptIrqSaveGuard::new();
unsafe { with_registry_mut(|registry| registry[n] = Some(slot)) };
}
pub fn unregister(n: usize) {
if n > MAX_COUNTER {
return;
}
let _guard = PreemptIrqSaveGuard::new();
unsafe { with_registry_mut(|registry| registry[n] = None) };
}
pub fn ensure_pmu_irq_registered() {
let pmu_irq = match pmu_irq() {
Ok(irq) => irq,
Err(err) => {
warn!("perf sampling: failed to resolve PMU overflow IRQ: {err:?}");
return;
}
};
if REGISTERED
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
let cpus = ax_hal::irq::CpuMask::first_n(ax_hal::cpu_num());
if let Err(err) = ax_hal::irq::request_percpu_irq(pmu_irq, cpus, pmu_overflow_handler) {
REGISTERED.store(false, Ordering::Release);
warn!("perf sampling: failed to register PMU overflow IRQ: {err:?}");
return;
}
}
if let Err(err) = ax_hal::irq::set_enable(pmu_irq, true) {
warn!("perf sampling: failed to enable PMU overflow IRQ {pmu_irq:?}: {err:?}");
}
}
pub fn pmu_overflow_handler(_ctx: IrqContext) -> IrqReturn {
let ip = ax_cpu::pmu::interrupted_pc();
let is_user = ax_cpu::pmu::interrupted_is_user();
let ovf = ax_cpu::pmu::overflow::status();
if ovf == 0 {
return IrqReturn::Unhandled;
}
let misc = if is_user {
PERF_RECORD_MISC_USER
} else {
PERF_RECORD_MISC_KERNEL
};
let mut handled: u32 = 0;
for n in 0..=MAX_COUNTER {
if ovf & (1 << n) == 0 {
continue;
}
handled |= 1 << n;
let sample = |registry: &mut [Option<SampleSlot>; 32]| {
let Some(slot) = registry[n].as_mut() else {
return false;
};
let sample_type = slot.sample_type;
let id = slot.id;
let notify_ptr = slot.notify;
let ring_vaddr = slot.ring_vaddr;
let ring_len = slot.ring_len;
let cur_period = slot.period;
let (pid, tid) = current_sample_ids(slot.observer);
let time = ax_runtime::hal::time::monotonic_time_nanos();
let cpu = ax_hal::percpu::this_cpu_id() as u32;
let mut record = [0u8; SAMPLE_RECORD_MAX_LEN];
let data = SampleData {
ip,
pid,
tid,
time,
addr: 0,
id,
stream_id: 0,
cpu,
period: cur_period as u64,
};
let len = build_sample(&mut record, sample_type, misc, &data);
unsafe { ring_write(ring_vaddr, ring_len, &record[..len]) };
let next_period = if slot.freq {
let np = if slot.last_time != 0 {
next_freq_period(
cur_period,
slot.target_freq,
time.saturating_sub(slot.last_time),
)
} else {
cur_period
};
slot.period = np;
slot.last_time = time;
np
} else {
cur_period
};
ax_cpu::pmu::counter::preload(n, next_period);
if !notify_ptr.is_null() {
let notify = unsafe { &*(notify_ptr as *const IrqNotify) };
notify.notify_irq();
}
true
};
let sampled = unsafe { with_registry_mut(sample) };
if !sampled {
continue;
}
}
ax_cpu::pmu::overflow::clear(handled);
IrqReturn::Handled
}
fn current_sample_ids(observer: PidNamespaceId) -> (Option<TgidNumber>, Option<TidNumber>) {
let task = ax_task::current();
let Some(thread) = task.try_as_thread() else {
return (None, None);
};
let tid = thread
.pid_identity()
.visible_number_in(observer)
.map(TidNumber::from);
let pid = thread
.proc_data
.identity()
.visible_number_in(observer)
.map(TgidNumber::from);
(pid, tid)
}
#[cfg(axtest)]
pub(crate) fn kernel_task_sample_ids_are_empty_for_test() -> bool {
let (pid, tid) = current_sample_ids(crate::task::ROOT_PID_NS.id());
pid.is_none() && tid.is_none()
}
struct SampleData {
ip: u64,
pid: Option<TgidNumber>,
tid: Option<TidNumber>,
time: u64,
addr: u64,
id: u64,
stream_id: u64,
cpu: u32,
period: u64,
}
fn build_sample(buf: &mut [u8], sample_type: u64, misc: u16, d: &SampleData) -> usize {
let mut off = 0usize;
macro_rules! put {
($v:expr) => {{
let bytes = $v.to_ne_bytes();
buf[off..off + bytes.len()].copy_from_slice(&bytes);
off += bytes.len();
}};
}
put!(PERF_RECORD_SAMPLE); put!(misc); let size_off = off;
put!(0u16);
if sample_type & PERF_SAMPLE_IDENTIFIER != 0 {
put!(d.id);
}
if sample_type & PERF_SAMPLE_IP != 0 {
put!(d.ip);
}
if sample_type & PERF_SAMPLE_TID != 0 {
put!(d.pid.map_or(0, TgidNumber::get));
put!(d.tid.map_or(0, TidNumber::get));
}
if sample_type & PERF_SAMPLE_TIME != 0 {
put!(d.time);
}
if sample_type & PERF_SAMPLE_ADDR != 0 {
put!(d.addr);
}
if sample_type & PERF_SAMPLE_ID != 0 {
put!(d.id);
}
if sample_type & PERF_SAMPLE_STREAM_ID != 0 {
put!(d.stream_id);
}
if sample_type & PERF_SAMPLE_CPU != 0 {
put!(d.cpu);
put!(0u32);
}
if sample_type & PERF_SAMPLE_PERIOD != 0 {
put!(d.period);
}
buf[size_off..size_off + 2].copy_from_slice(&(off as u16).to_ne_bytes());
off
}
unsafe fn ring_write(ring_vaddr: usize, ring_len: usize, record: &[u8]) {
if ring_vaddr == 0 || ring_len < core::mem::size_of::<perf_event_mmap_page>() {
return;
}
let header = ring_vaddr as *mut perf_event_mmap_page;
let data_offset =
unsafe { core::ptr::addr_of!((*header).data_offset).read_volatile() } as usize;
let data_size = unsafe { core::ptr::addr_of!((*header).data_size).read_volatile() } as usize;
if data_size == 0 || data_offset > ring_len || data_offset + data_size > ring_len {
return;
}
let len = record.len();
if len > data_size {
return;
}
let head = unsafe { core::ptr::addr_of!((*header).data_head).read_volatile() };
let tail = unsafe { core::ptr::addr_of!((*header).data_tail).read_volatile() };
if head.wrapping_sub(tail).wrapping_add(len as u64) > data_size as u64 {
return;
}
let data_base = ring_vaddr + data_offset;
let start = (head % data_size as u64) as usize;
let first = core::cmp::min(len, data_size - start);
unsafe {
core::ptr::copy_nonoverlapping(record.as_ptr(), (data_base + start) as *mut u8, first);
if first < len {
core::ptr::copy_nonoverlapping(
record.as_ptr().add(first),
data_base as *mut u8,
len - first,
);
}
}
core::sync::atomic::fence(Ordering::Release);
unsafe {
core::ptr::addr_of_mut!((*header).data_head).write_volatile(head.wrapping_add(len as u64));
}
}
pub unsafe fn ring_write_process(ring_vaddr: usize, ring_len: usize, record: &[u8]) {
let _guard = PreemptIrqSaveGuard::new();
unsafe { ring_write(ring_vaddr, ring_len, record) };
}