diff --git a/apps/system/CMakeLists.txt b/apps/system/CMakeLists.txt index f4cc8b9..54737e8 100644 --- a/apps/system/CMakeLists.txt +++ b/apps/system/CMakeLists.txt @@ -29,6 +29,19 @@ DeclareCAmkESComponent(DebugConsole LIBS kata_debug_console ) +RustAddLibrary( + kata_process_manager + SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/components/ProcessManager + TARGET "riscv32imc-unknown-none-elf" + LIB_FILENAME libkata_process_manager.a +) + +DeclareCAmkESComponent(ProcessManager + LIBS kata_process_manager + INCLUDES interfaces +) + + DeclareCAmkESComponent(UartDriver SOURCES components/UartDriver/src/driver.c ) diff --git a/apps/system/components/ProcessManager/Cargo.toml b/apps/system/components/ProcessManager/Cargo.toml new file mode 100644 index 0000000..6152dc6 --- /dev/null +++ b/apps/system/components/ProcessManager/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +members = [ + "kata-proc-common", + "kata-proc-manager", +] diff --git a/apps/system/components/ProcessManager/ProcessManager.camkes b/apps/system/components/ProcessManager/ProcessManager.camkes new file mode 100644 index 0000000..3783305 --- /dev/null +++ b/apps/system/components/ProcessManager/ProcessManager.camkes @@ -0,0 +1,13 @@ +// Kata OS ProcessManager services. + +import ; +import ; +import ; + +component ProcessManager { + control; + provides ProcessControlInterface proc_ctrl; + provides PackageManagementInterface proc_mgmt; + + uses SeL4DebugInterface sel4debug; +} diff --git a/apps/system/components/ProcessManager/cbindgen.toml b/apps/system/components/ProcessManager/cbindgen.toml new file mode 100644 index 0000000..4a26255 --- /dev/null +++ b/apps/system/components/ProcessManager/cbindgen.toml @@ -0,0 +1,7 @@ +language = "C" +include_guard = "__PROCESS_MANAGER_BINDINGS_H__" +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +no_includes = true + +[export] +include = ["Bundle", "BundleId", "BundleIdArray"] diff --git a/apps/system/components/ProcessManager/kata-proc-common/Cargo.toml b/apps/system/components/ProcessManager/kata-proc-common/Cargo.toml new file mode 100644 index 0000000..8df3039 --- /dev/null +++ b/apps/system/components/ProcessManager/kata-proc-common/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "kata-proc-common" +version = "0.1.0" +edition = "2018" + +[dependencies] diff --git a/apps/system/components/ProcessManager/kata-proc-common/src/lib.rs b/apps/system/components/ProcessManager/kata-proc-common/src/lib.rs new file mode 100644 index 0000000..f258a48 --- /dev/null +++ b/apps/system/components/ProcessManager/kata-proc-common/src/lib.rs @@ -0,0 +1,105 @@ +//! Kata OS process management support + +#![cfg_attr(not(test), no_std)] + +use core::ops::{Index, IndexMut}; + +// NB: struct's marked repr(C) are processed by cbindgen to get a .h file +// used in camkes interfaces. + +// Max bundles that can be installed at one time. +pub const MAX_BUNDLES: usize = 10; +// Max/fixed size of a BundleId; this is stopgap for C compatibility +pub const MAX_BUNDLE_ID_SIZE: usize = 32; + +#[repr(C)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct BundleId { + pub id: [u8; MAX_BUNDLE_ID_SIZE], +} +impl BundleId { + pub fn empty(value: u8) -> Self { + BundleId { + id: [value; MAX_BUNDLE_ID_SIZE], + } + } + pub fn new() -> Self { + BundleId::empty(0) + } + pub fn is_zero(&self) -> bool { + self.id == [0; MAX_BUNDLE_ID_SIZE] + } +} + +#[repr(C)] +pub struct BundleIdArray { + pub ids: [BundleId; MAX_BUNDLES], // TODO(sleffler): placeholder +} +impl BundleIdArray { + pub fn new() -> Self { + BundleIdArray { + ids: [BundleId::new(); MAX_BUNDLES], + } + } + pub fn len(&self) -> usize { + self.ids.iter().filter(|&id| !id.is_zero()).count() + } +} +impl Index for BundleIdArray { + type Output = BundleId; + fn index(&self, index: usize) -> &Self::Output { + &self.ids[index] + } +} +impl IndexMut for BundleIdArray { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.ids[index] + } +} + +// TODO(sleffler): Bundle should come from whomever implements install+uninstall +// for ProcessManagerInterface +#[repr(C)] +pub struct Bundle { + pub something: u32, // TODO(sleffler): placeholder +} +impl Bundle { + pub fn new() -> Self { + Bundle { something: 0 } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ProcessManagerError { + BundleNotFound, + BundleFound, + NoSpace, + // Generic errors for interface failures. + InstallFailed, + UninstallFailed, + StartFailed, + StopFailed, +} + +// Abstract interface for injecting fakes, etc. +pub trait ProcessManagerInterface { + fn install(&self, bundle: &Bundle) -> Result<(), ProcessManagerError>; + fn uninstall(&self, bundle: &Bundle) -> Result<(), ProcessManagerError>; + fn start(&self, bundle: &Bundle) -> Result<(), ProcessManagerError>; + fn stop(&self, bundle: &Bundle) -> Result<(), ProcessManagerError>; +} + +pub trait PackageManagementInterface<'a> { + fn install( + &mut self, + bundle_id: &BundleId, + bundle: &'a Bundle, + ) -> Result<(), ProcessManagerError>; + fn uninstall(&mut self, bundle_id: &BundleId) -> Result<(), ProcessManagerError>; +} + +pub trait ProcessControlInterface { + fn start(&mut self, bundle_id: &BundleId) -> Result<(), ProcessManagerError>; + fn stop(&mut self, bundle_id: &BundleId) -> Result<(), ProcessManagerError>; + fn get_running_bundles(&self) -> BundleIdArray; +} diff --git a/apps/system/components/ProcessManager/kata-proc-manager/Cargo.toml b/apps/system/components/ProcessManager/kata-proc-manager/Cargo.toml new file mode 100644 index 0000000..ff33839 --- /dev/null +++ b/apps/system/components/ProcessManager/kata-proc-manager/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "kata-process-manager" +version = "0.1.0" +description = "Kata OS ProcessManager services" +edition = "2018" + +[dependencies] +arrayvec = { version = "0.7", default-features = false } +cstr_core = { version = "0.2.3", default-features = false } +kata-proc-common = { path = "../kata-proc-common" } +panic-halt = "0.2.0" + +[lib] +name = "kata_process_manager" +path = "src/run.rs" +crate-type = ["staticlib"] diff --git a/apps/system/components/ProcessManager/kata-proc-manager/src/run.rs b/apps/system/components/ProcessManager/kata-proc-manager/src/run.rs new file mode 100644 index 0000000..4e78c3a --- /dev/null +++ b/apps/system/components/ProcessManager/kata-proc-manager/src/run.rs @@ -0,0 +1,269 @@ +//! Kata OS process management support + +// TODO(sleffler): need locking? (maybe guarantee single-threading via ipc) + +#![cfg_attr(not(test), no_std)] +#![feature(const_fn_trait_bound)] // NB: for ProcessManager::empty using manager: None + +#[cfg(not(test))] +extern crate panic_halt; + +use arrayvec::ArrayVec; +use core::marker::Sync; +use cstr_core::CStr; +use kata_proc_common as proc; +use proc::*; + +// Prints/logs a message to the console. +// Temporary workaround for LoggerInterface not working +fn syslog(_level: i32, msg: &str) { + // Print |str| on the consule using the SeL4 debug syscall. + // NB: for now the message must be explicitly \0-terminated. + fn sel4_putstr(msg: &str) { + extern "C" { + fn sel4debug_put_string(msg: *const cstr_core::c_char); + } + unsafe { + sel4debug_put_string(CStr::from_bytes_with_nul(msg.as_bytes()).unwrap().as_ptr()); + } + } + sel4_putstr(msg); // NB:assumes caller includes \0 +} + +// Bundle state tracks start/stop operations. +#[derive(Debug, Eq, PartialEq)] +enum BundleState { + Stopped, + Running, +} + +// We track the Bundle & ProcessControlInterface state. +// NB: assume storage manager (or other) owns Bundle +struct BundleData<'a> { + state: BundleState, + bundle: &'a Bundle, +} + +impl<'b> BundleData<'b> { + fn new(bundle: &'b Bundle) -> Self { + BundleData { + state: BundleState::Stopped, + bundle: bundle, + } + } +} + +// The ProcessManager presents the PackageManagementInterface (for loading +// applications from storage) and the ProcessControlInterface (for starting +// and stopping associated applications). The interface to the underlying +// system(s) are abstracted through the ProcessManagerInterface. One instance +// of the ProcessManager is created at start and accessed through SeL4 RPC's +// (from other components). +pub struct ProcessManager<'a> { + // TODO(sleffler): Option is for empty which is meant for static setup + manager: Option<&'a (dyn ProcessManagerInterface + Sync)>, + + // TODO(sleffler): hash table (requires missing deps) + ids: ArrayVec, + bundles: ArrayVec, MAX_BUNDLES>, +} + +impl<'a> ProcessManager<'a> { + // Creates a new ProcessManager instance. + pub fn new(manager: &'a (dyn ProcessManagerInterface + Sync)) -> ProcessManager<'a> { + ProcessManager { + manager: Some(manager), + ids: ArrayVec::::new(), + bundles: ArrayVec::::new(), + } + } + + // Creates an incomplete ProcessManager instance for static initialization. + // The instance must be followed with an init() call to complete setup. + pub const fn empty() -> Self { + ProcessManager { + manager: None, + ids: ArrayVec::::new_const(), + bundles: ArrayVec::::new_const(), + } + } + + // Completes initialization of an instance created with empty(). + pub fn init(&mut self, manager: &'a (dyn ProcessManagerInterface + Sync)) { + self.manager = Some(manager); + } + + // Returns the index of |bundle_id| if previously installed. + fn get_bundle_index(&self, bundle_id: &BundleId) -> Option { + self.ids.iter().position(|x| bundle_id == x) + } +} + +impl<'a> PackageManagementInterface<'a> for ProcessManager<'a> { + fn install( + &mut self, + bundle_id: &BundleId, + bundle: &'a Bundle, + ) -> Result<(), ProcessManagerError> { + match self.get_bundle_index(bundle_id) { + Some(_) => Err(ProcessManagerError::BundleFound), + None => { + if self.ids.is_full() { + return Err(ProcessManagerError::NoSpace); + } + self.manager.unwrap().install(bundle)?; + self.bundles.push(BundleData::new(bundle)); + self.ids.push(*bundle_id); + Ok(()) + } + } + } + fn uninstall(&mut self, bundle_id: &BundleId) -> Result<(), ProcessManagerError> { + match self.get_bundle_index(bundle_id) { + None => Err(ProcessManagerError::BundleNotFound), + Some(index) => { + let bundle = &mut self.bundles[index]; + // TODO(sleffler): remove private state regardless of error? + self.manager.unwrap().uninstall(bundle.bundle)?; + self.bundles.remove(index); + self.ids.remove(index); + Ok(()) + } + } + } +} + +impl<'a> ProcessControlInterface for ProcessManager<'a> { + fn start(&mut self, bundle_id: &BundleId) -> Result<(), ProcessManagerError> { + match self.get_bundle_index(bundle_id) { + Some(index) => { + let bundle = &mut self.bundles[index]; + if bundle.state == BundleState::Stopped { + self.manager.unwrap().start(bundle.bundle)?; + } + bundle.state = BundleState::Running; + Ok(()) + } + None => Err(ProcessManagerError::BundleNotFound), + } + } + fn stop(&mut self, bundle_id: &BundleId) -> Result<(), ProcessManagerError> { + match self.get_bundle_index(bundle_id) { + Some(index) => { + let bundle = &mut self.bundles[index]; + if bundle.state == BundleState::Running { + self.manager.unwrap().stop(bundle.bundle)?; + } + bundle.state = BundleState::Stopped; + Ok(()) + } + None => Err(ProcessManagerError::BundleNotFound), // XXX ignore & return true? + } + } + fn get_running_bundles(&self) -> BundleIdArray { + let mut result = BundleIdArray::new(); + for (index, (&id, _bundle)) in self + .ids + .iter() + .zip(self.bundles.iter()) + .filter(|(_, bundle)| matches!(bundle.state, BundleState::Running)) + .enumerate() + { + result[index] = id; + } + result + } +} + +// TODO(sleffler): move to init or similar if a thread isn't needed +#[no_mangle] +pub extern "C" fn run() { + // Setup the userland address spaces, lifecycles, and system introspection + // for third-party applications. + syslog(0, "ProcessManager::run\n\0"); +} + +#[cfg(test)] +mod tests { + use super::*; + use proc::ProcessManagerError as pme; + + struct FakeManager {} + + impl ProcessManagerInterface for FakeManager { + fn install(&self, _bundle: &Bundle) -> Result<(), pme> { + Ok(()) + } + fn uninstall(&self, _bundle: &Bundle) -> Result<(), pme> { + Ok(()) + } + fn start(&self, _bundle: &Bundle) -> Result<(), pme> { + Ok(()) + } + fn stop(&self, _bundle: &Bundle) -> Result<(), pme> { + Ok(()) + } + } + + #[test] + fn test_pkg_mgmt() { + let bundle_id = BundleId::empty(1); + let bundle = Bundle::new(); + let fake = tests::FakeManager {}; + let mut mgr = ProcessManager::new(&fake); + + // Not installed, should fail. + assert_eq!(mgr.uninstall(&bundle_id).err(), Some(pme::BundleNotFound)); + // Install the bundle. + assert!(mgr.install(&bundle_id, &bundle).is_ok()); + // Re-install the same bundle should fail. + assert_eq!( + mgr.install(&bundle_id, &bundle).err(), + Some(pme::BundleFound) + ); + // Now uninstalling the bundle should work. + assert!(mgr.uninstall(&bundle_id).is_ok()); + } + + #[test] + fn test_proc_ctrl() { + let bundle_id = BundleId::empty(2); + let bundle = Bundle::new(); + let fake = tests::FakeManager {}; + let mut mgr = ProcessManager::new(&fake); + + assert!(mgr.install(&bundle_id, &bundle).is_ok()); + assert!(mgr.stop(&bundle_id).is_ok()); + assert!(mgr.start(&bundle_id).is_ok()); + + let running = mgr.get_running_bundles(); + assert_eq!(running.len(), 1); + assert_eq!(running[0], bundle_id); + + assert!(mgr.stop(&bundle_id).is_ok()); + // After stopping the bundle we should see nothing running. + let running = mgr.get_running_bundles(); + assert_eq!(running.len(), 0); + } + + #[test] + fn test_empty_init() { + let bundle_id = BundleId { id: [1; 32] }; + let bundle = Bundle::new(); + let fake = tests::FakeManager {}; + let mut mgr = ProcessManager::empty(); + mgr.init(&fake); + + // Not installed, should fail. + assert_eq!(mgr.uninstall(&bundle_id).err(), Some(pme::BundleNotFound)); + // Install the bundle. + assert!(mgr.install(&bundle_id, &bundle).is_ok()); + // Re-install the same bundle should fail. + assert_eq!( + mgr.install(&bundle_id, &bundle).err(), + Some(pme::BundleFound) + ); + // Now uninstalling the bundle should work. + assert!(mgr.uninstall(&bundle_id).is_ok()); + } +} diff --git a/apps/system/interfaces/PackageManagementInterface.camkes b/apps/system/interfaces/PackageManagementInterface.camkes new file mode 100644 index 0000000..8fbfa23 --- /dev/null +++ b/apps/system/interfaces/PackageManagementInterface.camkes @@ -0,0 +1,6 @@ +procedure PackageManagementInterface { + include ; + + bool install(in string bundleId, in Bundle bundle); + bool uninstall(in string bundleId); +}; diff --git a/apps/system/interfaces/ProcessControlInterface.camkes b/apps/system/interfaces/ProcessControlInterface.camkes new file mode 100644 index 0000000..dba5503 --- /dev/null +++ b/apps/system/interfaces/ProcessControlInterface.camkes @@ -0,0 +1,7 @@ +procedure ProcessControlInterface { + include ; + + bool start(in string bundleId); + bool stop(in string bundleId); + BundleIdArray get_running_bundles(); +}; diff --git a/apps/system/interfaces/ProcessManagerBindings.h b/apps/system/interfaces/ProcessManagerBindings.h new file mode 100644 index 0000000..33ba585 --- /dev/null +++ b/apps/system/interfaces/ProcessManagerBindings.h @@ -0,0 +1,20 @@ +#ifndef __PROCESS_MANAGER_BINDINGS_H__ +#define __PROCESS_MANAGER_BINDINGS_H__ + +/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ + +#define MAX_BUNDLES 10 + +typedef struct Bundle { + uint32_t something; +} Bundle; + +typedef struct BundleId { + uint8_t id[32]; +} BundleId; + +typedef struct BundleIdArray { + struct BundleId ids[MAX_BUNDLES]; +} BundleIdArray; + +#endif /* __PROCESS_MANAGER_BINDINGS_H__ */ diff --git a/apps/system/system.camkes b/apps/system/system.camkes index 4758574..ae436ae 100644 --- a/apps/system/system.camkes +++ b/apps/system/system.camkes @@ -15,6 +15,7 @@ import ; import "interfaces/uart.idl4"; import "components/UartDriver/UartDriver.camkes"; import "components/DebugConsole/DebugConsole.camkes"; +import "components/ProcessManager/ProcessManager.camkes"; import "components/SeL4Debug/SeL4Debug.camkes"; component UART { @@ -29,6 +30,7 @@ assembly { component UART uart; component UartDriver drv; component DebugConsole debug_console; + component ProcessManager process_manager; component SeL4Debug sel4debug; connection seL4HardwareMMIO uart_mem(from drv.mem, to uart.mem); @@ -39,6 +41,7 @@ assembly { // Connect the SeL4Debug interface of each component that needs access. connection seL4RPCCall SeL4DebugInterface(from debug_console.sel4debug, + from process_manager.sel4debug, to sel4debug.sel4debug); connection seL4SharedData tx_channel(