1 // SPDX-License-Identifier: ISC
2 /* Copyright (C) 2022 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 mt7915_mem_region mt7915_mem_regions[] = {
15 {
16 .start = 0xe003b400,
17 .len = 0x00003bff,
18 .name = "CRAM",
19 },
20 };
21
22 static const struct mt7915_mem_region mt7916_mem_regions[] = {
23 {
24 .start = 0x00800000,
25 .len = 0x0005ffff,
26 .name = "ROM",
27 },
28 {
29 .start = 0x00900000,
30 .len = 0x00013fff,
31 .name = "ULM1",
32 },
33 {
34 .start = 0x02200000,
35 .len = 0x0004ffff,
36 .name = "ULM2",
37 },
38 {
39 .start = 0x02300000,
40 .len = 0x0004ffff,
41 .name = "ULM3",
42 },
43 {
44 .start = 0x00400000,
45 .len = 0x00027fff,
46 .name = "SRAM",
47 },
48 {
49 .start = 0xe0000000,
50 .len = 0x00157fff,
51 .name = "CRAM",
52 },
53 };
54
55 static const struct mt7915_mem_region mt798x_mem_regions[] = {
56 {
57 .start = 0x00800000,
58 .len = 0x0005ffff,
59 .name = "ROM",
60 },
61 {
62 .start = 0x00900000,
63 .len = 0x0000ffff,
64 .name = "ULM1",
65 },
66 {
67 .start = 0x02200000,
68 .len = 0x0004ffff,
69 .name = "ULM2",
70 },
71 {
72 .start = 0x02300000,
73 .len = 0x0004ffff,
74 .name = "ULM3",
75 },
76 {
77 .start = 0x00400000,
78 .len = 0x00017fff,
79 .name = "SRAM",
80 },
81 {
82 .start = 0xe0000000,
83 .len = 0x00113fff,
84 .name = "CRAM",
85 },
86 };
87
88 const struct mt7915_mem_region*
mt7915_coredump_get_mem_layout(struct mt7915_dev * dev,u32 * num)89 mt7915_coredump_get_mem_layout(struct mt7915_dev *dev, u32 *num)
90 {
91 switch (mt76_chip(&dev->mt76)) {
92 case 0x7915:
93 *num = ARRAY_SIZE(mt7915_mem_regions);
94 return &mt7915_mem_regions[0];
95 case 0x7981:
96 case 0x7986:
97 *num = ARRAY_SIZE(mt798x_mem_regions);
98 return &mt798x_mem_regions[0];
99 case 0x7916:
100 *num = ARRAY_SIZE(mt7916_mem_regions);
101 return &mt7916_mem_regions[0];
102 default:
103 return NULL;
104 }
105 }
106
mt7915_coredump_get_mem_size(struct mt7915_dev * dev)107 static int mt7915_coredump_get_mem_size(struct mt7915_dev *dev)
108 {
109 const struct mt7915_mem_region *mem_region;
110 size_t size = 0;
111 u32 num;
112 int i;
113
114 mem_region = mt7915_coredump_get_mem_layout(dev, &num);
115 if (!mem_region)
116 return 0;
117
118 for (i = 0; i < num; i++) {
119 size += mem_region->len;
120 mem_region++;
121 }
122
123 /* reserve space for the headers */
124 size += num * sizeof(struct mt7915_mem_hdr);
125 /* make sure it is aligned 4 bytes for debug message print out */
126 size = ALIGN(size, 4);
127
128 return size;
129 }
130
mt7915_coredump_new(struct mt7915_dev * dev)131 struct mt7915_crash_data *mt7915_coredump_new(struct mt7915_dev *dev)
132 {
133 struct mt7915_crash_data *crash_data = dev->coredump.crash_data;
134
135 lockdep_assert_held(&dev->dump_mutex);
136
137 guid_gen(&crash_data->guid);
138 ktime_get_real_ts64(&crash_data->timestamp);
139
140 return crash_data;
141 }
142
143 static void
mt7915_coredump_fw_state(struct mt7915_dev * dev,struct mt7915_coredump * dump,bool * exception)144 mt7915_coredump_fw_state(struct mt7915_dev *dev, struct mt7915_coredump *dump,
145 bool *exception)
146 {
147 u32 state, count, type;
148
149 type = (u32)mt76_get_field(dev, MT_FW_EXCEPT_TYPE, GENMASK(7, 0));
150 state = (u32)mt76_get_field(dev, MT_FW_ASSERT_STAT, GENMASK(7, 0));
151 count = is_mt7915(&dev->mt76) ?
152 (u32)mt76_get_field(dev, MT_FW_EXCEPT_COUNT, GENMASK(15, 8)) :
153 (u32)mt76_get_field(dev, MT_FW_EXCEPT_COUNT, GENMASK(7, 0));
154
155 /* normal mode: driver can manually trigger assert for detail info */
156 if (!count)
157 strscpy(dump->fw_state, "normal", sizeof(dump->fw_state));
158 else if (state > 1 && (count == 1) && type == 5)
159 strscpy(dump->fw_state, "assert", sizeof(dump->fw_state));
160 else if ((state > 1 && count == 1) || count > 1)
161 strscpy(dump->fw_state, "exception", sizeof(dump->fw_state));
162
163 *exception = !!count;
164 }
165
166 static void
mt7915_coredump_fw_trace(struct mt7915_dev * dev,struct mt7915_coredump * dump,bool exception)167 mt7915_coredump_fw_trace(struct mt7915_dev *dev, struct mt7915_coredump *dump,
168 bool exception)
169 {
170 u32 n, irq, sch, base = MT_FW_EINT_INFO;
171
172 /* trap or run? */
173 dump->last_msg_id = mt76_rr(dev, MT_FW_LAST_MSG_ID);
174
175 n = is_mt7915(&dev->mt76) ?
176 (u32)mt76_get_field(dev, base, GENMASK(7, 0)) :
177 (u32)mt76_get_field(dev, base, GENMASK(15, 8));
178 dump->eint_info_idx = n;
179
180 irq = mt76_rr(dev, base + 0x8);
181 n = is_mt7915(&dev->mt76) ?
182 FIELD_GET(GENMASK(7, 0), irq) : FIELD_GET(GENMASK(23, 16), irq);
183 dump->irq_info_idx = n;
184
185 sch = mt76_rr(dev, MT_FW_SCHED_INFO);
186 n = is_mt7915(&dev->mt76) ?
187 FIELD_GET(GENMASK(7, 0), sch) : FIELD_GET(GENMASK(15, 8), sch);
188 dump->sched_info_idx = n;
189
190 if (exception) {
191 u32 i, y;
192
193 /* sched trace */
194 n = is_mt7915(&dev->mt76) ?
195 FIELD_GET(GENMASK(15, 8), sch) : FIELD_GET(GENMASK(7, 0), sch);
196 n = n > 60 ? 60 : n;
197
198 strscpy(dump->trace_sched, "(sched_info) id, time",
199 sizeof(dump->trace_sched));
200
201 for (y = dump->sched_info_idx, i = 0; i < n; i++, y++) {
202 mt7915_memcpy_fromio(dev, dump->sched, base + 0xc + y * 12,
203 sizeof(dump->sched));
204 y = y >= n ? 0 : y;
205 }
206
207 /* irq trace */
208 n = is_mt7915(&dev->mt76) ?
209 FIELD_GET(GENMASK(15, 8), irq) : FIELD_GET(GENMASK(7, 0), irq);
210 n = n > 60 ? 60 : n;
211
212 strscpy(dump->trace_irq, "(irq_info) id, time",
213 sizeof(dump->trace_irq));
214
215 for (y = dump->irq_info_idx, i = 0; i < n; i++, y++) {
216 mt7915_memcpy_fromio(dev, dump->irq, base + 0x4 + y * 16,
217 sizeof(dump->irq));
218 y = y >= n ? 0 : y;
219 }
220 }
221 }
222
223 static void
mt7915_coredump_fw_stack(struct mt7915_dev * dev,struct mt7915_coredump * dump,bool exception)224 mt7915_coredump_fw_stack(struct mt7915_dev *dev, struct mt7915_coredump *dump,
225 bool exception)
226 {
227 u32 oldest, i, idx;
228
229 /* stop call stack record */
230 if (!exception)
231 mt76_clear(dev, 0x89050200, BIT(0));
232
233 oldest = (u32)mt76_get_field(dev, 0x89050200, GENMASK(20, 16)) + 2;
234 for (i = 0; i < 16; i++) {
235 idx = ((oldest + 2 * i + 1) % 32);
236 dump->call_stack[i] = mt76_rr(dev, 0x89050204 + idx * 4);
237 }
238
239 /* start call stack record */
240 if (!exception)
241 mt76_set(dev, 0x89050200, BIT(0));
242 }
243
244 static void
mt7915_coredump_fw_task(struct mt7915_dev * dev,struct mt7915_coredump * dump)245 mt7915_coredump_fw_task(struct mt7915_dev *dev, struct mt7915_coredump *dump)
246 {
247 u32 offs = is_mt7915(&dev->mt76) ? 0xe0 : 0x170;
248
249 strscpy(dump->task_qid, "(task queue id) read, write",
250 sizeof(dump->task_qid));
251
252 dump->taskq[0].read = mt76_rr(dev, MT_FW_TASK_QID1);
253 dump->taskq[0].write = mt76_rr(dev, MT_FW_TASK_QID1 - 4);
254 dump->taskq[1].read = mt76_rr(dev, MT_FW_TASK_QID2);
255 dump->taskq[1].write = mt76_rr(dev, MT_FW_TASK_QID2 - 4);
256
257 strscpy(dump->task_info, "(task stack) start, end, size",
258 sizeof(dump->task_info));
259
260 dump->taski[0].start = mt76_rr(dev, MT_FW_TASK_START);
261 dump->taski[0].end = mt76_rr(dev, MT_FW_TASK_END);
262 dump->taski[0].size = mt76_rr(dev, MT_FW_TASK_SIZE);
263 dump->taski[1].start = mt76_rr(dev, MT_FW_TASK_START + offs);
264 dump->taski[1].end = mt76_rr(dev, MT_FW_TASK_END + offs);
265 dump->taski[1].size = mt76_rr(dev, MT_FW_TASK_SIZE + offs);
266 }
267
268 static void
mt7915_coredump_fw_context(struct mt7915_dev * dev,struct mt7915_coredump * dump)269 mt7915_coredump_fw_context(struct mt7915_dev *dev, struct mt7915_coredump *dump)
270 {
271 u32 count, idx, id;
272
273 count = mt76_rr(dev, MT_FW_CIRQ_COUNT);
274
275 /* current context */
276 if (!count) {
277 strscpy(dump->fw_context, "(context) interrupt",
278 sizeof(dump->fw_context));
279
280 idx = is_mt7915(&dev->mt76) ?
281 (u32)mt76_get_field(dev, MT_FW_CIRQ_IDX, GENMASK(31, 16)) :
282 (u32)mt76_get_field(dev, MT_FW_CIRQ_IDX, GENMASK(15, 0));
283 dump->context.idx = idx;
284 dump->context.handler = mt76_rr(dev, MT_FW_CIRQ_LISR);
285 } else {
286 idx = mt76_rr(dev, MT_FW_TASK_IDX);
287 id = mt76_rr(dev, MT_FW_TASK_ID);
288
289 if (!id && idx == 3) {
290 strscpy(dump->fw_context, "(context) idle",
291 sizeof(dump->fw_context));
292 } else if (id && idx != 3) {
293 strscpy(dump->fw_context, "(context) task",
294 sizeof(dump->fw_context));
295
296 dump->context.idx = idx;
297 dump->context.handler = id;
298 }
299 }
300 }
301
mt7915_coredump_build(struct mt7915_dev * dev)302 static struct mt7915_coredump *mt7915_coredump_build(struct mt7915_dev *dev)
303 {
304 struct mt7915_crash_data *crash_data = dev->coredump.crash_data;
305 struct mt7915_coredump *dump;
306 struct mt7915_coredump_mem *dump_mem;
307 size_t len, sofar = 0, hdr_len = sizeof(*dump);
308 unsigned char *buf;
309 bool exception;
310
311 len = hdr_len;
312
313 if (coredump_memdump && crash_data->memdump_buf_len)
314 len += sizeof(*dump_mem) + crash_data->memdump_buf_len;
315
316 sofar += hdr_len;
317
318 /* this is going to get big when we start dumping memory and such,
319 * so go ahead and use vmalloc.
320 */
321 buf = vzalloc(len);
322 if (!buf)
323 return NULL;
324
325 mutex_lock(&dev->dump_mutex);
326
327 dump = (struct mt7915_coredump *)(buf);
328 dump->len = len;
329
330 /* plain text */
331 strscpy(dump->magic, "mt76-crash-dump", sizeof(dump->magic));
332 strscpy(dump->kernel, init_utsname()->release, sizeof(dump->kernel));
333 strscpy(dump->fw_ver, dev->mt76.hw->wiphy->fw_version,
334 sizeof(dump->fw_ver));
335
336 guid_copy(&dump->guid, &crash_data->guid);
337 dump->tv_sec = crash_data->timestamp.tv_sec;
338 dump->tv_nsec = crash_data->timestamp.tv_nsec;
339 dump->device_id = mt76_chip(&dev->mt76);
340
341 mt7915_coredump_fw_state(dev, dump, &exception);
342 mt7915_coredump_fw_trace(dev, dump, exception);
343 mt7915_coredump_fw_task(dev, dump);
344 mt7915_coredump_fw_context(dev, dump);
345 mt7915_coredump_fw_stack(dev, dump, exception);
346
347 /* gather memory content */
348 dump_mem = (struct mt7915_coredump_mem *)(buf + sofar);
349 dump_mem->len = crash_data->memdump_buf_len;
350 if (coredump_memdump && crash_data->memdump_buf_len)
351 memcpy(dump_mem->data, crash_data->memdump_buf,
352 crash_data->memdump_buf_len);
353
354 mutex_unlock(&dev->dump_mutex);
355
356 return dump;
357 }
358
mt7915_coredump_submit(struct mt7915_dev * dev)359 int mt7915_coredump_submit(struct mt7915_dev *dev)
360 {
361 struct mt7915_coredump *dump;
362
363 dump = mt7915_coredump_build(dev);
364 if (!dump) {
365 dev_warn(dev->mt76.dev, "no crash dump data found\n");
366 return -ENODATA;
367 }
368
369 dev_coredumpv(dev->mt76.dev, dump, dump->len, GFP_KERNEL);
370
371 return 0;
372 }
373
mt7915_coredump_register(struct mt7915_dev * dev)374 int mt7915_coredump_register(struct mt7915_dev *dev)
375 {
376 struct mt7915_crash_data *crash_data;
377
378 crash_data = vzalloc(sizeof(*dev->coredump.crash_data));
379 if (!crash_data)
380 return -ENOMEM;
381
382 dev->coredump.crash_data = crash_data;
383
384 if (coredump_memdump) {
385 crash_data->memdump_buf_len = mt7915_coredump_get_mem_size(dev);
386 if (!crash_data->memdump_buf_len)
387 /* no memory content */
388 return 0;
389
390 crash_data->memdump_buf = vzalloc(crash_data->memdump_buf_len);
391 if (!crash_data->memdump_buf) {
392 vfree(crash_data);
393 return -ENOMEM;
394 }
395 }
396
397 return 0;
398 }
399
mt7915_coredump_unregister(struct mt7915_dev * dev)400 void mt7915_coredump_unregister(struct mt7915_dev *dev)
401 {
402 if (dev->coredump.crash_data->memdump_buf) {
403 vfree(dev->coredump.crash_data->memdump_buf);
404 dev->coredump.crash_data->memdump_buf = NULL;
405 dev->coredump.crash_data->memdump_buf_len = 0;
406 }
407
408 vfree(dev->coredump.crash_data);
409 dev->coredump.crash_data = NULL;
410 }
411
412