/* DF-5783 trigger — legacy cursor fast-path corrupts live crtc->state->active_planes
 *
 * Target: DragonFlyBSD sys/dev/drm/i915/intel_display.c (vendored Linux v4.20)
 * Missing upstream fix: c249c5f64336 "drm/i915: Handle cursor updating
 * active_planes correctly, v2." (landed Linux v5.0).
 *
 * Mechanism (code-reading proof; requires Intel i915 hardware + DRM master):
 *   1. intel_legacy_cursor_update() (intel_display.c:13623) is the cursor
 *      plane's ->update_plane fast path, taken when the crtc is active and no
 *      modeset is pending (:13644-13646).
 *   2. At :13686-13689 it calls
 *        intel_plane_atomic_check_with_state(to_intel_crtc_state(crtc->state),
 *                                            to_intel_crtc_state(crtc->state), ...)
 *      passing the LIVE committed crtc state as BOTH old and new
 *      (the upstream FIXME comment "need a new crtc state?" marks the spot).
 *   3. intel_plane_atomic_check_with_state() MUTATES the passed crtc_state:
 *      intel_atomic_plane.c:129-132 — visible ? active_planes |= BIT :
 *      active_planes &= ~BIT(PLANE_CURSOR). For a cursor-off update the
 *      PLANE_CURSOR bit is cleared in the LIVE state.
 *   4. If a later step fails — e.g. mutex_lock_interruptible() at :13693
 *      returns -EINTR when a signal is delivered, or intel_plane_pin_fb()
 *      at :13697 fails — the function bails via out_unlock/out_free WITHOUT
 *      undoing the mutation and WITHOUT touching hardware: the hardware
 *      cursor is still ON, but crtc->state->active_planes no longer contains
 *      PLANE_CURSOR.
 *   5. Per upstream c249c5f64336: "we may clear active_planes if the new
 *      cursor update state will disable the cursor, but we fail after.
 *      If this is immediately followed by a modeset disable, we may soon not
 *      disable the planes correctly when we start depending on
 *      active_planes." Consumers in this tree: intel_crtc_disable_planes /
 *      disable_plane decisions, hsw_compute_ips_config (intel_display.c:6666),
 *      watermark calculations (intel_pm.c) — desynchronized plane-disable and
 *      wm/IPS programming => display engine underruns / hung pipe, i.e.
 *      local DoS of the display subsystem by a DRM master.
 *
 * NOTE on the concurrent page-flip aspect: the same commit's v2 note explains
 * the live state also cannot be swapped/freed here because a concurrent
 * atomic commit or page flip still references it.
 *
 * Trigger sequence (needs DRM master, i.e. unprivileged user who becomes
 * master on an idle VT, or root X exiting):
 *   a) cursor ON  : DRM_IOCTL_MODE_CURSOR2 with a valid bo handle
 *   b) race       : DRM_IOCTL_MODE_CURSOR2 with flags=DRM_MODE_CURSOR_BO (fb
 *                   handle = 0 => cursor-off geometry) while a signal is
 *                   queued so the -EINTR path at :13693 aborts AFTER the
 *                   check-phase mutation
 *   c) observe    : dmesg shows no cursor disable; following modeset off
 *                   leaves hardware cursor scanning on a disabled pipe
 *                   (FIFO underrun spam / dead pipe until reboot or
 *                   re-modeset with full sanitize).
 *
 * On the audit rig (Bochs VGA, no i915) this exits at card discovery.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <dev/drm/drm.h>
#include <dev/drm/drm_mode.h>

int main(void)
{
	char path[64];
	struct drm_version ver;
	char name[128] = {0};
	int fd;

	for (int i = 0; i < 16; i++) {
		snprintf(path, sizeof(path), "/dev/dri/card%d", i);
		if (access(path, F_OK) == 0)
			goto found;
	}
	printf("no /dev/dri/card* — nothing to test\n");
	return 2;
found:
	fd = open(path, O_RDWR);
	if (fd < 0) { perror("open"); return 2; }

	memset(&ver, 0, sizeof(ver));
	ver.name_len = sizeof(name) - 1;
	ver.name = name;
	if (ioctl(fd, DRM_IOCTL_VERSION, &ver) < 0) { perror("version"); return 2; }
	if (strcmp(name, "i915") != 0) {
		printf("driver is '%s', not i915 — cannot test on this rig\n", name);
		close(fd);
		return 3;
	}

	/* Real hardware sequence (needs master; abbreviated here):
	 *   1. ioctl(DRM_IOCTL_SET_MASTER)
	 *   2. cursor ON with a small ARGB bo (handle H)
	 *   3. fork a child that SIGALRMs us, then issue the cursor-OFF
	 *      update (DRM_IOCTL_MODE_CURSOR2, handle=0) so that the signal
	 *      lands inside intel_legacy_cursor_update() between the
	 *      check-phase mutation (13686-13689) and the mutex/pin steps
	 *      (13693/13697) -> -EINTR bail-out with corrupted
	 *      crtc->state->active_planes.
	 *   4. immediately DRM_IOCTL_MODE_SETCRTC-disable the pipe and watch
	 *      the cursor survive on hardware (underrun storm in dmesg).
	 */
	printf("i915 present — run the master-gated cursor race sequence "
	       "(see header comment) on the target machine.\n");
	close(fd);
	return 0;
}
