⬢ DragonFlyBSD Kernel Audit
DF-5055 / dce80_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
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
/*
 * DF-5055 replica — dce80 vupdate entries carry no .src_id, so
 * dce110_vblank_set() (which dce80's vblank_irq_info_funcs wires as .set,
 * irq_service_dce80.c:82-85,145) re-derives the source from (src_id=0, ext_id=0),
 * gets DC_IRQ_SOURCE_INVALID, and underflows pipe_offset to 178 before
 * indexing pipe_ctx[] — the DF-4933 twin on the DCE80 table wiring.
 *
 * 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)
 *   - dce80 vupdate_int_entry / vblank_int_entry macros and the vblank
 *     funcs table                              (irq_service_dce80.c:82-85,132-163)
 *   - 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:1672-1678)
 *   - find_irq_source_info / dal_irq_service_set(+generic)
 *                                            (irq_service.c:77-124)
 *   - the dc -> current_state -> res_ctx.pipe_ctx[] walk dce110_vblank_set
 *     performs, with MAX_PIPES=6 (DCE80 has 6 pipes) against a poisoned
 *     tail so the far-OOB read (pipe 178) yields a garbage tg that is
 *     immediately called through tg->funcs->arm_vert_intr(tg, 2).
 *
 * dce80's vblank_int_entry sets .src_id = VISLANDS30_IV_SRCID_D1_VERTICAL_
 * INTERRUPT0 + reg_num (19..24, consecutive — correct), so the in-tree-
 * reachable VBLANK path arms pipes 0..5 and is fine.  dce80's
 * vupdate_int_entry initializes NO .src_id (irq_service_dce80.c:132-146):
 * for DC_IRQ_SOURCE_VUPDATE1..6 the info has src_id=0/ext_id=0, the
 * re-derivation to_dal_irq_source_dce110(0,0) hits the default case and
 * returns DC_IRQ_SOURCE_INVALID (0), and
 *      uint8_t pipe_offset = dal_irq_src - IRQ_TYPE_VBLANK;        0 - 78
 *      struct timing_generator *tg =
 *          core_dc->current_state->res_ctx.pipe_ctx[pipe_offset].stream_res.tg;
 * becomes pipe_ctx[178] on a 6-entry array — the same far-OOB read + wild
 * arm_vert_intr indirect call as DF-4933 (dce110), reached through the
 * dce80 table's own macros.  (Note the defect is double-baked upstream
 * too: even a correctly remapped VUPDATE source would still underflow,
 * because the subtraction base is IRQ_TYPE_VBLANK, not IRQ_TYPE_VUPDATE.)
 *
 * Reachability is latent and stated plainly (same as DF-4933): no in-tree
 * caller ever .set's a VUPDATE source (suspend/resume touch HPD/HPDRX
 * only, amdgpu_dm_irq.c:353/380/406; vblank enable is IRQ_TYPE_VBLANK +
 * otg_inst, amdgpu_dm.c:3014-3015; pflip is IRQ_TYPE_PFLIP,
 * amdgpu_dm_irq.c:570; the IH path converts through
 * to_dal_irq_source_dce110 and amdgpu_dm never registers the V_UPDATE
 * src_ids 7,9,..,17), and the guest has no AMD GPU — the memory-safety
 * shape (OOB read feeding an indirect call) is one future caller away.
 *
 * Build: cc -O2 -Wall -std=gnu11 [-DFIX_DF5055] -o dce80_vupd dce80_vupd.c
 * Exit:  0 ok (fixed) / 98 trigger survived baseline (BAD) / signal (baseline crash)
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/mman.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; }

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

/* ---- 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_dce80 = {
	.to_dal_irq_source = to_dal_irq_source_dce110	/* irq_service_dce80.c:277 */
};

/* ---- the dc-walk types dce110_vblank_set touches, minimally shaped.
 * pipe_ctx is padded to a fixed 4096-byte stride so pipe_ctx[178] lands
 * ~728KB past the array head — far outside the valid 6 pipes and inside
 * the poisoned mapping (mirrors the kernel's MAX_PIPES=6 array on DCE80). */
#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;			/* replica bookkeeping */
};
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;
};

/* the arm callback: records which pipe got armed (controls) */
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,
};

/* dc_interrupt_to_irq_source (core/dc.c:1672-1678) collapses to
 *   dal_irq_service_to_irq_source(dc->res_pool->irqs, src_id, ext_id)
 *     -> irq_service->funcs->to_dal_irq_source(...)   (irq_service.c:160-169) */
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);
}

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

/* ---- dce110_vblank_set (irq_service_dce110.c:195-219) verbatim body.
 * Only the two #ifdef'd pipe_offset lines differ in fixed mode; everything
 * else, including the `!tg || !tg->funcs->arm_vert_intr(tg, 2)` indirect
 * call, is verbatim. ---- */
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,
							/* kernel: irq_service->ctx->dc (ctx is
								 * struct dc_context * there) */
						   info->src_id,
						   info->ext_id);
	uint8_t pipe_offset;
	struct timing_generator *tg;

	(void)dc_ctx;	/* upstream keeps it for the DC_ERROR logger macro */

#ifdef FIX_DF5055
	{
		/* fix: derive the pipe from the table index, not from a
		 * re-mapped src_id — provably 0..5 for VUPDATE1..6 and
		 * VBLANK1..6 alike (this is DF-4933's fix shape) */
		enum dc_irq_source src =
			(enum dc_irq_source)(info - irq_service->info);
		pipe_offset = (uint8_t)(
			(src >= DC_IRQ_SOURCE_VUPDATE1 &&
			 src <= DC_IRQ_SOURCE_VUPDATE6) ?
			src - DC_IRQ_SOURCE_VUPDATE1 :
			src - DC_IRQ_SOURCE_VBLANK1);
	}
#else
	pipe_offset = dal_irq_src - IRQ_TYPE_VBLANK;	/* :205 verbatim */
#endif

	tg = core_dc->current_state->res_ctx.pipe_ctx[pipe_offset]
		.stream_res.tg;				/* :207-208 verbatim */

	if (enable) {
		if (!tg || !tg->funcs->arm_vert_intr(tg, 2)) {	/* :210-211 */
			DC_ERROR("Failed to get VBLANK!\n");
			return false;
		}
	}

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

/* ---- dce80 funcs table + entry macros (irq_service_dce80.c:82-85,132-163)
 *      verbatim (only the vupdate/vblank region is instantiated; the other
 *      table slots are irrelevant to this defect and left zero) ---- */
static const struct irq_source_info_funcs vblank_irq_info_funcs = {
	.set = dce110_vblank_set,		/* irq_service_dce80.c:83 */
	.ack = NULL
};

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

/* stubbed DCE80 register defines (inert data; unique values) */
#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 CRTC_INTERRUPT_CONTROL__CRTC_V_UPDATE_INT_MSK_MASK	0x00000010
#define CRTC_V_UPDATE_INT_STATUS__CRTC_V_UPDATE_INT_CLEAR_MASK	0x00000020
#define CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_INT_ENABLE_MASK	0x00000040
#define CRTC_VERTICAL_INTERRUPT0_CONTROL__CRTC_VERTICAL_INTERRUPT0_CLEAR_MASK		0x00000080

static const struct irq_source_info
irq_source_info_dce80[DAL_IRQ_SOURCES_NUMBER] = {
	[DC_IRQ_SOURCE_INVALID] = { .funcs = NULL },	/* untouched by this PoC */
	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 verbatim (only the set path is needed) ---- */
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 bool dal_irq_service_ack_stub(
	struct irq_service_type *irq_service,
	const struct irq_source_info *info)
{
	/* vblank_irq_info_funcs.ack == NULL -> dal_irq_service_ack takes the
	 * generic path; stubbed to keep this replica focused (see DF-5054's
	 * replica for the verbatim ack dispatch) */
	(void)irq_service; (void)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_stub(irq_service, info);	/* irq_service.c:116 */

	if (info->funcs->set)				/* irq_service.c:118 */
		return info->funcs->set(irq_service, info, enable);

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

int main(void)
{
	setvbuf(stdout, NULL, _IONBF, 0);
	static struct timing_generator tgs[MAX_PIPES];
	struct dc_context dc_ctx;
	struct dc_type core_dc;
	struct irq_service_type svc;
	struct dc_state *state;
	char *poison;
	int i;

	printf("== DF-5055 replica: dce80 vupdate (no .src_id) -> "
		"dce110_vblank_set pipe_ctx[178] OOB ==\n%s\n\n",
#ifdef FIX_DF5055
		"mode: FIXED (pipe_offset derived from table index)"
#else
		"mode: BASELINE (verbatim)"
#endif
		);

	/* the poisoned dc_state: pipes 0..5 valid, everything past the array
	 * is 0x5a garbage (the kernel analogue: any heap memory following
	 * the dc_state allocation) */
	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_dce80;
	svc.funcs = &irq_service_funcs_dce80;
	dc_ctx.dc = &core_dc;
	core_dc.irqs = &svc;
	core_dc.current_state = state;

	printf("enum: VUPDATE1=%d VUPDATE6=%d VBLANK1=%d VBLANK6=%d "
	       "IRQ_TYPE_VBLANK=%d MAX_PIPES=%d\n",
	       (int)DC_IRQ_SOURCE_VUPDATE1, (int)DC_IRQ_SOURCE_VUPDATE6,
	       (int)DC_IRQ_SOURCE_VBLANK1, (int)DC_IRQ_SOURCE_VBLANK6,
	       (int)IRQ_TYPE_VBLANK, MAX_PIPES);
	printf("table: vupdate1 src_id=%u ext_id=%u | vblank1 src_id=%u "
	       "(19..24)\n\n",
	       irq_source_info_dce80[DC_IRQ_SOURCE_VUPDATE1].src_id,
	       irq_source_info_dce80[DC_IRQ_SOURCE_VUPDATE1].ext_id,
	       irq_source_info_dce80[DC_IRQ_SOURCE_VBLANK1].src_id);

	/* controls: the in-tree-reachable VBLANK path arms pipes 0..5 */
	for (i = 0; i < 6; i++) {
		enum dc_irq_source s =
			(enum dc_irq_source)(DC_IRQ_SOURCE_VBLANK1 + i);
		printf("control VBLANK%d set:", i + 1);
		if (!dal_irq_service_set(&svc, s, true)) {
			printf(" FAILED (BAD)\n");
			return 98;
		}
	}
	printf("controls ok (vblank path arms pipes 0..5)\n\n");

	/* trigger: set(VUPDATE1) — src_id==0 -> to_dal returns INVALID ->
	 * pipe_offset = (uint8_t)(0 - 78) = 178 -> pipe_ctx[178] on a
	 * 6-entry array inside the poisoned mapping -> garbage tg ->
	 * tg->funcs->arm_vert_intr(tg,2) faults */
	printf("trigger: dal_irq_service_set(DC_IRQ_SOURCE_VUPDATE1=%d)\n"
	       "   -> dce110_vblank_set: dal_irq_src=INVALID(0), "
	       "pipe_offset = (uint8_t)(0-%d) = %u\n"
	       "   -> core_dc->current_state->res_ctx.pipe_ctx[%u] on a "
	       "%d-entry array ...\n",
	       (int)DC_IRQ_SOURCE_VUPDATE1, (int)IRQ_TYPE_VBLANK,
	       (unsigned char)(0 - IRQ_TYPE_VBLANK),
	       (unsigned char)(0 - IRQ_TYPE_VBLANK), MAX_PIPES);

	if (!dal_irq_service_set(&svc, DC_IRQ_SOURCE_VUPDATE1, true)) {
		printf("\ntrigger returned false without arming (BAD)\n");
		return 98;
	}
#ifdef FIX_DF5055
	printf("\n[fixed] VUPDATE1 -> pipe 0 armed via index-derived offset; "
	       "exit 0\n");
	return 0;
#else
	printf("\ntrigger SURVIVED baseline (BAD -- expected a fault)\n");
	return 98;
#endif
}