/*
 * DF-3953 — radeon kv_dpm.c boot_ps NULL dereference (SIMULATION HARNESS)
 * ==========================================================================
 * Target: sys/dev/drm/radeon/kv_dpm.c  (radeon KV/Kaveri DPM, pass 2)
 *
 * Chain (source-certain, hardware-gated):
 *   kv_parse_pplib_non_clock_info() kv_dpm.c:2598-2601 only assigns
 *     rdev->pm.dpm.boot_ps for a state with ATOM_PPLIB_CLASSIFICATION_BOOT.
 *   A VBIOS PowerPlayInfo table that parses (kv_parse_power_table -> 0) but
 *     contains no BOOT-classified state leaves boot_ps == NULL.
 *   radeon_pm_init_dpm() radeon_pm.c:1448 *discards* kv_dpm_init()'s return
 *     value and proceeds to radeon_dpm_enable() == kv_dpm_enable() anyway.
 *   kv_dpm_enable() kv_dpm.c:1277 / kv_dpm_disable() kv_dpm.c:1326 call
 *     kv_update_current_ps(rdev, rdev->pm.dpm.boot_ps) with boot_ps == NULL
 *     -> kv_get_ps(NULL) kv_dpm.c:239-244 dereferences rps->ps_priv.
 *   Additional sink of the same family: kv_dpm_get_sclk() kv_dpm.c:2881
 *     dereferences pi->requested_rps.ps_priv which stays NULL whenever
 *     radeon_dpm_pick_power_state() returned NULL (radeon_pm.c:1051) and
 *     radeon_pm_compute_clocks_dpm bailed at radeon_pm.c:1075-1078; the next
 *     dce8_bandwidth_update (cik.c:9287/9327) then faults through
 *     requested_state->levels[0].
 *
 * This harness replicates the exact struct layout + dereference arithmetic in
 * userland: a forked child performs the verbatim `NULL + offsetof` read and
 * must die with SIGSEGV at exactly the address the kernel would page-fault on.
 * It is an arithmetic demonstration, NOT a kernel reproduction.
 *
 * Build: cc -O2 -Wall -o trigger_df3953 trigger_df3953.c
 * Run:   ./trigger_df3953           (analysis only, exits 0)
 *        ./trigger_df3953 crash     (child performs the faulting derefs)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>

/* --- verbatim-layout stand-ins (radeon.h / kv_dpm.h) ------------------- */
struct radeon_ps {			/* ps_priv is the last-ish member */
	unsigned long	pad0[8];	/* misc radeon_ps fields */
	void		*ps_priv;	/* kv_get_ps() reads this   */
};

struct kv_ps {
	struct { unsigned long sclk; unsigned char pad[8]; } levels[5];
	unsigned long num_levels;
};

static struct radeon_ps  boot_ps_null;	/* stays NULL-ps_priv */
static struct kv_ps	    current_ps;

/* kv_get_ps() — kv_dpm.c:239-244 */
static struct kv_ps *kv_get_ps(struct radeon_ps *rps)
{
	return rps->ps_priv;		/* faults when rps == NULL */
}

/* kv_update_current_ps() — kv_dpm.c:1135-1144 (volatile sink so the
 * compiler cannot eliminate the faulting load, unlike a dead store) */
static struct kv_ps *volatile observed_ps;
static void kv_update_current_ps(struct radeon_ps *rps)
{
	struct kv_ps *new_ps = kv_get_ps(rps);	/* <-- the deref of NULL */
	observed_ps = new_ps;			/* observable, not eliminable */
}

/* kv_dpm_get_sclk() low branch — kv_dpm.c:2878-2887 (ps_priv NULL window) */
static unsigned long kv_dpm_get_sclk_pspriv_null(struct radeon_ps *requested_rps)
{
	struct kv_ps *requested_state = kv_get_ps(requested_rps);
	return requested_state->levels[0].sclk;
}

int main(int argc, char **argv)
{
	int crash = (argc > 1 && !strcmp(argv[1], "crash"));
	size_t off_ps_priv = offsetof(struct radeon_ps, ps_priv);
	size_t off_lv0     = offsetof(struct kv_ps, levels[0]);

	printf("DF-3953 radeon kv_dpm boot_ps NULL family (simulation)\n");
	printf("offsetof(struct radeon_ps, ps_priv)          = %zu\n", off_ps_priv);
	printf("kv_get_ps(NULL) reads *(void **)(NULL + %zu)\n", off_ps_priv);
	printf("kv_dpm_enable (:1277)  -> kv_update_current_ps(rdev, boot_ps=NULL)\n");
	printf("kv_dpm_disable (:1326) -> kv_update_current_ps(rdev, boot_ps=NULL)\n");
	printf("kv_dpm_get_sclk (:2881) reads *(kv_ps **)(NULL + %zu).levels[0] @ +%zu\n",
	       off_ps_priv, off_lv0);
	printf("kernel fault address for kv_get_ps(NULL): 0x%zx (NULL page, always unmapped)\n",
	       off_ps_priv);

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

	/* Child 1: kv_dpm_enable() end-of-enable NULL deref */
	pid_t p1 = fork();
	if (p1 == 0) {
		printf("child1: executing kv_update_current_ps(boot_ps == NULL) ...\n");
		fflush(stdout);
		boot_ps_null.ps_priv = NULL;
		kv_update_current_ps(NULL);		/* the real bug: rps == NULL */
		printf("child1: NOT REACHED (observed_ps=%p)\n", (void *)observed_ps);
		_exit(0);
	}

	/* Child 2: kv_dpm_get_sclk() ps_priv-NULL window (pick_power_state==NULL) */
	pid_t p2 = fork();
	if (p2 == 0) {
		printf("child2: executing kv_dpm_get_sclk with requested_rps == NULL ...\n");
		fflush(stdout);
		unsigned long s = kv_dpm_get_sclk_pspriv_null(NULL);
		printf("child2: NOT REACHED (sclk=%lu)\n", s);
		_exit(0);
	}

	int st, rc = 0;
	waitpid(p1, &st, 0);
	printf("child1 (kv_get_ps(NULL) @ :239-244 via :1277): %s",
	       WIFSIGNALED(st) ? "SIGNALLED" : "exited");
	if (WIFSIGNALED(st)) printf(" sig=%d", WTERMSIG(st));
	printf(" -> kernel equivalent: page fault on NULL page => panic\n");
	if (!WIFSIGNALED(st) || WTERMSIG(st) != SIGSEGV) rc = 1;

	waitpid(p2, &st, 0);
	printf("child2 (kv_dpm_get_sclk :2881): %s",
	       WIFSIGNALED(st) ? "SIGNALLED" : "exited");
	if (WIFSIGNALED(st)) printf(" sig=%d", WTERMSIG(st));
	printf(" -> kernel equivalent: page fault on NULL page => panic\n");
	if (!WIFSIGNALED(st) || WTERMSIG(st) != SIGSEGV) rc |= 2;

	printf("RESULT: %s (rc=%d)\n", rc ? "DEVIATION" : "BOTH SINKS FAULT AS PREDICTED", rc);
	return rc;
}
