use crate::source::{EntropySource, Platform, Requirement, SourceCategory, SourceInfo};
static CNTFRQ_CACHE_TIMING_INFO: SourceInfo = SourceInfo {
name: "cntfrq_cache_timing",
description: "CNTFRQ_EL0 JIT-read trimodal system-register cache timing",
physics: "JIT-compiled MRS to S3_3_c14_c0_0 (CNTFRQ_EL0) elicits trimodal timing: \
83/125/151 ticks, CV=18.1%. The three modes reflect distinct hardware paths: \
L1 system-register cache hit (83t), L2 fabric register (125t), full \
system-register bus (151t). Path selection depends on pipeline fill state, \
register cache occupancy, and fabric congestion. Trimodal gives ~1.58 \
bits/sample. The JIT-probe forces the unoptimised MRS path; the native \
CNTFRQ_EL0 instruction uses an architectural shortcut with 0-tick latency.",
category: SourceCategory::Microarch,
platform: Platform::MacOS,
requirements: &[Requirement::AppleSilicon],
entropy_rate_estimate: 1.5,
composite: false,
is_fast: false,
};
pub struct CntfrqCacheTimingSource;
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
mod imp {
use super::*;
use crate::sources::helpers::extract_timing_entropy_debiased;
use crate::sources::helpers::mach_time;
use libc::{
MAP_ANONYMOUS, MAP_FAILED, MAP_JIT, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE, mmap,
munmap,
};
use std::sync::atomic::{Ordering, fence};
#[allow(clippy::identity_op)]
const CNTFRQ_MRS_X0: u32 = 0xD5380000u32
| (3u32 << 16) | (14u32 << 12) | (0u32 << 8) | (0u32 << 5); const RET: u32 = 0xD65F03C0u32;
type FnPtr = unsafe extern "C" fn() -> u64;
struct JitPage(*mut libc::c_void);
impl Drop for JitPage {
fn drop(&mut self) {
unsafe {
munmap(self.0, 4096);
}
}
}
unsafe fn build_jit_mrs() -> Option<(FnPtr, JitPage)> {
let page = unsafe {
mmap(
std::ptr::null_mut(),
4096,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_JIT,
-1,
0,
)
};
if page == MAP_FAILED {
return None;
}
unsafe {
libc::pthread_jit_write_protect_np(0);
let code = page as *mut u32;
code.write(CNTFRQ_MRS_X0);
code.add(1).write(RET);
libc::pthread_jit_write_protect_np(1);
core::arch::asm!("dc cvau, {p}", "ic ivau, {p}", p = in(reg) page, options(nostack));
core::arch::asm!("dsb ish", "isb", options(nostack));
}
let fn_ptr: FnPtr = unsafe { std::mem::transmute(page) };
Some((fn_ptr, JitPage(page)))
}
unsafe fn time_cntfrq_jit(fn_ptr: FnPtr) -> u64 {
fence(Ordering::SeqCst);
let t0 = mach_time();
let _v = unsafe { fn_ptr() };
let t1 = mach_time();
fence(Ordering::SeqCst);
t1.wrapping_sub(t0)
}
impl EntropySource for CntfrqCacheTimingSource {
fn info(&self) -> &SourceInfo {
&CNTFRQ_CACHE_TIMING_INFO
}
fn is_available(&self) -> bool {
use std::sync::OnceLock;
static CNTFRQ_AVAILABLE: OnceLock<bool> = OnceLock::new();
*CNTFRQ_AVAILABLE.get_or_init(|| {
unsafe {
if let Some((fn_ptr, _guard)) = build_jit_mrs() {
let t = time_cntfrq_jit(fn_ptr);
t < 100_000 } else {
false
}
}
})
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
unsafe {
let Some((fn_ptr, _page_guard)) = build_jit_mrs() else {
return Vec::new();
};
for _ in 0..64 {
let _ = time_cntfrq_jit(fn_ptr);
}
let raw_count = n_samples * 8 + 256;
let mut timings = Vec::with_capacity(raw_count);
for _ in 0..raw_count {
let t = time_cntfrq_jit(fn_ptr);
if t <= 300 {
timings.push(t);
}
}
extract_timing_entropy_debiased(&timings, n_samples)
}
}
}
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
impl EntropySource for CntfrqCacheTimingSource {
fn info(&self) -> &SourceInfo {
&CNTFRQ_CACHE_TIMING_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 = CntfrqCacheTimingSource;
assert_eq!(src.info().name, "cntfrq_cache_timing");
assert!(matches!(src.info().category, SourceCategory::Microarch));
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() {
let src = CntfrqCacheTimingSource;
let _ = src.is_available(); }
#[test]
#[ignore] fn collects_trimodal_timings() {
let src = CntfrqCacheTimingSource;
if !src.is_available() {
return;
}
let data = src.collect(32);
assert!(!data.is_empty());
let unique: std::collections::HashSet<u8> = data.iter().copied().collect();
assert!(unique.len() >= 2, "expected trimodal variation");
}
}