Linux zero-copy

This commit is contained in:
Kevin Mehall 2025-04-19 19:25:47 -06:00
parent 992fd16078
commit 2929a10b76
4 changed files with 66 additions and 6 deletions

View file

@ -1,6 +1,6 @@
use futures_lite::future::block_on;
use nusb::{
transfer::{Buffer, Bulk, In, Out},
transfer::{Bulk, In, Out},
MaybeFuture,
};
@ -23,12 +23,14 @@ fn main() {
loop {
while ep_in.pending() < 8 {
ep_in.submit(Buffer::new(256));
let buffer = ep_in.allocate(4096);
ep_in.submit(buffer);
}
let result = block_on(ep_in.next_complete());
println!("{result:?}");
if result.status.is_err() {
break;
}
ep_in.submit(result.data);
}
}

View file

@ -584,6 +584,26 @@ impl<EpType: EndpointType, Dir: EndpointDirection> Endpoint<EpType, Dir> {
/// Methods for Bulk and Interrupt endpoints.
impl<EpType: BulkOrInterrupt, Dir: EndpointDirection> Endpoint<EpType, Dir> {
/// Allocate a buffer for use on this endpoint, zero-copy if possible.
///
/// A zero-copy buffer allows the kernel to DMA directly to/from this
/// buffer for improved performance. However, because it is not allocated
/// with the system allocator, it cannot be converted to a `Vec` without
/// copying.
///
/// This is currently only supported on Linux, falling back to [`Buffer::new`]
/// on other platforms, or if the memory allocation fails.
pub fn allocate(&self, len: usize) -> Buffer {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
if let Ok(b) = self.backend.allocate(len) {
return b;
}
}
Buffer::new(len)
}
/// Begin a transfer on the endpoint.
///
/// Submitted transfers are queued and completed in order. Once the transfer

View file

@ -722,6 +722,15 @@ impl LinuxEndpoint {
Ok(usbfs::clear_halt(&inner.interface.device.fd, endpoint)?)
})
}
pub(crate) fn allocate(&self, len: usize) -> Result<Buffer, Errno> {
Buffer::mmap(&self.inner.interface.device.fd, len).inspect_err(|e| {
warn!(
"Failed to allocate zero-copy buffer of length {len} for endpoint {}: {e}",
self.inner.address
);
})
}
}
impl Drop for LinuxEndpoint {

View file

@ -51,26 +51,55 @@ pub struct Buffer {
impl Buffer {
/// Allocate a new bufffer with the default allocator.
///
/// This buffer will not support zero-copy transfers, but can be cheaply
/// converted to a `Vec<u8>`.
/// This buffer will not support [zero-copy
/// transfers][`crate::Endpoint::allocate`], but can be cheaply converted to
/// a `Vec<u8>`.
///
/// The passed size will be used as the `transfer_len`, and the `capacity`
/// be at least that large.
/// will be at least that large.
///
/// ### Panics
/// * If the requested length is greater than `u32::MAX`.
#[inline]
pub fn new(transfer_len: usize) -> Self {
let len_u32 = transfer_len.try_into().expect("length overflow");
let mut vec = ManuallyDrop::new(Vec::with_capacity(transfer_len));
Buffer {
ptr: vec.as_mut_ptr(),
len: 0,
transfer_len: transfer_len.try_into().expect("capacity overflow"),
transfer_len: len_u32,
capacity: vec.capacity().try_into().expect("capacity overflow"),
allocator: Allocator::Default,
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) fn mmap(
fd: &std::os::unix::prelude::OwnedFd,
len: usize,
) -> Result<Buffer, rustix::io::Errno> {
let len_u32 = len.try_into().expect("length overflow");
let ptr = unsafe {
rustix::mm::mmap(
std::ptr::null_mut(),
len,
rustix::mm::ProtFlags::READ | rustix::mm::ProtFlags::WRITE,
rustix::mm::MapFlags::SHARED,
fd,
0,
)
}?;
Ok(Buffer {
ptr: ptr as *mut u8,
len: 0,
transfer_len: len_u32,
capacity: len_u32,
allocator: Allocator::Mmap,
})
}
/// Get the number of initialized bytes in the buffer.
///
/// For OUT transfers, this is the amount of data written to the buffer which will be sent when the buffer is submitted.