r3 release
[warm.git] / module / warm_main.c
index bbbcd96..d209add 100644 (file)
 #error need proc_fs
 #endif
        
-#define WARM_VER "r2"
+#define WARM_VER "r3"
 #define PFX "wARM: "
 
 #define WARM_INFO(fmt, ...) \
        if (verbose) \
                pr_info(PFX fmt, ##__VA_ARGS__)
 
+#define SECTION_SIZE           0x100000
 #define MAX_CACHEOP_RANGE      16384
 
 /* assume RAM starts at phys addr 0 (this is really machine specific) */
@@ -62,6 +63,7 @@ extern unsigned long max_mapnr;
 /* "upper" physical memory, not seen by Linux and to be mmap'ed */
 static u32 uppermem_start;
 static u32 uppermem_end;
+static spinlock_t lock;
 
 static int verbose;
 
@@ -77,6 +79,7 @@ static u32 *get_pgtable(void)
 
 static int do_set_cb_uppermem(int in_cb, int is_set)
 {
+       unsigned long flags;
        u32 *pgtable, *cpt;
        int i, j, count = 0;
        int bits = 0;
@@ -89,6 +92,8 @@ static int do_set_cb_uppermem(int in_cb, int is_set)
        if (in_cb & WCB_B_BIT)
                bits |= 4;
 
+       spin_lock_irqsave(&lock, flags);
+
        pgtable = get_pgtable();
 
        for (i = 0; i < 4096; i++)
@@ -135,6 +140,8 @@ static int do_set_cb_uppermem(int in_cb, int is_set)
        warm_cop_clean_d();
        warm_drain_wb_inval_tlb();
 
+       spin_unlock_irqrestore(&lock, flags);
+
        WARM_INFO("%c%c bit(s) %s for phys %08x-%08x (%d pages)\n",
                bits & 8 ? 'c' : ' ', bits & 4 ? 'b' : ' ',
                is_set ? "set" : "cleared",
@@ -146,8 +153,9 @@ static int do_set_cb_uppermem(int in_cb, int is_set)
 static int do_set_cb_virt(int in_cb, int is_set, u32 addr, u32 size)
 {
        int count = 0, bits = 0;
-       u32 desc1, desc2;
-       u32 *pgtable, *cpt;
+       unsigned long flags;
+       u32 desc1, desc2 = 0;
+       u32 *pgtable, *cpt = NULL;
        u32 start, end;
        u32 mask;
 
@@ -164,22 +172,44 @@ static int do_set_cb_virt(int in_cb, int is_set, u32 addr, u32 size)
        start = addr;
        end = addr + size;
 
+       spin_lock_irqsave(&lock, flags);
+
        pgtable = get_pgtable();
 
-       for (; addr < end; addr += PAGE_SIZE)
+       while (addr < end)
        {
                desc1 = pgtable[addr >> 20];
 
-               if ((desc1 & 3) != 1) {
-                       printk(KERN_WARNING PFX "not coarse table? %08x %08x\n", desc1, addr);
+               switch (desc1 & 3) {
+               case 0:
+                       spin_unlock_irqrestore(&lock, flags);
+                       printk(KERN_WARNING PFX "address %08x not mapped.\n", addr);
                        return -EINVAL;
+               case 1:
+                       /* coarse table */
+                       cpt = __va(desc1 & 0xfffffc00);
+                       desc2 = cpt[(addr >> 12) & 0xff];
+                       break;
+               case 2:
+                       /* section */
+                       if (is_set)
+                               desc1 |= bits;
+                       else
+                               desc1 &= ~bits;
+                       pgtable[addr >> 20] = desc1;
+                       addr += SECTION_SIZE;
+                       count++;
+                       continue;
+               case 3:
+                       cpt = __va(desc1 & 0xfffff000);
+                       desc2 = cpt[(addr >> 10) & 0x3ff];
+                       break;
                }
 
-               cpt = __va(desc1 & 0xfffffc00);
-               desc2 = cpt[(addr >> 12) & 0xff];
-               
-               if ((desc2 & 3) != 2) {
-                       printk(KERN_WARNING PFX "not small page? %08x %08x\n", desc2, addr);
+               if ((desc2 & 3) == 0) {
+                       spin_unlock_irqrestore(&lock, flags);
+                       printk(KERN_WARNING PFX "address %08x not mapped (%08x)\n",
+                               addr, desc2);
                        return -EINVAL;
                }
 
@@ -187,15 +217,31 @@ static int do_set_cb_virt(int in_cb, int is_set, u32 addr, u32 size)
                        desc2 |= bits;
                else
                        desc2 &= ~bits;
-               desc2 |= 0xff0;
 
-               cpt[(addr >> 12) & 0xff] = desc2;
+               /* this might be bad idea, better let it fault so that Linux does
+                * it's accounting, but that will drop CB bits, so keep this
+                * for compatibility */
+               if ((desc2 & 3) == 2)
+                       desc2 |= 0xff0;
+
+               switch (desc1 & 3) {
+               case 1:
+                       cpt[(addr >> 12) & 0xff] = desc2;
+                       break;
+               case 3:
+                       cpt[(addr >> 10) & 0x3ff] = desc2;
+                       break;
+               }
+
+               addr += PAGE_SIZE;
                count++;
        }
 
        warm_cop_clean_d();
        warm_drain_wb_inval_tlb();
 
+       spin_unlock_irqrestore(&lock, flags);
+
        WARM_INFO("%c%c bit(s) %s virt %08x-%08x (%d pages)\n",
                bits & 8 ? 'c' : ' ', bits & 4 ? 'b' : ' ',
                is_set ? "set" : "cleared", start, end - 1, count);
@@ -227,8 +273,7 @@ static int do_virt2phys(unsigned long *_addr)
        default:
                return -EINVAL;
        }
-       
-       
+
        switch (desc2 & 3) {
        case 1: /* large page */
                *_addr = (desc2 & ~0xffff) | (addr & 0xffff);
@@ -304,6 +349,86 @@ static int do_cache_ops(int ops, u32 addr, u32 size)
        return 0;
 }
 
+static int do_map_op(u32 vaddr, u32 paddr, u32 size, int cb, int is_unmap)
+{
+       int count = 0, retval = 0;
+       unsigned long flags;
+       u32 pstart, start, end;
+       u32 desc1, apcb_bits;
+       u32 *pgtable;
+       u32 v, mask;
+
+       apcb_bits = (3 << 10) | (1 << 5); /* r/w, dom 1 */
+       if (cb & WCB_C_BIT)
+               apcb_bits |= 8;
+       if (cb & WCB_B_BIT)
+               apcb_bits |= 4;
+
+       mask = SECTION_SIZE - 1;
+       size = (size + mask) & ~mask;
+
+       pstart = paddr;
+       start = vaddr;
+       end = start + size;
+
+       /* check for overflows */
+       if (end - 1 < start)
+               return -EINVAL;
+       if (pstart + size - 1 < pstart)
+               return -EINVAL;
+
+       spin_lock_irqsave(&lock, flags);
+
+       pgtable = get_pgtable();
+
+       for (; vaddr < end; vaddr += SECTION_SIZE, paddr += SECTION_SIZE)
+       {
+               desc1 = pgtable[vaddr >> 20];
+
+               if (is_unmap) {
+                       if ((desc1 & 3) != 2) {
+                               spin_unlock_irqrestore(&lock, flags);
+                               printk(KERN_WARNING PFX "vaddr %08x is not a section? (%08x)\n",
+                                               vaddr, desc1);
+                               return -EINVAL;
+                       }
+                       v = 0;
+               } else {
+                       if ((desc1 & 3) != 0) {
+                               printk(KERN_WARNING PFX "vaddr %08x already mapped? (%08x)\n",
+                                               vaddr, desc1);
+                               retval = -EINVAL;
+                               break;
+                       }
+                       v = (paddr & ~mask) | apcb_bits | 0x12;
+               }
+
+               pgtable[vaddr >> 20] = v;
+               count++;
+       }
+
+       if (retval != 0) {
+               /* undo mappings */
+               vaddr = start;
+
+               for (; vaddr < end && count > 0; vaddr += SECTION_SIZE, count--)
+                       pgtable[vaddr >> 20] = 0;
+       }
+
+       warm_cop_clean_d();
+       warm_drain_wb_inval_tlb();
+
+       spin_unlock_irqrestore(&lock, flags);
+
+       if (retval == 0 && !is_unmap) {
+               WARM_INFO("mapped %08x to %08x with %c%c bit(s) (%d section(s))\n",
+                       start, pstart, apcb_bits & 8 ? 'c' : ' ',
+                       apcb_bits & 4 ? 'b' : ' ', count);
+       }
+
+       return retval;
+}
+
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11)
 static long warm_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)
 #else
@@ -315,6 +440,7 @@ static int warm_ioctl(struct inode *inode, struct file *file,
        union {
                struct warm_cache_op wcop;
                struct warm_change_cb ccb;
+               struct warm_map_op mop;
                unsigned long addr;
        } u;
        long ret;
@@ -348,6 +474,14 @@ static int warm_ioctl(struct inode *inode, struct file *file,
                if (copy_to_user(arg, &u.addr, sizeof(u.addr)))
                        return -EFAULT;
                break;
+       case WARMC_MMAP:
+               if (copy_from_user(&u.mop, arg, sizeof(u.mop)))
+                       return -EFAULT;
+               if (u.mop.cb & ~(WCB_C_BIT|WCB_B_BIT))
+                       return -EINVAL;
+               ret = do_map_op(u.mop.virt_addr, u.mop.phys_addr, u.mop.size,
+                       u.mop.cb, u.mop.is_unmap);
+               break;
        default:
                ret = -ENOTTY;
                break;
@@ -509,6 +643,8 @@ static int __init warm_module_init(void)
        pret->owner = THIS_MODULE;
        pret->proc_fops = &warm_fops;
 
+       spin_lock_init(&lock);
+
        uppermem_start = RAM_PHYS_START + (max_mapnr << PAGE_SHIFT);
        uppermem_end = RAM_PHYS_START + RAM_MAX_SIZE;