pub struct StreamingProcessSlice<'a> { /* private fields */ }
Expand description

A wrapper around a WriteableProcessSlice for streaming data from the kernel to a userspace process.

Applications like ADC sampling or network stacks require the kernel to provide a process with a continuous, lossless stream of data from a source that is not rate-controlled by the process. This wrapper implements the kernel-side of a simple protocol to achieve this goal, without requiring kernel-side buffering and by utilizing the atomic swap semantics of Tock’s allow system call. The protocol is versioned; the semantics for version 0 are as follows:

  1. To receive a data stream from the kernel, a userspace process allocates two buffers.

  2. The first buffer is prepared according to the format below. The flags field’s version bits are set to 0. The process clears the exceeded flag. It may set or clear the halt flag. All reserved flags must be set to 0. Finally, the offset bytes (interpreted as a u32 value in native endianness) are set to 0.

  3. The process allows this buffer to a kernel driver.

  4. The kernel driver writes incoming data starting at the data field + offset bytes. After each write, the kernel increments offset by the number of bytes written.

    For each chunk written to the buffer (where a chunk is an application-defined construct, such as a network packet), the kernel only increments offset if the full chunk was successfully written into the buffer. The kernel may or may not modify any data after the current offset value, regardless of whether any header fields were updated. The kernel never modifies any data in the region of [data.start; data.start + offset).

    Should the write of a chunk fail because the buffer has insufficient space left, the kernel will set the exceeded flag bit (index 0).

    The halt flag bit as set by the process governs the kernel’s behavior once the exceeded flag is set: if halt is cleared, the kernel will attempt to write future, smaller chunks to the buffer (and thus implicitly discarding some packets). If halt and exceeded are both set, the kernel will stop writing any data into the buffer.

  5. The kernel will schedule an upcall to the process, indicating that a write to the buffer (or setting the exceeded) flag occurred. The kernel may schedule only one upcall for the first chunk written to the buffer, or multiple upcalls (e.g., one upcall per chunk written). A process must not rely on the number of upcalls received and instead rely on the buffer header (offset and the flags bits) to determine the amount of data written to the buffer.

  6. The process prepares its second buffer, following step 2. The process then issues an allow operation that atomically swaps the current allowed buffer by its second buffer.

  7. The process can now process the received chunks contained in the initial buffer, while the kernel receives new chunks in the other, newly allowed buffer.

As the kernel cannot track if an allowed buffer for a particular [SyscallDriver] implementation is intended to be a StreamingProcessSlice, the kernel must use the header in the buffer as provided by the process. The implementation of StreamingProcessSlice ensures that an incorrect header will not cause a panic, but incoming packets could be dropped. A process using a syscall API that uses a StreamingProcessSlice must ensure it has properly initialized the header before allowing the buffer.

The version 0 buffer format is specified as follows:

0           2           4           6           8
+-----------+-----------+-----------------------+----------...
| version   | flags     | write offset (32 bit) | data
+-----------+-----------+-----------------------+----------...
| 000...000 | x{14},H,E | <native endian u32>   |
+-----------+-----------+-----------------------+----------...

The version field is a u16 integer stored in the target’s native endianness. The flags field is a bitfield laid out as shown in the diagram above (laid out in big endian, with E being the least significant bit at byte 3). The offset field is a u32 integer stored in the target’s native endianness.

The kernel does not impose any alignment restrictions on StreamingProcessSlices of version 0.

The flags field is structured as follows:

  • V: version bits. This kernel only supports version 0.
  • H: halt flag. If this flag is set and the exceeded flag is set, the kernel will not write any further data to this buffer.
  • E: exceeded flag. The kernel sets this flag when the remaining buffer capacity is insufficient to append the current chunk.
  • x{14}: reserved flag bits. Unless specified otherwise, processes must clear these flags prior to allowing a buffer to the kernel. A kernel that does not know of a reserved flag must refuse to operate on a buffer that has such a flag set.

Implementations§

source§

impl<'a> StreamingProcessSlice<'a>

source

pub fn new(slice: &'a WriteableProcessSlice) -> StreamingProcessSlice<'_>

source

pub fn append_chunk(&self, chunk: &[u8]) -> Result<(bool, u32), ErrorCode>

Append a chunk of data to the slice.

If the underlying slice has a correct flags and offset value, is not halted, and has sufficient space for this data chunk, this function returns the updated buffer offset (set to one past the last written byte).

This function returns whether this chunk was the first non-zero-length chunk appended to the slice, and the offset after the append operation (where the next chunk would be written in the data section).

This function fails with:

  • INVAL: if the version is not 0, or the reserved flags are not cleared.
  • BUSY: if both the halt and exceeded flags are set. In this case, the slice will not be modified.
  • SIZE: if the underlying slice is not large enough to fit the flags field and the offset field. In this case, the exceeded flag will be set and the slice will not be modified.
  • FAIL: would need to increment offset beyond 2**32 - 1. Neither the payload slice nor any header fields will be modified.

Appending a zero-length chunk will be treated as every other chunk, but appending it will not set the exceeded flag, even if offset is at the maximum position for this buffer. A zero-length append operation can still fail due to the buffer being halted, having an improper header, etc. A zero-length chunk will never be treated as the first chunk appended to a buffer.

source

pub fn append_chunk_from_iter<I: IntoIterator<Item = u8>>( &self, src: I, ) -> Result<(bool, u32), ErrorCode>

Append a chunk of data from an iterator.

If the underlying slice has a correct flags and offset value, is not halted, and has sufficient space for this data chunk, this function returns the updated buffer offset (set to one past the last written byte).

This function returns whether this chunk was the first non-zero-length chunk appended to the slice, and the offset after the append operation (where the next chunk would be written in the data section).

If the buffer does not have enough space, this function will still partially copy this chunk and modify the slice payload data after offset. It will not update the offset header field though, and instead set the exceeded flag.

This function fails with:

  • INVAL: if the version is not 0, or the reserved flags are not cleared.
  • BUSY: if both the halt and exceeded flags are set. In this case, the slice will not be modified.
  • SIZE: if the underlying slice is not large enough to fit the flags field and the offset field. In this case, the exceeded flag will be set and the slice will not be modified.
  • FAIL: would need to increment offset beyond 2**32 - 1. Neither the payload slice nor any header fields will be modified.

Appending a zero-length chunk will be treated as every other chunk, but appending it will not set the exceeded flag, even if offset is at the maximum position for this buffer. A zero-length append operation can still fail due to the buffer being halted, having an improper header, etc. A zero-length chunk will never be treated as the first chunk appended to a buffer.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.