/*
 * DF-4995 replica — irq_service_dcn10.c table gap DC_IRQ_SOURCE_DC{1..6}_VLINE0
 *
 * Verbatim userland replica of:
 *   - enum dc_irq_source                     (sys/dev/drm/amd/display/dc/irq_types.h:40-148)
 *   - struct irq_source_info / _funcs / irq_service
 *                                            (sys/dev/drm/amd/display/dc/irq/irq_service.h:44-79)
 *   - hpd_ack + funcs tables + irq_source_info_dcn10[] entry macros + table body
 *                                            (sys/dev/drm/amd/display/dc/irq/dcn10/irq_service_dcn10.c:110-331)
 *   - to_dal_irq_source_dcn10                (irq_service_dcn10.c:41-108, with the real
 *                                             DCN_1_0__SRCID__* / CTXID__* constants from
 *                                             amd/include/ivsrcid/irqsrcs_dcn_1_0.h)
 *   - find_irq_source_info / dal_irq_service_set / dal_irq_service_ack /
 *     dal_irq_service_set_generic / dal_irq_service_ack_generic
 *                                            (sys/dev/drm/amd/display/dc/irq/irq_service.c:77-158)
 *
 * The DCN10 register addresses/masks (dcn_1_0_offset.h / dcn_1_0_sh_mask.h) are
 * inert data for this defect and stubbed with unique constants (BASE(seg)=0 so
 * SRI collapses to the bare mm offset); everything that affects control flow —
 * the enum values, the designated initializers, the index bounds check, the
 * to_dal_irq_source mapping, and the `info->funcs->set/ack` dereferences — is
 * verbatim.
 *
 * The shared enum runs to DAL_IRQ_SOURCES_NUMBER=90 with
 * DC_IRQ_SOURCE_DC1_VLINE0..DC6_VLINE0 = 84..89, but irq_source_info_dcn10[]
 * (the table body ends with vblank_int_entry(5) == DC_IRQ_SOURCE_VBLANK6 = 83)
 * has no designators past 83: entries 84..89 stay zero-initialized, i.e.
 * .funcs == NULL. find_irq_source_info() (irq_service.c:82) bounds-checks only
 * the index, and dal_irq_service_ack() (:152) / dal_irq_service_set() (:118)
 * dereference info->funcs without checking it — a set/ack of
 * DC_IRQ_SOURCE_DC1_VLINE0 faults reading (NULL + offsetof(.ack/.set)).
 *
 * Also proves (hunt item d): to_dal_irq_source_dcn10 — the ONLY runtime entry
 * into the service driven by hardware (IH src_id/ext_id via
 * amdgpu_dm_irq_handler -> dc_interrupt_to_irq_source) — can only return the
 * covered VBLANK/PFLIP/HPD sources or DC_IRQ_SOURCE_INVALID; it can never
 * synthesize 84..89, which is why the defect is latent (one future caller
 * away), matching the DF-4932 (dce110) family verdict.
 *
 * Build: cc -O2 -Wall -std=gnu11 [-DFIX_DF4995] -o dcn10_vline0gap dcn10_vline0gap.c
 * Exit:  0 ok / 99 NULL-funcs table entries remain / signal on the gap call (baseline)
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

typedef uint32_t uint32;

#define DC_LOG_ERROR(fmt, ...) printf("DC_IRQ_ERR: " 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:44-79 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;
};

/* 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; }

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

/* ---- dce110 file: dummy funcs (irq_service_dce110.c:177-192) verbatim ---- */
bool dal_irq_service_dummy_set(struct irq_service_type *irq_service,
			       const struct irq_source_info *info, bool enable)
{
	(void)info; (void)enable;
	DC_LOG_ERROR("%s: called for non-implemented irq source\n", __func__);
	return false;
}

bool dal_irq_service_dummy_ack(struct irq_service_type *irq_service,
			       const struct irq_source_info *info)
{
	(void)info;
	DC_LOG_ERROR("%s: called for non-implemented irq source\n", __func__);
	return false;
}

static const struct irq_source_info_funcs dummy_irq_info_funcs = {
	.set = dal_irq_service_dummy_set,
	.ack = dal_irq_service_dummy_ack
};

/* stubbed DCN10 register defines (inert data; unique values).
 * BASE()/SRI() collapse to the bare mm offset; the IRQ_REG_ENTRY token
 * pastes below compose exactly the names #defined here. */
#define BASE_INNER(seg) 0
#define BASE(seg) BASE_INNER(seg)
#define SRI(reg_name, block, id) \
	BASE(mm ## block ## id ## _ ## reg_name ## _BASE_IDX) + \
	mm ## block ## id ## _ ## reg_name

#define mmHPD0_DC_HPD_INT_CONTROL	0x2000
#define mmHPD1_DC_HPD_INT_CONTROL	0x2001
#define mmHPD2_DC_HPD_INT_CONTROL	0x2002
#define mmHPD3_DC_HPD_INT_CONTROL	0x2003
#define mmHPD4_DC_HPD_INT_CONTROL	0x2004
#define mmHPD5_DC_HPD_INT_CONTROL	0x2005
#define mmHPD0_DC_HPD_INT_STATUS	0x2100
#define mmHPD1_DC_HPD_INT_STATUS	0x2101
#define mmHPD2_DC_HPD_INT_STATUS	0x2102
#define mmHPD3_DC_HPD_INT_STATUS	0x2103
#define mmHPD4_DC_HPD_INT_STATUS	0x2104
#define mmHPD5_DC_HPD_INT_STATUS	0x2105
#define mmHUBPREQ0_DCSURF_SURFACE_FLIP_INTERRUPT	0x2200
#define mmHUBPREQ1_DCSURF_SURFACE_FLIP_INTERRUPT	0x2201
#define mmHUBPREQ2_DCSURF_SURFACE_FLIP_INTERRUPT	0x2202
#define mmHUBPREQ3_DCSURF_SURFACE_FLIP_INTERRUPT	0x2203
#define mmOTG0_OTG_GLOBAL_SYNC_STATUS	0x2300
#define mmOTG1_OTG_GLOBAL_SYNC_STATUS	0x2301
#define mmOTG2_OTG_GLOBAL_SYNC_STATUS	0x2302
#define mmOTG3_OTG_GLOBAL_SYNC_STATUS	0x2303
#define mmOTG4_OTG_GLOBAL_SYNC_STATUS	0x2304
#define mmOTG5_OTG_GLOBAL_SYNC_STATUS	0x2305

#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define HPD1_DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define HPD2_DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define HPD3_DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define HPD4_DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define HPD5_DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define HPD1_DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define HPD2_DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define HPD3_DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define HPD4_DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define HPD5_DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK		0x00000100
#define HPD1_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK		0x00000100
#define HPD2_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK		0x00000100
#define HPD3_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK		0x00000100
#define HPD4_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK		0x00000100
#define HPD5_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK		0x00000100
#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK		0x00000200
#define HPD1_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK		0x00000200
#define HPD2_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK		0x00000200
#define HPD3_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK		0x00000200
#define HPD4_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK		0x00000200
#define HPD5_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK		0x00000200

#define HUBPREQ0_DCSURF_SURFACE_FLIP_INTERRUPT__SURFACE_FLIP_INT_MASK_MASK	0x00000001
#define HUBPREQ1_DCSURF_SURFACE_FLIP_INTERRUPT__SURFACE_FLIP_INT_MASK_MASK	0x00000001
#define HUBPREQ2_DCSURF_SURFACE_FLIP_INTERRUPT__SURFACE_FLIP_INT_MASK_MASK	0x00000001
#define HUBPREQ3_DCSURF_SURFACE_FLIP_INTERRUPT__SURFACE_FLIP_INT_MASK_MASK	0x00000001
#define HUBPREQ0_DCSURF_SURFACE_FLIP_INTERRUPT__SURFACE_FLIP_CLEAR_MASK	0x00000004
#define HUBPREQ1_DCSURF_SURFACE_FLIP_INTERRUPT__SURFACE_FLIP_CLEAR_MASK	0x00000004
#define HUBPREQ2_DCSURF_SURFACE_FLIP_INTERRUPT__SURFACE_FLIP_CLEAR_MASK	0x00000004
#define HUBPREQ3_DCSURF_SURFACE_FLIP_INTERRUPT__SURFACE_FLIP_CLEAR_MASK	0x00000004

#define OTG0_OTG_GLOBAL_SYNC_STATUS__VUPDATE_INT_EN_MASK	0x00000010
#define OTG1_OTG_GLOBAL_SYNC_STATUS__VUPDATE_INT_EN_MASK	0x00000010
#define OTG2_OTG_GLOBAL_SYNC_STATUS__VUPDATE_INT_EN_MASK	0x00000010
#define OTG3_OTG_GLOBAL_SYNC_STATUS__VUPDATE_INT_EN_MASK	0x00000010
#define OTG4_OTG_GLOBAL_SYNC_STATUS__VUPDATE_INT_EN_MASK	0x00000010
#define OTG5_OTG_GLOBAL_SYNC_STATUS__VUPDATE_INT_EN_MASK	0x00000010
#define OTG0_OTG_GLOBAL_SYNC_STATUS__VUPDATE_EVENT_CLEAR_MASK	0x00000020
#define OTG1_OTG_GLOBAL_SYNC_STATUS__VUPDATE_EVENT_CLEAR_MASK	0x00000020
#define OTG2_OTG_GLOBAL_SYNC_STATUS__VUPDATE_EVENT_CLEAR_MASK	0x00000020
#define OTG3_OTG_GLOBAL_SYNC_STATUS__VUPDATE_EVENT_CLEAR_MASK	0x00000020
#define OTG4_OTG_GLOBAL_SYNC_STATUS__VUPDATE_EVENT_CLEAR_MASK	0x00000020
#define OTG5_OTG_GLOBAL_SYNC_STATUS__VUPDATE_EVENT_CLEAR_MASK	0x00000020
#define OTG0_OTG_GLOBAL_SYNC_STATUS__VSTARTUP_INT_EN_MASK	0x00000100
#define OTG1_OTG_GLOBAL_SYNC_STATUS__VSTARTUP_INT_EN_MASK	0x00000100
#define OTG2_OTG_GLOBAL_SYNC_STATUS__VSTARTUP_INT_EN_MASK	0x00000100
#define OTG3_OTG_GLOBAL_SYNC_STATUS__VSTARTUP_INT_EN_MASK	0x00000100
#define OTG4_OTG_GLOBAL_SYNC_STATUS__VSTARTUP_INT_EN_MASK	0x00000100
#define OTG5_OTG_GLOBAL_SYNC_STATUS__VSTARTUP_INT_EN_MASK	0x00000100
#define OTG0_OTG_GLOBAL_SYNC_STATUS__VSTARTUP_EVENT_CLEAR_MASK	0x00000200
#define OTG1_OTG_GLOBAL_SYNC_STATUS__VSTARTUP_EVENT_CLEAR_MASK	0x00000200
#define OTG2_OTG_GLOBAL_SYNC_STATUS__VSTARTUP_EVENT_CLEAR_MASK	0x00000200
#define OTG3_OTG_GLOBAL_SYNC_STATUS__VSTARTUP_EVENT_CLEAR_MASK	0x00000200
#define OTG4_OTG_GLOBAL_SYNC_STATUS__VSTARTUP_EVENT_CLEAR_MASK	0x00000200
#define OTG5_OTG_GLOBAL_SYNC_STATUS__VSTARTUP_EVENT_CLEAR_MASK	0x00000200

/* field accessors used by hpd_ack (dm_services.h:120-126 paste shape:
 * reg_name ## __ ## reg_field ## _MASK / __SHIFT; set_* assigns back) + stubs */
#define get_reg_field_value(reg_value, base, field) \
	(((reg_value) & base ## __ ## field ## _MASK) >> base ## __ ## field ## __SHIFT)
#define set_reg_field_value(reg_value, field_value, base, field) \
	(reg_value) = (((reg_value) & ~(base ## __ ## field ## _MASK)) | \
	 (field_value) << base ## __ ## field ## __SHIFT)

#define HPD0_DC_HPD_INT_STATUS__DC_HPD_SENSE_DELAYED_MASK	0x00000040
#define HPD0_DC_HPD_INT_STATUS__DC_HPD_SENSE_DELAYED__SHIFT	6
#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_INT_POLARITY_MASK	0x00000004
#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_INT_POLARITY__SHIFT	2

/* ---- DCN_1_0 SRCID/CTXID constants —
 *      amd/include/ivsrcid/irqsrcs_dcn_1_0.h:156-190,1039-1044,1108-1113 verbatim ---- */
#define DCN_1_0__SRCID__DC_HPD1_INT		9
#define DCN_1_0__CTXID__DC_HPD1_INT		0
#define DCN_1_0__CTXID__DC_HPD2_INT		1
#define DCN_1_0__CTXID__DC_HPD3_INT		2
#define DCN_1_0__CTXID__DC_HPD4_INT		3
#define DCN_1_0__CTXID__DC_HPD5_INT		4
#define DCN_1_0__CTXID__DC_HPD6_INT		5
#define DCN_1_0__CTXID__DC_HPD1_RX_INT		6
#define DCN_1_0__CTXID__DC_HPD2_RX_INT		7
#define DCN_1_0__CTXID__DC_HPD3_RX_INT		8
#define DCN_1_0__CTXID__DC_HPD4_RX_INT		9
#define DCN_1_0__CTXID__DC_HPD5_RX_INT		10
#define DCN_1_0__CTXID__DC_HPD6_RX_INT		11
#define DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP	0x3C
#define DCN_1_0__SRCID__DC_D2_OTG_VSTARTUP	0x3D
#define DCN_1_0__SRCID__DC_D3_OTG_VSTARTUP	0x3E
#define DCN_1_0__SRCID__DC_D4_OTG_VSTARTUP	0x3F
#define DCN_1_0__SRCID__DC_D5_OTG_VSTARTUP	0x40
#define DCN_1_0__SRCID__DC_D6_OTG_VSTARTUP	0x41
#define DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT	0x4F
#define DCN_1_0__SRCID__HUBP1_FLIP_INTERRUPT	0x50
#define DCN_1_0__SRCID__HUBP2_FLIP_INTERRUPT	0x51
#define DCN_1_0__SRCID__HUBP3_FLIP_INTERRUPT	0x52
#define DCN_1_0__SRCID__HUBP4_FLIP_INTERRUPT	0x53
#define DCN_1_0__SRCID__HUBP5_FLIP_INTERRUPT	0x54

/* ---- to_dal_irq_source_dcn10 (irq_service_dcn10.c:41-108) verbatim ---- */
static
enum dc_irq_source to_dal_irq_source_dcn10(
		struct irq_service_type *irq_service,
		uint32_t src_id,
		uint32_t ext_id)
{
	(void)irq_service;
	switch (src_id) {
	case DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP:
		return DC_IRQ_SOURCE_VBLANK1;
	case DCN_1_0__SRCID__DC_D2_OTG_VSTARTUP:
		return DC_IRQ_SOURCE_VBLANK2;
	case DCN_1_0__SRCID__DC_D3_OTG_VSTARTUP:
		return DC_IRQ_SOURCE_VBLANK3;
	case DCN_1_0__SRCID__DC_D4_OTG_VSTARTUP:
		return DC_IRQ_SOURCE_VBLANK4;
	case DCN_1_0__SRCID__DC_D5_OTG_VSTARTUP:
		return DC_IRQ_SOURCE_VBLANK5;
	case DCN_1_0__SRCID__DC_D6_OTG_VSTARTUP:
		return DC_IRQ_SOURCE_VBLANK6;
	case DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT:
		return DC_IRQ_SOURCE_PFLIP1;
	case DCN_1_0__SRCID__HUBP1_FLIP_INTERRUPT:
		return DC_IRQ_SOURCE_PFLIP2;
	case DCN_1_0__SRCID__HUBP2_FLIP_INTERRUPT:
		return DC_IRQ_SOURCE_PFLIP3;
	case DCN_1_0__SRCID__HUBP3_FLIP_INTERRUPT:
		return DC_IRQ_SOURCE_PFLIP4;
	case DCN_1_0__SRCID__HUBP4_FLIP_INTERRUPT:
		return DC_IRQ_SOURCE_PFLIP5;
	case DCN_1_0__SRCID__HUBP5_FLIP_INTERRUPT:
		return DC_IRQ_SOURCE_PFLIP6;

	case DCN_1_0__SRCID__DC_HPD1_INT:
		/* generic src_id for all HPD and HPDRX interrupts */
		switch (ext_id) {
		case DCN_1_0__CTXID__DC_HPD1_INT:
			return DC_IRQ_SOURCE_HPD1;
		case DCN_1_0__CTXID__DC_HPD2_INT:
			return DC_IRQ_SOURCE_HPD2;
		case DCN_1_0__CTXID__DC_HPD3_INT:
			return DC_IRQ_SOURCE_HPD3;
		case DCN_1_0__CTXID__DC_HPD4_INT:
			return DC_IRQ_SOURCE_HPD4;
		case DCN_1_0__CTXID__DC_HPD5_INT:
			return DC_IRQ_SOURCE_HPD5;
		case DCN_1_0__CTXID__DC_HPD6_INT:
			return DC_IRQ_SOURCE_HPD6;
		case DCN_1_0__CTXID__DC_HPD1_RX_INT:
			return DC_IRQ_SOURCE_HPD1RX;
		case DCN_1_0__CTXID__DC_HPD2_RX_INT:
			return DC_IRQ_SOURCE_HPD2RX;
		case DCN_1_0__CTXID__DC_HPD3_RX_INT:
			return DC_IRQ_SOURCE_HPD3RX;
		case DCN_1_0__CTXID__DC_HPD4_RX_INT:
			return DC_IRQ_SOURCE_HPD4RX;
		case DCN_1_0__CTXID__DC_HPD5_RX_INT:
			return DC_IRQ_SOURCE_HPD5RX;
		case DCN_1_0__CTXID__DC_HPD6_RX_INT:
			return DC_IRQ_SOURCE_HPD6RX;
		default:
			return DC_IRQ_SOURCE_INVALID;
		}
		break;

	default:
		return DC_IRQ_SOURCE_INVALID;
	}
}

static void dal_irq_service_ack_generic(
	struct irq_service_type *irq_service,
	const struct irq_source_info *info);

/* ---- hpd_ack (irq_service_dcn10.c:110-135) verbatim ---- */
static bool hpd_ack(
	struct irq_service_type *irq_service,
	const struct irq_source_info *info)
{
	uint32_t addr = info->status_reg;
	uint32_t value = dm_read_reg(irq_service->ctx, addr);
	uint32_t current_status =
		get_reg_field_value(
			value,
			HPD0_DC_HPD_INT_STATUS,
			DC_HPD_SENSE_DELAYED);

	dal_irq_service_ack_generic(irq_service, info);

	value = dm_read_reg(irq_service->ctx, info->enable_reg);

	set_reg_field_value(
		value,
		current_status ? 0 : 1,
		HPD0_DC_HPD_INT_CONTROL,
		DC_HPD_INT_POLARITY);

	dm_write_reg(irq_service->ctx, info->enable_reg, value);

	return true;
}

/* ---- funcs tables (irq_service_dcn10.c:137-155) verbatim ---- */
static const struct irq_source_info_funcs hpd_irq_info_funcs = {
	.set = NULL,
	.ack = hpd_ack
};

static const struct irq_source_info_funcs hpd_rx_irq_info_funcs = {
	.set = NULL,
	.ack = NULL
};

static const struct irq_source_info_funcs pflip_irq_info_funcs = {
	.set = NULL,
	.ack = NULL
};

static const struct irq_source_info_funcs vblank_irq_info_funcs = {
	.set = NULL,
	.ack = NULL
};

/* ---- dcn10 entry macros (irq_service_dcn10.c:157-238) verbatim ---- */
#define IRQ_REG_ENTRY(block, reg_num, reg1, mask1, reg2, mask2)\
	.enable_reg = SRI(reg1, block, reg_num),\
	.enable_mask = \
		block ## reg_num ## _ ## reg1 ## __ ## mask1 ## _MASK,\
	.enable_value = {\
		block ## reg_num ## _ ## reg1 ## __ ## mask1 ## _MASK,\
		~block ## reg_num ## _ ## reg1 ## __ ## mask1 ## _MASK \
	},\
	.ack_reg = SRI(reg2, block, reg_num),\
	.ack_mask = \
		block ## reg_num ## _ ## reg2 ## __ ## mask2 ## _MASK,\
	.ack_value = \
		block ## reg_num ## _ ## reg2 ## __ ## mask2 ## _MASK \

#define hpd_int_entry(reg_num)\
	[DC_IRQ_SOURCE_HPD1 + reg_num] = {\
		IRQ_REG_ENTRY(HPD, reg_num,\
			DC_HPD_INT_CONTROL, DC_HPD_INT_EN,\
			DC_HPD_INT_CONTROL, DC_HPD_INT_ACK),\
		.status_reg = SRI(DC_HPD_INT_STATUS, HPD, reg_num),\
		.funcs = &hpd_irq_info_funcs\
	}

#define hpd_rx_int_entry(reg_num)\
	[DC_IRQ_SOURCE_HPD1RX + reg_num] = {\
		IRQ_REG_ENTRY(HPD, reg_num,\
			DC_HPD_INT_CONTROL, DC_HPD_RX_INT_EN,\
			DC_HPD_INT_CONTROL, DC_HPD_RX_INT_ACK),\
		.status_reg = SRI(DC_HPD_INT_STATUS, HPD, reg_num),\
		.funcs = &hpd_rx_irq_info_funcs\
	}
#define pflip_int_entry(reg_num)\
	[DC_IRQ_SOURCE_PFLIP1 + reg_num] = {\
		IRQ_REG_ENTRY(HUBPREQ, reg_num,\
			DCSURF_SURFACE_FLIP_INTERRUPT, SURFACE_FLIP_INT_MASK,\
			DCSURF_SURFACE_FLIP_INTERRUPT, SURFACE_FLIP_CLEAR),\
		.funcs = &pflip_irq_info_funcs\
	}

#define vupdate_int_entry(reg_num)\
	[DC_IRQ_SOURCE_VUPDATE1 + reg_num] = {\
		IRQ_REG_ENTRY(OTG, reg_num,\
			OTG_GLOBAL_SYNC_STATUS, VUPDATE_INT_EN,\
			OTG_GLOBAL_SYNC_STATUS, VUPDATE_EVENT_CLEAR),\
		.funcs = &vblank_irq_info_funcs\
	}

#define vblank_int_entry(reg_num)\
	[DC_IRQ_SOURCE_VBLANK1 + reg_num] = {\
		IRQ_REG_ENTRY(OTG, reg_num,\
			OTG_GLOBAL_SYNC_STATUS, VSTARTUP_INT_EN,\
			OTG_GLOBAL_SYNC_STATUS, VSTARTUP_EVENT_CLEAR),\
		.funcs = &vblank_irq_info_funcs\
	}

#define dummy_irq_entry() \
	{\
		.funcs = &dummy_irq_info_funcs\
	}

#define i2c_int_entry(reg_num) \
	[DC_IRQ_SOURCE_I2C_DDC ## reg_num] = dummy_irq_entry()

#define dp_sink_int_entry(reg_num) \
	[DC_IRQ_SOURCE_DPSINK ## reg_num] = dummy_irq_entry()

#define gpio_pad_int_entry(reg_num) \
	[DC_IRQ_SOURCE_GPIOPAD ## reg_num] = dummy_irq_entry()

#define dc_underflow_int_entry(reg_num) \
	[DC_IRQ_SOURCE_DC ## reg_num ## UNDERFLOW] = dummy_irq_entry()

/* ---- the table (irq_service_dcn10.c:245-331) verbatim body ---- */
static const struct irq_source_info
irq_source_info_dcn10[DAL_IRQ_SOURCES_NUMBER] = {
	[DC_IRQ_SOURCE_INVALID] = dummy_irq_entry(),
	hpd_int_entry(0),
	hpd_int_entry(1),
	hpd_int_entry(2),
	hpd_int_entry(3),
	hpd_int_entry(4),
	hpd_int_entry(5),
	hpd_rx_int_entry(0),
	hpd_rx_int_entry(1),
	hpd_rx_int_entry(2),
	hpd_rx_int_entry(3),
	hpd_rx_int_entry(4),
	hpd_rx_int_entry(5),
	i2c_int_entry(1),
	i2c_int_entry(2),
	i2c_int_entry(3),
	i2c_int_entry(4),
	i2c_int_entry(5),
	i2c_int_entry(6),
	dp_sink_int_entry(1),
	dp_sink_int_entry(2),
	dp_sink_int_entry(3),
	dp_sink_int_entry(4),
	dp_sink_int_entry(5),
	dp_sink_int_entry(6),
	[DC_IRQ_SOURCE_TIMER] = dummy_irq_entry(),
	pflip_int_entry(0),
	pflip_int_entry(1),
	pflip_int_entry(2),
	pflip_int_entry(3),
	[DC_IRQ_SOURCE_PFLIP5] = dummy_irq_entry(),
	[DC_IRQ_SOURCE_PFLIP6] = dummy_irq_entry(),
	[DC_IRQ_SOURCE_PFLIP_UNDERLAY0] = dummy_irq_entry(),
	gpio_pad_int_entry(0),
	gpio_pad_int_entry(1),
	gpio_pad_int_entry(2),
	gpio_pad_int_entry(3),
	gpio_pad_int_entry(4),
	gpio_pad_int_entry(5),
	gpio_pad_int_entry(6),
	gpio_pad_int_entry(7),
	gpio_pad_int_entry(8),
	gpio_pad_int_entry(9),
	gpio_pad_int_entry(10),
	gpio_pad_int_entry(11),
	gpio_pad_int_entry(12),
	gpio_pad_int_entry(13),
	gpio_pad_int_entry(14),
	gpio_pad_int_entry(15),
	gpio_pad_int_entry(16),
	gpio_pad_int_entry(17),
	gpio_pad_int_entry(18),
	gpio_pad_int_entry(19),
	gpio_pad_int_entry(20),
	gpio_pad_int_entry(21),
	gpio_pad_int_entry(22),
	gpio_pad_int_entry(23),
	gpio_pad_int_entry(24),
	gpio_pad_int_entry(25),
	gpio_pad_int_entry(26),
	gpio_pad_int_entry(27),
	gpio_pad_int_entry(28),
	gpio_pad_int_entry(29),
	gpio_pad_int_entry(30),
	dc_underflow_int_entry(1),
	dc_underflow_int_entry(2),
	dc_underflow_int_entry(3),
	dc_underflow_int_entry(4),
	dc_underflow_int_entry(5),
	dc_underflow_int_entry(6),
	[DC_IRQ_SOURCE_DMCU_SCP] = dummy_irq_entry(),
	[DC_IRQ_SOURCE_VBIOS_SW] = dummy_irq_entry(),
	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),
#ifdef FIX_DF4995
	/* fix: make the table total over the shared enum */
	[DC_IRQ_SOURCE_DC1_VLINE0] = dummy_irq_entry(),
	[DC_IRQ_SOURCE_DC2_VLINE0] = dummy_irq_entry(),
	[DC_IRQ_SOURCE_DC3_VLINE0] = dummy_irq_entry(),
	[DC_IRQ_SOURCE_DC4_VLINE0] = dummy_irq_entry(),
	[DC_IRQ_SOURCE_DC5_VLINE0] = dummy_irq_entry(),
	[DC_IRQ_SOURCE_DC6_VLINE0] = dummy_irq_entry(),
#endif
};

/* ---- irq_service.c:77-158 verbatim (funcs guard per FIX) ---- */
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 void dal_irq_service_ack_generic(
	struct irq_service_type *irq_service,
	const struct irq_source_info *info)
{
	uint32 addr = info->ack_reg;
	uint32 value = dm_read_reg(irq_service->ctx, addr);

	value = (value & ~info->ack_mask) |
		(info->ack_value & info->ack_mask);
	dm_write_reg(irq_service->ctx, addr, value);
}

static bool dal_irq_service_ack(
	struct irq_service_type *irq_service,
	enum dc_irq_source source)
{
	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;
	}

#ifdef FIX_DF4995
	if (!info->funcs) {			/* the missing guard */
		DC_LOG_ERROR("%s: no funcs for irq source %d\n",
			     __func__, source);
		return false;
	}
#endif
	if (info->funcs->ack)			/* irq_service.c:152 */
		return info->funcs->ack(irq_service, info);

	dal_irq_service_ack_generic(irq_service, 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(irq_service, source);

#ifdef FIX_DF4995
	if (!info->funcs) {			/* the missing guard */
		DC_LOG_ERROR("%s: no funcs for irq source %d\n",
			     __func__, source);
		return false;
	}
#endif
	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);
	struct irq_service_type svc = { .ctx = NULL,
					.info = irq_source_info_dcn10 };
	int i, gaps = 0;

	printf("== DF-4995 replica: dcn10 table gap -> NULL info->funcs "
		"==\n%s\n\n",
#ifdef FIX_DF4995
		"mode: FIXED (vline0 dummy entries + funcs NULL guards)"
#else
		"mode: BASELINE (table/dispatch verbatim)"
#endif
		);

	printf("enum: VUPDATE1=%d VBLANK1=%d DC1_VLINE0=%d DC6_VLINE0=%d "
	       "DAL_IRQ_SOURCES_NUMBER=%d\n\n",
	       (int)DC_IRQ_SOURCE_VUPDATE1, (int)DC_IRQ_SOURCE_VBLANK1,
	       (int)DC_IRQ_SOURCE_DC1_VLINE0, (int)DC_IRQ_SOURCE_DC6_VLINE0,
	       (int)DAL_IRQ_SOURCES_NUMBER);

	for (i = 0; i < (int)DAL_IRQ_SOURCES_NUMBER; i++) {
		if (!svc.info[i].funcs) {
			printf("table[%d] (.funcs == NULL)  <-- uncovered "
			       "enum value\n", i);
			gaps++;
		}
	}
	printf("\n%d NULL-funcs entr%s in irq_source_info_dcn10[]\n\n",
	       gaps, gaps == 1 ? "y" : "ies");

	/* control 1: index-out-of-range is rejected */
	if (dal_irq_service_set(&svc, DAL_IRQ_SOURCES_NUMBER, true))
		printf("control OOB-index set: returned true (BAD)\n");
	else
		printf("control OOB-index set: rejected by "
		       "find_irq_source_info (ok)\n");

	/* control 2: a covered source programs its enable reg via the
	 * generic path (dcn10 pflip .set == NULL) */
	if (dal_irq_service_set(&svc, DC_IRQ_SOURCE_PFLIP1, true))
		printf("control PFLIP1 set: ok, enable_reg=0x%08x -> 0x%08x\n",
		       (uint32)mmHUBPREQ0_DCSURF_SURFACE_FLIP_INTERRUPT,
		       fake_regs[mmHUBPREQ0_DCSURF_SURFACE_FLIP_INTERRUPT
				 & 0xffff]);

	/* control 3: the dcn10-local hpd_ack runs (polarity toggle) */
	fake_regs[mmHPD0_DC_HPD_INT_STATUS & 0xffff] =
					1 << 6; /* DC_HPD_SENSE_DELAYED */
	fake_regs[mmHPD0_DC_HPD_INT_CONTROL & 0xffff] = 0xffffffff;
	if (dal_irq_service_ack(&svc, DC_IRQ_SOURCE_HPD1))
		printf("control HPD1 ack: hpd_ack ran, polarity bit -> 0x%08x "
		       "(ok)\n",
		       fake_regs[mmHPD0_DC_HPD_INT_CONTROL & 0xffff]);

	/* control 4 (hunt item d): to_dal_irq_source_dcn10 — the IH-driven
	 * entry into the service — never returns 84..89 */
	{
		uint32_t srcs[] = { 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41,
				    0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 9 };
		uint32_t exts[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
				    0xdead };
		int nsrc = (int)(sizeof(srcs) / sizeof(srcs[0]));
		int next = (int)(sizeof(exts) / sizeof(exts[0]));
		int s, e, vline = 0, mapped = 0;
		for (s = 0; s < nsrc; s++) {
			for (e = 0; e < next; e++) {
				enum dc_irq_source r = to_dal_irq_source_dcn10(
					&svc, srcs[s], exts[e]);
				if (r >= DC_IRQ_SOURCE_DC1_VLINE0 &&
				    r < DAL_IRQ_SOURCES_NUMBER)
					vline++;
				else if (r != DC_IRQ_SOURCE_INVALID)
					mapped++;
			}
		}
		printf("control to_dal walk (%d src x %d ext): %d mapped to "
		       "covered sources, %d to VLINE0 84..89 (expect 0)%s\n",
		       nsrc, next, mapped, vline,
		       vline == 0 ? " (ok — latent)" : " (BAD)");
	}

	/* trigger: set() on the first uncovered source — ack() runs first */
	printf("\ntrigger: dal_irq_service_set(DC_IRQ_SOURCE_DC1_VLINE0=%d)"
	       "\n   -> dal_irq_service_ack -> `info->funcs->ack` "
	       "(irq_service.c:152) with funcs==NULL ...\n",
	       (int)DC_IRQ_SOURCE_DC1_VLINE0);

	dal_irq_service_set(&svc, DC_IRQ_SOURCE_DC1_VLINE0, true);

	/* only reached in fixed mode */
	printf("survived the VLINE0 set (fixed behavior)\n");
	if (gaps)
		return 99;
	return 0;
}
