From nobody Fri Oct 3 05:25:18 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 75F112BD58A for ; Thu, 4 Sep 2025 21:14:04 +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=1757020446; cv=none; b=HzAqGmR4mnMhQcCNQ7ninnEm3JMKYb9a+JlAB9j2ZhrtNnXpF4N87zKa8oOk2Nswdct5iNR8COEzwTNlqYufBeZa14tVNy5h+ylMjhF7bB8rw4y+fcLbWmsGYbzo2rcNje31y03oL0bC5sIuxCzzhEQLiVQ3AdoFUGRtBJ35qPQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757020446; c=relaxed/simple; bh=7Cs+rrmvUc6RnlQb7OHJuhyGOb5Nkjl9WcLbi0YymUk=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=H5oAbfTdbQavc22t3adPp9UC0SuKG5kYDqdvfsJVByLQ5wvEL5q0XHexSbLGmHQc9glY2dzjLHhx09gW5CIio9m+ewQOXzGqJKI6CCH97RDy5/o3NW20wBX27h4QDwxDzLiYBygszcBQuT4JlT6S9xJovNVoxu1nAc9cgXYY7B8= 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=x+P32p1y; 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="x+P32p1y" Received: by mail-pj1-f74.google.com with SMTP id 98e67ed59e1d1-327b5e7f2f6so1939527a91.2 for ; Thu, 04 Sep 2025 14:14:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1757020444; x=1757625244; 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=SYq6tB+gGL9lRINn2bAltq2P9gjzfb/ZMX7i5gK5qaM=; b=x+P32p1yayb4T+hPYhEo4b+qq9gxy3S4LE6bkHEzO72Drv4jKLqjOU8AcGiS1+sUjE lQpuwKO9IPYPbkmWfIXiMd91xUOPDzM/p895Yt6AnTV78/6z/NDurtQbB/WLgpfbVE8Y 9YnFvvN49nZEt2FYMq8+qA/egBnC+FxuEE3Qpa96JTx+Q9+sMw4zhhrpAktXRwSrH5H3 SDsH7DvTJ6T5YaUbcE9ocRrTt0v6ohI/utHNiAS0YgBIGK3RCHVpifKMt5/3XVSg3z0T mEG88G8WdPQY8q/vlwNQArij3Qumrm50GJCZ0m2sh5vesRV7XJpuBkgJaXuh0W1Uc/Ax CNqw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757020444; x=1757625244; 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=SYq6tB+gGL9lRINn2bAltq2P9gjzfb/ZMX7i5gK5qaM=; b=cuTgcjQAHXAqtq6IIeV9cNxkdFQdWMK5MgZpfF4oCtiNNpWTlghi8dAOTf3m1rjRB/ rkzWq98HoWsPZh2Yk9kmvLDIY4ThTR+dblwzcb+tT2JPc+fBECFWZGzrYJ4fKD7tAws0 SxQDr/m9/i1hLvbNQDe8mLEJDVT/G592R1Q4xA+O361y4DoOr5CP0ZJVnuGNOfdDQScV 2lf4r6pwcAcbUtY7rHACv68+Sb7zfeRlrFutgHTv4MsiIAmP796YUd74qRwMwTfR/xUP UhncSiED4ENcG9H26bJjHs2FFed6VSNKtpAh50LCzFiJcfae6By5cy9fdOS5v0FKpqHf leIg== X-Gm-Message-State: AOJu0YxKEb4FGZls3Rb5U/E1HErIJKa8jvJErI/A7Lw0lcJX60Q6SYc8 NK+wsbpgJPJsBhVER5LWIMrQT/FU8i63WADP08/lnCB6aZvwEH13qTmGqJsV4k/qd2Sh/UmRDxb fdatFo139Tg== X-Google-Smtp-Source: AGHT+IGXYqyfCWDovUpzGHCdIi4JQlyFuhKGtfDl9eflGOP98Whn/jF2Ow/e927xtqJV6ktkfHLgmuz23u9R X-Received: from pjg5.prod.google.com ([2002:a17:90b:3f45:b0:325:220a:dd41]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:5543:b0:32b:c5a9:7be9 with SMTP id 98e67ed59e1d1-32bc5a97d11mr512876a91.25.1757020443710; Thu, 04 Sep 2025 14:14:03 -0700 (PDT) Date: Thu, 04 Sep 2025 21:13:52 +0000 In-Reply-To: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1757020440; l=7433; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=7Cs+rrmvUc6RnlQb7OHJuhyGOb5Nkjl9WcLbi0YymUk=; b=Dd23oQQlC44VStRaoqy4wyvrm8FleZTxTYojIARhabJoQXxVAn5nzGpEvDcSVIcXe0UuUUJCH uFDKJ4RTUEiDXgnjB81H6iai7w3aqfXxOxbEpNSL3CIBdcDW1yngcF+ X-Mailer: b4 0.14.2 Message-ID: <20250904-debugfs-rust-v11-1-7d12a165685a@google.com> Subject: [PATCH v11 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..65be71600b8eda83c0d313f3d20= 5d0713e8e9510 --- /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. +/// +/// The directory in the filesystem represented by [`Dir`] will be removed= when handle has been +/// dropped *and* all children have been removed. +// If we have a parent, we hold a reference to it in the `Entry`. This pre= vents the `dentry` +// we point to from being cleaned up if our parent `Dir`/`Entry` is droppe= d before us. +// +// 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.355.g5224444f11-goog From nobody Fri Oct 3 05:25:18 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 088EE2BE020 for ; Thu, 4 Sep 2025 21:14:05 +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=1757020448; cv=none; b=a2SUpHM5m9TFmRRNwotibbR+pTg8mrv0A9V1EoecPLXI/Y8WlKsi4U55YW7vUBX+r7VzrCtxR0CD3D7tS526iyCjGzfMz87ONMKVMgM5rEeS+T/mHz/5SMfB5UeRJxLsJFwAg8FDm8extB6GpTHhTIp1ClgR+XupFGToGVFayjc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757020448; c=relaxed/simple; bh=ZUV/l0tgO0/gu7P9InBfYSi6gELScLGP48YsINmM9FQ=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=D8LipFbbJ1hEK4RSTJ+KJpo6bniL79LHFFl1plKeT38MZhuHjGoQt+h54bJD1t7Bty2ResMfOoVXnem3nv9jF5UczOs9UBrN9z4w4PTx6Mek9gv7MtJxaZWJ3UJtUcR16sVnmX7UU+QBMFgX2/RK5Y/PmbwTK0H9l+jtfGnJN7E= 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=LoqXfirR; 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="LoqXfirR" Received: by mail-pl1-f201.google.com with SMTP id d9443c01a7336-2445805d386so15662055ad.1 for ; Thu, 04 Sep 2025 14:14:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1757020445; x=1757625245; 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=vgnQBYuXpsRnOVkDIO8osDJStgMZHHV4XY3qeYJSpRo=; b=LoqXfirRS29kYH3TMG6CmtoL9R/6Oh00NIMUh0zcEL3YY+zj/dXxnfVLcBs9pqUfr1 qgClULK1GM63JsXs3CJsdO47OmcD5kHpBLNe8IIAWjnHg0kaT6WKKkeVBPBqJhxiuJ3O j9sHKgaFcb6nxnsAod4CNYJrNJM3UPzdH/2j0yWGeRbwysm9yvSrrlFn8C4ey3wacPGn pXXA90zfvAOxmvZRteKp6SwT7x5RbgysOJ4WudsjwST3fppDFy4h/NcU7q3Sxf2WVqyE QuCKoC57zb/aMPx0j47xR8xqVy4HEskCsXO4F/vg9/NJPbYEK/wlK0yVcu35006zWTEA CfoA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757020445; x=1757625245; 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=vgnQBYuXpsRnOVkDIO8osDJStgMZHHV4XY3qeYJSpRo=; b=HpCRZa1r1JYg0a7Ji+d5xlyvbIQyyGvTjyMPplELL4Q12p5sMzHTa4+/LTnL9ymDm0 zyLVj+PHyThZ//xiwmK0qSBwvnHaeio1fyd+H/yx/OuACrDONNCbsqwPKCwOCv/cLizZ LWj8c1EcVuJFPsw67Yhmd7ivBWI9YxeUkWjxViwq6T8C9Za+xcB3qG8JDo+VlHx/ytzC g01bmEooDBs0t2cJoua+9tVRIzs4/e+r/irE6B7gh/uAtDmkhJiHeMFx/GTLJ2uHlvVx hczrP1Hs0mHnjXHz65D/MlH/OUM70ibTZIB3AZDh+ytKo+gTXZlel5TOK1JYbGzavVCs 03Kg== X-Gm-Message-State: AOJu0YwaSi8qvbnwFkwX6q9sHJTSFu9L+DJ7ZTYH76/fTrSR1F7YYUy2 cBtbL+aHOZ9/uC5kTh50n1uYM9H9Yi1xweSjIbK96mnh56tq2kMdhMEENIqob4jmoP3syKt2p+J leOsbjk/ecw== X-Google-Smtp-Source: AGHT+IFFaO6y7SvElrpim9HOj1PeiwgqfzSjimV/D+OHG9aT4gphvY8EeN+mfgEO92TlQI5hYdduCEqy4ahZ X-Received: from plbkv11.prod.google.com ([2002:a17:903:28cb:b0:24b:1148:97c5]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a17:902:db03:b0:24b:1692:e0cc with SMTP id d9443c01a7336-24b1692e68amr154451625ad.15.1757020445290; Thu, 04 Sep 2025 14:14:05 -0700 (PDT) Date: Thu, 04 Sep 2025 21:13:53 +0000 In-Reply-To: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1757020440; l=14493; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=ZUV/l0tgO0/gu7P9InBfYSi6gELScLGP48YsINmM9FQ=; b=Xk1DgkkNTmy2fGKP89cIBcCYC7piVsHDdE04eRRNSvtTc2UQ7WZQ5dryVy3x+rRsdl5bZX/b1 ZK1RiGDyBkCBSgD8SVdME2X3PKxwuMYW80GXkvQQkcu9nJvZ/Ch2ncs X-Mailer: b4 0.14.2 Message-ID: <20250904-debugfs-rust-v11-2-7d12a165685a@google.com> Subject: [PATCH v11 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 `Writer` trait. The file's content is generated by the `Writer` 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 | 148 ++++++++++++++++++++++++++++++++++++= +++- rust/kernel/debugfs/entry.rs | 42 ++++++++++++ rust/kernel/debugfs/file_ops.rs | 128 ++++++++++++++++++++++++++++++++++ rust/kernel/debugfs/traits.rs | 33 +++++++++ 4 files changed, 350 insertions(+), 1 deletion(-) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index 65be71600b8eda83c0d313f3d205d0713e8e9510..b28665f58cd6a17e188aef5e8c5= 39f6c7433a3b0 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::Writer; =20 +mod file_ops; +use file_ops::{FileOps, ReadFile}; #[cfg(CONFIG_DEBUG_FS)] mod entry; #[cfg(CONFIG_DEBUG_FS)] @@ -53,6 +59,34 @@ 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, E: 'a>( + &'a self, + name: &'a CStr, + data: impl PinInit + 'a, + file_ops: &'static FileOps, + ) -> impl PinInit, E> + 'a + where + T: Sync + 'static, + { + 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 +113,116 @@ 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 [`Writer::write`] 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, E: 'a>( + &'a self, + name: &'a CStr, + data: impl PinInit + 'a, + ) -> impl PinInit, E> + 'a + where + T: Writer + Send + Sync + 'static, + { + let file_ops =3D &>::FILE_OPS; + self.create_file(name, data, file_ops) + } +} + +#[pin_data] +/// Handle to a DebugFS scope, which ensures that attached `data` will out= live the provided +/// [`Entry`] without moving. +/// Currently, this is used to back [`File`] so that its `read` and/or `wr= ite` implementations +/// can assume that their backing data is still alive. +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<'b, T: 'b> Scope { + fn new(data: impl PinInit + 'b, init: F) -> impl PinIn= it + 'b + where + F: for<'a> FnOnce(&'a T) -> Entry + 'b, + { + try_pin_init! { + Self { + data <- data, + _pin: PhantomPinned + } ? E + } + .pin_chain(|scope| { + init(&scope.data); + Ok(()) + }) + } +} + +#[cfg(CONFIG_DEBUG_FS)] +impl<'b, T: 'b> 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(data: impl PinInit + 'b, init: F) -> impl PinIn= it + 'b + where + F: for<'a> FnOnce(&'a T) -> Entry + '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..c2fbef96580eaa2fab7cc8c1ba5= 59c3284d12e1b --- /dev/null +++ b/rust/kernel/debugfs/file_ops.rs @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2025 Google LLC. + +use super::Writer; +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 WriterAdapter(T); + +impl<'a, T: Writer> Display for WriterAdapter<&'a T> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + self.0.write(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 writer_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(writer_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 valid po= inter to a `T` which may +/// not have any unique references alias it during the call. +unsafe extern "C" fn writer_act( + seq: *mut bindings::seq_file, + _: *mut c_void, +) -> c_int { + // SAFETY: By caller precondition, this pointer is valid pointer to a = `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, "{}", WriterAdapter(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(writer_open::), + // SAFETY: `file_operations` supports zeroes in all fields. + ..unsafe { core::mem::zeroed() } + }; + // SAFETY: `operations` is all stock `seq_file` implementations ex= cept for `writer_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..0e6e461324de42a3d80b692264d= 50e78a48f561d --- /dev/null +++ b/rust/kernel/debugfs/traits.rs @@ -0,0 +1,33 @@ +// 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 written 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 writable type i= nside a `Mutex`. +/// +/// The derived implementation of `Debug` [may +/// change](https://doc.rust-lang.org/std/fmt/trait.Debug.html#stability) +/// between Rust versions, so if stability is key for your use case, pleas= e implement `Writer` +/// explicitly instead. +pub trait Writer { + /// Formats the value using the given formatter. + fn write(&self, f: &mut Formatter<'_>) -> fmt::Result; +} + +impl Writer for Mutex { + fn write(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.lock().write(f) + } +} + +impl Writer for T { + fn write(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "{self:?}") + } +} --=20 2.51.0.355.g5224444f11-goog From nobody Fri Oct 3 05:25:19 2025 Received: from mail-yb1-f201.google.com (mail-yb1-f201.google.com [209.85.219.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 6F9E62C027A for ; Thu, 4 Sep 2025 21:14:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757020450; cv=none; b=phArgg3hmZprMxLBIVFucezFYL0YfODcg+D9EKg1SqD5DuMzlVwodG84Q178DntY8AUnGJGCJwMQL82OTOhi59l+X031jJAufVIzNwxJ9/bykaP7944r40guAbehlNdIvJA48kUUshV/N6ua2lpxMBrIKtwHZ0nAN42qrSUb0ck= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757020450; c=relaxed/simple; bh=GezwA+wXVKp0DRIhqN2BHGB5ngH9Od3xM+ZXjB5cz8k=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=GRhJNEzGG5Q5YRiu67N6LtuUF0F8RK1ghuW9iiWGGuNvFctr31DNGSt2ysOFo1AYYuK4/C4xlvxCXMrg4ymrmTvi6RHzxJwerIDLtkicR/nOwDKIWRuq4hQUdNJK0GtzNtHHjUVqvaGUI7TM4UlbSxMW+LhKRVrCDakyze3+q6A= 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=WnQ7uTeL; arc=none smtp.client-ip=209.85.219.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="WnQ7uTeL" Received: by mail-yb1-f201.google.com with SMTP id 3f1490d57ef6-e98962b132bso2786354276.0 for ; Thu, 04 Sep 2025 14:14:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1757020447; x=1757625247; 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=Fyc9Sezqjjgx53ixJnCGx8glIsEj3Qx8OmK+mprgV5M=; b=WnQ7uTeLd6nygjbzf9qvIYLBd8OwPs427X59x4FKlCYuXgFvWoQijFlbOQgOPebpJ7 9yqS+hbO3AsiGXAzSp7Yo4X4+J3DUXn3d9Q0czOOD/DCSj9sX+LVhAaVqTnOC2Q2vMyk OEWP6ikiZ58dErSvk52peaX7G5gkVsRrEl4j2lYDQjZbhwONAxzpSZoEzP+MagfWd/Xf BgXejL7lD0TK3tACLBeQwYk4EeZK6S3wRRmArx1TH+56Rr5CBzSTNVcnJ+ULNzfVMnLO ddhyyLdlYC/TQkihOHBPyc201OBlya90OTlplNcbuPe9NYjZltrNIDoTfztcYt05Lozk XEQA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757020447; x=1757625247; 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=Fyc9Sezqjjgx53ixJnCGx8glIsEj3Qx8OmK+mprgV5M=; b=ZqRI935V1sWvilL96K+cUYfIkLf9XOiWiC2SWYz6NzLGzSgv7AN1sfoZFjYTFeZKPI 36Xf8kTJbemZOyFTfBfRln9ukmyyGyEdZw+dKPaIfULJauZK+wI3gMVgV1fXqhh8g/2q nqM0h2oFf5bopGSqZUWTcmcDL2G6SVQzBam4ZAeW4X5M7FaoymubkHplJfZHrhSHCFAN 8hv1OEBF10cNMbZJGb1Js5E0wyOvf76HROcmV0zOtqrT4IAxlBzrmQEof7UuVYWKW+gO OKu0qRUr7zg/D3UAC6m5G5gfZoh4+pPSigeDtuMpU46HNPJfNsshmHEDLBryOUhFrbbE JiOA== X-Gm-Message-State: AOJu0YzEQAeCz+2ZF3TJc5tBVwHembRjqd9rkhAgKtUfiLZAzMTPKH3l GCL2bkeE95yN1kjOpE2sRu+HZ000mxtJEY2eqxUFvV66DQbkQbIGW/N5L9drL+xS1pG62aABcQL j2OZ86f0ytw== X-Google-Smtp-Source: AGHT+IHtJUU+BwbDx4oO0LPbQFnhU87b9Fk4a0b0A/+npzA9WhgEO7z8wfilcXuJx8DFXvvID3ZDVJfqDlnn X-Received: from ybmm5.prod.google.com ([2002:a25:2605:0:b0:e93:d518:e700]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6902:2b01:b0:e9b:c256:af4b with SMTP id 3f1490d57ef6-e9bc256b3e4mr10808881276.29.1757020447010; Thu, 04 Sep 2025 14:14:07 -0700 (PDT) Date: Thu, 04 Sep 2025 21:13:54 +0000 In-Reply-To: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1757020440; l=10526; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=GezwA+wXVKp0DRIhqN2BHGB5ngH9Od3xM+ZXjB5cz8k=; b=NsN9PoG5PU/07PDksIzzAFi+2OAYpKBmAOjenprrduVLB8G+uCSyg3NnR7F4PAI4tkqTTyaHf e3hIoEts4h2Asy/lazghdBZ8Z8GwGraJuYlseidPEFuuAfnTFAgkff7 X-Mailer: b4 0.14.2 Message-ID: <20250904-debugfs-rust-v11-3-7d12a165685a@google.com> Subject: [PATCH v11 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 `Reader` trait. Signed-off-by: Matthew Maurer Tested-by: Dirk Behme --- rust/kernel/debugfs.rs | 37 ++++++++++++- rust/kernel/debugfs/file_ops.rs | 113 ++++++++++++++++++++++++++++++++++++= +++- rust/kernel/debugfs/traits.rs | 69 ++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 3 deletions(-) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index b28665f58cd6a17e188aef5e8c539f6c7433a3b0..1f041f09a6eaf5603170ce4c772= 4b3ef50eecf13 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::Writer; +pub use traits::{Reader, Writer}; =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)] @@ -142,6 +142,39 @@ pub fn read_only_file<'a, T, E: 'a>( 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 [`Writer`] implementation. + /// Writing to the file uses the [`Reader`] implementation. + pub fn read_write_file<'a, T, E: 'a>( + &'a self, + name: &'a CStr, + data: impl PinInit + 'a, + ) -> impl PinInit, E> + 'a + where + T: Writer + Reader + Send + Sync + 'static, + { + 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 [`Rea= der`] + /// implementation. + /// + /// The file is removed when the returned [`File`] is dropped. + pub fn write_only_file<'a, T, E: 'a>( + &'a self, + name: &'a CStr, + data: impl PinInit + 'a, + ) -> impl PinInit, E> + 'a + where + T: Reader + Send + Sync + 'static, + { + 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 c2fbef96580eaa2fab7cc8c1ba559c3284d12e1b..2060c8d14d83455efa6ec179669= f2c3fcc35ccaf 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::Writer; +use super::{Reader, Writer}; 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 @@ -126,3 +127,113 @@ impl ReadFile for T { unsafe { FileOps::new(operations, 0o400) } }; } + +fn read(data: &T, buf: *const c_char, count: usize) -> i= size { + let mut reader =3D UserSlice::new(UserPtr::from_ptr(buf as *mut c_void= ), count).reader(); + + if let Err(e) =3D data.read_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 `Reader`. +/// `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) }; + read(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(writer_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 `writer_open` + // and `write`. + // `writer_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 = `writer_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 +/// `Reader`. +/// * `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) }; + read(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 0e6e461324de42a3d80b692264d50e78a48f561d..3d99482e53a395aef81bb045d3e= ffe827f5f4386 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 written into a string. /// @@ -31,3 +38,65 @@ fn write(&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 Reader { + /// Updates the value from the given user slice. + fn read_from_slice(&self, reader: &mut UserSliceReader) -> Result<()>; +} + +impl Reader for Mutex { + fn read_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_reader_for_atomic { + ($(($atomic_type:ty, $int_type:ty)),*) =3D> { + $( + impl Reader for $atomic_type { + fn read_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_reader_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.355.g5224444f11-goog From nobody Fri Oct 3 05:25:19 2025 Received: from mail-pg1-f201.google.com (mail-pg1-f201.google.com [209.85.215.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 E14532BF3F3 for ; Thu, 4 Sep 2025 21:14:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757020451; cv=none; b=opzxEQtYsEnqte/fGUpF9vgKdzGBoP4TwIQJ/ry1bEashvMFscTxLfiOkx5jN6doDom8fqt6JzOHff6TuXTz8CRQZiY4ZdgQ3PJxdBHHE3txv3wDAKDwUcJvniHaK8nx8YZpd3APt6OVBkccCnr7X7jlEQP8onB3gCsV6pqgmvk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757020451; c=relaxed/simple; bh=VBQJoE+Lm8CzDWuYMiCor0vZ902eURER25jDonvcF78=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=lXT2qlIGLzl78LjpahfRdv/gFAr8pvDnJEwzRR63hbH3M0ftvcrQGMoNc53hbXmA6ua2DKM+j7+ms2Vzo4CSL57c/5NHcdNEr9JtDgwYnk3JF9YnAQEsjFzamNx5u3lbYCqXuFsBtYJGGMrsrRwcxREy9mYlt8MqriJmfMWrckU= 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=aijcNr77; arc=none smtp.client-ip=209.85.215.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="aijcNr77" Received: by mail-pg1-f201.google.com with SMTP id 41be03b00d2f7-b4d48818a04so1021734a12.1 for ; Thu, 04 Sep 2025 14:14:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1757020449; x=1757625249; 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=PEBpj/QWOHMN6zKC2IFSIRLeTaHgu8MbKbl5WICV5ps=; b=aijcNr77trrDx3Sf/nDDkIXnKkwFvdrpUQnbgtcw5cvFEcYHMv/evriYZ546SdIqwc 18875I272yFTF6+WCa18QpipAL88VwlR5PAnvnxEcK5lOrJUJ490oCEdbTW+Qvjbh9iy 66NoGMMOkbzem9XFNlcSA8Hjv/o97OkiB5+Ff6MUDcBU0PBrl90mDbjhgz4+Pd/4ExAw peJ4baxpZ0spEvEOUc6F1bCj3nApHMQ6FohudgmRbDaC9rxKAqhyUEw9lvCwqdtiX3xs kxNUIxV/Xox8eU6GUWSodfqPtYkbvitlEoZsrWRaOAnO8xLYsDEDcpGyPA8yPcscHJEH E4AQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757020449; x=1757625249; 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=PEBpj/QWOHMN6zKC2IFSIRLeTaHgu8MbKbl5WICV5ps=; b=JH4sLf4VonmhscEsfi7d46W1ahrfb+z1Mealp6QBsw0RiFg8CDFj602mox2eSy8o8D f/2Xc7+OQZeoI4Nh6nPi6Gr+iSYZo8/VW+hMSRrMCar4eimN8445xAcOsAbMJafcqgyC rEyFvQQ79EW8un5zd+PBlJ4oRqsSOY2GN14H6aWGJ5CiFDUkaken7wQ5j5MLcyieGdKy VX4Z++wKOWxXN8VKQo6EpeVpJ4Q5nKxA714AmXz1q7cb4My+n8sK1Vmx6vaXR9QJgseW DFL0mghtIhdn2Ome2ScGgOxLlEv3D5ZnvgL4IiGvLJthwvdFHVJHtOv+4xDLU0wkp3NG OL8A== X-Gm-Message-State: AOJu0YxJGrhuWwdfJCrrivxGw8g8C+aXVxsEypdJVu+jUW19hKhAJeAI 3GnoQfRnQLG6zzAPqNEdiCkieLla/qdsX5dxPfEu/8kt7bxGdi0IyThfhx+vYMXvX4xhZBoSoCo oCnSunQ7Ubg== X-Google-Smtp-Source: AGHT+IGbS/wyh1uE8LybvJ86AyH49A6f/q1fbYysOfAJ3VDRse5YpA4slFp/VCzkU7qFmAIG4oezk/FPqPjQ X-Received: from plez7.prod.google.com ([2002:a17:902:ccc7:b0:24c:829d:8a31]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a17:903:2f92:b0:24c:784c:4a90 with SMTP id d9443c01a7336-24c784c4f25mr135221085ad.1.1757020449145; Thu, 04 Sep 2025 14:14:09 -0700 (PDT) Date: Thu, 04 Sep 2025 21:13:55 +0000 In-Reply-To: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1757020440; l=10441; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=VBQJoE+Lm8CzDWuYMiCor0vZ902eURER25jDonvcF78=; b=4yvOnY7aq++9sixiTSlDJ66p5sgpKg8M1iQFPoYkoUEbBai4xRYv8lCE4vAMamqvsom0z2Ezm 5/+XYC1AZBdALNM4ZXKIEDlwJi2mJnmqZkkOqHMfal0PUxiTjGlMhtD X-Mailer: b4 0.14.2 Message-ID: <20250904-debugfs-rust-v11-4-7d12a165685a@google.com> Subject: [PATCH v11 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 `Writer` or `Reader` 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 | 89 ++++++++++++++++++++++ rust/kernel/debugfs/callback_adapters.rs | 122 +++++++++++++++++++++++++++= ++++ rust/kernel/debugfs/file_ops.rs | 8 ++ 3 files changed, 219 insertions(+) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index 1f041f09a6eaf5603170ce4c7724b3ef50eecf13..1032f279da380c549991e903c41= 62b7e4aaec571 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::{Reader, Writer}; =20 +mod callback_adapters; +use callback_adapters::{FormatAdapter, NoWriter, WritableAdapter}; mod file_ops; use file_ops::{FileOps, ReadFile, ReadWriteFile, WriteFile}; #[cfg(CONFIG_DEBUG_FS)] @@ -143,6 +147,46 @@ pub fn read_only_file<'a, T, E: 'a>( 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, E: 'a, F>( + &'a self, + name: &'a CStr, + data: impl PinInit + 'a, + _f: &'static F, + ) -> impl PinInit, E> + 'a + where + T: Send + Sync + 'static, + F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync, + { + 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 [`Writer`] implementation. @@ -159,6 +203,31 @@ pub fn read_write_file<'a, T, E: 'a>( 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, E: 'a, F, W>( + &'a self, + name: &'a CStr, + data: impl PinInit + 'a, + _f: &'static F, + _w: &'static W, + ) -> impl PinInit, E> + 'a + where + T: Send + Sync + 'static, + F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync, + W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync, + { + 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 [`Rea= der`] @@ -175,6 +244,26 @@ pub fn write_only_file<'a, T, 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, E: 'a, W>( + &'a self, + name: &'a CStr, + data: impl PinInit + 'a, + _w: &'static W, + ) -> impl PinInit, E> + 'a + where + T: Send + Sync + 'static, + W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync, + { + 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..6c024230f676d55c8ddacb69de9= c27587e29c636 --- /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 write or read implementation= as a value rather +//! than a trait implementation. If provided, it will override the trait i= mplementation. + +use super::{Reader, Writer}; +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 `Reader` via a callback with the same representat= ion as `T`. +/// +/// * Layer it on top of `WriterAdapter` if you want to add a custom callb= ack for `write`. +/// * Layer it on top of `NoWriter` 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 Writer for WritableAdapter { + fn write(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.write(fmt) + } +} + +impl Reader for WritableAdapter +where + W: Fn(&D::Target, &mut UserSliceReader) -> Result + Send + Sync + 'sta= tic, +{ + fn read_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 `Writer` 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 Writer for FormatAdapter +where + F: Fn(&D, &mut Formatter<'_>) -> fmt::Result + 'static, +{ + fn write(&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 NoWriter { + inner: D, +} + +// SAFETY: Stripping off the adapter only removes constraints +unsafe impl Adapter for NoWriter { + type Inner =3D D; +} + +impl Deref for NoWriter { + 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 2060c8d14d83455efa6ec179669f2c3fcc35ccaf..50fead17b6f31feaf1caaef31c2= 4ccdf6d8a5835 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::{Reader, Writer}; +use crate::debugfs::callback_adapters::Adapter; use crate::prelude::*; use crate::seq_file::SeqFile; use crate::seq_print; @@ -46,6 +47,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.355.g5224444f11-goog From nobody Fri Oct 3 05:25:19 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 82F3829BD92 for ; Thu, 4 Sep 2025 21:14:11 +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=1757020453; cv=none; b=NAfowhj5hhG5z6zILT1YfulFNTVOvd9KW0NXYZKg8ITElP/kOp958bGocuoKtQDrwPJBbSgDLMFNfnssVpmzCUE30izH+KQLJwq07vJ5hxrAWcDwxoi8uTx79yTEE6PclBMkPmHD25kyzFfJP6Ov9nHPNOoSnjmK8jIcYf2zwfA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757020453; c=relaxed/simple; bh=IX+FmUJAsAt3bbv6WP+sppw+TzVK8DRPJvw00HnhzEY=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=LSN5de816wm3Th/sx2vc8eT3XDYBnjSnHxCcIqA7HHcGMN1QjC0PVmPuyu0kkj6GN7PN/lhEmApGJ2k8r6xmdTxtSrLK3g+BKJLDbXr2wPQCQgfrE9gMjTyTjQY5xn9oUqZcynQOHJDIltxI6zNSxH7AbVw8KgSt4UTINX5XToE= 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=zoV5pZBM; 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="zoV5pZBM" Received: by mail-pj1-f74.google.com with SMTP id 98e67ed59e1d1-3234811cab3so1699766a91.3 for ; Thu, 04 Sep 2025 14:14:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1757020451; x=1757625251; 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=arN2pmQBVtYOvUg5waEtOueGktesFqbTHbSY6edCK8Y=; b=zoV5pZBMjfEmoWMvwse4R8qkrAJrkPvbbmhaIxm845Ljhfed9OzicE8/JKDVdlqm9A y0eFhqMpwGr8AzBDwGgvTvEr015ZxlLv4edOZa4ze6ue8d/EsxYehOSjP9YncSfdecux ssimJOs979aStSmF0nUvwz3jhwPgbrr/a5Lrzjsv6JshHEZPFnft+H7XqEEJ1A51yfWR AfQgmH0GVg18mczS3a8TURLFGES/+TOHGpXMvPl5qHV/Huz9Q0r9+eKS0qP7KiYHHvfL s3F26BGSMLDLyxm7Kxn3HrCFJCRiKflBVxp+td3aKX1tfjCB6LbnvfuluNimNPbKYy0d tEow== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757020451; x=1757625251; 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=arN2pmQBVtYOvUg5waEtOueGktesFqbTHbSY6edCK8Y=; b=th3nSNFecX9WIZQHIfKFNHXIPRlujq1AQexm8R197FyOOJxFHe1deFYlaSLAeWbQ7w 5fxGZ11V/MjBCLdKhIlGqZdcqfMxkPbf9DDzisEPq8fXiwFNRDKuug6avHugSIP8ZhwJ RnrWBWNAm/q92wlrNnw3zb4PT5wx41vret9C5XdwmHtso3oZ2/ktyyWqIrgJ7sDGi4E3 ghUMjtK4zuSDvciJaBDOko/3SKMbkiQWeC3bK+6p05LXnqL5Z1zgUAT8503F1hAir64v 3QrZmw59h2KQdbMFa6a+g53QQ+qkvgUOQN4+s1KNgo+2ilBwvz1HYHUW3SJV5zf798jk N+gw== X-Gm-Message-State: AOJu0YyWQfGqI0JVZiw87Xtdhxly23SOzHBaotBPcu3aXoX16sxYQnxg Sf7VwO1IeOaLxoeoL2nnqkO2j5V0CXO+G67uIe301RxpRcAdHJ1Zx3s/5+ti1TIC2VL0bIC+FLz KJ9KbZbm6gQ== X-Google-Smtp-Source: AGHT+IGAgAM+cMeURLjfyTSd3DY3sRO4QhH61uz52v0RlipIbIDIiNT3/8p2I/kKs4JfuGm3wPmrDUpj6CNx X-Received: from pjbsl12.prod.google.com ([2002:a17:90b:2e0c:b0:32b:61c4:e48b]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:1b0d:b0:32b:bc2c:f9fd with SMTP id 98e67ed59e1d1-32bbc2cfd32mr1255448a91.24.1757020450800; Thu, 04 Sep 2025 14:14:10 -0700 (PDT) Date: Thu, 04 Sep 2025 21:13:56 +0000 In-Reply-To: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1757020440; l=7445; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=IX+FmUJAsAt3bbv6WP+sppw+TzVK8DRPJvw00HnhzEY=; b=WIjuHal8JPzrlTATTMgG7QCC701Z6YtD5hkSb1UaUNhCrvBGdxIBURre06/pLD+tg6sy0M+OT p082DNgLNfLC2S0n0yJ6Gf/6LQ2C7vCpyJAysUK+AM6n08SlbAij80Y X-Mailer: b4 0.14.2 Message-ID: <20250904-debugfs-rust-v11-5-7d12a165685a@google.com> Subject: [PATCH v11 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.355.g5224444f11-goog From nobody Fri Oct 3 05:25:19 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 51B5E2D0C8F for ; Thu, 4 Sep 2025 21:14:13 +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=1757020456; cv=none; b=qdIYzFA4aGvLXPaA82xBFn7zXgcBIvVv/ZxHHC5oPm8Br7B/ABIk4/d8pXOQsTN9waDPlB00WFl2t1i/VTSwZB6SZ7t1Izrx+k+2zBJskVrSDgaKtupx4Xk3/UpJged3tTmBKMicd8Cb4B2BY/ROegIg5PxNz8tpGme5AHfz7YY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757020456; c=relaxed/simple; bh=Rp2/78z6wWbP31ZJ/pcLvnZHB8e6n7jyKbwJrkKBH+E=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=X+tgGdcKcS59IqKn1Qka+bmKtflkxN5jZ/ppKnQD3fADRViObXPd/d1XRfX528l5ZIJPaBLRmtrF7Uq+8OvNtpZpTchetKgI3noUkx/pYYrIPiAQfYefJ6DDvcPQFcj8menD8pDvL1UN7N/famk5ZbRbQ2c6mAVs2ktv7HXRDCY= 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=OFe5aPo8; 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="OFe5aPo8" Received: by mail-pj1-f73.google.com with SMTP id 98e67ed59e1d1-329745d6960so1353914a91.0 for ; Thu, 04 Sep 2025 14:14:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1757020453; x=1757625253; 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=YK7rIEOJLTE2g+CUqPb3mblCrnFm8qMnQL5EsmM3boQ=; b=OFe5aPo8fIkg0jNMAzXA7VTAW2XPHotX3++QI9fGeJwrEUxC2dHdryloU16P6F60Xr YsxXtLW7YrJbm9ZU4X+Nls4OR84iXM51ajwrMMLUeAKlIbBUHTZSlnDSZ0mfjiIAdbm6 LpLGV4rI8Pb/FE4BHlrh6DMSDWKxNUa7TxqyQvS0fbxLplxC0bXSg/j0weEA9k7bEOAC MA6AIVU992qtlyiQtioE3GGy0uzHPz4KIIiwKNdu8/6C/HKINqE4smYiBwoiMNP6WzZ6 A56lUeXaLoHAzmxxi+karTQOjk0YKsBmvI4G2eAf3wD1h+EDosJg83OhVZffXt0z6VWN FTYg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757020453; x=1757625253; 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=YK7rIEOJLTE2g+CUqPb3mblCrnFm8qMnQL5EsmM3boQ=; b=IKZmwXKc0D9vioLiD0HOIz5Lh6fMN2t6lRNprmeaQAM2JcWoALPqGVpXO0noIJinSL R42yCevjdtOY7mXh8Ws+liC5iM1biv1ckazNwqFaSTCuqGHYhd134+qn2lV1iP/pl5q4 D2jwV9uspCyXFwbJowd0vo4M7WaPGEiqWdeTo2Y0A6G7SfhzsxzZZlmKTU0XpRRQOStF RHDJjECRV3lwW8wckwfJFaXbW4CfCN1CSUViJNeCl/XAkpZ/Utxr0lUcr+U8slUrPj0C i2naTPIUMefCJE7CSvdhekNxasOSTWZW11sJNkhV38X4FpzR3gcuiMJdSr/8N5NrVzOC CQ2g== X-Gm-Message-State: AOJu0YzI46q4f3hZYnwft6pfmiwybDMM3g+FgdiuqNDYCGuONigxDs91 c/iIans6HyTnzadxdiNTe9dN0oU96IrqodfS3SGiHOJ2WKGwYawWU//9Y2DwZtZ/UadbIMcTo0i qIdDY/k3yzg== X-Google-Smtp-Source: AGHT+IFJTm4WLkU5TNw6hUUMIiPxQOgn8IqE+zNoMTL78lMxGMTKL6T/kxc0f1x7J5hLBLOXp85luo8ZS4OL X-Received: from pjg4.prod.google.com ([2002:a17:90b:3f44:b0:327:e021:e61d]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:3d87:b0:32b:65e6:ec39 with SMTP id 98e67ed59e1d1-32b65e6f260mr8059481a91.21.1757020452465; Thu, 04 Sep 2025 14:14:12 -0700 (PDT) Date: Thu, 04 Sep 2025 21:13:57 +0000 In-Reply-To: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1757020440; l=17878; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=Rp2/78z6wWbP31ZJ/pcLvnZHB8e6n7jyKbwJrkKBH+E=; b=Nq4SlhLFTdjYz2h4iLTTJKY1OPZ55f4+Z2oIEzk1tmeeC5x28imOgvctz4AGeTmjRVjCfiy4g xlhPIGaIPfOArOS9CHuVLyypNsZTH9t8Ak9jNldkLVqR5YUvjbSKWDW X-Mailer: b4 0.14.2 Message-ID: <20250904-debugfs-rust-v11-6-7d12a165685a@google.com> Subject: [PATCH v11 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 | 262 +++++++++++++++++++++++++++++++++++++++= ++-- rust/kernel/debugfs/entry.rs | 73 +++++++++++- 2 files changed, 320 insertions(+), 15 deletions(-) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index 1032f279da380c549991e903c4162b7e4aaec571..ecfcce845d3f1e9183a55e16629= 564776f80c6f0 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. @@ -264,17 +267,67 @@ pub fn write_callback_file<'a, T, E: 'a, W>( .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, F>( + &'a self, + data: impl PinInit + 'a, + name: &'a CStr, + init: F, + ) -> impl PinInit, E> + 'a + where + F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>)= + 'a, + { + Scope::new(data, |data| { + let scoped =3D self.scoped_dir(name); + init(data, &scoped); + scoped.into_entry() + }) + } } =20 #[pin_data] -/// Handle to a DebugFS scope, which ensures that attached `data` will out= live the provided -/// [`Entry`] without moving. -/// Currently, this is used to back [`File`] so that its `read` and/or `wr= ite` implementations -/// can assume that their backing data is still alive. -struct Scope { +/// Handle to a DebugFS scope, which ensures that attached `data` will out= live the DebugFS entry +/// without moving. +/// +/// This is internally used to back [`File`], and used in the API to repre= sent the attachment +/// of a directory lifetime to a data structure which may be jointly acces= sed by a number of +/// different files. +/// +/// When dropped, a `Scope` will remove all directories and files in the f= ilesystem backed by the +/// attached data structure prior to releasing the attached data. +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. @@ -312,14 +365,14 @@ fn new(data: impl PinInit + 'b, init:= F) -> impl PinInit 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 } } =20 fn new(data: impl PinInit + 'b, init: F) -> impl PinIn= it + 'b where - F: for<'a> FnOnce(&'a T) -> Entry + 'b, + F: for<'a> FnOnce(&'a T) -> Entry<'static> + 'b, { try_pin_init! { Self { @@ -335,6 +388,31 @@ fn new(data: impl PinInit + 'b, init: = F) -> impl PinInit 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( + data: impl PinInit + 'a, + name: &'a CStr, + init: F, + ) -> impl PinInit + 'a + where + F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>)= + '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 { @@ -348,3 +426,169 @@ 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 [`Writer::write`]`. + /// + /// 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(&self, name: &CStr, data: &'data T, _f= : &'static F) + where + T: Send + Sync + 'static, + F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync, + { + 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 [`Writer`] implementation on `data`. Wri= ting to the file uses + /// the [`Reader`] 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( + &self, + name: &CStr, + data: &'data T, + _f: &'static F, + _w: &'static W, + ) where + T: Send + Sync + 'static, + F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync, + W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync, + { + 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 [`Reader`] 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(&self, name: &CStr, data: &'data= T, _w: &'static W) + where + T: Send + Sync + 'static, + W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync, + { + 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.355.g5224444f11-goog From nobody Fri Oct 3 05:25:19 2025 Received: from mail-pf1-f201.google.com (mail-pf1-f201.google.com [209.85.210.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 8B8112FC88C for ; Thu, 4 Sep 2025 21:14:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757020459; cv=none; b=B+j4DsQLU3YUvUtws4EUSn66pwUHJkgr5qVxkVHvm2MC9UUshHT93U/yHfNJeVqzggd/mXDnsVw3/R+wPiJ2ZPbjieUZ+kBObpJVYqpMXNlZQvXvR0jb5Ionpp/QpvTUvE8oJkP0mZUeTO1cpe0wwhLtK+er7huSx6vLd97ZXcQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757020459; c=relaxed/simple; bh=ZOByofkirTEhHxljqlcJt3YijCsj4603lkXVxLBHevc=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=Fpw/cLDprVU9pTciiUlKEqwtbkxCO6eEkZLPXYTma+Ae6U/lAeXOCddY7Qz/AnsxF/YZXnOVYipeqibfaqtPmCtvUTBb4KixvMp/JZ8wQm6Dmstg7QF3CVCzoeUvNWwgSx3I++90H6fRzsrrKHkI8qGDA7xoY4GDWpDsoeBv1S4= 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=BL7ohsv+; arc=none smtp.client-ip=209.85.210.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="BL7ohsv+" Received: by mail-pf1-f201.google.com with SMTP id d2e1a72fcca58-77260707951so2124071b3a.0 for ; Thu, 04 Sep 2025 14:14:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1757020455; x=1757625255; 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=MuVbHTcTn1dGtaw/xfjGHfOlmUWLY+u0Yv+dfyfZ2z0=; b=BL7ohsv+0QJsYKvY/8WoZ/6ufGM0HjlHpe2JUftH6WiV5zaHS7f9jn184BlKilDPsA GC4yvBywrHw+E0r5uaT86gkuMZWA3e0gTWaDm8hm8woGm9DU8/AMRsR903diXYQnTuZW Z2Lsl99uyRWRkLpH6g2mxyCxhFCVpjJWRIYi5QDIic0C5XtfogGh2L0/md4lcNry8Wkp yiC3CmCa9u3m3YHeoPg4GY+6FH/y6tbBBcXj55MecLOtb+R/ZalWjIeWT4kkEYHVfBp5 1eeiBOrhFhpx7xhnPotnnr1/P7r088rdBzSUZL/qQK77P0THBHWYdO8UQFie5OoArbnu Bgiw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757020455; x=1757625255; 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=MuVbHTcTn1dGtaw/xfjGHfOlmUWLY+u0Yv+dfyfZ2z0=; b=nRdszmsNkHgXUv+UU32gvmaZX9nDTjMVfcb/xNbAyAgiKtW3/3YqcgZKyAi5ac7iE+ i3R7xXkZCpYhrXSVMVOLV0DXgk8wk+gYz4OM9p1dmBKLgyQbcWrFSk2OzCqaucyVgvn5 TWE8NKMLpvc3ibSmvAuiRKr7eZOCKQFtQ8qCPjipTVMIULxb9odAZy/STmXSVauzCyQs pKcHnX5rUU4eyv/qW8VpsG4qoI2FBDcP2uZQWsNjqiaUii9mZC9OILFXy7E4zy4JIsoK D4jpwZLF74eYFMF53P6MUl5ry6C4617kpLZp4lSfDbUPlyNDI6W7+VB789aOoF+6O+aL MQPA== X-Gm-Message-State: AOJu0Yz7O9Hr2NcepgqFR6BOS6q8JWlI2XmXrBs8X9iWKsSPRW8LpUS5 Ts4SXZkEHv7NhMfDAicusjcw+xZEp30zvEUUrrzrqaiT84XJZaMRxJ7l+qcdrqPGX3ZYV770RTw +Q4jQ3583JQ== X-Google-Smtp-Source: AGHT+IE8miOQ1cTcO0C+Ywnb/WSRBEUZzd7DMsAk3P0BP9MWStesJpoYvtzfZYCTN4qKw3/Cfc/6aI10NvFj X-Received: from pfbjo21.prod.google.com ([2002:a05:6a00:9095:b0:771:83fa:dfac]) (user=mmaurer job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6300:218a:b0:243:b20d:5e05 with SMTP id adf61e73a8af0-243d6f051e5mr28631242637.39.1757020455228; Thu, 04 Sep 2025 14:14:15 -0700 (PDT) Date: Thu, 04 Sep 2025 21:13:58 +0000 In-Reply-To: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250904-debugfs-rust-v11-0-7d12a165685a@google.com> X-Developer-Key: i=mmaurer@google.com; a=ed25519; pk=2Ezhl7+fEjTOMVFpplDeak2AdQ8cjJieLRVJdNzrW+E= X-Developer-Signature: v=1; a=ed25519-sha256; t=1757020440; l=7155; i=mmaurer@google.com; s=20250429; h=from:subject:message-id; bh=ZOByofkirTEhHxljqlcJt3YijCsj4603lkXVxLBHevc=; b=z+SsK8AErLja4NFbvWC9hBFsO/jociQidr7OHZFQUBBZxVNnfNJfH3+U1dQALNPMVtQF/RxOC ABPHJUeKBRkBVRtNo40OpsQ44BZRYj8V0p1k7vJcJ/y5gvyxSfqOGJr X-Mailer: b4 0.14.2 Message-ID: <20250904-debugfs-rust-v11-7-7d12a165685a@google.com> Subject: [PATCH v11 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.355.g5224444f11-goog