use alloc::sync::{Arc, Weak};
use core::{
any::Any,
sync::atomic::{AtomicU32, Ordering, fence},
};
use ax_alloc::GlobalPage;
use ax_hal::mem::virt_to_phys;
use ax_memory_addr::{PAGE_SIZE_4K, PhysAddr};
use kbpf_basic::linux_bpf::perf_event_mmap_page;
use crate::sync::IrqMutex;
#[derive(Clone, Copy, Debug)]
pub(super) struct RdpmcSnapshot {
pub(super) offset: u64,
pub(super) time_enabled: u64,
pub(super) time_running: u64,
}
pub(super) struct PerfRdpmcPage {
_pages: GlobalPage,
kernel_address: usize,
physical_address: PhysAddr,
publish_gate: IrqMutex<()>,
}
impl core::fmt::Debug for PerfRdpmcPage {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PerfRdpmcPage")
.field("physical_address", &self.physical_address)
.finish_non_exhaustive()
}
}
impl PerfRdpmcPage {
fn allocate(initial: RdpmcSnapshot) -> crate::StarryResult<Arc<Self>> {
let mut pages = GlobalPage::alloc_contiguous(1, PAGE_SIZE_4K)?;
pages.zero();
let kernel_address = pages.start_vaddr().as_usize();
let physical_address = virt_to_phys(pages.start_vaddr());
let page = Arc::new(Self {
_pages: pages,
kernel_address,
physical_address,
publish_gate: IrqMutex::new(()),
});
let header = page.header();
unsafe {
core::ptr::addr_of_mut!((*header).version).write_volatile(1);
core::ptr::addr_of_mut!((*header).compat_version).write_volatile(0);
core::ptr::addr_of_mut!((*header).pmc_width).write_volatile(0);
core::ptr::addr_of_mut!((*header).__bindgen_anon_1.capabilities)
.write_volatile(0);
}
page.publish(initial);
Ok(page)
}
fn header(&self) -> *mut perf_event_mmap_page {
self.kernel_address as *mut perf_event_mmap_page
}
fn sequence(&self) -> &AtomicU32 {
unsafe { AtomicU32::from_ptr(core::ptr::addr_of_mut!((*self.header()).lock)) }
}
fn publish(&self, snapshot: RdpmcSnapshot) {
let _publish = self.publish_gate.lock();
let sequence = self.sequence();
let odd = sequence.load(Ordering::Relaxed).wrapping_add(1) | 1;
sequence.store(odd, Ordering::SeqCst);
let header = self.header();
unsafe {
core::ptr::addr_of_mut!((*header).index).write_volatile(0);
core::ptr::addr_of_mut!((*header).offset).write_volatile(snapshot.offset as i64);
core::ptr::addr_of_mut!((*header).time_enabled).write_volatile(snapshot.time_enabled);
core::ptr::addr_of_mut!((*header).time_running).write_volatile(snapshot.time_running);
}
fence(Ordering::Release);
sequence.store(odd.wrapping_add(1), Ordering::Release);
}
fn physical_address(&self) -> PhysAddr {
self.physical_address
}
}
#[derive(Debug)]
pub(super) struct RdpmcMapping {
page: IrqMutex<Option<Weak<PerfRdpmcPage>>>,
}
impl RdpmcMapping {
pub(super) const fn new() -> Self {
Self {
page: IrqMutex::new(None),
}
}
pub(super) fn install(
&self,
len: usize,
initial: RdpmcSnapshot,
) -> crate::StarryResult<Arc<PerfRdpmcPage>> {
if len != PAGE_SIZE_4K {
return Err(crate::StarryError::InvalidInput);
}
let page = PerfRdpmcPage::allocate(initial)?;
let mut published = self.page.lock();
if published.as_ref().and_then(Weak::upgrade).is_some() {
return Err(crate::StarryError::ResourceBusy);
}
*published = Some(Arc::downgrade(&page));
Ok(page)
}
pub(super) fn withdraw(&self, page: &Arc<PerfRdpmcPage>) {
let mut published = self.page.lock();
if published
.as_ref()
.is_some_and(|weak| Weak::ptr_eq(weak, &Arc::downgrade(page)))
{
published.take();
}
}
pub(super) fn publish_active(&self, snapshot: RdpmcSnapshot) {
if let Some(page) = self.page.lock().as_ref().and_then(Weak::upgrade) {
page.publish(snapshot);
}
}
pub(super) fn publish_inactive(&self, snapshot: RdpmcSnapshot) {
if let Some(page) = self.page.lock().as_ref().and_then(Weak::upgrade) {
page.publish(snapshot);
}
}
}
pub(super) fn mapping_result(page: Arc<PerfRdpmcPage>) -> (PhysAddr, Arc<dyn Any + Send + Sync>) {
let physical_address = page.physical_address();
let anchor: Arc<dyn Any + Send + Sync> = page;
(physical_address, anchor)
}