[PATCH iwl-net] ice: release IRQs and rings in ice_dis_vsi() when the VSI is already down

From: Ionut Nechita (Wind River)

Date: Fri Nov 06 2026 - 12:23:42 EST


From: Ionut Nechita <ionut.nechita@xxxxxxxxxxxxx>

Since commit d8c40b9d3a6c ("ice: check ICE_VSI_DOWN under rtnl_lock when
preparing for reset"), ice_dis_vsi() sets ICE_VSI_NEEDS_RESTART
unconditionally, but still skips ice_vsi_close() when ICE_VSI_DOWN is
already set. For a running netdev, ice_ena_vsi() then calls
ice_open_internal() -> ice_vsi_open(), which does a full open: it sets
up the rings and requests the IRQs again.

A running netdev with ICE_VSI_DOWN set is not always a closed VSI.
ice_down() only cleans the rings. It keeps the ring memory and the IRQs
so that ice_up() can restart the VSI quickly. If the restart fails, the
VSI stays in that state while the netdev is still running, for example:

- ice_down_up(), when ice_down() or ice_up() fails (the latter also
via ice_change_mtu());
- ice_xdp_setup_prog(), when ice_down() fails.

If ice_dis_vsi()/ice_ena_vsi() are called on such a VSI without an
ice_vsi_rebuild() in between, which is the case for the DCB
reconfiguration in ice_pf_dcb_cfg() and
ice_dcb_process_lldp_set_mib_change(), ice_vsi_open() runs on rings and
IRQs that were never released:

- ice_setup_tx_ring() hits WARN_ON(tx_ring->tx_buf) and overwrites
tx_buf and desc, leaking the previous arrays;
- ice_setup_rx_ring() overwrites desc, leaking the previous descriptor
memory;
- ice_vsi_req_irq_msix() requests the PF vectors again without
IRQF_SHARED and fails with -EBUSY, so the open fails and the
interface stays down until it is closed and opened again.

On the reset and mqprio paths, ice_vsi_rebuild() frees the ring
structures in between, so there is no WARN, but the ring memory still
held by them is not released until the device is unbound.

Call ice_vsi_close() unconditionally for a running netdev, under
rtnl_lock. ice_vsi_close() already handles a VSI that is down: it only
calls ice_down() if ICE_VSI_DOWN was not set, ice_vsi_free_irq() returns
early when the IRQs are not requested, and ice_vsi_free_{tx,rx}_rings()
only free rings that have a descriptor ring. This is also what
ice_stop() does for a VSI in this state. The race fixed by the commit
above does not come back: ICE_VSI_DOWN is still tested, by
ice_vsi_close(), under rtnl_lock.

The non-running and ICE_VSI_CTRL branches are left as they are. In both
cases ice_ena_vsi() either does not reopen the VSI or, for the control
VSI, the only paths that set ICE_VSI_DOWN also release its rings and
IRQs.

Fixes: d8c40b9d3a6c ("ice: check ICE_VSI_DOWN under rtnl_lock when preparing for reset")
Assisted-by: Claude:claude-opus-5.5
Signed-off-by: Ionut Nechita <ionut.nechita@xxxxxxxxxxxxx>
---
Found while chasing the same WARN_ON() in ice_setup_{rx,tx}_ring() with
the out-of-tree ice driver on an E830 port without a module installed,
where ice_vsi_cfg_lan() fails and ice_dis_vsi()/ice_ena_vsi() then
reopen a VSI that ice_down() left with its rings allocated. The
in-tree paths above were found by code reading.

Based on net (f66469d9905b, "Merge tag 'net-7.4-rc2'").
Tested: build only (W=1, no new warnings).

drivers/net/ethernet/intel/ice/ice_lib.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c
index 7cd2dd066ef5e..0421b0738abd1 100644
--- a/drivers/net/ethernet/intel/ice/ice_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_lib.c
@@ -2852,9 +2852,14 @@ void ice_dis_vsi(struct ice_vsi *vsi, bool locked)
if (netif_running(vsi->netdev)) {
if (!locked)
rtnl_lock();
- already_down = test_bit(ICE_VSI_DOWN, vsi->state);
- if (!already_down)
- ice_vsi_close(vsi);
+ /* ice_ena_vsi() will reopen the VSI with
+ * ice_open_internal(), so release IRQs and rings even
+ * if the VSI is already down: ice_down() keeps both,
+ * and a failed restart (e.g. in ice_down_up()) leaves
+ * the VSI in that state. ice_vsi_close() skips
+ * ice_down() when ICE_VSI_DOWN is already set.
+ */
+ ice_vsi_close(vsi);

if (!locked)
rtnl_unlock();
--
2.43.0