1// Copyright 2016 The Fuchsia Authors
2// Copyright (c) 2008-2015 Travis Geiselbrecht
3//
4// Use of this source code is governed by a MIT-style
5// license that can be found in the LICENSE file or at
6// https://opensource.org/licenses/MIT
7
8#include <lib/cbuf.h>
9
10#include <assert.h>
11#include <debug.h>
12#include <fbl/algorithm.h>
13#include <kernel/auto_lock.h>
14#include <kernel/event.h>
15#include <kernel/spinlock.h>
16#include <pow2.h>
17#include <stdlib.h>
18#include <string.h>
19#include <trace.h>
20
21#define LOCAL_TRACE 0
22
23static inline uint inc_pointer(const cbuf_t* cbuf, uint ptr, uint inc) {
24    return modpow2(ptr + inc, cbuf->len_pow2);
25}
26
27void cbuf_initialize(cbuf_t* cbuf, size_t len) {
28    cbuf_initialize_etc(cbuf, len, malloc(len));
29}
30
31void cbuf_initialize_etc(cbuf_t* cbuf, size_t len, void* buf) {
32    DEBUG_ASSERT(cbuf);
33    DEBUG_ASSERT(len > 0);
34    DEBUG_ASSERT(fbl::is_pow2(len));
35
36    cbuf->head = 0;
37    cbuf->tail = 0;
38    cbuf->len_pow2 = log2_ulong_floor(len);
39    cbuf->buf = static_cast<char*>(buf);
40    event_init(&cbuf->event, false, 0);
41    spin_lock_init(&cbuf->lock);
42
43    LTRACEF("len %zu, len_pow2 %u\n", len, cbuf->len_pow2);
44}
45
46size_t cbuf_space_avail(const cbuf_t* cbuf) {
47    uint consumed = modpow2(cbuf->head - cbuf->tail, cbuf->len_pow2);
48    return valpow2(cbuf->len_pow2) - consumed - 1;
49}
50
51size_t cbuf_write_char(cbuf_t* cbuf, char c) {
52    DEBUG_ASSERT(cbuf);
53
54    size_t ret = 0;
55    {
56        AutoSpinLock guard(&cbuf->lock);
57
58        if (cbuf_space_avail(cbuf) > 0) {
59            cbuf->buf[cbuf->head] = c;
60
61            cbuf->head = inc_pointer(cbuf, cbuf->head, 1);
62            ret = 1;
63        }
64    }
65
66    if (ret > 0) {
67        event_signal(&cbuf->event, true);
68    }
69
70    return ret;
71}
72
73size_t cbuf_read_char(cbuf_t* cbuf, char* c, bool block) {
74    DEBUG_ASSERT(cbuf);
75    DEBUG_ASSERT(c);
76
77retry:
78    if (block) {
79        event_wait(&cbuf->event);
80    }
81
82    size_t ret = 0;
83    {
84        AutoSpinLock guard(&cbuf->lock);
85
86        // see if there's data available
87        if (cbuf->tail != cbuf->head) {
88
89            *c = cbuf->buf[cbuf->tail];
90            cbuf->tail = inc_pointer(cbuf, cbuf->tail, 1);
91
92            if (cbuf->tail == cbuf->head) {
93                // we've emptied the buffer, unsignal the event
94                event_unsignal(&cbuf->event);
95            }
96
97            ret = 1;
98        }
99    }
100
101    if (block && ret == 0) {
102        goto retry;
103    }
104
105    return ret;
106}
107