| From bd81b32641b436d5db315012d4084c218890b257 Mon Sep 17 00:00:00 2001 |
| From: "Guilherme G. Piccoli" <gpiccoli@canonical.com> |
| Date: Fri, 20 Mar 2020 09:55:34 -0300 |
| Subject: [PATCH] net: ena: Add PCI shutdown handler to allow safe kexec |
| |
| commit 428c491332bca498c8eb2127669af51506c346c7 upstream. |
| |
| Currently ENA only provides the PCI remove() handler, used during rmmod |
| for example. This is not called on shutdown/kexec path; we are potentially |
| creating a failure scenario on kexec: |
| |
| (a) Kexec is triggered, no shutdown() / remove() handler is called for ENA; |
| instead pci_device_shutdown() clears the master bit of the PCI device, |
| stopping all DMA transactions; |
| |
| (b) Kexec reboot happens and the device gets enabled again, likely having |
| its FW with that DMA transaction buffered; then it may trigger the (now |
| invalid) memory operation in the new kernel, corrupting kernel memory area. |
| |
| This patch aims to prevent this, by implementing a shutdown() handler |
| quite similar to the remove() one - the difference being the handling |
| of the netdev, which is unregistered on remove(), but following the |
| convention observed in other drivers, it's only detached on shutdown(). |
| |
| This prevents an odd issue in AWS Nitro instances, in which after the 2nd |
| kexec the next one will fail with an initrd corruption, caused by a wild |
| DMA write to invalid kernel memory. The lspci output for the adapter |
| present in my instance is: |
| |
| 00:05.0 Ethernet controller [0200]: Amazon.com, Inc. Elastic Network |
| Adapter (ENA) [1d0f:ec20] |
| |
| Suggested-by: Gavin Shan <gshan@redhat.com> |
| Signed-off-by: Guilherme G. Piccoli <gpiccoli@canonical.com> |
| Acked-by: Sameeh Jubran <sameehj@amazon.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/drivers/net/ethernet/amazon/ena/ena_netdev.c b/drivers/net/ethernet/amazon/ena/ena_netdev.c |
| index 803d735f2889..14baeb7a2b64 100644 |
| --- a/drivers/net/ethernet/amazon/ena/ena_netdev.c |
| +++ b/drivers/net/ethernet/amazon/ena/ena_netdev.c |
| @@ -3487,13 +3487,15 @@ static int ena_probe(struct pci_dev *pdev, const struct pci_device_id *ent) |
| |
| /*****************************************************************************/ |
| |
| -/* ena_remove - Device Removal Routine |
| +/* __ena_shutoff - Helper used in both PCI remove/shutdown routines |
| * @pdev: PCI device information struct |
| + * @shutdown: Is it a shutdown operation? If false, means it is a removal |
| * |
| - * ena_remove is called by the PCI subsystem to alert the driver |
| - * that it should release a PCI device. |
| + * __ena_shutoff is a helper routine that does the real work on shutdown and |
| + * removal paths; the difference between those paths is with regards to whether |
| + * dettach or unregister the netdevice. |
| */ |
| -static void ena_remove(struct pci_dev *pdev) |
| +static void __ena_shutoff(struct pci_dev *pdev, bool shutdown) |
| { |
| struct ena_adapter *adapter = pci_get_drvdata(pdev); |
| struct ena_com_dev *ena_dev; |
| @@ -3512,13 +3514,17 @@ static void ena_remove(struct pci_dev *pdev) |
| |
| cancel_work_sync(&adapter->reset_task); |
| |
| - rtnl_lock(); |
| + rtnl_lock(); /* lock released inside the below if-else block */ |
| ena_destroy_device(adapter, true); |
| - rtnl_unlock(); |
| - |
| - unregister_netdev(netdev); |
| - |
| - free_netdev(netdev); |
| + if (shutdown) { |
| + netif_device_detach(netdev); |
| + dev_close(netdev); |
| + rtnl_unlock(); |
| + } else { |
| + rtnl_unlock(); |
| + unregister_netdev(netdev); |
| + free_netdev(netdev); |
| + } |
| |
| ena_com_rss_destroy(ena_dev); |
| |
| @@ -3535,6 +3541,30 @@ static void ena_remove(struct pci_dev *pdev) |
| vfree(ena_dev); |
| } |
| |
| +/* ena_remove - Device Removal Routine |
| + * @pdev: PCI device information struct |
| + * |
| + * ena_remove is called by the PCI subsystem to alert the driver |
| + * that it should release a PCI device. |
| + */ |
| + |
| +static void ena_remove(struct pci_dev *pdev) |
| +{ |
| + __ena_shutoff(pdev, false); |
| +} |
| + |
| +/* ena_shutdown - Device Shutdown Routine |
| + * @pdev: PCI device information struct |
| + * |
| + * ena_shutdown is called by the PCI subsystem to alert the driver that |
| + * a shutdown/reboot (or kexec) is happening and device must be disabled. |
| + */ |
| + |
| +static void ena_shutdown(struct pci_dev *pdev) |
| +{ |
| + __ena_shutoff(pdev, true); |
| +} |
| + |
| #ifdef CONFIG_PM |
| /* ena_suspend - PM suspend callback |
| * @pdev: PCI device information struct |
| @@ -3584,6 +3614,7 @@ static struct pci_driver ena_pci_driver = { |
| .id_table = ena_pci_tbl, |
| .probe = ena_probe, |
| .remove = ena_remove, |
| + .shutdown = ena_shutdown, |
| #ifdef CONFIG_PM |
| .suspend = ena_suspend, |
| .resume = ena_resume, |
| -- |
| 2.7.4 |
| |