From acacd7df3c52ba0702b6ea92c27a022d8e1955f8 Mon Sep 17 00:00:00 2001 From: Kat Watson Date: Wed, 24 Jul 2024 10:29:16 -0700 Subject: [PATCH 01/10] Add IPC exists command support --- Cargo.toml | 2 ++ apis/kernel/ipc/Cargo.toml | 15 ++++++++++++++ apis/kernel/ipc/src/lib.rs | 31 +++++++++++++++++++++++++++++ apis/kernel/ipc/src/tests.rs | 18 +++++++++++++++++ src/lib.rs | 4 ++++ unittest/src/fake/ipc/mod.rs | 36 ++++++++++++++++++++++++++++++++++ unittest/src/fake/ipc/tests.rs | 0 unittest/src/fake/mod.rs | 2 ++ 8 files changed, 108 insertions(+) create mode 100644 apis/kernel/ipc/Cargo.toml create mode 100644 apis/kernel/ipc/src/lib.rs create mode 100644 apis/kernel/ipc/src/tests.rs create mode 100644 unittest/src/fake/ipc/mod.rs create mode 100644 unittest/src/fake/ipc/tests.rs diff --git a/Cargo.toml b/Cargo.toml index 2accd7a91..5b846d52b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ libtock_i2c_master = { path = "apis/peripherals/i2c_master" } libtock_i2c_master_slave = { path = "apis/peripherals/i2c_master_slave" } libtock_key_value = { path = "apis/storage/key_value" } libtock_leds = { path = "apis/interface/leds" } +libtock_ipc = { path = "apis/kernel/ipc" } libtock_low_level_debug = { path = "apis/kernel/low_level_debug" } libtock_ninedof = { path = "apis/sensors/ninedof" } libtock_platform = { path = "platform" } @@ -71,6 +72,7 @@ members = [ "apis/interface/buzzer", "apis/interface/console", "apis/interface/leds", + "apis/kernel/ipc", "apis/kernel/low_level_debug", "apis/peripherals/adc", "apis/peripherals/alarm", diff --git a/apis/kernel/ipc/Cargo.toml b/apis/kernel/ipc/Cargo.toml new file mode 100644 index 000000000..400b01d8f --- /dev/null +++ b/apis/kernel/ipc/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "libtock_ipc" +version = "0.1.0" +authors = ["Tock Project Developers "] +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 driver" + +[dependencies] +libtock_platform = { path = "../../../platform" } + +[dev-dependencies] +libtock_unittest = { path = "../../../unittest" } diff --git a/apis/kernel/ipc/src/lib.rs b/apis/kernel/ipc/src/lib.rs new file mode 100644 index 000000000..d5fef24fe --- /dev/null +++ b/apis/kernel/ipc/src/lib.rs @@ -0,0 +1,31 @@ +#![no_std] + +use libtock_platform::Syscalls; + +/// TODO: add IPC API documentation here + +pub struct Ipc(S); + +impl Ipc { + /// Run a check against the IPC capsule to ensure it is present. + /// + /// Returns `true` if the driver was present. This does not necessarily mean + /// that the driver is working, as it may still fail to allocate grant + /// memory. + #[inline(always)] + pub fn exists() -> bool { + S::command(DRIVER_NUM, EXISTS, 0, 0).is_success() + } +} + +#[cfg(test)] +mod tests; + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x10000; + +// Command IDs +const EXISTS: u32 = 0; diff --git a/apis/kernel/ipc/src/tests.rs b/apis/kernel/ipc/src/tests.rs new file mode 100644 index 000000000..f51e0d9eb --- /dev/null +++ b/apis/kernel/ipc/src/tests.rs @@ -0,0 +1,18 @@ +use libtock_unittest::fake; + +type Ipc = super::Ipc; + +#[test] +fn no_driver() { + let _kernel = fake::Kernel::new(); + assert!(!Ipc::exists()); +} + +#[test] +fn exists() { + let kernel = fake::Kernel::new(); + let driver = fake::Ipc::new(); + kernel.add_driver(&driver); + + assert!(Ipc::exists()); +} diff --git a/src/lib.rs b/src/lib.rs index 0e4acd363..dc9813637 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,6 +54,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; diff --git a/unittest/src/fake/ipc/mod.rs b/unittest/src/fake/ipc/mod.rs new file mode 100644 index 000000000..8d1151031 --- /dev/null +++ b/unittest/src/fake/ipc/mod.rs @@ -0,0 +1,36 @@ +use libtock_platform::{CommandReturn, ErrorCode}; + +use crate::DriverInfo; + +pub struct Ipc {} + +impl Ipc { + pub fn new() -> std::rc::Rc { + std::rc::Rc::new(Ipc {}) + } +} + +impl crate::fake::SyscallDriver for Ipc { + fn info(&self) -> DriverInfo { + DriverInfo::new(DRIVER_NUM) + } + + fn command(&self, command_num: u32, argument0: u32, argument1: u32) -> CommandReturn { + match command_num { + EXISTS => crate::command_return::success(), + _ => crate::command_return::failure(ErrorCode::NoSupport), + } + } +} + +#[cfg(test)] +mod tests; + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x10000; + +// Command IDs +const EXISTS: u32 = 0; diff --git a/unittest/src/fake/ipc/tests.rs b/unittest/src/fake/ipc/tests.rs new file mode 100644 index 000000000..e69de29bb diff --git a/unittest/src/fake/mod.rs b/unittest/src/fake/mod.rs index ba042ab08..658693b5f 100644 --- a/unittest/src/fake/mod.rs +++ b/unittest/src/fake/mod.rs @@ -17,6 +17,7 @@ mod buttons; mod buzzer; mod console; mod gpio; +mod ipc; mod kernel; mod key_value; mod leds; @@ -36,6 +37,7 @@ pub use buttons::Buttons; pub use buzzer::Buzzer; pub use console::Console; pub use gpio::{Gpio, GpioMode, InterruptEdge, PullMode}; +pub use ipc::Ipc; pub use kernel::Kernel; pub use key_value::KeyValue; pub use leds::Leds; From 180f6f2b62345d8673444eb3dbda4e4ea0a93406 Mon Sep 17 00:00:00 2001 From: Kat Watson Date: Thu, 25 Jul 2024 14:36:02 -0700 Subject: [PATCH 02/10] Add discover command support --- apis/kernel/ipc/src/lib.rs | 44 +++++++++++++------ apis/kernel/ipc/src/tests.rs | 36 +++++++++++++++- unittest/src/fake/ipc/mod.rs | 79 +++++++++++++++++++++++++++++++--- unittest/src/fake/ipc/tests.rs | 1 + 4 files changed, 139 insertions(+), 21 deletions(-) diff --git a/apis/kernel/ipc/src/lib.rs b/apis/kernel/ipc/src/lib.rs index d5fef24fe..998c2b1e9 100644 --- a/apis/kernel/ipc/src/lib.rs +++ b/apis/kernel/ipc/src/lib.rs @@ -1,23 +1,34 @@ #![no_std] -use libtock_platform::Syscalls; +use libtock_platform as platform; +use libtock_platform::share; +use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; -/// TODO: add IPC API documentation here +pub struct Ipc(S, C); -pub struct Ipc(S); +impl Ipc { + /// Check if the IPC kernel driver exists + pub fn exists() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, command::EXISTS, 0, 0).to_result() + } + + /// Request the service ID of an IPC service by package name + pub fn discover(pkg_name: &[u8]) -> Result { + share::scope(|allow_search| { + // Share the package name buffer with the kernel to search for + S::allow_ro::(allow_search, pkg_name)?; -impl Ipc { - /// Run a check against the IPC capsule to ensure it is present. - /// - /// Returns `true` if the driver was present. This does not necessarily mean - /// that the driver is working, as it may still fail to allocate grant - /// memory. - #[inline(always)] - pub fn exists() -> bool { - S::command(DRIVER_NUM, EXISTS, 0, 0).is_success() + // Send the command to the kernel driver to retrieve the service id for the + // corresponding IPC service, if it exists + S::command(DRIVER_NUM, command::DISCOVER, 0, 0).to_result() + }) } } +/// System call configuration trait for `Ipc`. +pub trait Config: platform::allow_ro::Config {} +impl Config for T {} + #[cfg(test)] mod tests; @@ -28,4 +39,11 @@ mod tests; const DRIVER_NUM: u32 = 0x10000; // Command IDs -const EXISTS: u32 = 0; +mod command { + pub const EXISTS: u32 = 0; + pub const DISCOVER: u32 = 1; +} + +mod allow_ro { + pub const SEARCH: u32 = 0; +} diff --git a/apis/kernel/ipc/src/tests.rs b/apis/kernel/ipc/src/tests.rs index f51e0d9eb..f0baa0dde 100644 --- a/apis/kernel/ipc/src/tests.rs +++ b/apis/kernel/ipc/src/tests.rs @@ -1,3 +1,4 @@ +use libtock_platform::ErrorCode; use libtock_unittest::fake; type Ipc = super::Ipc; @@ -5,7 +6,7 @@ type Ipc = super::Ipc; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); - assert!(!Ipc::exists()); + assert_eq!(Ipc::exists(), Err(ErrorCode::NoDevice)); } #[test] @@ -14,5 +15,36 @@ fn exists() { let driver = fake::Ipc::new(); kernel.add_driver(&driver); - assert!(Ipc::exists()); + assert_eq!(Ipc::exists(), Ok(())); +} + +#[test] +fn discover_valid_service() { + let kernel = fake::Kernel::new(); + let processes = [ + b"org.tockos.test.app_0", + b"org.tockos.test.app_1", + b"org.tockos.test.app_2", + ]; + let driver = fake::Ipc::new_with_processes(&processes); + kernel.add_driver(&driver); + + assert_eq!(Ipc::discover(b"org.tockos.test.app_1"), Ok(1)) +} + +#[test] +fn discover_invalid_service() { + let kernel = fake::Kernel::new(); + let processes = [ + b"org.tockos.test.app_0", + b"org.tockos.test.app_1", + b"org.tockos.test.app_2", + ]; + let driver = fake::Ipc::new_with_processes(&processes); + kernel.add_driver(&driver); + + assert_eq!( + Ipc::discover(b"com.test.service.app_0"), + Err(ErrorCode::Invalid) + ) } diff --git a/unittest/src/fake/ipc/mod.rs b/unittest/src/fake/ipc/mod.rs index 8d1151031..b88b735c6 100644 --- a/unittest/src/fake/ipc/mod.rs +++ b/unittest/src/fake/ipc/mod.rs @@ -1,12 +1,46 @@ +//! Fake implementation of the Ipc API, documented here: + use libtock_platform::{CommandReturn, ErrorCode}; +use std::cell::RefCell; + +use crate::{DriverInfo, DriverShareRef, RoAllowBuffer}; -use crate::DriverInfo; +// TODO: remove identifier, just have processes be package names +// TODO: remove index, use .enumerate() on processes +// TODO: figure out how to simulate calls on processes -pub struct Ipc {} +#[derive(Clone, Debug)] +pub struct Process { + pkg_name: Vec, +} +impl Process { + pub fn new(pkg_name: &[u8]) -> Process { + Process { + pkg_name: Vec::from(pkg_name), + } + } +} + +pub struct Ipc { + processes: Vec, + search_buffer: RefCell, + share_ref: DriverShareRef, +} impl Ipc { pub fn new() -> std::rc::Rc { - std::rc::Rc::new(Ipc {}) + Self::new_with_processes(&[] as &[&[u8]]) + } + + pub fn new_with_processes>(pkg_names: &[T]) -> std::rc::Rc { + std::rc::Rc::new(Ipc { + processes: pkg_names + .iter() + .map(|name| Process::new(name.as_ref())) + .collect(), + search_buffer: Default::default(), + share_ref: Default::default(), + }) } } @@ -15,12 +49,37 @@ impl crate::fake::SyscallDriver for Ipc { DriverInfo::new(DRIVER_NUM) } - fn command(&self, command_num: u32, argument0: u32, argument1: u32) -> CommandReturn { + fn command(&self, command_num: u32, _argument0: u32, _argument1: u32) -> CommandReturn { match command_num { - EXISTS => crate::command_return::success(), + command::EXISTS => crate::command_return::success(), + command::DISCOVER => self + .processes + .iter() + .position(|process| { + let search = self.search_buffer.borrow(); + process.pkg_name.len() == search.len() + && process + .pkg_name + .iter() + .zip(search.iter()) + .all(|(c1, c2)| *c1 == *c2) + }) + .map(|index| crate::command_return::success_u32(index as u32)) + .unwrap_or(crate::command_return::failure(ErrorCode::Invalid)), _ => crate::command_return::failure(ErrorCode::NoSupport), } } + + fn allow_readonly( + &self, + buffer_num: u32, + buffer: RoAllowBuffer, + ) -> Result { + match buffer_num { + allow_ro::SEARCH => Ok(self.search_buffer.replace(buffer)), + _ => Err((buffer, ErrorCode::Invalid)), + } + } } #[cfg(test)] @@ -33,4 +92,12 @@ mod tests; const DRIVER_NUM: u32 = 0x10000; // Command IDs -const EXISTS: u32 = 0; + +mod command { + pub const EXISTS: u32 = 0; + pub const DISCOVER: u32 = 1; +} + +mod allow_ro { + pub const SEARCH: u32 = 0; +} diff --git a/unittest/src/fake/ipc/tests.rs b/unittest/src/fake/ipc/tests.rs index e69de29bb..8b1378917 100644 --- a/unittest/src/fake/ipc/tests.rs +++ b/unittest/src/fake/ipc/tests.rs @@ -0,0 +1 @@ + From 19cc7bf20ec8d4315049eaa70421d11515240aca Mon Sep 17 00:00:00 2001 From: Kat Watson Date: Thu, 25 Jul 2024 15:40:15 -0700 Subject: [PATCH 03/10] Refactor Ipc object to have NUM_PROCS const generic --- apis/kernel/ipc/src/tests.rs | 10 +++++----- unittest/src/fake/ipc/mod.rs | 25 ++++++++++++++++--------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/apis/kernel/ipc/src/tests.rs b/apis/kernel/ipc/src/tests.rs index f0baa0dde..180888ec7 100644 --- a/apis/kernel/ipc/src/tests.rs +++ b/apis/kernel/ipc/src/tests.rs @@ -21,21 +21,21 @@ fn exists() { #[test] fn discover_valid_service() { let kernel = fake::Kernel::new(); - let processes = [ + let processes: [&[u8]; 3] = [ b"org.tockos.test.app_0", - b"org.tockos.test.app_1", - b"org.tockos.test.app_2", + b"org.tockos.test.app_4", + b"org.tockos.test.app_18", ]; let driver = fake::Ipc::new_with_processes(&processes); kernel.add_driver(&driver); - assert_eq!(Ipc::discover(b"org.tockos.test.app_1"), Ok(1)) + assert_eq!(Ipc::discover(b"org.tockos.test.app_4"), Ok(1)) } #[test] fn discover_invalid_service() { let kernel = fake::Kernel::new(); - let processes = [ + let processes: [&[u8]; 3] = [ b"org.tockos.test.app_0", b"org.tockos.test.app_1", b"org.tockos.test.app_2", diff --git a/unittest/src/fake/ipc/mod.rs b/unittest/src/fake/ipc/mod.rs index b88b735c6..f94e586fc 100644 --- a/unittest/src/fake/ipc/mod.rs +++ b/unittest/src/fake/ipc/mod.rs @@ -22,31 +22,38 @@ impl Process { } } -pub struct Ipc { - processes: Vec, +pub struct Ipc { + processes: [Process; NUM_PROCS], search_buffer: RefCell, share_ref: DriverShareRef, } -impl Ipc { - pub fn new() -> std::rc::Rc { - Self::new_with_processes(&[] as &[&[u8]]) + +impl Ipc<0> { + pub fn new() -> std::rc::Rc> { + Self::new_with_processes(&[] as &[&[u8]; 0]) } +} - pub fn new_with_processes>(pkg_names: &[T]) -> std::rc::Rc { +impl Ipc { + pub fn new_with_processes>( + pkg_names: &[T; NUM_PROCS], + ) -> std::rc::Rc> { std::rc::Rc::new(Ipc { processes: pkg_names .iter() .map(|name| Process::new(name.as_ref())) - .collect(), + .collect::>() + .try_into() + .unwrap(), search_buffer: Default::default(), share_ref: Default::default(), }) } } -impl crate::fake::SyscallDriver for Ipc { +impl crate::fake::SyscallDriver for Ipc { fn info(&self) -> DriverInfo { - DriverInfo::new(DRIVER_NUM) + DriverInfo::new(DRIVER_NUM).upcall_count(NUM_PROCS as u32) } fn command(&self, command_num: u32, _argument0: u32, _argument1: u32) -> CommandReturn { From ecdeebe55769a9b694e5947729d01ea0adacdf17 Mon Sep 17 00:00:00 2001 From: Kat Watson Date: Tue, 6 Aug 2024 13:53:00 -0400 Subject: [PATCH 04/10] Add fake Ipc support for SERVICE_NOTIFY and CLIENT_NOTIFY --- apis/kernel/ipc/src/tests.rs | 16 +++++----- unittest/src/fake/ipc/mod.rs | 61 ++++++++++++++++++++++++++---------- unittest/src/fake/mod.rs | 2 +- 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/apis/kernel/ipc/src/tests.rs b/apis/kernel/ipc/src/tests.rs index 180888ec7..256dedfdc 100644 --- a/apis/kernel/ipc/src/tests.rs +++ b/apis/kernel/ipc/src/tests.rs @@ -21,10 +21,10 @@ fn exists() { #[test] fn discover_valid_service() { let kernel = fake::Kernel::new(); - let processes: [&[u8]; 3] = [ - b"org.tockos.test.app_0", - b"org.tockos.test.app_4", - b"org.tockos.test.app_18", + let processes: [fake::Process; 3] = [ + fake::Process::new(b"org.tockos.test.app_0", 311149534), + fake::Process::new(b"org.tockos.test.app_4", 202834883), + fake::Process::new(b"org.tockos.test.app_18", 256614857), ]; let driver = fake::Ipc::new_with_processes(&processes); kernel.add_driver(&driver); @@ -35,10 +35,10 @@ fn discover_valid_service() { #[test] fn discover_invalid_service() { let kernel = fake::Kernel::new(); - let processes: [&[u8]; 3] = [ - b"org.tockos.test.app_0", - b"org.tockos.test.app_1", - b"org.tockos.test.app_2", + let processes: [fake::Process; 3] = [ + fake::Process::new(b"org.tockos.test.app_0", 311149534), + fake::Process::new(b"org.tockos.test.app_4", 202834883), + fake::Process::new(b"org.tockos.test.app_18", 256614857), ]; let driver = fake::Ipc::new_with_processes(&processes); kernel.add_driver(&driver); diff --git a/unittest/src/fake/ipc/mod.rs b/unittest/src/fake/ipc/mod.rs index f94e586fc..a68ac26e3 100644 --- a/unittest/src/fake/ipc/mod.rs +++ b/unittest/src/fake/ipc/mod.rs @@ -1,54 +1,57 @@ //! Fake implementation of the Ipc API, documented here: use libtock_platform::{CommandReturn, ErrorCode}; -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use crate::{DriverInfo, DriverShareRef, RoAllowBuffer}; -// TODO: remove identifier, just have processes be package names -// TODO: remove index, use .enumerate() on processes -// TODO: figure out how to simulate calls on processes - #[derive(Clone, Debug)] pub struct Process { pkg_name: Vec, + process_id: u32, } impl Process { - pub fn new(pkg_name: &[u8]) -> Process { + pub fn new(pkg_name: &[u8], process_id: u32) -> Process { Process { pkg_name: Vec::from(pkg_name), + process_id, } } } pub struct Ipc { processes: [Process; NUM_PROCS], + current_index: Cell>, search_buffer: RefCell, share_ref: DriverShareRef, } impl Ipc<0> { pub fn new() -> std::rc::Rc> { - Self::new_with_processes(&[] as &[&[u8]; 0]) + Self::new_with_processes(&[] as &[Process; 0]) } } impl Ipc { - pub fn new_with_processes>( - pkg_names: &[T; NUM_PROCS], - ) -> std::rc::Rc> { + pub fn new_with_processes(processes: &[Process; NUM_PROCS]) -> std::rc::Rc> { std::rc::Rc::new(Ipc { - processes: pkg_names - .iter() - .map(|name| Process::new(name.as_ref())) - .collect::>() - .try_into() - .unwrap(), + processes: Vec::from(processes).try_into().unwrap(), + current_index: Cell::from(None), search_buffer: Default::default(), share_ref: Default::default(), }) } + + pub fn set_process(&mut self, process_id: u32) -> Result<(), ErrorCode> { + let index = self + .processes + .iter() + .position(|process| process.process_id == process_id) + .ok_or(ErrorCode::Invalid)?; + self.current_index.replace(Some(index as u32)); + Ok(()) + } } impl crate::fake::SyscallDriver for Ipc { @@ -56,7 +59,7 @@ impl crate::fake::SyscallDriver for Ipc { DriverInfo::new(DRIVER_NUM).upcall_count(NUM_PROCS as u32) } - fn command(&self, command_num: u32, _argument0: u32, _argument1: u32) -> CommandReturn { + fn command(&self, command_num: u32, target_index: u32, _argument1: u32) -> CommandReturn { match command_num { command::EXISTS => crate::command_return::success(), command::DISCOVER => self @@ -73,6 +76,28 @@ impl crate::fake::SyscallDriver for Ipc { }) .map(|index| crate::command_return::success_u32(index as u32)) .unwrap_or(crate::command_return::failure(ErrorCode::Invalid)), + command::SERVICE_NOTIFY => { + let index = self.current_index.get().expect("No current application"); + if index < self.processes.len() as u32 { + self.share_ref + .schedule_upcall(target_index, (index, 0, 0)) + .expect("Unable to schedule upcall {}"); + crate::command_return::success() + } else { + crate::command_return::failure(ErrorCode::Invalid) + } + } + command::CLIENT_NOTIFY => { + let index = self.current_index.get().expect("No current application"); + if index < self.processes.len() as u32 { + self.share_ref + .schedule_upcall(index, (index, 0, 0)) + .expect("Unable to schedule upcall {}"); + crate::command_return::success() + } else { + crate::command_return::failure(ErrorCode::Invalid) + } + } _ => crate::command_return::failure(ErrorCode::NoSupport), } } @@ -103,6 +128,8 @@ const DRIVER_NUM: u32 = 0x10000; mod command { pub const EXISTS: u32 = 0; pub const DISCOVER: u32 = 1; + pub const SERVICE_NOTIFY: u32 = 2; + pub const CLIENT_NOTIFY: u32 = 3; } mod allow_ro { diff --git a/unittest/src/fake/mod.rs b/unittest/src/fake/mod.rs index 658693b5f..78a7749b4 100644 --- a/unittest/src/fake/mod.rs +++ b/unittest/src/fake/mod.rs @@ -37,7 +37,7 @@ pub use buttons::Buttons; pub use buzzer::Buzzer; pub use console::Console; pub use gpio::{Gpio, GpioMode, InterruptEdge, PullMode}; -pub use ipc::Ipc; +pub use ipc::{Ipc, Process}; pub use kernel::Kernel; pub use key_value::KeyValue; pub use leds::Leds; From 0eb41a2276b8164a626fdc5b0a57bd9f4f32d2df Mon Sep 17 00:00:00 2001 From: Kat Watson Date: Tue, 6 Aug 2024 19:41:30 -0400 Subject: [PATCH 05/10] Add tests for IPC fake --- apis/kernel/ipc/src/tests.rs | 12 ++- unittest/src/fake/ipc/mod.rs | 20 ++--- unittest/src/fake/ipc/tests.rs | 151 +++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 18 deletions(-) diff --git a/apis/kernel/ipc/src/tests.rs b/apis/kernel/ipc/src/tests.rs index 256dedfdc..29a649141 100644 --- a/apis/kernel/ipc/src/tests.rs +++ b/apis/kernel/ipc/src/tests.rs @@ -12,7 +12,7 @@ fn no_driver() { #[test] fn exists() { let kernel = fake::Kernel::new(); - let driver = fake::Ipc::new(); + let driver = fake::Ipc::new(&[]); kernel.add_driver(&driver); assert_eq!(Ipc::exists(), Ok(())); @@ -21,12 +21,11 @@ fn exists() { #[test] fn discover_valid_service() { let kernel = fake::Kernel::new(); - let processes: [fake::Process; 3] = [ + let driver = fake::Ipc::new(&[ fake::Process::new(b"org.tockos.test.app_0", 311149534), fake::Process::new(b"org.tockos.test.app_4", 202834883), fake::Process::new(b"org.tockos.test.app_18", 256614857), - ]; - let driver = fake::Ipc::new_with_processes(&processes); + ]); kernel.add_driver(&driver); assert_eq!(Ipc::discover(b"org.tockos.test.app_4"), Ok(1)) @@ -35,12 +34,11 @@ fn discover_valid_service() { #[test] fn discover_invalid_service() { let kernel = fake::Kernel::new(); - let processes: [fake::Process; 3] = [ + let driver = fake::Ipc::new(&[ fake::Process::new(b"org.tockos.test.app_0", 311149534), fake::Process::new(b"org.tockos.test.app_4", 202834883), fake::Process::new(b"org.tockos.test.app_18", 256614857), - ]; - let driver = fake::Ipc::new_with_processes(&processes); + ]); kernel.add_driver(&driver); assert_eq!( diff --git a/unittest/src/fake/ipc/mod.rs b/unittest/src/fake/ipc/mod.rs index a68ac26e3..d41938f28 100644 --- a/unittest/src/fake/ipc/mod.rs +++ b/unittest/src/fake/ipc/mod.rs @@ -12,7 +12,7 @@ pub struct Process { } impl Process { - pub fn new(pkg_name: &[u8], process_id: u32) -> Process { + pub fn new(pkg_name: &[u8], process_id: u32) -> Self { Process { pkg_name: Vec::from(pkg_name), process_id, @@ -27,14 +27,8 @@ pub struct Ipc { share_ref: DriverShareRef, } -impl Ipc<0> { - pub fn new() -> std::rc::Rc> { - Self::new_with_processes(&[] as &[Process; 0]) - } -} - impl Ipc { - pub fn new_with_processes(processes: &[Process; NUM_PROCS]) -> std::rc::Rc> { + pub fn new(processes: &[Process; NUM_PROCS]) -> std::rc::Rc> { std::rc::Rc::new(Ipc { processes: Vec::from(processes).try_into().unwrap(), current_index: Cell::from(None), @@ -43,7 +37,7 @@ impl Ipc { }) } - pub fn set_process(&mut self, process_id: u32) -> Result<(), ErrorCode> { + pub fn set_process(&self, process_id: u32) -> Result<(), ErrorCode> { let index = self .processes .iter() @@ -59,6 +53,10 @@ impl crate::fake::SyscallDriver for Ipc { DriverInfo::new(DRIVER_NUM).upcall_count(NUM_PROCS as u32) } + fn register(&self, share_ref: DriverShareRef) { + self.share_ref.replace(share_ref); + } + fn command(&self, command_num: u32, target_index: u32, _argument1: u32) -> CommandReturn { match command_num { command::EXISTS => crate::command_return::success(), @@ -78,7 +76,7 @@ impl crate::fake::SyscallDriver for Ipc { .unwrap_or(crate::command_return::failure(ErrorCode::Invalid)), command::SERVICE_NOTIFY => { let index = self.current_index.get().expect("No current application"); - if index < self.processes.len() as u32 { + if target_index < NUM_PROCS as u32 { self.share_ref .schedule_upcall(target_index, (index, 0, 0)) .expect("Unable to schedule upcall {}"); @@ -89,7 +87,7 @@ impl crate::fake::SyscallDriver for Ipc { } command::CLIENT_NOTIFY => { let index = self.current_index.get().expect("No current application"); - if index < self.processes.len() as u32 { + if target_index < NUM_PROCS as u32 { self.share_ref .schedule_upcall(index, (index, 0, 0)) .expect("Unable to schedule upcall {}"); diff --git a/unittest/src/fake/ipc/tests.rs b/unittest/src/fake/ipc/tests.rs index 8b1378917..f2d4b9225 100644 --- a/unittest/src/fake/ipc/tests.rs +++ b/unittest/src/fake/ipc/tests.rs @@ -1 +1,152 @@ +use crate::allow_db::AllowDb; +use crate::fake; +use fake::ipc::*; +use libtock_platform::share; +use libtock_platform::{DefaultConfig, ErrorCode, Register, YieldNoWaitReturn}; +const APP_0_PROCESS_ID: u32 = 311149534; +const APP_1_PROCESS_ID: u32 = 202834883; +const APP_2_PROCESS_ID: u32 = 256614857; + +// Tests the command implementation. +#[test] +fn command() { + use fake::SyscallDriver; + let ipc = Ipc::new(&[ + Process::new(b"org.tockos.test.app_0", APP_0_PROCESS_ID), + Process::new(b"org.tockos.test.app_1", APP_1_PROCESS_ID), + Process::new(b"org.tockos.test.app_2", APP_2_PROCESS_ID), + ]); + + // Exists + assert!(ipc.command(command::EXISTS, 0, 0).is_success()); + + // Discover + let mut db: AllowDb = Default::default(); + + let present_str = b"org.tockos.test.app_1"; + let present_addr: Register = (present_str as *const u8).into(); + let present_len: Register = present_str.len().into(); + let present = unsafe { db.insert_ro_buffer(present_addr, present_len) }.unwrap(); + ipc.allow_readonly(fake::ipc::allow_ro::SEARCH, present) + .unwrap(); + assert_eq!( + ipc.command(command::DISCOVER, 0, 0).get_success_u32(), + Some(1) + ); + + let absent_str = b"com.example.test.app_other"; + let absent_addr: Register = (absent_str as *const u8).into(); + let absent_len: Register = absent_str.len().into(); + let absent = unsafe { db.insert_ro_buffer(absent_addr, absent_len) }.unwrap(); + ipc.allow_readonly(fake::ipc::allow_ro::SEARCH, absent) + .unwrap(); + assert_eq!( + ipc.command(command::DISCOVER, 0, 0).get_failure(), + Some(ErrorCode::Invalid) + ); + + // Service Notify + ipc.set_process(APP_1_PROCESS_ID).unwrap(); + assert!(ipc.command(command::SERVICE_NOTIFY, 2, 0).is_success()); + assert_eq!( + ipc.command(command::SERVICE_NOTIFY, 3, 0).get_failure(), + Some(ErrorCode::Invalid) + ); + + // Client Notify + ipc.set_process(APP_2_PROCESS_ID).unwrap(); + assert!(ipc.command(command::CLIENT_NOTIFY, 0, 0).is_success()); + assert_eq!( + ipc.command(command::CLIENT_NOTIFY, 4, 0).get_failure(), + Some(ErrorCode::Invalid) + ); +} + +// Integration test that verifies Ipc works with fake::Kernel and +// libtock_platform::Syscalls. +#[test] +fn kernel_integration() { + use libtock_platform::Syscalls; + let kernel = fake::Kernel::new(); + let ipc = Ipc::new(&[ + Process::new(b"org.tockos.test.app_0", APP_0_PROCESS_ID), + Process::new(b"org.tockos.test.app_1", APP_1_PROCESS_ID), + Process::new(b"org.tockos.test.app_2", APP_2_PROCESS_ID), + ]); + kernel.add_driver(&ipc); + + // Exists + assert!(fake::Syscalls::command(DRIVER_NUM, command::EXISTS, 0, 0).is_success()); + + // Discover + share::scope(|allow_search| { + assert_eq!( + fake::Syscalls::allow_ro::( + allow_search, + b"org.tockos.test.app_1", + ), + Ok(()) + ); + assert_eq!( + fake::Syscalls::command(DRIVER_NUM, command::DISCOVER, 0, 0).get_success_u32(), + Some(1) + ); + }); + + share::scope(|allow_search| { + fake::Syscalls::allow_ro::( + allow_search, + b"com.example.test.app_5", + ) + .unwrap(); + assert_eq!( + fake::Syscalls::command(DRIVER_NUM, command::DISCOVER, 0, 0).get_failure(), + Some(ErrorCode::Invalid) + ); + }); + + // Notify Service + ipc.set_process(APP_1_PROCESS_ID).unwrap(); + let listener = Cell::>::new(None); + share::scope(|subscribe_service| { + assert_eq!( + fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 2>( + subscribe_service, + &listener, + ), + Ok(()) + ); + assert!(fake::Syscalls::command(DRIVER_NUM, command::SERVICE_NOTIFY, 2, 0).is_success()); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + }); + assert_eq!(listener.get(), Some((1, 0, 0))); + + assert_eq!( + fake::Syscalls::command(DRIVER_NUM, command::SERVICE_NOTIFY, 3, 0).get_failure(), + Some(ErrorCode::Invalid) + ); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + + // Notify Client + ipc.set_process(APP_2_PROCESS_ID).unwrap(); + let listener = Cell::>::new(None); + share::scope(|subscribe_client| { + assert_eq!( + fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 2>( + subscribe_client, + &listener, + ), + Ok(()) + ); + assert!(fake::Syscalls::command(DRIVER_NUM, command::CLIENT_NOTIFY, 0, 0).is_success()); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + }); + assert_eq!(listener.get(), Some((2, 0, 0))); + + assert_eq!( + fake::Syscalls::command(DRIVER_NUM, command::CLIENT_NOTIFY, 5, 0).get_failure(), + Some(ErrorCode::Invalid) + ); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); +} From e58322ece361140ddf2c63fe5ea900e553e6e7d6 Mon Sep 17 00:00:00 2001 From: Kat Watson Date: Wed, 7 Aug 2024 18:34:44 -0400 Subject: [PATCH 06/10] Add tests for unittest fake Ipc driver and API --- apis/kernel/ipc/src/lib.rs | 162 ++++++++++++++++++++++++++++++++++- apis/kernel/ipc/src/tests.rs | 109 ++++++++++++++++++++--- unittest/src/fake/ipc/mod.rs | 4 +- 3 files changed, 257 insertions(+), 18 deletions(-) diff --git a/apis/kernel/ipc/src/lib.rs b/apis/kernel/ipc/src/lib.rs index 998c2b1e9..c246d7e43 100644 --- a/apis/kernel/ipc/src/lib.rs +++ b/apis/kernel/ipc/src/lib.rs @@ -1,11 +1,20 @@ #![no_std] +use core::slice::from_raw_parts_mut; use libtock_platform as platform; -use libtock_platform::share; -use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; +use libtock_platform::{ + exit_on_drop, return_variant, share, subscribe, syscall_class, DefaultConfig, ErrorCode, + Register, ReturnVariant, Syscalls, Upcall, +}; pub struct Ipc(S, C); +#[derive(Debug, Eq, PartialEq)] +pub struct IpcCallData<'a> { + caller_id: u32, + buffer: &'a mut [u8], +} + impl Ipc { /// Check if the IPC kernel driver exists pub fn exists() -> Result<(), ErrorCode> { @@ -23,11 +32,154 @@ impl Ipc { S::command(DRIVER_NUM, command::DISCOVER, 0, 0).to_result() }) } + + pub fn register_service_listener( + pkg_name: &[u8], + listener: &'static IpcListener, + ) -> Result<(), ErrorCode> { + let service_id = Self::discover(pkg_name)?; + Self::subscribe_ipc::(service_id, listener) + } + + pub fn register_client_listener( + service_id: u32, + listener: &'static IpcListener, + ) -> Result<(), ErrorCode> { + Self::subscribe_ipc::(service_id, listener) + } + + pub fn notify_service(svc_id: u32) -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, command::SERVICE_NOTIFY, svc_id, 0).to_result() + } + + pub fn notify_client(svc_id: u32) -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, command::CLIENT_NOTIFY, svc_id, 0).to_result() + } + + fn subscribe_ipc( + process_id: u32, + listener: &'static IpcListener, + ) -> Result<(), ErrorCode> { + // TODO: revise comments + // 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 IpcListener = 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 and SUBSCRIBE_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 = (listener as *const IpcListener).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, SUBSCRIBE_NUM>, + // proving one exists. upcall_fcn and upcall_data are derived in ways + // that satisfy inner's requirements. + unsafe { inner::(DRIVER_NUM, process_id, upcall_fcn, upcall_data) } + } +} + +/// A wrapper around a static function to be registered as an IPC callback +/// and called when the IPC service receives a notify. +/// +/// ```ignore +/// fn run_on_notify(caller_id: u32, buffer: &mut [u8]) { +/// // make use of the caller ID and shared buffer +/// } +/// +/// let callback = Ipc(run_on_notify); +/// ``` +pub struct IpcListener(pub F); + +impl Upcall for IpcListener { + fn upcall(&self, caller_id: u32, buffer_len: u32, buffer_ptr: u32) { + // Safety: TODO + let buffer: &mut [u8] = + unsafe { from_raw_parts_mut::(buffer_ptr as *mut u8, buffer_len as usize) }; + self.0(IpcCallData { caller_id, buffer }); + } } /// System call configuration trait for `Ipc`. -pub trait Config: platform::allow_ro::Config {} -impl Config for T {} +pub trait Config: platform::allow_ro::Config + platform::subscribe::Config {} +impl Config for T {} #[cfg(test)] mod tests; @@ -42,6 +194,8 @@ const DRIVER_NUM: u32 = 0x10000; mod command { pub const EXISTS: u32 = 0; pub const DISCOVER: u32 = 1; + pub const SERVICE_NOTIFY: u32 = 2; + pub const CLIENT_NOTIFY: u32 = 3; } mod allow_ro { diff --git a/apis/kernel/ipc/src/tests.rs b/apis/kernel/ipc/src/tests.rs index 29a649141..6f4bb3a2e 100644 --- a/apis/kernel/ipc/src/tests.rs +++ b/apis/kernel/ipc/src/tests.rs @@ -1,8 +1,18 @@ -use libtock_platform::ErrorCode; +use core::sync::atomic::{AtomicBool, Ordering}; +use libtock_platform::{ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake; +use crate::{IpcCallData, IpcListener}; + type Ipc = super::Ipc; +const APP_0_PROCESS_ID: u32 = 311149534; +const APP_1_PROCESS_ID: u32 = 202834883; +const APP_2_PROCESS_ID: u32 = 256614857; + +const SERVICE_PROCESS_ID: u32 = 2095420182; +const CLIENT_PROCESS_ID: u32 = 969262335; + #[test] fn no_driver() { let _kernel = fake::Kernel::new(); @@ -19,30 +29,103 @@ fn exists() { } #[test] -fn discover_valid_service() { +fn discover() { let kernel = fake::Kernel::new(); let driver = fake::Ipc::new(&[ - fake::Process::new(b"org.tockos.test.app_0", 311149534), - fake::Process::new(b"org.tockos.test.app_4", 202834883), - fake::Process::new(b"org.tockos.test.app_18", 256614857), + fake::Process::new(b"org.tockos.test.app_0", APP_0_PROCESS_ID), + fake::Process::new(b"org.tockos.test.app_1", APP_1_PROCESS_ID), + fake::Process::new(b"org.tockos.test.app_2", APP_2_PROCESS_ID), ]); kernel.add_driver(&driver); - assert_eq!(Ipc::discover(b"org.tockos.test.app_4"), Ok(1)) + assert_eq!(Ipc::discover(b"org.tockos.test.app_1"), Ok(1)); + assert_eq!( + Ipc::discover(b"com.example.test.app_0"), + Err(ErrorCode::Invalid) + ) } #[test] -fn discover_invalid_service() { +fn register_and_notify_service() { + static SERVICE_NOTIFIED: AtomicBool = AtomicBool::new(false); + + fn service_callback(_data: IpcCallData) { + SERVICE_NOTIFIED.store(true, Ordering::Relaxed); + } + + const SERVICE_LISTENER: IpcListener = IpcListener(service_callback); + let kernel = fake::Kernel::new(); let driver = fake::Ipc::new(&[ - fake::Process::new(b"org.tockos.test.app_0", 311149534), - fake::Process::new(b"org.tockos.test.app_4", 202834883), - fake::Process::new(b"org.tockos.test.app_18", 256614857), + fake::Process::new(b"org.tockos.test.service", SERVICE_PROCESS_ID), + fake::Process::new(b"org.tockos.test.client", CLIENT_PROCESS_ID), ]); kernel.add_driver(&driver); assert_eq!( - Ipc::discover(b"com.test.service.app_0"), - Err(ErrorCode::Invalid) - ) + driver.as_process(SERVICE_PROCESS_ID, || { + assert_eq!( + Ipc::register_service_listener(b"org.example.fake.service", &SERVICE_LISTENER), + Err(ErrorCode::Invalid) + ); + assert_eq!( + Ipc::register_service_listener(b"org.tockos.test.service", &SERVICE_LISTENER), + Ok(()) + ); + }), + Ok(()) + ); + + assert_eq!( + driver.as_process(CLIENT_PROCESS_ID, || { + assert_eq!(Ipc::notify_service(4), Err(ErrorCode::Invalid)); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + assert_eq!(Ipc::notify_service(0), Ok(())); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + }), + Ok(()) + ); + + assert!(SERVICE_NOTIFIED.load(Ordering::Relaxed)); +} + +#[test] +fn register_and_notify_client() { + static CLIENT_NOTIFIED: AtomicBool = AtomicBool::new(false); + + fn client_callback(_data: IpcCallData) { + CLIENT_NOTIFIED.store(true, Ordering::Relaxed); + } + + const CLIENT_LISTENER: IpcListener = IpcListener(client_callback); + + let kernel = fake::Kernel::new(); + let driver = fake::Ipc::new(&[ + fake::Process::new(b"org.tockos.test.service", SERVICE_PROCESS_ID), + fake::Process::new(b"org.tockos.test.client", CLIENT_PROCESS_ID), + ]); + kernel.add_driver(&driver); + + assert_eq!( + driver.as_process(CLIENT_PROCESS_ID, || { + assert_eq!( + Ipc::register_client_listener(4, &CLIENT_LISTENER), + Err(ErrorCode::Invalid) + ); + assert_eq!(Ipc::register_client_listener(0, &CLIENT_LISTENER), Ok(())); + }), + Ok(()) + ); + + assert_eq!( + driver.as_process(SERVICE_PROCESS_ID, || { + assert_eq!(Ipc::notify_client(4), Err(ErrorCode::Invalid)); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + assert_eq!(Ipc::notify_client(1), Ok(())); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + }), + Ok(()) + ); + + assert!(CLIENT_NOTIFIED.load(Ordering::Relaxed)); } diff --git a/unittest/src/fake/ipc/mod.rs b/unittest/src/fake/ipc/mod.rs index d41938f28..c31d4b163 100644 --- a/unittest/src/fake/ipc/mod.rs +++ b/unittest/src/fake/ipc/mod.rs @@ -37,13 +37,15 @@ impl Ipc { }) } - pub fn set_process(&self, process_id: u32) -> Result<(), ErrorCode> { + pub fn as_process(&self, process_id: u32, process_fn: F) -> Result<(), ErrorCode> { let index = self .processes .iter() .position(|process| process.process_id == process_id) .ok_or(ErrorCode::Invalid)?; self.current_index.replace(Some(index as u32)); + process_fn(); + self.current_index.set(None); Ok(()) } } From 2ff0f145267ce2dc1f7e276e816c2d9b56d5a399 Mon Sep 17 00:00:00 2001 From: Kat Watson Date: Thu, 8 Aug 2024 20:35:14 -0700 Subject: [PATCH 07/10] Add IPC share support with test --- apis/kernel/ipc/Cargo.toml | 1 + apis/kernel/ipc/src/lib.rs | 115 +++++++++++++++++++++++++++++------ apis/kernel/ipc/src/tests.rs | 56 ++++++++++++++++- unittest/src/fake/ipc/mod.rs | 86 ++++++++++++++++++-------- 4 files changed, 211 insertions(+), 47 deletions(-) diff --git a/apis/kernel/ipc/Cargo.toml b/apis/kernel/ipc/Cargo.toml index 400b01d8f..0e3123407 100644 --- a/apis/kernel/ipc/Cargo.toml +++ b/apis/kernel/ipc/Cargo.toml @@ -13,3 +13,4 @@ libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } +takecell = "0.1.1" diff --git a/apis/kernel/ipc/src/lib.rs b/apis/kernel/ipc/src/lib.rs index c246d7e43..e2e178578 100644 --- a/apis/kernel/ipc/src/lib.rs +++ b/apis/kernel/ipc/src/lib.rs @@ -1,20 +1,13 @@ -#![no_std] +#![cfg_attr(not(test), no_std)] -use core::slice::from_raw_parts_mut; use libtock_platform as platform; -use libtock_platform::{ - exit_on_drop, return_variant, share, subscribe, syscall_class, DefaultConfig, ErrorCode, - Register, ReturnVariant, Syscalls, Upcall, +use platform::{ + allow_rw, exit_on_drop, return_variant, share, subscribe, syscall_class, DefaultConfig, + ErrorCode, Register, ReturnVariant, Syscalls, Upcall, }; pub struct Ipc(S, C); -#[derive(Debug, Eq, PartialEq)] -pub struct IpcCallData<'a> { - caller_id: u32, - buffer: &'a mut [u8], -} - impl Ipc { /// Check if the IPC kernel driver exists pub fn exists() -> Result<(), ErrorCode> { @@ -48,12 +41,16 @@ impl Ipc { Self::subscribe_ipc::(service_id, listener) } - pub fn notify_service(svc_id: u32) -> Result<(), ErrorCode> { - S::command(DRIVER_NUM, command::SERVICE_NOTIFY, svc_id, 0).to_result() + pub fn notify_service(service_id: u32) -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, command::SERVICE_NOTIFY, service_id, 0).to_result() + } + + pub fn notify_client(client_id: u32) -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, command::CLIENT_NOTIFY, client_id, 0).to_result() } - pub fn notify_client(svc_id: u32) -> Result<(), ErrorCode> { - S::command(DRIVER_NUM, command::CLIENT_NOTIFY, svc_id, 0).to_result() + pub fn share(service_id: u32, buffer: &'static mut [u8]) -> Result<(), ErrorCode> { + Self::allow_rw_ipc::(service_id, buffer) } fn subscribe_ipc( @@ -154,6 +151,78 @@ impl Ipc { // that satisfy inner's requirements. unsafe { inner::(DRIVER_NUM, process_id, upcall_fcn, upcall_data) } } + + fn allow_rw_ipc( + buffer_num: u32, + buffer: &'static mut [u8], + ) -> Result<(), ErrorCode> { + // Inner function that does the majority of the work. This is not + // monomorphized over DRIVER_NUM and BUFFER_NUM to keep code size small. + // + // Safety: A share::Handle> + // must exist, and `buffer` must last for at least the 'share lifetime. + unsafe fn inner( + driver_num: u32, + buffer_num: u32, + buffer: &mut [u8], + ) -> Result<(), ErrorCode> { + // Safety: syscall4's documentation indicates it can be used to call + // Read-Write Allow. These arguments follow TRD104. + let [r0, r1, r2, _] = unsafe { + S::syscall4::<{ syscall_class::ALLOW_RW }>([ + driver_num.into(), + buffer_num.into(), + buffer.as_mut_ptr().into(), + buffer.len().into(), + ]) + }; + + let return_variant: ReturnVariant = r0.as_u32().into(); + // TRD 104 guarantees that Read-Write Allow 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 + // panicing. + 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 a zero buffer was + // returned, and it if wasn't then call the configured function. + // We're relying on the optimizer to remove this branch if + // returned_nozero_buffer is a no-op. + let returned_buffer: (usize, usize) = (r1.into(), r2.into()); + if returned_buffer != (0, 0) { + CONFIG::returned_nonzero_buffer(driver_num, buffer_num); + } + Ok(()) + } + + // Safety: The presence of the share::Handle> + // guarantees that an AllowRw exists and will clean up this Allow ID + // before the 'share lifetime ends. + unsafe { inner::(DRIVER_NUM, buffer_num, buffer) } + } +} + +#[derive(Debug, Eq, PartialEq)] +pub struct IpcCallData<'a> { + caller_id: u32, + buffer: &'a mut [u8], } /// A wrapper around a static function to be registered as an IPC callback @@ -170,16 +239,22 @@ pub struct IpcListener(pub F); impl Upcall for IpcListener { fn upcall(&self, caller_id: u32, buffer_len: u32, buffer_ptr: u32) { - // Safety: TODO - let buffer: &mut [u8] = - unsafe { from_raw_parts_mut::(buffer_ptr as *mut u8, buffer_len as usize) }; + let buffer_addr = buffer_ptr as *mut u8; + let buffer_len = buffer_len as usize; + let buffer = unsafe { core::slice::from_raw_parts_mut(buffer_addr, buffer_len) }; self.0(IpcCallData { caller_id, buffer }); } } /// System call configuration trait for `Ipc`. -pub trait Config: platform::allow_ro::Config + platform::subscribe::Config {} -impl Config for T {} +pub trait Config: + platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config +{ +} +impl + Config for T +{ +} #[cfg(test)] mod tests; diff --git a/apis/kernel/ipc/src/tests.rs b/apis/kernel/ipc/src/tests.rs index 6f4bb3a2e..83d0e1a94 100644 --- a/apis/kernel/ipc/src/tests.rs +++ b/apis/kernel/ipc/src/tests.rs @@ -1,6 +1,7 @@ -use core::sync::atomic::{AtomicBool, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use libtock_platform::{ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake; +use takecell::TakeCell; use crate::{IpcCallData, IpcListener}; @@ -129,3 +130,56 @@ fn register_and_notify_client() { assert!(CLIENT_NOTIFIED.load(Ordering::Relaxed)); } + +#[test] +fn share() { + static BUFFER: TakeCell<[u8; 16]> = TakeCell::new([0; 16]); + static EXPECTED_ADDR: AtomicU32 = AtomicU32::new(0); + static EXPECTED_LEN: AtomicU32 = AtomicU32::new(0); + + fn service_callback(data: IpcCallData) { + assert_eq!(data.caller_id, 1); + assert_eq!( + data.buffer.as_ptr() as u32, + EXPECTED_ADDR.load(Ordering::Relaxed) + ); + assert_eq!( + data.buffer.len() as u32, + EXPECTED_LEN.load(Ordering::Relaxed) + ) + } + + const SERVICE_LISTENER: IpcListener = IpcListener(service_callback); + + let kernel = fake::Kernel::new(); + let driver = fake::Ipc::new(&[ + fake::Process::new(b"org.tockos.test.service", SERVICE_PROCESS_ID), + fake::Process::new(b"org.tockos.test.client", CLIENT_PROCESS_ID), + ]); + kernel.add_driver(&driver); + + assert_eq!( + driver.as_process(SERVICE_PROCESS_ID, || { + assert_eq!( + Ipc::register_service_listener(b"org.tockos.test.service", &SERVICE_LISTENER), + Ok(()) + ); + }), + Ok(()) + ); + + assert_eq!( + driver.as_process(CLIENT_PROCESS_ID, || { + let buffer = BUFFER.take().unwrap(); + EXPECTED_ADDR.store(buffer.as_ptr() as u32, Ordering::Relaxed); + EXPECTED_LEN.store(buffer.len() as u32, Ordering::Relaxed); + + assert_eq!(Ipc::share(0, buffer), Ok(())); + assert_eq!(Ipc::notify_service(4), Err(ErrorCode::Invalid)); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + assert_eq!(Ipc::notify_service(0), Ok(())); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + }), + Ok(()) + ); +} diff --git a/unittest/src/fake/ipc/mod.rs b/unittest/src/fake/ipc/mod.rs index c31d4b163..5e6fff9ff 100644 --- a/unittest/src/fake/ipc/mod.rs +++ b/unittest/src/fake/ipc/mod.rs @@ -2,8 +2,9 @@ use libtock_platform::{CommandReturn, ErrorCode}; use std::cell::{Cell, RefCell}; +use std::collections::HashMap; -use crate::{DriverInfo, DriverShareRef, RoAllowBuffer}; +use crate::{DriverInfo, DriverShareRef, RoAllowBuffer, RwAllowBuffer}; #[derive(Clone, Debug)] pub struct Process { @@ -20,10 +21,17 @@ impl Process { } } +enum IpcProcessType { + Service, + Client, +} + pub struct Ipc { processes: [Process; NUM_PROCS], current_index: Cell>, search_buffer: RefCell, + share_buffers: [RefCell; NUM_PROCS], + mock_address_map: RefCell>, share_ref: DriverShareRef, } @@ -31,12 +39,13 @@ impl Ipc { pub fn new(processes: &[Process; NUM_PROCS]) -> std::rc::Rc> { std::rc::Rc::new(Ipc { processes: Vec::from(processes).try_into().unwrap(), - current_index: Cell::from(None), + current_index: Default::default(), search_buffer: Default::default(), + share_buffers: std::array::from_fn(|_| Default::default()), + mock_address_map: Default::default(), share_ref: Default::default(), }) } - pub fn as_process(&self, process_id: u32, process_fn: F) -> Result<(), ErrorCode> { let index = self .processes @@ -59,7 +68,7 @@ impl crate::fake::SyscallDriver for Ipc { self.share_ref.replace(share_ref); } - fn command(&self, command_num: u32, target_index: u32, _argument1: u32) -> CommandReturn { + fn command(&self, command_num: u32, to_index: u32, _argument1: u32) -> CommandReturn { match command_num { command::EXISTS => crate::command_return::success(), command::DISCOVER => self @@ -76,28 +85,8 @@ impl crate::fake::SyscallDriver for Ipc { }) .map(|index| crate::command_return::success_u32(index as u32)) .unwrap_or(crate::command_return::failure(ErrorCode::Invalid)), - command::SERVICE_NOTIFY => { - let index = self.current_index.get().expect("No current application"); - if target_index < NUM_PROCS as u32 { - self.share_ref - .schedule_upcall(target_index, (index, 0, 0)) - .expect("Unable to schedule upcall {}"); - crate::command_return::success() - } else { - crate::command_return::failure(ErrorCode::Invalid) - } - } - command::CLIENT_NOTIFY => { - let index = self.current_index.get().expect("No current application"); - if target_index < NUM_PROCS as u32 { - self.share_ref - .schedule_upcall(index, (index, 0, 0)) - .expect("Unable to schedule upcall {}"); - crate::command_return::success() - } else { - crate::command_return::failure(ErrorCode::Invalid) - } - } + command::SERVICE_NOTIFY => self.notify(IpcProcessType::Service, to_index), + command::CLIENT_NOTIFY => self.notify(IpcProcessType::Client, to_index), _ => crate::command_return::failure(ErrorCode::NoSupport), } } @@ -112,6 +101,51 @@ impl crate::fake::SyscallDriver for Ipc { _ => Err((buffer, ErrorCode::Invalid)), } } + + fn allow_readwrite( + &self, + buffer_num: u32, + buffer: RwAllowBuffer, + ) -> Result { + if let Some(search_buffer) = self.share_buffers.get(buffer_num as usize) { + Ok(search_buffer.replace(buffer)) + } else { + Err((buffer, ErrorCode::Invalid)) + } + } +} + +impl Ipc { + fn notify(&self, target: IpcProcessType, to_index: u32) -> CommandReturn { + if to_index >= NUM_PROCS as u32 { + return crate::command_return::failure(ErrorCode::Invalid); + } + + let from_index = self.current_index.get().expect("No current application"); + let service_index = match target { + IpcProcessType::Service => to_index, + IpcProcessType::Client => from_index, + }; + + let share_buffer = self.share_buffers.get(service_index as usize).unwrap(); + let buffer_addr: *mut u8 = share_buffer.borrow_mut().as_mut_ptr(); + + self.mock_address_map + .borrow_mut() + .insert(buffer_addr as u32, buffer_addr); + self.share_ref + .schedule_upcall( + service_index, + ( + from_index, + share_buffer.borrow().len() as u32, + share_buffer.borrow().as_ptr() as u32, + ), + ) + .expect("Unable to schedule upcall {}"); + + crate::command_return::success() + } } #[cfg(test)] From c71aefe791491dd39f1ae5983868abd260b6b159 Mon Sep 17 00:00:00 2001 From: Kat Watson Date: Fri, 9 Aug 2024 14:42:47 -0700 Subject: [PATCH 08/10] Document the IPC API and unittest mock --- apis/kernel/ipc/src/lib.rs | 197 ++++++++++++++++++++++----------- apis/kernel/ipc/src/tests.rs | 15 ++- src/lib.rs | 1 + unittest/src/fake/ipc/mod.rs | 41 +++---- unittest/src/fake/ipc/tests.rs | 108 ++++++++++-------- 5 files changed, 233 insertions(+), 129 deletions(-) diff --git a/apis/kernel/ipc/src/lib.rs b/apis/kernel/ipc/src/lib.rs index e2e178578..b090a4690 100644 --- a/apis/kernel/ipc/src/lib.rs +++ b/apis/kernel/ipc/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(test), no_std)] +#![no_std] use libtock_platform as platform; use platform::{ @@ -6,26 +6,78 @@ use platform::{ ErrorCode, Register, ReturnVariant, Syscalls, Upcall, }; +/// The IPC driver. +/// +/// # Example +/// +/// Service: +/// +/// ```ignore +/// use libtock::ipc::{Ipc, IpcCallData, IpcListener}; +/// use libtock::leds::Leds; +/// +/// fn led_callback(data: IpcCallData) { +/// let _ = Leds::on(0); +/// } +/// +/// // Creates an IPC service for turning on an LED +/// let listener = ipc::IpcListener(led_callback); +/// +/// // Registers the IPC service +/// let _ = Ipc::register_service_listener(listener); +/// ``` +/// +/// Client: +/// +/// ```ignore +/// use libtock::ipc::Ipc; +/// +/// // Discovers the IPC service +/// let service_id = Ipc::discover("org.tockos.example.led").unwrap(); +/// +/// // Runs the IPC service +/// let _ = Ipc::notify_service(service_id); +/// ```` + +#[derive(Debug, Eq, PartialEq)] +pub struct IpcCallData<'a> { + caller_id: u32, + buffer: Option<&'a mut [u8]>, +} + pub struct Ipc(S, C); impl Ipc { - /// Check if the IPC kernel driver exists + /// Run a check against the IPC capsule to ensure it is present + /// + /// Returns Ok(()) if the driver was present. This does not necessarily mean + /// that the driver is working, as it may still fail to allocate grant + /// memory. + #[inline(always)] pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, command::EXISTS, 0, 0).to_result() } - /// Request the service ID of an IPC service by package name + /// Look up the service ID of an IPC service + /// + /// The package name provided should be the one indicated in the TBF header + /// of the Tock binary presenting itself as an IPC service. For additional + /// details, see the Tock Binary Format documentation here: + /// + /// pub fn discover(pkg_name: &[u8]) -> Result { share::scope(|allow_search| { - // Share the package name buffer with the kernel to search for S::allow_ro::(allow_search, pkg_name)?; - - // Send the command to the kernel driver to retrieve the service id for the - // corresponding IPC service, if it exists S::command(DRIVER_NUM, command::DISCOVER, 0, 0).to_result() }) } + /// Register an IPC service + /// + /// This function is called by the IPC service to register a listener under + /// a given package name. Only a single listener can be registered per + /// package name. IPC clients can trigger this function to be executed by + /// calling `Ipc::notify_service` with the current service's service ID. pub fn register_service_listener( pkg_name: &[u8], listener: &'static IpcListener, @@ -34,6 +86,13 @@ impl Ipc { Self::subscribe_ipc::(service_id, listener) } + /// Register a client IPC callback + /// + /// This function is called by the IPC client to register a listener + /// (callback) for a given IPC service, identified by its service ID. A + /// single callback can be registered per service on each client. The + /// corresponding service can trigger this callback using + /// `Ipc::notify_slicent`, returning control to the user. pub fn register_client_listener( service_id: u32, listener: &'static IpcListener, @@ -41,26 +100,72 @@ impl Ipc { Self::subscribe_ipc::(service_id, listener) } + /// Notify an IPC service to run + /// + /// This function is called by the IPC client to trigger an IPC service + /// to run. The service ID passed to this function is the same one that + /// `Ipc::discover` returns. pub fn notify_service(service_id: u32) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, command::SERVICE_NOTIFY, service_id, 0).to_result() } + /// Notify a client IPC callback to run + /// + /// This function is called by the IPC service, generally as part of its + /// listener, to trigger an IPC client callback to run. The client ID + /// passed to this function is the same one that is presented in the + /// `caller_id` field of the `IpcCallData` when the IPC service + /// listener executes. pub fn notify_client(client_id: u32) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, command::CLIENT_NOTIFY, client_id, 0).to_result() } + /// Share a read/write buffer with an IPC service + /// + /// The client can call this function with a static mutable buffer in order + /// to share it via a read/write allow with an IPC service. Since the + /// buffer must be mutable and have static lifetime, the best way to + /// create a reference to it is via a TakeCell (see the `takecell` crate). pub fn share(service_id: u32, buffer: &'static mut [u8]) -> Result<(), ErrorCode> { Self::allow_rw_ipc::(service_id, buffer) } +} + +/// A wrapper around a function to be registered and called when an IPC notify +/// is received. +/// +/// The IPC API for registering listeners accepts static references to +/// instances of this struct, so in general the IPC function this struct +/// wraps should be an actual function instead of a short-lived closure. +pub struct IpcListener(pub F); + +impl Upcall for IpcListener { + fn upcall(&self, caller_id: u32, buffer_len: u32, buffer_ptr: u32) { + let buffer_len = buffer_len as usize; + let buffer = match buffer_len { + 0 => None, + _ => { + let buffer_addr = buffer_ptr as *mut u8; + let buffer = unsafe { core::slice::from_raw_parts_mut(buffer_addr, buffer_len) }; + Some(buffer) + } + }; + self.0(IpcCallData { caller_id, buffer }); + } +} +// ----------------------------------------------------------------------------- +// Implementation details below +// ----------------------------------------------------------------------------- + +impl Ipc { fn subscribe_ipc( process_id: u32, listener: &'static IpcListener, ) -> Result<(), ErrorCode> { - // TODO: revise comments // The upcall function passed to the Tock kernel. // - // Safety: data must be a reference to a valid instance of U. + // Safety: data must be a reference to a valid instance of Fn(IpcCallData). unsafe extern "C" fn kernel_upcall( arg0: u32, arg1: u32, @@ -74,28 +179,22 @@ impl Ipc { } // Inner function that does the majority of the work. This is not - // monomorphized over DRIVER_NUM and SUBSCRIBE_NUM to keep code size - // small. + // 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. + // Safety: upcall_fcn must be kernel_upcall and upcall_data + // must be a static reference to an instance of Fn(IpcCallData). 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. + // 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 static reference to an instance of Fn(IpcCallData), + // guaranteeing that upcall is still alive when kernel_upcall is + // invoked. let [r0, r1, _, _] = unsafe { S::syscall4::<{ syscall_class::SUBSCRIBE }>([ driver_num.into(), @@ -144,11 +243,9 @@ impl Ipc { let upcall_fcn = (kernel_upcall:: as *const ()).into(); let upcall_data = (listener as *const IpcListener).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, SUBSCRIBE_NUM>, - // proving one exists. upcall_fcn and upcall_data are derived in ways - // that satisfy inner's requirements. + // Safety: upcall is a static reference to a Fn(IpcCallData) and will + // therefore always be valid. upcall_fcn and upcall_data are derived in + // ways that satisfy inner's requirements. unsafe { inner::(DRIVER_NUM, process_id, upcall_fcn, upcall_data) } } @@ -159,12 +256,13 @@ impl Ipc { // Inner function that does the majority of the work. This is not // monomorphized over DRIVER_NUM and BUFFER_NUM to keep code size small. // - // Safety: A share::Handle> - // must exist, and `buffer` must last for at least the 'share lifetime. + // Safety: since `buffer` is a static reference, it will outlive + // the actual allow, meaning a `Handle` is not needed as in + // `libtock_platform::Syscalls::allow_rw`. unsafe fn inner( driver_num: u32, buffer_num: u32, - buffer: &mut [u8], + buffer: &'static mut [u8], ) -> Result<(), ErrorCode> { // Safety: syscall4's documentation indicates it can be used to call // Read-Write Allow. These arguments follow TRD104. @@ -211,41 +309,13 @@ impl Ipc { } Ok(()) } - - // Safety: The presence of the share::Handle> - // guarantees that an AllowRw exists and will clean up this Allow ID - // before the 'share lifetime ends. + // Safety: since `allow_rw_ipc` never emits a call to `unallow_rw` + // (unlike `libtock_platform::Syscalls::allow_rw`), we are guaranteed + // that an AllowRw will always exist. unsafe { inner::(DRIVER_NUM, buffer_num, buffer) } } } -#[derive(Debug, Eq, PartialEq)] -pub struct IpcCallData<'a> { - caller_id: u32, - buffer: &'a mut [u8], -} - -/// A wrapper around a static function to be registered as an IPC callback -/// and called when the IPC service receives a notify. -/// -/// ```ignore -/// fn run_on_notify(caller_id: u32, buffer: &mut [u8]) { -/// // make use of the caller ID and shared buffer -/// } -/// -/// let callback = Ipc(run_on_notify); -/// ``` -pub struct IpcListener(pub F); - -impl Upcall for IpcListener { - fn upcall(&self, caller_id: u32, buffer_len: u32, buffer_ptr: u32) { - let buffer_addr = buffer_ptr as *mut u8; - let buffer_len = buffer_len as usize; - let buffer = unsafe { core::slice::from_raw_parts_mut(buffer_addr, buffer_len) }; - self.0(IpcCallData { caller_id, buffer }); - } -} - /// System call configuration trait for `Ipc`. pub trait Config: platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config @@ -273,6 +343,7 @@ mod command { pub const CLIENT_NOTIFY: u32 = 3; } +// Read-only allow numbers mod allow_ro { pub const SEARCH: u32 = 0; } diff --git a/apis/kernel/ipc/src/tests.rs b/apis/kernel/ipc/src/tests.rs index 83d0e1a94..6e0f9f83e 100644 --- a/apis/kernel/ipc/src/tests.rs +++ b/apis/kernel/ipc/src/tests.rs @@ -29,6 +29,7 @@ fn exists() { assert_eq!(Ipc::exists(), Ok(())); } +// Tests the discover implementation #[test] fn discover() { let kernel = fake::Kernel::new(); @@ -46,6 +47,7 @@ fn discover() { ) } +// Tests the register and notify service implementations #[test] fn register_and_notify_service() { static SERVICE_NOTIFIED: AtomicBool = AtomicBool::new(false); @@ -90,6 +92,7 @@ fn register_and_notify_service() { assert!(SERVICE_NOTIFIED.load(Ordering::Relaxed)); } +// Tests the register and notify client implementations #[test] fn register_and_notify_client() { static CLIENT_NOTIFIED: AtomicBool = AtomicBool::new(false); @@ -131,7 +134,13 @@ fn register_and_notify_client() { assert!(CLIENT_NOTIFIED.load(Ordering::Relaxed)); } +// Tests the share buffer implementation +// +// Note that because IPC requires casting the buffer address passed to +// upcalls from a u32 into a *mut u8, and `make test` executes Miri with +// `-Zmiri-strict-provenance`, we need to tell Miri to ignore this test. #[test] +#[cfg_attr(miri, ignore)] fn share() { static BUFFER: TakeCell<[u8; 16]> = TakeCell::new([0; 16]); static EXPECTED_ADDR: AtomicU32 = AtomicU32::new(0); @@ -139,12 +148,14 @@ fn share() { fn service_callback(data: IpcCallData) { assert_eq!(data.caller_id, 1); + + let buffer_slice = data.buffer.expect("No IPC buffer found"); assert_eq!( - data.buffer.as_ptr() as u32, + buffer_slice.as_ptr() as u32, EXPECTED_ADDR.load(Ordering::Relaxed) ); assert_eq!( - data.buffer.len() as u32, + buffer_slice.len() as u32, EXPECTED_LEN.load(Ordering::Relaxed) ) } diff --git a/src/lib.rs b/src/lib.rs index dc9813637..37df5c567 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,7 @@ pub mod gpio { pub mod ipc { use libtock_ipc as ipc; pub type Ipc = ipc::Ipc; + pub use ipc::{IpcCallData, IpcListener}; } pub mod i2c_master { use libtock_i2c_master as i2c_master; diff --git a/unittest/src/fake/ipc/mod.rs b/unittest/src/fake/ipc/mod.rs index 5e6fff9ff..d9768ca8c 100644 --- a/unittest/src/fake/ipc/mod.rs +++ b/unittest/src/fake/ipc/mod.rs @@ -1,12 +1,19 @@ -//! Fake implementation of the Ipc API, documented here: +//! Fake implementation of the IPC API. +//! +//! Like the real IPC API, `Ipc` coordinates interprocess communication +//! between a set of fake processes. It provides a function `as_process` +//! used to mock interfacing with the IPC API as different processes. +//! +//! Process indexes (what the present IPC kernel driver uses to identify IPC +//! services and clients) are assigned implicitly based on the order of +//! processes passed in to `Ipc::new`. use libtock_platform::{CommandReturn, ErrorCode}; use std::cell::{Cell, RefCell}; -use std::collections::HashMap; use crate::{DriverInfo, DriverShareRef, RoAllowBuffer, RwAllowBuffer}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Process { pkg_name: Vec, process_id: u32, @@ -31,7 +38,6 @@ pub struct Ipc { current_index: Cell>, search_buffer: RefCell, share_buffers: [RefCell; NUM_PROCS], - mock_address_map: RefCell>, share_ref: DriverShareRef, } @@ -42,7 +48,6 @@ impl Ipc { current_index: Default::default(), search_buffer: Default::default(), share_buffers: std::array::from_fn(|_| Default::default()), - mock_address_map: Default::default(), share_ref: Default::default(), }) } @@ -127,21 +132,17 @@ impl Ipc { IpcProcessType::Client => from_index, }; - let share_buffer = self.share_buffers.get(service_index as usize).unwrap(); - let buffer_addr: *mut u8 = share_buffer.borrow_mut().as_mut_ptr(); - - self.mock_address_map - .borrow_mut() - .insert(buffer_addr as u32, buffer_addr); + let share_buffer = self + .share_buffers + .get(service_index as usize) + .expect("Unable to access share buffer"); + let share_len = share_buffer.borrow().len() as u32; + let share_ptr = match share_len { + 0 => 0, + _ => share_buffer.borrow().as_ptr() as u32, + }; self.share_ref - .schedule_upcall( - service_index, - ( - from_index, - share_buffer.borrow().len() as u32, - share_buffer.borrow().as_ptr() as u32, - ), - ) + .schedule_upcall(service_index, (from_index, share_len, share_ptr)) .expect("Unable to schedule upcall {}"); crate::command_return::success() @@ -158,7 +159,6 @@ mod tests; const DRIVER_NUM: u32 = 0x10000; // Command IDs - mod command { pub const EXISTS: u32 = 0; pub const DISCOVER: u32 = 1; @@ -166,6 +166,7 @@ mod command { pub const CLIENT_NOTIFY: u32 = 3; } +// Read-only allow numbers mod allow_ro { pub const SEARCH: u32 = 0; } diff --git a/unittest/src/fake/ipc/tests.rs b/unittest/src/fake/ipc/tests.rs index f2d4b9225..d226b3ebd 100644 --- a/unittest/src/fake/ipc/tests.rs +++ b/unittest/src/fake/ipc/tests.rs @@ -47,19 +47,27 @@ fn command() { ); // Service Notify - ipc.set_process(APP_1_PROCESS_ID).unwrap(); - assert!(ipc.command(command::SERVICE_NOTIFY, 2, 0).is_success()); assert_eq!( - ipc.command(command::SERVICE_NOTIFY, 3, 0).get_failure(), - Some(ErrorCode::Invalid) + ipc.as_process(APP_1_PROCESS_ID, || { + assert!(ipc.command(command::SERVICE_NOTIFY, 2, 0).is_success()); + assert_eq!( + ipc.command(command::SERVICE_NOTIFY, 3, 0).get_failure(), + Some(ErrorCode::Invalid) + ); + }), + Ok(()) ); // Client Notify - ipc.set_process(APP_2_PROCESS_ID).unwrap(); - assert!(ipc.command(command::CLIENT_NOTIFY, 0, 0).is_success()); assert_eq!( - ipc.command(command::CLIENT_NOTIFY, 4, 0).get_failure(), - Some(ErrorCode::Invalid) + ipc.as_process(APP_2_PROCESS_ID, || { + assert!(ipc.command(command::CLIENT_NOTIFY, 0, 0).is_success()); + assert_eq!( + ipc.command(command::CLIENT_NOTIFY, 4, 0).get_failure(), + Some(ErrorCode::Invalid) + ); + }), + Ok(()) ); } @@ -107,46 +115,58 @@ fn kernel_integration() { }); // Notify Service - ipc.set_process(APP_1_PROCESS_ID).unwrap(); - let listener = Cell::>::new(None); - share::scope(|subscribe_service| { - assert_eq!( - fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 2>( - subscribe_service, - &listener, - ), - Ok(()) - ); - assert!(fake::Syscalls::command(DRIVER_NUM, command::SERVICE_NOTIFY, 2, 0).is_success()); - assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); - }); - assert_eq!(listener.get(), Some((1, 0, 0))); - assert_eq!( - fake::Syscalls::command(DRIVER_NUM, command::SERVICE_NOTIFY, 3, 0).get_failure(), - Some(ErrorCode::Invalid) + ipc.as_process(APP_1_PROCESS_ID, || { + let listener = Cell::>::new(None); + share::scope(|subscribe_service| { + assert_eq!( + fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 2>( + subscribe_service, + &listener, + ), + Ok(()) + ); + assert!( + fake::Syscalls::command(DRIVER_NUM, command::SERVICE_NOTIFY, 2, 0).is_success() + ); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + }); + assert_eq!(listener.get(), Some((1, 0, 0))); + + assert_eq!( + fake::Syscalls::command(DRIVER_NUM, command::SERVICE_NOTIFY, 3, 0).get_failure(), + Some(ErrorCode::Invalid) + ); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + }), + Ok(()) ); - assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); // Notify Client - ipc.set_process(APP_2_PROCESS_ID).unwrap(); - let listener = Cell::>::new(None); - share::scope(|subscribe_client| { - assert_eq!( - fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 2>( - subscribe_client, - &listener, - ), - Ok(()) - ); - assert!(fake::Syscalls::command(DRIVER_NUM, command::CLIENT_NOTIFY, 0, 0).is_success()); - assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); - }); - assert_eq!(listener.get(), Some((2, 0, 0))); - assert_eq!( - fake::Syscalls::command(DRIVER_NUM, command::CLIENT_NOTIFY, 5, 0).get_failure(), - Some(ErrorCode::Invalid) + ipc.as_process(APP_2_PROCESS_ID, || { + let listener = Cell::>::new(None); + share::scope(|subscribe_client| { + assert_eq!( + fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 2>( + subscribe_client, + &listener, + ), + Ok(()) + ); + assert!( + fake::Syscalls::command(DRIVER_NUM, command::CLIENT_NOTIFY, 0, 0).is_success() + ); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + }); + assert_eq!(listener.get(), Some((2, 0, 0))); + + assert_eq!( + fake::Syscalls::command(DRIVER_NUM, command::CLIENT_NOTIFY, 5, 0).get_failure(), + Some(ErrorCode::Invalid) + ); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + }), + Ok(()) ); - assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); } From 892c5909c75b7582487d17a55623ff775f3d0249 Mon Sep 17 00:00:00 2001 From: Kat Watson Date: Fri, 9 Aug 2024 19:11:55 -0700 Subject: [PATCH 09/10] Add fixed-target values for STM32F3DISCOVERY board to run two apps --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 397a7fd5a..bce51b001 100644 --- a/Makefile +++ b/Makefile @@ -188,6 +188,9 @@ $(call fixed-target, F=0x00048000 R=0x20010000 T=thumbv7em-none-eabi A=cortex-m4 $(call fixed-target, F=0x00080000 R=0x20006000 T=thumbv7em-none-eabi A=cortex-m4) $(call fixed-target, F=0x00088000 R=0x2000e000 T=thumbv7em-none-eabi A=cortex-m4) +$(call fixed-target, F=0x08020000 R=0x20006000 T=thumbv7em-none-eabi A=cortex-m4) +$(call fixed-target, F=0x08028000 R=0x20007000 T=thumbv7em-none-eabi A=cortex-m4) + $(call fixed-target, F=0x403b0000 R=0x3fca2000 T=riscv32imc-unknown-none-elf A=riscv32imc) $(call fixed-target, F=0x40440000 R=0x3fcaa000 T=riscv32imc-unknown-none-elf A=riscv32imc) From 388784d519607926b8b1181a9aae34910650292f Mon Sep 17 00:00:00 2001 From: Kat Watson Date: Mon, 12 Aug 2024 21:44:53 -0700 Subject: [PATCH 10/10] Add examples, fix last issues with shared buffer lifetimes --- apis/kernel/ipc/Cargo.toml | 1 - apis/kernel/ipc/src/lib.rs | 6 +-- apis/kernel/ipc/src/tests.rs | 10 ++--- examples/ipc_client.rs | 74 ++++++++++++++++++++++++++++++++++++ examples/ipc_service.rs | 40 +++++++++++++++++++ 5 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 examples/ipc_client.rs create mode 100644 examples/ipc_service.rs diff --git a/apis/kernel/ipc/Cargo.toml b/apis/kernel/ipc/Cargo.toml index 0e3123407..400b01d8f 100644 --- a/apis/kernel/ipc/Cargo.toml +++ b/apis/kernel/ipc/Cargo.toml @@ -13,4 +13,3 @@ libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } -takecell = "0.1.1" diff --git a/apis/kernel/ipc/src/lib.rs b/apis/kernel/ipc/src/lib.rs index b090a4690..b8399f136 100644 --- a/apis/kernel/ipc/src/lib.rs +++ b/apis/kernel/ipc/src/lib.rs @@ -21,7 +21,7 @@ use platform::{ /// } /// /// // Creates an IPC service for turning on an LED -/// let listener = ipc::IpcListener(led_callback); +/// let listener = IpcListener(led_callback); /// /// // Registers the IPC service /// let _ = Ipc::register_service_listener(listener); @@ -41,8 +41,8 @@ use platform::{ #[derive(Debug, Eq, PartialEq)] pub struct IpcCallData<'a> { - caller_id: u32, - buffer: Option<&'a mut [u8]>, + pub caller_id: u32, + pub buffer: Option<&'a mut [u8]>, } pub struct Ipc(S, C); diff --git a/apis/kernel/ipc/src/tests.rs b/apis/kernel/ipc/src/tests.rs index 6e0f9f83e..7c95604d7 100644 --- a/apis/kernel/ipc/src/tests.rs +++ b/apis/kernel/ipc/src/tests.rs @@ -1,7 +1,6 @@ use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use libtock_platform::{ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake; -use takecell::TakeCell; use crate::{IpcCallData, IpcListener}; @@ -142,7 +141,7 @@ fn register_and_notify_client() { #[test] #[cfg_attr(miri, ignore)] fn share() { - static BUFFER: TakeCell<[u8; 16]> = TakeCell::new([0; 16]); + static mut BUFFER: &mut [u8] = &mut [0; 16]; static EXPECTED_ADDR: AtomicU32 = AtomicU32::new(0); static EXPECTED_LEN: AtomicU32 = AtomicU32::new(0); @@ -181,11 +180,10 @@ fn share() { assert_eq!( driver.as_process(CLIENT_PROCESS_ID, || { - let buffer = BUFFER.take().unwrap(); - EXPECTED_ADDR.store(buffer.as_ptr() as u32, Ordering::Relaxed); - EXPECTED_LEN.store(buffer.len() as u32, Ordering::Relaxed); + EXPECTED_ADDR.store(unsafe { BUFFER.as_ptr() } as u32, Ordering::Relaxed); + EXPECTED_LEN.store(unsafe { BUFFER.len() } as u32, Ordering::Relaxed); - assert_eq!(Ipc::share(0, buffer), Ok(())); + assert_eq!(Ipc::share(0, unsafe { BUFFER }), Ok(())); assert_eq!(Ipc::notify_service(4), Err(ErrorCode::Invalid)); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(Ipc::notify_service(0), Ok(())); diff --git a/examples/ipc_client.rs b/examples/ipc_client.rs new file mode 100644 index 000000000..ee21fb5ff --- /dev/null +++ b/examples/ipc_client.rs @@ -0,0 +1,74 @@ +//! A simple IPC example for using an RNG service. +//! +//! This application uses an RNG IPC service which, on request, will yield a +//! random string of bytes. It prints this random string to the console. + +#![no_main] +#![no_std] +use core::fmt::Write; +use libtock::alarm::{Alarm, Milliseconds}; +use libtock::console::Console; +use libtock::ipc::{Ipc, IpcCallData, IpcListener}; +use libtock::platform::Syscalls; +use libtock::runtime::TockSyscalls; +use libtock::runtime::{set_main, stack_size}; + +set_main! {main} +stack_size! {0x200} + +const NUM_BYTES: usize = 8; +static mut BUFFER: RngBuffer = RngBuffer([0; NUM_BYTES]); +const CLIENT_LISTENER: IpcListener = IpcListener(callback); + +#[repr(align(8))] +struct RngBuffer([u8; N]); + +struct Randomness<'a>(&'a [u8]); + +impl<'a> core::fmt::Display for Randomness<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut bytes = self.0.iter(); + while let Some(&byte) = bytes.next() { + write!(f, "{byte:02x}")?; + } + Ok(()) + } +} + +fn callback(data: IpcCallData) { + writeln!(Console::writer(), "callback thingy: {:?}", data.buffer,).unwrap(); + // writeln!( + // Console::writer(), + // "CLIENT: Successfully received random bytes {}.", + // Randomness(data.buffer.unwrap()) + // ) + // .unwrap(); +} + +fn main() { + let rng_service: u32 = match Ipc::discover(b"ipc_service") { + Ok(service_id) => service_id, + Err(e) => { + writeln!( + Console::writer(), + "CLIENT: Unable to discover IPC service: {e:?}. Is IPC service installed?" + ) + .unwrap(); + return; + } + }; + Ipc::register_client_listener(rng_service, &CLIENT_LISTENER).unwrap(); + Ipc::share(rng_service, unsafe { &mut BUFFER.0 }).unwrap(); + + loop { + writeln!( + Console::writer(), + "CLIENT: Requesting random bytes from {}...", + rng_service + ) + .unwrap(); + Ipc::notify_service(rng_service).unwrap(); + TockSyscalls::yield_wait(); + Alarm::sleep_for(Milliseconds(2000)).unwrap(); + } +} diff --git a/examples/ipc_service.rs b/examples/ipc_service.rs new file mode 100644 index 000000000..eff59494b --- /dev/null +++ b/examples/ipc_service.rs @@ -0,0 +1,40 @@ +//! A simple IPC example for setting up an RNG IPC service. +//! +//! This application sets up an RNG IPC service which, on request, will yield +//! a random string of bytes via a provided shared memory region. + +#![no_main] +#![no_std] +use core::fmt::Write; +use libtock::console::Console; +use libtock::ipc::{Ipc, IpcCallData, IpcListener}; +use libtock::platform::Syscalls; +use libtock::rng::Rng; +use libtock::runtime::TockSyscalls; +use libtock::runtime::{set_main, stack_size}; + +set_main! {main} +stack_size! {0x200} + +const NUM_BYTES: u32 = 8; +const SERVICE_LISTENER: IpcListener = IpcListener(callback); + +fn callback(data: IpcCallData) { + writeln!( + Console::writer(), + "SERVICE: Request received, generating random bytes..." + ) + .unwrap(); + + writeln!(Console::writer(), "SERVICE: {:?}", data.buffer).unwrap(); + + Rng::get_bytes_sync(data.buffer.unwrap(), NUM_BYTES).unwrap(); + Ipc::notify_client(data.caller_id).unwrap(); +} + +fn main() { + Ipc::register_service_listener(b"ipc_service", &SERVICE_LISTENER).unwrap(); + loop { + TockSyscalls::yield_wait(); + } +}