Edit

IABSD.fr/src/sys/dev/pv/if_vio.c

Branch :

  • Show log

    Commit

  • Author : mpi
    Date : 2019-12-31 10:05:32
    Hash : 03604742
    Message : Convert infinite sleeps to {m,t}sleep_nsec(9). ok kn@

  • sys/dev/pv/if_vio.c
  • /*	$OpenBSD: if_vio.c,v 1.16 2019/12/31 10:05:33 mpi 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 DPRINTF(x...) printf(x)
    #else
    #define DPRINTF(x...)
    #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			(1ULL<<0)
    #define VIRTIO_NET_F_GUEST_CSUM			(1ULL<<1)
    #define VIRTIO_NET_F_CTRL_GUEST_OFFLOADS        (1ULL<<2)
    #define VIRTIO_NET_F_MTU                        (1ULL<<3)
    #define VIRTIO_NET_F_MAC			(1ULL<<5)
    #define VIRTIO_NET_F_GSO			(1ULL<<6)
    #define VIRTIO_NET_F_GUEST_TSO4			(1ULL<<7)
    #define VIRTIO_NET_F_GUEST_TSO6			(1ULL<<8)
    #define VIRTIO_NET_F_GUEST_ECN			(1ULL<<9)
    #define VIRTIO_NET_F_GUEST_UFO			(1ULL<<10)
    #define VIRTIO_NET_F_HOST_TSO4			(1ULL<<11)
    #define VIRTIO_NET_F_HOST_TSO6			(1ULL<<12)
    #define VIRTIO_NET_F_HOST_ECN			(1ULL<<13)
    #define VIRTIO_NET_F_HOST_UFO			(1ULL<<14)
    #define VIRTIO_NET_F_MRG_RXBUF			(1ULL<<15)
    #define VIRTIO_NET_F_STATUS			(1ULL<<16)
    #define VIRTIO_NET_F_CTRL_VQ			(1ULL<<17)
    #define VIRTIO_NET_F_CTRL_RX			(1ULL<<18)
    #define VIRTIO_NET_F_CTRL_VLAN			(1ULL<<19)
    #define VIRTIO_NET_F_CTRL_RX_EXTRA		(1ULL<<20)
    #define VIRTIO_NET_F_GUEST_ANNOUNCE		(1ULL<<21)
    #define VIRTIO_NET_F_MQ				(1ULL<<22)
    #define VIRTIO_NET_F_CTRL_MAC_ADDR		(1ULL<<23)
    
    /*
     * 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[] = {
    #if VIRTIO_DEBUG
    	{ VIRTIO_NET_F_CSUM,			"CSum" },
    	{ VIRTIO_NET_F_GUEST_CSUM,		"GuestCSum" },
    	{ VIRTIO_NET_F_CTRL_GUEST_OFFLOADS,	"CtrlGuestOffl" },
    	{ VIRTIO_NET_F_MTU,			"MTU", },
    	{ 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" },
    	{ VIRTIO_NET_F_MQ,			"MQ" },
    	{ VIRTIO_NET_F_CTRL_MAC_ADDR,		"CtrlMAC" },
    #endif
    	{ 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_rx_dmamaps[i])
    			bus_dmamap_destroy(vsc->sc_dmat, sc->sc_rx_dmamaps[i]);
    	}
    	if (sc->sc_arrays) {
    		free(sc->sc_arrays, M_DEVBUF, allocsize);
    		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;
    	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;
    	vsc->sc_driver_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;
    
    	virtio_negotiate_features(vsc, virtio_net_feature_names);
    	if (virtio_has_feature(vsc, 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 (virtio_has_feature(vsc, VIRTIO_NET_F_MRG_RXBUF) ||
    	    vsc->sc_version_1) {
    		sc->sc_hdr_size = sizeof(struct virtio_net_hdr);
    	} else {
    		sc->sc_hdr_size = offsetof(struct virtio_net_hdr, num_buffers);
    	}
    	if (virtio_has_feature(vsc, VIRTIO_NET_F_MRG_RXBUF))
    		ifp->if_hardmtu = 16000; /* arbitrary limit */
    	else
    		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 (virtio_has_feature(vsc, VIRTIO_F_RING_EVENT_IDX))
    		virtio_postpone_intr_far(&sc->sc_vq[VQTX]);
    	else
    		virtio_stop_vq_intr(vsc, &sc->sc_vq[VQTX]);
    	if (virtio_has_feature(vsc, VIRTIO_NET_F_CTRL_VQ)
    	    && virtio_has_feature(vsc, 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 (virtio_has_feature(vsc, 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 (virtio_has_feature(vsc, 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_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 (virtio_has_feature(vsc, 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);
    	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) {
    		DPRINTF("%s: expected %d buffers, got %d\n", __func__,
    		    (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 (virtio_has_feature(vsc, 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);
    		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;
    	}
    
    	DPRINTF("%s: cmd %d %d: %d\n", __func__, cmd, (int)onoff, r);
    out:
    	vio_ctrl_wakeup(sc, FREE);
    	return r;
    }
    
    /*
     * XXXSMP As long as some per-ifp ioctl(2)s are executed with the
     * NET_LOCK() deadlocks are possible.  So release it here.
     */
    static inline int
    vio_sleep(struct vio_softc *sc, const char *wmesg)
    {
    	int status = rw_status(&netlock);
    
    	if (status != RW_WRITE && status != RW_READ)
    		return tsleep_nsec(&sc->sc_ctrl_inuse, PRIBIO|PCATCH, wmesg,
    		    INFSLP);
    
    	return rwsleep_nsec(&sc->sc_ctrl_inuse, &netlock, PRIBIO|PCATCH, wmesg,
    	    INFSLP);
    }
    
    int
    vio_wait_ctrl(struct vio_softc *sc)
    {
    	int r = 0;
    
    	while (sc->sc_ctrl_inuse != FREE) {
    		r = vio_sleep(sc, "viowait");
    		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 = vio_sleep(sc, "viodone");
    		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);
    }