>-----Original Message-----
>From: Intel-wired-lan <[email protected]> On Behalf Of Jose 
>Ignacio Tornos Martinez
>Sent: Tuesday, June 23, 2026 12:18 PM
>To: [email protected]
>Cc: [email protected]; Kitszel, Przemyslaw 
><[email protected]>; Loktionov, Aleksandr 
><[email protected]>; Keller, Jacob E <[email protected]>; 
>[email protected]; Nguyen, Anthony L <[email protected]>; 
>[email protected]; [email protected]; [email protected]; [email protected]; 
>Jose Ignacio Tornos Martinez <[email protected]>; [email protected]
>Subject: [Intel-wired-lan] [PATCH net v7 3/4] iavf: send MAC change request 
>synchronously
>
>After commit ad7c7b2172c3 ("net: hold netdev instance lock during sysfs 
>operations"), iavf_set_mac() is called with the netdev instance lock already 
>held.
>
>The function queues a MAC address change request via
>iavf_replace_primary_mac() and then waits for completion. However, in the 
>current flow, the actual virtchnl message is sent by the watchdog task, which 
>also needs to acquire the netdev lock to run. Additionally, the adminq_task 
>which processes virtchnl responses also needs the netdev lock.
>
>This creates a deadlock scenario:
>1. iavf_set_mac() holds netdev lock and waits for MAC change 2. Watchdog needs 
>netdev lock to send the request -> blocked 3. Even if request is sent, 
>adminq_task needs netdev lock to process
>   PF response -> blocked
>4. MAC change times out after 2.5 seconds 5. iavf_set_mac() returns -EAGAIN
>
>This particularly affects VFs during bonding setup when multiple VFs are 
>enslaved in quick succession.
>
>Fix by implementing a synchronous MAC change operation similar to the approach 
>used in commit fdadbf6e84c4 ("iavf: fix incorrect reset handling in 
>callbacks").
>
>The solution:
>1. Send the virtchnl ADD_ETH_ADDR message directly (not via watchdog) 2. Poll 
>the admin queue hardware directly for responses 3. Process all received 
>messages (including non-MAC messages) 4. Return when MAC change completes or 
>times out
>
>A new generic function iavf_poll_virtchnl_response() is introduced that can be 
>reused for any future synchronous virtchnl operations. It takes a callback to 
>check completion, allowing flexible condition checking.
>
>This allows the operation to complete synchronously while holding netdev_lock, 
>without relying on watchdog or adminq_task. The function can sleep for up to 
>2.5 seconds polling hardware, but this is acceptable since netdev_lock is 
>per-device and only serializes operations on the same interface.
>
>To support this, change iavf_add_ether_addrs() to return an error code instead 
>of void, allowing callers to detect failures. Additionally, export 
>iavf_mac_add_reject() to enable proper rollback on local failures (timeouts, 
>send errors) - PF rejections are already handled automatically by 
>iavf_virtchnl_completion().
>
>Remove vc_waitqueue entirely because iavf_set_mac was the only waiter on this 
>waitqueue and after the changes it is not needed.
>
>Fixes: ad7c7b2172c3 ("net: hold netdev instance lock during sysfs operations")
>cc: [email protected]
>Signed-off-by: Jose Ignacio Tornos Martinez <[email protected]>
>---
>v7: Rebase on current net tree
>    Remove the multi-batch processing loop from version 6 according to Przemek
>    Kitszel review: the loop cannot work without polling between iterations
>    since the second call would fail the current_op check. Multi-batch scenario
>    is extremely rare; send first batch and let watchdog handle remainder as v5
>    did
>v6: https://lore.kernel.org/all/[email protected]/
>
> drivers/net/ethernet/intel/iavf/iavf.h        | 11 ++-
> drivers/net/ethernet/intel/iavf/iavf_main.c   | 85 ++++++++++++----
> .../net/ethernet/intel/iavf/iavf_virtchnl.c   | 99 +++++++++++++++++--
> 3 files changed, 165 insertions(+), 30 deletions(-)
>
>diff --git a/drivers/net/ethernet/intel/iavf/iavf.h 
>b/drivers/net/ethernet/intel/iavf/iavf.h
>index 050f8241ef5e..5fcbfa0ca855 100644
>--- a/drivers/net/ethernet/intel/iavf/iavf.h
>+++ b/drivers/net/ethernet/intel/iavf/iavf.h
>@@ -259,7 +259,6 @@ struct iavf_adapter {
>       struct work_struct adminq_task;
>       struct work_struct finish_config;
>       wait_queue_head_t down_waitqueue;
>-      wait_queue_head_t vc_waitqueue;
>       struct iavf_q_vector *q_vectors;
>       struct list_head vlan_filter_list;
>       int num_vlan_filters;
>@@ -588,8 +587,9 @@ void iavf_configure_queues(struct iavf_adapter *adapter);  
>void iavf_enable_queues(struct iavf_adapter *adapter);  void 
>iavf_disable_queues(struct iavf_adapter *adapter);  void 
>iavf_map_queues(struct iavf_adapter *adapter); -void 
>iavf_add_ether_addrs(struct iavf_adapter *adapter);
>+int iavf_add_ether_addrs(struct iavf_adapter *adapter);
> void iavf_del_ether_addrs(struct iavf_adapter *adapter);
>+void iavf_mac_add_reject(struct iavf_adapter *adapter);
> void iavf_add_vlans(struct iavf_adapter *adapter);  void 
> iavf_del_vlans(struct iavf_adapter *adapter);  void 
> iavf_set_promiscuous(struct iavf_adapter *adapter); @@ -606,6 +606,13 @@ void 
> iavf_disable_vlan_stripping(struct iavf_adapter *adapter);  void 
> iavf_virtchnl_completion(struct iavf_adapter *adapter,
>                             enum virtchnl_ops v_opcode,
>                             enum iavf_status v_retval, u8 *msg, u16 msglen);
>+int iavf_poll_virtchnl_response(struct iavf_adapter *adapter,
>+                              struct iavf_arq_event_info *event,
>+                              bool (*condition)(struct iavf_adapter *adapter,
>+                                                const void *data,
>+                                                enum virtchnl_ops v_op),
>+                              const void *cond_data,
>+                              unsigned int timeout_ms);
> int iavf_config_rss(struct iavf_adapter *adapter);  void 
> iavf_cfg_queues_bw(struct iavf_adapter *adapter);  void 
> iavf_cfg_queues_quanta_size(struct iavf_adapter *adapter); diff --git 
> a/drivers/net/ethernet/intel/iavf/iavf_main.c 
> b/drivers/net/ethernet/intel/iavf/iavf_main.c
>index 630388e9d28c..3fa288e3798a 100644
>--- a/drivers/net/ethernet/intel/iavf/iavf_main.c
>+++ b/drivers/net/ethernet/intel/iavf/iavf_main.c
>@@ -1029,6 +1029,60 @@ static bool iavf_is_mac_set_handled(struct net_device 
>*netdev,
>       return ret;
> }
> 
>+/**
>+ * iavf_mac_change_done - Check if MAC change completed
>+ * @adapter: board private structure
>+ * @data: MAC address being checked (as const void *)
>+ * @v_op: virtchnl opcode from processed message
>+ *
>+ * Callback for iavf_poll_virtchnl_response() to check if MAC change 
>completed.
>+ *
>+ * Return: true if MAC change completed, false otherwise  */ static 
>+bool iavf_mac_change_done(struct iavf_adapter *adapter,
>+                               const void *data, enum virtchnl_ops v_op) {
>+      const u8 *addr = data;
>+
>+      return iavf_is_mac_set_handled(adapter->netdev, addr); }
>+
>+/**
>+ * iavf_set_mac_sync - Synchronously change MAC address
>+ * @adapter: board private structure
>+ * @addr: MAC address to set
>+ *
>+ * Send MAC change request to PF and poll admin queue for response.
>+ * Caller must hold netdev_lock. This can sleep for up to 2.5 seconds.
>+ * Event buffer is allocated before sending to avoid state mismatch if
>+ * allocation fails after message is sent to PF.
>+ *
>+ * Return: 0 on success, negative on failure  */ static int 
>+iavf_set_mac_sync(struct iavf_adapter *adapter, const u8 *addr) {
>+      struct iavf_arq_event_info event;
>+      int ret;
>+
>+      netdev_assert_locked(adapter->netdev);
>+
>+      event.buf_len = IAVF_MAX_AQ_BUF_SIZE;
>+      event.msg_buf = kzalloc(event.buf_len, GFP_KERNEL);
>+      if (!event.msg_buf)
>+              return -ENOMEM;
>+
>+      ret = iavf_add_ether_addrs(adapter);
>+      if (ret)
>+              goto out;
>+
>+      ret = iavf_poll_virtchnl_response(adapter, &event,
>+                                        iavf_mac_change_done, addr, 2500);
>+
>+out:
>+      kfree(event.msg_buf);
>+      return ret;
>+}
>+
> /**
>  * iavf_set_mac - NDO callback to set port MAC address
>  * @netdev: network interface device structure @@ -1049,25 +1103,23 @@ static 
> int iavf_set_mac(struct net_device *netdev, void *p)
>               return -EADDRNOTAVAIL;
> 
>       ret = iavf_replace_primary_mac(adapter, addr->sa_data);
>-
>       if (ret)
>               return ret;
> 
>-      ret = wait_event_interruptible_timeout(adapter->vc_waitqueue,
>-                                             iavf_is_mac_set_handled(netdev, 
>addr->sa_data),
>-                                             msecs_to_jiffies(2500));
>-
>-      /* If ret < 0 then it means wait was interrupted.
>-       * If ret == 0 then it means we got a timeout.
>-       * else it means we got response for set MAC from PF,
>-       * check if netdev MAC was updated to requested MAC,
>-       * if yes then set MAC succeeded otherwise it failed return -EACCES
>-       */
>-      if (ret < 0)
>+      ret = iavf_set_mac_sync(adapter, addr->sa_data);
>+      if (ret) {
>+              /* Rollback only if send failed (message never reached PF).
>+               * Don't rollback on timeout (-EAGAIN) because the message was
>+               * sent and PF will eventually respond. When the response 
>arrives,
>+               * iavf_virtchnl_completion() will handle rollback (on PF error)
>+               * or acceptance (on PF success) automatically.
>+               */
>+              if (ret != -EAGAIN) {
>+                      iavf_mac_add_reject(adapter);
>+                      ether_addr_copy(adapter->hw.mac.addr, netdev->dev_addr);
>+              }
>               return ret;
>-
>-      if (!ret)
>-              return -EAGAIN;
>+      }
> 
>       if (!ether_addr_equal(netdev->dev_addr, addr->sa_data))
>               return -EACCES;
>@@ -5397,9 +5449,6 @@ static int iavf_probe(struct pci_dev *pdev, const struct 
>pci_device_id *ent)
>       /* Setup the wait queue for indicating transition to down status */
>       init_waitqueue_head(&adapter->down_waitqueue);
> 
>-      /* Setup the wait queue for indicating virtchannel events */
>-      init_waitqueue_head(&adapter->vc_waitqueue);
>-
>       INIT_LIST_HEAD(&adapter->ptp.aq_cmds);
>       init_waitqueue_head(&adapter->ptp.phc_time_waitqueue);
>       mutex_init(&adapter->ptp.aq_cmd_lock);
>diff --git a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c 
>b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
>index ec234cc8bd9d..e6b7e8f82c7c 100644
>--- a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
>+++ b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
>@@ -2,6 +2,7 @@
> /* Copyright(c) 2013 - 2018 Intel Corporation. */
> 
> #include <linux/net/intel/libie/rx.h>
>+#include <net/netdev_lock.h>
> 
> #include "iavf.h"
> #include "iavf_ptp.h"
>@@ -555,20 +556,23 @@ iavf_set_mac_addr_type(struct virtchnl_ether_addr 
>*virtchnl_ether_addr,
>  * @adapter: adapter structure
>  *
>  * Request that the PF add one or more addresses to our filters.
>- **/
>-void iavf_add_ether_addrs(struct iavf_adapter *adapter)
>+ *
>+ * Return: 0 on success, negative on failure  */ int 
>+iavf_add_ether_addrs(struct iavf_adapter *adapter)
> {
>       struct virtchnl_ether_addr_list *veal;
>       struct iavf_mac_filter *f;
>       int i = 0, count = 0;
>       bool more = false;
>       size_t len;
>+      int ret;
The patch looks good. Minor suggestion:
-       int i = 0, count = 0;
+       int i = 0, count = 0, ret;
Please retain RCT.
Thank you.
Piotr

> 
>       if (adapter->current_op != VIRTCHNL_OP_UNKNOWN) {
>               /* bail because we already have a command pending */
>               dev_err(&adapter->pdev->dev, "Cannot add filters, command %d 
> pending\n",
>                       adapter->current_op);
>-              return;
>+              return -EBUSY;
>       }
> 
>       spin_lock_bh(&adapter->mac_vlan_list_lock);
>@@ -580,7 +584,7 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter)
>       if (!count) {
>               adapter->aq_required &= ~IAVF_FLAG_AQ_ADD_MAC_FILTER;
>               spin_unlock_bh(&adapter->mac_vlan_list_lock);
>-              return;
>+              return 0;
>       }
>       adapter->current_op = VIRTCHNL_OP_ADD_ETH_ADDR;
> 
>@@ -594,8 +598,9 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter)
> 
>       veal = kzalloc(len, GFP_ATOMIC);
>       if (!veal) {
>+              adapter->current_op = VIRTCHNL_OP_UNKNOWN;
>               spin_unlock_bh(&adapter->mac_vlan_list_lock);
>-              return;
>+              return -ENOMEM;
>       }
> 
>       veal->vsi_id = adapter->vsi_res->vsi_id; @@ -615,8 +620,15 @@ void 
> iavf_add_ether_addrs(struct iavf_adapter *adapter)
> 
>       spin_unlock_bh(&adapter->mac_vlan_list_lock);
> 
>-      iavf_send_pf_msg(adapter, VIRTCHNL_OP_ADD_ETH_ADDR, (u8 *)veal, len);
>+      ret = iavf_send_pf_msg(adapter, VIRTCHNL_OP_ADD_ETH_ADDR, (u8 *)veal, 
>+len);
>       kfree(veal);
>+      if (ret) {
>+              dev_err(&adapter->pdev->dev,
>+                      "Unable to send ADD_ETH_ADDR message to PF, error 
>%d\n", ret);
>+              adapter->current_op = VIRTCHNL_OP_UNKNOWN;
>+      }
>+
>+      return ret;
> }
> 
> /**
>@@ -712,8 +724,8 @@ static void iavf_mac_add_ok(struct iavf_adapter *adapter)
>  * @adapter: adapter structure
>  *
>  * Remove filters from list based on PF response.
>- **/
>-static void iavf_mac_add_reject(struct iavf_adapter *adapter)
>+ */
>+void iavf_mac_add_reject(struct iavf_adapter *adapter)
> {
>       struct net_device *netdev = adapter->netdev;
>       struct iavf_mac_filter *f, *ftmp;
>@@ -2364,7 +2376,6 @@ void iavf_virtchnl_completion(struct iavf_adapter 
>*adapter,
>                       iavf_mac_add_reject(adapter);
>                       /* restore administratively set MAC address */
>                       ether_addr_copy(adapter->hw.mac.addr, netdev->dev_addr);
>-                      wake_up(&adapter->vc_waitqueue);
>                       break;
>               case VIRTCHNL_OP_DEL_ETH_ADDR:
>                       dev_err(&adapter->pdev->dev, "Failed to delete MAC 
> filter, error %s\n", @@ -2555,7 +2566,6 @@ void 
> iavf_virtchnl_completion(struct iavf_adapter *adapter,
>                       eth_hw_addr_set(netdev, adapter->hw.mac.addr);
>                       netif_addr_unlock_bh(netdev);
>               }
>-              wake_up(&adapter->vc_waitqueue);
>               break;
>       case VIRTCHNL_OP_GET_STATS: {
>               struct iavf_eth_stats *stats =
>@@ -2950,3 +2960,72 @@ void iavf_virtchnl_completion(struct iavf_adapter 
>*adapter,
>       } /* switch v_opcode */
>       adapter->current_op = VIRTCHNL_OP_UNKNOWN;  }
>+
>+/**
>+ * iavf_poll_virtchnl_response - Poll admin queue for virtchnl response
>+ * @adapter: adapter structure
>+ * @event: pre-allocated event buffer to use for polling
>+ * @condition: callback to check if desired response received
>+ * @cond_data: context data passed to condition callback
>+ * @timeout_ms: maximum time to wait in milliseconds
>+ *
>+ * Polls the admin queue and processes all incoming virtchnl messages.
>+ * After processing each valid message, calls the condition callback to 
>+check
>+ * if the expected response has been received. The callback receives 
>+the opcode
>+ * of the processed message to identify which response was received. 
>+Continues
>+ * polling until the callback returns true or timeout expires.
>+ *
>+ * Caller must allocate event buffer before sending any messages to PF 
>+to avoid
>+ * state mismatch if allocation fails after message is sent.
>+ *
>+ * Caller must hold netdev_lock. This can sleep for up to timeout_ms 
>+while
>+ * polling hardware.
>+ *
>+ * Return: 0 on success (condition met), -EAGAIN on timeout, or error 
>+code  */ int iavf_poll_virtchnl_response(struct iavf_adapter *adapter,
>+                              struct iavf_arq_event_info *event,
>+                              bool (*condition)(struct iavf_adapter *adapter,
>+                                                const void *data,
>+                                                enum virtchnl_ops v_op),
>+                              const void *cond_data,
>+                              unsigned int timeout_ms)
>+{
>+      struct iavf_hw *hw = &adapter->hw;
>+      enum virtchnl_ops received_op;
>+      unsigned long timeout;
>+      int ret = -EAGAIN;
>+      u16 pending = 0;
>+      u32 v_retval;
Most of these variables may be declared inside the below statements.
Reviewed-by Piotr Kwapulinski <[email protected]>
Piotr

>+
>+      netdev_assert_locked(adapter->netdev);
>+
>+      timeout = jiffies + msecs_to_jiffies(timeout_ms);
>+      do {
>+              if (!pending)
>+                      usleep_range(50, 75);
>+
>+              if (iavf_clean_arq_element(hw, event, &pending) == 
>IAVF_SUCCESS) {
>+                      received_op = (enum 
>virtchnl_ops)le32_to_cpu(event->desc.cookie_high);
>+                      if (received_op != VIRTCHNL_OP_UNKNOWN) {
>+                              v_retval = le32_to_cpu(event->desc.cookie_low);
>+
>+                              iavf_virtchnl_completion(adapter, received_op,
>+                                                       (enum 
>iavf_status)v_retval,
>+                                                       event->msg_buf, 
>event->msg_len);
>+
>+                              if (condition(adapter, cond_data, received_op)) 
>{
>+                                      ret = 0;
>+                                      break;
>+                              }
>+                      }
>+
>+                      memset(event->msg_buf, 0, IAVF_MAX_AQ_BUF_SIZE);
>+
>+                      if (pending)
>+                              continue;
>+              }
>+      } while (time_before(jiffies, timeout));
>+
>+      return ret;
>+}
>--
>2.54.0
>
>

Reply via email to