blob: ba6dbea82cea34d3704855243f30cd98661c46c8 [file] [edit]
//! Implements a text console over the UART that allows
//! a terminal to inspect and control userspace processes.
//!
//! Protocol
//! --------
//!
//! This module provides a simple text-based console to inspect and control
//! which processes are running. The console has five commands:
//! - 'help' prints the available commands and arguments
//! - 'status' prints the current system status
//! - 'list' lists the current processes with their IDs and running state
//! - 'stop n' stops the process with name n
//! - 'start n' starts the stopped process with name n
//! - 'fault n' forces the process with name n into a fault state
//!
//! ### `list` Command Fields:
//!
//! - `PID`: The identifier for the process. This can change if the process
//! restarts.
//! - `Name`: The process name.
//! - `Quanta`: How many times this process has exceeded its alloted time
//! quanta.
//! - `Syscalls`: The number of system calls the process has made to the kernel.
//! - `Dropped Callbacks`: How many callbacks were dropped for this process
//! because the queue was full.
//! - `Restarts`: How many times this process has crashed and been restarted by
//! the kernel.
//! - `State`: The state the process is in.
//! - `Grants`: The number of grants that have been initialized for the process
//! out of the total number of grants defined by the kernel.
//!
//! Setup
//! -----
//!
//! You need a device that provides the `hil::uart::UART` trait. This code
//! connects a `ProcessConsole` directly up to USART0:
//!
//! ```rust
//! # use kernel::{capabilities, hil, static_init};
//! # use capsules::process_console::ProcessConsole;
//!
//! pub struct Capability;
//! unsafe impl capabilities::ProcessManagementCapability for Capability {}
//!
//! let pconsole = static_init!(
//! ProcessConsole<usart::USART>,
//! ProcessConsole::new(&usart::USART0,
//! 115200,
//! &mut console::WRITE_BUF,
//! &mut console::READ_BUF,
//! &mut console::COMMAND_BUF,
//! kernel,
//! Capability));
//! hil::uart::UART::set_client(&usart::USART0, pconsole);
//!
//! pconsole.initialize();
//! pconsole.start();
//! ```
//!
//! Buffer use and output
//! ---------------------
//! `ProcessConsole` does not use its own write buffer for output:
//! it uses the debug!() buffer, so as not to repeat all of its buffering and
//! to maintain a correct ordering with debug!() calls. The write buffer of
//! `ProcessConsole` is used solely for echoing what someone types.
//!
//! Using ProcessConsole
//! --------------------
//!
//! With this capsule properly added to a board's `main.rs` and that kernel
//! loaded to the board, make sure there is a serial connection to the board.
//! Likely, this just means connecting a USB cable from a computer to the board.
//! Next, establish a serial console connection to the board. An easy way to do
//! this is to run:
//!
//! ```shell
//! $ tockloader listen
//! ```
//!
//! With that console open, you can issue commands. For example, to see all of
//! the processes on the board, use `list`:
//!
//! ```text
//! $ tockloader listen
//! Using "/dev/cu.usbserial-c098e513000c - Hail IoT Module - TockOS"
//!
//! Listening for serial output.
//! ProcessConsole::start
//! Starting process console
//! Initialization complete. Entering main loop
//! Hello World!
//! list
//! PID Name Quanta Syscalls Dropped Callbacks Restarts State Grants
//! 00 blink 0 113 0 0 Yielded 1/12
//! 01 c_hello 0 8 0 0 Yielded 3/12
//! ```
//!
//! To get a general view of the system, use the status command:
//!
//! ```text
//! status
//! Total processes: 2
//! Active processes: 2
//! Timeslice expirations: 0
//! ```
//!
//! and you can control processes with the `start` and `stop` commands:
//!
//! ```text
//! stop blink
//! Process blink stopped
//! ```
use core::cell::Cell;
use core::cmp;
use core::str;
use kernel::capabilities::ProcessManagementCapability;
use kernel::common::cells::TakeCell;
use kernel::debug;
use kernel::hil::uart;
use kernel::introspection::KernelInfo;
use kernel::Kernel;
use kernel::ReturnCode;
// Since writes are character echoes, we do not need more than 4 bytes:
// the longest write is 3 bytes for a backspace (backspace, space, backspace).
pub static mut WRITE_BUF: [u8; 4] = [0; 4];
// Since reads are byte-by-byte, to properly echo what's typed,
// we can use a very small read buffer.
pub static mut READ_BUF: [u8; 4] = [0; 4];
// Commands can be up to 32 bytes long: since commands themselves are 4-5
// characters, limiting arguments to 25 bytes or so seems fine for now.
pub static mut COMMAND_BUF: [u8; 32] = [0; 32];
pub struct ProcessConsole<'a, C: ProcessManagementCapability> {
uart: &'a dyn uart::UartData<'a>,
tx_in_progress: Cell<bool>,
tx_buffer: TakeCell<'static, [u8]>,
rx_in_progress: Cell<bool>,
rx_buffer: TakeCell<'static, [u8]>,
command_buffer: TakeCell<'static, [u8]>,
command_index: Cell<usize>,
/// Flag to mark that the process console is active and has called receive
/// from the underlying UART.
running: Cell<bool>,
/// Internal flag that the process console should parse the command it just
/// received after finishing echoing the last newline character.
execute: Cell<bool>,
kernel: &'static Kernel,
capability: C,
}
impl<'a, C: ProcessManagementCapability> ProcessConsole<'a, C> {
pub fn new(
uart: &'a dyn uart::UartData<'a>,
tx_buffer: &'static mut [u8],
rx_buffer: &'static mut [u8],
cmd_buffer: &'static mut [u8],
kernel: &'static Kernel,
capability: C,
) -> ProcessConsole<'a, C> {
ProcessConsole {
uart: uart,
tx_in_progress: Cell::new(false),
tx_buffer: TakeCell::new(tx_buffer),
rx_in_progress: Cell::new(false),
rx_buffer: TakeCell::new(rx_buffer),
command_buffer: TakeCell::new(cmd_buffer),
command_index: Cell::new(0),
running: Cell::new(false),
execute: Cell::new(false),
kernel: kernel,
capability: capability,
}
}
pub fn start(&self) -> ReturnCode {
if self.running.get() == false {
self.rx_buffer.take().map(|buffer| {
self.rx_in_progress.set(true);
self.uart.receive_buffer(buffer, 1);
self.running.set(true);
//debug!("Starting process console");
});
}
ReturnCode::SUCCESS
}
// Process the command in the command buffer and clear the buffer.
fn read_command(&self) {
self.command_buffer.map(|command| {
let mut terminator = 0;
let len = command.len();
for i in 0..len {
if command[i] == 0 {
terminator = i;
break;
}
}
//debug!("Command: {}-{} {:?}", start, terminator, command);
// A command is valid only if it starts inside the buffer,
// ends before the beginning of the buffer, and ends after
// it starts.
if terminator > 0 {
let cmd_str = str::from_utf8(&command[0..terminator]);
match cmd_str {
Ok(s) => {
let clean_str = s.trim();
if clean_str.starts_with("help") {
debug!("Welcome to the process console.");
debug!("Valid commands are: help status list stop start fault");
} else if clean_str.starts_with("start") {
let argument = clean_str.split_whitespace().nth(1);
argument.map(|name| {
self.kernel.process_each_capability(
&self.capability,
|proc| {
let proc_name = proc.get_process_name();
if proc_name == name {
proc.resume();
debug!("Process {} resumed.", name);
}
},
);
});
} else if clean_str.starts_with("stop") {
let argument = clean_str.split_whitespace().nth(1);
argument.map(|name| {
self.kernel.process_each_capability(
&self.capability,
|proc| {
let proc_name = proc.get_process_name();
if proc_name == name {
proc.stop();
debug!("Process {} stopped", proc_name);
}
},
);
});
} else if clean_str.starts_with("fault") {
let argument = clean_str.split_whitespace().nth(1);
argument.map(|name| {
self.kernel.process_each_capability(
&self.capability,
|proc| {
let proc_name = proc.get_process_name();
if proc_name == name {
proc.set_fault_state();
debug!("Process {} now faulted", proc_name);
}
},
);
});
} else if clean_str.starts_with("list") {
debug!(" PID Name Quanta Syscalls Dropped Callbacks Restarts State Grants");
self.kernel
.process_each_capability(&self.capability, |proc| {
let info: KernelInfo = KernelInfo::new(self.kernel);
let pname = proc.get_process_name();
let appid = proc.appid();
let (grants_used, grants_total) = info.number_app_grant_uses(appid, &self.capability);
debug!(
" {:?}\t{:<20}{:6}{:10}{:19}{:10} {:?}{:5}/{}",
appid,
pname,
proc.debug_timeslice_expiration_count(),
proc.debug_syscall_count(),
proc.debug_dropped_callback_count(),
proc.get_restart_count(),
proc.get_state(),
grants_used,
grants_total
);
});
} else if clean_str.starts_with("status") {
let info: KernelInfo = KernelInfo::new(self.kernel);
debug!(
"Total processes: {}",
info.number_loaded_processes(&self.capability)
);
debug!(
"Active processes: {}",
info.number_active_processes(&self.capability)
);
debug!(
"Timeslice expirations: {}",
info.timeslice_expirations(&self.capability)
);
} else {
debug!("Valid commands are: help status list stop start fault");
}
}
Err(_e) => debug!("Invalid command: {:?}", command),
}
}
});
self.command_buffer.map(|command| {
command[0] = 0;
});
self.command_index.set(0);
}
fn write_byte(&self, byte: u8) -> ReturnCode {
if self.tx_in_progress.get() {
ReturnCode::EBUSY
} else {
self.tx_in_progress.set(true);
self.tx_buffer.take().map(|buffer| {
buffer[0] = byte;
self.uart.transmit_buffer(buffer, 1);
});
ReturnCode::SUCCESS
}
}
fn write_bytes(&self, bytes: &[u8]) -> ReturnCode {
if self.tx_in_progress.get() {
ReturnCode::EBUSY
} else {
self.tx_in_progress.set(true);
self.tx_buffer.take().map(|buffer| {
let len = cmp::min(bytes.len(), buffer.len());
// Copy elements of `bytes` into `buffer`
(&mut buffer[..len]).copy_from_slice(&bytes[..len]);
self.uart.transmit_buffer(buffer, len);
});
ReturnCode::SUCCESS
}
}
}
impl<'a, C: ProcessManagementCapability> uart::TransmitClient for ProcessConsole<'a, C> {
fn transmitted_buffer(&self, buffer: &'static mut [u8], _tx_len: usize, _rcode: ReturnCode) {
self.tx_buffer.replace(buffer);
self.tx_in_progress.set(false);
// Check if we just received and echoed a newline character, and
// therefore need to process the received message.
if self.execute.get() {
self.execute.set(false);
self.read_command();
}
}
}
impl<'a, C: ProcessManagementCapability> uart::ReceiveClient for ProcessConsole<'a, C> {
fn received_buffer(
&self,
read_buf: &'static mut [u8],
rx_len: usize,
_rcode: ReturnCode,
error: uart::Error,
) {
if error == uart::Error::None {
match rx_len {
0 => debug!("ProcessConsole had read of 0 bytes"),
1 => {
self.command_buffer.map(|command| {
let index = self.command_index.get() as usize;
if read_buf[0] == ('\n' as u8) || read_buf[0] == ('\r' as u8) {
self.execute.set(true);
self.write_bytes(&['\r' as u8, '\n' as u8]);
} else if read_buf[0] == ('\x08' as u8) && index > 0 {
// Backspace, echo and remove last byte
// Note echo is '\b \b' to erase
self.write_bytes(&['\x08' as u8, ' ' as u8, '\x08' as u8]);
command[index - 1] = '\0' as u8;
self.command_index.set(index - 1);
} else if index < (command.len() - 1) && read_buf[0] < 128 {
// For some reason, sometimes reads return > 127 but no error,
// which causes utf-8 decoding failure, so check byte is < 128. -pal
// Echo the byte and store it
self.write_byte(read_buf[0]);
command[index] = read_buf[0];
self.command_index.set(index + 1);
command[index + 1] = 0;
}
});
}
_ => debug!(
"ProcessConsole issues reads of 1 byte, but receive_complete was length {}",
rx_len
),
};
}
self.rx_in_progress.set(true);
self.uart.receive_buffer(read_buf, 1);
}
}