e1000e_down could be entered simultaneously by the reset task and the
runtime PM suspend callback. Prevent this by acquiring a runtime PM
reference in the reset task.
The intention is that:
* A reset attempted during runtime suspend should drop silently; the
interface is already going down.
* A runtime suspend attempted during a reset will be retried later.
* All callers of e1000e_down are now serialized.
Signed-off-by: Matt Vollrath <redacted>
Fixes: 23606cf5d119 ("e1000e / PCI / PM: Add basic runtime PM support (rev. 4)")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-5-fable
---
drivers/net/ethernet/intel/e1000e/netdev.c | 25 ++++++++++++++++++----
1 file changed, 21 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/intel/e1000e/netdev.c b/drivers/net/ethernet/intel/e1000e/netdev.c
index 47ff3c6ab451..4c15ca307b08 100644
--- a/drivers/net/ethernet/intel/e1000e/netdev.c
+++ b/drivers/net/ethernet/intel/e1000e/netdev.c
@@ -6004,20 +6004,37 @@ static void e1000_tx_timeout(struct net_device *netdev, unsigned int __always_un
static void e1000_reset_task(struct work_struct *work)
{
struct e1000_adapter *adapter;
+ struct device *dev;
+ int rc;
+
adapter = container_of(work, struct e1000_adapter, reset_task);
+ dev = &adapter->pdev->dev;
rtnl_lock();
+
+ /* Runtime suspend downs the device without holding rtnl. Hold a
+ * runtime PM reference so it cannot start underneath the reset, and
+ * skip the reset if the device is already suspending or suspended:
+ * resuming resets the hardware anyway.
+ */
+ rc = pm_runtime_get_if_active(dev);
+ if (!rc)
+ goto out_unlock;
+
/* don't run the task if already down */
- if (test_bit(__E1000_DOWN, &adapter->state)) {
- rtnl_unlock();
- return;
- }
+ if (test_bit(__E1000_DOWN, &adapter->state))
+ goto out_put;
if (!(adapter->flags & FLAG_RESTART_NOW)) {
e1000e_dump(adapter);
e_err("Reset adapter unexpectedly\n");
}
e1000e_reinit_locked(adapter);
+
+out_put:
+ if (rc > 0)
+ pm_runtime_put(dev);
+out_unlock:
rtnl_unlock();
}
--
2.43.0