/* 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; } }