1/* 2 * Based on: arch/blackfin/kernel/cplb-mpu/cplbmgr.c 3 * Author: Michael McTernan <mmcternan@airvana.com> 4 * 5 * Description: CPLB miss handler. 6 * 7 * Modified: 8 * Copyright 2008 Airvana Inc. 9 * Copyright 2008-2009 Analog Devices Inc. 10 * 11 * Licensed under the GPL-2 or later 12 */ 13 14#include <linux/kernel.h> 15#include <asm/blackfin.h> 16#include <asm/cplbinit.h> 17#include <asm/cplb.h> 18#include <asm/mmu_context.h> 19#include <asm/traps.h> 20 21/* 22 * WARNING 23 * 24 * This file is compiled with certain -ffixed-reg options. We have to 25 * make sure not to call any functions here that could clobber these 26 * registers. 27 */ 28 29int nr_dcplb_miss[NR_CPUS], nr_icplb_miss[NR_CPUS]; 30int nr_dcplb_supv_miss[NR_CPUS], nr_icplb_supv_miss[NR_CPUS]; 31int nr_cplb_flush[NR_CPUS], nr_dcplb_prot[NR_CPUS]; 32 33#ifdef CONFIG_EXCPT_IRQ_SYSC_L1 34#define MGR_ATTR __attribute__((l1_text)) 35#else 36#define MGR_ATTR 37#endif 38 39static inline void write_dcplb_data(int cpu, int idx, unsigned long data, 40 unsigned long addr) 41{ 42 _disable_dcplb(); 43 bfin_write32(DCPLB_DATA0 + idx * 4, data); 44 bfin_write32(DCPLB_ADDR0 + idx * 4, addr); 45 _enable_dcplb(); 46 47#ifdef CONFIG_CPLB_INFO 48 dcplb_tbl[cpu][idx].addr = addr; 49 dcplb_tbl[cpu][idx].data = data; 50#endif 51} 52 53static inline void write_icplb_data(int cpu, int idx, unsigned long data, 54 unsigned long addr) 55{ 56 _disable_icplb(); 57 bfin_write32(ICPLB_DATA0 + idx * 4, data); 58 bfin_write32(ICPLB_ADDR0 + idx * 4, addr); 59 _enable_icplb(); 60 61#ifdef CONFIG_CPLB_INFO 62 icplb_tbl[cpu][idx].addr = addr; 63 icplb_tbl[cpu][idx].data = data; 64#endif 65} 66 67/* Counters to implement round-robin replacement. */ 68static int icplb_rr_index[NR_CPUS] PDT_ATTR; 69static int dcplb_rr_index[NR_CPUS] PDT_ATTR; 70 71/* 72 * Find an ICPLB entry to be evicted and return its index. 73 */ 74static int evict_one_icplb(int cpu) 75{ 76 int i = first_switched_icplb + icplb_rr_index[cpu]; 77 if (i >= MAX_CPLBS) { 78 i -= MAX_CPLBS - first_switched_icplb; 79 icplb_rr_index[cpu] -= MAX_CPLBS - first_switched_icplb; 80 } 81 icplb_rr_index[cpu]++; 82 return i; 83} 84 85static int evict_one_dcplb(int cpu) 86{ 87 int i = first_switched_dcplb + dcplb_rr_index[cpu]; 88 if (i >= MAX_CPLBS) { 89 i -= MAX_CPLBS - first_switched_dcplb; 90 dcplb_rr_index[cpu] -= MAX_CPLBS - first_switched_dcplb; 91 } 92 dcplb_rr_index[cpu]++; 93 return i; 94} 95 96MGR_ATTR static int icplb_miss(int cpu) 97{ 98 unsigned long addr = bfin_read_ICPLB_FAULT_ADDR(); 99 int status = bfin_read_ICPLB_STATUS(); 100 int idx; 101 unsigned long i_data, base, addr1, eaddr; 102 103 nr_icplb_miss[cpu]++; 104 if (unlikely(status & FAULT_USERSUPV)) 105 nr_icplb_supv_miss[cpu]++; 106 107 base = 0; 108 idx = 0; 109 do { 110 eaddr = icplb_bounds[idx].eaddr; 111 if (addr < eaddr) 112 break; 113 base = eaddr; 114 } while (++idx < icplb_nr_bounds); 115 116 if (unlikely(idx == icplb_nr_bounds)) 117 return CPLB_NO_ADDR_MATCH; 118 119 i_data = icplb_bounds[idx].data; 120 if (unlikely(i_data == 0)) 121 return CPLB_NO_ADDR_MATCH; 122 123 addr1 = addr & ~(SIZE_4M - 1); 124 addr &= ~(SIZE_1M - 1); 125 i_data |= PAGE_SIZE_1MB; 126 if (addr1 >= base && (addr1 + SIZE_4M) <= eaddr) { 127 /* 128 * This works because 129 * (PAGE_SIZE_4MB & PAGE_SIZE_1MB) == PAGE_SIZE_1MB. 130 */ 131 i_data |= PAGE_SIZE_4MB; 132 addr = addr1; 133 } 134 135 /* Pick entry to evict */ 136 idx = evict_one_icplb(cpu); 137 138 write_icplb_data(cpu, idx, i_data, addr); 139 140 return CPLB_RELOADED; 141} 142 143MGR_ATTR static int dcplb_miss(int cpu) 144{ 145 unsigned long addr = bfin_read_DCPLB_FAULT_ADDR(); 146 int status = bfin_read_DCPLB_STATUS(); 147 int idx; 148 unsigned long d_data, base, addr1, eaddr, cplb_pagesize, cplb_pageflags; 149 150 nr_dcplb_miss[cpu]++; 151 if (unlikely(status & FAULT_USERSUPV)) 152 nr_dcplb_supv_miss[cpu]++; 153 154 base = 0; 155 idx = 0; 156 do { 157 eaddr = dcplb_bounds[idx].eaddr; 158 if (addr < eaddr) 159 break; 160 base = eaddr; 161 } while (++idx < dcplb_nr_bounds); 162 163 if (unlikely(idx == dcplb_nr_bounds)) 164 return CPLB_NO_ADDR_MATCH; 165 166 d_data = dcplb_bounds[idx].data; 167 if (unlikely(d_data == 0)) 168 return CPLB_NO_ADDR_MATCH; 169 170 addr &= ~(SIZE_1M - 1); 171 d_data |= PAGE_SIZE_1MB; 172 173 /* BF60x support large than 4M CPLB page size */ 174#ifdef PAGE_SIZE_16MB 175 cplb_pageflags = PAGE_SIZE_16MB; 176 cplb_pagesize = SIZE_16M; 177#else 178 cplb_pageflags = PAGE_SIZE_4MB; 179 cplb_pagesize = SIZE_4M; 180#endif 181 182find_pagesize: 183 addr1 = addr & ~(cplb_pagesize - 1); 184 if (addr1 >= base && (addr1 + cplb_pagesize) <= eaddr) { 185 /* 186 * This works because 187 * (PAGE_SIZE_4MB & PAGE_SIZE_1MB) == PAGE_SIZE_1MB. 188 */ 189 d_data |= cplb_pageflags; 190 addr = addr1; 191 goto found_pagesize; 192 } else { 193 if (cplb_pagesize > SIZE_4M) { 194 cplb_pageflags = PAGE_SIZE_4MB; 195 cplb_pagesize = SIZE_4M; 196 goto find_pagesize; 197 } 198 } 199 200found_pagesize: 201#ifdef CONFIG_BF60x 202 if ((addr >= ASYNC_BANK0_BASE) 203 && (addr < ASYNC_BANK3_BASE + ASYNC_BANK3_SIZE)) 204 d_data |= PAGE_SIZE_64MB; 205#endif 206 207 /* Pick entry to evict */ 208 idx = evict_one_dcplb(cpu); 209 210 write_dcplb_data(cpu, idx, d_data, addr); 211 212 return CPLB_RELOADED; 213} 214 215MGR_ATTR int cplb_hdr(int seqstat, struct pt_regs *regs) 216{ 217 int cause = seqstat & 0x3f; 218 unsigned int cpu = raw_smp_processor_id(); 219 switch (cause) { 220 case VEC_CPLB_I_M: 221 return icplb_miss(cpu); 222 case VEC_CPLB_M: 223 return dcplb_miss(cpu); 224 default: 225 return CPLB_UNKNOWN_ERR; 226 } 227} 228