/*- * Copyright (c) 2026 Ruslan Bukin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "if_dwc_if.h" struct dwc_hash_maddr_ctx { struct dwc_softc *sc; uint32_t hash[8]; }; #define STATS_HARVEST_INTERVAL 2 /* Pause time field in the transmitted control frame */ static int dwc_pause_time = 0xffff; TUNABLE_INT("hw.dwc.pause_time", &dwc_pause_time); /* * MIIBUS functions */ int dwc4_miibus_read_reg(device_t dev, int phy, int reg) { struct dwc_softc *sc; uint32_t mii; size_t cnt; int rv = 0; sc = device_get_softc(dev); mii = ((phy << MACMDIOAR_PA_S) & MACMDIOAR_PA_M) | ((reg << MACMDIOAR_RDA_S) & MACMDIOAR_RDA_M) | (sc->mii_clk << MACMDIOAR_CR_S) | MACMDIOAR_READ | MACMDIOAR_GB; /* Busy flag */ WRITE4(sc, ETH_MACMDIOAR, mii); for (cnt = 0; cnt < 1000; cnt++) { if (!(READ4(sc, ETH_MACMDIOAR) & MACMDIOAR_GB)) { rv = READ4(sc, ETH_MACMDIODR); break; } DELAY(10); } return rv; } int dwc4_miibus_write_reg(device_t dev, int phy, int reg, int val) { struct dwc_softc *sc; uint32_t mii; size_t cnt; sc = device_get_softc(dev); mii = ((phy << MACMDIOAR_PA_S) & MACMDIOAR_PA_M) | ((reg << MACMDIOAR_RDA_S) & MACMDIOAR_RDA_M) | (sc->mii_clk << MACMDIOAR_CR_S) | MACMDIOAR_WRITE | MACMDIOAR_GB; /* Busy flag */ WRITE4(sc, ETH_MACMDIODR, val); WRITE4(sc, ETH_MACMDIOAR, mii); for (cnt = 0; cnt < 1000; cnt++) { if (!(READ4(sc, ETH_MACMDIOAR) & MACMDIOAR_GB)) { break; } DELAY(10); } return (0); } void dwc4_miibus_statchg(device_t dev) { struct dwc_softc *sc; struct mii_data *mii; uint32_t reg; /* * Called by the MII bus driver when the PHY establishes * link to set the MAC interface registers. */ sc = device_get_softc(dev); DWC_ASSERT_LOCKED(sc); mii = sc->mii_softc; if (mii->mii_media_status & IFM_ACTIVE) sc->link_is_up = true; else sc->link_is_up = false; reg = READ4(sc, ETH_MACCR); switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_1000_T: case IFM_1000_SX: reg &= ~(MACCR_FES | MACCR_PS); break; case IFM_100_TX: reg |= (MACCR_FES | MACCR_PS); break; case IFM_10_T: reg &= ~(MACCR_FES); reg |= (MACCR_PS); break; case IFM_NONE: sc->link_is_up = false; return; default: sc->link_is_up = false; device_printf(dev, "Unsupported media %u\n", IFM_SUBTYPE(mii->mii_media_active)); return; } if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) reg |= (MACCR_DM); else reg &= ~(MACCR_DM); WRITE4(sc, ETH_MACCR, reg); IF_DWC_SET_SPEED(dev, IFM_SUBTYPE(mii->mii_media_active)); } void dwc4_core_setup(struct dwc_softc *sc) { uint32_t reg; DWC_ASSERT_LOCKED(sc); reg = READ4(sc, ETH_MACVR); device_printf(sc->dev, "MAC Version: %x\n", reg); /* Enable core */ reg = READ4(sc, ETH_MACCR); reg |= (MACCR_JD | MACCR_ACS | MACCR_BE); WRITE4(sc, ETH_MACCR, reg); } void dwc4_enable_mac(struct dwc_softc *sc, bool enable) { uint32_t reg; DWC_ASSERT_LOCKED(sc); reg = READ4(sc, ETH_MACCR); if (enable) reg |= MACCR_TE | MACCR_RE; else reg &= ~(MACCR_TE | MACCR_RE); WRITE4(sc, ETH_MACCR, reg); } void dwc4_enable_csum_offload(struct dwc_softc *sc) { uint32_t reg; DWC_ASSERT_LOCKED(sc); reg = READ4(sc, ETH_MACCR); if ((if_getcapenable(sc->ifp) & (IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6)) != 0) reg |= MACCR_IPC; else reg &= ~MACCR_IPC; WRITE4(sc, ETH_MACCR, reg); } static const uint8_t nibbletab[] = { /* 0x0 0000 -> 0000 */ 0x0, /* 0x1 0001 -> 1000 */ 0x8, /* 0x2 0010 -> 0100 */ 0x4, /* 0x3 0011 -> 1100 */ 0xc, /* 0x4 0100 -> 0010 */ 0x2, /* 0x5 0101 -> 1010 */ 0xa, /* 0x6 0110 -> 0110 */ 0x6, /* 0x7 0111 -> 1110 */ 0xe, /* 0x8 1000 -> 0001 */ 0x1, /* 0x9 1001 -> 1001 */ 0x9, /* 0xa 1010 -> 0101 */ 0x5, /* 0xb 1011 -> 1101 */ 0xd, /* 0xc 1100 -> 0011 */ 0x3, /* 0xd 1101 -> 1011 */ 0xb, /* 0xe 1110 -> 0111 */ 0x7, /* 0xf 1111 -> 1111 */ 0xf, }; static uint8_t bitreverse(uint8_t x) { return (nibbletab[x & 0xf] << 4) | nibbletab[x >> 4]; } static u_int dwc_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { struct dwc_hash_maddr_ctx *ctx = arg; uint32_t crc, hashbit, hashreg; uint8_t val; crc = ether_crc32_le(LLADDR(sdl), ETHER_ADDR_LEN); /* Take lower 8 bits and reverse it */ val = bitreverse(~crc & 0xff); /* * TODO: There is probably a HW_FEATURES bit which isn't * related to the extended descriptors that describe this */ if (!ctx->sc->dma_ext_desc) val >>= 2; /* Only need lower 6 bits */ hashreg = (val >> 5); hashbit = (val & 31); ctx->hash[hashreg] |= (1 << hashbit); return (1); } void dwc4_setup_rxfilter(struct dwc_softc *sc) { struct dwc_hash_maddr_ctx ctx; if_t ifp; uint8_t *eaddr; uint32_t ffval, hi, lo; int nhash, i; DWC_ASSERT_LOCKED(sc); ifp = sc->ifp; /* * TODO: There is probably a HW_FEATURES bit which isn't * related to the extended descriptors that describe this */ nhash = sc->dma_ext_desc == false ? 2 : 8; /* * Set the multicast (group) filter hash. */ if ((if_getflags(ifp) & IFF_ALLMULTI) != 0) { ffval = (MACPFR_PM); for (i = 0; i < nhash; i++) ctx.hash[i] = ~0; } else { ffval = (MACPFR_HMC); for (i = 0; i < nhash; i++) ctx.hash[i] = 0; ctx.sc = sc; if_foreach_llmaddr(ifp, dwc_hash_maddr, &ctx); } /* * Set the individual address filter hash. */ if ((if_getflags(ifp) & IFF_PROMISC) != 0) ffval |= MACPFR_PR; /* * Set the primary address. */ eaddr = if_getlladdr(ifp); lo = eaddr[0] | (eaddr[1] << 8) | (eaddr[2] << 16) | (eaddr[3] << 24); hi = eaddr[4] | (eaddr[5] << 8); WRITE4(sc, ETH_MACA0LR, lo); WRITE4(sc, ETH_MACA0HR, hi); WRITE4(sc, ETH_MACPFR, ffval); if (!sc->dma_ext_desc) { WRITE4(sc, ETH_MACHT0R, ctx.hash[0]); WRITE4(sc, ETH_MACHT1R, ctx.hash[1]); } else panic("implement me: ext_desc"); } void dwc4_get_hwaddr(struct dwc_softc *sc, uint8_t *hwaddr) { uint32_t hi, lo, rnd; /* * Try to recover a MAC address from the running hardware. If there's * something non-zero there, assume the bootloader did the right thing * and just use it. * * Otherwise, set the address to a convenient locally assigned address, * 'bsd' + random 24 low-order bits. 'b' is 0x62, which has the locally * assigned bit set, and the broadcast/multicast bit clear. */ lo = READ4(sc, ETH_MACA0LR); hi = READ4(sc, ETH_MACA0HR) & 0xffff; if ((lo != 0xffffffff) || (hi != 0xffff)) { hwaddr[0] = (lo >> 0) & 0xff; hwaddr[1] = (lo >> 8) & 0xff; hwaddr[2] = (lo >> 16) & 0xff; hwaddr[3] = (lo >> 24) & 0xff; hwaddr[4] = (hi >> 0) & 0xff; hwaddr[5] = (hi >> 8) & 0xff; } else { rnd = arc4random() & 0x00ffffff; hwaddr[0] = 'b'; hwaddr[1] = 's'; hwaddr[2] = 'd'; hwaddr[3] = rnd >> 16; hwaddr[4] = rnd >> 8; hwaddr[5] = rnd >> 0; } } /* * Stats */ static void dwc4_clear_stats(struct dwc_softc *sc) { uint32_t reg; reg = READ4(sc, ETH_MMC_CONTROL); reg |= MMC_CONTROL_CNTRST; WRITE4(sc, ETH_MMC_CONTROL, reg); } void dwc4_harvest_stats(struct dwc_softc *sc) { if_t ifp __unused; int val; /* We don't need to harvest too often. */ if (++sc->stats_harvest_count < STATS_HARVEST_INTERVAL) return; sc->stats_harvest_count = 0; ifp = sc->ifp; val = READ4(sc, ETH_RX_RECEIVE_ERROR); if_inc_counter(ifp, IFCOUNTER_IERRORS, val); val = READ4(sc, ETH_TX_PACKET_COUNT_GOOD_BAD); val -= READ4(sc, ETH_TX_PACKET_COUNT_GOOD); if_inc_counter(ifp, IFCOUNTER_OERRORS, val); val = READ4(sc, ETH_TX_SINGLE_COLLISION_GOOD_PACKETS); val += READ4(sc, ETH_TX_MULTIPLE_COLLISION_GOOD_PACKETS); val += READ4(sc, ETH_TX_LATE_COLLISION_PACKETS); val += READ4(sc, ETH_TX_EXCESSIVE_COLLISION_PACKETS); if_inc_counter(ifp, IFCOUNTER_COLLISIONS, val); dwc4_clear_stats(sc); } void dwc4_intr(struct dwc_softc *sc) { uint32_t reg; DWC_ASSERT_LOCKED(sc); reg = READ4(sc, ETH_MACISR); if (reg) READ4(sc, ETH_MACPHYCSR); } void dwc4_intr_disable(struct dwc_softc *sc) { WRITE4(sc, ETH_MACIER, 0); }