A Hardware-Independent Sample Driver

This is the sample code that's discussed in Writing Network Drivers for io-sock: sample.c, sample.h, and common.mk.

sample.c

/*
 * io-sock ofwbus/fdt sample driver
 */

#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>

#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/rman.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/taskqueue.h>
#include <net/bpf.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <net/if_var.h>

#include <machine/bus.h>
#include <dev/fdt/fdt_common.h>
#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <qnx/qnx_modload.h>
#include <qnx/qnx_smmu.h>
#include "miibus_if.h"
/* 
 * Include etherswitch callbacks in diag version of the driver.
 * Non-switch drivers can use the etherswitch framework as a debugging
 * tool to expose the ioctls used by the etherswitchcfg utility to
 * read HW registers. https://man.freebsd.org/cgi/man.cgi?query=
 * etherswitchcfg&sektion=8&manpath=FreeBSD+13.3-RELEASE+and+Ports
 */
#ifdef INVARIANTS
#define INCLUDE_ETHERSWITCH
#include <dev/etherswitch/etherswitch.h>
#include "etherswitch_if.h"
#endif
#include "sample.h"

#define SAMPLE_LOCK(sc) mtx_lock(&(sc)->mtx)
#define SAMPLE_UNLOCK(sc) mtx_unlock(&(sc)->mtx)
#define SAMPLE_ASSERT_LOCKED(sc) mtx_assert(&(sc)->mtx, MA_OWNED)
#define SAMPLE_ASSERT_UNLOCKED(sc) mtx_assert(&(sc)->mtx, MA_NOTOWNED)
#define SAMPLE_TX_LOCK(sc) mtx_lock(&(sc)->tx_mtx)
#define SAMPLE_TX_UNLOCK(sc) mtx_unlock(&(sc)->tx_mtx)
#define SAMPLE_ASSERT_TX_LOCKED(sc) mtx_assert(&(sc)->tx_mtx, MA_OWNED)
#define SAMPLE_ASSERT_TX_UNLOCKED(sc) mtx_assert(&(sc)->tx_mtx, MA_NOTOWNED)

#ifdef INVARIANTS
/* enable debug logs by default in the diag version of the driver */
int32_t g_debug = 1;
#else
int32_t g_debug = 0;
#endif
static SYSCTL_NODE(_hw, OID_AUTO, sam, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
    "sample driver global parameters");
SYSCTL_INT(_hw_sam, OID_AUTO, debug, CTLFLAG_RWTUN, &g_debug, 0,
    "Debug logging level (0 - 9)");

int drvr_ver = IOSOCK_VERSION_CUR;
SYSCTL_INT(_qnx_driver, OID_AUTO, sam, CTLFLAG_RD, &drvr_ver, 0,
    "Version");

/*
 * Array of compatibility strings that a device supported
 * by this driver will specify in the dtb.
 */
static struct ofw_compat_data compat_data[] = {
	{ "sam,sample-v1", 1 },
	{ "sam,sample-v2", 2 },
	{ "sam,sample-v3", 3 },
	{ NULL, 0 }
};

static struct resource_spec sam_spec[] = {
	{ SYS_RES_MEMORY, 0, RF_ACTIVE },
	{ SYS_RES_IRQ, 0, RF_ACTIVE },
	{ -1, 0 }
};

static bool
desc_owned(struct sam_hwdesc *desc)
{
	/* Checking descriptor ownership is hardware specifc */
	return (desc->flags & DESC_HW_OWN);
}

static uint32_t
next_txidx(uint32_t curidx)
{
	return ((curidx + 1) % TX_MAP_COUNT);
}

static uint32_t
next_rxidx(uint32_t curidx)
{
	return ((curidx + 1) % RX_DESC_COUNT);
}

static void
sam_txfinish_locked(struct sam_softc *sc)
{
	struct sam_bufmap *bmap;
	struct sam_hwdesc *desc;

	SAMPLE_ASSERT_TX_LOCKED(sc);

	bus_dmamap_sync(sc->txdesc_tag, sc->txdesc_map,
	    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
	while (sc->txbuf_map_tail != sc->txbuf_map_head) {
		bmap = &sc->txbuf_map[sc->txbuf_map_tail];
		desc = &sc->txdesc_ring[sc->txbuf_map_tail];
		/* assumes a single descriptor per map */
		if (desc_owned(desc)) {
			/* descriptor owned by hw, exit */
			break;
		}

		bus_dmamap_sync(sc->txbuf_tag, bmap->map,
		    BUS_DMASYNC_POSTWRITE);
		bus_dmamap_unload(sc->txbuf_tag, bmap->map);
		DEVPRINTFN(9, sc->dev, "Packet transmitted: %d bytes\n",
		    bmap->mbuf->m_len);
		m_freem(bmap->mbuf);
		bmap->mbuf = NULL;
		sc->tx_mapcount--;

		if_inc_counter(sc->ifp, IFCOUNTER_OPACKETS, 1);
		if_setdrvflagbits(sc->ifp, 0, IFF_DRV_OACTIVE);
		sc->txbuf_map_tail = next_txidx(sc->txbuf_map_tail);
	}
	/* stop the watchdog if there are no outstanding buffers */
	if (sc->txbuf_map_tail == sc->txbuf_map_head) {
		sc->tx_watchdog_count = 0;
	}
}

static void
sam_setup_txdesc(struct sam_softc *sc, int idx, bus_addr_t paddr,
    uint32_t len, uint32_t flags)
{
	/* setting up descriptors is HW dependent */
	sc->txdesc_ring[idx].paddrl = (uint32_t)paddr;
	sc->txdesc_ring[idx].paddrh = (uint32_t)(paddr >> 32);
	sc->txdesc_ring[idx].length = len;
	wmb();
	/*
	 * Indicating descriptor ownership is HW dependent.
	 * Use a memory barrier in this example to ensure the descriptor
	 * is fully written before transferring ownership.
	 */
	sc->txdesc_ring[idx].flags = flags;
	bus_dmamap_sync(sc->txdesc_tag, sc->txdesc_map,
	    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
}

static int
sam_setup_txbuf(struct sam_softc *sc, int idx, struct mbuf **mp)
{
	struct bus_dma_segment segs[TX_MAP_MAX_SEGS];
	int nsegs, error;
	struct mbuf * m;
	int desc_idx;

	error = bus_dmamap_load_mbuf_sg(sc->txbuf_tag, sc->txbuf_map[idx].map,
	    *mp, segs, &nsegs, 0);
	/*
	 * If bus_dmamap_load_mbuf_sg wasn't successful in mapping all
	 * segments, we will try to defrag it.
	 */
	if (error == EFBIG) {
		/* The map may be partially mapped from the first call. */
		bus_dmamap_unload(sc->txbuf_tag, sc->txbuf_map[idx].map);
		if ((m = m_defrag(*mp, M_NOWAIT)) == NULL)
			return (ENOMEM);
		*mp = m;
		error = bus_dmamap_load_mbuf_sg(sc->txbuf_tag,
		    sc->txbuf_map[idx].map, *mp, segs, &nsegs, 0);
	}
	if (error != 0) {
		bus_dmamap_unload(sc->txbuf_tag, sc->txbuf_map[idx].map);
		return (ENOMEM);
	}

	m = *mp;

	bus_dmamap_sync(sc->txbuf_tag, sc->txbuf_map[idx].map,
	    BUS_DMASYNC_PREWRITE);

	sc->txbuf_map[idx].mbuf = m;

	/*
	 * For simplicity, the sample only shows support for nseg = 1.
	 * (i.e. TX_MAP_MAX_SEGS = 1)
	 * In this case, the map index and descriptor index will be the same.
	 * How multiple segments are supported by the device
	 * descriptors is hardware dependent.
	 */
	desc_idx = idx;
	sam_setup_txdesc(sc, desc_idx, segs[0].ds_addr, segs[0].ds_len,
	    DESC_HW_OWN);

	return (0);
}

static void
sam_transmit_locked(struct sam_softc *sc)
{
	struct ifnet *ifp;
	struct mbuf  *m0;
	int error;
	int enqueued = 0;

	SAMPLE_ASSERT_TX_LOCKED(sc);
	if (((if_getdrvflags(sc->ifp) & IFF_DRV_RUNNING) == 0) ||
	    !sc->link_is_up) {
		return;
	}

	ifp = sc->ifp;

	while(1) {
		if (sc->tx_mapcount > (TX_MAP_COUNT -1)) {
			if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0);
			sam_txfinish_locked(sc);
			break;
		}
		/*
		 * TODO: also check for available descriptors.
		 * The sample only uses a single descriptor per map,
		 * so checking mapcount is sufficient for the sample.
		 */

		m0 = drbr_peek(sc->ifp, sc->tx_br);
		if (m0 == NULL) {
			break;
		}

		error = sam_setup_txbuf(sc, sc->txbuf_map_head, &m0);
		if (error != 0) {
			DEVPRINTF(sc->dev, "setup_txbuf error:%d\n", error);
			drbr_putback(sc->ifp, sc->tx_br, m0);
			if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0);
		}

		DEVPRINTFN(9, sc->dev, "Packet sent to hw: %d bytes\n",
		    m0->m_len);
		BPF_MTAP(ifp, m0);
		enqueued++;
		sc->txbuf_map_head = next_txidx(sc->txbuf_map_head);
		sc->tx_mapcount++;
		drbr_advance(sc->ifp, sc->tx_br);
	}
	if (enqueued != 0) {
		/* HW specific code to start TX, e.g. ring doorbell */

		/* Start watchdog */
		sc->tx_watchdog_count = WATCHDOG_TIMEOUT_SECS;
	}
	if (!drbr_empty(sc->ifp, sc->tx_br)) {
		taskqueue_enqueue(sc->tx_taskq, &sc->tx_task);
	}
}

static int
sam_transmit(struct ifnet *ifp, struct mbuf *m)
{
	struct sam_softc *sc;
	int ret, is_drbr_empty;

	sc = if_getsoftc(ifp);
	if(((if_getdrvflags(sc->ifp) & IFF_DRV_RUNNING) == 0) ||
	    !(sc->link_is_up)) {
		return ENETDOWN;
	}

	is_drbr_empty = drbr_empty(sc->ifp, sc->tx_br);
	ret = drbr_enqueue(ifp, sc->tx_br, m);
	if (ret != 0) {
		taskqueue_enqueue(sc->tx_taskq, &sc->tx_task);
		return (ret);
	}
	if (is_drbr_empty && mtx_trylock(&sc->tx_mtx)) {
		sam_transmit_locked(sc);
		SAMPLE_TX_UNLOCK(sc);
	} else {
		/* Wake up the task queue */
		taskqueue_enqueue(sc->tx_taskq, &sc->tx_task);
	}

	return ret;
}

static void
sam_transmit_deferred(void *arg, int pending)
{
	struct sam_softc *sc;

	sc = arg;

	while ((!drbr_empty(sc->ifp, sc->tx_br) &&
	    (if_getdrvflags(sc->ifp) & IFF_DRV_RUNNING) != 0)) {
		SAMPLE_TX_LOCK(sc);
		sam_transmit_locked(sc);
		SAMPLE_TX_UNLOCK(sc);
	}
}

static void
sam_get1paddr(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
{
	if (error != 0) {
		return;
	}
	*(bus_addr_t *)arg = segs[0].ds_addr;
}

static void
sam_setup_rxfilter(struct sam_softc *sc)
{
	/* HW specific code here: enable HW filtering */
}

static struct mbuf *
sam_alloc_mbufcl(struct sam_softc *sc)
{
	struct mbuf *m;

	m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
	if (m != NULL) {
		m->m_pkthdr.len = m->m_len = m->m_ext.ext_size;
	}

	return (m);
}

inline static void
sam_setup_rxdesc(struct sam_softc *sc, int idx, bus_addr_t paddr)
{
	/* 
	 * Setting up descriptors and indicating descriptor ownership
	 * is hardware specific. 
	 */
	sc->rxdesc_ring[idx].paddrh = (uint32_t)(paddr >> 32);
	sc->rxdesc_ring[idx].paddrl = (uint32_t)paddr;
	if (idx == (RX_DESC_COUNT -1)) {
		sc->rxdesc_ring[idx].flags |= DESC_LAST;
	}

	wmb();
	sc->rxdesc_ring[idx].flags = DESC_HW_OWN;
}

static int
sam_setup_rxbuf(struct sam_softc *sc, int idx, struct mbuf *m)
{
	struct bus_dma_segment seg;
	int error, nsegs;

	m_adj(m, ETHER_ALIGN);

	error = bus_dmamap_load_mbuf_sg(sc->rxbuf_tag, sc->rxbuf_map[idx].map,
	    m, &seg, &nsegs, 0);
	if (error != 0) {
		return (error);
	}

	KASSERT(nsegs == 1, ("%s: %d segments returned", __func__, nsegs));

	bus_dmamap_sync(sc->rxbuf_tag, sc->rxbuf_map[idx].map,
	    BUS_DMASYNC_PREREAD);

	sc->rxbuf_map[idx].mbuf = m;

	sam_setup_rxdesc(sc, idx, seg.ds_addr);

	return (0);
}

static struct mbuf *
sam_rxfinish_one(struct sam_softc *sc, struct sam_hwdesc *desc,
    struct sam_bufmap *map)
{
	struct ifnet *ifp;
	struct mbuf *m, *m0;
	int len = 0;

	m = map->mbuf;
	ifp = sc->ifp;

	/*
	 * HW specific code here: check length, return NULL if length doesn't
	 * make sense.
	 */

	/* Allocate new buffer */
	m0 = sam_alloc_mbufcl(sc);
	if (m0 == NULL) {
		/* no new mbuf available, recycle old */
		DEVPRINTFN(8, sc->dev, "Packet dropped, recycling mbuf\n");
		if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
		return (NULL);
	}
	/* do dmasync for newly received packet */
	bus_dmamap_sync(sc->rxbuf_tag, map->map, BUS_DMASYNC_POSTREAD);
	bus_dmamap_unload(sc->rxbuf_tag, map->map);

	/* Received packet is valid, process it. */
	m->m_pkthdr.rcvif = ifp;
	m->m_pkthdr.len = len;
	m->m_len = len;

	if_inc_counter(sc->ifp, IFCOUNTER_IPACKETS, 1);
	DEVPRINTFN(9, sc->dev, "Packet received: %d bytes.\n", len);

	SAMPLE_UNLOCK(sc);
	if_input(ifp, m);
	SAMPLE_LOCK(sc);

	return (m0);
}

static void
sam_rxfinish_locked(struct sam_softc *sc)
{
	struct sam_hwdesc *desc = NULL;
	struct mbuf *m;
	uint32_t idx;

	SAMPLE_ASSERT_LOCKED(sc);
	bus_dmamap_sync(sc->rxdesc_tag, sc->rxdesc_map,
	    BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD);
	while (1) {
		idx = sc->rx_idx;
		if (desc_owned(sc->rxdesc_ring + idx)) {
			/* descriptor owned by hw, exit */
			break;
		}
		m = sam_rxfinish_one(sc, desc, sc->rxbuf_map + idx);
		if (m == NULL) {
			/* error, reuse existing map */
			wmb();
			sc->rxdesc_ring[idx].flags |= DESC_HW_OWN;
			wmb();
		} else {
			int error;
			error = sam_setup_rxbuf(sc, idx, m);
			if (error != 0) {
				/* rx ring must be contigious, try a reset? */
				panic("setup_rxbuf failed:%d\n", error);
			}
		}
		sc->rx_idx = next_rxidx(sc->rx_idx);
	}
	bus_dmamap_sync(sc->rxdesc_tag, sc->rxdesc_map,
	    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
}

static void
sam_intr(void *arg)
{
	struct sam_softc *sc;
	uint32_t intr_status;

	sc = arg;
	SAMPLE_LOCK(sc);
	/*
	 * HW specific code to decode the interrupt.
	 */
	intr_status = READ4(sc, SAM_INTR_STATUS_REG);

	if (intr_status & SAM_INTR_RXREADY) {
		DEVPRINTFN(9,sc->dev, "RXREADY interrupt\n");
		sam_rxfinish_locked(sc);
	}

	if (intr_status & SAM_INTR_TXDONE) {
		DEVPRINTFN(9,sc->dev, "TXDONE interrupt\n");
		sam_txfinish_locked(sc);
		sam_transmit_locked(sc);
	}
	
	SAMPLE_UNLOCK(sc);
}

static void
sam_media_status(struct ifnet * ifp, struct ifmediareq *ifmr)
{
	struct sam_softc *sc;
	struct mii_data *mii;

	sc = if_getsoftc(ifp);
	mii = sc->mii_softc;

	SAMPLE_LOCK(sc);
	mii_pollstat(mii);
	ifmr->ifm_active = mii->mii_media_active;
	ifmr->ifm_status = mii->mii_media_status;
	SAMPLE_UNLOCK(sc);
}

static int
sam_media_change(struct ifnet * ifp)
{
	struct sam_softc *sc;
	int error;

	sc = if_getsoftc(ifp);
	SAMPLE_LOCK(sc);
	error = mii_mediachg(sc->mii_softc);
	SAMPLE_UNLOCK(sc);
	return (error);
}

static void
sam_dma_disable(struct sam_softc *sc)
{
	/* Disable hardware DMA */
}

static int
tx_dma_init(struct sam_softc *sc)
{
	/* Initialize hardware for Tx */

	/* Initialize buffer indices */
	sc->txbuf_map_head = 0;
	sc->txbuf_map_tail = 0;

	return (0);
}

static int
rx_dma_init(struct sam_softc *sc)
{
	struct mbuf *m;
	int error;
	int idx;

	/* Initialize hardware for Rx */

	/* Initialize buffer index */
	sc->rx_idx = 0;

	/* Populate Rx ring */
	for (idx = 0; idx < RX_DESC_COUNT; idx++) {
		if ((m = sam_alloc_mbufcl(sc)) == NULL) {
			device_printf(sc->dev, "failed to alloc mbuf\n");
			error = ENOMEM;
			break;
		}
		if ((error = sam_setup_rxbuf(sc, idx, m)) != 0) {
			device_printf(sc->dev,
			    "failed to create new RX buffer\n");
			break;
		}
	}	
	return (error);
}

static int
setup_dma(struct sam_softc *sc)
{
	int error;
	int idx;

	/*
	 * Set up TX descriptor ring, descriptors, and dma maps.
	 *
	 * See the FreeBSD manual pages for more information about
	 * the function parameters fo bus_dma_tag_create.
	 * https://man.freebsd.org/cgi/man.cgi?query=
	 * bus_dma_tag_create&sektion=9&manpath=FreeBSD+13.3-RELEASE+and+Ports
	 * 
	 * For devices that have restrictions on what memory ranges cannot be
	 * used for DMA, lowaddr may need to be diffferent than what is shown
	 * below.  e.g.  for devices that cannot use memory above 4G for DMA,
	 * lowaddr would be set to BUS_SPACE_MAXADDR_32BIT.
	 * 
	 */
	error = bus_dma_tag_create(
	    bus_get_dma_tag(sc->dev),   /* Parent tag. */
	    SAMPLE_DESC_RING_ALIGN, 0,  /* alignment, boundary */
	    BUS_SPACE_MAXADDR,          /* lowaddr */
	    BUS_SPACE_MAXADDR,          /* highaddr */
	    NULL, NULL,                 /* filter, filterarg */
	    TX_DESC_RING_SIZE, 1,       /* maxsize, nsegments */
	    TX_DESC_RING_SIZE,          /* maxsegsize */
	    0,                          /* flags */
	    NULL, NULL,                 /* lockfunc, lockarg */
	    &sc->txdesc_tag);           /* address of new tag */
	if (error != 0) {
		device_printf(sc->dev, "failed to create TX ring DMA tag\n");
		goto out;
	}

	error = bus_dmamem_alloc(sc->txdesc_tag, (void**)&sc->txdesc_ring,
	    BUS_DMA_COHERENT | BUS_DMA_WAITOK | BUS_DMA_ZERO,
	    &sc->txdesc_map);
	if (error != 0) {
		device_printf(sc->dev, "failed to allocate TX desc ring\n");
		goto out;
	}

	error = bus_dmamap_load(sc->txdesc_tag, sc->txdesc_map,
	    sc->txdesc_ring, TX_DESC_RING_SIZE, sam_get1paddr,
	    &sc->txdesc_ring_paddr, 0);
	if (error != 0) {
		device_printf(sc->dev, "failed to load TX desc ring mapp\n");
		goto out;
	}

	error = bus_dma_tag_create(
	    bus_get_dma_tag(sc->dev),    /* Parent tag. */
	    1, 0,                        /* alignment, boundary */
	    BUS_SPACE_MAXADDR,           /* lowaddr */
	    BUS_SPACE_MAXADDR,           /* highaddr */
	    NULL, NULL,                  /* filter, filterarg */
	    MCLBYTES,                    /* max size */
	    TX_MAP_MAX_SEGS,             /* nsegments */
	    MCLBYTES,                    /* maxsegsize */
	    0,                           /* flags */
	    NULL, NULL,                  /* lockfunc, lockarg */
	    &sc->txbuf_tag);             /* address of new tag */
	if (error != 0) {
		device_printf(sc->dev, "failed to create TX ring DMA tag\n");
		goto out;
	}

	for (idx = 0; idx < TX_MAP_COUNT; idx++) {
		error = bus_dmamap_create(sc->txbuf_tag, BUS_DMA_COHERENT,
		    &sc->txbuf_map[idx].map);
		if (error != 0) {
			device_printf(sc->dev,
			    "failed to create TX buffer DMA map\n");
			goto out;
		}
	}
	memset(sc->txdesc_ring, 0, TX_DESC_RING_SIZE);
	/*
	 * Set up RX descriptor ring, descriptors, dma maps.
	 */
	error = bus_dma_tag_create(
	    bus_get_dma_tag(sc->dev),    /* Parent tag. */
	    SAMPLE_DESC_RING_ALIGN, 0,   /* alignment, boundary */
	    BUS_SPACE_MAXADDR,           /* lowaddr */
	    BUS_SPACE_MAXADDR,           /* highaddr */
	    NULL, NULL,                  /* filter, filterarg */
	    RX_DESC_RING_SIZE, 1,        /* maxsize, nsegments */
	    RX_DESC_RING_SIZE,           /* maxsegsize */
	    0,                           /* flags */
	    NULL, NULL,                  /* lockfunc, lockarg */
	    &sc->rxdesc_tag);            /* address of new tag */
	if (error != 0) {
		device_printf(sc->dev, "failed to create RX ring DMA tag\n");
		goto out;
	}

	error = bus_dmamem_alloc(sc->rxdesc_tag, (void **)&sc->rxdesc_ring,
	    BUS_DMA_COHERENT | BUS_DMA_WAITOK |
	    BUS_DMA_ZERO | BUS_DMA_NOCACHE,
	    &sc->rxdesc_map);
	if (error != 0) {
		device_printf(sc->dev, "failed to allocate RX desc ring\n");
		goto out;
	}

	error = bus_dmamap_load(sc->rxdesc_tag, sc->rxdesc_map,
	    sc->rxdesc_ring, RX_DESC_RING_SIZE, sam_get1paddr,
	    &sc->rxdesc_ring_paddr, 0);
	if (error != 0) {
		device_printf(sc->dev, "failed to load RX desc ring map\n");
		goto out;
	}

	error = bus_dma_tag_create(
	    bus_get_dma_tag(sc->dev),    /* Parent tag. */
	    1, 0,                        /* alignment, boundary */
	    BUS_SPACE_MAXADDR,           /* lowaddr */
	    BUS_SPACE_MAXADDR,           /* highaddr */
	    NULL, NULL,                  /* filter, filterarg */
	    MCLBYTES, 1,                 /* maxsize, nsegments */
	    MCLBYTES,                    /* maxsegsize */
	    0,                           /* flags */
	    NULL, NULL,                  /* lockfunc, lockarg */
	    &sc->rxbuf_tag);             /* address of new tag */
	if (error != 0) {
		device_printf(sc->dev, "failed to create RX buf DMA tag\n");
		goto out;
	}

	for (idx = 0; idx < RX_DESC_COUNT; idx++) {
		error = bus_dmamap_create(sc->rxbuf_tag, BUS_DMA_COHERENT,
		    &sc->rxbuf_map[idx].map);
		if (error != 0) {
			device_printf(sc->dev,
			    "failed to create RX buffer DMA map\n");
			goto out;
		}
	}

out:
	if (error != 0) {
		return (ENXIO);
	}
	return (0);
}

static int
sam_reset(struct sam_softc *sc)
{
	/* HW specific code here: Reset the controller */
	return (0);
}

static int
sam_mac_init(struct sam_softc *sc)
{
	/* HW specific code here: Initialize MAC registers */
	return (0);
}

static int
sam_mac_disable(struct sam_softc *sc)
{
	/* HW specific code here: Set MAC registers for stop */
	return (0);
}

static void
sam_get_hwaddr(struct sam_softc *sc)
{
	phandle_t node;

	node = ofw_bus_get_node(sc->dev);
	if (OF_getprop(node, "mac-address", sc->hwaddr.octet,
	    ETHER_ADDR_LEN) != -1 ||
	    OF_getprop(node, "local-mac-address", sc->hwaddr.octet,
	    ETHER_ADDR_LEN) != -1)
		return;
	DEVPRINTF(sc->dev, "No mac address found in fdt.\n");

	/* generate a random mac address */
	ether_gen_addr(sc->ifp, &sc->hwaddr);
}

/* miibus callbacks */
static int
sam_miibus_read_reg(device_t dev, int phy, int reg)
{
	/* HW specific code here: read phy register here */
	return 0xdeadbeef;
}

static int
sam_miibus_write_reg(device_t dev, int phy, int reg, int val)
{
	/* HW specific code here: write phy register here */
	return 0;
}

static void
sam_miibus_statchg(device_t dev)
{
	struct sam_softc *sc;
	struct mii_data *mii;

	/*
	 * Called by the MII bus driver when the PHY establishes
	 * link to set the MAC interface registers.
	 */

	sc = device_get_softc(dev);
	SAMPLE_ASSERT_LOCKED(sc);

	mii = sc->mii_softc;

	if (mii->mii_media_status & IFM_ACTIVE) {
		sc->link_is_up = true;
	} else {
		sc->link_is_up = false;
	}

	switch (IFM_SUBTYPE(mii->mii_media_active)) {
	case IFM_1000_T:
	case IFM_1000_SX:
	case IFM_100_TX:
	case IFM_100_T2:
	case IFM_10_T:
	case IFM_100_T:
		/* HW Specific code here: set speed */
		break;
	case IFM_NONE:
		sc->link_is_up = false;
		return;
	default:
		sc->link_is_up = false;
		device_printf(dev, "Unsupported media %u\n",
		    IFM_SUBTYPE(mii->mii_media_active));
		return;
	}

	if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) {
		/* HW Specific code here: set full duplex */
	}
	else {
		/* HW Specific code here: clear full duplex */
	}

	/* HW specific code here: implement media specific HW initialization */

}

#ifdef INCLUDE_ETHERSWITCH
/*
 * Etherswitch callbacks. These could be used in a debug version of 
 * of a driver to read HW registers with the etherwitchcfg utility.
 */
static etherswitch_info_t *
sam_es_getinfo(device_t dev)
{
	struct sam_softc *sc;

	sc = device_get_softc(dev);

	sc->es_info.es_nports = 1;
	strncpy(sc->es_info.es_name, "Sample Ethernet MAC Driver",
	    ETHERSWITCH_NAMEMAX);

	return (&sc->es_info);
}

static int
sam_es_readreg(device_t dev, int reg)
{
	struct sam_softc *sc;
	uint32_t val;

	sc = device_get_softc(dev);

	if (reg > bus_get_resource_count(sc->dev, SYS_RES_MEMORY, 0)) {
		device_printf(dev, "%s - Register out of range\n", __func__);
		return 0;
	}

	val = READ4(sc, reg);

	return (val);
}
#endif

static void
sam_tick(void *arg)
{
	struct sam_softc *sc;
	int link_was_up;

	sc = arg;

	SAMPLE_ASSERT_LOCKED(sc);

	if (!(if_getdrvflags(sc->ifp) & IFF_DRV_RUNNING)) {
		return;
	}

	/*
	 * Typical tx watchdog.  If this fires it indicates that we enqueued
	 * packets for output and never got a txdone interrupt.
	 */
	if (sc->tx_watchdog_count > 0) {
		if (--sc->tx_watchdog_count == 0) {
			sam_txfinish_locked(sc);
		}
	}

	/* Check the media status. */
	link_was_up = sc->link_is_up;
	mii_tick(sc->mii_softc);
	if (sc->link_is_up && !link_was_up) {
		taskqueue_enqueue(sc->tx_taskq, &sc->tx_task);
	}

	/* Schedule another check one second from now. */
	callout_reset(&sc->sam_callout, hz, sam_tick, sc);
}

static void
sam_init_locked(struct sam_softc *sc)
{
	SAMPLE_ASSERT_LOCKED(sc);

	if (if_getdrvflags(sc->ifp) & IFF_DRV_RUNNING) {
		return;
	}

	if_setdrvflagbits(sc->ifp, IFF_DRV_RUNNING, IFF_DRV_OACTIVE);

	sam_setup_rxfilter(sc);

	sam_mac_init(sc);
	rx_dma_init(sc);
	tx_dma_init(sc);

	/*
	 * Call mii_mediachg() which will call back into sam_miibus_statchg()
	 * to set up the remaining config registers based on current media.
	 */
	mii_mediachg(sc->mii_softc);
	callout_reset(&sc->sam_callout, hz, sam_tick, sc);
}

static void
sam_stop_locked(struct sam_softc *sc)
{
	int i;

	SAMPLE_ASSERT_LOCKED(sc);

	/* clear the IFF_DRV_RUNNING flag */
	if_setdrvflagbits(sc->ifp, 0, IFF_DRV_RUNNING);
	sc->tx_watchdog_count = 0;

	callout_stop(&sc->sam_callout);

	/* HW specific code here: stop HW */
	sam_dma_disable(sc);
	sam_mac_disable(sc);

	wmb();
	/* Clear the tx ring buffer */
	for (i = 0; i < TX_DESC_COUNT; i++) {
		if (sc->txbuf_map[i].mbuf != NULL){
			bus_dmamap_sync(sc->txbuf_tag, sc->txbuf_map[i].map,
				BUS_DMASYNC_POSTWRITE);
			bus_dmamap_unload(sc->txbuf_tag, sc->txbuf_map[i].map);
			m_freem(sc->txbuf_map[i].mbuf);
			sc->txbuf_map[i].mbuf = NULL;
		}
	}

	/* Clear the rx ring buffer */
	for (i = 0; i < RX_DESC_COUNT; i++) {
		if (sc->rxbuf_map[i].mbuf != NULL) {
			bus_dmamap_sync(sc->rxbuf_tag, sc->rxbuf_map[i].map,
			    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
			bus_dmamap_unload(sc->rxbuf_tag, sc->rxbuf_map[i].map);
			m_freem(sc->rxbuf_map[i].mbuf);
			sc->rxbuf_map[i].mbuf = NULL;
		}
	}
}

static int
sam_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
	struct sam_softc *sc;
	struct mii_data *mii;
	struct ifreq *ifr;
	int mask, error;

	sc = if_getsoftc(ifp);
	ifr = (struct ifreq *)data;
	error = 0;
	switch (cmd) {
	case SIOCSIFFLAGS:
		SAMPLE_LOCK(sc);
		if (if_getflags(ifp) & IFF_UP) {
			if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) {
				if ((if_getflags(ifp) ^ sc->if_flags) &
				    (IFF_PROMISC | IFF_ALLMULTI)) {
					sam_setup_rxfilter(sc);
				}
			} else {
				if (!sc->is_detaching) {
					sam_init_locked(sc);
				}
			}
		} else {
			if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) {
				sam_stop_locked(sc);
			}
		}
		sc->if_flags = if_getflags(ifp);
		SAMPLE_UNLOCK(sc);
		break;
	case SIOCADDMULTI:
	case SIOCDELMULTI:
		if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) {
			SAMPLE_LOCK(sc);
			sam_setup_rxfilter(sc);
			SAMPLE_UNLOCK(sc);
		}
		break;
	case SIOCSIFMEDIA:
	case SIOCGIFMEDIA:
		mii = sc->mii_softc;
		error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd);
		break;
	case SIOCSIFCAP:
		mask = if_getcapenable(ifp) ^ ifr->ifr_reqcap;
		if (mask & IFCAP_VLAN_MTU) {
			if_togglecapenable(ifp, IFCAP_VLAN_MTU);
		}
		/*
		 * HW specific code here for additional capabilities,
		 * such as HW offload features.
		 */
		break;

	default:
		error = ether_ioctl(ifp, cmd, data);
		break;
	}
	return (error);
}

static void
sam_qflush(struct ifnet *ifp)
{
	struct sam_softc *sc;

	sc = if_getsoftc(ifp);
	if (!drbr_empty(ifp, sc->tx_br)) {
		SAMPLE_TX_LOCK(sc);
		drbr_flush(ifp, sc->tx_br);
		SAMPLE_TX_UNLOCK(sc);
	}

	if_qflush(ifp);
}

static void
sam_init(void *if_softc)
{
	struct sam_softc *sc;

	sc = if_softc;
	SAMPLE_LOCK(sc);
	sam_init_locked(sc);
	SAMPLE_UNLOCK(sc);
}

static int
sam_probe(device_t dev)
{
	if (!ofw_bus_status_okay(dev)) {
		return (ENXIO);
	}

	/* 
	 * Check device's compatibility string against list of
	 * supported devices
	 */
	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) {
		return (ENXIO);
	}
	device_set_desc(dev, "Sample Ethernet Controller");

	return (BUS_PROBE_DEFAULT);
}

static int
sam_detach(device_t dev)
{
	struct sam_softc *sc;
	bus_dmamap_t map;
	int idx;

	sc = device_get_softc(dev);

	SAMPLE_LOCK(sc);
	sc->is_detaching = true;
	sam_stop_locked(sc);
	SAMPLE_UNLOCK(sc);
	callout_drain(&sc->sam_callout);
	ether_ifdetach(sc->ifp);

	/* Detach the miibus */
	if (sc->miibus) {
		device_delete_child(dev, sc->miibus);
	}
	bus_generic_detach(dev);

	if (sc->ifp != NULL) {
		if_free(sc->ifp);
		sc->ifp = NULL;
	}

	/* Clean up RX DMA resources and free mbufs. */
	for (idx = 0; idx < RX_DESC_COUNT; ++idx) {
		if ((map = sc->rxbuf_map[idx].map) != NULL) {
			bus_dmamap_unload(sc->rxbuf_tag, map);
			bus_dmamap_destroy(sc->rxbuf_tag, map);
			m_freem(sc->rxbuf_map[idx].mbuf);
		}
	}
	if (sc->rxbuf_tag != NULL) {
		bus_dma_tag_destroy(sc->rxbuf_tag);
	}
	if (sc->rxdesc_map != NULL) {
		bus_dmamap_unload(sc->rxdesc_tag, sc->rxdesc_map);
		bus_dmamem_free(sc->rxdesc_tag, sc->rxdesc_ring,
		    sc->rxdesc_map);
	}
	if (sc->rxdesc_tag != NULL) {
		bus_dma_tag_destroy(sc->rxdesc_tag);
	}

	/* Clean up TX DMA resources. */
	for (idx = 0; idx < TX_MAP_COUNT; ++idx) {
		if ((map = sc->txbuf_map[idx].map) != NULL) {
			/* TX maps are already unloaded. */
			bus_dmamap_destroy(sc->txbuf_tag, map);
		}
	}
	if (sc->txbuf_tag != NULL) {
		bus_dma_tag_destroy(sc->txbuf_tag);
	}
	if (sc->txdesc_map != NULL) {
		bus_dmamap_unload(sc->txdesc_tag, sc->txdesc_map);
		bus_dmamem_free(sc->txdesc_tag, sc->txdesc_ring,
		sc->txdesc_map);
	}
	if (sc->txdesc_tag != NULL) {
		bus_dma_tag_destroy(sc->txdesc_tag);
	}

	/* Release bus resources. */
	del_mmio_smmu(rman_get_start(sc->res[0]), rman_get_size(sc->res[0]));
	bus_release_resources(dev, sam_spec, sc->res);

	SAMPLE_TX_LOCK(sc);
	if (sc->tx_taskq) {
		taskqueue_drain(sc->tx_taskq, &sc->tx_task);
		taskqueue_free(sc->tx_taskq);
		sc->tx_taskq = NULL;
	}
	if (sc->tx_br) {
		buf_ring_free(sc->tx_br, M_DEVBUF);
		sc->tx_br = NULL;
	}
	mtx_destroy(&sc->tx_mtx);

	if (sc->intr_cookie != NULL) {
		bus_teardown_intr(dev, sc->res[1], sc->intr_cookie);
		sc->intr_cookie = NULL;
	}

	mtx_destroy(&sc->mtx);
	return (0);
}

static int
sam_attach(device_t dev)
{
	struct sam_softc *sc;
	struct ifnet *ifp;
	int error, phynum;
	char *phy_mode;
	phandle_t node;
	void *dummy;
	char td_name[32];

	sc = device_get_softc(dev);

	node = ofw_bus_get_node(dev);
	if ((node = ofw_bus_get_node(dev)) == -1) {
		device_printf(dev, "failed to find ofw bus node\n");
		error = ENXIO;
		goto done;
	}

	/*
	 * HW specific code here: just an example of how to read some
	 * property from DTB.
	 */
	if (OF_getprop_alloc(node, "phy-mode", (void **)&phy_mode)) {
		if (strcmp(phy_mode, "rgmii") == 0) {
			sc->phy_mode = PHY_MODE_RGMII;
		}
		if (strcmp(phy_mode, "rmii") == 0) {
			sc->phy_mode = PHY_MODE_RMII;
		}
		OF_prop_free(phy_mode);
	}

	if (bus_alloc_resources(dev, sam_spec, sc->res)) {
		device_printf(dev, "failed to allocate resources\n");
		error = ENXIO;
		goto done;
	}
	add_mmio_smmu(rman_get_start(sc->res[0]), rman_get_size(sc->res[0]));

	/* Reset the HW if needed. */
	if (sam_reset(sc) != 0) {
		device_printf(dev, "failed to reset the HW\n");
		error = ENXIO;
		goto done;
	}

	mtx_init(&sc->tx_mtx, td_name, NULL, MTX_DEF);

	/* buf_ring_alloc requires a size that is a power of 2 */
	sc->tx_br = buf_ring_alloc(TX_QUEUE_LEN, M_DEVBUF, M_NOWAIT,
	    &sc->tx_mtx);
	if (sc->tx_br == NULL) {
		device_printf(dev, "buf_ring_alloc failed");
		error = ENOMEM;
		goto done;
	}

	TASK_INIT(&sc->tx_task, 0, sam_transmit_deferred, sc);
	sc->tx_taskq = taskqueue_create(td_name, M_NOWAIT,
	    taskqueue_thread_enqueue, &sc->tx_taskq);
	if (sc->tx_taskq == NULL) {
		device_printf(dev, "taskqueue_create failed");
		error = ENOMEM;
		goto done;
	}
	taskqueue_start_threads(&sc->tx_taskq, 1, PI_NET, td_name);

	if (setup_dma(sc) != 0) {
		error = ENXIO;
		goto done;
	}

	mtx_init(&sc->mtx, device_get_nameunit(sc->dev),
	    MTX_NETWORK_LOCK, MTX_DEF);

	callout_init_mtx(&sc->sam_callout, &sc->mtx, 0);

	/* Setup interrupt handler. */
	error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_NET | INTR_MPSAFE,
	    NULL, sam_intr, sc, &sc->intr_cookie);
	if (error != 0) {
		device_printf(dev, "failed to setup interrupt handler\n");
		error = ENXIO;
		goto done;
	}

	/* Set up the ethernet interface. */
	sc->ifp = ifp = if_alloc(IFT_ETHER);

	if_setsoftc(ifp, sc);
	if_initname(ifp, device_get_name(dev), device_get_unit(dev));
	if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
	if_setcapabilities(ifp, IFCAP_VLAN_MTU);
	if_setcapenable(ifp, if_getcapabilities(ifp));
	if_settransmitfn(ifp, sam_transmit);
	if_setqflushfn(ifp, sam_qflush);
	if_setioctlfn(ifp, sam_ioctl);
	if_setinitfn(ifp, sam_init);
	if_setsendqlen(ifp, TX_QUEUE_LEN - 1);
	if_setsendqready(ifp);
	if_setifheaderlen (ifp, sizeof(struct ether_vlan_header));

	if (fdt_get_phyaddr(node, dev, &phynum, &dummy) != 0) {
		phynum = MII_PHY_ANY;
	}
	/* Attach the mii driver. */
	error = mii_attach(dev, &sc->miibus, ifp, sam_media_change,
	    sam_media_status, BMSR_DEFCAPMASK, phynum,
	    MII_OFFSET_ANY, MIIF_FORCEANEG);
	if (error != 0) {
		device_printf(dev, "PHY attach failed\n");
		error = ENXIO;
		goto done;
	}
	sc->mii_softc = device_get_softc(sc->miibus);
	sam_get_hwaddr(sc);
	/* All ready to run, attach the ethernet interface. */
	ether_ifattach(ifp, sc->hwaddr.octet);

done:
	if (error != 0) {
		device_printf(dev, "failed to attach, error:%d\n", error);
		sam_detach(dev);
	}

	return error;
}

static int
sam_shutdown(device_t dev)
{
	/*
	 * HW Specific code here.
	 * Shutdown is called when the io-sock process terminates.
	 * Unlike sam_detach, sam_shutdown is not intended to be a
	 * graceful stop. This method should put the hardware in a safe
	 * state (e.g. disable HW DMA) in the simplest manner possible.
	 * There should be no blocking calls and this function should return
	 * as quickly as possible.
	 */

	return 0;
}

/* Driver callback registration */
static device_method_t sam_methods[] = {
	/* device callbacks */
	DEVMETHOD(device_probe, sam_probe),
	DEVMETHOD(device_attach, sam_attach),
	DEVMETHOD(device_detach, sam_detach),
	DEVMETHOD(device_shutdown, sam_shutdown),

	/* miibus callbacks */
	DEVMETHOD(miibus_readreg, sam_miibus_read_reg),
	DEVMETHOD(miibus_writereg, sam_miibus_write_reg),
	DEVMETHOD(miibus_statchg, sam_miibus_statchg),

#ifdef INCLUDE_ETHERSWITCH
	/* etherswitch callbacks */
	DEVMETHOD(etherswitch_getinfo, sam_es_getinfo),
	DEVMETHOD(etherswitch_readreg, sam_es_readreg),
	DEVMETHOD(etherswitch_readphyreg, sam_miibus_read_reg),
#endif

	DEVMETHOD_END
};

driver_t sample_driver = {
	"sam",
	sam_methods,
	sizeof(struct sam_softc),
};

static devclass_t sam_devclass;

/*
 * The sample driver is showing how the device name and module name
 * can be different.
 * e.g.  module name is sample, and device name is sam.
 * Most FreeBSD drivers will use the same name for the driver,
 * device and the module.
 */
DRIVER_MODULE(sample, simplebus, sample_driver, sam_devclass, 0, 0);
/*
 * Since the second parameter of the DRIVER_MODULE macro is the bus name,
 * the driver device name is used in the following and not the sample
 * driver module name.
 * DRIVER_MODULE(modname, busname, driver_t driver, modeventhand_t evh,
 *     void *arg);
 */
DRIVER_MODULE(miibus, sam, miibus_driver, miibus_devclass, 0, 0);
#ifdef INCLUDE_ETHERSWITCH
DRIVER_MODULE(etherswitch, sam, etherswitch_driver, etherswitch_devclass, 0, 0);
#endif
MODULE_DEPEND(sample, ether, 1, 1, 1);
MODULE_DEPEND(sample, miibus, 1, 1, 1);
#ifdef INCLUDE_ETHERSWITCH
MODULE_DEPEND(sample, etherswitch, 1, 1, 1);
#endif
MODULE_VERSION(sample, 1);

struct _iosock_module_version iosock_module_version =
    IOSOCK_MODULE_VER_SYM_INIT;

static void
sam_uninit(void *arg)
{
}
/*
 * If code is added to sam_uninit, then SI_SUB_DUMMY needs to be
 * changed to SI_SUB_DRIVERS.  With SI_SUB_DUMMY, sam_uninit does
 * not get called.
 */
SYSUNINIT(sam_uninit, SI_SUB_DUMMY, SI_ORDER_ANY, sam_uninit, NULL);

sample.h

/*
 * io-sock sample driver code
 */
#define RX_DESC_COUNT		1024
#define TX_DESC_COUNT		1024
#define TX_MAP_COUNT		TX_DESC_COUNT
#define TX_MAP_MAX_SEGS		1
#define WATCHDOG_TIMEOUT_SECS	5
#define SAMPLE_DESC_RING_ALIGN	2048
#define TX_QUEUE_LEN		4096
#define TX_DESC_SIZE		sizeof(struct sam_hwdesc)
#define RX_DESC_SIZE		sizeof(struct sam_hwdesc)
#define TX_DESC_RING_SIZE	(TX_DESC_SIZE * TX_DESC_COUNT)
#define RX_DESC_RING_SIZE	(RX_DESC_SIZE * RX_DESC_COUNT)

#define PHY_MODE_UNKNOWN	0x0
#define PHY_MODE_RMII		0x1
#define PHY_MODE_RGMII		0x2

/* macros used to access SYS_RES_MEMORY in sam_spec */
#define READ4(_sc, _reg)	bus_read_4((_sc)->res[0], _reg)
#define WRITE4(_sc, _reg, _val)	bus_write_4((_sc)->res[0], _reg, _val)

/* Sample defines used to service HW interrupts */
#define SAM_INTR_STATUS_REG	0x1000
#define SAM_INTR_RXREADY	(1 << 0)
#define SAM_INTR_TXDONE		(1 << 1)

/* Sample hardware descriptor.  Determined by the hardware. */
#define DESC_HW_OWN		(1 << 0)
#define DESC_LAST		(1 << 1)
struct sam_hwdesc {
	uint32_t	paddrl;
	uint32_t	paddrh;
	uint32_t	length;
	uint32_t	flags;
};

struct sam_bufmap {
	bus_dmamap_t	map;
	struct mbuf	*mbuf;
 };

struct sam_softc {
	struct resource		*res[2];
	device_t		dev;
	device_t		miibus;
	struct mii_data		*mii_softc;
	struct ifnet		*ifp;
	int			if_flags;
	struct mtx		mtx;
	void			*intr_cookie;
	struct callout		sam_callout;
	boolean_t		link_is_up;
	boolean_t		is_detaching;
	int			tx_watchdog_count;
	int			phy_mode;
	struct ether_addr	hwaddr;

	/* RX */
	bus_dma_tag_t		rxdesc_tag;
	bus_dmamap_t		rxdesc_map;
	struct sam_hwdesc	*rxdesc_ring;
	bus_addr_t		rxdesc_ring_paddr;
	bus_dma_tag_t		rxbuf_tag;
	struct sam_bufmap	rxbuf_map[RX_DESC_COUNT];
	uint32_t		rx_idx;

	/* TX */
	bus_dma_tag_t		txdesc_tag;
	bus_dmamap_t		txdesc_map;
	struct sam_hwdesc	*txdesc_ring;
	bus_addr_t		txdesc_ring_paddr;
	bus_dma_tag_t		txbuf_tag;
	struct sam_bufmap	txbuf_map[TX_DESC_COUNT];
	uint32_t		tx_mapcount;

	uint32_t		txbuf_map_head;
	uint32_t		txbuf_map_tail;

	struct mtx		tx_mtx;
	struct buf_ring		*tx_br;
	struct taskqueue	*tx_taskq;
	struct task		tx_task;
#ifdef INCLUDE_ETHERSWITCH
	struct etherswitch_info	es_info;
#endif
 };

/*
 * Define macros for debug logging.
 * A verbosity level, N, is used.
 */
extern int32_t g_debug;
#define	DEVPRINTFN(n, dev, fmt, ...) do { \
	if (g_debug >= n) { \
		device_printf(dev, "%s - " fmt, \
		    __func__ , ##__VA_ARGS__); \
	} \
} while (0)
#define DEVPRINTF(...) DEVPRINTFN(1, __VA_ARGS__)

common.mk

ifndef QCONFIG
QCONFIG=qconfig.mk
endif

include $(QCONFIG)

define PINFO
PINFO DESCRIPTION=Sample io-sock fdt mac driver
endef

INTERFACE_PREFIX="sam"

define DRIVER_SPECIFIC_OPTIONS

The following sysctls are create by this driver:
hw.sample.debug

endef

include devs/devs.mk
Page updated: