blob: f84711b4d0cfea19073583ed4404f6fd54349f93 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Cantrip OS command line interface
//!
//! This brief bootstrap of Rust-in-Cantrip prototypes a minimal modular design
//! for the DebugConsole CLI use case.
//!
//! * cantrip_io Read/Write interface (or move to std::, but that requires alloc)
//! * cantrip_uart_client implementation of the cantrip_io interface
//! * cantrip_line_reader
//! * cantrip_shell
//! * cantrip_debug_console main entry point fn run()
#![no_std]
//error[E0658]: dereferencing raw mutable pointers in statics is unstable
#![feature(const_mut_refs)]
use cantrip_os_common::camkes;
use cantrip_os_common::logger;
use core::fmt::Write;
use camkes::*;
use log::LevelFilter;
use log::Metadata;
use log::Record;
use logger::LoggerError;
use logger::LoggerRequest;
use cantrip_io as io;
// NB: this controls filtering log messages from all components because
// they are setup to send all log messges to the console.
#[cfg(feature = "LOG_DEBUG")]
const INIT_LOG_LEVEL: LevelFilter = LevelFilter::Debug;
#[cfg(feature = "LOG_TRACE")]
const INIT_LOG_LEVEL: LevelFilter = LevelFilter::Trace;
#[cfg(not(any(feature = "LOG_DEBUG", feature = "LOG_TRACE")))]
const INIT_LOG_LEVEL: LevelFilter = LevelFilter::Info;
// Generated code...
mod generated {
include!(concat!(env!("SEL4_OUT_DIR"), "/../debug_console/camkes.rs"));
}
use generated::*;
struct DebugConsoleControlThread;
impl CamkesThreadInterface for DebugConsoleControlThread {
fn pre_init() {
const HEAP_SIZE: usize = 12 * 1024;
static mut HEAP_MEMORY: [u8; HEAP_SIZE] = [0; HEAP_SIZE];
unsafe {
CAMKES.pre_init(&mut HEAP_MEMORY);
}
log::set_logger(&LoggerInterfaceThread).unwrap();
log::set_max_level(INIT_LOG_LEVEL);
}
// Entry point for DebugConsole. Optionally runs an autostart script
// after which it runs an interactive shell with UART IO.
fn run() {
#[cfg(feature = "autostart_support")]
run_autostart_shell();
#[cfg(feature = "interactive_shell")]
run_shodan_shell();
}
}
/// Console logging interface.
struct LoggerInterfaceThread;
impl CamkesThreadInterface for LoggerInterfaceThread {
fn run() {
rpc_shared_recv!(logger, logger::MAX_MSG_LEN, LoggerError::Success);
}
}
impl LoggerInterfaceThread {
fn dispatch(
_client_badge: usize,
request_buffer: &[u8],
_reply_buffer: &mut [u8],
) -> Result<(), LoggerError> {
let request = match postcard::from_bytes::<LoggerRequest>(request_buffer) {
Ok(request) => request,
Err(_) => return Err(LoggerError::DeserializeFailed),
};
match request {
LoggerRequest::Log { level, msg } => Self::log_request(level, msg),
}
}
fn log_request(level: u8, msg: &str) -> Result<(), LoggerError> {
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() {
Self::log_msg(msg);
}
Ok(())
}
fn log_msg(msg: &str) {
// TODO(sleffler): safeguard multiple writers
let output: &mut dyn io::Write = &mut get_tx();
let _ = writeln!(output, "{}", msg);
}
}
// Scaffolding for DebugConsole log msgs.
impl log::Log for LoggerInterfaceThread {
fn enabled(&self, metadata: &Metadata) -> bool { metadata.level() <= log::max_level() }
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
// NB: stack is too small for 2K but this should really be in self
const MAX_MSG_LEN: usize = 1024;
use core2::io::{Cursor, Write};
let mut buf = [0u8; MAX_MSG_LEN];
let mut cur = Cursor::new(&mut buf[..]);
write!(&mut cur, "{}::{}", record.target(), record.args()).unwrap_or_else(|_| {
// Too big, indicate overflow with a trailing "...".
cur.set_position((MAX_MSG_LEN - 3) as u64);
cur.write(b"...").expect("write!");
});
// NB: this releases the ref on buf held by the Cursor
let pos = cur.position() as usize;
Self::log_msg(core::str::from_utf8(&buf[..pos]).unwrap());
}
}
fn flush(&self) {}
}
/// Tx io trait that uses the kernel if console output is
/// is supported, otherwise discards all writes.
struct Tx {}
impl Tx {
pub fn new() -> Self { Self {} }
}
impl Default for Tx {
fn default() -> Self { Self::new() }
}
impl io::Write for Tx {
#[cfg(not(feature = "CONFIG_PRINTING"))]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { Ok(buf.len() as usize) }
#[cfg(feature = "CONFIG_PRINTING")]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
for &b in buf {
unsafe {
cantrip_os_common::sel4_sys::seL4_DebugPutChar(b);
}
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> { Ok(()) }
}
// Returns a trait-compatible Tx based on the selected features.
// NB: must use "return expr;" to avoid confusing the compiler.
fn get_tx() -> impl io::Write {
#[cfg(feature = "interactive_shell")]
return cantrip_uart_client::Tx::new();
#[cfg(not(feature = "interactive_shell"))]
return Tx::new();
}
// Run any "autostart.repl" file in the eFLASH 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() {
// Rx data comes from the embedded script
// Tx data goes to either the uart or /dev/null
// XXX test if autostart.repl is present
let mut rx = cantrip_io::BufReader::new("source -q autostart.repl\n".as_bytes());
cantrip_shell::repl_eof(&mut get_tx(), &mut rx);
}
// Runs an interactive shell using the Shodan UART.
#[cfg(feature = "interactive_shell")]
fn run_shodan_shell() -> ! {
let mut tx = cantrip_uart_client::Tx::new();
let mut rx = io::BufReader::new(cantrip_uart_client::Rx::new());
cantrip_shell::repl(&mut tx, &mut rx);
}