]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: ath11k: Add firmware coredump collection support
authorMiaoqing Pan <quic_miaoqing@quicinc.com>
Tue, 13 Aug 2024 01:30:28 +0000 (09:30 +0800)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Tue, 18 Feb 2025 17:53:34 +0000 (09:53 -0800)
In case of firmware assert snapshot of firmware memory is essential for
debugging. Add firmware coredump collection support for PCI bus.
Collect RDDM and firmware paging dumps from MHI and pack them in TLV
format and also pack various memory shared during QMI phase in separate
TLVs.  Add necessary header and share the dumps to user space using dev
coredump framework. Coredump collection is controlled by
CONFIG_DEV_COREDUMP. Dump collected for a radio is 55 MB approximately.

The changeset is mostly copied from:
https://lore.kernel.org/all/20240325183414.4016663-1-quic_ssreeela@quicinc.com/.

Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-04358-QCAHSPSWPL_V1_V2_SILICONZ_LITE-1

Signed-off-by: Miaoqing Pan <quic_miaoqing@quicinc.com>
Acked-by: Jeff Johnson <quic_jjohnson@quicinc.com>
Link: https://patch.msgid.link/20240813013028.2708111-2-quic_miaoqing@quicinc.com
Signed-off-by: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
drivers/net/wireless/ath/ath11k/Makefile
drivers/net/wireless/ath/ath11k/core.c
drivers/net/wireless/ath/ath11k/core.h
drivers/net/wireless/ath/ath11k/coredump.c [new file with mode: 0644]
drivers/net/wireless/ath/ath11k/coredump.h [new file with mode: 0644]
drivers/net/wireless/ath/ath11k/hif.h
drivers/net/wireless/ath/ath11k/mhi.c
drivers/net/wireless/ath/ath11k/mhi.h
drivers/net/wireless/ath/ath11k/pci.c
drivers/net/wireless/ath/ath11k/qmi.h

index 43d2d8ddcdc0526fce90eccadbf691dad278acbd..d9092414b362d056d539d8e49b15a878b7e28b85 100644 (file)
@@ -27,6 +27,7 @@ ath11k-$(CONFIG_ATH11K_TRACING) += trace.o
 ath11k-$(CONFIG_THERMAL) += thermal.o
 ath11k-$(CONFIG_ATH11K_SPECTRAL) += spectral.o
 ath11k-$(CONFIG_PM) += wow.o
+ath11k-$(CONFIG_DEV_COREDUMP) += coredump.o
 
 obj-$(CONFIG_ATH11K_AHB) += ath11k_ahb.o
 ath11k_ahb-y += ahb.o
index 12dd37c2e90440e3e82c0525de3681fe0bdf71f7..65bba0ae69a9a8e869e72b7e6d915577c8fb27d1 100644 (file)
@@ -2258,6 +2258,7 @@ static void ath11k_core_reset(struct work_struct *work)
        reinit_completion(&ab->recovery_start);
        atomic_set(&ab->recovery_start_count, 0);
 
+       ath11k_coredump_collect(ab);
        ath11k_core_pre_reconfigure_recovery(ab);
 
        reinit_completion(&ab->reconfigure_complete);
@@ -2393,6 +2394,7 @@ struct ath11k_base *ath11k_core_alloc(struct device *dev, size_t priv_size,
        INIT_WORK(&ab->restart_work, ath11k_core_restart);
        INIT_WORK(&ab->update_11d_work, ath11k_update_11d);
        INIT_WORK(&ab->reset_work, ath11k_core_reset);
+       INIT_WORK(&ab->dump_work, ath11k_coredump_upload);
        timer_setup(&ab->rx_replenish_retry, ath11k_ce_rx_replenish_retry, 0);
        init_completion(&ab->htc_suspend);
        init_completion(&ab->wow.wakeup_completed);
index 9842a4d65c7b9dfdc25cdd50aa18dd9856cea677..1a3d0de4afde833ec1ce4e3bcef3b7c8955a9c3d 100644 (file)
@@ -32,6 +32,7 @@
 #include "spectral.h"
 #include "wow.h"
 #include "fw.h"
+#include "coredump.h"
 
 #define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK)
 
@@ -904,6 +905,10 @@ struct ath11k_base {
        /* HW channel counters frequency value in hertz common to all MACs */
        u32 cc_freq_hz;
 
+       struct ath11k_dump_file_data *dump_data;
+       size_t ath11k_coredump_len;
+       struct work_struct dump_work;
+
        struct ath11k_htc htc;
 
        struct ath11k_dp dp;
diff --git a/drivers/net/wireless/ath/ath11k/coredump.c b/drivers/net/wireless/ath/ath11k/coredump.c
new file mode 100644 (file)
index 0000000..b8bad35
--- /dev/null
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#include <linux/devcoredump.h>
+#include "hif.h"
+#include "coredump.h"
+#include "debug.h"
+
+enum
+ath11k_fw_crash_dump_type ath11k_coredump_get_dump_type(int type)
+{
+       enum ath11k_fw_crash_dump_type dump_type;
+
+       switch (type) {
+       case HOST_DDR_REGION_TYPE:
+               dump_type = FW_CRASH_DUMP_REMOTE_MEM_DATA;
+               break;
+       case M3_DUMP_REGION_TYPE:
+               dump_type = FW_CRASH_DUMP_M3_DUMP;
+               break;
+       case PAGEABLE_MEM_REGION_TYPE:
+               dump_type = FW_CRASH_DUMP_PAGEABLE_DATA;
+               break;
+       case BDF_MEM_REGION_TYPE:
+       case CALDB_MEM_REGION_TYPE:
+               dump_type = FW_CRASH_DUMP_NONE;
+               break;
+       default:
+               dump_type = FW_CRASH_DUMP_TYPE_MAX;
+               break;
+       }
+
+       return dump_type;
+}
+EXPORT_SYMBOL(ath11k_coredump_get_dump_type);
+
+void ath11k_coredump_upload(struct work_struct *work)
+{
+       struct ath11k_base *ab = container_of(work, struct ath11k_base, dump_work);
+
+       ath11k_info(ab, "Uploading coredump\n");
+       /* dev_coredumpv() takes ownership of the buffer */
+       dev_coredumpv(ab->dev, ab->dump_data, ab->ath11k_coredump_len, GFP_KERNEL);
+       ab->dump_data = NULL;
+}
+
+void ath11k_coredump_collect(struct ath11k_base *ab)
+{
+       ath11k_hif_coredump_download(ab);
+}
diff --git a/drivers/net/wireless/ath/ath11k/coredump.h b/drivers/net/wireless/ath/ath11k/coredump.h
new file mode 100644 (file)
index 0000000..3960d93
--- /dev/null
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#ifndef _ATH11K_COREDUMP_H_
+#define _ATH11K_COREDUMP_H_
+
+#define ATH11K_FW_CRASH_DUMP_V2      2
+
+enum ath11k_fw_crash_dump_type {
+       FW_CRASH_DUMP_PAGING_DATA,
+       FW_CRASH_DUMP_RDDM_DATA,
+       FW_CRASH_DUMP_REMOTE_MEM_DATA,
+       FW_CRASH_DUMP_PAGEABLE_DATA,
+       FW_CRASH_DUMP_M3_DUMP,
+       FW_CRASH_DUMP_NONE,
+
+       /* keep last */
+       FW_CRASH_DUMP_TYPE_MAX,
+};
+
+#define COREDUMP_TLV_HDR_SIZE 8
+
+struct ath11k_tlv_dump_data {
+       /* see ath11k_fw_crash_dump_type above */
+       __le32 type;
+
+       /* in bytes */
+       __le32 tlv_len;
+
+       /* pad to 32-bit boundaries as needed */
+       u8 tlv_data[];
+} __packed;
+
+struct ath11k_dump_file_data {
+       /* "ATH11K-FW-DUMP" */
+       char df_magic[16];
+       /* total dump len in bytes */
+       __le32 len;
+       /* file dump version */
+       __le32 version;
+       /* pci device id */
+       __le32 chip_id;
+       /* qrtr instance id */
+       __le32 qrtr_id;
+       /* pci domain id */
+       __le32 bus_id;
+       guid_t guid;
+       /* time-of-day stamp */
+       __le64 tv_sec;
+       /* time-of-day stamp, nano-seconds */
+       __le64 tv_nsec;
+       /* room for growth w/out changing binary format */
+       u8 unused[128];
+       u8 data[];
+} __packed;
+
+#ifdef CONFIG_DEV_COREDUMP
+enum ath11k_fw_crash_dump_type ath11k_coredump_get_dump_type(int type);
+void ath11k_coredump_upload(struct work_struct *work);
+void ath11k_coredump_collect(struct ath11k_base *ab);
+#else
+static inline enum
+ath11k_fw_crash_dump_type ath11k_coredump_get_dump_type(int type)
+{
+       return FW_CRASH_DUMP_TYPE_MAX;
+}
+
+static inline void ath11k_coredump_upload(struct work_struct *work)
+{
+}
+
+static inline void ath11k_coredump_collect(struct ath11k_base *ab)
+{
+}
+#endif
+
+#endif
index 674ff772b181b99395da13b49ad322c1ea5ec339..770c39ff99b45e93de65c88c13bdf30e75fa8e64 100644 (file)
@@ -31,6 +31,7 @@ struct ath11k_hif_ops {
        void (*ce_irq_enable)(struct ath11k_base *ab);
        void (*ce_irq_disable)(struct ath11k_base *ab);
        void (*get_ce_msi_idx)(struct ath11k_base *ab, u32 ce_id, u32 *msi_idx);
+       void (*coredump_download)(struct ath11k_base *ab);
 };
 
 static inline void ath11k_hif_ce_irq_enable(struct ath11k_base *ab)
@@ -146,4 +147,10 @@ static inline void ath11k_get_ce_msi_idx(struct ath11k_base *ab, u32 ce_id,
                *msi_data_idx = ce_id;
 }
 
+static inline void ath11k_hif_coredump_download(struct ath11k_base *ab)
+{
+       if (ab->hif.ops->coredump_download)
+               ab->hif.ops->coredump_download(ab);
+}
+
 #endif /* _HIF_H_ */
index 6e45f464a429beb1ee5b40cab173d76406f7a55e..fc77eac83e953148b96cad096d26b32222157b24 100644 (file)
@@ -491,3 +491,8 @@ int ath11k_mhi_resume(struct ath11k_pci *ab_pci)
 
        return 0;
 }
+
+void ath11k_mhi_coredump(struct mhi_controller *mhi_ctrl, bool in_panic)
+{
+       mhi_download_rddm_image(mhi_ctrl, in_panic);
+}
index a682aad52fc515f15cabca4c396e85afebee1027..651470091bd5ab60b3fbfb37c1be14b048e877c5 100644 (file)
@@ -26,5 +26,6 @@ void ath11k_mhi_clear_vector(struct ath11k_base *ab);
 
 int ath11k_mhi_suspend(struct ath11k_pci *ar_pci);
 int ath11k_mhi_resume(struct ath11k_pci *ar_pci);
+void ath11k_mhi_coredump(struct mhi_controller *mhi_ctrl, bool in_panic);
 
 #endif
index f1121e33171944f40ec2c26ae6b31639a0d598c4..6565ac1917b795b3223cac45ccfcffb285429107 100644 (file)
@@ -8,6 +8,8 @@
 #include <linux/msi.h>
 #include <linux/pci.h>
 #include <linux/of.h>
+#include <linux/time.h>
+#include <linux/vmalloc.h>
 
 #include "pci.h"
 #include "core.h"
@@ -610,6 +612,187 @@ static void ath11k_pci_aspm_restore(struct ath11k_pci *ab_pci)
                                                   PCI_EXP_LNKCTL_ASPMC);
 }
 
+#ifdef CONFIG_DEV_COREDUMP
+static int ath11k_pci_coredump_calculate_size(struct ath11k_base *ab, u32 *dump_seg_sz)
+{
+       struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);
+       struct mhi_controller *mhi_ctrl = ab_pci->mhi_ctrl;
+       struct image_info *rddm_img, *fw_img;
+       struct ath11k_tlv_dump_data *dump_tlv;
+       enum ath11k_fw_crash_dump_type mem_type;
+       u32 len = 0, rddm_tlv_sz = 0, paging_tlv_sz = 0;
+       struct ath11k_dump_file_data *file_data;
+       int i;
+
+       rddm_img = mhi_ctrl->rddm_image;
+       if (!rddm_img) {
+               ath11k_err(ab, "No RDDM dump found\n");
+               return 0;
+       }
+
+       fw_img = mhi_ctrl->fbc_image;
+
+       for (i = 0; i < fw_img->entries ; i++) {
+               if (!fw_img->mhi_buf[i].buf)
+                       continue;
+
+               paging_tlv_sz += fw_img->mhi_buf[i].len;
+       }
+       dump_seg_sz[FW_CRASH_DUMP_PAGING_DATA] = paging_tlv_sz;
+
+       for (i = 0; i < rddm_img->entries; i++) {
+               if (!rddm_img->mhi_buf[i].buf)
+                       continue;
+
+               rddm_tlv_sz += rddm_img->mhi_buf[i].len;
+       }
+       dump_seg_sz[FW_CRASH_DUMP_RDDM_DATA] = rddm_tlv_sz;
+
+       for (i = 0; i < ab->qmi.mem_seg_count; i++) {
+               mem_type = ath11k_coredump_get_dump_type(ab->qmi.target_mem[i].type);
+
+               if (mem_type == FW_CRASH_DUMP_NONE)
+                       continue;
+
+               if (mem_type == FW_CRASH_DUMP_TYPE_MAX) {
+                       ath11k_dbg(ab, ATH11K_DBG_PCI,
+                                  "target mem region type %d not supported",
+                                  ab->qmi.target_mem[i].type);
+                       continue;
+               }
+
+               if (!ab->qmi.target_mem[i].anyaddr)
+                       continue;
+
+               dump_seg_sz[mem_type] += ab->qmi.target_mem[i].size;
+       }
+
+       for (i = 0; i < FW_CRASH_DUMP_TYPE_MAX; i++) {
+               if (!dump_seg_sz[i])
+                       continue;
+
+               len += sizeof(*dump_tlv) + dump_seg_sz[i];
+       }
+
+       if (len)
+               len += sizeof(*file_data);
+
+       return len;
+}
+
+static void ath11k_pci_coredump_download(struct ath11k_base *ab)
+{
+       struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);
+       struct mhi_controller *mhi_ctrl = ab_pci->mhi_ctrl;
+       struct image_info *rddm_img, *fw_img;
+       struct timespec64 timestamp;
+       int i, len, mem_idx;
+       enum ath11k_fw_crash_dump_type mem_type;
+       struct ath11k_dump_file_data *file_data;
+       struct ath11k_tlv_dump_data *dump_tlv;
+       size_t hdr_len = sizeof(*file_data);
+       void *buf;
+       u32 dump_seg_sz[FW_CRASH_DUMP_TYPE_MAX] = { 0 };
+
+       ath11k_mhi_coredump(mhi_ctrl, false);
+
+       len = ath11k_pci_coredump_calculate_size(ab, dump_seg_sz);
+       if (!len) {
+               ath11k_warn(ab, "No crash dump data found for devcoredump");
+               return;
+       }
+
+       rddm_img = mhi_ctrl->rddm_image;
+       fw_img = mhi_ctrl->fbc_image;
+
+       /* dev_coredumpv() requires vmalloc data */
+       buf = vzalloc(len);
+       if (!buf)
+               return;
+
+       ab->dump_data = buf;
+       ab->ath11k_coredump_len = len;
+       file_data = ab->dump_data;
+       strscpy(file_data->df_magic, "ATH11K-FW-DUMP", sizeof(file_data->df_magic));
+       file_data->len = cpu_to_le32(len);
+       file_data->version = cpu_to_le32(ATH11K_FW_CRASH_DUMP_V2);
+       file_data->chip_id = cpu_to_le32(ab_pci->dev_id);
+       file_data->qrtr_id = cpu_to_le32(ab_pci->ab->qmi.service_ins_id);
+       file_data->bus_id = cpu_to_le32(pci_domain_nr(ab_pci->pdev->bus));
+       guid_gen(&file_data->guid);
+       ktime_get_real_ts64(&timestamp);
+       file_data->tv_sec = cpu_to_le64(timestamp.tv_sec);
+       file_data->tv_nsec = cpu_to_le64(timestamp.tv_nsec);
+       buf += hdr_len;
+       dump_tlv = buf;
+       dump_tlv->type = cpu_to_le32(FW_CRASH_DUMP_PAGING_DATA);
+       dump_tlv->tlv_len = cpu_to_le32(dump_seg_sz[FW_CRASH_DUMP_PAGING_DATA]);
+       buf += COREDUMP_TLV_HDR_SIZE;
+
+       /* append all segments together as they are all part of a single contiguous
+        * block of memory
+        */
+       for (i = 0; i < fw_img->entries ; i++) {
+               if (!fw_img->mhi_buf[i].buf)
+                       continue;
+
+               memcpy_fromio(buf, (void const __iomem *)fw_img->mhi_buf[i].buf,
+                             fw_img->mhi_buf[i].len);
+               buf += fw_img->mhi_buf[i].len;
+       }
+
+       dump_tlv = buf;
+       dump_tlv->type = cpu_to_le32(FW_CRASH_DUMP_RDDM_DATA);
+       dump_tlv->tlv_len = cpu_to_le32(dump_seg_sz[FW_CRASH_DUMP_RDDM_DATA]);
+       buf += COREDUMP_TLV_HDR_SIZE;
+
+       /* append all segments together as they are all part of a single contiguous
+        * block of memory
+        */
+       for (i = 0; i < rddm_img->entries; i++) {
+               if (!rddm_img->mhi_buf[i].buf)
+                       continue;
+
+               memcpy_fromio(buf, (void const __iomem *)rddm_img->mhi_buf[i].buf,
+                             rddm_img->mhi_buf[i].len);
+               buf += rddm_img->mhi_buf[i].len;
+       }
+
+       mem_idx = FW_CRASH_DUMP_REMOTE_MEM_DATA;
+       for (; mem_idx < FW_CRASH_DUMP_TYPE_MAX; mem_idx++) {
+               if (mem_idx == FW_CRASH_DUMP_NONE)
+                       continue;
+
+               for (i = 0; i < ab->qmi.mem_seg_count; i++) {
+                       mem_type = ath11k_coredump_get_dump_type
+                                               (ab->qmi.target_mem[i].type);
+
+                       if (mem_type != mem_idx)
+                               continue;
+
+                       if (!ab->qmi.target_mem[i].anyaddr) {
+                               ath11k_dbg(ab, ATH11K_DBG_PCI,
+                                          "Skipping mem region type %d",
+                                          ab->qmi.target_mem[i].type);
+                               continue;
+                       }
+
+                       dump_tlv = buf;
+                       dump_tlv->type = cpu_to_le32(mem_idx);
+                       dump_tlv->tlv_len = cpu_to_le32(dump_seg_sz[mem_idx]);
+                       buf += COREDUMP_TLV_HDR_SIZE;
+
+                       memcpy_fromio(buf, ab->qmi.target_mem[i].iaddr,
+                                     ab->qmi.target_mem[i].size);
+
+                       buf += ab->qmi.target_mem[i].size;
+               }
+       }
+
+       queue_work(ab->workqueue, &ab->dump_work);
+}
+#endif
+
 static int ath11k_pci_power_up(struct ath11k_base *ab)
 {
        struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);
@@ -713,6 +896,9 @@ static const struct ath11k_hif_ops ath11k_pci_hif_ops = {
        .ce_irq_enable = ath11k_pci_hif_ce_irq_enable,
        .ce_irq_disable = ath11k_pci_hif_ce_irq_disable,
        .get_ce_msi_idx = ath11k_pcic_get_ce_msi_idx,
+#ifdef CONFIG_DEV_COREDUMP
+       .coredump_download = ath11k_pci_coredump_download,
+#endif
 };
 
 static void ath11k_pci_read_hw_version(struct ath11k_base *ab, u32 *major, u32 *minor)
@@ -981,6 +1167,8 @@ static void ath11k_pci_remove(struct pci_dev *pdev)
 
        set_bit(ATH11K_FLAG_UNREGISTERING, &ab->dev_flags);
 
+       cancel_work_sync(&ab->reset_work);
+       cancel_work_sync(&ab->dump_work);
        ath11k_core_deinit(ab);
 
 qmi_fail:
index fdf9b5f8c19f17db9c4ea06e8f308abfc096ad7e..7968ab122b65dc28a44eca7b34904f55f717f586 100644 (file)
@@ -157,6 +157,7 @@ struct ath11k_qmi {
 #define BDF_MEM_REGION_TYPE                            0x2
 #define M3_DUMP_REGION_TYPE                            0x3
 #define CALDB_MEM_REGION_TYPE                          0x4
+#define PAGEABLE_MEM_REGION_TYPE                       0x9
 
 struct qmi_wlanfw_host_cap_req_msg_v01 {
        u8 num_clients_valid;