/*
 * DF-3890 harness — kv_convert_vid2_to_vid7 with num_entries == 0:
 * vid_mapping_table->entries[num_entries - 1] wild read (+~16 GB).
 *
 * Mirrors sys/dev/drm/amd/amdgpu/kv_dpm.c:72-92 (kv_convert_vid2_to_vid7,
 * sink at :90) and the identical pattern at :114
 * (kv_convert_vid7_to_vid2), with 1:1 struct layouts (kv_dpm.h).
 *
 * Preconditions (all VBIOS-controlled, parse-legal):
 *   - IntegratedSystemInfo V1_8 sAvail_SCLK[] has strictly-increasing
 *     nonzero ulSupportedSCLK (so num_max_dpm_entries > 0 and the guard at
 *     kv_dpm.c:2040 passes) but usVoltageID == 0 for every entry
 *     -> sumo_construct_vid_mapping_table leaves every vid_7bit 0
 *     -> compaction breaks at i == 0 -> num_entries = 0.
 *   - PowerPlayInfo table has no VddcDependencyOnSCLK subtable (or
 *     usVddcDependencyOnSCLKOffset == 0) -> vddc_dependency_on_sclk.count
 *     == 0 -> the else branch at kv_dpm.c:86-91 is taken.
 *
 * Reach: kv_dpm_init -> kv_parse_sys_info_table (:2617)
 *        -> kv_construct_max_power_limits_table (:2045)
 *        -> kv_convert_2bit_index_to_voltage (:690)
 *        -> kv_convert_vid2_to_vid7 (:90)  -- at every KV boot.
 *
 * Modes:
 *   ./t          accounting: prints the unsigned index / byte offset.
 *   ./t crash    executes the verbatim read -> read at entries + 0xFFFFFFFF*4
 *                = +17179869180 bytes -> SIGSEGV (kernel equivalent: page
 *                fault in kernel mode -> panic).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>

#define SUMO_MAX_NUMBER_VOLTAGES 4

struct sumo_vid_mapping_entry {
	uint16_t vid_2bit;
	uint16_t vid_7bit;
};

struct sumo_vid_mapping_table {
	uint32_t num_entries;
	struct sumo_vid_mapping_entry entries[SUMO_MAX_NUMBER_VOLTAGES];
};

/* verbatim kv_dpm.c:72-92 shape; vddc_sclk_table.count == 0 so only the
 * else branch is live. */
static uint32_t kv_convert_vid2_to_vid7(struct sumo_vid_mapping_table *vid_mapping_table,
					uint32_t vid_2bit)
{
	uint32_t i;

	/* if (vddc_sclk_table && vddc_sclk_table->count) ... : count == 0 */
	for (i = 0; i < vid_mapping_table->num_entries; i++) {
		if (vid_mapping_table->entries[i].vid_2bit == vid_2bit)
			return vid_mapping_table->entries[i].vid_7bit;
	}
	return vid_mapping_table->entries[vid_mapping_table->num_entries - 1].vid_7bit; /* :90 */
}

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

	if (crash) {
		uint8_t *page = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
				     MAP_ANON | MAP_PRIVATE, -1, 0);
		if (page == MAP_FAILED) { perror("mmap"); return 1; }
		struct sumo_vid_mapping_table *vt = (void *)page;
		vt->num_entries = 0;	/* all usVoltageID == 0 in crafted VBIOS */

		printf("crash mode: entries at %p, reading entries[num_entries-1].vid_7bit\n",
		       (void *)vt->entries);
		fflush(stdout);
		(void)kv_convert_vid2_to_vid7(vt, 0);
		printf("NOT REACHED\n");
		return 0;
	}

	struct sumo_vid_mapping_table vt;
	memset(&vt, 0, sizeof(vt));	/* num_entries = 0 */

	uint64_t idx = (uint64_t)(uint32_t)(vt.num_entries - 1);
	printf("num_entries = %u (crafted VBIOS: all usVoltageID == 0)\n", vt.num_entries);
	printf("index used : entries[%llu]\n", (unsigned long long)idx);
	printf("byte offset: %llu * sizeof(entry)=%zu -> entries + %llu bytes (~%.1f GB)\n",
	       (unsigned long long)idx, sizeof(struct sumo_vid_mapping_entry),
	       (unsigned long long)(idx * sizeof(struct sumo_vid_mapping_entry)),
	       (double)(idx * sizeof(struct sumo_vid_mapping_entry)) / 1e9);
	printf("kernel sink: kv_dpm.c:90 (and twin :114), boot path kv_dpm.c:2617 -> :2045 -> :690\n");
	return 0;
}
