/*
 * DF-3956 — radeon kv_dpm.c vid_mapping_table num_entries==0 ->
 *           entries[0xFFFFFFFF] wild read (~+16 GiB)
 * ================================================================
 * Target: sys/dev/drm/radeon/kv_dpm.c (pass 2)
 *
 * Producer (other file, known as amdgpu DF-3890 / radeon sumo DF-1439 family):
 *   sumo_construct_vid_mapping_table() sumo_dpm.c:1616-1645 terminates with
 *   vid_mapping_table->num_entries = i; where i == 0 when no entry has a
 *   nonzero vid_7bit (all sAvail_SCLK[].ulSupportedSCLK == 0, or the
 *   IntegratedSystemInfo table is missing entirely — kv_parse_sys_info_table
 *   kv_dpm.c:2495-2546 skips everything and still returns 0).
 *   NOTE: the usVoltageIndex OOB *write* inside that constructor is DF-1437
 *   (sumo_dpm.c) — NOT re-reported here; this finding is the kv_dpm.c
 *   consumer-side underflow.
 *
 * Consumers (this file):
 *   kv_convert_vid2_to_vid7() kv_dpm.c:562-566 (else-branch, taken when
 *     rdev->pm.dpm.dyn_state.vddc_dependency_on_sclk.count == 0):
 *         return vid_mapping_table->entries[num_entries - 1].vid_7bit;
 *     num_entries is u32 (sumo_dpm.h:64-67) -> 0-1 = 0xFFFFFFFF,
 *     sizeof(entry)=4 -> displacement 0x3FFFFFFFC (~16 GiB) past the
 *     kv_power_info heap object.
 *   kv_convert_vid7_to_vid2() kv_dpm.c:585-590 — identical expression.
 *
 * Boot path: kv_dpm_enable -> kv_init_graphics_levels (:2344) else-branch ->
 *   kv_set_vid (:2382->:618) / high_voltage check (:2378) ->
 *   kv_convert_2bit_index_to_voltage (:604) -> kv_convert_vid2_to_vid7 (:566);
 *   also kv_parse_sys_info_table boot call kv_construct_max_power_limits_table
 *   (:2543->:1973) and kv_get_high_voltage_limit (:2122).
 *   Requires vddc_dependency_on_sclk.count == 0 (KB/ML IGP boards where the
 *   sclk dependency table is absent) + empty/missing IGP table => all
 *   VBIOS-gated.
 *
 * Harness replicates the exact expression against a guard-page-backed object;
 * arithmetic demonstration, NOT a kernel reproduction.
 *
 * Build: cc -O2 -Wall -o trigger_df3956 trigger_df3956.c
 * Run:   ./trigger_df3956 / ./trigger_df3956 crash
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <signal.h>

#define SUMO_MAX_NUMBER_VOLTAGES 4

struct sumo_vid_mapping_entry { unsigned short vid_2bit, vid_7bit; };
struct sumo_vid_mapping_table {			/* sumo_dpm.h:64-67 */
	unsigned int			num_entries;	/* u32 */
	struct sumo_vid_mapping_entry	entries[SUMO_MAX_NUMBER_VOLTAGES];
};

_Static_assert(sizeof(struct sumo_vid_mapping_entry) == 4, "entry layout drifted");

/* Producer replay (simplified to the num_entries computation) */
static void sumo_construct_vid_mapping_table_empty(
	struct sumo_vid_mapping_table *t, const unsigned long avail_sclk[5])
{
	int i, j;
	memset(t, 0, sizeof(*t));
	for (i = 0; i < 5; i++)
		if (avail_sclk[i] != 0)		/* all zero => nothing stored */
			t->entries[0].vid_7bit = 1; /* (indices are DF-1437's bug) */
	for (i = 0; i < SUMO_MAX_NUMBER_VOLTAGES; i++)
		if (t->entries[i].vid_7bit == 0) {
			for (j = i + 1; j < SUMO_MAX_NUMBER_VOLTAGES; j++)
				if (t->entries[j].vid_7bit != 0)
					break;
			if (j == SUMO_MAX_NUMBER_VOLTAGES)
				break;		/* i == 0 when table empty */
		}
	t->num_entries = i;			/* 0 on the crafted/empty table */
}

/* Consumer — verbatim shape of kv_dpm.c:562-566 */
static unsigned short kv_convert_vid2_to_vid7(struct sumo_vid_mapping_table *t,
					      unsigned int vid_2bit,
					      unsigned int vddc_sclk_count)
{
	if (vddc_sclk_count) {			/* if-branch: not our path */
		return 0;
	} else {
		for (unsigned int i = 0; i < t->num_entries; i++)	/* 0 iters */
			if (t->entries[i].vid_2bit == vid_2bit)
				return t->entries[i].vid_7bit;
		return t->entries[t->num_entries - 1].vid_7bit;	/* +0xFFFFFFFF*4 */
	}
}

int main(int argc, char **argv)
{
	int crash = (argc > 1 && !strcmp(argv[1], "crash"));

	unsigned long avail_sclk[5] = { 0, 0, 0, 0, 0 };	/* crafted IGP */
	struct sumo_vid_mapping_table probe;
	sumo_construct_vid_mapping_table_empty(&probe, avail_sclk);
	printf("DF-3956 radeon kv_dpm vid_mapping num_entries==0 (simulation)\n");
	printf("producer: sumo_construct_vid_mapping_table -> num_entries = %u\n",
	       probe.num_entries);
	unsigned int bad32 = (unsigned int)0 - 1;	/* u32 num_entries(0) - 1 */
	unsigned long disp = (size_t)bad32 * sizeof(struct sumo_vid_mapping_entry);
	printf("consumer: kv_convert_vid2_to_vid7 :566 -> entries[0x%x] -> +0x%lx (%.2f GiB)\n",
	       bad32, disp, disp / 1073741824.0);
	printf("kernel effect: wild read ~16 GiB past kv_power_info => page fault => panic\n");

	if (!crash) {
		printf("NON-CRASH MODE: analysis only. Re-run with 'crash' to execute the deref.\n");
		return 0;
	}

	const size_t map_sz = 4096;
	char *region = mmap(NULL, map_sz + 4096, PROT_READ | PROT_WRITE,
			    MAP_PRIVATE | MAP_ANON, -1, 0);
	if (region == MAP_FAILED) { perror("mmap"); return 1; }
	if (mprotect(region + map_sz, 4096, PROT_NONE) != 0) { perror("mprotect"); return 1; }
	struct sumo_vid_mapping_table *t =
		(struct sumo_vid_mapping_table *)(region + map_sz - sizeof(*t));
	sumo_construct_vid_mapping_table_empty(t, avail_sclk);

	pid_t p = fork();
	if (p == 0) {
		printf("child: executing 'entries[num_entries - 1].vid_7bit' (kv_dpm.c:566) ...\n");
		fflush(stdout);
		unsigned short v = kv_convert_vid2_to_vid7(t, 0, 0 /* count==0 */);
		printf("child: NOT REACHED (vid_7bit=0x%x)\n", v);
		_exit(0);
	}
	int st;
	waitpid(p, &st, 0);
	printf("child: %s", WIFSIGNALED(st) ? "SIGNALLED" : "exited");
	if (WIFSIGNALED(st)) printf(" sig=%d", WTERMSIG(st));
	printf(" -> predicted wild read at +0x%lx confirmed faulting\n", disp);
	printf("RESULT: %s\n",
	       (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV) ?
	       "WILD READ FAULTS AS PREDICTED" : "DEVIATION");
	return (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV) ? 0 : 1;
}
