/*
 * DF-5001 hub demonstration module.
 *
 * Executes the EXACT body of miibus_mediainit() (sys/dev/netif/mii_layer/
 * mii.c:228-245) against a real `struct ifmedia` whose ifm_list is EMPTY --
 * the state reached at attach time when every probed PHY takes its
 * "no media present" branch (family: atphy DF-4930, ciphy DF-4957,
 * jmphy DF-4981, truephy DF-5003, plus the ~20 siblings that share the
 * ukphy_attach shape) and the parent NIC's mediainit method adds nothing
 * of its own.
 *
 * The only omitted statement is the MIIBUS_MEDIAINIT(parent) poke
 * (mii.c:235) -- it is the parent NIC driver's callback and is a no-op for
 * this demonstration (the parents that have extra media would make the
 * panic UNREACHABLE, so skipping it is the conservative choice: we test
 * the hub walk in exactly the configuration where it is dangerous).
 *
 * With an empty list the walk (mii.c:238-243) never executes its body,
 * `media` stays 0 (mii.c:232), and the terminal ifmedia_set(&ifm, 0)
 * (mii.c:244) hits DragonFly's
 *
 *   ifmedia_match() == NULL -> kprintf("ifmedia_set: no match ...")
 *                          -> panic("ifmedia_set")     [sys/net/if_media.c]
 *
 * hw.miihub_fire=1    -> baseline: verbatim hub walk on empty list (panics)
 * hw.miihub_fixcheck=1-> the FIXED hub walk (fix.diff): empty list gets an
 *                        IFM_ETHER|IFM_AUTO entry before ifmedia_set(); must
 *                        survive, and ifm_cur must be non-NULL (which is
 *                        what keeps every mii *_service()'s
 *                        `ife = mii->mii_media.ifm_cur; ife->ifm_media`
 *                        dereference safe afterwards).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <net/if.h>
#include <net/if_media.h>

static struct ifmedia ifm;	/* stands in for mii->mii_media (mii_data) */

/*
 * Verbatim copy of the hub walk, mii.c:232-244.
 */
static void
miihub_mediainit_walk(struct ifmedia *ifmp)
{
	struct ifmedia_entry *m;
	int media = 0;

	/* mii.c:238-243 */
	for (m = LIST_FIRST(&ifmp->ifm_list); m != NULL;
	     m = LIST_NEXT(m, ifm_list)) {
		media = m->ifm_media;
		if (media == (IFM_ETHER | IFM_AUTO))
			break;
	}
	/* mii.c:244 */
	ifmedia_set(ifmp, media);
}

/*
 * The fix.diff shape: never call ifmedia_set() on an empty list.
 */
static void
miihub_mediainit_fixed(struct ifmedia *ifmp)
{
	struct ifmedia_entry *m;
	int media = 0;

	for (m = LIST_FIRST(&ifmp->ifm_list); m != NULL;
	     m = LIST_NEXT(m, ifm_list)) {
		media = m->ifm_media;
		if (media == (IFM_ETHER | IFM_AUTO))
			break;
	}
	if (m == NULL && LIST_EMPTY(&ifmp->ifm_list)) {
		ifmedia_add(ifmp, IFM_ETHER | IFM_AUTO, 0, NULL);
		media = IFM_ETHER | IFM_AUTO;
	}
	ifmedia_set(ifmp, media);
}

static int
miihub_fire(SYSCTL_HANDLER_ARGS)
{
	int v = 0, error;

	error = sysctl_handle_opaque(oidp, &v, sizeof(v), req);
	if (error != 0 || req->newptr == NULL)
		return (error);

	kprintf("miihub: ifmedia_init (EMPTY ifm_list -- all PHYs took the "
	    "'no media present' branch, parent added nothing)\n");
	ifmedia_init(&ifm, IFM_IMASK, NULL, NULL);

	kprintf("miihub: running miibus_mediainit walk verbatim (mii.c:238-244)\n");
	miihub_mediainit_walk(&ifm);	/* expected: panic("ifmedia_set") */

	kprintf("miihub: NOT REACHED\n");
	return (0);
}

static int
miihub_fixcheck(SYSCTL_HANDLER_ARGS)
{
	struct ifmedia ifm2;
	int v = 0, error;

	error = sysctl_handle_opaque(oidp, &v, sizeof(v), req);
	if (error != 0 || req->newptr == NULL)
		return (error);

	kprintf("miihub-fix: ifmedia_init (EMPTY list) + fixed hub walk "
	    "(empty -> add IFM_ETHER|IFM_AUTO -> ifmedia_set)\n");
	ifmedia_init(&ifm2, IFM_IMASK, NULL, NULL);
	miihub_mediainit_fixed(&ifm2);

	kprintf("miihub-fix: survived; ifm_cur=%p media=0x%x\n",
	    ifm2.ifm_cur, ifm2.ifm_media);
	if (ifm2.ifm_cur == NULL) {
		kprintf("miihub-fix: FAIL -- ifm_cur still NULL\n");
		return (1);
	}
	/* what every PHY service function does on the next tick (e.g.
	 * truephy.c:185 `ife = mii->mii_media.ifm_cur; ... IFM_INST(ife->ifm_media)`) */
	kprintf("miihub-fix: ifm_cur->ifm_media=0x%x (service deref safe)\n",
	    ifm2.ifm_cur->ifm_media);
	ifmedia_removeall(&ifm2);
	return (0);
}

static int
miihub_modevent(module_t mod, int type, void *arg)
{
	static struct sysctl_ctx_list ctx;

	switch (type) {
	case MOD_LOAD:
		sysctl_ctx_init(&ctx);
		SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO,
		    "miihub_fire", CTLTYPE_INT | CTLFLAG_RW,
		    0, 0, miihub_fire, "I",
		    "fire miibus_mediainit walk on empty media list (panics)");
		SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO,
		    "miihub_fixcheck", CTLTYPE_INT | CTLFLAG_RW,
		    0, 0, miihub_fixcheck, "I",
		    "replay with the fixed empty-list guard (must survive)");
		kprintf("miihub: loaded; hw.miihub_fire=1 panics, "
		    "hw.miihub_fixcheck=1 validates the fix shape\n");
		return (0);
	case MOD_UNLOAD:
		sysctl_ctx_free(&ctx);
		return (0);
	}
	return (0);
}
DEV_MODULE(miihub, miihub_modevent, NULL);
