Skip to content

Commit 1a64253

Browse files
committed
libtock/util/streaming_process_slice: switch to accepting two buffers
Instead of internally splitting a single buffer to create two halves used in the streaming process slice abstraction, this changes it to accept two separate buffers for the kernel- and application-owned buffer respectively. This was discussed on the pull request introducing this abstraction: #492 (comment) Suggested-by: Branden Ghena <[email protected]>
1 parent cac7f87 commit 1a64253

File tree

2 files changed

+102
-95
lines changed

2 files changed

+102
-95
lines changed

libtock/util/streaming_process_slice.c

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,31 @@ returncode_t streaming_process_slice_init(
1616
streaming_process_slice_state_t* state,
1717
uint32_t driver,
1818
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) {
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) {
2425
return RETURNCODE_ESIZE;
2526
}
2627

2728
state->driver = driver;
2829
state->allow = allow;
2930

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);
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);
4441

4542
allow_rw_return_t allow_res =
46-
allow_readwrite(driver, allow, kernel_buffer_ptr, kernel_buffer_size);
43+
allow_readwrite(driver, allow, buffer_b, size_b);
4744
if (!allow_res.success) {
4845
memset(state, 0, sizeof(streaming_process_slice_state_t));
4946
}
@@ -56,10 +53,6 @@ returncode_t streaming_process_slice_get_and_swap(
5653
uint8_t** buffer,
5754
uint32_t* size,
5855
bool* exceeded) {
59-
uint8_t* ret_buffer;
60-
uint32_t ret_size;
61-
bool ret_exceeded;
62-
6356
// Prepare the current app buffer to be shared with the kernel (writing a
6457
// zeroed-out header):
6558
streaming_process_slice_prepare_header(state->app_buffer_ptr);
@@ -69,20 +62,20 @@ returncode_t streaming_process_slice_get_and_swap(
6962
allow_readwrite(state->driver, state->allow, state->app_buffer_ptr,
7063
state->app_buffer_size);
7164

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;
7269
if (allow_res.success) {
7370
// Record the new app buffer:
7471
state->app_buffer_ptr = allow_res.ptr;
7572
state->app_buffer_size = allow_res.size;
73+
state->app_buffer_is_b = !state->app_buffer_is_b;
7674

7775
// Return information about the received payload:
7876
ret_buffer = state->app_buffer_ptr + 8;
7977
memcpy(&ret_size, state->app_buffer_ptr + 4, sizeof(uint32_t));
8078
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;
8679
}
8780

8881
// Write return values if provided with non-NULL pointers:
@@ -101,43 +94,50 @@ returncode_t streaming_process_slice_get_and_swap(
10194

10295
returncode_t streaming_process_slice_deinit(
10396
streaming_process_slice_state_t* state,
104-
uint8_t** buffer,
105-
size_t* size) {
106-
uint8_t* ret_buffer;
107-
size_t ret_size;
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;
108106

109107
// Unallow the buffer currently allowed to the kernel:
110108
allow_rw_return_t unallow_res =
111109
allow_readwrite(state->driver, state->allow, NULL, 0);
112110

113111
if (unallow_res.success) {
114-
// Unallow failed, don't modify the state struct.
115-
ret_buffer = NULL;
116-
ret_size = 0;
117-
} else {
118112
// The unallow worked, recreate the full, initial buffer from the app and
119113
// 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;
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;
125119
} 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;
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;
130124
}
131125

132126
// Wipe the state struct:
133127
memset(state, 0, sizeof(streaming_process_slice_state_t));
134128
}
135129

136-
if (buffer != NULL) {
137-
*buffer = ret_buffer;
130+
if (buffer_a != NULL) {
131+
*buffer_a = ret_buffer_a;
138132
}
139-
if (size != NULL) {
140-
*size = ret_size;
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;
141141
}
142142

143143
return tock_status_to_returncode(unallow_res.status);
Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,64 @@
11
#include "../tock.h"
22

3+
#define STREAMING_PROCESS_SLICE_HEADER_LEN 8
4+
35
typedef struct {
4-
uint8_t* app_buffer_ptr;
5-
size_t app_buffer_size;
66
uint32_t driver;
77
uint32_t allow;
8+
uint8_t* app_buffer_ptr;
9+
size_t app_buffer_size;
10+
bool app_buffer_is_b;
811
} streaming_process_slice_state_t;
912

1013
// Initialize a "streaming process slice" read-write allow slot
1114
//
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).
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.
2228
//
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.
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.
3032
//
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`.
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.
4041
returncode_t streaming_process_slice_init(
4142
streaming_process_slice_state_t* state,
4243
uint32_t driver,
4344
uint32_t allow,
44-
void* buffer,
45-
size_t size);
45+
void* buffer_a,
46+
size_t size_a,
47+
void* buffer_b,
48+
size_t size_b);
4649

4750
// Swap kernel- for app-owned buffer and get received payload
4851
//
49-
// This method atomically swaps the kernel-owned and application-owned
50-
// halves of the streaming process slice. This function will reset the
52+
// This method atomically swaps the kernel-owned and application-owned buffers
53+
// backing this streaming process slice. This function will reset the
5154
// application-owned buffers header, applying any flags set in the
52-
// `streaming_process_slice_state_t` and setting the write offset to
53-
// `0`.
55+
// `streaming_process_slice_state_t` and setting the write offset to `0`.
5456
//
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.
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.
6162
//
6263
// This function forwards any error from the underlying `allow_readwrite`
6364
// operation in its return value. In case of a return value other than
@@ -71,15 +72,21 @@ returncode_t streaming_process_slice_get_and_swap(
7172

7273
// Deinitialize an initialized `streaming_process_slice_state_t`
7374
//
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).
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`.
7782
//
7883
// This function forwards any error from the underlying `allow_readwrite`
7984
// operation in its return value. In case of a return value other than
8085
// `RETURNCODE_SUCCESS`, any values returned in `buffer`, `size` and `exceeded`
8186
// must not be considered valid.
8287
returncode_t streaming_process_slice_deinit(
8388
streaming_process_slice_state_t* state,
84-
uint8_t** buffer,
85-
size_t* size);
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)