Skip to content

Commit

Permalink
add async interrupt safe synchronization primitives
Browse files Browse the repository at this point in the history
An asynchronous interface has been added to the
interrupt-safe mutex so that interrupt handlers and
asynchronous tasks can be synchronized. The mutex was
demonstrated by using the shell as an example, where
the asynchronous task communicates with the serial
interface and its interrupt handler.
  • Loading branch information
stlankes committed Mar 16, 2024
1 parent b7ce240 commit 34111f4
Show file tree
Hide file tree
Showing 9 changed files with 389 additions and 22 deletions.
8 changes: 1 addition & 7 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ vga = []
common-os = []
nostd = []
semihosting = ["dep:semihosting"]
shell = ["simple-shell"]
shell = []

[dependencies]
ahash = { version = "0.8", default-features = false }
Expand Down Expand Up @@ -98,7 +98,7 @@ zerocopy = { version = "0.7", features = ["derive"] }
build-time = "0.1.3"
async-trait = "0.1.77"
async-lock = { version = "3.3.0", default-features = false }
simple-shell = { version = "0.0.1", optional = true }
interrupts = "0.1"

[dependencies.smoltcp]
version = "0.11"
Expand Down
4 changes: 2 additions & 2 deletions src/arch/x86_64/kernel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ use core::slice;
use core::sync::atomic::{AtomicU32, AtomicU64, 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;
Expand Down Expand Up @@ -52,7 +52,7 @@ pub fn raw_boot_info() -> &'static RawBootInfo {
}

/// Serial port to print kernel messages
pub(crate) static COM1: InterruptSpinMutex<Option<SerialPort>> = InterruptSpinMutex::new(None);
pub(crate) static COM1: AsyncInterruptMutex<Option<SerialPort>> = AsyncInterruptMutex::new(None);

pub fn get_ram_address() -> PhysAddr {
PhysAddr(boot_info().hardware_info.phys_addr_range.start)
Expand Down
11 changes: 11 additions & 0 deletions src/shell/constants.rs
Original file line number Diff line number Diff line change
@@ -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;
21 changes: 10 additions & 11 deletions src/shell.rs → src/shell/mod.rs
Original file line number Diff line number Diff line change
@@ -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<u8> {
COM1.lock().as_mut().map(|s| s.read())?
}
mod constants;
mod shell;
mod writer;

static mut SHELL: Lazy<Shell<'_>> = 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",
Expand All @@ -27,7 +28,7 @@ static mut SHELL: Lazy<Shell<'_>> = Lazy::new(|| {
"interrupts",
ShellCommand {
help: "Shows the number of received interrupts",
func: |_, shell| {
func: |_, _shell| {
print_statistics();
Ok(())
},
Expand All @@ -38,9 +39,8 @@ static mut SHELL: Lazy<Shell<'_>> = Lazy::new(|| {
"shutdown",
ShellCommand {
help: "Shutdown HermitOS",
func: |_, shell| {
func: |_, _shell| {
crate::__sys_shutdown(0);
Ok(())
},
aliases: &["s"],
},
Expand All @@ -50,6 +50,5 @@ static mut SHELL: Lazy<Shell<'_>> = Lazy::new(|| {
});

pub(crate) fn init() {
// Also supports async
crate::executor::spawn(unsafe { SHELL.run_async() });
}
190 changes: 190 additions & 0 deletions src/shell/shell.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
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::<Vec<_>>();

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");
}
}
44 changes: 44 additions & 0 deletions src/shell/writer.rs
Original file line number Diff line number Diff line change
@@ -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};
Loading

0 comments on commit 34111f4

Please sign in to comment.