git: cef57e81ce90 - main - ice: Protect the PF mailbox from flooding VFs

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Fri, 18 Sep 2026 01:42:40 UTC
The branch main has been updated by kbowling:

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

commit cef57e81ce906bcceb0bd3097021be4110f192c5
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-19 11:51:42 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-09-18 01:39:55 +0000

    ice: Protect the PF mailbox from flooding VFs
    
    Wire the shared code mailbox-overflow detector into the VF lifecycle and
    virtchnl dispatcher.  E830 controllers use their per-VF hardware
    in-flight-message watermark.  On older controllers, attribute a
    congested mailbox snapshot to its sender, reset it with its queues
    disabled, and discard its subsequent requests.  Advance snapshot
    accounting even for discarded requests.  A physical VFLR, PF reset, or
    IOV recreation releases the VF.  A blocked VF can still submit mailbox
    messages after reset, so discarding requests does not stop it from
    replenishing the shared queue.
    
    Process at most one initially full mailbox immediately.  If producers
    keep it nonempty, mask only the mailbox interrupt cause and let the
    periodic admin timer schedule bounded drain work.  Keep the shared admin
    vector enabled so that OICR and other control-queue events can still be
    serviced.  Re-enable the mailbox cause after draining and recheck the
    queue head for arrivals while the cause was masked.  Retry failed reads
    through the same deferred path instead of treating them as an empty queue.
    
    Document both protection models and add failure-injection points for the
    isolation and scheduling paths.
    
    E810-XXV tests with two host-attached iavf VFs injected overflow on VF 0,
    disabling only that VF while VF 1 completed 60 of 60 pings.  A physical
    VFLR and a PF reset each released VF 0; IOV recreation also reset its
    cumulative count.  Forced persistent mailbox-pending state produced 20
    admin and control-queue passes in five seconds, remained timer-paced,
    and did not interrupt sibling traffic.
    
    MFC after:      2 weeks
    Sponsored by:   BBOX.io
    Differential Revision:  https://reviews.freebsd.org/D59029
---
 share/man/man4/ice.4       |  10 ++++
 sys/dev/ice/ice_iflib.h    |   1 +
 sys/dev/ice/ice_iov.c      | 143 +++++++++++++++++++++++++++++++++++++++++++--
 sys/dev/ice/ice_iov.h      |   7 ++-
 sys/dev/ice/ice_lib.c      |  43 ++++++++++++--
 sys/dev/ice/ice_vf_mbx.h   |   1 +
 sys/dev/ice/if_ice_iflib.c |  69 ++++++++++++++++++----
 7 files changed, 253 insertions(+), 21 deletions(-)

diff --git a/share/man/man4/ice.4 b/share/man/man4/ice.4
index e3246db08689..91d8f95e8fc7 100644
--- a/share/man/man4/ice.4
+++ b/share/man/man4/ice.4
@@ -1185,6 +1185,16 @@ leaves that VF inactive instead of publishing incomplete hardware state.
 A later VF reset can retry this recovery; a PF or device reset, or SR-IOV
 configuration recreation, can also recover the VF.
 .Pp
+The driver protects the shared PF mailbox from a VF which submits requests
+faster than the PF can service them.
+E830 controllers enforce a per-VF limit of 64 outstanding messages in
+hardware.
+On older controllers, the driver detects a VF responsible for at least 63
+messages in a congested mailbox snapshot, resets it with its queues disabled,
+and discards its requests so that other VFs can continue to use the mailbox.
+An externally initiated VF function-level reset, PF reset, or SR-IOV
+configuration recreation releases the VF.
+.Pp
 An up to date list of parameters and their defaults can be found by using
 .Xr iovctl 8
 with the
diff --git a/sys/dev/ice/ice_iflib.h b/sys/dev/ice/ice_iflib.h
index ab576ec1d896..d3f96d9c7225 100644
--- a/sys/dev/ice/ice_iflib.h
+++ b/sys/dev/ice/ice_iflib.h
@@ -351,6 +351,7 @@ struct ice_softc {
 #ifdef PCI_IOV
 	struct ice_vf *vfs;
 	u16 num_vfs;
+	u8 mbx_admin_passes;
 	bool mdd_auto_reset_vf;
 #endif
 	struct ice_resmgr os_imgr;
diff --git a/sys/dev/ice/ice_iov.c b/sys/dev/ice/ice_iov.c
index d86044acfc64..d60b40b0e24d 100644
--- a/sys/dev/ice/ice_iov.c
+++ b/sys/dev/ice/ice_iov.c
@@ -59,6 +59,7 @@ static int ice_iov_configure_mac_anti_spoof(struct ice_softc *sc,
 static int ice_iov_restore_vf_host_config(struct ice_softc *sc,
     struct ice_vf *vf);
 static void ice_iov_clear_vf_queue_state(struct ice_vf *vf);
+static void ice_iov_clear_vf_mbx(struct ice_softc *sc, struct ice_vf *vf);
 static void ice_iov_complete_vf_reset(struct ice_softc *sc,
     struct ice_vf *vf, bool restore_mapping);
 static void ice_iov_ready_vf(struct ice_softc *sc, struct ice_vf *vf);
@@ -176,12 +177,38 @@ ice_iov_attach(struct ice_softc *sc)
 		    "pci_iov_attach failed (error=%s)\n",
 		    ice_err_str(error));
 		ice_clear_bit(ICE_FEATURE_SRIOV, sc->feat_en);
-	} else
+	} else {
 		ice_set_bit(ICE_FEATURE_SRIOV, sc->feat_en);
+		if (ice_is_e830(&sc->hw))
+			ice_iov_reconfigure_mbx(sc);
+		else
+			ice_mbx_init_snapshot(&sc->hw);
+	}
 
 	return (error);
 }
 
+/**
+ * ice_iov_reconfigure_mbx - Restore hardware mailbox flood protection
+ * @sc: device softc structure
+ *
+ * E830 limits each VF's outstanding messages in hardware.  The threshold
+ * register is reset by a core reset and must be restored during rebuild.
+ * Older devices use the software snapshot detector instead.
+ */
+void
+ice_iov_reconfigure_mbx(struct ice_softc *sc)
+{
+	struct ice_hw *hw = &sc->hw;
+
+	if (!ice_is_e830(hw))
+		return;
+
+	wr32(hw, E830_MBX_PF_IN_FLIGHT_VF_MSGS_THRESH,
+	    ICE_MBX_OVERFLOW_WATERMARK);
+	ice_flush(hw);
+}
+
 /**
  * ice_iov_detach - Teardown SR-IOV PF host support
  * @sc: device softc structure
@@ -225,8 +252,13 @@ ice_iov_init(struct ice_softc *sc, uint16_t num_vfs, const nvlist_t *params __un
 		return (ENOMEM);
 
 	/* Initialize each VF with basic information */
-	for (int i = 0; i < num_vfs; i++)
+	for (int i = 0; i < num_vfs; i++) {
 		sc->vfs[i].vf_num = i;
+		if (ice_is_e830(&sc->hw))
+			ice_mbx_vf_clear_cnt_e830(&sc->hw, i);
+		else
+			ice_mbx_init_vf_info(&sc->hw, &sc->vfs[i].mbx_info);
+	}
 
 	/* Save off number of configured VFs */
 	sc->num_vfs = num_vfs;
@@ -612,6 +644,8 @@ ice_iov_uninit(struct ice_softc *sc)
 	/* Release per-VF resources */
 	for (int i = 0; i < sc->num_vfs; i++) {
 		vf = &sc->vfs[i];
+		if (!ice_is_e830(&sc->hw))
+			LIST_DEL(&vf->mbx_info.list_entry);
 		atomic_store_rel_32(&vf->vf_flags, 0);
 		vsi = vf->vsi;
 		free(vf->mac_filters, M_ICE);
@@ -921,10 +955,26 @@ ice_iov_ready_vf(struct ice_softc *sc, struct ice_vf *vf)
 	ice_iov_clear_vf_queue_state(vf);
 	ice_iov_clear_vf_mdd(sc, vf);
 	atomic_clear_32(&vf->vf_flags, VF_FLAG_MDD_BLOCKED);
+	ice_iov_clear_vf_mbx(sc, vf);
 
 	ice_iov_complete_vf_reset(sc, vf, true);
 }
 
+/**
+ * ice_iov_clear_vf_mbx - Release mailbox isolation after a completed reset
+ * @sc: device softc structure
+ * @vf: VF whose mailbox state should be cleared
+ */
+static void
+ice_iov_clear_vf_mbx(struct ice_softc *sc, struct ice_vf *vf)
+{
+	if (ice_is_e830(&sc->hw))
+		ice_mbx_vf_clear_cnt_e830(&sc->hw, vf->vf_num);
+	else
+		ice_mbx_clear_malvf(&vf->mbx_info);
+	atomic_clear_32(&vf->vf_flags, VF_FLAG_MBX_BLOCKED);
+}
+
 /**
  * ice_iov_rebuild_vf - Rebuild a VF VSI after a PF or device reset
  * @sc: device softc structure
@@ -2707,17 +2757,98 @@ ice_vc_notify_vf_link_state(struct ice_softc *sc, struct ice_vf *vf)
 	    VIRTCHNL_STATUS_SUCCESS, (u8 *)&event, sizeof(event), NULL);
 }
 
+/**
+ * ice_iov_mbx_overflow - Detect and isolate a VF flooding the PF mailbox
+ * @sc: device private structure
+ * @vf: VF which sent the current message
+ * @mbx_data: software mailbox snapshot data, or NULL on E830
+ *
+ * E830 enforces the per-VF watermark in hardware.  On older devices, reset
+ * and block a VF after the Intel snapshot detector first attributes an
+ * overflow.  A later external VF reset, PF reset, or SR-IOV recreation
+ * releases it.
+ *
+ * @returns true if the current message must be discarded.
+ */
+static bool
+ice_iov_mbx_overflow(struct ice_softc *sc, struct ice_vf *vf,
+    struct ice_mbx_data *mbx_data)
+{
+	struct ice_hw *hw = &sc->hw;
+	bool report_malvf;
+	u32 reg, vf_flags;
+	int error, status;
+
+	if (mbx_data == NULL)
+		return (false);
+
+	/* Every message advances the snapshot, including a blocked VF's. */
+	report_malvf = false;
+	status = ice_mbx_vf_state_handler(hw, mbx_data, &vf->mbx_info,
+	    &report_malvf);
+	if ((atomic_load_acq_32(&vf->vf_flags) & VF_FLAG_MBX_BLOCKED) != 0)
+		return (true);
+	ICE_FAIL_POINT_CODE_COND(sc, _debug_fail_point_ice_iov,
+	    mailbox_overflow, ice_iov_fail_vf_matches(vf->vf_num),
+	    FAIL_POINT_NONSLEEPABLE, {
+		status = 0;
+		vf->mbx_info.malicious = 1;
+		report_malvf = true;
+	});
+	if (status != 0) {
+		device_printf(sc->dev,
+		    "Unable to check VF %u mailbox overflow, err %s\n",
+		    vf->vf_num, ice_status_str(status));
+		return (false);
+	}
+	if (!report_malvf)
+		return (vf->mbx_info.malicious != 0);
+
+	vf->mbx_overflow_events++;
+	atomic_set_32(&vf->vf_flags, VF_FLAG_MBX_BLOCKED);
+	device_printf(sc->dev,
+	    "VF %u exceeded the mailbox message limit; resetting and blocking it\n",
+	    vf->vf_num);
+
+	vf_flags = atomic_load_acq_32(&vf->vf_flags);
+	if ((vf_flags & VF_FLAG_ENABLED) != 0 && vf->vsi != NULL) {
+		error = ice_reset_vf(sc, vf, true, false);
+		if (error != 0) {
+			device_printf(sc->dev,
+			    "Unable to isolate VF %u after mailbox overflow: %s\n",
+			    vf->vf_num, ice_err_str(error));
+		} else {
+			/*
+			 * Leave queues and mailbox requests blocked, but complete VFR
+			 * so a later physical FLR can create a new reset edge and
+			 * recover the VF.
+			 */
+			ice_iov_complete_vf_reset(sc, vf, false);
+		}
+	} else {
+		/* An incompletely configured VF has no queues to drain. */
+		reg = rd32(hw, VPGEN_VFRTRIG(vf->vf_num));
+		reg |= VPGEN_VFRTRIG_VFSWR_M;
+		wr32(hw, VPGEN_VFRTRIG(vf->vf_num), reg);
+		ice_flush(hw);
+	}
+
+	return (true);
+}
+
 /**
  * ice_vc_handle_vf_msg - Handle a message from a VF
  * @sc: device private structure
  * @event: event received from the HW MBX queue
+ * @mbx_data: software overflow-detection data, or NULL on E830
  *
  * Called whenever an event is received from a VF on the HW mailbox queue.
  * Responsible for handling these messages as well as responding to the
  * VF afterwards, depending on the received message type.
  */
 void
-ice_vc_handle_vf_msg(struct ice_softc *sc, struct ice_rq_event_info *event)
+ice_vc_handle_vf_msg(struct ice_softc *sc, struct ice_rq_event_info *event,
+    struct ice_mbx_data *mbx_data)
 {
 	struct ice_hw *hw = &sc->hw;
 	device_t dev = sc->dev;
@@ -2737,6 +2868,8 @@ ice_vc_handle_vf_msg(struct ice_softc *sc, struct ice_rq_event_info *event)
 	}
 
 	vf = &sc->vfs[v_id];
+	if (ice_iov_mbx_overflow(sc, vf, mbx_data))
+		return;
 
 	/* Perform basic checks on the msg */
 	err = virtchnl_vc_validate_vf_msg(&vf->version, v_opcode, msg, msglen);
@@ -2750,8 +2883,8 @@ ice_vc_handle_vf_msg(struct ice_softc *sc, struct ice_rq_event_info *event)
 	vf_flags = atomic_load_acq_32(&vf->vf_flags);
 	if ((vf_flags & VF_FLAG_ENABLED) == 0 || vf->vsi == NULL)
 		return;
-	/* Only a reset outside this dispatcher may release an MDD-blocked VF. */
-	if ((vf_flags & VF_FLAG_MDD_BLOCKED) != 0)
+	/* Only a reset outside this dispatcher may release an isolated VF. */
+	if ((vf_flags & (VF_FLAG_MDD_BLOCKED | VF_FLAG_MBX_BLOCKED)) != 0)
 		return;
 
 	/*
diff --git a/sys/dev/ice/ice_iov.h b/sys/dev/ice/ice_iov.h
index 35f5edcb5acd..3ef565f709cc 100644
--- a/sys/dev/ice/ice_iov.h
+++ b/sys/dev/ice/ice_iov.h
@@ -74,6 +74,7 @@ enum ice_vf_flags {
 	VF_FLAG_REBUILD_FAILED		= BIT(6),
 	VF_FLAG_RESET_FAILED		= BIT(7),
 	VF_FLAG_MDD_BLOCKED		= BIT(8),
+	VF_FLAG_MBX_BLOCKED		= BIT(9),
 };
 
 struct ice_vf_mac_filter {
@@ -114,6 +115,8 @@ struct ice_vf {
 
 	u64 mdd_tx_events;
 	u64 mdd_rx_events;
+	struct ice_mbx_vf_info mbx_info;
+	u64 mbx_overflow_events;
 	struct timeval last_mdd_log;
 };
 
@@ -142,8 +145,10 @@ void ice_iov_handle_vflr(struct ice_softc *sc);
 u32 ice_iov_handle_mdd(struct ice_softc *sc);
 void ice_iov_notify_vfs_reset(struct ice_softc *sc);
 int ice_iov_quiesce_vfs_for_reset(struct ice_softc *sc);
+void ice_iov_reconfigure_mbx(struct ice_softc *sc);
 
-void ice_vc_handle_vf_msg(struct ice_softc *sc, struct ice_rq_event_info *event);
+void ice_vc_handle_vf_msg(struct ice_softc *sc, struct ice_rq_event_info *event,
+    struct ice_mbx_data *mbx_data);
 void ice_vc_notify_all_vfs_link_state(struct ice_softc *sc);
 
 #endif /* _ICE_IOV_H_ */
diff --git a/sys/dev/ice/ice_lib.c b/sys/dev/ice/ice_lib.c
index 679def5c0e6d..9c5732003494 100644
--- a/sys/dev/ice/ice_lib.c
+++ b/sys/dev/ice/ice_lib.c
@@ -108,7 +108,8 @@ static void ice_check_ctrlq_errors(struct ice_softc *sc, const char *qname,
 				   struct ice_ctl_q_info *cq);
 static void ice_process_link_event(struct ice_softc *sc, struct ice_rq_event_info *e);
 static void ice_process_ctrlq_event(struct ice_softc *sc, const char *qname,
-				    struct ice_rq_event_info *event);
+				    struct ice_rq_event_info *event,
+				    struct ice_mbx_data *mbx_data);
 static void ice_nvm_version_str(struct ice_hw *hw, struct sbuf *buf);
 static void ice_update_port_oversize(struct ice_softc *sc, u64 rx_errors);
 static void ice_active_pkg_version_str(struct ice_hw *hw, struct sbuf *buf);
@@ -2327,7 +2328,8 @@ ice_process_link_event(struct ice_softc *sc,
  */
 static void
 ice_process_ctrlq_event(struct ice_softc *sc, const char *qname,
-			struct ice_rq_event_info *event)
+			struct ice_rq_event_info *event,
+			struct ice_mbx_data *mbx_data)
 {
 	u16 opcode;
 
@@ -2339,7 +2341,7 @@ ice_process_ctrlq_event(struct ice_softc *sc, const char *qname,
 		break;
 #ifdef PCI_IOV
 	case ice_mbx_opc_send_msg_to_pf:
-		ice_vc_handle_vf_msg(sc, event);
+		ice_vc_handle_vf_msg(sc, event, mbx_data);
 		break;
 #endif
 	case ice_aqc_opc_fw_logs_event:
@@ -2375,6 +2377,9 @@ int
 ice_process_ctrlq(struct ice_softc *sc, enum ice_ctl_q q_type, u16 *pending)
 {
 	struct ice_rq_event_info event = { { 0 } };
+#ifdef PCI_IOV
+	struct ice_mbx_data mbx_data = { 0 };
+#endif
 	struct ice_hw *hw = &sc->hw;
 	struct ice_ctl_q_info *cq;
 	int status;
@@ -2393,6 +2398,11 @@ ice_process_ctrlq(struct ice_softc *sc, enum ice_ctl_q q_type, u16 *pending)
 	case ICE_CTL_Q_MAILBOX:
 		cq = &hw->mailboxq;
 		qname = "Mailbox";
+#ifdef PCI_IOV
+		if (!ice_is_e830(hw) && sc->num_vfs != 0)
+			hw->mbx_snapshot.mbx_buf.state =
+			    ICE_MAL_VF_DETECT_STATE_NEW_SNAPSHOT;
+#endif
 		break;
 	default:
 		device_printf(sc->dev,
@@ -2428,10 +2438,31 @@ ice_process_ctrlq(struct ice_softc *sc, enum ice_ctl_q q_type, u16 *pending)
 			return (EIO);
 		}
 		/* XXX should we separate this handler by controlq type? */
-		ice_process_ctrlq_event(sc, qname, &event);
+#ifdef PCI_IOV
+		if (q_type == ICE_CTL_Q_MAILBOX &&
+		    le16toh(event.desc.opcode) == ice_mbx_opc_send_msg_to_pf) {
+			if (ice_is_e830(hw)) {
+				ice_process_ctrlq_event(sc, qname, &event, NULL);
+				ice_e830_mbx_vf_dec_trig(hw, &event);
+			} else {
+				mbx_data.max_num_msgs_mbx = cq->num_rq_entries;
+				mbx_data.async_watermark_val =
+				    ICE_MBX_OVERFLOW_WATERMARK;
+				mbx_data.num_msg_proc = loop;
+				mbx_data.num_pending_arq = *pending;
+				ice_process_ctrlq_event(sc, qname, &event,
+				    &mbx_data);
+			}
+		} else
+#endif
+			ice_process_ctrlq_event(sc, qname, &event, NULL);
 	} while (*pending && (++loop < ICE_CTRLQ_WORK_LIMIT));
 
 	free(event.msg_buf, M_ICE);
+	ICE_FAIL_POINT_CODE_COND(sc, _debug_fail_point_ice, mailbox_pending,
+	    q_type == ICE_CTL_Q_MAILBOX, FAIL_POINT_NONSLEEPABLE, {
+		*pending = 1;
+	});
 
 	return 0;
 }
@@ -5354,6 +5385,10 @@ ice_configure_misc_interrupts(struct ice_softc *sc)
 	/* Associate the OICR interrupt with ITR 0, and enable it */
 	wr32(hw, PFINT_OICR_CTL, PFINT_OICR_CTL_CAUSE_ENA_M);
 
+#ifdef PCI_IOV
+	/* Start a fresh drain budget when restoring mailbox interrupts. */
+	sc->mbx_admin_passes = 0;
+#endif
 	/* Associate the Mailbox interrupt with ITR 0, and enable it */
 	wr32(hw, PFINT_MBX_CTL, PFINT_MBX_CTL_CAUSE_ENA_M);
 
diff --git a/sys/dev/ice/ice_vf_mbx.h b/sys/dev/ice/ice_vf_mbx.h
index 3b185ac89c11..7e4e628e25ec 100644
--- a/sys/dev/ice/ice_vf_mbx.h
+++ b/sys/dev/ice/ice_vf_mbx.h
@@ -44,6 +44,7 @@
   * MBX_VF_IN_FLIGHT_MSGS_AT_PF_CNT register.
   */
 #define ICE_ASYNC_VF_MSG_THRESHOLD	63
+#define ICE_MBX_OVERFLOW_WATERMARK	(ICE_ASYNC_VF_MSG_THRESHOLD + 1)
 
 int
 ice_aq_send_msg_to_pf(struct ice_hw *hw, enum virtchnl_ops v_opcode,
diff --git a/sys/dev/ice/if_ice_iflib.c b/sys/dev/ice/if_ice_iflib.c
index 78542abd706b..9068fd385314 100644
--- a/sys/dev/ice/if_ice_iflib.c
+++ b/sys/dev/ice/if_ice_iflib.c
@@ -2444,8 +2444,10 @@ ice_if_update_admin_status(if_ctx_t ctx)
 {
 	struct ice_softc *sc = (struct ice_softc *)iflib_get_softc(ctx);
 	enum ice_fw_modes fw_mode;
-	bool reschedule = false;
+	bool defer_mailbox = false, reschedule = false;
+	u32 reg;
 	u16 pending = 0;
+	int error;
 
 	ASSERT_CTX_LOCKED(sc);
 
@@ -2489,19 +2491,59 @@ ice_if_update_admin_status(if_ctx_t ctx)
 		 */
 		;
 	} else if (ice_testandclear_state(&sc->state, ICE_STATE_CONTROLQ_EVENT_PENDING)) {
+		pending = 0;
 		ice_process_ctrlq(sc, ICE_CTL_Q_ADMIN, &pending);
 		if (pending > 0)
 			reschedule = true;
 
 		if (ice_is_generic_mac(&sc->hw)) {
+			pending = 0;
 			ice_process_ctrlq(sc, ICE_CTL_Q_SB, &pending);
 			if (pending > 0)
 				reschedule = true;
 		}
 
-		ice_process_ctrlq(sc, ICE_CTL_Q_MAILBOX, &pending);
-		if (pending > 0)
+		pending = 0;
+		error = ice_process_ctrlq(sc, ICE_CTL_Q_MAILBOX, &pending);
+		if (error == 0 && pending == 0) {
+			reg = rd32(&sc->hw, PFINT_MBX_CTL);
+			if ((reg & PFINT_MBX_CTL_CAUSE_ENA_M) == 0) {
+				wr32(&sc->hw, PFINT_MBX_CTL,
+				    reg | PFINT_MBX_CTL_CAUSE_ENA_M);
+				ice_flush(&sc->hw);
+				/* Events received while masked may not interrupt. */
+				pending = (rd32(&sc->hw, sc->hw.mailboxq.rq.head) &
+				    sc->hw.mailboxq.rq.head_mask) !=
+				    sc->hw.mailboxq.rq.next_to_clean;
+			}
+		}
+		if (error != 0) {
+			/* Retry a failed read on the timer, not in a task loop. */
+			defer_mailbox = true;
+		} else if (pending > 0) {
+#ifdef PCI_IOV
+			/*
+			 * Two passes drain one initially full 512-entry mailbox.
+			 * If it remains nonempty, a VF is replenishing it faster
+			 * than this task can drain it. Mask only the mailbox cause
+			 * and let the periodic admin timer schedule bounded work.
+			 */
+			if (sc->mbx_admin_passes <
+			    howmany(ICE_MBXQ_LEN, ICE_CTRLQ_WORK_LIMIT))
+				sc->mbx_admin_passes++;
+			if (sc->mbx_admin_passes <
+			    howmany(ICE_MBXQ_LEN, ICE_CTRLQ_WORK_LIMIT))
+				reschedule = true;
+			else
+				defer_mailbox = true;
+#else
 			reschedule = true;
+#endif
+		} else {
+#ifdef PCI_IOV
+			sc->mbx_admin_passes = 0;
+#endif
+		}
 	}
 
 	/* Poll for link up */
@@ -2519,17 +2561,18 @@ ice_if_update_admin_status(if_ctx_t ctx)
 		iflib_iov_intr_deferred(ctx);
 #endif
 
-	/*
-	 * If there are still messages to process, we need to reschedule
-	 * ourselves. Otherwise, we can just re-enable the interrupt. We'll be
-	 * woken up at the next interrupt or timer event.
-	 */
-	if (reschedule) {
+	if (defer_mailbox) {
+		reg = rd32(&sc->hw, PFINT_MBX_CTL);
+		wr32(&sc->hw, PFINT_MBX_CTL,
+		    reg & ~PFINT_MBX_CTL_CAUSE_ENA_M);
+	}
+	if (reschedule || defer_mailbox)
 		ice_set_state(&sc->state, ICE_STATE_CONTROLQ_EVENT_PENDING);
+	if (reschedule)
 		iflib_admin_intr_deferred(ctx);
-	} else {
+	/* Keep OICR and the other control queues live during mailbox deferral. */
+	if (!reschedule || defer_mailbox)
 		ice_enable_intr(&sc->hw, sc->irqvs[0].me);
-	}
 }
 
 /**
@@ -2739,6 +2782,10 @@ ice_rebuild(struct ice_softc *sc)
 		goto err_shutdown_ctrlq;
 	}
 
+#ifdef PCI_IOV
+	ice_iov_reconfigure_mbx(sc);
+#endif
+
 	/* Query the allocated resources for Tx scheduler */
 	status = ice_sched_query_res_alloc(hw);
 	if (status) {