summaryrefslogtreecommitdiff
path: root/sys/dev/pv/if_vio.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/pv/if_vio.c')
-rw-r--r--sys/dev/pv/if_vio.c1463
1 files changed, 1463 insertions, 0 deletions
diff --git a/sys/dev/pv/if_vio.c b/sys/dev/pv/if_vio.c
new file mode 100644
index 00000000000..53354b25b72
--- /dev/null
+++ b/sys/dev/pv/if_vio.c
@@ -0,0 +1,1463 @@
+/* $OpenBSD: if_vio.c,v 1.1 2017/01/21 11:21:28 reyk Exp $ */
+
+/*
+ * Copyright (c) 2012 Stefan Fritsch, Alexander Fiveg.
+ * Copyright (c) 2010 Minoura Makoto.
+ * 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 ``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 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 "bpfilter.h"
+#include "vlan.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/device.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/timeout.h>
+
+#include <dev/pv/virtioreg.h>
+#include <dev/pv/virtiovar.h>
+
+#include <net/if.h>
+#include <net/if_media.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+
+#if NBPFILTER > 0
+#include <net/bpf.h>
+#endif
+
+#if VIRTIO_DEBUG
+#define DBGPRINT(fmt, args...) printf("%s: " fmt "\n", __func__, ## args)
+#else
+#define DBGPRINT(fmt, args...)
+#endif
+
+/*
+ * if_vioreg.h:
+ */
+/* Configuration registers */
+#define VIRTIO_NET_CONFIG_MAC 0 /* 8bit x 6byte */
+#define VIRTIO_NET_CONFIG_STATUS 6 /* 16bit */
+
+/* Feature bits */
+#define VIRTIO_NET_F_CSUM (1<<0)
+#define VIRTIO_NET_F_GUEST_CSUM (1<<1)
+#define VIRTIO_NET_F_MAC (1<<5)
+#define VIRTIO_NET_F_GSO (1<<6)
+#define VIRTIO_NET_F_GUEST_TSO4 (1<<7)
+#define VIRTIO_NET_F_GUEST_TSO6 (1<<8)
+#define VIRTIO_NET_F_GUEST_ECN (1<<9)
+#define VIRTIO_NET_F_GUEST_UFO (1<<10)
+#define VIRTIO_NET_F_HOST_TSO4 (1<<11)
+#define VIRTIO_NET_F_HOST_TSO6 (1<<12)
+#define VIRTIO_NET_F_HOST_ECN (1<<13)
+#define VIRTIO_NET_F_HOST_UFO (1<<14)
+#define VIRTIO_NET_F_MRG_RXBUF (1<<15)
+#define VIRTIO_NET_F_STATUS (1<<16)
+#define VIRTIO_NET_F_CTRL_VQ (1<<17)
+#define VIRTIO_NET_F_CTRL_RX (1<<18)
+#define VIRTIO_NET_F_CTRL_VLAN (1<<19)
+#define VIRTIO_NET_F_CTRL_RX_EXTRA (1<<20)
+#define VIRTIO_NET_F_GUEST_ANNOUNCE (1<<21)
+
+/*
+ * Config(8) flags. The lowest byte is reserved for generic virtio stuff.
+ */
+
+/* Workaround for vlan related bug in qemu < version 2.0 */
+#define CONFFLAG_QEMU_VLAN_BUG (1<<8)
+
+static const struct virtio_feature_name virtio_net_feature_names[] = {
+ { VIRTIO_NET_F_CSUM, "CSum" },
+ { VIRTIO_NET_F_GUEST_CSUM, "GuestCSum" },
+ { VIRTIO_NET_F_MAC, "MAC" },
+ { VIRTIO_NET_F_GSO, "GSO" },
+ { VIRTIO_NET_F_GUEST_TSO4, "GuestTSO4" },
+ { VIRTIO_NET_F_GUEST_TSO6, "GuestTSO6" },
+ { VIRTIO_NET_F_GUEST_ECN, "GuestECN" },
+ { VIRTIO_NET_F_GUEST_UFO, "GuestUFO" },
+ { VIRTIO_NET_F_HOST_TSO4, "HostTSO4" },
+ { VIRTIO_NET_F_HOST_TSO6, "HostTSO6" },
+ { VIRTIO_NET_F_HOST_ECN, "HostECN" },
+ { VIRTIO_NET_F_HOST_UFO, "HostUFO" },
+ { VIRTIO_NET_F_MRG_RXBUF, "MrgRXBuf" },
+ { VIRTIO_NET_F_STATUS, "Status" },
+ { VIRTIO_NET_F_CTRL_VQ, "CtrlVQ" },
+ { VIRTIO_NET_F_CTRL_RX, "CtrlRX" },
+ { VIRTIO_NET_F_CTRL_VLAN, "CtrlVLAN" },
+ { VIRTIO_NET_F_CTRL_RX_EXTRA, "CtrlRXExtra" },
+ { VIRTIO_NET_F_GUEST_ANNOUNCE, "GuestAnnounce" },
+ { 0, NULL }
+};
+
+/* Status */
+#define VIRTIO_NET_S_LINK_UP 1
+
+/* Packet header structure */
+struct virtio_net_hdr {
+ uint8_t flags;
+ uint8_t gso_type;
+ uint16_t hdr_len;
+ uint16_t gso_size;
+ uint16_t csum_start;
+ uint16_t csum_offset;
+
+ /* only present if VIRTIO_NET_F_MRG_RXBUF is negotiated */
+ uint16_t num_buffers;
+} __packed;
+
+#define VIRTIO_NET_HDR_F_NEEDS_CSUM 1 /* flags */
+#define VIRTIO_NET_HDR_GSO_NONE 0 /* gso_type */
+#define VIRTIO_NET_HDR_GSO_TCPV4 1 /* gso_type */
+#define VIRTIO_NET_HDR_GSO_UDP 3 /* gso_type */
+#define VIRTIO_NET_HDR_GSO_TCPV6 4 /* gso_type */
+#define VIRTIO_NET_HDR_GSO_ECN 0x80 /* gso_type, |'ed */
+
+#define VIRTIO_NET_MAX_GSO_LEN (65536+ETHER_HDR_LEN)
+
+/* Control virtqueue */
+struct virtio_net_ctrl_cmd {
+ uint8_t class;
+ uint8_t command;
+} __packed;
+#define VIRTIO_NET_CTRL_RX 0
+# define VIRTIO_NET_CTRL_RX_PROMISC 0
+# define VIRTIO_NET_CTRL_RX_ALLMULTI 1
+
+#define VIRTIO_NET_CTRL_MAC 1
+# define VIRTIO_NET_CTRL_MAC_TABLE_SET 0
+
+#define VIRTIO_NET_CTRL_VLAN 2
+# define VIRTIO_NET_CTRL_VLAN_ADD 0
+# define VIRTIO_NET_CTRL_VLAN_DEL 1
+
+struct virtio_net_ctrl_status {
+ uint8_t ack;
+} __packed;
+#define VIRTIO_NET_OK 0
+#define VIRTIO_NET_ERR 1
+
+struct virtio_net_ctrl_rx {
+ uint8_t onoff;
+} __packed;
+
+struct virtio_net_ctrl_mac_tbl {
+ uint32_t nentries;
+ uint8_t macs[][ETHER_ADDR_LEN];
+} __packed;
+
+struct virtio_net_ctrl_vlan {
+ uint16_t id;
+} __packed;
+
+/*
+ * if_viovar.h:
+ */
+enum vio_ctrl_state {
+ FREE, INUSE, DONE, RESET
+};
+
+struct vio_softc {
+ struct device sc_dev;
+
+ struct virtio_softc *sc_virtio;
+#define VQRX 0
+#define VQTX 1
+#define VQCTL 2
+ struct virtqueue sc_vq[3];
+
+ struct arpcom sc_ac;
+ struct ifmedia sc_media;
+
+ short sc_ifflags;
+
+ /* bus_dmamem */
+ bus_dma_segment_t sc_dma_seg;
+ bus_dmamap_t sc_dma_map;
+ size_t sc_dma_size;
+ caddr_t sc_dma_kva;
+
+ int sc_hdr_size;
+ struct virtio_net_hdr *sc_tx_hdrs;
+ struct virtio_net_ctrl_cmd *sc_ctrl_cmd;
+ struct virtio_net_ctrl_status *sc_ctrl_status;
+ struct virtio_net_ctrl_rx *sc_ctrl_rx;
+ struct virtio_net_ctrl_mac_tbl *sc_ctrl_mac_tbl_uc;
+#define sc_ctrl_mac_info sc_ctrl_mac_tbl_uc
+ struct virtio_net_ctrl_mac_tbl *sc_ctrl_mac_tbl_mc;
+
+ /* kmem */
+ bus_dmamap_t *sc_arrays;
+#define sc_rx_dmamaps sc_arrays
+ bus_dmamap_t *sc_tx_dmamaps;
+ struct mbuf **sc_rx_mbufs;
+ struct mbuf **sc_tx_mbufs;
+ struct if_rxring sc_rx_ring;
+
+ enum vio_ctrl_state sc_ctrl_inuse;
+
+ struct timeout sc_txtick, sc_rxtick;
+};
+
+#define VIO_DMAMEM_OFFSET(sc, p) ((caddr_t)(p) - (sc)->sc_dma_kva)
+#define VIO_DMAMEM_SYNC(vsc, sc, p, size, flags) \
+ bus_dmamap_sync((vsc)->sc_dmat, (sc)->sc_dma_map, \
+ VIO_DMAMEM_OFFSET((sc), (p)), (size), (flags))
+#define VIO_DMAMEM_ENQUEUE(sc, vq, slot, p, size, write) \
+ virtio_enqueue_p((vq), (slot), (sc)->sc_dma_map, \
+ VIO_DMAMEM_OFFSET((sc), (p)), (size), (write))
+#define VIO_HAVE_MRG_RXBUF(sc) \
+ ((sc)->sc_hdr_size == sizeof(struct virtio_net_hdr))
+
+#define VIRTIO_NET_TX_MAXNSEGS 16 /* for larger chains, defrag */
+#define VIRTIO_NET_CTRL_MAC_MC_ENTRIES 64 /* for more entries, use ALLMULTI */
+#define VIRTIO_NET_CTRL_MAC_UC_ENTRIES 1 /* one entry for own unicast addr */
+
+#define VIO_CTRL_MAC_INFO_SIZE \
+ (2*sizeof(struct virtio_net_ctrl_mac_tbl) + \
+ (VIRTIO_NET_CTRL_MAC_MC_ENTRIES + \
+ VIRTIO_NET_CTRL_MAC_UC_ENTRIES) * ETHER_ADDR_LEN)
+
+/* cfattach interface functions */
+int vio_match(struct device *, void *, void *);
+void vio_attach(struct device *, struct device *, void *);
+
+/* ifnet interface functions */
+int vio_init(struct ifnet *);
+void vio_stop(struct ifnet *, int);
+void vio_start(struct ifnet *);
+int vio_ioctl(struct ifnet *, u_long, caddr_t);
+void vio_get_lladr(struct arpcom *ac, struct virtio_softc *vsc);
+void vio_put_lladr(struct arpcom *ac, struct virtio_softc *vsc);
+
+/* rx */
+int vio_add_rx_mbuf(struct vio_softc *, int);
+void vio_free_rx_mbuf(struct vio_softc *, int);
+void vio_populate_rx_mbufs(struct vio_softc *);
+int vio_rxeof(struct vio_softc *);
+int vio_rx_intr(struct virtqueue *);
+void vio_rx_drain(struct vio_softc *);
+void vio_rxtick(void *);
+
+/* tx */
+int vio_tx_intr(struct virtqueue *);
+int vio_txeof(struct virtqueue *);
+void vio_tx_drain(struct vio_softc *);
+int vio_encap(struct vio_softc *, int, struct mbuf *);
+void vio_txtick(void *);
+
+/* other control */
+void vio_link_state(struct ifnet *);
+int vio_config_change(struct virtio_softc *);
+int vio_ctrl_rx(struct vio_softc *, int, int);
+int vio_set_rx_filter(struct vio_softc *);
+void vio_iff(struct vio_softc *);
+int vio_media_change(struct ifnet *);
+void vio_media_status(struct ifnet *, struct ifmediareq *);
+int vio_ctrleof(struct virtqueue *);
+int vio_wait_ctrl(struct vio_softc *sc);
+int vio_wait_ctrl_done(struct vio_softc *sc);
+void vio_ctrl_wakeup(struct vio_softc *, enum vio_ctrl_state);
+int vio_alloc_mem(struct vio_softc *);
+int vio_alloc_dmamem(struct vio_softc *);
+void vio_free_dmamem(struct vio_softc *);
+
+#if VIRTIO_DEBUG
+void vio_dump(struct vio_softc *);
+#endif
+
+int
+vio_match(struct device *parent, void *match, void *aux)
+{
+ struct virtio_softc *va = aux;
+
+ if (va->sc_childdevid == PCI_PRODUCT_VIRTIO_NETWORK)
+ return 1;
+
+ return 0;
+}
+
+struct cfattach vio_ca = {
+ sizeof(struct vio_softc), vio_match, vio_attach, NULL
+};
+
+struct cfdriver vio_cd = {
+ NULL, "vio", DV_IFNET
+};
+
+int
+vio_alloc_dmamem(struct vio_softc *sc)
+{
+ struct virtio_softc *vsc = sc->sc_virtio;
+ int nsegs;
+
+ if (bus_dmamap_create(vsc->sc_dmat, sc->sc_dma_size, 1,
+ sc->sc_dma_size, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW,
+ &sc->sc_dma_map) != 0)
+ goto err;
+ if (bus_dmamem_alloc(vsc->sc_dmat, sc->sc_dma_size, 16, 0,
+ &sc->sc_dma_seg, 1, &nsegs, BUS_DMA_NOWAIT | BUS_DMA_ZERO) != 0)
+ goto destroy;
+ if (bus_dmamem_map(vsc->sc_dmat, &sc->sc_dma_seg, nsegs,
+ sc->sc_dma_size, &sc->sc_dma_kva, BUS_DMA_NOWAIT) != 0)
+ goto free;
+ if (bus_dmamap_load(vsc->sc_dmat, sc->sc_dma_map, sc->sc_dma_kva,
+ sc->sc_dma_size, NULL, BUS_DMA_NOWAIT) != 0)
+ goto unmap;
+ return (0);
+
+unmap:
+ bus_dmamem_unmap(vsc->sc_dmat, sc->sc_dma_kva, sc->sc_dma_size);
+free:
+ bus_dmamem_free(vsc->sc_dmat, &sc->sc_dma_seg, 1);
+destroy:
+ bus_dmamap_destroy(vsc->sc_dmat, sc->sc_dma_map);
+err:
+ return (1);
+}
+
+void
+vio_free_dmamem(struct vio_softc *sc)
+{
+ struct virtio_softc *vsc = sc->sc_virtio;
+ bus_dmamap_unload(vsc->sc_dmat, sc->sc_dma_map);
+ bus_dmamem_unmap(vsc->sc_dmat, sc->sc_dma_kva, sc->sc_dma_size);
+ bus_dmamem_free(vsc->sc_dmat, &sc->sc_dma_seg, 1);
+ bus_dmamap_destroy(vsc->sc_dmat, sc->sc_dma_map);
+}
+
+/* allocate memory */
+/*
+ * dma memory is used for:
+ * sc_tx_hdrs[slot]: metadata array for frames to be sent (WRITE)
+ * sc_ctrl_cmd: command to be sent via ctrl vq (WRITE)
+ * sc_ctrl_status: return value for a command via ctrl vq (READ)
+ * sc_ctrl_rx: parameter for a VIRTIO_NET_CTRL_RX class command
+ * (WRITE)
+ * sc_ctrl_mac_tbl_uc: unicast MAC address filter for a VIRTIO_NET_CTRL_MAC
+ * class command (WRITE)
+ * sc_ctrl_mac_tbl_mc: multicast MAC address filter for a VIRTIO_NET_CTRL_MAC
+ * class command (WRITE)
+ * sc_ctrl_* structures are allocated only one each; they are protected by
+ * sc_ctrl_inuse, which must only be accessed at splnet
+ *
+ * metadata headers for received frames are stored at the start of the
+ * rx mbufs.
+ */
+/*
+ * dynamically allocated memory is used for:
+ * sc_rx_dmamaps[slot]: bus_dmamap_t array for received payload
+ * sc_tx_dmamaps[slot]: bus_dmamap_t array for sent payload
+ * sc_rx_mbufs[slot]: mbuf pointer array for received frames
+ * sc_tx_mbufs[slot]: mbuf pointer array for sent frames
+ */
+int
+vio_alloc_mem(struct vio_softc *sc)
+{
+ struct virtio_softc *vsc = sc->sc_virtio;
+ struct ifnet *ifp = &sc->sc_ac.ac_if;
+ int allocsize, r, i, txsize;
+ unsigned int offset = 0;
+ int rxqsize, txqsize;
+ caddr_t kva;
+
+ rxqsize = vsc->sc_vqs[0].vq_num;
+ txqsize = vsc->sc_vqs[1].vq_num;
+
+ /*
+ * For simplicity, we always allocate the full virtio_net_hdr size
+ * even if VIRTIO_NET_F_MRG_RXBUF is not negotiated and
+ * only a part of the memory is ever used.
+ */
+ allocsize = sizeof(struct virtio_net_hdr) * txqsize;
+
+ if (vsc->sc_nvqs == 3) {
+ allocsize += sizeof(struct virtio_net_ctrl_cmd) * 1;
+ allocsize += sizeof(struct virtio_net_ctrl_status) * 1;
+ allocsize += sizeof(struct virtio_net_ctrl_rx) * 1;
+ allocsize += VIO_CTRL_MAC_INFO_SIZE;
+ }
+ sc->sc_dma_size = allocsize;
+
+ if (vio_alloc_dmamem(sc) != 0) {
+ printf("unable to allocate dma region\n");
+ return -1;
+ }
+
+ kva = sc->sc_dma_kva;
+ sc->sc_tx_hdrs = (struct virtio_net_hdr*)(kva + offset);
+ offset += sizeof(struct virtio_net_hdr) * txqsize;
+ if (vsc->sc_nvqs == 3) {
+ sc->sc_ctrl_cmd = (void*)(kva + offset);
+ offset += sizeof(*sc->sc_ctrl_cmd);
+ sc->sc_ctrl_status = (void*)(kva + offset);
+ offset += sizeof(*sc->sc_ctrl_status);
+ sc->sc_ctrl_rx = (void*)(kva + offset);
+ offset += sizeof(*sc->sc_ctrl_rx);
+ sc->sc_ctrl_mac_tbl_uc = (void*)(kva + offset);
+ offset += sizeof(*sc->sc_ctrl_mac_tbl_uc) +
+ ETHER_ADDR_LEN * VIRTIO_NET_CTRL_MAC_UC_ENTRIES;
+ sc->sc_ctrl_mac_tbl_mc = (void*)(kva + offset);
+ }
+
+ sc->sc_arrays = mallocarray(rxqsize + txqsize,
+ 2 * sizeof(bus_dmamap_t) + sizeof(struct mbuf *), M_DEVBUF,
+ M_WAITOK | M_CANFAIL | M_ZERO);
+ if (sc->sc_arrays == NULL) {
+ printf("unable to allocate mem for dmamaps\n");
+ goto err_hdr;
+ }
+ allocsize = (rxqsize + txqsize) *
+ (2 * sizeof(bus_dmamap_t) + sizeof(struct mbuf *));
+
+ sc->sc_tx_dmamaps = sc->sc_arrays + rxqsize;
+ sc->sc_rx_mbufs = (void*) (sc->sc_tx_dmamaps + txqsize);
+ sc->sc_tx_mbufs = sc->sc_rx_mbufs + rxqsize;
+
+ for (i = 0; i < rxqsize; i++) {
+ r = bus_dmamap_create(vsc->sc_dmat, MCLBYTES, 1, MCLBYTES, 0,
+ BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW, &sc->sc_rx_dmamaps[i]);
+ if (r != 0)
+ goto err_reqs;
+ }
+
+ txsize = ifp->if_hardmtu + sc->sc_hdr_size + ETHER_HDR_LEN;
+ for (i = 0; i < txqsize; i++) {
+ r = bus_dmamap_create(vsc->sc_dmat, txsize,
+ VIRTIO_NET_TX_MAXNSEGS, txsize, 0,
+ BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW,
+ &sc->sc_tx_dmamaps[i]);
+ if (r != 0)
+ goto err_reqs;
+ }
+
+ return 0;
+
+err_reqs:
+ printf("dmamap creation failed, error %d\n", r);
+ for (i = 0; i < txqsize; i++) {
+ if (sc->sc_tx_dmamaps[i])
+ bus_dmamap_destroy(vsc->sc_dmat, sc->sc_tx_dmamaps[i]);
+ }
+ for (i = 0; i < rxqsize; i++) {
+ if (sc->sc_tx_dmamaps[i])
+ bus_dmamap_destroy(vsc->sc_dmat, sc->sc_rx_dmamaps[i]);
+ }
+ if (sc->sc_arrays) {
+ free(sc->sc_arrays, M_DEVBUF, 0);
+ sc->sc_arrays = 0;
+ }
+err_hdr:
+ vio_free_dmamem(sc);
+ return -1;
+}
+
+void
+vio_get_lladr(struct arpcom *ac, struct virtio_softc *vsc)
+{
+ int i;
+ for (i = 0; i < ETHER_ADDR_LEN; i++) {
+ ac->ac_enaddr[i] = virtio_read_device_config_1(vsc,
+ VIRTIO_NET_CONFIG_MAC + i);
+ }
+}
+
+void
+vio_put_lladr(struct arpcom *ac, struct virtio_softc *vsc)
+{
+ int i;
+ for (i = 0; i < ETHER_ADDR_LEN; i++) {
+ virtio_write_device_config_1(vsc, VIRTIO_NET_CONFIG_MAC + i,
+ ac->ac_enaddr[i]);
+ }
+}
+
+void
+vio_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct vio_softc *sc = (struct vio_softc *)self;
+ struct virtio_softc *vsc = (struct virtio_softc *)parent;
+ uint32_t features;
+ int i;
+ struct ifnet *ifp = &sc->sc_ac.ac_if;
+
+ if (vsc->sc_child != NULL) {
+ printf(": child already attached for %s; something wrong...\n",
+ parent->dv_xname);
+ return;
+ }
+
+ sc->sc_virtio = vsc;
+
+ vsc->sc_child = self;
+ vsc->sc_ipl = IPL_NET;
+ vsc->sc_vqs = &sc->sc_vq[0];
+ vsc->sc_config_change = 0;
+
+ features = VIRTIO_NET_F_MAC | VIRTIO_NET_F_STATUS |
+ VIRTIO_NET_F_CTRL_VQ | VIRTIO_NET_F_CTRL_RX |
+ VIRTIO_NET_F_MRG_RXBUF | VIRTIO_NET_F_CSUM;
+ /*
+ * VIRTIO_F_RING_EVENT_IDX can be switched off by setting bit 2 in the
+ * driver flags, see config(8)
+ */
+ if (!(sc->sc_dev.dv_cfdata->cf_flags & 2) &&
+ !(vsc->sc_dev.dv_cfdata->cf_flags & 2))
+ features |= VIRTIO_F_RING_EVENT_IDX;
+ else
+ printf(": RingEventIdx disabled by UKC");
+
+ features = virtio_negotiate_features(vsc, features,
+ virtio_net_feature_names);
+ if (features & VIRTIO_NET_F_MAC) {
+ vio_get_lladr(&sc->sc_ac, vsc);
+ } else {
+ ether_fakeaddr(ifp);
+ vio_put_lladr(&sc->sc_ac, vsc);
+ }
+ printf(": address %s\n", ether_sprintf(sc->sc_ac.ac_enaddr));
+
+ if (features & VIRTIO_NET_F_MRG_RXBUF) {
+ sc->sc_hdr_size = sizeof(struct virtio_net_hdr);
+ ifp->if_hardmtu = 16000; /* arbitrary limit */
+ } else {
+ sc->sc_hdr_size = offsetof(struct virtio_net_hdr, num_buffers);
+ ifp->if_hardmtu = MCLBYTES - sc->sc_hdr_size - ETHER_HDR_LEN;
+ }
+
+ if (virtio_alloc_vq(vsc, &sc->sc_vq[VQRX], 0, MCLBYTES, 2, "rx") != 0)
+ goto err;
+ vsc->sc_nvqs = 1;
+ sc->sc_vq[VQRX].vq_done = vio_rx_intr;
+ if (virtio_alloc_vq(vsc, &sc->sc_vq[VQTX], 1,
+ sc->sc_hdr_size + ifp->if_hardmtu + ETHER_HDR_LEN,
+ VIRTIO_NET_TX_MAXNSEGS + 1, "tx") != 0) {
+ goto err;
+ }
+ vsc->sc_nvqs = 2;
+ sc->sc_vq[VQTX].vq_done = vio_tx_intr;
+ virtio_start_vq_intr(vsc, &sc->sc_vq[VQRX]);
+ if (features & VIRTIO_F_RING_EVENT_IDX)
+ virtio_postpone_intr_far(&sc->sc_vq[VQTX]);
+ else
+ virtio_stop_vq_intr(vsc, &sc->sc_vq[VQTX]);
+ if ((features & VIRTIO_NET_F_CTRL_VQ)
+ && (features & VIRTIO_NET_F_CTRL_RX)) {
+ if (virtio_alloc_vq(vsc, &sc->sc_vq[VQCTL], 2, NBPG, 1,
+ "control") == 0) {
+ sc->sc_vq[VQCTL].vq_done = vio_ctrleof;
+ virtio_start_vq_intr(vsc, &sc->sc_vq[VQCTL]);
+ vsc->sc_nvqs = 3;
+ }
+ }
+
+ if (vio_alloc_mem(sc) < 0)
+ goto err;
+
+ strlcpy(ifp->if_xname, self->dv_xname, IFNAMSIZ);
+ ifp->if_softc = sc;
+ ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+ ifp->if_start = vio_start;
+ ifp->if_ioctl = vio_ioctl;
+ ifp->if_capabilities = IFCAP_VLAN_MTU;
+ if (features & VIRTIO_NET_F_CSUM)
+ ifp->if_capabilities |= IFCAP_CSUM_TCPv4|IFCAP_CSUM_UDPv4;
+ IFQ_SET_MAXLEN(&ifp->if_snd, vsc->sc_vqs[1].vq_num - 1);
+ ifmedia_init(&sc->sc_media, 0, vio_media_change, vio_media_status);
+ ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_AUTO, 0, NULL);
+ ifmedia_set(&sc->sc_media, IFM_ETHER | IFM_AUTO);
+ vsc->sc_config_change = vio_config_change;
+ timeout_set(&sc->sc_txtick, vio_txtick, &sc->sc_vq[VQTX]);
+ timeout_set(&sc->sc_rxtick, vio_rxtick, &sc->sc_vq[VQRX]);
+
+ if_attach(ifp);
+ ether_ifattach(ifp);
+
+ return;
+
+err:
+ for (i = 0; i < vsc->sc_nvqs; i++)
+ virtio_free_vq(vsc, &sc->sc_vq[i]);
+ vsc->sc_nvqs = 0;
+ vsc->sc_child = VIRTIO_CHILD_ERROR;
+ return;
+}
+
+/* check link status */
+void
+vio_link_state(struct ifnet *ifp)
+{
+ struct vio_softc *sc = ifp->if_softc;
+ struct virtio_softc *vsc = sc->sc_virtio;
+ int link_state = LINK_STATE_FULL_DUPLEX;
+
+ if (vsc->sc_features & VIRTIO_NET_F_STATUS) {
+ int status = virtio_read_device_config_2(vsc,
+ VIRTIO_NET_CONFIG_STATUS);
+ if (!(status & VIRTIO_NET_S_LINK_UP))
+ link_state = LINK_STATE_DOWN;
+ }
+ if (ifp->if_link_state != link_state) {
+ ifp->if_link_state = link_state;
+ if_link_state_change(ifp);
+ }
+}
+
+int
+vio_config_change(struct virtio_softc *vsc)
+{
+ struct vio_softc *sc = (struct vio_softc *)vsc->sc_child;
+ vio_link_state(&sc->sc_ac.ac_if);
+ return 1;
+}
+
+int
+vio_media_change(struct ifnet *ifp)
+{
+ /* Ignore */
+ return (0);
+}
+
+void
+vio_media_status(struct ifnet *ifp, struct ifmediareq *imr)
+{
+ imr->ifm_active = IFM_ETHER | IFM_AUTO;
+ imr->ifm_status = IFM_AVALID;
+
+ vio_link_state(ifp);
+ if (LINK_STATE_IS_UP(ifp->if_link_state) && ifp->if_flags & IFF_UP)
+ imr->ifm_status |= IFM_ACTIVE|IFM_FDX;
+}
+
+/*
+ * Interface functions for ifnet
+ */
+int
+vio_init(struct ifnet *ifp)
+{
+ struct vio_softc *sc = ifp->if_softc;
+
+ vio_stop(ifp, 0);
+ if_rxr_init(&sc->sc_rx_ring, 2 * ((ifp->if_hardmtu / MCLBYTES) + 1),
+ sc->sc_vq[VQRX].vq_num);
+ vio_populate_rx_mbufs(sc);
+ ifp->if_flags |= IFF_RUNNING;
+ ifq_clr_oactive(&ifp->if_snd);
+ vio_iff(sc);
+ vio_link_state(ifp);
+ return 0;
+}
+
+void
+vio_stop(struct ifnet *ifp, int disable)
+{
+ struct vio_softc *sc = ifp->if_softc;
+ struct virtio_softc *vsc = sc->sc_virtio;
+
+ timeout_del(&sc->sc_txtick);
+ timeout_del(&sc->sc_rxtick);
+ ifp->if_flags &= ~IFF_RUNNING;
+ ifq_clr_oactive(&ifp->if_snd);
+ /* only way to stop I/O and DMA is resetting... */
+ virtio_reset(vsc);
+ vio_rxeof(sc);
+ if (vsc->sc_nvqs >= 3)
+ vio_ctrleof(&sc->sc_vq[VQCTL]);
+ vio_tx_drain(sc);
+ if (disable)
+ vio_rx_drain(sc);
+
+ virtio_reinit_start(vsc);
+ virtio_negotiate_features(vsc, vsc->sc_features, NULL);
+ virtio_start_vq_intr(vsc, &sc->sc_vq[VQRX]);
+ virtio_stop_vq_intr(vsc, &sc->sc_vq[VQTX]);
+ if (vsc->sc_nvqs >= 3)
+ virtio_start_vq_intr(vsc, &sc->sc_vq[VQCTL]);
+ virtio_reinit_end(vsc);
+ if (vsc->sc_nvqs >= 3) {
+ if (sc->sc_ctrl_inuse != FREE)
+ sc->sc_ctrl_inuse = RESET;
+ wakeup(&sc->sc_ctrl_inuse);
+ }
+}
+
+void
+vio_start(struct ifnet *ifp)
+{
+ struct vio_softc *sc = ifp->if_softc;
+ struct virtio_softc *vsc = sc->sc_virtio;
+ struct virtqueue *vq = &sc->sc_vq[VQTX];
+ struct mbuf *m;
+ int queued = 0;
+
+ vio_txeof(vq);
+
+ if (!(ifp->if_flags & IFF_RUNNING) || ifq_is_oactive(&ifp->if_snd))
+ return;
+ if (IFQ_IS_EMPTY(&ifp->if_snd))
+ return;
+
+again:
+ for (;;) {
+ int slot, r;
+ struct virtio_net_hdr *hdr;
+
+ m = ifq_deq_begin(&ifp->if_snd);
+ if (m == NULL)
+ break;
+
+ r = virtio_enqueue_prep(vq, &slot);
+ if (r == EAGAIN) {
+ ifq_deq_rollback(&ifp->if_snd, m);
+ ifq_set_oactive(&ifp->if_snd);
+ break;
+ }
+ if (r != 0)
+ panic("enqueue_prep for a tx buffer: %d", r);
+
+ hdr = &sc->sc_tx_hdrs[slot];
+ memset(hdr, 0, sc->sc_hdr_size);
+ if (m->m_pkthdr.csum_flags & (M_TCP_CSUM_OUT|M_UDP_CSUM_OUT)) {
+ struct mbuf *mip;
+ struct ip *ip;
+ int ehdrlen = ETHER_HDR_LEN;
+ int ipoff;
+#if NVLAN > 0
+ struct ether_vlan_header *eh;
+
+ eh = mtod(m, struct ether_vlan_header *);
+ if (eh->evl_encap_proto == htons(ETHERTYPE_VLAN))
+ ehdrlen += ETHER_VLAN_ENCAP_LEN;
+#endif
+
+ if (m->m_pkthdr.csum_flags & M_TCP_CSUM_OUT)
+ hdr->csum_offset = offsetof(struct tcphdr, th_sum);
+ else
+ hdr->csum_offset = offsetof(struct udphdr, uh_sum);
+
+ mip = m_getptr(m, ehdrlen, &ipoff);
+ KASSERT(mip != NULL && mip->m_len - ipoff >= sizeof(*ip));
+ ip = (struct ip *)(mip->m_data + ipoff);
+ hdr->csum_start = ehdrlen + (ip->ip_hl << 2);
+ hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
+ }
+
+ r = vio_encap(sc, slot, m);
+ if (r != 0) {
+ virtio_enqueue_abort(vq, slot);
+ ifq_deq_commit(&ifp->if_snd, m);
+ m_freem(m);
+ ifp->if_oerrors++;
+ continue;
+ }
+ r = virtio_enqueue_reserve(vq, slot,
+ sc->sc_tx_dmamaps[slot]->dm_nsegs + 1);
+ if (r != 0) {
+ bus_dmamap_unload(vsc->sc_dmat,
+ sc->sc_tx_dmamaps[slot]);
+ ifq_deq_rollback(&ifp->if_snd, m);
+ sc->sc_tx_mbufs[slot] = NULL;
+ ifq_set_oactive(&ifp->if_snd);
+ break;
+ }
+ ifq_deq_commit(&ifp->if_snd, m);
+
+ bus_dmamap_sync(vsc->sc_dmat, sc->sc_tx_dmamaps[slot], 0,
+ sc->sc_tx_dmamaps[slot]->dm_mapsize, BUS_DMASYNC_PREWRITE);
+ VIO_DMAMEM_SYNC(vsc, sc, hdr, sc->sc_hdr_size,
+ BUS_DMASYNC_PREWRITE);
+ VIO_DMAMEM_ENQUEUE(sc, vq, slot, hdr, sc->sc_hdr_size, 1);
+ virtio_enqueue(vq, slot, sc->sc_tx_dmamaps[slot], 1);
+ virtio_enqueue_commit(vsc, vq, slot, 0);
+ queued++;
+#if NBPFILTER > 0
+ if (ifp->if_bpf)
+ bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT);
+#endif
+ }
+ if (ifq_is_oactive(&ifp->if_snd)) {
+ int r;
+ if (vsc->sc_features & VIRTIO_F_RING_EVENT_IDX)
+ r = virtio_postpone_intr_smart(&sc->sc_vq[VQTX]);
+ else
+ r = virtio_start_vq_intr(vsc, &sc->sc_vq[VQTX]);
+ if (r) {
+ vio_txeof(vq);
+ goto again;
+ }
+ }
+
+ if (queued > 0) {
+ virtio_notify(vsc, vq);
+ timeout_add_sec(&sc->sc_txtick, 1);
+ }
+}
+
+#if VIRTIO_DEBUG
+void
+vio_dump(struct vio_softc *sc)
+{
+ struct ifnet *ifp = &sc->sc_ac.ac_if;
+ struct virtio_softc *vsc = sc->sc_virtio;
+
+ printf("%s status dump:\n", ifp->if_xname);
+ printf("TX virtqueue:\n");
+ virtio_vq_dump(&vsc->sc_vqs[VQTX]);
+ printf("tx tick active: %d\n", !timeout_triggered(&sc->sc_txtick));
+ printf("rx tick active: %d\n", !timeout_triggered(&sc->sc_rxtick));
+ printf("RX virtqueue:\n");
+ virtio_vq_dump(&vsc->sc_vqs[VQRX]);
+ if (vsc->sc_nvqs == 3) {
+ printf("CTL virtqueue:\n");
+ virtio_vq_dump(&vsc->sc_vqs[VQCTL]);
+ printf("ctrl_inuse: %d\n", sc->sc_ctrl_inuse);
+ }
+}
+#endif
+
+int
+vio_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+ struct vio_softc *sc = ifp->if_softc;
+ struct ifreq *ifr = (struct ifreq *)data;
+ int s, r = 0;
+
+ s = splnet();
+ switch (cmd) {
+ case SIOCSIFADDR:
+ ifp->if_flags |= IFF_UP;
+ if (!(ifp->if_flags & IFF_RUNNING))
+ vio_init(ifp);
+ break;
+ case SIOCSIFFLAGS:
+ if (ifp->if_flags & IFF_UP) {
+#if VIRTIO_DEBUG
+ if (ifp->if_flags & IFF_DEBUG)
+ vio_dump(sc);
+#endif
+ if (ifp->if_flags & IFF_RUNNING)
+ r = ENETRESET;
+ else
+ vio_init(ifp);
+ } else {
+ if (ifp->if_flags & IFF_RUNNING)
+ vio_stop(ifp, 1);
+ }
+ break;
+ case SIOCGIFMEDIA:
+ case SIOCSIFMEDIA:
+ r = ifmedia_ioctl(ifp, ifr, &sc->sc_media, cmd);
+ break;
+ case SIOCGIFRXR:
+ r = if_rxr_ioctl((struct if_rxrinfo *)ifr->ifr_data,
+ NULL, MCLBYTES, &sc->sc_rx_ring);
+ break;
+ default:
+ r = ether_ioctl(ifp, &sc->sc_ac, cmd, data);
+ }
+
+ if (r == ENETRESET) {
+ if (ifp->if_flags & IFF_RUNNING)
+ vio_iff(sc);
+ r = 0;
+ }
+ splx(s);
+ return r;
+}
+
+/*
+ * Recieve implementation
+ */
+/* allocate and initialize a mbuf for receive */
+int
+vio_add_rx_mbuf(struct vio_softc *sc, int i)
+{
+ struct mbuf *m;
+ int r;
+
+ m = MCLGETI(NULL, M_DONTWAIT, NULL, MCLBYTES);
+ if (m == NULL)
+ return ENOBUFS;
+ sc->sc_rx_mbufs[i] = m;
+ m->m_len = m->m_pkthdr.len = m->m_ext.ext_size;
+ r = bus_dmamap_load_mbuf(sc->sc_virtio->sc_dmat, sc->sc_rx_dmamaps[i],
+ m, BUS_DMA_READ|BUS_DMA_NOWAIT);
+ if (r) {
+ m_freem(m);
+ sc->sc_rx_mbufs[i] = 0;
+ return r;
+ }
+
+ return 0;
+}
+
+/* free a mbuf for receive */
+void
+vio_free_rx_mbuf(struct vio_softc *sc, int i)
+{
+ bus_dmamap_unload(sc->sc_virtio->sc_dmat, sc->sc_rx_dmamaps[i]);
+ m_freem(sc->sc_rx_mbufs[i]);
+ sc->sc_rx_mbufs[i] = NULL;
+}
+
+/* add mbufs for all the empty receive slots */
+void
+vio_populate_rx_mbufs(struct vio_softc *sc)
+{
+ struct virtio_softc *vsc = sc->sc_virtio;
+ int r, done = 0;
+ u_int slots;
+ struct virtqueue *vq = &sc->sc_vq[VQRX];
+ int mrg_rxbuf = VIO_HAVE_MRG_RXBUF(sc);
+
+ for (slots = if_rxr_get(&sc->sc_rx_ring, vq->vq_num);
+ slots > 0; slots--) {
+ int slot;
+ r = virtio_enqueue_prep(vq, &slot);
+ if (r == EAGAIN)
+ break;
+ if (r != 0)
+ panic("enqueue_prep for rx buffers: %d", r);
+ if (sc->sc_rx_mbufs[slot] == NULL) {
+ r = vio_add_rx_mbuf(sc, slot);
+ if (r != 0) {
+ virtio_enqueue_abort(vq, slot);
+ break;
+ }
+ }
+ r = virtio_enqueue_reserve(vq, slot,
+ sc->sc_rx_dmamaps[slot]->dm_nsegs + (mrg_rxbuf ? 0 : 1));
+ if (r != 0) {
+ vio_free_rx_mbuf(sc, slot);
+ break;
+ }
+ bus_dmamap_sync(vsc->sc_dmat, sc->sc_rx_dmamaps[slot], 0,
+ MCLBYTES, BUS_DMASYNC_PREREAD);
+ if (mrg_rxbuf) {
+ virtio_enqueue(vq, slot, sc->sc_rx_dmamaps[slot], 0);
+ } else {
+ /*
+ * Buggy kvm wants a buffer of exactly the size of
+ * the header in this case, so we have to split in
+ * two.
+ */
+ virtio_enqueue_p(vq, slot, sc->sc_rx_dmamaps[slot],
+ 0, sc->sc_hdr_size, 0);
+ virtio_enqueue_p(vq, slot, sc->sc_rx_dmamaps[slot],
+ sc->sc_hdr_size, MCLBYTES - sc->sc_hdr_size, 0);
+ }
+ virtio_enqueue_commit(vsc, vq, slot, 0);
+ done = 1;
+ }
+ if_rxr_put(&sc->sc_rx_ring, slots);
+
+ if (done)
+ virtio_notify(vsc, vq);
+ if (vq->vq_used_idx != vq->vq_avail_idx)
+ timeout_del(&sc->sc_rxtick);
+ else
+ timeout_add_sec(&sc->sc_rxtick, 1);
+}
+
+/* dequeue received packets */
+int
+vio_rxeof(struct vio_softc *sc)
+{
+ struct virtio_softc *vsc = sc->sc_virtio;
+ struct virtqueue *vq = &sc->sc_vq[VQRX];
+ struct ifnet *ifp = &sc->sc_ac.ac_if;
+ struct mbuf_list ml = MBUF_LIST_INITIALIZER();
+ struct mbuf *m, *m0 = NULL, *mlast;
+ int r = 0;
+ int slot, len, bufs_left;
+ struct virtio_net_hdr *hdr;
+
+ while (virtio_dequeue(vsc, vq, &slot, &len) == 0) {
+ r = 1;
+ bus_dmamap_sync(vsc->sc_dmat, sc->sc_rx_dmamaps[slot], 0,
+ MCLBYTES, BUS_DMASYNC_POSTREAD);
+ m = sc->sc_rx_mbufs[slot];
+ KASSERT(m != NULL);
+ bus_dmamap_unload(vsc->sc_dmat, sc->sc_rx_dmamaps[slot]);
+ sc->sc_rx_mbufs[slot] = NULL;
+ virtio_dequeue_commit(vq, slot);
+ if_rxr_put(&sc->sc_rx_ring, 1);
+ m->m_len = m->m_pkthdr.len = len;
+ m->m_pkthdr.csum_flags = 0;
+ if (m0 == NULL) {
+ hdr = mtod(m, struct virtio_net_hdr *);
+ m_adj(m, sc->sc_hdr_size);
+ m0 = mlast = m;
+ if (VIO_HAVE_MRG_RXBUF(sc))
+ bufs_left = hdr->num_buffers - 1;
+ else
+ bufs_left = 0;
+ }
+ else {
+ m->m_flags &= ~M_PKTHDR;
+ m0->m_pkthdr.len += m->m_len;
+ mlast->m_next = m;
+ mlast = m;
+ bufs_left--;
+ }
+
+ if (bufs_left == 0) {
+ ml_enqueue(&ml, m0);
+ m0 = NULL;
+ }
+ }
+ if (m0 != NULL) {
+ DBGPRINT("expected %d buffers, got %d", (int)hdr->num_buffers,
+ (int)hdr->num_buffers - bufs_left);
+ ifp->if_ierrors++;
+ m_freem(m0);
+ }
+
+ if_input(ifp, &ml);
+ return r;
+}
+
+int
+vio_rx_intr(struct virtqueue *vq)
+{
+ struct virtio_softc *vsc = vq->vq_owner;
+ struct vio_softc *sc = (struct vio_softc *)vsc->sc_child;
+ int r, sum = 0;
+
+again:
+ r = vio_rxeof(sc);
+ sum += r;
+ if (r) {
+ vio_populate_rx_mbufs(sc);
+ /* set used event index to the next slot */
+ if (vsc->sc_features & VIRTIO_F_RING_EVENT_IDX) {
+ if (virtio_start_vq_intr(vq->vq_owner, vq))
+ goto again;
+ }
+ }
+
+ return sum;
+}
+
+void
+vio_rxtick(void *arg)
+{
+ struct virtqueue *vq = arg;
+ struct virtio_softc *vsc = vq->vq_owner;
+ struct vio_softc *sc = (struct vio_softc *)vsc->sc_child;
+ int s;
+
+ s = splnet();
+ vio_populate_rx_mbufs(sc);
+ splx(s);
+}
+
+/* free all the mbufs; called from if_stop(disable) */
+void
+vio_rx_drain(struct vio_softc *sc)
+{
+ struct virtqueue *vq = &sc->sc_vq[VQRX];
+ int i;
+
+ for (i = 0; i < vq->vq_num; i++) {
+ if (sc->sc_rx_mbufs[i] == NULL)
+ continue;
+ vio_free_rx_mbuf(sc, i);
+ }
+}
+
+/*
+ * Transmition implementation
+ */
+/* actual transmission is done in if_start */
+/* tx interrupt; dequeue and free mbufs */
+/*
+ * tx interrupt is actually disabled unless the tx queue is full, i.e.
+ * IFF_OACTIVE is set. vio_txtick is used to make sure that mbufs
+ * are dequeued and freed even if no further transfer happens.
+ */
+int
+vio_tx_intr(struct virtqueue *vq)
+{
+ struct virtio_softc *vsc = vq->vq_owner;
+ struct vio_softc *sc = (struct vio_softc *)vsc->sc_child;
+ struct ifnet *ifp = &sc->sc_ac.ac_if;
+ int r;
+
+ r = vio_txeof(vq);
+ vio_start(ifp);
+ return r;
+}
+
+void
+vio_txtick(void *arg)
+{
+ struct virtqueue *vq = arg;
+ int s = splnet();
+ vio_tx_intr(vq);
+ splx(s);
+}
+
+int
+vio_txeof(struct virtqueue *vq)
+{
+ struct virtio_softc *vsc = vq->vq_owner;
+ struct vio_softc *sc = (struct vio_softc *)vsc->sc_child;
+ struct ifnet *ifp = &sc->sc_ac.ac_if;
+ struct mbuf *m;
+ int r = 0;
+ int slot, len;
+
+ while (virtio_dequeue(vsc, vq, &slot, &len) == 0) {
+ struct virtio_net_hdr *hdr = &sc->sc_tx_hdrs[slot];
+ r++;
+ VIO_DMAMEM_SYNC(vsc, sc, hdr, sc->sc_hdr_size,
+ BUS_DMASYNC_POSTWRITE);
+ bus_dmamap_sync(vsc->sc_dmat, sc->sc_tx_dmamaps[slot], 0,
+ sc->sc_tx_dmamaps[slot]->dm_mapsize,
+ BUS_DMASYNC_POSTWRITE);
+ m = sc->sc_tx_mbufs[slot];
+ bus_dmamap_unload(vsc->sc_dmat, sc->sc_tx_dmamaps[slot]);
+ sc->sc_tx_mbufs[slot] = 0;
+ virtio_dequeue_commit(vq, slot);
+ ifp->if_opackets++;
+ m_freem(m);
+ }
+
+ if (r) {
+ ifq_clr_oactive(&ifp->if_snd);
+ virtio_stop_vq_intr(vsc, &sc->sc_vq[VQTX]);
+ }
+ if (vq->vq_used_idx == vq->vq_avail_idx)
+ timeout_del(&sc->sc_txtick);
+ else if (r)
+ timeout_add_sec(&sc->sc_txtick, 1);
+ return r;
+}
+
+int
+vio_encap(struct vio_softc *sc, int slot, struct mbuf *m)
+{
+ struct virtio_softc *vsc = sc->sc_virtio;
+ bus_dmamap_t dmap= sc->sc_tx_dmamaps[slot];
+ int r;
+
+ r = bus_dmamap_load_mbuf(vsc->sc_dmat, dmap, m,
+ BUS_DMA_WRITE|BUS_DMA_NOWAIT);
+ switch (r) {
+ case 0:
+ break;
+ case EFBIG:
+ if (m_defrag(m, M_DONTWAIT) == 0 &&
+ bus_dmamap_load_mbuf(vsc->sc_dmat, dmap, m,
+ BUS_DMA_WRITE|BUS_DMA_NOWAIT) == 0)
+ break;
+
+ /* FALLTHROUGH */
+ default:
+ return ENOBUFS;
+ }
+ sc->sc_tx_mbufs[slot] = m;
+ return 0;
+}
+
+/* free all the mbufs already put on vq; called from if_stop(disable) */
+void
+vio_tx_drain(struct vio_softc *sc)
+{
+ struct virtio_softc *vsc = sc->sc_virtio;
+ struct virtqueue *vq = &sc->sc_vq[VQTX];
+ int i;
+
+ for (i = 0; i < vq->vq_num; i++) {
+ if (sc->sc_tx_mbufs[i] == NULL)
+ continue;
+ bus_dmamap_unload(vsc->sc_dmat, sc->sc_tx_dmamaps[i]);
+ m_freem(sc->sc_tx_mbufs[i]);
+ sc->sc_tx_mbufs[i] = NULL;
+ }
+}
+
+/*
+ * Control vq
+ */
+/* issue a VIRTIO_NET_CTRL_RX class command and wait for completion */
+int
+vio_ctrl_rx(struct vio_softc *sc, int cmd, int onoff)
+{
+ struct virtio_softc *vsc = sc->sc_virtio;
+ struct virtqueue *vq = &sc->sc_vq[VQCTL];
+ int r, slot;
+
+ splassert(IPL_NET);
+
+ if ((r = vio_wait_ctrl(sc)) != 0)
+ return r;
+
+ sc->sc_ctrl_cmd->class = VIRTIO_NET_CTRL_RX;
+ sc->sc_ctrl_cmd->command = cmd;
+ sc->sc_ctrl_rx->onoff = onoff;
+
+ VIO_DMAMEM_SYNC(vsc, sc, sc->sc_ctrl_cmd,
+ sizeof(*sc->sc_ctrl_cmd), BUS_DMASYNC_PREWRITE);
+ VIO_DMAMEM_SYNC(vsc, sc, sc->sc_ctrl_rx,
+ sizeof(*sc->sc_ctrl_rx), BUS_DMASYNC_PREWRITE);
+ VIO_DMAMEM_SYNC(vsc, sc, sc->sc_ctrl_status,
+ sizeof(*sc->sc_ctrl_status), BUS_DMASYNC_PREREAD);
+
+ r = virtio_enqueue_prep(vq, &slot);
+ if (r != 0)
+ panic("%s: control vq busy!?", sc->sc_dev.dv_xname);
+ r = virtio_enqueue_reserve(vq, slot, 3);
+ if (r != 0)
+ panic("%s: control vq busy!?", sc->sc_dev.dv_xname);
+ VIO_DMAMEM_ENQUEUE(sc, vq, slot, sc->sc_ctrl_cmd,
+ sizeof(*sc->sc_ctrl_cmd), 1);
+ VIO_DMAMEM_ENQUEUE(sc, vq, slot, sc->sc_ctrl_rx,
+ sizeof(*sc->sc_ctrl_rx), 1);
+ VIO_DMAMEM_ENQUEUE(sc, vq, slot, sc->sc_ctrl_status,
+ sizeof(*sc->sc_ctrl_status), 0);
+ virtio_enqueue_commit(vsc, vq, slot, 1);
+
+ if ((r = vio_wait_ctrl_done(sc)) != 0)
+ goto out;
+
+ VIO_DMAMEM_SYNC(vsc, sc, sc->sc_ctrl_cmd,
+ sizeof(*sc->sc_ctrl_cmd), BUS_DMASYNC_POSTWRITE);
+ VIO_DMAMEM_SYNC(vsc, sc, sc->sc_ctrl_rx,
+ sizeof(*sc->sc_ctrl_rx), BUS_DMASYNC_POSTWRITE);
+ VIO_DMAMEM_SYNC(vsc, sc, sc->sc_ctrl_status,
+ sizeof(*sc->sc_ctrl_status), BUS_DMASYNC_POSTREAD);
+
+ if (sc->sc_ctrl_status->ack == VIRTIO_NET_OK) {
+ r = 0;
+ } else {
+ printf("%s: ctrl cmd %d failed\n", sc->sc_dev.dv_xname, cmd);
+ r = EIO;
+ }
+
+ DBGPRINT("cmd %d %d: %d", cmd, (int)onoff, r);
+out:
+ vio_ctrl_wakeup(sc, FREE);
+ return r;
+}
+
+int
+vio_wait_ctrl(struct vio_softc *sc)
+{
+ int r = 0;
+
+ while (sc->sc_ctrl_inuse != FREE) {
+ r = tsleep(&sc->sc_ctrl_inuse, PRIBIO|PCATCH, "viowait", 0);
+ if (r == EINTR)
+ return r;
+ }
+ sc->sc_ctrl_inuse = INUSE;
+
+ return r;
+}
+
+int
+vio_wait_ctrl_done(struct vio_softc *sc)
+{
+ int r = 0;
+
+ while (sc->sc_ctrl_inuse != DONE && sc->sc_ctrl_inuse != RESET) {
+ if (sc->sc_ctrl_inuse == RESET) {
+ r = 1;
+ break;
+ }
+ r = tsleep(&sc->sc_ctrl_inuse, PRIBIO|PCATCH, "viodone", 0);
+ if (r == EINTR)
+ break;
+ }
+ return r;
+}
+
+void
+vio_ctrl_wakeup(struct vio_softc *sc, enum vio_ctrl_state new)
+{
+ sc->sc_ctrl_inuse = new;
+ wakeup(&sc->sc_ctrl_inuse);
+}
+
+int
+vio_ctrleof(struct virtqueue *vq)
+{
+ struct virtio_softc *vsc = vq->vq_owner;
+ struct vio_softc *sc = (struct vio_softc *)vsc->sc_child;
+ int r = 0, ret, slot;
+
+again:
+ ret = virtio_dequeue(vsc, vq, &slot, NULL);
+ if (ret == ENOENT)
+ return r;
+ virtio_dequeue_commit(vq, slot);
+ r++;
+ vio_ctrl_wakeup(sc, DONE);
+ if (virtio_start_vq_intr(vsc, vq))
+ goto again;
+
+ return r;
+}
+
+/* issue VIRTIO_NET_CTRL_MAC_TABLE_SET command and wait for completion */
+int
+vio_set_rx_filter(struct vio_softc *sc)
+{
+ /* filter already set in sc_ctrl_mac_tbl */
+ struct virtio_softc *vsc = sc->sc_virtio;
+ struct virtqueue *vq = &sc->sc_vq[VQCTL];
+ int r, slot;
+
+ splassert(IPL_NET);
+
+ if ((r = vio_wait_ctrl(sc)) != 0)
+ return r;
+
+ sc->sc_ctrl_cmd->class = VIRTIO_NET_CTRL_MAC;
+ sc->sc_ctrl_cmd->command = VIRTIO_NET_CTRL_MAC_TABLE_SET;
+
+ VIO_DMAMEM_SYNC(vsc, sc, sc->sc_ctrl_cmd,
+ sizeof(*sc->sc_ctrl_cmd), BUS_DMASYNC_PREWRITE);
+ VIO_DMAMEM_SYNC(vsc, sc, sc->sc_ctrl_mac_info,
+ VIO_CTRL_MAC_INFO_SIZE, BUS_DMASYNC_PREWRITE);
+ VIO_DMAMEM_SYNC(vsc, sc, sc->sc_ctrl_status,
+ sizeof(*sc->sc_ctrl_status), BUS_DMASYNC_PREREAD);
+
+ r = virtio_enqueue_prep(vq, &slot);
+ if (r != 0)
+ panic("%s: control vq busy!?", sc->sc_dev.dv_xname);
+ r = virtio_enqueue_reserve(vq, slot, 4);
+ if (r != 0)
+ panic("%s: control vq busy!?", sc->sc_dev.dv_xname);
+ VIO_DMAMEM_ENQUEUE(sc, vq, slot, sc->sc_ctrl_cmd,
+ sizeof(*sc->sc_ctrl_cmd), 1);
+ VIO_DMAMEM_ENQUEUE(sc, vq, slot, sc->sc_ctrl_mac_tbl_uc,
+ sizeof(*sc->sc_ctrl_mac_tbl_uc) +
+ sc->sc_ctrl_mac_tbl_uc->nentries * ETHER_ADDR_LEN, 1);
+ VIO_DMAMEM_ENQUEUE(sc, vq, slot, sc->sc_ctrl_mac_tbl_mc,
+ sizeof(*sc->sc_ctrl_mac_tbl_mc) +
+ sc->sc_ctrl_mac_tbl_mc->nentries * ETHER_ADDR_LEN, 1);
+ VIO_DMAMEM_ENQUEUE(sc, vq, slot, sc->sc_ctrl_status,
+ sizeof(*sc->sc_ctrl_status), 0);
+ virtio_enqueue_commit(vsc, vq, slot, 1);
+
+ if ((r = vio_wait_ctrl_done(sc)) != 0)
+ goto out;
+
+ VIO_DMAMEM_SYNC(vsc, sc, sc->sc_ctrl_cmd,
+ sizeof(*sc->sc_ctrl_cmd), BUS_DMASYNC_POSTWRITE);
+ VIO_DMAMEM_SYNC(vsc, sc, sc->sc_ctrl_mac_info,
+ VIO_CTRL_MAC_INFO_SIZE, BUS_DMASYNC_POSTWRITE);
+ VIO_DMAMEM_SYNC(vsc, sc, sc->sc_ctrl_status,
+ sizeof(*sc->sc_ctrl_status), BUS_DMASYNC_POSTREAD);
+
+ if (sc->sc_ctrl_status->ack == VIRTIO_NET_OK) {
+ r = 0;
+ } else {
+ /* The host's filter table is not large enough */
+ printf("%s: failed setting rx filter\n", sc->sc_dev.dv_xname);
+ r = EIO;
+ }
+
+out:
+ vio_ctrl_wakeup(sc, FREE);
+ return r;
+}
+
+void
+vio_iff(struct vio_softc *sc)
+{
+ struct virtio_softc *vsc = sc->sc_virtio;
+ struct ifnet *ifp = &sc->sc_ac.ac_if;
+ struct arpcom *ac = &sc->sc_ac;
+ struct ether_multi *enm;
+ struct ether_multistep step;
+ int nentries = 0;
+ int promisc = 0, allmulti = 0, rxfilter = 0;
+ int r;
+
+ splassert(IPL_NET);
+
+ ifp->if_flags &= ~IFF_ALLMULTI;
+
+ if (vsc->sc_nvqs < 3) {
+ /* no ctrl vq; always promisc */
+ ifp->if_flags |= IFF_ALLMULTI | IFF_PROMISC;
+ return;
+ }
+
+ if (sc->sc_dev.dv_cfdata->cf_flags & CONFFLAG_QEMU_VLAN_BUG)
+ ifp->if_flags |= IFF_PROMISC;
+
+ if (ifp->if_flags & IFF_PROMISC || ac->ac_multirangecnt > 0 ||
+ ac->ac_multicnt >= VIRTIO_NET_CTRL_MAC_MC_ENTRIES) {
+ ifp->if_flags |= IFF_ALLMULTI;
+ if (ifp->if_flags & IFF_PROMISC)
+ promisc = 1;
+ else
+ allmulti = 1;
+ } else {
+ rxfilter = 1;
+
+ ETHER_FIRST_MULTI(step, ac, enm);
+ while (enm != NULL) {
+ memcpy(sc->sc_ctrl_mac_tbl_mc->macs[nentries++],
+ enm->enm_addrlo, ETHER_ADDR_LEN);
+
+ ETHER_NEXT_MULTI(step, enm);
+ }
+ }
+
+ /* set unicast address, VirtualBox wants that */
+ memcpy(sc->sc_ctrl_mac_tbl_uc->macs[0], ac->ac_enaddr, ETHER_ADDR_LEN);
+ sc->sc_ctrl_mac_tbl_uc->nentries = 1;
+
+ sc->sc_ctrl_mac_tbl_mc->nentries = rxfilter ? nentries : 0;
+
+ if (vsc->sc_nvqs < 3)
+ return;
+
+ r = vio_set_rx_filter(sc);
+ if (r == EIO)
+ allmulti = 1; /* fallback */
+ else if (r != 0)
+ return;
+
+ r = vio_ctrl_rx(sc, VIRTIO_NET_CTRL_RX_ALLMULTI, allmulti);
+ if (r == EIO)
+ promisc = 1; /* fallback */
+ else if (r != 0)
+ return;
+
+ vio_ctrl_rx(sc, VIRTIO_NET_CTRL_RX_PROMISC, promisc);
+}