/*
 * DF-3988 trigger.c — model of dc_link_dp_set_test_pattern's pipe scan
 * (dc_link_dp.c:2605-2610) dereferencing pipes[i].stream->sink without a
 * NULL check, plus the wrong-object blank at :2653.
 *
 * HARDWARE PRECONDITION (absent on audit guest): amdgpu DC + DP sink that
 * raises DEVICE_SERVICE_IRQ.AUTOMATED_TEST (link or PHY test request).
 * NO ACTIVE STREAM IS REQUIRED ON THE TESTED LINK:
 *   dc_link_handle_hpd_irq :2045 -> handle_automated_test :1990/:1994
 *   -> dp_test_send_link_test_pattern / dp_test_send_phy_test_pattern
 *   -> dc_link_dp_set_test_pattern :2588
 *      :2595 pipes = link->dc->current_state->res_ctx.pipe_ctx;
 *      :2605 for (i = 0; i < MAX_PIPES; i++)
 *      :2606     if (pipes[i].stream->sink->link == link)   <-- NULL deref
 * pipe_ctx[].stream is NULL for every inactive pipe; after boot with no
 * display commit, or after any configuration that leaves holes before the
 * matching pipe (e.g. monitor unplug leaves stream on pipe1, pipe0 NULL),
 * the very first hole dereferences NULL->sink.
 * Also :2653 pipes->stream_res.stream_enc->funcs->dp_blank(...) uses
 * pipes[0] instead of the matched pipe_ctx (wrong object; NULL deref when
 * pipe0 has no stream_enc).
 */
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

#define MAX_PIPES 6

struct sink { int link; };
struct stream_s { struct sink *sink; };
struct pipe_m { struct stream_s *stream; int has_enc; };

static struct sink sinks[2] = { { .link = 7 }, { .link = 9 } };
/* hole replica: pipe0 = no stream (post-unplug hole), pipe1 = stream on
 * link 7 (the tested link), rest empty */
static struct pipe_m pipes[MAX_PIPES] = {
	{ .stream = NULL,          .has_enc = 0 },
	{ .stream = NULL /*fake*/, .has_enc = 1 },
};

int main(void)
{
	struct pipe_m *pipe_ctx;
	int i, matched = -1;

	/* patch in the fake stream after static init (avoid const issues) */
	static struct stream_s s1 = { .sink = &sinks[0] };
	pipes[1].stream = &s1;

	printf("DF-3988 model: dc_link_dp.c:2605-2610 pipe scan\n");
	printf("  link under test = %d; pipes[0].stream = NULL, "
	    "match at pipes[1]\n", sinks[0].link);

	/* exact loop shape of :2605-2610 */
	for (i = 0; i < MAX_PIPES; i++) {
		printf("  i=%d pipes[%d].stream=%p -> ...->sink->link: ",
		    i, i, (void *)pipes[i].stream);
		if (pipes[i].stream == NULL) {
			printf("NULL DEREF (panic) "
			    "[KERNEL: pipes[i].stream->sink]\n");
			return (0);
		}
		if (pipes[i].stream->sink->link == sinks[0].link) {
			matched = i;
			printf("match\n");
			break;
		}
		printf("no match\n");
	}
	pipe_ctx = &pipes[matched >= 0 ? matched : 0];

	/* :2653 uses pipes[0] (not pipe_ctx) for the blank */
	if (!pipes[0].has_enc)
		printf("  :2653 pipes->stream_res.stream_enc == NULL "
		    "-> second NULL DEREF (wrong-object access)\n");
	(void)pipe_ctx;
	printf("TRIGGER_RC=0 (model OK; hardware-gated on this guest: "
	    "no AMD GPU)\n");
	return (0);
}
