Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dev_tests/src/ratchet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fn ratchet_globals() -> Result<()> {
("litebox_platform_linux_userland/", 5),
("litebox_platform_lvbs/", 24),
("litebox_platform_multiplex/", 1),
("litebox_platform_windows_userland/", 7),
("litebox_platform_windows_userland/", 8),
("litebox_runner_linux_userland/", 1),
("litebox_runner_lvbs/", 4),
("litebox_runner_snp/", 1),
Expand Down
5 changes: 5 additions & 0 deletions litebox/src/event/wait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ impl<'a, Platform: RawSyncPrimitivesProvider + TimeProvider> WaitContext<'a, Pla
/// evaluating the wait and interrupt conditions so that wakeups are not
/// missed.
fn start_wait(&self) {
self.waker
.0
.platform
.on_interruptible_wait_start(self.waker);
self.waker
.0
.set_state(ThreadState::WAITING, Ordering::SeqCst);
Expand All @@ -384,6 +388,7 @@ impl<'a, Platform: RawSyncPrimitivesProvider + TimeProvider> WaitContext<'a, Pla
self.waker
.0
.set_state(ThreadState::RUNNING_IN_HOST, Ordering::Relaxed);
self.waker.0.platform.on_interruptible_wait_end();
}

/// Checks whether the wait should be interrupted. If not, then performs
Expand Down
74 changes: 74 additions & 0 deletions litebox/src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,61 @@ pub trait ThreadProvider: RawPointerProvider {
/// [`EnterShim`]: crate::shim::EnterShim
/// [`EnterShim::interrupt`]: crate::shim::EnterShim::interrupt
fn interrupt_thread(&self, thread: &Self::ThreadHandle);

/// Runs `f` on the current thread after performing any platform-specific
/// thread registration needed for [`current_thread`](Self::current_thread)
/// and related functionality to work.
///
/// This is intended for test threads that do not go through the normal
/// [`spawn_thread`](Self::spawn_thread) / guest entry path. The platform
/// sets up thread state before calling `f` and tears it down afterward.
///
/// The default implementation simply calls `f()` with no additional setup.
/// Platforms that require explicit thread registration should override this.
#[cfg(debug_assertions)]
fn run_test_thread<R>(f: impl FnOnce() -> R) -> R {
f()
}
}

/// Timer support for proactive signal delivery.
///
/// Platforms that support this should set [`SUPPORTS_TIMER`](Self::SUPPORTS_TIMER)
/// to `true`.
pub trait TimerProvider {
/// The platform-specific timer handle type.
type TimerHandle: TimerHandle;

/// Whether this platform supports [`TimerProvider`] for proactive timer delivery.
const SUPPORTS_TIMER: bool = false;

/// Create a new one-shot timer that delivers `signal` when it fires.
fn create_timer(&self, signal: crate::shim::Signal) -> Self::TimerHandle;
}
Comment on lines +115 to +128
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor:

I'm not sure SUPPORTS_TIMER is the ideal move, a possible alternative is to do

pub trait TimerProvider {
  type TimerHandle: TimerHandle; // Remind users to use `trivial_providers::UnsupportedTimerHandle` if not supported
  fn create_timer(&self, signal: Signal) -> Option<Self::TimerHandle> { None }
}

UnsupportedTimerHandle should be defined as pub enum UnsupportedTimerHandle {} which is equivalent to Rust !, which means that it can never actually be created; that way you are guaranteeing it via the type system.


/// A handle to a platform timer created by [`TimerProvider::create_timer`].
///
/// Dropping the handle **must** cancel any pending timer and ensure that the
/// associated callback will not fire after the drop returns.
Comment on lines +132 to +133
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"associated callback"? It is unclear what this refers to.

Also who is in charge of cancelling? As I understand it, it is saying that the platform cancels pending timers, yes? If so, the writing style should just be Dropping the handle cancels any pending timer or similar, and it is the platform's job to uphold this contract.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re "associated callback", I think (if I understand correctly) this is making a long-range assumption that connects to the signals provider stuff. That should be documented if that's the design you want to go for. Ideally however, we do not use a long-range assumption like that and just literally have a callback registration right here?

pub trait TimerHandle {
/// Arm (or re-arm) the timer to fire after `duration` elapses.
///
/// If the timer is already armed, the previous deadline is replaced.
/// A zero duration cancels the timer without firing.
fn set_timer(&self, duration: core::time::Duration);
}

/// Provider for consuming platform-originating signals.
///
/// Platforms can record signals (e.g., `SIGINT`) and the shim should call
/// [`SignalProvider::take_pending_signals`] to consume them.
pub trait SignalProvider {
/// Atomically take all pending asynchronous signals (e.g., SIGINT and SIGALRM)
/// for the current thread, passing each one to `f`.
///
/// Platforms that support asynchronous signals should override this method.
#[allow(unused_variables, reason = "no-op by default")]
fn take_pending_signals(&self, f: impl FnMut(crate::shim::Signal)) {}
}

/// Punch through any functionality for a particular platform that is not explicitly part of the
Expand Down Expand Up @@ -220,6 +275,25 @@ where
/// A provider of raw mutexes
pub trait RawMutexProvider {
type RawMutex: RawMutex;

/// Called when a thread enters an interruptible wait.
///
/// The passed `waker` should live at least until the thread leaves the interruptible
/// wait (i.e., [`on_interruptible_wait_end`](Self::on_interruptible_wait_end) is called).
/// The platform can use the `waker` to wake up the thread while it is in the interruptible wait.
///
/// This is a no-op by default.
#[allow(unused_variables)]
fn on_interruptible_wait_start(&self, waker: &crate::event::wait::Waker<Self>)
where
Self: crate::sync::RawSyncPrimitivesProvider + Sized,
{
}

/// Called when a thread leaves an interruptible wait.
///
/// This is a no-op by default.
fn on_interruptible_wait_end(&self) {}
}

/// A raw mutex/lock API; expected to roughly match (or even be implemented using) a Linux futex.
Expand Down
12 changes: 11 additions & 1 deletion litebox/src/platform/trivial_providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,18 @@

use super::{
Punchthrough, PunchthroughError, PunchthroughProvider, PunchthroughToken, RawConstPointer,
RawMutPointer,
RawMutPointer, TimerHandle,
};

/// A [`TimerHandle`] stub for platforms that have not yet implemented
/// [`super::TimerProvider`]. All methods panic with `todo!()`.
pub struct StubTimerHandle(());
Comment on lines +14 to +16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioned elsewhere, define this as a pub enum UnsupportedTimerHandle {} instead of supporting its creation.


impl TimerHandle for StubTimerHandle {
fn set_timer(&self, _duration: core::time::Duration) {
todo!("TimerProvider not yet implemented for this platform")
}
}
use zerocopy::{FromBytes, IntoBytes};

/// A trivial provider, useful when no punchthrough is necessary.
Expand Down
105 changes: 105 additions & 0 deletions litebox/src/shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,108 @@ impl Exception {
/// #PF
pub const PAGE_FAULT: Self = Self(14);
}

/// A signal number.
///
/// Signal numbers are 1-based and must be in the range 1–63.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Signal(u32);

impl Signal {
/// SIGINT (signal 2) — interrupt from keyboard (Ctrl+C).
pub const SIGINT: Self = Self(2);
/// SIGALRM (signal 14) — timer signal from `alarm`.
pub const SIGALRM: Self = Self(14);

/// Create a `Signal` from a raw signal number.
///
/// Returns `None` if `signum` is outside the valid range 1–63.
pub const fn from_raw(signum: u32) -> Option<Self> {
if signum >= 1 && signum <= 63 {
Some(Self(signum))
} else {
None
}
}

/// Returns the raw signal number.
pub const fn as_raw(self) -> u32 {
self.0
}
}

/// A set of [`Signal`]s, stored as a 64-bit bitmask.
///
/// Bit `(signum - 1)` is set when signal `signum` is present in the set.
/// Because signal numbers are 1-based and capped at 63, all 63 possible
/// signals fit in a single `u64`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SigSet(u64);

impl SigSet {
/// An empty signal set.
pub const fn empty() -> Self {
Self(0)
}

/// Returns `true` if the set contains no signals.
pub const fn is_empty(&self) -> bool {
self.0 == 0
}

/// Adds `signal` to the set.
pub const fn add(&mut self, signal: Signal) {
self.0 |= 1 << (signal.0 - 1);
}

/// Returns a new set that is `self` with `signal` added.
#[must_use]
pub const fn with(self, signal: Signal) -> Self {
Self(self.0 | (1 << (signal.0 - 1)))
}

/// Removes `signal` from the set.
pub const fn remove(&mut self, signal: Signal) {
self.0 &= !(1 << (signal.0 - 1));
}

/// Returns `true` if the set contains `signal`.
pub const fn contains(&self, signal: Signal) -> bool {
(self.0 & (1 << (signal.0 - 1))) != 0
}

/// Removes and returns the lowest-numbered signal in the set, or `None`
/// if empty.
pub fn pop_lowest(&mut self) -> Option<Signal> {
if self.0 == 0 {
return None;
}
let bit = self.0.trailing_zeros();
self.0 &= !(1u64 << bit);
// bit is 0–62, so bit + 1 is 1–63 — always valid.
Some(Signal(bit + 1))
}

/// Creates a `SigSet` from a raw `u64` bitmask.
pub const fn from_u64(bits: u64) -> Self {
Self(bits)
}

/// Returns the underlying `u64` bitmask.
pub const fn as_u64(&self) -> u64 {
self.0
}
}

impl Iterator for SigSet {
type Item = Signal;

fn next(&mut self) -> Option<Signal> {
self.pop_lowest()
}

fn size_hint(&self) -> (usize, Option<usize>) {
let count = self.0.count_ones() as usize;
(count, Some(count))
}
}
11 changes: 11 additions & 0 deletions litebox_common_linux/src/signal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ impl TryFrom<i32> for Signal {
}
}
}
impl TryFrom<Signal> for litebox::shim::Signal {
type Error = Signal;

fn try_from(value: Signal) -> Result<Self, Self::Error> {
match value {
Signal::SIGINT => Ok(Self::SIGINT),
Signal::SIGALRM => Ok(Self::SIGALRM),
_ => Err(value),
}
}
}

/// The default disposition of a signal.
pub enum SignalDisposition {
Expand Down
8 changes: 8 additions & 0 deletions litebox_platform_linux_kernel/src/host/snp/snp_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,14 @@ impl litebox::platform::ThreadProvider for SnpLinuxKernel {
}
}

impl litebox::platform::TimerProvider for SnpLinuxKernel {
type TimerHandle = litebox::platform::trivial_providers::StubTimerHandle;

fn create_timer(&self, _signal: litebox::shim::Signal) -> Self::TimerHandle {
todo!("TimerProvider not yet implemented for SnpLinuxKernel")
}
}

impl bindings::SnpVmplRequestArgs {
#[inline]
fn new_request(code: u32, size: u32, args: ArgsArray) -> Self {
Expand Down
42 changes: 13 additions & 29 deletions litebox_platform_linux_kernel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ use core::sync::atomic::AtomicU64;
use core::{arch::asm, sync::atomic::AtomicU32};

use litebox::mm::linux::PageRange;
use litebox::platform::RawPointerProvider;
use litebox::platform::page_mgmt::FixedAddressBehavior;
use litebox::platform::{
DebugLogProvider, IPInterfaceProvider, ImmediatelyWokenUp, PageManagementProvider, Provider,
Punchthrough, PunchthroughProvider, PunchthroughToken, RawMutexProvider, TimeProvider,
UnblockedOrTimedOut,
Punchthrough, PunchthroughProvider, PunchthroughToken, RawMutexProvider, SignalProvider,
TimeProvider, UnblockedOrTimedOut,
};
use litebox::platform::{RawMutex as _, RawPointerProvider};
use litebox_common_linux::PunchthroughSyscall;
use litebox_common_linux::errno::Errno;

Expand Down Expand Up @@ -79,6 +79,7 @@ impl<'a, Host: HostInterface> PunchthroughToken for LinuxPunchthroughToken<'a, H
}

impl<Host: HostInterface> Provider for LinuxKernel<Host> {}
impl<Host: HostInterface> SignalProvider for LinuxKernel<Host> {}

// TODO: implement pointer validation to ensure the pointers are in user space.
type UserConstPtr<T> = litebox::platform::common_providers::userspace_pointers::UserConstPtr<
Expand Down Expand Up @@ -180,33 +181,16 @@ impl<Host: HostInterface> RawMutex<Host> {
val: u32,
timeout: Option<core::time::Duration>,
) -> Result<UnblockedOrTimedOut, ImmediatelyWokenUp> {
loop {
// No need to wait if the value already changed.
if self
.underlying_atomic()
.load(core::sync::atomic::Ordering::Relaxed)
!= val
{
return Err(ImmediatelyWokenUp);
match Host::block_or_maybe_timeout(&self.inner, val, timeout) {
Ok(()) | Err(Errno::EINTR) => Ok(UnblockedOrTimedOut::Unblocked),
Err(Errno::EAGAIN) => {
// If the futex value does not match val, then the call fails
// immediately with the error EAGAIN.
Err(ImmediatelyWokenUp)
}

let ret = Host::block_or_maybe_timeout(&self.inner, val, timeout);

match ret {
Ok(()) => {
return Ok(UnblockedOrTimedOut::Unblocked);
}
Err(Errno::EAGAIN | Errno::EINTR) => {
// If the futex value does not match val, then the call fails
// immediately with the error EAGAIN.
return Err(ImmediatelyWokenUp);
}
Err(Errno::ETIMEDOUT) => {
return Ok(UnblockedOrTimedOut::TimedOut);
}
Err(e) => {
todo!("Error: {:?}", e);
}
Err(Errno::ETIMEDOUT) => Ok(UnblockedOrTimedOut::TimedOut),
Err(e) => {
todo!("Error: {:?}", e);
}
}
}
Expand Down
Loading
Loading