DebugConsole: add autostart script support & make uart optional

- make kata-uart-client use dependent on a new "sparrow_uart_support"
  feature; this is needed for headless operation and for platforms
  without a working uart driver
- add a mechanism where an "autostart.repl" file is fetched  from the
  builtins (if present) and passed through the shell; output goes to
  the uart if configured, otherwise the kernel or /dev/nnull
- add a new "source" shell command that interprets the contents of a
  builtins file as console input
- rework the command interpreter to support autostart & source
- move the logging hookup to kata-debug-console so the system builds
  when no uart support is conffigured (need to add fallback to debug
  syscalls in case that works when no driver is present)

Change-Id: I5e6725c93488a48d212dfaca425ede37cbdb72e5
GitOrigin-RevId: 6f360cab71ea103af52e3c68ca240fc16e0f20bb
This commit is contained in:
Sam Leffler 2022-10-15 00:11:44 +00:00
parent d866234653
commit 88841cb7a7
7 changed files with 173 additions and 58 deletions

View File

@ -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;

View File

@ -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"

View File

@ -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);
}

View File

@ -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" }

View File

@ -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>,

View File

@ -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"] }

View File

@ -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 {