Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure
Source tree reorganization: - Move StarForth v3 engine to v3/ (src/, include/, Makefile) - Move kernel to kernel/ (src/, include/, linker/, Makefile) - Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md) - Move FABRIC-0..4.md to docs/fabric/ - Move ONTOLOGY.md and ROADMAP.md to docs/ Board infrastructure: - Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/ - Each board has board.mk (ISA, CPU flags, boot recipe) and README.md - Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET - make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board - ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet) - scripts/mkdiskimage.sh builds disk images for all boards Docs pipeline: - docs/book/ with LaTeX master (main.tex) and Makefile - pandoc converts Markdown to LaTeX at build time - Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks) - make docs builds single PDF (754 pages, 0 missing characters) - make docs TARGET=<board> adds board appendix - build/docs/<book|board>/meta.tex stamps git commit into PDF Bug fixes: - 42 include paths that only worked by accident now use correct relative paths - clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build) - Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved - Doxyfile, .clang-tidy, README.md, Kconfig paths updated Verified: - Hosted v3 build passes 1012 tests, 0 failures - SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE) - Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes - make clean TARGET=<board> removes only that board and its ISA objects - make all builds all boards, hosted v3, and docs in one run Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# src/starkernel/virtio/
|
||||
|
||||
- `virtio_blk.c` — VirtIO block device driver, discovered via
|
||||
`src/starkernel/pci/pci.c`; backs the kernel block subsystem when
|
||||
running under QEMU/virtualized storage rather than the RAM-only block
|
||||
backend.
|
||||
@@ -0,0 +1,569 @@
|
||||
/*
|
||||
* virtio_blk.c — Virtio 1.0 block device driver for StarKernel
|
||||
*
|
||||
* Modern virtio 1.0 interface only (device ID 0x1042).
|
||||
* Falls back to checking 0x1001 (transitional).
|
||||
*
|
||||
* Split virtqueue, queue depth = 4 (minimum for one in-flight I/O chain).
|
||||
* All I/O is synchronous: submit → poll used ring → return status.
|
||||
*
|
||||
* Memory model: all allocations via kmalloc(); identity-mapped so
|
||||
* virtual address == physical address for virtqueue ring pointers.
|
||||
*/
|
||||
|
||||
#ifndef __STARKERNEL__
|
||||
#error "virtio_blk.c is kernel-only"
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "starkernel/pci.h"
|
||||
#include "starkernel/virtio_blk.h"
|
||||
#include "starkernel/kmalloc.h"
|
||||
#include "console.h"
|
||||
#include "blkio.h"
|
||||
#include "log.h"
|
||||
#include "starkernel/arch.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Virtio 1.0 PCI capability structures
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define VIRTIO_PCI_CAP_VENDOR_ID 0x09u
|
||||
|
||||
#define VIRTIO_PCI_CAP_COMMON_CFG 1u
|
||||
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2u
|
||||
#define VIRTIO_PCI_CAP_ISR_CFG 3u
|
||||
#define VIRTIO_PCI_CAP_DEVICE_CFG 4u
|
||||
|
||||
/* Offsets within a virtio PCI capability (relative to cap ptr in config) */
|
||||
#define VCAP_OFF_CAP_VNDR 0u
|
||||
#define VCAP_OFF_CAP_NEXT 1u
|
||||
#define VCAP_OFF_CAP_LEN 2u
|
||||
#define VCAP_OFF_CFG_TYPE 3u
|
||||
#define VCAP_OFF_BAR 4u
|
||||
#define VCAP_OFF_OFFSET 8u
|
||||
#define VCAP_OFF_LENGTH 12u
|
||||
/* For notify cap only: 4-byte notify_off_multiplier after the 16-byte base */
|
||||
#define VCAP_OFF_NOTIFY_MULT 16u
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Virtio common config (MMIO layout, virtio 1.0 §4.1.4.3)
|
||||
*
|
||||
* NOT packed: every field is naturally aligned per the spec layout, so the
|
||||
* offsets are identical without the attribute — but packed forces GCC on
|
||||
* riscv64 (strict-alignment target) to emit byte-wise loads/stores, and
|
||||
* QEMU's common-cfg MMIO handler returns 0 for byte reads of the 16-bit
|
||||
* registers (queue_size = 256 reads back as 0). Field access must be
|
||||
* exact-width.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
/* device feature selection */
|
||||
volatile uint32_t device_feature_select;
|
||||
volatile uint32_t device_feature;
|
||||
/* driver feature selection */
|
||||
volatile uint32_t driver_feature_select;
|
||||
volatile uint32_t driver_feature;
|
||||
/* MSIX — we write 0xFFFF to disable */
|
||||
volatile uint16_t config_msix_vector;
|
||||
volatile uint16_t num_queues;
|
||||
volatile uint8_t device_status;
|
||||
volatile uint8_t config_generation;
|
||||
/* per-queue */
|
||||
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;
|
||||
|
||||
/* Device status bits */
|
||||
#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
|
||||
|
||||
/* Feature bits */
|
||||
#define VIRTIO_F_VERSION_1 (1ULL << 32)
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Split virtqueue structures (virtio 1.0 §2.6)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define VQUEUE_SIZE 4u /* power of 2; ≥ 3 for one in-flight chain */
|
||||
|
||||
#define VRING_DESC_F_NEXT 1u
|
||||
#define VRING_DESC_F_WRITE 2u
|
||||
|
||||
typedef struct {
|
||||
uint64_t addr;
|
||||
uint32_t len;
|
||||
uint16_t flags;
|
||||
uint16_t next;
|
||||
} VirtqDesc;
|
||||
|
||||
/* Ring structs also unpacked (naturally aligned; see VirtioCommonCfg note).
|
||||
* The device reads avail->idx concurrently — stores must be exact-width. */
|
||||
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;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Virtio-blk request header
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
uint32_t type;
|
||||
uint32_t reserved;
|
||||
uint64_t sector;
|
||||
} VirtBlkReq;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Driver state
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
VirtioCommonCfg *common;
|
||||
volatile uint16_t *notify; /* doorbell register address */
|
||||
uint32_t notify_off_mult; /* notify_off_multiplier */
|
||||
uint16_t queue_notify_off;/* per-queue notify offset */
|
||||
|
||||
VirtqDesc *desc;
|
||||
VirtqAvail *avail;
|
||||
VirtqUsed *used;
|
||||
|
||||
uint16_t avail_idx;
|
||||
uint16_t last_used_idx;
|
||||
|
||||
uint64_t capacity_sectors; /* from device config */
|
||||
uint32_t total_forth_blocks;
|
||||
|
||||
/* I/O bounce buffers (DMA-accessible, allocated via kmalloc) */
|
||||
VirtBlkReq *req_buf;
|
||||
uint8_t *data_buf; /* BLKIO_FORTH_BLOCK_SIZE bytes */
|
||||
uint8_t *status_buf; /* 1 byte status */
|
||||
} VirtBlkState;
|
||||
|
||||
/* Singleton — one Artemis disk */
|
||||
static VirtBlkState g_vblk;
|
||||
static int g_vblk_ready = 0;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Capability walker
|
||||
* Returns MMIO pointer to the virtio capability region, or NULL.
|
||||
* cap_type: VIRTIO_PCI_CAP_COMMON_CFG etc.
|
||||
* extra_out: if non-NULL, receives the 4-byte value 16 bytes into the cap
|
||||
* (used for notify_off_multiplier).
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
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; /* guard against malformed cap list */
|
||||
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;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Memory barrier helpers
|
||||
* ------------------------------------------------------------------------- */
|
||||
static inline void wmb(void) {
|
||||
__asm__ volatile("" : : : "memory");
|
||||
}
|
||||
static inline void rmb(void) {
|
||||
__asm__ volatile("" : : : "memory");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Virtqueue I/O
|
||||
* desc[0] = request header (read by device)
|
||||
* desc[1] = data buffer (read or write by device)
|
||||
* desc[2] = status byte (written by device)
|
||||
* ------------------------------------------------------------------------- */
|
||||
static int vblk_io(int write, uint64_t sector, void *buf, uint32_t nbytes) {
|
||||
VirtBlkState *s = &g_vblk;
|
||||
VirtqDesc *d = s->desc;
|
||||
int result;
|
||||
|
||||
/* Fill request header */
|
||||
s->req_buf->type = write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN;
|
||||
s->req_buf->reserved = 0;
|
||||
s->req_buf->sector = sector;
|
||||
*s->status_buf = 0xFF; /* sentinel: device overwrites with result */
|
||||
|
||||
/* Descriptor 0: request header (device-readable) */
|
||||
d[0].addr = (uint64_t)(uintptr_t)s->req_buf;
|
||||
d[0].len = (uint32_t)sizeof(VirtBlkReq);
|
||||
d[0].flags = (uint16_t)VRING_DESC_F_NEXT;
|
||||
d[0].next = 1;
|
||||
|
||||
/* Descriptor 1: data buffer (device-readable for OUT, device-writable for IN) */
|
||||
d[1].addr = (uint64_t)(uintptr_t)buf;
|
||||
d[1].len = nbytes;
|
||||
d[1].flags = (uint16_t)(write ? VRING_DESC_F_NEXT
|
||||
: (VRING_DESC_F_NEXT | VRING_DESC_F_WRITE));
|
||||
d[1].next = 2;
|
||||
|
||||
/* Descriptor 2: status (device-writable) */
|
||||
d[2].addr = (uint64_t)(uintptr_t)s->status_buf;
|
||||
d[2].len = 1;
|
||||
d[2].flags = (uint16_t)VRING_DESC_F_WRITE;
|
||||
d[2].next = 0;
|
||||
|
||||
/* Add desc chain 0 to available ring */
|
||||
uint16_t avail_idx = s->avail_idx & (uint16_t)(VQUEUE_SIZE - 1u);
|
||||
s->avail->ring[avail_idx] = 0; /* first descriptor in chain */
|
||||
wmb();
|
||||
s->avail->idx = (uint16_t)(s->avail->idx + 1u);
|
||||
s->avail_idx = s->avail->idx;
|
||||
wmb();
|
||||
|
||||
/* Notify device: queue 0 */
|
||||
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; /* queue index 0 */
|
||||
wmb();
|
||||
|
||||
/* Poll until device posts a used entry.
|
||||
*
|
||||
* FABRIC-3.md, 2026-09-09: live-caught a real request that never gets
|
||||
* a used-ring completion -- root cause not yet isolated (every
|
||||
* existing bounds check, vblk_read()'s own fblock vs total_forth_
|
||||
* blocks and read_devblock_4k()'s own lba vs total_blkio_blocks_1k,
|
||||
* already passed before reaching here, so it isn't a simple out-of-
|
||||
* range request; disabling interrupts around this call was tried and
|
||||
* made it strictly worse -- suggests QEMU's own device-model
|
||||
* completion here depends on guest-visible time/interrupt progress in
|
||||
* this TCG configuration, not a driver-side reentrancy bug -- so that
|
||||
* approach was reverted). spin was 0x2000000 (~33M); at that bound a
|
||||
* single such failure cost tens of real minutes (confirmed: 31+ CPU-
|
||||
* minutes on one boot) because blk_migration_idle_check()'s own scan
|
||||
* calls this per un-cached devblock, up to ~23,000 times a boot, and
|
||||
* apparently more than one of those hits this failure mode. Cut by
|
||||
* ~30x: still enormously generous for a real completion (confirmed
|
||||
* live those return in a handful of iterations, not millions), but
|
||||
* turns what was an effectively-unbounded stall into a fast, loud,
|
||||
* logged failure instead -- callers already tolerate BLKIO_EIO
|
||||
* (zero-fill and move on), they just couldn't afford the old wait to
|
||||
* find that out. */
|
||||
uint32_t spin = 0x100000u; /* ~1M iterations */
|
||||
result = BLKIO_OK;
|
||||
while (s->used->idx == s->last_used_idx) {
|
||||
rmb();
|
||||
arch_relax(); /* FABRIC-3.md, 2026-09-09: xhci_wait_bit()'s own doc
|
||||
* comment (xhci.c) has the full reasoning -- a tight
|
||||
* MMIO-adjacent poll with no yield hint can starve
|
||||
* TCG's own host-side timer injection. */
|
||||
if (!--spin) {
|
||||
log_message(LOG_ERROR,
|
||||
"virtio_blk: vblk_io timed out (write=%d sector=%llu) -- "
|
||||
"queue never posted a used entry",
|
||||
write, (unsigned long long)sector);
|
||||
result = BLKIO_EIO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result == BLKIO_OK) {
|
||||
s->last_used_idx = s->used->idx;
|
||||
uint8_t status = *s->status_buf;
|
||||
if (status != (uint8_t)VIRTIO_BLK_S_OK) result = BLKIO_EIO;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* blkio_dev vtable callbacks
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static int vblk_open(blkio_dev_t *dev, const blkio_params_t *p) {
|
||||
(void)p;
|
||||
if (!dev) return BLKIO_EINVAL;
|
||||
dev->state = &g_vblk;
|
||||
dev->forth_block_size = BLKIO_FORTH_BLOCK_SIZE;
|
||||
dev->total_blocks = g_vblk.total_forth_blocks;
|
||||
return BLKIO_OK;
|
||||
}
|
||||
|
||||
static int vblk_close(blkio_dev_t *dev) {
|
||||
(void)dev;
|
||||
return BLKIO_OK;
|
||||
}
|
||||
|
||||
static int vblk_read(blkio_dev_t *dev, uint32_t fblock, void *dst) {
|
||||
if (!dev || !dst) return BLKIO_EINVAL;
|
||||
VirtBlkState *s = (VirtBlkState *)dev->state;
|
||||
if (fblock >= s->total_forth_blocks) return BLKIO_EINVAL;
|
||||
|
||||
uint64_t sector = (uint64_t)fblock * VBLK_SECTORS_PER_BLOK;
|
||||
int rc = vblk_io(0, sector, s->data_buf, BLKIO_FORTH_BLOCK_SIZE);
|
||||
if (rc != BLKIO_OK) return rc;
|
||||
memcpy(dst, s->data_buf, BLKIO_FORTH_BLOCK_SIZE);
|
||||
return BLKIO_OK;
|
||||
}
|
||||
|
||||
static int vblk_write(blkio_dev_t *dev, uint32_t fblock, const void *src) {
|
||||
if (!dev || !src) return BLKIO_EINVAL;
|
||||
VirtBlkState *s = (VirtBlkState *)dev->state;
|
||||
if (fblock >= s->total_forth_blocks) return BLKIO_EINVAL;
|
||||
|
||||
memcpy(s->data_buf, src, BLKIO_FORTH_BLOCK_SIZE);
|
||||
uint64_t sector = (uint64_t)fblock * VBLK_SECTORS_PER_BLOK;
|
||||
return vblk_io(1, sector, s->data_buf, BLKIO_FORTH_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
static int vblk_flush(blkio_dev_t *dev) {
|
||||
(void)dev;
|
||||
return BLKIO_OK; /* virtio-blk flush command not implemented; write-through */
|
||||
}
|
||||
|
||||
static int vblk_info(blkio_dev_t *dev, blkio_info_t *out) {
|
||||
if (!dev || !out) return BLKIO_EINVAL;
|
||||
VirtBlkState *s = (VirtBlkState *)dev->state;
|
||||
out->forth_block_size = BLKIO_FORTH_BLOCK_SIZE;
|
||||
out->total_blocks = s->total_forth_blocks;
|
||||
out->phys_sector_size = VBLK_SECTOR_SIZE;
|
||||
out->phys_size_bytes = s->capacity_sectors * VBLK_SECTOR_SIZE;
|
||||
out->read_only = 0;
|
||||
return BLKIO_OK;
|
||||
}
|
||||
|
||||
static const blkio_vtable_t g_vblk_vtable = {
|
||||
.open = vblk_open,
|
||||
.close = vblk_close,
|
||||
.read = vblk_read,
|
||||
.write = vblk_write,
|
||||
.flush = vblk_flush,
|
||||
.info = vblk_info
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Device initialisation
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static int vblk_init_device(const PciDevice *pci) {
|
||||
VirtBlkState *s = &g_vblk;
|
||||
|
||||
/* Enable bus mastering + MMIO */
|
||||
pci_enable(pci);
|
||||
|
||||
/* Walk capabilities: find common config and notify regions */
|
||||
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-blk: cap walk failed");
|
||||
return -2;
|
||||
}
|
||||
|
||||
s->common = common;
|
||||
s->notify = notify;
|
||||
s->notify_off_mult = notify_mult;
|
||||
|
||||
/* Step 1: reset device */
|
||||
common->device_status = 0;
|
||||
wmb();
|
||||
|
||||
/* Step 2: ACKNOWLEDGE */
|
||||
common->device_status = (uint8_t)VIRTIO_STATUS_ACKNOWLEDGE;
|
||||
wmb();
|
||||
|
||||
/* Step 3: DRIVER */
|
||||
common->device_status = (uint8_t)(VIRTIO_STATUS_ACKNOWLEDGE | VIRTIO_STATUS_DRIVER);
|
||||
wmb();
|
||||
|
||||
/* Step 4: feature negotiation — request VERSION_1 only */
|
||||
common->driver_feature_select = 1; /* select high 32 bits */
|
||||
wmb();
|
||||
common->driver_feature = (uint32_t)(VIRTIO_F_VERSION_1 >> 32);
|
||||
common->driver_feature_select = 0;
|
||||
wmb();
|
||||
common->driver_feature = 0; /* no low-32 features required */
|
||||
wmb();
|
||||
|
||||
/* Step 5: FEATURES_OK */
|
||||
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-blk: FEATURES_OK rejected");
|
||||
common->device_status = (uint8_t)VIRTIO_STATUS_FAILED;
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* Step 6: configure virtqueue 0 */
|
||||
common->queue_select = 0;
|
||||
wmb();
|
||||
uint16_t max_size = common->queue_size;
|
||||
if (max_size == 0 || max_size > 0x8000u) {
|
||||
console_println("virtio-blk: bad queue size");
|
||||
return -2;
|
||||
}
|
||||
uint16_t qsize = (max_size < (uint16_t)VQUEUE_SIZE)
|
||||
? max_size : (uint16_t)VQUEUE_SIZE;
|
||||
common->queue_size = qsize;
|
||||
s->queue_notify_off = common->queue_notify_off;
|
||||
wmb();
|
||||
|
||||
/* Disable MSIX (not supported) */
|
||||
common->config_msix_vector = 0xFFFFu;
|
||||
common->queue_msix_vector = 0xFFFFu;
|
||||
wmb();
|
||||
|
||||
/* Allocate virtqueue rings — 64-byte aligned */
|
||||
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-blk: queue alloc failed");
|
||||
return -2;
|
||||
}
|
||||
memset(s->desc, 0, desc_bytes);
|
||||
memset(s->avail, 0, avail_bytes);
|
||||
memset(s->used, 0, used_bytes);
|
||||
|
||||
/* Allocate I/O bounce buffers */
|
||||
s->req_buf = (VirtBlkReq *)kmalloc_aligned(sizeof(VirtBlkReq), 16);
|
||||
s->data_buf = (uint8_t *) kmalloc_aligned(BLKIO_FORTH_BLOCK_SIZE, 512);
|
||||
s->status_buf = (uint8_t *) kmalloc(1);
|
||||
if (!s->req_buf || !s->data_buf || !s->status_buf) {
|
||||
console_println("virtio-blk: buf alloc failed");
|
||||
return -2;
|
||||
}
|
||||
|
||||
s->avail_idx = 0;
|
||||
s->last_used_idx = 0;
|
||||
|
||||
/* Wire queue physical addresses into device */
|
||||
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();
|
||||
|
||||
/* Read device capacity from device config (at BAR offset after common) */
|
||||
{
|
||||
/* Capacity is the first 8 bytes of the device-specific config region */
|
||||
void *dev_cfg_raw = walk_virtio_caps(pci, VIRTIO_PCI_CAP_DEVICE_CFG, NULL);
|
||||
if (dev_cfg_raw) {
|
||||
volatile uint32_t *cap32 = (volatile uint32_t *)dev_cfg_raw;
|
||||
uint64_t lo = cap32[0];
|
||||
uint64_t hi = cap32[1];
|
||||
s->capacity_sectors = lo | (hi << 32);
|
||||
} else {
|
||||
/* Fallback: assume Artemis size */
|
||||
s->capacity_sectors = VBLK_ARTEMIS_SECTORS;
|
||||
}
|
||||
}
|
||||
s->total_forth_blocks = (uint32_t)(s->capacity_sectors / VBLK_SECTORS_PER_BLOK);
|
||||
|
||||
/* Step 7: DRIVER_OK */
|
||||
common->device_status = (uint8_t)(VIRTIO_STATUS_ACKNOWLEDGE |
|
||||
VIRTIO_STATUS_DRIVER |
|
||||
VIRTIO_STATUS_FEATURES_OK |
|
||||
VIRTIO_STATUS_DRIVER_OK);
|
||||
wmb();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Public entry point
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
int virtio_blk_find_artemis(blkio_dev_t *dev_out) {
|
||||
if (!dev_out) return -1;
|
||||
|
||||
PciDevice pci;
|
||||
/* Try modern device first, then legacy/transitional */
|
||||
int found = pci_find_first(VIRTIO_PCI_VENDOR_ID, VIRTIO_BLK_DEVICE_MODERN, &pci);
|
||||
if (found != 0)
|
||||
found = pci_find_first(VIRTIO_PCI_VENDOR_ID, VIRTIO_BLK_DEVICE_LEGACY, &pci);
|
||||
if (found != 0) {
|
||||
console_println("virtio-blk: no device on PCI bus 0");
|
||||
return -1;
|
||||
}
|
||||
|
||||
console_println("virtio-blk: found device");
|
||||
|
||||
int rc = vblk_init_device(&pci);
|
||||
if (rc != 0) return rc;
|
||||
|
||||
g_vblk_ready = 1;
|
||||
|
||||
blkio_params_t params;
|
||||
params.forth_block_size = BLKIO_FORTH_BLOCK_SIZE;
|
||||
params.total_blocks = g_vblk.total_forth_blocks;
|
||||
params.opaque = NULL;
|
||||
|
||||
return blkio_open(dev_out, &g_vblk_vtable, ¶ms);
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
* virtio_input.c — Virtio 1.0 input device driver for StarKernel
|
||||
* (riscv64: item 4.3.5c; aarch64: item 4.3.5e)
|
||||
*
|
||||
* Modern virtio 1.0 interface only (device ID 0x1052). No legacy fallback
|
||||
* exists for virtio-input (unlike virtio-blk's 0x1001) -- input postdates
|
||||
* the legacy 0.9.5 spec.
|
||||
*
|
||||
* Split virtqueue (eventq, index 0) with pre-posted device-writable
|
||||
* buffers: unlike virtio_blk.c's synchronous request/response pattern, the
|
||||
* driver posts empty VirtioInputEvent buffers up front and the device
|
||||
* fills+posts them to the used ring asynchronously, signalled by the PLIC
|
||||
* (riscv64) or GIC (aarch64) -- see virtio_input_isr(), called from
|
||||
* arch/riscv64/interrupts.c or arch/aarch64/interrupts.c on a matching
|
||||
* claim -- no polling anywhere in this path.
|
||||
*
|
||||
* The PCI capability walker below is a deliberate duplicate of
|
||||
* virtio_blk.c's static walk_virtio_caps(), not a shared/promoted version
|
||||
* -- decided at item 4.3.5c rather than refactoring the working, tested
|
||||
* virtio_blk.c, carried forward here.
|
||||
*
|
||||
* Interrupt routing (source computation/enable) is arch-guarded below,
|
||||
* riscv64 (PLIC) and aarch64 (GIC) each with their own slot/pin-to-source
|
||||
* formula (FABRIC-0.md §27.5.1 aarch64, §27.5.2 riscv64, both derived live
|
||||
* from this host's own QEMU DTB, not assumed identical to each other). The
|
||||
* capability walk, feature negotiation, and queue/event handling are
|
||||
* arch-agnostic and compiled for all three targets (same idiom as pci.c
|
||||
* itself: config-space access already differs per arch internally, this
|
||||
* file adds one more arch-guarded piece on top rather than a second copy
|
||||
* of the whole file).
|
||||
*/
|
||||
|
||||
#ifndef __STARKERNEL__
|
||||
#error "virtio_input.c is kernel-only"
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "starkernel/pci.h"
|
||||
#include "starkernel/virtio_input.h"
|
||||
#include "starkernel/kmalloc.h"
|
||||
#include "console.h"
|
||||
|
||||
#if defined(ARCH_RISCV64)
|
||||
#include "starkernel/plic.h"
|
||||
#endif
|
||||
|
||||
#if defined(ARCH_AARCH64)
|
||||
/* Defined in arch/aarch64/apic.c (item 4.3.5d). Extern-in-place, same
|
||||
* convention that file's own cross-file declarations already use. */
|
||||
extern void apic_spi_enable(uint32_t intid);
|
||||
#endif
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Virtio 1.0 PCI capability structures -- duplicated from virtio_blk.c
|
||||
* (see file header for why), plus VIRTIO_PCI_CAP_ISR_CFG actually used
|
||||
* here (virtio_blk.c defines the constant but never reads it -- blk polls
|
||||
* and never needed to; this is the first interrupt-driven virtio-pci
|
||||
* device in this tree).
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define VIRTIO_PCI_CAP_VENDOR_ID 0x09u
|
||||
|
||||
#define VIRTIO_PCI_CAP_COMMON_CFG 1u
|
||||
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2u
|
||||
#define VIRTIO_PCI_CAP_ISR_CFG 3u
|
||||
|
||||
#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
|
||||
|
||||
/* virtio common config (MMIO layout, virtio 1.0 §4.1.4.3) -- not packed,
|
||||
* same rationale as virtio_blk.c's VirtioCommonCfg (riscv64 strict
|
||||
* alignment; QEMU's common-cfg MMIO handler mishandles byte reads of the
|
||||
* 16-bit fields otherwise). */
|
||||
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 (virtio 1.0 §2.6) -- duplicated shape from
|
||||
* virtio_blk.c, sized for the event queue instead.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define EVENTQ_SIZE 8u /* power of 2; headroom for several queued events */
|
||||
|
||||
#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[EVENTQ_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[EVENTQ_SIZE];
|
||||
uint16_t avail_event;
|
||||
} VirtqUsed;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Diagnostic ring -- consumed by keyboard_words.c's VKBD-EVENT, same
|
||||
* relationship KBD-SCAN has to i8042's ring (raw hardware-boundary peek,
|
||||
* no scancode-to-ASCII layout, no REPL wiring -- that's 4.3.5f/M8).
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define DIAG_RING_SIZE 16u
|
||||
|
||||
typedef struct {
|
||||
uint16_t code;
|
||||
uint32_t value;
|
||||
} DiagEvent;
|
||||
|
||||
static DiagEvent g_diag_ring[DIAG_RING_SIZE];
|
||||
static volatile uint32_t g_diag_head = 0;
|
||||
static volatile uint32_t g_diag_tail = 0;
|
||||
|
||||
static void diag_push(uint16_t code, uint32_t value)
|
||||
{
|
||||
uint32_t next = (g_diag_head + 1u) % DIAG_RING_SIZE;
|
||||
if (next == g_diag_tail) return; /* full: drop, same as i8042's bounded ring */
|
||||
g_diag_ring[g_diag_head].code = code;
|
||||
g_diag_ring[g_diag_head].value = value;
|
||||
g_diag_head = next;
|
||||
}
|
||||
|
||||
int virtio_input_pop_event(uint16_t *code, uint32_t *value)
|
||||
{
|
||||
if (g_diag_tail == g_diag_head) return 0;
|
||||
if (code) *code = g_diag_ring[g_diag_tail].code;
|
||||
if (value) *value = g_diag_ring[g_diag_tail].value;
|
||||
g_diag_tail = (g_diag_tail + 1u) % DIAG_RING_SIZE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Standing diagnostic, same shape as g_plic_claim_count/g_i8042_isr_count. */
|
||||
volatile uint32_t g_virtio_input_isr_count = 0;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Driver state
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
VirtioCommonCfg *common;
|
||||
volatile uint16_t *notify;
|
||||
uint32_t notify_off_mult;
|
||||
uint16_t queue_notify_off;
|
||||
volatile uint8_t *isr_status; /* VIRTIO_PCI_CAP_ISR_CFG -- read to
|
||||
* deassert the (level-triggered) line */
|
||||
|
||||
VirtqDesc *desc;
|
||||
VirtqAvail *avail;
|
||||
VirtqUsed *used;
|
||||
|
||||
VirtioInputEvent *bufs; /* EVENTQ_SIZE event buffers, device-writable */
|
||||
uint16_t avail_idx;
|
||||
uint16_t last_used_idx;
|
||||
} VirtInputState;
|
||||
|
||||
static VirtInputState g_vinput;
|
||||
static int g_vinput_ready = 0;
|
||||
|
||||
/* PLIC source (riscv64) this device's INTx routes to, computed at init.
|
||||
* 0 = invalid/not found -- matches plic_claim()'s own "0 = nothing
|
||||
* pending" sentinel, so an unfound device can never accidentally match a
|
||||
* real claim. Checked by arch/riscv64/interrupts.c's dispatch. */
|
||||
uint32_t g_virtio_input_plic_source = 0;
|
||||
|
||||
/* GIC INTID (aarch64) this device's INTx routes to, computed at init. 0 is
|
||||
* not a valid SPI INTID (SPIs start at 32), so it is likewise a safe
|
||||
* "invalid/not found" sentinel. Checked by arch/aarch64/interrupts.c's
|
||||
* dispatch. */
|
||||
uint32_t g_virtio_input_gic_intid = 0;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Capability walker -- duplicated from virtio_blk.c, see file header.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
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) {
|
||||
/* Compiler barrier only, same as virtio_blk.c -- reported not fixed
|
||||
* there (FABRIC-0.md item 4.3.5e note); this item is the first place an
|
||||
* interrupt-driven used-ring (device writes concurrently with the
|
||||
* driver's re-post loop) leans on the ordering harder than blk's
|
||||
* synchronous polled loop ever did. */
|
||||
__asm__ volatile("" : : : "memory");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Interrupt routing -- riscv64 (PLIC, item 4.3.5c) and aarch64 (GIC, item
|
||||
* 4.3.5e). Both compute their target source from the same PCI slot/pin
|
||||
* swizzle shape (§27.5.1 aarch64, §27.5.2 riscv64) but with different base
|
||||
* offsets and controller calls -- derived independently from each board's
|
||||
* own DTB, not assumed identical. The #else path (amd64, which never
|
||||
* attaches this device) reports rather than silently no-ops, so a future
|
||||
* arch attempting to use this file without adding its own routing fails
|
||||
* loudly instead of "working" with a permanently-pending, never-enabled
|
||||
* source.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#if defined(ARCH_RISCV64)
|
||||
static int enable_interrupt_route(const PciDevice *pci)
|
||||
{
|
||||
uint8_t pin = pci_read8(pci, (uint16_t)PCI_CFG_INT_PIN);
|
||||
if (pin < 1u || pin > 4u) {
|
||||
console_println("virtio-input: bad/absent INT_PIN");
|
||||
return -1;
|
||||
}
|
||||
uint32_t slot = pci->device;
|
||||
uint32_t source = 32u + ((slot + (uint32_t)pin - 1u) % 4u);
|
||||
|
||||
plic_set_priority(source, 1u);
|
||||
plic_enable(source);
|
||||
|
||||
g_virtio_input_plic_source = source;
|
||||
return 0;
|
||||
}
|
||||
#elif defined(ARCH_AARCH64)
|
||||
static int enable_interrupt_route(const PciDevice *pci)
|
||||
{
|
||||
uint8_t pin = pci_read8(pci, (uint16_t)PCI_CFG_INT_PIN);
|
||||
if (pin < 1u || pin > 4u) {
|
||||
console_println("virtio-input: bad/absent INT_PIN");
|
||||
return -1;
|
||||
}
|
||||
uint32_t slot = pci->device;
|
||||
uint32_t spi = 3u + ((slot + (uint32_t)pin - 1u) % 4u);
|
||||
uint32_t intid = 32u + spi;
|
||||
|
||||
apic_spi_enable(intid);
|
||||
|
||||
g_virtio_input_gic_intid = intid;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int enable_interrupt_route(const PciDevice *pci)
|
||||
{
|
||||
(void)pci;
|
||||
console_println("virtio-input: interrupt routing not implemented on this arch");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Device initialisation -- status/feature negotiation mirrors
|
||||
* vblk_init_device() (virtio_blk.c:361-499); differences noted inline.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static int vinput_init_device(const PciDevice *pci) {
|
||||
VirtInputState *s = &g_vinput;
|
||||
|
||||
pci_enable(pci);
|
||||
|
||||
/* pci_enable() only sets IO/MEM/BUS_MASTER -- it never clears PCI
|
||||
* COMMAND bit 10 (Interrupt Disable). If firmware left it set, INTx
|
||||
* never asserts and every step below "succeeds" while producing zero
|
||||
* real interrupts (FABRIC-0.md item 4.3.5c/4.3.5e note). Check and clear
|
||||
* explicitly. */
|
||||
{
|
||||
uint16_t cmd = pci_read16(pci, (uint16_t)PCI_CFG_COMMAND);
|
||||
if (cmd & (uint16_t)PCI_CMD_INTX_DISABLE) {
|
||||
pci_write16(pci, (uint16_t)PCI_CFG_COMMAND,
|
||||
cmd & (uint16_t)~PCI_CMD_INTX_DISABLE);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
volatile uint8_t *isr_status = (volatile uint8_t *)
|
||||
walk_virtio_caps(pci, VIRTIO_PCI_CAP_ISR_CFG, NULL);
|
||||
|
||||
if (!common || !notify || !isr_status) {
|
||||
console_println("virtio-input: cap walk failed");
|
||||
return -2;
|
||||
}
|
||||
|
||||
s->common = common;
|
||||
s->notify = notify;
|
||||
s->notify_off_mult = notify_mult;
|
||||
s->isr_status = isr_status;
|
||||
|
||||
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();
|
||||
if (!(common->device_status & (uint8_t)VIRTIO_STATUS_FEATURES_OK)) {
|
||||
console_println("virtio-input: FEATURES_OK rejected");
|
||||
common->device_status = (uint8_t)VIRTIO_STATUS_FAILED;
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* Queue 0 = eventq (device-to-driver key events). Queue 1 = statusq
|
||||
* (driver-to-device, e.g. LED state) -- not needed to read keypresses;
|
||||
* left unconfigured, consistent with this item's scope (input, not
|
||||
* output reports). Read num_queues back rather than assume 2. */
|
||||
if (common->num_queues < 1u) {
|
||||
console_println("virtio-input: no queues reported");
|
||||
return -2;
|
||||
}
|
||||
|
||||
common->queue_select = 0;
|
||||
wmb();
|
||||
uint16_t max_size = common->queue_size;
|
||||
if (max_size == 0u) {
|
||||
console_println("virtio-input: bad eventq size");
|
||||
return -2;
|
||||
}
|
||||
uint16_t qsize = (max_size < (uint16_t)EVENTQ_SIZE)
|
||||
? max_size : (uint16_t)EVENTQ_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);
|
||||
s->bufs = (VirtioInputEvent *)
|
||||
kmalloc_aligned((size_t)qsize * sizeof(VirtioInputEvent), 8);
|
||||
if (!s->desc || !s->avail || !s->used || !s->bufs) {
|
||||
console_println("virtio-input: alloc failed");
|
||||
return -2;
|
||||
}
|
||||
memset(s->desc, 0, desc_bytes);
|
||||
memset(s->avail, 0, avail_bytes);
|
||||
memset(s->used, 0, used_bytes);
|
||||
memset(s->bufs, 0, (size_t)qsize * sizeof(VirtioInputEvent));
|
||||
|
||||
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();
|
||||
|
||||
/* Pre-post every buffer, device-writable, single descriptor each
|
||||
* (events are fixed 8-byte structs, no chaining needed) -- unlike
|
||||
* vblk_io()'s per-request chain-of-three, this posts the whole queue
|
||||
* up front and never waits synchronously; the device fills them
|
||||
* asynchronously as real input events arrive. */
|
||||
uint16_t i;
|
||||
for (i = 0; i < qsize; i++) {
|
||||
s->desc[i].addr = (uint64_t)(uintptr_t)&s->bufs[i];
|
||||
s->desc[i].len = (uint32_t)sizeof(VirtioInputEvent);
|
||||
s->desc[i].flags = (uint16_t)VRING_DESC_F_WRITE;
|
||||
s->desc[i].next = 0;
|
||||
|
||||
s->avail->ring[i] = i;
|
||||
}
|
||||
wmb();
|
||||
s->avail->idx = qsize;
|
||||
s->avail_idx = qsize;
|
||||
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();
|
||||
|
||||
if (enable_interrupt_route(pci) != 0) {
|
||||
console_println("virtio-input: interrupt route unavailable, device idle");
|
||||
return -2;
|
||||
}
|
||||
|
||||
common->device_status = (uint8_t)(VIRTIO_STATUS_ACKNOWLEDGE |
|
||||
VIRTIO_STATUS_DRIVER |
|
||||
VIRTIO_STATUS_FEATURES_OK |
|
||||
VIRTIO_STATUS_DRIVER_OK);
|
||||
wmb();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int virtio_input_find_keyboard(void) {
|
||||
PciDevice pci;
|
||||
int found = pci_find_first(VIRTIO_INPUT_PCI_VENDOR_ID,
|
||||
VIRTIO_INPUT_PCI_DEVICE_ID, &pci);
|
||||
if (found != 0) {
|
||||
console_println("virtio-input: no device on PCI bus 0");
|
||||
return -1;
|
||||
}
|
||||
|
||||
console_println("virtio-input: found device");
|
||||
|
||||
int rc = vinput_init_device(&pci);
|
||||
if (rc != 0) return rc;
|
||||
|
||||
g_vinput_ready = 1;
|
||||
console_println("virtio-input: driver ready");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Interrupt path -- called from arch/riscv64/interrupts.c on a matching
|
||||
* PLIC claim (g_virtio_input_plic_source) or arch/aarch64/interrupts.c on
|
||||
* a matching GIC IRQ (g_virtio_input_gic_intid).
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
void virtio_input_isr(void) {
|
||||
VirtInputState *s = &g_vinput;
|
||||
if (!g_vinput_ready) return;
|
||||
|
||||
g_virtio_input_isr_count++;
|
||||
|
||||
/* Mandatory: reading ISR status is what deasserts the (level-triggered,
|
||||
* per FABRIC-0.md §27.5.2's decoded interrupt-map flags) line. Skipping
|
||||
* this leaves the PLIC source's condition latched -- storm or hang,
|
||||
* not a subtle bug (same finding 4.3.5e's plan flags). */
|
||||
(void)*s->isr_status;
|
||||
|
||||
while (s->used->idx != s->last_used_idx) {
|
||||
uint16_t used_slot = s->last_used_idx % EVENTQ_SIZE;
|
||||
uint32_t desc_id = s->used->ring[used_slot].id;
|
||||
VirtioInputEvent *ev = &s->bufs[desc_id];
|
||||
|
||||
if (ev->type == (uint16_t)VIRTIO_INPUT_EV_KEY) {
|
||||
diag_push(ev->code, ev->value);
|
||||
}
|
||||
/* EV_SYN and other event types are received but not surfaced to
|
||||
* the diagnostic ring -- this item decodes EV_KEY specifically,
|
||||
* per its own scope; a full input-event model is later/REPL work. */
|
||||
|
||||
/* Re-post this buffer immediately -- same descriptor, still
|
||||
* device-writable, not re-initialised (already zeroed content
|
||||
* doesn't matter, the device overwrites it in full each time). */
|
||||
uint16_t avail_slot = s->avail_idx % EVENTQ_SIZE;
|
||||
s->avail->ring[avail_slot] = (uint16_t)desc_id;
|
||||
wmb();
|
||||
s->avail_idx++;
|
||||
s->avail->idx = s->avail_idx;
|
||||
wmb();
|
||||
|
||||
s->last_used_idx++;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* 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);
|
||||
/* The device-controlled used length is trusted after this point:
|
||||
* data_buf is only VRNG_BUF_SIZE bytes, so clamp anything larger to
|
||||
* the buffer size to keep the caller's later data_buf copy in-bounds
|
||||
* against a buggy or malicious device. */
|
||||
uint32_t used_len = s->used->ring[used_slot].len;
|
||||
if (used_len > VRNG_BUF_SIZE) used_len = VRNG_BUF_SIZE;
|
||||
*bytes_out = used_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