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