/*
 * DF-4544 — vega20_processpptables.c one-sided count clamps
 * =========================================================
 * Lifted-copy harness (DF-1179/DF-4395 convention: the in-kernel path needs
 * a Vega20 ASIC + crafted VBIOS; absent on the audit guest).
 *
 * Faithful replica of:
 *   - ATOM_Vega20_POWERPLAYTABLE layout (vega20_pptable.h:76-138, pack(1);
 *     PPTable_t stubbed behind smcPPTable -- only .Version is read, at the
 *     exact struct offset)
 *   - the od_feature_count / od_setting_count clamps
 *     (vega20_processpptables.c:823-833)
 *   - copy_overdrive_feature_capabilities_array (:684-712) and
 *     phm_copy_overdrive_settings_limits_array (smu_helper.c:64-84):
 *     allocation sized by the (clamped) VBIOS count
 *   - the consumer condition sequence of vega20_od8_set_feature_capabilities
 *     (vega20_hwmgr.c:894-980), the od8_settings_array fill loop
 *     (vega20_hwmgr.c:1216-1227) and the OD_RANGE sysfs print
 *     (vega20_hwmgr.c:2861-2905)
 *
 * The kmalloc'd driver arrays are emulated with exact-size heap objects
 * followed by a marker-filled tail standing in for "adjacent slab memory".
 * Every read through od_max[]/od_min[]/od_feat[] is bounds-checked against
 * the object size; an access past the object is tallied as an OOB read and
 * its byte distance recorded, exactly like the kernel reading past the
 * kzalloc'd u32 array.
 *
 * Crafting: OverDrive8Table with ODFeatureCount=10 (full, so the capability
 * array is in-bounds and all OD features claim support) but
 * ODSettingCount=1 (od_settings_max/min allocated for ONE u32).
 * Consumers index od_settings_max/min by driver enum 0..13
 * (vega20_hwmgr.h:398-417 -> OD8_SETTING_*).
 *
 * Build: cc -O2 -Wall -o od_count_oflow od_count_oflow.c
 * Run:   ./od_count_oflow
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>

typedef uint8_t  UCHAR;
typedef uint16_t USHORT;
typedef uint32_t ULONG;

#define ATOM_VEGA20_TABLE_REVISION_VEGA20	11
#define ATOM_VEGA20_ODFEATURE_COUNT		10
#define ATOM_VEGA20_ODSETTING_COUNT		14
#define PPTABLE_V20_SMU_VERSION			3

/* driver-side enum (vega20_hwmgr.h:398-417) -- 16 members, indices 0..15 */
enum OD8_SETTING_ID {
	OD8_SETTING_GFXCLK_FMIN = 0,
	OD8_SETTING_GFXCLK_FMAX,
	OD8_SETTING_GFXCLK_FREQ1,
	OD8_SETTING_GFXCLK_VOLTAGE1,
	OD8_SETTING_GFXCLK_FREQ2,
	OD8_SETTING_GFXCLK_VOLTAGE2,
	OD8_SETTING_GFXCLK_FREQ3,
	OD8_SETTING_GFXCLK_VOLTAGE3,
	OD8_SETTING_UCLK_FMAX,
	OD8_SETTING_POWER_PERCENTAGE,
	OD8_SETTING_FAN_ACOUSTIC_LIMIT,
	OD8_SETTING_FAN_MIN_SPEED,
	OD8_SETTING_FAN_TARGET_TEMP,
	OD8_SETTING_OPERATING_TEMP_MAX,
	OD8_SETTING_AC_TIMING,
	OD8_SETTING_FAN_ZERO_RPM_CONTROL,
	OD8_SETTING_COUNT,
};
/* firmware-side enum (vega20_pptable.h:43-73) */
enum {
	ATOM_ODFEATURE_GFXCLK_LIMITS = 0,
	ATOM_ODFEATURE_GFXCLK_CURVE,
	ATOM_ODFEATURE_UCLK_MAX,
	ATOM_ODFEATURE_POWER_LIMIT,
	ATOM_ODFEATURE_FAN_ACOUSTIC_LIMIT,
	ATOM_ODFEATURE_FAN_SPEED_MIN,
	ATOM_ODFEATURE_TEMPERATURE_FAN,
	ATOM_ODFEATURE_TEMPERATURE_SYSTEM,
	ATOM_ODFEATURE_MEMORY_TIMING_TUNE,
	ATOM_ODFEATURE_FAN_ZERO_RPM_CONTROL,
};

#pragma pack(push, 1)
struct atom_common_table_header {
	USHORT structuresize;
	UCHAR  format_revision;
	UCHAR  content_revision;
};

typedef struct _ATOM_VEGA20_OVERDRIVE8_RECORD {
	UCHAR ucODTableRevision;
	ULONG ODFeatureCount;
	UCHAR ODFeatureCapabilities[32];	/* ATOM_VEGA20_ODFEATURE_MAX_COUNT */
	ULONG ODSettingCount;
	ULONG ODSettingsMax[32];		/* ATOM_VEGA20_ODSETTING_MAX_COUNT */
	ULONG ODSettingsMin[32];
} ATOM_VEGA20_OVERDRIVE8_RECORD;

typedef struct _ATOM_VEGA20_POWER_SAVING_CLOCK_RECORD {
	UCHAR ucTableRevision;
	ULONG PowerSavingClockCount;
	ULONG PowerSavingClockMax[16];
	ULONG PowerSavingClockMin[16];
} ATOM_VEGA20_POWER_SAVING_CLOCK_RECORD;

typedef struct _PPTable_t_stub {	/* only .Version is ever read here */
	ULONG Version;
	UCHAR pad[2044];
} PPTable_t_stub;

typedef struct _ATOM_Vega20_POWERPLAYTABLE {
	struct atom_common_table_header sHeader;
	UCHAR  ucTableRevision;
	USHORT usTableSize;
	ULONG  ulGoldenPPID;
	ULONG  ulGoldenRevision;
	USHORT usFormatID;
	ULONG  ulPlatformCaps;
	UCHAR  ucThermalControllerType;
	USHORT usSmallPowerLimit1;
	USHORT usSmallPowerLimit2;
	USHORT usBoostPowerLimit;
	USHORT usODTurboPowerLimit;
	USHORT usODPowerSavePowerLimit;
	USHORT usSoftwareShutdownTemp;
	ATOM_VEGA20_POWER_SAVING_CLOCK_RECORD PowerSavingClockTable;
	ATOM_VEGA20_OVERDRIVE8_RECORD OverDrive8Table;
	USHORT usReserve[5];
	PPTable_t_stub smcPPTable;
} ATOM_Vega20_POWERPLAYTABLE;
#pragma pack(pop)

/* ---------------- kmalloc emulation with OOB accounting ---------------- */

#define TAIL_MARKERS 64			/* bytes of "adjacent slab" per object */

struct guarded_array {
	uint8_t *obj;			/* the exact-size allocation */
	size_t   size;			/* requested size (what kzalloc got) */
	uint64_t oob_reads;		/* reads past `size` */
	int      first_oob_off;		/* byte offset of first OOB read */
	int      max_oob_off;		/* farthest byte offset read OOB */
};

static struct guarded_array g_feat, g_max, g_min;

static void ga_alloc(struct guarded_array *g, size_t size)
{
	g->obj = calloc(1, size + TAIL_MARKERS);
	g->size = size;
	g->oob_reads = 0;
	g->first_oob_off = -1;
	g->max_oob_off = -1;
	/* plant recognisable "adjacent heap" content: 0xA5A5A5A5 + index */
	for (size_t i = 0; i < TAIL_MARKERS / 4; i++)
		((uint32_t *)(g->obj + size))[i] = 0xA5A50000u | (uint32_t)(i + 1);
}

static void ga_free(struct guarded_array *g) { free(g->obj); }

/* a u32 read through the driver pointer, bounds-accounted like the kernel */
static uint32_t ga_read32(struct guarded_array *g, uint32_t idx)
{
	size_t off = (size_t)idx * 4;
	if (off >= g->size) {		/* OOB read past the kzalloc object */
		int d = (int)(off - g->size);
		g->oob_reads++;
		if (g->first_oob_off < 0)
			g->first_oob_off = d;
		if (d > g->max_oob_off)
			g->max_oob_off = d;
	}
	return *(uint32_t *)(g->obj + off);
}

static uint8_t ga_read8(struct guarded_array *g, uint32_t idx)
{
	size_t off = (size_t)idx;
	if (off >= g->size) {
		int d = (int)(off - g->size);
		g->oob_reads++;
		if (g->first_oob_off < 0)
			g->first_oob_off = d;
		if (d > g->max_oob_off)
			g->max_oob_off = d;
	}
	return g->obj[off];
}

/* ---------------- driver-side replicas ---------------- */

struct phm_ppt_v3_information {
	uint8_t  *od_feature_capabilities;
	uint32_t *od_settings_max;
	uint32_t *od_settings_min;
};

static int patched;			/* PATCHED control: floor allocations */

/* copy_overdrive_feature_capabilities_array (vega20_processpptables.c:684) */
static int copy_overdrive_feature_capabilities_array(
		struct phm_ppt_v3_information *pi,
		const UCHAR *pptable_array, uint32_t od_feature_count)
{
	ga_alloc(&g_feat, patched ? ATOM_VEGA20_ODFEATURE_COUNT
				  : od_feature_count);
	for (uint32_t i = 0; i < od_feature_count; i++)
		g_feat.obj[i] = pptable_array[i];
	pi->od_feature_capabilities = g_feat.obj;
	return 0;
}

/* phm_copy_overdrive_settings_limits_array (smu_helper.c:64) */
static int phm_copy_overdrive_settings_limits_array(
		uint8_t which, const ULONG *pptable_array, uint32_t count)
{
	struct guarded_array *g = which ? &g_min : &g_max;
	ga_alloc(g, patched ? ATOM_VEGA20_ODSETTING_COUNT * 4 : count * 4);
	for (uint32_t i = 0; i < count; i++)
		((uint32_t *)g->obj)[i] = pptable_array[i];
	return 0;
}

/* vega20_hwmgr.c:906-976 condition sequence (all SMU features enabled) */
static void od8_set_feature_capabilities(struct phm_ppt_v3_information *pi,
		uint32_t *caps)
{
	if (ga_read8(&g_feat, ATOM_ODFEATURE_GFXCLK_LIMITS) &&
	    ga_read32(&g_max, OD8_SETTING_GFXCLK_FMAX) > 0 &&
	    ga_read32(&g_min, OD8_SETTING_GFXCLK_FMIN) > 0 &&
	    ga_read32(&g_max, OD8_SETTING_GFXCLK_FMAX) >=
	    ga_read32(&g_min, OD8_SETTING_GFXCLK_FMIN))
		*caps |= 1u << 0;
	if (ga_read8(&g_feat, ATOM_ODFEATURE_GFXCLK_CURVE) &&
	    ga_read32(&g_min, OD8_SETTING_GFXCLK_VOLTAGE1) >= 1000 &&
	    ga_read32(&g_max, OD8_SETTING_GFXCLK_VOLTAGE3) <= 1200 &&
	    ga_read32(&g_max, OD8_SETTING_GFXCLK_VOLTAGE3) >=
	    ga_read32(&g_min, OD8_SETTING_GFXCLK_VOLTAGE1))
		*caps |= 1u << 1;
	if (ga_read8(&g_feat, ATOM_ODFEATURE_UCLK_MAX) &&
	    ga_read32(&g_min, OD8_SETTING_UCLK_FMAX) > 0 &&
	    ga_read32(&g_max, OD8_SETTING_UCLK_FMAX) > 0 &&
	    ga_read32(&g_max, OD8_SETTING_UCLK_FMAX) >=
	    ga_read32(&g_min, OD8_SETTING_UCLK_FMAX))
		*caps |= 1u << 2;
	if (ga_read8(&g_feat, ATOM_ODFEATURE_POWER_LIMIT) &&
	    ga_read32(&g_max, OD8_SETTING_POWER_PERCENTAGE) > 0 &&
	    ga_read32(&g_max, OD8_SETTING_POWER_PERCENTAGE) <= 100 &&
	    ga_read32(&g_min, OD8_SETTING_POWER_PERCENTAGE) > 0 &&
	    ga_read32(&g_min, OD8_SETTING_POWER_PERCENTAGE) <= 100)
		*caps |= 1u << 3;
	if (ga_read8(&g_feat, ATOM_ODFEATURE_FAN_ACOUSTIC_LIMIT) &&
	    ga_read32(&g_min, OD8_SETTING_FAN_ACOUSTIC_LIMIT) > 0 &&
	    ga_read32(&g_max, OD8_SETTING_FAN_ACOUSTIC_LIMIT) > 0 &&
	    ga_read32(&g_max, OD8_SETTING_FAN_ACOUSTIC_LIMIT) >=
	    ga_read32(&g_min, OD8_SETTING_FAN_ACOUSTIC_LIMIT))
		*caps |= 1u << 4;
	if (ga_read8(&g_feat, ATOM_ODFEATURE_FAN_SPEED_MIN) &&
	    ga_read32(&g_max, OD8_SETTING_FAN_MIN_SPEED) > 0 &&
	    ga_read32(&g_max, OD8_SETTING_FAN_MIN_SPEED) >=
	    ga_read32(&g_min, OD8_SETTING_FAN_MIN_SPEED))
		*caps |= 1u << 5;
	if (ga_read8(&g_feat, ATOM_ODFEATURE_TEMPERATURE_FAN) &&
	    ga_read32(&g_max, OD8_SETTING_FAN_TARGET_TEMP) > 0 &&
	    ga_read32(&g_min, OD8_SETTING_FAN_TARGET_TEMP) > 0 &&
	    ga_read32(&g_max, OD8_SETTING_FAN_TARGET_TEMP) >=
	    ga_read32(&g_min, OD8_SETTING_FAN_TARGET_TEMP))
		*caps |= 1u << 6;
	if (ga_read8(&g_feat, ATOM_ODFEATURE_TEMPERATURE_SYSTEM) &&
	    ga_read32(&g_max, OD8_SETTING_OPERATING_TEMP_MAX) > 0 &&
	    ga_read32(&g_min, OD8_SETTING_OPERATING_TEMP_MAX) > 0 &&
	    ga_read32(&g_max, OD8_SETTING_OPERATING_TEMP_MAX) >=
	    ga_read32(&g_min, OD8_SETTING_OPERATING_TEMP_MAX))
		*caps |= 1u << 7;
	if (ga_read8(&g_feat, ATOM_ODFEATURE_MEMORY_TIMING_TUNE))
		*caps |= 1u << 8;
	if (ga_read8(&g_feat, ATOM_ODFEATURE_FAN_ZERO_RPM_CONTROL))
		*caps |= 1u << 9;
}

struct od8_single { uint32_t feature_id; int32_t min, max, cur; };
static struct od8_single od8[OD8_SETTING_COUNT];

/* vega20_od8_set_feature_id (vega20_hwmgr.c:982-1067): cap bit -> setting ids */
static const int cap_bit_to_ids[10][8] = {
	{ OD8_SETTING_GFXCLK_FMIN, OD8_SETTING_GFXCLK_FMAX, -1 },
	{ OD8_SETTING_GFXCLK_FREQ1, OD8_SETTING_GFXCLK_VOLTAGE1,
	  OD8_SETTING_GFXCLK_FREQ2, OD8_SETTING_GFXCLK_VOLTAGE2,
	  OD8_SETTING_GFXCLK_FREQ3, OD8_SETTING_GFXCLK_VOLTAGE3, -1 },
	{ OD8_SETTING_UCLK_FMAX, -1 },
	{ OD8_SETTING_POWER_PERCENTAGE, -1 },
	{ OD8_SETTING_FAN_ACOUSTIC_LIMIT, -1 },
	{ OD8_SETTING_FAN_MIN_SPEED, -1 },
	{ OD8_SETTING_FAN_TARGET_TEMP, -1 },
	{ OD8_SETTING_OPERATING_TEMP_MAX, -1 },
	{ -1 },				/* MEMORY_TIMING_TUNE: no setting id */
	{ -1 },				/* FAN_ZERO_RPM: no setting id */
};

/* vega20_hwmgr.c:1216-1227 fill loop */
static void fill_od8(struct phm_ppt_v3_information *pi, uint32_t caps)
{
	(void)pi;
	for (int b = 0; b < 10; b++)
		for (int j = 0; cap_bit_to_ids[b][j] != -1; j++)
			od8[cap_bit_to_ids[b][j]].feature_id =
				(caps & (1u << b)) ? 0xdead : 0;
	for (int i = 0; i < OD8_SETTING_COUNT; i++) {
		if (od8[i].feature_id) {
			od8[i].min = ga_read32(&g_min, i);
			od8[i].max = ga_read32(&g_max, i);
		}
	}
}

/* vega20_hwmgr.c:2861+ OD_RANGE sysfs output */
static void print_od_range(void)
{
	printf("OD_RANGE:\n");
	if (od8[OD8_SETTING_GFXCLK_FMIN].feature_id &&
	    od8[OD8_SETTING_GFXCLK_FMAX].feature_id)
		printf("SCLK: %7uMhz %10uMhz\n",
		    (uint32_t)od8[OD8_SETTING_GFXCLK_FMIN].min,
		    (uint32_t)od8[OD8_SETTING_GFXCLK_FMAX].max);
	if (od8[OD8_SETTING_UCLK_FMAX].feature_id)
		printf("MCLK: %7uMhz %10uMhz\n", 0u,
		    (uint32_t)od8[OD8_SETTING_UCLK_FMAX].max);
	if (od8[OD8_SETTING_GFXCLK_FREQ1].feature_id &&
	    od8[OD8_SETTING_GFXCLK_VOLTAGE3].feature_id)
		printf("VDDC_CURVE_SCLK[0]: %7uMhz %10uMhz\n",
		    (uint32_t)od8[OD8_SETTING_GFXCLK_FREQ1].min,
		    (uint32_t)od8[OD8_SETTING_GFXCLK_FREQ1].max);
}

static int run_case(const char *name, uint32_t feat_cnt, uint32_t set_cnt,
		int is_patched)
{
	struct phm_ppt_v3_information pi = { NULL, NULL, NULL };
	uint32_t caps = 0;

	patched = is_patched;
	printf("=== %s (ODFeatureCount=%u ODSettingCount=%u%s) ===\n",
	    name, feat_cnt, set_cnt, is_patched ? " [PATCHED]" : "");

	/* vega20_processpptables.c:823-846 -- one-sided clamps */
	uint32_t od_feature_count =
		(feat_cnt > ATOM_VEGA20_ODFEATURE_COUNT) ?
		ATOM_VEGA20_ODFEATURE_COUNT : feat_cnt;
	uint32_t od_setting_count =
		(set_cnt > ATOM_VEGA20_ODSETTING_COUNT) ?
		ATOM_VEGA20_ODSETTING_COUNT : set_cnt;

	UCHAR feat_caps[32] = {1,1,1,1,1,1,1,1,1,1};	/* all features on */
	/* in-bounds source values the VBIOS provides for the entries it
	 * declares (count=1 -> only [0] is ever copied) */
	ULONG src_max[32], src_min[32];
	for (int i = 0; i < 32; i++) { src_max[i] = 1980; src_min[i] = 800; }
	copy_overdrive_feature_capabilities_array(&pi, feat_caps,
	    od_feature_count);
	phm_copy_overdrive_settings_limits_array(0, src_max, od_setting_count);
	phm_copy_overdrive_settings_limits_array(1, src_min, od_setting_count);

	printf("alloc: od_feature_capabilities=%zu B, od_settings_max=%zu B, "
	    "od_settings_min=%zu B\n", g_feat.size, g_max.size, g_min.size);

	od8_set_feature_capabilities(&pi, &caps);
	fill_od8(&pi, caps);
	print_od_range();

	printf("od_feature_capabilities OOB reads: %llu (max +%d B past)\n",
	    (unsigned long long)g_feat.oob_reads,
	    g_feat.oob_reads ? g_feat.max_oob_off : 0);
	printf("od_settings_max        OOB reads: %llu (object size %zu B)\n",
	    (unsigned long long)g_max.oob_reads, g_max.size);
	if (g_max.oob_reads)
		printf("    -> first +%d B, farthest +%d B past the object\n",
		    g_max.first_oob_off, g_max.max_oob_off);
	printf("od_settings_min        OOB reads: %llu\n",
	    (unsigned long long)g_min.oob_reads);
	if (g_min.oob_reads)
		printf("    -> first +%d B, farthest +%d B past the object\n",
		    g_min.first_oob_off, g_min.max_oob_off);

	int leaked = 0;
	for (int i = 0; i < OD8_SETTING_COUNT; i++)
		if (od8[i].feature_id &&
		    ((uint32_t)od8[i].max >= 0xA5A50001u &&
		     (uint32_t)od8[i].max <= 0xA5A50010u))
			leaked = 1;
	printf("sysfs OD_RANGE prints adjacent-slab marker values: %s\n\n",
	    leaked ? "YES (kernel heap info leak)" : "no");
	ga_free(&g_feat); ga_free(&g_max); ga_free(&g_min);
	memset(od8, 0, sizeof(od8));
	return leaked;
}

int main(void)
{
	printf("DF-4544 lifted-copy harness: vega20 OD count clamps "
	    "(vega20_processpptables.c:823-846 vs vega20_hwmgr.c:906-976/1216)\n");
	printf("consumer enum range: od_settings idx 0..13, "
	    "od_feature idx 0..9 (vega20_hwmgr.h:398-417)\n\n");

	/* control: honest full-size table -- everything in bounds */
	run_case("CONTROL full counts", 10, 14, 0);
	/* crafted: caps full (10) so all feature gates open, settings tiny (1):
	 * od_settings_max/min allocated 4 B, consumers read idx 1..13 */
	int leaked = run_case("CRAFTED small ODSettingCount", 10, 1, 0);
	/* crafted: tiny caps too -- od_feature_capabilities idx 1..9 OOB */
	run_case("CRAFTED small ODFeatureCount", 1, 1, 0);
	/* patched: allocation floored at driver counts, copy bounded */
	run_case("PATCHED same crafted input", 10, 1, 1);

	printf("%s: crafted table drove reads past the VBIOS-sized kzalloc "
	    "objects%s\n", leaked ? "REPRODUCED" : "NOT REPRODUCED",
	    leaked ? " and the markers reached the OD_RANGE output" : "");
	return leaked ? 0 : 1;
}
