/*
 * DF-4864 — amdgpu VM-clear IB overflow harness (userspace replica)
 * ================================================================
 *
 * Bug:  amdgpu_vm_clear_bo() (sys/dev/drm/amd/amdgpu/amdgpu_vm.c) allocates a
 *       64-byte (16-dword) SA sub-allocation for the clear-IB:
 *
 *           r = amdgpu_job_alloc_with_ib(adev, 64, &job);      [amdgpu_vm.c:855]
 *
 *       but on CHIP_RAVEN (the only ASIC with pte_support_ats, amdgpu_vm.c:3172)
 *       the ROOT page-directory clear (amdgpu_vm_init -> amdgpu_vm_clear_bo with
 *       level == root_level) takes BOTH branches:
 *
 *           if (ats_entries) amdgpu_vm_set_pte_pde(... ats_entries ...);   (10 dw)
 *           if (entries)     amdgpu_vm_set_pte_pde(... entries ...);       (10 dw)
 *
 *       amdgpu_vm_set_pte_pde is a macro (amdgpu_vm.h:293) into
 *       sdma_v4_0_vm_set_pte_pde (sdma_v4_0.c:1184-1200) which emits exactly
 *       TEN dwords per call, writing through ib->ptr[ib->length_dw++] with no
 *       bound of any kind.  2 x 10 = 20 dwords are written into 16 dwords, and
 *       amdgpu_ring_pad_ib() then appends another 4 NOP dwords
 *       ((8 - (20 & 7)) % 8), for 24 dwords = 96 bytes into a 64-byte kernel
 *       SA sub-allocation: a deterministic 32-byte kernel heap OOB write on
 *       every VM create on Raven.
 *
 * Trigger numbers (Raven rev0/1, gmc_v9_0.c:941: vm 256TB, block 9, 3 levels):
 *       root_level = PDB2 (amdgpu_vm.c:2986), root PD = 512 entries
 *       (amdgpu_vm_num_entries: round_up(2^36, 2^27) >> 27 = 512)
 *       ats_entries = AMDGPU_GMC_HOLE_START(2^47) >> (27 + 12) = 256
 *       entries     = 512 - 256 = 256          -> BOTH branches taken.
 *
 *       (Raven rev>=2, gmc_v9_0.c:945: 128TB+512GB -> 257-entry root PD,
 *        ats_entries = 256, entries = 1 -> both branches too.)
 *
 * This harness reproduces the EXACT emit/pad sequence (code copied verbatim
 * from sdma_v4_0.c) against a canaried 64-byte buffer, and probes whether the
 * running host actually has AMD graphics hardware (the kernel path itself
 * needs a Raven APU, which this QEMU guest does not have).
 *
 * Build:  cc -O2 -Wall -o df4864_trigger df4864_trigger.c
 * Run:    ./df4864_trigger
 */

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

/* ---- verbatim packet macros: vega10_sdma_pkt_open.h / sdma_v4_0.c ---- */
#define SDMA_OP_NOP		0
#define SDMA_OP_PTEPDE		12
#define SDMA_PKT_HEADER_OP(x)	(((uint32_t)(x) & 0xff) << 0)

#define lower_32_bits(n)	((uint32_t)(uint64_t)(n))
#define upper_32_bits(n)	((uint32_t)(((uint64_t)(n)) >> 32))

struct amdgpu_ib_replica {
	uint32_t	*ptr;
	uint32_t	length_dw;
};

/* verbatim: sys/dev/drm/amd/amdgpu/sdma_v4_0.c:1184-1200
 * sdma_v4_0_vm_set_pte_pde() — 10 dwords, unbounded */
static void sdma_v4_0_vm_set_pte_pde(struct amdgpu_ib_replica *ib,
				     uint64_t pe, uint64_t addr,
				     unsigned count, uint32_t incr,
				     uint64_t flags)
{
	ib->ptr[ib->length_dw++] = SDMA_PKT_HEADER_OP(SDMA_OP_PTEPDE);
	ib->ptr[ib->length_dw++] = lower_32_bits(pe);	/* dst addr */
	ib->ptr[ib->length_dw++] = upper_32_bits(pe);
	ib->ptr[ib->length_dw++] = lower_32_bits(flags);/* mask */
	ib->ptr[ib->length_dw++] = upper_32_bits(flags);
	ib->ptr[ib->length_dw++] = lower_32_bits(addr);	/* value */
	ib->ptr[ib->length_dw++] = upper_32_bits(addr);
	ib->ptr[ib->length_dw++] = incr;		/* increment size */
	ib->ptr[ib->length_dw++] = 0;
	ib->ptr[ib->length_dw++] = count - 1;		/* number of entries */
}

/* verbatim math: sys/dev/drm/amd/amdgpu/sdma_v4_0.c:1208-1222
 * sdma_v4_0_ring_pad_ib() — pads length_dw up to a multiple of 8 dwords */
static void sdma_v4_0_ring_pad_ib(struct amdgpu_ib_replica *ib)
{
	/* kernel variant consults a per-SDMA burst_nop flag; default path below */
	uint32_t pad_count = (8 - (ib->length_dw & 0x7)) % 8;
	uint32_t i;

	for (i = 0; i < pad_count; i++)
		ib->ptr[ib->length_dw++] = SDMA_PKT_HEADER_OP(SDMA_OP_NOP);
}

#define CANARY 0xAA

/* Replicates amdgpu_vm_clear_bo()'s IB usage against a canaried pool. */
static int run_clear_bo_case(const char *name, int dual,
			     uint64_t pt_gpu_addr, unsigned ats_entries,
			     unsigned entries)
{
	/* the kernel's SA sub-allocation is 64 bytes (amdgpu_ib_get ->
	 * amdgpu_sa_bo_new(..., size=64, align=256)); we place it inside a
	 * larger canaried region so the overrun is visible, exactly like the
	 * real overrun lands in whatever follows the 64-byte SA hole. */
	static uint8_t region[256 /*before*/ + 64 /*the IB*/ + 256 /*after*/];
	struct amdgpu_ib_replica ib;
	uint32_t over_dwords;
	int i, damaged = 0;

	memset(region, CANARY, sizeof(region));
	ib.ptr = (uint32_t *)(region + 256);
	ib.length_dw = 0;

	printf("[%s] amdgpu_job_alloc_with_ib(adev, 64, &job): ib buffer = 16 dwords (64 bytes)\n",
	       name);

	/* amdgpu_vm.c:867-874 — the emit sequence */
	if (ats_entries) {
		uint64_t ats_value = 0x00007fe0ULL /* AMDGPU_PTE_DEFAULT_ATC|PDE_PTE, illustrative */;
		sdma_v4_0_vm_set_pte_pde(&ib, pt_gpu_addr, 0,
					 ats_entries, 0, ats_value);
		printf("[%s] after set_pte_pde(ats_entries=%u): length_dw=%u\n",
		       name, ats_entries, ib.length_dw);
	}
	if (entries) {
		sdma_v4_0_vm_set_pte_pde(&ib, pt_gpu_addr + (uint64_t)ats_entries * 8,
					 0, entries, 0, 0);
		printf("[%s] after set_pte_pde(entries=%u):     length_dw=%u\n",
		       name, entries, ib.length_dw);
	}
	sdma_v4_0_ring_pad_ib(&ib);
	printf("[%s] after amdgpu_ring_pad_ib():           length_dw=%u\n",
	       name, ib.length_dw);

	over_dwords = ib.length_dw > 16 ? ib.length_dw - 16 : 0;
	printf("[%s] WARN_ON(length_dw > 64) in kernel: %s (bound is the BYTE size — never trips)\n",
	       name, ib.length_dw > 64 ? "would fire" : "silent");

	for (i = 0; i < 256; i++)
		if (region[256 + 64 + i] != CANARY)
			damaged++;
	if (over_dwords) {
		printf("[%s] *** OOB WRITE: %u dwords (%u bytes) past the 64-byte SA sub-allocation ***\n",
		       name, over_dwords, over_dwords * 4);
		printf("[%s] *** %d/256 canary bytes after the IB destroyed; first OOB dwords: ",
		       name, damaged);
		for (i = 0; i < (int)over_dwords && i < 8; i++)
			printf("0x%08x ", ib.ptr[16 + i]);
		printf("***\n");
	} else {
		printf("[%s] fits exactly (no overrun) — this is the non-ATS / non-root case\n",
		       name);
	}
	printf("\n");
	return over_dwords ? 1 : 0;
}

static void probe_hw(void)
{
	char buf[4096];
	FILE *f;
	int fd;

	printf("== hardware probe (kernel-path reachability on THIS host) ==\n");

	fd = open("/dev/dri/0", O_RDWR);
	if (fd >= 0) {
		printf("   /dev/dri/0: OPEN — a DRM device is present\n");
		close(fd);
	} else {
		printf("   /dev/dri/0: %s (no DRM node usable)\n",
		       strerror(errno));
	}
	fd = open("/dev/drm/0", O_RDWR);	/* DragonFly drm node name */
	if (fd >= 0) {
		printf("   /dev/drm/0: OPEN\n");
		close(fd);
	} else {
		printf("   /dev/drm/0: %s\n", strerror(errno));
	}

	f = popen("pciconf -l 2>/dev/null", "r");
	if (f) {
		int amd_vga = 0;
		while (fgets(buf, sizeof(buf), f)) {
			if (strstr(buf, "vgapci") || strstr(buf, "display"))
				printf("   pci: %s", buf);
			if (strstr(buf, "vgapci") &&
			    (strstr(buf, "chip=0x") && strstr(buf, "0x1002")))
				amd_vga = 1;
		}
		pclose(f);
		printf("   AMD (0x1002) VGA device: %s\n",
		       amd_vga ? "PRESENT" : "absent");
		printf("   -> amdgpu(4) can attach on this host: %s\n",
		       amd_vga ? "YES" : "NO");
	}
	printf("\n");
}

int main(void)
{
	int rc = 0;

	printf("DF-4864 harness: amdgpu_vm_clear_bo() 64-byte IB vs 2x10-dw set_pte_pde + SDMA pad\n");
	printf("kernel refs: amdgpu_vm.c:855 alloc(64) / :867-874 dual emit / sdma_v4_0.c:1184-1200 emit\n");
	printf("             sdma_v4_0.c:1208-1222 pad / amdgpu_ib.c:61-81 SA(64,align 256)\n\n");

	probe_hw();

	/* Case 1 — CHIP_RAVEN root PD clear (the bug): 512-entry root PD,
	 * hole split 256/256 (rev0/1).  Dual set_pte_pde. */
	rc |= run_clear_bo_case("Raven-root-512", 1, 0x8000000000000000ULL - 0x4000,
				256, 256);

	/* Case 2 — Raven2 root PD (rev>=2): 257 entries, ats=256, entries=1 */
	rc |= run_clear_bo_case("Raven2-root-257", 1, 0x8000000000000000ULL - 0x4000,
				256, 1);

	/* Case 3 — non-ATS / non-root single call (boundary: exactly fits) */
	rc |= run_clear_bo_case("non-ATS-single", 0, 0x123450000ULL, 0, 512);

	printf("== summary ==\n");
	printf("   cases with OOB write: %s\n", rc ? "YES — 32-byte (24dw) overrun into 16dw buffer" : "no");
	return 0;
}
