All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/3] drm/nouveau: nv04 FIFO cleanup + recovery for Tesla
@ 2026-08-06  8:52 ` Marek Czernohous
  0 siblings, 0 replies; 12+ messages in thread
From: Marek Czernohous @ 2026-08-06  8:52 UTC (permalink / raw)
  To: nouveau; +Cc: Danilo Krummrich, dri-devel, linux-kernel

From: Marek Czernohous <marek@czernohous.de>

v1 of this series is here:

  https://lore.kernel.org/nouveau/20260513175014.96599-1-marek@czernohous.de/

Please do not apply v1. While running it on the reference machine I found
three defects in my own patch, one of which is a guaranteed kernel panic on
exactly the hardware the series targets. v2 fixes all three and adds one new
patch as a prerequisite.

What went wrong in v1, and how v2 addresses it:

1) NULL pointer dereference in interrupt context (fixed in 3/3)

   v1 called nvkm_chan_error(chan, true). nv50 and g84 channels have no
   .preempt callback, and the call in nvkm_chan_error() is guarded only by
   the preempt argument and not by a NULL check, so preempt=true dereferences
   a NULL function pointer under chan->lock in interrupt context.

   This is not theoretical. On 2026-06-02 it fired on the reference machine
   (then running 7.0.10-p1) during a deliberate VRAM stress test, captured
   over netconsole:

     fifo: CACHE_ERROR - ch 2 [labwc[3950]] subc 3 mthd 0f00 data 0000007b
     nv04_fifo_intr_cache_error+0x111
      -> nv04_fifo_recover+0x80
      -> nvkm_chan_error+0x99
     RIP: 0010:0x0

   The oops escalated to a full panic because nouveau's drm_panic scanout
   path ioremaps in panic context:

     nv50_wndw_get_scanout_buffer -> nouveau_bo_map -> ttm_bo_kmap
      -> __ioremap_caller -> __get_vm_area_node
     kernel BUG at mm/vmalloc.c:3212

   That is a separate, pre-existing problem which I have not yet reported;
   I will do so on its own. The hardware watchdog then reset the machine.
   v2 uses preempt=false, which is what the existing caller in
   nvkm_runl_rc() does as well.

2) Killing the channel on the first fault is wrong (fixed in 3/3)

   v1 killed the channel on every fault. But there is a single PFIFO cache
   puller, and on a fault it names the channel that is *resident*, not
   necessarily the one that caused the fault; nv04_fifo_pause() documents
   this ("incorrect instance offsets to PGRAPH"). On the reference machine
   this killed the Wayland compositor twice for somebody else's fault, on
   2026-06-02 and again on 2026-07-22.

   v2 keeps mainline behaviour for the first faults (skip the method or drop
   the push segment and resume) and only escalates to the kill once the same
   channel object has faulted NVKM_FIFO_KILL_COUNT times inside
   NVKM_FIFO_KILL_WINDOW_MS. The streak is keyed on the channel object
   pointer as an identity token, never dereferenced, and dropped in
   nvkm_chan_del() so a reused channel id cannot inherit it.

3) A killed channel is not survivable on Tesla (new patch 2/3)

   This is the reason for the new patch, and it is the part I would most
   like reviewed.

   nouveau_channel_init() only subscribes to the channel-killed event for
   FERMI_CHANNEL_GPFIFO and newer. On Tesla the ERRORED event is therefore
   delivered into an empty notifier list, nouveau_fence_context_kill() never
   runs, and the pending fences of the killed channel are never signalled.

   Today that is harmless upstream, because nothing kills a Tesla channel.
   Patch 3/3 introduces exactly such a caller, so without 2/3 the series
   would trade a recoverable fault for an unrecoverable hang. On the
   reference machine this presented twice as a frozen desktop on a machine
   that was otherwise alive and reachable over ssh. The stacks as observed:

     kworker/u8 events_unbound: dma_fence_default_wait
       <- drm_atomic_helper_wait_for_fences <- nv50_disp_atomic_commit_tail
     12x kworker/uN ttm: dma_fence_default_wait
       <- dma_resv_wait_timeout <- ttm_bo_fini

   The ttm frame is reported as ttm_bo_fini; the wait that actually holds
   those workers is the MAX_SCHEDULE_TIMEOUT one in ttm_bo_delayed_delete,
   which is static and in the same file, so the shortened frame is
   consistent with inlining. Either way both waits are uninterruptible.
   The desktop stayed frozen for minutes until the fences timed out, after
   which a compositor respawn brought the display back; a reboot cleared it
   immediately. Leaving a fence unsignalled also violates the dma-fence
   contract, which is why I think the subscription belongs on NV50+ even
   independently of this series.

Testing

  Reference hardware: Apple Mac mini Late 2009, MCP79 / GeForce 9400M
  (NVAC), Core 2 Duo, Wayland (labwc) on Mesa 26.0.8.

  Fault injection. I restored the debugfs-style fault injector I used for the
  original validation (as a pair of write-only module parameters this time)
  and drove nv04_fifo_recover() directly on a throwaway GL process, so that
  the compositor was never the target:

    ch 5 fault 1/3 in 10000ms window, skipping method and resuming (Tier-0)
    ch 5 fault 2/3 in 10000ms window, skipping method and resuming (Tier-0)
    fifo:000000:0005:0005:[fbo-stress[13247]] errored - disabling channel
    Xwayland[4931]: channel 5 killed!

  (The last line is prefixed with the DRM client rather than the faulting
  process because the test program is an X11 GL client; NV_PRINTK names the
  client that owns the channel.)

  That covers all three defects:

  - The channel survives the first two faults and only the third one inside
    the window escalates, so an unrelated process is no longer killed for a
    fault the puller misattributed.
  - A fault 18 seconds after the previous one counted as 1/3 again, so the
    window expiry works.
  - The "channel N killed!" line is the one that never appeared before patch
    2/3. It proves the event is delivered and the fences of the killed
    channel are signalled: no kworker ended up in dma_fence_default_wait,
    no TTM worker was left in D state (there were twelve during the freeze),
    the compositor kept running, and the display commit never had to fall
    back to any timeout.
  - The victim process died with a SIGSEGV inside libgallium. That is the
    known Mesa nv50 limitation (no GL robustness on this generation), not a
    consequence of these patches.

  What the injection does not cover, to be explicit: it enters the recovery
  path from process context, where the real fault arrives in hard interrupt
  context. The locking is the same, but the context is not, so this is
  functional coverage of the recovery path rather than of the interrupt path.

  Soak. The v1 patches ran on that machine from 2026-05 onwards, which is how
  defects 1 and 2 were found. The v2 code, including the escalation ladder and
  the widened event subscription, has been running since 2026-07-25, twelve
  days as of this posting, across a kernel bump from 7.1.5 to 7.1.6 with the patch
  series unchanged. Since every channel now subscribes to the kill event, the
  interesting long-run question is channel churn, so the soak is ordinary
  daily driving with a browser and GL applications rather than a synthetic
  loop.

  One difference between the soaked code and the diff below, stated so it is
  not a surprise: downstream I carry the widened subscription behind a
  module parameter (default on) so I can fall back to the Fermi+ behaviour
  without rebuilding. Patch 2/3 as posted does it unconditionally. At the
  default the two are behaviourally identical, and that default is what ran
  for those twelve days; the parameter itself is a local debugging aid and I did
  not think it belonged upstream.

  Compile coverage, precisely: the changed translation units build clean in a
  downstream 7.1.6 tree that carries equivalent code. The series is rebased
  onto the mainline base commit below, but I have not built it there, because
  my mainline checkout is sparse. The full module, in the downstream form, is
  built and booted on the reference machine.

  A note on MAINTAINERS. Patch 3/3 adds a new file
  drivers/gpu/drm/nouveau/nvkm/engine/fifo/recover.c. The change is covered
  by the existing nouveau MAINTAINERS section (drivers/gpu/drm/nouveau/),
  so no MAINTAINERS update is included. checkpatch.pl flags this as a hint;
  it is not load-bearing.

AI assistance

  Lyude asked on the v1 thread whether these patches were written by a human
  and pointed at Documentation/process/coding-assistants.rst. The answer,
  repeated here for the archive: this work is AI assisted. I use Claude
  (claude-opus-4-7 for v1, claude-opus-5 for v2) as a coding and analysis
  assistant. Every patch carries an Assisted-by trailer accordingly, and no
  Signed-off-by is added by the tool.

  Nature of the assistance, so you can calibrate your review: the assistant
  did most of the code archaeology and drafting. I described symptoms
  ("the desktop freezes but the machine is alive", "the compositor gets
  killed for a fault it did not cause"), asked for the mechanism to be
  traced in the source rather than guessed, and asked for each claim to be
  backed by a file and line. The register dumps, kernel stacks and the
  netconsole trace quoted above are measurements from the machine, not
  model output. The assistant also reviewed its own earlier work
  adversarially, which is how defects 2 and 3 were found. I reviewed the
  result, I understand the code, and I take responsibility for it.

  One consequence worth stating: a first draft of this cover letter
  described the fence-context teardown ordering in nouveau_channel_del() as
  a use-after-free worth its own patch. On closer inspection most of that
  window is harmless, because nouveau_fence_context_del() empties the
  pending list before the event is unsubscribed. I dropped that patch rather
  than send an overstated claim. If you would still like the reordering as a
  hardening change, I can send it separately.

Changes since v1

  - new patch 2/3 (subscribe to channel-kill events on NV50+)
  - 3/3: preempt=false instead of preempt=true
  - 3/3: escalation ladder instead of kill on first fault
  - 3/3: per-channel streak dropped in nvkm_chan_del()
  - 3/3: chan_killed tracepoint moved to the actual kill
  - 3/3: comments that had slipped through in German are now in English
  - rebased onto current mainline
  - Assisted-by trailers added, as offered on the v1 thread
  - defects 2 and 3 reproduced and their fixes verified with fault
    injection; defect 1 was reproduced organically before the fix and is
    covered here by the absence of the panic under injection

Marek Czernohous (3):
  drm/nouveau/fifo/nv04: filter benign CACHE_ERROR from Mesa NV50 bind
    probe
  drm/nouveau: subscribe to channel-kill events on NV50 and newer
  drm/nouveau/fifo: add recovery path for Tesla cache_error/dma_pusher

 .../drm/nouveau/include/nvkm/engine/fifo.h    |  31 +++
 .../include/trace/events/nouveau_fifo.h       |  58 ++++++
 drivers/gpu/drm/nouveau/nouveau_chan.c        |   2 +-
 drivers/gpu/drm/nouveau/nouveau_drm.c         |  29 +++
 .../gpu/drm/nouveau/nvkm/engine/fifo/Kbuild   |   1 +
 .../gpu/drm/nouveau/nvkm/engine/fifo/base.c   |   3 +
 .../gpu/drm/nouveau/nvkm/engine/fifo/chan.c   |  14 ++
 .../gpu/drm/nouveau/nvkm/engine/fifo/nv04.c   |  29 ++-
 .../gpu/drm/nouveau/nvkm/engine/fifo/priv.h   |  10 +
 .../drm/nouveau/nvkm/engine/fifo/recover.c    | 176 ++++++++++++++++++
 10 files changed, 346 insertions(+), 7 deletions(-)
 create mode 100644 drivers/gpu/drm/nouveau/include/trace/events/nouveau_fifo.h
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/engine/fifo/recover.c


base-commit: c21bb4193868a8de71fc4693fa741e195fdf5d86
-- 
2.54.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 0/3] drm/nouveau: nv04 FIFO cleanup + recovery for Tesla
@ 2026-08-06  8:52 ` Marek Czernohous
  0 siblings, 0 replies; 12+ messages in thread
From: Marek Czernohous @ 2026-08-06  8:52 UTC (permalink / raw)
  To: nouveau; +Cc: Lyude Paul, Danilo Krummrich, dri-devel, linux-kernel

From: Marek Czernohous <marek@czernohous.de>

v1 of this series is here:

  https://lore.kernel.org/nouveau/20260513175014.96599-1-marek@czernohous.de/

Please do not apply v1. While running it on the reference machine I found
three defects in my own patch, one of which is a guaranteed kernel panic on
exactly the hardware the series targets. v2 fixes all three and adds one new
patch as a prerequisite.

What went wrong in v1, and how v2 addresses it:

1) NULL pointer dereference in interrupt context (fixed in 3/3)

   v1 called nvkm_chan_error(chan, true). nv50 and g84 channels have no
   .preempt callback, and the call in nvkm_chan_error() is guarded only by
   the preempt argument and not by a NULL check, so preempt=true dereferences
   a NULL function pointer under chan->lock in interrupt context.

   This is not theoretical. On 2026-06-02 it fired on the reference machine
   (then running 7.0.10-p1) during a deliberate VRAM stress test, captured
   over netconsole:

     fifo: CACHE_ERROR - ch 2 [labwc[3950]] subc 3 mthd 0f00 data 0000007b
     nv04_fifo_intr_cache_error+0x111
      -> nv04_fifo_recover+0x80
      -> nvkm_chan_error+0x99
     RIP: 0010:0x0

   The oops escalated to a full panic because nouveau's drm_panic scanout
   path ioremaps in panic context:

     nv50_wndw_get_scanout_buffer -> nouveau_bo_map -> ttm_bo_kmap
      -> __ioremap_caller -> __get_vm_area_node
     kernel BUG at mm/vmalloc.c:3212

   That is a separate, pre-existing problem which I have not yet reported;
   I will do so on its own. The hardware watchdog then reset the machine.
   v2 uses preempt=false, which is what the existing caller in
   nvkm_runl_rc() does as well.

2) Killing the channel on the first fault is wrong (fixed in 3/3)

   v1 killed the channel on every fault. But there is a single PFIFO cache
   puller, and on a fault it names the channel that is *resident*, not
   necessarily the one that caused the fault; nv04_fifo_pause() documents
   this ("incorrect instance offsets to PGRAPH"). On the reference machine
   this killed the Wayland compositor twice for somebody else's fault, on
   2026-06-02 and again on 2026-07-22.

   v2 keeps mainline behaviour for the first faults (skip the method or drop
   the push segment and resume) and only escalates to the kill once the same
   channel object has faulted NVKM_FIFO_KILL_COUNT times inside
   NVKM_FIFO_KILL_WINDOW_MS. The streak is keyed on the channel object
   pointer as an identity token, never dereferenced, and dropped in
   nvkm_chan_del() so a reused channel id cannot inherit it.

3) A killed channel is not survivable on Tesla (new patch 2/3)

   This is the reason for the new patch, and it is the part I would most
   like reviewed.

   nouveau_channel_init() only subscribes to the channel-killed event for
   FERMI_CHANNEL_GPFIFO and newer. On Tesla the ERRORED event is therefore
   delivered into an empty notifier list, nouveau_fence_context_kill() never
   runs, and the pending fences of the killed channel are never signalled.

   Today that is harmless upstream, because nothing kills a Tesla channel.
   Patch 3/3 introduces exactly such a caller, so without 2/3 the series
   would trade a recoverable fault for an unrecoverable hang. On the
   reference machine this presented twice as a frozen desktop on a machine
   that was otherwise alive and reachable over ssh. The stacks as observed:

     kworker/u8 events_unbound: dma_fence_default_wait
       <- drm_atomic_helper_wait_for_fences <- nv50_disp_atomic_commit_tail
     12x kworker/uN ttm: dma_fence_default_wait
       <- dma_resv_wait_timeout <- ttm_bo_fini

   The ttm frame is reported as ttm_bo_fini; the wait that actually holds
   those workers is the MAX_SCHEDULE_TIMEOUT one in ttm_bo_delayed_delete,
   which is static and in the same file, so the shortened frame is
   consistent with inlining. Either way both waits are uninterruptible.
   The desktop stayed frozen for minutes until the fences timed out, after
   which a compositor respawn brought the display back; a reboot cleared it
   immediately. Leaving a fence unsignalled also violates the dma-fence
   contract, which is why I think the subscription belongs on NV50+ even
   independently of this series.

Testing

  Reference hardware: Apple Mac mini Late 2009, MCP79 / GeForce 9400M
  (NVAC), Core 2 Duo, Wayland (labwc) on Mesa 26.0.8.

  Fault injection. I restored the debugfs-style fault injector I used for the
  original validation (as a pair of write-only module parameters this time)
  and drove nv04_fifo_recover() directly on a throwaway GL process, so that
  the compositor was never the target:

    ch 5 fault 1/3 in 10000ms window, skipping method and resuming (Tier-0)
    ch 5 fault 2/3 in 10000ms window, skipping method and resuming (Tier-0)
    fifo:000000:0005:0005:[fbo-stress[13247]] errored - disabling channel
    Xwayland[4931]: channel 5 killed!

  (The last line is prefixed with the DRM client rather than the faulting
  process because the test program is an X11 GL client; NV_PRINTK names the
  client that owns the channel.)

  That covers all three defects:

  - The channel survives the first two faults and only the third one inside
    the window escalates, so an unrelated process is no longer killed for a
    fault the puller misattributed.
  - A fault 18 seconds after the previous one counted as 1/3 again, so the
    window expiry works.
  - The "channel N killed!" line is the one that never appeared before patch
    2/3. It proves the event is delivered and the fences of the killed
    channel are signalled: no kworker ended up in dma_fence_default_wait,
    no TTM worker was left in D state (there were twelve during the freeze),
    the compositor kept running, and the display commit never had to fall
    back to any timeout.
  - The victim process died with a SIGSEGV inside libgallium. That is the
    known Mesa nv50 limitation (no GL robustness on this generation), not a
    consequence of these patches.

  What the injection does not cover, to be explicit: it enters the recovery
  path from process context, where the real fault arrives in hard interrupt
  context. The locking is the same, but the context is not, so this is
  functional coverage of the recovery path rather than of the interrupt path.

  Soak. The v1 patches ran on that machine from 2026-05 onwards, which is how
  defects 1 and 2 were found. The v2 code, including the escalation ladder and
  the widened event subscription, has been running since 2026-07-25, twelve
  days as of this posting, across a kernel bump from 7.1.5 to 7.1.6 with the patch
  series unchanged. Since every channel now subscribes to the kill event, the
  interesting long-run question is channel churn, so the soak is ordinary
  daily driving with a browser and GL applications rather than a synthetic
  loop.

  One difference between the soaked code and the diff below, stated so it is
  not a surprise: downstream I carry the widened subscription behind a
  module parameter (default on) so I can fall back to the Fermi+ behaviour
  without rebuilding. Patch 2/3 as posted does it unconditionally. At the
  default the two are behaviourally identical, and that default is what ran
  for those twelve days; the parameter itself is a local debugging aid and I did
  not think it belonged upstream.

  Compile coverage, precisely: the changed translation units build clean in a
  downstream 7.1.6 tree that carries equivalent code. The series is rebased
  onto the mainline base commit below, but I have not built it there, because
  my mainline checkout is sparse. The full module, in the downstream form, is
  built and booted on the reference machine.

  A note on MAINTAINERS. Patch 3/3 adds a new file
  drivers/gpu/drm/nouveau/nvkm/engine/fifo/recover.c. The change is covered
  by the existing nouveau MAINTAINERS section (drivers/gpu/drm/nouveau/),
  so no MAINTAINERS update is included. checkpatch.pl flags this as a hint;
  it is not load-bearing.

AI assistance

  Lyude asked on the v1 thread whether these patches were written by a human
  and pointed at Documentation/process/coding-assistants.rst. The answer,
  repeated here for the archive: this work is AI assisted. I use Claude
  (claude-opus-4-7 for v1, claude-opus-5 for v2) as a coding and analysis
  assistant. Every patch carries an Assisted-by trailer accordingly, and no
  Signed-off-by is added by the tool.

  Nature of the assistance, so you can calibrate your review: the assistant
  did most of the code archaeology and drafting. I described symptoms
  ("the desktop freezes but the machine is alive", "the compositor gets
  killed for a fault it did not cause"), asked for the mechanism to be
  traced in the source rather than guessed, and asked for each claim to be
  backed by a file and line. The register dumps, kernel stacks and the
  netconsole trace quoted above are measurements from the machine, not
  model output. The assistant also reviewed its own earlier work
  adversarially, which is how defects 2 and 3 were found. I reviewed the
  result, I understand the code, and I take responsibility for it.

  One consequence worth stating: a first draft of this cover letter
  described the fence-context teardown ordering in nouveau_channel_del() as
  a use-after-free worth its own patch. On closer inspection most of that
  window is harmless, because nouveau_fence_context_del() empties the
  pending list before the event is unsubscribed. I dropped that patch rather
  than send an overstated claim. If you would still like the reordering as a
  hardening change, I can send it separately.

Changes since v1

  - new patch 2/3 (subscribe to channel-kill events on NV50+)
  - 3/3: preempt=false instead of preempt=true
  - 3/3: escalation ladder instead of kill on first fault
  - 3/3: per-channel streak dropped in nvkm_chan_del()
  - 3/3: chan_killed tracepoint moved to the actual kill
  - 3/3: comments that had slipped through in German are now in English
  - rebased onto current mainline
  - Assisted-by trailers added, as offered on the v1 thread
  - defects 2 and 3 reproduced and their fixes verified with fault
    injection; defect 1 was reproduced organically before the fix and is
    covered here by the absence of the panic under injection

Marek Czernohous (3):
  drm/nouveau/fifo/nv04: filter benign CACHE_ERROR from Mesa NV50 bind
    probe
  drm/nouveau: subscribe to channel-kill events on NV50 and newer
  drm/nouveau/fifo: add recovery path for Tesla cache_error/dma_pusher

 .../drm/nouveau/include/nvkm/engine/fifo.h    |  31 +++
 .../include/trace/events/nouveau_fifo.h       |  58 ++++++
 drivers/gpu/drm/nouveau/nouveau_chan.c        |   2 +-
 drivers/gpu/drm/nouveau/nouveau_drm.c         |  29 +++
 .../gpu/drm/nouveau/nvkm/engine/fifo/Kbuild   |   1 +
 .../gpu/drm/nouveau/nvkm/engine/fifo/base.c   |   3 +
 .../gpu/drm/nouveau/nvkm/engine/fifo/chan.c   |  14 ++
 .../gpu/drm/nouveau/nvkm/engine/fifo/nv04.c   |  29 ++-
 .../gpu/drm/nouveau/nvkm/engine/fifo/priv.h   |  10 +
 .../drm/nouveau/nvkm/engine/fifo/recover.c    | 176 ++++++++++++++++++
 10 files changed, 346 insertions(+), 7 deletions(-)
 create mode 100644 drivers/gpu/drm/nouveau/include/trace/events/nouveau_fifo.h
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/engine/fifo/recover.c


base-commit: c21bb4193868a8de71fc4693fa741e195fdf5d86
-- 
2.54.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 1/3] drm/nouveau/fifo/nv04: filter benign CACHE_ERROR from Mesa NV50 bind probe
  2026-08-06  8:52 ` Marek Czernohous
@ 2026-08-06  8:52   ` Marek Czernohous
  -1 siblings, 0 replies; 12+ messages in thread
From: Marek Czernohous @ 2026-08-06  8:52 UTC (permalink / raw)
  To: nouveau; +Cc: Danilo Krummrich, dri-devel, linux-kernel

From: Marek Czernohous <marek@czernohous.de>

The Mesa userspace driver issues a method-0x0060 / data-0xbeef02xx
binding probe on Tesla GPUs that ends up triggering CACHE_ERROR in the
PFIFO interrupt handler. The probe is harmless and recovers cleanly,
but it floods dmesg at error level on every X/Wayland session start.

Filter that specific pattern down to debug level so dmesg stays clean
while real CACHE_ERROR conditions are still logged at error level.

Tested on an Apple Mac mini Late 2009 (MCP79, NVAC 0xac080b1): dmesg has
no CACHE_ERROR spam during normal operation, the previously visible
beef02xx pattern now only appears at debug level.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Marek Czernohous <marek@czernohous.de>
---
 .../gpu/drm/nouveau/nvkm/engine/fifo/nv04.c   | 25 ++++++++++++++-----
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c
index c4b8e567d86f..fa13cd55b593 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c
@@ -327,12 +327,25 @@ nv04_fifo_intr_cache_error(struct nvkm_fifo *fifo, u32 chid, u32 get)
 
 	if (!(pull0 & 0x00000100) ||
 	    !nv04_fifo_swmthd(device, chid, mthd, data)) {
-		chan = nvkm_chan_get_chid(&fifo->engine, chid, &flags);
-		nvkm_error(subdev, "CACHE_ERROR - "
-			   "ch %d [%s] subc %d mthd %04x data %08x\n",
-			   chid, chan ? chan->name : "unknown",
-			   (mthd >> 13) & 7, mthd & 0x1ffc, data);
-		nvkm_chan_put(&chan, flags);
+		/*
+		 * Filter benign Mesa NV50 bind probe: mthd 0x0060 with
+		 * data 0xbeef02xx is a harmless userspace probe on Tesla
+		 * GPUs and does not indicate an actual error condition.
+		 * Demote to debug to keep dmesg clean while still catching
+		 * real CACHE_ERROR events.
+		 */
+		if ((mthd & 0x1ffc) == 0x0060 &&
+		    (data & 0xffffff00) == 0xbeef0200) {
+			nvkm_debug(subdev, "CACHE_ERROR - ch %d subc %d mthd %04x data %08x (benign, skipped)\n",
+				   chid, (mthd >> 13) & 7, mthd & 0x1ffc, data);
+		} else {
+			chan = nvkm_chan_get_chid(&fifo->engine, chid, &flags);
+			nvkm_error(subdev, "CACHE_ERROR - "
+				   "ch %d [%s] subc %d mthd %04x data %08x\n",
+				   chid, chan ? chan->name : "unknown",
+				   (mthd >> 13) & 7, mthd & 0x1ffc, data);
+			nvkm_chan_put(&chan, flags);
+		}
 	}
 
 	nvkm_wr32(device, NV04_PFIFO_CACHE1_DMA_PUSH, 0);
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 1/3] drm/nouveau/fifo/nv04: filter benign CACHE_ERROR from Mesa NV50 bind probe
@ 2026-08-06  8:52   ` Marek Czernohous
  0 siblings, 0 replies; 12+ messages in thread
From: Marek Czernohous @ 2026-08-06  8:52 UTC (permalink / raw)
  To: nouveau; +Cc: Lyude Paul, Danilo Krummrich, dri-devel, linux-kernel

From: Marek Czernohous <marek@czernohous.de>

The Mesa userspace driver issues a method-0x0060 / data-0xbeef02xx
binding probe on Tesla GPUs that ends up triggering CACHE_ERROR in the
PFIFO interrupt handler. The probe is harmless and recovers cleanly,
but it floods dmesg at error level on every X/Wayland session start.

Filter that specific pattern down to debug level so dmesg stays clean
while real CACHE_ERROR conditions are still logged at error level.

Tested on an Apple Mac mini Late 2009 (MCP79, NVAC 0xac080b1): dmesg has
no CACHE_ERROR spam during normal operation, the previously visible
beef02xx pattern now only appears at debug level.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Marek Czernohous <marek@czernohous.de>
---
 .../gpu/drm/nouveau/nvkm/engine/fifo/nv04.c   | 25 ++++++++++++++-----
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c
index c4b8e567d86f..fa13cd55b593 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c
@@ -327,12 +327,25 @@ nv04_fifo_intr_cache_error(struct nvkm_fifo *fifo, u32 chid, u32 get)
 
 	if (!(pull0 & 0x00000100) ||
 	    !nv04_fifo_swmthd(device, chid, mthd, data)) {
-		chan = nvkm_chan_get_chid(&fifo->engine, chid, &flags);
-		nvkm_error(subdev, "CACHE_ERROR - "
-			   "ch %d [%s] subc %d mthd %04x data %08x\n",
-			   chid, chan ? chan->name : "unknown",
-			   (mthd >> 13) & 7, mthd & 0x1ffc, data);
-		nvkm_chan_put(&chan, flags);
+		/*
+		 * Filter benign Mesa NV50 bind probe: mthd 0x0060 with
+		 * data 0xbeef02xx is a harmless userspace probe on Tesla
+		 * GPUs and does not indicate an actual error condition.
+		 * Demote to debug to keep dmesg clean while still catching
+		 * real CACHE_ERROR events.
+		 */
+		if ((mthd & 0x1ffc) == 0x0060 &&
+		    (data & 0xffffff00) == 0xbeef0200) {
+			nvkm_debug(subdev, "CACHE_ERROR - ch %d subc %d mthd %04x data %08x (benign, skipped)\n",
+				   chid, (mthd >> 13) & 7, mthd & 0x1ffc, data);
+		} else {
+			chan = nvkm_chan_get_chid(&fifo->engine, chid, &flags);
+			nvkm_error(subdev, "CACHE_ERROR - "
+				   "ch %d [%s] subc %d mthd %04x data %08x\n",
+				   chid, chan ? chan->name : "unknown",
+				   (mthd >> 13) & 7, mthd & 0x1ffc, data);
+			nvkm_chan_put(&chan, flags);
+		}
 	}
 
 	nvkm_wr32(device, NV04_PFIFO_CACHE1_DMA_PUSH, 0);
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 2/3] drm/nouveau: subscribe to channel-kill events on NV50 and newer
  2026-08-06  8:52 ` Marek Czernohous
@ 2026-08-06  8:52   ` Marek Czernohous
  -1 siblings, 0 replies; 12+ messages in thread
From: Marek Czernohous @ 2026-08-06  8:52 UTC (permalink / raw)
  To: nouveau; +Cc: Danilo Krummrich, dri-devel, linux-kernel

From: Marek Czernohous <marek@czernohous.de>

nouveau_channel_init() only subscribes to the channel-killed event for
FERMI_CHANNEL_GPFIFO and newer. On NV50/Tesla the subscription therefore
never happens, and nvkm_chan_error()'s NVKM_CHAN_EVENT_ERRORED is
delivered into an empty notifier list.

Today that is harmless, because nothing kills a channel on Tesla: the
only nvkm_chan_error() callers are the Fermi and newer recovery paths.
The next patch adds such a caller for Tesla, and without a subscriber
the consequences are severe. nouveau_channel_killed() never runs, so
nouveau_fence_context_kill() never runs either, and the pending fences
of the killed channel are never signalled. Everything waiting on them
waits forever: drm_atomic_helper_wait_for_fences() in the display commit
tail waits uninterruptibly and without a timeout, and the TTM delayed
delete workers wait in TASK_UNINTERRUPTIBLE. The user sees a frozen
desktop on a machine that is otherwise alive; it stays that way for
minutes until the fences time out, and a reboot clears it immediately.
That is also a dma-fence contract violation: a fence must always be
signalled, with an error if necessary.

Lower the class gate to NV50_CHANNEL_GPFIFO. The nvkm side is already
class neutral: the KILLED case hangs the notifier on runl->chid->event,
which every fifo owns since the runlist rework, and nvkm_uchan_uevent()
does not discriminate by class. Pre-NV50 chips keep the old behaviour,
so NV04 to NV40 are unaffected.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Marek Czernohous <marek@czernohous.de>
---
 drivers/gpu/drm/nouveau/nouveau_chan.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_chan.c b/drivers/gpu/drm/nouveau/nouveau_chan.c
index 598513f60449..1db9fbd81f5c 100644
--- a/drivers/gpu/drm/nouveau/nouveau_chan.c
+++ b/drivers/gpu/drm/nouveau/nouveau_chan.c
@@ -363,7 +363,7 @@ nouveau_channel_init(struct nouveau_channel *chan, u32 vram, u32 gart)
 	if (ret)
 		return ret;
 
-	if (chan->user.oclass >= FERMI_CHANNEL_GPFIFO) {
+	if (chan->user.oclass >= NV50_CHANNEL_GPFIFO) {
 		DEFINE_RAW_FLEX(struct nvif_event_v0, args, data,
 				sizeof(struct nvif_chan_event_v0));
 		struct nvif_chan_event_v0 *host =
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 2/3] drm/nouveau: subscribe to channel-kill events on NV50 and newer
@ 2026-08-06  8:52   ` Marek Czernohous
  0 siblings, 0 replies; 12+ messages in thread
From: Marek Czernohous @ 2026-08-06  8:52 UTC (permalink / raw)
  To: nouveau; +Cc: Lyude Paul, Danilo Krummrich, dri-devel, linux-kernel

From: Marek Czernohous <marek@czernohous.de>

nouveau_channel_init() only subscribes to the channel-killed event for
FERMI_CHANNEL_GPFIFO and newer. On NV50/Tesla the subscription therefore
never happens, and nvkm_chan_error()'s NVKM_CHAN_EVENT_ERRORED is
delivered into an empty notifier list.

Today that is harmless, because nothing kills a channel on Tesla: the
only nvkm_chan_error() callers are the Fermi and newer recovery paths.
The next patch adds such a caller for Tesla, and without a subscriber
the consequences are severe. nouveau_channel_killed() never runs, so
nouveau_fence_context_kill() never runs either, and the pending fences
of the killed channel are never signalled. Everything waiting on them
waits forever: drm_atomic_helper_wait_for_fences() in the display commit
tail waits uninterruptibly and without a timeout, and the TTM delayed
delete workers wait in TASK_UNINTERRUPTIBLE. The user sees a frozen
desktop on a machine that is otherwise alive; it stays that way for
minutes until the fences time out, and a reboot clears it immediately.
That is also a dma-fence contract violation: a fence must always be
signalled, with an error if necessary.

Lower the class gate to NV50_CHANNEL_GPFIFO. The nvkm side is already
class neutral: the KILLED case hangs the notifier on runl->chid->event,
which every fifo owns since the runlist rework, and nvkm_uchan_uevent()
does not discriminate by class. Pre-NV50 chips keep the old behaviour,
so NV04 to NV40 are unaffected.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Marek Czernohous <marek@czernohous.de>
---
 drivers/gpu/drm/nouveau/nouveau_chan.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_chan.c b/drivers/gpu/drm/nouveau/nouveau_chan.c
index 598513f60449..1db9fbd81f5c 100644
--- a/drivers/gpu/drm/nouveau/nouveau_chan.c
+++ b/drivers/gpu/drm/nouveau/nouveau_chan.c
@@ -363,7 +363,7 @@ nouveau_channel_init(struct nouveau_channel *chan, u32 vram, u32 gart)
 	if (ret)
 		return ret;
 
-	if (chan->user.oclass >= FERMI_CHANNEL_GPFIFO) {
+	if (chan->user.oclass >= NV50_CHANNEL_GPFIFO) {
 		DEFINE_RAW_FLEX(struct nvif_event_v0, args, data,
 				sizeof(struct nvif_chan_event_v0));
 		struct nvif_chan_event_v0 *host =
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 3/3] drm/nouveau/fifo: add recovery path for Tesla cache_error/dma_pusher
  2026-08-06  8:52 ` Marek Czernohous
@ 2026-08-06  8:52   ` Marek Czernohous
  -1 siblings, 0 replies; 12+ messages in thread
From: Marek Czernohous @ 2026-08-06  8:52 UTC (permalink / raw)
  To: nouveau; +Cc: Danilo Krummrich, dri-devel, linux-kernel

From: Marek Czernohous <marek@czernohous.de>

On Tesla / NV50 family chipsets (nv50, g84, g94, g98, mcp77, mcp79), FIFO
fault handling in nv04_fifo_intr_cache_error() and
nv04_fifo_intr_dma_pusher() logs the fault and resets hardware registers
but leaves the offending channel running. Compared to Fermi and newer,
which call nvkm_chan_error() from nvkm_runl_rc(), Tesla has no
escalation at all: repeated faults on the same channel keep firing
forever and there is no telemetry beyond dmesg.

Add a shared recovery helper, nv04_fifo_recover(), that both interrupt
handlers call after the existing logging and reset sequence. It
implements two tiers:

  Tier-1: kill the channel with nvkm_chan_error(), but only after it has
  faulted NVKM_FIFO_KILL_COUNT times inside NVKM_FIFO_KILL_WINDOW_MS.
  The single PFIFO cache puller names the channel that is *resident*
  when the fault is noticed, not necessarily the one that caused it (see
  the comment in nv04_fifo_pause() about incorrect instance offsets), so
  one fault is not sufficient evidence to kill. Below the threshold the
  behaviour is unchanged from mainline: the method is skipped or the
  push segment dropped, and the channel resumes.

  Tier-2: after a burst of faults within a sliding window, request a
  device-wide drm_dev_wedged_event() so userspace can rebind the driver.
  Tier-2 is fed by every fault, including those Tier-1 lets pass.

The per-channel streak is keyed on the channel object pointer, which is
used purely as an identity token and never dereferenced. It is dropped
in nvkm_chan_del() so that a channel id handed out again cannot inherit
the streak of its predecessor.

nvkm_chan_error() is called with preempt=false. nv50 and g84 channels
have no .preempt callback, so preempt=true would dereference a NULL
function pointer under chan->lock in interrupt context.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Marek Czernohous <marek@czernohous.de>
---
 .../drm/nouveau/include/nvkm/engine/fifo.h    |  31 +++
 .../include/trace/events/nouveau_fifo.h       |  58 ++++++
 drivers/gpu/drm/nouveau/nouveau_drm.c         |  29 +++
 .../gpu/drm/nouveau/nvkm/engine/fifo/Kbuild   |   1 +
 .../gpu/drm/nouveau/nvkm/engine/fifo/base.c   |   3 +
 .../gpu/drm/nouveau/nvkm/engine/fifo/chan.c   |  14 ++
 .../gpu/drm/nouveau/nvkm/engine/fifo/nv04.c   |   4 +
 .../gpu/drm/nouveau/nvkm/engine/fifo/priv.h   |  10 +
 .../drm/nouveau/nvkm/engine/fifo/recover.c    | 176 ++++++++++++++++++
 9 files changed, 326 insertions(+)
 create mode 100644 drivers/gpu/drm/nouveau/include/trace/events/nouveau_fifo.h
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/engine/fifo/recover.c

diff --git a/drivers/gpu/drm/nouveau/include/nvkm/engine/fifo.h b/drivers/gpu/drm/nouveau/include/nvkm/engine/fifo.h
index 96c16cfccf16..973c3ee445dc 100644
--- a/drivers/gpu/drm/nouveau/include/nvkm/engine/fifo.h
+++ b/drivers/gpu/drm/nouveau/include/nvkm/engine/fifo.h
@@ -55,6 +55,36 @@ void nvkm_chan_put(struct nvkm_chan **, unsigned long irqflags);
 
 struct nvkm_chan *nvkm_uchan_chan(struct nvkm_object *);
 
+#define NVKM_FIFO_WEDGE_RING_MAX 32
+
+/*
+ * A channel is only killed once it has faulted NVKM_FIFO_KILL_COUNT times
+ * within NVKM_FIFO_KILL_WINDOW_MS.  The PFIFO cache puller names the channel
+ * that is resident when the fault is noticed, which is not necessarily the one
+ * that caused it, so a single fault is not sufficient evidence to kill.
+ */
+#define NVKM_FIFO_KILL_COUNT 3
+#define NVKM_FIFO_KILL_WINDOW_MS 10000
+#define NVKM_FIFO_KILL_CHID_MAX 128
+
+struct nvkm_fifo_wedge {
+	spinlock_t       lock;
+	u32              count;                          /* faults inside the window */
+	ktime_t          ts[NVKM_FIFO_WEDGE_RING_MAX];  /* ring of fault timestamps */
+	u32              head;                           /* ring head */
+	struct work_struct work;                        /* schedules drm_dev_wedged_event */
+	atomic_t         wedged;                        /* Tier-2 already fired? */
+
+	/* Per-channel fault streak for the Tier-1 escalation.  owner is an
+	 * identity token for the channel object and is never dereferenced.
+	 */
+	struct {
+		void    *owner;
+		ktime_t  first;
+		u32      count;
+	} chfault[NVKM_FIFO_KILL_CHID_MAX];
+};
+
 struct nvkm_fifo {
 	const struct nvkm_fifo_func *func;
 	struct nvkm_engine engine;
@@ -86,6 +116,7 @@ struct nvkm_fifo {
 
 	spinlock_t lock;
 	struct mutex mutex;
+	struct nvkm_fifo_wedge wedge;
 };
 
 void nvkm_fifo_fault(struct nvkm_fifo *, struct nvkm_fault_data *);
diff --git a/drivers/gpu/drm/nouveau/include/trace/events/nouveau_fifo.h b/drivers/gpu/drm/nouveau/include/trace/events/nouveau_fifo.h
new file mode 100644
index 000000000000..46d043a82850
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/include/trace/events/nouveau_fifo.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: MIT */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM nouveau
+
+#if !defined(_TRACE_NOUVEAU_FIFO_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_NOUVEAU_FIFO_H
+
+#include <linux/tracepoint.h>
+#include <drm/drm_device.h>
+
+TRACE_EVENT(nouveau_fifo_chan_killed,
+	TP_PROTO(struct drm_device *dev, u32 chid, u32 fault_type, u64 info),
+	TP_ARGS(dev, chid, fault_type, info),
+	TP_STRUCT__entry(
+		__string(devname, dev_name(dev->dev))
+		__field(u32, chid)
+		__field(u32, fault_type)
+		__field(u64, info)
+	),
+	TP_fast_assign(
+		__assign_str(devname);
+		__entry->chid = chid;
+		__entry->fault_type = fault_type;
+		__entry->info = info;
+	),
+	TP_printk("dev=%s chid=%u fault=%s info=0x%llx",
+		__get_str(devname),
+		__entry->chid,
+		__entry->fault_type == 0 ? "CACHE_ERROR" : "DMA_PUSHER",
+		__entry->info)
+);
+
+TRACE_EVENT(nouveau_fifo_dev_wedged,
+	TP_PROTO(struct drm_device *dev, u32 fault_count, u32 window_ms),
+	TP_ARGS(dev, fault_count, window_ms),
+	TP_STRUCT__entry(
+		__string(devname, dev_name(dev->dev))
+		__field(u32, fault_count)
+		__field(u32, window_ms)
+	),
+	TP_fast_assign(
+		__assign_str(devname);
+		__entry->fault_count = fault_count;
+		__entry->window_ms = window_ms;
+	),
+	TP_printk("dev=%s wedged after %u faults in %u ms",
+		__get_str(devname),
+		__entry->fault_count,
+		__entry->window_ms)
+);
+
+#endif /* _TRACE_NOUVEAU_FIFO_H */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH ../../drivers/gpu/drm/nouveau/include/trace/events
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE nouveau_fifo
+#include <trace/define_trace.h>
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index e16f59b00f6f..9a9278589a3a 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -22,6 +22,8 @@
  * Authors: Ben Skeggs
  */
 
+#define CREATE_TRACE_POINTS
+
 #include <linux/aperture.h>
 #include <linux/delay.h>
 #include <linux/module.h>
@@ -74,6 +76,9 @@
 #include "nouveau_uvmm.h"
 #include "nouveau_sched.h"
 
+#include <engine/fifo.h>
+#include <trace/events/nouveau_fifo.h>
+
 DECLARE_DYNDBG_CLASSMAP(drm_debug_classes, DD_CLASS_TYPE_DISJOINT_BITS, 0,
 			"DRM_UT_CORE",
 			"DRM_UT_DRIVER",
@@ -111,6 +116,18 @@ MODULE_PARM_DESC(runpm, "disable (0), force enable (1), optimus only default (-1
 static int nouveau_runtime_pm = -1;
 module_param_named(runpm, nouveau_runtime_pm, int, 0400);
 
+MODULE_PARM_DESC(fifo_wedge_count,
+	"FIFO faults within window before drm_dev_wedged_event "
+	"(0=disable Tier-2, max 32, default 10)");
+unsigned int nouveau_fifo_wedge_count = 10;
+module_param_named(fifo_wedge_count, nouveau_fifo_wedge_count, uint, 0400);
+
+MODULE_PARM_DESC(fifo_wedge_window_ms,
+	"Sliding-window width in milliseconds for fifo_wedge_count "
+	"(default 60000)");
+unsigned int nouveau_fifo_wedge_window_ms = 60000;
+module_param_named(fifo_wedge_window_ms, nouveau_fifo_wedge_window_ms, uint, 0400);
+
 static struct drm_driver driver_stub;
 static struct drm_driver driver_pci;
 static struct drm_driver driver_platform;
@@ -1495,6 +1512,18 @@ nouveau_drm_init(void)
 	if (!nouveau_modeset)
 		return 0;
 
+	if (nouveau_fifo_wedge_count > NVKM_FIFO_WEDGE_RING_MAX) {
+		pr_warn("nouveau: fifo_wedge_count=%u exceeds max %u; clamping\n",
+			nouveau_fifo_wedge_count, NVKM_FIFO_WEDGE_RING_MAX);
+		nouveau_fifo_wedge_count = NVKM_FIFO_WEDGE_RING_MAX;
+	}
+	if (nouveau_fifo_wedge_window_ms < 100 ||
+	    nouveau_fifo_wedge_window_ms > 600000) {
+		pr_warn("nouveau: fifo_wedge_window_ms=%u out of range; resetting to 60000\n",
+			nouveau_fifo_wedge_window_ms);
+		nouveau_fifo_wedge_window_ms = 60000;
+	}
+
 	nouveau_module_debugfs_init();
 
 #ifdef CONFIG_NOUVEAU_PLATFORM_DRIVER
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/Kbuild b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/Kbuild
index 376e9c3bcb1a..1ff29753731d 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/Kbuild
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/Kbuild
@@ -5,6 +5,7 @@ nvkm-y += nvkm/engine/fifo/chan.o
 nvkm-y += nvkm/engine/fifo/chid.o
 nvkm-y += nvkm/engine/fifo/runl.o
 nvkm-y += nvkm/engine/fifo/runq.o
+nvkm-y += nvkm/engine/fifo/recover.o
 
 nvkm-y += nvkm/engine/fifo/nv04.o
 nvkm-y += nvkm/engine/fifo/nv10.o
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/base.c
index 9dd924694306..a61183fa38af 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/base.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/base.c
@@ -337,6 +337,8 @@ nvkm_fifo_dtor(struct nvkm_engine *engine)
 	struct nvkm_runl *runl, *runt;
 	struct nvkm_runq *runq, *rtmp;
 
+	nv04_fifo_wedge_fini(fifo);
+
 	if (fifo->userd.bar1)
 		nvkm_vmm_put(nvkm_bar_bar1_vmm(engine->subdev.device), &fifo->userd.bar1);
 	nvkm_memory_unref(&fifo->userd.mem);
@@ -390,6 +392,7 @@ nvkm_fifo_new_(const struct nvkm_fifo_func *func, struct nvkm_device *device,
 	fifo->timeout.chan_msec = 10000;
 	spin_lock_init(&fifo->lock);
 	mutex_init(&fifo->mutex);
+	nv04_fifo_wedge_init(fifo);
 
 	return nvkm_engine_ctor(&nvkm_fifo, device, type, inst, true, &fifo->engine);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/chan.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/chan.c
index 418a8918bcb8..79774c6460f4 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/chan.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/chan.c
@@ -275,6 +275,20 @@ nvkm_chan_del(struct nvkm_chan **pchan)
 	nvkm_gpuobj_del(&chan->ramfc);
 
 	if (chan->cgrp) {
+		struct nvkm_fifo *fifo = chan->cgrp->runl->fifo;
+
+		/* Drop this channel's fault streak before the id is reused. */
+		if (chan->id >= 0) {
+			typeof(&fifo->wedge.chfault[0]) cf =
+				&fifo->wedge.chfault[chan->id % NVKM_FIFO_KILL_CHID_MAX];
+			unsigned long flags;
+
+			spin_lock_irqsave(&fifo->wedge.lock, flags);
+			if (cf->owner == chan)
+				cf->owner = NULL;
+			spin_unlock_irqrestore(&fifo->wedge.lock, flags);
+		}
+
 		nvkm_chid_put(chan->cgrp->runl->chid, chan->id, &chan->cgrp->lock);
 		nvkm_cgrp_unref(&chan->cgrp);
 	}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c
index fa13cd55b593..cb81941ecccd 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c
@@ -345,6 +345,8 @@ nv04_fifo_intr_cache_error(struct nvkm_fifo *fifo, u32 chid, u32 get)
 				   chid, chan ? chan->name : "unknown",
 				   (mthd >> 13) & 7, mthd & 0x1ffc, data);
 			nvkm_chan_put(&chan, flags);
+			nv04_fifo_recover(fifo, chid, NV04_FAULT_CACHE_ERROR,
+					  ((u64)mthd << 32) | data);
 		}
 	}
 
@@ -410,6 +412,8 @@ nv04_fifo_intr_dma_pusher(struct nvkm_fifo *fifo, u32 chid)
 	}
 	nvkm_chan_put(&chan, flags);
 
+	nv04_fifo_recover(fifo, chid, NV04_FAULT_DMA_PUSHER, state);
+
 	nvkm_wr32(device, 0x003228, 0x00000000);
 	nvkm_wr32(device, 0x003220, 0x00000001);
 	nvkm_wr32(device, 0x002100, NV_PFIFO_INTR_DMA_PUSHER);
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/priv.h
index fff1428ef267..bf551906dcd4 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/priv.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/priv.h
@@ -83,6 +83,16 @@ void nv04_chan_start(struct nvkm_chan *);
 void nv04_chan_stop(struct nvkm_chan *);
 void nv04_eobj_ramht_del(struct nvkm_chan *, int);
 
+/* Recovery helper for Tesla cache_error/dma_pusher (recover.c). */
+#define NV04_FAULT_CACHE_ERROR	0
+#define NV04_FAULT_DMA_PUSHER	1
+
+void nv04_fifo_recover(struct nvkm_fifo *fifo, u32 chid, u32 fault_type, u64 info);
+void nv04_fifo_wedge_init(struct nvkm_fifo *fifo);
+void nv04_fifo_wedge_fini(struct nvkm_fifo *fifo);
+extern unsigned int nouveau_fifo_wedge_count;
+extern unsigned int nouveau_fifo_wedge_window_ms;
+
 int nv10_fifo_chid_nr(struct nvkm_fifo *);
 
 int nv50_fifo_chid_nr(struct nvkm_fifo *);
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/recover.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/recover.c
new file mode 100644
index 000000000000..ea962ddf0bcb
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/recover.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: MIT
+/*
+ * nv04_fifo_recover - shared recovery helper for Tesla cache_error and
+ * dma_pusher fault paths.
+ *
+ * Tier-1: kill the offending channel via nvkm_chan_error, but only once it
+ *         has faulted repeatedly inside a short window.
+ * Tier-2: after a configurable burst of faults within a sliding time
+ *         window, request a device-wide drm_dev_wedged_event so userspace
+ *         can rebind the driver.
+ */
+
+#include "priv.h"
+#include "chan.h"
+
+#include <core/device.h>
+#include <subdev/timer.h>
+
+#include <linux/workqueue.h>
+#include <linux/jiffies.h>
+#include <linux/ktime.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_device.h>
+
+#include "nouveau_drv.h"
+#include <trace/events/nouveau_fifo.h>
+
+static struct drm_device *
+nv04_fifo_drm_device(struct nvkm_fifo *fifo)
+{
+	struct nvkm_device *device = fifo->engine.subdev.device;
+	struct nouveau_drm *drm = dev_get_drvdata(device->dev);
+
+	return (drm && drm->dev) ? drm->dev : NULL;
+}
+
+void
+nv04_fifo_recover(struct nvkm_fifo *fifo, u32 chid, u32 fault_type, u64 info)
+{
+	struct drm_device *drm_dev = nv04_fifo_drm_device(fifo);
+	struct nvkm_chan *chan;
+	unsigned long flags;
+	ktime_t now, cutoff;
+	u32 i, count;
+
+	chan = nvkm_chan_get_chid(&fifo->engine, chid, &flags);
+	if (chan) {
+		struct nvkm_fifo_wedge *w = &fifo->wedge;
+		typeof(&w->chfault[0]) cf =
+			&w->chfault[chid % NVKM_FIFO_KILL_CHID_MAX];
+		ktime_t tnow = ktime_get();
+		unsigned long wflags;
+		bool kill;
+		u32 seen;
+
+		/*
+		 * wedge.lock nests inside chan->cgrp->lock, which
+		 * nvkm_chan_get_chid() holds until nvkm_chan_put().  The other
+		 * users of wedge.lock take no channel lock, so there is no
+		 * reverse ordering.
+		 */
+		if (atomic_read(&chan->errored)) {
+			/*
+			 * The channel is already dead but can still be
+			 * resident and fault again, because nv50 and g84 have
+			 * no preempt and the handler re-enables the puller
+			 * unconditionally.  Do not restart its streak.
+			 */
+			nvkm_chan_put(&chan, flags);
+			goto tier2;
+		}
+
+		spin_lock_irqsave(&w->lock, wflags);
+		if (cf->owner != chan ||
+		    ktime_after(tnow, ktime_add_ms(cf->first,
+						   NVKM_FIFO_KILL_WINDOW_MS))) {
+			cf->owner = chan;
+			cf->first = tnow;
+			cf->count = 0;
+		}
+		seen = ++cf->count;
+		kill = seen >= NVKM_FIFO_KILL_COUNT;
+		if (kill)
+			cf->owner = NULL;
+		spin_unlock_irqrestore(&w->lock, wflags);
+
+		if (kill) {
+			if (drm_dev)
+				trace_nouveau_fifo_chan_killed(drm_dev, chid,
+							       fault_type, info);
+			/*
+			 * preempt must stay false: nv50 and g84 channels have
+			 * no .preempt callback, so nvkm_chan_error() would
+			 * dereference a NULL function pointer under a spinlock
+			 * in interrupt context.
+			 */
+			nvkm_chan_error(chan, false);
+		} else {
+			nvkm_warn(&fifo->engine.subdev,
+				  "ch %d fault %u/%u within %ums, resuming\n",
+				  chid, seen, NVKM_FIFO_KILL_COUNT,
+				  NVKM_FIFO_KILL_WINDOW_MS);
+		}
+		nvkm_chan_put(&chan, flags);
+	}
+
+tier2:
+	if (nouveau_fifo_wedge_count == 0)
+		return;
+
+	now = ktime_get();
+	cutoff = ktime_sub_ms(now, nouveau_fifo_wedge_window_ms);
+
+	spin_lock_irqsave(&fifo->wedge.lock, flags);
+
+	/* Insert current first, then purge expired and count survivors. */
+	fifo->wedge.ts[fifo->wedge.head] = now;
+	fifo->wedge.head = (fifo->wedge.head + 1) % NVKM_FIFO_WEDGE_RING_MAX;
+
+	count = 0;
+	for (i = 0; i < NVKM_FIFO_WEDGE_RING_MAX; i++) {
+		if (!ktime_to_ns(fifo->wedge.ts[i]))
+			continue;
+		if (ktime_before(fifo->wedge.ts[i], cutoff))
+			fifo->wedge.ts[i] = 0;
+		else
+			count++;
+	}
+	fifo->wedge.count = count;
+
+	if (count >= nouveau_fifo_wedge_count)
+		schedule_work(&fifo->wedge.work);
+
+	spin_unlock_irqrestore(&fifo->wedge.lock, flags);
+}
+
+static void
+nv04_fifo_wedge_work(struct work_struct *work)
+{
+	struct nvkm_fifo_wedge *w = container_of(work, struct nvkm_fifo_wedge, work);
+	struct nvkm_fifo *fifo = container_of(w, struct nvkm_fifo, wedge);
+	struct drm_device *drm_dev = nv04_fifo_drm_device(fifo);
+	u32 fault_count;
+
+	if (atomic_xchg(&w->wedged, 1) != 0)
+		return; /* already wedged this cycle */
+
+	if (!drm_dev)
+		return;
+
+	fault_count = w->count;
+
+	dev_info(drm_dev->dev,
+		 "nouveau: fifo wedged after %u faults in %u ms\n",
+		 fault_count, nouveau_fifo_wedge_window_ms);
+
+	trace_nouveau_fifo_dev_wedged(drm_dev, fault_count,
+				      nouveau_fifo_wedge_window_ms);
+
+	drm_dev_wedged_event(drm_dev, DRM_WEDGE_RECOVERY_REBIND, NULL);
+}
+
+void
+nv04_fifo_wedge_init(struct nvkm_fifo *fifo)
+{
+	memset(fifo->wedge.chfault, 0, sizeof(fifo->wedge.chfault));
+	spin_lock_init(&fifo->wedge.lock);
+	INIT_WORK(&fifo->wedge.work, nv04_fifo_wedge_work);
+	atomic_set(&fifo->wedge.wedged, 0);
+}
+
+void
+nv04_fifo_wedge_fini(struct nvkm_fifo *fifo)
+{
+	cancel_work_sync(&fifo->wedge.work);
+}
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 3/3] drm/nouveau/fifo: add recovery path for Tesla cache_error/dma_pusher
@ 2026-08-06  8:52   ` Marek Czernohous
  0 siblings, 0 replies; 12+ messages in thread
From: Marek Czernohous @ 2026-08-06  8:52 UTC (permalink / raw)
  To: nouveau; +Cc: Lyude Paul, Danilo Krummrich, dri-devel, linux-kernel

From: Marek Czernohous <marek@czernohous.de>

On Tesla / NV50 family chipsets (nv50, g84, g94, g98, mcp77, mcp79), FIFO
fault handling in nv04_fifo_intr_cache_error() and
nv04_fifo_intr_dma_pusher() logs the fault and resets hardware registers
but leaves the offending channel running. Compared to Fermi and newer,
which call nvkm_chan_error() from nvkm_runl_rc(), Tesla has no
escalation at all: repeated faults on the same channel keep firing
forever and there is no telemetry beyond dmesg.

Add a shared recovery helper, nv04_fifo_recover(), that both interrupt
handlers call after the existing logging and reset sequence. It
implements two tiers:

  Tier-1: kill the channel with nvkm_chan_error(), but only after it has
  faulted NVKM_FIFO_KILL_COUNT times inside NVKM_FIFO_KILL_WINDOW_MS.
  The single PFIFO cache puller names the channel that is *resident*
  when the fault is noticed, not necessarily the one that caused it (see
  the comment in nv04_fifo_pause() about incorrect instance offsets), so
  one fault is not sufficient evidence to kill. Below the threshold the
  behaviour is unchanged from mainline: the method is skipped or the
  push segment dropped, and the channel resumes.

  Tier-2: after a burst of faults within a sliding window, request a
  device-wide drm_dev_wedged_event() so userspace can rebind the driver.
  Tier-2 is fed by every fault, including those Tier-1 lets pass.

The per-channel streak is keyed on the channel object pointer, which is
used purely as an identity token and never dereferenced. It is dropped
in nvkm_chan_del() so that a channel id handed out again cannot inherit
the streak of its predecessor.

nvkm_chan_error() is called with preempt=false. nv50 and g84 channels
have no .preempt callback, so preempt=true would dereference a NULL
function pointer under chan->lock in interrupt context.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Marek Czernohous <marek@czernohous.de>
---
 .../drm/nouveau/include/nvkm/engine/fifo.h    |  31 +++
 .../include/trace/events/nouveau_fifo.h       |  58 ++++++
 drivers/gpu/drm/nouveau/nouveau_drm.c         |  29 +++
 .../gpu/drm/nouveau/nvkm/engine/fifo/Kbuild   |   1 +
 .../gpu/drm/nouveau/nvkm/engine/fifo/base.c   |   3 +
 .../gpu/drm/nouveau/nvkm/engine/fifo/chan.c   |  14 ++
 .../gpu/drm/nouveau/nvkm/engine/fifo/nv04.c   |   4 +
 .../gpu/drm/nouveau/nvkm/engine/fifo/priv.h   |  10 +
 .../drm/nouveau/nvkm/engine/fifo/recover.c    | 176 ++++++++++++++++++
 9 files changed, 326 insertions(+)
 create mode 100644 drivers/gpu/drm/nouveau/include/trace/events/nouveau_fifo.h
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/engine/fifo/recover.c

diff --git a/drivers/gpu/drm/nouveau/include/nvkm/engine/fifo.h b/drivers/gpu/drm/nouveau/include/nvkm/engine/fifo.h
index 96c16cfccf16..973c3ee445dc 100644
--- a/drivers/gpu/drm/nouveau/include/nvkm/engine/fifo.h
+++ b/drivers/gpu/drm/nouveau/include/nvkm/engine/fifo.h
@@ -55,6 +55,36 @@ void nvkm_chan_put(struct nvkm_chan **, unsigned long irqflags);
 
 struct nvkm_chan *nvkm_uchan_chan(struct nvkm_object *);
 
+#define NVKM_FIFO_WEDGE_RING_MAX 32
+
+/*
+ * A channel is only killed once it has faulted NVKM_FIFO_KILL_COUNT times
+ * within NVKM_FIFO_KILL_WINDOW_MS.  The PFIFO cache puller names the channel
+ * that is resident when the fault is noticed, which is not necessarily the one
+ * that caused it, so a single fault is not sufficient evidence to kill.
+ */
+#define NVKM_FIFO_KILL_COUNT 3
+#define NVKM_FIFO_KILL_WINDOW_MS 10000
+#define NVKM_FIFO_KILL_CHID_MAX 128
+
+struct nvkm_fifo_wedge {
+	spinlock_t       lock;
+	u32              count;                          /* faults inside the window */
+	ktime_t          ts[NVKM_FIFO_WEDGE_RING_MAX];  /* ring of fault timestamps */
+	u32              head;                           /* ring head */
+	struct work_struct work;                        /* schedules drm_dev_wedged_event */
+	atomic_t         wedged;                        /* Tier-2 already fired? */
+
+	/* Per-channel fault streak for the Tier-1 escalation.  owner is an
+	 * identity token for the channel object and is never dereferenced.
+	 */
+	struct {
+		void    *owner;
+		ktime_t  first;
+		u32      count;
+	} chfault[NVKM_FIFO_KILL_CHID_MAX];
+};
+
 struct nvkm_fifo {
 	const struct nvkm_fifo_func *func;
 	struct nvkm_engine engine;
@@ -86,6 +116,7 @@ struct nvkm_fifo {
 
 	spinlock_t lock;
 	struct mutex mutex;
+	struct nvkm_fifo_wedge wedge;
 };
 
 void nvkm_fifo_fault(struct nvkm_fifo *, struct nvkm_fault_data *);
diff --git a/drivers/gpu/drm/nouveau/include/trace/events/nouveau_fifo.h b/drivers/gpu/drm/nouveau/include/trace/events/nouveau_fifo.h
new file mode 100644
index 000000000000..46d043a82850
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/include/trace/events/nouveau_fifo.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: MIT */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM nouveau
+
+#if !defined(_TRACE_NOUVEAU_FIFO_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_NOUVEAU_FIFO_H
+
+#include <linux/tracepoint.h>
+#include <drm/drm_device.h>
+
+TRACE_EVENT(nouveau_fifo_chan_killed,
+	TP_PROTO(struct drm_device *dev, u32 chid, u32 fault_type, u64 info),
+	TP_ARGS(dev, chid, fault_type, info),
+	TP_STRUCT__entry(
+		__string(devname, dev_name(dev->dev))
+		__field(u32, chid)
+		__field(u32, fault_type)
+		__field(u64, info)
+	),
+	TP_fast_assign(
+		__assign_str(devname);
+		__entry->chid = chid;
+		__entry->fault_type = fault_type;
+		__entry->info = info;
+	),
+	TP_printk("dev=%s chid=%u fault=%s info=0x%llx",
+		__get_str(devname),
+		__entry->chid,
+		__entry->fault_type == 0 ? "CACHE_ERROR" : "DMA_PUSHER",
+		__entry->info)
+);
+
+TRACE_EVENT(nouveau_fifo_dev_wedged,
+	TP_PROTO(struct drm_device *dev, u32 fault_count, u32 window_ms),
+	TP_ARGS(dev, fault_count, window_ms),
+	TP_STRUCT__entry(
+		__string(devname, dev_name(dev->dev))
+		__field(u32, fault_count)
+		__field(u32, window_ms)
+	),
+	TP_fast_assign(
+		__assign_str(devname);
+		__entry->fault_count = fault_count;
+		__entry->window_ms = window_ms;
+	),
+	TP_printk("dev=%s wedged after %u faults in %u ms",
+		__get_str(devname),
+		__entry->fault_count,
+		__entry->window_ms)
+);
+
+#endif /* _TRACE_NOUVEAU_FIFO_H */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH ../../drivers/gpu/drm/nouveau/include/trace/events
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE nouveau_fifo
+#include <trace/define_trace.h>
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index e16f59b00f6f..9a9278589a3a 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -22,6 +22,8 @@
  * Authors: Ben Skeggs
  */
 
+#define CREATE_TRACE_POINTS
+
 #include <linux/aperture.h>
 #include <linux/delay.h>
 #include <linux/module.h>
@@ -74,6 +76,9 @@
 #include "nouveau_uvmm.h"
 #include "nouveau_sched.h"
 
+#include <engine/fifo.h>
+#include <trace/events/nouveau_fifo.h>
+
 DECLARE_DYNDBG_CLASSMAP(drm_debug_classes, DD_CLASS_TYPE_DISJOINT_BITS, 0,
 			"DRM_UT_CORE",
 			"DRM_UT_DRIVER",
@@ -111,6 +116,18 @@ MODULE_PARM_DESC(runpm, "disable (0), force enable (1), optimus only default (-1
 static int nouveau_runtime_pm = -1;
 module_param_named(runpm, nouveau_runtime_pm, int, 0400);
 
+MODULE_PARM_DESC(fifo_wedge_count,
+	"FIFO faults within window before drm_dev_wedged_event "
+	"(0=disable Tier-2, max 32, default 10)");
+unsigned int nouveau_fifo_wedge_count = 10;
+module_param_named(fifo_wedge_count, nouveau_fifo_wedge_count, uint, 0400);
+
+MODULE_PARM_DESC(fifo_wedge_window_ms,
+	"Sliding-window width in milliseconds for fifo_wedge_count "
+	"(default 60000)");
+unsigned int nouveau_fifo_wedge_window_ms = 60000;
+module_param_named(fifo_wedge_window_ms, nouveau_fifo_wedge_window_ms, uint, 0400);
+
 static struct drm_driver driver_stub;
 static struct drm_driver driver_pci;
 static struct drm_driver driver_platform;
@@ -1495,6 +1512,18 @@ nouveau_drm_init(void)
 	if (!nouveau_modeset)
 		return 0;
 
+	if (nouveau_fifo_wedge_count > NVKM_FIFO_WEDGE_RING_MAX) {
+		pr_warn("nouveau: fifo_wedge_count=%u exceeds max %u; clamping\n",
+			nouveau_fifo_wedge_count, NVKM_FIFO_WEDGE_RING_MAX);
+		nouveau_fifo_wedge_count = NVKM_FIFO_WEDGE_RING_MAX;
+	}
+	if (nouveau_fifo_wedge_window_ms < 100 ||
+	    nouveau_fifo_wedge_window_ms > 600000) {
+		pr_warn("nouveau: fifo_wedge_window_ms=%u out of range; resetting to 60000\n",
+			nouveau_fifo_wedge_window_ms);
+		nouveau_fifo_wedge_window_ms = 60000;
+	}
+
 	nouveau_module_debugfs_init();
 
 #ifdef CONFIG_NOUVEAU_PLATFORM_DRIVER
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/Kbuild b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/Kbuild
index 376e9c3bcb1a..1ff29753731d 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/Kbuild
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/Kbuild
@@ -5,6 +5,7 @@ nvkm-y += nvkm/engine/fifo/chan.o
 nvkm-y += nvkm/engine/fifo/chid.o
 nvkm-y += nvkm/engine/fifo/runl.o
 nvkm-y += nvkm/engine/fifo/runq.o
+nvkm-y += nvkm/engine/fifo/recover.o
 
 nvkm-y += nvkm/engine/fifo/nv04.o
 nvkm-y += nvkm/engine/fifo/nv10.o
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/base.c
index 9dd924694306..a61183fa38af 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/base.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/base.c
@@ -337,6 +337,8 @@ nvkm_fifo_dtor(struct nvkm_engine *engine)
 	struct nvkm_runl *runl, *runt;
 	struct nvkm_runq *runq, *rtmp;
 
+	nv04_fifo_wedge_fini(fifo);
+
 	if (fifo->userd.bar1)
 		nvkm_vmm_put(nvkm_bar_bar1_vmm(engine->subdev.device), &fifo->userd.bar1);
 	nvkm_memory_unref(&fifo->userd.mem);
@@ -390,6 +392,7 @@ nvkm_fifo_new_(const struct nvkm_fifo_func *func, struct nvkm_device *device,
 	fifo->timeout.chan_msec = 10000;
 	spin_lock_init(&fifo->lock);
 	mutex_init(&fifo->mutex);
+	nv04_fifo_wedge_init(fifo);
 
 	return nvkm_engine_ctor(&nvkm_fifo, device, type, inst, true, &fifo->engine);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/chan.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/chan.c
index 418a8918bcb8..79774c6460f4 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/chan.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/chan.c
@@ -275,6 +275,20 @@ nvkm_chan_del(struct nvkm_chan **pchan)
 	nvkm_gpuobj_del(&chan->ramfc);
 
 	if (chan->cgrp) {
+		struct nvkm_fifo *fifo = chan->cgrp->runl->fifo;
+
+		/* Drop this channel's fault streak before the id is reused. */
+		if (chan->id >= 0) {
+			typeof(&fifo->wedge.chfault[0]) cf =
+				&fifo->wedge.chfault[chan->id % NVKM_FIFO_KILL_CHID_MAX];
+			unsigned long flags;
+
+			spin_lock_irqsave(&fifo->wedge.lock, flags);
+			if (cf->owner == chan)
+				cf->owner = NULL;
+			spin_unlock_irqrestore(&fifo->wedge.lock, flags);
+		}
+
 		nvkm_chid_put(chan->cgrp->runl->chid, chan->id, &chan->cgrp->lock);
 		nvkm_cgrp_unref(&chan->cgrp);
 	}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c
index fa13cd55b593..cb81941ecccd 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/nv04.c
@@ -345,6 +345,8 @@ nv04_fifo_intr_cache_error(struct nvkm_fifo *fifo, u32 chid, u32 get)
 				   chid, chan ? chan->name : "unknown",
 				   (mthd >> 13) & 7, mthd & 0x1ffc, data);
 			nvkm_chan_put(&chan, flags);
+			nv04_fifo_recover(fifo, chid, NV04_FAULT_CACHE_ERROR,
+					  ((u64)mthd << 32) | data);
 		}
 	}
 
@@ -410,6 +412,8 @@ nv04_fifo_intr_dma_pusher(struct nvkm_fifo *fifo, u32 chid)
 	}
 	nvkm_chan_put(&chan, flags);
 
+	nv04_fifo_recover(fifo, chid, NV04_FAULT_DMA_PUSHER, state);
+
 	nvkm_wr32(device, 0x003228, 0x00000000);
 	nvkm_wr32(device, 0x003220, 0x00000001);
 	nvkm_wr32(device, 0x002100, NV_PFIFO_INTR_DMA_PUSHER);
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/priv.h
index fff1428ef267..bf551906dcd4 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/priv.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/priv.h
@@ -83,6 +83,16 @@ void nv04_chan_start(struct nvkm_chan *);
 void nv04_chan_stop(struct nvkm_chan *);
 void nv04_eobj_ramht_del(struct nvkm_chan *, int);
 
+/* Recovery helper for Tesla cache_error/dma_pusher (recover.c). */
+#define NV04_FAULT_CACHE_ERROR	0
+#define NV04_FAULT_DMA_PUSHER	1
+
+void nv04_fifo_recover(struct nvkm_fifo *fifo, u32 chid, u32 fault_type, u64 info);
+void nv04_fifo_wedge_init(struct nvkm_fifo *fifo);
+void nv04_fifo_wedge_fini(struct nvkm_fifo *fifo);
+extern unsigned int nouveau_fifo_wedge_count;
+extern unsigned int nouveau_fifo_wedge_window_ms;
+
 int nv10_fifo_chid_nr(struct nvkm_fifo *);
 
 int nv50_fifo_chid_nr(struct nvkm_fifo *);
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/recover.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/recover.c
new file mode 100644
index 000000000000..ea962ddf0bcb
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/recover.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: MIT
+/*
+ * nv04_fifo_recover - shared recovery helper for Tesla cache_error and
+ * dma_pusher fault paths.
+ *
+ * Tier-1: kill the offending channel via nvkm_chan_error, but only once it
+ *         has faulted repeatedly inside a short window.
+ * Tier-2: after a configurable burst of faults within a sliding time
+ *         window, request a device-wide drm_dev_wedged_event so userspace
+ *         can rebind the driver.
+ */
+
+#include "priv.h"
+#include "chan.h"
+
+#include <core/device.h>
+#include <subdev/timer.h>
+
+#include <linux/workqueue.h>
+#include <linux/jiffies.h>
+#include <linux/ktime.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_device.h>
+
+#include "nouveau_drv.h"
+#include <trace/events/nouveau_fifo.h>
+
+static struct drm_device *
+nv04_fifo_drm_device(struct nvkm_fifo *fifo)
+{
+	struct nvkm_device *device = fifo->engine.subdev.device;
+	struct nouveau_drm *drm = dev_get_drvdata(device->dev);
+
+	return (drm && drm->dev) ? drm->dev : NULL;
+}
+
+void
+nv04_fifo_recover(struct nvkm_fifo *fifo, u32 chid, u32 fault_type, u64 info)
+{
+	struct drm_device *drm_dev = nv04_fifo_drm_device(fifo);
+	struct nvkm_chan *chan;
+	unsigned long flags;
+	ktime_t now, cutoff;
+	u32 i, count;
+
+	chan = nvkm_chan_get_chid(&fifo->engine, chid, &flags);
+	if (chan) {
+		struct nvkm_fifo_wedge *w = &fifo->wedge;
+		typeof(&w->chfault[0]) cf =
+			&w->chfault[chid % NVKM_FIFO_KILL_CHID_MAX];
+		ktime_t tnow = ktime_get();
+		unsigned long wflags;
+		bool kill;
+		u32 seen;
+
+		/*
+		 * wedge.lock nests inside chan->cgrp->lock, which
+		 * nvkm_chan_get_chid() holds until nvkm_chan_put().  The other
+		 * users of wedge.lock take no channel lock, so there is no
+		 * reverse ordering.
+		 */
+		if (atomic_read(&chan->errored)) {
+			/*
+			 * The channel is already dead but can still be
+			 * resident and fault again, because nv50 and g84 have
+			 * no preempt and the handler re-enables the puller
+			 * unconditionally.  Do not restart its streak.
+			 */
+			nvkm_chan_put(&chan, flags);
+			goto tier2;
+		}
+
+		spin_lock_irqsave(&w->lock, wflags);
+		if (cf->owner != chan ||
+		    ktime_after(tnow, ktime_add_ms(cf->first,
+						   NVKM_FIFO_KILL_WINDOW_MS))) {
+			cf->owner = chan;
+			cf->first = tnow;
+			cf->count = 0;
+		}
+		seen = ++cf->count;
+		kill = seen >= NVKM_FIFO_KILL_COUNT;
+		if (kill)
+			cf->owner = NULL;
+		spin_unlock_irqrestore(&w->lock, wflags);
+
+		if (kill) {
+			if (drm_dev)
+				trace_nouveau_fifo_chan_killed(drm_dev, chid,
+							       fault_type, info);
+			/*
+			 * preempt must stay false: nv50 and g84 channels have
+			 * no .preempt callback, so nvkm_chan_error() would
+			 * dereference a NULL function pointer under a spinlock
+			 * in interrupt context.
+			 */
+			nvkm_chan_error(chan, false);
+		} else {
+			nvkm_warn(&fifo->engine.subdev,
+				  "ch %d fault %u/%u within %ums, resuming\n",
+				  chid, seen, NVKM_FIFO_KILL_COUNT,
+				  NVKM_FIFO_KILL_WINDOW_MS);
+		}
+		nvkm_chan_put(&chan, flags);
+	}
+
+tier2:
+	if (nouveau_fifo_wedge_count == 0)
+		return;
+
+	now = ktime_get();
+	cutoff = ktime_sub_ms(now, nouveau_fifo_wedge_window_ms);
+
+	spin_lock_irqsave(&fifo->wedge.lock, flags);
+
+	/* Insert current first, then purge expired and count survivors. */
+	fifo->wedge.ts[fifo->wedge.head] = now;
+	fifo->wedge.head = (fifo->wedge.head + 1) % NVKM_FIFO_WEDGE_RING_MAX;
+
+	count = 0;
+	for (i = 0; i < NVKM_FIFO_WEDGE_RING_MAX; i++) {
+		if (!ktime_to_ns(fifo->wedge.ts[i]))
+			continue;
+		if (ktime_before(fifo->wedge.ts[i], cutoff))
+			fifo->wedge.ts[i] = 0;
+		else
+			count++;
+	}
+	fifo->wedge.count = count;
+
+	if (count >= nouveau_fifo_wedge_count)
+		schedule_work(&fifo->wedge.work);
+
+	spin_unlock_irqrestore(&fifo->wedge.lock, flags);
+}
+
+static void
+nv04_fifo_wedge_work(struct work_struct *work)
+{
+	struct nvkm_fifo_wedge *w = container_of(work, struct nvkm_fifo_wedge, work);
+	struct nvkm_fifo *fifo = container_of(w, struct nvkm_fifo, wedge);
+	struct drm_device *drm_dev = nv04_fifo_drm_device(fifo);
+	u32 fault_count;
+
+	if (atomic_xchg(&w->wedged, 1) != 0)
+		return; /* already wedged this cycle */
+
+	if (!drm_dev)
+		return;
+
+	fault_count = w->count;
+
+	dev_info(drm_dev->dev,
+		 "nouveau: fifo wedged after %u faults in %u ms\n",
+		 fault_count, nouveau_fifo_wedge_window_ms);
+
+	trace_nouveau_fifo_dev_wedged(drm_dev, fault_count,
+				      nouveau_fifo_wedge_window_ms);
+
+	drm_dev_wedged_event(drm_dev, DRM_WEDGE_RECOVERY_REBIND, NULL);
+}
+
+void
+nv04_fifo_wedge_init(struct nvkm_fifo *fifo)
+{
+	memset(fifo->wedge.chfault, 0, sizeof(fifo->wedge.chfault));
+	spin_lock_init(&fifo->wedge.lock);
+	INIT_WORK(&fifo->wedge.work, nv04_fifo_wedge_work);
+	atomic_set(&fifo->wedge.wedged, 0);
+}
+
+void
+nv04_fifo_wedge_fini(struct nvkm_fifo *fifo)
+{
+	cancel_work_sync(&fifo->wedge.work);
+}
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 2/3] drm/nouveau: subscribe to channel-kill events on NV50 and newer
  2026-08-06  8:52   ` Marek Czernohous
  (?)
@ 2026-08-06  9:20   ` sashiko-bot
  -1 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-08-06  9:20 UTC (permalink / raw)
  To: Marek Czernohous; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] Asymmetric teardown of `chan->kill` event and `chan->fence` leads to a Use-After-Free if a channel kill event fires during channel destruction.
- [High] Channel kill event can fire before `chan->fence` is fully initialized, leading to uninitialized spinlock and list head access.
--

--- Patch [2]: [PATCH v2 2/3] drm/nouveau: subscribe to channel-kill events on NV50 and newer ---
commit 66f8740c572f7ea5ed5181be09ddea5061fdb648
Author: Marek Czernohous <marek@czernohous.de>

drm/nouveau: subscribe to channel-kill events on NV50 and newer

nouveau_channel_init() only subscribes to the channel-killed event for
FERMI_CHANNEL_GPFIFO and newer. This patch lowers the class gate to
NV50_CHANNEL_GPFIFO, ensuring that pending fences on a killed channel are
properly signalled for NV50/Tesla hardware.

> diff --git a/drivers/gpu/drm/nouveau/nouveau_chan.c b/drivers/gpu/drm/nouveau/nouveau_chan.c
> index 598513f604493..1db9fbd81f5c0 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_chan.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_chan.c
> @@ -363,7 +363,7 @@ nouveau_channel_init(struct nouveau_channel *chan, u32 vram, u32 gart)
>  	if (ret)
>  		return ret;
>  
> -	if (chan->user.oclass >= FERMI_CHANNEL_GPFIFO) {
> +	if (chan->user.oclass >= NV50_CHANNEL_GPFIFO) {

[Severity: High]
This is a pre-existing issue, but does expanding this subscription to NV50
expose it to a use-after-free during channel teardown?

During teardown, nouveau_channel_del() destroys the fence context before
unregistering the channel kill event:

nouveau_channel_del() {
    ...
    if (chan->fence)
        nouveau_fence(chan->cli->drm)->context_del(chan);
    ...
    nvif_event_dtor(&chan->kill);
}

The fence context is freed in nv10_fence_context_del() where chan->fence is
set to NULL non-atomically. If a channel kill event fires concurrently, could
the asynchronous handler nouveau_channel_kill() read chan->fence locklessly
just before it is set to NULL and dereference the freed fence context inside
nouveau_fence_context_kill()?

[Severity: High]
This isn't a bug introduced by this patch, but is there also a race during
initialization where the kill event can fire before the fence context is fully
initialized?

In nouveau_channel_init(), the kill event is enabled early:

nouveau_channel_init() {
    ...
    if (ret == 0)
        ret = nvif_event_allow(&chan->kill);
    ...
}

However, the fence context is created at the very end of the function:

nouveau_channel_init() {
    ...
    return nouveau_fence(drm)->context_new(chan);
}

Inside nv50_fence_context_new(), chan->fence is assigned via kzalloc before
the underlying lists and spinlocks are initialized by
nouveau_fence_context_new():

nv50_fence_context_new() {
    ...
    fctx = chan->fence = kzalloc_obj(*fctx);
    if (!fctx)
        return -ENOMEM;

    nouveau_fence_context_new(chan, &fctx->base);
}

If a kill event fires during this window, could nouveau_fence_context_kill()
attempt to acquire an uninitialized spinlock and traverse an uninitialized
list?

>  		DEFINE_RAW_FLEX(struct nvif_event_v0, args, data,
>  				sizeof(struct nvif_chan_event_v0));
>  		struct nvif_chan_event_v0 *host =

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260806085228.1848994-1-mczernohous@gmail.com?part=2

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 3/3] drm/nouveau/fifo: add recovery path for Tesla cache_error/dma_pusher
  2026-08-06  8:52   ` Marek Czernohous
  (?)
@ 2026-08-06  9:29   ` sashiko-bot
  -1 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-08-06  9:29 UTC (permalink / raw)
  To: Marek Czernohous; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Race condition between nvkm_chan_del() and nv04_fifo_recover() leaves a dangling pointer in wedge.chfault, allowing a newly allocated channel to incorrectly inherit a fault streak.
--

--- Patch [3]: [PATCH v2 3/3] drm/nouveau/fifo: add recovery path for Tesla cache_error/dma_pusher ---
commit 673f2147a13507bd6f62792657604b061ac497d7
Author: Marek Czernohous <marek@czernohous.de>

drm/nouveau/fifo: add recovery path for Tesla cache_error/dma_pusher

This commit adds a shared recovery helper, nv04_fifo_recover, for Tesla
chipset FIFO fault handling. It implements a two-tier recovery mechanism
that kills channels after a burst of faults and requests a device-wide
wedge event if faults continue.

> diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/chan.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/chan.c
> index 418a8918bcb8f..79774c6460f4f 100644
> --- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/chan.c
> +++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/chan.c

[ ... ]

> @@ -275,6 +275,20 @@ nvkm_chan_del(struct nvkm_chan **pchan)
>  	nvkm_gpuobj_del(&chan->ramfc);
>  
>  	if (chan->cgrp) {
> +		struct nvkm_fifo *fifo = chan->cgrp->runl->fifo;
> +
> +		/* Drop this channel's fault streak before the id is reused. */
> +		if (chan->id >= 0) {
> +			typeof(&fifo->wedge.chfault[0]) cf =
> +				&fifo->wedge.chfault[chan->id % NVKM_FIFO_KILL_CHID_MAX];
> +			unsigned long flags;
> +
> +			spin_lock_irqsave(&fifo->wedge.lock, flags);
> +			if (cf->owner == chan)
> +				cf->owner = NULL;
> +			spin_unlock_irqrestore(&fifo->wedge.lock, flags);

[Severity: Medium]
Can a concurrent nv04_fifo_recover() race with this teardown and leave a
dangling pointer in cf->owner?

Because this is cleared before nvkm_chid_put() unlinks the channel, and
before chan->cgrp->lock is acquired, is it possible for the following
sequence to occur:

CPU1 in nvkm_chan_del():
    spin_lock_irqsave(&fifo->wedge.lock, flags);
    if (cf->owner == chan)
        cf->owner = NULL;
    spin_unlock_irqrestore(&fifo->wedge.lock, flags);

CPU2 handling an interrupt in nv04_fifo_recover():
    chan = nvkm_chan_get_chid(...) 
    ...
    spin_lock_irqsave(&w->lock, wflags);
    if (cf->owner != chan || ...) {
        cf->owner = chan;
    }

CPU1 then calls nvkm_chid_put() unlinking the channel, and it is freed.

Would this leave the freed memory address in cf->owner, causing a newly
allocated channel that happens to reuse this address to incorrectly inherit
the previous channel's fault streak?

> +		}
> +
>  		nvkm_chid_put(chan->cgrp->runl->chid, chan->id, &chan->cgrp->lock);
>  		nvkm_cgrp_unref(&chan->cgrp);
>  	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260806085228.1848994-1-mczernohous@gmail.com?part=3

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 0/3] drm/nouveau: nv04 FIFO cleanup + recovery for Tesla
  2026-08-06  8:52 ` Marek Czernohous
@ 2026-08-06  9:59   ` Marek Czernohous
  -1 siblings, 0 replies; 12+ messages in thread
From: Marek Czernohous @ 2026-08-06  9:59 UTC (permalink / raw)
  To: nouveau; +Cc: Lyude Paul, Danilo Krummrich, dri-devel, linux-kernel

Both findings are correct, thank you.

I checked them against mainline rather than against my downstream tree:

1) nouveau_channel_del() frees the fence context first and only drops the
   kill subscription later, among the nvif object teardown calls. The
   subscribed handler reaches nouveau_fence_context_kill(chan->fence), so
   a kill delivered in that window takes fctx->lock and walks
   fctx->pending on a context that context_del() has already freed.

2) nouveau_channel_init() arms the subscription right after mapping userd
   and creates the fence context at the end of the same function. The
   backends publish the pointer before the context is usable:

	fctx = chan->fence = kzalloc_obj(*fctx);
	if (!fctx)
		return -ENOMEM;

	nouveau_fence_context_new(chan, &fctx->base);

   and it is nouveau_fence_context_new() that runs spin_lock_init(&fctx->lock)
   and INIT_LIST_HEAD(&fctx->pending). The NULL check in
   nouveau_channel_kill() does not cover that window: chan->fence is
   non-NULL and unusable, so the handler locks something that was never
   initialised and walks a list head whose next pointer is still the NULL
   left by kzalloc().

Both are unreachable below Fermi today, which is exactly why they belong
in this series: 2/3 lowers the gate to NV50 and 3/3 adds the caller that
kills Tesla channels. This series is what makes them reachable, so
shipping it without them would trade a recoverable fault for a
use-after-free.

I should have carried the first one from the start. It has been running
on the reference machine since 2026-07-25, and my v2 cover letter
described that same ordering and then dismissed it as "most of that
window is harmless". That judgement was wrong.

v3 will put both in front of the subscription change, as 1/5 and 2/5,
with the remaining three unchanged apart from the rebase.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 0/3] drm/nouveau: nv04 FIFO cleanup + recovery for Tesla
@ 2026-08-06  9:59   ` Marek Czernohous
  0 siblings, 0 replies; 12+ messages in thread
From: Marek Czernohous @ 2026-08-06  9:59 UTC (permalink / raw)
  To: nouveau; +Cc: Danilo Krummrich, dri-devel, linux-kernel

Both findings are correct, thank you.

I checked them against mainline rather than against my downstream tree:

1) nouveau_channel_del() frees the fence context first and only drops the
   kill subscription later, among the nvif object teardown calls. The
   subscribed handler reaches nouveau_fence_context_kill(chan->fence), so
   a kill delivered in that window takes fctx->lock and walks
   fctx->pending on a context that context_del() has already freed.

2) nouveau_channel_init() arms the subscription right after mapping userd
   and creates the fence context at the end of the same function. The
   backends publish the pointer before the context is usable:

	fctx = chan->fence = kzalloc_obj(*fctx);
	if (!fctx)
		return -ENOMEM;

	nouveau_fence_context_new(chan, &fctx->base);

   and it is nouveau_fence_context_new() that runs spin_lock_init(&fctx->lock)
   and INIT_LIST_HEAD(&fctx->pending). The NULL check in
   nouveau_channel_kill() does not cover that window: chan->fence is
   non-NULL and unusable, so the handler locks something that was never
   initialised and walks a list head whose next pointer is still the NULL
   left by kzalloc().

Both are unreachable below Fermi today, which is exactly why they belong
in this series: 2/3 lowers the gate to NV50 and 3/3 adds the caller that
kills Tesla channels. This series is what makes them reachable, so
shipping it without them would trade a recoverable fault for a
use-after-free.

I should have carried the first one from the start. It has been running
on the reference machine since 2026-07-25, and my v2 cover letter
described that same ordering and then dismissed it as "most of that
window is harmless". That judgement was wrong.

v3 will put both in front of the subscription change, as 1/5 and 2/5,
with the remaining three unchanged apart from the rebase.

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-08-06 10:00 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-06  8:52 [PATCH v2 0/3] drm/nouveau: nv04 FIFO cleanup + recovery for Tesla Marek Czernohous
2026-08-06  8:52 ` Marek Czernohous
2026-08-06  8:52 ` [PATCH v2 1/3] drm/nouveau/fifo/nv04: filter benign CACHE_ERROR from Mesa NV50 bind probe Marek Czernohous
2026-08-06  8:52   ` Marek Czernohous
2026-08-06  8:52 ` [PATCH v2 2/3] drm/nouveau: subscribe to channel-kill events on NV50 and newer Marek Czernohous
2026-08-06  8:52   ` Marek Czernohous
2026-08-06  9:20   ` sashiko-bot
2026-08-06  8:52 ` [PATCH v2 3/3] drm/nouveau/fifo: add recovery path for Tesla cache_error/dma_pusher Marek Czernohous
2026-08-06  8:52   ` Marek Czernohous
2026-08-06  9:29   ` sashiko-bot
2026-08-06  9:59 ` [PATCH v2 0/3] drm/nouveau: nv04 FIFO cleanup + recovery for Tesla Marek Czernohous
2026-08-06  9:59   ` Marek Czernohous

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.