/* * Copyright (c) 2026 Justin Hibbits * * SPDX-License-Identifier: BSD-2-Clause */ #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 "miibus_if.h" #include "dpaa_eth.h" #include "fman.h" #include "fman_port.h" #include "if_memac.h" #include "fman_if.h" #include "fman_port_if.h" #define MEMAC_MIN_FRAME_SIZE 64 #define MEMAC_MAX_FRAME_SIZE 32736 #define MEMAC_COMMAND_CONFIG 0x008 #define COMMAND_CONFIG_RXSTP 0x20000000 #define COMMAND_CONFIG_NO_LEN_CHK 0x00020000 #define COMMAND_CONFIG_SWR 0x00001000 #define COMMAND_CONFIG_TXP 0x00000800 #define COMMAND_CONFIG_CRC 0x00000040 #define COMMAND_CONFIG_PROMISC 0x00000010 #define COMMAND_CONFIG_RX_EN 0x00000002 #define COMMAND_CONFIG_TX_EN 0x00000001 #define MEMAC_MAC_ADDR_0 0x00c #define MEMAC_MAC_ADDR_1 0x010 #define MEMAC_REG_MAXFRM 0x14 #define MEMAC_REG_TX_FIFO_SECTIONS 0x020 #define TX_FIFO_SECTIONS_TX_EMPTY_M 0xffff0000 #define TX_FIFO_SECTIONS_TX_EMPTY_S 16 #define TX_FIFO_SECTIONS_TX_AVAIL_M 0x0000ffff #define HASHTABLE_CTRL 0x02c #define CTRL_MCAST 0x00000100 #define CTRL_HASH_ADDR_M 0x0000003f #define HASHTABLE_SIZE 64 #define MEMAC_IEVENT 0x040 #define IEVENT_RX_EMPTY 0x00000040 #define IEVENT_TX_EMPTY 0x00000020 #define MEMAC_CL01_PAUSE_QUANTA 0x054 #define MEMAC_IF_MODE 0x300 #define IF_MODE_ENA 0x00008000 #define IF_MODE_SSP_M 0x00006000 #define IF_MODE_SSP_100MB 0x00000000 #define IF_MODE_SSP_10MB 0x00002000 #define IF_MODE_SSP_1GB 0x00004000 #define IF_MODE_SFD 0x00001000 #define IF_MODE_MSG 0x00000200 #define IF_MODE_HG 0x00000100 #define IF_MODE_HD 0x00000040 #define IF_MODE_RLP 0x00000020 #define IF_MODE_RG 0x00000004 #define IF_MODE_IFMODE_M 0x00000003 #define IF_MODE_IFMODE_XGMII 0x00000000 #define IF_MODE_IFMODE_MII 0x00000001 #define IF_MODE_IFMODE_GMII 0x00000002 #define DEFAULT_PAUSE_QUANTA 0xf000 #define DPAA_CSUM_TX_OFFLOAD (CSUM_IP | CSUM_DELAY_DATA | CSUM_DELAY_DATA_IPV6) /** * @group FMan MAC routines. * @{ */ #define MEMAC_MAC_EXCEPTIONS_END (-1) static void memac_if_init_locked(struct memac_softc *sc); static int memac_fm_mac_init(struct memac_softc *sc, uint8_t *mac) { uint32_t reg; FMAN_GET_REVISION(device_get_parent(sc->sc_base.sc_dev), &sc->sc_base.sc_rev_major, &sc->sc_base.sc_rev_minor); if (FMAN_RESET_MAC(device_get_parent(sc->sc_base.sc_dev), sc->sc_base.sc_eth_id) != 0) return (ENXIO); reg = bus_read_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG); reg |= COMMAND_CONFIG_SWR; bus_write_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG, reg); while (bus_read_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG) & COMMAND_CONFIG_SWR) ; /* TODO: TX_FIFO_SECTIONS */ /* TODO: CL01 pause quantum */ bus_write_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG, COMMAND_CONFIG_NO_LEN_CHK | COMMAND_CONFIG_TXP | COMMAND_CONFIG_CRC); reg = bus_read_4(sc->sc_base.sc_mem, MEMAC_IF_MODE); reg &= ~(IF_MODE_IFMODE_M | IF_MODE_RG); switch (sc->sc_base.sc_mac_enet_mode) { case MII_CONTYPE_RGMII: reg |= IF_MODE_RG; /* FALLTHROUGH */ case MII_CONTYPE_GMII: case MII_CONTYPE_SGMII: case MII_CONTYPE_QSGMII: reg |= IF_MODE_IFMODE_GMII; break; case MII_CONTYPE_RMII: reg |= IF_MODE_RG; /* FALLTHROUGH */ case MII_CONTYPE_MII: reg |= IF_MODE_IFMODE_MII; break; } bus_write_4(sc->sc_base.sc_mem, MEMAC_IF_MODE, reg); return (0); } /** @} */ /** * @group IFnet routines. * @{ */ static int memac_set_mtu(struct memac_softc *sc, unsigned int mtu) { mtu += ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN + ETHER_CRC_LEN; MEMAC_LOCK_ASSERT(sc); if (mtu >= MEMAC_MIN_FRAME_SIZE && mtu <= MEMAC_MAX_FRAME_SIZE) { bus_write_4(sc->sc_base.sc_mem, MEMAC_REG_MAXFRM, mtu); return (mtu); } return (0); } static u_int memac_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { struct memac_softc *sc = arg; uint8_t *addr = LLADDR(sdl); uint32_t hash = 0; uint8_t a, h; /* Hash is 6 bits, composed if [XOR{47:40},XOR{39:32},....] */ for (int i = 0; i < 6; i++) { a = addr[i]; h = 0; for (int j = 0; j < 8; j++, a >>= 1) { h ^= (a & 0x1); } hash |= (h << i); } bus_write_4(sc->sc_base.sc_mem, HASHTABLE_CTRL, hash | CTRL_MCAST); return (1); } static void memac_setup_multicast(struct memac_softc *sc) { if (if_getflags(sc->sc_base.sc_ifnet) & IFF_ALLMULTI) { for (int i = 0; i < HASHTABLE_SIZE; i++) bus_write_4(sc->sc_base.sc_mem, HASHTABLE_CTRL, CTRL_MCAST | i); } else { /* Clear the hash table */ for (int i = 0; i < HASHTABLE_SIZE; i++) bus_write_4(sc->sc_base.sc_mem, HASHTABLE_CTRL, i); } if_foreach_llmaddr(sc->sc_base.sc_ifnet, memac_hash_maddr, sc); } static void memac_setup_promisc(struct memac_softc *sc) { uint32_t reg; reg = bus_read_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG); reg &= ~COMMAND_CONFIG_PROMISC; if ((if_getflags(sc->sc_base.sc_ifnet) & IFF_PROMISC) != 0) bus_write_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG, reg | COMMAND_CONFIG_PROMISC); } static void memac_if_graceful_stop(struct memac_softc *sc) { struct resource *regs = sc->sc_base.sc_mem; uint32_t reg; reg = bus_read_4(regs, MEMAC_COMMAND_CONFIG); reg |= COMMAND_CONFIG_RXSTP; bus_write_4(regs, MEMAC_COMMAND_CONFIG, reg); while ((bus_read_4(regs, MEMAC_IEVENT) & IEVENT_RX_EMPTY) == 0) ; reg &= COMMAND_CONFIG_RX_EN; bus_write_4(regs, MEMAC_COMMAND_CONFIG, reg); while ((bus_read_4(regs, MEMAC_IEVENT) & IEVENT_TX_EMPTY) == 0) ; bus_write_4(regs, MEMAC_COMMAND_CONFIG, reg & ~COMMAND_CONFIG_TX_EN); } static void memac_mac_enable(struct memac_softc *sc) { uint32_t reg = bus_read_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG); reg |= (COMMAND_CONFIG_RX_EN | COMMAND_CONFIG_TX_EN); bus_write_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG, reg); } static int memac_if_enable_locked(struct memac_softc *sc) { int error; MEMAC_LOCK_ASSERT(sc); memac_set_mtu(sc, if_getmtu(sc->sc_base.sc_ifnet)); memac_mac_enable(sc); error = FMAN_PORT_ENABLE(sc->sc_base.sc_rx_port); if (error != 0) return (EIO); error = FMAN_PORT_ENABLE(sc->sc_base.sc_tx_port); if (error != 0) return (EIO); bus_write_4(sc->sc_base.sc_mem, MEMAC_IEVENT, 0); memac_setup_multicast(sc); memac_setup_promisc(sc); if_setdrvflagbits(sc->sc_base.sc_ifnet, IFF_DRV_RUNNING, 0); /* Refresh link state */ memac_miibus_statchg(sc->sc_base.sc_dev); return (0); } static int memac_if_disable_locked(struct memac_softc *sc) { int error; MEMAC_LOCK_ASSERT(sc); error = FMAN_PORT_DISABLE(sc->sc_base.sc_tx_port); if (error != 0) return (EIO); memac_if_graceful_stop(sc); error = FMAN_PORT_DISABLE(sc->sc_base.sc_rx_port); if (error != 0) return (EIO); if_setdrvflagbits(sc->sc_base.sc_ifnet, 0, IFF_DRV_RUNNING); return (0); } static int memac_if_ioctl(if_t ifp, u_long command, caddr_t data) { struct memac_softc *sc; struct ifreq *ifr; uint32_t changed; int error; sc = if_getsoftc(ifp); ifr = (struct ifreq *)data; error = 0; /* Basic functionality to achieve media status reports */ switch (command) { case SIOCSIFMTU: MEMAC_LOCK(sc); if (memac_set_mtu(sc, ifr->ifr_mtu)) if_setmtu(ifp, ifr->ifr_mtu); else error = EINVAL; MEMAC_UNLOCK(sc); break; case SIOCSIFFLAGS: MEMAC_LOCK(sc); if (if_getflags(ifp) & IFF_UP) { if (!(if_getdrvflags(ifp) & IFF_DRV_RUNNING)) memac_if_init_locked(sc); } else if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) error = memac_if_disable_locked(sc); MEMAC_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: if (if_getflags(sc->sc_base.sc_ifnet) & IFF_UP) { MEMAC_LOCK(sc); memac_setup_multicast(sc); MEMAC_UNLOCK(sc); } break; case SIOCSIFCAP: changed = if_getcapenable(ifp) ^ ifr->ifr_reqcap; if ((changed & (IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6)) != 0) if_togglecapenable(ifp, IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6); if ((changed & (IFCAP_TXCSUM | IFCAP_TXCSUM_IPV6)) != 0) { if_togglecapenable(ifp, IFCAP_TXCSUM | IFCAP_TXCSUM_IPV6); if_togglehwassist(ifp, DPAA_CSUM_TX_OFFLOAD); } break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &sc->sc_base.sc_mii->mii_media, command); break; default: error = ether_ioctl(ifp, command, data); } return (error); } static void memac_if_tick(void *arg) { struct memac_softc *sc; sc = arg; /* TODO */ MEMAC_LOCK(sc); mii_tick(sc->sc_base.sc_mii); callout_reset(&sc->sc_base.sc_tick_callout, hz, memac_if_tick, sc); MEMAC_UNLOCK(sc); } static void memac_if_deinit_locked(struct memac_softc *sc) { MEMAC_LOCK_ASSERT(sc); MEMAC_UNLOCK(sc); callout_drain(&sc->sc_base.sc_tick_callout); MEMAC_LOCK(sc); } static void memac_if_set_macaddr(struct memac_softc *sc, const char *addr) { uint32_t reg; reg = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0]; bus_write_4(sc->sc_base.sc_mem, MEMAC_MAC_ADDR_0, reg); reg = (addr[5] << 8) | (addr[4]); bus_write_4(sc->sc_base.sc_mem, MEMAC_MAC_ADDR_1, reg); } static void memac_if_init_locked(struct memac_softc *sc) { int error; const char *macaddr; MEMAC_LOCK_ASSERT(sc); macaddr = if_getlladdr(sc->sc_base.sc_ifnet); memac_if_set_macaddr(sc, macaddr); /* Start MII polling */ if (sc->sc_base.sc_mii) callout_reset(&sc->sc_base.sc_tick_callout, hz, memac_if_tick, sc); if (if_getflags(sc->sc_base.sc_ifnet) & IFF_UP) { error = memac_if_enable_locked(sc); if (error != 0) goto err; } else { error = memac_if_disable_locked(sc); if (error != 0) goto err; } if_link_state_change(sc->sc_base.sc_ifnet, LINK_STATE_UP); bus_write_4(sc->sc_base.sc_mem, MEMAC_CL01_PAUSE_QUANTA, DEFAULT_PAUSE_QUANTA); return; err: memac_if_deinit_locked(sc); device_printf(sc->sc_base.sc_dev, "initialization error.\n"); return; } static void memac_if_init(void *data) { struct memac_softc *sc; sc = data; MEMAC_LOCK(sc); memac_if_init_locked(sc); MEMAC_UNLOCK(sc); } static void memac_if_start(if_t ifp) { struct memac_softc *sc; sc = if_getsoftc(ifp); MEMAC_LOCK(sc); dpaa_eth_if_start_locked(&sc->sc_base); MEMAC_UNLOCK(sc); } static void memac_if_watchdog(if_t ifp) { /* TODO */ } /** @} */ /** * @group IFmedia routines. * @{ */ static int memac_ifmedia_upd(if_t ifp) { struct memac_softc *sc = if_getsoftc(ifp); return (0); MEMAC_LOCK(sc); mii_mediachg(sc->sc_base.sc_mii); MEMAC_UNLOCK(sc); return (0); } static void memac_ifmedia_fixed_sts(if_t ifp, struct ifmediareq *ifmr) { struct memac_softc *sc = if_getsoftc(ifp); MEMAC_LOCK(sc); ifmr->ifm_count = 1; ifmr->ifm_mask = 0; ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE; ifmr->ifm_current = ifmr->ifm_active = sc->sc_base.sc_mii->mii_media.ifm_cur->ifm_media; ifmr->ifm_active = ifmr->ifm_current; /* * In non-PHY usecases, we need to signal link state up, otherwise * certain things requiring a link event (e.g async DHCP client) from * devd do not happen. */ if (if_getlinkstate(ifp) == LINK_STATE_UNKNOWN) { if_link_state_change(ifp, LINK_STATE_UP); } /* We assume the link is static, as in a peer switch. */ MEMAC_UNLOCK(sc); return; } static void memac_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { struct memac_softc *sc = if_getsoftc(ifp); MEMAC_LOCK(sc); mii_pollstat(sc->sc_base.sc_mii); ifmr->ifm_active = sc->sc_base.sc_mii->mii_media_active; ifmr->ifm_status = sc->sc_base.sc_mii->mii_media_status; MEMAC_UNLOCK(sc); } /** @} */ /** * @group dTSEC bus interface. * @{ */ int memac_attach(device_t dev) { struct memac_softc *sc; int error; if_t ifp; sc = device_get_softc(dev); sc->sc_base.sc_dev = dev; /* Init locks */ mtx_init(&sc->sc_base.sc_lock, device_get_nameunit(dev), "mEMAC Global Lock", MTX_DEF); mtx_init(&sc->sc_base.sc_mii_lock, device_get_nameunit(dev), "mEMAC MII Lock", MTX_DEF); /* Init callouts */ callout_init(&sc->sc_base.sc_tick_callout, CALLOUT_MPSAFE); /* Create RX buffer pool */ error = dpaa_eth_pool_rx_init(&sc->sc_base); if (error != 0) return (EIO); /* Create RX frame queue range */ error = dpaa_eth_fq_rx_init(&sc->sc_base); if (error != 0) return (EIO); /* Create frame info pool */ error = dpaa_eth_fi_pool_init(&sc->sc_base); if (error != 0) return (EIO); /* Create TX frame queue range */ error = dpaa_eth_fq_tx_init(&sc->sc_base); if (error != 0) return (EIO); /* Init FMan MAC module. */ error = memac_fm_mac_init(sc, sc->sc_base.sc_mac_addr); if (error != 0) { memac_detach(dev); return (ENXIO); } dpaa_eth_fm_port_rx_init(&sc->sc_base); dpaa_eth_fm_port_tx_init(&sc->sc_base); /* Create network interface for upper layers */ ifp = sc->sc_base.sc_ifnet = if_alloc(IFT_ETHER); if_setsoftc(ifp, sc); if_setflags(ifp, IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST); if_setinitfn(ifp, memac_if_init); if_setstartfn(ifp, memac_if_start); if_setioctlfn(ifp, memac_if_ioctl); if_setsendqlen(ifp, IFQ_MAXLEN); if_setsendqready(ifp); if (sc->sc_base.sc_phy_addr >= 0) if_initname(ifp, device_get_name(sc->sc_base.sc_dev), device_get_unit(sc->sc_base.sc_dev)); else if_initname(ifp, "memac_phy", device_get_unit(sc->sc_base.sc_dev)); if_setcapabilities(ifp, IFCAP_JUMBO_MTU | IFCAP_VLAN_MTU | IFCAP_VLAN_HWCSUM | IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6 | IFCAP_TXCSUM | IFCAP_TXCSUM_IPV6); if_setcapenable(ifp, if_getcapabilities(ifp)); if_sethwassist(ifp, DPAA_CSUM_TX_OFFLOAD); /* Attach PHY(s) */ if (!sc->sc_fixed_link) { error = mii_attach(sc->sc_base.sc_dev, &sc->sc_base.sc_mii_dev, ifp, memac_ifmedia_upd, memac_ifmedia_sts, BMSR_DEFCAPMASK, sc->sc_base.sc_phy_addr, MII_OFFSET_ANY, 0); if (error) { device_printf(sc->sc_base.sc_dev, "attaching PHYs failed: %d\n", error); memac_detach(sc->sc_base.sc_dev); return (error); } sc->sc_base.sc_mii = device_get_softc(sc->sc_base.sc_mii_dev); } else { phandle_t node; uint32_t type = IFM_ETHER; uint32_t speed; node = ofw_bus_find_child(ofw_bus_get_node(dev), "fixed-link"); if (OF_getencprop(node, "speed", &speed, sizeof(speed)) <= 0) { device_printf(dev, "fixed link has no speed property\n"); memac_detach(sc->sc_base.sc_dev); return (ENXIO); } switch (speed) { case 10: type |= IFM_10_T; break; case 100: type |= IFM_100_TX; break; case 1000: type |= IFM_1000_T; break; case 2500: type |= IFM_2500_T; break; case 5000: type |= IFM_5000_T; break; case 10000: type |= IFM_10G_T; break; } if (OF_hasprop(node, "full-duplex")) type |= IFM_FDX; sc->sc_base.sc_mii = malloc(sizeof(*sc->sc_base.sc_mii), M_DEVBUF, M_WAITOK | M_ZERO); ifmedia_init(&sc->sc_base.sc_mii->mii_media, 0, memac_ifmedia_upd, memac_ifmedia_fixed_sts); ifmedia_add(&sc->sc_base.sc_mii->mii_media, type, 0, NULL); ifmedia_set(&sc->sc_base.sc_mii->mii_media, type); } /* Attach to stack */ ether_ifattach(ifp, sc->sc_base.sc_mac_addr); return (0); } int memac_detach(device_t dev) { struct memac_softc *sc; if_t ifp; sc = device_get_softc(dev); ifp = sc->sc_base.sc_ifnet; if (device_is_attached(dev)) { ether_ifdetach(ifp); /* Shutdown interface */ MEMAC_LOCK(sc); memac_if_deinit_locked(sc); MEMAC_UNLOCK(sc); } if (sc->sc_base.sc_ifnet) { if_free(sc->sc_base.sc_ifnet); sc->sc_base.sc_ifnet = NULL; } /* Free RX/TX FQRs */ dpaa_eth_fq_rx_free(&sc->sc_base); dpaa_eth_fq_tx_free(&sc->sc_base); /* Free frame info pool */ dpaa_eth_fi_pool_free(&sc->sc_base); /* Free RX buffer pool */ dpaa_eth_pool_rx_free(&sc->sc_base); /* Destroy lock */ mtx_destroy(&sc->sc_base.sc_lock); return (0); } int memac_suspend(device_t dev) { return (0); } int memac_resume(device_t dev) { return (0); } int memac_shutdown(device_t dev) { return (0); } /** @} */ /** * @group MII bus interface. * @{ */ int memac_miibus_readreg(device_t dev, int phy, int reg) { struct memac_softc *sc; sc = device_get_softc(dev); return (MIIBUS_READREG(sc->sc_base.sc_mdio, phy, reg)); } int memac_miibus_writereg(device_t dev, int phy, int reg, int value) { struct memac_softc *sc; sc = device_get_softc(dev); return (MIIBUS_WRITEREG(sc->sc_base.sc_mdio, phy, reg, value)); } void memac_miibus_statchg(device_t dev) { struct memac_softc *sc; uint32_t reg; bool duplex; int speed; sc = device_get_softc(dev); MEMAC_LOCK_ASSERT(sc); duplex = ((sc->sc_base.sc_mii->mii_media_active & IFM_GMASK) == IFM_FDX); switch (IFM_SUBTYPE(sc->sc_base.sc_mii->mii_media_active)) { case IFM_AUTO: speed = IF_MODE_ENA; break; case IFM_1000_T: case IFM_1000_SX: if (!duplex) { device_printf(sc->sc_base.sc_dev, "Only full-duplex supported for 1Gbps speeds"); return; } speed = IF_MODE_SSP_1GB; break; case IFM_100_TX: speed = IF_MODE_SSP_100MB; break; default: speed = IF_MODE_SSP_10MB; break; } reg = bus_read_4(sc->sc_base.sc_mem, MEMAC_IF_MODE); reg &= ~(IF_MODE_ENA | IF_MODE_SSP_M | IF_MODE_SFD); reg |= 0x2; if (duplex) reg |= IF_MODE_SFD; else reg |= IF_MODE_HD; reg |= speed; bus_write_4(sc->sc_base.sc_mem, MEMAC_IF_MODE, reg); } /** @} */