[PATCH v3 02/13] drm/xe/psmi: Add debugfs interface for PSMI
Lucas De Marchi
lucas.demarchi at intel.com
Fri Aug 15 21:35:28 UTC 2025
On Wed, Aug 13, 2025 at 11:42:07AM +0100, Matthew Auld wrote:
>On 08/08/2025 18:29, Lucas De Marchi wrote:
>>Requirement for PSMI capture is to have a physically contiguous buffer.
>>All the needed configuration is done by the userspace tool directly to
>>the GPU via mmio access.
>>
>>This interface only support allocating from VRAM regions. For integrated
>>devices, the PSMI buffer is in SYSTEM memory and should be allocated by
>>userspace using hugetlbfs.
>>
>>Here we add the ability to allocate a region of physically contiguous
>>memory by writing to debugfs file (listed below). For multi-tile devices,
>>the capture tool requires ability to allocate a capture buffer per tile
>>(VRAM region) and so user can specify a region_mask. The tool then
>>can mmap the buffers via direct mmap of the PCIBAR via sysfs.
>>
>>To support the capture tool, 3 new debugfs entries are added:
>>
>> psmi_capture_addr - physical address per VRAM region's capture buffer
>> psmi_capture_region_mask - select which region(s) to allocate a buffer
>> psmi_capture_size - size of current capture buffer
>>
>>Writing psmi_capture_size will allocate new buffer of requested size per
>>region after freeing any current buffers.
>>
>>Cc: Matt Roper <matthew.d.roper at intel.com>
>>Cc: Vinay Belgaumkar <vinay.belgaumkar at intel.com>
>>Original-author: Brian Welty <brian.welty at intel.com>
>>Signed-off-by: Lucas De Marchi <lucas.demarchi at intel.com>
>>---
>>v2:
>> - Fix kernel-doc
>> - Do not walk all region_mask on cleanup: it should never be needed
>> - Replace sysmem checks by asserts as they should never be set
>> - s/debugfs_create/debugfs_register/ and do not pass the root dir:
>> this makes it similar to other parts registering debugfs
>> - Do not export a cleanup function, rather use a init that registers
>> a devm action if needed
>> - Drop modparam in favor of configfs whose attribute will be
>> implemented when everything is ready
>>---
>> drivers/gpu/drm/xe/Makefile | 1 +
>> drivers/gpu/drm/xe/xe_debugfs.c | 3 +
>> drivers/gpu/drm/xe/xe_device.c | 5 +
>> drivers/gpu/drm/xe/xe_device_types.h | 8 +
>> drivers/gpu/drm/xe/xe_psmi.c | 313 +++++++++++++++++++++++++++++++++++
>> drivers/gpu/drm/xe/xe_psmi.h | 14 ++
>> 6 files changed, 344 insertions(+)
>>
>>diff --git a/drivers/gpu/drm/xe/Makefile b/drivers/gpu/drm/xe/Makefile
>>index 8e0c3412a757c..85b8d3a59ef07 100644
>>--- a/drivers/gpu/drm/xe/Makefile
>>+++ b/drivers/gpu/drm/xe/Makefile
>>@@ -98,6 +98,7 @@ xe-y += xe_bb.o \
>> xe_pcode.o \
>> xe_pm.o \
>> xe_preempt_fence.o \
>>+ xe_psmi.o \
>> xe_pt.o \
>> xe_pt_walk.o \
>> xe_pxp.o \
>>diff --git a/drivers/gpu/drm/xe/xe_debugfs.c b/drivers/gpu/drm/xe/xe_debugfs.c
>>index 0b4a532f7c45c..bc717519502dd 100644
>>--- a/drivers/gpu/drm/xe/xe_debugfs.c
>>+++ b/drivers/gpu/drm/xe/xe_debugfs.c
>>@@ -20,6 +20,7 @@
>> #include "xe_guc_ads.h"
>> #include "xe_mmio.h"
>> #include "xe_pm.h"
>>+#include "xe_psmi.h"
>> #include "xe_pxp_debugfs.h"
>> #include "xe_sriov.h"
>> #include "xe_sriov_pf.h"
>>@@ -400,6 +401,8 @@ void xe_debugfs_register(struct xe_device *xe)
>> xe_pxp_debugfs_register(xe->pxp);
>>+ xe_psmi_debugfs_register(xe);
>>+
>> fault_create_debugfs_attr("fail_gt_reset", root, >_reset_failure);
>> if (IS_SRIOV_PF(xe))
>>diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c
>>index 57edbc63da6f4..62edb39b61fb0 100644
>>--- a/drivers/gpu/drm/xe/xe_device.c
>>+++ b/drivers/gpu/drm/xe/xe_device.c
>>@@ -54,6 +54,7 @@
>> #include "xe_pcode.h"
>> #include "xe_pm.h"
>> #include "xe_pmu.h"
>>+#include "xe_psmi.h"
>> #include "xe_pxp.h"
>> #include "xe_query.h"
>> #include "xe_shrinker.h"
>>@@ -908,6 +909,10 @@ int xe_device_probe(struct xe_device *xe)
>> if (err)
>> return err;
>>+ err = xe_psmi_init(xe);
>>+ if (err)
>>+ return err;
>>+
>> err = drm_dev_register(&xe->drm, 0);
>> if (err)
>> return err;
>>diff --git a/drivers/gpu/drm/xe/xe_device_types.h b/drivers/gpu/drm/xe/xe_device_types.h
>>index 01e8fa0d2f9f7..bf9af8d0b84ae 100644
>>--- a/drivers/gpu/drm/xe/xe_device_types.h
>>+++ b/drivers/gpu/drm/xe/xe_device_types.h
>>@@ -576,6 +576,14 @@ struct xe_device {
>> atomic64_t global_total_pages;
>> #endif
>>+ /** @psmi: GPU debugging via additional validation HW */
>>+ struct {
>>+ /** @psmi.capture_obj: PSMI buffer for VRAM */
>>+ struct xe_bo *capture_obj[XE_MAX_TILES_PER_DEVICE + 1];
>>+ /** @psmi.region_mask: Mask of valid memory regions */
>>+ u8 region_mask;
>>+ } psmi;
>>+
>> /* private: */
>> #if IS_ENABLED(CONFIG_DRM_XE_DISPLAY)
>>diff --git a/drivers/gpu/drm/xe/xe_psmi.c b/drivers/gpu/drm/xe/xe_psmi.c
>>new file mode 100644
>>index 0000000000000..e6a67e85e1bb2
>>--- /dev/null
>>+++ b/drivers/gpu/drm/xe/xe_psmi.c
>>@@ -0,0 +1,313 @@
>>+// SPDX-License-Identifier: MIT
>>+/*
>>+ * Copyright © 2025 Intel Corporation
>>+ */
>>+
>>+#include <linux/debugfs.h>
>>+
>>+#include "xe_bo.h"
>>+#include "xe_device.h"
>>+#include "xe_configfs.h"
>>+#include "xe_psmi.h"
>>+
>>+/*
>>+ * PSMI capture support
>>+ *
>>+ * Requirement for PSMI capture is to have a physically contiguous buffer. The
>>+ * PSMI tool owns doing all necessary configuration (MMIO register writes are
>>+ * done from user-space). However, KMD needs to provide the PSMI tool with the
>>+ * required physical address of the base of PSMI buffer in case of VRAM.
>>+ *
>>+ * VRAM backed PSMI buffer:
,,>>+ * Buffer is allocated as GEM object and with XE_BO_CREATE_PINNED_BIT flag which
>>+ * creates a contiguous allocation. The physical address is returned from
>>+ * psmi_debugfs_capture_addr_show(). PSMI tool can mmap the buffer via the
>>+ * PCIBAR through sysfs.
>>+ *
>>+ * SYSTEM memory backed PSMI buffer:
>>+ * Interface here does not support allocating from SYSTEM memory region. The
>>+ * PSMI tool needs to allocate memory themselves using hugetlbfs. In order to
>>+ * get the physical address, user-space can query /proc/[pid]/pagemap. As an
>>+ * alternative, CMA debugfs could also be used to allocate reserved CMA memory.
>>+ */
>>+
>>+static bool psmi_enabled(struct xe_device *xe)
>>+{
>>+ return xe_configfs_get_psmi_enabled(to_pci_dev(xe->drm.dev));
>>+}
>>+
>>+static void psmi_free_object(struct xe_bo *bo)
>>+{
>>+ xe_bo_lock(bo, NULL);
>>+ xe_bo_unpin(bo);
>>+ xe_bo_unlock(bo);
>>+ xe_bo_put(bo);
>>+}
>>+
>>+/*
>>+ * Free PSMI capture buffer objects.
>>+ */
>>+static void psmi_cleanup(struct xe_device *xe)
>>+{
>>+ unsigned long id, region_mask = xe->psmi.region_mask;
>>+ struct xe_bo *bo;
>>+
>>+ for_each_set_bit(id, ®ion_mask,
>>+ ARRAY_SIZE(xe->psmi.capture_obj)) {
>>+ /* smem should never be set */
>>+ xe_assert(xe, id);
>>+
>>+ bo = xe->psmi.capture_obj[id];
>>+ if (bo) {
>>+ psmi_free_object(bo);
>>+ xe->psmi.capture_obj[id] = NULL;
>>+ }
>>+ }
>>+}
>>+
>>+static struct xe_bo *psmi_alloc_object(struct xe_device *xe,
>>+ unsigned int id, size_t bo_size)
>>+{
>>+ struct xe_bo *bo = NULL;
>>+ struct xe_tile *tile;
>>+ int err;
>>+
>>+ if (!id || !bo_size)
>>+ return NULL;
>>+
>>+ tile = &xe->tiles[id - 1];
>>+
>>+ /* VRAM: Allocate GEM object for the capture buffer */
>>+ bo = xe_bo_create_locked(xe, tile, NULL, bo_size,
>>+ ttm_bo_type_kernel,
>>+ XE_BO_FLAG_VRAM_IF_DGFX(tile) |
>>+ XE_BO_FLAG_PINNED |
>>+ XE_BO_FLAG_NEEDS_CPU_ACCESS);
>
>It might make sense to add XE_BO_FLAG_PINNED_LATE_RESTORE, assuming
>this memory needs to be saved and restored for VRAM case. Since it
>might could be ~large it might benefit from using the blitter instead
>of CPU?
ok
>
>Also do you want the memory to be pre-cleared here? Currently it just
>gives you back uncleared memory, also with uncleared CCS. If uncleared
>is fine, then should that be documented somewhere for the user so that
>there are no surprises?
I don't think it needs to be cleared - for example for the system memory
case it's coming from hugetlbfs. I will double check that... may not
change anything in next version regarding that though as I want to
resolve other comments.
>
>>+
>>+ if (!IS_ERR(bo)) {
>>+ /* Buffer written by HW, ensure stays resident */
>>+ err = xe_bo_pin(bo);
>>+ if (err)
>>+ bo = ERR_PTR(err);
>>+ xe_bo_unlock(bo);
>>+ }
>>+
>>+ return bo;
>>+}
>>+
>>+/*
>>+ * Allocate PSMI capture buffer objects (via debugfs set function), based on
>>+ * which regions the user has selected in region_mask. @size: size in bytes
>>+ * (should be power of 2)
>>+ *
>>+ * Always release/free the current buffer objects before attempting to allocate
>>+ * new ones. Size == 0 will free all current buffers.
>>+ *
>>+ * Note, we don't write any registers as the capture tool is already configuring
>>+ * all PSMI registers itself via mmio space.
>>+ */
>>+static int psmi_resize_object(struct xe_device *xe, size_t size)
>>+{
>>+ unsigned long id, region_mask = xe->psmi.region_mask;
>>+ struct xe_bo *bo = NULL;
>>+ int err = 0;
>>+
>>+ /*
>>+ * Buddy allocator anyway will roundup to next power of 2,
>>+ * so rather than waste unused pages, require user to ask for
>>+ * power of 2 sized PSMI buffers.
>
>It will internally do a trim for you to give back any excess, if not a
>power-of-two, so might be beneficial to drop this restriction.
>Probably doesn't matter all that much though.
ok
thanks
Lucas De Marchi
>
>>+ */
>>+ if (size && !is_power_of_2(size))
>>+ return -EINVAL;
>>+
>>+ /* if resizing, free currently allocated buffers first */
>>+ psmi_cleanup(xe);
>>+
>>+ /* can set size to 0, in which case, now done */
>>+ if (!size)
>>+ return 0;
>>+
>>+ for_each_set_bit(id, ®ion_mask,
>>+ ARRAY_SIZE(xe->psmi.capture_obj)) {
>>+ /* smem should never be set */
>>+ xe_assert(xe, id);
>>+
>>+ bo = psmi_alloc_object(xe, id, size);
>>+ if (IS_ERR(bo)) {
>>+ err = PTR_ERR(bo);
>>+ break;
>>+ }
>>+ xe->psmi.capture_obj[id] = bo;
>>+
>>+ drm_info(&xe->drm,
>>+ "PSMI capture size requested: %zu bytes, allocated: %lu:%zu\n",
>>+ size, id, bo ? xe_bo_size(bo) : 0);
>>+ }
>>+
>>+ /* on error, reverse what was allocated */
>>+ if (err)
>>+ psmi_cleanup(xe);
>>+
>>+ return err;
>>+}
>>+
>>+/*
>>+ * Returns an address for the capture tool to use to find start of capture
>>+ * buffer. Capture tool requires the capability to have a buffer allocated per
>>+ * each tile (VRAM region), thus we return an address for each region.
>>+ */
>>+static int psmi_debugfs_capture_addr_show(struct seq_file *m, void *data)
>>+{
>>+ struct xe_device *xe = m->private;
>>+ unsigned long id, region_mask;
>>+ struct xe_bo *bo;
>>+ u64 val;
>>+
>>+ region_mask = xe->psmi.region_mask;
>>+ for_each_set_bit(id, ®ion_mask,
>>+ ARRAY_SIZE(xe->psmi.capture_obj)) {
>>+ /* smem should never be set */
>>+ xe_assert(xe, id);
>>+
>>+ /* VRAM region */
>>+ bo = xe->psmi.capture_obj[id];
>>+ if (!bo)
>>+ continue;
>>+
>>+ /* pinned, so don't need bo_lock */
>>+ val = __xe_bo_addr(bo, 0, PAGE_SIZE);
>>+ seq_printf(m, "%ld: 0x%llx\n", id, val);
>>+ }
>>+
>>+ return 0;
>>+}
>>+
>>+/*
>>+ * Return capture buffer size, using the size from first allocated object that
>>+ * is found. This works because all objects must be of the same size.
>>+ */
>>+static int psmi_debugfs_capture_size_get(void *data, u64 *val)
>>+{
>>+ unsigned long id, region_mask;
>>+ struct xe_device *xe = data;
>>+ struct xe_bo *bo;
>>+
>>+ region_mask = xe->psmi.region_mask;
>>+ for_each_set_bit(id, ®ion_mask,
>>+ ARRAY_SIZE(xe->psmi.capture_obj)) {
>>+ /* smem should never be set */
>>+ xe_assert(xe, id);
>>+
>>+ bo = xe->psmi.capture_obj[id];
>>+ if (bo) {
>>+ *val = xe_bo_size(bo);
>>+ return 0;
>>+ }
>>+ }
>>+
>>+ /* no capture objects are allocated */
>>+ *val = 0;
>>+
>>+ return 0;
>>+}
>>+
>>+/*
>>+ * Set size of PSMI capture buffer. This triggers the allocation of capture
>>+ * buffer in each memory region as specified with prior write to
>>+ * psmi_capture_region_mask.
>>+ */
>>+static int psmi_debugfs_capture_size_set(void *data, u64 val)
>>+{
>>+ struct xe_device *xe = data;
>>+
>>+ /* user must have specified at least one region */
>>+ if (!xe->psmi.region_mask)
>>+ return -EINVAL;
>>+
>>+ return psmi_resize_object(xe, val);
>>+}
>>+
>>+static int psmi_debugfs_capture_region_mask_get(void *data, u64 *val)
>>+{
>>+ struct xe_device *xe = data;
>>+
>>+ *val = xe->psmi.region_mask;
>>+
>>+ return 0;
>>+}
>>+
>>+/*
>>+ * Select VRAM regions for multi-tile devices, only allowed when buffer is not
>>+ * currently allocated.
>>+ */
>>+static int psmi_debugfs_capture_region_mask_set(void *data, u64 region_mask)
>>+{
>>+ struct xe_device *xe = data;
>>+ u64 size = 0;
>>+
>>+ /* SMEM is not supported (see comments at top of file) */
>>+ if (region_mask & 0x1)
>>+ return -EOPNOTSUPP;
>>+
>>+ /* input bitmask should contain only valid TTM regions */
>>+ if (!region_mask || region_mask & ~xe->info.mem_region_mask)
>>+ return -EINVAL;
>>+
>>+ /* only allow setting mask if buffer is not yet allocated */
>>+ psmi_debugfs_capture_size_get(xe, &size);
>>+ if (size)
>>+ return -EBUSY;
>>+
>>+ xe->psmi.region_mask = region_mask;
>>+
>>+ return 0;
>>+}
>>+
>>+DEFINE_SHOW_ATTRIBUTE(psmi_debugfs_capture_addr);
>>+
>>+DEFINE_DEBUGFS_ATTRIBUTE(psmi_debugfs_capture_region_mask_fops,
>>+ psmi_debugfs_capture_region_mask_get,
>>+ psmi_debugfs_capture_region_mask_set,
>>+ "0x%llx\n");
>>+
>>+DEFINE_DEBUGFS_ATTRIBUTE(psmi_debugfs_capture_size_fops,
>>+ psmi_debugfs_capture_size_get,
>>+ psmi_debugfs_capture_size_set,
>>+ "%lld\n");
>>+
>>+void xe_psmi_debugfs_register(struct xe_device *xe)
>>+{
>>+ struct drm_minor *minor;
>>+
>>+ if (!psmi_enabled(xe))
>>+ return;
>>+
>>+ minor = xe->drm.primary;
>>+ if (!minor->debugfs_root)
>>+ return;
>>+
>>+ debugfs_create_file("psmi_capture_addr",
>>+ 0400, minor->debugfs_root, xe,
>>+ &psmi_debugfs_capture_addr_fops);
>>+
>>+ debugfs_create_file("psmi_capture_region_mask",
>>+ 0600, minor->debugfs_root, xe,
>>+ &psmi_debugfs_capture_region_mask_fops);
>>+
>>+ debugfs_create_file("psmi_capture_size",
>>+ 0600, minor->debugfs_root, xe,
>>+ &psmi_debugfs_capture_size_fops);
>>+}
>>+
>>+static void psmi_fini(void *arg)
>>+{
>>+ psmi_cleanup(arg);
>>+}
>>+
>>+int xe_psmi_init(struct xe_device *xe)
>>+{
>>+ if (!psmi_enabled(xe))
>>+ return 0;
>>+
>>+ return devm_add_action(xe->drm.dev, psmi_fini, xe);
>>+}
>>diff --git a/drivers/gpu/drm/xe/xe_psmi.h b/drivers/gpu/drm/xe/xe_psmi.h
>>new file mode 100644
>>index 0000000000000..b1dfba80d893d
>>--- /dev/null
>>+++ b/drivers/gpu/drm/xe/xe_psmi.h
>>@@ -0,0 +1,14 @@
>>+/* SPDX-License-Identifier: MIT */
>>+/*
>>+ * Copyright © 2025 Intel Corporation
>>+ */
>>+
>>+#ifndef _XE_PSMI_H_
>>+#define _XE_PSMI_H_
>>+
>>+struct xe_device;
>>+
>>+int xe_psmi_init(struct xe_device *xe);
>>+void xe_psmi_debugfs_register(struct xe_device *xe);
>>+
>>+#endif
>>
>
More information about the Intel-xe
mailing list