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> |
198a1649 |
17 | #include <linux/seq_file.h> |
18 | |
8d04105a |
19 | #include <linux/version.h> |
20 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11) |
21 | #include <linux/uaccess.h> |
22 | #else |
23 | #include <linux/init.h> |
24 | #include <linux/moduleparam.h> |
25 | #include <asm/uaccess.h> |
26 | #define __user |
27 | #define unlocked_ioctl ioctl |
28 | #endif |
29 | |
198a1649 |
30 | #define WARM_CODE |
31 | #include "../warm.h" |
32 | #include "warm_ops.h" |
33 | |
34 | #ifndef CONFIG_PROC_FS |
35 | #error need proc_fs |
36 | #endif |
37 | |
a6f015da |
38 | #define WARM_VER "r3" |
198a1649 |
39 | #define PFX "wARM: " |
40 | |
8d04105a |
41 | #define WARM_INFO(fmt, ...) \ |
42 | if (verbose) \ |
43 | pr_info(PFX fmt, ##__VA_ARGS__) |
44 | |
159a48bb |
45 | #define SECTION_SIZE 0x100000 |
198a1649 |
46 | #define MAX_CACHEOP_RANGE 16384 |
47 | |
48 | /* assume RAM starts at phys addr 0 (this is really machine specific) */ |
49 | #define RAM_PHYS_START 0 |
50 | #define RAM_MAX_SIZE 0x10000000 /* 256M, try to be future proof */ |
51 | |
8d04105a |
52 | /* expected CPU id */ |
53 | #if defined(CONFIG_CPU_ARM926T) |
54 | #define EXPECTED_ID 0x069260 |
55 | #elif defined(CONFIG_CPU_ARM920T) |
56 | #define EXPECTED_ID 0x029200 |
57 | #else |
58 | #error "unsupported CPU" |
59 | #endif |
60 | |
198a1649 |
61 | extern unsigned long max_mapnr; |
62 | |
63 | /* "upper" physical memory, not seen by Linux and to be mmap'ed */ |
64 | static u32 uppermem_start; |
65 | static u32 uppermem_end; |
55b74e0b |
66 | static spinlock_t lock; |
198a1649 |
67 | |
8d04105a |
68 | static int verbose; |
69 | |
198a1649 |
70 | static u32 *get_pgtable(void) |
71 | { |
72 | u32 ttb; |
73 | |
74 | /* get the pointer to the translation table base... */ |
75 | asm ("mrc p15, 0, %0, c2, c0, 0" : "=r"(ttb)); |
76 | |
77 | return __va((ttb) & 0xffffc000); |
78 | } |
79 | |
80 | static int do_set_cb_uppermem(int in_cb, int is_set) |
81 | { |
55b74e0b |
82 | unsigned long flags; |
198a1649 |
83 | u32 *pgtable, *cpt; |
84 | int i, j, count = 0; |
85 | int bits = 0; |
86 | |
87 | if (uppermem_end <= uppermem_start) |
88 | return -ENODEV; |
89 | |
90 | if (in_cb & WCB_C_BIT) |
91 | bits |= 8; |
92 | if (in_cb & WCB_B_BIT) |
93 | bits |= 4; |
94 | |
55b74e0b |
95 | spin_lock_irqsave(&lock, flags); |
96 | |
198a1649 |
97 | pgtable = get_pgtable(); |
98 | |
99 | for (i = 0; i < 4096; i++) |
100 | { |
8d04105a |
101 | if ((pgtable[i] & 3) != 1) |
102 | /* must be coarse page table */ |
198a1649 |
103 | continue; |
104 | |
105 | cpt = __va(pgtable[i] & 0xfffffc00); |
106 | |
107 | for (j = 0; j < 256; j++) |
108 | { |
109 | u32 addr, entry; |
110 | |
111 | entry = cpt[j]; |
112 | if (!(entry & 3)) |
113 | /* fault */ |
114 | continue; |
115 | |
116 | addr = entry & 0xfffff000; |
117 | if (uppermem_start <= addr && addr < uppermem_end) |
118 | { |
119 | pr_debug(PFX "%s C&B bits %08x\n", |
120 | is_set ? "set" : "clear", entry); |
121 | |
122 | if (is_set) |
123 | entry |= bits; |
124 | else |
125 | entry &= ~bits; |
126 | |
127 | /* need to also set AP bits (so that no faults |
128 | * happen and kernel doesn't touch this after us) */ |
129 | if ((entry & 3) == 3) |
130 | entry |= 0x030; /* tiny page */ |
131 | else |
132 | entry |= 0xff0; |
133 | |
134 | cpt[j] = entry; |
135 | count++; |
136 | } |
137 | } |
138 | } |
139 | |
140 | warm_cop_clean_d(); |
141 | warm_drain_wb_inval_tlb(); |
142 | |
55b74e0b |
143 | spin_unlock_irqrestore(&lock, flags); |
144 | |
8d04105a |
145 | WARM_INFO("%c%c bit(s) %s for phys %08x-%08x (%d pages)\n", |
198a1649 |
146 | bits & 8 ? 'c' : ' ', bits & 4 ? 'b' : ' ', |
147 | is_set ? "set" : "cleared", |
148 | uppermem_start, uppermem_end - 1, count); |
149 | |
150 | return 0; |
151 | } |
152 | |
153 | static int do_set_cb_virt(int in_cb, int is_set, u32 addr, u32 size) |
154 | { |
155 | int count = 0, bits = 0; |
55b74e0b |
156 | unsigned long flags; |
159a48bb |
157 | u32 desc1, desc2 = 0; |
158 | u32 *pgtable, *cpt = NULL; |
198a1649 |
159 | u32 start, end; |
cc951732 |
160 | u32 mask; |
198a1649 |
161 | |
162 | if (in_cb & WCB_C_BIT) |
163 | bits |= 8; |
164 | if (in_cb & WCB_B_BIT) |
165 | bits |= 4; |
166 | |
cc951732 |
167 | mask = PAGE_SIZE - 1; |
168 | size += addr & mask; |
169 | size = (size + mask) & ~mask; |
198a1649 |
170 | |
171 | addr &= ~(PAGE_SIZE - 1); |
172 | start = addr; |
173 | end = addr + size; |
174 | |
55b74e0b |
175 | spin_lock_irqsave(&lock, flags); |
176 | |
198a1649 |
177 | pgtable = get_pgtable(); |
178 | |
159a48bb |
179 | while (addr < end) |
198a1649 |
180 | { |
181 | desc1 = pgtable[addr >> 20]; |
182 | |
159a48bb |
183 | switch (desc1 & 3) { |
184 | case 0: |
55b74e0b |
185 | spin_unlock_irqrestore(&lock, flags); |
159a48bb |
186 | printk(KERN_WARNING PFX "address %08x not mapped.\n", addr); |
198a1649 |
187 | return -EINVAL; |
159a48bb |
188 | case 1: |
189 | /* coarse table */ |
190 | cpt = __va(desc1 & 0xfffffc00); |
191 | desc2 = cpt[(addr >> 12) & 0xff]; |
192 | break; |
193 | case 2: |
194 | /* section */ |
195 | if (is_set) |
196 | desc1 |= bits; |
197 | else |
198 | desc1 &= ~bits; |
199 | pgtable[addr >> 20] = desc1; |
200 | addr += SECTION_SIZE; |
201 | count++; |
202 | continue; |
203 | case 3: |
204 | cpt = __va(desc1 & 0xfffff000); |
205 | desc2 = cpt[(addr >> 10) & 0x3ff]; |
206 | break; |
8d04105a |
207 | } |
198a1649 |
208 | |
159a48bb |
209 | if ((desc2 & 3) == 0) { |
55b74e0b |
210 | spin_unlock_irqrestore(&lock, flags); |
159a48bb |
211 | printk(KERN_WARNING PFX "address %08x not mapped (%08x)\n", |
212 | addr, desc2); |
198a1649 |
213 | return -EINVAL; |
214 | } |
215 | |
216 | if (is_set) |
217 | desc2 |= bits; |
218 | else |
219 | desc2 &= ~bits; |
198a1649 |
220 | |
159a48bb |
221 | /* this might be bad idea, better let it fault so that Linux does |
222 | * it's accounting, but that will drop CB bits, so keep this |
223 | * for compatibility */ |
224 | if ((desc2 & 3) == 2) |
225 | desc2 |= 0xff0; |
226 | |
227 | switch (desc1 & 3) { |
228 | case 1: |
229 | cpt[(addr >> 12) & 0xff] = desc2; |
230 | break; |
231 | case 3: |
232 | cpt[(addr >> 10) & 0x3ff] = desc2; |
233 | break; |
234 | } |
235 | |
236 | addr += PAGE_SIZE; |
198a1649 |
237 | count++; |
238 | } |
239 | |
240 | warm_cop_clean_d(); |
241 | warm_drain_wb_inval_tlb(); |
242 | |
55b74e0b |
243 | spin_unlock_irqrestore(&lock, flags); |
244 | |
8d04105a |
245 | WARM_INFO("%c%c bit(s) %s virt %08x-%08x (%d pages)\n", |
198a1649 |
246 | bits & 8 ? 'c' : ' ', bits & 4 ? 'b' : ' ', |
247 | is_set ? "set" : "cleared", start, end - 1, count); |
248 | |
249 | return 0; |
250 | } |
251 | |
252 | static int do_virt2phys(unsigned long *_addr) |
253 | { |
254 | u32 addr = *_addr; |
255 | u32 desc1, desc2; |
256 | u32 *pgtable, *cpt; |
257 | |
258 | pgtable = get_pgtable(); |
259 | desc1 = pgtable[addr >> 20]; |
260 | |
8d04105a |
261 | switch (desc1 & 3) { |
262 | case 1: /* coarse table */ |
263 | cpt = __va(desc1 & 0xfffffc00); |
264 | desc2 = cpt[(addr >> 12) & 0xff]; |
265 | break; |
266 | case 2: /* 1MB section */ |
198a1649 |
267 | *_addr = (desc1 & 0xfff00000) | (addr & 0xfffff); |
268 | return 0; |
8d04105a |
269 | case 3: /* fine table */ |
270 | cpt = __va(desc1 & 0xfffff000); |
271 | desc2 = cpt[(addr >> 10) & 0x3ff]; |
cc951732 |
272 | break; |
8d04105a |
273 | default: |
274 | return -EINVAL; |
198a1649 |
275 | } |
159a48bb |
276 | |
8d04105a |
277 | switch (desc2 & 3) { |
278 | case 1: /* large page */ |
cc951732 |
279 | *_addr = (desc2 & ~0xffff) | (addr & 0xffff); |
8d04105a |
280 | break; |
281 | case 2: /* small page */ |
cc951732 |
282 | *_addr = (desc2 & ~0x0fff) | (addr & 0x0fff); |
8d04105a |
283 | break; |
284 | case 3: /* tiny page */ |
cc951732 |
285 | *_addr = (desc2 & ~0x03ff) | (addr & 0x03ff); |
8d04105a |
286 | break; |
287 | default: |
198a1649 |
288 | return -EINVAL; |
289 | } |
290 | |
198a1649 |
291 | return 0; |
292 | } |
293 | |
294 | static int do_cache_ops_whole(int ops) |
295 | { |
296 | if ((ops & (WOP_D_CLEAN|WOP_D_INVALIDATE)) == (WOP_D_CLEAN|WOP_D_INVALIDATE)) |
297 | warm_cop_clean_inval_d(); |
298 | |
299 | else if (ops & WOP_D_CLEAN) |
300 | warm_cop_clean_d(); |
301 | |
302 | else if (ops & WOP_D_INVALIDATE) { |
303 | printk(KERN_WARNING PFX "invalidate without clean is dangerous!\n"); |
304 | warm_cop_inval_d(); |
305 | } |
306 | |
307 | if (ops & WOP_I_INVALIDATE) |
308 | warm_cop_inval_i(); |
309 | |
310 | warm_cop_drain_wb(); |
311 | return 0; |
312 | } |
313 | |
314 | static int do_cache_ops(int ops, u32 addr, u32 size) |
315 | { |
316 | if (addr & 31) { |
317 | size += addr & 31; |
318 | addr &= ~31; |
319 | } |
320 | |
321 | switch (ops) { |
322 | case WOP_D_CLEAN|WOP_D_INVALIDATE|WOP_I_INVALIDATE: |
323 | warm_cop_r_clean_d_inval_di(addr, size); |
324 | break; |
325 | case WOP_D_CLEAN|WOP_D_INVALIDATE: |
326 | warm_cop_r_clean_d_inval_d(addr, size); |
327 | break; |
328 | case WOP_D_CLEAN|WOP_I_INVALIDATE: |
329 | warm_cop_r_clean_d_inval_i(addr, size); |
330 | break; |
331 | case WOP_D_CLEAN: |
332 | warm_cop_r_clean_d(addr, size); |
333 | break; |
334 | case WOP_D_INVALIDATE|WOP_I_INVALIDATE: |
335 | warm_cop_r_inval_di(addr, size); |
336 | break; |
337 | case WOP_D_INVALIDATE: |
338 | warm_cop_r_inval_d(addr, size); |
339 | break; |
340 | case WOP_I_INVALIDATE: |
341 | warm_cop_r_inval_i(addr, size); |
342 | break; |
343 | default: |
344 | /* only drain wb */ |
345 | break; |
346 | } |
347 | |
348 | warm_cop_drain_wb(); |
349 | return 0; |
350 | } |
351 | |
87956811 |
352 | static int do_map_op(u32 vaddr, u32 paddr, u32 size, int cb, int is_unmap) |
353 | { |
354 | int count = 0, retval = 0; |
55b74e0b |
355 | unsigned long flags; |
87956811 |
356 | u32 pstart, start, end; |
357 | u32 desc1, apcb_bits; |
358 | u32 *pgtable; |
359 | u32 v, mask; |
360 | |
361 | apcb_bits = (3 << 10) | (1 << 5); /* r/w, dom 1 */ |
362 | if (cb & WCB_C_BIT) |
363 | apcb_bits |= 8; |
364 | if (cb & WCB_B_BIT) |
365 | apcb_bits |= 4; |
87956811 |
366 | |
367 | mask = SECTION_SIZE - 1; |
368 | size = (size + mask) & ~mask; |
369 | |
370 | pstart = paddr; |
371 | start = vaddr; |
372 | end = start + size; |
373 | |
374 | /* check for overflows */ |
375 | if (end - 1 < start) |
376 | return -EINVAL; |
377 | if (pstart + size - 1 < pstart) |
378 | return -EINVAL; |
379 | |
55b74e0b |
380 | spin_lock_irqsave(&lock, flags); |
381 | |
87956811 |
382 | pgtable = get_pgtable(); |
383 | |
384 | for (; vaddr < end; vaddr += SECTION_SIZE, paddr += SECTION_SIZE) |
385 | { |
386 | desc1 = pgtable[vaddr >> 20]; |
387 | |
388 | if (is_unmap) { |
389 | if ((desc1 & 3) != 2) { |
55b74e0b |
390 | spin_unlock_irqrestore(&lock, flags); |
87956811 |
391 | printk(KERN_WARNING PFX "vaddr %08x is not a section? (%08x)\n", |
392 | vaddr, desc1); |
393 | return -EINVAL; |
394 | } |
395 | v = 0; |
396 | } else { |
397 | if ((desc1 & 3) != 0) { |
398 | printk(KERN_WARNING PFX "vaddr %08x already mapped? (%08x)\n", |
399 | vaddr, desc1); |
400 | retval = -EINVAL; |
401 | break; |
402 | } |
403 | v = (paddr & ~mask) | apcb_bits | 0x12; |
404 | } |
405 | |
406 | pgtable[vaddr >> 20] = v; |
407 | count++; |
408 | } |
409 | |
410 | if (retval != 0) { |
411 | /* undo mappings */ |
412 | vaddr = start; |
413 | |
414 | for (; vaddr < end && count > 0; vaddr += SECTION_SIZE, count--) |
415 | pgtable[vaddr >> 20] = 0; |
416 | } |
417 | |
418 | warm_cop_clean_d(); |
419 | warm_drain_wb_inval_tlb(); |
420 | |
55b74e0b |
421 | spin_unlock_irqrestore(&lock, flags); |
422 | |
87956811 |
423 | if (retval == 0 && !is_unmap) { |
424 | WARM_INFO("mapped %08x to %08x with %c%c bit(s) (%d section(s))\n", |
425 | start, pstart, apcb_bits & 8 ? 'c' : ' ', |
426 | apcb_bits & 4 ? 'b' : ' ', count); |
427 | } |
428 | |
429 | return retval; |
430 | } |
431 | |
8d04105a |
432 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11) |
198a1649 |
433 | static long warm_ioctl(struct file *file, unsigned int cmd, unsigned long __arg) |
8d04105a |
434 | #else |
435 | static int warm_ioctl(struct inode *inode, struct file *file, |
436 | unsigned int cmd, unsigned long __arg) |
437 | #endif |
198a1649 |
438 | { |
439 | void __user *arg = (void __user *) __arg; |
440 | union { |
441 | struct warm_cache_op wcop; |
442 | struct warm_change_cb ccb; |
87956811 |
443 | struct warm_map_op mop; |
198a1649 |
444 | unsigned long addr; |
445 | } u; |
446 | long ret; |
447 | |
448 | switch (cmd) { |
449 | case WARMC_CACHE_OP: |
450 | if (copy_from_user(&u.wcop, arg, sizeof(u.wcop))) |
451 | return -EFAULT; |
452 | if (u.wcop.ops & ~(WOP_D_CLEAN|WOP_D_INVALIDATE|WOP_I_INVALIDATE)) |
453 | return -EINVAL; |
8d04105a |
454 | if (u.wcop.size == (unsigned long)-1 || |
455 | (u.wcop.size > MAX_CACHEOP_RANGE && !(u.wcop.ops & WOP_D_INVALIDATE))) |
198a1649 |
456 | ret = do_cache_ops_whole(u.wcop.ops); |
457 | else |
458 | ret = do_cache_ops(u.wcop.ops, u.wcop.addr, u.wcop.size); |
459 | break; |
460 | case WARMC_CHANGE_CB: |
461 | if (copy_from_user(&u.ccb, arg, sizeof(u.ccb))) |
462 | return -EFAULT; |
463 | if (u.ccb.cb & ~(WCB_C_BIT|WCB_B_BIT)) |
464 | return -EINVAL; |
465 | if (u.ccb.addr == 0 && u.ccb.size == 0) |
466 | ret = do_set_cb_uppermem(u.ccb.cb, u.ccb.is_set); |
467 | else |
468 | ret = do_set_cb_virt(u.ccb.cb, u.ccb.is_set, u.ccb.addr, u.ccb.size); |
469 | break; |
470 | case WARMC_VIRT2PHYS: |
471 | if (copy_from_user(&u.addr, arg, sizeof(u.addr))) |
472 | return -EFAULT; |
473 | ret = do_virt2phys(&u.addr); |
474 | if (copy_to_user(arg, &u.addr, sizeof(u.addr))) |
475 | return -EFAULT; |
476 | break; |
87956811 |
477 | case WARMC_MMAP: |
478 | if (copy_from_user(&u.mop, arg, sizeof(u.mop))) |
479 | return -EFAULT; |
480 | if (u.mop.cb & ~(WCB_C_BIT|WCB_B_BIT)) |
481 | return -EINVAL; |
482 | ret = do_map_op(u.mop.virt_addr, u.mop.phys_addr, u.mop.size, |
483 | u.mop.cb, u.mop.is_unmap); |
484 | break; |
198a1649 |
485 | default: |
486 | ret = -ENOTTY; |
487 | break; |
488 | } |
489 | |
490 | return ret; |
491 | } |
492 | |
493 | static const char *warm_implementor_name(char code) |
494 | { |
495 | switch (code) { |
496 | case 'A': |
497 | return "ARM"; |
498 | case 'D': |
499 | return "DEC"; |
500 | case 'i': |
501 | return "Intel"; |
502 | case 'M': |
503 | return "Motorola - Freescale"; |
504 | case 'V': |
505 | return "Marvell"; |
506 | } |
507 | return "???"; |
508 | } |
509 | |
510 | static const char *warm_arch_name(int code) |
511 | { |
512 | switch (code) { |
513 | case 1: |
514 | return "4"; |
515 | case 2: |
516 | return "4T"; |
517 | case 3: |
518 | return "5"; |
519 | case 4: |
520 | return "5T"; |
521 | case 5: |
522 | return "5TE"; |
523 | case 6: |
524 | return "5TEJ"; |
525 | case 7: |
526 | return "6"; |
527 | } |
528 | return "?"; |
529 | } |
530 | |
531 | static int warm_cache_size(int code, int m) |
532 | { |
533 | int base = 512; |
534 | if (m) |
535 | base = 768; |
536 | return base << code; |
537 | } |
538 | |
539 | static int warm_cache_assoc(int code, int m) |
540 | { |
541 | int base = 2; |
542 | if (code == 0) |
543 | return m ? 0 : 1; |
544 | if (m) |
545 | base = 3; |
546 | return base << (code - 1); |
547 | } |
548 | |
549 | static int warm_cache_line(int code) |
550 | { |
551 | return 8 << code; |
552 | } |
553 | |
554 | static int warm_seq_show(struct seq_file *seq, void *offset) |
555 | { |
556 | u32 tmp; |
557 | |
558 | seq_printf(seq, "wARM: " WARM_VER "\n"); |
559 | |
560 | /* ID codes */ |
561 | asm ("mrc p15, 0, %0, c0, c0, 0" : "=r"(tmp)); |
562 | seq_printf(seq, "ID: %08x\n", tmp); |
563 | if (tmp & 0x80000) { |
564 | /* revised format, not yet documented */ |
565 | } else if ((tmp & 0xf000) == 0) { |
566 | /* pre-ARM7 */ |
567 | seq_printf(seq, "Architecture: %d\n", |
568 | (tmp & 0xf00) == 0x600 ? 3 : 2); |
569 | seq_printf(seq, "Variant: %d%d0\n", (tmp & 0xf00) >> 8, |
570 | (tmp & 0xf0) >> 4); |
571 | } else { |
572 | seq_printf(seq, "Implementor: %c (%s)\n", tmp >> 24, |
573 | warm_implementor_name(tmp >> 24)); |
574 | if ((tmp & 0xf000) == 7) { |
575 | seq_printf(seq, "Architecture: %s\n", |
576 | tmp & 0x800000 ? "4T" : "3"); |
577 | seq_printf(seq, "Variant: 0x%x\n", (tmp & 0x7f0000) >> 16); |
578 | } else { |
579 | seq_printf(seq, "Architecture: %s\n", |
580 | warm_arch_name((tmp & 0x0f0000) >> 16)); |
581 | seq_printf(seq, "Variant: 0x%x\n", (tmp & 0xf00000) >> 20); |
582 | } |
583 | seq_printf(seq, "Part number: 0x%x\n", (tmp & 0xfff0) >> 4); |
584 | } |
585 | seq_printf(seq, "Revision: 0x%x\n", tmp & 0xf); |
586 | |
587 | /* cache type */ |
588 | asm ("mrc p15, 0, %0, c0, c0, 1" : "=r"(tmp)); |
589 | seq_printf(seq, "Cache ctype: 0x%x\n", (tmp & 0x1e000000) >> 25); |
590 | seq_printf(seq, "Cache unified: %s\n", (tmp & 0x01000000) ? "no" : "yes"); |
591 | seq_printf(seq, "DCache size: %d\n", |
592 | warm_cache_size((tmp >> (6+12)) & 0xf, (tmp >> (2+12)) & 1)); |
593 | seq_printf(seq, "DCache associativity: %d\n", |
594 | warm_cache_assoc((tmp >> (3+12)) & 7, (tmp >> (2+12)) & 1)); |
595 | seq_printf(seq, "DCache line size: %d\n", |
596 | warm_cache_line((tmp >> (0+12)) & 3)); |
597 | seq_printf(seq, "ICache size: %d\n", |
598 | warm_cache_size((tmp >> 6) & 0xf, (tmp >> 2) & 1)); |
599 | seq_printf(seq, "ICache associativity: %d\n", |
600 | warm_cache_assoc((tmp >> 3) & 7, (tmp >> 2) & 1)); |
601 | seq_printf(seq, "ICache line size: %d\n", |
602 | warm_cache_line((tmp >> 0) & 3)); |
603 | |
604 | return 0; |
605 | } |
606 | |
607 | static int warm_open(struct inode *inode, struct file *file) |
608 | { |
609 | return single_open(file, warm_seq_show, NULL); |
610 | } |
611 | |
612 | static int warm_close(struct inode *ino, struct file *file) |
613 | { |
614 | return single_release(ino, file); |
615 | } |
616 | |
617 | static const struct file_operations warm_fops = { |
618 | .owner = THIS_MODULE, |
619 | .open = warm_open, |
620 | .read = seq_read, |
621 | .llseek = seq_lseek, |
622 | .unlocked_ioctl = warm_ioctl, |
623 | .release = warm_close, |
624 | }; |
625 | |
626 | static int __init warm_module_init(void) |
627 | { |
628 | struct proc_dir_entry *pret; |
8d04105a |
629 | u32 cpuid; |
630 | |
631 | asm ("mrc p15, 0, %0, c0, c0, 0" : "=r"(cpuid)); |
632 | if ((cpuid & 0x0ffff0) != EXPECTED_ID) { |
633 | printk(KERN_ERR PFX "module was compiled for different CPU, aborting\n"); |
634 | return -1; |
635 | } |
198a1649 |
636 | |
637 | pret = create_proc_entry("warm", S_IWUGO | S_IRUGO, NULL); |
638 | if (!pret) { |
639 | printk(KERN_ERR PFX "can't create proc entry\n"); |
640 | return -1; |
641 | } |
642 | |
643 | pret->owner = THIS_MODULE; |
644 | pret->proc_fops = &warm_fops; |
645 | |
55b74e0b |
646 | spin_lock_init(&lock); |
647 | |
198a1649 |
648 | uppermem_start = RAM_PHYS_START + (max_mapnr << PAGE_SHIFT); |
649 | uppermem_end = RAM_PHYS_START + RAM_MAX_SIZE; |
650 | |
651 | pr_info(PFX WARM_VER " loaded, "); |
652 | if (uppermem_end <= uppermem_start) |
653 | printk("no upper mem"); |
654 | else |
655 | printk("upper mem %08x-%08x", uppermem_start, uppermem_end - 1); |
656 | printk("\n"); |
657 | |
658 | /* give time for /proc node to appear */ |
659 | mdelay(200); |
660 | |
661 | return 0; |
662 | } |
663 | |
664 | static void __exit warm_module_exit(void) |
665 | { |
666 | remove_proc_entry("warm", NULL); |
667 | |
668 | pr_info(PFX "unloaded.\n"); |
669 | } |
670 | |
671 | module_init(warm_module_init); |
672 | module_exit(warm_module_exit); |
673 | |
8d04105a |
674 | module_param(verbose, int, 0644); |
675 | |
198a1649 |
676 | MODULE_LICENSE("GPL"); |
677 | MODULE_DESCRIPTION("ARM processor services"); |
678 | MODULE_AUTHOR("Grazvydas Ignotas"); |