/*
 * DF-4187 / DF-4188 / DF-4189 / DF-4190 — sumo_dpm.c pass-2 verification harness
 * =============================================================================
 * Userspace replica of the vulnerable code paths in
 *   sys/dev/drm/radeon/sumo_dpm.c  (DragonFly 6.5-DEVELOPMENT, GLM-5.3 pass 2)
 *
 * The kernel functions below are copied VERBATIM from sumo_dpm.c (line numbers
 * cited in comments).  Only the following are stubbed, none of which affect
 * the pointer/index arithmetic under test:
 *   - le16_to_cpu()/le32_to_cpu()  -> identity (little-endian host)
 *   - WREG32/WREG32_P/RREG32       -> no-op counters / constant reads
 *     (a WREG32 call budget detects the num_levels-1 underflow runaway loop
 *      that would otherwise issue 2^32 register writes)
 *   - r600_calculate_u_and_p()     -> no-op
 *   - kzalloc()/kfree()            -> canary allocator with 64-byte poison
 *     redzones on BOTH sides of every allocation + double-free/invalid-free
 *     detection (replaces the kernel heap; the kernel code calls kzalloc/
 *     kfree in exactly the same places)
 *   - atom_parse_data_header()     -> satisfied by the caller (data_offset)
 *   - the `if (!rdev->pm.power_state[i].clock_info) return -EINVAL;` check at
 *     sumo_dpm.c:1498 references the legacy atombios power_state array, which
 *     is populated before dpm init in the kernel and passes there; omitted.
 *   - ATOM_PPLIB_POWERPLAYTABLE reduced to the three USHORT array offsets the
 *     code actually reads; the VBIOS image is crafted with the same
 *     definition (logic-level replica — see VERDICT.md).
 *
 * Modes:
 *   4187   no-BOOT-classified VBIOS power table -> sumo_dpm_enable() reaches
 *          sumo_update_current_ps(rdev, boot_ps==NULL) -> NULL->ps_priv page
 *          fault (kernel: page-fault panic; harness: SIGSEGV at 0x28)
 *   4188   kzalloc(struct sumo_ps) failure at state #1 -> parse kfrees
 *          rdev->pm.dpm.ps and leaves it dangling -> (error swallowed per
 *          radeon_pm.c:1448) -> sumo_dpm_fini kfrees it again -> DOUBLE FREE
 *   4189   power state with ucNumDPMLevels==0 -> ps->num_levels stays 0 ->
 *          levels[num_levels-1] == levels[-1] reads 24 bytes BEFORE the
 *          kzalloc'd struct sumo_ps (canary poison 0xA5); "4189z" uses a
 *          zeroed neighbour to reach 65535*100/high_clk div-by-zero in
 *          sumo_calculate_bsp (SIGFPE == kernel div0 panic)
 *   4190   power state with nonClockInfoIndex=250 -> nonClockInfo[250] reads
 *          ~6 KB past the NonClockInfoArray / bios image (rps->class/caps
 *          read from canary poison)
 *
 * Build:  cc -O2 -Wall -std=gnu11 -o harness harness.c
 *         cc -O2 -Wall -std=gnu11 -DFIX_ALL -o harness_fixed harness.c
 * (FIX_ALL compiles the proposed fixes; harness_fixed must complete every
 *  mode without SIGSEGV/SIGFPE/runaway/double-free/poison reads.)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <signal.h>
#include <unistd.h>

#define ENOMEM 12
#define EINVAL 22

typedef uint8_t  UCHAR;
typedef uint16_t USHORT;
typedef uint32_t ULONG;
typedef uint32_t u32;
typedef uint16_t u16;
typedef uint8_t  u8;

#define le16_to_cpu(x) (x)
#define le32_to_cpu(x) (x)
#define DRM_ERROR(fmt, ...) printf("DRM_ERROR: " fmt "\n", ## __VA_ARGS__)

/* ------------------------------------------------------------------ */
/* canary allocator: 64-byte poison redzones on both sides            */
/* ------------------------------------------------------------------ */
#define POISON 0xA5
#define REDZONE 64

struct alloc_rec { void *obj; size_t sz; int live; };
static struct alloc_rec arec[8192];
static int arec_n;
static int kmalloc_calls;
static int kmalloc_fail_at = -1;   /* 1-based: fail the Nth kzalloc */
static unsigned char poison_byte = POISON;
static int canary_violations;
static const char *kmalloc_tag;

static void *kzalloc(size_t sz, int flags)
{
	void *base;
	struct alloc_rec *r;
	size_t total = REDZONE + sz + REDZONE;

	(void)flags;
	kmalloc_calls++;
	if (kmalloc_fail_at == kmalloc_calls) {
		printf("[%s] kzalloc(#%d, %zu) -> INJECTED FAILURE (ENOMEM)\n",
		    kmalloc_tag ? kmalloc_tag : "?", kmalloc_calls, sz);
		return NULL;
	}
	base = malloc(total);
	if (!base)
		return NULL;
	memset(base, poison_byte, REDZONE);                        /* front  */
	memset((char *)base + REDZONE + sz, poison_byte, REDZONE); /* rear   */
	memset((char *)base + REDZONE, 0, sz);                     /* zeroed */
	r = &arec[arec_n++];
	r->obj = (char *)base + REDZONE;
	r->sz = sz;
	r->live = 1;
	return r->obj;
}

static void kfree(void *p)
{
	int i;
	if (p == NULL)
		return;
	for (i = 0; i < arec_n; i++)
		if (arec[i].obj == p)
			break;
	if (i == arec_n) {
		printf("*** CANARY ALLOCATOR: kfree(%p) of a pointer that is "
		    "NOT a live allocation base (INVALID FREE / not the pointer "
		    "that was allocated) ***\n", p);
		canary_violations++;
		return;
	}
	if (!arec[i].live) {
		printf("*** CANARY ALLOCATOR: DOUBLE FREE of %p (size %zu) "
		    "DETECTED ***\n", p, arec[i].sz);
		canary_violations++;
		return;
	}
	arec[i].live = 0;
	/* redzones stay poisoned to catch later UAF reads */
}

/* verify [p, p+len) lies inside one live allocation */
static int check_in_object(const char *what, const void *p, size_t len)
{
	int i;
	for (i = 0; i < arec_n; i++) {
		char *o = arec[i].obj;
		if ((const char *)p >= o && (const char *)p + len <= o + arec[i].sz)
			return 1; /* in-bounds */
	}
	printf("*** CANARY: %s at %p (len %zu) is OUTSIDE every live "
	    "allocation -> heap OOB access ***\n", what, p, len);
	canary_violations++;
	return 0;
}

/* ------------------------------------------------------------------ */
/* minimal struct replicas (field ORDER faithful to the kernel)        */
/* ------------------------------------------------------------------ */
#define SUMO_MAX_HARDWARE_POWERLEVELS 5
#define SUMO_MAX_NUMBER_VOLTAGES 4

struct sumo_pl {
	u32 sclk;
	u32 vddc_index;
	u32 ds_divider_index;
	u32 ss_divider_index;
	u32 allow_gnb_slow;
	u32 sclk_dpm_tdp_limit;
};

struct sumo_ps {
	struct sumo_pl levels[SUMO_MAX_HARDWARE_POWERLEVELS];  /* FIRST member! */
	u32 num_levels;
	u32 flags;
};
#define SUMO_POWERSTATE_FLAGS_FORCE_NBPS1_STATE (1 << 0)
#define SUMO_POWERSTATE_FLAGS_BOOST_STATE       (1 << 1)

enum radeon_vce_level { RADEON_VCE_LEVEL_NONE = 0 };

/* field order identical to sys/dev/drm/radeon/radeon.h:1342-1356 */
struct radeon_ps {
	u32 caps;
	u32 class;
	u32 class2;
	u32 vclk;
	u32 dclk;
	u32 evclk;
	u32 ecclk;
	bool vce_active;
	enum radeon_vce_level vce_level;
	void *ps_priv;
};

struct radeon_dpm {
	struct radeon_ps *ps;
	int num_ps;
	struct radeon_ps *current_ps;
	struct radeon_ps *requested_ps;
	struct radeon_ps *boot_ps;
	struct radeon_ps *uvd_ps;
	void *priv;
};

struct atom_context { uint8_t *bios; };
struct radeon_mode_info { struct atom_context *atom_context; };

struct radeon_device {
	struct radeon_mode_info mode_info;
	struct {
		struct radeon_dpm dpm;
	} pm;
	u16 _atom_data_offset;	/* filled by the atom_parse_data_header stub */
};

/* ------------------------------------------------------------------ */
/* ATOM table replicas (packed like the kernel's #pragma pack(1))      */
/* ------------------------------------------------------------------ */
#define ATOM_PPLIB_CLASSIFICATION_BOOT         (1<<0)
#define ATOM_PPLIB_CLASSIFICATION_UVDSTATE     (1<<5)
#define ATOM_PPLIB_NONCLOCKINFO_VER1           12

typedef struct _ATOM_PPLIB_NONCLOCK_INFO {   /* pptable.h:298 */
	USHORT usClassification;
	UCHAR  ucMinTemperature;
	UCHAR  ucMaxTemperature;
	ULONG  ulCapsAndSettings;
	UCHAR  ucRequiredPower;
	USHORT usClassification2;
	ULONG  ulVCLK;
	ULONG  ulDCLK;
	UCHAR  ucUnused[5];
} __attribute__((packed)) ATOM_PPLIB_NONCLOCK_INFO;

typedef struct _ATOM_PPLIB_SUMO_CLOCK_INFO {  /* pptable.h:413 */
	USHORT usEngineClockLow;
	UCHAR  ucEngineClockHigh;
	UCHAR  vddcIndex;
	USHORT tdpLimit;
	USHORT rsv1;
	ULONG  rsv2[2];
} __attribute__((packed)) ATOM_PPLIB_SUMO_CLOCK_INFO;

typedef struct _ATOM_PPLIB_STATE_V2 {         /* pptable.h:424 */
	UCHAR ucNumDPMLevels;
	UCHAR nonClockInfoIndex;
	UCHAR clockInfoIndex[1];
} __attribute__((packed)) ATOM_PPLIB_STATE_V2;

typedef struct _StateArray {                  /* pptable.h:438 */
	UCHAR ucNumEntries;
	ATOM_PPLIB_STATE_V2 states[1];
} __attribute__((packed)) StateArray;

typedef struct _ClockInfoArray {              /* pptable.h:446 */
	UCHAR ucNumEntries;
	UCHAR ucEntrySize;
	UCHAR clockInfo[1];
} __attribute__((packed)) ClockInfoArray;

typedef struct _NonClockInfoArray {           /* pptable.h:456 */
	UCHAR ucNumEntries;
	UCHAR ucEntrySize;
	ATOM_PPLIB_NONCLOCK_INFO nonClockInfo[1];
} __attribute__((packed)) NonClockInfoArray;

/* reduced ATOM_PPLIB_POWERPLAYTABLE: the three USHORT array offsets
 * (sumo_dpm.c reads only these via union power_info.pplib) */
typedef struct {
	USHORT usStateArrayOffset;
	USHORT usClockInfoArrayOffset;
	USHORT usNonClockInfoArrayOffset;
} __attribute__((packed)) PPLIB_MIN;

union power_info { PPLIB_MIN pplib; };
union pplib_clock_info { ATOM_PPLIB_SUMO_CLOCK_INFO sumo; };
union pplib_power_state {
	struct { UCHAR ucNumDPMLevels; UCHAR nonClockInfoIndex;
		 UCHAR clockInfoIndex[8]; } v2;
};

/* ------------------------------------------------------------------ */
/* sumo private state + register stubs                                 */
/* ------------------------------------------------------------------ */
struct sumo_sys_info {
	u32 bootup_sclk;
	u32 min_sclk;
	u32 bootup_nb_voltage_index;
	u32 boost_sclk;
	u32 boost_vid_2bit;
	u32 sclk_dpm_tdp_limit_boost;
	int enable_boost;
};
struct sumo_power_info {
	u32 asi, pasi, bsp, bsu, pbsp, pbsu, dsp, psp;
	struct sumo_sys_info sys_info;
	struct sumo_pl boot_pl, acpi_pl, boost_pl;
	bool enable_sclk_ds, enable_dynamic_patch_ps, enable_dpm, enable_boost;
	bool enable_auto_thermal_throttling;
	struct radeon_ps current_rps, requested_rps;
	struct sumo_ps current_ps, requested_ps;
};

/* register plumbing stubs (pure no-ops in the harness) */
#define CG_BSP_0			0
#define SCLK_PWRMGT_CNTL			0
#define FIR_RESET				2
#define CG_AT_0					0
#define CG_R(x)					(x)
#define CG_L(x)					(x)
#define CG_R_MASK				0xffff
#define CG_L_MASK				0xffff
#define CG_FFCT_0				0
#define UTC_0(x)				(x)
#define DTC_0(x)				(x)
#define UTC_0_MASK				0xff
#define DTC_0_MASK				0xff
#define CG_SCLK_DPM_CTRL_3			0
#define DPM_SCLK_ENABLE				2
#define SCLK_OVERCLK_DETECT			1
#define TARGET_AND_CURRENT_PROFILE_INDEX	0
#define CURR_SCLK_INDEX_MASK			0xff
#define CURR_INDEX_MASK				0xf

static long wreg_budget = 1000000;
static void wreg_hit(void)
{
	if (--wreg_budget < 0) {
		printf("*** WREG32 budget exhausted: runaway register-write "
		    "loop (u32 num_levels-1 underflow -> 2^32 iterations in "
		    "the kernel: MMIO hang / watch-dog DoS) ***\n");
		canary_violations++;
		exit(97);
	}
}
#define WREG32(r, v)		do { (void)(r); (void)(v); wreg_hit(); } while (0)
#define WREG32_P(r, v, m)	do { (void)(r); (void)(v); (void)(m); wreg_hit(); } while (0)
#define RREG32(r)		(0)

static void r600_calculate_u_and_p(u32 a, u32 b, int c, u32 *p, u32 *u)
{ (void)a; (void)b; (void)c; *p = 0; *u = 0; }
static u32 radeon_get_xclk(struct radeon_device *rdev) { (void)rdev; return 10000; }

static const u32 sumo_utc[15] = {0};
static const u32 sumo_dtc[15] = {0};

/* forward decls */
static int sumo_dpm_set_power_state(struct radeon_device *rdev);

/* ================================================================== */
/* VERBATIM kernel code below — sys/dev/drm/radeon/sumo_dpm.c          */
/* ================================================================== */

/* sumo_dpm.c:77-82 */
static struct sumo_ps *sumo_get_ps(struct radeon_ps *rps)
{
	struct sumo_ps *ps = rps->ps_priv;

	return ps;
}

/* sumo_dpm.c:84-89 */
static struct sumo_power_info *sumo_get_pi(struct radeon_device *rdev)
{
	struct sumo_power_info *pi = rdev->pm.dpm.priv;

	return pi;
}

/* sumo_dpm.c:317-334 */
static void sumo_calculate_bsp(struct radeon_device *rdev,
			       u32 high_clk)
{
	struct sumo_power_info *pi = sumo_get_pi(rdev);
	u32 xclk = radeon_get_xclk(rdev);

	pi->pasi = 65535 * 100 / high_clk;
	pi->asi = 65535 * 100 / high_clk;

	r600_calculate_u_and_p(pi->asi,
			       xclk, 16, &pi->bsp, &pi->bsu);

	r600_calculate_u_and_p(pi->pasi,
			       xclk, 16, &pi->pbsp, &pi->pbsu);

	pi->dsp = (pi->bsp) | (pi->bsu);
	pi->psp = (pi->pbsp) | (pi->pbsu);
}

/* sumo_dpm.c:336-341 */
static void sumo_init_bsp(struct radeon_device *rdev)
{
	struct sumo_power_info *pi = sumo_get_pi(rdev);

	WREG32(CG_BSP_0, pi->psp);
}

/* sumo_dpm.c:344-364 */
static void sumo_program_bsp(struct radeon_device *rdev,
			     struct radeon_ps *rps)
{
	struct sumo_power_info *pi = sumo_get_pi(rdev);
	struct sumo_ps *ps = sumo_get_ps(rps);
	u32 i;
	u32 highest_engine_clock = ps->levels[ps->num_levels - 1].sclk; /* :350 */

	if (ps->flags & SUMO_POWERSTATE_FLAGS_BOOST_STATE)
		highest_engine_clock = pi->boost_pl.sclk;

	sumo_calculate_bsp(rdev, highest_engine_clock);                 /* :355 */

	for (i = 0; i < ps->num_levels - 1; i++)
		WREG32(CG_BSP_0 + (i * 4), pi->dsp);

	WREG32(CG_BSP_0 + (i * 4), pi->psp);

	if (ps->flags & SUMO_POWERSTATE_FLAGS_BOOST_STATE)
		WREG32(CG_BSP_0 + (7 * 4), pi->psp);
}

/* sumo_dpm.c:433-453 (register-only) */
static void sumo_program_tp(struct radeon_device *rdev)
{
	int i;

	for (i = 0; i < 15; i++) {
		WREG32_P(CG_FFCT_0 + (i * 4), UTC_0(sumo_utc[i]), ~UTC_0_MASK);
		WREG32_P(CG_FFCT_0 + (i * 4), DTC_0(sumo_dtc[i]), ~DTC_0_MASK);
	}
}

/* sumo_dpm.c:918-921 */
static void sumo_reset_am(struct radeon_device *rdev)
{
	WREG32_P(SCLK_PWRMGT_CNTL, FIR_RESET, ~FIR_RESET);
}

/* sumo_dpm.c:912-916 */
static void sumo_program_bootup_at(struct radeon_device *rdev)
{
	WREG32_P(CG_AT_0, CG_R(0xffff), ~CG_R_MASK);
	WREG32_P(CG_AT_0, CG_L(0), ~CG_L_MASK);
}

/* sumo_dpm.c:923-926 */
static void sumo_start_am(struct radeon_device *rdev)
{
	WREG32_P(SCLK_PWRMGT_CNTL, 0, ~FIR_RESET);
}

/* sumo_dpm.c:602-608 */
static bool sumo_dpm_enabled(struct radeon_device *rdev)
{
	if (RREG32(CG_SCLK_DPM_CTRL_3) & DPM_SCLK_ENABLE)
		return true;
	else
		return false;
}

/* sumo_dpm.c:610-613 */
static void sumo_start_dpm(struct radeon_device *rdev)
{
	WREG32_P(CG_SCLK_DPM_CTRL_3, DPM_SCLK_ENABLE, ~DPM_SCLK_ENABLE);
}

/* sumo_dpm.c:640-654 */
static void sumo_wait_for_level_0(struct radeon_device *rdev)
{
	int i;

	for (i = 0; i < 1000; i++) {
		if ((RREG32(TARGET_AND_CURRENT_PROFILE_INDEX) & CURR_SCLK_INDEX_MASK) == 0)
			break;
	}
	for (i = 0; i < 1000; i++) {
		if ((RREG32(TARGET_AND_CURRENT_PROFILE_INDEX) & CURR_INDEX_MASK) == 0)
			break;
	}
}

/* sumo_dpm.c:817-834 (register programming replaced by an observable store
 * so the OOB loads feeding it cannot be dead-code-eliminated) */
static u32 uvd_stub_sink;
static void sumo_setup_uvd_clocks(struct radeon_device *rdev,
				  struct radeon_ps *new_rps,
				  struct radeon_ps *old_rps)
{
	uvd_stub_sink += new_rps->vclk + old_rps->dclk + (uintptr_t)rdev;
}

/* sumo_dpm.c:836-852 */
static void sumo_set_uvd_clock_before_set_eng_clock(struct radeon_device *rdev,
						    struct radeon_ps *new_rps,
						    struct radeon_ps *old_rps)
{
	struct sumo_ps *new_ps = sumo_get_ps(new_rps);
	struct sumo_ps *current_ps = sumo_get_ps(old_rps);

	if ((new_rps->vclk == old_rps->vclk) &&
	    (new_rps->dclk == old_rps->dclk))
		return;

	if (new_ps->levels[new_ps->num_levels - 1].sclk >=          /* :847 OOB */
	    current_ps->levels[current_ps->num_levels - 1].sclk)    /* :848 OOB */
		return;

	sumo_setup_uvd_clocks(rdev, new_rps, old_rps);
}

/* sumo_dpm.c:854-870 */
static void sumo_set_uvd_clock_after_set_eng_clock(struct radeon_device *rdev,
						   struct radeon_ps *new_rps,
						   struct radeon_ps *old_rps)
{
	struct sumo_ps *new_ps = sumo_get_ps(new_rps);
	struct sumo_ps *current_ps = sumo_get_ps(old_rps);

	if ((new_rps->vclk == old_rps->vclk) &&
	    (new_rps->dclk == old_rps->dclk))
		return;

	if (new_ps->levels[new_ps->num_levels - 1].sclk <           /* :865 OOB */
	    current_ps->levels[current_ps->num_levels - 1].sclk)    /* :866 OOB */
		return;

	sumo_setup_uvd_clocks(rdev, new_rps, old_rps);
}

/* sumo_dpm.c:1183-1192 */
static void sumo_update_current_ps(struct radeon_device *rdev,
				   struct radeon_ps *rps)
{
	struct sumo_ps *new_ps = sumo_get_ps(rps);      /* :1186 rps==NULL -> fault */
	struct sumo_power_info *pi = sumo_get_pi(rdev);

	pi->current_rps = *rps;                         /* :1189 */
	pi->current_ps = *new_ps;
	pi->current_rps.ps_priv = &pi->current_ps;
}

/* sumo_dpm.c:1194-1203 */
static void sumo_update_requested_ps(struct radeon_device *rdev,
				     struct radeon_ps *rps)
{
	struct sumo_ps *new_ps = sumo_get_ps(rps);
	struct sumo_power_info *pi = sumo_get_pi(rdev);

	pi->requested_rps = *rps;
	pi->requested_ps = *new_ps;
	pi->requested_rps.ps_priv = &pi->requested_ps;
}

/* sumo_dpm.c:1205-1238 (register-only sub-programming stubbed, call
 * structure preserved; see header comment) */
static int sumo_dpm_enable(struct radeon_device *rdev)
{
	struct sumo_power_info *pi = sumo_get_pi(rdev);

#ifdef FIX_DF4187
	/* DF-4187 fix: refuse to enable DPM without a boot power state */
	if (rdev->pm.dpm.boot_ps == NULL) {
		DRM_ERROR("no boot power state, refusing to enable DPM");
		return -1;
	}
#endif
	if (sumo_dpm_enabled(rdev))
		return -22;

	sumo_program_tp(rdev);
	sumo_init_bsp(rdev);
	sumo_reset_am(rdev);
	sumo_program_bootup_at(rdev);
	sumo_start_am(rdev);
	if (pi->enable_auto_thermal_throttling) {
		/* sumo_program_ttp/ttt: register-only, stubbed */
	}
	/* dc_hto / power_level_enter_state / voltage_scaling / sstp / vc /
	 * cnb_override / sclk_ds / boost_timer: register-only, stubbed */
	sumo_start_dpm(rdev);
	sumo_wait_for_level_0(rdev);

	sumo_update_current_ps(rdev, rdev->pm.dpm.boot_ps);   /* :1235 the deref */

	return 0;
}

/* sumo_dpm.c:1260-1281 */
static void sumo_dpm_disable(struct radeon_device *rdev)
{
	if (!sumo_dpm_enabled(rdev))
		return;
	/* register-only teardown stubbed */
	sumo_update_current_ps(rdev, rdev->pm.dpm.boot_ps);    /* :1280 */
}

/* sumo_dpm.c:1299-1333 (forced-mode plumbing stubbed) */
static int sumo_dpm_set_power_state(struct radeon_device *rdev)
{
	struct sumo_power_info *pi = sumo_get_pi(rdev);
	struct radeon_ps *new_ps = &pi->requested_rps;
	struct radeon_ps *old_ps = &pi->current_rps;

	if (pi->enable_dpm)
		sumo_set_uvd_clock_before_set_eng_clock(rdev, new_ps, old_ps);
	if (pi->enable_dpm)
		sumo_program_bsp(rdev, new_ps);               /* :1319 */
	if (pi->enable_dpm)
		sumo_set_uvd_clock_after_set_eng_clock(rdev, new_ps, old_ps);

	return 0;
}

/* sumo_dpm.c:1395-1403 */
static void sumo_patch_boot_state(struct radeon_device *rdev,
				  struct sumo_ps *ps)
{
	struct sumo_power_info *pi = sumo_get_pi(rdev);

	ps->num_levels = 1;
	ps->flags = 0;
	ps->levels[0] = pi->boot_pl;
}

/* sumo_dpm.c:1405-1430 */
static void sumo_parse_pplib_non_clock_info(struct radeon_device *rdev,
					    struct radeon_ps *rps,
					    ATOM_PPLIB_NONCLOCK_INFO *non_clock_info,
					    u8 table_rev)
{
	struct sumo_ps *ps = sumo_get_ps(rps);

	rps->caps = le32_to_cpu(non_clock_info->ulCapsAndSettings);   /* :1412 */
	rps->class = le16_to_cpu(non_clock_info->usClassification);   /* :1413 */
	rps->class2 = le16_to_cpu(non_clock_info->usClassification2); /* :1414 */

	if (ATOM_PPLIB_NONCLOCKINFO_VER1 < table_rev) {
		rps->vclk = le32_to_cpu(non_clock_info->ulVCLK);
		rps->dclk = le32_to_cpu(non_clock_info->ulDCLK);
	} else {
		rps->vclk = 0;
		rps->dclk = 0;
	}

	if (rps->class & ATOM_PPLIB_CLASSIFICATION_BOOT) {
		rdev->pm.dpm.boot_ps = rps;                            /* :1425 */
		sumo_patch_boot_state(rdev, ps);
	}
	if (rps->class & ATOM_PPLIB_CLASSIFICATION_UVDSTATE)
		rdev->pm.dpm.uvd_ps = rps;
}

/* sumo_dpm.c:1432-1453 */
static void sumo_parse_pplib_clock_info(struct radeon_device *rdev,
					struct radeon_ps *rps, int index,
					union pplib_clock_info *clock_info)
{
	struct sumo_power_info *pi = sumo_get_pi(rdev);
	struct sumo_ps *ps = sumo_get_ps(rps);
	struct sumo_pl *pl = &ps->levels[index];
	u32 sclk;

	sclk = le16_to_cpu(clock_info->sumo.usEngineClockLow);
	sclk |= clock_info->sumo.ucEngineClockHigh << 16;
	pl->sclk = sclk;
	pl->vddc_index = clock_info->sumo.vddcIndex;
	pl->sclk_dpm_tdp_limit = clock_info->sumo.tdpLimit;

	ps->num_levels = index + 1;                                 /* :1447 */

	if (pi->enable_sclk_ds) {
		pl->ds_divider_index = 5;
		pl->ss_divider_index = 4;
	}
}

/* sumo_dpm.c:1455-1528 (atom_parse_data_header satisfied by caller) */
static int sumo_parse_power_table(struct radeon_device *rdev)
{
	struct radeon_mode_info *mode_info = &rdev->mode_info;
	ATOM_PPLIB_NONCLOCK_INFO *non_clock_info;
	union pplib_power_state *power_state;
	int i, j, k, non_clock_array_index, clock_array_index;
	union pplib_clock_info *clock_info;
	StateArray *state_array;
	ClockInfoArray *clock_info_array;
	NonClockInfoArray *non_clock_info_array;
	union power_info *power_info;
	u16 data_offset = rdev->_atom_data_offset;
	u8 *power_state_offset;
	struct sumo_ps *ps;

	power_info = (union power_info *)(mode_info->atom_context->bios + data_offset);

	state_array = (StateArray *)
		(mode_info->atom_context->bios + data_offset +
		 le16_to_cpu(power_info->pplib.usStateArrayOffset));
	clock_info_array = (ClockInfoArray *)
		(mode_info->atom_context->bios + data_offset +
		 le16_to_cpu(power_info->pplib.usClockInfoArrayOffset));
	non_clock_info_array = (NonClockInfoArray *)
		(mode_info->atom_context->bios + data_offset +
		 le16_to_cpu(power_info->pplib.usNonClockInfoArrayOffset));

	rdev->pm.dpm.ps = kzalloc(sizeof(struct radeon_ps) *
				  state_array->ucNumEntries, 0);
	if (!rdev->pm.dpm.ps)
		return -ENOMEM;
	power_state_offset = (u8 *)state_array->states;
	for (i = 0; i < state_array->ucNumEntries; i++) {
		u8 *idx;
		power_state = (union pplib_power_state *)power_state_offset;
		non_clock_array_index = power_state->v2.nonClockInfoIndex;   /* :1495 */
#ifdef FIX_DF4190
		if (non_clock_array_index >= non_clock_info_array->ucNumEntries) {
			DRM_ERROR("invalid nonClockInfoIndex %d", non_clock_array_index);
			kfree(rdev->pm.dpm.ps);
			rdev->pm.dpm.ps = NULL;
			return -22;
		}
#endif
		non_clock_info = (ATOM_PPLIB_NONCLOCK_INFO *)
			&non_clock_info_array->nonClockInfo[non_clock_array_index]; /* :1496-1497 */
		ps = kzalloc(sizeof(struct sumo_ps), 0);                     /* :1500 */
		if (ps == NULL) {
#ifdef FIX_DF4188
			{ int x; for (x = i - 1; x >= 0; x--)
				kfree(rdev->pm.dpm.ps[x].ps_priv); }
			kfree(rdev->pm.dpm.ps);
			rdev->pm.dpm.ps = NULL;                          /* no dangling */
			return -ENOMEM;
#else
			kfree(rdev->pm.dpm.ps);                           /* :1502 dangling! */
			return -ENOMEM;
#endif
		}
		rdev->pm.dpm.ps[i].ps_priv = ps;
		k = 0;
		idx = (u8 *)&power_state->v2.clockInfoIndex[0];
		for (j = 0; j < power_state->v2.ucNumDPMLevels; j++) {       /* :1508 */
			clock_array_index = idx[j];
			if (k >= SUMO_MAX_HARDWARE_POWERLEVELS)
				break;

			clock_info = (union pplib_clock_info *)
				((u8 *)&clock_info_array->clockInfo[0] +
				 (clock_array_index * clock_info_array->ucEntrySize));
			sumo_parse_pplib_clock_info(rdev,
						    &rdev->pm.dpm.ps[i], k,
						    clock_info);
			k++;
		}
#ifdef FIX_DF4189
		if (k == 0) {
			/* DF-4189 fix: a pplib state with zero DPM levels would
			 * leave num_levels==0 -> levels[-1] everywhere */
			DRM_ERROR("power state %d has no DPM levels", i);
			kfree(ps);
			rdev->pm.dpm.ps[i].ps_priv = NULL;
			kfree(rdev->pm.dpm.ps);
			rdev->pm.dpm.ps = NULL;
			return -22;
		}
#endif
		sumo_parse_pplib_non_clock_info(rdev, &rdev->pm.dpm.ps[i],
						non_clock_info,
						non_clock_info_array->ucEntrySize);
		power_state_offset += 2 + power_state->v2.ucNumDPMLevels;    /* :1524 */
	}
	rdev->pm.dpm.num_ps = state_array->ucNumEntries;                 /* :1526 */
	return 0;
}

/* sumo_dpm.c:1870-1881 */
static void sumo_dpm_fini(struct radeon_device *rdev)
{
	int i;

	for (i = 0; i < rdev->pm.dpm.num_ps; i++) {
		kfree(rdev->pm.dpm.ps[i].ps_priv);                       /* :1877 */
	}
	kfree(rdev->pm.dpm.ps);                                          /* :1879 */
	kfree(rdev->pm.dpm.priv);
}

/* sumo_dpm.c:1883-1892 */
static u32 sumo_dpm_get_sclk(struct radeon_device *rdev, bool low)
{
	struct sumo_power_info *pi = sumo_get_pi(rdev);
	struct sumo_ps *requested_state = sumo_get_ps(&pi->requested_rps);

	if (low)
		return requested_state->levels[0].sclk;
	else
		return requested_state->levels[requested_state->num_levels - 1].sclk; /* :1891 */
}

/* ================================================================== */
/* VBIOS image crafting                                               */
/* ================================================================== */
#define BIOS_SZ   16384
#define DATA_OFF  64		/* atom data_offset of the PowerPlayInfo table */

#define OFF_STATES    16
#define OFF_CLOCKS    2048
#define OFF_NONCLOCK  (BIOS_SZ - DATA_OFF - 512)   /* near the image end */

static struct radeon_device RDEV;
static struct sumo_power_info *PI;

static void bios_init(uint8_t *bios)
{
	PPLIB_MIN *p;

	memset(bios, 0, BIOS_SZ);
	RDEV.mode_info.atom_context->bios = bios;
	RDEV._atom_data_offset = DATA_OFF;
	p = (PPLIB_MIN *)(bios + DATA_OFF);
	p->usStateArrayOffset = OFF_STATES;
	p->usClockInfoArrayOffset = OFF_CLOCKS;
	p->usNonClockInfoArrayOffset = OFF_NONCLOCK;
}

static void bios_add_state(uint8_t *bios, int n, int num_dpm_levels,
			   int nonclock_index)
{
	StateArray *sa = (StateArray *)(bios + DATA_OFF + OFF_STATES);
	uint8_t *soff = (uint8_t *)sa->states;
	ATOM_PPLIB_STATE_V2 *st;
	int i;

	sa->ucNumEntries = n + 1;
	for (i = 0; i < n; i++)
		soff += 2 + soff[0];         /* skip previous states */
	st = (ATOM_PPLIB_STATE_V2 *)soff;
	st->ucNumDPMLevels = num_dpm_levels;
	st->nonClockInfoIndex = nonclock_index;
	for (i = 0; i < num_dpm_levels && i < 8; i++)
		st->clockInfoIndex[i] = i;
}

static void bios_add_clock(uint8_t *bios, int n, u32 sclk10k, u8 vddc)
{
	ClockInfoArray *ca = (ClockInfoArray *)(bios + DATA_OFF + OFF_CLOCKS);
	ATOM_PPLIB_SUMO_CLOCK_INFO *ci;

	if (n == 0)
		ca->ucEntrySize = sizeof(ATOM_PPLIB_SUMO_CLOCK_INFO);
	ca->ucNumEntries = n + 1;
	ci = (ATOM_PPLIB_SUMO_CLOCK_INFO *)((uint8_t *)&ca->clockInfo[0] +
	    n * ca->ucEntrySize);
	ci->usEngineClockLow = sclk10k & 0xffff;
	ci->ucEngineClockHigh = sclk10k >> 16;
	ci->vddcIndex = vddc;
	ci->tdpLimit = 0;
}

static void bios_add_nonclock(uint8_t *bios, int n, u16 class, u32 vclk)
{
	NonClockInfoArray *na = (NonClockInfoArray *)(bios + DATA_OFF + OFF_NONCLOCK);
	ATOM_PPLIB_NONCLOCK_INFO *ni;

	if (n == 0)
		na->ucEntrySize = sizeof(ATOM_PPLIB_NONCLOCK_INFO);
	na->ucNumEntries = n + 1;
	ni = (ATOM_PPLIB_NONCLOCK_INFO *)((uint8_t *)&na->nonClockInfo[0] +
	    n * na->ucEntrySize);
	memset(ni, 0, sizeof(*ni));
	ni->usClassification = class;
	ni->ulVCLK = vclk;
	ni->ulDCLK = vclk;
}

static void common_setup(void)
{
	memset(&RDEV, 0, sizeof(RDEV));
	RDEV.mode_info.atom_context = kzalloc(sizeof(struct atom_context), 0);
	PI = kzalloc(sizeof(struct sumo_power_info), 0);
	RDEV.pm.dpm.priv = PI;
	PI->enable_sclk_ds = true;
	PI->enable_dynamic_patch_ps = true;
	PI->enable_dpm = true;
	PI->enable_auto_thermal_throttling = true;
	PI->sys_info.bootup_sclk = 8000;	/* 80 MHz in 10 kHz units */
	PI->sys_info.min_sclk = 800;
	PI->boot_pl.sclk = PI->sys_info.bootup_sclk;
	PI->boot_pl.vddc_index = 1;
}

/* ================================================================== */
/* scenarios                                                          */
/* ================================================================== */
static void mode_4187(void)
{
	uint8_t *bios;
	int ret;

	printf("== DF-4187: sumo_dpm_enable NULL boot_ps ==\n");
	printf("offsetof(struct radeon_ps, ps_priv) = %zu (kernel layout: 40)\n",
	    offsetof(struct radeon_ps, ps_priv));
	common_setup();
	bios = kzalloc(BIOS_SZ, 0);
	bios_init(bios);
	/* state 0: 2 DPM levels, classified PERFORMANCE (no BOOT bit) */
	bios_add_clock(bios, 0, 30000, 1);
	bios_add_clock(bios, 1, 50000, 2);
	bios_add_nonclock(bios, 0, 0x0002, 0);
	bios_add_state(bios, 0, 2, 0);

	ret = sumo_parse_power_table(&RDEV);
	printf("sumo_parse_power_table() = %d num_ps=%d boot_ps=%p\n",
	    ret, RDEV.pm.dpm.num_ps, (void *)RDEV.pm.dpm.boot_ps);
	if (RDEV.pm.dpm.boot_ps != NULL) {
		printf("PRECONDITION FAILED (boot_ps set) - bad scenario\n");
		exit(2);
	}
	/* radeon_pm.c:1448 swallows sumo_dpm_init() errors; :1453 enables */
	printf("calling sumo_dpm_enable() with boot_ps == NULL ...\n");
	fflush(stdout);
	ret = sumo_dpm_enable(&RDEV);            /* :1235 derefs NULL->ps_priv */
	printf("sumo_dpm_enable() returned %d - NO CRASH%s\n", ret,
#ifdef FIX_DF4187
	    " (fix active)"
#else
	    " - UNEXPECTED (expected SIGSEGV at 0x28)"
#endif
	    );
}

static void mode_4188(void)
{
	uint8_t *bios;
	int ret;

	printf("== DF-4188: dangling dpm.ps double free in sumo_dpm_fini ==\n");
	common_setup();
	bios = kzalloc(BIOS_SZ, 0);
	bios_init(bios);
	bios_add_clock(bios, 0, 30000, 1);
	bios_add_clock(bios, 1, 50000, 2);
	bios_add_nonclock(bios, 0, 0x0001 /* BOOT */, 0);
	bios_add_nonclock(bios, 1, 0x0002, 0);
	bios_add_state(bios, 0, 1, 0);
	bios_add_state(bios, 1, 1, 1);

	/* fail the kzalloc(struct sumo_ps) of the SECOND state:
	 * kzalloc order: atom_ctx(1) pi(2) bios(3) dpm.ps(4) ps#0(5) ps#1(6) */
	kmalloc_fail_at = 6;
	kmalloc_tag = "sumo_ps#1";
	ret = sumo_parse_power_table(&RDEV);
	printf("sumo_parse_power_table() = %d (ENOMEM injected at state 1)\n", ret);
	printf("rdev->pm.dpm.ps is %sNULL after the failed parse\n",
	    RDEV.pm.dpm.ps == NULL ? "" : "NOT (dangling pointer kept)");
	fflush(stdout);
	/* radeon_pm.c:1448 discards the error; the driver loads anyway */
	printf("driver unload -> radeon_pm_fini -> sumo_dpm_fini ...\n");
	fflush(stdout);
	sumo_dpm_fini(&RDEV);                    /* :1879 kfree(dangling) */
	printf("sumo_dpm_fini() returned; canary_violations so far = %d%s\n",
	    canary_violations,
#ifdef FIX_DF4188
	    " (fix active: pointer NULLed after cleanup, no double free)"
#else
	    canary_violations ? " (DOUBLE FREE detected above)" :
	    " - UNEXPECTED (expected double-free detection)"
#endif
	    );
}

static void mode_4189(int variant)
{
	uint8_t *bios;
	int ret;
	struct sumo_ps *ps0;
	unsigned long long wild;

	printf("== DF-4189: ucNumDPMLevels==0 -> num_levels==0 -> "
	    "levels[0xFFFFFFFF] wild read ==\n");
	printf("variant note: num_levels is u32; num_levels-1 == 0xFFFFFFFF, "
	    "so the index is levels[0xFFFFFFFF] = object + 0xFFFFFFFF*24 "
	    "(~96 GB past the kzalloc'd sumo_ps)\n");
	poison_byte = variant ? 0x00 : POISON;	/* irrelevant: the wild read faults */
	common_setup();
	bios = kzalloc(BIOS_SZ, 0);
	bios_init(bios);
	/* state 0: ZERO DPM levels, non-BOOT, vclk != current (force UVD path) */
	bios_add_nonclock(bios, 0, 0x0002, 6789);
	bios_add_state(bios, 0, 0, 0);
	/* state 1: BOOT state with 1 level (parse succeeds, boot_ps set) */
	bios_add_clock(bios, 0, 40000, 1);
	bios_add_nonclock(bios, 1, 0x0001, 0);
	bios_add_state(bios, 1, 1, 1);

	ret = sumo_parse_power_table(&RDEV);
	printf("sumo_parse_power_table() = %d num_ps=%d\n", ret, RDEV.pm.dpm.num_ps);
	if (ret == 0) {
		ps0 = RDEV.pm.dpm.ps[0].ps_priv;
		wild = (unsigned long long)(uintptr_t)ps0 +
		    0xFFFFFFFFULL * sizeof(struct sumo_pl);
		printf("state0 ps_priv=%p levels=%p num_levels=%u (kzalloc'd, "
		    "all zero)\n", (void *)ps0, (void *)ps0->levels, ps0->num_levels);
		printf("expected fault address for levels[num_levels-1].sclk "
		    "~= 0x%llx (object + ~%llu GB)\n", wild,
		    0xFFFFFFFFULL * sizeof(struct sumo_pl) >> 30);
		/* radeon_dpm_pick_power_state selects state 0; pre_set copies */
		sumo_update_requested_ps(&RDEV, &RDEV.pm.dpm.ps[0]);
		sumo_update_current_ps(&RDEV, RDEV.pm.dpm.boot_ps);
		printf("calling sumo_set_uvd_clock_before_set_eng_clock "
		    "(sumo_dpm.c:847 levels[num_levels-1]) ...\n");
		fflush(stdout);
		sumo_set_uvd_clock_before_set_eng_clock(&RDEV,
		    &PI->requested_rps, &PI->current_rps);
		printf("survived uvd_before; calling sumo_dpm_set_power_state "
		    "(program_bsp :350, get_sclk :1891 same index) ...\n");
		fflush(stdout);
		sumo_dpm_set_power_state(&RDEV);
		printf("returned - no crash%s\n",
#ifdef FIX_DF4189
		    " (fix active)"
#else
		    " - UNEXPECTED (expected SIGSEGV ~96 GB past the object)"
#endif
		    );
	} else {
		printf("sumo_parse_power_table() rejected the crafted table%s\n",
#ifdef FIX_DF4189
		    " (fix active: zero-level state rejected)"
#else
		    " - UNEXPECTED"
#endif
		    );
	}
}

static void mode_4190(void)
{
	uint8_t *bios;
	int ret;
	NonClockInfoArray *na;
	ATOM_PPLIB_NONCLOCK_INFO *oob;

	printf("== DF-4190: unchecked nonClockInfoIndex -> OOB read ==\n");
	common_setup();
	bios = kzalloc(BIOS_SZ, 0);
	bios_init(bios);
	bios_add_clock(bios, 0, 40000, 1);
	bios_add_nonclock(bios, 0, 0x0001 /* BOOT */, 0);
	/* crafted state: nonClockInfoIndex = 250 (u8, never bounds-checked) */
	bios_add_state(bios, 0, 1, 250);

	na = (NonClockInfoArray *)(bios + DATA_OFF + OFF_NONCLOCK);
	oob = &na->nonClockInfo[250];
#ifndef FIX_DF4190
	check_in_object("nonClockInfo[250] (bios-relative OOB read)", oob,
	    sizeof(*oob));
	printf("bios image: %d bytes at %p; nonClockInfo[250] lands at bios+%td "
	    "(%td bytes PAST the end of the image)\n",
	    BIOS_SZ, (void *)bios, (char *)oob - (char *)bios,
	    (char *)oob - ((char *)bios + BIOS_SZ));
	fflush(stdout);
#endif
	ret = sumo_parse_power_table(&RDEV);
	printf("sumo_parse_power_table() = %d\n", ret);
#ifdef FIX_DF4190
	if (ret != 0)
		printf("crafted table rejected (fix active)\n");
#else
	if (ret == 0) {
		printf("rps[0].class = 0x%04x %s\n", RDEV.pm.dpm.ps[0].class,
		    RDEV.pm.dpm.ps[0].class == 0xA5A5 ?
		    "(== canary poison: OOB READ CONFIRMED)" : "");
		printf("rps[0].caps  = 0x%08x %s\n", RDEV.pm.dpm.ps[0].caps,
		    RDEV.pm.dpm.ps[0].caps == 0xA5A5A5A5 ?
		    "(== canary poison)" : "");
		printf("rps[0].vclk  = 0x%08x %s\n", RDEV.pm.dpm.ps[0].vclk,
		    RDEV.pm.dpm.ps[0].vclk == 0xA5A5A5A5 ?
		    "(== canary poison)" : "");
	}
#endif
}

static void sig_handler(int sig, siginfo_t *si, void *uc)
{
	(void)uc;
	fflush(stdout);
	printf("\n*** SIGNAL %d at address %p: %s ***\n", sig, si->si_addr,
	    sig == SIGSEGV ? "SIGSEGV - wild/NULL pointer dereference "
		"(kernel: page-fault PANIC)" :
	    sig == SIGFPE  ? "SIGFPE - divide by zero "
		"(kernel: div0 PANIC in sumo_calculate_bsp, sumo_dpm.c:323-324)" :
	    "abort");
	fflush(stdout);
	_exit(99);
}

int main(int argc, char **argv)
{
	struct sigaction sa;
	const char *mode = argc > 1 ? argv[1] : "4187";

	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = sig_handler;
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL);
	sigaction(SIGFPE, &sa, NULL);
	sigaction(SIGABRT, &sa, NULL);

	if (!strcmp(mode, "4187"))		mode_4187();
	else if (!strcmp(mode, "4188"))	mode_4188();
	else if (!strcmp(mode, "4189"))	mode_4189(0);
	else if (!strcmp(mode, "4189z"))	mode_4189(1);
	else if (!strcmp(mode, "4190"))	mode_4190();
	else { printf("unknown mode %s\n", mode); return 2; }

	printf("canary_violations = %d\n", canary_violations);
	return canary_violations ? 98 : 0;
}

/* keep sumo_dpm_disable referenced (part of the DF-4187 family analysis) */
static void (*const sumo_disable_ref)(struct radeon_device *) = sumo_dpm_disable;
__attribute__((unused)) static void (*const sumo_keep)(struct radeon_device *) = sumo_disable_ref;
