root/sys/arch/arm/gemini/if_gpn.c
/* $NetBSD: if_gpn.c,v 1.18 2024/06/29 12:11:10 riastradh Exp $ */
/*-
 * Copyright (c) 2008 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Matt Thomas <matt@3am-software.com>
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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>

#include "opt_gemini.h"

__KERNEL_RCSID(0, "$NetBSD: if_gpn.c,v 1.18 2024/06/29 12:11:10 riastradh Exp $");

#include <sys/param.h>
#include <sys/device.h>
#include <sys/mbuf.h>
#include <sys/ioctl.h>

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

#include <sys/bus.h>

#include <arm/gemini/gemini_var.h>
#include <arm/gemini/gemini_ipm.h>

#define GPN_MOF         0x00    /* Middle Of Frame */
#define GPN_SOF         0x01    /* Start of Frame */
#define GPN_EOF         0x02    /* End of Frame */
#define GPN_FRAME       0x03    /* Complete Frame */

#define GPN_IFUP        0x05    /* partner is up */
#define GPN_IFDOWN      0x06    /* partner is down */

#define GPN_ACK0        0x10    /* placeholder */
#define GPN_ACK1        0x11    /* Ack 1 descriptor */
#define GPN_ACK2        0x12    /* Ack 2 descriptors */
#define GPN_ACK3        0x13    /* Ack 3 descriptors */
#define GPN_ACK4        0x14    /* Ack 4 descriptors */
#define GPN_ACK5        0x15    /* Ack 5 descriptors */
#define GPN_ACK6        0x16    /* Ack 6 descriptors */
#define GPN_ACK7        0x17    /* Ack 7 descriptors */
#define GPN_ACK8        0x18    /* Ack 8 descriptors */
#define GPN_ACK9        0x19    /* Ack 9 descriptors */
#define GPN_ACK10       0x1a    /* Ack 10 descriptors */
#define GPN_ACK11       0x1b    /* Ack 11 descriptors */
#define GPN_ACK12       0x1c    /* Ack 12 descriptors */
#define GPN_ACK13       0x1d    /* Ack 13 descriptors */
#define GPN_ACK14       0x1e    /* Ack 14 descriptors */

typedef struct {
        uint8_t gd_tag;
        uint8_t gd_subtype;
        uint8_t gd_txid;
        uint8_t gd_pktlen64;
        uint16_t gd_len1;
        uint16_t gd_len2;
        uint32_t gd_addr1;
        uint32_t gd_addr2;
} ipm_gpn_desc_t;

typedef struct {
        uint8_t agd_tag;
        uint8_t agd_subtype;
        uint8_t agd_txids[14];
} ipm_gpn_ack_desc_t;

#define MAX_TXACTIVE    60

struct gpn_txinfo {
        struct mbuf *ti_mbuf;
        bus_dmamap_t ti_map;
};

struct gpn_softc {
        device_t sc_dev;
        bus_dma_tag_t sc_dmat;
        struct ifmedia sc_im;
        struct ethercom sc_ec;
#define sc_if sc_ec.ec_if
        size_t sc_free;
        size_t sc_txactive;
        bool sc_txbusy;
        void *sc_ih;
        ipm_gpn_ack_desc_t sc_ack_desc;
        struct mbuf *sc_rxmbuf;
        struct gpn_txinfo sc_txinfo[MAX_TXACTIVE];
        uint8_t sc_lastid;
        bool sc_remoteup;               /* remote side up? */
};

CTASSERT((GPN_SOF | GPN_EOF) == GPN_FRAME);
CTASSERT((GPN_SOF & GPN_EOF) == 0);

extern struct cfdriver gpn_cd;

static void gpn_ifstart(struct ifnet *);

#ifdef GPNDEBUG
static uint32_t
m_crc32_le(struct mbuf *m)
{
        static const uint32_t crctab[] = {
                0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
                0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
                0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
                0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
        };
        uint32_t crc;
        size_t i;

        crc = 0xffffffffU;      /* initial value */

        for (; m; m = m->m_next) {
                for (i = 0; i < m->m_len; i++) {
                        crc ^= m->m_data[i];
                        crc = (crc >> 4) ^ crctab[crc & 0xf];
                        crc = (crc >> 4) ^ crctab[crc & 0xf];
                }
        }

        return crc;
}
#endif

static void
gpn_free_dmamaps(struct gpn_softc *sc)
{
        struct gpn_txinfo *ti = sc->sc_txinfo;
        struct gpn_txinfo * const end_ti = ti + __arraycount(sc->sc_txinfo);

        for (; ti < end_ti; ti++) {
                if (ti->ti_map == NULL)
                        continue;
                bus_dmamap_destroy(sc->sc_dmat, ti->ti_map);
                ti->ti_map = NULL;
        }
}

static int
gpn_alloc_dmamaps(struct gpn_softc *sc)
{
        struct gpn_txinfo *ti = sc->sc_txinfo;
        struct gpn_txinfo * const end_ti = ti + __arraycount(sc->sc_txinfo);
        int error;

        for (error = 0; ti < end_ti; ti++) {
                if (ti->ti_map != NULL)
                        continue;
                error = bus_dmamap_create(sc->sc_dmat,
                    10000, 2, 8192, 0,
                    BUS_DMA_ALLOCNOW | BUS_DMA_WAITOK,
                    &ti->ti_map);
                if (error)
                        break;
        }

        if (error)
                gpn_free_dmamaps(sc);

        return error;
}

static bool
gpn_add_data(struct gpn_softc *sc, bus_addr_t addr, bus_size_t len)
{
        struct mbuf *m, *m0;
        size_t space;

        m = sc->sc_rxmbuf;
        KASSERT(m != NULL);

        m->m_pkthdr.len += len;

        while (m->m_next != NULL)
                m = m->m_next;

        KASSERT(len > 0);
        space = M_TRAILINGSPACE(m);
        for (;;) {
                if (space > 0) {
                        if (len < space)
                                space = len;
                        gemini_ipm_copyin(mtod(m, uint8_t *) + m->m_len, addr,
                            space);
                        len -= space;
                        m->m_len += space;
                        if (len == 0)
                                return true;
                        addr += space;
                }
                MGET(m0, M_DONTWAIT, MT_DATA);
                if (m0 == NULL)
                        break;
                space = MLEN;
                if (len > space) {
                        MCLGET(m0, M_DONTWAIT);
                        if (m0->m_flags & M_EXT)
                                space = MCLBYTES;
                }
                m->m_len = 0;
                m->m_next = m0;
                m = m0;
        }
        return false;
}

static void
gpn_ack_txid(struct gpn_softc *sc, unsigned int txid)
{
        ipm_gpn_ack_desc_t * const agd = &sc->sc_ack_desc;
        agd->agd_txids[agd->agd_subtype] = txid;
        if (++agd->agd_subtype == __arraycount(agd->agd_txids)) {
                agd->agd_subtype += GPN_ACK0;
                sc->sc_free--;
                gemini_ipm_produce(agd, 1);
                agd->agd_subtype = 0;
        }
}

static void
gpn_process_data(struct gpn_softc *sc, const ipm_gpn_desc_t *gd)
{
        struct ifnet * const ifp = &sc->sc_if;
        size_t pktlen = gd->gd_pktlen64 * 64;
        unsigned int subtype = gd->gd_subtype;
        bool ok;

        if ((subtype & GPN_SOF) == 0 && sc->sc_rxmbuf == NULL) {
                if_statinc(ifp, if_ierrors);
                goto out;
        }

        if ((subtype & GPN_SOF) && sc->sc_rxmbuf != NULL) {
                if_statinc(ifp, if_ierrors);
                m_freem(sc->sc_rxmbuf);
                sc->sc_rxmbuf = NULL;
        }

        if (sc->sc_rxmbuf == NULL) {
                struct mbuf *m;
                MGETHDR(m, M_DONTWAIT, MT_DATA);
                if (m == NULL) {
                        if_statinc(ifp, if_ierrors);
                        goto out;
                }
                if (pktlen > MHLEN - 2) {
                        MCLGET(m, M_DONTWAIT);
                        if ((m->m_flags & M_EXT) == 0) {
                                if_statinc(ifp, if_ierrors);
                                m_free(m);
                                goto out;
                        }
                }
                m->m_data += 2; /* makes ethernet payload 32bit aligned */
                m->m_len = 0;
                m->m_pkthdr.len = 0;
                sc->sc_rxmbuf = m;
        }

        ok = gpn_add_data(sc, gd->gd_addr1, gd->gd_len1);
        if (ok && gd->gd_addr2 && gd->gd_len2)
                ok = gpn_add_data(sc, gd->gd_addr2, gd->gd_len2);
        if (!ok) {
                if_statinc(ifp, if_ierrors);
                m_freem(sc->sc_rxmbuf);
                sc->sc_rxmbuf = NULL;
                goto out;
        }

        if (subtype & GPN_EOF) {
                struct mbuf *m;
                m = sc->sc_rxmbuf;
                sc->sc_rxmbuf = NULL;
                m_set_rcvif(m, ifp);
                KASSERT(((m->m_pkthdr.len + 63) >> 6) == gd->gd_pktlen64);
#ifdef GPNDEBUG
                printf("%s: rx len=%d crc=%#x\n", ifp->if_xname,
                    m->m_pkthdr.len, m_crc32_le(m));
#endif
                if_percpuq_enqueue(ifp->if_percpuq, m);
        }

out:
        gpn_ack_txid(sc, gd->gd_txid);
}

static void
gpn_free_txid(struct gpn_softc *sc, size_t txid)
{
        struct gpn_txinfo * const ti = sc->sc_txinfo + txid;

        KASSERT(txid < MAX_TXACTIVE);

        if (ti->ti_mbuf == NULL)
                return;

        bus_dmamap_sync(sc->sc_dmat, ti->ti_map,
            0, ti->ti_mbuf->m_len, BUS_DMASYNC_POSTREAD);
        bus_dmamap_unload(sc->sc_dmat, ti->ti_map);
        m_freem(ti->ti_mbuf);
        ti->ti_mbuf = NULL;
        sc->sc_txactive--;
        KASSERT(sc->sc_txactive < MAX_TXACTIVE);
        if (sc->sc_txbusy) {
                sc->sc_txbusy = false;
                gpn_ifstart(&sc->sc_if);
        }
}

static void
gpn_ipm_rebate(void *arg, size_t count)
{
        struct gpn_softc * const sc = arg;
        int s;

        s = splnet();
        sc->sc_free += count;

        sc->sc_txbusy = false;
        gpn_ifstart(&sc->sc_if);
        splx(s);
}

static void
gpn_ifstart(struct ifnet *ifp)
{
        struct gpn_softc * const sc = ifp->if_softc;

        for (;;) {
                struct mbuf *m, *m0;
                ipm_gpn_desc_t gd;
                ipm_gpn_desc_t *last_gd;
                size_t count;

                if (sc->sc_free == 0) {
                        sc->sc_txbusy = true;
                        break;
                }

                IF_POLL(&ifp->if_snd, m);
                if (!m)
                        break;

                if ((ifp->if_flags & IFF_UP) == 0) {
                        m_freem(m);
                        continue;
                }

                /*
                 * Make sure to send any pending acks first.
                 */
                if (sc->sc_ack_desc.agd_subtype) {
                        sc->sc_free--;
                        sc->sc_ack_desc.agd_subtype += GPN_ACK0;
                        gemini_ipm_produce(&sc->sc_ack_desc, 1);
                        sc->sc_ack_desc.agd_subtype = 0;
                }

                /*
                 * Let's find out how many mbufs we are using.
                 */
                for (m0 = m, count = 0; m0; m0 = m0->m_next) {
                        if (m0->m_len == 0)
                                continue;
                        count++;
                }

                /*
                 * Make sure there is always enough room.
                 */
                if (sc->sc_free < count
                    || sc->sc_txactive + count > MAX_TXACTIVE) {
                        sc->sc_txbusy = true;
                        return;
                }
                IF_DEQUEUE(&ifp->if_snd, m);

                bpf_mtap(ifp, m, BPF_D_OUT);
#ifdef GPNDEBUG
                printf("%s: tx len=%d crc=%#x\n", ifp->if_xname,
                    m->m_pkthdr.len, m_crc32_le(m));
#endif

                last_gd = NULL;
                gd.gd_tag = IPM_TAG_GPN;
                gd.gd_subtype = GPN_SOF;
                gd.gd_pktlen64 = (m->m_pkthdr.len + 63) >> 6;
                net_stat_ref_t nsr = IF_STAT_GETREF(ifp);
                for (; m != NULL; m = m0) {
                        struct gpn_txinfo *ti;
                        bus_dmamap_t map;
                        size_t id;
                        int error;

                        m0 = m->m_next;
                        m->m_next = NULL;
                        if (m->m_len == 0) {
                                m_free(m);
                                continue;
                        }
                        if (last_gd) {
                                sc->sc_txactive++;
                                sc->sc_free--;
                                gemini_ipm_produce(last_gd, 1);
                                last_gd = NULL;
                                gd.gd_subtype = GPN_MOF;
                        }
                        for (id = sc->sc_lastid;
                             sc->sc_txinfo[id].ti_mbuf != NULL;) {
                                if (++id == __arraycount(sc->sc_txinfo))
                                        id = 0;
                        }
                        KASSERT(id < MAX_TXACTIVE);
                        ti = sc->sc_txinfo + id;
                        map = ti->ti_map;
                        error = bus_dmamap_load(sc->sc_dmat, map,
                            mtod(m, void *), m->m_len, NULL,
                            BUS_DMA_READ | BUS_DMA_NOWAIT);
                        if (error) {
                                if_statinc_ref(ifp, nsr, if_oerrors);
                                m_freem(m);
                                break;
                        }
                        bus_dmamap_sync(sc->sc_dmat, map, 0,
                            m->m_len, BUS_DMASYNC_PREREAD);
                        KASSERT(map->dm_nsegs > 0);
                        KASSERT(map->dm_nsegs <= 2);
                        KASSERT(map->dm_segs[0].ds_addr != 0);
                        gd.gd_len1 = map->dm_segs[0].ds_len;
                        gd.gd_addr1 = map->dm_segs[0].ds_addr;
                        if (map->dm_nsegs == 1) {
                                gd.gd_len2 = 0;
                                gd.gd_addr2 = 0;
                        } else {
                                KASSERT(map->dm_segs[0].ds_addr != 0);
                                gd.gd_len2 = map->dm_segs[1].ds_len;
                                gd.gd_addr2 = map->dm_segs[1].ds_addr;
                        }

                        gd.gd_txid = id;
                        ti->ti_mbuf = m;
                        last_gd = &gd;
                        if_statadd_ref(ifp, nsr, if_obytes, m->m_len);
                }
                if_statinc_ref(ifp, nsr, if_opackets);
                IF_STAT_PUTREF(ifp);

                if (last_gd != NULL) {
                        last_gd->gd_subtype |= GPN_EOF;

                        sc->sc_txactive++;
                        sc->sc_free--;
                        gemini_ipm_produce(last_gd, 1);
                }
        }
}

static void
gpn_ipm_ifup(struct gpn_softc *sc)
{
        sc->sc_remoteup = true;
        if (sc->sc_if.if_flags & IFF_UP)
                ifmedia_set(&sc->sc_im, IFM_ETHER | IFM_1000_T | IFM_FDX);
}

static void
gpn_ipm_ifdown(struct gpn_softc *sc)
{
        struct gpn_txinfo *ti = sc->sc_txinfo;
        struct gpn_txinfo * const end_ti = ti + __arraycount(sc->sc_txinfo);

        if (sc->sc_rxmbuf) {
                m_freem(sc->sc_rxmbuf);
                sc->sc_rxmbuf = NULL;
        }

        IF_PURGE(&sc->sc_if.if_snd);

        for (; ti < end_ti; ti++) {
                if (ti->ti_mbuf == NULL)
                        continue;
                bus_dmamap_sync(sc->sc_dmat, ti->ti_map,
                    0, ti->ti_mbuf->m_len, BUS_DMASYNC_POSTREAD);
                bus_dmamap_unload(sc->sc_dmat, ti->ti_map);
                m_freem(ti->ti_mbuf);
                ti->ti_mbuf = NULL;
        }
        sc->sc_lastid = 0;
        ifmedia_set(&sc->sc_im, IFM_ETHER | IFM_NONE);
        sc->sc_remoteup = false;
}

static void
gpn_ipm_handler(void *arg, const void *desc)
{
        struct gpn_softc * const sc = arg;
        const ipm_gpn_desc_t * const gd = desc;
        const ipm_gpn_ack_desc_t * const agd = desc;
        int s;

        s = splnet();

        switch (gd->gd_subtype) {
        case GPN_ACK14: gpn_free_txid(sc, agd->agd_txids[13]); /* FALLTHROUGH */
        case GPN_ACK13: gpn_free_txid(sc, agd->agd_txids[12]); /* FALLTHROUGH */
        case GPN_ACK12: gpn_free_txid(sc, agd->agd_txids[11]); /* FALLTHROUGH */
        case GPN_ACK11: gpn_free_txid(sc, agd->agd_txids[10]); /* FALLTHROUGH */
        case GPN_ACK10: gpn_free_txid(sc, agd->agd_txids[9]); /* FALLTHROUGH */
        case GPN_ACK9: gpn_free_txid(sc, agd->agd_txids[8]); /* FALLTHROUGH */
        case GPN_ACK8: gpn_free_txid(sc, agd->agd_txids[7]); /* FALLTHROUGH */
        case GPN_ACK7: gpn_free_txid(sc, agd->agd_txids[6]); /* FALLTHROUGH */
        case GPN_ACK6: gpn_free_txid(sc, agd->agd_txids[5]); /* FALLTHROUGH */
        case GPN_ACK5: gpn_free_txid(sc, agd->agd_txids[4]); /* FALLTHROUGH */
        case GPN_ACK4: gpn_free_txid(sc, agd->agd_txids[3]); /* FALLTHROUGH */
        case GPN_ACK3: gpn_free_txid(sc, agd->agd_txids[2]); /* FALLTHROUGH */
        case GPN_ACK2: gpn_free_txid(sc, agd->agd_txids[1]); /* FALLTHROUGH */
        case GPN_ACK1: gpn_free_txid(sc, agd->agd_txids[0]); break;
        case GPN_MOF:
        case GPN_SOF:
        case GPN_FRAME:
        case GPN_EOF:
                gpn_process_data(sc, gd);
                break;
        case GPN_IFUP:
                gpn_ipm_ifup(sc);
                break;
        case GPN_IFDOWN:
                gpn_ipm_ifdown(sc);
                break;
        default:
                KASSERT(0);
        }

        splx(s);
}

static int
gpn_ifinit(struct ifnet *ifp)
{
        struct gpn_softc * const sc = ifp->if_softc;
        ipm_gpn_desc_t gd;
        int error;

        error = gpn_alloc_dmamaps(sc);
        if (error)
                return error;

        memset(&gd, 0, sizeof(gd));
        gd.gd_tag = IPM_TAG_GPN;
        gd.gd_subtype = GPN_IFUP;
        KASSERT(sc->sc_free > 0);
        sc->sc_free--;
        gemini_ipm_produce(&gd, 1);

        if (sc->sc_remoteup)
                ifmedia_set(&sc->sc_im, IFM_ETHER | IFM_1000_T | IFM_FDX);

        ifp->if_flags |= IFF_RUNNING;

        return error;
}

static void
gpn_ifstop(struct ifnet *ifp, int disable)
{
        struct gpn_softc * const sc = ifp->if_softc;
        ipm_gpn_desc_t gd;

        memset(&gd, 0, sizeof(gd));
        gd.gd_tag = IPM_TAG_GPN;
        gd.gd_subtype = GPN_IFDOWN;
        KASSERT(sc->sc_free > 0);
        sc->sc_free--;
        gemini_ipm_produce(&gd, 1);
        ifp->if_flags &= ~IFF_RUNNING;
        gpn_ipm_ifdown(sc);

        if (disable) {
                gpn_free_dmamaps(sc);
        }
}

static int
gpn_ifioctl(struct ifnet *ifp, u_long cmd, void *data)
{
        struct ifaliasreq * const ifra = data;
        int s, error;

        s = splnet();

        switch (cmd) {
        case SIOCSIFPHYADDR: {
                const struct sockaddr_dl *sdl = satosdl(&ifra->ifra_addr);

                if (sdl->sdl_family != AF_LINK) {
                        error = EINVAL;
                        break;
                }

                if_set_sadl(ifp, CLLADDR(sdl), ETHER_ADDR_LEN, false);
                error = 0;
                break;
        }
        default:
                error = ether_ioctl(ifp, cmd, data);
                if (error == ENETRESET)
                        error = 0;
                break;
        }

        splx(s);
        return error;
}

static int
gpn_mediachange(struct ifnet *ifp)
{
        return 0;
}

static void
gpn_mediastatus(struct ifnet *ifp, struct ifmediareq *imr)
{
        struct gpn_softc * const sc = ifp->if_softc;
        imr->ifm_active = sc->sc_im.ifm_cur->ifm_media;
}

static int
gpn_match(device_t parent, cfdata_t cf, void *aux)
{
        return strcmp(gpn_cd.cd_name, aux) == 0;
}

static void
gpn_attach(device_t parent, device_t self, void *aux)
{
        struct gpn_softc * const sc = device_private(self);
        struct ifnet * const ifp = &sc->sc_if;
        char enaddr[6];

        enaddr[0] = 2;
        enaddr[1] = 0;
        enaddr[2] = 0;
        enaddr[3] = 0;
        enaddr[4] = 0;
#ifdef GEMINI_MASTER
        enaddr[5] = 0;
#elif defined(GEMINI_SLAVE)
        enaddr[5] = 1;
#else
#error not master nor slave
#endif

        aprint_normal("\n");
        aprint_naive("\n");
        sc->sc_dev = self;
        sc->sc_dmat = &gemini_bus_dma_tag;

        /* Setup ifmedia interface */
        sc->sc_ec.ec_ifmedia = &sc->sc_im;
        ifmedia_init(&sc->sc_im, 0, gpn_mediachange, gpn_mediastatus);
        /* Pretend we are full-duplex gigabit ethernet. */
        ifmedia_add(&sc->sc_im, IFM_ETHER | IFM_1000_T | IFM_FDX, 0, NULL);
        ifmedia_add(&sc->sc_im, IFM_ETHER | IFM_NONE, 0, NULL);
        ifmedia_set(&sc->sc_im, IFM_ETHER | IFM_NONE);

        strlcpy(ifp->if_xname, device_xname(self), sizeof(ifp->if_xname));
        ifp->if_softc = sc;
        ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
        ifp->if_ioctl = gpn_ifioctl;
        ifp->if_start = gpn_ifstart;
        ifp->if_init  = gpn_ifinit;
        ifp->if_stop  = gpn_ifstop;

        IFQ_SET_READY(&ifp->if_snd);

        sc->sc_ec.ec_capabilities = ETHERCAP_VLAN_MTU | ETHERCAP_JUMBO_MTU;

        if_attach(ifp);
        ether_ifattach(ifp, enaddr);

        sc->sc_free = MAX_TXACTIVE*2;
        sc->sc_ih = gemini_ipm_register(IPM_TAG_GPN, IPL_SOFTNET, sc->sc_free,
            gpn_ipm_handler, gpn_ipm_rebate, sc);
        KASSERT(sc->sc_ih);

        sc->sc_ack_desc.agd_tag = IPM_TAG_GPN;
}

void gpn_print_gd(ipm_gpn_desc_t *);
void
gpn_print_gd(ipm_gpn_desc_t *gd)
{
        printf("%s: %p\n", __FUNCTION__, gd);
        printf("\ttag %d, subtype %d, id %d, pktlen64 %d\n",
                gd->gd_tag, gd->gd_subtype, gd->gd_txid, gd->gd_pktlen64);
        printf("\tlen1 %d, len2 %d, addr1 %#x, addr2 %#x\n",
                gd->gd_len1, gd->gd_len2, gd->gd_addr1, gd->gd_addr2);
}

CFATTACH_DECL_NEW(gpn, sizeof(struct gpn_softc),
    gpn_match, gpn_attach, NULL, NULL);