| /* |
| * Copyright (c) 2021 Intel Corporation |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <zephyr/sys/util.h> |
| #include <zephyr/sys/winstream.h> |
| |
| /* This code may be used (e.g. for trace/logging) in very early |
| * environments where the standard library isn't available yet. |
| * Implement a simple memcpy() as an option. |
| */ |
| #ifndef CONFIG_WINSTREAM_STDLIB_MEMCOPY |
| # define MEMCPY(dst, src, n) \ |
| do { for (int i = 0; i < (n); i++) { (dst)[i] = (src)[i]; } } while (0) |
| #else |
| # include <string.h> |
| # define MEMCPY memcpy |
| #endif |
| |
| /* These are just compiler barriers now. Zephyr doesn't currently |
| * have a framework for hardware memory ordering, and all our targets |
| * (arm64 excepted) either promise firm ordering (x86) or are in-order |
| * cores (everything else). But these are marked for future |
| * enhancement. |
| */ |
| #define READ_BARRIER() __asm__ volatile("" ::: "memory") |
| #define WRITE_BARRIER() __asm__ volatile("") |
| |
| static uint32_t idx_mod(struct sys_winstream *ws, uint32_t idx) |
| { |
| return idx >= ws->len ? idx - ws->len : idx; |
| } |
| |
| /* Computes modular a - b, assuming a and b are in [0:len) */ |
| static uint32_t idx_sub(struct sys_winstream *ws, uint32_t a, uint32_t b) |
| { |
| return idx_mod(ws, a + (ws->len - b)); |
| } |
| |
| void sys_winstream_write(struct sys_winstream *ws, |
| const char *data, uint32_t len) |
| { |
| uint32_t len0 = len, suffix; |
| uint32_t start = ws->start, end = ws->end, seq = ws->seq; |
| |
| /* Overflow: if we're truncating then just reset the buffer. |
| * (Max bytes buffered is actually len-1 because start==end is |
| * reserved to mean "empty") |
| */ |
| if (len > ws->len - 1) { |
| start = end; |
| len = ws->len - 1; |
| } |
| |
| /* Make room in the buffer by advancing start first (note same |
| * len-1 from above) |
| */ |
| len = MIN(len, ws->len); |
| if (seq != 0) { |
| uint32_t avail = (ws->len - 1) - idx_sub(ws, end, start); |
| |
| if (len > avail) { |
| ws->start = idx_mod(ws, start + (len - avail)); |
| WRITE_BARRIER(); |
| } |
| } |
| |
| /* Had to truncate? */ |
| if (len < len0) { |
| ws->start = end; |
| data += len0 - len; |
| } |
| |
| suffix = MIN(len, ws->len - end); |
| MEMCPY(&ws->data[end], data, suffix); |
| if (len > suffix) { |
| MEMCPY(&ws->data[0], data + suffix, len - suffix); |
| } |
| |
| ws->end = idx_mod(ws, end + len); |
| ws->seq += len0; /* seq represents dropped bytes too! */ |
| WRITE_BARRIER(); |
| } |
| |
| uint32_t sys_winstream_read(struct sys_winstream *ws, |
| uint32_t *seq, char *buf, uint32_t buflen) |
| { |
| uint32_t seq0 = *seq, start, end, wseq, len, behind, copy, suffix; |
| |
| do { |
| start = ws->start; end = ws->end; wseq = ws->seq; |
| READ_BARRIER(); |
| |
| /* No change in buffer state or empty initial stream are easy */ |
| if (*seq == wseq || start == end) { |
| *seq = wseq; |
| return 0; |
| } |
| |
| /* Underflow: we're trying to read from a spot farther |
| * back than start. We dropped some bytes, so cheat |
| * and just drop them all to catch up. |
| */ |
| behind = wseq - *seq; |
| if (behind > idx_sub(ws, ws->end, ws->start)) { |
| *seq = wseq; |
| return 0; |
| } |
| |
| /* Copy data */ |
| copy = idx_sub(ws, ws->end, behind); |
| len = MIN(buflen, behind); |
| suffix = MIN(len, ws->len - copy); |
| MEMCPY(buf, &ws->data[copy], suffix); |
| if (len > suffix) { |
| MEMCPY(buf + suffix, &ws->data[0], len - suffix); |
| } |
| *seq = seq0 + len; |
| |
| /* Check vs. the state we initially read and repeat if |
| * needed. This can't loop forever even if the other |
| * side is stuck spamming writes: we'll run out of |
| * buffer space and exit via the underflow condition. |
| */ |
| READ_BARRIER(); |
| } while (start != ws->start || wseq != ws->seq); |
| |
| return len; |
| } |