diff --git a/apps/system/components/DebugConsole/kata-shell/Cargo.toml b/apps/system/components/DebugConsole/kata-shell/Cargo.toml index 4c6c344..e2b8ff5 100644 --- a/apps/system/components/DebugConsole/kata-shell/Cargo.toml +++ b/apps/system/components/DebugConsole/kata-shell/Cargo.toml @@ -11,13 +11,28 @@ build = "build.rs" sel4-config = { path = "../../kata-os-common/src/sel4-config" } [features] -default = [] +default = [ + "TEST_GLOBAL_ALLOCATOR", + "TEST_MEMORY_MANAGER", + "TEST_ML_COORDINATOR", + "TEST_PANIC", + "TEST_TIMER_SERVICE", +] CONFIG_DEBUG_BUILD = [] CONFIG_KERNEL_MCS = [] +# Commands that are likely not useful +FRINGE_CMDS = [] +# Runtime tests for various services (please keep sorted) +TEST_GLOBAL_ALLOCATOR = [] +TEST_MEMORY_MANAGER = [] +TEST_ML_COORDINATOR = [] +TEST_PANIC = [] +TEST_TIMER_SERVICE = [] [dependencies] crc = { version = "1.4.0", default_features = false } cpio = { git = "https://github.com/rcore-os/cpio" } +hashbrown = { version = "0.11", features = ["ahash-compile-time-rng"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } kata-io = { path = "../kata-io" } kata-line-reader = { path = "../kata-line-reader" } diff --git a/apps/system/components/DebugConsole/kata-shell/src/fringe_cmds.rs b/apps/system/components/DebugConsole/kata-shell/src/fringe_cmds.rs new file mode 100644 index 0000000..c37d5a2 --- /dev/null +++ b/apps/system/components/DebugConsole/kata-shell/src/fringe_cmds.rs @@ -0,0 +1,94 @@ +// Infrequently used shell commands + +extern crate alloc; +use alloc::vec::Vec; +use core::fmt::Write; +use crate::CmdFn; +use crate::CommandError; +use crate::rz; + +use kata_io as io; +use kata_security_interface::kata_security_echo; + +pub fn add_cmds(cmds: &mut HashMap::<&str, CmdFn>) { + cmds.extend([ + ("add", add_command as CmdFn), + ("echo", echo_command as CmdFn), + ("clear", clear_command as CmdFn), + ("rz", rz_command as CmdFn), + ("scecho", scecho_command as CmdFn), + ]); +} + +/// Implements a binary float addition command. +/// +/// This is a toy to demonstrate that the CLI can operate on some very basic +/// dynamic input and that the Rust runtime provides floating point arithmetic +/// on integer-only hardware. It is also a prototype example of "command taking +/// arguments." It should be removed once actually useful system control +/// commands are implemented and done cribbing from it. +fn add_command( + args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + let x_str = args.next().ok_or(CommandError::BadArgs)?; + let x = x_str.parse::()?; + let y_str = args.next().ok_or(CommandError::BadArgs)?; + let y = y_str.parse::()?; + return Ok(writeln!(output, "{}", x + y)?); +} + +/// Implements a command that outputs the ANSI "clear console" sequence. +fn clear_command( + _args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + Ok(output.write_str("\x1b\x63")?) +} + +/// Implements an "echo" command which writes its arguments to output. +fn echo_command( + args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + let value = args.collect::>().join(" "); + Ok(writeln!(output, "{}", &value)?) +} + +/// Implements an "scecho" command that sends arguments to the Security Core's echo service. +fn scecho_command( + args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + let request = args.collect::>().join(" "); + match kata_security_echo(&request) { + Ok(result) => writeln!(output, "{}", result)?, + Err(status) => writeln!(output, "ECHO replied {:?}", status)?, + } + Ok(()) +} + +/// Implements a command to receive a blob using ZMODEM. +fn rz_command( + _args: &mut dyn Iterator, + input: &mut dyn io::BufRead, + mut output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + let upload = rz::rz(input, &mut output)?; + writeln!( + output, + "size: {}, crc32: {}", + upload.len(), + hex::encode(upload.crc32().to_be_bytes()) + )?; + Ok(()) +} diff --git a/apps/system/components/DebugConsole/kata-shell/src/lib.rs b/apps/system/components/DebugConsole/kata-shell/src/lib.rs index 4f4776b..019c962 100644 --- a/apps/system/components/DebugConsole/kata-shell/src/lib.rs +++ b/apps/system/components/DebugConsole/kata-shell/src/lib.rs @@ -1,11 +1,11 @@ #![no_std] extern crate alloc; -use alloc::vec; use alloc::vec::Vec; use core::fmt; use core::fmt::Write; use cpio::CpioNewcReader; +use hashbrown::HashMap; use kata_io as io; use kata_line_reader::LineReader; @@ -18,31 +18,37 @@ use kata_proc_interface::kata_pkg_mgmt_uninstall; use kata_proc_interface::kata_proc_ctrl_get_running_bundles; use kata_proc_interface::kata_proc_ctrl_start; use kata_proc_interface::kata_proc_ctrl_stop; -use kata_security_interface::kata_security_echo; use kata_storage_interface::kata_storage_delete; use kata_storage_interface::kata_storage_read; use kata_storage_interface::kata_storage_write; -use kata_timer_interface::timer_service_completed_timers; -use kata_timer_interface::TimerServiceError; -use kata_timer_interface::timer_service_oneshot; -use kata_timer_interface::timer_service_wait; use sel4_sys::seL4_CNode_Delete; use sel4_sys::seL4_CPtr; -use sel4_sys::seL4_MinSchedContextBits; -use sel4_sys::seL4_ObjectType::*; use sel4_sys::seL4_WordBits; use slot_allocator::KATA_CSPACE_SLOTS; mod rz; +#[cfg(feature = "FRINGE_CMDS")] +mod fringe_cmds; +#[cfg(feature = "TEST_GLOBAL_ALLOCATOR")] +mod test_global_allocator; +#[cfg(feature = "TEST_MEMORY_MANAGER")] +mod test_memory_manager; +#[cfg(feature = "TEST_ML_COORDINATOR")] +mod test_ml_coordinator; +#[cfg(feature = "TEST_PANIC")] +mod test_panic; +#[cfg(feature = "TEST_TIMER_SERVICE")] +mod test_timer_service; + extern "C" { static SELF_CNODE: seL4_CPtr; } /// Error type indicating why a command line is not runnable. -enum CommandError { +pub enum CommandError { UnknownCommand, BadArgs, IO, @@ -92,95 +98,86 @@ impl From for CommandError { } } +type CmdFn = fn( + args: &mut dyn Iterator, + input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + builtin_cpio: &[u8] +) -> Result<(), CommandError>; + /// Read-eval-print loop for the DebugConsole command line interface. pub fn repl( output: &mut dyn io::Write, input: &mut T, builtin_cpio: &[u8], ) -> ! { + let mut cmds = HashMap::<&str, CmdFn>::new(); + cmds.extend([ + ("builtins", builtins_command as CmdFn), + ("bundles", bundles_command as CmdFn), + ("kvdelete", kvdelete_command as CmdFn), + ("kvread", kvread_command as CmdFn), + ("kvwrite", kvwrite_command as CmdFn), + ("install", install_command as CmdFn), + ("loglevel", loglevel_command as CmdFn), + ("mstats", mstats_command as CmdFn), + ("ps", ps_command as CmdFn), + ("start", start_command as CmdFn), + ("stop", stop_command as CmdFn), + ("uninstall", uninstall_command as CmdFn), + + ("state_mlcoord", state_mlcoord_command as CmdFn), + ]); + #[cfg(feature = "FRINGE_CMDS")] + fringe_cmds::add_cmds(&mut cmds); + #[cfg(feature = "TEST_GLOBAL_ALLOCATOR")] + test_global_allocator::add_cmds(&mut cmds); + #[cfg(feature = "TEST_MEMORY_MANAGER")] + test_memory_manager::add_cmds(&mut cmds); + #[cfg(feature = "TEST_ML_COORDINATOR")] + test_ml_coordinator::add_cmds(&mut cmds); + #[cfg(feature = "TEST_PANIC")] + test_panic::add_cmds(&mut cmds); + #[cfg(feature = "TEST_TIMER_SERVICE")] + test_timer_service::add_cmds(&mut 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) => dispatch_command(cmdline, input, output, builtin_cpio), - Err(e) => { - let _ = writeln!(output, "\n{}", e); + 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"); }, + } } + Err(e) => { let _ = writeln!(output, "\n{}", e); }, } } } -/// Runs a command line. -/// -/// The line is split on whitespace. The first token is the command; the -/// remaining tokens are the arguments. -fn dispatch_command( - cmdline: &str, - input: &mut dyn io::BufRead, - output: &mut dyn io::Write, - builtin_cpio: &[u8], -) { - let mut args = cmdline.split_ascii_whitespace(); - match args.next() { - Some(command) => { - // Statically binds command names to implementations fns, which are - // defined below. - // - // Since even the binding is static, it is fine for each command - // implementation to use its own preferred signature. - let result = match command { - "add" => add_command(&mut args, output), - "builtins" => builtins_command(&mut args, builtin_cpio, output), - "echo" => echo_command(cmdline, output), - "clear" => clear_command(output), - "bundles" => bundles_command(output), - "kvdelete" => kvdelete_command(&mut args, output), - "kvread" => kvread_command(&mut args, output), - "kvwrite" => kvwrite_command(&mut args, output), - "install" => install_command(&mut args, builtin_cpio, input, output), - "loglevel" => loglevel_command(&mut args, output), - "malloc" => malloc_command(&mut args, output), - "mfree" => mfree_command(&mut args, output), - "mstats" => mstats_command(&mut args, output), - "rz" => rz_command(input, output), - "ps" => ps_command(output), - "scecho" => scecho_command(cmdline, output), - "start" => start_command(&mut args, output), - "stop" => stop_command(&mut args, output), - "uninstall" => uninstall_command(&mut args, output), - - "test_alloc" => test_alloc_command(output), - "test_alloc_error" => test_alloc_error_command(output), - "test_bootinfo" => test_bootinfo_command(output), - "test_mlcancel" => test_mlcancel_command(&mut args, output), - "test_mlexecute" => test_mlexecute_command(&mut args, output), - "test_mlperiodic" => test_mlperiodic_command(&mut args, output), - "test_obj_alloc" => test_obj_alloc_command(output), - "test_panic" => test_panic_command(), - "test_timer_async" => test_timer_async_command(&mut args, output), - "test_timer_blocking" => test_timer_blocking_command(&mut args, output), - "test_timer_completed" => test_timer_completed_command(output), - - "state_mlcoord" => state_mlcoord_command(), - - _ => Err(CommandError::UnknownCommand), - }; - if let Err(e) = result { - let _ = writeln!(output, "{}", e); - }; - } - None => { - let _ = output.write_str("\n"); - } - }; -} - /// Implements a "builtins" command that lists the contents of the built-in cpio archive. fn builtins_command( _args: &mut dyn Iterator, - builtin_cpio: &[u8], + _input: &mut dyn io::BufRead, output: &mut dyn io::Write, + builtin_cpio: &[u8], ) -> Result<(), CommandError> { for e in CpioNewcReader::new(builtin_cpio) { if e.is_err() { @@ -193,34 +190,12 @@ fn builtins_command( Ok(()) } -/// Implements an "echo" command which writes its arguments to output. -fn echo_command(cmdline: &str, output: &mut dyn io::Write) -> Result<(), CommandError> { - const COMMAND_LENGTH: usize = 5; // "echo " - if cmdline.len() < COMMAND_LENGTH { - Ok(()) - } else { - Ok(writeln!( - output, - "{}", - &cmdline[COMMAND_LENGTH..cmdline.len()] - )?) - } -} - -/// Implements an "scecho" command that sends arguments to the Security Core's echo service. -fn scecho_command(cmdline: &str, output: &mut dyn io::Write) -> Result<(), CommandError> { - let (_, request) = cmdline.split_at(7); // 'scecho' - match kata_security_echo(request) { - Ok(result) => writeln!(output, "{}", result)?, - Err(status) => writeln!(output, "ECHO replied {:?}", status)?, - } - Ok(()) -} - /// Implements a command to configure the max log level for the DebugConsole. fn loglevel_command( args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, output: &mut dyn io::Write, + _builtin_cpio: &[u8], ) -> Result<(), CommandError> { if let Some(level) = args.next() { use log::LevelFilter; @@ -237,59 +212,30 @@ fn loglevel_command( Ok(writeln!(output, "{}", log::max_level())?) } -/// Implements a command to receive a blob using ZMODEM. -fn rz_command( - input: &mut dyn io::BufRead, - mut output: &mut dyn io::Write, -) -> Result<(), CommandError> { - let upload = rz::rz(input, &mut output)?; - writeln!( - output, - "size: {}, crc32: {}", - upload.len(), - hex::encode(upload.crc32().to_be_bytes()) - )?; - Ok(()) -} - /// Implements a "ps" command that dumps seL4 scheduler state to the console. -#[cfg(feature = "CONFIG_DEBUG_BUILD")] -fn ps_command(_output: &mut dyn io::Write) -> Result<(), CommandError> { +#[allow(unused_variables)] +fn ps_command( + _args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + #[cfg(feature = "CONFIG_DEBUG_BUILD")] unsafe { kata_os_common::sel4_sys::seL4_DebugDumpScheduler(); + Ok(()) } - Ok(()) -} -#[cfg(not(feature = "CONFIG_DEBUG_BUILD"))] -fn ps_command(output: &mut dyn io::Write) -> Result<(), CommandError> { + #[cfg(not(feature = "CONFIG_DEBUG_BUILD"))] Ok(writeln!(output, "Kernel support not configured!")?) } -/// Implements a binary float addition command. -/// -/// This is a toy to demonstrate that the CLI can operate on some very basic -/// dynamic input and that the Rust runtime provides floating point arithmetic -/// on integer-only hardware. It is also a prototype example of "command taking -/// arguments." It should be removed once actually useful system control -/// commands are implemented and done cribbing from it. -fn add_command( - args: &mut dyn Iterator, +fn bundles_command( + _args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, output: &mut dyn io::Write, + _builtin_cpio: &[u8], ) -> Result<(), CommandError> { - let x_str = args.next().ok_or(CommandError::BadArgs)?; - let x = x_str.parse::()?; - let y_str = args.next().ok_or(CommandError::BadArgs)?; - let y = y_str.parse::()?; - return Ok(writeln!(output, "{}", x + y)?); -} - -/// Implements a command that outputs the ANSI "clear console" sequence. -fn clear_command(output: &mut dyn io::Write) -> Result<(), CommandError> { - Ok(output.write_str("\x1b\x63")?) -} - -fn bundles_command(output: &mut dyn io::Write) -> Result<(), CommandError> { match kata_proc_ctrl_get_running_bundles() { Ok(bundle_ids) => { writeln!(output, "{}", bundle_ids.join("\n"))?; @@ -343,9 +289,9 @@ fn collect_from_zmodem( fn install_command( args: &mut dyn Iterator, - builtin_cpio: &[u8], input: &mut dyn io::BufRead, mut output: &mut dyn io::Write, + builtin_cpio: &[u8], ) -> Result<(), CommandError> { fn clear_slot(slot: seL4_CPtr) { unsafe { @@ -399,7 +345,9 @@ fn install_command( fn uninstall_command( args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, output: &mut dyn io::Write, + _builtin_cpio: &[u8], ) -> Result<(), CommandError> { let bundle_id = args.next().ok_or(CommandError::BadArgs)?; match kata_pkg_mgmt_uninstall(bundle_id) { @@ -415,7 +363,9 @@ fn uninstall_command( fn start_command( args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, output: &mut dyn io::Write, + _builtin_cpio: &[u8], ) -> Result<(), CommandError> { let bundle_id = args.next().ok_or(CommandError::BadArgs)?; match kata_proc_ctrl_start(bundle_id) { @@ -431,7 +381,9 @@ fn start_command( fn stop_command( args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, output: &mut dyn io::Write, + _builtin_cpio: &[u8], ) -> Result<(), CommandError> { let bundle_id = args.next().ok_or(CommandError::BadArgs)?; match kata_proc_ctrl_stop(bundle_id) { @@ -447,7 +399,9 @@ fn stop_command( fn kvdelete_command( args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, output: &mut dyn io::Write, + _builtin_cpio: &[u8], ) -> Result<(), CommandError> { let key = args.next().ok_or(CommandError::BadArgs)?; match kata_storage_delete(key) { @@ -463,7 +417,9 @@ fn kvdelete_command( fn kvread_command( args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, output: &mut dyn io::Write, + _builtin_cpio: &[u8], ) -> Result<(), CommandError> { let key = args.next().ok_or(CommandError::BadArgs)?; match kata_storage_read(key) { @@ -479,7 +435,9 @@ fn kvread_command( fn kvwrite_command( args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, output: &mut dyn io::Write, + _builtin_cpio: &[u8], ) -> Result<(), CommandError> { let key = args.next().ok_or(CommandError::BadArgs)?; let value = args.collect::>().join(" "); @@ -494,52 +452,6 @@ fn kvwrite_command( Ok(()) } -fn malloc_command( - args: &mut dyn Iterator, - output: &mut dyn io::Write, -) -> Result<(), CommandError> { - let space_str = args.next().ok_or(CommandError::BadArgs)?; - let space_bytes = space_str.parse::()?; - match kata_frame_alloc(space_bytes) { - Ok(frames) => { - writeln!(output, "Allocated {:?}", frames)?; - } - Err(status) => { - writeln!(output, "malloc failed: {:?}", status)?; - } - } - Ok(()) -} - -fn mfree_command( - args: &mut dyn Iterator, - output: &mut dyn io::Write, -) -> Result<(), CommandError> { - extern "C" { static SELF_CNODE: seL4_CPtr; } - let cptr_str = args.next().ok_or(CommandError::BadArgs)?; - let count_str = args.next().ok_or(CommandError::BadArgs)?; - let frames = ObjDescBundle::new( - unsafe { SELF_CNODE }, - seL4_WordBits as u8, - vec![ - ObjDesc::new( - sel4_sys::seL4_RISCV_4K_Page, - count_str.parse::()?, - cptr_str.parse::()? as seL4_CPtr, - ), - ], - ); - match kata_object_free_toplevel(&frames) { - Ok(_) => { - writeln!(output, "Free'd {:?}", frames)?; - } - Err(status) => { - writeln!(output, "mfree failed: {:?}", status)?; - } - } - Ok(()) -} - fn mstats(output: &mut dyn io::Write, stats: &MemoryManagerStats) -> Result<(), CommandError> { @@ -556,7 +468,9 @@ fn mstats(output: &mut dyn io::Write, stats: &MemoryManagerStats) fn mstats_command( _args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, output: &mut dyn io::Write, + _builtin_cpio: &[u8], ) -> Result<(), CommandError> { match kata_memory_stats() { Ok(stats) => { mstats(output, &stats)?; } @@ -565,275 +479,11 @@ fn mstats_command( Ok(()) } -/// Implements a command that tests facilities that use the global allocator. -/// Shamelessly cribbed from https://os.phil-opp.com/heap-allocation/ -fn test_alloc_command(output: &mut dyn io::Write) -> Result<(), CommandError> { - extern crate alloc; - use alloc::{boxed::Box, rc::Rc}; - - // allocate a number on the heap - let heap_value = Box::new(41); - writeln!(output, "heap_value at {:p}", heap_value).expect("Box failed"); - - // create a dynamically sized vector - let mut vec = Vec::new(); - for i in 0..500 { - vec.push(i); - } - writeln!(output, "vec at {:p}", vec.as_slice()).expect("Vec failed"); - - // create a reference counted vector -> will be freed when count reaches 0 - let reference_counted = Rc::new(vec![1, 2, 3]); - let cloned_reference = reference_counted.clone(); - writeln!( - output, - "current reference count is {}", - Rc::strong_count(&cloned_reference) - ) - .expect("Rc 1 failed"); - core::mem::drop(reference_counted); - writeln!( - output, - "reference count is {} now", - Rc::strong_count(&cloned_reference) - ) - .expect("Rc 2 failed"); - - Ok(writeln!(output, "All tests passed!")?) -} - -/// Implements a command that tests the global allocator error handling. -fn test_alloc_error_command(output: &mut dyn io::Write) -> Result<(), CommandError> { - // Default heap holds 16KB. - let mut vec = Vec::with_capacity(16384); - for i in 0..16348 { - vec.push(i); - } - Ok(writeln!(output, "vec at {:p}", vec.as_slice())?) -} - -fn test_bootinfo_command(output: &mut dyn io::Write) -> Result<(), CommandError> { - use kata_os_common::sel4_sys::seL4_BootInfo; - extern "C" { - fn sel4runtime_bootinfo() -> *const seL4_BootInfo; - } - let bootinfo_ref = unsafe { &*sel4runtime_bootinfo() }; - writeln!(output, "{}:{} empty slots {}:{} untyped", - bootinfo_ref.empty.start, bootinfo_ref.empty.end, - bootinfo_ref.untyped.start, bootinfo_ref.untyped.end)?; - - // NB: seL4_DebugCapIdentify is only available in debug builds - #[cfg(feature = "CONFIG_DEBUG_BUILD")] - for ut in bootinfo_ref.untyped.start..bootinfo_ref.untyped.end { - let cap_tag = unsafe { kata_os_common::sel4_sys::seL4_DebugCapIdentify(ut) }; - assert_eq!(cap_tag, 2, - "expected untyped (2), got {} for cap at {}", cap_tag, ut); - } - Ok(()) -} - -/// Implements a command that tests panic handling. -fn test_panic_command() -> Result<(), CommandError> { - panic!("testing"); -} - -/// Implements a command that cancels an ML execution. -fn test_mlcancel_command( - args: &mut dyn Iterator, - output: &mut dyn io::Write, +fn state_mlcoord_command( + _args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + _output: &mut dyn io::Write, + _builtin_cpio: &[u8], ) -> Result<(), CommandError> { - let bundle_id = args.next().ok_or(CommandError::BadArgs)?; - let model_id = args.next().ok_or(CommandError::BadArgs)?; - - if let Err(e) = kata_mlcoord_cancel(bundle_id, model_id) { - writeln!(output, "Cancel {:?} {:?} err: {:?}", bundle_id, model_id, e)?; - } else { - writeln!(output, "Cancelled {:?} {:?}", bundle_id, model_id)?; - } - Ok(()) -} - -/// Implements a command that runs a oneshot ML execution. -fn test_mlexecute_command( - args: &mut dyn Iterator, - output: &mut dyn io::Write, -) -> Result<(), CommandError> { - let bundle_id = args.next().ok_or(CommandError::BadArgs)?; - let model_id = args.next().ok_or(CommandError::BadArgs)?; - - if let Err(e) = kata_mlcoord_oneshot(bundle_id, model_id) { - writeln!(output, "Execute {:?} {:?} err: {:?}", bundle_id, model_id, e)?; - } - - Ok(()) -} - -/// Implements a command that runs a periodic ML execution. -fn test_mlperiodic_command( - args: &mut dyn Iterator, - output: &mut dyn io::Write, -) -> Result<(), CommandError> { - let bundle_id = args.next().ok_or(CommandError::BadArgs)?; - let model_id = args.next().ok_or(CommandError::BadArgs)?; - let rate_str = args.next().ok_or(CommandError::BadArgs)?; - let rate_in_ms = rate_str.parse::()?; - - if let Err(e) = kata_mlcoord_periodic(bundle_id, model_id, rate_in_ms) { - writeln!(output, "Periodic {:?} {:?} err: {:?}", bundle_id, model_id, e)?; - } - - Ok(()) -} - -fn test_obj_alloc_command(output: &mut dyn io::Write) -> Result<(), CommandError> { - let before_stats = kata_memory_stats().expect("before stats"); - mstats(output, &before_stats)?; - - fn check_alloc(output: &mut dyn io::Write, - name: &str, - res: Result) { - match res { - Ok(obj) => { - if let Err(e) = kata_object_free_toplevel(&obj) { - let _ = writeln!(output, "free {} {:?} failed: {:?}", name, obj, e); - } - } - Err(e) => { - let _ = writeln!(output, "alloc {} failed: {:?}", name, e); - } - } - } - - // NB: alloc+free immediately so we don't run out of top-level CNode slots - check_alloc(output, "untyped", kata_untyped_alloc(12)); // NB: 4KB - check_alloc(output, "tcb", kata_tcb_alloc()); - check_alloc(output, "endpoint", kata_endpoint_alloc()); - check_alloc(output, "notification", kata_notification_alloc()); - check_alloc(output, "cnode", kata_cnode_alloc(5)); // NB: 32 slots - check_alloc(output, "frame", kata_frame_alloc(4096)); -// check_alloc(output, "large frame", kata_frame_alloc(1024*1024)); - check_alloc(output, "page table", kata_page_table_alloc()); - - #[cfg(feature = "CONFIG_KERNEL_MCS")] - check_alloc(output, "sched context", - kata_sched_context_alloc(seL4_MinSchedContextBits)); - - #[cfg(feature = "CONFIG_KERNEL_MCS")] - check_alloc(output, "reply", kata_reply_alloc()); - - let after_stats = kata_memory_stats().expect("after stats"); - mstats(output, &after_stats)?; - assert_eq!(before_stats.allocated_bytes, after_stats.allocated_bytes); - assert_eq!(before_stats.free_bytes, after_stats.free_bytes); - - // Batch allocate into a private CNode as we might to build a process. - const CNODE_DEPTH: usize = 7; // 128 slots - let cnode = kata_cnode_alloc(CNODE_DEPTH).unwrap(); // XXX handle error - let objs = ObjDescBundle::new( - cnode.objs[0].cptr, - CNODE_DEPTH as u8, - vec![ - ObjDesc::new(seL4_TCBObject, 1, 0), // 1 tcb - ObjDesc::new(seL4_EndpointObject, 2, 1), // 2 endpoiints - ObjDesc::new(seL4_ReplyObject, 2, 3), // 2 replys - ObjDesc::new(seL4_SchedContextObject, // 1 sched context - seL4_MinSchedContextBits, 5), - ObjDesc::new(seL4_RISCV_4K_Page, 10, 6), // 10 4K pages - ], - ); - match kata_object_alloc(&objs) { - Ok(_) => { - writeln!(output, "Batch alloc ok: {:?}", objs)?; - if let Err(e) = kata_object_free(&objs) { - writeln!(output, "Batch free err: {:?}", e)?; - } - } - Err(e) => { - writeln!(output, "Batch alloc err: {:?} {:?}", objs, e)?; - } - } - if let Err(e) = kata_object_free_toplevel(&cnode) { - writeln!(output, "Cnode free err: {:?} {:?}", cnode, e)?; - } - - // Batch allocate using the newer api that constructs a CNode based - // on the batch of objects specified. - match kata_object_alloc_in_cnode( - vec![ - ObjDesc::new(seL4_TCBObject, 1, 0), // 1 tcb - ObjDesc::new(seL4_EndpointObject, 1, 1), // 1 endpoiints - ObjDesc::new(seL4_ReplyObject, 1, 2), // 1 replys - ObjDesc::new(seL4_SchedContextObject, // 1 sched context - seL4_MinSchedContextBits, 3), - ObjDesc::new(seL4_RISCV_4K_Page, 2, 4), // 2 4K pages - ], - ) { - Ok(objs) => { - writeln!(output, "kata_object_alloc_in_cnode ok: {:?}", objs)?; - if let Err(e) = kata_object_free_in_cnode(&objs) { - writeln!(output, "kata_object_free_in_cnode failed: {:?}", e)?; - } - } - Err(e) => { - writeln!(output, "kata_object_alloc_in_cnode failed: {:?}", e)?; - } - } - - Ok(writeln!(output, "All tests passed!")?) -} - -/// Implements a command that starts a timer, but does not wait on the -/// notification. -fn test_timer_async_command( - args: &mut dyn Iterator, - output: &mut dyn io::Write, -) -> Result<(), CommandError> { - let id_str = args.next().ok_or(CommandError::BadArgs)?; - let id = id_str.parse::()?; - let time_str = args.next().ok_or(CommandError::BadArgs)?; - let time_ms = time_str.parse::()?; - - writeln!(output, "Starting timer {} for {} ms.", id, time_ms)?; - - match timer_service_oneshot(id, time_ms) { - TimerServiceError::TimerOk => (), - _ => return Err(CommandError::BadArgs), - } - - timer_service_oneshot(id, time_ms); - - Ok(()) -} - -/// Implements a command that starts a timer, blocking until the timer has -/// completed. -fn test_timer_blocking_command( - args: &mut dyn Iterator, - output: &mut dyn io::Write, -) -> Result<(), CommandError> { - let time_str = args.next().ok_or(CommandError::BadArgs)?; - let time_ms = time_str.parse::()?; - - writeln!(output, "Blocking {} ms waiting for timer.", time_ms)?; - - // Set timer_id to 0, we don't need to use multiple timers here. - match timer_service_oneshot(0, time_ms) { - TimerServiceError::TimerOk => (), - _ => return Err(CommandError::BadArgs), - } - - timer_service_wait(); - - return Ok(writeln!(output, "Timer completed.")?); -} - -/// Implements a command that checks the completed timers. -fn test_timer_completed_command( - output: &mut dyn io::Write, -) -> Result<(), CommandError> { - return Ok(writeln!(output, "Timers completed: {:#032b}", timer_service_completed_timers())?); -} - -fn state_mlcoord_command() -> Result<(), CommandError> { return Ok(kata_mlcoord_debug_state()); } diff --git a/apps/system/components/DebugConsole/kata-shell/src/test_global_allocator.rs b/apps/system/components/DebugConsole/kata-shell/src/test_global_allocator.rs new file mode 100644 index 0000000..4eaa905 --- /dev/null +++ b/apps/system/components/DebugConsole/kata-shell/src/test_global_allocator.rs @@ -0,0 +1,75 @@ +// Global allocator shell test commands + +extern crate alloc; +use alloc::vec; +use alloc::vec::Vec; +use core::fmt::Write; +use crate::CmdFn; +use crate::CommandError; +use crate::HashMap; + +use kata_io as io; + +pub fn add_cmds(cmds: &mut HashMap::<&str, CmdFn>) { + cmds.extend([ + ("test_alloc", alloc_command as CmdFn), + ("test_alloc_error", alloc_error_command as CmdFn), + ]); +} + +/// Implements a command that tests facilities that use the global allocator. +/// Shamelessly cribbed from https://os.phil-opp.com/heap-allocation/ +fn alloc_command( + _args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + extern crate alloc; + use alloc::{boxed::Box, rc::Rc}; + + // allocate a number on the heap + let heap_value = Box::new(41); + writeln!(output, "heap_value at {:p}", heap_value).expect("Box failed"); + + // create a dynamically sized vector + let mut vec = Vec::new(); + for i in 0..500 { + vec.push(i); + } + writeln!(output, "vec at {:p}", vec.as_slice()).expect("Vec failed"); + + // create a reference counted vector -> will be freed when count reaches 0 + let reference_counted = Rc::new(vec![1, 2, 3]); + let cloned_reference = reference_counted.clone(); + writeln!( + output, + "current reference count is {}", + Rc::strong_count(&cloned_reference) + ) + .expect("Rc 1 failed"); + core::mem::drop(reference_counted); + writeln!( + output, + "reference count is {} now", + Rc::strong_count(&cloned_reference) + ) + .expect("Rc 2 failed"); + + Ok(writeln!(output, "All tests passed!")?) +} + +/// Implements a command that tests the global allocator error handling. +fn alloc_error_command( + _args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + // Default heap holds 16KB. + let mut vec = Vec::with_capacity(16384); + for i in 0..16384 { + vec.push(i); + } + Ok(writeln!(output, "vec at {:p}", vec.as_slice())?) +} diff --git a/apps/system/components/DebugConsole/kata-shell/src/test_memory_manager.rs b/apps/system/components/DebugConsole/kata-shell/src/test_memory_manager.rs new file mode 100644 index 0000000..5dde6ab --- /dev/null +++ b/apps/system/components/DebugConsole/kata-shell/src/test_memory_manager.rs @@ -0,0 +1,204 @@ +// MemoryManager service shell test commands + +extern crate alloc; +use alloc::vec; +use core::fmt::Write; +use crate::CmdFn; +use crate::CommandError; +use crate::HashMap; +use crate::mstats; + +use kata_io as io; +use kata_memory_interface::*; +use kata_os_common::sel4_sys; + +use sel4_sys::seL4_CPtr; +use sel4_sys::seL4_MinSchedContextBits; +use sel4_sys::seL4_ObjectType::*; +use sel4_sys::seL4_WordBits; + +pub fn add_cmds(cmds: &mut HashMap::<&str, CmdFn>) { + cmds.extend([ + ("test_bootinfo", bootinfo_command as CmdFn), + ("test_malloc", malloc_command as CmdFn), + ("test_mfree", mfree_command as CmdFn), + ("test_obj_alloc", obj_alloc_command as CmdFn), + ]); +} + +fn bootinfo_command( + _args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + use kata_os_common::sel4_sys::seL4_BootInfo; + extern "C" { + fn sel4runtime_bootinfo() -> *const seL4_BootInfo; + } + let bootinfo_ref = unsafe { &*sel4runtime_bootinfo() }; + writeln!(output, "{}:{} empty slots {}:{} untyped", + bootinfo_ref.empty.start, bootinfo_ref.empty.end, + bootinfo_ref.untyped.start, bootinfo_ref.untyped.end)?; + + // NB: seL4_DebugCapIdentify is only available in debug builds + #[cfg(feature = "CONFIG_DEBUG_BUILD")] + for ut in bootinfo_ref.untyped.start..bootinfo_ref.untyped.end { + let cap_tag = unsafe { kata_os_common::sel4_sys::seL4_DebugCapIdentify(ut) }; + assert_eq!(cap_tag, 2, + "expected untyped (2), got {} for cap at {}", cap_tag, ut); + } + Ok(()) +} + +fn malloc_command( + args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + let space_str = args.next().ok_or(CommandError::BadArgs)?; + let space_bytes = space_str.parse::()?; + match kata_frame_alloc(space_bytes) { + Ok(frames) => { + writeln!(output, "Allocated {:?}", frames)?; + } + Err(status) => { + writeln!(output, "malloc failed: {:?}", status)?; + } + } + Ok(()) +} + +fn mfree_command( + args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + extern "C" { static SELF_CNODE: seL4_CPtr; } + let cptr_str = args.next().ok_or(CommandError::BadArgs)?; + let count_str = args.next().ok_or(CommandError::BadArgs)?; + let frames = ObjDescBundle::new( + unsafe { SELF_CNODE }, + seL4_WordBits as u8, + vec![ + ObjDesc::new( + sel4_sys::seL4_RISCV_4K_Page, + count_str.parse::()?, + cptr_str.parse::()? as seL4_CPtr, + ), + ], + ); + match kata_object_free_toplevel(&frames) { + Ok(_) => { + writeln!(output, "Free'd {:?}", frames)?; + } + Err(status) => { + writeln!(output, "mfree failed: {:?}", status)?; + } + } + Ok(()) +} + +fn obj_alloc_command( + _args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + let before_stats = kata_memory_stats().expect("before stats"); + mstats(output, &before_stats)?; + + fn check_alloc(output: &mut dyn io::Write, + name: &str, + res: Result) { + match res { + Ok(obj) => { + if let Err(e) = kata_object_free_toplevel(&obj) { + let _ = writeln!(output, "free {} {:?} failed: {:?}", name, obj, e); + } + } + Err(e) => { + let _ = writeln!(output, "alloc {} failed: {:?}", name, e); + } + } + } + + // NB: alloc+free immediately so we don't run out of top-level CNode slots + check_alloc(output, "untyped", kata_untyped_alloc(12)); // NB: 4KB + check_alloc(output, "tcb", kata_tcb_alloc()); + check_alloc(output, "endpoint", kata_endpoint_alloc()); + check_alloc(output, "notification", kata_notification_alloc()); + check_alloc(output, "cnode", kata_cnode_alloc(5)); // NB: 32 slots + check_alloc(output, "frame", kata_frame_alloc(4096)); +// check_alloc(output, "large frame", kata_frame_alloc(1024*1024)); + check_alloc(output, "page table", kata_page_table_alloc()); + + #[cfg(feature = "CONFIG_KERNEL_MCS")] + check_alloc(output, "sched context", + kata_sched_context_alloc(seL4_MinSchedContextBits)); + + #[cfg(feature = "CONFIG_KERNEL_MCS")] + check_alloc(output, "reply", kata_reply_alloc()); + + let after_stats = kata_memory_stats().expect("after stats"); + mstats(output, &after_stats)?; + assert_eq!(before_stats.allocated_bytes, after_stats.allocated_bytes); + assert_eq!(before_stats.free_bytes, after_stats.free_bytes); + + // Batch allocate into a private CNode as we might to build a process. + const CNODE_DEPTH: usize = 7; // 128 slots + let cnode = kata_cnode_alloc(CNODE_DEPTH).unwrap(); // XXX handle error + let objs = ObjDescBundle::new( + cnode.objs[0].cptr, + CNODE_DEPTH as u8, + vec![ + ObjDesc::new(seL4_TCBObject, 1, 0), // 1 tcb + ObjDesc::new(seL4_EndpointObject, 2, 1), // 2 endpoiints + ObjDesc::new(seL4_ReplyObject, 2, 3), // 2 replys + ObjDesc::new(seL4_SchedContextObject, // 1 sched context + seL4_MinSchedContextBits, 5), + ObjDesc::new(seL4_RISCV_4K_Page, 10, 6), // 10 4K pages + ], + ); + match kata_object_alloc(&objs) { + Ok(_) => { + writeln!(output, "Batch alloc ok: {:?}", objs)?; + if let Err(e) = kata_object_free(&objs) { + writeln!(output, "Batch free err: {:?}", e)?; + } + } + Err(e) => { + writeln!(output, "Batch alloc err: {:?} {:?}", objs, e)?; + } + } + if let Err(e) = kata_object_free_toplevel(&cnode) { + writeln!(output, "Cnode free err: {:?} {:?}", cnode, e)?; + } + + // Batch allocate using the newer api that constructs a CNode based + // on the batch of objects specified. + match kata_object_alloc_in_cnode( + vec![ + ObjDesc::new(seL4_TCBObject, 1, 0), // 1 tcb + ObjDesc::new(seL4_EndpointObject, 1, 1), // 1 endpoiints + ObjDesc::new(seL4_ReplyObject, 1, 2), // 1 replys + ObjDesc::new(seL4_SchedContextObject, // 1 sched context + seL4_MinSchedContextBits, 3), + ObjDesc::new(seL4_RISCV_4K_Page, 2, 4), // 2 4K pages + ], + ) { + Ok(objs) => { + writeln!(output, "kata_object_alloc_in_cnode ok: {:?}", objs)?; + if let Err(e) = kata_object_free_in_cnode(&objs) { + writeln!(output, "kata_object_free_in_cnode failed: {:?}", e)?; + } + } + Err(e) => { + writeln!(output, "kata_object_alloc_in_cnode failed: {:?}", e)?; + } + } + + Ok(writeln!(output, "All tests passed!")?) +} diff --git a/apps/system/components/DebugConsole/kata-shell/src/test_ml_coordinator.rs b/apps/system/components/DebugConsole/kata-shell/src/test_ml_coordinator.rs new file mode 100644 index 0000000..9269561 --- /dev/null +++ b/apps/system/components/DebugConsole/kata-shell/src/test_ml_coordinator.rs @@ -0,0 +1,71 @@ +// MlCoordinator service shell test commands + +use core::fmt::Write; +use crate::CmdFn; +use crate::CommandError; +use crate::HashMap; + +use kata_io as io; +use kata_ml_interface::*; + +pub fn add_cmds(cmds: &mut HashMap::<&str, CmdFn>) { + cmds.extend([ + ("test_mlcancel", mlcancel_command as CmdFn), + ("test_mlexecute", mlexecute_command as CmdFn), + ("test_mlperiodic", mlperiodic_command as CmdFn), + ]); +} + +/// Implements a command that cancels an ML execution. +fn mlcancel_command( + args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + let bundle_id = args.next().ok_or(CommandError::BadArgs)?; + let model_id = args.next().ok_or(CommandError::BadArgs)?; + + if let Err(e) = kata_mlcoord_cancel(bundle_id, model_id) { + writeln!(output, "Cancel {:?} {:?} err: {:?}", bundle_id, model_id, e)?; + } else { + writeln!(output, "Cancelled {:?} {:?}", bundle_id, model_id)?; + } + Ok(()) +} + +/// Implements a command that runs a oneshot ML execution. +fn mlexecute_command( + args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + let bundle_id = args.next().ok_or(CommandError::BadArgs)?; + let model_id = args.next().ok_or(CommandError::BadArgs)?; + + if let Err(e) = kata_mlcoord_oneshot(bundle_id, model_id) { + writeln!(output, "Execute {:?} {:?} err: {:?}", bundle_id, model_id, e)?; + } + + Ok(()) +} + +/// Implements a command that runs a periodic ML execution. +fn mlperiodic_command( + args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + let bundle_id = args.next().ok_or(CommandError::BadArgs)?; + let model_id = args.next().ok_or(CommandError::BadArgs)?; + let rate_str = args.next().ok_or(CommandError::BadArgs)?; + let rate_in_ms = rate_str.parse::()?; + + if let Err(e) = kata_mlcoord_periodic(bundle_id, model_id, rate_in_ms) { + writeln!(output, "Periodic {:?} {:?} err: {:?}", bundle_id, model_id, e)?; + } + + Ok(()) +} diff --git a/apps/system/components/DebugConsole/kata-shell/src/test_panic.rs b/apps/system/components/DebugConsole/kata-shell/src/test_panic.rs new file mode 100644 index 0000000..b0aa776 --- /dev/null +++ b/apps/system/components/DebugConsole/kata-shell/src/test_panic.rs @@ -0,0 +1,23 @@ +// Panic-related shell test commands + +use crate::CmdFn; +use crate::CommandError; +use crate::HashMap; + +use kata_io as io; + +pub fn add_cmds(cmds: &mut HashMap::<&str, CmdFn>) { + cmds.extend([ + ("test_panic", panic_command as CmdFn), + ]); +} + +/// Implements a command that tests panic handling. +fn panic_command( + _args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + _output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + panic!("testing"); +} diff --git a/apps/system/components/DebugConsole/kata-shell/src/test_timer_service.rs b/apps/system/components/DebugConsole/kata-shell/src/test_timer_service.rs new file mode 100644 index 0000000..1de835b --- /dev/null +++ b/apps/system/components/DebugConsole/kata-shell/src/test_timer_service.rs @@ -0,0 +1,80 @@ +// TimerService shell test commands + +use core::fmt::Write; +use crate::CmdFn; +use crate::CommandError; +use crate::HashMap; + +use kata_io as io; + +use kata_timer_interface::timer_service_completed_timers; +use kata_timer_interface::TimerServiceError; +use kata_timer_interface::timer_service_oneshot; +use kata_timer_interface::timer_service_wait; + +pub fn add_cmds(cmds: &mut HashMap::<&str, CmdFn>) { + cmds.extend([ + ("test_timer_async", timer_async_command as CmdFn), + ("test_timer_blocking", timer_blocking_command as CmdFn), + ("test_timer_completed",timer_completed_command as CmdFn), + ]); +} + +/// Implements a command that starts a timer, but does not wait on the +/// notification. +fn timer_async_command( + args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + let id_str = args.next().ok_or(CommandError::BadArgs)?; + let id = id_str.parse::()?; + let time_str = args.next().ok_or(CommandError::BadArgs)?; + let time_ms = time_str.parse::()?; + + writeln!(output, "Starting timer {} for {} ms.", id, time_ms)?; + + match timer_service_oneshot(id, time_ms) { + TimerServiceError::TimerOk => (), + _ => return Err(CommandError::BadArgs), + } + + timer_service_oneshot(id, time_ms); + + Ok(()) +} + +/// Implements a command that starts a timer, blocking until the timer has +/// completed. +fn timer_blocking_command( + args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + let time_str = args.next().ok_or(CommandError::BadArgs)?; + let time_ms = time_str.parse::()?; + + writeln!(output, "Blocking {} ms waiting for timer.", time_ms)?; + + // Set timer_id to 0, we don't need to use multiple timers here. + match timer_service_oneshot(0, time_ms) { + TimerServiceError::TimerOk => (), + _ => return Err(CommandError::BadArgs), + } + + timer_service_wait(); + + return Ok(writeln!(output, "Timer completed.")?); +} + +/// Implements a command that checks the completed timers. +fn timer_completed_command( + _args: &mut dyn Iterator, + _input: &mut dyn io::BufRead, + output: &mut dyn io::Write, + _builtin_cpio: &[u8], +) -> Result<(), CommandError> { + return Ok(writeln!(output, "Timers completed: {:#032b}", timer_service_completed_timers())?); +}