support section mmap, update test
[warm.git] / module / warm_main.c
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
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;
66
67 static int verbose;
68
69 static 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
79 static 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
147 static 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
240 static 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
282 static 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
302 static 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
340 static 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)
416 static long warm_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)
417 #else
418 static 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
476 static 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
493 static 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
514 static 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
522 static 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
532 static int warm_cache_line(int code)
533 {
534         return 8 << code;
535 }
536
537 static 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
590 static int warm_open(struct inode *inode, struct file *file)
591 {
592         return single_open(file, warm_seq_show, NULL);
593 }
594
595 static int warm_close(struct inode *ino, struct file *file)
596 {
597         return single_release(ino, file);
598 }
599
600 static 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
609 static 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
645 static void __exit warm_module_exit(void)
646 {
647         remove_proc_entry("warm", NULL);
648
649         pr_info(PFX "unloaded.\n");
650 }
651
652 module_init(warm_module_init);
653 module_exit(warm_module_exit);
654
655 module_param(verbose, int, 0644);
656
657 MODULE_LICENSE("GPL");
658 MODULE_DESCRIPTION("ARM processor services");
659 MODULE_AUTHOR("Grazvydas Ignotas");