linux/arch/powerpc/perf/8xx-pmu.c
<<
>>
Prefs
   1/*
   2 * Performance event support - PPC 8xx
   3 *
   4 * Copyright 2016 Christophe Leroy, CS Systemes d'Information
   5 *
   6 * This program is free software; you can redistribute it and/or
   7 * modify it under the terms of the GNU General Public License
   8 * as published by the Free Software Foundation; either version
   9 * 2 of the License, or (at your option) any later version.
  10 */
  11
  12#include <linux/kernel.h>
  13#include <linux/sched.h>
  14#include <linux/perf_event.h>
  15#include <linux/percpu.h>
  16#include <linux/hardirq.h>
  17#include <asm/pmc.h>
  18#include <asm/machdep.h>
  19#include <asm/firmware.h>
  20#include <asm/ptrace.h>
  21#include <asm/code-patching.h>
  22#include <asm/inst.h>
  23
  24#define PERF_8xx_ID_CPU_CYCLES          1
  25#define PERF_8xx_ID_HW_INSTRUCTIONS     2
  26#define PERF_8xx_ID_ITLB_LOAD_MISS      3
  27#define PERF_8xx_ID_DTLB_LOAD_MISS      4
  28
  29#define C(x)    PERF_COUNT_HW_CACHE_##x
  30#define DTLB_LOAD_MISS  (C(DTLB) | (C(OP_READ) << 8) | (C(RESULT_MISS) << 16))
  31#define ITLB_LOAD_MISS  (C(ITLB) | (C(OP_READ) << 8) | (C(RESULT_MISS) << 16))
  32
  33extern unsigned long itlb_miss_counter, dtlb_miss_counter;
  34extern atomic_t instruction_counter;
  35extern unsigned int itlb_miss_perf, dtlb_miss_perf;
  36extern unsigned int itlb_miss_exit_1, itlb_miss_exit_2;
  37extern unsigned int dtlb_miss_exit_1, dtlb_miss_exit_2, dtlb_miss_exit_3;
  38
  39static atomic_t insn_ctr_ref;
  40static atomic_t itlb_miss_ref;
  41static atomic_t dtlb_miss_ref;
  42
  43static s64 get_insn_ctr(void)
  44{
  45        int ctr;
  46        unsigned long counta;
  47
  48        do {
  49                ctr = atomic_read(&instruction_counter);
  50                counta = mfspr(SPRN_COUNTA);
  51        } while (ctr != atomic_read(&instruction_counter));
  52
  53        return ((s64)ctr << 16) | (counta >> 16);
  54}
  55
  56static int event_type(struct perf_event *event)
  57{
  58        switch (event->attr.type) {
  59        case PERF_TYPE_HARDWARE:
  60                if (event->attr.config == PERF_COUNT_HW_CPU_CYCLES)
  61                        return PERF_8xx_ID_CPU_CYCLES;
  62                if (event->attr.config == PERF_COUNT_HW_INSTRUCTIONS)
  63                        return PERF_8xx_ID_HW_INSTRUCTIONS;
  64                break;
  65        case PERF_TYPE_HW_CACHE:
  66                if (event->attr.config == ITLB_LOAD_MISS)
  67                        return PERF_8xx_ID_ITLB_LOAD_MISS;
  68                if (event->attr.config == DTLB_LOAD_MISS)
  69                        return PERF_8xx_ID_DTLB_LOAD_MISS;
  70                break;
  71        case PERF_TYPE_RAW:
  72                break;
  73        default:
  74                return -ENOENT;
  75        }
  76        return -EOPNOTSUPP;
  77}
  78
  79static int mpc8xx_pmu_event_init(struct perf_event *event)
  80{
  81        int type = event_type(event);
  82
  83        if (type < 0)
  84                return type;
  85        return 0;
  86}
  87
  88static int mpc8xx_pmu_add(struct perf_event *event, int flags)
  89{
  90        int type = event_type(event);
  91        s64 val = 0;
  92
  93        if (type < 0)
  94                return type;
  95
  96        switch (type) {
  97        case PERF_8xx_ID_CPU_CYCLES:
  98                val = get_tb();
  99                break;
 100        case PERF_8xx_ID_HW_INSTRUCTIONS:
 101                if (atomic_inc_return(&insn_ctr_ref) == 1)
 102                        mtspr(SPRN_ICTRL, 0xc0080007);
 103                val = get_insn_ctr();
 104                break;
 105        case PERF_8xx_ID_ITLB_LOAD_MISS:
 106                if (atomic_inc_return(&itlb_miss_ref) == 1) {
 107                        unsigned long target = (unsigned long)&itlb_miss_perf;
 108
 109                        patch_branch(&itlb_miss_exit_1, target, 0);
 110#ifndef CONFIG_PIN_TLB_TEXT
 111                        patch_branch(&itlb_miss_exit_2, target, 0);
 112#endif
 113                }
 114                val = itlb_miss_counter;
 115                break;
 116        case PERF_8xx_ID_DTLB_LOAD_MISS:
 117                if (atomic_inc_return(&dtlb_miss_ref) == 1) {
 118                        unsigned long target = (unsigned long)&dtlb_miss_perf;
 119
 120                        patch_branch(&dtlb_miss_exit_1, target, 0);
 121                        patch_branch(&dtlb_miss_exit_2, target, 0);
 122                        patch_branch(&dtlb_miss_exit_3, target, 0);
 123                }
 124                val = dtlb_miss_counter;
 125                break;
 126        }
 127        local64_set(&event->hw.prev_count, val);
 128        return 0;
 129}
 130
 131static void mpc8xx_pmu_read(struct perf_event *event)
 132{
 133        int type = event_type(event);
 134        s64 prev, val = 0, delta = 0;
 135
 136        if (type < 0)
 137                return;
 138
 139        do {
 140                prev = local64_read(&event->hw.prev_count);
 141                switch (type) {
 142                case PERF_8xx_ID_CPU_CYCLES:
 143                        val = get_tb();
 144                        delta = 16 * (val - prev);
 145                        break;
 146                case PERF_8xx_ID_HW_INSTRUCTIONS:
 147                        val = get_insn_ctr();
 148                        delta = prev - val;
 149                        if (delta < 0)
 150                                delta += 0x1000000000000LL;
 151                        break;
 152                case PERF_8xx_ID_ITLB_LOAD_MISS:
 153                        val = itlb_miss_counter;
 154                        delta = (s64)((s32)val - (s32)prev);
 155                        break;
 156                case PERF_8xx_ID_DTLB_LOAD_MISS:
 157                        val = dtlb_miss_counter;
 158                        delta = (s64)((s32)val - (s32)prev);
 159                        break;
 160                }
 161        } while (local64_cmpxchg(&event->hw.prev_count, prev, val) != prev);
 162
 163        local64_add(delta, &event->count);
 164}
 165
 166static void mpc8xx_pmu_del(struct perf_event *event, int flags)
 167{
 168        /* mfspr r10, SPRN_SPRG_SCRATCH0 */
 169        unsigned int insn = ppc_inst(PPC_INST_MFSPR | __PPC_RS(R10) |
 170                                     __PPC_SPR(SPRN_SPRG_SCRATCH0));
 171
 172        mpc8xx_pmu_read(event);
 173
 174        /* If it was the last user, stop counting to avoid useles overhead */
 175        switch (event_type(event)) {
 176        case PERF_8xx_ID_CPU_CYCLES:
 177                break;
 178        case PERF_8xx_ID_HW_INSTRUCTIONS:
 179                if (atomic_dec_return(&insn_ctr_ref) == 0)
 180                        mtspr(SPRN_ICTRL, 7);
 181                break;
 182        case PERF_8xx_ID_ITLB_LOAD_MISS:
 183                if (atomic_dec_return(&itlb_miss_ref) == 0) {
 184                        patch_instruction(&itlb_miss_exit_1, insn);
 185#ifndef CONFIG_PIN_TLB_TEXT
 186                        patch_instruction(&itlb_miss_exit_2, insn);
 187#endif
 188                }
 189                break;
 190        case PERF_8xx_ID_DTLB_LOAD_MISS:
 191                if (atomic_dec_return(&dtlb_miss_ref) == 0) {
 192                        patch_instruction(&dtlb_miss_exit_1, insn);
 193                        patch_instruction(&dtlb_miss_exit_2, insn);
 194                        patch_instruction(&dtlb_miss_exit_3, insn);
 195                }
 196                break;
 197        }
 198}
 199
 200static struct pmu mpc8xx_pmu = {
 201        .event_init     = mpc8xx_pmu_event_init,
 202        .add            = mpc8xx_pmu_add,
 203        .del            = mpc8xx_pmu_del,
 204        .read           = mpc8xx_pmu_read,
 205        .capabilities   = PERF_PMU_CAP_NO_INTERRUPT |
 206                          PERF_PMU_CAP_NO_NMI,
 207};
 208
 209static int init_mpc8xx_pmu(void)
 210{
 211        mtspr(SPRN_ICTRL, 7);
 212        mtspr(SPRN_CMPA, 0);
 213        mtspr(SPRN_COUNTA, 0xffff);
 214
 215        return perf_pmu_register(&mpc8xx_pmu, "cpu", PERF_TYPE_RAW);
 216}
 217
 218early_initcall(init_mpc8xx_pmu);
 219