⬢ DragonFlyBSD Kernel Audit
DF-2489 / dfrig2489.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
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
/*
 * dfrig2489.c -- DF-2489 verification rig
 *                (sys/dev/disk/nata/ata-disk.c:147 and :540)
 *
 * Finding under test:
 *
 *	ad_get_geometry() (ata-disk.c:462-500) copies adp->heads and
 *	adp->sectors straight out of the device-supplied ATA IDENTIFY
 *	words (u_int16_t param.heads / param.sectors, else-branch at
 *	ata-disk.c:477-478; the current_* branch at :469-475 is guarded,
 *	the raw branch is not -- and only the raw branch is taken whenever
 *	word 53 does not report words 54-58 valid, or current_heads /
 *	current_sectors are zero).  A device reporting heads==0 or
 *	sectors==0 (word 3 / word 6) then makes ad_attach() execute
 *
 *	    info.d_ncylinders = adp->total_secs/(adp->heads*adp->sectors);
 *						 ^^ ata-disk.c:147
 *
 *	and, under bootverbose, ad_describe() execute the identical
 *	divisor
 *
 *	    adp->total_secs / (adp->heads * adp->sectors)
 *						 ^^ ata-disk.c:540
 *
 *	-- an integer divide by zero: x86 #DE, "Fatal trap 18: integer
 *	divide fault", at device attach (boot or hot-plug).  Nothing
 *	between ata_getparam() (ata-all.c:590, copies IDENTIFY verbatim,
 *	only requires a printable model byte) and ad_attach() validates
 *	the geometry words.
 *
 * Rig design (established nata verbatim-interposition class, cf.
 * DF-2210 ata-national, DF-249x/DF-2496 nata):
 *
 *  1. FULL PRODUCTION FUNNEL dispatch (tests 1/2/3): the real
 *     kernel's own ata_identify() (global, ata-all.c:683) is called
 *     on a fabricated ata channel.  It runs the REAL kmalloc'ed
 *     ata_device, the REAL ata_getparam() (ATA_ATA_IDENTIFY served by
 *     a hw.begin_transaction stub -- exactly the seam a QEMU IDE
 *     disk answers), the REAL le16toh/bswap/btrim string parse, the
 *     REAL newbus child creation (device_add_child on a channel
 *     carrying the real "ata" devclass), bus_generic_attach, the REAL
 *     ad_probe() and -- the point -- the RUNNING KERNEL'S OWN
 *     ad_attach() (nm: t ad_attach), whose inlined ad_get_geometry
 *     computes the divisor from the crafted IDENTIFY words and whose
 *     ata-disk.c:147 idiv traps.  Everything from the IDENTIFY bytes
 *     to the fault is shipped production code; the only fabrications
 *     are the channel HW stubs (begin_transaction / the ata_if setmode
 *     and locking defaults / the device graph), none of which sit
 *     between the cited division and its device-supplied input.
 *
 *  2. VERBATIM COPY dispatch (tests 4/5): the production
 *     sys/dev/disk/nata/ata-disk.c is #included UNMODIFIED (module
 *     plumbing neutralized, the unused global ad_devclass renamed --
 *     build.sh sha256-checks the file against the audited sys/ tree),
 *     so this KLD also carries its own byte-identical ad_attach /
 *     ad_describe / ad_get_geometry, driven directly on the same
 *     fabricated post-getparam state (DF-2496 test22 shape).
 *
 *  3. DESCRIBE variant (test 5): fabricated ad_softc with heads==0
 *     (exactly what production ad_get_geometry leaves after a
 *     heads==0 IDENTIFY) + bootverbose=1 -> ad_describe():540 traps;
 *     on the stock kernel this site is shadowed by :147 during
 *     attach, but it is the SAME unvalidated divisor and must be
 *     fixed with it (a :147-only fix still panics every bootverbose
 *     boot).
 *
 * Fabricated machine state (what a real ata channel looks like to
 * nata when a PIO-only disk sits on it):
 *
 *   rig bus (kobj null class) <- fake_pci_dev (devclass NULL)
 *        <- fake_ch_dev (struct ata_channel softc, devclass "ata",
 *           state=ATA_IDLE, dma=NULL [PIO-only channel, legitimate,
 *           ata-dma.c:153], hw.begin_transaction=rig stub,
 *           production ata_queue_init() locks/queue)
 *        <- [ata_identify creates the REAL ad child here]
 *
 * Tests (sysctl kern.dfrig2489.go=N; read kern.dfrig2489.status):
 *
 *   1  CONTROL, full funnel: crafted IDENTIFY with real geometry
 *      (16383/16/63, LBA 0x2000000) -> kernel own ad_attach runs to
 *      completion: ad0 created, describe line + bootverbose
 *      "[33230C/16H/63S]" line, subdisk child attaches, RAID
 *      metadata scan (reads served as zeros) finds nothing.  Expect
 *      RETURNED, no panic.
 *   2  THE FINDING, full funnel, heads==0 (IDENTIFY word 3 = 0):
 *      kernel own ad_attach -> trap 18 at ata-disk.c:147.
 *   3  THE FINDING, full funnel, sectors==0 (word 6 = 0): same trap.
 *   4  THE FINDING, verbatim rig copy ad_attach, heads==0: identical
 *      trap in the rig-compiled production copy (byte-identity).
 *   5  THE FINDING, verbatim rig copy ad_describe, heads==0 ad_softc,
 *      bootverbose: trap 18 at ata-disk.c:540.
 *
 *   2/3/4/5 are FATAL (one per boot); proof = serial console panic.
 *   Module is loaded AT BOOT via /boot/loader.conf (on this INVARIANTS
 *   guest runtime kldload GPFs); dispatch at runtime via sysctl onto a
 *   dedicated worker thread that holds the MP token (the boot-attach
 *   context -- the built-in atapicam driver's attach asserts it).
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/bus.h>
#include <sys/bus_private.h>	/* struct bsd_device layout */
#include <sys/conf.h>		/* struct cdev / si_drv1 */
#include <sys/bio.h>
#include <sys/buf.h>
#include <sys/device.h>		/* struct dev_strategy_args */
#include <sys/callout.h>
#include <sys/lock.h>
#include <sys/kthread.h>
#include <sys/mplock2.h>		/* get_mplock: boot-attach context */

#include <sys/nata.h>

/* NOTE: nata headers (ata-all.h/ata-pci.h) have NO include guards;
 * the production ata-disk.c below includes ata-all.h itself, so all
 * further nata includes happen AFTER it, exactly once. */

/* ------------------------------------------------------------------ */
/* neutralize the production file's module/driver plumbing so this KLD */
/* registers nothing that clashes with the built-in ad driver, and     */
/* rename its (now unreferenced) global ad_devclass definition.        */
/* The functions under test keep their production names and bodies.    */
/* ------------------------------------------------------------------ */
#undef DRIVER_MODULE
#undef DECLARE_MODULE
#undef MODULE_DEPEND
#undef MODULE_VERSION
#define DRIVER_MODULE(name, busname, driver, devclass, evh, arg)	/* rig */
#define DECLARE_MODULE(name, data, sub, order)				/* rig */
#define MODULE_DEPEND(name, m, v1, v2, v3)				/* rig */
#define MODULE_VERSION(m, v)						/* rig */

/* rename the production file's global devclass definition (its only
 * reference, DRIVER_MODULE(ad, ata, ...), is neutralized above); the
 * extern in ata-disk.h and the definition in ata-disk.c rename in
 * lock inside this TU, avoiding a duplicate global against the
 * built-in kernel copy */
#define ad_devclass rig_unused_ad_devclass
#include "ata-disk.c"		/* <-- UNMODIFIED production code */
#undef ad_devclass

#include "ata-pci.h"		/* struct ata_pci_controller (once) */

#undef DRIVER_MODULE
#undef DECLARE_MODULE
#undef MODULE_DEPEND
#undef MODULE_VERSION

/* ------------------------------------------------------------------ */
/* channel HW seam: what the drive answers.  IDENTIFY is served from   */
/* the crafted image; data READs are served as zeros (a plain disk     */
/* with no ataraid metadata); control commands succeed.  Everything    */
/* else (queueing, locking, completion, taskqueue, retries, status     */
/* decoding) is the kernel's own ata_queue_request()/ata_start().      */
/* ------------------------------------------------------------------ */
static struct ata_params rig_params;	/* desired FINAL parsed words */
static u_int8_t rig_identify_img[512];	/* device-side IDENTIFY image */
static int rig_identify_served, rig_reads_served, rig_ctl_served;

/* device delivers ATA strings with each byte pair swapped;
 * ata_getparam() bswap()s them back (ata-all.c:631-635) */
static void
rig_swap_str(u_int8_t *dst, const char *src, int len)
{
	int i;

	for (i = 0; i < len; i += 2) {
		if (i + 1 < len) {
			dst[i] = src[i + 1];
			dst[i + 1] = src[i];
		} else {
			dst[i] = src[i];
		}
	}
}

/* variant: 0 = sane geometry (control), 1 = heads==0, 2 = sectors==0 */
static void
rig_build_params(int variant)
{

	bzero(&rig_params, sizeof(rig_params));
	rig_params.config = 0x0040;		/* non-ATAPI direct disk */
	rig_params.cylinders = 16383;
	rig_params.heads = (variant == 1) ? 0 : 16;	/* IDENTIFY word 3 */
	rig_params.sectors = (variant == 2) ? 0 : 63;	/* IDENTIFY word 6 */
	/* word 53: words 64-70 & 88 valid, 54-58 NOT valid -> forces the
	 * unguarded else-branch of ad_get_geometry (ata-disk.c:476-480) */
	rig_params.atavalid = ATA_FLAG_64_70 | ATA_FLAG_88;
	rig_params.apiomodes = 0x0002;		/* PIO4 */
	rig_params.sectors_intr = 16;		/* word 47: 16 secs/int */
	rig_params.lba_size_1 = 0x0000;		/* word 60/61 = 0x2000000 */
	rig_params.lba_size_2 = 0x0200;
	rig_params.version_major = 0x007e;	/* ATA/ATAPI-6 */
	bcopy("DF2489SERIAL00000001", rig_params.serial, 20);
	bcopy(variant == 0 ? "DF2489 SANE GEOMETRY" :
	      "DF2489 ZEROGEOM DISK", rig_params.model, 20);
	bcopy("FW000001", rig_params.revision, 8);
}

static void
rig_make_identify(void)
{
	size_t off;

	bzero(rig_identify_img, sizeof(rig_identify_img));
	bcopy(&rig_params, rig_identify_img,
	      umin(sizeof(rig_params), sizeof(rig_identify_img)));

	/* strings pairwise swapped, the way the wire delivers them */
	off = __offsetof(struct ata_params, serial);
	rig_swap_str(rig_identify_img + off, "DF2489SERIAL00000001", 20);
	off = __offsetof(struct ata_params, revision);
	rig_swap_str(rig_identify_img + off, "FW000001", 8);
	off = __offsetof(struct ata_params, model);
	rig_swap_str(rig_identify_img + off,
		     (const char *)&rig_params.model, 40);
}

static int
rig_begin_transaction(struct ata_request *request)
{
	kprintf("dfrig2489:   hw: cmd=0x%02x feature=0x%02x count=%u "
		"lba=%ju bytecount=%u flags=0x%x\n",
		request->u.ata.command, request->u.ata.feature,
		request->u.ata.count, (uintmax_t)request->u.ata.lba,
		request->bytecount, request->flags);
	if (request->flags & ATA_R_READ) {
		if (request->u.ata.command == ATA_ATA_IDENTIFY) {
			bcopy(rig_identify_img, request->data,
			      umin(request->bytecount,
				   sizeof(rig_identify_img)));
			rig_identify_served++;
		} else {
			/* plain-disk data: zeros, no ataraid magic */
			bzero(request->data, request->bytecount);
			rig_reads_served++;
		}
		request->donecount = request->bytecount;
	} else {
		rig_ctl_served++;	/* SETFEATURES / SET_MULTI accepted */
	}
	request->status = ATA_S_READY;		/* 0x40, no error bits */
	request->error = 0;
	request->result = 0;
	return ATA_OP_FINISHED;
}

/* ------------------------------------------------------------------ */
/* fabricated channel kobj ops: the ata_if defaults, exactly what a    */
/* channel without a chipset driver gets (ata_if.m CODE blocks)        */
/* ------------------------------------------------------------------ */
static int
rig_ata_locking(device_t dev, int mode)
{
	struct ata_channel *ch = device_get_softc(dev);

	return ch->unit;	/* interface DEFAULT ata_null_locking */
}

static void
rig_ata_setmode(device_t parent, device_t dev)
{
	struct ata_device *atadev = device_get_softc(dev);

	/* interface DEFAULT ata_null_setmode (ata_if.m) */
	atadev->mode = ata_limit_mode(dev, atadev->mode, ATA_PIO_MAX);
	kprintf("dfrig2489: rig ata setmode default -> mode=0x%02x\n",
		atadev->mode);
}

static struct kobj_method rig_ata_methods[] = {
	{ &ata_setmode_desc,	(kobjop_t)rig_ata_setmode },
	{ &ata_locking_desc,	(kobjop_t)rig_ata_locking },
	{ NULL,			NULL }
};
static struct kobj_class rig_ata_class = {
	"dfrig2489_atach", rig_ata_methods, 0, NULL, 0, NULL
};
static struct kobj_ops rig_ata_ops;

/* null class for the fabricated pci/bus devices (never dispatched on
 * these paths; kobj falls back to kobj_error_method) */
static struct kobj_method rig_null_methods[] = {
	{ NULL, NULL }
};
static struct kobj_class rig_null_class = {
	"dfrig2489_null", rig_null_methods, 0, NULL, 0, NULL
};
static struct kobj_ops rig_null_ops;
static struct kobj_method rig_dummy_method = { NULL, NULL };

/* ------------------------------------------------------------------ */
/* fabricated device graph                                              */
/* ------------------------------------------------------------------ */
static struct bsd_device fake_bus_dev, fake_pci_dev, fake_ch_dev;
static struct ata_pci_controller fake_ctlr;
static struct ata_channel fake_ch;
static char ch_nameunit[] = "ata9f";
static char pci_nameunit[] = "pcid9f";

static int
rig_prepare(int variant)
{
	int i;
	devclass_t ata_dc = devclass_find("ata");

	if (ata_dc == NULL) {
		kprintf("dfrig2489: devclass \"ata\" not found\n");
		return ENXIO;
	}

	{
		/* preserve the lockinit'ed locks across the reset */
		struct lock saved_state = fake_ch.state_mtx;
		struct lock saved_queue = fake_ch.queue_mtx;

		bzero(&fake_ch, sizeof(fake_ch));
		fake_ch.state_mtx = saved_state;
		fake_ch.queue_mtx = saved_queue;
	}
	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_ctlr, sizeof(fake_ctlr));

	/* kobj ops caches pre-filled so kernel kobj lookups through these
	 * devices resolve via the class method lists (DF-2210 pattern) */
	for (i = 0; i < KOBJ_CACHE_SIZE; i++) {
		rig_ata_ops.cache[i] = &rig_dummy_method;
		rig_null_ops.cache[i] = &rig_dummy_method;
	}
	rig_ata_ops.cls = &rig_ata_class;
	rig_null_ops.cls = &rig_null_class;

	fake_bus_dev.ops = (kobj_ops_t)&rig_null_ops;
	fake_pci_dev.ops = (kobj_ops_t)&rig_null_ops;
	fake_ch_dev.ops = (kobj_ops_t)&rig_ata_ops;

	fake_pci_dev.parent = &fake_bus_dev;
	fake_pci_dev.softc = &fake_ctlr;
	fake_pci_dev.devclass = NULL;	/* not pci/atapci: generic scan */
	fake_pci_dev.nameunit = pci_nameunit;
	TAILQ_INIT(&fake_pci_dev.children);

	fake_ch_dev.parent = &fake_pci_dev;
	fake_ch_dev.softc = &fake_ch;
	fake_ch_dev.devclass = ata_dc;	/* real "ata" bus devclass */
	fake_ch_dev.unit = 0;
	fake_ch_dev.nameunit = ch_nameunit;
	TAILQ_INIT(&fake_ch_dev.children);

	/* the channel a PIO-only disk lives on */
	fake_ch.dev = &fake_ch_dev;
	fake_ch.unit = 0;
	fake_ch.devices = ATA_ATA_MASTER;	/* disk present on master */
	fake_ch.state = ATA_IDLE;			/* normal channel state */
	fake_ch.dma = NULL;	/* PIO-only channel (ata-dma.c:153) */
	fake_ch.hw.begin_transaction = rig_begin_transaction;
	ata_queue_init(&fake_ch);			/* PRODUCTION */

	/* serve the crafted IDENTIFY */
	rig_build_params(variant);
	rig_make_identify();
	rig_identify_served = rig_reads_served = rig_ctl_served = 0;

	kprintf("dfrig2489: channel prepared: variant=%d "
		"(params.heads=%u params.sectors=%u cylinders=%u "
		"atavalid=0x%04x lba_size=0x%08x)\n",
		variant, rig_params.heads, rig_params.sectors,
		rig_params.cylinders, rig_params.atavalid,
		(rig_params.lba_size_2 << 16) | rig_params.lba_size_1);
	return 0;
}

/* ------------------------------------------------------------------ */
/* status buffer                                                        */
/* ------------------------------------------------------------------ */
static char rig_status_buf[8192];
static int rig_last_rc;

#define P(fmt, ...) \
	ksnprintf(rig_status_buf + strlen(rig_status_buf), \
		  sizeof(rig_status_buf) - strlen(rig_status_buf), \
		  fmt "\n", ## __VA_ARGS__)

/* ------------------------------------------------------------------ */
/* tests 1/2/3: the FULL production funnel -- kernel's own identify,   */
/* getparam parse, newbus child, ad_probe, ad_attach                    */
/* ------------------------------------------------------------------ */
static int
rig_test_funnel(int variant, const char *tag)
{
	int saved_bootverbose = bootverbose;
	int rc;

	rc = rig_prepare(variant);
	if (rc)
		return rc;

	P("== DF-2489 %s: PRODUCTION ata_identify() funnel -> "
	  "RUNNING KERNEL own ad_attach, variant=%d (%s) ==",
	  tag, variant,
	  variant == 0 ? "SANE 16383/16/63" :
	  variant == 1 ? "CRAFTED heads=0 (word 3)" :
			 "CRAFTED sectors=0 (word 6)");
	P("firing ata_identify(%p): real getparam parses the crafted "
	  "IDENTIFY, real newbus child, real ad_probe/ad_attach ...",
	  &fake_ch_dev);
	P("EXPECT%s: %s", variant == 0 ? "ED CLEAN ATTACH" :
	  "ING FATAL TRAP 18 (integer divide fault) at ata-disk.c:147",
	  variant == 0 ? "ad0 created, describe + bootverbose "
	  "[C/H/S] lines on console" :
	  "ad_attach:147 total_secs/(heads*sectors) with divisor 0");

	kprintf("dfrig2489[%s]: firing PRODUCTION ata_identify() "
		"(variant=%d) -- kernel own ad_attach follows\n", tag,
		variant);
	bootverbose = 1;	/* also exercise the :537-543 path */
	ata_identify(&fake_ch_dev);		/* PRODUCTION */
	bootverbose = saved_bootverbose;

	P("ata_identify returned (no panic); IDENTIFY served=%d "
	  "data reads served=%d control cmds served=%d",
	  rig_identify_served, rig_reads_served, rig_ctl_served);
	if (variant == 0) {
		struct ata_device *atadev;
		device_t child;

		child = TAILQ_FIRST(&fake_ch_dev.children);
		atadev = child ? device_get_softc(child) : NULL;
		if (child && atadev && device_get_ivars(child)) {
			struct ad_softc *adp = device_get_ivars(child);

			P("control attach state: child=%p ad_softc=%p "
			  "heads=%u sectors=%u total_secs=%ju "
			  "(disk_info d_ncylinders would be %ju)",
			  child, adp, adp->heads, adp->sectors,
			  (uintmax_t)adp->total_secs,
			  (uintmax_t)(adp->total_secs /
				      (adp->heads * adp->sectors)));
			rig_last_rc = (adp->heads == 16 &&
				       adp->sectors == 63);
		} else {
			P("no ad child attached?!");
			rig_last_rc = 0;
		}
	}
	return 0;
}

/* ------------------------------------------------------------------ */
/* test 4: verbatim rig copy ad_attach on the same post-getparam state */
/* ------------------------------------------------------------------ */
static struct bsd_device fake_ata_dev_b;
static struct ata_device fake_atadev_b;
static char b_nameunit[] = "ad9b";

static void
rig_test_verbatim_attach(void)
{
	devclass_t ad_dc = devclass_find("ad");

	if (rig_prepare(1) == 0) {
		bzero(&fake_ata_dev_b, sizeof(fake_ata_dev_b));
		bzero(&fake_atadev_b, sizeof(fake_atadev_b));

		fake_ata_dev_b.parent = &fake_ch_dev;
		fake_ata_dev_b.softc = &fake_atadev_b;
		fake_ata_dev_b.ivars = NULL;	/* virgin disk */
		fake_ata_dev_b.devclass = ad_dc;
		fake_ata_dev_b.unit = 7;
		fake_ata_dev_b.nameunit = b_nameunit;
		TAILQ_INIT(&fake_ata_dev_b.children);

		/* exactly what production ata_getparam leaves after the
		 * crafted heads==0 IDENTIFY (rig_params, variant 1) */
		fake_atadev_b.dev = &fake_ata_dev_b;
		fake_atadev_b.unit = ATA_MASTER;
		fake_atadev_b.mode = ATA_PIO_MAX;
		bcopy(&rig_params, &fake_atadev_b.param,
		      sizeof(struct ata_params));
	}

	P("== DF-2489 go4: VERBATIM #included copy ad_attach(), "
	  "heads=0 ==");
	P("ad_softc state entering ad_attach (from ad_get_geometry "
	  "else-branch, ata-disk.c:477-478): heads=0 sectors=63 "
	  "total_secs=0x2000000 (lba_size won, :489-490)");
	P("EXPECTING FATAL TRAP 18 at ata-disk.c:147 "
	  "`total_secs/(heads*sectors)' -- panic follows on serial");

	kprintf("dfrig2489[go4]: dispatching VERBATIM COPY ad_attach() "
		"(heads=0) -- divisor heads*sectors=0\n");
	ad_attach(&fake_ata_dev_b);		/* PRODUCTION (rig copy) */
	P("SURVIVED -- on a FIXED kernel this is the expected outcome "
	  "(clamp in ad_get_geometry -> no division by zero)");
	rig_last_rc = 0;
}

/* ------------------------------------------------------------------ */
/* test 5: verbatim rig copy ad_describe with a zero-geometry ad_softc */
/* ------------------------------------------------------------------ */
static struct bsd_device fake_ata_dev_c;
static struct ata_device fake_atadev_c;
static struct ad_softc fake_adsc_c;
static char c_nameunit[] = "ad9c";

static void
rig_test_describe(void)
{
	int saved_bootverbose = bootverbose;

	rig_prepare(0);
	bzero(&fake_ata_dev_c, sizeof(fake_ata_dev_c));
	bzero(&fake_atadev_c, sizeof(fake_atadev_c));
	bzero(&fake_adsc_c, sizeof(fake_adsc_c));

	fake_ata_dev_c.parent = &fake_ch_dev;
	fake_ata_dev_c.softc = &fake_atadev_c;
	fake_ata_dev_c.ivars = &fake_adsc_c;
	fake_ata_dev_c.unit = 0;
	fake_ata_dev_c.nameunit = c_nameunit;

	bcopy(&rig_params, &fake_atadev_c.param,
	      sizeof(struct ata_params));
	fake_atadev_c.dev = &fake_ata_dev_c;
	fake_atadev_c.unit = ATA_MASTER;
	fake_atadev_c.mode = ATA_PIO4;

	/* post-ad_get_geometry state of a heads==0 device */
	fake_adsc_c.total_secs = 100000;
	fake_adsc_c.heads = 0;		/* <-- crafted IDENTIFY word 3 */
	fake_adsc_c.sectors = 63;
	fake_adsc_c.num_tags = 0;

	P("== DF-2489 go5: VERBATIM #included copy ad_describe(), "
	  "bootverbose, heads=0 ==");
	P("ad_softc: total_secs=100000 heads=0 sectors=63 -- the state "
	  "ad_get_geometry leaves after a heads==0 IDENTIFY");
	P("EXPECTING FATAL TRAP 18 at ata-disk.c:540 "
	  "`total_secs/(heads*sectors)' in the bootverbose path");

	kprintf("dfrig2489[go5]: dispatching VERBATIM COPY ad_describe() "
		"(bootverbose, heads=0) -- :540 divisor heads*sectors=0\n");
	bootverbose = 1;
	ad_describe(&fake_ata_dev_c);		/* PRODUCTION (rig copy) */
	bootverbose = saved_bootverbose;
	P("SURVIVED -- on a FIXED kernel this is the expected outcome");
	rig_last_rc = 0;
}

/* ------------------------------------------------------------------ */
/* tests 6/7: semi-funnel diagnostics -- replicate ata_identify()'s    */
/* per-device body (ata-all.c:697-716) with the param installed        */
/* directly (fabricated post-getparse state), then run the REAL newbus */
/* probe/attach machinery stepwise to bracket any wedge               */
/* ------------------------------------------------------------------ */
static device_t rig_child;
static struct ata_device *rig_atadev;

static void
rig_test_semi(int variant, int direct_probe, const char *tag)
{
	int saved_bootverbose = bootverbose;

	if (rig_prepare(variant) != 0)
		return;

	rig_atadev = kmalloc(sizeof(struct ata_device), M_ATA,
			     M_INTWAIT | M_ZERO);
	rig_atadev->unit = ATA_MASTER;
	rig_child = device_add_child(&fake_ch_dev, NULL, -1);
	if (rig_child == NULL) {
		P("device_add_child failed");
		kfree(rig_atadev, M_ATA);
		rig_atadev = NULL;
		return;
	}
	device_set_softc(rig_child, rig_atadev);
	device_quiet(rig_child);
	rig_atadev->dev = rig_child;
	rig_atadev->max_iosize = DEV_BSIZE;
	rig_atadev->mode = ATA_PIO_MAX;
	bcopy(&rig_params, &rig_atadev->param, sizeof(struct ata_params));

	P("== DF-2489 %s: semi-funnel diagnostic (variant=%d) ==", tag,
	  variant);
	kprintf("dfrig2489[%s]: child=%p fabricated (ata-all.c:697-716 "
		"body); bus_generic_probe next\n", tag, rig_child);
	bus_generic_probe(&fake_ch_dev);
	kprintf("dfrig2489[%s]: bus_generic_probe RETURNED\n", tag);

	bootverbose = 1;
	if (direct_probe) {
		kprintf("dfrig2489[%s]: device_probe_and_attach(child) "
			"next\n", tag);
		device_probe_and_attach(rig_child);
	} else {
		kprintf("dfrig2489[%s]: bus_generic_attach(channel) "
			"next\n", tag);
		bus_generic_attach(&fake_ch_dev);
	}
	bootverbose = saved_bootverbose;

	kprintf("dfrig2489[%s]: newbus attach machinery RETURNED\n", tag);
	P("newbus attach machinery returned; hw stats: identify=%d "
	  "reads=%d ctl=%d", rig_identify_served, rig_reads_served,
	  rig_ctl_served);
	if (device_get_ivars(rig_child)) {
		struct ad_softc *adp = device_get_ivars(rig_child);

		P("child attached: ad_softc=%p heads=%u sectors=%u "
		  "total_secs=%ju", adp, adp->heads, adp->sectors,
		  (uintmax_t)adp->total_secs);
		rig_last_rc = (adp->heads != 0 && adp->sectors != 0);
	} else {
		P("child did not attach (no ivars)");
		rig_last_rc = 0;
	}
}

/* ------------------------------------------------------------------ */
/* scenario dispatch + worker thread (the sysctl handler only posts    */
/* the command; the scenario runs in a dedicated kernel thread so a    */
/* wedge cannot hold a sysctl/driverf-context lock)                    */
/* ------------------------------------------------------------------ */
static int rig_fire_cmd;	/* protected by being single-writer */
static int rig_seq;

static void
rig_run(int cmd)
{
	rig_status_buf[0] = '\0';
	rig_last_rc = -1;
	switch (cmd) {
	case 1:
		rig_test_funnel(0, "go1");
		break;
	case 2:
		rig_test_funnel(1, "go2");	/* fatal on stock */
		break;
	case 3:
		rig_test_funnel(2, "go3");	/* fatal on stock */
		break;
	case 4:
		rig_test_verbatim_attach();	/* fatal on stock */
		break;
	case 5:
		rig_test_describe();		/* fatal on stock */
		break;
	case 6:
		rig_test_semi(0, 0, "go6");	/* diagnostic, sane */
		break;
	case 7:
		rig_test_semi(0, 1, "go7");	/* diagnostic, sane */
		break;
	default:
		P("unknown test %d", cmd);
		rig_last_rc = -2;
		return;
	}
	P("last_rc=%d", rig_last_rc);
}

static void
rig_worker(void *arg)
{

	kprintf("dfrig2489: worker thread ready\n");
	for (;;) {
		int cmd;

		tsleep(&rig_fire_cmd, 0, "dfrig2489w", 0);
		cmd = rig_fire_cmd;
		rig_fire_cmd = 0;
		if (cmd == 0)
			continue;
		kprintf("dfrig2489: worker firing scenario %d "
			"(under mp_token, like the boot attach context)\n",
			cmd);
		/*
		 * The funnel attaches through the real newbus machinery,
		 * which on this kernel also attaches the built-in
		 * atapicam driver (its identify() adds a child to every
		 * probed channel); xpt_bus_register() asserts the MP
		 * token is held, exactly as the boot-time attach context
		 * holds it.  Take it -- this mirrors, not alters, the
		 * production attach context.
		 */
		get_mplock();
		rig_run(cmd);
		rel_mplock();
		rig_seq++;
		wakeup(&rig_seq);
		kprintf("dfrig2489: scenario %d DONE (seq=%d)\n", cmd,
			rig_seq);
	}
}

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;

	if (val < 1 || val > 7)
		return EINVAL;
	rig_fire_cmd = val;
	wakeup_one(&rig_fire_cmd);
	return 0;
}

static int
rig_status_sysctl(SYSCTL_HANDLER_ARGS)
{
	return SYSCTL_OUT(req, rig_status_buf, strlen(rig_status_buf) + 1);
}

static int
rig_seq_sysctl(SYSCTL_HANDLER_ARGS)
{
	int v = rig_seq;

	return sysctl_handle_int(oidp, &v, 0, req);
}

static SYSCTL_NODE(_kern, OID_AUTO, dfrig2489, CTLFLAG_RW, 0,
		   "DF-2489 ata-disk zero-geometry rig");

SYSCTL_PROC(_kern_dfrig2489, OID_AUTO, go, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, rig_go_sysctl, "I", "fire DF-2489 scenario N");

SYSCTL_PROC(_kern_dfrig2489, OID_AUTO, status, CTLTYPE_STRING |
	    CTLFLAG_RD, 0, 0, rig_status_sysctl, "A",
	    "last DF-2489 scenario output");

SYSCTL_PROC(_kern_dfrig2489, OID_AUTO, seq, CTLTYPE_INT | CTLFLAG_RD,
	    0, 0, rig_seq_sysctl, "I", "completed scenario count");

/* ------------------------------------------------------------------ */
/* module (loaded at BOOT via /boot/loader.conf; runtime kldload GPFs  */
/* on this INVARIANTS guest)                                           */
/* ------------------------------------------------------------------ */
static struct thread *rig_td;

static int
dfrig2489_modevent(module_t mod, int type, void *data)
{
	int error;

	switch (type) {
	case MOD_LOAD:
		lockinit(&fake_ch.state_mtx, "dfrig2489_st", 0,
			 LK_CANRECURSE);
		lockinit(&fake_ch.queue_mtx, "dfrig2489_q", 0,
			 LK_CANRECURSE);
		error = kthread_create(rig_worker, NULL, &rig_td,
				       "dfrig2489");
		if (error)
			return error;
		kprintf("dfrig2489: loaded (production ata-disk.c "
			"included verbatim; scenarios "
			"kern.dfrig2489.go=1..7)\n");
		return 0;
	case MOD_UNLOAD:
		return EBUSY;	/* worker thread never exits */
	default:
		return EOPNOTSUPP;
	}
}

static moduledata_t dfrig2489_mod = { "dfrig2489", dfrig2489_modevent, 0 };

static struct mod_metadata _mod_metadata_dfrig2489 = {
	MDT_STRUCT_VERSION, MDT_MODULE, &dfrig2489_mod, "dfrig2489"
};
DATA_SET(modmetadata_set, _mod_metadata_dfrig2489);
SYSINIT(dfrig2489module, SI_SUB_DRIVERS, SI_ORDER_MIDDLE,
	module_register_init, &dfrig2489_mod);