Skip to main content

cbuf/
lib.rs

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