/*
 * DF-4862 PATCHED TWIN — identical to cgs_mec_jt_oob.c except the two
 * fixes from fix.diff applied at the amdgpu_cgs.c construction site
 * (amdgpu_cgs.c:214-215 and :234-235).  Every other byte identical.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdbool.h>
#include <errno.h>
#include <stddef.h>

typedef uint8_t  u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;

#define PAGE_SIZE_ 4096UL
#define ALIGN_(x, a) (((x) + ((a)-1)) & ~((a)-1))

/* ---- 1:1 struct layouts -------------------------------------------------- */

/* sys/dev/drm/amd/amdgpu/amdgpu_ucode.h:26-37 */
struct common_firmware_header {
	u32 size_bytes;
	u32 header_size_bytes;
	u16 header_version_major;
	u16 header_version_minor;
	u16 ip_version_major;
	u16 ip_version_minor;
	u32 ucode_version;
	u32 ucode_size_bytes;
	u32 ucode_array_offset_bytes;
	u32 crc32;
};

/* amdgpu_ucode.h:61-66 */
struct gfx_firmware_header_v1_0 {
	struct common_firmware_header header;
	u32 ucode_feature_version;
	u32 jt_offset; /* jt location */
	u32 jt_size;  /* size of jt */
};

/* sys/sys/firmware.h:51-56 (DragonFly request_firmware object) */
struct firmware {
	const char	*name;		/* offset 0 */
	const uint8_t	*data;		/* offset 8  <-- type-confusion read */
	size_t		 datasize;
	unsigned int	 version;
};

/* sys/dev/drm/amd/amdgpu/amdgpu_ucode.h:187-210 */
enum AMDGPU_UCODE_ID {
	AMDGPU_UCODE_ID_SDMA0 = 0,
	AMDGPU_UCODE_ID_SDMA1,
	AMDGPU_UCODE_ID_CP_CE,
	AMDGPU_UCODE_ID_CP_PFP,
	AMDGPU_UCODE_ID_CP_ME,
	AMDGPU_UCODE_ID_CP_MEC1,
	AMDGPU_UCODE_ID_CP_MEC1_JT,
	AMDGPU_UCODE_ID_CP_MEC2,
	AMDGPU_UCODE_ID_CP_MEC2_JT,
	AMDGPU_UCODE_ID_RLC_G,
	AMDGPU_UCODE_ID_RLC_RESTORE_LIST_CNTL,
	AMDGPU_UCODE_ID_RLC_RESTORE_LIST_GPM_MEM,
	AMDGPU_UCODE_ID_RLC_RESTORE_LIST_SRM_MEM,
	AMDGPU_UCODE_ID_STORAGE,
	AMDGPU_UCODE_ID_SMC,
	AMDGPU_UCODE_ID_UVD,
	AMDGPU_UCODE_ID_UVD1,
	AMDGPU_UCODE_ID_VCE,
	AMDGPU_UCODE_ID_VCN,
	AMDGPU_UCODE_ID_DMCU_ERAM,
	AMDGPU_UCODE_ID_DMCU_INTV,
	AMDGPU_UCODE_ID_MAXIMUM,	/* == 21 */
};

/* amdgpu_ucode.h:236-250 — size 48 (8-aligned) */
struct amdgpu_firmware_info {
	enum AMDGPU_UCODE_ID ucode_id;		/* +0  (+4 pad) */
	const struct firmware *fw;		/* +8  */
	u64 mc_addr;				/* +16 */
	void *kaddr;				/* +24 */
	u32 ucode_size;				/* +32 */
	u32 tmr_mc_addr_lo;			/* +36 */
	u32 tmr_mc_addr_hi;			/* +40 */
};

/* amdgpu_ucode.h:252-268 — prove the one-past-end alias on the guest
 * toolchain: ucode[MAXIMUM].fw must land exactly on fw_buf. */
#define AMDGPU_FW_LOAD_DIRECT 0
#define AMDGPU_FW_LOAD_SMU    1
#define AMDGPU_FW_LOAD_PSP    2
struct amdgpu_firmware {
	struct amdgpu_firmware_info ucode[AMDGPU_UCODE_ID_MAXIMUM];
	int load_type;				/* +1008 (+4 pad) */
	void *fw_buf;				/* +1016 */
	unsigned int fw_size;
	unsigned int max_ucodes;
	const void *funcs;
	void *rbuf;
	int mutex;
	const struct firmware *gpu_info_fw;
	void *fw_buf_ptr;
	u64 fw_buf_mc;
};
_Static_assert(sizeof(struct amdgpu_firmware_info) == 48, "info size");
_Static_assert(AMDGPU_UCODE_ID_MAXIMUM == 21, "maximum == 21");
_Static_assert(offsetof(struct amdgpu_firmware, fw_buf)
	    == AMDGPU_UCODE_ID_MAXIMUM * 48 + 8,
	    "ucode[MAXIMUM].fw aliases fw_buf");
_Static_assert(offsetof(struct amdgpu_firmware_info, fw) == 8, "fw @ +8");

/* sys/dev/drm/amd/include/cgs_common.h:46-63 */
enum cgs_ucode_id {
	CGS_UCODE_ID_SMU = 0,
	CGS_UCODE_ID_SMU_SK,
	CGS_UCODE_ID_SDMA0,
	CGS_UCODE_ID_SDMA1,
	CGS_UCODE_ID_CP_CE,
	CGS_UCODE_ID_CP_PFP,
	CGS_UCODE_ID_CP_ME,
	CGS_UCODE_ID_CP_MEC,
	CGS_UCODE_ID_CP_MEC_JT1,
	CGS_UCODE_ID_CP_MEC_JT2,
	CGS_UCODE_ID_GMCON_RENG,	/* <-- NOT handled by fw_type_convert */
	CGS_UCODE_ID_RLC_G,
	CGS_UCODE_ID_STORAGE,
	CGS_UCODE_ID_MAXIMUM,
};

/* cgs_common.h:69-80 */
struct cgs_firmware_info {
	u16 version;
	u16 fw_version;
	u16 feature_version;
	u32 image_size;
	u64 mc_addr;
	u32 ucode_start_address;
	void *kptr;
	bool is_kicker;
};

/* ---- mock device plumbing ------------------------------------------------ */

struct amdgpu_device {
	struct firmware fw_pfp, fw_me, fw_ce, fw_rlc, fw_mec;
	struct amdgpu_firmware firmware;	/* the real layout replica */
	struct { u16 pfp_fw_version, me_fw_version, ce_fw_version,
		 mec_fw_version, rlc_fw_version; int asic_type; } gfx;
};
#define CHIP_TOPAZ 8

struct amdgpu_cgs_device { const void *ops; struct amdgpu_device *adev; };
struct cgs_device { const void *ops; };

/* smu_ucode_xfer_vi.h:71-93 (SMU TOC entry, consumed by the SMU DMA engine) */
struct SMU_Entry {
	u16 id, version;
	u32 image_addr_high, image_addr_low;
	u32 meta_data_addr_high, meta_data_addr_low;
	u32 data_size_byte;
	u16 flags, num_register_entries;
};

struct pp_hwmgr {
	struct amdgpu_device *adev;
	bool not_vf;
};

static struct amdgpu_device ADEV;

/* kernel-side (CPU) read-from-firmware-blob bounds accounting */
static unsigned long g_kernel_oob_reads;	/* bytes read past a fw blob */

/* ---- [VERBATIM] amdgpu_cgs.c:111-156 -------------------------------------- */
enum CHIP_ { CHIP_VEGA20 = 30 };
#define le32_to_cpu(x) (x)

static enum AMDGPU_UCODE_ID fw_type_convert(struct cgs_device *cgs_device, u32 fw_type)
{
	struct amdgpu_device *adev =
		((struct amdgpu_cgs_device *)cgs_device)->adev;
	enum AMDGPU_UCODE_ID result = AMDGPU_UCODE_ID_MAXIMUM;

	switch (fw_type) {
	case CGS_UCODE_ID_SDMA0:
		result = AMDGPU_UCODE_ID_SDMA0;
		break;
	case CGS_UCODE_ID_SDMA1:
		result = AMDGPU_UCODE_ID_SDMA1;
		break;
	case CGS_UCODE_ID_CP_CE:
		result = AMDGPU_UCODE_ID_CP_CE;
		break;
	case CGS_UCODE_ID_CP_PFP:
		result = AMDGPU_UCODE_ID_CP_PFP;
		break;
	case CGS_UCODE_ID_CP_ME:
		result = AMDGPU_UCODE_ID_CP_ME;
		break;
	case CGS_UCODE_ID_CP_MEC:
	case CGS_UCODE_ID_CP_MEC_JT1:
		result = AMDGPU_UCODE_ID_CP_MEC1;
		break;
	case CGS_UCODE_ID_CP_MEC_JT2:
		if (adev->gfx.asic_type >= CHIP_TOPAZ)
			result = AMDGPU_UCODE_ID_CP_MEC1;
		else
			result = AMDGPU_UCODE_ID_CP_MEC2;
		break;
	case CGS_UCODE_ID_RLC_G:
		result = AMDGPU_UCODE_ID_RLC_G;
		break;
	case CGS_UCODE_ID_STORAGE:
		result = AMDGPU_UCODE_ID_STORAGE;
		break;
	default:
		fprintf(stderr, "DRM_ERROR: Firmware type not supported\n");
	}
	return result;
}

/* ---- [VERBATIM] amdgpu_cgs.c:158-199 -------------------------------------- */
static u16 amdgpu_get_firmware_version(struct cgs_device *cgs_device,
					enum cgs_ucode_id type)
{
	struct amdgpu_device *adev =
		((struct amdgpu_cgs_device *)cgs_device)->adev;
	u16 fw_version = 0;

	switch (type) {
		case CGS_UCODE_ID_SDMA0:
			fw_version = 0; break;
		case CGS_UCODE_ID_CP_CE:
			fw_version = adev->gfx.ce_fw_version; break;
		case CGS_UCODE_ID_CP_PFP:
			fw_version = adev->gfx.pfp_fw_version; break;
		case CGS_UCODE_ID_CP_ME:
			fw_version = adev->gfx.me_fw_version; break;
		case CGS_UCODE_ID_CP_MEC:
		case CGS_UCODE_ID_CP_MEC_JT1:
		case CGS_UCODE_ID_CP_MEC_JT2:
			fw_version = adev->gfx.mec_fw_version; break;
		case CGS_UCODE_ID_RLC_G:
			fw_version = adev->gfx.rlc_fw_version; break;
		case CGS_UCODE_ID_STORAGE:
			break;
		default:
			break;
	}
	return fw_version;
}

/* ---- [VERBATIM] amdgpu_cgs.c:201-238 (non-SMU branch; SMU branch elided —
 *        that is the DF-4774 site and is not exercised here) ---------------- */
static int amdgpu_cgs_get_firmware_info(struct cgs_device *cgs_device,
					enum cgs_ucode_id type,
					struct cgs_firmware_info *info)
{
	struct amdgpu_device *adev =
		((struct amdgpu_cgs_device *)cgs_device)->adev;

	if ((CGS_UCODE_ID_SMU != type) && (CGS_UCODE_ID_SMU_SK != type)) {
		u64 gpu_addr;
		u32 data_size;
		const struct gfx_firmware_header_v1_0 *header;
		enum AMDGPU_UCODE_ID id;
		struct amdgpu_firmware_info *ucode;

		id = fw_type_convert(cgs_device, type);
		if (id >= AMDGPU_UCODE_ID_MAXIMUM)		/* FIX DF-4863 */
			return -EINVAL;
		ucode = &adev->firmware.ucode[id];		/* :215 */
		if (ucode->fw == NULL)				/* :216 */
			return -EINVAL;

		gpu_addr  = ucode->mc_addr;			/* :219 */
		header = (const struct gfx_firmware_header_v1_0 *)ucode->fw->data;
		data_size = le32_to_cpu(header->header.ucode_size_bytes); /* :221 */

		if ((type == CGS_UCODE_ID_CP_MEC_JT1) ||
		    (type == CGS_UCODE_ID_CP_MEC_JT2)) {
			gpu_addr += ALIGN_(le32_to_cpu(header->header.ucode_size_bytes), PAGE_SIZE_);
			data_size = le32_to_cpu(header->jt_size) << 2;
		}

		info->kptr = ucode->kaddr;
		info->image_size = data_size;
		info->mc_addr = gpu_addr;
		info->version = (u16)le32_to_cpu(header->header.ucode_version);

		if (CGS_UCODE_ID_CP_MEC == type) {
			u64 jt_off_dw = (u64)le32_to_cpu(header->jt_offset) << 2; /* :235 */
			if (jt_off_dw > data_size) {		/* FIX DF-4862 */
				fprintf(stderr, "DRM_ERROR: MEC jt payload window beyond ucode image\n");
				return -EINVAL;
			}
			info->image_size = (u32)jt_off_dw;
		}

		info->fw_version = amdgpu_get_firmware_version(cgs_device, type);
		info->feature_version = (u16)le32_to_cpu(header->ucode_feature_version);
	} else {
		/* SMU branch: amdgpu_cgs.c:239-473 — DF-4774 site, elided */
		return -EINVAL;
	}
	return 0;
}

/* ---- [VERBATIM] amdgpu_ucode.c:251-259 ------------------------------------ */
static int amdgpu_ucode_validate(const struct firmware *fw)
{
	const struct common_firmware_header *hdr =
		(const struct common_firmware_header *)fw->data;

	if (fw->datasize == le32_to_cpu(hdr->size_bytes))
		return 0;

	return -EINVAL;
}

/* helper: read common header field of ucode->fw (pure macro of the verbatim cast) */
#define cp_hdr_fld(uc, f) (((const struct common_firmware_header *)((uc)->fw->data))->f)

/* ---- [VERBATIM] amdgpu_ucode.c:316-344 (non-PSP first branch) + bounds probe */
static void amdgpu_ucode_init_single_fw(struct amdgpu_device *adev,
					struct amdgpu_firmware_info *ucode,
					u64 mc_addr, void *kptr)
{
	if (NULL == ucode->fw)
		return;

	ucode->mc_addr = mc_addr;
	ucode->kaddr = kptr;

	if (ucode->ucode_id == AMDGPU_UCODE_ID_STORAGE)
		return;

	/* the load_type != PSP first branch: whole declared image staged */
	ucode->ucode_size = le32_to_cpu(cp_hdr_fld(ucode, ucode_size_bytes));

	if ((uintptr_t)ucode->fw->data + ucode->ucode_size >
	    (uintptr_t)ucode->fw->data + ucode->fw->datasize)
		g_kernel_oob_reads += ucode->ucode_size;	/* staging read would be OOB */
	memcpy(ucode->kaddr,
	       (void *)((u8 *)ucode->fw->data + cp_hdr_fld(ucode, ucode_array_offset_bytes)),
	       ucode->ucode_size);
	(void)adev;
}

/* ---- [VERBATIM math] amdgpu_ucode.c:410-422 (patch_jt :410-422 + init_bo walk 449-485) */
static void amdgpu_ucode_patch_jt(struct amdgpu_firmware_info *ucode)
{
	const struct gfx_firmware_header_v1_0 *header =
		(const struct gfx_firmware_header_v1_0 *)ucode->fw->data;
	const struct common_firmware_header *comm_hdr =
		(const struct common_firmware_header *)ucode->fw->data;
	u8 *dst_addr = ucode->kaddr + ALIGN_(le32_to_cpu(comm_hdr->ucode_size_bytes), PAGE_SIZE_);
	u8 *src_addr = (u8 *)ucode->fw->data +
		       le32_to_cpu(comm_hdr->ucode_array_offset_bytes) +
		       (le32_to_cpu(header->jt_offset) * 4);

	if ((uintptr_t)src_addr + (u64)le32_to_cpu(header->jt_size) * 4 >
	    (uintptr_t)ucode->fw->data + ucode->fw->datasize)
		g_kernel_oob_reads += le32_to_cpu(header->jt_size) * 4; /* OOB read */
	/* the real kernel memcpy reads the source now — a wild src_addr faults
	 * exactly like the kernel would (demonstrated in scenario [3]) */
	(void)*(volatile u8 *)src_addr;
	(void)dst_addr;
}

static void amdgpu_ucode_init_bo(struct amdgpu_device *adev, u64 fw_buf_mc, u8 *fw_buf_ptr)
{
	u64 fw_offset = 0;
	int i;
	struct amdgpu_firmware_info *ucode = NULL;

	for (i = 0; i < (int)adev->firmware.max_ucodes; i++) {
		ucode = &adev->firmware.ucode[i];
		if (ucode->fw) {
			amdgpu_ucode_init_single_fw(adev, ucode,
					fw_buf_mc + fw_offset, fw_buf_ptr + fw_offset);
			if (i == AMDGPU_UCODE_ID_CP_MEC1) {
				const struct gfx_firmware_header_v1_0 *cp_hdr;
				cp_hdr = (const struct gfx_firmware_header_v1_0 *)ucode->fw->data;
				amdgpu_ucode_patch_jt(ucode);
				fw_offset += ALIGN_(le32_to_cpu(cp_hdr->jt_size) << 2, PAGE_SIZE_);
			}
			fw_offset += ALIGN_(ucode->ucode_size, PAGE_SIZE_);
		}
	}
}

/* ---- [VERBATIM] smu7_smumgr.c:305-336 -------------------------------------- */
#define UCODE_ID_CP_MEC 0x0b		/* ppsmcdefs.h UCODE_ID_CP_MEC */
struct SMU_DRAMData_TOC { u32 num_entries, structure_version; struct SMU_Entry entry[16]; };

static int smu7_populate_single_firmware_entry(struct pp_hwmgr *hwmgr,
						u32 fw_type,
						struct SMU_Entry *entry)
{
	int result = 0;
	struct cgs_firmware_info info = {0};
	struct amdgpu_cgs_device cgs_dev = { .ops = NULL, .adev = hwmgr->adev };

	result = amdgpu_cgs_get_firmware_info((struct cgs_device *)&cgs_dev,
			(enum cgs_ucode_id)fw_type, &info);

	if (!result) {
		entry->version = info.fw_version;
		entry->id = (u16)fw_type;
		entry->image_addr_high = (u32)(info.mc_addr >> 32);
		entry->image_addr_low = (u32)info.mc_addr;
		entry->meta_data_addr_high = 0;
		entry->meta_data_addr_low = 0;

		/* digest need be excluded out */
		if (!hwmgr->not_vf)
			info.image_size -= 20;
		entry->data_size_byte = info.image_size;	/* smu7_smumgr.c:327 */
		entry->num_register_entries = 0;
	}

	if ((fw_type == UCODE_ID_CP_MEC) ||
	    (fw_type == 0x11))
		entry->flags = 1;
	else
		entry->flags = 0;

	return result;
}

/* ---- mock GTT / SMU DMA engine ------------------------------------------- */

static u8  *g_fw_buf;		/* the staging BO (amdgpu_firmware.fw_buf_ptr) */
static u64  g_fw_buf_mc;	/* its GPU address */
static unsigned g_fw_size;
static u8  *g_gt_adj, *g_gt_guard;	/* adjacent GTT page + unmapped guard */

static void smu_dma_load_entry(const struct SMU_Entry *e, u64 mec_lo, u64 mec_hi,
			       unsigned long *oob_bytes, int *fault)
{
	u64 base = ((u64)e->image_addr_high << 32) | e->image_addr_low;
	u64 end = base + (u64)e->data_size_byte;
	unsigned long inb = 0;

	*oob_bytes = 0; *fault = 0;
	printf("  SMU DMA: src=0x%llx size=0x%x (%u bytes)  staged MEC window=[0x%llx,0x%llx)\n",
	       (unsigned long long)base, e->data_size_byte, e->data_size_byte,
	       (unsigned long long)mec_lo, (unsigned long long)mec_hi);

	if (base < mec_lo) { *oob_bytes += mec_lo - base; }
	if (end > mec_hi)  { *oob_bytes += end - mec_hi; }

	/* actually walk the read like the SMU would: through the GTT view */
	for (u64 a = base; a < end; a += 4) {
		u64 off = a - g_fw_buf_mc;
		if (off < g_fw_size) {			/* inside the staging BO */
			if (a >= mec_lo && a < mec_hi) inb += 4;
			else (*oob_bytes) += 0;		/* counted above */
			volatile u32 v = *(volatile u32 *)(g_fw_buf + off);
			(void)v;
		} else if (off < g_fw_size + PAGE_SIZE_) {	/* adjacent mapped GTT page */
			volatile u32 v = *(volatile u32 *)(g_gt_adj + off - g_fw_size);
			(void)v;
		} else {					/* unmapped: GPU page fault */
			*fault = 1;
			break;
		}
	}
	printf("  SMU DMA: %lu bytes in-bounds, %lu bytes BEYOND the staged window",
	       inb, *oob_bytes);
	if (*fault) printf(" (hit UNMAPPED GPU page at +0x%llx -> GPU page fault / SMU hang)",
			   (unsigned long long)(base + (u64)e->data_size_byte - g_fw_buf_mc));
	printf("\n");
}

/* ---- firmware file constructor -------------------------------------------- */

static struct firmware *make_mec_fw(u32 jt_offset_dw, u32 jt_size_dw,
				    u32 ucode_size, u32 arr_off, u32 total)
{
	u8 *blob = mmap(NULL, total + PAGE_SIZE_, PROT_READ|PROT_WRITE,
			MAP_PRIVATE|MAP_ANON, -1, 0);
	if (blob == MAP_FAILED) { perror("mmap"); exit(1); }
	mprotect(blob + total, PAGE_SIZE_, PROT_NONE);	/* kmalloc redzone analog */

	struct gfx_firmware_header_v1_0 h;
	memset(&h, 0, sizeof h);
	h.header.size_bytes = total;
	h.header.header_size_bytes = 44;
	h.header.header_version_major = 1;
	h.header.ucode_version = 0x01020304;
	h.header.ucode_size_bytes = ucode_size;
	h.header.ucode_array_offset_bytes = arr_off;
	h.ucode_feature_version = 0x25;
	h.jt_offset = jt_offset_dw;
	h.jt_size = jt_size_dw;
	memcpy(blob, &h, sizeof h);
	for (u32 i = sizeof h; i < total; i += 4)
		*(u32 *)(blob + i) = 0x4D454321;	/* "MEC!" payload */

	struct firmware *f = calloc(1, sizeof *f);
	f->name = "amdgpu/mec_polaris10.bin";
	f->data = blob;
	f->datasize = total;
	return f;
}

/* ---- gfx_v8_0_init_firmware fw_size accounting (gfx_v8_0.c:1176-1214, verbatim math) */
static unsigned compute_fw_size_and_register(struct amdgpu_device *adev)
{
	const struct firmware *fl[5] = {
		&adev->fw_pfp, &adev->fw_me, &adev->fw_ce, &adev->fw_rlc, &adev->fw_mec };
	const enum AMDGPU_UCODE_ID ids[5] = {
		AMDGPU_UCODE_ID_CP_PFP, AMDGPU_UCODE_ID_CP_ME, AMDGPU_UCODE_ID_CP_CE,
		AMDGPU_UCODE_ID_RLC_G,  AMDGPU_UCODE_ID_CP_MEC1 };
	unsigned size = 0;

	for (int i = 0; i < 5; i++) {
		const struct common_firmware_header *hdr =
			(const struct common_firmware_header *)fl[i]->data;
		struct amdgpu_firmware_info *info = &adev->firmware.ucode[ids[i]];
		info->ucode_id = ids[i];
		info->fw = fl[i];
		size += ALIGN_(le32_to_cpu(hdr->ucode_size_bytes), PAGE_SIZE_);
		if (ids[i] == AMDGPU_UCODE_ID_CP_MEC1) {
			const struct gfx_firmware_header_v1_0 *cp =
				(const struct gfx_firmware_header_v1_0 *)fl[i]->data;
			size += ALIGN_(le32_to_cpu(cp->jt_size) << 2, PAGE_SIZE_);
		}
	}
	adev->firmware.fw_size = size;
	adev->firmware.max_ucodes = AMDGPU_UCODE_ID_MAXIMUM - 4;
	return size;
}

static void build_gtt(unsigned fw_size)
{
	g_fw_size = fw_size;
	g_fw_buf = mmap(NULL, fw_size + PAGE_SIZE_ + PAGE_SIZE_,
			PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
	if (g_fw_buf == MAP_FAILED) { perror("mmap gtt"); exit(1); }
	g_gt_adj   = g_fw_buf + fw_size;			/* adjacent mapped GTT page */
	g_gt_guard = g_gt_adj + PAGE_SIZE_;
	mprotect(g_gt_guard, PAGE_SIZE_, PROT_NONE);		/* unmapped GPU page */
	memset(g_fw_buf, 0x5A, fw_size);
	memset(g_gt_adj, 0xA5, PAGE_SIZE_);
	g_fw_buf_mc = 0x100000000ULL;		/* arbitrary GPU base */
}

static void scenario(const char *tag, u32 jt_off_dw, u32 jt_sz_dw, int expect)
{
	struct pp_hwmgr hwmgr = { .adev = &ADEV, .not_vf = true };
	struct SMU_DRAMData_TOC toc;

	printf("\n[%s] MEC header: jt_offset=0x%x dwords, jt_size=0x%x dwords\n",
	       tag, jt_off_dw, jt_sz_dw);

	/* fresh asic state: rebuild fw + staging exactly as driver attach would */
	ADEV.fw_mec = *make_mec_fw(jt_off_dw, jt_sz_dw, 0x1000, 0x1000, 0x12000);
	ADEV.fw_pfp = *make_mec_fw(0x300, 0x100, 0x1000, 0x1000, 0x12000);
	ADEV.fw_me  = ADEV.fw_ce = ADEV.fw_rlc = ADEV.fw_pfp;
	memset(&ADEV.firmware, 0, sizeof ADEV.firmware);
	ADEV.firmware.load_type = 1 /* SMU */;

	unsigned fw_size = compute_fw_size_and_register(&ADEV);
	printf("  amdgpu_ucode_validate(mec.bin): %s (datasize==size_bytes only)\n",
	       amdgpu_ucode_validate(&ADEV.fw_mec) == 0 ? "PASS" : "FAIL");

	build_gtt(fw_size);
	unsigned long kern_oob_before = g_kernel_oob_reads;
	amdgpu_ucode_init_bo(&ADEV, g_fw_buf_mc, g_fw_buf);
	printf("  staging: kernel-side OOB reads during init_bo/patch_jt: %lu bytes%s\n",
	       g_kernel_oob_reads - kern_oob_before,
	       (g_kernel_oob_reads == kern_oob_before) ? " (fully in-bounds)" : " (OOB!)");

	memset(&toc, 0, sizeof toc); toc.num_entries = 0; toc.structure_version = 1;
	int r = smu7_populate_single_firmware_entry(&hwmgr, CGS_UCODE_ID_CP_MEC,
		                                            &toc.entry[toc.num_entries++]);
	if (r) { printf("  populate CP_MEC entry: %d (rejected at the cgs construction — FIX VALIDATED)\n", r); goto out; }

	struct amdgpu_firmware_info *mec = &ADEV.firmware.ucode[AMDGPU_UCODE_ID_CP_MEC1];
	u64 w_lo = mec->mc_addr, w_hi = mec->mc_addr +
		ALIGN_(0x1000, PAGE_SIZE_) + ALIGN_(jt_sz_dw*4, PAGE_SIZE_);
	printf("  TOC entry[%u]: id=%u data_size_byte=0x%x (%u) image_addr=0x%llx\n",
	       toc.num_entries-1, toc.entry[0].id, toc.entry[0].data_size_byte,
	       toc.entry[0].data_size_byte,
	       (unsigned long long)(((u64)toc.entry[0].image_addr_high<<32)|toc.entry[0].image_addr_low));

	unsigned long oob; int fault;
	smu_dma_load_entry(&toc.entry[0], w_lo, w_hi, &oob, &fault);

	if (expect == 1 && oob == 0)
		printf("  RESULT: legit header -> fully in-bounds (no false positive)\n");
	else if (expect == 2 && oob > 0)
		printf("  RESULT: SMU-SIDE OOB DMA READ CONFIRMED: %lu bytes beyond the staged fw_buf window%s\n",
		       oob, fault ? " + unmapped-page fault class" : "");
out:
	munmap((void*)ADEV.fw_mec.data, 0x12000 + PAGE_SIZE_);
	munmap(g_fw_buf, g_fw_size + 2*PAGE_SIZE_);
}

/* scenario [4]: one-past-end aliasing (DF-4863) */
static void scenario_alias(void)
{
	struct amdgpu_cgs_device cgs = { .ops = NULL, .adev = &ADEV };
	struct cgs_firmware_info info = {0};
	struct pp_hwmgr hwmgr = { .adev = &ADEV, .not_vf = true };

	printf("\n[4] fw_type_convert default -> AMDGPU_UCODE_ID_MAXIMUM one-past-end\n");
	memset(&ADEV.firmware, 0, sizeof ADEV.firmware);
	ADEV.firmware.load_type = 1;
	ADEV.firmware.fw_buf = g_fw_buf;		/* non-NULL after init_bo (amdgpu_ucode.c:461) */

	/* what the verbatim code reads at ucode[MAXIMUM].fw: exactly fw_buf */
	u64 *aliased = (u64 *)((u8 *)&ADEV.firmware.ucode[AMDGPU_UCODE_ID_MAXIMUM]
				+ offsetof(struct amdgpu_firmware_info, fw));
	printf("  &firmware.ucode[MAXIMUM].fw = %p  == &firmware.fw_buf = %p  : %s\n",
	       (void *)aliased, (void *)&ADEV.firmware.fw_buf,
	       (void *)aliased == (void *)&ADEV.firmware.fw_buf ? "ALIAS CONFIRMED" : "??");

	/* make the type-confused ->data deref survivable to show propagation:
	 * fw_buf points at our staging buffer; its +8 qword (struct amdgpu_bo
	 * internals on real hw) is read as `const uint8_t *data` (fw.h:53). */
	*(u64 *)(g_fw_buf + 8) = (u64)(g_fw_buf + 0x100);	/* "garbage" bo field */
	int r = amdgpu_cgs_get_firmware_info((struct cgs_device *)&cgs,
			CGS_UCODE_ID_GMCON_RENG, &info);
	printf("  cgs_get_firmware_info(CGS_UCODE_ID_GMCON_RENG) = %d (%s)\n", r,
	       r == -EINVAL ? "one-past-end index rejected — FIX VALIDATED" : "NOT REJECTED");
	if (r == 0)
		printf("  UNEXPECTED: type-confused header=bo+8 -> image_size=0x%x version=0x%x "
		       "propagated to caller (wild deref on real hw: ttm_buffer_object "
		       "internals as firmware*)\n", info.image_size, info.version);
}

int main(void)
{
	setvbuf(stdout, NULL, _IONBF, 0);
	printf("DF-4862 harness: amdgpu_cgs.c:234-235 CP_MEC image_size = jt_offset<<2 (unvalidated)\n");

	/* [1] legit header: JT at 0x300 dwords (0xC00 B) inside 0x1000 image */
	scenario("1", 0x300, 0x100, 1);

	/* [2] crafted: kernel-side fully in-bounds, SMU-side massively OOB */
	scenario("2", 0x4000, 0x100, 2);

	/* [3] extreme: also shows the patch_jt overlap (DF-1947) in a child */
	{
		pid_t p = fork();
		if (p == 0) {
			printf("\n[3] jt_offset=0x3F000000 (image_size wraps to ~4GB)\n");
			ADEV.fw_mec = *make_mec_fw(0x3F000000, 0x100, 0x1000, 0x1000, 0x12000);
			memset(&ADEV.firmware, 0, sizeof ADEV.firmware);
			ADEV.firmware.load_type = 1;
			unsigned fsz = compute_fw_size_and_register(&ADEV);
			build_gtt(fsz);
			amdgpu_ucode_init_bo(&ADEV, g_fw_buf_mc, g_fw_buf);
			printf("  survived init_bo (kernel OOB reads: %lu bytes)\n", g_kernel_oob_reads);
			_exit(0);
		}
		int st; waitpid(p, &st, 0);
		if (WIFSIGNALED(st))
			printf("[3] child killed by SIGSEGV (%d) — kernel panic class via "
			       "patch_jt wild source (DF-1947 overlap); cgs image_size=0x%x\n",
			       WTERMSIG(st), 0x3F000000u << 2);
		else
			printf("[3] child survived init_bo\n");
	}

	/* [4] one-past-end alias demonstration (DF-4863) */
	{
		unsigned fsz = 5 * 0x1000 + 0x1000;
		build_gtt(fsz);
		scenario_alias();
		munmap(g_fw_buf, g_fw_size + 2*PAGE_SIZE_);
	}

	printf("\nDONE\n");
	return 0;
}
