From c55cb8af04d696075af2aecccdcc7e2a7a63a79e Mon Sep 17 00:00:00 2001 From: Yuhan Deng Date: Mon, 8 Jun 2026 13:37:01 -0700 Subject: [PATCH] feat: doorbell --- common/src/lib.rs | 1 + common/src/pipe.rs | 8 +- common/src/pipe/bidirectional_pipe.rs | 140 ++++++++++++++++++++------ common/src/pipe/traits.rs | 15 +++ common/src/sync_stream.rs | 30 +++--- kernel/src/doorbell.rs | 27 +++++ kernel/src/lib.rs | 1 + vmm/src/doorbell.rs | 82 +++++++++++++++ vmm/src/lib.rs | 1 + 9 files changed, 256 insertions(+), 49 deletions(-) create mode 100644 kernel/src/doorbell.rs create mode 100644 vmm/src/doorbell.rs diff --git a/common/src/lib.rs b/common/src/lib.rs index 1581972..cc219a4 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,6 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(stable_features, unused_features)] #![feature(allocator_api)] +#![feature(new_range_api)] #![feature(fn_traits)] #![cfg_attr(feature = "std", feature(layout_for_ptr))] #![feature(negative_impls)] diff --git a/common/src/pipe.rs b/common/src/pipe.rs index a3b10d4..0edac25 100644 --- a/common/src/pipe.rs +++ b/common/src/pipe.rs @@ -23,4 +23,10 @@ pub use ring::{RingData, RingHeader}; pub use ring_consumer::RingConsumer; pub use ring_producer::RingProducer; pub use shared_memory_region::{RawSharedMemoryRegion, SharedMemoryRegion}; -pub use traits::{Read, Write}; +pub use traits::{DoorBell, Read, Write}; + +#[cfg(feature = "std")] +pub use traits::DoorBellWaiter; + +#[cfg(test)] +pub(crate) use bidirectional_pipe::test_utils; diff --git a/common/src/pipe/bidirectional_pipe.rs b/common/src/pipe/bidirectional_pipe.rs index e9f4c13..fa29b38 100644 --- a/common/src/pipe/bidirectional_pipe.rs +++ b/common/src/pipe/bidirectional_pipe.rs @@ -3,7 +3,7 @@ use crate::pipe::ring::{RingData, RingHeader}; use crate::pipe::ring_consumer::RingConsumer; use crate::pipe::ring_producer::RingProducer; use crate::pipe::shared_memory_region::SharedMemoryRegion; -use crate::pipe::traits::{self, OwnedSplit}; +use crate::pipe::traits::{self, DoorBell, OwnedSplit}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Side { @@ -22,24 +22,28 @@ pub enum Side { /// whenever `R: Send`. /// /// Memory layout: `[HeaderA][Ring A->B data][HeaderB][Ring B->A data]`. -pub struct BidirectionalPipe { +pub struct BidirectionalPipe { writer: RingProducer, reader: RingConsumer, + read_available_doorbell: D, + write_available_doorbell: D, } #[allow(unused)] -pub struct BidirectionalPipeWriteEnd { +pub struct BidirectionalPipeWriteEnd { writer: RingProducer, + read_available_doorbell: D, } #[allow(unused)] -pub struct BidirectionalPipeReadEnd { +pub struct BidirectionalPipeReadEnd { reader: RingConsumer, + write_available_doorbell: D, } const HEADER_SIZE: u64 = core::mem::size_of::() as u64; -impl BidirectionalPipe { +impl BidirectionalPipe { /// Total bytes of shared memory needed for a given `ring_size`. pub const fn required_size(ring_size: u64) -> u64 { 2 * (HEADER_SIZE + ring_size) @@ -51,7 +55,13 @@ impl BidirectionalPipe { /// each keep the mapping alive on their own. Caller must ensure the region /// is zero-initialized before the first side is constructed, and that /// exactly one `Side::A` and one `Side::B` are created per region. - pub fn new(region: R, ring_size: u64, side: Side) -> Self { + pub fn new( + region: R, + ring_size: u64, + side: Side, + read_available_doorbell: D, + write_available_doorbell: D, + ) -> Self { assert!(region.len() >= Self::required_size(ring_size)); assert!( ring_size.is_multiple_of(core::mem::align_of::() as u64), @@ -82,7 +92,12 @@ impl BidirectionalPipe { let reader = RingConsumer::new(region, reader_header, unsafe { RingData::new(reader_data, ring_size) }); - Self { writer, reader } + Self { + writer, + reader, + read_available_doorbell, + write_available_doorbell, + } } /// Consume the pipe and split it into independent, owned read and write @@ -91,13 +106,20 @@ impl BidirectionalPipe { /// Each end already owns its own clone of the region handle, so the two can /// be moved to separate threads / async tasks and each independently keeps /// the shared mapping alive. - pub fn split(self) -> (BidirectionalPipeReadEnd, BidirectionalPipeWriteEnd) { + pub fn split( + self, + ) -> ( + BidirectionalPipeReadEnd, + BidirectionalPipeWriteEnd, + ) { ( BidirectionalPipeReadEnd { reader: self.reader, + write_available_doorbell: self.write_available_doorbell, }, BidirectionalPipeWriteEnd { writer: self.writer, + read_available_doorbell: self.read_available_doorbell, }, ) } @@ -128,31 +150,49 @@ impl BidirectionalPipe { } } -impl traits::Read for BidirectionalPipe { +impl traits::Read for BidirectionalPipe { fn read(&mut self, buf: &mut [u8]) -> Result { - self.reader.read(buf) + let res = self.reader.read(buf); + if let Ok(s) = res { + self.write_available_doorbell.ring(); + if s > 0 {} + } + res } } -impl traits::Read for BidirectionalPipeReadEnd { +impl traits::Read for BidirectionalPipeReadEnd { fn read(&mut self, buf: &mut [u8]) -> Result { - self.reader.read(buf) + let res = self.reader.read(buf); + if let Ok(s) = res { + self.write_available_doorbell.ring(); + if s > 0 {} + } + res } } -impl traits::Write for BidirectionalPipe { +impl traits::Write for BidirectionalPipe { fn write(&mut self, buf: &[u8]) -> Result { - self.writer.write(buf) + let res = self.writer.write(buf); + if let Ok(s) = res { + if s > 0 { + self.read_available_doorbell.ring(); + } + } + res } } -impl traits::Write for BidirectionalPipeWriteEnd { +impl traits::Write for BidirectionalPipeWriteEnd { fn write(&mut self, buf: &[u8]) -> Result { self.writer.write(buf) } } -impl OwnedSplit for BidirectionalPipe { +impl OwnedSplit + for BidirectionalPipe +{ fn split( self, ) -> ( @@ -164,37 +204,75 @@ impl OwnedSplit for BidirectionalPipe } #[cfg(test)] -mod tests { - use super::*; - use crate::pipe::shared_memory_region::RawSharedMemoryRegion; - use crate::pipe::traits::{Read, Write}; +pub(crate) mod test_utils { + use crate::pipe::DoorBell; + use core::sync::atomic::{AtomicUsize, Ordering}; - #[repr(align(8))] - struct Aligned([u8; N]); + pub struct TestDoorBell { + count: AtomicUsize, + } + + impl DoorBell for TestDoorBell { + fn ring(&self) { + self.count.fetch_add(1, Ordering::Release); + } + } + + impl TestDoorBell { + pub fn new() -> Self { + Self { + count: AtomicUsize::new(0), + } + } + } + #[macro_export] macro_rules! pipe_pair { ($ring:expr, $mem:ident, $a:ident, $b:ident) => { let mut $mem = Aligned( - [0u8; BidirectionalPipe::::required_size($ring as u64) - as usize], + [0u8; BidirectionalPipe::::required_size( + $ring as u64, + ) as usize], ); - // A `RawSharedMemoryRegion` is a `Copy` handle, so each side gets its - // own owned clone pointing at the same backing bytes. let region = unsafe { RawSharedMemoryRegion::from_raw($mem.0.as_mut_ptr(), $mem.0.len() as u64) }; - // `mut` is needed by tests that read/write; split-only tests don't. #[allow(unused_mut)] - let mut $a = BidirectionalPipe::new(region, $ring, Side::A); + let mut $a = BidirectionalPipe::new( + region, + $ring, + Side::A, + TestDoorBell::new(), + TestDoorBell::new(), + ); #[allow(unused_mut)] - let mut $b = BidirectionalPipe::new(region, $ring, Side::B); + let mut $b = BidirectionalPipe::new( + region, + $ring, + Side::B, + TestDoorBell::new(), + TestDoorBell::new(), + ); }; } + pub(crate) use pipe_pair; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::pipe::traits::{Read, Write}; + use crate::pipe::{RawSharedMemoryRegion, RingConsumer, RingProducer}; + use test_utils::{pipe_pair, TestDoorBell}; + + #[repr(align(8))] + struct Aligned([u8; N]); + #[test] fn required_size_matches_layout() { assert_eq!( - BidirectionalPipe::::required_size(64), + BidirectionalPipe::::required_size(64), 2 * (24 + 64) ); } @@ -220,7 +298,7 @@ mod tests { // The whole point of dropping the lifetime: owned, `Send` endpoints // that can move to other threads / async tasks. fn assert_send() {} - assert_send::>(); + assert_send::>(); assert_send::>(); assert_send::>(); } diff --git a/common/src/pipe/traits.rs b/common/src/pipe/traits.rs index 7f83bb5..242c50a 100644 --- a/common/src/pipe/traits.rs +++ b/common/src/pipe/traits.rs @@ -42,3 +42,18 @@ pub trait Write { pub trait OwnedSplit { fn split(self) -> (impl Read + Send + 'static, impl Write + Send + 'static); } + +/// Notify the waiter on newly available event (readable/writable) +/// +/// ring blocks until it's possible to ring +pub trait DoorBell { + fn ring(&self); +} + +/// Wait for an event. +/// +/// DoorBellWaiter on the arca-side is handled by kthreads wfi +#[cfg(feature = "std")] +pub trait DoorBellWaiter { + fn wait(&mut self); +} diff --git a/common/src/sync_stream.rs b/common/src/sync_stream.rs index ec124b6..7cb16d0 100644 --- a/common/src/sync_stream.rs +++ b/common/src/sync_stream.rs @@ -1,16 +1,17 @@ -use crate::pipe::{BidirectionalPipe, PipeError, Read, SharedMemoryRegion, Write}; +use crate::pipe::{BidirectionalPipe, DoorBell, PipeError, Read, SharedMemoryRegion, Write}; #[derive(Debug)] pub enum StreamError { WriteClosed, } -pub struct SyncStream { - pipe: BidirectionalPipe, +pub struct SyncStream { + /// BuddyAllocator offset of the SHM region backing this pipe. + pipe: BidirectionalPipe, } -impl SyncStream { - pub fn from_pipe(pipe: BidirectionalPipe) -> Self { +impl SyncStream { + pub fn from_pipe(pipe: BidirectionalPipe) -> Self { Self { pipe } } @@ -53,7 +54,10 @@ impl SyncStream { } } -fn read_exact(pipe: &mut BidirectionalPipe, buf: &mut [u8]) -> usize { +fn read_exact( + pipe: &mut BidirectionalPipe, + buf: &mut [u8], +) -> usize { let mut filled = 0; while filled < buf.len() { match pipe.read(&mut buf[filled..]) { @@ -72,6 +76,8 @@ fn read_exact(pipe: &mut BidirectionalPipe, buf: &mut #[cfg(test)] mod tests { use super::*; + use crate::pipe::test_utils::pipe_pair; + use crate::pipe::test_utils::TestDoorBell; use crate::pipe::{BidirectionalPipe, RawSharedMemoryRegion, Side}; #[repr(align(8))] @@ -79,17 +85,7 @@ mod tests { macro_rules! stream_pair { ($ring:expr, $mem:ident, $a:ident, $b:ident) => { - let mut $mem = Aligned( - [0u8; BidirectionalPipe::::required_size($ring as u64) - as usize], - ); - // A `RawSharedMemoryRegion` is a `Copy` handle, so each side gets its - // own owned clone pointing at the same backing bytes. - let region = unsafe { - RawSharedMemoryRegion::from_raw($mem.0.as_mut_ptr(), $mem.0.len() as u64) - }; - let pipe_a = BidirectionalPipe::new(region, $ring, Side::A); - let pipe_b = BidirectionalPipe::new(region, $ring, Side::B); + pipe_pair!($ring, $mem, pipe_a, pipe_b); let mut $a = SyncStream::from_pipe(pipe_a); let mut $b = SyncStream::from_pipe(pipe_b); }; diff --git a/kernel/src/doorbell.rs b/kernel/src/doorbell.rs new file mode 100644 index 0000000..fe6ab58 --- /dev/null +++ b/kernel/src/doorbell.rs @@ -0,0 +1,27 @@ +#![allow(unused)] + +use common::{ + pipe::{DoorBell, PipeError}, + BuddyAllocator, +}; + +pub struct VMToHostDoorBell { + addr: *mut u64, + datamatch: u64, +} + +impl VMToHostDoorBell { + fn from_raw_parts(addr: u64, datamatch: u64) -> Self { + let addr: *const u64 = BuddyAllocator.from_offset(addr as usize); + let addr: *mut u64 = addr as *mut u64; + Self { addr, datamatch } + } +} + +impl DoorBell for VMToHostDoorBell { + fn ring(&self) { + unsafe { + core::ptr::write_volatile(self.addr, self.datamatch); + } + } +} diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 7e9c83c..5e054a4 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -37,6 +37,7 @@ pub mod tsc; pub mod types; pub mod vm; +mod doorbell; mod gdt; mod idt; mod interrupts; diff --git a/vmm/src/doorbell.rs b/vmm/src/doorbell.rs new file mode 100644 index 0000000..3c5801b --- /dev/null +++ b/vmm/src/doorbell.rs @@ -0,0 +1,82 @@ +#![allow(unused)] + +use common::{ + pipe::{DoorBell, DoorBellWaiter, PipeError}, + BuddyAllocator, +}; +use kvm_ioctls::{IoEventAddress, VmFd}; +use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK}; + +pub struct HostToVMDoorBell { + fd: EventFd, +} + +impl HostToVMDoorBell { + pub fn new(vm: &VmFd, gsi: u32) -> Self { + let evtfd = EventFd::new(EFD_NONBLOCK).unwrap(); + vm.register_irqfd(&evtfd, gsi) + .expect("Failed to register irqfd"); + Self { fd: evtfd } + } +} + +impl DoorBell for HostToVMDoorBell { + fn ring(&self) { + while self.fd.write(1).is_err() {} + } +} + +pub struct VMToHostDoorBellWaiter { + fd: EventFd, +} + +impl VMToHostDoorBellWaiter { + /// Each eventfd needs to have a unique {addr, datamatch} pair, and it is + /// allowed to have multiple eventfds registered at the same address with + /// different datamatch. The caller needs to guarantee that {addr, datamatch} + /// hasn't been registered before + fn new(vm: &VmFd, addr: &IoEventAddress, datamatch: u64) -> Self { + let evtfd = EventFd::new(EFD_NONBLOCK).unwrap(); + vm.register_ioevent(&evtfd, addr, datamatch) + .expect("Failed to register ioevent"); + Self { fd: evtfd } + } +} + +impl DoorBellWaiter for VMToHostDoorBellWaiter { + fn wait(&mut self) { + while let Err(e) = self.fd.read() {} + } +} + +pub struct VMToHostDoorBell { + addr: IoEventAddress, + datamatch: u64, +} + +impl VMToHostDoorBell { + fn new(addr: IoEventAddress, datamatch: u64) -> Self { + Self { addr, datamatch } + } + + fn into_raw_parts(self) -> (u64, u64) { + let addr = match self.addr { + IoEventAddress::Pio(_) => todo!(), + IoEventAddress::Mmio(addr) => addr, + }; + + let addr = BuddyAllocator.to_offset(addr as usize as *const ()); + + (addr as u64, self.datamatch) + } +} + +pub fn new_vm_to_host_door_bell( + vm: &VmFd, + addr: IoEventAddress, + datamatch: u64, +) -> (VMToHostDoorBell, VMToHostDoorBellWaiter) { + let doorbellwaiter = VMToHostDoorBellWaiter::new(vm, &addr, datamatch); + let doorbell = VMToHostDoorBell::new(addr, datamatch); + (doorbell, doorbellwaiter) +} diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 858e25c..87a739c 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -5,4 +5,5 @@ #![feature(exitcode_exit_method)] #![feature(cstr_display)] +mod doorbell; pub mod runtime;