lib/os: Add sys_winstream lockless shared memory byte stream IPC
It's not uncommon to have Zephyr running in environments where it shares a memory bus with a foreign/non-Zephyr system (both the older Intel Quark and cAVS audio DSP systems share this property). In those circumstances, it would be nice to have a utility that allows an arbitrary-sized chunk of that memory to be used as a unidirectional buffered byte stream without requiring complicated driver support. sys_winstream is one such abstraction. This code is lockless, it makes no synchronization demands of the OS or hardware beyond memory ordering[1]. It implements a simple file/socket-style read/write API. It produces small code and is high performance (e.g. a read or write on Xtensa is about 60 cycles plus one per byte copied). It's bidirectional, with no internal Zephyr dependencies (allowing it to be easily ported to the foreign system). And it's quite a bit simpler (especially for the reader) than the older cAVS trace protocol it's designed to replace. [1] Which means that right now it won't work reliably on arm64 until we add a memory barrier framework to Zephyr! See notes in the code; the locations for the barriers are present, but there's no utility to call. Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
This commit is contained in:
parent
b246de1ba2
commit
528bef2d22
8 changed files with 361 additions and 0 deletions
5
tests/unit/winstream/CMakeLists.txt
Normal file
5
tests/unit/winstream/CMakeLists.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
project(winstream)
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
set(SOURCES main.c)
|
||||
find_package(ZephyrUnittest REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
106
tests/unit/winstream/main.c
Normal file
106
tests/unit/winstream/main.c
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Intel Corporation
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <ztest.h>
|
||||
#include <sys/winstream.h>
|
||||
|
||||
/* This, uh, seems to be the standard way to unit test library code.
|
||||
* Or so I gather from tests/unit/rbtree ...
|
||||
*/
|
||||
#include "../../../lib/os/winstream.c"
|
||||
|
||||
#define BUFLEN 64
|
||||
|
||||
const char *msg = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
char wsmem[BUFLEN + 1]; /* Extra 1 to have a null for easier debugging */
|
||||
|
||||
void test_winstream(void)
|
||||
{
|
||||
struct sys_winstream *ws = sys_winstream_init(wsmem, BUFLEN);
|
||||
|
||||
/* Write one byte */
|
||||
sys_winstream_write(ws, "a", 1);
|
||||
|
||||
uint32_t seq = 0;
|
||||
char c;
|
||||
|
||||
/* Read the byte back */
|
||||
uint32_t bytes = sys_winstream_read(ws, &seq, &c, 1);
|
||||
|
||||
zassert_true(bytes == 1, "");
|
||||
zassert_true(seq == 1, "");
|
||||
zassert_true(c == 'a', "");
|
||||
|
||||
/* Read from an empty buffer */
|
||||
bytes = sys_winstream_read(ws, &seq, &c, 1);
|
||||
zassert_true(bytes == 0, "");
|
||||
zassert_true(seq == 1, "");
|
||||
|
||||
/* Write an overflowing string */
|
||||
sys_winstream_write(ws, msg, strlen(msg));
|
||||
zassert_true(ws->seq == 1 + strlen(msg), "");
|
||||
zassert_true(ws->start == 1, "");
|
||||
zassert_true(ws->end == 0, "");
|
||||
|
||||
/* Read after underflow, verify empty string comes back with the
|
||||
* correct sequence number
|
||||
*/
|
||||
char readback[BUFLEN + 1];
|
||||
|
||||
memset(readback, 0, sizeof(readback));
|
||||
bytes = sys_winstream_read(ws, &seq, readback, sizeof(readback));
|
||||
zassert_true(seq == ws->seq, "");
|
||||
zassert_true(bytes == 0, "");
|
||||
|
||||
/* Read back from empty buffer */
|
||||
uint32_t seq0 = seq;
|
||||
|
||||
bytes = sys_winstream_read(ws, &seq, readback, sizeof(readback));
|
||||
zassert_true(seq == seq0, "");
|
||||
zassert_true(bytes == 0, "");
|
||||
|
||||
/* Write a "short-enough" string that fits in before the wrap,
|
||||
* then read it out
|
||||
*/
|
||||
seq0 = seq;
|
||||
sys_winstream_write(ws, msg, ws->len / 2);
|
||||
bytes = sys_winstream_read(ws, &seq, readback, sizeof(readback));
|
||||
zassert_true(bytes == ws->len / 2, "");
|
||||
zassert_true(seq == seq0 + ws->len / 2, "");
|
||||
zassert_true(strncmp(readback, msg, ws->len / 2) == 0, "");
|
||||
|
||||
/* Do it again, this time it will need to wrap around the buffer */
|
||||
memset(readback, 0, sizeof(readback));
|
||||
seq0 = seq;
|
||||
sys_winstream_write(ws, msg, ws->len / 2);
|
||||
bytes = sys_winstream_read(ws, &seq, readback, sizeof(readback));
|
||||
zassert_true(bytes == ws->len / 2, "");
|
||||
zassert_true(seq == seq0 + ws->len / 2, "");
|
||||
zassert_true(strncmp(readback, msg, ws->len / 2) == 0, "");
|
||||
|
||||
/* Finally loop with a relatively prime (actually prime prime)
|
||||
* buffer size to stress for edges.
|
||||
*/
|
||||
int n = 13;
|
||||
char msg2[13];
|
||||
|
||||
for (int i = 0; i < (n + 1) * (ws->len + 1); i++) {
|
||||
memset(msg2, 'A' + (i % 26), n);
|
||||
seq0 = seq;
|
||||
memset(readback, 0, sizeof(readback));
|
||||
sys_winstream_write(ws, msg2, n);
|
||||
bytes = sys_winstream_read(ws, &seq, readback, sizeof(readback));
|
||||
zassert_true(bytes == n, "");
|
||||
zassert_true(seq == seq0 + n, "");
|
||||
zassert_true(strncmp(readback, msg2, n) == 0, "");
|
||||
}
|
||||
}
|
||||
|
||||
void test_main(void)
|
||||
{
|
||||
ztest_test_suite(test_winstream,
|
||||
ztest_unit_test(test_winstream)
|
||||
);
|
||||
ztest_run_test_suite(test_winstream);
|
||||
}
|
4
tests/unit/winstream/testcase.yaml
Normal file
4
tests/unit/winstream/testcase.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
tests:
|
||||
utilities.winstream:
|
||||
tags: winstream
|
||||
type: unit
|
Loading…
Add table
Add a link
Reference in a new issue