/*
 * DF-5351 / DF-5352 evidence: drive the ioapic_abi MSI/MSI-X/legacy-config
 * ABI entry points directly as a driver (KLD) would.
 *
 * Safe phase (no crash expected on a production kernel):
 *  - msix_alloc/msi_map/msix_release round trip
 *  - msi_alloc(zone, 64, 0): 'zone' is a 128-int buffer; only zone[0..31]
 *    are inside the ABI's documented count<=32 contract.  If words
 *    zone[32..63] come back modified, msi_alloc_intern() wrote past the
 *    caller's 32-int array (ioapic_abi.c:1295).
 *
 * On an INVARIANTS kernel this instead panics at the KASSERT
 * ("invalid count 64"), proving the KASSERT is the only guard.
 */
#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;

#define CANARY	0xAAAAAAA

static int zone[128];

static void
probe_msix(void)
{
	uint64_t addr;
	uint32_t data;
	int irq = -1, rc;

	rc = MachIntrABI_IOAPIC.msix_alloc(&irq, 0);
	kprintf("msiabi_probe: msix_alloc(cpu0) rc=%d irq=%d\n", rc, irq);
	if (rc == 0) {
		MachIntrABI_IOAPIC.msi_map(irq, &addr, &data, 0);
		kprintf("msiabi_probe: msi_map addr=0x%llx data=0x%x\n",
		    (unsigned long long)addr, data);
		MachIntrABI_IOAPIC.msix_release(irq, 0);
		kprintf("msiabi_probe: msix_release done\n");
	}
}

static void
probe_count64(void)
{
	int i, hit, rc;

	for (i = 0; i < 128; ++i)
		zone[i] = CANARY;

	kprintf("msiabi_probe: calling msi_alloc(count=64, cpu0) "
	    "(contract max is 32)\n");
	rc = MachIntrABI_IOAPIC.msi_alloc(zone, 64, 0);
	kprintf("msiabi_probe: msi_alloc rc=%d\n", rc);
	if (rc != 0) {
		kprintf("msiabi_probe: alloc failed, no canary test\n");
		return;
	}
	kprintf("msiabi_probe: zone[0..7] =");
	for (i = 0; i < 8; ++i)
		kprintf(" %d", zone[i]);
	kprintf(" (allocated block)\n");

	hit = 0;
	for (i = 32; i < 128; ++i) {
		if (zone[i] != CANARY)
			++hit;
	}
	kprintf("msiabi_probe: %d/96 canary words past zone[31] overwritten "
	    "(32 expected for a 64-count alloc)\n", hit);
	for (i = 32; i < 64; ++i) {
		if (zone[i] != CANARY) {
			kprintf("msiabi_probe:   zone[%d]=%d  <-- OOB write "
			    "past 32-int contract\n", i, zone[i]);
			break;
		}
	}

	/*
	 * Release the full 64-wide view so the maps are restored
	 * (release_intern also reads intrs[0..63], see finding).
	 */
	MachIntrABI_IOAPIC.msi_release(zone, 64, 0);
	kprintf("msiabi_probe: msi_release(64) done\n");
}

static int
msiabi_modevent(module_t mod __unused, int type, void *data __unused)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("msiabi_probe: loaded\n");
		probe_msix();
		probe_count64();
		kprintf("msiabi_probe: SAFE PHASE COMPLETE\n");
		return 0;
	case MOD_UNLOAD:
		return 0;
	default:
		return EOPNOTSUPP;
	}
}

static moduledata_t msiabi_probe_mod = { "msiabi_probe", msiabi_modevent, NULL };
DECLARE_MODULE(msiabi_probe, msiabi_probe_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(msiabi_probe, 1);
