/*
 * DF-4981 sink demonstration module (jmphy).
 *
 * Executes the exact terminal sequence of the jmphy no-media attach chain:
 *
 *   jmphy_attach (jmphy.c:119-129): BMSR reports zero media bits and
 *     (no BMSR_EXTSTAT, or EXTSR media bits zero) -> the "no media present"
 *     branch at jmphy.c:124-127 prints and SKIPS mii_phy_add_media(),
 *     then jmphy.c:131 still calls MIIBUS_MEDIAINIT(sc->mii_dev)
 *     -> miibus_mediainit (mii.c:228-246): the parent (jme(4), which
 *        implements no miibus_mediainit method -- if_jme.c:218-220 -- and
 *        attaches the bus via mii_phy_probe at if_jme.c:1028) adds no media
 *        of its own, so the ifm_list walk finds NOTHING, media stays 0,
 *        and mii.c:244 calls ifmedia_set(&mii->mii_media, 0)
 *     -> ifmedia_set (sys/net/if_media.c:168-175): ifmedia_match() on an
 *        empty list returns NULL -> kprintf + panic("ifmedia_set")
 *
 * Only the PHY hardware state (a JMicron JMP211/JMP202 whose ID registers
 * match miidevs JMICRON/JMP211|JMP202 while BMSR reads 0 -- e.g. jme's SMI
 * read timing out returns 0, if_jme.c:303-308 -- i.e. a stuck/powered-down
 * PHY) is not emulatable on this guest; the kernel-side sink is executed
 * verbatim.
 */
#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;

static int
jmsink_fire(SYSCTL_HANDLER_ARGS)
{
	int v = 0, error;
	struct ifmedia_entry *m;
	int media = 0;

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

	kprintf("jmsink: ifmedia_init (empty list)\n");
	ifmedia_init(&ifm, 0, NULL, NULL);

	/* mii.c:236-240 verbatim (miibus_mediainit list walk) */
	for (m = LIST_FIRST(&ifm.ifm_list); m != NULL;
	     m = LIST_NEXT(m, ifm_list)) {
		media = m->ifm_media;
		if (media == (IFM_ETHER | IFM_AUTO))
			break;
	}
	kprintf("jmsink: list walk done, media=0x%x; calling ifmedia_set (mii.c:244)\n",
	    media);
	ifmedia_set(&ifm, media);	/* expected: panic("ifmedia_set") */

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

/*
 * DF-4981 fix-mechanism check:
 *
 * The proposed fix (fix.diff) makes jmphy_attach call
 * mii_phy_add_media() unconditionally.  With zero capabilities that adds
 * exactly the IFM_NONE fallback entry (mii_physubr.c:235-237:
 * "if ((sc->mii_flags & MIIF_NOISOLATE) == 0) ADD(IFM_ETHER|IFM_NONE ...)";
 * jmphy never sets MIIF_NOISOLATE and jme passes no such flag through
 * mii_phy_probe), so miibus_mediainit's ifmedia_set() finds a match,
 * does not panic, and ifm_cur is non-NULL for later jmphy_service()
 * calls (jmphy.c:138 dereferences mii->mii_media.ifm_cur).
 *
 * hw.jmsink_fixcheck=1 replays the same sequence WITH the fallback entry.
 */
static int
jmsink_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("jmsink-fix: ifmedia_init + IFM_NONE fallback entry (the fixed jmphy path)\n");
	ifmedia_init(&ifm2, 0, NULL, NULL);
	ifmedia_add(&ifm2,
	    IFM_MAKEWORD(IFM_ETHER, IFM_NONE, 0, 0), 0, NULL);
	kprintf("jmsink-fix: calling ifmedia_set(IFM_ETHER|IFM_NONE) -- must NOT panic\n");
	ifmedia_set(&ifm2, IFM_ETHER | IFM_NONE);
	kprintf("jmsink-fix: survived; ifm_cur=%p (non-NULL keeps jmphy_service safe)\n",
	    ifm2.ifm_cur);
	ifmedia_removeall(&ifm2);
	return (0);
}

static int
jmsink_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,
		    "jmsink_fire", CTLTYPE_INT | CTLFLAG_RW,
		    0, 0, jmsink_fire, "I", "fire the empty-list ifmedia_set");
		SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO,
		    "jmsink_fixcheck", CTLTYPE_INT | CTLFLAG_RW,
		    0, 0, jmsink_fixcheck, "I", "replay with the fixed fallback entry");
		kprintf("jmsink: loaded; sysctl hw.jmsink_fire=1 to panic\n");
		return (0);
	case MOD_UNLOAD:
		sysctl_ctx_free(&ctx);
		return (0);
	}
	return (0);
}
DEV_MODULE(jmsink, jmsink_modevent, NULL);
