/*
 * DF-3743 harness: spread-spectrum rate parsed to zero -> kernel divide-by-zero (#DE)
 *
 * Parse side (the bug), sys/dev/drm/radeon/radeon_atombios.c, radeon_atombios_get_asic_ss_info():
 *   v1 :1546  ss->rate = le16_to_cpu(ss_assign->v1.usSpreadRateInKhz);      // 0 unchecked
 *   v2 :1564-1569  ss->rate = usSpreadRateIn10Hz; if ((crev == 2) && (ENGINE||MEMORY))
 *                       ss->rate /= 100;      // 1..99 -> 0  (integer truncation)
 *   v3 :1586-1594  same /= 100 for ENGINE/MEMORY SS
 * and radeon_atombios_get_ppll_ss_info() :1390-1413 NEVER assigns ss->rate at all
 * (memset zero at :1390 stays) yet returns true.
 * All three paths can return success with ss->rate == 0.
 *
 * Sinks (integer division by ss->rate):
 *   si_dpm.c:4833  u32 clk_s = reference_clock * 5 / (reference_divider * ss.rate);
 *   ci_dpm.c:3229  identical expression
 * (sink-side filings DF-3621/DF-3647; the parse-side zero-rate is proven here)
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>

typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32;

struct radeon_atom_ss_model { u16 percentage; u8 type; u32 rate; u16 percentage_divider; };

static volatile int stage;
static void fpe_handler(int sig) { (void)sig;
	printf("SIGFPE (#DE) at stage %d — kernel equivalent: divide-by-zero panic\n", stage);
	_exit(42);
}

int main(void)
{
	setbuf(stdout, NULL);
	signal(SIGFPE, fpe_handler);

	/* ---- v3 parse path, verbatim arithmetic radeon_atombios.c:1580-1597 ---- */
	u16 usSpreadRateIn10Hz = 50;		/* plausible BIOS value: 0.5 kHz in 10 Hz units */
	int id = 1;				/* ASIC_INTERNAL_ENGINE_SS */
	struct radeon_atom_ss_model ss;
	memset(&ss, 0, sizeof(ss));
	ss.rate = usSpreadRateIn10Hz;
	if ((id == 1) || (id == 0))		/* ENGINE or MEMORY */
		ss.rate /= 100;		/* :1594 — 50/100 == 0 */
	printf("[v3] usSpreadRateIn10Hz=%u -> ss.rate=%u (parse returns success)\n",
	       usSpreadRateIn10Hz, ss.rate);
	if (ss.rate != 0) { printf("unexpected\n"); return 1; }

	/* ---- sink, verbatim si_dpm.c:4833 / ci_dpm.c:3229 ---- */
	u32 reference_clock = 27000, reference_divider = 2;
	volatile u32 vrate = ss.rate;			/* volatile: kernel really divides */
	printf("dividing by ss.rate=%u now (si_dpm.c:4833 / ci_dpm.c:3229)...\n", vrate);
	volatile u32 clk_s;
	stage = 1;
	clk_s = reference_clock * 5 / (reference_divider * vrate);	/* #DE */
	(void)clk_s;

	/* ---- ppll path: rate never assigned (memset 0), atombios_crtc.c consumer ---- */
	stage = 2;
	struct radeon_atom_ss_model ppll_ss;
	memset(&ppll_ss, 0, sizeof(ppll_ss));	/* radeon_atombios.c:1390 */
	ppll_ss.percentage = 22;
	/* radeon_atombios_get_ppll_ss_info (:1400-1413) assigns percentage/type/step/delay/
	 * range/refdiv only — rate stays 0 and the function returns true */
	volatile u32 vrate2 = ppll_ss.rate;		/* 0, never assigned by the parser */
	volatile u32 clk_s2;
	clk_s2 = reference_clock * 5 / (reference_divider * vrate2);	/* #DE */
	(void)clk_s2;

	printf("FAIL: no SIGFPE\n");
	return 1;
}
