Skip to content

Commit cac7f87

Browse files
committed
libtock: add util/streaming_process_slice
This adds userspace helpers implementing the "streaming process slice" contract as implemented in the Tock kernel in [1]. Instead of working with two separate buffers, these helpers present an interface similar to a regular read-write allow based on a single buffer. Internally, this buffer is then subdivided into an "application-owned" and "kernel-owned" part which are swapped atomically. The caller is responsible for keeping a `streaming_process_slice_state_t`, and is provided with ephemeral references to the kernel-written payloads on each swap operation. The full, original buffer can be reclaimed by deinitializing the `streaming_process_slice_state_t` struct. Currently, these helpers are quite minimal and do not expose the ability to set any optional flags (such as `halt`). They are tested to work against against the interface implemented in [1] (with the fix of [2] included) as part of the LwIP Ethernet tap-driver userspace app. [1]: tock/tock#4208 [2]: tock/tock#4343
1 parent 14e590a commit cac7f87

File tree

4 files changed

+248
-0
lines changed

4 files changed

+248
-0
lines changed

libtock/Makefile

Lines changed: 1 addition & 0 deletions
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

Lines changed: 18 additions & 0 deletions
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>
Lines changed: 144 additions & 0 deletions
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,
20+
size_t size) {
21+
// Each slice's header is 8 bytes long, and we create two slices from this
22+
// buffer. Thus ensure that the provided buffer is at least 16 bytes long:
23+
if (size < 16) {
24+
return RETURNCODE_ESIZE;
25+
}
26+
27+
state->driver = driver;
28+
state->allow = allow;
29+
30+
// We split the buffer in half, an application and kernel side. These two
31+
// buffers are then atomically swapped with each other.
32+
//
33+
// Initially, the first half of this buffer is designated as the application
34+
// buffer.
35+
state->app_buffer_ptr = buffer;
36+
state->app_buffer_size = size / 2;
37+
38+
// Write a streaming process slice header to the second half of this buffer,
39+
// and allow it to be the kernel buffer. We currently only support version
40+
// 0, and don't set the `halt` flag:
41+
uint8_t* kernel_buffer_ptr = state->app_buffer_ptr + state->app_buffer_size;
42+
size_t kernel_buffer_size = size - state->app_buffer_size;
43+
streaming_process_slice_prepare_header(kernel_buffer_ptr);
44+
45+
allow_rw_return_t allow_res =
46+
allow_readwrite(driver, allow, kernel_buffer_ptr, kernel_buffer_size);
47+
if (!allow_res.success) {
48+
memset(state, 0, sizeof(streaming_process_slice_state_t));
49+
}
50+
51+
return tock_status_to_returncode(allow_res.status);
52+
}
53+
54+
returncode_t streaming_process_slice_get_and_swap(
55+
streaming_process_slice_state_t* state,
56+
uint8_t** buffer,
57+
uint32_t* size,
58+
bool* exceeded) {
59+
uint8_t* ret_buffer;
60+
uint32_t ret_size;
61+
bool ret_exceeded;
62+
63+
// Prepare the current app buffer to be shared with the kernel (writing a
64+
// zeroed-out header):
65+
streaming_process_slice_prepare_header(state->app_buffer_ptr);
66+
67+
// Swap the current app buffer for the kernel buffer:
68+
allow_rw_return_t allow_res =
69+
allow_readwrite(state->driver, state->allow, state->app_buffer_ptr,
70+
state->app_buffer_size);
71+
72+
if (allow_res.success) {
73+
// Record the new app buffer:
74+
state->app_buffer_ptr = allow_res.ptr;
75+
state->app_buffer_size = allow_res.size;
76+
77+
// Return information about the received payload:
78+
ret_buffer = state->app_buffer_ptr + 8;
79+
memcpy(&ret_size, state->app_buffer_ptr + 4, sizeof(uint32_t));
80+
ret_exceeded = (state->app_buffer_ptr[3] & 0x01) == 0x01;
81+
} else {
82+
// Allow was not successful, return safe dummy values instead:
83+
ret_buffer = NULL;
84+
ret_size = 0;
85+
ret_exceeded = false;
86+
}
87+
88+
// Write return values if provided with non-NULL pointers:
89+
if (buffer != NULL) {
90+
*buffer = ret_buffer;
91+
}
92+
if (size != NULL) {
93+
*size = ret_size;
94+
}
95+
if (exceeded != NULL) {
96+
*exceeded = ret_exceeded;
97+
}
98+
99+
return tock_status_to_returncode(allow_res.status);
100+
}
101+
102+
returncode_t streaming_process_slice_deinit(
103+
streaming_process_slice_state_t* state,
104+
uint8_t** buffer,
105+
size_t* size) {
106+
uint8_t* ret_buffer;
107+
size_t ret_size;
108+
109+
// Unallow the buffer currently allowed to the kernel:
110+
allow_rw_return_t unallow_res =
111+
allow_readwrite(state->driver, state->allow, NULL, 0);
112+
113+
if (unallow_res.success) {
114+
// Unallow failed, don't modify the state struct.
115+
ret_buffer = NULL;
116+
ret_size = 0;
117+
} else {
118+
// The unallow worked, recreate the full, initial buffer from the app and
119+
// kernel halves:
120+
if ((void*)state->app_buffer_ptr < unallow_res.ptr) {
121+
// App buffer is left half, kernel buffer is right half:
122+
// `[ app_buffer ][ kernel_buffer ]`
123+
ret_buffer = state->app_buffer_ptr;
124+
ret_size = state->app_buffer_size + unallow_res.size;
125+
} else {
126+
// App buffer is right half, kernel buffer is left half:
127+
// `[ kernel_buffer ][ app_buffer ]`
128+
ret_buffer = unallow_res.ptr;
129+
ret_size = unallow_res.size + state->app_buffer_size;
130+
}
131+
132+
// Wipe the state struct:
133+
memset(state, 0, sizeof(streaming_process_slice_state_t));
134+
}
135+
136+
if (buffer != NULL) {
137+
*buffer = ret_buffer;
138+
}
139+
if (size != NULL) {
140+
*size = ret_size;
141+
}
142+
143+
return tock_status_to_returncode(unallow_res.status);
144+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#include "../tock.h"
2+
3+
typedef struct {
4+
uint8_t* app_buffer_ptr;
5+
size_t app_buffer_size;
6+
uint32_t driver;
7+
uint32_t allow;
8+
} streaming_process_slice_state_t;
9+
10+
// Initialize a "streaming process slice" read-write allow slot
11+
//
12+
// This method allows a userspace buffer into a "streaming process
13+
// slice" allow slot, implementing its atomic-swap semantics and
14+
// header layout. The streaming process slice abstraction allows a
15+
// userspace process to lossessly receive data from a kernel
16+
// capsule. This is done by maintaining two buffers, where at any time
17+
// one of which is owned by the kernel (for writing new, incoming data
18+
// into) and one by the application, to process received data. These
19+
// buffers are atomically swapped by the application, upon receipt of
20+
// a signal that some data has been inserted into the kernel-owned
21+
// buffer (such as an upcall).
22+
//
23+
// This method abstracts this interface by consuming one buffer and
24+
// splitting it into two halves, owned by the application and kernel
25+
// respectively. It tracks all necessary state in the
26+
// `streaming_process_slice_state_t` object. For this to work, the
27+
// passed buffer must be able to hold at least two streaming process
28+
// slice headers (8 byte each), i.e., it must be at least 16 bytes
29+
// long.
30+
//
31+
// In case of an error while allowing the kernel-owned buffer to the
32+
// specified driver and read-write allow slot, this function converts
33+
// this error-status into a returncode using
34+
// `tock_status_to_returncode` and returns it to the caller. When this
35+
// method returns `RETURNCODE_SUCCESS`, the passed buffer is assumed
36+
// to be owned by this `streaming_process_slice_state_t` and must not
37+
// be used until after a successful call to
38+
// `streaming_process_slice_deinit`. When the buffer is of
39+
// insufficient size, it returns `RETURNCODE_ESIZE`.
40+
returncode_t streaming_process_slice_init(
41+
streaming_process_slice_state_t* state,
42+
uint32_t driver,
43+
uint32_t allow,
44+
void* buffer,
45+
size_t size);
46+
47+
// Swap kernel- for app-owned buffer and get received payload
48+
//
49+
// This method atomically swaps the kernel-owned and application-owned
50+
// halves of the streaming process slice. This function will reset the
51+
// application-owned buffers header, applying any flags set in the
52+
// `streaming_process_slice_state_t` and setting the write offset to
53+
// `0`.
54+
//
55+
// Following the swap operation, when returning `RETURNCODE_SUCCESS`,
56+
// it provides the buffer's payload and any kernel-set flags to the
57+
// caller through the `buffer`, `size`, and `exceeded` arguments
58+
// respectively. Callers must either provide pointers to variables for
59+
// these values, or set them to `NULL` in case they are not interested
60+
// in any given value.
61+
//
62+
// This function forwards any error from the underlying `allow_readwrite`
63+
// operation in its return value. In case of a return value other than
64+
// `RETURNCODE_SUCCESS`, any values returned in `buffer`, `size` and `exceeded`
65+
// must not be considered valid.
66+
returncode_t streaming_process_slice_get_and_swap(
67+
streaming_process_slice_state_t* state,
68+
uint8_t** buffer,
69+
uint32_t* size,
70+
bool* exceeded);
71+
72+
// Deinitialize an initialized `streaming_process_slice_state_t`
73+
//
74+
// This function reconstructs the passed into `streaming_process_slice_init` and
75+
// returns it through the `buffer` and `size` arguments (if not set to `NULL`
76+
// respectively).
77+
//
78+
// This function forwards any error from the underlying `allow_readwrite`
79+
// operation in its return value. In case of a return value other than
80+
// `RETURNCODE_SUCCESS`, any values returned in `buffer`, `size` and `exceeded`
81+
// must not be considered valid.
82+
returncode_t streaming_process_slice_deinit(
83+
streaming_process_slice_state_t* state,
84+
uint8_t** buffer,
85+
size_t* size);

0 commit comments

Comments
 (0)