protect operations with spinlock
[warm.git] / module / warm_main.c
CommitLineData
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
8d04105a 38#define WARM_VER "r2"
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 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;
55b74e0b 66static spinlock_t lock;
198a1649 67
8d04105a 68static int verbose;
69
198a1649 70static 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
80static 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
153static 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
252static 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
294static 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
314static 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 352static 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 433static long warm_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)
8d04105a 434#else
435static 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
493static 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
510static 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
531static 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
539static 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
549static int warm_cache_line(int code)
550{
551 return 8 << code;
552}
553
554static 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
607static int warm_open(struct inode *inode, struct file *file)
608{
609 return single_open(file, warm_seq_show, NULL);
610}
611
612static int warm_close(struct inode *ino, struct file *file)
613{
614 return single_release(ino, file);
615}
616
617static 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
626static 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
664static void __exit warm_module_exit(void)
665{
666 remove_proc_entry("warm", NULL);
667
668 pr_info(PFX "unloaded.\n");
669}
670
671module_init(warm_module_init);
672module_exit(warm_module_exit);
673
8d04105a 674module_param(verbose, int, 0644);
675
198a1649 676MODULE_LICENSE("GPL");
677MODULE_DESCRIPTION("ARM processor services");
678MODULE_AUTHOR("Grazvydas Ignotas");