Skip to content

Commit

Permalink
kernel: add interface registry / views
Browse files Browse the repository at this point in the history
  • Loading branch information
Qix- committed Jan 15, 2025
1 parent 28143f9 commit 28a1956
Show file tree
Hide file tree
Showing 14 changed files with 661 additions and 94 deletions.
41 changes: 16 additions & 25 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ limine = "0.2.0"
uart_16550 = "0.3.0"
volatile-register = "0.2.2"
buddy_system_allocator = "0.11.0"
stash = { version = "0.1.6", default-features = false }
hashbrown = { version = "0.15.2", default-features = false, features = ["nightly", "inline-more", "allocator-api2"] }

bindgen = { git = "https://github.com/oro-os/dep.rust-bindgen.git" }
syn = { version = "2.0.60", features = ["full", "printing"] }
Expand Down
2 changes: 1 addition & 1 deletion oro-arch-x86_64/src/syscall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use core::arch::{asm, naked_asm};

use oro_kernel::scheduler::{Switch, SystemCallRequest};
use oro_kernel::{interface::SystemCallRequest, scheduler::Switch};
use oro_mem::mapper::AddressSegment;
use oro_sync::Lock;
use oro_sysabi::syscall::Opcode;
Expand Down
2 changes: 1 addition & 1 deletion oro-kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ oro-debug.workspace = true
oro-sync.workspace = true
oro-sysabi.workspace = true

stash = { workspace = true, default-features = false }
hashbrown.workspace = true

[lints]
workspace = true
20 changes: 20 additions & 0 deletions oro-kernel/src/id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//! Globally unique identifier allocator.
use core::sync::atomic::{AtomicU64, Ordering};

/// Static, system-wide monotonically increasing resource counter.
///
/// Used for a variety of resources; all resources in the system are guaranteed
/// to have a unique ID, even across resource types.
static COUNTER: AtomicU64 = AtomicU64::new(1);

/// Allocates a new globally unique identifier.
///
/// Guaranteed to be unique across all cores in the system,
/// and monotonically increasing. This function is lock-free.
///
/// Guaranteed never to return 0.
#[inline]
pub fn allocate() -> u64 {
COUNTER.fetch_add(1, Ordering::Relaxed)
}
6 changes: 3 additions & 3 deletions oro-kernel/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl<A: Arch> Instance<A> {
module: &Arc<ReentrantMutex<Module<A>>>,
ring: &Arc<ReentrantMutex<Ring<A>>>,
) -> Result<Arc<ReentrantMutex<Self>>, MapError> {
let id = Kernel::<A>::get().state().allocate_id();
let id = crate::id::allocate();

let mapper = AddressSpace::<A>::new_user_space(Kernel::<A>::get().mapper())
.ok_or(MapError::OutOfMemory)?;
Expand All @@ -84,7 +84,7 @@ impl<A: Arch> Instance<A> {

// Apply the ring mapper overlay to the instance.
AddressSpace::<A>::sysabi()
.apply_user_space_shallow(handle.mapper(), &ring.lock().mapper)?;
.apply_user_space_shallow(handle.mapper(), ring.lock().mapper())?;

// Apply the module's read-only mapper overlay to the instance.
AddressSpace::<A>::user_rodata()
Expand All @@ -99,7 +99,7 @@ impl<A: Arch> Instance<A> {
handle,
}));

ring.lock().instances.push(r.clone());
ring.lock().instances_mut().push(r.clone());
module.lock().instances.push(Arc::downgrade(&r));
Kernel::<A>::get()
.state()
Expand Down
234 changes: 234 additions & 0 deletions oro-kernel/src/interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
//! Types and traits for Oro interfaces, exposed by modules and the kernel.
use core::{
cell::UnsafeCell,
mem::MaybeUninit,
sync::atomic::{
AtomicU8,
Ordering::{Acquire, Relaxed, Release},
},
};

use oro_mem::alloc::sync::Arc;

/// Implements an interface, which is a flat namespace of `(index, flay_keyvals)`.
///
/// Typically, these are interacted with via system calls. Modules (and the kernel)
/// can consume or provide interfaces to the ring in order for applications to
/// interact with them.
///
/// Interfaces can be backed by a number of mechanisms; notably, the kernel implements
/// them as direct interfaces to kernel data structures and machinery. They can also
/// be implemented by userspace applications, which can then be interacted with by
/// system calls or ports (or perhaps other interfaces).
pub trait Interface: Send + Sync {
/// Returns the type ID of the interface. Note that IDs with all high 32 bits
/// cleared are reserved for kernel usage.
fn type_id(&self) -> u64;
/// Handles a system call request to this interface.
///
/// System call handling must be quick and non-blocking. Either it can be
/// serviced immediately, or can be processed "offline", returning a handle
/// that can be polled for completion.
fn handle(&self, request: SystemCallRequest) -> InterfaceResponse;
}

/// Response from an interface after handling a system call.
///
/// When performing a [`Interface::handle`] operation, the interface can either
/// respond immediately, or defer the response to a later time. In the latter case,
/// a pollable handle is returned.
pub enum InterfaceResponse {
/// The interface has handled the request and the response is ready.
Immediate(SystemCallResponse),
/// The interface has received the request, but the response is not ready yet.
Pending(InFlightSystemCallHandle),
}

/// The producer side of an in-flight system call.
///
/// Interfaces should hold onto this handle when they defer a system call response,
/// allowing to submit the response at a later time.
#[repr(transparent)]
pub struct InFlightSystemCall(Arc<InFlightSystemCallInner>);

/// Inner shared state for an in-flight system call.
struct InFlightSystemCallInner {
/// The response is ready. Used as an atomic rather than an `Option`.
state: AtomicU8,
/// The response data; only valid if `state` is [`InFlightState::Ready`].
response: UnsafeCell<MaybeUninit<SystemCallResponse>>,
}

// SAFETY: We strictly control access to the inner state and control exactly
// SAFETY: how it is used, and can guarantee that it is safe to share across
// SAFETY: threads. Any misbehavior is a bug.
unsafe impl Sync for InFlightSystemCallInner {}
// SAFETY: The inner state is valid across threads, despite using an unsafe cell.
unsafe impl Send for InFlightSystemCallInner {}

/// Indicates the state of the in-flight system call.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum InFlightState {
/// Pending
Pending,
/// The response is ready.
Ready,
/// The caller has canceled the system call.
CallerCanceled,
/// The interface has canceled the system call.
InterfaceCanceled,
/// The interface serviced the response, and the caller has
/// already taken the response. This is considered an error.
Finished,
}

/// The caller side of an in-flight system call.
pub struct InFlightSystemCallHandle(Arc<InFlightSystemCallInner>);

impl InFlightSystemCall {
/// Creates a new in-flight system call interface/caller pair.
///
/// The interface should call this, hold on to the first element
/// (the interface side), and return the second element (the caller side)
/// via [`InterfaceResponse::Pending`].
///
/// Once the interface has a response, it can submit it via [`Self::submit`],
/// which consumes this handle and notifies the caller.
#[must_use]
pub fn new() -> (Self, InFlightSystemCallHandle) {
let inner = Arc::new(InFlightSystemCallInner {
state: AtomicU8::new(InFlightState::Pending as u8),
response: UnsafeCell::new(MaybeUninit::uninit()),
});

(Self(inner.clone()), InFlightSystemCallHandle(inner))
}

/// Checks if the caller canceled the system call.
///
/// There's no need to check this before submitting a response; however,
/// if the caller is doing something long-running, it may be useful to
/// check, as if this returns `true`, the response will be dropped immediately.
#[inline]
#[must_use]
pub fn canceled(&self) -> bool {
self.0.state.load(Relaxed) == (InFlightState::CallerCanceled as u8)
}

/// Submits a response to the in-flight system call, consuming the
/// interface side of the handle.
///
/// **Note:** there is no guarantee that the response will be taken by the
/// caller; if the caller canceled the system call, the response will be
/// dropped. Further, the caller may cancel the system call after this
/// method is called, but before the response is taken.
///
/// If the interface's processing of this sytem call request is long-winded,
/// and side effects are not a concern, it may be useful to check if the
/// system call was canceled before submitting the response via [`Self::canceled`].
pub fn submit(self, response: SystemCallResponse) {
// SAFETY: We are the only ones that can write to the response,
// SAFETY: and it can only happen here, once.
unsafe { self.0.response.get().write(MaybeUninit::new(response)) };
// NOTE(qix-): Even if the caller canceled the system call, this is
// NOTE(qix-): innocuous; it's going to get dropped anyway, and we
// NOTE(qix-): save a branch.
self.0.state.store(InFlightState::Ready as u8, Release);
}
}

impl Drop for InFlightSystemCall {
fn drop(&mut self) {
self.0
.state
.compare_exchange(
InFlightState::Pending as u8,
InFlightState::InterfaceCanceled as u8,
Relaxed,
Relaxed,
)
.ok();
}
}

impl InFlightSystemCallHandle {
/// Get the current state of the in-flight system call.
#[inline]
#[must_use]
pub fn state(&self) -> InFlightState {
// SAFETY: We control the value entirely; we can transmute it back.
unsafe { ::core::mem::transmute::<u8, InFlightState>(self.0.state.load(Acquire)) }
}

/// Try to take the response from the in-flight system call.
///
/// - If the response is ready, it is returned as `Ok(Some(response))`.
/// - If the response is not ready, `Ok(None)` is returned.
/// - If the system call was canceled by the interface, `Err(InFlightState::InterfaceCanceled)`
/// is returned.
/// - Attempts to take the response after it has been taken by the caller will return
/// `Err(InFlightState::Finished)`.
#[inline]
pub fn try_take_response(&self) -> Result<Option<SystemCallResponse>, InFlightState> {
match self.state() {
InFlightState::Pending => Ok(None),
InFlightState::CallerCanceled => unreachable!(),
InFlightState::Ready => {
// SAFETY: We are the only ones that can write to the response,
// SAFETY: and it can only happen once.
self.0.state.store(InFlightState::Finished as u8, Release);
Ok(Some(unsafe { self.0.response.get().read().assume_init() }))
}
status @ (InFlightState::Finished | InFlightState::InterfaceCanceled) => Err(status),
}
}
}

impl Drop for InFlightSystemCallHandle {
fn drop(&mut self) {
self.0
.state
.compare_exchange(
InFlightState::Pending as u8,
InFlightState::CallerCanceled as u8,
Relaxed,
Relaxed,
)
.ok();
}
}

/// System call request data.
#[derive(Debug, Clone)]
pub struct SystemCallRequest {
/// The opcode.
pub opcode: oro_sysabi::syscall::Opcode,
/// The first argument.
pub arg1: u64,
/// The second argument.
pub arg2: u64,
/// The third argument.
pub arg3: u64,
/// The fourth argument.
pub arg4: u64,
}

/// System call response data.
#[derive(Debug, Clone, Copy)]
pub struct SystemCallResponse {
/// The error code.
pub error: oro_sysabi::syscall::Error,
/// The return value.
pub ret: u64,
}

/// Response action from the registry after dispatching a system call.
#[derive(Debug)]
pub enum SystemCallAction {
/// The system call has been processed and the thread should be resumed.
RespondImmediate(SystemCallResponse),
/// The system call has been processed or is in-flight and the thread should be paused.
Pause,
}
Loading

0 comments on commit 28a1956

Please sign in to comment.