On Fri, Oct 16, 2026 at 03:31:02AM +0000, Jia Yao wrote:
> Wa_22013059131 sets FORCE_1_SUB_MESSAGE_PER_FRAGMENT in LSC_CHICKEN_BIT_0
> at engine init, but this is known to cause GPU hangs in certain workloads.
> Add I915_CONTEXT_PARAM_WA_22013059131 so userspace that handles the
> workaround itself (e.g. by limiting SLM size) can set it to 1 to let the
> kernel know bit 15 programming is not needed. The register is not
> context-saved by hardware, so the value is programmed on context switch
> via the indirect context batchbuffer, and the old unconditional write in
> intel_workarounds.c is removed.
>
> LSC_CHICKEN_BIT_0 is shared by the RCS and CCS engines, not per-context
> state, and GuC only runs RCS and CCS concurrently when they share an
> address space, i.e. belong to the same DRM client. Tracking the opt-out
> per client is therefore both necessary and sufficient to avoid the race.
> The value is latched once, by whichever comes first between an explicit
> CONTEXT_CREATE_EXT_SETPARAM and the first context of the client being
> submitted, and a later request for the opposite value gets -EINVAL; this
> covers the default context (id 0) and needs no new ioctl. The client is
> the unit of consistency, not the register's true isolation boundary:
> unrelated clients can still override each other on RCS and CCS, which is
> unchanged from current behaviour.
>
> v13:
> - Reject any value other than 0 or 1 with -EINVAL, instead of silently
> treating any non-zero value the same as 1, so other values remain
> available to give explicit meaning to in the future (Matt)
> - Drop the v11 ctx_setparam() support for
> I915_CONTEXT_PARAM_WA_22013059131 added by Sashiko for context 0
> coverage. The parameter is creation-time only and current
> compute-runtime usage relies solely on CONTEXT_CREATE_EXT_SETPARAM.
> Retain getparam() support. (Matt)
> - Rename I915_WA_22013059131_{UNSET,KERNEL,USERSPACE} to
> I915_WA_22013059131_CLIENT_{UNSET,KERNEL,USERSPACE} to distinguish them
> from the CONTEXT_WA_22013059131_APPLY context flag (Matt)
>
> v12:
> - Don't dereference ce->gem_context / ctx->client from lrc_update_regs(),
> which also runs from reset paths (guc_reset_state(), lrc_reset()) where
> the ctx lifetime is not guaranteed by the caller. Latch the decision
> once into CONTEXT_WA_22013059131_APPLY from
> intel_context_alloc_state(), where the pinner holds a ctx reference,
> and only sample that bit afterwards (Sashiko)
>
> v11:
> - Add the missing I915_CONTEXT_PARAM_WA_22013059131 cases to
> ctx_setparam() and i915_gem_context_getparam_ioctl(), so the
> parameter can also be set on an already-finalized context (e.g. the
> default context, id 0) and queried back, not just set at proto-context
> creation time (Sashiko)
>
> v10:
> - Move the opt-out from intel_context to i915_drm_client, latched once
> per client (by explicit setparam or first LRC init) instead of tracked
> per intel_context, so all contexts of a client - including the
> default context - always agree and conflicting requests get -EINVAL
> (Joonas, Sashiko, Matt)
> - Note that GuC's requirement for RCS/CCS contexts to share an address
> space before running concurrently is what makes client-wide latching
> sufficient to prevent the race (Matt)
> - Document that this requirement applies to the whole fd, so userspace
> sharing an fd across an interop boundary must coordinate ordering
> itself
>
> v9:
> - Restrict Wa_22013059131 to compute engine only in
> gen12_emit_indirect_ctx_xcs() (Sashiko)
>
> v8:
> - Clarify in the uAPI comment that setting this parameter only opts out
> of LSC_CHICKEN_BIT_0 bit 15 (FORCE_1_SUB_MESSAGE_PER_FRAGMENT);
> LSC_CHICKEN_BIT_0_UDW MAXREQS_PER_BANK remains unconditionally
> programmed by the kernel as the other part of Wa_22013059131
>
> v7:
> - Reject ioctl with -ENODEV on non-DG2-G11 platforms
>
> v6:
> - Remove excessive blank lines
>
> v5:
> - Remove fix and stable
>
> v4:
> - Add a link of the userspace using this API
>
> v3:
> - Kernel-internal context will not change workaround settings
>
> Bspec: 54833
> Link: https://github.com/intel/compute-runtime/pull/919
> Cc: [email protected]
> Cc: Shuicheng Lin <[email protected]>
> Cc: Matt Roper <[email protected]>
> Cc: Joonas Lahtinen <[email protected]>
> Cc: Rodrigo Vivi <[email protected]>
> Cc: Maciej Plewka <[email protected]>
> Cc: Andi Shyti <[email protected]>
> Signed-off-by: Jia Yao <[email protected]>
It's been a couple years since I worked closely with the i915 code, but
the changes here look okay to me.
Reviewed-by: Matt Roper <[email protected]>
Matt
> ---
> drivers/gpu/drm/i915/gem/i915_gem_context.c | 53 +++++++++++++++++
> drivers/gpu/drm/i915/gt/intel_context.c | 2 +
> drivers/gpu/drm/i915/gt/intel_context_types.h | 1 +
> drivers/gpu/drm/i915/gt/intel_lrc.c | 59 ++++++++++++++++++-
> drivers/gpu/drm/i915/gt/intel_lrc.h | 3 +
> drivers/gpu/drm/i915/gt/intel_workarounds.c | 10 ++--
> drivers/gpu/drm/i915/i915_drm_client.h | 57 ++++++++++++++++++
> include/uapi/drm/i915_drm.h | 25 ++++++++
> 8 files changed, 204 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/gpu/drm/i915/gem/i915_gem_context.c
> b/drivers/gpu/drm/i915/gem/i915_gem_context.c
> index 6ac0f23570f3..c2d70fbde7d3 100644
> --- a/drivers/gpu/drm/i915/gem/i915_gem_context.c
> +++ b/drivers/gpu/drm/i915/gem/i915_gem_context.c
> @@ -874,6 +874,39 @@ static int set_proto_ctx_sseu(struct
> drm_i915_file_private *fpriv,
> return 0;
> }
>
> +/*
> + * Wa_22013059131:dg2 - Latch who owns LSC_CHICKEN_BIT_0 bit 15 for every
> + * context of @fpriv. The register is shared by the RCS and CCS engines, so
> + * the first context to express a preference decides for the whole client
> + * and any later context asking for the opposite is rejected, rather than
> + * letting the two race over the register at context-switch time.
> + *
> + * The owner is also latched, to the kernel, the first time a context of
> + * the client is pinned (see lrc_latch_wa_22013059131()), so opting out is
> + * only possible while none of the client's RCS/CCS contexts has run yet.
> + */
> +static int set_client_wa_22013059131(struct drm_i915_file_private *fpriv,
> + u64 value)
> +{
> + int want;
> +
> + /*
> + * Only 0 and 1 are currently defined; reject anything else now so
> + * that other values remain available to give explicit meaning to in
> + * the future, rather than silently treating them as 1 today.
> + */
> + if (value > 1)
> + return -EINVAL;
> +
> + want = value ? I915_WA_22013059131_CLIENT_USERSPACE :
> + I915_WA_22013059131_CLIENT_KERNEL;
> +
> + if (i915_drm_client_latch_wa_22013059131(fpriv->client, want) != want)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> static int set_proto_ctx_param(struct drm_i915_file_private *fpriv,
> struct i915_gem_proto_context *pc,
> struct drm_i915_gem_context_param *args)
> @@ -911,6 +944,15 @@ static int set_proto_ctx_param(struct
> drm_i915_file_private *fpriv,
> ret = -EINVAL;
> break;
>
> + case I915_CONTEXT_PARAM_WA_22013059131:
> + if (args->size)
> + ret = -EINVAL;
> + else if (!IS_DG2_G11(i915))
> + ret = -ENODEV;
> + else
> + ret = set_client_wa_22013059131(fpriv, args->value);
> + break;
> +
> case I915_CONTEXT_PARAM_RECOVERABLE:
> if (args->size)
> ret = -EINVAL;
> @@ -2249,6 +2291,7 @@ static int ctx_setparam(struct drm_i915_file_private
> *fpriv,
> case I915_CONTEXT_PARAM_RINGSIZE:
> case I915_CONTEXT_PARAM_VM:
> case I915_CONTEXT_PARAM_ENGINES:
> + case I915_CONTEXT_PARAM_WA_22013059131: /* creation-time only */
> default:
> ret = -EINVAL;
> break;
> @@ -2583,6 +2626,16 @@ int i915_gem_context_getparam_ioctl(struct drm_device
> *dev, void *data,
> ret = get_protected(ctx, args);
> break;
>
> + case I915_CONTEXT_PARAM_WA_22013059131:
> + if (!IS_DG2_G11(ctx->i915)) {
> + ret = -ENODEV;
> + } else {
> + args->size = 0;
> + args->value =
> atomic_read(&file_priv->client->wa_22013059131_owner) ==
> + I915_WA_22013059131_CLIENT_USERSPACE;
> + }
> + break;
> +
> case I915_CONTEXT_PARAM_NO_ZEROMAP:
> case I915_CONTEXT_PARAM_BAN_PERIOD:
> case I915_CONTEXT_PARAM_ENGINES:
> diff --git a/drivers/gpu/drm/i915/gt/intel_context.c
> b/drivers/gpu/drm/i915/gt/intel_context.c
> index b1b8695ba7c9..dc1b69631046 100644
> --- a/drivers/gpu/drm/i915/gt/intel_context.c
> +++ b/drivers/gpu/drm/i915/gt/intel_context.c
> @@ -13,6 +13,7 @@
> #include "intel_context.h"
> #include "intel_engine.h"
> #include "intel_engine_pm.h"
> +#include "intel_lrc.h"
> #include "intel_ring.h"
>
> static struct kmem_cache *slab_ce;
> @@ -80,6 +81,7 @@ int intel_context_alloc_state(struct intel_context *ce)
> if (ctx->client)
> i915_drm_client_add_context_objects(ctx->client,
> ce);
> + lrc_latch_wa_22013059131(ce, ctx);
> i915_gem_context_put(ctx);
> }
> }
> diff --git a/drivers/gpu/drm/i915/gt/intel_context_types.h
> b/drivers/gpu/drm/i915/gt/intel_context_types.h
> index 10070ee4d74c..928eab48f38d 100644
> --- a/drivers/gpu/drm/i915/gt/intel_context_types.h
> +++ b/drivers/gpu/drm/i915/gt/intel_context_types.h
> @@ -133,6 +133,7 @@ struct intel_context {
> #define CONTEXT_EXITING 13
> #define CONTEXT_LOW_LATENCY 14
> #define CONTEXT_OWN_STATE 15
> +#define CONTEXT_WA_22013059131_APPLY 16 /* see lrc_latch_wa_22013059131() */
>
> struct {
> u64 timeout_us;
> diff --git a/drivers/gpu/drm/i915/gt/intel_lrc.c
> b/drivers/gpu/drm/i915/gt/intel_lrc.c
> index 147d22907960..c94b7e3d3980 100644
> --- a/drivers/gpu/drm/i915/gt/intel_lrc.c
> +++ b/drivers/gpu/drm/i915/gt/intel_lrc.c
> @@ -5,9 +5,11 @@
>
> #include <drm/drm_print.h>
>
> +#include "gem/i915_gem_context.h"
> #include "gem/i915_gem_lmem.h"
>
> #include "gen8_engine_cs.h"
> +#include "i915_drm_client.h"
> #include "i915_drv.h"
> #include "i915_perf.h"
> #include "i915_reg.h"
> @@ -1348,6 +1350,50 @@ gen12_invalidate_state_cache(u32 *cs)
> return cs;
> }
>
> +/* Latched on first pin while the pinner holds a ctx ref; see
> intel_context_alloc_state(). */
> +void lrc_latch_wa_22013059131(struct intel_context *ce,
> + struct i915_gem_context *ctx)
> +{
> + int owner;
> +
> + if (!IS_DG2_G11(ce->engine->i915))
> + return;
> +
> + /* Only RCS and CCS ever emit this workaround; don't latch for others.
> */
> + if (ce->engine->class != RENDER_CLASS && ce->engine->class !=
> COMPUTE_CLASS)
> + return;
> +
> + owner = I915_WA_22013059131_CLIENT_KERNEL;
> + if (ctx->client)
> + owner = i915_drm_client_latch_wa_22013059131(ctx->client,
> owner);
> +
> + if (owner != I915_WA_22013059131_CLIENT_USERSPACE)
> + set_bit(CONTEXT_WA_22013059131_APPLY, &ce->flags);
> +}
> +
> +static u32 *
> +dg2_g11_emit_wa_22013059131(const struct intel_context *ce, u32 *cs)
> +{
> + /*
> + * While re-writing LSC_CHICKEN_BIT_0 for Wa_22013059131, the
> + * other bits of the register will also get overwritten. The
> + * hardware default for all other bits is 0, but any workarounds
> + * that adjust the other bits in the lower dword of the register
> + * also need to be re-applied here. At the moment that's just
> + * Wa_22014226127, which is always set for DG2-G11 platforms.
> + */
> + u32 val = DISABLE_D8_D16_COASLESCE;
> +
> + if (test_bit(CONTEXT_WA_22013059131_APPLY, &ce->flags))
> + val |= FORCE_1_SUB_MESSAGE_PER_FRAGMENT;
> +
> + *cs++ = MI_LOAD_REGISTER_IMM(1);
> + *cs++ = i915_mmio_reg_offset(LSC_CHICKEN_BIT_0);
> + *cs++ = val;
> +
> + return cs;
> +}
> +
> static u32 *
> gen12_emit_indirect_ctx_rcs(const struct intel_context *ce, u32 *cs)
> {
> @@ -1371,6 +1417,10 @@ gen12_emit_indirect_ctx_rcs(const struct intel_context
> *ce, u32 *cs)
> IS_DG2(ce->engine->i915))
> cs = dg2_emit_draw_watermark_setting(cs);
>
> + /* Wa_22013059131:dg2 */
> + if (IS_DG2_G11(ce->engine->i915))
> + cs = dg2_g11_emit_wa_22013059131(ce, cs);
> +
> return cs;
> }
>
> @@ -1387,7 +1437,14 @@ gen12_emit_indirect_ctx_xcs(const struct intel_context
> *ce, u32 *cs)
>
> PIPE_CONTROL_INSTRUCTION_CACHE_INVALIDATE,
> 0);
>
> - return gen12_emit_aux_table_inv(ce->engine, cs);
> + cs = gen12_emit_aux_table_inv(ce->engine, cs);
> +
> + /* Wa_22013059131:dg2 */
> + if (IS_DG2_G11(ce->engine->i915))
> + if (ce->engine->class == COMPUTE_CLASS)
> + cs = dg2_g11_emit_wa_22013059131(ce, cs);
> +
> + return cs;
> }
>
> static u32 *xehp_emit_fastcolor_blt_wabb(const struct intel_context *ce, u32
> *cs)
> diff --git a/drivers/gpu/drm/i915/gt/intel_lrc.h
> b/drivers/gpu/drm/i915/gt/intel_lrc.h
> index 7111bae759f3..cc204ac2d7bf 100644
> --- a/drivers/gpu/drm/i915/gt/intel_lrc.h
> +++ b/drivers/gpu/drm/i915/gt/intel_lrc.h
> @@ -14,6 +14,7 @@
> #include "intel_context.h"
>
> struct drm_i915_gem_object;
> +struct i915_gem_context;
> struct i915_gem_ww_ctx;
> struct intel_engine_cs;
> struct intel_ring;
> @@ -35,6 +36,8 @@ void lrc_fini_wa_ctx(struct intel_engine_cs *engine);
>
> int lrc_alloc(struct intel_context *ce,
> struct intel_engine_cs *engine);
> +void lrc_latch_wa_22013059131(struct intel_context *ce,
> + struct i915_gem_context *ctx);
> void lrc_reset(struct intel_context *ce);
> void lrc_fini(struct intel_context *ce);
> void lrc_destroy(struct kref *kref);
> diff --git a/drivers/gpu/drm/i915/gt/intel_workarounds.c
> b/drivers/gpu/drm/i915/gt/intel_workarounds.c
> index 24ea5d8d529c..ef6eea3ab597 100644
> --- a/drivers/gpu/drm/i915/gt/intel_workarounds.c
> +++ b/drivers/gpu/drm/i915/gt/intel_workarounds.c
> @@ -2840,7 +2840,11 @@ general_render_compute_wa_init(struct intel_engine_cs
> *engine, struct i915_wa_li
> if (IS_GFX_GT_IP_STEP(gt, IP_VER(12, 70), STEP_A0, STEP_B0) ||
> IS_GFX_GT_IP_STEP(gt, IP_VER(12, 71), STEP_A0, STEP_B0) ||
> IS_DG2(i915)) {
> - /* Wa_22014226127 */
> + /*
> + * Wa_22014226127: Note that this workaround also needs to be
> + * re-applied in intel_lrc.c when LSC_CHICKEN_BIT_0 is
> + * re-written for Wa_22013059131.
> + */
> wa_mcr_write_or(wal, LSC_CHICKEN_BIT_0,
> DISABLE_D8_D16_COASLESCE);
> }
>
> @@ -2867,10 +2871,6 @@ general_render_compute_wa_init(struct intel_engine_cs
> *engine, struct i915_wa_li
> MAXREQS_PER_BANK,
> REG_FIELD_PREP(MAXREQS_PER_BANK, 2));
>
> - /* Wa_22013059131:dg2 */
> - wa_mcr_write_or(wal, LSC_CHICKEN_BIT_0,
> - FORCE_1_SUB_MESSAGE_PER_FRAGMENT);
> -
> /*
> * Wa_22012654132
> *
> diff --git a/drivers/gpu/drm/i915/i915_drm_client.h
> b/drivers/gpu/drm/i915/i915_drm_client.h
> index 2e7a50d16a88..684a66bcd71f 100644
> --- a/drivers/gpu/drm/i915/i915_drm_client.h
> +++ b/drivers/gpu/drm/i915/i915_drm_client.h
> @@ -45,8 +45,65 @@ struct i915_drm_client {
> * @past_runtime: Accumulation of pphwsp runtimes from closed contexts.
> */
> atomic64_t past_runtime[I915_LAST_UABI_ENGINE_CLASS + 1];
> +
> + /**
> + * @wa_22013059131_owner: Who programs Wa_22013059131 for this client.
> + *
> + * One of the I915_WA_22013059131_* values below. Latched once, on
> + * the first of either I915_CONTEXT_PARAM_WA_22013059131 being set or
> + * a context of this client being pinned for the first time
> + * (intel_context_alloc_state(), which holds ce->pin_mutex and a
> + * reference on the context), and read-only afterwards. See
> + * i915_drm_client_latch_wa_22013059131().
> + */
> + atomic_t wa_22013059131_owner;
> };
>
> +/*
> + * Wa_22013059131:dg2 - who programs LSC_CHICKEN_BIT_0's
> + * FORCE_1_SUB_MESSAGE_PER_FRAGMENT bit for the contexts of a client.
> + *
> + * LSC_CHICKEN_BIT_0 is shared by the RCS and CCS engines rather than being
> + * part of the saved context image, so it cannot be tracked per context.
> + * GuC only lets RCS and CCS run concurrently when their contexts share an
> + * address space, so tracking this per DRM client (which forces every
> + * address space of that client to agree) is sufficient to avoid contexts
> + * that could actually run at the same time racing over the register.
> + *
> + * I915_WA_22013059131_CLIENT_UNSET must be 0 as the client is
> zero-initialised.
> + */
> +#define I915_WA_22013059131_CLIENT_UNSET 0
> +#define I915_WA_22013059131_CLIENT_KERNEL 1
> +#define I915_WA_22013059131_CLIENT_USERSPACE 2
> +
> +/**
> + * i915_drm_client_latch_wa_22013059131 - Latch Wa_22013059131 ownership
> + * @client: the DRM client
> + * @want: I915_WA_22013059131_CLIENT_KERNEL or
> I915_WA_22013059131_CLIENT_USERSPACE
> + *
> + * Claim @want as the owner of Wa_22013059131 for @client, unless an owner
> + * has already been latched. Callers that must not change the owner pass
> + * the value they rely on and compare it against the return value.
> + *
> + * The value is latched with a single cmpxchg so that no extra locking is
> + * needed. This matters because the callers run with different locks held:
> + * GEM_CONTEXT_CREATE_EXT_SETPARAM does not hold &proto_context_lock, while
> + * GEM_CONTEXT_SETPARAM does, and intel_context_alloc_state() holds
> + * ce->pin_mutex instead.
> + *
> + * Returns: the owner in effect for @client, never
> I915_WA_22013059131_CLIENT_UNSET.
> + */
> +static inline int
> +i915_drm_client_latch_wa_22013059131(struct i915_drm_client *client, int
> want)
> +{
> + int owner;
> +
> + owner = atomic_cmpxchg(&client->wa_22013059131_owner,
> + I915_WA_22013059131_CLIENT_UNSET, want);
> +
> + return owner == I915_WA_22013059131_CLIENT_UNSET ? want : owner;
> +}
> +
> static inline struct i915_drm_client *
> i915_drm_client_get(struct i915_drm_client *client)
> {
> diff --git a/include/uapi/drm/i915_drm.h b/include/uapi/drm/i915_drm.h
> index 535cb68fdb5c..94553daec4a2 100644
> --- a/include/uapi/drm/i915_drm.h
> +++ b/include/uapi/drm/i915_drm.h
> @@ -2172,6 +2172,31 @@ struct drm_i915_gem_context_param {
> * Note that this is a debug API not available on production kernel builds.
> */
> #define I915_CONTEXT_PARAM_CONTEXT_IMAGE 0xf
> +
> +/*
> + * I915_CONTEXT_PARAM_WA_22013059131:
> + *
> + * Default value 0 means the kernel sets LSC_CHICKEN_BIT_0 bit 15
> + * (FORCE_1_SUB_MESSAGE_PER_FRAGMENT) as part of Wa_22013059131. Set to 1
> + * to inform the kernel that userspace is handling the SLM contention
> + * workaround itself (e.g. by limiting SLM size), so bit 15 programming is
> + * not needed. Only 0 and 1 are valid; any other value is rejected with
> + * -EINVAL.
> + *
> + * LSC_CHICKEN_BIT_0 is shared by the RCS and CCS engines, so this setting
> + * takes effect for every context of the calling DRM client (the whole file
> + * descriptor), not just the context the ioctl is issued on. It is latched
> + * once, by whichever comes first: an explicit call to this parameter, or
> + * any context of the client being submitted for the first time. A later
> + * request for the opposite value fails with -EINVAL. Userspace that wants
> + * to opt out must therefore do so before submitting any work on this fd,
> + * including via the default context (id 0) created on open().
> + *
> + * Note: LSC_CHICKEN_BIT_0_UDW MAXREQS_PER_BANK (bits 39:37) is the
> + * other part of Wa_22013059131 and remains unconditionally programmed
> + * by the kernel regardless of this setting. DG2-G11 only.
> + */
> +#define I915_CONTEXT_PARAM_WA_22013059131 0x10
> /* Must be kept compact -- no holes and well documented */
>
> /** @value: Context parameter value to be set or queried */
> --
> 2.43.0
>
--
Matt Roper
Graphics Software Engineer
Linux GPU Platform Enablement
Intel Corporation