veridian_kernel/arch/entropy.rs
1//! Architecture-independent hardware entropy abstractions.
2//!
3//! Centralizes hardware entropy/random number operations so that non-arch code
4//! (particularly `crypto/random.rs`) does not need scattered
5//! `#[cfg(target_arch)]` blocks with inline assembly.
6//!
7//! # Functions
8//!
9//! * [`read_timestamp`] -- read the hardware cycle/timer counter for entropy
10//! jitter.
11//! * [`try_hardware_rng`] -- attempt to read from a hardware RNG (RDRAND on
12//! x86_64).
13//! * [`collect_timer_entropy`] -- collect 32 bytes of timer-jitter entropy.
14
15/// Read the hardware timestamp/cycle counter.
16///
17/// Returns a raw counter value suitable for entropy collection via jitter
18/// timing.
19///
20/// * **x86_64**: `RDTSC` (Time Stamp Counter).
21/// * **AArch64**: `CNTVCT_EL0` (Virtual Timer Count).
22/// * **RISC-V**: `rdcycle` CSR.
23#[inline]
24pub fn read_timestamp() -> u64 {
25 #[cfg(target_arch = "x86_64")]
26 {
27 // SAFETY: _rdtsc reads the Time Stamp Counter register. It is always
28 // available on x86_64 and returns the current cycle count as u64.
29 unsafe { core::arch::x86_64::_rdtsc() }
30 }
31
32 #[cfg(target_arch = "aarch64")]
33 {
34 let val: u64;
35 // SAFETY: Reading CNTVCT_EL0 is a read-only operation that accesses
36 // the virtual timer count register. Always safe from any exception level.
37 unsafe {
38 core::arch::asm!("mrs {}, cntvct_el0", out(reg) val);
39 }
40 val
41 }
42
43 #[cfg(target_arch = "riscv64")]
44 {
45 let val: u64;
46 // SAFETY: Reading the cycle CSR is a read-only operation that
47 // accesses a performance counter. Always safe.
48 unsafe {
49 core::arch::asm!("rdcycle {}", out(reg) val);
50 }
51 val
52 }
53
54 #[cfg(not(any(
55 target_arch = "x86_64",
56 target_arch = "aarch64",
57 target_arch = "riscv64"
58 )))]
59 {
60 0u64
61 }
62}
63
64/// Check whether the CPU supports the RDRAND instruction.
65///
66/// Queries CPUID leaf 1 and tests ECX bit 30 (RDRAND feature flag).
67/// Returns `false` if CPUID is unavailable or the bit is not set.
68#[cfg(target_arch = "x86_64")]
69fn cpu_has_rdrand() -> bool {
70 // SAFETY: CPUID with EAX=1 is a read-only, side-effect-free instruction
71 // that returns CPU feature information. It is always available on x86_64
72 // (CPUID support is mandatory in long mode). RBX is saved and restored
73 // because LLVM reserves it as a frame pointer and CPUID clobbers it.
74 let ecx: u32;
75 unsafe {
76 core::arch::asm!(
77 "push rbx",
78 "mov eax, 1",
79 "cpuid",
80 "pop rbx",
81 out("ecx") ecx,
82 out("eax") _,
83 out("edx") _,
84 options(nomem, preserves_flags),
85 );
86 }
87 (ecx & (1 << 30)) != 0
88}
89
90/// Attempt to read 32 bytes from a hardware random number generator.
91///
92/// Returns `true` if the hardware RNG was available and `dest` was filled,
93/// `false` if hardware RNG is unavailable (in which case `dest` is unmodified).
94///
95/// * **x86_64**: Uses `RDRAND` instruction with retry logic. Falls back to
96/// `false` if the CPU does not support RDRAND (checked via CPUID).
97/// * **AArch64/RISC-V**: No dedicated hardware RNG; always returns `false`.
98pub fn try_hardware_rng(dest: &mut [u8; 32]) -> bool {
99 #[cfg(target_arch = "x86_64")]
100 {
101 // Check CPUID before executing RDRAND. Without this guard, executing
102 // RDRAND on a CPU that lacks the feature triggers #UD (Invalid Opcode),
103 // which cascades to a double fault during early boot.
104 if !cpu_has_rdrand() {
105 return false;
106 }
107
108 use core::arch::x86_64::_rdrand64_step;
109
110 // SAFETY: _rdrand64_step is an x86_64 RDRAND intrinsic that writes a
111 // hardware-generated random u64 into `value`. Returns 0 on failure.
112 // We have verified RDRAND support via CPUID above, so the instruction
113 // will not fault.
114 unsafe {
115 for chunk in dest.chunks_exact_mut(8) {
116 let mut value: u64 = 0;
117 let mut attempts = 0;
118 let mut success = false;
119 while attempts < 10 {
120 if _rdrand64_step(&mut value) != 0 {
121 success = true;
122 break;
123 }
124 attempts += 1;
125 }
126 if !success {
127 return false;
128 }
129 chunk.copy_from_slice(&value.to_le_bytes());
130 }
131 }
132 true
133 }
134
135 #[cfg(not(target_arch = "x86_64"))]
136 {
137 let _ = dest;
138 false
139 }
140}
141
142/// Collect 32 bytes of timer-jitter entropy.
143///
144/// Uses [`read_timestamp`] to sample the hardware counter with variable-work
145/// delays, then mixes the jitter into the output buffer using an LCG.
146pub fn collect_timer_entropy(dest: &mut [u8; 32]) {
147 let mut pool = [0u64; 4];
148 let mut sample = 0;
149 while sample < 4 {
150 let t1 = read_timestamp();
151 // Introduce variable delay via computation
152 let mut work: u64 = t1;
153 let mut j = 0u32;
154 while j < 100 + (sample as u32 * 37) {
155 work = work
156 .wrapping_mul(6364136223846793005)
157 .wrapping_add(1442695040888963407);
158 j += 1;
159 }
160 let t2 = read_timestamp();
161 // Mix timing jitter with computation result
162 pool[sample] = t1 ^ t2 ^ work;
163 sample += 1;
164 }
165
166 // Convert pool to bytes
167 let mut i = 0;
168 while i < 32 {
169 let pool_word = pool[i / 8];
170 let byte_idx = i % 8;
171 dest[i] = (pool_word >> (byte_idx * 8)) as u8;
172 i += 1;
173 }
174
175 // Additional mixing pass using LCG
176 let mut state = read_timestamp();
177 i = 0;
178 while i < 32 {
179 state = state
180 .wrapping_mul(6364136223846793005)
181 .wrapping_add(1442695040888963407);
182 dest[i] ^= (state >> 33) as u8;
183 i += 1;
184 }
185}