From d6f20046fc75601ae1dda658e3c8845cdbc6f1b2 Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Tue, 20 Feb 2024 14:56:20 +1000 Subject: [PATCH] IPC: Initial commit of Inter-process communication Signed-off-by: Alistair Francis --- Cargo.toml | 2 + apis/ipc/Cargo.toml | 16 +++++ apis/ipc/src/lib.rs | 119 ++++++++++++++++++++++++++++++++++ platform/src/syscalls.rs | 12 ++++ platform/src/syscalls_impl.rs | 105 ++++++++++++++++++++++++++++++ src/lib.rs | 4 ++ 6 files changed, 258 insertions(+) create mode 100644 apis/ipc/Cargo.toml create mode 100644 apis/ipc/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 584a5581..dcde9698 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ libtock_buzzer = { path = "apis/buzzer" } libtock_console = { path = "apis/console" } libtock_debug_panic = { path = "panic_handlers/debug_panic" } libtock_gpio = { path = "apis/gpio" } +libtock_ipc = { path = "apis/ipc" } libtock_i2c_master = { path = "apis/i2c_master" } libtock_i2c_master_slave = { path = "apis/i2c_master_slave" } libtock_key_value = { path = "apis/key_value" } @@ -62,6 +63,7 @@ members = [ "apis/buzzer", "apis/console", "apis/gpio", + "apis/ipc", "apis/i2c_master", "apis/i2c_master_slave", "apis/key_value", diff --git a/apis/ipc/Cargo.toml b/apis/ipc/Cargo.toml new file mode 100644 index 00000000..4087c031 --- /dev/null +++ b/apis/ipc/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "libtock_ipc" +version = "0.1.0" +authors = [ + "Tock Project Developers ", + "Alistair Francis ", +] +license = "Apache-2.0 OR MIT" +edition = "2021" +repository = "https://www.github.com/tock/libtock-rs" +rust-version.workspace = true +description = "libtock Inter-process communication" + +[dependencies] +libtock_platform = { path = "../../platform" } + diff --git a/apis/ipc/src/lib.rs b/apis/ipc/src/lib.rs new file mode 100644 index 00000000..c117c5c8 --- /dev/null +++ b/apis/ipc/src/lib.rs @@ -0,0 +1,119 @@ +#![no_std] + +use crate::share::Handle; +use core::cell::Cell; +use libtock_platform as platform; +use libtock_platform::share; +use libtock_platform::AllowRo; +use libtock_platform::Subscribe; +use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; + +pub struct IPC(S, C); + +impl IPC { + pub fn exists() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, i2c_master_cmd::EXISTS, 0, 0).to_result() + } + + /// # Summary + /// + /// Performs service discovery + /// + /// Retrieves the process identifier of the process with the given package + /// name, or a negative value on error. + /// + /// # Parameter + /// + /// * `pkg_name`: The package name of this service + /// + /// # Returns + /// On success: Ok(svc_id) + /// Where `svc_id` is the process id of the service + /// On failure: Err(ErrorCode) + pub fn discover(pkg_name: &[u8]) -> Result { + share::scope::< + ( + AllowRo<_, DRIVER_NUM, { ro_allow::BUFFER }>, + Subscribe<_, DRIVER_NUM, 0>, + ), + _, + _, + >( + |handle: Handle< + '_, + ( + AllowRo<'_, S, DRIVER_NUM, { ro_allow::BUFFER }>, + Subscribe<'_, _, DRIVER_NUM, 0>, + ), + >| { + let (allow_ro, _subscribe): ( + Handle<'_, AllowRo<'_, S, DRIVER_NUM, { ro_allow::BUFFER }>>, + Handle<'_, Subscribe<'_, S, DRIVER_NUM, 0>>, + ) = handle.split(); + S::allow_ro::(allow_ro, pkg_name)?; + + let svc_id: u32 = S::command(DRIVER_NUM, i2c_master_cmd::DISCOVER, 0, 0) + .to_result::()?; + + Ok(svc_id as usize) + }, + ) + } + + pub fn wait_for_client_notify(pkg_name: &[u8]) -> Result { + let svc_id = IPC::::discover(pkg_name)?; + + let called: Cell> = Cell::new(None); + share::scope::< + ( + AllowRo<_, DRIVER_NUM, { ro_allow::BUFFER }>, + Subscribe<_, DRIVER_NUM, 0>, + ), + _, + _, + >(|handle| { + let (allow_ro, subscribe) = handle.split(); + S::allow_ro::(allow_ro, pkg_name)?; + S::ipc_subscribe::<_, _, C, DRIVER_NUM>(subscribe, svc_id as u32, &called)?; + + let svc_id: u32 = S::command(DRIVER_NUM, i2c_master_cmd::DISCOVER, 0, 0).to_result()?; + + loop { + S::yield_wait(); + if let Some((pid, len, _)) = called.get() { + assert_eq!(pid, svc_id); + return Ok(len as usize); + } + } + }) + } +} + +/// System call configuration trait for `IPC`. +pub trait Config: + platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config +{ +} +impl + Config for T +{ +} + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- +const DRIVER_NUM: u32 = 0x10000; + +/// Ids for read-only allow buffers +#[allow(unused)] +mod ro_allow { + pub const BUFFER: u32 = 0; +} + +#[allow(unused)] +mod i2c_master_cmd { + pub const EXISTS: u32 = 0; + pub const DISCOVER: u32 = 1; + pub const SERVICE_NOTIFY: u32 = 2; + pub const CLIENT_NOTIFY: u32 = 3; +} diff --git a/platform/src/syscalls.rs b/platform/src/syscalls.rs index 74dba69b..a28d6fe3 100644 --- a/platform/src/syscalls.rs +++ b/platform/src/syscalls.rs @@ -37,6 +37,18 @@ pub trait Syscalls: RawSyscalls + Sized { upcall: &'share U, ) -> Result<(), ErrorCode>; + fn ipc_subscribe< + 'share, + IDS: subscribe::SupportsId, + U: Upcall, + CONFIG: subscribe::Config, + const DRIVER_NUM: u32, + >( + subscribe: share::Handle>, + svc_id: u32, + upcall: &'share U, + ) -> Result<(), ErrorCode>; + /// Unregisters the upcall with the given ID. If no upcall is registered /// with the given ID, `unsubscribe` does nothing. fn unsubscribe(driver_num: u32, subscribe_num: u32); diff --git a/platform/src/syscalls_impl.rs b/platform/src/syscalls_impl.rs index 96bfbec2..6c17a72f 100644 --- a/platform/src/syscalls_impl.rs +++ b/platform/src/syscalls_impl.rs @@ -144,6 +144,111 @@ impl Syscalls for S { unsafe { inner::(DRIVER_NUM, SUBSCRIBE_NUM, upcall_fcn, upcall_data) } } + fn ipc_subscribe< + 'share, + IDS: subscribe::SupportsId, + U: Upcall, + CONFIG: subscribe::Config, + const DRIVER_NUM: u32, + >( + _subscribe: share::Handle>, + svc_id: u32, + upcall: &'share U, + ) -> Result<(), ErrorCode> { + // The upcall function passed to the Tock kernel. + // + // Safety: data must be a reference to a valid instance of U. + unsafe extern "C" fn kernel_upcall>( + arg0: u32, + arg1: u32, + arg2: u32, + data: Register, + ) { + let exit: exit_on_drop::ExitOnDrop = Default::default(); + let upcall: *const U = data.into(); + unsafe { &*upcall }.upcall(arg0, arg1, arg2); + core::mem::forget(exit); + } + + // Inner function that does the majority of the work. This is not + // monomorphized over DRIVER_NUM to keep code size + // small. + // + // Safety: upcall_fcn must be kernel_upcall and upcall_data + // must be a reference to an instance of U that will remain valid as + // long as the 'scope lifetime is alive. Can only be called if a + // Subscribe<'scope, S, driver_num, subscribe_num> exists. + unsafe fn inner( + driver_num: u32, + subscribe_num: u32, + upcall_fcn: Register, + upcall_data: Register, + ) -> Result<(), ErrorCode> { + // Safety: syscall4's documentation indicates it can be used to call + // Subscribe. These arguments follow TRD104. kernel_upcall has the + // required signature. This function's preconditions mean that + // upcall is a reference to an instance of U that will remain valid + // until the 'scope lifetime is alive The existence of the + // Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM> guarantees + // that if this Subscribe succeeds then the upcall will be cleaned + // up before the 'scope lifetime ends, guaranteeing that upcall is + // still alive when kernel_upcall is invoked. + let [r0, r1, _, _] = unsafe { + S::syscall4::<{ syscall_class::SUBSCRIBE }>([ + driver_num.into(), + subscribe_num.into(), + upcall_fcn, + upcall_data, + ]) + }; + + let return_variant: ReturnVariant = r0.as_u32().into(); + // TRD 104 guarantees that Subscribe returns either Success with 2 + // U32 or Failure with 2 U32. We check the return variant by + // comparing against Failure with 2 U32 for 2 reasons: + // + // 1. On RISC-V with compressed instructions, it generates smaller + // code. FAILURE_2_U32 has value 2, which can be loaded into a + // register with a single compressed instruction, whereas + // loading SUCCESS_2_U32 uses an uncompressed instruction. + // 2. In the event the kernel malfuctions and returns a different + // return variant, the success path is actually safer than the + // failure path. The failure path assumes that r1 contains an + // ErrorCode, and produces UB if it has an out of range value. + // Incorrectly assuming the call succeeded will not generate + // unsoundness, and will likely lead to the application + // hanging. + if return_variant == return_variant::FAILURE_2_U32 { + // Safety: TRD 104 guarantees that if r0 is Failure with 2 U32, + // then r1 will contain a valid error code. ErrorCode is + // designed to be safely transmuted directly from a kernel error + // code. + return Err(unsafe { core::mem::transmute(r1.as_u32()) }); + } + + // r0 indicates Success with 2 u32s. Confirm the null upcall was + // returned, and it if wasn't then call the configured function. + // We're relying on the optimizer to remove this branch if + // returned_nonnull_upcall is a no-op. + // Note: TRD 104 specifies that the null upcall has address 0, + // not necessarily a null pointer. + let returned_upcall: usize = r1.into(); + if returned_upcall != 0usize { + CONFIG::returned_nonnull_upcall(driver_num, subscribe_num); + } + Ok(()) + } + + let upcall_fcn = (kernel_upcall:: as *const ()).into(); + let upcall_data = (upcall as *const U).into(); + // Safety: upcall's type guarantees it is a reference to a U that will + // remain valid for at least the 'scope lifetime. _subscribe is a + // reference to a Subscribe<'scope, Self, DRIVER_NUM, 0>, + // proving one exists. upcall_fcn and upcall_data are derived in ways + // that satisfy inner's requirements. + unsafe { inner::(DRIVER_NUM, svc_id, upcall_fcn, upcall_data) } + } + fn unsubscribe(driver_num: u32, subscribe_num: u32) { unsafe { // syscall4's documentation indicates it can be used to call diff --git a/src/lib.rs b/src/lib.rs index 90d0ff29..bf539576 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,10 @@ pub mod gpio { PullDown, PullNone, PullUp, }; } +pub mod ipc { + use libtock_ipc as ipc; + pub type IPC = ipc::IPC; +} pub mod i2c_master { use libtock_i2c_master as i2c_master; pub type I2CMaster = i2c_master::I2CMaster;