18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Performance event support - PPC 8xx
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright 2016 Christophe Leroy, CS Systemes d'Information
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/kernel.h>
98c2ecf20Sopenharmony_ci#include <linux/sched.h>
108c2ecf20Sopenharmony_ci#include <linux/perf_event.h>
118c2ecf20Sopenharmony_ci#include <linux/percpu.h>
128c2ecf20Sopenharmony_ci#include <linux/hardirq.h>
138c2ecf20Sopenharmony_ci#include <asm/pmc.h>
148c2ecf20Sopenharmony_ci#include <asm/machdep.h>
158c2ecf20Sopenharmony_ci#include <asm/firmware.h>
168c2ecf20Sopenharmony_ci#include <asm/ptrace.h>
178c2ecf20Sopenharmony_ci#include <asm/code-patching.h>
188c2ecf20Sopenharmony_ci#include <asm/inst.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define PERF_8xx_ID_CPU_CYCLES		1
218c2ecf20Sopenharmony_ci#define PERF_8xx_ID_HW_INSTRUCTIONS	2
228c2ecf20Sopenharmony_ci#define PERF_8xx_ID_ITLB_LOAD_MISS	3
238c2ecf20Sopenharmony_ci#define PERF_8xx_ID_DTLB_LOAD_MISS	4
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define C(x)	PERF_COUNT_HW_CACHE_##x
268c2ecf20Sopenharmony_ci#define DTLB_LOAD_MISS	(C(DTLB) | (C(OP_READ) << 8) | (C(RESULT_MISS) << 16))
278c2ecf20Sopenharmony_ci#define ITLB_LOAD_MISS	(C(ITLB) | (C(OP_READ) << 8) | (C(RESULT_MISS) << 16))
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ciextern unsigned long itlb_miss_counter, dtlb_miss_counter;
308c2ecf20Sopenharmony_ciextern atomic_t instruction_counter;
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistatic atomic_t insn_ctr_ref;
338c2ecf20Sopenharmony_cistatic atomic_t itlb_miss_ref;
348c2ecf20Sopenharmony_cistatic atomic_t dtlb_miss_ref;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic s64 get_insn_ctr(void)
378c2ecf20Sopenharmony_ci{
388c2ecf20Sopenharmony_ci	int ctr;
398c2ecf20Sopenharmony_ci	unsigned long counta;
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	do {
428c2ecf20Sopenharmony_ci		ctr = atomic_read(&instruction_counter);
438c2ecf20Sopenharmony_ci		counta = mfspr(SPRN_COUNTA);
448c2ecf20Sopenharmony_ci	} while (ctr != atomic_read(&instruction_counter));
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	return ((s64)ctr << 16) | (counta >> 16);
478c2ecf20Sopenharmony_ci}
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_cistatic int event_type(struct perf_event *event)
508c2ecf20Sopenharmony_ci{
518c2ecf20Sopenharmony_ci	switch (event->attr.type) {
528c2ecf20Sopenharmony_ci	case PERF_TYPE_HARDWARE:
538c2ecf20Sopenharmony_ci		if (event->attr.config == PERF_COUNT_HW_CPU_CYCLES)
548c2ecf20Sopenharmony_ci			return PERF_8xx_ID_CPU_CYCLES;
558c2ecf20Sopenharmony_ci		if (event->attr.config == PERF_COUNT_HW_INSTRUCTIONS)
568c2ecf20Sopenharmony_ci			return PERF_8xx_ID_HW_INSTRUCTIONS;
578c2ecf20Sopenharmony_ci		break;
588c2ecf20Sopenharmony_ci	case PERF_TYPE_HW_CACHE:
598c2ecf20Sopenharmony_ci		if (event->attr.config == ITLB_LOAD_MISS)
608c2ecf20Sopenharmony_ci			return PERF_8xx_ID_ITLB_LOAD_MISS;
618c2ecf20Sopenharmony_ci		if (event->attr.config == DTLB_LOAD_MISS)
628c2ecf20Sopenharmony_ci			return PERF_8xx_ID_DTLB_LOAD_MISS;
638c2ecf20Sopenharmony_ci		break;
648c2ecf20Sopenharmony_ci	case PERF_TYPE_RAW:
658c2ecf20Sopenharmony_ci		break;
668c2ecf20Sopenharmony_ci	default:
678c2ecf20Sopenharmony_ci		return -ENOENT;
688c2ecf20Sopenharmony_ci	}
698c2ecf20Sopenharmony_ci	return -EOPNOTSUPP;
708c2ecf20Sopenharmony_ci}
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_cistatic int mpc8xx_pmu_event_init(struct perf_event *event)
738c2ecf20Sopenharmony_ci{
748c2ecf20Sopenharmony_ci	int type = event_type(event);
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	if (type < 0)
778c2ecf20Sopenharmony_ci		return type;
788c2ecf20Sopenharmony_ci	return 0;
798c2ecf20Sopenharmony_ci}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_cistatic int mpc8xx_pmu_add(struct perf_event *event, int flags)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	int type = event_type(event);
848c2ecf20Sopenharmony_ci	s64 val = 0;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	if (type < 0)
878c2ecf20Sopenharmony_ci		return type;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	switch (type) {
908c2ecf20Sopenharmony_ci	case PERF_8xx_ID_CPU_CYCLES:
918c2ecf20Sopenharmony_ci		val = get_tb();
928c2ecf20Sopenharmony_ci		break;
938c2ecf20Sopenharmony_ci	case PERF_8xx_ID_HW_INSTRUCTIONS:
948c2ecf20Sopenharmony_ci		if (atomic_inc_return(&insn_ctr_ref) == 1)
958c2ecf20Sopenharmony_ci			mtspr(SPRN_ICTRL, 0xc0080007);
968c2ecf20Sopenharmony_ci		val = get_insn_ctr();
978c2ecf20Sopenharmony_ci		break;
988c2ecf20Sopenharmony_ci	case PERF_8xx_ID_ITLB_LOAD_MISS:
998c2ecf20Sopenharmony_ci		if (atomic_inc_return(&itlb_miss_ref) == 1) {
1008c2ecf20Sopenharmony_ci			unsigned long target = patch_site_addr(&patch__itlbmiss_perf);
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci			patch_branch_site(&patch__itlbmiss_exit_1, target, 0);
1038c2ecf20Sopenharmony_ci		}
1048c2ecf20Sopenharmony_ci		val = itlb_miss_counter;
1058c2ecf20Sopenharmony_ci		break;
1068c2ecf20Sopenharmony_ci	case PERF_8xx_ID_DTLB_LOAD_MISS:
1078c2ecf20Sopenharmony_ci		if (atomic_inc_return(&dtlb_miss_ref) == 1) {
1088c2ecf20Sopenharmony_ci			unsigned long target = patch_site_addr(&patch__dtlbmiss_perf);
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci			patch_branch_site(&patch__dtlbmiss_exit_1, target, 0);
1118c2ecf20Sopenharmony_ci		}
1128c2ecf20Sopenharmony_ci		val = dtlb_miss_counter;
1138c2ecf20Sopenharmony_ci		break;
1148c2ecf20Sopenharmony_ci	}
1158c2ecf20Sopenharmony_ci	local64_set(&event->hw.prev_count, val);
1168c2ecf20Sopenharmony_ci	return 0;
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_cistatic void mpc8xx_pmu_read(struct perf_event *event)
1208c2ecf20Sopenharmony_ci{
1218c2ecf20Sopenharmony_ci	int type = event_type(event);
1228c2ecf20Sopenharmony_ci	s64 prev, val = 0, delta = 0;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (type < 0)
1258c2ecf20Sopenharmony_ci		return;
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	do {
1288c2ecf20Sopenharmony_ci		prev = local64_read(&event->hw.prev_count);
1298c2ecf20Sopenharmony_ci		switch (type) {
1308c2ecf20Sopenharmony_ci		case PERF_8xx_ID_CPU_CYCLES:
1318c2ecf20Sopenharmony_ci			val = get_tb();
1328c2ecf20Sopenharmony_ci			delta = 16 * (val - prev);
1338c2ecf20Sopenharmony_ci			break;
1348c2ecf20Sopenharmony_ci		case PERF_8xx_ID_HW_INSTRUCTIONS:
1358c2ecf20Sopenharmony_ci			val = get_insn_ctr();
1368c2ecf20Sopenharmony_ci			delta = prev - val;
1378c2ecf20Sopenharmony_ci			if (delta < 0)
1388c2ecf20Sopenharmony_ci				delta += 0x1000000000000LL;
1398c2ecf20Sopenharmony_ci			break;
1408c2ecf20Sopenharmony_ci		case PERF_8xx_ID_ITLB_LOAD_MISS:
1418c2ecf20Sopenharmony_ci			val = itlb_miss_counter;
1428c2ecf20Sopenharmony_ci			delta = (s64)((s32)val - (s32)prev);
1438c2ecf20Sopenharmony_ci			break;
1448c2ecf20Sopenharmony_ci		case PERF_8xx_ID_DTLB_LOAD_MISS:
1458c2ecf20Sopenharmony_ci			val = dtlb_miss_counter;
1468c2ecf20Sopenharmony_ci			delta = (s64)((s32)val - (s32)prev);
1478c2ecf20Sopenharmony_ci			break;
1488c2ecf20Sopenharmony_ci		}
1498c2ecf20Sopenharmony_ci	} while (local64_cmpxchg(&event->hw.prev_count, prev, val) != prev);
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	local64_add(delta, &event->count);
1528c2ecf20Sopenharmony_ci}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic void mpc8xx_pmu_del(struct perf_event *event, int flags)
1558c2ecf20Sopenharmony_ci{
1568c2ecf20Sopenharmony_ci	mpc8xx_pmu_read(event);
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	/* If it was the last user, stop counting to avoid useles overhead */
1598c2ecf20Sopenharmony_ci	switch (event_type(event)) {
1608c2ecf20Sopenharmony_ci	case PERF_8xx_ID_CPU_CYCLES:
1618c2ecf20Sopenharmony_ci		break;
1628c2ecf20Sopenharmony_ci	case PERF_8xx_ID_HW_INSTRUCTIONS:
1638c2ecf20Sopenharmony_ci		if (atomic_dec_return(&insn_ctr_ref) == 0)
1648c2ecf20Sopenharmony_ci			mtspr(SPRN_ICTRL, 7);
1658c2ecf20Sopenharmony_ci		break;
1668c2ecf20Sopenharmony_ci	case PERF_8xx_ID_ITLB_LOAD_MISS:
1678c2ecf20Sopenharmony_ci		if (atomic_dec_return(&itlb_miss_ref) == 0) {
1688c2ecf20Sopenharmony_ci			/* mfspr r10, SPRN_SPRG_SCRATCH0 */
1698c2ecf20Sopenharmony_ci			struct ppc_inst insn = ppc_inst(PPC_INST_MFSPR | __PPC_RS(R10) |
1708c2ecf20Sopenharmony_ci					    __PPC_SPR(SPRN_SPRG_SCRATCH0));
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci			patch_instruction_site(&patch__itlbmiss_exit_1, insn);
1738c2ecf20Sopenharmony_ci		}
1748c2ecf20Sopenharmony_ci		break;
1758c2ecf20Sopenharmony_ci	case PERF_8xx_ID_DTLB_LOAD_MISS:
1768c2ecf20Sopenharmony_ci		if (atomic_dec_return(&dtlb_miss_ref) == 0) {
1778c2ecf20Sopenharmony_ci			/* mfspr r10, SPRN_DAR */
1788c2ecf20Sopenharmony_ci			struct ppc_inst insn = ppc_inst(PPC_INST_MFSPR | __PPC_RS(R10) |
1798c2ecf20Sopenharmony_ci					    __PPC_SPR(SPRN_DAR));
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci			patch_instruction_site(&patch__dtlbmiss_exit_1, insn);
1828c2ecf20Sopenharmony_ci		}
1838c2ecf20Sopenharmony_ci		break;
1848c2ecf20Sopenharmony_ci	}
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_cistatic struct pmu mpc8xx_pmu = {
1888c2ecf20Sopenharmony_ci	.event_init	= mpc8xx_pmu_event_init,
1898c2ecf20Sopenharmony_ci	.add		= mpc8xx_pmu_add,
1908c2ecf20Sopenharmony_ci	.del		= mpc8xx_pmu_del,
1918c2ecf20Sopenharmony_ci	.read		= mpc8xx_pmu_read,
1928c2ecf20Sopenharmony_ci	.capabilities	= PERF_PMU_CAP_NO_INTERRUPT |
1938c2ecf20Sopenharmony_ci			  PERF_PMU_CAP_NO_NMI,
1948c2ecf20Sopenharmony_ci};
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_cistatic int init_mpc8xx_pmu(void)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	mtspr(SPRN_ICTRL, 7);
1998c2ecf20Sopenharmony_ci	mtspr(SPRN_CMPA, 0);
2008c2ecf20Sopenharmony_ci	mtspr(SPRN_COUNTA, 0xffff);
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	return perf_pmu_register(&mpc8xx_pmu, "cpu", PERF_TYPE_RAW);
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ciearly_initcall(init_mpc8xx_pmu);
206