[PATCH 1/2] drm/amdgpu: Add MES debug extension support for gfx11.
Yongqiang Sun <[email protected]>
| Newsgroups | org.freedesktop.lists.amd-gfx |
|---|---|
| Message-ID | <[email protected]> |
Add configurable MES debug logging with interrupt and polling collection modes. Initialize and parse the firmware ring buffer, route GFX11 MES debug interrupts, and print messages to dmesg. Expose mes_dbgext_buffer_size and mes_dbgext_options module parameters to configure the buffer and collection mode. Signed-off-by: Yongqiang Sun <[email protected]> --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 2 + drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.h | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 31 +- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 619 +++++++++++++++++- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h | 37 +- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 12 + drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 39 ++ drivers/gpu/drm/amd/include/mes_v11_api_def.h | 18 + 9 files changed, 755 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 8a7c89afc88e..bf9a787d46f8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -216,6 +216,8 @@ extern int amdgpu_async_gfx_ring; extern int amdgpu_mcbp; extern int amdgpu_discovery; extern int amdgpu_mes_log_enable; +extern int amdgpu_mes_dbgext_buffer_size; +extern int amdgpu_mes_dbgext_options; extern int amdgpu_uni_mes; extern int amdgpu_noretry; extern int amdgpu_force_asic_type; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c index 132d054900b5..8e4c2b4519e1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c @@ -2175,7 +2175,7 @@ int amdgpu_debugfs_init(struct amdgpu_device *adev) amdgpu_debugfs_firmware_init(adev); amdgpu_ta_if_debugfs_init(adev); - amdgpu_debugfs_mes_event_log_init(adev); + amdgpu_debugfs_mes_init(adev); #if defined(CONFIG_DRM_AMD_DC) if (adev->dc_enabled) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.h index e88b4a1e564c..3f01e7537add 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.h @@ -33,7 +33,7 @@ void amdgpu_debugfs_fini(struct amdgpu_device *adev); void amdgpu_debugfs_fence_init(struct amdgpu_device *adev); void amdgpu_debugfs_firmware_init(struct amdgpu_device *adev); void amdgpu_debugfs_gem_init(struct amdgpu_device *adev); -void amdgpu_debugfs_mes_event_log_init(struct amdgpu_device *adev); +void amdgpu_debugfs_mes_init(struct amdgpu_device *adev); void amdgpu_debugfs_vm_init(struct drm_file *file); void amdgpu_debugfs_userq_init(struct drm_file *file, struct amdgpu_usermode_queue *queue, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 53738b40c97f..90dd91599c00 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -225,7 +225,9 @@ uint amdgpu_dc_visual_confirm; int amdgpu_async_gfx_ring = 1; int amdgpu_mcbp = -1; int amdgpu_discovery = -1; -int amdgpu_mes_log_enable = 0; +int amdgpu_mes_log_enable; +int amdgpu_mes_dbgext_buffer_size; +int amdgpu_mes_dbgext_options = 1; int amdgpu_uni_mes = 1; int amdgpu_noretry = -1; int amdgpu_force_asic_type = -1; @@ -698,6 +700,33 @@ MODULE_PARM_DESC(mes_log_enable, "Enable Micro Engine Scheduler log (0 = disabled (default), 1 = enabled)"); module_param_named(mes_log_enable, amdgpu_mes_log_enable, int, 0444); +/** + * DOC: mes_dbgext_buffer_size (int) + * Size in KB of the MES firmware debug-extension log buffer. The MES firmware + * writes log messages into this buffer and the driver drains and prints them to + * dmesg. Requires an MES firmware image built with debug extension support. + * (0 = disabled at boot (default)) + * + * The feature can also be toggled at runtime via the per-device debugfs file + * <debugfs>/dri/N/amdgpu_mes_dbgext (echo 1/0 to enable/disable); when enabled + * at runtime with this parameter left at 0, a small default buffer is used. + */ +MODULE_PARM_DESC(mes_dbgext_buffer_size, + "MES firmware debug-extension log buffer size in KB (0 = disabled (default))"); +module_param_named(mes_dbgext_buffer_size, amdgpu_mes_dbgext_buffer_size, int, 0444); + +/** + * DOC: mes_dbgext_options (int) + * MES firmware debug-extension option bits sent to the firmware (u64_all). + * bit0 = trigger_interrupt_per_new_msg: when set, the firmware raises an + * interrupt per message and the driver collects them via the interrupt path; + * when clear, the driver polls the log buffer with a kthread instead. + * (default 1 = interrupt driven) + */ +MODULE_PARM_DESC(mes_dbgext_options, + "MES debug-extension option bits (bit0: 1 = interrupt (default), 0 = polling)"); +module_param_named(mes_dbgext_options, amdgpu_mes_dbgext_options, int, 0444); + /** * DOC: uni_mes (int) * Enable Unified Micro Engine Scheduler. This is a new engine pipe for unified scheduler. diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index e0e38d6bcafc..01544c4f10e2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -22,6 +22,11 @@ */ #include <linux/firmware.h> +#include <linux/kthread.h> +#include <linux/delay.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/workqueue.h> #include <drm/drm_exec.h> #include "amdgpu_mes.h" @@ -120,6 +125,7 @@ int amdgpu_mes_init(struct amdgpu_device *adev) ida_init(&adev->mes.doorbell_ida); spin_lock_init(&adev->mes.queue_id_lock); mutex_init(&adev->mes.mutex_hidden); + mutex_init(&adev->mes.dbgext_lock); for (i = 0; i < AMDGPU_MAX_MES_PIPES * num_xcc; i++) spin_lock_init(&adev->mes.ring_lock[i]); @@ -320,6 +326,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); } @@ -1233,6 +1240,534 @@ void amdgpu_mes_userq_queue_unmapped(struct amdgpu_device *adev) cancel_delayed_work(&adev->mes.userq_notify_unmap_work); } +/* + * MES firmware debug extension ("mes_dbgext") + * + * The driver hands the MES a log buffer; the MES firmware writes text log items + * into it and the driver drains and prints them. The buffer is a single + * circular byte stream with a 16-byte header at offset 0: + * + * dword0 : rptr - driver reads/advances (with wrap) + * dword1 : wptr - firmware writes/advances (with wrap) + * dword2 : buffer_size - total buffer size in bytes (set by the driver) + * dword3 : header_size - size of this header / wrap-back offset (16) + * + * Item data starts at header_size and wraps from buffer_size back to + * header_size. Each log item begins with a 4-byte header (type, xor-signature, + * 16-bit length including the header) followed by NUL-free text. + * + * The driver owns rptr; the firmware owns wptr. The collection method is + * selectable via the mes_dbgext_options bit0: interrupt-driven (default - the + * FW raises its host interrupt per message, handled via the CP EOP path) or a + * polling kthread. Polling is also used automatically as a fallback when the + * ASIC has no IRQ-enable hook. + * + * NOTE: this requires an MES firmware image built with debug-extension support. + */ + +#define MES_DBGEXT_ITEM_HDR_SIZE 4 +#define MES_DBGEXT_BUF_HDR_SIZE 16 +#define MES_DBGEXT_MAX_ITEM_SIZE 2048 +#define MES_DBGEXT_POLL_INTERVAL_MS 200 +/* Default log-buffer size (KB) used when enabled at runtime with no size set. */ +#define MES_DBGEXT_DEFAULT_KB 8 + +/* Log item types, must match the MES firmware (mes_dbgext.h MesMsgType). */ +#define MES_DBGEXT_MSG 0x80 +#define MES_DBGEXT_MSG_ASSERT 0x81 +#define MES_DBGEXT_MSG_BTRACE 0x82 +#define MES_DBGEXT_MSG_HALT 0x83 +#define MES_DBGEXT_MSG_STEP 0x84 + +/* + * Log buffer option bits, must match the MES firmware's MES_DBGEXT_INIT_DATA. + * bit0 = trigger_interrupt_per_new_msg: when SET, the firmware raises the + * debug-message host interrupt after each message + * (mes_dbgext.cpp: "if (trigger_interrupt_per_new_msg) SendIntToHost()"). + * When CLEAR, the firmware only writes the buffer and the driver must poll. + */ +#define MES_DBGEXT_OPT_TRIGGER_INT_PER_MSG (1ULL << 0) + +/* + * Log buffer header at offset 0 (matches the MES firmware LOG_BUFF_HEADER). + * The buffer is a single circular byte stream: the driver owns rptr, the + * firmware owns wptr, both wrapping from buffer_size back to header_size. + * buffer_size and header_size are initialized by the driver in + * amdgpu_mes_dbgext_start(). + */ +struct mes_dbgext_buf_header { + u32 rptr; + u32 wptr; + u32 buffer_size; + u32 header_size; +}; + +/* + * Copy @n bytes out of the circular data region starting at byte offset @off, + * wrapping back to @hdr_size when @buffer_size is reached. + */ +static void mes_dbgext_buf_read(const u8 *buf, u32 buffer_size, u32 hdr_size, + u32 off, u8 *dst, u32 n) +{ + while (n--) { + *dst++ = buf[off++]; + if (off >= buffer_size) + off = hdr_size; + } +} + +enum mes_dbgext_kind { + MES_DBGEXT_KIND_MSG, + MES_DBGEXT_KIND_ASSERT, + MES_DBGEXT_KIND_HALT, +}; + +static void mes_dbgext_print_item(struct amdgpu_device *adev, + enum mes_dbgext_kind kind, char *text) +{ + size_t n = strlen(text); + + /* Normalize to exactly one trailing newline: the gfx11 firmware appends + * one to the text, the gfx12/mes_aux firmware does not. + */ + while (n && (text[n - 1] == '\n' || text[n - 1] == '\r')) + text[--n] = '\0'; + + switch (kind) { + case MES_DBGEXT_KIND_ASSERT: + dev_warn(adev->dev, "[mes_dbgext] ASSERT %s\n", text); + break; + case MES_DBGEXT_KIND_HALT: + dev_warn(adev->dev, "[mes_dbgext] HALT %s\n", text); + break; + default: + dev_info(adev->dev, "[mes_dbgext] %s\n", text); + break; + } +} + +static void mes_dbgext_process_flat(struct amdgpu_device *adev) +{ + struct amdgpu_mes *mes = &adev->mes; + struct mes_dbgext_buf_header *bh = mes->dbgext_log_cpu_addr; + u32 rptr, wptr, buffer_size, hdr_size; + u8 *item; + + if (!bh) + return; + + buffer_size = READ_ONCE(bh->buffer_size); + hdr_size = READ_ONCE(bh->header_size); + rptr = READ_ONCE(bh->rptr); + wptr = READ_ONCE(bh->wptr); + + /* Nothing to do until the firmware has written a new message. */ + if (rptr == wptr) + return; + + /* + * Order the wptr load ahead of the item-body loads below. The firmware + * publishes an item by writing its body first and advancing wptr last; + * this barrier ensures we observe the body that wptr claims is present. + */ + dma_rmb(); + + /* Sanity-check the header against the allocation. */ + if (hdr_size < sizeof(*bh) || buffer_size > mes->dbgext_log_size || + buffer_size <= hdr_size || + rptr < hdr_size || rptr >= buffer_size || + wptr < hdr_size || wptr >= buffer_size) + return; + + /* Scratch to linearize a (possibly wrapped) item; +1 for NUL. */ + item = kmalloc(MES_DBGEXT_MAX_ITEM_SIZE + 1, GFP_KERNEL); + if (!item) + return; + + while (rptr != wptr) { + u8 hb[MES_DBGEXT_ITEM_HDR_SIZE]; + enum mes_dbgext_kind kind; + u32 type, len; + u8 sign, len_hi, len_lo; + + mes_dbgext_buf_read((u8 *)bh, buffer_size, hdr_size, rptr, + hb, sizeof(hb)); + type = hb[0]; + sign = hb[1]; + len_hi = hb[2]; + len_lo = hb[3]; + len = ((u32)len_hi << 8) | len_lo; + + /* On corruption/desync, skip everything up to wptr. */ + if ((u8)(type ^ len_hi ^ len_lo) != sign || + len <= MES_DBGEXT_ITEM_HDR_SIZE || + len > MES_DBGEXT_MAX_ITEM_SIZE) { + dev_dbg(adev->dev, + "mes_dbgext: bad item @%u (type 0x%x len %u), skipping to %u\n", + rptr, type, len, wptr); + rptr = wptr; + break; + } + + mes_dbgext_buf_read((u8 *)bh, buffer_size, hdr_size, rptr, + item, len); + item[len] = '\0'; /* NUL-terminate the text tail */ + switch (type) { + case MES_DBGEXT_MSG_ASSERT: + kind = MES_DBGEXT_KIND_ASSERT; + break; + case MES_DBGEXT_MSG_HALT: + kind = MES_DBGEXT_KIND_HALT; + break; + default: + kind = MES_DBGEXT_KIND_MSG; + break; + } + mes_dbgext_print_item(adev, kind, + (char *)item + MES_DBGEXT_ITEM_HDR_SIZE); + + rptr += len; + if (rptr >= buffer_size) + rptr -= (buffer_size - hdr_size); + if (rptr < hdr_size || rptr >= buffer_size) { + rptr = wptr; + break; + } + } + + /* + * Ensure all item-body reads complete before we publish the new rptr; + * otherwise the firmware may observe the advanced rptr and reuse buffer + * space we have not finished reading. + */ + dma_wmb(); + WRITE_ONCE(bh->rptr, rptr); + kfree(item); +} + +static void mes_dbgext_process_all(struct amdgpu_device *adev) +{ + struct amdgpu_mes *mes = &adev->mes; + + if (!mes->dbgext_log_cpu_addr) + return; + + mes_dbgext_process_flat(adev); +} + +static int amdgpu_mes_dbgext_reader(void *param) +{ + struct amdgpu_device *adev = param; + + while (!kthread_should_stop()) { + mes_dbgext_process_all(adev); + msleep_interruptible(MES_DBGEXT_POLL_INTERVAL_MS); + } + return 0; +} + +/* Runs in process context; does the (sleepable) buffer drain. */ +static void amdgpu_mes_dbgext_work_fn(struct work_struct *work) +{ + struct amdgpu_mes *mes = container_of(work, struct amdgpu_mes, + dbgext_work); + struct amdgpu_device *adev = + container_of(mes, struct amdgpu_device, mes); + + mes_dbgext_process_all(adev); +} + +/* + * Called from the MES interrupt handler (hard/soft IRQ context). Decodes the + * MES->host interrupt type and, for a debug-message notification, schedules the + * drain on a workqueue (the parser sleeps / allocates, so it cannot run here). + */ +void amdgpu_mes_dbgext_notify(struct amdgpu_device *adev, u32 context_data) +{ + struct amdgpu_mes *mes = &adev->mes; + + /* + * The MES->host interrupt "type" lives in the upper bits of the context + * word, but on gfx12 the IH cookie truncates that field so a DBGMSG + * cannot be reliably told apart from other MES host interrupts. The + * caller has already narrowed this down to the MES scheduler pipe, so + * just kick the drain: it is a no-op when the log buffer has no new + * data, which makes an occasional spurious poke harmless. + */ + + /* + * 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. + */ + if (!READ_ONCE(mes->dbgext_use_irq)) + return; + + if (READ_ONCE(mes->dbgext_log_cpu_addr)) + schedule_work(&mes->dbgext_work); +} + +static int amdgpu_mes_dbgext_setup_fw(struct amdgpu_device *adev, + u64 log_buffer_mc_addr, u64 log_options) +{ + struct amdgpu_mes *mes = &adev->mes; + struct mes_misc_op_input op_input = {0}; + int r; + + if (!mes->funcs || !mes->funcs->misc_op) + return -EINVAL; + + op_input.op = MES_MISC_OP_SETUP_MES_DBGEXT; + op_input.setup_mes_dbgext.log_buffer_mc_addr = log_buffer_mc_addr; + op_input.setup_mes_dbgext.log_options = log_options; + + amdgpu_mes_lock(mes); + r = mes->funcs->misc_op(mes, &op_input); + amdgpu_mes_unlock(mes); + + return r; +} + +static int amdgpu_mes_dbgext_start_locked(struct amdgpu_device *adev) +{ + struct amdgpu_mes *mes = &adev->mes; + struct task_struct *reader; + bool use_irq; + u64 fw_options; + u32 size; + u32 req_kb; + int r; + + /* + * Effective buffer size (KB): the module parameter wins (so a boot-time + * request is honored), otherwise use the runtime-requested size set by + * the debugfs on/off switch. Zero means the feature is off. + */ + req_kb = amdgpu_mes_dbgext_buffer_size ? + amdgpu_mes_dbgext_buffer_size : mes->dbgext_runtime_kb; + if (!req_kb) + return 0; + + /* + * Already armed for this hw bring-up. hw_init() can run start() more + * than once (e.g. kiq_hw_init() -> hw_init(), then the MES IP block's + * own hw_init()); only the first should arm. A post-suspend resume + * comes back here with the buffer kept but dbgext_active cleared by + * stop(), so re-arm runs below. + */ + + if (mes->dbgext_active) + return 0; + + if (!mes->funcs || !mes->funcs->misc_op) { + dev_warn(adev->dev, "mes_dbgext not supported by this MES\n"); + return 0; + } + + if (!mes->dbgext_log_gpu_obj) { + /* Clamp to a sane range (4 KB .. 1 MB). */ + size = clamp(req_kb, 4U, 1024U); + size = ALIGN((u32)size * SZ_1K, PAGE_SIZE); + + r = amdgpu_bo_create_kernel(adev, size, PAGE_SIZE, + AMDGPU_GEM_DOMAIN_GTT, + &mes->dbgext_log_gpu_obj, + &mes->dbgext_log_gpu_addr, + &mes->dbgext_log_cpu_addr); + if (r) { + dev_warn(adev->dev, + "failed to create mes_dbgext log buffer (%d)\n", r); + return r; + } + mes->dbgext_log_size = size; + INIT_WORK(&mes->dbgext_work, amdgpu_mes_dbgext_work_fn); + } else { + /* + * Resume: the log buffer is kept allocated across a suspend/ + * resume cycle (freeing a kernel BO while suspended is not + * allowed), so reuse it and just re-arm the firmware below. + */ + size = mes->dbgext_log_size; + } + + /* + * (Re)initialize the log buffer header. On resume the MES was reset and + * re-lays its header, and the driver's rptr/wptr must restart clean. + */ + memset(mes->dbgext_log_cpu_addr, 0, size); + { + /* + * gfx11: initialize the LOG_BUFF_HEADER the firmware expects: a + * single circular byte stream following the 16-byte header. The + * driver owns rptr, the firmware owns wptr; both start just past + * the header and wrap from buffer_size back to header_size. The + * firmware reads buffer_size and header_size on every message, + * so they must be set up before setup. + */ + struct mes_dbgext_buf_header *bh = mes->dbgext_log_cpu_addr; + + bh->rptr = MES_DBGEXT_BUF_HDR_SIZE; + bh->wptr = MES_DBGEXT_BUF_HDR_SIZE; + bh->buffer_size = size; + bh->header_size = MES_DBGEXT_BUF_HDR_SIZE; + } + + /* + * The options word is passed verbatim to the firmware. bit0 + * (trigger_interrupt_per_new_msg) selects the collection method: + * set -> firmware raises an interrupt per message; the driver + * collects them via the CP EOP path (gfx_v11_0_eop_irq). + * clear -> firmware only writes the buffer; the driver polls with a + * kthread. + * If interrupts are requested but the ASIC has no CP enable hook, fall + * back to polling and clear the bit so the firmware does not raise an + * interrupt nobody will service. + */ + mes->dbgext_log_options = (u32)amdgpu_mes_dbgext_options; + use_irq = (mes->dbgext_log_options & MES_DBGEXT_OPT_TRIGGER_INT_PER_MSG) && + mes->funcs->enable_dbgext_irq; + if (!use_irq) + mes->dbgext_log_options &= ~MES_DBGEXT_OPT_TRIGGER_INT_PER_MSG; + mes->dbgext_use_irq = use_irq; + + /* + * The options word (bit0 = trigger_interrupt_per_new_msg) is passed to + * the firmware verbatim. + */ + fw_options = mes->dbgext_log_options; + + /* Enable delivery of the MES host interrupt at the CP (process ctx). */ + if (use_irq) + mes->funcs->enable_dbgext_irq(mes, true); + + r = amdgpu_mes_dbgext_setup_fw(adev, mes->dbgext_log_gpu_addr, + fw_options); + if (r) { + dev_err(adev->dev, "failed to setup mes_dbgext in FW (%d)\n", r); + goto err_disable; + } + + if (!use_irq) { + /* + * Never leak a reader: if one is somehow still around (a missed + * stop()), reap it before creating a new one so it cannot outlive + * the module. + */ + if (WARN_ON(mes->dbgext_reader)) { + kthread_stop(mes->dbgext_reader); + mes->dbgext_reader = NULL; + } + + reader = kthread_run(amdgpu_mes_dbgext_reader, adev, + "amdgpu_mes_dbgext"); + if (IS_ERR(reader)) { + r = PTR_ERR(reader); + dev_err(adev->dev, + "failed to start mes_dbgext reader (%d)\n", r); + goto err_fw_detach; + } + mes->dbgext_reader = reader; + } + + mes->dbgext_active = true; + + dev_info(adev->dev, + "mes_dbgext enabled: %u KB @ 0x%llx (%s, fw options 0x%llx)\n", + size / SZ_1K, mes->dbgext_log_gpu_addr, + use_irq ? "interrupt" : "polling", fw_options); + return 0; + +err_fw_detach: + amdgpu_mes_dbgext_setup_fw(adev, 0, 0); +err_disable: + 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, + &mes->dbgext_log_gpu_addr, + &mes->dbgext_log_cpu_addr); + mes->dbgext_log_size = 0; + } + return r; +} + +int amdgpu_mes_dbgext_start(struct amdgpu_device *adev) +{ + int r; + + mutex_lock(&adev->mes.dbgext_lock); + r = amdgpu_mes_dbgext_start_locked(adev); + mutex_unlock(&adev->mes.dbgext_lock); + + return r; +} + +static void amdgpu_mes_dbgext_stop_locked(struct amdgpu_device *adev) +{ + struct amdgpu_mes *mes = &adev->mes; + + mes->dbgext_active = 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. + */ + 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; + } + + if (!mes->dbgext_log_gpu_obj) + return; + + /* + * Detach the firmware from the buffer before we free it. Skip this + * across a suspend: the MES is being torn down anyway, the buffer is + * kept for resume, and submitting a packet on the suspend path is + * unnecessary. + */ + if (!adev->in_suspend) + amdgpu_mes_dbgext_setup_fw(adev, 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 + * amdgpu_bo_free_kernel()); only free it on real teardown. + */ + if (adev->in_suspend) + return; + + amdgpu_bo_free_kernel(&mes->dbgext_log_gpu_obj, + &mes->dbgext_log_gpu_addr, + &mes->dbgext_log_cpu_addr); + mes->dbgext_log_size = 0; +} + +void amdgpu_mes_dbgext_stop(struct amdgpu_device *adev) +{ + mutex_lock(&adev->mes.dbgext_lock); + amdgpu_mes_dbgext_stop_locked(adev); + mutex_unlock(&adev->mes.dbgext_lock); +} + #if defined(CONFIG_DEBUG_FS) static int amdgpu_debugfs_mes_event_log_show(struct seq_file *m, void *unused) @@ -1248,17 +1783,95 @@ static int amdgpu_debugfs_mes_event_log_show(struct seq_file *m, void *unused) DEFINE_SHOW_ATTRIBUTE(amdgpu_debugfs_mes_event_log); -#endif +/* + * Runtime on/off switch for the MES firmware debug extension. + * + * cat <debugfs>/amdgpu_mes_dbgext -> current state / size / mode + * echo 1 > <debugfs>/amdgpu_mes_dbgext -> enable + * echo 0 > <debugfs>/amdgpu_mes_dbgext -> disable + * + * When enabled with no boot-time size (mes_dbgext_buffer_size=0) a default + * buffer size is used; the collection method still follows mes_dbgext_options. + */ +static int amdgpu_debugfs_mes_dbgext_show(struct seq_file *m, void *unused) +{ + struct amdgpu_device *adev = m->private; + struct amdgpu_mes *mes = &adev->mes; + + mutex_lock(&mes->dbgext_lock); + seq_printf(m, "state: %s\n", mes->dbgext_active ? "on" : "off"); + seq_printf(m, "size: %u KB\n", mes->dbgext_log_size / SZ_1K); + seq_printf(m, "mode: %s\n", + mes->dbgext_use_irq ? "interrupt" : "polling"); + seq_printf(m, "options: 0x%llx\n", mes->dbgext_log_options); + mutex_unlock(&mes->dbgext_lock); + + return 0; +} + +static int amdgpu_debugfs_mes_dbgext_open(struct inode *inode, struct file *file) +{ + return single_open(file, amdgpu_debugfs_mes_dbgext_show, + inode->i_private); +} -void amdgpu_debugfs_mes_event_log_init(struct amdgpu_device *adev) +static ssize_t amdgpu_debugfs_mes_dbgext_write(struct file *file, + const char __user *buf, + size_t count, loff_t *ppos) { + struct amdgpu_device *adev = + ((struct seq_file *)file->private_data)->private; + struct amdgpu_mes *mes = &adev->mes; + bool enable; + int r; + + r = kstrtobool_from_user(buf, count, &enable); + if (r) + return r; + if (!mes->funcs || !mes->funcs->misc_op) + return -EOPNOTSUPP; + + mutex_lock(&mes->dbgext_lock); + if (enable) { + mes->dbgext_runtime_kb = amdgpu_mes_dbgext_buffer_size ? + amdgpu_mes_dbgext_buffer_size : MES_DBGEXT_DEFAULT_KB; + r = amdgpu_mes_dbgext_start_locked(adev); + } else { + amdgpu_mes_dbgext_stop_locked(adev); + mes->dbgext_runtime_kb = 0; + r = 0; + } + mutex_unlock(&mes->dbgext_lock); + + return r ? r : count; +} + +static const struct file_operations amdgpu_debugfs_mes_dbgext_fops = { + .owner = THIS_MODULE, + .open = amdgpu_debugfs_mes_dbgext_open, + .read = seq_read, + .write = amdgpu_debugfs_mes_dbgext_write, + .llseek = seq_lseek, + .release = single_release, +}; + +#endif + +void amdgpu_debugfs_mes_init(struct amdgpu_device *adev) +{ #if defined(CONFIG_DEBUG_FS) struct drm_minor *minor = adev_to_drm(adev)->primary; struct dentry *root = minor->debugfs_root; - if (adev->enable_mes && amdgpu_mes_log_enable) + + if (!adev->enable_mes) + return; + + if (amdgpu_mes_log_enable) debugfs_create_file("amdgpu_mes_event_log", 0444, root, adev, &amdgpu_debugfs_mes_event_log_fops); + debugfs_create_file("amdgpu_mes_dbgext", 0644, root, + adev, &amdgpu_debugfs_mes_dbgext_fops); #endif } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h index 5160b227943b..1cd02101f54e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h @@ -155,6 +155,24 @@ struct amdgpu_mes { uint64_t event_log_gpu_addr; void *event_log_cpu_addr; + /* MES firmware debug extension ("mes_dbgext") log buffer */ + uint32_t dbgext_log_size; + uint64_t dbgext_log_options; + struct amdgpu_bo *dbgext_log_gpu_obj; + uint64_t dbgext_log_gpu_addr; + void *dbgext_log_cpu_addr; + bool dbgext_use_irq; + + /* 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 */ + + /* runtime-requested size (KB); 0 = follow module param */ + uint32_t dbgext_runtime_kb; + /* ip specific functions */ const struct amdgpu_mes_funcs *funcs; @@ -371,7 +389,8 @@ enum mes_misc_opcode { MES_MISC_OP_WRM_REG_WR_WAIT, MES_MISC_OP_SET_SHADER_DEBUGGER, MES_MISC_OP_CHANGE_CONFIG, - MES_MISC_OP_NOTIFY_WORK_ON_UNMAPPED_QUEUE + MES_MISC_OP_NOTIFY_WORK_ON_UNMAPPED_QUEUE, + MES_MISC_OP_SETUP_MES_DBGEXT, }; struct mes_misc_op_input { @@ -426,6 +445,11 @@ struct mes_misc_op_input { uint32_t tdr_delay; } tdr_config; } change_config; + + struct { + uint64_t log_buffer_mc_addr; + uint64_t log_options; + } setup_mes_dbgext; }; }; @@ -451,6 +475,13 @@ struct amdgpu_mes_funcs { int (*misc_op)(struct amdgpu_mes *mes, struct mes_misc_op_input *input); + /* + * Enable/disable the MES firmware debug-extension host interrupt at the + * hardware level. Runs in process context (may take srbm_mutex), unlike + * the atomic irq .set callback. + */ + int (*enable_dbgext_irq)(struct amdgpu_mes *mes, bool enable); + int (*reset_hw_queue)(struct amdgpu_mes *mes, struct mes_reset_queue_input *input); @@ -518,6 +549,10 @@ void amdgpu_mes_validate_fw_version(struct amdgpu_device *adev); int amdgpu_mes_init(struct amdgpu_device *adev); void amdgpu_mes_fini(struct amdgpu_device *adev); +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); + int amdgpu_mes_suspend(struct amdgpu_device *adev, u32 xcc_id); int amdgpu_mes_resume(struct amdgpu_device *adev, u32 xcc_id); diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index a447562977ab..0ecfc0991e1f 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -6530,6 +6530,18 @@ static int gfx_v11_0_eop_irq(struct amdgpu_device *adev, DRM_DEBUG("IH: CP EOP\n"); + /* + * MES firmware debug-extension ("mes_dbgext") messages are delivered as + * an EOP interrupt from the MES pipe (me 3), carrying MES_DBGMSG (type 7) + * in the top 6 bits of the context word. Route them to the mes_dbgext + * drain instead of the (gfx/compute) EOP fence handling below. + */ + if (((entry->ring_id & 0x0c) >> 2) == 3 && + (entry->src_data[0] >> 26) == 7) { + amdgpu_mes_dbgext_notify(adev, entry->src_data[0]); + return 0; + } + if (!adev->gfx.disable_kq) { u8 me_id = (entry->ring_id & 0x0c) >> 2; u8 pipe_id = (entry->ring_id & 0x03) >> 0; diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index 33ff1afd7c4c..f507e8ada3fb 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -966,6 +966,13 @@ static int mes_v11_0_misc_op(struct amdgpu_mes *mes, misc_pkt.opcode = MESAPI_MISC__NOTIFY_WORK_ON_UNMAPPED_QUEUE; misc_pkt.queue_sch_level = AMD_PRIORITY_LEVEL_NORMAL; break; + case MES_MISC_OP_SETUP_MES_DBGEXT: + misc_pkt.opcode = MESAPI_MISC__SETUP_MES_DBGEXT; + misc_pkt.dbgext_init_data.dbg_ext_mc_addr = + input->setup_mes_dbgext.log_buffer_mc_addr; + misc_pkt.dbgext_init_data.u64_all = + input->setup_mes_dbgext.log_options; + break; default: drm_err(adev_to_drm(mes->adev), "unsupported misc op (%d)\n", input->op); return -EINVAL; @@ -1132,6 +1139,32 @@ static int mes_v11_0_detect_and_reset_hung_queues(struct amdgpu_mes *mes, offsetof(union MESAPI__RESET, api_status)); } +/* + * Enable/disable delivery of the MES firmware host interrupts at the CP. The + * MES firmware debug-message interrupt is delivered as a CP EOP from the MES + * pipe (me 3) and handled in gfx_v11_0_eop_irq(); it is gated by CPC_INT_CNTL + * in that ME/pipe context, selected via GRBM_GFX_CNTL. Mirrors the Windows KMD + * MES interrupt-enable path. Must run in process context (takes srbm_mutex). + */ +static int mes_v11_0_enable_dbgext_irq(struct amdgpu_mes *mes, bool enable) +{ + struct amdgpu_device *adev = mes->adev; + u32 cp_int_cntl; + + mutex_lock(&adev->srbm_mutex); + soc21_grbm_select(adev, 3, AMDGPU_MES_SCHED_PIPE, 0, 0); + + cp_int_cntl = RREG32_SOC15(GC, 0, regCPC_INT_CNTL); + cp_int_cntl = REG_SET_FIELD(cp_int_cntl, CPC_INT_CNTL, + TIME_STAMP_INT_ENABLE, enable ? 1 : 0); + WREG32_SOC15(GC, 0, regCPC_INT_CNTL, cp_int_cntl); + + soc21_grbm_select(adev, 0, 0, 0, 0); + mutex_unlock(&adev->srbm_mutex); + + return 0; +} + static const struct amdgpu_mes_funcs mes_v11_0_funcs = { .add_hw_queue = mes_v11_0_add_hw_queue, .remove_hw_queue = mes_v11_0_remove_hw_queue, @@ -1140,6 +1173,7 @@ static const struct amdgpu_mes_funcs mes_v11_0_funcs = { .suspend_gang = mes_v11_0_suspend_gang, .resume_gang = mes_v11_0_resume_gang, .misc_op = mes_v11_0_misc_op, + .enable_dbgext_irq = mes_v11_0_enable_dbgext_irq, .reset_hw_queue = mes_v11_0_reset_hw_queue, .detect_and_reset_hung_queues = mes_v11_0_detect_and_reset_hung_queues, }; @@ -2037,6 +2071,8 @@ static int mes_v11_0_hw_init(struct amdgpu_ip_block *ip_block) adev->gfx.kiq[0].ring.sched.ready = false; adev->mes.ring[0].sched.ready = true; + amdgpu_mes_dbgext_start(adev); + return 0; failure: @@ -2046,6 +2082,9 @@ static int mes_v11_0_hw_init(struct amdgpu_ip_block *ip_block) static int mes_v11_0_hw_fini(struct amdgpu_ip_block *ip_block) { + struct amdgpu_device *adev = ip_block->adev; + + amdgpu_mes_dbgext_stop(adev); return 0; } diff --git a/drivers/gpu/drm/amd/include/mes_v11_api_def.h b/drivers/gpu/drm/amd/include/mes_v11_api_def.h index a8e6ccc7817c..e251ccc41f9c 100644 --- a/drivers/gpu/drm/amd/include/mes_v11_api_def.h +++ b/drivers/gpu/drm/amd/include/mes_v11_api_def.h @@ -622,6 +622,7 @@ enum MESAPI_MISC_OPCODE { MESAPI_MISC__NOTIFY_TO_UNMAP_PROCESSES, MESAPI_MISC__CHANGE_CONFIG, MESAPI_MISC__LAUNCH_CLEANER_SHADER, + MESAPI_MISC__SETUP_MES_DBGEXT, MESAPI_MISC__MAX, }; @@ -702,6 +703,22 @@ struct CHANGE_CONFIG { } tdr_config; }; +/* + * MES firmware debug extension ("mes_dbgext"): the driver hands the MES a + * GART/GTT log buffer; the MES firmware writes text/binary log items into a + * circular, zone-partitioned buffer that the driver drains and prints. + */ +struct MES_DBGEXT_INIT_DATA { + uint64_t dbg_ext_mc_addr; + union { + struct { + uint64_t trigger_interrupt_per_new_msg : 1; + uint64_t reserved : 63; + }; + uint64_t u64_all; + }; +}; + union MESAPI__MISC { struct { union MES_API_HEADER header; @@ -717,6 +734,7 @@ union MESAPI__MISC { struct SET_SHADER_DEBUGGER set_shader_debugger; enum MES_AMD_PRIORITY_LEVEL queue_sch_level; struct CHANGE_CONFIG change_config; + struct MES_DBGEXT_INIT_DATA dbgext_init_data; uint32_t data[MISC_DATA_MAX_SIZE_IN_DWORDS]; }; -- 2.43.0