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

Reply via email to