diff options
author | Vineet Gupta <vgupta@synopsys.com> | 2013-01-18 04:42:23 -0500 |
---|---|---|
committer | Vineet Gupta <vgupta@synopsys.com> | 2013-02-15 12:46:02 -0500 |
commit | 0ef88a54aa341f754707414500158addbf35c780 (patch) | |
tree | 0b849aebc28e9140e79cb7e7f2822939cbac0b39 /arch/arc/kernel | |
parent | fa1c3ff935179453801d763940c38c3ac2d581eb (diff) |
ARC: Diagnostics: show_regs() etc
Signed-off-by: Vineet Gupta <vgupta@synopsys.com>
Diffstat (limited to 'arch/arc/kernel')
-rw-r--r-- | arch/arc/kernel/troubleshoot.c | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/arch/arc/kernel/troubleshoot.c b/arch/arc/kernel/troubleshoot.c index 80bfe2a15a98..7c10873c311f 100644 --- a/arch/arc/kernel/troubleshoot.c +++ b/arch/arc/kernel/troubleshoot.c | |||
@@ -6,12 +6,317 @@ | |||
6 | */ | 6 | */ |
7 | 7 | ||
8 | #include <linux/ptrace.h> | 8 | #include <linux/ptrace.h> |
9 | #include <linux/module.h> | ||
10 | #include <linux/mm.h> | ||
11 | #include <linux/fs.h> | ||
12 | #include <linux/kdev_t.h> | ||
13 | #include <linux/fs_struct.h> | ||
14 | #include <linux/proc_fs.h> | ||
15 | #include <linux/file.h> | ||
16 | #include <asm/arcregs.h> | ||
17 | |||
18 | /* | ||
19 | * Common routine to print scratch regs (r0-r12) or callee regs (r13-r25) | ||
20 | * -Prints 3 regs per line and a CR. | ||
21 | * -To continue, callee regs right after scratch, special handling of CR | ||
22 | */ | ||
23 | static noinline void print_reg_file(long *reg_rev, int start_num) | ||
24 | { | ||
25 | unsigned int i; | ||
26 | char buf[512]; | ||
27 | int n = 0, len = sizeof(buf); | ||
28 | |||
29 | /* weird loop because pt_regs regs rev r12..r0, r25..r13 */ | ||
30 | for (i = start_num; i < start_num + 13; i++) { | ||
31 | n += scnprintf(buf + n, len - n, "r%02u: 0x%08lx\t", | ||
32 | i, (unsigned long)*reg_rev); | ||
33 | |||
34 | if (((i + 1) % 3) == 0) | ||
35 | n += scnprintf(buf + n, len - n, "\n"); | ||
36 | |||
37 | reg_rev--; | ||
38 | } | ||
39 | |||
40 | if (start_num != 0) | ||
41 | n += scnprintf(buf + n, len - n, "\n\n"); | ||
42 | |||
43 | pr_info("%s", buf); | ||
44 | } | ||
45 | |||
46 | static void show_callee_regs(struct callee_regs *cregs) | ||
47 | { | ||
48 | print_reg_file(&(cregs->r13), 13); | ||
49 | } | ||
50 | |||
51 | void print_task_path_n_nm(struct task_struct *tsk, char *buf) | ||
52 | { | ||
53 | struct path path; | ||
54 | char *path_nm = NULL; | ||
55 | struct mm_struct *mm; | ||
56 | struct file *exe_file; | ||
57 | |||
58 | mm = get_task_mm(tsk); | ||
59 | if (!mm) | ||
60 | goto done; | ||
61 | |||
62 | exe_file = get_mm_exe_file(mm); | ||
63 | mmput(mm); | ||
64 | |||
65 | if (exe_file) { | ||
66 | path = exe_file->f_path; | ||
67 | path_get(&exe_file->f_path); | ||
68 | fput(exe_file); | ||
69 | path_nm = d_path(&path, buf, 255); | ||
70 | path_put(&path); | ||
71 | } | ||
72 | |||
73 | done: | ||
74 | pr_info("%s, TGID %u\n", path_nm, tsk->tgid); | ||
75 | } | ||
76 | EXPORT_SYMBOL(print_task_path_n_nm); | ||
77 | |||
78 | static void show_faulting_vma(unsigned long address, char *buf) | ||
79 | { | ||
80 | struct vm_area_struct *vma; | ||
81 | struct inode *inode; | ||
82 | unsigned long ino = 0; | ||
83 | dev_t dev = 0; | ||
84 | char *nm = buf; | ||
85 | |||
86 | vma = find_vma(current->active_mm, address); | ||
87 | |||
88 | /* check against the find_vma( ) behaviour which returns the next VMA | ||
89 | * if the container VMA is not found | ||
90 | */ | ||
91 | if (vma && (vma->vm_start <= address)) { | ||
92 | struct file *file = vma->vm_file; | ||
93 | if (file) { | ||
94 | struct path *path = &file->f_path; | ||
95 | nm = d_path(path, buf, PAGE_SIZE - 1); | ||
96 | inode = vma->vm_file->f_path.dentry->d_inode; | ||
97 | dev = inode->i_sb->s_dev; | ||
98 | ino = inode->i_ino; | ||
99 | } | ||
100 | pr_info(" @off 0x%lx in [%s]\n" | ||
101 | " VMA: 0x%08lx to 0x%08lx\n\n", | ||
102 | address - vma->vm_start, nm, vma->vm_start, vma->vm_end); | ||
103 | } else | ||
104 | pr_info(" @No matching VMA found\n"); | ||
105 | } | ||
106 | |||
107 | static void show_ecr_verbose(struct pt_regs *regs) | ||
108 | { | ||
109 | unsigned int vec, cause_code, cause_reg; | ||
110 | unsigned long address; | ||
111 | |||
112 | cause_reg = current->thread.cause_code; | ||
113 | pr_info("\n[ECR]: 0x%08x => ", cause_reg); | ||
114 | |||
115 | /* For Data fault, this is data address not instruction addr */ | ||
116 | address = current->thread.fault_address; | ||
117 | |||
118 | vec = cause_reg >> 16; | ||
119 | cause_code = (cause_reg >> 8) & 0xFF; | ||
120 | |||
121 | /* For DTLB Miss or ProtV, display the memory involved too */ | ||
122 | if (vec == ECR_V_DTLB_MISS) { | ||
123 | pr_cont("Invalid (%s) @ 0x%08lx by insn @ 0x%08lx\n", | ||
124 | (cause_code == 0x01) ? "Read From" : | ||
125 | ((cause_code == 0x02) ? "Write to" : "EX"), | ||
126 | address, regs->ret); | ||
127 | } else if (vec == ECR_V_ITLB_MISS) { | ||
128 | pr_cont("Insn could not be fetched\n"); | ||
129 | } else if (vec == ECR_V_MACH_CHK) { | ||
130 | pr_cont("%s\n", (cause_code == 0x0) ? | ||
131 | "Double Fault" : "Other Fatal Err"); | ||
132 | |||
133 | } else if (vec == ECR_V_PROTV) { | ||
134 | if (cause_code == ECR_C_PROTV_INST_FETCH) | ||
135 | pr_cont("Execute from Non-exec Page\n"); | ||
136 | else if (cause_code == ECR_C_PROTV_LOAD) | ||
137 | pr_cont("Read from Non-readable Page\n"); | ||
138 | else if (cause_code == ECR_C_PROTV_STORE) | ||
139 | pr_cont("Write to Non-writable Page\n"); | ||
140 | else if (cause_code == ECR_C_PROTV_XCHG) | ||
141 | pr_cont("Data exchange protection violation\n"); | ||
142 | else if (cause_code == ECR_C_PROTV_MISALIG_DATA) | ||
143 | pr_cont("Misaligned r/w from 0x%08lx\n", address); | ||
144 | } else if (vec == ECR_V_INSN_ERR) { | ||
145 | pr_cont("Illegal Insn\n"); | ||
146 | } else { | ||
147 | pr_cont("Check Programmer's Manual\n"); | ||
148 | } | ||
149 | } | ||
150 | |||
151 | /************************************************************************ | ||
152 | * API called by rest of kernel | ||
153 | ***********************************************************************/ | ||
9 | 154 | ||
10 | void show_regs(struct pt_regs *regs) | 155 | void show_regs(struct pt_regs *regs) |
11 | { | 156 | { |
157 | struct task_struct *tsk = current; | ||
158 | struct callee_regs *cregs; | ||
159 | char *buf; | ||
160 | |||
161 | buf = (char *)__get_free_page(GFP_TEMPORARY); | ||
162 | if (!buf) | ||
163 | return; | ||
164 | |||
165 | print_task_path_n_nm(tsk, buf); | ||
166 | |||
167 | if (current->thread.cause_code) | ||
168 | show_ecr_verbose(regs); | ||
169 | |||
170 | pr_info("[EFA]: 0x%08lx\n", current->thread.fault_address); | ||
171 | pr_info("[ERET]: 0x%08lx (PC of Faulting Instr)\n", regs->ret); | ||
172 | |||
173 | show_faulting_vma(regs->ret, buf); /* faulting code, not data */ | ||
174 | |||
175 | /* can't use print_vma_addr() yet as it doesn't check for | ||
176 | * non-inclusive vma | ||
177 | */ | ||
178 | |||
179 | /* print special regs */ | ||
180 | pr_info("status32: 0x%08lx\n", regs->status32); | ||
181 | pr_info(" SP: 0x%08lx\tFP: 0x%08lx\n", regs->sp, regs->fp); | ||
182 | pr_info("BTA: 0x%08lx\tBLINK: 0x%08lx\n", | ||
183 | regs->bta, regs->blink); | ||
184 | pr_info("LPS: 0x%08lx\tLPE: 0x%08lx\tLPC: 0x%08lx\n", | ||
185 | regs->lp_start, regs->lp_end, regs->lp_count); | ||
186 | |||
187 | /* print regs->r0 thru regs->r12 | ||
188 | * Sequential printing was generating horrible code | ||
189 | */ | ||
190 | print_reg_file(&(regs->r0), 0); | ||
191 | |||
192 | /* If Callee regs were saved, display them too */ | ||
193 | cregs = (struct callee_regs *)current->thread.callee_reg; | ||
194 | if (cregs) | ||
195 | show_callee_regs(cregs); | ||
196 | |||
197 | free_page((unsigned long)buf); | ||
12 | } | 198 | } |
13 | 199 | ||
14 | void show_kernel_fault_diag(const char *str, struct pt_regs *regs, | 200 | void show_kernel_fault_diag(const char *str, struct pt_regs *regs, |
15 | unsigned long address, unsigned long cause_reg) | 201 | unsigned long address, unsigned long cause_reg) |
16 | { | 202 | { |
203 | current->thread.fault_address = address; | ||
204 | current->thread.cause_code = cause_reg; | ||
205 | |||
206 | /* Caller and Callee regs */ | ||
207 | show_regs(regs); | ||
208 | |||
209 | /* Show stack trace if this Fatality happened in kernel mode */ | ||
210 | if (!user_mode(regs)) | ||
211 | show_stacktrace(current, regs); | ||
17 | } | 212 | } |
213 | |||
214 | #ifdef CONFIG_DEBUG_FS | ||
215 | |||
216 | #include <linux/module.h> | ||
217 | #include <linux/fs.h> | ||
218 | #include <linux/mount.h> | ||
219 | #include <linux/pagemap.h> | ||
220 | #include <linux/init.h> | ||
221 | #include <linux/namei.h> | ||
222 | #include <linux/debugfs.h> | ||
223 | |||
224 | static struct dentry *test_dentry; | ||
225 | static struct dentry *test_dir; | ||
226 | static struct dentry *test_u32_dentry; | ||
227 | |||
228 | static u32 clr_on_read = 1; | ||
229 | |||
230 | #ifdef CONFIG_ARC_DBG_TLB_MISS_COUNT | ||
231 | u32 numitlb, numdtlb, num_pte_not_present; | ||
232 | |||
233 | static int fill_display_data(char *kbuf) | ||
234 | { | ||
235 | size_t num = 0; | ||
236 | num += sprintf(kbuf + num, "I-TLB Miss %x\n", numitlb); | ||
237 | num += sprintf(kbuf + num, "D-TLB Miss %x\n", numdtlb); | ||
238 | num += sprintf(kbuf + num, "PTE not present %x\n", num_pte_not_present); | ||
239 | |||
240 | if (clr_on_read) | ||
241 | numitlb = numdtlb = num_pte_not_present = 0; | ||
242 | |||
243 | return num; | ||
244 | } | ||
245 | |||
246 | static int tlb_stats_open(struct inode *inode, struct file *file) | ||
247 | { | ||
248 | file->private_data = (void *)__get_free_page(GFP_KERNEL); | ||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | /* called on user read(): display the couters */ | ||
253 | static ssize_t tlb_stats_output(struct file *file, /* file descriptor */ | ||
254 | char __user *user_buf, /* user buffer */ | ||
255 | size_t len, /* length of buffer */ | ||
256 | loff_t *offset) /* offset in the file */ | ||
257 | { | ||
258 | size_t num; | ||
259 | char *kbuf = (char *)file->private_data; | ||
260 | |||
261 | /* All of the data can he shoved in one iteration */ | ||
262 | if (*offset != 0) | ||
263 | return 0; | ||
264 | |||
265 | num = fill_display_data(kbuf); | ||
266 | |||
267 | /* simple_read_from_buffer() is helper for copy to user space | ||
268 | It copies up to @2 (num) bytes from kernel buffer @4 (kbuf) at offset | ||
269 | @3 (offset) into the user space address starting at @1 (user_buf). | ||
270 | @5 (len) is max size of user buffer | ||
271 | */ | ||
272 | return simple_read_from_buffer(user_buf, num, offset, kbuf, len); | ||
273 | } | ||
274 | |||
275 | /* called on user write : clears the counters */ | ||
276 | static ssize_t tlb_stats_clear(struct file *file, const char __user *user_buf, | ||
277 | size_t length, loff_t *offset) | ||
278 | { | ||
279 | numitlb = numdtlb = num_pte_not_present = 0; | ||
280 | return length; | ||
281 | } | ||
282 | |||
283 | static int tlb_stats_close(struct inode *inode, struct file *file) | ||
284 | { | ||
285 | free_page((unsigned long)(file->private_data)); | ||
286 | return 0; | ||
287 | } | ||
288 | |||
289 | static const struct file_operations tlb_stats_file_ops = { | ||
290 | .read = tlb_stats_output, | ||
291 | .write = tlb_stats_clear, | ||
292 | .open = tlb_stats_open, | ||
293 | .release = tlb_stats_close | ||
294 | }; | ||
295 | #endif | ||
296 | |||
297 | static int __init arc_debugfs_init(void) | ||
298 | { | ||
299 | test_dir = debugfs_create_dir("arc", NULL); | ||
300 | |||
301 | #ifdef CONFIG_ARC_DBG_TLB_MISS_COUNT | ||
302 | test_dentry = debugfs_create_file("tlb_stats", 0444, test_dir, NULL, | ||
303 | &tlb_stats_file_ops); | ||
304 | #endif | ||
305 | |||
306 | test_u32_dentry = | ||
307 | debugfs_create_u32("clr_on_read", 0444, test_dir, &clr_on_read); | ||
308 | |||
309 | return 0; | ||
310 | } | ||
311 | |||
312 | module_init(arc_debugfs_init); | ||
313 | |||
314 | static void __exit arc_debugfs_exit(void) | ||
315 | { | ||
316 | debugfs_remove(test_u32_dentry); | ||
317 | debugfs_remove(test_dentry); | ||
318 | debugfs_remove(test_dir); | ||
319 | } | ||
320 | module_exit(arc_debugfs_exit); | ||
321 | |||
322 | #endif | ||