/*
 * DF-5078 replica — irq_source_info_dce120[] (irq_service_dce120.c:177-263)
 * designators cover exactly enum sources 0..83; the shared enum runs to
 * DAL_IRQ_SOURCES_NUMBER=90 (irq_types.h:40-148), so
 * DC_IRQ_SOURCE_DC{1..6}_VLINE0 (84..89) stay zero-initialized with
 * .funcs==NULL.  find_irq_source_info (irq_service.c:77-86) bounds-checks
 * only the INDEX, so dal_irq_service_ack (:152, invoked first by
 * dal_irq_service_set at :116) dereferences info->funcs->ack on NULL and
 * dal_irq_service_set (:118) info->funcs->set — page-0 read => kernel
 * panic; userland replica => SIGSEGV (exit 139).
 *
 * This is the DF-2168/DF-4713 (hub) / DF-4932 (dce110) / DF-4995 (dcn10) /
 * DF-5054 (dce80) NULL-funcs-dispatch family instance on the DCE120
 * (Vega10/12) table — the shape already noted for maintainers in DF-4932's
 * verdict ("dce120/dce80 tables share the shape").  Each asic table is its
 * own code; this file files the dce120 instance.
 *
 * 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)
 *   - the FULL dce120 entry macros + table body
 *                                          (irq_service_dce120.c:87-263)
 *   - dce120 hpd_ack                        (irq_service_dce120.c:40-65)
 *   - dummy set/ack                         (irq_service_dce110.c:177-192)
 *   - 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:1673-1678)
 *   - find_irq_source_info / dal_irq_service_set(+generic) / _ack(+generic)
 *                                          (irq_service.c:77-158)
 *   - the dc->current_state->res_ctx.pipe_ctx[] walk with MAX_PIPES=6
 *     (core_types.h:239-241, hw_shared.h:37) against a poisoned tail.
 *
 * Reachability is latent and stated plainly (same as the whole family):
 * the IH path converts through to_dal_irq_source_dce110 which can only
 * return covered sources or INVALID (dummy-handled); dm_irq_state uses
 * IRQ_TYPE_PFLIP/IRQ_TYPE_VBLANK + otg_inst hardware-bounded 0..5
 * (amdgpu_dm_irq.c:535-556); hpd_init/fini touch HPD/HPDRX only
 * (amdgpu_dm_irq.c:625-645); amdgpu_dm.c:3014 is IRQ_TYPE_VBLANK +
 * otg_inst.  Nothing passes 84..89 into a dce120 service today, and the
 * guest has no AMD GPU (0 PCI 1002: devices) — the NULL-dispatch panic is
 * one future caller away (a DM change using VLINE0 on DCE12, or the
 * DAL_VALID_IRQ_SRC_NUM off-by-one at irq_types.h:157-158 admitting
 * src==DAL_IRQ_SOURCES_NUMBER into any caller gating on it).
 *
 * Build: cc -O2 -Wall -std=gnu11 [-DFIX_DF5078] -o dce120_vline0gap dce120_vline0gap.c
 * Exit:  0 ok (fixed) / signal (baseline crash at the funcs deref)
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.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; }

/* fake reg-field helpers (get/set_reg_field_value on single-bit fields) */
#define get_reg_field_value(v, r, f)	((v) & r##__##f##_MASK)
#define set_reg_field_value(v, nv, r, f) \
	((v) = ((v) & ~r##__##f##_MASK) | ((nv) ? r##__##f##_MASK : 0))

/* stubbed DCE12 register defines (inert data; unique values) */
#define mmHPD0_DC_HPD_INT_CONTROL	0x2000
#define mmHPD1_DC_HPD_INT_CONTROL	0x2010
#define mmHPD2_DC_HPD_INT_CONTROL	0x2020
#define mmHPD3_DC_HPD_INT_CONTROL	0x2030
#define mmHPD4_DC_HPD_INT_CONTROL	0x2040
#define mmHPD5_DC_HPD_INT_CONTROL	0x2050
#define mmHPD0_DC_HPD_INT_STATUS		0x2001
#define mmHPD1_DC_HPD_INT_STATUS		0x2011
#define mmHPD2_DC_HPD_INT_STATUS		0x2021
#define mmHPD3_DC_HPD_INT_STATUS		0x2031
#define mmHPD4_DC_HPD_INT_STATUS		0x2041
#define mmHPD5_DC_HPD_INT_STATUS		0x2051
#define mmDCP0_GRPH_INTERRUPT_CONTROL	0x2100
#define mmDCP1_GRPH_INTERRUPT_CONTROL	0x2110
#define mmDCP2_GRPH_INTERRUPT_CONTROL	0x2120
#define mmDCP3_GRPH_INTERRUPT_CONTROL	0x2130
#define mmDCP4_GRPH_INTERRUPT_CONTROL	0x2140
#define mmDCP5_GRPH_INTERRUPT_CONTROL	0x2150
#define mmDCP0_GRPH_INTERRUPT_STATUS	0x2101
#define mmDCP1_GRPH_INTERRUPT_STATUS	0x2111
#define mmDCP2_GRPH_INTERRUPT_STATUS	0x2121
#define mmDCP3_GRPH_INTERRUPT_STATUS	0x2131
#define mmDCP4_GRPH_INTERRUPT_STATUS	0x2141
#define mmDCP5_GRPH_INTERRUPT_STATUS	0x2151
#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 HPD0_DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK		0x00000004
#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK		0x00000008
#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_INT_POLARITY_MASK	0x00000010
#define HPD0_DC_HPD_INT_STATUS__DC_HPD_SENSE_DELAYED_MASK	0x00000020
#define DCP0_GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK	0x00000040
#define DCP0_GRPH_INTERRUPT_STATUS__GRPH_PFLIP_INT_CLEAR_MASK	0x00000080
#define CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK	0x00000100
#define CRTC_V_UPDATE_INT_STATUS__CRTC_V_UPDATE_INT_CLEAR_MASK	0x00000200
#define CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK 0x00000400
#define CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_CLEAR_MASK	    0x00000800
#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK		0x00000004
#define HPD0_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK	0x00000008
#define DCP0_GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK	0x00000040
#define DCP0_GRPH_INTERRUPT_STATUS__GRPH_PFLIP_INT_CLEAR_MASK	0x00000080
#define CRTC0_CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK	0x00000100
#define CRTC0_CRTC_V_UPDATE_INT_STATUS__CRTC_V_UPDATE_INT_CLEAR_MASK	0x00000200
#define CRTC0_CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK 0x00000400
#define CRTC0_CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_CLEAR_MASK	    0x00000800
#define HPD1_DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define HPD1_DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define HPD1_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK		0x00000004
#define HPD1_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK	0x00000008
#define DCP1_GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK	0x00000040
#define DCP1_GRPH_INTERRUPT_STATUS__GRPH_PFLIP_INT_CLEAR_MASK	0x00000080
#define CRTC1_CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK	0x00000100
#define CRTC1_CRTC_V_UPDATE_INT_STATUS__CRTC_V_UPDATE_INT_CLEAR_MASK	0x00000200
#define CRTC1_CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK 0x00000400
#define CRTC1_CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_CLEAR_MASK	    0x00000800
#define HPD2_DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define HPD2_DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define HPD2_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK		0x00000004
#define HPD2_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK	0x00000008
#define DCP2_GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK	0x00000040
#define DCP2_GRPH_INTERRUPT_STATUS__GRPH_PFLIP_INT_CLEAR_MASK	0x00000080
#define CRTC2_CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK	0x00000100
#define CRTC2_CRTC_V_UPDATE_INT_STATUS__CRTC_V_UPDATE_INT_CLEAR_MASK	0x00000200
#define CRTC2_CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK 0x00000400
#define CRTC2_CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_CLEAR_MASK	    0x00000800
#define HPD3_DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define HPD3_DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define HPD3_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK		0x00000004
#define HPD3_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK	0x00000008
#define DCP3_GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK	0x00000040
#define DCP3_GRPH_INTERRUPT_STATUS__GRPH_PFLIP_INT_CLEAR_MASK	0x00000080
#define CRTC3_CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK	0x00000100
#define CRTC3_CRTC_V_UPDATE_INT_STATUS__CRTC_V_UPDATE_INT_CLEAR_MASK	0x00000200
#define CRTC3_CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK 0x00000400
#define CRTC3_CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_CLEAR_MASK	    0x00000800
#define HPD4_DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define HPD4_DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define HPD4_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK		0x00000004
#define HPD4_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK	0x00000008
#define DCP4_GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK	0x00000040
#define DCP4_GRPH_INTERRUPT_STATUS__GRPH_PFLIP_INT_CLEAR_MASK	0x00000080
#define CRTC4_CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK	0x00000100
#define CRTC4_CRTC_V_UPDATE_INT_STATUS__CRTC_V_UPDATE_INT_CLEAR_MASK	0x00000200
#define CRTC4_CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK 0x00000400
#define CRTC4_CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_CLEAR_MASK	    0x00000800
#define HPD5_DC_HPD_INT_CONTROL__DC_HPD_INT_EN_MASK		0x00000001
#define HPD5_DC_HPD_INT_CONTROL__DC_HPD_INT_ACK_MASK		0x00000002
#define HPD5_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_EN_MASK		0x00000004
#define HPD5_DC_HPD_INT_CONTROL__DC_HPD_RX_INT_ACK_MASK	0x00000008
#define DCP5_GRPH_INTERRUPT_CONTROL__GRPH_PFLIP_INT_MASK_MASK	0x00000040
#define DCP5_GRPH_INTERRUPT_STATUS__GRPH_PFLIP_INT_CLEAR_MASK	0x00000080
#define CRTC5_CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK	0x00000100
#define CRTC5_CRTC_V_UPDATE_INT_STATUS__CRTC_V_UPDATE_INT_CLEAR_MASK	0x00000200
#define CRTC5_CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK 0x00000400
#define CRTC5_CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_CLEAR_MASK	    0x00000800
/* ---- 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

/* ---- the dc-walk types dce110_vblank_set touches, minimally shaped.
 * pipe_ctx padded to a 4096-byte stride (mirrors MAX_PIPES=6 on DCE12,
 * core_types.h:239-241 / hw_shared.h:37). */
#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;
};
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;
};

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,
};

static enum dc_irq_source to_dal_irq_source_dce110(
		struct irq_service_type *irq_service,
		uint32_t src_id, uint32_t ext_id);
static void dal_irq_service_set_generic(
		struct irq_service_type *irq_service,
		const struct irq_source_info *info,
		bool enable);
static void dal_irq_service_ack_generic(
		struct irq_service_type *irq_service,
		const struct irq_source_info *info);
static bool dal_irq_service_dummy_set(struct irq_service_type *irq_service,
			       const struct irq_source_info *info, bool enable);
static bool dal_irq_service_dummy_ack(struct irq_service_type *irq_service,
			       const struct irq_source_info *info);

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);
}

/* ---- dce110_vblank_set (irq_service_dce110.c:195-219), verbatim except
 *      the FIX_DF5079-style pipe derivation is NOT used here (this is the
 *      stock function; the vblank controls only reach pipe_offset 0..5 via
 *      the real conversion path). ---- */
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,
						   info->src_id,
						   info->ext_id);
	uint8_t pipe_offset = dal_irq_src - IRQ_TYPE_VBLANK;

	struct timing_generator *tg =
			core_dc->current_state->res_ctx.pipe_ctx[pipe_offset]
			.stream_res.tg;

	(void)dc_ctx;
	if (enable) {
		if (!tg || !tg->funcs->arm_vert_intr(tg, 2)) {
			DC_ERROR("Failed to get VBLANK!\n");
			return false;
		}
	}

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

/* ---- 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_dce120 = {
	.to_dal_irq_source = to_dal_irq_source_dce110	/* irq_service_dce120.c:266 */
};

/* ---- dce120 hpd_ack (irq_service_dce120.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,
			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;
}

/* ---- dce120 funcs tables (irq_service_dce120.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
};

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

/* ---- dce120 entry macros (irq_service_dce120.c:87-175) verbatim.
 *      SRI() collapses to the straight mm<block><id>_<reg> define (unique
 *      fake register numbers — inert data for the fake register file);
 *      IRQ_REG_ENTRY expansion is preserved verbatim. ---- */
#define SRI(reg_name, block, id) \
	mm ## block ## id ## _ ## reg_name

#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(DCP, reg_num, \
			GRPH_INTERRUPT_CONTROL, GRPH_PFLIP_INT_MASK, \
			GRPH_INTERRUPT_STATUS, GRPH_PFLIP_INT_CLEAR),\
		.status_reg = SRI(GRPH_INTERRUPT_STATUS, DCP, reg_num),\
		.funcs = &pflip_irq_info_funcs\
	}

#define vupdate_int_entry(reg_num)\
	[DC_IRQ_SOURCE_VUPDATE1 + reg_num] = {\
		IRQ_REG_ENTRY(CRTC, reg_num,\
			CRTC_INTERRUPT_CONTROL, CRTC_V_UPDATE_INT_MSK,\
			CRTC_V_UPDATE_INT_STATUS, CRTC_V_UPDATE_INT_CLEAR),\
		.funcs = &vblank_irq_info_funcs\
	}

#define vblank_int_entry(reg_num)\
	[DC_IRQ_SOURCE_VBLANK1 + reg_num] = {\
		IRQ_REG_ENTRY(CRTC, reg_num,\
				CRTC_VERTICAL_INTERRUPT0_CONTROL, CRTC_VERTICAL_INTERRUPT0_INT_ENABLE,\
				CRTC_VERTICAL_INTERRUPT0_CONTROL, CRTC_VERTICAL_INTERRUPT0_CLEAR),\
		.funcs = &vblank_irq_info_funcs,\
		.src_id = VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0 + reg_num\
	}

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

#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()

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


/* ---- THE dce120 TABLE (irq_service_dce120.c:177-263) verbatim ---- */
static const struct irq_source_info
irq_source_info_dce120[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_DF5078
	/* fix.diff: complete the table 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 ---- */
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 — NULL deref
						 * when funcs == NULL */
		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;
}

static enum dc_irq_source dal_irq_service_to_irq_source(
		struct irq_service_type *irq_service,
		uint32_t src_id,
		uint32_t ext_id)
{
	return irq_service->funcs->to_dal_irq_source(
		irq_service,
		src_id,
		ext_id);
}

static struct irq_service_type svc;
static struct dc_context dc_ctx;
static struct dc_type core_dc;
static struct dc_state *state;

int main(void)
{
	static struct timing_generator tgs[MAX_PIPES];
	char *poison;
	int i, n_null = 0, bad = 0;

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("== DF-5078 replica: dce120 table VLINE0 gap (84..89) -> "
		"NULL .funcs dispatch ==\n%s\n\n",
#ifdef FIX_DF5078
		"mode: FIXED (six VLINE0 dummy_irq_entry()s — fix.diff)"
#else
		"mode: BASELINE (verbatim table)"
#endif
		);

	/* poisoned dc_state: pipes 0..5 valid, everything past is 0x5a */
	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_dce120;
	svc.funcs = &irq_service_funcs_dce120;
	dc_ctx.dc = &core_dc;
	core_dc.irqs = &svc;
	core_dc.current_state = state;

	printf("enum: VBLANK6=%d DC1_VLINE0=%d DC6_VLINE0=%d "
		"DAL_IRQ_SOURCES_NUMBER=%d\n\n",
		(int)DC_IRQ_SOURCE_VBLANK6, (int)DC_IRQ_SOURCE_DC1_VLINE0,
		(int)DC_IRQ_SOURCE_DC6_VLINE0, (int)DAL_IRQ_SOURCES_NUMBER);

	/* 1. static table audit — which enum slots have funcs == NULL? */
	printf("[audit] NULL-funcs entries:");
	for (i = 0; i < (int)DAL_IRQ_SOURCES_NUMBER; i++)
		if (irq_source_info_dce120[i].funcs == NULL) {
			printf(" %d", i);
			n_null++;
		}
	printf("%s (%d NULL-funcs slot%s)\n", n_null ? "" : " none",
		n_null, n_null == 1 ? "" : "s");

	/* 2. controls: index bounds + generic paths + hpd_ack + vblank */
	printf("[ctl] ack(OOB=90) rejected: ");
	if (dal_irq_service_ack(&svc, (enum dc_irq_source)90))
		bad++, printf("NOT rejected (BAD)");
	else
		printf("yes");
	printf("\n[ctl] set(PFLIP1) generic path: ");
	if (!dal_irq_service_set(&svc, DC_IRQ_SOURCE_PFLIP1, true))
		bad++, printf("FAILED");
	else
		printf("ok (enable_reg now 0x%x)",
		       fake_regs[mmDCP0_GRPH_INTERRUPT_CONTROL & 0xffff]);
	printf("\n[ctl] hpd_ack(HDP1) polarity toggle: ");
	fake_regs[mmHPD0_DC_HPD_INT_STATUS & 0xffff] =
		HPD0_DC_HPD_INT_STATUS__DC_HPD_SENSE_DELAYED_MASK;
	if (!dal_irq_service_ack(&svc, DC_IRQ_SOURCE_HPD1))
		bad++, printf("FAILED");
	else
		printf("ok (polarity bit now %u)",
		       !!(fake_regs[mmHPD0_DC_HPD_INT_CONTROL & 0xffff] &
			  HPD0_DC_HPD_INT_CONTROL__DC_HPD_INT_POLARITY_MASK));
	printf("\n[ctl] VBLANK1..6 through dce110_vblank_set (real "
		"conversion, pipes 0..5):\n");
	for (i = 0; i < 6; i++)
		if (!dal_irq_service_set(&svc,
				(enum dc_irq_source)(DC_IRQ_SOURCE_VBLANK1 + i),
				true)) {
			printf("      VBLANK%d FAILED (BAD)\n", i + 1);
			bad++;
		}

	/* 3. to_dal sweep: can any (src_id, ext_id) reach 84..89? */
	{
		int hits = 0;
		uint32_t exts[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0xdead };
		for (i = 0; i < 256; i++) {
			size_t j;
			for (j = 0; j < sizeof(exts) / sizeof(exts[0]); j++) {
				enum dc_irq_source r = dal_irq_service_to_irq_source(
					&svc, (uint32_t)i, exts[j]);
				if (r >= DC_IRQ_SOURCE_DC1_VLINE0 && r < DAL_IRQ_SOURCES_NUMBER)
					hits++;
			}
		}
		printf("\n[ctl] to_dal sweep 256 src x 13 ext: %d return(s) "
			"in 84..89 (latent gap: no in-tree caller reaches it)\n",
			hits);
	}

	if (bad) {
		printf("\nCONTROLS FAILED (BAD)\n");
		return 98;
	}

	/* 4. TRIGGER — direct ack(84) in a child (irq_service.c:152 deref),
	 *    then set(84) in the MAIN process (:116 -> :152) */
#ifdef FIX_DF5078
	printf("\n[trigger] set/ack(DC_IRQ_SOURCE_DC1_VLINE0) with the fixed "
		"table: dummy handlers log-and-return, no fault\n");
	(void)dal_irq_service_ack(&svc, DC_IRQ_SOURCE_DC1_VLINE0);
	(void)dal_irq_service_set(&svc, DC_IRQ_SOURCE_DC1_VLINE0, true);
	printf("\nfixed: 0 faults, all controls ok -> exit 0\n");
	return 0;
#else
	{
		pid_t p = fork();
		int st;

		if (p == 0) {
			printf("\n[trigger-child] dal_irq_service_ack(DC_IRQ_SOURCE_"
				"DC1_VLINE0=%d): info->funcs == %p, dereferencing "
				"info->funcs->ack ...\n",
				(int)DC_IRQ_SOURCE_DC1_VLINE0,
				(void *)irq_source_info_dce120[
					DC_IRQ_SOURCE_DC1_VLINE0].funcs);
			dal_irq_service_ack(&svc, DC_IRQ_SOURCE_DC1_VLINE0);
			_exit(99);	/* should not get here */
		}
		waitpid(p, &st, 0);
		printf("[trigger-child] status: %s (exit %d)\n",
		       WIFSIGNALED(st) ? "SIGNALLED" : "exited",
		       WIFSIGNALED(st) ? WTERMSIG(st) : WEXITSTATUS(st));
		if (!WIFSIGNALED(st)) {
			printf("child SURVIVED (BAD)\n");
			return 98;
		}
	}

	printf("\n[trigger-main] dal_irq_service_set(DC_IRQ_SOURCE_DC1_VLINE0) "
		"— :116 acks first, same NULL funcs deref ...\n");
	dal_irq_service_set(&svc, DC_IRQ_SOURCE_DC1_VLINE0, true);
	printf("\ntrigger SURVIVED baseline (BAD -- expected SIGSEGV)\n");
	return 98;
#endif
}
