/*
 * DF-4054 replica — vega12_setup_single_dpm_table fill loop, host-side.
 *
 * Reproduces the exact store pattern of
 * sys/dev/drm/amd/powerplay/hwmgr/vega12_hwmgr.c:507-530 with the real
 * struct geometry from vega12_hwmgr.h (MAX_REGULAR_DPM_NUMBER=16,
 * struct vega12_dpm_level {bool enabled; uint32 value; uint32 param1;}).
 *
 * The SMU-fed num_of_levels (smum_get_argument -> raw uint32 from the
 * firmware response register, only checked > 0 at :481) becomes
 * dpm_table->count at :518 and the loop stores dpm_levels[i] for
 * i < num_of_levels at :520-527 — past dpm_levels[16].
 *
 * This replica proves the arithmetic: with num_of_levels=20 the loop
 * writes 4 x 12 bytes past the end of soc_table.dpm_levels[16] into the
 * following struct members (gfx_table.count / dpm_state / dpm_levels on
 * real hardware; the guard band in this replica catches it). On the
 * driver the same walk continues through the whole kzalloc'd
 * struct vega12_hwmgr backend and past its end for large firmware values.
 *
 * Build:  cc -O2 -Wall -o trigger trigger.c
 * Run:    ./trigger [num_of_levels]
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>

#define MAX_REGULAR_DPM_NUMBER 16

struct vega12_dpm_state {
	uint32_t soft_min_level;
	uint32_t soft_max_level;
	uint32_t hard_min_level;
	uint32_t hard_max_level;
};

struct vega12_dpm_level {
	bool enabled;		/* vega12_hwmgr.h:90 */
	uint32_t value;		/* :91 */
	uint32_t param1;	/* :92 */
};

struct vega12_single_dpm_table {		/* vega12_hwmgr.h:107 */
	uint32_t count;
	struct vega12_dpm_state dpm_state;
	struct vega12_dpm_level dpm_levels[MAX_REGULAR_DPM_NUMBER];
};

struct vega12_dpm_table {			/* vega12_hwmgr.h:125 */
	struct vega12_single_dpm_table soc_table;
	struct vega12_single_dpm_table gfx_table;
	struct vega12_single_dpm_table mem_table;
	/* ... 7 more tables + pcie_table on real hardware ... */
};

#define GUARD 128
static unsigned char backing[sizeof(struct vega12_dpm_table) + GUARD];
static unsigned char *guard_start;

int
main(int argc, char **argv)
{
	struct vega12_dpm_table *tbl = (void *)backing;
	uint32_t num_of_levels = (argc > 1) ? (uint32_t)strtoul(argv[1], NULL, 0) : 20;
	uint32_t i, over = 0;

	memset(backing, 0, sizeof(backing));
	guard_start = backing + sizeof(struct vega12_dpm_table);
	memset(guard_start, 0xAA, GUARD);	/* post-object guard band */

	printf("sizeof(struct vega12_dpm_table)=%zu  soc.dpm_levels at +%zu ends at +%zu\n",
	    sizeof(struct vega12_dpm_table),
	    (size_t)((unsigned char *)&tbl->soc_table.dpm_levels[0] - backing),
	    (size_t)((unsigned char *)&tbl->soc_table.dpm_levels[MAX_REGULAR_DPM_NUMBER] - backing));
	printf("SMU-fed num_of_levels = %u (checked only >0 by the driver)\n",
	    num_of_levels);

	tbl->soc_table.count = num_of_levels;			/* :518 */

	for (i = 0; i < num_of_levels; i++) {			/* :520 */
		uint32_t clk = 0xdeadbe00 + i; /* smum_get_argument() */
		tbl->soc_table.dpm_levels[i].value = clk;	/* :525 */
		tbl->soc_table.dpm_levels[i].enabled = true;	/* :526 */
	}

	/* count stores that landed past the end of the backing object */
	for (i = 0; i < GUARD; i++)
		if (guard_start[i] != 0xAA)
			over++;

	printf("stores executed: %u x {enabled=true,value=SMU-fed}\n",
	    num_of_levels);
	printf("bytes written past end of object (guard band): %u %s\n",
	    over, over ? "<-- HEAP OVERFLOW (replica)" : "");
	printf("in-object cross-field smash: gfx_table.count = 0x%08x "
	    "(expected 0; = value store %u)\n",
	    tbl->gfx_table.count, 16 /* entry 16 lands on gfx_table.count */);
	return (over || tbl->gfx_table.count) ? 0 : 1;
}
