/*
 * rv6xx_clkv_div0.c — harness for DF-4127 (rv6xx_dpm.c pass 2):
 *
 *   rv6xx_calculate_spread_spectrum_clk_v() (sys/dev/drm/radeon/rv6xx_dpm.c:534-542)
 *   divides by `5375 * ((vco_freq * 10) / (4096 >> fb_divider_scale))`.
 *   The inner division TRUNCATES: for a small VCO (attacker-influenced via the
 *   crafted-VBIOS firmware clock tables — rdev->clock.mpll.reference_freq and
 *   the power-table MCLK values that feed the ATOM divider computation) the
 *   inner quotient is 0, making the whole divisor 0 -> u32 divide-by-zero
 *   (kernel #DE -> panic; userland SIGFPE here). This is the rv6xx instance
 *   of the "second-division truncation div0" family (DF-4120 cypress twin);
 *   distinct from DF-1392 which covers zero SCLK and zero ss.rate.
 *
 *   The sibling first division `vco_freq / ref_freq` (:538) traps the same
 *   way when the passed ref_freq == 0 (reference_freq 0 in crafted firmware
 *   info / ref_div+1 wrap); demonstrated as case B.
 *
 * The function below is VERBATIM (u32 == uint32_t, radeon types stripped).
 * Run:  ./clkv_div0 {a|b} [-p]     -p = proposed zero-divisor guard
 * Exit: 77 = SIGFPE taken (division by zero confirmed), 0 = guarded, 70 = err
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>

typedef uint32_t u32;

/* VERBATIM rv6xx_dpm.c:534-542 */
static inline u32 rv6xx_calculate_spread_spectrum_clk_v(u32 vco_freq, u32 ref_freq,
							u32 ss_rate, u32 ss_percent,
							u32 fb_divider_scale)
{
	u32 fb_divider = vco_freq / ref_freq;

	return (ss_percent * ss_rate * 4 * (fb_divider * fb_divider) /
		(5375 * ((vco_freq * 10) / (4096 >> fb_divider_scale))));
}

static int patched = 0;

/* fixed variant: guard both divisions */
static inline u32 fixed_clk_v(u32 vco_freq, u32 ref_freq,
			      u32 ss_rate, u32 ss_percent, u32 fb_divider_scale)
{
	u32 inner, fb_divider;

	if (ref_freq == 0)
		return 0;
	fb_divider = vco_freq / ref_freq;
	inner = (vco_freq * 10) / (4096 >> fb_divider_scale);
	if (inner == 0)
		return 0;
	return (ss_percent * ss_rate * 4 * (fb_divider * fb_divider) /
		(5375 * inner));
}

static void fpe_handler(int sig, siginfo_t *si, void *uc)
{
	(void)uc; (void)sig; (void)si;
	printf("OOB confirmed: SIGFPE (integer divide by zero) in verbatim "
	       "rv6xx_calculate_spread_spectrum_clk_v\n");
	printf("RESULT: VULNERABLE — divide-by-zero taken (kernel analog: #DE panic) (exit 77)\n");
	_exit(77);
}

int main(int argc, char **argv)
{
	struct sigaction sa;
	u32 r;

	if (argc < 2) { fprintf(stderr, "usage: %s {a|b} [-p]\n", argv[0]); return 70; }
	if (argc > 2 && !strcmp(argv[2], "-p")) patched = 1;

	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = fpe_handler;
	sa.sa_flags = SA_SIGINFO;
	if (sigaction(SIGFPE, &sa, NULL)) { perror("sigaction"); return 70; }
	setvbuf(stdout, NULL, _IONBF, 0);

	{
		/* volatile: defeat constant folding of the division (gcc folds
		 * literal 0/0 as UB); forces a real runtime division */
		volatile u32 vvco = 200, vref = 10000, vrate = 30, vpct = 2, vscale = 1;
		volatile u32 zvco = 200, zref = 0;
		(void)vvco; (void)vref; (void)vrate; (void)vpct; (void)vscale;
		(void)zvco; (void)zref;
	if (argv[1][0] == 'a') {
		/* Case A (the DF-4120-twin "second division"): nonzero
		 * ref_freq (10000 = 100 MHz in 10 kHz units, plausible mpll
		 * reference), nonzero ss.rate (30) / ss.percentage (2) —
		 * DF-1392 conditions explicitly NOT used — but small VCO
		 * (200 * 10 kHz = 2 MHz), fb_divider_scale = 1 (RV670+):
		 * (200*10)/(4096>>1) = 2000/2048 = 0 -> divisor 0. */
		printf("[DF-4127 case A] vco=%u ref=%u ss_rate=%u ss_pct=%u scale=1 -> "
		       "inner (vco*10)/2048 = %u\n", 200u, 10000u, 30u, 2u,
		       (200u * 10u) / 2048u);
		r = patched ? fixed_clk_v(vvco, vref, vrate, vpct, vscale)
			    : rv6xx_calculate_spread_spectrum_clk_v(vvco, vref, vrate, vpct, vscale);
		printf("clk_v = %u (no trap)\n", r);
	} else {
		/* Case B (sibling first division at :538): ref_freq == 0 */
		printf("[DF-4127 case B] ref_freq = 0 -> vco/ref_freq traps at rv6xx_dpm.c:538\n");
		r = patched ? fixed_clk_v(zvco, zref, vrate, vpct, vscale)
			    : rv6xx_calculate_spread_spectrum_clk_v(zvco, zref, vrate, vpct, vscale);
		printf("clk_v = %u (no trap)\n", r);
	}
	}
	if (patched)
		printf("RESULT: OK — zero-divisor guard returned without trapping\n");
	return 0;
}
