From: Alexandru Radovici <alexandru.radovici@wyliodrin.com>
To: "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>
Cc: linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
rust-for-linux@vger.kernel.org,
Alexandru Radovici <alexandru.radovici@wyliodrin.com>
Subject: [PATCH RFC 2/2] rust: usb: add control message send and receive
Date: Sat, 01 Aug 2026 03:01:08 +0300 [thread overview]
Message-ID: <20260801-rust-usb_control_msg-v1-2-655bb444b52c@wyliodrin.com> (raw)
In-Reply-To: <20260801-rust-usb_control_msg-v1-0-655bb444b52c@wyliodrin.com>
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
next prev parent reply other threads:[~2026-08-01 0:01 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
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-11 9:39 ` Alrexandru Radovici
2026-08-01 0:01 ` Alexandru Radovici [this message]
2026-08-02 8:32 ` [PATCH RFC 2/2] rust: usb: add control message send and receive Greg Kroah-Hartman
2026-08-03 11:39 ` Alrexandru Radovici
2026-08-10 12:04 ` kernel test robot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260801-rust-usb_control_msg-v1-2-655bb444b52c@wyliodrin.com \
--to=alexandru.radovici@wyliodrin.com \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=gary@garyguo.net \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.