Files
LithosAnanake/src/starkernel/rng/rng.c
T
Robert Allan James 28de700645 v2.0.1: G.4 amd64 RDRAND backend behind rng_get_bytes() (SER5 entropy)
First real per-arch RNG backend, added to the v2.0.0 unified entry point in
src/starkernel/rng/rng.c, #if-guarded to amd64: CPUID.01H:ECX[30] RDRAND
detection + inline-asm rdrand draws feeding rdrand_fill() (whole-byte
emission from the low end; a partial final draw is discarded -- throwing
away entropy is always safe).

Probe order honors the release policy: virtio-rng is tried first, so the
QEMU path stays on virtio-rng unchanged; RDRAND is the fallback only real
hardware (which has no virtio-rng device) reaches. QEMU-verified both ways
on amd64: with virtio-rng present -> "rng: backend = virtio-rng" (unchanged);
with virtio-rng absent and RDRAND exposed (-cpu max) -> "rng: backend =
rdrand" + "entropy: ready" + Zuse attach confirmed. rdrand_fill()'s exact
logic host-proven: fills 32-byte/16-byte buffers and yields differing draws
run-to-run (non-deterministic). aarch64/riscv64 builds unaffected (guarded
off). riscv64 Zkr and aarch64 peripheral-RNG backends remain parked for their
real boards.

FABRIC-3.md G.4 amd64 slice marked BUILT + QEMU-verified.
2026-08-29 10:18:07 -04:00

131 lines
4.4 KiB
C

/*
StarKernel — Unified entropy layer (rng_get_bytes)
Single entropy entry point for the kernel; see include/starkernel/rng.h for
the contract and the probe-and-refuse-loudly discipline this implements.
v2.0.0: virtio-rng is the sole active backend (QEMU-only, uniform across all
three arches). The backend-selection switch in rng_get_bytes() is the exact
seam where the v2.5.0 real per-arch drivers plug in without touching the call
path.
v2.0.1 (G.4, metadata): the real per-arch backends begin to land here, each
guarded by its ISA. amd64 RDRAND is first (SER5). Backend probe order matters
and honors the release policy: virtio-rng is tried first so the QEMU path
stays on virtio-rng unchanged; the real CPU primitive (RDRAND on amd64) is
the fallback that only real hardware reaches, because real boards have no
virtio-rng device. Each backend prints its identity at rng_init() so a boot
log states exactly which source serves entropy.
*/
#ifndef __STARKERNEL__
#error "rng.c is kernel-only"
#endif
#include "starkernel/rng.h"
#include "starkernel/virtio_rng.h"
#include "starkernel/console.h"
/* Number of entropy backends known to this build. Each maps to one case in
* rng_init() and rng_get_bytes(). v2.0.0: virtio-rng. v2.0.1 adds the amd64
* RDRAND backend (real-hardware/SER5 path). */
enum {
RNG_BACKEND_NONE = 0,
RNG_BACKEND_VIRTIO, /* virtio-rng (QEMU, all three arches) */
#if defined(__x86_64__) || defined(__amd64__)
RNG_BACKEND_RDRAND, /* amd64 RDRAND (real hardware, e.g. Beelink SER5) */
#endif
};
static int g_rng_backend = RNG_BACKEND_NONE;
/* --- amd64 RDRAND backend (G.4) ---------------------------------------- */
#if defined(__x86_64__) || defined(__amd64__)
/* CPUID.01H:ECX bit 30 = RDRAND supported. */
static inline int rdrand_available(void) {
uint32_t eax, ebx, ecx, edx;
__asm__ volatile ("cpuid"
: "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
: "a"(1u), "c"(0u));
(void)eax; (void)ebx; (void)edx;
return (int)((ecx >> 30) & 1u);
}
/* Execute RDRAND; returns 1 on a valid draw (CF=1), 0 otherwise. */
static inline int rdrand64(uint64_t *out) {
uint8_t ok;
__asm__ volatile ("rdrand %0; setc %1"
: "=r"(*out), "=qm"(ok)
:
: "cc");
return (int)ok;
}
static int rdrand_fill(uint8_t *buf, size_t n) {
size_t filled = 0;
uint32_t attempts = 0;
const uint32_t MAX_ATTEMPTS = 128u; /* generous: hardware RDRAND is fast */
while (filled < n) {
uint64_t w;
if (!rdrand64(&w)) {
if (++attempts > MAX_ATTEMPTS) return RNG_ERR_NO_BACKEND;
continue;
}
/* Emit whole bytes from the low end. The last (partial) draw is
* simply discarded — throwing away entropy is always safe. */
for (size_t i = 0; i < sizeof(w) && filled < n; i++)
buf[filled++] = (uint8_t)(w >> (8 * i));
}
return 0;
}
#endif /* amd64 */
int rng_init(void) {
/* Probe backends in priority order; first success wins. virtio-rng is
* tried first so the QEMU path stays on virtio-rng unchanged; the real
* per-arch primitive is the fallback that only real hardware reaches. */
if (virtio_rng_init() == 0) {
g_rng_backend = RNG_BACKEND_VIRTIO;
console_println("rng: backend = virtio-rng");
return 0;
}
#if defined(__x86_64__) || defined(__amd64__)
if (rdrand_available()) {
g_rng_backend = RNG_BACKEND_RDRAND;
console_println("rng: backend = rdrand");
return 0;
}
#endif
/* Refuse loudly: never fall through to a deterministic seed. */
g_rng_backend = RNG_BACKEND_NONE;
console_println(
"rng: WARNING — no entropy backend available; rng_get_bytes() "
"will refuse (no deterministic seed fallback)");
return -1;
}
int rng_ready(void) {
return g_rng_backend != RNG_BACKEND_NONE;
}
int rng_get_bytes(uint8_t *buf, size_t n) {
if (!buf || n == 0) return RNG_ERR_NO_BACKEND;
switch (g_rng_backend) {
case RNG_BACKEND_VIRTIO:
return virtio_rng_get_bytes(buf, n);
#if defined(__x86_64__) || defined(__amd64__)
case RNG_BACKEND_RDRAND:
return rdrand_fill(buf, n);
#endif
default:
/* No backend: refuse loudly, never return a deterministic
* throwaway. */
return RNG_ERR_NO_BACKEND;
}
}