// 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[] = {
{0 x00, 0 x20, "L2T" },
{0 x20, 0 x21, "PTB" },
{0 x40, 0 x41, "PSE" },
{0 x60, 0 x80, "TLB" },
{0 x80, 0 x88, "CLE" },
{0 xA0, 0 xA1, "TFU" },
{0 xC0, 0 xE0, "MMU" },
{0 xE0, 0 xE1, "GMP" },
}, v3d71_axi_ids[] = {
{0 x00, 0 x30, "L2T" },
{0 x30, 0 x38, "CLE" },
{0 x38, 0 x39, "PTB" },
{0 x39, 0 x3A, "PSE" },
{0 x3A, 0 x3B, "CSD" },
{0 x40, 0 x60, "TLB" },
{0 x60, 0 x70, "MMU" },
{0 x7C, 0 x7E, "TFU" },
{0 x7F, 0 x80, "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 & 0 x7F;
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 & 0 xFF;
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);
}
Messung V0.5 in Prozent C=94 H=91 G=92
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-09-27)
¤
*© Formatika GbR, Deutschland