Skip to main content

cbuf/
lib.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7#![no_std]
8
9use core::ptr::NonNull;
10use pin_init::PinInit;
11use zx_status::Status;
12
13/// Context returned along with a character read from the circular buffer.
14#[derive(Copy, Clone, Debug, PartialEq, Eq)]
15pub struct ReadContext {
16    /// The character that was read.
17    pub c: u8,
18    /// Whether the buffer transitioned away from being completely full as a result of this read.
19    pub transitioned_from_full: bool,
20}
21
22/// A thread-safe, lock-protected circular byte buffer.
23#[ksync::guarded]
24#[repr(C)]
25pub struct Cbuf {
26    #[guarded_by(lock)]
27    head: u32,
28
29    #[guarded_by(lock)]
30    tail: u32,
31
32    #[guarded_by(lock)]
33    len_pow2: u32,
34
35    #[guarded_by(lock)]
36    buf: Option<NonNull<u8>>,
37
38    #[pin]
39    event: ksync::KEvent,
40
41    #[mutex]
42    lock: ksync::KMutex<ksync::RawSpinlock>,
43}
44
45// SAFETY: Cbuf is safe to send across thread boundaries because all mutable access
46// to its fields is protected by the internal spinlock (`lock`).
47unsafe impl Send for Cbuf {}
48
49// SAFETY: Cbuf is safe to share across thread boundaries because all mutable access
50// to its fields is protected by the internal spinlock (`lock`).
51unsafe impl Sync for Cbuf {}
52
53impl Cbuf {
54    /// Returns a pin initializer for a new, uninitialized `Cbuf`.
55    pub fn init() -> impl PinInit<Self, core::convert::Infallible> {
56        pin_init::pin_init!(Self {
57            head: 0.into(),
58            tail: 0.into(),
59            len_pow2: 0.into(),
60            buf: None.into(),
61            event <- ksync::KEvent::init(false),
62            lock <- ksync::KMutex::init(),
63        })
64    }
65
66    /// Initializes the circular buffer with the specified size and backing memory region.
67    ///
68    /// # Safety
69    ///
70    /// The caller must ensure that `buf` points to a valid memory region of at least `len` bytes,
71    /// and that this memory region remains valid for the lifetime of `Cbuf`.
72    pub unsafe fn initialize(&self, len: usize, buf: *mut u8) -> Result<(), Status> {
73        if len == 0 || !len.is_power_of_two() {
74            return Err(Status::INVALID_ARGS);
75        }
76        let len_pow2 = len.trailing_zeros();
77
78        ksync::lock!(let mut guard = self.lock_lock());
79        let fields = guard.as_mut().fields_mut();
80        *fields.len_pow2 = len_pow2;
81        *fields.buf = NonNull::new(buf);
82        *fields.head = 0;
83        *fields.tail = 0;
84
85        Ok(())
86    }
87
88    /// Returns `true` if the buffer is currently full.
89    pub fn full(&self) -> bool {
90        ksync::lock!(let guard = self.lock_lock());
91        let fields = guard.fields();
92        is_full(*fields.head, *fields.tail, *fields.len_pow2)
93    }
94
95    /// Writes a single character to the buffer if space is available.
96    ///
97    /// Returns `1` if the write succeeded, or `0` if the buffer was full.
98    pub fn write_char(&self, c: u8) -> usize {
99        let wrote = {
100            ksync::lock!(let mut guard = self.lock_lock());
101            let fields = guard.fields_mut();
102            if is_full(*fields.head, *fields.tail, *fields.len_pow2) {
103                0
104            } else {
105                if let Some(buf) = fields.buf {
106                    // SAFETY: `initialize` caller guarantees that `buf` is valid.
107                    unsafe {
108                        buf.as_ptr().add(*fields.head as usize).write(c);
109                    }
110                    inc_pointer(fields.head, 1, *fields.len_pow2);
111                    1
112                } else {
113                    0
114                }
115            }
116        };
117
118        if wrote > 0 {
119            self.event.signal();
120        }
121        wrote
122    }
123
124    /// Reads a single character from the buffer, returning it along with the transition context.
125    ///
126    /// If `block` is true, this function blocks until a character is available to read.
127    /// If `block` is false, it returns `Err(Status::SHOULD_WAIT)` if no character is available.
128    pub fn read_char_with_context(&self, block: bool) -> Result<ReadContext, Status> {
129        loop {
130            {
131                ksync::lock!(let mut guard = self.lock_lock());
132                let fields = guard.fields_mut();
133                if *fields.tail != *fields.head {
134                    if let Some(buf) = fields.buf {
135                        // SAFETY: `initialize` caller guarantees that `buf` is valid.
136                        let c = unsafe { buf.as_ptr().add(*fields.tail as usize).read() };
137                        let transitioned_from_full =
138                            is_full(*fields.head, *fields.tail, *fields.len_pow2);
139
140                        inc_pointer(fields.tail, 1, *fields.len_pow2);
141                        if *fields.tail == *fields.head {
142                            self.event.unsignal();
143                        }
144                        return Ok(ReadContext { c, transitioned_from_full });
145                    }
146                }
147
148                // Because the signal state does not 100% match the buffer state, it is critical
149                // that the event is unsignaled when the buffer is found to be empty (not just when
150                // it *transitions* to empty).
151                self.event.unsignal();
152            }
153
154            if !block {
155                return Err(Status::SHOULD_WAIT);
156            }
157
158            self.event.wait()?;
159        }
160    }
161
162    /// Reads a single character from the buffer.
163    ///
164    /// If `block` is true, this function blocks until a character is available to read.
165    /// If `block` is false, it returns `Err(Status::SHOULD_WAIT)` if no character is available.
166    pub fn read_char(&self, block: bool) -> Result<u8, Status> {
167        self.read_char_with_context(block).map(|ctx| ctx.c)
168    }
169}
170
171#[inline]
172fn inc_pointer(ptr: &mut u32, inc: u32, len_pow2: u32) {
173    let mask = (1u32 << len_pow2) - 1;
174    *ptr = ptr.wrapping_add(inc) & mask;
175}
176
177#[inline]
178fn is_full(head: u32, tail: u32, len_pow2: u32) -> bool {
179    if len_pow2 == 0 {
180        return true;
181    }
182    let mask = (1u32 << len_pow2) - 1;
183    let consumed = head.wrapping_sub(tail) & mask;
184    let avail = (1u32 << len_pow2) - consumed - 1;
185    avail == 0
186}