/*
 * DF-3653 harness — userspace model of the NULL-stream deref in
 * amdgpu_dm_atomic_commit_tail() -> update_stream_scaling_settings().
 *
 * Kernel path (sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c):
 *   4861  for_each_oldnew_connector_in_state(...)
 *   4864      acrtc = to_amdgpu_crtc(dm_new_con_state->base.crtc);
 *   4873      if (!acrtc || drm_atomic_crtc_needs_modeset(new_crtc_state)) continue;
 *   4877      if (!is_scaling_state_different(...)) continue;
 *   4882      update_stream_scaling_settings(&...crtc->mode, dm_state,
 *   4883                                     dm_new_crtc_state->stream);   <-- NULL
 *   4885      if (!dm_new_crtc_state->stream) continue;   <-- guard is TOO LATE
 *   2479      dst.width = stream->timing.h_addressable;   <-- NULL DEREF
 *
 * Struct prefixes below replicate the exact field order of
 * dc_stream_state (dc/dc_stream.h: sink, timing, ...) and dc_crtc_timing
 * (dc/dc_hw_types.h:717: h_total, h_border_left, h_addressable, ...) so the
 * faulting address equals the kernel's: offset 0x10 on amd64.
 *
 * Expected: SIGSEGV reading address 0x10 — the same page-0 fault the kernel
 * takes (panic), because no page is mapped at that address in the kernel or
 * here.
 */
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>

/* verbatim prefix of struct dc_crtc_timing, dc/dc_hw_types.h:717 */
struct dc_crtc_timing {
	uint32_t h_total;
	uint32_t h_border_left;
	uint32_t h_addressable;
	uint32_t h_border_right;
	uint32_t h_front_porch;
	uint32_t h_sync_width;
	uint32_t v_total;
	uint32_t v_border_top;
	uint32_t v_addressable;
	uint32_t v_border_bottom;
};

/* verbatim prefix of struct dc_stream_state, dc/dc_stream.h */
struct dc_sink;
struct dc_stream_state {
	struct dc_sink *sink;
	struct dc_crtc_timing timing;
};

struct rect { int x, y, width, height; };

/* models amdgpu_dm.c:2463-2516 (update_stream_scaling_settings) */
static void update_stream_scaling_settings(const int *mode,
					   const int *underscan_enable,
					   struct dc_stream_state *stream)
{
	struct rect src = {0}, dst = {0};

	if (!mode)	/* `if (!mode) return;` — in-kernel `mode` is
			 * &crtc->mode, an embedded struct: NEVER NULL */
		return;

	src.width  = mode[0];			/* hdisplay */
	src.height = mode[1];			/* vdisplay */
	dst.width  = stream->timing.h_addressable;	/* amdgpu_dm.c:2479  <-- */
	dst.height = stream->timing.v_addressable;	/* amdgpu_dm.c:2480  <-- */

	printf("unreachable (kernel would have faulted): dst=%dx%d\n",
	       dst.width, dst.height);
}

int main(void)
{
	/*
	 * Commit N+3 state (see VERDICT.md):
	 *   connector attached to CRTC (enable=1, active=0), mode set,
	 *   needs_modeset == false, is_scaling_state_different == true,
	 *   dm_new_crtc_state->stream == NULL (never created: active==0).
	 */
	int crtc_mode[2] = { 1920, 1080 };
	int underscan = 1;
	struct dc_stream_state *dm_new_crtc_state_stream = NULL;

	printf("offsetof(dc_stream_state, timing.h_addressable) = 0x%zx\n",
	       offsetof(struct dc_stream_state, timing.h_addressable));
	printf("commit-tail scaling loop: acrtc!=NULL, needs_modeset=0, "
	       "scaling_changed=1, stream=NULL\n");
	printf("-> update_stream_scaling_settings(mode, dm_state, stream=NULL)\n");
	fflush(stdout);

	update_stream_scaling_settings(crtc_mode, &underscan,
					dm_new_crtc_state_stream);

	printf("NOT REACHED\n");
	return 0;
}
