From: Andreas Hindborg <a.hindborg@kernel.org>
To: "Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun.feng@gmail.com>,
"Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>
Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
Andreas Hindborg <a.hindborg@kernel.org>
Subject: [PATCH] rust: add a ring buffer implementation
Date: Sun, 15 Feb 2026 21:24:59 +0100 [thread overview]
Message-ID: <20260215-ringbuffer-v1-1-9b359758a1b6@kernel.org> (raw)
Add a fixed-capacity FIFO ring buffer. The implementation uses a circular
buffer with head and tail pointers, providing constant-time push and pop
operations.
The module includes a few tests covering basic operations, wrap-around
behavior, interleaved push/pop sequences, and edge cases such as
single-capacity buffers.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/kernel/lib.rs | 1 +
rust/kernel/ringbuffer.rs | 321 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 322 insertions(+)
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index f812cf1200428..d6555ccceb32f 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -133,6 +133,7 @@
pub mod rbtree;
pub mod regulator;
pub mod revocable;
+pub mod ringbuffer;
pub mod scatterlist;
pub mod security;
pub mod seq_file;
diff --git a/rust/kernel/ringbuffer.rs b/rust/kernel/ringbuffer.rs
new file mode 100644
index 0000000000000..9a66ebf1bb390
--- /dev/null
+++ b/rust/kernel/ringbuffer.rs
@@ -0,0 +1,321 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! A fixed-capacity FIFO ring buffer.
+//!
+//! This module provides [`RingBuffer`], a circular buffer implementation that
+//! supports efficient push and pop operations at opposite ends of the buffer.
+
+use kernel::prelude::*;
+
+/// A fixed-capacity FIFO ring buffer.
+///
+/// `RingBuffer` is a circular buffer that allows pushing elements to the head
+/// and popping elements from the tail in constant time. The buffer has a fixed
+/// capacity specified at construction time and will return an error if a push
+/// is attempted when full.
+///
+/// # Invariants
+///
+/// - `self.head` points at the next empty slot.
+/// - `self.tail` points at the last full slot, except if the buffer is empty.
+/// - The buffer is empty when `self.head == self.tail`.
+/// - The buffer will always have at least one empty slot, even when full.
+pub struct RingBuffer<T> {
+ nodes: KVec<Option<T>>,
+ size: usize,
+ head: usize,
+ tail: usize,
+}
+
+impl<T> RingBuffer<T> {
+ /// Creates a new `RingBuffer` with the specified capacity.
+ ///
+ /// The buffer will be able to hold exactly `capacity` elements. Memory is
+ /// allocated during construction.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if memory allocation fails.
+ pub fn new(capacity: usize) -> Result<Self> {
+ let mut this = Self {
+ nodes: KVec::with_capacity(capacity + 1, GFP_KERNEL)?,
+ size: capacity + 1,
+ head: 0,
+ tail: 0,
+ };
+
+ for _ in 0..this.size {
+ this.nodes.push_within_capacity(None)?;
+ }
+
+ Ok(this)
+ }
+
+ /// Returns `true` if the buffer is full.
+ ///
+ /// When the buffer is full, any call to [`push_head`] will return an error.
+ ///
+ /// [`push_head`]: Self::push_head
+ pub fn full(&self) -> bool {
+ (self.head + 1) % self.size == self.tail
+ }
+
+ fn empty(&self) -> bool {
+ self.head == self.tail
+ }
+
+ /// Returns the number of available slots in the buffer.
+ ///
+ /// This is the number of elements that can be pushed before the buffer
+ /// becomes full.
+ pub fn free_count(&self) -> usize {
+ (if self.head >= self.tail {
+ self.size - (self.head - self.tail)
+ } else {
+ (self.size - self.tail) + self.head
+ } - 1)
+ }
+
+ /// Pushes a value to the head of the buffer.
+ ///
+ /// # Errors
+ ///
+ /// Returns [`ENOSPC`] if the buffer is full.
+ pub fn push_head(&mut self, value: T) -> Result {
+ if self.full() {
+ return Err(ENOSPC);
+ }
+
+ self.nodes[self.head] = Some(value);
+ self.head = (self.head + 1) % self.size;
+
+ Ok(())
+ }
+
+ /// Pops and returns the value at the tail of the buffer.
+ ///
+ /// Returns `None` if the buffer is empty. Values are returned in FIFO
+ /// order, i.e., the oldest value is returned first.
+ pub fn pop_tail(&mut self) -> Option<T> {
+ if self.empty() {
+ return None;
+ }
+
+ let value = self.nodes[self.tail].take();
+ self.tail = (self.tail + 1) % self.size;
+ value
+ }
+}
+
+impl<T> Drop for RingBuffer<T> {
+ fn drop(&mut self) {
+ while !self.empty() {
+ drop(self.pop_tail().expect("Not empty"));
+ }
+ }
+}
+
+#[kunit_tests(rust_kernel_ringbuffer)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_new_buffer_is_empty() {
+ let buffer: RingBuffer<i32> = RingBuffer::new(5).expect("Failed to create buffer");
+ assert!(!buffer.full());
+ assert!(buffer.empty());
+ }
+
+ #[test]
+ fn test_new_buffer_has_correct_capacity() {
+ let capacity = 10;
+ let buffer: RingBuffer<i32> = RingBuffer::new(capacity).expect("Failed to create buffer");
+ assert_eq!(buffer.free_count(), capacity);
+ }
+
+ #[test]
+ fn test_push_single_element() {
+ let mut buffer: RingBuffer<i32> = RingBuffer::new(5).expect("Failed to create buffer");
+ assert!(buffer.push_head(42).is_ok());
+ assert!(!buffer.empty());
+ assert!(!buffer.full());
+ }
+
+ #[test]
+ fn test_push_and_pop_single_element() {
+ let mut buffer: RingBuffer<i32> = RingBuffer::new(5).expect("Failed to create buffer");
+ buffer.push_head(42).expect("Failed to push");
+ let value = buffer.pop_tail();
+ assert_eq!(value, Some(42));
+ assert!(buffer.empty());
+ }
+
+ #[test]
+ fn test_push_and_pop_multiple_elements() {
+ let mut buffer: RingBuffer<i32> = RingBuffer::new(5).expect("Failed to create buffer");
+
+ for i in 0..5 {
+ buffer.push_head(i).expect("Failed to push");
+ }
+
+ for i in 0..5 {
+ assert_eq!(buffer.pop_tail(), Some(i));
+ }
+
+ assert!(buffer.empty());
+ }
+
+ #[test]
+ fn test_pop_from_empty_buffer() {
+ let mut buffer: RingBuffer<i32> = RingBuffer::new(5).expect("Failed to create buffer");
+ assert_eq!(buffer.pop_tail(), None);
+ }
+
+ #[test]
+ fn test_buffer_full() {
+ let capacity = 3;
+ let mut buffer: RingBuffer<i32> =
+ RingBuffer::new(capacity).expect("Failed to create buffer");
+
+ for i in 0..capacity {
+ assert!(buffer.push_head(i as i32).is_ok());
+ }
+
+ assert!(buffer.full());
+ assert_eq!(buffer.free_count(), 0);
+ }
+
+ #[test]
+ fn test_push_to_full_buffer_fails() {
+ let capacity = 3;
+ let mut buffer: RingBuffer<i32> =
+ RingBuffer::new(capacity).expect("Failed to create buffer");
+
+ for i in 0..capacity {
+ buffer.push_head(i as i32).expect("Failed to push");
+ }
+
+ assert!(buffer.full());
+ assert!(buffer.push_head(999).is_err());
+ }
+
+ #[test]
+ fn test_free_count_decreases_on_push() {
+ let capacity = 5;
+ let mut buffer: RingBuffer<i32> =
+ RingBuffer::new(capacity).expect("Failed to create buffer");
+
+ assert_eq!(buffer.free_count(), capacity);
+
+ for i in 0..capacity {
+ buffer.push_head(i as i32).expect("Failed to push");
+ assert_eq!(buffer.free_count(), capacity - i - 1);
+ }
+ }
+
+ #[test]
+ fn test_free_count_increases_on_pop() {
+ let capacity = 5;
+ let mut buffer: RingBuffer<i32> =
+ RingBuffer::new(capacity).expect("Failed to create buffer");
+
+ for i in 0..capacity {
+ buffer.push_head(i as i32).expect("Failed to push");
+ }
+
+ assert_eq!(buffer.free_count(), 0);
+
+ for i in 1..=capacity {
+ buffer.pop_tail();
+ assert_eq!(buffer.free_count(), i);
+ }
+ }
+
+ #[test]
+ fn test_wrap_around_behavior() {
+ let capacity = 3;
+ let mut buffer: RingBuffer<i32> =
+ RingBuffer::new(capacity).expect("Failed to create buffer");
+
+ // Fill the buffer.
+ for i in 0..capacity {
+ buffer.push_head(i as i32).expect("Failed to push");
+ }
+
+ // Pop two elements.
+ assert_eq!(buffer.pop_tail(), Some(0));
+ assert_eq!(buffer.pop_tail(), Some(1));
+
+ // Push two more (should wrap around).
+ buffer.push_head(10).expect("Failed to push");
+ buffer.push_head(11).expect("Failed to push");
+
+ // Verify order.
+ assert_eq!(buffer.pop_tail(), Some(2));
+ assert_eq!(buffer.pop_tail(), Some(10));
+ assert_eq!(buffer.pop_tail(), Some(11));
+ assert!(buffer.empty());
+ }
+
+ #[test]
+ fn test_fifo_order() {
+ let mut buffer: RingBuffer<i32> = RingBuffer::new(10).expect("Failed to create buffer");
+
+ let values = [1, 2, 3, 4, 5];
+ for &value in &values {
+ buffer.push_head(value).expect("Failed to push");
+ }
+
+ for &expected in &values {
+ assert_eq!(buffer.pop_tail(), Some(expected));
+ }
+ }
+
+ #[test]
+ fn test_interleaved_push_pop() {
+ let mut buffer: RingBuffer<i32> = RingBuffer::new(5).expect("Failed to create buffer");
+
+ buffer.push_head(1).expect("Failed to push");
+ buffer.push_head(2).expect("Failed to push");
+ assert_eq!(buffer.pop_tail(), Some(1));
+
+ buffer.push_head(3).expect("Failed to push");
+ assert_eq!(buffer.pop_tail(), Some(2));
+ assert_eq!(buffer.pop_tail(), Some(3));
+
+ assert!(buffer.empty());
+ }
+
+ #[test]
+ fn test_buffer_state_after_fill_and_drain() {
+ let capacity = 4;
+ let mut buffer: RingBuffer<i32> =
+ RingBuffer::new(capacity).expect("Failed to create buffer");
+
+ // Fill and drain twice.
+ for _ in 0..2 {
+ for i in 0..capacity {
+ buffer.push_head(i as i32).expect("Failed to push");
+ }
+ assert!(buffer.full());
+
+ for _ in 0..capacity {
+ buffer.pop_tail();
+ }
+ assert!(buffer.empty());
+ }
+ }
+
+ #[test]
+ fn test_single_capacity_buffer() {
+ let mut buffer: RingBuffer<i32> = RingBuffer::new(1).expect("Failed to create buffer");
+
+ assert_eq!(buffer.free_count(), 1);
+ buffer.push_head(42).expect("Failed to push");
+ assert!(buffer.full());
+ assert_eq!(buffer.free_count(), 0);
+
+ assert_eq!(buffer.pop_tail(), Some(42));
+ assert!(buffer.empty());
+ }
+}
---
base-commit: 05f7e89ab9731565d8a62e3b5d1ec206485eeb0b
change-id: 20260215-ringbuffer-42455964aaf2
Best regards,
--
Andreas Hindborg <a.hindborg@kernel.org>
next reply other threads:[~2026-02-15 20:25 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-15 20:24 Andreas Hindborg [this message]
2026-02-16 4:35 ` [PATCH] rust: add a ring buffer implementation Daniel Almeida
2026-02-16 7:11 ` Andreas Hindborg
2026-02-16 11:44 ` Daniel Almeida
2026-02-16 12:25 ` Alice Ryhl
2026-02-16 12:43 ` Daniel Almeida
2026-02-16 13:27 ` Andreas Hindborg
2026-02-16 13:45 ` Daniel Almeida
2026-02-16 14:06 ` Danilo Krummrich
2026-02-16 14:21 ` Daniel Almeida
2026-02-16 14:39 ` Danilo Krummrich
2026-02-16 14:46 ` Daniel Almeida
2026-02-17 10:02 ` Andreas Hindborg
2026-02-17 14:26 ` Danilo Krummrich
2026-02-17 19:10 ` Andreas Hindborg
2026-02-17 19:25 ` Daniel Almeida
2026-02-18 8:29 ` Alice Ryhl
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260215-ringbuffer-v1-1-9b359758a1b6@kernel.org \
--to=a.hindborg@kernel.org \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=dakr@kernel.org \
--cc=gary@garyguo.net \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tmgross@umich.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.