412 lines · c
1// SPDX-License-Identifier: ISC2/* 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 10static bool coredump_memdump;11module_param(coredump_memdump, bool, 0644);12MODULE_PARM_DESC(coredump_memdump, "Optional ability to dump firmware memory");13 14static const struct mt7915_mem_region mt7915_mem_regions[] = {15 {16 .start = 0xe003b400,17 .len = 0x00003bff,18 .name = "CRAM",19 },20};21 22static 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 55static 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 88const struct mt7915_mem_region*89mt7915_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 107static 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 131struct 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 143static void144mt7915_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 166static void167mt7915_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 223static void224mt7915_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 244static void245mt7915_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 268static void269mt7915_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 302static 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 359int 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 374int 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 400void 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