few bugfixes, some features, starting 2.4 port
[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
153         if (in_cb & WCB_C_BIT)
154                 bits |= 8;
155         if (in_cb & WCB_B_BIT)
156                 bits |= 4;
157
158         size += addr & (PAGE_SIZE - 1);
159         size = ALIGN(size, PAGE_SIZE);
160
161         addr &= ~(PAGE_SIZE - 1);
162         start = addr;
163         end = addr + size;
164
165         pgtable = get_pgtable();
166
167         for (; addr < end; addr += PAGE_SIZE)
168         {
169                 desc1 = pgtable[addr >> 20];
170
171                 if ((desc1 & 3) != 1) {
172                         printk(KERN_WARNING PFX "not coarse table? %08x %08x\n", desc1, addr);
173                         return -EINVAL;
174                 }
175
176                 cpt = __va(desc1 & 0xfffffc00);
177                 desc2 = cpt[(addr >> 12) & 0xff];
178                 
179                 if ((desc2 & 3) != 2) {
180                         printk(KERN_WARNING PFX "not small page? %08x %08x\n", desc2, addr);
181                         return -EINVAL;
182                 }
183
184                 if (is_set)
185                         desc2 |= bits;
186                 else
187                         desc2 &= ~bits;
188                 desc2 |= 0xff0;
189
190                 cpt[(addr >> 12) & 0xff] = desc2;
191                 count++;
192         }
193
194         warm_cop_clean_d();
195         warm_drain_wb_inval_tlb();
196
197         WARM_INFO("%c%c bit(s) %s virt %08x-%08x (%d pages)\n",
198                 bits & 8 ? 'c' : ' ', bits & 4 ? 'b' : ' ',
199                 is_set ? "set" : "cleared", start, end - 1, count);
200
201         return 0;
202 }
203
204 static int do_virt2phys(unsigned long *_addr)
205 {
206         u32 addr = *_addr;
207         u32 desc1, desc2;
208         u32 *pgtable, *cpt;
209
210         pgtable = get_pgtable();
211         desc1 = pgtable[addr >> 20];
212
213         switch (desc1 & 3) {
214         case 1: /* coarse table */
215                 cpt = __va(desc1 & 0xfffffc00);
216                 desc2 = cpt[(addr >> 12) & 0xff];
217                 break;
218         case 2: /* 1MB section */
219                 *_addr = (desc1 & 0xfff00000) | (addr & 0xfffff);
220                 return 0;
221         case 3: /* fine table */
222                 cpt = __va(desc1 & 0xfffff000);
223                 desc2 = cpt[(addr >> 10) & 0x3ff];
224         default:
225                 return -EINVAL;
226         }
227         
228         
229         switch (desc2 & 3) {
230         case 1: /* large page */
231                 *_addr = (desc2 & 0xffff0000) | (addr & 0xffff);
232                 break;
233         case 2: /* small page */
234                 *_addr = (desc2 & 0xfffff000) | (addr & 0x0fff);
235                 break;
236         case 3: /* tiny page */
237                 *_addr = (desc2 & 0xfffffc00) | (addr & 0x03ff);
238                 break;
239         default:
240                 return -EINVAL;
241         }
242
243         return 0;
244 }
245
246 static int do_cache_ops_whole(int ops)
247 {
248         if ((ops & (WOP_D_CLEAN|WOP_D_INVALIDATE)) == (WOP_D_CLEAN|WOP_D_INVALIDATE))
249                 warm_cop_clean_inval_d();
250
251         else if (ops & WOP_D_CLEAN)
252                 warm_cop_clean_d();
253
254         else if (ops & WOP_D_INVALIDATE) {
255                 printk(KERN_WARNING PFX "invalidate without clean is dangerous!\n");
256                 warm_cop_inval_d();
257         }
258
259         if (ops & WOP_I_INVALIDATE)
260                 warm_cop_inval_i();
261         
262         warm_cop_drain_wb();
263         return 0;
264 }
265
266 static int do_cache_ops(int ops, u32 addr, u32 size)
267 {
268         if (addr & 31) {
269                 size += addr & 31;
270                 addr &= ~31;
271         }
272
273         switch (ops) {
274         case WOP_D_CLEAN|WOP_D_INVALIDATE|WOP_I_INVALIDATE:
275                 warm_cop_r_clean_d_inval_di(addr, size);
276                 break;
277         case WOP_D_CLEAN|WOP_D_INVALIDATE:
278                 warm_cop_r_clean_d_inval_d(addr, size);
279                 break;
280         case WOP_D_CLEAN|WOP_I_INVALIDATE:
281                 warm_cop_r_clean_d_inval_i(addr, size);
282                 break;
283         case WOP_D_CLEAN:
284                 warm_cop_r_clean_d(addr, size);
285                 break;
286         case WOP_D_INVALIDATE|WOP_I_INVALIDATE:
287                 warm_cop_r_inval_di(addr, size);
288                 break;
289         case WOP_D_INVALIDATE:
290                 warm_cop_r_inval_d(addr, size);
291                 break;
292         case WOP_I_INVALIDATE:
293                 warm_cop_r_inval_i(addr, size);
294                 break;
295         default:
296                 /* only drain wb */
297                 break;
298         }
299
300         warm_cop_drain_wb();
301         return 0;
302 }
303
304 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11)
305 static long warm_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)
306 #else
307 static int warm_ioctl(struct inode *inode, struct file *file,
308                         unsigned int cmd, unsigned long __arg)
309 #endif
310 {
311         void __user *arg = (void __user *) __arg;
312         union {
313                 struct warm_cache_op wcop;
314                 struct warm_change_cb ccb;
315                 unsigned long addr;
316         } u;
317         long ret;
318
319         switch (cmd) {
320         case WARMC_CACHE_OP:
321                 if (copy_from_user(&u.wcop, arg, sizeof(u.wcop)))
322                         return -EFAULT;
323                 if (u.wcop.ops & ~(WOP_D_CLEAN|WOP_D_INVALIDATE|WOP_I_INVALIDATE))
324                         return -EINVAL;
325                 if (u.wcop.size == (unsigned long)-1 ||
326                                 (u.wcop.size > MAX_CACHEOP_RANGE && !(u.wcop.ops & WOP_D_INVALIDATE)))
327                         ret = do_cache_ops_whole(u.wcop.ops);
328                 else
329                         ret = do_cache_ops(u.wcop.ops, u.wcop.addr, u.wcop.size);
330                 break;
331         case WARMC_CHANGE_CB:
332                 if (copy_from_user(&u.ccb, arg, sizeof(u.ccb)))
333                         return -EFAULT;
334                 if (u.ccb.cb & ~(WCB_C_BIT|WCB_B_BIT))
335                         return -EINVAL;
336                 if (u.ccb.addr == 0 && u.ccb.size == 0)
337                         ret = do_set_cb_uppermem(u.ccb.cb, u.ccb.is_set);
338                 else
339                         ret = do_set_cb_virt(u.ccb.cb, u.ccb.is_set, u.ccb.addr, u.ccb.size);
340                 break;
341         case WARMC_VIRT2PHYS:
342                 if (copy_from_user(&u.addr, arg, sizeof(u.addr)))
343                         return -EFAULT;
344                 ret = do_virt2phys(&u.addr);
345                 if (copy_to_user(arg, &u.addr, sizeof(u.addr)))
346                         return -EFAULT;
347                 break;
348         default:
349                 ret = -ENOTTY;
350                 break;
351         }
352
353         return ret;
354 }
355
356 static const char *warm_implementor_name(char code)
357 {
358         switch (code) {
359         case 'A':
360                 return "ARM";
361         case 'D':
362                 return "DEC";
363         case 'i':
364                 return "Intel";
365         case 'M':
366                 return "Motorola - Freescale";
367         case 'V':
368                 return "Marvell";
369         }
370         return "???";
371 }
372
373 static const char *warm_arch_name(int code)
374 {
375         switch (code) {
376         case 1:
377                 return "4";
378         case 2:
379                 return "4T";
380         case 3:
381                 return "5";
382         case 4:
383                 return "5T";
384         case 5:
385                 return "5TE";
386         case 6:
387                 return "5TEJ";
388         case 7:
389                 return "6";
390         }
391         return "?";
392 }
393
394 static int warm_cache_size(int code, int m)
395 {
396         int base = 512;
397         if (m)
398                 base = 768;
399         return base << code;
400 }
401
402 static int warm_cache_assoc(int code, int m)
403 {
404         int base = 2;
405         if (code == 0)
406                 return m ? 0 : 1;
407         if (m)
408                 base = 3;
409         return base << (code - 1); 
410 }
411
412 static int warm_cache_line(int code)
413 {
414         return 8 << code;
415 }
416
417 static int warm_seq_show(struct seq_file *seq, void *offset)
418 {
419         u32 tmp;
420
421         seq_printf(seq, "wARM: " WARM_VER "\n");
422
423         /* ID codes */
424         asm ("mrc p15, 0, %0, c0, c0, 0" : "=r"(tmp));
425         seq_printf(seq, "ID: %08x\n", tmp);
426         if (tmp & 0x80000) {
427                 /* revised format, not yet documented */
428         } else if ((tmp & 0xf000) == 0) {
429                 /* pre-ARM7 */
430                 seq_printf(seq, "Architecture: %d\n",
431                                 (tmp & 0xf00) == 0x600 ? 3 : 2);
432                 seq_printf(seq, "Variant: %d%d0\n", (tmp & 0xf00) >> 8,
433                                 (tmp & 0xf0) >> 4);
434         } else {
435                 seq_printf(seq, "Implementor: %c (%s)\n", tmp >> 24,
436                                 warm_implementor_name(tmp >> 24));
437                 if ((tmp & 0xf000) == 7) {
438                         seq_printf(seq, "Architecture: %s\n",
439                                 tmp & 0x800000 ? "4T" : "3");
440                         seq_printf(seq, "Variant: 0x%x\n", (tmp & 0x7f0000) >> 16);
441                 } else {
442                         seq_printf(seq, "Architecture: %s\n",
443                                 warm_arch_name((tmp & 0x0f0000) >> 16));
444                         seq_printf(seq, "Variant: 0x%x\n", (tmp & 0xf00000) >> 20);
445                 }
446                 seq_printf(seq, "Part number: 0x%x\n", (tmp & 0xfff0) >> 4);
447         }
448         seq_printf(seq, "Revision: 0x%x\n", tmp & 0xf);
449
450         /* cache type */
451         asm ("mrc p15, 0, %0, c0, c0, 1" : "=r"(tmp));
452         seq_printf(seq, "Cache ctype: 0x%x\n", (tmp & 0x1e000000) >> 25);
453         seq_printf(seq, "Cache unified: %s\n", (tmp & 0x01000000) ? "no" : "yes");
454         seq_printf(seq, "DCache size: %d\n",
455                         warm_cache_size((tmp >> (6+12)) & 0xf, (tmp >> (2+12)) & 1));
456         seq_printf(seq, "DCache associativity: %d\n",
457                         warm_cache_assoc((tmp >> (3+12)) & 7, (tmp >> (2+12)) & 1));
458         seq_printf(seq, "DCache line size: %d\n",
459                         warm_cache_line((tmp >> (0+12)) & 3));
460         seq_printf(seq, "ICache size: %d\n",
461                         warm_cache_size((tmp >> 6) & 0xf, (tmp >> 2) & 1));
462         seq_printf(seq, "ICache associativity: %d\n",
463                         warm_cache_assoc((tmp >> 3) & 7, (tmp >> 2) & 1));
464         seq_printf(seq, "ICache line size: %d\n",
465                         warm_cache_line((tmp >> 0) & 3));
466
467         return 0;
468 }
469
470 static int warm_open(struct inode *inode, struct file *file)
471 {
472         return single_open(file, warm_seq_show, NULL);
473 }
474
475 static int warm_close(struct inode *ino, struct file *file)
476 {
477         return single_release(ino, file);
478 }
479
480 static const struct file_operations warm_fops = {
481         .owner  = THIS_MODULE,
482         .open   = warm_open,
483         .read   = seq_read,
484         .llseek = seq_lseek,
485         .unlocked_ioctl = warm_ioctl,
486         .release = warm_close,
487 };
488
489 static int __init warm_module_init(void)
490 {
491         struct proc_dir_entry *pret;
492         u32 cpuid;
493
494         asm ("mrc p15, 0, %0, c0, c0, 0" : "=r"(cpuid));
495         if ((cpuid & 0x0ffff0) != EXPECTED_ID) {
496                 printk(KERN_ERR PFX "module was compiled for different CPU, aborting\n");
497                 return -1;
498         }
499
500         pret = create_proc_entry("warm", S_IWUGO | S_IRUGO, NULL);
501         if (!pret) {
502                 printk(KERN_ERR PFX "can't create proc entry\n");
503                 return -1;
504         }
505
506         pret->owner = THIS_MODULE;
507         pret->proc_fops = &warm_fops;
508
509         uppermem_start = RAM_PHYS_START + (max_mapnr << PAGE_SHIFT);
510         uppermem_end = RAM_PHYS_START + RAM_MAX_SIZE;
511
512         pr_info(PFX WARM_VER " loaded, ");
513         if (uppermem_end <= uppermem_start)
514                 printk("no upper mem");
515         else
516                 printk("upper mem %08x-%08x", uppermem_start, uppermem_end - 1);
517         printk("\n");
518
519         /* give time for /proc node to appear */
520         mdelay(200);
521
522         return 0;
523 }
524
525 static void __exit warm_module_exit(void)
526 {
527         remove_proc_entry("warm", NULL);
528
529         pr_info(PFX "unloaded.\n");
530 }
531
532 module_init(warm_module_init);
533 module_exit(warm_module_exit);
534
535 module_param(verbose, int, 0644);
536
537 MODULE_LICENSE("GPL");
538 MODULE_DESCRIPTION("ARM processor services");
539 MODULE_AUTHOR("Grazvydas Ignotas");