use alloc::sync::Arc;
use core::any::Any;
use ax_errno::AxError;
use ax_hal::{kcov::KCOV_GLOBAL_GATE, mem::phys_to_virt, paging::PageSize};
use ax_memory_addr::PAGE_SIZE_4K;
use ax_sync::Mutex;
use axfs_ng_vfs::{NodeFlags, VfsError, VfsResult};
use crate::{
mm::SharedPages,
pseudofs::{DeviceMmap, DeviceOps},
task::AsThread,
};
const fn _ioc(dir: u32, ty: u8, nr: u32, size: usize) -> u32 {
(dir << 30) | ((ty as u32) << 8) | nr | ((size as u32) << 16)
}
const fn _io(ty: u8, nr: u32) -> u32 {
_ioc(0, ty, nr, 0)
}
const fn _ior(ty: u8, nr: u32, size: usize) -> u32 {
_ioc(2, ty, nr, size)
}
pub const KCOV_INIT_TRACE: u32 = _ior(b'c', 1, core::mem::size_of::<u64>());
pub const KCOV_ENABLE: u32 = _io(b'c', 100);
pub const KCOV_DISABLE: u32 = _io(b'c', 101);
pub const KCOV_RESET_TRACE: u32 = _io(b'c', 104);
pub const KCOV_TRACE_PC: u32 = 0;
pub const KCOV_TRACE_CMP: u32 = 1;
pub const KCOV_MODE_DISABLED: u32 = 0;
pub const KCOV_MODE_INIT: u32 = 1;
pub const KCOV_MODE_TRACE_PC: u32 = 2;
pub const KCOV_MODE_TRACE_CMP: u32 = 3;
pub const KCOV_MAX_ENTRIES: usize = 1024 * 1024;
#[derive(Clone)]
pub struct KcovThreadState {
pub buf_pages: Arc<SharedPages>,
pub buf_entries: usize,
pub mode: u32,
}
pub struct KcovFdState {
inner: Mutex<KcovFdInner>,
}
struct KcovFdInner {
mode: u32,
buf_pages: Option<Arc<SharedPages>>,
buf_entries: usize,
tracer_tid: Option<u64>,
}
impl KcovFdState {
pub fn new() -> Self {
Self {
inner: Mutex::new(KcovFdInner {
mode: KCOV_MODE_DISABLED,
buf_pages: None,
buf_entries: 0,
tracer_tid: None,
}),
}
}
pub fn ioctl(&self, cmd: u32, arg: usize) -> VfsResult<usize> {
match cmd {
KCOV_INIT_TRACE => {
let cover_size = arg;
if !(2..=KCOV_MAX_ENTRIES).contains(&cover_size) {
return Err(VfsError::InvalidInput);
}
let mut inner = self.inner.lock();
if inner.mode != KCOV_MODE_DISABLED {
return Err(VfsError::ResourceBusy);
}
let total_entries = cover_size;
let buf_byte_size = total_entries * core::mem::size_of::<u64>();
let num_pages = buf_byte_size.div_ceil(PAGE_SIZE_4K);
let aligned_size = num_pages * PAGE_SIZE_4K;
let pages = Arc::new(
SharedPages::new(aligned_size, PageSize::Size4K)
.map_err(|_| VfsError::InvalidInput)?,
);
let base_vaddr = phys_to_virt(pages.phys_pages[0]);
unsafe {
core::ptr::write_volatile(base_vaddr.as_mut_ptr_of::<u64>(), 0u64);
}
inner.mode = KCOV_MODE_INIT;
inner.buf_pages = Some(pages);
inner.buf_entries = total_entries - 1;
Ok(0)
}
KCOV_ENABLE => {
let mode_arg = arg as u32;
let internal_mode = match mode_arg {
KCOV_TRACE_PC => KCOV_MODE_TRACE_PC,
KCOV_TRACE_CMP => return Err(VfsError::InvalidInput),
_ => return Err(VfsError::InvalidInput),
};
let task = ax_task::current();
let mut inner = self.inner.lock();
if inner.mode != KCOV_MODE_INIT {
return Err(VfsError::InvalidInput);
}
if let Some(thr) = task.try_as_thread()
&& thr.with_kcov(|k| k.is_some())
{
return Err(VfsError::ResourceBusy);
}
inner.mode = internal_mode;
inner.tracer_tid = Some(task.id().as_u64());
unsafe {
KCOV_GLOBAL_GATE = 1;
}
if let Some(thr) = task.try_as_thread() {
let thread_state = KcovThreadState {
buf_pages: inner.buf_pages.clone().unwrap(),
buf_entries: inner.buf_entries,
mode: internal_mode,
};
thr.set_kcov(Some(thread_state));
}
Ok(0)
}
KCOV_DISABLE => {
if arg != 0 {
return Err(VfsError::InvalidInput);
}
let task = ax_task::current();
let mut inner = self.inner.lock();
if inner.tracer_tid != Some(task.id().as_u64()) {
return Err(VfsError::InvalidInput);
}
inner.mode = KCOV_MODE_INIT;
inner.tracer_tid = None;
if let Some(thr) = task.try_as_thread() {
thr.set_kcov(None);
}
Ok(0)
}
KCOV_RESET_TRACE => {
if arg != 0 {
return Err(VfsError::InvalidInput);
}
let task = ax_task::current();
let inner = self.inner.lock();
if inner.mode != KCOV_MODE_TRACE_PC && inner.mode != KCOV_MODE_TRACE_CMP {
return Err(VfsError::InvalidInput);
}
if inner.tracer_tid != Some(task.id().as_u64()) {
return Err(VfsError::InvalidInput);
}
if let Some(ref pages) = inner.buf_pages {
let count_vaddr = phys_to_virt(pages.phys_pages[0]);
unsafe {
core::ptr::write_volatile(count_vaddr.as_mut_ptr_of::<u64>(), 0u64);
}
}
Ok(0)
}
_ => Err(VfsError::NotATty),
}
}
pub fn mmap(&self, offset: u64) -> DeviceMmap {
if offset != 0 {
return DeviceMmap::NotConfigured;
}
let inner = self.inner.lock();
match inner.buf_pages {
Some(ref pages) => DeviceMmap::SharedPages(pages.clone()),
None => DeviceMmap::NotConfigured,
}
}
pub fn on_close(&self) {
let mut inner = self.inner.lock();
if inner.mode == KCOV_MODE_TRACE_PC || inner.mode == KCOV_MODE_TRACE_CMP {
if inner.tracer_tid == Some(ax_task::current().id().as_u64())
&& let Some(thr) = ax_task::current().try_as_thread()
{
thr.set_kcov(None);
}
inner.mode = KCOV_MODE_INIT;
inner.buf_pages = None;
inner.buf_entries = 0;
inner.tracer_tid = None;
} else {
inner.mode = KCOV_MODE_DISABLED;
inner.buf_pages = None;
inner.buf_entries = 0;
inner.tracer_tid = None;
}
}
}
pub struct KcovDevice;
impl DeviceOps for KcovDevice {
fn read_at(&self, _buf: &mut [u8], _offset: u64) -> VfsResult<usize> {
Err(AxError::InvalidInput)
}
fn write_at(&self, _buf: &[u8], _offset: u64) -> VfsResult<usize> {
Err(AxError::InvalidInput)
}
fn close(&self, _exclusive: bool) {}
fn ioctl(&self, _cmd: u32, _arg: usize) -> VfsResult<usize> {
Err(VfsError::InvalidInput)
}
fn mmap(&self, _offset: u64, _length: u64) -> DeviceMmap {
DeviceMmap::NotConfigured
}
fn as_any(&self) -> &dyn Any {
self
}
fn flags(&self) -> NodeFlags {
NodeFlags::NON_CACHEABLE
}
}
pub fn disable_for_thread(_tid: u32) {
if let Some(thr) = ax_task::current().try_as_thread() {
thr.set_kcov(None);
}
}
pub fn on_fork(_child_tid: u32) {}
#[unsafe(no_mangle)]
extern "C" fn kcov_trace_pc_impl(pc: u64) {
if unsafe { KCOV_GLOBAL_GATE == 0 } {
return;
}
let task = ax_task::current();
let Some(thr) = task.try_as_thread() else {
return;
};
thr.with_kcov(|kcov| {
let Some(kcov) = kcov else {
return;
};
if kcov.mode != KCOV_MODE_TRACE_PC {
return;
}
let pages = &kcov.buf_pages.phys_pages;
let entries = kcov.buf_entries;
let count_vaddr = phys_to_virt(pages[0]);
let count_ptr = count_vaddr.as_mut_ptr_of::<u64>();
let idx = unsafe { core::ptr::read_volatile(count_ptr) };
if idx >= entries as u64 {
return; }
let target_byte_offset = (1 + idx as usize) * core::mem::size_of::<u64>();
let page_idx = target_byte_offset / PAGE_SIZE_4K;
let page_off = target_byte_offset % PAGE_SIZE_4K;
if page_idx < pages.len() {
let entry_vaddr = phys_to_virt(pages[page_idx]);
unsafe {
let entry_ptr = entry_vaddr.as_mut_ptr().add(page_off) as *mut u64;
core::ptr::write_volatile(entry_ptr, pc);
core::sync::atomic::fence(core::sync::atomic::Ordering::Release);
core::ptr::write_volatile(count_ptr, idx + 1);
}
}
});
}