Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPC: Initial commit of Inter-process communication #535

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down Expand Up @@ -62,6 +63,7 @@ members = [
"apis/buzzer",
"apis/console",
"apis/gpio",
"apis/ipc",
"apis/i2c_master",
"apis/i2c_master_slave",
"apis/key_value",
Expand Down
16 changes: 16 additions & 0 deletions apis/ipc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "libtock_ipc"
version = "0.1.0"
authors = [
"Tock Project Developers <[email protected]>",
"Alistair Francis <[email protected]>",
]
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" }

119 changes: 119 additions & 0 deletions apis/ipc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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: Syscalls, C: Config = DefaultConfig>(S, C);

impl<S: Syscalls, C: Config> IPC<S, C> {
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<usize, ErrorCode> {
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::<C, DRIVER_NUM, { ro_allow::BUFFER }>(allow_ro, pkg_name)?;

let svc_id: u32 = S::command(DRIVER_NUM, i2c_master_cmd::DISCOVER, 0, 0)
.to_result::<u32, ErrorCode>()?;

Ok(svc_id as usize)
},
)
}

pub fn wait_for_client_notify(pkg_name: &[u8]) -> Result<usize, ErrorCode> {
let svc_id = IPC::<S, C>::discover(pkg_name)?;

let called: Cell<Option<(u32, u32, u32)>> = 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::<C, DRIVER_NUM, { ro_allow::BUFFER }>(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<T: platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config>
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;
}
12 changes: 12 additions & 0 deletions platform/src/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ pub trait Syscalls: RawSyscalls + Sized {
upcall: &'share U,
) -> Result<(), ErrorCode>;

fn ipc_subscribe<
'share,
IDS: subscribe::SupportsId<DRIVER_NUM, 0>,
U: Upcall<IDS>,
CONFIG: subscribe::Config,
const DRIVER_NUM: u32,
>(
subscribe: share::Handle<Subscribe<'share, Self, DRIVER_NUM, 0>>,
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);
Expand Down
105 changes: 105 additions & 0 deletions platform/src/syscalls_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,111 @@ impl<S: RawSyscalls> Syscalls for S {
unsafe { inner::<Self, CONFIG>(DRIVER_NUM, SUBSCRIBE_NUM, upcall_fcn, upcall_data) }
}

fn ipc_subscribe<
'share,
IDS: subscribe::SupportsId<DRIVER_NUM, 0>,
U: Upcall<IDS>,
CONFIG: subscribe::Config,
const DRIVER_NUM: u32,
>(
_subscribe: share::Handle<Subscribe<'share, Self, DRIVER_NUM, 0>>,
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<S: Syscalls, IDS, U: Upcall<IDS>>(
arg0: u32,
arg1: u32,
arg2: u32,
data: Register,
) {
let exit: exit_on_drop::ExitOnDrop<S> = 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<S, IDS, U> 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<S: Syscalls, CONFIG: subscribe::Config>(
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::<S, IDS, U> 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::<Self, CONFIG>(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
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ pub mod gpio {
PullDown, PullNone, PullUp,
};
}
pub mod ipc {
use libtock_ipc as ipc;
pub type IPC = ipc::IPC<super::runtime::TockSyscalls>;
}
pub mod i2c_master {
use libtock_i2c_master as i2c_master;
pub type I2CMaster = i2c_master::I2CMaster<super::runtime::TockSyscalls>;
Expand Down
Loading