Skip to content

Commit

Permalink
libtock-platform work.
Browse files Browse the repository at this point in the history
  • Loading branch information
jrvanwhy committed Sep 1, 2020
1 parent b13550b commit 738d2dd
Show file tree
Hide file tree
Showing 14 changed files with 435 additions and 41 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ exclude = [ "tock" ]
members = [
"codegen",
"core",
"core/console",
"core/platform",
"core/sync",
"test_runner",
"tools/print_sizes",
]
11 changes: 11 additions & 0 deletions core/console/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
authors = ["Tock Project Developers <[email protected]>"]
categories = ["embedded", "no-std", "os"]
edition = "2018"
license = "Apache-2.0 OR MIT"
name = "libtock_console"
repository = "https://www.github.com/tock/libtock-rs"
version = "0.1.0"

[dependencies]
libtock_platform = { path = "../platform" }
8 changes: 8 additions & 0 deletions core/console/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[derive(Clone,Copy)]
pub struct Console;

impl Console {
pub fn check_exists<'a, P: libtock_platform::Platform<'a>>() -> libtock_platform::ReturnCode {
unimplemented!()
}
}
14 changes: 0 additions & 14 deletions core/platform/src/allows/allowed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,7 @@ impl<'b, T: Copy + 'b> Allowed<'b, T> {
}

impl<'b, T: crate::AllowReadable + Copy + 'b> Allowed<'b, T> {
pub fn replace(&self, value: T) -> T {
let current = unsafe { core::ptr::read_volatile(self.buffer.as_ptr()) };
unsafe {
core::ptr::write_volatile(self.buffer.as_ptr(), value);
}
current
}

pub fn get(&self) -> T {
unsafe { core::ptr::read_volatile(self.buffer.as_ptr()) }
}
}

impl<'b, T: crate::AllowReadable + Copy + Default + 'b> Allowed<'b, T> {
pub fn take(&self) -> T {
self.replace(T::default())
}
}
78 changes: 78 additions & 0 deletions core/platform/src/allows/allowed_slice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/// `AllowedSlice` is a slice-based analogue of `Allowed`. It represents a slice
/// that has been shared with the kernel using the `allow` system call.
/// Unlike `Allowed`, `AllowSlice`'s methods accept an index into the shared
/// slice, and operate on the element at that index.
// Like Allowed, AllowedSlice does not directly use the 'b lifetime. Platform
// uses 'b to prevent the AllowedSlice from accessing the buffer after the
// buffer becomes invalid.
// AllowedSlice requires T to be Copy for the same reasons as Allowed. See
// Allowed's comments for more information.
pub struct AllowedSlice<'b, T: Copy + 'b> {
// `data` points to the start of the shared slice, and `len` is the length
// of that slice.
// Safety properties:
// 1. The slice remains valid and usable for the lifetime of this
// AllowedSlice instance.
// 2. Read and write accesses into the slice must be performed as a
// volatile operation, as the kernel may mutate the slice at any time.
// 3. The shared slice may have an arbitrary bit pattern in it, so reading
// from the slice is only safe if the type contained within is
// AllowReadable.
data: core::ptr::NonNull<T>,
len: usize,

// Use the 'b parameter, and make AllowedSlice invariant w.r.t. T. See
// Allowed's comment for a more detailed description.
_phantom: core::marker::PhantomData<&'b mut [T]>,
}

// AllowedSlice's API mirrors that of Allowed, but with indexing on most
// methods. Se Allowed's comments for more details.
impl<'b, T: Copy + 'b> AllowedSlice<'b, T> {
// The caller (Platform) must make sure the following are true:
// 1. `data` points to a valid [T] slice of length `len`.
// 2. There are no other references to the slice.
// 3. The slice remains usable until the AllowedSlice's lifetime has ended.
#[allow(unused)] // TODO: Remove when Platform is implemented.
pub(crate) unsafe fn new(data: core::ptr::NonNull<T>, len: usize) -> AllowedSlice<'b, T> {
AllowedSlice {
data,
len,
_phantom: core::marker::PhantomData,
}
}

/// Sets the value at `index`, or does nothing if `index` is out of range.
pub fn set(&self, index: usize, value: T) -> Result<(), OutOfBounds> {
if index >= self.len {
return Err(OutOfBounds);
}
unsafe {
core::ptr::write_volatile(self.data.as_ptr().add(index), value);
}
Ok(())
}
}

impl<'b, T: crate::AllowReadable + Copy + 'b> AllowedSlice<'b, T> {
/// Returns the value at `index` without changing it.
pub fn get(&self, index: usize) -> Result<T, OutOfBounds> {
if index >= self.len {
return Err(OutOfBounds);
}
Ok(unsafe { core::ptr::read_volatile(self.data.as_ptr().add(index)) })
}

/// Returns the value at `index` without changing it. If `index` is out of
/// bounds, returns the provided default value.
pub fn get_or_default(&self, index: usize, default: T) -> T {
if index >= self.len {
return default;
}
unsafe { core::ptr::read_volatile(self.data.as_ptr().add(index)) }
}
}

/// An error type indicating an out-of-bounds access.
#[derive(PartialEq)]
pub struct OutOfBounds;
93 changes: 93 additions & 0 deletions core/platform/src/allows/allowed_slice_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use crate::{AllowedSlice, OutOfBounds};
use core::marker::PhantomData;
use core::ptr::NonNull;

// KernelSlice simulates a kernel's access to a slice that has been shared with
// the kernel. It is the slice-based equivalent to KernelPtr, defined in
// allowed_tests.rs. See KernelPtr's documentation for a description of
// KernelSlice's purpose.
struct KernelSlice<'b, T: Copy + 'b> {
data: NonNull<T>,
len: usize,

// Consume the 'b lifetime.
_phantom: PhantomData<&'b mut [T]>,
}

impl<'b, T: Copy + 'b> KernelSlice<'b, T> {
pub fn allow_slice(buffer: &'b mut [T]) -> (AllowedSlice<'b, T>, KernelSlice<'b, T>) {
let data = NonNull::new(buffer.as_mut_ptr()).unwrap();
let len = buffer.len();
let _ = buffer;
let allowed_slice = unsafe { AllowedSlice::new(data, len) };
let kernel_slice = KernelSlice {
data,
len,
_phantom: PhantomData,
};
(allowed_slice, kernel_slice)
}

// Copies the value out of the given entry in the buffer and returns it.
pub fn get(&self, index: usize) -> T {
assert!(index < self.len);
unsafe { core::ptr::read(self.data.as_ptr().add(index)) }
}
}

#[test]
fn set() {
let mut buffer = [0, 1, 2];
let (allowed_slice, kernel_slice) = KernelSlice::allow_slice(&mut buffer);
assert_eq!(kernel_slice.get(0), 0);
assert_eq!(kernel_slice.get(1), 1);
assert_eq!(kernel_slice.get(2), 2);

assert!(allowed_slice.set(4, 4) == Err(OutOfBounds));
assert_eq!(kernel_slice.get(0), 0);
assert_eq!(kernel_slice.get(1), 1);
assert_eq!(kernel_slice.get(2), 2);

assert!(allowed_slice.set(1, 4) == Ok(()));
assert_eq!(kernel_slice.get(0), 0);
assert_eq!(kernel_slice.get(1), 4);
assert_eq!(kernel_slice.get(2), 2);
}

#[test]
fn get() {
let mut buffer = [0, 1, 2];
let (allowed_slice, kernel_slice) = KernelSlice::allow_slice(&mut buffer);
assert_eq!(kernel_slice.get(0), 0);
assert_eq!(kernel_slice.get(1), 1);
assert_eq!(kernel_slice.get(2), 2);

assert!(allowed_slice.get(4).is_err());
assert_eq!(kernel_slice.get(0), 0);
assert_eq!(kernel_slice.get(1), 1);
assert_eq!(kernel_slice.get(2), 2);

assert!(allowed_slice.get(1) == Ok(1));
assert_eq!(kernel_slice.get(0), 0);
assert_eq!(kernel_slice.get(1), 1);
assert_eq!(kernel_slice.get(2), 2);
}

#[test]
fn get_or_default() {
let mut buffer = [0, 1, 2];
let (allowed_slice, kernel_slice) = KernelSlice::allow_slice(&mut buffer);
assert_eq!(kernel_slice.get(0), 0);
assert_eq!(kernel_slice.get(1), 1);
assert_eq!(kernel_slice.get(2), 2);

assert_eq!(allowed_slice.get_or_default(4, 3), 3);
assert_eq!(kernel_slice.get(0), 0);
assert_eq!(kernel_slice.get(1), 1);
assert_eq!(kernel_slice.get(2), 2);

assert_eq!(allowed_slice.get_or_default(1, 4), 1);
assert_eq!(kernel_slice.get(0), 0);
assert_eq!(kernel_slice.get(1), 1);
assert_eq!(kernel_slice.get(2), 2);
}
26 changes: 0 additions & 26 deletions core/platform/src/allows/allowed_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,19 +83,6 @@ fn set() {
assert_eq!(kernel_ptr.get(), 3);
}

#[test]
fn replace() {
let mut buffer = 1;
let (allowed, kernel_ptr) = KernelPtr::allow(&mut buffer);
assert_eq!(kernel_ptr.get(), 1);

// Simulate the kernel replacing the value in buffer.
kernel_ptr.set(2);
let returned = allowed.replace(3);
assert_eq!(returned, 2);
assert_eq!(kernel_ptr.get(), 3);
}

#[test]
fn get() {
let mut buffer = 1;
Expand All @@ -109,16 +96,3 @@ fn get() {
assert_eq!(allowed.get(), 2);
assert_eq!(kernel_ptr.get(), 2);
}

#[test]
fn take() {
let mut buffer = 1;
let (allowed, kernel_ptr) = KernelPtr::allow(&mut buffer);
assert_eq!(kernel_ptr.get(), 1);

// Simulate the kernel replacing the value in buffer.
kernel_ptr.set(2);
let returned = allowed.take();
assert_eq!(returned, 2);
assert_eq!(kernel_ptr.get(), 0);
}
4 changes: 4 additions & 0 deletions core/platform/src/allows/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
mod allow_readable;
mod allowed;
mod allowed_slice;

pub use allow_readable::AllowReadable;
pub use allowed::Allowed;
pub use allowed_slice::{AllowedSlice, OutOfBounds};

#[cfg(test)]
mod allowed_slice_tests;
#[cfg(test)]
mod allowed_tests;
59 changes: 59 additions & 0 deletions core/platform/src/async_traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//! `async_traits` provides the `AsyncCall`, `Callback`, and `StaticCallback`
//! traits. These traits form the basis for `libtock_core`'s asynchronous API
//! structure.
//!
//! In general, an `AsyncCall` is used to initiate an asynchronous operation.
//! When the operation is complete, a `Callback` or `StaticCall` is used to
//! inform the client of the operation's completion. Clients may then issue
//! additional `AsyncCall` invocations from within the response callback.
//!
//! Note that callbacks should not be issued from within the `AsyncCall` call;
//! doing so causes reentrancy and stack utilization issues. This is enforced
//! through use of the CallbackContext type parameter.
//!
//! These traits are designed to be implemented on zero sized types (ZSTs) that
//! refer indirectly to the objects that implement the corresponding
//! functionality. As a result, they require `Copy` and take `self` by value. In
//! many cases, these traits will be implemented on references to objects rather
//! than on the objects themselves. For example, a `Console` driver would
//! implement `AsyncCall` on `&Console`, not on `Console` itself, as `Console`
//! would not be a `Copy` type.
/// Represents a call that starts an asynchronous operation. The `start` request
/// can return a payload synchronously; this may be used to return a "failed to
/// start"-style error message.
pub trait AsyncCall<Request, SyncResponse>: Copy {
fn start(self, request: Request) -> SyncResponse;
}

/// A callback reporting the results of an asynchronous operation. Like
/// AsyncCall, this callback may carry data, and as such `Callback` may be
/// implemented on reference types.
pub trait Callback<AsyncResponse>: Copy {
fn callback(self, context: CallbackContext, response: AsyncResponse);
}

/// A marker type indicating this method executes as a callback. In this case,
/// callbacks refer either to kernel callbacks (those resulting from a
/// `subscribe` sysem call) or to deferred callbacks. In both cases, the
/// `Platform` implementation provides the `CallbackContext`.
///
/// `CallbackContext` is `Copy` so it is easy to pass into function invocations.
// The lifetime parameter 'c exists to prevent a CallbackContext from being
// moved into a location that outlives the callback's execution.
#[derive(Clone, Copy)]
pub struct CallbackContext<'c> {
// This serves two purposes. First, it is `pub(crate)`, so user code cannot
// construct the CallbackContext. Second, it uses the lifetime parameter to
// avoid an "unused lifetime parameter" error.
pub(crate) _private: core::marker::PhantomData<&'c ()>,
}

/// A variation of callback that is not allowed to carry data. This conveys the
/// callback entirely via the type system. This is used in places that cannot
/// carry runtime data, such as the system call API, but generally requires the
/// client to store the data it needs in a `static` location. As such, APIs
/// should prefer to support `Callback` wherever possible.
pub trait StaticCallback<AsyncResponse> {
fn callback(context: CallbackContext, response: AsyncResponse);
}
8 changes: 7 additions & 1 deletion core/platform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@
// unit test environments. [DONE]

mod allows;
mod async_traits;
mod error_code;
mod platform_api;
mod return_code;
mod syscalls;

pub use allows::{AllowReadable, Allowed};
pub use allows::{AllowReadable, Allowed, AllowedSlice, OutOfBounds};
pub use async_traits::{AsyncCall, Callback, CallbackContext, StaticCallback};
pub use error_code::ErrorCode;
pub use platform_api::PlatformApi;
pub use return_code::ReturnCode;
pub use syscalls::{MemopNoArg, MemopWithArg, Syscalls};
38 changes: 38 additions & 0 deletions core/platform/src/platform_api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! PlatformApi is a trait presenting a safe interface to Tock's system API as
//! well as a deferred call mechanism. It is implemented by the Platform type in
//! this crate.
//!
//! PlatformApi exists so that code that uses Platform's API only needs one type
//! parameter, rather than the two required by Platform.
// A lifetime (for buffers) is omitted because it is extremely annoying to use
// as a generic constraint: you need drivers to have a 'k generic argument to
// pass into the PlatformApi<'k> constraint, but then you get errors about 'k
// being unused.
// TODO: Remove the lifetime parameter on Allowed and AllowedSlice.
pub trait PlatformApi: Copy {
// Shares a value with the kernel. The value becomes a read-write shared
// buffer between userspace and the kernel.
fn allow<T: Copy>(
self,
driver: usize,
minor: usize,
buffer: &'static mut T,
) -> Result<crate::Allowed<'static, T>, crate::ErrorCode>;

// Shares a slice with the kernel. The shared slice becomes a read-write
// shared buffer between userspace and the kernel.
fn allow_slice<T: Copy>(
self,
driver: usize,
minor: usize,
buffer: &'static mut [T],
) -> Result<crate::AllowedSlice<'static, T>, crate::ErrorCode>;

// TODO: Finish PlatformApi

// Executes a single callback. Will run a deferred callback if one is
// available, or wait for one kernel callback if no deferred callback is
// queued.
fn run_callback(self);
}
Loading

0 comments on commit 738d2dd

Please sign in to comment.