This commit was manufactured by cvs2svn to create branch 'vserver'.
[linux-2.6.git] / arch / avr32 / mm / tlb.c
diff --git a/arch/avr32/mm/tlb.c b/arch/avr32/mm/tlb.c
new file mode 100644 (file)
index 0000000..7b07305
--- /dev/null
@@ -0,0 +1,380 @@
+/*
+ * AVR32 TLB operations
+ *
+ * Copyright (C) 2004-2006 Atmel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/mm.h>
+
+#include <asm/mmu_context.h>
+
+#define _TLBEHI_I      0x100
+
+void show_dtlb_entry(unsigned int index)
+{
+       unsigned int tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save;
+       unsigned long flags;
+
+       local_irq_save(flags);
+       mmucr_save = sysreg_read(MMUCR);
+       tlbehi_save = sysreg_read(TLBEHI);
+       mmucr = mmucr_save & 0x13;
+       mmucr |= index << 14;
+       sysreg_write(MMUCR, mmucr);
+
+       asm volatile("tlbr" : : : "memory");
+       cpu_sync_pipeline();
+
+       tlbehi = sysreg_read(TLBEHI);
+       tlbelo = sysreg_read(TLBELO);
+
+       printk("%2u: %c %c %02x   %05x %05x %o  %o  %c %c %c %c\n",
+              index,
+              (tlbehi & 0x200)?'1':'0',
+              (tlbelo & 0x100)?'1':'0',
+              (tlbehi & 0xff),
+              (tlbehi >> 12), (tlbelo >> 12),
+              (tlbelo >> 4) & 7, (tlbelo >> 2) & 3,
+              (tlbelo & 0x200)?'1':'0',
+              (tlbelo & 0x080)?'1':'0',
+              (tlbelo & 0x001)?'1':'0',
+              (tlbelo & 0x002)?'1':'0');
+
+       sysreg_write(MMUCR, mmucr_save);
+       sysreg_write(TLBEHI, tlbehi_save);
+       cpu_sync_pipeline();
+       local_irq_restore(flags);
+}
+
+void dump_dtlb(void)
+{
+       unsigned int i;
+
+       printk("ID  V G ASID VPN   PFN   AP SZ C B W D\n");
+       for (i = 0; i < 32; i++)
+               show_dtlb_entry(i);
+}
+
+static unsigned long last_mmucr;
+
+static inline void set_replacement_pointer(unsigned shift)
+{
+       unsigned long mmucr, mmucr_save;
+
+       mmucr = mmucr_save = sysreg_read(MMUCR);
+
+       /* Does this mapping already exist? */
+       __asm__ __volatile__(
+               "       tlbs\n"
+               "       mfsr %0, %1"
+               : "=r"(mmucr)
+               : "i"(SYSREG_MMUCR));
+
+       if (mmucr & SYSREG_BIT(MMUCR_N)) {
+               /* Not found -- pick a not-recently-accessed entry */
+               unsigned long rp;
+               unsigned long tlbar = sysreg_read(TLBARLO);
+
+               rp = 32 - fls(tlbar);
+               if (rp == 32) {
+                       rp = 0;
+                       sysreg_write(TLBARLO, -1L);
+               }
+
+               mmucr &= 0x13;
+               mmucr |= (rp << shift);
+
+               sysreg_write(MMUCR, mmucr);
+       }
+
+       last_mmucr = mmucr;
+}
+
+static void update_dtlb(unsigned long address, pte_t pte, unsigned long asid)
+{
+       unsigned long vpn;
+
+       vpn = (address & MMU_VPN_MASK) | _TLBEHI_VALID | asid;
+       sysreg_write(TLBEHI, vpn);
+       cpu_sync_pipeline();
+
+       set_replacement_pointer(14);
+
+       sysreg_write(TLBELO, pte_val(pte) & _PAGE_FLAGS_HARDWARE_MASK);
+
+       /* Let's go */
+       asm volatile("nop\n\ttlbw" : : : "memory");
+       cpu_sync_pipeline();
+}
+
+void update_mmu_cache(struct vm_area_struct *vma,
+                     unsigned long address, pte_t pte)
+{
+       unsigned long flags;
+
+       /* ptrace may call this routine */
+       if (vma && current->active_mm != vma->vm_mm)
+               return;
+
+       local_irq_save(flags);
+       update_dtlb(address, pte, get_asid());
+       local_irq_restore(flags);
+}
+
+void __flush_tlb_page(unsigned long asid, unsigned long page)
+{
+       unsigned long mmucr, tlbehi;
+
+       page |= asid;
+       sysreg_write(TLBEHI, page);
+       cpu_sync_pipeline();
+       asm volatile("tlbs");
+       mmucr = sysreg_read(MMUCR);
+
+       if (!(mmucr & SYSREG_BIT(MMUCR_N))) {
+               unsigned long tlbarlo;
+               unsigned long entry;
+
+               /* Clear the "valid" bit */
+               tlbehi = sysreg_read(TLBEHI);
+               tlbehi &= ~_TLBEHI_VALID;
+               sysreg_write(TLBEHI, tlbehi);
+               cpu_sync_pipeline();
+
+               /* mark the entry as "not accessed" */
+               entry = (mmucr >> 14) & 0x3f;
+               tlbarlo = sysreg_read(TLBARLO);
+               tlbarlo |= (0x80000000 >> entry);
+               sysreg_write(TLBARLO, tlbarlo);
+
+               /* update the entry with valid bit clear */
+               asm volatile("tlbw");
+               cpu_sync_pipeline();
+       }
+}
+
+void flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
+{
+       if (vma->vm_mm && vma->vm_mm->context != NO_CONTEXT) {
+               unsigned long flags, asid;
+               unsigned long saved_asid = MMU_NO_ASID;
+
+               asid = vma->vm_mm->context & MMU_CONTEXT_ASID_MASK;
+               page &= PAGE_MASK;
+
+               local_irq_save(flags);
+               if (vma->vm_mm != current->mm) {
+                       saved_asid = get_asid();
+                       set_asid(asid);
+               }
+
+               __flush_tlb_page(asid, page);
+
+               if (saved_asid != MMU_NO_ASID)
+                       set_asid(saved_asid);
+               local_irq_restore(flags);
+       }
+}
+
+void flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
+                    unsigned long end)
+{
+       struct mm_struct *mm = vma->vm_mm;
+
+       if (mm->context != NO_CONTEXT) {
+               unsigned long flags;
+               int size;
+
+               local_irq_save(flags);
+               size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
+               if (size > (MMU_DTLB_ENTRIES / 4)) { /* Too many entries to flush */
+                       mm->context = NO_CONTEXT;
+                       if (mm == current->mm)
+                               activate_context(mm);
+               } else {
+                       unsigned long asid = mm->context & MMU_CONTEXT_ASID_MASK;
+                       unsigned long saved_asid = MMU_NO_ASID;
+
+                       start &= PAGE_MASK;
+                       end += (PAGE_SIZE - 1);
+                       end &= PAGE_MASK;
+                       if (mm != current->mm) {
+                               saved_asid = get_asid();
+                               set_asid(asid);
+                       }
+
+                       while (start < end) {
+                               __flush_tlb_page(asid, start);
+                               start += PAGE_SIZE;
+                       }
+                       if (saved_asid != MMU_NO_ASID)
+                               set_asid(saved_asid);
+               }
+               local_irq_restore(flags);
+       }
+}
+
+/*
+ * TODO: If this is only called for addresses > TASK_SIZE, we can probably
+ * skip the ASID stuff and just use the Global bit...
+ */
+void flush_tlb_kernel_range(unsigned long start, unsigned long end)
+{
+       unsigned long flags;
+       int size;
+
+       local_irq_save(flags);
+       size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
+       if (size > (MMU_DTLB_ENTRIES / 4)) { /* Too many entries to flush */
+               flush_tlb_all();
+       } else {
+               unsigned long asid = init_mm.context & MMU_CONTEXT_ASID_MASK;
+               unsigned long saved_asid = get_asid();
+
+               start &= PAGE_MASK;
+               end += (PAGE_SIZE - 1);
+               end &= PAGE_MASK;
+               set_asid(asid);
+               while (start < end) {
+                       __flush_tlb_page(asid, start);
+                       start += PAGE_SIZE;
+               }
+               set_asid(saved_asid);
+       }
+       local_irq_restore(flags);
+}
+
+void flush_tlb_mm(struct mm_struct *mm)
+{
+       /* Invalidate all TLB entries of this process by getting a new ASID */
+       if (mm->context != NO_CONTEXT) {
+               unsigned long flags;
+
+               local_irq_save(flags);
+               mm->context = NO_CONTEXT;
+               if (mm == current->mm)
+                       activate_context(mm);
+               local_irq_restore(flags);
+       }
+}
+
+void flush_tlb_all(void)
+{
+       unsigned long flags;
+
+       local_irq_save(flags);
+       sysreg_write(MMUCR, sysreg_read(MMUCR) | SYSREG_BIT(MMUCR_I));
+       local_irq_restore(flags);
+}
+
+#ifdef CONFIG_PROC_FS
+
+#include <linux/seq_file.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+
+static void *tlb_start(struct seq_file *tlb, loff_t *pos)
+{
+       static unsigned long tlb_index;
+
+       if (*pos >= 32)
+               return NULL;
+
+       tlb_index = 0;
+       return &tlb_index;
+}
+
+static void *tlb_next(struct seq_file *tlb, void *v, loff_t *pos)
+{
+       unsigned long *index = v;
+
+       if (*index >= 31)
+               return NULL;
+
+       ++*pos;
+       ++*index;
+       return index;
+}
+
+static void tlb_stop(struct seq_file *tlb, void *v)
+{
+
+}
+
+static int tlb_show(struct seq_file *tlb, void *v)
+{
+       unsigned int tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save;
+       unsigned long flags;
+       unsigned long *index = v;
+
+       if (*index == 0)
+               seq_puts(tlb, "ID  V G ASID VPN   PFN   AP SZ C B W D\n");
+
+       BUG_ON(*index >= 32);
+
+       local_irq_save(flags);
+       mmucr_save = sysreg_read(MMUCR);
+       tlbehi_save = sysreg_read(TLBEHI);
+       mmucr = mmucr_save & 0x13;
+       mmucr |= *index << 14;
+       sysreg_write(MMUCR, mmucr);
+
+       asm volatile("tlbr" : : : "memory");
+       cpu_sync_pipeline();
+
+       tlbehi = sysreg_read(TLBEHI);
+       tlbelo = sysreg_read(TLBELO);
+
+       sysreg_write(MMUCR, mmucr_save);
+       sysreg_write(TLBEHI, tlbehi_save);
+       cpu_sync_pipeline();
+       local_irq_restore(flags);
+
+       seq_printf(tlb, "%2lu: %c %c %02x   %05x %05x %o  %o  %c %c %c %c\n",
+              *index,
+              (tlbehi & 0x200)?'1':'0',
+              (tlbelo & 0x100)?'1':'0',
+              (tlbehi & 0xff),
+              (tlbehi >> 12), (tlbelo >> 12),
+              (tlbelo >> 4) & 7, (tlbelo >> 2) & 3,
+              (tlbelo & 0x200)?'1':'0',
+              (tlbelo & 0x080)?'1':'0',
+              (tlbelo & 0x001)?'1':'0',
+              (tlbelo & 0x002)?'1':'0');
+
+       return 0;
+}
+
+static struct seq_operations tlb_ops = {
+       .start          = tlb_start,
+       .next           = tlb_next,
+       .stop           = tlb_stop,
+       .show           = tlb_show,
+};
+
+static int tlb_open(struct inode *inode, struct file *file)
+{
+       return seq_open(file, &tlb_ops);
+}
+
+static struct file_operations proc_tlb_operations = {
+       .open           = tlb_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = seq_release,
+};
+
+static int __init proctlb_init(void)
+{
+       struct proc_dir_entry *entry;
+
+       entry = create_proc_entry("tlb", 0, NULL);
+       if (entry)
+               entry->proc_fops = &proc_tlb_operations;
+       return 0;
+}
+late_initcall(proctlb_init);
+#endif /* CONFIG_PROC_FS */