/*
 * DF-4932 replica — irq_service_dce110.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)
 *   - the funcs tables and irq_source_info_dce110[] entry macros + table body
 *                                            (sys/dev/drm/amd/display/dc/irq/dce110/irq_service_dce110.c:42-313)
 *   - 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 register addresses/masks (dce_11_0_d.h / dce_11_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, the
 * index bounds check, and the `info->funcs->set/ack` dereferences — is
 * verbatim.
 *
 * The enum runs to DAL_IRQ_SOURCES_NUMBER=90 with DC_IRQ_SOURCE_DC1_VLINE0..
 * DC6_VLINE0 = 84..89, but irq_source_info_dce110[] stops at DC_IRQ_SOURCE_VBLANK6
 * (=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)).
 *
 * Build: cc -O2 -Wall -std=gnu11 [-DFIX_DF4932] -o dce110_vline0gap dce110_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 ---- */
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 DCE11 register defines (inert data; unique values) */
#define mmHPD0_DC_HPD_INT_CONTROL	0x1000
#define mmHPD1_DC_HPD_INT_CONTROL	0x1001
#define mmHPD2_DC_HPD_INT_CONTROL	0x1002
#define mmHPD3_DC_HPD_INT_CONTROL	0x1003
#define mmHPD4_DC_HPD_INT_CONTROL	0x1004
#define mmHPD5_DC_HPD_INT_CONTROL	0x1005
#define mmHPD0_DC_HPD_INT_STATUS	0x1100
#define mmHPD1_DC_HPD_INT_STATUS	0x1101
#define mmHPD2_DC_HPD_INT_STATUS	0x1102
#define mmHPD3_DC_HPD_INT_STATUS	0x1103
#define mmHPD4_DC_HPD_INT_STATUS	0x1104
#define mmHPD5_DC_HPD_INT_STATUS	0x1105
#define mmDCP0_GRPH_INTERRUPT_CONTROL	0x1200
#define mmDCP1_GRPH_INTERRUPT_CONTROL	0x1201
#define mmDCP2_GRPH_INTERRUPT_CONTROL	0x1202
#define mmDCP3_GRPH_INTERRUPT_CONTROL	0x1203
#define mmDCP4_GRPH_INTERRUPT_CONTROL	0x1204
#define mmDCP5_GRPH_INTERRUPT_CONTROL	0x1205
#define mmDCP0_GRPH_INTERRUPT_STATUS	0x1300
#define mmDCP1_GRPH_INTERRUPT_STATUS	0x1301
#define mmDCP2_GRPH_INTERRUPT_STATUS	0x1302
#define mmDCP3_GRPH_INTERRUPT_STATUS	0x1303
#define mmDCP4_GRPH_INTERRUPT_STATUS	0x1304
#define mmDCP5_GRPH_INTERRUPT_STATUS	0x1305
#define mmCRTC0_CRTC_INTERRUPT_CONTROL	0x1400
#define mmCRTC1_CRTC_INTERRUPT_CONTROL	0x1401
#define mmCRTC2_CRTC_INTERRUPT_CONTROL	0x1402
#define mmCRTC3_CRTC_INTERRUPT_CONTROL	0x1403
#define mmCRTC4_CRTC_INTERRUPT_CONTROL	0x1404
#define mmCRTC5_CRTC_INTERRUPT_CONTROL	0x1405
#define mmCRTC0_CRTC_V_UPDATE_INT_STATUS	0x1500
#define mmCRTC1_CRTC_V_UPDATE_INT_STATUS	0x1501
#define mmCRTC2_CRTC_V_UPDATE_INT_STATUS	0x1502
#define mmCRTC3_CRTC_V_UPDATE_INT_STATUS	0x1503
#define mmCRTC4_CRTC_V_UPDATE_INT_STATUS	0x1504
#define mmCRTC5_CRTC_V_UPDATE_INT_STATUS	0x1505
#define mmCRTC0_CRTC_VERTICAL_INTERRUPT0_CONTROL	0x1600
#define mmCRTC1_CRTC_VERTICAL_INTERRUPT0_CONTROL	0x1601
#define mmCRTC2_CRTC_VERTICAL_INTERRUPT0_CONTROL	0x1602
#define mmCRTC3_CRTC_VERTICAL_INTERRUPT0_CONTROL	0x1603
#define mmCRTC4_CRTC_VERTICAL_INTERRUPT0_CONTROL	0x1604
#define mmCRTC5_CRTC_VERTICAL_INTERRUPT0_CONTROL	0x1605
#define DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK	0x00000100
#define DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK	0x00000200
#define GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK	0x00000001
#define GRPH_INTERRUPT_STATUS__GRPH_PFLIP_INT_CLEAR_MASK	0x00000001
#define CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK	0x00000001
#define CRTC_V_UPDATE_INT_STATUS__CRTC_V_UPDATE_INT_CLEAR_MASK	0x00000001
#define CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK 0x00000001
#define CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_CLEAR_MASK      0x00000100

/* ---- dce110 entry macros (irq_service_dce110.c:84-175) verbatim ---- */
#define hpd_int_entry(reg_num)\
	[DC_IRQ_SOURCE_HPD1 + reg_num] = {\
		.enable_reg = mmHPD ## reg_num ## _DC_HPD_INT_CONTROL,\
		.enable_mask = DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK,\
		.enable_value = {\
			DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK,\
			~DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK\
		},\
		.ack_reg = mmHPD ## reg_num ## _DC_HPD_INT_CONTROL,\
		.ack_mask = DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK,\
		.ack_value = DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK,\
		.status_reg = mmHPD ## reg_num ## _DC_HPD_INT_STATUS,\
		.funcs = &hpd_irq_info_funcs\
	}

#define hpd_rx_int_entry(reg_num)\
	[DC_IRQ_SOURCE_HPD1RX + reg_num] = {\
		.enable_reg = mmHPD ## reg_num ## _DC_HPD_INT_CONTROL,\
		.enable_mask = DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK,\
		.enable_value = {\
			DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK,\
			~DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK },\
		.ack_reg = mmHPD ## reg_num ## _DC_HPD_INT_CONTROL,\
		.ack_mask = DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK,\
		.ack_value = DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK,\
		.status_reg = mmHPD ## reg_num ## _DC_HPD_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 = 19 + 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()

/* funcs tables (:64-82) — .set/.ack NULL exactly as the file */
static bool dce110_vblank_set_stub(struct irq_service_type *s,
				   const struct irq_source_info *i, bool e)
{ (void)s; (void)i; (void)e; return true; }
static const struct irq_source_info_funcs hpd_irq_info_funcs = {
	.set = NULL, .ack = NULL };
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 = dce110_vblank_set_stub, .ack = NULL };

/* ---- the table (irq_service_dce110.c:226-313) verbatim body ---- */
static const struct irq_source_info
irq_source_info_dce110[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),
	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_DF4932
	/* 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_DF4932
	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_DF4932
	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_dce110 };
	int i, gaps = 0;

	printf("== DF-4932 replica: dce110 table gap -> NULL info->funcs "
	       "==\n%s\n\n",
#ifdef FIX_DF4932
	       "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_dce110[]\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 */
	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]);

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