1/* 2 * Broadcom BCM47xx Performance Counters 3 * 4 * Copyright 2006, Broadcom Corporation 5 * All Rights Reserved. 6 * 7 * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY 8 * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM 9 * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS 10 * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. 11 * 12 * $Id: perfcntr.c,v 1.1.1.1 2008/10/15 03:26:06 james26_jang Exp $ 13 */ 14 15#include <linux/config.h> 16 17#ifdef CONFIG_PROC_FS 18#include <linux/proc_fs.h> 19#include <typedefs.h> 20#include <osl.h> 21#include <mipsinc.h> 22 23/* 24 * BCM4710 performance counter register select values 25 * No even-odd control-counter mapping, just counters 26 */ 27#define PERF_DCACHE_HIT 0 28#define PERF_DCACHE_MISS 1 29#define PERF_ICACHE_HIT 2 30#define PERF_ICACHE_MISS 3 31#define PERF_ICOUNT 4 32 33asmlinkage uint read_perf_cntr(uint counter) 34{ 35 uint32 prid = MFC0(C0_PRID, 0); 36 if (BCM330X(prid)) { 37#ifdef CONFIG_HND_BMIPS3300_PROF 38 switch (counter) { 39 case PERF_DCACHE_HIT: return -MFC0(C0_PERFORMANCE, 0); 40 case PERF_DCACHE_MISS: return -MFC0(C0_PERFORMANCE, 1); 41 case PERF_ICACHE_HIT: return -MFC0(C0_PERFORMANCE, 2); 42 case PERF_ICACHE_MISS: return -MFC0(C0_PERFORMANCE, 3); 43 } 44#endif /* CONFIG_HND_BMIPS3300_PROF */ 45 return 0; 46 } 47 else { 48 switch (counter) { 49 case PERF_DCACHE_HIT: return MFC0(C0_PERFORMANCE, 0); 50 case PERF_DCACHE_MISS: return MFC0(C0_PERFORMANCE, 1); 51 case PERF_ICACHE_HIT: return MFC0(C0_PERFORMANCE, 2); 52 case PERF_ICACHE_MISS: return MFC0(C0_PERFORMANCE, 3); 53 case PERF_ICOUNT: return MFC0(C0_PERFORMANCE, 4); 54 } 55 } 56 return 0; 57} 58 59/* 60 * 'data' passed to proc entry callback is formatted as: 61 * (reg << 16) + sel 62 */ 63#define REGSHIFT 16 /* register # is at high 16 bit */ 64#define SELMASK 0xffff /* select # is at low 16 bit */ 65 66/* 67 * Template to read/write cp0 register. Caller will modify 68 * the mfc0 and mtc0 instructions before calling. 69 */ 70static void tmfc0(void) 71{ 72 __asm__ __volatile__( 73 ".set\tnoreorder\n\t" 74 ".set\tnoat\n\t" 75 "mfc0\t$1,$0\n\t" 76 "move\t$2,$1\n\t" 77 ".set\tat\n\t" 78 ".set\treorder"); 79} 80 81static void tmtc0(uint val) 82{ 83 __asm__ __volatile__( 84 ".set\tnoreorder\n\t" 85 ".set\tnoat\n\t" 86 "move\t$1,$4\n\t" 87 "mtc0\t$1,$0\n\t" 88 ".set\tat\n\t" 89 ".set\treorder"); 90} 91 92/* 93 * Read/write cp0 register. Assuming code space is writeable 94 * so modify the mfc0 and mtc0 instructions before calling the 95 * function tmfc0() and tmtc0() to build the correct mfc0 and 96 * mtc0 instructions. 97 */ 98static int cp0_read(char *page, char **start, off_t off, 99 int count, int *eof, void *data) 100{ 101 uint reg, sel; 102 uint (*cp0i)(void); 103 size_t len; 104 105 /* we have done once so stop */ 106 if (off) { 107 len = 0; 108 goto done; 109 } 110 111 /* change the mfc0 instr with the right reg/sel */ 112 reg = (uintptr)data >> REGSHIFT; 113 sel = (uintptr)data & SELMASK; 114 cp0i = (uint (*)(void))KSEG1ADDR(tmfc0); 115 *(uint *)cp0i = 0x40010000 | (reg << 11) | sel; 116 __asm__ __volatile__("nop; nop; nop; nop;"); 117 118 /* return the value in hex string */ 119 len = sprintf(page, "0x%08x\n", cp0i()); 120 *start = page; 121done: return len; 122} 123 124static int cp0_write(struct file *file, const char *buf, 125 unsigned long count, void *data) 126{ 127 uint reg, sel, val; 128 void (*cp0i)(uint); 129 130 /* change the mtc0 instr with the right reg/sel */ 131 reg = (uintptr)data >> REGSHIFT; 132 sel = (uintptr)data & SELMASK; 133 val = simple_strtoul(buf, NULL, 0); 134 cp0i = (void (*)(uint))KSEG1ADDR(tmtc0); 135 *((uint *)cp0i + 1) = 0x40810000 | (reg << 11) | sel; 136 __asm__ __volatile__("nop; nop; nop; nop;"); 137 138 /* set the value and we are all done */ 139 cp0i(val); 140 return count; 141} 142 143#ifdef CONFIG_HND_BMIPS3300_PROF 144/* 145 * Enable/disable cache hits/misses countings - counters are 146 * hard wired as: 147 * d$ - cntr 0 and 1 148 * i$ - cntr 2 and 3 149 * They can't be enabled at the same time according to the BMIPS 150 * 3300 documentations, although they are displayed togather. 151 */ 152static void bmips3300_dccntenab(bool enable) 153{ 154 if (enable) { 155 MTC0(C0_PERFORMANCE, 6, 0x80000211); /* enable D$ counting */ 156 MTC0(C0_PERFORMANCE, 4, 0x80248028); /* enable cntr 0 and 1 */ 157 MTC0(C0_PERFORMANCE, 0, 0); /* zero cntr 0 - # hits */ 158 MTC0(C0_PERFORMANCE, 1, 0); /* zero cntr 1 - # misses */ 159 printk("enabled performance counter 0 for D$ hits\n"); 160 printk("enabled performance counter 1 for D$ misses\n"); 161 } 162 else { 163 MTC0(C0_PERFORMANCE, 4, 0); /* disable cntr 0 and 1 */ 164 MTC0(C0_PERFORMANCE, 6, 0); /* disable D$ counting */ 165 printk("disabled performance counters\n"); 166 } 167} 168 169static void bmips3300_iccntenab(bool enable) 170{ 171 if (enable) { 172 MTC0(C0_PERFORMANCE, 6, 0x80000218); /* enable I$ counting */ 173 MTC0(C0_PERFORMANCE, 5, 0x80148018); /* enable cntr 2 and 3 */ 174 MTC0(C0_PERFORMANCE, 2, 0); /* zero cntr 0 - # hits */ 175 MTC0(C0_PERFORMANCE, 3, 0); /* zero cntr 1 - # misses */ 176 printk("enabled performance counter 2 for I$ hits\n"); 177 printk("enabled performance counter 3 for I$ misses\n"); 178 } 179 else { 180 MTC0(C0_PERFORMANCE, 5, 0); /* disable cntr 2 and 3 */ 181 MTC0(C0_PERFORMANCE, 6, 0); /* disable I$ counting */ 182 printk("disabled performance counters\n"); 183 } 184} 185 186/* cache counting enable/disable proc entry callback */ 187#define DCACHE_CNTENAB 0 188#define ICACHE_CNTENAB 1 189 190static int bmips3300_ccntenab(struct file *file, const char *buf, 191 unsigned long count, void *data) 192{ 193 uint val = simple_strtoul(buf, NULL, 0); 194 switch ((uint)data) { 195 case DCACHE_CNTENAB: 196 bmips3300_dccntenab(val != 0); 197 break; 198 case ICACHE_CNTENAB: 199 bmips3300_iccntenab(val != 0); 200 break; 201 } 202 return count; 203} 204#endif /* CONFIG_HND_BMIPS3300_PROF */ 205 206/* cp0 registers/selects map */ 207#define MAXREGS 32 /* max # registers */ 208#define MAXSELS 32 /* max # selects */ 209#define CP0REG(r) (1<<(r)) 210#define CP0SEL(s) (1<<(s)) 211typedef struct 212{ 213 uint32 reg_map; /* registers map */ 214 uint8 sel_map[MAXSELS]; /* selects map */ 215} cp0_reg_map_t; 216 217#ifdef CONFIG_HND_BMIPS3300_PROF 218static cp0_reg_map_t bmips3300_cp0regmap = 219{ 220 CP0REG(0)|CP0REG(1)|CP0REG(2)|CP0REG(3)|CP0REG(4)|CP0REG(5)| 221 CP0REG(6)|CP0REG(7)|CP0REG(8)|CP0REG(9)|CP0REG(10)| 222 CP0REG(11)|CP0REG(12)|CP0REG(13)|CP0REG(14)|CP0REG(15)| 223 CP0REG(16)|CP0REG(22)|CP0REG(23)|CP0REG(24)|CP0REG(25)| 224 CP0REG(28)|CP0REG(30)|CP0REG(31), 225 { 226 /* 0 */ CP0SEL(0), 227 /* 1 */ CP0SEL(0), 228 /* 2 */ CP0SEL(0), 229 /* 3 */ CP0SEL(0), 230 /* 4 */ CP0SEL(0), 231 /* 5 */ CP0SEL(0), 232 /* 6 */ CP0SEL(0), 233 /* 7 */ CP0SEL(0), 234 /* 8 */ CP0SEL(0), 235 /* 9 */ CP0SEL(0), 236 /* 10 */ CP0SEL(0), 237 /* 11 */ CP0SEL(0), 238 /* 12 */ CP0SEL(0), 239 /* 13 */ CP0SEL(0), 240 /* 14 */ CP0SEL(0), 241 /* 15 */ CP0SEL(0), 242 /* 16 */ CP0SEL(0)|CP0SEL(1), 243 0, 244 0, 245 0, 246 0, 247 0, 248 /* 22 */ CP0SEL(0)|CP0SEL(4)|CP0SEL(5), 249 /* 23 */ CP0SEL(0), 250 /* 24 */ CP0SEL(0), 251 /* 25 */ CP0SEL(0)|CP0SEL(1)|CP0SEL(2)|CP0SEL(3)|CP0SEL(4)| 252 CP0SEL(5)|CP0SEL(6), 253 0, 254 0, 255 /* 28 */ CP0SEL(0)|CP0SEL(1), 256 0, 257 /* 30 */ CP0SEL(0), 258 /* 31 */ CP0SEL(0), 259 } 260}; 261#endif /* CONFIG_HND_BMIPS3300_PROF */ 262 263/* create/remove proc entries - no worry about error handling;-( */ 264static int __init cp0_init(void) 265{ 266 struct proc_dir_entry *cp0_proc; 267#ifdef CONFIG_HND_BMIPS3300_PROF 268 struct proc_dir_entry *cache_proc; 269#endif /* CONFIG_HND_BMIPS3300_PROF */ 270 struct proc_dir_entry *reg_proc, *sel_proc; 271 cp0_reg_map_t *reg_map = NULL; 272 uint32 prid; 273 char name[16]; 274 int i, j; 275 276 /* create proc entry cp0 in root */ 277 cp0_proc = create_proc_entry("cp0", 0444 | S_IFDIR, &proc_root); 278 if (!cp0_proc) 279 return 0; 280 281 /* create proc entries for enabling cache hit/miss counting */ 282 prid = MFC0(C0_PRID, 0); 283 if (BCM330X(prid)) { 284#ifdef CONFIG_HND_BMIPS3300_PROF 285 /* D$ */ 286 cache_proc = create_proc_entry("dccnt", 0644, cp0_proc); 287 if (!cache_proc) 288 return 0; 289 cache_proc->write_proc = bmips3300_ccntenab; 290 cache_proc->data = (void *)DCACHE_CNTENAB; 291 /* I$ */ 292 cache_proc = create_proc_entry("iccnt", 0644, cp0_proc); 293 if (!cache_proc) 294 return 0; 295 cache_proc->write_proc = bmips3300_ccntenab; 296 cache_proc->data = (void *)ICACHE_CNTENAB; 297#endif /* CONFIG_HND_BMIPS3300_PROF */ 298 } 299 300 /* select cp0 registers/selects map table */ 301 if (BCM330X(prid)) { 302#ifdef CONFIG_HND_BMIPS3300_PROF 303 reg_map = &bmips3300_cp0regmap; 304#endif /* CONFIG_HND_BMIPS3300_PROF */ 305 } 306 if (!reg_map) 307 return 0; 308 /* create proc entry for each register select */ 309 for (i = 0; i < MAXREGS; i ++) { 310 if (!(reg_map->reg_map & (1 << i))) 311 continue; 312 sprintf(name, "%u", i); 313 reg_proc = create_proc_entry(name, 0444 | S_IFDIR, cp0_proc); 314 if (!reg_proc) 315 break; 316 for (j = 0; j < MAXSELS; j ++) { 317 if (!(reg_map->sel_map[i] & (1 << j))) 318 continue; 319 sprintf(name, "%u", j); 320 sel_proc = create_proc_entry(name, 0644, reg_proc); 321 if (!sel_proc) 322 break; 323 sel_proc->read_proc = cp0_read; 324 sel_proc->write_proc = cp0_write; 325 sel_proc->data = (void *)((i << REGSHIFT) + j); 326 } 327 } 328 return 0; 329} 330 331static void __exit cp0_cleanup(void) 332{ 333 remove_proc_entry("cp0", &proc_root); 334} 335 336/* hook it up with system at boot time */ 337module_init(cp0_init); 338module_exit(cp0_cleanup); 339 340#endif /* CONFIG_PROC_FS */ 341