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*
mt7996_coredump_get_mem_layout(struct mt7996_dev * dev,u32 * num)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
mt7996_coredump_get_mem_size(struct mt7996_dev * dev)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
mt7996_coredump_new(struct mt7996_dev * dev)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
mt7996_coredump_fw_state(struct mt7996_dev * dev,struct mt7996_coredump * dump,bool * exception)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
mt7996_coredump_fw_stack(struct mt7996_dev * dev,struct mt7996_coredump * dump,bool exception)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
mt7996_coredump_build(struct mt7996_dev * dev)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
mt7996_coredump_submit(struct mt7996_dev * dev)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
mt7996_coredump_register(struct mt7996_dev * dev)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
mt7996_coredump_unregister(struct mt7996_dev * dev)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
269