From nobody Fri Oct 2 12:20:45 2026 Received: from mail-wm1-f43.google.com (mail-wm1-f43.google.com [209.85.128.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3BF66285CA2 for ; Sat, 1 Aug 2026 00:01:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785542490; cv=none; b=fs2XPX7z3ms+vk1MxiaLKdH2x8LiT9HaLXNM3lWhpE4LyKOyjmsC3tf82K8mSGZxkgJKBwS9E3nPSiTXGCKmimfVntlBTU8CZj4wU3+oBXBlylrtF3XzErF+cAhmJRKZqIY92+dMkrqGlIUEqQOwHx//+I1nAikO9M60SiV7spQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785542490; c=relaxed/simple; bh=n0NpDNBD8KJ39pEKMF2PsU//Q7FgTlACJzL1ls80DK8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=O+GOE9+LjnzWeNncu7CTUJ9swAeC7GjO6bV2CECCrEMEHCoPv2RY/XFHu2VUs3hcmnFb7EcV2GrhNdM74akqBa/tF18Gdpfn9WnlexnP4xqVADar9GnNWfcgwQsfCaaPhuK1m+fkob0h47L7Gif0srfWE39tRF86vfRhJz1Q0D4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=wyliodrin.com; spf=pass smtp.mailfrom=wyliodrin.com; dkim=pass (1024-bit key) header.d=wyliodrin.com header.i=@wyliodrin.com header.b=dfYDzrFB; arc=none smtp.client-ip=209.85.128.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=wyliodrin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=wyliodrin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=wyliodrin.com header.i=@wyliodrin.com header.b="dfYDzrFB" Received: by mail-wm1-f43.google.com with SMTP id 5b1f17b1804b1-4954a9e8490so7116275e9.1 for ; Fri, 31 Jul 2026 17:01:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=wyliodrin.com; s=google; t=1785542485; x=1786147285; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=fhGCZPQrCzgc+r5pSkdCx02xaW/EGSSmOaW0NiiuQ+4=; b=dfYDzrFBzpXUPlN9FP0NcpYaqzTmNeyrg9lQc93d6/P/YfAzkRyVfJnpk3mAdmvGbT a8kPGR8oGooYCGxnwQ91v2xJFQRPWqFVn9ruhQxQ7qJSASNFuB/vr1pFrDKm66eEcY7q 2b1yvkd119lSDIvFkEzUvkvkMPKMY9hEmVpC0= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785542485; x=1786147285; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=fhGCZPQrCzgc+r5pSkdCx02xaW/EGSSmOaW0NiiuQ+4=; b=m+weFfi27ZFCIsOLoXyZN5ME2j9SqWDlhvhDIxPeW42x7eqJmrKBrKIXjq0uCE8tK1 LXEytheiJSACZnyU/A3IOMeG3FfFYHojxK1gVLwj0zttCiqRnI3Jn9kb1MN/mArtkiwU of6cn6vd9Oh8b3oDw5kt3ST1lJsHztUc4ERcyd0unG8oGIclTBP8aH4zmycCieyV9yq/ CNkm+Z+DbyVBpr5BrXeJIuD9SQTP7EnHCIoTj1n13TyMgnnFFXvIwR9dQIUPxaXRDTuW cPu131uosrnfE6T74ZDftUhroaRE/AsyGEsfCp6gfel7aVEitrRc2fobZBmfhCTbbQot p4bw== X-Gm-Message-State: AOJu0YyOhHlu7RTop7AHRHK0oDOVwlBKhyR6pb5YhakqCcbibCMJf+Vz LEDMmUZHHz6TMautlnV+yzu1T61Rz2dVyoEOJF6+RVjFVteXXVl5LRXw9p+MMj8yRdE= X-Gm-Gg: AR+sD10Se8i7baJA1JUPYKaVDmRF4fSUn7VS/95NtMR50KL7s/8DOvZjQ/lEKAdfd+v 6sxY2OWqSYe8bu0DdX8og0SUg9NcWSYmIIM/oVvxQ0IVXsDEtcqT/4LqDwBRf1+R83LTwUnexgK HFn7Q8EXlxyDmcF1gQuA7ZOg5Cw7OyBrkKsnZIT4R91jizqQgvD1HKxXRKvDFO35F2OefM9VNoY z7p8vBYdEgWHukfJgmfR7dSOwxDRtCqYN3IZ4auSRWMFT3uEIXEGnH2ihC6KQC+pSumPpsz89CS NeL5j5XVygWAaEY4cjMaw/5lOrBPEe7jeZIAJUUTUqhX07D2Kn2iLxyfpql4cT5fIyFh670dXWu /YUMd+KkyaE2nhzVI2gZZ2LTg9PBQmz31GhEKIatiPvroZLpGuuShGuuVdubJqY6E7BDHlKdPm6 A13V6ICVmQKJK+PDzbFjAVVoAep2f/eBWh6M+9Y0fe2ANSgTPbsFYsm52V1zgaHdOjzX2hh5VrS mKE/TtsgmNy0ziBZXKwOH3CjHTWWEIOyweKyQTjuw== X-Received: by 2002:a05:600c:1d18:b0:498:40a:16b3 with SMTP id 5b1f17b1804b1-4980ebc2159mr1811335e9.10.1785542485147; Fri, 31 Jul 2026 17:01:25 -0700 (PDT) Received: from [192.168.2.138] ([86.122.199.88]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49807b8d04fsm6568935e9.3.2026.07.31.17.01.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 31 Jul 2026 17:01:24 -0700 (PDT) From: Alexandru Radovici Date: Sat, 01 Aug 2026 03:01:07 +0300 Subject: [PATCH RFC 1/2] rust: usb: add endpoint abstraction Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260801-rust-usb_control_msg-v1-1-655bb444b52c@wyliodrin.com> References: <20260801-rust-usb_control_msg-v1-0-655bb444b52c@wyliodrin.com> In-Reply-To: <20260801-rust-usb_control_msg-v1-0-655bb444b52c@wyliodrin.com> To: Greg Kroah-Hartman , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= Cc: linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org, rust-for-linux@vger.kernel.org, Alexandru Radovici X-Mailer: b4 0.14.3 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` 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 --- 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, }; =20 +pub mod endpoint; + /// An adapter for the registration of USB drivers. pub struct Adapter(T); =20 @@ -334,6 +338,78 @@ fn disconnect<'bound>( ); } =20 +/// 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 U= VC camera exposing one +/// setting per bandwidth tier, plus a zero-bandwidth setting used while i= dle. 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 `alts= etting` array of a +/// `struct usb_interface` owned by the C side, which guarantees that `des= c` is initialised and +/// that the setting outlives the borrow. +#[repr(transparent)] +pub struct AlternateSetting(Opaque); + +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 in= itialised 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; settin= g 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 val= id + // `struct usb_host_interface` with an initialised `desc`. + unsafe { (*self.as_raw()).desc.bAlternateSetting } + } + + /// Returns the `bInterfaceNumber` of the interface this setting belon= gs to. + /// + /// Every alternate setting of a given interface reports the same numb= er, 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 val= id + // `struct usb_host_interface` with an initialised `desc`. + unsafe { (*self.as_raw()).desc.bInterfaceNumber } + } + + /// Returns the endpoints declared by this alternate setting, in descr= iptor order. + /// + /// The endpoints come back untyped, as `Endpoint`; = refine them with + /// [`Endpoint::as_in()`], [`Endpoint::as_out()`] and [`Endpoint::as_c= ontrol()`]. + pub fn endpoints(&self) -> &[HostEndpoint] { + // SAFETY: By the type invariants, `self.as_raw()` points at a val= id + // `struct usb_host_interface` with an initialised `desc`. + let (ptr, len) =3D (unsafe { (*self.as_raw()).endpoint }, unsafe { + (*self.as_raw()).desc.bNumEndpoints + }); + + if len =3D=3D 0 { + &[] + } else { + // SAFETY: When `bNumEndpoints` is non-zero the C side has all= ocated an array of that + // many initialised `struct usb_host_endpoint` at `ptr`, livin= g as long as the + // interface. `Endpoint` is a `#[repr(transparent)]` wrapper a= round + // `Opaque`, which is itself layo= ut-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_in= terface`]. @@ -356,6 +432,54 @@ impl Interface { fn as_raw(&self) -> *mut bindings::usb_interface { self.0.get() } + + /// Returns all alternate settings of this interface, in `bAlternateSe= tting` 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 val= id `struct usb_interface`, + // so both fields are initialised. Reading them requires nothing o= f the device context: + // `altsetting` is filled in when the interface is created and hol= ds until it is released. + let (ptr, len) =3D (unsafe { (*self.as_raw()).altsetting }, unsafe= { + (*self.as_raw()).num_altsetting + }); + + if len =3D=3D 0 { + &[] + } else { + // SAFETY: When `num_altsetting` is non-zero the C side has al= located 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 arou= nd + // `Opaque`, which is itself lay= out-compatible with + // `struct usb_host_interface`, so the cast preserves both siz= e 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 int= erface. + /// + /// This is the setting whose endpoints the device is actually prepare= d to service, so it is + /// the one a driver should read endpoint descriptors from. After conf= iguration it is + /// setting 0. + /// + /// The result is a snapshot. `usb_set_interface()` can repoint the in= terface at a different + /// setting, which does not invalidate the returned reference - both p= oint into the same live + /// array - but does stop it being the current one. A driver that cach= es 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 val= id `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` and so layout-compatible= with it, and the borrow + // lasts no longer than `&self`. + unsafe { &*(*self.as_raw()).cur_altsetting.cast() } + } } =20 // SAFETY: `usb::Interface` is a transparent wrapper of `struct usb_interf= ace`. 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 endpo= int descriptor the device +//! reported: [`number()`](HostEndpoint::number) and [`address()`](HostEnd= point::address), +//! [`direction()`](HostEndpoint::direction) and [`transfer_type()`](HostE= ndpoint::transfer_type), +//! and the packet and interval geometry needed to size and schedule trans= fers. +//! +//! # Where endpoints come from +//! +//! Endpoints declared by an interface are borrowed from the alternate set= ting that describes them, +//! via [`AlternateSetting::endpoints()`](crate::usb::AlternateSetting::en= dpoints). The +//! default control endpoint - endpoint 0 - is not among them: no interfac= e descriptor lists +//! it, because the specification excludes it from `bNumEndpoints` and dev= ices 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 +//! | | | +//! as_in() <-- v as_control() --> as_ou= t() +//! Endpoint Endpoint Endpoi= nt +//! ``` +//! +//! A function taking `&Endpoint` therefore cannot be handed any= thing but a bulk IN +//! endpoint, and needs no run-time check of its own. The markers are [`Ph= antomData`], 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 US= B 2.0 +//! section 9.6.6 correspondingly defines bit 7 of `bEndpointAddress` as i= gnored +//! 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 erro= r 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>, Option<&End= point>) { +//! let eps =3D 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::AlternateSett= ing). +/// +/// `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 `Endp= oint`, +/// 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 `&Endpoin= t` may then +/// rely on the endpoint really being a bulk IN endpoint without re-checki= ng it. +/// +/// Direction and transfer type are not independent: a control endpoint ha= s no direction, so +/// [`as_control()`] yields [`Bidirectional`] rather than preserving or di= scovering 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 sam= e layout as +/// an untyped one and the refinement is free at run time. +/// +/// # Invariants +/// +/// - The wrapped [`Opaque`] holds an initialised `struct usb_host_endpoin= t`. Instances are never +/// constructed by Rust code; they are only ever borrowed out of a `stru= ct 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( + Opaque, + PhantomData, + PhantomData, +); + +/// 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 wit= h 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 m= arker 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 de= fines bit 7 +/// of `bEndpointAddress` as ignored for control endpoints - so "is this e= ndpoint IN or OUT" +/// has no answer for one. +/// +/// Since [`HostEndpoint::as_in()`] and [`HostEndpoint::as_out()`] are def= ined 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 b= eing implemented + /// outside of this module, so that the type invariants of [`HostEndpo= int`] 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 t= he 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 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 =3D=3D 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 err= or retry, for + /// time-sensitive streams such as audio and video. + Isochronous, + /// Transfers with guaranteed delivery but no bandwidth or latency gua= rantee, for bulk data + /// such as mass storage. + Bulk, + /// Small, periodically polled transfers with bounded latency and guar= anteed delivery, for + /// devices such as keyboards and mice. + Interrupt, +} + +impl From for TransferType { + /// Extracts the transfer type from a raw `bmAttributes`. + /// + /// Bits 1:0 of the endpoint attributes hold the transfer type (`USB_E= NDPOINT_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 =3D> TransferType::Control, + 1 =3D> TransferType::Isochronous, + 2 =3D> TransferType::Bulk, + _ =3D> TransferType::Interrupt, + } + } +} + +impl HostEndpoint { + /// Returns a raw pointer to the underlying `struct usb_host_endpoint`. + /// + /// By the type invariants the pointer is non-null and points at an in= itialised 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 e= ndpoint of the same + /// interface may share one, so pair it with [`direction()`](Self::dir= ection), 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 val= id + // `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 i= n bit 7 (set for IN, + /// clear for OUT); bits 6:4 are reserved and zero. Taken together tho= se 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 val= id + // `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 i= t reports for one is + /// whatever bit 7 of the address byte happens to hold, which the spec= ification leaves + /// undefined. + pub fn direction(&self) -> Direction { + // SAFETY: By the type invariants, `self.as_raw()` points at a val= id + // `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 val= id + // `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 isochrono= us and interrupt + /// endpoints the total payload per microframe is this value multiplie= d 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 val= id + // `struct usb_host_endpoint` with an initialised `desc`. + unsafe { (*self.as_raw()).desc.wMaxPacketSize & 0x07ff } + } + + /// Returns the number of *additional* transaction opportunities per m= icroframe. + /// + /// This is bits 12:11 of `wMaxPacketSize`, so the total number of tra= nsactions 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..=3D2`; `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 val= id + // `struct usb_host_endpoint` with an initialised `desc`. + unsafe { (((*self.as_raw()).desc.wMaxPacketSize >> 11) & 0b11) as = u8 + 1 } + } +} + +impl HostEndpoint { + /// 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 endpoin= t - refining direction + /// first and transfer type second would otherwise produce an `Endpoin= t`, 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 lis= ts; reach that one + /// /// through `Device::control_endpoint()` instead. + /// fn extra_control(alt: &AlternateSetting) -> Option<&Endpoint> { + /// alt.endpoints().iter().find_map(|ep| ep.as_control()) + /// } + /// ``` + pub fn as_control(&self) -> Option<&HostEndpoint> { + matches!(self.transfer_type(), TransferType::Control).then(|| { + // SAFETY: The transfer type was just checked, so the [`Contro= l`] invariant holds, and + // [`Bidirectional`] is exactly what a control endpoint warran= ts. + // `Endpoint` is a `#[repr(transparent= )]` wrapper around the + // same `struct usb_host_endpoint` and differs only in its `Ph= antomData` 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 HostEndpoint { + /// Refines this endpoint into a device-to-host endpoint, if it is one. + /// + /// Returns [`None`] if [`direction()`](Self::direction) is not [`Dire= ction::In`]. The known + /// transfer type, if any, is preserved. + pub fn as_in(&self) -> Option<&HostEndpoint> { + if self.direction() =3D=3D Direction::In { + // SAFETY: The direction was just checked, so the [`In`] invar= iant holds. + // `Endpoint` is a `#[repr(transparent)]` wrapper ar= ound the same + // `struct usb_host_endpoint` and differs only in its `Phantom= Data` 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 [`Dire= ction::Out`]. The known + /// transfer type, if any, is preserved. + pub fn as_out(&self) -> Option<&HostEndpoint> { + if self.direction() =3D=3D Direction::Out { + // SAFETY: The direction was just checked, so the [`Out`] inva= riant holds. + // `Endpoint` is a `#[repr(transparent)]` wrapper a= round the same + // `struct usb_host_endpoint` and differs only in its `Phantom= Data` 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 HostEndpoint { + /// Returns the raw `bInterval` value of the endpoint descriptor. + /// + /// How this encodes a service interval depends on the transfer type a= nd the device's + /// operating speed: + /// + /// - Full-/low-speed interrupt endpoints: the interval in frames (1 m= s), `1..=3D255`. + /// - High-speed interrupt endpoints and all isochronous endpoints: an= exponent, giving an + /// interval of `2^(bInterval - 1)` microframes (125 micros), with `= bInterval` in `1..=3D16`. + pub fn interval(&self) -> u8 { + // SAFETY: By the type invariants, `self.as_raw()` points at a val= id + // `struct usb_host_endpoint` with an initialised `desc`. + unsafe { (*self.as_raw()).desc.bInterval } + } +} + +impl HostEndpoint { + /// 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 val= id + // `struct usb_host_endpoint` with an initialised `desc`. + unsafe { (*self.as_raw()).desc.bInterval } + } +} + +impl HostEndpoint { + /// 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 val= id + // `struct usb_host_endpoint` with an initialised `desc`. + unsafe { (*self.as_raw()).desc.bInterval } + } +} + +impl Device { + /// Returns the default control endpoint (endpoint 0) of this device. + /// + /// Every USB device has exactly one, and it is the endpoint all enume= ration and standard + /// requests travel over. Unlike the endpoints of an + /// [`AlternateSetting`](crate::usb::AlternateSetting), it is not desc= ribed by any + /// descriptor the device sends: the USB core synthesizes its descript= or + /// in `usb_alloc_dev()` and fills in the packet size from `bMaxPacket= Size0` of the device + /// descriptor during enumeration. It therefore cannot be found by sea= rching + /// [`AlternateSetting::endpoints()`](crate::usb::AlternateSetting::en= dpoints), and + /// this accessor is the only way to reach it. + /// + /// Neither marker needs a run-time check. [`Control`] holds because t= he core sets + /// `bmAttributes` to `USB_ENDPOINT_XFER_CONTROL` itself and the speci= fication fixes endpoint 0 + /// as a control endpoint; [`Bidirectional`] holds because control end= points have no direction. + /// Endpoint 0's address byte is `0x00`, so [`direction()`](HostEndpoi= nt::direction) + /// would report [`Direction::Out`] - a meaningless answer, which is w= hy 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 val= id `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 { + // SAFETY: By the type invariants, `self.as_raw()` points at a val= id `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 =3D unsafe { core::ptr::addr_of!((*self.as_raw()).ep0) }; + + // SAFETY: `HostEndpoint` is a `#[repr(transparent)]` wrapper arou= nd + // `Opaque`, which is layout-compatib= le with + // `struct usb_host_endpoint`, so the cast preserves size and alig= nment. The [`Control`] + // invariant holds because the core fixes `ep0.desc.bmAttributes` = to + // `USB_ENDPOINT_XFER_CONTROL`, and [`Bidirectional`] holds for an= y control endpoint. The + // `Opaque` accounts for the C side mutating the endpoint behind t= his shared reference, and + // the borrow lasts no longer than `&self`. + unsafe { &*ep0.cast() } + } +} --=20 2.55.0 From nobody Fri Oct 2 12:20:45 2026 Received: from mail-wm1-f49.google.com (mail-wm1-f49.google.com [209.85.128.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E8616242D89 for ; Sat, 1 Aug 2026 00:01:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785542491; cv=none; b=rq0tH+isoHZo9plph2T4XJWey0yek4lvn22XqpcOxRRdIrbwMNA7ET+q2XM31yfw490Ldx9AN1uLudgE8gtCBG7WoXz82ggIzHZNzupCri7xINhbSU7FZUgqQcVIzAyavIu3HzRMIX7MUe3TZvQCP+slJo+hM6bbxbCKCVPn2tE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785542491; c=relaxed/simple; bh=3T6QpkPI//WGbA+RmHlvgDJPZcPLkgFPEUBMEoDHZGo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Q/NPO7GLPK5Qj75AqSNKrIMVZlTxXkz9qYrlAtDi+lZNpGbx+9LcW4/Es11UHSEf4zDeC1Cgav6XAkBbTMPTSqx96bOibUNg0S0kviUl1PaaE/94ljp2u1FBpHOtbWTuUmEqFcpJRbWAmXfM6HF1QbWb9QlYiOcP30AyjxlOrdM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=wyliodrin.com; spf=pass smtp.mailfrom=wyliodrin.com; dkim=pass (1024-bit key) header.d=wyliodrin.com header.i=@wyliodrin.com header.b=h+lIeGFq; arc=none smtp.client-ip=209.85.128.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=wyliodrin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=wyliodrin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=wyliodrin.com header.i=@wyliodrin.com header.b="h+lIeGFq" Received: by mail-wm1-f49.google.com with SMTP id 5b1f17b1804b1-498028b3d5eso3759835e9.1 for ; Fri, 31 Jul 2026 17:01:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=wyliodrin.com; s=google; t=1785542487; x=1786147287; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=dxjeR4PCY58BhrAQyXpzwzfVgEnX583XUlxdGj566C0=; b=h+lIeGFqsH+yCRgylqutJgrlEN7AA6IJhx0FZR6IfL3vVDS8f5Qy54hRDdu9cThCxQ QRPiDdfKXz4GwsaU16D1sQlnKEXXTHMQV17uv3vTikyyg8Vvh9aE4swdm6Au/8PSYCha Q6zm02R8KaaCYbWrku3UDIC81x69+HxG0Fqq8= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785542487; x=1786147287; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=dxjeR4PCY58BhrAQyXpzwzfVgEnX583XUlxdGj566C0=; b=E8ACn3nBjSLMX60lI1RvMTpknlyBOXysHQDhgDAd0naMmIucnNQznR+7kecPmnuD1+ 4J/dbrCN5e/YA4LN26YKYJ6kbDNFJ9SVdvg6yqor3dc89AT4B63qZnLbRltapCfbFJdy Yd6Qwh+k2UgdjGy4UaJVNK5XBCbAArJMm+PL6dgLmRB2elbUKEn+lOEbSXoniWCcvSqr rHpskuWZT1Wl1COjjPMPmzy7W/KtxnbA9SJXPd01ieCm8M4a8E9d9YBUG4mKBmMCyVsh cmVV81iAok74R/3ScpgbeXqz/UHeAiaZvdYoF02se5OTNQeLvNIhnm6EY8yKji/VeOlx IJtQ== X-Gm-Message-State: AOJu0Yyn4nex5WnD8KjC9z3UXRkMRd1+8JZft4eAd9dUqaxRQjICQvnv DPAIAOkFjDTrlLSEYnllcCcNqUbjJTRxHldEvFRtMyoswA9DTf/J1FRI6oE0K77I1b8= X-Gm-Gg: AR+sD12jVsbbXakz1x6dALFCWOtqbI6woUfUw00mK3eeTt5hinZef3ikBMXghsDpLKz TgwP3eY0L1MEZ1uXNfNfakUghnigqzgmWiAG4tQb8/e1f+Rn2KyepaPPqxos1oU6Dz4YY0rM+cT 3SlYwptXJlr7AEDvkYkg75eXXu5xAUKXceSbWDWZoi/9vqIWumo+GqhZtp7VqIVqK+JDGbhVmyx J/XkW6dFWm4K485DVyINEP49Mbyf8BCLB0BVBmjE081LmuRr+cili04ke2g1IdtQOUgKLzbeXSR Nz6jRXBQbjsdTw8jYy4zqaMZi/nMVEaTfePanYPiR4wU6O51IRg+z0YzuHC1TAS/jjInhFMCdHt 1cYn6UL515haiuZY6Q3UqVzUUlFWTuWQZPlUtL6ghm5BcWBl0rU8BuaEsiIdR+DHm0yFmBG6YRw NndnOLSgYcAaa9cviOBqFM+vx6PFF10ONG0qpBfrBrQqLphAp8gXgtyDqMl2HEatGboZ9nv7zMb oI9X6gqLagdaW7Jcbqn47RPZYBhek3Xa/mlu533xA== X-Received: by 2002:a05:600c:a01:b0:495:63f5:7a4f with SMTP id 5b1f17b1804b1-4980c6a3cd3mr3509945e9.33.1785542487018; Fri, 31 Jul 2026 17:01:27 -0700 (PDT) Received: from [192.168.2.138] ([86.122.199.88]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49807b8d04fsm6568935e9.3.2026.07.31.17.01.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 31 Jul 2026 17:01:26 -0700 (PDT) From: Alexandru Radovici Date: Sat, 01 Aug 2026 03:01:08 +0300 Subject: [PATCH RFC 2/2] rust: usb: add control message send and receive Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260801-rust-usb_control_msg-v1-2-655bb444b52c@wyliodrin.com> References: <20260801-rust-usb_control_msg-v1-0-655bb444b52c@wyliodrin.com> In-Reply-To: <20260801-rust-usb_control_msg-v1-0-655bb444b52c@wyliodrin.com> To: Greg Kroah-Hartman , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= Cc: linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org, rust-for-linux@vger.kernel.org, Alexandru Radovici X-Mailer: b4 0.14.3 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`, so a non-control endpoint cannot be passed by construction. Signed-off-by: Alexandru Radovici --- 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, }; =20 +pub mod control; pub mod endpoint; =20 /// 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-a= pi/usb/usb.html#c.usb_device #[repr(transparent)] -struct Device( +pub struct Device( Opaque, PhantomData, ); 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 stat= us 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 bidirect= ional, the endpoint does +//! not determine the direction: the `Direction` bit of `bmRequestType` do= es, 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 =3D 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 interprete= d 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 it= self and interpreted the + /// same way by every device. Usually handled by the USB core rather t= han by a driver; see + /// [`Device`]. + Standard =3D 0, + /// Request is intended for a USB class. + Class =3D 1, + /// Request is vendor-specific. + Vendor =3D 2, + /// Reserved. + Reserved =3D 3, +} + +/// The target a [`Request`] is addressed to. +/// +/// This is the `Recipient` field of `bmRequestType`, occupying bits 4:0 o= f 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 define= d - hence +/// the bound on the wrapped [`Bounded`]. +#[derive(Copy, Clone, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct Recipient(Bounded); + +impl Recipient { + /// The device as a whole. + pub const DEVICE: Self =3D Self(Bounded::::new::<0u8>()); + /// A specific interface, named by [`Request::index`]. + pub const INTERFACE: Self =3D Self(Bounded::::new::<1u8>()); + /// A specific endpoint, named by [`Request::index`]. + pub const ENDPOINT: Self =3D Self(Bounded::::new::<2u8>()); + /// Some other target, defined by the request itself. + pub const OTHER: Self =3D Self(Bounded::::new::<3u8>()); + /// A port. Wireless USB only. + pub const PORT: Self =3D Self(Bounded::::new::<4u8>()); + /// An RPipe. Wireless USB only. + pub const RPIPE: Self =3D Self(Bounded::::new::<5u8>()); + + /// Builds a recipient from a raw field value. + /// + /// Prefer the associated constants; this exists for values a future s= pecification revision may + /// define. The [`Bounded`] parameter rules out values the field canno= t hold, but does not + /// guarantee the device understands the one you pass. + pub fn new(val: Bounded) -> 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 `bmRequestTyp= e` 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 th= em 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 fie= lds. + pub request: u8, + /// Request value. The meaning of the value depends on the previous fi= elds. + pub value: u16, + /// Request index. The meaning of the value depends on the previous fi= elds. + 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 endpoin= t it runs over: bit 7 of + /// `bmRequestType` is what the host controller obeys for a control tr= ansfer, and bit 7 of the + /// endpoint address is defined to be ignored. + fn bm_request_type(&self, direction: Direction) -> u8 { + let dir =3D match direction { + // `USB_DIR_IN` is `0x80`; `USB_DIR_OUT` is zero. + Direction::In =3D> bindings::USB_DIR_IN as u8, + Direction::Out =3D> 0, + }; + + dir | ((self.request_type as u8) << REQUEST_TYPE_SHIFT) | self.rec= ipient.0.get() + } +} + +/// Converts a buffer length into a `wLength` value. +/// +/// The field is 16 bits, so anything larger cannot be expressed in a sing= le control transfer. +fn transfer_length(len: usize) -> Result { + 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 `wLengt= h` 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 defau= lt control endpoint - + /// almost always the right choice - pass [`control_endpoint()`](Devic= e::control_endpoint). + /// + /// `timeout` is in milliseconds, with zero meaning "wait forever". `m= emflags` 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, + request: Request, + data: Option<&[u8]>, + timeout: i32, + memflags: Flags, + ) -> Result { + let (data, size) =3D match data { + Some(bytes) =3D> ( + bytes.as_ptr().cast::(), + transfer_length(bytes.len())?, + ), + None =3D> (ptr::null(), 0), + }; + + // SAFETY: `self.as_raw()` is a valid `struct usb_device` by the t= ype 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 `s= ize` initialised bytes + // that outlive the call - the callee only reads from it, and copi= es before submitting, so + // no DMA is performed on the caller's buffer. + let ret =3D 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. devic= e 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 `wLengt= h` to zero - note that + /// this makes the direction bit meaningless, so such a request is ind= istinguishable from the + /// [`send_control_message()`](Device::send_control_message) equivalen= t. + /// + /// `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 defau= lt control endpoint - + /// almost always the right choice - pass [`control_endpoint()`](Devic= e::control_endpoint). + /// + /// `timeout` is in milliseconds, with zero meaning "wait forever". `m= emflags` 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 { + /// let mut buf =3D [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, + request: Request, + data: Option<&mut [u8]>, + timeout: i32, + memflags: Flags, + ) -> Result { + let (data, size) =3D match data { + Some(bytes) =3D> { + let size =3D transfer_length(bytes.len())?; + (bytes.as_mut_ptr().cast::(), size) + } + None =3D> (ptr::null_mut(), 0), + }; + + // SAFETY: `self.as_raw()` is a valid `struct usb_device` by the t= ype 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 `s= ize` bytes of a buffer + // uniquely borrowed for the duration of the call, which the calle= e may only write to. + let ret =3D 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(()) + } + } +} --=20 2.55.0