Skip to content
Merged
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
8 changes: 4 additions & 4 deletions kernel/src/devices/rs232.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! RS-232 serial port driver

use core::fmt::{Display, Write, Error, Formatter};
use crate::sync::{Once, SpinLock};
use crate::sync::{Once, SpinLockIRQ};
use crate::io::Io;
use crate::i386::pio::Pio;

Expand Down Expand Up @@ -98,7 +98,7 @@ impl Display for SerialAttributes {
/// Initialized on first use.
///
/// Log functions will access the [SerialInternal] it wraps, and send text to it.
static G_SERIAL: Once<SpinLock<SerialInternal<Pio<u8>>>> = Once::new();
static G_SERIAL: Once<SpinLockIRQ<SerialInternal<Pio<u8>>>> = Once::new();

/// A COM output. Wraps the IO ports of this COM, and provides function for writing to it.
struct SerialInternal<T> {
Expand Down Expand Up @@ -168,15 +168,15 @@ impl SerialLogger {
///
/// This function should only be used when panicking.
pub unsafe fn force_unlock(&mut self) {
G_SERIAL.call_once(|| SpinLock::new(SerialInternal::<Pio<u8>>::new(COM1))).force_unlock();
G_SERIAL.call_once(|| SpinLockIRQ::new(SerialInternal::<Pio<u8>>::new(COM1))).force_unlock();
}
}

impl Write for SerialLogger {
/// Writes a string to COM1.
#[cfg(not(test))]
fn write_str(&mut self, s: &str) -> Result<(), ::core::fmt::Error> {
let mut internal = G_SERIAL.call_once(|| SpinLock::new(SerialInternal::<Pio<u8>>::new(COM1))).lock();
let mut internal = G_SERIAL.call_once(|| SpinLockIRQ::new(SerialInternal::<Pio<u8>>::new(COM1))).lock();
internal.send_string(s);
Ok(())
}
Expand Down
48 changes: 44 additions & 4 deletions kernel/src/i386/interrupt_service_routines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use crate::i386::PrivilegeLevel;
use crate::scheduler::{get_current_thread, get_current_process};
use crate::process::{ProcessStruct, ThreadState};
use crate::sync::{SpinLock, SpinLockIRQ};
use core::sync::atomic::Ordering;
use core::sync::atomic::{AtomicU8, Ordering};

use crate::scheduler;
use crate::i386::gdt::GdtIndex;
Expand All @@ -49,6 +49,12 @@ use crate::syscalls::*;
use bit_field::BitArray;
use sunrise_libkern::{nr, SYSCALL_NAMES};

/// Contains the number of interrupts we are currently inside.
///
/// When this is 0, we are not inside an interrupt context.
#[thread_local]
pub static INSIDE_INTERRUPT_COUNT: AtomicU8 = AtomicU8::new(0);

/// Checks if our thread was killed, in which case unschedule ourselves.
///
/// # Note
Expand Down Expand Up @@ -326,7 +332,8 @@ macro_rules! trap_gate_asm {
/// wrapper_rust_fnname: bound_range_exceeded_exception_rust_wrapper, // name for the high-level rust handler this macro will generate.
/// kernel_fault_strategy: panic, // what to do if we were in kernelspace when this interruption happened.
/// user_fault_strategy: panic, // what to do if we were in userspace when this interruption happened, and feature "panic-on-exception" is enabled.
/// handler_strategy: kill // what to for this interrupt otherwise
/// handler_strategy: kill, // what to for this interrupt otherwise
/// interrupt_context: true // OPTIONAL: basically: are IRQs disabled. True by default, false used for syscalls.
///);
/// ```
///
Expand Down Expand Up @@ -517,6 +524,28 @@ macro_rules! generate_trap_gate_handler {
}
};

// handle optional argument interrupt_context.
(
name: $exception_name:literal,
has_errcode: $has_errcode:ident,
wrapper_asm_fnname: $wrapper_asm_fnname:ident,
wrapper_rust_fnname: $wrapper_rust_fnname:ident,
kernel_fault_strategy: $kernel_fault_strategy:ident,
user_fault_strategy: $user_fault_strategy:ident,
handler_strategy: $handler_strategy:ident
) => {
generate_trap_gate_handler!(
name: $exception_name,
has_errcode: $has_errcode,
wrapper_asm_fnname: $wrapper_asm_fnname,
wrapper_rust_fnname: $wrapper_rust_fnname,
kernel_fault_strategy: $kernel_fault_strategy,
user_fault_strategy: $user_fault_strategy,
handler_strategy: $handler_strategy,
interrupt_context: true
);
};

/* The full wrapper */

// The rule called to generate an exception handler.
Expand All @@ -527,7 +556,8 @@ macro_rules! generate_trap_gate_handler {
wrapper_rust_fnname: $wrapper_rust_fnname:ident,
kernel_fault_strategy: $kernel_fault_strategy:ident,
user_fault_strategy: $user_fault_strategy:ident,
handler_strategy: $handler_strategy:ident
handler_strategy: $handler_strategy:ident,
interrupt_context: $interrupt_context:literal
) => {

generate_trap_gate_handler!(__gen asm_wrapper; $wrapper_asm_fnname, $wrapper_rust_fnname, $has_errcode);
Expand All @@ -536,7 +566,12 @@ macro_rules! generate_trap_gate_handler {
extern "C" fn $wrapper_rust_fnname(userspace_context: &mut UserspaceHardwareContext) {

use crate::i386::structures::gdt::SegmentSelector;
use crate::i386::interrupt_service_routines::INSIDE_INTERRUPT_COUNT;
use core::sync::atomic::Ordering;

if $interrupt_context {
let _ = INSIDE_INTERRUPT_COUNT.fetch_add(1, Ordering::SeqCst);
}

if let PrivilegeLevel::Ring0 = SegmentSelector(userspace_context.cs as u16).rpl() {
generate_trap_gate_handler!(__gen kernel_fault; name: $exception_name, userspace_context, errcode: $has_errcode, strategy: $kernel_fault_strategy);
Expand All @@ -555,6 +590,10 @@ macro_rules! generate_trap_gate_handler {
// call the handler
generate_trap_gate_handler!(__gen handler; name: $exception_name, userspace_context, errcode: $has_errcode, strategy: $handler_strategy);

if $interrupt_context {
let _ = INSIDE_INTERRUPT_COUNT.fetch_sub(1, Ordering::SeqCst);
}

// if we're returning to userspace, check we haven't been killed
if let PrivilegeLevel::Ring3 = SegmentSelector(userspace_context.cs as u16).rpl() {
check_thread_killed();
Expand Down Expand Up @@ -787,7 +826,8 @@ generate_trap_gate_handler!(name: "Syscall Interrupt",
wrapper_rust_fnname: syscall_interrupt_rust_wrapper,
kernel_fault_strategy: panic, // you aren't expected to syscall from the kernel
user_fault_strategy: ignore, // don't worry it's fine ;)
handler_strategy: syscall_interrupt_dispatcher
handler_strategy: syscall_interrupt_dispatcher,
interrupt_context: false
);

impl UserspaceHardwareContext {
Expand Down
7 changes: 3 additions & 4 deletions kernel/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ pub struct ThreadStruct {
/// Userspace hardware context of this thread.
///
/// Registers are backed up every time we enter the kernel via a syscall/exception, for debug purposes.
pub userspace_hwcontext: SpinLock<UserspaceHardwareContext>,
pub userspace_hwcontext: SpinLockIRQ<UserspaceHardwareContext>,

/// Thread state event
///
Expand Down Expand Up @@ -796,7 +796,7 @@ impl ThreadStruct {
process: Arc::clone(belonging_process),
tls_region: tls,
tls_elf: SpinLock::new(VirtualAddress(0x00000000)),
userspace_hwcontext: SpinLock::new(UserspaceHardwareContext::default()),
userspace_hwcontext: SpinLockIRQ::new(UserspaceHardwareContext::default()),
state_event: ThreadStateEvent {
waiting_threads: SpinLock::new(Vec::new())
},
Expand Down Expand Up @@ -894,7 +894,7 @@ impl ThreadStruct {
process: Arc::clone(&process),
tls_region: tls,
tls_elf: SpinLock::new(VirtualAddress(0x00000000)),
userspace_hwcontext: SpinLock::new(UserspaceHardwareContext::default()),
userspace_hwcontext: SpinLockIRQ::new(UserspaceHardwareContext::default()),
state_event: ThreadStateEvent {
waiting_threads: SpinLock::new(Vec::new())
},
Expand Down Expand Up @@ -984,4 +984,3 @@ impl Drop for ThreadStruct {
info!("💀 Dropped a thread : {}", self.process.name)
}
}

16 changes: 13 additions & 3 deletions kernel/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
//! try to access it in regular context. This would be useful on multi-core systems to arbitrate
//! access to a resource when two IRQs are run concurrently.
//! But we highly discourage it, as we see no use case for a resource that would be accessed *only*
//! in interrupt context.
//! in interrupt context. So, our implementation panics when locked in interrupt context.
//!
//! # SpinLockIRQ
//!
Expand Down Expand Up @@ -84,16 +84,26 @@
//! [Mutex]: crate::sync::mutex::Mutex

// export spin::Mutex as less ambiguous "SpinLock".
pub use spin::{Mutex as SpinLock, MutexGuard as SpinLockGuard,
RwLock as SpinRwLock, RwLockReadGuard as SpinRwLockReadGuard, RwLockWriteGuard as SpinRwLockWriteGuard,
pub use spin::{RwLock as SpinRwLock, RwLockReadGuard as SpinRwLockReadGuard, RwLockWriteGuard as SpinRwLockWriteGuard,
Once };

pub mod spin_lock_irq;
pub use self::spin_lock_irq::{SpinLockIRQ, SpinLockIRQGuard};

pub mod spin_lock;
pub use self::spin_lock::{SpinLock, SpinLockGuard};

pub mod mutex;
pub use self::mutex::{Mutex, MutexGuard};

/// Boolean to [spin_lock_irq::permanently_disable_interrupts].
///
/// If this bool is set, all attempts to enable interrupts through a SpinLockIRQ
/// are ignored, leaving the system in an unrecoverable state.
///
/// This is used by kernel panic handlers.
static INTERRUPT_DISARM: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);

/// Abstraction around various kind of locks.
///
/// Some functions need to take a Lock and/or a LockGuard as argument, but don't
Expand Down
171 changes: 171 additions & 0 deletions kernel/src/sync/spin_lock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//! Lock that panics when used in a IRQ context
//!
//! See the [sync] module documentation.
//!
//! [sync]: crate::sync

use core::fmt;

pub use spin::MutexGuard as SpinLockGuard;

/// This type provides mutual exclusion based on spinning.
/// It will panic if used in the context of an interrupt.
///
/// # Description
///
/// The behaviour of these locks is similar to `std::sync::Mutex`. they
/// differ on the following:
///
/// - The lock will not be poisoned in case of failure;
///
/// # Simple examples
///
/// ```
/// use crate::sync::SpinLock;
/// let spin_lock = SpinLock::new(0);
///
/// // Modify the data
/// {
/// let mut data = spin_lock.lock();
/// *data = 2;
/// }
///
/// // Read the data
/// let answer =
/// {
/// let data = spin_lock.lock();
/// *data
/// };
///
/// assert_eq!(answer, 2);
/// ```
///
/// # Thread-safety example
///
/// ```
/// use crate::sync::SpinLock;
/// use std::sync::{Arc, Barrier};
///
/// let numthreads = 1000;
/// let spin_lock = Arc::new(SpinLock::new(0));
///
/// // We use a barrier to ensure the readout happens after all writing
/// let barrier = Arc::new(Barrier::new(numthreads + 1));
///
/// for _ in (0..numthreads)
/// {
/// let my_barrier = barrier.clone();
/// let my_lock = spin_lock.clone();
/// std::thread::spawn(move||
/// {
/// let mut guard = my_lock.lock();
/// *guard += 1;
///
/// // Release the lock to prevent a deadlock
/// drop(guard);
/// my_barrier.wait();
/// });
/// }
///
/// barrier.wait();
///
/// let answer = { *spin_lock.lock() };
/// assert_eq!(answer, numthreads);
/// ```
#[repr(transparent)]
pub struct SpinLock<T: ?Sized>(spin::Mutex<T>);

impl<T> SpinLock<T> {
/// Creates a new spinlock wrapping the supplied data.
///
/// May be used statically:
///
/// ```
/// use crate::sync::SpinLock;
///
/// static SPINLOCK: SpinLock<()> = SpinLock::new(());
///
/// fn demo() {
/// let lock = SPINLOCK.lock();
/// // do something with lock
/// drop(lock);
/// }
/// ```
pub const fn new(data: T) -> SpinLock<T> {
SpinLock(spin::Mutex::new(data))
}

/// Consumes this spinlock, returning the underlying data.
pub fn into_inner(self) -> T {
self.0.into_inner()
}
}

impl<T: ?Sized> SpinLock<T> {
/// Locks the spinlock and returns a guard.
///
/// The returned value may be dereferenced for data access
/// and the lock will be dropped when the guard falls out of scope.
///
/// Panics if called in an interrupt context.
///
/// ```
/// let mylock = crate::sync::SpinLock::new(0);
/// {
/// let mut data = mylock.lock();
/// // The lock is now locked and the data can be accessed
/// *data += 1;
/// // The lock is implicitly dropped
/// }
///
/// ```
pub fn lock(&self) -> SpinLockGuard<T> {
use core::sync::atomic::Ordering;
use crate::cpu_locals::ARE_CPU_LOCALS_INITIALIZED_YET;
use crate::i386::interrupt_service_routines::INSIDE_INTERRUPT_COUNT;
use super::INTERRUPT_DISARM;
if !INTERRUPT_DISARM.load(Ordering::SeqCst) && ARE_CPU_LOCALS_INITIALIZED_YET.load(Ordering::SeqCst) && INSIDE_INTERRUPT_COUNT.load(Ordering::SeqCst) != 0 {
panic!("\
You have attempted to lock a spinlock in interrupt context. \
This is most likely a design flaw. \
See documentation of the sync module.");
}
self.0.lock()
}

/// Force unlock the spinlock.
///
/// This is *extremely* unsafe if the lock is not held by the current
/// thread. However, this can be useful in some instances for exposing the
/// lock to FFI that doesn't know how to deal with RAII.
///
/// If the lock isn't held, this is a no-op.
pub unsafe fn force_unlock(&self) {
self.0.force_unlock()
}

/// Tries to lock the spinlock. If it is already locked, it will return None. Otherwise it returns
/// a guard within Some.
pub fn try_lock(&self) -> Option<SpinLockGuard<T>> {
use core::sync::atomic::Ordering;
if crate::i386::interrupt_service_routines::INSIDE_INTERRUPT_COUNT.load(Ordering::SeqCst) != 0 {
panic!("\
You have attempted to lock a spinlock in interrupt context. \
This is most likely a design flaw. \
See documentation of the sync module.");
}
self.0.try_lock()
}
}

impl<T: ?Sized + fmt::Debug> fmt::Debug for SpinLock<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}

impl<T: ?Sized + Default> Default for SpinLock<T> {
fn default() -> SpinLock<T> {
Self::new(Default::default())
}
}
Loading