drm/xe/gsc: Handle GSCCS ER interrupt
authorDaniele Ceraolo Spurio <daniele.ceraolospurio@intel.com>
Mon, 4 Mar 2024 14:56:34 +0000 (06:56 -0800)
committerDaniele Ceraolo Spurio <daniele.ceraolospurio@intel.com>
Thu, 14 Mar 2024 21:47:13 +0000 (14:47 -0700)
Starting on Xe2, the GSCCS engine reset is a 2-step process. When the
driver or the GuC hits the GDRST register, the CS is immediately reset
and a success is reported, but the GSC shim continues its reset in the
background. While the shim reset is ongoing, the CS is able to accept
new context submission, but any commands that require the shim will
be stalled until the reset is completed. This means that we can keep
submitting to the GSCCS as long as we make sure that the preemption
timeout is big enough to cover any delay introduced by the reset; since
the GSC preempt timeout is not tunable at runtime, we only need to check
that the value set in kconfig is big enough (and increase it if it
isn't).
When the shim reset completes, a specific CS interrupt is triggered,
in response to which we need to check the GSCI_TIMER_STATUS register
to see if the reset was successful or not.
Note that the GSCI_TIMER_STATUS register is not power save/restored,
so it gets reset on MC6 entry. However, a reset failure stops MC6,
so in that scenario we're always guaranteed to find the correct value.

Since we can't check the register within interrupt context, the
existing GSC worker has been updated to handle it.
The expected action to take on ER failure is to trigger a driver FLR,
but we still don't support that, so for now we just print an error. A
comment has been added to the code to keep track of the FLR requirement.

v2: Add a check for the initial timeout value (Alan)

Signed-off-by: Daniele Ceraolo Spurio <daniele.ceraolospurio@intel.com>
Cc: Alan Previn <alan.previn.teres.alexis@intel.com>
Reviewed-by: Alan Previn <alan.previn.teres.alexis@intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20240304145634.820684-1-daniele.ceraolospurio@intel.com
drivers/gpu/drm/xe/regs/xe_gsc_regs.h
drivers/gpu/drm/xe/regs/xe_gt_regs.h
drivers/gpu/drm/xe/xe_gsc.c
drivers/gpu/drm/xe/xe_gsc.h
drivers/gpu/drm/xe/xe_gsc_types.h
drivers/gpu/drm/xe/xe_hw_engine.c
drivers/gpu/drm/xe/xe_irq.c

index 9886ec9cb08e91cf26ff723ff70bbcb99846723e..e2a925be137cd33dc32c89687d8f37ad6b23924f 100644 (file)
 #define HECI_H_GS1(base)       XE_REG((base) + 0xc4c)
 #define   HECI_H_GS1_ER_PREP   REG_BIT(0)
 
+#define GSCI_TIMER_STATUS                              XE_REG(0x11ca28)
+#define   GSCI_TIMER_STATUS_VALUE                      REG_GENMASK(1, 0)
+#define   GSCI_TIMER_STATUS_RESET_IN_PROGRESS          0
+#define   GSCI_TIMER_STATUS_TIMER_EXPIRED              1
+#define   GSCI_TIMER_STATUS_RESET_COMPLETE             2
+#define   GSCI_TIMER_STATUS_OUT_OF_RESET               3
+
 #endif
index 15ac2d284d48f6cdf59ded2fff9ee459ae4242a5..abb6e86fe3671695179eb8cb9ecc63d33f62a250 100644 (file)
 #define XEHPC_BCS7_BCS8_INTR_MASK              XE_REG(0x19011c)
 #define   GT_WAIT_SEMAPHORE_INTERRUPT          REG_BIT(11)
 #define   GT_CONTEXT_SWITCH_INTERRUPT          REG_BIT(8)
+#define   GSC_ER_COMPLETE                      REG_BIT(5)
 #define   GT_RENDER_PIPECTL_NOTIFY_INTERRUPT   REG_BIT(4)
 #define   GT_CS_MASTER_ERROR_INTERRUPT         REG_BIT(3)
 #define   GT_RENDER_USER_INTERRUPT             REG_BIT(0)
index d9aa815a5bc2c81fce76101a59836fe798360f49..09f6e789992100a5cbf3118df25056ca08043411 100644 (file)
@@ -27,6 +27,7 @@
 #include "xe_wa.h"
 #include "instructions/xe_gsc_commands.h"
 #include "regs/xe_gsc_regs.h"
+#include "regs/xe_gt_regs.h"
 
 static struct xe_gt *
 gsc_to_gt(struct xe_gsc *gsc)
@@ -273,6 +274,44 @@ static int gsc_upload_and_init(struct xe_gsc *gsc)
        return 0;
 }
 
+static int gsc_er_complete(struct xe_gt *gt)
+{
+       u32 er_status;
+
+       if (!gsc_fw_is_loaded(gt))
+               return 0;
+
+       /*
+        * Starting on Xe2, the GSCCS engine reset is a 2-step process. When the
+        * driver or the GuC hit the GDRST register, the CS is immediately reset
+        * and a success is reported, but the GSC shim keeps resetting in the
+        * background. While the shim reset is ongoing, the CS is able to accept
+        * new context submission, but any commands that require the shim will
+        * be stalled until the reset is completed. This means that we can keep
+        * submitting to the GSCCS as long as we make sure that the preemption
+        * timeout is big enough to cover any delay introduced by the reset.
+        * When the shim reset completes, a specific CS interrupt is triggered,
+        * in response to which we need to check the GSCI_TIMER_STATUS register
+        * to see if the reset was successful or not.
+        * Note that the GSCI_TIMER_STATUS register is not power save/restored,
+        * so it gets reset on MC6 entry. However, a reset failure stops MC6,
+        * so in that scenario we're always guaranteed to find the correct
+        * value.
+        */
+       er_status = xe_mmio_read32(gt, GSCI_TIMER_STATUS) & GSCI_TIMER_STATUS_VALUE;
+
+       if (er_status == GSCI_TIMER_STATUS_TIMER_EXPIRED) {
+               /*
+                * XXX: we should trigger an FLR here, but we don't have support
+                * for that yet.
+                */
+               xe_gt_err(gt, "GSC ER timed out!\n");
+               return -EIO;
+       }
+
+       return 0;
+}
+
 static void gsc_work(struct work_struct *work)
 {
        struct xe_gsc *gsc = container_of(work, typeof(*gsc), work);
@@ -289,6 +328,12 @@ static void gsc_work(struct work_struct *work)
        xe_pm_runtime_get(xe);
        xe_force_wake_get(gt_to_fw(gt), XE_FW_GSC);
 
+       if (actions & GSC_ACTION_ER_COMPLETE) {
+               ret = gsc_er_complete(gt);
+               if (ret)
+                       goto out;
+       }
+
        if (actions & GSC_ACTION_FW_LOAD) {
                ret = gsc_upload_and_init(gsc);
                if (ret && ret != -EEXIST)
@@ -300,10 +345,28 @@ static void gsc_work(struct work_struct *work)
        if (actions & GSC_ACTION_SW_PROXY)
                xe_gsc_proxy_request_handler(gsc);
 
+out:
        xe_force_wake_put(gt_to_fw(gt), XE_FW_GSC);
        xe_pm_runtime_put(xe);
 }
 
+void xe_gsc_hwe_irq_handler(struct xe_hw_engine *hwe, u16 intr_vec)
+{
+       struct xe_gt *gt = hwe->gt;
+       struct xe_gsc *gsc = &gt->uc.gsc;
+
+       if (unlikely(!intr_vec))
+               return;
+
+       if (intr_vec & GSC_ER_COMPLETE) {
+               spin_lock(&gsc->lock);
+               gsc->work_actions |= GSC_ACTION_ER_COMPLETE;
+               spin_unlock(&gsc->lock);
+
+               queue_work(gsc->wq, &gsc->work);
+       }
+}
+
 int xe_gsc_init(struct xe_gsc *gsc)
 {
        struct xe_gt *gt = gsc_to_gt(gsc);
index c6fb32e3fd79645a78488b103a22f075a3397941..dd16e9b8b8942f640b3ece903cef9548a56dc7ef 100644 (file)
@@ -9,12 +9,14 @@
 #include "xe_gsc_types.h"
 
 struct xe_gt;
+struct xe_hw_engine;
 
 int xe_gsc_init(struct xe_gsc *gsc);
 int xe_gsc_init_post_hwconfig(struct xe_gsc *gsc);
 void xe_gsc_wait_for_worker_completion(struct xe_gsc *gsc);
 void xe_gsc_load_start(struct xe_gsc *gsc);
 void xe_gsc_remove(struct xe_gsc *gsc);
+void xe_gsc_hwe_irq_handler(struct xe_hw_engine *hwe, u16 intr_vec);
 
 void xe_gsc_wa_14015076503(struct xe_gt *gt, bool prep);
 
index 138d8cc0f19c22337e38f3a207e32465c18b189e..5926de20214c8a9053e4bc430cb3d52ccb785cbe 100644 (file)
@@ -47,6 +47,7 @@ struct xe_gsc {
        u32 work_actions;
 #define GSC_ACTION_FW_LOAD BIT(0)
 #define GSC_ACTION_SW_PROXY BIT(1)
+#define GSC_ACTION_ER_COMPLETE BIT(2)
 
        /** @proxy: sub-structure containing the SW proxy-related variables */
        struct {
index b5e83ea172f3ffccf16470759ad19a71472ed4dd..2c5615130a3875cbf3bfc02be85f9a46fb45c26f 100644 (file)
 #include "xe_device.h"
 #include "xe_execlist.h"
 #include "xe_force_wake.h"
+#include "xe_gsc.h"
 #include "xe_gt.h"
 #include "xe_gt_ccs_mode.h"
+#include "xe_gt_printk.h"
 #include "xe_gt_topology.h"
 #include "xe_hw_fence.h"
 #include "xe_irq.h"
@@ -463,6 +465,32 @@ static void hw_engine_init_early(struct xe_gt *gt, struct xe_hw_engine *hwe,
                hwe->eclass->sched_props.preempt_timeout_us = XE_HW_ENGINE_PREEMPT_TIMEOUT;
                hwe->eclass->sched_props.preempt_timeout_min = XE_HW_ENGINE_PREEMPT_TIMEOUT_MIN;
                hwe->eclass->sched_props.preempt_timeout_max = XE_HW_ENGINE_PREEMPT_TIMEOUT_MAX;
+
+               /*
+                * The GSC engine can accept submissions while the GSC shim is
+                * being reset, during which time the submission is stalled. In
+                * the worst case, the shim reset can take up to the maximum GSC
+                * command execution time (250ms), so the request start can be
+                * delayed by that much; the request itself can take that long
+                * without being preemptible, which means worst case it can
+                * theoretically take up to 500ms for a preemption to go through
+                * on the GSC engine. Adding to that an extra 100ms as a safety
+                * margin, we get a minimum recommended timeout of 600ms.
+                * The preempt_timeout value can't be tuned for OTHER_CLASS
+                * because the class is reserved for kernel usage, so we just
+                * need to make sure that the starting value is above that
+                * threshold; since our default value (640ms) is greater than
+                * 600ms, the only way we can go below is via a kconfig setting.
+                * If that happens, log it in dmesg and update the value.
+                */
+               if (hwe->class == XE_ENGINE_CLASS_OTHER) {
+                       const u32 min_preempt_timeout = 600 * 1000;
+                       if (hwe->eclass->sched_props.preempt_timeout_us < min_preempt_timeout) {
+                               hwe->eclass->sched_props.preempt_timeout_us = min_preempt_timeout;
+                               xe_gt_notice(gt, "Increasing preempt_timeout for GSC to 600ms\n");
+                       }
+               }
+
                /* Record default props */
                hwe->eclass->defaults = hwe->eclass->sched_props;
        }
@@ -509,8 +537,13 @@ static int hw_engine_init(struct xe_gt *gt, struct xe_hw_engine *hwe,
                }
        }
 
-       if (xe_device_uc_enabled(xe))
+       if (xe_device_uc_enabled(xe)) {
+               /* GSCCS has a special interrupt for reset */
+               if (hwe->class == XE_ENGINE_CLASS_OTHER)
+                       hwe->irq_handler = xe_gsc_hwe_irq_handler;
+
                xe_hw_engine_enable_ring(hwe);
+       }
 
        /* We reserve the highest BCS instance for USM */
        if (xe->info.has_usm && hwe->class == XE_ENGINE_CLASS_COPY)
index 2f5d179e0d00b7b8cf09ef04ec2fc4a6c7b1150f..2275ca35a3c79552b7e08debb5b31a90dee4d8c1 100644 (file)
@@ -187,7 +187,7 @@ void xe_irq_enable_hwe(struct xe_gt *gt)
                 * GSCCS interrupts, but it has its own mask register.
                 */
                if (xe_hw_engine_mask_per_class(gt, XE_ENGINE_CLASS_OTHER)) {
-                       gsc_mask = irqs;
+                       gsc_mask = irqs | GSC_ER_COMPLETE;
                        heci_mask = GSC_IRQ_INTF(1);
                } else if (HAS_HECI_GSCFI(xe)) {
                        gsc_mask = GSC_IRQ_INTF(1);