/*
 * DF-2681 PoC: bus driver that installs bus_generic_delete_resource()
 * (subr_bus.c:3144) as its bus_delete_resource method -- an exported
 * NEWBUS default intended for exactly this use -- and then deletes a
 * resource on one of its children.  bus_generic_delete_resource()
 * dispatches BUS_DELETE_RESOURCE() on ITSELF instead of dev->parent,
 * so the call recurses forever until the kernel stack is exhausted.
 *
 * Load as root: kldload ./dfrec.ko  -> panic during attach.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/systm.h>

struct dfrec_softc {
	device_t	child;
};

static void	dfrec_identify(driver_t *, device_t);
static int	dfrec_probe(device_t);
static int	dfrec_attach(device_t);
static int	dfrec_detach(device_t);

static device_method_t dfrec_methods[] = {
	/* Device interface */
	DEVMETHOD(device_identify,	dfrec_identify),
	DEVMETHOD(device_probe,		dfrec_probe),
	DEVMETHOD(device_attach,	dfrec_attach),
	DEVMETHOD(device_detach,	dfrec_detach),

	/* Bus interface: the shipped "generic" default that recurses */
	DEVMETHOD(bus_delete_resource,	bus_generic_delete_resource),

	DEVMETHOD_END
};

static driver_t dfrec_driver = {
	"dfrec",
	dfrec_methods,
	sizeof(struct dfrec_softc)
};
static devclass_t dfrec_devclass;


DRIVER_MODULE(dfrec, nexus, dfrec_driver, dfrec_devclass, NULL, NULL);

static void
dfrec_identify(driver_t *driver, device_t parent)
{
	if (device_find_child(parent, "dfrec", -1) == NULL)
		BUS_ADD_CHILD(parent, parent, 0, "dfrec", -1);
}

static int
dfrec_probe(device_t dev)
{
	return (0);
}

static int
dfrec_attach(device_t dev)
{
	struct dfrec_softc *sc = device_get_softc(dev);

	sc->child = device_add_child(dev, "dfrecchild", 0);
	kprintf("dfrec: attached, calling bus_delete_resource() on child\n");
	/* dispatch on dfrec bus -> bus_generic_delete_resource -> itself */
	bus_delete_resource(sc->child, SYS_RES_IRQ, 0);
	kprintf("dfrec: returned (no bug)\n");
	return (0);
}

static int
dfrec_detach(device_t dev)
{
	struct dfrec_softc *sc = device_get_softc(dev);

	if (sc->child)
		device_delete_child(dev, sc->child);
	return (0);
}
