269 lines · c
1// SPDX-License-Identifier: ISC2/* 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 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 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 47const struct mt7996_mem_region*48mt7996_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 60static 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 84struct 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 100static void101mt7996_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 else112 strscpy(dump->fw_state, "exception", sizeof(dump->fw_state));113 114 *exception = !!count;115}116 117static void118mt7996_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 162static 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 216int 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 231int 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 257void 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