diff options
Diffstat (limited to 'arch/tile/kernel/perf_event.c')
-rw-r--r-- | arch/tile/kernel/perf_event.c | 1005 |
1 files changed, 0 insertions, 1005 deletions
diff --git a/arch/tile/kernel/perf_event.c b/arch/tile/kernel/perf_event.c deleted file mode 100644 index 6394c1ccb68e..000000000000 --- a/arch/tile/kernel/perf_event.c +++ /dev/null @@ -1,1005 +0,0 @@ -/* - * 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); -} |