sfc: Fix EEH with legacy interrupts.
authorAlexandre Rames <arames@solarflare.com>
Thu, 21 Mar 2013 16:41:43 +0000 (16:41 +0000)
committerBen Hutchings <bhutchings@solarflare.com>
Mon, 24 Jun 2013 18:58:25 +0000 (19:58 +0100)
PCI legacy interrupts are level-triggered, and we cannot mask them up
on an isolated device.  Instead, disable the IRQ at the controller
until we have recovered.

Signed-off-by: Ben Hutchings <bhutchings@solarflare.com>
drivers/net/ethernet/sfc/efx.c
drivers/net/ethernet/sfc/efx.h
drivers/net/ethernet/sfc/net_driver.h
drivers/net/ethernet/sfc/nic.c

index 46cc11d5e205a22cdb6e6e4308731ea0b75b8b93..787c9ebc17eed595a6cac416f1c102e9ede63b1a 100644 (file)
@@ -23,6 +23,7 @@
 #include <linux/gfp.h>
 #include <linux/cpu_rmap.h>
 #include <linux/aer.h>
+#include <linux/interrupt.h>
 #include "net_driver.h"
 #include "efx.h"
 #include "nic.h"
@@ -1427,6 +1428,10 @@ static void efx_start_interrupts(struct efx_nic *efx, bool may_keep_eventq)
 
        BUG_ON(efx->state == STATE_DISABLED);
 
+       if (efx->eeh_disabled_legacy_irq) {
+               enable_irq(efx->legacy_irq);
+               efx->eeh_disabled_legacy_irq = false;
+       }
        if (efx->legacy_irq)
                efx->legacy_irq_enabled = true;
        efx_nic_enable_interrupts(efx);
@@ -2365,7 +2370,7 @@ out:
  * Returns 0 if the recovery mechanisms are unsuccessful.
  * Returns a non-zero value otherwise.
  */
-static int efx_try_recovery(struct efx_nic *efx)
+int efx_try_recovery(struct efx_nic *efx)
 {
 #ifdef CONFIG_EEH
        /* A PCI error can occur and not be seen by EEH because nothing
index 8372da239b43eef9928fa6d207b60a774a83aa5c..bdb30bbb0c973f13d05e86491b20f9a1e5470111 100644 (file)
@@ -124,6 +124,7 @@ extern const struct ethtool_ops efx_ethtool_ops;
 extern int efx_reset(struct efx_nic *efx, enum reset_type method);
 extern void efx_reset_down(struct efx_nic *efx, enum reset_type method);
 extern int efx_reset_up(struct efx_nic *efx, enum reset_type method, bool ok);
+extern int efx_try_recovery(struct efx_nic *efx);
 
 /* Global */
 extern void efx_schedule_reset(struct efx_nic *efx, enum reset_type type);
index 9a2914cfd3453e229575c51b85e707ea6c03dc49..2dec48bed27486b5915b3958080c41b3290eff2d 100644 (file)
@@ -788,6 +788,7 @@ struct efx_nic {
        const struct efx_nic_type *type;
        int legacy_irq;
        bool legacy_irq_enabled;
+       bool eeh_disabled_legacy_irq;
        struct workqueue_struct *workqueue;
        char workqueue_name[16];
        struct work_struct reset_work;
index b0503cd8c2a0807ad8becde51644d7054b56896d..39432d3d1fe908aa4134bc5e0f49cb771f126848 100644 (file)
@@ -1579,6 +1579,16 @@ static irqreturn_t efx_legacy_interrupt(int irq, void *dev_id)
        efx_readd(efx, &reg, FR_BZ_INT_ISR0);
        queues = EFX_EXTRACT_DWORD(reg, 0, 31);
 
+       /* Legacy interrupts are disabled too late by the EEH kernel
+        * code. Disable them earlier.
+        * If an EEH error occurred, the read will have returned all ones.
+        */
+       if (EFX_DWORD_IS_ALL_ONES(reg) && efx_try_recovery(efx) &&
+           !efx->eeh_disabled_legacy_irq) {
+               disable_irq_nosync(efx->legacy_irq);
+               efx->eeh_disabled_legacy_irq = true;
+       }
+
        /* Handle non-event-queue sources */
        if (queues & (1U << efx->irq_level)) {
                syserr = EFX_OWORD_FIELD(*int_ker, FSF_AZ_NET_IVEC_FATAL_INT);