mirror of
https://github.com/Telecominfraproject/wlan-ap.git
synced 2025-12-20 19:03:39 +00:00
433 lines
12 KiB
Diff
433 lines
12 KiB
Diff
From 30f9030503fa82b63f49909f98ac0d9f91b98096 Mon Sep 17 00:00:00 2001
|
|
From: Aishwarya R <quic_aisr@quicinc.com>
|
|
Date: Mon, 14 Mar 2022 23:54:14 +0530
|
|
Subject: [PATCH] ath12K: Enable RAM dump support
|
|
|
|
This commit brings in inital coredump support
|
|
In order to get full dump, set_fw_recovery in
|
|
debugfs flag should be set to 1.
|
|
|
|
Based on set_fw_recovery the functionality decides
|
|
whether firmware / full core needs to be dumped.
|
|
|
|
Signed-off-by: Aishwarya R <quic_aisr@quicinc.com>
|
|
Signed-off-by: Balamurugan Selvarajan <quic_bselvara@quicinc.com>
|
|
---
|
|
drivers/net/wireless/ath/ath12k/Makefile | 1 +
|
|
drivers/net/wireless/ath/ath12k/core.c | 4 +
|
|
drivers/net/wireless/ath/ath12k/core.h | 13 +-
|
|
drivers/net/wireless/ath/ath12k/coredump.c | 222 +++++++++++++++++++++
|
|
drivers/net/wireless/ath/ath12k/coredump.h | 87 ++++++++
|
|
drivers/net/wireless/ath/ath12k/mhi.c | 6 +
|
|
drivers/net/wireless/ath/ath12k/mhi.h | 2 +
|
|
7 files changed, 328 insertions(+), 7 deletions(-)
|
|
create mode 100644 drivers/net/wireless/ath/ath12k/coredump.c
|
|
create mode 100644 drivers/net/wireless/ath/ath12k/coredump.h
|
|
|
|
--- a/drivers/net/wireless/ath/ath12k/Makefile
|
|
+++ b/drivers/net/wireless/ath/ath12k/Makefile
|
|
@@ -27,6 +27,7 @@ ath12k-$(CPTCFG_NL80211_TESTMODE) += tes
|
|
ath12k-$(CPTCFG_ATH12K_TRACING) += trace.o
|
|
ath12k-$(CONFIG_THERMAL) += thermal.o
|
|
ath12k-$(CPTCFG_ATH12K_SPECTRAL) += spectral.o
|
|
+ath12k-$(CPTCFG_WANT_DEV_COREDUMP) += coredump.o
|
|
|
|
# for tracing framework to find trace.h
|
|
CFLAGS_trace.o := -I$(src)
|
|
--- a/drivers/net/wireless/ath/ath12k/core.c
|
|
+++ b/drivers/net/wireless/ath/ath12k/core.c
|
|
@@ -10,6 +10,7 @@
|
|
#include <linux/firmware.h>
|
|
#include <linux/of.h>
|
|
#include "core.h"
|
|
+#include "coredump.h"
|
|
#include "dp_tx.h"
|
|
#include "dp_rx.h"
|
|
#include "debug.h"
|
|
@@ -925,6 +926,9 @@ static void ath12k_core_reset(struct wor
|
|
ath12k_dbg(ab, ATH12K_DBG_BOOT, "reset starting\n");
|
|
|
|
ab->is_reset = true;
|
|
+ /* prepare coredump */
|
|
+ ath12k_coredump_download_rddm(ab);
|
|
+
|
|
atomic_set(&ab->recovery_count, 0);
|
|
|
|
ath12k_core_pre_reconfigure_recovery(ab);
|
|
--- a/drivers/net/wireless/ath/ath12k/core.h
|
|
+++ b/drivers/net/wireless/ath/ath12k/core.h
|
|
@@ -503,6 +503,14 @@ struct ath12k_per_peer_tx_stats {
|
|
bool is_ampdu;
|
|
};
|
|
|
|
+enum ath12k_fw_recovery_option {
|
|
+ ATH12K_FW_RECOVERY_DISABLE = 0,
|
|
+ ATH12K_FW_RECOVERY_ENABLE_AUTO, /* Automatically recover after FW assert */
|
|
+ /* Enable only recovery. Send MPD SSR WMI */
|
|
+ /* command to unlink UserPD assert from RootPD */
|
|
+ ATH12K_FW_RECOVERY_ENABLE_SSR_ONLY,
|
|
+};
|
|
+
|
|
#define ATH12K_FLUSH_TIMEOUT (5 * HZ)
|
|
#define ATH12K_VDEV_DELETE_TIMEOUT_HZ (5 * HZ)
|
|
|
|
@@ -833,6 +841,7 @@ struct ath12k_base {
|
|
struct timer_list mon_reap_timer;
|
|
|
|
struct completion htc_suspend;
|
|
+ bool fw_recovery_support;
|
|
|
|
u64 fw_soc_drop_count;
|
|
|
|
--- /dev/null
|
|
+++ b/drivers/net/wireless/ath/ath12k/coredump.c
|
|
@@ -0,0 +1,222 @@
|
|
+// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
+/*
|
|
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
|
|
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
+ */
|
|
+#include <linux/devcoredump.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/dma-direction.h>
|
|
+#include <linux/pci.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/uuid.h>
|
|
+#include <linux/time.h>
|
|
+#include "core.h"
|
|
+#include "coredump.h"
|
|
+#include "pci.h"
|
|
+#include "mhi.h"
|
|
+#include "debug.h"
|
|
+
|
|
+struct ath12k_coredump_segment_info ath12k_coredump_seg_info;
|
|
+EXPORT_SYMBOL(ath12k_coredump_seg_info);
|
|
+
|
|
+static void *ath12k_coredump_find_segment(loff_t user_offset,
|
|
+ struct ath12k_dump_segment *segment,
|
|
+ int num_seg, size_t *data_left)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < num_seg; i++, segment++) {
|
|
+ if (user_offset < segment->len) {
|
|
+ *data_left = user_offset;
|
|
+ return segment;
|
|
+ }
|
|
+ user_offset -= segment->len;
|
|
+ }
|
|
+
|
|
+ *data_left = 0;
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static ssize_t ath12k_coredump_read_q6dump(char *buffer, loff_t offset, size_t count,
|
|
+ void *data, size_t header_size)
|
|
+{
|
|
+ struct ath12k_coredump_state *dump_state = data;
|
|
+ struct ath12k_dump_segment *segments = dump_state->segments;
|
|
+ struct ath12k_dump_segment *seg;
|
|
+ void *elfcore = dump_state->header;
|
|
+ size_t data_left, copy_size, bytes_left = count;
|
|
+ void __iomem *addr;
|
|
+
|
|
+ /* Copy the header first */
|
|
+ if (offset < header_size) {
|
|
+ copy_size = header_size - offset;
|
|
+ copy_size = min(copy_size, bytes_left);
|
|
+
|
|
+ memcpy(buffer, elfcore + offset, copy_size);
|
|
+ offset += copy_size;
|
|
+ bytes_left -= copy_size;
|
|
+ buffer += copy_size;
|
|
+
|
|
+ return copy_size;
|
|
+ }
|
|
+
|
|
+ while (bytes_left) {
|
|
+ seg = ath12k_coredump_find_segment(offset - header_size, segments,
|
|
+ dump_state->num_seg, &data_left);
|
|
+ /* End of segments check */
|
|
+ if (!seg) {
|
|
+ pr_info("Ramdump complete %lld bytes read\n", offset);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ if (data_left)
|
|
+ copy_size = min_t(size_t, bytes_left, data_left);
|
|
+ else
|
|
+ copy_size = bytes_left;
|
|
+
|
|
+ addr = (void __iomem *)seg->vaddr;
|
|
+ addr += data_left;
|
|
+ memcpy_fromio(buffer, addr, copy_size);
|
|
+
|
|
+ offset += copy_size;
|
|
+ buffer += copy_size;
|
|
+ bytes_left -= copy_size;
|
|
+ }
|
|
+
|
|
+ return count - bytes_left;
|
|
+}
|
|
+
|
|
+static void ath12k_coredump_free_q6dump(void *data)
|
|
+{
|
|
+ struct ath12k_coredump_state *dump_state = data;
|
|
+
|
|
+ complete(&dump_state->dump_done);
|
|
+}
|
|
+
|
|
+void ath12k_coredump_build_inline(struct ath12k_base *ab,
|
|
+ struct ath12k_dump_segment *segments, int num_seg)
|
|
+{
|
|
+ struct ath12k_coredump_state dump_state;
|
|
+ struct timespec64 timestamp;
|
|
+ struct ath12k_dump_file_data *file_data;
|
|
+ size_t header_size;
|
|
+ struct ath12k_pci *ar_pci = (struct ath12k_pci *)ab->drv_priv;
|
|
+ struct device dev;
|
|
+ u8 *buf;
|
|
+
|
|
+ header_size = sizeof(*file_data);
|
|
+ header_size += num_seg * sizeof(*segments);
|
|
+ header_size = PAGE_ALIGN(header_size);
|
|
+ buf = kzalloc(header_size, GFP_KERNEL);
|
|
+ if (!buf)
|
|
+ return;
|
|
+
|
|
+ file_data = (struct ath12k_dump_file_data *)buf;
|
|
+ strscpy(file_data->df_magic, "ATH12K-FW-DUMP",
|
|
+ sizeof(file_data->df_magic));
|
|
+ file_data->len = cpu_to_le32(header_size);
|
|
+ file_data->version = cpu_to_le32(ATH12K_FW_CRASH_DUMP_VERSION);
|
|
+ file_data->chip_id = cpu_to_le32(ar_pci->dev_id);
|
|
+ file_data->qrtr_id = cpu_to_le32(ar_pci->ab->qmi.service_ins_id);
|
|
+ file_data->bus_id = pci_domain_nr(ar_pci->pdev->bus);
|
|
+ dev = ar_pci->pdev->dev;
|
|
+
|
|
+ guid_gen(&file_data->guid);
|
|
+ ktime_get_real_ts64(×tamp);
|
|
+ file_data->tv_sec = cpu_to_le64(timestamp.tv_sec);
|
|
+ file_data->tv_nsec = cpu_to_le64(timestamp.tv_nsec);
|
|
+ file_data->num_seg = cpu_to_le32(num_seg);
|
|
+ file_data->seg_size = cpu_to_le32(sizeof(*segments));
|
|
+
|
|
+ /* copy segment details to file */
|
|
+ buf += offsetof(struct ath12k_dump_file_data, seg);
|
|
+ file_data->seg = (struct ath12k_dump_segment *)buf;
|
|
+ memcpy(file_data->seg, segments, num_seg * sizeof(*segments));
|
|
+
|
|
+ dump_state.header = file_data;
|
|
+ dump_state.num_seg = num_seg;
|
|
+ dump_state.segments = segments;
|
|
+ init_completion(&dump_state.dump_done);
|
|
+
|
|
+ dev_coredumpm(&dev, NULL, &dump_state, header_size, GFP_KERNEL,
|
|
+ ath12k_coredump_read_q6dump, ath12k_coredump_free_q6dump);
|
|
+
|
|
+ /* Wait until the dump is read and free is called */
|
|
+ wait_for_completion(&dump_state.dump_done);
|
|
+ kfree(file_data);
|
|
+}
|
|
+
|
|
+void ath12k_coredump_download_rddm(struct ath12k_base *ab)
|
|
+{
|
|
+ struct ath12k_pci *ar_pci = (struct ath12k_pci *)ab->drv_priv;
|
|
+ struct mhi_controller *mhi_ctrl = ar_pci->mhi_ctrl;
|
|
+ struct image_info *rddm_img, *fw_img;
|
|
+ struct ath12k_dump_segment *segment, *seg_info;
|
|
+ int i, rem_seg_cnt = 0, len, num_seg, seg_sz;
|
|
+
|
|
+ ath12k_mhi_coredump(mhi_ctrl, false);
|
|
+
|
|
+ rddm_img = mhi_ctrl->rddm_image;
|
|
+ fw_img = mhi_ctrl->fbc_image;
|
|
+
|
|
+ for (i = 0; i < ab->qmi.mem_seg_count; i++) {
|
|
+ if (ab->qmi.target_mem[i].type == HOST_DDR_REGION_TYPE ||
|
|
+ ab->qmi.target_mem[i].type == CALDB_MEM_REGION_TYPE ||
|
|
+ ab->qmi.target_mem[i].type == M3_DUMP_REGION_TYPE)
|
|
+ rem_seg_cnt++;
|
|
+ }
|
|
+
|
|
+ num_seg = fw_img->entries + rddm_img->entries + rem_seg_cnt;
|
|
+ len = num_seg * sizeof(*segment);
|
|
+
|
|
+ segment = kzalloc(len, GFP_KERNEL);
|
|
+ if (!segment)
|
|
+ return;
|
|
+
|
|
+ seg_info = segment;
|
|
+ for (i = 0; i < fw_img->entries ; i++) {
|
|
+ seg_sz = fw_img->mhi_buf[i].len;
|
|
+ seg_info->len = PAGE_ALIGN(seg_sz);
|
|
+ seg_info->addr = fw_img->mhi_buf[i].dma_addr;
|
|
+ seg_info->vaddr = fw_img->mhi_buf[i].buf;
|
|
+ seg_info->type = ATH12K_FW_CRASH_PAGING_DATA;
|
|
+ seg_info++;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < rddm_img->entries; i++) {
|
|
+ seg_sz = rddm_img->mhi_buf[i].len;
|
|
+ seg_info->len = PAGE_ALIGN(seg_sz);
|
|
+ seg_info->addr = rddm_img->mhi_buf[i].dma_addr;
|
|
+ seg_info->vaddr = rddm_img->mhi_buf[i].buf;
|
|
+ seg_info->type = ATH12K_FW_CRASH_RDDM_DATA;
|
|
+ seg_info++;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < ab->qmi.mem_seg_count; i++) {
|
|
+ if (ab->qmi.target_mem[i].type == CALDB_MEM_REGION_TYPE) {
|
|
+ if (ath12k_cold_boot_cal ||
|
|
+ ab->hw_params->cold_boot_calib) {
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ seg_info->len = ab->qmi.target_mem[i].size;
|
|
+ seg_info->addr = ab->qmi.target_mem[i].paddr;
|
|
+ seg_info->vaddr = ab->qmi.target_mem[i].v.addr;
|
|
+ seg_info->type = ATH12K_FW_REMOTE_MEM_DATA;
|
|
+ seg_info++;
|
|
+ }
|
|
+
|
|
+ if(!ab->fw_recovery_support) {
|
|
+ ath12k_coredump_seg_info.chip_id = ar_pci->dev_id;
|
|
+ ath12k_coredump_seg_info.qrtr_id = ar_pci->ab->qmi.service_ins_id;
|
|
+ ath12k_coredump_seg_info.bus_id = pci_domain_nr(ar_pci->pdev->bus);
|
|
+ ath12k_coredump_seg_info.num_seg = num_seg;
|
|
+ ath12k_coredump_seg_info.seg = segment;
|
|
+
|
|
+ BUG_ON(1);
|
|
+ } else {
|
|
+ ath12k_coredump_build_inline(ab, segment, num_seg);
|
|
+ }
|
|
+
|
|
+ kfree(segment);
|
|
+}
|
|
--- /dev/null
|
|
+++ b/drivers/net/wireless/ath/ath12k/coredump.h
|
|
@@ -0,0 +1,87 @@
|
|
+// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
+/*
|
|
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
|
|
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
+ */
|
|
+#ifndef _COREDUMP_H_
|
|
+#define _COREDUMP_H_
|
|
+
|
|
+#define ATH12K_FW_CRASH_DUMP_VERSION 1
|
|
+
|
|
+enum ath12k_fw_crash_dump_type {
|
|
+ ATH12K_FW_CRASH_PAGING_DATA,
|
|
+ ATH12K_FW_CRASH_RDDM_DATA,
|
|
+ ATH12K_FW_REMOTE_MEM_DATA,
|
|
+ ATH12K_FW_QDSS_DATA,
|
|
+};
|
|
+
|
|
+struct ath12k_dump_segment {
|
|
+ unsigned long addr;
|
|
+ void *vaddr;
|
|
+ unsigned int len;
|
|
+ unsigned int type;
|
|
+};
|
|
+
|
|
+struct ath12k_dump_file_data {
|
|
+ /* "ATH12K-FW-DUMP" */
|
|
+ char df_magic[16];
|
|
+ __le32 len;
|
|
+ /* file dump version */
|
|
+ __le32 version;
|
|
+ /* pci device id */
|
|
+ __le32 chip_id;
|
|
+ /* qrtr instance id */
|
|
+ __le32 qrtr_id;
|
|
+ /* pci domain id */
|
|
+ u8 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[8];
|
|
+ /* number of segments */
|
|
+ __le32 num_seg;
|
|
+ /* ath12k_dump_segment struct size */
|
|
+ __le32 seg_size;
|
|
+
|
|
+ struct ath12k_dump_segment *seg;
|
|
+ /* struct ath12k_dump_segment + more */
|
|
+
|
|
+ u8 data[0];
|
|
+} __packed;
|
|
+
|
|
+struct ath12k_coredump_state {
|
|
+ struct ath12k_dump_file_data *header;
|
|
+ struct ath12k_dump_segment *segments;
|
|
+ struct completion dump_done;
|
|
+ u32 num_seg;
|
|
+};
|
|
+
|
|
+struct ath12k_coredump_segment_info {
|
|
+ u32 chip_id;
|
|
+ u32 qrtr_id;
|
|
+ u32 num_seg;
|
|
+ struct ath12k_dump_segment *seg;
|
|
+ u8 bus_id;
|
|
+};
|
|
+
|
|
+#ifdef CONFIG_WANT_DEV_COREDUMP
|
|
+void ath12k_coredump_download_rddm(struct ath12k_base *ab);
|
|
+void ath12k_coredump_build_inline(struct ath12k_base *ab,
|
|
+ struct ath12k_dump_segment *segments, int num_seg);
|
|
+#else
|
|
+static inline void ath12k_coredump_download_rddm(struct ath12k_base *ab)
|
|
+{
|
|
+}
|
|
+
|
|
+static inline void ath12k_coredump_build_inline(struct ath12k_base *ab,
|
|
+ struct ath12k_dump_segment *segments,
|
|
+ int num_seg)
|
|
+{
|
|
+}
|
|
+#endif
|
|
+
|
|
+#endif
|
|
+
|
|
--- a/drivers/net/wireless/ath/ath12k/mhi.c
|
|
+++ b/drivers/net/wireless/ath/ath12k/mhi.c
|
|
@@ -392,6 +392,7 @@ int ath12k_mhi_register(struct ath12k_pc
|
|
#if LINUX_VERSION_IS_GEQ(5,10,0)
|
|
mhi_ctrl->reg_len = ab->mem_len;
|
|
#endif
|
|
+ mhi_ctrl->rddm_size = ATH12K_PCI_FW_RDDM_SZ;
|
|
|
|
ret = ath12k_mhi_get_msi(ab_pci);
|
|
if (ret) {
|
|
@@ -648,3 +649,8 @@ void ath12k_mhi_resume(struct ath12k_pci
|
|
{
|
|
ath12k_mhi_set_state(ab_pci, ATH12K_MHI_RESUME);
|
|
}
|
|
+
|
|
+void ath12k_mhi_coredump(struct mhi_controller *mhi_ctrl, bool state)
|
|
+{
|
|
+ mhi_download_rddm_image(mhi_ctrl, false);
|
|
+}
|
|
--- a/drivers/net/wireless/ath/ath12k/mhi.h
|
|
+++ b/drivers/net/wireless/ath/ath12k/mhi.h
|
|
@@ -16,6 +16,7 @@
|
|
#define MHISTATUS 0x48
|
|
#define MHICTRL 0x38
|
|
#define MHICTRL_RESET_MASK 0x2
|
|
+#define ATH12K_PCI_FW_RDDM_SZ 0x600000
|
|
|
|
enum ath12k_mhi_state {
|
|
ATH12K_MHI_INIT,
|
|
@@ -42,5 +43,6 @@ void ath12k_mhi_clear_vector(struct ath1
|
|
|
|
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 state);
|
|
|
|
#endif
|