Rust for Linux List
 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>,
	"Rafael J. Wysocki" <rafael@kernel.org>
Cc: linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
	 rust-for-linux@vger.kernel.org, driver-core@lists.linux.dev,
	 Alexandru Radovici <alexandru.radovici@wyliodrin.com>
Subject: [PATCH RFC v2 4/4] rust: usb: allow drivers to expose sysfs attributes
Date: Tue, 11 Aug 2026 12:42:31 +0300	[thread overview]
Message-ID: <20260811-rust-usb_control_msg-v2-4-ef79c92bd898@wyliodrin.com> (raw)
In-Reply-To: <20260811-rust-usb_control_msg-v2-0-ef79c92bd898@wyliodrin.com>

Add an optional DEVICE_GROUPS constant to the USB driver trait and pass
it to struct usb_driver::dev_groups, so that a driver can expose sysfs
files on every interface it binds to. Drivers that leave it unset keep
the current behaviour, as the field stays NULL.

usbcore forwards dev_groups to the embedded struct device_driver, so the
files are created only after probe() has returned successfully and are
removed before disconnect() runs. An attribute callback therefore always
finds the private data that probe() stored.

The constant is typed AttributeGroups<Self::Data<'static>> because a
'static reference cannot name the 'bound lifetime that probe() works
with, while the value handed to a callback is a Self::Data<'bound>. A
driver whose private data borrows from 'bound must not set this
constant; only types that are the same for every instantiation are
sound here.

Signed-off-by: Alexandru Radovici <alexandru.radovici@wyliodrin.com>
---
 rust/kernel/usb.rs              | 100 ++++++++++++++++++++++++++++++++++++++--
 samples/rust/rust_driver_usb.rs |  14 ++++++
 2 files changed, 111 insertions(+), 3 deletions(-)

diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs
index 6670fa2ff377..27fc5e28b45a 100644
--- a/rust/kernel/usb.rs
+++ b/rust/kernel/usb.rs
@@ -19,6 +19,7 @@
     },
     prelude::*,
     sync::aref::AlwaysRefCounted,
+    sysfs::AttributeGroups,
     types::Opaque,
     usb::endpoint::HostEndpoint,
     ThisModule, //
@@ -26,10 +27,13 @@
 use core::{
     marker::PhantomData,
     mem::{
-        offset_of,
-        MaybeUninit, //
+        offset_of, //
+        MaybeUninit,
+    },
+    ptr::{
+        self,
+        NonNull, //
     },
-    ptr::NonNull,
     slice, //
 };
 
@@ -64,6 +68,11 @@ unsafe fn register(
             (*udrv.get()).probe = Some(Self::probe_callback);
             (*udrv.get()).disconnect = Some(Self::disconnect_callback);
             (*udrv.get()).id_table = T::ID_TABLE.as_ptr();
+            (*udrv.get()).dev_groups = if let Some(dev_group) = T::DEVICE_GROUPS {
+                dev_group.as_ptr()
+            } else {
+                ptr::null_mut()
+            };
         }
 
         // SAFETY: `udrv` is guaranteed to be a valid `DriverType`.
@@ -320,6 +329,91 @@ pub trait Driver {
     /// The table of device ids supported by the driver.
     const ID_TABLE: IdTable<Self::IdInfo>;
 
+    /// The sysfs attribute groups to create for interfaces bound to this driver.
+    ///
+    /// Defaults to `None`, i.e. the driver exposes no attributes of its own.
+    /// Build the value with [`attribute_list!`](crate::attribute_list), which
+    /// declares the necessary `static`s and evaluates to a
+    /// `&'static AttributeGroups`. Only a single group is supported.
+    ///
+    /// The files appear in the sysfs directory of each bound USB *interface*, not
+    /// of the USB device, for instance `/sys/bus/usb/devices/1-1:1.0/`. Because
+    /// `dev_groups` belongs to the driver rather than to one device, every
+    /// interface this driver binds to gets the same set of files, and there is no
+    /// way to hide an individual attribute for some interfaces.
+    ///
+    /// # Registration window
+    ///
+    /// The array is stored in `struct usb_driver::dev_groups`, which usbcore
+    /// forwards to the embedded `struct device_driver`. The driver core creates
+    /// the files only after [`Driver::probe`] has returned successfully and
+    /// removes them before [`Driver::disconnect`] runs, so an attribute callback
+    /// always finds live private data on the interface. That is what makes it
+    /// sound for the callbacks to recover it at all. Groups installed anywhere
+    /// that is populated earlier, such as a `device_type`, would expose the files
+    /// from `device_add` onwards, before `probe` had stored anything.
+    ///
+    /// # `Sync`
+    ///
+    /// Attribute callbacks receive a shared reference to the private data, and
+    /// two readers on separate file descriptors can be inside a `show` for the
+    /// same interface at once, so [`Self::Data`] has to be `Sync` for a driver
+    /// that sets this to `Some`. The bound is deliberately not stated here: it
+    /// comes from `AttributeOperations::Data`, so it is checked at the
+    /// `attribute_list!` call site rather than being imposed on every driver,
+    /// including the ones that leave this as `None`.
+    ///
+    /// # The `'static` in `Self::Data<'static>`
+    ///
+    /// The reference is `'static`, so `'static` is the only lifetime this type
+    /// can name. The value a callback is handed at runtime is the
+    /// `Self::Data<'bound>` that [`Driver::probe`] returned for the current
+    /// binding, so the tag names a different instantiation of the GAT than the
+    /// one that exists, and the attribute code reads the private data as a
+    /// `Self::Data<'static>`. Variance turns `'static` into `'bound`, not the
+    /// reverse, so nothing recovers the difference.
+    ///
+    /// Only set this to `Some` when [`Self::Data`] does not borrow from `'bound`,
+    /// i.e. when every instantiation is the same owning type. A `Data` holding
+    /// `&'bound` references can leak them out of an attribute callback with a
+    /// longer lifetime than they have, and nothing here catches it.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// const BLINK: u64 = 0;
+    ///
+    /// // No `'bound` borrows, so `Data<'static>` is the type that exists.
+    /// struct MyData { blinking: AtomicBool }
+    ///
+    /// impl usb::Driver for MyDriver {
+    ///     type Data<'bound> = MyData;
+    ///
+    ///     const DEVICE_GROUPS: Option<&'static AttributeGroups<Self::Data<'static>>> =
+    ///         Some(kernel::attribute_list!(
+    ///             data: MyData,
+    ///             ops: MyDriver,
+    ///             attributes: BLINK,
+    ///         ));
+    ///
+    ///     // ... ID_TABLE, probe, disconnect
+    /// }
+    ///
+    /// impl kernel::sysfs::AttributeOperations<BLINK> for MyDriver {
+    ///     type Data = MyData;
+    ///
+    ///     fn show(
+    ///         data: Pin<&MyData>,
+    ///         _dev: &Device<Bound>,
+    ///         buf: &mut [u8; PAGE_SIZE],
+    ///     ) -> Result<usize> {
+    ///         // Format into `buf` and return the byte count.
+    ///         Ok(0)
+    ///     }
+    /// }
+    /// ```
+    const DEVICE_GROUPS: Option<&'static AttributeGroups<Self::Data<'static>>> = None;
+
     /// USB driver probe.
     ///
     /// Called when a new USB interface is bound to this driver.
diff --git a/samples/rust/rust_driver_usb.rs b/samples/rust/rust_driver_usb.rs
index 02bd5085f9bc..055c46faf144 100644
--- a/samples/rust/rust_driver_usb.rs
+++ b/samples/rust/rust_driver_usb.rs
@@ -3,6 +3,9 @@
 
 //! Rust USB driver sample.
 
+const ATTR1: u64 = 0;
+const ATTR2: u64 = 1;
+
 use kernel::{
     device::{
         self,
@@ -10,6 +13,7 @@
     },
     prelude::*,
     sync::aref::ARef,
+    sysfs::AttributeOperations,
     usb, //
 };
 
@@ -17,6 +21,16 @@ struct SampleDriver {
     _intf: ARef<usb::Interface>,
 }
 
+#[vtable]
+impl AttributeOperations<ATTR1> for SampleDriver {
+    type Data = Self;
+}
+
+#[vtable]
+impl AttributeOperations<ATTR2> for SampleDriver {
+    type Data = Self;
+}
+
 kernel::usb_device_table!(
     USB_TABLE,
     MODULE_USB_TABLE,

-- 
2.55.0


      parent reply	other threads:[~2026-08-11  9:42 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-11  9:42 [PATCH RFC v2 0/4] rust: usb: abstractions towards the port of usbsevseg.c to Rust Alexandru Radovici
2026-08-11  9:42 ` [PATCH RFC v2 1/4] rust: usb: add endpoint abstraction Alexandru Radovici
2026-08-11  9:42 ` [PATCH RFC v2 2/4] rust: usb: add control message send and receive Alexandru Radovici
2026-08-11  9:42 ` [PATCH RFC v2 3/4] rust: sysfs: add abstractions for device attributes Alexandru Radovici
2026-08-11  9:42 ` 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=20260811-rust-usb_control_msg-v2-4-ef79c92bd898@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=driver-core@lists.linux.dev \
    --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=rafael@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