Commit | Line | Data |
---|---|---|
dd101ca5 DC |
1 | /* |
2 | * Copyright (c) 2013 Samsung Electronics Co., Ltd. | |
3 | * http://www.samsung.com | |
4 | * | |
5 | * Debug-SnapShot: Debug Framework for Ramdump based debugging method | |
6 | * The original code is Exynos-Snapshot for Exynos SoC | |
7 | * | |
8 | * Author: Hosung Kim <hosung0.kim@samsung.com> | |
9 | * Author: Changki Kim <changki.kim@samsung.com> | |
10 | * | |
11 | * This program is free software; you can redistribute it and/or modify | |
12 | * it under the terms of the GNU General Public License version 2 as | |
13 | * published by the Free Software Foundation. | |
14 | */ | |
15 | ||
16 | #include <linux/kernel.h> | |
17 | #include <linux/io.h> | |
18 | #include <linux/notifier.h> | |
19 | #include <linux/reboot.h> | |
20 | #include <linux/delay.h> | |
21 | #include <linux/kallsyms.h> | |
22 | #include <linux/input.h> | |
23 | #include <linux/smc.h> | |
24 | #include <linux/bitops.h> | |
25 | #include <linux/sched/clock.h> | |
26 | #include <linux/sched/debug.h> | |
27 | #include <linux/nmi.h> | |
28 | #include <linux/init_task.h> | |
29 | #include <linux/ftrace.h> | |
30 | ||
31 | #include <asm/cputype.h> | |
32 | #include <asm/smp_plat.h> | |
33 | #include <asm/core_regs.h> | |
dd101ca5 DC |
34 | |
35 | #include "debug-snapshot-local.h" | |
36 | #include <linux/debug-snapshot-helper.h> | |
37 | ||
38 | static void dbg_snapshot_soc_dummy_func(void *dummy) {return;} | |
39 | static int dbg_snapshot_soc_dummy_func_int(void *dummy) {return 0;} | |
40 | static int dbg_snapshot_soc_dummy_func_smc(unsigned long dummy1, | |
41 | unsigned long dummy2, | |
42 | unsigned long dummy3, | |
43 | unsigned long dummy4) {return 0;} | |
44 | ||
45 | static struct dbg_snapshot_helper_ops dss_soc_dummy_ops = { | |
46 | .soc_early_panic = dbg_snapshot_soc_dummy_func, | |
47 | .soc_prepare_panic_entry = dbg_snapshot_soc_dummy_func, | |
48 | .soc_prepare_panic_exit = dbg_snapshot_soc_dummy_func, | |
dd101ca5 DC |
49 | .soc_post_panic_entry = dbg_snapshot_soc_dummy_func, |
50 | .soc_post_panic_exit = dbg_snapshot_soc_dummy_func, | |
6b666fd8 HK |
51 | .soc_post_reboot_entry = dbg_snapshot_soc_dummy_func, |
52 | .soc_post_reboot_exit = dbg_snapshot_soc_dummy_func, | |
53 | .soc_save_context_entry = dbg_snapshot_soc_dummy_func, | |
54 | .soc_save_context_exit = dbg_snapshot_soc_dummy_func, | |
55 | .soc_save_core = dbg_snapshot_soc_dummy_func, | |
56 | .soc_save_system = dbg_snapshot_soc_dummy_func, | |
57 | .soc_dump_info = dbg_snapshot_soc_dummy_func, | |
dd101ca5 DC |
58 | .soc_start_watchdog = dbg_snapshot_soc_dummy_func, |
59 | .soc_expire_watchdog = dbg_snapshot_soc_dummy_func, | |
60 | .soc_stop_watchdog = dbg_snapshot_soc_dummy_func, | |
61 | .soc_kick_watchdog = dbg_snapshot_soc_dummy_func, | |
62 | .soc_is_power_cpu = dbg_snapshot_soc_dummy_func_int, | |
63 | .soc_smc_call = dbg_snapshot_soc_dummy_func_smc, | |
64 | }; | |
65 | ||
66 | struct dbg_snapshot_helper_ops *dss_soc_ops; | |
67 | ||
68 | void __iomem *dbg_snapshot_get_base_vaddr(void) | |
69 | { | |
70 | return (void __iomem *)(dss_base.vaddr); | |
71 | } | |
72 | ||
73 | void __iomem *dbg_snapshot_get_base_paddr(void) | |
74 | { | |
75 | return (void __iomem *)(dss_base.paddr); | |
76 | } | |
77 | ||
78 | static void dbg_snapshot_set_core_power_stat(unsigned int val, unsigned cpu) | |
79 | { | |
80 | if (dbg_snapshot_get_enable("header")) | |
81 | __raw_writel(val, (dbg_snapshot_get_base_vaddr() + | |
82 | DSS_OFFSET_CORE_POWER_STAT + cpu * 4)); | |
83 | } | |
84 | ||
85 | unsigned int dbg_snapshot_get_core_panic_stat(unsigned cpu) | |
86 | { | |
87 | if (dbg_snapshot_get_enable("header")) | |
88 | return __raw_readl(dbg_snapshot_get_base_vaddr() + | |
89 | DSS_OFFSET_PANIC_STAT + cpu * 4); | |
90 | else | |
91 | return 0; | |
92 | } | |
93 | ||
94 | void dbg_snapshot_set_core_panic_stat(unsigned int val, unsigned cpu) | |
95 | { | |
96 | if (dbg_snapshot_get_enable("header")) | |
97 | __raw_writel(val, (dbg_snapshot_get_base_vaddr() + | |
98 | DSS_OFFSET_PANIC_STAT + cpu * 4)); | |
99 | } | |
100 | ||
101 | static void dbg_snapshot_report_reason(unsigned int val) | |
102 | { | |
103 | if (dbg_snapshot_get_enable("header")) | |
104 | __raw_writel(val, dbg_snapshot_get_base_vaddr() + DSS_OFFSET_EMERGENCY_REASON); | |
105 | } | |
106 | ||
1abf4739 HK |
107 | int dbg_snapshot_get_debug_level_reg(void) |
108 | { | |
2b577af2 | 109 | int ret = DSS_DEBUG_LEVEL_MID; |
1abf4739 HK |
110 | |
111 | if (dbg_snapshot_get_enable("header")) { | |
112 | int val = __raw_readl(dbg_snapshot_get_base_vaddr() + DSS_OFFSET_DEBUG_LEVEL); | |
113 | ||
114 | if ((val & GENMASK(31, 16)) == DSS_DEBUG_LEVEL_PREFIX) | |
115 | ret = val & GENMASK(15, 0); | |
116 | } | |
117 | ||
118 | return ret; | |
119 | } | |
120 | ||
dd101ca5 DC |
121 | void dbg_snapshot_scratch_reg(unsigned int val) |
122 | { | |
123 | if (dbg_snapshot_get_enable("header")) | |
124 | __raw_writel(val, dbg_snapshot_get_base_vaddr() + DSS_OFFSET_SCRATCH); | |
125 | } | |
126 | ||
127 | bool dbg_snapshot_is_scratch(void) | |
128 | { | |
129 | return __raw_readl(dbg_snapshot_get_base_vaddr() + | |
130 | DSS_OFFSET_SCRATCH) == DSS_SIGN_SCRATCH; | |
131 | } | |
132 | ||
133 | unsigned long dbg_snapshot_get_last_pc_paddr(void) | |
134 | { | |
135 | /* | |
136 | * Basically we want to save the pc value to non-cacheable region | |
137 | * if ESS is enabled. But we should also consider cases that are not so. | |
138 | */ | |
139 | ||
140 | if (dbg_snapshot_get_enable("header")) | |
141 | return ((unsigned long)dbg_snapshot_get_base_paddr() + DSS_OFFSET_CORE_LAST_PC); | |
142 | else | |
143 | return virt_to_phys((void *)dss_desc.hardlockup_core_pc); | |
144 | } | |
145 | ||
146 | unsigned long dbg_snapshot_get_last_pc(unsigned int cpu) | |
147 | { | |
148 | if (dbg_snapshot_get_enable("header")) | |
149 | return __raw_readq(dbg_snapshot_get_base_vaddr() + | |
150 | DSS_OFFSET_CORE_LAST_PC + cpu * 8); | |
151 | else | |
152 | return dss_desc.hardlockup_core_pc[cpu]; | |
153 | } | |
154 | ||
155 | unsigned long dbg_snapshot_get_spare_vaddr(unsigned int offset) | |
156 | { | |
157 | return (unsigned long)(dbg_snapshot_get_base_vaddr() + | |
158 | DSS_OFFSET_SPARE_BASE + offset); | |
159 | } | |
160 | ||
161 | unsigned long dbg_snapshot_get_spare_paddr(unsigned int offset) | |
162 | { | |
163 | unsigned long base_vaddr = 0; | |
164 | unsigned long base_paddr = (unsigned long)dbg_snapshot_get_base_paddr(); | |
165 | ||
166 | if (base_paddr) | |
167 | base_vaddr = (unsigned long)(base_paddr + | |
168 | DSS_OFFSET_SPARE_BASE + offset); | |
169 | ||
170 | return base_vaddr; | |
171 | } | |
172 | ||
173 | unsigned int dbg_snapshot_get_item_size(char* name) | |
174 | { | |
175 | unsigned long i; | |
176 | ||
758cb36c | 177 | for (i = 0; i < dss_desc.log_cnt; i++) { |
dd101ca5 DC |
178 | if (!strncmp(dss_items[i].name, name, strlen(name))) |
179 | return dss_items[i].entry.size; | |
180 | } | |
181 | return 0; | |
182 | } | |
183 | EXPORT_SYMBOL(dbg_snapshot_get_item_size); | |
184 | ||
185 | unsigned long dbg_snapshot_get_item_vaddr(char *name) | |
186 | { | |
187 | unsigned long i; | |
188 | ||
758cb36c | 189 | for (i = 0; i < dss_desc.log_cnt; i++) { |
dd101ca5 DC |
190 | if (!strncmp(dss_items[i].name, name, strlen(name))) |
191 | return dss_items[i].entry.vaddr; | |
192 | } | |
193 | return 0; | |
194 | } | |
195 | ||
196 | unsigned int dbg_snapshot_get_item_paddr(char* name) | |
197 | { | |
198 | unsigned long i; | |
199 | ||
758cb36c | 200 | for (i = 0; i < dss_desc.log_cnt; i++) { |
dd101ca5 DC |
201 | if (!strncmp(dss_items[i].name, name, strlen(name))) |
202 | return dss_items[i].entry.paddr; | |
203 | } | |
204 | return 0; | |
205 | } | |
206 | EXPORT_SYMBOL(dbg_snapshot_get_item_paddr); | |
207 | ||
208 | int dbg_snapshot_get_hardlockup(void) | |
209 | { | |
210 | return dss_desc.hardlockup_detected; | |
211 | } | |
212 | EXPORT_SYMBOL(dbg_snapshot_get_hardlockup); | |
213 | ||
214 | int dbg_snapshot_set_hardlockup(int val) | |
215 | { | |
216 | unsigned long flags; | |
217 | ||
218 | if (unlikely(!dss_base.enabled)) | |
219 | return 0; | |
220 | ||
221 | raw_spin_lock_irqsave(&dss_desc.ctrl_lock, flags); | |
222 | dss_desc.hardlockup_detected = val; | |
223 | raw_spin_unlock_irqrestore(&dss_desc.ctrl_lock, flags); | |
224 | return 0; | |
225 | } | |
226 | EXPORT_SYMBOL(dbg_snapshot_set_hardlockup); | |
227 | ||
228 | int dbg_snapshot_early_panic(void) | |
229 | { | |
230 | dss_soc_ops->soc_early_panic(NULL); | |
231 | return 0; | |
232 | } | |
233 | ||
234 | int dbg_snapshot_prepare_panic(void) | |
235 | { | |
236 | unsigned long cpu; | |
237 | ||
238 | if (unlikely(!dss_base.enabled)) | |
239 | return 0; | |
240 | /* | |
241 | * kick watchdog to prevent unexpected reset during panic sequence | |
242 | * and it prevents the hang during panic sequence by watchedog | |
243 | */ | |
244 | dss_soc_ops->soc_start_watchdog(NULL); | |
245 | ||
246 | dss_soc_ops->soc_prepare_panic_entry(NULL); | |
247 | ||
248 | /* Again disable log_kevents */ | |
249 | dbg_snapshot_set_enable("log_kevents", false); | |
250 | ||
251 | for_each_possible_cpu(cpu) { | |
252 | if (dss_soc_ops->soc_is_power_cpu((void *)cpu)) | |
253 | dbg_snapshot_set_core_power_stat(DSS_SIGN_ALIVE, cpu); | |
254 | else | |
255 | dbg_snapshot_set_core_power_stat(DSS_SIGN_DEAD, cpu); | |
256 | } | |
257 | dss_soc_ops->soc_prepare_panic_exit(NULL); | |
258 | ||
259 | return 0; | |
260 | } | |
261 | EXPORT_SYMBOL(dbg_snapshot_prepare_panic); | |
262 | ||
263 | int dbg_snapshot_post_panic(void) | |
264 | { | |
265 | if (dss_base.enabled) { | |
dd101ca5 DC |
266 | dbg_snapshot_recall_hardlockup_core(); |
267 | #ifdef CONFIG_DEBUG_SNAPSHOT_PMU | |
268 | dbg_snapshot_dump_sfr(); | |
269 | #endif | |
270 | dbg_snapshot_save_context(NULL); | |
5bcdb379 | 271 | |
dd101ca5 DC |
272 | dbg_snapshot_print_panic_report(); |
273 | ||
6b666fd8 | 274 | dss_soc_ops->soc_post_panic_entry(NULL); |
dd101ca5 DC |
275 | |
276 | #ifdef CONFIG_DEBUG_SNAPSHOT_PANIC_REBOOT | |
277 | if (!dss_desc.no_wdt_dev) { | |
278 | #ifdef CONFIG_DEBUG_SNAPSHOT_WATCHDOG_RESET | |
279 | if (dss_desc.hardlockup_detected || num_online_cpus() > 1) { | |
280 | /* for stall cpu */ | |
6b666fd8 | 281 | dbg_snapshot_spin_func(); |
dd101ca5 DC |
282 | } |
283 | #endif | |
284 | } | |
285 | #endif | |
286 | } | |
6b666fd8 HK |
287 | dss_soc_ops->soc_post_panic_exit(NULL); |
288 | ||
dd101ca5 | 289 | /* for stall cpu when not enabling panic reboot */ |
6b666fd8 | 290 | dbg_snapshot_spin_func(); |
dd101ca5 DC |
291 | |
292 | /* Never run this function */ | |
293 | pr_emerg("debug-snapshot: %s DO NOT RUN this function (CPU:%d)\n", | |
294 | __func__, raw_smp_processor_id()); | |
295 | return 0; | |
296 | } | |
297 | EXPORT_SYMBOL(dbg_snapshot_post_panic); | |
298 | ||
299 | int dbg_snapshot_dump_panic(char *str, size_t len) | |
300 | { | |
301 | if (unlikely(!dss_base.enabled) || | |
302 | !dbg_snapshot_get_enable("header")) | |
303 | return 0; | |
304 | ||
305 | /* This function is only one which runs in panic funcion */ | |
306 | if (str && len && len < SZ_1K) | |
307 | memcpy(dbg_snapshot_get_base_vaddr() + DSS_OFFSET_PANIC_STRING, str, len); | |
308 | ||
309 | return 0; | |
310 | } | |
311 | EXPORT_SYMBOL(dbg_snapshot_dump_panic); | |
312 | ||
313 | int dbg_snapshot_post_reboot(char *cmd) | |
314 | { | |
315 | int cpu; | |
316 | ||
317 | if (unlikely(!dss_base.enabled)) | |
318 | return 0; | |
319 | ||
00fd6f7b HK |
320 | dss_soc_ops->soc_post_reboot_entry(NULL); |
321 | ||
dd101ca5 DC |
322 | dbg_snapshot_report_reason(DSS_SIGN_NORMAL_REBOOT); |
323 | ||
324 | if (!cmd) | |
325 | dbg_snapshot_scratch_reg(DSS_SIGN_RESET); | |
326 | else if (strcmp((char *)cmd, "bootloader") && strcmp((char *)cmd, "ramdump")) | |
327 | dbg_snapshot_scratch_reg(DSS_SIGN_RESET); | |
328 | ||
329 | pr_emerg("debug-snapshot: normal reboot done\n"); | |
330 | ||
331 | dbg_snapshot_save_context(NULL); | |
dd101ca5 | 332 | |
1ffb8082 HK |
333 | /* clear DSS_SIGN_PANIC when normal reboot */ |
334 | for_each_possible_cpu(cpu) { | |
335 | dbg_snapshot_set_core_panic_stat(DSS_SIGN_RESET, cpu); | |
336 | } | |
337 | ||
00fd6f7b HK |
338 | dss_soc_ops->soc_post_reboot_exit(NULL); |
339 | ||
dd101ca5 DC |
340 | return 0; |
341 | } | |
342 | EXPORT_SYMBOL(dbg_snapshot_post_reboot); | |
343 | ||
344 | static int dbg_snapshot_reboot_handler(struct notifier_block *nb, | |
345 | unsigned long l, void *p) | |
346 | { | |
347 | if (unlikely(!dss_base.enabled)) | |
348 | return 0; | |
349 | ||
350 | pr_emerg("debug-snapshot: normal reboot starting\n"); | |
351 | ||
352 | return 0; | |
353 | } | |
354 | ||
355 | static int dbg_snapshot_panic_handler(struct notifier_block *nb, | |
356 | unsigned long l, void *buf) | |
357 | { | |
358 | dbg_snapshot_report_reason(DSS_SIGN_PANIC); | |
359 | if (unlikely(!dss_base.enabled)) | |
360 | return 0; | |
361 | ||
362 | #ifdef CONFIG_DEBUG_SNAPSHOT_PANIC_REBOOT | |
363 | local_irq_disable(); | |
364 | pr_emerg("debug-snapshot: panic - reboot[%s]\n", __func__); | |
365 | #else | |
366 | pr_emerg("debug-snapshot: panic - normal[%s]\n", __func__); | |
367 | #endif | |
c8241d42 YN |
368 | /* |
369 | * To get more kernel panic log | |
370 | * dbg_snapshot_dump_task_info(); | |
371 | */ | |
dd101ca5 | 372 | pr_emerg("linux_banner: %s\n", linux_banner); |
5bcdb379 | 373 | |
dd101ca5 DC |
374 | return 0; |
375 | } | |
376 | ||
377 | static struct notifier_block nb_reboot_block = { | |
378 | .notifier_call = dbg_snapshot_reboot_handler | |
379 | }; | |
380 | ||
381 | static struct notifier_block nb_panic_block = { | |
382 | .notifier_call = dbg_snapshot_panic_handler, | |
383 | }; | |
384 | ||
385 | void dbg_snapshot_panic_handler_safe(void) | |
386 | { | |
387 | char *cpu_num[SZ_16] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; | |
388 | char text[SZ_32] = "safe panic handler at cpu "; | |
389 | int cpu = raw_smp_processor_id(); | |
390 | size_t len; | |
391 | ||
392 | if (unlikely(!dss_base.enabled)) | |
393 | return; | |
394 | ||
395 | strncat(text, cpu_num[cpu], 1); | |
396 | len = strnlen(text, SZ_32); | |
397 | ||
398 | dbg_snapshot_report_reason(DSS_SIGN_SAFE_FAULT); | |
399 | dbg_snapshot_dump_panic(text, len); | |
400 | dss_soc_ops->soc_expire_watchdog((void *)NULL); | |
401 | } | |
402 | ||
403 | void dbg_snapshot_register_soc_ops(struct dbg_snapshot_helper_ops *ops) | |
404 | { | |
405 | if (ops) | |
406 | dss_soc_ops = ops; | |
407 | } | |
408 | ||
1abf4739 | 409 | void __init dbg_snapshot_init_helper(void) |
dd101ca5 DC |
410 | { |
411 | register_reboot_notifier(&nb_reboot_block); | |
412 | atomic_notifier_chain_register(&panic_notifier_list, &nb_panic_block); | |
413 | dss_soc_ops = &dss_soc_dummy_ops; | |
414 | ||
415 | /* hardlockup_detector function should be called before secondary booting */ | |
416 | dbg_snapshot_soc_helper_init(); | |
417 | } |