use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
use crate::sources::helpers::{extract_timing_entropy, mach_time};
use crate::sources::helpers::extract_timing_entropy_variance;
#[derive(Debug, Clone)]
pub struct TLBShootdownConfig {
pub page_count_range: (usize, usize),
pub region_pages: usize,
pub measure_variance: bool,
}
impl Default for TLBShootdownConfig {
fn default() -> Self {
Self {
page_count_range: (8, 128),
region_pages: 256,
measure_variance: true,
}
}
}
#[derive(Default)]
pub struct TLBShootdownSource {
pub config: TLBShootdownConfig,
}
static TLB_SHOOTDOWN_INFO: SourceInfo = SourceInfo {
name: "tlb_shootdown",
description: "TLB invalidation broadcast timing via variable-count mprotect IPI storms",
physics: "Toggles page protection via mprotect() on varying page counts to trigger TLB \
shootdown broadcasts. Each mprotect() sends IPIs to ALL cores to flush stale \
TLB entries. Varying page counts creates different IPI patterns. Different \
memory regions each time prevent TLB prefetch. Variance between consecutive \
shootdowns captures relative timing with higher min-entropy. IPI latency depends \
on: what each core is executing, P-core vs E-core cluster latency, core power \
states, and concurrent IPI traffic.",
category: SourceCategory::Microarch,
platform: Platform::MacOS,
requirements: &[],
entropy_rate_estimate: 2.0,
composite: false,
is_fast: true,
};
impl EntropySource for TLBShootdownSource {
fn info(&self) -> &SourceInfo {
&TLB_SHOOTDOWN_INFO
}
fn is_available(&self) -> bool {
cfg!(target_os = "macos")
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };
let region_pages = self.config.region_pages.max(8);
let region_size = page_size * region_pages;
let (min_pages, max_pages) = self.config.page_count_range;
let min_pages = min_pages.max(1).min(region_pages);
let max_pages = max_pages.max(min_pages).min(region_pages);
let addr = unsafe {
libc::mmap(
std::ptr::null_mut(),
region_size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_ANONYMOUS | libc::MAP_PRIVATE,
-1,
0,
)
};
if addr == libc::MAP_FAILED {
return Vec::new();
}
for p in 0..region_pages {
unsafe {
std::ptr::write_volatile((addr as *mut u8).add(p * page_size), 0xAA);
}
}
let raw_count = n_samples * 4 + 64;
let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
let mut lcg: u64 = mach_time() | 1;
for _ in 0..raw_count {
lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
let num_pages = if min_pages == max_pages {
min_pages
} else {
min_pages + ((lcg >> 32) as usize % (max_pages - min_pages + 1))
};
let prot_size = num_pages * page_size;
let max_offset_pages = region_pages.saturating_sub(num_pages);
lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
let offset_pages = if max_offset_pages > 0 {
(lcg >> 48) as usize % max_offset_pages
} else {
0
};
let offset = offset_pages * page_size;
let t0 = mach_time();
unsafe {
let target = (addr as *mut u8).add(offset) as *mut libc::c_void;
libc::mprotect(target, prot_size, libc::PROT_READ);
libc::mprotect(target, prot_size, libc::PROT_READ | libc::PROT_WRITE);
}
let t1 = mach_time();
timings.push(t1.wrapping_sub(t0));
}
unsafe {
libc::munmap(addr, region_size);
}
if self.config.measure_variance {
extract_timing_entropy_variance(&timings, n_samples)
} else {
extract_timing_entropy(&timings, n_samples)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn info() {
let src = TLBShootdownSource::default();
assert_eq!(src.name(), "tlb_shootdown");
assert_eq!(src.info().category, SourceCategory::Microarch);
assert!(!src.info().composite);
}
#[test]
fn default_config() {
let config = TLBShootdownConfig::default();
assert_eq!(config.page_count_range, (8, 128));
assert_eq!(config.region_pages, 256);
assert!(config.measure_variance);
}
#[test]
fn custom_config() {
let src = TLBShootdownSource {
config: TLBShootdownConfig {
page_count_range: (4, 64),
region_pages: 128,
measure_variance: false,
},
};
assert_eq!(src.config.page_count_range, (4, 64));
}
#[test]
#[ignore] fn collects_bytes() {
let src = TLBShootdownSource::default();
if src.is_available() {
let data = src.collect(64);
assert!(!data.is_empty());
assert!(data.len() <= 64);
}
}
#[test]
#[ignore] fn absolute_mode() {
let src = TLBShootdownSource {
config: TLBShootdownConfig {
measure_variance: false,
..TLBShootdownConfig::default()
},
};
if src.is_available() {
assert!(!src.collect(64).is_empty());
}
}
}