Rust for Linux List
 help / color / mirror / Atom feed
* [PATCH RFC 0/2] rust: usb: abstractions towards the port of usbsevseg.c to Rust
@ 2026-08-01  0:01 Alexandru Radovici
  2026-08-01  0:01 ` [PATCH RFC 1/2] rust: usb: add endpoint abstraction Alexandru Radovici
  2026-08-01  0:01 ` [PATCH RFC 2/2] rust: usb: add control message send and receive Alexandru Radovici
  0 siblings, 2 replies; 5+ messages in thread
From: Alexandru Radovici @ 2026-08-01  0:01 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Miguel Ojeda, 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
  Cc: linux-kernel, linux-usb, rust-for-linux, Alexandru Radovici

My end goal is to port the usbsevseg.c USB driver to Rust. That driver is
small enough to make a useful first target; the remaining piece it
needs is sysfs attribute support for USB devices, which I plan to send
separately.

Porting the driver requires:
- retrieving endpoints and sending control messages API (this series)
- enabling sysfs attributes API for USB devices (future patch)

These patches add the USB abstractions needed to port
drivers/usb/misc/usbsevseg.c to Rust: retrieving an interface's
endpoints, and sending and receiving control messages.

Patch 1 adds `AlternateEndpoint`, along with `AlternateSetting`
and the accessors needed to reach one. Patch 2 adds the
control transfer methods and the `Request` type describing a setup packet.

The part I would most like feedback on is the typestate design in patch
1. `HostEndpoint` is generic over a direction and a transfer type, both
sealed 1-ZST markers, so a function can require e.g.
`&HostEndpoint<In, Bulk>` and skip checking the descriptor itself. Control
endpoints get a `Bidirectional` marker rather than a direction, since
bit 7 of bEndpointAddress is ignored for them. I am not sure whether
this earns its complexity for the drivers we expect, or whether plain
run-time check would be better.

Only `as_control()`, `as_in()` and `as_out()` are implemented so far;
`as_bulk()`, `as_interrupt()` and `as_isochronous()` are skiped for now 
as no current user needs them.

Signed-off-by: Alexandru Radovici <alexandru.radovici@wyliodrin.com>
---
Alexandru Radovici (2):
      rust: usb: add endpoint abstraction
      rust: usb: add control message send and receive

 rust/kernel/usb.rs          | 127 ++++++++++-
 rust/kernel/usb/control.rs  | 353 ++++++++++++++++++++++++++++++
 rust/kernel/usb/endpoint.rs | 508 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 987 insertions(+), 1 deletion(-)
---
base-commit: 8ba098e6b6ff0db8edf28528d1552be261af30d4
change-id: 20260801-rust-usb_control_msg-ee34e002b818

Best regards,
-- 
Alexandru Radovici <alexandru.radovici@wyliodrin.com>


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

* [PATCH RFC 1/2] rust: usb: add endpoint abstraction
  2026-08-01  0:01 [PATCH RFC 0/2] rust: usb: abstractions towards the port of usbsevseg.c to Rust Alexandru Radovici
@ 2026-08-01  0:01 ` Alexandru Radovici
  2026-08-02  8:39   ` Greg Kroah-Hartman
  2026-08-01  0:01 ` [PATCH RFC 2/2] rust: usb: add control message send and receive Alexandru Radovici
  1 sibling, 1 reply; 5+ messages in thread
From: Alexandru Radovici @ 2026-08-01  0:01 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Miguel Ojeda, 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
  Cc: linux-kernel, linux-usb, rust-for-linux, Alexandru Radovici

Add an abstraction for `struct usb_host_endpoint`, together with the
accessors needed to reach one: `AlternateSetting` wrapping
`struct usb_host_interface`, `Interface::alternate_settings()` and
`Interface::current_alternate_setting()`, and `Device::control_endpoint()`
for the default control endpoint, which no interface descriptor lists.

`HostEndpoint` is generic over two sealed marker traits,
`EndpointDirection` and `EndpointTransferType`, whose implementors are
1-ZSTs held in `PhantomData`. An endpoint borrowed from an alternate
setting starts out generic in both; `as_in()`, `as_out()` and
`as_control()` check the descriptor once and return a reference
carrying the corresponding marker, so a function taking
`&HostEndpoint<In, Bulk>` needs no check of its own. The type is
`#[repr(transparent)]` over the C struct and the markers are
zero-sized, so the refinement costs nothing and a slice of endpoints
can be borrowed directly from the C array.

Control endpoints get a distinct `Bidirectional` marker rather than an
IN or OUT one. A control transfer takes its direction from bit 7 of the
setup packet's bmRequestType, and USB 2.0 section 9.6.6 defines the
corresponding bit of bEndpointAddress as ignored for control endpoints.
`as_in()` and `as_out()` are not implemented for `Bidirectional`, making
calling them a compile error rather than a misleading result.

Signed-off-by: Alexandru Radovici <alexandru.radovici@wyliodrin.com>
---
 rust/kernel/usb.rs          | 124 +++++++++++
 rust/kernel/usb/endpoint.rs | 508 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 632 insertions(+)

diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs
index 7aff0c82d0af..55c627be1658 100644
--- a/rust/kernel/usb.rs
+++ b/rust/kernel/usb.rs
@@ -20,6 +20,7 @@
     prelude::*,
     sync::aref::AlwaysRefCounted,
     types::Opaque,
+    usb::endpoint::HostEndpoint,
     ThisModule, //
 };
 use core::{
@@ -29,8 +30,11 @@
         MaybeUninit, //
     },
     ptr::NonNull,
+    slice,
 };
 
+pub mod endpoint;
+
 /// An adapter for the registration of USB drivers.
 pub struct Adapter<T: Driver>(T);
 
@@ -334,6 +338,78 @@ fn disconnect<'bound>(
     );
 }
 
+/// A single alternate setting of an [`Interface`].
+///
+/// A USB interface declares one or more alternate settings, each of which describes a different
+/// endpoint configuration for the same logical function - for example a UVC camera exposing one
+/// setting per bandwidth tier, plus a zero-bandwidth setting used while idle. Exactly one is
+/// active at a time; see [`Interface::current_alt_setting()`].
+///
+/// # Invariants
+///
+/// The wrapped [`Opaque`] holds an initialised `struct usb_host_interface`. Instances are never
+/// constructed by Rust code: they are only ever borrowed out of the `altsetting` array of a
+/// `struct usb_interface` owned by the C side, which guarantees that `desc` is initialised and
+/// that the setting outlives the borrow.
+#[repr(transparent)]
+pub struct AlternateSetting(Opaque<bindings::usb_host_interface>);
+
+impl AlternateSetting {
+    /// Returns a raw pointer to the underlying `struct usb_host_interface`.
+    ///
+    /// By the type invariants the pointer is non-null and points at an initialised alternate
+    /// setting for at least the lifetime of `&self`.
+    fn as_raw(&self) -> *mut bindings::usb_host_interface {
+        self.0.get()
+    }
+
+    /// Returns this setting's `bAlternateSetting` number.
+    ///
+    /// Alternate settings of one interface are numbered from zero; setting 0 always exists and is
+    /// the one the device defaults to after a configuration is selected. This is the value passed
+    /// to `usb_set_interface()` to activate the setting.
+    pub fn number(&self) -> u8 {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid
+        // `struct usb_host_interface` with an initialised `desc`.
+        unsafe { (*self.as_raw()).desc.bAlternateSetting }
+    }
+
+    /// Returns the `bInterfaceNumber` of the interface this setting belongs to.
+    ///
+    /// Every alternate setting of a given interface reports the same number, so this identifies
+    /// the interface within its configuration rather than distinguishing settings from one
+    /// another, use [`number()`](Self::number) for that.
+    pub fn interface_number(&self) -> u8 {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid
+        // `struct usb_host_interface` with an initialised `desc`.
+        unsafe { (*self.as_raw()).desc.bInterfaceNumber }
+    }
+
+    /// Returns the endpoints declared by this alternate setting, in descriptor order.
+    ///
+    /// The endpoints come back untyped, as `Endpoint<Generic, Generic>`; refine them with
+    /// [`Endpoint::as_in()`], [`Endpoint::as_out()`] and [`Endpoint::as_control()`].
+    pub fn endpoints(&self) -> &[HostEndpoint] {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid
+        // `struct usb_host_interface` with an initialised `desc`.
+        let (ptr, len) = (unsafe { (*self.as_raw()).endpoint }, unsafe {
+            (*self.as_raw()).desc.bNumEndpoints
+        });
+
+        if len == 0 {
+            &[]
+        } else {
+            // SAFETY: When `bNumEndpoints` is non-zero the C side has allocated an array of that
+            // many initialised `struct usb_host_endpoint` at `ptr`, living as long as the
+            // interface. `Endpoint` is a `#[repr(transparent)]` wrapper around
+            // `Opaque<bindings::usb_host_endpoint>`, which is itself layout-compatible with
+            // `struct usb_host_endpoint`, so the cast preserves both size and alignment and the
+            // resulting slice borrows for no longer than `&self`.
+            unsafe { slice::from_raw_parts(ptr.cast(), len as usize) }
+        }
+    }
+}
+
 /// A USB interface.
 ///
 /// This structure represents the Rust abstraction for a C [`struct usb_interface`].
@@ -356,6 +432,54 @@ impl<Ctx: device::DeviceContext> Interface<Ctx> {
     fn as_raw(&self) -> *mut bindings::usb_interface {
         self.0.get()
     }
+
+    /// Returns all alternate settings of this interface, in `bAlternateSetting` order.
+    ///
+    /// The slice is never empty: every interface has at least setting 0.
+    pub fn alternate_settings(&self) -> &[AlternateSetting] {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid `struct usb_interface`,
+        // so both fields are initialised. Reading them requires nothing of the device context:
+        // `altsetting` is filled in when the interface is created and holds until it is released.
+        let (ptr, len) = (unsafe { (*self.as_raw()).altsetting }, unsafe {
+            (*self.as_raw()).num_altsetting
+        });
+
+        if len == 0 {
+            &[]
+        } else {
+            // SAFETY: When `num_altsetting` is non-zero the C side has allocated an array of that
+            // many initialised `struct usb_host_interface` at `ptr`, kept alive by the interface's
+            // reference for at least as long as `&self`.
+            //
+            // `AlternateSetting` is a `#[repr(transparent)]` wrapper around
+            // `Opaque<bindings::usb_host_interface>`, which is itself layout-compatible with
+            // `struct usb_host_interface`, so the cast preserves both size and alignment and
+            // the resulting slice borrows for no longer than `&self`.
+            unsafe { slice::from_raw_parts(ptr.cast(), len as usize) }
+        }
+    }
+
+    /// Returns the alternate setting that is currently active on this interface.
+    ///
+    /// This is the setting whose endpoints the device is actually prepared to service, so it is
+    /// the one a driver should read endpoint descriptors from. After configuration it is
+    /// setting 0.
+    ///
+    /// The result is a snapshot. `usb_set_interface()` can repoint the interface at a different
+    /// setting, which does not invalidate the returned reference - both point into the same live
+    /// array - but does stop it being the current one. A driver that caches endpoints across a
+    /// setting switch will go on using the previous setting's descriptors.
+    pub fn current_alternate_setting(&self) -> &AlternateSetting {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid `struct usb_interface`.
+        // `cur_altsetting` is set when the interface is created and only ever repointed within
+        // that same array by `usb_set_interface()`, so it is non-null and names an initialised
+        // `struct usb_host_interface` for at least the lifetime of `&self`.
+        //
+        // `AlternateSetting` is a `#[repr(transparent)]` wrapper around
+        // `Opaque<bindings::usb_host_interface>` and so layout-compatible with it, and the borrow
+        // lasts no longer than `&self`.
+        unsafe { &*(*self.as_raw()).cur_altsetting.cast() }
+    }
 }
 
 // SAFETY: `usb::Interface` is a transparent wrapper of `struct usb_interface`.
diff --git a/rust/kernel/usb/endpoint.rs b/rust/kernel/usb/endpoint.rs
new file mode 100644
index 000000000000..17301ef4137d
--- /dev/null
+++ b/rust/kernel/usb/endpoint.rs
@@ -0,0 +1,508 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (C) 2026 Wyliodrin SRL.
+
+//! USB endpoints.
+//!
+//! C header: [`include/linux/usb.h`](srctree/include/linux/usb.h)
+//!
+//! An [`HostEndpoint`] is a borrowed view of a `struct usb_host_endpoint` - one of the addressable
+//! sources or sinks of data on a USB device. Its accessors read the endpoint descriptor the device
+//! reported: [`number()`](HostEndpoint::number) and [`address()`](HostEndpoint::address),
+//! [`direction()`](HostEndpoint::direction) and [`transfer_type()`](HostEndpoint::transfer_type),
+//! and the packet and interval geometry needed to size and schedule transfers.
+//!
+//! # Where endpoints come from
+//!
+//! Endpoints declared by an interface are borrowed from the alternate setting that describes them,
+//! via [`AlternateSetting::endpoints()`](crate::usb::AlternateSetting::endpoints). The
+//! default control endpoint - endpoint 0 - is not among them: no interface descriptor lists
+//! it, because the specification excludes it from `bNumEndpoints` and devices never send a
+//! descriptor for it. It is reached through [`Device::control_endpoint()`] instead.
+//!
+//! # Type-state
+//!
+//! [`HostEndpoint`] carries two marker type parameters recording what is *statically* known
+//! about it. Endpoints start out fully generic, and the `as_*` accessors check the
+//! descriptor once and hand back a reference that remembers the answer:
+//!
+//! ```text
+//!                              Endpoint<Generic, Generic>
+//!                                 |        |                   |
+//!                       as_in() <--        v as_control()      --> as_out()
+//!      Endpoint<In, Generic>   Endpoint<Bidirectional, Control>   Endpoint<Out, Generic>
+//! ```
+//!
+//! A function taking `&Endpoint<In, Bulk>` therefore cannot be handed anything but a bulk IN
+//! endpoint, and needs no run-time check of its own. The markers are [`PhantomData`], so this
+//! costs nothing at run time and a typed endpoint has the same layout as an untyped one.
+//!
+//! # Direction and control endpoints
+//!
+//! The two axes are not independent. A control endpoint is bidirectional: the direction of a
+//! control transfer comes from the setup packet's `bmRequestType`, and USB 2.0
+//! section 9.6.6 correspondingly defines bit 7 of `bEndpointAddress` as ignored
+//! for control endpoints. So the direction of a control endpoint is
+//! not merely unknown - it does not exist.
+//!
+//! That is what [`Bidirectional`] marks. Because [`as_in()`](HostEndpoint::as_in) and
+//! [`as_out()`](HostEndpoint::as_out) are defined only for [`Generic`],
+//! asking a control endpoint which direction it runs in is a compile error rather than
+//! an answer that would mislead whichever way it came out. See [`control`](crate::usb::control)
+//! for issuing transfers on one.
+//!
+//! # Examples
+//!
+//! ```
+//! use kernel::usb::{
+//!     endpoint::{HostEndpoint, In, Out},
+//!     AlternateSetting,
+//! };
+//!
+//! /// Picks out the first IN and OUT endpoints of an alternate setting.
+//! fn pair(alt: &AlternateSetting) -> (Option<&Endpoint<In>>, Option<&Endpoint<Out>>) {
+//!     let eps = alt.endpoints();
+//!
+//!     (
+//!         eps.iter().find_map(ep.as_in),
+//!         eps.iter().find_map(ep.as_out),
+//!     )
+//! }
+//! ```
+
+use core::marker::PhantomData;
+
+use crate::{device, types::Opaque, usb::Device};
+
+/// A single endpoint of an [`AlternateSetting`](crate::usb::AlternateSetting).
+///
+/// `Dir` and `Type` are compile-time markers recording what is statically known about the
+/// endpoint's direction and transfer type. An endpoint obtained from an
+/// [`AlternateSetting`](crate::usb::AlternateSetting) starts out as `Endpoint<Generic, Generic>`,
+/// i.e. nothing is known about it yet. The [`as_in()`], [`as_out()`] and [`as_control()`]
+/// accessors inspect the endpoint descriptor at run time and, on success, hand back a reference
+/// carrying the corresponding marker. Code that accepts only an `&Endpoint<In, Bulk>` may then
+/// rely on the endpoint really being a bulk IN endpoint without re-checking it.
+///
+/// Direction and transfer type are not independent: a control endpoint has no direction, so
+/// [`as_control()`] yields [`Bidirectional`] rather than preserving or discovering an IN/OUT
+/// marker, and [`as_in()`]/[`as_out()`] are not defined on the result.
+///
+/// The markers are [`PhantomData`], so a typed `HostEndpoint` has the same layout as
+/// an untyped one and the refinement is free at run time.
+///
+/// # Invariants
+///
+/// - The wrapped [`Opaque`] holds an initialised `struct usb_host_endpoint`. Instances are never
+///   constructed by Rust code; they are only ever borrowed out of a `struct usb_host_interface`
+///   owned by the C side, which guarantees that `desc` is initialised and that the endpoint
+///   outlives the reference.
+/// - If `Type` is [`Control`], [`Isochronous`], [`Bulk`] or [`Interrupt`], then
+///   `desc.bmAttributes` really encodes that transfer type.
+/// - If `Dir` is [`In`] or [`Out`], then bit 7 of `desc.bEndpointAddress` really encodes that
+///   direction. If `Dir` is [`Bidirectional`], then `Type` is [`Control`] and the direction bit
+///   carries no meaning at all.
+/// - [`Generic`] asserts nothing in either position.
+///
+/// [`as_in()`]: HostEndpoint::as_in
+/// [`as_out()`]: HostEndpoint::as_out
+/// [`as_control()`]: HostEndpoint::as_control
+#[repr(transparent)]
+pub struct HostEndpoint<Dir: EndpointDirection = Generic, Type: EndpointTransferType = Generic>(
+    Opaque<bindings::usb_host_endpoint>,
+    PhantomData<Dir>,
+    PhantomData<Type>,
+);
+
+/// A marker usable in the `Dir` position of [`HostEndpoint`].
+///
+/// This trait is sealed: it is implemented by [`Generic`], [`In`], [`Out`] and [`Bidirectional`]
+/// only, and cannot be implemented outside of this module.
+pub trait EndpointDirection: private::Sealed {}
+
+/// A marker usable in the `Type` position of [`HostEndpoint`].
+///
+/// This trait is sealed: it is implemented by [`Generic`], [`Control`], [`Isochronous`],
+/// [`Bulk`] and [`Interrupt`] only, and cannot be implemented outside of this module.
+pub trait EndpointTransferType: private::Sealed {}
+
+/// Marker for an [`HostEndpoint`] whose direction or transfer type is not statically known.
+///
+/// This is the default in both marker positions. It carries no guarantee, so the property has
+/// to be queried at run time with [`HostEndpoint::direction()`] or
+/// [`HostEndpoint::transfer_type()`], or established once and for all with one of
+/// the `as_*` accessors.
+pub struct Generic;
+
+/// Marker for a device-to-host ("IN") [`HostEndpoint`].
+pub struct In;
+
+/// Marker for a host-to-device ("OUT") [`HostEndpoint`].
+pub struct Out;
+
+/// Marker for an [`HostEndpoint`] that carries data in both directions.
+///
+/// This is the direction marker of every control endpoint, and the only marker they get. A control
+/// transfer takes the direction of its data stage from bit 7 of the setup
+/// packet's `bmRequestType`, and USB 2.0 section 9.6.6 correspondingly defines bit 7
+/// of `bEndpointAddress` as ignored for control endpoints - so "is this endpoint IN or OUT"
+/// has no answer for one.
+///
+/// Since [`HostEndpoint::as_in()`] and [`HostEndpoint::as_out()`] are defined only
+/// for [`Generic`], asking that question of a [`Bidirectional`] endpoint fails to compile rather
+/// than returning an answer that would be misleading either way.
+pub struct Bidirectional;
+
+/// Marker for an [`HostEndpoint`] using control transfers.
+pub struct Control;
+
+/// Marker for an [`HostEndpoint`] using isochronous transfers.
+pub struct Isochronous;
+
+/// Marker for an [`HostEndpoint`] using bulk transfers.
+pub struct Bulk;
+
+/// Marker for an [`HostEndpoint`] using interrupt transfers.
+pub struct Interrupt;
+
+mod private {
+    /// Prevents [`EndpointDirection`] and [`EndpointTransferType`] from being implemented
+    /// outside of this module, so that the type invariants of [`HostEndpoint`] cannot be forged by
+    /// downstream code.
+    ///
+    /// [`EndpointDirection`]: super::EndpointDirection
+    /// [`EndpointTransferType`]: super::EndpointTransferType
+    /// [`HostEndpoint`]: super::Endpoint
+    pub trait Sealed {}
+
+    impl Sealed for super::Generic {}
+    impl Sealed for super::In {}
+    impl Sealed for super::Out {}
+    impl Sealed for super::Bidirectional {}
+    impl Sealed for super::Control {}
+    impl Sealed for super::Isochronous {}
+    impl Sealed for super::Bulk {}
+    impl Sealed for super::Interrupt {}
+}
+
+impl EndpointDirection for Generic {}
+impl EndpointTransferType for Generic {}
+impl EndpointDirection for In {}
+impl EndpointDirection for Out {}
+impl EndpointDirection for Bidirectional {}
+impl EndpointTransferType for Control {}
+impl EndpointTransferType for Isochronous {}
+impl EndpointTransferType for Bulk {}
+impl EndpointTransferType for Interrupt {}
+
+/// The direction in which data flows over an [`HostEndpoint`].
+///
+/// The direction is fixed by the endpoint descriptor and is stated from the host's point of
+/// view. Control endpoints are bidirectional; for those the descriptor's direction bit is
+/// meaningless and this enum should be ignored.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum Direction {
+    /// Data flows from the device to the host (`USB_DIR_IN`).
+    In,
+    /// Data flows from the host to the device (`USB_DIR_OUT`).
+    Out,
+}
+
+impl From<u8> for Direction {
+    /// Extracts the direction from a raw `bEndpointAddress`.
+    ///
+    /// Bit 7 of the endpoint address is the direction bit: set means IN, clear means OUT. All
+    /// other bits are ignored, so any `u8` is a valid input.
+    fn from(value: u8) -> Self {
+        if (value >> 7) & 0b1 == 1 {
+            Direction::In
+        } else {
+            Direction::Out
+        }
+    }
+}
+
+/// The transfer type an [`HostEndpoint`] uses.
+///
+/// Every endpoint is fixed to exactly one of these by its descriptor; see USB 2.0 section 5.4
+/// for what each one guarantees.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum TransferType {
+    /// Bidirectional, request/response transfers with guaranteed delivery. Used for device
+    /// configuration; endpoint 0 is always a control endpoint.
+    Control,
+    /// Transfers with guaranteed bandwidth and bounded latency but no error retry, for
+    /// time-sensitive streams such as audio and video.
+    Isochronous,
+    /// Transfers with guaranteed delivery but no bandwidth or latency guarantee, for bulk data
+    /// such as mass storage.
+    Bulk,
+    /// Small, periodically polled transfers with bounded latency and guaranteed delivery, for
+    /// devices such as keyboards and mice.
+    Interrupt,
+}
+
+impl From<u8> for TransferType {
+    /// Extracts the transfer type from a raw `bmAttributes`.
+    ///
+    /// Bits 1:0 of the endpoint attributes hold the transfer type (`USB_ENDPOINT_XFERTYPE_MASK`)
+    /// and all four encodings are defined, so any `u8` is a valid input. The remaining bits,
+    /// which further describe isochronous endpoints, are ignored.
+    fn from(value: u8) -> Self {
+        match value & 0b11 {
+            0 => TransferType::Control,
+            1 => TransferType::Isochronous,
+            2 => TransferType::Bulk,
+            _ => TransferType::Interrupt,
+        }
+    }
+}
+
+impl<Dir: EndpointDirection, Type: EndpointTransferType> HostEndpoint<Dir, Type> {
+    /// Returns a raw pointer to the underlying `struct usb_host_endpoint`.
+    ///
+    /// By the type invariants the pointer is non-null and points at an initialised endpoint for
+    /// at least the lifetime of `&self`.
+    fn as_raw(&self) -> *mut bindings::usb_host_endpoint {
+        self.0.get()
+    }
+
+    /// Returns the endpoint number, i.e. bits 3:0 of `bEndpointAddress`.
+    ///
+    /// The number alone does not identify an endpoint: an IN and an OUT endpoint of the same
+    /// interface may share one, so pair it with [`direction()`](Self::direction), or use
+    /// [`address()`](Self::address), which combines the two.
+    pub fn number(&self) -> u8 {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid
+        // `struct usb_host_endpoint` with an initialised `desc`.
+        unsafe { (*self.as_raw()).desc.bEndpointAddress & 0x0f }
+    }
+
+    /// Returns the full `bEndpointAddress` of the endpoint descriptor.
+    ///
+    /// The byte packs the endpoint number in bits 3:0 and the direction in bit 7 (set for IN,
+    /// clear for OUT); bits 6:4 are reserved and zero. Taken together those fields uniquely
+    /// identify the endpoint within its configuration, which is why this is the value host-side
+    /// APIs use to name an endpoint - for example when constructing a URB pipe.
+    ///
+    /// For a control endpoint bit 7 is defined to be ignored, so the byte should not be read as a
+    /// direction there; see [`Bidirectional`].
+    ///
+    /// Use [`number()`](Self::number) or [`direction()`](Self::direction) to get the fields
+    /// individually.
+    pub fn address(&self) -> u8 {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid
+        // `struct usb_host_endpoint` with an initialised `desc`.
+        unsafe { (*self.as_raw()).desc.bEndpointAddress }
+    }
+
+    /// Returns the direction data flows in over this endpoint.
+    ///
+    /// Meaningless for control endpoints, which are bidirectional; what it reports for one is
+    /// whatever bit 7 of the address byte happens to hold, which the specification leaves
+    /// undefined.
+    pub fn direction(&self) -> Direction {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid
+        // `struct usb_host_endpoint` with an initialised `desc`.
+        unsafe { (*self.as_raw()).desc.bEndpointAddress.into() }
+    }
+
+    /// Returns the transfer type this endpoint uses.
+    pub fn transfer_type(&self) -> TransferType {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid
+        // `struct usb_host_endpoint` with an initialised `desc`.
+        unsafe { (*self.as_raw()).desc.bmAttributes.into() }
+    }
+
+    /// Returns the maximum payload size of a single transaction, in bytes.
+    ///
+    /// This is bits 10:0 of `wMaxPacketSize`. The permitted values depend on the transfer type
+    /// and the speed the device is operating at; for high-speed isochronous and interrupt
+    /// endpoints the total payload per microframe is this value multiplied by the number of
+    /// transactions per microframe, which [`max_packet_mult()`](Self::max_packet_mult)
+    /// describes.
+    pub fn max_packet_size(&self) -> u16 {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid
+        // `struct usb_host_endpoint` with an initialised `desc`.
+        unsafe { (*self.as_raw()).desc.wMaxPacketSize & 0x07ff }
+    }
+
+    /// Returns the number of *additional* transaction opportunities per microframe.
+    ///
+    /// This is bits 12:11 of `wMaxPacketSize`, so the total number of transactions per microframe
+    /// is one more than the returned value. The field is only defined for high-speed isochronous
+    /// and interrupt endpoints and must not be used for anything else.
+    ///
+    /// The specification defines the encodings `0..=2`; `3` is reserved, so a conforming device
+    /// never reports it, but a malformed descriptor can and this returns it unchanged.
+    pub fn max_packet_multipier(&self) -> u8 {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid
+        // `struct usb_host_endpoint` with an initialised `desc`.
+        unsafe { (((*self.as_raw()).desc.wMaxPacketSize >> 11) & 0b11) as u8 + 1 }
+    }
+}
+
+impl HostEndpoint<Generic, Generic> {
+    /// Refines this endpoint into a control endpoint, if it is one.
+    ///
+    /// Returns [`None`] if [`transfer_type()`](Self::transfer_type) is not
+    /// [`TransferType::Control`].
+    ///
+    /// The result is [`Bidirectional`], not the direction the descriptor happens to record: bit 7
+    /// of a control endpoint's address is defined to be ignored, so there is nothing to preserve.
+    /// That is also why this is only available on a fully generic endpoint - refining direction
+    /// first and transfer type second would otherwise produce an `Endpoint<In, Control>`, a
+    /// combination that has no meaning.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::usb::{
+    ///     Hostendpoint::{Bidirectional, Control, Endpoint},
+    ///     AlternateSetting,
+    /// };
+    ///
+    /// /// Finds an interface's own control endpoint, if it declares one.
+    /// ///
+    /// /// This never finds endpoint 0, which no interface descriptor lists; reach that one
+    /// /// through `Device::control_endpoint()` instead.
+    /// fn extra_control(alt: &AlternateSetting) -> Option<&Endpoint<Bidirectional, Control>> {
+    ///     alt.endpoints().iter().find_map(|ep| ep.as_control())
+    /// }
+    /// ```
+    pub fn as_control(&self) -> Option<&HostEndpoint<Bidirectional, Control>> {
+        matches!(self.transfer_type(), TransferType::Control).then(|| {
+            // SAFETY: The transfer type was just checked, so the [`Control`] invariant holds, and
+            // [`Bidirectional`] is exactly what a control endpoint warrants.
+            // `Endpoint<Bidirectional, Control>` is a `#[repr(transparent)]` wrapper around the
+            // same `struct usb_host_endpoint` and differs only in its `PhantomData` markers, which
+            // are 1-ZSTs, so the two types have identical layout and the reference stays valid for
+            // the same lifetime.
+            unsafe { &*core::ptr::from_ref(self).cast() }
+        })
+    }
+}
+
+impl<Type: EndpointTransferType> HostEndpoint<Generic, Type> {
+    /// Refines this endpoint into a device-to-host endpoint, if it is one.
+    ///
+    /// Returns [`None`] if [`direction()`](Self::direction) is not [`Direction::In`]. The known
+    /// transfer type, if any, is preserved.
+    pub fn as_in(&self) -> Option<&HostEndpoint<In, Type>> {
+        if self.direction() == Direction::In {
+            // SAFETY: The direction was just checked, so the [`In`] invariant holds.
+            // `Endpoint<In, Type>` is a `#[repr(transparent)]` wrapper around the same
+            // `struct usb_host_endpoint` and differs only in its `PhantomData` markers, which
+            // are 1-ZSTs, so the two types have identical layout and the reference stays valid
+            // for the same lifetime.
+            unsafe { Some(&*core::ptr::from_ref(self).cast()) }
+        } else {
+            None
+        }
+    }
+
+    /// Refines this endpoint into a host-to-device endpoint, if it is one.
+    ///
+    /// Returns [`None`] if [`direction()`](Self::direction) is not [`Direction::Out`]. The known
+    /// transfer type, if any, is preserved.
+    pub fn as_out(&self) -> Option<&HostEndpoint<Out, Type>> {
+        if self.direction() == Direction::Out {
+            // SAFETY: The direction was just checked, so the [`Out`] invariant holds.
+            // `Endpoint<Out, Type>` is a `#[repr(transparent)]` wrapper around the same
+            // `struct usb_host_endpoint` and differs only in its `PhantomData` markers, which
+            // are 1-ZSTs, so the two types have identical layout and the reference stays valid
+            // for the same lifetime.
+            unsafe { Some(&*core::ptr::from_ref(self).cast()) }
+        } else {
+            None
+        }
+    }
+}
+
+impl<Generic: EndpointDirection> HostEndpoint<Generic, Interrupt> {
+    /// Returns the raw `bInterval` value of the endpoint descriptor.
+    ///
+    /// How this encodes a service interval depends on the transfer type and the device's
+    /// operating speed:
+    ///
+    /// - Full-/low-speed interrupt endpoints: the interval in frames (1 ms), `1..=255`.
+    /// - High-speed interrupt endpoints and all isochronous endpoints: an exponent, giving an
+    ///   interval of `2^(bInterval - 1)` microframes (125 micros), with `bInterval` in `1..=16`.
+    pub fn interval(&self) -> u8 {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid
+        // `struct usb_host_endpoint` with an initialised `desc`.
+        unsafe { (*self.as_raw()).desc.bInterval }
+    }
+}
+
+impl HostEndpoint<Out, Control> {
+    /// Returns the raw `bInterval` value of the endpoint descriptor.
+    ///
+    /// This applies only to high-speed bulk/control OUT endpoints and
+    /// represents the maximum NAK rate, or zero for no limit.
+    pub fn max_nak_rate(&self) -> u8 {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid
+        // `struct usb_host_endpoint` with an initialised `desc`.
+        unsafe { (*self.as_raw()).desc.bInterval }
+    }
+}
+
+impl HostEndpoint<Out, Bulk> {
+    /// Returns the raw `bInterval` value of the endpoint descriptor.
+    ///
+    /// This applies only to high-speed bulk/control OUT endpoints and
+    /// represents the maximum NAK rate, or zero for no limit.
+    pub fn max_nak_rate(&self) -> u8 {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid
+        // `struct usb_host_endpoint` with an initialised `desc`.
+        unsafe { (*self.as_raw()).desc.bInterval }
+    }
+}
+
+impl<Ctx: device::DeviceContext> Device<Ctx> {
+    /// Returns the default control endpoint (endpoint 0) of this device.
+    ///
+    /// Every USB device has exactly one, and it is the endpoint all enumeration and standard
+    /// requests travel over. Unlike the endpoints of an
+    /// [`AlternateSetting`](crate::usb::AlternateSetting), it is not described by any
+    /// descriptor the device sends: the USB core synthesizes its descriptor
+    /// in `usb_alloc_dev()` and fills in the packet size from `bMaxPacketSize0` of the device
+    /// descriptor during enumeration. It therefore cannot be found by searching
+    /// [`AlternateSetting::endpoints()`](crate::usb::AlternateSetting::endpoints), and
+    /// this accessor is the only way to reach it.
+    ///
+    /// Neither marker needs a run-time check. [`Control`] holds because the core sets
+    /// `bmAttributes` to `USB_ENDPOINT_XFER_CONTROL` itself and the specification fixes endpoint 0
+    /// as a control endpoint; [`Bidirectional`] holds because control endpoints have no direction.
+    /// Endpoint 0's address byte is `0x00`, so [`direction()`](HostEndpoint::direction)
+    /// would report [`Direction::Out`] - a meaningless answer, which is why the direction
+    /// refinements are not available on the returned type. Route control transfers
+    /// on `bmRequestType` instead.
+    ///
+    /// No device-state bound is required: a `struct usb_device` has a valid `ep0` from allocation
+    /// onwards, so this is available wherever a [`Device`] is.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::usb::Device;
+    ///
+    /// fn ep0_packet_size(dev: &Device) -> u16 {
+    ///     dev.control_endpoint().max_packet_size()
+    /// }
+    /// ```
+    pub fn control_endpoint(&self) -> &HostEndpoint<Bidirectional, Control> {
+        // SAFETY: By the type invariants, `self.as_raw()` points at a valid `struct usb_device`.
+        // `ep0` is an embedded field rather than a pointer, so it is live for as long as the
+        // device is, and `usb_alloc_dev()` has initialised its descriptor.
+        let ep0 = unsafe { core::ptr::addr_of!((*self.as_raw()).ep0) };
+
+        // SAFETY: `HostEndpoint` is a `#[repr(transparent)]` wrapper around
+        // `Opaque<bindings::usb_host_endpoint>`, which is layout-compatible with
+        // `struct usb_host_endpoint`, so the cast preserves size and alignment. The [`Control`]
+        // invariant holds because the core fixes `ep0.desc.bmAttributes` to
+        // `USB_ENDPOINT_XFER_CONTROL`, and [`Bidirectional`] holds for any control endpoint. The
+        // `Opaque` accounts for the C side mutating the endpoint behind this shared reference, and
+        // the borrow lasts no longer than `&self`.
+        unsafe { &*ep0.cast() }
+    }
+}

-- 
2.55.0


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

* [PATCH RFC 2/2] rust: usb: add control message send and receive
  2026-08-01  0:01 [PATCH RFC 0/2] rust: usb: abstractions towards the port of usbsevseg.c to Rust Alexandru Radovici
  2026-08-01  0:01 ` [PATCH RFC 1/2] rust: usb: add endpoint abstraction Alexandru Radovici
@ 2026-08-01  0:01 ` Alexandru Radovici
  2026-08-02  8:32   ` Greg Kroah-Hartman
  1 sibling, 1 reply; 5+ messages in thread
From: Alexandru Radovici @ 2026-08-01  0:01 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Miguel Ojeda, 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
  Cc: linux-kernel, linux-usb, rust-for-linux, Alexandru Radovici

Add `Device::send_control_message()` and
`Device::receive_control_message()`, wrapping usb_control_msg_send()
and usb_control_msg_recv(), plus a `Request` type describing the
bmRequestType, bRequest, wValue and wIndex fields of a setup packet.

`Request` deliberately omits the two setup packet fields that are
properties of a submission rather than of the request itself: the
direction bit of bmRequestType and wLength. Both are derived from the
method called and the buffer passed, so a caller cannot describe an
inbound transfer while handing over a read-only buffer, nor set a
length that disagrees with one.

Both methods take a `&HostEndpoint<Bidirectional, Control>`, so a
non-control endpoint cannot be passed by construction.

Signed-off-by: Alexandru Radovici <alexandru.radovici@wyliodrin.com>
---
 rust/kernel/usb.rs         |   3 +-
 rust/kernel/usb/control.rs | 353 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 355 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs
index 55c627be1658..7d23186630ce 100644
--- a/rust/kernel/usb.rs
+++ b/rust/kernel/usb.rs
@@ -33,6 +33,7 @@
     slice,
 };
 
+pub mod control;
 pub mod endpoint;
 
 /// An adapter for the registration of USB drivers.
@@ -550,7 +551,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>,
 );
diff --git a/rust/kernel/usb/control.rs b/rust/kernel/usb/control.rs
new file mode 100644
index 000000000000..32edd17ab90d
--- /dev/null
+++ b/rust/kernel/usb/control.rs
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (C) 2026 Wyliodrin SRL.
+
+//! USB control transfers.
+//!
+//! Control transfers are the request/response mechanism every USB device must support. A transfer
+//! consists of an 8-byte setup packet, an optional data stage, and a status stage;
+//! the setup packet is described by [`Request`], and the direction of the data stage is chosen by
+//! calling either [`Device::send_control_message()`] or [`Device::receive_control_message()`].
+//!
+//! Both travel over a control endpoint - usually the device's default one, from
+//! [`Device::control_endpoint()`]. Because control endpoints are bidirectional, the endpoint does
+//! not determine the direction: the `Direction` bit of `bmRequestType` does, and these two methods
+//! set it so it cannot disagree with the buffer you passed.
+
+use core::ptr;
+
+use ffi::c_void;
+
+use crate::{
+    alloc::Flags,
+    bindings,
+    error::{code::EOVERFLOW, Error, Result},
+    num::Bounded,
+    usb::{
+        endpoint::{Bidirectional, Control, Direction, HostEndpoint},
+        Device,
+    },
+};
+
+/// Position of the `Type` field within `bmRequestType` (bits 6:5).
+const REQUEST_TYPE_SHIFT: u32 = 5;
+
+/// Which part of the USB specification defines the meaning of a [`Request`].
+///
+/// This is the `Type` field of `bmRequestType`, occupying bits 6:5 of the setup packet's first
+/// byte. It selects the namespace that [`Request::request`] is interpreted in, so the same request
+/// code means different things under different types.
+#[derive(Copy, Clone, PartialEq, PartialOrd)]
+#[repr(u8)]
+pub enum RequestType {
+    /// Request is a USB standard request, defined by the specification itself and interpreted the
+    /// same way by every device. Usually handled by the USB core rather than by a driver; see
+    /// [`Device`].
+    Standard = 0,
+    /// Request is intended for a USB class.
+    Class = 1,
+    /// Request is vendor-specific.
+    Vendor = 2,
+    /// Reserved.
+    Reserved = 3,
+}
+
+/// The target a [`Request`] is addressed to.
+///
+/// This is the `Recipient` field of `bmRequestType`, occupying bits 4:0 of the setup
+/// packet's first byte. For [`INTERFACE`](Self::INTERFACE) and [`ENDPOINT`](Self::ENDPOINT)
+/// the specific interface or endpoint is named by [`Request::index`]; the other recipients
+/// ignore it or give it a request-specific meaning.
+///
+/// Although the field is five bits wide, only the values below are defined - hence
+/// the bound on the wrapped [`Bounded`].
+#[derive(Copy, Clone, PartialEq, PartialOrd)]
+#[repr(transparent)]
+pub struct Recipient(Bounded<u8, 5>);
+
+impl Recipient {
+    /// The device as a whole.
+    pub const DEVICE: Self = Self(Bounded::<u8, 5>::new::<0u8>());
+    /// A specific interface, named by [`Request::index`].
+    pub const INTERFACE: Self = Self(Bounded::<u8, 5>::new::<1u8>());
+    /// A specific endpoint, named by [`Request::index`].
+    pub const ENDPOINT: Self = Self(Bounded::<u8, 5>::new::<2u8>());
+    /// Some other target, defined by the request itself.
+    pub const OTHER: Self = Self(Bounded::<u8, 5>::new::<3u8>());
+    /// A port. Wireless USB only.
+    pub const PORT: Self = Self(Bounded::<u8, 5>::new::<4u8>());
+    /// An RPipe. Wireless USB only.
+    pub const RPIPE: Self = Self(Bounded::<u8, 5>::new::<5u8>());
+
+    /// Builds a recipient from a raw field value.
+    ///
+    /// Prefer the associated constants; this exists for values a future specification revision may
+    /// define. The [`Bounded`] parameter rules out values the field cannot hold, but does not
+    /// guarantee the device understands the one you pass.
+    pub fn new(val: Bounded<u8, 5>) -> Recipient {
+        Recipient(val)
+    }
+}
+
+/// The setup packet of a control transfer, minus the direction and length.
+///
+/// These fields map onto the setup packet as `bmRequestType` (from
+/// [`request_type`](Self::request_type) and [`recipient`](Self::recipient)), `bRequest`, `wValue`
+/// and `wIndex`. The remaining two - the `Direction` bit of `bmRequestType` and `wLength` - are
+/// filled in by [`Device::send_control_message()`] and [`Device::receive_control_message()`] from
+/// the method you call and the buffer you hand it, which is what keeps them consistent with each
+/// other.
+pub struct Request {
+    /// Type of the request.
+    pub request_type: RequestType,
+    /// Recipient of the request.
+    pub recipient: Recipient,
+    /// Request code. The meaning of the value depends on the previous fields.
+    pub request: u8,
+    /// Request value. The meaning of the value depends on the previous fields.
+    pub value: u16,
+    /// Request index. The meaning of the value depends on the previous fields.
+    pub index: u16,
+}
+
+impl Request {
+    /// Encodes this request into a `bmRequestType` byte for a data stage flowing in `direction`.
+    ///
+    /// `direction` here is a property of the transfer, not of the endpoint it runs over: bit 7 of
+    /// `bmRequestType` is what the host controller obeys for a control transfer, and bit 7 of the
+    /// endpoint address is defined to be ignored.
+    fn bm_request_type(&self, direction: Direction) -> u8 {
+        let dir = match direction {
+            // `USB_DIR_IN` is `0x80`; `USB_DIR_OUT` is zero.
+            Direction::In => bindings::USB_DIR_IN as u8,
+            Direction::Out => 0,
+        };
+
+        dir | ((self.request_type as u8) << REQUEST_TYPE_SHIFT) | self.recipient.0.get()
+    }
+}
+
+/// Converts a buffer length into a `wLength` value.
+///
+/// The field is 16 bits, so anything larger cannot be expressed in a single control transfer.
+fn transfer_length(len: usize) -> Result<u16> {
+    u16::try_from(len).map_err(|_| EOVERFLOW)
+}
+
+impl Device {
+    /// Performs a control transfer with an outbound data stage, i.e. host to device.
+    ///
+    /// The `Direction` bit of `bmRequestType` is set to `USB_DIR_OUT` for you. Pass [`None`] as
+    /// `data` for a request with no data stage at all, which sets `wLength` to zero.
+    ///
+    /// `endpoint` selects the control endpoint to use; it must belong to this device, since only
+    /// its [`number()`](Endpoint::number) is taken from it. For the default control endpoint -
+    /// almost always the right choice - pass [`control_endpoint()`](Device::control_endpoint).
+    ///
+    /// `timeout` is in milliseconds, with zero meaning "wait forever". `memflags` are used for an
+    /// internal copy of `data`, so `data` itself need not be DMA-capable.
+    ///
+    /// Returns `Ok(())` only if the whole request completed; unlike `usb_control_msg()` there
+    /// is no partial-success case to inspect.
+    ///
+    /// This sleeps, so it must not be called from atomic context.
+    ///
+    /// # Examples
+    ///
+    /// A vendor-specific request with no data stage:
+    ///
+    /// ```
+    /// use kernel::{
+    ///     alloc::flags::GFP_KERNEL,
+    ///     error::Result,
+    ///     usb::{
+    ///         transfer::{Recipient, Request, RequestType},
+    ///         Device,
+    ///     },
+    /// };
+    ///
+    /// fn set_led(dev: &Device, on: bool) -> Result {
+    ///     dev.send_control_message(
+    ///         dev.control_endpoint(),
+    ///         Request {
+    ///             request_type: RequestType::Vendor,
+    ///             recipient: Recipient::DEVICE,
+    ///             request: 0x01,
+    ///             value: on as u16,
+    ///             index: 0,
+    ///         },
+    ///         None,
+    ///         1000,
+    ///         GFP_KERNEL,
+    ///     )
+    /// }
+    /// ```
+    ///
+    /// A class request that carries a payload:
+    ///
+    /// ```
+    /// use kernel::{
+    ///     alloc::flags::GFP_KERNEL,
+    ///     error::Result,
+    ///     usb::{
+    ///         transfer::{Recipient, Request, RequestType},
+    ///         Device,
+    ///     },
+    /// };
+    ///
+    /// fn set_line_coding(dev: &Device, interface: u8, coding: &[u8; 7]) -> Result {
+    ///     dev.send_control_message(
+    ///         dev.control_endpoint(),
+    ///         Request {
+    ///             request_type: RequestType::Class,
+    ///             recipient: Recipient::INTERFACE,
+    ///             request: 0x20,
+    ///             value: 0,
+    ///             index: interface as u16,
+    ///         },
+    ///         Some(coding),
+    ///         1000,
+    ///         GFP_KERNEL,
+    ///     )
+    /// }
+    /// ```
+    pub fn send_control_message(
+        &self,
+        endpoint: &HostEndpoint<Bidirectional, Control>,
+        request: Request,
+        data: Option<&[u8]>,
+        timeout: i32,
+        memflags: Flags,
+    ) -> Result {
+        let (data, size) = match data {
+            Some(bytes) => (
+                bytes.as_ptr().cast::<c_void>(),
+                transfer_length(bytes.len())?,
+            ),
+            None => (ptr::null(), 0),
+        };
+
+        // SAFETY: `self.as_raw()` is a valid `struct usb_device` by the type invariants of
+        // [`Device`], and `endpoint` belongs to it, so its number names a control endpoint of this
+        // device. `data` is either null with `size` zero, or points at `size` initialised bytes
+        // that outlive the call - the callee only reads from it, and copies before submitting, so
+        // no DMA is performed on the caller's buffer.
+        let ret = unsafe {
+            bindings::usb_control_msg_send(
+                self.as_raw(),
+                endpoint.number(),
+                request.request,
+                request.bm_request_type(Direction::Out),
+                request.value,
+                request.index,
+                data,
+                size,
+                timeout,
+                memflags.as_raw(),
+            )
+        };
+
+        if ret < 0 {
+            Err(Error::from_errno(ret))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Performs a control transfer with an inbound data stage, i.e. device to host.
+    ///
+    /// The `Direction` bit of `bmRequestType` is set to `USB_DIR_IN` for you. Pass [`None`] as
+    /// `data` for a request with no data stage at all, which sets `wLength` to zero - note that
+    /// this makes the direction bit meaningless, so such a request is indistinguishable from the
+    /// [`send_control_message()`](Device::send_control_message) equivalent.
+    ///
+    /// `endpoint` selects the control endpoint to use; it must belong to this device, since only
+    /// its [`number()`](Endpoint::number) is taken from it. For the default control endpoint -
+    /// almost always the right choice - pass [`control_endpoint()`](Device::control_endpoint).
+    ///
+    /// `timeout` is in milliseconds, with zero meaning "wait forever". `memflags` are used for an
+    /// internal DMA-capable buffer that is copied into `data` on success, so `data`
+    /// itself need not be DMA-capable.
+    ///
+    /// The transfer must fill `data` exactly. A device that returns fewer bytes than requested
+    /// fails with `EREMOTEIO` and leaves `data` untouched, so this is not the right method for
+    /// requests of variable-length descriptors - size the buffer from the device's own length
+    /// field, or use a lower-level transfer.
+    ///
+    /// This sleeps, so it must not be called from atomic context.
+    ///
+    /// # Examples
+    ///
+    /// Reading a fixed-size vendor-specific register:
+    ///
+    /// ```
+    /// use kernel::{
+    ///     alloc::flags::GFP_KERNEL,
+    ///     error::Result,
+    ///     usb::{
+    ///         transfer::{Recipient, Request, RequestType},
+    ///         Device,
+    ///     },
+    /// };
+    ///
+    /// fn firmware_version(dev: &Device) -> Result<u16> {
+    ///     let mut buf = [0u8; 2];
+    ///
+    ///     dev.receive_control_message(
+    ///         dev.control_endpoint(),
+    ///         Request {
+    ///             request_type: RequestType::Vendor,
+    ///             recipient: Recipient::DEVICE,
+    ///             request: 0x02,
+    ///             value: 0,
+    ///             index: 0,
+    ///         },
+    ///         Some(&mut buf),
+    ///         1000,
+    ///         GFP_KERNEL,
+    ///     )?;
+    ///
+    ///     Ok(u16::from_le_bytes(buf))
+    /// }
+    /// ```
+    pub fn receive_control_message(
+        &self,
+        endpoint: &HostEndpoint<Bidirectional, Control>,
+        request: Request,
+        data: Option<&mut [u8]>,
+        timeout: i32,
+        memflags: Flags,
+    ) -> Result {
+        let (data, size) = match data {
+            Some(bytes) => {
+                let size = transfer_length(bytes.len())?;
+                (bytes.as_mut_ptr().cast::<c_void>(), size)
+            }
+            None => (ptr::null_mut(), 0),
+        };
+
+        // SAFETY: `self.as_raw()` is a valid `struct usb_device` by the type invariants of
+        // [`Device`], and `endpoint` belongs to it, so its number names a control endpoint of this
+        // device. `data` is either null with `size` zero, or points at `size` bytes of a buffer
+        // uniquely borrowed for the duration of the call, which the callee may only write to.
+        let ret = unsafe {
+            bindings::usb_control_msg_recv(
+                self.as_raw(),
+                endpoint.number(),
+                request.request,
+                request.bm_request_type(Direction::In),
+                request.value,
+                request.index,
+                data,
+                size,
+                timeout,
+                memflags.as_raw(),
+            )
+        };
+
+        if ret < 0 {
+            Err(Error::from_errno(ret))
+        } else {
+            Ok(())
+        }
+    }
+}

-- 
2.55.0


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

* Re: [PATCH RFC 2/2] rust: usb: add control message send and receive
  2026-08-01  0:01 ` [PATCH RFC 2/2] rust: usb: add control message send and receive Alexandru Radovici
@ 2026-08-02  8:32   ` Greg Kroah-Hartman
  0 siblings, 0 replies; 5+ messages in thread
From: Greg Kroah-Hartman @ 2026-08-02  8:32 UTC (permalink / raw)
  To: Alexandru Radovici
  Cc: Miguel Ojeda, 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, linux-kernel, linux-usb,
	rust-for-linux

On Sat, Aug 01, 2026 at 03:01:08AM +0300, Alexandru Radovici wrote:
> Add `Device::send_control_message()` and
> `Device::receive_control_message()`, wrapping usb_control_msg_send()
> and usb_control_msg_recv(), plus a `Request` type describing the
> bmRequestType, bRequest, wValue and wIndex fields of a setup packet.

Why not keep the names as close as possible to the C calls?  You have
switched around the noun/verb use here.

thanks,

greg k-h

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

* Re: [PATCH RFC 1/2] rust: usb: add endpoint abstraction
  2026-08-01  0:01 ` [PATCH RFC 1/2] rust: usb: add endpoint abstraction Alexandru Radovici
@ 2026-08-02  8:39   ` Greg Kroah-Hartman
  0 siblings, 0 replies; 5+ messages in thread
From: Greg Kroah-Hartman @ 2026-08-02  8:39 UTC (permalink / raw)
  To: Alexandru Radovici
  Cc: Miguel Ojeda, 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, linux-kernel, linux-usb,
	rust-for-linux

On Sat, Aug 01, 2026 at 03:01:07AM +0300, Alexandru Radovici wrote:
> Add an abstraction for `struct usb_host_endpoint`, together with the
> accessors needed to reach one: `AlternateSetting` wrapping
> `struct usb_host_interface`, `Interface::alternate_settings()` and
> `Interface::current_alternate_setting()`, and `Device::control_endpoint()`
> for the default control endpoint, which no interface descriptor lists.

Why?  USB drivers shouldn't be messing with usb_host_endpoint structures
for the most part, what user do you have for this?

> `HostEndpoint` is generic over two sealed marker traits,
> `EndpointDirection` and `EndpointTransferType`, whose implementors are
> 1-ZSTs held in `PhantomData`. An endpoint borrowed from an alternate
> setting starts out generic in both; `as_in()`, `as_out()` and
> `as_control()` check the descriptor once and return a reference
> carrying the corresponding marker, so a function taking
> `&HostEndpoint<In, Bulk>` needs no check of its own. The type is
> `#[repr(transparent)]` over the C struct and the markers are
> zero-sized, so the refinement costs nothing and a slice of endpoints
> can be borrowed directly from the C array.
> 
> Control endpoints get a distinct `Bidirectional` marker rather than an
> IN or OUT one. A control transfer takes its direction from bit 7 of the
> setup packet's bmRequestType, and USB 2.0 section 9.6.6 defines the
> corresponding bit of bEndpointAddress as ignored for control endpoints.
> `as_in()` and `as_out()` are not implemented for `Bidirectional`, making
> calling them a compile error rather than a misleading result.

Don't over-think USB endpoints, they are "just" a pipe that contain a
numbering scheme that the USB core uses.  Is that what you are trying to
create here?  What are you trying to "enforce" here that the C code does
not already do?

thanks,

greg k-h

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

end of thread, other threads:[~2026-08-02  8:41 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-01  0:01 [PATCH RFC 0/2] rust: usb: abstractions towards the port of usbsevseg.c to Rust Alexandru Radovici
2026-08-01  0:01 ` [PATCH RFC 1/2] rust: usb: add endpoint abstraction Alexandru Radovici
2026-08-02  8:39   ` Greg Kroah-Hartman
2026-08-01  0:01 ` [PATCH RFC 2/2] rust: usb: add control message send and receive Alexandru Radovici
2026-08-02  8:32   ` Greg Kroah-Hartman

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