Page MenuHomeFreeBSD

D58266.id183099.diff
No OneTemporary

D58266.id183099.diff

diff --git a/share/man/man4/iflib.4 b/share/man/man4/iflib.4
--- a/share/man/man4/iflib.4
+++ b/share/man/man4/iflib.4
@@ -1,4 +1,4 @@
-.Dd January 7, 2026
+.Dd July 30, 2026
.Dt IFLIB 4
.Os
.Sh NAME
@@ -128,6 +128,14 @@
Doing so usually increases the transmit throughput by reducing the number of
transmit interrupts.
Setting this to a non-zero value will disable the use of this feature.
+.It Va net.iflib.tx_watchdog_periods
+Number of consecutive
+.Va net.iflib.timer_default
+intervals for which a transmit queue must appear frozen
+before iflib checks it for a transmit hang.
+If the hardware then reports no pending completions,
+the interface is reset.
+Setting this to zero disables the check.
.El
.Pp
These
diff --git a/sys/net/iflib.c b/sys/net/iflib.c
--- a/sys/net/iflib.c
+++ b/sys/net/iflib.c
@@ -312,6 +312,10 @@
/* The minimum descriptors per second before we start coalescing */
#define IFLIB_MIN_DESC_SEC 16384
#define IFLIB_DEFAULT_TX_UPDATE_FREQ 16
+/*
+ * The queue states below and ift_qstatus no longer participate in
+ * the TX watchdog decision and are scheduled for removal.
+ */
#define IFLIB_QUEUE_IDLE 0
#define IFLIB_QUEUE_HUNG 1
#define IFLIB_QUEUE_WORKING 2
@@ -374,7 +378,7 @@
uint8_t ift_txd_size[8];
uint64_t ift_processed;
uint64_t ift_cleaned;
- uint64_t ift_cleaned_prev;
+ uint64_t ift_processed_prev;
#if MEMORY_LOGGING
uint64_t ift_enqueued;
uint64_t ift_dequeued;
@@ -404,9 +408,26 @@
#endif /* DEV_NETMAP */
if_txsd_vec_t ift_sds;
+ /*
+ * TX watchdog state, updated once per iflib_timer period: the
+ * writeback-owed descriptor count at the end of the previous
+ * period, and the number of consecutive frozen periods since
+ * the queue armed. The period count saturates instead of
+ * wrapping and is 16 bits wide so that it still reaches any
+ * value net.iflib.tx_watchdog_periods is plausibly set to; an
+ * 8-bit counter would silently disable the check for a
+ * threshold above 255. These two fields and ift_spare0 below
+ * live here rather than with the counters they are derived
+ * from because that region is packed: an insertion there adds
+ * padding, while here it consumes padding this struct already
+ * had.
+ */
+ qidx_t ift_outstanding_prev;
+ uint16_t ift_wdog_armed;
uint8_t ift_qstatus;
uint8_t ift_closed;
uint8_t ift_update_freq;
+ uint8_t ift_spare0; /* pad to the next pointer boundary */
struct iflib_filter_info ift_filter_info;
bus_dma_tag_t ift_buf_tag;
bus_dma_tag_t ift_tso_buf_tag;
@@ -569,6 +590,21 @@
static int iflib_timer_default = 1000;
SYSCTL_INT(_net_iflib, OID_AUTO, timer_default, CTLFLAG_RW,
&iflib_timer_default, 0, "number of ticks between iflib_timer calls");
+/*
+ * Consecutive iflib_timer periods for which a suspect TX queue must
+ * stay frozen before it is declared hung. A healthy queue on
+ * hardware with descriptor-writeback coalescing (e.g. 8254x,
+ * TXDCTL.WTHRESH) stays frozen for at most two periods (measured on
+ * 82541PI); a wedged queue stays frozen until it is reset. This is
+ * a threshold in time, not in device work: with the default timer
+ * interval the verdict falls after roughly two seconds. Zero
+ * disables the check.
+ */
+static int iflib_tx_watchdog_periods = 4;
+SYSCTL_INT(_net_iflib, OID_AUTO, tx_watchdog_periods, CTLFLAG_RWTUN,
+ &iflib_tx_watchdog_periods, 0,
+ "consecutive frozen timer periods before a TX queue is declared hung "
+ "(0 disables the check)");
#if IFLIB_DEBUG_COUNTERS
@@ -2403,25 +2439,81 @@
return;
/*
- ** Check on the state of the TX queue(s), this
- ** can be done without the lock because its RO
- ** and the HUNG state will be static if set.
- */
+ * Check on the state of the TX queue(s); this can be done
+ * without the lock: the counters the check reads are only
+ * advanced by the queue's tx task and a stale read just
+ * delays the verdict by one timer period.
+ */
if (this_tick - txq->ift_last_timer_tick >= iflib_timer_default) {
+ qidx_t outstanding;
+ bool frozen;
+
txq->ift_last_timer_tick = this_tick;
IFDI_TIMER(ctx, txq->ift_id);
- if ((txq->ift_qstatus == IFLIB_QUEUE_HUNG) &&
- ((txq->ift_cleaned_prev == txq->ift_cleaned) ||
- (sctx->isc_pause_frames == 0)))
- goto hung;
- if (txq->ift_qstatus != IFLIB_QUEUE_IDLE &&
- ifmp_ring_is_stalled(txq->ift_br)) {
- KASSERT(ctx->ifc_link_state == LINK_STATE_UP,
- ("queue can't be marked as hung if interface is down"));
- txq->ift_qstatus = IFLIB_QUEUE_HUNG;
+ /*
+ * Descriptors handed to the hardware for which a
+ * completion writeback is still owed: those neither
+ * harvested as credits (ift_processed) nor reclaimed
+ * (ift_cleaned accounts the difference to ift_in_use).
+ * The tail whose report-status request is still
+ * deferred (ift_rs_pending over-counts it by one per
+ * packet) never writes back and must not count as
+ * owed.
+ */
+ outstanding = txq->ift_in_use -
+ (qidx_t)(txq->ift_processed - txq->ift_cleaned);
+
+ /*
+ * A period is frozen if writeback-owed work exists
+ * and no completions were harvested since the last
+ * period, with the link up, no pause frames seen,
+ * and no doorbell pending (the laggard check below
+ * rings it).
+ *
+ * Frozen alone is a normal state: hardware with
+ * descriptor-writeback coalescing (e.g. 8254x,
+ * TXDCTL.WTHRESH) legitimately withholds the final
+ * report-status writeback of a quiet queue
+ * indefinitely, so a constant owed tail must never
+ * trigger the watchdog. Only a queue that received
+ * new work across a frozen period (the owed count
+ * grew) arms, and the verdict waits until it has
+ * stayed frozen for net.iflib.tx_watchdog_periods
+ * consecutive periods.
+ */
+ frozen = outstanding > txq->ift_rs_pending &&
+ txq->ift_processed == txq->ift_processed_prev &&
+ txq->ift_db_pending == 0 &&
+ sctx->isc_pause_frames == 0 &&
+ ctx->ifc_link_state == LINK_STATE_UP;
+ if (!frozen)
+ txq->ift_wdog_armed = 0;
+ else if (txq->ift_wdog_armed > 0 ||
+ outstanding > txq->ift_outstanding_prev) {
+ if (txq->ift_wdog_armed < UINT16_MAX)
+ txq->ift_wdog_armed++;
}
- txq->ift_cleaned_prev = txq->ift_cleaned;
+
+ /*
+ * Armed long enough - ask the hardware: if
+ * completions are ready but nobody harvested them
+ * for this long, the completion interrupt went
+ * missing - kick the queue's task instead of
+ * resetting; if it reported nothing although the
+ * owed count grew, the queue is hung.
+ */
+ if (iflib_tx_watchdog_periods > 0 &&
+ txq->ift_wdog_armed >= iflib_tx_watchdog_periods) {
+ bus_dmamap_sync(txq->ift_ifdi->idi_tag,
+ txq->ift_ifdi->idi_map, BUS_DMASYNC_POSTREAD);
+ if (ctx->isc_txd_credits_update(ctx->ifc_softc,
+ txq->ift_id, false) == 0)
+ goto hung;
+ GROUPTASK_ENQUEUE(&txq->ift_task);
+ }
+ txq->ift_outstanding_prev = outstanding;
+ txq->ift_processed_prev = txq->ift_processed;
}
/* handle any laggards */
if (txq->ift_db_pending)
@@ -2625,6 +2717,9 @@
iflib_txsd_free(ctx, txq, j);
}
txq->ift_processed = txq->ift_cleaned = txq->ift_cidx_processed = 0;
+ txq->ift_processed_prev = 0;
+ txq->ift_outstanding_prev = 0;
+ txq->ift_wdog_armed = 0;
txq->ift_in_use = txq->ift_gen = txq->ift_no_desc_avail = 0;
txq->ift_npending = txq->ift_db_pending = 0;
txq->ift_rs_pending = 0;

File Metadata

Mime Type
text/plain
Expires
Fri, Aug 14, 12:06 PM (9 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
36702815
Default Alt Text
D58266.id183099.diff (7 KB)

Event Timeline