⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

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}