From: Andreas Hindborg <a.hindborg@kernel.org>
To: "Miguel Ojeda" <ojeda@kernel.org>, "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>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>,
"Boqun Feng" <boqun@kernel.org>
Cc: Daniel Almeida <daniel.almeida@collabora.com>,
linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
Andreas Hindborg <a.hindborg@kernel.org>,
Lorenzo Stoakes <ljs@kernel.org>,
Vlastimil Babka <vbabka@kernel.org>,
"Liam R. Howlett" <liam@infradead.org>,
Uladzislau Rezki <urezki@gmail.com>,
Boqun Feng <boqun@kernel.org>
Subject: [PATCH v3] rust: add a ring buffer implementation
Date: Thu, 05 Nov 2026 11:37:50 +0100 [thread overview]
Message-ID: <20261105-ringbuffer-v3-1-b5d74efa8dc6@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>
---
Changes in v3:
- Lay out the imports vertically, as the coding guidelines require
(Onur).
- Mark the trivial accessors `#[inline]` (Onur).
- Rebase on v7.4-rc1.
- Link to v2: https://msgid.link/20260605-ringbuffer-v2-1-99df02489185@kernel.org
Changes in v2:
- Move the module into `rust/kernel/alloc/`, next to `Vec` (Alice).
- Drop the redundant `size` field; use `nodes.len()` instead (Alice).
- Return `ENOMEM` when `capacity + 1` overflows `usize` instead of
panicking inside `with_capacity` (Alice).
- Simplify the `head < tail` branch of `free_count` to `tail - head`
(Alice).
- Remove the manual `Drop` impl; the backing `KVec` already drops the
stored slots (Alice).
- Make `RingBuffer` generic over the backing allocator. Add
`KRingBuffer`, `VRingBuffer` and `KVRingBuffer` aliases and re-export
them from `kernel::alloc`. The constructor now takes an additional
`Flags` argument (Daniel, Danilo).
- Link to v1: https://msgid.link/20260215-ringbuffer-v1-1-9b359758a1b6@kernel.org
To: Danilo Krummrich <dakr@kernel.org>
To: Lorenzo Stoakes <ljs@kernel.org>
To: Vlastimil Babka <vbabka@kernel.org>
To: "Liam R. Howlett" <liam@infradead.org>
To: Uladzislau Rezki <urezki@gmail.com>
To: Miguel Ojeda <ojeda@kernel.org>
To: Boqun Feng <boqun@kernel.org>
To: Gary Guo <gary@garyguo.net>
To: Björn Roy Baron <bjorn3_gh@protonmail.com>
To: Benno Lossin <lossin@kernel.org>
To: Andreas Hindborg <a.hindborg@kernel.org>
To: Alice Ryhl <aliceryhl@google.com>
To: Trevor Gross <tmgross@umich.edu>
Cc: linux-kernel@vger.kernel.org
Cc: rust-for-linux@vger.kernel.org
---
rust/kernel/alloc.rs | 6 +
rust/kernel/alloc/ringbuffer.rs | 349 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 355 insertions(+)
diff --git a/rust/kernel/alloc.rs b/rust/kernel/alloc.rs
index 68d80f3fa6c8..c27eda267317 100644
--- a/rust/kernel/alloc.rs
+++ b/rust/kernel/alloc.rs
@@ -6,6 +6,7 @@
pub mod kbox;
pub mod kvec;
pub mod layout;
+pub mod ringbuffer;
pub use self::kbox::Box;
pub use self::kbox::KBox;
@@ -18,6 +19,11 @@
pub use self::kvec::VVec;
pub use self::kvec::Vec;
+pub use self::ringbuffer::KRingBuffer;
+pub use self::ringbuffer::KVRingBuffer;
+pub use self::ringbuffer::RingBuffer;
+pub use self::ringbuffer::VRingBuffer;
+
/// Indicates an allocation error.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct AllocError;
diff --git a/rust/kernel/alloc/ringbuffer.rs b/rust/kernel/alloc/ringbuffer.rs
new file mode 100644
index 000000000000..abe0ae96f071
--- /dev/null
+++ b/rust/kernel/alloc/ringbuffer.rs
@@ -0,0 +1,349 @@
+// 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 super::{
+ allocator::{
+ KVmalloc,
+ Kmalloc,
+ Vmalloc, //
+ },
+ Allocator,
+ Flags,
+ Vec, //
+};
+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.
+///
+/// The backing storage is provided by the allocator `A`. Type aliases are
+/// provided for the common choices: [`KRingBuffer`], [`VRingBuffer`] and
+/// [`KVRingBuffer`].
+///
+/// # 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, A: Allocator> {
+ nodes: Vec<Option<T>, A>,
+ head: usize,
+ tail: usize,
+}
+
+/// A [`RingBuffer`] backed by [`Kmalloc`].
+pub type KRingBuffer<T> = RingBuffer<T, Kmalloc>;
+
+/// A [`RingBuffer`] backed by [`Vmalloc`].
+pub type VRingBuffer<T> = RingBuffer<T, Vmalloc>;
+
+/// A [`RingBuffer`] backed by [`KVmalloc`].
+pub type KVRingBuffer<T> = RingBuffer<T, KVmalloc>;
+
+impl<T, A: Allocator> RingBuffer<T, A> {
+ /// Creates a new `RingBuffer` with the specified capacity.
+ ///
+ /// The buffer will be able to hold exactly `capacity` elements. Memory is
+ /// allocated during construction using `flags`.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if memory allocation fails.
+ pub fn new(capacity: usize, flags: Flags) -> Result<Self> {
+ let slots = capacity.checked_add(1).ok_or(ENOMEM)?;
+ let mut this = Self {
+ nodes: Vec::with_capacity(slots, flags)?,
+ head: 0,
+ tail: 0,
+ };
+
+ for _ in 0..this.nodes.capacity() {
+ 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
+ #[inline]
+ pub fn full(&self) -> bool {
+ (self.head + 1) % self.nodes.len() == self.tail
+ }
+
+ #[inline]
+ 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.
+ #[inline]
+ pub fn free_count(&self) -> usize {
+ (if self.head >= self.tail {
+ self.nodes.len() - (self.head - self.tail)
+ } else {
+ self.tail - self.head
+ } - 1)
+ }
+
+ /// Pushes a value to the head of the buffer.
+ ///
+ /// # Errors
+ ///
+ /// Returns [`ENOSPC`] if the buffer is full.
+ #[inline]
+ 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.nodes.len();
+
+ 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.
+ #[inline]
+ 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.nodes.len();
+ value
+ }
+}
+
+#[kunit_tests(rust_kernel_ringbuffer)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_new_buffer_is_empty() {
+ let buffer: KRingBuffer<i32> =
+ KRingBuffer::new(5, GFP_KERNEL).expect("Failed to create buffer");
+ assert!(!buffer.full());
+ assert!(buffer.empty());
+ }
+
+ #[test]
+ fn test_new_buffer_has_correct_capacity() {
+ let capacity = 10;
+ let buffer: KRingBuffer<i32> =
+ KRingBuffer::new(capacity, GFP_KERNEL).expect("Failed to create buffer");
+ assert_eq!(buffer.free_count(), capacity);
+ }
+
+ #[test]
+ fn test_push_single_element() {
+ let mut buffer: KRingBuffer<i32> =
+ KRingBuffer::new(5, GFP_KERNEL).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: KRingBuffer<i32> =
+ KRingBuffer::new(5, GFP_KERNEL).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: KRingBuffer<i32> =
+ KRingBuffer::new(5, GFP_KERNEL).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: KRingBuffer<i32> =
+ KRingBuffer::new(5, GFP_KERNEL).expect("Failed to create buffer");
+ assert_eq!(buffer.pop_tail(), None);
+ }
+
+ #[test]
+ fn test_buffer_full() {
+ let capacity = 3;
+ let mut buffer: KRingBuffer<i32> =
+ KRingBuffer::new(capacity, GFP_KERNEL).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: KRingBuffer<i32> =
+ KRingBuffer::new(capacity, GFP_KERNEL).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: KRingBuffer<i32> =
+ KRingBuffer::new(capacity, GFP_KERNEL).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: KRingBuffer<i32> =
+ KRingBuffer::new(capacity, GFP_KERNEL).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: KRingBuffer<i32> =
+ KRingBuffer::new(capacity, GFP_KERNEL).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: KRingBuffer<i32> =
+ KRingBuffer::new(10, GFP_KERNEL).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: KRingBuffer<i32> =
+ KRingBuffer::new(5, GFP_KERNEL).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: KRingBuffer<i32> =
+ KRingBuffer::new(capacity, GFP_KERNEL).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: KRingBuffer<i32> =
+ KRingBuffer::new(1, GFP_KERNEL).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: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20260215-ringbuffer-42455964aaf2
Best regards,
--
Andreas Hindborg <a.hindborg@kernel.org>
next reply other threads:[~2026-11-05 10:38 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-05 10:37 Andreas Hindborg [this message]
2026-11-10 13:08 ` [PATCH v3] rust: add a ring buffer implementation Miguel Ojeda
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=20261105-ringbuffer-v3-1-b5d74efa8dc6@kernel.org \
--to=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=gary@garyguo.net \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=ljs@kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=urezki@gmail.com \
--cc=vbabka@kernel.org \
--cc=work@onurozkan.dev \
/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.