aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/ath/ath10k/sdio.c
diff options
context:
space:
mode:
authorWen Gong <wgong@codeaurora.org>2020-08-18 17:12:02 +0300
committerKalle Valo <kvalo@codeaurora.org>2020-08-19 20:36:19 +0300
commit3c45f21af84eb05a355919abc80cf70a3a681cee (patch)
treeb5873c34e6082c58bd9a68efd4f59c69ba31798d /drivers/net/wireless/ath/ath10k/sdio.c
parentath10k: add bus type for each layout of coredump (diff)
downloadlinux-dev-3c45f21af84eb05a355919abc80cf70a3a681cee.tar.xz
linux-dev-3c45f21af84eb05a355919abc80cf70a3a681cee.zip
ath10k: sdio: add firmware coredump support
When firmware crashes it's possible to create a coredump for later analysis, add support to collect the register and memory info from SDIO devices. The coredump configuration is different between QCA6174 PCI and QCA6174 SDIO, so add specific registers and memory regions for the latter. QCA6174 SDIO has two methods to dump the firmware: fastdump and slowdump. Fastdump is not supported in olded versions of firmware, and for these ath10k will automatically select slowdump. If firmware supports fastdump, ath10k will automatically select it. QCA6174 SDIO firmware version WLAN.RMH.4.4.1-00017-QCARMSWPZ-2 is the first version supporting fastdump. For slowdump, ath10k_sdio_hif_diag_read() can not be used as the diag window has a limit value, it is 4 bytes and the dump's buffer length is larger than it, it will trigger error. So this patch adds ath10k_sdio_read_mem() to read 4 bytes for each time. Example output of a firmware crash: ath10k_sdio mmc1:0001:1: simulating soft firmware crash ath10k_sdio mmc1:0001:1: firmware crashed! (guid 413d98b1-84c0-4298-b605-2b10ec0c54a5) ath10k_sdio mmc1:0001:1: qca6174 hw3.2 sdio target 0x05030000 chip_id 0x00000000 sub 0000:0000 ath10k_sdio mmc1:0001:1: kconfig debug 1 debugfs 1 tracing 1 dfs 0 testmode 1 ath10k_sdio mmc1:0001:1: firmware ver WLAN.RMH4.4.1-00126-QCARMSWP-1 api 6 features wowlan,ignore-otp,raw-mode crc32 b84317cf ath10k_sdio mmc1:0001:1: board_file api 2 bmi_id 0:4 crc32 6364cfcc ath10k_sdio mmc1:0001:1: htt-ver 3.69 wmi-op 4 htt-op 3 cal otp max-sta 32 raw 0 hwcrypto 1 ath10k_sdio mmc1:0001:1: firmware register dump: ath10k_sdio mmc1:0001:1: [00]: 0x05030000 0x000015B3 0x0099908D 0x00955B31 ath10k_sdio mmc1:0001:1: [04]: 0x0099908D 0x00060730 0x00000018 0x004641A0 ath10k_sdio mmc1:0001:1: [08]: 0x0041FAA4 0x0041FA9C 0x00999070 0x00404490 ath10k_sdio mmc1:0001:1: [12]: 0x00000009 0xFFFFFFFF 0x00952CD0 0x00952CE6 ath10k_sdio mmc1:0001:1: [16]: 0x00952CC4 0x00910712 0x00000000 0x00000000 ath10k_sdio mmc1:0001:1: [20]: 0x4099908D 0x0040E9E8 0x00000001 0x00423AC0 ath10k_sdio mmc1:0001:1: [24]: 0x809F3189 0x0040EA48 0x00426240 0xC099908D ath10k_sdio mmc1:0001:1: [28]: 0x809143A7 0x0040EA68 0x0041FAA4 0x00423A80 ath10k_sdio mmc1:0001:1: [32]: 0x809F1193 0x0040EA88 0x00411770 0x004117E0 ath10k_sdio mmc1:0001:1: [36]: 0x809F0EEE 0x0040EAA8 0x00000000 0x00000000 ath10k_sdio mmc1:0001:1: [40]: 0x80911210 0x0040EAC8 0x00000008 0x00404130 ath10k_sdio mmc1:0001:1: [44]: 0x80911154 0x0040EB28 0x00400000 0x00000000 ath10k_sdio mmc1:0001:1: [48]: 0x8091122D 0x0040EB48 0x00000000 0x00400600 ath10k_sdio mmc1:0001:1: [52]: 0x40910024 0x0040EB78 0x0040AB98 0x0040AB98 ath10k_sdio mmc1:0001:1: [56]: 0x00000000 0x0040EB98 0x009BB001 0x00040020 Tested-on: QCA6174 SDIO WLAN.RMH.4.4.1-00018-QCARMSWP-1 Signed-off-by: Wen Gong <wgong@codeaurora.org> Signed-off-by: Kalle Valo <kvalo@codeaurora.org> Link: https://lore.kernel.org/r/1569310030-834-3-git-send-email-wgong@codeaurora.org
Diffstat (limited to 'drivers/net/wireless/ath/ath10k/sdio.c')
-rw-r--r--drivers/net/wireless/ath/ath10k/sdio.c327
1 files changed, 323 insertions, 4 deletions
diff --git a/drivers/net/wireless/ath/ath10k/sdio.c b/drivers/net/wireless/ath/ath10k/sdio.c
index 0841e69b10b1..81ddaafb6721 100644
--- a/drivers/net/wireless/ath/ath10k/sdio.c
+++ b/drivers/net/wireless/ath/ath10k/sdio.c
@@ -23,6 +23,9 @@
#include "targaddrs.h"
#include "trace.h"
#include "sdio.h"
+#include "coredump.h"
+
+void ath10k_sdio_fw_crashed_dump(struct ath10k *ar);
#define ATH10K_SDIO_VSG_BUF_SIZE (64 * 1024)
@@ -916,10 +919,9 @@ static int ath10k_sdio_mbox_proc_cpu_intr(struct ath10k *ar)
out:
mutex_unlock(&irq_data->mtx);
- if (cpu_int_status & MBOX_CPU_STATUS_ENABLE_ASSERT_MASK) {
- ath10k_err(ar, "firmware crashed!\n");
- queue_work(ar->workqueue, &ar->restart_work);
- }
+ if (cpu_int_status & MBOX_CPU_STATUS_ENABLE_ASSERT_MASK)
+ ath10k_sdio_fw_crashed_dump(ar);
+
return ret;
}
@@ -2185,6 +2187,323 @@ static int ath10k_sdio_napi_poll(struct napi_struct *ctx, int budget)
return done;
}
+static int ath10k_sdio_read_host_interest_value(struct ath10k *ar,
+ u32 item_offset,
+ u32 *val)
+{
+ u32 addr;
+ int ret;
+
+ addr = host_interest_item_address(item_offset);
+
+ ret = ath10k_sdio_diag_read32(ar, addr, val);
+
+ if (ret)
+ ath10k_warn(ar, "unable to read host interest offset %d value\n",
+ item_offset);
+
+ return ret;
+}
+
+static int ath10k_sdio_read_mem(struct ath10k *ar, u32 address, void *buf,
+ u32 buf_len)
+{
+ u32 val;
+ int i, ret;
+
+ for (i = 0; i < buf_len; i += 4) {
+ ret = ath10k_sdio_diag_read32(ar, address + i, &val);
+ if (ret) {
+ ath10k_warn(ar, "unable to read mem %d value\n", address + i);
+ break;
+ }
+ memcpy(buf + i, &val, 4);
+ }
+
+ return ret;
+}
+
+static bool ath10k_sdio_is_fast_dump_supported(struct ath10k *ar)
+{
+ u32 param;
+
+ ath10k_sdio_read_host_interest_value(ar, HI_ITEM(hi_option_flag2), &param);
+
+ ath10k_dbg(ar, ATH10K_DBG_SDIO, "sdio hi_option_flag2 %x\n", param);
+
+ return param & HI_OPTION_SDIO_CRASH_DUMP_ENHANCEMENT_FW;
+}
+
+static void ath10k_sdio_dump_registers(struct ath10k *ar,
+ struct ath10k_fw_crash_data *crash_data,
+ bool fast_dump)
+{
+ u32 reg_dump_values[REG_DUMP_COUNT_QCA988X] = {};
+ int i, ret;
+ u32 reg_dump_area;
+
+ ret = ath10k_sdio_read_host_interest_value(ar, HI_ITEM(hi_failure_state),
+ &reg_dump_area);
+ if (ret) {
+ ath10k_warn(ar, "failed to read firmware dump area: %d\n", ret);
+ return;
+ }
+
+ if (fast_dump)
+ ret = ath10k_bmi_read_memory(ar, reg_dump_area, reg_dump_values,
+ sizeof(reg_dump_values));
+ else
+ ret = ath10k_sdio_read_mem(ar, reg_dump_area, reg_dump_values,
+ sizeof(reg_dump_values));
+
+ if (ret) {
+ ath10k_warn(ar, "failed to read firmware dump value: %d\n", ret);
+ return;
+ }
+
+ ath10k_err(ar, "firmware register dump:\n");
+ for (i = 0; i < ARRAY_SIZE(reg_dump_values); i += 4)
+ ath10k_err(ar, "[%02d]: 0x%08X 0x%08X 0x%08X 0x%08X\n",
+ i,
+ reg_dump_values[i],
+ reg_dump_values[i + 1],
+ reg_dump_values[i + 2],
+ reg_dump_values[i + 3]);
+
+ if (!crash_data)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(reg_dump_values); i++)
+ crash_data->registers[i] = __cpu_to_le32(reg_dump_values[i]);
+}
+
+static int ath10k_sdio_dump_memory_section(struct ath10k *ar,
+ const struct ath10k_mem_region *mem_region,
+ u8 *buf, size_t buf_len)
+{
+ const struct ath10k_mem_section *cur_section, *next_section;
+ unsigned int count, section_size, skip_size;
+ int ret, i, j;
+
+ if (!mem_region || !buf)
+ return 0;
+
+ cur_section = &mem_region->section_table.sections[0];
+
+ if (mem_region->start > cur_section->start) {
+ ath10k_warn(ar, "incorrect memdump region 0x%x with section start address 0x%x.\n",
+ mem_region->start, cur_section->start);
+ return 0;
+ }
+
+ skip_size = cur_section->start - mem_region->start;
+
+ /* fill the gap between the first register section and register
+ * start address
+ */
+ for (i = 0; i < skip_size; i++) {
+ *buf = ATH10K_MAGIC_NOT_COPIED;
+ buf++;
+ }
+
+ count = 0;
+
+ for (i = 0; cur_section; i++) {
+ section_size = cur_section->end - cur_section->start;
+
+ if (section_size <= 0) {
+ ath10k_warn(ar, "incorrect ramdump format with start address 0x%x and stop address 0x%x\n",
+ cur_section->start,
+ cur_section->end);
+ break;
+ }
+
+ if ((i + 1) == mem_region->section_table.size) {
+ /* last section */
+ next_section = NULL;
+ skip_size = 0;
+ } else {
+ next_section = cur_section + 1;
+
+ if (cur_section->end > next_section->start) {
+ ath10k_warn(ar, "next ramdump section 0x%x is smaller than current end address 0x%x\n",
+ next_section->start,
+ cur_section->end);
+ break;
+ }
+
+ skip_size = next_section->start - cur_section->end;
+ }
+
+ if (buf_len < (skip_size + section_size)) {
+ ath10k_warn(ar, "ramdump buffer is too small: %zu\n", buf_len);
+ break;
+ }
+
+ buf_len -= skip_size + section_size;
+
+ /* read section to dest memory */
+ ret = ath10k_sdio_read_mem(ar, cur_section->start,
+ buf, section_size);
+ if (ret) {
+ ath10k_warn(ar, "failed to read ramdump from section 0x%x: %d\n",
+ cur_section->start, ret);
+ break;
+ }
+
+ buf += section_size;
+ count += section_size;
+
+ /* fill in the gap between this section and the next */
+ for (j = 0; j < skip_size; j++) {
+ *buf = ATH10K_MAGIC_NOT_COPIED;
+ buf++;
+ }
+
+ count += skip_size;
+
+ if (!next_section)
+ /* this was the last section */
+ break;
+
+ cur_section = next_section;
+ }
+
+ return count;
+}
+
+/* if an error happened returns < 0, otherwise the length */
+static int ath10k_sdio_dump_memory_generic(struct ath10k *ar,
+ const struct ath10k_mem_region *current_region,
+ u8 *buf,
+ bool fast_dump)
+{
+ int ret;
+
+ if (current_region->section_table.size > 0)
+ /* Copy each section individually. */
+ return ath10k_sdio_dump_memory_section(ar,
+ current_region,
+ buf,
+ current_region->len);
+
+ /* No individiual memory sections defined so we can
+ * copy the entire memory region.
+ */
+ if (fast_dump)
+ ret = ath10k_bmi_read_memory(ar,
+ current_region->start,
+ buf,
+ current_region->len);
+ else
+ ret = ath10k_sdio_read_mem(ar,
+ current_region->start,
+ buf,
+ current_region->len);
+
+ if (ret) {
+ ath10k_warn(ar, "failed to copy ramdump region %s: %d\n",
+ current_region->name, ret);
+ return ret;
+ }
+
+ return current_region->len;
+}
+
+static void ath10k_sdio_dump_memory(struct ath10k *ar,
+ struct ath10k_fw_crash_data *crash_data,
+ bool fast_dump)
+{
+ const struct ath10k_hw_mem_layout *mem_layout;
+ const struct ath10k_mem_region *current_region;
+ struct ath10k_dump_ram_data_hdr *hdr;
+ u32 count;
+ size_t buf_len;
+ int ret, i;
+ u8 *buf;
+
+ if (!crash_data)
+ return;
+
+ mem_layout = ath10k_coredump_get_mem_layout(ar);
+ if (!mem_layout)
+ return;
+
+ current_region = &mem_layout->region_table.regions[0];
+
+ buf = crash_data->ramdump_buf;
+ buf_len = crash_data->ramdump_buf_len;
+
+ memset(buf, 0, buf_len);
+
+ for (i = 0; i < mem_layout->region_table.size; i++) {
+ count = 0;
+
+ if (current_region->len > buf_len) {
+ ath10k_warn(ar, "memory region %s size %d is larger that remaining ramdump buffer size %zu\n",
+ current_region->name,
+ current_region->len,
+ buf_len);
+ break;
+ }
+
+ /* Reserve space for the header. */
+ hdr = (void *)buf;
+ buf += sizeof(*hdr);
+ buf_len -= sizeof(*hdr);
+
+ ret = ath10k_sdio_dump_memory_generic(ar, current_region, buf,
+ fast_dump);
+ if (ret >= 0)
+ count = ret;
+
+ hdr->region_type = cpu_to_le32(current_region->type);
+ hdr->start = cpu_to_le32(current_region->start);
+ hdr->length = cpu_to_le32(count);
+
+ if (count == 0)
+ /* Note: the header remains, just with zero length. */
+ break;
+
+ buf += count;
+ buf_len -= count;
+
+ current_region++;
+ }
+}
+
+void ath10k_sdio_fw_crashed_dump(struct ath10k *ar)
+{
+ struct ath10k_fw_crash_data *crash_data;
+ char guid[UUID_STRING_LEN + 1];
+ bool fast_dump;
+
+ fast_dump = ath10k_sdio_is_fast_dump_supported(ar);
+
+ if (fast_dump)
+ ath10k_bmi_start(ar);
+
+ ar->stats.fw_crash_counter++;
+
+ ath10k_sdio_disable_intrs(ar);
+
+ crash_data = ath10k_coredump_new(ar);
+
+ if (crash_data)
+ scnprintf(guid, sizeof(guid), "%pUl", &crash_data->guid);
+ else
+ scnprintf(guid, sizeof(guid), "n/a");
+
+ ath10k_err(ar, "firmware crashed! (guid %s)\n", guid);
+ ath10k_print_driver_info(ar);
+ ath10k_sdio_dump_registers(ar, crash_data, fast_dump);
+ ath10k_sdio_dump_memory(ar, crash_data, fast_dump);
+
+ ath10k_sdio_enable_intrs(ar);
+
+ queue_work(ar->workqueue, &ar->restart_work);
+}
+
static int ath10k_sdio_probe(struct sdio_func *func,
const struct sdio_device_id *id)
{