mac80211: brcmfmac: backport fixes for 4.7 and 4.8
[openwrt/svn-archive/archive.git] / package / kernel / mac80211 / patches / 351-0003-brcmfmac-Fix-kernel-oops-in-failed-chip_attach.patch
diff --git a/package/kernel/mac80211/patches/351-0003-brcmfmac-Fix-kernel-oops-in-failed-chip_attach.patch b/package/kernel/mac80211/patches/351-0003-brcmfmac-Fix-kernel-oops-in-failed-chip_attach.patch
new file mode 100644 (file)
index 0000000..59f1e42
--- /dev/null
@@ -0,0 +1,88 @@
+From: Christian Daudt <csd@broadcom.com>
+Date: Wed, 11 May 2016 15:06:48 -0700
+Subject: [PATCH] brcmfmac: Fix kernel oops in failed chip_attach
+
+When chip attach fails, brcmf_sdiod_intr_unregister is being called
+but that is too early as sdiodev->settings has not been set yet
+nor has brcmf_sdiod_intr_register been called.
+Change to use oob_irq_requested + newly created sd_irq_requested
+to decide on what to unregister at intr_unregister time.
+
+Steps to reproduce problem:
+- modprobe brcmfmac using buggy FW
+- rmmod brcmfmac
+- modprobe brcmfmac again.
+
+If done with a buggy firmware, brcm_chip_attach will fail on the
+2nd modprobe triggering the call to intr_unregister and the
+kernel oops when attempting to de-reference sdiodev->settings->bus.sdio
+which has not yet been set.
+
+Signed-off-by: Christian Daudt <csd@broadcom.com>
+Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
+---
+
+--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c
++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c
+@@ -166,6 +166,7 @@ int brcmf_sdiod_intr_register(struct brc
+               sdio_claim_irq(sdiodev->func[1], brcmf_sdiod_ib_irqhandler);
+               sdio_claim_irq(sdiodev->func[2], brcmf_sdiod_dummy_irqhandler);
+               sdio_release_host(sdiodev->func[1]);
++              sdiodev->sd_irq_requested = true;
+       }
+       return 0;
+@@ -173,27 +174,30 @@ int brcmf_sdiod_intr_register(struct brc
+ int brcmf_sdiod_intr_unregister(struct brcmf_sdio_dev *sdiodev)
+ {
+-      struct brcmfmac_sdio_pd *pdata;
+-      brcmf_dbg(SDIO, "Entering\n");
++      brcmf_dbg(SDIO, "Entering oob=%d sd=%d\n",
++                sdiodev->oob_irq_requested,
++                sdiodev->sd_irq_requested);
+-      pdata = &sdiodev->settings->bus.sdio;
+-      if (pdata->oob_irq_supported) {
++      if (sdiodev->oob_irq_requested) {
++              struct brcmfmac_sdio_pd *pdata;
++
++              pdata = &sdiodev->settings->bus.sdio;
+               sdio_claim_host(sdiodev->func[1]);
+               brcmf_sdiod_regwb(sdiodev, SDIO_CCCR_BRCM_SEPINT, 0, NULL);
+               brcmf_sdiod_regwb(sdiodev, SDIO_CCCR_IENx, 0, NULL);
+               sdio_release_host(sdiodev->func[1]);
+-              if (sdiodev->oob_irq_requested) {
+-                      sdiodev->oob_irq_requested = false;
+-                      if (sdiodev->irq_wake) {
+-                              disable_irq_wake(pdata->oob_irq_nr);
+-                              sdiodev->irq_wake = false;
+-                      }
+-                      free_irq(pdata->oob_irq_nr, &sdiodev->func[1]->dev);
+-                      sdiodev->irq_en = false;
++              sdiodev->oob_irq_requested = false;
++              if (sdiodev->irq_wake) {
++                      disable_irq_wake(pdata->oob_irq_nr);
++                      sdiodev->irq_wake = false;
+               }
+-      } else {
++              free_irq(pdata->oob_irq_nr, &sdiodev->func[1]->dev);
++              sdiodev->irq_en = false;
++      }
++
++      if (sdiodev->sd_irq_requested) {
+               sdio_claim_host(sdiodev->func[1]);
+               sdio_release_irq(sdiodev->func[2]);
+               sdio_release_irq(sdiodev->func[1]);
+--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h
++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h
+@@ -186,6 +186,7 @@ struct brcmf_sdio_dev {
+       struct brcmf_bus *bus_if;
+       struct brcmf_mp_device *settings;
+       bool oob_irq_requested;
++      bool sd_irq_requested;
+       bool irq_en;                    /* irq enable flags */
+       spinlock_t irq_en_lock;
+       bool irq_wake;                  /* irq wake enable flags */