git: 3c995919dc4a - main - if_bnxt: add host-based TX completion coalescing

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

URL: https://cgit.FreeBSD.org/src/commit/?id=3c995919dc4a7f5de35534b3220f972bf0153fb9

commit 3c995919dc4a7f5de35534b3220f972bf0153fb9
Author:     Sumit Saxena <sumit.saxena@broadcom.com>
AuthorDate: 2026-08-03 09:07:36 +0000
Commit:     Sumit Saxena <ssaxena@FreeBSD.org>
CommitDate: 2026-10-09 08:24:42 +0000

    if_bnxt: add host-based TX completion coalescing
    
    Suppress per-BD completions (TX_BD_LONG_FLAGS_NO_CMPL/_COAL_NOW) and
    accumulate a running_bds count per ring, forcing a real completion
    when iflib requests one or when the count nears overflowing the
    opaque bd-count field. Replaces the previous raw (bd_count << 24 |
    idx) TX completion opaque encoding, which only had 8 bits for the
    count and would overflow well within these coalescing thresholds,
    with a tx_bd_opaque bitfield (16-bit idx + 15-bit bds + 1-bit
    ktls_replay for later use). Add tx_host_coal_bds/tx_host_coal_enable
    sysctls to tune and toggle it.
    
    Reviewed by:    gallatin
    MFC after:      2 weeks
    Sponsored by:   Broadcom Inc.
    Differential Revision:  https://reviews.freebsd.org/D58600
---
 sys/dev/bnxt/bnxt_en/bnxt.h        | 22 +++++++++++++++++++
 sys/dev/bnxt/bnxt_en/bnxt_sysctl.c | 44 ++++++++++++++++++++++++++++++++++++++
 sys/dev/bnxt/bnxt_en/bnxt_txrx.c   | 32 +++++++++++++++++++++++++--
 sys/dev/bnxt/bnxt_en/if_bnxt.c     |  7 ++++++
 4 files changed, 103 insertions(+), 2 deletions(-)

diff --git a/sys/dev/bnxt/bnxt_en/bnxt.h b/sys/dev/bnxt/bnxt_en/bnxt.h
index ac91661eda7f..80cc8905d66b 100644
--- a/sys/dev/bnxt/bnxt_en/bnxt.h
+++ b/sys/dev/bnxt/bnxt_en/bnxt.h
@@ -600,6 +600,14 @@ struct bnxt_grp_info {
 
 #define	EPOCH_ARR_SZ	4096
 
+struct tx_bd_opaque {
+	uint16_t idx;
+	uint16_t bds:15,
+		 ktls_replay:1;
+} __packed;
+
+#define TX_RING_MASK(txr) ((txr)->ring_size - 1)
+
 struct bnxt_ring {
 	uint64_t		paddr;
 	vm_offset_t		doorbell;
@@ -609,6 +617,7 @@ struct bnxt_ring {
 	uint16_t		id;		/* Logical ID */
 	uint16_t		phys_id;
 	uint16_t		idx;
+	uint16_t		running_bds;	/* host TX-completion-coalescing accumulator */
 	struct bnxt_full_tpa_start *tpa_start;
 	union {
 		u64             db_key64;
@@ -1433,6 +1442,19 @@ struct bnxt_softc {
 #define BNXT_CAGR_TICK_RES_MAX			0x4
 #define BNXT_CAGR_TICK_RES_OFFSET		16
 	uint32_t		cagr_tick_res;
+
+	/* Matches this branch's scctx->isc_tx_nsegments; cached for the
+	 * tx_host_coal_bds underflow guard below. */
+#define BNXT_MAX_NUM_SEGS	31
+	uint32_t		tx_ring_size;	/* cached scctx->isc_ntxd[1] */
+	/*
+	 * Host-based TX coalescing: driver suppresses per-BD completions by
+	 * setting TX_BD_LONG_FLAGS_NO_CMPL / TX_BD_LONG_FLAGS_COAL_NOW on BDs.
+	 */
+#define BNXT_TX_HOST_COAL_BDS_MIN	33	/* must be > BNXT_MAX_NUM_SEGS+1 to avoid uint underflow */
+#define BNXT_TX_HOST_COAL_BDS_DEFAULT	256	/* cap for large rings; halved for small rings */
+	uint32_t		tx_host_coal_bds;    /* max BDs before forcing completion */
+	bool			tx_host_coal_enable; /* enable host-driven TX coalescing */
 	/* Firmware coalesces completions autonomously based on cmpl_coal_cnt
 	 * (only valid when BNXT_FLAG_TX_COAL_CMPL is set); raw codes map to
 	 * counts 4,8,12,16,24,32,48,64,96,128,192,256,320,384,MAX. */
diff --git a/sys/dev/bnxt/bnxt_en/bnxt_sysctl.c b/sys/dev/bnxt/bnxt_en/bnxt_sysctl.c
index 729d0aa1f5c7..cfe57cf25c2c 100644
--- a/sys/dev/bnxt/bnxt_en/bnxt_sysctl.c
+++ b/sys/dev/bnxt/bnxt_en/bnxt_sysctl.c
@@ -1580,6 +1580,30 @@ bnxt_set_cagr_tick_res(SYSCTL_HANDLER_ARGS) {
 	return rc;
 }
 
+static int
+bnxt_set_tx_host_coal_bds(SYSCTL_HANDLER_ARGS)
+{
+	struct bnxt_softc *softc = arg1;
+	uint32_t val;
+	int rc;
+
+	if (softc == NULL)
+		return EBUSY;
+
+	val = softc->tx_host_coal_bds;
+
+	rc = sysctl_handle_int(oidp, (int *)&val, 0, req);
+	if (rc || !req->newptr)
+		return rc;
+
+	if (val < BNXT_TX_HOST_COAL_BDS_MIN || val > softc->tx_ring_size)
+		return EINVAL;
+
+	softc->tx_host_coal_bds = val;
+
+	return 0;
+}
+
 static int
 bnxt_set_tx_hw_coal_cnt(SYSCTL_HANDLER_ARGS)
 {
@@ -1700,7 +1724,27 @@ bnxt_create_config_sysctls_pre(struct bnxt_softc *softc)
 	    CTLTYPE_INT | CTLFLAG_RDTUN | CTLFLAG_MPSAFE, softc, 0,
 	    bnxt_set_cagr_tick_res, "I", "CAGR tick resolution");
 
+	/*
+	 * Default coal_bds to half the Tx ring depth, clamped between
+	 * BNXT_TX_HOST_COAL_BDS_MIN and DEFAULT; disable below the floor to
+	 * avoid uint underflow in (coal_bds - (BNXT_MAX_NUM_SEGS+1)).
+	 */
+	softc->tx_host_coal_bds = min(softc->tx_ring_size,
+	    max(BNXT_TX_HOST_COAL_BDS_MIN,
+		min(BNXT_TX_HOST_COAL_BDS_DEFAULT, softc->tx_ring_size / 2)));
+	softc->tx_host_coal_enable =
+	    (softc->tx_ring_size >= BNXT_TX_HOST_COAL_BDS_MIN);
 	softc->tx_hw_coal_cnt = BNXT_TX_HW_COAL_CNT_DEFAULT;
+	SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "tx_host_coal_bds",
+	    CTLTYPE_INT | CTLFLAG_RDTUN | CTLFLAG_MPSAFE, softc, 0,
+	    bnxt_set_tx_host_coal_bds, "I",
+	    "host-driven TX coalescing: max BDs before forcing completion "
+	    "(33 to Tx ring size, default max(33, min(256, ring_size/2)) "
+	    "clamped to ring_size; feature auto-disabled if ring_size < 33)");
+	SYSCTL_ADD_BOOL(ctx, children, OID_AUTO, "tx_host_coal_enable", CTLFLAG_RDTUN,
+	    &softc->tx_host_coal_enable, 0,
+	    "enable host-driven TX coalescing via NO_CMPL/COAL_NOW BD flags "
+	    "(default enabled)");
 	SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "tx_hw_coal_cnt",
 	    CTLTYPE_INT | CTLFLAG_RDTUN | CTLFLAG_MPSAFE, softc, 0,
 	    bnxt_set_tx_hw_coal_cnt, "I",
diff --git a/sys/dev/bnxt/bnxt_en/bnxt_txrx.c b/sys/dev/bnxt/bnxt_en/bnxt_txrx.c
index 40b02744c6c1..430913c61547 100644
--- a/sys/dev/bnxt/bnxt_en/bnxt_txrx.c
+++ b/sys/dev/bnxt/bnxt_en/bnxt_txrx.c
@@ -94,12 +94,24 @@ bnxt_isc_txd_encap(void *sc, if_pkt_info_t pi)
 	struct bnxt_ring *txr = &softc->tx_rings[pi->ipi_qsidx];
 	struct tx_bd_long *tbd;
 	struct tx_bd_long_hi *tbdh;
+	bool need_cpl = false;
 	bool need_hi = false;
 	uint16_t flags_type;
 	uint16_t lflags;
 	uint32_t cfa_meta;
 	int seg = 0;
 	uint8_t wrap = 0;
+	struct tx_bd_opaque *opq;
+
+	if ((pi->ipi_flags & IPI_TX_INTR) != 0)
+		need_cpl = true;
+
+	if (softc->tx_host_coal_enable) {
+		/* BD field of opaque is only 15 bits; respect tx_host_coal_bds threshold */
+		if (txr->running_bds >
+		    (softc->tx_host_coal_bds - (BNXT_MAX_NUM_SEGS + 1)))
+			need_cpl = true;
+	}
 
 	/* If we have offloads enabled, we need to use two BDs. */
 	if ((pi->ipi_csum_flags & (CSUM_OFFLOAD | CSUM_TSO | CSUM_IP)) ||
@@ -112,12 +124,26 @@ bnxt_isc_txd_encap(void *sc, if_pkt_info_t pi)
 	pi->ipi_new_pidx = pi->ipi_pidx;
 	tbd = &((struct tx_bd_long *)txr->vaddr)[pi->ipi_new_pidx];
 	pi->ipi_ndescs = 0;
+	opq = (struct tx_bd_opaque *)&tbd->opaque;
 	/* No need to byte-swap the opaque value */
-	tbd->opaque = ((pi->ipi_nsegs + need_hi) << 24) | pi->ipi_new_pidx;
+	opq->ktls_replay = 0;
+	opq->idx = pi->ipi_new_pidx & TX_RING_MASK(txr);
+	opq->bds = txr->running_bds + pi->ipi_nsegs + need_hi;
 	tbd->len = htole16(pi->ipi_segs[seg].ds_len);
 	tbd->addr = htole64(pi->ipi_segs[seg++].ds_addr);
 	flags_type = ((pi->ipi_nsegs + need_hi) <<
 	    TX_BD_SHORT_FLAGS_BD_CNT_SFT) & TX_BD_SHORT_FLAGS_BD_CNT_MASK;
+
+	if (softc->tx_host_coal_enable) {
+		flags_type |= TX_BD_LONG_FLAGS_COAL_NOW;
+		if (need_cpl)
+			txr->running_bds = 0;
+		else {
+			txr->running_bds += (pi->ipi_nsegs + 1);
+			flags_type |= TX_BD_LONG_FLAGS_NO_CMPL;
+		}
+	}
+
 	if (pi->ipi_len >= 2048)
 		flags_type |= TX_BD_SHORT_FLAGS_LHINT_GTE2K;
 	else
@@ -235,6 +261,7 @@ bnxt_isc_txd_credits_update(void *sc, uint16_t txqid, bool clear)
 	uint32_t last_raw_cons;
 	uint16_t type;
 	uint16_t err;
+	struct tx_bd_opaque *opq;
 
 	for (;;) {
 		last_cons = cons;
@@ -258,7 +285,8 @@ bnxt_isc_txd_credits_update(void *sc, uint16_t txqid, bool clear)
 				device_printf(softc->dev,
 				    "TX completion error %u\n", err);
 			/* No need to byte-swap the opaque value */
-			avail += cmpl[cons].opaque >> 24;
+			opq = (struct tx_bd_opaque *)&cmpl[cons].opaque;
+			avail += opq->bds;
 			/*
 			 * If we're not clearing, iflib only cares if there's
 			 * at least one buffer.  Don't scan the whole ring in
diff --git a/sys/dev/bnxt/bnxt_en/if_bnxt.c b/sys/dev/bnxt/bnxt_en/if_bnxt.c
index 512afb450494..098fb0685dd3 100644
--- a/sys/dev/bnxt/bnxt_en/if_bnxt.c
+++ b/sys/dev/bnxt/bnxt_en/if_bnxt.c
@@ -2913,6 +2913,13 @@ bnxt_attach_pre(if_ctx_t ctx)
 	    HWRM_VNIC_RSS_CFG_INPUT_HASH_TYPE_TCP_IPV6 |
 	    HWRM_VNIC_RSS_CFG_INPUT_HASH_TYPE_UDP_IPV6;
 
+	/*
+	 * Cache the Tx BD ring size so sysctl validators (e.g.
+	 * tx_host_coal_bds) can compare against it before per-queue
+	 * tx_rings[i].ring_size has been populated.
+	 */
+	softc->tx_ring_size = scctx->isc_ntxd[1];
+
 	if (BNXT_CHIP_P7(softc))
 		softc->cagr_tick_res = BNXT_CAGR_TICK_RES_DEFAULT;