use alloc::sync::{Arc, Weak};
use core::{
any::Any,
fmt::Debug,
sync::atomic::{AtomicBool, Ordering},
};
use ax_alloc::GlobalPage;
use ax_hal::mem::virt_to_phys;
use ax_memory_addr::{PAGE_SIZE_4K, PhysAddr};
use axpoll::{IoEvents, Pollable};
use axpoll_set::PollSet;
use kbpf_basic::{
linux_bpf::{perf_event_mmap_page, 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::{
StarryError, StarryResult,
ebpf::{BPF_HELPER_FUN_SET, error::BpfResultExt, prog::BpfProg},
file::FileLike,
sync::IrqMutex,
task::future::IrqNotify,
};
#[cfg(target_arch = "x86_64")]
const BPF_JIT_MEM_PAGES: usize = 4;
struct BpfPerfEventState {
inner: BpfPerfEvent,
pages: Option<Weak<GlobalPage>>,
}
impl BpfPerfEventState {
fn is_mapped(&self) -> bool {
self.pages
.as_ref()
.is_some_and(|pages| pages.strong_count() > 0)
}
}
#[derive(Clone)]
pub(super) struct BpfPerfOutput {
state: Arc<IrqMutex<BpfPerfEventState>>,
poll_notify: Arc<IrqNotify>,
}
#[derive(Clone)]
pub(super) struct BpfPerfPoll {
state: Arc<IrqMutex<BpfPerfEventState>>,
poll_ready: Arc<PollSet>,
}
impl Pollable for BpfPerfPoll {
fn poll(&self) -> IoEvents {
if self.state.lock().inner.readable() {
IoEvents::IN
} else {
IoEvents::empty()
}
}
unsafe fn register_shared(
&self,
sink: &mut dyn axpoll::SharedRegistrationSink,
events: IoEvents,
) {
if events.contains(IoEvents::IN) {
unsafe { sink.register_shared(&self.poll_ready, IoEvents::IN) };
}
}
unsafe fn register_exclusive(
&self,
sink: &mut dyn axpoll::ExclusiveRegistrationSink,
events: IoEvents,
) {
if events.contains(IoEvents::IN) {
unsafe { sink.register_exclusive(&self.poll_ready, IoEvents::IN) };
}
}
}
impl BpfPerfOutput {
pub(super) fn write_event(&self, data: &[u8]) -> StarryResult<()> {
let notify = {
let mut state = self.state.lock();
if !state.is_mapped() {
return Ok(());
}
state.inner.write_event(data).into_starry_result()?;
state.inner.enabled()
};
if notify {
self.poll_notify.notify_irq();
}
Ok(())
}
}
pub struct BpfPerfEventWrapper {
state: Arc<IrqMutex<BpfPerfEventState>>,
poll: BpfPerfPoll,
poll_notify: Arc<IrqNotify>,
poll_alive: Arc<AtomicBool>,
}
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());
let state = Arc::new(IrqMutex::new(BpfPerfEventState { inner, pages: None }));
Self {
poll: BpfPerfPoll {
state: Arc::clone(&state),
poll_ready,
},
state,
poll_notify,
poll_alive,
}
}
pub(super) fn output_handle(&self) -> BpfPerfOutput {
BpfPerfOutput {
state: Arc::clone(&self.state),
poll_notify: Arc::clone(&self.poll_notify),
}
}
pub(super) fn poll_handle(&self) -> BpfPerfPoll {
self.poll.clone()
}
}
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>,
) {
crate::task::spawn_kernel_thread(
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) -> StarryResult<()> {
self.state.lock().inner.enable().into_starry_result()?;
Ok(())
}
fn disable(&mut self) -> StarryResult<()> {
self.state.lock().inner.disable().into_starry_result()?;
Ok(())
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn device_mmap(&mut self, len: usize) -> StarryResult<(PhysAddr, Arc<dyn Any + Send + Sync>)> {
if self.state.lock().is_mapped() {
return Err(StarryError::ResourceBusy);
}
if len == 0 || !len.is_multiple_of(PAGE_SIZE_4K) {
return Err(StarryError::InvalidInput);
}
let num_pages = len / PAGE_SIZE_4K;
if num_pages < 2 || !(num_pages - 1).is_power_of_two() {
return Err(StarryError::InvalidInput);
}
let mut pages = GlobalPage::alloc_contiguous(num_pages, PAGE_SIZE_4K)
.map_err(|_| StarryError::NoMemory)?;
pages.zero();
let kvirt = pages.start_vaddr();
let paddr = virt_to_phys(kvirt);
let pages = Arc::new(pages);
let mut state = self.state.lock();
if state.is_mapped() {
return Err(StarryError::ResourceBusy);
}
state
.inner
.do_mmap(kvirt.as_usize(), len, 0)
.map_err(|_| StarryError::InvalidInput)?;
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);
}
state.pages = Some(Arc::downgrade(&pages));
drop(state);
let anchor: Arc<dyn Any + Send + Sync> = pages;
Ok((paddr, anchor))
}
}
impl Pollable for BpfPerfEventWrapper {
fn poll(&self) -> axpoll::IoEvents {
self.poll.poll()
}
unsafe fn register_shared(
&self,
sink: &mut dyn axpoll::SharedRegistrationSink,
events: axpoll::IoEvents,
) {
unsafe { self.poll.register_shared(sink, events) };
}
unsafe fn register_exclusive(
&self,
sink: &mut dyn axpoll::ExclusiveRegistrationSink,
events: axpoll::IoEvents,
) {
unsafe { self.poll.register_exclusive(sink, events) };
}
}
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>) -> StarryResult<Self> {
let prog = bpf_prog
.into_any_arc()
.downcast::<BpfProg>()
.map_err(|_| StarryError::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:?}");
StarryError::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(BPF_JIT_MEM_PAGES)?;
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:?}");
StarryError::InvalidInput
})?;
vm.jit_compile().map_err(|e| {
error!("rbpf::EbpfVmRaw::jit_compile failed: {e:?}");
StarryError::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 {}