linux/arch/blackfin/kernel/cplb-mpu/cplbmgr.c
<<
>>
Prefs
   1/*
   2 * Blackfin CPLB exception handling for when MPU in on
   3 *
   4 * Copyright 2008-2009 Analog Devices Inc.
   5 *
   6 * Licensed under the GPL-2 or later.
   7 */
   8
   9#include <linux/module.h>
  10#include <linux/mm.h>
  11
  12#include <asm/blackfin.h>
  13#include <asm/cacheflush.h>
  14#include <asm/cplb.h>
  15#include <asm/cplbinit.h>
  16#include <asm/mmu_context.h>
  17
  18/*
  19 * WARNING
  20 *
  21 * This file is compiled with certain -ffixed-reg options.  We have to
  22 * make sure not to call any functions here that could clobber these
  23 * registers.
  24 */
  25
  26int page_mask_nelts;
  27int page_mask_order;
  28unsigned long *current_rwx_mask[NR_CPUS];
  29
  30int nr_dcplb_miss[NR_CPUS], nr_icplb_miss[NR_CPUS];
  31int nr_icplb_supv_miss[NR_CPUS], nr_dcplb_prot[NR_CPUS];
  32int nr_cplb_flush[NR_CPUS];
  33
  34#ifdef CONFIG_EXCPT_IRQ_SYSC_L1
  35#define MGR_ATTR __attribute__((l1_text))
  36#else
  37#define MGR_ATTR
  38#endif
  39
  40/*
  41 * Given the contents of the status register, return the index of the
  42 * CPLB that caused the fault.
  43 */
  44static inline int faulting_cplb_index(int status)
  45{
  46        int signbits = __builtin_bfin_norm_fr1x32(status & 0xFFFF);
  47        return 30 - signbits;
  48}
  49
  50/*
  51 * Given the contents of the status register and the DCPLB_DATA contents,
  52 * return true if a write access should be permitted.
  53 */
  54static inline int write_permitted(int status, unsigned long data)
  55{
  56        if (status & FAULT_USERSUPV)
  57                return !!(data & CPLB_SUPV_WR);
  58        else
  59                return !!(data & CPLB_USER_WR);
  60}
  61
  62/* Counters to implement round-robin replacement.  */
  63static int icplb_rr_index[NR_CPUS], dcplb_rr_index[NR_CPUS];
  64
  65/*
  66 * Find an ICPLB entry to be evicted and return its index.
  67 */
  68MGR_ATTR static int evict_one_icplb(unsigned int cpu)
  69{
  70        int i;
  71        for (i = first_switched_icplb; i < MAX_CPLBS; i++)
  72                if ((icplb_tbl[cpu][i].data & CPLB_VALID) == 0)
  73                        return i;
  74        i = first_switched_icplb + icplb_rr_index[cpu];
  75        if (i >= MAX_CPLBS) {
  76                i -= MAX_CPLBS - first_switched_icplb;
  77                icplb_rr_index[cpu] -= MAX_CPLBS - first_switched_icplb;
  78        }
  79        icplb_rr_index[cpu]++;
  80        return i;
  81}
  82
  83MGR_ATTR static int evict_one_dcplb(unsigned int cpu)
  84{
  85        int i;
  86        for (i = first_switched_dcplb; i < MAX_CPLBS; i++)
  87                if ((dcplb_tbl[cpu][i].data & CPLB_VALID) == 0)
  88                        return i;
  89        i = first_switched_dcplb + dcplb_rr_index[cpu];
  90        if (i >= MAX_CPLBS) {
  91                i -= MAX_CPLBS - first_switched_dcplb;
  92                dcplb_rr_index[cpu] -= MAX_CPLBS - first_switched_dcplb;
  93        }
  94        dcplb_rr_index[cpu]++;
  95        return i;
  96}
  97
  98MGR_ATTR static noinline int dcplb_miss(unsigned int cpu)
  99{
 100        unsigned long addr = bfin_read_DCPLB_FAULT_ADDR();
 101        int status = bfin_read_DCPLB_STATUS();
 102        unsigned long *mask;
 103        int idx;
 104        unsigned long d_data;
 105
 106        nr_dcplb_miss[cpu]++;
 107
 108        d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB;
 109#ifdef CONFIG_BFIN_EXTMEM_DCACHEABLE
 110        if (bfin_addr_dcacheable(addr)) {
 111                d_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND;
 112# ifdef CONFIG_BFIN_EXTMEM_WRITETHROUGH
 113                d_data |= CPLB_L1_AOW | CPLB_WT;
 114# endif
 115        }
 116#endif
 117
 118        if (L2_LENGTH && addr >= L2_START && addr < L2_START + L2_LENGTH) {
 119                addr = L2_START;
 120                d_data = L2_DMEMORY;
 121        } else if (addr >= physical_mem_end) {
 122                if (addr >= ASYNC_BANK0_BASE && addr < ASYNC_BANK3_BASE + ASYNC_BANK3_SIZE) {
 123#if defined(CONFIG_ROMFS_ON_MTD) && defined(CONFIG_MTD_ROM)
 124                        mask = current_rwx_mask[cpu];
 125                        if (mask) {
 126                                int page = (addr - (ASYNC_BANK0_BASE - _ramend)) >> PAGE_SHIFT;
 127                                int idx = page >> 5;
 128                                int bit = 1 << (page & 31);
 129
 130                                if (mask[idx] & bit)
 131                                        d_data |= CPLB_USER_RD;
 132                        }
 133#endif
 134                } else if (addr >= BOOT_ROM_START && addr < BOOT_ROM_START + BOOT_ROM_LENGTH
 135                    && (status & (FAULT_RW | FAULT_USERSUPV)) == FAULT_USERSUPV) {
 136                        addr &= ~(1 * 1024 * 1024 - 1);
 137                        d_data &= ~PAGE_SIZE_4KB;
 138                        d_data |= PAGE_SIZE_1MB;
 139                } else
 140                        return CPLB_PROT_VIOL;
 141        } else if (addr >= _ramend) {
 142                d_data |= CPLB_USER_RD | CPLB_USER_WR;
 143                if (reserved_mem_dcache_on)
 144                        d_data |= CPLB_L1_CHBL;
 145        } else {
 146                mask = current_rwx_mask[cpu];
 147                if (mask) {
 148                        int page = addr >> PAGE_SHIFT;
 149                        int idx = page >> 5;
 150                        int bit = 1 << (page & 31);
 151
 152                        if (mask[idx] & bit)
 153                                d_data |= CPLB_USER_RD;
 154
 155                        mask += page_mask_nelts;
 156                        if (mask[idx] & bit)
 157                                d_data |= CPLB_USER_WR;
 158                }
 159        }
 160        idx = evict_one_dcplb(cpu);
 161
 162        addr &= PAGE_MASK;
 163        dcplb_tbl[cpu][idx].addr = addr;
 164        dcplb_tbl[cpu][idx].data = d_data;
 165
 166        _disable_dcplb();
 167        bfin_write32(DCPLB_DATA0 + idx * 4, d_data);
 168        bfin_write32(DCPLB_ADDR0 + idx * 4, addr);
 169        _enable_dcplb();
 170
 171        return 0;
 172}
 173
 174MGR_ATTR static noinline int icplb_miss(unsigned int cpu)
 175{
 176        unsigned long addr = bfin_read_ICPLB_FAULT_ADDR();
 177        int status = bfin_read_ICPLB_STATUS();
 178        int idx;
 179        unsigned long i_data;
 180
 181        nr_icplb_miss[cpu]++;
 182
 183        /* If inside the uncached DMA region, fault.  */
 184        if (addr >= _ramend - DMA_UNCACHED_REGION && addr < _ramend)
 185                return CPLB_PROT_VIOL;
 186
 187        if (status & FAULT_USERSUPV)
 188                nr_icplb_supv_miss[cpu]++;
 189
 190        /*
 191         * First, try to find a CPLB that matches this address.  If we
 192         * find one, then the fact that we're in the miss handler means
 193         * that the instruction crosses a page boundary.
 194         */
 195        for (idx = first_switched_icplb; idx < MAX_CPLBS; idx++) {
 196                if (icplb_tbl[cpu][idx].data & CPLB_VALID) {
 197                        unsigned long this_addr = icplb_tbl[cpu][idx].addr;
 198                        if (this_addr <= addr && this_addr + PAGE_SIZE > addr) {
 199                                addr += PAGE_SIZE;
 200                                break;
 201                        }
 202                }
 203        }
 204
 205        i_data = CPLB_VALID | CPLB_PORTPRIO | PAGE_SIZE_4KB;
 206
 207#ifdef CONFIG_BFIN_EXTMEM_ICACHEABLE
 208        /*
 209         * Normal RAM, and possibly the reserved memory area, are
 210         * cacheable.
 211         */
 212        if (addr < _ramend ||
 213            (addr < physical_mem_end && reserved_mem_icache_on))
 214                i_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND;
 215#endif
 216
 217        if (L2_LENGTH && addr >= L2_START && addr < L2_START + L2_LENGTH) {
 218                addr = L2_START;
 219                i_data = L2_IMEMORY;
 220        } else if (addr >= physical_mem_end) {
 221                if (addr >= ASYNC_BANK0_BASE && addr < ASYNC_BANK3_BASE + ASYNC_BANK3_SIZE) {
 222                        if (!(status & FAULT_USERSUPV)) {
 223                                unsigned long *mask = current_rwx_mask[cpu];
 224
 225                                if (mask) {
 226                                        int page = (addr - (ASYNC_BANK0_BASE - _ramend)) >> PAGE_SHIFT;
 227                                        int idx = page >> 5;
 228                                        int bit = 1 << (page & 31);
 229
 230                                        mask += 2 * page_mask_nelts;
 231                                        if (mask[idx] & bit)
 232                                                i_data |= CPLB_USER_RD;
 233                                }
 234                        }
 235                } else if (addr >= BOOT_ROM_START && addr < BOOT_ROM_START + BOOT_ROM_LENGTH
 236                    && (status & FAULT_USERSUPV)) {
 237                        addr &= ~(1 * 1024 * 1024 - 1);
 238                        i_data &= ~PAGE_SIZE_4KB;
 239                        i_data |= PAGE_SIZE_1MB;
 240                } else
 241                    return CPLB_PROT_VIOL;
 242        } else if (addr >= _ramend) {
 243                i_data |= CPLB_USER_RD;
 244                if (reserved_mem_icache_on)
 245                        i_data |= CPLB_L1_CHBL;
 246        } else {
 247                /*
 248                 * Two cases to distinguish - a supervisor access must
 249                 * necessarily be for a module page; we grant it
 250                 * unconditionally (could do better here in the future).
 251                 * Otherwise, check the x bitmap of the current process.
 252                 */
 253                if (!(status & FAULT_USERSUPV)) {
 254                        unsigned long *mask = current_rwx_mask[cpu];
 255
 256                        if (mask) {
 257                                int page = addr >> PAGE_SHIFT;
 258                                int idx = page >> 5;
 259                                int bit = 1 << (page & 31);
 260
 261                                mask += 2 * page_mask_nelts;
 262                                if (mask[idx] & bit)
 263                                        i_data |= CPLB_USER_RD;
 264                        }
 265                }
 266        }
 267        idx = evict_one_icplb(cpu);
 268        addr &= PAGE_MASK;
 269        icplb_tbl[cpu][idx].addr = addr;
 270        icplb_tbl[cpu][idx].data = i_data;
 271
 272        _disable_icplb();
 273        bfin_write32(ICPLB_DATA0 + idx * 4, i_data);
 274        bfin_write32(ICPLB_ADDR0 + idx * 4, addr);
 275        _enable_icplb();
 276
 277        return 0;
 278}
 279
 280MGR_ATTR static noinline int dcplb_protection_fault(unsigned int cpu)
 281{
 282        int status = bfin_read_DCPLB_STATUS();
 283
 284        nr_dcplb_prot[cpu]++;
 285
 286        if (status & FAULT_RW) {
 287                int idx = faulting_cplb_index(status);
 288                unsigned long data = dcplb_tbl[cpu][idx].data;
 289                if (!(data & CPLB_WT) && !(data & CPLB_DIRTY) &&
 290                    write_permitted(status, data)) {
 291                        data |= CPLB_DIRTY;
 292                        dcplb_tbl[cpu][idx].data = data;
 293                        bfin_write32(DCPLB_DATA0 + idx * 4, data);
 294                        return 0;
 295                }
 296        }
 297        return CPLB_PROT_VIOL;
 298}
 299
 300MGR_ATTR int cplb_hdr(int seqstat, struct pt_regs *regs)
 301{
 302        int cause = seqstat & 0x3f;
 303        unsigned int cpu = raw_smp_processor_id();
 304        switch (cause) {
 305        case 0x23:
 306                return dcplb_protection_fault(cpu);
 307        case 0x2C:
 308                return icplb_miss(cpu);
 309        case 0x26:
 310                return dcplb_miss(cpu);
 311        default:
 312                return 1;
 313        }
 314}
 315
 316void flush_switched_cplbs(unsigned int cpu)
 317{
 318        int i;
 319        unsigned long flags;
 320
 321        nr_cplb_flush[cpu]++;
 322
 323        flags = hard_local_irq_save();
 324        _disable_icplb();
 325        for (i = first_switched_icplb; i < MAX_CPLBS; i++) {
 326                icplb_tbl[cpu][i].data = 0;
 327                bfin_write32(ICPLB_DATA0 + i * 4, 0);
 328        }
 329        _enable_icplb();
 330
 331        _disable_dcplb();
 332        for (i = first_switched_dcplb; i < MAX_CPLBS; i++) {
 333                dcplb_tbl[cpu][i].data = 0;
 334                bfin_write32(DCPLB_DATA0 + i * 4, 0);
 335        }
 336        _enable_dcplb();
 337        hard_local_irq_restore(flags);
 338
 339}
 340
 341void set_mask_dcplbs(unsigned long *masks, unsigned int cpu)
 342{
 343        int i;
 344        unsigned long addr = (unsigned long)masks;
 345        unsigned long d_data;
 346        unsigned long flags;
 347
 348        if (!masks) {
 349                current_rwx_mask[cpu] = masks;
 350                return;
 351        }
 352
 353        flags = hard_local_irq_save();
 354        current_rwx_mask[cpu] = masks;
 355
 356        if (L2_LENGTH && addr >= L2_START && addr < L2_START + L2_LENGTH) {
 357                addr = L2_START;
 358                d_data = L2_DMEMORY;
 359        } else {
 360                d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB;
 361#ifdef CONFIG_BFIN_EXTMEM_DCACHEABLE
 362                d_data |= CPLB_L1_CHBL;
 363# ifdef CONFIG_BFIN_EXTMEM_WRITETHROUGH
 364                d_data |= CPLB_L1_AOW | CPLB_WT;
 365# endif
 366#endif
 367        }
 368
 369        _disable_dcplb();
 370        for (i = first_mask_dcplb; i < first_switched_dcplb; i++) {
 371                dcplb_tbl[cpu][i].addr = addr;
 372                dcplb_tbl[cpu][i].data = d_data;
 373                bfin_write32(DCPLB_DATA0 + i * 4, d_data);
 374                bfin_write32(DCPLB_ADDR0 + i * 4, addr);
 375                addr += PAGE_SIZE;
 376        }
 377        _enable_dcplb();
 378        hard_local_irq_restore(flags);
 379}
 380