/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Ruslan Bukin * * This software was developed by SRI International and the University of * Cambridge Computer Laboratory (Department of Computer Science and * Technology) under DARPA contract HR0011-18-C-0016 ("ECATS"), as part of the * DARPA SSITH research programme. * * 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 __FBSDID("$FreeBSD$"); #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 #include #include "miibus_if.h" #define READ4(_sc, _reg) \ bus_read_4((_sc)->res[0], _reg) #define WRITE4(_sc, _reg, _val) \ bus_write_4((_sc)->res[0], _reg, _val) #define READ8(_sc, _reg) \ bus_read_8((_sc)->res[0], _reg) #define WRITE8(_sc, _reg, _val) \ bus_write_8((_sc)->res[0], _reg, _val) #define XAE_LOCK(sc) mtx_lock(&(sc)->mtx) #define XAE_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define XAE_ASSERT_LOCKED(sc) mtx_assert(&(sc)->mtx, MA_OWNED) #define XAE_ASSERT_UNLOCKED(sc) mtx_assert(&(sc)->mtx, MA_NOTOWNED) #define XAE_DEBUG #undef XAE_DEBUG #ifdef XAE_DEBUG #define dprintf(fmt, ...) printf(fmt, ##__VA_ARGS__) #else #define dprintf(fmt, ...) #endif #define RX_QUEUE_SIZE 64 #define TX_QUEUE_SIZE 64 #define NUM_RX_MBUF 16 #define BUFRING_SIZE 8192 #define MDIO_CLK_DIV_DEFAULT 29 #define PHY1_RD(sc, _r) \ xae_miibus_read_reg(sc->dev, 1, _r) #define PHY1_WR(sc, _r, _v) \ xae_miibus_write_reg(sc->dev, 1, _r, _v) #define PHY_RD(sc, _r) \ xae_miibus_read_reg(sc->dev, sc->phy_addr, _r) #define PHY_WR(sc, _r, _v) \ xae_miibus_write_reg(sc->dev, sc->phy_addr, _r, _v) /* Use this macro to access regs > 0x1f */ #define WRITE_TI_EREG(sc, reg, data) { \ PHY_WR(sc, MII_MMDACR, MMDACR_DADDRMASK); \ PHY_WR(sc, MII_MMDAADR, reg); \ PHY_WR(sc, MII_MMDACR, MMDACR_DADDRMASK | MMDACR_FN_DATANPI); \ PHY_WR(sc, MII_MMDAADR, data); \ } /* Not documented, Xilinx VCU118 workaround */ #define CFG4_SGMII_TMR 0x160 /* bits 8:7 MUST be '10' */ #define DP83867_SGMIICTL1 0xD3 /* not documented register */ #define SGMIICTL1_SGMII_6W (1 << 14) /* no idea what it is */ static struct resource_spec xae_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; static void xae_stop_locked(struct xae_softc *sc); static void xae_setup_rxfilter(struct xae_softc *sc); static int xae_rx_enqueue(struct xae_softc *sc, uint32_t n) { struct mbuf *m; int i; for (i = 0; i < n; i++) { m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { device_printf(sc->dev, "%s: Can't alloc rx mbuf\n", __func__); return (-1); } m->m_pkthdr.len = m->m_len = m->m_ext.ext_size; xdma_enqueue_mbuf(sc->xchan_rx, &m, 0, 4, 4, XDMA_DEV_TO_MEM); } return (0); } static int xae_get_phyaddr(phandle_t node, int *phy_addr) { phandle_t phy_node; pcell_t phy_handle, phy_reg; if (OF_getencprop(node, "phy-handle", (void *)&phy_handle, sizeof(phy_handle)) <= 0) return (ENXIO); phy_node = OF_node_from_xref(phy_handle); if (OF_getencprop(phy_node, "reg", (void *)&phy_reg, sizeof(phy_reg)) <= 0) return (ENXIO); *phy_addr = phy_reg; return (0); } static int xae_xdma_tx_intr(void *arg, xdma_transfer_status_t *status) { xdma_transfer_status_t st; struct xae_softc *sc; if_t ifp; struct mbuf *m; int err; sc = arg; XAE_LOCK(sc); ifp = sc->ifp; for (;;) { err = xdma_dequeue_mbuf(sc->xchan_tx, &m, &st); if (err != 0) { break; } if (st.error != 0) { if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); } m_freem(m); } if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE); XAE_UNLOCK(sc); return (0); } static int xae_xdma_rx_intr(void *arg, xdma_transfer_status_t *status) { xdma_transfer_status_t st; struct xae_softc *sc; if_t ifp; struct mbuf *m; int err; uint32_t cnt_processed; sc = arg; dprintf("%s\n", __func__); XAE_LOCK(sc); ifp = sc->ifp; cnt_processed = 0; for (;;) { err = xdma_dequeue_mbuf(sc->xchan_rx, &m, &st); if (err != 0) { break; } cnt_processed++; if (st.error != 0) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); m_freem(m); continue; } m->m_pkthdr.len = m->m_len = st.transferred; m->m_pkthdr.rcvif = ifp; XAE_UNLOCK(sc); if_input(ifp, m); XAE_LOCK(sc); } xae_rx_enqueue(sc, cnt_processed); XAE_UNLOCK(sc); return (0); } static void xae_qflush(if_t ifp) { } static int xae_transmit_locked(if_t ifp) { struct xae_softc *sc; struct mbuf *m; struct buf_ring *br; int error; int enq; dprintf("%s\n", __func__); sc = if_getsoftc(ifp); br = sc->br; enq = 0; while ((m = drbr_peek(ifp, br)) != NULL) { error = xdma_enqueue_mbuf(sc->xchan_tx, &m, 0, 4, 4, XDMA_MEM_TO_DEV); if (error != 0) { /* No space in request queue available yet. */ drbr_putback(ifp, br, m); break; } drbr_advance(ifp, br); enq++; /* If anyone is interested give them a copy. */ ETHER_BPF_MTAP(ifp, m); } if (enq > 0) xdma_queue_submit(sc->xchan_tx); return (0); } static int xae_transmit(if_t ifp, struct mbuf *m) { struct xae_softc *sc; int error; dprintf("%s\n", __func__); sc = if_getsoftc(ifp); XAE_LOCK(sc); error = drbr_enqueue(ifp, sc->br, m); if (error) { XAE_UNLOCK(sc); return (error); } if ((if_getdrvflags(ifp) & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING) { XAE_UNLOCK(sc); return (0); } if (!sc->link_is_up) { XAE_UNLOCK(sc); return (0); } error = xae_transmit_locked(ifp); XAE_UNLOCK(sc); return (error); } static void xae_stop_locked(struct xae_softc *sc) { if_t ifp; uint32_t reg; XAE_ASSERT_LOCKED(sc); ifp = sc->ifp; if_setdrvflagbits(ifp, 0, (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)); callout_stop(&sc->xae_callout); /* Stop the transmitter */ reg = READ4(sc, XAE_TC); reg &= ~TC_TX; WRITE4(sc, XAE_TC, reg); /* Stop the receiver. */ reg = READ4(sc, XAE_RCW1); reg &= ~RCW1_RX; WRITE4(sc, XAE_RCW1, reg); } static uint64_t xae_stat(struct xae_softc *sc, int counter_id) { uint64_t new, old; uint64_t delta; KASSERT(counter_id < XAE_MAX_COUNTERS, ("counter %d is out of range", counter_id)); new = READ8(sc, XAE_STATCNT(counter_id)); old = sc->counters[counter_id]; if (new >= old) delta = new - old; else delta = UINT64_MAX - old + new; sc->counters[counter_id] = new; return (delta); } static void xae_harvest_stats(struct xae_softc *sc) { if_t ifp; ifp = sc->ifp; if_inc_counter(ifp, IFCOUNTER_IPACKETS, xae_stat(sc, RX_GOOD_FRAMES)); if_inc_counter(ifp, IFCOUNTER_IMCASTS, xae_stat(sc, RX_GOOD_MCASTS)); if_inc_counter(ifp, IFCOUNTER_IERRORS, xae_stat(sc, RX_FRAME_CHECK_SEQ_ERROR) + xae_stat(sc, RX_LEN_OUT_OF_RANGE) + xae_stat(sc, RX_ALIGNMENT_ERRORS)); if_inc_counter(ifp, IFCOUNTER_OBYTES, xae_stat(sc, TX_BYTES)); if_inc_counter(ifp, IFCOUNTER_OPACKETS, xae_stat(sc, TX_GOOD_FRAMES)); if_inc_counter(ifp, IFCOUNTER_OMCASTS, xae_stat(sc, TX_GOOD_MCASTS)); if_inc_counter(ifp, IFCOUNTER_OERRORS, xae_stat(sc, TX_GOOD_UNDERRUN_ERRORS)); if_inc_counter(ifp, IFCOUNTER_COLLISIONS, xae_stat(sc, TX_SINGLE_COLLISION_FRAMES) + xae_stat(sc, TX_MULTI_COLLISION_FRAMES) + xae_stat(sc, TX_LATE_COLLISIONS) + xae_stat(sc, TX_EXCESS_COLLISIONS)); } static void xae_tick(void *arg) { struct xae_softc *sc; if_t ifp; int link_was_up; sc = arg; XAE_ASSERT_LOCKED(sc); ifp = sc->ifp; if (!(if_getdrvflags(ifp) & IFF_DRV_RUNNING)) return; /* Gather stats from hardware counters. */ xae_harvest_stats(sc); /* Check the media status. */ link_was_up = sc->link_is_up; mii_tick(sc->mii_softc); if (sc->link_is_up && !link_was_up) xae_transmit_locked(sc->ifp); /* Schedule another check one second from now. */ callout_reset(&sc->xae_callout, hz, xae_tick, sc); } static void xae_init_locked(struct xae_softc *sc) { if_t ifp; XAE_ASSERT_LOCKED(sc); ifp = sc->ifp; if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) return; if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0); xae_setup_rxfilter(sc); /* Enable the transmitter */ WRITE4(sc, XAE_TC, TC_TX); /* Enable the receiver. */ WRITE4(sc, XAE_RCW1, RCW1_RX); /* * Call mii_mediachg() which will call back into xae_miibus_statchg() * to set up the remaining config registers based on current media. */ mii_mediachg(sc->mii_softc); callout_reset(&sc->xae_callout, hz, xae_tick, sc); } static void xae_init(void *arg) { struct xae_softc *sc; sc = arg; XAE_LOCK(sc); xae_init_locked(sc); XAE_UNLOCK(sc); } static void xae_media_status(if_t ifp, struct ifmediareq *ifmr) { struct xae_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = sc->mii_softc; XAE_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; XAE_UNLOCK(sc); } static int xae_media_change_locked(struct xae_softc *sc) { return (mii_mediachg(sc->mii_softc)); } static int xae_media_change(if_t ifp) { struct xae_softc *sc; int error; sc = if_getsoftc(ifp); XAE_LOCK(sc); error = xae_media_change_locked(sc); XAE_UNLOCK(sc); return (error); } static u_int xae_write_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { struct xae_softc *sc = arg; uint32_t reg; uint8_t *ma; if (cnt >= XAE_MULTICAST_TABLE_SIZE) return (1); ma = LLADDR(sdl); reg = READ4(sc, XAE_FFC) & 0xffffff00; reg |= cnt; WRITE4(sc, XAE_FFC, reg); reg = (ma[0]); reg |= (ma[1] << 8); reg |= (ma[2] << 16); reg |= (ma[3] << 24); WRITE4(sc, XAE_FFV(0), reg); reg = ma[4]; reg |= ma[5] << 8; WRITE4(sc, XAE_FFV(1), reg); return (1); } static void xae_setup_rxfilter(struct xae_softc *sc) { if_t ifp; uint32_t reg; XAE_ASSERT_LOCKED(sc); ifp = sc->ifp; /* * Set the multicast (group) filter hash. */ if ((if_getflags(ifp) & (IFF_ALLMULTI | IFF_PROMISC)) != 0) { reg = READ4(sc, XAE_FFC); reg |= FFC_PM; WRITE4(sc, XAE_FFC, reg); } else { reg = READ4(sc, XAE_FFC); reg &= ~FFC_PM; WRITE4(sc, XAE_FFC, reg); if_foreach_llmaddr(ifp, xae_write_maddr, sc); } /* * Set the primary address. */ reg = sc->macaddr[0]; reg |= (sc->macaddr[1] << 8); reg |= (sc->macaddr[2] << 16); reg |= (sc->macaddr[3] << 24); WRITE4(sc, XAE_UAW0, reg); reg = sc->macaddr[4]; reg |= (sc->macaddr[5] << 8); WRITE4(sc, XAE_UAW1, reg); } static int xae_ioctl(if_t ifp, u_long cmd, caddr_t data) { struct xae_softc *sc; struct mii_data *mii; struct ifreq *ifr; int mask, error; sc = if_getsoftc(ifp); ifr = (struct ifreq *)data; error = 0; switch (cmd) { case SIOCSIFFLAGS: XAE_LOCK(sc); if (if_getflags(ifp) & IFF_UP) { if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { if ((if_getflags(ifp) ^ sc->if_flags) & (IFF_PROMISC | IFF_ALLMULTI)) xae_setup_rxfilter(sc); } else { if (!sc->is_detaching) xae_init_locked(sc); } } else { if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) xae_stop_locked(sc); } sc->if_flags = if_getflags(ifp); XAE_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { XAE_LOCK(sc); xae_setup_rxfilter(sc); XAE_UNLOCK(sc); } break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: mii = sc->mii_softc; error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); break; case SIOCSIFCAP: mask = if_getcapenable(ifp) ^ ifr->ifr_reqcap; if (mask & IFCAP_VLAN_MTU) { /* No work to do except acknowledge the change took */ if_togglecapenable(ifp, IFCAP_VLAN_MTU); } break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } static void xae_intr(void *arg) { } static int xae_get_hwaddr(struct xae_softc *sc, uint8_t *hwaddr) { phandle_t node; int len; node = ofw_bus_get_node(sc->dev); /* Check if there is property */ if ((len = OF_getproplen(node, "local-mac-address")) <= 0) return (EINVAL); if (len != ETHER_ADDR_LEN) return (EINVAL); OF_getprop(node, "local-mac-address", hwaddr, ETHER_ADDR_LEN); return (0); } static int mdio_wait(struct xae_softc *sc) { uint32_t reg; int timeout; timeout = 200; do { reg = READ4(sc, XAE_MDIO_CTRL); if (reg & MDIO_CTRL_READY) break; DELAY(1); } while (timeout--); if (timeout <= 0) { printf("Failed to get MDIO ready\n"); return (1); } return (0); } static int xae_miibus_read_reg(device_t dev, int phy, int reg) { struct xae_softc *sc; uint32_t mii; int rv; sc = device_get_softc(dev); if (mdio_wait(sc)) return (0); mii = MDIO_CTRL_TX_OP_READ | MDIO_CTRL_INITIATE; mii |= (reg << MDIO_TX_REGAD_S); mii |= (phy << MDIO_TX_PHYAD_S); WRITE4(sc, XAE_MDIO_CTRL, mii); if (mdio_wait(sc)) return (0); rv = READ4(sc, XAE_MDIO_READ); return (rv); } static int xae_miibus_write_reg(device_t dev, int phy, int reg, int val) { struct xae_softc *sc; uint32_t mii; sc = device_get_softc(dev); if (mdio_wait(sc)) return (1); mii = MDIO_CTRL_TX_OP_WRITE | MDIO_CTRL_INITIATE; mii |= (reg << MDIO_TX_REGAD_S); mii |= (phy << MDIO_TX_PHYAD_S); WRITE4(sc, XAE_MDIO_WRITE, val); WRITE4(sc, XAE_MDIO_CTRL, mii); if (mdio_wait(sc)) return (1); return (0); } static void xae_phy_fixup(struct xae_softc *sc) { uint32_t reg; do { WRITE_TI_EREG(sc, DP83867_SGMIICTL1, SGMIICTL1_SGMII_6W); PHY_WR(sc, DP83867_PHYCR, PHYCR_SGMII_EN); reg = PHY_RD(sc, DP83867_CFG2); reg &= ~CFG2_SPEED_OPT_ATTEMPT_CNT_M; reg |= (CFG2_SPEED_OPT_ATTEMPT_CNT_4); reg |= CFG2_INTERRUPT_POLARITY; reg |= CFG2_SPEED_OPT_ENHANCED_EN; reg |= CFG2_SPEED_OPT_10M_EN; PHY_WR(sc, DP83867_CFG2, reg); WRITE_TI_EREG(sc, DP83867_CFG4, CFG4_SGMII_TMR); PHY_WR(sc, MII_BMCR, BMCR_AUTOEN | BMCR_FDX | BMCR_SPEED1 | BMCR_RESET); } while (PHY1_RD(sc, MII_BMCR) == 0x0ffff); do { PHY1_WR(sc, MII_BMCR, BMCR_AUTOEN | BMCR_FDX | BMCR_SPEED1 | BMCR_STARTNEG); DELAY(40000); } while ((PHY1_RD(sc, MII_BMSR) & BMSR_ACOMP) == 0); } static int get_xdma_std(struct xae_softc *sc) { sc->xdma_tx = xdma_ofw_get(sc->dev, "tx"); if (sc->xdma_tx == NULL) return (ENXIO); sc->xdma_rx = xdma_ofw_get(sc->dev, "rx"); if (sc->xdma_rx == NULL) { xdma_put(sc->xdma_tx); return (ENXIO); } return (0); } static int get_xdma_axistream(struct xae_softc *sc) { struct axidma_fdt_data *data; device_t dma_dev; phandle_t node; pcell_t prop; size_t len; node = ofw_bus_get_node(sc->dev); len = OF_getencprop(node, "axistream-connected", &prop, sizeof(prop)); if (len != sizeof(prop)) { device_printf(sc->dev, "%s: Couldn't get axistream-connected prop.\n", __func__); return (ENXIO); } dma_dev = OF_device_from_xref(prop); if (dma_dev == NULL) { device_printf(sc->dev, "Could not get DMA device by xref.\n"); return (ENXIO); } sc->xdma_tx = xdma_get(sc->dev, dma_dev); if (sc->xdma_tx == NULL) { device_printf(sc->dev, "Could not find DMA controller.\n"); return (ENXIO); } data = malloc(sizeof(struct axidma_fdt_data), M_DEVBUF, (M_WAITOK | M_ZERO)); data->id = AXIDMA_TX_CHAN; sc->xdma_tx->data = data; sc->xdma_rx = xdma_get(sc->dev, dma_dev); if (sc->xdma_rx == NULL) { device_printf(sc->dev, "Could not find DMA controller.\n"); return (ENXIO); } data = malloc(sizeof(struct axidma_fdt_data), M_DEVBUF, (M_WAITOK | M_ZERO)); data->id = AXIDMA_RX_CHAN; sc->xdma_rx->data = data; return (0); } static int setup_xdma(struct xae_softc *sc) { device_t dev; vmem_t *vmem; int error; dev = sc->dev; /* Get xDMA controller */ error = get_xdma_std(sc); if (error) { device_printf(sc->dev, "Fallback to axistream-connected property\n"); error = get_xdma_axistream(sc); } if (error) { device_printf(dev, "Could not find xDMA controllers.\n"); return (ENXIO); } /* Alloc xDMA TX virtual channel. */ sc->xchan_tx = xdma_channel_alloc(sc->xdma_tx, 0); if (sc->xchan_tx == NULL) { device_printf(dev, "Can't alloc virtual DMA TX channel.\n"); return (ENXIO); } /* Setup interrupt handler. */ error = xdma_setup_intr(sc->xchan_tx, 0, xae_xdma_tx_intr, sc, &sc->ih_tx); if (error) { device_printf(sc->dev, "Can't setup xDMA TX interrupt handler.\n"); return (ENXIO); } /* Alloc xDMA RX virtual channel. */ sc->xchan_rx = xdma_channel_alloc(sc->xdma_rx, 0); if (sc->xchan_rx == NULL) { device_printf(dev, "Can't alloc virtual DMA RX channel.\n"); return (ENXIO); } /* Setup interrupt handler. */ error = xdma_setup_intr(sc->xchan_rx, XDMA_INTR_NET, xae_xdma_rx_intr, sc, &sc->ih_rx); if (error) { device_printf(sc->dev, "Can't setup xDMA RX interrupt handler.\n"); return (ENXIO); } /* Setup bounce buffer */ vmem = xdma_get_memory(dev); if (vmem) { xchan_set_memory(sc->xchan_tx, vmem); xchan_set_memory(sc->xchan_rx, vmem); } xdma_prep_sg(sc->xchan_tx, TX_QUEUE_SIZE, /* xchan requests queue size */ MCLBYTES, /* maxsegsize */ 8, /* maxnsegs */ 16, /* alignment */ 0, /* boundary */ BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR); xdma_prep_sg(sc->xchan_rx, RX_QUEUE_SIZE, /* xchan requests queue size */ MCLBYTES, /* maxsegsize */ 1, /* maxnsegs */ 16, /* alignment */ 0, /* boundary */ BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR); return (0); } static int xae_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "xlnx,axi-ethernet-1.00.a")) return (ENXIO); device_set_desc(dev, "Xilinx AXI Ethernet"); return (BUS_PROBE_DEFAULT); } static int xae_attach(device_t dev) { struct xae_softc *sc; if_t ifp; phandle_t node; uint32_t reg; int error; sc = device_get_softc(dev); sc->dev = dev; node = ofw_bus_get_node(dev); if (setup_xdma(sc) != 0) { device_printf(dev, "Could not setup xDMA.\n"); return (ENXIO); } mtx_init(&sc->mtx, device_get_nameunit(sc->dev), MTX_NETWORK_LOCK, MTX_DEF); sc->br = buf_ring_alloc(BUFRING_SIZE, M_DEVBUF, M_NOWAIT, &sc->mtx); if (sc->br == NULL) return (ENOMEM); if (bus_alloc_resources(dev, xae_spec, sc->res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } /* Memory interface */ sc->bst = rman_get_bustag(sc->res[0]); sc->bsh = rman_get_bushandle(sc->res[0]); device_printf(sc->dev, "Identification: %x\n", READ4(sc, XAE_IDENT)); /* Get MAC addr */ if (xae_get_hwaddr(sc, sc->macaddr)) { device_printf(sc->dev, "can't get mac\n"); return (ENXIO); } /* Enable MII clock */ reg = (MDIO_CLK_DIV_DEFAULT << MDIO_SETUP_CLK_DIV_S); reg |= MDIO_SETUP_ENABLE; WRITE4(sc, XAE_MDIO_SETUP, reg); if (mdio_wait(sc)) return (ENXIO); callout_init_mtx(&sc->xae_callout, &sc->mtx, 0); /* Setup interrupt handler. */ error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_NET | INTR_MPSAFE, NULL, xae_intr, sc, &sc->intr_cookie); if (error != 0) { device_printf(dev, "could not setup interrupt handler.\n"); return (ENXIO); } /* Set up the ethernet interface. */ sc->ifp = ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(dev, "could not allocate ifp.\n"); return (ENXIO); } if_setsoftc(ifp, sc); if_initname(ifp, device_get_name(dev), device_get_unit(dev)); if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST); if_setcapabilities(ifp, IFCAP_VLAN_MTU); if_setcapenable(ifp, if_getcapabilities(ifp)); if_settransmitfn(ifp, xae_transmit); if_setqflushfn(ifp, xae_qflush); if_setioctlfn(ifp, xae_ioctl); if_setinitfn(ifp, xae_init); if_setsendqlen(ifp, TX_DESC_COUNT - 1); if_setsendqready(ifp); if (xae_get_phyaddr(node, &sc->phy_addr) != 0) return (ENXIO); /* Attach the mii driver. */ error = mii_attach(dev, &sc->miibus, ifp, xae_media_change, xae_media_status, BMSR_DEFCAPMASK, sc->phy_addr, MII_OFFSET_ANY, 0); if (error != 0) { device_printf(dev, "PHY attach failed\n"); return (ENXIO); } sc->mii_softc = device_get_softc(sc->miibus); /* Apply vcu118 workaround. */ if (OF_getproplen(node, "xlnx,vcu118") >= 0) xae_phy_fixup(sc); /* All ready to run, attach the ethernet interface. */ ether_ifattach(ifp, sc->macaddr); sc->is_attached = true; xae_rx_enqueue(sc, NUM_RX_MBUF); xdma_queue_submit(sc->xchan_rx); return (0); } static int xae_detach(device_t dev) { struct xae_softc *sc; if_t ifp; sc = device_get_softc(dev); KASSERT(mtx_initialized(&sc->mtx), ("%s: mutex not initialized", device_get_nameunit(dev))); ifp = sc->ifp; /* Only cleanup if attach succeeded. */ if (device_is_attached(dev)) { XAE_LOCK(sc); xae_stop_locked(sc); XAE_UNLOCK(sc); callout_drain(&sc->xae_callout); ether_ifdetach(ifp); } if (sc->miibus != NULL) device_delete_child(dev, sc->miibus); if (ifp != NULL) if_free(ifp); mtx_destroy(&sc->mtx); bus_teardown_intr(dev, sc->res[1], sc->intr_cookie); bus_release_resources(dev, xae_spec, sc->res); xdma_channel_free(sc->xchan_tx); xdma_channel_free(sc->xchan_rx); xdma_put(sc->xdma_tx); xdma_put(sc->xdma_rx); return (0); } static void xae_miibus_statchg(device_t dev) { struct xae_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); XAE_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; switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_1000_T: case IFM_1000_SX: reg = SPEED_1000; break; case IFM_100_TX: reg = SPEED_100; break; case IFM_10_T: reg = SPEED_10; 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; } WRITE4(sc, XAE_SPEED, reg); } static device_method_t xae_methods[] = { DEVMETHOD(device_probe, xae_probe), DEVMETHOD(device_attach, xae_attach), DEVMETHOD(device_detach, xae_detach), /* MII Interface */ DEVMETHOD(miibus_readreg, xae_miibus_read_reg), DEVMETHOD(miibus_writereg, xae_miibus_write_reg), DEVMETHOD(miibus_statchg, xae_miibus_statchg), { 0, 0 } }; driver_t xae_driver = { "xae", xae_methods, sizeof(struct xae_softc), }; DRIVER_MODULE(xae, simplebus, xae_driver, 0, 0); DRIVER_MODULE(miibus, xae, miibus_driver, 0, 0); MODULE_DEPEND(xae, ether, 1, 1, 1); MODULE_DEPEND(xae, miibus, 1, 1, 1);