#[cfg(target_arch = "aarch64")]
use alloc::sync::Arc;
use core::any::Any;
#[cfg(target_arch = "aarch64")]
use core::sync::atomic::Ordering;
#[cfg(target_arch = "aarch64")]
use ax_memory_addr::PhysAddr;
#[cfg(target_arch = "aarch64")]
use axpoll::{ExclusiveRegistrationSink, SharedRegistrationSink};
use axpoll::{IoEvents, Pollable};
#[cfg(target_arch = "aarch64")]
use axpoll_set::PollSet;
#[cfg(not(target_arch = "aarch64"))]
use kbpf_basic::linux_bpf::perf_event_attr;
use super::PerfEventOps;
#[cfg(target_arch = "aarch64")]
use super::PerfReadValues;
#[cfg(target_arch = "aarch64")]
use super::control::PerfControl;
#[cfg(target_arch = "aarch64")]
use super::target::PerfCpuId;
#[cfg(not(target_arch = "aarch64"))]
use super::{access::AuthorizedPerfTarget, hw::ValidatedHwOpen};
#[cfg(target_arch = "aarch64")]
use super::{
cpu_worker,
hw_allocation::free_counter,
hw_owner::{Counter, SystemPmuDisable, SystemPmuEnable, SystemPmuRead, SystemPmuReset},
hw_sampling::{SamplingState, alloc_sampling_ring, device_mmap_per_task, ring_has_data},
inheritance::PerfInheritanceFamily,
output::{PerfOutputScope, PerfRingOutput},
rdpmc::{RdpmcMapping, RdpmcSnapshot, mapping_result},
sampling::{SampleOutput, SampleSlot, SampleSlotConfig},
sampling_lifecycle::SampleRegistration,
};
#[cfg(target_arch = "aarch64")]
use crate::sync::Mutex;
pub const ARMV8_PMUV3_PERF_TYPE: u32 = 8;
#[cfg(target_arch = "aarch64")]
#[derive(Debug)]
struct HwPerfEventState {
counter: Counter,
system_owner: Option<PerfCpuId>,
output_scope: PerfOutputScope,
sample_id: u64,
read_format: u64,
enabled_since: Option<u64>,
time_enabled: u64,
time_running: u64,
sampling: Option<SamplingState>,
sampling_registration: Option<SampleRegistration>,
per_task: Option<Arc<PerfInheritanceFamily>>,
rdpmc: RdpmcMapping,
}
#[cfg(target_arch = "aarch64")]
pub(super) struct SystemEventInit {
pub(super) counter: Counter,
pub(super) owner: PerfCpuId,
pub(super) read_format: u64,
pub(super) sampling: Option<SamplingState>,
pub(super) enable_at_open: bool,
}
#[cfg(target_arch = "aarch64")]
pub(super) struct TaskEventInit {
pub(super) counter: Counter,
pub(super) scheduler_id: u64,
pub(super) read_format: u64,
pub(super) family: Arc<PerfInheritanceFamily>,
}
#[cfg(target_arch = "aarch64")]
impl HwPerfEventState {
fn system_rdpmc_snapshot(&self, offset: u64, observed_at: u64) -> RdpmcSnapshot {
let (mut time_enabled, mut time_running) = (self.time_enabled, self.time_running);
if let Some(since) = self.enabled_since {
let elapsed = observed_at.saturating_sub(since);
time_enabled = time_enabled.saturating_add(elapsed);
time_running = time_running.saturating_add(elapsed);
}
RdpmcSnapshot {
offset,
time_enabled,
time_running,
}
}
fn device_mmap_system_rdpmc(
&self,
len: usize,
) -> crate::StarryResult<(PhysAddr, Arc<dyn Any + Send + Sync>)> {
let owner = self.system_owner.ok_or(crate::StarryError::BadState)?;
let hardware = cpu_worker::read_system(
owner,
SystemPmuRead {
counter: self.counter,
},
)?;
let active = self.enabled_since.is_some();
let initial = self.system_rdpmc_snapshot(
if active { 0 } else { hardware.value },
hardware.observed_at,
);
let page = self.rdpmc.install(len, initial)?;
if active {
self.rdpmc.publish_active(initial);
}
Ok(mapping_result(page))
}
}
#[cfg(target_arch = "aarch64")]
impl HwPerfEventState {
fn close(&mut self) -> crate::StarryResult<()> {
if let Some(family) = &self.per_task {
return family.close();
}
let owner = self.system_owner.ok_or(crate::StarryError::BadState)?;
let stopped = cpu_worker::disable_system(
owner,
SystemPmuDisable {
counter: self.counter,
registration: self.sampling_registration,
},
)?;
self.sampling_registration = None;
if let Some(since) = self.enabled_since.take() {
let elapsed = stopped.stopped_at.saturating_sub(since);
self.time_enabled = self.time_enabled.saturating_add(elapsed);
self.time_running = self.time_running.saturating_add(elapsed);
}
self.rdpmc
.publish_inactive(self.system_rdpmc_snapshot(stopped.value, stopped.stopped_at));
free_counter(self.counter);
if let Some(sampling) = &mut self.sampling {
sampling.poll_alive.store(false, Ordering::Release);
sampling.notify.notify();
sampling.output.clear();
}
Ok(())
}
}
#[cfg(target_arch = "aarch64")]
impl HwPerfEventState {
fn poll(&self) -> IoEvents {
if let Some(family) = &self.per_task {
let ptc = family.root();
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.output.owned().as_ref().is_some_and(ring_has_data) {
IoEvents::IN
} else {
IoEvents::empty()
}
}
None => IoEvents::IN,
}
}
}
#[cfg(target_arch = "aarch64")]
impl HwPerfEventState {
fn enable(&mut self) -> crate::StarryResult<()> {
if let Some(family) = &self.per_task {
return family.enable();
}
if self.enabled_since.is_some() {
return Ok(());
}
let owner = self.system_owner.ok_or(crate::StarryError::BadState)?;
let sampling = if let Some(sampling) = &self.sampling {
let Counter::Programmable(_) = self.counter else {
return Err(crate::StarryError::BadState);
};
let period = sampling.period;
let (ring, redirected) = sampling
.output
.effective()
.map_or((None, false), |(ring, redirected)| (Some(ring), redirected));
let notify = (!redirected).then(|| Arc::clone(&sampling.notify));
Some((
period,
SampleSlot::new(
SampleOutput::new(ring, notify),
SampleSlotConfig {
period,
sample_type: sampling.sample_type,
id: self.sample_id,
observer: sampling.observer,
freq: sampling.freq,
target_freq: sampling.target_freq,
last_time: 0,
},
),
))
} else {
None
};
let result = cpu_worker::enable_system(
owner,
SystemPmuEnable {
counter: self.counter,
sampling,
},
)?;
self.sampling_registration = result.registration;
self.enabled_since = Some(result.started_at);
self.rdpmc
.publish_active(self.system_rdpmc_snapshot(0, result.started_at));
Ok(())
}
fn disable(&mut self) -> crate::StarryResult<()> {
if let Some(family) = &self.per_task {
return family.disable();
}
let Some(since) = self.enabled_since else {
return Ok(());
};
let owner = self.system_owner.ok_or(crate::StarryError::BadState)?;
let stopped = cpu_worker::disable_system(
owner,
SystemPmuDisable {
counter: self.counter,
registration: self.sampling_registration,
},
)?;
self.sampling_registration = None;
self.enabled_since = None;
let elapsed = stopped.stopped_at.saturating_sub(since);
self.time_enabled = self.time_enabled.saturating_add(elapsed);
self.time_running = self.time_running.saturating_add(elapsed);
self.rdpmc
.publish_inactive(self.system_rdpmc_snapshot(stopped.value, stopped.stopped_at));
Ok(())
}
fn reset(&mut self) -> crate::StarryResult<()> {
if let Some(family) = &self.per_task {
return family.reset();
}
let owner = self.system_owner.ok_or(crate::StarryError::BadState)?;
cpu_worker::reset_system(
owner,
SystemPmuReset {
counter: self.counter,
sampling_period: self.sampling.as_ref().map(|sampling| sampling.period),
},
)?;
let snapshot = self.system_rdpmc_snapshot(0, ax_runtime::hal::time::monotonic_time_nanos());
if self.enabled_since.is_some() {
self.rdpmc.publish_active(snapshot);
} else {
self.rdpmc.publish_inactive(snapshot);
}
Ok(())
}
fn read_values(&mut self) -> crate::StarryResult<PerfReadValues> {
if let Some(family) = &self.per_task {
let (value, time_enabled, time_running) = family.read()?;
let root = family.root();
return Ok(PerfReadValues {
value,
time_enabled,
time_running,
read_format: root.read_format(),
});
}
let owner = self.system_owner.ok_or(crate::StarryError::BadState)?;
let snapshot = cpu_worker::read_system(
owner,
SystemPmuRead {
counter: self.counter,
},
)?;
let (mut time_enabled, mut time_running) = (self.time_enabled, self.time_running);
if let Some(since) = self.enabled_since {
let elapsed = snapshot.observed_at.saturating_sub(since);
time_enabled = time_enabled.saturating_add(elapsed);
time_running = time_running.saturating_add(elapsed);
}
Ok(PerfReadValues {
value: snapshot.value,
time_enabled,
time_running,
read_format: self.read_format,
})
}
fn set_sample_id(&mut self, id: u64) {
self.sample_id = id;
if let Some(family) = &self.per_task {
family.set_sample_id(id);
}
}
fn output_ring(&self) -> Option<PerfRingOutput> {
if let Some(family) = &self.per_task {
return family.root().output_ring();
}
self.sampling.as_ref()?.output.owned()
}
fn redirect_output(&mut self, output: PerfRingOutput) -> crate::StarryResult<()> {
if self.output_ring().is_some() {
return Err(crate::StarryError::InvalidInput);
}
if let Some(family) = &self.per_task {
return family.redirect_output(output);
}
let was_enabled = self.enabled_since.is_some();
if was_enabled {
self.disable()?;
}
if let Some(sampling) = &mut self.sampling {
sampling.output.redirect(output);
}
if was_enabled {
self.enable()?;
}
Ok(())
}
fn detach_output(&mut self) -> crate::StarryResult<()> {
if self.output_ring().is_some() {
return Err(crate::StarryError::InvalidInput);
}
if let Some(family) = &self.per_task {
return family.detach_output();
}
let was_enabled = self.enabled_since.is_some();
if was_enabled {
self.disable()?;
}
if let Some(sampling) = &mut self.sampling {
sampling.output.detach();
}
if was_enabled {
self.enable()?;
}
Ok(())
}
fn device_mmap(
&mut self,
len: usize,
) -> crate::StarryResult<(PhysAddr, Arc<dyn Any + Send + Sync>)> {
if let Some(family) = &self.per_task {
let ptc = family.root();
if ptc.is_sampling() {
return device_mmap_per_task(family, len);
}
return ptc.device_mmap_rdpmc(len);
}
let Some(sampling) = &mut self.sampling else {
return self.device_mmap_system_rdpmc(len);
};
if sampling.output.owned().is_some() {
return Err(crate::StarryError::ResourceBusy);
}
let (pages, ring_vaddr, paddr) = alloc_sampling_ring(len)?;
let page_anchor: Arc<dyn Any + Send + Sync> = pages;
let output = PerfRingOutput::new(ring_vaddr, len, page_anchor);
sampling.output.publish_owned(&output);
Ok((paddr, output.mapping_anchor()))
}
}
#[cfg(target_arch = "aarch64")]
struct HwPerfControl {
state: Mutex<HwPerfEventState>,
}
#[cfg(target_arch = "aarch64")]
impl core::fmt::Debug for HwPerfControl {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("HwPerfControl").finish_non_exhaustive()
}
}
#[cfg(target_arch = "aarch64")]
impl HwPerfControl {
fn task_family(&self) -> Option<Arc<PerfInheritanceFamily>> {
self.state.lock().per_task.clone()
}
fn system_poll_source(&self) -> Option<Arc<PollSet>> {
self.state
.lock()
.sampling
.as_ref()
.map(|sampling| Arc::clone(&sampling.poll_ready))
}
}
#[cfg(target_arch = "aarch64")]
impl Pollable for HwPerfControl {
fn poll(&self) -> IoEvents {
if let Some(family) = self.task_family() {
let root = family.root();
return if root.is_sampling() {
if root.ring_has_data() {
IoEvents::IN
} else {
IoEvents::empty()
}
} else {
IoEvents::IN
};
}
self.state.lock().poll()
}
unsafe fn register_shared(&self, sink: &mut dyn SharedRegistrationSink, events: IoEvents) {
if !events.contains(IoEvents::IN) {
return;
}
if let Some(family) = self.task_family() {
let root = family.root();
if root.is_sampling() {
unsafe { root.register_poll_shared(sink) };
}
return;
}
if let Some(source) = self.system_poll_source() {
unsafe { sink.register_shared(&source, IoEvents::IN) };
}
}
unsafe fn register_exclusive(
&self,
sink: &mut dyn ExclusiveRegistrationSink,
events: IoEvents,
) {
if !events.contains(IoEvents::IN) {
return;
}
if let Some(family) = self.task_family() {
let root = family.root();
if root.is_sampling() {
unsafe { root.register_poll_exclusive(sink) };
}
return;
}
if let Some(source) = self.system_poll_source() {
unsafe { sink.register_exclusive(&source, IoEvents::IN) };
}
}
}
#[cfg(target_arch = "aarch64")]
impl PerfControl for HwPerfControl {
fn enable(&self) -> crate::StarryResult<()> {
if let Some(family) = self.task_family() {
return family.enable();
}
self.state.lock().enable()
}
fn disable(&self) -> crate::StarryResult<()> {
if let Some(family) = self.task_family() {
return family.disable();
}
self.state.lock().disable()
}
fn reset(&self) -> crate::StarryResult<()> {
if let Some(family) = self.task_family() {
return family.reset();
}
self.state.lock().reset()
}
fn read_values(&self) -> crate::StarryResult<PerfReadValues> {
if let Some(family) = self.task_family() {
let (value, time_enabled, time_running) = family.read()?;
return Ok(PerfReadValues {
value,
time_enabled,
time_running,
read_format: family.root().read_format(),
});
}
self.state.lock().read_values()
}
fn device_mmap(
&self,
len: usize,
) -> crate::StarryResult<(PhysAddr, Arc<dyn Any + Send + Sync>)> {
self.state.lock().device_mmap(len)
}
fn output_ring(&self) -> Option<PerfRingOutput> {
if let Some(family) = self.task_family() {
return family.root().output_ring();
}
self.state.lock().output_ring()
}
fn output_scope(&self) -> Option<PerfOutputScope> {
Some(self.state.lock().output_scope)
}
fn redirect_output(&self, output: PerfRingOutput) -> crate::StarryResult<()> {
if let Some(family) = self.task_family() {
return family.redirect_output(output);
}
self.state.lock().redirect_output(output)
}
fn detach_output(&self) -> crate::StarryResult<()> {
if let Some(family) = self.task_family() {
return family.detach_output();
}
self.state.lock().detach_output()
}
}
#[cfg(target_arch = "aarch64")]
pub struct HwPerfEvent {
control: Arc<HwPerfControl>,
enable_at_open: bool,
}
#[cfg(target_arch = "aarch64")]
impl HwPerfEvent {
fn new(state: HwPerfEventState, enable_at_open: bool) -> Self {
Self {
control: Arc::new(HwPerfControl {
state: Mutex::new(state),
}),
enable_at_open,
}
}
pub(super) fn new_system(init: SystemEventInit) -> Self {
Self::new(
HwPerfEventState {
counter: init.counter,
system_owner: Some(init.owner),
output_scope: PerfOutputScope::Cpu(init.owner.as_usize()),
sample_id: 0,
read_format: init.read_format,
enabled_since: None,
time_enabled: 0,
time_running: 0,
sampling: init.sampling,
sampling_registration: None,
per_task: None,
rdpmc: RdpmcMapping::new(),
},
init.enable_at_open,
)
}
pub(super) fn new_task(init: TaskEventInit) -> Self {
Self::new(
HwPerfEventState {
counter: init.counter,
system_owner: None,
output_scope: PerfOutputScope::Task(init.scheduler_id),
sample_id: 0,
read_format: init.read_format,
enabled_since: None,
time_enabled: 0,
time_running: 0,
sampling: None,
sampling_registration: None,
per_task: Some(init.family),
rdpmc: RdpmcMapping::new(),
},
false,
)
}
pub(super) fn control_handle(&self) -> Arc<dyn PerfControl> {
self.control.clone()
}
}
#[cfg(target_arch = "aarch64")]
impl core::fmt::Debug for HwPerfEvent {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("HwPerfEvent").finish_non_exhaustive()
}
}
#[cfg(target_arch = "aarch64")]
impl Drop for HwPerfEvent {
fn drop(&mut self) {
let result = if let Some(family) = self.control.task_family() {
family.close()
} else {
self.control.state.lock().close()
};
if let Err(error) = result {
warn!("perf: owner-CPU PMU teardown failed, retaining resources: {error}");
core::mem::forget(Arc::clone(&self.control));
}
}
}
#[cfg(target_arch = "aarch64")]
impl Pollable for HwPerfEvent {
fn poll(&self) -> IoEvents {
self.control.poll()
}
unsafe fn register_shared(&self, sink: &mut dyn SharedRegistrationSink, events: IoEvents) {
unsafe { self.control.register_shared(sink, events) };
}
unsafe fn register_exclusive(
&self,
sink: &mut dyn ExclusiveRegistrationSink,
events: IoEvents,
) {
unsafe { self.control.register_exclusive(sink, events) };
}
}
#[cfg(target_arch = "aarch64")]
impl PerfEventOps for HwPerfEvent {
fn finish_open(&mut self) -> crate::StarryResult<()> {
if core::mem::take(&mut self.enable_at_open) {
self.control.enable()
} else {
Ok(())
}
}
fn enable(&mut self) -> crate::StarryResult<()> {
self.control.enable()
}
fn disable(&mut self) -> crate::StarryResult<()> {
self.control.disable()
}
fn reset(&mut self) -> crate::StarryResult<()> {
self.control.reset()
}
fn read_values(&mut self) -> crate::StarryResult<PerfReadValues> {
self.control.read_values()
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn set_sample_id(&mut self, id: u64) {
self.control.state.lock().set_sample_id(id);
}
fn device_mmap(
&mut self,
len: usize,
) -> crate::StarryResult<(PhysAddr, Arc<dyn Any + Send + Sync>)> {
self.control.device_mmap(len)
}
}
#[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
}
unsafe fn register_shared(
&self,
_sink: &mut dyn axpoll::SharedRegistrationSink,
_events: IoEvents,
) {
}
}
#[cfg(not(target_arch = "aarch64"))]
impl PerfEventOps for HwPerfEvent {
fn enable(&mut self) -> crate::StarryResult<()> {
Err(crate::StarryError::Unsupported)
}
fn disable(&mut self) -> crate::StarryResult<()> {
Err(crate::StarryError::Unsupported)
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[cfg(not(target_arch = "aarch64"))]
pub(super) fn perf_event_open_hw(
_attr: &perf_event_attr,
target: AuthorizedPerfTarget,
validated: ValidatedHwOpen,
) -> crate::StarryResult<HwPerfEvent> {
let _ = validated;
match target {
AuthorizedPerfTarget::Task { task, cpu } => {
let _ = (task, cpu);
}
AuthorizedPerfTarget::Cpu(cpu) => {
let _ = cpu;
}
}
Err(crate::StarryError::Unsupported)
}