diff --git a/Cargo.lock b/Cargo.lock index ff2bf40610..1149755522 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,9 +238,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" dependencies = [ "clap_builder", "clap_derive", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -572,6 +572,7 @@ dependencies = [ "sbi", "semihosting", "shell-words", + "simple-shell", "smallvec", "smoltcp", "take-static", @@ -1133,6 +1134,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "simple-shell" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e977035c0f40249ba591c6b8c03b8b5393779817b8db5f29753acf6c012d234a" + [[package]] name = "siphasher" version = "0.3.11" diff --git a/Cargo.toml b/Cargo.toml index 707ee57fee..02dd3ca2c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ trace = [] vga = [] common-os = [] semihosting = ["dep:semihosting"] +shell = ["simple-shell"] [dependencies] ahash = { version = "0.8", default-features = false } @@ -96,6 +97,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 } [dependencies.smoltcp] version = "0.11" diff --git a/src/arch/aarch64/kernel/serial.rs b/src/arch/aarch64/kernel/serial.rs index 0c13f4db15..4eac1b3ce2 100644 --- a/src/arch/aarch64/kernel/serial.rs +++ b/src/arch/aarch64/kernel/serial.rs @@ -34,6 +34,11 @@ impl SerialPort { } } + #[allow(dead_code)] + pub fn read(&mut self) -> Option { + None + } + pub fn init(&self, _baudrate: u32) { // We don't do anything here (yet). } diff --git a/src/arch/x86_64/kernel/interrupts.rs b/src/arch/x86_64/kernel/interrupts.rs index 7bd3b15de6..ed310a97bf 100644 --- a/src/arch/x86_64/kernel/interrupts.rs +++ b/src/arch/x86_64/kernel/interrupts.rs @@ -322,17 +322,17 @@ impl IrqStatistics { } pub(crate) fn print_statistics() { - info!("Number of interrupts"); + println!("Number of interrupts"); for (core_id, irg_statistics) in IRQ_COUNTERS.lock().iter() { for (i, counter) in irg_statistics.counters.iter().enumerate() { let counter = counter.load(Ordering::Relaxed); if counter > 0 { match get_irq_name(i.try_into().unwrap()) { Some(name) => { - info!("[{core_id}][{name}]: {counter}"); + println!("[{core_id}][{name}]: {counter}"); } _ => { - info!("[{core_id}][{i}]: {counter}"); + println!("[{core_id}][{i}]: {counter}"); } } } diff --git a/src/arch/x86_64/kernel/mod.rs b/src/arch/x86_64/kernel/mod.rs index 782d2a9ea5..4dd227a350 100644 --- a/src/arch/x86_64/kernel/mod.rs +++ b/src/arch/x86_64/kernel/mod.rs @@ -52,7 +52,7 @@ pub fn raw_boot_info() -> &'static RawBootInfo { } /// Serial port to print kernel messages -static COM1: InterruptSpinMutex> = InterruptSpinMutex::new(None); +pub(crate) static COM1: InterruptSpinMutex> = InterruptSpinMutex::new(None); pub fn get_ram_address() -> PhysAddr { PhysAddr(boot_info().hardware_info.phys_addr_range.start) diff --git a/src/arch/x86_64/kernel/serial.rs b/src/arch/x86_64/kernel/serial.rs index 5e677c4ccd..53361f7fc3 100644 --- a/src/arch/x86_64/kernel/serial.rs +++ b/src/arch/x86_64/kernel/serial.rs @@ -1,45 +1,79 @@ +#[cfg(feature = "shell")] +use alloc::collections::VecDeque; + use x86_64::instructions::port::Port; +use crate::arch::x86_64::kernel::core_local::increment_irq_counter; use crate::arch::x86_64::kernel::interrupts::{self, IDT}; use crate::arch::x86_64::kernel::{apic, COM1}; -enum Inner { +const SERIAL_IRQ: u8 = 36; + +enum SerialInner { Uart(uart_16550::SerialPort), Uhyve(Port), } -pub struct SerialPort(Inner); +pub struct SerialPort { + inner: SerialInner, + #[cfg(feature = "shell")] + buffer: VecDeque, +} impl SerialPort { pub unsafe fn new(base: u16) -> Self { if crate::env::is_uhyve() { let serial = Port::new(base); - Self(Inner::Uhyve(serial)) + Self { + inner: SerialInner::Uhyve(serial), + #[cfg(feature = "shell")] + buffer: VecDeque::new(), + } } else { let mut serial = unsafe { uart_16550::SerialPort::new(base) }; serial.init(); - Self(Inner::Uart(serial)) + Self { + inner: SerialInner::Uart(serial), + #[cfg(feature = "shell")] + buffer: VecDeque::new(), + } } } - pub fn receive(&mut self) -> u8 { - if let Inner::Uart(s) = &mut self.0 { - s.receive() - } else { - 0 + pub fn buffer_input(&mut self) { + if let SerialInner::Uart(s) = &mut self.inner { + let c = unsafe { char::from_u32_unchecked(s.receive().into()) }; + #[cfg(not(feature = "shell"))] + if !c.is_ascii_control() { + print!("{}", c); + } + #[cfg(feature = "shell")] + self.buffer.push_back(c.try_into().unwrap()); } } + #[allow(dead_code)] + #[cfg(feature = "shell")] + pub fn read(&mut self) -> Option { + self.buffer.pop_front() + } + + #[allow(dead_code)] + #[cfg(not(feature = "shell"))] + pub fn read(&mut self) -> Option { + None + } + pub fn send(&mut self, buf: &[u8]) { - match &mut self.0 { - Inner::Uhyve(s) => { + match &mut self.inner { + SerialInner::Uhyve(s) => { for &data in buf { unsafe { s.write(data); } } } - Inner::Uart(s) => { + SerialInner::Uart(s) => { for &data in buf { s.send(data); } @@ -49,22 +83,18 @@ impl SerialPort { } extern "x86-interrupt" fn serial_interrupt(_stack_frame: crate::interrupts::ExceptionStackFrame) { - let c = unsafe { char::from_u32_unchecked(COM1.lock().as_mut().unwrap().receive().into()) }; - if !c.is_ascii_control() { - print!("{}", c); - } + COM1.lock().as_mut().unwrap().buffer_input(); + increment_irq_counter(SERIAL_IRQ); apic::eoi(); } pub(crate) fn install_serial_interrupt() { - const SERIAL_IRQ: usize = 36; - unsafe { let mut idt = IDT.lock(); - idt[SERIAL_IRQ] + idt[SERIAL_IRQ.into()] .set_handler_fn(serial_interrupt) .set_stack_index(0); } - interrupts::add_irq_name((SERIAL_IRQ - 32).try_into().unwrap(), "COM1"); + interrupts::add_irq_name(SERIAL_IRQ - 32, "COM1"); } diff --git a/src/lib.rs b/src/lib.rs index d1ba286635..cd51074329 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,8 @@ pub mod fs; pub mod io; mod mm; pub mod scheduler; +#[cfg(all(feature = "shell", target_arch = "x86_64"))] +mod shell; mod synch; pub mod syscalls; pub mod time; @@ -283,6 +285,8 @@ extern "C" fn initd(_arg: usize) { syscalls::init(); fs::init(); + #[cfg(all(feature = "shell", target_arch = "x86_64"))] + shell::init(); // Get the application arguments and environment variables. #[cfg(not(test))] diff --git a/src/shell.rs b/src/shell.rs new file mode 100644 index 0000000000..3d59ddfc31 --- /dev/null +++ b/src/shell.rs @@ -0,0 +1,55 @@ +use hermit_sync::Lazy; +use simple_shell::*; + +use crate::arch::kernel::COM1; +use crate::interrupts::print_statistics; + +fn read() -> Option { + COM1.lock().as_mut().map(|s| s.read())? +} + +static mut SHELL: Lazy> = Lazy::new(|| { + let (print, read) = (|s: &str| print!("{}", s), read); + let mut shell = Shell::new(print, read); + + shell.commands.insert( + "help", + ShellCommand { + help: "Print this help message", + func: |_, shell| { + shell.print_help_screen(); + Ok(()) + }, + aliases: &["?", "h"], + }, + ); + shell.commands.insert( + "interrupts", + ShellCommand { + help: "Shows the number of received interrupts", + func: |_, shell| { + print_statistics(); + Ok(()) + }, + aliases: &["i"], + }, + ); + shell.commands.insert( + "shutdown", + ShellCommand { + help: "Shutdown HermitOS", + func: |_, shell| { + crate::__sys_shutdown(0); + Ok(()) + }, + aliases: &["s"], + }, + ); + + shell +}); + +pub(crate) fn init() { + // Also supports async + crate::executor::spawn(unsafe { SHELL.run_async() }); +}