/*
 * dfrig_nat.c -- DF-2210 verification rig
 *                (sys/dev/disk/nata/chipsets/ata-national.c:77-78)
 *
 * Finding under test:
 *
 *	ata_national_setmode() unconditionally executes
 *
 *	    ch->dma->alignment = 16;          <-- ata-national.c:77
 *	    ch->dma->max_iosize = 64*DEV_BSIZE; <-- ata-national.c:78
 *
 *	with no NULL guard, while its own template (the Cyrix original,
 * chipsets/ata-cyrix.c:82-101) guards the identical derefs behind
 * `if (ctlr->r_res1)`.  ch->dma is legitimately NULL in production:
 * ata-dma.c:153-154 frees ch->dma and sets it to NULL whenever DMA
 * allocation fails ("WARNING - DMA allocation failed, disabling DMA"),
 * after which the channel keeps operating in PIO -- and every later
 * setmode dispatch panics the kernel instead.
 *
 * Real dispatch chain that reaches the handler (all production cites):
 *
 *   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);
 *   (same funnel for boot reinit and acd_open -> atapi-cd.c:147/226,
 *    atapi-tape.c:121/214, atapi-fd.c:107/180, ata-disk.c:436)
 *   -> ata_national_setmode()  chipsets/ata-national.c:60
 *   -> line 77 NULL deref: write 16 to absolute vaddr 0x58
 *      (offsetof(struct ata_dma, alignment) == 0x58 on amd64;
 *       max_iosize sits at 0x5c)
 *
 * The hardware is gated (National Geode SC1100, PCI ID 0x0502100b), so
 * this rig follows the established production-handler dispatch class
 * (DF-252x trm, DF-249x nata):
 *
 *  - the production file chipsets/ata-national.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_national_ident rig_unused_ata_national_ident`, which
 *    renames the never-called ident() entry point to avoid a duplicate
 *    global against the built-in kernel copy; ata_national_setmode
 *    itself is compiled byte-for-byte as written, static, under its
 *    production name (backtrace below reads `ata_national_setmode+..`).
 *
 *  - the RUNNING KERNEL's OWN ata_national_setmode (local symbol, from
 *    `nm /boot/kernel/kernel`, address injected via sysctl) is ALSO
 *    dispatched -- the shipped binary, zero recompilation.
 *
 *  - the channel/controller/ata-device state is exactly what the
 *    machine presents when DMA is unavailable: ch->dma == NULL, disk
 *    present on the channel (ch->devices = ATA_ATA_MASTER), PIO4/UDMA2
 *    capable identify data in atadev->param.  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:158-190,
 *    synchronous CONTROL completion at ata-queue.c:100-115) run
 *    unmodified.  The PCI config timing writes at ata-national.c:88-100
 *    land on a fabricated kobj bus that records them (the production
 *    code calls the pcivar.h static inlines PCI_WRITE_CONFIG /
 *    PCI_READ_CONFIG; those dispatch through the kernel kobj machinery
 *    on our fabricated ops table).
 *
 * Tests (sysctl kern.dfrig_nat.go=N):
 *
 *   1  THE FINDING, shipped kernel handler: ch->dma=NULL, direct call
 *      of the kernel's own ata_national_setmode with mode=ATA_PIO_MAX
 *      (exactly the first call of the extracted call site).
 *      STOCK KERNEL: fatal trap 12 page fault, vad=0x58, stopped at
 *      ata_national_setmode+0x..: movl $0x10,0x58(%rax)  [line 77].
 *   2  CONTROL, shipped kernel handler: same but ch->dma=&fake_dma ->
 *      normal setmode: alignment 2->16, max_iosize 65536->32768,
 *      PCI timing writes at 0x44/0x40 recorded, atadev->mode set.
 *   3  THE FINDING, verbatim rig copy: dispatch the #included
 *      production source's own ata_national_setmode, ch->dma=NULL.
 *      STOCK KERNEL: identical panic (byte-identity of the copy).
 *   4  CONTROL, verbatim rig copy, ch->dma=&fake_dma.
 *   5  FULL extracted call site (ata-pci.c:855-866): ctlr->setmode via
 *      the controller softc, requested mode ATA_UDMA2 (a UDMA disk at
 *      boot), ch->dma=NULL.  STOCK KERNEL: panic on the first
 *      ctlr->setmode call.  FIXED KERNEL: both calls complete (PIO
 *      timings for call 1, udmatiming[2] for call 2).
 *   6  CONTROL, full extracted call site, ch->dma=&fake_dma.
 */

#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_national_ident(device_t dev);	/* kproto */
#define ata_national_ident rig_unused_ata_national_ident
#include "chipsets/ata-national.c"	/* <-- UNMODIFIED production code */
#undef ata_national_ident

/* ------------------------------------------------------------------ */
/* 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.  This is the state a channel is  */
/* in while reinit/setmode choreography runs.  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() (ata-queue.c:158-190/69-157) 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 kobj bus: the PCI config timing writes of the production */
/* handler (ata-national.c:88-100) dispatch through the kernel kobj    */
/* machinery on this ops table and get recorded                         */
/* ------------------------------------------------------------------ */
static int rig_pci_n;
static struct { int reg; u_int32_t val; int width; } rig_pci_wr[16];
static int rig_pci_reads;
static int rig_i;

static u_int32_t rig_pci_read(device_t bus, device_t child, int reg,
			      int width);
static void rig_pci_write(device_t bus, device_t child, int reg,
			  u_int32_t val, int width);

static struct kobj_method rig_pci_methods[] = {
	{ &pci_read_config_desc,  (kobjop_t)rig_pci_read },
	{ &pci_write_config_desc, (kobjop_t)rig_pci_write },
	{ NULL, NULL }
};
static struct kobj_class rig_bus_class = {
	"dfrig_nat_pcib", rig_pci_methods, 0, NULL, 0, NULL
};
static struct kobj_ops rig_bus_ops;
static struct kobj_method rig_dummy_method = { NULL, NULL };

static u_int32_t
rig_pci_read(device_t bus, device_t child, int reg, int width)
{
	rig_pci_reads++;
	return 0;		/* config reads as zero (like a fresh chip) */
}

static void
rig_pci_write(device_t bus, device_t child, int reg, u_int32_t val,
	      int width)
{
	if (rig_pci_n < 16) {
		rig_pci_wr[rig_pci_n].reg = reg;
		rig_pci_wr[rig_pci_n].val = val;
		rig_pci_wr[rig_pci_n].width = width;
	}
	rig_pci_n++;
}

/* ------------------------------------------------------------------ */
/* fake device graph -- the exact graph nata hands the chipset driver  */
/* when a disk sits on the channel of an SC1100 controller whose DMA   */
/* could not be allocated:                                             */
/*                                                                     */
/*   rig_bus (kobj ops) <- fake_pci_dev (gparent, ctlr softc)          */
/*        <- fake_ch_dev (struct ata_channel, dma==NULL)               */
/*        <- 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 int rig_armed;

static void
rig_setup_graph(void)
{
	int i;

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

	/* kobj ops cache pre-filled so the kernel kobj lookup (slow path
	 * through rig_bus_class.methods) can run without touching real hw */
	for (i = 0; i < KOBJ_CACHE_SIZE; i++)
		rig_bus_ops.cache[i] = &rig_dummy_method;
	rig_bus_ops.cls = &rig_bus_class;
	fake_bus_dev.ops = (kobj_ops_t)&rig_bus_ops;

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

	/* channel: DMA unavailable is the whole point (set by caller) */
	fake_ch.dev = &fake_ch_dev;
	fake_ch.unit = 0;
	fake_ch.devices = ATA_ATA_MASTER;	/* disk present */
	fake_ch.state = ATA_STALL_QUEUE;	/* sync CONTROL completion */
	fake_ch.hw.begin_transaction = rig_begin_transaction;
	lockinit(&fake_ch.state_mtx, "dfrig_nat_st", 0, LK_CANRECURSE);
	lockinit(&fake_ch.queue_mtx, "dfrig_nat_q", 0, LK_CANRECURSE);

	/* ata device: master, PIO4/UDMA2 capable disk identify data */
	fake_atadev.dev = &fake_ata_dev;
	fake_atadev.unit = ATA_MASTER;
	fake_atadev.mode = 0xff;
	fake_atadev.param.atavalid = ATA_FLAG_64_70;
	fake_atadev.param.apiomodes = 0x02;	/* PIO4 (ata_pmode -> 0x0c) */
	fake_atadev.param.atavalid |= ATA_FLAG_88;
	fake_atadev.param.udmamodes = 0x04;	/* UDMA2 (ata_umode -> 0x42) */
}

/* one dispatch of the handler under test */
static void
rig_dispatch(int via_kernel, int dma_null, const char *tag)
{
	/* production ata-dmainit defaults (ata-dma.c:75-83) so the control
	 * 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 = dma_null ? NULL : &fake_dma;
	fake_atadev.mode = 0xff;
	rig_pci_n = 0;
	rig_pci_reads = 0;
	rig_cmd_seen = 0;
	bzero(rig_pci_wr, sizeof(rig_pci_wr));

	kprintf("dfrig_nat[%s]: BEFORE dispatch: ch=%p ch->dma=%p "
		"(NULL means DMA unavailable, ata-dma.c:153) atadev->unit=%d\n",
		tag, &fake_ch, fake_ch.dma, fake_atadev.unit);
	kprintf("dfrig_nat[%s]: dispatching production ata_national_setmode"
		"(dev, ATA_PIO_MAX) %s\n", tag,
		via_kernel ? "(RUNNING KERNEL own copy)" :
		"(verbatim #included copy)");

	if (via_kernel)
		((void (*)(device_t, int))rig_kern_addr)(&fake_ata_dev,
							ATA_PIO_MAX);
	else
		ata_national_setmode(&fake_ata_dev, ATA_PIO_MAX);

	kprintf("dfrig_nat[%s]: RETURNED (no panic): ch->dma=%p "
		"alignment=%u max_iosize=%u atadev->mode=0x%02x "
		"controlcmd_seen=%d pci_reads=%d\n",
		tag, fake_ch.dma, fake_dma.alignment, fake_dma.max_iosize,
		fake_atadev.mode, rig_cmd_seen, rig_pci_reads);
	for (rig_i = 0; rig_i < rig_pci_n && rig_i < 16; rig_i++)
		kprintf("dfrig_nat[%s]:   pci_write_config(gparent, "
			"0x%02x, 0x%08x, %d)\n", tag, rig_pci_wr[rig_i].reg,
			rig_pci_wr[rig_i].val, rig_pci_wr[rig_i].width);
}

/* 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);
}

static void
rig_dispatch_full(int via_kernel, int dma_null, const char *tag)
{
	bzero(&fake_dma, sizeof(fake_dma));
	fake_dma.alignment = 2;
	fake_dma.max_iosize = 128 * DEV_BSIZE;

	fake_ch.dma = dma_null ? NULL : &fake_dma;
	fake_atadev.mode = ATA_UDMA2;	/* requested mode, as at ioctl */
	rig_pci_n = 0;
	rig_pci_reads = 0;
	rig_cmd_seen = 0;
	bzero(rig_pci_wr, sizeof(rig_pci_wr));
	fake_ctlr.setmode = via_kernel ?
		(void (*)(device_t, int))rig_kern_addr : ata_national_setmode;

	kprintf("dfrig_nat[%s]: BEFORE: ch->dma=%p requested mode=ATA_UDMA2 "
		"-- full extracted call site ata-pci.c:855-866\n",
		tag, fake_ch.dma);

	rig_extracted_callsite(&fake_ata_dev, fake_atadev.mode);

	kprintf("dfrig_nat[%s]: RETURNED (no panic): alignment=%u "
		"max_iosize=%u atadev->mode=0x%02x controlcmd_seen=%d\n",
		tag, fake_dma.alignment, fake_dma.max_iosize,
		fake_atadev.mode, rig_cmd_seen);
	for (rig_i = 0; rig_i < rig_pci_n && rig_i < 16; rig_i++)
		kprintf("dfrig_nat[%s]:   pci_write_config(gparent, "
			"0x%02x, 0x%08x, %d)\n", tag, rig_pci_wr[rig_i].reg,
			rig_pci_wr[rig_i].val, rig_pci_wr[rig_i].width);
}

/* ------------------------------------------------------------------ */
/* 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;
	switch (val) {
	case 1:
		rig_dispatch(1, 1, "go1");
		break;			/* never reached on stock kernel */
	case 2:
		rig_dispatch(1, 0, "go2");
		break;
	case 3:
		rig_dispatch(0, 1, "go3");
		break;			/* never reached on stock kernel */
	case 4:
		rig_dispatch(0, 0, "go4");
		break;
	case 5:
		rig_dispatch_full(1, 1, "go5");
		break;			/* never reached on stock kernel */
	case 6:
		rig_dispatch_full(1, 0, "go6");
		break;
	default:
		return EINVAL;
	}
	return 0;
}

static SYSCTL_NODE(_kern, OID_AUTO, dfrig_nat, CTLFLAG_RW, 0,
		   "DF-2210 ata-national rig");

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

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

/* ------------------------------------------------------------------ */
/* module                                                              */
/* ------------------------------------------------------------------ */
static int
dfrig_nat_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		rig_setup_graph();
		kprintf("dfrig_nat: DF-2210 rig loaded "
			"(production chipsets/ata-national.c:77-78 included "
			"verbatim; kernel handler addr=0x%lx)\n",
			rig_kern_addr);
		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_nat_mod = { "dfrig_nat", dfrig_nat_load, 0 };
DECLARE_MODULE(dfrig_nat, dfrig_nat_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
