From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wm1-f54.google.com (mail-wm1-f54.google.com [209.85.128.54]) (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 829E942B320 for ; Tue, 11 Aug 2026 09:42:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.54 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786441371; cv=none; b=Ls6RaOgwzA/CHeBaPECqdzWrl+LcbaDlkJbjpktQy9DywsFA+1o6zyPnWJtmmiNZ9JY+yGspjrg11cUSWZkCMb64XxNwUiKvqKEare6qeU4ja3L9bnZjeB76VlRE4bzJfyxhCjfXotP0ZCKlDOmi7K1/LmR15aT8NNIhshF3YFM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786441371; c=relaxed/simple; bh=8zshD9o1aEgDQAPfsQQefG9ZjD9sl+/fnH6CGT3YDTU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=V2uPAhHAJTd2zo4ix4/YN9chsBjVr9sPO5qIIaFEfcN6emOHqc57XjUGoEA5fWwtvsGQ479U+B58AXZ8XUSF1SAGzO/DDjTEK0ZYYQWiUDrtBrj/UFsQ9r5MTyu9Lu5ut99gu1YIof7qVO4Pmpm4tlfjym8/5/A7IWF41ad2X9g= 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=KXO4NWum; arc=none smtp.client-ip=209.85.128.54 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="KXO4NWum" Received: by mail-wm1-f54.google.com with SMTP id 5b1f17b1804b1-4954dff6536so22531315e9.0 for ; Tue, 11 Aug 2026 02:42:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=wyliodrin.com; s=google; t=1786441367; x=1787046167; 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=bXGxdGnTIFSXmiJ8Kpd6ItKriKpKtoQz+U+0jIIi7d8=; b=KXO4NWum5kQi9baOQ3AemgdXjMuQgSiGQ7BnKC3YGMzVQBBjfuWeHpXbh2eqRAVH6z fpa5FsTmt01ptmDQ9MK7toCrT2kLZRDDilQih2bSuro9vB4xQiDyC1yIs4nIMZtwRl8Z Ab3IMLVitTRzfI8dZOgSqLMM70EOgCzCk+4bw= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786441367; x=1787046167; 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=bXGxdGnTIFSXmiJ8Kpd6ItKriKpKtoQz+U+0jIIi7d8=; b=XsIVYXQhiNTm1W1WOofcng5VWhKqTrnq994ZgkF8jPctIicNDwj1pVSxERvxYM0ffJ yFtOFVyv1dqugpEKirUKTM/s2uYOV9oARcFXQDaeqT28mExYbUYCHo5SC+SEADJ+k/6t Ati50awRdILv7X0aMHxdvHPN+YXIueZyCpaU2XewEo9gNINc8Mql+B6W8X/y0NXLlTrJ iRQHsWoAuJ6St/3AsdflZH4rbbUiVvVvw6XTDZmKbTslrrhEva1wyJxEPbqIirSH8jP8 rZc605OG6owEAvmKwqoyBUnVcYmp7BaXJzbo62T7HxFZ6ml6b+5WCtoDnMkD/bzoq3SX ZrYg== X-Gm-Message-State: AOJu0Yyx75TioFtAuR6d+zI2zKYP4jv8eRQJpNGjHETeotYAXrXGTiFm lZ5IYwNZnmCZTJOL62zz7kZprVQDqfAxwXy3L9gfcrrwAehfdZfF4xJgCdcbcHRrhc8= X-Gm-Gg: AR+sD13Bf/MxKHtHcQPL2NP7XAcEQSHo8XV2faPntd1cETpUsmdII+rJh/yyNQBShhP XctMWZPhw3463SvmuK5BQKJ2asEuUNShFNs+ze8dHwUYVlZ4O6UUqeyA5AKwv2cNnTV8NASSRYS VAcx2rVhCETa/+dFav/dAejNX6CFtetUt+7ufGF+MgQzrL122qIEkoQqDe7V9+6iOGxVzwcnMD6 IlqHfsk4tmwQr469qc8su6GhgIzay/tbSSx0TZWKTijypnJNbV9mcOVEskodxvd2i2JW/Povv3j gMjLzKq+CgW1UDVizk7GMx0jy9bZDaWetN6GbW8Car8puzY9ESc00YxAjGBD2bWwase4zjexcYN usALlQYH2mzZIxt7zg43Afudk0KthEq32M8A6jazgOqpmDmPY6834888dunWZlwu7KmqnUX7JSj 0zfyVfhsPcir3wo6iItSmEATZexMrkiZyMoBJ68GeB113YMYipu91fHY7YEolVESYEONcoFwXqK 5f03QObjoZI9CkH058Vz/E1aWErHU7Wo1n4EQ== X-Received: by 2002:a05:6000:4802:b0:473:6e8d:7f3 with SMTP id ffacd0b85a97d-4814ad77f51mr3692843f8f.1.1786441366646; Tue, 11 Aug 2026 02:42:46 -0700 (PDT) Received: from [10.0.36.57] ([217.73.170.83]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-4814a709624sm3388917f8f.22.2026.08.11.02.42.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 11 Aug 2026 02:42:46 -0700 (PDT) From: Alexandru Radovici Date: Tue, 11 Aug 2026 12:42:31 +0300 Subject: [PATCH RFC v2 4/4] rust: usb: allow drivers to expose sysfs attributes 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: 7bit Message-Id: <20260811-rust-usb_control_msg-v2-4-ef79c92bd898@wyliodrin.com> References: <20260811-rust-usb_control_msg-v2-0-ef79c92bd898@wyliodrin.com> In-Reply-To: <20260811-rust-usb_control_msg-v2-0-ef79c92bd898@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?= , "Rafael J. Wysocki" Cc: linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org, rust-for-linux@vger.kernel.org, driver-core@lists.linux.dev, Alexandru Radovici X-Mailer: b4 0.14.3 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> 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 --- 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; + /// 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>> = + /// Some(kernel::attribute_list!( + /// data: MyData, + /// ops: MyDriver, + /// attributes: BLINK, + /// )); + /// + /// // ... ID_TABLE, probe, disconnect + /// } + /// + /// impl kernel::sysfs::AttributeOperations for MyDriver { + /// type Data = MyData; + /// + /// fn show( + /// data: Pin<&MyData>, + /// _dev: &Device, + /// buf: &mut [u8; PAGE_SIZE], + /// ) -> Result { + /// // Format into `buf` and return the byte count. + /// Ok(0) + /// } + /// } + /// ``` + const DEVICE_GROUPS: Option<&'static AttributeGroups>> = 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, } +#[vtable] +impl AttributeOperations for SampleDriver { + type Data = Self; +} + +#[vtable] +impl AttributeOperations for SampleDriver { + type Data = Self; +} + kernel::usb_device_table!( USB_TABLE, MODULE_USB_TABLE, -- 2.55.0