/*-
 * Copyright (c) 2003
 *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
 * 	All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Author: Harti Brandt <harti@freebsd.org>
 *
 * HARP pseudo-driver. This driver when loaded attaches to all ngATM drivers
 * in the system and creates a HARP physical interface for each of them.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/dev/harp/if_harp.c,v 1.7 2005/06/10 16:49:09 brooks Exp $");

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/dev/harp/if_harp.c,v 1.7 2005/06/10 16:49:09 brooks Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/module.h>
#include <sys/queue.h>
#include <sys/syslog.h>

#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>

#include <net/if.h>
#include <net/if_var.h>
#include <net/if_types.h>
#include <net/if_media.h>
#include <net/netisr.h>

#include <netatm/port.h>
#include <netatm/queue.h>
#include <netatm/atm.h>
#include <netatm/atm_sys.h>
#include <netatm/atm_sap.h>
#include <netatm/atm_cm.h>
#include <netatm/atm_if.h>
#include <netatm/atm_stack.h>
#include <netatm/atm_pcb.h>
#include <netatm/atm_var.h>
#include <netatm/atm_vc.h>

#include <net/if_atm.h>

#define HARP_MTU	9188

/*
 * Physical interface softc
 */
struct harp_softc {
	Cmn_unit	cmn;
	struct ifnet	*parent;
	LIST_ENTRY(harp_softc) link;
};

struct harp_vcc {
	struct cmn_vcc	cmn;
};

MODULE_VERSION(harp, 1);
MODULE_DEPEND(harp, atm, 1, 1, 1);

/* hooks from if_atmsubr.c */
extern void (*atm_harp_input_p)(struct ifnet *ifp, struct mbuf **m,
    struct atm_pseudohdr *ah, void *rxhand);
extern void (*atm_harp_attach_p)(struct ifnet *);
extern void (*atm_harp_detach_p)(struct ifnet *);

static MALLOC_DEFINE(M_HARP, "harp", "Harp pseudo interface");

static uma_zone_t harp_nif_zone;
static uma_zone_t harp_vcc_zone;

/* List of all existing 'harp' interfaces */
static LIST_HEAD(, harp_softc) harp_softc_list =
    LIST_HEAD_INITIALIZER(harp_softc_list);

static struct stack_defn harp_svaal5 = {
	NULL,
	SAP_CPCS_AAL5,
	SDF_TERM,
	atm_dev_inst,
	atm_dev_lower,
	NULL,
	0,
};

static struct stack_defn *harp_services = &harp_svaal5;

/*
 * Map between constants
 */
static const struct {
	u_int	vendor;
	u_int	api;
	u_int	dev;
} map_devs[] = {
	[ATM_DEVICE_UNKNOWN] =
		{ VENDOR_UNKNOWN, VENDAPI_UNKNOWN, DEV_UNKNOWN },
	[ATM_DEVICE_PCA200E] =
		{ VENDOR_FORE, VENDAPI_FORE_1, DEV_FORE_PCA200E },
	[ATM_DEVICE_HE155] =
		{ VENDOR_FORE, VENDAPI_FORE_2, DEV_FORE_HE155 },
	[ATM_DEVICE_HE622] =
		{ VENDOR_FORE, VENDAPI_FORE_2, DEV_FORE_HE622 },
	[ATM_DEVICE_ENI155P] =
		{ VENDOR_ENI, VENDAPI_ENI_1, DEV_ENI_155P },
	[ATM_DEVICE_ADP155P] =
		{ VENDOR_ENI, VENDAPI_ENI_1, DEV_ENI_155P },
	[ATM_DEVICE_FORELE25] =
		{ VENDOR_FORE, VENDAPI_IDT_1, DEV_FORE_LE25 },
	[ATM_DEVICE_FORELE155] =
		{ VENDOR_FORE, VENDAPI_IDT_1, DEV_FORE_LE155 },
	[ATM_DEVICE_NICSTAR25] =
		{ VENDOR_IDT, VENDAPI_IDT_1, DEV_IDT_25 },
	[ATM_DEVICE_NICSTAR155] =
		{ VENDOR_IDT, VENDAPI_IDT_1, DEV_IDT_155 },
	[ATM_DEVICE_IDTABR25] =
		{ VENDOR_IDT, VENDAPI_IDT_2, DEV_IDTABR_25 },
	[ATM_DEVICE_IDTABR155] =
		{ VENDOR_IDT, VENDAPI_IDT_2, DEV_IDTABR_155 },
	[ATM_DEVICE_PROATM25] =
		{ VENDOR_PROSUM, VENDAPI_IDT_2, DEV_PROATM_25 },
	[ATM_DEVICE_PROATM155] =
		{ VENDOR_PROSUM, VENDAPI_IDT_2, DEV_PROATM_155 },
};

/*
 * Return zero if this interface is ok for us.
 * XXX This should go away when we have full ngATM-ified the en driver.
 */
static int
harp_check_if(const struct ifnet *ifp)
{
	if (ifp->if_type == IFT_ATM && strcmp(ifp->if_dname, "en"))
		return (0);
	else
		return (-1);
}

/*
 * Instantiate a VCC stack.
 *
 * Could check for correct attributes here.
 */
static int
harp_instvcc(Cmn_unit *up, Cmn_vcc *vp)
{
	struct harp_softc *sc;

	if (up == NULL || vp == NULL || vp->cv_connvc == NULL)
		return (EINVAL);

	sc = (struct harp_softc *)up;

	return (0);
}

/*
 * Open a VCC.
 */
static int
harp_openvcc(Cmn_unit *up, Cmn_vcc *vp)
{
	struct harp_softc *sc;
	struct atmio_openvcc data;
	Atm_attributes *attrib;
	struct vccb *vccinf;
	const struct ifatm_mib *mib;
	int err;

	if (up == NULL || vp == NULL || vp->cv_connvc == NULL)
		return (EINVAL);

	sc = (struct harp_softc *)up;
	mib = sc->parent->if_linkmib;

	attrib = &vp->cv_connvc->cvc_attr;
	vccinf = vp->cv_connvc->cvc_vcc;

	if (attrib == NULL || vccinf == NULL)
		return (EINVAL);

	if (vccinf->vc_vpi >= (1 << mib->vpi_bits) ||
	    vccinf->vc_vci >= (1 << mib->vci_bits))
		return (EINVAL);

	memset(&data, 0, sizeof(data));

	switch (attrib->aal.type) {

	  case ATM_AAL0:
		data.param.aal = ATMIO_AAL_0;
		break;

	  case ATM_AAL5:
		data.param.aal = ATMIO_AAL_5;
		break;

	  default:
		return (EINVAL);
	}
	data.param.vpi = vccinf->vc_vpi;
	data.param.vci = vccinf->vc_vci;
	data.param.rmtu = HARP_MTU;
	data.param.tmtu = HARP_MTU;

	switch (attrib->bearer.v.bearer_class) {

	  case T_ATM_CLASS_C:
		data.param.traffic = ATMIO_TRAFFIC_VBR;
		break;

	  case T_ATM_CLASS_X:
		switch (attrib->bearer.v.traffic_type) {

		  case T_ATM_CBR:
			data.param.traffic = ATMIO_TRAFFIC_CBR;
			break;

		  case T_ATM_VBR:
			data.param.traffic = ATMIO_TRAFFIC_VBR;
			break;

		  case T_ATM_ABR:
			/* not really supported by HARP */
			return (EINVAL);

		  default:
		  case T_ATM_UBR:
			data.param.traffic = ATMIO_TRAFFIC_UBR;
			break;
		}
		break;

	  default:
		return (EINVAL);
	}
	data.param.tparam.pcr = attrib->traffic.v.forward.PCR_all_traffic;
	data.param.tparam.scr = attrib->traffic.v.forward.SCR_all_traffic;
	data.param.tparam.mbs = attrib->traffic.v.forward.MBS_all_traffic;

	data.rxhand = sc;
	data.param.flags = ATMIO_FLAG_HARP;

	err = (*sc->parent->if_ioctl)(sc->parent, SIOCATMOPENVCC,
	    (caddr_t)&data);

	return (err);
}

/*
 * Close VCC
 */
static int
harp_closevcc(Cmn_unit *up, Cmn_vcc *vp)
{
	struct harp_softc *sc;
	struct atmio_closevcc data;
	int err;

	if (vp == NULL || vp->cv_connvc == NULL ||
	    vp->cv_connvc->cvc_vcc == NULL)
		return (EINVAL);

	sc = (struct harp_softc *)up;
	
	data.vpi = vp->cv_connvc->cvc_vcc->vc_vpi;
	data.vci = vp->cv_connvc->cvc_vcc->vc_vci;

	err = (*sc->parent->if_ioctl)(sc->parent, SIOCATMCLOSEVCC,
	    (caddr_t)&data);

	return (err);
}

/*
 * IOCTLs
 */
static int
harp_ioctl(int code, caddr_t addr, caddr_t arg)
{
	return (ENOSYS);
}

/*
 * Output data
 */
static void
harp_output(Cmn_unit *cu, Cmn_vcc *cv, KBuffer *m)
{
	struct harp_softc *sc = (struct harp_softc *)cu;
	struct atm_pseudohdr *aph;
	int error;
	int mlen;

	if (cv == NULL || cv->cv_connvc == NULL ||
	    cv->cv_connvc->cvc_vcc == NULL) {
		m_freem(m);
		return;
	}
	M_ASSERTPKTHDR(m);

	/*
	 * Harp seems very broken with regard to mbuf handling. The length
	 * in the packet header is mostly broken here so recompute it.
	 */
	m->m_pkthdr.len = mlen = m_length(m, NULL);

	/*
	 * Prepend pseudo-hdr. Drivers don't care about the flags.
	 */
	M_PREPEND(m, sizeof(*aph), M_DONTWAIT);
	if (m == NULL)
		return;

	aph = mtod(m, struct atm_pseudohdr *);
	ATM_PH_VPI(aph) = cv->cv_connvc->cvc_vcc->vc_vpi;
	ATM_PH_SETVCI(aph, cv->cv_connvc->cvc_vcc->vc_vci);
	ATM_PH_FLAGS(aph) = 0;

	error = atm_output(sc->parent, m, NULL, NULL);

	if (error) {
		printf("%s: error %d\n", __func__, error);
		sc->cmn.cu_pif.pif_oerrors++;
		cv->cv_connvc->cvc_vcc->vc_oerrors++;
		if (cv->cv_connvc->cvc_vcc->vc_nif)
			ANIF2IFP(cv->cv_connvc->cvc_vcc->vc_nif)->if_oerrors++;
		return;
	}

	/* statistics */
	sc->cmn.cu_pif.pif_opdus++;
	sc->cmn.cu_pif.pif_obytes += mlen;
	cv->cv_connvc->cvc_vcc->vc_opdus++;
	cv->cv_connvc->cvc_vcc->vc_obytes += mlen;
	if (cv->cv_connvc->cvc_vcc->vc_nif) {
		cv->cv_connvc->cvc_vcc->vc_nif->nif_obytes += mlen;
		ANIF2IFP(cv->cv_connvc->cvc_vcc->vc_nif)->if_obytes += mlen;
		ANIF2IFP(cv->cv_connvc->cvc_vcc->vc_nif)->if_opackets++;
	}
}

/*
 * Attach a new interface
 */
static void
harp_attach(struct ifnet *parent)
{
	struct harp_softc *sc;
	const struct ifatm_mib *mib;
	int error;

	if (harp_check_if(parent) != 0)
		return;

	sc = malloc(sizeof(*sc), M_HARP, M_WAITOK | M_ZERO);

	sc->parent = parent;
	sc->cmn.cu_unit = parent->if_dunit;
	sc->cmn.cu_mtu = HARP_MTU;
	sc->cmn.cu_ioctl = harp_ioctl;
	sc->cmn.cu_instvcc = harp_instvcc;
	sc->cmn.cu_openvcc = harp_openvcc;
	sc->cmn.cu_closevcc = harp_closevcc;
	sc->cmn.cu_output = harp_output;
	sc->cmn.cu_vcc_zone = harp_vcc_zone;
	sc->cmn.cu_nif_zone = harp_nif_zone;
	sc->cmn.cu_softc = sc;

	/* config */
	mib = parent->if_linkmib;
	if (mib->device >= sizeof(map_devs) / sizeof(map_devs[0])) {
		sc->cmn.cu_config.ac_vendor = VENDOR_UNKNOWN;
		sc->cmn.cu_config.ac_vendapi = VENDAPI_UNKNOWN;
		sc->cmn.cu_config.ac_device = DEV_UNKNOWN;
	} else {
		sc->cmn.cu_config.ac_vendor = map_devs[mib->device].vendor;
		sc->cmn.cu_config.ac_vendapi = map_devs[mib->device].api;
		sc->cmn.cu_config.ac_device = map_devs[mib->device].dev;
	}

	switch (mib->media) {

	  case IFM_ATM_UTP_25:
		sc->cmn.cu_config.ac_media = MEDIA_UTP25;;
		break;

	  case IFM_ATM_TAXI_100:
		sc->cmn.cu_config.ac_media = MEDIA_TAXI_100;
		break;

	  case IFM_ATM_TAXI_140:
		sc->cmn.cu_config.ac_media = MEDIA_TAXI_140;
		break;

	  case IFM_ATM_MM_155:
	  case IFM_ATM_SM_155:
		sc->cmn.cu_config.ac_media = MEDIA_OC3C;
		break;

	  case IFM_ATM_MM_622:
	  case IFM_ATM_SM_622:
		sc->cmn.cu_config.ac_media = MEDIA_OC12C;
		break;

	  case IFM_ATM_UTP_155:
		sc->cmn.cu_config.ac_media = MEDIA_UTP155;
		break;

	  default:
		sc->cmn.cu_config.ac_media = MEDIA_UNKNOWN;
		break;
	}
	sc->cmn.cu_config.ac_bustype = BUS_PCI;
	sc->cmn.cu_pif.pif_pcr = mib->pcr;
	sc->cmn.cu_pif.pif_maxvpi = (1 << mib->vpi_bits) - 1;
	sc->cmn.cu_pif.pif_maxvci = (1 << mib->vci_bits) - 1;

	snprintf(sc->cmn.cu_config.ac_hard_vers,
	    sizeof(sc->cmn.cu_config.ac_hard_vers), "0x%lx",
	    (u_long)mib->hw_version);
	snprintf(sc->cmn.cu_config.ac_firm_vers,
	    sizeof(sc->cmn.cu_config.ac_firm_vers), "0x%lx",
	    (u_long)mib->sw_version);
	sc->cmn.cu_config.ac_serial = mib->serial;
	sc->cmn.cu_config.ac_ram = 0;
	sc->cmn.cu_config.ac_ramsize = 0;

	sc->cmn.cu_config.ac_macaddr.ma_data[0] =
	    sc->cmn.cu_pif.pif_macaddr.ma_data[0] = mib->esi[0];
	sc->cmn.cu_config.ac_macaddr.ma_data[1] =
	    sc->cmn.cu_pif.pif_macaddr.ma_data[1] = mib->esi[1];
	sc->cmn.cu_config.ac_macaddr.ma_data[2] =
	    sc->cmn.cu_pif.pif_macaddr.ma_data[2] = mib->esi[2];
	sc->cmn.cu_config.ac_macaddr.ma_data[3] =
	    sc->cmn.cu_pif.pif_macaddr.ma_data[3] = mib->esi[3];
	sc->cmn.cu_config.ac_macaddr.ma_data[4] =
	    sc->cmn.cu_pif.pif_macaddr.ma_data[4] = mib->esi[4];
	sc->cmn.cu_config.ac_macaddr.ma_data[5] =
	    sc->cmn.cu_pif.pif_macaddr.ma_data[5] = mib->esi[5];

	error = atm_physif_register(&sc->cmn, parent->if_dname, harp_services);
	if (error) {
		log(LOG_ERR, "%s: pif registration failed %d\n",
		    parent->if_dname, error);
		free(sc, M_HARP);
		return;
	}
	LIST_INSERT_HEAD(&harp_softc_list, sc, link);

	sc->cmn.cu_flags |= CUF_INITED;
}

/*
 * Destroy a cloned device
 */
static void
harp_detach(struct ifnet *ifp)
{
	struct harp_softc *sc;
	int error;

	LIST_FOREACH(sc, &harp_softc_list, link)
		if (sc->parent == ifp)
			break;
	if (sc == NULL)
		return;

	error = atm_physif_deregister(&sc->cmn);
	if (error)
		log(LOG_ERR, "%s: de-registration failed %d\n", ifp->if_dname,
		    error);

	LIST_REMOVE(sc, link);

	free(sc, M_HARP);
}

/*
 * Pass PDU up the stack
 */
static void
harp_recv_stack(void *tok, KBuffer *m)
{
	Cmn_vcc *vcc = tok;
	int err;

	M_ASSERTPKTHDR(m);
	STACK_CALL(CPCS_UNITDATA_SIG, vcc->cv_upper, vcc->cv_toku,
	    vcc->cv_connvc, (intptr_t)m, 0, err);
	if (err) {
		printf("%s: error %d\n", __func__, err);
		KB_FREEALL(m);
	}
}

/*
 * Possible input from NATM
 */
static void
harp_input(struct ifnet *ifp, struct mbuf **mp, struct atm_pseudohdr *ah,
    void *rxhand)
{
	struct harp_softc *sc = rxhand;
	Cmn_vcc *vcc;
	char *cp;
	u_int pfxlen;
	struct mbuf *m, *m0;
	int mlen;

	if ((ATM_PH_FLAGS(ah) & ATMIO_FLAG_HARP) == 0)
		return;

	/* grab the packet */
	m = *mp;
	*mp = NULL;

	if (sc->parent != ifp) {
		printf("%s: parent=%p ifp=%p\n", __func__, sc->parent, ifp);
		goto drop;
	}

	vcc = atm_dev_vcc_find(&sc->cmn, ATM_PH_VPI(ah),
	    ATM_PH_VCI(ah), VCC_IN);
	if (vcc == NULL) {
		printf("%s: VCC %u/%u not found\n", __func__,ATM_PH_VPI(ah),
		    ATM_PH_VCI(ah));
		goto drop;
	}

	/* fit two pointers into the mbuf - assume, that the the data is
	 * pointer aligned. If it doesn't fit into the first mbuf, prepend
	 * another one.
	 * Don't count the new fields in the packet length (XXX)
	 */
	mlen = m->m_pkthdr.len;
	pfxlen = sizeof(atm_intr_func_t) + sizeof(void *);
	if (M_LEADINGSPACE(m) < pfxlen) {
		MGETHDR(m0, 0, MT_DATA);
		if (m0 == NULL) {
			printf("%s: no leading space in buffer\n", __func__);
			goto drop;
		}
		m0->m_len = 0;
		m0->m_next = m;

		M_MOVE_PKTHDR(m0, m);

		m = m0;
	}
	m->m_len += pfxlen;
	m->m_data -= pfxlen;
	cp = mtod(m, char *);
	*((atm_intr_func_t *)cp) = harp_recv_stack;
	cp += sizeof(atm_intr_func_t);
	*((void **)cp) = (void *)vcc;

	/* count the packet */
	sc->cmn.cu_pif.pif_ipdus++;
	sc->cmn.cu_pif.pif_ibytes += mlen;
	vcc->cv_connvc->cvc_vcc->vc_ipdus++;
	vcc->cv_connvc->cvc_vcc->vc_ibytes += mlen;
	if (vcc->cv_connvc->cvc_vcc->vc_nif) {
		vcc->cv_connvc->cvc_vcc->vc_nif->nif_ibytes += mlen;
		ANIF2IFP(vcc->cv_connvc->cvc_vcc->vc_nif)->if_ipackets++;
		ANIF2IFP(vcc->cv_connvc->cvc_vcc->vc_nif)->if_ibytes += mlen;
	}

	/* hand it off */
	netisr_dispatch(NETISR_ATM, m);
	return;

  drop:
	m_freem(m);
}

/*
 * Module loading/unloading
 */
static int
harp_modevent(module_t mod, int event, void *data)
{
	struct ifnet *ifp;

	switch (event) {

	  case MOD_LOAD:
		harp_nif_zone = uma_zcreate("harp nif", sizeof(struct atm_nif),
		    NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
		if (harp_nif_zone == NULL)
			panic("%s: nif_zone", __func__);

		harp_vcc_zone = uma_zcreate("harp vcc", sizeof(struct harp_vcc),
		    NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
		if (harp_vcc_zone == NULL)
			panic("%s: vcc_zone", __func__);

		/* Create harp interfaces for all existing ATM interfaces */
		TAILQ_FOREACH(ifp, &ifnet, if_link)
			harp_attach(ifp);

		atm_harp_attach_p = harp_attach;
		atm_harp_detach_p = harp_detach;
		atm_harp_input_p = harp_input;
		break;

	  case MOD_UNLOAD:
		atm_harp_attach_p = NULL;
		atm_harp_detach_p = NULL;
		atm_harp_input_p = NULL;

		while (!LIST_EMPTY(&harp_softc_list))
			harp_detach(LIST_FIRST(&harp_softc_list)->parent);

		uma_zdestroy(harp_nif_zone);
		uma_zdestroy(harp_vcc_zone);

		break;
	  default:
		return (EOPNOTSUPP);
	}
	return (0);
}

static moduledata_t harp_mod = {
	"if_harp",
	harp_modevent,
	0
};

DECLARE_MODULE(harp, harp_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
