⬢ DragonFlyBSD Kernel Audit
DF-4933 / dce110_vupd.c
← back to finding ↓ download raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
/*
 * 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;
}