Linux USB
 help / color / mirror / Atom feed
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


      parent reply	other threads:[~2026-08-01  0:01 UTC|newest]

Thread overview: 3+ 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-01  0:01 ` Alexandru Radovici [this message]

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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox