Rust for Linux List
 help / color / mirror / Atom feed
* [RFC PATCH 0/4] rust: usb: add usb request block abstractions and a user
@ 2026-07-12 21:07 Colin Braun
  2026-07-12 21:07 ` [RFC PATCH 1/4] rust: usb: add USB ch9 standard descriptors and constants Colin Braun
                   ` (5 more replies)
  0 siblings, 6 replies; 16+ messages in thread
From: Colin Braun @ 2026-07-12 21:07 UTC (permalink / raw)
  To: Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Mauro Carvalho Chehab,
	Alan Stern, Mathias Nyman
  Cc: linux-kernel, rust-for-linux, linux-usb, linux-media, Colin Braun

This series introduces initial abstractions to allow for the
implementation of USB drivers in Rust.

This is an RFC to demonstrate a rough idea on how the USB abstractions
needed to create drivers in Rust could be implemented.

The series is broken up into 4 parts:

1. USB chapter 9 standard descriptors and constants - Exposes the
   necessary structs and constants from include/uapi/linux/usb/ch9.h as
   a new ch9 submodule.

2. Interface and endpoint abstractions - Wraps the relevant C structs
   and functions defined in include/linux/usb.h, allowing drivers to
   safely query and configure interfaces and their endpoints.

3. USB Request Block (URB) abstractions - Creates a safe wrapper around
   the C `struct urb` to allow Rust drivers to communicate with devices.

4. An initial user of the new abstractions - A driver for the GV-USB2
   composite-usb video capture device.

Patch 3 is the bulk and core of this series. By their asynchronous
nature, creating a safe URB abstraction for drivers to use is tricky.
The goals of the URB abstraction are:

1. No `unsafe` needed in driver code. This means providing a way for a
   driver to safely access private data sent with the URB.
2. Drivers are forced to handle the URB status in their completion
   callback before accessing the URB data.
3. Dropping an URB ensures it is not in-flight and frees its resources.
4. The URB can be safely resubmitted from the completion callback.

The patch elaborates on how these goals are achieved in more detail.

Although the URB abstractions do not include immediate support for bulk
or interrupt URBs, I believe it creates a foundation conducive to
future, safe abstractions for them.

Patch 4 is first user of these USB abstractions. The initial
implementation is very much bare-bones, only exposing audio data via
debugfs. It is based on the in-tree STK1160 driver and an old,
out-of-tree driver written by Isaac Lozano [1].

[1] https://github.com/Isaac-Lozano/GV-USB2-Driver

Signed-off-by: Colin Braun <colin.braun.cl@gmail.com>
---
Colin Braun (4):
      rust: usb: add USB ch9 standard descriptors and constants
      rust: usb: add usb host interface and endpoint abstractions
      rust: usb: add urb abstraction with control and isochronous support
      media: add gv-usb2 audio capture driver

 drivers/media/usb/Kconfig            |   1 +
 drivers/media/usb/Makefile           |   1 +
 drivers/media/usb/gv-usb2/Kconfig    |   9 +
 drivers/media/usb/gv-usb2/Makefile   |   1 +
 drivers/media/usb/gv-usb2/driver.rs  | 361 ++++++++++++++
 drivers/media/usb/gv-usb2/gv_usb2.rs |  16 +
 drivers/media/usb/gv-usb2/regs.rs    |  25 +
 include/linux/usb.h                  |   4 +
 rust/kernel/usb.rs                   | 905 ++++++++++++++++++++++++++++++++++-
 rust/kernel/usb/ch9.rs               | 295 ++++++++++++
 10 files changed, 1613 insertions(+), 5 deletions(-)
---
base-commit: 30e873dd61b044092dbc657c7c67a5d19adfd933
change-id: 20260712-urb-abstraction-v1-020a67f16cff

Best regards,
--  
Colin Braun <colin.braun.cl@gmail.com>


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [RFC PATCH 1/4] rust: usb: add USB ch9 standard descriptors and constants
  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 ` Colin Braun
  2026-07-12 21:07 ` [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions Colin Braun
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 16+ messages in thread
From: Colin Braun @ 2026-07-12 21:07 UTC (permalink / raw)
  To: Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Mauro Carvalho Chehab,
	Alan Stern, Mathias Nyman
  Cc: linux-kernel, rust-for-linux, linux-usb, linux-media, Colin Braun

Add a new module for USB chapter 9 structs and constants.

The goal of this patch is to provide safe Rust wrappers around the C
types from include/uapi/linux/usb/ch9.h.

These are needed by drivers and other USB abstractions (interfaces,
endpoints, URBs, etc.).

Signed-off-by: Colin Braun <colin.braun.cl@gmail.com>
---
 rust/kernel/usb.rs     |   2 +
 rust/kernel/usb/ch9.rs | 295 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 297 insertions(+)

diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs
index 7aff0c82d0af..3ae9c05cd32a 100644
--- a/rust/kernel/usb.rs
+++ b/rust/kernel/usb.rs
@@ -31,6 +31,8 @@
     ptr::NonNull,
 };
 
+pub mod ch9;
+
 /// An adapter for the registration of USB drivers.
 pub struct Adapter<T: Driver>(T);
 
diff --git a/rust/kernel/usb/ch9.rs b/rust/kernel/usb/ch9.rs
new file mode 100644
index 000000000000..f451b7273731
--- /dev/null
+++ b/rust/kernel/usb/ch9.rs
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Abstractions for USB chapter 9.
+//!
+//! C header: [`include/linux/usb/ch9.h`](srctree/include/linux/usb/ch9.h)
+
+use crate::fmt;
+
+/// USB interface class code.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(transparent)]
+pub struct InterfaceClass(u8);
+
+impl InterfaceClass {
+    /// Create an [`InterfaceClass`] from a raw `u8` class code.
+    pub const fn from_raw(class: u8) -> Self {
+        Self(class)
+    }
+
+    /// Get the raw `u8` class code value.
+    pub const fn as_raw(self) -> u8 {
+        self.0
+    }
+}
+
+macro_rules! define_all_usb_classes {
+    (
+        $($variant:ident = $binding:expr,)+
+    ) => {
+        impl InterfaceClass {
+            $(
+                #[allow(missing_docs)]
+                pub const $variant: Self = Self($binding as u8);
+            )+
+        }
+
+        impl fmt::Display for InterfaceClass {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                match self {
+                    $(
+                        &Self::$variant => write!(f, stringify!($variant)),
+                    )+
+                    _ => <Self as fmt::Debug>::fmt(self, f),
+                }
+            }
+        }
+    };
+}
+
+define_all_usb_classes! {
+    PER_INTERFACE           = bindings::USB_CLASS_PER_INTERFACE,
+    AUDIO                   = bindings::USB_CLASS_AUDIO,
+    COMM                    = bindings::USB_CLASS_COMM,
+    HID                     = bindings::USB_CLASS_HID,
+    PHYSICAL                = bindings::USB_CLASS_PHYSICAL,
+    STILL_IMAGE             = bindings::USB_CLASS_STILL_IMAGE,
+    PRINTER                 = bindings::USB_CLASS_PRINTER,
+    MASS_STORAGE            = bindings::USB_CLASS_MASS_STORAGE,
+    HUB                     = bindings::USB_CLASS_HUB,
+    CDC_DATA                = bindings::USB_CLASS_CDC_DATA,
+    CSCID                   = bindings::USB_CLASS_CSCID,
+    CONTENT_SEC             = bindings::USB_CLASS_CONTENT_SEC,
+    VIDEO                   = bindings::USB_CLASS_VIDEO,
+    WIRELESS_CONTROLLER     = bindings::USB_CLASS_WIRELESS_CONTROLLER,
+    PERSONAL_HEALTHCARE     = bindings::USB_CLASS_PERSONAL_HEALTHCARE,
+    AUDIO_VIDEO             = bindings::USB_CLASS_AUDIO_VIDEO,
+    BILLBOARD               = bindings::USB_CLASS_BILLBOARD,
+    USB_TYPE_C_BRIDGE       = bindings::USB_CLASS_USB_TYPE_C_BRIDGE,
+    MCTP                    = bindings::USB_CLASS_MCTP,
+    MISC                    = bindings::USB_CLASS_MISC,
+    APP_SPEC                = bindings::USB_CLASS_APP_SPEC,
+    VENDOR_SPEC             = bindings::USB_CLASS_VENDOR_SPEC,
+}
+
+/// USB interface descriptor.
+///
+/// Wraps the C `struct usb_interface_descriptor` defined in
+/// `include/uapi/linux/usb/ch9.h`. Corresponds to USB 2.0 spec §9.6.5,
+/// table 9-12.
+#[repr(transparent)]
+pub struct InterfaceDescriptor(bindings::usb_interface_descriptor);
+
+impl InterfaceDescriptor {
+    /// Returns the size of this descriptor in bytes.
+    #[allow(non_snake_case)]
+    pub fn bLength(&self) -> u8 {
+        self.0.bLength
+    }
+
+    /// Returns the descriptor type (`USB_DT_INTERFACE`).
+    #[allow(non_snake_case)]
+    pub fn bDescriptorType(&self) -> u8 {
+        self.0.bDescriptorType
+    }
+
+    /// Returns the interface number (zero-based).
+    #[allow(non_snake_case)]
+    pub fn bInterfaceNumber(&self) -> u8 {
+        self.0.bInterfaceNumber
+    }
+
+    /// Returns the alternate setting number.
+    #[allow(non_snake_case)]
+    pub fn bAlternateSetting(&self) -> u8 {
+        self.0.bAlternateSetting
+    }
+
+    /// Returns the number of endpoints used by this interface (excluding
+    /// the default control endpoint).
+    #[allow(non_snake_case)]
+    pub fn bNumEndpoints(&self) -> u8 {
+        self.0.bNumEndpoints
+    }
+
+    /// Returns the interface class code.
+    #[allow(non_snake_case)]
+    pub fn bInterfaceClass(&self) -> InterfaceClass {
+        InterfaceClass(self.0.bInterfaceClass)
+    }
+
+    /// Returns the interface subclass code.
+    #[allow(non_snake_case)]
+    pub fn bInterfaceSubClass(&self) -> u8 {
+        self.0.bInterfaceSubClass
+    }
+
+    /// Returns the interface protocol code.
+    #[allow(non_snake_case)]
+    pub fn bInterfaceProtocol(&self) -> u8 {
+        self.0.bInterfaceProtocol
+    }
+
+    /// Returns the index of the string descriptor describing this
+    /// interface.
+    #[allow(non_snake_case)]
+    pub fn iInterface(&self) -> u8 {
+        self.0.iInterface
+    }
+}
+
+/// USB endpoint descriptor.
+///
+/// Wraps the C `struct usb_endpoint_descriptor` defined in
+/// `include/uapi/linux/usb/ch9.h`. Corresponds to USB 2.0 spec §9.6.6,
+/// table 9-13.
+#[repr(transparent)]
+pub struct EndpointDescriptor(bindings::usb_endpoint_descriptor);
+
+impl EndpointDescriptor {
+    /// Returns the endpoint address (direction + endpoint number).
+    #[allow(non_snake_case)]
+    pub fn bEndpointAddress(&self) -> u8 {
+        self.0.bEndpointAddress
+    }
+
+    /// Returns the endpoint attributes (transfer type).
+    #[allow(non_snake_case)]
+    pub fn bmAttributes(&self) -> u8 {
+        self.0.bmAttributes
+    }
+
+    /// Returns the maximum packet size for this endpoint.
+    #[allow(non_snake_case)]
+    pub fn wMaxPacketSize(&self) -> u16 {
+        self.0.wMaxPacketSize
+    }
+
+    /// Returns the interval for isochronous/interrupt endpoints.
+    #[allow(non_snake_case)]
+    pub fn bInterval(&self) -> u8 {
+        self.0.bInterval
+    }
+}
+
+/// USB control request (SETUP packet).
+///
+/// Wraps the C `struct usb_ctrlrequest` defined in
+/// `include/uapi/linux/usb/ch9.h`. Corresponds to USB 2.0 spec §9.3,
+/// table 9-2.
+#[repr(transparent)]
+pub struct CtrlRequest(bindings::usb_ctrlrequest);
+
+impl CtrlRequest {
+    /// Creates a new control request from its constituent fields.
+    pub const fn new(
+        requesttype: RequestType,
+        request: u8,
+        value: u16,
+        index: u16,
+        length: u16,
+    ) -> Self {
+        Self(bindings::usb_ctrlrequest {
+            bRequestType: requesttype.0,
+            bRequest: request,
+            wValue: value.to_le(),
+            wIndex: index.to_le(),
+            wLength: length.to_le(),
+        })
+    }
+
+    /// Returns the data-transfer direction encoded in the setup packet.
+    pub fn direction(&self) -> Direction {
+        if self.requesttype() & Direction::In as u8 == 0 {
+            Direction::Out
+        } else {
+            Direction::In
+        }
+    }
+
+    /// Returns the `bRequestType` field.
+    pub fn requesttype(&self) -> u8 {
+        self.0.bRequestType
+    }
+
+    /// Returns the `bRequest` field.
+    pub fn request(&self) -> u8 {
+        self.0.bRequest
+    }
+
+    /// Returns the `wValue` field (native endian).
+    pub fn value(&self) -> u16 {
+        u16::from_le(self.0.wValue)
+    }
+
+    /// Returns the `wIndex` field (native endian).
+    pub fn index(&self) -> u16 {
+        u16::from_le(self.0.wIndex)
+    }
+
+    /// Returns the `wLength` field (native endian).
+    pub fn length(&self) -> u16 {
+        u16::from_le(self.0.wLength)
+    }
+}
+
+/// USB data transfer direction for a control request.
+///
+/// Used in the `bRequestType` field of a SETUP packet
+/// (USB 2.0 spec §9.3, table 9-2).
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u8)]
+pub enum Direction {
+    /// Host-to-device.
+    Out = bindings::USB_DIR_OUT as u8,
+    /// Device-to-host.
+    In = bindings::USB_DIR_IN as u8,
+}
+
+/// USB request type for a control request.
+///
+/// Used in the `bmRequestType` field of a SETUP packet to distinguish
+/// standard, class, and vendor-specific requests.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u8)]
+pub enum Type {
+    /// Standard request defined by the USB specification.
+    Standard = bindings::USB_TYPE_STANDARD as u8,
+    /// Class-specific request defined by a USB class specification.
+    Class = bindings::USB_TYPE_CLASS as u8,
+    /// Vendor-specific request.
+    Vendor = bindings::USB_TYPE_VENDOR as u8,
+    /// Reserved for future use.
+    Reserved = bindings::USB_TYPE_RESERVED as u8,
+}
+
+/// USB setup packet request type (`bmRequestType`).
+///
+/// Encodes the direction, type, and recipient of a control request.
+pub struct RequestType(u8);
+
+/// USB request recipient for a control request.
+///
+/// Used in the `bmRequestType` field of a SETUP packet.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u8)]
+pub enum Recipient {
+    /// Recipient is the device.
+    Device = bindings::USB_RECIP_DEVICE as u8,
+    /// Recipient is an interface.
+    Interface = bindings::USB_RECIP_INTERFACE as u8,
+    /// Recipient is an endpoint.
+    Endpoint = bindings::USB_RECIP_ENDPOINT as u8,
+    /// None of the above.
+    Other = bindings::USB_RECIP_OTHER as u8,
+}
+
+impl RequestType {
+    /// Creates a [`RequestType`] from a direction, type, and recipient.
+    ///
+    /// The three fields are packed into a single `u8` per the USB
+    /// specification (USB 2.0 spec §9.3, table 9-2).
+    pub const fn new(dir: Direction, r#type: Type, recipient: Recipient) -> Self {
+        Self(dir as u8 | r#type as u8 | recipient as u8)
+    }
+}

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions
  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
  2026-07-13 13:22   ` Danilo Krummrich
  2026-07-12 21:08 ` [RFC PATCH 3/4] rust: usb: add urb abstraction with control and isochronous support Colin Braun
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 16+ messages in thread
From: Colin Braun @ 2026-07-12 21:07 UTC (permalink / raw)
  To: Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Mauro Carvalho Chehab,
	Alan Stern, Mathias Nyman
  Cc: linux-kernel, rust-for-linux, linux-usb, linux-media, Colin Braun

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


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 3/4] rust: usb: add urb abstraction with control and isochronous support
  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 ` [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions Colin Braun
@ 2026-07-12 21:08 ` Colin Braun
  2026-07-12 21:08 ` [RFC PATCH 4/4] media: add gv-usb2 audio capture driver Colin Braun
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 16+ messages in thread
From: Colin Braun @ 2026-07-12 21:08 UTC (permalink / raw)
  To: Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Mauro Carvalho Chehab,
	Alan Stern, Mathias Nyman
  Cc: linux-kernel, rust-for-linux, linux-usb, linux-media, Colin Braun

Add abstractions for the USB Request Block (URB).

Reiterating the goals and how they are achieved:

1. No `unsafe` needed in driver code. This means providing a way for a
   driver to safely access private data sent with the URB.

- This is accomplished using a trampoline-like mechanism to reconstruct
a valid Urb, allowing us to access a driver's passed completion callback
and provide it the URB result.

2. Drivers are forced to handle the URB status in their completion
   callback before accessing the URB data.

- This is accomplished by wrapping the completed URB in a struct such
that the URB status must be checked before it transitions to a type
where the data is accessible.

3. Dropping an URB ensures it is not in-flight and frees its resources.

- This is accomplished by creating a custom `Drop` implementation that
kills the URB and performs the necessary actions to free its resources.

4. The URB can be safely resubmitted from the completion callback.

- This is trivial, just a resubmit method that consumes the URB to avoid
multiple resubmits or accessing data after submission.

The typestate pattern is used to flag an urb handle as either idle or
submitted. Once a driver submits an urb with an idle handle, the handle
moves to an active state that the driver must hold (otherwise its
`Drop` will run, killing the URB and freeing its resources).

Pipe and urb creation functions are created to support future bulk,
interrupt, and control requests.

Signed-off-by: Colin Braun <colin.braun.cl@gmail.com>
---
 include/linux/usb.h |   4 +
 rust/kernel/usb.rs  | 709 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 711 insertions(+), 2 deletions(-)

diff --git a/include/linux/usb.h b/include/linux/usb.h
index 1da4ad1610bc..c588d73c1592 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -1513,6 +1513,9 @@ typedef void (*usb_complete_t)(struct urb *);
  * @complete: Completion handler. This URB is passed as the parameter to the
  *	completion function.  The completion function may then do what
  *	it likes with the URB, including resubmitting or freeing it.
+ * @rust_complete: Completion handler for Rust drivers. This is necessary to
+ *	allow Rust drivers to create completion handlers using the Rust ABI,
+ *	avoiding the need for `extern "C"`.
  * @iso_frame_desc: Used to provide arrays of ISO transfer buffers and to
  *	collect the transfer status for each buffer.
  *
@@ -1663,6 +1666,7 @@ struct urb {
 	int error_count;		/* (return) number of ISO errors */
 	void *context;			/* (in) context for completion */
 	usb_complete_t complete;	/* (in) completion routine */
+	void *rust_complete;		/* (in) rust completion routine */
 	struct usb_iso_packet_descriptor iso_frame_desc[];
 					/* (in) ISO ONLY */
 };
diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs
index 21dddc735bdf..f4a3d7c0ef6c 100644
--- a/rust/kernel/usb.rs
+++ b/rust/kernel/usb.rs
@@ -18,9 +18,15 @@
         to_result, //
     },
     prelude::*,
-    sync::aref::AlwaysRefCounted,
+    sync::{
+        aref::AlwaysRefCounted,
+        Arc,
+        ArcBorrow, //
+    },
+    time::Delta,
     types::Opaque,
     usb::ch9::{
+        CtrlRequest,
         Direction,
         EndpointDescriptor,
         InterfaceClass,
@@ -34,7 +40,11 @@
         offset_of,
         MaybeUninit, //
     },
-    ptr::NonNull,
+    ops::Deref,
+    ptr::{
+        self,
+        NonNull, //
+    },
     slice, //
 };
 
@@ -589,6 +599,645 @@ unsafe impl Send for Interface {}
 // allow any mutation through a shared reference.
 unsafe impl Sync for Interface {}
 
+crate::impl_flags!(
+    /// URB transfer flags.
+    ///
+    /// These correspond to the `URB_*` constants in `include/linux/usb.h`.
+    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
+    pub struct TransferFlags(u32);
+
+    /// Represents a single URB transfer flag.
+    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+    pub enum TransferFlag {
+        /// Short packet flag: return an error if the packet is shorter than
+        /// expected.
+        ShortNotOk = bindings::URB_SHORT_NOT_OK,
+        /// Isochronous ASAP flag: schedule the isochronous transfer as soon as
+        /// possible.
+        IsoAsap = bindings::URB_ISO_ASAP,
+        /// Do not perform a DMA mapping for the transfer buffer.
+        NoTransferDmaMap = bindings::URB_NO_TRANSFER_DMA_MAP,
+        /// Send a zero-length packet at the end of the transfer.
+        ZeroPacket = bindings::URB_ZERO_PACKET,
+        /// Do not interrupt the CPU when the URB completes.
+        NoInterrupt = bindings::URB_NO_INTERRUPT,
+    }
+);
+
+/// A USB pipe encoding endpoint type, direction, device address, and
+/// endpoint number into a single `u32`.
+///
+/// Pipe encoding follows the kernel's `PIPE_*` macros used by the USB
+/// core for control, bulk, isochronous, and interrupt transfers.
+#[derive(Clone, Copy)]
+pub struct Pipe(u32);
+
+impl Pipe {
+    /// Create a host-to-device (OUT) control pipe (endpoint 0).
+    pub fn new_send_control_pipe(dev: &Device) -> Self {
+        Self(bindings::PIPE_CONTROL << 30 | dev.devnum() << 8)
+    }
+
+    /// Create a device-to-host (IN) control pipe (endpoint 0).
+    pub fn new_receive_control_pipe(dev: &Device) -> Self {
+        Self(bindings::PIPE_CONTROL << 30 | dev.devnum() << 8 | bindings::USB_DIR_IN)
+    }
+
+    /// Create a device-to-host (IN) isochronous pipe.
+    pub fn new_receive_isoc_pipe(dev: &Device, endpoint: &HostEndpoint) -> Self {
+        Self(
+            bindings::PIPE_ISOCHRONOUS << 30
+                | dev.devnum() << 8
+                | u32::from(endpoint.endpoint_number()) << 15
+                | bindings::USB_DIR_IN,
+        )
+    }
+
+    /// Create a host-to-device (OUT) isochronous pipe.
+    pub fn new_send_isoc_pipe(dev: &Device, endpoint: &HostEndpoint) -> Self {
+        Self(
+            bindings::PIPE_ISOCHRONOUS << 30
+                | dev.devnum() << 8
+                | u32::from(endpoint.endpoint_number()) << 15,
+        )
+    }
+
+    /// Create a host-to-device (OUT) bulk pipe.
+    pub fn new_send_bulk_pipe(dev: &Device, endpoint: &HostEndpoint) -> Self {
+        Self(
+            bindings::PIPE_BULK << 30
+                | dev.devnum() << 8
+                | u32::from(endpoint.endpoint_number()) << 15,
+        )
+    }
+
+    /// Create a device-to-host (IN) bulk pipe.
+    pub fn new_receive_bulk_pipe(dev: &Device, endpoint: &HostEndpoint) -> Self {
+        Self(
+            bindings::PIPE_BULK << 30
+                | dev.devnum() << 8
+                | u32::from(endpoint.endpoint_number()) << 15
+                | bindings::USB_DIR_IN,
+        )
+    }
+
+    /// Create a host-to-device (OUT) interrupt pipe.
+    pub fn new_send_int_pipe(dev: &Device, endpoint: &HostEndpoint) -> Self {
+        Self(
+            bindings::PIPE_INTERRUPT << 30
+                | dev.devnum() << 8
+                | u32::from(endpoint.endpoint_number()) << 15,
+        )
+    }
+
+    /// Create a device-to-host (IN) interrupt pipe.
+    pub fn new_receive_int_pipe(dev: &Device, endpoint: &HostEndpoint) -> Self {
+        Self(
+            bindings::PIPE_INTERRUPT << 30
+                | dev.devnum() << 8
+                | u32::from(endpoint.endpoint_number()) << 15
+                | bindings::USB_DIR_IN,
+        )
+    }
+}
+
+/// A single isochronous packet descriptor within an URB.
+///
+/// Wraps `struct usb_iso_packet_descriptor` from the C USB core.
+#[repr(transparent)]
+pub struct IsoPacketDescriptor(bindings::usb_iso_packet_descriptor);
+
+impl IsoPacketDescriptor {
+    /// Returns the offset of the packet's data within the transfer buffer.
+    pub fn offset(&self) -> u32 {
+        self.0.offset
+    }
+
+    /// Returns the length of the packet in bytes.
+    pub fn length(&self) -> u32 {
+        self.0.length
+    }
+
+    /// Returns the actual number of bytes transferred in this packet.
+    ///
+    /// Valid only after the URB completes.
+    pub fn actual_length(&self) -> u32 {
+        self.0.actual_length
+    }
+
+    /// Returns the per-packet completion status.
+    ///
+    /// Valid only after the URB completes.
+    pub fn status(&self) -> i32 {
+        self.0.status
+    }
+}
+
+/// Trait implemented by all URB state marker types.
+///
+/// Each state specifies pre-cleanup behaviour that runs before the
+/// underlying allocation is freed.
+pub trait UrbState {
+    /// Called before the URB allocation is freed.
+    fn pre_drop(urb: &mut bindings::urb);
+}
+
+/// Marker type for an idle (unsubmitted) URB.
+pub struct Idle;
+/// Marker type for an active (submitted, in-flight) URB.
+pub struct Active;
+
+impl UrbState for Idle {
+    fn pre_drop(_urb: &mut bindings::urb) {}
+}
+impl UrbState for Active {
+    fn pre_drop(urb: &mut bindings::urb) {
+        // SAFETY: `urb` is a valid pointer to an initialized `struct urb`.
+        unsafe { bindings::usb_kill_urb(urb) }
+    }
+}
+
+/// A USB Request Block (URB).
+///
+/// This structure wraps the C [`struct urb`] and provides a safe
+/// abstraction for USB transfers.
+///
+/// [`struct urb`]: https://www.kernel.org/doc/html/latest/driver-api/usb/usb.html#c.urb
+#[repr(transparent)]
+pub struct Urb<T>(Opaque<bindings::urb>, PhantomData<T>);
+
+impl<T> Urb<T> {
+    fn as_raw(&self) -> *mut bindings::urb {
+        self.0.get()
+    }
+
+    fn inner(&self) -> &bindings::urb {
+        // SAFETY: The type invariants guarantee that `self.0` wraps a valid
+        // `struct urb`.
+        unsafe { &*self.as_raw() }
+    }
+
+    fn status(&self) -> i32 {
+        self.inner().status
+    }
+
+    /// Returns a borrow of the driver-private context data, if any.
+    pub fn context(&self) -> Option<ArcBorrow<'_, T>> {
+        let context = self.inner().context;
+        if context.is_null() {
+            None
+        } else {
+            // SAFETY: `context` was initialized by `Arc::into_raw` in `init_common`.
+            Some(unsafe { ArcBorrow::from_raw(context.cast()) })
+        }
+    }
+}
+
+/// A handle to a [`struct urb`] allocated via `usb_alloc_urb`.
+///
+/// Created by [`Urb::new_bulk`], [`Urb::new_isoc`], etc. The URB is
+/// owned by this handle — dropping the handle frees the allocation.
+///
+/// Use [`Urb::submit`] to transition to [`UrbHandle<T, Active>`].
+pub struct UrbHandle<T, S: UrbState = Idle> {
+    /// Pointer to the underlying C `struct urb`.
+    urb: NonNull<bindings::urb>,
+    /// State marker.
+    _state: PhantomData<S>,
+    /// Type of driver-private context data.
+    _ty: PhantomData<T>,
+}
+
+// SAFETY: The underlying urb is always reference-counted and can be released from any thread.
+unsafe impl<T> Send for UrbHandle<T, Active> {}
+
+impl<T, S: UrbState> Deref for UrbHandle<T, S> {
+    type Target = Urb<T>;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: `Urb<T>` is a `#[repr(transparent)]` wrapper of `struct urb`,
+        unsafe { &*(self.urb.as_ptr() as *const Urb<T>) }
+    }
+}
+
+impl<T, S: UrbState> Drop for UrbHandle<T, S> {
+    fn drop(&mut self) {
+        // SAFETY: `self.as_raw()` points to a valid, initialized C `struct urb`.
+        let urb: &mut bindings::urb = unsafe { &mut *self.as_raw() };
+        S::pre_drop(urb);
+
+        if !urb.context.is_null() {
+            // SAFETY: After `pre_drop` the URB is idle, so it is safe to
+            // reclaim the context data.
+            unsafe {
+                drop(Arc::from_raw(urb.context.cast::<T>()));
+            }
+        }
+
+        if !urb.setup_packet.is_null() {
+            // SAFETY: The setup packet was allocated via `KBox::into_raw` in
+            // `init_common` and `urb.setup_packet` is still valid.
+            unsafe {
+                drop(KBox::from_raw(urb.setup_packet.cast::<CtrlRequest>()));
+            }
+        }
+
+        if !urb.transfer_buffer.is_null() {
+            // SAFETY: The transfer buffer was allocated via `KBox::into_raw` in
+            // `init_common` and `urb.transfer_buffer` is still valid.
+            unsafe {
+                drop(KBox::from_raw(ptr::slice_from_raw_parts_mut(
+                    urb.transfer_buffer.cast::<u8>(),
+                    urb.transfer_buffer_length as usize,
+                )));
+            }
+        }
+
+        // SAFETY: `urb` points to a valid, initialized `struct urb`
+        // and is not in-flight.
+        unsafe { bindings::usb_free_urb(ptr::from_mut(urb)) };
+    }
+}
+
+/// A completed URB whose status must be checked before accessing data.
+///
+/// The driver receives this in its completion handler. Call
+/// [`check`](UrbResult::check) to verify the transfer succeeded.
+pub struct UrbResult<'a, T> {
+    /// The pinned URB reference delivered by the trampoline.
+    urb: Pin<&'a mut Urb<T>>,
+}
+
+impl<'a, T> Deref for UrbResult<'a, T> {
+    type Target = Urb<T>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.urb
+    }
+}
+
+impl<'a, T> UrbResult<'a, T> {
+    /// Re-submit the URB from a completion handler.
+    ///
+    /// Consumes this handle, transferring ownership to the kernel.
+    /// This is intentionally private, since a driver should always
+    /// check the result in the completion handler.
+    fn resubmit(self, mem_flags: kernel::alloc::Flags) -> Result {
+        // SAFETY: `self.urb.as_raw()` points to a valid, initialized C `struct urb`.
+        to_result(unsafe { bindings::usb_submit_urb(self.as_raw(), mem_flags.as_raw()) })
+    }
+
+    /// Check the completion status and grant access to the URB data.
+    pub fn check(&mut self) -> Result<UrbData<'_, T>> {
+        if self.status() != 0 {
+            Err(Error::from_errno(self.status()))
+        } else {
+            Ok(UrbData {
+                urb: self.urb.as_mut(),
+            })
+        }
+    }
+
+    /// Check the completion status, granting data access on success or
+    /// resubmitting the URB on failure.
+    pub fn check_or_resubmit(
+        self,
+        mem_flags: kernel::alloc::Flags,
+    ) -> Result<UrbData<'a, T>, Result> {
+        if self.status() != 0 {
+            Err(self.resubmit(mem_flags))
+        } else {
+            Ok(UrbData { urb: self.urb })
+        }
+    }
+}
+
+/// A successfully completed URB whose data is safe to read.
+pub struct UrbData<'a, T> {
+    /// The pinned URB reference.
+    urb: Pin<&'a mut Urb<T>>,
+}
+
+impl<'a, T> Deref for UrbData<'a, T> {
+    type Target = Urb<T>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.urb
+    }
+}
+
+impl<'a, T> UrbData<'a, T> {
+    /// Returns the number of bytes actually transferred.
+    ///
+    /// For isochronous URBs this is the sum of all packet
+    /// `actual_length` values.
+    pub fn actual_length(&self) -> u32 {
+        self.inner().actual_length
+    }
+
+    /// Returns the transfer buffer as a byte slice.
+    pub fn transfer_buffer(&self) -> &[u8] {
+        let urb = self.inner();
+        if urb.transfer_buffer.is_null() {
+            &[]
+        } else {
+            // SAFETY: The transfer buffer was set in `init_common`.
+            // The pointer and length are valid for the lifetime of the `Urb`.
+            unsafe {
+                slice::from_raw_parts(
+                    urb.transfer_buffer as *const u8,
+                    urb.transfer_buffer_length as usize,
+                )
+            }
+        }
+    }
+
+    /// Returns the ISO frame descriptors for this URB.
+    pub fn iso_frame_descs(&self) -> &[IsoPacketDescriptor] {
+        let urb = self.inner();
+
+        if urb.number_of_packets == 0 {
+            &[]
+        } else {
+            let data = urb.iso_frame_desc.as_ptr().cast::<IsoPacketDescriptor>();
+
+            // SAFETY: The `iso_frame_desc` flexible array was allocated as
+            // part of the `usb_alloc_urb` allocation. `number_of_packets`
+            // is the corresponding length.
+            unsafe { slice::from_raw_parts(data, urb.number_of_packets as usize) }
+        }
+    }
+
+    /// Extracts the payload data for a given ISO packet descriptor.
+    ///
+    /// Returns `Err` if the packet status is non-zero.
+    pub fn data_from_iso_packet_desc(
+        &self,
+        iso_packet_desc: &IsoPacketDescriptor,
+    ) -> Result<&[u8]> {
+        if iso_packet_desc.status() != 0 {
+            return Err(Error::from_errno(iso_packet_desc.status()));
+        }
+        let urb = self.inner();
+        // SAFETY: `iso_packet_desc.offset()` was computed in
+        // `init_common` and lies within the transfer buffer.
+        let data =
+            unsafe { (urb.transfer_buffer.cast::<u8>()).add(iso_packet_desc.offset() as usize) };
+
+        // SAFETY: After URB completion `actual_length()` reflects the
+        // valid bytes in the packet. The slice is within the transfer
+        // buffer allocation. The packet status was verified above.
+        unsafe {
+            Ok(slice::from_raw_parts(
+                data,
+                iso_packet_desc.actual_length() as usize,
+            ))
+        }
+    }
+
+    /// Re-submit the URB from a completion handler.
+    ///
+    /// Consumes this handle, transferring ownership to the kernel.
+    pub fn resubmit(self, mem_flags: kernel::alloc::Flags) -> Result {
+        // SAFETY: `self.as_raw()` points to a valid, initialized C `struct urb`.
+        to_result(unsafe { bindings::usb_submit_urb(self.as_raw(), mem_flags.as_raw()) })
+    }
+}
+
+/// Trampoline function to call safe completion handlers.
+///
+/// # Safety
+///
+/// `urb_ptr` must point to a valid, initialized `struct urb` whose
+/// `context` and `rust_complete` fields were set by [`Urb::init_common`].
+unsafe extern "C" fn urb_complete_trampoline<T>(urb_ptr: *mut bindings::urb) {
+    // SAFETY: `urb_ptr` is a valid pointer provided by the USB core.
+    // `rust_complete` was set to a `fn(UrbResult<'_, T>)` when initialized.
+    let complete: fn(UrbResult<'_, T>) = unsafe { core::mem::transmute((*urb_ptr).rust_complete) };
+    // SAFETY: `urb_ptr` points to a valid `struct urb`.
+    let urb = unsafe { &mut *urb_ptr.cast() };
+    // SAFETY: The data `urb` references is never moved.
+    let urb = unsafe { Pin::new_unchecked(urb) };
+    complete(UrbResult { urb });
+}
+
+impl<T> Urb<T> {
+    #[allow(clippy::too_many_arguments)]
+    fn init_common(
+        mem_flags: kernel::alloc::Flags,
+        intf: &Interface<device::Bound>,
+        pipe: Pipe,
+        setup_packet: Option<KBox<CtrlRequest>>,
+        transfer_buffer: Option<KBox<[u8]>>,
+        context_data: Option<Arc<T>>,
+        complete: fn(UrbResult<'_, T>),
+        number_of_packets: u32,
+        iso_packet_len: u16,
+        transfer_flags: TransferFlags,
+        interval: i32,
+    ) -> Result<Pin<UrbHandle<T, Idle>>> {
+        // SAFETY: `usb_alloc_urb` allocates a `struct urb` + ISO frame.
+        let urb_ptr =
+            unsafe { bindings::usb_alloc_urb(number_of_packets as c_int, mem_flags.as_raw()) };
+        if urb_ptr.is_null() {
+            return Err(ENOMEM);
+        }
+
+        // SAFETY: `urb_ptr` points to allocated and zero-initialized memory
+        // of the correct layout for `struct urb` + ISO tail.
+        let urb = unsafe { &mut *urb_ptr };
+
+        let dev: &Device<device::Bound> = intf.as_ref();
+
+        urb.complete = Some(urb_complete_trampoline::<T>);
+        urb.dev = dev.as_raw();
+        urb.pipe = pipe.0;
+        urb.number_of_packets = number_of_packets as c_int;
+        urb.transfer_flags = u32::from(transfer_flags);
+        urb.interval = interval;
+
+        // Set up ISO frame descriptors.
+        if number_of_packets > 0 {
+            // SAFETY: `urb_ptr` was allocated with `number_of_packets` ISO
+            // descriptors via `usb_alloc_urb`. `as_mut_slice` yields a valid
+            // mutable slice of that length.
+            let descs = unsafe { urb.iso_frame_desc.as_mut_slice(number_of_packets as usize) };
+            for (i, desc) in descs.iter_mut().enumerate() {
+                let pkt_len = u32::from(iso_packet_len);
+                desc.offset = (i as u32) * pkt_len;
+                desc.length = pkt_len;
+            }
+        }
+
+        if let Some(sp) = setup_packet {
+            urb.setup_packet = KBox::into_raw(sp).cast::<u8>();
+        }
+
+        if let Some(tb) = transfer_buffer {
+            let len = tb.len();
+            urb.transfer_buffer_length = len as u32;
+            urb.transfer_buffer = KBox::into_raw(tb).cast::<core::ffi::c_void>();
+        }
+
+        if let Some(data) = context_data {
+            urb.context = Arc::into_raw(data).cast_mut().cast();
+        }
+
+        urb.rust_complete = complete as *mut core::ffi::c_void;
+
+        let urb_handle = UrbHandle {
+            // SAFETY: `urb_ptr` is guaranteed non-null by the null check above.
+            urb: unsafe { NonNull::new_unchecked(urb_ptr) },
+            _state: PhantomData,
+            _ty: PhantomData,
+        };
+
+        // SAFETY: `urb_handle.urb` is never moved.
+        Ok(unsafe { Pin::new_unchecked(urb_handle) })
+    }
+
+    /// Submit the URB for execution.
+    ///
+    /// On success the caller receives an [`UrbHandle<T, Active>`] which
+    /// holds the resources for the in-flight URB. Dropping it cancels the
+    /// URB and frees the allocation.
+    pub fn submit(
+        self: Pin<UrbHandle<T, Idle>>,
+        mem_flags: kernel::alloc::Flags,
+    ) -> Result<UrbHandle<T, Active>> {
+        // SAFETY: The urb pointed to is not moved.
+        let handle = unsafe { Pin::into_inner_unchecked(self) };
+        // SAFETY: `handle.as_raw()` points to a valid, initialized `struct urb`.
+        let result = unsafe { bindings::usb_submit_urb(handle.as_raw(), mem_flags.as_raw()) };
+
+        if result == 0 {
+            let urb = handle.urb;
+            core::mem::forget(handle);
+            Ok(UrbHandle {
+                urb,
+                _state: PhantomData,
+                _ty: PhantomData,
+            })
+        } else {
+            Err(Error::from_errno(result))
+        }
+    }
+
+    /// Creates a new bulk URB.
+    pub fn new_bulk(
+        mem_flags: kernel::alloc::Flags,
+        intf: &Interface<device::Bound>,
+        pipe: Pipe,
+        transfer_buffer: KBox<[u8]>,
+        context_data: Option<Arc<T>>,
+        complete: fn(UrbResult<'_, T>),
+        transfer_flags: TransferFlags,
+    ) -> Result<Pin<UrbHandle<T, Idle>>> {
+        Self::init_common(
+            mem_flags,
+            intf,
+            pipe,
+            None,
+            Some(transfer_buffer),
+            context_data,
+            complete,
+            0,
+            0,
+            transfer_flags,
+            0,
+        )
+    }
+
+    /// Creates a new interrupt URB.
+    #[allow(clippy::too_many_arguments)]
+    pub fn new_int(
+        mem_flags: kernel::alloc::Flags,
+        intf: &Interface<device::Bound>,
+        pipe: Pipe,
+        transfer_buffer: KBox<[u8]>,
+        context_data: Option<Arc<T>>,
+        complete: fn(UrbResult<'_, T>),
+        transfer_flags: TransferFlags,
+        interval: i32,
+    ) -> Result<Pin<UrbHandle<T, Idle>>> {
+        Self::init_common(
+            mem_flags,
+            intf,
+            pipe,
+            None,
+            Some(transfer_buffer),
+            context_data,
+            complete,
+            0,
+            0,
+            transfer_flags,
+            interval,
+        )
+    }
+
+    /// Creates a new control URB.
+    #[allow(clippy::too_many_arguments)]
+    pub fn new_ctrl(
+        mem_flags: kernel::alloc::Flags,
+        intf: &Interface<device::Bound>,
+        pipe: Pipe,
+        setup_packet: KBox<CtrlRequest>,
+        transfer_buffer: Option<KBox<[u8]>>,
+        context_data: Option<Arc<T>>,
+        complete: fn(UrbResult<'_, T>),
+        transfer_flags: TransferFlags,
+    ) -> Result<Pin<UrbHandle<T, Idle>>> {
+        Self::init_common(
+            mem_flags,
+            intf,
+            pipe,
+            Some(setup_packet),
+            transfer_buffer,
+            context_data,
+            complete,
+            0,
+            0,
+            transfer_flags,
+            0,
+        )
+    }
+
+    /// Creates a new isochronous URB.
+    #[allow(clippy::too_many_arguments)]
+    pub fn new_isoc(
+        mem_flags: kernel::alloc::Flags,
+        intf: &Interface<device::Bound>,
+        pipe: Pipe,
+        transfer_buffer: KBox<[u8]>,
+        context_data: Option<Arc<T>>,
+        complete: fn(UrbResult<'_, T>),
+        number_of_packets: u32,
+        iso_packet_len: u16,
+        transfer_flags: TransferFlags,
+        interval: i32,
+    ) -> Result<Pin<UrbHandle<T, Idle>>> {
+        // Reject URBs whose buffer is too small to hold all packets.
+        let needed = (number_of_packets as usize).saturating_mul(iso_packet_len as usize);
+        if transfer_buffer.len() < needed {
+            return Err(EINVAL);
+        }
+
+        Self::init_common(
+            mem_flags,
+            intf,
+            pipe,
+            None,
+            Some(transfer_buffer),
+            context_data,
+            complete,
+            number_of_packets,
+            iso_packet_len,
+            transfer_flags,
+            interval,
+        )
+    }
+}
+
 /// A USB device.
 ///
 /// This structure represents the Rust abstraction for a C [`struct usb_device`].
@@ -611,6 +1260,17 @@ impl<Ctx: device::DeviceContext> Device<Ctx> {
     fn as_raw(&self) -> *mut bindings::usb_device {
         self.0.get()
     }
+
+    fn inner(&self) -> &bindings::usb_device {
+        // SAFETY: The type invariants guarantee that `self.0` wraps a valid
+        // `struct usb_device`.
+        unsafe { &*self.as_raw() }
+    }
+
+    /// Returns the USB device number assigned by the bus.
+    fn devnum(&self) -> u32 {
+        self.inner().devnum as u32
+    }
 }
 
 impl Device<device::Bound> {
@@ -625,6 +1285,51 @@ pub fn set_interface(&self, interface: u8, altsetting: u8) -> Result {
             bindings::usb_set_interface(self.as_raw(), i32::from(interface), i32::from(altsetting))
         })
     }
+
+    /// Send a USB control message synchronously.
+    ///
+    /// Wraps `usb_control_msg`. The pipe direction is inferred from the setup
+    /// packet's [`Direction`]. The optional `data` buffer is
+    /// written to for IN transfers or read from for OUT transfers.
+    ///
+    /// Returns the number of bytes transferred on success.
+    pub fn control_msg(
+        &self,
+        setup: &CtrlRequest,
+        data: Option<&mut [u8]>,
+        timeout: Delta,
+    ) -> Result<i32> {
+        let pipe = match setup.direction() {
+            Direction::In => Pipe::new_receive_control_pipe(self),
+            Direction::Out => Pipe::new_send_control_pipe(self),
+        };
+        let (buf, len) = match data {
+            Some(d) => (d.as_mut_ptr().cast::<core::ffi::c_void>(), d.len() as u16),
+            None => (ptr::null_mut(), 0),
+        };
+        let timeout_ms = timeout.as_millis() as i32;
+
+        // SAFETY: `self.as_raw()` returns a valid `struct usb_device` pointer.
+        let ret = unsafe {
+            bindings::usb_control_msg(
+                self.as_raw(),
+                pipe.0,
+                setup.request(),
+                setup.requesttype(),
+                setup.value(),
+                setup.index(),
+                buf,
+                len,
+                timeout_ms,
+            )
+        };
+
+        if ret >= 0 {
+            Ok(ret)
+        } else {
+            Err(Error::from_errno(ret))
+        }
+    }
 }
 
 // SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 4/4] media: add gv-usb2 audio capture driver
  2026-07-12 21:07 [RFC PATCH 0/4] rust: usb: add usb request block abstractions and a user Colin Braun
                   ` (2 preceding siblings ...)
  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 ` Colin Braun
  2026-07-13 14:44   ` Danilo Krummrich
  2026-07-13 13:22 ` [RFC PATCH 0/4] rust: usb: add usb request block abstractions and a user Danilo Krummrich
  2026-07-13 13:53 ` Daniel Almeida
  5 siblings, 1 reply; 16+ messages in thread
From: Colin Braun @ 2026-07-12 21:08 UTC (permalink / raw)
  To: Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Mauro Carvalho Chehab,
	Alan Stern, Mathias Nyman
  Cc: linux-kernel, rust-for-linux, linux-usb, linux-media, Colin Braun

Add a Rust driver for the I-O Data GV-USB2 Composite-USB Video
Capture Device. Leverages the USB abstractions from patches 1-3.

The GV-USB2 contains an STK1150, a USB bridge microchip for converting
old analog video signals into digital data. The STK1160's datasheet
is available, but the same is not true for the STK1150. However, it
seems that most of the registers are the same as the STK1160's.

This implementation demonstrates the ability to send and receive data
via control and isochronous USB transfers. Due to a lack of V4L2 and
ALSA abstractions, this initial implementation only exposes data from
an audio endpoint via debugfs. I would like to expand on this driver
later.

Signed-off-by: Colin Braun <colin.braun.cl@gmail.com>
---
 drivers/media/usb/Kconfig            |   1 +
 drivers/media/usb/Makefile           |   1 +
 drivers/media/usb/gv-usb2/Kconfig    |   9 +
 drivers/media/usb/gv-usb2/Makefile   |   1 +
 drivers/media/usb/gv-usb2/driver.rs  | 361 +++++++++++++++++++++++++++++++++++
 drivers/media/usb/gv-usb2/gv_usb2.rs |  16 ++
 drivers/media/usb/gv-usb2/regs.rs    |  25 +++
 7 files changed, 414 insertions(+)

diff --git a/drivers/media/usb/Kconfig b/drivers/media/usb/Kconfig
index 813171d25ac5..9867d0dc5626 100644
--- a/drivers/media/usb/Kconfig
+++ b/drivers/media/usb/Kconfig
@@ -14,6 +14,7 @@ if MEDIA_CAMERA_SUPPORT
 	comment "Webcam devices"
 
 source "drivers/media/usb/gspca/Kconfig"
+source "drivers/media/usb/gv-usb2/Kconfig"
 source "drivers/media/usb/pwc/Kconfig"
 source "drivers/media/usb/s2255/Kconfig"
 source "drivers/media/usb/usbtv/Kconfig"
diff --git a/drivers/media/usb/Makefile b/drivers/media/usb/Makefile
index 6d171beea20d..f69aaec671ca 100644
--- a/drivers/media/usb/Makefile
+++ b/drivers/media/usb/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_VIDEO_AU0828) += au0828/
 obj-$(CONFIG_VIDEO_CX231XX) += cx231xx/
 obj-$(CONFIG_VIDEO_EM28XX) += em28xx/
 obj-$(CONFIG_VIDEO_GO7007) += go7007/
+obj-$(CONFIG_VIDEO_GVUSB2) += gv-usb2/
 obj-$(CONFIG_VIDEO_HDPVR) += hdpvr/
 obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2/
 obj-$(CONFIG_VIDEO_STK1160) += stk1160/
diff --git a/drivers/media/usb/gv-usb2/Kconfig b/drivers/media/usb/gv-usb2/Kconfig
new file mode 100644
index 000000000000..755a669bba26
--- /dev/null
+++ b/drivers/media/usb/gv-usb2/Kconfig
@@ -0,0 +1,9 @@
+config VIDEO_GVUSB2
+	tristate "GV-USB2 audio/video capture device driver"
+	depends on VIDEO_DEV && RUST
+	help
+	  Rust driver for the I-O Data GV-USB2 Composite-USB Video Capture
+	  Device.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called gv_usb2.
diff --git a/drivers/media/usb/gv-usb2/Makefile b/drivers/media/usb/gv-usb2/Makefile
new file mode 100644
index 000000000000..7cc0f83a11c1
--- /dev/null
+++ b/drivers/media/usb/gv-usb2/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_VIDEO_GVUSB2) += gv_usb2.o
diff --git a/drivers/media/usb/gv-usb2/driver.rs b/drivers/media/usb/gv-usb2/driver.rs
new file mode 100644
index 000000000000..a8ab03e9a133
--- /dev/null
+++ b/drivers/media/usb/gv-usb2/driver.rs
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use crate::regs;
+use kernel::{
+    debugfs::{
+        self,
+        BinaryWriter, //
+    },
+    device,
+    prelude::*,
+    sync::{
+        new_spinlock,
+        Arc,
+        SpinLock, //
+    },
+    time::Delta,
+    usb,
+};
+
+/// The number of isochronous urbs to submit.
+const NUM_URBS: usize = 4;
+
+/// The expected max packet size for audio.
+const MAX_AUDIO_PACKET_SIZE: u16 = 256;
+
+/// The GV-USB2 USB driver struct.
+///
+/// Created during probe and stored as the driver's private data.
+pub(crate) struct GvUsb2Driver {
+    /// Shared audio state.
+    _audio: Arc<AudioState>,
+    /// Pool of isochronous URBs submitted for audio capture.
+    _urbs: [Option<usb::UrbHandle<AudioState, usb::Active>>; NUM_URBS],
+    /// The debugfs file exposing captured audio to userspace.
+    _capture_file: Pin<KBox<debugfs::File<IsocCapture>>>,
+    /// The debugfs directory node.
+    _debugfs_dir: debugfs::Dir,
+}
+
+/// Shared state for audio capture.
+#[pin_data]
+struct AudioState {
+    /// Ring buffer for streaming audio data from URBs to userspace.
+    #[pin]
+    ring: SpinLock<AudioRingBuffer>,
+}
+
+/// A ring buffer for audio data.
+///
+/// Used to transfer audio data from the URB completions to userspace
+/// via debugfs.
+struct AudioRingBuffer {
+    /// Audio buffer, sized to a power of two.
+    buf: KBox<[u8]>,
+    /// Producer index.
+    head: u32,
+    /// Consumer index.
+    tail: u32,
+    /// Mask to handle ring buffer wrap-around. Must be capacity - 1.
+    mask: u32,
+}
+
+impl AudioRingBuffer {
+    /// Write data into the ring buffer.
+    ///
+    /// Returns the number of bytes written.
+    fn write(&mut self, data: &[u8]) -> usize {
+        let capacity = self.mask as usize + 1;
+        let fill = self.head.wrapping_sub(self.tail) as usize;
+
+        if fill >= capacity {
+            return 0;
+        }
+
+        let to_write = core::cmp::min(capacity - fill, data.len());
+        if to_write == 0 {
+            return 0;
+        }
+
+        let mask = self.mask as usize;
+        let head_idx = self.head as usize & mask;
+        let space_to_end = capacity - head_idx;
+
+        if to_write <= space_to_end {
+            self.buf[head_idx..head_idx + to_write].copy_from_slice(&data[..to_write]);
+        } else {
+            let (first, rest) = data.split_at(space_to_end);
+            self.buf[head_idx..capacity].copy_from_slice(first);
+            self.buf[..to_write - space_to_end].copy_from_slice(rest);
+        }
+
+        self.head = self.head.wrapping_add(to_write as u32);
+
+        to_write
+    }
+
+    /// Read data from the ring buffer.
+    ///
+    /// Returns the number of bytes copied into `buf`.
+    fn read(&mut self, buf: &mut [u8]) -> usize {
+        let capacity = self.mask as usize + 1;
+        let fill = self.head.wrapping_sub(self.tail) as usize;
+
+        if fill == 0 {
+            return 0;
+        }
+
+        let to_read = core::cmp::min(fill, buf.len());
+        let mask = self.mask as usize;
+        let tail_idx = self.tail as usize & mask;
+        let space_to_end = capacity - tail_idx;
+
+        if to_read <= space_to_end {
+            buf[..to_read].copy_from_slice(&self.buf[tail_idx..tail_idx + to_read]);
+        } else {
+            let first_len = space_to_end;
+            let second_len = to_read - space_to_end;
+            buf[..first_len].copy_from_slice(&self.buf[tail_idx..capacity]);
+            buf[first_len..to_read].copy_from_slice(&self.buf[..second_len]);
+        }
+
+        self.tail = self.tail.wrapping_add(to_read as u32);
+
+        to_read
+    }
+}
+
+/// Debugfs reader that drains the audio ring buffer.
+struct IsocCapture {
+    /// Shared audio state whose ring buffer is read on each `read()` call.
+    state: Arc<AudioState>,
+}
+
+impl BinaryWriter for IsocCapture {
+    fn write_to_slice(
+        &self,
+        writer: &mut kernel::uaccess::UserSliceWriter,
+        offset: &mut kernel::fs::file::Offset,
+    ) -> Result<usize> {
+        let mut buf = [0u8; 4096];
+        let n = match self.state.ring.try_lock() {
+            Some(mut guard) => guard.read(&mut buf),
+            None => 0,
+        };
+        if n == 0 {
+            return Ok(0);
+        }
+        writer.write_slice_file(&buf[..n], offset)
+    }
+}
+
+kernel::usb_device_table!(
+    USB_TABLE,
+    MODULE_USB_TABLE,
+    <GvUsb2Driver as usb::Driver>::IdInfo,
+    [(usb::DeviceId::from_id(0x04BB, 0x0532), ()),]
+);
+
+impl usb::Driver for GvUsb2Driver {
+    type IdInfo = ();
+    type Data<'bound> = Self;
+    const ID_TABLE: usb::IdTable<Self::IdInfo> = &USB_TABLE;
+
+    /// Probe the GV-USB2 device.
+    ///
+    /// Matches only audio interface 2 (isochronous endpoint 4).
+    /// Switches to altsetting 1, initializes the ADC, submits four isochronous
+    /// URBs, and exposes the audio stream via debugfs.
+    fn probe<'bound>(
+        intf: &'bound usb::Interface<device::Core<'_>>,
+        _id: &usb::DeviceId,
+        _info: &'bound Self::IdInfo,
+    ) -> impl PinInit<Self, Error> + 'bound {
+        let dev: &device::Device<device::Core<'_>> = intf.as_ref();
+
+        dev_dbg!(
+            dev,
+            "INTERFACE {}: num altsettings = {}\n",
+            intf.cur_altsetting().number(),
+            intf.altsettings().len(),
+        );
+        for endpoint in intf.cur_altsetting().endpoints() {
+            dev_dbg!(
+                dev,
+                "  endpoint dir: {:?}, endpoint type: {:?}\n",
+                endpoint.endpoint_dir(),
+                endpoint.endpoint_type()
+            );
+        }
+
+        if intf.cur_altsetting().class() == usb::ch9::InterfaceClass::AUDIO {
+            // Audio class is supported by snd-usb-audio, reject the interface.
+            return Err(ENODEV);
+        }
+
+        // Interface 2 is the audio one for gv-usb2, skip the others
+        if intf.cur_altsetting().number() != 2 {
+            return Err(ENODEV);
+        }
+
+        // Switch to altsetting 1 (full-bandwidth).
+        intf.set_interface(1)?;
+
+        let audio_ep = find_audio_endpoint(intf)?;
+        dev_dbg!(
+            dev,
+            "Found audio endpoint: {}\n",
+            audio_ep.endpoint_number()
+        );
+
+        let buf = KBox::new([0; 32_768], GFP_KERNEL)?;
+        let audio: Arc<AudioState> = Arc::pin_init(
+            pin_init!(AudioState {
+                ring <- new_spinlock!(AudioRingBuffer {
+                    buf,
+                    head: 0,
+                    tail: 0,
+                    mask: 32_767,
+                }),
+            }),
+            GFP_KERNEL,
+        )?;
+
+        // Initialize the audio ADC: GPIO, AC97 disable, I2S enable.
+        reset_adc(intf)?;
+
+        let urbs = submit_audio_urbs(intf, audio_ep, &audio)?;
+
+        let dir = debugfs::Dir::new(c"gv-usb2");
+        let file = KBox::pin_init(
+            dir.read_binary_file(
+                c"audio_raw",
+                IsocCapture {
+                    state: audio.clone(),
+                },
+            ),
+            GFP_KERNEL,
+        )?;
+
+        let driver = Self {
+            _audio: audio,
+            _urbs: urbs,
+            _capture_file: file,
+            _debugfs_dir: dir,
+        };
+
+        dev_dbg!(dev, "GV-USB2 successfully initialized\n");
+        Ok(driver)
+    }
+
+    /// Disconnect the GV-USB2 device.
+    ///
+    /// All resources are cleaned up when dropped.
+    fn disconnect<'bound>(intf: &'bound usb::Interface<device::Core<'_>>, _data: Pin<&Self>) {
+        let dev: &device::Device<device::Core<'_>> = intf.as_ref();
+        dev_dbg!(dev, "GV-USB2 disconnected\n");
+    }
+}
+
+/// Write a vendor-specific control register on the GV-USB2 device.
+///
+/// Uses a vendor-type control request (`REQ_WRITE_REG`) to write the
+/// given `value` to the given `reg` address.
+fn write_reg(intf: &usb::Interface<device::Bound>, reg: u16, value: u8) -> Result {
+    let req = usb::ch9::CtrlRequest::new(
+        usb::ch9::RequestType::new(
+            usb::ch9::Direction::Out,
+            usb::ch9::Type::Vendor,
+            usb::ch9::Recipient::Device,
+        ),
+        regs::REQ_WRITE_REG,
+        u16::from(value),
+        reg,
+        0,
+    );
+    let dev: &usb::Device<device::Bound> = intf.as_ref();
+    dev.control_msg(&req, None, Delta::from_millis(1_000))
+        .map(|_| ())
+}
+
+/// Initialize the audio ADC on the GV-USB2.
+///
+/// Configures GPIO direction and output values, disables the AC97 codec,
+/// and enables the I2S interface.
+fn reset_adc(intf: &usb::Interface<device::Bound>) -> Result {
+    // GPIO: bits 4+5 as outputs; SOUND_ENABLE=0 (active), SDA_RESET=1 (inactive)
+    write_reg(intf, regs::GPIO_CTRL + 2, 0x30)?;
+    write_reg(intf, regs::GPIO_CTRL, 0x10)?;
+    // Disable AC97, enable I2S
+    write_reg(intf, regs::AC97_CTRL, 0x00)?;
+    write_reg(intf, regs::I2S_CTRL, 0x01)
+}
+
+/// Completion callback for isochronous audio URBs.
+///
+/// Copies received audio data into the ring buffer and resubmits the URB.
+fn audio_complete(result: usb::UrbResult<'_, AudioState>) {
+    let Ok(urb_data) = result.check_or_resubmit(GFP_ATOMIC) else {
+        return;
+    };
+
+    if let Some(state) = urb_data.context() {
+        let mut ring_guard = state.ring.lock();
+        for desc in urb_data.iso_frame_descs() {
+            if let Ok(data) = urb_data.data_from_iso_packet_desc(desc) {
+                ring_guard.write(data);
+            }
+        }
+    }
+
+    let _ = urb_data.resubmit(GFP_ATOMIC);
+}
+
+/// Create and submit isochronous URBs for audio capture.
+///
+/// URBs are resubmitted continuously by `audio_complete`.
+fn submit_audio_urbs(
+    intf: &usb::Interface<device::Bound>,
+    ep: &usb::HostEndpoint,
+    audio: &Arc<AudioState>,
+) -> Result<[Option<usb::UrbHandle<AudioState, usb::Active>>; NUM_URBS]> {
+    const PACKETS_PER_URB: u32 = 8;
+    const URB_BUF_SIZE: usize = (PACKETS_PER_URB as usize) * (MAX_AUDIO_PACKET_SIZE as usize);
+
+    let dev: &usb::Device<device::Bound> = intf.as_ref();
+    let pipe = usb::Pipe::new_receive_isoc_pipe(dev, ep);
+    let mut urbs = [const { None }; NUM_URBS];
+    for active_urb in urbs.iter_mut().take(NUM_URBS) {
+        let buf = KBox::new([0u8; URB_BUF_SIZE], GFP_KERNEL)?;
+        let urb_handle = usb::Urb::<AudioState>::new_isoc(
+            GFP_KERNEL,
+            intf,
+            pipe,
+            buf,
+            Some(audio.clone()),
+            audio_complete,
+            PACKETS_PER_URB,
+            MAX_AUDIO_PACKET_SIZE,
+            usb::TransferFlag::IsoAsap.into(),
+            i32::from(ep.interval()),
+        )?;
+        active_urb.replace(urb_handle.submit(GFP_KERNEL)?);
+    }
+    Ok(urbs)
+}
+
+/// Attempt to find the isochronous audio IN endpoint on the interface.
+fn find_audio_endpoint(intf: &usb::Interface) -> Result<&usb::HostEndpoint> {
+    for altsetting in intf.altsettings() {
+        for ep in altsetting.endpoints() {
+            if ep.endpoint_dir() == usb::ch9::Direction::In
+                && ep.endpoint_type() == usb::EndpointType::Isoc
+                && ep.endpoint_number() == 4
+                && ep.maxp() == MAX_AUDIO_PACKET_SIZE
+            {
+                return Ok(ep);
+            }
+        }
+    }
+    Err(ENODEV)
+}
diff --git a/drivers/media/usb/gv-usb2/gv_usb2.rs b/drivers/media/usb/gv-usb2/gv_usb2.rs
new file mode 100644
index 000000000000..e42c61c54773
--- /dev/null
+++ b/drivers/media/usb/gv-usb2/gv_usb2.rs
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust GV-USB2 driver.
+
+use crate::driver::GvUsb2Driver;
+
+kernel::module_usb_driver! {
+    type: GvUsb2Driver,
+    name: "gv_usb2",
+    authors: ["Colin Braun"],
+    description: "GV-USB2 Composite-USB Video Capture Device",
+    license: "GPL v2",
+}
+
+mod driver;
+mod regs;
diff --git a/drivers/media/usb/gv-usb2/regs.rs b/drivers/media/usb/gv-usb2/regs.rs
new file mode 100644
index 000000000000..cc96086d5d40
--- /dev/null
+++ b/drivers/media/usb/gv-usb2/regs.rs
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// USB Requests
+
+/// USB request value to read to a register on the STK1150.
+#[allow(dead_code)]
+pub(crate) const REQ_READ_REG: u8 = 0x00;
+
+/// USB request value to write to a register on the STK1150.
+pub(crate) const REQ_WRITE_REG: u8 = 0x01;
+
+// Registers (accessed via REQ_READ_REG and REQ_WRITE_REG)
+
+/// GPIO Control Register.
+///
+/// b31:    EEPROM Disable
+/// b25-16: DIR
+/// b9-0:   VALUE
+pub(crate) const GPIO_CTRL: u16 = 0x0000;
+
+/// Audio Control Register 0.
+pub(crate) const AC97_CTRL: u16 = 0x0500;
+
+/// I2S Control Register.
+pub(crate) const I2S_CTRL: u16 = 0x050C;

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 0/4] rust: usb: add usb request block abstractions and a user
  2026-07-12 21:07 [RFC PATCH 0/4] rust: usb: add usb request block abstractions and a user Colin Braun
                   ` (3 preceding siblings ...)
  2026-07-12 21:08 ` [RFC PATCH 4/4] media: add gv-usb2 audio capture driver Colin Braun
@ 2026-07-13 13:22 ` Danilo Krummrich
  2026-07-13 20:10   ` Colin Braun
  2026-07-13 13:53 ` Daniel Almeida
  5 siblings, 1 reply; 16+ messages in thread
From: Danilo Krummrich @ 2026-07-13 13:22 UTC (permalink / raw)
  To: Colin Braun
  Cc: Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
	Onur Özkan, Mauro Carvalho Chehab, Alan Stern, Mathias Nyman,
	linux-kernel, rust-for-linux, linux-usb, linux-media, Colin Braun

On Sun Jul 12, 2026 at 11:07 PM CEST, Colin Braun wrote:
> This series introduces initial abstractions to allow for the
> implementation of USB drivers in Rust.

Note that there's also [1].

[1] https://lore.kernel.org/lkml/20260617145946.1894-1-mike@fireburn.co.uk/

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions
  2026-07-12 21:07 ` [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions Colin Braun
@ 2026-07-13 13:22   ` Danilo Krummrich
  2026-07-13 20:03     ` Colin Braun
  0 siblings, 1 reply; 16+ messages in thread
From: Danilo Krummrich @ 2026-07-13 13:22 UTC (permalink / raw)
  To: Colin Braun
  Cc: Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
	Onur Özkan, Mauro Carvalho Chehab, Alan Stern, Mathias Nyman,
	linux-kernel, rust-for-linux, linux-usb, linux-media, Colin Braun,
	oneukum

(Cc: Oliver)

On Sun Jul 12, 2026 at 11:07 PM CEST, Colin Braun wrote:
> @@ -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()) };

Please see commit f12140f21acb ("rust: usb: don't retain device context for the
interface parent").

We can't derive the device context of a USB device from a USB interface. Please
also see the device context documentation in [1].

USB device drivers are separate from USB interface drivers, we can't assume that
a USB device is bound to a USB device driver just because a USB interface (of
that same device) is bound to an USB interface driver.

The same is true from the Core context, which means the device is in a bus
device callback, where the device lock is held.

This is also the reason why I keep proposing to only expose simple forwarding
helpers on usb::Interface to implement URBs (see also [2] and [3]).

An URB requires either usb::Interface<Bound> or, for a USB device driver,
usb::Device<Bound>. But since we can't derive usb::Device<Bound> from
usb::Interface<Bound> a simple forwarding helper does the trick.

[1] https://rust.docs.kernel.org/kernel/device/trait.DeviceContext.html
[2] https://lore.kernel.org/lkml/DJRF98V7SMXT.14BS8WGBEESZ8@kernel.org/
[3] https://lore.kernel.org/lkml/24196da3-62e8-4707-8024-d989bcd5d3a8@rowland.harvard.edu/

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 0/4] rust: usb: add usb request block abstractions and a user
  2026-07-12 21:07 [RFC PATCH 0/4] rust: usb: add usb request block abstractions and a user Colin Braun
                   ` (4 preceding siblings ...)
  2026-07-13 13:22 ` [RFC PATCH 0/4] rust: usb: add usb request block abstractions and a user Danilo Krummrich
@ 2026-07-13 13:53 ` Daniel Almeida
  2026-07-13 20:32   ` Colin Braun
  5 siblings, 1 reply; 16+ messages in thread
From: Daniel Almeida @ 2026-07-13 13:53 UTC (permalink / raw)
  To: Colin Braun
  Cc: Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Mauro Carvalho Chehab,
	Alan Stern, Mathias Nyman, linux-kernel, rust-for-linux,
	linux-usb, linux-media, Colin Braun

Hi Colin!

> On 12 Jul 2026, at 18:07, Colin Braun <colinbrauncl@gmail.com> wrote:
> 
> This series introduces initial abstractions to allow for the
> implementation of USB drivers in Rust.
> 
> This is an RFC to demonstrate a rough idea on how the USB abstractions
> needed to create drivers in Rust could be implemented.
> 
> The series is broken up into 4 parts:
> 
> 1. USB chapter 9 standard descriptors and constants - Exposes the
>   necessary structs and constants from include/uapi/linux/usb/ch9.h as
>   a new ch9 submodule.
> 
> 2. Interface and endpoint abstractions - Wraps the relevant C structs
>   and functions defined in include/linux/usb.h, allowing drivers to
>   safely query and configure interfaces and their endpoints.
> 
> 3. USB Request Block (URB) abstractions - Creates a safe wrapper around
>   the C `struct urb` to allow Rust drivers to communicate with devices.
> 
> 4. An initial user of the new abstractions - A driver for the GV-USB2
>   composite-usb video capture device.
> 
> Patch 3 is the bulk and core of this series. By their asynchronous
> nature, creating a safe URB abstraction for drivers to use is tricky.
> The goals of the URB abstraction are:
> 
> 1. No `unsafe` needed in driver code. This means providing a way for a
>   driver to safely access private data sent with the URB.
> 2. Drivers are forced to handle the URB status in their completion
>   callback before accessing the URB data.
> 3. Dropping an URB ensures it is not in-flight and frees its resources.
> 4. The URB can be safely resubmitted from the completion callback.
> 
> The patch elaborates on how these goals are achieved in more detail.
> 
> Although the URB abstractions do not include immediate support for bulk
> or interrupt URBs, I believe it creates a foundation conducive to
> future, safe abstractions for them.
> 
> Patch 4 is first user of these USB abstractions. The initial
> implementation is very much bare-bones, only exposing audio data via
> debugfs. It is based on the in-tree STK1160 driver and an old,
> out-of-tree driver written by Isaac Lozano [1].
> 
> [1] https://github.com/Isaac-Lozano/GV-USB2-Driver
> 
> Signed-off-by: Colin Braun <colin.braun.cl@gmail.com>
> ---
> Colin Braun (4):
>      rust: usb: add USB ch9 standard descriptors and constants
>      rust: usb: add usb host interface and endpoint abstractions
>      rust: usb: add urb abstraction with control and isochronous support
>      media: add gv-usb2 audio capture driver

Have you talked to the media people about adding a Rust driver?

> 
> drivers/media/usb/Kconfig            |   1 +
> drivers/media/usb/Makefile           |   1 +
> drivers/media/usb/gv-usb2/Kconfig    |   9 +
> drivers/media/usb/gv-usb2/Makefile   |   1 +
> drivers/media/usb/gv-usb2/driver.rs  | 361 ++++++++++++++
> drivers/media/usb/gv-usb2/gv_usb2.rs |  16 +
> drivers/media/usb/gv-usb2/regs.rs    |  25 +
> include/linux/usb.h                  |   4 +
> rust/kernel/usb.rs                   | 905 ++++++++++++++++++++++++++++++++++-
> rust/kernel/usb/ch9.rs               | 295 ++++++++++++
> 10 files changed, 1613 insertions(+), 5 deletions(-)
> ---
> base-commit: 30e873dd61b044092dbc657c7c67a5d19adfd933
> change-id: 20260712-urb-abstraction-v1-020a67f16cff
> 
> Best regards,
> --  
> Colin Braun <colin.braun.cl@gmail.com>
> 
> 


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 4/4] media: add gv-usb2 audio capture driver
  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
  0 siblings, 1 reply; 16+ messages in thread
From: Danilo Krummrich @ 2026-07-13 14:44 UTC (permalink / raw)
  To: Colin Braun
  Cc: Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
	Onur Özkan, Mauro Carvalho Chehab, Alan Stern, Mathias Nyman,
	linux-kernel, rust-for-linux, linux-usb, linux-media, Colin Braun

On Sun Jul 12, 2026 at 11:08 PM CEST, Colin Braun wrote:
> +/// Write a vendor-specific control register on the GV-USB2 device.
> +///
> +/// Uses a vendor-type control request (`REQ_WRITE_REG`) to write the
> +/// given `value` to the given `reg` address.
> +fn write_reg(intf: &usb::Interface<device::Bound>, reg: u16, value: u8) -> Result {

In addition to raw control messages, this can leverage the generic I/O backend
infrastructure, so you don't have to roll your own write_reg() function and use
the register!() infrastructure instead. See also [1] and [2].

[1] https://lore.kernel.org/driver-core/20260706-io_projection-v6-0-72cd5d055d54@garyguo.net/
[2] https://lore.kernel.org/lkml/DJVQ852J7SOH.26YBIJTQ9B66G@kernel.org/

> +    let req = usb::ch9::CtrlRequest::new(
> +        usb::ch9::RequestType::new(
> +            usb::ch9::Direction::Out,
> +            usb::ch9::Type::Vendor,
> +            usb::ch9::Recipient::Device,
> +        ),
> +        regs::REQ_WRITE_REG,
> +        u16::from(value),
> +        reg,
> +        0,
> +    );
> +    let dev: &usb::Device<device::Bound> = intf.as_ref();
> +    dev.control_msg(&req, None, Delta::from_millis(1_000))
> +        .map(|_| ())
> +}

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions
  2026-07-13 13:22   ` Danilo Krummrich
@ 2026-07-13 20:03     ` Colin Braun
  2026-07-13 20:09       ` Danilo Krummrich
  0 siblings, 1 reply; 16+ messages in thread
From: Colin Braun @ 2026-07-13 20:03 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: Colin Braun, Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Mauro Carvalho Chehab,
	Alan Stern, Mathias Nyman, linux-kernel, rust-for-linux,
	linux-usb, linux-media, Colin Braun, oneukum

On Mon, Jul 13, 2026 at 03:22:33PM +0200, Danilo Krummrich wrote:
> (Cc: Oliver)
> 
> On Sun Jul 12, 2026 at 11:07 PM CEST, Colin Braun wrote:
> > @@ -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()) };
> 
> Please see commit f12140f21acb ("rust: usb: don't retain device context for the
> interface parent").
> 
> We can't derive the device context of a USB device from a USB interface. Please
> also see the device context documentation in [1].
> 
> USB device drivers are separate from USB interface drivers, we can't assume that
> a USB device is bound to a USB device driver just because a USB interface (of
> that same device) is bound to an USB interface driver.
> 
> The same is true from the Core context, which means the device is in a bus
> device callback, where the device lock is held.
> 
> This is also the reason why I keep proposing to only expose simple forwarding
> helpers on usb::Interface to implement URBs (see also [2] and [3]).
> 
> An URB requires either usb::Interface<Bound> or, for a USB device driver,
> usb::Device<Bound>. But since we can't derive usb::Device<Bound> from
> usb::Interface<Bound> a simple forwarding helper does the trick.

That makes sense, thank you for pointing this out. I should have taken a
look at the git log for that line to try to understand its background.

I'll remove the usb::Device<device::Bound>::set_interface() and
usb::Device<device::Bound>::control_msg() implementations in my next
revision (since they will no longer be used) and just implement them on
usb::Interface<device::Bound>.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions
  2026-07-13 20:03     ` Colin Braun
@ 2026-07-13 20:09       ` Danilo Krummrich
  2026-07-14  9:26         ` Oliver Neukum
  0 siblings, 1 reply; 16+ messages in thread
From: Danilo Krummrich @ 2026-07-13 20:09 UTC (permalink / raw)
  To: Colin Braun
  Cc: Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
	Onur Özkan, Mauro Carvalho Chehab, Alan Stern, Mathias Nyman,
	linux-kernel, rust-for-linux, linux-usb, linux-media, Colin Braun,
	oneukum

On Mon Jul 13, 2026 at 10:03 PM CEST, Colin Braun wrote:
> On Mon, Jul 13, 2026 at 03:22:33PM +0200, Danilo Krummrich wrote:
>> (Cc: Oliver)
>> 
>> On Sun Jul 12, 2026 at 11:07 PM CEST, Colin Braun wrote:
>> > @@ -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()) };
>> 
>> Please see commit f12140f21acb ("rust: usb: don't retain device context for the
>> interface parent").
>> 
>> We can't derive the device context of a USB device from a USB interface. Please
>> also see the device context documentation in [1].
>> 
>> USB device drivers are separate from USB interface drivers, we can't assume that
>> a USB device is bound to a USB device driver just because a USB interface (of
>> that same device) is bound to an USB interface driver.
>> 
>> The same is true from the Core context, which means the device is in a bus
>> device callback, where the device lock is held.
>> 
>> This is also the reason why I keep proposing to only expose simple forwarding
>> helpers on usb::Interface to implement URBs (see also [2] and [3]).
>> 
>> An URB requires either usb::Interface<Bound> or, for a USB device driver,
>> usb::Device<Bound>. But since we can't derive usb::Device<Bound> from
>> usb::Interface<Bound> a simple forwarding helper does the trick.
>
> That makes sense, thank you for pointing this out. I should have taken a
> look at the git log for that line to try to understand its background.
>
> I'll remove the usb::Device<device::Bound>::set_interface() and
> usb::Device<device::Bound>::control_msg() implementations in my next
> revision (since they will no longer be used) and just implement them on
> usb::Interface<device::Bound>.

I'd keep them unsafely on usb::Device and then safely expose forwarding via
usb::Device<Bound> and usb::Interface<Bound> once required.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 0/4] rust: usb: add usb request block abstractions and a user
  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
  0 siblings, 0 replies; 16+ messages in thread
From: Colin Braun @ 2026-07-13 20:10 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: Colin Braun, Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Mauro Carvalho Chehab,
	Alan Stern, Mathias Nyman, linux-kernel, rust-for-linux,
	linux-usb, linux-media, Colin Braun

On Mon, Jul 13, 2026 at 03:22:25PM +0200, Danilo Krummrich wrote:
> On Sun Jul 12, 2026 at 11:07 PM CEST, Colin Braun wrote:
> > This series introduces initial abstractions to allow for the
> > implementation of USB drivers in Rust.
> 
> Note that there's also [1].
> 
> [1] https://lore.kernel.org/lkml/20260617145946.1894-1-mike@fireburn.co.uk/

Yep, I saw that series pop up while I was working on this one.
By and large there is not a lot of overlap, so I felt that my patch
series was okay to submit.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 0/4] rust: usb: add usb request block abstractions and a user
  2026-07-13 13:53 ` Daniel Almeida
@ 2026-07-13 20:32   ` Colin Braun
  0 siblings, 0 replies; 16+ messages in thread
From: Colin Braun @ 2026-07-13 20:32 UTC (permalink / raw)
  To: Daniel Almeida
  Cc: Colin Braun, Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Mauro Carvalho Chehab,
	Alan Stern, Mathias Nyman, linux-kernel, rust-for-linux,
	linux-usb, linux-media, Colin Braun

On Mon, Jul 13, 2026 at 10:53:53AM -0300, Daniel Almeida wrote:
> 
> Have you talked to the media people about adding a Rust driver?
> 

I have not. I was actually wondering if this belongs in
drivers/staging/media, given how much work still needs to be done for it
to support basic functionality. I had seen some v4l2 Rust abstraction
work done but not merged in, so I had hoped this could eventually be a
user of that work. Still trying to get a lay of the land, this is my
first attempt at contributing to the kernel.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 4/4] media: add gv-usb2 audio capture driver
  2026-07-13 14:44   ` Danilo Krummrich
@ 2026-07-13 21:08     ` Colin Braun
  0 siblings, 0 replies; 16+ messages in thread
From: Colin Braun @ 2026-07-13 21:08 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: Colin Braun, Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Mauro Carvalho Chehab,
	Alan Stern, Mathias Nyman, linux-kernel, rust-for-linux,
	linux-usb, linux-media, Colin Braun

On Mon, Jul 13, 2026 at 04:44:24PM +0200, Danilo Krummrich wrote:
> On Sun Jul 12, 2026 at 11:08 PM CEST, Colin Braun wrote:
> > +/// Write a vendor-specific control register on the GV-USB2 device.
> > +///
> > +/// Uses a vendor-type control request (`REQ_WRITE_REG`) to write the
> > +/// given `value` to the given `reg` address.
> > +fn write_reg(intf: &usb::Interface<device::Bound>, reg: u16, value: u8) -> Result {
> 
> In addition to raw control messages, this can leverage the generic I/O backend
> infrastructure, so you don't have to roll your own write_reg() function and use
> the register!() infrastructure instead. See also [1] and [2].
> 
> [1] https://lore.kernel.org/driver-core/20260706-io_projection-v6-0-72cd5d055d54@garyguo.net/
> [2] https://lore.kernel.org/lkml/DJVQ852J7SOH.26YBIJTQ9B66G@kernel.org/
> 

Interesting, I had briefly glanced at this macro and was disappointed
that it didn't seem to apply well to my situation, but I see that the
Io and IoCapable traits are generic in a way that does support this.

I'll include this in my next revision, thank you!

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions
  2026-07-13 20:09       ` Danilo Krummrich
@ 2026-07-14  9:26         ` Oliver Neukum
  2026-07-14 13:05           ` Danilo Krummrich
  0 siblings, 1 reply; 16+ messages in thread
From: Oliver Neukum @ 2026-07-14  9:26 UTC (permalink / raw)
  To: Danilo Krummrich, Colin Braun
  Cc: Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
	Onur Özkan, Mauro Carvalho Chehab, Alan Stern, Mathias Nyman,
	linux-kernel, rust-for-linux, linux-usb, linux-media, Colin Braun

On 13.07.26 22:09, Danilo Krummrich wrote:
> On Mon Jul 13, 2026 at 10:03 PM CEST, Colin Braun wrote:
>> On Mon, Jul 13, 2026 at 03:22:33PM +0200, Danilo Krummrich wrote:
>>> (Cc: Oliver)
>>>
>>> On Sun Jul 12, 2026 at 11:07 PM CEST, Colin Braun wrote:

[..]

>>> An URB requires either usb::Interface<Bound> or, for a USB device driver,
>>> usb::Device<Bound>. But since we can't derive usb::Device<Bound> from
>>> usb::Interface<Bound> a simple forwarding helper does the trick.
>>
>> That makes sense, thank you for pointing this out. I should have taken a
>> look at the git log for that line to try to understand its background.
>>
>> I'll remove the usb::Device<device::Bound>::set_interface() and
>> usb::Device<device::Bound>::control_msg() implementations in my next
>> revision (since they will no longer be used) and just implement them on
>> usb::Interface<device::Bound>.
> 
> I'd keep them unsafely on usb::Device and then safely expose forwarding via
> usb::Device<Bound> and usb::Interface<Bound> once required.

Hi,

you are making me think that the fundamental assumptions of the USB
layer are ill documented. If you find this to be the case, please tell
me what should be improved and I'll see what I can do.

Very well, this will be a bit longer, because I'll try to explain:
For now, there is exactly one device driver, named "generic" (yes,
imaginative). However, its behavior in that regard is rather fundamental,
so if another driver were ever to be written, its drivers would not
be normal interface drivers.

In principle the chain goes as such:

Device -> Configuration -> Interfaces

The dependencies are in that order. You can have a configuration without
interfaces and a device without configurations (it would be useless, but
it is within spec)
The life times are limited in the same way. An interface never lives
longer than its configuration and a configuration does not live longer
than its device.

This is controlled by code in drivers/usb/core/generic.c

int usb_generic_driver_probe(struct usb_device *udev)
{
         int err, c;

         /* Choose and set the configuration.  This registers the interfaces
          * with the driver core and lets interface drivers bind to them.
          */
         if (udev->authorized == 0)
                 dev_info(&udev->dev, "Device is not authorized for usage\n");
         else {
                 c = usb_choose_configuration(udev);
                 if (c >= 0) {
                         err = usb_set_configuration(udev, c);

This is the usual way a configuration is created. [You need not worry about
the other ways. They also call usb_set_configuration().]

Also important for this discussion is that this cannot fail:

                         if (err && err != -ENODEV) {
                                 dev_err(&udev->dev, "can't set config #%d, error %d\n",
                                         c, err);
                                 /* This need not be fatal.  The user can try to
                                  * set other configurations. */
                         }
                 }
         }
         /* USB device state == configured ... usable */
         usb_notify_add_device(udev);

         return 0;
}

A configuration is destroyed by usb_set_configuration(). This
is used in disconnect:

void usb_generic_driver_disconnect(struct usb_device *udev)
{
         usb_notify_remove_device(udev);

         /* if this is only an unbind, not a physical disconnect, then
          * unconfigure the device */
         if (udev->actconfig)
                 usb_set_configuration(udev, -1);
}

( -1 means without replacement)

You can see that there is no way a configuration and thereby its interfaces
can last longer than its device.

An interface driver is allowed to talk to two sets of endpoints

1. endpoints associated with interfaces it has claimed, accepted or is probed for
2. endpoint 0 of the device whose interfaces it has claimed, accepted or is probed for

(Please do not ask about cdc-wdm)

That does _not_ mean that a driver can communicate to them at all times. Communication
is limited as follows:

Communication may begin when

1. probe() is called
2. usb_claim_interface() returns without error
3. resume() is called
4. reset_resume() is called
5. post_reset() is called

Communication must cease

1. before disconnect() returns
2. before suspend() returns
3. before pre_reset() returns
4. before probe() is exited with an error return

The only exception to that is that you may schedule a reset.

I hope this makes things a bit clearer. Please ask questions
if anything is unclear. I'd be happy to help.
This area may be a bit murky because only the states of devices
are named and documented. There simply is no data structure
equivalent to the binding of a driver and an interface, hence
we cannot just give interfaces a state.

	Regards
		Oliver




^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions
  2026-07-14  9:26         ` Oliver Neukum
@ 2026-07-14 13:05           ` Danilo Krummrich
  0 siblings, 0 replies; 16+ messages in thread
From: Danilo Krummrich @ 2026-07-14 13:05 UTC (permalink / raw)
  To: Oliver Neukum
  Cc: Colin Braun, Miguel Ojeda, Greg Kroah-Hartman, Boqun Feng,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Mauro Carvalho Chehab,
	Alan Stern, Mathias Nyman, linux-kernel, rust-for-linux,
	linux-usb, linux-media, Colin Braun

On Tue Jul 14, 2026 at 11:26 AM CEST, Oliver Neukum wrote:
> You can see that there is no way a configuration and thereby its interfaces
> can last longer than its device.

So, what you're saying is that, in the generic case, there is a guarantee that
if a usb_interface is bound to a usb_driver, then the usb_interface's parent
usb_device is also bound to a usb_device_driver.

But the relevant question is whether this always holds. In a previous discussion
[1] Alan explained that it currently doesn't hold.

Note that I'm not looking at this from a USB topology perspective, but from a
driver model perspective. All I'm saying is that usb::Device<Bound> from the
driver model side means "it is guaranteed that the usb_device is bound to a
usb_device_driver" and therefore can implement functions that rely on this
invariant.

Analogously, usb::Interface<Bound> means that the usb_interface is bound to a
usb_driver. So, if we want to be able to derive usb::Device<Bound> from
usb::Interface<Bound> it must always be guaranteed that this holds, not just in
the most common case.

As for the question whether it should be

	let dev = intf.device();
	dev.bulk_recv();

or

	intf.bulk_recv();

the former does not work if we can't uphold the guarantee that
usb::Device<Bound> follows from usb::Interface<Bound>; at least not without an
additional type state wrapper.

However, I don't see why we don't want to have the helper regardless. A
usb_driver primarily deals with the usb_interface device, so that makes perfect
sense from a driver model perspective: The "device" a usb_driver deals with is
the usb_interface.

I think our main disconnect comes from the fact that you see this from a USB
stack topology point of view, whereas I see it from a driver model topology
point of view.

From the driver core perspective a usb_interface is just another device that
happens to have a usb_device parent. Lifecycle wise any device resources
requested by a usb_driver are tied to the lifetime of the usb_interface being
bound to the usb_driver.

The semantic relationship of a usb_interface and a usb_device is a USB subsystem
implementation detail, but it doesn't change the core lifecycle and ownership
rules as far as the driver model is concerned.

That said, the question of having or not having those helpers is "bikeshedding"
about USB topology vs. driver model perspective and either seems reasonable
IMHO. However, it has a correctness implication, as giving out
usb::Device<Bound> from usb::Interface<Bound> would currently be unsound as by
[1]; deriving usb::Device<Core> from usb::Interface<Core> is never correct, as
it implies being in the scope of a device lock protected bus callback.

> There simply is no data structure equivalent to the binding of a driver and an
> interface,

I don't know what you mean by this.

> hence we cannot just give interfaces a state.

Of course we can, and we have to. As mentioned above, from a driver core
perspective a usb_interface is just another device, with an own struct device it
embedds, its own device lock and its own driver structure (struct usb_driver) it
can be bound to.

The device types states match exactly this. For instance the 'Core' context
represents a device that is given out in a bus callback while the device lock is
held, such that we can restrict methods that require this scope to this context.

The same goes for the 'Bound' device context state. In the case of usb_interface
it means that the usb_interface is bound to the usb_driver.

[1] https://lore.kernel.org/all/0ff2a825-1115-426a-a6f9-df544cd0c5fc@rowland.harvard.edu/

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2026-07-14 13:05 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions Colin Braun
2026-07-13 13:22   ` 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-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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox