Contributors: 7
Author Tokens Token Proportion Commits Commit Proportion
Lucas De Marchi 801 88.12% 2 18.18%
Matthew Brost 78 8.58% 3 27.27%
Unknown 9 0.99% 1 9.09%
Rodrigo Vivi 9 0.99% 2 18.18%
Thomas Hellstrom 7 0.77% 1 9.09%
Eric Anholt 3 0.33% 1 9.09%
Michal Wajdeczko 2 0.22% 1 9.09%
Total 909 11


// 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, &region_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_tile *tile;

	if (!id || !bo_size)
		return NULL;

	tile = &xe->tiles[id - 1];

	/* VRAM: Allocate GEM object for the capture buffer */
	return xe_bo_create_pin_range_novm(xe, tile, bo_size, 0, ~0ull,
					   ttm_bo_type_kernel,
					   XE_BO_FLAG_VRAM_IF_DGFX(tile) |
					   XE_BO_FLAG_PINNED |
					   XE_BO_FLAG_PINNED_LATE_RESTORE |
					   XE_BO_FLAG_NEEDS_CPU_ACCESS);
}

/*
 * 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;

	/* 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, &region_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, &region_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, &region_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);
}