drm/xe/gsc: Handle GSCCS ER interrupt

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: default avatarDaniele Ceraolo Spurio <daniele.ceraolospurio@intel.com>
Cc: Alan Previn <alan.previn.teres.alexis@intel.com>
Reviewed-by: default avatarAlan Previn <alan.previn.teres.alexis@intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20240304145634.820684-1-daniele.ceraolospurio@intel.com
parent 2c5b70f7
...@@ -38,4 +38,11 @@ ...@@ -38,4 +38,11 @@
#define HECI_H_GS1(base) XE_REG((base) + 0xc4c) #define HECI_H_GS1(base) XE_REG((base) + 0xc4c)
#define HECI_H_GS1_ER_PREP REG_BIT(0) #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 #endif
...@@ -486,6 +486,7 @@ ...@@ -486,6 +486,7 @@
#define XEHPC_BCS7_BCS8_INTR_MASK XE_REG(0x19011c) #define XEHPC_BCS7_BCS8_INTR_MASK XE_REG(0x19011c)
#define GT_WAIT_SEMAPHORE_INTERRUPT REG_BIT(11) #define GT_WAIT_SEMAPHORE_INTERRUPT REG_BIT(11)
#define GT_CONTEXT_SWITCH_INTERRUPT REG_BIT(8) #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_RENDER_PIPECTL_NOTIFY_INTERRUPT REG_BIT(4)
#define GT_CS_MASTER_ERROR_INTERRUPT REG_BIT(3) #define GT_CS_MASTER_ERROR_INTERRUPT REG_BIT(3)
#define GT_RENDER_USER_INTERRUPT REG_BIT(0) #define GT_RENDER_USER_INTERRUPT REG_BIT(0)
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include "xe_wa.h" #include "xe_wa.h"
#include "instructions/xe_gsc_commands.h" #include "instructions/xe_gsc_commands.h"
#include "regs/xe_gsc_regs.h" #include "regs/xe_gsc_regs.h"
#include "regs/xe_gt_regs.h"
static struct xe_gt * static struct xe_gt *
gsc_to_gt(struct xe_gsc *gsc) gsc_to_gt(struct xe_gsc *gsc)
...@@ -273,6 +274,44 @@ static int gsc_upload_and_init(struct xe_gsc *gsc) ...@@ -273,6 +274,44 @@ static int gsc_upload_and_init(struct xe_gsc *gsc)
return 0; 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) static void gsc_work(struct work_struct *work)
{ {
struct xe_gsc *gsc = container_of(work, typeof(*gsc), work); struct xe_gsc *gsc = container_of(work, typeof(*gsc), work);
...@@ -289,6 +328,12 @@ static void gsc_work(struct work_struct *work) ...@@ -289,6 +328,12 @@ static void gsc_work(struct work_struct *work)
xe_pm_runtime_get(xe); xe_pm_runtime_get(xe);
xe_force_wake_get(gt_to_fw(gt), XE_FW_GSC); 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) { if (actions & GSC_ACTION_FW_LOAD) {
ret = gsc_upload_and_init(gsc); ret = gsc_upload_and_init(gsc);
if (ret && ret != -EEXIST) if (ret && ret != -EEXIST)
...@@ -300,10 +345,28 @@ static void gsc_work(struct work_struct *work) ...@@ -300,10 +345,28 @@ static void gsc_work(struct work_struct *work)
if (actions & GSC_ACTION_SW_PROXY) if (actions & GSC_ACTION_SW_PROXY)
xe_gsc_proxy_request_handler(gsc); xe_gsc_proxy_request_handler(gsc);
out:
xe_force_wake_put(gt_to_fw(gt), XE_FW_GSC); xe_force_wake_put(gt_to_fw(gt), XE_FW_GSC);
xe_pm_runtime_put(xe); 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) int xe_gsc_init(struct xe_gsc *gsc)
{ {
struct xe_gt *gt = gsc_to_gt(gsc); struct xe_gt *gt = gsc_to_gt(gsc);
......
...@@ -9,12 +9,14 @@ ...@@ -9,12 +9,14 @@
#include "xe_gsc_types.h" #include "xe_gsc_types.h"
struct xe_gt; struct xe_gt;
struct xe_hw_engine;
int xe_gsc_init(struct xe_gsc *gsc); int xe_gsc_init(struct xe_gsc *gsc);
int xe_gsc_init_post_hwconfig(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_wait_for_worker_completion(struct xe_gsc *gsc);
void xe_gsc_load_start(struct xe_gsc *gsc); void xe_gsc_load_start(struct xe_gsc *gsc);
void xe_gsc_remove(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); void xe_gsc_wa_14015076503(struct xe_gt *gt, bool prep);
......
...@@ -47,6 +47,7 @@ struct xe_gsc { ...@@ -47,6 +47,7 @@ struct xe_gsc {
u32 work_actions; u32 work_actions;
#define GSC_ACTION_FW_LOAD BIT(0) #define GSC_ACTION_FW_LOAD BIT(0)
#define GSC_ACTION_SW_PROXY BIT(1) #define GSC_ACTION_SW_PROXY BIT(1)
#define GSC_ACTION_ER_COMPLETE BIT(2)
/** @proxy: sub-structure containing the SW proxy-related variables */ /** @proxy: sub-structure containing the SW proxy-related variables */
struct { struct {
......
...@@ -14,8 +14,10 @@ ...@@ -14,8 +14,10 @@
#include "xe_device.h" #include "xe_device.h"
#include "xe_execlist.h" #include "xe_execlist.h"
#include "xe_force_wake.h" #include "xe_force_wake.h"
#include "xe_gsc.h"
#include "xe_gt.h" #include "xe_gt.h"
#include "xe_gt_ccs_mode.h" #include "xe_gt_ccs_mode.h"
#include "xe_gt_printk.h"
#include "xe_gt_topology.h" #include "xe_gt_topology.h"
#include "xe_hw_fence.h" #include "xe_hw_fence.h"
#include "xe_irq.h" #include "xe_irq.h"
...@@ -463,6 +465,32 @@ static void hw_engine_init_early(struct xe_gt *gt, struct xe_hw_engine *hwe, ...@@ -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_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_min = XE_HW_ENGINE_PREEMPT_TIMEOUT_MIN;
hwe->eclass->sched_props.preempt_timeout_max = XE_HW_ENGINE_PREEMPT_TIMEOUT_MAX; 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 */ /* Record default props */
hwe->eclass->defaults = hwe->eclass->sched_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, ...@@ -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); xe_hw_engine_enable_ring(hwe);
}
/* We reserve the highest BCS instance for USM */ /* We reserve the highest BCS instance for USM */
if (xe->info.has_usm && hwe->class == XE_ENGINE_CLASS_COPY) if (xe->info.has_usm && hwe->class == XE_ENGINE_CLASS_COPY)
......
...@@ -187,7 +187,7 @@ void xe_irq_enable_hwe(struct xe_gt *gt) ...@@ -187,7 +187,7 @@ void xe_irq_enable_hwe(struct xe_gt *gt)
* GSCCS interrupts, but it has its own mask register. * GSCCS interrupts, but it has its own mask register.
*/ */
if (xe_hw_engine_mask_per_class(gt, XE_ENGINE_CLASS_OTHER)) { 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); heci_mask = GSC_IRQ_INTF(1);
} else if (HAS_HECI_GSCFI(xe)) { } else if (HAS_HECI_GSCFI(xe)) {
gsc_mask = GSC_IRQ_INTF(1); gsc_mask = GSC_IRQ_INTF(1);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment