198a1649 |
1 | /* |
2 | * wARM - exporting ARM processor specific privileged services to userspace |
3 | * kernelspace part |
4 | * |
5 | * Author: GraÅžvydas "notaz" Ignotas |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License version 2 as |
9 | * published by the Free Software Foundation. |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/proc_fs.h> |
15 | #include <linux/delay.h> |
16 | #include <linux/fs.h> |
17 | #include <linux/uaccess.h> |
18 | #include <linux/seq_file.h> |
19 | |
20 | #define WARM_CODE |
21 | #include "../warm.h" |
22 | #include "warm_ops.h" |
23 | |
24 | #ifndef CONFIG_PROC_FS |
25 | #error need proc_fs |
26 | #endif |
27 | |
28 | #define WARM_VER "r1" |
29 | #define PFX "wARM: " |
30 | |
31 | #define MAX_CACHEOP_RANGE 16384 |
32 | |
33 | /* assume RAM starts at phys addr 0 (this is really machine specific) */ |
34 | #define RAM_PHYS_START 0 |
35 | #define RAM_MAX_SIZE 0x10000000 /* 256M, try to be future proof */ |
36 | |
37 | extern unsigned long max_mapnr; |
38 | |
39 | /* "upper" physical memory, not seen by Linux and to be mmap'ed */ |
40 | static u32 uppermem_start; |
41 | static u32 uppermem_end; |
42 | |
43 | static u32 *get_pgtable(void) |
44 | { |
45 | u32 ttb; |
46 | |
47 | /* get the pointer to the translation table base... */ |
48 | asm ("mrc p15, 0, %0, c2, c0, 0" : "=r"(ttb)); |
49 | |
50 | return __va((ttb) & 0xffffc000); |
51 | } |
52 | |
53 | static int do_set_cb_uppermem(int in_cb, int is_set) |
54 | { |
55 | u32 *pgtable, *cpt; |
56 | int i, j, count = 0; |
57 | int bits = 0; |
58 | |
59 | if (uppermem_end <= uppermem_start) |
60 | return -ENODEV; |
61 | |
62 | if (in_cb & WCB_C_BIT) |
63 | bits |= 8; |
64 | if (in_cb & WCB_B_BIT) |
65 | bits |= 4; |
66 | |
67 | pgtable = get_pgtable(); |
68 | |
69 | for (i = 0; i < 4096; i++) |
70 | { |
71 | if (!(pgtable[i] & 1)) |
72 | /* must be course of fine page table */ |
73 | continue; |
74 | |
75 | cpt = __va(pgtable[i] & 0xfffffc00); |
76 | |
77 | for (j = 0; j < 256; j++) |
78 | { |
79 | u32 addr, entry; |
80 | |
81 | entry = cpt[j]; |
82 | if (!(entry & 3)) |
83 | /* fault */ |
84 | continue; |
85 | |
86 | addr = entry & 0xfffff000; |
87 | if (uppermem_start <= addr && addr < uppermem_end) |
88 | { |
89 | pr_debug(PFX "%s C&B bits %08x\n", |
90 | is_set ? "set" : "clear", entry); |
91 | |
92 | if (is_set) |
93 | entry |= bits; |
94 | else |
95 | entry &= ~bits; |
96 | |
97 | /* need to also set AP bits (so that no faults |
98 | * happen and kernel doesn't touch this after us) */ |
99 | if ((entry & 3) == 3) |
100 | entry |= 0x030; /* tiny page */ |
101 | else |
102 | entry |= 0xff0; |
103 | |
104 | cpt[j] = entry; |
105 | count++; |
106 | } |
107 | } |
108 | } |
109 | |
110 | warm_cop_clean_d(); |
111 | warm_drain_wb_inval_tlb(); |
112 | |
113 | pr_info(PFX "%c%c bit(s) %s for phys %08x-%08x (%d pages)\n", |
114 | bits & 8 ? 'c' : ' ', bits & 4 ? 'b' : ' ', |
115 | is_set ? "set" : "cleared", |
116 | uppermem_start, uppermem_end - 1, count); |
117 | |
118 | return 0; |
119 | } |
120 | |
121 | static int do_set_cb_virt(int in_cb, int is_set, u32 addr, u32 size) |
122 | { |
123 | int count = 0, bits = 0; |
124 | u32 desc1, desc2; |
125 | u32 *pgtable, *cpt; |
126 | u32 start, end; |
127 | |
128 | if (in_cb & WCB_C_BIT) |
129 | bits |= 8; |
130 | if (in_cb & WCB_B_BIT) |
131 | bits |= 4; |
132 | |
133 | size += addr & ~(PAGE_SIZE - 1); |
134 | size = ALIGN(size, PAGE_SIZE); |
135 | |
136 | addr &= ~(PAGE_SIZE - 1); |
137 | start = addr; |
138 | end = addr + size; |
139 | |
140 | pgtable = get_pgtable(); |
141 | |
142 | for (; addr < end; addr += PAGE_SIZE) |
143 | { |
144 | desc1 = pgtable[addr >> 20]; |
145 | |
146 | if (!(desc1 & 3)) |
147 | return -EINVAL; |
148 | |
149 | cpt = __va(desc1 & 0xfffffc00); |
150 | desc2 = cpt[(addr >> 12) & 0xff]; |
151 | |
152 | if ((desc2 & 3) != 2) { |
153 | printk(KERN_WARNING PFX "not small page? %08x %08x\n", desc2, addr); |
154 | return -EINVAL; |
155 | } |
156 | |
157 | if (is_set) |
158 | desc2 |= bits; |
159 | else |
160 | desc2 &= ~bits; |
161 | desc2 |= 0xff0; |
162 | |
163 | cpt[(addr >> 12) & 0xff] = desc2; |
164 | count++; |
165 | } |
166 | |
167 | warm_cop_clean_d(); |
168 | warm_drain_wb_inval_tlb(); |
169 | |
170 | pr_info(PFX "%c%c bit(s) %s virt %08x-%08x (%d pages)\n", |
171 | bits & 8 ? 'c' : ' ', bits & 4 ? 'b' : ' ', |
172 | is_set ? "set" : "cleared", start, end - 1, count); |
173 | |
174 | return 0; |
175 | } |
176 | |
177 | static int do_virt2phys(unsigned long *_addr) |
178 | { |
179 | u32 addr = *_addr; |
180 | u32 desc1, desc2; |
181 | u32 *pgtable, *cpt; |
182 | |
183 | pgtable = get_pgtable(); |
184 | desc1 = pgtable[addr >> 20]; |
185 | |
186 | if (!(desc1 & 3)) |
187 | return -EINVAL; |
188 | |
189 | if ((desc1 & 3) == 2) { |
190 | /* 1MB section */ |
191 | *_addr = (desc1 & 0xfff00000) | (addr & 0xfffff); |
192 | return 0; |
193 | } |
194 | |
195 | cpt = __va(desc1 & 0xfffffc00); |
196 | desc2 = cpt[(addr >> 12) & 0xff]; |
197 | |
198 | if ((desc2 & 3) != 2) { |
199 | printk(KERN_WARNING PFX "not small page? %08x %08x\n", desc2, addr); |
200 | return -EINVAL; |
201 | } |
202 | |
203 | *_addr = (desc2 & 0xfffffc00) | (addr & 0x3ff); |
204 | return 0; |
205 | } |
206 | |
207 | static int do_cache_ops_whole(int ops) |
208 | { |
209 | if ((ops & (WOP_D_CLEAN|WOP_D_INVALIDATE)) == (WOP_D_CLEAN|WOP_D_INVALIDATE)) |
210 | warm_cop_clean_inval_d(); |
211 | |
212 | else if (ops & WOP_D_CLEAN) |
213 | warm_cop_clean_d(); |
214 | |
215 | else if (ops & WOP_D_INVALIDATE) { |
216 | printk(KERN_WARNING PFX "invalidate without clean is dangerous!\n"); |
217 | warm_cop_inval_d(); |
218 | } |
219 | |
220 | if (ops & WOP_I_INVALIDATE) |
221 | warm_cop_inval_i(); |
222 | |
223 | warm_cop_drain_wb(); |
224 | return 0; |
225 | } |
226 | |
227 | static int do_cache_ops(int ops, u32 addr, u32 size) |
228 | { |
229 | if (addr & 31) { |
230 | size += addr & 31; |
231 | addr &= ~31; |
232 | } |
233 | |
234 | switch (ops) { |
235 | case WOP_D_CLEAN|WOP_D_INVALIDATE|WOP_I_INVALIDATE: |
236 | warm_cop_r_clean_d_inval_di(addr, size); |
237 | break; |
238 | case WOP_D_CLEAN|WOP_D_INVALIDATE: |
239 | warm_cop_r_clean_d_inval_d(addr, size); |
240 | break; |
241 | case WOP_D_CLEAN|WOP_I_INVALIDATE: |
242 | warm_cop_r_clean_d_inval_i(addr, size); |
243 | break; |
244 | case WOP_D_CLEAN: |
245 | warm_cop_r_clean_d(addr, size); |
246 | break; |
247 | case WOP_D_INVALIDATE|WOP_I_INVALIDATE: |
248 | warm_cop_r_inval_di(addr, size); |
249 | break; |
250 | case WOP_D_INVALIDATE: |
251 | warm_cop_r_inval_d(addr, size); |
252 | break; |
253 | case WOP_I_INVALIDATE: |
254 | warm_cop_r_inval_i(addr, size); |
255 | break; |
256 | default: |
257 | /* only drain wb */ |
258 | break; |
259 | } |
260 | |
261 | warm_cop_drain_wb(); |
262 | return 0; |
263 | } |
264 | |
265 | static long warm_ioctl(struct file *file, unsigned int cmd, unsigned long __arg) |
266 | { |
267 | void __user *arg = (void __user *) __arg; |
268 | union { |
269 | struct warm_cache_op wcop; |
270 | struct warm_change_cb ccb; |
271 | unsigned long addr; |
272 | } u; |
273 | long ret; |
274 | |
275 | switch (cmd) { |
276 | case WARMC_CACHE_OP: |
277 | if (copy_from_user(&u.wcop, arg, sizeof(u.wcop))) |
278 | return -EFAULT; |
279 | if (u.wcop.ops & ~(WOP_D_CLEAN|WOP_D_INVALIDATE|WOP_I_INVALIDATE)) |
280 | return -EINVAL; |
281 | if (u.wcop.size > MAX_CACHEOP_RANGE) |
282 | ret = do_cache_ops_whole(u.wcop.ops); |
283 | else |
284 | ret = do_cache_ops(u.wcop.ops, u.wcop.addr, u.wcop.size); |
285 | break; |
286 | case WARMC_CHANGE_CB: |
287 | if (copy_from_user(&u.ccb, arg, sizeof(u.ccb))) |
288 | return -EFAULT; |
289 | if (u.ccb.cb & ~(WCB_C_BIT|WCB_B_BIT)) |
290 | return -EINVAL; |
291 | if (u.ccb.addr == 0 && u.ccb.size == 0) |
292 | ret = do_set_cb_uppermem(u.ccb.cb, u.ccb.is_set); |
293 | else |
294 | ret = do_set_cb_virt(u.ccb.cb, u.ccb.is_set, u.ccb.addr, u.ccb.size); |
295 | break; |
296 | case WARMC_VIRT2PHYS: |
297 | if (copy_from_user(&u.addr, arg, sizeof(u.addr))) |
298 | return -EFAULT; |
299 | ret = do_virt2phys(&u.addr); |
300 | if (copy_to_user(arg, &u.addr, sizeof(u.addr))) |
301 | return -EFAULT; |
302 | break; |
303 | default: |
304 | ret = -ENOTTY; |
305 | break; |
306 | } |
307 | |
308 | return ret; |
309 | } |
310 | |
311 | static const char *warm_implementor_name(char code) |
312 | { |
313 | switch (code) { |
314 | case 'A': |
315 | return "ARM"; |
316 | case 'D': |
317 | return "DEC"; |
318 | case 'i': |
319 | return "Intel"; |
320 | case 'M': |
321 | return "Motorola - Freescale"; |
322 | case 'V': |
323 | return "Marvell"; |
324 | } |
325 | return "???"; |
326 | } |
327 | |
328 | static const char *warm_arch_name(int code) |
329 | { |
330 | switch (code) { |
331 | case 1: |
332 | return "4"; |
333 | case 2: |
334 | return "4T"; |
335 | case 3: |
336 | return "5"; |
337 | case 4: |
338 | return "5T"; |
339 | case 5: |
340 | return "5TE"; |
341 | case 6: |
342 | return "5TEJ"; |
343 | case 7: |
344 | return "6"; |
345 | } |
346 | return "?"; |
347 | } |
348 | |
349 | static int warm_cache_size(int code, int m) |
350 | { |
351 | int base = 512; |
352 | if (m) |
353 | base = 768; |
354 | return base << code; |
355 | } |
356 | |
357 | static int warm_cache_assoc(int code, int m) |
358 | { |
359 | int base = 2; |
360 | if (code == 0) |
361 | return m ? 0 : 1; |
362 | if (m) |
363 | base = 3; |
364 | return base << (code - 1); |
365 | } |
366 | |
367 | static int warm_cache_line(int code) |
368 | { |
369 | return 8 << code; |
370 | } |
371 | |
372 | static int warm_seq_show(struct seq_file *seq, void *offset) |
373 | { |
374 | u32 tmp; |
375 | |
376 | seq_printf(seq, "wARM: " WARM_VER "\n"); |
377 | |
378 | /* ID codes */ |
379 | asm ("mrc p15, 0, %0, c0, c0, 0" : "=r"(tmp)); |
380 | seq_printf(seq, "ID: %08x\n", tmp); |
381 | if (tmp & 0x80000) { |
382 | /* revised format, not yet documented */ |
383 | } else if ((tmp & 0xf000) == 0) { |
384 | /* pre-ARM7 */ |
385 | seq_printf(seq, "Architecture: %d\n", |
386 | (tmp & 0xf00) == 0x600 ? 3 : 2); |
387 | seq_printf(seq, "Variant: %d%d0\n", (tmp & 0xf00) >> 8, |
388 | (tmp & 0xf0) >> 4); |
389 | } else { |
390 | seq_printf(seq, "Implementor: %c (%s)\n", tmp >> 24, |
391 | warm_implementor_name(tmp >> 24)); |
392 | if ((tmp & 0xf000) == 7) { |
393 | seq_printf(seq, "Architecture: %s\n", |
394 | tmp & 0x800000 ? "4T" : "3"); |
395 | seq_printf(seq, "Variant: 0x%x\n", (tmp & 0x7f0000) >> 16); |
396 | } else { |
397 | seq_printf(seq, "Architecture: %s\n", |
398 | warm_arch_name((tmp & 0x0f0000) >> 16)); |
399 | seq_printf(seq, "Variant: 0x%x\n", (tmp & 0xf00000) >> 20); |
400 | } |
401 | seq_printf(seq, "Part number: 0x%x\n", (tmp & 0xfff0) >> 4); |
402 | } |
403 | seq_printf(seq, "Revision: 0x%x\n", tmp & 0xf); |
404 | |
405 | /* cache type */ |
406 | asm ("mrc p15, 0, %0, c0, c0, 1" : "=r"(tmp)); |
407 | seq_printf(seq, "Cache ctype: 0x%x\n", (tmp & 0x1e000000) >> 25); |
408 | seq_printf(seq, "Cache unified: %s\n", (tmp & 0x01000000) ? "no" : "yes"); |
409 | seq_printf(seq, "DCache size: %d\n", |
410 | warm_cache_size((tmp >> (6+12)) & 0xf, (tmp >> (2+12)) & 1)); |
411 | seq_printf(seq, "DCache associativity: %d\n", |
412 | warm_cache_assoc((tmp >> (3+12)) & 7, (tmp >> (2+12)) & 1)); |
413 | seq_printf(seq, "DCache line size: %d\n", |
414 | warm_cache_line((tmp >> (0+12)) & 3)); |
415 | seq_printf(seq, "ICache size: %d\n", |
416 | warm_cache_size((tmp >> 6) & 0xf, (tmp >> 2) & 1)); |
417 | seq_printf(seq, "ICache associativity: %d\n", |
418 | warm_cache_assoc((tmp >> 3) & 7, (tmp >> 2) & 1)); |
419 | seq_printf(seq, "ICache line size: %d\n", |
420 | warm_cache_line((tmp >> 0) & 3)); |
421 | |
422 | return 0; |
423 | } |
424 | |
425 | static int warm_open(struct inode *inode, struct file *file) |
426 | { |
427 | return single_open(file, warm_seq_show, NULL); |
428 | } |
429 | |
430 | static int warm_close(struct inode *ino, struct file *file) |
431 | { |
432 | return single_release(ino, file); |
433 | } |
434 | |
435 | static const struct file_operations warm_fops = { |
436 | .owner = THIS_MODULE, |
437 | .open = warm_open, |
438 | .read = seq_read, |
439 | .llseek = seq_lseek, |
440 | .unlocked_ioctl = warm_ioctl, |
441 | .release = warm_close, |
442 | }; |
443 | |
444 | static int __init warm_module_init(void) |
445 | { |
446 | struct proc_dir_entry *pret; |
447 | |
448 | pret = create_proc_entry("warm", S_IWUGO | S_IRUGO, NULL); |
449 | if (!pret) { |
450 | printk(KERN_ERR PFX "can't create proc entry\n"); |
451 | return -1; |
452 | } |
453 | |
454 | pret->owner = THIS_MODULE; |
455 | pret->proc_fops = &warm_fops; |
456 | |
457 | uppermem_start = RAM_PHYS_START + (max_mapnr << PAGE_SHIFT); |
458 | uppermem_end = RAM_PHYS_START + RAM_MAX_SIZE; |
459 | |
460 | pr_info(PFX WARM_VER " loaded, "); |
461 | if (uppermem_end <= uppermem_start) |
462 | printk("no upper mem"); |
463 | else |
464 | printk("upper mem %08x-%08x", uppermem_start, uppermem_end - 1); |
465 | printk("\n"); |
466 | |
467 | /* give time for /proc node to appear */ |
468 | mdelay(200); |
469 | |
470 | return 0; |
471 | } |
472 | |
473 | static void __exit warm_module_exit(void) |
474 | { |
475 | remove_proc_entry("warm", NULL); |
476 | |
477 | pr_info(PFX "unloaded.\n"); |
478 | } |
479 | |
480 | module_init(warm_module_init); |
481 | module_exit(warm_module_exit); |
482 | |
483 | MODULE_LICENSE("GPL"); |
484 | MODULE_DESCRIPTION("ARM processor services"); |
485 | MODULE_AUTHOR("Grazvydas Ignotas"); |