Implement AMDGPU_SPM_OP_ACQUIRE to allow userspace to request exclusive access to the SPM hardware. Only one drm_file can own SPM at a time; a second acquire returns -EBUSY.
On acquire the driver does more than record ownership: - allocates the per-XCP SPM counter state (amdgpu_spm_cntr) and records the owning drm_file and the calling thread group leader, pinned via get_task_struct(). The matching put_task_struct() runs on this patch's acquire-failure path; - for every XCC instance in the XCP's xcc_mask, allocates a 4 MB GTT ring buffer BO (amdgpu_bo_create_kernel(), AMDGPU_GEM_DOMAIN_GTT) for the SPM data stream, reserving the trailing 32 bytes as a metadata area (usable ring_size = 4 MB - 0x20); - calls amdgpu_rlc_spm_acquire() per instance, which allocates a reserved VMID (amdgpu_vmid_alloc_reserved()), programs the SPM VMID into the RLC and programs the perfmon ring registers (ring base, size and read pointer) via KIQ. On failure at any instance the acquire path invokes the per-instance release helper for every instance in xcc_mask, drops the task reference and frees the counter state. _amdgpu_spm_release() is only a stub in this patch, so the per-instance hardware resources (ring BOs, reserved VMID and RLC perfmon programming) are not torn down on this error path yet; only the task reference and the amdgpu_spm_cntr allocation are cleaned up here. A new per-XCP mutex is added to struct amdgpu_profiler_xcp_mgr to serialize acquire/release against the shared XCP SPM resources. It is initialized once for the device lifetime in amdgpu_profiler_device_init() (alongside adev/xcp_id) so it stays valid for teardown paths such as amdgpu_spm_mgr_release_owner() and amdgpu_spm_mgr_fini(). The existing spm_irq_lock spinlock continues to guard spm_cntr publication against the interrupt handler. AMDGPU_SPM_OP_RELEASE and full teardown of these resources land in a follow-up patch. Signed-off-by: James Zhu <[email protected]> Reviewed-by: Bing Ma <[email protected]> --- drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c | 151 +++++++++++++++++++++++- drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h | 28 +++++ 2 files changed, 177 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c index 06747dd22b85..36a2d2d1f0ed 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c @@ -23,22 +23,36 @@ #include "amdgpu.h" +#define AMDGPU_SPM_RING_SIZE (4 * 1024 * 1024) +#define AMDGPU_SPM_META_SIZE 0x20 + +static int amdgpu_spm_acquire(struct amdgpu_spm_mgr *spm_mgr, struct drm_file *filp); +static void _amdgpu_spm_release(struct amdgpu_spm_mgr *spm_mgr, int inst, struct drm_file *filp); + /* 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; } +static void amdgpu_spm_work(struct work_struct *work) +{ + /* TODO: dump spm ring buffer to user buffer */ +} + int amdgpu_spm_ioctl(struct drm_device *dev, struct drm_amdgpu_spm_args *args, struct drm_file *filp) { + struct amdgpu_fpriv *fpriv = filp->driver_priv; + struct amdgpu_device *adev = drm_to_adev(dev); + struct amdgpu_spm_mgr *spm_mgr = &fpriv->prof_xcp_mgr->spm_mgr; + 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; + return amdgpu_spm_acquire(spm_mgr, filp); case AMDGPU_SPM_OP_RELEASE: /* Implemented in a later patch */ @@ -49,11 +63,144 @@ int amdgpu_spm_ioctl(struct drm_device *dev, struct drm_amdgpu_spm_args *args, return -EOPNOTSUPP; default: + dev_dbg(adev->dev, "Invalid option: %u\n", args->op); return -EINVAL; } } static void amdgpu_spm_init_device(struct amdgpu_spm_mgr *spm_mgr) +{ + spm_mgr->spm_cntr = NULL; + spin_lock_init(&spm_mgr->spm_irq_lock); + /* spm_work lives for the device lifetime; initialize it once here so + * it is ready before any interrupt can schedule it. + */ + INIT_WORK(&spm_mgr->spm_work, amdgpu_spm_work); +} + +static int _amdgpu_spm_acquire(struct amdgpu_spm_mgr *spm_mgr, int inst, struct drm_file *filp) +{ + struct amdgpu_profiler_xcp_mgr *prof_xcp_mgr = to_prof_xcp_mgr(spm_mgr, spm_mgr); + struct amdgpu_device *adev = prof_xcp_mgr->adev; + struct amdgpu_spm_base *spm; + int ret = 0; + + if (WARN_ON_ONCE(inst >= MAX_XCC)) + return -EINVAL; + + spm = &spm_mgr->spm_cntr->spm[inst]; + + /* allocate 4M spm ring buffer */ + spm->ring_size = AMDGPU_SPM_RING_SIZE; + + ret = amdgpu_bo_create_kernel(adev, spm->ring_size, PAGE_SIZE, + AMDGPU_GEM_DOMAIN_GTT, &spm->spm_obj, + &spm->gpu_addr, &spm->cpu_addr); + + if (ret) { + memset(spm, 0, sizeof(*spm)); + goto out; + } + + ret = amdgpu_rlc_spm_acquire(adev, inst, drm_priv_to_vm(filp), + spm->gpu_addr, spm->ring_size); + if (ret) + goto rlc_spm_acquire_failure; + + /* + * The first 8 DWs of the buffer are the Meta data area (RLC writes + * the wptr there); the ring itself starts at AMDGPU_SPM_META_SIZE. + */ + spm->ring_size -= AMDGPU_SPM_META_SIZE; + + goto out; + +rlc_spm_acquire_failure: + amdgpu_bo_free_kernel(&spm->spm_obj, &spm->gpu_addr, &spm->cpu_addr); + memset(spm, 0, sizeof(*spm)); +out: + return ret; +} + +static int amdgpu_spm_acquire(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); + struct amdgpu_device *adev = prof_xcp_mgr->adev; + u32 xcp_id = prof_xcp_mgr->xcp_id; + struct amdgpu_spm_cntr *spm_cntr; + unsigned long flags; + u32 xcc_mask; + int ret = 0; + int inst; + + mutex_lock(&prof_xcp_mgr->mutex); + + /* spm_mgr->file is the single source of truth for the busy check: it is + * assigned last on the acquire success path and cleared on teardown. + */ + if (spm_mgr->file) { + ret = -EBUSY; + goto out; + } + + ret = amdgpu_xcp_get_xcc_mask(adev, xcp_id, &xcc_mask); + if (ret) + goto out; + + spm_cntr = kzalloc(sizeof(*spm_cntr), GFP_KERNEL); + if (!spm_cntr) { + ret = -ENOMEM; + goto out; + } + get_task_struct(current->group_leader); + spm_mgr->lead_thread = current->group_leader; + + /* Initialize the per-session worker mutex before the acquire loop so it + * is ready before spm_cntr is published. spm_work is initialized once in + * amdgpu_spm_init_device(). + */ + mutex_init(&spm_cntr->spm_worker_mutex); + spm_cntr->xcc_mask = xcc_mask; + + spin_lock_irqsave(&spm_mgr->spm_irq_lock, flags); + spm_mgr->spm_cntr = spm_cntr; + spin_unlock_irqrestore(&spm_mgr->spm_irq_lock, flags); + + for_each_inst(inst, xcc_mask) { + ret = _amdgpu_spm_acquire(spm_mgr, inst, filp); + if (ret) + goto acquire_spm_failure; + } + + spm_mgr->file = filp; + + goto out; + +acquire_spm_failure: + /* Iterate all instances in xcc_mask; _amdgpu_spm_release uses ring_size + * as a sentinel to skip instances that were never acquired. + */ + for_each_inst(inst, xcc_mask) + _amdgpu_spm_release(spm_mgr, inst, filp); + /* Drain any pending work before dropping lead_thread and clearing + * spm_cntr. Nothing can schedule the work during acquire, so this is + * defensive ordering. + */ + cancel_work_sync(&spm_mgr->spm_work); + put_task_struct(spm_mgr->lead_thread); + spm_mgr->lead_thread = NULL; + spin_lock_irqsave(&spm_mgr->spm_irq_lock, flags); + spm_mgr->spm_cntr = NULL; + spin_unlock_irqrestore(&spm_mgr->spm_irq_lock, flags); + mutex_destroy(&spm_cntr->spm_worker_mutex); + kfree(spm_cntr); + +out: + mutex_unlock(&prof_xcp_mgr->mutex); + return ret; +} + +static void _amdgpu_spm_release(struct amdgpu_spm_mgr *spm_mgr, int inst, struct drm_file *filp) { /* TODO */ } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h index 59fa641ebb86..d59c3d93a64d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h @@ -27,9 +27,37 @@ struct drm_device; struct drm_file; struct drm_amdgpu_spm_args; +struct amdgpu_bo; + +struct amdgpu_spm_base { + u64 gpu_addr; + u32 ring_size; + void *cpu_addr; /* Metadata area (first u32 = wptr) + ring data (u64 array) */ + struct amdgpu_bo *spm_obj; +}; + +struct amdgpu_spm_cntr { + /* Indexed by XCC instance id (from for_each_inst or xcc_id parameter). + * Each spm_mgr/spm_cntr is per-XCP partition, and spm[] tracks each + * XCC instance within that partition. Array sized MAX_XCC to handle + * the maximum possible XCC instance ID. + */ + struct amdgpu_spm_base spm[MAX_XCC]; + u32 xcc_mask; /* Cached from acquire; avoids error handling in release */ + /* Protects dest-buffer state and worker operations */ + struct mutex spm_worker_mutex; +}; struct amdgpu_spm_mgr { struct drm_file *file; + + struct task_struct *lead_thread; + + /* spm data */ + struct amdgpu_spm_cntr *spm_cntr; + struct work_struct spm_work; + /* Protects spm_cntr publication and the SPM state read in the IRQ path */ + spinlock_t spm_irq_lock; }; int amdgpu_spm_ioctl(struct drm_device *dev, struct drm_amdgpu_spm_args *args, -- 2.34.1
