/*
 * DF-4582 — verbatim extraction of the vulnerable rs690 fixed-point math.
 *
 * Contents (each copied byte-identical from the DragonFly tree):
 *   - fixed20_12 dfixed_* macros ........ sys/dev/drm/include/drm/drm_fixed.h:30-70
 *   - do_div ............................. sys/dev/drm/include/linux/math64.h:36-42
 *   - struct rs690_watermark ............. sys/dev/drm/radeon/rs690.c:257-268
 *   - rs690_crtc_bandwidth_compute() ..... sys/dev/drm/radeon/rs690.c:270-457
 *
 * The only changes are the surrounding stub structs (rdev/crtc fields the
 * function reads), collected here so the file compiles standalone in
 * userland and as a DragonFly KLD.  The FUNCTION BODY is untouched.
 *
 * df4582_scenario selects the attacker-controlled input:
 *   0 : mode->clock = 1048576 kHz (2^20)  — the DRM_IOCTL_MODE_SETCRTC wrap
 *       (div-by-zero at rs690.c:329, pclk = dfixed_div(1000, 0))
 *   1 : VBIOS igp_sideport_mclk = 0 with sideport enabled
 *       (div-by-zero at rs690.c:372, DF-4583)
 *   2 : VBIOS default_sclk = 0  =>  rdev->pm.sclk == 0
 *       (div-by-zero at rs690.c:388, DF-4583)
 */
#ifndef DF4582_BODY_H
#define DF4582_BODY_H

#include <stdint.h>
#ifndef KERNEL
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#define KPRINTF(...) do { fprintf(stderr, __VA_ARGS__); fflush(stderr); } while (0)
#else
#include <sys/systm.h>
#include <sys/types.h>
#define KPRINTF kprintf
#endif
typedef uint32_t u32;
typedef uint64_t u64;
typedef uint8_t  u8;
typedef uint16_t u16;

#define DRM_ERROR(fmt, ...) KPRINTF("DF4582-DRM_ERROR: " fmt, ##__VA_ARGS__)

/* ---- verbatim: sys/dev/drm/include/linux/math64.h:36-42, upper/lower_32_bits
 *      from linux/kernel.h:132-133 (must precede dfixed_div, exactly as
 *      math64.h is included before drm_fixed.h in the kernel build) ---- */
#define do_div(n, base) ({			\
	uint32_t __base = (base);		\
	uint32_t __rem;				\
	__rem = ((uint64_t)(n)) % __base;	\
	(n) = ((uint64_t)(n)) / __base;		\
	__rem;					\
})
#define upper_32_bits(n)	((u32)(((n) >> 16) >> 16))
#define lower_32_bits(n)	((u32)(n))
/* ---- end verbatim helpers ---- */

/* ---- verbatim: sys/dev/drm/include/drm/drm_fixed.h:30-70 ---- */
typedef union dfixed {
	u32 full;
} fixed20_12;

#define dfixed_const(A) (u32)(((A) << 12))/*  + ((B + 0.000122)*4096)) */
#define dfixed_const_half(A) (u32)(((A) << 12) + 2048)
#define dfixed_const_666(A) (u32)(((A) << 12) + 2731)
#define dfixed_const_8(A) (u32)(((A) << 12) + 3277)
#define dfixed_mul(A, B) ((u64)((u64)(A).full * (B).full + 2048) >> 12)
#define dfixed_init(A) { .full = dfixed_const((A)) }
#define dfixed_init_half(A) { .full = dfixed_const_half((A)) }
#define dfixed_trunc(A) ((A).full >> 12)
#define dfixed_frac(A) ((A).full & ((1 << 12) - 1))

static inline u32 dfixed_floor(fixed20_12 A)
{
	u32 non_frac = dfixed_trunc(A);

	return dfixed_const(non_frac);
}

static inline u32 dfixed_ceil(fixed20_12 A)
{
	u32 non_frac = dfixed_trunc(A);

	if (A.full > dfixed_const(non_frac))
		return dfixed_const(non_frac + 1);
	else
		return dfixed_const(non_frac);
}

/*
 * The KERNEL build marks dfixed_div noinline so gcc cannot UB-fuse the
 * inner (by-constant) and outer divisions — the SHIPPED radeon.ko code
 * (objdump 0x5ac39-0x5ac61) keeps them separate: truncating shl $0xc,
 * magic-mul for the inner, then a plain runtime `div %rcx` by the
 * possibly-zero result.  noinline only prevents an optimization the
 * real build also happened not to perform; the arithmetic is unchanged.
 */
#ifdef KERNEL
__attribute__((noinline)) static u32 dfixed_div(fixed20_12 A, fixed20_12 B)
#else
static inline u32 dfixed_div(fixed20_12 A, fixed20_12 B)
#endif
{
	u64 tmp = ((u64)A.full << 13);

	do_div(tmp, B.full);
	tmp += 1;
	tmp /= 2;
	return lower_32_bits(tmp);
}
/* ---- end drm_fixed.h verbatim ---- */

/*
 * Stub structs: same field names/types the real function reaches through.
 * (struct radeon_device / radeon_crtc carry hundreds of fields; only the
 * ones touched by rs690_crtc_bandwidth_compute are reproduced here.)
 */
enum radeon_family { CHIP_RS690 = 1, CHIP_RS780, CHIP_RS880 };
enum { PM_METHOD_PROFILE, PM_METHOD_DPM };
enum rmx_type_t { RMX_OFF, RMX_FULL, RMX_CENTER, RMX_ASPECT };
struct df4582_mode {
	int clock;		/* in kHz — exactly like drm_modes.h:278 */
	u32 hdisplay, hsync_start, hsync_end, htotal;
	u32 vdisplay, vsync_start, vsync_end, vtotal;
	u32 crtc_hdisplay, crtc_hsync_start, crtc_hsync_end, crtc_htotal;
	u32 crtc_vdisplay, crtc_vsync_start, crtc_vsync_end, crtc_vtotal;
};
struct df4582_pm {
	u32 current_sclk;		/* 10 kHz units */
	fixed20_12 sclk;
	fixed20_12 igp_sideport_mclk;
	fixed20_12 igp_system_mclk;
	fixed20_12 sideport_bandwidth;
	fixed20_12 k8_bandwidth;
	fixed20_12 ht_bandwidth;
	int pm_method;
	int dpm_enabled;
};
struct df4582_mc { int igp_sideport_enabled; };
struct df4582_rdev {
	enum radeon_family family;
	struct df4582_pm pm;
	struct df4582_mc mc;
};
struct df4582_crtc {
	struct { int enabled; struct df4582_mode mode; } base;
	fixed20_12 vsc, hsc;
	enum rmx_type_t rmx_type;
};

#define radeon_device  df4582_rdev
#define radeon_crtc    df4582_crtc
#define drm_display_mode df4582_mode

/* ---- verbatim: sys/dev/drm/radeon/rs690.c:257-268 ---- */
struct rs690_watermark {
	u32        lb_request_fifo_depth;
	fixed20_12 num_line_pair;
	fixed20_12 estimated_width;
	fixed20_12 worst_case_latency;
	fixed20_12 consumption_rate;
	fixed20_12 active_time;
	fixed20_12 dbpp;
	fixed20_12 priority_mark_max;
	fixed20_12 priority_mark;
	fixed20_12 sclk;
};

/* guard under test — compiled in only with -DDF4582_FIXED (mirrors fix.diff) */
#ifdef DF4582_FIXED
#define DF4582_GUARD_ZERO(x, tag) do { if ((x) == 0) { \
	KPRINTF("DF4582-FIX: zero divisor avoided at %s — bailing with safe watermark\n", tag); \
	wm->lb_request_fifo_depth = 4; \
	return; \
} } while (0)
#define DF4582_GUARD_ZERO_KEEP(x, tag) do { if ((x) == 0) { \
	KPRINTF("DF4582-FIX: zero mclk avoided at %s — using UMA latency\n", tag); \
} } while (0)
#endif

/* ---- verbatim: sys/dev/drm/radeon/rs690.c:270-457 (body UNCHANGED) ---- */
static void rs690_crtc_bandwidth_compute(struct radeon_device *rdev,
					 struct radeon_crtc *crtc,
					 struct rs690_watermark *wm,
					 int low)
{
	struct drm_display_mode *mode = &crtc->base.mode;
	fixed20_12 a, b, c;
	fixed20_12 pclk, request_fifo_depth, tolerable_latency, estimated_width;
	fixed20_12 consumption_time, line_time, chunk_time, read_delay_latency;
	fixed20_12 sclk, core_bandwidth, max_bandwidth;
	u32 selected_sclk;

	bzero(wm, sizeof(*wm));	/* avoid gcc warning */
	if (!crtc->base.enabled) {
		/* FIXME: wouldn't it better to set priority mark to maximum */
		wm->lb_request_fifo_depth = 4;
		return;
	}

	if (((rdev->family == CHIP_RS780) || (rdev->family == CHIP_RS880)) &&
	    (rdev->pm.pm_method == PM_METHOD_DPM) && rdev->pm.dpm_enabled)
		selected_sclk = 0; /* radeon_dpm_get_sclk(rdev, low); not reached */
	else
		selected_sclk = rdev->pm.current_sclk;

	/* sclk in Mhz */
	a.full = dfixed_const(100);
	sclk.full = dfixed_const(selected_sclk);
	sclk.full = dfixed_div(sclk, a);

	/* core_bandwidth = sclk(Mhz) * 16 */
	a.full = dfixed_const(16);
	core_bandwidth.full = dfixed_div(rdev->pm.sclk, a);

	if (crtc->vsc.full > dfixed_const(2))
		wm->num_line_pair.full = dfixed_const(2);
	else
		wm->num_line_pair.full = dfixed_const(1);

	b.full = dfixed_const(mode->crtc_hdisplay);
	c.full = dfixed_const(256);
	a.full = dfixed_div(b, c);
	request_fifo_depth.full = dfixed_mul(a, wm->num_line_pair);
	request_fifo_depth.full = dfixed_ceil(request_fifo_depth);
	if (a.full < dfixed_const(4)) {
		wm->lb_request_fifo_depth = 4;
	} else {
		wm->lb_request_fifo_depth = dfixed_trunc(request_fifo_depth);
	}

	/* Determine consumption rate
	 *  pclk = pixel clock period(ns) = 1000 / (mode.clock / 1000)
	 *  vtaps = number of vertical taps,
	 *  vsc = vertical scaling ratio, defined as source/destination
	 *  hsc = horizontal scaling ration, defined as source/destination
	 */
	a.full = dfixed_const(mode->clock);
	b.full = dfixed_const(1000);
	a.full = dfixed_div(a, b);
#ifdef DF4582_FIXED
	/* DF-4582 fix: (u32)(clock << 12) wraps to 0 for clock ≡ 0 (mod 2^20) */
	DF4582_GUARD_ZERO(a.full, "pclk");
#endif
	pclk.full = dfixed_div(b, a);
	if (crtc->rmx_type != RMX_OFF) {
		b.full = dfixed_const(2);
		if (crtc->vsc.full > b.full)
			b.full = crtc->vsc.full;
		b.full = dfixed_mul(b, crtc->hsc);
		c.full = dfixed_const(2);
		b.full = dfixed_div(b, c);
		consumption_time.full = dfixed_div(pclk, b);
	} else {
		consumption_time.full = pclk.full;
	}
	a.full = dfixed_const(1);
	wm->consumption_rate.full = dfixed_div(a, consumption_time);


	/* Determine line time
	 *  LineTime = total time for one line of displayhtotal
	 *  LineTime = total number of horizontal pixels
	 *  pclk = pixel clock period(ns)
	 */
	a.full = dfixed_const(crtc->base.mode.crtc_htotal);
	line_time.full = dfixed_mul(a, pclk);

	/* Determine active time
	 *  ActiveTime = time of active region of display within one line,
	 *  hactive = total number of horizontal active pixels
	 *  htotal = total number of horizontal pixels
	 */
	a.full = dfixed_const(crtc->base.mode.crtc_htotal);
	b.full = dfixed_const(crtc->base.mode.crtc_hdisplay);
	wm->active_time.full = dfixed_mul(line_time, b);
	wm->active_time.full = dfixed_div(wm->active_time, a);

	/* Maximun bandwidth is the minimun bandwidth of all component */
	max_bandwidth = core_bandwidth;
	if (rdev->mc.igp_sideport_enabled) {
		if (max_bandwidth.full > rdev->pm.sideport_bandwidth.full &&
			rdev->pm.sideport_bandwidth.full)
			max_bandwidth = rdev->pm.sideport_bandwidth;
#ifdef DF4582_FIXED
		/* DF-4583 fix: zero sideport mclk from VBIOS must not divide */
		DF4582_GUARD_ZERO_KEEP(rdev->pm.igp_sideport_mclk.full, "rdl");
#endif
		read_delay_latency.full = dfixed_const(370 * 800);
		a.full = dfixed_const(1000);
		b.full = dfixed_div(rdev->pm.igp_sideport_mclk, a);
#ifdef DF4582_FIXED
		DF4582_GUARD_ZERO(b.full, "rdl-mclk");
#endif
		read_delay_latency.full = dfixed_div(read_delay_latency, b);
		read_delay_latency.full = dfixed_mul(read_delay_latency, a);
	} else {
		if (max_bandwidth.full > rdev->pm.k8_bandwidth.full &&
			rdev->pm.k8_bandwidth.full)
			max_bandwidth = rdev->pm.k8_bandwidth;
		if (max_bandwidth.full > rdev->pm.ht_bandwidth.full &&
			rdev->pm.ht_bandwidth.full)
			max_bandwidth = rdev->pm.ht_bandwidth;
		read_delay_latency.full = dfixed_const(5000);
	}

	/* sclk = system clocks(ns) = 1000 / max_bandwidth / 16 */
	a.full = dfixed_const(16);
	sclk.full = dfixed_mul(max_bandwidth, a);
	a.full = dfixed_const(1000);
#ifdef DF4582_FIXED
	/* DF-4583 fix: all-zero VBIOS clocks must not divide */
	DF4582_GUARD_ZERO(sclk.full, "1000/sclk");
#endif
	sclk.full = dfixed_div(a, sclk);
	/* Determine chunk time
	 * ChunkTime = the time it takes the DCP to send one chunk of data
	 * to the LB which consists of pipeline delay and inter chunk gap
	 * sclk = system clock(ns)
	 */
	a.full = dfixed_const(256 * 13);
	chunk_time.full = dfixed_mul(sclk, a);
	a.full = dfixed_const(10);
	chunk_time.full = dfixed_div(chunk_time, a);

	/* Determine the worst case latency
	 * NumLinePair = Number of line pairs to request(1=2 lines, 2=4 lines)
	 * WorstCaseLatency = worst case time from urgent to when the MC starts
	 *                    to return data
	 * READ_DELAY_IDLE_MAX = constant of 1us
	 * ChunkTime = time it takes the DCP to send one chunk of data to the LB
	 *             which consists of pipeline delay and inter chunk gap
	 */
	if (dfixed_trunc(wm->num_line_pair) > 1) {
		a.full = dfixed_const(3);
		wm->worst_case_latency.full = dfixed_mul(a, chunk_time);
		wm->worst_case_latency.full += read_delay_latency.full;
	} else {
		a.full = dfixed_const(2);
		wm->worst_case_latency.full = dfixed_mul(a, chunk_time);
		wm->worst_case_latency.full += read_delay_latency.full;
	}

	/* Determine the tolerable latency
	 * TolerableLatency = Any given request has only 1 line time
	 *                    for the data to be returned
	 * LBRequestFifoDepth = Number of chunk requests the LB can
	 *                      put into the request FIFO for a display
	 *  LineTime = total time for one line of display
	 *  ChunkTime = the time it takes the DCP to send one chunk
	 *              of data to the LB which consists of
	 *  pipeline delay and inter chunk gap
	 */
	if ((2+wm->lb_request_fifo_depth) >= dfixed_trunc(request_fifo_depth)) {
		tolerable_latency.full = line_time.full;
	} else {
		tolerable_latency.full = dfixed_const(wm->lb_request_fifo_depth - 2);
		tolerable_latency.full = request_fifo_depth.full - tolerable_latency.full;
		tolerable_latency.full = dfixed_mul(tolerable_latency, chunk_time);
		tolerable_latency.full = line_time.full - tolerable_latency.full;
	}
	/* We assume worst case 32bits (4 bytes) */
	wm->dbpp.full = dfixed_const(4 * 8);

	/* Determine the maximum priority mark
	 *  width = viewport width in pixels
	 */
	a.full = dfixed_const(16);
	wm->priority_mark_max.full = dfixed_const(crtc->base.mode.crtc_hdisplay);
	wm->priority_mark_max.full = dfixed_div(wm->priority_mark_max, a);
	wm->priority_mark_max.full = dfixed_ceil(wm->priority_mark_max);

	/* Determine estimated width */
	estimated_width.full = tolerable_latency.full - wm->worst_case_latency.full;
	estimated_width.full = dfixed_div(estimated_width, consumption_time);
	if (dfixed_trunc(estimated_width) > crtc->base.mode.crtc_hdisplay) {
		wm->priority_mark.full = dfixed_const(10);
	} else {
		a.full = dfixed_const(16);
		wm->priority_mark.full = dfixed_div(estimated_width, a);
		wm->priority_mark.full = dfixed_ceil(wm->priority_mark);
		wm->priority_mark.full = wm->priority_mark_max.full - wm->priority_mark.full;
	}
	KPRINTF("DF4582: compute completed without division (scenario reached end)\n");
}
/* ---- end verbatim function ---- */

#undef radeon_device
#undef radeon_crtc
#undef drm_display_mode

/*
 * Attacker-shaped inputs.
 */
static void df4582_setup(struct df4582_rdev *rdev, struct df4582_crtc *crtc,
			 int scenario)
{
	memset(rdev, 0, sizeof(*rdev));
	memset(crtc, 0, sizeof(*crtc));

	rdev->family = CHIP_RS690;
	rdev->pm.pm_method = PM_METHOD_PROFILE;

	/* realistic IGP numbers (all from a healthy BIOS) */
	rdev->pm.current_sclk = 40000;		     /* 400 MHz  (10 kHz)   */
	rdev->pm.sclk.full     = dfixed_const(400);   /* radeon_update_.. */
	rdev->pm.igp_system_mclk.full = dfixed_const(667);
	rdev->pm.igp_sideport_mclk.full = dfixed_const(667);
	rdev->pm.k8_bandwidth.full   = dfixed_const(400 * 4);
	rdev->pm.ht_bandwidth.full   = dfixed_const(8000 / 5);
	rdev->pm.sideport_bandwidth.full = dfixed_const(900);
	rdev->mc.igp_sideport_enabled = 0;

	crtc->base.enabled = 1;
	crtc->rmx_type = RMX_OFF;
	/* a plain 640x480@60-ish mode ... */
	crtc->base.mode.clock = 25175;		/* kHz */
	crtc->base.mode.hdisplay = 640; crtc->base.mode.htotal = 800;
	crtc->base.mode.crtc_hdisplay = 640; crtc->base.mode.crtc_htotal = 800;

	switch (scenario) {
	case 0:	/* DF-4582: pixel clock chosen by the DRM user */
		crtc->base.mode.clock = 1048576;	/* 2^20 kHz — wraps to 0 */
		break;
	case 1:	/* DF-4583: VBIOS says sideport present, boot clock 0 */
		rdev->mc.igp_sideport_enabled = 1;
		rdev->pm.igp_sideport_mclk.full = 0;
		break;
	case 2:	/* DF-4583: VBIOS ulDefaultEngineClock == 0 */
		rdev->pm.sclk.full = 0;
		rdev->pm.k8_bandwidth.full = dfixed_const(100);
		rdev->pm.ht_bandwidth.full = dfixed_const(100);
		break;
	}
}

#endif /* DF4582_BODY_H */
