r3 release
[warm.git] / module / warm_main.c
index 18e9762..d209add 100644 (file)
@@ -35,7 +35,7 @@
 #error need proc_fs
 #endif
        
-#define WARM_VER "r2"
+#define WARM_VER "r3"
 #define PFX "wARM: "
 
 #define WARM_INFO(fmt, ...) \
@@ -63,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;
 
@@ -78,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;
@@ -90,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++)
@@ -136,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",
@@ -147,6 +153,7 @@ 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;
+       unsigned long flags;
        u32 desc1, desc2 = 0;
        u32 *pgtable, *cpt = NULL;
        u32 start, end;
@@ -165,6 +172,8 @@ 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();
 
        while (addr < end)
@@ -173,6 +182,7 @@ static int do_set_cb_virt(int in_cb, int is_set, u32 addr, u32 size)
 
                switch (desc1 & 3) {
                case 0:
+                       spin_unlock_irqrestore(&lock, flags);
                        printk(KERN_WARNING PFX "address %08x not mapped.\n", addr);
                        return -EINVAL;
                case 1:
@@ -196,8 +206,8 @@ static int do_set_cb_virt(int in_cb, int is_set, u32 addr, u32 size)
                        break;
                }
 
-               
                if ((desc2 & 3) == 0) {
+                       spin_unlock_irqrestore(&lock, flags);
                        printk(KERN_WARNING PFX "address %08x not mapped (%08x)\n",
                                addr, desc2);
                        return -EINVAL;
@@ -230,6 +240,8 @@ static int do_set_cb_virt(int in_cb, int is_set, u32 addr, u32 size)
        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);
@@ -337,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
@@ -348,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;
@@ -381,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;
@@ -542,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;