linux/arch/avr32/mm/tlb.c
<<
>>
Prefs
   1/*
   2 * AVR32 TLB operations
   3 *
   4 * Copyright (C) 2004-2006 Atmel Corporation
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9 */
  10#include <linux/mm.h>
  11
  12#include <asm/mmu_context.h>
  13
  14/* TODO: Get the correct number from the CONFIG1 system register */
  15#define NR_TLB_ENTRIES 32
  16
  17static void show_dtlb_entry(unsigned int index)
  18{
  19        u32 tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save;
  20        unsigned long flags;
  21
  22        local_irq_save(flags);
  23        mmucr_save = sysreg_read(MMUCR);
  24        tlbehi_save = sysreg_read(TLBEHI);
  25        mmucr = SYSREG_BFINS(DRP, index, mmucr_save);
  26        sysreg_write(MMUCR, mmucr);
  27
  28        __builtin_tlbr();
  29        cpu_sync_pipeline();
  30
  31        tlbehi = sysreg_read(TLBEHI);
  32        tlbelo = sysreg_read(TLBELO);
  33
  34        printk("%2u: %c %c %02x   %05x %05x %o  %o  %c %c %c %c\n",
  35               index,
  36               SYSREG_BFEXT(TLBEHI_V, tlbehi) ? '1' : '0',
  37               SYSREG_BFEXT(G, tlbelo) ? '1' : '0',
  38               SYSREG_BFEXT(ASID, tlbehi),
  39               SYSREG_BFEXT(VPN, tlbehi) >> 2,
  40               SYSREG_BFEXT(PFN, tlbelo) >> 2,
  41               SYSREG_BFEXT(AP, tlbelo),
  42               SYSREG_BFEXT(SZ, tlbelo),
  43               SYSREG_BFEXT(TLBELO_C, tlbelo) ? 'C' : ' ',
  44               SYSREG_BFEXT(B, tlbelo) ? 'B' : ' ',
  45               SYSREG_BFEXT(W, tlbelo) ? 'W' : ' ',
  46               SYSREG_BFEXT(TLBELO_D, tlbelo) ? 'D' : ' ');
  47
  48        sysreg_write(MMUCR, mmucr_save);
  49        sysreg_write(TLBEHI, tlbehi_save);
  50        cpu_sync_pipeline();
  51        local_irq_restore(flags);
  52}
  53
  54void dump_dtlb(void)
  55{
  56        unsigned int i;
  57
  58        printk("ID  V G ASID VPN   PFN   AP SZ C B W D\n");
  59        for (i = 0; i < NR_TLB_ENTRIES; i++)
  60                show_dtlb_entry(i);
  61}
  62
  63static void update_dtlb(unsigned long address, pte_t pte)
  64{
  65        u32 tlbehi;
  66        u32 mmucr;
  67
  68        /*
  69         * We're not changing the ASID here, so no need to flush the
  70         * pipeline.
  71         */
  72        tlbehi = sysreg_read(TLBEHI);
  73        tlbehi = SYSREG_BF(ASID, SYSREG_BFEXT(ASID, tlbehi));
  74        tlbehi |= address & MMU_VPN_MASK;
  75        tlbehi |= SYSREG_BIT(TLBEHI_V);
  76        sysreg_write(TLBEHI, tlbehi);
  77
  78        /* Does this mapping already exist? */
  79        __builtin_tlbs();
  80        mmucr = sysreg_read(MMUCR);
  81
  82        if (mmucr & SYSREG_BIT(MMUCR_N)) {
  83                /* Not found -- pick a not-recently-accessed entry */
  84                unsigned int rp;
  85                u32 tlbar = sysreg_read(TLBARLO);
  86
  87                rp = 32 - fls(tlbar);
  88                if (rp == 32) {
  89                        rp = 0;
  90                        sysreg_write(TLBARLO, -1L);
  91                }
  92
  93                mmucr = SYSREG_BFINS(DRP, rp, mmucr);
  94                sysreg_write(MMUCR, mmucr);
  95        }
  96
  97        sysreg_write(TLBELO, pte_val(pte) & _PAGE_FLAGS_HARDWARE_MASK);
  98
  99        /* Let's go */
 100        __builtin_tlbw();
 101}
 102
 103void update_mmu_cache(struct vm_area_struct *vma,
 104                      unsigned long address, pte_t pte)
 105{
 106        unsigned long flags;
 107
 108        /* ptrace may call this routine */
 109        if (vma && current->active_mm != vma->vm_mm)
 110                return;
 111
 112        local_irq_save(flags);
 113        update_dtlb(address, pte);
 114        local_irq_restore(flags);
 115}
 116
 117static void __flush_tlb_page(unsigned long asid, unsigned long page)
 118{
 119        u32 mmucr, tlbehi;
 120
 121        /*
 122         * Caller is responsible for masking out non-PFN bits in page
 123         * and changing the current ASID if necessary. This means that
 124         * we don't need to flush the pipeline after writing TLBEHI.
 125         */
 126        tlbehi = page | asid;
 127        sysreg_write(TLBEHI, tlbehi);
 128
 129        __builtin_tlbs();
 130        mmucr = sysreg_read(MMUCR);
 131
 132        if (!(mmucr & SYSREG_BIT(MMUCR_N))) {
 133                unsigned int entry;
 134                u32 tlbarlo;
 135
 136                /* Clear the "valid" bit */
 137                sysreg_write(TLBEHI, tlbehi);
 138
 139                /* mark the entry as "not accessed" */
 140                entry = SYSREG_BFEXT(DRP, mmucr);
 141                tlbarlo = sysreg_read(TLBARLO);
 142                tlbarlo |= (0x80000000UL >> entry);
 143                sysreg_write(TLBARLO, tlbarlo);
 144
 145                /* update the entry with valid bit clear */
 146                __builtin_tlbw();
 147        }
 148}
 149
 150void flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
 151{
 152        if (vma->vm_mm && vma->vm_mm->context != NO_CONTEXT) {
 153                unsigned long flags, asid;
 154                unsigned long saved_asid = MMU_NO_ASID;
 155
 156                asid = vma->vm_mm->context & MMU_CONTEXT_ASID_MASK;
 157                page &= PAGE_MASK;
 158
 159                local_irq_save(flags);
 160                if (vma->vm_mm != current->mm) {
 161                        saved_asid = get_asid();
 162                        set_asid(asid);
 163                }
 164
 165                __flush_tlb_page(asid, page);
 166
 167                if (saved_asid != MMU_NO_ASID)
 168                        set_asid(saved_asid);
 169                local_irq_restore(flags);
 170        }
 171}
 172
 173void flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
 174                     unsigned long end)
 175{
 176        struct mm_struct *mm = vma->vm_mm;
 177
 178        if (mm->context != NO_CONTEXT) {
 179                unsigned long flags;
 180                int size;
 181
 182                local_irq_save(flags);
 183                size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
 184
 185                if (size > (MMU_DTLB_ENTRIES / 4)) { /* Too many entries to flush */
 186                        mm->context = NO_CONTEXT;
 187                        if (mm == current->mm)
 188                                activate_context(mm);
 189                } else {
 190                        unsigned long asid;
 191                        unsigned long saved_asid;
 192
 193                        asid = mm->context & MMU_CONTEXT_ASID_MASK;
 194                        saved_asid = MMU_NO_ASID;
 195
 196                        start &= PAGE_MASK;
 197                        end += (PAGE_SIZE - 1);
 198                        end &= PAGE_MASK;
 199
 200                        if (mm != current->mm) {
 201                                saved_asid = get_asid();
 202                                set_asid(asid);
 203                        }
 204
 205                        while (start < end) {
 206                                __flush_tlb_page(asid, start);
 207                                start += PAGE_SIZE;
 208                        }
 209                        if (saved_asid != MMU_NO_ASID)
 210                                set_asid(saved_asid);
 211                }
 212                local_irq_restore(flags);
 213        }
 214}
 215
 216/*
 217 * This function depends on the pages to be flushed having the G
 218 * (global) bit set in their pte. This is true for all
 219 * PAGE_KERNEL(_RO) pages.
 220 */
 221void flush_tlb_kernel_range(unsigned long start, unsigned long end)
 222{
 223        unsigned long flags;
 224        int size;
 225
 226        size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
 227        if (size > (MMU_DTLB_ENTRIES / 4)) { /* Too many entries to flush */
 228                flush_tlb_all();
 229        } else {
 230                unsigned long asid;
 231
 232                local_irq_save(flags);
 233                asid = get_asid();
 234
 235                start &= PAGE_MASK;
 236                end += (PAGE_SIZE - 1);
 237                end &= PAGE_MASK;
 238
 239                while (start < end) {
 240                        __flush_tlb_page(asid, start);
 241                        start += PAGE_SIZE;
 242                }
 243                local_irq_restore(flags);
 244        }
 245}
 246
 247void flush_tlb_mm(struct mm_struct *mm)
 248{
 249        /* Invalidate all TLB entries of this process by getting a new ASID */
 250        if (mm->context != NO_CONTEXT) {
 251                unsigned long flags;
 252
 253                local_irq_save(flags);
 254                mm->context = NO_CONTEXT;
 255                if (mm == current->mm)
 256                        activate_context(mm);
 257                local_irq_restore(flags);
 258        }
 259}
 260
 261void flush_tlb_all(void)
 262{
 263        unsigned long flags;
 264
 265        local_irq_save(flags);
 266        sysreg_write(MMUCR, sysreg_read(MMUCR) | SYSREG_BIT(MMUCR_I));
 267        local_irq_restore(flags);
 268}
 269
 270#ifdef CONFIG_PROC_FS
 271
 272#include <linux/seq_file.h>
 273#include <linux/proc_fs.h>
 274#include <linux/init.h>
 275
 276static void *tlb_start(struct seq_file *tlb, loff_t *pos)
 277{
 278        static unsigned long tlb_index;
 279
 280        if (*pos >= NR_TLB_ENTRIES)
 281                return NULL;
 282
 283        tlb_index = 0;
 284        return &tlb_index;
 285}
 286
 287static void *tlb_next(struct seq_file *tlb, void *v, loff_t *pos)
 288{
 289        unsigned long *index = v;
 290
 291        if (*index >= NR_TLB_ENTRIES - 1)
 292                return NULL;
 293
 294        ++*pos;
 295        ++*index;
 296        return index;
 297}
 298
 299static void tlb_stop(struct seq_file *tlb, void *v)
 300{
 301
 302}
 303
 304static int tlb_show(struct seq_file *tlb, void *v)
 305{
 306        unsigned int tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save;
 307        unsigned long flags;
 308        unsigned long *index = v;
 309
 310        if (*index == 0)
 311                seq_puts(tlb, "ID  V G ASID VPN   PFN   AP SZ C B W D\n");
 312
 313        BUG_ON(*index >= NR_TLB_ENTRIES);
 314
 315        local_irq_save(flags);
 316        mmucr_save = sysreg_read(MMUCR);
 317        tlbehi_save = sysreg_read(TLBEHI);
 318        mmucr = SYSREG_BFINS(DRP, *index, mmucr_save);
 319        sysreg_write(MMUCR, mmucr);
 320
 321        /* TLBR might change the ASID */
 322        __builtin_tlbr();
 323        cpu_sync_pipeline();
 324
 325        tlbehi = sysreg_read(TLBEHI);
 326        tlbelo = sysreg_read(TLBELO);
 327
 328        sysreg_write(MMUCR, mmucr_save);
 329        sysreg_write(TLBEHI, tlbehi_save);
 330        cpu_sync_pipeline();
 331        local_irq_restore(flags);
 332
 333        seq_printf(tlb, "%2lu: %c %c %02x   %05x %05x %o  %o  %c %c %c %c\n",
 334                   *index,
 335                   SYSREG_BFEXT(TLBEHI_V, tlbehi) ? '1' : '0',
 336                   SYSREG_BFEXT(G, tlbelo) ? '1' : '0',
 337                   SYSREG_BFEXT(ASID, tlbehi),
 338                   SYSREG_BFEXT(VPN, tlbehi) >> 2,
 339                   SYSREG_BFEXT(PFN, tlbelo) >> 2,
 340                   SYSREG_BFEXT(AP, tlbelo),
 341                   SYSREG_BFEXT(SZ, tlbelo),
 342                   SYSREG_BFEXT(TLBELO_C, tlbelo) ? '1' : '0',
 343                   SYSREG_BFEXT(B, tlbelo) ? '1' : '0',
 344                   SYSREG_BFEXT(W, tlbelo) ? '1' : '0',
 345                   SYSREG_BFEXT(TLBELO_D, tlbelo) ? '1' : '0');
 346
 347        return 0;
 348}
 349
 350static const struct seq_operations tlb_ops = {
 351        .start          = tlb_start,
 352        .next           = tlb_next,
 353        .stop           = tlb_stop,
 354        .show           = tlb_show,
 355};
 356
 357static int tlb_open(struct inode *inode, struct file *file)
 358{
 359        return seq_open(file, &tlb_ops);
 360}
 361
 362static const struct file_operations proc_tlb_operations = {
 363        .open           = tlb_open,
 364        .read           = seq_read,
 365        .llseek         = seq_lseek,
 366        .release        = seq_release,
 367};
 368
 369static int __init proctlb_init(void)
 370{
 371        proc_create("tlb", 0, NULL, &proc_tlb_operations);
 372        return 0;
 373}
 374late_initcall(proctlb_init);
 375#endif /* CONFIG_PROC_FS */
 376