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
next prev 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