brcmfmac: expose device memory to devcoredump subsystem
authorArend van Spriel <arend@broadcom.com>
Thu, 8 Oct 2015 18:33:11 +0000 (20:33 +0200)
committerKalle Valo <kvalo@codeaurora.org>
Wed, 21 Oct 2015 07:56:23 +0000 (10:56 +0300)
Upon PSM watchdog event received from firmware the driver will obtain
a memory snapshot of the device and expose it to user-space through
the devcoredump framework. This will trigger a uevent.

Reviewed-by: Hante Meuleman <meuleman@broadcom.com>
Reviewed-by: Franky (Zhenhui) Lin <frankyl@broadcom.com>
Reviewed-by: Pieter-Paul Giesberts <pieterpg@broadcom.com>
Signed-off-by: Arend van Spriel <arend@broadcom.com>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
drivers/net/wireless/brcm80211/Kconfig
drivers/net/wireless/brcm80211/brcmfmac/bus.h
drivers/net/wireless/brcm80211/brcmfmac/core.c
drivers/net/wireless/brcm80211/brcmfmac/debug.c
drivers/net/wireless/brcm80211/brcmfmac/debug.h
drivers/net/wireless/brcm80211/brcmfmac/pcie.c
drivers/net/wireless/brcm80211/brcmfmac/sdio.c

index fe3dc126b149f8fd014f0bd18ff407d80af00003..ab42b1fea03c170e6728f5b71fe720aa61174053 100644 (file)
@@ -82,5 +82,6 @@ config BRCM_TRACING
 config BRCMDBG
        bool "Broadcom driver debug functions"
        depends on BRCMSMAC || BRCMFMAC
+       select WANT_DEV_COREDUMP
        ---help---
          Selecting this enables additional code for debug purposes.
index 89e6a4dc105ecff9e0251d44cc576bc8e5f4facd..230cad788ace44ebe9bff08b3c92562bf0887159 100644 (file)
@@ -65,6 +65,8 @@ struct brcmf_bus_dcmd {
  * @rxctl: receive a control response message from dongle.
  * @gettxq: obtain a reference of bus transmit queue (optional).
  * @wowl_config: specify if dongle is configured for wowl when going to suspend
+ * @get_ramsize: obtain size of device memory.
+ * @get_memdump: obtain device memory dump in provided buffer.
  *
  * This structure provides an abstract interface towards the
  * bus specific driver. For control messages to common driver
@@ -79,6 +81,8 @@ struct brcmf_bus_ops {
        int (*rxctl)(struct device *dev, unsigned char *msg, uint len);
        struct pktq * (*gettxq)(struct device *dev);
        void (*wowl_config)(struct device *dev, bool enabled);
+       size_t (*get_ramsize)(struct device *dev);
+       int (*get_memdump)(struct device *dev, void *data, size_t len);
 };
 
 
@@ -185,6 +189,23 @@ void brcmf_bus_wowl_config(struct brcmf_bus *bus, bool enabled)
                bus->ops->wowl_config(bus->dev, enabled);
 }
 
+static inline size_t brcmf_bus_get_ramsize(struct brcmf_bus *bus)
+{
+       if (!bus->ops->get_ramsize)
+               return 0;
+
+       return bus->ops->get_ramsize(bus->dev);
+}
+
+static inline
+int brcmf_bus_get_memdump(struct brcmf_bus *bus, void *data, size_t len)
+{
+       if (!bus->ops->get_memdump)
+               return -EOPNOTSUPP;
+
+       return bus->ops->get_memdump(bus->dev, data, len);
+}
+
 /*
  * interface functions from common layer
  */
index 8c2a280f0c9879f4ddc38b873cb4a176ebfc95ee..a1d39b1966f7db277fbfe6d43ab74ba4043f0399 100644 (file)
@@ -957,8 +957,8 @@ int brcmf_attach(struct device *dev)
        drvr->bus_if = dev_get_drvdata(dev);
        drvr->bus_if->drvr = drvr;
 
-       /* create device debugfs folder */
-       brcmf_debugfs_attach(drvr);
+       /* attach debug facilities */
+       brcmf_debug_attach(drvr);
 
        /* Attach and link in the protocol */
        ret = brcmf_proto_attach(drvr);
@@ -1155,7 +1155,7 @@ void brcmf_detach(struct device *dev)
 
        brcmf_proto_detach(drvr);
 
-       brcmf_debugfs_detach(drvr);
+       brcmf_debug_detach(drvr);
        bus_if->drvr = NULL;
        kfree(drvr);
 }
index 2d6d0055385877087a467dfa1ab265538fdb7102..1299dccc78b4da22293677ec4cf0a1f763af2a19 100644 (file)
 #include <linux/debugfs.h>
 #include <linux/netdevice.h>
 #include <linux/module.h>
+#include <linux/devcoredump.h>
 
 #include <brcmu_wifi.h>
 #include <brcmu_utils.h>
 #include "core.h"
 #include "bus.h"
+#include "fweh.h"
 #include "debug.h"
 
 static struct dentry *root_folder;
 
+static int brcmf_debug_create_memdump(struct brcmf_bus *bus, const void *data,
+                                     size_t len)
+{
+       void *dump;
+       size_t ramsize;
+
+       ramsize = brcmf_bus_get_ramsize(bus);
+       if (ramsize) {
+               dump = vzalloc(len + ramsize);
+               if (!dump)
+                       return -ENOMEM;
+               memcpy(dump, data, len);
+               brcmf_bus_get_memdump(bus, dump + len, ramsize);
+               dev_coredumpv(bus->dev, dump, len + ramsize, GFP_KERNEL);
+       }
+       return 0;
+}
+
+static int brcmf_debug_psm_watchdog_notify(struct brcmf_if *ifp,
+                                          const struct brcmf_event_msg *evtmsg,
+                                          void *data)
+{
+       brcmf_dbg(TRACE, "enter: idx=%d\n", ifp->bssidx);
+
+       return brcmf_debug_create_memdump(ifp->drvr->bus_if, data,
+                                         evtmsg->datalen);
+}
+
 void brcmf_debugfs_init(void)
 {
        root_folder = debugfs_create_dir(KBUILD_MODNAME, NULL);
@@ -41,7 +71,7 @@ void brcmf_debugfs_exit(void)
        root_folder = NULL;
 }
 
-int brcmf_debugfs_attach(struct brcmf_pub *drvr)
+int brcmf_debug_attach(struct brcmf_pub *drvr)
 {
        struct device *dev = drvr->bus_if->dev;
 
@@ -49,12 +79,18 @@ int brcmf_debugfs_attach(struct brcmf_pub *drvr)
                return -ENODEV;
 
        drvr->dbgfs_dir = debugfs_create_dir(dev_name(dev), root_folder);
+       if (IS_ERR(drvr->dbgfs_dir))
+               return PTR_ERR(drvr->dbgfs_dir);
 
-       return PTR_ERR_OR_ZERO(drvr->dbgfs_dir);
+
+       return brcmf_fweh_register(drvr, BRCMF_E_PSM_WATCHDOG,
+                                  brcmf_debug_psm_watchdog_notify);
 }
 
-void brcmf_debugfs_detach(struct brcmf_pub *drvr)
+void brcmf_debug_detach(struct brcmf_pub *drvr)
 {
+       brcmf_fweh_unregister(drvr, BRCMF_E_PSM_WATCHDOG);
+
        if (!IS_ERR_OR_NULL(drvr->dbgfs_dir))
                debugfs_remove_recursive(drvr->dbgfs_dir);
 }
index 48648ca44ba8e4d017a6c93f3af4a43b802490a3..d0d9676f7f9de952692f40523e97409c104a8e91 100644 (file)
@@ -109,8 +109,8 @@ struct brcmf_pub;
 #ifdef DEBUG
 void brcmf_debugfs_init(void);
 void brcmf_debugfs_exit(void);
-int brcmf_debugfs_attach(struct brcmf_pub *drvr);
-void brcmf_debugfs_detach(struct brcmf_pub *drvr);
+int brcmf_debug_attach(struct brcmf_pub *drvr);
+void brcmf_debug_detach(struct brcmf_pub *drvr);
 struct dentry *brcmf_debugfs_get_devdir(struct brcmf_pub *drvr);
 int brcmf_debugfs_add_entry(struct brcmf_pub *drvr, const char *fn,
                            int (*read_fn)(struct seq_file *seq, void *data));
@@ -121,11 +121,11 @@ static inline void brcmf_debugfs_init(void)
 static inline void brcmf_debugfs_exit(void)
 {
 }
-static inline int brcmf_debugfs_attach(struct brcmf_pub *drvr)
+static inline int brcmf_debug_attach(struct brcmf_pub *drvr)
 {
        return 0;
 }
-static inline void brcmf_debugfs_detach(struct brcmf_pub *drvr)
+static inline void brcmf_debug_detach(struct brcmf_pub *drvr)
 {
 }
 static inline
index 30baf352e23436ee43c14b7cd3f5cb9ab5026c52..36b386a000ca16effc467925093cf110b1676e1d 100644 (file)
@@ -448,6 +448,47 @@ brcmf_pcie_copy_mem_todev(struct brcmf_pciedev_info *devinfo, u32 mem_offset,
 }
 
 
+static void
+brcmf_pcie_copy_dev_tomem(struct brcmf_pciedev_info *devinfo, u32 mem_offset,
+                         void *dstaddr, u32 len)
+{
+       void __iomem *address = devinfo->tcm + mem_offset;
+       __le32 *dst32;
+       __le16 *dst16;
+       u8 *dst8;
+
+       if (((ulong)address & 4) || ((ulong)dstaddr & 4) || (len & 4)) {
+               if (((ulong)address & 2) || ((ulong)dstaddr & 2) || (len & 2)) {
+                       dst8 = (u8 *)dstaddr;
+                       while (len) {
+                               *dst8 = ioread8(address);
+                               address++;
+                               dst8++;
+                               len--;
+                       }
+               } else {
+                       len = len / 2;
+                       dst16 = (__le16 *)dstaddr;
+                       while (len) {
+                               *dst16 = cpu_to_le16(ioread16(address));
+                               address += 2;
+                               dst16++;
+                               len--;
+                       }
+               }
+       } else {
+               len = len / 4;
+               dst32 = (__le32 *)dstaddr;
+               while (len) {
+                       *dst32 = cpu_to_le32(ioread32(address));
+                       address += 4;
+                       dst32++;
+                       len--;
+               }
+       }
+}
+
+
 #define WRITECC32(devinfo, reg, value) brcmf_pcie_write_reg32(devinfo, \
                CHIPCREGOFFS(reg), value)
 
@@ -1352,12 +1393,36 @@ static void brcmf_pcie_wowl_config(struct device *dev, bool enabled)
 }
 
 
+static size_t brcmf_pcie_get_ramsize(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;
+
+       return devinfo->ci->ramsize - devinfo->ci->srsize;
+}
+
+
+static int brcmf_pcie_get_memdump(struct device *dev, void *data, size_t len)
+{
+       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;
+
+       brcmf_dbg(PCIE, "dump at 0x%08X: len=%zu\n", devinfo->ci->rambase, len);
+       brcmf_pcie_copy_dev_tomem(devinfo, devinfo->ci->rambase, data, len);
+       return 0;
+}
+
+
 static struct brcmf_bus_ops brcmf_pcie_bus_ops = {
        .txdata = brcmf_pcie_tx,
        .stop = brcmf_pcie_down,
        .txctl = brcmf_pcie_tx_ctlpkt,
        .rxctl = brcmf_pcie_rx_ctlpkt,
        .wowl_config = brcmf_pcie_wowl_config,
+       .get_ramsize = brcmf_pcie_get_ramsize,
+       .get_memdump = brcmf_pcie_get_memdump,
 };
 
 
index 7f574f26cdef657a3c243acb7f1f5e301da9cce3..7e74ac3ad81519491ac01460cd3b578e6fb3cc80 100644 (file)
@@ -3539,6 +3539,51 @@ done:
        return err;
 }
 
+static size_t brcmf_sdio_bus_get_ramsize(struct device *dev)
+{
+       struct brcmf_bus *bus_if = dev_get_drvdata(dev);
+       struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+       struct brcmf_sdio *bus = sdiodev->bus;
+
+       return bus->ci->ramsize - bus->ci->srsize;
+}
+
+static int brcmf_sdio_bus_get_memdump(struct device *dev, void *data,
+                                     size_t mem_size)
+{
+       struct brcmf_bus *bus_if = dev_get_drvdata(dev);
+       struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+       struct brcmf_sdio *bus = sdiodev->bus;
+       int err;
+       int address;
+       int offset;
+       int len;
+
+       brcmf_dbg(INFO, "dump at 0x%08x: size=%zu\n", bus->ci->rambase,
+                 mem_size);
+
+       address = bus->ci->rambase;
+       offset = err = 0;
+       sdio_claim_host(sdiodev->func[1]);
+       while (offset < mem_size) {
+               len = ((offset + MEMBLOCK) < mem_size) ? MEMBLOCK :
+                     mem_size - offset;
+               err = brcmf_sdiod_ramrw(sdiodev, false, address, data, len);
+               if (err) {
+                       brcmf_err("error %d on reading %d membytes at 0x%08x\n",
+                                 err, len, address);
+                       goto done;
+               }
+               data += len;
+               offset += len;
+               address += len;
+       }
+
+done:
+       sdio_release_host(sdiodev->func[1]);
+       return err;
+}
+
 void brcmf_sdio_trigger_dpc(struct brcmf_sdio *bus)
 {
        if (!bus->dpc_triggered) {
@@ -3987,7 +4032,9 @@ static struct brcmf_bus_ops brcmf_sdio_bus_ops = {
        .txctl = brcmf_sdio_bus_txctl,
        .rxctl = brcmf_sdio_bus_rxctl,
        .gettxq = brcmf_sdio_bus_gettxq,
-       .wowl_config = brcmf_sdio_wowl_config
+       .wowl_config = brcmf_sdio_wowl_config,
+       .get_ramsize = brcmf_sdio_bus_get_ramsize,
+       .get_memdump = brcmf_sdio_bus_get_memdump,
 };
 
 static void brcmf_sdio_firmware_callback(struct device *dev,