diff --git a/apps/system/components/DebugConsole/DebugConsole.camkes b/apps/system/components/DebugConsole/DebugConsole.camkes index 1921837..185c487 100644 --- a/apps/system/components/DebugConsole/DebugConsole.camkes +++ b/apps/system/components/DebugConsole/DebugConsole.camkes @@ -27,10 +27,10 @@ component DebugConsole { dataport Buf(0x1000000) cpio_archive; dataport Buf tx_dataport; - uses rust_write_inf uart_write; + maybe uses rust_write_inf uart_write; dataport Buf rx_dataport; - uses rust_read_inf uart_read; + maybe uses rust_read_inf uart_read; provides LoggerInterface logger; uses MemoryInterface memory; diff --git a/apps/system/components/DebugConsole/kata-debug-console/Cargo.toml b/apps/system/components/DebugConsole/kata-debug-console/Cargo.toml index 2643acb..49a7191 100644 --- a/apps/system/components/DebugConsole/kata-debug-console/Cargo.toml +++ b/apps/system/components/DebugConsole/kata-debug-console/Cargo.toml @@ -20,18 +20,28 @@ edition = "2021" description = "Kata OS DebugConsole" [features] -default = [] +default = [ + "autostart_support", + "sparrow_uart_support", +] +sparrow_uart_support = ["kata-uart-client"] +autostart_support = ["default-uart-client"] # Log level is Info unless LOG_DEBUG or LOG_TRACE are specified LOG_DEBUG = [] LOG_TRACE = [] [dependencies] -panic-halt = "0.2.0" +cpio = { git = "https://github.com/rcore-os/cpio" } +# Disable default so we don't pull in CString which requires an allocator +cstr_core = { version = "0.2.3", default-features = false } +cty = "0.2.1" +default-uart-client = { path = "../default-uart-client", optional = true } kata-io = { path = "../kata-io" } kata-os-common = { path = "../../kata-os-common" } kata-shell = { path = "../kata-shell" } -kata-uart-client = { path = "../kata-uart-client" } +kata-uart-client = { path = "../kata-uart-client", optional = true } log = { version = "0.4", features = ["release_max_level_info"] } +panic-halt = "0.2.0" [lib] name = "kata_debug_console" diff --git a/apps/system/components/DebugConsole/kata-debug-console/src/run.rs b/apps/system/components/DebugConsole/kata-debug-console/src/run.rs index 6ecfb4b..4c07952 100644 --- a/apps/system/components/DebugConsole/kata-debug-console/src/run.rs +++ b/apps/system/components/DebugConsole/kata-debug-console/src/run.rs @@ -24,8 +24,12 @@ //! * kata_debug_console main entry point fn run() #![no_std] +#![allow(clippy::missing_safety_doc)] +use core::fmt::Write; use core::slice; +use cpio::CpioNewcReader; +use cstr_core::CStr; use kata_os_common::camkes::Camkes; use log::LevelFilter; @@ -51,14 +55,82 @@ pub unsafe extern "C" fn pre_init() { CAMKES.pre_init(INIT_LOG_LEVEL, &mut HEAP_MEMORY); } -/// Entry point for DebugConsole. Runs the shell with UART IO. +// Returns a trait-compatible Tx based on the selected features. +// NB: must use "return expr;" to avoid confusing the compiler. +fn get_tx() -> impl kata_io::Write { + #[cfg(feature = "sparrow_uart_support")] + return kata_uart_client::Tx::new(); + + #[cfg(not(feature = "sparrow_uart_support"))] + return default_uart_client::Tx::new(); +} + +/// Console logging interface. #[no_mangle] -pub extern "C" fn run() -> ! { +pub unsafe extern "C" fn logger_log(level: u8, msg: *const cstr_core::c_char) { + use log::Level; + let l = match level { + x if x == Level::Error as u8 => Level::Error, + x if x == Level::Warn as u8 => Level::Warn, + x if x == Level::Info as u8 => Level::Info, + x if x == Level::Debug as u8 => Level::Debug, + _ => Level::Trace, + }; + if l <= log::max_level() { + // TODO(sleffler): is the uart driver ok w/ multiple writers? + // TODO(sleffler): fallback to seL4_DebugPutChar? + let output: &mut dyn kata_io::Write = &mut get_tx(); + let _ = writeln!(output, "{}", CStr::from_ptr(msg).to_str().unwrap()); + } +} + +// If the builtins archive includes an "autostart.repl" file it is run +// through the shell with output sent either to the console or /dev/null +// depending on the feature selection. +#[cfg(feature = "autostart_support")] +fn run_autostart_shell(cpio_archive_ref: &[u8]) { + const AUTOSTART_NAME: &str = "autostart.repl"; + + let mut autostart_script: Option<&[u8]> = None; + let reader = CpioNewcReader::new(cpio_archive_ref); + for e in reader { + if e.is_err() { + break; + } + let entry = e.unwrap(); + if entry.name == AUTOSTART_NAME { + autostart_script = Some(entry.data); + break; + } + } + if let Some(script) = autostart_script { + // Rx data comes from the embedded script + // Tx data goes to either the uart or /dev/null + let mut rx = kata_io::BufReader::new(default_uart_client::Rx::new(script)); + kata_shell::repl_eof(&mut get_tx(), &mut rx, cpio_archive_ref); + } +} + +// Runs an interactive shell using the Sparrow UART. +#[cfg(feature = "sparrow_uart_support")] +fn run_sparrow_shell(cpio_archive_ref: &[u8]) -> ! { let mut tx = kata_uart_client::Tx::new(); let mut rx = kata_io::BufReader::new(kata_uart_client::Rx::new()); + kata_shell::repl(&mut tx, &mut rx, cpio_archive_ref); +} + +/// Entry point for DebugConsole. Optionally runs an autostart script +/// after which it runs an interactive shell with UART IO. +#[no_mangle] +pub extern "C" fn run() { let cpio_archive_ref = unsafe { // XXX want begin-end or begin+size instead of a fixed-size block slice::from_raw_parts(cpio_archive, 16777216) }; - kata_shell::repl(&mut tx, &mut rx, cpio_archive_ref); + + #[cfg(feature = "autostart_support")] + run_autostart_shell(cpio_archive_ref); + + #[cfg(feature = "sparrow_uart_support")] + run_sparrow_shell(cpio_archive_ref); } diff --git a/apps/system/components/DebugConsole/kata-shell/Cargo.toml b/apps/system/components/DebugConsole/kata-shell/Cargo.toml index 78ce720..7a873e5 100644 --- a/apps/system/components/DebugConsole/kata-shell/Cargo.toml +++ b/apps/system/components/DebugConsole/kata-shell/Cargo.toml @@ -53,6 +53,7 @@ TEST_UART = [] [dependencies] crc = { version = "1.4.0", default_features = false } cpio = { git = "https://github.com/rcore-os/cpio" } +default-uart-client = { path = "../default-uart-client" } hashbrown = { version = "0.11", features = ["ahash-compile-time-rng"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } kata-io = { path = "../kata-io" } diff --git a/apps/system/components/DebugConsole/kata-shell/src/lib.rs b/apps/system/components/DebugConsole/kata-shell/src/lib.rs index 6163ca5..f60f7cd 100644 --- a/apps/system/components/DebugConsole/kata-shell/src/lib.rs +++ b/apps/system/components/DebugConsole/kata-shell/src/lib.rs @@ -114,8 +114,7 @@ type CmdFn = fn( builtin_cpio: &[u8], ) -> Result<(), CommandError>; -/// Read-eval-print loop for the DebugConsole command line interface. -pub fn repl<T: io::BufRead>(output: &mut dyn io::Write, input: &mut T, builtin_cpio: &[u8]) -> ! { +fn get_cmds() -> HashMap<&'static str, CmdFn> { let mut cmds = HashMap::<&str, CmdFn>::new(); cmds.extend([ ("builtins", builtins_command as CmdFn), @@ -129,6 +128,7 @@ pub fn repl<T: io::BufRead>(output: &mut dyn io::Write, input: &mut T, builtin_c ("mdebug", mdebug_command as CmdFn), ("mstats", mstats_command as CmdFn), ("ps", ps_command as CmdFn), + ("source", source_command as CmdFn), ("start", start_command as CmdFn), ("stop", stop_command as CmdFn), ("uninstall", uninstall_command as CmdFn), @@ -152,35 +152,49 @@ pub fn repl<T: io::BufRead>(output: &mut dyn io::Write, input: &mut T, builtin_c #[cfg(feature = "TEST_UART")] test_uart::add_cmds(&mut cmds); + cmds +} + +pub fn eval<T: io::BufRead>( + cmdline: &str, + cmds: &HashMap<&str, CmdFn>, + output: &mut dyn io::Write, + input: &mut T, + builtin_cpio: &[u8], +) { + let mut args = cmdline.split_ascii_whitespace(); + match args.next() { + Some("?") | Some("help") => { + let mut keys: Vec<&str> = cmds.keys().copied().collect(); + keys.sort(); + for k in keys { + let _ = writeln!(output, "{}", k); + } + } + Some(cmd) => { + let result = cmds.get(cmd).map_or_else( + || Err(CommandError::UnknownCommand), + |func| func(&mut args, input, output, builtin_cpio), + ); + if let Err(e) = result { + let _ = writeln!(output, "{}", e); + }; + } + None => { + let _ = output.write_str("\n"); + } + } +} + +/// Read-eval-print loop for the DebugConsole command line interface. +pub fn repl<T: io::BufRead>(output: &mut dyn io::Write, input: &mut T, builtin_cpio: &[u8]) -> ! { + let cmds = get_cmds(); let mut line_reader = LineReader::new(); loop { const PROMPT: &str = "KATA> "; let _ = output.write_str(PROMPT); match line_reader.read_line(output, input) { - Ok(cmdline) => { - let mut args = cmdline.split_ascii_whitespace(); - match args.next() { - Some("?") | Some("help") => { - let mut keys: Vec<&str> = cmds.keys().copied().collect(); - keys.sort(); - for k in keys { - let _ = writeln!(output, "{}", k); - } - } - Some(cmd) => { - let result = cmds.get(cmd).map_or_else( - || Err(CommandError::UnknownCommand), - |func| func(&mut args, input, output, builtin_cpio), - ); - if let Err(e) = result { - let _ = writeln!(output, "{}", e); - }; - } - None => { - let _ = output.write_str("\n"); - } - } - } + Ok(cmdline) => eval(cmdline, &cmds, output, input, builtin_cpio), Err(e) => { let _ = writeln!(output, "\n{}", e); } @@ -188,6 +202,48 @@ pub fn repl<T: io::BufRead>(output: &mut dyn io::Write, input: &mut T, builtin_c } } +/// Stripped down repl for running automation scripts. Like repl but prints +/// each cmd line and stops at EOF/error. +pub fn repl_eof<T: io::BufRead>(output: &mut dyn io::Write, input: &mut T, builtin_cpio: &[u8]) { + let cmds = get_cmds(); + let mut line_reader = LineReader::new(); + while let Ok(cmdline) = line_reader.read_line(output, input) { + // NB: LineReader echo's input + eval(cmdline, &cmds, output, input, builtin_cpio); + } +} + +/// Implements a "source" command that interprets commands from a file +/// in the built-in cpio archive. +fn source_command( + args: &mut dyn Iterator<Item = &str>, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + builtin_cpio: &[u8], +) -> Result<(), CommandError> { + for script_name in args { + let mut script_data: Option<&[u8]> = None; + for e in CpioNewcReader::new(builtin_cpio) { + if e.is_err() { + writeln!(output, "cpio error")?; + break; // NB: iterator does not terminate on error + } + let entry = e.unwrap(); + if entry.name == script_name { + script_data = Some(entry.data); + break; + } + } + if let Some(data) = script_data { + let mut script_input = kata_io::BufReader::new(default_uart_client::Rx::new(data)); + repl_eof(output, &mut script_input, builtin_cpio); + } else { + writeln!(output, "{}: not found", script_name)?; + } + } + Ok(()) +} + /// Implements a "builtins" command that lists the contents of the built-in cpio archive. fn builtins_command( _args: &mut dyn Iterator<Item = &str>, diff --git a/apps/system/components/DebugConsole/kata-uart-client/Cargo.toml b/apps/system/components/DebugConsole/kata-uart-client/Cargo.toml index 652dfa2..6675038 100644 --- a/apps/system/components/DebugConsole/kata-uart-client/Cargo.toml +++ b/apps/system/components/DebugConsole/kata-uart-client/Cargo.toml @@ -19,8 +19,5 @@ authors = ["Matt Harvey <mattharvey@google.com>"] edition = "2021" [dependencies] -# Disable default so we don't pull in CString which requires an allocator -cstr_core = { version = "0.2.3", default-features = false } cty = "0.2.1" kata-io = { path = "../kata-io" } -log = { version = "0.4", features = ["release_max_level_info"] } diff --git a/apps/system/components/DebugConsole/kata-uart-client/src/lib.rs b/apps/system/components/DebugConsole/kata-uart-client/src/lib.rs index b5b9036..558e4ac 100644 --- a/apps/system/components/DebugConsole/kata-uart-client/src/lib.rs +++ b/apps/system/components/DebugConsole/kata-uart-client/src/lib.rs @@ -14,29 +14,8 @@ #![no_std] -use core::fmt::Write; -use cstr_core::CStr; use kata_io as io; -/// Console logging interface. -#[no_mangle] -#[allow(clippy::missing_safety_doc)] -pub unsafe extern "C" fn logger_log(level: u8, msg: *const cstr_core::c_char) { - use log::Level; - let l = match level { - x if x == Level::Error as u8 => Level::Error, - x if x == Level::Warn as u8 => Level::Warn, - x if x == Level::Info as u8 => Level::Info, - x if x == Level::Debug as u8 => Level::Debug, - _ => Level::Trace, - }; - if l <= log::max_level() { - // TODO(sleffler): is the uart driver ok w/ multiple writers? - let output: &mut dyn io::Write = &mut self::Tx::new(); - let _ = writeln!(output, "{}", CStr::from_ptr(msg).to_str().unwrap()); - } -} - const DATAPORT_SIZE: usize = 4096; pub struct Rx {