/*
 * DF-4933 replica — dce110_vblank_set() pipe_offset underflow for VUPDATE
 *
 * Verbatim userland replica of:
 *   - to_dal_irq_source_dce110()
 *       (sys/dev/drm/amd/display/dc/irq/dce110/irq_service_dce110.c:315-393)
 *   - dce110_vblank_set()
 *       (sys/dev/drm/amd/display/dc/irq/dce110/irq_service_dce110.c:195-219)
 *   - vupdate_int_entry()/vblank_int_entry() macros
 *       (sys/dev/drm/amd/display/dc/irq/dce110/irq_service_dce110.c:127-158)
 *       — vupdate_int_entry does NOT set .src_id/.ext_id, so those struct
 *       members are 0 for DC_IRQ_SOURCE_VUPDATE1..6
 *   - dal_irq_service_set()/find_irq_source_info()
 *       (sys/dev/drm/amd/display/dc/irq/irq_service.c:77-124)
 *   - enum dc_irq_source / IRQ_TYPE_VBLANK
 *       (sys/dev/drm/amd/display/dc/irq_types.h:40-155:
 *        DC_IRQ_SOURCE_INVALID=0 ... VUPDATE1=72..77, VBLANK1=78..83,
 *        IRQ_TYPE_VBLANK = DC_IRQ_SOURCE_VBLANK1)
 *   - IV src-id constants verbatim from
 *       sys/dev/drm/amd/include/ivsrcid/ivsrcid_vislands30.h
 *
 * dce110_vblank_set() re-derives the pipe from info->src_id:
 *   dc_interrupt_to_irq_source(dc, 0, 0) -> to_dal_irq_source_dce110(0,0)
 *     -> default -> DC_IRQ_SOURCE_INVALID (0)
 *   pipe_offset = (uint8_t)(0 - 78) = 178
 *   core_dc->current_state->res_ctx.pipe_ctx[178]   <-- MAX_PIPES(6) array
 * -> far out-of-bounds read of a `tg` pointer, then
 *    tg->funcs->arm_vert_intr(tg, 2) — a wild indirect call.
 *
 * The replica models current_state as the first object in an 8 MiB mapping
 * poisoned with 0x5a (an OOB read in the kernel lands in whatever follows
 * the dc_state allocation; the poison makes the wild pointer deterministic).
 * pipe_ctx replicas are 64 bytes (the real one is far larger — the OOB
 * distance is even bigger); MAX_PIPES = 6 as on dce110.
 *
 * Build: cc -O2 -Wall -std=gnu11 [-DFIX_DF4933] -o dce110_vupd dce110_vupd.c
 * Exit:  0 ok / signal SIGSEGV on the VUPDATE set (baseline bug demonstrated)
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/mman.h>

typedef uint32_t uint32;
typedef uint8_t uint8;

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

/* ---- ivsrcid_vislands30.h (needed slice) verbatim ---- */
#define VISLANDS30_IV_SRCID_D1_V_UPDATE_INT		7	// 0x07
#define VISLANDS30_IV_SRCID_D2_V_UPDATE_INT		9	// 0x09
#define VISLANDS30_IV_SRCID_D3_V_UPDATE_INT		11	// 0x0b
#define VISLANDS30_IV_SRCID_D4_V_UPDATE_INT		13	// 0x0d
#define VISLANDS30_IV_SRCID_D5_V_UPDATE_INT		15	// 0x0f
#define VISLANDS30_IV_SRCID_D6_V_UPDATE_INT		17	// 0x11
#define VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0	19	// 0x13
#define VISLANDS30_IV_SRCID_D2_VERTICAL_INTERRUPT0	20	// 0x14
#define VISLANDS30_IV_SRCID_D3_VERTICAL_INTERRUPT0	21	// 0x15
#define VISLANDS30_IV_SRCID_D4_VERTICAL_INTERRUPT0	22	// 0x16
#define VISLANDS30_IV_SRCID_D5_VERTICAL_INTERRUPT0	23	// 0x17
#define VISLANDS30_IV_SRCID_D6_VERTICAL_INTERRUPT0	24	// 0x18
#define VISLANDS30_IV_SRCID_HOTPLUG_DETECT_A		6	// 0x06
#define VISLANDS30_IV_EXTID_HOTPLUG_DETECT_A		1
#define VISLANDS30_IV_EXTID_HOTPLUG_DETECT_B		2
#define VISLANDS30_IV_EXTID_HOTPLUG_DETECT_C		3
#define VISLANDS30_IV_EXTID_HOTPLUG_DETECT_D		4
#define VISLANDS30_IV_EXTID_HOTPLUG_DETECT_E		5
#define VISLANDS30_IV_EXTID_HOTPLUG_DETECT_F		6
#define VISLANDS30_IV_EXTID_HPD_RX_A			10
#define VISLANDS30_IV_EXTID_HPD_RX_B			11
#define VISLANDS30_IV_EXTID_HPD_RX_C			12
#define VISLANDS30_IV_EXTID_HPD_RX_D			13
#define VISLANDS30_IV_EXTID_HPD_RX_E			14
#define VISLANDS30_IV_EXTID_HPD_RX_F			15

/* ---- DC object model (members used by dce110_vblank_set) ---- */
#define MAX_PIPES 6			/* dce110 */

struct timing_generator;
struct tg_funcs {
	bool (*arm_vert_intr)(struct timing_generator *tg, uint8_t n);
};
struct timing_generator {
	const struct tg_funcs *funcs;
};

struct stream_res {
	struct timing_generator *tg;
};
struct pipe_ctx {			/* 64-byte replica; real is larger */
	struct stream_res stream_res;
	char pad[64 - sizeof(struct stream_res)];
};
struct resource_ctx {
	struct pipe_ctx pipe_ctx[MAX_PIPES];
};
struct dc_state {
	struct resource_ctx res_ctx;
};
struct dc {
	struct dc_state *current_state;
};
struct dc_context {
	struct dc *dc;
};

/* ---- irq_service.h:44-79 verbatim (slice) ---- */
struct irq_service;
struct irq_source_info;
struct irq_source_info_funcs {
	bool (*set)(struct irq_service *irq_service,
		    const struct irq_source_info *info, bool enable);
	bool (*ack)(struct irq_service *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 {
	struct dc_context *ctx;
	const struct irq_source_info *info;
};

static uint32 fake_regs[65536];
static uint32 dm_read_reg(struct dc_context *c, uint32 a)
{ (void)c; return fake_regs[a & 0xffff]; }
static void dm_write_reg(struct dc_context *c, uint32 a, uint32 v)
{ (void)c; fake_regs[a & 0xffff] = v; }

/* ---- irq_service_dce110.c:127-158 entry macros verbatim ---- */
#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 CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK		0x1
#define CRTC_V_UPDATE_INT_STATUS__CRTC_V_UPDATE_INT_CLEAR_MASK		0x1
#define CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK 0x1
#define CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_CLEAR_MASK      0x100

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

/* ---- to_dal_irq_source_dce110 (:315-393) verbatim ---- */
static enum dc_irq_source to_dal_irq_source_dce110(
		struct irq_service *irq_service,
		uint32 src_id,
		uint32 ext_id)
{
	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;
	default:
		return DC_IRQ_SOURCE_INVALID;
	}
}

static enum dc_irq_source dc_interrupt_to_irq_source(
		struct dc *dc, uint32 src_id, uint32 ext_id)
{
	(void)dc;	/* core/dc.c:1673-1678: straight into the service */
	return to_dal_irq_source_dce110(NULL, src_id, ext_id);
}

/* ---- dce110_vblank_set (:195-219) verbatim (FIX swaps pipe derivation) */
static int last_armed_pipe = -1;
static struct timing_generator *last_armed_tg;
static struct timing_generator tg_stub[MAX_PIPES];
static bool arm_stub_idx(struct timing_generator *tg, uint8 n)
{
	int i;
	for (i = 0; i < MAX_PIPES; i++)
		if (tg == &tg_stub[i])
			last_armed_pipe = i;
	last_armed_tg = tg;
	(void)n;
	return true;
}
static const struct tg_funcs tg_funcs_stub = { .arm_vert_intr = arm_stub_idx };

static void dal_irq_service_set_generic(struct irq_service *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);
}

bool dce110_vblank_set(struct irq_service *irq_service,
		       const struct irq_source_info *info,
		       bool enable)
{
	struct dc_context *dc_ctx = irq_service->ctx;
	struct dc *core_dc = irq_service->ctx->dc;
#ifdef FIX_DF4933
	/* fixed: derive the pipe from the table index — always 0..5 */
	enum dc_irq_source source =
		(enum dc_irq_source)(info - irq_service->info);
	uint8_t pipe_offset;

	(void)dc_ctx;
	if (source >= DC_IRQ_SOURCE_VUPDATE1 &&
	    source <= DC_IRQ_SOURCE_VUPDATE6)
		pipe_offset = source - DC_IRQ_SOURCE_VUPDATE1;
	else
		pipe_offset = source - DC_IRQ_SOURCE_VBLANK1;
	printf("   [fixed] source=%d -> pipe_offset=%u\n",
	       (int)source, pipe_offset);
#else
	enum dc_irq_source dal_irq_src =
			dc_interrupt_to_irq_source(irq_service->ctx->dc,
						   info->src_id,
						   info->ext_id);
	uint8_t pipe_offset = dal_irq_src - IRQ_TYPE_VBLANK;

	(void)dc_ctx;
#endif

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

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

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

/* the vupdate/vblank table slice, verbatim shape */
static const struct irq_source_info
irq_source_info_dce110[DAL_IRQ_SOURCES_NUMBER] = {
	[DC_IRQ_SOURCE_INVALID] = { .funcs = NULL },
	vupdate_int_entry(0),
	vupdate_int_entry(1),
	vupdate_int_entry(2),
	vupdate_int_entry(3),
	vupdate_int_entry(4),
	vupdate_int_entry(5),
	vblank_int_entry(0),
	vblank_int_entry(1),
	vblank_int_entry(2),
	vblank_int_entry(3),
	vblank_int_entry(4),
	vblank_int_entry(5),
};

/* ---- irq_service.c:77-124 set path verbatim (slice) ---- */
static
const struct irq_source_info *find_irq_source_info(
	struct irq_service *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 bool dal_irq_service_set(struct irq_service *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__, (int)source);
		return false;
	}

	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);
	const size_t REGION = 8u << 20;
	void *poison = mmap(NULL, REGION, PROT_READ | PROT_WRITE,
			    MAP_ANON | MAP_PRIVATE, -1, 0);
	struct dc_state *state;
	struct dc dc;
	struct dc_context ctx;
	struct irq_service svc;
	int i;

	if (poison == MAP_FAILED) {
		perror("mmap");
		return 1;
	}
	memset(poison, 0x5a, REGION);		/* wild-pointer determinism */

	/* current_state at region start; only pipes 0..5 get valid TGs */
	state = (struct dc_state *)poison;
	for (i = 0; i < MAX_PIPES; i++) {
		tg_stub[i].funcs = &tg_funcs_stub;
		state->res_ctx.pipe_ctx[i].stream_res.tg = &tg_stub[i];
	}

	dc.current_state = state;
	ctx.dc = &dc;
	svc.ctx = &ctx;
	svc.info = irq_source_info_dce110;

	printf("== DF-4933 replica: dce110_vblank_set pipe_offset underflow "
	       "==\n%s\n\n",
#ifdef FIX_DF4933
	       "mode: FIXED (pipe derived from table index)"
#else
	       "mode: BASELINE (kernel code verbatim)"
#endif
	       );

	printf("enum: INVALID=%d VUPDATE1=%d VBLANK1=%d IRQ_TYPE_VBLANK=%d\n",
	       (int)DC_IRQ_SOURCE_INVALID, (int)DC_IRQ_SOURCE_VUPDATE1,
	       (int)DC_IRQ_SOURCE_VBLANK1, (int)IRQ_TYPE_VBLANK);
	printf("table: vupdate src_id=%d ext_id=%d | vblank1 src_id=%d\n\n",
	       (int)irq_source_info_dce110[DC_IRQ_SOURCE_VUPDATE1].src_id,
	       (int)irq_source_info_dce110[DC_IRQ_SOURCE_VUPDATE1].ext_id,
	       (int)irq_source_info_dce110[DC_IRQ_SOURCE_VBLANK1].src_id);

	/* control: VBLANK sources map back correctly, pipe 0..5 */
	for (i = 0; i < MAX_PIPES; i++) {
		printf("control VBLANK%d set: ", i + 1);
		last_armed_pipe = -1;
		if (!dal_irq_service_set(&svc,
					 (enum dc_irq_source)(DC_IRQ_SOURCE_VBLANK1 + i),
					 true)) {
			printf("FAILED\n");
			return 2;
		}
		printf("armed pipe %d (expected %d)\n", last_armed_pipe, i);
	}

	/*
	 * Trigger: enable of a VUPDATE source.
	 *   info->src_id == 0 (never initialized by vupdate_int_entry)
	 *   to_dal_irq_source_dce110(0,0) -> DC_IRQ_SOURCE_INVALID
	 *   pipe_offset = (uint8_t)(0 - 78) = 178
	 *   pipe_ctx[178] (MAX_PIPES=6) -> 0x5a5a5a5a5a5a5a5a -> wild call
	 */
	printf("\ntrigger: dal_irq_service_set(DC_IRQ_SOURCE_VUPDATE1)\n");
	printf("   pipe_offset = (uint8_t)(DC_IRQ_SOURCE_INVALID - "
	       "IRQ_TYPE_VBLANK) = (uint8_t)(%d) = %u\n",
	       (int)(DC_IRQ_SOURCE_INVALID - IRQ_TYPE_VBLANK),
	       (uint8_t)(DC_IRQ_SOURCE_INVALID - IRQ_TYPE_VBLANK));
	printf("   -> pipe_ctx[%u] on a %d-entry array; tg = %p"
	       " (poison) -> tg->funcs->arm_vert_intr ...\n",
	       (uint8_t)(DC_IRQ_SOURCE_INVALID - IRQ_TYPE_VBLANK), MAX_PIPES,
	       (void *)state->res_ctx.pipe_ctx[178].stream_res.tg);

	if (dal_irq_service_set(&svc, DC_IRQ_SOURCE_VUPDATE1, true))
		printf("\nsurvived VUPDATE1 set\n");

#ifdef FIX_DF4933
	for (i = 1; i < MAX_PIPES; i++) {
		printf("fixed VUPDATE%d set: ", i + 1);
		last_armed_pipe = -1;
		if (!dal_irq_service_set(&svc,
					 (enum dc_irq_source)(DC_IRQ_SOURCE_VUPDATE1 + i),
					 true))
			return 2;
		printf("armed pipe %d (expected %d)\n", last_armed_pipe, i);
	}
	printf("\nRESULT: all VUPDATE/VBLANK sources arm the correct pipe\n");
#endif
	return 0;
}
