Implement a kernel thread for polling SPM ring buffer on pre-GFX12 hardware that lacks IRQ support for profiler events.
The thread runs at 100us intervals to prevent ring buffer overflow on hardware without watermark interrupts. When the buffer exceeds the 50% watermark threshold, the thread schedules drain work via schedule_work(). The 100us polling cadence ensures the 4MB ring buffer cannot overflow at typical SPM data rates while limiting the thread to ~10k wakeups/s per active render node. The thread is created on the first _spm_start() and stopped from _spm_stop() with kthread_stop_put(). The creator holds a task_struct reference for the thread's lifetime, because the thread can still return early if amdgpu_xcp_get_xcc_mask() fails. Signed-off-by: James Zhu <[email protected]> Reviewed-by: Bing Ma <[email protected]> --- drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c | 149 +++++++++++++++++++++++- drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h | 2 + 2 files changed, 150 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c index 532d1bc34f89..1b10618dde08 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.c @@ -21,6 +21,7 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include <linux/delay.h> #include <linux/kthread.h> #include <linux/sched/mm.h> #include <linux/sched/task.h> @@ -54,6 +55,105 @@ static bool amdgpu_spm_is_owner(struct amdgpu_spm_mgr *spm_mgr, struct drm_file return spm_mgr->file == filp; } +static int amdgpu_spm_monitor_thread(void *param) +{ + struct amdgpu_spm_mgr *spm_mgr = param; + 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; + u32 xcc_mask; + int ret; + + ret = amdgpu_xcp_get_xcc_mask(adev, xcp_id, &xcc_mask); + if (ret) + return 0; + + /* Snapshot spm_cntr once per iteration: kthread_stop() (synchronous) + * joins this thread before release clears and frees spm_cntr. + */ + while (!kthread_should_stop()) { + struct amdgpu_spm_cntr *spm_cntr = READ_ONCE(spm_mgr->spm_cntr); + bool need_schedule = false; + u32 inst; + + if (!spm_cntr) + break; + + /* Pre-GFX12 lacks watermark interrupts, poll every 100us to prevent overflow */ + usleep_range(100, 110); + + if (!mutex_trylock(&spm_cntr->spm_worker_mutex)) + continue; + + for_each_inst(inst, xcc_mask) { + struct amdgpu_spm_base *spm = &spm_cntr->spm[inst]; + u32 last_drain_rptr; + u32 ring_size; + u32 ring_rptr; + u32 ring_wptr; + bool above_watermark; + + if (!spm->is_spm_started || !spm->ring_size) + continue; + + ring_size = spm->ring_size; + ring_rptr = READ_ONCE(spm->ring_rptr); + last_drain_rptr = READ_ONCE(spm->last_drain_rptr); + ring_wptr = READ_ONCE(((u32 *)spm->cpu_addr)[0]); + + if (ring_wptr >= ring_size) + continue; + + /* Evaluate watermark condition per-instance independently */ + above_watermark = (ring_rptr != last_drain_rptr && + (ring_size + ring_wptr - ring_rptr) % ring_size > + (ring_size >> 1)); + + if (above_watermark) { + WRITE_ONCE(spm->last_drain_rptr, ring_rptr); + dev_dbg(adev->dev, + "[SPM#%d] soft interrupt rptr:0x%08x--wptr:0x%08x\n", + inst, ring_rptr, ring_wptr); + need_schedule = true; + } else if (need_schedule) { + /* Another instance triggered drain, update our reference too */ + WRITE_ONCE(spm->last_drain_rptr, ring_rptr); + } + } + mutex_unlock(&spm_cntr->spm_worker_mutex); + if (need_schedule) + schedule_work(&spm_mgr->spm_work); + } + + /* The thread never clears spm_monitor_thread: the pointer and its + * task reference are owned by the start/stop paths. + */ + return 0; +} + +static int amdgpu_spm_monitor_thread_start(struct amdgpu_spm_mgr *spm_mgr) +{ + 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 task_struct *task; + + task = kthread_create(amdgpu_spm_monitor_thread, spm_mgr, "spm_%d_%u", + adev->ddev.render->index, prof_xcp_mgr->xcp_id); + if (IS_ERR(task)) { + dev_dbg(adev->dev, "Failed to create spm monitor thread (%ld).\n", + PTR_ERR(task)); + return PTR_ERR(task); + } + + /* Pin the task_struct before waking: the thread can exit on its own. */ + get_task_struct(task); + spm_mgr->spm_monitor_thread = task; + wake_up_process(task); + + return 0; +} + /* Re-arm overflow detection region with magic values. * * Called with size = spm_overflow_reserved at acquire time to arm the entire @@ -191,6 +291,7 @@ static int amdgpu_spm_read_ring_buffer(struct amdgpu_spm_mgr *spm_mgr, if (ring_rptr == ring_wptr) goto exit; + WRITE_ONCE(spm->last_drain_rptr, ring_rptr); if (ring_wptr > ring_rptr) { size_to_copy = ring_wptr - ring_rptr; ret = amdgpu_spm_data_copy(spm_mgr, spm_cntr, ring_rptr, size_to_copy, inst); @@ -415,6 +516,7 @@ static int _amdgpu_spm_acquire(struct amdgpu_spm_mgr *spm_mgr, int inst, struct */ spm->ring_size -= AMDGPU_SPM_META_SIZE; amdgpu_spm_preset(spm, spm_mgr->spm_overflow_reserved); + spm->last_drain_rptr = ~0; goto out; @@ -479,6 +581,10 @@ static int amdgpu_spm_acquire(struct amdgpu_spm_mgr *spm_mgr, struct drm_file *f spm_mgr->spm_cntr->spm_use_cnt++; } + /* No thread can be live here: release and both start/stop failure paths + * clear it, and a live session blocks re-acquire. + */ + WARN_ON_ONCE(spm_mgr->spm_monitor_thread); spm_mgr->file = filp; goto out; @@ -524,13 +630,39 @@ static int _spm_start(struct amdgpu_spm_mgr *spm_mgr, spin_lock_irqsave(&spm_mgr->spm_irq_lock, flags); spm->is_spm_started = true; WRITE_ONCE(spm->ring_rptr, 0); + WRITE_ONCE(spm->last_drain_rptr, ~0); spin_unlock_irqrestore(&spm_mgr->spm_irq_lock, flags); + /* Pre-GFX12 lacks HW watermark interrupts, so start a polling thread. + * GFX12+ uses amdgpu_spm_interrupt() instead. spm_monitor_thread is + * never touched from IRQ context, so the mutex above is enough. + */ + if (amdgpu_ip_version(adev, GC_HWIP, 0) < IP_VERSION(12, 0, 0) && + !spm_mgr->spm_monitor_thread) { + ret = amdgpu_spm_monitor_thread_start(spm_mgr); + if (ret) { + /* Thread failed to start, revert state */ + dev_dbg(adev->dev, + "Failed to start SPM monitor thread, ret = %d\n", ret); + spin_lock_irqsave(&spm_mgr->spm_irq_lock, flags); + spm->is_spm_started = false; + spin_unlock_irqrestore(&spm_mgr->spm_irq_lock, flags); + return ret; + } + } + ret = amdgpu_rlc_spm_cntl(adev, inst, 1); if (ret) { + struct task_struct *monitor_thread; + spin_lock_irqsave(&spm_mgr->spm_irq_lock, flags); spm->is_spm_started = false; + monitor_thread = spm_mgr->spm_monitor_thread; + spm_mgr->spm_monitor_thread = NULL; spin_unlock_irqrestore(&spm_mgr->spm_irq_lock, flags); + + if (monitor_thread) + kthread_stop_put(monitor_thread); } return ret; } @@ -538,9 +670,23 @@ static int _spm_start(struct amdgpu_spm_mgr *spm_mgr, static void _spm_stop(struct amdgpu_spm_mgr *spm_mgr, struct amdgpu_device *adev, int inst) { + struct amdgpu_profiler_xcp_mgr *prof_xcp_mgr = to_prof_xcp_mgr(spm_mgr, spm_mgr); struct amdgpu_spm_base *spm = &spm_mgr->spm_cntr->spm[inst]; + struct task_struct *monitor_thread; unsigned long flags; + lockdep_assert_held(&prof_xcp_mgr->mutex); + + /* Stopping any instance tears down the manager-scoped monitor thread. + * Safe because callers always stop the whole started set, which is + * all-or-nothing. + */ + monitor_thread = spm_mgr->spm_monitor_thread; + spm_mgr->spm_monitor_thread = NULL; + + if (monitor_thread) + kthread_stop_put(monitor_thread); + amdgpu_rlc_spm_cntl(adev, inst, 0); spin_lock_irqsave(&spm_mgr->spm_irq_lock, flags); spm->is_spm_started = false; @@ -548,6 +694,7 @@ static void _spm_stop(struct amdgpu_spm_mgr *spm_mgr, * Adjust rptr accordingly. */ WRITE_ONCE(spm->ring_rptr, 0); + WRITE_ONCE(spm->last_drain_rptr, ~0); spin_unlock_irqrestore(&spm_mgr->spm_irq_lock, flags); } @@ -639,7 +786,7 @@ static int amdgpu_spm_release(struct amdgpu_spm_mgr *spm_mgr, struct drm_file *f /* Stop hardware SPM for all instances and free their ring buffers. * spm_worker_mutex locks out the drain worker, which would otherwise * keep updating rptr after the reset and read the ring after it is - * freed. + * freed. Monitor thread is stopped on the first _spm_stop() call. */ mutex_lock(&spm_cntr->spm_worker_mutex); for_each_inst(inst, xcc_mask) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h index 552c5701e08c..b057d763fddf 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_spm.h @@ -46,6 +46,7 @@ struct amdgpu_spm_base { bool has_user_buf; bool is_user_buf_filled; bool is_spm_started; + u32 last_drain_rptr; }; struct amdgpu_spm_cntr { @@ -68,6 +69,7 @@ struct amdgpu_spm_cntr { }; struct amdgpu_spm_mgr { + struct task_struct *spm_monitor_thread; struct drm_file *file; struct task_struct *lead_thread; -- 2.34.1
