mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Colin Braun <colinbrauncl@gmail.com>
To: "Miguel Ojeda" <ojeda@kernel.org>,
	"Greg Kroah-Hartman" <gregkh@linuxfoundation.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>,
	"Daniel Almeida" <daniel.almeida@collabora.com>,
	"Tamir Duberstein" <tamird@kernel.org>,
	"Alexandre Courbot" <acourbot@nvidia.com>,
	"Onur Özkan" <work@onurozkan.dev>,
	"Mauro Carvalho Chehab" <mchehab@kernel.org>,
	"Alan Stern" <stern@rowland.harvard.edu>,
	"Mathias Nyman" <mathias.nyman@intel.com>
Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
	 linux-usb@vger.kernel.org, linux-media@vger.kernel.org,
	 Colin Braun <colin.braun.cl@gmail.com>
Subject: [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions
Date: Sun, 12 Jul 2026 16:07:59 -0500	[thread overview]
Message-ID: <20260712-urb-abstraction-v1-v1-2-9fa011634ead@gmail.com> (raw)
In-Reply-To: <20260712-urb-abstraction-v1-v1-0-9fa011634ead@gmail.com>

The goal of this patch is to create safe USB host interface and endpoint
descriptors necessary for USB driver development. Specifically, this
safely wraps the C side `struct usb_host_interface` and
`struct usb_host_endpoint` types.

Additionally, support for querying and configuring USB interface
altsettings is added to the existing Rust `Interface` and `Device`
types.

The `Device` struct is made public for two reasons:
- Sending control URBs in a way that makes it clear that endpoint 0 is a
  shared device endpoint, not specific to an interface.
- Allows for the possiblity of a future driver that binds to the USB
  device rather than an interface.

Signed-off-by: Colin Braun <colin.braun.cl@gmail.com>
---
 rust/kernel/usb.rs | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 191 insertions(+), 3 deletions(-)

diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs
index 3ae9c05cd32a..21dddc735bdf 100644
--- a/rust/kernel/usb.rs
+++ b/rust/kernel/usb.rs
@@ -20,6 +20,12 @@
     prelude::*,
     sync::aref::AlwaysRefCounted,
     types::Opaque,
+    usb::ch9::{
+        Direction,
+        EndpointDescriptor,
+        InterfaceClass,
+        InterfaceDescriptor, //
+    },
     ThisModule, //
 };
 use core::{
@@ -29,6 +35,7 @@
         MaybeUninit, //
     },
     ptr::NonNull,
+    slice, //
 };
 
 pub mod ch9;
@@ -358,6 +365,173 @@ impl<Ctx: device::DeviceContext> Interface<Ctx> {
     fn as_raw(&self) -> *mut bindings::usb_interface {
         self.0.get()
     }
+
+    fn inner(&self) -> &bindings::usb_interface {
+        // SAFETY: The type invariants guarantee that `self.0` wraps a valid
+        // `struct usb_interface`.
+        unsafe { &*self.as_raw() }
+    }
+
+    /// Returns the current alternate setting for this interface.
+    pub fn cur_altsetting(&self) -> &HostInterface {
+        // SAFETY: `cur_altsetting` is a valid `struct usb_host_interface`
+        // pointer provided by the USB core. `HostInterface` is
+        // `#[repr(transparent)]` over it.
+        unsafe { &*(self.inner().cur_altsetting as *const HostInterface) }
+    }
+
+    /// Returns all alternate settings for this interface.
+    pub fn altsettings(&self) -> &[HostInterface] {
+        // SAFETY: `altsetting` is a valid array of `num_altsetting`
+        // entries provided by the USB core. `HostInterface` is
+        // `#[repr(transparent)]` over `usb_host_interface`.
+        unsafe {
+            slice::from_raw_parts(
+                self.inner().altsetting as *const HostInterface,
+                self.inner().num_altsetting as usize,
+            )
+        }
+    }
+}
+
+impl Interface<device::Bound> {
+    /// Select an alternate setting for this interface.
+    ///
+    /// On success the device switches to the given alternate setting,
+    /// which may change the set of active endpoints. This is a convenience
+    /// wrapper around [`Device<Bound>::set_interface`].
+    pub fn set_interface(&self, altsetting: u8) -> Result {
+        let dev: &Device<device::Bound> = self.as_ref();
+        dev.set_interface(self.cur_altsetting().number(), altsetting)
+    }
+}
+
+/// Abstraction for the USB Host Interface structure, i.e. `struct usb_host_interface`.
+#[repr(transparent)]
+pub struct HostInterface(Opaque<bindings::usb_host_interface>);
+
+impl HostInterface {
+    fn inner(&self) -> &bindings::usb_host_interface {
+        // SAFETY: The type invariants guarantee that `self.0` wraps a valid
+        // `struct usb_host_interface`.
+        unsafe { &*self.0.get() }
+    }
+
+    /// Returns the interface descriptor.
+    fn desc(&self) -> &InterfaceDescriptor {
+        // SAFETY: `desc` is a valid `struct usb_interface_descriptor`
+        // embedded in `usb_host_interface`. `InterfaceDescriptor` is
+        // `#[repr(transparent)]` over it.
+        unsafe { &*((core::ptr::from_ref(&self.inner().desc)).cast()) }
+    }
+
+    /// Returns the list of endpoints in this alternate setting.
+    pub fn endpoints(&self) -> &[HostEndpoint] {
+        // SAFETY: `endpoint` is a valid array of `bNumEndpoints` entries.
+        // `HostEndpoint` is `#[repr(transparent)]` over
+        // `usb_host_endpoint`.
+        unsafe {
+            core::ptr::slice_from_raw_parts(
+                self.inner().endpoint as *const HostEndpoint,
+                self.desc().bNumEndpoints() as usize,
+            )
+            .as_ref()
+            .unwrap_or(&[])
+        }
+    }
+
+    /// Returns the interface number (`bInterfaceNumber`).
+    pub fn number(&self) -> u8 {
+        self.desc().bInterfaceNumber()
+    }
+
+    /// Returns the alternate setting number (`bAlternateSetting`).
+    pub fn alternate_setting(&self) -> u8 {
+        self.desc().bAlternateSetting()
+    }
+
+    /// Returns the interface class (`bInterfaceClass`).
+    pub fn class(&self) -> InterfaceClass {
+        self.desc().bInterfaceClass()
+    }
+}
+
+/// USB endpoint transfer type.
+///
+/// Maps to the `bmAttributes` field of the endpoint descriptor
+/// (`USB_ENDPOINT_XFER_*` constants).
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u8)]
+pub enum EndpointType {
+    /// Control endpoint.
+    Control = bindings::USB_ENDPOINT_XFER_CONTROL as u8,
+    /// Isochronous endpoint.
+    Isoc = bindings::USB_ENDPOINT_XFER_ISOC as u8,
+    /// Bulk endpoint.
+    Bulk = bindings::USB_ENDPOINT_XFER_BULK as u8,
+    /// Interrupt endpoint.
+    Int = bindings::USB_ENDPOINT_XFER_INT as u8,
+}
+
+/// Abstraction for the USB Host Endpoint structure, i.e. [`struct usb_host_endpoint`].
+///
+/// [`struct usb_host_endpoint`]: https://docs.kernel.org/driver-api/usb/usb.html#c.usb_host_endpoint
+#[repr(transparent)]
+pub struct HostEndpoint(Opaque<bindings::usb_host_endpoint>);
+
+impl HostEndpoint {
+    fn inner(&self) -> &bindings::usb_host_endpoint {
+        // SAFETY: The type invariants guarantee that `self.0` wraps a valid
+        // `struct usb_host_endpoint`.
+        unsafe { &*self.0.get() }
+    }
+
+    /// Returns the endpoint descriptor.
+    fn desc(&self) -> &EndpointDescriptor {
+        // SAFETY: `desc` is a valid `struct usb_endpoint_descriptor`
+        // embedded in `usb_host_endpoint`. `EndpointDescriptor` is
+        // `#[repr(transparent)]` over it.
+        unsafe { &*(core::ptr::from_ref(&self.inner().desc).cast()) }
+    }
+
+    /// Returns the direction of this endpoint (IN or OUT).
+    pub fn endpoint_dir(&self) -> Direction {
+        if self.desc().bEndpointAddress() & Direction::In as u8 == 0 {
+            Direction::Out
+        } else {
+            Direction::In
+        }
+    }
+
+    /// Returns the endpoint number (0-15).
+    pub fn endpoint_number(&self) -> u8 {
+        self.desc().bEndpointAddress() & bindings::USB_ENDPOINT_NUMBER_MASK as u8
+    }
+
+    /// Returns the transfer type of this endpoint.
+    pub fn endpoint_type(&self) -> EndpointType {
+        let val = self.desc().bmAttributes() & bindings::USB_ENDPOINT_XFERTYPE_MASK as u8;
+        // SAFETY: `bmAttributes` masked with `USB_ENDPOINT_XFERTYPE_MASK`
+        // is guaranteed to be 0-3, which maps exactly to the four
+        // `EndpointType` variants.
+        unsafe { core::mem::transmute::<u8, EndpointType>(val) }
+    }
+
+    /// Returns the interval for interrupt and isochronous endpoints.
+    pub fn interval(&self) -> u8 {
+        self.desc().bInterval()
+    }
+
+    /// Returns the maximum packet size for this endpoint.
+    pub fn maxp(&self) -> u16 {
+        u16::from_le(self.desc().wMaxPacketSize()) & bindings::USB_ENDPOINT_MAXP_MASK as u16
+    }
+
+    /// Returns the high-speed multiplier for isochronous endpoints.
+    pub fn maxp_mult(&self) -> u16 {
+        (u16::from_le(self.desc().wMaxPacketSize()) & bindings::USB_EP_MAXP_MULT_MASK as u16)
+            >> bindings::USB_EP_MAXP_MULT_SHIFT
+    }
 }
 
 // SAFETY: `usb::Interface` is a transparent wrapper of `struct usb_interface`.
@@ -382,8 +556,8 @@ fn as_ref(&self) -> &device::Device<Ctx> {
     }
 }
 
-impl<Ctx: device::DeviceContext> AsRef<Device> for Interface<Ctx> {
-    fn as_ref(&self) -> &Device {
+impl<Ctx: device::DeviceContext> AsRef<Device<Ctx>> for Interface<Ctx> {
+    fn as_ref(&self) -> &Device<Ctx> {
         // SAFETY: `self.as_raw()` is valid by the type invariants.
         let usb_dev = unsafe { bindings::interface_to_usbdev(self.as_raw()) };
 
@@ -428,7 +602,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)]
-struct Device<Ctx: device::DeviceContext = device::Normal>(
+pub struct Device<Ctx: device::DeviceContext = device::Normal>(
     Opaque<bindings::usb_device>,
     PhantomData<Ctx>,
 );
@@ -439,6 +613,20 @@ fn as_raw(&self) -> *mut bindings::usb_device {
     }
 }
 
+impl Device<device::Bound> {
+    /// Select an alternate setting for the given interface.
+    ///
+    /// On success the device switches the given interface to the given alternate setting,
+    /// which may change the set of active endpoints.
+    pub fn set_interface(&self, interface: u8, altsetting: u8) -> Result {
+        // SAFETY: `self.as_raw()` is a valid `struct usb_device` pointer by the type
+        // invariants. `usb_set_interface` is safe to call on a bound device.
+        to_result(unsafe {
+            bindings::usb_set_interface(self.as_raw(), i32::from(interface), i32::from(altsetting))
+        })
+    }
+}
+
 // SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic
 // argument.
 kernel::impl_device_context_deref!(unsafe { Device });

-- 
2.54.0


  parent reply	other threads:[~2026-07-12 21:09 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-12 21:07 [RFC PATCH 0/4] rust: usb: add usb request block abstractions and a user Colin Braun
2026-07-12 21:07 ` [RFC PATCH 1/4] rust: usb: add USB ch9 standard descriptors and constants Colin Braun
2026-07-12 21:07 ` Colin Braun [this message]
2026-07-13 13:22   ` [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions Danilo Krummrich
2026-07-13 20:03     ` Colin Braun
2026-07-13 20:09       ` Danilo Krummrich
2026-07-14  9:26         ` Oliver Neukum
2026-07-14 13:05           ` Danilo Krummrich
2026-07-14 16:26             ` Alan Stern
2026-07-14 17:53               ` Danilo Krummrich
2026-07-14 18:48                 ` Oliver Neukum
2026-07-14 18:57                   ` Danilo Krummrich
2026-07-14 19:25                 ` Alan Stern
2026-07-15  0:27                   ` Danilo Krummrich
2026-07-14 17:57               ` Oliver Neukum
2026-07-14 19:03                 ` Alan Stern
2026-07-14 18:26               ` Miguel Ojeda
2026-07-12 21:08 ` [RFC PATCH 3/4] rust: usb: add urb abstraction with control and isochronous support Colin Braun
2026-07-12 21:08 ` [RFC PATCH 4/4] media: add gv-usb2 audio capture driver Colin Braun
2026-07-13 14:44   ` Danilo Krummrich
2026-07-13 21:08     ` Colin Braun
2026-07-13 13:22 ` [RFC PATCH 0/4] rust: usb: add usb request block abstractions and a user Danilo Krummrich
2026-07-13 20:10   ` Colin Braun
2026-07-13 13:53 ` Daniel Almeida
2026-07-13 20:32   ` Colin Braun
2026-07-15  2:35     ` Daniel Almeida
2026-07-15  5:43       ` Greg Kroah-Hartman
2026-07-15 19:57       ` Colin Braun

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=20260712-urb-abstraction-v1-v1-2-9fa011634ead@gmail.com \
    --to=colinbrauncl@gmail.com \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=aliceryhl@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun@kernel.org \
    --cc=colin.braun.cl@gmail.com \
    --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-media@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=mathias.nyman@intel.com \
    --cc=mchehab@kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=stern@rowland.harvard.edu \
    --cc=tamird@kernel.org \
    --cc=tmgross@umich.edu \
    --cc=work@onurozkan.dev \
    /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