Implement PCI device reset handler to allow the network device to get re-initialized and function after a PCI-level reset.
This is necessary for the adapter to avoid TX queue timeouts occurring after the PCI reset is performed via sysfs during its operation. The reset codepath may trigger a number of dependencies in the reset of the driver, so that it is necessary to check if the netdev is present and running there. Signed-off-by: Sergey Temerkhanov <[email protected]> Reviewed-by: Przemyslaw Korba <[email protected]> Reviewed-by: Aleksandr Loktionov <[email protected]> Reviewed-by: Simon Horman <[email protected]> --- drivers/net/ethernet/intel/ixgbe/ixgbe.h | 1 + drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 111 ++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe.h b/drivers/net/ethernet/intel/ixgbe/ixgbe.h index bac3b85a1d63..88247e760bda 100644 --- a/drivers/net/ethernet/intel/ixgbe/ixgbe.h +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe.h @@ -912,6 +912,7 @@ enum ixgbe_state_t { __IXGBE_PTP_TX_IN_PROGRESS, __IXGBE_RESET_REQUESTED, __IXGBE_PHY_INIT_COMPLETE, + __IXGBE_PCI_RESET_PREPARED, }; struct ixgbe_cb { diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c index 6ef51b822a50..da26d1013f3e 100644 --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c @@ -10537,6 +10537,9 @@ void ixgbe_do_reset(struct net_device *netdev) { struct ixgbe_adapter *adapter = ixgbe_from_netdev(netdev); + if (!netif_device_present(netdev)) + return; + if (netif_running(netdev)) ixgbe_reinit_locked(adapter); else @@ -12396,6 +12399,7 @@ static int ixgbe_rebuild_fcoe_queues(struct ixgbe_adapter *adapter) ixgbe_clear_interrupt_scheme(adapter); err = ixgbe_init_interrupt_scheme(adapter); + /* Keep reinit_pending set so ixgbe_open() retries before using queues. */ if (err) return err; @@ -12404,6 +12408,110 @@ static int ixgbe_rebuild_fcoe_queues(struct ixgbe_adapter *adapter) return 0; } +/** + * ixgbe_pci_reset_prepare - called before the pci bus is reset. + * @pdev: Pointer to PCI device + * + * Quiesce the driver in preparation for a PCI function reset. Called from + * pci_dev_save_and_disable() before the core saves config state and writes + * PCI_COMMAND_INTX_DISABLE to clear bus mastering and MMIO decode, so MMIO + * access to the device is still valid here. + */ +static void ixgbe_pci_reset_prepare(struct pci_dev *pdev) +{ + struct ixgbe_adapter *adapter = pci_get_drvdata(pdev); + struct net_device *netdev; + + if (!adapter) + return; + + netdev = adapter->netdev; + + rtnl_lock(); + if (!netif_device_present(netdev)) { + rtnl_unlock(); + return; + } + + set_bit(__IXGBE_PCI_RESET_PREPARED, &adapter->state); + netif_device_detach(netdev); + if (netif_running(netdev)) + ixgbe_close_suspend(adapter); + rtnl_unlock(); + + /* __IXGBE_RESETTING is intentionally not set here: it is spun on + * while holding rtnl by ixgbe_reinit_locked(), ixgbe_dcbnl_devreset() + * and the ethtool reset paths, so holding it across the rtnl drop + * would deadlock those callers against ixgbe_pci_reset_done(), which + * needs to re-acquire rtnl. During the reset window concurrent + * rtnl-holding paths must treat the netdev as detached, while teardown + * paths also observe __IXGBE_DOWN set by ixgbe_down() via + * ixgbe_close_suspend(), matching the existing ixgbe_io_error_detected() + * flow. + */ + + if (test_bit(__IXGBE_SERVICE_INITED, &adapter->state)) { + /* The service timer was already stopped by ixgbe_down() via + * ixgbe_close_suspend(); if the netdev was not running, the + * timer is not armed. Only the currently queued service task + * (if any) still needs to be flushed here. + */ + cancel_work_sync(&adapter->service_task); + clear_bit(__IXGBE_SERVICE_SCHED, &adapter->state); + } +} + +/** + * ixgbe_pci_reset_done - called after the pci bus has been reset. + * @pdev: Pointer to PCI device + * + * Re-initialize the device after a PCI function reset. The PCI core has + * already called pci_restore_state() before invoking this callback, so the + * saved Command register (including bus mastering) is back in place. + */ +static void ixgbe_pci_reset_done(struct pci_dev *pdev) +{ + struct ixgbe_adapter *adapter = pci_get_drvdata(pdev); + struct net_device *netdev; + bool running; + int err = 0; + + if (!adapter) + return; + + netdev = adapter->netdev; + + rtnl_lock(); + if (!test_and_clear_bit(__IXGBE_PCI_RESET_PREPARED, &adapter->state)) { + rtnl_unlock(); + return; + } + + adapter->hw.hw_addr = adapter->io_addr; + ixgbe_reset(adapter); + IXGBE_WRITE_REG(&adapter->hw, IXGBE_WUS, ~0); + running = netif_running(netdev); + + err = ixgbe_rebuild_fcoe_queues(adapter); + if (err) + e_dev_err("Cannot rebuild queue layout after PCI reset: %d\n", + err); + if (running && !err) { + err = ixgbe_open(netdev); + if (err) + e_dev_err("Cannot re-open netdev after PCI reset: %d. A new reset is needed.\n", + err); + } + if (err && running) + dev_close(netdev); + /* Restore presence so userspace can retry later. If recovery failed, + * dev_close() cleared IFF_UP first so netif_device_attach() will not wake + * Tx queues without a successful open. + */ + netif_device_attach(netdev); + rtnl_unlock(); +} + /** * ixgbe_io_resume - called when traffic can start flowing again. * @pdev: Pointer to PCI device @@ -12441,6 +12549,7 @@ static void ixgbe_io_resume(struct pci_dev *pdev) if (err && running) dev_close(netdev); + /* A failed running device is down; attach permits a later open retry. */ netif_device_attach(netdev); rtnl_unlock(); } @@ -12449,6 +12558,8 @@ static const struct pci_error_handlers ixgbe_err_handler = { .error_detected = ixgbe_io_error_detected, .slot_reset = ixgbe_io_slot_reset, .resume = ixgbe_io_resume, + .reset_prepare = ixgbe_pci_reset_prepare, + .reset_done = ixgbe_pci_reset_done, }; static DEFINE_SIMPLE_DEV_PM_OPS(ixgbe_pm_ops, ixgbe_suspend, ixgbe_resume); -- 2.53.0
