use alloc::sync::{Arc, Weak};
use core::{
any::Any,
fmt::Debug,
sync::atomic::{AtomicBool, Ordering},
};
use ax_alloc::GlobalPage;
use ax_errno::{AxError, AxResult};
use ax_hal::mem::virt_to_phys;
use ax_memory_addr::{PAGE_SIZE_4K, PhysAddr};
use ax_task::IrqNotify;
use axpoll::{IoEvents, PollSet, Pollable};
use kbpf_basic::{
linux_bpf::perf_event_sample_format,
perf::{PerfProbeArgs, bpf::BpfPerfEvent},
};
use kprobe::PtRegs;
use rbpf::EbpfVmRaw;
use super::PerfEventOps;
#[cfg(target_arch = "x86_64")]
use crate::perf::BPFJitMemory;
use crate::{
ebpf::{BPF_HELPER_FUN_SET, error::BpfResultExt, prog::BpfProg},
file::FileLike,
};
pub struct BpfPerfEventWrapper {
inner: BpfPerfEvent,
poll_ready: Arc<PollSet>,
poll_notify: Arc<IrqNotify>,
poll_alive: Arc<AtomicBool>,
pages: Option<Weak<GlobalPage>>,
}
impl BpfPerfEventWrapper {
pub fn new(inner: BpfPerfEvent) -> Self {
let poll_ready = Arc::new(PollSet::new());
let poll_notify = Arc::new(IrqNotify::new());
let poll_alive = Arc::new(AtomicBool::new(true));
start_bpf_perf_notify_worker(poll_ready.clone(), poll_notify.clone(), poll_alive.clone());
Self {
inner,
poll_ready,
poll_notify,
poll_alive,
pages: None,
}
}
fn is_mapped(&self) -> bool {
self.pages.as_ref().is_some_and(|w| w.strong_count() > 0)
}
pub fn write_event(&mut self, data: &[u8]) -> AxResult<()> {
if !self.is_mapped() {
return Ok(());
}
self.inner.write_event(data).into_ax_result()?;
if self.inner.enabled() {
self.poll_notify.notify_irq();
}
Ok(())
}
}
impl Drop for BpfPerfEventWrapper {
fn drop(&mut self) {
self.poll_alive.store(false, Ordering::Release);
self.poll_notify.notify();
}
}
fn start_bpf_perf_notify_worker(
poll_ready: Arc<PollSet>,
poll_notify: Arc<IrqNotify>,
poll_alive: Arc<AtomicBool>,
) {
ax_task::spawn_with_name(
move || loop {
poll_notify.wait();
if !poll_alive.load(Ordering::Acquire) {
break;
}
unsafe { poll_ready.wake(IoEvents::IN) };
},
"bpf-perf-notify".into(),
);
}
impl Debug for BpfPerfEventWrapper {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "BpfPerfEventWrapper")
}
}
impl PerfEventOps for BpfPerfEventWrapper {
fn enable(&mut self) -> AxResult<()> {
self.inner.enable().into_ax_result()?;
Ok(())
}
fn disable(&mut self) -> AxResult<()> {
self.inner.disable().into_ax_result()?;
Ok(())
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn device_mmap(&mut self, len: usize) -> AxResult<(PhysAddr, Arc<dyn Any + Send + Sync>)> {
if self.is_mapped() {
return Err(AxError::ResourceBusy);
}
if len == 0 || !len.is_multiple_of(PAGE_SIZE_4K) {
return Err(AxError::InvalidInput);
}
let num_pages = len / 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, PAGE_SIZE_4K)?;
pages.zero();
let kvirt = pages.start_vaddr();
let paddr = virt_to_phys(kvirt);
self.inner
.do_mmap(kvirt.as_usize(), len, 0)
.map_err(|_| AxError::InvalidInput)?;
let pages = Arc::new(pages);
self.pages = Some(Arc::downgrade(&pages));
let anchor: Arc<dyn Any + Send + Sync> = pages;
Ok((paddr, anchor))
}
}
impl Pollable for BpfPerfEventWrapper {
fn poll(&self) -> axpoll::IoEvents {
if self.inner.readable() {
IoEvents::IN
} else {
IoEvents::empty()
}
}
fn register(&self, context: &mut core::task::Context<'_>, events: axpoll::IoEvents) {
if events.contains(IoEvents::IN) {
unsafe { self.poll_ready.register(context.waker(), IoEvents::IN) };
}
}
}
pub fn perf_event_open_bpf(args: PerfProbeArgs) -> BpfPerfEventWrapper {
debug_assert_eq!(
args.sample_type,
Some(perf_event_sample_format::PERF_SAMPLE_RAW)
);
BpfPerfEventWrapper::new(BpfPerfEvent::new(args))
}
pub struct OwnedEbpfVm {
vm: EbpfVmRaw<'static>,
#[cfg(target_arch = "x86_64")]
_jit_exec_memory: BPFJitMemory,
_prog: Arc<BpfProg>,
}
impl OwnedEbpfVm {
pub fn new(bpf_prog: Arc<dyn FileLike>) -> AxResult<Self> {
let prog = bpf_prog
.into_any_arc()
.downcast::<BpfProg>()
.map_err(|_| AxError::InvalidInput)?;
let prog_slice = prog.insns();
let prog_slice =
unsafe { core::slice::from_raw_parts(prog_slice.as_ptr(), prog_slice.len()) };
let mut vm = EbpfVmRaw::new(Some(prog_slice)).map_err(|e| {
error!("rbpf::EbpfVmRaw::new failed: {e:?}");
AxError::InvalidInput
})?;
if let Some(table) = BPF_HELPER_FUN_SET.get() {
for (key, value) in table.iter() {
let _ = vm.register_helper(*key, *value);
}
}
vm.register_allowed_memory(0..u64::MAX);
#[cfg(target_arch = "x86_64")]
{
let mut jit_exec_memory = BPFJitMemory::new(4)?;
let jit_slice = unsafe { jit_exec_memory.as_static_mut_slice() };
vm.set_jit_exec_memory(jit_slice).map_err(|e| {
error!("rbpf::EbpfVmRaw::set_jit_exec_memory failed: {e:?}");
AxError::InvalidInput
})?;
vm.jit_compile().map_err(|e| {
error!("rbpf::EbpfVmRaw::jit_compile failed: {e:?}");
AxError::InvalidInput
})?;
Ok(Self {
vm,
_jit_exec_memory: jit_exec_memory,
_prog: prog,
})
}
#[cfg(not(target_arch = "x86_64"))]
{
Ok(Self { vm, _prog: prog })
}
}
pub fn execute_program(&self, ctx: &mut [u8]) -> Result<u64, rbpf::lib::Error> {
#[cfg(not(target_arch = "x86_64"))]
{
self.vm.execute_program(ctx)
}
#[cfg(target_arch = "x86_64")]
{
unsafe { self.vm.execute_program_jit(ctx) }
}
}
pub fn execute_with_ptregs(&self, pt_regs: &mut PtRegs) -> Result<u64, rbpf::lib::Error> {
let probe_context = unsafe {
core::slice::from_raw_parts_mut(
pt_regs as *mut PtRegs as *mut u8,
core::mem::size_of::<PtRegs>(),
)
};
self.execute_program(probe_context)
}
}
impl Debug for OwnedEbpfVm {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "OwnedEbpfVm")
}
}
unsafe impl Send for OwnedEbpfVm {}
unsafe impl Sync for OwnedEbpfVm {}