2.4 kernel support, manual module loading for 2.6
[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
198a1649 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
8d04105a 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
198a1649 60extern unsigned long max_mapnr;
61
62/* "upper" physical memory, not seen by Linux and to be mmap'ed */
63static u32 uppermem_start;
64static u32 uppermem_end;
65
8d04105a 66static int verbose;
67
198a1649 68static 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
78static 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 {
8d04105a 96 if ((pgtable[i] & 3) != 1)
97 /* must be coarse page table */
198a1649 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
8d04105a 138 WARM_INFO("%c%c bit(s) %s for phys %08x-%08x (%d pages)\n",
198a1649 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
146static 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;
cc951732 152 u32 mask;
198a1649 153
154 if (in_cb & WCB_C_BIT)
155 bits |= 8;
156 if (in_cb & WCB_B_BIT)
157 bits |= 4;
158
cc951732 159 mask = PAGE_SIZE - 1;
160 size += addr & mask;
161 size = (size + mask) & ~mask;
198a1649 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
8d04105a 173 if ((desc1 & 3) != 1) {
174 printk(KERN_WARNING PFX "not coarse table? %08x %08x\n", desc1, addr);
198a1649 175 return -EINVAL;
8d04105a 176 }
198a1649 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
8d04105a 199 WARM_INFO("%c%c bit(s) %s virt %08x-%08x (%d pages)\n",
198a1649 200 bits & 8 ? 'c' : ' ', bits & 4 ? 'b' : ' ',
201 is_set ? "set" : "cleared", start, end - 1, count);
202
203 return 0;
204}
205
206static 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
8d04105a 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 */
198a1649 221 *_addr = (desc1 & 0xfff00000) | (addr & 0xfffff);
222 return 0;
8d04105a 223 case 3: /* fine table */
224 cpt = __va(desc1 & 0xfffff000);
225 desc2 = cpt[(addr >> 10) & 0x3ff];
cc951732 226 break;
8d04105a 227 default:
228 return -EINVAL;
198a1649 229 }
230
198a1649 231
8d04105a 232 switch (desc2 & 3) {
233 case 1: /* large page */
cc951732 234 *_addr = (desc2 & ~0xffff) | (addr & 0xffff);
8d04105a 235 break;
236 case 2: /* small page */
cc951732 237 *_addr = (desc2 & ~0x0fff) | (addr & 0x0fff);
8d04105a 238 break;
239 case 3: /* tiny page */
cc951732 240 *_addr = (desc2 & ~0x03ff) | (addr & 0x03ff);
8d04105a 241 break;
242 default:
198a1649 243 return -EINVAL;
244 }
245
198a1649 246 return 0;
247}
248
249static 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
269static 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
8d04105a 307#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11)
198a1649 308static long warm_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)
8d04105a 309#else
310static int warm_ioctl(struct inode *inode, struct file *file,
311 unsigned int cmd, unsigned long __arg)
312#endif
198a1649 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;
8d04105a 328 if (u.wcop.size == (unsigned long)-1 ||
329 (u.wcop.size > MAX_CACHEOP_RANGE && !(u.wcop.ops & WOP_D_INVALIDATE)))
198a1649 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
359static 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
376static 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
397static 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
405static 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
415static int warm_cache_line(int code)
416{
417 return 8 << code;
418}
419
420static 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
473static int warm_open(struct inode *inode, struct file *file)
474{
475 return single_open(file, warm_seq_show, NULL);
476}
477
478static int warm_close(struct inode *ino, struct file *file)
479{
480 return single_release(ino, file);
481}
482
483static 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
492static int __init warm_module_init(void)
493{
494 struct proc_dir_entry *pret;
8d04105a 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 }
198a1649 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
528static void __exit warm_module_exit(void)
529{
530 remove_proc_entry("warm", NULL);
531
532 pr_info(PFX "unloaded.\n");
533}
534
535module_init(warm_module_init);
536module_exit(warm_module_exit);
537
8d04105a 538module_param(verbose, int, 0644);
539
198a1649 540MODULE_LICENSE("GPL");
541MODULE_DESCRIPTION("ARM processor services");
542MODULE_AUTHOR("Grazvydas Ignotas");