blob: 386562fcafb19a22754aa599092878c275ffb1a0 [file] [log] [blame]
/// An individual value that has been shared with the kernel using the `allow`
/// system call.
// Allowed's implementation does not directly use the 'b lifetime. Platform uses
// 'b to prevent the Allowed from accessing the buffer after the buffer becomes
// invalid.
// Allowed requires T to be Copy due to concerns about the semantics of
// non-copyable types in shared memory as well as concerns about unexpected
// behavior with Drop types. See the following PR discussion for more
// information: https://github.com/tock/libtock-rs/pull/222
pub struct Allowed<'b, T: Copy + 'b> {
// Safety properties:
// 1. `buffer` remains valid and usable for the lifetime of this Allowed
// instance.
// 2. Read and write accesses of `buffer`'s pointee must be performed as a
// volatile operation, as the kernel may mutate buffer's pointee at any
// time.
// 3. The value `buffer` points to may have an arbitrary bit pattern in
// it, so reading from `buffer` is only safe if the type contained
// within is AllowReadable.
buffer: core::ptr::NonNull<T>,
// We need to use the 'b lifetime in Allowed to prevent an "unused lifetime"
// compiler error. We use it here. Note that the phantom data must be an
// &mut rather than a shared reference in order to make Allowed invariant
// with respect to T. Invariance is required because Allowed allows us to
// mutate the value in buffer, and invariance is the required property to do
// so without allowing callers to produce dangling references. This is
// documented at https://doc.rust-lang.org/nomicon/subtyping.html.
_phantom: core::marker::PhantomData<&'b mut T>,
}
// Allowed's API is based on that of core::cell::Cell, but removes some methods
// that are not safe for use with shared memory.
//
// Internally, Allowed performs accesses to the shared memory using volatile
// reads and writes through raw pointers. We avoid constructing references to
// shared memory because that leads to undefined behavior (there is some
// background on this in the following discussion:
// https://github.com/rust-lang/unsafe-code-guidelines/issues/33). Tock runs on
// single-threaded platforms, some of which lack atomic instructions, so we only
// need to be able to deconflict races between the kernel (which will never
// interrupt an instruction's execution) and this process. Therefore volatile
// accesses are sufficient to deconflict races.
impl<'b, T: Copy + 'b> Allowed<'b, T> {
// Allowed can only be constructed by the Platform. It is constructed after
// the `allow` system call, and as such must accept a raw pointer rather
// than a reference. The caller must make sure the following are true:
// 1. buffer points to a valid instance of type T
// 2. There are no references to buffer's pointee
// 3. buffer remains usable until the Allowed's lifetime has ended.
#[allow(dead_code)] // TODO: Remove when Platform is implemented
pub(crate) unsafe fn new(buffer: core::ptr::NonNull<T>) -> Allowed<'b, T> {
Allowed {
buffer,
_phantom: core::marker::PhantomData,
}
}
// Sets the value in the buffer.
pub fn set(&self, value: T) {
unsafe {
core::ptr::write_volatile(self.buffer.as_ptr(), value);
}
}
}
impl<'b, T: crate::AllowReadable + Copy + 'b> Allowed<'b, T> {
pub fn get(&self) -> T {
unsafe { core::ptr::read_volatile(self.buffer.as_ptr()) }
}
}