[PATCH v4 04/13] gpu: nova-core: Add a slice-buffer (sbuffer) datastructure

Alistair Popple posted 13 patches 4 months ago
There is a newer version of this series
[PATCH v4 04/13] gpu: nova-core: Add a slice-buffer (sbuffer) datastructure
Posted by Alistair Popple 4 months ago
From: Joel Fernandes <joelagnelf@nvidia.com>

A data structure that can be used to write across multiple slices which
may be out of order in memory. This lets SBuffer user correctly and
safely write out of memory order, without error-prone tracking of
pointers/offsets.

 let mut buf1 = [0u8; 3];
 let mut buf2 = [0u8; 5];
 let mut sbuffer = SBuffer::new([&mut buf1[..], &mut buf2[..]]);

 let data = b"hello";
 let result = sbuffer.write(data);

An internal conversion of gsp.rs to use this resulted in a nice -ve delta:
gsp.rs: 37 insertions(+), 99 deletions(-)

Co-developed-by: Alistair Popple <apopple@nvidia.com>
Signed-off-by: Alistair Popple <apopple@nvidia.com>
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>

---

Changes for v3:
 - Addressed minor review comment from Lyude
---
 drivers/gpu/nova-core/nova_core.rs |   1 +
 drivers/gpu/nova-core/sbuffer.rs   | 191 +++++++++++++++++++++++++++++
 2 files changed, 192 insertions(+)
 create mode 100644 drivers/gpu/nova-core/sbuffer.rs

diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
index fffcaee2249f..a6feeba6254c 100644
--- a/drivers/gpu/nova-core/nova_core.rs
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -11,6 +11,7 @@
 mod gpu;
 mod gsp;
 mod regs;
+mod sbuffer;
 mod util;
 mod vbios;
 
diff --git a/drivers/gpu/nova-core/sbuffer.rs b/drivers/gpu/nova-core/sbuffer.rs
new file mode 100644
index 000000000000..e82f9d97ad21
--- /dev/null
+++ b/drivers/gpu/nova-core/sbuffer.rs
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use core::ops::Deref;
+
+use kernel::alloc::KVec;
+use kernel::error::code::*;
+use kernel::prelude::*;
+
+/// A buffer abstraction for discontiguous byte slices.
+///
+/// This allows you to treat multiple non-contiguous `&mut [u8]` slices
+/// as a single stream-like read/write buffer.
+///
+/// Example:
+///
+/// let mut buf1 = [0u8; 3];
+/// let mut buf2 = [0u8; 5];
+/// let mut sbuffer = SWriteBuffer::new([&buf1, &buf2]);
+///
+/// let data = b"hellowo";
+/// let result = sbuffer.write_all(0, data);
+///
+/// A sliding window of slices to proceed.
+///
+/// Both read and write buffers are implemented in terms of operating on slices of a requested
+/// size. This base class implements logic that can be shared between the two to support that.
+///
+/// `S` is a slice type, `I` is an iterator yielding `S`.
+pub(crate) struct SBuffer<I: Iterator> {
+    /// `Some` if we are not at the end of the data yet.
+    cur_slice: Option<I::Item>,
+    /// All the slices remaining after `cur_slice`.
+    slices: I,
+}
+
+impl<'a, I> SBuffer<I>
+where
+    I: Iterator,
+{
+    #[expect(unused)]
+    pub(crate) fn new_reader(slices: impl IntoIterator<IntoIter = I>) -> Self
+    where
+        I: Iterator<Item = &'a [u8]>,
+    {
+        Self::new(slices)
+    }
+
+    #[expect(unused)]
+    pub(crate) fn new_writer(slices: impl IntoIterator<IntoIter = I>) -> Self
+    where
+        I: Iterator<Item = &'a mut [u8]>,
+    {
+        Self::new(slices)
+    }
+
+    fn new(slices: impl IntoIterator<IntoIter = I>) -> Self
+    where
+        I::Item: Deref<Target = [u8]>,
+    {
+        let mut slices = slices.into_iter();
+
+        Self {
+            // Skip empty slices to avoid trouble down the road.
+            cur_slice: slices.find(|s| !s.deref().is_empty()),
+            slices,
+        }
+    }
+
+    fn get_slice_internal(
+        &mut self,
+        len: usize,
+        mut f: impl FnMut(I::Item, usize) -> (I::Item, I::Item),
+    ) -> Option<I::Item>
+    where
+        I::Item: Deref<Target = [u8]>,
+    {
+        match self.cur_slice.take() {
+            None => None,
+            Some(cur_slice) => {
+                if len >= cur_slice.len() {
+                    // Caller requested more data than is in the current slice, return it entirely
+                    // and prepare the following slice for being used. Skip empty slices to avoid
+                    // trouble.
+                    self.cur_slice = self.slices.find(|s| !s.is_empty());
+
+                    Some(cur_slice)
+                } else {
+                    // The current slice can satisfy the request, split it and return a slice of
+                    // the requested size.
+                    let (ret, next) = f(cur_slice, len);
+                    self.cur_slice = Some(next);
+
+                    Some(ret)
+                }
+            }
+        }
+    }
+}
+
+/// Provides a way to get non-mutable slices of data to read from.
+impl<'a, I> SBuffer<I>
+where
+    I: Iterator<Item = &'a [u8]>,
+{
+    /// Returns a slice of at most `len` bytes, or `None` if we are at the end of the data.
+    ///
+    /// If a slice shorter than `len` bytes has been returned, the caller can call this method
+    /// again until it returns `None` to try and obtain the remainder of the data.
+    fn get_slice(&mut self, len: usize) -> Option<&'a [u8]> {
+        self.get_slice_internal(len, |s, pos| s.split_at(pos))
+    }
+
+    /// Ideally we would implement `Read`, but it is not available in `core`.
+    /// So mimic `std::io::Read::read_exact`.
+    #[expect(unused)]
+    pub(crate) fn read_exact(&mut self, mut dst: &mut [u8]) -> Result {
+        while !dst.is_empty() {
+            match self.get_slice(dst.len()) {
+                None => return Err(ETOOSMALL),
+                Some(src) => {
+                    let dst_slice;
+                    (dst_slice, dst) = dst.split_at_mut(src.len());
+                    dst_slice.copy_from_slice(src);
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Read all the remaining data into a `KVec`.
+    ///
+    /// `self` will be empty after this operation.
+    #[expect(unused)]
+    pub(crate) fn read_into_kvec(&mut self, flags: kernel::alloc::Flags) -> Result<KVec<u8>> {
+        let mut buf = KVec::<u8>::new();
+
+        if let Some(slice) = core::mem::take(&mut self.cur_slice) {
+            buf.extend_from_slice(slice, flags)?;
+        }
+        for slice in &mut self.slices {
+            buf.extend_from_slice(slice, flags)?;
+        }
+
+        Ok(buf)
+    }
+}
+
+/// Provides a way to get mutable slices of data to write into.
+impl<'a, I> SBuffer<I>
+where
+    I: Iterator<Item = &'a mut [u8]>,
+{
+    /// Returns a mutable slice of at most `len` bytes, or `None` if we are at the end of the data.
+    ///
+    /// If a slice shorter than `len` bytes has been returned, the caller can call this method
+    /// again until it returns `None` to try and obtain the remainder of the data.
+    fn get_slice_mut(&mut self, len: usize) -> Option<&'a mut [u8]> {
+        self.get_slice_internal(len, |s, pos| s.split_at_mut(pos))
+    }
+
+    /// Ideally we would implement `Write`, but it is not available in `core`.
+    /// So mimic `std::io::Write::write_all`.
+    #[expect(unused)]
+    pub(crate) fn write_all(&mut self, mut src: &[u8]) -> Result {
+        while !src.is_empty() {
+            match self.get_slice_mut(src.len()) {
+                None => return Err(ETOOSMALL),
+                Some(dst) => {
+                    let src_slice;
+                    (src_slice, src) = src.split_at(dst.len());
+                    dst.copy_from_slice(src_slice);
+                }
+            }
+        }
+
+        Ok(())
+    }
+}
+
+impl<'a, I> Iterator for SBuffer<I>
+where
+    I: Iterator<Item = &'a [u8]>,
+{
+    type Item = u8;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        // Returned slices are guaranteed to not be empty so we can safely index the first entry.
+        self.get_slice(1).map(|s| s[0])
+    }
+}
-- 
2.50.1
Re: [PATCH v4 04/13] gpu: nova-core: Add a slice-buffer (sbuffer) datastructure
Posted by Miguel Ojeda 4 months ago
On Wed, Oct 8, 2025 at 2:13 AM Alistair Popple <apopple@nvidia.com> wrote:
>
> +/// Example:
> +///
> +/// let mut buf1 = [0u8; 3];
> +/// let mut buf2 = [0u8; 5];
> +/// let mut sbuffer = SWriteBuffer::new([&buf1, &buf2]);
> +///
> +/// let data = b"hellowo";
> +/// let result = sbuffer.write_all(0, data);
> +///
> +/// A sliding window of slices to proceed.

Please write documentation in the expected format, i.e. using a code
block and a header:

    https://docs.kernel.org/rust/coding-guidelines.html#code-documentation

Eventually, this documentation will get rendered just like the
`kernel` crate (and the examples building/running as tests etc.),
which requires following the expected conventions.

By the way, is the sentence "A sliding window of slices to proceed." cut?

Thanks!

Cheers,
Miguel
Re: [PATCH v4 04/13] gpu: nova-core: Add a slice-buffer (sbuffer) datastructure
Posted by Joel Fernandes 4 months ago

On 10/8/2025 12:56 PM, Miguel Ojeda wrote:
> On Wed, Oct 8, 2025 at 2:13 AM Alistair Popple <apopple@nvidia.com> wrote:
>>
>> +/// Example:
>> +///
>> +/// let mut buf1 = [0u8; 3];
>> +/// let mut buf2 = [0u8; 5];
>> +/// let mut sbuffer = SWriteBuffer::new([&buf1, &buf2]);
>> +///
>> +/// let data = b"hellowo";
>> +/// let result = sbuffer.write_all(0, data);
>> +///
>> +/// A sliding window of slices to proceed.
> 
> Please write documentation in the expected format, i.e. using a code
> block and a header:
> 
>     https://docs.kernel.org/rust/coding-guidelines.html#code-documentation

Sorry about that. I will fix this up for the next series posting.

> Eventually, this documentation will get rendered just like the
> `kernel` crate (and the examples building/running as tests etc.),
> which requires following the expected conventions.
> 
> By the way, is the sentence "A sliding window of slices to proceed." cut?
> 
It is a typo, should be "A sliding window of slices to process". Will fix :)

thanks,

 - Joel


Re: [PATCH v4 04/13] gpu: nova-core: Add a slice-buffer (sbuffer) datastructure
Posted by Danilo Krummrich 4 months ago
On Wed Oct 8, 2025 at 2:12 AM CEST, Alistair Popple wrote:
> diff --git a/drivers/gpu/nova-core/sbuffer.rs b/drivers/gpu/nova-core/sbuffer.rs
> new file mode 100644
> index 000000000000..e82f9d97ad21
> --- /dev/null
> +++ b/drivers/gpu/nova-core/sbuffer.rs
> @@ -0,0 +1,191 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +use core::ops::Deref;
> +
> +use kernel::alloc::KVec;
> +use kernel::error::code::*;
> +use kernel::prelude::*;
> +
> +/// A buffer abstraction for discontiguous byte slices.
> +///
> +/// This allows you to treat multiple non-contiguous `&mut [u8]` slices
> +/// as a single stream-like read/write buffer.
> +///
> +/// Example:
> +///
> +/// let mut buf1 = [0u8; 3];
> +/// let mut buf2 = [0u8; 5];
> +/// let mut sbuffer = SWriteBuffer::new([&buf1, &buf2]);
> +///
> +/// let data = b"hellowo";

Not that it matters, but "hellowo"? :)

> +/// let result = sbuffer.write_all(0, data);
> +///
> +/// A sliding window of slices to proceed.
> +///
> +/// Both read and write buffers are implemented in terms of operating on slices of a requested
> +/// size. This base class implements logic that can be shared between the two to support that.
> +///
> +/// `S` is a slice type, `I` is an iterator yielding `S`.
> +pub(crate) struct SBuffer<I: Iterator> {
> +    /// `Some` if we are not at the end of the data yet.
> +    cur_slice: Option<I::Item>,
> +    /// All the slices remaining after `cur_slice`.
> +    slices: I,
> +}

Does it make sense to split SBuffer into itself and a separate SBufferIter that
keeps a reference to the SBuffer? If not, I'd rename it to SBufferIter to make
it obvious to the user that it is an iterator type.

> +impl<'a, I> SBuffer<I>
> +where
> +    I: Iterator,
> +{
> +    #[expect(unused)]
> +    pub(crate) fn new_reader(slices: impl IntoIterator<IntoIter = I>) -> Self
> +    where
> +        I: Iterator<Item = &'a [u8]>,
> +    {
> +        Self::new(slices)
> +    }
> +
> +    #[expect(unused)]
> +    pub(crate) fn new_writer(slices: impl IntoIterator<IntoIter = I>) -> Self
> +    where
> +        I: Iterator<Item = &'a mut [u8]>,
> +    {
> +        Self::new(slices)
> +    }

Please add some documentation for the constructors.

> +    fn new(slices: impl IntoIterator<IntoIter = I>) -> Self
> +    where
> +        I::Item: Deref<Target = [u8]>,
> +    {
> +        let mut slices = slices.into_iter();
> +
> +        Self {
> +            // Skip empty slices to avoid trouble down the road.
> +            cur_slice: slices.find(|s| !s.deref().is_empty()),
> +            slices,
> +        }
> +    }
> +
> +    fn get_slice_internal(
> +        &mut self,
> +        len: usize,
> +        mut f: impl FnMut(I::Item, usize) -> (I::Item, I::Item),
> +    ) -> Option<I::Item>
> +    where
> +        I::Item: Deref<Target = [u8]>,
> +    {
> +        match self.cur_slice.take() {
> +            None => None,
> +            Some(cur_slice) => {
> +                if len >= cur_slice.len() {
> +                    // Caller requested more data than is in the current slice, return it entirely
> +                    // and prepare the following slice for being used. Skip empty slices to avoid
> +                    // trouble.
> +                    self.cur_slice = self.slices.find(|s| !s.is_empty());
> +
> +                    Some(cur_slice)
> +                } else {
> +                    // The current slice can satisfy the request, split it and return a slice of
> +                    // the requested size.
> +                    let (ret, next) = f(cur_slice, len);
> +                    self.cur_slice = Some(next);
> +
> +                    Some(ret)
> +                }
> +            }
> +        }
> +    }
> +}
> +
> +/// Provides a way to get non-mutable slices of data to read from.
> +impl<'a, I> SBuffer<I>
> +where
> +    I: Iterator<Item = &'a [u8]>,
> +{
> +    /// Returns a slice of at most `len` bytes, or `None` if we are at the end of the data.
> +    ///
> +    /// If a slice shorter than `len` bytes has been returned, the caller can call this method
> +    /// again until it returns `None` to try and obtain the remainder of the data.
> +    fn get_slice(&mut self, len: usize) -> Option<&'a [u8]> {
> +        self.get_slice_internal(len, |s, pos| s.split_at(pos))
> +    }
> +
> +    /// Ideally we would implement `Read`, but it is not available in `core`.
> +    /// So mimic `std::io::Read::read_exact`.
> +    #[expect(unused)]
> +    pub(crate) fn read_exact(&mut self, mut dst: &mut [u8]) -> Result {
> +        while !dst.is_empty() {
> +            match self.get_slice(dst.len()) {
> +                None => return Err(ETOOSMALL),

ETOOSMALL is an NFS error code (it should also never be exposed to userspace). I
suggest to implement a custom error type instead and make it resolve to ENOSPC
or probably just EINVAL instead.

> +                Some(src) => {
> +                    let dst_slice;
> +                    (dst_slice, dst) = dst.split_at_mut(src.len());
> +                    dst_slice.copy_from_slice(src);
> +                }
> +            }
> +        }
> +
> +        Ok(())
> +    }
> +
> +    /// Read all the remaining data into a `KVec`.
> +    ///
> +    /// `self` will be empty after this operation.
> +    #[expect(unused)]
> +    pub(crate) fn read_into_kvec(&mut self, flags: kernel::alloc::Flags) -> Result<KVec<u8>> {
> +        let mut buf = KVec::<u8>::new();
> +
> +        if let Some(slice) = core::mem::take(&mut self.cur_slice) {
> +            buf.extend_from_slice(slice, flags)?;
> +        }
> +        for slice in &mut self.slices {
> +            buf.extend_from_slice(slice, flags)?;
> +        }
> +
> +        Ok(buf)
> +    }
> +}
> +
> +/// Provides a way to get mutable slices of data to write into.
> +impl<'a, I> SBuffer<I>
> +where
> +    I: Iterator<Item = &'a mut [u8]>,
> +{
> +    /// Returns a mutable slice of at most `len` bytes, or `None` if we are at the end of the data.
> +    ///
> +    /// If a slice shorter than `len` bytes has been returned, the caller can call this method
> +    /// again until it returns `None` to try and obtain the remainder of the data.
> +    fn get_slice_mut(&mut self, len: usize) -> Option<&'a mut [u8]> {
> +        self.get_slice_internal(len, |s, pos| s.split_at_mut(pos))
> +    }
> +
> +    /// Ideally we would implement `Write`, but it is not available in `core`.
> +    /// So mimic `std::io::Write::write_all`.
> +    #[expect(unused)]
> +    pub(crate) fn write_all(&mut self, mut src: &[u8]) -> Result {
> +        while !src.is_empty() {
> +            match self.get_slice_mut(src.len()) {
> +                None => return Err(ETOOSMALL),
> +                Some(dst) => {
> +                    let src_slice;
> +                    (src_slice, src) = src.split_at(dst.len());
> +                    dst.copy_from_slice(src_slice);
> +                }
> +            }
> +        }
> +
> +        Ok(())
> +    }
> +}
> +
> +impl<'a, I> Iterator for SBuffer<I>
> +where
> +    I: Iterator<Item = &'a [u8]>,
> +{
> +    type Item = u8;
> +
> +    fn next(&mut self) -> Option<Self::Item> {
> +        // Returned slices are guaranteed to not be empty so we can safely index the first entry.
> +        self.get_slice(1).map(|s| s[0])
> +    }
> +}
> -- 
> 2.50.1
Re: [PATCH v4 04/13] gpu: nova-core: Add a slice-buffer (sbuffer) datastructure
Posted by Alistair Popple 4 months ago
On 2025-10-09 at 03:41 +1100, Danilo Krummrich <dakr@kernel.org> wrote...
> On Wed Oct 8, 2025 at 2:12 AM CEST, Alistair Popple wrote:
> > diff --git a/drivers/gpu/nova-core/sbuffer.rs b/drivers/gpu/nova-core/sbuffer.rs
> > new file mode 100644
> > index 000000000000..e82f9d97ad21
> > --- /dev/null
> > +++ b/drivers/gpu/nova-core/sbuffer.rs
> > @@ -0,0 +1,191 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +use core::ops::Deref;
> > +
> > +use kernel::alloc::KVec;
> > +use kernel::error::code::*;
> > +use kernel::prelude::*;
> > +
> > +/// A buffer abstraction for discontiguous byte slices.
> > +///
> > +/// This allows you to treat multiple non-contiguous `&mut [u8]` slices
> > +/// as a single stream-like read/write buffer.
> > +///
> > +/// Example:
> > +///
> > +/// let mut buf1 = [0u8; 3];
> > +/// let mut buf2 = [0u8; 5];
> > +/// let mut sbuffer = SWriteBuffer::new([&buf1, &buf2]);
> > +///
> > +/// let data = b"hellowo";
> 
> Not that it matters, but "hellowo"? :)

Ugh. I fixed that in the commit message but not here!

> > +/// let result = sbuffer.write_all(0, data);
> > +///
> > +/// A sliding window of slices to proceed.
> > +///
> > +/// Both read and write buffers are implemented in terms of operating on slices of a requested
> > +/// size. This base class implements logic that can be shared between the two to support that.
> > +///
> > +/// `S` is a slice type, `I` is an iterator yielding `S`.
> > +pub(crate) struct SBuffer<I: Iterator> {
> > +    /// `Some` if we are not at the end of the data yet.
> > +    cur_slice: Option<I::Item>,
> > +    /// All the slices remaining after `cur_slice`.
> > +    slices: I,
> > +}
> 
> Does it make sense to split SBuffer into itself and a separate SBufferIter that
> keeps a reference to the SBuffer? If not, I'd rename it to SBufferIter to make
> it obvious to the user that it is an iterator type.

We had that originally before this was sent to the list for review but there
wasn't a use for it so will rename it. If memory serves me Alex removed the
separate SBuffer so maybe he can comment further why we removed it.

> > +impl<'a, I> SBuffer<I>
> > +where
> > +    I: Iterator,
> > +{
> > +    #[expect(unused)]
> > +    pub(crate) fn new_reader(slices: impl IntoIterator<IntoIter = I>) -> Self
> > +    where
> > +        I: Iterator<Item = &'a [u8]>,
> > +    {
> > +        Self::new(slices)
> > +    }
> > +
> > +    #[expect(unused)]
> > +    pub(crate) fn new_writer(slices: impl IntoIterator<IntoIter = I>) -> Self
> > +    where
> > +        I: Iterator<Item = &'a mut [u8]>,
> > +    {
> > +        Self::new(slices)
> > +    }
> 
> Please add some documentation for the constructors.

Ok.

> > +    fn new(slices: impl IntoIterator<IntoIter = I>) -> Self
> > +    where
> > +        I::Item: Deref<Target = [u8]>,
> > +    {
> > +        let mut slices = slices.into_iter();
> > +
> > +        Self {
> > +            // Skip empty slices to avoid trouble down the road.
> > +            cur_slice: slices.find(|s| !s.deref().is_empty()),
> > +            slices,
> > +        }
> > +    }
> > +
> > +    fn get_slice_internal(
> > +        &mut self,
> > +        len: usize,
> > +        mut f: impl FnMut(I::Item, usize) -> (I::Item, I::Item),
> > +    ) -> Option<I::Item>
> > +    where
> > +        I::Item: Deref<Target = [u8]>,
> > +    {
> > +        match self.cur_slice.take() {
> > +            None => None,
> > +            Some(cur_slice) => {
> > +                if len >= cur_slice.len() {
> > +                    // Caller requested more data than is in the current slice, return it entirely
> > +                    // and prepare the following slice for being used. Skip empty slices to avoid
> > +                    // trouble.
> > +                    self.cur_slice = self.slices.find(|s| !s.is_empty());
> > +
> > +                    Some(cur_slice)
> > +                } else {
> > +                    // The current slice can satisfy the request, split it and return a slice of
> > +                    // the requested size.
> > +                    let (ret, next) = f(cur_slice, len);
> > +                    self.cur_slice = Some(next);
> > +
> > +                    Some(ret)
> > +                }
> > +            }
> > +        }
> > +    }
> > +}
> > +
> > +/// Provides a way to get non-mutable slices of data to read from.
> > +impl<'a, I> SBuffer<I>
> > +where
> > +    I: Iterator<Item = &'a [u8]>,
> > +{
> > +    /// Returns a slice of at most `len` bytes, or `None` if we are at the end of the data.
> > +    ///
> > +    /// If a slice shorter than `len` bytes has been returned, the caller can call this method
> > +    /// again until it returns `None` to try and obtain the remainder of the data.
> > +    fn get_slice(&mut self, len: usize) -> Option<&'a [u8]> {
> > +        self.get_slice_internal(len, |s, pos| s.split_at(pos))
> > +    }
> > +
> > +    /// Ideally we would implement `Read`, but it is not available in `core`.
> > +    /// So mimic `std::io::Read::read_exact`.
> > +    #[expect(unused)]
> > +    pub(crate) fn read_exact(&mut self, mut dst: &mut [u8]) -> Result {
> > +        while !dst.is_empty() {
> > +            match self.get_slice(dst.len()) {
> > +                None => return Err(ETOOSMALL),
> 
> ETOOSMALL is an NFS error code (it should also never be exposed to userspace). I
> suggest to implement a custom error type instead and make it resolve to ENOSPC
> or probably just EINVAL instead.

Will do.

> > +                Some(src) => {
> > +                    let dst_slice;
> > +                    (dst_slice, dst) = dst.split_at_mut(src.len());
> > +                    dst_slice.copy_from_slice(src);
> > +                }
> > +            }
> > +        }
> > +
> > +        Ok(())
> > +    }
> > +
> > +    /// Read all the remaining data into a `KVec`.
> > +    ///
> > +    /// `self` will be empty after this operation.
> > +    #[expect(unused)]
> > +    pub(crate) fn read_into_kvec(&mut self, flags: kernel::alloc::Flags) -> Result<KVec<u8>> {
> > +        let mut buf = KVec::<u8>::new();
> > +
> > +        if let Some(slice) = core::mem::take(&mut self.cur_slice) {
> > +            buf.extend_from_slice(slice, flags)?;
> > +        }
> > +        for slice in &mut self.slices {
> > +            buf.extend_from_slice(slice, flags)?;
> > +        }
> > +
> > +        Ok(buf)
> > +    }
> > +}
> > +
> > +/// Provides a way to get mutable slices of data to write into.
> > +impl<'a, I> SBuffer<I>
> > +where
> > +    I: Iterator<Item = &'a mut [u8]>,
> > +{
> > +    /// Returns a mutable slice of at most `len` bytes, or `None` if we are at the end of the data.
> > +    ///
> > +    /// If a slice shorter than `len` bytes has been returned, the caller can call this method
> > +    /// again until it returns `None` to try and obtain the remainder of the data.
> > +    fn get_slice_mut(&mut self, len: usize) -> Option<&'a mut [u8]> {
> > +        self.get_slice_internal(len, |s, pos| s.split_at_mut(pos))
> > +    }
> > +
> > +    /// Ideally we would implement `Write`, but it is not available in `core`.
> > +    /// So mimic `std::io::Write::write_all`.
> > +    #[expect(unused)]
> > +    pub(crate) fn write_all(&mut self, mut src: &[u8]) -> Result {
> > +        while !src.is_empty() {
> > +            match self.get_slice_mut(src.len()) {
> > +                None => return Err(ETOOSMALL),
> > +                Some(dst) => {
> > +                    let src_slice;
> > +                    (src_slice, src) = src.split_at(dst.len());
> > +                    dst.copy_from_slice(src_slice);
> > +                }
> > +            }
> > +        }
> > +
> > +        Ok(())
> > +    }
> > +}
> > +
> > +impl<'a, I> Iterator for SBuffer<I>
> > +where
> > +    I: Iterator<Item = &'a [u8]>,
> > +{
> > +    type Item = u8;
> > +
> > +    fn next(&mut self) -> Option<Self::Item> {
> > +        // Returned slices are guaranteed to not be empty so we can safely index the first entry.
> > +        self.get_slice(1).map(|s| s[0])
> > +    }
> > +}
> > -- 
> > 2.50.1
>