/*
 * gpiofree.c -- DF-5527 production-code harness.
 *
 * Demonstrates on the STOCK kernel that gpio_cherryview_unmap_intr()
 * (gpio_cherryview.c:380-414) never clears the interrupt MASK bit:
 * releasing a mapped GPIO interrupt via GPIO_FREE_INTR *without* a
 * preceding GPIO_TEARDOWN_INTR leaves the community IRQ line armed
 * (CHV_GPIO_REG_MASK bit set) while its pin_intr_map is destroyed
 * (pin = -1, is_level zeroed).  On real hardware a level-asserted pad
 * then drives gpio_cherryview_intr() into the edge-clear path in a
 * tight re-asserting loop -> interrupt storm / hard lockup.
 *
 * Runs on the DF-5518 planted-ACPI rig (fake INT33FF _UID 2 community,
 * RAM register image at 0xD0000+0x8000); stock drivers, no kernel
 * modifications.
 *
 * A/B experiment (single kldload):
 *   line 5 / pin 8 : ALLOC -> SETUP -> FREE               (skip teardown)
 *   line 6 / pin 15: ALLOC -> SETUP -> TEARDOWN -> FREE   (documented order)
 * Expected (defect): MASK bit5 == 1 with intrmaps[5].pin == -1
 * Expected (control): MASK bit6 == 0 after teardown+free
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <machine/vmparam.h>

#include <vm/vm_page.h>

#include "gpio_if.h"
#include "gpio_intel_var.h"	/* copied next to this file by build.sh */

/* Cherry-View register image offsets (gpio_cherryview.c:56-81) */
#define GPO_BASE	0x000D0000ul	/* _CRS Memory32Fixed window */
#define CHV_IS		0x300
#define CHV_MASK	0x380
#define CHV_PINS	0x4400
#define PIN_CTL0(p)	(GPO_BASE + CHV_PINS + ((p) / 15) * 0x400 + \
			 ((p) % 15) * 8)

/* gpio_cherryview.c bit fields */
#define CTL0_GPIOEN	0x00008000u
#define INTSEL_SHIFT	28

/* acrestyp.h */
#define TRIG_EDGE	1	/* ACPI_EDGE_SENSITIVE */
#define POL_HIGH	0	/* ACPI_ACTIVE_HIGH */
#define TERM_DEFAULT	0	/* ACPI_PIN_CONFIG_DEFAULT */

#define PIN_A	8	/* valid chv_n pin, family 0 member 8 */
#define PIN_B	15	/* valid chv_n pin, family 1 member 0 */
#define LINE_A	5
#define LINE_B	6

static uint32_t
rd32(vm_paddr_t pa)
{
	return (*(volatile uint32_t *)PHYS_TO_DMAP(pa));
}

static void
wr32(vm_paddr_t pa, uint32_t v)
{
	*(volatile uint32_t *)PHYS_TO_DMAP(pa) = v;
}

static void
dummy_intr(void *arg)
{
}

static void
dump_state(const char *tag, struct gpio_intel_softc *sc)
{
	uint32_t is = rd32(GPO_BASE + CHV_IS);
	uint32_t mask = rd32(GPO_BASE + CHV_MASK);

	kprintf("gpiofree[%s]: IS=%08x MASK=%08x "
	    "(bit5=%d bit6=%d) map5{pin=%d,intidx=%d,is_level=%d} "
	    "map6{pin=%d,intidx=%d,is_level=%d}\n", tag, is, mask,
	    !!(mask & (1u << LINE_A)), !!(mask & (1u << LINE_B)),
	    sc->intrmaps[LINE_A].pin, sc->intrmaps[LINE_A].intidx,
	    sc->intrmaps[LINE_A].is_level,
	    sc->intrmaps[LINE_B].pin, sc->intrmaps[LINE_B].intidx,
	    sc->intrmaps[LINE_B].is_level);
}

static int
gpiofree_modevent(module_t mod, int type, void *arg)
{
	devclass_t dc;
	device_t dev;
	struct gpio_intel_softc *sc;
	void *ca = NULL, *cb = NULL;
	int error;

	switch (type) {
	case MOD_LOAD:
		dc = devclass_find("gpio_intel");
		dev = dc ? devclass_get_device(dc, 0) : NULL;
		if (dev == NULL) {
			kprintf("gpiofree: gpio_intel0 not found "
			    "(is the DF-5518 rig booted?)\n");
			return (ENXIO);
		}
		sc = device_get_softc(dev);
		kprintf("gpiofree: gpio_intel0 dev=%p sc=%p\n", dev, sc);

		/*
		 * Plant two pads in the register image:
		 *   CTL0 = INTSEL<<28 | GPIOEN  (GPIOCFG = 0: RX-capable)
		 *   CTL1 = INTCFG 2 (edge/rising, matches TRIG_EDGE/POL_HIGH)
		 */
		wr32(PIN_CTL0(PIN_A), (LINE_A << INTSEL_SHIFT) | CTL0_GPIOEN);
		wr32(PIN_CTL0(PIN_A) + 4, 2u);
		wr32(PIN_CTL0(PIN_B), (LINE_B << INTSEL_SHIFT) | CTL0_GPIOEN);
		wr32(PIN_CTL0(PIN_B) + 4, 2u);
		kprintf("gpiofree: planted pin%d CTL0=%08x CTL1=%08x; "
		    "pin%d CTL0=%08x CTL1=%08x\n",
		    PIN_A, rd32(PIN_CTL0(PIN_A)), rd32(PIN_CTL0(PIN_A) + 4),
		    PIN_B, rd32(PIN_CTL0(PIN_B)), rd32(PIN_CTL0(PIN_B) + 4));

		dump_state("before", sc);

		/* ---- A: ALLOC -> SETUP -> FREE (teardown SKIPPED) ---- */
		error = GPIO_ALLOC_INTR(dev, PIN_A, TRIG_EDGE, POL_HIGH,
		    TERM_DEFAULT, &ca);
		kprintf("gpiofree[A]: GPIO_ALLOC_INTR(pin%d line%d) = %d "
		    "cookie=%p\n", PIN_A, LINE_A, error, ca);
		dump_state("A-alloc", sc);
		if (error == 0) {
			GPIO_SETUP_INTR(dev, ca, NULL, dummy_intr);
			kprintf("gpiofree[A]: after GPIO_SETUP_INTR "
			    "(enable_intr unmasked line %d)\n", LINE_A);
			dump_state("A-setup", sc);

			GPIO_FREE_INTR(dev, ca);	/* NO teardown */
			kprintf("gpiofree[A]: after GPIO_FREE_INTR with NO "
			    "teardown -> unmap_intr destroyed the mapping\n");
			dump_state("A-free", sc);
		}

		/* ---- B: ALLOC -> SETUP -> TEARDOWN -> FREE ---- */
		error = GPIO_ALLOC_INTR(dev, PIN_B, TRIG_EDGE, POL_HIGH,
		    TERM_DEFAULT, &cb);
		kprintf("gpiofree[B]: GPIO_ALLOC_INTR(pin%d line%d) = %d "
		    "cookie=%p\n", PIN_B, LINE_B, error, cb);
		if (error == 0) {
			GPIO_SETUP_INTR(dev, cb, NULL, dummy_intr);
			GPIO_TEARDOWN_INTR(dev, cb);
			GPIO_FREE_INTR(dev, cb);
			kprintf("gpiofree[B]: after SETUP -> TEARDOWN -> FREE "
			    "(documented order)\n");
			dump_state("B-done", sc);
		}

		kprintf("gpiofree: SUMMARY MASK=%08x: line%d armed-with-no-"
		    "mapping=%d (DEFECT if 1), line%d left-armed=%d "
		    "(control, expect 0)\n",
		    rd32(GPO_BASE + CHV_MASK), LINE_A,
		    !!(rd32(GPO_BASE + CHV_MASK) & (1u << LINE_A)) &&
		    sc->intrmaps[LINE_A].pin == -1,
		    LINE_B,
		    !!(rd32(GPO_BASE + CHV_MASK) & (1u << LINE_B)));
		break;
	case MOD_UNLOAD:
		kprintf("gpiofree: unloaded\n");
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (0);
}

static moduledata_t gpiofree_mod = { "gpiofree", gpiofree_modevent, NULL };
DECLARE_MODULE(gpiofree, gpiofree_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(gpiofree, 1);
