]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: ath12k: Add firmware coredump collection support
authorSowmiya Sree Elavalagan <quic_ssreeela@quicinc.com>
Wed, 17 Jul 2024 08:56:04 +0000 (14:26 +0530)
committerJeff Johnson <quic_jjohnson@quicinc.com>
Wed, 16 Oct 2024 14:43:07 +0000 (07:43 -0700)
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 disabled by default and can
be enabled using menuconfig. Dump collected for a radio is 55 MB
approximately.

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.2.1-00201-QCAHKSWPL_SILICONZ-1
Tested-on: WCN7850 WLAN.HMT.1.0-03427-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.15378.4

Signed-off-by: Sowmiya Sree Elavalagan <quic_ssreeela@quicinc.com>
Acked-by: Jeff Johnson <quic_jjohnson@quicinc.com>
Link: https://patch.msgid.link/20240717085604.4131642-1-quic_ssreeela@quicinc.com
Signed-off-by: Jeff Johnson <quic_jjohnson@quicinc.com>
drivers/net/wireless/ath/ath12k/Kconfig
drivers/net/wireless/ath/ath12k/Makefile
drivers/net/wireless/ath/ath12k/core.c
drivers/net/wireless/ath/ath12k/core.h
drivers/net/wireless/ath/ath12k/coredump.c [new file with mode: 0644]
drivers/net/wireless/ath/ath12k/coredump.h [new file with mode: 0644]
drivers/net/wireless/ath/ath12k/hif.h
drivers/net/wireless/ath/ath12k/hw.c
drivers/net/wireless/ath/ath12k/mhi.c
drivers/net/wireless/ath/ath12k/mhi.h
drivers/net/wireless/ath/ath12k/pci.c

index f64e7c32221612db91b38f154fbda1a6aa263fa9..52a1bb19e3dad1a18791735369b78745604d8e0a 100644 (file)
@@ -42,3 +42,13 @@ config ATH12K_TRACING
 
          If unsure, say Y to make it easier to debug problems. But if
          you want optimal performance choose N.
+
+config ATH12K_COREDUMP
+       bool "ath12k coredump"
+       depends on ATH12K
+       select WANT_DEV_COREDUMP
+       help
+         Enable ath12k coredump collection
+
+         If unsure, say Y to make it easier to debug problems. But if
+         dump collection not required choose N.
index 5a1ed20d730e85c3c92215551c8f136f46c15322..b5bb3e2599cd0eb4994fdd22b2a98a37c979fe01 100644 (file)
@@ -27,6 +27,7 @@ ath12k-$(CONFIG_ATH12K_DEBUGFS) += debugfs.o debugfs_htt_stats.o
 ath12k-$(CONFIG_ACPI) += acpi.o
 ath12k-$(CONFIG_ATH12K_TRACING) += trace.o
 ath12k-$(CONFIG_PM) += wow.o
+ath12k-$(CONFIG_ATH12K_COREDUMP) += coredump.o
 
 # for tracing framework to find trace.h
 CFLAGS_trace.o := -I$(src)
index 9cd485ed42ab7db743609cbeca9883e659714d93..c57322221e1d037a7bdb7986cb0589cbd505518e 100644 (file)
@@ -1187,6 +1187,7 @@ static void ath12k_core_reset(struct work_struct *work)
        ab->is_reset = true;
        atomic_set(&ab->recovery_count, 0);
 
+       ath12k_coredump_collect(ab);
        ath12k_core_pre_reconfigure_recovery(ab);
 
        ath12k_core_post_reconfigure_recovery(ab);
@@ -1311,6 +1312,7 @@ struct ath12k_base *ath12k_core_alloc(struct device *dev, size_t priv_size,
        INIT_WORK(&ab->restart_work, ath12k_core_restart);
        INIT_WORK(&ab->reset_work, ath12k_core_reset);
        INIT_WORK(&ab->rfkill_work, ath12k_rfkill_work);
+       INIT_WORK(&ab->dump_work, ath12k_coredump_upload);
 
        timer_setup(&ab->rx_replenish_retry, ath12k_ce_rx_replenish_retry, 0);
        init_completion(&ab->htc_suspend);
index ebfc1e370acc3bdbfadfdbdd052757869bba8360..e8a5d96378adf787f687eef9725606ffc3f8e817 100644 (file)
@@ -30,6 +30,7 @@
 #include "acpi.h"
 #include "wow.h"
 #include "debugfs_htt_stats.h"
+#include "coredump.h"
 
 #define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK)
 
@@ -779,6 +780,10 @@ struct ath12k_base {
        /* HW channel counters frequency value in hertz common to all MACs */
        u32 cc_freq_hz;
 
+       struct ath12k_dump_file_data *dump_data;
+       size_t ath12k_coredump_len;
+       struct work_struct dump_work;
+
        struct ath12k_htc htc;
 
        struct ath12k_dp dp;
diff --git a/drivers/net/wireless/ath/ath12k/coredump.c b/drivers/net/wireless/ath/ath12k/coredump.c
new file mode 100644 (file)
index 0000000..72d675d
--- /dev/null
@@ -0,0 +1,51 @@
+// 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
+ath12k_fw_crash_dump_type ath12k_coredump_get_dump_type(enum ath12k_qmi_target_mem type)
+{
+       enum ath12k_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;
+}
+
+void ath12k_coredump_upload(struct work_struct *work)
+{
+       struct ath12k_base *ab = container_of(work, struct ath12k_base, dump_work);
+
+       ath12k_info(ab, "Uploading coredump\n");
+       /* dev_coredumpv() takes ownership of the buffer */
+       dev_coredumpv(ab->dev, ab->dump_data, ab->ath12k_coredump_len, GFP_KERNEL);
+       ab->dump_data = NULL;
+}
+
+void ath12k_coredump_collect(struct ath12k_base *ab)
+{
+       ath12k_hif_coredump_download(ab);
+}
diff --git a/drivers/net/wireless/ath/ath12k/coredump.h b/drivers/net/wireless/ath/ath12k/coredump.h
new file mode 100644 (file)
index 0000000..5d6003b
--- /dev/null
@@ -0,0 +1,80 @@
+/* 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 _ATH12K_COREDUMP_H_
+#define _ATH12K_COREDUMP_H_
+
+#define ATH12K_FW_CRASH_DUMP_V2      2
+
+enum ath12k_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 ath12k_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 ath12k_dump_file_data {
+       /* "ATH12K-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_ATH12K_COREDUMP
+enum ath12k_fw_crash_dump_type ath12k_coredump_get_dump_type
+                                               (enum ath12k_qmi_target_mem type);
+void ath12k_coredump_upload(struct work_struct *work);
+void ath12k_coredump_collect(struct ath12k_base *ab);
+#else
+static inline enum ath12k_fw_crash_dump_type ath12k_coredump_get_dump_type
+                                                       (enum ath12k_qmi_target_mem type)
+{
+       return FW_CRASH_DUMP_TYPE_MAX;
+}
+
+static inline void ath12k_coredump_upload(struct work_struct *work)
+{
+}
+
+static inline void ath12k_coredump_collect(struct ath12k_base *ab)
+{
+}
+#endif
+
+#endif
index 0e53ec269fa4f2a9d1290b09a9bdf23f3ec294a9..e8840fab6061c7e5372bff8de438ec9f0c46f07b 100644 (file)
@@ -31,6 +31,7 @@ struct ath12k_hif_ops {
        void (*ce_irq_disable)(struct ath12k_base *ab);
        void (*get_ce_msi_idx)(struct ath12k_base *ab, u32 ce_id, u32 *msi_idx);
        int (*panic_handler)(struct ath12k_base *ab);
+       void (*coredump_download)(struct ath12k_base *ab);
 };
 
 static inline int ath12k_hif_map_service_to_pipe(struct ath12k_base *ab, u16 service_id,
@@ -156,4 +157,9 @@ static inline int ath12k_hif_panic_handler(struct ath12k_base *ab)
        return ab->hif.ops->panic_handler(ab);
 }
 
+static inline void ath12k_hif_coredump_download(struct ath12k_base *ab)
+{
+       if (ab->hif.ops->coredump_download)
+               ab->hif.ops->coredump_download(ab);
+}
 #endif /* ATH12K_HIF_H */
index ec1bda95e555ddea49febc312a1a0b935973b032..b7b583fadb5ac1fef33d16ec46b473fc742212ad 100644 (file)
@@ -913,7 +913,7 @@ static const struct ath12k_hw_params ath12k_hw_params[] = {
                .rfkill_cfg = 0,
                .rfkill_on_level = 0,
 
-               .rddm_size = 0,
+               .rddm_size = 0x600000,
 
                .def_num_link = 0,
                .max_mlo_peer = 256,
@@ -1069,7 +1069,7 @@ static const struct ath12k_hw_params ath12k_hw_params[] = {
                .rfkill_cfg = 0,
                .rfkill_on_level = 0,
 
-               .rddm_size = 0,
+               .rddm_size = 0x600000,
 
                .def_num_link = 0,
                .max_mlo_peer = 256,
index df96b0f91f54c1f05126663e1c23f8d31f1de769..2f6d14382ed70c7aeecd83c2329f23022412c7ec 100644 (file)
@@ -649,3 +649,8 @@ void ath12k_mhi_resume(struct ath12k_pci *ab_pci)
 {
        ath12k_mhi_set_state(ab_pci, ATH12K_MHI_RESUME);
 }
+
+void ath12k_mhi_coredump(struct mhi_controller *mhi_ctrl, bool in_panic)
+{
+       mhi_download_rddm_image(mhi_ctrl, in_panic);
+}
index 9362ad1958c3a78cd2d3241ea6628cc158f41cb1..7358b8477536a636743f4fd16d37500a8d458ab1 100644 (file)
@@ -43,5 +43,5 @@ void ath12k_mhi_clear_vector(struct ath12k_base *ab);
 
 void ath12k_mhi_suspend(struct ath12k_pci *ar_pci);
 void ath12k_mhi_resume(struct ath12k_pci *ar_pci);
-
+void ath12k_mhi_coredump(struct mhi_controller *mhi_ctrl, bool in_panic);
 #endif
index bd269aa1740bcd4e1d840ddc4a0f1de91fa8271a..fd47394553b8593f7c8047bffd3ce055e465185b 100644 (file)
@@ -7,6 +7,8 @@
 #include <linux/module.h>
 #include <linux/msi.h>
 #include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/vmalloc.h>
 
 #include "pci.h"
 #include "core.h"
@@ -1259,6 +1261,186 @@ void ath12k_pci_write32(struct ath12k_base *ab, u32 offset, u32 value)
                ab_pci->pci_ops->release(ab);
 }
 
+#ifdef CONFIG_ATH12K_COREDUMP
+static int ath12k_pci_coredump_calculate_size(struct ath12k_base *ab, u32 *dump_seg_sz)
+{
+       struct ath12k_pci *ab_pci = ath12k_pci_priv(ab);
+       struct mhi_controller *mhi_ctrl = ab_pci->mhi_ctrl;
+       struct image_info *rddm_img, *fw_img;
+       struct ath12k_tlv_dump_data *dump_tlv;
+       enum ath12k_fw_crash_dump_type mem_type;
+       u32 len = 0, rddm_tlv_sz = 0, paging_tlv_sz = 0;
+       struct ath12k_dump_file_data *file_data;
+       int i;
+
+       rddm_img = mhi_ctrl->rddm_image;
+       if (!rddm_img) {
+               ath12k_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 = ath12k_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) {
+                       ath12k_dbg(ab, ATH12K_DBG_PCI,
+                                  "target mem region type %d not supported",
+                                  ab->qmi.target_mem[i].type);
+                       continue;
+               }
+
+               if (!ab->qmi.target_mem[i].paddr)
+                       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 ath12k_pci_coredump_download(struct ath12k_base *ab)
+{
+       struct ath12k_pci *ab_pci = ath12k_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 ath12k_fw_crash_dump_type mem_type;
+       struct ath12k_dump_file_data *file_data;
+       struct ath12k_tlv_dump_data *dump_tlv;
+       size_t hdr_len = sizeof(*file_data);
+       void *buf;
+       u32 dump_seg_sz[FW_CRASH_DUMP_TYPE_MAX] = { 0 };
+
+       ath12k_mhi_coredump(mhi_ctrl, false);
+
+       len = ath12k_pci_coredump_calculate_size(ab, dump_seg_sz);
+       if (!len) {
+               ath12k_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->ath12k_coredump_len = len;
+       file_data = ab->dump_data;
+       strscpy(file_data->df_magic, "ATH12K-FW-DUMP", sizeof(file_data->df_magic));
+       file_data->len = cpu_to_le32(len);
+       file_data->version = cpu_to_le32(ATH12K_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 (!dump_seg_sz[mem_idx] || mem_idx == FW_CRASH_DUMP_NONE)
+                       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;
+
+               for (i = 0; i < ab->qmi.mem_seg_count; i++) {
+                       mem_type = ath12k_coredump_get_dump_type
+                                                       (ab->qmi.target_mem[i].type);
+
+                       if (mem_type != mem_idx)
+                               continue;
+
+                       if (!ab->qmi.target_mem[i].paddr) {
+                               ath12k_dbg(ab, ATH12K_DBG_PCI,
+                                          "Skipping mem region type %d",
+                                          ab->qmi.target_mem[i].type);
+                               continue;
+                       }
+
+                       memcpy_fromio(buf, ab->qmi.target_mem[i].v.ioaddr,
+                                     ab->qmi.target_mem[i].size);
+                       buf += ab->qmi.target_mem[i].size;
+               }
+       }
+
+       queue_work(ab->workqueue, &ab->dump_work);
+}
+#endif
+
 int ath12k_pci_power_up(struct ath12k_base *ab)
 {
        struct ath12k_pci *ab_pci = ath12k_pci_priv(ab);
@@ -1329,6 +1511,9 @@ static const struct ath12k_hif_ops ath12k_pci_hif_ops = {
        .ce_irq_disable = ath12k_pci_hif_ce_irq_disable,
        .get_ce_msi_idx = ath12k_pci_get_ce_msi_idx,
        .panic_handler = ath12k_pci_panic_handler,
+#ifdef CONFIG_ATH12K_COREDUMP
+       .coredump_download = ath12k_pci_coredump_download,
+#endif
 };
 
 static
@@ -1538,6 +1723,7 @@ static void ath12k_pci_remove(struct pci_dev *pdev)
        set_bit(ATH12K_FLAG_UNREGISTERING, &ab->dev_flags);
 
        cancel_work_sync(&ab->reset_work);
+       cancel_work_sync(&ab->dump_work);
        ath12k_core_deinit(ab);
 
 qmi_fail: