* [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; 8+ 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] 8+ 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; 8+ 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] 8+ 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; 8+ 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] 8+ 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
2026-08-03 11:39 ` Alrexandru Radovici
0 siblings, 1 reply; 8+ 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] 8+ 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
2026-08-03 12:46 ` Alrexandru Radovici
0 siblings, 1 reply; 8+ 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] 8+ messages in thread
* Re: [PATCH RFC 2/2] rust: usb: add control message send and receive
2026-08-02 8:32 ` Greg Kroah-Hartman
@ 2026-08-03 11:39 ` Alrexandru Radovici
0 siblings, 0 replies; 8+ messages in thread
From: Alrexandru Radovici @ 2026-08-03 11:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, 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 Sun Aug 2, 2026 at 11:32 AM EEST, Greg Kroah-Hartman wrote:
> 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.
Sorry, my bad, I will rename them in v2 to `control_message_send` and
`control_message_receive`. Thanks for pointing this out.
> thanks,
>
> greg k-h
Alexandru
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH RFC 1/2] rust: usb: add endpoint abstraction
2026-08-02 8:39 ` Greg Kroah-Hartman
@ 2026-08-03 12:46 ` Alrexandru Radovici
2026-08-03 13:23 ` Greg Kroah-Hartman
0 siblings, 1 reply; 8+ messages in thread
From: Alrexandru Radovici @ 2026-08-03 12:46 UTC (permalink / raw)
To: Greg Kroah-Hartman, 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 Sun Aug 2, 2026 at 11:39 AM EEST, Greg Kroah-Hartman wrote:
> 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?
The more I think of this, I think you are right. `HostEndpoint`'s accessor
methods are only used for debug, as the `kernel` crate can access
the actual `usb_host_endpoint` underneeth. For debug purposes, we should
just derive the `Debug` trait instead.
>
>> `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?
My USB knowledge is limited, so I hope I am not saying something
stupid here. My understanding is that drivers should not expect
interfaces to map the same endpoints (numbers) every time.
A driver should expect an interface to expose a certain number of
endpoints, each one with a certain type, but the actual number of each
exposed endpoint is not to be considered hardcoded. This means that drivers
should anyway iterate over the endpoints to discover the numbers
of the required endpoints.
My idea is to leaverage Rust's type system to prevent users from supplying
the wrong endpoint type at compile time rather then at runtime. By making
the `HostEndpoint` its own Rust type with no public constructor,
users will be forced to iterate the endpoints to discover the correct
number for each endpoint that they require. Once they have it, users
can hold to the reference as long as the interface is valid.
By adding the `Dir` and `Type` generic markers, suplying the wrong endpoint
to a function will be caught at compile time rather than at runtime. This
should hopefully shorthen the debug work needed for a driver, as some of
the errors become impossible.
As endpoint 0 is always provided and basically _almost hardcoded_``, I added
the `control_endpoint` function.
Best regards,
Alexandru
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH RFC 1/2] rust: usb: add endpoint abstraction
2026-08-03 12:46 ` Alrexandru Radovici
@ 2026-08-03 13:23 ` Greg Kroah-Hartman
0 siblings, 0 replies; 8+ messages in thread
From: Greg Kroah-Hartman @ 2026-08-03 13:23 UTC (permalink / raw)
To: Alrexandru 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 Mon, Aug 03, 2026 at 03:46:44PM +0300, Alrexandru Radovici wrote:
> On Sun Aug 2, 2026 at 11:39 AM EEST, Greg Kroah-Hartman wrote:
> > 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?
>
> The more I think of this, I think you are right. `HostEndpoint`'s accessor
> methods are only used for debug, as the `kernel` crate can access
> the actual `usb_host_endpoint` underneeth. For debug purposes, we should
> just derive the `Debug` trait instead.
Great, if it's even really needed. Let's see how that works out, as I
don't know what you want to provide for debugging.
> >> `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?
>
> My USB knowledge is limited, so I hope I am not saying something
> stupid here. My understanding is that drivers should not expect
> interfaces to map the same endpoints (numbers) every time.
Why not? Well, they can, or can not, depending on the device, and the
driver knows this. For some drivers, a specific endpoint will _ALWAYS_
be a specific number, while for others, they are dynamically determined.
It depends on the device/protocol being used.
> A driver should expect an interface to expose a certain number of
> endpoints, each one with a certain type, but the actual number of each
> exposed endpoint is not to be considered hardcoded. This means that drivers
> should anyway iterate over the endpoints to discover the numbers
> of the required endpoints.
Again, sometimes, but not always. What a driver SHOULD always do is
verify that the device is providing the specific number and types of
endpoints that it is expecting at probe time and call the core to "find"
the expected endpoints that are present. In the C api we do that with
the usb_find_common_endpoints() or the usb_check_bulk_endpoints() type
functions.
> My idea is to leaverage Rust's type system to prevent users from supplying
> the wrong endpoint type at compile time rather then at runtime. By making
> the `HostEndpoint` its own Rust type with no public constructor,
> users will be forced to iterate the endpoints to discover the correct
> number for each endpoint that they require. Once they have it, users
> can hold to the reference as long as the interface is valid.
Having a reference is great, but really, these are things that you
should just call the core for and get a reference back. No need for the
special encoding logic, see how "simple" the C code is for this please.
> By adding the `Dir` and `Type` generic markers, suplying the wrong endpoint
> to a function will be caught at compile time rather than at runtime. This
> should hopefully shorthen the debug work needed for a driver, as some of
> the errors become impossible.
functions should be taking any "type" of endpoint as this will be
checked when the USB core actually submits the data to the device, so no
driver will get very far if all is not correct. No real need to attempt
to provide many different types and check it all in the api as the api
needs to handle all endpoint types, right?
> As endpoint 0 is always provided and basically _almost hardcoded_``, I added
> the `control_endpoint` function.
That's great, but again, we "know" what that endpoint type is, and it
will be used for both read and write operations, BUT you need to specify
it somehow which way you want that operation to happen when you make the
API call, right?
I would recommend actually porting/writing a USB driver using the apis
while you are attempting to make these bindings, as I think a lot of
these issues will fall out automatically when doing so. USB really
isn't that complicated, it's just a dumb and slow "pipe" that for every
message, is triggered by a host request, no matter which way the data is
flowing.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-08-03 13:24 UTC | newest]
Thread overview: 8+ 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-03 12:46 ` Alrexandru Radovici
2026-08-03 13:23 ` 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
2026-08-03 11:39 ` Alrexandru Radovici
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox