ath10k: make sure to really disable irqs
authorMichal Kazior <michal.kazior@tieto.com>
Fri, 22 Aug 2014 12:23:33 +0000 (14:23 +0200)
committerKalle Valo <kvalo@qca.qualcomm.com>
Mon, 25 Aug 2014 08:28:56 +0000 (11:28 +0300)
This fixes two corner cases.

One is a race between disabling copy engine
interrupts and unhandled pending interrupts on the
host. This could end up with a runaway tasklet and
consequently memory leak of a few copy engine
rx buffers.

The other one is an unexpected (and non-maskable
via device CSR) MSI fw indication interrupt during
teardown. This could trigger the same problem as
the first corner case.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
drivers/net/wireless/ath/ath10k/pci.c

index c764dd713f009df24eaca31ba15dc8d266a76a52..6224952ba523574845e222dee977325fe6bf0afa 100644 (file)
@@ -1121,6 +1121,40 @@ static int ath10k_pci_post_rx(struct ath10k *ar)
        return 0;
 }
 
+static void ath10k_pci_irq_disable(struct ath10k *ar)
+{
+       struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+       int i;
+
+       ath10k_ce_disable_interrupts(ar);
+
+       /* Regardless how many interrupts were assigned for MSI the first one
+        * is always used for firmware indications (crashes). There's no way to
+        * mask the irq in the device so call disable_irq(). Legacy (shared)
+        * interrupts can be masked on the device though.
+        */
+       if (ar_pci->num_msi_intrs > 0)
+               disable_irq(ar_pci->pdev->irq);
+       else
+               ath10k_pci_disable_and_clear_legacy_irq(ar);
+
+       for (i = 0; i < max(1, ar_pci->num_msi_intrs); i++)
+               synchronize_irq(ar_pci->pdev->irq + i);
+}
+
+static void ath10k_pci_irq_enable(struct ath10k *ar)
+{
+       struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+
+       ath10k_ce_enable_interrupts(ar);
+
+       /* See comment in ath10k_pci_irq_disable() */
+       if (ar_pci->num_msi_intrs > 0)
+               enable_irq(ar_pci->pdev->irq);
+       else
+               ath10k_pci_enable_legacy_irq(ar);
+}
+
 static int ath10k_pci_hif_start(struct ath10k *ar)
 {
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
@@ -1138,7 +1172,7 @@ static int ath10k_pci_hif_start(struct ath10k *ar)
                goto err_early_irq;
        }
 
-       ath10k_ce_enable_interrupts(ar);
+       ath10k_pci_irq_enable(ar);
 
        /* Post buffers once to start things off. */
        ret = ath10k_pci_post_rx(ar);
@@ -1152,7 +1186,7 @@ static int ath10k_pci_hif_start(struct ath10k *ar)
        return 0;
 
 err_stop:
-       ath10k_ce_disable_interrupts(ar);
+       ath10k_pci_irq_disable(ar);
        ath10k_pci_free_irq(ar);
        ath10k_pci_kill_tasklet(ar);
 err_early_irq:
@@ -1275,10 +1309,7 @@ static void ath10k_pci_hif_stop(struct ath10k *ar)
        if (WARN_ON(!ar_pci->started))
                return;
 
-       ret = ath10k_ce_disable_interrupts(ar);
-       if (ret)
-               ath10k_warn("failed to disable CE interrupts: %d\n", ret);
-
+       ath10k_pci_irq_disable(ar);
        ath10k_pci_free_irq(ar);
        ath10k_pci_kill_tasklet(ar);