freebsd.org> List-Unsubscribe: X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: kbowling X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: ac2be06e0c697f0c15c9a4fcb9503b2ddb14e135 Auto-Submitted: auto-generated Date: Mon, 03 Aug 2026 10:18:34 +0000 Message-Id: <6a706afa.40a62.6f74575e@gitrepo.freebsd.org> The branch main has been updated by kbowling: URL: https://cgit.FreeBSD.org/src/commit/?id=ac2be06e0c697f0c15c9a4fcb9503b2ddb14e135 commit ac2be06e0c697f0c15c9a4fcb9503b2ddb14e135 Author: Kevin Bowling AuthorDate: 2026-08-03 05:37:12 +0000 Commit: Kevin Bowling CommitDate: 2026-08-03 10:16:35 +0000 igb: preserve coalesced 82576 MDD events WVBR is read-clear, so reading it from the deferred admin pass loses earlier queue bits when multiple VF malicious-driver events arrive before that pass. Snapshot WVBR in the interrupt filter, translate its staggered queue bitmap to pool bits, and OR observations into software latches for deferred notification and recovery. Retain the one-queue VMDq policy used for mixed-driver safety (the vswitch cannot handle a 2Q guest loopback to a 1Q guest per errata). --- sys/dev/e1000/if_em.c | 1 + sys/dev/e1000/if_em.h | 1 + sys/dev/e1000/if_igb_iov.c | 85 +++++++++++++++++++++++++++++++++++----------- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/sys/dev/e1000/if_em.c b/sys/dev/e1000/if_em.c index 6b6de5d74621..5be866657e71 100644 --- a/sys/dev/e1000/if_em.c +++ b/sys/dev/e1000/if_em.c @@ -1109,6 +1109,7 @@ em_set_num_queues(if_ctx_t ctx) maxqueues = 2; break; case e1000_vfadapt: + /* Keep 82576 VFs at one RX/TX queue for mixed-driver safety. */ case e1000_vfadapt_i350: maxqueues = 1; break; diff --git a/sys/dev/e1000/if_em.h b/sys/dev/e1000/if_em.h index 4676cffc5033..a25aaff3307d 100644 --- a/sys/dev/e1000/if_em.h +++ b/sys/dev/e1000/if_em.h @@ -610,6 +610,7 @@ struct e1000_softc { u32 iov_mdd_cause; u32 iov_pending; u32 iov_spoof_pending; + u32 iov_blocked_pending; u32 iov_teardown; struct timeval iov_last_mdd_log; u16 num_vfs; diff --git a/sys/dev/e1000/if_igb_iov.c b/sys/dev/e1000/if_igb_iov.c index 3e03d056865e..5544d1e3dd8c 100644 --- a/sys/dev/e1000/if_igb_iov.c +++ b/sys/dev/e1000/if_igb_iov.c @@ -111,6 +111,8 @@ SDT_PROBE_DEFINE3(igb_iov, vlan, rebuild, vfta_set, "struct e1000_softc *", "u_int", "uint32_t"); SDT_PROBE_DEFINE3(igb_iov, vlan, rebuild, state, "struct e1000_softc *", "uint32_t *", "uint32_t *"); +SDT_PROBE_DEFINE4(igb_iov, mdd, sample, wvbr, + "struct e1000_softc *", "uint32_t", "uint32_t", "uint32_t"); static const struct timeval igb_iov_nack_interval = { 2, 0 }; static const struct timeval igb_iov_mbx_log_interval = { 2, 0 }; @@ -647,6 +649,7 @@ igb_iov_reset_prepare(struct e1000_softc *sc) atomic_readandclear_32(&sc->iov_mdd_cause); atomic_readandclear_32(&sc->iov_pending); atomic_readandclear_32(&sc->iov_spoof_pending); + atomic_readandclear_32(&sc->iov_blocked_pending); } void @@ -1546,19 +1549,19 @@ void igb_iov_handle_mdd(struct e1000_softc *sc) { struct igb_vf *vf; - u32 blocked, blocked_queues, cleared, handled, lvmmc, queue; + u32 blocked, cleared, handled, lvmmc; u32 readback, spoofed; - u32 spoof_queues; - u32 wvbr; bool mdfb_valid, pending; int i; pending = atomic_readandclear_32(&sc->iov_pending) != 0; lvmmc = pending ? atomic_readandclear_32(&sc->iov_mdd_cause) : 0; - spoofed = atomic_readandclear_32(&sc->iov_spoof_pending); - if (!sc->iov_hw_active) + if (!sc->iov_hw_active) { + atomic_readandclear_32(&sc->iov_spoof_pending); + atomic_readandclear_32(&sc->iov_blocked_pending); return; + } blocked = 0; handled = 0; @@ -1566,6 +1569,7 @@ igb_iov_handle_mdd(struct e1000_softc *sc) if (sc->hw.mac.type == e1000_i350) { u32 mdfb; + spoofed = atomic_readandclear_32(&sc->iov_spoof_pending); /* * I350 reports ordinary MAC/VLAN spoofing through the * interrupt-time LVMMC snapshot rather than WVBR. The @@ -1611,20 +1615,15 @@ igb_iov_handle_mdd(struct e1000_softc *sc) } else { if (!pending) return; - wvbr = E1000_READ_REG(&sc->hw, E1000_WVBR); - if (__predict_false(wvbr == 0xffffffff)) - wvbr = 0; - spoof_queues = wvbr & IGB_82576_QUEUE_MASK; - blocked_queues = (wvbr >> 16) & IGB_82576_QUEUE_MASK; - spoofed = (spoof_queues & 0xff) | - (spoof_queues >> IGB_82576_STAGGERED_QUEUE_SHIFT); - blocked = (blocked_queues & 0xff) | - (blocked_queues >> IGB_82576_STAGGERED_QUEUE_SHIFT); - if (blocked == 0 && - (lvmmc & IGB_82576_LVMMC_BLOCK_MASK) != 0) { - queue = (lvmmc >> 16) & 0xf; - blocked = 1U << (queue & 0x7); - } + /* + * WVBR is read-clear and does not preserve every queue across + * multiple MDDET interrupts. The interrupt filter snapshots and + * accumulates its pool bitmaps before this deferred admin pass. + */ + spoofed = atomic_readandclear_32(&sc->iov_spoof_pending); + blocked = atomic_readandclear_32(&sc->iov_blocked_pending); + /* A blocked-queue classification dominates its WVBR low bit. */ + spoofed &= ~blocked; } for (i = 0; i < sc->num_vfs; i++) { @@ -1713,7 +1712,7 @@ igb_iov_handle_mdd(struct e1000_softc *sc) void igb_iov_mdd_event(struct e1000_softc *sc) { - u32 cause, queue; + u32 blocked, cause, queues, queue, spoofed, wvbr; /* * LVMMC is clear-on-read. Preserve it in the interrupt filter, as @@ -1722,6 +1721,49 @@ igb_iov_mdd_event(struct e1000_softc *sc) cause = E1000_READ_REG(&sc->hw, E1000_LVMMC); if (__predict_false(cause == 0xffffffff)) return; + if (sc->hw.mac.type == e1000_82576) { + /* + * Snapshot WVBR in the interrupt filter. Waiting for the admin + * task loses all but the last of back-to-back VF MDD events on + * 82576. Convert the staggered queue map into pool bits and OR + * each observation into software latches for deferred recovery. + */ + wvbr = E1000_READ_REG(&sc->hw, E1000_WVBR); + if (__predict_false(wvbr == 0xffffffff)) { + spoofed = 0; + blocked = 0; + } else { + queues = wvbr & IGB_82576_QUEUE_MASK; + spoofed = (queues & 0xff) | + (queues >> IGB_82576_STAGGERED_QUEUE_SHIFT); + queues = (wvbr >> 16) & IGB_82576_QUEUE_MASK; + blocked = (queues & 0xff) | + (queues >> IGB_82576_STAGGERED_QUEUE_SHIFT); + } + SDT_PROBE4(igb_iov, mdd, sample, wvbr, sc, wvbr, spoofed, + blocked); + /* + * 82576 can report a coalesced block-class event with all affected + * queues in WVBR's low half and no high-half blocked bits. If an + * ordinary spoof shares that snapshot, the register has no per-queue + * cause information. Deliberately fail closed by recovering every + * low-half queue; this can reset a spoof-only sibling, but avoids + * stranding a blocked VF. LVMMC.Last_Q identifies only the final + * event and lost simultaneous blocked VFs on tested silicon. + */ + if (blocked == 0 && + (cause & IGB_82576_LVMMC_BLOCK_MASK) != 0) { + blocked = spoofed; + if (blocked == 0) { + queue = (cause >> 16) & 0xf; + blocked = 1U << (queue & 0x7); + } + } + if (spoofed != 0) + atomic_set_32(&sc->iov_spoof_pending, spoofed); + if (blocked != 0) + atomic_set_32(&sc->iov_blocked_pending, blocked); + } if (sc->hw.mac.type == e1000_i350 && (cause & IGB_I350_LVMMC_MAC_VLAN_SPOOF) != 0) { queue = (cause >> IGB_I350_LVMMC_LAST_Q_SHIFT) & @@ -1775,6 +1817,8 @@ igb_iov_initialize(struct e1000_softc *sc) atomic_readandclear_32(&sc->iov_mdd_cause); atomic_readandclear_32(&sc->iov_pending); atomic_readandclear_32(&sc->iov_spoof_pending); + atomic_readandclear_32(&sc->iov_blocked_pending); + /* Plain VMDq keeps every 82576 PF/VF pool on queue zero. */ E1000_WRITE_REG(hw, E1000_MRQC, E1000_MRQC_ENABLE_VMDQ); vt_ctl = E1000_READ_REG(hw, E1000_VT_CTL); @@ -1982,6 +2026,7 @@ igb_if_iov_uninit(if_ctx_t ctx) atomic_readandclear_32(&sc->iov_mdd_cause); atomic_readandclear_32(&sc->iov_pending); atomic_readandclear_32(&sc->iov_spoof_pending); + atomic_readandclear_32(&sc->iov_blocked_pending); atomic_store_rel_32(&sc->iov_teardown, 0); }