diff mbox series

[1/2] wifi: ath11k: Add coredump support

Message ID 20230518101546.27998-2-quic_mpubbise@quicinc.com
State New
Headers show
Series wifi: ath11k: Add coredump support | expand

Commit Message

Manikanta Pubbisetty May 18, 2023, 10:15 a.m. UTC
In the event of firmware asserts, it is very useful to capture the
snapshot of the memory that firmware uses. This aids in debugging
firmware asserts. The snapshot of the DDR memory that firmware uses
can be dumped to user space by using kernel's devcoredump framework.

The design of coredump is taken from ath10k. The ABI looks more or
less similar to ath10k although some fields are removed/modified
to suite the design of ath11k.

coredump is disabled by default and can be controlled by a module
parameter called coredump_mask. Currently only RAMDUMPs are
supported and the interface can be easily extended in future.

RAMDUMPs can be enabled by using the following command:

modprobe ath11k coredump_mask=0x1

This patch adds the framework required to enable coredump in ath11k
but no hardware support is included. The next patch will add support
for WCN6750.

Tested-on: WCN6750 hw1.0 AHB WLAN.MSL.1.0.1-00887-QCAMSLSWPLZ-1

Signed-off-by: Manikanta Pubbisetty <quic_mpubbise@quicinc.com>
---
 drivers/net/wireless/ath/ath11k/Kconfig    |   1 +
 drivers/net/wireless/ath/ath11k/Makefile   |   1 +
 drivers/net/wireless/ath/ath11k/core.c     |  18 +-
 drivers/net/wireless/ath/ath11k/core.h     |  19 +-
 drivers/net/wireless/ath/ath11k/coredump.c | 209 +++++++++++++++++++++
 drivers/net/wireless/ath/ath11k/coredump.h | 147 +++++++++++++++
 6 files changed, 393 insertions(+), 2 deletions(-)
 create mode 100644 drivers/net/wireless/ath/ath11k/coredump.c
 create mode 100644 drivers/net/wireless/ath/ath11k/coredump.h
diff mbox series

Patch

diff --git a/drivers/net/wireless/ath/ath11k/Kconfig b/drivers/net/wireless/ath/ath11k/Kconfig
index ad5cc6cac05b..96074f5edfd0 100644
--- a/drivers/net/wireless/ath/ath11k/Kconfig
+++ b/drivers/net/wireless/ath/ath11k/Kconfig
@@ -5,6 +5,7 @@  config ATH11K
 	depends on CRYPTO_MICHAEL_MIC
 	select ATH_COMMON
 	select QCOM_QMI_HELPERS
+	select WANT_DEV_COREDUMP
 	help
 	  This module adds support for Qualcomm Technologies 802.11ax family of
 	  chipsets.
diff --git a/drivers/net/wireless/ath/ath11k/Makefile b/drivers/net/wireless/ath/ath11k/Makefile
index cc47e0114595..44f8039453d5 100644
--- a/drivers/net/wireless/ath/ath11k/Makefile
+++ b/drivers/net/wireless/ath/ath11k/Makefile
@@ -25,6 +25,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
diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c
index b1b90bd34d67..d666da5c746c 100644
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -1,7 +1,7 @@ 
 // SPDX-License-Identifier: BSD-3-Clause-Clear
 /*
  * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
- * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved.
  */
 
 #include <linux/module.h>
@@ -16,6 +16,7 @@ 
 #include "debug.h"
 #include "hif.h"
 #include "wow.h"
+#include "coredump.h"
 
 unsigned int ath11k_debug_mask;
 EXPORT_SYMBOL(ath11k_debug_mask);
@@ -32,6 +33,10 @@  module_param_named(frame_mode, ath11k_frame_mode, uint, 0644);
 MODULE_PARM_DESC(frame_mode,
 		 "Datapath frame mode (0: raw, 1: native wifi (default), 2: ethernet)");
 
+unsigned long ath11k_coredump_mask;
+module_param_named(coredump_mask, ath11k_coredump_mask, ulong, 0444);
+MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file");
+
 static const struct ath11k_hw_params ath11k_hw_params[] = {
 	{
 		.hw_rev = ATH11K_HW_IPQ8074,
@@ -1825,6 +1830,11 @@  static void ath11k_core_restart(struct work_struct *work)
 	struct ath11k_base *ab = container_of(work, struct ath11k_base, restart_work);
 	int ret;
 
+	ret = ath11k_coredump_submit(ab);
+	if (ret)
+		ath11k_warn(ab, "failed to send firmware crash dump via devcoredump: %d",
+			    ret);
+
 	ret = ath11k_core_reconfigure_on_crash(ab);
 	if (ret) {
 		ath11k_err(ab, "failed to reconfigure driver on crash recovery\n");
@@ -1956,6 +1966,10 @@  int ath11k_core_init(struct ath11k_base *ab)
 		return ret;
 	}
 
+	ret = ath11k_coredump_create(ab);
+	if (ret)
+		ath11k_warn(ab, "failed to create coredump memory: %d", ret);
+
 	return 0;
 }
 EXPORT_SYMBOL(ath11k_core_init);
@@ -1971,6 +1985,7 @@  void ath11k_core_deinit(struct ath11k_base *ab)
 
 	ath11k_hif_power_down(ab);
 	ath11k_mac_destroy(ab);
+	ath11k_coredump_destroy(ab);
 	ath11k_core_soc_destroy(ab);
 }
 EXPORT_SYMBOL(ath11k_core_deinit);
@@ -2005,6 +2020,7 @@  struct ath11k_base *ath11k_core_alloc(struct device *dev, size_t priv_size,
 
 	mutex_init(&ab->core_lock);
 	mutex_init(&ab->tbl_mtx_lock);
+	mutex_init(&ab->dump_mutex);
 	spin_lock_init(&ab->base_lock);
 	mutex_init(&ab->vdev_id_11d_lock);
 	init_completion(&ab->reset_complete);
diff --git a/drivers/net/wireless/ath/ath11k/core.h b/drivers/net/wireless/ath/ath11k/core.h
index 0830276e5028..82334bc0f514 100644
--- a/drivers/net/wireless/ath/ath11k/core.h
+++ b/drivers/net/wireless/ath/ath11k/core.h
@@ -1,7 +1,7 @@ 
 /* SPDX-License-Identifier: BSD-3-Clause-Clear */
 /*
  * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
- * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved.
  */
 
 #ifndef ATH11K_CORE_H
@@ -52,6 +52,7 @@ 
 #define ATH11K_SMBIOS_BDF_EXT_MAGIC "BDF_"
 
 extern unsigned int ath11k_frame_mode;
+extern unsigned long ath11k_coredump_mask;
 
 #define ATH11K_SCAN_TIMEOUT_HZ (20 * HZ)
 
@@ -589,6 +590,14 @@  struct ath11k_per_peer_tx_stats {
 #define ATH11K_FLUSH_TIMEOUT (5 * HZ)
 #define ATH11K_VDEV_DELETE_TIMEOUT_HZ (5 * HZ)
 
+/* used for crash-dump storage, protected by dump_lock */
+struct ath11k_fw_crash_data {
+	guid_t guid;
+	struct timespec64 timestamp;
+	u8 *ramdump_buf;
+	size_t ramdump_buf_len;
+};
+
 struct ath11k {
 	struct ath11k_base *ab;
 	struct ath11k_pdev *pdev;
@@ -978,6 +987,14 @@  struct ath11k_base {
 		const struct ath11k_pci_ops *ops;
 	} pci;
 
+#ifdef CONFIG_DEV_COREDUMP
+	struct {
+		struct ath11k_fw_crash_data *fw_crash_data;
+	} coredump;
+#endif
+	/* protects coredump data */
+	struct mutex dump_mutex;
+
 	/* must be last */
 	u8 drv_priv[] __aligned(sizeof(void *));
 };
diff --git a/drivers/net/wireless/ath/ath11k/coredump.c b/drivers/net/wireless/ath/ath11k/coredump.c
new file mode 100644
index 000000000000..1e5a58ec673e
--- /dev/null
+++ b/drivers/net/wireless/ath/ath11k/coredump.c
@@ -0,0 +1,209 @@ 
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/*
+ * Copyright (c) 2011-2017 Qualcomm Atheros, Inc.
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/devcoredump.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/utsname.h>
+
+#include "coredump.h"
+#include "debug.h"
+
+static const struct ath11k_hw_mem_layout hw_mem_layouts[] = {
+	/* HW memory layouts shall be defined here */
+};
+
+const struct ath11k_hw_mem_layout *
+ath11k_coredump_get_mem_layout(struct ath11k_base *ab)
+{
+	int i;
+
+	if (!test_bit(ATH11K_FW_CRASH_DUMP_RAM_DATA, &ath11k_coredump_mask))
+		return NULL;
+
+	for (i = 0; i < ARRAY_SIZE(hw_mem_layouts); i++) {
+		if (ab->hw_rev == hw_mem_layouts[i].hw_rev &&
+		    hw_mem_layouts[i].bus == ab->hif.bus)
+			return &hw_mem_layouts[i];
+	}
+
+	return NULL;
+}
+EXPORT_SYMBOL(ath11k_coredump_get_mem_layout);
+
+static u32 ath11k_coredump_get_ramdump_size(struct ath11k_base *ab)
+{
+	const struct ath11k_hw_mem_layout *hw;
+	const struct ath11k_mem_region *mem_region;
+	size_t size = 0;
+	int i;
+
+	hw = ath11k_coredump_get_mem_layout(ab);
+
+	if (!hw)
+		return 0;
+
+	mem_region = &hw->region_table.regions[0];
+
+	for (i = 0; i < hw->region_table.size; i++) {
+		size += mem_region->len;
+		mem_region++;
+	}
+
+	/* reserve space for the headers */
+	size += hw->region_table.size * sizeof(struct ath11k_ramdump_hdr);
+
+	/* make sure it is aligned 16 bytes for debug message print out */
+	size = ALIGN(size, 16);
+
+	return size;
+}
+
+struct ath11k_fw_crash_data *ath11k_coredump_new(struct ath11k_base *ab)
+{
+	struct ath11k_fw_crash_data *crash_data = ab->coredump.fw_crash_data;
+
+	lockdep_assert_held(&ab->dump_mutex);
+
+	if (ath11k_coredump_mask == 0 || !crash_data)
+		/* coredump disabled */
+		return NULL;
+
+	guid_gen(&crash_data->guid);
+	ktime_get_real_ts64(&crash_data->timestamp);
+
+	return crash_data;
+}
+EXPORT_SYMBOL(ath11k_coredump_new);
+
+static struct ath11k_dump_file_data *ath11k_coredump_build(struct ath11k_base *ab)
+{
+	struct ath11k_fw_crash_data *crash_data = ab->coredump.fw_crash_data;
+	struct ath11k_dump_file_data *dump_data;
+	struct ath11k_tlv_dump_data *dump_tlv;
+	size_t hdr_len = sizeof(*dump_data);
+	size_t len, sofar = 0;
+	unsigned char *buf;
+
+	len = hdr_len;
+
+	if (test_bit(ATH11K_FW_CRASH_DUMP_RAM_DATA, &ath11k_coredump_mask) &&
+	    crash_data->ramdump_buf)
+		len += sizeof(*dump_tlv) + crash_data->ramdump_buf_len;
+
+	sofar += hdr_len;
+
+	buf = vzalloc(len);
+	if (!buf)
+		return NULL;
+
+	mutex_lock(&ab->dump_mutex);
+
+	dump_data = (struct ath11k_dump_file_data *)(buf);
+
+	strscpy(dump_data->df_magic, "ATH11K-FW-DUMP",
+		sizeof(dump_data->df_magic));
+	dump_data->len = cpu_to_le32(len);
+
+	guid_copy(&dump_data->guid, &crash_data->guid);
+
+	dump_data->version = cpu_to_le32(ATH11K_FW_CRASH_DUMP_VERSION);
+	dump_data->hw_rev = cpu_to_le32(ab->hw_params.hw_rev);
+	dump_data->bus_type = cpu_to_le32(ab->hif.bus);
+
+	strscpy(dump_data->fw_ver, ab->qmi.target.fw_build_id,
+		sizeof(ab->qmi.target.fw_build_id));
+
+	dump_data->kernel_ver_code = 0;
+	strscpy(dump_data->kernel_ver, init_utsname()->release,
+		sizeof(dump_data->kernel_ver));
+
+	dump_data->tv_sec = cpu_to_le64(crash_data->timestamp.tv_sec);
+	dump_data->tv_nsec = cpu_to_le64(crash_data->timestamp.tv_nsec);
+
+	if (test_bit(ATH11K_FW_CRASH_DUMP_RAM_DATA, &ath11k_coredump_mask) &&
+	    crash_data->ramdump_buf) {
+		dump_tlv = (struct ath11k_tlv_dump_data *)(buf + sofar);
+		dump_tlv->type = cpu_to_le32(ATH11K_FW_CRASH_DUMP_RAM_DATA);
+		dump_tlv->tlv_len = cpu_to_le32(crash_data->ramdump_buf_len);
+		if (crash_data->ramdump_buf_len) {
+			memcpy(dump_tlv->tlv_data, crash_data->ramdump_buf,
+			       crash_data->ramdump_buf_len);
+			sofar += sizeof(*dump_tlv) + crash_data->ramdump_buf_len;
+		}
+	}
+
+	mutex_unlock(&ab->dump_mutex);
+
+	return dump_data;
+}
+
+int ath11k_coredump_submit(struct ath11k_base *ab)
+{
+	struct ath11k_dump_file_data *dump;
+
+	if (ath11k_coredump_mask == 0 || !ab->coredump.fw_crash_data)
+		/* coredump disabled */
+		return 0;
+
+	dump = ath11k_coredump_build(ab);
+	if (!dump) {
+		ath11k_warn(ab, "no crash dump data found for devcoredump");
+		return -ENODATA;
+	}
+
+	dev_coredumpv(ab->dev, dump, le32_to_cpu(dump->len), GFP_KERNEL);
+
+	return 0;
+}
+
+int ath11k_coredump_create(struct ath11k_base *ab)
+{
+	struct ath11k_fw_crash_data *crash_data;
+
+	if (ath11k_coredump_mask == 0)
+		/* coredump disabled */
+		return 0;
+
+	ab->coredump.fw_crash_data = vzalloc(sizeof(*ab->coredump.fw_crash_data));
+	if (!ab->coredump.fw_crash_data)
+		return -ENOMEM;
+
+	crash_data = ab->coredump.fw_crash_data;
+
+	if (test_bit(ATH11K_FW_CRASH_DUMP_RAM_DATA, &ath11k_coredump_mask)) {
+		crash_data->ramdump_buf_len = ath11k_coredump_get_ramdump_size(ab);
+
+		if (!crash_data->ramdump_buf_len)
+			return 0;
+
+		crash_data->ramdump_buf = vzalloc(crash_data->ramdump_buf_len);
+		if (!crash_data->ramdump_buf) {
+			ath11k_warn(ab, "failed to allocate ramdump buffer\n");
+			return -ENOMEM;
+		}
+	}
+
+	return 0;
+}
+
+void ath11k_coredump_destroy(struct ath11k_base *ab)
+{
+	struct ath11k_fw_crash_data *crash_data = ab->coredump.fw_crash_data;
+
+	if (ath11k_coredump_mask == 0)
+		/* coredump disabled */
+		return;
+
+	if (crash_data->ramdump_buf) {
+		vfree(crash_data->ramdump_buf);
+		crash_data->ramdump_buf = NULL;
+		crash_data->ramdump_buf_len = 0;
+	}
+
+	vfree(crash_data);
+	ab->coredump.fw_crash_data = NULL;
+}
diff --git a/drivers/net/wireless/ath/ath11k/coredump.h b/drivers/net/wireless/ath/ath11k/coredump.h
new file mode 100644
index 000000000000..47fe4b66cd25
--- /dev/null
+++ b/drivers/net/wireless/ath/ath11k/coredump.h
@@ -0,0 +1,147 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause-Clear
+ *
+ * Copyright (c) 2011-2017 Qualcomm Atheros, Inc.
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _COREDUMP_H_
+#define _COREDUMP_H_
+
+#include "core.h"
+
+#define ATH11K_FW_CRASH_DUMP_VERSION 1
+
+enum ath11k_fw_crash_dump_type {
+	/* contains multiple struct ath11k_ramdump_hdr */
+	ATH11K_FW_CRASH_DUMP_RAM_DATA = 0,
+	ATH11K_FW_CRASH_DUMP_MAX,
+};
+
+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];
+
+	__le32 len;
+
+	/* file dump version */
+	__le32 version;
+
+	/* some info we can get from ath11k_base struct that might help */
+
+	guid_t guid;
+
+	__le32 hw_rev;
+
+	__le32 bus_type;
+
+	/* firmware version string */
+	char fw_ver[ATH11K_QMI_WLANFW_MAX_BUILD_ID_LEN_V01 + 1];
+
+	/* Kernel related information */
+
+	/* time-of-day stamp */
+	__le64 tv_sec;
+
+	/* time-of-day stamp, nano-seconds */
+	__le64 tv_nsec;
+
+	/* LINUX_VERSION_CODE */
+	__le32 kernel_ver_code;
+
+	/* VERMAGIC_STRING */
+	char kernel_ver[64];
+
+	/* room for growth w/out changing binary format */
+	u8 unused[128];
+
+	/* struct ath11k_tlv_dump_data + more */
+	u8 data[];
+} __packed;
+
+struct ath11k_ramdump_hdr {
+	/* enum ath11k_mem_region_type */
+	__le32 region_type;
+
+	__le64 start;
+
+	/* length of payload data, not including this header */
+	__le32 length;
+
+	u8 data[];
+};
+
+/* part of user space ABI */
+enum ath11k_mem_region_type {
+	ATH11K_MEM_REGION_TYPE_MSA = 1,
+};
+
+struct ath11k_mem_region {
+	enum ath11k_mem_region_type type;
+	u32 start;
+	u32 len;
+
+	const char *name;
+};
+
+/* Contains the memory layout of a hardware version identified with the
+ * hw_rev, split into regions.
+ */
+struct ath11k_hw_mem_layout {
+	u32 hw_rev;
+	enum ath11k_bus bus;
+
+	struct {
+		const struct ath11k_mem_region *regions;
+		int size;
+	} region_table;
+};
+
+#ifdef CONFIG_DEV_COREDUMP
+
+int ath11k_coredump_submit(struct  ath11k_base *ab);
+struct ath11k_fw_crash_data *ath11k_coredump_new(struct  ath11k_base *ab);
+int ath11k_coredump_create(struct  ath11k_base *ab);
+void ath11k_coredump_destroy(struct  ath11k_base *ab);
+const struct ath11k_hw_mem_layout *
+ath11k_coredump_get_mem_layout(struct  ath11k_base *ab);
+
+#else /* CONFIG_DEV_COREDUMP */
+
+static inline int ath11k_coredump_submit(struct  ath11k_base *ab)
+{
+	return 0;
+}
+
+static inline struct ath11k_fw_crash_data *ath11k_coredump_new(struct  ath11k_base *ab)
+{
+	return NULL;
+}
+
+static inline int ath11k_coredump_create(struct  ath11k_base *ab)
+{
+	return 0;
+}
+
+static inline void ath11k_coredump_destroy(struct  ath11k_base *ab)
+{
+}
+
+static inline const struct ath11k_hw_mem_layout *
+ath11k_coredump_get_mem_layout(struct  ath11k_base *ab)
+{
+	return NULL;
+}
+#endif /* CONFIG_DEV_COREDUMP */
+
+#endif /* _COREDUMP_H_ */