/*
 * DF-5598 harness — ichsmb detach-veto family probe.
 *
 * Target: sys/bus/smbus/ichsmb/ichsmb_pci.c:175 wires device_detach to
 * ichsmb_detach() (sys/bus/smbus/ichsmb/ichsmb.c:680), which IGNORES the
 * return of device_delete_child(dev, sc->smb) at ichsmb.c:689 and then
 * unconditionally runs ichsmb_release_resources() + lockuninit() and
 * returns 0 — the DF-5544/5545/5562 family shape.
 *
 * DIFFERENCE under test: unlike uhci/xhci/ohci (device_delete_children()
 * first, swallowed), ichsmb_detach runs a CHECKED bus_generic_detach()
 * cascade FIRST (ichsmb.c:686), and smbus_detach() checks its own cascade
 * (smbus.c:110-112). The only in-tree leaf, smb_detach(), never vetoes
 * (smb.c:147-155). So a vetoing grandchild should be caught at
 * bus_generic_detach and ichsmb_detach should return EBUSY with ALL
 * resources intact — the deterministic family panic should NOT reproduce.
 *
 * This KLD attaches a vetoing child directly under smbus0 (grandchild of
 * ichsmb0) and the driver script drives the exact kldunload path
 * (linker_file_unload -> driver_module_handler -> devclass_delete_driver,
 * subr_bus.c:897) — the same context as PCI hot-unplug.
 *
 * sysctl hw.df5598_armed=1  detach of the child returns EBUSY (default)
 * sysctl hw.df5598_armed=0  child cooperates (clean-unload control run)
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/sysctl.h>

#include <bus/smbus/smbconf.h>

static device_t veto_child;
static int armed = 1;

SYSCTL_INT(_hw, OID_AUTO, df5598_armed, CTLFLAG_RW, &armed, 0,
    "DF-5598: 1 = veto child returns EBUSY from detach");

static int veto_probe(device_t dev);
static int veto_attach(device_t dev);
static int veto_detach(device_t dev);

static device_method_t veto_methods[] = {
	DEVMETHOD(device_probe, veto_probe),
	DEVMETHOD(device_attach, veto_attach),
	DEVMETHOD(device_detach, veto_detach),
	DEVMETHOD_END
};

static driver_t veto_driver = { "df5598veto", veto_methods, 0 };
static devclass_t veto_devclass;

DRIVER_MODULE(df5598veto, smbus, veto_driver, veto_devclass, NULL, NULL);

static int
veto_probe(device_t dev)
{
	device_set_desc(dev, "DF-5598 detach-veto child");
	return (0);
}

static int
veto_attach(device_t dev)
{
	device_printf(dev, "armed; detach returns EBUSY while "
	    "hw.df5598_armed=1\n");
	return (0);
}

static int
veto_detach(device_t dev)
{
	if (armed) {
		device_printf(dev, "DETACH VETO: returning EBUSY -- if this "
		    "is swallowed, ichsmb_release_resources() runs under me\n");
		return (EBUSY);
	}
	device_printf(dev, "cooperating\n");
	return (0);
}

static int
df5598_load(struct module *m, int what, void *arg)
{
	device_t bus;
	devclass_t dc;
	int error;

	switch (what) {
	case MOD_LOAD:
		bus = devclass_get_device(devclass_find("smbus"), 0);
		if (bus == NULL) {
			kprintf("DF5598: no smbus0; kldload ichsmb first\n");
			return (ENXIO);
		}
		veto_child = device_add_child(bus, "df5598veto", -1);
		if (veto_child == NULL)
			return (ENOMEM);
		error = device_probe_and_attach(veto_child);
		if (error) {
			kprintf("DF5598: probe_and_attach failed: %d\n", error);
			return (error);
		}
		kprintf("DF5598: veto child attached under %s\n",
		    device_get_nameunit(bus));
		break;
	case MOD_UNLOAD:
		/* re-resolve: smbus0/child may already be gone (clean
		 * ichsmb unload deletes the whole subtree) */
		dc = devclass_find("df5598veto");
		veto_child = dc ? devclass_get_device(dc, 0) : NULL;
		if (veto_child != NULL) {
			armed = 0;
			error = device_delete_child(
			    device_get_parent(veto_child), veto_child);
			if (error) {
				kprintf("DF5598: child delete: %d\n", error);
				return (error);
			}
		}
		kprintf("DF5598: unloaded\n");
		break;
	default:
		break;
	}
	return (0);
}

static moduledata_t df5598_mod = { "df5598", df5598_load, NULL };
DECLARE_MODULE(df5598, df5598_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_DEPEND(df5598, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER);
