diff --git a/examples/bulk.rs b/examples/bulk.rs index 684d77a..cb79fee 100644 --- a/examples/bulk.rs +++ b/examples/bulk.rs @@ -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); } } diff --git a/src/device.rs b/src/device.rs index 72edd5f..18e0831 100644 --- a/src/device.rs +++ b/src/device.rs @@ -584,6 +584,26 @@ impl Endpoint { /// Methods for Bulk and Interrupt endpoints. impl Endpoint { + /// 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 diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index ecaf21b..2262c67 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -722,6 +722,15 @@ impl LinuxEndpoint { Ok(usbfs::clear_halt(&inner.interface.device.fd, endpoint)?) }) } + + pub(crate) fn allocate(&self, len: usize) -> Result { + 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 { diff --git a/src/transfer/buffer.rs b/src/transfer/buffer.rs index ebf74af..8c94b7e 100644 --- a/src/transfer/buffer.rs +++ b/src/transfer/buffer.rs @@ -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`. + /// This buffer will not support [zero-copy + /// transfers][`crate::Endpoint::allocate`], but can be cheaply converted to + /// a `Vec`. /// /// 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 { + 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.