openentropy_core/sources/microarch/
sev_event_timing.rs1use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
44
45#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
46use crate::sources::helpers::{extract_timing_entropy, mach_time};
47
48static SEV_EVENT_TIMING_INFO: SourceInfo = SourceInfo {
49 name: "sev_event_timing",
50 description: "ARM64 SEV/SEVL broadcast event timing via ICC fabric load",
51 physics: "Times `SEV` (Send Event, all-core broadcast) and `SEVL` (Send Event Local). \
52 SEV traverses the ICC fabric to wake WFE-waiting cores; timing reflects ICC \
53 bus load from all concurrent WFE/SEV operations across all processes. \
54 Paradox: SEVL is 3\u{00d7} slower than SEV (local event register write is less \
55 optimized than ICC broadcast path). Measured: SEV CV=240.4%, mean=6.1 ticks; \
56 SEVL CV=103.5%, mean=20.1 ticks, range=0\u{2013}58 ticks. XOR-mixed to combine \
57 ICC fabric state (SEV) with local core pipeline state (SEVL).",
58 category: SourceCategory::Microarch,
59 platform: Platform::MacOS,
60 requirements: &[],
61 entropy_rate_estimate: 3.0,
62 composite: false,
63 is_fast: false,
64};
65
66pub struct SEVEventTimingSource;
68
69#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
70impl EntropySource for SEVEventTimingSource {
71 fn info(&self) -> &SourceInfo {
72 &SEV_EVENT_TIMING_INFO
73 }
74
75 fn is_available(&self) -> bool {
76 true
77 }
78
79 fn collect(&self, n_samples: usize) -> Vec<u8> {
80 let raw = n_samples * 4 + 32;
82 let mut sev_times = Vec::with_capacity(raw);
83 let mut sevl_times = Vec::with_capacity(raw);
84
85 for _ in 0..16 {
87 unsafe {
88 core::arch::asm!("sev", options(nostack, nomem));
89 core::arch::asm!("sevl", options(nostack, nomem));
90 }
91 }
92
93 for _ in 0..raw {
94 let t0 = mach_time();
95 unsafe {
96 core::arch::asm!("sev", options(nostack, nomem));
97 }
98 let sev_t = mach_time().wrapping_sub(t0);
99
100 let t1 = mach_time();
101 unsafe {
102 core::arch::asm!("sevl", options(nostack, nomem));
103 }
104 let sevl_t = mach_time().wrapping_sub(t1);
105
106 if sev_t < 48_000 && sevl_t < 48_000 {
108 sev_times.push(sev_t);
112 sevl_times.push(sevl_t ^ (sev_t.wrapping_shl(7)));
113 }
114 }
115
116 let min = sev_times.len().min(sevl_times.len());
118 let mut combined = Vec::with_capacity(min * 2);
119 for i in 0..min {
120 combined.push(sev_times[i]);
121 combined.push(sevl_times[i]);
122 }
123
124 extract_timing_entropy(&combined, n_samples)
125 }
126}
127
128#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
129impl EntropySource for SEVEventTimingSource {
130 fn info(&self) -> &SourceInfo {
131 &SEV_EVENT_TIMING_INFO
132 }
133 fn is_available(&self) -> bool {
134 false
135 }
136 fn collect(&self, _n_samples: usize) -> Vec<u8> {
137 Vec::new()
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn info() {
147 let src = SEVEventTimingSource;
148 assert_eq!(src.info().name, "sev_event_timing");
149 assert!(matches!(src.info().category, SourceCategory::Microarch));
150 assert_eq!(src.info().platform, Platform::MacOS);
151 assert!(!src.info().composite);
152 }
153
154 #[test]
155 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
156 fn is_available_on_apple_silicon() {
157 assert!(SEVEventTimingSource.is_available());
158 }
159
160 #[test]
161 #[ignore]
162 fn collects_with_variation() {
163 let data = SEVEventTimingSource.collect(32);
164 assert!(!data.is_empty());
165 }
166}