/*
 * DF-4492 / DF-4493 / DF-4494 — rs780_dpm.c pass-2 verification harness
 * =============================================================================
 * Userspace replica of the vulnerable code paths in
 *   sys/dev/drm/radeon/rs780_dpm.c  (DragonFly 6.5-DEVELOPMENT, GLM-5.3 pass 2)
 *
 * The kernel functions below are copied VERBATIM from rs780_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
 *   - udelay()/mdelay()/radeon_wait_for_vblank() -> no-ops
 *   - r600_* register helpers      -> no-op macros (pure MMIO in the kernel)
 *   - radeon_atom_get_clock_dividers() -> fills a zeroed divider struct, ret 0
 *   - kzalloc()/kfree()            -> canary allocator with 64-byte poison
 *     redzones on BOTH sides, double-free/invalid-free detection and a
 *     0xB0 free-fill so use-after-free reads/writes are observable
 *   - atom_parse_data_header()     -> satisfied by the caller (data_offset,
 *                                     data_size from the crafted image)
 *   - ATOM_PPLIB_POWERPLAYTABLE reduced (packed) to the fields rs780 reads;
 *     the VBIOS image is crafted with the same definition.
 *
 * Modes:
 *   4492    unchecked VBIOS indices/strides in rs780_parse_power_table:
 *           nonClockStateIndex*ucNonClockSize (:820-821),
 *           clockStateIndices[0]*ucClockInfoSize (:826-827) and the
 *           usStateArrayOffset + i*ucStateEntrySize walk (:812-816) all read
 *           past the BIOS image -> kernel heap OOB reads (canary reports the
 *           exact landings; values land in rps->class/caps/vclk/sclk_low...)
 *   4492g   far-OOB variant on a guard-page-backed BIOS image ->
 *           deterministic SIGSEGV at the predicted address (kernel:
 *           page-fault panic)
 *   4493    PowerPlayInfo with no BOOT-classified state -> boot_ps stays NULL
 *           -> radeon_pm_init_dpm still calls rs780_dpm_enable
 *           (return of rs780_dpm_init is discarded, radeon_pm.c:1448) ->
 *           rs780_get_ps(NULL) reads NULL->ps_priv -> SIGSEGV at 0x28
 *   4493b   ucStateEntrySize == 1 makes the `if (ucStateEntrySize - 1)` guard
 *           (:822) skip EVERY state -> all ps_priv stay NULL, boot_ps NULL,
 *           num_ps still ucNumStates (:842) -> radeon_dpm_print_power_states
 *           -> rs780_dpm_print_power_state derefs ps->sclk_low (:946) ->
 *           SIGSEGV at 0x8
 *   4494    kzalloc(struct igp_ps) failure at state #1 -> parse kfrees
 *           rdev->pm.dpm.ps and leaves it dangling (:830) -> (error swallowed
 *           per radeon_pm.c:1448) -> driver unload -> radeon_pm_fini
 *           (radeon_pm.c:1705) -> rs780_dpm_fini kfrees it AGAIN -> DOUBLE
 *           FREE (:959)
 *   4494u   same dangling state, but rs780_dpm_enable dereferences
 *           boot_ps == &ps[0] into the FREED array -> UAF read, then the
 *           loaded 0xB0B0.. ps_priv is dereferenced -> SIGSEGV
 *   4494w   successful parse + missing IntegratedSystemInfo header ->
 *           rs780_dpm_init calls radeon_dpm_fini(rdev) itself (:933) and
 *           returns -EINVAL which radeon_pm.c:1448 DISCARDS ->
 *           rs780_dpm_enable writes pi->crtc_id into the FREED
 *           igp_power_info (UAF WRITE), then faults
 *
 * 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/double-free/UAF/OOB detection.)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>

#define ENOMEM 12
#define EINVAL 22
#define GFP_KERNEL 0

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__)
#define udelay(x)	do { } while (0)
#define mdelay(x)	do { } while (0)

/* ------------------------------------------------------------------ */
/* canary allocator: 64-byte poison redzones + 0xB0 free-fill          */
/* ------------------------------------------------------------------ */
#define POISON      0xA5
#define FREE_POISON 0xB0
#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 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("[kzalloc #%d (%zu bytes) -> INJECTED FAILURE (ENOMEM)]\n",
		    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 struct alloc_rec *find_alloc(const void *p)
{
	int i;
	for (i = 0; i < arec_n; i++)
		if (arec[i].obj == p)
			return &arec[i];
	return NULL;
}

static void kfree(void *p)
{
	struct alloc_rec *r;

	if (p == NULL)
		return;
	r = find_alloc(p);
	if (!r) {
		printf("*** CANARY ALLOCATOR: kfree(%p) of a pointer that is "
		    "NOT a live allocation base (INVALID FREE) ***\n", p);
		canary_violations++;
		return;
	}
	if (!r->live) {
		printf("*** CANARY ALLOCATOR: DOUBLE FREE of %p (size %zu) "
		    "DETECTED ***\n", p, r->sz);
		canary_violations++;
		return;
	}
	r->live = 0;
	memset(p, FREE_POISON, r->sz);   /* UAF reads/writes observable */
}

/* 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 (arec[i].live && (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;
}

/* is p inside a FREED (not live) allocation? -> UAF */
static int check_is_freed(const char *what, const void *p)
{
	int i;
	for (i = 0; i < arec_n; i++)
		if (arec[i].obj == (void *)p && !arec[i].live) {
			printf("*** CANARY: %s == %p points into a FREED "
			    "allocation (size %zu) -> USE AFTER FREE ***\n",
			    what, p, arec[i].sz);
			canary_violations++;
			return 1;
		}
	return 0;
}

/* did a freed object change from the 0xB0 free-fill? -> UAF WRITE */
static int check_freed_written(const char *what, const void *p)
{
	int i;
	const unsigned char *b = p;
	for (i = 0; i < arec_n; i++)
		if (arec[i].obj == p && !arec[i].live) {
			size_t j, hits = 0;
			for (j = 0; j < arec[i].sz; j++)
				if (b[j] != FREE_POISON)
					hits++;
			if (hits) {
				printf("*** CANARY: freed object %s (%p, %zu "
				    "bytes) has %zu bytes != 0x%02x -> object "
				    "WRITTEN AFTER FREE (UAF WRITE) ***\n",
				    what, p, arec[i].sz, hits, FREE_POISON);
				canary_violations++;
				return 1;
			}
			return 0;
		}
	return 0;
}

/* ------------------------------------------------------------------ */
/* minimal struct replicas (field ORDER faithful to the kernel)        */
/* ------------------------------------------------------------------ */
struct igp_ps {                       /* rs780_dpm.h:52-58 */
	u32 min_voltage;             /* enum, 4 bytes */
	u32 max_voltage;
	u32 sclk_low;
	u32 sclk_high;
	u32 flags;
};

enum rs780_vddc_level_x { RS780_VDDC_LEVEL_UNKNOWN = 0, RS780_VDDC_LEVEL_LOW = 1,
			  RS780_VDDC_LEVEL_HIGH = 2 };

struct igp_power_info {               /* rs780_dpm.h:32-50 */
	bool invert_pwm_required;
	bool pwm_voltage_control;
	bool voltage_control;
	bool gfx_clock_gating;
	u32 system_config;
	u32 bootup_uma_clk;
	u16 max_voltage;
	u16 min_voltage;
	u16 boot_voltage;
	u16 inter_voltage_low;
	u16 inter_voltage_high;
	u16 num_of_cycles_in_period;
	int crtc_id;
	int refresh_rate;
};

enum radeon_vce_level_x { RADEON_VCE_LEVEL_NONE = 0 };

/* field order identical to sys/dev/drm/radeon/radeon.h:1342-1357 */
struct radeon_ps {
	u32 caps;
	u32 class;
	u32 class2;
	u32 vclk;
	u32 dclk;
	u32 evclk;
	u32 ecclk;
	bool vce_active;
	enum radeon_vce_level_x 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; u16 data_off, data_size; };
struct radeon_mode_info { struct atom_context *atom_context; };

/* fake CRTC plumbing for rs780_get_pm_mode_parameters (:48-70) */
struct drm_crtc { bool enabled; struct { int htotal, vtotal; } mode; };
struct radeon_crtc { struct drm_crtc base; int crtc_id; };
#define to_radeon_crtc(x) container_of_(x, struct radeon_crtc, base)
#define container_of_(ptr, type, member) \
	((type *)((char *)(ptr) - offsetof(type, member)))
#define RADEON_MAX_CRTCS 6

struct radeon_device {
	struct radeon_mode_info mode_info;
	struct {
		struct radeon_dpm dpm;
	} pm;
	struct drm_crtc *crtcs[RADEON_MAX_CRTCS];
	int num_crtc;
	struct { u32 default_sclk; } clock;
};

/* ------------------------------------------------------------------ */
/* 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

#define RS780_DEFAULT_VCLK_FREQ  53300
#define RS780_DEFAULT_DCLK_FREQ  40000

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_RS780_CLOCK_INFO { /* pptable.h:338 */
	USHORT usLowEngineClockLow;
	UCHAR  ucLowEngineClockHigh;
	USHORT usHighEngineClockLow;
	UCHAR  ucHighEngineClockHigh;
	USHORT usMemoryClockLow;
	UCHAR  ucMemoryClockHigh;
	UCHAR  ucPadding;
	USHORT usVDDC;
	UCHAR  ucMaxHTLinkWidth;
	UCHAR  ucMinHTLinkWidth;
	USHORT usHTLinkFreq;
	ULONG  ulFlags;
} __attribute__((packed)) ATOM_PPLIB_RS780_CLOCK_INFO;

typedef struct _ATOM_PPLIB_STATE {           /* pptable.h:74 */
	UCHAR ucNonClockStateIndex;
	UCHAR ucClockStateIndices[1];
} __attribute__((packed)) ATOM_PPLIB_STATE;

/* reduced ATOM_PPLIB_POWERPLAYTABLE (pptable.h:147): field offsets are
 * what matters — ucNumStates at +5, entry sizes +6..+8, array offsets
 * +9/+11/+13 from the table start */
typedef struct {
	UCHAR  r0[4];			/* ATOM_COMMON_TABLE_HEADER */
	UCHAR  ucDataRevision;		/* +4 */
	UCHAR  ucNumStates;		/* +5 */
	UCHAR  ucStateEntrySize;	/* +6 */
	UCHAR  ucClockInfoSize;		/* +7 */
	UCHAR  ucNonClockSize;		/* +8 */
	USHORT usStateArrayOffset;	/* +9 */
	USHORT usClockInfoArrayOffset;	/* +11 */
	USHORT usNonClockInfoArrayOffset;/* +13 */
} __attribute__((packed)) PPLIB_MIN;

union power_info { PPLIB_MIN pplib; };
union pplib_clock_info { ATOM_PPLIB_RS780_CLOCK_INFO rs780; };
union pplib_power_state { ATOM_PPLIB_STATE v1; };

#define ATOM_PPLIB_RS780_VOLTAGE_NONE       0
#define ATOM_PPLIB_RS780_VOLTAGE_LOW        1
#define ATOM_PPLIB_RS780_VOLTAGE_HIGH       2
#define ATOM_PPLIB_RS780_VOLTAGE_VARIABLE   3

/* ------------------------------------------------------------------ */
/* register / helper stubs                                             */
/* ------------------------------------------------------------------ */
#define R600_PM_NUMBER_OF_TC      15
#define R600_PM_NUMBER_OF_SCLKS   20
enum { R600_POWER_LEVEL_LOW = 0, R600_POWER_LEVEL_MEDIUM = 1,
       R600_POWER_LEVEL_HIGH = 2, R600_POWER_LEVEL_CTXSW = 3 };
enum { R600_DISPLAY_WATERMARK_HIGH = 1 };

static long wreg_budget = 1000000;
static void wreg_hit(void)
{
	if (--wreg_budget < 0) {
		printf("*** WREG32 budget exhausted (runaway loop) ***\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)

#define r600_engine_clock_entry_set_reference_divider(r, e, v) wreg_hit()
#define r600_engine_clock_entry_set_feedback_divider(r, e, v)  wreg_hit()
#define r600_engine_clock_entry_set_post_divider(r, e, v)       wreg_hit()
#define r600_engine_clock_entry_enable_post_divider(r, e, b)    wreg_hit()
#define r600_engine_clock_entry_set_step_time(r, e, v)          wreg_hit()
#define r600_engine_clock_entry_enable_pulse_skipping(r, e, b)  wreg_hit()
#define r600_engine_clock_entry_enable(r, e, b)                 wreg_hit()
#define r600_enable_mclk_control(r, b)                          wreg_hit()
#define r600_voltage_control_enable_pins(r, v)                  wreg_hit()
#define r600_set_bsp(r, a, b)                    wreg_hit()
#define r600_set_at(r, a, b, c, d)               wreg_hit()
#define r600_set_git(r, v)                       wreg_hit()
#define r600_set_tc(r, i, a, b)                  wreg_hit()
#define r600_select_td(r, v)                     wreg_hit()
#define r600_set_vrc(r, v)                       wreg_hit()
#define r600_set_tpu(r, v)                       wreg_hit()
#define r600_set_tpc(r, v)                       wreg_hit()
#define r600_set_sstu(r, v)                      wreg_hit()
#define r600_set_sst(r, v)                       wreg_hit()
#define r600_set_fctu(r, v)                      wreg_hit()
#define r600_set_fct(r, v)                       wreg_hit()
#define r600_set_vddc3d_oorsu(r, v)              wreg_hit()
#define r600_set_vddc3d_oorphc(r, v)             wreg_hit()
#define r600_set_vddc3d_oorsdc(r, v)             wreg_hit()
#define r600_set_ctxcgtt3d_rphc(r, v)            wreg_hit()
#define r600_set_ctxcgtt3d_rsdc(r, v)            wreg_hit()
#define r600_vid_rt_set_vru(r, v)                wreg_hit()
#define r600_vid_rt_set_vrt(r, v)                wreg_hit()
#define r600_vid_rt_set_ssu(r, v)                wreg_hit()
#define r600_power_level_set_voltage_index(r, l, v) wreg_hit()
#define r600_power_level_set_mem_clock_index(r, l, v) wreg_hit()
#define r600_power_level_set_eng_clock_index(r, l, v) wreg_hit()
#define r600_power_level_set_watermark_id(r, l, v)   wreg_hit()
#define r600_power_level_enable(r, l, b)             wreg_hit()
#define r600_power_level_set_enter_index(r, l)       wreg_hit()
#define r600_enable_sclk_control(r, b)           wreg_hit()
#define r600_dynamicpm_enable(r, b)              wreg_hit()
#define r600_enable_spll_bypass(r, b)            wreg_hit()
#define r600_wait_for_spll_change(r)             wreg_hit()
static void radeon_wait_for_vblank(struct radeon_device *rdev, int i)
{ (void)rdev; (void)i; }
static void r600_gfx_clockgating_enable(struct radeon_device *rdev, bool b)
{ (void)rdev; (void)b; }
static bool r600_dynamicpm_enabled(struct radeon_device *rdev)
{ (void)rdev; return RREG32(0) ? true : false; }

struct atom_clock_dividers { u32 ref_div, fb_div, post_div; bool enable_post_div; };
static volatile u32 g_clk_sink;   /* keeps default_state->sclk_low loads live */
static int radeon_atom_get_clock_dividers(struct radeon_device *rdev, int ctl,
	u32 clk, bool b, struct atom_clock_dividers *d)
{ (void)rdev; (void)ctl; (void)b; memset(d, 0, sizeof(*d)); g_clk_sink = clk; return 0; }

static bool r600_is_uvd_state(u32 class, u32 class2)
{ (void)class2; return class & ATOM_PPLIB_CLASSIFICATION_UVDSTATE; }

static void r600_dpm_print_class_info(u32 a, u32 b) { (void)a; (void)b; }
static void r600_dpm_print_cap_info(u32 a) { (void)a; }
static void r600_dpm_print_ps_status(struct radeon_device *r, struct radeon_ps *p)
{ (void)r; (void)p; }
#define printk printf

/* radeon_dpm_fini macro (radeon.h:2802) dispatches to asic->dpm.fini */
static void rs780_dpm_fini(struct radeon_device *rdev);
#define radeon_dpm_fini(rdev) rs780_dpm_fini(rdev)

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

/* rs780_dpm.c:34-39 */
static struct igp_ps *rs780_get_ps(struct radeon_ps *rps)
{
	struct igp_ps *ps = rps->ps_priv;

	return ps;
}

/* rs780_dpm.c:41-46 */
static struct igp_power_info *rs780_get_pi(struct radeon_device *rdev)
{
	struct igp_power_info *pi = rdev->pm.dpm.priv;

	return pi;
}

/* rs780_dpm.c:48-70 */
static void rs780_get_pm_mode_parameters(struct radeon_device *rdev)
{
	struct igp_power_info *pi = rs780_get_pi(rdev);
	struct radeon_mode_info *minfo = &rdev->mode_info;
	struct drm_crtc *crtc;
	struct radeon_crtc *radeon_crtc;
	int i;

	/* defaults */
	pi->crtc_id = 0;
	pi->refresh_rate = 60;

	for (i = 0; i < rdev->num_crtc; i++) {
		crtc = rdev->crtcs[i];
		if (crtc && crtc->enabled) {
			radeon_crtc = to_radeon_crtc(crtc);
			pi->crtc_id = radeon_crtc->crtc_id;
			if (crtc->mode.htotal && crtc->mode.vtotal)
				; /* drm_mode_vrefresh stubbed: keep default */
			break;
		}
	}
}

/* rs780_dpm.c:74-106 */
static int rs780_initialize_dpm_power_state(struct radeon_device *rdev,
					    struct radeon_ps *boot_ps)
{
	struct atom_clock_dividers dividers;
	struct igp_ps *default_state = rs780_get_ps(boot_ps);   /* :78 !! */
	int i, ret;

	ret = radeon_atom_get_clock_dividers(rdev, 0,
					     default_state->sclk_low, false, &dividers); /* :82 !! */
	if (ret)
		return ret;

	r600_engine_clock_entry_set_reference_divider(rdev, 0, dividers.ref_div);
	r600_engine_clock_entry_set_feedback_divider(rdev, 0, dividers.fb_div);
	r600_engine_clock_entry_set_post_divider(rdev, 0, dividers.post_div);

	if (dividers.enable_post_div)
		r600_engine_clock_entry_enable_post_divider(rdev, 0, true);
	else
		r600_engine_clock_entry_enable_post_divider(rdev, 0, false);

	r600_engine_clock_entry_set_step_time(rdev, 0, 0);
	r600_engine_clock_entry_enable_pulse_skipping(rdev, 0, false);

	r600_engine_clock_entry_enable(rdev, 0, true);
	for (i = 1; i < R600_PM_NUMBER_OF_SCLKS; i++)
		r600_engine_clock_entry_enable(rdev, i, false);

	r600_enable_mclk_control(rdev, false);
	r600_voltage_control_enable_pins(rdev, 0);

	return 0;
}

/* rs780_dpm.c:108-173 (register plumbing stubbed) */
static int rs780_initialize_dpm_parameters(struct radeon_device *rdev,
					   struct radeon_ps *boot_ps)
{
	int ret = 0;
	int i;

	r600_set_bsp(rdev, 0, 0);
	r600_set_at(rdev, 0, 0, 0, 0);
	r600_set_git(rdev, 0);
	for (i = 0; i < R600_PM_NUMBER_OF_TC; i++)
		r600_set_tc(rdev, i, 0, 0);
	r600_select_td(rdev, 0);
	r600_set_vrc(rdev, 0);
	r600_set_vrc(rdev, 0);

	ret = rs780_initialize_dpm_power_state(rdev, boot_ps);   /* :145 !! */

	r600_power_level_set_voltage_index(rdev, R600_POWER_LEVEL_LOW,     0);
	r600_power_level_set_voltage_index(rdev, R600_POWER_LEVEL_MEDIUM,  0);
	r600_power_level_set_voltage_index(rdev, R600_POWER_LEVEL_HIGH,    0);
	r600_power_level_set_mem_clock_index(rdev, R600_POWER_LEVEL_LOW,    0);
	r600_power_level_set_mem_clock_index(rdev, R600_POWER_LEVEL_MEDIUM, 0);
	r600_power_level_set_mem_clock_index(rdev, R600_POWER_LEVEL_HIGH,   0);
	r600_power_level_set_eng_clock_index(rdev, R600_POWER_LEVEL_LOW,    0);
	r600_power_level_set_eng_clock_index(rdev, R600_POWER_LEVEL_MEDIUM, 0);
	r600_power_level_set_eng_clock_index(rdev, R600_POWER_LEVEL_HIGH,   0);
	r600_power_level_set_watermark_id(rdev, R600_POWER_LEVEL_LOW,    R600_DISPLAY_WATERMARK_HIGH);
	r600_power_level_set_watermark_id(rdev, R600_POWER_LEVEL_MEDIUM, R600_DISPLAY_WATERMARK_HIGH);
	r600_power_level_set_watermark_id(rdev, R600_POWER_LEVEL_HIGH,   R600_DISPLAY_WATERMARK_HIGH);
	r600_power_level_enable(rdev, R600_POWER_LEVEL_CTXSW, false);
	r600_power_level_enable(rdev, R600_POWER_LEVEL_HIGH, false);
	r600_power_level_enable(rdev, R600_POWER_LEVEL_MEDIUM, false);
	r600_power_level_enable(rdev, R600_POWER_LEVEL_LOW, true);
	r600_power_level_set_enter_index(rdev, R600_POWER_LEVEL_LOW);
	r600_set_vrc(rdev, 0);

	return ret;
}

/* rs780_dpm.c:175-196 (register-only) */
static void rs780_start_dpm(struct radeon_device *rdev)
{
	r600_enable_sclk_control(rdev, false);
	r600_enable_mclk_control(rdev, false);
	r600_dynamicpm_enable(rdev, true);
	radeon_wait_for_vblank(rdev, 0);
	radeon_wait_for_vblank(rdev, 1);
	r600_enable_spll_bypass(rdev, true);
	r600_wait_for_spll_change(rdev);
	r600_enable_spll_bypass(rdev, false);
	r600_wait_for_spll_change(rdev);
	r600_enable_spll_bypass(rdev, true);
	r600_wait_for_spll_change(rdev);
	r600_enable_spll_bypass(rdev, false);
	r600_wait_for_spll_change(rdev);
	r600_enable_sclk_control(rdev, true);
}

/* rs780_dpm.c:199-207,209-220 (register-only) */
static void rs780_preset_ranges_slow_clk_fbdiv_en(struct radeon_device *rdev)
{ (void)rdev; }
static void rs780_preset_starting_fbdiv(struct radeon_device *rdev)
{ (void)rdev; }

/* rs780_dpm.c:303-311 */
static void rs780_clk_scaling_enable(struct radeon_device *rdev, bool enable)
{ (void)rdev; (void)enable; }

/* rs780_dpm.c:362-371 */
static void rs780_program_at(struct radeon_device *rdev)
{
	struct igp_power_info *pi = rs780_get_pi(rdev);

	WREG32(0, 30000000 / pi->refresh_rate);
	WREG32(0, 1000000 * 5 / pi->refresh_rate);
	WREG32(0, 1000000 * 10 / pi->refresh_rate);
	WREG32(0, 1000000 * 30 / pi->refresh_rate);
	WREG32(0, 1000000 * 50 / pi->refresh_rate);
}

/* rs780_dpm.c:373-376 */
static void rs780_disable_vbios_powersaving(struct radeon_device *rdev)
{ (void)rdev; }

/* rs780_dpm.c:313-319 (register-only) */
static void rs780_voltage_scaling_enable(struct radeon_device *rdev, bool enable)
{ (void)rdev; (void)enable; }

/* rs780_dpm.c:321-334,336-345,347-350,352-360 (register-only) */
static void rs780_set_engine_clock_wfc(struct radeon_device *rdev) { (void)rdev; }
static void rs780_set_engine_clock_sc(struct radeon_device *rdev)  { (void)rdev; }
static void rs780_set_engine_clock_tdc(struct radeon_device *rdev) { (void)rdev; }
static void rs780_set_engine_clock_ssc(struct radeon_device *rdev) { (void)rdev; }

/* rs780_dpm.c:597-628 */
static int rs780_dpm_enable(struct radeon_device *rdev)
{
	struct igp_power_info *pi = rs780_get_pi(rdev);
	struct radeon_ps *boot_ps = rdev->pm.dpm.boot_ps;   /* :600 */
	int ret;

	rs780_get_pm_mode_parameters(rdev);                 /* :603 (pi deref) */
	rs780_disable_vbios_powersaving(rdev);

	if (r600_dynamicpm_enabled(rdev))
		return -EINVAL;
#ifdef FIX_DF4493
	if (boot_ps == NULL || boot_ps->ps_priv == NULL) {  /* proposed fix */
		DRM_ERROR("no boot power state, refusing to enable DPM");
		return -EINVAL;
	}
#endif
	ret = rs780_initialize_dpm_parameters(rdev, boot_ps); /* :608 !! */
	if (ret)
		return ret;
	rs780_start_dpm(rdev);

	rs780_preset_ranges_slow_clk_fbdiv_en(rdev);
	rs780_preset_starting_fbdiv(rdev);
	if (pi->voltage_control)
		rs780_clk_scaling_enable(rdev, true);   /* stub stand-in */
	rs780_clk_scaling_enable(rdev, true);
	rs780_set_engine_clock_sc(rdev);
	rs780_set_engine_clock_wfc(rdev);
	rs780_program_at(rdev);
	rs780_set_engine_clock_tdc(rdev);
	rs780_set_engine_clock_ssc(rdev);

	if (pi->gfx_clock_gating)
		r600_gfx_clockgating_enable(rdev, true);

	return 0;
}

/* rs780_dpm.c:717-745 */
static void rs780_parse_pplib_non_clock_info(struct radeon_device *rdev,
					     struct radeon_ps *rps,
					     ATOM_PPLIB_NONCLOCK_INFO *non_clock_info,
					     u8 table_rev)
{
	rps->caps = le32_to_cpu(non_clock_info->ulCapsAndSettings);      /* :722 */
	rps->class = le16_to_cpu(non_clock_info->usClassification);      /* :723 */
	rps->class2 = le16_to_cpu(non_clock_info->usClassification2);    /* :724 */

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

	if (r600_is_uvd_state(rps->class, rps->class2)) {
		if ((rps->vclk == 0) || (rps->dclk == 0)) {
			rps->vclk = RS780_DEFAULT_VCLK_FREQ;
			rps->dclk = RS780_DEFAULT_DCLK_FREQ;
		}
	}

	if (rps->class & ATOM_PPLIB_CLASSIFICATION_BOOT)
		rdev->pm.dpm.boot_ps = rps;                              /* :742 */
	if (rps->class & ATOM_PPLIB_CLASSIFICATION_UVDSTATE)
		rdev->pm.dpm.uvd_ps = rps;
}

/* rs780_dpm.c:747-787 */
static void rs780_parse_pplib_clock_info(struct radeon_device *rdev,
					 struct radeon_ps *rps,
					 union pplib_clock_info *clock_info)
{
	struct igp_ps *ps = rs780_get_ps(rps);
	u32 sclk;

	sclk = le16_to_cpu(clock_info->rs780.usLowEngineClockLow);       /* :754 */
	sclk |= clock_info->rs780.ucLowEngineClockHigh << 16;
	ps->sclk_low = sclk;
	sclk = le16_to_cpu(clock_info->rs780.usHighEngineClockLow);      /* :757 */
	sclk |= clock_info->rs780.ucHighEngineClockHigh << 16;
	ps->sclk_high = sclk;
	switch (le16_to_cpu(clock_info->rs780.usVDDC)) {
	case ATOM_PPLIB_RS780_VOLTAGE_NONE:
	default:
		ps->min_voltage = RS780_VDDC_LEVEL_UNKNOWN;
		ps->max_voltage = RS780_VDDC_LEVEL_UNKNOWN;
		break;
	case ATOM_PPLIB_RS780_VOLTAGE_LOW:
		ps->min_voltage = RS780_VDDC_LEVEL_LOW;
		ps->max_voltage = RS780_VDDC_LEVEL_LOW;
		break;
	case ATOM_PPLIB_RS780_VOLTAGE_HIGH:
		ps->min_voltage = RS780_VDDC_LEVEL_HIGH;
		ps->max_voltage = RS780_VDDC_LEVEL_HIGH;
		break;
	case ATOM_PPLIB_RS780_VOLTAGE_VARIABLE:
		ps->min_voltage = RS780_VDDC_LEVEL_LOW;
		ps->max_voltage = RS780_VDDC_LEVEL_HIGH;
		break;
	}
	ps->flags = le32_to_cpu(clock_info->rs780.ulFlags);              /* :779 */

	if (rps->class & ATOM_PPLIB_CLASSIFICATION_BOOT) {
		ps->sclk_low = rdev->clock.default_sclk;
		ps->sclk_high = rdev->clock.default_sclk;
		ps->min_voltage = RS780_VDDC_LEVEL_HIGH;
		ps->max_voltage = RS780_VDDC_LEVEL_HIGH;
	}
}

/* proposed DF-4492 fix helper */
static int rs780_pplib_walk_ok(struct radeon_device *rdev, u32 extra)
{
	u32 data_offset = rdev->mode_info.atom_context->data_off;
	u32 data_size   = rdev->mode_info.atom_context->data_size;
	u32 base = data_offset + extra;

	return base <= data_offset + data_size && data_size >= 1;
}

/* rs780_dpm.c:789-844 (atom_parse_data_header satisfied by caller) */
static int rs780_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;
	union pplib_clock_info *clock_info;
	union power_info *power_info;
	u16 data_offset = rdev->mode_info.atom_context->data_off;
	u8 frev, crev;
	struct igp_ps *ps;

	frev = crev = 1;
	(void)frev; (void)crev;
	power_info = (union power_info *)(mode_info->atom_context->bios + data_offset);

#ifdef FIX_DF4492
	if (rdev->mode_info.atom_context->data_size < 15) {
		/* fixed header (15 bytes read below) larger than the table */
		DRM_ERROR("power table too small");
		return -EINVAL;
	}
#endif

	rdev->pm.dpm.ps = kzalloc(sizeof(struct radeon_ps) *
				  power_info->pplib.ucNumStates, GFP_KERNEL); /* :807 */
	if (!rdev->pm.dpm.ps)
		return -ENOMEM;

#ifdef FIX_DF4492
	if (power_info->pplib.ucStateEntrySize < 2) {      /* v1 state is 2 bytes */
		DRM_ERROR("invalid ucStateEntrySize %d",
		    power_info->pplib.ucStateEntrySize);
		kfree(rdev->pm.dpm.ps);
		rdev->pm.dpm.ps = NULL;
		return -EINVAL;
	}
	if (!rs780_pplib_walk_ok(rdev,
		(u32)le16_to_cpu(power_info->pplib.usStateArrayOffset) +
		(u32)power_info->pplib.ucNumStates *
		power_info->pplib.ucStateEntrySize)) {
		DRM_ERROR("state array walks past the power table");
		kfree(rdev->pm.dpm.ps);
		rdev->pm.dpm.ps = NULL;
		return -EINVAL;
	}
#endif
	for (i = 0; i < power_info->pplib.ucNumStates; i++) {
		power_state = (union pplib_power_state *)
			((uint8_t*)mode_info->atom_context->bios + data_offset +
			 le16_to_cpu(power_info->pplib.usStateArrayOffset) +
			 i * power_info->pplib.ucStateEntrySize);            /* :812-816 */
#ifdef FIX_DF4492
		if (!rs780_pplib_walk_ok(rdev,
			(u32)le16_to_cpu(power_info->pplib.usNonClockInfoArrayOffset) +
			(u32)power_state->v1.ucNonClockStateIndex *
			power_info->pplib.ucNonClockSize +
			sizeof(ATOM_PPLIB_NONCLOCK_INFO))) {
			DRM_ERROR("nonClockInfoIndex %d walks past the table",
			    power_state->v1.ucNonClockStateIndex);
			kfree(rdev->pm.dpm.ps);
			rdev->pm.dpm.ps = NULL;
			return -EINVAL;
		}
#endif
		non_clock_info = (ATOM_PPLIB_NONCLOCK_INFO *)
			((uint8_t*)mode_info->atom_context->bios + data_offset +
			 le16_to_cpu(power_info->pplib.usNonClockInfoArrayOffset) +
			 (power_state->v1.ucNonClockStateIndex *
			  power_info->pplib.ucNonClockSize));               /* :817-821 */
		if (power_info->pplib.ucStateEntrySize - 1) {            /* :822 */
#ifdef FIX_DF4492
			if (!rs780_pplib_walk_ok(rdev,
				(u32)le16_to_cpu(power_info->pplib.usClockInfoArrayOffset) +
				(u32)power_state->v1.ucClockStateIndices[0] *
				power_info->pplib.ucClockInfoSize +
				sizeof(ATOM_PPLIB_RS780_CLOCK_INFO))) {
				DRM_ERROR("clockInfoIndex %d walks past the table",
				    power_state->v1.ucClockStateIndices[0]);
				kfree(rdev->pm.dpm.ps);
				rdev->pm.dpm.ps = NULL;
				return -EINVAL;
			}
#endif
			clock_info = (union pplib_clock_info *)
				((uint8_t*)mode_info->atom_context->bios + data_offset +
				 le16_to_cpu(power_info->pplib.usClockInfoArrayOffset) +
				 (power_state->v1.ucClockStateIndices[0] *
				  power_info->pplib.ucClockInfoSize));       /* :823-827 */
			ps = kzalloc(sizeof(struct igp_ps), GFP_KERNEL);  /* :828 */
			if (ps == NULL) {
#ifdef FIX_DF4494
				{ 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 */
				rdev->pm.dpm.boot_ps = NULL;
				rdev->pm.dpm.uvd_ps = NULL;
				return -ENOMEM;
#else
				kfree(rdev->pm.dpm.ps);                   /* :830 dangling! */
				return -ENOMEM;
#endif
			}
			rdev->pm.dpm.ps[i].ps_priv = ps;
			rs780_parse_pplib_non_clock_info(rdev, &rdev->pm.dpm.ps[i],
							 non_clock_info,
							 power_info->pplib.ucNonClockSize);
			rs780_parse_pplib_clock_info(rdev,
						     &rdev->pm.dpm.ps[i],
						     clock_info);
		}
	}
	rdev->pm.dpm.num_ps = power_info->pplib.ucNumStates;            /* :842 */
	return 0;
}

/* rs780_dpm.c:846-935 (scenario-relevant subset; r600_get_platform_caps
 * stubbed to success, igp_info header presence scenario-controlled) */
static int rs780_dpm_init(struct radeon_device *rdev, int igp_header_present)
{
	struct igp_power_info *pi;
	int ret;

	pi = kzalloc(sizeof(struct igp_power_info), GFP_KERNEL);     /* :855 */
	if (pi == NULL)
		return -ENOMEM;
	rdev->pm.dpm.priv = pi;

	ret = rs780_parse_power_table(rdev);                         /* :864 */
	if (ret)
		return ret;

	pi->voltage_control = false;
	pi->gfx_clock_gating = true;

	if (igp_header_present) {
		/* crev 1/2 fields irrelevant to the lifetimes under test */
		pi->max_voltage = 100;
		pi->min_voltage = 50;
		pi->voltage_control = false;
		return 0;
	}
#ifndef FIX_DF4494
	radeon_dpm_fini(rdev);                                       /* :933 !! */
	return -EINVAL;                                              /* :934 */
#else
	/* proposed fix: do NOT tear the state down here; the caller
	 * (radeon_pm_fini -> radeon_dpm_fini at teardown) owns cleanup,
	 * so every pointer stays valid or NULL — never dangling. */
	DRM_ERROR("No integrated system info for your GPU");
	return -EINVAL;
#endif
}

/* rs780_dpm.c:937-950 */
static void rs780_dpm_print_power_state(struct radeon_device *rdev,
					struct radeon_ps *rps)
{
	struct igp_ps *ps = rs780_get_ps(rps);

	r600_dpm_print_class_info(rps->class, rps->class2);
	r600_dpm_print_cap_info(rps->caps);
	printk("\tuvd    vclk: %d dclk: %d\n", rps->vclk, rps->dclk);
	printk("\t\tpower level 0    sclk: %u vddc_index: %d\n",
	       ps->sclk_low, ps->min_voltage);                       /* :946 !! */
	printk("\t\tpower level 1    sclk: %u vddc_index: %d\n",
	       ps->sclk_high, ps->max_voltage);
	r600_dpm_print_ps_status(rdev, rps);
}

/* rs780_dpm.c:952-961 */
static void rs780_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);                   /* :957 */
	}
	kfree(rdev->pm.dpm.ps);                                      /* :959 */
	kfree(rdev->pm.dpm.priv);                                    /* :960 */
}

/* radeon_pm.c:1412-1420 (caller replica) */
static void radeon_dpm_print_power_states(struct radeon_device *rdev)
{
	int i;

	for (i = 0; i < rdev->pm.dpm.num_ps; i++) {
		printk("== power state %d ==\n", i);
		rs780_dpm_print_power_state(rdev, &rdev->pm.dpm.ps[i]);
	}
}

/* ================================================================== */
/* VBIOS image crafting                                               */
/* ================================================================== */
#define BIOS_SZ   16384
#define DATA_OFF  64		/* atom data_offset of the PowerPlayInfo table */
#define TBL_SZ    512		/* claimed power-table structure size */

#define OFF_STATES    16
#define OFF_CLOCKS    128
#define OFF_NONCLOCK  (TBL_SZ - 64)  /* near the table end */

static struct radeon_device RDEV;
static struct igp_power_info *PI;

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

	memset(bios, 0, BIOS_SZ);
	RDEV.mode_info.atom_context->bios = bios;
	RDEV.mode_info.atom_context->data_off = DATA_OFF;
	RDEV.mode_info.atom_context->data_size = TBL_SZ;
	p = (PPLIB_MIN *)(bios + DATA_OFF);
	p->ucNumStates = 1;
	p->ucStateEntrySize = 2;
	p->ucClockInfoSize = sizeof(ATOM_PPLIB_RS780_CLOCK_INFO);
	p->ucNonClockSize = sizeof(ATOM_PPLIB_NONCLOCK_INFO);
	p->usStateArrayOffset = OFF_STATES;
	p->usClockInfoArrayOffset = OFF_CLOCKS;
	p->usNonClockInfoArrayOffset = OFF_NONCLOCK;
}

static void bios_state(uint8_t *bios, int n, int nonclock_idx, int clock_idx)
{
	PPLIB_MIN *p = (PPLIB_MIN *)(bios + DATA_OFF);
	ATOM_PPLIB_STATE *st =
	    (ATOM_PPLIB_STATE *)(bios + DATA_OFF + OFF_STATES + n * 2);

	p->ucNumStates = n + 1;
	st->ucNonClockStateIndex = nonclock_idx;
	st->ucClockStateIndices[0] = clock_idx;
}

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

	memset(ni, 0, sizeof(*ni));
	ni->usClassification = class;
	ni->ulVCLK = vclk;
	ni->ulDCLK = vclk;
}

static void bios_clock(uint8_t *bios, int n, u32 lo, u32 hi, u16 vddc)
{
	ATOM_PPLIB_RS780_CLOCK_INFO *ci =
	    (ATOM_PPLIB_RS780_CLOCK_INFO *)(bios + DATA_OFF + OFF_CLOCKS +
		n * sizeof(ATOM_PPLIB_RS780_CLOCK_INFO));

	memset(ci, 0, sizeof(*ci));
	ci->usLowEngineClockLow = lo & 0xffff;
	ci->ucLowEngineClockHigh = lo >> 16;
	ci->usHighEngineClockLow = hi & 0xffff;
	ci->ucHighEngineClockHigh = hi >> 16;
	ci->usVDDC = vddc;
}

static void common_setup(void)
{
	memset(&RDEV, 0, sizeof(RDEV));
	RDEV.mode_info.atom_context = kzalloc(sizeof(struct atom_context), GFP_KERNEL);
	RDEV.clock.default_sclk = 40000;
	RDEV.num_crtc = 0;
}

static void alloc_pi(void)
{
	PI = kzalloc(sizeof(struct igp_power_info), GFP_KERNEL);
	RDEV.pm.dpm.priv = PI;
	PI->refresh_rate = 60;
	PI->voltage_control = false;
	PI->gfx_clock_gating = false;   /* skip clockgating stub noise */
}

/* ================================================================== */
/* scenarios                                                          */
/* ================================================================== */

/* DF-4492: near OOB — poison ingestion (redzone landings), canary reports */
static void mode_4492(void)
{
	uint8_t *bios;
	int ret;
	PPLIB_MIN *p;
	ATOM_PPLIB_NONCLOCK_INFO *oob_nc;
	union pplib_clock_info *oob_ci;
	union pplib_power_state *oob_st;
	/* table-relative coordinate of the image end: bios+DATA_OFF+16320 */
	const u32 tbl_end = BIOS_SZ - DATA_OFF;   /* 16320 */

	printf("== DF-4492: unchecked pplib indices/strides -> OOB reads ==\n");
	common_setup();
	alloc_pi();
	bios = kzalloc(BIOS_SZ, GFP_KERNEL);
	bios_common(bios);
	p = (PPLIB_MIN *)(bios + DATA_OFF);

	/* craft 1: nonClockStateIndex = 250 (u8) with ucNonClockSize = 24 ->
	 * usNonClockInfoArrayOffset(10336) + 250*24 = 16336 lands 16 bytes
	 * PAST the 16 KB image, inside the 64-byte rear redzone (poison) */
	p->ucNonClockSize = 24;
	p->usNonClockInfoArrayOffset = 16336 - 250 * 24;
	bios_nonclock(bios, 0, 0x0001 /* BOOT */, 0);
	bios_state(bios, 0, 250, 0);

	oob_nc = (ATOM_PPLIB_NONCLOCK_INFO *)(bios + DATA_OFF +
	    p->usNonClockInfoArrayOffset + 250 * 24);
	printf("BIOS image: %d bytes; nonClockInfo[250] lands at bios+%u "
	    "(%u bytes PAST the image end, inside the rear redzone)\n",
	    BIOS_SZ, p->usNonClockInfoArrayOffset + 250 * 24,
	    p->usNonClockInfoArrayOffset + 250 * 24 - tbl_end);
#ifndef FIX_DF4492
	check_in_object("nonClockInfo[250] (OOB vs bios allocation)", oob_nc,
	    sizeof(*oob_nc));

	/* craft 2: clockInfoIndex = 250 with ucClockInfoSize = 20 ->
	 * landing at 16336, same redzone */
	p->ucClockInfoSize = 20;
	p->usClockInfoArrayOffset = 16336 - 250 * 20;
	bios_clock(bios, 0, 30000, 50000, 3);
	bios_state(bios, 0, 0, 250);
	oob_ci = (union pplib_clock_info *)(bios + DATA_OFF +
	    p->usClockInfoArrayOffset + 250 * 20);
	check_in_object("clockInfo[250] (OOB vs bios allocation)", oob_ci,
	    sizeof(ATOM_PPLIB_RS780_CLOCK_INFO));

	/* craft 3: state-array walk, ucStateEntrySize = 254 ->
	 * state[7] at 16318 + 7*254 = 18096, ~1.7 KB past the image */
	bios_state(bios, 0, 0, 0);
	p->usStateArrayOffset = tbl_end - 2;
	p->ucStateEntrySize = 254;
	p->ucNumStates = 8;
	oob_st = (union pplib_power_state *)(bios + DATA_OFF +
	    p->usStateArrayOffset + 7 * 254);
	check_in_object("state[7] of the ucStateEntrySize=254 walk (OOB)",
	    oob_st, sizeof(ATOM_PPLIB_STATE));
	p->ucStateEntrySize = 2;
	p->ucNumStates = 1;
	p->usStateArrayOffset = OFF_STATES;

	/* decisive parse run with the redzone-poison settings */
	bios_state(bios, 0, 250, 250);
#endif
	fflush(stdout);
	ret = rs780_parse_power_table(&RDEV);
	printf("rs780_parse_power_table() = %d num_ps=%d\n",
	    ret, RDEV.pm.dpm.num_ps);
#ifdef FIX_DF4492
	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)" : "");
		printf("ps[0].sclk_low  = 0x%08x %s\n",
		    ((struct igp_ps *)RDEV.pm.dpm.ps[0].ps_priv)->sclk_low,
		    ((struct igp_ps *)RDEV.pm.dpm.ps[0].ps_priv)->sclk_low ==
		    0xA5A5A5A5 ? "(== canary poison)" : "");
	}
#endif
	rs780_dpm_fini(&RDEV);
}

/* DF-4492 far variant: guard-page-backed bios -> deterministic fault */
static void mode_4492g(void)
{
	uint8_t *mapping, *bios;
	PPLIB_MIN *p;
	void *target;
	int ret;
	size_t pages = BIOS_SZ / 4096;

	printf("== DF-4492g: far OOB on guard-page-backed BIOS image ==\n");
	common_setup();
	alloc_pi();
	mapping = mmap(NULL, (pages + 1) * 4096, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANON, -1, 0);
	if (mapping == MAP_FAILED) {
		printf("mmap failed\n");
		exit(3);
	}
	if (mprotect(mapping + pages * 4096, 4096, PROT_NONE) != 0) {
		printf("mprotect failed\n");
		exit(3);
	}
	bios = mapping;                    /* image occupies [bios, bios+16KB) */
	bios_common(bios);
	p = (PPLIB_MIN *)(bios + DATA_OFF);

	/* clockInfoIndex 66 * ucClockInfoSize 255 = 16830 -> landing at
	 * bios+DATA_OFF+128+16830 = bios+17022, 638 bytes past the image end
	 * and INSIDE the PROT_NONE guard page */
	p->ucClockInfoSize = 255;
	bios_nonclock(bios, 0, 0x0001, 0);
	bios_clock(bios, 0, 30000, 50000, 3);
	bios_state(bios, 0, 0, 66);
	target = bios + DATA_OFF + p->usClockInfoArrayOffset + 66 * 255;
	printf("BIOS image: %d bytes at %p, guard page at %p\n",
	    BIOS_SZ, (void *)bios, (void *)(bios + BIOS_SZ));
	printf("clockInfo[66] (x255) lands at %p = %td bytes PAST the image "
	    "end (inside the guard page)\n", target,
	    (char *)target - (char *)(bios + BIOS_SZ));
	printf("calling rs780_parse_power_table() ...\n");
	fflush(stdout);
	ret = rs780_parse_power_table(&RDEV);
	printf("rs780_parse_power_table() = %d - returned%s\n", ret,
#ifdef FIX_DF4492
	    " (fix active)");
#else
	    " - UNEXPECTED (expected SIGSEGV in the guard page)");
#endif
	rs780_dpm_fini(&RDEV);
}

/* DF-4493: no BOOT-classified state -> NULL boot_ps -> enable faults */
static void mode_4493(void)
{
	uint8_t *bios;
	int ret;

	printf("== DF-4493: no BOOT-classified state -> NULL boot_ps ==\n");
	printf("offsetof(struct radeon_ps, ps_priv) = %zu (kernel layout: 40)\n",
	    offsetof(struct radeon_ps, ps_priv));
	common_setup();
	alloc_pi();
	bios = kzalloc(BIOS_SZ, GFP_KERNEL);
	bios_common(bios);
	bios_clock(bios, 0, 30000, 50000, 3);
	bios_nonclock(bios, 0, 0x0002 /* PERFORMANCE, no BOOT bit */, 0);
	bios_state(bios, 0, 0, 0);

	ret = rs780_parse_power_table(&RDEV);
	printf("rs780_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 discards the dpm_init error; :1449 sets
	 * current_ps=requested_ps=boot_ps; :1453 enables anyway */
	RDEV.pm.dpm.current_ps = RDEV.pm.dpm.requested_ps = RDEV.pm.dpm.boot_ps;
	printf("calling rs780_dpm_enable() with boot_ps == NULL ...\n");
	printf("(rs780_dpm.c:78 rs780_get_ps(NULL) reads NULL->ps_priv; "
	    "kernel: page-fault panic; harness: SIGSEGV at 0x28)\n");
	fflush(stdout);
	ret = rs780_dpm_enable(&RDEV);
	printf("rs780_dpm_enable() returned %d - NO CRASH%s\n", ret,
#ifdef FIX_DF4493
	    " (fix active)");
#else
	    " - UNEXPECTED (expected SIGSEGV at 0x28)");
#endif
	rs780_dpm_fini(&RDEV);
}

/* DF-4493 variant b: ucStateEntrySize == 1 skips every state -> NULL
 * ps_priv everywhere, num_ps still set */
static void mode_4493b(void)
{
	uint8_t *bios;
	PPLIB_MIN *p;
	int ret;

	printf("== DF-4493b: ucStateEntrySize==1 -> no ps_priv, NULL deref in "
	    "print ==\n");
	common_setup();
	alloc_pi();
	bios = kzalloc(BIOS_SZ, GFP_KERNEL);
	bios_common(bios);
	p = (PPLIB_MIN *)(bios + DATA_OFF);
	p->ucStateEntrySize = 1;         /* `if (ucStateEntrySize - 1)` false */
	bios_clock(bios, 0, 30000, 50000, 3);
	bios_nonclock(bios, 0, 0x0001, 0);
	bios_state(bios, 0, 0, 0);

	ret = rs780_parse_power_table(&RDEV);
	printf("rs780_parse_power_table() = %d num_ps=%d boot_ps=%p "
	    "ps[0].ps_priv=%p\n", ret, RDEV.pm.dpm.num_ps,
	    (void *)RDEV.pm.dpm.boot_ps,
	    ret == 0 ? RDEV.pm.dpm.ps[0].ps_priv : NULL);
	if (ret != 0) {
#ifdef FIX_DF4492
		printf("crafted table rejected (fix active: ucStateEntrySize "
		    "< 2 is malformed)\n");
#else
		printf("parse failed - UNEXPECTED\n");
#endif
		return;
	}
	printf("radeon_dpm_print_power_states() walks %d states whose "
	    "ps_priv are NULL ...\n", RDEV.pm.dpm.num_ps);
	printf("(rs780_dpm.c:946 ps->sclk_low with ps == NULL; harness: "
	    "SIGSEGV at 0x8)\n");
	fflush(stdout);
	radeon_dpm_print_power_states(&RDEV);   /* radeon_dpm=1 boot path */
	printf("returned - NO CRASH%s\n",
#ifdef FIX_DF4493
	    "");
#else
	    " - UNEXPECTED (expected SIGSEGV at 0x8)");
#endif
	rs780_dpm_fini(&RDEV);
}

/* DF-4494: ENOMEM mid-parse -> dangling dpm.ps -> double free in fini */
static void mode_4494(void)
{
	uint8_t *bios;
	int ret;

	printf("== DF-4494: dangling dpm.ps double free in rs780_dpm_fini ==\n");
	common_setup();
	alloc_pi();
	bios = kzalloc(BIOS_SZ, GFP_KERNEL);
	bios_common(bios);
	bios_clock(bios, 0, 30000, 50000, 3);
	bios_clock(bios, 1, 20000, 40000, 3);
	bios_nonclock(bios, 0, 0x0001 /* BOOT */, 0);
	bios_nonclock(bios, 1, 0x0002, 0);
	bios_state(bios, 0, 0, 0);
	bios_state(bios, 1, 1, 1);

	/* fail the kzalloc(struct igp_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;
	ret = rs780_parse_power_table(&RDEV);
	printf("rs780_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)");
	printf("rdev->pm.dpm.boot_ps = %p (%s)\n",
	    (void *)RDEV.pm.dpm.boot_ps,
	    RDEV.pm.dpm.boot_ps ? "points into the freed array" : "NULL");
	fflush(stdout);
	/* radeon_pm.c:1448 discards the error; the driver loads anyway;
	 * radeon_pm.c:1705 teardown calls dpm fini unconditionally */
	printf("driver unload -> radeon_pm_fini -> rs780_dpm_fini ...\n");
	fflush(stdout);
	rs780_dpm_fini(&RDEV);                    /* :959 kfree(dangling) */
	printf("rs780_dpm_fini() returned; canary_violations so far = %d%s\n",
	    canary_violations,
#ifdef FIX_DF4494
	    " (fix active: pointer NULLed after cleanup, no double free)");
#else
	    canary_violations ? " (DOUBLE FREE detected above)" :
	    " - UNEXPECTED (expected double-free detection)");
#endif
}

/* DF-4494 variant u: enable dereferences the dangling boot_ps (UAF) */
static void mode_4494u(void)
{
	uint8_t *bios;
	int ret;

	printf("== DF-4494u: enable after ENOMEM-dangling -> UAF read ==\n");
	common_setup();
	alloc_pi();
	bios = kzalloc(BIOS_SZ, GFP_KERNEL);
	bios_common(bios);
	bios_clock(bios, 0, 30000, 50000, 3);
	bios_clock(bios, 1, 20000, 40000, 3);
	bios_nonclock(bios, 0, 0x0001 /* BOOT */, 0);
	bios_nonclock(bios, 1, 0x0002, 0);
	bios_state(bios, 0, 0, 0);
	bios_state(bios, 1, 1, 1);

	kmalloc_fail_at = 6;
	ret = rs780_parse_power_table(&RDEV);
	printf("rs780_parse_power_table() = %d, dpm.ps=%p boot_ps=%p\n",
	    ret, (void *)RDEV.pm.dpm.ps, (void *)RDEV.pm.dpm.boot_ps);
	/* radeon_pm.c:1448 swallows; :1449 keeps the dangling boot_ps */
	RDEV.pm.dpm.current_ps = RDEV.pm.dpm.requested_ps = RDEV.pm.dpm.boot_ps;
	check_is_freed("boot_ps (&dpm.ps[0])", RDEV.pm.dpm.boot_ps);
	printf("calling rs780_dpm_enable() (reads ps[0].ps_priv from the "
	    "FREED array, then dereferences the 0xB0B0.. value) ...\n");
	fflush(stdout);
	ret = rs780_dpm_enable(&RDEV);
	printf("rs780_dpm_enable() returned %d - NO CRASH%s\n", ret,
#ifdef FIX_DF4494
	    " (fix active: dpm.ps/boot_ps NULLed, enable guard)");
#else
	    " - UNEXPECTED (expected UAF report + SIGSEGV)");
#endif
}

/* DF-4494 variant w: rs780_dpm_init self-fini + swallowed error ->
 * UAF WRITE of the freed igp_power_info */
static void mode_4494w(void)
{
	uint8_t *bios;
	int ret;

	printf("== DF-4494w: rs780_dpm_init self-fini (:933) + swallowed "
	    "error -> UAF write ==\n");
	common_setup();
	bios = kzalloc(BIOS_SZ, GFP_KERNEL);
	bios_common(bios);
	bios_clock(bios, 0, 30000, 50000, 3);
	bios_nonclock(bios, 0, 0x0001, 0);
	bios_state(bios, 0, 0, 0);

	/* full rs780_dpm_init replica with the IntegratedSystemInfo header
	 * ABSENT (atom_parse_data_header returns false at rs780_dpm.c:871) */
	ret = rs780_dpm_init(&RDEV, 0);
	printf("rs780_dpm_init() = %d (igp header missing; init called "
	    "radeon_dpm_fini itself at :933)\n", ret);
	printf("after init: dpm.ps=%p priv=%p boot_ps=%p (all dangling)\n",
	    (void *)RDEV.pm.dpm.ps, RDEV.pm.dpm.priv, (void *)RDEV.pm.dpm.boot_ps);
	/* radeon_pm.c:1448 DISCARDS the -EINVAL and continues to :1453 */
	printf("radeon_pm.c:1448 swallows the error -> rs780_get_pm_mode_"
	    "parameters writes the FREED igp_power_info ...\n");
	fflush(stdout);
	rs780_get_pm_mode_parameters(&RDEV);   /* pi->crtc_id UAF WRITE */
	check_freed_written("igp_power_info (dpm.priv)", RDEV.pm.dpm.priv);
	/* then enable faults on the freed ps array */
	printf("continuing into rs780_dpm_enable() (boot_ps/ps also freed) "
	    "...\n");
	fflush(stdout);
	ret = rs780_dpm_enable(&RDEV);
	printf("rs780_dpm_enable() returned %d\n", ret);
	printf("UAF WRITE detection result reported above%s\n",
#ifdef FIX_DF4494
	    " (fix active: init no longer tears the state down)");
#else
	    "");
#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)" :
	    "abort");
	fflush(stdout);
	_exit(99);
}

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

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

	if (!strcmp(mode, "4492"))		mode_4492();
	else if (!strcmp(mode, "4492g"))	mode_4492g();
	else if (!strcmp(mode, "4493"))		mode_4493();
	else if (!strcmp(mode, "4493b"))	mode_4493b();
	else if (!strcmp(mode, "4494"))		mode_4494();
	else if (!strcmp(mode, "4494u"))	mode_4494u();
	else if (!strcmp(mode, "4494w"))	mode_4494w();
	else { printf("unknown mode %s\n", mode); return 2; }

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