/*
 * DF-3853 harness — BIOS sysclk reference_freq == 0 -> kernel divide by zero.
 *
 * Kernel source replicated 1:1 (arithmetic copied verbatim):
 *   sys/dev/drm/radeon/radeon_combios.c:758
 *        spll->reference_freq = RBIOS16(pll_info + 0x1a);   [NO zero check]
 *   (p1pll :741 and mpll :773 equally unchecked; the sibling clamps in
 *    radeon_clocks.c:194-216 only sanitize reference_div, never *_freq)
 *   sys/dev/drm/radeon/radeon_clocks.c:344-385 calc_eng_mem_clock():
 *        req_clock /= (2 * spll->reference_freq);   <- divide by zero
 *   sys/dev/drm/radeon/radeon_pm.c:224 radeon_set_engine_clock(rdev, sclk)
 *        -> radeon_legacy_set_engine_clock (radeon_asic.c:253,... pre-AVIVO)
 *        -> calc_eng_mem_clock
 *
 * On hardware this is a 32-bit integer division by zero -> #DE -> kernel
 * panic. In userspace the identical arithmetic raises SIGFPE (core dumped).
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

static uint8_t bios[512];                       /* crafted COMBIOS image */
#define RBIOS8(i)  (bios[i])
#define RBIOS16(i) (RBIOS8(i) | (RBIOS8((i) + 1) << 8))

struct radeon_pll {
	uint32_t reference_freq;    /* 10 kHz units                        */
	uint32_t reference_div;
};
static struct radeon_pll spll;

/* r100-era hardware register read stub (RADEON_M_SPLL_REF_FB_DIV): a real
 * chip returns its configured ref div here; pick a plausible non-zero so
 * the ONLY zero denominator is the BIOS-controlled reference_freq. */
static uint32_t RREG32_PLL(uint32_t reg) { (void)reg; return 0x0021; }

/* ---- radeon_combios.c:725-784 radeon_combios_get_clock_info() ------- */
static void combios_get_clock_info(uint16_t pll_info)
{
	spll.reference_freq = RBIOS16(pll_info + 0x1a);   /* :758 — unchecked */
	spll.reference_div  = RBIOS16(pll_info + 0x1c);   /* :759             */
	printf("[combios] spll->reference_freq=%u spll->reference_div=%u "
	       "(no validation in parser or in radeon_get_clock_info clamp block)\n",
	       spll.reference_freq, spll.reference_div);
}

/* ---- radeon_clocks.c:344-385 calc_eng_mem_clock() — verbatim -------- */
static uint32_t calc_eng_mem_clock(uint32_t req_clock, int *fb_div, int *post_div)
{
	int ref_div = spll.reference_div;

	if (!ref_div)
		ref_div = RREG32_PLL(0) & 0x3ff;      /* reg fallback */

	if (req_clock < 15000)      { *post_div = 8; req_clock *= 8; }
	else if (req_clock < 30000) { *post_div = 4; req_clock *= 4; }
	else if (req_clock < 60000) { *post_div = 2; req_clock *= 2; }
	else                          *post_div = 1;

	req_clock *= ref_div;
	req_clock += spll.reference_freq;
	printf("[calc] about to execute: req_clock /= (2 * %u)   <- denominator is ZERO\n",
	       spll.reference_freq);
	req_clock /= (2 * spll.reference_freq);          /* <-- SIGFPE / #DE   */

	*fb_div = req_clock & 0xff;
	req_clock = (req_clock & 0xffff) << 1;
	req_clock *= spll.reference_freq;
	req_clock /= ref_div;
	req_clock /= *post_div;
	return req_clock;
}

int main(void)
{
	uint16_t pll_info = 0x30;
	int fb, post;

	/* crafted PLL info table: sysclk reference frequency word = 0 */
	memset(bios, 0, sizeof(bios));
	bios[pll_info] = 11;                 /* rev > 10 flags consumed fine  */
	/* bios[pll_info + 0x1a .. 0x1b] stay 0 -> reference_freq == 0 */
	bios[pll_info + 0x1c] = 0x0c;         /* reference_div = 12 (valid)    */

	printf("== DF-3853: crafted COMBIOS PLL table, sysclk reference_freq = 0 ==\n");
	combios_get_clock_info(pll_info);
	fflush(stdout);

	/* PM transition -> radeon_set_engine_clock -> calc_eng_mem_clock:
	 * same division the kernel executes on a mode/PM clock change.      */
	printf("[pm] radeon_legacy_set_engine_clock -> calc_eng_mem_clock:\n");
	uint32_t out = calc_eng_mem_clock(25000, &fb, &post);
	printf("unreachable: 0x%x\n", out);
	return 0;
}
