Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: "Thomas Hellström" <thomas.hellstrom@linux.intel.com>
To: intel-xe@lists.freedesktop.org
Cc: "Thomas Hellström" <thomas.hellstrom@linux.intel.com>,
	"Himal Prasad Ghimiray" <himal.prasad.ghimiray@intel.com>,
	"Rodrigo Vivi" <rodrigo.vivi@intel.com>,
	"Matthew Brost" <matthew.brost@intel.com>,
	"Matthew Auld" <matthew.auld@intel.com>,
	"Andi Shyti" <andi.shyti@kernel.org>
Subject: [PATCH v3] drm/xe: Add a per-client debugfs file reporting bo NUMA node distribution
Date: Wed,  4 Nov 2026 16:13:35 +0100	[thread overview]
Message-ID: <20261104151335.163299-1-thomas.hellstrom@linux.intel.com> (raw)

When debugging NUMA placement of bo system memory, there is currently
no way to tell on which nodes the pages backing a bo reside. The
/proc/<pid>/numa_maps interface doesn't help since bo CPU mappings are
VM_PFNMAP, and clients may have thousands of bos, so a dump of all bos
is undesirable.

Add a "bo_numa" file to the drm core per-client debugfs directory,
client-<id>/. The file implements a request-response protocol per open
file: Writing a GEM handle valid for the client takes a snapshot, under
the bo's dma_resv lock, of the bo size, placement, and the number of
pages on each NUMA node. A bo whose pages are swapped out, backed up
or purged is reported as swapped/purged, and one whose pages are not
populated as not populated. Subsequent reads return that snapshot. Once the
snapshot has been completely read it is discarded, and a new handle
may be written. Until then writes fail with -EBUSY, so that a pending
snapshot can't be replaced before it is consumed. This allows querying
many bos using a single open file.

The write-request / read-response protocol per open file follows the
simple_transaction helpers in fs/libfs.c, used by e.g. nfsd and
selinuxfs, and amdgpu already populates the per-client debugfs
directory with its vm_pagetable_info file.

For sg bos, typically imported dma-bufs, the backing pages are owned
by the exporter and can't be inspected, so only size and placement are
reported.

Example:
  exec 3<>/sys/kernel/debug/dri/client-<id>/bo_numa
  echo <handle1> >&3
  cat <&3
  echo <handle2> >&3
  cat <&3
  exec 3>&-

The client id can be obtained from the drm-client-id fdinfo key.

v2:
- Use drm_coredump_printer_is_full() (Andi Shyti)
- Drop the Assisted-by tag and add a Signed-off-by (Andi Shyti)

v3:
- Report swapped or purged bos as swapped/purged (Sashiko)

Cc: Himal Prasad Ghimiray <himal.prasad.ghimiray@intel.com>
Cc: Rodrigo Vivi <rodrigo.vivi@intel.com>
Cc: Matthew Brost <matthew.brost@intel.com>
Cc: Matthew Auld <matthew.auld@intel.com>
Cc: Andi Shyti <andi.shyti@kernel.org>
Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
---
 drivers/gpu/drm/xe/xe_bo.c      | 109 ++++++++++++++++++++++
 drivers/gpu/drm/xe/xe_bo.h      |   6 ++
 drivers/gpu/drm/xe/xe_debugfs.c | 157 ++++++++++++++++++++++++++++++++
 drivers/gpu/drm/xe/xe_debugfs.h |   3 +
 drivers/gpu/drm/xe/xe_device.c  |   2 +
 5 files changed, 277 insertions(+)

diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c
index 42a6aa1a2e4a..ee232e73bc3b 100644
--- a/drivers/gpu/drm/xe/xe_bo.c
+++ b/drivers/gpu/drm/xe/xe_bo.c
@@ -6,12 +6,14 @@
 #include "xe_bo.h"
 
 #include <linux/dma-buf.h>
+#include <linux/nodemask.h>
 #include <linux/nospec.h>
 
 #include <drm/drm_drv.h>
 #include <drm/drm_dumb_buffers.h>
 #include <drm/drm_gem_ttm_helper.h>
 #include <drm/drm_managed.h>
+#include <drm/drm_print.h>
 #include <drm/ttm/ttm_backup.h>
 #include <drm/ttm/ttm_device.h>
 #include <drm/ttm/ttm_placement.h>
@@ -4248,6 +4250,113 @@ void xe_bo_runtime_pm_release_mmap_offset(struct xe_bo *bo)
 	list_del_init(&bo->vram_userfault_link);
 }
 
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void xe_bo_count_numa_pages(struct ttm_tt *tt, unsigned long *nodes)
+{
+	pgoff_t i;
+
+	for (i = 0; i < tt->num_pages; ++i)
+		nodes[page_to_nid(tt->pages[i])]++;
+}
+
+static void xe_bo_print_numa_info_locked(struct xe_bo *bo, unsigned long *nodes,
+					 struct drm_printer *p)
+{
+	struct ttm_resource *res = bo->ttm.resource;
+	struct ttm_tt *tt = bo->ttm.ttm;
+	unsigned long total = 0;
+	const char *name;
+	int nid;
+
+	xe_bo_assert_held(bo);
+
+	name = res ? xe_mem_type_to_name[res->mem_type] : NULL;
+	drm_printf(p, "size: %zu\n", xe_bo_size(bo));
+	if (name)
+		drm_printf(p, "placement: %s\n", name);
+	else if (res)
+		drm_printf(p, "placement: mem_type %u\n", res->mem_type);
+	else
+		drm_puts(p, "placement: none\n");
+
+	/* The pages of sg bos are owned by the exporter and not visible. */
+	if (bo->ttm.type == ttm_bo_type_sg) {
+		drm_puts(p, "pages: unknown (sg bo)\n");
+		return;
+	}
+
+	if (tt && ttm_tt_is_swapped(tt)) {
+		drm_puts(p, "pages: swapped/purged\n");
+		return;
+	}
+
+	if (tt && !ttm_tt_is_populated(tt)) {
+		drm_puts(p, "pages: not populated\n");
+		return;
+	}
+
+	if (tt)
+		xe_bo_count_numa_pages(tt, nodes);
+
+	for_each_node(nid) {
+		if (!nodes[nid] && !node_online(nid))
+			continue;
+		drm_printf(p, "node%d: %lu\n", nid, nodes[nid]);
+		total += nodes[nid];
+	}
+
+	drm_printf(p, "total: %lu\n", total);
+}
+
+/**
+ * xe_bo_print_numa_info() - Print the NUMA node distribution of a bo
+ * @file: The drm_file of the client owning @handle.
+ * @handle: The GEM handle of the bo.
+ * @p: The drm_printer to print to.
+ *
+ * Print the size and placement of the bo and, for bos with
+ * driver-allocated system pages, the number of pages residing on each
+ * NUMA node. If the bo's pages are currently swapped out, backed up or
+ * purged by the shrinker, the bo is reported as swapped/purged, and if
+ * they are not populated, as not populated. For sg bos, typically
+ * imported dma-bufs, the backing pages are owned by the exporter and are
+ * reported as unknown. The information is gathered under the bo's
+ * dma_resv lock and thus represents a consistent snapshot.
+ *
+ * Return: 0 on success, -ENOENT if @handle doesn't refer to a bo,
+ * negative error code on other failures.
+ */
+int xe_bo_print_numa_info(struct drm_file *file, u32 handle,
+			  struct drm_printer *p)
+{
+	struct drm_gem_object *obj;
+	unsigned long *nodes;
+	int ret;
+
+	nodes = kcalloc(nr_node_ids, sizeof(*nodes), GFP_KERNEL_ACCOUNT);
+	if (!nodes)
+		return -ENOMEM;
+
+	obj = drm_gem_object_lookup(file, handle);
+	if (!obj) {
+		ret = -ENOENT;
+		goto out_free;
+	}
+
+	ret = dma_resv_lock_interruptible(obj->resv, NULL);
+	if (!ret) {
+		xe_bo_print_numa_info_locked(gem_to_xe_bo(obj), nodes, p);
+		dma_resv_unlock(obj->resv);
+	}
+
+	drm_gem_object_put(obj);
+out_free:
+	kfree(nodes);
+
+	return ret;
+}
+#endif
+
 #if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST)
 #include "tests/xe_bo.c"
 #endif
diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h
index 77b3a02e9b8e..13197e4e0358 100644
--- a/drivers/gpu/drm/xe/xe_bo.h
+++ b/drivers/gpu/drm/xe/xe_bo.h
@@ -112,6 +112,7 @@ enum xe_madv_purgeable_state {
 	XE_MADV_PURGEABLE_PURGED,
 };
 
+struct drm_printer;
 struct sg_table;
 
 struct xe_bo *xe_bo_alloc(void);
@@ -458,6 +459,11 @@ int xe_bo_dumb_create(struct drm_file *file_priv,
 		      struct drm_device *dev,
 		      struct drm_mode_create_dumb *args);
 
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+int xe_bo_print_numa_info(struct drm_file *file, u32 handle,
+			  struct drm_printer *p);
+#endif
+
 bool xe_bo_needs_ccs_pages(struct xe_bo *bo);
 
 int xe_bo_decompress(struct xe_bo *bo);
diff --git a/drivers/gpu/drm/xe/xe_debugfs.c b/drivers/gpu/drm/xe/xe_debugfs.c
index e59d4964d811..1a74a7106462 100644
--- a/drivers/gpu/drm/xe/xe_debugfs.c
+++ b/drivers/gpu/drm/xe/xe_debugfs.c
@@ -11,6 +11,8 @@
 #include <linux/string_helpers.h>
 
 #include <drm/drm_debugfs.h>
+#include <drm/drm_file.h>
+#include <drm/drm_print.h>
 
 #include "regs/xe_pmt.h"
 #include "xe_bo.h"
@@ -785,3 +787,158 @@ void xe_debugfs_register(struct xe_device *xe)
 	else if (IS_SRIOV_VF(xe))
 		xe_sriov_vf_debugfs_register(xe, root);
 }
+
+/*
+ * Per-client "bo_numa" file: Report the NUMA node distribution of the
+ * system pages backing a single bo.
+ *
+ * The file implements a request-response protocol: write() takes a GEM
+ * handle, valid for the client, and takes a snapshot of the bo's page
+ * distribution under the bo's dma_resv lock. Subsequent read()s return
+ * that snapshot. Once the snapshot has been completely read, it is
+ * discarded and a new handle may be written. Until then, writes return
+ * -EBUSY, so that a pending snapshot is never replaced before it is
+ * consumed.
+ *
+ * See xe_bo_print_numa_info() for the snapshot contents.
+ */
+struct bo_numa_snapshot {
+	/* Serializes reads and writes on the open file. */
+	struct mutex lock;
+	/* The pending formatted snapshot, or NULL if none. */
+	char *buf;
+	/* Length of the formatted snapshot. */
+	size_t len;
+};
+
+static int bo_numa_open(struct inode *inode, struct file *f)
+{
+	struct bo_numa_snapshot *snap;
+
+	snap = kzalloc_obj(*snap, GFP_KERNEL_ACCOUNT);
+	if (!snap)
+		return -ENOMEM;
+
+	mutex_init(&snap->lock);
+	f->private_data = snap;
+
+	return nonseekable_open(inode, f);
+}
+
+static int bo_numa_release(struct inode *inode, struct file *f)
+{
+	struct bo_numa_snapshot *snap = f->private_data;
+
+	kvfree(snap->buf);
+	mutex_destroy(&snap->lock);
+	kfree(snap);
+
+	return 0;
+}
+
+static ssize_t bo_numa_read(struct file *f, char __user *ubuf, size_t size,
+			    loff_t *pos)
+{
+	struct bo_numa_snapshot *snap = f->private_data;
+	ssize_t ret = 0;
+
+	mutex_lock(&snap->lock);
+	if (snap->buf) {
+		ret = simple_read_from_buffer(ubuf, size, pos, snap->buf,
+					      snap->len);
+		if (*pos >= snap->len) {
+			kvfree(snap->buf);
+			snap->buf = NULL;
+		}
+	}
+	mutex_unlock(&snap->lock);
+
+	return ret;
+}
+
+static ssize_t bo_numa_write(struct file *f, const char __user *ubuf,
+			     size_t size, loff_t *pos)
+{
+	struct drm_file *file = file_inode(f)->i_private;
+	struct bo_numa_snapshot *snap = f->private_data;
+	struct drm_print_iterator iter;
+	struct drm_printer p;
+	size_t bufsize = PAGE_SIZE / 2;
+	char *buf = NULL;
+	u32 handle;
+	int ret;
+
+	ret = kstrtou32_from_user(ubuf, size, 0, &handle);
+	if (ret)
+		return ret;
+
+	ret = mutex_lock_interruptible(&snap->lock);
+	if (ret)
+		return ret;
+
+	if (snap->buf) {
+		ret = -EBUSY;
+		goto out_unlock;
+	}
+
+	/*
+	 * Retry with a larger buffer until the snapshot fits. The coredump
+	 * printer is used as a generic bounded buffer printer, and reports
+	 * when its buffer is full.
+	 */
+	do {
+		kvfree(buf);
+		bufsize *= 2;
+		buf = kvzalloc(bufsize, GFP_KERNEL_ACCOUNT);
+		if (!buf) {
+			ret = -ENOMEM;
+			goto out_unlock;
+		}
+
+		iter = (struct drm_print_iterator) {
+			.data = buf,
+			.remain = bufsize,
+		};
+		p = drm_coredump_printer(&iter);
+		ret = xe_bo_print_numa_info(file, handle, &p);
+		if (ret)
+			goto out_free;
+	} while (drm_coredump_printer_is_full(&p));
+
+	snap->buf = buf;
+	snap->len = bufsize - iter.remain;
+	*pos = 0;
+	buf = NULL;
+
+out_free:
+	kvfree(buf);
+out_unlock:
+	mutex_unlock(&snap->lock);
+
+	return ret ?: size;
+}
+
+static const struct file_operations bo_numa_fops = {
+	.owner = THIS_MODULE,
+	.open = bo_numa_open,
+	.release = bo_numa_release,
+	.read = bo_numa_read,
+	.write = bo_numa_write,
+};
+
+/**
+ * xe_debugfs_client_add() - Add xe-specific per-client debugfs files
+ * @file: The drm_file of the client.
+ *
+ * Populate the drm core per-client debugfs directory with xe-specific
+ * files. The files are removed by the drm core together with the
+ * directory when @file is released.
+ */
+void xe_debugfs_client_add(struct drm_file *file)
+{
+	if (IS_ERR_OR_NULL(file->debugfs_client))
+		return;
+
+	debugfs_create_file("bo_numa", 0600, file->debugfs_client, file,
+			    &bo_numa_fops);
+}
diff --git a/drivers/gpu/drm/xe/xe_debugfs.h b/drivers/gpu/drm/xe/xe_debugfs.h
index 88d91c78036b..90effba271d9 100644
--- a/drivers/gpu/drm/xe/xe_debugfs.h
+++ b/drivers/gpu/drm/xe/xe_debugfs.h
@@ -8,6 +8,7 @@
 
 #include <linux/types.h>
 
+struct drm_file;
 struct xe_device;
 
 #ifdef CONFIG_DEBUG_FS
@@ -16,12 +17,14 @@ bool xe_fault_csc_hw_error(void);
 bool xe_fault_wedge_cold_reset(void);
 bool xe_fault_mempage_offline(void);
 void xe_debugfs_register(struct xe_device *xe);
+void xe_debugfs_client_add(struct drm_file *file);
 #else
 static inline bool xe_fault_gt_reset(void) { return false; }
 static inline bool xe_fault_csc_hw_error(void) { return false; }
 static inline bool xe_fault_wedge_cold_reset(void) { return false; }
 static inline bool xe_fault_mempage_offline(void) { return false; }
 static inline void xe_debugfs_register(struct xe_device *xe) { }
+static inline void xe_debugfs_client_add(struct drm_file *file) { }
 #endif
 
 #endif
diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c
index 273e2c45fea9..e1f457d03b09 100644
--- a/drivers/gpu/drm/xe/xe_device.c
+++ b/drivers/gpu/drm/xe/xe_device.c
@@ -126,6 +126,8 @@ static int xe_file_open(struct drm_device *dev, struct drm_file *file)
 		put_task_struct(task);
 	}
 
+	xe_debugfs_client_add(file);
+
 	return 0;
 }
 
-- 
2.56.0


             reply	other threads:[~2026-11-04 15:14 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04 15:13 Thomas Hellström [this message]
2026-11-04 15:36 ` [PATCH v3] drm/xe: Add a per-client debugfs file reporting bo NUMA node distribution sashiko-bot
2026-11-04 16:56 ` ✓ CI.KUnit: success for drm/xe: Add a per-client debugfs file reporting bo NUMA node distribution (rev3) Patchwork
2026-11-04 17:37 ` ✓ Xe.CI.BAT: " 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=20261104151335.163299-1-thomas.hellstrom@linux.intel.com \
    --to=thomas.hellstrom@linux.intel.com \
    --cc=andi.shyti@kernel.org \
    --cc=himal.prasad.ghimiray@intel.com \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=matthew.auld@intel.com \
    --cc=matthew.brost@intel.com \
    --cc=rodrigo.vivi@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