From 65517c66414bdb5a88eeeeb5714004d537fcf455 Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Mon, 2 Mar 2026 14:08:18 +0000 Subject: [PATCH 1/4] Add BLAKE2s entropy pool with per-CPU ChaCha20 CSPRNG Replace the single `SmallRng` with a proper two-layer RNG architecture: - Global BLAKE2s-256 entropy pool that accumulates entropy and gates seed extraction behind a 256-bit threshold. - Per-CPU ChaCha20Rng instances that are lazily seeded from the pool on first use and periodically reseed every 1 MB by XOR-ing a fresh BLAKE2 extract with 32 bytes of their own output. The /dev/random chardev uses fill_random_bytes directly instead of routing through the syscall layer. --- Cargo.lock | 69 ++++++++++++ Cargo.toml | 2 + src/drivers/chrdev/random.rs | 12 +- src/kernel/rand.rs | 212 ++++++++++++++++++++++++++++++----- 4 files changed, 263 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7120f1..e1f3b87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,6 +58,24 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -126,6 +144,16 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "defmt" version = "0.3.100" @@ -176,6 +204,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "embedded-io" version = "0.6.1" @@ -305,6 +344,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getargs" version = "0.5.0" @@ -492,6 +541,8 @@ dependencies = [ "arm-pl011-uart", "async-trait", "bitflags 2.11.0", + "blake2", + "chacha20", "fdt-parser", "futures", "getargs", @@ -815,6 +866,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.117" @@ -911,6 +968,12 @@ dependencies = [ "syn", ] +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -933,6 +996,12 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "virtio-drivers" version = "0.12.0" diff --git a/Cargo.toml b/Cargo.toml index cfe94aa..031ec46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,8 @@ rustc-hash = { version = "2.1", default-features = false } smoltcp = { version = "0.12.0", default-features = false, features = ["alloc", "medium-ethernet", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-tcp", "socket-udp"] } tock-registers = "0.10.1" virtio-drivers = "0.12.0" +blake2 = { version = "0.10.6", default-features = false } +chacha20 = { version = "0.10.0", default-features = false, features = ["rng"] } [build-dependencies] time = { version = "0.3.47", features = ["formatting", "macros"] } # For build timestamping via build.rs diff --git a/src/drivers/chrdev/random.rs b/src/drivers/chrdev/random.rs index 0474f90..14218db 100644 --- a/src/drivers/chrdev/random.rs +++ b/src/drivers/chrdev/random.rs @@ -7,10 +7,11 @@ use crate::{ fops::FileOps, open_file::{FileCtx, OpenFile}, }, - kernel::rand::sys_getrandom, + kernel::rand::fill_random_bytes, kernel_driver, + memory::uaccess::copy_to_user_slice, }; -use alloc::{boxed::Box, string::ToString, sync::Arc}; +use alloc::{boxed::Box, string::ToString, sync::Arc, vec}; use async_trait::async_trait; use core::{future::Future, pin::Pin}; use libkernel::{ @@ -34,7 +35,12 @@ impl FileOps for RandomFileOps { } async fn readat(&mut self, buf: UA, count: usize, _offset: u64) -> Result { - sys_getrandom(buf.cast(), count as _, 0).await + // TODO: Add an implementation of `/dev/urandom` which doesn't block if + // the entropy pool hasn't yet been seeded. + let mut kbuf = vec![0u8; count]; + fill_random_bytes(&mut kbuf).await; + copy_to_user_slice(&kbuf, buf).await?; + Ok(count) } fn poll_read_ready(&self) -> Pin> + Send>> { diff --git a/src/kernel/rand.rs b/src/kernel/rand.rs index fd47158..dc0bd97 100644 --- a/src/kernel/rand.rs +++ b/src/kernel/rand.rs @@ -1,39 +1,193 @@ -// TODO: generate a pool of entropy. +use core::sync::atomic::{AtomicUsize, Ordering}; use crate::{ drivers::timer::uptime, memory::uaccess::copy_to_user_slice, - sync::{OnceLock, SpinLock}, + per_cpu_private, + sync::{CondVar, OnceLock, SpinLock}, }; -use alloc::vec::Vec; -use libkernel::error::Result; +use blake2::{Blake2s256, Digest}; +use chacha20::ChaCha20Rng; use libkernel::memory::address::TUA; -use rand::{Rng, SeedableRng, rngs::SmallRng}; +use libkernel::{error::Result, sync::condvar::WakeupType}; +use rand::{Rng, SeedableRng}; -pub async fn sys_getrandom(ubuf: TUA, size: isize, _flags: u32) -> Result { - let buf = { - let mut rng = ENTROPY_POOL - .get_or_init(|| { - let now = uptime(); +/// Number of bytes generated before a per-CPU RNG reseeds from the entropy pool. +const RESEED_BYTES: usize = 1024 * 1024; - SpinLock::new(SmallRng::seed_from_u64( - (now.as_micros() & 0xffffffff_ffffffff) as u64, - )) - }) - .lock_save_irq(); - - let mut buf = Vec::with_capacity(size as usize); - - for _ in 0..size { - buf.push((rng.next_u32() & 0xff) as u8); - } - - buf - }; - - copy_to_user_slice(&buf, ubuf.to_untyped()).await?; - - Ok(size as _) +pub struct EntropyPool { + state: SpinLock, + pool_waiters: CondVar, + pool_bits: AtomicUsize, } -static ENTROPY_POOL: OnceLock> = OnceLock::new(); +impl EntropyPool { + fn new() -> Self { + Self { + state: SpinLock::new(Blake2s256::default()), + pool_waiters: CondVar::new(false), + pool_bits: AtomicUsize::new(0), + } + } + + pub fn add_entropy(&self, entropy: &[u8], approx_bits: usize) { + self.state.lock_save_irq().update(entropy); + + let old_val = self.pool_bits.fetch_add(approx_bits, Ordering::Relaxed); + + if old_val + approx_bits >= 256 && old_val < 256 { + // Pool was initialised. Wake up any waiters. + self.pool_waiters.update(|s| { + *s = true; + WakeupType::All + }); + } + } + + /// Add entropy into this pool based upon the uptime of the system. + /// + /// WARNING: This method should *not* be called for predictable or periodic + /// events. + pub fn add_temporal_entropy(&self) { + let uptime = uptime(); + + self.add_entropy( + &uptime.subsec_micros().to_le_bytes(), + 1_000_000_usize.ilog2() as usize, + ); + } + + /// Block until the pool has accumulated at least 256 bits of entropy, then + /// return a 32-byte seed derived from the pool state. + pub async fn extract_seed(&self) -> [u8; 32] { + self.pool_waiters + .wait_until(|s| if *s { Some(()) } else { None }) + .await; + + self.extract_seed_inner() + } + + /// Non-blocking seed extraction. Returns `None` if the pool has not yet + /// accumulated 256 bits of entropy. + pub fn try_extract_seed(&self) -> Option<[u8; 32]> { + if self.pool_bits.load(Ordering::Relaxed) < 256 { + return None; + } + + Some(self.extract_seed_inner()) + } + + fn extract_seed_inner(&self) -> [u8; 32] { + let mut state = self.state.lock_save_irq(); + + let hash = (*state).clone().finalize(); + + let mut seed = [0u8; 32]; + seed.copy_from_slice(&hash); + + // Feed the extracted hash back so the pool state diverges from the + // output (forward secrecy). + state.update(seed); + + seed + } +} + +pub fn entropy_pool() -> &'static EntropyPool { + ENTROPY_POOL.get_or_init(EntropyPool::new) +} + +static ENTROPY_POOL: OnceLock = OnceLock::new(); + +struct CpuRng { + rng: ChaCha20Rng, + seeded: bool, + bytes_since_reseed: usize, +} + +unsafe impl Send for CpuRng {} + +impl CpuRng { + fn new() -> Self { + Self { + rng: ChaCha20Rng::from_seed([0u8; 32]), + seeded: false, + bytes_since_reseed: 0, + } + } + + fn apply_seed(&mut self, seed: [u8; 32]) { + self.rng = ChaCha20Rng::from_seed(seed); + self.seeded = true; + self.bytes_since_reseed = 0; + } + + /// Reseed by XOR-ing a fresh BLAKE2 seed with 32 bytes of our own output, + /// then constructing a new ChaCha20 instance from the combined material. + fn reseed_with_blake(&mut self, blake_seed: [u8; 32]) { + let mut self_bytes = [0u8; 32]; + self.rng.fill_bytes(&mut self_bytes); + + let mut new_seed = [0u8; 32]; + + for i in 0..32 { + new_seed[i] = blake_seed[i] ^ self_bytes[i]; + } + + self.rng = ChaCha20Rng::from_seed(new_seed); + self.bytes_since_reseed = 0; + } + + fn fill(&mut self, buf: &mut [u8]) { + self.rng.fill_bytes(buf); + self.bytes_since_reseed += buf.len(); + } +} + +per_cpu_private! { + static CPU_RNG: CpuRng = CpuRng::new; +} + +/// Fill `buf` with cryptographically-strong random bytes. +/// +/// On the first invocation per CPU the call blocks until the global entropy +/// pool has been seeded (>= 256 bits). Subsequent calls are non-blocking; +/// periodic reseeding from the pool happens inline. +pub async fn fill_random_bytes(buf: &mut [u8]) { + // Ensure the per-CPU RNG has been seeded at least once. + let seeded = CPU_RNG.borrow().seeded; + + if !seeded { + let seed = entropy_pool().extract_seed().await; + CPU_RNG.borrow_mut().apply_seed(seed); + } + + // Reseed from the entropy pool if we have generated enough bytes. + let needs_reseed = CPU_RNG.borrow().bytes_since_reseed >= RESEED_BYTES; + if needs_reseed && let Some(blake_seed) = entropy_pool().try_extract_seed() { + CPU_RNG.borrow_mut().reseed_with_blake(blake_seed); + } + + CPU_RNG.borrow_mut().fill(buf); +} + +const GETRANDOM_CHUNK: usize = 256; + +pub async fn sys_getrandom(ubuf: TUA, size: isize, _flags: u32) -> Result { + let total = size as usize; + let mut buf = [0u8; GETRANDOM_CHUNK]; + let mut offset = 0; + + while offset < total { + let n = (total - offset).min(GETRANDOM_CHUNK); + let chunk = &mut buf[..n]; + + fill_random_bytes(chunk).await; + + copy_to_user_slice(chunk, ubuf.to_untyped().add_bytes(offset)).await?; + + offset += n; + } + + Ok(total) +} From 79b2df5bf49c3900f7681a0c1b3b0372c5867b1f Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Mon, 2 Mar 2026 15:08:10 +0000 Subject: [PATCH 2/4] tty: add entropy Add entropy to the entropy pool when processing input on TTYs. --- src/console/tty/cooker.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/console/tty/cooker.rs b/src/console/tty/cooker.rs index 444459e..3d56478 100644 --- a/src/console/tty/cooker.rs +++ b/src/console/tty/cooker.rs @@ -3,6 +3,7 @@ use super::meta::VSUSP; use super::{TtyInputHandler, meta::*}; use crate::console::Console; use crate::kernel::kpipe::KPipe; +use crate::kernel::rand::entropy_pool; use crate::process::thread_group::Pgid; use crate::process::thread_group::signal::SigId; use crate::process::thread_group::signal::kill::send_signal_to_pg; @@ -52,6 +53,11 @@ impl TtyInputHandler for SpinLock { .. } = &mut *this; + // Seed the entropy pool. + // + // SAFETY: A console interrupt isn't periodic. + entropy_pool().add_temporal_entropy(); + // Handle signal-generating control characters if termios.c_lflag.contains(TermiosLocalFlags::ISIG) { let intr_char = termios.c_cc[VINTR]; From 70ce893dafc0e376d976b37d7db7fde0fa934d79 Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Mon, 2 Mar 2026 21:56:58 +0000 Subject: [PATCH 3/4] rand: add `EntropySource` Add an `EntropySource` trait which allows the `EntropyPool` to pull entropy from various sources. --- src/kernel/rand.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/kernel/rand.rs b/src/kernel/rand.rs index dc0bd97..3044e04 100644 --- a/src/kernel/rand.rs +++ b/src/kernel/rand.rs @@ -1,5 +1,7 @@ use core::sync::atomic::{AtomicUsize, Ordering}; +use alloc::{sync::Arc, vec::Vec}; + use crate::{ drivers::timer::uptime, memory::uaccess::copy_to_user_slice, @@ -12,6 +14,13 @@ use libkernel::memory::address::TUA; use libkernel::{error::Result, sync::condvar::WakeupType}; use rand::{Rng, SeedableRng}; +/// A hardware or software source of entropy that the pool can query. +pub trait EntropySource: Send + Sync { + /// Pull up to `buf.len()` bytes of entropy from this source. + /// Returns `(bytes_written, approx_entropy_bits)`. + fn get_entropy(&self, buf: &mut [u8]) -> (usize, usize); +} + /// Number of bytes generated before a per-CPU RNG reseeds from the entropy pool. const RESEED_BYTES: usize = 1024 * 1024; @@ -19,6 +28,7 @@ pub struct EntropyPool { state: SpinLock, pool_waiters: CondVar, pool_bits: AtomicUsize, + sources: SpinLock>>, } impl EntropyPool { @@ -27,6 +37,7 @@ impl EntropyPool { state: SpinLock::new(Blake2s256::default()), pool_waiters: CondVar::new(false), pool_bits: AtomicUsize::new(0), + sources: SpinLock::new(Vec::new()), } } @@ -57,19 +68,37 @@ impl EntropyPool { ); } + /// Poll all registered entropy sources and feed their output into the pool. + fn poll_sources(&self) { + let sources = self.sources.lock_save_irq(); + let mut buf = [0u8; 64]; + + for source in sources.iter() { + let (written, bits) = source.get_entropy(&mut buf); + if written > 0 && bits > 0 { + self.add_entropy(&buf[..written], bits); + } + } + } + /// Block until the pool has accumulated at least 256 bits of entropy, then /// return a 32-byte seed derived from the pool state. pub async fn extract_seed(&self) -> [u8; 32] { + self.poll_sources(); + self.pool_waiters .wait_until(|s| if *s { Some(()) } else { None }) .await; + self.poll_sources(); self.extract_seed_inner() } /// Non-blocking seed extraction. Returns `None` if the pool has not yet /// accumulated 256 bits of entropy. pub fn try_extract_seed(&self) -> Option<[u8; 32]> { + self.poll_sources(); + if self.pool_bits.load(Ordering::Relaxed) < 256 { return None; } @@ -97,6 +126,11 @@ pub fn entropy_pool() -> &'static EntropyPool { ENTROPY_POOL.get_or_init(EntropyPool::new) } +/// Register a new entropy source that the pool can pull from. +pub fn register_entropy_source(source: Arc) { + entropy_pool().sources.lock_save_irq().push(source); +} + static ENTROPY_POOL: OnceLock = OnceLock::new(); struct CpuRng { From 4a98779020d713773448dffde731a9ef6a02e63c Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Thu, 5 Mar 2026 07:09:20 +0000 Subject: [PATCH 4/4] Add virtio-rng driver and ProbeError::NoMatch for silent probe skipping Add a virtio-rng entropy source driver that registers with the kernel RNG subsystem. Introduce `ProbeError::NoMatch` so virtio drivers can silently reject empty or wrong-type MMIO slots without spamming the boot log with fatal errors or leaving devices stuck in the deferred queue. --- libkernel/src/error.rs | 4 ++ scripts/qemu_runner.py | 9 ++- src/drivers/display/virtio.rs | 10 +-- src/drivers/fdt_prober.rs | 3 + src/drivers/mod.rs | 1 + src/drivers/rng/mod.rs | 1 + src/drivers/rng/virtio.rs | 115 ++++++++++++++++++++++++++++++++++ 7 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 src/drivers/rng/mod.rs create mode 100644 src/drivers/rng/virtio.rs diff --git a/libkernel/src/error.rs b/libkernel/src/error.rs index 729fa62..4a9df9b 100644 --- a/libkernel/src/error.rs +++ b/libkernel/src/error.rs @@ -23,6 +23,10 @@ pub enum ProbeError { // Driver probing should be tried again after other probes have succeeded. #[error("Driver probing deferred for other dependencies")] Deferred, + + // Device inspected but not a match for this driver; skip silently. + #[error("Device not matched by driver")] + NoMatch, } #[derive(Debug, Error, PartialEq, Eq, Clone)] diff --git a/scripts/qemu_runner.py b/scripts/qemu_runner.py index 609060d..d67a62f 100755 --- a/scripts/qemu_runner.py +++ b/scripts/qemu_runner.py @@ -41,9 +41,12 @@ default_args = { "-nographic": None, "-s": None, "-kernel": bin_executable_location, - "-append": f"{append_args} --rootfs=ext4fs --automount=/dev,devfs --automount=/tmp,tmpfs --automount=/proc,procfs --automount=/sys,sysfs" + "-append": f"{append_args} --rootfs=ext4fs --automount=/dev,devfs --automount=/tmp,tmpfs --automount=/proc,procfs --automount=/sys,sysfs", } +# Arguments that can appear multiple times (e.g. -device) +extra_args = ["-device", "virtio-rng-device"] + if args.debug: default_args["-S"] = None @@ -53,7 +56,7 @@ if args.display: default_args["-nic"] = "none" # Add uart default_args["-serial"] = "stdio" - default_args["-device"] = "virtio-gpu-device" + extra_args += ["-device", "virtio-gpu-device"] qemu_command = ["qemu-system-aarch64"] @@ -62,4 +65,6 @@ for key, value in default_args.items(): if value is not None: qemu_command.append(value) +qemu_command += extra_args + subprocess.run(qemu_command, check=True) diff --git a/src/drivers/display/virtio.rs b/src/drivers/display/virtio.rs index 3079974..2a48abe 100644 --- a/src/drivers/display/virtio.rs +++ b/src/drivers/display/virtio.rs @@ -127,15 +127,15 @@ fn virtio_gpu_probe(_dm: &mut DriverManager, d: DeviceDescriptor) -> Result t, + Err(_) => return Err(KernelError::Probe(ProbeError::NoMatch)), + } }; // Only bind to GPU here; other virtio-mmio devices should be handled by their own drivers. if !matches!(transport.device_type(), DeviceType::GPU) { - return Err(KernelError::Probe(ProbeError::Deferred)); + return Err(KernelError::Probe(ProbeError::NoMatch)); } if mapped != VA::from_value(0xffffd00000f92e00) { diff --git a/src/drivers/fdt_prober.rs b/src/drivers/fdt_prober.rs index d46f7bb..15be448 100644 --- a/src/drivers/fdt_prober.rs +++ b/src/drivers/fdt_prober.rs @@ -73,6 +73,9 @@ pub fn probe_for_fdt_devices() { Err(KernelError::Probe(ProbeError::Deferred)) => { deferred_list.push(desc); } + Err(KernelError::Probe(ProbeError::NoMatch)) => { + // Driver inspected the device but it's not a match; skip silently. + } Ok(None) => { // No driver found for this compatible string. Not an error, just ignore. } diff --git a/src/drivers/mod.rs b/src/drivers/mod.rs index 788181c..ee9c726 100644 --- a/src/drivers/mod.rs +++ b/src/drivers/mod.rs @@ -27,6 +27,7 @@ pub mod fs; pub mod init; pub mod interrupts; pub mod probe; +pub mod rng; pub mod timer; pub mod uart; mod virtio_hal; diff --git a/src/drivers/rng/mod.rs b/src/drivers/rng/mod.rs new file mode 100644 index 0000000..d43f304 --- /dev/null +++ b/src/drivers/rng/mod.rs @@ -0,0 +1 @@ +pub mod virtio; diff --git a/src/drivers/rng/virtio.rs b/src/drivers/rng/virtio.rs new file mode 100644 index 0000000..d761cca --- /dev/null +++ b/src/drivers/rng/virtio.rs @@ -0,0 +1,115 @@ +use crate::drivers::virtio_hal::VirtioHal; +use crate::sync::SpinLock; +use crate::{ + arch::ArchImpl, + drivers::{ + Driver, DriverManager, + init::PlatformBus, + probe::{DeviceDescriptor, DeviceMatchType}, + }, + kernel::rand::{EntropySource, register_entropy_source}, + kernel_driver, +}; +use alloc::{boxed::Box, sync::Arc}; +use core::ptr::NonNull; +use libkernel::{ + KernAddressSpace, VirtualMemory, + error::{KernelError, ProbeError, Result}, + memory::{ + address::{PA, VA}, + region::PhysMemoryRegion, + }, +}; +use log::info; +use virtio_drivers::{ + device::rng::VirtIORng, + transport::{ + DeviceType, Transport, + mmio::{MmioTransport, VirtIOHeader}, + }, +}; + +pub struct VirtioRngDriver { + rng: SpinLock>>, +} + +impl Driver for VirtioRngDriver { + fn name(&self) -> &'static str { + "virtio-rng" + } +} + +impl EntropySource for VirtioRngDriver { + fn get_entropy(&self, buf: &mut [u8]) -> (usize, usize) { + let mut rng = self.rng.lock_save_irq(); + match rng.request_entropy(buf) { + Ok(n) => (n, n * 8), + Err(_) => (0, 0), + } + } +} + +fn virtio_rng_probe(_dm: &mut DriverManager, d: DeviceDescriptor) -> Result> { + match d { + DeviceDescriptor::Fdt(fdt_node, _flags) => { + let region = fdt_node + .reg() + .ok_or(ProbeError::NoReg)? + .next() + .ok_or(ProbeError::NoReg)?; + + let size = region.size.ok_or(ProbeError::NoRegSize)?; + + let mapped: VA = + ArchImpl::kern_address_space() + .lock_save_irq() + .map_mmio(PhysMemoryRegion::new( + PA::from_value(region.address as usize), + size, + ))?; + + let header = NonNull::new(mapped.value() as *mut VirtIOHeader) + .ok_or(KernelError::InvalidValue)?; + + let transport = unsafe { + match MmioTransport::new(header, size) { + Ok(t) => t, + Err(_) => return Err(KernelError::Probe(ProbeError::NoMatch)), + } + }; + + if !matches!(transport.device_type(), DeviceType::EntropySource) { + return Err(KernelError::Probe(ProbeError::NoMatch)); + } + + info!("virtio-rng found (node {})", fdt_node.name); + + let rng = VirtIORng::::new(transport) + .map_err(|_| KernelError::Other("virtio-rng init failed"))?; + + let driver = Arc::new(VirtioRngDriver { + rng: SpinLock::new(rng), + }); + + register_entropy_source(driver.clone()); + + Ok(driver) + } + } +} + +fn virtio_rng_init(bus: &mut PlatformBus, _dm: &mut DriverManager) -> Result<()> { + bus.register_platform_driver( + DeviceMatchType::FdtCompatible("virtio,mmio"), + Box::new(virtio_rng_probe), + ); + + bus.register_platform_driver( + DeviceMatchType::FdtCompatible("virtio-mmio"), + Box::new(virtio_rng_probe), + ); + + Ok(()) +} + +kernel_driver!(virtio_rng_init);