[PATCH net 1/2] enic: preserve V2 VF carrier across netdev reopen

From: Satish Kharat

Date: Sun Aug 30 2026 - 18:25:48 EST


A V2 VF receives carrier state only from PF MBOX notifications.
enic_stop() forces carrier off, but enic_open() does not request a fresh
notification or restore the previous one. An ordinary down/up cycle
therefore leaves the VF in NO-CARRIER and unable to pass traffic until the
PF repeats the link-state command, even when the physical link remained
up.

Cache each valid PF link-state notification. Serialize updates with the V2
VF datapath running state. Keep carrier off while the netdev is stopped.
Restore the cached state after an ordinary open. Before either internal
reset reopens the datapath, invalidate the cache. Carrier then remains off
until re-registration receives a fresh PF link-state notification.

Fixes: 72b65c94058e ("enic: add MBOX VF handlers for capability, register and link state")
Signed-off-by: Satish Kharat <satishkh@xxxxxxxxx>
---
drivers/net/ethernet/cisco/enic/enic.h | 13 ++++++++++
drivers/net/ethernet/cisco/enic/enic_main.c | 12 +++++++++-
drivers/net/ethernet/cisco/enic/enic_mbox.c | 37 +++++++++++++++++++++++++----
drivers/net/ethernet/cisco/enic/enic_mbox.h | 2 ++
4 files changed, 59 insertions(+), 5 deletions(-)

diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index 4a67947cfb9f..2822fbfb6474 100644
--- a/drivers/net/ethernet/cisco/enic/enic.h
+++ b/drivers/net/ethernet/cisco/enic/enic.h
@@ -137,6 +137,12 @@ struct enic_port_profile {
u8 mac_addr[ETH_ALEN];
};

+enum enic_vf_link_state {
+ ENIC_VF_LINK_STATE_UNKNOWN,
+ ENIC_VF_LINK_STATE_DOWN,
+ ENIC_VF_LINK_STATE_UP,
+};
+
/* enic_rfs_fltr_node - rfs filter node in hash table
* @@keys: IPv4 5 tuple
* @flow_id: flow_id of clsf filter provided by kernel
@@ -312,6 +318,13 @@ struct enic {
unsigned int admin_msg_count; /* current depth of admin_msg_list */
void (*admin_rq_handler)(struct enic *enic, void *buf,
unsigned int len);
+ /* The PF is authoritative for a V2 VF's carrier. Keep the last
+ * notification across an ordinary netdev close/open and serialize it
+ * against the open/stop carrier transition.
+ */
+ spinlock_t vf_link_state_lock;
+ enum enic_vf_link_state vf_link_state;
+ bool vf_link_running;

/* MBOX protocol state — mbox_lock serializes admin WQ sends */
struct mutex mbox_lock;
diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c
index 0baef7a120ec..48d16ef18c49 100644
--- a/drivers/net/ethernet/cisco/enic/enic_main.c
+++ b/drivers/net/ethernet/cisco/enic/enic_main.c
@@ -1800,6 +1800,8 @@ static int enic_open(struct net_device *netdev)

enic_notify_timer_start(enic);
enic_rfs_timer_start(enic);
+ if (enic_is_sriov_vf_v2(enic))
+ enic_mbox_vf_link_state_set_running(enic, true);

return 0;

@@ -1853,7 +1855,10 @@ static int enic_stop(struct net_device *netdev)
for (i = 0; i < enic->rq_count; i++)
napi_disable(&enic->napi[i]);

- netif_carrier_off(netdev);
+ if (enic_is_sriov_vf_v2(enic))
+ enic_mbox_vf_link_state_set_running(enic, false);
+ else
+ netif_carrier_off(netdev);
if (vnic_dev_get_intr_mode(enic->vdev) == VNIC_DEV_INTR_MODE_MSIX)
for (i = 0; i < enic->wq_count; i++)
napi_disable(&enic->napi[enic_cq_wq(enic, i)]);
@@ -2271,6 +2276,8 @@ static void enic_reset(struct work_struct *work)
enic_admin_channel_close(enic);

enic_stop(enic->netdev);
+ if (enic_is_sriov_vf_v2(enic))
+ enic_mbox_vf_link_state_reset(enic);

enic_dev_soft_reset(enic);
enic_reset_addr_lists(enic);
@@ -2315,6 +2322,8 @@ static void enic_tx_hang_reset(struct work_struct *work)

enic_dev_hang_notify(enic);
enic_stop(enic->netdev);
+ if (enic_is_sriov_vf_v2(enic))
+ enic_mbox_vf_link_state_reset(enic);

enic_dev_hang_reset(enic);
enic_reset_addr_lists(enic);
@@ -3015,6 +3024,7 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
enic = netdev_priv(netdev);
enic->netdev = netdev;
enic->pdev = pdev;
+ spin_lock_init(&enic->vf_link_state_lock);

/* Setup PCI resources
*/
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c
index 2fb0f1e2ff50..ad79d3951f3d 100644
--- a/drivers/net/ethernet/cisco/enic/enic_mbox.c
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c
@@ -396,25 +396,32 @@ static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload)
{
struct enic_mbox_pf_link_state_notif_msg *notif = payload;
struct enic_mbox_pf_link_state_ack_msg ack = {};
+ u32 link_state = le32_to_cpu(notif->link_state);
int err;

- switch (le32_to_cpu(notif->link_state)) {
+ spin_lock_bh(&enic->vf_link_state_lock);
+ switch (link_state) {
case ENIC_MBOX_LINK_STATE_ENABLE:
- if (!netif_carrier_ok(enic->netdev))
+ enic->vf_link_state = ENIC_VF_LINK_STATE_UP;
+ if (enic->vf_link_running &&
+ !netif_carrier_ok(enic->netdev))
netif_carrier_on(enic->netdev);
netdev_dbg(enic->netdev, "MBOX: link state -> UP\n");
break;
case ENIC_MBOX_LINK_STATE_DISABLE:
- if (netif_carrier_ok(enic->netdev))
+ enic->vf_link_state = ENIC_VF_LINK_STATE_DOWN;
+ if (enic->vf_link_running &&
+ netif_carrier_ok(enic->netdev))
netif_carrier_off(enic->netdev);
netdev_dbg(enic->netdev, "MBOX: link state -> DOWN\n");
break;
default:
netdev_warn(enic->netdev, "MBOX: unknown link state %u\n",
- le32_to_cpu(notif->link_state));
+ link_state);
ack.ack.ret_major = cpu_to_le16(ENIC_MBOX_ERR_GENERIC);
break;
}
+ spin_unlock_bh(&enic->vf_link_state_lock);

err = enic_mbox_send_msg(enic, ENIC_MBOX_PF_LINK_STATE_ACK,
ENIC_MBOX_DST_PF, &ack, sizeof(ack));
@@ -423,6 +430,28 @@ static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload)
"MBOX: failed to send link state ACK: %d\n", err);
}

+void enic_mbox_vf_link_state_reset(struct enic *enic)
+{
+ spin_lock_bh(&enic->vf_link_state_lock);
+ enic->vf_link_state = ENIC_VF_LINK_STATE_UNKNOWN;
+ if (enic->vf_link_running && netif_carrier_ok(enic->netdev))
+ netif_carrier_off(enic->netdev);
+ spin_unlock_bh(&enic->vf_link_state_lock);
+}
+
+void enic_mbox_vf_link_state_set_running(struct enic *enic, bool running)
+{
+ spin_lock_bh(&enic->vf_link_state_lock);
+ enic->vf_link_running = running;
+ if (running && enic->vf_link_state == ENIC_VF_LINK_STATE_UP) {
+ if (!netif_carrier_ok(enic->netdev))
+ netif_carrier_on(enic->netdev);
+ } else if (netif_carrier_ok(enic->netdev)) {
+ netif_carrier_off(enic->netdev);
+ }
+ spin_unlock_bh(&enic->vf_link_state_lock);
+}
+
static bool enic_mbox_vf_payload_ok(struct enic *enic, u8 msg_type,
u16 payload_len, size_t min_len)
{
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h
index 15e30ee2b0ed..60409bad2f28 100644
--- a/drivers/net/ethernet/cisco/enic/enic_mbox.h
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h
@@ -88,6 +88,8 @@ void enic_mbox_init(struct enic *enic);
int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id,
void *payload, u16 payload_len);
int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state);
+void enic_mbox_vf_link_state_reset(struct enic *enic);
+void enic_mbox_vf_link_state_set_running(struct enic *enic, bool running);
int enic_mbox_vf_capability_check(struct enic *enic);
int enic_mbox_vf_register(struct enic *enic);
int enic_mbox_vf_unregister(struct enic *enic);

--
2.43.0