Skip to content

Commit 878161d

Browse files
ryderlee1110nbd168
authored andcommitted
wifi: mt76: mt7996: enable coredump support
Host triggered and catastrophic event triggered firmware core dumping for basic firmware issues triage, including state reporting, function calltrace and MCU memory dump. Signed-off-by: Ryder Lee <[email protected]> Signed-off-by: Felix Fietkau <[email protected]>
1 parent 672662f commit 878161d

File tree

9 files changed

+496
-2
lines changed

9 files changed

+496
-2
lines changed

drivers/net/wireless/mediatek/mt76/mt7996/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
config MT7996E
33
tristate "MediaTek MT7996 (PCIe) support"
44
select MT76_CONNAC_LIB
5+
select WANT_DEV_COREDUMP
56
select RELAY
67
depends on MAC80211
78
depends on PCI

drivers/net/wireless/mediatek/mt76/mt7996/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ obj-$(CONFIG_MT7996E) += mt7996e.o
44

55
mt7996e-y := pci.o init.o dma.o eeprom.o main.o mcu.o mac.o \
66
debugfs.o mmio.o
7+
8+
mt7996e-$(CONFIG_DEV_COREDUMP) += coredump.o
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
// SPDX-License-Identifier: ISC
2+
/* Copyright (C) 2023 MediaTek Inc. */
3+
4+
#include <linux/devcoredump.h>
5+
#include <linux/kernel.h>
6+
#include <linux/types.h>
7+
#include <linux/utsname.h>
8+
#include "coredump.h"
9+
10+
static bool coredump_memdump;
11+
module_param(coredump_memdump, bool, 0644);
12+
MODULE_PARM_DESC(coredump_memdump, "Optional ability to dump firmware memory");
13+
14+
static const struct mt7996_mem_region mt7996_mem_regions[] = {
15+
{
16+
.start = 0x00800000,
17+
.len = 0x0004ffff,
18+
.name = "ULM0",
19+
},
20+
{
21+
.start = 0x00900000,
22+
.len = 0x00037fff,
23+
.name = "ULM1",
24+
},
25+
{
26+
.start = 0x02200000,
27+
.len = 0x0003ffff,
28+
.name = "ULM2",
29+
},
30+
{
31+
.start = 0x00400000,
32+
.len = 0x00067fff,
33+
.name = "SRAM",
34+
},
35+
{
36+
.start = 0xe0000000,
37+
.len = 0x0015ffff,
38+
.name = "CRAM0",
39+
},
40+
{
41+
.start = 0xe0160000,
42+
.len = 0x0011bfff,
43+
.name = "CRAM1",
44+
},
45+
};
46+
47+
const struct mt7996_mem_region*
48+
mt7996_coredump_get_mem_layout(struct mt7996_dev *dev, u32 *num)
49+
{
50+
switch (mt76_chip(&dev->mt76)) {
51+
case 0x7990:
52+
case 0x7991:
53+
*num = ARRAY_SIZE(mt7996_mem_regions);
54+
return &mt7996_mem_regions[0];
55+
default:
56+
return NULL;
57+
}
58+
}
59+
60+
static int mt7996_coredump_get_mem_size(struct mt7996_dev *dev)
61+
{
62+
const struct mt7996_mem_region *mem_region;
63+
size_t size = 0;
64+
u32 num;
65+
int i;
66+
67+
mem_region = mt7996_coredump_get_mem_layout(dev, &num);
68+
if (!mem_region)
69+
return 0;
70+
71+
for (i = 0; i < num; i++) {
72+
size += mem_region->len;
73+
mem_region++;
74+
}
75+
76+
/* reserve space for the headers */
77+
size += num * sizeof(struct mt7996_mem_hdr);
78+
/* make sure it is aligned 4 bytes for debug message print out */
79+
size = ALIGN(size, 4);
80+
81+
return size;
82+
}
83+
84+
struct mt7996_crash_data *mt7996_coredump_new(struct mt7996_dev *dev)
85+
{
86+
struct mt7996_crash_data *crash_data = dev->coredump.crash_data;
87+
88+
lockdep_assert_held(&dev->dump_mutex);
89+
90+
if (coredump_memdump &&
91+
!mt76_poll_msec(dev, MT_FW_DUMP_STATE, 0x3, 0x2, 500))
92+
return NULL;
93+
94+
guid_gen(&crash_data->guid);
95+
ktime_get_real_ts64(&crash_data->timestamp);
96+
97+
return crash_data;
98+
}
99+
100+
static void
101+
mt7996_coredump_fw_state(struct mt7996_dev *dev, struct mt7996_coredump *dump,
102+
bool *exception)
103+
{
104+
u32 count;
105+
106+
count = mt76_rr(dev, MT_FW_ASSERT_CNT);
107+
108+
/* normal mode: driver can manually trigger assert for detail info */
109+
if (!count)
110+
strscpy(dump->fw_state, "normal", sizeof(dump->fw_state));
111+
else
112+
strscpy(dump->fw_state, "exception", sizeof(dump->fw_state));
113+
114+
*exception = !!count;
115+
}
116+
117+
static void
118+
mt7996_coredump_fw_stack(struct mt7996_dev *dev, struct mt7996_coredump *dump,
119+
bool exception)
120+
{
121+
u32 oldest, i, idx;
122+
123+
strscpy(dump->pc_current, "program counter", sizeof(dump->pc_current));
124+
125+
/* 0: WM PC log output */
126+
mt76_wr(dev, MT_CONN_DBG_CTL_OUT_SEL, 0);
127+
/* choose 33th PC log buffer to read current PC index */
128+
mt76_wr(dev, MT_CONN_DBG_CTL_PC_LOG_SEL, 0x3f);
129+
130+
/* read current PC */
131+
dump->pc_stack[0] = mt76_rr(dev, MT_CONN_DBG_CTL_PC_LOG);
132+
133+
/* stop call stack record */
134+
if (!exception) {
135+
mt76_clear(dev, MT_MCU_WM_EXCP_PC_CTRL, BIT(0));
136+
mt76_clear(dev, MT_MCU_WM_EXCP_LR_CTRL, BIT(0));
137+
}
138+
139+
oldest = (u32)mt76_get_field(dev, MT_MCU_WM_EXCP_PC_CTRL,
140+
GENMASK(20, 16)) + 2;
141+
for (i = 0; i < 16; i++) {
142+
idx = ((oldest + 2 * i + 1) % 32);
143+
dump->pc_stack[i + 1] =
144+
mt76_rr(dev, MT_MCU_WM_EXCP_PC_LOG + idx * 4);
145+
}
146+
147+
oldest = (u32)mt76_get_field(dev, MT_MCU_WM_EXCP_LR_CTRL,
148+
GENMASK(20, 16)) + 2;
149+
for (i = 0; i < 16; i++) {
150+
idx = ((oldest + 2 * i + 1) % 32);
151+
dump->lr_stack[i] =
152+
mt76_rr(dev, MT_MCU_WM_EXCP_LR_LOG + idx * 4);
153+
}
154+
155+
/* start call stack record */
156+
if (!exception) {
157+
mt76_set(dev, MT_MCU_WM_EXCP_PC_CTRL, BIT(0));
158+
mt76_set(dev, MT_MCU_WM_EXCP_LR_CTRL, BIT(0));
159+
}
160+
}
161+
162+
static struct mt7996_coredump *mt7996_coredump_build(struct mt7996_dev *dev)
163+
{
164+
struct mt7996_crash_data *crash_data = dev->coredump.crash_data;
165+
struct mt7996_coredump *dump;
166+
struct mt7996_coredump_mem *dump_mem;
167+
size_t len, sofar = 0, hdr_len = sizeof(*dump);
168+
unsigned char *buf;
169+
bool exception;
170+
171+
len = hdr_len;
172+
173+
if (coredump_memdump && crash_data->memdump_buf_len)
174+
len += sizeof(*dump_mem) + crash_data->memdump_buf_len;
175+
176+
sofar += hdr_len;
177+
178+
/* this is going to get big when we start dumping memory and such,
179+
* so go ahead and use vmalloc.
180+
*/
181+
buf = vzalloc(len);
182+
if (!buf)
183+
return NULL;
184+
185+
mutex_lock(&dev->dump_mutex);
186+
187+
dump = (struct mt7996_coredump *)(buf);
188+
dump->len = len;
189+
190+
/* plain text */
191+
strscpy(dump->magic, "mt76-crash-dump", sizeof(dump->magic));
192+
strscpy(dump->kernel, init_utsname()->release, sizeof(dump->kernel));
193+
strscpy(dump->fw_ver, dev->mt76.hw->wiphy->fw_version,
194+
sizeof(dump->fw_ver));
195+
196+
guid_copy(&dump->guid, &crash_data->guid);
197+
dump->tv_sec = crash_data->timestamp.tv_sec;
198+
dump->tv_nsec = crash_data->timestamp.tv_nsec;
199+
dump->device_id = mt76_chip(&dev->mt76);
200+
201+
mt7996_coredump_fw_state(dev, dump, &exception);
202+
mt7996_coredump_fw_stack(dev, dump, exception);
203+
204+
/* gather memory content */
205+
dump_mem = (struct mt7996_coredump_mem *)(buf + sofar);
206+
dump_mem->len = crash_data->memdump_buf_len;
207+
if (coredump_memdump && crash_data->memdump_buf_len)
208+
memcpy(dump_mem->data, crash_data->memdump_buf,
209+
crash_data->memdump_buf_len);
210+
211+
mutex_unlock(&dev->dump_mutex);
212+
213+
return dump;
214+
}
215+
216+
int mt7996_coredump_submit(struct mt7996_dev *dev)
217+
{
218+
struct mt7996_coredump *dump;
219+
220+
dump = mt7996_coredump_build(dev);
221+
if (!dump) {
222+
dev_warn(dev->mt76.dev, "no crash dump data found\n");
223+
return -ENODATA;
224+
}
225+
226+
dev_coredumpv(dev->mt76.dev, dump, dump->len, GFP_KERNEL);
227+
228+
return 0;
229+
}
230+
231+
int mt7996_coredump_register(struct mt7996_dev *dev)
232+
{
233+
struct mt7996_crash_data *crash_data;
234+
235+
crash_data = vzalloc(sizeof(*dev->coredump.crash_data));
236+
if (!crash_data)
237+
return -ENOMEM;
238+
239+
dev->coredump.crash_data = crash_data;
240+
241+
if (coredump_memdump) {
242+
crash_data->memdump_buf_len = mt7996_coredump_get_mem_size(dev);
243+
if (!crash_data->memdump_buf_len)
244+
/* no memory content */
245+
return 0;
246+
247+
crash_data->memdump_buf = vzalloc(crash_data->memdump_buf_len);
248+
if (!crash_data->memdump_buf) {
249+
vfree(crash_data);
250+
return -ENOMEM;
251+
}
252+
}
253+
254+
return 0;
255+
}
256+
257+
void mt7996_coredump_unregister(struct mt7996_dev *dev)
258+
{
259+
if (dev->coredump.crash_data->memdump_buf) {
260+
vfree(dev->coredump.crash_data->memdump_buf);
261+
dev->coredump.crash_data->memdump_buf = NULL;
262+
dev->coredump.crash_data->memdump_buf_len = 0;
263+
}
264+
265+
vfree(dev->coredump.crash_data);
266+
dev->coredump.crash_data = NULL;
267+
}
268+
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/* SPDX-License-Identifier: ISC */
2+
/* Copyright (C) 2023 MediaTek Inc. */
3+
4+
#ifndef _COREDUMP_H_
5+
#define _COREDUMP_H_
6+
7+
#include "mt7996.h"
8+
9+
struct mt7996_coredump {
10+
char magic[16];
11+
12+
u32 len;
13+
14+
guid_t guid;
15+
16+
/* time-of-day stamp */
17+
u64 tv_sec;
18+
/* time-of-day stamp, nano-seconds */
19+
u64 tv_nsec;
20+
/* kernel version */
21+
char kernel[64];
22+
/* firmware version */
23+
char fw_ver[ETHTOOL_FWVERS_LEN];
24+
25+
u32 device_id;
26+
27+
/* exception state */
28+
char fw_state[12];
29+
30+
/* program counters */
31+
char pc_current[16];
32+
u32 pc_stack[17];
33+
/* link registers */
34+
u32 lr_stack[16];
35+
36+
/* memory content */
37+
u8 data[];
38+
} __packed;
39+
40+
struct mt7996_coredump_mem {
41+
u32 len;
42+
u8 data[];
43+
} __packed;
44+
45+
struct mt7996_mem_hdr {
46+
u32 start;
47+
u32 len;
48+
u8 data[];
49+
};
50+
51+
struct mt7996_mem_region {
52+
u32 start;
53+
size_t len;
54+
55+
const char *name;
56+
};
57+
58+
#ifdef CONFIG_DEV_COREDUMP
59+
60+
const struct mt7996_mem_region *
61+
mt7996_coredump_get_mem_layout(struct mt7996_dev *dev, u32 *num);
62+
struct mt7996_crash_data *mt7996_coredump_new(struct mt7996_dev *dev);
63+
int mt7996_coredump_submit(struct mt7996_dev *dev);
64+
int mt7996_coredump_register(struct mt7996_dev *dev);
65+
void mt7996_coredump_unregister(struct mt7996_dev *dev);
66+
67+
#else /* CONFIG_DEV_COREDUMP */
68+
69+
static inline const struct mt7996_mem_region *
70+
mt7996_coredump_get_mem_layout(struct mt7996_dev *dev, u32 *num)
71+
{
72+
return NULL;
73+
}
74+
75+
static inline int mt7996_coredump_submit(struct mt7996_dev *dev)
76+
{
77+
return 0;
78+
}
79+
80+
static inline struct
81+
mt7996_crash_data *mt7996_coredump_new(struct mt7996_dev *dev)
82+
{
83+
return NULL;
84+
}
85+
86+
static inline int mt7996_coredump_register(struct mt7996_dev *dev)
87+
{
88+
return 0;
89+
}
90+
91+
static inline void mt7996_coredump_unregister(struct mt7996_dev *dev)
92+
{
93+
}
94+
95+
#endif /* CONFIG_DEV_COREDUMP */
96+
97+
#endif /* _COREDUMP_H_ */

0 commit comments

Comments
 (0)