Support `virtio-rng` and expose it as `/dev/hwrng`

This commit is contained in:
li041 2026-02-04 09:18:07 +00:00 committed by lxh
parent c3dda32805
commit 3382754270
8 changed files with 488 additions and 1 deletions

View File

@ -0,0 +1,85 @@
// SPDX-License-Identifier: MPL-2.0
use alloc::{boxed::Box, format, string::String, sync::Arc};
use core::sync::atomic::{AtomicUsize, Ordering};
use aster_util::mem_obj_slice::Slice;
use log::debug;
use ostd::{
arch::trap::TrapFrame,
mm::dma::{DmaStream, FromDevice},
sync::{Mutex, SpinLock},
};
use spin::Once;
use crate::{
device::{
VirtioDeviceError,
entropy::{handle_recv_irq, register_device},
},
queue::VirtQueue,
transport::VirtioTransport,
};
pub static ENTROPY_DEVICE_PREFIX: &str = "virtio_rng.";
static ENTROPY_DEVICE_ID: AtomicUsize = AtomicUsize::new(0);
pub static RNG_CURRENT: Once<Mutex<String>> = Once::new();
pub struct EntropyDevice {
transport: SpinLock<Box<dyn VirtioTransport>>,
pub request_queue: SpinLock<VirtQueue>,
pub receive_buffer: DmaStream<FromDevice>,
}
impl EntropyDevice {
pub fn init(mut transport: Box<dyn VirtioTransport>) -> Result<(), VirtioDeviceError> {
let request_queue = SpinLock::new(VirtQueue::new(0, 1, transport.as_mut()).unwrap());
let receive_buffer = DmaStream::alloc_uninit(1, false).unwrap();
let device = Arc::new(EntropyDevice {
transport: SpinLock::new(transport),
request_queue,
receive_buffer,
});
// Register irq callbacks
let mut transport = device.transport.disable_irq().lock();
transport
.register_queue_callback(0, Box::new(handle_recv_irq), false)
.unwrap();
transport
.register_cfg_callback(Box::new(config_space_change))
.unwrap();
transport.finish_init();
drop(transport);
let device_id = ENTROPY_DEVICE_ID.fetch_add(1, Ordering::SeqCst);
let name = format!("{ENTROPY_DEVICE_PREFIX}{device_id}");
register_device(name.clone(), device);
RNG_CURRENT.call_once(|| Mutex::new(name));
Ok(())
}
pub fn can_pop(&self) -> bool {
let request_queue = self.request_queue.lock();
request_queue.can_pop()
}
pub fn activate_receive_buffer(&self, receive_queue: &mut VirtQueue, to_read: usize) {
receive_queue
.add_dma_buf(&[], &[&Slice::new(&self.receive_buffer, 0..to_read)])
.unwrap();
if receive_queue.should_notify() {
receive_queue.notify();
}
}
}
fn config_space_change(_: &TrapFrame) {
debug!("Virtio-Entropy device configuration space change");
}

View File

@ -0,0 +1,55 @@
// SPDX-License-Identifier: MPL-2.0
use alloc::{boxed::Box, collections::btree_map::BTreeMap, string::String, sync::Arc, vec::Vec};
use ostd::{
arch::trap::TrapFrame,
sync::{LocalIrqDisabled, SpinLock},
};
use spin::Once;
use crate::device::entropy::device::EntropyDevice;
pub mod device;
pub trait EntropyDeviceIrqHandler = Fn() + Send + Sync + 'static;
pub fn register_device(name: String, device: Arc<EntropyDevice>) {
ENTROPY_DEVICE_TABLE
.get()
.unwrap()
.lock()
.insert(name, device);
}
pub fn get_device(str: &str) -> Option<Arc<EntropyDevice>> {
let lock = ENTROPY_DEVICE_TABLE.get().unwrap().lock();
lock.get(str).cloned()
}
pub fn all_devices() -> Vec<(String, Arc<EntropyDevice>)> {
let entropy_devs = ENTROPY_DEVICE_TABLE.get().unwrap().lock();
entropy_devs
.iter()
.map(|(name, dev)| (name.clone(), dev.clone()))
.collect()
}
pub fn register_recv_callback(callback: impl EntropyDeviceIrqHandler) {
ENTROPY_DEVICE_CALLBACK.call_once(|| Box::new(callback));
}
pub fn handle_recv_irq(_: &TrapFrame) {
ENTROPY_DEVICE_CALLBACK.get().unwrap()()
}
pub fn init() {
ENTROPY_DEVICE_TABLE.call_once(|| SpinLock::new(BTreeMap::new()));
}
pub static ENTROPY_DEVICE_CALLBACK: Once<Box<dyn EntropyDeviceIrqHandler>> = Once::new();
pub static ENTROPY_DEVICE_TABLE: Once<
SpinLock<BTreeMap<String, Arc<EntropyDevice>>, LocalIrqDisabled>,
> = Once::new();

View File

@ -6,6 +6,7 @@ use crate::queue::QueueError;
pub mod block;
pub mod console;
pub mod entropy;
pub mod input;
pub mod network;
pub mod socket;

View File

@ -18,6 +18,7 @@ use device::{
VirtioDeviceType,
block::device::BlockDevice,
console::device::ConsoleDevice,
entropy::{self, device::EntropyDevice},
input::device::InputDevice,
network::device::NetworkDevice,
socket::{self, device::SocketDevice},
@ -42,6 +43,8 @@ fn virtio_component_init() -> Result<(), ComponentInitError> {
// Find all devices and register them to the corresponding crate
transport::init();
// For entropy table static init
entropy::init();
// For vsock table static init
socket::init();
while let Some(mut transport) = pop_device_transport() {
@ -70,9 +73,10 @@ fn virtio_component_init() -> Result<(), ComponentInitError> {
let device_type = transport.device_type();
let res = match transport.device_type() {
VirtioDeviceType::Block => BlockDevice::init(transport),
VirtioDeviceType::Console => ConsoleDevice::init(transport),
VirtioDeviceType::Entropy => EntropyDevice::init(transport),
VirtioDeviceType::Input => InputDevice::init(transport),
VirtioDeviceType::Network => NetworkDevice::init(transport),
VirtioDeviceType::Console => ConsoleDevice::init(transport),
VirtioDeviceType::Socket => SocketDevice::init(transport),
_ => {
warn!("[Virtio]: Found unimplemented device:{:?}", device_type);

269
kernel/src/device/hwrng.rs Normal file
View File

@ -0,0 +1,269 @@
// SPDX-License-Identifier: MPL-2.0
use alloc::{boxed::Box, string::String, sync::Arc};
use aster_virtio::device::entropy::{
all_devices,
device::{EntropyDevice, RNG_CURRENT},
get_device, register_recv_callback,
};
use device_id::{DeviceId, MajorId, MinorId};
use ostd::mm::{VmReader, VmWriter, io_util::HasVmReaderWriter};
use crate::{
device::registry::char,
events::IoEvents,
fs::{
device::{Device, DeviceType},
inode_handle::FileIo,
utils::{InodeIo, StatusFlags},
},
prelude::*,
process::signal::{PollHandle, Pollable, Pollee},
util::ring_buffer::RingBuffer,
};
static HW_RNG_HANDLE: Mutex<Option<Arc<HwRngHandle>>> = Mutex::new(None);
const HW_RNG_BUFFER_CAPACITY: usize = 4096;
#[derive(Clone)]
struct HwRngHandle {
rng: Arc<EntropyDevice>,
pollee: Pollee,
recv_state: Arc<SpinLock<HwRngRecvState>>,
}
struct HwRngRecvState {
buffer: RingBuffer<u8>,
in_flight: bool,
}
impl HwRngHandle {
pub fn new(rng: Arc<EntropyDevice>) -> Self {
Self {
rng,
pollee: Pollee::new(),
recv_state: Arc::new(SpinLock::new(HwRngRecvState {
buffer: RingBuffer::new(HW_RNG_BUFFER_CAPACITY),
in_flight: false,
})),
}
}
pub fn check_io_events(&self) -> IoEvents {
let mut events = IoEvents::empty();
if !self.recv_state.lock().buffer.is_empty() {
events |= IoEvents::IN;
}
events
}
fn activate_receive_buffer(&self) {
let should_activate = {
let mut state = self.recv_state.disable_irq().lock();
if state.in_flight || state.buffer.is_full() {
return;
}
state.in_flight = true;
true
};
if should_activate {
let mut request_queue = self.rng.request_queue.disable_irq().lock();
self.rng
.activate_receive_buffer(&mut request_queue, PAGE_SIZE);
}
}
fn handle_recv_irq(&self) {
let mut request_queue = self.rng.request_queue.disable_irq().lock();
let Ok((_, used_len)) = request_queue.pop_used() else {
return;
};
drop(request_queue);
let used_len = used_len as usize;
self.rng
.receive_buffer
.sync_from_device(0..used_len)
.unwrap();
let (wrote, should_activate) = {
let mut state = self.recv_state.disable_irq().lock();
let free_len = state.buffer.free_len();
let read_len = used_len.min(free_len);
let mut wrote = 0;
if read_len > 0 {
let mut reader = self.rng.receive_buffer.reader().unwrap();
reader.limit(read_len);
let mut tmp = vec![0u8; read_len];
let mut writer = VmWriter::from(tmp.as_mut_slice());
wrote = reader.read(&mut writer);
state.buffer.push_slice(&tmp[..wrote]).unwrap();
}
state.in_flight = false;
let should_activate = if state.buffer.is_full() {
false
} else {
state.in_flight = true;
true
};
(wrote, should_activate)
};
if wrote > 0 {
self.pollee.notify(IoEvents::IN);
}
if should_activate {
let mut request_queue = self.rng.request_queue.disable_irq().lock();
self.rng
.activate_receive_buffer(&mut request_queue, PAGE_SIZE);
}
}
fn try_read(&self, writer: &mut VmWriter) -> Result<usize> {
let mut state = self.recv_state.disable_irq().lock();
if state.buffer.is_empty() {
return_errno_with_message!(Errno::EAGAIN, "entropy buffer is not ready");
}
state.buffer.read_fallible(writer)
}
}
struct HwRngDevice;
impl Device for HwRngDevice {
fn type_(&self) -> DeviceType {
DeviceType::Char
}
fn id(&self) -> DeviceId {
// Same Value with Linux: major 10, minor 183
device_id::DeviceId::new(MajorId::new(10), MinorId::new(183))
}
fn devtmpfs_path(&self) -> Option<String> {
Some("hwrng".into())
}
fn open(&self) -> Result<Box<dyn FileIo>> {
if RNG_CURRENT.get().is_none() {
return_errno_with_message!(Errno::ENODEV, "No hardware RNG device found");
}
let mut handle_lock = HW_RNG_HANDLE.lock();
if handle_lock.is_none() {
let name_lock = RNG_CURRENT.get().unwrap();
let mut device = get_device(&name_lock.lock());
if device.is_none() {
let all = all_devices();
if let Some((fallback_name, fallback_device)) = all.first() {
*name_lock.lock() = fallback_name.clone();
device = Some(fallback_device.clone());
}
}
let device = device.ok_or_else(|| {
Error::with_message(Errno::ENODEV, "No hardware RNG device found")
})?;
*handle_lock = Some(Arc::new(HwRngHandle::new(device)));
}
let hwrng_handle = handle_lock.as_ref().unwrap();
Ok(Box::new((**hwrng_handle).clone()))
}
}
impl Pollable for HwRngHandle {
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
self.pollee
.poll_with(mask, poller, || self.check_io_events())
}
}
impl InodeIo for HwRngHandle {
fn read_at(
&self,
_offset: usize,
writer: &mut VmWriter,
status_flags: StatusFlags,
) -> Result<usize> {
let len = writer.avail();
let mut written_bytes = 0;
let is_nonblocking = status_flags.contains(StatusFlags::O_NONBLOCK);
while written_bytes < len {
self.activate_receive_buffer();
let read_once = if is_nonblocking {
self.try_read(writer)
} else {
self.wait_events(IoEvents::IN, None, || self.try_read(writer))
};
match read_once {
Ok(0) => break,
Ok(copied) => {
written_bytes += copied;
self.pollee.invalidate();
}
Err(err) if is_nonblocking && err.error() == Errno::EAGAIN => {
if written_bytes == 0 {
return Err(err);
}
break;
}
Err(err) => return Err(err),
}
}
Ok(written_bytes)
}
fn write_at(
&self,
_offset: usize,
reader: &mut VmReader,
_status_flags: StatusFlags,
) -> Result<usize> {
let len = reader.remain();
reader.skip(len);
Ok(len)
}
}
impl FileIo for HwRngHandle {
fn check_seekable(&self) -> Result<()> {
Ok(())
}
fn is_offset_aware(&self) -> bool {
false
}
}
pub(super) fn init_in_first_process() -> Result<()> {
register_recv_callback(|| {
let device_lock = HW_RNG_HANDLE.lock();
if let Some(hwrng_handle) = &*device_lock {
hwrng_handle.handle_recv_irq();
}
});
char::register(Arc::new(HwRngDevice))?;
Ok(())
}

View File

@ -2,6 +2,7 @@
mod evdev;
mod fb;
mod hwrng;
mod mem;
pub mod misc;
mod pty;
@ -38,6 +39,7 @@ pub fn init_in_first_process(ctx: &Context) -> Result<()> {
let dev_path = path_resolver.lookup(&FsPath::try_from("/dev")?)?;
dev_path.mount(RamFs::new(), PerMountFlags::default(), ctx)?;
hwrng::init_in_first_process()?;
tty::init_in_first_process()?;
pty::init_in_first_process(&path_resolver, ctx)?;
shm::init_in_first_process(&path_resolver, ctx)?;

View File

@ -0,0 +1,69 @@
// SPDX-License-Identifier: MPL-2.0
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/wait.h>
#include <unistd.h>
#include "../test.h"
#define HWRNG_DEVICE "/dev/hwrng"
#define HWRNG_MAJOR 10
#define HWRNG_MINOR 183
static int hwrng_fd = -1;
FN_TEST(open_hwrng)
{
struct stat st;
hwrng_fd = TEST_SUCC(open(HWRNG_DEVICE, O_RDONLY));
TEST_RES(fstat(hwrng_fd, &st),
S_ISCHR(st.st_mode) && major(st.st_rdev) == HWRNG_MAJOR &&
minor(st.st_rdev) == HWRNG_MINOR);
}
END_TEST()
FN_TEST(read_hwrng)
{
uint8_t buf1[64];
uint8_t buf2[64];
ssize_t ret = read(hwrng_fd, buf1, sizeof(buf1));
if (ret < 0) {
if (errno == ENODEV) {
fprintf(stderr, "hwrng tests skipped: %s (%s)\n",
HWRNG_DEVICE, strerror(errno));
exit(EXIT_SUCCESS);
}
fprintf(stderr,
"fatal error: read_hwrng: read('%s') failed: %s\n",
HWRNG_DEVICE, strerror(errno));
exit(EXIT_FAILURE);
}
TEST_RES(read(hwrng_fd, buf2, sizeof(buf2)), _ret == 64);
TEST_RES(memcmp(buf1, buf2, sizeof(buf1)), _ret != 0);
}
END_TEST()
FN_SETUP(close_hwrng)
{
CHECK(close(hwrng_fd));
}
END_SETUP()

View File

@ -118,6 +118,8 @@ QEMU_ARGS="\
-machine q35,kernel-irqchip=split \
-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off,queue-size=64,num-queues=1,request-merging=off,backend_defaults=off,discard=off,write-zeroes=off,event_idx=off,indirect_desc=off,queue_reset=off$IOMMU_DEV_EXTRA \
-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off,queue-size=64,num-queues=1,request-merging=off,backend_defaults=off,discard=off,write-zeroes=off,event_idx=off,indirect_desc=off,queue_reset=off$IOMMU_DEV_EXTRA \
-object rng-random,id=rng0,filename=/dev/urandom \
-device virtio-rng-pci,bus=pcie.0,addr=0x8,disable-legacy=on,disable-modern=off,rng=rng0,event_idx=off,indirect_desc=off,queue_reset=off$IOMMU_DEV_EXTRA \
-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off$VIRTIO_NET_FEATURES$IOMMU_DEV_EXTRA \
-device virtio-serial-pci,disable-legacy=on,disable-modern=off$IOMMU_DEV_EXTRA \
$CONSOLE_ARGS \