* [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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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
2026-07-14 16:26 ` Alan Stern
0 siblings, 1 reply; 20+ 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] 20+ messages in thread
* Re: [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions
2026-07-14 13:05 ` Danilo Krummrich
@ 2026-07-14 16:26 ` Alan Stern
2026-07-14 17:53 ` Danilo Krummrich
` (2 more replies)
0 siblings, 3 replies; 20+ messages in thread
From: Alan Stern @ 2026-07-14 16:26 UTC (permalink / raw)
To: Danilo Krummrich
Cc: Oliver Neukum, 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, Mathias Nyman, linux-kernel,
rust-for-linux, linux-usb, linux-media, Colin Braun
On Tue, Jul 14, 2026 at 03:05:16PM +0200, Danilo Krummrich wrote:
> 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.
At first I thought that we ought to have such a guarantee. But in fact
we don't, because the user can at any time write to a USB device's
bConfigurationValue sysfs attribute even if the device isn't bound to a
driver. This can create interfaces which may then be bound to drivers.
I just tried it and it worked.
This wasn't the original intent, but I don't see any good reason to
prevent people from doing it.
> 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.
I don't see why Rust needs to distinguish between a USB device that is
bound and one that is unbound. There isn't much you can do with one
that can't be done with the other.
> 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.
Similarly, I don't see why Rust needs to distinguish between an
interface that is bound and one that isn't.
Even from the point of view of the device core, a device that is bound
to a driver is the same kind of data structure as one that isn't bound;
the only difference is whether the ->driver pointer is set.
I'm aware that Rust has separate types for pointers that may be NULL and
pointers that cannot be NULL. Putting that into the language helps
prevent a lot of bugs. How would making the language be aware of
whether a device is bound to a driver be similarly useful?
Alan Stern
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions
2026-07-14 16:26 ` Alan Stern
@ 2026-07-14 17:53 ` Danilo Krummrich
2026-07-14 17:57 ` Oliver Neukum
2026-07-14 18:26 ` Miguel Ojeda
2 siblings, 0 replies; 20+ messages in thread
From: Danilo Krummrich @ 2026-07-14 17:53 UTC (permalink / raw)
To: Alan Stern
Cc: Oliver Neukum, 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, Mathias Nyman, linux-kernel,
rust-for-linux, linux-usb, linux-media, Colin Braun,
Rafael J. Wysocki, driver-core
(Cc: driver-core)
On Tue Jul 14, 2026 at 6:26 PM CEST, Alan Stern wrote:
> I don't see why Rust needs to distinguish between a USB device that is
> bound and one that is unbound. There isn't much you can do with one
> that can't be done with the other.
It has nothing to do with Rust, those driver lifecycle rules exist regardless
and they are present universally, including in C.
The only difference is that in C all the responsibility to enforce them is
usually on the driver -- e.g. by creating, destroying and calling things in the
correct order in probe() and remove() -- and a lot of drivers have bugs in this
regard as a consequence.
With Rust we can enforce those rules with the help of the type system at compile
time; device context states are a part of that.
> Similarly, I don't see why Rust needs to distinguish between an
> interface that is bound and one that isn't.
>
> Even from the point of view of the device core, a device that is bound
> to a driver is the same kind of data structure as one that isn't bound;
> the only difference is whether the ->driver pointer is set.
This is a huge understatement.
The state of a device being bound to a driver defines which entity (i.e. which
driver) is in charge of operating the underlying device, and thus defines who
owns the device (associated) resources.
Many APIs rely on this, as in they only guarantee valid behavior when called
from a scope where the device is guaranteed to be bound to a driver, or IOW
where a driver can prove that it actually operates the device.
Drivers must only acquire device resources when they are actually bound to the
corresponding device, and must hand them back before the device is unbound. The
devres API, for instance, exists for this fundamental reason.
For instance, we can't have drivers manage IRQs, mess with I/O memory, program
IOMMU page tables (e.g. through DMA APIs), etc. for devices they are not bound
to and hence are not allowed to operate (anymore).
Those device resources all have a lifetime that is tied to the lifetime of the
device being bound to a driver.
Consequently, any asynchronous scopes such as IOCTLs from class device
registrations, IRQs, work queued on workqueues, etc. must all be synchronized in
some way such that those asynchronous scopes do not access device resources that
have already been destroyed on driver unbind.
Or in other words, they must be synchronized against the "bound" scope, which is
exactly what the Device<Bound> type state in Rust represents.
So, again, all those lifetime rules around the driver lifecycle exist
universally, it's just that in Rust we enforce them through the type system.
For instance, tying it back to USB, we don't want that a usb_driver still messes
with a usb_interface, e.g. initiating transfers after it has been unbound from
the interface and hence must not operate it anymore. This can easily happen if
e.g. a class device registration is not properly synchronized and the driver
still receives IOCTLs after driver unbind. In Rust we know through the "Bound"
type state which scope provides the guarantee that the device is still bound,
such that mistakes like this become impossible.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions
2026-07-14 16:26 ` Alan Stern
2026-07-14 17:53 ` Danilo Krummrich
@ 2026-07-14 17:57 ` Oliver Neukum
2026-07-14 18:26 ` Miguel Ojeda
2 siblings, 0 replies; 20+ messages in thread
From: Oliver Neukum @ 2026-07-14 17:57 UTC (permalink / raw)
To: Alan Stern, Danilo Krummrich
Cc: Oliver Neukum, 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, Mathias Nyman, linux-kernel,
rust-for-linux, linux-usb, linux-media, Colin Braun
On 14.07.26 18:26, Alan Stern wrote:
> At first I thought that we ought to have such a guarantee. But in fact
> we don't, because the user can at any time write to a USB device's
> bConfigurationValue sysfs attribute even if the device isn't bound to a
> driver. This can create interfaces which may then be bound to drivers.
> I just tried it and it worked.
>
> This wasn't the original intent, but I don't see any good reason to
> prevent people from doing it.
I hardly dare ask, but what happens if you unplug the device?
Regards
Oliver
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions
2026-07-14 16:26 ` Alan Stern
2026-07-14 17:53 ` Danilo Krummrich
2026-07-14 17:57 ` Oliver Neukum
@ 2026-07-14 18:26 ` Miguel Ojeda
2 siblings, 0 replies; 20+ messages in thread
From: Miguel Ojeda @ 2026-07-14 18:26 UTC (permalink / raw)
To: Alan Stern
Cc: Danilo Krummrich, Oliver Neukum, 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, Mathias Nyman,
linux-kernel, rust-for-linux, linux-usb, linux-media, Colin Braun
On Tue, Jul 14, 2026 at 6:26 PM Alan Stern <stern@rowland.harvard.edu> wrote:
>
> I'm aware that Rust has separate types for pointers that may be NULL and
> pointers that cannot be NULL. Putting that into the language helps
> prevent a lot of bugs. How would making the language be aware of
> whether a device is bound to a driver be similarly useful?
Generally speaking, different types can implement different methods,
which allows one to restrict what functions can be called in certain
cases, which prevents bugs.
Similarly, having different types allow one to put different
invariants on them, like the non-NULL you mention [*]. In turn, that
allows one to design APIs that require callers to pass the proof they
have the right type.
That is, even if something may be exactly the same underlying data
structure, the types being different allows to disambiguate this.
Otherwise, you may need to check or assume those facts, without
compiler support. In some cases, if those facts could lead to UB, then
it means one's API may need to be `unsafe` instead of a safe
abstraction, which make them more cumbersome and "dangerous" to use
for callers.
So, for instance,
https://rust.docs.kernel.org/kernel/device/struct.Bound.html
says
Some APIs, such as `dma::Coherent` or `Devres` rely on the `Device`
to be bound, which can be proven with the `Bound` device context.
And indeed there are methods that require such a `&Device<Bound>` like:
https://rust.docs.kernel.org/kernel/devres/struct.Devres.html#method.new
Then the person implementing a safe abstraction can actually rely on
the fact that it knows those facts to argue certain things -- the
compiler will check it. For instance, that a call to the C side will
not introduce UB. But you can use this to argue for other things,
unrelated to UB, like making correctness arguments.
I hope that clarifies a bit and serves as context for the rest of the
discussion.
[*] By the way, Rust's references guarantee way more than just not
being NULL! Not being NULL is just the "easy case" which ones can
dynamically check in other languages anyway.
Cheers,
Miguel
^ permalink raw reply [flat|nested] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ 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; 20+ 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] 20+ messages in thread