From nobody Sat Oct 4 04:56:48 2025 Received: from mail-pj1-f73.google.com (mail-pj1-f73.google.com [209.85.216.73]) (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 C2C9C23BCF0 for ; Tue, 19 Aug 2025 22:53:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.73 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644029; cv=none; b=cflEKrePy1bvGTMyf1LJLqfkRStySmOCusfHMQ379emga9HP698CEjef+EWIecQXZBNyMvATDZcmEnHAIln0t7g4V1iWAqVuuI9vKJG3TBWi4Z3If3lmxuamCW/06Muk5H7hzKbCUXnYVXERB+J4P04xyNeJ491s6/5rA3gwBCI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644029; c=relaxed/simple; bh=zm/+KjQEPIp+EtrHdVvycc6RQsnywHbzucuPtQjImAY=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=REefQMjAZjQbl6cxGPLcw3IsVgLyhLuXBnlgZIJCu9LWmc8iPDK1K8FJiZFrz00zJX9J5z83VbHKHB4Oy4YXBFF7kFcKQvhCR3AVf3C09PAfZQpJrBukT3ZY7GqaaiTAIw54ST5/n7n33OvO3R0d6EfWsvegh2niLbGrFJ1X8Wg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=XNbvYLBI; arc=none smtp.client-ip=209.85.216.73 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="XNbvYLBI" Received: by mail-pj1-f73.google.com with SMTP id 98e67ed59e1d1-32326bf571bso10511203a91.2 for ; Tue, 19 Aug 2025 15:53:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1755644026; x=1756248826; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=s4Jzmac8xTrDRTqzeHLgtcseGNH14Ugiac3trTaCatc=; b=XNbvYLBIxJ2vD9bDHVCnqk3xbGKXmx6y0+HhWxzCgYxgPk4/6Dq/9qzmT5ue4DPGNJ bLWHMkyrrjeysrvMu670+YudraNVv3OsWo1D/E9h3SBmE31NuvrkMYSyS9Ajv/IU5wXx tFavwlxROACfWVCTzqoofZo//mRA6A6/Zajo20qIGk6LU6bYW9mjcpt/xSZi9JOIZ2g9 OaywWQcnFvj/AAxYcIhwaBwsAP3CI/rFzLUCfCyJoJ76iVKETO0NFQxizlgDotmvOtLm T1EpWtZIsjp1ywfK1w/ZqyfEYaBzPNnZKOjIPv657klT6v6zoblajo2XB32YojPWtHK5 4GBQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755644026; x=1756248826; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=s4Jzmac8xTrDRTqzeHLgtcseGNH14Ugiac3trTaCatc=; b=uyDEbJvSACM0s/LSuXJwara3mHWvQ41mkcHyewNp/KAxay5IOaFvhFlJ7qSLjjTH8i PrgcBXuw0hC3DeOT2PxgujSC6sCJfrgJJB5JDeBygl08+MTq0KZk41DYhmjSdXNR7UxY jyFg4qjgd+vJzDYmZi985aZEIxvHchmm2zfX1hZxJzmQ2Oq0F5WNvV7aNogSdmDcAtLZ EhDEuPuDh4R+UmeKAfOLmLIzmtKocssIuYaoGtmq2BzTAPclfc6fXpM7Bw0NSp+Wo+3j tB/U092uRZg5mqjP/OV5BdasUHq8GlGRO/6eP5kvb1Bt5k2wDSEmrO4OaYe6A9EG/Lfx Gzbg== X-Gm-Message-State: AOJu0YyyoS7+V9AlJkvG1DRhguKc393/nMBn6qY/IsVdTXQWweic6gAN QD50CBVTykJE2jhzfdy1cgV01kg4rwPn1fa0Q+dj8nWjyADn8IzQexckJjo3o2MDAHYRkP1de7n yd0EaYxu1Cg== X-Google-Smtp-Source: AGHT+IFCTfJyogs2xNZbquzoKUKRwhZa6iBMvrq2vrgnx/8y1STo8dQUoJKux2wc3q9TUHJXXb0iLl0AteNH X-Received: from pjm15.prod.google.com ([2002:a17:90b:2fcf:b0:31f:61fc:b283]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:2fcf:b0:324:e364:2c78 with SMTP id 98e67ed59e1d1-324e364363amr43846a91.34.1755644026078; Tue, 19 Aug 2025 15:53:46 -0700 (PDT) Date: Tue, 19 Aug 2025 22:53:36 +0000 In-Reply-To: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1755644022; l=7374; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=zm/+KjQEPIp+EtrHdVvycc6RQsnywHbzucuPtQjImAY=; b=fHITCkOUehkW4i4iVZYq5nL0M0vlneb1dnFIkpSrVfKClEKMaaOs3mcj+YMFOt5ilA916h1vz ANwvHt2r4+nC5tzDr1PepJW44a6jkZMs+WEjWA9oC9CS3N4pHojthIh X-Mailer: b4 0.14.2 Message-ID: <20250819-debugfs-rust-v10-1-86e20f3cf3bb@google.com> Subject: [PATCH v10 1/7] rust: debugfs: Add initial support for directories From: Matthew Maurer To: Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , "=?utf-8?q?Bj=C3=B6rn_Roy_Baron?=" , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Greg Kroah-Hartman , "Rafael J. Wysocki" , Sami Tolvanen , Timur Tabi , Benno Lossin , Dirk Beheme Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Matthew Maurer Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Adds a `debugfs::Dir` type that can be used to create and remove DebugFS directories. The `Dir` handle automatically cleans up the directory on `Drop`. Signed-off-by: Matthew Maurer Tested-by: Dirk Behme --- MAINTAINERS | 2 + rust/bindings/bindings_helper.h | 1 + rust/kernel/debugfs.rs | 82 +++++++++++++++++++++++++++++++++++++= ++++ rust/kernel/debugfs/entry.rs | 61 ++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 5 files changed, 147 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index daf520a13bdf6a991c0160a96620f40308c29ee0..8f2dbf71ca3f8f97e4d76193752= 79ed11d1261b2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7472,6 +7472,8 @@ F: include/linux/kobj* F: include/linux/property.h F: include/linux/sysfs.h F: lib/kobj* +F: rust/kernel/debugfs.rs +F: rust/kernel/debugfs/ F: rust/kernel/device.rs F: rust/kernel/device/ F: rust/kernel/device_id.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index 84d60635e8a9baef1f1a1b2752dc0fa044f8542f..e847820dc807fdda2d682d496a3= c6361bb944c10 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs new file mode 100644 index 0000000000000000000000000000000000000000..893aee54b920bac80f77c272656= 7da76929b7244 --- /dev/null +++ b/rust/kernel/debugfs.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2025 Google LLC. + +//! DebugFS Abstraction +//! +//! C header: [`include/linux/debugfs.h`](srctree/include/linux/debugfs.h) + +// When DebugFS is disabled, many parameters are dead. Linting for this is= n't helpful. +#![cfg_attr(not(CONFIG_DEBUG_FS), allow(unused_variables))] + +#[cfg(CONFIG_DEBUG_FS)] +use crate::prelude::*; +use crate::str::CStr; +#[cfg(CONFIG_DEBUG_FS)] +use crate::sync::Arc; + +#[cfg(CONFIG_DEBUG_FS)] +mod entry; +#[cfg(CONFIG_DEBUG_FS)] +use entry::Entry; + +/// Owning handle to a DebugFS directory. +/// +/// This directory will be removed when this handle has been dropped *and*= all children have been +/// removed. +// We hold a reference to our parent if it exists in the `Entry` to preven= t the dentry we point +// to from being cleaned up when our parent is removed. +// +// The `None` option indicates that the `Arc` could not be allocated, so o= ur children would not be +// able to refer to us. In this case, we need to silently fail. All future= child directories/files +// will silently fail as well. +#[derive(Clone)] +pub struct Dir(#[cfg(CONFIG_DEBUG_FS)] Option>); + +impl Dir { + /// Create a new directory in DebugFS. If `parent` is [`None`], it wil= l be created at the root. + fn create(name: &CStr, parent: Option<&Dir>) -> Self { + #[cfg(CONFIG_DEBUG_FS)] + { + let parent_entry =3D match parent { + // If the parent couldn't be allocated, just early-return + Some(Dir(None)) =3D> return Self(None), + Some(Dir(Some(entry))) =3D> Some(entry.clone()), + None =3D> None, + }; + Self( + // If Arc creation fails, the `Entry` will be dropped, so = the directory will be + // cleaned up. + Arc::new(Entry::dynamic_dir(name, parent_entry), GFP_KERNE= L).ok(), + ) + } + #[cfg(not(CONFIG_DEBUG_FS))] + Self() + } + + /// Create a new directory in DebugFS at the root. + /// + /// # Examples + /// + /// ``` + /// # use kernel::c_str; + /// # use kernel::debugfs::Dir; + /// let debugfs =3D Dir::new(c_str!("parent")); + /// ``` + pub fn new(name: &CStr) -> Self { + Dir::create(name, None) + } + + /// Creates a subdirectory within this directory. + /// + /// # Examples + /// + /// ``` + /// # use kernel::c_str; + /// # use kernel::debugfs::Dir; + /// let parent =3D Dir::new(c_str!("parent")); + /// let child =3D parent.subdir(c_str!("child")); + /// ``` + pub fn subdir(&self, name: &CStr) -> Self { + Dir::create(name, Some(self)) + } +} diff --git a/rust/kernel/debugfs/entry.rs b/rust/kernel/debugfs/entry.rs new file mode 100644 index 0000000000000000000000000000000000000000..d2fba0e65e20e954e2a33e776b8= 72bac4adb12e8 --- /dev/null +++ b/rust/kernel/debugfs/entry.rs @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2025 Google LLC. + +use crate::str::CStr; +use crate::sync::Arc; + +/// Owning handle to a DebugFS entry. +/// +/// # Invariants +/// +/// The wrapped pointer will always be `NULL`, an error, or an owned Debug= FS `dentry`. +pub(crate) struct Entry { + entry: *mut bindings::dentry, + // If we were created with an owning parent, this is the keep-alive + _parent: Option>, +} + +// SAFETY: [`Entry`] is just a `dentry` under the hood, which the API prom= ises can be transferred +// between threads. +unsafe impl Send for Entry {} + +// SAFETY: All the C functions we call on the `dentry` pointer are threads= afe. +unsafe impl Sync for Entry {} + +impl Entry { + pub(crate) fn dynamic_dir(name: &CStr, parent: Option>) -> S= elf { + let parent_ptr =3D match &parent { + Some(entry) =3D> entry.as_ptr(), + None =3D> core::ptr::null_mut(), + }; + // SAFETY: The invariants of this function's arguments ensure the = safety of this call. + // * `name` is a valid C string by the invariants of `&CStr`. + // * `parent_ptr` is either `NULL` (if `parent` is `None`), or a p= ointer to a valid + // `dentry` by our invariant. `debugfs_create_dir` handles `NULL= ` pointers correctly. + let entry =3D unsafe { bindings::debugfs_create_dir(name.as_char_p= tr(), parent_ptr) }; + + Entry { + entry, + _parent: parent, + } + } + + /// Returns the pointer representation of the DebugFS directory. + /// + /// # Guarantees + /// + /// Due to the type invariant, the value returned from this function w= ill always be an error + /// code, NULL, or a live DebugFS directory. If it is live, it will re= main live at least as + /// long as this entry lives. + pub(crate) fn as_ptr(&self) -> *mut bindings::dentry { + self.entry + } +} + +impl Drop for Entry { + fn drop(&mut self) { + // SAFETY: `debugfs_remove` can take `NULL`, error values, and leg= al DebugFS dentries. + // `as_ptr` guarantees that the pointer is of this form. + unsafe { bindings::debugfs_remove(self.as_ptr()) } + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index ed53169e795c0badf548025a57f946fa18bc73e3..828620c8441566a638f31d03633= fc1bf4c1bda85 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -76,6 +76,7 @@ pub mod cpufreq; pub mod cpumask; pub mod cred; +pub mod debugfs; pub mod device; pub mod device_id; pub mod devres; --=20 2.51.0.rc1.167.g924127e9c0-goog From nobody Sat Oct 4 04:56:48 2025 Received: from mail-pj1-f74.google.com (mail-pj1-f74.google.com [209.85.216.74]) (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 629402494FF for ; Tue, 19 Aug 2025 22:53:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.74 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644030; cv=none; b=qEimZX4tpFzvBr0abeweEU63mermXpAUL3UwzdrRT/TxsLXA0k5J3GuZjCw3ojFc0fVzmHcz8qlkFnubKNjd8Lp65k1FrlrwF7j081V386FhVYCHD0VjOUtQAebtFxCr0zlKT4qzGYv84W7n6M6pnIxnILJuCMwUAFQxdDf5nAI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644030; c=relaxed/simple; bh=9xvFviyFtcxJh2vxhNHl4ezZw79kWDSBe2uv8ULynnk=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=HzooUUvy7Ea2CECO0GMTrC0j2eBsurmSmAL8DY200gS74yEsf940vrEXIDulwjTEXWFGK470GMkM7YVVvWZLgPTbTOBI/rNCcdZRJw9sX/0OMRoD3ieNCx3hvmmhISEyAOwvrdzmDcosZnbKuWEf5L3nO5xDGlcLC4za4pg8w2U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=MvOhLV5d; arc=none smtp.client-ip=209.85.216.74 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="MvOhLV5d" Received: by mail-pj1-f74.google.com with SMTP id 98e67ed59e1d1-32326bd7502so5202165a91.2 for ; Tue, 19 Aug 2025 15:53:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1755644028; x=1756248828; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=OXQqr0jamg7WIkvF5k/2nGStwbn54x7YRQeARNyaUng=; b=MvOhLV5dT379KzDsxzCq9clrdXkDZDRxIsfSRAQ4O5CApnFVfh1QAhrk/bLznm10t/ 2rMyWzxRxm9bqmipWVmXAC9vl0oe8ffscXKe/KANul1GHKlaeXRFk5vUQPN2b+eYpV1A rxlaYihEK79uwafL6ZyjI6nP+87C8ugVoBRwfAbOTz9OhWfXbuKYOGw/d3ML0k35TqKb v+6Cdng7pyUZOqPvVTvj6gN1djFVpmbu7HLwW5ctmvrsgClmkH8qqHtcu71PMCvBbWKD aRF1jIz7VWZITbjx6yotYxVYCwHUeb464klZ0LHgOgCktOXd5EMfq97+lC4ZpkCLiUhg e2DQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755644028; x=1756248828; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=OXQqr0jamg7WIkvF5k/2nGStwbn54x7YRQeARNyaUng=; b=MRIrO5K+kALAbnyEwlqRvM9g6jUkXjdLTg6/KaOkw+lAmaaOcFs4O4x35T7jA0RMcY er26IXQgikRqnu3xSxVWXkiWemRmqq37RhLjcdt09h12FqjmreKckfxVWWAD36zjSwow 0r5L0P0+Kgx1CCGS8kzY19I3/DHS3Fh7rRNt9rp1mBCNHceSH3vg2Rll8NFFdOI6jf01 MncYJ0fWsPxIyGGLqVCjRx78PfYv+5+80YSJnBe+7DTS7j5uNH9EBlZ+f3ULb+x0yP9k BscqH2UFEAjh8CJNvn372AnM7gE5bhp9jcmhrtZxmh1BFTXJpq0U3pYKZacLj3+vQx54 3L/g== X-Gm-Message-State: AOJu0Ywz1gXNz+/AavFGxODlJM4tn5MYMtMjDv3RLjm/tMvv7UK4ovEM R+Kg5cKhqXFxQIZtNAQ7q3HjoUSi0kBcOJ9Csrjxxwq1iuxBOnIm1XZ9NmGo5JWfrUHJ5LZtWVC fnGpO5JI2uQ== X-Google-Smtp-Source: AGHT+IF7rJS0JgdEObO2kLIendfBNucoHFxr0vzjfs9fV9IR9EmY+lSfuo8EuEAeaOpbAufEGw4yFxjZnAcD X-Received: from pjboh13.prod.google.com ([2002:a17:90b:3a4d:b0:31f:26b:cc66]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:3dcf:b0:321:2b8a:430a with SMTP id 98e67ed59e1d1-324e140bdedmr934145a91.28.1755644027720; Tue, 19 Aug 2025 15:53:47 -0700 (PDT) Date: Tue, 19 Aug 2025 22:53:37 +0000 In-Reply-To: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1755644022; l=14032; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=9xvFviyFtcxJh2vxhNHl4ezZw79kWDSBe2uv8ULynnk=; b=xx3uWkG7wUoqxkUytlHlTitEuKIqtpUmvcpWtth8Vm0UYl2ItnVaeoX5hIRcBCU2VuSCDo6Mc 68EC+Xf3RRODd1tXV488aHC3F8IT2tme5MyiIpUCv+rzvUCA1XjuPQi X-Mailer: b4 0.14.2 Message-ID: <20250819-debugfs-rust-v10-2-86e20f3cf3bb@google.com> Subject: [PATCH v10 2/7] rust: debugfs: Add support for read-only files From: Matthew Maurer To: Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , "=?utf-8?q?Bj=C3=B6rn_Roy_Baron?=" , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Greg Kroah-Hartman , "Rafael J. Wysocki" , Sami Tolvanen , Timur Tabi , Benno Lossin , Dirk Beheme Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Matthew Maurer Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Extends the `debugfs` API to support creating read-only files. This is done via the `Dir::read_only_file` method, which takes a data object that implements the `Render` trait. The file's content is generated by the `Render` implementation, and the file is automatically removed when the returned `File` handle is dropped. Signed-off-by: Matthew Maurer Tested-by: Dirk Behme --- rust/kernel/debugfs.rs | 142 ++++++++++++++++++++++++++++++++++++= +++- rust/kernel/debugfs/entry.rs | 42 ++++++++++++ rust/kernel/debugfs/file_ops.rs | 125 +++++++++++++++++++++++++++++++++++ rust/kernel/debugfs/traits.rs | 28 ++++++++ 4 files changed, 336 insertions(+), 1 deletion(-) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index 893aee54b920bac80f77c2726567da76929b7244..875d433fc3608cc9ffcf022d7c0= 0cb207016f146 100644 --- a/rust/kernel/debugfs.rs +++ b/rust/kernel/debugfs.rs @@ -8,12 +8,18 @@ // When DebugFS is disabled, many parameters are dead. Linting for this is= n't helpful. #![cfg_attr(not(CONFIG_DEBUG_FS), allow(unused_variables))] =20 -#[cfg(CONFIG_DEBUG_FS)] use crate::prelude::*; use crate::str::CStr; #[cfg(CONFIG_DEBUG_FS)] use crate::sync::Arc; +use core::marker::PhantomPinned; +use core::ops::Deref; + +mod traits; +pub use traits::Render; =20 +mod file_ops; +use file_ops::{FileOps, ReadFile}; #[cfg(CONFIG_DEBUG_FS)] mod entry; #[cfg(CONFIG_DEBUG_FS)] @@ -53,6 +59,31 @@ fn create(name: &CStr, parent: Option<&Dir>) -> Self { Self() } =20 + /// Creates a DebugFS file which will own the data produced by the ini= tializer provided in + /// `data`. + fn create_file<'a, T: Sync + 'static, E: 'a, TI: PinInit + 'a>( + &'a self, + name: &'a CStr, + data: TI, + file_ops: &'static FileOps, + ) -> impl PinInit, E> + 'a { + let scope =3D Scope::::new(data, move |data| { + #[cfg(CONFIG_DEBUG_FS)] + if let Some(parent) =3D &self.0 { + // SAFETY: Because data derives from a scope, and our entr= y will be dropped before + // the data is dropped, it is guaranteed to outlive the en= try we return. + unsafe { Entry::dynamic_file(name, parent.clone(), data, f= ile_ops) } + } else { + Entry::empty() + } + }); + try_pin_init! { + File { + scope <- scope + } ? E + } + } + /// Create a new directory in DebugFS at the root. /// /// # Examples @@ -79,4 +110,113 @@ pub fn new(name: &CStr) -> Self { pub fn subdir(&self, name: &CStr) -> Self { Dir::create(name, Some(self)) } + + /// Creates a read-only file in this directory. + /// + /// The file's contents are produced by invoking [`Render::render`] on= the value initialized by + /// `data`. + /// + /// # Examples + /// + /// ``` + /// # use kernel::c_str; + /// # use kernel::debugfs::Dir; + /// # use kernel::prelude::*; + /// # let dir =3D Dir::new(c_str!("my_debugfs_dir")); + /// let file =3D KBox::pin_init(dir.read_only_file(c_str!("foo"), 200)= , GFP_KERNEL)?; + /// // "my_debugfs_dir/foo" now contains the number 200. + /// // The file is removed when `file` is dropped. + /// # Ok::<(), Error>(()) + /// ``` + pub fn read_only_file<'a, T: Render + Send + Sync + 'static, E: 'a, TI= : PinInit + 'a>( + &'a self, + name: &'a CStr, + data: TI, + ) -> impl PinInit, E> + 'a { + let file_ops =3D &>::FILE_OPS; + self.create_file(name, data, file_ops) + } +} + +#[pin_data] +/// Handle to a DebugFS scope, which allows a variety of DebugFS files/dir= ectories to hang off a +/// single structure. +pub struct Scope { + // This order is load-bearing for drops - `_entry` must be dropped bef= ore `data`. + #[cfg(CONFIG_DEBUG_FS)] + _entry: Entry, + #[pin] + data: T, + // Even if `T` is `Unpin`, we still can't allow it to be moved. + #[pin] + _pin: PhantomPinned, +} + +#[pin_data] +/// Handle to a DebugFS file, owning its backing data. +/// +/// When dropped, the DebugFS file will be removed and the attached data w= ill be dropped. +pub struct File { + #[pin] + scope: Scope, +} + +#[cfg(not(CONFIG_DEBUG_FS))] +impl Scope { + fn new, F: for<'a> FnOnce(&'a T)>( + data: TI, + init: F, + ) -> impl PinInit { + try_pin_init! { + Self { + data <- data, + _pin: PhantomPinned + } ? E + } + .pin_chain(|scope| { + init(&scope.data); + Ok(()) + }) + } +} + +#[cfg(CONFIG_DEBUG_FS)] +impl Scope { + fn entry_mut(self: Pin<&mut Self>) -> &mut Entry { + // SAFETY: _entry is not structurally pinned + unsafe { &mut Pin::into_inner_unchecked(self)._entry } + } + fn new<'b, E: 'b, TI: PinInit + 'b, F: for<'a> FnOnce(&'a T) -> = Entry + 'b>( + data: TI, + init: F, + ) -> impl PinInit + 'b + where + T: 'b, + { + try_pin_init! { + Self { + _entry: Entry::empty(), + data <- data, + _pin: PhantomPinned + } ? E + } + .pin_chain(|scope| { + *scope.entry_mut() =3D init(&scope.data); + Ok(()) + }) + } +} + +impl Deref for Scope { + type Target =3D T; + fn deref(&self) -> &T { + &self.data + } +} + +impl Deref for File { + type Target =3D T; + fn deref(&self) -> &T { + &self.scope + } } diff --git a/rust/kernel/debugfs/entry.rs b/rust/kernel/debugfs/entry.rs index d2fba0e65e20e954e2a33e776b872bac4adb12e8..227fa50b7a79aeab49779e54b8c= 4241f455777c3 100644 --- a/rust/kernel/debugfs/entry.rs +++ b/rust/kernel/debugfs/entry.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2025 Google LLC. =20 +use crate::debugfs::file_ops::FileOps; +use crate::ffi::c_void; use crate::str::CStr; use crate::sync::Arc; =20 @@ -40,6 +42,46 @@ pub(crate) fn dynamic_dir(name: &CStr, parent: Option>) -> Self { } } =20 + /// # Safety + /// + /// * `data` must outlive the returned `Entry`. + pub(crate) unsafe fn dynamic_file( + name: &CStr, + parent: Arc, + data: &T, + file_ops: &'static FileOps, + ) -> Self { + // SAFETY: The invariants of this function's arguments ensure the = safety of this call. + // * `name` is a valid C string by the invariants of `&CStr`. + // * `parent.as_ptr()` is a pointer to a valid `dentry` by invaria= nt. + // * The caller guarantees that `data` will outlive the returned `= Entry`. + // * The guarantees on `FileOps` assert the vtable will be compati= ble with the data we have + // provided. + let entry =3D unsafe { + bindings::debugfs_create_file_full( + name.as_char_ptr(), + file_ops.mode(), + parent.as_ptr(), + core::ptr::from_ref(data) as *mut c_void, + core::ptr::null(), + &**file_ops, + ) + }; + + Entry { + entry, + _parent: Some(parent), + } + } + + /// Constructs a placeholder DebugFS [`Entry`]. + pub(crate) fn empty() -> Self { + Self { + entry: core::ptr::null_mut(), + _parent: None, + } + } + /// Returns the pointer representation of the DebugFS directory. /// /// # Guarantees diff --git a/rust/kernel/debugfs/file_ops.rs b/rust/kernel/debugfs/file_ops= .rs new file mode 100644 index 0000000000000000000000000000000000000000..134ac26e80f2e5b9cae53ed5a00= 462af7ce1aa38 --- /dev/null +++ b/rust/kernel/debugfs/file_ops.rs @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2025 Google LLC. + +use super::Render; +use crate::prelude::*; +use crate::seq_file::SeqFile; +use crate::seq_print; +use core::fmt::{Display, Formatter, Result}; +use core::marker::PhantomData; + +#[cfg(CONFIG_DEBUG_FS)] +use core::ops::Deref; + +/// # Invariant +/// +/// `FileOps` will always contain an `operations` which is safe to use = for a file backed +/// off an inode which has a pointer to a `T` in its private data that is = safe to convert +/// into a reference. +pub(super) struct FileOps { + #[cfg(CONFIG_DEBUG_FS)] + operations: bindings::file_operations, + #[cfg(CONFIG_DEBUG_FS)] + mode: u16, + _phantom: PhantomData, +} + +impl FileOps { + /// # Safety + /// The caller asserts that the provided `operations` is safe to use f= or a file whose + /// inode has a pointer to `T` in its private data that is safe to con= vert into a reference. + const unsafe fn new(operations: bindings::file_operations, mode: u16) = -> Self { + Self { + #[cfg(CONFIG_DEBUG_FS)] + operations, + #[cfg(CONFIG_DEBUG_FS)] + mode, + _phantom: PhantomData, + } + } + #[cfg(CONFIG_DEBUG_FS)] + pub(crate) const fn mode(&self) -> u16 { + self.mode + } +} + +#[cfg(CONFIG_DEBUG_FS)] +impl Deref for FileOps { + type Target =3D bindings::file_operations; + fn deref(&self) -> &Self::Target { + &self.operations + } +} + +struct RenderAdapter(T); + +impl<'a, T: Render> Display for RenderAdapter<&'a T> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + self.0.render(f) + } +} + +/// Implements `open` for `file_operations` via `single_open` to fill out = a `seq_file`. +/// +/// # Safety +/// +/// * `inode`'s private pointer must point to a value of type `T` which wi= ll outlive the `inode` +/// and will not have any unique references alias it during the call. +/// * `file` must point to a live, not-yet-initialized file object. +unsafe extern "C" fn render_open( + inode: *mut bindings::inode, + file: *mut bindings::file, +) -> c_int { + // SAFETY: The caller ensures that `inode` is a valid pointer. + let data =3D unsafe { (*inode).i_private }; + // SAFETY: + // * `file` is acceptable by caller precondition. + // * `print_act` will be called on a `seq_file` with private data set = to the third argument, + // so we meet its safety requirements. + // * The `data` pointer passed in the third argument is a valid `T` po= inter that outlives + // this call by caller preconditions. + unsafe { bindings::single_open(file, Some(render_act::), data) } +} + +/// Prints private data stashed in a seq_file to that seq file. +/// +/// # Safety +/// +/// `seq` must point to a live `seq_file` whose private data is a live poi= nter to a `T` which may +/// not have any unique references alias it during the call. +unsafe extern "C" fn render_act( + seq: *mut bindings::seq_file, + _: *mut c_void, +) -> c_int { + // SAFETY: By caller precondition, this pointer is live, points to a v= alue of type `T`, and + // there are not and will not be any unique references until we are do= ne. + let data =3D unsafe { &*((*seq).private.cast::()) }; + // SAFETY: By caller precondition, `seq_file` points to a live `seq_fi= le`, so we can lift + // it. + let seq_file =3D unsafe { SeqFile::from_raw(seq) }; + seq_print!(seq_file, "{}", RenderAdapter(data)); + 0 +} + +// Work around lack of generic const items. +pub(crate) trait ReadFile { + const FILE_OPS: FileOps; +} + +impl ReadFile for T { + const FILE_OPS: FileOps =3D { + let operations =3D bindings::file_operations { + read: Some(bindings::seq_read), + llseek: Some(bindings::seq_lseek), + release: Some(bindings::single_release), + open: Some(render_open::), + // SAFETY: `file_operations` supports zeroes in all fields. + ..unsafe { core::mem::zeroed() } + }; + // SAFETY: `operations` is all stock `seq_file` implementations ex= cept for `render_open`. + // `open`'s only requirement beyond what is provided to all open f= unctions is that the + // inode's data pointer must point to a `T` that will outlive it, = which matches the + // `FileOps` requirements. + unsafe { FileOps::new(operations, 0o400) } + }; +} diff --git a/rust/kernel/debugfs/traits.rs b/rust/kernel/debugfs/traits.rs new file mode 100644 index 0000000000000000000000000000000000000000..2939e18e3dda39571cd7255505e= 5f605f0e3d154 --- /dev/null +++ b/rust/kernel/debugfs/traits.rs @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2025 Google LLC. + +//! Traits for rendering or updating values exported to DebugFS. + +use crate::sync::Mutex; +use core::fmt::{self, Debug, Formatter}; + +/// A trait for types that can be rendered into a string. +/// +/// This works very similarly to `Debug`, and is automatically implemented= if `Debug` is +/// implemented for a type. It is also implemented for any renderable type= inside a `Mutex`. +pub trait Render { + /// Formats the value using the given formatter. + fn render(&self, f: &mut Formatter<'_>) -> fmt::Result; +} + +impl Render for Mutex { + fn render(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.lock().render(f) + } +} + +impl Render for T { + fn render(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "{self:?}") + } +} --=20 2.51.0.rc1.167.g924127e9c0-goog From nobody Sat Oct 4 04:56:48 2025 Received: from mail-pl1-f201.google.com (mail-pl1-f201.google.com [209.85.214.201]) (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 20FC1258EE9 for ; Tue, 19 Aug 2025 22:53:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644031; cv=none; b=XWHACFzOZv5P9sY8zlqjDIlen904gAZJDzGoChGBszXof+4AhMdwi6EXmSE00f6ZV7kWtjL7nULVs0Zu3vzIrilWtRL9Y5zQXabpS66poMjQoVnfq2AjsBMXVP4mCg1XHwexg9B91uTiUuelRwLUb7HB3O+jvAd9iHF75xv/PoU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644031; c=relaxed/simple; bh=O+A6dgp2BS8VYQ5vzwke7ZnXg1yBMRZ4JxfOHZfkf4w=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=Czt5Ywe8hpKbxPT02dJvkXWuc/rBA4KGlc1uUFqdfOOlhbTOhuIdSUGskTy+lpr7OqxBe7bz4xQqw2jx8HNjjHC2SR9qloh/Mky3Z2tiYTFLKM9oduhpYvv5JPJWPgdCtQBOHwjunuj+SJEKRvVzFsvp9EKMrzQu6GAQwUJuOIs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=ySrS5Yrt; arc=none smtp.client-ip=209.85.214.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="ySrS5Yrt" Received: by mail-pl1-f201.google.com with SMTP id d9443c01a7336-24457f44a29so71736725ad.0 for ; Tue, 19 Aug 2025 15:53:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1755644029; x=1756248829; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=2bcGp6peJGEPcea/LfRat8rVkOX5gU/294WJfcP8Kk0=; b=ySrS5Yrt0Exl+RZ4xukUxHONIbGoOPyUo+SRnHYEI/D2p7IwDHmLczdY8fLWV98l75 MH2o4hWBDVElpA3VmsLomMb2HQwGT2TK4ru374hKV46ZGFbNpkTfuNeG8RJePm3/jW1e WcDhsSWjQ8v5Tud9bL64ilo//MInlt9UvoVHBKXd1ljlc8l7WEm/AqyLtuMhK08EDIcN PbAd65h5t3yG9DhCz6b3BzRvJMxYdca79UgB2QrZ5/KU0k5qSlsFYZkua/Z1Tvln/13T wOa2mM3NOtuWWjP3QOsH+knXRoQGVZTk1/JbeZuyfFtRFx8GftLo33hneXl5RNYU/pus e7fQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755644029; x=1756248829; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=2bcGp6peJGEPcea/LfRat8rVkOX5gU/294WJfcP8Kk0=; b=FvWbW/WqLPNMxU9ujTGpWCsf6mWYLsGqJVwUwU6+jy6ylEknm6xHWfArdYAk6vutUP RWlNi8y4mZUJrnw6ZQVPnNj38oNLt6YFFK695fjZQ3Ef3GpNjS1Gu5BriiCmhLmbZOy2 O0q0DNz72d0RZiB5CnhI6MlucvjjNpwmSw8icmtwuNf8GHinDXlOF0byZhRj9cecrCdz /qJOOB93pxstzg4cz2gnXAaoJkE3u5NmohmoIbdaYTw6Xq7cCoTbpkmJ3MGRGQpUIxAX 44YoZvLQJjSFz0N1X46KRtUwfjVLJ4H43cQOZyM9W8fbNIpQAsAYOeeC+ksgYFXQlraW wprQ== X-Gm-Message-State: AOJu0YzyageG2a6onyr5uf61WC39zq0RnFq16NUHesnVsBFvgD27Vd3B 9wGj0/K5rRKRCgDrOI5yZm6CkrzZaWWORy1iNilfQh4vBuXeFgt4QHGoNuT6PhkWnKGvRj2ctQ/ gAGd6FILgAQ== X-Google-Smtp-Source: AGHT+IGI+obYjde/gY6lcO3uPnCqStCuQ/C4QjHznPDL7Oi9XnQ5kAT5YtFaewtYLwGWYsrBoJ6V32tIcBUE X-Received: from plhu2.prod.google.com ([2002:a17:903:1242:b0:23f:fd13:e74d]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a17:902:f312:b0:244:9c27:9829 with SMTP id d9443c01a7336-245ef113d3cmr4376245ad.10.1755644029432; Tue, 19 Aug 2025 15:53:49 -0700 (PDT) Date: Tue, 19 Aug 2025 22:53:38 +0000 In-Reply-To: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1755644022; l=10824; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=O+A6dgp2BS8VYQ5vzwke7ZnXg1yBMRZ4JxfOHZfkf4w=; b=Znvy+yVFxzuQ41yLC/kMxLvFcKXS/+upGpgyKn1EAbmIqrb56WY6yRVYamYZM05sZbl7hv+6v JtYa+rKAHacDE7+4cc+eQ1BQNfKHtDZ4/lsJcIFu9/CtEPpxtHRyWVe X-Mailer: b4 0.14.2 Message-ID: <20250819-debugfs-rust-v10-3-86e20f3cf3bb@google.com> Subject: [PATCH v10 3/7] rust: debugfs: Add support for writable files From: Matthew Maurer To: Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , "=?utf-8?q?Bj=C3=B6rn_Roy_Baron?=" , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Greg Kroah-Hartman , "Rafael J. Wysocki" , Sami Tolvanen , Timur Tabi , Benno Lossin , Dirk Beheme Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Matthew Maurer Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Extends the `debugfs` API to support creating writable files. This is done via the `Dir::write_only_file` and `Dir::read_write_file` methods, which take a data object that implements the `UpdateFromSlice` trait. Signed-off-by: Matthew Maurer Tested-by: Dirk Behme --- rust/kernel/debugfs.rs | 41 +++++++++++++- rust/kernel/debugfs/file_ops.rs | 115 ++++++++++++++++++++++++++++++++++++= +++- rust/kernel/debugfs/traits.rs | 69 ++++++++++++++++++++++++ 3 files changed, 222 insertions(+), 3 deletions(-) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index 875d433fc3608cc9ffcf022d7c00cb207016f146..62bc2b1d4e5a4b21441a09e03bf= f74c32c6781d2 100644 --- a/rust/kernel/debugfs.rs +++ b/rust/kernel/debugfs.rs @@ -16,10 +16,10 @@ use core::ops::Deref; =20 mod traits; -pub use traits::Render; +pub use traits::{Render, UpdateFromSlice}; =20 mod file_ops; -use file_ops::{FileOps, ReadFile}; +use file_ops::{FileOps, ReadFile, ReadWriteFile, WriteFile}; #[cfg(CONFIG_DEBUG_FS)] mod entry; #[cfg(CONFIG_DEBUG_FS)] @@ -136,6 +136,43 @@ pub fn read_only_file<'a, T: Render + Send + Sync + 's= tatic, E: 'a, TI: PinInit< let file_ops =3D &>::FILE_OPS; self.create_file(name, data, file_ops) } + + /// Creates a read-write file in this directory. + /// + /// Reading the file uses the [`Render`] implementation. + /// Writing to the file uses the [`UpdateFromSlice`] implementation. + pub fn read_write_file< + 'a, + T: Render + UpdateFromSlice + Send + Sync + 'static, + E: 'a, + TI: PinInit + 'a, + >( + &'a self, + name: &'a CStr, + data: TI, + ) -> impl PinInit, E> + 'a { + let file_ops =3D &>::FILE_OPS; + self.create_file(name, data, file_ops) + } + + /// Creates a write-only file in this directory. + /// + /// The file owns its backing data. Writing to the file uses the [`Upd= ateFromSlice`] + /// implementation. + /// + /// The file is removed when the returned [`File`] is dropped. + pub fn write_only_file< + 'a, + T: UpdateFromSlice + Send + Sync + 'static, + E: 'a, + TI: PinInit + 'a, + >( + &'a self, + name: &'a CStr, + data: TI, + ) -> impl PinInit, E> + 'a { + self.create_file(name, data, &T::FILE_OPS) + } } =20 #[pin_data] diff --git a/rust/kernel/debugfs/file_ops.rs b/rust/kernel/debugfs/file_ops= .rs index 134ac26e80f2e5b9cae53ed5a00462af7ce1aa38..30f6a0532c7f5f4a2974edc8f11= 00f5485aa8da9 100644 --- a/rust/kernel/debugfs/file_ops.rs +++ b/rust/kernel/debugfs/file_ops.rs @@ -1,10 +1,11 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2025 Google LLC. =20 -use super::Render; +use super::{Render, UpdateFromSlice}; use crate::prelude::*; use crate::seq_file::SeqFile; use crate::seq_print; +use crate::uaccess::UserSlice; use core::fmt::{Display, Formatter, Result}; use core::marker::PhantomData; =20 @@ -123,3 +124,115 @@ impl ReadFile for T { unsafe { FileOps::new(operations, 0o400) } }; } + +fn update(data: &T, buf: *const c_char, count: = usize) -> isize { + let mut reader =3D UserSlice::new(UserPtr::from_ptr(buf as *mut c_void= ), count).reader(); + + if let Err(e) =3D data.update_from_slice(&mut reader) { + return e.to_errno() as isize; + } + + count as isize +} + +/// # Safety +/// +/// `file` must be a valid pointer to a `file` struct. +/// The `private_data` of the file must contain a valid pointer to a `seq_= file` whose +/// `private` data in turn points to a `T` that implements `UpdateFromSlic= e`. +/// `buf` must be a valid user-space buffer. +pub(crate) unsafe extern "C" fn write( + file: *mut bindings::file, + buf: *const c_char, + count: usize, + _ppos: *mut bindings::loff_t, +) -> isize { + // SAFETY: The file was opened with `single_open`, which sets `private= _data` to a `seq_file`. + let seq =3D unsafe { &mut *((*file).private_data.cast::()) }; + // SAFETY: By caller precondition, this pointer is live and points to = a value of type `T`. + let data =3D unsafe { &*(seq.private as *const T) }; + update(data, buf, count) +} + +// A trait to get the file operations for a type. +pub(crate) trait ReadWriteFile { + const FILE_OPS: FileOps; +} + +impl ReadWriteFile for T { + const FILE_OPS: FileOps =3D { + let operations =3D bindings::file_operations { + open: Some(render_open::), + read: Some(bindings::seq_read), + write: Some(write::), + llseek: Some(bindings::seq_lseek), + release: Some(bindings::single_release), + // SAFETY: `file_operations` supports zeroes in all fields. + ..unsafe { core::mem::zeroed() } + }; + // SAFETY: `operations` is all stock `seq_file` implementations ex= cept for `render_open` + // and `write`. + // `render_open`'s only requirement beyond what is provided to all= open functions is that + // the inode's data pointer must point to a `T` that will outlive = it, which matches the + // `FileOps` requirements. + // `write` only requires that the file's private data pointer poin= ts to `seq_file` + // which points to a `T` that will outlive it, which matches what = `render_open` + // provides. + unsafe { FileOps::new(operations, 0o600) } + }; +} + +/// # Safety +/// +/// `inode` must be a valid pointer to an `inode` struct. +/// `file` must be a valid pointer to a `file` struct. +unsafe extern "C" fn write_only_open( + inode: *mut bindings::inode, + file: *mut bindings::file, +) -> c_int { + // SAFETY: The caller ensures that `inode` and `file` are valid pointe= rs. + unsafe { + (*file).private_data =3D (*inode).i_private; + } + 0 +} + +/// # Safety +/// +/// * `file` must be a valid pointer to a `file` struct. +/// * The `private_data` of the file must contain a valid pointer to a `T`= that implements +/// `UpdateFromSlice`. +/// * `buf` must be a valid user-space buffer. +pub(crate) unsafe extern "C" fn write_only_write( + file: *mut bindings::file, + buf: *const c_char, + count: usize, + _ppos: *mut bindings::loff_t, +) -> isize { + // SAFETY: The caller ensures that `file` is a valid pointer and that = `private_data` holds a + // valid pointer to `T`. + let data =3D unsafe { &*((*file).private_data as *const T) }; + update(data, buf, count) +} + +pub(crate) trait WriteFile { + const FILE_OPS: FileOps; +} + +impl WriteFile for T { + const FILE_OPS: FileOps =3D { + let operations =3D bindings::file_operations { + open: Some(write_only_open), + write: Some(write_only_write::), + llseek: Some(bindings::noop_llseek), + // SAFETY: `file_operations` supports zeroes in all fields. + ..unsafe { core::mem::zeroed() } + }; + // SAFETY: + // * `write_only_open` populates the file private data with the in= ode private data + // * `write_only_write`'s only requirement is that the private dat= a of the file point to + // a `T` and be legal to convert to a shared reference, which `w= rite_only_open` + // satisfies. + unsafe { FileOps::new(operations, 0o200) } + }; +} diff --git a/rust/kernel/debugfs/traits.rs b/rust/kernel/debugfs/traits.rs index 2939e18e3dda39571cd7255505e5f605f0e3d154..d64638898faaa1a6a9898c374b8= c1114993376c9 100644 --- a/rust/kernel/debugfs/traits.rs +++ b/rust/kernel/debugfs/traits.rs @@ -3,8 +3,15 @@ =20 //! Traits for rendering or updating values exported to DebugFS. =20 +use crate::prelude::*; use crate::sync::Mutex; +use crate::uaccess::UserSliceReader; use core::fmt::{self, Debug, Formatter}; +use core::str::FromStr; +use core::sync::atomic::{ + AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicU16, Ato= micU32, AtomicU64, + AtomicU8, AtomicUsize, Ordering, +}; =20 /// A trait for types that can be rendered into a string. /// @@ -26,3 +33,65 @@ fn render(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln!(f, "{self:?}") } } + +/// A trait for types that can be updated from a user slice. +/// +/// This works similarly to `FromStr`, but operates on a `UserSliceReader`= rather than a &str. +/// +/// It is automatically implemented for all atomic integers, or any type t= hat implements `FromStr` +/// wrapped in a `Mutex`. +pub trait UpdateFromSlice { + /// Updates the value from the given user slice. + fn update_from_slice(&self, reader: &mut UserSliceReader) -> Result<()= >; +} + +impl UpdateFromSlice for Mutex { + fn update_from_slice(&self, reader: &mut UserSliceReader) -> Result<()= > { + let mut buf =3D [0u8; 128]; + if reader.len() > buf.len() { + return Err(EINVAL); + } + let n =3D reader.len(); + reader.read_slice(&mut buf[..n])?; + + let s =3D core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?; + let val =3D s.trim().parse::().map_err(|_| EINVAL)?; + *self.lock() =3D val; + Ok(()) + } +} + +macro_rules! impl_update_from_slice_for_atomic { + ($(($atomic_type:ty, $int_type:ty)),*) =3D> { + $( + impl UpdateFromSlice for $atomic_type { + fn update_from_slice(&self, reader: &mut UserSliceReader) = -> Result<()> { + let mut buf =3D [0u8; 21]; // Enough for a 64-bit numb= er. + if reader.len() > buf.len() { + return Err(EINVAL); + } + let n =3D reader.len(); + reader.read_slice(&mut buf[..n])?; + + let s =3D core::str::from_utf8(&buf[..n]).map_err(|_| = EINVAL)?; + let val =3D s.trim().parse::<$int_type>().map_err(|_| = EINVAL)?; + self.store(val, Ordering::Relaxed); + Ok(()) + } + } + )* + }; +} + +impl_update_from_slice_for_atomic!( + (AtomicI16, i16), + (AtomicI32, i32), + (AtomicI64, i64), + (AtomicI8, i8), + (AtomicIsize, isize), + (AtomicU16, u16), + (AtomicU32, u32), + (AtomicU64, u64), + (AtomicU8, u8), + (AtomicUsize, usize) +); --=20 2.51.0.rc1.167.g924127e9c0-goog From nobody Sat Oct 4 04:56:48 2025 Received: from mail-pl1-f202.google.com (mail-pl1-f202.google.com [209.85.214.202]) (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 CF50026B0BE for ; Tue, 19 Aug 2025 22:53:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644033; cv=none; b=gGALHsx3JM1IcqVvYoDT5Fdi713cY7Mgqe3wMjqlGvpaTeDIfh8SoWgon4zpy9drC9ZSJ4F26rk2/2nKhhCma0bk2pbHXFnZV9r3QoO6fQm9XKOz3Kb+TAdqQOnGhhkPBY+5aM1Aj3ifIf5ymppP3/fvqaN3XeGmIjWia4OiKr4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644033; c=relaxed/simple; bh=LgVE70Exr9MebvQgLl2/p51njoOXYcatQNyOcdi3UNU=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=q7pGzH6K/bvYjutUnkd0I4MdmRsrY89C9+3xDxQud32xml3c8it2xQl/LocnBMqE9An7kb+fl2PgMbwEwBpsLxMRpGmgEeLjohaSpUDD/Gw7+ijY5g/lElj6wMFmQWX/pgjP0eIv1tpOu3y9ogkBb327E327juhpVKKxcHhQspY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=c0hqOXTS; arc=none smtp.client-ip=209.85.214.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="c0hqOXTS" Received: by mail-pl1-f202.google.com with SMTP id d9443c01a7336-2445806dc88so145983435ad.1 for ; Tue, 19 Aug 2025 15:53:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1755644031; x=1756248831; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=rRluY9LNozQ5/bivWkiW6p+82OL/8mCZ5RHLv0QIIwY=; b=c0hqOXTSc7A2QDFmOu483vRlcPPcKSaFo3+DmCr1dgv8Q9fEcWhVPq2yDkyR4W9I72 IWMCRRF4kLsxd8N/IXPxDKoAO7WaDKEefYt9REv1uQ2OUQ6oTSoJJ1vDw1TrMPV0FLbz vIQ6pX4qC5bzRa5YwKLgZluTurR1HP5bjQCJVH4GI2oxCv97Z+p6waJC/lDv7YNV1Y22 fYLJ6vjper+6tEgRETOIxWUx6JyFUEd7z0qEDse7hf48x7XDBQ8w/yRSjiWWAwVX139P t4Ip2im6VhXMqklqF7FuNzdCZy/m3OCXnECyNOnVUcMKn3N9mrw5dv/AfPyegsakyZZL PGWw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755644031; x=1756248831; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=rRluY9LNozQ5/bivWkiW6p+82OL/8mCZ5RHLv0QIIwY=; b=OqO6w7UTjYs6M3c5v1cKYmw/jG/F09Zb96B37AlAlR9oxpH84544aoZ2VuavrIUtVQ kuFDP5iK70K1K2oEyvNlbz05BIzPTRyiHazP5LpngtJ4vHntKSGl9FY9buXkk7e+5O7f LR7/u137u9H6VfEI25wIciIHVkjRWxN91x9xpAJlYLaBeNri9DFjSDTJotz35Rwuu3zn iBeoaho9arBwj91ZpP0yqgltnjgNTQ1j33D/eo2fj07lEitnEKQyrhEvWu9/P7ylzD5/ snF1Aw09Lza6vcMAs3OielPeTkbhpcrnNAkY7xWzCkL89x96E3pfufC/fB1o24jaVpfJ Y6fQ== X-Gm-Message-State: AOJu0Yyi6U8B/s9zczqZ/yFPvCaZUkPse4P0TIzoq+JWNekNf21Qu3RJ 1+Sst9j9ysCvbFwiADULfWEiDXis4Ye3gXizTzwte5fBsVJL2qcISxMWwMyHf+KjMhWtB/uBpcl lNgX6RFaysg== X-Google-Smtp-Source: AGHT+IGdrOzV7i91Ri5nCXJFbGeFIMcS2nMKO8t8GS8h0YIrL5C0k2wIG1zRldKx0LZr2bGqQFR3vvJw5JxL X-Received: from plnq2.prod.google.com ([2002:a17:902:f782:b0:23f:c627:bd6d]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a17:902:ced0:b0:242:9bbc:c773 with SMTP id d9443c01a7336-245ef287c6amr7974025ad.54.1755644031091; Tue, 19 Aug 2025 15:53:51 -0700 (PDT) Date: Tue, 19 Aug 2025 22:53:39 +0000 In-Reply-To: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1755644022; l=10621; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=LgVE70Exr9MebvQgLl2/p51njoOXYcatQNyOcdi3UNU=; b=Uhz2yFwSTqO8EFH5FIStOa9+c28rNUOLUVBGjWtryQ8irg6IPrpQpKIsoUQGOAYwZS6VFJ7tD q8FYD/5jh8vD1NssjGlCBn82W0IzCoIyqO46pSdGDp6/IwNUfjHOROh X-Mailer: b4 0.14.2 Message-ID: <20250819-debugfs-rust-v10-4-86e20f3cf3bb@google.com> Subject: [PATCH v10 4/7] rust: debugfs: Add support for callback-based files From: Matthew Maurer To: Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , "=?utf-8?q?Bj=C3=B6rn_Roy_Baron?=" , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Greg Kroah-Hartman , "Rafael J. Wysocki" , Sami Tolvanen , Timur Tabi , Benno Lossin , Dirk Beheme Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Matthew Maurer Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Extends the `debugfs` API to support creating files with content generated and updated by callbacks. This is done via the `read_callback_file`, `write_callback_file`, and `read_write_callback_file` methods. These methods allow for more flexible file definition, either because the type already has a `Render` or `UpdateFromSlice` method that doesn't do what you'd like, or because you cannot implement it (e.g. because it's a type defined in another crate or a primitive type). Signed-off-by: Matthew Maurer Tested-by: Dirk Behme --- rust/kernel/debugfs.rs | 95 ++++++++++++++++++++++++ rust/kernel/debugfs/callback_adapters.rs | 122 +++++++++++++++++++++++++++= ++++ rust/kernel/debugfs/file_ops.rs | 8 ++ 3 files changed, 225 insertions(+) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index 62bc2b1d4e5a4b21441a09e03bff74c32c6781d2..a843d01506a54d5f8626dab5223= d006c9a363a91 100644 --- a/rust/kernel/debugfs.rs +++ b/rust/kernel/debugfs.rs @@ -12,12 +12,16 @@ use crate::str::CStr; #[cfg(CONFIG_DEBUG_FS)] use crate::sync::Arc; +use crate::uaccess::UserSliceReader; +use core::fmt; use core::marker::PhantomPinned; use core::ops::Deref; =20 mod traits; pub use traits::{Render, UpdateFromSlice}; =20 +mod callback_adapters; +use callback_adapters::{FormatAdapter, NoRender, WritableAdapter}; mod file_ops; use file_ops::{FileOps, ReadFile, ReadWriteFile, WriteFile}; #[cfg(CONFIG_DEBUG_FS)] @@ -137,6 +141,48 @@ pub fn read_only_file<'a, T: Render + Send + Sync + 's= tatic, E: 'a, TI: PinInit< self.create_file(name, data, file_ops) } =20 + /// Creates a read-only file in this directory, with contents from a c= allback. + /// + /// `f` must be a function item or a non-capturing closure. + /// This is statically asserted and not a safety requirement. + /// + /// # Examples + /// + /// ``` + /// # use core::sync::atomic::{AtomicU32, Ordering}; + /// # use kernel::c_str; + /// # use kernel::debugfs::Dir; + /// # use kernel::prelude::*; + /// # let dir =3D Dir::new(c_str!("foo")); + /// let file =3D KBox::pin_init( + /// dir.read_callback_file(c_str!("bar"), + /// AtomicU32::new(3), + /// &|val, f| { + /// let out =3D val.load(Ordering::Relaxed); + /// writeln!(f, "{out:#010x}") + /// }), + /// GFP_KERNEL)?; + /// // Reading "foo/bar" will show "0x00000003". + /// file.store(10, Ordering::Relaxed); + /// // Reading "foo/bar" will now show "0x0000000a". + /// # Ok::<(), Error>(()) + /// ``` + pub fn read_callback_file< + 'a, + T: Send + Sync + 'static, + E: 'a, + TI: PinInit + 'a, + F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync, + >( + &'a self, + name: &'a CStr, + data: TI, + _f: &'static F, + ) -> impl PinInit, E> + 'a { + let file_ops =3D >::FILE_OPS.adapt(); + self.create_file(name, data, file_ops) + } + /// Creates a read-write file in this directory. /// /// Reading the file uses the [`Render`] implementation. @@ -155,6 +201,33 @@ pub fn read_write_file< self.create_file(name, data, file_ops) } =20 + /// Creates a read-write file in this directory, with logic from callb= acks. + /// + /// Reading from the file is handled by `f`. Writing to the file is ha= ndled by `w`. + /// + /// `f` and `w` must be function items or non-capturing closures. + /// This is statically asserted and not a safety requirement. + pub fn read_write_callback_file< + 'a, + T: Send + Sync + 'static, + E: 'a, + TI: PinInit + 'a, + F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync, + W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync, + >( + &'a self, + name: &'a CStr, + data: TI, + _f: &'static F, + _w: &'static W, + ) -> impl PinInit, E> + 'a { + let file_ops =3D + , W> as file_ops::ReadWrit= eFile<_>>::FILE_OPS + .adapt() + .adapt(); + self.create_file(name, data, file_ops) + } + /// Creates a write-only file in this directory. /// /// The file owns its backing data. Writing to the file uses the [`Upd= ateFromSlice`] @@ -173,6 +246,28 @@ pub fn write_only_file< ) -> impl PinInit, E> + 'a { self.create_file(name, data, &T::FILE_OPS) } + + /// Creates a write-only file in this directory, with write logic from= a callback. + /// + /// `w` must be a function item or a non-capturing closure. + /// This is statically asserted and not a safety requirement. + pub fn write_callback_file< + 'a, + T: Send + Sync + 'static, + E: 'a, + TI: PinInit + 'a, + W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync, + >( + &'a self, + name: &'a CStr, + data: TI, + _w: &'static W, + ) -> impl PinInit, E> + 'a { + let file_ops =3D , W> as WriteFile<_>>= ::FILE_OPS + .adapt() + .adapt(); + self.create_file(name, data, file_ops) + } } =20 #[pin_data] diff --git a/rust/kernel/debugfs/callback_adapters.rs b/rust/kernel/debugfs= /callback_adapters.rs new file mode 100644 index 0000000000000000000000000000000000000000..1b6001306bf4efabf144cb24aa7= 93e07ea8ac2fb --- /dev/null +++ b/rust/kernel/debugfs/callback_adapters.rs @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2025 Google LLC. + +//! Adapters which allow the user to supply a render or update implementat= ion as a value rather +//! than a trait implementation. If provided, it will override the trait i= mplementation. + +use super::{Render, UpdateFromSlice}; +use crate::prelude::*; +use crate::uaccess::UserSliceReader; +use core::fmt; +use core::fmt::Formatter; +use core::marker::PhantomData; +use core::ops::Deref; + +/// # Safety +/// +/// To implement this trait, it must be safe to cast a `&Self` to a `&Inne= r`. +/// It is intended for use in unstacking adapters out of `FileOps` backing= s. +pub(crate) unsafe trait Adapter { + type Inner; +} + +/// Adapter to implement `UpdateFromSlice` via a callback with the same re= presentation as `T`. +/// +/// * Layer it on top of `RenderAdapter` if you want to add a custom callb= ack for `render`. +/// * Layer it on top of `NoRender` to pass through any support present on= the underlying type. +/// +/// # Invariants +/// +/// If an instance for `WritableAdapter<_, W>` is constructed, `W` is inha= bited. +#[repr(transparent)] +pub(crate) struct WritableAdapter { + inner: D, + _writer: PhantomData, +} + +// SAFETY: Stripping off the adapter only removes constraints +unsafe impl Adapter for WritableAdapter { + type Inner =3D D; +} + +impl Render for WritableAdapter { + fn render(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.render(fmt) + } +} + +impl UpdateFromSlice for WritableAdapter +where + W: Fn(&D::Target, &mut UserSliceReader) -> Result + Send + Sync + 'sta= tic, +{ + fn update_from_slice(&self, reader: &mut UserSliceReader) -> Result { + // SAFETY: WritableAdapter<_, W> can only be constructed if W is i= nhabited + let w: &W =3D unsafe { materialize_zst() }; + w(self.inner.deref(), reader) + } +} + +/// Adapter to implement `Render` via a callback with the same representat= ion as `T`. +/// +/// # Invariants +/// +/// If an instance for `FormatAdapter<_, F>` is constructed, `F` is inhabi= ted. +#[repr(transparent)] +pub(crate) struct FormatAdapter { + inner: D, + _formatter: PhantomData, +} + +impl Deref for FormatAdapter { + type Target =3D D; + fn deref(&self) -> &D { + &self.inner + } +} + +impl Render for FormatAdapter +where + F: Fn(&D, &mut Formatter<'_>) -> fmt::Result + 'static, +{ + fn render(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + // SAFETY: FormatAdapter<_, F> can only be constructed if F is inh= abited + let f: &F =3D unsafe { materialize_zst() }; + f(&self.inner, fmt) + } +} + +// SAFETY: Stripping off the adapter only removes constraints +unsafe impl Adapter for FormatAdapter { + type Inner =3D D; +} + +#[repr(transparent)] +pub(crate) struct NoRender { + inner: D, +} + +// SAFETY: Stripping off the adapter only removes constraints +unsafe impl Adapter for NoRender { + type Inner =3D D; +} + +impl Deref for NoRender { + type Target =3D D; + fn deref(&self) -> &D { + &self.inner + } +} + +/// For types with a unique value, produce a static reference to it. +/// +/// # Safety +/// +/// The caller asserts that F is inhabited +unsafe fn materialize_zst() -> &'static F { + const { assert!(core::mem::size_of::() =3D=3D 0) }; + let zst_dangle: core::ptr::NonNull =3D core::ptr::NonNull::dangling= (); + // SAFETY: While the pointer is dangling, it is a dangling pointer to = a ZST, based on the + // assertion above. The type is also inhabited, by the caller's assert= ion. This means + // we can materialize it. + unsafe { zst_dangle.as_ref() } +} diff --git a/rust/kernel/debugfs/file_ops.rs b/rust/kernel/debugfs/file_ops= .rs index 30f6a0532c7f5f4a2974edc8f1100f5485aa8da9..d11c09520d4464417003362b746= 8c9b9e1a2e1bf 100644 --- a/rust/kernel/debugfs/file_ops.rs +++ b/rust/kernel/debugfs/file_ops.rs @@ -2,6 +2,7 @@ // Copyright (C) 2025 Google LLC. =20 use super::{Render, UpdateFromSlice}; +use crate::debugfs::callback_adapters::Adapter; use crate::prelude::*; use crate::seq_file::SeqFile; use crate::seq_print; @@ -44,6 +45,13 @@ pub(crate) const fn mode(&self) -> u16 { } } =20 +impl FileOps { + pub(super) const fn adapt(&self) -> &FileOps { + // SAFETY: `Adapter` asserts that `T` can be legally cast to `T::I= nner`. + unsafe { core::mem::transmute(self) } + } +} + #[cfg(CONFIG_DEBUG_FS)] impl Deref for FileOps { type Target =3D bindings::file_operations; --=20 2.51.0.rc1.167.g924127e9c0-goog From nobody Sat Oct 4 04:56:48 2025 Received: from mail-pl1-f202.google.com (mail-pl1-f202.google.com [209.85.214.202]) (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 71007270EDF for ; Tue, 19 Aug 2025 22:53:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644035; cv=none; b=rKk09K1CSaZdnNHppaGq5hbD6qG8qXYg7hbKRTbI8XhZnkqRQHUHrwPZ0QMwzJSIhaHAJZSUQwelnQPSWHTHjwGEGxfnWUiui+fZvVFTJFLiSr0Je1awkpwwGxhEexGHJEIMOVU7wnQn2MeE4Iq9DjV/YYBaQJ089EFbycnhg2o= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644035; c=relaxed/simple; bh=ZpGc70QPrrtvElfWXJhaEH++kUb3knJDS/UdBQgrwLI=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=u6kVJTSrpKGd+F5TmF38iZO9gBuC8SgMHKrXQKCNe+aszY55o9Yywb1zcrkbAZ1N03j2sUexMBlmHX+SZozjNkkv1Qgv4gapi4WC+rtuhmZl24Rk8+yWLZNarLPZmFBG//dXfCNaeNNF2Hiy59jo/pDTKcGO6wPgtbbayrYwohg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=RmKSj6G+; arc=none smtp.client-ip=209.85.214.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="RmKSj6G+" Received: by mail-pl1-f202.google.com with SMTP id d9443c01a7336-24458345f5dso62707005ad.3 for ; Tue, 19 Aug 2025 15:53:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1755644033; x=1756248833; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=/SLswo1lDFjuBwXI2bgASbVb3tblBIglQLwWcu6IbpU=; b=RmKSj6G+XGEyqQNhdjkcAntzd2bMR99tiG0DmQfmWPxUBxgcEPCGAgr9wCF0ZaclBW K9SYZ4ik0cPKoRLtl6qPtyKCGoU2YCwAurbeqz+AB+/T/VytoW1GG+qpySy4XaCJPgwU v6S0zB9oiVUYo/1QrVc4nIgUq9Ne2KnnIxKGr1viJyspymjztV3QdUsO20KsrTKQpfB5 OkpWykZWesYs/wS4i2uRPwQwY6FnU1D8Rps90DTsr/u2qvMXJOpLJ76HulymAO+EcAvQ BtJ6zOGLtuPbKcxZcMH3hEQkkqCj1zBOKzwaVzZkhuA/aP2P4eIsHyPX6PMDPjzpZACN g5Dw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755644033; x=1756248833; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=/SLswo1lDFjuBwXI2bgASbVb3tblBIglQLwWcu6IbpU=; b=cH21QF51ot+rAJCKwFjlx9Au0pkPSmNE+I90HTg/xug0Y5ZPXZiXwPeaKPsQ2ZB2Ff bFqH1VZjjrPAz/JgOP/ukUmeeECopY58CNmdU5leVLdlFwzE0MeChPNAWZIdItw42K6q XwJCvzpaO/qZASyCH7qdyGKbiFfndaxl5FkzHiHEo8Rrb88lwm5O1AwNbFm3XLeM/ITu zSFmhRLY4cE8KASg4wHOgk7Rh+SLmSr643YGSa04+X1I+zmkz2kXyyMUoIsn+iaajAWL 35yMHr/2PlBfGLzCehdCEo55vXsE7Hv1+wVk7Z5ixJFnaAVNekO0DJqEQCDQX/a2J6g6 4p9A== X-Gm-Message-State: AOJu0YzKAtag/X6rIZffu6kWeR5myafgZKNS3adETEyJR53oQXVpQRMd Un3lRYoJ4JHvXy5CSCeBg2O44q5FvwTRxa9Rmifyl4JW9zpoJDGDHn+6WanjVnn06Q2knexi/zm /wBMlogH0EQ== X-Google-Smtp-Source: AGHT+IE3H/SdYKRJfmy7QtD9T+6UGxj+Ps8s8uwL4nrDI/GDDg3zFTD+cehisHjgy5z9d1F23iE1jQ0Fg4Fo X-Received: from plbmq6.prod.google.com ([2002:a17:902:fd46:b0:23f:e9a5:d20a]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a17:902:cec3:b0:240:70d4:85d9 with SMTP id d9443c01a7336-245eee3fd84mr7365435ad.0.1755644032810; Tue, 19 Aug 2025 15:53:52 -0700 (PDT) Date: Tue, 19 Aug 2025 22:53:40 +0000 In-Reply-To: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1755644022; l=7449; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=ZpGc70QPrrtvElfWXJhaEH++kUb3knJDS/UdBQgrwLI=; b=9QmaIAstxTcrp08mXXo5Gzw9nlNHf5yZZNsJA3fvv6cPu0QlQH7XntqecATb38XL917IOyf5s NrO8cyphnf+BDTxJz4iw/4xFMIEIwL+toGyFlPrbtBLXsodgDk8uaHY X-Mailer: b4 0.14.2 Message-ID: <20250819-debugfs-rust-v10-5-86e20f3cf3bb@google.com> Subject: [PATCH v10 5/7] samples: rust: Add debugfs sample driver From: Matthew Maurer To: Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , "=?utf-8?q?Bj=C3=B6rn_Roy_Baron?=" , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Greg Kroah-Hartman , "Rafael J. Wysocki" , Sami Tolvanen , Timur Tabi , Benno Lossin , Dirk Beheme Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Matthew Maurer Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Adds a new sample driver that demonstrates the debugfs APIs. The driver creates a directory in debugfs and populates it with a few files: - A read-only file that displays a fwnode property. - A read-write file that exposes an atomic counter. - A read-write file that exposes a custom struct. This sample serves as a basic example of how to use the `debugfs::Dir` and `debugfs::File` APIs to create and manage debugfs entries. Signed-off-by: Matthew Maurer Tested-by: Dirk Behme --- MAINTAINERS | 1 + samples/rust/Kconfig | 11 ++++ samples/rust/Makefile | 1 + samples/rust/rust_debugfs.rs | 151 +++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 164 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 8f2dbf71ca3f8f97e4d7619375279ed11d1261b2..5b6db584a33dd7ee39de3fdd008= 5d2bd7b7bef0e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7481,6 +7481,7 @@ F: rust/kernel/devres.rs F: rust/kernel/driver.rs F: rust/kernel/faux.rs F: rust/kernel/platform.rs +F: samples/rust/rust_debugfs.rs F: samples/rust/rust_driver_platform.rs F: samples/rust/rust_driver_faux.rs =20 diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index 7f7371a004ee0a8f67dca99c836596709a70c4fa..01101db41ae31b08a86d048cdd2= 7da8ef9bb23a2 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -62,6 +62,17 @@ config SAMPLE_RUST_DMA =20 If unsure, say N. =20 +config SAMPLE_RUST_DEBUGFS + tristate "DebugFS Test Module" + depends on DEBUG_FS + help + This option builds the Rust DebugFS Test module sample. + + To compile this as a module, choose M here: + the module will be called rust_debugfs. + + If unsure, say N. + config SAMPLE_RUST_DRIVER_PCI tristate "PCI Driver" depends on PCI diff --git a/samples/rust/Makefile b/samples/rust/Makefile index bd2faad63b4f3befe7d1ed5139fe25c7a8b6d7f6..61276222a99f8cc6d7f84c26d05= 33b30815ebadd 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -4,6 +4,7 @@ ccflags-y +=3D -I$(src) # needed for trace events obj-$(CONFIG_SAMPLE_RUST_MINIMAL) +=3D rust_minimal.o obj-$(CONFIG_SAMPLE_RUST_MISC_DEVICE) +=3D rust_misc_device.o obj-$(CONFIG_SAMPLE_RUST_PRINT) +=3D rust_print.o +obj-$(CONFIG_SAMPLE_RUST_DEBUGFS) +=3D rust_debugfs.o obj-$(CONFIG_SAMPLE_RUST_DMA) +=3D rust_dma.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI) +=3D rust_driver_pci.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM) +=3D rust_driver_platform.o diff --git a/samples/rust/rust_debugfs.rs b/samples/rust/rust_debugfs.rs new file mode 100644 index 0000000000000000000000000000000000000000..8d394f06b37e69ea1c30a3b0d84= 44c80562cc5ab --- /dev/null +++ b/samples/rust/rust_debugfs.rs @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! Sample DebugFS exporting platform driver +//! +//! To successfully probe this driver with ACPI, use an ssdt that looks li= ke +//! +//! ```dsl +//! DefinitionBlock ("", "SSDT", 2, "TEST", "VIRTACPI", 0x00000001) +//! { +//! Scope (\_SB) +//! { +//! Device (T432) +//! { +//! Name (_HID, "LNUXDEBF") // ACPI hardware ID to match +//! Name (_UID, 1) +//! Name (_STA, 0x0F) // Device present, enabled +//! Name (_DSD, Package () { // Sample attribute +//! ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), +//! Package() { +//! Package(2) {"compatible", "sample-debugfs"} +//! } +//! }) +//! Name (_CRS, ResourceTemplate () +//! { +//! Memory32Fixed (ReadWrite, 0xFED00000, 0x1000) +//! }) +//! } +//! } +//! } +//! ``` + +use core::str::FromStr; +use core::sync::atomic::AtomicUsize; +use core::sync::atomic::Ordering; +use kernel::c_str; +use kernel::debugfs::{Dir, File}; +use kernel::new_mutex; +use kernel::prelude::*; +use kernel::sync::Mutex; + +use kernel::{acpi, device::Core, of, platform, str::CString, types::ARef}; + +kernel::module_platform_driver! { + type: RustDebugFs, + name: "rust_debugfs", + authors: ["Matthew Maurer"], + description: "Rust DebugFS usage sample", + license: "GPL", +} + +#[pin_data] +struct RustDebugFs { + pdev: ARef, + // As we only hold these for drop effect (to remove the directory/file= s) we have a leading + // underscore to indicate to the compiler that we don't expect to use = this field directly. + _debugfs: Dir, + #[pin] + _compatible: File, + #[pin] + counter: File, + #[pin] + inner: File>, +} + +#[derive(Debug)] +struct Inner { + x: u32, + y: u32, +} + +impl FromStr for Inner { + type Err =3D Error; + fn from_str(s: &str) -> Result { + let mut parts =3D s.split_whitespace(); + let x =3D parts + .next() + .ok_or(EINVAL)? + .parse::() + .map_err(|_| EINVAL)?; + let y =3D parts + .next() + .ok_or(EINVAL)? + .parse::() + .map_err(|_| EINVAL)?; + if parts.next().is_some() { + return Err(EINVAL); + } + Ok(Inner { x, y }) + } +} + +kernel::acpi_device_table!( + ACPI_TABLE, + MODULE_ACPI_TABLE, + ::IdInfo, + [(acpi::DeviceId::new(c_str!("LNUXDEBF")), ())] +); + +impl platform::Driver for RustDebugFs { + type IdInfo =3D (); + const OF_ID_TABLE: Option> =3D None; + const ACPI_ID_TABLE: Option> =3D Some(&ACP= I_TABLE); + + fn probe( + pdev: &platform::Device, + _info: Option<&Self::IdInfo>, + ) -> Result>> { + let result =3D KBox::try_pin_init(RustDebugFs::new(pdev), GFP_KERN= EL)?; + // We can still mutate fields through the files which are atomic o= r mutexed: + result.counter.store(91, Ordering::Relaxed); + { + let mut guard =3D result.inner.lock(); + guard.x =3D guard.y; + guard.y =3D 42; + } + Ok(result) + } +} + +impl RustDebugFs { + fn build_counter(dir: &Dir) -> impl PinInit> + '_ { + dir.read_write_file(c_str!("counter"), AtomicUsize::new(0)) + } + + fn build_inner(dir: &Dir) -> impl PinInit>> + '_ { + dir.read_write_file(c_str!("pair"), new_mutex!(Inner { x: 3, y: 10= })) + } + + fn new(pdev: &platform::Device) -> impl PinInit + '= _ { + let debugfs =3D Dir::new(c_str!("sample_debugfs")); + let dev =3D pdev.as_ref(); + + try_pin_init! { + Self { + _compatible <- debugfs.read_only_file( + c_str!("compatible"), + dev.fwnode() + .ok_or(ENOENT)? + .property_read::(c_str!("compatible")) + .required_by(dev)?, + ), + counter <- Self::build_counter(&debugfs), + inner <- Self::build_inner(&debugfs), + _debugfs: debugfs, + pdev: pdev.into(), + } + } + } +} --=20 2.51.0.rc1.167.g924127e9c0-goog From nobody Sat Oct 4 04:56:48 2025 Received: from mail-pl1-f202.google.com (mail-pl1-f202.google.com [209.85.214.202]) (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 29E95274B59 for ; Tue, 19 Aug 2025 22:53:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644038; cv=none; b=bleXQJcUQHqsVbfHKl5bmtf7trYnU5Te5wb67X/Ds9OkKFYdsAmUPjJs1mHHkxx/DjONycE5yiHma/3Qh8F6equHGY9CDRIhteSS+bJeIWWajau9HURqKCOlbkNeAnyBhMaYWNvrPeGQy2qN+Pp0jd8rUPNJPQN6EYJt0ZiZnZM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644038; c=relaxed/simple; bh=Q6gbENqBZLgftJNURwx+pcGrvP2HoM5MUdDZJTubatI=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=UvLPW19Boi82Lx7v+OInWfxd8nwOd2vQnJy9LSFLBj2AbkQr8dBA69/JCT9KbVAemDIKm4NztHHv97VxpTOx2JR8gOYpieRibO4D3s7qayl4pGXarEzrzzz2aDm9emyK8OyVpcJB28Iiycj3izcNEu/kpbxj+nm+uzOyicQCqZ0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=F1ME8vxL; arc=none smtp.client-ip=209.85.214.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="F1ME8vxL" Received: by mail-pl1-f202.google.com with SMTP id d9443c01a7336-24457f59889so61982905ad.0 for ; Tue, 19 Aug 2025 15:53:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1755644036; x=1756248836; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=iYi9g84IW9mJ6DZoQ8KgU6DxtgqHpReZrDyUTPXUQwY=; b=F1ME8vxL+fNdIgm0T3v+cctm/Jk50ZGqrW810nyDRhhGhLVUyylMuqOwixf+KX7urC NUGOnmT5TrhSyVmp5IqGge4LzU8qUyvKYzsJXN2E/DfzLhnEZ9532m93IN8xUlhWvPOd 1vg48sGBRcWP4HxObjyuuuJrbsF+f31JRL9my829Noko7CafIYSOGb8Te2Xa4dpo6gSb lOqzUmejIVEaVKDCk2N06w7N6A/sJYWySS0VyNNRsiMQYenRmHpIa6F4Q4dXU6mvZW8Q Prkc037nKHR55j5ZNsHi97CRhYX5/QFKxw5SWYm10S7OLrXEdu7pGjHJxrkKOzU+qc5Y sbTA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755644036; x=1756248836; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=iYi9g84IW9mJ6DZoQ8KgU6DxtgqHpReZrDyUTPXUQwY=; b=tuurW2nNhKEHQ3Q6XwyCDn8PssZdk+8BVx/jxq9QMrwZ1ZkTPnnGymubRTLYWngdpr aVexnyFt1s0XQK44rVc3YweBaj6mIr5a6rmUkOkzR/JOeW1F88xnlGWQZ/x5lslUei0G O1eoML/D3JYb4eYS1ll086lfK0slaQRkcK5wEagQoGhajU9VTj2d8ZeAgbWPj2S6AwF3 OJNv9h+B9vrgbqeIGtiXhwHQW0ND/Jlfl7PzBRWw1MQCh0EM924suAhGoGIrGWJ3puIU 2Aw1yC4Tijm50+62wn533GmOvHCz+kl2EKVoCF8jUkXiPH0nDIf2ivNsKKHhREZ2NSGq NMeQ== X-Gm-Message-State: AOJu0YxOhped2DR20c/eKx+97lxEpcre+y7EXnuzIqh+NxzVDxpKH7Fi Rw8PkqAzwej8FQQsLdhMwaPtJX1VuNRacKnezmsWbRuDgA5MG85ihdqzHcJkS4NGUZCXmAz354p jXDUnTSebZQ== X-Google-Smtp-Source: AGHT+IHFMsiN8zWFvgpPY4KvwcYgAO/SFPqahNYCFRQvt77/3GvahzpIinaV/VYbyYstkHvBGY5gInZDaxyU X-Received: from pldo18.prod.google.com ([2002:a17:903:92:b0:235:f4e3:ba29]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a17:902:ef47:b0:240:99e6:6bc3 with SMTP id d9443c01a7336-245ef15604cmr6993685ad.20.1755644035672; Tue, 19 Aug 2025 15:53:55 -0700 (PDT) Date: Tue, 19 Aug 2025 22:53:41 +0000 In-Reply-To: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1755644022; l=17271; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=Q6gbENqBZLgftJNURwx+pcGrvP2HoM5MUdDZJTubatI=; b=JWi2UzldE/O4OPm9njbpBfh2G30Bs4WkbSyMoGucjKWWlNyaZftKZnSa6mOPqHvyYLCiT1NJf JqOtO/niVpeAp/KRB/cDTA28XaKYm+Aw6R2alKeVBKH0Q9/xbrW6sPb X-Mailer: b4 0.14.2 Message-ID: <20250819-debugfs-rust-v10-6-86e20f3cf3bb@google.com> Subject: [PATCH v10 6/7] rust: debugfs: Add support for scoped directories From: Matthew Maurer To: Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , "=?utf-8?q?Bj=C3=B6rn_Roy_Baron?=" , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Greg Kroah-Hartman , "Rafael J. Wysocki" , Sami Tolvanen , Timur Tabi , Benno Lossin , Dirk Beheme Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Matthew Maurer Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Introduces the concept of a `ScopedDir`, which allows for the creation of debugfs directories and files that are tied to the lifetime of a particular data structure. This ensures that debugfs entries do not outlive the data they refer to. The new `Dir::scope` method creates a new directory that is owned by a `Scope` handle. All files and subdirectories created within this scope are automatically cleaned up when the `Scope` is dropped. Signed-off-by: Matthew Maurer Tested-by: Dirk Behme --- rust/kernel/debugfs.rs | 267 +++++++++++++++++++++++++++++++++++++++= +++- rust/kernel/debugfs/entry.rs | 73 +++++++++++- 2 files changed, 330 insertions(+), 10 deletions(-) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index a843d01506a54d5f8626dab5223d006c9a363a91..5e2b60cc1ea3eff859dbad8d7dd= 7a84d7c08d766 100644 --- a/rust/kernel/debugfs.rs +++ b/rust/kernel/debugfs.rs @@ -14,7 +14,10 @@ use crate::sync::Arc; use crate::uaccess::UserSliceReader; use core::fmt; +use core::marker::PhantomData; use core::marker::PhantomPinned; +#[cfg(CONFIG_DEBUG_FS)] +use core::mem::ManuallyDrop; use core::ops::Deref; =20 mod traits; @@ -40,7 +43,7 @@ // able to refer to us. In this case, we need to silently fail. All future= child directories/files // will silently fail as well. #[derive(Clone)] -pub struct Dir(#[cfg(CONFIG_DEBUG_FS)] Option>); +pub struct Dir(#[cfg(CONFIG_DEBUG_FS)] Option>>); =20 impl Dir { /// Create a new directory in DebugFS. If `parent` is [`None`], it wil= l be created at the root. @@ -268,6 +271,54 @@ pub fn write_callback_file< .adapt(); self.create_file(name, data, file_ops) } + + // While this function is safe, it is intentionally not public because= it's a bit of a + // footgun. + // + // Unless you also extract the `entry` later and schedule it for `Drop= ` at the appropriate + // time, a `ScopedDir` with a `Dir` parent will never be deleted. + fn scoped_dir<'data>(&self, name: &CStr) -> ScopedDir<'data, 'static> { + #[cfg(CONFIG_DEBUG_FS)] + { + let parent_entry =3D match &self.0 { + None =3D> return ScopedDir::empty(), + Some(entry) =3D> entry.clone(), + }; + ScopedDir { + entry: ManuallyDrop::new(Entry::dynamic_dir(name, Some(par= ent_entry))), + _phantom: PhantomData, + } + } + #[cfg(not(CONFIG_DEBUG_FS))] + ScopedDir::empty() + } + + /// Creates a new scope, which is a directory associated with some dat= a `T`. + /// + /// The created directory will be a subdirectory of `self`. The `init`= closure is called to + /// populate the directory with files and subdirectories. These files = can reference the data + /// stored in the scope. + /// + /// The entire directory tree created within the scope will be removed= when the returned + /// `Scope` handle is dropped. + pub fn scope< + 'a, + T: 'a, + E: 'a, + TI: PinInit + 'a, + F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>)= + 'a, + >( + &'a self, + data: TI, + name: &'a CStr, + init: F, + ) -> impl PinInit, E> + 'a { + Scope::new(data, |data| { + let scoped =3D self.scoped_dir(name); + init(data, &scoped); + scoped.into_entry() + }) + } } =20 #[pin_data] @@ -276,7 +327,7 @@ pub fn write_callback_file< pub struct Scope { // This order is load-bearing for drops - `_entry` must be dropped bef= ore `data`. #[cfg(CONFIG_DEBUG_FS)] - _entry: Entry, + _entry: Entry<'static>, #[pin] data: T, // Even if `T` is `Unpin`, we still can't allow it to be moved. @@ -314,11 +365,11 @@ fn new, F: for<'a> FnOnce(&'a T)= >( =20 #[cfg(CONFIG_DEBUG_FS)] impl Scope { - fn entry_mut(self: Pin<&mut Self>) -> &mut Entry { + fn entry_mut(self: Pin<&mut Self>) -> &mut Entry<'static> { // SAFETY: _entry is not structurally pinned unsafe { &mut Pin::into_inner_unchecked(self)._entry } } - fn new<'b, E: 'b, TI: PinInit + 'b, F: for<'a> FnOnce(&'a T) -> = Entry + 'b>( + fn new<'b, E: 'b, TI: PinInit + 'b, F: for<'a> FnOnce(&'a T) -> = Entry<'static> + 'b>( data: TI, init: F, ) -> impl PinInit + 'b @@ -339,6 +390,36 @@ fn new<'b, E: 'b, TI: PinInit + 'b, F: for<'a> F= nOnce(&'a T) -> Entry + 'b } } =20 +impl Scope { + /// Creates a new scope, which is a directory at the root of the debug= fs filesystem, + /// associated with some data `T`. + /// + /// The `init` closure is called to populate the directory with files = and subdirectories. These + /// files can reference the data stored in the scope. + /// + /// The entire directory tree created within the scope will be removed= when the returned + /// `Scope` handle is dropped. + pub fn dir< + 'a, + E: 'a, + TI: PinInit + 'a, + F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>)= + 'a, + >( + data: TI, + name: &'a CStr, + init: F, + ) -> impl PinInit + 'a + where + T: 'a, + { + Scope::new(data, |data| { + let scoped =3D ScopedDir::new(name); + init(data, &scoped); + scoped.into_entry() + }) + } +} + impl Deref for Scope { type Target =3D T; fn deref(&self) -> &T { @@ -352,3 +433,181 @@ fn deref(&self) -> &T { &self.scope } } + +/// A handle to a directory which will live at most `'dir`, accessing data= that will live for at +/// least `'data`. +/// +/// Dropping a ScopedDir will not delete or clean it up, this is expected = to occur through dropping +/// the `Scope` that created it. +pub struct ScopedDir<'data, 'dir> { + #[cfg(CONFIG_DEBUG_FS)] + entry: ManuallyDrop>, + _phantom: PhantomData &'dir ()>, +} + +impl<'data, 'dir> ScopedDir<'data, 'dir> { + /// Creates a subdirectory inside this `ScopedDir`. + /// + /// The returned directory handle cannot outlive this one. + pub fn dir<'dir2>(&'dir2 self, name: &CStr) -> ScopedDir<'data, 'dir2>= { + #[cfg(not(CONFIG_DEBUG_FS))] + let _ =3D name; + ScopedDir { + #[cfg(CONFIG_DEBUG_FS)] + entry: ManuallyDrop::new(Entry::dir(name, Some(&*self.entry))), + _phantom: PhantomData, + } + } + + fn create_file(&self, name: &CStr, data: &'data T, vtable: &'= static FileOps) { + #[cfg(CONFIG_DEBUG_FS)] + core::mem::forget(Entry::file(name, &self.entry, data, vtable)); + } + + /// Creates a read-only file in this directory. + /// + /// The file's contents are produced by invoking [`Render::render`]`. + /// + /// This function does not produce an owning handle to the file. The c= reated + /// file is removed when the [`Scope`] that this directory belongs + /// to is dropped. + pub fn read_only_file(&self, name: = &CStr, data: &'data T) { + self.create_file(name, data, &T::FILE_OPS) + } + + /// Creates a read-only file in this directory, with contents from a c= allback. + /// + /// The file contents are generated by calling `f` with `data`. + /// + /// + /// `f` must be a function item or a non-capturing closure. + /// This is statically asserted and not a safety requirement. + /// + /// This function does not produce an owning handle to the file. The c= reated + /// file is removed when the [`Scope`] that this directory belongs + /// to is dropped. + pub fn read_callback_file< + T: Send + Sync + 'static, + F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync, + >( + &self, + name: &CStr, + data: &'data T, + _f: &'static F, + ) { + let vtable =3D as ReadFile<_>>::FILE_OPS.adap= t(); + self.create_file(name, data, vtable) + } + + /// Creates a read-write file in this directory. + /// + /// Reading the file uses the [`Render`] implementation on `data`. Wri= ting to the file uses + /// the [`UpdateFromSlice`] implementation on `data`. + /// + /// This function does not produce an owning handle to the file. The c= reated + /// file is removed when the [`Scope`] that this directory belongs + /// to is dropped. + pub fn read_write_file( + &self, + name: &CStr, + data: &'data T, + ) { + let vtable =3D &>::FILE_OPS; + self.create_file(name, data, vtable) + } + + /// Creates a read-write file in this directory, with logic from callb= acks. + /// + /// Reading from the file is handled by `f`. Writing to the file is ha= ndled by `w`. + /// + /// `f` and `w` must be function items or non-capturing closures. + /// This is statically asserted and not a safety requirement. + /// + /// This function does not produce an owning handle to the file. The c= reated + /// file is removed when the [`Scope`] that this directory belongs + /// to is dropped. + pub fn read_write_callback_file< + T: Send + Sync + 'static, + F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync, + W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync, + >( + &self, + name: &CStr, + data: &'data T, + _f: &'static F, + _w: &'static W, + ) { + let vtable =3D , W> as ReadWri= teFile<_>>::FILE_OPS + .adapt() + .adapt(); + self.create_file(name, data, vtable) + } + + /// Creates a write-only file in this directory. + /// + /// Writing to the file uses the [`UpdateFromSlice`] implementation on= `data`. + /// + /// This function does not produce an owning handle to the file. The c= reated + /// file is removed when the [`Scope`] that this directory belongs + /// to is dropped. + pub fn write_only_file( + &self, + name: &CStr, + data: &'data T, + ) { + let vtable =3D &>::FILE_OPS; + self.create_file(name, data, vtable) + } + + /// Creates a write-only file in this directory, with write logic from= a callback. + /// + /// Writing to the file is handled by `w`. + /// + /// `w` must be a function item or a non-capturing closure. + /// This is statically asserted and not a safety requirement. + /// + /// This function does not produce an owning handle to the file. The c= reated + /// file is removed when the [`Scope`] that this directory belongs + /// to is dropped. + pub fn write_only_callback_file< + T: Send + Sync + 'static, + W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync, + >( + &self, + name: &CStr, + data: &'data T, + _w: &'static W, + ) { + let vtable =3D &, W> as WriteFile<_>>:= :FILE_OPS + .adapt() + .adapt(); + self.create_file(name, data, vtable) + } + + fn empty() -> Self { + ScopedDir { + #[cfg(CONFIG_DEBUG_FS)] + entry: ManuallyDrop::new(Entry::empty()), + _phantom: PhantomData, + } + } + #[cfg(CONFIG_DEBUG_FS)] + fn into_entry(self) -> Entry<'dir> { + ManuallyDrop::into_inner(self.entry) + } + #[cfg(not(CONFIG_DEBUG_FS))] + fn into_entry(self) {} +} + +impl<'data> ScopedDir<'data, 'static> { + // This is safe, but intentionally not exported due to footgun status.= A ScopedDir with no + // parent will never be released by default, and needs to have its ent= ry extracted and used + // somewhere. + fn new(name: &CStr) -> ScopedDir<'data, 'static> { + ScopedDir { + #[cfg(CONFIG_DEBUG_FS)] + entry: ManuallyDrop::new(Entry::dir(name, None)), + _phantom: PhantomData, + } + } +} diff --git a/rust/kernel/debugfs/entry.rs b/rust/kernel/debugfs/entry.rs index 227fa50b7a79aeab49779e54b8c4241f455777c3..f99402cd3ba0ca12f62d3699e4d= 6e460d0085d26 100644 --- a/rust/kernel/debugfs/entry.rs +++ b/rust/kernel/debugfs/entry.rs @@ -5,26 +5,29 @@ use crate::ffi::c_void; use crate::str::CStr; use crate::sync::Arc; +use core::marker::PhantomData; =20 /// Owning handle to a DebugFS entry. /// /// # Invariants /// /// The wrapped pointer will always be `NULL`, an error, or an owned Debug= FS `dentry`. -pub(crate) struct Entry { +pub(crate) struct Entry<'a> { entry: *mut bindings::dentry, // If we were created with an owning parent, this is the keep-alive - _parent: Option>, + _parent: Option>>, + // If we were created with a non-owning parent, this prevents us from = outliving it + _phantom: PhantomData<&'a ()>, } =20 // SAFETY: [`Entry`] is just a `dentry` under the hood, which the API prom= ises can be transferred // between threads. -unsafe impl Send for Entry {} +unsafe impl Send for Entry<'_> {} =20 // SAFETY: All the C functions we call on the `dentry` pointer are threads= afe. -unsafe impl Sync for Entry {} +unsafe impl Sync for Entry<'_> {} =20 -impl Entry { +impl Entry<'static> { pub(crate) fn dynamic_dir(name: &CStr, parent: Option>) -> S= elf { let parent_ptr =3D match &parent { Some(entry) =3D> entry.as_ptr(), @@ -39,6 +42,7 @@ pub(crate) fn dynamic_dir(name: &CStr, parent: Option>) -> Self { Entry { entry, _parent: parent, + _phantom: PhantomData, } } =20 @@ -71,14 +75,71 @@ pub(crate) unsafe fn dynamic_file( Entry { entry, _parent: Some(parent), + _phantom: PhantomData, } } +} + +impl<'a> Entry<'a> { + pub(crate) fn dir(name: &CStr, parent: Option<&'a Entry<'_>>) -> Self { + let parent_ptr =3D match &parent { + Some(entry) =3D> entry.as_ptr(), + None =3D> core::ptr::null_mut(), + }; + // SAFETY: The invariants of this function's arguments ensure the = safety of this call. + // * `name` is a valid C string by the invariants of `&CStr`. + // * `parent_ptr` is either `NULL` (if `parent` is `None`), or a p= ointer to a valid + // `dentry` (because `parent` is a valid reference to an `Entry`= ). The lifetime `'a` + // ensures that the parent outlives this entry. + let entry =3D unsafe { bindings::debugfs_create_dir(name.as_char_p= tr(), parent_ptr) }; + + Entry { + entry, + _parent: None, + _phantom: PhantomData, + } + } + + pub(crate) fn file( + name: &CStr, + parent: &'a Entry<'_>, + data: &'a T, + file_ops: &FileOps, + ) -> Self { + // SAFETY: The invariants of this function's arguments ensure the = safety of this call. + // * `name` is a valid C string by the invariants of `&CStr`. + // * `parent.as_ptr()` is a pointer to a valid `dentry` because we= have `&'a Entry`. + // * `data` is a valid pointer to `T` for lifetime `'a`. + // * The returned `Entry` has lifetime `'a`, so it cannot outlive = `parent` or `data`. + // * The caller guarantees that `vtable` is compatible with `data`. + // * The guarantees on `FileOps` assert the vtable will be compati= ble with the data we have + // provided. + let entry =3D unsafe { + bindings::debugfs_create_file_full( + name.as_char_ptr(), + file_ops.mode(), + parent.as_ptr(), + core::ptr::from_ref(data) as *mut c_void, + core::ptr::null(), + &**file_ops, + ) + }; + + Entry { + entry, + _parent: None, + _phantom: PhantomData, + } + } +} =20 +impl Entry<'_> { /// Constructs a placeholder DebugFS [`Entry`]. pub(crate) fn empty() -> Self { Self { entry: core::ptr::null_mut(), _parent: None, + _phantom: PhantomData, } } =20 @@ -94,7 +155,7 @@ pub(crate) fn as_ptr(&self) -> *mut bindings::dentry { } } =20 -impl Drop for Entry { +impl Drop for Entry<'_> { fn drop(&mut self) { // SAFETY: `debugfs_remove` can take `NULL`, error values, and leg= al DebugFS dentries. // `as_ptr` guarantees that the pointer is of this form. --=20 2.51.0.rc1.167.g924127e9c0-goog From nobody Sat Oct 4 04:56:48 2025 Received: from mail-pl1-f201.google.com (mail-pl1-f201.google.com [209.85.214.201]) (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 EE2A223F41F for ; Tue, 19 Aug 2025 22:53:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644039; cv=none; b=eua1CVNHLlJwr8EM4xPo2acRduIF6uMKSwcv2J+fnZmzlYRUd38bBBsfFJikHwbaWuvo58FUAUi+DagNlLU6zkiY3CBhBpMTd+hjDgxzhS84/szhu7xW/NFNZs0m3KNNCdvjdWADVwkL9BzSGyqtcEl9FH9bxe0+Go0S/DKmRf8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755644039; c=relaxed/simple; bh=guKkQWk0Wjy3KEPKk9viQ+9k5JCWYAdpW0xugQqvnfY=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=BQpmipT+uJR+P4GNYCNfho8v3dyc1QiZULWq4iRyNx2i3u8JtWNc5u0pvxiblCZ2fmbZpqEDr3RA0HLoKz4pbHsbNeSWifIRrEQcN1xvwj0VRV3ssFF7XGa3ZKlQ1y/3ickG6JDL87F1du+Cw0BkDNe9zHVhlylaIHauO0w0K28= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=ASAlEIyU; arc=none smtp.client-ip=209.85.214.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--mmaurer.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="ASAlEIyU" Received: by mail-pl1-f201.google.com with SMTP id d9443c01a7336-24458345f5dso62707375ad.3 for ; Tue, 19 Aug 2025 15:53:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1755644037; x=1756248837; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=a5b7ATtWYbDHgkSUGYgdvos0SG8tLhPoIOBmVwWuj9Y=; b=ASAlEIyUnCLLf1PdGwc8unmgxoZTmAKjkPoesAbEh6nuTIdtOEWE+yh9UI4x39fXsi 0OBCY44Jbq7OVVc/8t4WawYDsmc3t0teh/M9n4wa/IbxwTXwkAILEdhSe8FtBI9TKkGo DLfoiOgLEtHHyCjHcBZ+ePXbPgdbpZihr7sgg3ZLjShFtZnZeUH+6mABn02WHJ3l8wwp z2k68cw2bCyYncdUydU9CCg84uuLoDO1U5cBJ15jhn/cS33yOkNfOLBm7WyhqkSy0PD2 VMKbHsT9lYvWnt0/ol0I+XZSTKB/pIxEJqiuT07jP0z+WrmLRZlTPhhEmMeQg2oNApAp Dyzg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755644037; x=1756248837; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=a5b7ATtWYbDHgkSUGYgdvos0SG8tLhPoIOBmVwWuj9Y=; b=AUqJ3tCiKW57EVjFvdPqdWQIe64EQS8OjXgFBWHpCEyl4bXG65DyXEXcvwIfDetZMO G5Fasy+L8lJU+t5IMRLRsjKfv5mpYeep2g0/L/1FJP8/mE4Y89uTV81sV9WE6J9ojLXI 4fgndB9ozH5xidDGX6XMine4ZOqLfx45Jg0GKggWMClyudeaanmSuwLl1oDBW3GcB3Cw 6MRjImvOPkFBSyIUjX67yNn240gXNdn1eqNKpo6C9O3FgimGbd5UGJFY5THkKad82VmW bS27K3S+H4HQ4l7mOryY1WtzRnAHJ96GiEO3g9QvkpEsiHNZP/5KVbRmY12X29aNInDx 8aMQ== X-Gm-Message-State: AOJu0YxOFcCzZxx5kulDfuzHFphrHG++SRVRMzcYPiiOc5rChud5O/nc dUSeFHI5pQUvQVJTsmCqybN4S/QCf46ILIp41Wkm1QD//dtdxrLJQE+lnT2AJi0jDf9Y+KF+dpT xIUjZKvXwZw== X-Google-Smtp-Source: AGHT+IFVb5OKS4efQ9HXGY/B21B3D+QSi9OXzLMr7CD2GATBbB4apepGntwqOQXPMO28tpHljh4Xa0qkb3DQ X-Received: from pjbsd14.prod.google.com ([2002:a17:90b:514e:b0:321:78e7:57fb]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a17:903:181:b0:242:89fd:48cc with SMTP id d9443c01a7336-245ef25b64fmr6737275ad.50.1755644037264; Tue, 19 Aug 2025 15:53:57 -0700 (PDT) Date: Tue, 19 Aug 2025 22:53:42 +0000 In-Reply-To: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1755644022; l=7159; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=guKkQWk0Wjy3KEPKk9viQ+9k5JCWYAdpW0xugQqvnfY=; b=jk2cW46gmwo95KDYJcb0fC6ITVr/buzZ/KWUWFbtmsVlvWaZf4/5notCvfPMHxcEaU2Dy7/jh xeX1m3sFYeVCA0qaSM/V9ZmV08MirTHBWdmuAqEztjgQZag7PvA4CCb X-Mailer: b4 0.14.2 Message-ID: <20250819-debugfs-rust-v10-7-86e20f3cf3bb@google.com> Subject: [PATCH v10 7/7] samples: rust: Add scoped debugfs sample driver From: Matthew Maurer To: Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , "=?utf-8?q?Bj=C3=B6rn_Roy_Baron?=" , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Greg Kroah-Hartman , "Rafael J. Wysocki" , Sami Tolvanen , Timur Tabi , Benno Lossin , Dirk Beheme Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Matthew Maurer Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Adds a new sample driver `rust_scoped_debugfs` that demonstrates the use of the scoped debugfs APIs. The driver creates a `control` directory with two write-only files, `create` and `remove`. Writing a name and a series of numbers to `create` will create a new subdirectory under a `dynamic` directory. This new subdirectory will contain files that expose the numbers as atomic values. Writing a name to `remove` will remove the corresponding subdirectory from the `dynamic` directory. This sample serves as an example of how to use the `debugfs::Scope` and `debugfs::ScopedDir` APIs to create and manage debugfs entries that are tied to the lifetime of a data structure. Signed-off-by: Matthew Maurer Tested-by: Dirk Behme --- MAINTAINERS | 1 + samples/rust/Kconfig | 11 +++ samples/rust/Makefile | 1 + samples/rust/rust_scoped_debugfs.rs | 134 ++++++++++++++++++++++++++++++++= ++++ 4 files changed, 147 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 5b6db584a33dd7ee39de3fdd0085d2bd7b7bef0e..2cbe890085dbb6a652623b38dd0= eadeeaa127a94 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7482,6 +7482,7 @@ F: rust/kernel/driver.rs F: rust/kernel/faux.rs F: rust/kernel/platform.rs F: samples/rust/rust_debugfs.rs +F: samples/rust/rust_scoped_debugfs.rs F: samples/rust/rust_driver_platform.rs F: samples/rust/rust_driver_faux.rs =20 diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index 01101db41ae31b08a86d048cdd27da8ef9bb23a2..3372935519d658529ee7ba25fb2= c3fff6adae8c4 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -73,6 +73,17 @@ config SAMPLE_RUST_DEBUGFS =20 If unsure, say N. =20 +config SAMPLE_RUST_SCOPED_DEBUGFS + tristate "Scoped DebugFS Test Module" + depends on DEBUG_FS + help + This option builds the Rust Scoped DebugFS Test module sample. + + To compile this as a module, choose M here: + the module will be called rust_scoped_debugfs. + + If unsure, say N. + config SAMPLE_RUST_DRIVER_PCI tristate "PCI Driver" depends on PCI diff --git a/samples/rust/Makefile b/samples/rust/Makefile index 61276222a99f8cc6d7f84c26d0533b30815ebadd..de10b7f4db3996dc57be813ceb0= 76d050ad8f65a 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_SAMPLE_RUST_MINIMAL) +=3D rust_minimal.o obj-$(CONFIG_SAMPLE_RUST_MISC_DEVICE) +=3D rust_misc_device.o obj-$(CONFIG_SAMPLE_RUST_PRINT) +=3D rust_print.o obj-$(CONFIG_SAMPLE_RUST_DEBUGFS) +=3D rust_debugfs.o +obj-$(CONFIG_SAMPLE_RUST_SCOPED_DEBUGFS) +=3D rust_scoped_debugfs.o obj-$(CONFIG_SAMPLE_RUST_DMA) +=3D rust_dma.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI) +=3D rust_driver_pci.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM) +=3D rust_driver_platform.o diff --git a/samples/rust/rust_scoped_debugfs.rs b/samples/rust/rust_scoped= _debugfs.rs new file mode 100644 index 0000000000000000000000000000000000000000..7c34cab62a2753d1ede3a1334be= 1fb13ddce780c --- /dev/null +++ b/samples/rust/rust_scoped_debugfs.rs @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! Sample DebugFS exporting platform driver that demonstrates the use of +//! `Scope::dir` to create a variety of files without the need to separate= ly +//! track them all. + +use core::sync::atomic::AtomicUsize; +use kernel::debugfs::{Dir, Scope}; +use kernel::prelude::*; +use kernel::sync::Mutex; +use kernel::{c_str, new_mutex, str::CString}; + +module! { + type: RustScopedDebugFs, + name: "rust_scoped_debugfs", + authors: ["Matthew Maurer"], + description: "Rust Scoped DebugFS usage sample", + license: "GPL", +} + +fn remove_file_write( + mod_data: &ModuleData, + reader: &mut kernel::uaccess::UserSliceReader, +) -> Result<()> { + let mut buf =3D [0u8; 128]; + if reader.len() >=3D buf.len() { + return Err(EINVAL); + } + let n =3D reader.len(); + reader.read_slice(&mut buf[..n])?; + + let s =3D core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?.trim(); + let nul_idx =3D s.len(); + buf[nul_idx] =3D 0; + let to_remove =3D CStr::from_bytes_with_nul(&buf[..nul_idx + 1]).map_e= rr(|_| EINVAL)?; + mod_data + .devices + .lock() + .retain(|device| device.name.as_bytes() !=3D to_remove.as_bytes()); + Ok(()) +} + +fn create_file_write( + mod_data: &ModuleData, + reader: &mut kernel::uaccess::UserSliceReader, +) -> Result<()> { + let mut buf =3D [0u8; 128]; + if reader.len() > buf.len() { + return Err(EINVAL); + } + let n =3D reader.len(); + reader.read_slice(&mut buf[..n])?; + + let mut nums =3D KVec::new(); + + let s =3D core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?.trim(); + let mut items =3D s.split_whitespace(); + let name_str =3D items.next().ok_or(EINVAL)?; + let name =3D CString::try_from_fmt(fmt!("{name_str}"))?; + let file_name =3D CString::try_from_fmt(fmt!("{name_str}"))?; + for sub in items { + nums.push( + AtomicUsize::new(sub.parse().map_err(|_| EINVAL)?), + GFP_KERNEL, + )?; + } + + let scope =3D KBox::pin_init( + mod_data + .device_dir + .scope(DeviceData { name, nums }, &file_name, |dev_data, dir| { + for (idx, val) in dev_data.nums.iter().enumerate() { + let Ok(name) =3D CString::try_from_fmt(fmt!("{idx}")) = else { + return; + }; + dir.read_write_file(&name, val); + } + }), + GFP_KERNEL, + )?; + (*mod_data.devices.lock()).push(scope, GFP_KERNEL)?; + + Ok(()) +} + +struct RustScopedDebugFs { + _data: Pin>>, +} + +#[pin_data] +struct ModuleData { + device_dir: Dir, + #[pin] + devices: Mutex>>>>, +} + +impl ModuleData { + fn init(device_dir: Dir) -> impl PinInit { + pin_init! { + Self { + device_dir: device_dir, + devices <- new_mutex!(KVec::new()) + } + } + } +} + +struct DeviceData { + name: CString, + nums: KVec, +} + +fn init_control(base_dir: &Dir, dyn_dirs: Dir) -> impl PinInit> + '_ { + base_dir.scope( + ModuleData::init(dyn_dirs), + c_str!("control"), + |data, dir| { + dir.write_only_callback_file(c_str!("create"), data, &create_f= ile_write); + dir.write_only_callback_file(c_str!("remove"), data, &remove_f= ile_write); + }, + ) +} + +impl kernel::Module for RustScopedDebugFs { + fn init(_module: &'static kernel::ThisModule) -> Result { + let base_dir =3D Dir::new(c_str!("rust_scoped_debugfs")); + let dyn_dirs =3D base_dir.subdir(c_str!("dynamic")); + Ok(Self { + _data: KBox::pin_init(init_control(&base_dir, dyn_dirs), GFP_K= ERNEL)?, + }) + } +} --=20 2.51.0.rc1.167.g924127e9c0-goog