diff options
Diffstat (limited to 'sys/powerpc/pseries/phyp_llan.c')
| -rw-r--r-- | sys/powerpc/pseries/phyp_llan.c | 556 | 
1 files changed, 556 insertions, 0 deletions
| diff --git a/sys/powerpc/pseries/phyp_llan.c b/sys/powerpc/pseries/phyp_llan.c new file mode 100644 index 000000000000..4ba4549a9cf5 --- /dev/null +++ b/sys/powerpc/pseries/phyp_llan.c @@ -0,0 +1,556 @@ +/*- + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/sockio.h> +#include <sys/endian.h> +#include <sys/lock.h> +#include <sys/mbuf.h> +#include <sys/module.h> +#include <sys/malloc.h> +#include <sys/mutex.h> +#include <sys/kernel.h> +#include <sys/socket.h> + +#include <net/bpf.h> +#include <net/if.h> +#include <net/if_var.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/bus.h> +#include <sys/rman.h> + +#include <powerpc/pseries/phyp-hvcall.h> + +#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); +} | 
