/*
 * DF-5352 crasher: legacy_intr_config() on an IRQ that is not a legacy
 * IRQ map (MSI-X allocated).
 *
 * ioapic_abi_legacy_intr_config() (ioapic_abi.c:987-1053) validates its
 * input only with KKASSERT/KASSERT, both of which are compiled out on
 * non-INVARIANTS (production) kernels (sys/sys/systm.h:94-122):
 *   - KKASSERT(irq < ioapic_abi_legacy_irq_max)  [ioapic_abi.c:999]
 *   - KKASSERT(cpuid < ncpus) after the legacy search loop
 *     [ioapic_abi.c:1005] -- the loop just falls through when no CPU has
 *     a LEGACY map for this irq, leaving 'map' pointing at the last CPU's
 *     (non-legacy) map.
 * For an MSI/MSI-X map, im_gsi == -1 (ioapic_abi.c:763-764), so
 * ioapic_gsi_pin(-1) -> ioapic_gsi_search(-1) ->
 * panic("ioapic_gsi_search: no I/O APIC")   [ioapic.c:415-427].
 *
 * This is the driver-facing ABI: any driver calling
 * bus_config_intr()/machintr_legacy_intr_config() with a non-legacy IRQ
 * panics a production kernel.  Return type is void -- no error path.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/machintr.h>
/* enum intr_trigger/polarity values from sys/bus.h:116-125 */
#define INTR_TRIGGER_CONFORM 0
#define INTR_TRIGGER_EDGE    1
#define INTR_TRIGGER_LEVEL   2
#define INTR_POLARITY_CONFORM 0
#define INTR_POLARITY_HIGH   1
#define INTR_POLARITY_LOW    2

extern struct machintr_abi MachIntrABI_IOAPIC;

static int
legacy_modevent(module_t mod __unused, int type, void *unused __unused)
{
	int irq = -1, rc, irqv[16], n = 0, i, victim = -1;

	switch (type) {
	case MOD_LOAD:
		/*
		 * Grab MSI-X irqs until one lands past the legacy range
		 * (QEMU's ioapic maps gsis 0..23 as legacy on some cpu);
		 * a map that is MSI-X on cpu0 and not LEGACY anywhere is
		 * what we want.
		 */
		for (i = 0; i < 16; ++i) {
			irq = -1;
			rc = MachIntrABI_IOAPIC.msix_alloc(&irq, 0);
			if (rc != 0)
				break;
			kprintf("legacy_crash: msix_alloc rc=%d irq=%d\n",
			    rc, irq);
			if (irq >= 24) {
				victim = irq;
				break;
			}
			irqv[n++] = irq;
		}
		if (victim < 0) {
			kprintf("legacy_crash: no irq past legacy range, "
			    "abort\n");
			while (--n >= 0)
				MachIntrABI_IOAPIC.msix_release(irqv[n], 0);
			return 0;
		}
		kprintf("legacy_crash: calling legacy_intr_config(irq=%d, "
		    "LEVEL, LOW); production kernel should panic in "
		    "ioapic_gsi_search (im_gsi == -1)\n", victim);
		MachIntrABI_IOAPIC.legacy_intr_config(victim,
		    INTR_TRIGGER_LEVEL, INTR_POLARITY_LOW);
		kprintf("legacy_crash: RETURNED WITHOUT PANIC (unexpected)\n");
		while (--n >= 0)
			MachIntrABI_IOAPIC.msix_release(irqv[n], 0);
		MachIntrABI_IOAPIC.msix_release(victim, 0);
		return 0;
	case MOD_UNLOAD:
		return 0;
	default:
		return EOPNOTSUPP;
	}
}

static moduledata_t legacy_crash_mod = { "legacy_crash", legacy_modevent, NULL };
DECLARE_MODULE(legacy_crash, legacy_crash_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
