/*
 * DF-3858 PoC — arithmetic-level reproduction of the two defects in
 * vega20_get_sclk_od() / vega20_get_mclk_od()
 * (sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:1318-1381),
 * the DF-3686 / DF-3707 twins for Vega20.
 *
 *  int value        = sclk_table->dpm_levels[sclk_table->count - 1].value;   :1326
 *  int golden_value = golden_sclk_table->dpm_levels[golden_sclk_table->count - 1].value; :1327-1328
 *  value -= golden_value;
 *  value = DIV_ROUND_UP(value * 100, golden_value);                          :1332  <-- #DE
 *
 * Case A (divide-by-zero, DF-3707 twin):
 *   DPM_GFXCLK feature disabled -> vega20_setup_gfxclk_dpm_table() takes the
 *   boot-clock branch: count=1, dpm_levels[0].value =
 *   data->vbios_boot_state.gfx_clock / 100 (:546), where gfx_clock is the raw
 *   VBIOS bootup_sclk_in10khz copied with no zero check
 *   (ppatomfwctrl.c:523/:556 -> vega20_hwmgr.c:745). A crafted/blank VBIOS
 *   value of 0 makes golden_value == 0 -> DIV_ROUND_UP traps #DE.
 *   Reach: world-readable sysfs pp_sclk_od / pp_mclk_od
 *   (amdgpu_pm.c:1019-1022, S_IRUGO) -> amdgpu_dpm_get_sclk_od ->
 *   pp_dpm_get_sclk_od (amd_powerplay.c:757, only !hwmgr||!pm_en guard;
 *   pm_en is set at powerplay create time, amd_powerplay.c:51).
 *
 * Case B (count-1 unsigned underflow, wild OOB read, DF-3686 twin):
 *   If dpm bring-up aborts inside vega20_setup_default_dpm_tables()
 *   (e.g. SMU message failure), the failure is tolerated by the late_init
 *   error path but dpm_table/golden_dpm_table stay the memset() zero copy
 *   (vega20_hwmgr.c:588) -> count == 0 -> index (uint32_t)0 - 1 = 0xFFFFFFFF
 *   -> dpm_levels[0xFFFFFFFF].value is a read at
 *   offsetof(golden_dpm_table.gfx_table.dpm_levels) + 0xFFFFFFFF * 8 bytes
 *   (~34 GB past the kzalloc'd vega20_hwmgr backend). If the wild read lands
 *   on a zero, the DIV_ROUND_UP then also divides by zero -> #DE.
 *
 * This userspace replica reproduces both arithmetic facts: case A traps with
 * SIGFPE (signal 8 = the same #DE fault class the kernel would take), and
 * case B computes the effective wild index/offset and performs the verbatim
 * access (trapping SIGSEGV, proving the access is out of the object).
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>
#include <stdbool.h>

#define MAX_REGULAR_DPM_NUMBER 16
#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))

/* verbatim struct shapes from vega20_hwmgr.h:140-171 */
struct vega20_dpm_level { bool enabled; uint32_t value; };
struct vega20_single_dpm_table {
	uint32_t count;
	uint32_t dpm_state[4];
	struct vega20_dpm_level dpm_levels[MAX_REGULAR_DPM_NUMBER];
};

static struct vega20_single_dpm_table table;	/* stand-in for golden table */

/* verbatim vega20_get_sclk_od() body, vega20_hwmgr.c:1318-1335 */
static int get_sclk_od(void)
{
	int value = table.dpm_levels[table.count - 1].value;		/* :1326 */
	int golden_value = table.dpm_levels[table.count - 1].value;	/* :1327 */

	value -= golden_value;
	value = DIV_ROUND_UP(value * 100, golden_value);		/* :1332 */
	return value;
}

int main(int argc, char **argv)
{
	int mode = argc > 1 ? atoi(argv[1]) : 0;

	if (mode == 0) {
		/* Case A: DPM_GFXCLK disabled, VBIOS bootup_sclk_in10khz == 0
		 * -> count=1, dpm_levels[0].value = 0/100 = 0 (vega20_hwmgr.c:546) */
		table.count = 1;
		table.dpm_levels[0].value = 0;
		printf("case A: golden top clock = 0 (VBIOS boot clock 0, "
		       "DPM_GFXCLK disabled) -> DIV_ROUND_UP(x, 0)\n");
		fflush(stdout);
		int v = get_sclk_od();
		printf("UNEXPECTED: returned %d without trapping\n", v);
		return 1;
	}

	/* Case B: golden table still the memset-zero copy after aborted
	 * vega20_setup_default_dpm_tables() -> count == 0 */
	table.count = 0;
	printf("case B: golden gfx_table.count = 0 -> index (uint32_t)0-1 = %u\n",
	       (uint32_t)(table.count - 1));
	printf("        effective read offset = %llu bytes past dpm_levels[0] "
	       "(elem size %zu)\n",
	       (unsigned long long)(uint64_t)(table.count - 1) *
			sizeof(struct vega20_dpm_level),
	       sizeof(struct vega20_dpm_level));
	fflush(stdout);
	int v = get_sclk_od();	/* verbatim access: traps SIGSEGV or feeds 0/garbage
				 * into the case-A divide */
	printf("wild read returned %d (no trap; garbage leaked into OD math)\n", v);
	return 0;
}
