/** * Copyright (C) ARM Limited 2010-2014. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include "gator.h" // gator_events_perf_pmu.c is used if perf is supported #if GATOR_NO_PERF_SUPPORT static const char *pmnc_name; /* * Per-CPU PMCR */ #define PMCR_E (1 << 0) /* Enable */ #define PMCR_P (1 << 1) /* Count reset */ #define PMCR_C (1 << 2) /* Cycle counter reset */ #define PMCR_OFL_PMN0 (1 << 8) /* Count reg 0 overflow */ #define PMCR_OFL_PMN1 (1 << 9) /* Count reg 1 overflow */ #define PMCR_OFL_CCNT (1 << 10) /* Cycle counter overflow */ #define PMN0 0 #define PMN1 1 #define CCNT 2 #define CNTMAX (CCNT+1) static int pmnc_counters = 0; static unsigned long pmnc_enabled[CNTMAX]; static unsigned long pmnc_event[CNTMAX]; static unsigned long pmnc_key[CNTMAX]; static DEFINE_PER_CPU(int[CNTMAX * 2], perfCnt); static inline void armv6_pmnc_write(u32 val) { /* upper 4bits and 7, 11 are write-as-0 */ val &= 0x0ffff77f; asm volatile("mcr p15, 0, %0, c15, c12, 0" : : "r" (val)); } static inline u32 armv6_pmnc_read(void) { u32 val; asm volatile("mrc p15, 0, %0, c15, c12, 0" : "=r" (val)); return val; } static void armv6_pmnc_reset_counter(unsigned int cnt) { u32 val = 0; switch (cnt) { case CCNT: asm volatile("mcr p15, 0, %0, c15, c12, 1" : : "r" (val)); break; case PMN0: asm volatile("mcr p15, 0, %0, c15, c12, 2" : : "r" (val)); break; case PMN1: asm volatile("mcr p15, 0, %0, c15, c12, 3" : : "r" (val)); break; } } int gator_events_armv6_create_files(struct super_block *sb, struct dentry *root) { struct dentry *dir; int i; pmnc_counters = 3; for (i = PMN0; i <= CCNT; i++) { char buf[40]; if (i == CCNT) { snprintf(buf, sizeof buf, "ARM_%s_ccnt", pmnc_name); } else { snprintf(buf, sizeof buf, "ARM_%s_cnt%d", pmnc_name, i); } dir = gatorfs_mkdir(sb, root, buf); if (!dir) { return -1; } gatorfs_create_ulong(sb, dir, "enabled", &pmnc_enabled[i]); gatorfs_create_ro_ulong(sb, dir, "key", &pmnc_key[i]); if (i != CCNT) { gatorfs_create_ulong(sb, dir, "event", &pmnc_event[i]); } } return 0; } static int gator_events_armv6_online(int **buffer, bool migrate) { unsigned int cnt, len = 0, cpu = smp_processor_id(); u32 pmnc; if (armv6_pmnc_read() & PMCR_E) { armv6_pmnc_write(armv6_pmnc_read() & ~PMCR_E); } /* initialize PMNC, reset overflow, D bit, C bit and P bit. */ armv6_pmnc_write(PMCR_OFL_PMN0 | PMCR_OFL_PMN1 | PMCR_OFL_CCNT | PMCR_C | PMCR_P); /* configure control register */ for (pmnc = 0, cnt = PMN0; cnt <= CCNT; cnt++) { unsigned long event; if (!pmnc_enabled[cnt]) continue; event = pmnc_event[cnt] & 255; // Set event (if destined for PMNx counters) if (cnt == PMN0) { pmnc |= event << 20; } else if (cnt == PMN1) { pmnc |= event << 12; } // Reset counter armv6_pmnc_reset_counter(cnt); } armv6_pmnc_write(pmnc | PMCR_E); // return zero values, no need to read as the counters were just reset for (cnt = PMN0; cnt <= CCNT; cnt++) { if (pmnc_enabled[cnt]) { per_cpu(perfCnt, cpu)[len++] = pmnc_key[cnt]; per_cpu(perfCnt, cpu)[len++] = 0; } } if (buffer) *buffer = per_cpu(perfCnt, cpu); return len; } static int gator_events_armv6_offline(int **buffer, bool migrate) { unsigned int cnt; armv6_pmnc_write(armv6_pmnc_read() & ~PMCR_E); for (cnt = PMN0; cnt <= CCNT; cnt++) { armv6_pmnc_reset_counter(cnt); } return 0; } static void gator_events_armv6_stop(void) { unsigned int cnt; for (cnt = PMN0; cnt <= CCNT; cnt++) { pmnc_enabled[cnt] = 0; pmnc_event[cnt] = 0; } } static int gator_events_armv6_read(int **buffer) { int cnt, len = 0; int cpu = smp_processor_id(); // a context switch may occur before the online hotplug event, thus need to check that the pmu is enabled if (!(armv6_pmnc_read() & PMCR_E)) { return 0; } for (cnt = PMN0; cnt <= CCNT; cnt++) { if (pmnc_enabled[cnt]) { u32 value = 0; switch (cnt) { case CCNT: asm volatile("mrc p15, 0, %0, c15, c12, 1" : "=r" (value)); break; case PMN0: asm volatile("mrc p15, 0, %0, c15, c12, 2" : "=r" (value)); break; case PMN1: asm volatile("mrc p15, 0, %0, c15, c12, 3" : "=r" (value)); break; } armv6_pmnc_reset_counter(cnt); per_cpu(perfCnt, cpu)[len++] = pmnc_key[cnt]; per_cpu(perfCnt, cpu)[len++] = value; } } if (buffer) *buffer = per_cpu(perfCnt, cpu); return len; } static struct gator_interface gator_events_armv6_interface = { .create_files = gator_events_armv6_create_files, .stop = gator_events_armv6_stop, .online = gator_events_armv6_online, .offline = gator_events_armv6_offline, .read = gator_events_armv6_read, }; int gator_events_armv6_init(void) { unsigned int cnt; switch (gator_cpuid()) { case ARM1136: case ARM1156: case ARM1176: pmnc_name = "ARM11"; break; case ARM11MPCORE: pmnc_name = "ARM11MPCore"; break; default: return -1; } for (cnt = PMN0; cnt <= CCNT; cnt++) { pmnc_enabled[cnt] = 0; pmnc_event[cnt] = 0; pmnc_key[cnt] = gator_events_get_key(); } return gator_events_install(&gator_events_armv6_interface); } #endif