Phase 8 A: virtio-rng entropy source for real Ed25519 signing
The kernel's ed25519_verify() is deliberately verify-only -- no signing, no keygen, no entropy source. That conflicts with the on-device MINT word vision (Zuse signing new user certs live at runtime), so this reopens that constraint on request rather than reshaping MINT around verify-only. vm_uuid.h already found the real gap: amd64 has RDRAND, riscv64 has Zkr, but QEMU's aarch64 CPU models have neither -- confirmed against QEMU 10.2.1. A deterministic PRNG (fine for VM UUIDs) is not safe for key generation, so this adds a virtio-rng device instead of a per-arch split: real host entropy, identical guest-side protocol on all three arches. New src/starkernel/virtio/virtio_rng.c + include/starkernel/virtio_rng.h, transport plumbing mirroring the existing virtio_blk.c driver exactly. Wired into kernel_main.c boot, -device virtio-rng-pci added to all three QEMU targets. Verified live (temp probe, written/run/captured/reverted): 16 real bytes pulled through the full request/notify/poll round trip on all three arches, three different values confirming real entropy. Final boot against the reverted, permanent code: clean compile, clean boot to ok> on amd64/aarch64/riscv64, Stadium conservation intact, no panics or guest errors. Ed25519 keygen/signing itself (Phase B) and the MINT word design (Phase C) remain open, documented in FABRIC-3.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6f5605d479
commit
309e792f07
@@ -64,6 +64,7 @@ EFI_RUNTIME_SERVICES *g_sk_runtime_services = NULL;
|
||||
#include "starkernel/repl.h"
|
||||
#include "starkernel/pci.h"
|
||||
#include "starkernel/virtio_blk.h"
|
||||
#include "starkernel/virtio_rng.h"
|
||||
#include "starkernel/virtio_input.h"
|
||||
#include "starkernel/xhci_driver.h"
|
||||
#include "block_subsystem.h"
|
||||
@@ -610,6 +611,23 @@ static void kernel_main_deep(BootInfo *boot_info) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Phase 8: virtio-rng entropy source. Real per-arch RNG doesn't cover
|
||||
* all three architectures (amd64 RDRAND, riscv64 Zkr, but aarch64 has
|
||||
* neither in QEMU's CPU models -- see vm_uuid.h's identical finding),
|
||||
* so signing/keygen entropy comes from this paravirtualized device
|
||||
* instead. Unconditional call site, same graceful-noop precedent as
|
||||
* virtio_blk_find_artemis() above -- boot proceeds either way, the
|
||||
* device is only required once something actually calls
|
||||
* virtio_rng_get_bytes(). */
|
||||
{
|
||||
int rrc = virtio_rng_init();
|
||||
if (rrc == 0) {
|
||||
console_println("virtio-rng: ready");
|
||||
} else {
|
||||
console_println("virtio-rng: not available (continuing without)");
|
||||
}
|
||||
}
|
||||
|
||||
/* item 4.3.5c: virtio-keyboard-pci, riscv64 only today. Unconditional
|
||||
* call site, same as virtio_blk_find_artemis() above -- the function
|
||||
* itself no-ops with a console message on architectures/boards where
|
||||
|
||||
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
* virtio_rng.c — Virtio 1.0 entropy source driver for StarKernel
|
||||
*
|
||||
* Modern virtio 1.0 interface only (device ID 0x1044).
|
||||
* Falls back to checking 0x1005 (legacy/transitional).
|
||||
*
|
||||
* Split virtqueue, queue depth = 1 (a single device-writable buffer is
|
||||
* the entire request shape for this device — no header, no status byte,
|
||||
* no device-specific config space at all).
|
||||
*
|
||||
* The device may fill fewer bytes than the buffer offers per request
|
||||
* (the used-ring element's len says how many); virtio_rng_get_bytes()
|
||||
* loops internally until the caller's full byte count is satisfied.
|
||||
*
|
||||
* Memory model: all allocations via kmalloc(); identity-mapped so
|
||||
* virtual address == physical address for virtqueue ring pointers.
|
||||
* Transport plumbing (capability walk, common-cfg negotiation, split
|
||||
* virtqueue layout) mirrors virtio_blk.c exactly — same device family,
|
||||
* same board, same quirks (see that file's comments for why the config
|
||||
* structs are unpacked and why notify offset math looks the way it does).
|
||||
*/
|
||||
|
||||
#ifndef __STARKERNEL__
|
||||
#error "virtio_rng.c is kernel-only"
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "starkernel/pci.h"
|
||||
#include "starkernel/virtio_rng.h"
|
||||
#include "starkernel/kmalloc.h"
|
||||
#include "console.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Virtio 1.0 PCI capability structures (identical to virtio_blk.c)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define VIRTIO_PCI_CAP_VENDOR_ID 0x09u
|
||||
|
||||
#define VIRTIO_PCI_CAP_COMMON_CFG 1u
|
||||
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2u
|
||||
|
||||
#define VCAP_OFF_CAP_VNDR 0u
|
||||
#define VCAP_OFF_CAP_NEXT 1u
|
||||
#define VCAP_OFF_CFG_TYPE 3u
|
||||
#define VCAP_OFF_BAR 4u
|
||||
#define VCAP_OFF_OFFSET 8u
|
||||
#define VCAP_OFF_LENGTH 12u
|
||||
#define VCAP_OFF_NOTIFY_MULT 16u
|
||||
|
||||
typedef struct {
|
||||
volatile uint32_t device_feature_select;
|
||||
volatile uint32_t device_feature;
|
||||
volatile uint32_t driver_feature_select;
|
||||
volatile uint32_t driver_feature;
|
||||
volatile uint16_t config_msix_vector;
|
||||
volatile uint16_t num_queues;
|
||||
volatile uint8_t device_status;
|
||||
volatile uint8_t config_generation;
|
||||
volatile uint16_t queue_select;
|
||||
volatile uint16_t queue_size;
|
||||
volatile uint16_t queue_msix_vector;
|
||||
volatile uint16_t queue_enable;
|
||||
volatile uint16_t queue_notify_off;
|
||||
volatile uint64_t queue_desc;
|
||||
volatile uint64_t queue_driver;
|
||||
volatile uint64_t queue_device;
|
||||
volatile uint16_t queue_notify_data;
|
||||
volatile uint16_t queue_reset;
|
||||
} VirtioCommonCfg;
|
||||
|
||||
#define VIRTIO_STATUS_ACKNOWLEDGE 0x01u
|
||||
#define VIRTIO_STATUS_DRIVER 0x02u
|
||||
#define VIRTIO_STATUS_DRIVER_OK 0x04u
|
||||
#define VIRTIO_STATUS_FEATURES_OK 0x08u
|
||||
#define VIRTIO_STATUS_FAILED 0x80u
|
||||
|
||||
#define VIRTIO_F_VERSION_1 (1ULL << 32)
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Split virtqueue structures (queue depth 1 — one descriptor, no chaining)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define VQUEUE_SIZE 1u
|
||||
|
||||
#define VRING_DESC_F_WRITE 2u
|
||||
|
||||
typedef struct {
|
||||
uint64_t addr;
|
||||
uint32_t len;
|
||||
uint16_t flags;
|
||||
uint16_t next;
|
||||
} VirtqDesc;
|
||||
|
||||
typedef struct {
|
||||
uint16_t flags;
|
||||
uint16_t idx;
|
||||
uint16_t ring[VQUEUE_SIZE];
|
||||
uint16_t used_event;
|
||||
} VirtqAvail;
|
||||
|
||||
typedef struct {
|
||||
uint32_t id;
|
||||
uint32_t len;
|
||||
} VirtqUsedElem;
|
||||
|
||||
typedef struct {
|
||||
uint16_t flags;
|
||||
uint16_t idx;
|
||||
VirtqUsedElem ring[VQUEUE_SIZE];
|
||||
uint16_t avail_event;
|
||||
} VirtqUsed;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Driver state
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define VRNG_BUF_SIZE 64u /* bytes requested from the device per round */
|
||||
|
||||
typedef struct {
|
||||
VirtioCommonCfg *common;
|
||||
volatile uint16_t *notify;
|
||||
uint32_t notify_off_mult;
|
||||
uint16_t queue_notify_off;
|
||||
|
||||
VirtqDesc *desc;
|
||||
VirtqAvail *avail;
|
||||
VirtqUsed *used;
|
||||
|
||||
uint16_t avail_idx;
|
||||
uint16_t last_used_idx;
|
||||
|
||||
uint8_t *data_buf; /* VRNG_BUF_SIZE bytes, DMA-accessible */
|
||||
} VirtRngState;
|
||||
|
||||
static VirtRngState g_vrng;
|
||||
static int g_vrng_ready = 0;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Capability walker (identical shape to virtio_blk.c's walk_virtio_caps)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static void *walk_virtio_caps(const PciDevice *d, uint8_t cap_type,
|
||||
uint32_t *extra_out) {
|
||||
uint8_t cap_ptr = pci_read8(d, (uint16_t)PCI_CFG_CAP_PTR) & 0xFCu;
|
||||
if (!cap_ptr) return NULL;
|
||||
|
||||
int limit = 48;
|
||||
while (cap_ptr && limit--) {
|
||||
uint8_t vndr = pci_read8(d, cap_ptr + (uint16_t)VCAP_OFF_CAP_VNDR);
|
||||
uint8_t next = pci_read8(d, cap_ptr + (uint16_t)VCAP_OFF_CAP_NEXT);
|
||||
uint8_t ctype = pci_read8(d, cap_ptr + (uint16_t)VCAP_OFF_CFG_TYPE);
|
||||
|
||||
if (vndr == (uint8_t)VIRTIO_PCI_CAP_VENDOR_ID && ctype == cap_type) {
|
||||
uint8_t bar = pci_read8 (d, cap_ptr + (uint16_t)VCAP_OFF_BAR);
|
||||
uint32_t offset = pci_read32(d, cap_ptr + (uint16_t)VCAP_OFF_OFFSET);
|
||||
uint32_t length = pci_read32(d, cap_ptr + (uint16_t)VCAP_OFF_LENGTH);
|
||||
|
||||
if (bar > 5u) { cap_ptr = next & 0xFCu; continue; }
|
||||
|
||||
uint64_t bar_base = pci_bar(d, (int)bar);
|
||||
if (!bar_base) { cap_ptr = next & 0xFCu; continue; }
|
||||
|
||||
if (pci_map_bar(bar_base, (uint64_t)length + offset) != 0) {
|
||||
cap_ptr = next & 0xFCu; continue;
|
||||
}
|
||||
|
||||
if (extra_out && cap_type == VIRTIO_PCI_CAP_NOTIFY_CFG) {
|
||||
*extra_out = pci_read32(d, cap_ptr + (uint16_t)VCAP_OFF_NOTIFY_MULT);
|
||||
}
|
||||
|
||||
return (void *)(uintptr_t)(bar_base + offset);
|
||||
}
|
||||
|
||||
cap_ptr = next & 0xFCu;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void wmb(void) { __asm__ volatile("" : : : "memory"); }
|
||||
static inline void rmb(void) { __asm__ volatile("" : : : "memory"); }
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* One round: ask the device to fill data_buf, return bytes actually
|
||||
* written (may be less than VRNG_BUF_SIZE).
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static int vrng_request(uint32_t *bytes_out) {
|
||||
VirtRngState *s = &g_vrng;
|
||||
VirtqDesc *d = s->desc;
|
||||
|
||||
d[0].addr = (uint64_t)(uintptr_t)s->data_buf;
|
||||
d[0].len = (uint32_t)VRNG_BUF_SIZE;
|
||||
d[0].flags = (uint16_t)VRING_DESC_F_WRITE;
|
||||
d[0].next = 0;
|
||||
|
||||
uint16_t avail_idx = s->avail_idx & (uint16_t)(VQUEUE_SIZE - 1u);
|
||||
s->avail->ring[avail_idx] = 0;
|
||||
wmb();
|
||||
s->avail->idx = (uint16_t)(s->avail->idx + 1u);
|
||||
s->avail_idx = s->avail->idx;
|
||||
wmb();
|
||||
|
||||
uint16_t notify_idx = (uint16_t)(s->queue_notify_off *
|
||||
(s->notify_off_mult & 0xFFFFu));
|
||||
volatile uint16_t *doorbell = s->notify + notify_idx;
|
||||
*doorbell = 0;
|
||||
wmb();
|
||||
|
||||
uint32_t spin = 0x2000000u;
|
||||
while (s->used->idx == s->last_used_idx) {
|
||||
rmb();
|
||||
if (!--spin) return -2;
|
||||
}
|
||||
|
||||
uint32_t used_slot = (uint32_t)s->last_used_idx & (VQUEUE_SIZE - 1u);
|
||||
*bytes_out = s->used->ring[used_slot].len;
|
||||
s->last_used_idx = s->used->idx;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Device initialisation
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static int vrng_init_device(const PciDevice *pci) {
|
||||
VirtRngState *s = &g_vrng;
|
||||
|
||||
pci_enable(pci);
|
||||
|
||||
uint32_t notify_mult = 0;
|
||||
VirtioCommonCfg *common = (VirtioCommonCfg *)
|
||||
walk_virtio_caps(pci, VIRTIO_PCI_CAP_COMMON_CFG, NULL);
|
||||
volatile uint16_t *notify = (volatile uint16_t *)
|
||||
walk_virtio_caps(pci, VIRTIO_PCI_CAP_NOTIFY_CFG, ¬ify_mult);
|
||||
|
||||
if (!common || !notify) {
|
||||
console_println("virtio-rng: cap walk failed");
|
||||
return -2;
|
||||
}
|
||||
|
||||
s->common = common;
|
||||
s->notify = notify;
|
||||
s->notify_off_mult = notify_mult;
|
||||
|
||||
common->device_status = 0;
|
||||
wmb();
|
||||
common->device_status = (uint8_t)VIRTIO_STATUS_ACKNOWLEDGE;
|
||||
wmb();
|
||||
common->device_status = (uint8_t)(VIRTIO_STATUS_ACKNOWLEDGE | VIRTIO_STATUS_DRIVER);
|
||||
wmb();
|
||||
|
||||
common->driver_feature_select = 1;
|
||||
wmb();
|
||||
common->driver_feature = (uint32_t)(VIRTIO_F_VERSION_1 >> 32);
|
||||
common->driver_feature_select = 0;
|
||||
wmb();
|
||||
common->driver_feature = 0;
|
||||
wmb();
|
||||
|
||||
common->device_status = (uint8_t)(VIRTIO_STATUS_ACKNOWLEDGE |
|
||||
VIRTIO_STATUS_DRIVER |
|
||||
VIRTIO_STATUS_FEATURES_OK);
|
||||
wmb();
|
||||
rmb();
|
||||
if (!(common->device_status & (uint8_t)VIRTIO_STATUS_FEATURES_OK)) {
|
||||
console_println("virtio-rng: FEATURES_OK rejected");
|
||||
common->device_status = (uint8_t)VIRTIO_STATUS_FAILED;
|
||||
return -2;
|
||||
}
|
||||
|
||||
common->queue_select = 0;
|
||||
wmb();
|
||||
uint16_t max_size = common->queue_size;
|
||||
if (max_size == 0) {
|
||||
console_println("virtio-rng: bad queue size");
|
||||
return -2;
|
||||
}
|
||||
uint16_t qsize = (uint16_t)VQUEUE_SIZE;
|
||||
common->queue_size = qsize;
|
||||
s->queue_notify_off = common->queue_notify_off;
|
||||
wmb();
|
||||
|
||||
common->config_msix_vector = 0xFFFFu;
|
||||
common->queue_msix_vector = 0xFFFFu;
|
||||
wmb();
|
||||
|
||||
size_t desc_bytes = (size_t)qsize * sizeof(VirtqDesc);
|
||||
size_t avail_bytes = sizeof(uint16_t) * 2u +
|
||||
(size_t)qsize * sizeof(uint16_t) +
|
||||
sizeof(uint16_t);
|
||||
size_t used_bytes = sizeof(uint16_t) * 2u +
|
||||
(size_t)qsize * sizeof(VirtqUsedElem) +
|
||||
sizeof(uint16_t);
|
||||
|
||||
s->desc = (VirtqDesc *)kmalloc_aligned(desc_bytes, 64);
|
||||
s->avail = (VirtqAvail *)kmalloc_aligned(avail_bytes, 2);
|
||||
s->used = (VirtqUsed *)kmalloc_aligned(used_bytes, 4);
|
||||
if (!s->desc || !s->avail || !s->used) {
|
||||
console_println("virtio-rng: queue alloc failed");
|
||||
return -2;
|
||||
}
|
||||
memset(s->desc, 0, desc_bytes);
|
||||
memset(s->avail, 0, avail_bytes);
|
||||
memset(s->used, 0, used_bytes);
|
||||
|
||||
s->data_buf = (uint8_t *)kmalloc_aligned(VRNG_BUF_SIZE, 16);
|
||||
if (!s->data_buf) {
|
||||
console_println("virtio-rng: buf alloc failed");
|
||||
return -2;
|
||||
}
|
||||
|
||||
s->avail_idx = 0;
|
||||
s->last_used_idx = 0;
|
||||
|
||||
common->queue_desc = (uint64_t)(uintptr_t)s->desc;
|
||||
common->queue_driver = (uint64_t)(uintptr_t)s->avail;
|
||||
common->queue_device = (uint64_t)(uintptr_t)s->used;
|
||||
wmb();
|
||||
|
||||
common->queue_enable = 1;
|
||||
wmb();
|
||||
|
||||
common->device_status = (uint8_t)(VIRTIO_STATUS_ACKNOWLEDGE |
|
||||
VIRTIO_STATUS_DRIVER |
|
||||
VIRTIO_STATUS_FEATURES_OK |
|
||||
VIRTIO_STATUS_DRIVER_OK);
|
||||
wmb();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Public entry points
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
int virtio_rng_init(void) {
|
||||
PciDevice pci;
|
||||
int found = pci_find_first(VIRTIO_RNG_PCI_VENDOR_ID, VIRTIO_RNG_DEVICE_MODERN, &pci);
|
||||
if (found != 0)
|
||||
found = pci_find_first(VIRTIO_RNG_PCI_VENDOR_ID, VIRTIO_RNG_DEVICE_LEGACY, &pci);
|
||||
if (found != 0) {
|
||||
console_println("virtio-rng: no device on PCI bus 0");
|
||||
return -1;
|
||||
}
|
||||
|
||||
console_println("virtio-rng: found device");
|
||||
|
||||
int rc = vrng_init_device(&pci);
|
||||
if (rc != 0) return rc;
|
||||
|
||||
g_vrng_ready = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int virtio_rng_ready(void) {
|
||||
return g_vrng_ready;
|
||||
}
|
||||
|
||||
int virtio_rng_get_bytes(uint8_t *buf, size_t n) {
|
||||
if (!g_vrng_ready) return -1;
|
||||
if (!buf) return -2;
|
||||
|
||||
size_t filled = 0;
|
||||
uint32_t attempts_left = 64u; /* generous bound: real entropy always yields > 0 bytes */
|
||||
|
||||
while (filled < n) {
|
||||
uint32_t got = 0;
|
||||
int rc = vrng_request(&got);
|
||||
if (rc != 0) return -2;
|
||||
|
||||
if (got == 0) {
|
||||
if (!--attempts_left) return -2;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t take = (size_t)got;
|
||||
if (take > n - filled) take = n - filled;
|
||||
memcpy(buf + filled, g_vrng.data_buf, take);
|
||||
filled += take;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user