Add the DRM_AMDGPU_PROFILER ioctl handler and its manager, the base for
GPU performance monitoring features such as SPM (Stream Performance
Monitor).

The manager is per-XCP (eXtended Compute Partition) and refcounted, so
several clients can share a partition. Clients with no partition assigned
are rejected on a partitioned device, to keep partition 0's counters
isolated. Teardown is split into a prepare/fini pair so that in-flight
ioctls are drained across drm_dev_unplug().

The ioctl is not registered in amdgpu_ioctls_kms[] and the SPM operations
return -EOPNOTSUPP. Both land later in this series, so the UAPI only
becomes reachable once it is fully implemented.

Signed-off-by: James Zhu <[email protected]>
Reviewed-by: Bing Ma <[email protected]>
---
 drivers/gpu/drm/amd/amdgpu/Makefile          |   2 +-
 drivers/gpu/drm/amd/amdgpu/amdgpu.h          |   8 +
 drivers/gpu/drm/amd/amdgpu/amdgpu_device.c   |   3 +
 drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c      |  16 +
 drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c      |  11 +
 drivers/gpu/drm/amd/amdgpu/amdgpu_profiler.c | 303 +++++++++++++++++++
 drivers/gpu/drm/amd/amdgpu/amdgpu_profiler.h |  92 ++++++
 drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c      |  98 ++++++
 drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h      |  43 +++
 9 files changed, 575 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_profiler.c
 create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_profiler.h
 create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c
 create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h

diff --git a/drivers/gpu/drm/amd/amdgpu/Makefile 
b/drivers/gpu/drm/amd/amdgpu/Makefile
index ec199715ee76..b17d8f7b4e8f 100644
--- a/drivers/gpu/drm/amd/amdgpu/Makefile
+++ b/drivers/gpu/drm/amd/amdgpu/Makefile
@@ -72,7 +72,7 @@ amdgpu-y += amdgpu_device.o amdgpu_reg_access.o 
amdgpu_doorbell_mgr.o amdgpu_kms
        amdgpu_eeprom.o amdgpu_mca.o amdgpu_psp_ta.o amdgpu_lsdma.o 
amdgpu_lockdep.o \
        amdgpu_ring_mux.o amdgpu_xcp.o amdgpu_seq64.o amdgpu_dev_coredump.o \
        amdgpu_cper.o amdgpu_userq_fence.o amdgpu_eviction_fence.o amdgpu_ip.o \
-       amdgpu_wb.o amdgpu_trap.o amdgpu_events.o
+       amdgpu_wb.o amdgpu_trap.o amdgpu_events.o amdgpu_profiler.o amdgpu_spm.o
 
 amdgpu-$(CONFIG_PROC_FS) += amdgpu_fdinfo.o
 
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h 
b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
index e3763c197138..244215320916 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
@@ -110,6 +110,7 @@
 #include "amdgpu_seq64.h"
 #include "amdgpu_reg_state.h"
 #include "amdgpu_userq.h"
+#include "amdgpu_profiler.h"
 #include "amdgpu_eviction_fence.h"
 #include "amdgpu_wb.h"
 #include "amdgpu_ip.h"
@@ -424,6 +425,11 @@ struct amdgpu_fpriv {
 
        /** GPU partition selection */
        uint32_t                xcp_id;
+       /** Profiler XCP manager this fd holds a reference on, latched by
+        *  amdgpu_profiler_mgr_init(). NULL if no reference was taken, which
+        *  gates the matching release and the profiler ioctl.
+        */
+       struct amdgpu_profiler_xcp_mgr  *prof_xcp_mgr;
 };
 
 int amdgpu_file_to_fpriv(struct file *filp, struct amdgpu_fpriv **fpriv);
@@ -960,6 +966,8 @@ struct amdgpu_device {
 
        struct amdgpu_uma_carveout_info uma_info;
 
+       struct amdgpu_profiler_mgr      prof_mgr;
+
        /* KFD
         * Must be last --ends in a flexible-array member.
         */
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
index 380989f4c965..a4703bc28359 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
@@ -3686,6 +3686,7 @@ int amdgpu_device_init(struct amdgpu_device *adev,
        mutex_init(&adev->gfx.userq_sch_mutex);
        mutex_init(&adev->gfx.workload_profile_mutex);
        mutex_init(&adev->vcn.workload_profile_mutex);
+       amdgpu_profiler_device_init(adev);
 
        spin_lock_init(&adev->irq.lock);
 
@@ -4244,6 +4245,8 @@ void amdgpu_device_fini_sw(struct amdgpu_device *adev)
        kfree(adev->pci_state);
        kfree(adev->pcie_reset_ctx.swds_pcistate);
        kfree(adev->pcie_reset_ctx.swus_pcistate);
+
+       amdgpu_profiler_device_sw_fini(adev);
 }
 
 /**
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
index a93ee106ab92..03bbf101bf23 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
@@ -2556,9 +2556,25 @@ amdgpu_pci_remove(struct pci_dev *pdev)
        struct amdgpu_device *adev = drm_to_adev(dev);
 
        amdgpu_ras_eeprom_check_and_recover(adev);
+       /*
+        * Signal profiler teardown and wake any blocked SPM waiters so their
+        * ioctls can exit. This must run before drm_dev_unplug(dev) below,
+        * which is what drains in-flight profiler ioctls via SRCU: a profiler
+        * ioctl blocked in a client-supplied SPM wait would otherwise stall
+        * that unplug until the client's timeout expired. (XCP partition nodes
+        * redirect their ioctls to the primary drm_device in
+        * amdgpu_xcp_dev_alloc(), so they register on the primary device's SRCU
+        * and amdgpu_xcp_dev_unplug() itself never waits on a profiler ioctl.)
+        */
+       amdgpu_profiler_device_prepare_fini(adev);
        amdgpu_xcp_dev_unplug(adev);
        amdgpu_gmc_prepare_nps_mode_change(adev);
        drm_dev_unplug(dev);
+       /*
+        * Release profiler/SPM state now that drm_dev_unplug() guarantees no
+        * ioctl is in flight. HW is still live (before 
amdgpu_driver_unload_kms()).
+        */
+       amdgpu_profiler_device_fini(adev);
 
        if (adev->pm.rpm_mode != AMDGPU_RUNPM_NONE) {
                pm_runtime_get_sync(dev->dev);
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
index 47e4680db797..57fa69c0382d 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
@@ -1647,6 +1647,10 @@ int amdgpu_driver_open_kms(struct drm_device *dev, 
struct drm_file *file_priv)
                        dev_dbg(adev->dev, "trap handler not enabled");
        }
 
+       /* Profiler is optional - don't fail open if unavailable */
+       if (amdgpu_profiler_mgr_init(adev, fpriv))
+               dev_dbg(adev->dev, "Can't setup profiler, continuing without 
it\n");
+
        amdgpu_ctx_mgr_init(&fpriv->ctx_mgr, adev);
 
        file_priv->driver_priv = fpriv;
@@ -1693,6 +1697,13 @@ void amdgpu_driver_postclose_kms(struct drm_device *dev,
 
        pm_runtime_get_sync(dev->dev);
 
+       /* Drop the profiler reference here rather than in ->release: postclose
+        * also runs for in-kernel drm_client teardown (drm_file_free), and
+        * before kfree(fpriv) below. Placed after pm_runtime_get_sync() so a
+        * later SPM release can touch registers on a resumed device.
+        */
+       amdgpu_profiler_mgr_fini(file_priv);
+
        if (amdgpu_device_ip_get_ip_block(adev, AMD_IP_BLOCK_TYPE_UVD) != NULL)
                amdgpu_uvd_free_handles(adev, file_priv);
        if (amdgpu_device_ip_get_ip_block(adev, AMD_IP_BLOCK_TYPE_VCE) != NULL)
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_profiler.c 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_profiler.c
new file mode 100644
index 000000000000..da322d2e6431
--- /dev/null
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_profiler.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <drm/drm_drv.h>
+
+#include "amdgpu.h"
+
+/*
+ * Profiler revision change log
+ *
+ * 1.0 - Initial revision
+ */
+
+#define AMDGPU_PROFILER_VERSION_MAJOR 1
+#define AMDGPU_PROFILER_VERSION_MINOR 0
+
+int amdgpu_profiler_ioctl(struct drm_device *dev,
+                         void *data,
+                         struct drm_file *filp)
+{
+       struct amdgpu_device *adev = drm_to_adev(dev);
+       struct amdgpu_fpriv *fpriv = filp->driver_priv;
+       struct amdgpu_profiler_mgr *prof_mgr = &adev->prof_mgr;
+       struct drm_amdgpu_profiler_args *args = data;
+       struct amdgpu_profiler_xcp_mgr *prof_xcp_mgr = fpriv->prof_xcp_mgr;
+       int ret = 0, idx;
+
+       if (args->pad)
+               return -EINVAL;
+
+       /* Reject clients that never took a profiler reference, i.e. never
+        * successfully called amdgpu_profiler_mgr_init(). Without this an fd
+        * that never initialized could ride on another client's XCP-0
+        * reference and reach amdgpu_spm_ioctl().
+        */
+       if (!prof_xcp_mgr)
+               return -EACCES;
+
+       /* Prevent ioctl from racing with device removal. drm_dev_enter()
+        * fails once drm_dev_unplug() has been called, and drm_dev_unplug()
+        * blocks until every in-flight drm_dev_enter() section (this ioctl
+        * included) has exited. The torn_down flag, set before unplug, keeps
+        * new ioctls from touching resources that are about to be released.
+        */
+       if (!drm_dev_enter(dev, &idx))
+               return -ENODEV;
+
+       mutex_lock(&prof_mgr->mutex);
+       if (!prof_xcp_mgr->refcount || prof_xcp_mgr->torn_down) {
+               dev_dbg(adev->dev, "Profiler IOCTL op %u on xcp %u %s\n",
+                       args->op, prof_xcp_mgr->xcp_id,
+                       prof_xcp_mgr->torn_down ? "torn down" : "not 
initialized");
+               mutex_unlock(&prof_mgr->mutex);
+               ret = -ENODEV;
+               goto exit;
+       }
+       /*
+        * Dispatch without prof_mgr->mutex: holding it here would let a
+        * client-supplied SPM wait in amdgpu_spm_ioctl() stall every other
+        * profiler fd. The fd's own reference keeps refcount >= 1 so a
+        * concurrent close (amdgpu_profiler_mgr_fini) cannot release the
+        * manager (and its spm_mgr) under us; device teardown is instead
+        * excluded by the drm_dev_enter() section that drm_dev_unplug() drains.
+        * The reference does not serialize spm_mgr mutation against
+        * amdgpu_spm_mgr_release_owner() on another fd's close, so the SPM
+        * layer serializes that itself by taking prof_xcp_mgr->mutex around
+        * spm_mgr->file on both the acquire and the release/close paths.
+        */
+       mutex_unlock(&prof_mgr->mutex);
+
+       switch (args->op) {
+       case AMDGPU_PROFILER_OP_GET_VER:
+               if (memchr_inv(&args->data, 0, sizeof(args->data))) {
+                       ret = -EINVAL;
+               } else {
+                       args->data.version = 
((u32)AMDGPU_PROFILER_VERSION_MAJOR << 24) |
+                                            (AMDGPU_PROFILER_VERSION_MINOR & 
GENMASK(23, 0));
+                       ret = 0;
+               }
+               break;
+
+       case AMDGPU_PROFILER_OP_SPM:
+               ret = amdgpu_spm_ioctl(dev, &args->data.spm, filp);
+               break;
+
+       default:
+               dev_dbg(adev->dev, "Invalid profiler op: %u\n", args->op);
+               ret = -EINVAL;
+               break;
+       }
+
+exit:
+       drm_dev_exit(idx);
+       return ret;
+}
+
+void amdgpu_profiler_device_init(struct amdgpu_device *adev)
+{
+       struct amdgpu_profiler_mgr *prof_mgr = &adev->prof_mgr;
+       struct amdgpu_profiler_xcp_mgr *prof_xcp_mgr;
+       int xcp_id;
+
+       mutex_init(&prof_mgr->mutex);
+
+       /* Initialize adev, xcp_id, and the per-XCP mutex for all XCP managers
+        * at device init. adev and xcp_id remain constant for the device
+        * lifetime, eliminating the need for mutex protection when reading
+        * them. Both these mutexes and prof_mgr->mutex are only destroyed by
+        * amdgpu_profiler_device_sw_fini(), which runs after the last drm_file
+        * is gone, so teardown paths (amdgpu_spm_mgr_release_owner() and
+        * amdgpu_spm_mgr_fini()) can take them regardless of the fd refcount.
+        */
+       for (xcp_id = 0; xcp_id < MAX_XCP; xcp_id++) {
+               prof_xcp_mgr = &prof_mgr->prof_xcp_mgr[xcp_id];
+               prof_xcp_mgr->adev = adev;
+               prof_xcp_mgr->xcp_id = xcp_id;
+               mutex_init(&prof_xcp_mgr->mutex);
+       }
+}
+
+void amdgpu_profiler_device_sw_fini(struct amdgpu_device *adev)
+{
+       struct amdgpu_profiler_mgr *prof_mgr = &adev->prof_mgr;
+       int xcp_id;
+
+       for (xcp_id = 0; xcp_id < MAX_XCP; xcp_id++)
+               mutex_destroy(&prof_mgr->prof_xcp_mgr[xcp_id].mutex);
+
+       mutex_destroy(&prof_mgr->mutex);
+}
+
+int amdgpu_profiler_mgr_init(struct amdgpu_device *adev,
+                            struct amdgpu_fpriv *fpriv)
+{
+       struct amdgpu_profiler_mgr *prof_mgr = &adev->prof_mgr;
+       struct amdgpu_profiler_xcp_mgr *prof_xcp_mgr;
+       u32 xcp_id;
+
+       /* On a partitioned device, reject clients with no partition
+        * assigned (AMDGPU_XCP_NO_PARTITION): amdgpu_prof_xcp_id() maps
+        * them to XCP 0, which would expose partition 0's performance
+        * counters as a cross-partition side channel. Enforce isolation
+        * here, since amdgpu_xcp_get_xcc_mask() returns the full device
+        * mask for NO_PARTITION.
+        */
+       if (fpriv->xcp_id == AMDGPU_XCP_NO_PARTITION &&
+           adev->xcp_mgr && adev->xcp_mgr->mode != AMDGPU_XCP_MODE_NONE)
+               return -EINVAL;
+
+       /* fpriv->xcp_id is mutable (rewritten by amdgpu_xcp_select_scheds on
+        * first submit), so the resolved manager is latched into
+        * fpriv->prof_xcp_mgr below and close drops the refcount on that same
+        * XCP.
+        */
+       xcp_id = amdgpu_prof_xcp_id(fpriv->xcp_id);
+       if (xcp_id >= MAX_XCP || !adev->ddev.render)
+               return -ENODEV;
+
+       mutex_lock(&prof_mgr->mutex);
+
+       prof_xcp_mgr = &prof_mgr->prof_xcp_mgr[xcp_id];
+
+       /* Prevent initialization during device teardown */
+       if (prof_xcp_mgr->torn_down) {
+               mutex_unlock(&prof_mgr->mutex);
+               return -ENODEV;
+       }
+
+       if (!prof_xcp_mgr->refcount) {
+               /* First open - initialize everything */
+               prof_xcp_mgr->refcount = 1;
+               dev_dbg(adev->dev, "Initialize profiler on render %u\n",
+                       adev->ddev.render->index);
+               amdgpu_spm_mgr_init(&prof_xcp_mgr->spm_mgr);
+       } else {
+               /* Subsequent opens - just increment refcount */
+               prof_xcp_mgr->refcount++;
+       }
+
+       /* Latch the reference. Only set on the success path, so release and
+        * the profiler ioctl act only for clients that actually took one.
+        */
+       fpriv->prof_xcp_mgr = prof_xcp_mgr;
+
+       mutex_unlock(&prof_mgr->mutex);
+       return 0;
+}
+
+static void amdgpu_profiler_xcp_mgr_release(struct amdgpu_profiler_xcp_mgr 
*prof_xcp_mgr)
+{
+       amdgpu_spm_mgr_fini(&prof_xcp_mgr->spm_mgr);
+}
+
+void amdgpu_profiler_mgr_fini(struct drm_file *filp)
+{
+       struct amdgpu_fpriv *fpriv = filp->driver_priv;
+       struct amdgpu_profiler_xcp_mgr *prof_xcp_mgr = fpriv->prof_xcp_mgr;
+       struct amdgpu_profiler_mgr *prof_mgr;
+
+       /* Only clients that successfully took a reference may drop one. fpriv
+        * is kzalloc'd, so a client that failed or never called mgr_init has
+        * this NULL and must not decrement any refcount.
+        */
+       if (!prof_xcp_mgr)
+               return;
+
+       /* prof_xcp_mgr->adev is set at device init: after unplug
+        * filp->minor->dev may point at a partition drm_device, so
+        * drm_to_adev() would be bogus here.
+        */
+       prof_mgr = &prof_xcp_mgr->adev->prof_mgr;
+
+       /* Release outside prof_mgr->mutex: the release path can block on
+        * prof_xcp_mgr->mutex during another fd's acquire, and holding
+        * prof_mgr->mutex there would stall all profiler ioctls. No-op unless
+        * this fd owns the session.
+        */
+       amdgpu_spm_mgr_release_owner(&prof_xcp_mgr->spm_mgr, filp);
+
+       mutex_lock(&prof_mgr->mutex);
+       /* Skip if device_fini already tore down this XCP manager */
+       if (!prof_xcp_mgr->torn_down) {
+               if (!WARN_ON(!prof_xcp_mgr->refcount) &&
+                   --prof_xcp_mgr->refcount == 0)
+                       amdgpu_profiler_xcp_mgr_release(prof_xcp_mgr);
+       }
+       mutex_unlock(&prof_mgr->mutex);
+}
+
+void amdgpu_profiler_device_prepare_fini(struct amdgpu_device *adev)
+{
+       struct amdgpu_profiler_mgr *prof_mgr = &adev->prof_mgr;
+       struct amdgpu_profiler_xcp_mgr *prof_xcp_mgr;
+       int xcp_id;
+
+       /*
+        * First stage of PCI-removal teardown, run *before* drm_dev_unplug().
+        *
+        * Mark every XCP torn down so no new ioctl proceeds. teardown_cb
+        * provides the hook for waking a thread blocked in an SPM wait so its
+        * ioctl can return promptly, but no callback is registered until a
+        * later patch wires it up; until then this stage only sets torn_down.
+        * drm_dev_unplug() then drains all in-flight profiler ioctls via SRCU
+        * without stalling on a client-supplied SPM timeout. The actual
+        * resource release happens afterwards in amdgpu_profiler_device_fini().
+        */
+       mutex_lock(&prof_mgr->mutex);
+       for (xcp_id = 0; xcp_id < MAX_XCP; xcp_id++)
+               prof_mgr->prof_xcp_mgr[xcp_id].torn_down = true;
+
+       for (xcp_id = 0; xcp_id < MAX_XCP; xcp_id++) {
+               prof_xcp_mgr = &prof_mgr->prof_xcp_mgr[xcp_id];
+               if (prof_xcp_mgr->refcount > 0 && prof_xcp_mgr->teardown_cb)
+                       prof_xcp_mgr->teardown_cb(prof_xcp_mgr);
+       }
+       mutex_unlock(&prof_mgr->mutex);
+}
+
+void amdgpu_profiler_device_fini(struct amdgpu_device *adev)
+{
+       struct amdgpu_profiler_mgr *prof_mgr = &adev->prof_mgr;
+       struct amdgpu_profiler_xcp_mgr *prof_xcp_mgr;
+       int xcp_id;
+
+       /*
+        * Second stage of PCI-removal teardown, run *after* drm_dev_unplug().
+        *
+        * drm_dev_unplug() has already drained every in-flight profiler ioctl
+        * via SRCU, so nothing can be running here and no active-ioctl drain is
+        * needed. torn_down was set in amdgpu_profiler_device_prepare_fini().
+        * Release the HW/SPM resources of any XCP that still holds a session;
+        * the device is still live (this runs before 
amdgpu_driver_unload_kms()).
+        */
+       mutex_lock(&prof_mgr->mutex);
+       for (xcp_id = 0; xcp_id < MAX_XCP; xcp_id++) {
+               prof_xcp_mgr = &prof_mgr->prof_xcp_mgr[xcp_id];
+               if (prof_xcp_mgr->refcount > 0) {
+                       amdgpu_profiler_xcp_mgr_release(prof_xcp_mgr);
+                       prof_xcp_mgr->refcount = 0;
+               }
+       }
+       mutex_unlock(&prof_mgr->mutex);
+}
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_profiler.h 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_profiler.h
new file mode 100644
index 000000000000..a220f05c9512
--- /dev/null
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_profiler.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+/*
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#ifndef AMDGPU_PROFILER_H_
+#define AMDGPU_PROFILER_H_
+
+#include <linux/mutex.h>
+
+#include "amdgpu_xcp.h"
+#include "amdgpu_spm.h"
+
+/* Map XCP ID to profiler array index.
+ * AMDGPU_XCP_NO_PARTITION maps to 0. On a partitioned device the SPM
+ * consumer rejects NO_PARTITION before this mapping (see
+ * amdgpu_profiler_mgr_init()), so mapping to 0 only applies to
+ * non-partitioned systems, which legitimately use XCP 0.
+ */
+static inline u32 amdgpu_prof_xcp_id(u32 xcp_id)
+{
+       return xcp_id == AMDGPU_XCP_NO_PARTITION ? 0 : xcp_id;
+}
+
+/*
+ * y is the member name within amdgpu_profiler_xcp_mgr (e.g., spm_mgr)
+ */
+#define to_prof_xcp_mgr(x, y) \
+       container_of(x, struct amdgpu_profiler_xcp_mgr, y)
+
+struct amdgpu_profiler_xcp_mgr {
+       /* track multiple file opens (protected by prof_mgr->mutex) */
+       unsigned int                   refcount;
+       /* back-pointer to device */
+       struct amdgpu_device           *adev;
+       /* protect xcp_mgr shared resources */
+       struct mutex                   mutex;
+       u32                            xcp_id;
+       /* device removal in progress */
+       bool                           torn_down;
+       /* Called during device teardown (before drm_dev_unplug()) to wake
+        * threads blocked in SPM waits so their ioctls can exit.
+        * IMPORTANT: Invoked while holding prof_mgr->mutex.
+        * Callback MUST NOT attempt to acquire prof_mgr->mutex to avoid 
deadlock.
+        */
+       void (*teardown_cb)(struct amdgpu_profiler_xcp_mgr *prof_xcp_mgr);
+
+       /*
+        * Union scaffolding for future profiler sub-managers (PC, SPM2, etc.).
+        * Currently only SPM, but designed for extensibility.
+        */
+       union {
+               struct amdgpu_spm_mgr      spm_mgr;
+       };
+};
+
+struct amdgpu_profiler_mgr {
+       /* protect profiler manager share resource */
+       struct mutex                   mutex;
+       struct amdgpu_profiler_xcp_mgr prof_xcp_mgr[MAX_XCP];
+};
+
+int amdgpu_profiler_ioctl(struct drm_device *dev, void *data,
+                         struct drm_file *filp);
+void amdgpu_profiler_device_init(struct amdgpu_device *adev);
+void amdgpu_profiler_device_sw_fini(struct amdgpu_device *adev);
+int amdgpu_profiler_mgr_init(struct amdgpu_device *adev,
+                            struct amdgpu_fpriv *fpriv);
+void amdgpu_profiler_mgr_fini(struct drm_file *filp);
+void amdgpu_profiler_device_prepare_fini(struct amdgpu_device *adev);
+void amdgpu_profiler_device_fini(struct amdgpu_device *adev);
+
+#endif /* AMDGPU_PROFILER_H_ */
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c
new file mode 100644
index 000000000000..06747dd22b85
--- /dev/null
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "amdgpu.h"
+
+/* Check if the calling file owns the SPM session */
+static bool amdgpu_spm_is_owner(struct amdgpu_spm_mgr *spm_mgr, struct 
drm_file *filp)
+{
+       return spm_mgr->file == filp;
+}
+
+int amdgpu_spm_ioctl(struct drm_device *dev, struct drm_amdgpu_spm_args *args,
+                    struct drm_file *filp)
+{
+       if (memchr_inv(args->reserved, 0, sizeof(args->reserved)))
+               return -EINVAL;
+
+       switch (args->op) {
+       case AMDGPU_SPM_OP_ACQUIRE:
+               /* Implemented in a later patch */
+               return -EOPNOTSUPP;
+
+       case AMDGPU_SPM_OP_RELEASE:
+               /* Implemented in a later patch */
+               return -EOPNOTSUPP;
+
+       case AMDGPU_SPM_OP_SET_DEST_BUF:
+               /* Implemented in a later patch */
+               return -EOPNOTSUPP;
+
+       default:
+               return -EINVAL;
+       }
+}
+
+static void amdgpu_spm_init_device(struct amdgpu_spm_mgr *spm_mgr)
+{
+       /* TODO */
+}
+
+static int amdgpu_spm_release(struct amdgpu_spm_mgr *spm_mgr, struct drm_file 
*filp)
+{
+       struct amdgpu_profiler_xcp_mgr *prof_xcp_mgr = to_prof_xcp_mgr(spm_mgr, 
spm_mgr);
+
+       /* Full teardown lands with AMDGPU_SPM_OP_RELEASE. Re-check ownership
+        * under the mutex so a close and a concurrent device teardown cannot
+        * both release the same session.
+        */
+       mutex_lock(&prof_xcp_mgr->mutex);
+       if (!spm_mgr->file) {
+               mutex_unlock(&prof_xcp_mgr->mutex);
+               return 0;
+       }
+       if (filp && !amdgpu_spm_is_owner(spm_mgr, filp)) {
+               mutex_unlock(&prof_xcp_mgr->mutex);
+               return -EPERM;
+       }
+       spm_mgr->file = NULL;
+       mutex_unlock(&prof_xcp_mgr->mutex);
+
+       return 0;
+}
+
+void amdgpu_spm_mgr_init(struct amdgpu_spm_mgr *spm_mgr)
+{
+       amdgpu_spm_init_device(spm_mgr);
+}
+
+void amdgpu_spm_mgr_release_owner(struct amdgpu_spm_mgr *spm_mgr,
+                                 struct drm_file *filp)
+{
+       amdgpu_spm_release(spm_mgr, filp);
+}
+
+void amdgpu_spm_mgr_fini(struct amdgpu_spm_mgr *spm_mgr)
+{
+       amdgpu_spm_release(spm_mgr, NULL);
+}
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h
new file mode 100644
index 000000000000..59fa641ebb86
--- /dev/null
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+/*
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AMDGPU_SPM_H_
+#define AMDGPU_SPM_H_
+
+struct drm_device;
+struct drm_file;
+struct drm_amdgpu_spm_args;
+
+struct amdgpu_spm_mgr {
+       struct drm_file *file;
+};
+
+int amdgpu_spm_ioctl(struct drm_device *dev, struct drm_amdgpu_spm_args *args,
+                    struct drm_file *filp);
+
+void amdgpu_spm_mgr_init(struct amdgpu_spm_mgr *spm_mgr);
+void amdgpu_spm_mgr_fini(struct amdgpu_spm_mgr *spm_mgr);
+void amdgpu_spm_mgr_release_owner(struct amdgpu_spm_mgr *spm_mgr,
+                                 struct drm_file *filp);
+
+#endif
-- 
2.34.1

Reply via email to