/*
 * DF-5055 replica — dce80 vupdate entries carry no .src_id, so
 * dce110_vblank_set() (which dce80's vblank_irq_info_funcs wires as .set,
 * irq_service_dce80.c:82-85,145) re-derives the source from (src_id=0, ext_id=0),
 * gets DC_IRQ_SOURCE_INVALID, and underflows pipe_offset to 178 before
 * indexing pipe_ctx[] — the DF-4933 twin on the DCE80 table wiring.
 *
 * Verbatim userland replica of:
 *   - enum dc_irq_source / enum irq_type    (irq_types.h:40-158)
 *   - struct irq_source_info / _funcs       (irq_service.h:36-70)
 *   - dce80 vupdate_int_entry / vblank_int_entry macros and the vblank
 *     funcs table                              (irq_service_dce80.c:82-85,132-163)
 *   - dce110_vblank_set                      (irq_service_dce110.c:195-219)
 *   - to_dal_irq_source_dce110               (irq_service_dce110.c:315-393)
 *   - dc_interrupt_to_irq_source             (core/dc.c:1672-1678)
 *   - find_irq_source_info / dal_irq_service_set(+generic)
 *                                            (irq_service.c:77-124)
 *   - the dc -> current_state -> res_ctx.pipe_ctx[] walk dce110_vblank_set
 *     performs, with MAX_PIPES=6 (DCE80 has 6 pipes) against a poisoned
 *     tail so the far-OOB read (pipe 178) yields a garbage tg that is
 *     immediately called through tg->funcs->arm_vert_intr(tg, 2).
 *
 * dce80's vblank_int_entry sets .src_id = VISLANDS30_IV_SRCID_D1_VERTICAL_
 * INTERRUPT0 + reg_num (19..24, consecutive — correct), so the in-tree-
 * reachable VBLANK path arms pipes 0..5 and is fine.  dce80's
 * vupdate_int_entry initializes NO .src_id (irq_service_dce80.c:132-146):
 * for DC_IRQ_SOURCE_VUPDATE1..6 the info has src_id=0/ext_id=0, the
 * re-derivation to_dal_irq_source_dce110(0,0) hits the default case and
 * returns DC_IRQ_SOURCE_INVALID (0), and
 *      uint8_t pipe_offset = dal_irq_src - IRQ_TYPE_VBLANK;        0 - 78
 *      struct timing_generator *tg =
 *          core_dc->current_state->res_ctx.pipe_ctx[pipe_offset].stream_res.tg;
 * becomes pipe_ctx[178] on a 6-entry array — the same far-OOB read + wild
 * arm_vert_intr indirect call as DF-4933 (dce110), reached through the
 * dce80 table's own macros.  (Note the defect is double-baked upstream
 * too: even a correctly remapped VUPDATE source would still underflow,
 * because the subtraction base is IRQ_TYPE_VBLANK, not IRQ_TYPE_VUPDATE.)
 *
 * Reachability is latent and stated plainly (same as DF-4933): no in-tree
 * caller ever .set's a VUPDATE source (suspend/resume touch HPD/HPDRX
 * only, amdgpu_dm_irq.c:353/380/406; vblank enable is IRQ_TYPE_VBLANK +
 * otg_inst, amdgpu_dm.c:3014-3015; pflip is IRQ_TYPE_PFLIP,
 * amdgpu_dm_irq.c:570; the IH path converts through
 * to_dal_irq_source_dce110 and amdgpu_dm never registers the V_UPDATE
 * src_ids 7,9,..,17), and the guest has no AMD GPU — the memory-safety
 * shape (OOB read feeding an indirect call) is one future caller away.
 *
 * Build: cc -O2 -Wall -std=gnu11 [-DFIX_DF5055] -o dce80_vupd dce80_vupd.c
 * Exit:  0 ok (fixed) / 98 trigger survived baseline (BAD) / signal (baseline crash)
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/mman.h>

typedef uint32_t uint32;

#define DC_LOG_ERROR(fmt, ...) printf("DC_IRQ_ERR: " fmt, ##__VA_ARGS__)
#define DC_ERROR(fmt, ...) printf("DC_ERROR: " fmt, ##__VA_ARGS__)

/* ---- enum dc_irq_source — irq_types.h:40-148 verbatim ---- */
enum dc_irq_source {
	DC_IRQ_SOURCE_INVALID = 0,
	DC_IRQ_SOURCE_HPD1, DC_IRQ_SOURCE_HPD2, DC_IRQ_SOURCE_HPD3,
	DC_IRQ_SOURCE_HPD4, DC_IRQ_SOURCE_HPD5, DC_IRQ_SOURCE_HPD6,
	DC_IRQ_SOURCE_HPD1RX, DC_IRQ_SOURCE_HPD2RX, DC_IRQ_SOURCE_HPD3RX,
	DC_IRQ_SOURCE_HPD4RX, DC_IRQ_SOURCE_HPD5RX, DC_IRQ_SOURCE_HPD6RX,
	DC_IRQ_SOURCE_I2C_DDC1, DC_IRQ_SOURCE_I2C_DDC2, DC_IRQ_SOURCE_I2C_DDC3,
	DC_IRQ_SOURCE_I2C_DDC4, DC_IRQ_SOURCE_I2C_DDC5, DC_IRQ_SOURCE_I2C_DDC6,
	DC_IRQ_SOURCE_DPSINK1, DC_IRQ_SOURCE_DPSINK2, DC_IRQ_SOURCE_DPSINK3,
	DC_IRQ_SOURCE_DPSINK4, DC_IRQ_SOURCE_DPSINK5, DC_IRQ_SOURCE_DPSINK6,
	DC_IRQ_SOURCE_TIMER,
	DC_IRQ_SOURCE_PFLIP_FIRST,
	DC_IRQ_SOURCE_PFLIP1 = DC_IRQ_SOURCE_PFLIP_FIRST,
	DC_IRQ_SOURCE_PFLIP2, DC_IRQ_SOURCE_PFLIP3,
	DC_IRQ_SOURCE_PFLIP4, DC_IRQ_SOURCE_PFLIP5, DC_IRQ_SOURCE_PFLIP6,
	DC_IRQ_SOURCE_PFLIP_UNDERLAY0,
	DC_IRQ_SOURCE_PFLIP_LAST = DC_IRQ_SOURCE_PFLIP_UNDERLAY0,
	DC_IRQ_SOURCE_GPIOPAD0, DC_IRQ_SOURCE_GPIOPAD1, DC_IRQ_SOURCE_GPIOPAD2,
	DC_IRQ_SOURCE_GPIOPAD3, DC_IRQ_SOURCE_GPIOPAD4, DC_IRQ_SOURCE_GPIOPAD5,
	DC_IRQ_SOURCE_GPIOPAD6, DC_IRQ_SOURCE_GPIOPAD7, DC_IRQ_SOURCE_GPIOPAD8,
	DC_IRQ_SOURCE_GPIOPAD9, DC_IRQ_SOURCE_GPIOPAD10, DC_IRQ_SOURCE_GPIOPAD11,
	DC_IRQ_SOURCE_GPIOPAD12, DC_IRQ_SOURCE_GPIOPAD13, DC_IRQ_SOURCE_GPIOPAD14,
	DC_IRQ_SOURCE_GPIOPAD15, DC_IRQ_SOURCE_GPIOPAD16, DC_IRQ_SOURCE_GPIOPAD17,
	DC_IRQ_SOURCE_GPIOPAD18, DC_IRQ_SOURCE_GPIOPAD19, DC_IRQ_SOURCE_GPIOPAD20,
	DC_IRQ_SOURCE_GPIOPAD21, DC_IRQ_SOURCE_GPIOPAD22, DC_IRQ_SOURCE_GPIOPAD23,
	DC_IRQ_SOURCE_GPIOPAD24, DC_IRQ_SOURCE_GPIOPAD25, DC_IRQ_SOURCE_GPIOPAD26,
	DC_IRQ_SOURCE_GPIOPAD27, DC_IRQ_SOURCE_GPIOPAD28, DC_IRQ_SOURCE_GPIOPAD29,
	DC_IRQ_SOURCE_GPIOPAD30,
	DC_IRQ_SOURCE_DC1UNDERFLOW, DC_IRQ_SOURCE_DC2UNDERFLOW,
	DC_IRQ_SOURCE_DC3UNDERFLOW, DC_IRQ_SOURCE_DC4UNDERFLOW,
	DC_IRQ_SOURCE_DC5UNDERFLOW, DC_IRQ_SOURCE_DC6UNDERFLOW,
	DC_IRQ_SOURCE_DMCU_SCP, DC_IRQ_SOURCE_VBIOS_SW,
	DC_IRQ_SOURCE_VUPDATE1, DC_IRQ_SOURCE_VUPDATE2, DC_IRQ_SOURCE_VUPDATE3,
	DC_IRQ_SOURCE_VUPDATE4, DC_IRQ_SOURCE_VUPDATE5, DC_IRQ_SOURCE_VUPDATE6,
	DC_IRQ_SOURCE_VBLANK1, DC_IRQ_SOURCE_VBLANK2, DC_IRQ_SOURCE_VBLANK3,
	DC_IRQ_SOURCE_VBLANK4, DC_IRQ_SOURCE_VBLANK5, DC_IRQ_SOURCE_VBLANK6,
	DC_IRQ_SOURCE_DC1_VLINE0, DC_IRQ_SOURCE_DC2_VLINE0, DC_IRQ_SOURCE_DC3_VLINE0,
	DC_IRQ_SOURCE_DC4_VLINE0, DC_IRQ_SOURCE_DC5_VLINE0, DC_IRQ_SOURCE_DC6_VLINE0,
	DAL_IRQ_SOURCES_NUMBER
};

enum irq_type {
	IRQ_TYPE_PFLIP = DC_IRQ_SOURCE_PFLIP1,
	IRQ_TYPE_VUPDATE = DC_IRQ_SOURCE_VUPDATE1,
	IRQ_TYPE_VBLANK = DC_IRQ_SOURCE_VBLANK1,
};

/* ---- irq_service.h:36-70 verbatim (member order/layout) ---- */
struct irq_service_type;
struct irq_source_info;
struct irq_source_info_funcs {
	bool (*set)(struct irq_service_type *irq_service,
		    const struct irq_source_info *info, bool enable);
	bool (*ack)(struct irq_service_type *irq_service,
		    const struct irq_source_info *info);
};
struct irq_source_info {
	uint32 src_id;
	uint32 ext_id;
	uint32 enable_reg;
	uint32 enable_mask;
	uint32 enable_value[2];
	uint32 ack_reg;
	uint32 ack_mask;
	uint32 ack_value;
	uint32 status_reg;
	const struct irq_source_info_funcs *funcs;
};

struct irq_service_funcs_type {
	enum dc_irq_source (*to_dal_irq_source)(
			struct irq_service_type *irq_service,
			uint32_t src_id, uint32_t ext_id);
};

struct irq_service_type {
	void *ctx;
	const struct irq_source_info *info;
	const struct irq_service_funcs_type *funcs;
};

/* fake register file for dm_read_reg/dm_write_reg */
static uint32 fake_regs[65536];
static uint32 dm_read_reg(void *c, uint32 a) { (void)c; return fake_regs[a & 0xffff]; }
static void dm_write_reg(void *c, uint32 a, uint32 v) { (void)c; fake_regs[a & 0xffff] = v; }

/* ---- VISLANDS30 IV constants (ivsrcid_vislands30.h) verbatim ---- */
#define VISLANDS30_IV_SRCID_D1_V_UPDATE_INT		7
#define VISLANDS30_IV_SRCID_D2_V_UPDATE_INT		9
#define VISLANDS30_IV_SRCID_D3_V_UPDATE_INT		11
#define VISLANDS30_IV_SRCID_D4_V_UPDATE_INT		13
#define VISLANDS30_IV_SRCID_D5_V_UPDATE_INT		15
#define VISLANDS30_IV_SRCID_D6_V_UPDATE_INT		17
#define VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0	19
#define VISLANDS30_IV_SRCID_D2_VERTICAL_INTERRUPT0	20
#define VISLANDS30_IV_SRCID_D3_VERTICAL_INTERRUPT0	21
#define VISLANDS30_IV_SRCID_D4_VERTICAL_INTERRUPT0	22
#define VISLANDS30_IV_SRCID_D5_VERTICAL_INTERRUPT0	23
#define VISLANDS30_IV_SRCID_D6_VERTICAL_INTERRUPT0	24
#define VISLANDS30_IV_SRCID_D1_GRPH_PFLIP		8
#define VISLANDS30_IV_SRCID_D2_GRPH_PFLIP		10
#define VISLANDS30_IV_SRCID_D3_GRPH_PFLIP		12
#define VISLANDS30_IV_SRCID_D4_GRPH_PFLIP		14
#define VISLANDS30_IV_SRCID_D5_GRPH_PFLIP		16
#define VISLANDS30_IV_SRCID_D6_GRPH_PFLIP		18
#define VISLANDS30_IV_SRCID_HOTPLUG_DETECT_A		42
#define VISLANDS30_IV_EXTID_HOTPLUG_DETECT_A		0
#define VISLANDS30_IV_EXTID_HOTPLUG_DETECT_B		1
#define VISLANDS30_IV_EXTID_HOTPLUG_DETECT_C		2
#define VISLANDS30_IV_EXTID_HOTPLUG_DETECT_D		3
#define VISLANDS30_IV_EXTID_HOTPLUG_DETECT_E		4
#define VISLANDS30_IV_EXTID_HOTPLUG_DETECT_F		5
#define VISLANDS30_IV_EXTID_HPD_RX_A			6
#define VISLANDS30_IV_EXTID_HPD_RX_B			7
#define VISLANDS30_IV_EXTID_HPD_RX_C			8
#define VISLANDS30_IV_EXTID_HPD_RX_D			9
#define VISLANDS30_IV_EXTID_HPD_RX_E			10
#define VISLANDS30_IV_EXTID_HPD_RX_F			11

/* ---- to_dal_irq_source_dce110 (irq_service_dce110.c:315-393) verbatim ---- */
static enum dc_irq_source to_dal_irq_source_dce110(
		struct irq_service_type *irq_service,
		uint32_t src_id,
		uint32_t ext_id)
{
	(void)irq_service;
	switch (src_id) {
	case VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0:
		return DC_IRQ_SOURCE_VBLANK1;
	case VISLANDS30_IV_SRCID_D2_VERTICAL_INTERRUPT0:
		return DC_IRQ_SOURCE_VBLANK2;
	case VISLANDS30_IV_SRCID_D3_VERTICAL_INTERRUPT0:
		return DC_IRQ_SOURCE_VBLANK3;
	case VISLANDS30_IV_SRCID_D4_VERTICAL_INTERRUPT0:
		return DC_IRQ_SOURCE_VBLANK4;
	case VISLANDS30_IV_SRCID_D5_VERTICAL_INTERRUPT0:
		return DC_IRQ_SOURCE_VBLANK5;
	case VISLANDS30_IV_SRCID_D6_VERTICAL_INTERRUPT0:
		return DC_IRQ_SOURCE_VBLANK6;
	case VISLANDS30_IV_SRCID_D1_V_UPDATE_INT:
		return DC_IRQ_SOURCE_VUPDATE1;
	case VISLANDS30_IV_SRCID_D2_V_UPDATE_INT:
		return DC_IRQ_SOURCE_VUPDATE2;
	case VISLANDS30_IV_SRCID_D3_V_UPDATE_INT:
		return DC_IRQ_SOURCE_VUPDATE3;
	case VISLANDS30_IV_SRCID_D4_V_UPDATE_INT:
		return DC_IRQ_SOURCE_VUPDATE4;
	case VISLANDS30_IV_SRCID_D5_V_UPDATE_INT:
		return DC_IRQ_SOURCE_VUPDATE5;
	case VISLANDS30_IV_SRCID_D6_V_UPDATE_INT:
		return DC_IRQ_SOURCE_VUPDATE6;
	case VISLANDS30_IV_SRCID_D1_GRPH_PFLIP:
		return DC_IRQ_SOURCE_PFLIP1;
	case VISLANDS30_IV_SRCID_D2_GRPH_PFLIP:
		return DC_IRQ_SOURCE_PFLIP2;
	case VISLANDS30_IV_SRCID_D3_GRPH_PFLIP:
		return DC_IRQ_SOURCE_PFLIP3;
	case VISLANDS30_IV_SRCID_D4_GRPH_PFLIP:
		return DC_IRQ_SOURCE_PFLIP4;
	case VISLANDS30_IV_SRCID_D5_GRPH_PFLIP:
		return DC_IRQ_SOURCE_PFLIP5;
	case VISLANDS30_IV_SRCID_D6_GRPH_PFLIP:
		return DC_IRQ_SOURCE_PFLIP6;
	case VISLANDS30_IV_SRCID_HOTPLUG_DETECT_A:
		/* generic src_id for all HPD and HPDRX interrupts */
		switch (ext_id) {
		case VISLANDS30_IV_EXTID_HOTPLUG_DETECT_A:
			return DC_IRQ_SOURCE_HPD1;
		case VISLANDS30_IV_EXTID_HOTPLUG_DETECT_B:
			return DC_IRQ_SOURCE_HPD2;
		case VISLANDS30_IV_EXTID_HOTPLUG_DETECT_C:
			return DC_IRQ_SOURCE_HPD3;
		case VISLANDS30_IV_EXTID_HOTPLUG_DETECT_D:
			return DC_IRQ_SOURCE_HPD4;
		case VISLANDS30_IV_EXTID_HOTPLUG_DETECT_E:
			return DC_IRQ_SOURCE_HPD5;
		case VISLANDS30_IV_EXTID_HOTPLUG_DETECT_F:
			return DC_IRQ_SOURCE_HPD6;
		case VISLANDS30_IV_EXTID_HPD_RX_A:
			return DC_IRQ_SOURCE_HPD1RX;
		case VISLANDS30_IV_EXTID_HPD_RX_B:
			return DC_IRQ_SOURCE_HPD2RX;
		case VISLANDS30_IV_EXTID_HPD_RX_C:
			return DC_IRQ_SOURCE_HPD3RX;
		case VISLANDS30_IV_EXTID_HPD_RX_D:
			return DC_IRQ_SOURCE_HPD4RX;
		case VISLANDS30_IV_EXTID_HPD_RX_E:
			return DC_IRQ_SOURCE_HPD5RX;
		case VISLANDS30_IV_EXTID_HPD_RX_F:
			return DC_IRQ_SOURCE_HPD6RX;
		default:
			return DC_IRQ_SOURCE_INVALID;
		}
		break;

	default:
		return DC_IRQ_SOURCE_INVALID;
	}
}

static const struct irq_service_funcs_type irq_service_funcs_dce80 = {
	.to_dal_irq_source = to_dal_irq_source_dce110	/* irq_service_dce80.c:277 */
};

/* ---- the dc-walk types dce110_vblank_set touches, minimally shaped.
 * pipe_ctx is padded to a fixed 4096-byte stride so pipe_ctx[178] lands
 * ~728KB past the array head — far outside the valid 6 pipes and inside
 * the poisoned mapping (mirrors the kernel's MAX_PIPES=6 array on DCE80). */
#define MAX_PIPES 6
#define PIPE_CTX_STRIDE 4096

struct timing_generator;

struct tg_funcs_type {
	bool (*arm_vert_intr)(struct timing_generator *tg, uint8_t id);
};
struct timing_generator {
	const struct tg_funcs_type *funcs;
	int pipe_idx;			/* replica bookkeeping */
};
struct stream_res {
	struct timing_generator *tg;
};
struct pipe_ctx_type {
	struct stream_res stream_res;
	unsigned char pad[PIPE_CTX_STRIDE - sizeof(struct stream_res)];
};
struct resource_context {
	struct pipe_ctx_type pipe_ctx[MAX_PIPES];
};
struct dc_state {
	struct resource_context res_ctx;
};
struct dc_type {
	struct irq_service_type *irqs;		/* dc->res_pool->irqs stand-in */
	struct dc_state *current_state;
};
struct dc_context {
	struct dc_type *dc;
};

/* the arm callback: records which pipe got armed (controls) */
static bool tg_arm_vert_intr(struct timing_generator *tg, uint8_t id)
{
	printf("      arm_vert_intr(tg->pipe_idx=%d, id=%u)\n",
	       tg->pipe_idx, (unsigned)id);
	return true;
}
static const struct tg_funcs_type tg_funcs_real = {
	.arm_vert_intr = tg_arm_vert_intr,
};

/* dc_interrupt_to_irq_source (core/dc.c:1672-1678) collapses to
 *   dal_irq_service_to_irq_source(dc->res_pool->irqs, src_id, ext_id)
 *     -> irq_service->funcs->to_dal_irq_source(...)   (irq_service.c:160-169) */
static enum dc_irq_source dc_interrupt_to_irq_source(
	struct dc_type *dc, uint32_t src_id, uint32_t ext_id)
{
	return dc->irqs->funcs->to_dal_irq_source(dc->irqs, src_id, ext_id);
}

static void dal_irq_service_set_generic(
	struct irq_service_type *irq_service,
	const struct irq_source_info *info,
	bool enable);

/* ---- dce110_vblank_set (irq_service_dce110.c:195-219) verbatim body.
 * Only the two #ifdef'd pipe_offset lines differ in fixed mode; everything
 * else, including the `!tg || !tg->funcs->arm_vert_intr(tg, 2)` indirect
 * call, is verbatim. ---- */
bool dce110_vblank_set(
	struct irq_service_type *irq_service,
	const struct irq_source_info *info,
	bool enable)
{
	struct dc_context *dc_ctx = (struct dc_context *)irq_service->ctx;
	struct dc_type *core_dc = ((struct dc_context *)irq_service->ctx)->dc;
	enum dc_irq_source dal_irq_src =
			dc_interrupt_to_irq_source(
				((struct dc_context *)irq_service->ctx)->dc,
							/* kernel: irq_service->ctx->dc (ctx is
								 * struct dc_context * there) */
						   info->src_id,
						   info->ext_id);
	uint8_t pipe_offset;
	struct timing_generator *tg;

	(void)dc_ctx;	/* upstream keeps it for the DC_ERROR logger macro */

#ifdef FIX_DF5055
	{
		/* fix: derive the pipe from the table index, not from a
		 * re-mapped src_id — provably 0..5 for VUPDATE1..6 and
		 * VBLANK1..6 alike (this is DF-4933's fix shape) */
		enum dc_irq_source src =
			(enum dc_irq_source)(info - irq_service->info);
		pipe_offset = (uint8_t)(
			(src >= DC_IRQ_SOURCE_VUPDATE1 &&
			 src <= DC_IRQ_SOURCE_VUPDATE6) ?
			src - DC_IRQ_SOURCE_VUPDATE1 :
			src - DC_IRQ_SOURCE_VBLANK1);
	}
#else
	pipe_offset = dal_irq_src - IRQ_TYPE_VBLANK;	/* :205 verbatim */
#endif

	tg = core_dc->current_state->res_ctx.pipe_ctx[pipe_offset]
		.stream_res.tg;				/* :207-208 verbatim */

	if (enable) {
		if (!tg || !tg->funcs->arm_vert_intr(tg, 2)) {	/* :210-211 */
			DC_ERROR("Failed to get VBLANK!\n");
			return false;
		}
	}

	dal_irq_service_set_generic(irq_service, info, enable);
	return true;
}

/* ---- dce80 funcs table + entry macros (irq_service_dce80.c:82-85,132-163)
 *      verbatim (only the vupdate/vblank region is instantiated; the other
 *      table slots are irrelevant to this defect and left zero) ---- */
static const struct irq_source_info_funcs vblank_irq_info_funcs = {
	.set = dce110_vblank_set,		/* irq_service_dce80.c:83 */
	.ack = NULL
};

#define vupdate_int_entry(reg_num)\
	[DC_IRQ_SOURCE_VUPDATE1 + reg_num] = {\
		.enable_reg = mmCRTC ## reg_num ## _CRTC_INTERRUPT_CONTROL,\
		.enable_mask =\
		CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK,\
		.enable_value = {\
			CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK,\
			~CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK},\
		.ack_reg = mmCRTC ## reg_num ## _CRTC_V_UPDATE_INT_STATUS,\
		.ack_mask =\
		CRTC_V_UPDATE_INT_STATUS__CRTC_V_UPDATE_INT_CLEAR_MASK,\
		.ack_value =\
		CRTC_V_UPDATE_INT_STATUS__CRTC_V_UPDATE_INT_CLEAR_MASK,\
		.funcs = &vblank_irq_info_funcs\
	}

#define vblank_int_entry(reg_num)\
	[DC_IRQ_SOURCE_VBLANK1 + reg_num] = {\
		.enable_reg = mmCRTC ## reg_num ## _CRTC_VERTICAL_INTERRUPT0_CONTROL,\
		.enable_mask =\
		CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK,\
		.enable_value = {\
			CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK,\
			~CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK},\
		.ack_reg = mmCRTC ## reg_num ## _CRTC_VERTICAL_INTERRUPT0_CONTROL,\
		.ack_mask =\
		CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_CLEAR_MASK,\
		.ack_value =\
		CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_CLEAR_MASK,\
		.funcs = &vblank_irq_info_funcs,\
		.src_id = VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0 + reg_num\
	}

/* stubbed DCE80 register defines (inert data; unique values) */
#define mmCRTC0_CRTC_INTERRUPT_CONTROL	0x2400
#define mmCRTC1_CRTC_INTERRUPT_CONTROL	0x2401
#define mmCRTC2_CRTC_INTERRUPT_CONTROL	0x2402
#define mmCRTC3_CRTC_INTERRUPT_CONTROL	0x2403
#define mmCRTC4_CRTC_INTERRUPT_CONTROL	0x2404
#define mmCRTC5_CRTC_INTERRUPT_CONTROL	0x2405
#define mmCRTC0_CRTC_V_UPDATE_INT_STATUS	0x2500
#define mmCRTC1_CRTC_V_UPDATE_INT_STATUS	0x2501
#define mmCRTC2_CRTC_V_UPDATE_INT_STATUS	0x2502
#define mmCRTC3_CRTC_V_UPDATE_INT_STATUS	0x2503
#define mmCRTC4_CRTC_V_UPDATE_INT_STATUS	0x2504
#define mmCRTC5_CRTC_V_UPDATE_INT_STATUS	0x2505
#define mmCRTC0_CRTC_VERTICAL_INTERRUPT0_CONTROL	0x2600
#define mmCRTC1_CRTC_VERTICAL_INTERRUPT0_CONTROL	0x2601
#define mmCRTC2_CRTC_VERTICAL_INTERRUPT0_CONTROL	0x2602
#define mmCRTC3_CRTC_VERTICAL_INTERRUPT0_CONTROL	0x2603
#define mmCRTC4_CRTC_VERTICAL_INTERRUPT0_CONTROL	0x2604
#define mmCRTC5_CRTC_VERTICAL_INTERRUPT0_CONTROL	0x2605
#define CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK	0x00000010
#define CRTC_V_UPDATE_INT_STATUS__CRTC_V_UPDATE_INT_CLEAR_MASK	0x00000020
#define CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK	0x00000040
#define CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_CLEAR_MASK		0x00000080

static const struct irq_source_info
irq_source_info_dce80[DAL_IRQ_SOURCES_NUMBER] = {
	[DC_IRQ_SOURCE_INVALID] = { .funcs = NULL },	/* untouched by this PoC */
	vupdate_int_entry(0),
	vupdate_int_entry(1),
	vupdate_int_entry(2),
	vupdate_int_entry(3),
	vupdate_int_entry(4),
	vupdate_int_entry(5),
	vblank_int_entry(0),
	vblank_int_entry(1),
	vblank_int_entry(2),
	vblank_int_entry(3),
	vblank_int_entry(4),
	vblank_int_entry(5),
};

/* ---- irq_service.c:77-124 verbatim (only the set path is needed) ---- */
static
const struct irq_source_info *find_irq_source_info(
	struct irq_service_type *irq_service,
	enum dc_irq_source source)
{
	if (source >= DAL_IRQ_SOURCES_NUMBER || source < DC_IRQ_SOURCE_INVALID)
		return NULL;

	return &irq_service->info[source];
}

static void dal_irq_service_set_generic(
	struct irq_service_type *irq_service,
	const struct irq_source_info *info,
	bool enable)
{
	uint32 addr = info->enable_reg;
	uint32 value = dm_read_reg(irq_service->ctx, addr);

	value = (value & ~info->enable_mask) |
		(info->enable_value[enable ? 0 : 1] & info->enable_mask);
	dm_write_reg(irq_service->ctx, addr, value);
}

static bool dal_irq_service_ack_stub(
	struct irq_service_type *irq_service,
	const struct irq_source_info *info)
{
	/* vblank_irq_info_funcs.ack == NULL -> dal_irq_service_ack takes the
	 * generic path; stubbed to keep this replica focused (see DF-5054's
	 * replica for the verbatim ack dispatch) */
	(void)irq_service; (void)info;
	return true;
}

static bool dal_irq_service_set(
	struct irq_service_type *irq_service,
	enum dc_irq_source source,
	bool enable)
{
	const struct irq_source_info *info =
		find_irq_source_info(irq_service, source);

	if (!info) {
		DC_LOG_ERROR("%s: cannot find irq info table entry for %d\n",
			__func__, source);
		return false;
	}

	dal_irq_service_ack_stub(irq_service, info);	/* irq_service.c:116 */

	if (info->funcs->set)				/* irq_service.c:118 */
		return info->funcs->set(irq_service, info, enable);

	dal_irq_service_set_generic(irq_service, info, enable);
	return true;
}

int main(void)
{
	setvbuf(stdout, NULL, _IONBF, 0);
	static struct timing_generator tgs[MAX_PIPES];
	struct dc_context dc_ctx;
	struct dc_type core_dc;
	struct irq_service_type svc;
	struct dc_state *state;
	char *poison;
	int i;

	printf("== DF-5055 replica: dce80 vupdate (no .src_id) -> "
		"dce110_vblank_set pipe_ctx[178] OOB ==\n%s\n\n",
#ifdef FIX_DF5055
		"mode: FIXED (pipe_offset derived from table index)"
#else
		"mode: BASELINE (verbatim)"
#endif
		);

	/* the poisoned dc_state: pipes 0..5 valid, everything past the array
	 * is 0x5a garbage (the kernel analogue: any heap memory following
	 * the dc_state allocation) */
	poison = mmap(NULL, 8u << 20, PROT_READ | PROT_WRITE,
		      MAP_PRIVATE | MAP_ANON, -1, 0);
	if (poison == MAP_FAILED) {
		perror("mmap");
		return 97;
	}
	memset(poison, 0x5a, 8u << 20);
	state = (struct dc_state *)poison;
	for (i = 0; i < MAX_PIPES; i++) {
		tgs[i].funcs = &tg_funcs_real;
		tgs[i].pipe_idx = i;
		state->res_ctx.pipe_ctx[i].stream_res.tg = &tgs[i];
	}

	svc.ctx = &dc_ctx;
	svc.info = irq_source_info_dce80;
	svc.funcs = &irq_service_funcs_dce80;
	dc_ctx.dc = &core_dc;
	core_dc.irqs = &svc;
	core_dc.current_state = state;

	printf("enum: VUPDATE1=%d VUPDATE6=%d VBLANK1=%d VBLANK6=%d "
	       "IRQ_TYPE_VBLANK=%d MAX_PIPES=%d\n",
	       (int)DC_IRQ_SOURCE_VUPDATE1, (int)DC_IRQ_SOURCE_VUPDATE6,
	       (int)DC_IRQ_SOURCE_VBLANK1, (int)DC_IRQ_SOURCE_VBLANK6,
	       (int)IRQ_TYPE_VBLANK, MAX_PIPES);
	printf("table: vupdate1 src_id=%u ext_id=%u | vblank1 src_id=%u "
	       "(19..24)\n\n",
	       irq_source_info_dce80[DC_IRQ_SOURCE_VUPDATE1].src_id,
	       irq_source_info_dce80[DC_IRQ_SOURCE_VUPDATE1].ext_id,
	       irq_source_info_dce80[DC_IRQ_SOURCE_VBLANK1].src_id);

	/* controls: the in-tree-reachable VBLANK path arms pipes 0..5 */
	for (i = 0; i < 6; i++) {
		enum dc_irq_source s =
			(enum dc_irq_source)(DC_IRQ_SOURCE_VBLANK1 + i);
		printf("control VBLANK%d set:", i + 1);
		if (!dal_irq_service_set(&svc, s, true)) {
			printf(" FAILED (BAD)\n");
			return 98;
		}
	}
	printf("controls ok (vblank path arms pipes 0..5)\n\n");

	/* trigger: set(VUPDATE1) — src_id==0 -> to_dal returns INVALID ->
	 * pipe_offset = (uint8_t)(0 - 78) = 178 -> pipe_ctx[178] on a
	 * 6-entry array inside the poisoned mapping -> garbage tg ->
	 * tg->funcs->arm_vert_intr(tg,2) faults */
	printf("trigger: dal_irq_service_set(DC_IRQ_SOURCE_VUPDATE1=%d)\n"
	       "   -> dce110_vblank_set: dal_irq_src=INVALID(0), "
	       "pipe_offset = (uint8_t)(0-%d) = %u\n"
	       "   -> core_dc->current_state->res_ctx.pipe_ctx[%u] on a "
	       "%d-entry array ...\n",
	       (int)DC_IRQ_SOURCE_VUPDATE1, (int)IRQ_TYPE_VBLANK,
	       (unsigned char)(0 - IRQ_TYPE_VBLANK),
	       (unsigned char)(0 - IRQ_TYPE_VBLANK), MAX_PIPES);

	if (!dal_irq_service_set(&svc, DC_IRQ_SOURCE_VUPDATE1, true)) {
		printf("\ntrigger returned false without arming (BAD)\n");
		return 98;
	}
#ifdef FIX_DF5055
	printf("\n[fixed] VUPDATE1 -> pipe 0 armed via index-derived offset; "
	       "exit 0\n");
	return 0;
#else
	printf("\ntrigger SURVIVED baseline (BAD -- expected a fault)\n");
	return 98;
#endif
}
