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