linux/arch/nios2/mm/tlb.c
<<
>>
Prefs
   1/*
   2 * Nios2 TLB handling
   3 *
   4 * Copyright (C) 2009, Wind River Systems Inc
   5 *   Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com
   6 *
   7 * This file is subject to the terms and conditions of the GNU General Public
   8 * License.  See the file "COPYING" in the main directory of this archive
   9 * for more details.
  10 */
  11
  12#include <linux/init.h>
  13#include <linux/sched.h>
  14#include <linux/mm.h>
  15#include <linux/pagemap.h>
  16
  17#include <asm/tlb.h>
  18#include <asm/mmu_context.h>
  19#include <asm/pgtable.h>
  20#include <asm/cpuinfo.h>
  21
  22#define TLB_INDEX_MASK          \
  23        ((((1UL << (cpuinfo.tlb_ptr_sz - cpuinfo.tlb_num_ways_log2))) - 1) \
  24                << PAGE_SHIFT)
  25
  26static void get_misc_and_pid(unsigned long *misc, unsigned long *pid)
  27{
  28        *misc  = RDCTL(CTL_TLBMISC);
  29        *misc &= (TLBMISC_PID | TLBMISC_WAY);
  30        *pid  = *misc & TLBMISC_PID;
  31}
  32
  33/*
  34 * This provides a PTEADDR value for addr that will cause a TLB miss
  35 * (fast TLB miss). TLB invalidation replaces entries with this value.
  36 */
  37static unsigned long pteaddr_invalid(unsigned long addr)
  38{
  39        return ((addr | 0xC0000000UL) >> PAGE_SHIFT) << 2;
  40}
  41
  42/*
  43 * This one is only used for pages with the global bit set so we don't care
  44 * much about the ASID.
  45 */
  46static void replace_tlb_one_pid(unsigned long addr, unsigned long mmu_pid, unsigned long tlbacc)
  47{
  48        unsigned int way;
  49        unsigned long org_misc, pid_misc;
  50
  51        /* remember pid/way until we return. */
  52        get_misc_and_pid(&org_misc, &pid_misc);
  53
  54        WRCTL(CTL_PTEADDR, (addr >> PAGE_SHIFT) << 2);
  55
  56        for (way = 0; way < cpuinfo.tlb_num_ways; way++) {
  57                unsigned long pteaddr;
  58                unsigned long tlbmisc;
  59                unsigned long pid;
  60
  61                tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT);
  62                WRCTL(CTL_TLBMISC, tlbmisc);
  63
  64                pteaddr = RDCTL(CTL_PTEADDR);
  65                if (((pteaddr >> 2) & 0xfffff) != (addr >> PAGE_SHIFT))
  66                        continue;
  67
  68                tlbmisc = RDCTL(CTL_TLBMISC);
  69                pid = (tlbmisc >> TLBMISC_PID_SHIFT) & TLBMISC_PID_MASK;
  70                if (pid != mmu_pid)
  71                        continue;
  72
  73                tlbmisc = (mmu_pid << TLBMISC_PID_SHIFT) | TLBMISC_WE |
  74                          (way << TLBMISC_WAY_SHIFT);
  75                WRCTL(CTL_TLBMISC, tlbmisc);
  76                if (tlbacc == 0)
  77                        WRCTL(CTL_PTEADDR, pteaddr_invalid(addr));
  78                WRCTL(CTL_TLBACC, tlbacc);
  79                /*
  80                 * There should be only a single entry that maps a
  81                 * particular {address,pid} so break after a match.
  82                 */
  83                break;
  84        }
  85
  86        WRCTL(CTL_TLBMISC, org_misc);
  87}
  88
  89static void flush_tlb_one_pid(unsigned long addr, unsigned long mmu_pid)
  90{
  91        pr_debug("Flush tlb-entry for vaddr=%#lx\n", addr);
  92
  93        replace_tlb_one_pid(addr, mmu_pid, 0);
  94}
  95
  96static void reload_tlb_one_pid(unsigned long addr, unsigned long mmu_pid, pte_t pte)
  97{
  98        pr_debug("Reload tlb-entry for vaddr=%#lx\n", addr);
  99
 100        replace_tlb_one_pid(addr, mmu_pid, pte_val(pte));
 101}
 102
 103void flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
 104                        unsigned long end)
 105{
 106        unsigned long mmu_pid = get_pid_from_context(&vma->vm_mm->context);
 107
 108        while (start < end) {
 109                flush_tlb_one_pid(start, mmu_pid);
 110                start += PAGE_SIZE;
 111        }
 112}
 113
 114void reload_tlb_page(struct vm_area_struct *vma, unsigned long addr, pte_t pte)
 115{
 116        unsigned long mmu_pid = get_pid_from_context(&vma->vm_mm->context);
 117
 118        reload_tlb_one_pid(addr, mmu_pid, pte);
 119}
 120
 121/*
 122 * This one is only used for pages with the global bit set so we don't care
 123 * much about the ASID.
 124 */
 125static void flush_tlb_one(unsigned long addr)
 126{
 127        unsigned int way;
 128        unsigned long org_misc, pid_misc;
 129
 130        pr_debug("Flush tlb-entry for vaddr=%#lx\n", addr);
 131
 132        /* remember pid/way until we return. */
 133        get_misc_and_pid(&org_misc, &pid_misc);
 134
 135        WRCTL(CTL_PTEADDR, (addr >> PAGE_SHIFT) << 2);
 136
 137        for (way = 0; way < cpuinfo.tlb_num_ways; way++) {
 138                unsigned long pteaddr;
 139                unsigned long tlbmisc;
 140
 141                tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT);
 142                WRCTL(CTL_TLBMISC, tlbmisc);
 143
 144                pteaddr = RDCTL(CTL_PTEADDR);
 145                if (((pteaddr >> 2) & 0xfffff) != (addr >> PAGE_SHIFT))
 146                        continue;
 147
 148                pr_debug("Flush entry by writing way=%dl pid=%ld\n",
 149                         way, (pid_misc >> TLBMISC_PID_SHIFT));
 150
 151                tlbmisc = TLBMISC_WE | (way << TLBMISC_WAY_SHIFT);
 152                WRCTL(CTL_TLBMISC, tlbmisc);
 153                WRCTL(CTL_PTEADDR, pteaddr_invalid(addr));
 154                WRCTL(CTL_TLBACC, 0);
 155        }
 156
 157        WRCTL(CTL_TLBMISC, org_misc);
 158}
 159
 160void flush_tlb_kernel_range(unsigned long start, unsigned long end)
 161{
 162        while (start < end) {
 163                flush_tlb_one(start);
 164                start += PAGE_SIZE;
 165        }
 166}
 167
 168void dump_tlb_line(unsigned long line)
 169{
 170        unsigned int way;
 171        unsigned long org_misc;
 172
 173        pr_debug("dump tlb-entries for line=%#lx (addr %08lx)\n", line,
 174                line << (PAGE_SHIFT + cpuinfo.tlb_num_ways_log2));
 175
 176        /* remember pid/way until we return */
 177        org_misc = (RDCTL(CTL_TLBMISC) & (TLBMISC_PID | TLBMISC_WAY));
 178
 179        WRCTL(CTL_PTEADDR, line << 2);
 180
 181        for (way = 0; way < cpuinfo.tlb_num_ways; way++) {
 182                unsigned long pteaddr;
 183                unsigned long tlbmisc;
 184                unsigned long tlbacc;
 185
 186                WRCTL(CTL_TLBMISC, TLBMISC_RD | (way << TLBMISC_WAY_SHIFT));
 187                pteaddr = RDCTL(CTL_PTEADDR);
 188                tlbmisc = RDCTL(CTL_TLBMISC);
 189                tlbacc = RDCTL(CTL_TLBACC);
 190
 191                if ((tlbacc << PAGE_SHIFT) != 0) {
 192                        pr_debug("-- way:%02x vpn:0x%08lx phys:0x%08lx pid:0x%02lx flags:%c%c%c%c%c\n",
 193                                way,
 194                                (pteaddr << (PAGE_SHIFT-2)),
 195                                (tlbacc << PAGE_SHIFT),
 196                                ((tlbmisc >> TLBMISC_PID_SHIFT) &
 197                                TLBMISC_PID_MASK),
 198                                (tlbacc & _PAGE_READ ? 'r' : '-'),
 199                                (tlbacc & _PAGE_WRITE ? 'w' : '-'),
 200                                (tlbacc & _PAGE_EXEC ? 'x' : '-'),
 201                                (tlbacc & _PAGE_GLOBAL ? 'g' : '-'),
 202                                (tlbacc & _PAGE_CACHED ? 'c' : '-'));
 203                }
 204        }
 205
 206        WRCTL(CTL_TLBMISC, org_misc);
 207}
 208
 209void dump_tlb(void)
 210{
 211        unsigned int i;
 212
 213        for (i = 0; i < cpuinfo.tlb_num_lines; i++)
 214                dump_tlb_line(i);
 215}
 216
 217void flush_tlb_pid(unsigned long mmu_pid)
 218{
 219        unsigned long addr = 0;
 220        unsigned int line;
 221        unsigned int way;
 222        unsigned long org_misc, pid_misc;
 223
 224        /* remember pid/way until we return */
 225        get_misc_and_pid(&org_misc, &pid_misc);
 226
 227        for (line = 0; line < cpuinfo.tlb_num_lines; line++) {
 228                WRCTL(CTL_PTEADDR, pteaddr_invalid(addr));
 229
 230                for (way = 0; way < cpuinfo.tlb_num_ways; way++) {
 231                        unsigned long tlbmisc;
 232                        unsigned long pid;
 233
 234                        tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT);
 235                        WRCTL(CTL_TLBMISC, tlbmisc);
 236                        tlbmisc = RDCTL(CTL_TLBMISC);
 237                        pid = (tlbmisc >> TLBMISC_PID_SHIFT) & TLBMISC_PID_MASK;
 238                        if (pid != mmu_pid)
 239                                continue;
 240
 241                        tlbmisc = TLBMISC_WE | (way << TLBMISC_WAY_SHIFT);
 242                        WRCTL(CTL_TLBMISC, tlbmisc);
 243                        WRCTL(CTL_TLBACC, 0);
 244                }
 245
 246                addr += PAGE_SIZE;
 247        }
 248
 249        WRCTL(CTL_TLBMISC, org_misc);
 250}
 251
 252/*
 253 * All entries common to a mm share an asid.  To effectively flush these
 254 * entries, we just bump the asid.
 255 */
 256void flush_tlb_mm(struct mm_struct *mm)
 257{
 258        if (current->mm == mm) {
 259                unsigned long mmu_pid = get_pid_from_context(&mm->context);
 260                flush_tlb_pid(mmu_pid);
 261        } else {
 262                memset(&mm->context, 0, sizeof(mm_context_t));
 263        }
 264}
 265
 266void flush_tlb_all(void)
 267{
 268        unsigned long addr = 0;
 269        unsigned int line;
 270        unsigned int way;
 271        unsigned long org_misc, pid_misc;
 272
 273        /* remember pid/way until we return */
 274        get_misc_and_pid(&org_misc, &pid_misc);
 275
 276        /* Start at way 0, way is auto-incremented after each TLBACC write */
 277        WRCTL(CTL_TLBMISC, TLBMISC_WE);
 278
 279        /* Map each TLB entry to physcal address 0 with no-access and a
 280           bad ptbase */
 281        for (line = 0; line < cpuinfo.tlb_num_lines; line++) {
 282                WRCTL(CTL_PTEADDR, pteaddr_invalid(addr));
 283                for (way = 0; way < cpuinfo.tlb_num_ways; way++)
 284                        WRCTL(CTL_TLBACC, 0);
 285
 286                addr += PAGE_SIZE;
 287        }
 288
 289        /* restore pid/way */
 290        WRCTL(CTL_TLBMISC, org_misc);
 291}
 292
 293void set_mmu_pid(unsigned long pid)
 294{
 295        unsigned long tlbmisc;
 296
 297        tlbmisc = RDCTL(CTL_TLBMISC);
 298        tlbmisc = (tlbmisc & TLBMISC_WAY);
 299        tlbmisc |= (pid & TLBMISC_PID_MASK) << TLBMISC_PID_SHIFT;
 300        WRCTL(CTL_TLBMISC, tlbmisc);
 301}
 302