Contributors: 15
Author Tokens Token Proportion Commits Commit Proportion
Ville Syrjälä 1391 64.67% 20 25.32%
Chris Wilson 364 16.92% 31 39.24%
Daniel Vetter 187 8.69% 6 7.59%
Jani Nikula 96 4.46% 10 12.66%
John Harrison 39 1.81% 2 2.53%
Tvrtko A. Ursulin 38 1.77% 1 1.27%
Dave Gordon 12 0.56% 1 1.27%
Pankaj Bharadiya 6 0.28% 1 1.27%
Thomas Zimmermann 4 0.19% 1 1.27%
Dave Airlie 3 0.14% 1 1.27%
Nitin Gote 3 0.14% 1 1.27%
Michał Winiarski 3 0.14% 1 1.27%
Uma Shankar 3 0.14% 1 1.27%
CQ Tang 1 0.05% 1 1.27%
Kees Cook 1 0.05% 1 1.27%
Total 2151 79


// SPDX-License-Identifier: MIT
/*
 * Copyright 2026, Intel Corporation.
 */

#include <drm/drm_print.h>

#include <drm/intel/display_parent_interface.h>
#include <drm/intel/intel_gmd_interrupt_regs.h>

#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,
};