// SPDX-License-Identifier: MIT /* * Copyright 2026, Intel Corporation. */ #include #include #include #include "gem/i915_gem_internal.h" #include "gem/i915_gem_object_frontbuffer.h" #include "gem/i915_gem_pm.h" #include "gt/intel_gpu_commands.h" #include "gt/intel_ring.h" #include "i915_drv.h" #include "i915_overlay.h" #include "i915_reg.h" #include "intel_pci_config.h" #include "display/intel_frontbuffer.h" /* overlay flip addr flag */ #define OFC_UPDATE 0x1 struct i915_overlay { struct drm_i915_private *i915; struct intel_context *context; struct i915_vma *vma; struct i915_vma *old_vma; struct i915_frontbuffer *frontbuffer; /* register access */ struct drm_i915_gem_object *reg_bo; void __iomem *regs; u32 flip_addr; u32 frontbuffer_bits; /* flip handling */ struct i915_active last_flip; void (*flip_complete)(struct i915_overlay *overlay); }; static void i830_overlay_clock_gating(struct drm_i915_private *i915, bool enable) { struct pci_dev *pdev = to_pci_dev(i915->drm.dev); u8 val; /* * WA_OVERLAY_CLKGATE:alm * * FIXME should perhaps be done on the display side? */ if (enable) intel_uncore_write(&i915->uncore, DSPCLK_GATE_D, 0); else intel_uncore_write(&i915->uncore, DSPCLK_GATE_D, OVRUNIT_CLOCK_GATE_DISABLE); /* WA_DISABLE_L2CACHE_CLOCK_GATING:alm */ pci_bus_read_config_byte(pdev->bus, PCI_DEVFN(0, 0), I830_CLOCK_GATE, &val); if (enable) val &= ~I830_L2_CACHE_CLOCK_GATE_DISABLE; else val |= I830_L2_CACHE_CLOCK_GATE_DISABLE; pci_bus_write_config_byte(pdev->bus, PCI_DEVFN(0, 0), I830_CLOCK_GATE, val); } static struct i915_request * alloc_request(struct i915_overlay *overlay, void (*fn)(struct i915_overlay *)) { struct i915_request *rq; int err; overlay->flip_complete = fn; rq = i915_request_create(overlay->context); if (IS_ERR(rq)) return rq; err = i915_active_add_request(&overlay->last_flip, rq); if (err) { i915_request_add(rq); return ERR_PTR(err); } return rq; } static bool i915_overlay_is_active(struct drm_device *drm) { struct drm_i915_private *i915 = to_i915(drm); struct i915_overlay *overlay = i915->overlay; return overlay->frontbuffer_bits; } /* overlay needs to be disable in OCMD reg */ static int i915_overlay_on(struct drm_device *drm, u32 frontbuffer_bits) { struct drm_i915_private *i915 = to_i915(drm); struct i915_overlay *overlay = i915->overlay; struct i915_request *rq; u32 *cs; drm_WARN_ON(drm, i915_overlay_is_active(drm)); rq = alloc_request(overlay, NULL); if (IS_ERR(rq)) return PTR_ERR(rq); cs = intel_ring_begin(rq, 4); if (IS_ERR(cs)) { i915_request_add(rq); return PTR_ERR(cs); } overlay->frontbuffer_bits = frontbuffer_bits; if (IS_I830(i915)) i830_overlay_clock_gating(i915, false); *cs++ = MI_OVERLAY_FLIP | MI_OVERLAY_ON; *cs++ = overlay->flip_addr | OFC_UPDATE; *cs++ = MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP; *cs++ = MI_NOOP; intel_ring_advance(rq, cs); i915_request_add(rq); return i915_active_wait(&overlay->last_flip); } static void i915_overlay_flip_prepare(struct i915_overlay *overlay, struct i915_vma *vma) { struct drm_i915_private *i915 = overlay->i915; struct i915_frontbuffer *frontbuffer = NULL; drm_WARN_ON(&i915->drm, overlay->old_vma); if (vma) frontbuffer = i915_gem_object_frontbuffer_get(vma->obj); i915_gem_object_frontbuffer_track(overlay->frontbuffer, frontbuffer, overlay->frontbuffer_bits); if (overlay->frontbuffer) i915_gem_object_frontbuffer_put(overlay->frontbuffer); overlay->frontbuffer = frontbuffer; overlay->old_vma = overlay->vma; if (vma) overlay->vma = i915_vma_get(vma); else overlay->vma = NULL; } /* overlay needs to be enabled in OCMD reg */ static int i915_overlay_continue(struct drm_device *drm, struct i915_vma *vma, bool load_polyphase_filter) { struct drm_i915_private *i915 = to_i915(drm); struct i915_overlay *overlay = i915->overlay; struct i915_request *rq; u32 flip_addr = overlay->flip_addr; u32 *cs; drm_WARN_ON(drm, !i915_overlay_is_active(drm)); if (load_polyphase_filter) flip_addr |= OFC_UPDATE; rq = alloc_request(overlay, NULL); if (IS_ERR(rq)) return PTR_ERR(rq); cs = intel_ring_begin(rq, 2); if (IS_ERR(cs)) { i915_request_add(rq); return PTR_ERR(cs); } *cs++ = MI_OVERLAY_FLIP | MI_OVERLAY_CONTINUE; *cs++ = flip_addr; intel_ring_advance(rq, cs); i915_overlay_flip_prepare(overlay, vma); i915_request_add(rq); return 0; } static void i915_overlay_release_old_vma(struct i915_overlay *overlay) { struct drm_i915_private *i915 = overlay->i915; struct intel_display *display = i915->display; struct i915_vma *vma; vma = fetch_and_zero(&overlay->old_vma); if (drm_WARN_ON(&i915->drm, !vma)) return; intel_frontbuffer_flip(display, overlay->frontbuffer_bits); i915_vma_unpin(vma); i915_vma_put(vma); } static void i915_overlay_release_old_vid_tail(struct i915_overlay *overlay) { i915_overlay_release_old_vma(overlay); } static void i915_overlay_off_tail(struct i915_overlay *overlay) { struct drm_i915_private *i915 = overlay->i915; i915_overlay_release_old_vma(overlay); overlay->frontbuffer_bits = 0; if (IS_I830(i915)) i830_overlay_clock_gating(i915, true); } static void i915_overlay_last_flip_retire(struct i915_active *active) { struct i915_overlay *overlay = container_of(active, typeof(*overlay), last_flip); if (overlay->flip_complete) overlay->flip_complete(overlay); } /* overlay needs to be disabled in OCMD reg */ static int i915_overlay_off(struct drm_device *drm) { struct drm_i915_private *i915 = to_i915(drm); struct i915_overlay *overlay = i915->overlay; struct i915_request *rq; u32 *cs, flip_addr = overlay->flip_addr; drm_WARN_ON(drm, !i915_overlay_is_active(drm)); /* * According to intel docs the overlay hw may hang (when switching * off) without loading the filter coeffs. It is however unclear whether * this applies to the disabling of the overlay or to the switching off * of the hw. Do it in both cases. */ flip_addr |= OFC_UPDATE; rq = alloc_request(overlay, i915_overlay_off_tail); if (IS_ERR(rq)) return PTR_ERR(rq); cs = intel_ring_begin(rq, 6); if (IS_ERR(cs)) { i915_request_add(rq); return PTR_ERR(cs); } /* wait for overlay to go idle */ *cs++ = MI_OVERLAY_FLIP | MI_OVERLAY_CONTINUE; *cs++ = flip_addr; *cs++ = MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP; /* turn overlay off */ *cs++ = MI_OVERLAY_FLIP | MI_OVERLAY_OFF; *cs++ = flip_addr; *cs++ = MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP; intel_ring_advance(rq, cs); i915_overlay_flip_prepare(overlay, NULL); i915_request_add(rq); return i915_active_wait(&overlay->last_flip); } /* * Recover from an interruption due to a signal. * We have to be careful not to repeat work forever an make forward progress. */ static int i915_overlay_recover_from_interrupt(struct drm_device *drm) { struct drm_i915_private *i915 = to_i915(drm); struct i915_overlay *overlay = i915->overlay; return i915_active_wait(&overlay->last_flip); } /* * Wait for pending overlay flip and release old frame. * Needs to be called before the overlay register are changed * via intel_overlay_(un)map_regs. */ static int i915_overlay_release_old_vid(struct drm_device *drm) { struct drm_i915_private *i915 = to_i915(drm); struct i915_overlay *overlay = i915->overlay; struct i915_request *rq; u32 *cs; /* * Only wait if there is actually an old frame to release to * guarantee forward progress. */ if (!overlay->old_vma) return 0; if (!(intel_uncore_read(&i915->uncore, GEN2_ISR) & I915_OVERLAY_PLANE_FLIP_PENDING_INTERRUPT)) { i915_overlay_release_old_vid_tail(overlay); return 0; } rq = alloc_request(overlay, i915_overlay_release_old_vid_tail); if (IS_ERR(rq)) return PTR_ERR(rq); cs = intel_ring_begin(rq, 2); if (IS_ERR(cs)) { i915_request_add(rq); return PTR_ERR(cs); } *cs++ = MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP; *cs++ = MI_NOOP; intel_ring_advance(rq, cs); i915_request_add(rq); return i915_active_wait(&overlay->last_flip); } static void i915_overlay_reset(struct drm_device *drm) { struct drm_i915_private *i915 = to_i915(drm); struct i915_overlay *overlay = i915->overlay; if (!overlay) return; overlay->frontbuffer_bits = 0; } static struct i915_vma *i915_overlay_pin_fb(struct drm_device *drm, struct drm_gem_object *obj, u32 *offset) { struct drm_i915_gem_object *new_bo = to_intel_bo(obj); struct i915_gem_ww_ctx ww; struct i915_vma *vma; int ret; i915_gem_ww_ctx_init(&ww, true); retry: ret = i915_gem_object_lock(new_bo, &ww); if (!ret) { vma = i915_gem_object_pin_to_display_plane(new_bo, &ww, 0, 0, NULL, PIN_MAPPABLE); ret = PTR_ERR_OR_ZERO(vma); } if (ret == -EDEADLK) { ret = i915_gem_ww_ctx_backoff(&ww); if (!ret) goto retry; } i915_gem_ww_ctx_fini(&ww); if (ret) return ERR_PTR(ret); *offset = i915_ggtt_offset(vma); return vma; } static void i915_overlay_unpin_fb(struct drm_device *drm, struct i915_vma *vma) { i915_vma_unpin(vma); } static struct drm_gem_object * i915_overlay_obj_lookup(struct drm_device *drm, struct drm_file *file_priv, u32 handle) { struct drm_i915_gem_object *bo; bo = i915_gem_object_lookup(file_priv, handle); if (!bo) return ERR_PTR(-ENOENT); if (i915_gem_object_is_tiled(bo)) { drm_dbg(drm, "buffer used for overlay image can not be tiled\n"); i915_gem_object_put(bo); return ERR_PTR(-EINVAL); } return intel_bo_to_drm_bo(bo); } static int get_registers(struct i915_overlay *overlay, bool use_phys) { struct drm_i915_private *i915 = overlay->i915; struct drm_i915_gem_object *obj; struct i915_vma *vma; int err; obj = i915_gem_object_create_stolen(i915, PAGE_SIZE); if (IS_ERR(obj)) obj = i915_gem_object_create_internal(i915, PAGE_SIZE); if (IS_ERR(obj)) return PTR_ERR(obj); vma = i915_gem_object_ggtt_pin(obj, NULL, 0, 0, PIN_MAPPABLE); if (IS_ERR(vma)) { err = PTR_ERR(vma); goto err_put_bo; } if (use_phys) overlay->flip_addr = sg_dma_address(obj->mm.pages->sgl); else overlay->flip_addr = i915_ggtt_offset(vma); overlay->regs = i915_vma_pin_iomap(vma); i915_vma_unpin(vma); if (IS_ERR(overlay->regs)) { err = PTR_ERR(overlay->regs); goto err_put_bo; } overlay->reg_bo = obj; return 0; err_put_bo: i915_gem_object_put(obj); return err; } static void __iomem *i915_overlay_setup(struct drm_device *drm, bool needs_physical) { struct drm_i915_private *i915 = to_i915(drm); struct intel_engine_cs *engine; struct i915_overlay *overlay; int ret; engine = to_gt(i915)->engine[RCS0]; if (!engine || !engine->kernel_context) return ERR_PTR(-ENOENT); overlay = kzalloc_obj(*overlay); if (!overlay) return ERR_PTR(-ENOMEM); overlay->i915 = i915; overlay->context = engine->kernel_context; i915_active_init(&overlay->last_flip, NULL, i915_overlay_last_flip_retire, 0); ret = get_registers(overlay, needs_physical); if (ret) { kfree(overlay); return ERR_PTR(ret); } i915->overlay = overlay; return overlay->regs; } static void i915_overlay_cleanup(struct drm_device *drm) { struct drm_i915_private *i915 = to_i915(drm); struct i915_overlay *overlay = i915->overlay; if (!overlay) return; /* * The bo's should be free'd by the generic code already. * Furthermore modesetting teardown happens beforehand so the * hardware should be off already. */ drm_WARN_ON(drm, i915_overlay_is_active(drm)); i915_gem_object_put(overlay->reg_bo); i915_active_fini(&overlay->last_flip); kfree(overlay); i915->overlay = NULL; } const struct intel_display_overlay_interface i915_display_overlay_interface = { .is_active = i915_overlay_is_active, .overlay_on = i915_overlay_on, .overlay_continue = i915_overlay_continue, .overlay_off = i915_overlay_off, .recover_from_interrupt = i915_overlay_recover_from_interrupt, .release_old_vid = i915_overlay_release_old_vid, .reset = i915_overlay_reset, .obj_lookup = i915_overlay_obj_lookup, .pin_fb = i915_overlay_pin_fb, .unpin_fb = i915_overlay_unpin_fb, .setup = i915_overlay_setup, .cleanup = i915_overlay_cleanup, };