| /* | |
| * Copyright 2014 Tilera Corporation. 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 | |
| * as published by the Free Software Foundation, version 2. | |
| * | |
| * This program is distributed in the hope that it will be useful, but | |
| * WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or | |
| * NON INFRINGEMENT. See the GNU General Public License for | |
| * more details. | |
| * | |
| * | |
| * Perf_events support for Tile processor. | |
| * | |
| * This code is based upon the x86 perf event | |
| * code, which is: | |
| * | |
| * Copyright (C) 2008 Thomas Gleixner <tglx@linutronix.de> | |
| * Copyright (C) 2008-2009 Red Hat, Inc., Ingo Molnar | |
| * Copyright (C) 2009 Jaswinder Singh Rajput | |
| * Copyright (C) 2009 Advanced Micro Devices, Inc., Robert Richter | |
| * Copyright (C) 2008-2009 Red Hat, Inc., Peter Zijlstra | |
| * Copyright (C) 2009 Intel Corporation, <markus.t.metzger@intel.com> | |
| * Copyright (C) 2009 Google, Inc., Stephane Eranian | |
| */ | |
| #include <linux/kprobes.h> | |
| #include <linux/kernel.h> | |
| #include <linux/kdebug.h> | |
| #include <linux/mutex.h> | |
| #include <linux/bitmap.h> | |
| #include <linux/irq.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/perf_event.h> | |
| #include <linux/atomic.h> | |
| #include <asm/traps.h> | |
| #include <asm/stack.h> | |
| #include <asm/pmc.h> | |
| #include <hv/hypervisor.h> | |
| #define TILE_MAX_COUNTERS 4 | |
| #define PERF_COUNT_0_IDX 0 | |
| #define PERF_COUNT_1_IDX 1 | |
| #define AUX_PERF_COUNT_0_IDX 2 | |
| #define AUX_PERF_COUNT_1_IDX 3 | |
| struct cpu_hw_events { | |
| int n_events; | |
| struct perf_event *events[TILE_MAX_COUNTERS]; /* counter order */ | |
| struct perf_event *event_list[TILE_MAX_COUNTERS]; /* enabled | |
| order */ | |
| int assign[TILE_MAX_COUNTERS]; | |
| unsigned long active_mask[BITS_TO_LONGS(TILE_MAX_COUNTERS)]; | |
| unsigned long used_mask; | |
| }; | |
| /* TILE arch specific performance monitor unit */ | |
| struct tile_pmu { | |
| const char *name; | |
| int version; | |
| const int *hw_events; /* generic hw events table */ | |
| /* generic hw cache events table */ | |
| const int (*cache_events)[PERF_COUNT_HW_CACHE_MAX] | |
| [PERF_COUNT_HW_CACHE_OP_MAX] | |
| [PERF_COUNT_HW_CACHE_RESULT_MAX]; | |
| int (*map_hw_event)(u64); /*method used to map | |
| hw events */ | |
| int (*map_cache_event)(u64); /*method used to map | |
| cache events */ | |
| u64 max_period; /* max sampling period */ | |
| u64 cntval_mask; /* counter width mask */ | |
| int cntval_bits; /* counter width */ | |
| int max_events; /* max generic hw events | |
| in map */ | |
| int num_counters; /* number base + aux counters */ | |
| int num_base_counters; /* number base counters */ | |
| }; | |
| DEFINE_PER_CPU(u64, perf_irqs); | |
| static DEFINE_PER_CPU(struct cpu_hw_events, cpu_hw_events); | |
| #define TILE_OP_UNSUPP (-1) | |
| #ifndef __tilegx__ | |
| /* TILEPro hardware events map */ | |
| static const int tile_hw_event_map[] = { | |
| [PERF_COUNT_HW_CPU_CYCLES] = 0x01, /* ONE */ | |
| [PERF_COUNT_HW_INSTRUCTIONS] = 0x06, /* MP_BUNDLE_RETIRED */ | |
| [PERF_COUNT_HW_CACHE_REFERENCES] = TILE_OP_UNSUPP, | |
| [PERF_COUNT_HW_CACHE_MISSES] = TILE_OP_UNSUPP, | |
| [PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = 0x16, /* | |
| MP_CONDITIONAL_BRANCH_ISSUED */ | |
| [PERF_COUNT_HW_BRANCH_MISSES] = 0x14, /* | |
| MP_CONDITIONAL_BRANCH_MISSPREDICT */ | |
| [PERF_COUNT_HW_BUS_CYCLES] = TILE_OP_UNSUPP, | |
| }; | |
| #else | |
| /* TILEGx hardware events map */ | |
| static const int tile_hw_event_map[] = { | |
| [PERF_COUNT_HW_CPU_CYCLES] = 0x181, /* ONE */ | |
| [PERF_COUNT_HW_INSTRUCTIONS] = 0xdb, /* INSTRUCTION_BUNDLE */ | |
| [PERF_COUNT_HW_CACHE_REFERENCES] = TILE_OP_UNSUPP, | |
| [PERF_COUNT_HW_CACHE_MISSES] = TILE_OP_UNSUPP, | |
| [PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = 0xd9, /* | |
| COND_BRANCH_PRED_CORRECT */ | |
| [PERF_COUNT_HW_BRANCH_MISSES] = 0xda, /* | |
| COND_BRANCH_PRED_INCORRECT */ | |
| [PERF_COUNT_HW_BUS_CYCLES] = TILE_OP_UNSUPP, | |
| }; | |
| #endif | |
| #define C(x) PERF_COUNT_HW_CACHE_##x | |
| /* | |
| * Generalized hw caching related hw_event table, filled | |
| * in on a per model basis. A value of -1 means | |
| * 'not supported', any other value means the | |
| * raw hw_event ID. | |
| */ | |
| #ifndef __tilegx__ | |
| /* TILEPro hardware cache event map */ | |
| static const int tile_cache_event_map[PERF_COUNT_HW_CACHE_MAX] | |
| [PERF_COUNT_HW_CACHE_OP_MAX] | |
| [PERF_COUNT_HW_CACHE_RESULT_MAX] = { | |
| [C(L1D)] = { | |
| [C(OP_READ)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = 0x21, /* RD_MISS */ | |
| }, | |
| [C(OP_WRITE)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = 0x22, /* WR_MISS */ | |
| }, | |
| [C(OP_PREFETCH)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| }, | |
| [C(L1I)] = { | |
| [C(OP_READ)] = { | |
| [C(RESULT_ACCESS)] = 0x12, /* MP_ICACHE_HIT_ISSUED */ | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_WRITE)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_PREFETCH)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| }, | |
| [C(LL)] = { | |
| [C(OP_READ)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_WRITE)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_PREFETCH)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| }, | |
| [C(DTLB)] = { | |
| [C(OP_READ)] = { | |
| [C(RESULT_ACCESS)] = 0x1d, /* TLB_CNT */ | |
| [C(RESULT_MISS)] = 0x20, /* TLB_EXCEPTION */ | |
| }, | |
| [C(OP_WRITE)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_PREFETCH)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| }, | |
| [C(ITLB)] = { | |
| [C(OP_READ)] = { | |
| [C(RESULT_ACCESS)] = 0x13, /* MP_ITLB_HIT_ISSUED */ | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_WRITE)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_PREFETCH)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| }, | |
| [C(BPU)] = { | |
| [C(OP_READ)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_WRITE)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_PREFETCH)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| }, | |
| }; | |
| #else | |
| /* TILEGx hardware events map */ | |
| static const int tile_cache_event_map[PERF_COUNT_HW_CACHE_MAX] | |
| [PERF_COUNT_HW_CACHE_OP_MAX] | |
| [PERF_COUNT_HW_CACHE_RESULT_MAX] = { | |
| [C(L1D)] = { | |
| /* | |
| * Like some other architectures (e.g. ARM), the performance | |
| * counters don't differentiate between read and write | |
| * accesses/misses, so this isn't strictly correct, but it's the | |
| * best we can do. Writes and reads get combined. | |
| */ | |
| [C(OP_READ)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = 0x44, /* RD_MISS */ | |
| }, | |
| [C(OP_WRITE)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = 0x45, /* WR_MISS */ | |
| }, | |
| [C(OP_PREFETCH)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| }, | |
| [C(L1I)] = { | |
| [C(OP_READ)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_WRITE)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_PREFETCH)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| }, | |
| [C(LL)] = { | |
| [C(OP_READ)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_WRITE)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_PREFETCH)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| }, | |
| [C(DTLB)] = { | |
| [C(OP_READ)] = { | |
| [C(RESULT_ACCESS)] = 0x40, /* TLB_CNT */ | |
| [C(RESULT_MISS)] = 0x43, /* TLB_EXCEPTION */ | |
| }, | |
| [C(OP_WRITE)] = { | |
| [C(RESULT_ACCESS)] = 0x40, /* TLB_CNT */ | |
| [C(RESULT_MISS)] = 0x43, /* TLB_EXCEPTION */ | |
| }, | |
| [C(OP_PREFETCH)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| }, | |
| [C(ITLB)] = { | |
| [C(OP_READ)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = 0xd4, /* ITLB_MISS_INT */ | |
| }, | |
| [C(OP_WRITE)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = 0xd4, /* ITLB_MISS_INT */ | |
| }, | |
| [C(OP_PREFETCH)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| }, | |
| [C(BPU)] = { | |
| [C(OP_READ)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_WRITE)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| [C(OP_PREFETCH)] = { | |
| [C(RESULT_ACCESS)] = TILE_OP_UNSUPP, | |
| [C(RESULT_MISS)] = TILE_OP_UNSUPP, | |
| }, | |
| }, | |
| }; | |
| #endif | |
| static atomic_t tile_active_events; | |
| static DEFINE_MUTEX(perf_intr_reserve_mutex); | |
| static int tile_map_hw_event(u64 config); | |
| static int tile_map_cache_event(u64 config); | |
| static int tile_pmu_handle_irq(struct pt_regs *regs, int fault); | |
| /* | |
| * To avoid new_raw_count getting larger then pre_raw_count | |
| * in tile_perf_event_update(), we limit the value of max_period to 2^31 - 1. | |
| */ | |
| static const struct tile_pmu tilepmu = { | |
| #ifndef __tilegx__ | |
| .name = "tilepro", | |
| #else | |
| .name = "tilegx", | |
| #endif | |
| .max_events = ARRAY_SIZE(tile_hw_event_map), | |
| .map_hw_event = tile_map_hw_event, | |
| .hw_events = tile_hw_event_map, | |
| .map_cache_event = tile_map_cache_event, | |
| .cache_events = &tile_cache_event_map, | |
| .cntval_bits = 32, | |
| .cntval_mask = (1ULL << 32) - 1, | |
| .max_period = (1ULL << 31) - 1, | |
| .num_counters = TILE_MAX_COUNTERS, | |
| .num_base_counters = TILE_BASE_COUNTERS, | |
| }; | |
| static const struct tile_pmu *tile_pmu __read_mostly; | |
| /* | |
| * Check whether perf event is enabled. | |
| */ | |
| int tile_perf_enabled(void) | |
| { | |
| return atomic_read(&tile_active_events) != 0; | |
| } | |
| /* | |
| * Read Performance Counters. | |
| */ | |
| static inline u64 read_counter(int idx) | |
| { | |
| u64 val = 0; | |
| /* __insn_mfspr() only takes an immediate argument */ | |
| switch (idx) { | |
| case PERF_COUNT_0_IDX: | |
| val = __insn_mfspr(SPR_PERF_COUNT_0); | |
| break; | |
| case PERF_COUNT_1_IDX: | |
| val = __insn_mfspr(SPR_PERF_COUNT_1); | |
| break; | |
| case AUX_PERF_COUNT_0_IDX: | |
| val = __insn_mfspr(SPR_AUX_PERF_COUNT_0); | |
| break; | |
| case AUX_PERF_COUNT_1_IDX: | |
| val = __insn_mfspr(SPR_AUX_PERF_COUNT_1); | |
| break; | |
| default: | |
| WARN_ON_ONCE(idx > AUX_PERF_COUNT_1_IDX || | |
| idx < PERF_COUNT_0_IDX); | |
| } | |
| return val; | |
| } | |
| /* | |
| * Write Performance Counters. | |
| */ | |
| static inline void write_counter(int idx, u64 value) | |
| { | |
| /* __insn_mtspr() only takes an immediate argument */ | |
| switch (idx) { | |
| case PERF_COUNT_0_IDX: | |
| __insn_mtspr(SPR_PERF_COUNT_0, value); | |
| break; | |
| case PERF_COUNT_1_IDX: | |
| __insn_mtspr(SPR_PERF_COUNT_1, value); | |
| break; | |
| case AUX_PERF_COUNT_0_IDX: | |
| __insn_mtspr(SPR_AUX_PERF_COUNT_0, value); | |
| break; | |
| case AUX_PERF_COUNT_1_IDX: | |
| __insn_mtspr(SPR_AUX_PERF_COUNT_1, value); | |
| break; | |
| default: | |
| WARN_ON_ONCE(idx > AUX_PERF_COUNT_1_IDX || | |
| idx < PERF_COUNT_0_IDX); | |
| } | |
| } | |
| /* | |
| * Enable performance event by setting | |
| * Performance Counter Control registers. | |
| */ | |
| static inline void tile_pmu_enable_event(struct perf_event *event) | |
| { | |
| struct hw_perf_event *hwc = &event->hw; | |
| unsigned long cfg, mask; | |
| int shift, idx = hwc->idx; | |
| /* | |
| * prevent early activation from tile_pmu_start() in hw_perf_enable | |
| */ | |
| if (WARN_ON_ONCE(idx == -1)) | |
| return; | |
| if (idx < tile_pmu->num_base_counters) | |
| cfg = __insn_mfspr(SPR_PERF_COUNT_CTL); | |
| else | |
| cfg = __insn_mfspr(SPR_AUX_PERF_COUNT_CTL); | |
| switch (idx) { | |
| case PERF_COUNT_0_IDX: | |
| case AUX_PERF_COUNT_0_IDX: | |
| mask = TILE_EVENT_MASK; | |
| shift = 0; | |
| break; | |
| case PERF_COUNT_1_IDX: | |
| case AUX_PERF_COUNT_1_IDX: | |
| mask = TILE_EVENT_MASK << 16; | |
| shift = 16; | |
| break; | |
| default: | |
| WARN_ON_ONCE(idx < PERF_COUNT_0_IDX || | |
| idx > AUX_PERF_COUNT_1_IDX); | |
| return; | |
| } | |
| /* Clear mask bits to enable the event. */ | |
| cfg &= ~mask; | |
| cfg |= hwc->config << shift; | |
| if (idx < tile_pmu->num_base_counters) | |
| __insn_mtspr(SPR_PERF_COUNT_CTL, cfg); | |
| else | |
| __insn_mtspr(SPR_AUX_PERF_COUNT_CTL, cfg); | |
| } | |
| /* | |
| * Disable performance event by clearing | |
| * Performance Counter Control registers. | |
| */ | |
| static inline void tile_pmu_disable_event(struct perf_event *event) | |
| { | |
| struct hw_perf_event *hwc = &event->hw; | |
| unsigned long cfg, mask; | |
| int idx = hwc->idx; | |
| if (idx == -1) | |
| return; | |
| if (idx < tile_pmu->num_base_counters) | |
| cfg = __insn_mfspr(SPR_PERF_COUNT_CTL); | |
| else | |
| cfg = __insn_mfspr(SPR_AUX_PERF_COUNT_CTL); | |
| switch (idx) { | |
| case PERF_COUNT_0_IDX: | |
| case AUX_PERF_COUNT_0_IDX: | |
| mask = TILE_PLM_MASK; | |
| break; | |
| case PERF_COUNT_1_IDX: | |
| case AUX_PERF_COUNT_1_IDX: | |
| mask = TILE_PLM_MASK << 16; | |
| break; | |
| default: | |
| WARN_ON_ONCE(idx < PERF_COUNT_0_IDX || | |
| idx > AUX_PERF_COUNT_1_IDX); | |
| return; | |
| } | |
| /* Set mask bits to disable the event. */ | |
| cfg |= mask; | |
| if (idx < tile_pmu->num_base_counters) | |
| __insn_mtspr(SPR_PERF_COUNT_CTL, cfg); | |
| else | |
| __insn_mtspr(SPR_AUX_PERF_COUNT_CTL, cfg); | |
| } | |
| /* | |
| * Propagate event elapsed time into the generic event. | |
| * Can only be executed on the CPU where the event is active. | |
| * Returns the delta events processed. | |
| */ | |
| static u64 tile_perf_event_update(struct perf_event *event) | |
| { | |
| struct hw_perf_event *hwc = &event->hw; | |
| int shift = 64 - tile_pmu->cntval_bits; | |
| u64 prev_raw_count, new_raw_count; | |
| u64 oldval; | |
| int idx = hwc->idx; | |
| u64 delta; | |
| /* | |
| * Careful: an NMI might modify the previous event value. | |
| * | |
| * Our tactic to handle this is to first atomically read and | |
| * exchange a new raw count - then add that new-prev delta | |
| * count to the generic event atomically: | |
| */ | |
| again: | |
| prev_raw_count = local64_read(&hwc->prev_count); | |
| new_raw_count = read_counter(idx); | |
| oldval = local64_cmpxchg(&hwc->prev_count, prev_raw_count, | |
| new_raw_count); | |
| if (oldval != prev_raw_count) | |
| goto again; | |
| /* | |
| * Now we have the new raw value and have updated the prev | |
| * timestamp already. We can now calculate the elapsed delta | |
| * (event-)time and add that to the generic event. | |
| * | |
| * Careful, not all hw sign-extends above the physical width | |
| * of the count. | |
| */ | |
| delta = (new_raw_count << shift) - (prev_raw_count << shift); | |
| delta >>= shift; | |
| local64_add(delta, &event->count); | |
| local64_sub(delta, &hwc->period_left); | |
| return new_raw_count; | |
| } | |
| /* | |
| * Set the next IRQ period, based on the hwc->period_left value. | |
| * To be called with the event disabled in hw: | |
| */ | |
| static int tile_event_set_period(struct perf_event *event) | |
| { | |
| struct hw_perf_event *hwc = &event->hw; | |
| int idx = hwc->idx; | |
| s64 left = local64_read(&hwc->period_left); | |
| s64 period = hwc->sample_period; | |
| int ret = 0; | |
| /* | |
| * If we are way outside a reasonable range then just skip forward: | |
| */ | |
| if (unlikely(left <= -period)) { | |
| left = period; | |
| local64_set(&hwc->period_left, left); | |
| hwc->last_period = period; | |
| ret = 1; | |
| } | |
| if (unlikely(left <= 0)) { | |
| left += period; | |
| local64_set(&hwc->period_left, left); | |
| hwc->last_period = period; | |
| ret = 1; | |
| } | |
| if (left > tile_pmu->max_period) | |
| left = tile_pmu->max_period; | |
| /* | |
| * The hw event starts counting from this event offset, | |
| * mark it to be able to extra future deltas: | |
| */ | |
| local64_set(&hwc->prev_count, (u64)-left); | |
| write_counter(idx, (u64)(-left) & tile_pmu->cntval_mask); | |
| perf_event_update_userpage(event); | |
| return ret; | |
| } | |
| /* | |
| * Stop the event but do not release the PMU counter | |
| */ | |
| static void tile_pmu_stop(struct perf_event *event, int flags) | |
| { | |
| struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); | |
| struct hw_perf_event *hwc = &event->hw; | |
| int idx = hwc->idx; | |
| if (__test_and_clear_bit(idx, cpuc->active_mask)) { | |
| tile_pmu_disable_event(event); | |
| cpuc->events[hwc->idx] = NULL; | |
| WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED); | |
| hwc->state |= PERF_HES_STOPPED; | |
| } | |
| if ((flags & PERF_EF_UPDATE) && !(hwc->state & PERF_HES_UPTODATE)) { | |
| /* | |
| * Drain the remaining delta count out of a event | |
| * that we are disabling: | |
| */ | |
| tile_perf_event_update(event); | |
| hwc->state |= PERF_HES_UPTODATE; | |
| } | |
| } | |
| /* | |
| * Start an event (without re-assigning counter) | |
| */ | |
| static void tile_pmu_start(struct perf_event *event, int flags) | |
| { | |
| struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); | |
| int idx = event->hw.idx; | |
| if (WARN_ON_ONCE(!(event->hw.state & PERF_HES_STOPPED))) | |
| return; | |
| if (WARN_ON_ONCE(idx == -1)) | |
| return; | |
| if (flags & PERF_EF_RELOAD) { | |
| WARN_ON_ONCE(!(event->hw.state & PERF_HES_UPTODATE)); | |
| tile_event_set_period(event); | |
| } | |
| event->hw.state = 0; | |
| cpuc->events[idx] = event; | |
| __set_bit(idx, cpuc->active_mask); | |
| unmask_pmc_interrupts(); | |
| tile_pmu_enable_event(event); | |
| perf_event_update_userpage(event); | |
| } | |
| /* | |
| * Add a single event to the PMU. | |
| * | |
| * The event is added to the group of enabled events | |
| * but only if it can be scehduled with existing events. | |
| */ | |
| static int tile_pmu_add(struct perf_event *event, int flags) | |
| { | |
| struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); | |
| struct hw_perf_event *hwc; | |
| unsigned long mask; | |
| int b, max_cnt; | |
| hwc = &event->hw; | |
| /* | |
| * We are full. | |
| */ | |
| if (cpuc->n_events == tile_pmu->num_counters) | |
| return -ENOSPC; | |
| cpuc->event_list[cpuc->n_events] = event; | |
| cpuc->n_events++; | |
| hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED; | |
| if (!(flags & PERF_EF_START)) | |
| hwc->state |= PERF_HES_ARCH; | |
| /* | |
| * Find first empty counter. | |
| */ | |
| max_cnt = tile_pmu->num_counters; | |
| mask = ~cpuc->used_mask; | |
| /* Find next free counter. */ | |
| b = find_next_bit(&mask, max_cnt, 0); | |
| /* Should not happen. */ | |
| if (WARN_ON_ONCE(b == max_cnt)) | |
| return -ENOSPC; | |
| /* | |
| * Assign counter to event. | |
| */ | |
| event->hw.idx = b; | |
| __set_bit(b, &cpuc->used_mask); | |
| /* | |
| * Start if requested. | |
| */ | |
| if (flags & PERF_EF_START) | |
| tile_pmu_start(event, PERF_EF_RELOAD); | |
| return 0; | |
| } | |
| /* | |
| * Delete a single event from the PMU. | |
| * | |
| * The event is deleted from the group of enabled events. | |
| * If it is the last event, disable PMU interrupt. | |
| */ | |
| static void tile_pmu_del(struct perf_event *event, int flags) | |
| { | |
| struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); | |
| int i; | |
| /* | |
| * Remove event from list, compact list if necessary. | |
| */ | |
| for (i = 0; i < cpuc->n_events; i++) { | |
| if (cpuc->event_list[i] == event) { | |
| while (++i < cpuc->n_events) | |
| cpuc->event_list[i-1] = cpuc->event_list[i]; | |
| --cpuc->n_events; | |
| cpuc->events[event->hw.idx] = NULL; | |
| __clear_bit(event->hw.idx, &cpuc->used_mask); | |
| tile_pmu_stop(event, PERF_EF_UPDATE); | |
| break; | |
| } | |
| } | |
| /* | |
| * If there are no events left, then mask PMU interrupt. | |
| */ | |
| if (cpuc->n_events == 0) | |
| mask_pmc_interrupts(); | |
| perf_event_update_userpage(event); | |
| } | |
| /* | |
| * Propagate event elapsed time into the event. | |
| */ | |
| static inline void tile_pmu_read(struct perf_event *event) | |
| { | |
| tile_perf_event_update(event); | |
| } | |
| /* | |
| * Map generic events to Tile PMU. | |
| */ | |
| static int tile_map_hw_event(u64 config) | |
| { | |
| if (config >= tile_pmu->max_events) | |
| return -EINVAL; | |
| return tile_pmu->hw_events[config]; | |
| } | |
| /* | |
| * Map generic hardware cache events to Tile PMU. | |
| */ | |
| static int tile_map_cache_event(u64 config) | |
| { | |
| unsigned int cache_type, cache_op, cache_result; | |
| int code; | |
| if (!tile_pmu->cache_events) | |
| return -ENOENT; | |
| cache_type = (config >> 0) & 0xff; | |
| if (cache_type >= PERF_COUNT_HW_CACHE_MAX) | |
| return -EINVAL; | |
| cache_op = (config >> 8) & 0xff; | |
| if (cache_op >= PERF_COUNT_HW_CACHE_OP_MAX) | |
| return -EINVAL; | |
| cache_result = (config >> 16) & 0xff; | |
| if (cache_result >= PERF_COUNT_HW_CACHE_RESULT_MAX) | |
| return -EINVAL; | |
| code = (*tile_pmu->cache_events)[cache_type][cache_op][cache_result]; | |
| if (code == TILE_OP_UNSUPP) | |
| return -EINVAL; | |
| return code; | |
| } | |
| static void tile_event_destroy(struct perf_event *event) | |
| { | |
| if (atomic_dec_return(&tile_active_events) == 0) | |
| release_pmc_hardware(); | |
| } | |
| static int __tile_event_init(struct perf_event *event) | |
| { | |
| struct perf_event_attr *attr = &event->attr; | |
| struct hw_perf_event *hwc = &event->hw; | |
| int code; | |
| switch (attr->type) { | |
| case PERF_TYPE_HARDWARE: | |
| code = tile_pmu->map_hw_event(attr->config); | |
| break; | |
| case PERF_TYPE_HW_CACHE: | |
| code = tile_pmu->map_cache_event(attr->config); | |
| break; | |
| case PERF_TYPE_RAW: | |
| code = attr->config & TILE_EVENT_MASK; | |
| break; | |
| default: | |
| /* Should not happen. */ | |
| return -EOPNOTSUPP; | |
| } | |
| if (code < 0) | |
| return code; | |
| hwc->config = code; | |
| hwc->idx = -1; | |
| if (attr->exclude_user) | |
| hwc->config |= TILE_CTL_EXCL_USER; | |
| if (attr->exclude_kernel) | |
| hwc->config |= TILE_CTL_EXCL_KERNEL; | |
| if (attr->exclude_hv) | |
| hwc->config |= TILE_CTL_EXCL_HV; | |
| if (!hwc->sample_period) { | |
| hwc->sample_period = tile_pmu->max_period; | |
| hwc->last_period = hwc->sample_period; | |
| local64_set(&hwc->period_left, hwc->sample_period); | |
| } | |
| event->destroy = tile_event_destroy; | |
| return 0; | |
| } | |
| static int tile_event_init(struct perf_event *event) | |
| { | |
| int err = 0; | |
| perf_irq_t old_irq_handler = NULL; | |
| if (atomic_inc_return(&tile_active_events) == 1) | |
| old_irq_handler = reserve_pmc_hardware(tile_pmu_handle_irq); | |
| if (old_irq_handler) { | |
| pr_warn("PMC hardware busy (reserved by oprofile)\n"); | |
| atomic_dec(&tile_active_events); | |
| return -EBUSY; | |
| } | |
| switch (event->attr.type) { | |
| case PERF_TYPE_RAW: | |
| case PERF_TYPE_HARDWARE: | |
| case PERF_TYPE_HW_CACHE: | |
| break; | |
| default: | |
| return -ENOENT; | |
| } | |
| err = __tile_event_init(event); | |
| if (err) { | |
| if (event->destroy) | |
| event->destroy(event); | |
| } | |
| return err; | |
| } | |
| static struct pmu tilera_pmu = { | |
| .event_init = tile_event_init, | |
| .add = tile_pmu_add, | |
| .del = tile_pmu_del, | |
| .start = tile_pmu_start, | |
| .stop = tile_pmu_stop, | |
| .read = tile_pmu_read, | |
| }; | |
| /* | |
| * PMU's IRQ handler, PMU has 2 interrupts, they share the same handler. | |
| */ | |
| int tile_pmu_handle_irq(struct pt_regs *regs, int fault) | |
| { | |
| struct perf_sample_data data; | |
| struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); | |
| struct perf_event *event; | |
| struct hw_perf_event *hwc; | |
| u64 val; | |
| unsigned long status; | |
| int bit; | |
| __this_cpu_inc(perf_irqs); | |
| if (!atomic_read(&tile_active_events)) | |
| return 0; | |
| status = pmc_get_overflow(); | |
| pmc_ack_overflow(status); | |
| for_each_set_bit(bit, &status, tile_pmu->num_counters) { | |
| event = cpuc->events[bit]; | |
| if (!event) | |
| continue; | |
| if (!test_bit(bit, cpuc->active_mask)) | |
| continue; | |
| hwc = &event->hw; | |
| val = tile_perf_event_update(event); | |
| if (val & (1ULL << (tile_pmu->cntval_bits - 1))) | |
| continue; | |
| perf_sample_data_init(&data, 0, event->hw.last_period); | |
| if (!tile_event_set_period(event)) | |
| continue; | |
| if (perf_event_overflow(event, &data, regs)) | |
| tile_pmu_stop(event, 0); | |
| } | |
| return 0; | |
| } | |
| static bool __init supported_pmu(void) | |
| { | |
| tile_pmu = &tilepmu; | |
| return true; | |
| } | |
| int __init init_hw_perf_events(void) | |
| { | |
| supported_pmu(); | |
| perf_pmu_register(&tilera_pmu, "cpu", PERF_TYPE_RAW); | |
| return 0; | |
| } | |
| arch_initcall(init_hw_perf_events); | |
| /* Callchain handling code. */ | |
| /* | |
| * Tile specific backtracing code for perf_events. | |
| */ | |
| static inline void perf_callchain(struct perf_callchain_entry_ctx *entry, | |
| struct pt_regs *regs) | |
| { | |
| struct KBacktraceIterator kbt; | |
| unsigned int i; | |
| /* | |
| * Get the address just after the "jalr" instruction that | |
| * jumps to the handler for a syscall. When we find this | |
| * address in a backtrace, we silently ignore it, which gives | |
| * us a one-step backtrace connection from the sys_xxx() | |
| * function in the kernel to the xxx() function in libc. | |
| * Otherwise, we lose the ability to properly attribute time | |
| * from the libc calls to the kernel implementations, since | |
| * oprofile only considers PCs from backtraces a pair at a time. | |
| */ | |
| unsigned long handle_syscall_pc = handle_syscall_link_address(); | |
| KBacktraceIterator_init(&kbt, NULL, regs); | |
| kbt.profile = 1; | |
| /* | |
| * The sample for the pc is already recorded. Now we are adding the | |
| * address of the callsites on the stack. Our iterator starts | |
| * with the frame of the (already sampled) call site. If our | |
| * iterator contained a "return address" field, we could have just | |
| * used it and wouldn't have needed to skip the first | |
| * frame. That's in effect what the arm and x86 versions do. | |
| * Instead we peel off the first iteration to get the equivalent | |
| * behavior. | |
| */ | |
| if (KBacktraceIterator_end(&kbt)) | |
| return; | |
| KBacktraceIterator_next(&kbt); | |
| /* | |
| * Set stack depth to 16 for user and kernel space respectively, that | |
| * is, total 32 stack frames. | |
| */ | |
| for (i = 0; i < 16; ++i) { | |
| unsigned long pc; | |
| if (KBacktraceIterator_end(&kbt)) | |
| break; | |
| pc = kbt.it.pc; | |
| if (pc != handle_syscall_pc) | |
| perf_callchain_store(entry, pc); | |
| KBacktraceIterator_next(&kbt); | |
| } | |
| } | |
| void perf_callchain_user(struct perf_callchain_entry_ctx *entry, | |
| struct pt_regs *regs) | |
| { | |
| perf_callchain(entry, regs); | |
| } | |
| void perf_callchain_kernel(struct perf_callchain_entry_ctx *entry, | |
| struct pt_regs *regs) | |
| { | |
| perf_callchain(entry, regs); | |
| } |