Enable the MES debug-extension log for gfx12. gfx12 routes mes_dbgext
through the mes_aux component, which uses a zone-partitioned buffer
layout (8-byte item headers, 24-bit length, per-zone rptr/wptr) instead
of the gfx11 flat circular buffer, so add a zoned parser selected via
mes->dbgext_zoned and translate the setup options to the mes_aux format.

Wire up mes_v12_0 (misc-op setup, dbgext IRQ enable, hw_init/hw_fini
start/stop) and the gfx_v12_0 EOP interrupt path, and add the required
mes_v12 API definitions.

Signed-off-by: Yongqiang Sun <[email protected]>
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c       | 219 +++++++++++++++++-
 drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h       |   3 +
 drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c        |  18 ++
 drivers/gpu/drm/amd/amdgpu/mes_v12_0.c        |  54 +++++
 drivers/gpu/drm/amd/include/mes_v12_api_def.h |  17 ++
 5 files changed, 305 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c
index 01544c4f10e2..e05fade51e98 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c
@@ -1302,6 +1302,55 @@ struct mes_dbgext_buf_header {
        u32 header_size;
 };
 
+/*
+ * gfx12 (Navi4x) MES firmware routes mes_dbgext through the shared "mes_aux"
+ * component, which uses a different, zone-partitioned buffer layout:
+ *
+ *   offset 0: struct { u32 zone_count; struct {u32 offset, 
length}[zone_count]; }
+ *
+ * Each zone starts (at its byte offset from the buffer base) with a 16-byte
+ * LOG_ZONE_HEADER {rptr, wptr, buffer_size, header_size} whose rptr/wptr are
+ * relative to the zone start and wrap from buffer_size back to header_size.
+ * Every log item begins with an 8-byte header: type, xor-signature, a 24-bit
+ * big-endian length (total item size including the header, in bytes[2..4]),
+ * then level, seq and a reserved byte.  Zone 0 carries human-readable text
+ * (printed to dmesg); other zones carry binary event/interrupt/api records
+ * (types 0x93..0x95) that are not text and are skipped.
+ *
+ * Unlike gfx11, the driver seeds only the total buffer size in the first 
dword;
+ * the firmware (mes_aux InitializeLogBuffer) reads it and writes the zone 
header
+ * in place.
+ */
+#define MES_DBGEXT_Z_ITEM_HDR_SIZE     8
+#define MES_DBGEXT_Z_MAX_ZONES         8
+
+/* Zoned (mes_aux) text log item types. */
+#define MES_DBGEXT_Z_MSG               0x80
+#define MES_DBGEXT_Z_ASSERT            0x81
+#define MES_DBGEXT_Z_HALT              0x82
+
+/*
+ * mes_aux option word (struct MesExtConfig) layout, which differs from the
+ * gfx11 MES_DBGEXT_INIT_DATA: bit0 is host_poll_msg (INVERTED sense - when 
set,
+ * the firmware does not raise the per-message interrupt), bit1 enables 
logging,
+ * and bit3 enables an internal write-back cache (left off for prompt 
delivery).
+ */
+#define MES_DBGEXT_AUX_OPT_HOST_POLL           (1ULL << 0)
+#define MES_DBGEXT_AUX_OPT_ENABLE_MES_LOG      (1ULL << 1)
+#define MES_DBGEXT_AUX_OPT_ENABLE_LOG_CACHE    (1ULL << 3)
+
+struct mes_dbgext_zone_info {
+       u32 offset;
+       u32 length;
+};
+
+struct mes_dbgext_zone_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.
@@ -1445,6 +1494,137 @@ static void mes_dbgext_process_flat(struct 
amdgpu_device *adev)
        kfree(item);
 }
 
+/*
+ * Drain a single zone of a zoned (gfx12/mes_aux) buffer.  @zbase points at the
+ * zone start (its LOG_ZONE_HEADER); rptr/wptr are relative to @zbase.  @item 
is
+ * caller-provided scratch of at least MES_DBGEXT_MAX_ITEM_SIZE + 1 bytes.
+ */
+static void mes_dbgext_process_zone(struct amdgpu_device *adev, u8 *zbase,
+                                   u8 *item)
+{
+       struct mes_dbgext_zone_header *zh =
+               (struct mes_dbgext_zone_header *)zbase;
+       u32 rptr, wptr, buffer_size, hdr_size;
+
+       buffer_size = READ_ONCE(zh->buffer_size);
+       hdr_size = READ_ONCE(zh->header_size);
+       rptr = READ_ONCE(zh->rptr);
+       wptr = READ_ONCE(zh->wptr);
+
+       /* Nothing to do until the firmware has written a new message. */
+       if (rptr == wptr)
+               return;
+
+       /* See the ordering note in mes_dbgext_process_flat(). */
+       dma_rmb();
+
+       if (hdr_size < sizeof(*zh) || buffer_size <= hdr_size ||
+           rptr < hdr_size || rptr >= buffer_size ||
+           wptr < hdr_size || wptr >= buffer_size)
+               return;
+
+       while (rptr != wptr) {
+               u8 hb[MES_DBGEXT_Z_ITEM_HDR_SIZE];
+               enum mes_dbgext_kind kind;
+               bool text = true;
+               u32 type, len;
+
+               mes_dbgext_buf_read(zbase, buffer_size, hdr_size, rptr,
+                                   hb, sizeof(hb));
+               type = hb[0];
+               len = ((u32)hb[2] << 16) | ((u32)hb[3] << 8) | hb[4];
+
+               /* sign = xor of all header bytes except the sign byte itself. 
*/
+               if ((u8)(hb[0] ^ hb[2] ^ hb[3] ^ hb[4] ^ hb[5] ^ hb[6]) != 
hb[1] ||
+                   len <= MES_DBGEXT_Z_ITEM_HDR_SIZE ||
+                   len > MES_DBGEXT_MAX_ITEM_SIZE ||
+                   len > buffer_size - hdr_size) {
+                       dev_dbg(adev->dev,
+                               "mes_dbgext: bad zoned item @%u (type 0x%x len 
%u), skipping to %u\n",
+                               rptr, type, len, wptr);
+                       rptr = wptr;
+                       break;
+               }
+
+               switch (type) {
+               case MES_DBGEXT_Z_MSG:
+                       kind = MES_DBGEXT_KIND_MSG;
+                       break;
+               case MES_DBGEXT_Z_ASSERT:
+                       kind = MES_DBGEXT_KIND_ASSERT;
+                       break;
+               case MES_DBGEXT_Z_HALT:
+                       kind = MES_DBGEXT_KIND_HALT;
+                       break;
+               default:
+                       /* binary event/interrupt/api record - not for dmesg */
+                       text = false;
+                       break;
+               }
+
+               if (text) {
+                       mes_dbgext_buf_read(zbase, buffer_size, hdr_size, rptr,
+                                           item, len);
+                       item[len] = '\0';
+                       mes_dbgext_print_item(adev, kind,
+                                       (char *)item + 
MES_DBGEXT_Z_ITEM_HDR_SIZE);
+               }
+
+               rptr += len;
+               if (rptr >= buffer_size)
+                       rptr -= (buffer_size - hdr_size);
+               if (rptr < hdr_size || rptr >= buffer_size) {
+                       rptr = wptr;
+                       break;
+               }
+       }
+
+       /* See the ordering note in mes_dbgext_process_flat(). */
+       dma_wmb();
+       WRITE_ONCE(zh->rptr, rptr);
+}
+
+static void mes_dbgext_process_zoned(struct amdgpu_device *adev)
+{
+       struct amdgpu_mes *mes = &adev->mes;
+       u8 *base = mes->dbgext_log_cpu_addr;
+       u32 zone_count, hdr_len, z;
+       u8 *item;
+
+       if (!base)
+               return;
+
+       zone_count = READ_ONCE(*(u32 *)base);
+       if (zone_count == 0 || zone_count > MES_DBGEXT_Z_MAX_ZONES)
+               return;
+
+       hdr_len = sizeof(u32) + zone_count * sizeof(struct 
mes_dbgext_zone_info);
+       if (hdr_len >= mes->dbgext_log_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;
+
+       for (z = 0; z < zone_count; z++) {
+               struct mes_dbgext_zone_info *zi =
+                       (struct mes_dbgext_zone_info *)(base + sizeof(u32)) + z;
+               u32 zoff = READ_ONCE(zi->offset);
+               u32 zlen = READ_ONCE(zi->length);
+
+               /* Skip a bogus zone descriptor rather than the whole buffer. */
+               if (zoff < hdr_len || zoff > mes->dbgext_log_size ||
+                   zlen < sizeof(struct mes_dbgext_zone_header) ||
+                   zlen > mes->dbgext_log_size - zoff)
+                       continue;
+
+               mes_dbgext_process_zone(adev, base + zoff, item);
+       }
+
+       kfree(item);
+}
+
 static void mes_dbgext_process_all(struct amdgpu_device *adev)
 {
        struct amdgpu_mes *mes = &adev->mes;
@@ -1452,7 +1632,10 @@ static void mes_dbgext_process_all(struct amdgpu_device 
*adev)
        if (!mes->dbgext_log_cpu_addr)
                return;
 
-       mes_dbgext_process_flat(adev);
+       if (mes->dbgext_zoned)
+               mes_dbgext_process_zoned(adev);
+       else
+               mes_dbgext_process_flat(adev);
 }
 
 static int amdgpu_mes_dbgext_reader(void *param)
@@ -1568,6 +1751,13 @@ static int amdgpu_mes_dbgext_start_locked(struct 
amdgpu_device *adev)
                /* Clamp to a sane range (4 KB .. 1 MB). */
                size = clamp(req_kb, 4U, 1024U);
                size = ALIGN((u32)size * SZ_1K, PAGE_SIZE);
+               /*
+                * The gfx12/mes_aux firmware splits the buffer into per-thread
+                * zones and rejects buffers that are not larger than its 4 KB
+                * minimum, so give the zoned format at least 8 KB.
+                */
+               if (mes->dbgext_zoned && size < SZ_8K)
+                       size = SZ_8K;
 
                r = amdgpu_bo_create_kernel(adev, size, PAGE_SIZE,
                                            AMDGPU_GEM_DOMAIN_GTT,
@@ -1595,7 +1785,14 @@ static int amdgpu_mes_dbgext_start_locked(struct 
amdgpu_device *adev)
         * re-lays its header, and the driver's rptr/wptr must restart clean.
         */
        memset(mes->dbgext_log_cpu_addr, 0, size);
-       {
+       if (mes->dbgext_zoned) {
+               /*
+                * gfx12/mes_aux: seed only the total buffer size in the first
+                * dword.  The firmware (InitializeLogBuffer) reads it during
+                * setup and lays out its own zone-partitioned header in place.
+                */
+               *(u32 *)mes->dbgext_log_cpu_addr = size;
+       } else {
                /*
                 * gfx11: initialize the LOG_BUFF_HEADER the firmware expects: a
                 * single circular byte stream following the 16-byte header.  
The
@@ -1631,10 +1828,19 @@ static int amdgpu_mes_dbgext_start_locked(struct 
amdgpu_device *adev)
        mes->dbgext_use_irq = use_irq;
 
        /*
-        * The options word (bit0 = trigger_interrupt_per_new_msg) is passed to
-        * the firmware verbatim.
+        * The word passed to the firmware differs by format.  gfx11 takes the
+        * options verbatim (bit0 = trigger_interrupt_per_new_msg).  The gfx12/
+        * mes_aux firmware uses a different layout, so translate: enable 
logging
+        * (bit1), select interrupt vs polling via host_poll_msg (bit0, inverted
+        * sense), and leave the write-back cache off for prompt delivery.
         */
-       fw_options = mes->dbgext_log_options;
+       if (mes->dbgext_zoned) {
+               fw_options = MES_DBGEXT_AUX_OPT_ENABLE_MES_LOG;
+               if (!use_irq)
+                       fw_options |= MES_DBGEXT_AUX_OPT_HOST_POLL;
+       } else {
+               fw_options = mes->dbgext_log_options;
+       }
 
        /* Enable delivery of the MES host interrupt at the CP (process ctx). */
        if (use_irq)
@@ -1672,8 +1878,9 @@ static int amdgpu_mes_dbgext_start_locked(struct 
amdgpu_device *adev)
        mes->dbgext_active = true;
 
        dev_info(adev->dev,
-                "mes_dbgext enabled: %u KB @ 0x%llx (%s, fw options 0x%llx)\n",
+                "mes_dbgext enabled: %u KB @ 0x%llx (%s, %s, fw options 
0x%llx)\n",
                 size / SZ_1K, mes->dbgext_log_gpu_addr,
+                mes->dbgext_zoned ? "zoned" : "flat",
                 use_irq ? "interrupt" : "polling", fw_options);
        return 0;
 
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h
index 1cd02101f54e..945d72f0ec8b 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h
@@ -166,6 +166,9 @@ struct amdgpu_mes {
        /* armed: FW attached + reader/irq running */
        bool                                    dbgext_active;
 
+       /* gfx12 mes_aux zone-partitioned buffer format */
+       bool                                    dbgext_zoned;
+
        struct work_struct              dbgext_work;
        struct task_struct              *dbgext_reader;         /* polling 
fallback */
        struct mutex                    dbgext_lock;            /* serializes 
runtime start/stop */
diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c 
b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c
index fdfee88e41e3..553c47a99e14 100644
--- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c
+++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c
@@ -4867,6 +4867,24 @@ static int gfx_v12_0_eop_irq(struct amdgpu_device *adev,
 
        DRM_DEBUG("IH: CP EOP\n");
 
+       /*
+        * MES firmware host interrupts - including the mes_dbgext debug-message
+        * notification - are raised via the RS64 host-interrupt path (the
+        * firmware triggers the CPC TIME_STAMP assertion).  Although the 
firmware
+        * tags the cookie with src_id CP_GENERIC_INT (177), the CP actually
+        * delivers this to the host IH as a CP EOP (src_id 181) on the MES
+        * scheduler pipe (me 3) - so it arrives through the normal CP_EOP
+        * registration, not a CP_GENERIC_INT one.  The gfx12 IH cookie does not
+        * preserve the MES interrupt type, so when dbgext is active, drain on 
any
+        * me3 EOP; draining is a no-op when the log buffer has no new data, and
+        * returning early keeps it out of the (gfx/compute/userq) fence paths.
+        */
+       if (adev->mes.dbgext_active &&
+           ((entry->ring_id & 0x0c) >> 2) == 3) {
+               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_v12_0.c 
b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c
index ee72afdb0a1d..ae5dc909e575 100644
--- a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c
+++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c
@@ -904,6 +904,13 @@ static int mes_v12_0_misc_op(struct amdgpu_mes *mes,
                misc_pkt.change_config.option.bits.limit_single_process =
                                
input->change_config.option.limit_single_process;
                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_ERROR("unsupported misc op (%d)\n", input->op);
@@ -1207,6 +1214,45 @@ static int mes_v12_0_inv_tlbs_pasid(struct amdgpu_mes 
*mes,
 
 }
 
+/*
+ * Enable/disable delivery of the MES firmware host interrupts at the CP.  The
+ * MES firmware debug-message ("mes_dbgext") interrupt is delivered to the IH
+ * as a CP EOP (src_id 181) on the MES scheduler pipe (me 3) and handled in
+ * gfx_v12_0_eop_irq(); it is gated by CPC_INT_CNTL in the me3/MES pipe 
context,
+ * selected via GRBM_GFX_CNTL.  Must run in process context (takes srbm_mutex).
+ *
+ * Although the firmware C code passes Rs64HostIntrGeneric1IntEnable, the RS64
+ * library routine that actually raises the interrupt (AsmRs64SetHostIntr)
+ * hardcodes the TIME_STAMP assertion bit (CP_INT_STAT_DEBUG bit 26).  So
+ * TIME_STAMP_INT_ENABLE - not GENERIC1_INT_ENABLE - is the CPC_INT_CNTL bit
+ * that lets the CP forward the assertion to the host IH.  CPC_INT_CNTL is
+ * per-(me,pipe); the enable must be set on both me3 pipes (0 and 1), matching
+ * the Windows/SR-IOV KMD path - setting it only on the scheduler pipe is not
+ * sufficient.
+ */
+static int mes_v12_0_enable_dbgext_irq(struct amdgpu_mes *mes, bool enable)
+{
+       struct amdgpu_device *adev = mes->adev;
+       u32 cp_int_cntl;
+       int pipe;
+
+       mutex_lock(&adev->srbm_mutex);
+
+       for (pipe = 0; pipe < 2; pipe++) {
+               soc24_grbm_select(adev, 3, 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);
+       }
+
+       soc24_grbm_select(adev, 0, 0, 0, 0);
+       mutex_unlock(&adev->srbm_mutex);
+
+       return 0;
+}
+
 static const struct amdgpu_mes_funcs mes_v12_0_funcs = {
        .add_hw_queue = mes_v12_0_add_hw_queue,
        .remove_hw_queue = mes_v12_0_remove_hw_queue,
@@ -1218,6 +1264,7 @@ static const struct amdgpu_mes_funcs mes_v12_0_funcs = {
        .reset_hw_queue = mes_v12_0_reset_hw_queue,
        .invalidate_tlbs_pasid = mes_v12_0_inv_tlbs_pasid,
        .detect_and_reset_hung_queues = mes_v12_0_detect_and_reset_hung_queues,
+       .enable_dbgext_irq = mes_v12_0_enable_dbgext_irq,
 };
 
 static int mes_v12_0_allocate_ucode_buffer(struct amdgpu_device *adev,
@@ -1825,6 +1872,8 @@ static int mes_v12_0_sw_init(struct amdgpu_ip_block 
*ip_block)
        adev->mes.kiq_hw_init = &mes_v12_0_kiq_hw_init;
        adev->mes.kiq_hw_fini = &mes_v12_0_kiq_hw_fini;
        adev->mes.enable_legacy_queue_map = true;
+       /* gfx12 MES routes mes_dbgext through mes_aux (zone-partitioned 
buffer). */
+       adev->mes.dbgext_zoned = true;
 
        adev->mes.event_log_size = adev->enable_uni_mes ?
                (AMDGPU_MAX_MES_PIPES * (AMDGPU_MES_LOG_BUFFER_SIZE + 
AMDGPU_MES_MSCRATCH_SIZE)) :
@@ -2097,6 +2146,8 @@ static int mes_v12_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:
@@ -2106,6 +2157,9 @@ static int mes_v12_0_hw_init(struct amdgpu_ip_block 
*ip_block)
 
 static int mes_v12_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_v12_api_def.h 
b/drivers/gpu/drm/amd/include/mes_v12_api_def.h
index cb7ebdfffeeb..3e214cb4f6e1 100644
--- a/drivers/gpu/drm/amd/include/mes_v12_api_def.h
+++ b/drivers/gpu/drm/amd/include/mes_v12_api_def.h
@@ -839,6 +839,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;
@@ -853,6 +869,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];
                };
                uint64_t                timestamp;
-- 
2.43.0

Reply via email to