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:
Andy Ross 2021-12-30 19:29:49 -08:00 committed by Anas Nashif
commit 528bef2d22
8 changed files with 361 additions and 0 deletions

View 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
View 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);
}

View file

@ -0,0 +1,4 @@
tests:
utilities.winstream:
tags: winstream
type: unit