git: 22f37c46b484 - main - if_bnxt: rework interrupt coalescing onto the AGGINT_QCAPS scheme

From: Sumit Saxena <ssaxena_at_FreeBSD.org>
Date: Fri, 09 Oct 2026 10:12:57 UTC
The branch main has been updated by ssaxena:

URL: https://cgit.FreeBSD.org/src/commit/?id=22f37c46b4841fec22612fd4bc485f0515f27a28

commit 22f37c46b4841fec22612fd4bc485f0515f27a28
Author:     Sumit Saxena <sumit.saxena@broadcom.com>
AuthorDate: 2026-08-03 08:52:00 +0000
Commit:     Sumit Saxena <ssaxena@FreeBSD.org>
CommitDate: 2026-10-09 08:24:45 +0000

    if_bnxt: rework interrupt coalescing onto the AGGINT_QCAPS scheme
    
    Query firmware-advertised coalescing capabilities and program
    per-direction rx/tx coalescing settings against them, replacing the
    old hardcoded scheme. Add sysctls for the coalescing mode, budget,
    and stats-timer knobs the new scheme exposes.
    
    Reviewed by:    gallatin
    MFC after:      2 weeks
    Sponsored by:   Broadcom Inc.
    Differential Revision:  https://reviews.freebsd.org/D58621
---
 sys/dev/bnxt/bnxt_en/bnxt.h        |  64 ++++++++--
 sys/dev/bnxt/bnxt_en/bnxt_hwrm.c   | 250 +++++++++++++++++++++++++++---------
 sys/dev/bnxt/bnxt_en/bnxt_hwrm.h   |   1 +
 sys/dev/bnxt/bnxt_en/bnxt_sysctl.c | 253 ++++++++++++++++++++++++++++++++++---
 sys/dev/bnxt/bnxt_en/if_bnxt.c     |  50 ++++++++
 5 files changed, 534 insertions(+), 84 deletions(-)

diff --git a/sys/dev/bnxt/bnxt_en/bnxt.h b/sys/dev/bnxt/bnxt_en/bnxt.h
index e8ffc9fdb057..f93f0d4fe794 100644
--- a/sys/dev/bnxt/bnxt_en/bnxt.h
+++ b/sys/dev/bnxt/bnxt_en/bnxt.h
@@ -813,6 +813,56 @@ struct bnxt_hw_lro {
 	uint32_t min_agg_len;
 };
 
+#define BNXT_LEGACY_COAL_CMPL_PARAMS					\
+	(HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_INT_LAT_TMR_MIN |		\
+	 HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_INT_LAT_TMR_MAX |		\
+	 HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_TIMER_RESET |		\
+	 HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_RING_IDLE |			\
+	 HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_NUM_CMPL_DMA_AGGR |		\
+	 HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_NUM_CMPL_DMA_AGGR_DURING_INT | \
+	 HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_CMPL_AGGR_DMA_TMR |		\
+	 HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_CMPL_AGGR_DMA_TMR_DURING_INT | \
+	 HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_NUM_CMPL_AGGR_INT)
+
+#define BNXT_COAL_CMPL_ENABLES						\
+	(HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_ENABLES_NUM_CMPL_DMA_AGGR | \
+	 HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_ENABLES_CMPL_AGGR_DMA_TMR | \
+	 HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_ENABLES_INT_LAT_TMR_MAX | \
+	 HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_ENABLES_NUM_CMPL_AGGR_INT)
+
+#define BNXT_COAL_CMPL_MIN_TMR_ENABLE					\
+	HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_ENABLES_INT_LAT_TMR_MIN
+
+/* Gates cmpl_aggr_dma_tmr_during_int, but reuses num_cmpl_dma_aggr_during_int's
+ * enable bit - there's no separate bit for the timer field. */
+#define BNXT_COAL_CMPL_AGGR_TMR_DURING_INT_ENABLE			\
+	HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_ENABLES_NUM_CMPL_DMA_AGGR_DURING_INT
+
+struct bnxt_coal_cap {
+	uint32_t			cmpl_params;
+	uint32_t			nq_params;
+	uint16_t			num_cmpl_dma_aggr_max;
+	uint16_t			num_cmpl_dma_aggr_during_int_max;
+	uint16_t			cmpl_aggr_dma_tmr_max;
+	uint16_t			cmpl_aggr_dma_tmr_during_int_max;
+	uint16_t			int_lat_tmr_min_max;
+	uint16_t			int_lat_tmr_max_max;
+	uint16_t			num_cmpl_aggr_int_max;
+	uint16_t			timer_units;
+};
+
+struct bnxt_coal {
+	uint16_t			coal_ticks;
+	uint16_t			coal_ticks_irq;
+	uint16_t			coal_bufs;
+	uint16_t			coal_bufs_irq;
+	uint16_t			idle_thresh; /* RING_IDLE enabled when coal ticks < idle_thresh  */
+	uint8_t				bufs_per_record;
+	uint16_t			budget;
+	uint16_t			flags;
+	uint8_t				timer_reset_during_ring_alloc;
+};
+
 /* The hardware supports certain page sizes.  Use the supported page sizes
  * to allocate the rings.
  */
@@ -1367,15 +1417,12 @@ struct bnxt_softc {
 	volatile int detached;
 	struct bnxt_hw_lro	hw_lro;
 	uint8_t wol_filter_id;
-	uint16_t		rx_coal_usecs;
-	uint16_t		rx_coal_usecs_irq;
-	uint16_t               	rx_coal_frames;
-	uint16_t               	rx_coal_frames_irq;
-	uint16_t               	tx_coal_usecs;
-	uint16_t               	tx_coal_usecs_irq;
-	uint16_t               	tx_coal_frames;
-	uint16_t		tx_coal_frames_irq;
 
+	struct bnxt_coal_cap	coal_cap;
+	struct bnxt_coal	rx_coal;
+	struct bnxt_coal	tx_coal;
+
+	uint32_t		stats_coal_ticks;
 #define BNXT_USEC_TO_COAL_TIMER(x)      ((x) * 25 / 2)
 #define BNXT_DEF_STATS_COAL_TICKS        1000000
 #define BNXT_MIN_STATS_COAL_TICKS         250000
@@ -1476,6 +1523,7 @@ struct bnxt_softc {
 #define BNXT_STATE_FW_NON_FATAL_COND	13
 #define BNXT_STATE_FW_ACTIVATE_RESET	14
 #define BNXT_STATE_HALF_OPEN		15
+#define BNXT_STATE_UP			16
 #define BNXT_NO_FW_ACCESS(bp)		\
 	test_bit(BNXT_STATE_FW_FATAL_COND, &(bp)->state)
 	struct pci_dev			*pdev;
diff --git a/sys/dev/bnxt/bnxt_en/bnxt_hwrm.c b/sys/dev/bnxt/bnxt_en/bnxt_hwrm.c
index b9cd7174209c..77e43079fd96 100644
--- a/sys/dev/bnxt/bnxt_en/bnxt_hwrm.c
+++ b/sys/dev/bnxt/bnxt_en/bnxt_hwrm.c
@@ -2136,6 +2136,9 @@ bnxt_hwrm_ring_alloc(struct bnxt_softc *softc, uint8_t type,
 			req.nq_ring_id = htole16(softc->nq_rings[idx].ring.phys_id);
 		req.enables |= htole32(
 			HWRM_RING_ALLOC_INPUT_ENABLES_NQ_RING_ID_VALID);
+
+		if (softc->rx_coal.timer_reset_during_ring_alloc)
+			req.flags |= HWRM_RING_ALLOC_INPUT_FLAGS_TIMER_RESET;
 		break;
 	case HWRM_RING_ALLOC_INPUT_RING_TYPE_NQ:
 		req.int_mode = HWRM_RING_ALLOC_INPUT_INT_MODE_MSIX;
@@ -3475,81 +3478,208 @@ bnxt_hwrm_free_wol_fltr(struct bnxt_softc *softc)
 	return hwrm_send_message(softc, &req, sizeof(req));
 }
 
-static void bnxt_hwrm_set_coal_params(struct bnxt_softc *softc, uint32_t max_frames,
-        uint32_t buf_tmrs, uint16_t flags,
-        struct hwrm_ring_cmpl_ring_cfg_aggint_params_input *req)
+void
+bnxt_hwrm_coal_params_qcaps(struct bnxt_softc *softc)
 {
-        req->flags = htole16(flags);
-        req->num_cmpl_dma_aggr = htole16((uint16_t)max_frames);
-        req->num_cmpl_dma_aggr_during_int = htole16(max_frames >> 16);
-        req->cmpl_aggr_dma_tmr = htole16((uint16_t)buf_tmrs);
-        req->cmpl_aggr_dma_tmr_during_int = htole16(buf_tmrs >> 16);
-        /* Minimum time between 2 interrupts set to buf_tmr x 2 */
-        req->int_lat_tmr_min = htole16((uint16_t)buf_tmrs * 2);
-        req->int_lat_tmr_max = htole16((uint16_t)buf_tmrs * 4);
-        req->num_cmpl_aggr_int = htole16((uint16_t)max_frames * 4);
+	struct bnxt_coal_cap *coal_cap = &softc->coal_cap;
+	struct hwrm_ring_aggint_qcaps_output *resp =
+	    (void *)softc->hwrm_cmd_resp.idi_vaddr;
+	struct hwrm_ring_aggint_qcaps_input req = {0};
+	int rc;
+
+	coal_cap->cmpl_params = BNXT_LEGACY_COAL_CMPL_PARAMS;
+	coal_cap->num_cmpl_dma_aggr_max = 63;
+	coal_cap->num_cmpl_dma_aggr_during_int_max = 63;
+	coal_cap->cmpl_aggr_dma_tmr_max = 65535;
+	coal_cap->cmpl_aggr_dma_tmr_during_int_max = 65535;
+	coal_cap->int_lat_tmr_min_max = 65535;
+	coal_cap->int_lat_tmr_max_max = 65535;
+	coal_cap->num_cmpl_aggr_int_max = 65535;
+	coal_cap->timer_units = 80;
+
+	if (softc->hwrm_spec_code < 0x10902)
+		return;
+
+	bnxt_hwrm_cmd_hdr_init(softc, &req, HWRM_RING_AGGINT_QCAPS);
+
+	BNXT_HWRM_LOCK(softc);
+	rc = _hwrm_send_message(softc, &req, sizeof(req));
+	if (rc) {
+		BNXT_HWRM_UNLOCK(softc);
+		return;
+	}
+
+	coal_cap->cmpl_params = le32toh(resp->cmpl_params);
+	coal_cap->nq_params = le32toh(resp->nq_params);
+	coal_cap->num_cmpl_dma_aggr_max = le16toh(resp->num_cmpl_dma_aggr_max);
+	coal_cap->num_cmpl_dma_aggr_during_int_max =
+	    le16toh(resp->num_cmpl_dma_aggr_during_int_max);
+	coal_cap->cmpl_aggr_dma_tmr_max = le16toh(resp->cmpl_aggr_dma_tmr_max);
+	coal_cap->cmpl_aggr_dma_tmr_during_int_max =
+	    le16toh(resp->cmpl_aggr_dma_tmr_during_int_max);
+	coal_cap->int_lat_tmr_min_max = le16toh(resp->int_lat_tmr_min_max);
+	coal_cap->int_lat_tmr_max_max = le16toh(resp->int_lat_tmr_max_max);
+	coal_cap->num_cmpl_aggr_int_max = le16toh(resp->num_cmpl_aggr_int_max);
+	coal_cap->timer_units = le16toh(resp->timer_units);
+	BNXT_HWRM_UNLOCK(softc);
 }
 
-int bnxt_hwrm_set_coal(struct bnxt_softc *softc)
+static uint16_t
+bnxt_usec_to_coal_tmr(struct bnxt_softc *softc, uint16_t usec)
 {
-	int i, rc = 0;
-	struct hwrm_ring_cmpl_ring_cfg_aggint_params_input req_rx = {0},
-							   req_tx = {0}, *req;
-	uint16_t max_buf, max_buf_irq;
-	uint16_t buf_tmr, buf_tmr_irq;
-	uint32_t flags;
+	struct bnxt_coal_cap *coal_cap = &softc->coal_cap;
 
-	bnxt_hwrm_cmd_hdr_init(softc, &req_rx,
-			       HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS);
-	bnxt_hwrm_cmd_hdr_init(softc, &req_tx,
-			       HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS);
+	return (usec * 1000 / coal_cap->timer_units);
+}
 
-	/* Each rx completion (2 records) should be DMAed immediately.
-	 * DMA 1/4 of the completion buffers at a time.
-	 */
-	max_buf = min_t(uint16_t, softc->rx_coal_frames / 4, 2);
-	/* max_buf must not be zero */
-	max_buf = clamp_t(uint16_t, max_buf, 1, 63);
-	max_buf_irq = clamp_t(uint16_t, softc->rx_coal_frames_irq, 1, 63);
-	buf_tmr = BNXT_USEC_TO_COAL_TIMER(softc->rx_coal_usecs);
-	/* buf timer set to 1/4 of interrupt timer */
-	buf_tmr = max_t(uint16_t, buf_tmr / 4, 1);
-	buf_tmr_irq = BNXT_USEC_TO_COAL_TIMER(softc->rx_coal_usecs_irq);
-	buf_tmr_irq = max_t(uint16_t, buf_tmr_irq, 1);
-
-	flags = HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_FLAGS_TIMER_RESET;
-
-	/* RING_IDLE generates more IRQs for lower latency.  Enable it only
-	 * if coal_usecs is less than 25 us.
+static void
+bnxt_hwrm_set_coal_params(struct bnxt_softc *softc, struct bnxt_coal *hw_coal,
+    struct hwrm_ring_cmpl_ring_cfg_aggint_params_input *req)
+{
+	struct bnxt_coal_cap *coal_cap = &softc->coal_cap;
+	uint16_t val, tmr, max, flags = hw_coal->flags;
+	uint32_t cmpl_params = coal_cap->cmpl_params;
+
+	max = hw_coal->bufs_per_record * 128;
+	if (hw_coal->budget)
+		max = hw_coal->bufs_per_record * hw_coal->budget;
+	max = min_t(uint16_t, max, coal_cap->num_cmpl_aggr_int_max);
+
+	val = clamp_t(uint16_t, hw_coal->coal_bufs, 1, max);
+	req->num_cmpl_aggr_int = htole16(val);
+
+	val = min_t(uint16_t, val, coal_cap->num_cmpl_dma_aggr_max);
+	req->num_cmpl_dma_aggr = htole16(val);
+
+	val = clamp_t(uint16_t, hw_coal->coal_bufs_irq, 1,
+	    coal_cap->num_cmpl_dma_aggr_during_int_max);
+	req->num_cmpl_dma_aggr_during_int = htole16(val);
+
+	tmr = bnxt_usec_to_coal_tmr(softc, hw_coal->coal_ticks);
+	tmr = clamp_t(uint16_t, tmr, 1, coal_cap->int_lat_tmr_max_max);
+	req->int_lat_tmr_max = htole16(tmr);
+
+	/* Minimum timer set to 1/2 of the interrupt timer. */
+	if (cmpl_params &
+	    HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_INT_LAT_TMR_MIN) {
+		val = tmr / 2;
+		val = clamp_t(uint16_t, val, 1, coal_cap->int_lat_tmr_min_max);
+		req->int_lat_tmr_min = htole16(val);
+		req->enables |= htole16(BNXT_COAL_CMPL_MIN_TMR_ENABLE);
+	}
+
+	/* Buf timer set to 1/4 of the interrupt timer. */
+	val = clamp_t(uint16_t, tmr / 4, 1, coal_cap->cmpl_aggr_dma_tmr_max);
+	req->cmpl_aggr_dma_tmr = htole16(val);
+
+	/*
+	 * Gate on CMPL_AGGR_DMA_TMR_DURING_INT, not the similarly-named
+	 * NUM_CMPL_DMA_AGGR_DURING_INT bit which covers the unrelated count field.
 	 */
-	if (softc->rx_coal_usecs < 25)
+	if (cmpl_params &
+	    HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_CMPL_AGGR_DMA_TMR_DURING_INT) {
+		tmr = bnxt_usec_to_coal_tmr(softc, hw_coal->coal_ticks_irq);
+		val = clamp_t(uint16_t, tmr, 1,
+		    coal_cap->cmpl_aggr_dma_tmr_during_int_max);
+		req->cmpl_aggr_dma_tmr_during_int = htole16(val);
+		/*
+		 * No dedicated enable bit exists for this field; reusing
+		 * NUM_CMPL_DMA_AGGR_DURING_INT's enable bit matches existing behavior.
+		 */
+		req->enables |=
+		    htole16(BNXT_COAL_CMPL_AGGR_TMR_DURING_INT_ENABLE);
+	}
+
+	if ((cmpl_params & HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_RING_IDLE) &&
+	    hw_coal->idle_thresh && hw_coal->coal_ticks < hw_coal->idle_thresh)
 		flags |= HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_FLAGS_RING_IDLE;
 
-	bnxt_hwrm_set_coal_params(softc, max_buf_irq << 16 | max_buf,
-				  buf_tmr_irq << 16 | buf_tmr, flags, &req_rx);
-
-	/* max_buf must not be zero */
-	max_buf = clamp_t(uint16_t, softc->tx_coal_frames, 1, 63);
-	max_buf_irq = clamp_t(uint16_t, softc->tx_coal_frames_irq, 1, 63);
-	buf_tmr = BNXT_USEC_TO_COAL_TIMER(softc->tx_coal_usecs);
-	/* buf timer set to 1/4 of interrupt timer */
-	buf_tmr = max_t(uint16_t, buf_tmr / 4, 1);
-	buf_tmr_irq = BNXT_USEC_TO_COAL_TIMER(softc->tx_coal_usecs_irq);
-	buf_tmr_irq = max_t(uint16_t, buf_tmr_irq, 1);
-	flags = HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_FLAGS_TIMER_RESET;
-	bnxt_hwrm_set_coal_params(softc, max_buf_irq << 16 | max_buf,
-				  buf_tmr_irq << 16 | buf_tmr, flags, &req_tx);
+	req->flags = htole16(flags);
+	req->enables |= htole16(BNXT_COAL_CMPL_ENABLES);
+}
 
-	for (i = 0; i < softc->nrxqsets; i++) {
+static int
+bnxt_hwrm_set_coal_nq(struct bnxt_softc *softc, uint32_t ring_id,
+    struct bnxt_coal *hw_coal)
+{
+	struct hwrm_ring_cmpl_ring_cfg_aggint_params_input req = {0};
+	struct bnxt_coal_cap *coal_cap = &softc->coal_cap;
+	uint16_t tmr;
+
+	if (!(coal_cap->nq_params &
+	    HWRM_RING_AGGINT_QCAPS_OUTPUT_NQ_PARAMS_INT_LAT_TMR_MIN))
+		return (0);
+
+	bnxt_hwrm_cmd_hdr_init(softc, &req,
+	    HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS);
+
+	req.ring_id = htole16(ring_id);
+	req.flags =
+	    htole16(HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_FLAGS_IS_NQ);
+
+	tmr = bnxt_usec_to_coal_tmr(softc, hw_coal->coal_ticks) / 2;
+	tmr = clamp_t(uint16_t, tmr, 1, coal_cap->int_lat_tmr_min_max);
+	req.int_lat_tmr_min = htole16(tmr);
+	req.enables |= htole16(BNXT_COAL_CMPL_MIN_TMR_ENABLE);
+
+	return (hwrm_send_message(softc, &req, sizeof(req)));
+}
+
+int bnxt_hwrm_set_coal(struct bnxt_softc *softc)
+{
+	struct hwrm_ring_cmpl_ring_cfg_aggint_params_input req = {0};
+	int i, rc = 0;
+
+	if (!test_bit(BNXT_STATE_UP, &softc->state))
+		return (rc);
+
+	bnxt_hwrm_cmd_hdr_init(softc, &req,
+	    HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS);
+	bnxt_hwrm_set_coal_params(softc, &softc->rx_coal, &req);
 
-		req = &req_rx;
-		req->ring_id = htole16(softc->grp_info[i].cp_ring_id);
+	for (i = 0; i < softc->nrxqsets; i++) {
+		req.ring_id = htole16(softc->grp_info[i].cp_ring_id);
 
-		rc = hwrm_send_message(softc, req, sizeof(*req));
+		rc = hwrm_send_message(softc, &req, sizeof(req));
 		if (rc)
 			break;
+
+		if (BNXT_CHIP_P5_PLUS(softc))
+			rc = bnxt_hwrm_set_coal_nq(softc,
+			    softc->nq_rings[i].ring.phys_id, &softc->rx_coal);
+		else
+			rc = bnxt_hwrm_set_coal_nq(softc,
+			    softc->rx_cp_rings[i].ring.phys_id, &softc->rx_coal);
+		if (rc) {
+			device_printf(softc->dev,
+			    "%s: Rx NQ coalesce setting failed for queue %d\n",
+			    __func__, i);
+			break;
+		}
 	}
-	return rc;
+
+	if (softc->fw_cap & BNXT_FLAG_TX_COAL_CMPL) {
+		struct hwrm_ring_cmpl_ring_cfg_aggint_params_input req_tx = {0};
+
+		bnxt_hwrm_cmd_hdr_init(softc, &req_tx,
+		    HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS);
+		bnxt_hwrm_set_coal_params(softc, &softc->tx_coal, &req_tx);
+
+		for (i = 0; i < softc->ntxqsets; i++) {
+			req_tx.ring_id =
+			    htole16(softc->tx_cp_rings[i].ring.phys_id);
+
+			rc = hwrm_send_message(softc, &req_tx, sizeof(req_tx));
+			if (rc) {
+				device_printf(softc->dev,
+				    "%s: Tx coalesce setting failed for queue %d\n",
+				    __func__, i);
+				break;
+			}
+		}
+	}
+
+	return (rc);
 }
 
 void bnxt_hwrm_ring_info_get(struct bnxt_softc *softc, uint8_t ring_type,
diff --git a/sys/dev/bnxt/bnxt_en/bnxt_hwrm.h b/sys/dev/bnxt/bnxt_en/bnxt_hwrm.h
index e507b716303d..d3b25a243ec5 100644
--- a/sys/dev/bnxt/bnxt_en/bnxt_hwrm.h
+++ b/sys/dev/bnxt/bnxt_en/bnxt_hwrm.h
@@ -128,6 +128,7 @@ uint16_t bnxt_hwrm_get_wol_fltrs(struct bnxt_softc *softc, uint16_t handle);
 int bnxt_hwrm_alloc_wol_fltr(struct bnxt_softc *softc);
 int bnxt_hwrm_free_wol_fltr(struct bnxt_softc *softc);
 int bnxt_hwrm_set_coal(struct bnxt_softc *softc);
+void bnxt_hwrm_coal_params_qcaps(struct bnxt_softc *softc);
 int bnxt_hwrm_func_rgtr_async_events(struct bnxt_softc *softc, unsigned long *bmap,
 				     int bmap_size);
 int bnxt_hwrm_func_backing_store_qcaps(struct bnxt_softc *softc);
diff --git a/sys/dev/bnxt/bnxt_en/bnxt_sysctl.c b/sys/dev/bnxt/bnxt_en/bnxt_sysctl.c
index 37a878c9d85f..cb906c38b906 100644
--- a/sys/dev/bnxt/bnxt_en/bnxt_sysctl.c
+++ b/sys/dev/bnxt/bnxt_en/bnxt_sysctl.c
@@ -1604,21 +1604,46 @@ bnxt_vlan_strip_sysctl(SYSCTL_HANDLER_ARGS) {
 	return rc;
 }
 
+/*
+ * sysctl_handle_int() allows any int-sized value; reject anything that
+ * would truncate/overflow when narrowed into the uint16_t coal field.
+ */
+static int
+bnxt_sysctl_check_u16_range(struct bnxt_softc *softc, const char *what,
+    int val, uint16_t mult)
+{
+	int max = mult ? (UINT16_MAX / mult) : UINT16_MAX;
+
+	if (val < 0 || val > max) {
+		device_printf(softc->dev,
+		    "%s: value %d out of range [0, %d]\n", what, val, max);
+		return EINVAL;
+	}
+	return 0;
+}
+
 static int
 bnxt_set_coal_rx_usecs(SYSCTL_HANDLER_ARGS) {
 	struct bnxt_softc *softc = arg1;
+	struct bnxt_coal *hw_coal;
 	int rc;
 	int val;
 
 	if (softc == NULL)
 		return EBUSY;
 
-	val = softc->rx_coal_usecs;
+	hw_coal = &softc->rx_coal;
+	val = hw_coal->coal_ticks;
+
 	rc = sysctl_handle_int(oidp, &val, 0, req);
 	if (rc || !req->newptr)
 		return rc;
 
-	softc->rx_coal_usecs = val;
+	rc = bnxt_sysctl_check_u16_range(softc, __func__, val, 0);
+	if (rc)
+		return rc;
+
+	hw_coal->coal_ticks = val;
 	rc = bnxt_hwrm_set_coal(softc);
 
 	return rc;
@@ -1627,18 +1652,33 @@ bnxt_set_coal_rx_usecs(SYSCTL_HANDLER_ARGS) {
 static int
 bnxt_set_coal_rx_frames(SYSCTL_HANDLER_ARGS) {
 	struct bnxt_softc *softc = arg1;
+	struct bnxt_coal *hw_coal;
+	uint16_t mult;
 	int rc;
 	int val;
 
 	if (softc == NULL)
 		return EBUSY;
 
-	val = softc->rx_coal_frames;
+	hw_coal = &softc->rx_coal;
+	mult = hw_coal->bufs_per_record;
+	if (!mult) {
+		device_printf(softc->dev,
+		    "%s: bufs_per_record is zero which is invalid\n", __func__);
+		return EINVAL;
+	}
+
+	val = hw_coal->coal_bufs / mult;
+
 	rc = sysctl_handle_int(oidp, &val, 0, req);
 	if (rc || !req->newptr)
 		return rc;
 
-	softc->rx_coal_frames = val;
+	rc = bnxt_sysctl_check_u16_range(softc, __func__, val, mult);
+	if (rc)
+		return rc;
+
+	hw_coal->coal_bufs = val * mult;
 	rc = bnxt_hwrm_set_coal(softc);
 
 	return rc;
@@ -1647,18 +1687,25 @@ bnxt_set_coal_rx_frames(SYSCTL_HANDLER_ARGS) {
 static int
 bnxt_set_coal_rx_usecs_irq(SYSCTL_HANDLER_ARGS) {
 	struct bnxt_softc *softc = arg1;
+	struct bnxt_coal *hw_coal;
 	int rc;
 	int val;
 
 	if (softc == NULL)
 		return EBUSY;
 
-	val = softc->rx_coal_usecs_irq;
+	hw_coal = &softc->rx_coal;
+	val = hw_coal->coal_ticks_irq;
+
 	rc = sysctl_handle_int(oidp, &val, 0, req);
 	if (rc || !req->newptr)
 		return rc;
 
-	softc->rx_coal_usecs_irq = val;
+	rc = bnxt_sysctl_check_u16_range(softc, __func__, val, 0);
+	if (rc)
+		return rc;
+
+	hw_coal->coal_ticks_irq = val;
 	rc = bnxt_hwrm_set_coal(softc);
 
 	return rc;
@@ -1667,38 +1714,111 @@ bnxt_set_coal_rx_usecs_irq(SYSCTL_HANDLER_ARGS) {
 static int
 bnxt_set_coal_rx_frames_irq(SYSCTL_HANDLER_ARGS) {
 	struct bnxt_softc *softc = arg1;
+	struct bnxt_coal *hw_coal;
+	uint16_t mult;
 	int rc;
 	int val;
 
 	if (softc == NULL)
 		return EBUSY;
 
-	val = softc->rx_coal_frames_irq;
+	hw_coal = &softc->rx_coal;
+	mult = hw_coal->bufs_per_record;
+	if (!mult) {
+		device_printf(softc->dev,
+		    "%s: bufs_per_record is zero which is invalid\n", __func__);
+		return EINVAL;
+	}
+
+	val = hw_coal->coal_bufs_irq / mult;
+
 	rc = sysctl_handle_int(oidp, &val, 0, req);
 	if (rc || !req->newptr)
 		return rc;
 
-	softc->rx_coal_frames_irq = val;
+	rc = bnxt_sysctl_check_u16_range(softc, __func__, val, mult);
+	if (rc)
+		return rc;
+
+	hw_coal->coal_bufs_irq = val * mult;
 	rc = bnxt_hwrm_set_coal(softc);
 
 	return rc;
 }
 
+static int
+bnxt_set_rx_coalesce_mode(SYSCTL_HANDLER_ARGS) {
+	struct bnxt_softc *softc = arg1;
+	struct bnxt_coal_cap *coal_cap;
+	struct bnxt_coal *hw_coal;
+	uint16_t flags;
+	int val;
+	int rc;
+
+	if (softc == NULL)
+		return EBUSY;
+
+	coal_cap = &softc->coal_cap;
+
+	if (coal_cap->cmpl_params &
+	    HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_TIMER_RESET) {
+		hw_coal = &softc->rx_coal;
+		flags = hw_coal->flags;
+
+		val = flags & HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_FLAGS_TIMER_RESET;
+		rc = sysctl_handle_int(oidp, &val, 0, req);
+		if (rc || !req->newptr)
+			return rc;
+
+		if (val)
+			flags |= HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_FLAGS_TIMER_RESET;
+		else
+			flags &= ~HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_FLAGS_TIMER_RESET;
+
+		hw_coal->flags = flags;
+		rc = bnxt_hwrm_set_coal(softc);
+		return rc;
+	}
+
+	if (coal_cap->cmpl_params &
+	    HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_TMR_RESET_ON_ALLOC) {
+		hw_coal = &softc->rx_coal;
+		val = hw_coal->timer_reset_during_ring_alloc;
+		rc = sysctl_handle_int(oidp, &val, 0, req);
+		if (rc || !req->newptr)
+			return rc;
+
+		/* Takes effect on next ring (re)alloc, so no bnxt_hwrm_set_coal() call needed here. */
+		hw_coal->timer_reset_during_ring_alloc = val ? 1 : 0;
+
+		return rc;
+	}
+
+	return EOPNOTSUPP;
+}
+
 static int
 bnxt_set_coal_tx_usecs(SYSCTL_HANDLER_ARGS) {
 	struct bnxt_softc *softc = arg1;
+	struct bnxt_coal *hw_coal;
 	int rc;
 	int val;
 
 	if (softc == NULL)
 		return EBUSY;
 
-	val = softc->tx_coal_usecs;
+	hw_coal = &softc->tx_coal;
+	val = hw_coal->coal_ticks;
+
 	rc = sysctl_handle_int(oidp, &val, 0, req);
 	if (rc || !req->newptr)
 		return rc;
 
-	softc->tx_coal_usecs = val;
+	rc = bnxt_sysctl_check_u16_range(softc, __func__, val, 0);
+	if (rc)
+		return rc;
+
+	hw_coal->coal_ticks = val;
 	rc = bnxt_hwrm_set_coal(softc);
 
 	return rc;
@@ -1707,18 +1827,33 @@ bnxt_set_coal_tx_usecs(SYSCTL_HANDLER_ARGS) {
 static int
 bnxt_set_coal_tx_frames(SYSCTL_HANDLER_ARGS) {
 	struct bnxt_softc *softc = arg1;
+	struct bnxt_coal *hw_coal;
+	uint16_t mult;
 	int rc;
 	int val;
 
 	if (softc == NULL)
 		return EBUSY;
 
-	val = softc->tx_coal_frames;
+	hw_coal = &softc->tx_coal;
+	mult = hw_coal->bufs_per_record;
+	if (!mult) {
+		device_printf(softc->dev,
+		    "%s: bufs_per_record is zero which is invalid\n", __func__);
+		return EINVAL;
+	}
+
+	val = hw_coal->coal_bufs / mult;
+
 	rc = sysctl_handle_int(oidp, &val, 0, req);
 	if (rc || !req->newptr)
 		return rc;
 
-	softc->tx_coal_frames = val;
+	rc = bnxt_sysctl_check_u16_range(softc, __func__, val, mult);
+	if (rc)
+		return rc;
+
+	hw_coal->coal_bufs = val * mult;
 	rc = bnxt_hwrm_set_coal(softc);
 
 	return rc;
@@ -1727,18 +1862,25 @@ bnxt_set_coal_tx_frames(SYSCTL_HANDLER_ARGS) {
 static int
 bnxt_set_coal_tx_usecs_irq(SYSCTL_HANDLER_ARGS) {
 	struct bnxt_softc *softc = arg1;
+	struct bnxt_coal *hw_coal;
 	int rc;
 	int val;
 
 	if (softc == NULL)
 		return EBUSY;
 
-	val = softc->tx_coal_usecs_irq;
+	hw_coal = &softc->tx_coal;
+	val = hw_coal->coal_ticks_irq;
+
 	rc = sysctl_handle_int(oidp, &val, 0, req);
 	if (rc || !req->newptr)
 		return rc;
 
-	softc->tx_coal_usecs_irq = val;
+	rc = bnxt_sysctl_check_u16_range(softc, __func__, val, 0);
+	if (rc)
+		return rc;
+
+	hw_coal->coal_ticks_irq = val;
 	rc = bnxt_hwrm_set_coal(softc);
 
 	return rc;
@@ -1747,23 +1889,62 @@ bnxt_set_coal_tx_usecs_irq(SYSCTL_HANDLER_ARGS) {
 static int
 bnxt_set_coal_tx_frames_irq(SYSCTL_HANDLER_ARGS) {
 	struct bnxt_softc *softc = arg1;
+	struct bnxt_coal *hw_coal;
+	uint16_t mult;
 	int rc;
 	int val;
 
 	if (softc == NULL)
 		return EBUSY;
 
-	val = softc->tx_coal_frames_irq;
+	hw_coal = &softc->tx_coal;
+	mult = hw_coal->bufs_per_record;
+	if (!mult) {
+		device_printf(softc->dev,
+		    "%s: bufs_per_record is zero which is invalid\n", __func__);
+		return EINVAL;
+	}
+
+	val = hw_coal->coal_bufs_irq / mult;
+
 	rc = sysctl_handle_int(oidp, &val, 0, req);
 	if (rc || !req->newptr)
 		return rc;
 
-	softc->tx_coal_frames_irq = val;
+	rc = bnxt_sysctl_check_u16_range(softc, __func__, val, mult);
+	if (rc)
+		return rc;
+
+	hw_coal->coal_bufs_irq = val * mult;
 	rc = bnxt_hwrm_set_coal(softc);
 
 	return rc;
 }
 
+static int
+bnxt_set_stats_coal_ticks(SYSCTL_HANDLER_ARGS) {
+	struct bnxt_softc *softc = arg1;
+	int rc;
+	int val;
+
+	if (softc == NULL)
+		return EBUSY;
+
+	val = softc->stats_coal_ticks;
+
+	rc = sysctl_handle_int(oidp, &val, 0, req);
+	if (rc || !req->newptr)
+		return rc;
+
+	softc->stats_coal_ticks = val;
+	/*
+	 * Stats DMA period is set once at HWRM_STAT_CTX_ALLOC time; there's no
+	 * HWRM_STAT_CTX_CFG to reprogram it, so nothing to apply this to yet.
+	 */
+
+	return 0;
+}
+
 static int
 bnxt_set_cagr_tick_res(SYSCTL_HANDLER_ARGS) {
 	struct bnxt_softc *softc = arg1;
@@ -1788,6 +1969,34 @@ bnxt_set_cagr_tick_res(SYSCTL_HANDLER_ARGS) {
 	return rc;
 }
 
+static int
+bnxt_set_rx_coal_budget(SYSCTL_HANDLER_ARGS) {
+	struct bnxt_softc *softc = arg1;
+	struct bnxt_coal *hw_coal;
+	int rc;
+	int val;
+
+	if (softc == NULL)
+		return EBUSY;
+
+	hw_coal = &softc->rx_coal;
+
+	val = hw_coal->budget;
+
+	rc = sysctl_handle_int(oidp, &val, 0, req);
+	if (rc || !req->newptr)
+		return rc;
+
+	rc = bnxt_sysctl_check_u16_range(softc, __func__, val, 0);
+	if (rc)
+		return rc;
+
+	hw_coal->budget = val;
+	rc = bnxt_hwrm_set_coal(softc);
+
+	return rc;
+}
+
 static int
 bnxt_set_tx_host_coal_bds(SYSCTL_HANDLER_ARGS)
 {
@@ -1914,6 +2123,10 @@ bnxt_create_config_sysctls_pre(struct bnxt_softc *softc)
 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, softc, 0,
 	    bnxt_set_coal_rx_frames_irq, "I",
 	    "interrupt coalescing Rx Frames IRQ");
+	SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "rx_coalesce_mode",
+	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, softc, 0,
+	    bnxt_set_rx_coalesce_mode, "I",
+	    "set rx_coalesce_mode 0/1");
 	SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "intr_coal_tx_usecs",
 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, softc, 0,
 	    bnxt_set_coal_tx_usecs, "I", "interrupt coalescing Tx Usces");
@@ -1928,9 +2141,17 @@ bnxt_create_config_sysctls_pre(struct bnxt_softc *softc)
 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, softc, 0,
 	    bnxt_set_coal_tx_frames_irq, "I",
 	    "interrupt coalescing Tx Frames IRQ");
+	SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "intr_stats_coal_ticks",
+	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, softc, 0,
+	    bnxt_set_stats_coal_ticks, "I",
+	    "interrupt coalescing stats coal ticks");
 	SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "cagr_tick_res",
 	    CTLTYPE_INT | CTLFLAG_RDTUN | CTLFLAG_MPSAFE, softc, 0,
 	    bnxt_set_cagr_tick_res, "I", "CAGR tick resolution");
+	SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "intr_rx_coal_budget",
+	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, softc, 0,
+	    bnxt_set_rx_coal_budget, "I",
+	    "Rx interrupt coalescing budget");
 
 	/*
 	 * Default coal_bds to half the Tx ring depth, clamped between
diff --git a/sys/dev/bnxt/bnxt_en/if_bnxt.c b/sys/dev/bnxt/bnxt_en/if_bnxt.c
index 0757985fa49f..e9c034065a6c 100644
--- a/sys/dev/bnxt/bnxt_en/if_bnxt.c
+++ b/sys/dev/bnxt/bnxt_en/if_bnxt.c
@@ -221,6 +221,7 @@ static int bnxt_detach(if_ctx_t ctx);
 /* Device configuration */
 static void bnxt_init(if_ctx_t ctx);
 static int bnxt_init_hw(if_ctx_t ctx);
+static void bnxt_init_dflt_coal(struct bnxt_softc *softc);
 static void bnxt_stop(if_ctx_t ctx);
 static void bnxt_if_led_func(if_ctx_t ctx, int onoff);
 static bool bnxt_if_led_supported(if_ctx_t ctx);
@@ -2989,6 +2990,9 @@ bnxt_attach_pre(if_ctx_t ctx)
 	scctx->isc_min_frame_size = BNXT_MIN_FRAME_SIZE;
 	scctx->isc_txrx = &bnxt_txrx;
 
+	bnxt_hwrm_coal_params_qcaps(softc);
+	bnxt_init_dflt_coal(softc);
+
 	if (scctx->isc_nrxd[0] <
 	    ((scctx->isc_nrxd[1] * 4) + scctx->isc_nrxd[2]))
 		device_printf(softc->dev,
@@ -3627,6 +3631,49 @@ skip_aux_init:
 	return;
 }
 
+static void
+bnxt_init_dflt_coal(struct bnxt_softc *softc)
+{
+	struct bnxt_coal_cap *coal_cap = &softc->coal_cap;
+	struct bnxt_coal *coal;
+	uint16_t flags = 0;
+
+	if (coal_cap->cmpl_params &
+	    HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_TIMER_RESET)
+		flags |= HWRM_RING_CMPL_RING_CFG_AGGINT_PARAMS_INPUT_FLAGS_TIMER_RESET;
+
+	coal = &softc->rx_coal;
+	if (coal_cap->cmpl_params &
+	    HWRM_RING_AGGINT_QCAPS_OUTPUT_CMPL_PARAMS_TMR_RESET_ON_ALLOC)
+		coal->timer_reset_during_ring_alloc = 1;
+
+	/*
+	 * Tick values in microseconds.
+	 * 1 coal_buf x bufs_per_record = 1 completion record.
+	 */
+	coal->coal_ticks = 6;
+	coal->coal_bufs = 12;
+	coal->coal_ticks_irq = 1;
+	if (BNXT_CHIP_P7(softc))
+		coal->coal_bufs_irq = 4;
+	else
+		coal->coal_bufs_irq = 2;
+	coal->idle_thresh = 50;
+	coal->bufs_per_record = 2;
+	coal->budget = 64;
+	coal->flags = flags;
+
+	coal = &softc->tx_coal;
+	coal->coal_ticks = 28;
+	coal->coal_bufs = 30;
+	coal->coal_ticks_irq = 2;
+	coal->coal_bufs_irq = 2;
+	coal->bufs_per_record = 1;
+	coal->flags = flags;
+
+	softc->stats_coal_ticks = BNXT_MIN_STATS_COAL_TICKS;
+}
+
 /* Device configuration */
 static void
 bnxt_init(if_ctx_t ctx)
@@ -3850,6 +3897,8 @@ skip_def_cp_ring:
 	bnxt_get_port_module_status(softc);
 	bnxt_media_status(softc->ctx, &ifmr);
 	bnxt_hwrm_cfa_l2_set_rx_mask(softc, &softc->vnic_info);
+	set_bit(BNXT_STATE_UP, &softc->state);
+	bnxt_hwrm_set_coal(softc);
 	return (0);
 
 fail:
@@ -3869,6 +3918,7 @@ bnxt_stop(if_ctx_t ctx)
 		bnxt_ptp_stop_calibration(softc->ptp_cfg);
 		softc->rx_ts_enabled = false;
 	}
+	clear_bit(BNXT_STATE_UP, &softc->state);
 	softc->is_dev_init = false;
 	bnxt_do_disable_intr(&softc->def_cp_ring);
 	bnxt_func_reset(softc);