Track armed dbgext regions independently so selected XCCs can be drained and disarmed without stopping the device-wide reader or disturbing other XCCs. Add helpers to reseed and reattach those regions after their MES instances restart. Serialize notification, draining, and region initialization while preserving the existing whole-device start and stop callbacks.
Signed-off-by: Yongqiang Sun <[email protected]> --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 217 +++++++++++++++++++++--- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h | 28 +++ 2 files changed, 220 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index 2e51bf882574f..9e60e78925756 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -26,6 +26,7 @@ #include <linux/delay.h> #include <linux/sizes.h> #include <linux/slab.h> +#include <linux/bitmap.h> #include <linux/workqueue.h> #include <drm/drm_exec.h> @@ -125,6 +126,8 @@ int amdgpu_mes_init(struct amdgpu_device *adev) spin_lock_init(&adev->mes.queue_id_lock); mutex_init(&adev->mes.mutex_hidden); mutex_init(&adev->mes.dbgext_lock); + mutex_init(&adev->mes.dbgext_drain_lock); + spin_lock_init(&adev->mes.dbgext_notify_lock); for (i = 0; i < AMDGPU_MAX_MES_PIPES * num_xcc; i++) spin_lock_init(&adev->mes.ring_lock[i]); @@ -320,6 +323,7 @@ void amdgpu_mes_fini(struct amdgpu_device *adev) ida_destroy(&adev->mes.doorbell_ida); mutex_destroy(&adev->mes.mutex_hidden); mutex_destroy(&adev->mes.dbgext_lock); + mutex_destroy(&adev->mes.dbgext_drain_lock); } int amdgpu_mes_suspend(struct amdgpu_device *adev, u32 xcc_id) @@ -1573,6 +1577,12 @@ static u32 mes_dbgext_num_regions(struct amdgpu_mes *mes) mes_dbgext_num_pipes(mes); } +static u32 mes_dbgext_region(struct amdgpu_mes *mes, unsigned long xcc, + u32 pipe) +{ + return xcc * mes_dbgext_num_pipes(mes) + pipe; +} + static void mes_dbgext_process_all(struct amdgpu_device *adev) { struct amdgpu_mes *mes = &adev->mes; @@ -1585,12 +1595,18 @@ static void mes_dbgext_process_all(struct amdgpu_device *adev) return; /* - * Every armed (xcc, pipe) raises the same host interrupt, so drain all - * regions; each is a no-op when it has no new data. + * Every armed (xcc, pipe) raises the same host interrupt, so drain + * every region that is still attached. Regions cleared while an XCC's + * MES is restarting are skipped until that XCC is rearmed. Caller + * holds dbgext_drain_lock. */ - for (i = 0; i < num_regions; i++) + for (i = 0; i < num_regions; i++) { + if (!test_bit(i, mes->dbgext_armed_regions)) + continue; + mes_dbgext_process_region(adev, buf + i * mes->dbgext_log_size, i / num_pipes, i % num_pipes); + } } static int amdgpu_mes_dbgext_reader(void *param) @@ -1598,7 +1614,9 @@ static int amdgpu_mes_dbgext_reader(void *param) struct amdgpu_device *adev = param; while (!kthread_should_stop()) { + mutex_lock(&adev->mes.dbgext_drain_lock); mes_dbgext_process_all(adev); + mutex_unlock(&adev->mes.dbgext_drain_lock); msleep_interruptible(MES_DBGEXT_POLL_INTERVAL_MS); } return 0; @@ -1612,7 +1630,9 @@ static void amdgpu_mes_dbgext_work_fn(struct work_struct *work) struct amdgpu_device *adev = container_of(mes, struct amdgpu_device, mes); + mutex_lock(&mes->dbgext_drain_lock); mes_dbgext_process_all(adev); + mutex_unlock(&mes->dbgext_drain_lock); } /* @@ -1623,6 +1643,7 @@ static void amdgpu_mes_dbgext_work_fn(struct work_struct *work) void amdgpu_mes_dbgext_notify(struct amdgpu_device *adev, u32 context_data) { struct amdgpu_mes *mes = &adev->mes; + unsigned long flags; /* * Caller has already matched MES_DBGMSG (type 7 in bits 31:26 of the @@ -1634,12 +1655,13 @@ void amdgpu_mes_dbgext_notify(struct amdgpu_device *adev, u32 context_data) * Only the interrupt path may schedule the drain work. In polling mode * the kthread owns rptr; honoring a stale/spurious interrupt here would * let the work item and the kthread drain concurrently and race on rptr. + * The flag is published under dbgext_notify_lock so stop can pair this + * check with cancel_work_sync and not lose a late schedule. */ - if (!READ_ONCE(mes->dbgext_use_irq)) - return; - - if (READ_ONCE(mes->dbgext_log_cpu_addr)) + spin_lock_irqsave(&mes->dbgext_notify_lock, flags); + if (mes->dbgext_use_irq && mes->dbgext_log_cpu_addr) schedule_work(&mes->dbgext_work); + spin_unlock_irqrestore(&mes->dbgext_notify_lock, flags); } static int amdgpu_mes_dbgext_setup_fw(struct amdgpu_device *adev, u32 xcc_id, @@ -1675,6 +1697,7 @@ static int amdgpu_mes_dbgext_start_locked(struct amdgpu_device *adev) u32 size; u32 req_kb; u32 num_xcc, num_pipes, num_regions, i; + unsigned long flags; int r; /* @@ -1784,7 +1807,9 @@ static int amdgpu_mes_dbgext_start_locked(struct amdgpu_device *adev) mes->funcs->enable_dbgext_irq; if (!use_irq) mes->dbgext_log_options &= ~MES_DBGEXT_OPT_TRIGGER_INT_PER_MSG; + spin_lock_irqsave(&mes->dbgext_notify_lock, flags); mes->dbgext_use_irq = use_irq; + spin_unlock_irqrestore(&mes->dbgext_notify_lock, flags); /* * Translate the options to the mes_aux firmware layout: enable logging @@ -1828,6 +1853,11 @@ static int amdgpu_mes_dbgext_start_locked(struct amdgpu_device *adev) } } + mutex_lock(&mes->dbgext_drain_lock); + for (i = 0; i < num_regions; i++) + set_bit(i, mes->dbgext_armed_regions); + mutex_unlock(&mes->dbgext_drain_lock); + if (!use_irq) { /* * Never leak a reader: if one is somehow still around (a missed @@ -1873,10 +1903,15 @@ static int amdgpu_mes_dbgext_start_locked(struct amdgpu_device *adev) 0, 0); err_disable: /* Undo the early arming done before setup_fw(). */ + spin_lock_irqsave(&mes->dbgext_notify_lock, flags); mes->dbgext_active = false; + mes->dbgext_use_irq = false; + spin_unlock_irqrestore(&mes->dbgext_notify_lock, flags); + mutex_lock(&mes->dbgext_drain_lock); + bitmap_zero(mes->dbgext_armed_regions, AMDGPU_MAX_MES_INST_PIPES); + mutex_unlock(&mes->dbgext_drain_lock); if (use_irq) mes->funcs->enable_dbgext_irq(mes, false); - mes->dbgext_use_irq = false; /* Never free a kernel BO while suspended; keep it for the next start. */ if (!adev->in_suspend) { amdgpu_bo_free_kernel(&mes->dbgext_log_gpu_obj, @@ -1901,27 +1936,40 @@ int amdgpu_mes_dbgext_start(struct amdgpu_device *adev) static void amdgpu_mes_dbgext_stop_locked(struct amdgpu_device *adev) { struct amdgpu_mes *mes = &adev->mes; + bool use_irq = mes->dbgext_use_irq; + unsigned long flags; + /* + * Publish "off" before cancelling the reader and work item. notify() + * checks these flags under the same lock, so a late interrupt cannot + * schedule another drain after cancel_work_sync returns. + */ + spin_lock_irqsave(&mes->dbgext_notify_lock, flags); mes->dbgext_active = false; + mes->dbgext_use_irq = false; + spin_unlock_irqrestore(&mes->dbgext_notify_lock, flags); + + if (use_irq) + mes->funcs->enable_dbgext_irq(mes, false); /* - * Tear down the reader kthread and the host interrupt first, and do so - * independently of the buffer state. The kthread runs code that lives - * in this module, so it must never be left alive past module unload - - * otherwise it faults on an instruction fetch once the module text is - * freed. A second stop() in a teardown/reset sequence, or one reached - * after the buffer was already freed on an error path, must still be - * able to reap a stale reader; hence this runs before the buffer guard. + * Tear down the reader kthread independently of the buffer state. The + * kthread runs code that lives in this module, so it must never be + * left alive past module unload. It takes dbgext_drain_lock only + * around one drain, so stopping it here does not invert that lock + * with dbgext_lock. */ if (mes->dbgext_reader) { kthread_stop(mes->dbgext_reader); mes->dbgext_reader = NULL; } - if (mes->dbgext_use_irq) { - mes->funcs->enable_dbgext_irq(mes, false); - mes->dbgext_use_irq = false; - } + cancel_work_sync(&mes->dbgext_work); + + mutex_lock(&mes->dbgext_drain_lock); + mes_dbgext_process_all(adev); + bitmap_zero(mes->dbgext_armed_regions, AMDGPU_MAX_MES_INST_PIPES); + mutex_unlock(&mes->dbgext_drain_lock); if (!mes->dbgext_log_gpu_obj) return; @@ -1942,12 +1990,6 @@ static void amdgpu_mes_dbgext_stop_locked(struct amdgpu_device *adev) i % num_pipes, 0, 0); } - /* Make sure no drain work is still touching the buffer. */ - cancel_work_sync(&mes->dbgext_work); - - /* Drain anything the firmware wrote before we stopped. */ - mes_dbgext_process_all(adev); - /* * Keep the buffer allocated across a suspend/resume cycle (freeing a * kernel BO while suspended is not allowed - it trips a WARN in @@ -1969,6 +2011,131 @@ void amdgpu_mes_dbgext_stop(struct amdgpu_device *adev) mutex_unlock(&adev->mes.dbgext_lock); } +void amdgpu_mes_dbgext_disarm_xcc(struct amdgpu_device *adev, u32 xcc_id) +{ + struct amdgpu_mes *mes = &adev->mes; + u32 num_pipes, pipe, index; + + mutex_lock(&mes->dbgext_lock); + if (!mes->dbgext_active || !mes->dbgext_log_gpu_obj || + xcc_id >= mes->dbgext_num_xcc) { + mutex_unlock(&mes->dbgext_lock); + return; + } + + /* + * Drain this XCC's regions, then stop draining them, before its MES is + * reset. The bits are cleared under the drain lock, so an in-flight + * reader finishes its current pass before the headers can be rebuilt + * on rearm. Rearm zeroes the region, so this is the last chance to + * print what the firmware already wrote. + */ + num_pipes = mes_dbgext_num_pipes(mes); + mutex_lock(&mes->dbgext_drain_lock); + for (pipe = 0; pipe < num_pipes; pipe++) { + index = mes_dbgext_region(mes, xcc_id, pipe); + if (index >= AMDGPU_MAX_MES_INST_PIPES || + !test_bit(index, mes->dbgext_armed_regions)) + continue; + + if (mes->dbgext_log_cpu_addr && mes->dbgext_log_size) + mes_dbgext_process_region(adev, + (u8 *)mes->dbgext_log_cpu_addr + + index * mes->dbgext_log_size, + xcc_id, pipe); + clear_bit(index, mes->dbgext_armed_regions); + } + mutex_unlock(&mes->dbgext_drain_lock); + + if (mes->dbgext_use_irq && mes->funcs && + mes->funcs->enable_dbgext_irq_xcc) + mes->funcs->enable_dbgext_irq_xcc(mes, false, xcc_id); + + mutex_unlock(&mes->dbgext_lock); +} + +int amdgpu_mes_dbgext_rearm_xcc(struct amdgpu_device *adev, u32 xcc_id) +{ + struct amdgpu_mes *mes = &adev->mes; + u32 num_pipes, pipe, index; + u64 fw_options; + bool xcc_ok = true; + int ret = 0; + int r; + + mutex_lock(&mes->dbgext_lock); + if (!mes->dbgext_active || !mes->dbgext_log_cpu_addr || + !mes->dbgext_log_size || xcc_id >= mes->dbgext_num_xcc) { + mutex_unlock(&mes->dbgext_lock); + return 0; + } + + fw_options = MES_DBGEXT_AUX_OPT_ENABLE_MES_LOG; + if (!mes->dbgext_use_irq) + fw_options |= MES_DBGEXT_AUX_OPT_HOST_POLL; + + num_pipes = mes_dbgext_num_pipes(mes); + for (pipe = 0; pipe < num_pipes; pipe++) { + u8 *base; + + index = mes_dbgext_region(mes, xcc_id, pipe); + if (index >= AMDGPU_MAX_MES_INST_PIPES) { + xcc_ok = false; + continue; + } + + /* + * Reseed only while the region is unarmed and the + * drain lock is held, so the reader cannot parse a + * half-rebuilt zone header. Drop the lock before + * submitting the MES packet: that waits for firmware + * and must not block behind, or deadlock with, the + * drain work. + */ + mutex_lock(&mes->dbgext_drain_lock); + clear_bit(index, mes->dbgext_armed_regions); + base = (u8 *)mes->dbgext_log_cpu_addr + + index * mes->dbgext_log_size; + memset(base, 0, mes->dbgext_log_size); + *(u32 *)base = mes->dbgext_log_size; + mutex_unlock(&mes->dbgext_drain_lock); + + r = amdgpu_mes_dbgext_setup_fw(adev, xcc_id, pipe, + mes->dbgext_log_gpu_addr + + index * mes->dbgext_log_size, + fw_options); + if (r) { + dev_err(adev->dev, + "failed to rearm mes_dbgext on xcc%u %s pipe (%d)\n", + xcc_id, mes_dbgext_pipe_name(pipe), r); + ret = r; + xcc_ok = false; + continue; + } + + mutex_lock(&mes->dbgext_drain_lock); + set_bit(index, mes->dbgext_armed_regions); + mutex_unlock(&mes->dbgext_drain_lock); + } + + /* + * Disarm left this XCC's interrupt disabled. Enable it only + * after every pipe has attached, so a failed SETUP cannot + * leave TIME_STAMP_INT_ENABLE set with the region unarmed. + * The notify below collects the messages firmware wrote + * during setup. + */ + if (xcc_ok && mes->dbgext_use_irq && mes->funcs && + mes->funcs->enable_dbgext_irq_xcc) + mes->funcs->enable_dbgext_irq_xcc(mes, true, xcc_id); + + if (mes->dbgext_use_irq) + amdgpu_mes_dbgext_notify(adev, 0); + + mutex_unlock(&mes->dbgext_lock); + return ret; +} + #if defined(CONFIG_DEBUG_FS) static int amdgpu_debugfs_mes_event_log_show(struct seq_file *m, void *unused) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h index 0f368fbd2c80e..652cd09504538 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h @@ -170,12 +170,24 @@ struct amdgpu_mes { uint32_t dbgext_num_xcc; uint32_t dbgext_num_pipes; + /* + * One bit per (xcc, pipe) region, at xcc * dbgext_num_pipes + pipe. + * Cleared while that XCC's MES is reset so the drain skips a region + * whose zone header is being rebuilt. dbgext_active stays set for + * the whole device while selected XCCs restart. + */ + DECLARE_BITMAP(dbgext_armed_regions, AMDGPU_MAX_MES_INST_PIPES); + /* armed: FW attached + reader/irq running */ bool dbgext_active; struct work_struct dbgext_work; struct task_struct *dbgext_reader; /* polling fallback */ struct mutex dbgext_lock; /* serializes runtime start/stop */ + /* serializes region drain vs reseed */ + struct mutex dbgext_drain_lock; + /* publishes use_irq to the IH path */ + spinlock_t dbgext_notify_lock; /* runtime-requested size (KB); 0 = follow module param */ uint32_t dbgext_runtime_kb; @@ -492,6 +504,13 @@ struct amdgpu_mes_funcs { */ int (*enable_dbgext_irq)(struct amdgpu_mes *mes, bool enable); + /* + * Per-XCC variant used when one MES instance restarts. + * xcc_id is the logical XCC. + */ + int (*enable_dbgext_irq_xcc)(struct amdgpu_mes *mes, bool enable, + u32 xcc_id); + int (*reset_hw_queue)(struct amdgpu_mes *mes, struct mes_reset_queue_input *input); @@ -563,6 +582,15 @@ int amdgpu_mes_dbgext_start(struct amdgpu_device *adev); void amdgpu_mes_dbgext_stop(struct amdgpu_device *adev); void amdgpu_mes_dbgext_notify(struct amdgpu_device *adev, u32 context_data); +/* + * Drop or restore dbgext for one logical XCC without tearing down the + * device-wide reader or the other XCCs' regions. Disarm before that XCC's + * MES is reset; rearm after both MES pipes on the XCC can accept API packets + * again. + */ +void amdgpu_mes_dbgext_disarm_xcc(struct amdgpu_device *adev, u32 xcc_id); +int amdgpu_mes_dbgext_rearm_xcc(struct amdgpu_device *adev, u32 xcc_id); + /* * MES host-interrupt context dword (IH src_data[0]), same overlay as KMD * IRQMGR_INTERRUPT_CONTEXTDATA_MES and firmware MES_INT_CONTEXT_DATA: -- 2.43.0
