Rust for Linux List
 help / color / mirror / Atom feed
From: Mike Lothian <mike@fireburn.co.uk>
To: rust-for-linux@vger.kernel.org
Cc: linux-usb@vger.kernel.org,
	"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
	"Daniel Almeida" <daniel.almeida@collabora.com>,
	"Miguel Ojeda" <ojeda@kernel.org>,
	"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <lossin@kernel.org>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	"Danilo Krummrich" <dakr@kernel.org>,
	"Alexandre Courbot" <acourbot@nvidia.com>,
	linux-kernel@vger.kernel.org,
	"Mike Lothian" <mike@fireburn.co.uk>
Subject: [RFC PATCH v2 10/11] rust: usb: keep usb::Device private and gate transfers on Interface<Bound>
Date: Fri,  3 Jul 2026 04:00:14 +0100	[thread overview]
Message-ID: <20260703030020.2694-11-mike@fireburn.co.uk> (raw)
In-Reply-To: <20260703030020.2694-1-mike@fireburn.co.uk>

Address the v1 RFC review (Danilo Krummrich):

 - Do not make `usb::Device` public. Writing a `usb::Interface` driver should
   not require naming the underlying `usb::Device` (cf. commit 22d693e45d4a
   ("rust: usb: keep usb::Device private for now")), so the struct is
   private again and the device-wide transfer operations are exposed on
   the interface.

 - Gate the transfer methods (`bulk_send`/`bulk_recv`/`interrupt_recv`/
   `control_send`/`control_recv`/`set_interface`/`reset_configuration`/
   `clear_halt`/`bulk_in_queue`/`bulk_out_queue`) on `Interface<Bound>`, so a
   transfer cannot be issued before the interface's device is bound to the
   driver. Each forwards to the (now private) `Device` helper.

 - Add `Interface::as_bound()` for drivers that keep a refcounted
   `ARef<Interface>` and perform transfers from a deferred context (a work
   item), with a safety contract requiring the work be flushed before unbind.

Signed-off-by: Mike Lothian <mike@fireburn.co.uk>
Assisted-by: Claude:claude-opus-4-8 [Claude-Code]
---
 rust/kernel/usb.rs | 162 +++++++++++++++++++++++++++++++++++++++------
 1 file changed, 140 insertions(+), 22 deletions(-)

diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs
index 6c49de422b30..1fcaf34433cc 100644
--- a/rust/kernel/usb.rs
+++ b/rust/kernel/usb.rs
@@ -368,6 +368,130 @@ pub fn number(&self) -> u8 {
             (*altsetting).desc.bInterfaceNumber
         }
     }
+
+    /// Returns a [`Bound`](device::Bound)-context view of this interface, on which
+    /// the device-wide transfer operations (`bulk_send`, `control_recv`, …) become
+    /// callable.
+    ///
+    /// During `probe()` the interface is already available in a bound context
+    /// ([`Core`](device::Core) dereferences to [`Bound`](device::Bound)), so this
+    /// is only needed by drivers that keep a refcounted [`ARef<Interface>`] and
+    /// perform transfers from a deferred context (e.g. a work item).
+    ///
+    /// # Safety
+    ///
+    /// The caller must ensure the interface stays bound to this driver for the
+    /// entire lifetime of the returned reference — for example by cancelling and
+    /// flushing any such deferred work in `disconnect()`. Issuing a transfer on an
+    /// unbound interface is undefined behaviour.
+    ///
+    /// [`ARef<Interface>`]: crate::sync::aref::ARef
+    pub unsafe fn as_bound(&self) -> &Interface<device::Bound> {
+        // SAFETY: `Interface<Ctx>` is `#[repr(transparent)]` over the same
+        // `Opaque<usb_interface>` regardless of the `Ctx` marker, so this cast only
+        // changes the zero-sized context type; the caller upholds boundness.
+        unsafe { &*(self as *const Self as *const Interface<device::Bound>) }
+    }
+}
+
+/// Device-wide USB transfer operations, exposed only on a [`Bound`](device::Bound)
+/// interface so a transfer cannot be issued before the interface's device is bound
+/// to this driver. Each method forwards to the (sleeping) USB core helper for the
+/// interface's [`struct usb_device`]; all must be called from process context.
+impl Interface<device::Bound> {
+    fn device(&self) -> &Device {
+        // SAFETY: `self.as_raw()` is a valid `struct usb_interface` by the type
+        // invariant; `interface_to_usbdev()` returns its valid `struct usb_device`,
+        // and `Device` is `#[repr(transparent)]` over `Opaque<usb_device>`. The
+        // borrow is tied to `&self`, which keeps the interface (and device) alive.
+        let usb_dev = unsafe { bindings::interface_to_usbdev(self.as_raw()) };
+        // SAFETY: `usb_dev` is a valid `struct usb_device` pointer (see above).
+        unsafe { &*(usb_dev.cast::<Device>()) }
+    }
+
+    /// Clears a halt/stall on bulk `endpoint` (both the device-side stall and the
+    /// host-side data toggle). The direction is taken from bit 7 of the endpoint
+    /// address, so it works for IN and OUT endpoints. Sleeps.
+    pub fn clear_halt(&self, endpoint: u8) -> Result {
+        self.device().clear_halt(endpoint)
+    }
+
+    /// Issues a synchronous bulk OUT transfer of `data` to `endpoint`, returning
+    /// the number of bytes transferred. `data` must be DMA-capable. Sleeps.
+    pub fn bulk_send(&self, endpoint: u8, data: &[u8], timeout: Delta) -> Result<usize> {
+        self.device().bulk_send(endpoint, data, timeout)
+    }
+
+    /// Issues a synchronous bulk IN transfer into `data`, returning the number of
+    /// bytes received. `data` must be DMA-capable. Sleeps.
+    pub fn bulk_recv(&self, endpoint: u8, data: &mut [u8], timeout: Delta) -> Result<usize> {
+        self.device().bulk_recv(endpoint, data, timeout)
+    }
+
+    /// Issues a synchronous interrupt IN transfer into `data`, returning the number
+    /// of bytes received. `data` must be DMA-capable. Sleeps.
+    pub fn interrupt_recv(&self, endpoint: u8, data: &mut [u8], timeout: Delta) -> Result<usize> {
+        self.device().interrupt_recv(endpoint, data, timeout)
+    }
+
+    /// Issues a synchronous control OUT transfer on the default control endpoint.
+    /// The buffer is copied internally, so `data` need not be DMA-capable. Sleeps.
+    pub fn control_send(
+        &self,
+        request: u8,
+        request_type: u8,
+        value: u16,
+        index: u16,
+        data: &[u8],
+        timeout: Delta,
+    ) -> Result {
+        self.device()
+            .control_send(request, request_type, value, index, data, timeout)
+    }
+
+    /// Issues a synchronous control IN transfer on the default control endpoint,
+    /// filling `data` with exactly `data.len()` bytes. Sleeps.
+    pub fn control_recv(
+        &self,
+        request: u8,
+        request_type: u8,
+        value: u16,
+        index: u16,
+        data: &mut [u8],
+        timeout: Delta,
+    ) -> Result {
+        self.device()
+            .control_recv(request, request_type, value, index, data, timeout)
+    }
+
+    /// Selects alternate setting `alternate` of interface `interface`
+    /// (`SET_INTERFACE`). Sleeps.
+    pub fn set_interface(&self, interface: u8, alternate: u8) -> Result {
+        self.device().set_interface(interface, alternate)
+    }
+
+    /// Re-issues `SET_CONFIGURATION` for the current configuration, resetting all
+    /// endpoint data toggles without re-enumerating the device. Sleeps.
+    pub fn reset_configuration(&self) -> Result {
+        self.device().reset_configuration()
+    }
+
+    /// Opens a persistently-queued asynchronous bulk IN reader on `endpoint`
+    /// ([`BulkInQueue`]). Must be called from process context.
+    pub fn bulk_in_queue(&self, endpoint: u8, depth: usize, buf_len: usize) -> Result<BulkInQueue> {
+        self.device().bulk_in_queue(endpoint, depth, buf_len)
+    }
+
+    /// Opens an asynchronous, pipelined bulk OUT writer on `endpoint`
+    /// ([`BulkOutQueue`]). Must be called from process context.
+    pub fn bulk_out_queue(
+        &self,
+        endpoint: u8,
+        depth: usize,
+        buf_len: usize,
+    ) -> Result<BulkOutQueue> {
+        self.device().bulk_out_queue(endpoint, depth, buf_len)
+    }
 }
 
 // SAFETY: `usb::Interface` is a transparent wrapper of `struct usb_interface`.
@@ -392,17 +516,6 @@ fn as_ref(&self) -> &device::Device<Ctx> {
     }
 }
 
-impl<Ctx: device::DeviceContext> AsRef<Device> for Interface<Ctx> {
-    fn as_ref(&self) -> &Device {
-        // SAFETY: `self.as_raw()` is valid by the type invariants.
-        let usb_dev = unsafe { bindings::interface_to_usbdev(self.as_raw()) };
-
-        // SAFETY: For a valid `struct usb_interface` pointer, the above call to
-        // `interface_to_usbdev()` guarantees to return a valid pointer to a `struct usb_device`.
-        unsafe { &*(usb_dev.cast()) }
-    }
-}
-
 // SAFETY: Instances of `Interface` are always reference-counted.
 unsafe impl AlwaysRefCounted for Interface {
     fn inc_ref(&self) {
@@ -431,6 +544,11 @@ unsafe impl Sync for Interface {}
 /// The implementation abstracts the usage of a C [`struct usb_device`] passed in
 /// from the C side.
 ///
+/// It is kept private to this module: a `usb::Interface` driver should never need
+/// to name the underlying `usb::Device`. The device-wide transfer operations are
+/// instead exposed on [`Interface<Bound>`](Interface), which proves the interface
+/// (and thus its device) is bound to this driver for the duration of the call.
+///
 /// # Invariants
 ///
 /// A [`Device`] instance represents a valid [`struct usb_device`] created by the C portion of the
@@ -438,7 +556,7 @@ unsafe impl Sync for Interface {}
 ///
 /// [`struct usb_device`]: https://www.kernel.org/doc/html/latest/driver-api/usb/usb.html#c.usb_device
 #[repr(transparent)]
-pub struct Device<Ctx: device::DeviceContext = device::Normal>(
+struct Device<Ctx: device::DeviceContext = device::Normal>(
     Opaque<bindings::usb_device>,
     PhantomData<Ctx>,
 );
@@ -454,7 +572,7 @@ fn as_raw(&self) -> *mut bindings::usb_device {
     /// address (`USB_DIR_IN`), so this works for both bulk IN and bulk OUT endpoints.
     /// Must be called from process context (it sleeps). Needed e.g. when another
     /// driver left a streaming endpoint stalled.
-    pub fn clear_halt(&self, endpoint: u8) -> Result {
+    pub(crate) fn clear_halt(&self, endpoint: u8) -> Result {
         // `usb_clear_halt()` must be given a pipe whose direction matches the endpoint:
         // it issues CLEAR_FEATURE to that endpoint address and resets the matching data
         // toggle. Build an IN or OUT pipe from the `USB_DIR_IN` bit of the address;
@@ -491,7 +609,7 @@ pub fn clear_halt(&self, endpoint: u8) -> Result {
     /// not arrange DMA-capable storage themselves.
     ///
     /// [`usb_bulk_msg()`]: https://docs.kernel.org/driver-api/usb/usb.html#c.usb_bulk_msg
-    pub fn bulk_send(&self, endpoint: u8, data: &[u8], timeout: Delta) -> Result<usize> {
+    pub(crate) fn bulk_send(&self, endpoint: u8, data: &[u8], timeout: Delta) -> Result<usize> {
         let mut actual: kernel::ffi::c_int = 0;
 
         // `usb_bulk_msg()` requires a DMA-capable buffer; `data` may live on the
@@ -537,7 +655,7 @@ pub fn bulk_send(&self, endpoint: u8, data: &[u8], timeout: Delta) -> Result<usi
     /// `data` may be any slice.
     ///
     /// [`usb_bulk_msg()`]: https://docs.kernel.org/driver-api/usb/usb.html#c.usb_bulk_msg
-    pub fn bulk_recv(&self, endpoint: u8, data: &mut [u8], timeout: Delta) -> Result<usize> {
+    pub(crate) fn bulk_recv(&self, endpoint: u8, data: &mut [u8], timeout: Delta) -> Result<usize> {
         let mut actual: kernel::ffi::c_int = 0;
 
         // `usb_bulk_msg()` requires a DMA-capable buffer; receive into a kmalloc'd
@@ -579,7 +697,7 @@ pub fn bulk_recv(&self, endpoint: u8, data: &mut [u8], timeout: Delta) -> Result
     /// milliseconds; a [`Delta`] of zero, or any non-zero value below 1 ms, waits indefinitely.
     ///
     /// [`bulk_recv`]: Self::bulk_recv
-    pub fn interrupt_recv(&self, endpoint: u8, data: &mut [u8], timeout: Delta) -> Result<usize> {
+    pub(crate) fn interrupt_recv(&self, endpoint: u8, data: &mut [u8], timeout: Delta) -> Result<usize> {
         let mut actual: kernel::ffi::c_int = 0;
 
         // `usb_interrupt_msg()` requires a DMA-capable buffer; receive into a kmalloc'd
@@ -623,7 +741,7 @@ pub fn interrupt_recv(&self, endpoint: u8, data: &mut [u8], timeout: Delta) -> R
     /// non-zero value below 1 ms — waits indefinitely.
     ///
     /// [`usb_control_msg_send()`]: https://docs.kernel.org/driver-api/usb/usb.html#c.usb_control_msg_send
-    pub fn control_send(
+    pub(crate) fn control_send(
         &self,
         request: u8,
         request_type: u8,
@@ -658,7 +776,7 @@ pub fn control_send(
     /// rules are as for [`Device::control_send`].
     ///
     /// [`usb_control_msg_recv()`]: https://docs.kernel.org/driver-api/usb/usb.html#c.usb_control_msg_recv
-    pub fn control_recv(
+    pub(crate) fn control_recv(
         &self,
         request: u8,
         request_type: u8,
@@ -691,7 +809,7 @@ pub fn control_recv(
     /// a blocking, sleeping call and must only be invoked from process context.
     ///
     /// [`usb_set_interface()`]: https://docs.kernel.org/driver-api/usb/usb.html#c.usb_set_interface
-    pub fn set_interface(&self, interface: u8, alternate: u8) -> Result {
+    pub(crate) fn set_interface(&self, interface: u8, alternate: u8) -> Result {
         // SAFETY: `self.as_raw()` is a valid `struct usb_device` by the type invariant.
         to_result(unsafe {
             bindings::usb_set_interface(self.as_raw(), interface.into(), alternate.into())
@@ -708,7 +826,7 @@ pub fn set_interface(&self, interface: u8, alternate: u8) -> Result {
     /// drivers that are mid-transfer.
     ///
     /// [`usb_reset_configuration()`]: https://docs.kernel.org/driver-api/usb/usb.html#c.usb_reset_configuration
-    pub fn reset_configuration(&self) -> Result {
+    pub(crate) fn reset_configuration(&self) -> Result {
         // SAFETY: `self.as_raw()` is a valid `struct usb_device` by the type invariant;
         // `usb_reset_configuration()` only re-issues SET_CONFIGURATION and updates the
         // host-side endpoint state for this device.
@@ -734,7 +852,7 @@ pub fn reset_configuration(&self) -> Result {
     ///
     /// [`bulk_recv`]: Self::bulk_recv
     /// [`BulkInQueue::recv`]: BulkInQueue::recv
-    pub fn bulk_in_queue(&self, endpoint: u8, depth: usize, buf_len: usize) -> Result<BulkInQueue> {
+    pub(crate) fn bulk_in_queue(&self, endpoint: u8, depth: usize, buf_len: usize) -> Result<BulkInQueue> {
         let dev = self.as_raw();
 
         // SAFETY: `dev` is a valid `struct usb_device` by the type invariant; take a
@@ -818,7 +936,7 @@ pub fn bulk_in_queue(&self, endpoint: u8, depth: usize, buf_len: usize) -> Resul
     /// [`bulk_in_queue`]: Self::bulk_in_queue
     /// [`bulk_send`]: Self::bulk_send
     /// [`send`]: BulkOutQueue::send
-    pub fn bulk_out_queue(
+    pub(crate) fn bulk_out_queue(
         &self,
         endpoint: u8,
         depth: usize,
-- 
2.55.0


  parent reply	other threads:[~2026-07-03  3:00 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-17 14:59 [RFC PATCH 0/9] rust: usb: synchronous bulk/control transfers + helpers Mike Lothian
2026-06-17 14:59 ` [RFC PATCH 1/9] rust: usb: add synchronous bulk transfer support Mike Lothian
2026-06-17 16:07   ` Danilo Krummrich
2026-06-17 14:59 ` [RFC PATCH 2/9] rust: usb: add synchronous control " Mike Lothian
2026-06-22  9:54   ` Oliver Neukum
2026-06-17 14:59 ` [RFC PATCH 3/9] rust: usb: add usb::Device::set_interface() Mike Lothian
2026-06-17 14:59 ` [RFC PATCH 4/9] rust: usb: add usb::Interface::number() Mike Lothian
2026-06-17 14:59 ` [RFC PATCH 5/9] rust: usb: add usb::Device::clear_halt() Mike Lothian
2026-06-17 14:59 ` [RFC PATCH 6/9] rust: usb: add usb::Device::interrupt_recv() Mike Lothian
2026-06-17 14:59 ` [RFC PATCH 7/9] rust: usb: add usb::Device::reset_configuration() Mike Lothian
2026-06-17 14:59 ` [RFC PATCH 8/9] rust: usb: add an asynchronous persistently-queued bulk IN reader Mike Lothian
2026-06-17 14:59 ` [RFC PATCH 9/9] rust: usb: add an asynchronous pipelined bulk OUT queue Mike Lothian
2026-07-03  3:00 ` [RFC PATCH v2 00/11] rust: usb: synchronous + asynchronous bulk/control transfers + helpers Mike Lothian
2026-07-03  3:00   ` [RFC PATCH v2 01/11] rust: usb: add synchronous bulk transfer support Mike Lothian
2026-07-06  9:17     ` Oliver Neukum
2026-07-03  3:00   ` [RFC PATCH v2 02/11] rust: usb: add synchronous control " Mike Lothian
2026-07-03  3:00   ` [RFC PATCH v2 03/11] rust: usb: add usb::Device::set_interface() Mike Lothian
2026-07-03  3:00   ` [RFC PATCH v2 04/11] rust: usb: add usb::Interface::number() Mike Lothian
2026-07-03  3:00   ` [RFC PATCH v2 05/11] rust: usb: add usb::Device::clear_halt() Mike Lothian
2026-07-03  3:00   ` [RFC PATCH v2 06/11] rust: usb: add usb::Device::interrupt_recv() Mike Lothian
2026-07-03  3:00   ` [RFC PATCH v2 07/11] rust: usb: add usb::Device::reset_configuration() Mike Lothian
2026-07-03  3:00   ` [RFC PATCH v2 08/11] rust: usb: add an asynchronous persistently-queued bulk IN reader Mike Lothian
2026-07-03  3:00   ` [RFC PATCH v2 09/11] rust: usb: add an asynchronous pipelined bulk OUT queue Mike Lothian
2026-07-03  3:00   ` Mike Lothian [this message]
2026-07-06  9:45     ` [RFC PATCH v2 10/11] rust: usb: keep usb::Device private and gate transfers on Interface<Bound> Oliver Neukum
2026-07-06 10:38       ` Danilo Krummrich
2026-07-06 13:46         ` Alan Stern
2026-07-06 10:40     ` Danilo Krummrich
2026-07-03  3:00   ` [RFC PATCH v2 11/11] rust: usb: let drivers choose the transfer allocation flags Mike Lothian
2026-07-06  9:47     ` Oliver Neukum

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=20260703030020.2694-11-mike@fireburn.co.uk \
    --to=mike@fireburn.co.uk \
    --cc=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=gregkh@linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox