diff options
Diffstat (limited to 'drivers/iommu/omap-iommu-debug.c')
-rw-r--r-- | drivers/iommu/omap-iommu-debug.c | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/drivers/iommu/omap-iommu-debug.c b/drivers/iommu/omap-iommu-debug.c new file mode 100644 index 000000000000..288da5c1499d --- /dev/null +++ b/drivers/iommu/omap-iommu-debug.c | |||
@@ -0,0 +1,419 @@ | |||
1 | /* | ||
2 | * omap iommu: debugfs interface | ||
3 | * | ||
4 | * Copyright (C) 2008-2009 Nokia Corporation | ||
5 | * | ||
6 | * Written by Hiroshi DOYU <Hiroshi.DOYU@nokia.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | */ | ||
12 | |||
13 | #include <linux/module.h> | ||
14 | #include <linux/err.h> | ||
15 | #include <linux/clk.h> | ||
16 | #include <linux/io.h> | ||
17 | #include <linux/slab.h> | ||
18 | #include <linux/uaccess.h> | ||
19 | #include <linux/platform_device.h> | ||
20 | #include <linux/debugfs.h> | ||
21 | |||
22 | #include <plat/iommu.h> | ||
23 | #include <plat/iovmm.h> | ||
24 | |||
25 | #include <plat/iopgtable.h> | ||
26 | |||
27 | #define MAXCOLUMN 100 /* for short messages */ | ||
28 | |||
29 | static DEFINE_MUTEX(iommu_debug_lock); | ||
30 | |||
31 | static struct dentry *iommu_debug_root; | ||
32 | |||
33 | static ssize_t debug_read_ver(struct file *file, char __user *userbuf, | ||
34 | size_t count, loff_t *ppos) | ||
35 | { | ||
36 | u32 ver = omap_iommu_arch_version(); | ||
37 | char buf[MAXCOLUMN], *p = buf; | ||
38 | |||
39 | p += sprintf(p, "H/W version: %d.%d\n", (ver >> 4) & 0xf , ver & 0xf); | ||
40 | |||
41 | return simple_read_from_buffer(userbuf, count, ppos, buf, p - buf); | ||
42 | } | ||
43 | |||
44 | static ssize_t debug_read_regs(struct file *file, char __user *userbuf, | ||
45 | size_t count, loff_t *ppos) | ||
46 | { | ||
47 | struct omap_iommu *obj = file->private_data; | ||
48 | char *p, *buf; | ||
49 | ssize_t bytes; | ||
50 | |||
51 | buf = kmalloc(count, GFP_KERNEL); | ||
52 | if (!buf) | ||
53 | return -ENOMEM; | ||
54 | p = buf; | ||
55 | |||
56 | mutex_lock(&iommu_debug_lock); | ||
57 | |||
58 | bytes = omap_iommu_dump_ctx(obj, p, count); | ||
59 | bytes = simple_read_from_buffer(userbuf, count, ppos, buf, bytes); | ||
60 | |||
61 | mutex_unlock(&iommu_debug_lock); | ||
62 | kfree(buf); | ||
63 | |||
64 | return bytes; | ||
65 | } | ||
66 | |||
67 | static ssize_t debug_read_tlb(struct file *file, char __user *userbuf, | ||
68 | size_t count, loff_t *ppos) | ||
69 | { | ||
70 | struct omap_iommu *obj = file->private_data; | ||
71 | char *p, *buf; | ||
72 | ssize_t bytes, rest; | ||
73 | |||
74 | buf = kmalloc(count, GFP_KERNEL); | ||
75 | if (!buf) | ||
76 | return -ENOMEM; | ||
77 | p = buf; | ||
78 | |||
79 | mutex_lock(&iommu_debug_lock); | ||
80 | |||
81 | p += sprintf(p, "%8s %8s\n", "cam:", "ram:"); | ||
82 | p += sprintf(p, "-----------------------------------------\n"); | ||
83 | rest = count - (p - buf); | ||
84 | p += omap_dump_tlb_entries(obj, p, rest); | ||
85 | |||
86 | bytes = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf); | ||
87 | |||
88 | mutex_unlock(&iommu_debug_lock); | ||
89 | kfree(buf); | ||
90 | |||
91 | return bytes; | ||
92 | } | ||
93 | |||
94 | static ssize_t debug_write_pagetable(struct file *file, | ||
95 | const char __user *userbuf, size_t count, loff_t *ppos) | ||
96 | { | ||
97 | struct iotlb_entry e; | ||
98 | struct cr_regs cr; | ||
99 | int err; | ||
100 | struct omap_iommu *obj = file->private_data; | ||
101 | char buf[MAXCOLUMN], *p = buf; | ||
102 | |||
103 | count = min(count, sizeof(buf)); | ||
104 | |||
105 | mutex_lock(&iommu_debug_lock); | ||
106 | if (copy_from_user(p, userbuf, count)) { | ||
107 | mutex_unlock(&iommu_debug_lock); | ||
108 | return -EFAULT; | ||
109 | } | ||
110 | |||
111 | sscanf(p, "%x %x", &cr.cam, &cr.ram); | ||
112 | if (!cr.cam || !cr.ram) { | ||
113 | mutex_unlock(&iommu_debug_lock); | ||
114 | return -EINVAL; | ||
115 | } | ||
116 | |||
117 | omap_iotlb_cr_to_e(&cr, &e); | ||
118 | err = omap_iopgtable_store_entry(obj, &e); | ||
119 | if (err) | ||
120 | dev_err(obj->dev, "%s: fail to store cr\n", __func__); | ||
121 | |||
122 | mutex_unlock(&iommu_debug_lock); | ||
123 | return count; | ||
124 | } | ||
125 | |||
126 | #define dump_ioptable_entry_one(lv, da, val) \ | ||
127 | ({ \ | ||
128 | int __err = 0; \ | ||
129 | ssize_t bytes; \ | ||
130 | const int maxcol = 22; \ | ||
131 | const char *str = "%d: %08x %08x\n"; \ | ||
132 | bytes = snprintf(p, maxcol, str, lv, da, val); \ | ||
133 | p += bytes; \ | ||
134 | len -= bytes; \ | ||
135 | if (len < maxcol) \ | ||
136 | __err = -ENOMEM; \ | ||
137 | __err; \ | ||
138 | }) | ||
139 | |||
140 | static ssize_t dump_ioptable(struct omap_iommu *obj, char *buf, ssize_t len) | ||
141 | { | ||
142 | int i; | ||
143 | u32 *iopgd; | ||
144 | char *p = buf; | ||
145 | |||
146 | spin_lock(&obj->page_table_lock); | ||
147 | |||
148 | iopgd = iopgd_offset(obj, 0); | ||
149 | for (i = 0; i < PTRS_PER_IOPGD; i++, iopgd++) { | ||
150 | int j, err; | ||
151 | u32 *iopte; | ||
152 | u32 da; | ||
153 | |||
154 | if (!*iopgd) | ||
155 | continue; | ||
156 | |||
157 | if (!(*iopgd & IOPGD_TABLE)) { | ||
158 | da = i << IOPGD_SHIFT; | ||
159 | |||
160 | err = dump_ioptable_entry_one(1, da, *iopgd); | ||
161 | if (err) | ||
162 | goto out; | ||
163 | continue; | ||
164 | } | ||
165 | |||
166 | iopte = iopte_offset(iopgd, 0); | ||
167 | |||
168 | for (j = 0; j < PTRS_PER_IOPTE; j++, iopte++) { | ||
169 | if (!*iopte) | ||
170 | continue; | ||
171 | |||
172 | da = (i << IOPGD_SHIFT) + (j << IOPTE_SHIFT); | ||
173 | err = dump_ioptable_entry_one(2, da, *iopgd); | ||
174 | if (err) | ||
175 | goto out; | ||
176 | } | ||
177 | } | ||
178 | out: | ||
179 | spin_unlock(&obj->page_table_lock); | ||
180 | |||
181 | return p - buf; | ||
182 | } | ||
183 | |||
184 | static ssize_t debug_read_pagetable(struct file *file, char __user *userbuf, | ||
185 | size_t count, loff_t *ppos) | ||
186 | { | ||
187 | struct omap_iommu *obj = file->private_data; | ||
188 | char *p, *buf; | ||
189 | size_t bytes; | ||
190 | |||
191 | buf = (char *)__get_free_page(GFP_KERNEL); | ||
192 | if (!buf) | ||
193 | return -ENOMEM; | ||
194 | p = buf; | ||
195 | |||
196 | p += sprintf(p, "L: %8s %8s\n", "da:", "pa:"); | ||
197 | p += sprintf(p, "-----------------------------------------\n"); | ||
198 | |||
199 | mutex_lock(&iommu_debug_lock); | ||
200 | |||
201 | bytes = PAGE_SIZE - (p - buf); | ||
202 | p += dump_ioptable(obj, p, bytes); | ||
203 | |||
204 | bytes = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf); | ||
205 | |||
206 | mutex_unlock(&iommu_debug_lock); | ||
207 | free_page((unsigned long)buf); | ||
208 | |||
209 | return bytes; | ||
210 | } | ||
211 | |||
212 | static ssize_t debug_read_mmap(struct file *file, char __user *userbuf, | ||
213 | size_t count, loff_t *ppos) | ||
214 | { | ||
215 | struct omap_iommu *obj = file->private_data; | ||
216 | char *p, *buf; | ||
217 | struct iovm_struct *tmp; | ||
218 | int uninitialized_var(i); | ||
219 | ssize_t bytes; | ||
220 | |||
221 | buf = (char *)__get_free_page(GFP_KERNEL); | ||
222 | if (!buf) | ||
223 | return -ENOMEM; | ||
224 | p = buf; | ||
225 | |||
226 | p += sprintf(p, "%-3s %-8s %-8s %6s %8s\n", | ||
227 | "No", "start", "end", "size", "flags"); | ||
228 | p += sprintf(p, "-------------------------------------------------\n"); | ||
229 | |||
230 | mutex_lock(&iommu_debug_lock); | ||
231 | |||
232 | list_for_each_entry(tmp, &obj->mmap, list) { | ||
233 | size_t len; | ||
234 | const char *str = "%3d %08x-%08x %6x %8x\n"; | ||
235 | const int maxcol = 39; | ||
236 | |||
237 | len = tmp->da_end - tmp->da_start; | ||
238 | p += snprintf(p, maxcol, str, | ||
239 | i, tmp->da_start, tmp->da_end, len, tmp->flags); | ||
240 | |||
241 | if (PAGE_SIZE - (p - buf) < maxcol) | ||
242 | break; | ||
243 | i++; | ||
244 | } | ||
245 | |||
246 | bytes = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf); | ||
247 | |||
248 | mutex_unlock(&iommu_debug_lock); | ||
249 | free_page((unsigned long)buf); | ||
250 | |||
251 | return bytes; | ||
252 | } | ||
253 | |||
254 | static ssize_t debug_read_mem(struct file *file, char __user *userbuf, | ||
255 | size_t count, loff_t *ppos) | ||
256 | { | ||
257 | struct omap_iommu *obj = file->private_data; | ||
258 | char *p, *buf; | ||
259 | struct iovm_struct *area; | ||
260 | ssize_t bytes; | ||
261 | |||
262 | count = min_t(ssize_t, count, PAGE_SIZE); | ||
263 | |||
264 | buf = (char *)__get_free_page(GFP_KERNEL); | ||
265 | if (!buf) | ||
266 | return -ENOMEM; | ||
267 | p = buf; | ||
268 | |||
269 | mutex_lock(&iommu_debug_lock); | ||
270 | |||
271 | area = omap_find_iovm_area(obj, (u32)ppos); | ||
272 | if (IS_ERR(area)) { | ||
273 | bytes = -EINVAL; | ||
274 | goto err_out; | ||
275 | } | ||
276 | memcpy(p, area->va, count); | ||
277 | p += count; | ||
278 | |||
279 | bytes = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf); | ||
280 | err_out: | ||
281 | mutex_unlock(&iommu_debug_lock); | ||
282 | free_page((unsigned long)buf); | ||
283 | |||
284 | return bytes; | ||
285 | } | ||
286 | |||
287 | static ssize_t debug_write_mem(struct file *file, const char __user *userbuf, | ||
288 | size_t count, loff_t *ppos) | ||
289 | { | ||
290 | struct omap_iommu *obj = file->private_data; | ||
291 | struct iovm_struct *area; | ||
292 | char *p, *buf; | ||
293 | |||
294 | count = min_t(size_t, count, PAGE_SIZE); | ||
295 | |||
296 | buf = (char *)__get_free_page(GFP_KERNEL); | ||
297 | if (!buf) | ||
298 | return -ENOMEM; | ||
299 | p = buf; | ||
300 | |||
301 | mutex_lock(&iommu_debug_lock); | ||
302 | |||
303 | if (copy_from_user(p, userbuf, count)) { | ||
304 | count = -EFAULT; | ||
305 | goto err_out; | ||
306 | } | ||
307 | |||
308 | area = omap_find_iovm_area(obj, (u32)ppos); | ||
309 | if (IS_ERR(area)) { | ||
310 | count = -EINVAL; | ||
311 | goto err_out; | ||
312 | } | ||
313 | memcpy(area->va, p, count); | ||
314 | err_out: | ||
315 | mutex_unlock(&iommu_debug_lock); | ||
316 | free_page((unsigned long)buf); | ||
317 | |||
318 | return count; | ||
319 | } | ||
320 | |||
321 | static int debug_open_generic(struct inode *inode, struct file *file) | ||
322 | { | ||
323 | file->private_data = inode->i_private; | ||
324 | return 0; | ||
325 | } | ||
326 | |||
327 | #define DEBUG_FOPS(name) \ | ||
328 | static const struct file_operations debug_##name##_fops = { \ | ||
329 | .open = debug_open_generic, \ | ||
330 | .read = debug_read_##name, \ | ||
331 | .write = debug_write_##name, \ | ||
332 | .llseek = generic_file_llseek, \ | ||
333 | }; | ||
334 | |||
335 | #define DEBUG_FOPS_RO(name) \ | ||
336 | static const struct file_operations debug_##name##_fops = { \ | ||
337 | .open = debug_open_generic, \ | ||
338 | .read = debug_read_##name, \ | ||
339 | .llseek = generic_file_llseek, \ | ||
340 | }; | ||
341 | |||
342 | DEBUG_FOPS_RO(ver); | ||
343 | DEBUG_FOPS_RO(regs); | ||
344 | DEBUG_FOPS_RO(tlb); | ||
345 | DEBUG_FOPS(pagetable); | ||
346 | DEBUG_FOPS_RO(mmap); | ||
347 | DEBUG_FOPS(mem); | ||
348 | |||
349 | #define __DEBUG_ADD_FILE(attr, mode) \ | ||
350 | { \ | ||
351 | struct dentry *dent; \ | ||
352 | dent = debugfs_create_file(#attr, mode, parent, \ | ||
353 | obj, &debug_##attr##_fops); \ | ||
354 | if (!dent) \ | ||
355 | return -ENOMEM; \ | ||
356 | } | ||
357 | |||
358 | #define DEBUG_ADD_FILE(name) __DEBUG_ADD_FILE(name, 600) | ||
359 | #define DEBUG_ADD_FILE_RO(name) __DEBUG_ADD_FILE(name, 400) | ||
360 | |||
361 | static int iommu_debug_register(struct device *dev, void *data) | ||
362 | { | ||
363 | struct platform_device *pdev = to_platform_device(dev); | ||
364 | struct omap_iommu *obj = platform_get_drvdata(pdev); | ||
365 | struct dentry *d, *parent; | ||
366 | |||
367 | if (!obj || !obj->dev) | ||
368 | return -EINVAL; | ||
369 | |||
370 | d = debugfs_create_dir(obj->name, iommu_debug_root); | ||
371 | if (!d) | ||
372 | return -ENOMEM; | ||
373 | parent = d; | ||
374 | |||
375 | d = debugfs_create_u8("nr_tlb_entries", 400, parent, | ||
376 | (u8 *)&obj->nr_tlb_entries); | ||
377 | if (!d) | ||
378 | return -ENOMEM; | ||
379 | |||
380 | DEBUG_ADD_FILE_RO(ver); | ||
381 | DEBUG_ADD_FILE_RO(regs); | ||
382 | DEBUG_ADD_FILE_RO(tlb); | ||
383 | DEBUG_ADD_FILE(pagetable); | ||
384 | DEBUG_ADD_FILE_RO(mmap); | ||
385 | DEBUG_ADD_FILE(mem); | ||
386 | |||
387 | return 0; | ||
388 | } | ||
389 | |||
390 | static int __init iommu_debug_init(void) | ||
391 | { | ||
392 | struct dentry *d; | ||
393 | int err; | ||
394 | |||
395 | d = debugfs_create_dir("iommu", NULL); | ||
396 | if (!d) | ||
397 | return -ENOMEM; | ||
398 | iommu_debug_root = d; | ||
399 | |||
400 | err = omap_foreach_iommu_device(d, iommu_debug_register); | ||
401 | if (err) | ||
402 | goto err_out; | ||
403 | return 0; | ||
404 | |||
405 | err_out: | ||
406 | debugfs_remove_recursive(iommu_debug_root); | ||
407 | return err; | ||
408 | } | ||
409 | module_init(iommu_debug_init) | ||
410 | |||
411 | static void __exit iommu_debugfs_exit(void) | ||
412 | { | ||
413 | debugfs_remove_recursive(iommu_debug_root); | ||
414 | } | ||
415 | module_exit(iommu_debugfs_exit) | ||
416 | |||
417 | MODULE_DESCRIPTION("omap iommu: debugfs interface"); | ||
418 | MODULE_AUTHOR("Hiroshi DOYU <Hiroshi.DOYU@nokia.com>"); | ||
419 | MODULE_LICENSE("GPL v2"); | ||