Commit 4dbcb912 authored by Ryder Lee's avatar Ryder Lee Committed by Felix Fietkau
Browse files

wifi: mt76: mt7915: enable coredump support



Host triggered and catastrophic event triggered firmware core dumping
for basic firmware issues triage, including state reporting, task/irq
info, function calltrace and MCU memory dump.

Signed-off-by: default avatarRyder Lee <ryder.lee@mediatek.com>
Signed-off-by: default avatarFelix Fietkau <nbd@nbd.name>
parent b662b71a
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@
config MT7915E
	tristate "MediaTek MT7915E (PCIe) support"
	select MT76_CONNAC_LIB
	select WANT_DEV_COREDUMP
	depends on MAC80211
	depends on PCI
	select RELAY
+2 −1
Original line number Diff line number Diff line
@@ -7,3 +7,4 @@ mt7915e-y := pci.o init.o dma.o eeprom.o main.o mcu.o mac.o \

mt7915e-$(CONFIG_NL80211_TESTMODE) += testmode.o
mt7915e-$(CONFIG_MT7986_WMAC) += soc.o
mt7915e-$(CONFIG_DEV_COREDUMP) += coredump.o
+410 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: ISC
/* Copyright (C) 2022 MediaTek Inc. */

#include <linux/devcoredump.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/utsname.h>
#include "coredump.h"

static bool coredump_memdump;
module_param(coredump_memdump, bool, 0644);
MODULE_PARM_DESC(coredump_memdump, "Optional ability to dump firmware memory");

static const struct mt7915_mem_region mt7915_mem_regions[] = {
	{
		.start = 0xe003b400,
		.len = 0x00003bff,
		.name = "CRAM",
	},
};

static const struct mt7915_mem_region mt7916_mem_regions[] = {
	{
		.start = 0x00800000,
		.len = 0x0005ffff,
		.name = "ROM",
	},
	{
		.start = 0x00900000,
		.len = 0x00013fff,
		.name = "ULM1",
	},
	{
		.start = 0x02200000,
		.len = 0x0004ffff,
		.name = "ULM2",
	},
	{
		.start = 0x02300000,
		.len = 0x0004ffff,
		.name = "ULM3",
	},
	{
		.start = 0x00400000,
		.len = 0x00027fff,
		.name = "SRAM",
	},
	{
		.start = 0xe0000000,
		.len = 0x00157fff,
		.name = "CRAM",
	},
};

static const struct mt7915_mem_region mt7986_mem_regions[] = {
	{
		.start = 0x00800000,
		.len = 0x0005ffff,
		.name = "ROM",
	},
	{
		.start = 0x00900000,
		.len = 0x0000ffff,
		.name = "ULM1",
	},
	{
		.start = 0x02200000,
		.len = 0x0004ffff,
		.name = "ULM2",
	},
	{
		.start = 0x02300000,
		.len = 0x0004ffff,
		.name = "ULM3",
	},
	{
		.start = 0x00400000,
		.len = 0x00017fff,
		.name = "SRAM",
	},
	{
		.start = 0xe0000000,
		.len = 0x00113fff,
		.name = "CRAM",
	},
};

const struct mt7915_mem_region*
mt7915_coredump_get_mem_layout(struct mt7915_dev *dev, u32 *num)
{
	switch (mt76_chip(&dev->mt76)) {
	case 0x7915:
		*num = ARRAY_SIZE(mt7915_mem_regions);
		return &mt7915_mem_regions[0];
	case 0x7986:
		*num = ARRAY_SIZE(mt7986_mem_regions);
		return &mt7986_mem_regions[0];
	case 0x7916:
		*num = ARRAY_SIZE(mt7916_mem_regions);
		return &mt7916_mem_regions[0];
	default:
		return NULL;
	}
}

static int mt7915_coredump_get_mem_size(struct mt7915_dev *dev)
{
	const struct mt7915_mem_region *mem_region;
	size_t size = 0;
	u32 num;
	int i;

	mem_region = mt7915_coredump_get_mem_layout(dev, &num);
	if (!mem_region)
		return 0;

	for (i = 0; i < num; i++) {
		size += mem_region->len;
		mem_region++;
	}

	/* reserve space for the headers */
	size += num * sizeof(struct mt7915_mem_hdr);
	/* make sure it is aligned 4 bytes for debug message print out */
	size = ALIGN(size, 4);

	return size;
}

struct mt7915_crash_data *mt7915_coredump_new(struct mt7915_dev *dev)
{
	struct mt7915_crash_data *crash_data = dev->coredump.crash_data;

	lockdep_assert_held(&dev->dump_mutex);

	guid_gen(&crash_data->guid);
	ktime_get_real_ts64(&crash_data->timestamp);

	return crash_data;
}

static void
mt7915_coredump_fw_state(struct mt7915_dev *dev, struct mt7915_coredump *dump,
			 bool *exception)
{
	u32 state, count, type;

	type = (u32)mt76_get_field(dev, MT_FW_EXCEPT_TYPE, GENMASK(7, 0));
	state = (u32)mt76_get_field(dev, MT_FW_ASSERT_STAT, GENMASK(7, 0));
	count = is_mt7915(&dev->mt76) ?
		(u32)mt76_get_field(dev, MT_FW_EXCEPT_COUNT, GENMASK(15, 8)) :
		(u32)mt76_get_field(dev, MT_FW_EXCEPT_COUNT, GENMASK(7, 0));

	/* normal mode: driver can manually trigger assert for detail info */
	if (!count)
		strscpy(dump->fw_state, "normal", sizeof(dump->fw_state));
	else if (state > 1 && (count == 1) && type == 5)
		strscpy(dump->fw_state, "assert", sizeof(dump->fw_state));
	else if ((state > 1 && count == 1) || count > 1)
		strscpy(dump->fw_state, "exception", sizeof(dump->fw_state));

	*exception = !!count;
}

static void
mt7915_coredump_fw_trace(struct mt7915_dev *dev, struct mt7915_coredump *dump,
			 bool exception)
{
	u32 n, irq, sch, base = MT_FW_EINT_INFO;

	/* trap or run? */
	dump->last_msg_id = mt76_rr(dev, MT_FW_LAST_MSG_ID);

	n = is_mt7915(&dev->mt76) ?
	    (u32)mt76_get_field(dev, base, GENMASK(7, 0)) :
	    (u32)mt76_get_field(dev, base, GENMASK(15, 8));
	dump->eint_info_idx = n;

	irq = mt76_rr(dev, base + 0x8);
	n = is_mt7915(&dev->mt76) ?
	    FIELD_GET(GENMASK(7, 0), irq) : FIELD_GET(GENMASK(23, 16), irq);
	dump->irq_info_idx = n;

	sch = mt76_rr(dev, MT_FW_SCHED_INFO);
	n = is_mt7915(&dev->mt76) ?
	    FIELD_GET(GENMASK(7, 0), sch) : FIELD_GET(GENMASK(15, 8), sch);
	dump->sched_info_idx = n;

	if (exception) {
		u32 i, y;

		/* sched trace */
		n = is_mt7915(&dev->mt76) ?
		    FIELD_GET(GENMASK(15, 8), sch) : FIELD_GET(GENMASK(7, 0), sch);
		n = n > 60 ? 60 : n;

		strscpy(dump->trace_sched, "(sched_info) id, time",
			sizeof(dump->trace_sched));

		for (y = dump->sched_info_idx, i = 0; i < n; i++, y++) {
			mt7915_memcpy_fromio(dev, dump->sched, base + 0xc + y * 12,
					     sizeof(dump->sched));
			y = y >= n ? 0 : y;
		}

		/* irq trace */
		n = is_mt7915(&dev->mt76) ?
		    FIELD_GET(GENMASK(15, 8), irq) : FIELD_GET(GENMASK(7, 0), irq);
		n = n > 60 ? 60 : n;

		strscpy(dump->trace_irq, "(irq_info) id, time",
			sizeof(dump->trace_irq));

		for (y = dump->irq_info_idx, i = 0; i < n; i++, y++) {
			mt7915_memcpy_fromio(dev, dump->irq, base + 0x4 + y * 16,
					     sizeof(dump->irq));
			y = y >= n ? 0 : y;
		}
	}
}

static void
mt7915_coredump_fw_stack(struct mt7915_dev *dev, struct mt7915_coredump *dump,
			 bool exception)
{
	u32 oldest, i, idx;

	/* stop call stack record */
	if (!exception)
		mt76_clear(dev, 0x89050200, BIT(0));

	oldest = (u32)mt76_get_field(dev, 0x89050200, GENMASK(20, 16)) + 2;
	for (i = 0; i < 16; i++) {
		idx = ((oldest + 2 * i + 1) % 32);
		dump->call_stack[i] = mt76_rr(dev, 0x89050204 + idx * 4);
	}

	/* start call stack record */
	if (!exception)
		mt76_set(dev, 0x89050200, BIT(0));
}

static void
mt7915_coredump_fw_task(struct mt7915_dev *dev, struct mt7915_coredump *dump)
{
	u32 offs = is_mt7915(&dev->mt76) ? 0xe0 : 0x170;

	strscpy(dump->task_qid, "(task queue id) read, write",
		sizeof(dump->task_qid));

	dump->taskq[0].read = mt76_rr(dev, MT_FW_TASK_QID1);
	dump->taskq[0].write = mt76_rr(dev, MT_FW_TASK_QID1 - 4);
	dump->taskq[1].read = mt76_rr(dev, MT_FW_TASK_QID2);
	dump->taskq[1].write = mt76_rr(dev, MT_FW_TASK_QID2 - 4);

	strscpy(dump->task_info, "(task stack) start, end, size",
		sizeof(dump->task_info));

	dump->taski[0].start = mt76_rr(dev, MT_FW_TASK_START);
	dump->taski[0].end = mt76_rr(dev, MT_FW_TASK_END);
	dump->taski[0].size = mt76_rr(dev, MT_FW_TASK_SIZE);
	dump->taski[1].start = mt76_rr(dev, MT_FW_TASK_START + offs);
	dump->taski[1].end = mt76_rr(dev, MT_FW_TASK_END + offs);
	dump->taski[1].size = mt76_rr(dev, MT_FW_TASK_SIZE + offs);
}

static void
mt7915_coredump_fw_context(struct mt7915_dev *dev, struct mt7915_coredump *dump)
{
	u32 count, idx, id;

	count = mt76_rr(dev, MT_FW_CIRQ_COUNT);

	/* current context */
	if (!count) {
		strscpy(dump->fw_context, "(context) interrupt",
			sizeof(dump->fw_context));

		idx = is_mt7915(&dev->mt76) ?
		      (u32)mt76_get_field(dev, MT_FW_CIRQ_IDX, GENMASK(31, 16)) :
		      (u32)mt76_get_field(dev, MT_FW_CIRQ_IDX, GENMASK(15, 0));
		dump->context.idx = idx;
		dump->context.handler = mt76_rr(dev, MT_FW_CIRQ_LISR);
	} else {
		idx = mt76_rr(dev, MT_FW_TASK_IDX);
		id = mt76_rr(dev, MT_FW_TASK_ID);

		if (!id && idx == 3) {
			strscpy(dump->fw_context, "(context) idle",
				sizeof(dump->fw_context));
		} else if (id && idx != 3) {
			strscpy(dump->fw_context, "(context) task",
				sizeof(dump->fw_context));

			dump->context.idx = idx;
			dump->context.handler = id;
		}
	}
}

static struct mt7915_coredump *mt7915_coredump_build(struct mt7915_dev *dev)
{
	struct mt7915_crash_data *crash_data = dev->coredump.crash_data;
	struct mt7915_coredump *dump;
	struct mt7915_coredump_mem *dump_mem;
	size_t len, sofar = 0, hdr_len = sizeof(*dump);
	unsigned char *buf;
	bool exception;

	len = hdr_len;

	if (coredump_memdump && crash_data->memdump_buf_len)
		len += sizeof(*dump_mem) + crash_data->memdump_buf_len;

	sofar += hdr_len;

	/* this is going to get big when we start dumping memory and such,
	 * so go ahead and use vmalloc.
	 */
	buf = vzalloc(len);
	if (!buf)
		return NULL;

	mutex_lock(&dev->dump_mutex);

	dump = (struct mt7915_coredump *)(buf);
	dump->len = len;

	/* plain text */
	strscpy(dump->magic, "mt76-crash-dump", sizeof(dump->magic));
	strscpy(dump->kernel, init_utsname()->release, sizeof(dump->kernel));
	strscpy(dump->fw_ver, dev->mt76.hw->wiphy->fw_version,
		sizeof(dump->fw_ver));

	guid_copy(&dump->guid, &crash_data->guid);
	dump->tv_sec = crash_data->timestamp.tv_sec;
	dump->tv_nsec = crash_data->timestamp.tv_nsec;
	dump->device_id = mt76_chip(&dev->mt76);

	mt7915_coredump_fw_state(dev, dump, &exception);
	mt7915_coredump_fw_trace(dev, dump, exception);
	mt7915_coredump_fw_task(dev, dump);
	mt7915_coredump_fw_context(dev, dump);
	mt7915_coredump_fw_stack(dev, dump, exception);

	/* gather memory content */
	dump_mem = (struct mt7915_coredump_mem *)(buf + sofar);
	dump_mem->len = crash_data->memdump_buf_len;
	if (coredump_memdump && crash_data->memdump_buf_len)
		memcpy(dump_mem->data, crash_data->memdump_buf,
		       crash_data->memdump_buf_len);

	mutex_unlock(&dev->dump_mutex);

	return dump;
}

int mt7915_coredump_submit(struct mt7915_dev *dev)
{
	struct mt7915_coredump *dump;

	dump = mt7915_coredump_build(dev);
	if (!dump) {
		dev_warn(dev->mt76.dev, "no crash dump data found\n");
		return -ENODATA;
	}

	dev_coredumpv(dev->mt76.dev, dump, dump->len, GFP_KERNEL);

	return 0;
}

int mt7915_coredump_register(struct mt7915_dev *dev)
{
	struct mt7915_crash_data *crash_data;

	crash_data = vzalloc(sizeof(*dev->coredump.crash_data));
	if (!crash_data)
		return -ENOMEM;

	dev->coredump.crash_data = crash_data;

	if (coredump_memdump) {
		crash_data->memdump_buf_len = mt7915_coredump_get_mem_size(dev);
		if (!crash_data->memdump_buf_len)
			/* no memory content */
			return 0;

		crash_data->memdump_buf = vzalloc(crash_data->memdump_buf_len);
		if (!crash_data->memdump_buf) {
			vfree(crash_data);
			return -ENOMEM;
		}
	}

	return 0;
}

void mt7915_coredump_unregister(struct mt7915_dev *dev)
{
	if (dev->coredump.crash_data->memdump_buf) {
		vfree(dev->coredump.crash_data->memdump_buf);
		dev->coredump.crash_data->memdump_buf = NULL;
		dev->coredump.crash_data->memdump_buf_len = 0;
	}

	vfree(dev->coredump.crash_data);
	dev->coredump.crash_data = NULL;
}
+136 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: ISC */
/* Copyright (C) 2022 MediaTek Inc. */

#ifndef _COREDUMP_H_
#define _COREDUMP_H_

#include "mt7915.h"

struct trace {
	u32 id;
	u32 timestamp;
};

struct mt7915_coredump {
	char magic[16];

	u32 len;

	guid_t guid;

	/* time-of-day stamp */
	u64 tv_sec;
	/* time-of-day stamp, nano-seconds */
	u64 tv_nsec;
	/* kernel version */
	char kernel[64];
	/* firmware version */
	char fw_ver[ETHTOOL_FWVERS_LEN];

	u32 device_id;

	/* exception state */
	char fw_state[12];

	u32 last_msg_id;
	u32 eint_info_idx;
	u32 irq_info_idx;
	u32 sched_info_idx;

	/* schedule info */
	char trace_sched[32];
	struct {
		struct trace t;
		u32 pc;
	} sched[60];

	/* irq info */
	char trace_irq[32];
	struct trace irq[60];

	/* task queue status */
	char task_qid[32];
	struct {
		u32 read;
		u32 write;
	} taskq[2];

	/* task stack info */
	char task_info[32];
	struct {
		u32 start;
		u32 end;
		u32 size;
	} taski[2];

	/* firmware context */
	char fw_context[24];
	struct {
		u32 idx;
		u32 handler;
	} context;

	/* link registers calltrace */
	u32 call_stack[16];

	/* memory content */
	u8 data[];
} __packed;

struct mt7915_coredump_mem {
	u32 len;
	u8 data[];
} __packed;

struct mt7915_mem_hdr {
	u32 start;
	u32 len;
	u8 data[];
};

struct mt7915_mem_region {
	u32 start;
	size_t len;

	const char *name;
};

#ifdef CONFIG_DEV_COREDUMP

const struct mt7915_mem_region *
mt7915_coredump_get_mem_layout(struct mt7915_dev *dev, u32 *num);
struct mt7915_crash_data *mt7915_coredump_new(struct mt7915_dev *dev);
int mt7915_coredump_submit(struct mt7915_dev *dev);
int mt7915_coredump_register(struct mt7915_dev *dev);
void mt7915_coredump_unregister(struct mt7915_dev *dev);

#else /* CONFIG_DEV_COREDUMP */

static inline const struct mt7915_mem_region *
mt7915_coredump_get_mem_layout(struct mt7915_dev *dev, u32 *num)
{
	return NULL;
}

static inline int mt7915_coredump_submit(struct mt7915_dev *dev)
{
	return 0;
}

static inline struct mt7915_crash_data *mt7915_coredump_new(struct mt7915_dev *dev)
{
	return NULL;
}

static inline int mt7915_coredump_register(struct mt7915_dev *dev)
{
	return 0;
}

static inline void mt7915_coredump_unregister(struct mt7915_dev *dev)
{
}

#endif /* CONFIG_DEV_COREDUMP */

#endif /* _COREDUMP_H_ */
+11 −1
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
#include "mt7915.h"
#include "mac.h"
#include "mcu.h"
#include "coredump.h"
#include "eeprom.h"

static const struct ieee80211_iface_limit if_limits[] = {
@@ -1100,6 +1101,8 @@ int mt7915_register_device(struct mt7915_dev *dev)

	init_waitqueue_head(&dev->reset_wait);
	INIT_WORK(&dev->reset_work, mt7915_mac_reset_work);
	INIT_WORK(&dev->dump_work, mt7915_mac_dump_work);
	mutex_init(&dev->dump_mutex);

	dev->dbdc_support = mt7915_band_config(dev);

@@ -1142,7 +1145,13 @@ int mt7915_register_device(struct mt7915_dev *dev)

	dev->recovery.hw_init_done = true;

	mt7915_init_debugfs(&dev->phy);
	ret = mt7915_init_debugfs(&dev->phy);
	if (ret)
		goto unreg_thermal;

	ret = mt7915_coredump_register(dev);
	if (ret)
		goto unreg_thermal;

	return 0;

@@ -1161,6 +1170,7 @@ int mt7915_register_device(struct mt7915_dev *dev)
void mt7915_unregister_device(struct mt7915_dev *dev)
{
	mt7915_unregister_ext_phy(dev);
	mt7915_coredump_unregister(dev);
	mt7915_unregister_thermal(&dev->phy);
	mt76_unregister_device(&dev->mt76);
	mt7915_stop_hardware(dev);
Loading