From: Rafał Miłecki <[email protected]>

This includes bus reset & reloading a firmware. It should be sufficient
for a user space to (setup and) use a wireless device again.

Support for reset on USB & SDIO can be added later.

Signed-off-by: Rafał Miłecki <[email protected]>
---
 .../broadcom/brcm80211/brcmfmac/bus.h         | 10 ++++++
 .../broadcom/brcm80211/brcmfmac/core.c        | 12 +++++++
 .../broadcom/brcm80211/brcmfmac/core.h        |  2 ++
 .../broadcom/brcm80211/brcmfmac/pcie.c        | 35 +++++++++++++++++++
 4 files changed, 59 insertions(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h 
b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
index 801106583ae7..2fe167eae22c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
@@ -91,6 +91,7 @@ struct brcmf_bus_ops {
        int (*get_fwname)(struct device *dev, const char *ext,
                          unsigned char *fw_name);
        void (*debugfs_create)(struct device *dev);
+       int (*reset)(struct device *dev);
 };
 
 
@@ -245,6 +246,15 @@ void brcmf_bus_debugfs_create(struct brcmf_bus *bus)
        return bus->ops->debugfs_create(bus->dev);
 }
 
+static inline
+int brcmf_bus_reset(struct brcmf_bus *bus)
+{
+       if (!bus->ops->reset)
+               return -EOPNOTSUPP;
+
+       return bus->ops->reset(bus->dev);
+}
+
 /*
  * interface functions from common layer
  */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c 
b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
index 7f4d9356b79e..5f3548b13639 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
@@ -1084,6 +1084,14 @@ static int brcmf_revinfo_read(struct seq_file *s, void 
*data)
        return 0;
 }
 
+static void brcmf_core_bus_reset(struct work_struct *work)
+{
+       struct brcmf_pub *drvr = container_of(work, struct brcmf_pub,
+                                             bus_reset);
+
+       brcmf_bus_reset(drvr->bus_if);
+}
+
 static int brcmf_bus_started(struct brcmf_pub *drvr, struct cfg80211_ops *ops)
 {
        int ret = -1;
@@ -1155,6 +1163,8 @@ static int brcmf_bus_started(struct brcmf_pub *drvr, 
struct cfg80211_ops *ops)
 #endif
 #endif /* CONFIG_INET */
 
+       INIT_WORK(&drvr->bus_reset, brcmf_core_bus_reset);
+
        /* populate debugfs */
        brcmf_debugfs_add_entry(drvr, "revinfo", brcmf_revinfo_read);
        brcmf_feat_debugfs_create(drvr);
@@ -1281,6 +1291,8 @@ void brcmf_fw_crashed(struct device *dev)
        bphy_err(drvr, "Firmware has halted or crashed\n");
 
        brcmf_dev_coredump(dev);
+
+       schedule_work(&drvr->bus_reset);
 }
 
 void brcmf_detach(struct device *dev)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h 
b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
index d8085ce579f4..9f09aa31eeda 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
@@ -143,6 +143,8 @@ struct brcmf_pub {
        struct notifier_block inet6addr_notifier;
        struct brcmf_mp_device *settings;
 
+       struct work_struct bus_reset;
+
        u8 clmver[BRCMF_DCMD_SMLEN];
 };
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c 
b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index cfa34672315f..e941039ee1c3 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -345,6 +345,10 @@ static const u32 
brcmf_ring_itemsize[BRCMF_NROF_COMMON_MSGRINGS] = {
        BRCMF_D2H_MSGRING_RX_COMPLETE_ITEMSIZE
 };
 
+static void brcmf_pcie_setup(struct device *dev, int ret,
+                            struct brcmf_fw_request *fwreq);
+static struct brcmf_fw_request *
+brcmf_pcie_prepare_fw_request(struct brcmf_pciedev_info *devinfo);
 
 static u32
 brcmf_pcie_read_reg32(struct brcmf_pciedev_info *devinfo, u32 reg_offset)
@@ -1409,6 +1413,36 @@ int brcmf_pcie_get_fwname(struct device *dev, const char 
*ext, u8 *fw_name)
        return 0;
 }
 
+static int brcmf_pcie_reset(struct device *dev)
+{
+       struct brcmf_bus *bus_if = dev_get_drvdata(dev);
+       struct brcmf_pciedev *buspub = bus_if->bus_priv.pcie;
+       struct brcmf_pciedev_info *devinfo = buspub->devinfo;
+       struct brcmf_fw_request *fwreq;
+       int err;
+
+       brcmf_detach(dev);
+
+       brcmf_pcie_release_irq(devinfo);
+       brcmf_pcie_release_scratchbuffers(devinfo);
+       brcmf_pcie_release_ringbuffers(devinfo);
+       brcmf_pcie_reset_device(devinfo);
+
+       fwreq = brcmf_pcie_prepare_fw_request(devinfo);
+       if (!fwreq) {
+               dev_err(dev, "Failed to prepare FW request\n");
+               return -ENOMEM;
+       }
+
+       err = brcmf_fw_get_firmwares(dev, fwreq, brcmf_pcie_setup);
+       if (err) {
+               dev_err(dev, "Failed to prepare FW request\n");
+               kfree(fwreq);
+       }
+
+       return err;
+}
+
 static const struct brcmf_bus_ops brcmf_pcie_bus_ops = {
        .txdata = brcmf_pcie_tx,
        .stop = brcmf_pcie_down,
@@ -1418,6 +1452,7 @@ static const struct brcmf_bus_ops brcmf_pcie_bus_ops = {
        .get_ramsize = brcmf_pcie_get_ramsize,
        .get_memdump = brcmf_pcie_get_memdump,
        .get_fwname = brcmf_pcie_get_fwname,
+       .reset = brcmf_pcie_reset,
 };
 
 
-- 
2.20.1

Reply via email to