support remaining mapping sizes in do_set_cb
[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;
66
8d04105a 67static int verbose;
68
198a1649 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 {
8d04105a 97 if ((pgtable[i] & 3) != 1)
98 /* must be coarse page table */
198a1649 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
8d04105a 139 WARM_INFO("%c%c bit(s) %s for phys %08x-%08x (%d pages)\n",
198a1649 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;
159a48bb 150 u32 desc1, desc2 = 0;
151 u32 *pgtable, *cpt = NULL;
198a1649 152 u32 start, end;
cc951732 153 u32 mask;
198a1649 154
155 if (in_cb & WCB_C_BIT)
156 bits |= 8;
157 if (in_cb & WCB_B_BIT)
158 bits |= 4;
159
cc951732 160 mask = PAGE_SIZE - 1;
161 size += addr & mask;
162 size = (size + mask) & ~mask;
198a1649 163
164 addr &= ~(PAGE_SIZE - 1);
165 start = addr;
166 end = addr + size;
167
168 pgtable = get_pgtable();
169
159a48bb 170 while (addr < end)
198a1649 171 {
172 desc1 = pgtable[addr >> 20];
173
159a48bb 174 switch (desc1 & 3) {
175 case 0:
176 printk(KERN_WARNING PFX "address %08x not mapped.\n", addr);
198a1649 177 return -EINVAL;
159a48bb 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;
8d04105a 197 }
198a1649 198
198a1649 199
159a48bb 200 if ((desc2 & 3) == 0) {
201 printk(KERN_WARNING PFX "address %08x not mapped (%08x)\n",
202 addr, desc2);
198a1649 203 return -EINVAL;
204 }
205
206 if (is_set)
207 desc2 |= bits;
208 else
209 desc2 &= ~bits;
198a1649 210
159a48bb 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;
198a1649 227 count++;
228 }
229
230 warm_cop_clean_d();
231 warm_drain_wb_inval_tlb();
232
8d04105a 233 WARM_INFO("%c%c bit(s) %s virt %08x-%08x (%d pages)\n",
198a1649 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
8d04105a 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 */
198a1649 255 *_addr = (desc1 & 0xfff00000) | (addr & 0xfffff);
256 return 0;
8d04105a 257 case 3: /* fine table */
258 cpt = __va(desc1 & 0xfffff000);
259 desc2 = cpt[(addr >> 10) & 0x3ff];
cc951732 260 break;
8d04105a 261 default:
262 return -EINVAL;
198a1649 263 }
159a48bb 264
8d04105a 265 switch (desc2 & 3) {
266 case 1: /* large page */
cc951732 267 *_addr = (desc2 & ~0xffff) | (addr & 0xffff);
8d04105a 268 break;
269 case 2: /* small page */
cc951732 270 *_addr = (desc2 & ~0x0fff) | (addr & 0x0fff);
8d04105a 271 break;
272 case 3: /* tiny page */
cc951732 273 *_addr = (desc2 & ~0x03ff) | (addr & 0x03ff);
8d04105a 274 break;
275 default:
198a1649 276 return -EINVAL;
277 }
278
198a1649 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
8d04105a 340#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11)
198a1649 341static long warm_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)
8d04105a 342#else
343static int warm_ioctl(struct inode *inode, struct file *file,
344 unsigned int cmd, unsigned long __arg)
345#endif
198a1649 346{
347 void __user *arg = (void __user *) __arg;
348 union {
349 struct warm_cache_op wcop;
350 struct warm_change_cb ccb;
351 unsigned long addr;
352 } u;
353 long ret;
354
355 switch (cmd) {
356 case WARMC_CACHE_OP:
357 if (copy_from_user(&u.wcop, arg, sizeof(u.wcop)))
358 return -EFAULT;
359 if (u.wcop.ops & ~(WOP_D_CLEAN|WOP_D_INVALIDATE|WOP_I_INVALIDATE))
360 return -EINVAL;
8d04105a 361 if (u.wcop.size == (unsigned long)-1 ||
362 (u.wcop.size > MAX_CACHEOP_RANGE && !(u.wcop.ops & WOP_D_INVALIDATE)))
198a1649 363 ret = do_cache_ops_whole(u.wcop.ops);
364 else
365 ret = do_cache_ops(u.wcop.ops, u.wcop.addr, u.wcop.size);
366 break;
367 case WARMC_CHANGE_CB:
368 if (copy_from_user(&u.ccb, arg, sizeof(u.ccb)))
369 return -EFAULT;
370 if (u.ccb.cb & ~(WCB_C_BIT|WCB_B_BIT))
371 return -EINVAL;
372 if (u.ccb.addr == 0 && u.ccb.size == 0)
373 ret = do_set_cb_uppermem(u.ccb.cb, u.ccb.is_set);
374 else
375 ret = do_set_cb_virt(u.ccb.cb, u.ccb.is_set, u.ccb.addr, u.ccb.size);
376 break;
377 case WARMC_VIRT2PHYS:
378 if (copy_from_user(&u.addr, arg, sizeof(u.addr)))
379 return -EFAULT;
380 ret = do_virt2phys(&u.addr);
381 if (copy_to_user(arg, &u.addr, sizeof(u.addr)))
382 return -EFAULT;
383 break;
384 default:
385 ret = -ENOTTY;
386 break;
387 }
388
389 return ret;
390}
391
392static const char *warm_implementor_name(char code)
393{
394 switch (code) {
395 case 'A':
396 return "ARM";
397 case 'D':
398 return "DEC";
399 case 'i':
400 return "Intel";
401 case 'M':
402 return "Motorola - Freescale";
403 case 'V':
404 return "Marvell";
405 }
406 return "???";
407}
408
409static const char *warm_arch_name(int code)
410{
411 switch (code) {
412 case 1:
413 return "4";
414 case 2:
415 return "4T";
416 case 3:
417 return "5";
418 case 4:
419 return "5T";
420 case 5:
421 return "5TE";
422 case 6:
423 return "5TEJ";
424 case 7:
425 return "6";
426 }
427 return "?";
428}
429
430static int warm_cache_size(int code, int m)
431{
432 int base = 512;
433 if (m)
434 base = 768;
435 return base << code;
436}
437
438static int warm_cache_assoc(int code, int m)
439{
440 int base = 2;
441 if (code == 0)
442 return m ? 0 : 1;
443 if (m)
444 base = 3;
445 return base << (code - 1);
446}
447
448static int warm_cache_line(int code)
449{
450 return 8 << code;
451}
452
453static int warm_seq_show(struct seq_file *seq, void *offset)
454{
455 u32 tmp;
456
457 seq_printf(seq, "wARM: " WARM_VER "\n");
458
459 /* ID codes */
460 asm ("mrc p15, 0, %0, c0, c0, 0" : "=r"(tmp));
461 seq_printf(seq, "ID: %08x\n", tmp);
462 if (tmp & 0x80000) {
463 /* revised format, not yet documented */
464 } else if ((tmp & 0xf000) == 0) {
465 /* pre-ARM7 */
466 seq_printf(seq, "Architecture: %d\n",
467 (tmp & 0xf00) == 0x600 ? 3 : 2);
468 seq_printf(seq, "Variant: %d%d0\n", (tmp & 0xf00) >> 8,
469 (tmp & 0xf0) >> 4);
470 } else {
471 seq_printf(seq, "Implementor: %c (%s)\n", tmp >> 24,
472 warm_implementor_name(tmp >> 24));
473 if ((tmp & 0xf000) == 7) {
474 seq_printf(seq, "Architecture: %s\n",
475 tmp & 0x800000 ? "4T" : "3");
476 seq_printf(seq, "Variant: 0x%x\n", (tmp & 0x7f0000) >> 16);
477 } else {
478 seq_printf(seq, "Architecture: %s\n",
479 warm_arch_name((tmp & 0x0f0000) >> 16));
480 seq_printf(seq, "Variant: 0x%x\n", (tmp & 0xf00000) >> 20);
481 }
482 seq_printf(seq, "Part number: 0x%x\n", (tmp & 0xfff0) >> 4);
483 }
484 seq_printf(seq, "Revision: 0x%x\n", tmp & 0xf);
485
486 /* cache type */
487 asm ("mrc p15, 0, %0, c0, c0, 1" : "=r"(tmp));
488 seq_printf(seq, "Cache ctype: 0x%x\n", (tmp & 0x1e000000) >> 25);
489 seq_printf(seq, "Cache unified: %s\n", (tmp & 0x01000000) ? "no" : "yes");
490 seq_printf(seq, "DCache size: %d\n",
491 warm_cache_size((tmp >> (6+12)) & 0xf, (tmp >> (2+12)) & 1));
492 seq_printf(seq, "DCache associativity: %d\n",
493 warm_cache_assoc((tmp >> (3+12)) & 7, (tmp >> (2+12)) & 1));
494 seq_printf(seq, "DCache line size: %d\n",
495 warm_cache_line((tmp >> (0+12)) & 3));
496 seq_printf(seq, "ICache size: %d\n",
497 warm_cache_size((tmp >> 6) & 0xf, (tmp >> 2) & 1));
498 seq_printf(seq, "ICache associativity: %d\n",
499 warm_cache_assoc((tmp >> 3) & 7, (tmp >> 2) & 1));
500 seq_printf(seq, "ICache line size: %d\n",
501 warm_cache_line((tmp >> 0) & 3));
502
503 return 0;
504}
505
506static int warm_open(struct inode *inode, struct file *file)
507{
508 return single_open(file, warm_seq_show, NULL);
509}
510
511static int warm_close(struct inode *ino, struct file *file)
512{
513 return single_release(ino, file);
514}
515
516static const struct file_operations warm_fops = {
517 .owner = THIS_MODULE,
518 .open = warm_open,
519 .read = seq_read,
520 .llseek = seq_lseek,
521 .unlocked_ioctl = warm_ioctl,
522 .release = warm_close,
523};
524
525static int __init warm_module_init(void)
526{
527 struct proc_dir_entry *pret;
8d04105a 528 u32 cpuid;
529
530 asm ("mrc p15, 0, %0, c0, c0, 0" : "=r"(cpuid));
531 if ((cpuid & 0x0ffff0) != EXPECTED_ID) {
532 printk(KERN_ERR PFX "module was compiled for different CPU, aborting\n");
533 return -1;
534 }
198a1649 535
536 pret = create_proc_entry("warm", S_IWUGO | S_IRUGO, NULL);
537 if (!pret) {
538 printk(KERN_ERR PFX "can't create proc entry\n");
539 return -1;
540 }
541
542 pret->owner = THIS_MODULE;
543 pret->proc_fops = &warm_fops;
544
545 uppermem_start = RAM_PHYS_START + (max_mapnr << PAGE_SHIFT);
546 uppermem_end = RAM_PHYS_START + RAM_MAX_SIZE;
547
548 pr_info(PFX WARM_VER " loaded, ");
549 if (uppermem_end <= uppermem_start)
550 printk("no upper mem");
551 else
552 printk("upper mem %08x-%08x", uppermem_start, uppermem_end - 1);
553 printk("\n");
554
555 /* give time for /proc node to appear */
556 mdelay(200);
557
558 return 0;
559}
560
561static void __exit warm_module_exit(void)
562{
563 remove_proc_entry("warm", NULL);
564
565 pr_info(PFX "unloaded.\n");
566}
567
568module_init(warm_module_init);
569module_exit(warm_module_exit);
570
8d04105a 571module_param(verbose, int, 0644);
572
198a1649 573MODULE_LICENSE("GPL");
574MODULE_DESCRIPTION("ARM processor services");
575MODULE_AUTHOR("Grazvydas Ignotas");