diff --git a/Cargo.lock b/Cargo.lock index 2f6e0149c2..400bd056c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -616,6 +616,7 @@ dependencies = [ "hermit-entry", "hermit-macro", "hermit-sync", + "interrupts", "llvm-tools", "lock_api", "log", diff --git a/Cargo.toml b/Cargo.toml index b9ce5d59c8..16f404e704 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ vga = [] common-os = [] nostd = [] semihosting = ["dep:semihosting"] -shell = ["simple-shell"] +shell = [] [dependencies] hermit-macro = { path = "hermit-macro" } @@ -106,6 +106,7 @@ talc = { version = "4" } time = { version = "0.3", default-features = false } volatile = { version = "0.5.4", features = ["unstable"] } zerocopy = { version = "0.7", features = ["derive"] } +interrupts = "0.1" [dependencies.smoltcp] version = "0.11" diff --git a/src/arch/x86_64/kernel/mod.rs b/src/arch/x86_64/kernel/mod.rs index aef2bc5768..e888f42381 100644 --- a/src/arch/x86_64/kernel/mod.rs +++ b/src/arch/x86_64/kernel/mod.rs @@ -5,13 +5,13 @@ use core::ptr; use core::sync::atomic::{AtomicPtr, AtomicU32, Ordering}; use hermit_entry::boot_info::{BootInfo, PlatformInfo, RawBootInfo}; -use hermit_sync::InterruptSpinMutex; use x86::controlregs::{cr0, cr0_write, cr4, Cr0}; use self::serial::SerialPort; use crate::arch::mm::{PhysAddr, VirtAddr}; use crate::arch::x86_64::kernel::core_local::*; use crate::env::{self, is_uhyve}; +use crate::synch::r#async::AsyncInterruptMutex; #[cfg(feature = "acpi")] pub mod acpi; @@ -52,7 +52,7 @@ pub fn raw_boot_info() -> &'static RawBootInfo { } /// Serial port to print kernel messages -pub(crate) static COM1: InterruptSpinMutex> = InterruptSpinMutex::new(None); +pub(crate) static COM1: AsyncInterruptMutex> = AsyncInterruptMutex::new(None); pub fn get_ram_address() -> PhysAddr { PhysAddr(boot_info().hardware_info.phys_addr_range.start) diff --git a/src/shell/constants.rs b/src/shell/constants.rs new file mode 100644 index 0000000000..cca9056139 --- /dev/null +++ b/src/shell/constants.rs @@ -0,0 +1,11 @@ +pub(crate) const CTRL_L: u8 = 12; +pub(crate) const ENTER: u8 = 13; +pub(crate) const BACKSPACE: u8 = 127; +pub(crate) const CTRL_C: u8 = 3; +pub(crate) const ESCAPE: u8 = 27; + +pub(crate) const CSI: u8 = 91; +pub(crate) const CSI_UP: u8 = 65; +pub(crate) const CSI_DOWN: u8 = 66; +pub(crate) const CSI_RIGHT: u8 = 67; +pub(crate) const CSI_LEFT: u8 = 68; diff --git a/src/shell.rs b/src/shell/mod.rs similarity index 70% rename from src/shell.rs rename to src/shell/mod.rs index 52cab9ac17..daea99241d 100644 --- a/src/shell.rs +++ b/src/shell/mod.rs @@ -1,16 +1,17 @@ +/// This shell implementation is derived form +/// https://github.com/explodingcamera/pogos/tree/main/crates/simple-shell use hermit_sync::Lazy; -use simple_shell::*; -use crate::arch::kernel::COM1; +use self::shell::*; use crate::interrupts::print_statistics; -fn read() -> Option { - COM1.lock().as_mut().map(|s| s.read())? -} +mod constants; +mod shell; +mod writer; static mut SHELL: Lazy> = Lazy::new(|| { - let (print, read) = (|s: &str| print!("{}", s), read); - let mut shell = Shell::new(print, read); + let print = |s: &str| print!("{}", s); + let mut shell = Shell::new(print); shell.commands.insert( "help", @@ -27,7 +28,7 @@ static mut SHELL: Lazy> = Lazy::new(|| { "interrupts", ShellCommand { help: "Shows the number of received interrupts", - func: |_, shell| { + func: |_, _shell| { print_statistics(); Ok(()) }, @@ -38,9 +39,8 @@ static mut SHELL: Lazy> = Lazy::new(|| { "shutdown", ShellCommand { help: "Shutdown HermitOS", - func: |_, shell| { + func: |_, _shell| { crate::shutdown(0); - Ok(()) }, aliases: &["s"], }, @@ -50,6 +50,5 @@ static mut SHELL: Lazy> = Lazy::new(|| { }); pub(crate) fn init() { - // Also supports async crate::executor::spawn(unsafe { SHELL.run_async() }); } diff --git a/src/shell/shell.rs b/src/shell/shell.rs new file mode 100644 index 0000000000..d2a25ee5f3 --- /dev/null +++ b/src/shell/shell.rs @@ -0,0 +1,190 @@ +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::future::{self, Future}; +use core::task::{ready, Poll}; + +use super::constants::*; +use super::writer::*; +use crate::arch::kernel::COM1; + +#[derive(Clone, Copy)] +pub struct ShellCommand<'a> { + pub help: &'a str, + pub func: fn(&[&str], &mut Shell<'a>) -> Result<(), &'a str>, + pub aliases: &'a [&'a str], +} + +pub struct Shell<'a> { + pub history: Vec, + pub commands: BTreeMap<&'a str, ShellCommand<'a>>, + pub command: String, + pub cursor: usize, + writer: Writer, +} + +impl<'a> Shell<'a> { + pub fn new(write: fn(&str)) -> Self { + Self { + history: Vec::new(), + commands: BTreeMap::new(), + command: String::new(), + cursor: 0, + writer: Writer::new(write), + } + } + + async fn get_char_async(&self) -> u8 { + future::poll_fn(|cx| { + let mut pinned = core::pin::pin!(COM1.async_lock()); + let mut guard = ready!(pinned.as_mut().poll(cx)); + if let Some(Some(c)) = guard.as_mut().map(|s| s.read()) { + Poll::Ready(c) + } else { + cx.waker().wake_by_ref(); + Poll::Pending + } + }) + .await + } + + #[allow(dead_code)] + pub fn with_commands(mut self, mut commands: BTreeMap<&'a str, ShellCommand<'a>>) -> Self { + self.commands.append(&mut commands); + self + } + + pub async fn run_async(&mut self) { + self.print_prompt(); + + loop { + let c = self.get_char_async().await; + match c { + ESCAPE => self.handle_escape_async().await, + _ => self.match_char(c), + } + } + } + + fn match_char(&mut self, b: u8) { + match b { + CTRL_C => self.process_command("exit".to_string()), + CTRL_L => self.handle_clear(), + ENTER => self.handle_enter(), + BACKSPACE => self.handle_backspace(), + c if (32..=126).contains(&c) => { + self.command.insert(self.cursor, c as char); + self.cursor += 1; + + if self.cursor < self.command.len() { + // Print the remaining text + shell_print!(self.writer, "{}", &self.command[self.cursor - 1..]); + // Move cursor to the correct position + shell_print!(self.writer, "\x1b[{}D", self.command.len() - self.cursor); + } else { + shell_print!(self.writer, "{}", c as char); + } + } + _ => {} + } + } + + fn handle_clear(&mut self) { + self.clear_screen(); + self.print_prompt(); + shell_print!(self.writer, "{}", self.command); + self.cursor = self.command.len(); + } + + fn handle_backspace(&mut self) { + if self.cursor > 0 { + self.command.remove(self.cursor - 1); + self.cursor -= 1; + shell_print!(self.writer, "\x08"); // Move cursor left + shell_print!(self.writer, "{}", &self.command[self.cursor..]); // Print the remaining text + shell_print!(self.writer, " "); // Clear last character + shell_print!( + self.writer, + "\x1b[{}D", + self.command.len() - self.cursor + 1 + ); + // Move cursor to the correct position + } + } + + fn handle_enter(&mut self) { + shell_println!(self.writer, ""); + self.process_command(self.command.clone()); + self.history.push(self.command.clone()); + self.command.clear(); + self.cursor = 0; + self.print_prompt(); + } + + async fn handle_escape_async(&mut self) { + if self.get_char_async().await != CSI { + return; + } + let b = self.get_char_async().await; + self._handle_escape(b); + } + + fn _handle_escape(&mut self, b: u8) { + match b { + CSI_UP => {} + CSI_DOWN => {} + CSI_RIGHT => { + if self.cursor < self.command.len() { + shell_print!(self.writer, "\x1b[1C"); + self.cursor += 1; + } + } + CSI_LEFT => { + if self.cursor > 0 { + shell_print!(self.writer, "\x1b[1D"); + self.cursor -= 1; + } + } + _ => {} + } + } + + fn process_command(&mut self, command: String) { + let mut args = command.split_whitespace(); + let command = args.next().unwrap_or(""); + let args = args.collect::>(); + + for (name, shell_command) in &self.commands { + if shell_command.aliases.contains(&command) || name == &command { + return (shell_command.func)(&args, self).unwrap_or_else(|err| { + shell_println!(self.writer, "{}: {}", command, err); + }); + } + } + + if command.is_empty() { + return; + } + + shell_println!(self.writer, "{}: command not found", command); + } + + pub fn print_help_screen(&mut self) { + shell_println!(self.writer, "available commands:"); + for (name, command) in &self.commands { + shell_print!(self.writer, " {:<12}{:<25}", name, command.help); + if !command.aliases.is_empty() { + shell_print!(self.writer, " aliases: {}", command.aliases.join(", ")); + } + shell_println!(self.writer, ""); + } + } + + pub fn print_prompt(&mut self) { + shell_print!(self.writer, "> "); + } + + pub fn clear_screen(&mut self) { + shell_print!(self.writer, "\x1b[2J\x1b[1;1H"); + } +} diff --git a/src/shell/writer.rs b/src/shell/writer.rs new file mode 100644 index 0000000000..5d1aef41bf --- /dev/null +++ b/src/shell/writer.rs @@ -0,0 +1,44 @@ +use core::fmt::Write; + +pub(crate) struct Writer { + print: fn(&str), +} + +impl core::fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + (self.print)(s); + Ok(()) + } +} + +impl Writer { + pub fn new(print: fn(&str)) -> Self { + Self { print } + } + + pub fn print(&mut self, t: &str) { + self.write_str(t).unwrap(); + } + + pub fn print_args(&mut self, t: core::fmt::Arguments<'_>) { + self.write_fmt(t).unwrap(); + } +} + +macro_rules! shell_print { + ($writer:expr, $fmt:literal$(, $($arg: tt)+)?) => { + $writer.print_args(format_args!($fmt $(,$($arg)+)?)) + } +} + +macro_rules! shell_println { + ($writer:expr, $fmt:literal$(, $($arg: tt)+)?) => {{ + shell_print!($writer, $fmt $(,$($arg)+)?); + $writer.print("\n"); + }}; + () => { + $writer.print("\n"); + } +} + +pub(crate) use {shell_print, shell_println}; diff --git a/src/synch/async.rs b/src/synch/async.rs new file mode 100644 index 0000000000..bd78bace6f --- /dev/null +++ b/src/synch/async.rs @@ -0,0 +1,128 @@ +use core::cell::UnsafeCell; +use core::future; +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut}; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +use crossbeam_utils::Backoff; + +#[derive(Debug)] +pub(crate) struct AsyncInterruptMutex { + lock: AtomicBool, + interrupt_guard: UnsafeCell>, + data: UnsafeCell, +} + +/// A guard to which the protected data can be accessed +/// +/// When the guard falls out of scope it will release the lock. +#[derive(Debug)] +pub(crate) struct AsyncInterruptMutexGuard<'a, T: ?Sized> { + lock: &'a AtomicBool, + interrupt_guard: &'a UnsafeCell>, + data: &'a mut T, +} + +unsafe impl Sync for AsyncInterruptMutex {} +unsafe impl Send for AsyncInterruptMutex {} + +impl AsyncInterruptMutex { + pub const fn new(data: T) -> AsyncInterruptMutex { + Self { + lock: AtomicBool::new(false), + interrupt_guard: UnsafeCell::new(MaybeUninit::uninit()), + data: UnsafeCell::new(data), + } + } + + #[inline] + fn obtain_lock(&self) { + let backoff = Backoff::new(); + let guard = interrupts::disable(); + while self + .lock + .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + while self.is_locked() { + backoff.snooze(); + } + } + // SAFETY: We have exclusive access through locking `inner`. + unsafe { + self.interrupt_guard.get().write(MaybeUninit::new(guard)); + } + } + + #[inline] + fn is_locked(&self) -> bool { + self.lock.load(Ordering::Relaxed) + } + + pub fn lock(&self) -> AsyncInterruptMutexGuard<'_, T> { + self.obtain_lock(); + AsyncInterruptMutexGuard { + lock: &self.lock, + interrupt_guard: &self.interrupt_guard, + data: unsafe { &mut *self.data.get() }, + } + } + + #[inline] + fn obtain_try_lock(&self) -> bool { + let guard = interrupts::disable(); + let ok = self + .lock + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_ok(); + if ok { + // SAFETY: We have exclusive access through locking `inner`. + unsafe { + self.interrupt_guard.get().write(MaybeUninit::new(guard)); + } + } + + ok + } + + pub async fn async_lock(&self) -> AsyncInterruptMutexGuard<'_, T> { + future::poll_fn(|cx| { + if self.obtain_try_lock() { + Poll::Ready(AsyncInterruptMutexGuard { + lock: &self.lock, + interrupt_guard: &self.interrupt_guard, + data: unsafe { &mut *self.data.get() }, + }) + } else { + cx.waker().wake_by_ref(); + Poll::Pending + } + }) + .await + } +} + +impl<'a, T: ?Sized> Deref for AsyncInterruptMutexGuard<'a, T> { + type Target = T; + fn deref<'b>(&'b self) -> &'b T { + &*self.data + } +} + +impl<'a, T: ?Sized> DerefMut for AsyncInterruptMutexGuard<'a, T> { + fn deref_mut<'b>(&'b mut self) -> &'b mut T { + &mut *self.data + } +} + +impl<'a, T: ?Sized> Drop for AsyncInterruptMutexGuard<'a, T> { + /// The dropping of the AsyncInterruptMutexGuard will release the lock it was created from. + fn drop(&mut self) { + // SAFETY: We have exclusive access through locking `inner`. + let guard = unsafe { self.interrupt_guard.get().replace(MaybeUninit::uninit()) }; + // SAFETY: `guard` was initialized when locking. + let _guard = unsafe { guard.assume_init() }; + self.lock.store(false, Ordering::Release); + } +} diff --git a/src/synch/mod.rs b/src/synch/mod.rs index a60e68331f..ca7be88716 100644 --- a/src/synch/mod.rs +++ b/src/synch/mod.rs @@ -1,5 +1,6 @@ //! Synchronization primitives +pub(crate) mod r#async; pub mod futex; #[cfg(feature = "newlib")] pub mod recmutex;