use super::hw_owner::Counter;
use crate::sync::IrqMutex;
struct HwAlloc {
flexible: [u32; super::percpu::MAX_TRACKED_CPUS],
cycle_used: bool,
local_cycle_used: [bool; super::percpu::MAX_TRACKED_CPUS],
}
impl HwAlloc {
const fn new() -> Self {
Self {
flexible: [0; super::percpu::MAX_TRACKED_CPUS],
cycle_used: false,
local_cycle_used: [false; super::percpu::MAX_TRACKED_CPUS],
}
}
fn alloc_cycle(&mut self) -> Option<Counter> {
if self.cycle_used || self.local_cycle_used.iter().any(|used| *used) {
return None;
}
self.cycle_used = true;
Some(Counter::Cycle)
}
fn alloc_flexible(&mut self, cpu: usize, num_counters: usize) -> Option<usize> {
let local = self.flexible.get_mut(cpu)?;
for n in 0..num_counters.min(32) {
if *local & (1 << n) == 0 {
*local |= 1 << n;
return Some(n);
}
}
None
}
fn alloc_system(
&mut self,
cpu: usize,
prefer_cycle: bool,
num_counters: usize,
) -> Option<Counter> {
let local_cycle = self.local_cycle_used.get_mut(cpu)?;
if prefer_cycle && !self.cycle_used && !*local_cycle {
*local_cycle = true;
return Some(Counter::Cycle);
}
self.alloc_flexible(cpu, num_counters)
.map(Counter::Programmable)
}
}
static ALLOC: IrqMutex<HwAlloc> = IrqMutex::new(HwAlloc::new());
pub(super) fn alloc_cycle_counter() -> Option<Counter> {
ALLOC.lock().alloc_cycle()
}
pub(super) fn alloc_system_cycle(cpu: super::target::PerfCpuId) -> Option<Counter> {
ALLOC.lock().alloc_system(cpu.as_usize(), true, 0)
}
pub(super) fn free_counter(counter: Counter) {
assert_eq!(
counter,
Counter::Cycle,
"only native cycles have task-fixed reservations"
);
let mut allocator = ALLOC.lock();
assert!(allocator.cycle_used, "task cycle reservation must be owned");
allocator.cycle_used = false;
}
pub(super) fn alloc_system(
cpu: super::target::PerfCpuId,
event: u16,
prefer_cycle: bool,
num_counters: usize,
) -> crate::StarryResult<Counter> {
let cpu = cpu.as_usize();
if !super::percpu::cpu_info(cpu)
.is_some_and(|info| crate::perf::event_map::event_supported_by(info, event))
{
return Err(crate::StarryError::Unsupported);
}
ALLOC
.lock()
.alloc_system(cpu, prefer_cycle, num_counters)
.ok_or(crate::StarryError::ResourceBusy)
}
pub(super) fn free_system(cpu: super::target::PerfCpuId, counter: Counter) {
match counter {
Counter::Cycle => {
let mut allocator = ALLOC.lock();
let used = &mut allocator.local_cycle_used[cpu.as_usize()];
assert!(*used, "system cycle reservation must be owned");
*used = false;
}
Counter::Programmable(slot) => free_flexible(cpu.as_usize(), slot),
}
}
pub(super) fn alloc_flexible(cpu: usize, num_counters: usize) -> Option<usize> {
ALLOC.lock().alloc_flexible(cpu, num_counters)
}
pub(super) fn free_flexible(cpu: usize, slot: usize) {
let mut allocator = ALLOC.lock();
let used = allocator.flexible.get_mut(cpu).expect("validated perf CPU");
assert!(slot < 32 && *used & (1 << slot) != 0);
*used &= !(1 << slot);
}
#[cfg(all(test, axtest))]
mod tests {
#[axtest::axtest]
fn task_and_system_cycle_reservations_are_mutually_exclusive() {
let mut allocator = super::HwAlloc::new();
assert_eq!(
allocator.alloc_system(0, true, 0),
Some(super::Counter::Cycle)
);
assert!(allocator.alloc_cycle().is_none());
allocator.local_cycle_used[0] = false;
assert_eq!(allocator.alloc_cycle(), Some(super::Counter::Cycle));
assert!(allocator.alloc_system(0, true, 0).is_none());
assert_eq!(allocator.alloc_flexible(0, 1), Some(0));
assert!(allocator.alloc_flexible(0, 1).is_none());
assert_eq!(allocator.alloc_flexible(1, 1), Some(0));
}
}