From 13973c940f2772163a367b9493809b7d3a405c35 Mon Sep 17 00:00:00 2001 From: Jose Maria Casanova Crespo Date: Tue, 14 Jul 2026 19:29:35 +0200 Subject: [PATCH 1/3] drm/vc4: Supply the overflow slot size in BPOS, not the whole bin BO size vc4_overflow_mem_work() points BPOA at a 512KB slot inside the 16MB binner BO, but writes the size of the whole BO to BPOS. On every binner out-of-memory event the PTB is therefore authorized to write tile lists across all the other slots (which may hold the tile state, tile alloc and overflow memory of in-flight jobs) and, for any slot but the first, past the end of the binner BO into unrelated CMA memory. Since CMA pages are recycled into page cache and user allocations, this is arbitrary memory corruption by GPU DMA. In practice it shows up as GPU hangs with corrupted control list pointers, userspace heap corruption, a GPU that stays permanently wedged after the first hang, and occasional full system crashes, whenever a job overflows the initial binner slot. EmulationStation's screen transitions at 1080p on a Raspberry Pi 3 reproduce it within seconds. The bug dates back to the conversion from a dedicated overflow BO (where writing the full BO size was correct) to the slotted binner BO. Fixes: 553c942f8b2c ("drm/vc4: Allow using more than 256MB of CMA memory.") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Jose Maria Casanova Crespo --- drivers/gpu/drm/vc4/vc4_irq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c index 69b399f3b8027c..4fdad88e53e100 100644 --- a/drivers/gpu/drm/vc4/vc4_irq.c +++ b/drivers/gpu/drm/vc4/vc4_irq.c @@ -104,7 +104,7 @@ vc4_overflow_mem_work(struct work_struct *work) vc4->bin_alloc_overflow = BIT(bin_bo_slot); V3D_WRITE(V3D_BPOA, bo->base.dma_addr + bin_bo_slot * vc4->bin_alloc_size); - V3D_WRITE(V3D_BPOS, bo->base.base.size); + V3D_WRITE(V3D_BPOS, vc4->bin_alloc_size); V3D_WRITE(V3D_INTCTL, V3D_INT_OUTOMEM); V3D_WRITE(V3D_INTENA, V3D_INT_OUTOMEM); spin_unlock_irqrestore(&vc4->job_lock, irqflags); From d2bcddb4f73ea75652fa3a33a52da77a3d107202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Thu, 16 Jul 2026 11:43:36 -0300 Subject: [PATCH 2/3] drm/vc4: Reset the binner overflow allocator on GPU reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Although vc4_irq_reset() tears down the in-flight jobs, it never resets the software binner-overflow allocator or the HW overflow registers (BPOA/BPOS). The current overflow slot is tracked in bin_alloc_overflow and is attached to a job's bin_slots at the *next* out-of-memory event. On reset, the RENDER job that would have read it is force-completed, so it is never attached and never freed: a slot leaks on every reset. BPOA/BPOS are also left pointing at the stale overflow slot, so the re-queued BIN job would consume that stale pointer before raising a new OOM interrupt. Free the pending overflow slot, clear bin_alloc_overflow, and zero BPOA/BPOS under job_lock, so the allocator and hardware return to a clean state before submitting new jobs. Fixes: 553c942f8b2c ("drm/vc4: Allow using more than 256MB of CMA memory.") Cc: stable@vger.kernel.org Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_irq.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c index 4fdad88e53e100..08340991e5a75e 100644 --- a/drivers/gpu/drm/vc4/vc4_irq.c +++ b/drivers/gpu/drm/vc4/vc4_irq.c @@ -352,6 +352,18 @@ void vc4_irq_reset(struct drm_device *dev) V3D_WRITE(V3D_INTENA, V3D_DRIVER_IRQS); spin_lock_irqsave(&vc4->job_lock, irqflags); + + /* + * The reset wiped the binner's hardware state, and the job that may + * have been reading overflow memory is torn down just below. Free the + * current overflow slot and clear the overflow registers so the + * re-queued BIN job restarts from a clean OOM state. + */ + vc4->bin_alloc_used &= ~vc4->bin_alloc_overflow; + vc4->bin_alloc_overflow = 0; + V3D_WRITE(V3D_BPOA, 0); + V3D_WRITE(V3D_BPOS, 0); + vc4_cancel_bin_job(dev); vc4_irq_finish_render_job(dev); spin_unlock_irqrestore(&vc4->job_lock, irqflags); From 6d2086e9ac37527d9e38041482a3431e4ab46265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Tue, 14 Jul 2026 19:29:35 +0200 Subject: [PATCH 3/3] drm/vc4: Dynamically size the initial tile-alloc block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The binner writes each tile's binned command list into an initial tile-allocation block and, when that list outgrows the block, continues it into a chain of continuation blocks. On VC4 the hardware does not link that chain reliably: the render thread that later walks it desyncs -- it has been seen to fetch an invalid opcode partway through a block (CT1CS reports CTERR) or to follow the chain into never-written memory and stall -- and hangcheck then resets the GPU. The chain is only walked once a tile spills its initial block, so the problem scales with how small that block is. The continuation-block size is not the lever: the hang reproduces with both 32- and 128-byte continuation blocks. Enlarging the *initial* block is what helps, as it keeps more tiles' lists in that block and off the chain entirely, and 256 bytes is the hardware maximum. A fixed 256-byte block cannot be used unconditionally, though. The tile_count initial blocks are laid out contiguously right after the tile state and must fit within the binner slot, and the render thread reaches each tile's list by a fixed per-tile stride. If the blocks did not fit, the later ones would land at a non-contiguous address and that stride would point at the wrong memory. So pick the largest initial block (256, 128, 64 or 32 bytes) that still fits the slot for the job's tile count, and program the RCL branch stride to match. This is a mitigation for a hardware limitation, not a complete fix: a single tile whose list exceeds even a 256-byte block, or a scene dense enough to force a smaller initial block, can still reach the chain and hang. But the case users have reported in EmulationStation's 1080p transitions between consoles, which previously stormed "Resetting GPU", now runs with zero resets. Link: https://github.com/raspberrypi/linux/issues/3221 Link: https://github.com/raspberrypi/linux/issues/5780 Fixes: d5b1a78a772f ("drm/vc4: Add support for drawing 3D frames.") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_drv.h | 5 +++++ drivers/gpu/drm/vc4/vc4_render_cl.c | 3 ++- drivers/gpu/drm/vc4/vc4_validate.c | 29 ++++++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 60fa75ff7d09eb..541d9d5aa0f87f 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -793,6 +793,11 @@ struct vc4_exec_info { * (where each tile's binned CL will start) */ uint32_t tile_alloc_offset; + /* Stride between adjacent tiles' initial alloc blocks. Equals the + * initial block size programmed in the binning config, and is used + * as the per-tile stride when emitting the RCL branches. + */ + u32 tile_alloc_stride; /* Bitmask of which binner slots are freed when this job completes. */ uint32_t bin_slots; diff --git a/drivers/gpu/drm/vc4/vc4_render_cl.c b/drivers/gpu/drm/vc4/vc4_render_cl.c index 14079853338ebd..a34cbbbc932c52 100644 --- a/drivers/gpu/drm/vc4/vc4_render_cl.c +++ b/drivers/gpu/drm/vc4/vc4_render_cl.c @@ -183,7 +183,8 @@ static void emit_tile(struct vc4_exec_info *exec, if (has_bin) { rcl_u8(setup, VC4_PACKET_BRANCH_TO_SUB_LIST); rcl_u32(setup, (exec->tile_alloc_offset + - (y * exec->bin_tiles_x + x) * 32)); + (y * exec->bin_tiles_x + x) * + exec->tile_alloc_stride)); } if (setup->msaa_color_write) { diff --git a/drivers/gpu/drm/vc4/vc4_validate.c b/drivers/gpu/drm/vc4/vc4_validate.c index 1e7bdda556984b..e7f76b4b28b0ae 100644 --- a/drivers/gpu/drm/vc4/vc4_validate.c +++ b/drivers/gpu/drm/vc4/vc4_validate.c @@ -356,8 +356,9 @@ validate_tile_binning_config(VALIDATE_ARGS) struct drm_device *dev = exec->exec_bo->base.dev; struct vc4_dev *vc4 = to_vc4_dev(dev); uint8_t flags; - uint32_t tile_state_size; + u32 tile_state_size, tile_state_aligned; uint32_t tile_count, bin_addr; + u8 init_block; int bin_slot; if (exec->found_tile_binning_mode_config_packet) { @@ -405,13 +406,35 @@ validate_tile_binning_config(VALIDATE_ARGS) tile_state_size = 48 * tile_count; /* Since the tile alloc array will follow us, align. */ - exec->tile_alloc_offset = bin_addr + roundup(tile_state_size, 4096); + tile_state_aligned = roundup(tile_state_size, 4096); + exec->tile_alloc_offset = bin_addr + tile_state_aligned; + + /* + * Pick the largest initial tile-alloc block that still fits the slot. + * Larger initial blocks keep more tiles' binned lists off the + * continuation-block chain, which the binner does not always link + * reliably. Denser tile counts fall back to smaller blocks, down to + * the 32-byte minimum which always fits. + */ + if (tile_state_aligned + 256 * tile_count <= vc4->bin_alloc_size) { + exec->tile_alloc_stride = 256; + init_block = VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_256; + } else if (tile_state_aligned + 128 * tile_count <= vc4->bin_alloc_size) { + exec->tile_alloc_stride = 128; + init_block = VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_128; + } else if (tile_state_aligned + 64 * tile_count <= vc4->bin_alloc_size) { + exec->tile_alloc_stride = 64; + init_block = VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_64; + } else { + exec->tile_alloc_stride = 32; + init_block = VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_32; + } *(uint8_t *)(validated + 14) = ((flags & ~(VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_MASK | VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE_MASK)) | VC4_BIN_CONFIG_AUTO_INIT_TSDA | - VC4_SET_FIELD(VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_32, + VC4_SET_FIELD(init_block, VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE) | VC4_SET_FIELD(VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE_128, VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE));