Skip to content

Commit 39f83a8

Browse files
authored
Merge pull request #492 from tock/dev/streaming-process-slice
libtock: add util/streaming_process_slice
2 parents 8212d86 + 1a64253 commit 39f83a8

File tree

4 files changed

+255
-0
lines changed

4 files changed

+255
-0
lines changed

libtock/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ $(LIBNAME)_SRCS += $(wildcard $($(LIBNAME)_DIR)/sensors/*.c)
2525
$(LIBNAME)_SRCS += $(wildcard $($(LIBNAME)_DIR)/sensors/syscalls/*.c)
2626
$(LIBNAME)_SRCS += $(wildcard $($(LIBNAME)_DIR)/storage/*.c)
2727
$(LIBNAME)_SRCS += $(wildcard $($(LIBNAME)_DIR)/storage/syscalls/*.c)
28+
$(LIBNAME)_SRCS += $(wildcard $($(LIBNAME)_DIR)/util/*.c)
2829

2930
# Temporary hack for alarm
3031
$(LIBNAME)_SRCS += $(wildcard $($(LIBNAME)_DIR)/internal/*.c)

libtock/util/README.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# `libtock/util`
2+
3+
This directory contains utilities related to abstractions provided by
4+
the Tock kernel. Currently, the following utilities exist:
5+
6+
- Streaming Process Slice:
7+
[`streaming_process_slice.h`](./streaming_process_slice.h)
8+
9+
A contract over read-write allows for losslessly streaming data from the Tock
10+
kernel to a userspace process.
11+
12+
Applications like ADC sampling or network stacks require the kernel to provide
13+
a process with a continuous, lossless stream of data from a source that is not
14+
rate-controlled by the process. This utility implements the userspace-side of
15+
a simple protocol to achieve this goal, without requiring kernel-side
16+
buffering and by utilizing the atomic swap semantics of Tock’s allow system
17+
call. For more information on this contract, see
18+
<https://docs.tockos.org/kernel/utilities/streaming_process_slice/struct.streamingprocessslice>
+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
#include "streaming_process_slice.h"
2+
#include <string.h>
3+
4+
static void streaming_process_slice_prepare_header(uint8_t* buf) {
5+
buf[0] = 0; // version[H]
6+
buf[1] = 0; // version[L]
7+
buf[2] = 0; // flags[H]
8+
buf[3] = 0; // flags[L]
9+
buf[4] = 0; // write offset
10+
buf[5] = 0; // write offset
11+
buf[6] = 0; // write offset
12+
buf[7] = 0; // write offset
13+
}
14+
15+
returncode_t streaming_process_slice_init(
16+
streaming_process_slice_state_t* state,
17+
uint32_t driver,
18+
uint32_t allow,
19+
void* buffer_a,
20+
size_t size_a,
21+
void* buffer_b,
22+
size_t size_b) {
23+
// Ensure that both buffers can hold the streaming process slice header:
24+
if (size_a < STREAMING_PROCESS_SLICE_HEADER_LEN || size_b < STREAMING_PROCESS_SLICE_HEADER_LEN) {
25+
return RETURNCODE_ESIZE;
26+
}
27+
28+
state->driver = driver;
29+
state->allow = allow;
30+
31+
// Initially, `buffer_a` is used as the application-owned buffer.
32+
state->app_buffer_ptr = buffer_a;
33+
state->app_buffer_size = size_a;
34+
state->app_buffer_is_b = false; // false -> buffer_a
35+
36+
// Write a streaming process slice header to bufferB, and use it as the
37+
// kernel-owned buffer. Its pointer and length are stored within the kernel's
38+
// allow slot. We currently only support version 0, and don't set the `halt`
39+
// flag:
40+
streaming_process_slice_prepare_header(buffer_b);
41+
42+
allow_rw_return_t allow_res =
43+
allow_readwrite(driver, allow, buffer_b, size_b);
44+
if (!allow_res.success) {
45+
memset(state, 0, sizeof(streaming_process_slice_state_t));
46+
}
47+
48+
return tock_status_to_returncode(allow_res.status);
49+
}
50+
51+
returncode_t streaming_process_slice_get_and_swap(
52+
streaming_process_slice_state_t* state,
53+
uint8_t** buffer,
54+
uint32_t* size,
55+
bool* exceeded) {
56+
// Prepare the current app buffer to be shared with the kernel (writing a
57+
// zeroed-out header):
58+
streaming_process_slice_prepare_header(state->app_buffer_ptr);
59+
60+
// Swap the current app buffer for the kernel buffer:
61+
allow_rw_return_t allow_res =
62+
allow_readwrite(state->driver, state->allow, state->app_buffer_ptr,
63+
state->app_buffer_size);
64+
65+
// Initialize to safe dummy values in case the allow was not successful
66+
uint8_t* ret_buffer = NULL;
67+
uint32_t ret_size = 0;
68+
bool ret_exceeded = false;
69+
if (allow_res.success) {
70+
// Record the new app buffer:
71+
state->app_buffer_ptr = allow_res.ptr;
72+
state->app_buffer_size = allow_res.size;
73+
state->app_buffer_is_b = !state->app_buffer_is_b;
74+
75+
// Return information about the received payload:
76+
ret_buffer = state->app_buffer_ptr + 8;
77+
memcpy(&ret_size, state->app_buffer_ptr + 4, sizeof(uint32_t));
78+
ret_exceeded = (state->app_buffer_ptr[3] & 0x01) == 0x01;
79+
}
80+
81+
// Write return values if provided with non-NULL pointers:
82+
if (buffer != NULL) {
83+
*buffer = ret_buffer;
84+
}
85+
if (size != NULL) {
86+
*size = ret_size;
87+
}
88+
if (exceeded != NULL) {
89+
*exceeded = ret_exceeded;
90+
}
91+
92+
return tock_status_to_returncode(allow_res.status);
93+
}
94+
95+
returncode_t streaming_process_slice_deinit(
96+
streaming_process_slice_state_t* state,
97+
uint8_t** buffer_a,
98+
size_t* size_a,
99+
uint8_t** buffer_b,
100+
size_t* size_b) {
101+
// Safe default values:
102+
uint8_t* ret_buffer_a = NULL;
103+
size_t ret_size_b = 0;
104+
uint8_t* ret_buffer_b = NULL;
105+
size_t ret_size_a = 0;
106+
107+
// Unallow the buffer currently allowed to the kernel:
108+
allow_rw_return_t unallow_res =
109+
allow_readwrite(state->driver, state->allow, NULL, 0);
110+
111+
if (unallow_res.success) {
112+
// The unallow worked, recreate the full, initial buffer from the app and
113+
// kernel halves:
114+
if (state->app_buffer_is_b) {
115+
ret_buffer_a = unallow_res.ptr;
116+
ret_size_a = unallow_res.size;
117+
ret_buffer_b = state->app_buffer_ptr;
118+
ret_size_b = state->app_buffer_size;
119+
} else {
120+
ret_buffer_a = state->app_buffer_ptr;
121+
ret_size_a = state->app_buffer_size;
122+
ret_buffer_b = unallow_res.ptr;
123+
ret_size_b = unallow_res.size;
124+
}
125+
126+
// Wipe the state struct:
127+
memset(state, 0, sizeof(streaming_process_slice_state_t));
128+
}
129+
130+
if (buffer_a != NULL) {
131+
*buffer_a = ret_buffer_a;
132+
}
133+
if (size_a != NULL) {
134+
*size_a = ret_size_a;
135+
}
136+
if (buffer_b != NULL) {
137+
*buffer_b = ret_buffer_b;
138+
}
139+
if (size_b != NULL) {
140+
*size_b = ret_size_b;
141+
}
142+
143+
return tock_status_to_returncode(unallow_res.status);
144+
}
+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#include "../tock.h"
2+
3+
#define STREAMING_PROCESS_SLICE_HEADER_LEN 8
4+
5+
typedef struct {
6+
uint32_t driver;
7+
uint32_t allow;
8+
uint8_t* app_buffer_ptr;
9+
size_t app_buffer_size;
10+
bool app_buffer_is_b;
11+
} streaming_process_slice_state_t;
12+
13+
// Initialize a "streaming process slice" read-write allow slot
14+
//
15+
// This method allows a userspace buffer into a "streaming process slice" allow
16+
// slot, implementing its atomic-swap semantics and header layout. The streaming
17+
// process slice abstraction allows a userspace process to lossessly receive
18+
// data from a kernel capsule. This is done by maintaining two buffers, where at
19+
// any time one of which is owned by the kernel (for writing new, incoming data
20+
// into) and one by the application, to process received data. These buffers are
21+
// atomically swapped by the application, upon receipt of a signal that some
22+
// data has been inserted into the kernel-owned buffer (such as an upcall).
23+
//
24+
// This method abstracts this interface by consuming two buffers, owned by the
25+
// application and kernel respectively. It tracks all necessary state in the
26+
// `streaming_process_slice_state_t` object. This struct is initialized by this
27+
// method.
28+
//
29+
// The passed buffers must be each be able to hold at least the streaming
30+
// process slice headers (`STREAMING_PROCESS_SLICE_HEADER_LEN` bytes), in
31+
// addition to any payload.
32+
//
33+
// In case of an error while allowing the kernel-owned buffer to the specified
34+
// driver and read-write allow slot, this function converts this error-status
35+
// into a returncode using `tock_status_to_returncode` and returns it to the
36+
// caller. When this method returns `RETURNCODE_SUCCESS`, the passed buffers are
37+
// assumed to be owned by this `streaming_process_slice_state_t` and must not be
38+
// used until after a successful call to `streaming_process_slice_deinit`. When
39+
// either buffer is of insufficient size, it returns `RETURNCODE_ESIZE` and does
40+
// not perform any allow operation or initialize the state reference.
41+
returncode_t streaming_process_slice_init(
42+
streaming_process_slice_state_t* state,
43+
uint32_t driver,
44+
uint32_t allow,
45+
void* buffer_a,
46+
size_t size_a,
47+
void* buffer_b,
48+
size_t size_b);
49+
50+
// Swap kernel- for app-owned buffer and get received payload
51+
//
52+
// This method atomically swaps the kernel-owned and application-owned buffers
53+
// backing this streaming process slice. This function will reset the
54+
// application-owned buffers header, applying any flags set in the
55+
// `streaming_process_slice_state_t` and setting the write offset to `0`.
56+
//
57+
// Following the swap operation, when returning `RETURNCODE_SUCCESS`, it
58+
// provides the buffer's payload and any kernel-set flags to the caller through
59+
// the `buffer`, `size`, and `exceeded` arguments respectively. Callers must
60+
// either provide pointers to variables for these values, or set them to `NULL`
61+
// in case they are not interested in any given value.
62+
//
63+
// This function forwards any error from the underlying `allow_readwrite`
64+
// operation in its return value. In case of a return value other than
65+
// `RETURNCODE_SUCCESS`, any values returned in `buffer`, `size` and `exceeded`
66+
// must not be considered valid.
67+
returncode_t streaming_process_slice_get_and_swap(
68+
streaming_process_slice_state_t* state,
69+
uint8_t** buffer,
70+
uint32_t* size,
71+
bool* exceeded);
72+
73+
// Deinitialize an initialized `streaming_process_slice_state_t`
74+
//
75+
// This function returns the buffers passed into `streaming_process_slice_init`
76+
// through the `buffer_a`, `size_a`, `buffer_b` and `size_b` arguments (if not
77+
// set to `NULL` respectively). It ensures that the `buffer_a` and `size_a`
78+
// arguments passed to `streaming_process_slice_init` match those of `buffer_a`
79+
// and `size_a` for this function, i.e., it will not swap `buffer_a` for
80+
// `buffer_b`, regardless of the number of calls to
81+
// `streaming_process_slice_get_and_swap`.
82+
//
83+
// This function forwards any error from the underlying `allow_readwrite`
84+
// operation in its return value. In case of a return value other than
85+
// `RETURNCODE_SUCCESS`, any values returned in `buffer`, `size` and `exceeded`
86+
// must not be considered valid.
87+
returncode_t streaming_process_slice_deinit(
88+
streaming_process_slice_state_t* state,
89+
uint8_t** buffer_a,
90+
size_t* size_a,
91+
uint8_t** buffer_b,
92+
size_t* size_b);

0 commit comments

Comments
 (0)