/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2013 Nathan Whitehorn * All rights reserved. * * 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 #define LLAN_MAX_RX_PACKETS 100 #define LLAN_MAX_TX_PACKETS 100 #define LLAN_RX_BUF_LEN 8*PAGE_SIZE #define LLAN_BUFDESC_VALID (1ULL << 63) #define LLAN_ADD_MULTICAST 0x1 #define LLAN_DEL_MULTICAST 0x2 #define LLAN_CLEAR_MULTICAST 0x3 struct llan_xfer { struct mbuf *rx_mbuf; bus_dmamap_t rx_dmamap; uint64_t rx_bufdesc; }; struct llan_receive_queue_entry { /* PAPR page 539 */ uint8_t control; uint8_t reserved; uint16_t offset; uint32_t length; uint64_t handle; } __packed; struct llan_softc { device_t dev; struct mtx io_lock; cell_t unit; uint8_t mac_address[8]; struct ifmedia media; int irqid; struct resource *irq; void *irq_cookie; bus_dma_tag_t rx_dma_tag; bus_dma_tag_t rxbuf_dma_tag; bus_dma_tag_t tx_dma_tag; bus_dmamap_t tx_dma_map; struct llan_receive_queue_entry *rx_buf; int rx_dma_slot; int rx_valid_val; bus_dmamap_t rx_buf_map; bus_addr_t rx_buf_phys; bus_size_t rx_buf_len; bus_addr_t input_buf_phys; bus_addr_t filter_buf_phys; struct llan_xfer rx_xfer[LLAN_MAX_RX_PACKETS]; struct ifnet *ifp; }; static int llan_probe(device_t); static int llan_attach(device_t); static void llan_intr(void *xsc); static void llan_init(void *xsc); static void llan_start(struct ifnet *ifp); static int llan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data); static void llan_media_status(struct ifnet *ifp, struct ifmediareq *ifmr); static int llan_media_change(struct ifnet *ifp); static void llan_rx_load_cb(void *xsc, bus_dma_segment_t *segs, int nsegs, int err); static int llan_add_rxbuf(struct llan_softc *sc, struct llan_xfer *rx); static int llan_set_multicast(struct llan_softc *sc); static device_method_t llan_methods[] = { DEVMETHOD(device_probe, llan_probe), DEVMETHOD(device_attach, llan_attach), DEVMETHOD_END }; static driver_t llan_driver = { "llan", llan_methods, sizeof(struct llan_softc) }; DRIVER_MODULE(llan, vdevice, llan_driver, 0, 0); static int llan_probe(device_t dev) { if (!ofw_bus_is_compatible(dev,"IBM,l-lan")) return (ENXIO); device_set_desc(dev, "POWER Hypervisor Virtual Ethernet"); return (0); } static int llan_attach(device_t dev) { struct llan_softc *sc; phandle_t node; int i; ssize_t len; sc = device_get_softc(dev); sc->dev = dev; /* Get firmware properties */ node = ofw_bus_get_node(dev); len = OF_getprop(node, "local-mac-address", sc->mac_address, sizeof(sc->mac_address)); /* If local-mac-address property has only 6 bytes (ETHER_ADDR_LEN) * instead of 8 (sizeof(sc->mac_address)), then its value must be * shifted 2 bytes to the right. */ if (len == ETHER_ADDR_LEN) { bcopy(sc->mac_address, &sc->mac_address[2], len); /* Zero out the first 2 bytes. */ bzero(sc->mac_address, 2); } OF_getencprop(node, "reg", &sc->unit, sizeof(sc->unit)); mtx_init(&sc->io_lock, "llan", NULL, MTX_DEF); /* Setup interrupt */ sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE); if (!sc->irq) { device_printf(dev, "Could not allocate IRQ\n"); mtx_destroy(&sc->io_lock); return (ENXIO); } bus_setup_intr(dev, sc->irq, INTR_TYPE_NET | INTR_MPSAFE | INTR_ENTROPY, NULL, llan_intr, sc, &sc->irq_cookie); /* Setup DMA */ bus_dma_tag_create(bus_get_dma_tag(dev), 16, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, LLAN_RX_BUF_LEN, 1, BUS_SPACE_MAXSIZE_32BIT, 0, NULL, NULL, &sc->rx_dma_tag); bus_dma_tag_create(bus_get_dma_tag(dev), 4, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BUS_SPACE_MAXSIZE, 1, BUS_SPACE_MAXSIZE_32BIT, 0, NULL, NULL, &sc->rxbuf_dma_tag); bus_dma_tag_create(bus_get_dma_tag(dev), 1, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, BUS_SPACE_MAXSIZE, 6, BUS_SPACE_MAXSIZE_32BIT, 0, busdma_lock_mutex, &sc->io_lock, &sc->tx_dma_tag); bus_dmamem_alloc(sc->rx_dma_tag, (void **)&sc->rx_buf, BUS_DMA_WAITOK | BUS_DMA_ZERO, &sc->rx_buf_map); bus_dmamap_load(sc->rx_dma_tag, sc->rx_buf_map, sc->rx_buf, LLAN_RX_BUF_LEN, llan_rx_load_cb, sc, 0); /* TX DMA maps */ bus_dmamap_create(sc->tx_dma_tag, 0, &sc->tx_dma_map); /* RX DMA */ for (i = 0; i < LLAN_MAX_RX_PACKETS; i++) { bus_dmamap_create(sc->rxbuf_dma_tag, 0, &sc->rx_xfer[i].rx_dmamap); sc->rx_xfer[i].rx_mbuf = NULL; } /* Attach to network stack */ sc->ifp = if_alloc(IFT_ETHER); if_setsoftc(sc->ifp, sc); if_initname(sc->ifp, device_get_name(dev), device_get_unit(dev)); if_setmtu(sc->ifp, ETHERMTU); /* XXX max-frame-size from OF? */ if_setflags(sc->ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST); if_sethwassist(sc->ifp, 0); /* XXX: ibm,illan-options */ if_setcapabilities(sc->ifp, 0); if_setcapenable(sc->ifp, 0); if_setstartfn(sc->ifp, llan_start); if_setioctlfn(sc->ifp, llan_ioctl); if_setinitfn(sc->ifp, llan_init); ifmedia_init(&sc->media, IFM_IMASK, llan_media_change, llan_media_status); ifmedia_add(&sc->media, IFM_ETHER | IFM_AUTO, 0, NULL); ifmedia_set(&sc->media, IFM_ETHER | IFM_AUTO); if_setsendqlen(sc->ifp, LLAN_MAX_RX_PACKETS); if_setsendqready(sc->ifp); ether_ifattach(sc->ifp, &sc->mac_address[2]); /* We don't have link state reporting, so make it always up */ if_link_state_change(sc->ifp, LINK_STATE_UP); return (0); } static int llan_media_change(struct ifnet *ifp) { struct llan_softc *sc = if_getsoftc(ifp); if (IFM_TYPE(sc->media.ifm_media) != IFM_ETHER) return (EINVAL); if (IFM_SUBTYPE(sc->media.ifm_media) != IFM_AUTO) return (EINVAL); return (0); } static void llan_media_status(struct ifnet *ifp, struct ifmediareq *ifmr) { ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE | IFM_UNKNOWN | IFM_FDX; ifmr->ifm_active = IFM_ETHER; } static void llan_rx_load_cb(void *xsc, bus_dma_segment_t *segs, int nsegs, int err) { struct llan_softc *sc = xsc; sc->rx_buf_phys = segs[0].ds_addr; sc->rx_buf_len = segs[0].ds_len - 2*PAGE_SIZE; sc->input_buf_phys = segs[0].ds_addr + segs[0].ds_len - PAGE_SIZE; sc->filter_buf_phys = segs[0].ds_addr + segs[0].ds_len - 2*PAGE_SIZE; } static void llan_init(void *xsc) { struct llan_softc *sc = xsc; uint64_t rx_buf_desc; uint64_t macaddr; int i; mtx_lock(&sc->io_lock); phyp_hcall(H_FREE_LOGICAL_LAN, sc->unit); /* Create buffers (page 539) */ sc->rx_dma_slot = 0; sc->rx_valid_val = 1; rx_buf_desc = LLAN_BUFDESC_VALID; rx_buf_desc |= (sc->rx_buf_len << 32); rx_buf_desc |= sc->rx_buf_phys; memcpy(&macaddr, sc->mac_address, 8); phyp_hcall(H_REGISTER_LOGICAL_LAN, sc->unit, sc->input_buf_phys, rx_buf_desc, sc->filter_buf_phys, macaddr); for (i = 0; i < LLAN_MAX_RX_PACKETS; i++) llan_add_rxbuf(sc, &sc->rx_xfer[i]); phyp_hcall(H_VIO_SIGNAL, sc->unit, 1); /* Enable interrupts */ /* Tell stack we're up */ if_setdrvflagbits(sc->ifp, IFF_DRV_RUNNING, IFF_DRV_OACTIVE); mtx_unlock(&sc->io_lock); /* Check for pending receives scheduled before interrupt enable */ llan_intr(sc); } static int llan_add_rxbuf(struct llan_softc *sc, struct llan_xfer *rx) { struct mbuf *m; bus_dma_segment_t segs[1]; int error, nsegs; mtx_assert(&sc->io_lock, MA_OWNED); m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) return (ENOBUFS); m->m_len = m->m_pkthdr.len = m->m_ext.ext_size; if (rx->rx_mbuf != NULL) { bus_dmamap_sync(sc->rxbuf_dma_tag, rx->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->rxbuf_dma_tag, rx->rx_dmamap); } /* Save pointer to buffer structure */ m_copyback(m, 0, 8, (void *)&rx); error = bus_dmamap_load_mbuf_sg(sc->rxbuf_dma_tag, rx->rx_dmamap, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc->dev, "cannot load RX DMA map %p, error = %d\n", rx, error); m_freem(m); return (error); } /* If nsegs is wrong then the stack is corrupt. */ KASSERT(nsegs == 1, ("%s: too many DMA segments (%d)", __func__, nsegs)); rx->rx_mbuf = m; bus_dmamap_sync(sc->rxbuf_dma_tag, rx->rx_dmamap, BUS_DMASYNC_PREREAD); rx->rx_bufdesc = LLAN_BUFDESC_VALID; rx->rx_bufdesc |= (((uint64_t)segs[0].ds_len) << 32); rx->rx_bufdesc |= segs[0].ds_addr; error = phyp_hcall(H_ADD_LOGICAL_LAN_BUFFER, sc->unit, rx->rx_bufdesc); if (error != 0) { m_freem(m); rx->rx_mbuf = NULL; return (ENOBUFS); } return (0); } static void llan_intr(void *xsc) { struct llan_softc *sc = xsc; struct llan_xfer *rx; struct mbuf *m; mtx_lock(&sc->io_lock); restart: phyp_hcall(H_VIO_SIGNAL, sc->unit, 0); while ((sc->rx_buf[sc->rx_dma_slot].control >> 7) == sc->rx_valid_val) { rx = (struct llan_xfer *)sc->rx_buf[sc->rx_dma_slot].handle; m = rx->rx_mbuf; m_adj(m, sc->rx_buf[sc->rx_dma_slot].offset - 8); m->m_len = sc->rx_buf[sc->rx_dma_slot].length; /* llan_add_rxbuf does DMA sync and unload as well as requeue */ if (llan_add_rxbuf(sc, rx) != 0) { if_inc_counter(sc->ifp, IFCOUNTER_IERRORS, 1); continue; } if_inc_counter(sc->ifp, IFCOUNTER_IPACKETS, 1); m_adj(m, sc->rx_buf[sc->rx_dma_slot].offset); m->m_len = sc->rx_buf[sc->rx_dma_slot].length; m->m_pkthdr.rcvif = sc->ifp; m->m_pkthdr.len = m->m_len; sc->rx_dma_slot++; if (sc->rx_dma_slot >= sc->rx_buf_len/sizeof(sc->rx_buf[0])) { sc->rx_dma_slot = 0; sc->rx_valid_val = !sc->rx_valid_val; } mtx_unlock(&sc->io_lock); if_input(sc->ifp, m); mtx_lock(&sc->io_lock); } phyp_hcall(H_VIO_SIGNAL, sc->unit, 1); /* * H_VIO_SIGNAL enables interrupts for future packets only. * Make sure none were queued between the end of the loop and the * enable interrupts call. */ if ((sc->rx_buf[sc->rx_dma_slot].control >> 7) == sc->rx_valid_val) goto restart; mtx_unlock(&sc->io_lock); } static void llan_send_packet(void *xsc, bus_dma_segment_t *segs, int nsegs, bus_size_t mapsize, int error) { struct llan_softc *sc = xsc; uint64_t bufdescs[6]; int i, err; bzero(bufdescs, sizeof(bufdescs)); for (i = 0; i < nsegs; i++) { bufdescs[i] = LLAN_BUFDESC_VALID; bufdescs[i] |= (((uint64_t)segs[i].ds_len) << 32); bufdescs[i] |= segs[i].ds_addr; } err = phyp_hcall(H_SEND_LOGICAL_LAN, sc->unit, bufdescs[0], bufdescs[1], bufdescs[2], bufdescs[3], bufdescs[4], bufdescs[5], 0); /* * The hypercall returning implies completion -- or that the call will * not complete. In principle, we should try a few times if we get back * H_BUSY based on the continuation token in R4. For now, just drop * the packet in such cases. */ if (err == H_SUCCESS) if_inc_counter(sc->ifp, IFCOUNTER_OPACKETS, 1); else if_inc_counter(sc->ifp, IFCOUNTER_OERRORS, 1); } static void llan_start_locked(struct ifnet *ifp) { struct llan_softc *sc = if_getsoftc(ifp); int nsegs; struct mbuf *mb_head, *m; mtx_assert(&sc->io_lock, MA_OWNED); if ((if_getdrvflags(ifp) & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING) return; while (!if_sendq_empty(ifp)) { mb_head = if_dequeue(ifp); if (mb_head == NULL) break; BPF_MTAP(ifp, mb_head); for (m = mb_head, nsegs = 0; m != NULL; m = m->m_next) nsegs++; if (nsegs > 6) { m = m_collapse(mb_head, M_NOWAIT, 6); if (m == NULL) { m_freem(mb_head); continue; } } bus_dmamap_load_mbuf(sc->tx_dma_tag, sc->tx_dma_map, mb_head, llan_send_packet, sc, 0); bus_dmamap_unload(sc->tx_dma_tag, sc->tx_dma_map); m_freem(mb_head); } } static void llan_start(struct ifnet *ifp) { struct llan_softc *sc = if_getsoftc(ifp); mtx_lock(&sc->io_lock); llan_start_locked(ifp); mtx_unlock(&sc->io_lock); } static u_int llan_set_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { struct llan_softc *sc = arg; uint64_t macaddr = 0; memcpy((uint8_t *)&macaddr + 2, LLADDR(sdl), 6); phyp_hcall(H_MULTICAST_CTRL, sc->unit, LLAN_ADD_MULTICAST, macaddr); return (1); } static int llan_set_multicast(struct llan_softc *sc) { struct ifnet *ifp = sc->ifp; mtx_assert(&sc->io_lock, MA_OWNED); phyp_hcall(H_MULTICAST_CTRL, sc->unit, LLAN_CLEAR_MULTICAST, 0); if_foreach_llmaddr(ifp, llan_set_maddr, sc); return (0); } static int llan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { int err = 0; struct llan_softc *sc = if_getsoftc(ifp); switch (cmd) { case SIOCADDMULTI: case SIOCDELMULTI: mtx_lock(&sc->io_lock); if ((if_getdrvflags(sc->ifp) & IFF_DRV_RUNNING) != 0) llan_set_multicast(sc); mtx_unlock(&sc->io_lock); break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: err = ifmedia_ioctl(ifp, (struct ifreq *)data, &sc->media, cmd); break; case SIOCSIFFLAGS: default: err = ether_ioctl(ifp, cmd, data); break; } return (err); }