Commit | Line | Data |
---|---|---|
dd101ca5 DC |
1 | /* |
2 | * Copyright (c) 2013 Samsung Electronics Co., Ltd. | |
3 | * http://www.samsung.com | |
4 | * | |
5 | * Exynos-SnapShot debugging framework for Exynos SoC | |
6 | * | |
7 | * Author: Hosung Kim <Hosung0.kim@samsung.com> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License version 2 as | |
11 | * published by the Free Software Foundation. | |
12 | */ | |
13 | ||
14 | #include <linux/kernel.h> | |
15 | #include <linux/slab.h> | |
16 | #include <linux/module.h> | |
17 | #include <linux/ktime.h> | |
18 | #include <linux/kallsyms.h> | |
19 | #include <linux/platform_device.h> | |
20 | #include <linux/clk-provider.h> | |
21 | #include <linux/pstore_ram.h> | |
22 | #include <linux/sched/clock.h> | |
23 | #include <linux/ftrace.h> | |
24 | ||
25 | #include "debug-snapshot-local.h" | |
26 | #include <asm/irq.h> | |
27 | #include <asm/traps.h> | |
28 | #include <asm/hardirq.h> | |
29 | #include <asm/stacktrace.h> | |
30 | #include <linux/debug-snapshot.h> | |
31 | #include <linux/kernel_stat.h> | |
32 | #include <linux/irqnr.h> | |
33 | #include <linux/irq.h> | |
34 | #include <linux/irqdesc.h> | |
35 | ||
36 | /* This defines are for PSTORE */ | |
37 | #define DSS_LOGGER_LEVEL_HEADER (1) | |
38 | #define DSS_LOGGER_LEVEL_PREFIX (2) | |
39 | #define DSS_LOGGER_LEVEL_TEXT (3) | |
40 | #define DSS_LOGGER_LEVEL_MAX (4) | |
41 | #define DSS_LOGGER_SKIP_COUNT (4) | |
42 | #define DSS_LOGGER_STRING_PAD (1) | |
43 | #define DSS_LOGGER_HEADER_SIZE (68) | |
44 | ||
45 | #define DSS_LOG_ID_MAIN (0) | |
46 | #define DSS_LOG_ID_RADIO (1) | |
47 | #define DSS_LOG_ID_EVENTS (2) | |
48 | #define DSS_LOG_ID_SYSTEM (3) | |
49 | #define DSS_LOG_ID_CRASH (4) | |
50 | #define DSS_LOG_ID_KERNEL (5) | |
51 | ||
52 | typedef struct __attribute__((__packed__)) { | |
53 | uint8_t magic; | |
54 | uint16_t len; | |
55 | uint16_t uid; | |
56 | uint16_t pid; | |
57 | } dss_pmsg_log_header_t; | |
58 | ||
59 | typedef struct __attribute__((__packed__)) { | |
60 | unsigned char id; | |
61 | uint16_t tid; | |
62 | int32_t tv_sec; | |
63 | int32_t tv_nsec; | |
64 | } dss_android_log_header_t; | |
65 | ||
66 | typedef struct dss_logger { | |
67 | uint16_t len; | |
68 | uint16_t id; | |
69 | uint16_t pid; | |
70 | uint16_t tid; | |
71 | uint16_t uid; | |
72 | uint16_t level; | |
73 | int32_t tv_sec; | |
74 | int32_t tv_nsec; | |
75 | char msg; | |
76 | char *buffer; | |
77 | void (*func_hook_logger)(const char*, const char*, size_t); | |
78 | } __attribute__((__packed__)) dss_logger; | |
79 | ||
80 | static dss_logger logger; | |
81 | ||
82 | void register_hook_logger(void (*func)(const char *name, const char *buf, size_t size)) | |
83 | { | |
84 | logger.func_hook_logger = func; | |
85 | logger.buffer = vmalloc(PAGE_SIZE * 3); | |
86 | ||
87 | if (logger.buffer) | |
88 | pr_info("debug-snapshot: logger buffer alloc address: 0x%p\n", logger.buffer); | |
89 | } | |
90 | EXPORT_SYMBOL(register_hook_logger); | |
91 | ||
92 | static int dbg_snapshot_combine_pmsg(char *buffer, size_t count, unsigned int level) | |
93 | { | |
94 | char *logbuf = logger.buffer; | |
95 | ||
96 | if (!logbuf) | |
97 | return -ENOMEM; | |
98 | ||
99 | switch (level) { | |
100 | case DSS_LOGGER_LEVEL_HEADER: | |
101 | { | |
102 | struct tm tmBuf; | |
103 | u64 tv_kernel; | |
104 | unsigned int logbuf_len; | |
105 | unsigned long rem_nsec; | |
106 | ||
107 | if (logger.id == DSS_LOG_ID_EVENTS) | |
108 | break; | |
109 | ||
110 | tv_kernel = local_clock(); | |
111 | rem_nsec = do_div(tv_kernel, 1000000000); | |
112 | time_to_tm(logger.tv_sec, 0, &tmBuf); | |
113 | ||
114 | logbuf_len = snprintf(logbuf, DSS_LOGGER_HEADER_SIZE, | |
115 | "\n[%5lu.%06lu][%d:%16s] %02d-%02d %02d:%02d:%02d.%03d %5d %5d ", | |
116 | (unsigned long)tv_kernel, rem_nsec / 1000, | |
117 | raw_smp_processor_id(), current->comm, | |
118 | tmBuf.tm_mon + 1, tmBuf.tm_mday, | |
119 | tmBuf.tm_hour, tmBuf.tm_min, tmBuf.tm_sec, | |
120 | logger.tv_nsec / 1000000, logger.pid, logger.tid); | |
121 | ||
122 | logger.func_hook_logger("log_platform", logbuf, logbuf_len - 1); | |
123 | } | |
124 | break; | |
125 | case DSS_LOGGER_LEVEL_PREFIX: | |
126 | { | |
127 | static const char *kPrioChars = "!.VDIWEFS"; | |
128 | unsigned char prio = logger.msg; | |
129 | ||
130 | if (logger.id == DSS_LOG_ID_EVENTS) | |
131 | break; | |
132 | ||
133 | logbuf[0] = prio < strlen(kPrioChars) ? kPrioChars[prio] : '?'; | |
134 | logbuf[1] = ' '; | |
135 | ||
136 | logger.func_hook_logger("log_platform", logbuf, DSS_LOGGER_LEVEL_PREFIX); | |
137 | } | |
138 | break; | |
139 | case DSS_LOGGER_LEVEL_TEXT: | |
140 | { | |
141 | char *eatnl = buffer + count - DSS_LOGGER_STRING_PAD; | |
142 | ||
143 | if (logger.id == DSS_LOG_ID_EVENTS) | |
144 | break; | |
145 | if (count == DSS_LOGGER_SKIP_COUNT && *eatnl != '\0') | |
146 | break; | |
147 | ||
148 | logger.func_hook_logger("log_platform", buffer, count - 1); | |
149 | } | |
150 | break; | |
151 | default: | |
152 | break; | |
153 | } | |
154 | return 0; | |
155 | } | |
156 | ||
157 | int dbg_snapshot_hook_pmsg(char *buffer, size_t count) | |
158 | { | |
159 | dss_android_log_header_t header; | |
160 | dss_pmsg_log_header_t pmsg_header; | |
161 | ||
162 | if (!logger.buffer) | |
163 | return -ENOMEM; | |
164 | ||
165 | switch (count) { | |
166 | case sizeof(pmsg_header): | |
167 | memcpy((void *)&pmsg_header, buffer, count); | |
168 | if (pmsg_header.magic != 'l') { | |
169 | dbg_snapshot_combine_pmsg(buffer, count, DSS_LOGGER_LEVEL_TEXT); | |
170 | } else { | |
171 | /* save logger data */ | |
172 | logger.pid = pmsg_header.pid; | |
173 | logger.uid = pmsg_header.uid; | |
174 | logger.len = pmsg_header.len; | |
175 | } | |
176 | break; | |
177 | case sizeof(header): | |
178 | /* save logger data */ | |
179 | memcpy((void *)&header, buffer, count); | |
180 | logger.id = header.id; | |
181 | logger.tid = header.tid; | |
182 | logger.tv_sec = header.tv_sec; | |
183 | logger.tv_nsec = header.tv_nsec; | |
184 | if (logger.id > 7) { | |
185 | /* write string */ | |
186 | dbg_snapshot_combine_pmsg(buffer, count, DSS_LOGGER_LEVEL_TEXT); | |
187 | } else { | |
188 | /* write header */ | |
189 | dbg_snapshot_combine_pmsg(buffer, count, DSS_LOGGER_LEVEL_HEADER); | |
190 | } | |
191 | break; | |
192 | case sizeof(unsigned char): | |
193 | logger.msg = buffer[0]; | |
194 | /* write char for prefix */ | |
195 | dbg_snapshot_combine_pmsg(buffer, count, DSS_LOGGER_LEVEL_PREFIX); | |
196 | break; | |
197 | default: | |
198 | /* write string */ | |
199 | dbg_snapshot_combine_pmsg(buffer, count, DSS_LOGGER_LEVEL_TEXT); | |
200 | break; | |
201 | } | |
202 | ||
203 | return 0; | |
204 | } | |
205 | EXPORT_SYMBOL(dbg_snapshot_hook_pmsg); | |
206 | ||
207 | /* | |
208 | * To support pstore/pmsg/pstore_ram, following is implementation for debug-snapshot | |
209 | * dss_ramoops platform_device is used by pstore fs. | |
210 | */ | |
211 | ||
212 | static struct ramoops_platform_data dss_ramoops_data = { | |
213 | .record_size = SZ_512K, | |
214 | .console_size = SZ_512K, | |
215 | .ftrace_size = SZ_512K, | |
216 | .pmsg_size = SZ_512K, | |
217 | .dump_oops = 1, | |
218 | }; | |
219 | ||
220 | static struct platform_device dss_ramoops = { | |
221 | .name = "ramoops", | |
222 | .dev = { | |
223 | .platform_data = &dss_ramoops_data, | |
224 | }, | |
225 | }; | |
226 | ||
227 | static int __init dss_pstore_init(void) | |
228 | { | |
229 | if (dbg_snapshot_get_enable("log_pstore")) { | |
230 | dss_ramoops_data.mem_size = dbg_snapshot_get_item_size("log_pstore"); | |
231 | dss_ramoops_data.mem_address = dbg_snapshot_get_item_paddr("log_pstore"); | |
232 | } | |
233 | return platform_device_register(&dss_ramoops); | |
234 | } | |
235 | ||
236 | static void __exit dss_pstore_exit(void) | |
237 | { | |
238 | platform_device_unregister(&dss_ramoops); | |
239 | } | |
240 | module_init(dss_pstore_init); | |
241 | module_exit(dss_pstore_exit); | |
242 | ||
243 | MODULE_DESCRIPTION("Exynos Snapshot pstore module"); | |
244 | MODULE_LICENSE("GPL"); |