/*
 * DF-5054 replica — irq_service_dce80.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:36-70)
 *   - hpd_ack + funcs tables + irq_source_info_dce80[] entry macros + table body
 *                                            (sys/dev/drm/amd/display/dc/irq/dce80/irq_service_dce80.c:40-274)
 *   - to_dal_irq_source_dce110               (irq_service_dce110.c:315-393, with the real
 *                                             VISLANDS30_IV_* constants from
 *                                             amd/include/ivsrcid/ivsrcid_vislands30.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 DCE80 register addresses/masks (dce/dce_8_0_d.h / dce_8_0_sh_mask.h) are
 * inert data for this defect and stubbed with unique constants; everything that
 * affects control flow — the enum values, the designated initializers (dce80's
 * [DC_IRQ_SOURCE_INVALID + reg_num] / [DC_IRQ_SOURCE_HPD6 + reg_num] forms
 * included), 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_dce80[]
 * (the table body ends with vblank_int_entry(5) == DC_IRQ_SOURCE_VBLANK6 = 83,
 * irq_service_dce80.c:273) 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, called first by dal_irq_service_set at :116) / 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 c): to_dal_irq_source_dce110 — 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) and the converter dce80
 * installs (irq_service_dce80.c:277) — can only return the covered
 * VBLANK/VUPDATE/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-2168/DF-4932 (dce110) / DF-4995 (dcn10) family
 * verdicts. The dce80 instance is the DCE80 (Sea Islands: Oland/Bonaire/
 * Hawaii/Kabini) table.
 *
 * Build: cc -O2 -Wall -std=gnu11 [-DFIX_DF5054] -o dce80_vline0gap dce80_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: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;
};

/* 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 DCE80 register defines (inert data; unique values).
 * The dce80 macros paste mmDC_HPD<n>_INT_CONTROL etc. below compose exactly
 * the names #defined here.  NOTE the dce80 file borrows the *HPD1* field
 * mask names for every pin (only the mm address varies with reg_num). */
#define mmDC_HPD1_INT_CONTROL	0x2001
#define mmDC_HPD2_INT_CONTROL	0x2002
#define mmDC_HPD3_INT_CONTROL	0x2003
#define mmDC_HPD4_INT_CONTROL	0x2004
#define mmDC_HPD5_INT_CONTROL	0x2005
#define mmDC_HPD6_INT_CONTROL	0x2006
#define mmDC_HPD1_INT_STATUS	0x2101
#define mmDC_HPD2_INT_STATUS	0x2102
#define mmDC_HPD3_INT_STATUS	0x2103
#define mmDC_HPD4_INT_STATUS	0x2104
#define mmDC_HPD5_INT_STATUS	0x2105
#define mmDC_HPD6_INT_STATUS	0x2106
#define mmDCP0_GRPH_INTERRUPT_CONTROL	0x2200
#define mmDCP1_GRPH_INTERRUPT_CONTROL	0x2201
#define mmDCP2_GRPH_INTERRUPT_CONTROL	0x2202
#define mmDCP3_GRPH_INTERRUPT_CONTROL	0x2203
#define mmDCP4_GRPH_INTERRUPT_CONTROL	0x2204
#define mmDCP5_GRPH_INTERRUPT_CONTROL	0x2205
#define mmDCP0_GRPH_INTERRUPT_STATUS	0x2300
#define mmDCP1_GRPH_INTERRUPT_STATUS	0x2301
#define mmDCP2_GRPH_INTERRUPT_STATUS	0x2302
#define mmDCP3_GRPH_INTERRUPT_STATUS	0x2303
#define mmDCP4_GRPH_INTERRUPT_STATUS	0x2304
#define mmDCP5_GRPH_INTERRUPT_STATUS	0x2305
#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 DC_HPD1_INT_CONTROL__DC_HPD1_INT_EN_MASK			0x00000001
#define DC_HPD1_INT_CONTROL__DC_HPD1_INT_ACK_MASK			0x00000002
#define DC_HPD1_INT_CONTROL__DC_HPD1_RX_INT_EN_MASK		0x00000100
#define DC_HPD1_INT_CONTROL__DC_HPD1_RX_INT_ACK_MASK		0x00000200
#define GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK	0x00000001
#define GRPH_INTERRUPT_STATUS__GRPH_PFLIP_INT_CLEAR_MASK	0x00000004
#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

/* field accessors used by hpd_ack (dm_services.h paste shape:
 * reg_name ## __ ## reg_field ## _MASK / __SHIFT; set_* assigns back) */
#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 DC_HPD1_INT_STATUS__DC_HPD1_SENSE_DELAYED_MASK	0x00000040
#define DC_HPD1_INT_STATUS__DC_HPD1_SENSE_DELAYED__SHIFT	6
#define DC_HPD1_INT_CONTROL__DC_HPD1_INT_POLARITY_MASK	0x00000004
#define DC_HPD1_INT_CONTROL__DC_HPD1_INT_POLARITY__SHIFT	2

/* ---- VISLANDS30 IV constants —
 *      amd/include/ivsrcid/ivsrcid_vislands30.h:30-63,66,84,102,120,138,156,165-199 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

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

/* ---- hpd_ack (irq_service_dce80.c:40-65) 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,
			DC_HPD1_INT_STATUS,
			DC_HPD1_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,
		DC_HPD1_INT_CONTROL,
		DC_HPD1_INT_POLARITY);

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

	return true;
}

/* ---- funcs tables (irq_service_dce80.c:67-85) 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
};

/* dce110_vblank_set — the real dce80 table wires .set = dce110_vblank_set
 * (irq_service_dce80.c:83).  Its body is the subject of DF-5055 (its own
 * finding+replica); the VLINE0 defect never reaches it, so it is stubbed
 * here (logging stand-in) purely to keep the funcs wiring faithful. */
static bool dce110_vblank_set(
	struct irq_service_type *irq_service,
	const struct irq_source_info *info,
	bool enable)
{
	(void)info;
	DC_LOG_ERROR("%s: [stub] called, enable=%d (see DF-5055)\n",
		     __func__, enable);
	dal_irq_service_set_generic(irq_service, info, enable);
	return true;
}

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

/* ---- dce80 entry macros (irq_service_dce80.c:88-180) verbatim,
 *      incl. the dce80-specific designator forms ---- */
#define hpd_int_entry(reg_num)\
	[DC_IRQ_SOURCE_INVALID + reg_num] = {\
		.enable_reg = mmDC_HPD ## reg_num ## _INT_CONTROL,\
		.enable_mask = DC_HPD1_INT_CONTROL__DC_HPD1_INT_EN_MASK,\
		.enable_value = {\
			DC_HPD1_INT_CONTROL__DC_HPD1_INT_EN_MASK,\
			~DC_HPD1_INT_CONTROL__DC_HPD1_INT_EN_MASK\
		},\
		.ack_reg = mmDC_HPD ## reg_num ## _INT_CONTROL,\
		.ack_mask = DC_HPD1_INT_CONTROL__DC_HPD1_INT_ACK_MASK,\
		.ack_value = DC_HPD1_INT_CONTROL__DC_HPD1_INT_ACK_MASK,\
		.status_reg = mmDC_HPD ## reg_num ## _INT_STATUS,\
		.funcs = &hpd_irq_info_funcs\
	}

#define hpd_rx_int_entry(reg_num)\
	[DC_IRQ_SOURCE_HPD6 + reg_num] = {\
		.enable_reg = mmDC_HPD ## reg_num ## _INT_CONTROL,\
		.enable_mask = DC_HPD1_INT_CONTROL__DC_HPD1_RX_INT_EN_MASK,\
		.enable_value = {\
				DC_HPD1_INT_CONTROL__DC_HPD1_RX_INT_EN_MASK,\
			~DC_HPD1_INT_CONTROL__DC_HPD1_RX_INT_EN_MASK },\
		.ack_reg = mmDC_HPD ## reg_num ## _INT_CONTROL,\
		.ack_mask = DC_HPD1_INT_CONTROL__DC_HPD1_RX_INT_ACK_MASK,\
		.ack_value = DC_HPD1_INT_CONTROL__DC_HPD1_RX_INT_ACK_MASK,\
		.status_reg = mmDC_HPD ## reg_num ## _INT_STATUS,\
		.funcs = &hpd_rx_irq_info_funcs\
	}

#define pflip_int_entry(reg_num)\
	[DC_IRQ_SOURCE_PFLIP1 + reg_num] = {\
		.enable_reg = mmDCP ## reg_num ## _GRPH_INTERRUPT_CONTROL,\
		.enable_mask =\
		GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK,\
		.enable_value = {\
			GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK,\
			~GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK},\
		.ack_reg = mmDCP ## reg_num ## _GRPH_INTERRUPT_STATUS,\
		.ack_mask = GRPH_INTERRUPT_STATUS__GRPH_PFLIP_INT_CLEAR_MASK,\
		.ack_value = GRPH_INTERRUPT_STATUS__GRPH_PFLIP_INT_CLEAR_MASK,\
		.status_reg = mmDCP ## reg_num ##_GRPH_INTERRUPT_STATUS,\
		.funcs = &pflip_irq_info_funcs\
 	}

#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\
	}

#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_dce80.c:188-274) verbatim body ---- */
static const struct irq_source_info
irq_source_info_dce80[DAL_IRQ_SOURCES_NUMBER] = {
	[DC_IRQ_SOURCE_INVALID] = dummy_irq_entry(),
	hpd_int_entry(1),
	hpd_int_entry(2),
	hpd_int_entry(3),
	hpd_int_entry(4),
	hpd_int_entry(5),
	hpd_int_entry(6),
	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),
	hpd_rx_int_entry(6),
	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),
	pflip_int_entry(4),
	pflip_int_entry(5),
	[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_DF5054
	/* 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
};

/* ---- to_dal_irq_source_dce110 (irq_service_dce110.c:315-393) verbatim
 *      (the converter dce80 installs, irq_service_dce80.c:277) ---- */
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;
	}
}

/* ---- irq_service.c:77-158 verbatim ---- */
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;
	}

	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);	/* 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);
	struct irq_service_type svc = { .ctx = NULL,
					.info = irq_source_info_dce80 };
	int i, gaps = 0;

	printf("== DF-5054 replica: dce80 table gap -> NULL info->funcs "
		"==\n%s\n\n",
#ifdef FIX_DF5054
		"mode: FIXED (six VLINE0 dummy entries)"
#else
		"mode: BASELINE (table/dispatch verbatim)"
#endif
		);

	printf("enum: HPD6=%d HPD1RX=%d HPD6RX=%d PFLIP1=%d GPIOPAD0=%d "
	       "DC1UNDERFLOW=%d VUPDATE1=%d VBLANK1=%d DC1_VLINE0=%d "
	       "DC6_VLINE0=%d DAL_IRQ_SOURCES_NUMBER=%d\n\n",
	       (int)DC_IRQ_SOURCE_HPD6, (int)DC_IRQ_SOURCE_HPD1RX,
	       (int)DC_IRQ_SOURCE_HPD6RX, (int)DC_IRQ_SOURCE_PFLIP1,
	       (int)DC_IRQ_SOURCE_GPIOPAD0, (int)DC_IRQ_SOURCE_DC1UNDERFLOW,
	       (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_dce80[]\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 (dce80 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)mmDCP0_GRPH_INTERRUPT_CONTROL,
		       fake_regs[mmDCP0_GRPH_INTERRUPT_CONTROL & 0xffff]);

	/* control 3: the dce80-local hpd_ack runs (polarity toggle) */
	fake_regs[mmDC_HPD1_INT_STATUS & 0xffff] =
					1 << 6; /* DC_HPD1_SENSE_DELAYED */
	fake_regs[mmDC_HPD1_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[mmDC_HPD1_INT_CONTROL & 0xffff]);

	/* control 4 (hunt item c): to_dal_irq_source_dce110 — the IH-driven
	 * entry into the service — never returns 84..89, over a full
	 * 0..255 src_id sweep (all VISLANDS30 ids live there) x the 13
	 * meaningful ext_ids */
	{
		int s, e, vline = 0, mapped = 0;
		for (s = 0; s < 256; s++) {
			uint32_t exts[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
					    11, 0xdead };
			for (e = 0; e < 13; e++) {
				enum dc_irq_source r =
					to_dal_irq_source_dce110(&svc, 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 sweep (256 src x 13 ext): %d mapped to "
		       "covered sources, %d to VLINE0 84..89 (expect 0)%s\n",
		       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;
}
