/*
 * DF-5600 harness - ig4_pci.c probe/attach version wipe.
 *
 * Target: sys/bus/smbus/ichiic/ig4_pci.c
 *   ig4iic_pci_probe()      ig4_pci.c:151  sc->version = <table row>
 *   ig4iic_pci_attach()     ig4_pci.c:167  bzero(sc, sizeof(*sc))  <-- WIPES IT
 *   ig4iic_pci_attach()     ig4_pci.c:193  ig4iic_attach(sc) consumes version
 *
 * The ACPI sibling does NOT have the bug: ig4_acpi.c:104-108 sets
 * sc->version = IG4_ATOM in attach, after init, with no bzero.
 *
 * Consequence, executed through the REAL production ig4iic_attach() (from
 * the stock /boot/kernel/ig4.ko): a Cannon Lake-LP controller (devid
 * 0x9dc58086 -> IG4_CANNONLAKE per ig4_pci.c:128) is programmed as a
 * Haswell:
 *   - ig4_iic.c:513-518  IG4_REG_GENERAL (0x808, HSW-only register) gets
 *                         IG4_GENERAL_SWMODE (0x4) written into it
 *   - ig4_iic.c:522-526  SW/AUTO LTR regs 0x810/0x814 read (HSW offsets)
 *   - ig4_iic.c:534-541  COMP_VER gate applied to a generation upstream
 *                         restricts to HSW/ATOM
 *
 * Run A replays the exact production statement order (probe stores
 * version, attach prologue bzero's it) and calls the real ig4iic_attach()
 * on a fake MMIO register file.  Run B is the control: identical except
 * sc->version is restored after the bzero (the fix / the ig4_acpi.c
 * ordering).  Success: run A's fake file shows GENERAL(0x808)=0x4 (SWMODE
 * written on non-Haswell silicon); run B's stays 0x0.
 *
 * Rig notes (no ig4 LPSS controller exists in QEMU): fake MMIO register
 * file is kmalloc'd memory; on x86_64 the bus_space tag is unused for
 * memory space (X86_64_BUS_SPACE_MEM == 1) and the handle is dereferenced
 * directly, so regs_t=1 + regs_h=<kva> works.  The harness device lives
 * on a pseudo-bus parented to the root bus (root_print_child is a safe
 * no-op; nexus_print_child would deref nexus ivars that raw
 * device_add_child() children lack).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/malloc.h>
#include <sys/serialize.h>

#include <bus/pci/pcireg.h>

#include "ig4_reg.h"
#include "ig4_var.h"

/* ig4iic_attach / ig4iic_detach are declared in ig4_var.h */

#define FAKE_DEVID	0x9dc58086	/* Cannon Lake-LP I2C Controller-0 */

static device_t df56_bus;

/* ======================= pseudo bus: df56bus ======================= */

static int
df56bus_probe(device_t dev)
{
	device_set_desc(dev, "DF-56xx pseudo bus (bus_setup_intr -> EINVAL)");
	return (0);
}

static int
df56bus_attach(device_t dev)
{

	/* children are created by module glue */
	return (0);
}

static int
df56bus_setup_intr(device_t bus, device_t child, struct resource *r,
		   int flags, void (*ihand)(void *), void *arg,
		   void **cookiep, lwkt_serialize_t serializer,
		   const char *desc)
{

	kprintf("DF5601: pseudo-bus bus_setup_intr() -> EINVAL (stands in "
	    "for rman_activate_resource()/register_int() failure on real "
	    "hardware)\n");
	(void)bus;
	(void)child;
	(void)r;
	(void)flags;
	(void)ihand;
	(void)arg;
	(void)cookiep;
	(void)serializer;
	(void)desc;
	return (EINVAL);
}

static device_method_t df56bus_methods[] = {
	DEVMETHOD(device_probe,		df56bus_probe),
	DEVMETHOD(device_attach,	df56bus_attach),
	DEVMETHOD(bus_print_child,	bus_generic_print_child),
	DEVMETHOD(bus_setup_intr,	df56bus_setup_intr),
	DEVMETHOD_END
};

static driver_t df56bus_driver = { "df56bus", df56bus_methods, 0 };
static devclass_t df56bus_devclass;

/* root bus: root_print_child() is a safe no-op, unlike nexus_print_child
 * which derefs nexus ivars that raw device_add_child() children lack. */
DRIVER_MODULE(df56bus, root, df56bus_driver, df56bus_devclass, NULL, NULL);

/* =================== DF-5600: version wipe scenario =================== */

static int
df5600_devid_version(uint32_t devid)
{
	/* the ig4iic_pci_devices[] row for 0x9dc58086 (ig4_pci.c:128) */
	if (devid == FAKE_DEVID)
		return (IG4_CANNONLAKE);
	return (IG4_HASWELL);
}

static void
df5600_dump_file(const char *tag, volatile uint32_t *f)
{
	int i;

	kprintf("DF5600[%s]: fake-MMIO write map (offset=value):", tag);
	for (i = 0; i < 0x1000 / 4; i++) {
		if (f[i] != 0)
			kprintf(" %03x=%08x", i * 4, f[i]);
	}
	kprintf("\n");
}

static int
df5600_run_scenario(device_t dev, int control)
{
	ig4iic_softc_t *sc;
	volatile uint32_t *f;
	const char *tag = control ? "B-fixed" : "A-wiped";
	int error;
	int vprobe;

	f = kmalloc(0x1000, M_DEVBUF, M_WAITOK | M_ZERO);
	sc = kmalloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);
	if (f == NULL || sc == NULL)
		return (ENOMEM);

	/* fake controller identity: designware core regs */
	f[0x0F4 / 4] = 0x0000003F;		/* COMP_PARAM1 */
	f[0x0F8 / 4] = 0x7FFFFFFF;		/* COMP_VER: passes any gate */
	f[0x0FC / 4] = 0x7FFFFFFF;		/* COMP_TYPE */
	f[0x09C / 4] = 0;			/* ENABLE_STATUS: enable never
						 * latches -> attach-2 ENXIO */

	/* ---- ig4iic_pci_probe() replay (ig4_pci.c:147-153) ---- */
	sc->version = df5600_devid_version(FAKE_DEVID);
	vprobe = sc->version;

	/* ---- ig4iic_pci_attach() prologue replay (ig4_pci.c:167-191) ---- */
	bzero(sc, sizeof(*sc));			/* ig4_pci.c:167 */
	lwkt_serialize_init(&sc->slz);		/* ig4_pci.c:169 */
	sc->dev = dev;				/* ig4_pci.c:171 */
	sc->regs_rid = PCIR_BAR(0);		/* ig4_pci.c:172 */
	sc->regs_t = 1;				/* mem space; tag unused on x86 */
	sc->regs_h = (bus_space_handle_t)f;	/* bus_alloc_resource_any faked */
	sc->intr_type = 0;			/* PCI_INTR_TYPE_LEGACY */
	sc->pci_attached = 1;			/* ig4_pci.c:191 */
	if (control)
		sc->version = vprobe;		/* THE FIX: re-set after bzero */

	kprintf("DF5600[%s]: probe stored version=%d; after attach prologue "
	    "sc->version=%d -> %s\n", tag, vprobe, sc->version,
	    (sc->version == vprobe) ? "preserved" :
	    "WIPED to 0 == IG4_HASWELL");

	error = ig4iic_attach(sc);		/* ig4_pci.c:193 */
	kprintf("DF5600[%s]: ig4iic_attach() = %d (ENXIO expected: fake "
	    "ENABLE_STATUS never latches)\n", tag, error);
	kprintf("DF5600[%s]: GENERAL(0x808)=%08x -> Haswell-only SWMODE "
	    "write %s this non-Haswell part\n", tag, f[0x808 / 4],
	    (f[0x808 / 4] & IG4_GENERAL_SWMODE) ?
	    "HAPPENED ON" : "did not happen on");
	df5600_dump_file(tag, f);

	/* production teardown path (what ig4_pci.c:208 does) */
	error = ig4iic_detach(sc);
	kprintf("DF5600[%s]: ig4iic_detach() = %d\n", tag, error);

	kfree(__DEVOLATILE(uint32_t *, f), M_DEVBUF);
	kfree(sc, M_DEVBUF);
	return (0);
}

static int
df5600_probe(device_t dev)
{
	device_set_desc(dev, "DF-5600 ig4 version-wipe harness");
	return (0);
}

static int
df5600_attach(device_t dev)
{
	device_printf(dev, "run A: production ordering (probe sets version, "
	    "attach bzero wipes it)\n");
	df5600_run_scenario(dev, 0);
	device_printf(dev, "run B: control (version restored after bzero)\n");
	df5600_run_scenario(dev, 1);
	return (0);
}

static device_method_t df5600_methods[] = {
	DEVMETHOD(device_probe,		df5600_probe),
	DEVMETHOD(device_attach,	df5600_attach),
	DEVMETHOD_END
};

static driver_t df5600_driver = { "df56f1", df5600_methods, 0 };
static devclass_t df5600_devclass;

DRIVER_MODULE(df56f1, df56bus, df5600_driver, df5600_devclass, NULL, NULL);

/* ============================ module glue ============================ */

static int
df56_load(struct module *m, int what, void *arg)
{
	device_t dev;
	int error;

	switch (what) {
	case MOD_LOAD:
		df56_bus = device_add_child(root_bus, "df56bus", -1);
		if (df56_bus == NULL)
			return (ENOMEM);
		error = device_probe_and_attach(df56_bus);
		if (error) {
			kprintf("DF56xx: pseudo-bus attach failed: %d\n",
			    error);
			return (error);
		}
		/* DF-5600 runs immediately */
		dev = device_add_child(df56_bus, "df56f1", -1);
		if (dev == NULL)
			return (ENOMEM);
		error = device_probe_and_attach(dev);
		if (error)
			kprintf("DF5600: harness device attach failed: %d\n",
			    error);
		kprintf("DF56xx: loaded; DF-5600 done; load df56f2.ko to "
		    "arm the DF-5601 scenario\n");
		break;
	default:
		break;
	}
	return (0);
}

static moduledata_t df56_mod = { "df56", df56_load, NULL };
DECLARE_MODULE(df56, df56_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_DEPEND(df56, ig4iic, 1, 1, 1);	/* ig4iic_attach / ig4iic_detach */
