mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-31 22:23:05 +00:00

Currently, an interrupt can be triggered during a GPU reset, which can
lead to GPU hangs and NULL pointer dereference in an interrupt context
as shown in the following trace:
[ 314.035040] Unable to handle kernel NULL pointer dereference at virtual address 00000000000000c0
[ 314.043822] Mem abort info:
[ 314.046606] ESR = 0x0000000096000005
[ 314.050347] EC = 0x25: DABT (current EL), IL = 32 bits
[ 314.055651] SET = 0, FnV = 0
[ 314.058695] EA = 0, S1PTW = 0
[ 314.061826] FSC = 0x05: level 1 translation fault
[ 314.066694] Data abort info:
[ 314.069564] ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000
[ 314.075039] CM = 0, WnR = 0, TnD = 0, TagAccess = 0
[ 314.080080] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
[ 314.085382] user pgtable: 4k pages, 39-bit VAs, pgdp=0000000102728000
[ 314.091814] [00000000000000c0] pgd=0000000000000000, p4d=0000000000000000, pud=0000000000000000
[ 314.100511] Internal error: Oops: 0000000096000005 [#1] PREEMPT SMP
[ 314.106770] Modules linked in: v3d i2c_brcmstb vc4 snd_soc_hdmi_codec gpu_sched drm_shmem_helper drm_display_helper cec drm_dma_helper drm_kms_helper drm drm_panel_orientation_quirks snd_soc_core snd_compress snd_pcm_dmaengine snd_pcm snd_timer snd backlight
[ 314.129654] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 6.12.25+rpt-rpi-v8 #1 Debian 1:6.12.25-1+rpt1
[ 314.139388] Hardware name: Raspberry Pi 4 Model B Rev 1.4 (DT)
[ 314.145211] pstate: 600000c5 (nZCv daIF -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[ 314.152165] pc : v3d_irq+0xec/0x2e0 [v3d]
[ 314.156187] lr : v3d_irq+0xe0/0x2e0 [v3d]
[ 314.160198] sp : ffffffc080003ea0
[ 314.163502] x29: ffffffc080003ea0 x28: ffffffec1f184980 x27: 021202b000000000
[ 314.170633] x26: ffffffec1f17f630 x25: ffffff8101372000 x24: ffffffec1f17d9f0
[ 314.177764] x23: 000000000000002a x22: 000000000000002a x21: ffffff8103252000
[ 314.184895] x20: 0000000000000001 x19: 00000000deadbeef x18: 0000000000000000
[ 314.192026] x17: ffffff94e51d2000 x16: ffffffec1dac3cb0 x15: c306000000000000
[ 314.199156] x14: 0000000000000000 x13: b2fc982e03cc5168 x12: 0000000000000001
[ 314.206286] x11: ffffff8103f8bcc0 x10: ffffffec1f196868 x9 : ffffffec1dac3874
[ 314.213416] x8 : 0000000000000000 x7 : 0000000000042a3a x6 : ffffff810017a180
[ 314.220547] x5 : ffffffec1ebad400 x4 : ffffffec1ebad320 x3 : 00000000000bebeb
[ 314.227677] x2 : 0000000000000000 x1 : 0000000000000000 x0 : 0000000000000000
[ 314.234807] Call trace:
[ 314.237243] v3d_irq+0xec/0x2e0 [v3d]
[ 314.240906] __handle_irq_event_percpu+0x58/0x218
[ 314.245609] handle_irq_event+0x54/0xb8
[ 314.249439] handle_fasteoi_irq+0xac/0x240
[ 314.253527] handle_irq_desc+0x48/0x68
[ 314.257269] generic_handle_domain_irq+0x24/0x38
[ 314.261879] gic_handle_irq+0x48/0xd8
[ 314.265533] call_on_irq_stack+0x24/0x58
[ 314.269448] do_interrupt_handler+0x88/0x98
[ 314.273624] el1_interrupt+0x34/0x68
[ 314.277193] el1h_64_irq_handler+0x18/0x28
[ 314.281281] el1h_64_irq+0x64/0x68
[ 314.284673] default_idle_call+0x3c/0x168
[ 314.288675] do_idle+0x1fc/0x230
[ 314.291895] cpu_startup_entry+0x3c/0x50
[ 314.295810] rest_init+0xe4/0xf0
[ 314.299030] start_kernel+0x5e8/0x790
[ 314.302684] __primary_switched+0x80/0x90
[ 314.306691] Code: 940029eb 360ffc13 f9442ea0 52800001 (f9406017)
[ 314.312775] ---[ end trace 0000000000000000 ]---
[ 314.317384] Kernel panic - not syncing: Oops: Fatal exception in interrupt
[ 314.324249] SMP: stopping secondary CPUs
[ 314.328167] Kernel Offset: 0x2b9da00000 from 0xffffffc080000000
[ 314.334076] PHYS_OFFSET: 0x0
[ 314.336946] CPU features: 0x08,00002013,c0200000,0200421b
[ 314.342337] Memory Limit: none
[ 314.345382] ---[ end Kernel panic - not syncing: Oops: Fatal exception in interrupt ]---
Before resetting the GPU, it's necessary to disable all interrupts and
deal with any interrupt handler still in-flight. Otherwise, the GPU might
reset with jobs still running, or yet, an interrupt could be handled
during the reset.
Cc: stable@vger.kernel.org
Fixes: 57692c94dc
("drm/v3d: Introduce a new DRM driver for Broadcom V3D V3.x+")
Reviewed-by: Juan A. Suarez <jasuarez@igalia.com>
Reviewed-by: Iago Toral Quiroga <itoral@igalia.com>
Link: https://lore.kernel.org/r/20250628224243.47599-1-mcanal@igalia.com
Signed-off-by: Maíra Canal <mcanal@igalia.com>
364 lines
9.0 KiB
C
364 lines
9.0 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/* Copyright (C) 2014-2018 Broadcom */
|
|
|
|
/**
|
|
* DOC: Interrupt management for the V3D engine
|
|
*
|
|
* When we take a bin, render, TFU done, or CSD done interrupt, we
|
|
* need to signal the fence for that job so that the scheduler can
|
|
* queue up the next one and unblock any waiters.
|
|
*
|
|
* When we take the binner out of memory interrupt, we need to
|
|
* allocate some new memory and pass it to the binner so that the
|
|
* current job can make progress.
|
|
*/
|
|
|
|
#include <linux/platform_device.h>
|
|
#include <linux/sched/clock.h>
|
|
|
|
#include "v3d_drv.h"
|
|
#include "v3d_regs.h"
|
|
#include "v3d_trace.h"
|
|
|
|
#define V3D_CORE_IRQS(ver) ((u32)(V3D_INT_OUTOMEM | \
|
|
V3D_INT_FLDONE | \
|
|
V3D_INT_FRDONE | \
|
|
V3D_INT_CSDDONE(ver) | \
|
|
(ver < 71 ? V3D_INT_GMPV : 0)))
|
|
|
|
#define V3D_HUB_IRQS(ver) ((u32)(V3D_HUB_INT_MMU_WRV | \
|
|
V3D_HUB_INT_MMU_PTI | \
|
|
V3D_HUB_INT_MMU_CAP | \
|
|
V3D_HUB_INT_TFUC | \
|
|
(ver >= 71 ? V3D_V7_HUB_INT_GMPV : 0)))
|
|
|
|
static irqreturn_t
|
|
v3d_hub_irq(int irq, void *arg);
|
|
|
|
static void
|
|
v3d_overflow_mem_work(struct work_struct *work)
|
|
{
|
|
struct v3d_dev *v3d =
|
|
container_of(work, struct v3d_dev, overflow_mem_work);
|
|
struct drm_device *dev = &v3d->drm;
|
|
struct v3d_bo *bo = v3d_bo_create(dev, NULL /* XXX: GMP */, 256 * 1024);
|
|
struct drm_gem_object *obj;
|
|
unsigned long irqflags;
|
|
|
|
if (IS_ERR(bo)) {
|
|
DRM_ERROR("Couldn't allocate binner overflow mem\n");
|
|
return;
|
|
}
|
|
obj = &bo->base.base;
|
|
|
|
/* We lost a race, and our work task came in after the bin job
|
|
* completed and exited. This can happen because the HW
|
|
* signals OOM before it's fully OOM, so the binner might just
|
|
* barely complete.
|
|
*
|
|
* If we lose the race and our work task comes in after a new
|
|
* bin job got scheduled, that's fine. We'll just give them
|
|
* some binner pool anyway.
|
|
*/
|
|
spin_lock_irqsave(&v3d->job_lock, irqflags);
|
|
if (!v3d->bin_job) {
|
|
spin_unlock_irqrestore(&v3d->job_lock, irqflags);
|
|
goto out;
|
|
}
|
|
|
|
drm_gem_object_get(obj);
|
|
list_add_tail(&bo->unref_head, &v3d->bin_job->render->unref_list);
|
|
spin_unlock_irqrestore(&v3d->job_lock, irqflags);
|
|
|
|
v3d_mmu_flush_all(v3d);
|
|
|
|
V3D_CORE_WRITE(0, V3D_PTB_BPOA, bo->node.start << V3D_MMU_PAGE_SHIFT);
|
|
V3D_CORE_WRITE(0, V3D_PTB_BPOS, obj->size);
|
|
|
|
out:
|
|
drm_gem_object_put(obj);
|
|
}
|
|
|
|
static irqreturn_t
|
|
v3d_irq(int irq, void *arg)
|
|
{
|
|
struct v3d_dev *v3d = arg;
|
|
u32 intsts;
|
|
irqreturn_t status = IRQ_NONE;
|
|
|
|
intsts = V3D_CORE_READ(0, V3D_CTL_INT_STS);
|
|
|
|
/* Acknowledge the interrupts we're handling here. */
|
|
V3D_CORE_WRITE(0, V3D_CTL_INT_CLR, intsts);
|
|
|
|
if (intsts & V3D_INT_OUTOMEM) {
|
|
/* Note that the OOM status is edge signaled, so the
|
|
* interrupt won't happen again until the we actually
|
|
* add more memory. Also, as of V3D 4.1, FLDONE won't
|
|
* be reported until any OOM state has been cleared.
|
|
*/
|
|
schedule_work(&v3d->overflow_mem_work);
|
|
status = IRQ_HANDLED;
|
|
}
|
|
|
|
if (intsts & V3D_INT_FLDONE) {
|
|
struct v3d_fence *fence =
|
|
to_v3d_fence(v3d->bin_job->base.irq_fence);
|
|
|
|
v3d_job_update_stats(&v3d->bin_job->base, V3D_BIN);
|
|
trace_v3d_bcl_irq(&v3d->drm, fence->seqno);
|
|
|
|
v3d->bin_job = NULL;
|
|
dma_fence_signal(&fence->base);
|
|
|
|
status = IRQ_HANDLED;
|
|
}
|
|
|
|
if (intsts & V3D_INT_FRDONE) {
|
|
struct v3d_fence *fence =
|
|
to_v3d_fence(v3d->render_job->base.irq_fence);
|
|
|
|
v3d_job_update_stats(&v3d->render_job->base, V3D_RENDER);
|
|
trace_v3d_rcl_irq(&v3d->drm, fence->seqno);
|
|
|
|
v3d->render_job = NULL;
|
|
dma_fence_signal(&fence->base);
|
|
|
|
status = IRQ_HANDLED;
|
|
}
|
|
|
|
if (intsts & V3D_INT_CSDDONE(v3d->ver)) {
|
|
struct v3d_fence *fence =
|
|
to_v3d_fence(v3d->csd_job->base.irq_fence);
|
|
|
|
v3d_job_update_stats(&v3d->csd_job->base, V3D_CSD);
|
|
trace_v3d_csd_irq(&v3d->drm, fence->seqno);
|
|
|
|
v3d->csd_job = NULL;
|
|
dma_fence_signal(&fence->base);
|
|
|
|
status = IRQ_HANDLED;
|
|
}
|
|
|
|
/* We shouldn't be triggering these if we have GMP in
|
|
* always-allowed mode.
|
|
*/
|
|
if (v3d->ver < V3D_GEN_71 && (intsts & V3D_INT_GMPV))
|
|
dev_err(v3d->drm.dev, "GMP violation\n");
|
|
|
|
/* V3D 4.2 wires the hub and core IRQs together, so if we &
|
|
* didn't see the common one then check hub for MMU IRQs.
|
|
*/
|
|
if (v3d->single_irq_line && status == IRQ_NONE)
|
|
return v3d_hub_irq(irq, arg);
|
|
|
|
return status;
|
|
}
|
|
|
|
static irqreturn_t
|
|
v3d_hub_irq(int irq, void *arg)
|
|
{
|
|
struct v3d_dev *v3d = arg;
|
|
u32 intsts;
|
|
irqreturn_t status = IRQ_NONE;
|
|
|
|
intsts = V3D_READ(V3D_HUB_INT_STS);
|
|
|
|
/* Acknowledge the interrupts we're handling here. */
|
|
V3D_WRITE(V3D_HUB_INT_CLR, intsts);
|
|
|
|
if (intsts & V3D_HUB_INT_TFUC) {
|
|
struct v3d_fence *fence =
|
|
to_v3d_fence(v3d->tfu_job->base.irq_fence);
|
|
|
|
v3d_job_update_stats(&v3d->tfu_job->base, V3D_TFU);
|
|
trace_v3d_tfu_irq(&v3d->drm, fence->seqno);
|
|
|
|
v3d->tfu_job = NULL;
|
|
dma_fence_signal(&fence->base);
|
|
|
|
status = IRQ_HANDLED;
|
|
}
|
|
|
|
if (intsts & (V3D_HUB_INT_MMU_WRV |
|
|
V3D_HUB_INT_MMU_PTI |
|
|
V3D_HUB_INT_MMU_CAP)) {
|
|
u32 axi_id = V3D_READ(V3D_MMU_VIO_ID);
|
|
u64 vio_addr = ((u64)V3D_READ(V3D_MMU_VIO_ADDR) <<
|
|
(v3d->va_width - 32));
|
|
static const struct {
|
|
u32 begin;
|
|
u32 end;
|
|
const char *client;
|
|
} v3d41_axi_ids[] = {
|
|
{0x00, 0x20, "L2T"},
|
|
{0x20, 0x21, "PTB"},
|
|
{0x40, 0x41, "PSE"},
|
|
{0x60, 0x80, "TLB"},
|
|
{0x80, 0x88, "CLE"},
|
|
{0xA0, 0xA1, "TFU"},
|
|
{0xC0, 0xE0, "MMU"},
|
|
{0xE0, 0xE1, "GMP"},
|
|
}, v3d71_axi_ids[] = {
|
|
{0x00, 0x30, "L2T"},
|
|
{0x30, 0x38, "CLE"},
|
|
{0x38, 0x39, "PTB"},
|
|
{0x39, 0x3A, "PSE"},
|
|
{0x3A, 0x3B, "CSD"},
|
|
{0x40, 0x60, "TLB"},
|
|
{0x60, 0x70, "MMU"},
|
|
{0x7C, 0x7E, "TFU"},
|
|
{0x7F, 0x80, "GMP"},
|
|
};
|
|
const char *client = "?";
|
|
|
|
V3D_WRITE(V3D_MMU_CTL, V3D_READ(V3D_MMU_CTL));
|
|
|
|
if (v3d->ver >= V3D_GEN_71) {
|
|
size_t i;
|
|
|
|
axi_id = axi_id & 0x7F;
|
|
for (i = 0; i < ARRAY_SIZE(v3d71_axi_ids); i++) {
|
|
if (axi_id >= v3d71_axi_ids[i].begin &&
|
|
axi_id < v3d71_axi_ids[i].end) {
|
|
client = v3d71_axi_ids[i].client;
|
|
break;
|
|
}
|
|
}
|
|
} else if (v3d->ver >= V3D_GEN_41) {
|
|
size_t i;
|
|
|
|
axi_id = axi_id & 0xFF;
|
|
for (i = 0; i < ARRAY_SIZE(v3d41_axi_ids); i++) {
|
|
if (axi_id >= v3d41_axi_ids[i].begin &&
|
|
axi_id < v3d41_axi_ids[i].end) {
|
|
client = v3d41_axi_ids[i].client;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
dev_err(v3d->drm.dev, "MMU error from client %s (0x%x) at 0x%llx%s%s%s\n",
|
|
client, axi_id, (long long)vio_addr,
|
|
((intsts & V3D_HUB_INT_MMU_WRV) ?
|
|
", write violation" : ""),
|
|
((intsts & V3D_HUB_INT_MMU_PTI) ?
|
|
", pte invalid" : ""),
|
|
((intsts & V3D_HUB_INT_MMU_CAP) ?
|
|
", cap exceeded" : ""));
|
|
status = IRQ_HANDLED;
|
|
}
|
|
|
|
if (v3d->ver >= V3D_GEN_71 && (intsts & V3D_V7_HUB_INT_GMPV)) {
|
|
dev_err(v3d->drm.dev, "GMP Violation\n");
|
|
status = IRQ_HANDLED;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int
|
|
v3d_irq_init(struct v3d_dev *v3d)
|
|
{
|
|
int irq, ret, core;
|
|
|
|
INIT_WORK(&v3d->overflow_mem_work, v3d_overflow_mem_work);
|
|
|
|
/* Clear any pending interrupts someone might have left around
|
|
* for us.
|
|
*/
|
|
for (core = 0; core < v3d->cores; core++)
|
|
V3D_CORE_WRITE(core, V3D_CTL_INT_CLR, V3D_CORE_IRQS(v3d->ver));
|
|
V3D_WRITE(V3D_HUB_INT_CLR, V3D_HUB_IRQS(v3d->ver));
|
|
|
|
irq = platform_get_irq_optional(v3d_to_pdev(v3d), 1);
|
|
if (irq == -EPROBE_DEFER)
|
|
return irq;
|
|
if (irq > 0) {
|
|
v3d->irq[V3D_CORE_IRQ] = irq;
|
|
|
|
ret = devm_request_irq(v3d->drm.dev, v3d->irq[V3D_CORE_IRQ],
|
|
v3d_irq, IRQF_SHARED,
|
|
"v3d_core0", v3d);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
irq = platform_get_irq(v3d_to_pdev(v3d), 0);
|
|
if (irq < 0)
|
|
return irq;
|
|
v3d->irq[V3D_HUB_IRQ] = irq;
|
|
|
|
ret = devm_request_irq(v3d->drm.dev, v3d->irq[V3D_HUB_IRQ],
|
|
v3d_hub_irq, IRQF_SHARED,
|
|
"v3d_hub", v3d);
|
|
if (ret)
|
|
goto fail;
|
|
} else {
|
|
v3d->single_irq_line = true;
|
|
|
|
irq = platform_get_irq(v3d_to_pdev(v3d), 0);
|
|
if (irq < 0)
|
|
return irq;
|
|
v3d->irq[V3D_CORE_IRQ] = irq;
|
|
|
|
ret = devm_request_irq(v3d->drm.dev, v3d->irq[V3D_CORE_IRQ],
|
|
v3d_irq, IRQF_SHARED,
|
|
"v3d", v3d);
|
|
if (ret)
|
|
goto fail;
|
|
}
|
|
|
|
v3d_irq_enable(v3d);
|
|
return 0;
|
|
|
|
fail:
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(v3d->drm.dev, "IRQ setup failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
v3d_irq_enable(struct v3d_dev *v3d)
|
|
{
|
|
int core;
|
|
|
|
/* Enable our set of interrupts, masking out any others. */
|
|
for (core = 0; core < v3d->cores; core++) {
|
|
V3D_CORE_WRITE(core, V3D_CTL_INT_MSK_SET, ~V3D_CORE_IRQS(v3d->ver));
|
|
V3D_CORE_WRITE(core, V3D_CTL_INT_MSK_CLR, V3D_CORE_IRQS(v3d->ver));
|
|
}
|
|
|
|
V3D_WRITE(V3D_HUB_INT_MSK_SET, ~V3D_HUB_IRQS(v3d->ver));
|
|
V3D_WRITE(V3D_HUB_INT_MSK_CLR, V3D_HUB_IRQS(v3d->ver));
|
|
}
|
|
|
|
void
|
|
v3d_irq_disable(struct v3d_dev *v3d)
|
|
{
|
|
int core;
|
|
|
|
/* Disable all interrupts. */
|
|
for (core = 0; core < v3d->cores; core++)
|
|
V3D_CORE_WRITE(core, V3D_CTL_INT_MSK_SET, ~0);
|
|
V3D_WRITE(V3D_HUB_INT_MSK_SET, ~0);
|
|
|
|
/* Finish any interrupt handler still in flight. */
|
|
for (int i = 0; i < V3D_MAX_IRQS; i++) {
|
|
if (v3d->irq[i])
|
|
synchronize_irq(v3d->irq[i]);
|
|
}
|
|
|
|
/* Clear any pending interrupts we might have left. */
|
|
for (core = 0; core < v3d->cores; core++)
|
|
V3D_CORE_WRITE(core, V3D_CTL_INT_CLR, V3D_CORE_IRQS(v3d->ver));
|
|
V3D_WRITE(V3D_HUB_INT_CLR, V3D_HUB_IRQS(v3d->ver));
|
|
|
|
cancel_work_sync(&v3d->overflow_mem_work);
|
|
}
|
|
|
|
/** Reinitializes interrupt registers when a GPU reset is performed. */
|
|
void v3d_irq_reset(struct v3d_dev *v3d)
|
|
{
|
|
v3d_irq_enable(v3d);
|
|
}
|