Intel-GFX Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Kunal Joshi <kunal1.joshi@intel.com>
To: intel-gfx@lists.freedesktop.org, intel-xe@lists.freedesktop.org
Cc: imre.deak@intel.com, jani.nikula@intel.com,
	Kunal Joshi <kunal1.joshi@intel.com>
Subject: [RFC 1/7] drm/display/dp_tunnel: Add debugfs interface with info file
Date: Mon, 11 May 2026 11:10:22 +0530	[thread overview]
Message-ID: <20260511054028.1310995-2-kunal1.joshi@intel.com> (raw)
In-Reply-To: <20260511054028.1310995-1-kunal1.joshi@intel.com>

The DP tunnel core has no way for userspace or IGT to observe
per-tunnel state (estimated BW, allocated BW, DPRX caps, BWA
enabled) without parsing dmesg. Add drm_dp_tunnel_debugfs_add()
and drm_dp_tunnel_debugfs_remove() helpers and an 'info' file
under each tunnel's debugfs subdir so this state is inspectable
through a stable ABI.

Per-tunnel debugfs subdirs are tracked in a list keyed by the
parent dentry so the same tunnel can be exposed under multiple
connector debugfs roots (e.g. an MST trunk and its children).
Teardown is two-phase under mgr->debugfs_lock to close the race
window where a concurrent add could observe a partially torn-down
tunnel.

Cc: Imre Deak <imre.deak@intel.com>
Assisted-by: Copilot:claude-sonnet-4-6
Signed-off-by: Kunal Joshi <kunal1.joshi@intel.com>
---
 drivers/gpu/drm/display/drm_dp_tunnel.c | 239 ++++++++++++++++++++++++
 include/drm/display/drm_dp_tunnel.h     |  17 ++
 2 files changed, 256 insertions(+)

diff --git a/drivers/gpu/drm/display/drm_dp_tunnel.c b/drivers/gpu/drm/display/drm_dp_tunnel.c
index 76c6bc84a806f..79d35d0be6b75 100644
--- a/drivers/gpu/drm/display/drm_dp_tunnel.c
+++ b/drivers/gpu/drm/display/drm_dp_tunnel.c
@@ -3,6 +3,7 @@
  * Copyright © 2023 Intel Corporation
  */
 
+#include <linux/debugfs.h>
 #include <linux/export.h>
 #include <linux/ref_tracker.h>
 #include <linux/types.h>
@@ -150,7 +151,19 @@ struct drm_dp_tunnel {
 	bool has_io_error:1;
 	bool destroyed:1;
 	bool pr_optimization_support:1;
+
+#ifdef CONFIG_DEBUG_FS
+	struct list_head debugfs_dirs;
+#endif
+};
+
+#ifdef CONFIG_DEBUG_FS
+struct drm_dp_tunnel_debugfs_dir {
+	struct list_head link;
+	struct dentry *parent;
+	struct dentry *dentry;
 };
+#endif
 
 struct drm_dp_tunnel_group_state;
 
@@ -193,6 +206,20 @@ struct drm_dp_tunnel_mgr {
 	struct drm_dp_tunnel_group *groups;
 	wait_queue_head_t bw_req_queue;
 
+#ifdef CONFIG_DEBUG_FS
+	/*
+	 * Serializes per-tunnel debugfs_dirs list mutations and the
+	 * debugfs writers (bw_alloc_enable, bw_limit). The lock is
+	 * also taken when flipping tunnel->destroyed in
+	 * drm_dp_tunnel_destroy() so debugfs writers and
+	 * drm_dp_tunnel_debugfs_add() can observe the teardown and
+	 * bail out. It does NOT synchronize against driver-side
+	 * modeset paths; the debug knobs are intended for test /
+	 * validation use only.
+	 */
+	struct mutex debugfs_lock;
+#endif
+
 #ifdef CONFIG_DRM_DISPLAY_DP_TUNNEL_STATE_DEBUG
 	struct ref_tracker_dir ref_tracker;
 #endif
@@ -482,6 +509,9 @@ create_tunnel(struct drm_dp_tunnel_mgr *mgr,
 		return NULL;
 
 	INIT_LIST_HEAD(&tunnel->node);
+#ifdef CONFIG_DEBUG_FS
+	INIT_LIST_HEAD(&tunnel->debugfs_dirs);
+#endif
 
 	kref_init(&tunnel->kref);
 
@@ -820,7 +850,36 @@ int drm_dp_tunnel_destroy(struct drm_dp_tunnel *tunnel)
 
 	tun_dbg(tunnel, "destroying\n");
 
+#ifdef CONFIG_DEBUG_FS
+	{
+		struct drm_dp_tunnel_debugfs_dir *d, *tmp;
+		LIST_HEAD(debugfs_dirs);
+
+		/*
+		 * Mark the tunnel destroyed and detach the tracked
+		 * debugfs entries under debugfs_lock so concurrent
+		 * drm_dp_tunnel_debugfs_add() / writer paths observe
+		 * teardown and bail out before touching this tunnel.
+		 * Do the actual debugfs removal after dropping
+		 * debugfs_lock: removal may synchronize against active
+		 * debugfs file operations, and those writers also take
+		 * debugfs_lock.
+		 */
+		mutex_lock(&tunnel->group->mgr->debugfs_lock);
+		tunnel->destroyed = true;
+		list_splice_init(&tunnel->debugfs_dirs, &debugfs_dirs);
+		mutex_unlock(&tunnel->group->mgr->debugfs_lock);
+
+		list_for_each_entry_safe(d, tmp, &debugfs_dirs, link) {
+			debugfs_remove_recursive(d->dentry);
+			list_del(&d->link);
+			kfree(d);
+		}
+	}
+#else
 	tunnel->destroyed = true;
+#endif
+
 	destroy_tunnel(tunnel);
 
 	return 0;
@@ -1897,6 +1956,179 @@ int drm_dp_tunnel_atomic_check_stream_bws(struct drm_atomic_commit *state,
 }
 EXPORT_SYMBOL(drm_dp_tunnel_atomic_check_stream_bws);
 
+#ifdef CONFIG_DEBUG_FS
+
+static const char *dp_link_rate_name(int rate)
+{
+	switch (rate) {
+	case 162000:	return "RBR";
+	case 270000:	return "HBR";
+	case 540000:	return "HBR2";
+	case 810000:	return "HBR3";
+	case 1000000:	return "UHBR10";
+	case 1350000:	return "UHBR13.5";
+	case 2000000:	return "UHBR20";
+	default:	return "unknown";
+	}
+}
+
+static int tunnel_info_show(struct seq_file *m, void *data)
+{
+	struct drm_dp_tunnel *tunnel = m->private;
+	bool bwa_enabled = drm_dp_tunnel_bw_alloc_is_enabled(tunnel);
+	int allocated_bw = drm_dp_tunnel_get_allocated_bw(tunnel);
+	int max_rate = tunnel->max_dprx_rate;
+
+	seq_printf(m, "Tunnel: DPTUN %s\n", drm_dp_tunnel_name(tunnel));
+	seq_printf(m, "  BW alloc mode:     %s\n",
+		   bwa_enabled ? "enabled" : "disabled");
+
+	/*
+	 * Print BW values in kB/s only so userspace parsers can use a
+	 * single format. Estimated/Allocated/granularity are only
+	 * meaningful while BWA is enabled; when disabled, surface 'n/a'
+	 * rather than the raw -1 sentinel returned by
+	 * drm_dp_tunnel_get_allocated_bw().
+	 */
+	if (bwa_enabled) {
+		seq_printf(m, "  Estimated BW:      %d kB/s\n",
+			   tunnel->estimated_bw);
+		seq_printf(m, "  Allocated BW:      %d kB/s\n", allocated_bw);
+		seq_printf(m, "  BW granularity:    %d kB/s\n",
+			   tunnel->bw_granularity);
+	} else {
+		seq_puts(m, "  Estimated BW:      n/a\n");
+		seq_puts(m, "  Allocated BW:      n/a\n");
+		seq_puts(m, "  BW granularity:    n/a\n");
+	}
+
+	/*
+	 * The DPRX caps are only valid once BWA has been enabled at
+	 * least once. Surface 'n/a' before that to avoid a bogus
+	 * '0 (unknown)'.
+	 */
+	if (max_rate > 0)
+		seq_printf(m, "  DPRX max rate:     %d (%s)\n",
+			   max_rate, dp_link_rate_name(max_rate));
+	else
+		seq_puts(m, "  DPRX max rate:     n/a\n");
+	seq_printf(m, "  DPRX max lanes:    %d\n",
+		   tunnel->max_dprx_lane_count);
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(tunnel_info);
+
+/**
+ * drm_dp_tunnel_debugfs_add - Add DP tunnel debugfs entries
+ * @tunnel: Tunnel object the entries are registered for
+ * @root: Parent debugfs directory under which the tunnel debugfs
+ *        subdir is created. Typically a connector's debugfs root.
+ *
+ * Create a "dp_tunnel" subdirectory under @root and populate it
+ * with the DP tunnel debugfs files. The subdirectory is owned by
+ * @tunnel; it is removed when @tunnel is destroyed via
+ * drm_dp_tunnel_destroy() or when the caller explicitly drops it
+ * via drm_dp_tunnel_debugfs_remove().
+ *
+ * May be called multiple times with different @root values - for
+ * example to expose the same tunnel's files under every DP
+ * connector that shares the tunnel (primary + MST children).
+ * Passing the same @root twice is a no-op.
+ *
+ * If @root may be removed before @tunnel is destroyed (for
+ * instance, an MST child connector being unregistered), the
+ * matching teardown path must call drm_dp_tunnel_debugfs_remove()
+ * so the tunnel does not retain a stale dentry pointer.
+ */
+void drm_dp_tunnel_debugfs_add(struct drm_dp_tunnel *tunnel, struct dentry *root)
+{
+	struct drm_dp_tunnel_debugfs_dir *d;
+	struct dentry *dir;
+
+	if (!tunnel || !root)
+		return;
+
+	mutex_lock(&tunnel->group->mgr->debugfs_lock);
+
+	/*
+	 * Bail out if the tunnel is being torn down: drm_dp_tunnel_destroy()
+	 * has already drained debugfs_dirs and will not run again, so any
+	 * entry added here would be leaked.
+	 */
+	if (tunnel->destroyed)
+		goto unlock;
+
+	list_for_each_entry(d, &tunnel->debugfs_dirs, link) {
+		if (d->parent == root)
+			goto unlock;
+	}
+
+	d = kzalloc(sizeof(*d), GFP_KERNEL);
+	if (!d)
+		goto unlock;
+
+	dir = debugfs_create_dir("dp_tunnel", root);
+	if (IS_ERR(dir)) {
+		kfree(d);
+		goto unlock;
+	}
+
+	d->parent = root;
+	d->dentry = dir;
+	list_add(&d->link, &tunnel->debugfs_dirs);
+
+	debugfs_create_file("info", 0444, dir, tunnel, &tunnel_info_fops);
+
+unlock:
+	mutex_unlock(&tunnel->group->mgr->debugfs_lock);
+}
+EXPORT_SYMBOL(drm_dp_tunnel_debugfs_add);
+
+/**
+ * drm_dp_tunnel_debugfs_remove - Drop DP tunnel debugfs entries for a parent
+ * @tunnel: Tunnel object the entries were registered for
+ * @root: Parent debugfs directory previously passed to
+ *        drm_dp_tunnel_debugfs_add()
+ *
+ * Remove the "dp_tunnel" subdirectory previously created under
+ * @root for @tunnel and drop it from the tunnel's tracked list.
+ * Must be called when @root is about to be torn down while
+ * @tunnel may still be alive (e.g. an MST child connector being
+ * unregistered) so the tunnel does not retain a stale dentry
+ * pointer.
+ *
+ * Calling with a @root that was never registered (or that has
+ * already been removed) is a no-op.
+ */
+void drm_dp_tunnel_debugfs_remove(struct drm_dp_tunnel *tunnel, struct dentry *root)
+{
+	struct drm_dp_tunnel_debugfs_dir *d, *found = NULL;
+
+	if (!tunnel || !root)
+		return;
+
+	mutex_lock(&tunnel->group->mgr->debugfs_lock);
+
+	list_for_each_entry(d, &tunnel->debugfs_dirs, link) {
+		if (d->parent == root) {
+			found = d;
+			list_del(&d->link);
+			break;
+		}
+	}
+
+	mutex_unlock(&tunnel->group->mgr->debugfs_lock);
+
+	if (found) {
+		debugfs_remove_recursive(found->dentry);
+		kfree(found);
+	}
+}
+EXPORT_SYMBOL(drm_dp_tunnel_debugfs_remove);
+
+#endif /* CONFIG_DEBUG_FS */
+
 static void destroy_mgr(struct drm_dp_tunnel_mgr *mgr)
 {
 	int i;
@@ -1910,6 +2142,10 @@ static void destroy_mgr(struct drm_dp_tunnel_mgr *mgr)
 	ref_tracker_dir_exit(&mgr->ref_tracker);
 #endif
 
+#ifdef CONFIG_DEBUG_FS
+	mutex_destroy(&mgr->debugfs_lock);
+#endif
+
 	kfree(mgr->groups);
 	kfree(mgr);
 }
@@ -1936,6 +2172,9 @@ drm_dp_tunnel_mgr_create(struct drm_device *dev, int max_group_count)
 
 	mgr->dev = dev;
 	init_waitqueue_head(&mgr->bw_req_queue);
+#ifdef CONFIG_DEBUG_FS
+	mutex_init(&mgr->debugfs_lock);
+#endif
 
 	mgr->groups = kzalloc_objs(*mgr->groups, max_group_count);
 	if (!mgr->groups) {
diff --git a/include/drm/display/drm_dp_tunnel.h b/include/drm/display/drm_dp_tunnel.h
index 57f5e90ba8fda..53ce71de6f258 100644
--- a/include/drm/display/drm_dp_tunnel.h
+++ b/include/drm/display/drm_dp_tunnel.h
@@ -18,6 +18,8 @@ struct drm_atomic_commit;
 struct drm_dp_tunnel_mgr;
 struct drm_dp_tunnel_state;
 
+struct dentry;
+
 struct ref_tracker;
 
 struct drm_dp_tunnel_ref {
@@ -97,6 +99,16 @@ struct drm_dp_tunnel_mgr *
 drm_dp_tunnel_mgr_create(struct drm_device *dev, int max_group_count);
 void drm_dp_tunnel_mgr_destroy(struct drm_dp_tunnel_mgr *mgr);
 
+#if defined(CONFIG_DEBUG_FS)
+void drm_dp_tunnel_debugfs_add(struct drm_dp_tunnel *tunnel, struct dentry *root);
+void drm_dp_tunnel_debugfs_remove(struct drm_dp_tunnel *tunnel, struct dentry *root);
+#else
+static inline void
+drm_dp_tunnel_debugfs_add(struct drm_dp_tunnel *tunnel, struct dentry *root) {}
+static inline void
+drm_dp_tunnel_debugfs_remove(struct drm_dp_tunnel *tunnel, struct dentry *root) {}
+#endif
+
 #else
 
 static inline struct drm_dp_tunnel *
@@ -249,6 +261,11 @@ drm_dp_tunnel_mgr_create(struct drm_device *dev, int max_group_count)
 static inline
 void drm_dp_tunnel_mgr_destroy(struct drm_dp_tunnel_mgr *mgr) {}
 
+static inline void
+drm_dp_tunnel_debugfs_add(struct drm_dp_tunnel *tunnel, struct dentry *root) {}
+static inline void
+drm_dp_tunnel_debugfs_remove(struct drm_dp_tunnel *tunnel, struct dentry *root) {}
+
 #endif /* CONFIG_DRM_DISPLAY_DP_TUNNEL */
 
 #endif /* __DRM_DP_TUNNEL_H__ */
-- 
2.25.1


  reply	other threads:[~2026-05-11  5:19 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-11  5:40 [RFC 0/7] drm/display/dp_tunnel: Add debugfs surface for BWA validation Kunal Joshi
2026-05-11  5:40 ` Kunal Joshi [this message]
2026-05-11  5:40 ` [RFC 2/7] drm/display/dp_tunnel: Add bw_alloc_enable debugfs knob Kunal Joshi
2026-05-11  5:40 ` [RFC 3/7] drm/display/dp_tunnel: Add bw_limit debugfs cap for BW pressure injection Kunal Joshi
2026-05-11  5:40 ` [RFC 4/7] drm/i915/dp_tunnel: Wire up DP tunnel debugfs from DRM core Kunal Joshi
2026-05-11  5:40 ` [RFC 5/7] drm/i915/display: Expose DP tunnel debugfs under each connector Kunal Joshi
2026-05-11  5:40 ` [RFC 6/7] drm/display/dp_tunnel: Sync SW allocated_bw after enabling BW alloc Kunal Joshi
2026-05-11  5:40 ` [RFC 7/7] drm/i915/dp_tunnel: Re-attach dp_tunnel debugfs to MST children on re-detect Kunal Joshi
2026-05-11 14:54 ` ✓ i915.CI.BAT: success for drm/display/dp_tunnel: Add debugfs surface for BWA validation Patchwork
2026-05-11 20:10 ` ✗ i915.CI.Full: failure " Patchwork

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260511054028.1310995-2-kunal1.joshi@intel.com \
    --to=kunal1.joshi@intel.com \
    --cc=imre.deak@intel.com \
    --cc=intel-gfx@lists.freedesktop.org \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=jani.nikula@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox