Merge #260
260: Add traits for lightweight asynchronous APIs to `libtock_platform`. r=alistair23 a=jrvanwhy
This adds `CallbackContext`, `FreeCallback`, `Locator`, and `MethodCallback`.
Co-authored-by: Johnathan Van Why <jrvanwhy@google.com>
diff --git a/core/platform/src/async_traits.rs b/core/platform/src/async_traits.rs
new file mode 100644
index 0000000..2853026
--- /dev/null
+++ b/core/platform/src/async_traits.rs
@@ -0,0 +1,93 @@
+//! Traits for building lightweight asynchronous APIs. These traits lack the
+//! dynamic capabilities of `core::future::Future`, but have much smaller code
+//! size and RAM usage costs.
+//!
+//! Tock kernel developers will be familiar with the 2-phase pattern of
+//! operation these traits support. Client code (code using an asynchronous
+//! component) calls a function or method provided by the asynchronous component
+//! to start an asynchronous operation. When the operation is complete, the
+//! asynchronous component calls a callback defined using the `FreeCallback`
+//! and/or `MethodCallback` traits.
+//!
+//! Note that asynchronous callbacks must only be called from within "callback
+//! context" -- that is, within a kernel callback (registered using the
+//! `subscribe` system call). To enforce that, the callback traits require the
+//! `CallbackContext` type, which is only instantiated by an instance of the
+//! `Syscalls` trait.
+//!
+//! There is no prohibition on a callback calling back into the asynchronous
+//! component that called it. In other words, if component B calls
+//! `<A as FreeCallback<Done>>::call(...)`, then `call()` can call back into B
+//! to start a new operation. Asynchronous components should clean up their
+//! state internally before calling callbacks so as to support reentrant calls
+//! into themselves!
+//!
+//! In client code, prefer to implement `FreeCallback` instead of
+//! `MethodCallback` when possible, as it is easier to pass a `FreeCallback` to
+//! an asynchronous component.
+//!
+//! To bridge the gap between `FreeCallback` and `MethodCallback`, we also have
+//! the `Locator` trait. `Locator` allows `FreeCallback` implementations to find
+//! their global state, and also provides `FreeCallback` versions of callbacks
+//! for types that implement `MethodCallback`. In general, `Locator` should be
+//! implemented by `libtock_runtime::static_component!` (in real Tock apps) and
+//! `libtock_unittest::test_component!` (in unit tests), rather than directly by
+//! user code.
+// TODO: At the time of this writing, neither `libtock_runtime` nor
+// `libtock_unittest` are implemented. Remove this TODO when they are
+// implemented.
+
+/// `FreeCallback` is the callback equivalent of a free function: it does not
+/// have access to the client component's data. `FreeCallback` is used by
+/// asynchronous components -- such as `Syscalls` -- which cannot efficiently
+/// store a client reference.
+pub trait FreeCallback<AsyncResponse> {
+ fn call(context: CallbackContext, response: AsyncResponse);
+}
+
+/// `MethodCallback` is a callback method; it can access the client component's
+/// data. Note that asynchronous components generally need to use interior
+/// mutability to mutate data, as `MethodCallback` is designed under the
+/// assumption that there are multiple references to most asynchronous
+/// components at any given time.
+pub trait MethodCallback<AsyncResponse> {
+ fn call(&self, context: CallbackContext, response: AsyncResponse);
+}
+
+/// `Syscalls` instantiates a `CallbackContext` when a kernel callback is
+/// called. The lifetime prevents the `CallbackContext` from being copied into
+/// storage that outlives the callback. Code that is only safe to call from
+/// callback context can request a `CallbackContext` argument. `CallbackContext`
+/// is a zero-sized type so passing it around has no runtime cost.
+#[derive(Clone, Copy)]
+pub struct CallbackContext<'c> {
+ // `_phantom` serves three purposes. It uses the `c lifetime to avoid an
+ // "unused lifetime" error, it provides the proper variance over 'c, and it
+ // prevents code outside this crate from directly constructing a
+ // CallbackContext (because of its visibility control). Code outside this
+ // crate can copy a CallbackContext, but that is fine as copying the
+ // CallbackContext preserves its associated lifetime.
+ pub(crate) _phantom: core::marker::PhantomData<&'c ()>,
+}
+
+/// Provides access to a global instance of type `Target`. Every call to
+/// `locate()` on a given Locator type should return a reference to the same
+/// instance of `Target`. An instance of `Locator` generally isn't instantiated
+/// directly; instead, its type is passed to where it is needed via generic
+/// arguments.
+///
+/// For convenience, Locator provides a `FreeCallback` implementation for every
+/// `MethodCallback` implementation that `Target` has.
+pub trait Locator: 'static {
+ type Target;
+ fn locate() -> &'static Self::Target;
+}
+
+impl<L: Locator, AsyncResponse> FreeCallback<AsyncResponse> for L
+where
+ L::Target: MethodCallback<AsyncResponse>,
+{
+ fn call(context: CallbackContext, response: AsyncResponse) {
+ L::locate().call(context, response);
+ }
+}
diff --git a/core/platform/src/lib.rs b/core/platform/src/lib.rs
index 7237a1d..abcb8bd 100644
--- a/core/platform/src/lib.rs
+++ b/core/platform/src/lib.rs
@@ -9,9 +9,11 @@
// unit test environments. [DONE]
mod allows;
+mod async_traits;
mod error_code;
mod syscalls;
pub use allows::{AllowReadable, Allowed};
+pub use async_traits::{CallbackContext, FreeCallback, Locator, MethodCallback};
pub use error_code::ErrorCode;
pub use syscalls::{MemopNoArg, MemopWithArg, Syscalls};