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
next prev parent 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