use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
use crate::sources::helpers::xor_fold_u64;
static PREEMPTION_BOUNDARY_INFO: SourceInfo = SourceInfo {
name: "preemption_boundary",
description: "Kernel scheduler preemption timing via consecutive CNTVCT_EL0 reads",
physics: "Reads the ARM64 virtual counter in a tight loop. Consecutive reads normally \
return the same tick (84% of pairs at 24MHz). When the kernel's scheduler \
interrupt fires between two reads, the counter jumps forward by an irregular \
amount (measured max: 4,625 ticks = 193\u{00b5}s). Jump magnitude encodes: which \
IRQ fired (different handlers take different time), runqueue depth at context \
switch, kernel memory allocator lock contention, and network/disk interrupt \
latency from remote hosts. Reads kernel scheduler state from EL0 with \
zero syscall overhead via a single MRS instruction.",
category: SourceCategory::Scheduling,
platform: Platform::MacOS,
requirements: &[],
entropy_rate_estimate: 2.0,
composite: false,
is_fast: false,
};
pub struct PreemptionBoundarySource;
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
impl EntropySource for PreemptionBoundarySource {
fn info(&self) -> &SourceInfo {
&PREEMPTION_BOUNDARY_INFO
}
fn is_available(&self) -> bool {
true
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
let loop_count = (n_samples * 8).max(16_384);
let mut preemption_times: Vec<u64> = Vec::with_capacity(loop_count / 6);
let mut prev: u64;
unsafe {
core::arch::asm!(
"mrs {v}, cntvct_el0",
v = out(reg) prev,
options(nostack, nomem),
);
}
for _ in 0..loop_count {
let cur: u64;
unsafe {
core::arch::asm!(
"mrs {v}, cntvct_el0",
v = out(reg) cur,
options(nostack, nomem),
);
}
let delta = cur.wrapping_sub(prev);
if delta > 0 && delta < 10_000_000 {
preemption_times.push(delta);
}
prev = cur;
}
if preemption_times.is_empty() {
return Vec::new();
}
let mut out = Vec::with_capacity(n_samples);
for pair in preemption_times.windows(2) {
out.push(xor_fold_u64(pair[0] ^ pair[1]));
if out.len() >= n_samples {
break;
}
}
if out.len() < n_samples {
for &t in &preemption_times {
out.push(xor_fold_u64(t));
if out.len() >= n_samples {
break;
}
}
}
out.truncate(n_samples);
out
}
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
impl EntropySource for PreemptionBoundarySource {
fn info(&self) -> &SourceInfo {
&PREEMPTION_BOUNDARY_INFO
}
fn is_available(&self) -> bool {
false
}
fn collect(&self, _n_samples: usize) -> Vec<u8> {
Vec::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn info() {
let src = PreemptionBoundarySource;
assert_eq!(src.info().name, "preemption_boundary");
assert!(matches!(src.info().category, SourceCategory::Scheduling));
assert_eq!(src.info().platform, Platform::MacOS);
assert!(!src.info().composite);
}
#[test]
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
fn is_available_on_apple_silicon() {
assert!(PreemptionBoundarySource.is_available());
}
#[test]
#[ignore]
fn collects_preemption_events() {
let data = PreemptionBoundarySource.collect(32);
assert!(!data.is_empty());
}
}