diff --git a/share/man/man4/ice.4 b/share/man/man4/ice.4 index c8924e2de8f9..e3246db08689 100644 --- a/share/man/man4/ice.4 +++ b/share/man/man4/ice.4 @@ -32,7 +32,7 @@ .\" .\" * Other names and brands may be claimed as the property of others. .\" -.Dd August 27, 2026 +.Dd September 5, 2026 .Dt ICE 4 .Os .Sh NAME @@ -46,6 +46,7 @@ In .Xr loader.conf 5 : .Cd if_ice_load .Cd hw.ice.enable_health_events +.Cd hw.ice.mdd_auto_reset_vf .Cd hw.ice.irdma .Cd hw.ice.irdma_max_msix .Cd hw.ice.debug.enable_tx_fc_filter @@ -1039,6 +1040,18 @@ Enabled by default. If enabled, when the driver receives a firmware health event message, it will print out a description of the event to the kernel message buffer and if applicable, possible actions to take to remedy it. +.It Va hw.ice.mdd_auto_reset_vf +Automatically reconstruct and release a VF after malicious-driver detection +resets it. +This is disabled by default so a non-cooperative VF is reset without restoring +its queues and remains unable to issue traffic until an externally initiated +VF function-level reset, PF reset, or SR-IOV configuration recreation +reconstructs it. +Enabling this tunable favors availability but allows a persistently faulty or +hostile VF to resume after each reset. +An individual PF can override the global setting with the +.Va dev.ice.#.mdd_auto_reset_vf +loader tunable. .It Va hw.ice.irdma Set to 1 to enable the RDMA client interface, required by the .Xr irdma 4 diff --git a/sys/dev/ice/ice_common_sysctls.h b/sys/dev/ice/ice_common_sysctls.h index 37a50296ea81..5224e367f95c 100644 --- a/sys/dev/ice/ice_common_sysctls.h +++ b/sys/dev/ice/ice_common_sysctls.h @@ -93,6 +93,21 @@ bool ice_enable_tx_lldp_filter = true; */ bool ice_enable_health_events = true; +#ifdef PCI_IOV +/** + * @var ice_mdd_auto_reset_vf + * @brief reconstruct and release a VF after a malicious-driver reset + * + * Global default for automatically reconstructing and releasing a VF after + * hardware reports a malicious-driver event and the driver resets it. Leave + * this disabled unless availability is more important than keeping a + * persistently faulty VF blocked. + * + * @remark each PF has a separate sysctl which can override this value. + */ +bool ice_mdd_auto_reset_vf = false; +#endif + /** * @var ice_tx_balance_en * @brief boolean permitting the 5-layer scheduler topology enablement @@ -133,6 +148,12 @@ SYSCTL_BOOL(_hw_ice, OID_AUTO, enable_health_events, CTLFLAG_RDTUN, &ice_enable_health_events, 0, "Enable FW health event reporting globally"); +#ifdef PCI_IOV +SYSCTL_BOOL(_hw_ice, OID_AUTO, mdd_auto_reset_vf, CTLFLAG_RDTUN, + &ice_mdd_auto_reset_vf, 0, + "Automatically restore VFs after an MDD reset"); +#endif + SYSCTL_BOOL(_hw_ice, OID_AUTO, irdma, CTLFLAG_RDTUN, &ice_enable_irdma, 0, "Enable iRDMA client interface"); diff --git a/sys/dev/ice/ice_hw_autogen.h b/sys/dev/ice/ice_hw_autogen.h index 3f2778d91a4b..cecef6bbe959 100644 --- a/sys/dev/ice/ice_hw_autogen.h +++ b/sys/dev/ice/ice_hw_autogen.h @@ -5508,6 +5508,7 @@ #define GL_MDET_TX_PQM_VALID_S 31 #define GL_MDET_TX_PQM_VALID_M BIT(31) #define GL_MDET_TX_TCLAN 0x000FC068 /* Reset Source: CORER */ +#define E830_GL_MDET_TX_TCLAN 0x000FCCC0 #define GL_MDET_TX_TCLAN_QNUM_S 0 #define GL_MDET_TX_TCLAN_QNUM_M MAKEMASK(0x7FFF, 0) #define GL_MDET_TX_TCLAN_VF_NUM_S 15 @@ -5539,6 +5540,7 @@ #define PF_MDET_TX_PQM_VALID_S 0 #define PF_MDET_TX_PQM_VALID_M BIT(0) #define PF_MDET_TX_TCLAN 0x000FC000 /* Reset Source: CORER */ +#define E830_PF_MDET_TX_TCLAN 0x000FCC00 #define PF_MDET_TX_TCLAN_VALID_S 0 #define PF_MDET_TX_TCLAN_VALID_M BIT(0) #define PF_MDET_TX_TDPU 0x00040800 /* Reset Source: CORER */ diff --git a/sys/dev/ice/ice_iflib.h b/sys/dev/ice/ice_iflib.h index fe1f53f4cb68..ab576ec1d896 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; + bool mdd_auto_reset_vf; #endif struct ice_resmgr os_imgr; /* For mirror interface */ diff --git a/sys/dev/ice/ice_iov.c b/sys/dev/ice/ice_iov.c index 315950b6f8f8..d86044acfc64 100644 --- a/sys/dev/ice/ice_iov.c +++ b/sys/dev/ice/ice_iov.c @@ -59,6 +59,8 @@ 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_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); static int ice_reset_vf(struct ice_softc *sc, struct ice_vf *vf, bool trigger_reset, bool release_vf); @@ -702,6 +704,117 @@ ice_iov_handle_vflr(struct ice_softc *sc) } } +/** + * ice_iov_handle_mdd - Attribute malicious-driver events to VFs + * @sc: device softc structure + * + * Consume every per-VF MDD latch. Block further virtchnl requests and reset a + * newly blocked VF without restoring its queues, so even event classes which + * only drop the offending packet cannot continue traffic. An optional policy + * reconstructs and releases the VF immediately instead. + * + * @returns a mask of enum ice_mdd_source_bits attributed to configured or + * unconfigured VFs of this PF. + */ +u32 +ice_iov_handle_mdd(struct ice_softc *sc) +{ + static const struct timeval log_interval = { 2, 0 }; + struct ice_hw *hw = &sc->hw; + struct virtchnl_pf_event event = {}; + struct ice_vf *vf; + u32 reg, sources, vf_sources, tx_events, rx_events, vf_flags; + bool newly_blocked; + int error; + + event.event = VIRTCHNL_EVENT_RESET_IMPENDING; + event.severity = PF_EVENT_SEVERITY_CERTAIN_DOOM; + vf_sources = 0; + for (int i = 0; i < sc->num_vfs; i++) { + vf = &sc->vfs[i]; + sources = 0; + tx_events = 0; + rx_events = 0; + + reg = rd32(hw, VP_MDET_TX_PQM(vf->vf_num)); + if ((reg & VP_MDET_TX_PQM_VALID_M) != 0) { + wr32(hw, VP_MDET_TX_PQM(vf->vf_num), 0xffff); + sources |= ICE_MDD_TX_PQM; + tx_events++; + } + reg = rd32(hw, VP_MDET_TX_TCLAN(vf->vf_num)); + if ((reg & VP_MDET_TX_TCLAN_VALID_M) != 0) { + wr32(hw, VP_MDET_TX_TCLAN(vf->vf_num), 0xffff); + sources |= ICE_MDD_TX_TCLAN; + tx_events++; + } + reg = rd32(hw, VP_MDET_TX_TDPU(vf->vf_num)); + if ((reg & VP_MDET_TX_TDPU_VALID_M) != 0) { + wr32(hw, VP_MDET_TX_TDPU(vf->vf_num), 0xffff); + sources |= ICE_MDD_TX_TDPU; + tx_events++; + } + reg = rd32(hw, VP_MDET_RX(vf->vf_num)); + if ((reg & VP_MDET_RX_VALID_M) != 0) { + wr32(hw, VP_MDET_RX(vf->vf_num), 0xffff); + sources |= ICE_MDD_RX; + rx_events++; + } + if (tx_events == 0 && rx_events == 0) + continue; + vf_sources |= sources; + + vf_flags = atomic_load_acq_32(&vf->vf_flags); + if ((vf_flags & VF_FLAG_ENABLED) == 0 || vf->vsi == NULL) + continue; + vf->mdd_tx_events += tx_events; + vf->mdd_rx_events += rx_events; + newly_blocked = (vf_flags & VF_FLAG_MDD_BLOCKED) == 0; + atomic_set_32(&vf->vf_flags, VF_FLAG_MDD_BLOCKED); + + if (ratecheck(&vf->last_mdd_log, &log_interval)) { + device_printf(sc->dev, + "malicious-driver event from VF-%d " + "(tx %ju, rx %ju); %s\n", vf->vf_num, + (uintmax_t)vf->mdd_tx_events, + (uintmax_t)vf->mdd_rx_events, + sc->mdd_auto_reset_vf && newly_blocked ? + "resetting VF" : "VF remains blocked"); + } + if (!newly_blocked) + continue; + + /* Ignore notification failure; reset does not require VF help. */ + if (sc->mdd_auto_reset_vf && + (vf_flags & VF_FLAG_INITIALIZED) != 0 && + ice_check_sq_alive(hw, &hw->mailboxq)) { + (void)ice_aq_send_msg_to_vf(hw, vf->vf_num, + VIRTCHNL_OP_EVENT, VIRTCHNL_STATUS_SUCCESS, + (u8 *)&event, sizeof(event), NULL); + } + /* + * TDPU MDD drops only the offending packet. Reset the entire VF so + * the software blocked state always means that traffic is actually + * fenced. The opt-in policy reconstructs its queues immediately. + */ + error = ice_reset_vf(sc, vf, true, sc->mdd_auto_reset_vf); + if (error != 0) { + device_printf(sc->dev, + "failed to quiesce MDD-blocked VF-%d: %d\n", + vf->vf_num, error); + } else if (!sc->mdd_auto_reset_vf) { + /* + * Complete VFR without restoring queues. This leaves the VF + * inactive and DMA-fenced, but permits a later physical FLR to + * create a new reset edge and recover it. + */ + ice_iov_complete_vf_reset(sc, vf, false); + } + } + ice_flush(hw); + return (vf_sources); +} + /** * ice_iov_notify_vfs_reset - Notify initialized VFs of an impending reset * @sc: device softc structure @@ -744,6 +857,54 @@ ice_iov_clear_vf_queue_state(struct ice_vf *vf) vf->rxq_enabled = 0; } +/** + * ice_iov_clear_vf_mdd - Clear hardware MDD latches for a reset VF + * @sc: device softc structure + * @vf: driver's VF structure for the VF to update + * + * Function reset can generate a spurious anti-spoof MDD indication. Consume + * all per-VF latches before releasing reset so it cannot re-block a VF which + * has just been reconstructed successfully. + */ +static void +ice_iov_clear_vf_mdd(struct ice_softc *sc, struct ice_vf *vf) +{ + struct ice_hw *hw = &sc->hw; + + wr32(hw, VP_MDET_TX_PQM(vf->vf_num), 0xffff); + wr32(hw, VP_MDET_TX_TCLAN(vf->vf_num), 0xffff); + wr32(hw, VP_MDET_TX_TDPU(vf->vf_num), 0xffff); + wr32(hw, VP_MDET_RX(vf->vf_num), 0xffff); + ice_flush(hw); +} + +/** + * ice_iov_complete_vf_reset - Complete a VF reset + * @sc: device softc structure + * @vf: driver's VF structure for the VF to update + * @restore_mapping: restore the VF queue and interrupt mappings + * + * Clear VFSWR after the hardware drain, optionally restore the VF mappings, + * and then publish VFACTIVE. The mapping registers do not retain writes made + * while VFSWR remains asserted. Callers may instead leave a software-blocked + * VF with no queue or interrupt mappings. + */ +static void +ice_iov_complete_vf_reset(struct ice_softc *sc, struct ice_vf *vf, + bool restore_mapping) +{ + struct ice_hw *hw = &sc->hw; + u32 reg; + + reg = rd32(hw, VPGEN_VFRTRIG(vf->vf_num)); + reg &= ~VPGEN_VFRTRIG_VFSWR_M; + wr32(hw, VPGEN_VFRTRIG(vf->vf_num), reg); + if (restore_mapping) + ice_iov_setup_intr_mapping(sc, vf); + wr32(hw, VFGEN_RSTAT(vf->vf_num), VIRTCHNL_VFR_VFACTIVE); + ice_flush(hw); +} + /** * ice_iov_ready_vf - Setup VF interrupts and mark it as ready * @sc: device softc structure @@ -756,24 +917,12 @@ ice_iov_clear_vf_queue_state(struct ice_vf *vf) static void ice_iov_ready_vf(struct ice_softc *sc, struct ice_vf *vf) { - struct ice_hw *hw = &sc->hw; - u32 reg; - /* A VF or PF reset discards all queue configuration and state. */ ice_iov_clear_vf_queue_state(vf); + ice_iov_clear_vf_mdd(sc, vf); + atomic_clear_32(&vf->vf_flags, VF_FLAG_MDD_BLOCKED); - /* Clear the triggering bit */ - reg = rd32(hw, VPGEN_VFRTRIG(vf->vf_num)); - reg &= ~VPGEN_VFRTRIG_VFSWR_M; - wr32(hw, VPGEN_VFRTRIG(vf->vf_num), reg); - - /* Setup VF interrupt allocation and mapping */ - ice_iov_setup_intr_mapping(sc, vf); - - /* Indicate to the VF that reset is done */ - wr32(hw, VFGEN_RSTAT(vf->vf_num), VIRTCHNL_VFR_VFACTIVE); - - ice_flush(hw); + ice_iov_complete_vf_reset(sc, vf, true); } /** @@ -983,6 +1132,8 @@ ice_reset_vf(struct ice_softc *sc, struct ice_vf *vf, bool trigger_reset, } if (!release_vf) { + /* Discard any anti-spoof MDD indication caused by the reset. */ + ice_iov_clear_vf_mdd(sc, vf); atomic_clear_32(&vf->vf_flags, VF_FLAG_RESET_FAILED); return (0); } @@ -2599,6 +2750,9 @@ 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) + return; /* * Permit only reset negotiation while VF hardware state is unsafe. diff --git a/sys/dev/ice/ice_iov.h b/sys/dev/ice/ice_iov.h index fc78169172d2..35f5edcb5acd 100644 --- a/sys/dev/ice/ice_iov.h +++ b/sys/dev/ice/ice_iov.h @@ -73,6 +73,7 @@ enum ice_vf_flags { VF_FLAG_INITIALIZED = BIT(5), VF_FLAG_REBUILD_FAILED = BIT(6), VF_FLAG_RESET_FAILED = BIT(7), + VF_FLAG_MDD_BLOCKED = BIT(8), }; struct ice_vf_mac_filter { @@ -110,6 +111,10 @@ struct ice_vf { u32 txq_configured; u32 rxq_configured; u32 rxq_enabled; + + u64 mdd_tx_events; + u64 mdd_rx_events; + struct timeval last_mdd_log; }; #define ICE_PCIE_DEV_STATUS 0xAA @@ -134,6 +139,7 @@ int ice_iov_rebuild_vf(struct ice_softc *sc, struct ice_vsi *vsi); void ice_iov_uninit(struct ice_softc *sc); 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); diff --git a/sys/dev/ice/ice_lib.c b/sys/dev/ice/ice_lib.c index 9d634b6a7658..679def5c0e6d 100644 --- a/sys/dev/ice/ice_lib.c +++ b/sys/dev/ice/ice_lib.c @@ -5968,6 +5968,13 @@ ice_add_device_tunables(struct ice_softc *sc) CTLFLAG_RDTUN, &sc->enable_health_events, 0, "Enable FW health event reporting for this PF"); +#ifdef PCI_IOV + sc->mdd_auto_reset_vf = ice_mdd_auto_reset_vf; + SYSCTL_ADD_BOOL(ctx, ctx_list, OID_AUTO, "mdd_auto_reset_vf", + CTLFLAG_RDTUN, &sc->mdd_auto_reset_vf, 0, + "Automatically restore VFs after an MDD reset"); +#endif + /* Add a node to track VSI sysctls. Keep track of the node in the * softc so that we can hook other sysctls into it later. This * includes both the VSI statistics, as well as potentially dynamic @@ -8395,11 +8402,22 @@ ice_init_link_events(struct ice_softc *sc) return (0); } -#ifndef GL_MDET_TX_TCLAN -/* Temporarily use this redefinition until the definition is fixed */ -#define GL_MDET_TX_TCLAN E800_GL_MDET_TX_TCLAN -#define PF_MDET_TX_TCLAN E800_PF_MDET_TX_TCLAN -#endif /* !defined(GL_MDET_TX_TCLAN) */ +static u32 +ice_gl_mdet_tx_tclan(struct ice_hw *hw) +{ + + return (ice_is_e830(hw) ? E830_GL_MDET_TX_TCLAN : + GL_MDET_TX_TCLAN); +} + +static u32 +ice_pf_mdet_tx_tclan(struct ice_hw *hw) +{ + + return (ice_is_e830(hw) ? E830_PF_MDET_TX_TCLAN : + PF_MDET_TX_TCLAN); +} + /** * ice_handle_mdd_event - Handle possibly malicious events * @sc: the device softc @@ -8412,98 +8430,143 @@ void ice_handle_mdd_event(struct ice_softc *sc) { struct ice_hw *hw = &sc->hw; - bool mdd_detected = false, request_reinit = false; device_t dev = sc->dev; - u32 reg; + u32 pf_sources, reg, tclan_reg, vf_sources; + bool request_reinit; if (!ice_testandclear_state(&sc->state, ICE_STATE_MDD_PENDING)) return; - reg = rd32(hw, GL_MDET_TX_TCLAN); + pf_sources = 0; + vf_sources = 0; + tclan_reg = ice_gl_mdet_tx_tclan(hw); + reg = rd32(hw, tclan_reg); if (reg & GL_MDET_TX_TCLAN_VALID_M) { - u8 pf_num = (reg & GL_MDET_TX_TCLAN_PF_NUM_M) >> GL_MDET_TX_TCLAN_PF_NUM_S; - u16 vf_num = (reg & GL_MDET_TX_TCLAN_VF_NUM_M) >> GL_MDET_TX_TCLAN_VF_NUM_S; - u8 event = (reg & GL_MDET_TX_TCLAN_MAL_TYPE_M) >> GL_MDET_TX_TCLAN_MAL_TYPE_S; - u16 queue = (reg & GL_MDET_TX_TCLAN_QNUM_M) >> GL_MDET_TX_TCLAN_QNUM_S; + u8 pf_num = (reg & GL_MDET_TX_TCLAN_PF_NUM_M) >> + GL_MDET_TX_TCLAN_PF_NUM_S; + u16 vf_num = (reg & GL_MDET_TX_TCLAN_VF_NUM_M) >> + GL_MDET_TX_TCLAN_VF_NUM_S; + u8 event = (reg & GL_MDET_TX_TCLAN_MAL_TYPE_M) >> + GL_MDET_TX_TCLAN_MAL_TYPE_S; + u16 queue = (reg & GL_MDET_TX_TCLAN_QNUM_M) >> + GL_MDET_TX_TCLAN_QNUM_S; - device_printf(dev, "Malicious Driver Detection Tx Descriptor check event '%s' on Tx queue %u PF# %u VF# %u\n", - ice_mdd_tx_tclan_str(event), queue, pf_num, vf_num); + device_printf(dev, + "malicious-driver Tx descriptor event '%s' on queue %u, " + "PF %u, VF %u\n", ice_mdd_tx_tclan_str(event), queue, + pf_num, vf_num); /* Only clear this event if it matches this PF, that way other * PFs can read the event and determine VF and queue number. */ if (pf_num == hw->pf_id) - wr32(hw, GL_MDET_TX_TCLAN, 0xffffffff); - - mdd_detected = true; + wr32(hw, tclan_reg, 0xffffffff); } /* Determine what triggered the MDD event */ reg = rd32(hw, GL_MDET_TX_PQM); if (reg & GL_MDET_TX_PQM_VALID_M) { - u8 pf_num = (reg & GL_MDET_TX_PQM_PF_NUM_M) >> GL_MDET_TX_PQM_PF_NUM_S; - u16 vf_num = (reg & GL_MDET_TX_PQM_VF_NUM_M) >> GL_MDET_TX_PQM_VF_NUM_S; - u8 event = (reg & GL_MDET_TX_PQM_MAL_TYPE_M) >> GL_MDET_TX_PQM_MAL_TYPE_S; - u16 queue = (reg & GL_MDET_TX_PQM_QNUM_M) >> GL_MDET_TX_PQM_QNUM_S; + u8 pf_num = (reg & GL_MDET_TX_PQM_PF_NUM_M) >> + GL_MDET_TX_PQM_PF_NUM_S; + u16 vf_num = (reg & GL_MDET_TX_PQM_VF_NUM_M) >> + GL_MDET_TX_PQM_VF_NUM_S; + u8 event = (reg & GL_MDET_TX_PQM_MAL_TYPE_M) >> + GL_MDET_TX_PQM_MAL_TYPE_S; + u16 queue = (reg & GL_MDET_TX_PQM_QNUM_M) >> + GL_MDET_TX_PQM_QNUM_S; - device_printf(dev, "Malicious Driver Detection Tx Quanta check event '%s' on Tx queue %u PF# %u VF# %u\n", - ice_mdd_tx_pqm_str(event), queue, pf_num, vf_num); + device_printf(dev, + "malicious-driver Tx quanta event '%s' on queue %u, " + "PF %u, VF %u\n", ice_mdd_tx_pqm_str(event), queue, + pf_num, vf_num); /* Only clear this event if it matches this PF, that way other * PFs can read the event and determine VF and queue number. */ if (pf_num == hw->pf_id) wr32(hw, GL_MDET_TX_PQM, 0xffffffff); + } - mdd_detected = true; + reg = rd32(hw, GL_MDET_TX_TDPU); + if (reg & GL_MDET_TX_TDPU_VALID_M) { + u8 pf_num = (reg & GL_MDET_TX_TDPU_PF_NUM_M) >> + GL_MDET_TX_TDPU_PF_NUM_S; + u16 vf_num = (reg & GL_MDET_TX_TDPU_VF_NUM_M) >> + GL_MDET_TX_TDPU_VF_NUM_S; + u8 event = (reg & GL_MDET_TX_TDPU_MAL_TYPE_M) >> + GL_MDET_TX_TDPU_MAL_TYPE_S; + u16 queue = (reg & GL_MDET_TX_TDPU_QNUM_M) >> + GL_MDET_TX_TDPU_QNUM_S; + + device_printf(dev, + "malicious-driver Tx data event %#x on queue %u, " + "PF %u, VF %u\n", event, queue, pf_num, vf_num); + if (pf_num == hw->pf_id) + wr32(hw, GL_MDET_TX_TDPU, 0xffffffff); } reg = rd32(hw, GL_MDET_RX); if (reg & GL_MDET_RX_VALID_M) { - u8 pf_num = (reg & GL_MDET_RX_PF_NUM_M) >> GL_MDET_RX_PF_NUM_S; - u16 vf_num = (reg & GL_MDET_RX_VF_NUM_M) >> GL_MDET_RX_VF_NUM_S; - u8 event = (reg & GL_MDET_RX_MAL_TYPE_M) >> GL_MDET_RX_MAL_TYPE_S; - u16 queue = (reg & GL_MDET_RX_QNUM_M) >> GL_MDET_RX_QNUM_S; + u8 pf_num = (reg & GL_MDET_RX_PF_NUM_M) >> + GL_MDET_RX_PF_NUM_S; + u8 event = (reg & GL_MDET_RX_MAL_TYPE_M) >> + GL_MDET_RX_MAL_TYPE_S; + u16 queue = (reg & GL_MDET_RX_QNUM_M) >> + GL_MDET_RX_QNUM_S; - device_printf(dev, "Malicious Driver Detection Rx event '%s' on Rx queue %u PF# %u VF# %u\n", - ice_mdd_rx_str(event), queue, pf_num, vf_num); + /* + * E810 Datasheet section 9.2.2.2.1 says only the queue field in + * GL_MDET_RX is valid. VP_MDET_RX provides VF attribution. + */ + device_printf(dev, + "malicious-driver Rx event '%s' on queue %u, PF %u\n", + ice_mdd_rx_str(event), queue, pf_num); /* Only clear this event if it matches this PF, that way other - * PFs can read the event and determine VF and queue number. + * PFs can read the event and determine the queue number. */ if (pf_num == hw->pf_id) wr32(hw, GL_MDET_RX, 0xffffffff); - - mdd_detected = true; } - /* Now, confirm that this event actually affects this PF, by checking - * the PF registers. + /* Per-function latches provide authoritative PF/VF attribution. */ + tclan_reg = ice_pf_mdet_tx_tclan(hw); + reg = rd32(hw, tclan_reg); + if (reg & PF_MDET_TX_TCLAN_VALID_M) { + wr32(hw, tclan_reg, 0xffff); + sc->soft_stats.tx_mdd_count++; + pf_sources |= ICE_MDD_TX_TCLAN; + } + reg = rd32(hw, PF_MDET_TX_PQM); + if (reg & PF_MDET_TX_PQM_VALID_M) { + wr32(hw, PF_MDET_TX_PQM, 0xffff); + sc->soft_stats.tx_mdd_count++; + pf_sources |= ICE_MDD_TX_PQM; + } + reg = rd32(hw, PF_MDET_TX_TDPU); + if (reg & PF_MDET_TX_TDPU_VALID_M) { + wr32(hw, PF_MDET_TX_TDPU, 0xffff); + sc->soft_stats.tx_mdd_count++; + pf_sources |= ICE_MDD_TX_TDPU; + } + reg = rd32(hw, PF_MDET_RX); + if (reg & PF_MDET_RX_VALID_M) { + wr32(hw, PF_MDET_RX, 0xffff); + sc->soft_stats.rx_mdd_count++; + pf_sources |= ICE_MDD_RX; + } + +#ifdef PCI_IOV + vf_sources = ice_iov_handle_mdd(sc); +#endif + /* + * E810 sets the parent PF_MDET latch for events attributed by a + * VP_MDET latch to one of its VFs. Recover the PF only for event + * classes which were not attributed to a VF. TDPU drops only the + * offending packet and does not stop a queue. */ - if (mdd_detected) { - reg = rd32(hw, PF_MDET_TX_TCLAN); - if (reg & PF_MDET_TX_TCLAN_VALID_M) { - wr32(hw, PF_MDET_TX_TCLAN, 0xffff); - sc->soft_stats.tx_mdd_count++; - request_reinit = true; - } - - reg = rd32(hw, PF_MDET_TX_PQM); - if (reg & PF_MDET_TX_PQM_VALID_M) { - wr32(hw, PF_MDET_TX_PQM, 0xffff); - sc->soft_stats.tx_mdd_count++; - request_reinit = true; - } - - reg = rd32(hw, PF_MDET_RX); - if (reg & PF_MDET_RX_VALID_M) { - wr32(hw, PF_MDET_RX, 0xffff); - sc->soft_stats.rx_mdd_count++; - request_reinit = true; - } - } - - /* TODO: Implement logic to detect and handle events caused by VFs. */ + request_reinit = (pf_sources & ~vf_sources & + (ICE_MDD_TX_PQM | ICE_MDD_TX_TCLAN | ICE_MDD_RX)) != 0; /* request that the upper stack re-initialize the Tx/Rx queues */ if (request_reinit) diff --git a/sys/dev/ice/ice_lib.h b/sys/dev/ice/ice_lib.h index 6c6f93b97228..912e6f690a95 100644 --- a/sys/dev/ice/ice_lib.h +++ b/sys/dev/ice/ice_lib.h @@ -72,6 +72,13 @@ #include "ice_rss.h" +enum ice_mdd_source_bits { + ICE_MDD_TX_PQM = BIT(0), + ICE_MDD_TX_TCLAN = BIT(1), + ICE_MDD_TX_TDPU = BIT(2), + ICE_MDD_RX = BIT(3), +}; + /* Hide debug sysctls unless INVARIANTS is enabled */ #ifdef INVARIANTS #define ICE_CTLFLAG_DEBUG 0 @@ -122,6 +129,11 @@ extern bool ice_enable_tx_lldp_filter; /* global sysctl indicating whether FW health status events should be enabled */ extern bool ice_enable_health_events; +#ifdef PCI_IOV +/* reconstruct and release a VF automatically after an MDD reset */ +extern bool ice_mdd_auto_reset_vf; +#endif + /* global sysctl indicating whether to enable 5-layer scheduler topology */ extern bool ice_tx_balance_en;