/*
 * DF-4696 — trigger + arithmetic proof for divide-by-zero in
 * sys/dev/drm/amd/display/dc/dcn10/dcn10_dpp_dscl.c:439-441
 * (dpp1_dscl_calc_lb_num_partitions).
 *
 * SINK (kernel, my audit file):
 *   dcn10_dpp_dscl.c:411-412  line_size = min(viewport.width, recout.width)
 *   dcn10_dpp_dscl.c:416      memory_line_size_y = (line_size*lb_bpc + 71)/72
 *   dcn10_dpp_dscl.c:439      *num_part_y = lb_memory_size / memory_line_size_y
 *                              ^^^^^^^^^^^^^^^^^^^^ 816 / 0 -> #DE -> panic
 *   (also :440 via memory_line_size_c and :441 via memory_line_size_a,
 *    same line_size root cause)
 *
 * PRODUCER CHAIN (shared with DF-4373, every link static, same types):
 *   amdgpu_dm.c:2210-2240  fill_rects_from_plane_state: raw crtc_x/y/w/h
 *                          copied, only zero-size rejected, clip_rect=dst_rect
 *                          (plane rects NEVER clipped to the CRTC window).
 *   amdgpu_dm.c:3585-3603  dm_plane_atomic_check: fill_rects +
 *   dc_resource.c:2891-2900 dc_validate_plane: "validates pixel format only".
 *   dc_resource.c:543      viewport.width = clip.w * src.w / dst.w  (SOURCE px)
 *   dc_resource.c:1125-1127 the ONLY degeneracy guard: viewport >= 16 —
 *                          recout is computed AFTER it, never guarded.
 *   dc_resource.c:637-643  recout.width = clip.w * dst.w / src.w, then
 *                          clamped to the stream dst window; for a plane
 *                          flush off the right edge this yields EXACTLY 0.
 *   dcn10_dpp.c:141-210    dpp_get_optimal_number_of_taps: computes
 *                          pixel_width = min(viewport.w, recout.w) at :148-151
 *                          but NEVER USES IT — returns true unconditionally
 *                          (non-FP16) — the missing guard on DCN10.
 *   validation: dcn10_validate_plane (dcn10_resource.c:1131-1138) checks
 *                          video src width only; validate_bandwidth never
 *                          looks at recout. Context validates DC_OK.
 *   COMMIT: dcn10_hw_sequencer.c:2101-2104 update_dchubp_dpp (full_update)
 *                          -> :2029-2041 update_scaler
 *                          -> dpp1_dscl_set_scaler_manual_scale (dscl.c:647)
 *                          -> find_lb_memory_config (:463; use_max_lb=false,
 *                             dc kzalloc'd so dc->debug zeroed, dc.c:729-732)
 *                          -> caps->dscl_calc_lb_num_partitions (:476)
 *                          -> 816 / 0  =>  integer #DE in kernel mode.
 *
 * TRIGGER GEOMETRY (UABI-legal atomic commit, session DRM master):
 *   CRTC/mode 640x480; plane buffer 640x480 (src 640 wide);
 *   CRTC_X = 640 (== mode width: flush off the right edge),
 *   CRTC_Y = 0, CRTC_W = 1, CRTC_H = 480; format XRGB8888 (not FP16, so the
 *   DSCL_MODE_DSCL_BYPASS early-return at dscl.c:684-685 is not taken).
 *
 * This program:
 *   1. probes /dev/dri/card* (on the audit QEMU guest there is none: QEMU
 *      std VGA only, no vendor 0x1002 PCI device — amdgpu DC cannot attach);
 *   2. replays the producer+sink arithmetic 1:1 with the kernel's integer
 *      types (struct rect fields are signed int, dc_hw_types.h) and executes
 *      the exact division sequence. The attack case dies with SIGFPE
 *      (userland twin of kernel #DE/panic); the control case completes and
 *      prints the clamped partition counts.
 */
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* mirror of struct rect (sys/dev/drm/amd/display/include/dc_hw_types.h) */
struct rect { int x, y, width, height; };

/* Faithful division executor. The kernel computes
 *   lb_memory_size / memory_line_size_y      (dcn10_dpp_dscl.c:439)
 * with a runtime-unknown divisor, which compiles to a real idiv and faults
 * with #DE when the divisor is 0. A naive userland replica with the same
 * constants lets the compiler exploit the C UB of x/0 and skip the fault,
 * so we hide the divisor behind an asm barrier: the compiler must emit the
 * actual divide instruction, making SIGFPE the exact userland twin of the
 * kernel's #DE. (Proof this matters: without the barrier, gcc8 computes
 * garbage and the process survives — observed and fixed during verification.)
 */
static int kdiv(int a, int b)
{
	asm volatile("" : "+r"(b));
	return a / b;
}

static int run_case(const char *name, int crtc_x, int expect_fault)
{
	/* --- fill_rects_from_plane_state (amdgpu_dm.c:2210-2240) --- */
	struct rect src_rect   = { 0, 0, 640, 480 };  /* 640x480 buffer */
	struct rect dst_rect   = { crtc_x, 0, 1, 480 }; /* raw UABI crtc_* */
	struct rect clip_rect  = dst_rect;	      /* :2240 clip_rect = dst_rect */

	/* stream state: mode 640x480, src window == dst window == full mode */
	struct rect stream_src = { 0, 0, 640, 480 };
	struct rect stream_dst = { 0, 0, 640, 480 };

	/* --- calculate_viewport width (dc_resource.c:543): SOURCE pixels --- */
	int viewport_w = clip_rect.width * src_rect.width / dst_rect.width;

	/* --- resource_build_scaling_params guard (dc_resource.c:1125-1127) --- */
	if (viewport_w < 16) {
		printf("%s: viewport.width=%d < 16 -> build returns false "
		       "(config REJECTED, no bug)\n", name, viewport_w);
		return 0;
	}

	/* --- calculate_recout (dc_resource.c:631-657) --- */
	int recout_x = stream_dst.x;
	if (stream_src.x < clip_rect.x)
		recout_x += (clip_rect.x - stream_src.x) * stream_dst.width
				/ stream_src.width;
	int recout_w = clip_rect.width * stream_dst.width / stream_src.width;
	if (recout_w + recout_x > stream_dst.x + stream_dst.width)
		recout_w = stream_dst.x + stream_dst.width - recout_x;

	/* --- dcn10_dpp.c:148-151: pixel_width computed, then IGNORED ---
	 * (the dead variable is why validation never rejects this geometry) */
	int pixel_width = viewport_w < recout_w ? viewport_w : recout_w;
	(void)pixel_width; /* kernel computes this and never reads it */

	/* --- dpp1_dscl_calc_lb_num_partitions (dcn10_dpp_dscl.c:411-441) --- */
	int lb_bpc = 10; /* LB_PIXEL_DEPTH_30BPP, forced by
			    dcn10_hw_sequencer.c:2037 / dc_resource.c:1136 */
	int line_size = viewport_w < recout_w ? viewport_w : recout_w;
	int memory_line_size_y = (line_size * lb_bpc + 71) / 72; /* +71 to ceil */
	int memory_line_size_a = (line_size + 5) / 6;            /* +5 to ceil */
	int lb_memory_size = 816;  /* LB_MEMORY_CONFIG_1, dscl.c:422 */

	printf("%s: viewport.width=%d recout.x=%d recout.width=%d "
	       "line_size=%d memory_line_size_y=%d memory_line_size_a=%d\n",
	       name, viewport_w, recout_x, recout_w, line_size,
	       memory_line_size_y, memory_line_size_a);

	if (memory_line_size_y == 0 || memory_line_size_a == 0) {
		printf("%s: divisor is 0 -> kernel would execute "
		       "816 / 0 at dcn10_dpp_dscl.c:439 -> #DE -> panic. "
		       "Executing userland twin; SIGFPE follows:\n", name);
		fflush(stdout);
		if (!expect_fault)
			abort();
	}

	/* the exact division from dscl.c:439 (and :441) */
	volatile int num_part_y = kdiv(lb_memory_size, memory_line_size_y);
	if (num_part_y > 64)
		num_part_y = 64;		     /* dscl.c:447-448 clamp */
	printf("%s: no fault: num_part_y clamped to %d\n", name, num_part_y);
	return 0;
}

int main(int argc, char **argv)
{
	const char *path = argc > 1 ? argv[1] : "/dev/dri/card0";
	int fd = open(path, O_RDWR);

	printf("DF-4696 device probe: open(%s) = %d errno=%d (%s)\n",
	       path, fd, errno, strerror(errno));
	if (fd < 0) {
		printf("  -> no DRM device: the amdgpu DC path (which compiles\n"
		       "     dcn10_dpp_dscl.c into the amdgpu module) cannot\n"
		       "     attach on this guest; runtime trigger impossible.\n"
		       "     Proving the arithmetic chain instead.\n\n");
	} else {
		close(fd);
		printf("  -> DRM device present: run the atomic commit described\n"
		       "     in the header comment (plane flush off right edge).\n"
		       "     NOT attempted automatically (needs fb/object setup).\n\n");
	}

	/* control: plane fully on-screen -> normal path, no fault */
	run_case("control (CRTC_X=0, fully visible)", 0, 0);

	/* attack: plane flush off the right edge of a 640-wide mode */
	run_case("attack  (CRTC_X=640, flush off right edge)", 640, 1);

	printf("attack case survived?? — unexpected, chain broken\n");
	return 1;
}
