Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: add async interrupt safe synchronization primitives #1099

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

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

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

[dependencies]
hermit-macro = { path = "hermit-macro" }
Expand Down Expand Up @@ -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"
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::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;
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::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