Skip to content
Closed
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/", 22),
("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
19 changes: 14 additions & 5 deletions litebox/src/event/wait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,12 @@ impl<Platform: RawSyncPrimitivesProvider> WaitState<Platform> {
.0
.set_state(ThreadState::RUNNING_IN_HOST, Ordering::Relaxed);
}
}

impl<Platform: RawSyncPrimitivesProvider> WaitStateInner<Platform> {
/// Wakes up the thread if it is waiting (but not if it is running in the guest).
fn wake(&self) {
let condvar = &self.condvar;
/// Attempts to wake a thread that is in the WAITING state, then issuing a futex wake.
///
/// Returns `true` if the thread was woken. Returns `false` if the thread was
/// not in the WAITING state.
pub fn try_wake_condvar(condvar: &Platform::RawMutex) {
let v = condvar.underlying_atomic().fetch_update(
Ordering::Release,
Ordering::Relaxed,
Expand All @@ -186,6 +186,13 @@ impl<Platform: RawSyncPrimitivesProvider> WaitStateInner<Platform> {
}
}
}
}

impl<Platform: RawSyncPrimitivesProvider> WaitStateInner<Platform> {
/// Wakes up the thread if it is waiting (but not if it is running in the guest).
fn wake(&self) {
WaitState::<Platform>::try_wake_condvar(&self.condvar);
}

fn state_for_assert(&self) -> ThreadState {
ThreadState(self.condvar.underlying_atomic().load(Ordering::Relaxed))
Expand Down Expand Up @@ -374,6 +381,7 @@ 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.condvar.on_wait_start();
self.waker
.0
.set_state(ThreadState::WAITING, Ordering::SeqCst);
Expand All @@ -384,6 +392,7 @@ impl<'a, Platform: RawSyncPrimitivesProvider + TimeProvider> WaitContext<'a, Pla
self.waker
.0
.set_state(ThreadState::RUNNING_IN_HOST, Ordering::Relaxed);
self.waker.0.condvar.on_wait_end();
}

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

/// Similar to [`interrupt_thread`], but allows for a `delay` before the interrupt
/// takes effect.
///
/// If `thread` is `Some`, the interrupt targets that specific thread.
/// Otherwise, the platform chooses the target.
///
/// Platforms that support this operation should override this and set
/// [`SUPPORTS_SCHEDULE_INTERRUPT`](Self::SUPPORTS_SCHEDULE_INTERRUPT)
/// to `true`.
///
/// [`interrupt_thread`]: Self::interrupt_thread
#[allow(unused_variables, reason = "no-op by default")]
fn schedule_interrupt(&self, thread: Option<&Self::ThreadHandle>, delay: core::time::Duration) {
}

/// Whether this platform implements [`schedule_interrupt`](Self::schedule_interrupt).
const SUPPORTS_SCHEDULE_INTERRUPT: bool = false;

/// 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.
fn run_test_thread<R>(f: impl FnOnce() -> R) -> R {
f()
}
}

/// 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 @@ -250,6 +295,22 @@ pub trait RawMutex: Send + Sync + 'static {
self.wake_many(i32::MAX as usize)
}

/// Called when a thread enters an interruptible wait on this mutex.
///
/// Platforms can use this to store the condvar address in thread-local
/// storage so that signal handlers can wake the thread via
/// [`try_wake_condvar`](crate::event::wait::WaitState::try_wake_condvar).
///
/// This is a no-op by default.
fn on_wait_start(&self) {}

/// Called when a thread leaves an interruptible wait on this mutex.
///
/// Platforms should clear any state set by [`on_wait_start`](Self::on_wait_start).
///
/// This is a no-op by default.
fn on_wait_end(&self) {}

/// If the underlying value is `val`, block until a wake operation wakes us up.
///
/// Importantly, a wake operation does NOT guarantee that the underlying value has changed; it
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
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