⬢ DragonFlyBSD Kernel Audit
DF-2213 / dfrig_cyx.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
/*
 * dfrig_cyx.c -- DF-2213 verification rig
 *                (sys/dev/disk/nata/chipsets/ata-cyrix.c:98)
 *
 * Finding under test:
 *
 *	ata_cyrix_setmode() programs the Cyrix 5530 PIO timing register
 *	with
 *
 *	    ATA_OUTL(ch->r_io[ATA_BMCMD_PORT].res,
 *	             0x20 + (devno << 3),
 *	             piotiming[mode & ATA_MODE_MASK]);   <-- ata-cyrix.c:98
 *
 *	where piotiming[] has FIVE elements (ata-cyrix.c:67-68) but the
 *	ATA PIO mode constants start at 0x08 (sys/sys/nata.h:203-207:
 *	ATA_PIO0=0x08 .. ATA_PIO4=0x0c) and ATA_MODE_MASK is 0x0f
 *	(sys/sys/nata.h:200).  For every real PIO mode the masked index
 *	is therefore 8..12 -- reading 3..7 elements (12..28 bytes) past
 *	the end of the 20-byte table -- and the garbage constant so read
 *	is written to the disk controller PIO timing register.  In the
 *	shipped stock kernel the elements at piotiming[8..12] are
 *	dmatiming[2] / a hole / udmatiming[0..2] (see
 *	kernel_rodata.txt), so the standard boot probe (setmode settles
 *	at ATA_PIO4) programs a UDMA2 timing (0x00911030) where a PIO4
 *	timing (0x00040010) belongs: silent wrong-timing / disk data
 *	corruption class.  Every sibling driver indexes the same shape
 *	of table with ata_mode2idx() (ata-pci.c:719-726, the idiom that
 *	subtracts ATA_PIO0: e.g. ata-national.c:102 -- the NatSemi clone
 * of this very driver).
 *
 * Real dispatch chain that reaches the handler (all production cites):
 *
 *   boot reinit / ioctl(IOCATASMODE) sys/dev/disk/nata/ata-all.c:523-526
 *	ATA_SETMODE(parent, dev)                     (newbus method, ata_if.m)
 *   -> ata_pcichannel_setmode()  sys/dev/disk/nata/ata-pci.c:855-866
 *	ctlr->setmode(dev, ATA_PIO_MAX);             <-- extracted call site
 *	if (mode >= ATA_DMA) ctlr->setmode(dev, mode);
 *   -> ata_cyrix_setmode()  chipsets/ata-cyrix.c:60
 *   -> line 98: piotiming[mode & 0x0f] with mode>=0x08  => OOB .rodata read
 *      ATA_OUTL(...) writes the OOB-sourced constant into the controller
 *      PIO timing register (0x20 + devno*8 in the BMIO port space).
 *
 * The hardware is gated (Cyrix 5530, PCI ID 0x0100780e... actually
 * ATA_CYRIX_5530 per ata-pci.h), so this rig follows the established
 * production-handler dispatch class (DF-2210 national, DF-252x trm,
 * DF-249x nata):
 *
 *  - the production file chipsets/ata-cyrix.c is #included VERBATIM
 *    from the guest /usr/src tree (build.sh verifies the file sha256
 *    against the audited sys/ tree).  The ONLY textual intervention is
 *    `#define ata_cyrix_ident rig_unused_ata_cyrix_ident`, which renames
 *    the never-called ident() entry point to avoid a duplicate global
 *    against the built-in kernel copy; ata_cyrix_setmode itself is
 *    compiled byte-for-byte as written, static, under its production
 *    name.
 *
 *  - the RUNNING KERNEL's OWN ata_cyrix_setmode (local symbol, from
 *    `nm /boot/kernel/kernel`, address injected via sysctl) is ALSO
 *    dispatched -- the shipped binary, zero recompilation.  Its OOB
 *    reads come from the shipped kernel's own .rodata (whose layout
 *    was recovered by objdump: kernel_handler_disasm.txt /
 *    kernel_rodata.txt), and the rig cross-checks the recorded
 *    register write value against the live kernel memory at
 *    tbl_base + 4*index.
 *
 *  - the channel/controller/ata-device state is exactly what the
 *    machine presents on a working Cyrix 5530 channel: ctlr->r_res1
 *    allocated (the guard at ata-cyrix.c:84 passes -- THIS file guards
 *    its derefs, unlike the National clone of DF-2210), ch->dma present
 *    (alignment/max_iosize get overridden), disk present
 *    (ch->devices = ATA_ATA_MASTER) with per-scenario identify data
 *    driving ata_limit_mode() to the PIO mode under test.  The channel
 *    is in the ATA_STALL_QUEUE state with a begin_transaction that
 *    reports the drive accepting SETXFER, so the kernel's own
 *    production ata_controlcmd()/ata_queue_request() (ata-queue.c:100-
 *    115,158-190) run unmodified.  The ATA_OUTL timing writes dispatch
 *    through the real bus_space_write_4() onto a fabricated MEM-space
 *    bus tag + handle: on DragonFly/amd64 bus_space_write_4(tag==MEM)
 *    is `*(volatile u_int32_t *)(handle + offset) = value`, so the
 *    production register writes land in the rig's 128-byte "PCI
 *    register file" (fake_bmio) and get dumped -- the garbage timing
 *    landing in the controller register, captured.
 *
 * Tests (sysctl kern.dfrig_cyx.go=N), all on a STOCK kernel:
 *
 *   1  THE FINDING, shipped kernel handler: identify PIO4/UDMA2 disk,
 *      mode=ATA_PIO_MAX (exactly the first call of the extracted call
 *      site ata-pci.c:863).  Settles at ATA_PIO4 (0x0c) -> index 12 ->
 *      OOB read = udmatiming[2] = 0x00911030 -> regfile[0x20] gets a
 *      UDMA2 timing where piotiming[4]=0x00040010 belongs.
 *   2  THE FINDING, verbatim rig copy: identical dispatch through the
 *      #included production source (byte-identity of the copy).
 *   3  RANGE FLOOR: identify PIO0-only disk -> settles ATA_PIO0 (0x08)
 *      -> index 8 -> OOB read = dmatiming[2] = 0x00002020.
 *   4  FULL extracted call site (ata-pci.c:855-866) with requested
 *      ATA_UDMA2 (a UDMA disk at boot): call 1 = the OOB PIO write
 *      above; call 2 = udmatiming[2] (IN bounds -- the UDMA branch is
 *      correctly indexed) -> contrast: both controller slots end up
 *      holding UDMA timings, the PIO one illegitimately.
 *   5  CONTROL (correct branch): WDMA2-only identify, request
 *      ATA_WDMA2 -> dmatiming[2] in bounds -> regfile[0x24] =
 *      0x00002020 -- the WDMA branch indexes correctly; the defect is
 *      specific to the PIO branch (line 98).
 *
 * After every dispatch the rig prints: the settled mode, the computed
 * index (mode & ATA_MODE_MASK), the CORRECT index (kernel-exported
 * ata_mode2idx()) and the correct value (the five source constants),
 * the captured register-file slots, and a dump of the live kernel
 * piotiming[] window [0..13] with in-bounds/OOB annotation so the
 * recorded write value is attributable to a specific OOB element.
 */

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/bus_dma.h>
#include <sys/bus_resource.h>
#include <sys/callout.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/libkern.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/nata.h>
#include <sys/queue.h>
#include <sys/rman.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/bus_private.h>	/* struct bsd_device layout */

#include <bus/pci/pcireg.h>
#include <bus/pci/pcivar.h>

#include "ata-all.h"
#include "ata-pci.h"

/* ------------------------------------------------------------------ */
/* CHECKER NOTE: the production file below is included VERBATIM.       */
/* The single #define renames only the never-called ident() symbol so  */
/* this KLD does not collide with the built-in kernel copy; the        */
/* setmode function under test keeps its production name and body.     */
/* build.sh pins the file by sha256 before compiling.                  */
/* ------------------------------------------------------------------ */
int rig_unused_ata_cyrix_ident(device_t dev);	/* kproto */
#define ata_cyrix_ident rig_unused_ata_cyrix_ident
#include "chipsets/ata-cyrix.c"	/* <-- UNMODIFIED production code */
#undef ata_cyrix_ident

/* the five real PIO timings from the audited source (ata-cyrix.c:67-68),
 * used to (a) verify the kernel .rodata table was found and (b) show
 * the CORRECT value the controller should have received */
static const uint32_t rig_piotiming_src[5] =
    { 0x00009172, 0x00012171, 0x00020080, 0x00032010, 0x00040010 };

/* ------------------------------------------------------------------ */
/* channel HW completion fabrication: with ch->state == ATA_STALL_QUEUE */
/* the PRODUCTION ata_queue_request() (ata-queue.c:100-115) executes   */
/* ATA_R_CONTROL requests synchronously via ch->hw.begin_transaction   */
/* and returns immediately when it reports ATA_OP_FINISHED -- no       */
/* queueing, no tsleep, no interrupt.  Our begin_transaction stands    */
/* for the drive accepting SET FEATURES / SETXFER.  With this in place */
/* even the RUNNING KERNEL's own ata_controlcmd() and                  */
/* ata_queue_request() run unmodified.                                 */
/* ------------------------------------------------------------------ */
static int rig_cmd_seen;		/* begin_transaction dispatches */

static int
rig_begin_transaction(struct ata_request *request)
{
	rig_cmd_seen++;
	request->result = 0;			/* drive accepted SETXFER */
	request->flags |= ATA_R_COMPLETED;
	return ATA_OP_FINISHED;
}

/* ------------------------------------------------------------------ */
/* fabricated controller register file: the production ATA_OUTL at     */
/* ata-cyrix.c:89/93/97 expands to bus_space_write_4(res->r_bustag,    */
/* res->r_bushandle, offset, value); with r_bustag ==                  */
/* X86_64_BUS_SPACE_MEM that is *(volatile u_int32_t *)(handle+off)    */
/* (cpu/x86_64/include/bus_dma.h:404-412), so a MEM tag + kernel       */
/* buffer handle turns the production register writes into captures.   */
/* ------------------------------------------------------------------ */
#define RIG_BMIO_NREG	32			/* 128 bytes: 0x00..0x7f */
static u_int32_t fake_bmio[RIG_BMIO_NREG];
static struct resource fake_res;		/* tag/handle carrier */

/* ------------------------------------------------------------------ */
/* fake device graph -- the exact graph nata hands the chipset driver  */
/* when a disk sits on a working Cyrix 5530 channel:                    */
/*                                                                     */
/*   rig_bus_dev (opaque parent) <- fake_pci_dev (ctlr softc, r_res1)  */
/*        <- fake_ch_dev (struct ata_channel, dma present)             */
/*        <- fake_ata_dev (struct ata_device, unit=ATA_MASTER)         */
/* ------------------------------------------------------------------ */
static struct bsd_device fake_bus_dev, fake_pci_dev, fake_ch_dev;
static struct bsd_device fake_ata_dev;
static struct ata_channel fake_ch;
static struct ata_device fake_atadev;
static struct ata_dma fake_dma;
static struct ata_pci_controller fake_ctlr;

static unsigned long rig_kern_addr;	/* kernel own handler, via nm */
static unsigned long rig_kern_tbl;	/* kernel piotiming[] base */
static int rig_armed;

static void
rig_setup_graph(void)
{
	bzero(&fake_bus_dev, sizeof(fake_bus_dev));
	bzero(&fake_pci_dev, sizeof(fake_pci_dev));
	bzero(&fake_ch_dev, sizeof(fake_ch_dev));
	bzero(&fake_ata_dev, sizeof(fake_ata_dev));
	bzero(&fake_ch, sizeof(fake_ch));
	bzero(&fake_atadev, sizeof(fake_atadev));
	bzero(&fake_ctlr, sizeof(fake_ctlr));
	bzero(&fake_bmio, sizeof(fake_bmio));
	bzero(&fake_res, sizeof(fake_res));

	/* the BMIO resource: MEM-space tag, handle = register file */
	fake_res.r_bustag = 1;		/* X86_64_BUS_SPACE_MEM */
	fake_res.r_bushandle = (bus_space_handle_t)fake_bmio;

	/* pci <- channel <- ata device, exactly as GRANDPARENT walks it */
	fake_pci_dev.parent = &fake_bus_dev;
	fake_pci_dev.softc = &fake_ctlr;
	fake_ch_dev.parent = &fake_pci_dev;
	fake_ch_dev.softc = &fake_ch;
	fake_ata_dev.parent = &fake_ch_dev;
	fake_ata_dev.softc = &fake_atadev;

	/* controller: BMIO resource allocated -- the ata-cyrix.c:84 guard
	 * passes (this file guards its derefs, unlike DF-2210 national) */
	fake_ctlr.r_res1 = &fake_res;

	/* channel: working DMA, disk present, sync CONTROL completion */
	fake_ch.dev = &fake_ch_dev;
	fake_ch.unit = 0;
	fake_ch.devices = ATA_ATA_MASTER;
	fake_ch.state = ATA_STALL_QUEUE;
	fake_ch.hw.begin_transaction = rig_begin_transaction;
	fake_ch.r_io[ATA_BMCMD_PORT].res = &fake_res;
	fake_ch.r_io[ATA_BMCMD_PORT].offset = 0;
	lockinit(&fake_ch.state_mtx, "dfrig_cyx_st", 0, LK_CANRECURSE);
	lockinit(&fake_ch.queue_mtx, "dfrig_cyx_q", 0, LK_CANRECURSE);

	/* ata device: master; identify data set per scenario */
	fake_atadev.dev = &fake_ata_dev;
	fake_atadev.unit = ATA_MASTER;
	fake_atadev.mode = 0xff;
}

/* identify-data presets driving ata_limit_mode() to the mode under
 * test (ata-all.c ata_pmode/ata_wmode/ata_umode) */
static void
rig_identify_pio4_udma2(void)
{
	fake_atadev.param.atavalid = ATA_FLAG_64_70 | ATA_FLAG_88;
	fake_atadev.param.apiomodes = 0x02;	/* PIO4 (ata_pmode -> 0x0c) */
	fake_atadev.param.udmamodes = 0x04;	/* UDMA2 (ata_umode -> 0x42) */
	fake_atadev.param.mwdmamodes = 0x00;
	fake_atadev.param.retired_piomode = 0;
}

static void
rig_identify_pio0_only(void)
{
	/* no valid words 64-70/88, no mwdma, retired_piomode==0:
	 * ata_pmode() -> ATA_PIO0 (0x08), ata_umode() -> -1 */
	fake_atadev.param.atavalid = 0;
	fake_atadev.param.apiomodes = 0x00;
	fake_atadev.param.udmamodes = 0x00;
	fake_atadev.param.mwdmamodes = 0x00;
	fake_atadev.param.retired_piomode = 0;
}

static void
rig_identify_wdma2_only(void)
{
	fake_atadev.param.atavalid = 0;
	fake_atadev.param.apiomodes = 0x00;
	fake_atadev.param.udmamodes = 0x00;
	fake_atadev.param.mwdmamodes = 0x04;	/* WDMA2 (ata_wmode -> 0x22) */
	fake_atadev.param.retired_piomode = 0;
}

/* print the live kernel piotiming[] window with annotation */
static void
rig_dump_kernel_table(void)
{
	volatile u_int32_t *tbl = (volatile u_int32_t *)rig_kern_tbl;
	int i;

	if (rig_kern_tbl == 0)
		return;
	if (tbl[0] != rig_piotiming_src[0] || tbl[1] != rig_piotiming_src[1] ||
	    tbl[2] != rig_piotiming_src[2] || tbl[3] != rig_piotiming_src[3] ||
	    tbl[4] != rig_piotiming_src[4]) {
		kprintf("dfrig_cyx: TABLE-CHECK FAILED at 0x%lx: [0..4] != "
			"source constants\n", rig_kern_tbl);
		return;
	}
	kprintf("dfrig_cyx: kernel .rodata piotiming[] @ 0x%lx "
		"(verified: [0..4] == source constants):\n", rig_kern_tbl);
	for (i = 0; i < 14; i++) {
		const char *what;
		switch (i) {
		case 0: case 1: case 2: case 3: case 4:
			what = "IN BOUNDS (piotiming[i])"; break;
		case 5: what = "OOB: pad hole"; break;
		case 6: what = "OOB: dmatiming[0]"; break;
		case 7: what = "OOB: dmatiming[1]"; break;
		case 8: what = "OOB: dmatiming[2]  <-- PIO0 (0x08) lands here"; break;
		case 9: what = "OOB: pad hole      <-- PIO1 (0x09) lands here"; break;
		case 10: what = "OOB: udmatiming[0] <-- PIO2 (0x0a) lands here"; break;
		case 11: what = "OOB: udmatiming[1] <-- PIO3 (0x0b) lands here"; break;
		case 12: what = "OOB: udmatiming[2] <-- PIO4 (0x0c) lands here"; break;
		default: what = "OOB: unrelated driver constant"; break;
		}
		kprintf("dfrig_cyx:   [%2d] @+0x%02x = 0x%08x  %s\n",
			i, i * 4, tbl[i], what);
	}
}

/* post-dispatch report: index used vs correct, register file dump */
static void
rig_report(const char *tag, int requested)
{
	int settled = fake_atadev.mode;
	int idx = settled & ATA_MODE_MASK;
	int cidx = (settled >= 0) ? ata_mode2idx(settled) : -1;
	u_int32_t pio_slot = fake_bmio[0x20 / 4];
	u_int32_t dma_slot = fake_bmio[0x24 / 4];
	u_int32_t oob_src = 0;

	kprintf("dfrig_cyx[%s]: requested mode=0x%02x settled mode=0x%02x "
		"controlcmd_seen=%d\n", tag, requested, settled, rig_cmd_seen);
	kprintf("dfrig_cyx[%s]: INDEX USED  = mode & ATA_MODE_MASK = 0x%02x & "
		"0x0f = %d   (piotiming[] has 5 elements: 0..4)%s\n",
		tag, settled, idx, (idx >= 5 && settled < ATA_WDMA0) ?
		"  <<< OUT OF BOUNDS" : "");
	if (settled < ATA_WDMA0) {
		kprintf("dfrig_cyx[%s]: INDEX RIGHT = ata_mode2idx(mode)   = "
			"%d   (the idiom every sibling uses)\n", tag, cidx);
		if (cidx >= 0 && cidx <= 4)
			kprintf("dfrig_cyx[%s]: CORRECT VALUE piotiming[%d] = "
				"0x%08x (source constant)\n", tag, cidx,
				rig_piotiming_src[cidx]);
	}
	kprintf("dfrig_cyx[%s]: controller register file (fabricated BMIO "
		"MEM space):\n", tag);
	kprintf("dfrig_cyx[%s]:   reg 0x20 (PIO timing, devno 0)  = 0x%08x\n",
		tag, pio_slot);
	kprintf("dfrig_cyx[%s]:   reg 0x24 (DMA timing, devno 0)  = 0x%08x\n",
		tag, dma_slot);
	if (rig_kern_tbl && settled < ATA_WDMA0 && idx >= 5) {
		oob_src = ((volatile u_int32_t *)rig_kern_tbl)[idx];
		kprintf("dfrig_cyx[%s]: OOB SOURCE kernel_mem[piotiming+%d*4] = "
			"0x%08x%s\n", tag, idx, oob_src,
			(oob_src == pio_slot) ?
			"  == reg 0x20 write  (OOB read CONFIRMED)" :
			"  (MISMATCH vs reg 0x20?!)");
	}
}

/* one direct dispatch of the handler under test */
static void
rig_dispatch(int via_kernel, int identify, int mode, const char *tag)
{
	/* production ata-dmainit defaults (ata-dma.c:75-83) so the run
	 * shows the chipset OVERRIDING them, as on real hardware */
	bzero(&fake_dma, sizeof(fake_dma));
	fake_dma.alignment = 2;
	fake_dma.max_iosize = 128 * DEV_BSIZE;
	fake_ch.dma = &fake_dma;
	fake_atadev.mode = 0xff;
	bzero(&fake_atadev.param, sizeof(fake_atadev.param));
	rig_cmd_seen = 0;
	bzero(fake_bmio, sizeof(fake_bmio));

	if (identify == 0)
		rig_identify_pio4_udma2();
	else if (identify == 1)
		rig_identify_pio0_only();
	else
		rig_identify_wdma2_only();

	kprintf("dfrig_cyx[%s]: BEFORE dispatch: ch->dma=%p ctlr->r_res1=%p "
		"(guard ata-cyrix.c:84 passes) atadev->unit=%d\n",
		tag, fake_ch.dma, fake_ctlr.r_res1, fake_atadev.unit);
	kprintf("dfrig_cyx[%s]: dispatching production ata_cyrix_setmode"
		"(dev, 0x%02x) %s\n", tag, mode,
		via_kernel ? "(RUNNING KERNEL own copy)" :
		"(verbatim #included copy)");

	if (via_kernel)
		((void (*)(device_t, int))rig_kern_addr)(&fake_ata_dev, mode);
	else
		ata_cyrix_setmode(&fake_ata_dev, mode);

	kprintf("dfrig_cyx[%s]: RETURNED: ch->dma->alignment=%u "
		"max_iosize=%u\n", tag, fake_dma.alignment,
		fake_dma.max_iosize);
	rig_report(tag, mode);
}

/* extracted call site: ata_pcichannel_setmode(), ata-pci.c:855-866,
 * verbatim extraction (static in the kernel, cannot be linked) */
static void
rig_extracted_callsite(device_t dev, int mode)
{
	struct ata_pci_controller *ctlr = device_get_softc(GRANDPARENT(dev));

	ctlr->setmode(dev, ATA_PIO_MAX);
	if (mode >= ATA_DMA)
		ctlr->setmode(dev, mode);
}

/* full-call-site scenario: two ctlr->setmode dispatches as at boot */
static void
rig_dispatch_full(int via_kernel, const char *tag)
{
	bzero(&fake_dma, sizeof(fake_dma));
	fake_dma.alignment = 2;
	fake_dma.max_iosize = 128 * DEV_BSIZE;
	fake_ch.dma = &fake_dma;
	fake_atadev.mode = ATA_UDMA2;	/* requested mode, as at boot */
	bzero(&fake_atadev.param, sizeof(fake_atadev.param));
	rig_identify_pio4_udma2();
	rig_cmd_seen = 0;
	bzero(fake_bmio, sizeof(fake_bmio));
	fake_ctlr.setmode = via_kernel ?
		(void (*)(device_t, int))rig_kern_addr : ata_cyrix_setmode;

	kprintf("dfrig_cyx[%s]: BEFORE: requested mode=ATA_UDMA2 -- full "
		"extracted call site ata-pci.c:855-866 "
		"(call1 = ctlr->setmode(dev, ATA_PIO_MAX), "
		"call2 = ctlr->setmode(dev, 0x42))\n", tag);

	rig_extracted_callsite(&fake_ata_dev, fake_atadev.mode);

	kprintf("dfrig_cyx[%s]: RETURNED: alignment=%u max_iosize=%u "
		"controlcmd_seen=%d (2 SETXFER = normal)\n",
		tag, fake_dma.alignment, fake_dma.max_iosize, rig_cmd_seen);
	rig_report(tag, ATA_UDMA2);
}

/* ------------------------------------------------------------------ */
/* sysctl plumbing                                                     */
/* ------------------------------------------------------------------ */
static int
rig_go_sysctl(SYSCTL_HANDLER_ARGS)
{
	int val = 0, error;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error || !req->newptr)
		return error;

	rig_armed = 1;
	rig_dump_kernel_table();
	switch (val) {
	case 1:
		rig_dispatch(1, 0, ATA_PIO_MAX, "go1");
		break;
	case 2:
		rig_dispatch(0, 0, ATA_PIO_MAX, "go2");
		break;
	case 3:
		rig_dispatch(1, 1, ATA_PIO_MAX, "go3");
		break;
	case 4:
		rig_dispatch_full(1, "go4");
		break;
	case 5:
		rig_dispatch(1, 2, ATA_WDMA2, "go5");
		break;
	default:
		return EINVAL;
	}
	return 0;
}

static SYSCTL_NODE(_kern, OID_AUTO, dfrig_cyx, CTLFLAG_RW, 0,
		   "DF-2213 ata-cyrix rig");

SYSCTL_ULONG(_kern_dfrig_cyx, OID_AUTO, addr, CTLFLAG_RW, &rig_kern_addr, 0,
	     "address of the kernel own ata_cyrix_setmode (from nm)");

SYSCTL_ULONG(_kern_dfrig_cyx, OID_AUTO, tbl, CTLFLAG_RW, &rig_kern_tbl, 0,
	     "address of the kernel .rodata piotiming[] table "
	     "(from objdump of the shipped handler)");

SYSCTL_PROC(_kern_dfrig_cyx, OID_AUTO, go, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, rig_go_sysctl, "I", "fire a scenario");

/* ------------------------------------------------------------------ */
/* module                                                              */
/* ------------------------------------------------------------------ */
static int
dfrig_cyx_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		rig_setup_graph();
		kprintf("dfrig_cyx: DF-2213 rig loaded "
			"(production chipsets/ata-cyrix.c:98 included "
			"verbatim; kernel handler addr=0x%lx tbl=0x%lx)\n",
			rig_kern_addr, rig_kern_tbl);
		return 0;
	case MOD_UNLOAD:
		if (rig_armed)
			return EBUSY;
		lockuninit(&fake_ch.state_mtx);
		lockuninit(&fake_ch.queue_mtx);
		return 0;
	default:
		return 0;
	}
}

static moduledata_t dfrig_cyx_mod = { "dfrig_cyx", dfrig_cyx_load, 0 };
DECLARE_MODULE(dfrig_cyx, dfrig_cyx_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);