git: 86a88972dc31 - main - DPAA: Minor performance improvements

From: Justin Hibbits <jhibbits_at_FreeBSD.org>
Date: Fri, 21 Aug 2026 02:26:52 UTC
The branch main has been updated by jhibbits:

URL: https://cgit.FreeBSD.org/src/commit/?id=86a88972dc311b5d4fd112564bfe19c6d7549b5f

commit 86a88972dc311b5d4fd112564bfe19c6d7549b5f
Author:     Justin Hibbits <jhibbits@FreeBSD.org>
AuthorDate: 2026-07-26 04:01:51 +0000
Commit:     Justin Hibbits <jhibbits@FreeBSD.org>
CommitDate: 2026-08-21 02:26:10 +0000

    DPAA: Minor performance improvements
    
    * Add interrupt coalescing for DQRR and MR, with thresholds and period
      as tunable sysctls under the `hw.qman` tree.
    * Do lazy/sloppy buffer management to avoid constantly checking
      thresholds via QMan portal round-trips.
    * Add cache stashing to prewarm caches, reducing latency.
    * Fix the definition of Context_A in the init_fq MC command/result
      structures, they're 64-bit fields, not 32-bit.
    * Reorder the dpaa_eth_frame_info as a bit of cleanup.
    * Take advantage of the fact that UMA small allocations are returned in
      the DMAP, and avoid pmap_kextract().
    
    These changes together improve throughput by ~1.5%
    (925Mbps->935-940Mbps) consistently, and reduce CPU usage by a bit,
    increasing idle CPU from 30%->35% minimum.
---
 sys/dev/dpaa/dpaa_eth.c     | 139 +++++++++++++++++++++++++++++++-------------
 sys/dev/dpaa/dpaa_eth.h     |   9 ++-
 sys/dev/dpaa/qman.c         |  28 ++++++++-
 sys/dev/dpaa/qman.h         |   3 +-
 sys/dev/dpaa/qman_portals.c |  88 ++++++++++++++++++++++++++++
 sys/dev/dpaa/qman_var.h     |   6 +-
 6 files changed, 227 insertions(+), 46 deletions(-)

diff --git a/sys/dev/dpaa/dpaa_eth.c b/sys/dev/dpaa/dpaa_eth.c
index 6424a6e0b0c3..2fb902c4d300 100644
--- a/sys/dev/dpaa/dpaa_eth.c
+++ b/sys/dev/dpaa/dpaa_eth.c
@@ -73,16 +73,42 @@
 #define DPAA_ETH_UNLOCK(sc)		mtx_unlock(&(sc)->sc_lock)
 #define DPAA_ETH_LOCK_ASSERT(sc)	mtx_assert(&(sc)->sc_lock, MA_OWNED)
 
+/*
+ * On 64-bit Book-E the direct map is always present, and the driver's
+ * UMA zones plus page-sized mbuf clusters live in it.  Bypass the
+ * page-table walk in pmap_kextract() for those; fall back for
+ * MJUM9BYTES/MJUM16BYTES clusters, which are kmem_alloc_contig()'d
+ * into KVA.
+ */
+static inline vm_paddr_t
+dpaa_eth_va_to_phys(vm_offset_t va)
+{
+	if (__predict_true(va >= DMAP_BASE_ADDRESS && va <= DMAP_MAX_ADDRESS))
+		return (DMAP_TO_PHYS(va));
+	return (pmap_kextract(va));
+}
+
 /**
  * @group dTSEC RM private defines.
  * @{
  */
 #define	DTSEC_BPOOLS_USED	(1)
 #define	DTSEC_MAX_TX_QUEUE_LEN	256
+/*
+ * Sample the hardware TX FQ counter every Nth packet.  The FQ counter is
+ * 24 bits and the soft cap above is 256, so overshoot by N is trivial.
+ */
+#define	DTSEC_MAX_TX_QUEUE_CHECK_INTERVAL	32
+/*
+ * Confirmation callback drain-detection.  Fast path (TX not backpressured)
+ * skips the MC call entirely; when flagged, we sample every Nth callback to
+ * detect the drain-to-zero transition.
+ */
+#define	DTSEC_TX_CONF_CHECK_INTERVAL		32
 
 struct dpaa_eth_frame_info {
-	struct mbuf			*fi_mbuf;
 	struct fman_internal_context	fi_ic;
+	struct mbuf			*fi_mbuf;
 	struct dpaa_sgte		fi_sgt[DPAA_NUM_OF_SG_TABLE_ENTRY];
 };
 
@@ -90,6 +116,13 @@ enum dpaa_eth_pool_params {
 	DTSEC_RM_POOL_RX_LOW_MARK	= 16,
 	DTSEC_RM_POOL_RX_HIGH_MARK	= 64,
 	DTSEC_RM_POOL_RX_MAX_SIZE	= 256,
+	/*
+	 * MAX_SIZE is a soft cap set well below the BMan hardware pool
+	 * limit, so sampling the depth every N put-backs per CPU is safe:
+	 * worst-case overshoot is N * ncpus buffers, still tiny vs. the
+	 * hardware pool.
+	 */
+	DTSEC_RM_POOL_RX_CHECK_INTERVAL	= 32,
 
 	DTSEC_RM_POOL_FI_LOW_MARK	= 16,
 	DTSEC_RM_POOL_FI_HIGH_MARK	= 64,
@@ -227,7 +260,7 @@ dtsec_add_buffers(struct dpaa_eth_softc *sc, int count)
 			b = uma_zalloc(sc->sc_rx_zone, M_NOWAIT);
 			if (b == NULL)
 				return (ENOMEM);
-			pa = pmap_kextract((vm_offset_t)b);
+			pa = DMAP_TO_PHYS((vm_offset_t)b);
 			bufs[i].buf_hi = (pa >> 32);
 			bufs[i].buf_lo = (pa & 0xffffffff);
 		}
@@ -273,6 +306,9 @@ dpaa_eth_pool_rx_free(struct dpaa_eth_softc *sc)
 
 	if (sc->sc_rx_zone != NULL)
 		uma_zdestroy(sc->sc_rx_zone);
+
+	free(sc->sc_rx_pool_check_cnt, M_DEVBUF);
+	sc->sc_rx_pool_check_cnt = NULL;
 }
 
 int
@@ -288,6 +324,10 @@ dpaa_eth_pool_rx_init(struct dpaa_eth_softc *sc)
 	sc->sc_rx_zone = uma_zcreate(sc->sc_rx_zname, MCLBYTES, NULL,
 	    NULL, NULL, NULL, MCLBYTES - 1, 0);
 
+	sc->sc_rx_pool_check_cnt = malloc_aligned(
+	    (mp_maxid + 1) * sizeof(struct dpaa_pcpu_cnt),
+	    CACHE_LINE_SIZE, M_DEVBUF, M_WAITOK | M_ZERO);
+
 	sc->sc_rx_pool = bman_pool_create(&sc->sc_rx_bpid, MCLBYTES,
 	    DTSEC_RM_POOL_RX_MAX_SIZE, DTSEC_RM_POOL_RX_LOW_MARK,
 	    DTSEC_RM_POOL_RX_HIGH_MARK, 0, 0, dpaa_eth_pool_rx_depleted, sc);
@@ -316,11 +356,19 @@ dpaa_eth_fq_mext_free(struct mbuf *m)
 
 	buffer = m->m_ext.ext_arg1;
 	sc = m->m_ext.ext_arg2;
-	if (bman_count(sc->sc_rx_pool) <= DTSEC_RM_POOL_RX_MAX_SIZE)
-		bman_put_buffer(sc->sc_rx_pool,
-		    pmap_kextract((vm_offset_t)buffer), sc->sc_rx_bpid);
-	else
+	/*
+	 * Sloppy per-CPU sampling: no pin, no atomic.  A stray migration
+	 * between the curcpu read and the increment can only mis-attribute
+	 * one bump to the wrong CPU's counter; the sampling rate stays
+	 * within the acceptable slop window.
+	 */
+	if ((++sc->sc_rx_pool_check_cnt[curcpu].cnt &
+	    (DTSEC_RM_POOL_RX_CHECK_INTERVAL - 1)) == 0 &&
+	    bman_count(sc->sc_rx_pool) > DTSEC_RM_POOL_RX_MAX_SIZE)
 		dpaa_eth_pool_rx_put_buffer(sc, buffer, NULL);
+	else
+		bman_put_buffer(sc->sc_rx_pool,
+		    DMAP_TO_PHYS((vm_offset_t)buffer), sc->sc_rx_bpid);
 }
 
 static int
@@ -421,8 +469,6 @@ dpaa_eth_fq_tx_confirm_callback(device_t portal, struct qman_fq *fq,
 {
 	struct dpaa_eth_frame_info *fi;
 	struct dpaa_eth_softc *sc;
-	unsigned int qlen;
-	struct dpaa_sgte *sgt0;
 
 	sc = app;
 
@@ -434,25 +480,33 @@ dpaa_eth_fq_tx_confirm_callback(device_t portal, struct qman_fq *fq,
 	 * We are storing struct dpaa_eth_frame_info in first entry
 	 * of scatter-gather table.
 	 */
-	sgt0 = (struct dpaa_sgte *)PHYS_TO_DMAP(frame->addr + frame->offset);
-	fi = (struct dpaa_eth_frame_info *)PHYS_TO_DMAP(sgt0->addr);
+	fi = (struct dpaa_eth_frame_info *)PHYS_TO_DMAP(frame->addr);
 
 	/* Free transmitted frame */
 	m_freem(fi->fi_mbuf);
 	dpaa_eth_fi_free(sc, fi);
 
-	qlen = qman_fq_get_counter(sc->sc_tx_conf_fq, QMAN_COUNTER_FRAME);
-
-	if (qlen == 0) {
-		DPAA_ETH_LOCK(sc);
-
-		if (sc->sc_tx_fq_full) {
-			sc->sc_tx_fq_full = 0;
-			dpaa_eth_if_start_locked(sc);
-		}
-
-		DPAA_ETH_UNLOCK(sc);
+	/*
+	 * Fast path: TX isn't backpressured, so there's nothing to
+	 * restart.  Acquire load pairs with the release store on the
+	 * TX path so a concurrent set of the flag is observed here.
+	 */
+	if (atomic_load_acq_int(&sc->sc_tx_fq_full) == 0)
+		return (1);
+
+	/* Rate-limit the MC round-trip to detect drain-to-zero. */
+	if ((sc->sc_tx_conf_check_cnt++ &
+	    (DTSEC_TX_CONF_CHECK_INTERVAL - 1)) != 0)
+		return (1);
+	if (qman_fq_get_counter(sc->sc_tx_conf_fq, QMAN_COUNTER_FRAME) != 0)
+		return (1);
+
+	DPAA_ETH_LOCK(sc);
+	if (sc->sc_tx_fq_full) {
+		atomic_store_rel_int(&sc->sc_tx_fq_full, 0);
+		dpaa_eth_if_start_locked(sc);
 	}
+	DPAA_ETH_UNLOCK(sc);
 
 	return (1);
 }
@@ -484,8 +538,13 @@ dpaa_eth_fq_rx_init(struct dpaa_eth_softc *sc)
 	/* Default Frame Queue */
 	if (sc->sc_rx_channel == 0)
 		sc->sc_rx_channel = qman_alloc_channel();
+	/*
+	 * Stash 1 cacheline of frame annotation (parse result / IC) and
+	 * 1 of frame data head into the destination core's cache when
+	 * QMan dequeues an RX frame -- the RX callback reads both.
+	 */
 	fq = qman_fq_create(1, sc->sc_rx_channel, DTSEC_RM_FQR_RX_WQ,
-	    false, 0, false, false, true, false, 0, 0, 0);
+	    false, 0, false, false, true, false, 0, 0, 0, 1, 1);
 	if (fq == NULL) {
 		device_printf(sc->sc_dev,
 		    "could not create default RX queue\n");
@@ -529,7 +588,8 @@ dpaa_eth_fq_tx_init(struct dpaa_eth_softc *sc)
 
 	/* TX Frame Queue */
 	fq = qman_fq_create(1, sc->sc_port_tx_qman_chan,
-	    DTSEC_RM_FQR_TX_WQ, false, 0, false, false, true, false, 0, 0, 0);
+	    DTSEC_RM_FQR_TX_WQ, false, 0, false, false, true, false, 0, 0, 0,
+	    0, 0);
 	if (fq == NULL) {
 		device_printf(sc->sc_dev, "could not create default TX queue"
 		    "\n");
@@ -543,7 +603,7 @@ dpaa_eth_fq_tx_init(struct dpaa_eth_softc *sc)
 	/* TX Confirmation Frame Queue */
 	fq = qman_fq_create(1, sc->sc_rx_channel,
 	    DTSEC_RM_FQR_TX_CONF_WQ, false, 0, false, false, true, false, 0, 0,
-	    0);
+	    0, 0, 0);
 	if (fq == NULL) {
 		device_printf(sc->sc_dev, "could not create TX confirmation "
 		    "queue\n");
@@ -612,7 +672,7 @@ dpaa_eth_if_start_locked(struct dpaa_eth_softc *sc)
 {
 	vm_size_t dsize, psize, ssize;
 	struct dpaa_eth_frame_info *fi;
-	unsigned int qlen, i;
+	unsigned int i;
 	struct mbuf *m0, *m;
 	vm_offset_t vaddr;
 	struct dpaa_fd fd;
@@ -626,12 +686,16 @@ dpaa_eth_if_start_locked(struct dpaa_eth_softc *sc)
 	if ((if_getdrvflags(sc->sc_ifnet) & IFF_DRV_RUNNING) != IFF_DRV_RUNNING)
 		return;
 
-	while (!if_sendq_empty(sc->sc_ifnet)) {
-		/* Check length of the TX queue */
-		qlen = qman_fq_get_counter(sc->sc_tx_fq, QMAN_COUNTER_FRAME);
+	if (sc->sc_tx_fq_full)
+		return;
 
-		if (qlen >= DTSEC_MAX_TX_QUEUE_LEN) {
-			sc->sc_tx_fq_full = 1;
+	while (!if_sendq_empty(sc->sc_ifnet)) {
+		if ((sc->sc_tx_queue_check_cnt++ &
+		    (DTSEC_MAX_TX_QUEUE_CHECK_INTERVAL - 1)) == 0 &&
+		    qman_fq_get_counter(sc->sc_tx_fq, QMAN_COUNTER_FRAME) >=
+		    DTSEC_MAX_TX_QUEUE_LEN) {
+			atomic_store_rel_int(&sc->sc_tx_fq_full, 1);
+			sc->sc_tx_queue_check_cnt = 0;
 			return;
 		}
 
@@ -650,17 +714,11 @@ dpaa_eth_if_start_locked(struct dpaa_eth_softc *sc)
 		psize = 0;
 		dsize = 0;
 		fi->fi_mbuf = m0;
+
 		while (m && i < DPAA_NUM_OF_SG_TABLE_ENTRY) {
 			if (m->m_len == 0)
 				continue;
 
-			/*
-			 * First entry in scatter-gather table is used to keep
-			 * pointer to frame info structure.
-			 */
-			fi->fi_sgt[i].addr = pmap_kextract((vm_offset_t)fi);
-			i++;
-
 			dsize = m->m_len;
 			vaddr = (vm_offset_t)m->m_data;
 			while (dsize > 0 && i < DPAA_NUM_OF_SG_TABLE_ENTRY) {
@@ -668,7 +726,7 @@ dpaa_eth_if_start_locked(struct dpaa_eth_softc *sc)
 				if (m->m_len < ssize)
 					ssize = m->m_len;
 
-				fi->fi_sgt[i].addr = pmap_kextract(vaddr);
+				fi->fi_sgt[i].addr = dpaa_eth_va_to_phys(vaddr);
 				fi->fi_sgt[i].length = ssize;
 
 				fi->fi_sgt[i].extension = 0;
@@ -697,15 +755,14 @@ dpaa_eth_if_start_locked(struct dpaa_eth_softc *sc)
 
 		fi->fi_sgt[i - 1].final = 1;
 
-		fd.addr = pmap_kextract((vm_offset_t)&fi->fi_ic);
+		fd.addr = DMAP_TO_PHYS((vm_offset_t)fi);
 		fd.length = psize;
 		fd.format = DPAA_FD_FORMAT_SHORT_MBSF;
 
 		fd.liodn = 0;
 		fd.bpid = 0;
 		fd.eliodn = 0;
-		fd.offset = offsetof(struct dpaa_eth_frame_info, fi_sgt) -
-		    offsetof(struct dpaa_eth_frame_info, fi_ic);
+		fd.offset = offsetof(struct dpaa_eth_frame_info, fi_sgt);
 		fd.cmd_stat = dpaa_eth_tx_add_csum(fi);
 
 		DPAA_ETH_UNLOCK(sc);
diff --git a/sys/dev/dpaa/dpaa_eth.h b/sys/dev/dpaa/dpaa_eth.h
index 7832b4dd7bff..cc46dc5485d6 100644
--- a/sys/dev/dpaa/dpaa_eth.h
+++ b/sys/dev/dpaa/dpaa_eth.h
@@ -27,6 +27,10 @@
 #ifndef DPAA_ETH_H_
 #define DPAA_ETH_H_
 
+struct dpaa_pcpu_cnt {
+	u_int	cnt;
+} __aligned(CACHE_LINE_SIZE);
+
 struct dpaa_eth_softc {
 	/* XXX MII bus requires that struct ifnet is first!!! */
 	if_t				sc_ifnet;
@@ -42,6 +46,7 @@ struct dpaa_eth_softc {
 	uint8_t				sc_rx_bpid;
 	uma_zone_t			sc_rx_zone;
 	char				sc_rx_zname[64];
+	struct dpaa_pcpu_cnt		*sc_rx_pool_check_cnt;	/* per-CPU */
 
 	/* RX Frame Queue */
 	struct qman_fq			*sc_rx_fq;
@@ -49,9 +54,11 @@ struct dpaa_eth_softc {
 
 	/* TX Frame Queue */
 	struct qman_fq			*sc_tx_fq;
-	bool				sc_tx_fq_full;
+	volatile u_int			sc_tx_fq_full;
+	u_int				sc_tx_queue_check_cnt;
 	struct qman_fq			*sc_tx_conf_fq;
 	uint32_t			sc_tx_conf_fqid;
+	u_int				sc_tx_conf_check_cnt;
 
 	/* Methods */
 	int				(*sc_port_rx_init)
diff --git a/sys/dev/dpaa/qman.c b/sys/dev/dpaa/qman.c
index 9143ebde5cb4..fe609711f413 100644
--- a/sys/dev/dpaa/qman.c
+++ b/sys/dev/dpaa/qman.c
@@ -149,6 +149,13 @@
 #define	QM_FQCTRL_HOLDACTIVE		0x0002
 #define	QM_FQCTRL_LIC			0x0001
 
+/*
+ * Context_A stashing config.
+ */
+#define	QM_STASHING_EXCL_ANNOTATION	0x04
+#define	QM_STASHING_EXCL_DATA		0x02
+#define	QM_STASHING_EXCL_CONTEXT	0x01
+
 #define	QMAN_CHANNEL_POOL1_REV1		0x21
 #define	QMAN_CHANNEL_POOL1_REV3		0x401
 
@@ -496,7 +503,8 @@ qman_fq_create(uint32_t fqids_num, int channel, uint8_t wq,
     bool force_fqid, uint32_t fqid_or_align, bool init_parked,
     bool hold_active, bool prefer_in_cache, bool congst_avoid_ena,
     void *congst_group, int8_t overhead_accounting_len,
-    uint32_t tail_drop_threshold)
+    uint32_t tail_drop_threshold,
+    uint8_t annotation_cl, uint8_t data_cl)
 {
 	union qman_mc_command cmd;
 	struct qman_softc *sc;
@@ -529,6 +537,24 @@ qman_fq_create(uint32_t fqids_num, int channel, uint8_t wq,
 	    (hold_active ? QM_FQCTRL_HOLDACTIVE : 0) |
 	    (congst_avoid_ena ? QM_FQCTRL_AVOIDBLOCK : 0);
 
+	/*
+	 * Configure hardware cache stashing: on dequeue, QMan will push
+	 * the requested number of cachelines of frame annotation and/or
+	 * frame data into the destination core's cache, hiding memory
+	 * latency for the RX callback.
+	 */
+	if (annotation_cl != 0 || data_cl != 0) {
+		uint64_t excl, cl;
+
+		excl = (annotation_cl != 0 ? QM_STASHING_EXCL_ANNOTATION : 0) |
+		    (data_cl != 0 ? QM_STASHING_EXCL_DATA : 0);
+		cl = ((annotation_cl & 3) << 4) | ((data_cl & 3) << 2);
+		/* excl in wire byte 0 (bits 63-56), cl in wire byte 1. */
+		cmd.init_fq.context_a = (excl << 56) | (cl << 48);
+		cmd.init_fq.fq_ctrl |= QM_FQCTRL_CTXASTASH;
+		cmd.init_fq.we_mask |= QCSP_INIT_FQ_WE_CONTEXT_A;
+	}
+
 	critical_enter();
 
 	/* Ensure we have got QMan port initialized */
diff --git a/sys/dev/dpaa/qman.h b/sys/dev/dpaa/qman.h
index 0e841dbc6ae6..d02e6d8bf94c 100644
--- a/sys/dev/dpaa/qman.h
+++ b/sys/dev/dpaa/qman.h
@@ -245,7 +245,8 @@ struct qman_fq *qman_fq_create(uint32_t fqids_num, int channel,
     uint8_t wq, bool force_fqid, uint32_t fqid_or_align, bool init_parked,
     bool hold_active, bool prefer_in_cache, bool congst_avoid_ena,
     void *congst_group, int8_t overhead_accounting_len,
-    uint32_t tail_drop_threshold);
+    uint32_t tail_drop_threshold,
+    uint8_t annotation_cl, uint8_t data_cl);
 
 /**
  * Free Frame Queue Range.
diff --git a/sys/dev/dpaa/qman_portals.c b/sys/dev/dpaa/qman_portals.c
index 3b64aca71cb7..e0a67410884e 100644
--- a/sys/dev/dpaa/qman_portals.c
+++ b/sys/dev/dpaa/qman_portals.c
@@ -16,6 +16,8 @@
 #include <sys/proc.h>
 #include <sys/pcpu.h>
 #include <sys/sched.h>
+#include <sys/smp.h>	/* For CPU_FOREACH() */
+#include <sys/sysctl.h>
 #include <ddb/ddb.h>
 
 #include <machine/bus.h>
@@ -115,6 +117,7 @@
 #define	QCSP_IER		0x0e04
 #define	QCSP_ISDR		0x0e08
 #define	QCSP_IIR		0xe0c
+#define	QCSP_ITPR		0x0e14
 
 #define	QM_EQCR_VERB_CMD_ENQUEUE	0x01
 #define	QM_EQCR_VERB_BIT_INT		0x04
@@ -130,6 +133,85 @@ DPAA_RING(qman_eqcr, QMAN_EQCR_COUNT, QCSP_EQCR_PI_CENA, QCSP_EQCR_CI_CENA,
 DPAA_RING(qman_dqrr, QMAN_DQRR_COUNT, QCSP_DQRR_PI_CENA, QCSP_DQRR_CI_CENA,
 		QCSP_DQRR_PI_CINH, QCSP_DQRR_CI_CINH);
 
+/*
+ *  dqrr_ithresh: fire DQRR interrupt once N (of 16) dequeues queued (1-15)
+ *  mr_ithresh:   fire MR interrupt after N messages (1-7)
+ *  iperiod:      timer bound on interrupt latency, in units of 256 QMan
+ *                clocks (~32 us on typical QorIQ platform clock at 100)
+ */
+static int qman_dqrr_ithresh = 12;
+static int qman_mr_ithresh = 4;
+static int qman_iperiod = 100;
+
+static void
+qman_portal_update_reg(bus_size_t reg, uint32_t val)
+{
+	struct qman_portal_softc *sc;
+	device_t portal;
+	int cpu;
+
+	CPU_FOREACH(cpu) {
+		portal = DPCPU_ID_GET(cpu, qman_affine_portal);
+		if (portal == NULL)
+			continue;
+		sc = device_get_softc(portal);
+		bus_write_4(sc->sc_base.sc_mres[1], reg, val);
+	}
+}
+
+static int
+qman_sysctl_reg(struct sysctl_oid *oidp, struct sysctl_req *req,
+    int *var, int min, int max, bus_size_t reg)
+{
+	int val, error;
+
+	val = *var;
+	error = sysctl_handle_int(oidp, &val, 0, req);
+	if (error != 0 || req->newptr == NULL)
+		return (error);
+	if (val < min || val > max)
+		return (EINVAL);
+	*var = val;
+	qman_portal_update_reg(reg, val);
+	return (0);
+}
+
+static int
+qman_sysctl_dqrr_ithresh(SYSCTL_HANDLER_ARGS)
+{
+	return (qman_sysctl_reg(oidp, req,
+	    &qman_dqrr_ithresh, 1, 15, QCSP_DQRR_ITR));
+}
+
+static int
+qman_sysctl_mr_ithresh(SYSCTL_HANDLER_ARGS)
+{
+	return (qman_sysctl_reg(oidp, req,
+	    &qman_mr_ithresh, 1, 7, QCSP_MR_ITR));
+}
+
+static int
+qman_sysctl_iperiod(SYSCTL_HANDLER_ARGS)
+{
+	return (qman_sysctl_reg(oidp, req,
+	    &qman_iperiod, 1, 65535, QCSP_ITPR));
+}
+
+SYSCTL_NODE(_hw, OID_AUTO, qman, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
+    "QMan portal tunables");
+SYSCTL_PROC(_hw_qman, OID_AUTO, dqrr_ithresh,
+    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+    NULL, 0, qman_sysctl_dqrr_ithresh, "I",
+    "DQRR interrupt threshold (dequeues queued before interrupt; 1-15)");
+SYSCTL_PROC(_hw_qman, OID_AUTO, mr_ithresh,
+    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+    NULL, 0, qman_sysctl_mr_ithresh, "I",
+    "Message Ring interrupt threshold (messages queued before interrupt; 1-7)");
+SYSCTL_PROC(_hw_qman, OID_AUTO, iperiod,
+    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+    NULL, 0, qman_sysctl_iperiod, "I",
+    "Interrupt time period (units of 256 QMan clocks)");
+
 /*
  * pmode: one of the CFG_EPM constants.
  * stash_prio: 0 or CFG_EP
@@ -208,6 +290,12 @@ qman_portal_attach(device_t dev, int cpu)
 	}
 	sc->sc_affine_channel = cell;
 	DPCPU_ID_SET(cpu, qman_affine_portal, dev);
+
+	/* Interrupt coalescing.  See tunables above. */
+	bus_write_4(sc->sc_base.sc_mres[1], QCSP_DQRR_ITR, qman_dqrr_ithresh);
+	bus_write_4(sc->sc_base.sc_mres[1], QCSP_MR_ITR, qman_mr_ithresh);
+	bus_write_4(sc->sc_base.sc_mres[1], QCSP_ITPR, qman_iperiod);
+
 	bus_write_4(sc->sc_base.sc_mres[1], QCSP_IER,
 	    QM_PIRQ_EQCI | QM_PIRQ_EQRI | QM_PIRQ_MRI | QM_PIRQ_CSCI |
 	    QM_PIRQ_DQRI);
diff --git a/sys/dev/dpaa/qman_var.h b/sys/dev/dpaa/qman_var.h
index 8ed36bbb6a35..6d13340ca365 100644
--- a/sys/dev/dpaa/qman_var.h
+++ b/sys/dev/dpaa/qman_var.h
@@ -44,7 +44,7 @@ union qman_mc_command {
 		uint16_t ics_cred;
 		uint16_t td_thresh_oac;
 		uint32_t context_b;
-		uint32_t context_a;
+		uint64_t context_a;
 		uint8_t _rsvd1[32];
 	} init_fq;
 	struct {
@@ -92,7 +92,7 @@ union qman_mc_result {
 		uint16_t ics_cred;
 		uint16_t td_thresh;
 		uint32_t context_b;
-		uint32_t context_a;
+		uint64_t context_a;	/* 8 bytes on the wire */
 		uint16_t oac;
 		uint8_t _rsvd1[30];
 	} query_fq;
@@ -130,6 +130,8 @@ union qman_mc_result {
 		uint8_t _rsvd[61];
 	} alter_fqs;
 };
+_Static_assert(sizeof(union qman_mc_command) == 64, "MC command mis-sized");
+_Static_assert(sizeof(union qman_mc_result) == 64, "MC result mis-sized");
 
 struct qman_mc {
 	uint8_t polarity;