/*	$NetBSD: if_virt.c,v 1.57 2018/06/26 06:48:03 msaitoh Exp $	*/

/*
 * Copyright (c) 2008, 2013 Antti Kantee.  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 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/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_virt.c,v 1.57 2018/06/26 06:48:03 msaitoh Exp $");

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/cprng.h>
#include <sys/module.h>

#include <net/bpf.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>

#include <netinet/in.h>
#include <netinet/in_var.h>

#include "if_virt.h"
#include "virtif_user.h"

/*
 * Virtual interface.  Uses hypercalls to shovel packets back
 * and forth.  The exact method for shoveling depends on the
 * hypercall implementation.
 */

static int	virtif_init(struct ifnet *);
static int	virtif_ioctl(struct ifnet *, u_long, void *);
static void	virtif_start(struct ifnet *);
static void	virtif_stop(struct ifnet *, int);

struct virtif_sc {
	struct ethercom sc_ec;
	struct virtif_user *sc_viu;

	int sc_num;
	char *sc_linkstr;
};

static int  virtif_clone(struct if_clone *, int);
static int  virtif_unclone(struct ifnet *);

struct if_clone VIF_CLONER =
    IF_CLONE_INITIALIZER(VIF_NAME, virtif_clone, virtif_unclone);

static int
virtif_create(struct ifnet *ifp)
{
	uint8_t enaddr[ETHER_ADDR_LEN] = { 0xb2, 0x0a, 0x00, 0x0b, 0x0e, 0x01 };
	char enaddrstr[3*ETHER_ADDR_LEN];
	struct virtif_sc *sc = ifp->if_softc;
	int error;

	if (sc->sc_viu)
		panic("%s: already created", ifp->if_xname);

	enaddr[2] = cprng_fast32() & 0xff;
	enaddr[5] = sc->sc_num & 0xff;

	if ((error = VIFHYPER_CREATE(sc->sc_linkstr,
	    sc, enaddr, &sc->sc_viu)) != 0) {
		printf("VIFHYPER_CREATE failed: %d\n", error);
		return error;
	}

	ether_ifattach(ifp, enaddr);
	ether_snprintf(enaddrstr, sizeof(enaddrstr), enaddr);
	aprint_normal_ifnet(ifp, "Ethernet address %s\n", enaddrstr);

	IFQ_SET_READY(&ifp->if_snd);

	return 0;
}

static int
virtif_clone(struct if_clone *ifc, int num)
{
	struct virtif_sc *sc;
	struct ifnet *ifp;
	int error = 0;

	sc = kmem_zalloc(sizeof(*sc), KM_SLEEP);
	sc->sc_num = num;
	ifp = &sc->sc_ec.ec_if;

	if_initname(ifp, VIF_NAME, num);
	ifp->if_softc = sc;

	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
	ifp->if_init = virtif_init;
	ifp->if_ioctl = virtif_ioctl;
	ifp->if_start = virtif_start;
	ifp->if_stop = virtif_stop;
	ifp->if_mtu = ETHERMTU;
	ifp->if_dlt = DLT_EN10MB;

	error = if_initialize(ifp);
	if (error != 0) {
		aprint_error("%s: if_initialize failed(%d)\n", ifp->if_xname,
		    error);
		goto fail_1;
	}

	if_register(ifp);

#ifndef RUMP_VIF_LINKSTR
	/*
	 * if the underlying interface does not expect linkstr, we can
	 * create everything now.  Otherwise, we need to wait for
	 * SIOCSLINKSTR.
	 */
#define LINKSTRNUMLEN 16
	sc->sc_linkstr = kmem_alloc(LINKSTRNUMLEN, KM_SLEEP);
	if (sc->sc_linkstr == NULL) {
		error = ENOMEM;
		goto fail_2;
	}
	snprintf(sc->sc_linkstr, LINKSTRNUMLEN, "%d", sc->sc_num);
	error = virtif_create(ifp);
	if (error) {
fail_2:
		if_detach(ifp);
		if (sc->sc_linkstr != NULL)
			kmem_free(sc->sc_linkstr, LINKSTRNUMLEN);
#undef LINKSTRNUMLEN
fail_1:
		kmem_free(sc, sizeof(*sc));
		ifp->if_softc = NULL;
	}
#endif /* !RUMP_VIF_LINKSTR */

	return error;
}

static int
virtif_unclone(struct ifnet *ifp)
{
	struct virtif_sc *sc = ifp->if_softc;
	int rv;

	if (ifp->if_flags & IFF_UP)
		return EBUSY;

	if ((rv = VIFHYPER_DYING(sc->sc_viu)) != 0)
		return rv;

	virtif_stop(ifp, 1);
	if_down(ifp);

	VIFHYPER_DESTROY(sc->sc_viu);

	kmem_free(sc, sizeof(*sc));

	ether_ifdetach(ifp);
	if_detach(ifp);

	return 0;
}

static int
virtif_init(struct ifnet *ifp)
{
	struct virtif_sc *sc = ifp->if_softc;

	if (sc->sc_viu == NULL)
		return ENXIO;

	ifp->if_flags |= IFF_RUNNING;
	return 0;
}

static int
virtif_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
	struct virtif_sc *sc = ifp->if_softc;
	int rv;

	switch (cmd) {
#ifdef RUMP_VIF_LINKSTR
	struct ifdrv *ifd;
	size_t linkstrlen;

#ifndef RUMP_VIF_LINKSTRMAX
#define RUMP_VIF_LINKSTRMAX 4096
#endif

	case SIOCGLINKSTR:
		ifd = data;

		if (!sc->sc_linkstr) {
			rv = ENOENT;
			break;
		}
		linkstrlen = strlen(sc->sc_linkstr)+1;

		if (ifd->ifd_cmd == IFLINKSTR_QUERYLEN) {
			ifd->ifd_len = linkstrlen;
			rv = 0;
			break;
		}
		if (ifd->ifd_cmd != 0) {
			rv = ENOTTY;
			break;
		}

		rv = copyoutstr(sc->sc_linkstr,
		    ifd->ifd_data, MIN(ifd->ifd_len,linkstrlen), NULL);
		break;
	case SIOCSLINKSTR:
		if (ifp->if_flags & IFF_UP) {
			rv = EBUSY;
			break;
		}

		ifd = data;

		if (ifd->ifd_cmd == IFLINKSTR_UNSET) {
			panic("unset linkstr not implemented");
		} else if (ifd->ifd_cmd != 0) {
			rv = ENOTTY;
			break;
		} else if (sc->sc_linkstr) {
			rv = EBUSY;
			break;
		}

		if (ifd->ifd_len > RUMP_VIF_LINKSTRMAX) {
			rv = E2BIG;
			break;
		} else if (ifd->ifd_len < 1) {
			rv = EINVAL;
			break;
		}


		sc->sc_linkstr = kmem_alloc(ifd->ifd_len, KM_SLEEP);
		rv = copyinstr(ifd->ifd_data, sc->sc_linkstr,
		    ifd->ifd_len, NULL);
		if (rv) {
			kmem_free(sc->sc_linkstr, ifd->ifd_len);
			break;
		}

		rv = virtif_create(ifp);
		if (rv) {
			kmem_free(sc->sc_linkstr, ifd->ifd_len);
		}
		break;
#endif /* RUMP_VIF_LINKSTR */
	default:
		if (!sc->sc_linkstr)
			rv = ENXIO;
		else
			rv = ether_ioctl(ifp, cmd, data);
		if (rv == ENETRESET)
			rv = 0;
		break;
	}

	return rv;
}

/*
 * Output packets in-context until outgoing queue is empty.
 * Leave responsibility of choosing whether or not to drop the
 * kernel lock to VIPHYPER_SEND().
 */
#define LB_SH 32
static void
virtif_start(struct ifnet *ifp)
{
	struct virtif_sc *sc = ifp->if_softc;
	struct mbuf *m, *m0;
	struct iovec io[LB_SH];
	int i;

	ifp->if_flags |= IFF_OACTIVE;

	for (;;) {
		IF_DEQUEUE(&ifp->if_snd, m0);
		if (!m0) {
			break;
		}

		m = m0;
		for (i = 0; i < LB_SH && m; ) {
			if (m->m_len) {
				io[i].iov_base = mtod(m, void *);
				io[i].iov_len = m->m_len;
				i++;
			}
			m = m->m_next;
		}
		if (i == LB_SH && m)
			panic("lazy bum");
		bpf_mtap(ifp, m0, BPF_D_OUT);

		VIFHYPER_SEND(sc->sc_viu, io, i);

		m_freem(m0);
		ifp->if_opackets++;
	}

	ifp->if_flags &= ~IFF_OACTIVE;
}

static void
virtif_stop(struct ifnet *ifp, int disable)
{

	/* XXX: VIFHYPER_STOP() */

	ifp->if_flags &= ~IFF_RUNNING;
}

void
VIF_DELIVERPKT(struct virtif_sc *sc, struct iovec *iov, size_t iovlen)
{
	struct ifnet *ifp = &sc->sc_ec.ec_if;
	struct ether_header *eth;
	struct mbuf *m;
	size_t i;
	int off, olen;
	bool passup;
	const int align
	    = ALIGN(sizeof(struct ether_header)) - sizeof(struct ether_header);

	if ((ifp->if_flags & IFF_RUNNING) == 0)
		return;

	m = m_gethdr(M_NOWAIT, MT_DATA);
	if (m == NULL)
		return; /* drop packet */
	m->m_len = m->m_pkthdr.len = 0;

	for (i = 0, off = align; i < iovlen; i++) {
		olen = m->m_pkthdr.len;
		m_copyback(m, off, iov[i].iov_len, iov[i].iov_base);
		off += iov[i].iov_len;
		if (olen + off != m->m_pkthdr.len) {
			aprint_verbose_ifnet(ifp, "m_copyback failed\n");
			m_freem(m);
			return;
		}
	}
	m->m_data += align;
	m->m_pkthdr.len -= align;
	m->m_len -= align;

	eth = mtod(m, struct ether_header *);
	if (memcmp(eth->ether_dhost, CLLADDR(ifp->if_sadl),
	    ETHER_ADDR_LEN) == 0) {
		passup = true;
	} else if (ETHER_IS_MULTICAST(eth->ether_dhost)) {
		passup = true;
	} else if (ifp->if_flags & IFF_PROMISC) {
		m->m_flags |= M_PROMISC;
		passup = true;
	} else {
		passup = false;
	}

	if (passup) {
		int bound;
		m_set_rcvif(m, ifp);
		KERNEL_LOCK(1, NULL);
		/* Prevent LWP migrations between CPUs for psref(9) */
		bound = curlwp_bind();
		if_input(ifp, m);
		curlwp_bindx(bound);
		KERNEL_UNLOCK_LAST(NULL);
	} else {
		m_freem(m);
	}
	m = NULL;
}

/*
 * The following ensures that no two modules using if_virt end up with
 * the same module name.  MODULE() and modcmd wrapped in ... bad mojo.
 */
#define VIF_MOJO(x) MODULE(MODULE_CLASS_DRIVER,x,NULL);
#define VIF_MODULE() VIF_MOJO(VIF_BASENAME(if_virt_,VIRTIF_BASE))
#define VIF_MODCMD VIF_BASENAME3(if_virt_,VIRTIF_BASE,_modcmd)
VIF_MODULE();
static int
VIF_MODCMD(modcmd_t cmd, void *opaque)
{
	int error = 0;

	switch (cmd) {
	case MODULE_CMD_INIT:
		if_clone_attach(&VIF_CLONER);
		break;
	case MODULE_CMD_FINI:
		/*
		 * not sure if interfaces are refcounted
		 * and properly protected
		 */
#if 0
		if_clone_detach(&VIF_CLONER);
#else
		error = ENOTTY;
#endif
		break;
	default:
		error = ENOTTY;
	}
	return error;
}
