Skip to main content

openentropy_core/sources/frontier/
pdn_resonance.rs

1//! Power delivery network resonance — cross-core voltage noise entropy.
2//!
3//! The PCB power planes have LC resonances at specific frequencies. When
4//! different chip components draw current, standing waves form in the power
5//! delivery network creating voltage droops that affect operation timing.
6//!
7//! By running a stress workload on background threads while measuring timing
8//! on the current thread, we capture PDN voltage noise from cross-core coupling.
9//!
10
11use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
12use crate::sources::helpers::{extract_timing_entropy, mach_time};
13
14/// Number of iterations per timing measurement.
15const MEASUREMENT_ITERS: usize = 100;
16
17static PDN_RESONANCE_INFO: SourceInfo = SourceInfo {
18    name: "pdn_resonance",
19    description: "Power delivery network resonance from cross-core voltage noise",
20    physics: "Runs stress workloads on background threads (memory thrashing, ALU, FPU) \
21              while measuring timing of a fixed small workload on the current thread. \
22              The timing perturbation captures power delivery network (PDN) voltage noise: \
23              LC resonances in PCB power planes, voltage droop from bursty current draw, \
24              and cross-core power supply coupling. Each measurement thread combination \
25              creates a different current profile exciting different PDN modes.",
26    category: SourceCategory::Thermal,
27    platform: Platform::Any,
28    requirements: &[],
29    entropy_rate_estimate: 500.0,
30    composite: false,
31};
32
33/// Entropy source that harvests PDN voltage noise via cross-core timing perturbation.
34pub struct PDNResonanceSource;
35
36impl EntropySource for PDNResonanceSource {
37    fn info(&self) -> &SourceInfo {
38        &PDN_RESONANCE_INFO
39    }
40
41    fn is_available(&self) -> bool {
42        true
43    }
44
45    fn collect(&self, n_samples: usize) -> Vec<u8> {
46        let raw_count = n_samples * 4 + 64;
47        let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
48
49        // Spawn stress threads to excite PDN resonance.
50        let running = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
51
52        let mut handles = Vec::new();
53
54        // Memory stress thread — bursty current from cache misses.
55        {
56            let running = running.clone();
57            handles.push(std::thread::spawn(move || {
58                let mut buf = vec![0u64; 512 * 1024]; // 4 MB
59                while running.load(std::sync::atomic::Ordering::Relaxed) {
60                    for i in (0..buf.len()).step_by(64) {
61                        buf[i] = buf[i].wrapping_add(1);
62                    }
63                    std::hint::black_box(&buf);
64                }
65            }));
66        }
67
68        // ALU stress thread — different current profile.
69        {
70            let running = running.clone();
71            handles.push(std::thread::spawn(move || {
72                let mut a: u64 = mach_time() | 1;
73                while running.load(std::sync::atomic::Ordering::Relaxed) {
74                    for _ in 0..10000 {
75                        a = a
76                            .wrapping_mul(6364136223846793005)
77                            .wrapping_add(1442695040888963407);
78                    }
79                    std::hint::black_box(a);
80                }
81            }));
82        }
83
84        // Brief warmup for stress threads.
85        std::thread::sleep(std::time::Duration::from_millis(1));
86
87        // Measure timing on this thread while stress threads run.
88        for _ in 0..raw_count {
89            let t0 = mach_time();
90            let mut acc: u64 = 0;
91            for j in 0..MEASUREMENT_ITERS as u64 {
92                acc = acc.wrapping_add(j);
93            }
94            let t1 = mach_time();
95            std::hint::black_box(acc);
96            timings.push(t1.wrapping_sub(t0));
97        }
98
99        // Stop stress threads.
100        running.store(false, std::sync::atomic::Ordering::Relaxed);
101        for h in handles {
102            let _ = h.join();
103        }
104
105        extract_timing_entropy(&timings, n_samples)
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn info() {
115        let src = PDNResonanceSource;
116        assert_eq!(src.name(), "pdn_resonance");
117        assert_eq!(src.info().category, SourceCategory::Thermal);
118        assert!(!src.info().composite);
119    }
120
121    #[test]
122    #[ignore] // Hardware/timing dependent
123    fn collects_bytes() {
124        let src = PDNResonanceSource;
125        assert!(src.is_available());
126        let data = src.collect(64);
127        assert!(!data.is_empty());
128        assert!(data.len() <= 64);
129    }
130}