[Intel-wired-lan] [PATCH iwl-net v6 2/4] iavf: Don't lock rtnl_lock twice in reset
Kamil Maziarz
kamil.maziarz at intel.com
Thu May 11 09:56:26 UTC 2023
From: Marcin Szycik <marcin.szycik at linux.intel.com>
Some ndo/ethtool callbacks are called under rtnl_lock.
If such a callback triggers a reset, the reset task might try to take
the rtnl_lock again, causing a deadlock.
When the driver is unable to obtain the rtnl_lock in the reset flow,
the reset task does not attempt to double-lock rtnl_lock and avoids
the deadlock by scheduling the netdev update.
Iavf_delayed_set_interrupt_capability is scheduled on the global wq
we don't schedule it on the iavf workqueue to avoid deadlock.
(iavf_workqueue is ordered and designed to support only one active task)
e.g:
-A iavf schedules reset
-B another iavf reset asks for RTNL_LOCK
-A iavf schedules the iavf_delayed_set_interrupt_capability.
-A reset ends
-A iavf_delayed_set_interrupt_capability asks for RTNL_LOCK
-B obtains the RTNL_LOCK waits for iavf_delayed_set_interrupt_capability
to free the working queue
-A waits for RTNL_LOCK
Before this patch, a deadlock could be caused by e.g.:
echo 1 > /sys/class/net/$PF1/device/sriov_numvfs
while :; do
ip l s $VF1 up
ethtool --set-channels $VF1 combined 8
ip l s $VF1 down
ip l s $VF1 up
ethtool --set-channels $VF1 combined 16
ip l s $VF1 down
done
Fixes: aa626da947e9 ("iavf: Detach device during reset task")
Signed-off-by: Marcin Szycik <marcin.szycik at linux.intel.com>
Co-developed-by: Dawid Wesierski <dawidx.wesierski at intel.com>
Signed-off-by: Dawid Wesierski <dawidx.wesierski at intel.com>
Signed-off-by: Kamil Maziarz <kamil.maziarz at intel.com>
---
v1->v3: changes were done internally
v4: added space before open parenthesis '(', fixed code indent
v5: changed the way the scheduled function updates the netdev from { trylock -> reschedule }
design to one that just takes and wait for rtnl_lock lock. Introduced 30ms delay in scheduling
to account for scheduling the resets in quick succession.
v6: added a guard to iavf_delayed_set_interrupt_capability function to prevent updating an unregistered netdev.
Removed the delay from the scheduling and moved the rtnl to obtain the number of queues after acquiring the lock
---
drivers/net/ethernet/intel/iavf/iavf.h | 1 +
drivers/net/ethernet/intel/iavf/iavf_main.c | 38 ++++++++++++++++++---
2 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/intel/iavf/iavf.h b/drivers/net/ethernet/intel/iavf/iavf.h
index c51b9ed4dc29..f49b7ac4ab43 100644
--- a/drivers/net/ethernet/intel/iavf/iavf.h
+++ b/drivers/net/ethernet/intel/iavf/iavf.h
@@ -255,6 +255,7 @@ struct iavf_adapter {
struct workqueue_struct *wq;
struct work_struct reset_task;
struct work_struct adminq_task;
+ struct work_struct set_interrupt_capability;
struct delayed_work client_task;
wait_queue_head_t down_waitqueue;
wait_queue_head_t reset_waitqueue;
diff --git a/drivers/net/ethernet/intel/iavf/iavf_main.c b/drivers/net/ethernet/intel/iavf/iavf_main.c
index bc17ea34bb73..ba9dc64220ce 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_main.c
+++ b/drivers/net/ethernet/intel/iavf/iavf_main.c
@@ -1707,11 +1707,38 @@ static int iavf_set_interrupt_capability(struct iavf_adapter *adapter)
err = iavf_acquire_msix_vectors(adapter, v_budget);
out:
- netif_set_real_num_rx_queues(adapter->netdev, pairs);
- netif_set_real_num_tx_queues(adapter->netdev, pairs);
+ if (rtnl_trylock()) {
+ netif_set_real_num_rx_queues(adapter->netdev, pairs);
+ netif_set_real_num_tx_queues(adapter->netdev, pairs);
+ rtnl_unlock();
+ } else {
+ schedule_work(&adapter->set_interrupt_capability);
+ }
+
return err;
}
+/**
+ * iavf_delayed_set_interrupt_capability - schedule the update of the netdev
+ * @work: pointer to work_struct containing our data
+ **/
+static void iavf_delayed_set_interrupt_capability(struct work_struct *work)
+{
+ struct iavf_adapter *adapter;
+ int pairs;
+
+ rtnl_lock();
+ adapter = container_of(work, struct iavf_adapter,
+ set_interrupt_capability);
+ pairs = adapter->num_active_queues;
+
+ if (adapter->netdev_registered) {
+ netif_set_real_num_rx_queues(adapter->netdev, pairs);
+ netif_set_real_num_tx_queues(adapter->netdev, pairs);
+ }
+ rtnl_unlock();
+}
+
/**
* iavf_config_rss_aq - Configure RSS keys and lut by using AQ commands
* @adapter: board private structure
@@ -1930,10 +1957,8 @@ int iavf_init_interrupt_scheme(struct iavf_adapter *adapter)
"Unable to allocate memory for queues\n");
goto err_alloc_queues;
}
-
- rtnl_lock();
err = iavf_set_interrupt_capability(adapter);
- rtnl_unlock();
+
if (err) {
dev_err(&adapter->pdev->dev,
"Unable to setup interrupt capabilities\n");
@@ -4983,6 +5008,8 @@ static int iavf_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
INIT_WORK(&adapter->reset_task, iavf_reset_task);
INIT_WORK(&adapter->adminq_task, iavf_adminq_task);
+ INIT_WORK(&adapter->set_interrupt_capability,
+ iavf_delayed_set_interrupt_capability);
INIT_DELAYED_WORK(&adapter->watchdog_task, iavf_watchdog_task);
INIT_DELAYED_WORK(&adapter->client_task, iavf_client_task);
queue_delayed_work(adapter->wq, &adapter->watchdog_task,
@@ -5156,6 +5183,7 @@ static void iavf_remove(struct pci_dev *pdev)
cancel_work_sync(&adapter->reset_task);
cancel_delayed_work_sync(&adapter->watchdog_task);
cancel_work_sync(&adapter->adminq_task);
+ cancel_work_sync(&adapter->set_interrupt_capability);
cancel_delayed_work_sync(&adapter->client_task);
adapter->aq_required = 0;
--
2.31.1
More information about the Intel-wired-lan
mailing list