/* DF-5616 — live ICU introspection + icu_ioapic_extint(-1) harness (DragonFly pc64 KLD)
 *
 * Loaded as root via kldload(8) on the audit QEMU guest (X86_64_GENERIC,
 * ICU is the live MachIntrABI — every device irq flows through the 8259).
 *
 * Phases (all on CPU0, destructive phases with IF=0):
 *   D0: live ABI type, icu_imen shadow vs IMR ports coherence, ELCR read
 *   P3: compute uint8_t mask = ~(1 << irq) for irq = -1 (the exact
 *       expression of icu_ioapic_extint, icu.c:170) — documents the
 *       C-undefined-shift result on the live kernel/compiler
 *   P2: ICU_INTRDIS/ICU_INTREN mask cycle on an unused masked irq, ELCR
 *       read before/after — proves trigger-mode persistence across masks
 *   P5: full ICW1..ICW4+IMR reprogram of ICU2 (what icu_init does),
 *       ELCR high byte before/after — empirical "does ICW clobber ELCR"
 *   P4: THE test: call the real icu_ioapic_extint(-1, 0x20) (icu.c:158)
 *       on the live 8259, read back IMR1, then restore via a manual ICW
 *       sequence (icu_init twin; icu_definit() would KKASSERT-panic
 *       under the IOAPIC ABI, icu.c:109). Runs on any ABI.
 *   P6: liveness: sleep 2 ticks, print ticks delta + final coherence.
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/machintr.h>
#include <machine/cpufunc.h>

/* kernel symbols (all .globl) */
extern int icu_imen;			/* icu_ipl.s:94 */
extern void ICU_INTREN(int);		/* icu_ipl.s:127 */
extern void ICU_INTRDIS(int);		/* icu_ipl.s:108 */
extern int icu_ioapic_extint(int, int);	/* icu.c:158 */
extern int ioapic_enable;		/* ioapic.c:84 */

#define	IO_ICU1		0x20		/* bus/isa/isareg.h */
#define	IO_ICU2		0xA0
#define	ICU_IMR_OFFSET	1
#define	ELCR_PORT	0x4D0		/* elcr.c:56 */
#define	IDT_OFFSET	0x20

static int icuintro_ident;

static int
read_elcr(void)
{
	return (inb(ELCR_PORT) | (inb(ELCR_PORT + 1) << 8));
}

static int
icuintro_modevent(module_t mod, int type, void *data)
{
	int imr1, imr2, elcr, shadow, hw;
	int i, u, rc, imen_saved;
	int e_b, e_a, e_icw;
	int imr1_after, imr1_r, imr2_r, elcr_mid;
	volatile int nirq;
	uint8_t m;
	register_t ef;

	switch (type) {
	case MOD_LOAD:
		break;
	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}

	/* NOTE: v2 does NOT pin to CPU0 — lwkt_setcpu_self() from a process
	 * thread violates usched_dfly's lwp_qcpu invariant (v1 panicked in
	 * dfly_acquire_curproc). On this IOAPIC-ABI guest the 8259 is an
	 * idle legacy device; no concurrent IMR writers exist. */

	shadow = icu_imen;
	imr1 = inb(IO_ICU1 + ICU_IMR_OFFSET);
	imr2 = inb(IO_ICU2 + ICU_IMR_OFFSET);
	elcr = read_elcr();
	hw = (imr2 << 8) | imr1;
	kprintf("D0: ncpu=%d mycpuid=%d MachIntrABI.type=%d (MACHINTR_ICU=%d)\n",
	    ncpus, mycpuid, MachIntrABI.type, MACHINTR_ICU);
	kprintf("D1: icu_imen(shadow)=%04x hw_imr=%04x coherent=%s\n",
	    shadow & 0xffff, hw & 0xffff,
	    ((shadow & 0xffff) == (hw & 0xffff)) ? "YES" : "NO");
	kprintf("D2: ELCR=%04x (bit=1 -> level)\n", elcr & 0xffff);

	/* P3: the exact icu.c:170 expression with the unguarded value */
	nirq = -1;
	m = ~(1 << nirq);
	kprintf("P3: (uint8_t)~(1 << -1) = 0x%02x (C99 UB shift; x86: 1<<31)\n", m);
	nirq = 3;
	m = ~(1 << nirq);
	kprintf("P3: (uint8_t)~(1 << 3)  = 0x%02x (well-defined case)\n", m);

	/* P2: mask cycle on an already-masked, unused master irq (prefer 7) */
	u = -1;
	for (i = 7; i >= 0; --i) {
		if ((shadow & (1 << i)) && i != 2)
			u = i;
	}
	if (u >= 0) {
		e_b = read_elcr();
		ICU_INTRDIS(u);
		i = inb(IO_ICU1 + ICU_IMR_OFFSET);
		ICU_INTREN(u);
		rc = inb(IO_ICU1 + ICU_IMR_OFFSET);
		ICU_INTRDIS(u);
		imr1 = inb(IO_ICU1 + ICU_IMR_OFFSET);
		e_a = read_elcr();
		kprintf("P2: irq%d mask-cycle IMR1 %02x(en-dis path) -> %02x -> %02x; "
		    "shadow now %04x; ELCR %04x->%04x persist=%s\n",
		    u, i, rc, imr1, icu_imen & 0xffff, e_b & 0xffff, e_a & 0xffff,
		    (e_b == e_a) ? "YES" : "NO");
	} else {
		kprintf("P2: no masked master irq found (unexpected)\n");
	}

	/* P5: does a full ICW reprogram of ICU2 clobber ELCR? (icu_init twin) */
	ef = read_rflags();
	cpu_disable_intr();
	e_b = inb(ELCR_PORT + 1);
	i = inb(IO_ICU2 + ICU_IMR_OFFSET);			/* save IMR2 */
	outb(IO_ICU2, 0x11);					/* ICW1  */
	outb(IO_ICU2 + ICU_IMR_OFFSET, IDT_OFFSET + 8);		/* ICW2  */
	outb(IO_ICU2 + ICU_IMR_OFFSET, 2);			/* ICW3  */
	outb(IO_ICU2 + ICU_IMR_OFFSET, 1);			/* ICW4  */
	outb(IO_ICU2 + ICU_IMR_OFFSET, i);			/* IMR   */
	e_icw = inb(ELCR_PORT + 1);
	write_rflags(ef);
	kprintf("P5: ICU2 ICW cycle, ELCR_HI %02x -> %02x (%s)\n",
	    e_b, e_icw, (e_b == e_icw) ? "persists across ICW" : "CLEARED by ICW");

	/* P4: the real thing — icu_ioapic_extint(-1, 0x20) on the live 8259.
	 * v2 restore is a manual ICW sequence (icu_init() twin) because
	 * icu_definit() would KKASSERT-panic when MachIntrABI != ICU
	 * (icu.c:109). Restores the exact boot configuration:
	 * cascade mode, ICU1 ICW4 = auto-EOI|8086 (ioapic_enable boot),
	 * ICU2 ICW4 = 8086, saved IMRs, icu_init's OCW2/OCW3 tail. */
	ef = read_rflags();
	cpu_disable_intr();
	imen_saved = icu_imen;
	e_b = read_elcr();
	imr1 = inb(IO_ICU1 + ICU_IMR_OFFSET);
	imr2 = inb(IO_ICU2 + ICU_IMR_OFFSET);

	rc = icu_ioapic_extint(-1, 0x20);
	imr1_after = inb(IO_ICU1 + ICU_IMR_OFFSET);
	elcr_mid = read_elcr();

	/* manual restore — mirrors icu_init() (icu.c:62-102) */
	outb(IO_ICU1, 0x11);				/* ICW1 cascade */
	outb(IO_ICU1 + ICU_IMR_OFFSET, IDT_OFFSET);	/* ICW2 0x20   */
	outb(IO_ICU1 + ICU_IMR_OFFSET, 1 << 2);		/* ICW3 slave 2 */
	outb(IO_ICU1 + ICU_IMR_OFFSET, 0x03);		/* ICW4 aEOI|8086 */
	outb(IO_ICU1 + ICU_IMR_OFFSET, imr1);		/* IMR restore */
	outb(IO_ICU1, 0x0a);				/* OCW3: IRR   */
	outb(IO_ICU1, 0xc0 | 2);			/* OCW2: prio  */
	outb(IO_ICU2, 0x11);
	outb(IO_ICU2 + ICU_IMR_OFFSET, IDT_OFFSET + 8);
	outb(IO_ICU2 + ICU_IMR_OFFSET, 2);
	outb(IO_ICU2 + ICU_IMR_OFFSET, 1);
	outb(IO_ICU2 + ICU_IMR_OFFSET, imr2);
	outb(IO_ICU2, 0x0a);
	imr1_r = inb(IO_ICU1 + ICU_IMR_OFFSET);
	imr2_r = inb(IO_ICU2 + ICU_IMR_OFFSET);
	e_a = read_elcr();
	write_rflags(ef);
	kprintf("P4: ioapic_enable=%d icu_ioapic_extint(-1,0x20)=%d "
	    "IMR1_after=0x%02x (expect 0xff: all-masked, benign) "
	    "shadow_untouched=%s\n",
	    ioapic_enable, rc, imr1_after,
	    (icu_imen == imen_saved) ? "YES" : "NO");
	kprintf("P4: restored IMR={%02x,%02x} want={%02x,%02x} shadow=%04x "
	    "ELCR %04x->%04x->%04x\n",
	    imr1_r, imr2_r, imr1, imr2, icu_imen & 0xffff,
	    e_b & 0xffff, elcr_mid & 0xffff, e_a & 0xffff);

	/* P6: liveness — clock still ticking, mask protocol still coherent */
	i = ticks;
	tsleep(&icuintro_ident, 0, "icuintro", 2 * hz);
	kprintf("P6: alive, ticks delta=%d (want >=2); final icu_imen=%04x "
	    "hw=%04x coherent=%s\n",
	    ticks - i, icu_imen & 0xffff,
	    ((inb(IO_ICU2 + ICU_IMR_OFFSET) << 8) |
	      inb(IO_ICU1 + ICU_IMR_OFFSET)) & 0xffff,
	    ((icu_imen & 0xffff) ==
	     (((inb(IO_ICU2 + ICU_IMR_OFFSET) << 8) |
	       inb(IO_ICU1 + ICU_IMR_OFFSET)) & 0xffff)) ? "YES" : "NO");

	return (0);
}

static moduledata_t icuintro_mod = {
	"icuintro",
	icuintro_modevent,
	NULL
};
DECLARE_MODULE(icuintro, icuintro_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(icuintro, 1);
