diff --git a/kernel/comps/virtio/src/device/entropy/device.rs b/kernel/comps/virtio/src/device/entropy/device.rs new file mode 100644 index 000000000..55d8187ab --- /dev/null +++ b/kernel/comps/virtio/src/device/entropy/device.rs @@ -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> = Once::new(); + +pub struct EntropyDevice { + transport: SpinLock>, + pub request_queue: SpinLock, + pub receive_buffer: DmaStream, +} + +impl EntropyDevice { + pub fn init(mut transport: Box) -> 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"); +} diff --git a/kernel/comps/virtio/src/device/entropy/mod.rs b/kernel/comps/virtio/src/device/entropy/mod.rs new file mode 100644 index 000000000..744cc07ac --- /dev/null +++ b/kernel/comps/virtio/src/device/entropy/mod.rs @@ -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) { + ENTROPY_DEVICE_TABLE + .get() + .unwrap() + .lock() + .insert(name, device); +} + +pub fn get_device(str: &str) -> Option> { + let lock = ENTROPY_DEVICE_TABLE.get().unwrap().lock(); + lock.get(str).cloned() +} + +pub fn all_devices() -> Vec<(String, Arc)> { + 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> = Once::new(); + +pub static ENTROPY_DEVICE_TABLE: Once< + SpinLock>, LocalIrqDisabled>, +> = Once::new(); diff --git a/kernel/comps/virtio/src/device/mod.rs b/kernel/comps/virtio/src/device/mod.rs index 2f8640654..3411bf627 100644 --- a/kernel/comps/virtio/src/device/mod.rs +++ b/kernel/comps/virtio/src/device/mod.rs @@ -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; diff --git a/kernel/comps/virtio/src/lib.rs b/kernel/comps/virtio/src/lib.rs index 7241a1d2a..8482919ba 100644 --- a/kernel/comps/virtio/src/lib.rs +++ b/kernel/comps/virtio/src/lib.rs @@ -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); diff --git a/kernel/src/device/hwrng.rs b/kernel/src/device/hwrng.rs new file mode 100644 index 000000000..51db179cb --- /dev/null +++ b/kernel/src/device/hwrng.rs @@ -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>> = Mutex::new(None); + +const HW_RNG_BUFFER_CAPACITY: usize = 4096; + +#[derive(Clone)] +struct HwRngHandle { + rng: Arc, + pollee: Pollee, + recv_state: Arc>, +} + +struct HwRngRecvState { + buffer: RingBuffer, + in_flight: bool, +} + +impl HwRngHandle { + pub fn new(rng: Arc) -> 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 { + 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 { + Some("hwrng".into()) + } + + fn open(&self) -> Result> { + 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 { + 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 { + 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(()) +} diff --git a/kernel/src/device/mod.rs b/kernel/src/device/mod.rs index 49ca6670d..bbb6d3dc6 100644 --- a/kernel/src/device/mod.rs +++ b/kernel/src/device/mod.rs @@ -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)?; diff --git a/test/initramfs/src/apps/devfs/hwrng.c b/test/initramfs/src/apps/devfs/hwrng.c new file mode 100644 index 000000000..c7eb9e11d --- /dev/null +++ b/test/initramfs/src/apps/devfs/hwrng.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MPL-2.0 + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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() diff --git a/tools/qemu_args.sh b/tools/qemu_args.sh index 1be720403..f7e8fce20 100755 --- a/tools/qemu_args.sh +++ b/tools/qemu_args.sh @@ -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 \