DF-5053 / qsmedia.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 | /* * qsmedia.c โ synthetic MII MAC controller (DF-5053 PoC trigger) * * Reproduces the hardware precondition for DF-5053 without hardware: * a MII bus carrying exactly one PHY whose ID registers answer * "Quality Semiconductor QS6612" (OUI 0x006051, model 0x0000 โ * miidevs:66,241; qsphys[] qsphy.c:109-112) but whose BMSR loses its * media capability bits across the software reset the driver itself * issues at attach (qsphy_reset -> mii_phy_reset writes BMCR_RESET, * qsphy.c:162 -> mii_physubr.c:191). * * The resulting attach sequence on a stock kernel: * * miibus_probe (mii.c:105-116) BMSR pre-reset has media bits -> PHY exists * qsphy_probe (qsphy.c:129) PHYIDR1/2 match QUALSEMI/QS6612 -> qsphy * qsphy_attach (qsphy.c:143) qsphy_reset -> BMCR_RESET write [latch] * PHY_READ(MII_BMSR) == 0 (media gone) * "no media present" (qsphy.c:171) * mii_phy_add_media SKIPPED (qsphy.c:168-171) * MIIBUS_MEDIAINIT -> miibus_mediainit (mii.c:227-245) * parent adds nothing; ifm_list EMPTY; * ifmedia_set(&mii->mii_media, 0) (mii.c:244) * ifmedia_set (if_media.c) no match for 0x0 -> panic("ifmedia_set") * * i.e. the DF-4930/DF-4957/DF-4981/DF-5003 empty-media family panic (hub: * DF-5001) with a REAL qsphy driver in the backtrace โ the 5th leaf. * * The register image: * - pre-reset BMSR: BMSR_ANEG|100TXFDX|100TXHDX|10TFDX|10THDX (enough for * miibus_no_phy, mii.c:114-115, and a plausible QS6612); * - any BMCR write with BMCR_RESET latches "hosed": all later BMSR reads * return 0 โ the flaky/faulty-PHY story the family documents; * - PHYIDR1=0x0181, PHYIDR2=0x4401 => MII_OUI()=0x6051 (QUALSEMI), * MII_MODEL()=0 (QS6612), MII_REV()=1 (mii.h:109-111); * - BMCR reads back with BMCR_RESET clear so mii_phy_reset's poll * (mii_physubr.c:204-209) completes at once. * * fixcheck mode (hw.qsmedia_fixcheck=1, run after the panic on a reset * guest): replays the FIXED attach shape โ the fix.diff branch adds * ifmedia_add(&mii->mii_media, IFM_MAKEWORD(IFM_ETHER, IFM_NONE, 0, * sc->mii_inst), MII_MEDIA_NONE, NULL); * to the no-media branch (mirroring DF-5003's leaf fix) โ against a real * struct ifmedia, then runs the hub walk verbatim (mii.c:232-244) and * verifies ifm_cur is non-NULL afterwards (which is what keeps every * mii *_service()'s `ife->ifm_cur->ifm_media' dereference safe). * * Usage in the guest: * make SYSDIR=/usr/src/sys # builds qsmedia.ko * kldload ./qsmedia.ko # baseline: REAL qsphy attach -> panic * (after vm.sh reset:) * sysctl hw.qsmedia_fixcheck=1 # fixed attach shape survives */ #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/bus.h> #include <sys/malloc.h> #include <sys/socket.h> #include <sys/sysctl.h> #include <net/if.h> #include <net/if_media.h> #include <net/if_var.h> #include <dev/netif/mii_layer/mii.h> #include <dev/netif/mii_layer/miivar.h> #include "miibus_if.h" /* QS6612: OUI 0x006051 -> PHYIDR1 = OUI>>6 = 0x0181, * PHYIDR2 = (OUI&0x3f)<<10 | model<<4 | rev = 0x4401 */ #define QS6612_ID1 0x0181 #define QS6612_ID2 0x4401 #define QSM_BMSR_GOOD (BMSR_ANEG | BMSR_100TXFDX | BMSR_100TXHDX | \ BMSR_10TFDX | BMSR_10THDX) struct qsmedia_softc { struct ifnet ifnet; /* mii_ifp; zeroed => !IFF_UP */ device_t miibus; uint16_t phy[8]; /* canned register image */ int hosed; /* set by a BMCR_RESET write */ }; static void qsmedia_identify(driver_t *, device_t); static int qsmedia_probe(device_t); static int qsmedia_attach(device_t); static int qsmedia_detach(device_t); static int qsmedia_readreg(device_t, int, int); static int qsmedia_writereg(device_t, int, int, int); static void qsmedia_statchg(device_t); static void qsmedia_mediainit(device_t); static int qsmedia_ifmedia_upd(struct ifnet *); static void qsmedia_ifmedia_sts(struct ifnet *, struct ifmediareq *); static device_method_t qsmedia_methods[] = { DEVMETHOD(device_identify, qsmedia_identify), DEVMETHOD(device_probe, qsmedia_probe), DEVMETHOD(device_attach, qsmedia_attach), DEVMETHOD(device_detach, qsmedia_detach), DEVMETHOD(miibus_readreg, qsmedia_readreg), DEVMETHOD(miibus_writereg, qsmedia_writereg), DEVMETHOD(miibus_statchg, qsmedia_statchg), DEVMETHOD(miibus_mediainit, qsmedia_mediainit), DEVMETHOD_END }; static devclass_t qsmedia_devclass; static driver_t qsmedia_driver = { "qsmedia", qsmedia_methods, sizeof(struct qsmedia_softc) }; DRIVER_MODULE(qsmedia, nexus, qsmedia_driver, qsmedia_devclass, NULL, NULL); DRIVER_MODULE(miibus, qsmedia, miibus_driver, miibus_devclass, NULL, NULL); MODULE_DEPEND(qsmedia, miibus, 1, 1, 1); static void qsmedia_identify(driver_t *driver, device_t parent) { if (device_find_child(parent, "qsmedia", -1) == NULL) BUS_ADD_CHILD(parent, parent, 0, "qsmedia", -1); } static int qsmedia_probe(device_t dev) { device_set_desc(dev, "synthetic single-PHY MII controller (DF-5053)"); return (0); } static int qsmedia_attach(device_t dev) { struct qsmedia_softc *sc = device_get_softc(dev); int error; bzero(sc, sizeof(*sc)); sc->phy[MII_BMCR] = 0; sc->phy[MII_BMSR] = QSM_BMSR_GOOD; sc->phy[MII_PHYIDR1] = QS6612_ID1; sc->phy[MII_PHYIDR2] = QS6612_ID2; sc->phy[MII_ANAR] = ANAR_CSMA; /* fixmode (kenv hw.qsmedia.fixmode=1 before kldload) suppresses the * probe so the fixcheck sysctl can run on a live guest */ if (kgetenv("hw.qsmedia.fixmode")) { kprintf("qsmedia: fixmode -- miibus probe suppressed\n"); return (0); } kprintf("qsmedia: probing MII bus (QS6612 id1=0x%04x id2=0x%04x, " "pre-reset BMSR=0x%04x)\n", QS6612_ID1, QS6612_ID2, QSM_BMSR_GOOD); error = mii_phy_probe(dev, &sc->miibus, qsmedia_ifmedia_upd, qsmedia_ifmedia_sts); if (error) { kprintf("qsmedia: mii_phy_probe failed: %d\n", error); return (error); } /* NOT REACHED on a stock kernel: qsphy_attach panics inside the * MIIBUS_MEDIAINIT below (empty media list -> ifmedia_set panic) */ kprintf("qsmedia: NOT REACHED (expected panic in qsphy_attach)\n"); return (0); } static int qsmedia_detach(device_t dev) { return (0); } static int qsmedia_readreg(device_t dev, int phy, int reg) { struct qsmedia_softc *sc = device_get_softc(dev); uint16_t v; if (phy != 0 || reg < 0 || reg > 7) return (0xffff); /* absent */ if (reg == MII_BMSR && sc->hosed) return (0); /* media bits gone after reset */ v = sc->phy[reg]; if (reg == MII_BMCR) v &= ~BMCR_RESET; /* reset completes instantly */ return (v); } static int qsmedia_writereg(device_t dev, int phy, int reg, int data) { struct qsmedia_softc *sc = device_get_softc(dev); if (phy != 0 || reg < 0 || reg > 7) return (0); if (reg == MII_BMCR && (data & BMCR_RESET)) sc->hosed = 1; /* PHY comes out of reset broken */ sc->phy[reg] = (uint16_t)data; return (0); } static void qsmedia_statchg(device_t dev) { } static void qsmedia_mediainit(device_t dev) { /* parent NIC adds no media of its own (the family precondition) */ } static int qsmedia_ifmedia_upd(struct ifnet *ifp) { return (0); } static void qsmedia_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { ifmr->ifm_active = IFM_ETHER | IFM_AUTO; ifmr->ifm_status = IFM_AVALID; } /* * Fix-mechanism replay (DF-5001's run_fix.sh pattern): the FIXED * qsphy_attach no-media branch (fix.diff, mirroring DF-5003's leaf fix) * adds an IFM_NONE fallback entry before MIIBUS_MEDIAINIT, so the hub * walk (mii.c:232-244, replayed verbatim) finds it and ifmedia_set() * succeeds. Must survive with ifm_cur non-NULL โ the exact invariant * that keeps the *_service() entry dereferences safe. */ static struct ifmedia qsmedia_ifm; static void qsmedia_fixed_attach_shape(struct ifmedia *ifmp) { /* qsphy_attach no-media branch WITH fix.diff applied (qsphy.c:168ff): * kprintf("no media present"); + the fix's ifmedia_add fallback */ ifmedia_add(ifmp, IFM_MAKEWORD(IFM_ETHER, IFM_NONE, 0, 0), MII_MEDIA_NONE, NULL); /* MIIBUS_MEDIAINIT -> miibus_mediainit hub walk, mii.c:232-244 */ { 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; } ifmedia_set(ifmp, media); } } static int qsmedia_fixcheck_sysctl(SYSCTL_HANDLER_ARGS) { int error, t = 0; error = sysctl_handle_int(oidp, &t, 0, req); if (error || req->newptr == NULL) return (error); if (t == 0) return (0); kprintf("qsmedia-fix: ifmedia_init (EMPTY list; qsphy took the " "'no media present' branch)\n"); ifmedia_init(&qsmedia_ifm, IFM_IMASK, NULL, NULL); qsmedia_fixed_attach_shape(&qsmedia_ifm); kprintf("qsmedia-fix: survived; ifm_cur=%p media=0x%x\n", qsmedia_ifm.ifm_cur, qsmedia_ifm.ifm_cur ? qsmedia_ifm.ifm_cur->ifm_media : -1); kprintf("qsmedia-fix: %s (service deref %s)\n", qsmedia_ifm.ifm_cur ? "ifm_cur non-NULL" : "ifm_cur NULL", qsmedia_ifm.ifm_cur ? "safe" : "UNSAFE"); return (0); } SYSCTL_PROC(_hw, OID_AUTO, qsmedia_fixcheck, CTLTYPE_INT | CTLFLAG_RW, 0, 0, qsmedia_fixcheck_sysctl, "I", "replay DF-5053 fixed attach shape"); |