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