From 18c766024444c6c8cc863c25fd390b563a30f508 Mon Sep 17 00:00:00 2001 From: Sam Leffler Date: Tue, 29 Mar 2022 18:49:55 +0000 Subject: [PATCH] Add MemoryManager service. The MemoryManager service allocates & frees seL4 objects. Requests can be batched. Capabilities to dynamically allocated objects are moved in CNode containers attached to IPC requests. Specific changes: - Add new CAmkES MemoryManager component. - Add api's for allocating & freeing singleton objects (e.g. kata_cnode_alloc) and batches of objects (kata_object_alloc & kata_object_free). - Add support to kata-os-rootserver to hand-off UntypedMemory objects just before terminating. The objects are placed directly in the MemoryManager's top-level CNode and a BootInfo frame is constructed that describes where the objects are. - Switch the rootserver to kata-os-rootserver as the C version lacks the UntypedMemory hand-off. - Add test_bootinfo kata-shell command to dump the MemoryManager BootInfo frame contents (broken for now because it directlry references the shared page). - Add test_obj_alloc kata-shell command that exercises the MemoryManager singleton and batch api's While here, did some cleanup of arg handling in kata-shell. TODO: top-level object allocations use a simplistic capability allocator TODO: move test_bootinfo to the MemoryManager and add an interface rpc Change-Id: I778b2d5fe7f2f9b65ee642ff905cf56d4b2b02fd GitOrigin-RevId: 7fc72d1927bba165234955e68f8b9ad1b556f6fb --- apps/system/CMakeLists.txt | 21 +- .../DebugConsole/DebugConsole.camkes | 6 +- .../DebugConsole/kata-shell/Cargo.toml | 5 +- .../DebugConsole/kata-shell/build.rs | 1 - .../DebugConsole/kata-shell/src/lib.rs | 311 ++++++++--- .../components/MemoryManager/Cargo.toml | 24 + .../MemoryManager/MemoryManager.camkes | 18 + .../components/MemoryManager/cbindgen.toml | 7 + .../kata-memory-component/Cargo.toml | 19 + .../kata-memory-component/src/run.rs | 182 +++++++ .../kata-memory-interface/Cargo.toml | 19 + .../kata-memory-interface/build.rs | 20 + .../kata-memory-interface/src/lib.rs | 509 ++++++++++++++++++ .../kata-memory-manager/Cargo.toml | 13 + .../kata-memory-manager/src/lib.rs | 50 ++ .../src/memory_manager/mod.rs | 263 +++++++++ .../kata-os-common/src/capdl/mod.rs | 14 + .../kata-os-model/feature/dynamic_alloc.rs | 54 +- .../kata-os-common/src/kata-os-model/mod.rs | 107 +++- .../kata-os-common/src/sel4-sys/lib.rs | 3 +- apps/system/interfaces/MemoryInterface.camkes | 7 + .../system/interfaces/MemoryManagerBindings.h | 33 ++ apps/system/system.camkes | 12 + easy-settings.cmake | 4 +- 24 files changed, 1601 insertions(+), 101 deletions(-) create mode 100644 apps/system/components/MemoryManager/Cargo.toml create mode 100644 apps/system/components/MemoryManager/MemoryManager.camkes create mode 100644 apps/system/components/MemoryManager/cbindgen.toml create mode 100644 apps/system/components/MemoryManager/kata-memory-component/Cargo.toml create mode 100644 apps/system/components/MemoryManager/kata-memory-component/src/run.rs create mode 100644 apps/system/components/MemoryManager/kata-memory-interface/Cargo.toml create mode 100644 apps/system/components/MemoryManager/kata-memory-interface/build.rs create mode 100644 apps/system/components/MemoryManager/kata-memory-interface/src/lib.rs create mode 100644 apps/system/components/MemoryManager/kata-memory-manager/Cargo.toml create mode 100644 apps/system/components/MemoryManager/kata-memory-manager/src/lib.rs create mode 100644 apps/system/components/MemoryManager/kata-memory-manager/src/memory_manager/mod.rs create mode 100644 apps/system/interfaces/MemoryInterface.camkes create mode 100644 apps/system/interfaces/MemoryManagerBindings.h diff --git a/apps/system/CMakeLists.txt b/apps/system/CMakeLists.txt index 5ffea2d..e15671b 100644 --- a/apps/system/CMakeLists.txt +++ b/apps/system/CMakeLists.txt @@ -31,13 +31,13 @@ DeclareCAmkESComponent(DebugConsole ) RustAddLibrary( - kata_process_manager - SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/components/ProcessManager - LIB_FILENAME libkata_process_manager.a + kata_memory_manager + SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/components/MemoryManager + LIB_FILENAME libkata_memory_manager.a ) -DeclareCAmkESComponent(ProcessManager - LIBS kata_process_manager +DeclareCAmkESComponent(MemoryManager + LIBS kata_memory_manager INCLUDES interfaces ) @@ -52,6 +52,17 @@ DeclareCAmkESComponent(MlCoordinator INCLUDES interfaces ) +RustAddLibrary( + kata_process_manager + SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/components/ProcessManager + LIB_FILENAME libkata_process_manager.a +) + +DeclareCAmkESComponent(ProcessManager + LIBS kata_process_manager + INCLUDES interfaces +) + RustAddLibrary( kata_security_coordinator SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/components/SecurityCoordinator diff --git a/apps/system/components/DebugConsole/DebugConsole.camkes b/apps/system/components/DebugConsole/DebugConsole.camkes index c8c6460..d114810 100644 --- a/apps/system/components/DebugConsole/DebugConsole.camkes +++ b/apps/system/components/DebugConsole/DebugConsole.camkes @@ -2,6 +2,7 @@ import ; import ; import ; import ; +import ; import ; import ; @@ -15,11 +16,12 @@ component DebugConsole { uses rust_read_inf uart_read; provides LoggerInterface logger; - uses ProcessControlInterface proc_ctrl; + uses MemoryInterface memory; + uses MlCoordinatorInterface mlcoord; uses PackageManagementInterface pkg_mgmt; + uses ProcessControlInterface proc_ctrl; // TODO(b/200707300): for debugging uses SecurityCoordinatorInterface security; // TODO(b/200707300): for debugging uses StorageInterface storage; - uses MlCoordinatorInterface mlcoord; } diff --git a/apps/system/components/DebugConsole/kata-shell/Cargo.toml b/apps/system/components/DebugConsole/kata-shell/Cargo.toml index bd4f3a4..c9375b6 100644 --- a/apps/system/components/DebugConsole/kata-shell/Cargo.toml +++ b/apps/system/components/DebugConsole/kata-shell/Cargo.toml @@ -8,18 +8,17 @@ build = "build.rs" [build-dependencies] sel4-config = { path = "../../kata-os-common/src/sel4-config" } -[build-env] -SEL4_OUT_DIR = "${ROOTDIR}out/kata/kernel" - [features] default = [] CONFIG_DEBUG_BUILD = [] +CONFIG_KERNEL_MCS = [] [dependencies] crc = { version = "1.4.0", default_features = false } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } kata-io = { path = "../kata-io" } kata-line-reader = { path = "../kata-line-reader" } +kata-memory-interface = { path = "../../MemoryManager/kata-memory-interface" } kata-proc-interface = { path = "../../ProcessManager/kata-proc-interface" } kata-os-common = { path = "../../kata-os-common" } kata-security-interface = { path = "../../SecurityCoordinator/kata-security-interface" } diff --git a/apps/system/components/DebugConsole/kata-shell/build.rs b/apps/system/components/DebugConsole/kata-shell/build.rs index 50c7d61..eef87c8 100644 --- a/apps/system/components/DebugConsole/kata-shell/build.rs +++ b/apps/system/components/DebugConsole/kata-shell/build.rs @@ -1,4 +1,3 @@ -//extern crate sel4_config; use std::env; fn main() { diff --git a/apps/system/components/DebugConsole/kata-shell/src/lib.rs b/apps/system/components/DebugConsole/kata-shell/src/lib.rs index 7e5e39a..4eea5f4 100644 --- a/apps/system/components/DebugConsole/kata-shell/src/lib.rs +++ b/apps/system/components/DebugConsole/kata-shell/src/lib.rs @@ -2,6 +2,7 @@ extern crate alloc; use alloc::string::String; +use alloc::vec; use alloc::vec::Vec; use core::fmt; use core::fmt::Write; @@ -10,6 +11,8 @@ use log; use kata_io as io; use kata_line_reader::LineReader; +use kata_os_common::sel4_sys; +use kata_memory_interface::*; use kata_proc_interface::kata_pkg_mgmt_install; use kata_proc_interface::kata_pkg_mgmt_uninstall; use kata_proc_interface::kata_proc_ctrl_get_running_bundles; @@ -19,6 +22,11 @@ use kata_storage_interface::kata_storage_delete; use kata_storage_interface::kata_storage_read; use kata_storage_interface::kata_storage_write; +use sel4_sys::seL4_CPtr; +use sel4_sys::seL4_MinSchedContextBits; +use sel4_sys::seL4_ObjectType::*; +use sel4_sys::seL4_WordBits; + mod rz; /// Error type indicating why a command line is not runnable. @@ -108,6 +116,9 @@ fn dispatch_command(cmdline: &str, input: &mut dyn io::BufRead, output: &mut dyn "kvwrite" => kvwrite_command(&mut args, output), "install" => install_command(&mut args, 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), @@ -117,9 +128,11 @@ fn dispatch_command(cmdline: &str, input: &mut dyn io::BufRead, output: &mut dyn "test_alloc" => test_alloc_command(output), "test_alloc_error" => test_alloc_error_command(output), - "test_panic" => test_panic_command(), + "test_bootinfo" => test_bootinfo_command(output), "test_mlexecute" => test_mlexecute_command(), "test_mlcontinuous" => test_mlcontinuous_command(&mut args), + "test_obj_alloc" => test_obj_alloc_command(output), + "test_panic" => test_panic_command(), _ => Err(CommandError::UnknownCommand), }; @@ -237,14 +250,11 @@ fn add_command( args: &mut dyn Iterator, output: &mut dyn io::Write, ) -> Result<(), CommandError> { - if let Some(x_str) = args.nth(0) { - if let Some(y_str) = args.nth(0) { - let x = x_str.parse::()?; - let y = y_str.parse::()?; - return Ok(writeln!(output, "{}", x + y)?); - } - } - Err(CommandError::BadArgs) + 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. @@ -285,122 +295,175 @@ fn uninstall_command( args: &mut dyn Iterator, output: &mut dyn io::Write, ) -> Result<(), CommandError> { - if let Some(bundle_id) = args.nth(0) { - match kata_pkg_mgmt_uninstall(bundle_id) { - Ok(_) => { - writeln!(output, "Bundle \"{}\" uninstalled.", bundle_id)?; - } - Err(status) => { - writeln!(output, "uninstall failed: {:?}", status)?; - } + let bundle_id = args.next().ok_or(CommandError::BadArgs)?; + match kata_pkg_mgmt_uninstall(bundle_id) { + Ok(_) => { + writeln!(output, "Bundle \"{}\" uninstalled.", bundle_id)?; + } + Err(status) => { + writeln!(output, "uninstall failed: {:?}", status)?; } - Ok(()) - } else { - Err(CommandError::BadArgs) } + Ok(()) } fn start_command( args: &mut dyn Iterator, output: &mut dyn io::Write, ) -> Result<(), CommandError> { - if let Some(bundle_id) = args.nth(0) { - match kata_proc_ctrl_start(bundle_id) { - Ok(_) => { - writeln!(output, "Bundle \"{}\" started.", bundle_id)?; - } - Err(status) => { - writeln!(output, "start failed: {:?}", status)?; - } + let bundle_id = args.next().ok_or(CommandError::BadArgs)?; + match kata_proc_ctrl_start(bundle_id) { + Ok(_) => { + writeln!(output, "Bundle \"{}\" started.", bundle_id)?; + } + Err(status) => { + writeln!(output, "start failed: {:?}", status)?; } - Ok(()) - } else { - Err(CommandError::BadArgs) } + Ok(()) } fn stop_command( args: &mut dyn Iterator, output: &mut dyn io::Write, ) -> Result<(), CommandError> { - if let Some(bundle_id) = args.nth(0) { - match kata_proc_ctrl_stop(bundle_id) { - Ok(_) => { - writeln!(output, "Bundle \"{}\" stopped.", bundle_id)?; - } - Err(status) => { - writeln!(output, "stop failed: {:?}", status)?; - } + let bundle_id = args.next().ok_or(CommandError::BadArgs)?; + match kata_proc_ctrl_stop(bundle_id) { + Ok(_) => { + writeln!(output, "Bundle \"{}\" stopped.", bundle_id)?; + } + Err(status) => { + writeln!(output, "stop failed: {:?}", status)?; } - Ok(()) - } else { - Err(CommandError::BadArgs) } + Ok(()) } fn kvdelete_command( args: &mut dyn Iterator, output: &mut dyn io::Write, ) -> Result<(), CommandError> { - if let Some(key) = args.nth(0) { - match kata_storage_delete(key) { - Ok(_) => { - writeln!(output, "Delete key \"{}\".", key)?; - } - Err(status) => { - writeln!(output, "Delete key \"{}\" failed: {:?}", key, status)?; - } + let key = args.next().ok_or(CommandError::BadArgs)?; + match kata_storage_delete(key) { + Ok(_) => { + writeln!(output, "Delete key \"{}\".", key)?; + } + Err(status) => { + writeln!(output, "Delete key \"{}\" failed: {:?}", key, status)?; } - Ok(()) - } else { - Err(CommandError::BadArgs) } + Ok(()) } fn kvread_command( args: &mut dyn Iterator, output: &mut dyn io::Write, ) -> Result<(), CommandError> { - if let Some(key) = args.nth(0) { - match kata_storage_read(key) { - Ok(value) => { - writeln!(output, "Read key \"{}\" = {:?}.", key, value)?; - } - Err(status) => { - writeln!(output, "Read key \"{}\" failed: {:?}", key, status)?; - } + let key = args.next().ok_or(CommandError::BadArgs)?; + match kata_storage_read(key) { + Ok(value) => { + writeln!(output, "Read key \"{}\" = {:?}.", key, value)?; + } + Err(status) => { + writeln!(output, "Read key \"{}\" failed: {:?}", key, status)?; } - Ok(()) - } else { - Err(CommandError::BadArgs) } + Ok(()) } fn kvwrite_command( args: &mut dyn Iterator, output: &mut dyn io::Write, ) -> Result<(), CommandError> { - if let Some(key) = args.nth(0) { - let value = args.collect::>().join(" "); - match kata_storage_write(key, value.as_bytes()) { - Ok(_) => { - writeln!(output, "Write key \"{}\" = {:?}.", key, value)?; - } - Err(status) => { - writeln!(output, "Write key \"{}\" failed: {:?}", key, status)?; - } + let key = args.next().ok_or(CommandError::BadArgs)?; + let value = args.collect::>().join(" "); + match kata_storage_write(key, value.as_bytes()) { + Ok(_) => { + writeln!(output, "Write key \"{}\" = {:?}.", key, value)?; + } + Err(status) => { + writeln!(output, "Write key \"{}\" failed: {:?}", key, status)?; } - Ok(()) - } else { - Err(CommandError::BadArgs) } + 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> +{ + writeln!(output, "{} bytes in-use, {} bytes free, {} bytes requested, {} overhead", + stats.allocated_bytes, + stats.free_bytes, + stats.total_requested_bytes, + stats.overhead_bytes)?; + writeln!(output, "{} objs in-use, {} objs requested", + stats.allocated_objs, + stats.total_requested_objs)?; + Ok(()) +} + +fn mstats_command( + _args: &mut dyn Iterator, + output: &mut dyn io::Write, +) -> Result<(), CommandError> { + match kata_memory_stats() { + Ok(stats) => { mstats(output, &stats)?; } + Err(status) => { writeln!(output, "stats failed: {:?}", status)?; } + } + 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, vec}; + use alloc::{boxed::Box, rc::Rc}; // allocate a number on the heap let heap_value = Box::new(41); @@ -443,6 +506,26 @@ fn test_alloc_error_command(output: &mut dyn io::Write) -> Result<(), CommandErr 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"); @@ -473,3 +556,77 @@ fn test_mlcontinuous_command(args: &mut dyn Iterator) -> Result<(), } Err(CommandError::BadArgs) } + +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)?; + } + + Ok(writeln!(output, "All tests passed!")?) +} diff --git a/apps/system/components/MemoryManager/Cargo.toml b/apps/system/components/MemoryManager/Cargo.toml new file mode 100644 index 0000000..2e53248 --- /dev/null +++ b/apps/system/components/MemoryManager/Cargo.toml @@ -0,0 +1,24 @@ +[workspace] + +members = [ + "kata-memory-component", + "kata-memory-interface", + "kata-memory-manager", +] +resolver = "2" + +[profile.dev] +opt-level = 0 +debug = true +lto = false +codegen-units = 1 + +[profile.release] +opt-level = "z" +lto = "fat" +codegen-units = 1 +split-debuginfo = "unpacked" + +[profile.release.build-override] +opt-level = "z" +codegen-units = 1 diff --git a/apps/system/components/MemoryManager/MemoryManager.camkes b/apps/system/components/MemoryManager/MemoryManager.camkes new file mode 100644 index 0000000..e161072 --- /dev/null +++ b/apps/system/components/MemoryManager/MemoryManager.camkes @@ -0,0 +1,18 @@ +// Kata OS MemoryManager service. + +import ; +import ; + +component MemoryManager { + provides MemoryInterface memory; + + uses LoggerInterface logger; + + // Mark the component that should receive the unallocated UntypedMemory + // passed to the rootserver from the kernel. In addition to the + // capabilities the component also gets a page with Bootinfo data that + // includes updated UntypedMemory descriptors. In order to pass the + // capabilitiies the component's cnode is up-sized to be large enough + // to hold the extra capabilties. + attribute int untyped_memory = true; +} diff --git a/apps/system/components/MemoryManager/cbindgen.toml b/apps/system/components/MemoryManager/cbindgen.toml new file mode 100644 index 0000000..cec2834 --- /dev/null +++ b/apps/system/components/MemoryManager/cbindgen.toml @@ -0,0 +1,7 @@ +language = "C" +include_guard = "__MEMORY_MANAGER_BINDINGS_H__" +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +no_includes = true + +[export] +include = ["RawPageDescData", "RawMemoryStatsData", "MemoryManagerStats", "MemoryManagerError"] diff --git a/apps/system/components/MemoryManager/kata-memory-component/Cargo.toml b/apps/system/components/MemoryManager/kata-memory-component/Cargo.toml new file mode 100644 index 0000000..ba8afda --- /dev/null +++ b/apps/system/components/MemoryManager/kata-memory-component/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "kata-memory-component" +version = "0.1.0" +edition = "2018" + +[dependencies] +kata-allocator = { path = "../../DebugConsole/kata-allocator" } +kata-logger = { path = "../../DebugConsole/kata-logger" } +kata-panic = { path = "../../DebugConsole/kata-panic" } +kata-memory-interface = { path = "../kata-memory-interface" } +kata-memory-manager = { path = "../kata-memory-manager" } +kata-os-common = { path = "../../kata-os-common" } +log = "0.4" +postcard = { version = "0.7", features = ["alloc"], default-features = false } + +[lib] +name = "kata_memory_manager" +path = "src/run.rs" +crate-type = ["staticlib"] diff --git a/apps/system/components/MemoryManager/kata-memory-component/src/run.rs b/apps/system/components/MemoryManager/kata-memory-component/src/run.rs new file mode 100644 index 0000000..e95f914 --- /dev/null +++ b/apps/system/components/MemoryManager/kata-memory-component/src/run.rs @@ -0,0 +1,182 @@ +//! Kata OS MemoryManager component support. + +// Code here binds the camkes component to the rust code. +#![no_std] + +use core::ops::Range; +use core::slice; +extern crate kata_panic; +use kata_allocator; +use kata_logger::KataLogger; +use kata_memory_interface::MemoryManagerError; +use kata_memory_interface::MemoryManagerInterface; +use kata_memory_interface::ObjDescBundle; +use kata_memory_interface::RawMemoryStatsData; +use kata_memory_manager::KataMemoryManager; +use kata_os_common::sel4_sys; +use log::{info, trace}; +use sel4_sys::seL4_BootInfo; +use sel4_sys::seL4_CNode_Delete; +use sel4_sys::seL4_CPtr; +use sel4_sys::seL4_GetCapReceivePath; +use sel4_sys::seL4_SetCapReceivePath; +use sel4_sys::seL4_Word; +use sel4_sys::seL4_WordBits; + +// NB: KATA_MEMORY cannot be used before setup is completed with a call to init() +static mut KATA_MEMORY: KataMemoryManager = KataMemoryManager::empty(); + +extern "C" { + // Each CAmkES-generated CNode has a writable self-reference to itself in + // the slot SELF_CNODE. This is used to pass CNode containers of dynamically + // allocated objects between clients & the MemoryManager. + static SELF_CNODE: seL4_CPtr; + + // Each CAmkES-component has a CNode setup at a well-known slot in SELF_CNODE. + // We re-use that slot to receive CNode caps passed with alloc & free requests. + static RECV_CNODE: seL4_CPtr; +} + +#[no_mangle] +pub extern "C" fn pre_init() { + static KATA_LOGGER: KataLogger = KataLogger; + log::set_logger(&KATA_LOGGER).unwrap(); + // NB: set to max; the LoggerInterface will filter + log::set_max_level(log::LevelFilter::Trace); + + // TODO(sleffler): temp until we integrate with seL4 + static mut HEAP_MEMORY: [u8; 8 * 1024] = [0; 8 * 1024]; + unsafe { + kata_allocator::ALLOCATOR.init(HEAP_MEMORY.as_mut_ptr() as usize, HEAP_MEMORY.len()); + trace!( + "setup heap: start_addr {:p} size {}", + HEAP_MEMORY.as_ptr(), + HEAP_MEMORY.len() + ); + } + + extern "C" { + fn sel4runtime_bootinfo() -> *const seL4_BootInfo; + } + unsafe { + // The MemoryManager component is labeled to receive BootInfo); use + // it to complete initialization of the MemoryManager interface. + let bootinfo = &*sel4runtime_bootinfo(); + KATA_MEMORY.init( + /*slots=*/Range:: { + start: bootinfo.untyped.start, + end: bootinfo.untyped.end + }, + /*untypeds=*/ bootinfo.untyped_descs(), + ); + if let Ok(stats) = KATA_MEMORY.stats() { + trace!("Global memory: {} allocated {} free", + stats.allocated_bytes, + stats.free_bytes, + ); + } + } + unsafe { + // Delete the CAmkES-setup CNode; we're going to reuse the + // well-known slot once it is empty (see below). + seL4_CNode_Delete(SELF_CNODE, RECV_CNODE, seL4_WordBits as u8) + .expect("recv_node"); + } +} + +#[no_mangle] +pub extern "C" fn memory__init() { + unsafe { + // Point the receive path to the well-known slot that was emptied. + // This will be used to receive CNode's from clients for alloc & + // free requests. + // + // NB: this must be done here (rather than someplace like pre_init) + // so it's in the context of the MemoryInterface thread (so we write + // the correct ipc buffer). + seL4_SetCapReceivePath(SELF_CNODE, RECV_CNODE, seL4_WordBits); + trace!("Cap receive path {}:{}:{}", SELF_CNODE, RECV_CNODE, seL4_WordBits); + } +} + +// MemoryInterface glue stubs. + +// Clears any capability the specified path points to. +fn clear_path(&(root, index, depth): &(seL4_CPtr, seL4_CPtr, seL4_Word)) { + // TODO(sleffler): assert since future receives are likely to fail? + if let Err(e) = unsafe { seL4_CNode_Delete(root, index, depth as u8) } { + // NB: no error is returned if the slot is empty. + info!("Failed to clear receive path {:?}: {:?}", + (root, index, depth), e); + } +} + +#[no_mangle] +pub extern "C" fn memory_alloc( + c_raw_data_len: u32, + c_raw_data: *const u8, +) -> MemoryManagerError { + unsafe { + let recv_path = seL4_GetCapReceivePath(); + // NB: make sure noone clobbers the setup done in memory__init + assert_eq!(recv_path, (SELF_CNODE, RECV_CNODE, seL4_WordBits)); + + let raw_slice = slice::from_raw_parts(c_raw_data, c_raw_data_len as usize); + let ret_status = match postcard::from_bytes::(raw_slice) { + Ok(mut bundle) => { + // TODO(sleffler): verify we received a CNode in RECV_CNODE. + bundle.cnode = recv_path.1; + // NB: bundle.depth should reflect the received cnode + KATA_MEMORY.alloc(&bundle).into() + } + Err(_) => MemoryManagerError::MmeDeserializeFailed, + }; + // NB: must clear ReceivePath for next request + clear_path(&recv_path); + ret_status + } +} + +#[no_mangle] +pub extern "C" fn memory_free( + c_raw_data_len: u32, + c_raw_data: *const u8, +) -> MemoryManagerError { + unsafe { + let recv_path = seL4_GetCapReceivePath(); + // NB: make sure noone clobbers the setup done in memory__init + assert_eq!(recv_path, (SELF_CNODE, RECV_CNODE, seL4_WordBits)); + + let raw_slice = slice::from_raw_parts(c_raw_data, c_raw_data_len as usize); + let ret_status = match postcard::from_bytes::(raw_slice) { + Ok(mut bundle) => { + // TODO(sleffler): verify we received a CNode in RECV_CNODE. + bundle.cnode = recv_path.1; + // NB: bundle.depth should reflect the received cnode + KATA_MEMORY.free(&bundle).into() + } + Err(_) => MemoryManagerError::MmeDeserializeFailed, + }; + // NB: must clear ReceivePath for next request + clear_path(&recv_path); + ret_status + } +} + +#[no_mangle] +pub extern "C" fn memory_stats( + c_raw_resp_data: *mut RawMemoryStatsData, +) -> MemoryManagerError { + unsafe { + // TODO(sleffler): verify no cap was received + match KATA_MEMORY.stats() { + Ok(stats) => { + match postcard::to_slice(&stats, &mut (*c_raw_resp_data)[..]) { + Ok(_) => MemoryManagerError::MmeSuccess, + Err(_) => MemoryManagerError::MmeSerializeFailed, + } + } + Err(e) => e.into(), + } + } +} diff --git a/apps/system/components/MemoryManager/kata-memory-interface/Cargo.toml b/apps/system/components/MemoryManager/kata-memory-interface/Cargo.toml new file mode 100644 index 0000000..db7b74b --- /dev/null +++ b/apps/system/components/MemoryManager/kata-memory-interface/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "kata-memory-interface" +version = "0.1.0" +edition = "2018" +build = "build.rs" + +[build-dependencies] +sel4-config = { path = "../../kata-os-common/src/sel4-config" } + +[features] +default = [] +CONFIG_KERNEL_MCS = [] + +[dependencies] +postcard = { version = "0.7", features = ["alloc"], default-features = false } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +kata-os-common = { path = "../../kata-os-common" } +log = "0.4" +smallvec = "1.2" diff --git a/apps/system/components/MemoryManager/kata-memory-interface/build.rs b/apps/system/components/MemoryManager/kata-memory-interface/build.rs new file mode 100644 index 0000000..eef87c8 --- /dev/null +++ b/apps/system/components/MemoryManager/kata-memory-interface/build.rs @@ -0,0 +1,20 @@ +use std::env; + +fn main() { + // If SEL4_OUT_DIR is not set we expect the kernel build at a fixed + // location relative to the ROOTDIR env variable. + println!("SEL4_OUT_DIR {:?}", env::var("SEL4_OUT_DIR")); + let sel4_out_dir = env::var("SEL4_OUT_DIR").unwrap_or_else( + |_| format!("{}/out/kata/kernel", env::var("ROOTDIR").unwrap()) + ); + println!("sel4_out_dir {}", sel4_out_dir); + + // Dredge seL4 kernel config for settings we need as features to generate + // correct code: e.g. CONFIG_KERNEL_MCS enables MCS support which changes + // the system call numbering. + let features = sel4_config::get_sel4_features(&sel4_out_dir); + println!("features={:?}", features); + for feature in features { + println!("cargo:rustc-cfg=feature=\"{}\"", feature); + } +} diff --git a/apps/system/components/MemoryManager/kata-memory-interface/src/lib.rs b/apps/system/components/MemoryManager/kata-memory-interface/src/lib.rs new file mode 100644 index 0000000..12fd677 --- /dev/null +++ b/apps/system/components/MemoryManager/kata-memory-interface/src/lib.rs @@ -0,0 +1,509 @@ +//! Kata OS memory management support + +#![cfg_attr(not(test), no_std)] + +extern crate alloc; +use alloc::vec; +use alloc::vec::Vec; +use kata_os_common::sel4_sys; +use log::trace; +use postcard; +use sel4_sys::seL4_CNode_Move; +use sel4_sys::seL4_CPtr; +use sel4_sys::seL4_ObjectType::*; +use sel4_sys::seL4_ObjectType; +use sel4_sys::seL4_PageBits; +use sel4_sys::seL4_SetCap; +use sel4_sys::seL4_WordBits; +use serde::{Deserialize, Serialize}; + +// NB: @14b per desc this supports ~150 descriptors (depending +// on serde overhead), the rpc buffer is actually 4K so we could +// raise this +pub const RAW_OBJ_DESC_DATA_SIZE: usize = 2048; +pub type RawObjDescData = [u8; RAW_OBJ_DESC_DATA_SIZE]; + +// The MemoryManager takes collections of Object Descriptors. +// +// For an alloc request an object descriptor provides everything needed +// to allocate & retype untyped memory. Capabilities for the realized +// objects are attached to the IPC buffer holding the reply in a CNode +// container. For free requests the same object descriptors should be +// provided. Otherwise clients are responsible for filling in +// allocated objects; e.g. map page frames into a VSpace, bind endpoints +// to irq's, configure TCB slots, etc. +// +// TODO(sleffler): support setting fixed physical address for drivers +// TODO(sleffler): allocate associated resources like endpoint #'s? +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct ObjDesc { + // Requested object type or type of object being released. + pub type_: seL4_ObjectType, + + // Count of consecutive objects with the same type or, for CNode + // objects the log2 number of slots to use in sizing the object, + // or for untyped objects the log2 size in bytes, or for scheduler + // context objects the size in bits. See seL4_ObjectType::size_bits(). + count: usize, // XXX oversized (except for untyped use) + + // CSpace address for realized objects requested. If |count| is >1 + // this descriptor describes objects with |cptr|'s [0..|count|). + // Since each block of objects has it's own |cptr| one can describe + // a collection with random layout in CSpace (useful for construction). + // + // Object capabilities returned by the MemoryManager have the maximal + // rights. We depend on trusted agents (e.g. ProcessManager) to reduce + // rights when assigning them to an application. This also applies to + // the vm attributes of page frames (e.g. mark not executable as + // appropriate). + pub cptr: seL4_CPtr, +} +impl ObjDesc { + pub fn new( + type_: seL4_ObjectType, + count: usize, + cptr: seL4_CPtr, + ) -> Self { + ObjDesc { type_, count, cptr } + } + // Parameters for seL4_Untyped_Retype call. + pub fn retype_size_bits(&self) -> Option { + match self.type_ { + seL4_UntypedObject => Some(self.count), // Log2 memory size + seL4_CapTableObject => Some(self.count), // Log2 number of slots + seL4_SchedContextObject => Some(self.count), // Log2 context size + _ => self.type_.size_bits(), + } + } + pub fn retype_count(&self) -> usize { + match self.type_ { + // NB: we don't support creating multiple instances of the same + // size; the caller must supply multiple object descriptors. + seL4_UntypedObject + | seL4_CapTableObject + | seL4_SchedContextObject => 1, + _ => self.count, + } + } + + // Memory occupied by objects. Used for bookkeeping and statistics. + pub fn size_bytes(&self) -> Option { + match self.type_ { + seL4_UntypedObject => Some(1 << self.count), + seL4_CapTableObject => + self.type_.size_bits().map(|x| self.count * (1 << x)), + seL4_SchedContextObject => Some(1 << self.count), + _ => self.type_.size_bits().map(|x| 1 << x), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ObjDescBundle { + pub cnode: seL4_CPtr, + pub depth: u8, + pub objs: Vec, +} +impl ObjDescBundle { + pub fn new( + cnode: seL4_CPtr, + depth: u8, + objs: Vec, + ) -> Self { + ObjDescBundle { cnode, depth, objs } + } +} + +#[derive(Debug, Eq, PartialEq)] +pub enum MemoryError { + ObjCountInvalid = 0, // Too many objects requested + ObjTypeInvalid, // Request with invalid object type + ObjCapInvalid, // Request with invalid cptr XXX + UnknownMemoryError, + // Generic errors. + AllocFailed, + FreeFailed, +} + +pub const RAW_MEMORY_STATS_DATA_SIZE: usize = 100; +pub type RawMemoryStatsData = [u8; RAW_MEMORY_STATS_DATA_SIZE]; + +#[repr(C)] +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct MemoryManagerStats { + // Current space committed to allocations. + pub allocated_bytes: usize, + + // Current space available. + pub free_bytes: usize, + + // Total space for user requests (independent of any alignment). + pub total_requested_bytes: usize, + + // Space required for operation of the MemoryManager service. + pub overhead_bytes: usize, + + // Current number of seL4 objects allocated. + pub allocated_objs: usize, + + // Total number of seL4 objects allocated. + pub total_requested_objs: usize, + + // Retype requests failed due to insufficient available memory. + pub untyped_slab_too_small: usize, + + // Alloc requests failed due to lack of untyped memory. + pub out_of_memory: usize, +} + +// Objects are potentially batched with caps to allocated objects returned +// in the container slots specified by the |bundle] objects. +pub trait MemoryManagerInterface { + fn alloc(&mut self, bundle: &ObjDescBundle) -> Result<(), MemoryError>; + fn free(&mut self, bundle: &ObjDescBundle) -> Result<(), MemoryError>; + fn stats(&self) -> Result; +} + +// Public version of MemoryError presented over rpc interface. +// This is needed because the enum is exported to C users and needs to +// be unique from other enum's. +// TODO(sleffler): switch to single generic error space ala absl::StatusCode +#[repr(C)] +#[derive(Debug, Eq, PartialEq)] +pub enum MemoryManagerError { + MmeSuccess = 0, + MmeObjCountInvalid, + MmeObjTypeInvalid, + MmeObjCapInvalid, + MmeSerializeFailed, + MmeDeserializeFailed, + MmeUnknownError, + // Generic errors. + MmeAllocFailed, + MmeFreeFailed, +} +impl From for MemoryManagerError { + fn from(err: MemoryError) -> MemoryManagerError { + match err { + MemoryError::ObjCountInvalid => MemoryManagerError::MmeObjCountInvalid, + MemoryError::ObjTypeInvalid => MemoryManagerError::MmeObjTypeInvalid, + MemoryError::ObjCapInvalid => MemoryManagerError::MmeObjCapInvalid, + MemoryError::AllocFailed => MemoryManagerError::MmeAllocFailed, + MemoryError::FreeFailed => MemoryManagerError::MmeFreeFailed, + _ => MemoryManagerError::MmeUnknownError, + } + } +} +impl From> for MemoryManagerError { + fn from(result: Result<(), MemoryError>) -> MemoryManagerError { + result.map_or_else( + |e| MemoryManagerError::from(e), + |_v| MemoryManagerError::MmeSuccess, + ) + } +} +impl From for Result<(), MemoryManagerError> { + fn from(err: MemoryManagerError) -> Result<(), MemoryManagerError> { + if err == MemoryManagerError::MmeSuccess { + Ok(()) + } else { + Err(err) + } + } +} + +// Client wrappers. + +extern "C" { + // Each CAmkES-generated CNode has a writable self-reference to itself in + // the slot SELF_CNODE. This is used to pass CNode containers of dynamically + // allocated objects between clients & the MemoryManager. + static SELF_CNODE: seL4_CPtr; + + // Each CAmkES-component has a CNode setup at a well-known slot. We use that + // CNode to pass capabilities with alloc & free requests. + static RECV_CNODE: seL4_CPtr; + static RECV_CNODE_DEPTH: u8; +} + +pub fn kata_object_alloc( + request: &ObjDescBundle, +) -> Result<(), MemoryManagerError> { + extern "C" { + // NB: this assumes the MemoryManager component is named "memory". + fn memory_alloc(c_request_len: u32, c_request_data: *const u8) + -> MemoryManagerError; + } + trace!("kata_object_alloc {:?}", request); // XXX + let raw_data = &mut [0u8; RAW_OBJ_DESC_DATA_SIZE]; + postcard::to_slice(&request, &mut raw_data[..]) + .map_err(|_| MemoryManagerError::MmeSerializeFailed)?; + unsafe { + // Attach our CNode for returning objects; the CAmkES template + // forces extraCaps=1 when constructing the MessageInfo struct + // used by the seL4_Call inside memory_alloc. + seL4_SetCap(0, request.cnode); + + memory_alloc(raw_data.len() as u32, raw_data.as_ptr()).into() + } +} + +// TODO(sleffler): is anyone going to use these convience wrappers given +// the objects are returned inside a CNode (so it's way more convenient +// to allocate 'em all togerher and then move the CNode to a TCB)? + +// XXX need real allocator +static mut EMPTY_SLOT: seL4_CPtr = 30; // Next empty slot in debug_console_cnode XXX + +impl ObjDescBundle { + // Move objects from |src_cnode| to our top-levbel CSpace and mutate + // the Object Descriptor with adjusted cptr's. + // TODO(sleffler) make generic? + fn move_objects_to_toplevel( + &mut self, + dest_cnode: seL4_CPtr, + dest_depth: u8, + ) -> Result<(), MemoryManagerError> { + unsafe { + let mut dest_slot = EMPTY_SLOT; + for od in &mut self.objs { + for offset in 0..od.retype_count() { + // XXX cleanup on error? + seL4_CNode_Move( + /*root=*/ dest_cnode, + /*dest_index=*/ dest_slot, + /*dest_depth=*/ dest_depth, + /*src_root=*/ self.cnode, + /*src_index=*/ od.cptr + offset, + /*src_depth=*/ self.depth, + ).map_err(|_| MemoryManagerError::MmeObjCapInvalid)?; + dest_slot += 1; + } + od.cptr = dest_slot - od.retype_count(); + } + EMPTY_SLOT = dest_slot; + } + self.cnode = dest_cnode; + self.depth = dest_depth; + Ok(()) + } +} + +#[inline] +#[allow(dead_code)] +pub fn kata_untyped_alloc(space_bytes: usize) + -> Result +{ + let mut objs = ObjDescBundle::new( + unsafe { RECV_CNODE }, + unsafe { RECV_CNODE_DEPTH }, + vec![ ObjDesc::new(seL4_UntypedObject, space_bytes, /*cptr=*/ 0) ], + ); + kata_object_alloc(&objs)?; + objs.move_objects_to_toplevel(unsafe { SELF_CNODE }, seL4_WordBits as u8)?; + Ok(objs) +} + +#[inline] +#[allow(dead_code)] +pub fn kata_tcb_alloc() -> Result { + let mut objs = ObjDescBundle::new( + unsafe { RECV_CNODE }, + unsafe { RECV_CNODE_DEPTH }, + vec![ ObjDesc::new(seL4_TCBObject, 1, /*cptr=*/ 0) ], + ); + kata_object_alloc(&objs)?; + objs.move_objects_to_toplevel(unsafe { SELF_CNODE }, seL4_WordBits as u8)?; + Ok(objs) +} + +#[inline] +#[allow(dead_code)] +pub fn kata_endpoint_alloc() -> Result { + let mut objs = ObjDescBundle::new( + unsafe { RECV_CNODE }, + unsafe { RECV_CNODE_DEPTH }, + vec![ ObjDesc::new(seL4_EndpointObject, 1, /*cptr=*/ 0) ], + ); + kata_object_alloc(&objs)?; + objs.move_objects_to_toplevel(unsafe { SELF_CNODE }, seL4_WordBits as u8)?; + Ok(objs) +} + +#[inline] +#[allow(dead_code)] +pub fn kata_notification_alloc() -> Result { + let mut objs = ObjDescBundle::new( + unsafe { RECV_CNODE }, + unsafe { RECV_CNODE_DEPTH }, + vec![ ObjDesc::new(seL4_NotificationObject, 1, /*cptr=*/ 0) ], + ); + kata_object_alloc(&objs)?; + objs.move_objects_to_toplevel(unsafe { SELF_CNODE }, seL4_WordBits as u8)?; + Ok(objs) +} + +#[inline] +#[allow(dead_code)] +// |size_bits| is the log2 of the #slots to allocate. +pub fn kata_cnode_alloc(size_bits: usize) -> Result +{ + let mut objs = ObjDescBundle::new( + unsafe { RECV_CNODE }, + unsafe { RECV_CNODE_DEPTH }, + vec![ ObjDesc::new(seL4_CapTableObject, size_bits, /*cptr=*/ 0 )], + ); + kata_object_alloc(&objs)?; + objs.move_objects_to_toplevel(unsafe { SELF_CNODE }, seL4_WordBits as u8)?; + Ok(objs) +} + +#[cfg(feature = "CONFIG_KERNEL_MCS")] +#[inline] +#[allow(dead_code)] +pub fn kata_sched_context_alloc(size_bits: usize) -> Result +{ + let mut objs = ObjDescBundle::new( + unsafe { RECV_CNODE }, + unsafe { RECV_CNODE_DEPTH }, + vec![ ObjDesc::new(seL4_SchedContextObject, size_bits, /*cptr=*/ 0) ], + ); + kata_object_alloc(&objs)?; + objs.move_objects_to_toplevel(unsafe { SELF_CNODE }, seL4_WordBits as u8)?; + Ok(objs) +} + +#[cfg(feature = "CONFIG_KERNEL_MCS")] +#[inline] +#[allow(dead_code)] +pub fn kata_reply_alloc() -> Result +{ + let mut objs = ObjDescBundle::new( + unsafe { RECV_CNODE }, + unsafe { RECV_CNODE_DEPTH }, + vec![ ObjDesc::new(seL4_ReplyObject, 1, /*cptr=*/ 0) ], + ); + kata_object_alloc(&objs)?; + objs.move_objects_to_toplevel(unsafe { SELF_CNODE }, seL4_WordBits as u8)?; + Ok(objs) +} + +#[inline] +#[allow(dead_code)] +// Wrapper for allocating 4K pages. +pub fn kata_frame_alloc(space_bytes: usize) -> Result +{ + fn howmany(value: usize, unit: usize) -> usize { + (value + (unit - 1)) / unit + } + // NB: always allocate 4K pages + let mut objs = ObjDescBundle::new( + unsafe { RECV_CNODE }, + unsafe { RECV_CNODE_DEPTH }, + vec![ ObjDesc::new( + seL4_RISCV_4K_Page, + howmany(space_bytes, 1 << seL4_PageBits), + /*cptr=*/ 0, + )], + ); + kata_object_alloc(&objs)?; + objs.move_objects_to_toplevel(unsafe { SELF_CNODE }, seL4_WordBits as u8)?; + Ok(objs) +} + +#[inline] +#[allow(dead_code)] +pub fn kata_page_table_alloc() -> Result { + let mut objs = ObjDescBundle::new( + unsafe { RECV_CNODE }, + unsafe { RECV_CNODE_DEPTH }, + vec![ ObjDesc::new(seL4_RISCV_PageTableObject, 1, /*cptr=*/ 0) ], + ); + kata_object_alloc(&objs)?; + objs.move_objects_to_toplevel(unsafe { SELF_CNODE }, seL4_WordBits as u8)?; + Ok(objs) +} + +// TODO(sleffler): other objects, esp. vm stuff + +pub fn kata_object_free( + request: &ObjDescBundle, +) -> Result<(), MemoryManagerError> { + extern "C" { + // NB: this assumes the MemoryManager component is named "memory". + fn memory_free(c_data_len: u32, c_data: *const u8) -> MemoryManagerError; + } + trace!("kata_object_free {:?}", request); // XXX + let raw_data = &mut [0u8; RAW_OBJ_DESC_DATA_SIZE]; + postcard::to_slice(request, &mut raw_data[..]) + .map_err(|_| MemoryManagerError::MmeSerializeFailed)?; + unsafe { + // Attach our CNode for returning objects; the CAmkES template + // forces extraCaps=1 when constructing the MessageInfo struct + // used in the seL4_Call. + seL4_SetCap(0, request.cnode); + + memory_free(raw_data.len() as u32, raw_data.as_ptr()).into() + } +} + +impl ObjDescBundle { + // Move objects from our top-level CSpace to |dest_cnode| and return + // mutate Object Descriptor with adjusted cptr's. + fn move_objects_from_toplevel( + &mut self, + dest_cnode: seL4_CPtr, + dest_depth: u8, + ) -> Result<(), MemoryManagerError> { + unsafe { + let mut dest_slot = 0; + for od in &mut self.objs { + for offset in 0..od.retype_count() { + // XXX cleanup on error? + seL4_CNode_Move( + /*root=*/ dest_cnode, + /*dest_index=*/ dest_slot, + /*dest_depth=*/ dest_depth, + /*src_root=*/ self.cnode, + /*src_index=*/ od.cptr + offset, + /*src_depth=*/ self.depth, + ).map_err(|_| MemoryManagerError::MmeObjCapInvalid)?; + dest_slot += 1; + } + // XXX assumes od.cptr's are sequential + od.cptr = dest_slot - od.retype_count(); + EMPTY_SLOT -= od.retype_count(); // XXX assumes no intervening alloc + } + } + self.cnode = dest_cnode; + self.depth = dest_depth; + Ok(()) + } +} + +#[allow(dead_code)] +pub fn kata_object_free_toplevel(objs: &ObjDescBundle) -> Result<(), MemoryManagerError> { + let mut objs_mut = objs.clone(); + objs_mut.move_objects_from_toplevel( + unsafe { RECV_CNODE }, + unsafe { RECV_CNODE_DEPTH } + )?; + kata_object_free(&objs_mut) +} + +#[allow(dead_code)] +pub fn kata_memory_stats() -> Result { + extern "C" { + // NB: this assumes the MemoryManager component is named "memory". + fn memory_stats(c_data: *mut RawMemoryStatsData) -> MemoryManagerError; + } + let raw_data = &mut [0u8; RAW_MEMORY_STATS_DATA_SIZE]; + match unsafe { memory_stats(raw_data as *mut _) }.into() { + MemoryManagerError::MmeSuccess => { + let stats = postcard::from_bytes::(raw_data) + .map_err(|_| MemoryManagerError::MmeDeserializeFailed)?; + Ok(stats) + } + status => Err(status), + } +} diff --git a/apps/system/components/MemoryManager/kata-memory-manager/Cargo.toml b/apps/system/components/MemoryManager/kata-memory-manager/Cargo.toml new file mode 100644 index 0000000..21d2b34 --- /dev/null +++ b/apps/system/components/MemoryManager/kata-memory-manager/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "kata-memory-manager" +version = "0.1.0" +description = "Kata OS MemoryManager service" +edition = "2018" + +[dependencies] +kata-allocator = { path = "../../DebugConsole/kata-allocator" } +kata-os-common = { path = "../../kata-os-common" } +kata-memory-interface = { path = "../kata-memory-interface" } +log = "0.4" +smallvec = "1.2" +spin = "0.9" diff --git a/apps/system/components/MemoryManager/kata-memory-manager/src/lib.rs b/apps/system/components/MemoryManager/kata-memory-manager/src/lib.rs new file mode 100644 index 0000000..ca6bde1 --- /dev/null +++ b/apps/system/components/MemoryManager/kata-memory-manager/src/lib.rs @@ -0,0 +1,50 @@ +//! Kata OS global memory management support + +#![cfg_attr(not(test), no_std)] + +use core::ops::Range; +use kata_memory_interface::ObjDescBundle; +use kata_memory_interface::MemoryError; +use kata_memory_interface::MemoryManagerInterface; +use kata_memory_interface::MemoryManagerStats; +use kata_os_common::sel4_sys; +use sel4_sys::seL4_CPtr; +use sel4_sys::seL4_UntypedDesc; +use spin::Mutex; + +mod memory_manager; +pub use memory_manager::MemoryManager; + +// KataMemoryManager bundles an instance of the MemoryManager that operates +// on KataOS interfaces and synchronizes public use with a Mutex. There is +// a two-step dance to setup an instance because we want KATA_MEMORY static +// and MemoryManager is incapable of supplying a const fn due it's use of +// hashbrown::HashMap. +pub struct KataMemoryManager { + manager: Mutex>, +} +impl KataMemoryManager { + // Constructs a partially-initialized instance; to complete call init(). + pub const fn empty() -> KataMemoryManager { + KataMemoryManager { + manager: Mutex::new(None), + } + } + + // Finishes the setup started by empty(): + pub fn init(&self, ut_slots: Range, untypeds: &[seL4_UntypedDesc]) { + *self.manager.lock() = Some(MemoryManager::new(ut_slots, untypeds)); + } +} +// These just lock accesses and handle the necessary indirection. +impl MemoryManagerInterface for KataMemoryManager { + fn alloc(&mut self, objs: &ObjDescBundle) -> Result<(), MemoryError> { + self.manager.lock().as_mut().unwrap().alloc(objs) + } + fn free(&mut self, objs: &ObjDescBundle) -> Result<(), MemoryError> { + self.manager.lock().as_mut().unwrap().free(objs) + } + fn stats(&self) -> Result { + self.manager.lock().as_ref().unwrap().stats() + } +} diff --git a/apps/system/components/MemoryManager/kata-memory-manager/src/memory_manager/mod.rs b/apps/system/components/MemoryManager/kata-memory-manager/src/memory_manager/mod.rs new file mode 100644 index 0000000..a48635e --- /dev/null +++ b/apps/system/components/MemoryManager/kata-memory-manager/src/memory_manager/mod.rs @@ -0,0 +1,263 @@ +//! Kata OS global memory management support + +extern crate alloc; +use core::ops::Range; +use kata_memory_interface::ObjDesc; +use kata_memory_interface::ObjDescBundle; +use kata_memory_interface::MemoryError; +use kata_memory_interface::MemoryManagerInterface; +use kata_memory_interface::MemoryManagerStats; +use kata_os_common::sel4_sys; +use log::{debug, error}; +use sel4_sys::seL4_CPtr; +use sel4_sys::seL4_CNode_Delete; +use sel4_sys::seL4_Error; +use sel4_sys::seL4_Result; +use sel4_sys::seL4_Word; +use sel4_sys::seL4_UntypedDesc; +use sel4_sys::seL4_Untyped_Retype; +use smallvec::SmallVec; + +// SmallVec capacity for untyped memory slabs. There are two instances; +// one for anonymous memory and one for device-backed memory. The memory +// manager is expected to be setup as a static global so these data +// structures will land in .bss and only overflow to the heap if +// initialized with more than this count. +const UNTYPED_SLAB_CAPACITY: usize = 64; // # slabs kept inline + +// The MemoryManager supports allocating & freeing seL4 objects that are +// instantiated from UntypedMemory "slabs". Allocation causes untyped memory +// to be converted to concrete types. Freeing deletes the specified capabilities +// and updates the bookkeeping. Note that a free only releases the specified +// cap; if there are dups or derived objects the memory will not be returned +// to the untyped slab from which it was allocated and the bookkeeping done +// here will be out of sync with the kernel. +// TODO(sleffler): support device-backed memory objects +#[derive(Debug)] +struct UntypedSlab { + pub size_bits: usize, // XXX only used to sort + pub _base_paddr: seL4_Word, // Physical address of slab start + pub _last_paddr: seL4_Word, // Physical address of slab end + pub cptr: seL4_CPtr, // seL4 untyped object + pub _next_paddr: seL4_Word, // Physical address of next available chunk +} +impl UntypedSlab { + fn new(ut: &seL4_UntypedDesc, cptr: seL4_CPtr) -> Self { + UntypedSlab { + size_bits: ut.size_bits(), + _base_paddr: ut.paddr, + _last_paddr: ut.paddr + (1 << ut.size_bits()), + cptr: cptr, + _next_paddr: ut.paddr, + } + } + fn _size(&self) -> usize { l2tob(self.size_bits) } + fn size_bits(&self) -> usize { self.size_bits } +} +pub struct MemoryManager { + untypeds: SmallVec<[UntypedSlab; UNTYPED_SLAB_CAPACITY]>, + _device_untypeds: SmallVec<[UntypedSlab; UNTYPED_SLAB_CAPACITY]>, + cur_untyped: usize, + _cur_device_untyped: usize, + + total_bytes: usize, // Total available space + allocated_bytes: usize, // Amount of space currently allocated + requested_bytes: usize, // Amount of space allocated over all time + + allocated_objs: usize, // # seL4 objects currently allocated + requested_objs: usize, // # seL4 objects allocated over all time + + // Retype requests failed due to insufficient available memory. + untyped_slab_too_small: usize, + + // Alloc requests failed due to lack of untyped memory (NB: may be + // due to fragmentation of untyped slabs). + out_of_memory: usize, +} + +fn _howmany(value: usize, unit: usize) -> usize { + value + (unit - 1) / unit +} +fn _round_up(value: usize, align: usize) -> usize { + _howmany(value, align) * align +} + +// Log2 bits to bytes. +fn l2tob(size_bits: usize) -> usize { 1 << size_bits } + +impl MemoryManager { + // Creates a new MemoryManager instance. The allocator is seeded + // from the untyped memory descriptors. + pub fn new(slots: Range, untypeds: &[seL4_UntypedDesc]) -> Self { + assert!(untypeds.len() > 0); + assert_eq!(slots.end - slots.start, untypeds.len()); + let mut m = MemoryManager { + untypeds: SmallVec::new(), + _device_untypeds: SmallVec::new(), + cur_untyped: 0, + _cur_device_untyped: 0, + + total_bytes: 0, + allocated_bytes: 0, + requested_bytes: 0, + + allocated_objs: 0, + requested_objs: 0, + + untyped_slab_too_small: 0, + out_of_memory: 0, + }; + for (ut_index, ut) in untypeds.iter().enumerate() { + if ut.is_device() { + m._device_untypeds.push(UntypedSlab::new(ut, slots.start + ut_index)); + } else { + m.untypeds.push(UntypedSlab::new(ut, slots.start + ut_index)); + m.total_bytes += l2tob(ut.size_bits()); + } + } + // Sort non-device slabs by descending size. + m.untypeds.sort_unstable_by(|a, b| b.size_bits().cmp(&a.size_bits())); + m + } + + // Total available space. + pub fn total_available_space(&self) -> usize { + self.total_bytes + } + // Current allocated space + pub fn allocated_space(&self) -> usize { + self.allocated_bytes + } + // Current free space. + pub fn free_space(&self) -> usize { + self.total_bytes - self.allocated_bytes + } + // Total space allocated over time + pub fn total_requested_space(&self) -> usize { + self.requested_bytes + } + + // Current allocated objects + pub fn allocated_objs(&self) -> usize { + self.allocated_objs + } + // Total objects allocated over time + pub fn total_requested_objs(&self) -> usize { + self.requested_objs + } + + pub fn untyped_slab_too_small(&self) -> usize { + self.untyped_slab_too_small + } + pub fn out_of_memory(&self) -> usize { + self.out_of_memory + } + + fn retype_untyped( + free_untyped: seL4_CPtr, + root: seL4_CPtr, + obj: &ObjDesc, + ) -> seL4_Result { + unsafe { + seL4_Untyped_Retype( + free_untyped, + /*type=*/ obj.type_.into(), + /*size_bits=*/ obj.retype_size_bits().unwrap(), + /*root=*/ root, + /*node_index=*/ 0, // Ignored 'cuz depth is zero + /*node_depth=*/ 0, // NB: store in cnode + /*node_offset=*/ obj.cptr, + /*num_objects=*/ obj.retype_count(), + ) + } + } + + fn delete_caps(root: seL4_CPtr, depth: u8, od: &ObjDesc) -> seL4_Result { + for offset in 0..od.retype_count() { + // XXX warn about errors? + unsafe { seL4_CNode_Delete(root, od.cptr + offset, depth) }?; + } + Ok(()) + } +} + +impl MemoryManagerInterface for MemoryManager { + fn alloc(&mut self, bundle: &ObjDescBundle) -> Result<(), MemoryError> { + // TODO(sleffler): split by device vs no-device (or allow mixing) + let first_ut = self.cur_untyped; + let mut ut_index = first_ut; + + let mut allocated_bytes: usize = 0; + let mut allocated_objs: usize = 0; + + for od in &bundle.objs { + // NB: we don't check slots are available (the kernel will tell us). + // XXX check size_bytes() against untyped slab? (depend on kernel for now) + while let Err(e) = + // XXX ASIDPool maps to UntypedObject? + MemoryManager::retype_untyped(self.untypeds[ut_index].cptr, bundle.cnode, od) + { + if e != seL4_Error::seL4_NotEnoughMemory { + // Should not happen. + // TODO(sleffler): reclaim allocations + error!("Allocation request failed (retype returned {:?})", e); + return Err(MemoryError::UnknownMemoryError); + } + // This untyped does not have enough available space, try + // the next slab until we exhaust all slabs. This is the best + // we can do without per-slab bookkeeping. + self.untyped_slab_too_small += 1; + ut_index = (ut_index + 1) % self.untypeds.len(); + debug!("Advance to untyped slab {}", ut_index); + if ut_index == first_ut { + // TODO(sleffler): reclaim allocations + self.out_of_memory += 1; + debug!("Allocation request failed (out of space)"); + return Err(MemoryError::AllocFailed); + } + } + allocated_objs += od.retype_count(); + allocated_bytes += od.size_bytes().unwrap(); + + } + self.cur_untyped = ut_index; + + self.allocated_bytes += allocated_bytes; + self.allocated_objs += allocated_objs; + + // NB: does not include requests that fail + self.requested_objs += allocated_objs; + self.requested_bytes += allocated_bytes; + + Ok(()) + } + fn free(&mut self, bundle: &ObjDescBundle) -> Result<(), MemoryError> { + for od in &bundle.objs { + // XXX support leaving objects so client can do bulk reclaim on exit + // (maybe require cptr != 0) + if MemoryManager::delete_caps(bundle.cnode, bundle.depth, od).is_ok() { + // NB: atm we do not do per-untyped bookkeeping so just track + // global stats. + self.allocated_bytes -= od.size_bytes().ok_or(MemoryError::ObjTypeInvalid)?; + self.allocated_objs -= od.retype_count(); + } + } + Ok(()) + } + fn stats(&self) -> Result { + Ok(MemoryManagerStats { + allocated_bytes: self.allocated_space(), + free_bytes: self.free_space(), + total_requested_bytes: self.total_requested_space(), + // TODO(sleffler): track fragmentation + // NB: assumes all heap usage is for the buddy allocator + overhead_bytes: 0, + + allocated_objs: self.allocated_objs(), + total_requested_objs: self.total_requested_objs(), + + untyped_slab_too_small: self.untyped_slab_too_small(), + out_of_memory: self.out_of_memory(), + }) + } +} diff --git a/apps/system/components/kata-os-common/src/capdl/mod.rs b/apps/system/components/kata-os-common/src/capdl/mod.rs index 13d5b4c..38e0bec 100644 --- a/apps/system/components/kata-os-common/src/capdl/mod.rs +++ b/apps/system/components/kata-os-common/src/capdl/mod.rs @@ -12,6 +12,7 @@ use core::mem::size_of; use cstr_core; +use sel4_sys::SEL4_BOOTINFO_HEADER_BOOTINFO; use sel4_sys::SEL4_BOOTINFO_HEADER_FDT; use sel4_sys::SEL4_BOOTINFO_HEADER_PADDING; use sel4_sys::SEL4_BOOTINFO_HEADER_X86_ACPI_RSDP; @@ -461,6 +462,12 @@ impl From for seL4_ObjectType { } } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct CDL_CNodeExtraInfo { + pub has_untyped_memory: bool, +} + #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct CDL_TCBExtraInfo { @@ -536,6 +543,7 @@ pub enum CDL_FrameFill_BootInfoEnum_t { CDL_FrameFill_BootInfo_X86_Framebuffer = SEL4_BOOTINFO_HEADER_X86_FRAMEBUFFER as isize, CDL_FrameFill_BootInfo_X86_TSC_Freq = SEL4_BOOTINFO_HEADER_X86_TSC_FREQ as isize, CDL_FrameFill_BootInfo_FDT = SEL4_BOOTINFO_HEADER_FDT as isize, + CDL_FrameFill_BootInfo_BootInfo = SEL4_BOOTINFO_HEADER_BOOTINFO as isize, } impl From for usize { fn from(bi_type: CDL_FrameFill_BootInfoEnum_t) -> usize { @@ -666,6 +674,11 @@ impl<'a> CDL_Object { self.paddr().map_or(false, |v| v != 0) } + #[inline] + pub fn cnode_has_untyped_memory(&self) -> bool { + unsafe { self.extra.cnode_extra.has_untyped_memory } + } + // TODO(sleffler): maybe assert type_ before referencing union members // NB: return everything as seL4_Word to minimize conversions #[inline] @@ -804,6 +817,7 @@ pub union CDL_ObjectExtra { pub msiirq_extra: CDL_MSIIRQExtraInfo, pub armirq_extra: CDL_ARMIRQExtraInfo, pub frame_extra: CDL_FrameExtraInfo, + pub cnode_extra: CDL_CNodeExtraInfo, // Physical address; only defined for untyped objects. pub paddr: seL4_Word, diff --git a/apps/system/components/kata-os-common/src/kata-os-model/feature/dynamic_alloc.rs b/apps/system/components/kata-os-common/src/kata-os-model/feature/dynamic_alloc.rs index e0d25c7..3aa0be3 100644 --- a/apps/system/components/kata-os-common/src/kata-os-model/feature/dynamic_alloc.rs +++ b/apps/system/components/kata-os-common/src/kata-os-model/feature/dynamic_alloc.rs @@ -2,9 +2,11 @@ use crate::KataOsModel; use capdl::kobject_t::KOBJECT_FRAME; +use capdl::CDL_FrameFillType_t::*; +use capdl::CDL_FrameFill_BootInfoEnum_t::*; use capdl::CDL_ObjectType::*; use capdl::*; -use log::{debug, trace}; +use log::{debug, info, trace}; use smallvec::SmallVec; use sel4_sys::seL4_BootInfo; @@ -58,6 +60,10 @@ impl<'a> KataOsModel<'a> { // possibility of vpsace_roots spilling to the heap. let mut roots = SmallVec::new(); + // Record objects that receive special treatment later on. + let mut bootinfo_frame: CDL_ObjID = CDL_ObjID::MAX; + let mut untyped_cnode: CDL_ObjID = CDL_ObjID::MAX; + // First, allocate most objects and record the cslot locations. // The exception is ASIDPools, where create_object only allocates // the backing untypeds. @@ -90,11 +96,40 @@ impl<'a> KataOsModel<'a> { } } - // Capture VSpace roots for later use. - if obj.r#type() == CDL_TCB { - if let Some(root_cap) = obj.get_cap_at(CDL_TCB_VTable_Slot) { - roots.push(root_cap.obj_id); + match obj.r#type() { + // Capture VSpace roots & TCBs for later use. + CDL_TCB => { + if let Some(root_cap) = obj.get_cap_at(CDL_TCB_VTable_Slot) { + roots.push(root_cap.obj_id); + } } + // Capture one bootinfo frame for processing below. + CDL_Frame => { + fn is_bootinfo_frame(obj: &CDL_Object) -> bool { + let frame_fill = &obj.frame_fill(0).unwrap(); + frame_fill.type_ == CDL_FrameFill_BootInfo && + frame_fill.get_bootinfo().type_ == CDL_FrameFill_BootInfo_BootInfo + } + if is_bootinfo_frame(obj) { + // NB: can instantiate multiple frames but only one + // CNode can receive the untypeds since we must move + // 'em from the rootserver (since they are "derived"). + // XXX maybe just complain & ignore + info!("Found bootinfo Frame at {}", obj_id); + assert!(!is_objid_valid(bootinfo_frame)); + bootinfo_frame = obj_id; + } + } + // Look for a CNode associated with any bootinfo frame. + CDL_CNode => { + if obj.cnode_has_untyped_memory() { + if is_objid_valid(untyped_cnode) { + info!("Duplicate bootinfo cnode at {}, prev {}", obj_id, untyped_cnode); + } + untyped_cnode = obj_id; + } + } + _ => {} } // Record the cslot assigned to the object. self.set_orig_cap(obj_id, free_slot); @@ -140,6 +175,15 @@ impl<'a> KataOsModel<'a> { roots.dedup(); self.vspace_roots = roots; + // Record any CNode designated to receive the UntypedMemory caps when + // constructing their CSpace. + // NB: we conditionally assign based on there being a BootInfo frame + // because the UntypedMemory caps are not useful w/o the descriptors. + if is_objid_valid(bootinfo_frame) { + assert!(is_objid_valid(untyped_cnode)); + self.untyped_cnode = untyped_cnode; + } + Ok(()) } diff --git a/apps/system/components/kata-os-common/src/kata-os-model/mod.rs b/apps/system/components/kata-os-common/src/kata-os-model/mod.rs index e8b0c66..a1b6d85 100644 --- a/apps/system/components/kata-os-common/src/kata-os-model/mod.rs +++ b/apps/system/components/kata-os-common/src/kata-os-model/mod.rs @@ -19,7 +19,7 @@ use core::mem::size_of; use core::ptr; use cpio::CpioNewcReader; use cstr_core::CStr; -use log::{debug, error, trace}; +use log::{debug, info, error, trace}; use smallvec::SmallVec; use static_assertions::*; @@ -127,7 +127,7 @@ const_assert!(seL4_WordBits == 32 || seL4_WordBits == 64); const CONFIG_CAPDL_LOADER_FILLS_PER_FRAME: usize = 1; -fn _BIT(bit_num: usize) -> usize { 1 << bit_num } +fn BIT(bit_num: usize) -> usize { 1 << bit_num } fn ROUND_UP(a: usize, b: usize) -> usize { if (a % b) == 0 { a @@ -193,6 +193,8 @@ pub struct KataOsModel<'a> { #[cfg(feature = "CONFIG_ARM_SMMU")] sid_number: usize, + untyped_cnode: CDL_ObjID, + extended_bootinfo_table: [*const seL4_BootInfoHeader; SEL4_BOOTINFO_HEADER_NUM], vspace_roots: SmallVec<[CDL_ObjID; 32]>, // NB: essentially #components @@ -219,6 +221,8 @@ impl<'a> KataOsModel<'a> { #[cfg(feature = "CONFIG_ARM_SMMU")] sid_number: 0, + untyped_cnode: CDL_ObjID::MAX, + extended_bootinfo_table: [ptr::null(); SEL4_BOOTINFO_HEADER_NUM], vspace_roots: SmallVec::new(), @@ -255,6 +259,19 @@ impl<'a> KataOsModel<'a> { Ok(()) } + pub fn handoff_capabilities(&mut self) -> seL4_Result { + // Hand-off capabilities needed to dynamically create seL4 objects. + // The MemoryManager needs the UntypedMemory slabs to instantiate + // objects. The ProcessManager needs various other object(s) to + // configure TCB's but those are expressed with capDL and moved + // as part of the work done by init_cspsace. + + if is_objid_valid(self.untyped_cnode) { + self.handoff_untypeds(self.untyped_cnode)?; + } + Ok(()) + } + pub fn start_threads(&self) -> seL4_Result { trace!("Starting threads..."); for (obj_id, obj) in self.spec.obj_slice().iter().enumerate() { @@ -468,6 +485,7 @@ impl<'a> KataOsModel<'a> { let endpoint_obj = endpoint_cap.obj_id; let badge = endpoint_cap.cap_data(); let endpoint_cptr = if badge != 0 { + // Endpoint needs badging, mint a new cap self.mint_cap(endpoint_obj, badge)? } else { self.get_orig_cap(endpoint_obj) @@ -554,7 +572,8 @@ impl<'a> KataOsModel<'a> { match bi.type_ { CDL_FrameFill_BootInfo_X86_VBE | CDL_FrameFill_BootInfo_X86_TSC_Freq - | CDL_FrameFill_BootInfo_FDT => {} + | CDL_FrameFill_BootInfo_FDT + | CDL_FrameFill_BootInfo_BootInfo => {} _ => { panic!("Unsupported bootinfo frame fill type {:?}", bi.type_) } @@ -566,8 +585,10 @@ impl<'a> KataOsModel<'a> { let header: *const seL4_BootInfoHeader = self.extended_bootinfo_table[usize::from(bi.type_)]; - /* Check if the bootinfo has been found */ - if header.is_null() { + // Check if the bootinfo has been found. + if bi.type_ == CDL_FrameFill_BootInfo_BootInfo { + self.fill_with_bootinfo(dest, max_len); + } else if header.is_null() { /* No bootinfo. * If we're at the start of a block, copy an empty header across, otherwise skip the copy */ if block_offset == 0 { @@ -595,6 +616,35 @@ impl<'a> KataOsModel<'a> { } } + // Fill with our BootInfo patched per the destination cnode. + fn fill_with_bootinfo(&self, dest: *mut u8, max_len: usize) { + unsafe { + ptr::copy_nonoverlapping( + ptr::addr_of!(self.bootinfo.extraLen) as *const u8, dest as _, max_len) + } + + assert!(is_objid_valid(self.untyped_cnode)); + let cnode = &self.spec.obj_slice()[self.untyped_cnode]; + assert_eq!(cnode.r#type(), CDL_CNode); + + // NB: page-aligned so safe to deref. + let bootinfo = unsafe { &mut *(dest as *mut seL4_BootInfo) }; + + // UntypedMemory caps are appended to the specified slots. + bootinfo.untyped = sel4_sys::seL4_SlotRegion { + start: cnode.num_slots() + 1, + end: cnode.num_slots() + 1 + + (self.bootinfo.untyped.end - self.bootinfo.untyped.start), + }; + // Update the empty region to support dynamic cap allocation. + bootinfo.empty = sel4_sys::seL4_SlotRegion { + start: bootinfo.untyped.end + 1, + end: BIT(cnode.size_bits()), + }; + // NB: we could adjust the UntypedDesc's but they will not + // reflect rootserver resources that are released at exit + } + // Fill a frame's contents from a file in the cpio archive; // in particular this loads each CAmkES component's executable. fn fill_frame_with_filedata(&self, base: usize, frame_fill: &CDL_FrameFill_Element_t) { @@ -880,6 +930,53 @@ impl<'a> KataOsModel<'a> { Ok(()) } + fn handoff_untypeds(&mut self, cnode_obj_id: CDL_ObjID) -> seL4_Result { + let num_untypeds = self.bootinfo.untyped.end - self.bootinfo.untyped.start; + + // NB: UntypedMemory caps are appended to the CAmkES-generated slots + let dest_start = self.spec.obj_slice()[cnode_obj_id].num_slots() + 1; + + info!("Hand-off {} untypeds from {} to {}", + num_untypeds, + self.bootinfo.untyped.start, + dest_start); + // NB: we let kernel tell us if the CNode is too small. + for ut in 0..num_untypeds { + self.handoff_cap(cnode_obj_id, + /*src_index=*/ self.bootinfo.untyped.start + ut, + /*dest_index=*/ dest_start + ut)?; + } + Ok(()) + } + + fn handoff_cap( + &mut self, + cnode_obj_id: CDL_ObjID, + src_index: seL4_CPtr, + dest_index: seL4_CPtr + ) -> seL4_Result { + let cnode = &self.spec.obj_slice()[cnode_obj_id]; + assert_eq!(cnode.r#type(), CDL_CNode); + + let src_root = seL4_CapInitThreadCNode; + let src_depth = seL4_WordBits as u8; + + // Blindly use the dup'd cap a la init_cnode_slot. + let dest_root = self.get_dup_cap(cnode_obj_id); + let dest_depth: u8 = cnode.size_bits.try_into().unwrap(); + + unsafe { + seL4_CNode_Move( + dest_root, + dest_index, + dest_depth, + src_root, + src_index, + src_depth, + ) + } + } + fn init_cnode_slot( &self, mode: InitCnodeCmode, diff --git a/apps/system/components/kata-os-common/src/sel4-sys/lib.rs b/apps/system/components/kata-os-common/src/sel4-sys/lib.rs index 3acff45..59cf1af 100644 --- a/apps/system/components/kata-os-common/src/sel4-sys/lib.rs +++ b/apps/system/components/kata-os-common/src/sel4-sys/lib.rs @@ -377,4 +377,5 @@ pub const SEL4_BOOTINFO_HEADER_X86_ACPI_RSDP: usize = 3; pub const SEL4_BOOTINFO_HEADER_X86_FRAMEBUFFER: usize = 4; pub const SEL4_BOOTINFO_HEADER_X86_TSC_FREQ: usize = 5; pub const SEL4_BOOTINFO_HEADER_FDT: usize = 6; -pub const SEL4_BOOTINFO_HEADER_NUM: usize = SEL4_BOOTINFO_HEADER_FDT + 1; +pub const SEL4_BOOTINFO_HEADER_BOOTINFO: usize = 7; // Copy of rootserver's BootInfo +pub const SEL4_BOOTINFO_HEADER_NUM: usize = SEL4_BOOTINFO_HEADER_BOOTINFO + 1; diff --git a/apps/system/interfaces/MemoryInterface.camkes b/apps/system/interfaces/MemoryInterface.camkes new file mode 100644 index 0000000..9a37cd2 --- /dev/null +++ b/apps/system/interfaces/MemoryInterface.camkes @@ -0,0 +1,7 @@ +procedure MemoryInterface { + include ; + + MemoryManagerError alloc(in char request[]); + MemoryManagerError free(in char request[]); + MemoryManagerError stats(out RawMemoryStatsData data); +}; diff --git a/apps/system/interfaces/MemoryManagerBindings.h b/apps/system/interfaces/MemoryManagerBindings.h new file mode 100644 index 0000000..37b07a0 --- /dev/null +++ b/apps/system/interfaces/MemoryManagerBindings.h @@ -0,0 +1,33 @@ +#ifndef __MEMORY_MANAGER_BINDINGS_H__ +#define __MEMORY_MANAGER_BINDINGS_H__ + +/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ + +#define RAW_OBJ_DESC_DATA_SIZE 2048 + +#define RAW_MEMORY_STATS_DATA_SIZE 100 + +typedef enum MemoryManagerError { + MmeSuccess = 0, + MmeObjCountInvalid, + MmeObjTypeInvalid, + MmeObjCapInvalid, + MmeSerializeFailed, + MmeDeserializeFailed, + MmeUnknownError, + MmeAllocFailed, + MmeFreeFailed, +} MemoryManagerError; + +typedef uint8_t RawMemoryStatsData[RAW_MEMORY_STATS_DATA_SIZE]; + +typedef struct MemoryManagerStats { + uintptr_t allocated_bytes; + uintptr_t free_bytes; + uintptr_t total_requested_bytes; + uintptr_t overhead_bytes; + uintptr_t allocated_objs; + uintptr_t total_requested_objs; +} MemoryManagerStats; + +#endif /* __MEMORY_MANAGER_BINDINGS_H__ */ diff --git a/apps/system/system.camkes b/apps/system/system.camkes index 576ebe2..6b30f54 100644 --- a/apps/system/system.camkes +++ b/apps/system/system.camkes @@ -17,6 +17,7 @@ import "components/OpenTitanUARTDriver/OpenTitanUARTDriver.camkes"; import "components/DebugConsole/DebugConsole.camkes"; import "components/ProcessManager/ProcessManager.camkes"; import "components/MlCoordinator/MlCoordinator.camkes"; +import "components/MemoryManager/MemoryManager.camkes"; import "components/StorageManager/StorageManager.camkes"; import "components/SecurityCoordinator/SecurityCoordinator.camkes"; @@ -54,6 +55,7 @@ assembly { component OpenTitanUART uart; component OpenTitanUARTDriver uart_driver; + component MemoryManager memory_manager; component ProcessManager process_manager; component MlCoordinator ml_coordinator; component DebugConsole debug_console; @@ -98,6 +100,15 @@ assembly { connection seL4RPCCall shell_storage(from debug_console.storage, to storage_manager.storage); + // Connect the MemoryInterface to each component that needs to allocate + // global memory. Note this allocates a 4KB shared memory region to each + // component and copies data between components. + connection seL4RPCOverMultiSharedData multi_memory( + from debug_console.memory, + // TODO(sleffler): from process_manager.memory, + // TOOD(sleffler): from ml_coordinator.memory, + to memory_manager.memory); + // Connect the SecurityCoordinatorInterface to each component that needs // access to the Security Core. Note this allocates a 4KB shared memory // region to each component and copies data between components. @@ -124,6 +135,7 @@ assembly { connection seL4RPCOverMultiSharedData multi_logger( from process_manager.logger, from ml_coordinator.logger, + from memory_manager.logger, from security_coordinator.logger, from storage_manager.logger, to debug_console.logger); diff --git a/easy-settings.cmake b/easy-settings.cmake index edd610b..ff50b38 100644 --- a/easy-settings.cmake +++ b/easy-settings.cmake @@ -1,6 +1,6 @@ set(CAMKES_APP "system" CACHE STRING "The one and only CAmkES application in this project") -set(CAPDL_LOADER_APP "capdl-loader-app" CACHE STRING "") -#set(CAPDL_LOADER_APP "kata-os-rootserver" CACHE STRING "") +#set(CAPDL_LOADER_APP "capdl-loader-app" CACHE STRING "") +set(CAPDL_LOADER_APP "kata-os-rootserver" CACHE STRING "") set(PLATFORM "sparrow" CACHE STRING "The one and only seL4 platform for Sparrow") set(KernelSel4Arch "riscv32" CACHE STRING "Specifies 32-bit branch of the seL4 spike platform")