848 lines · 21.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0+//// Linux performance counter support for ARC CPUs.// This code is inspired by the perf support of various other architectures.//// Copyright (C) 2013-2018 Synopsys, Inc. (www.synopsys.com) #include <linux/errno.h>#include <linux/interrupt.h>#include <linux/module.h>#include <linux/of.h>#include <linux/perf_event.h>#include <linux/platform_device.h>#include <asm/arcregs.h>#include <asm/stacktrace.h> /* HW holds 8 symbols + one for null terminator */#define ARCPMU_EVENT_NAME_LEN	9 /* * Some ARC pct quirks: * * PERF_COUNT_HW_STALLED_CYCLES_BACKEND * PERF_COUNT_HW_STALLED_CYCLES_FRONTEND *	The ARC 700 can either measure stalls per pipeline stage, or all stalls *	combined; for now we assign all stalls to STALLED_CYCLES_BACKEND *	and all pipeline flushes (e.g. caused by mispredicts, etc.) to *	STALLED_CYCLES_FRONTEND. * *	We could start multiple performance counters and combine everything *	afterwards, but that makes it complicated. * *	Note that I$ cache misses aren't counted by either of the two! */ /* * ARC PCT has hardware conditions with fixed "names" but variable "indexes" * (based on a specific RTL build) * Below is the static map between perf generic/arc specific event_id and * h/w condition names. * At the time of probe, we loop thru each index and find its name to * complete the mapping of perf event_id to h/w index as latter is needed * to program the counter really */static const char * const arc_pmu_ev_hw_map[] = {	/* count cycles */	[PERF_COUNT_HW_CPU_CYCLES] = "crun",	[PERF_COUNT_HW_REF_CPU_CYCLES] = "crun",	[PERF_COUNT_HW_BUS_CYCLES] = "crun", 	[PERF_COUNT_HW_STALLED_CYCLES_FRONTEND] = "bflush",	[PERF_COUNT_HW_STALLED_CYCLES_BACKEND] = "bstall", 	/* counts condition */	[PERF_COUNT_HW_INSTRUCTIONS] = "iall",	/* All jump instructions that are taken */	[PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = "ijmptak",#ifdef CONFIG_ISA_ARCV2	[PERF_COUNT_HW_BRANCH_MISSES] = "bpmp",#else	[PERF_COUNT_ARC_BPOK]         = "bpok",	  /* NP-NT, PT-T, PNT-NT */	[PERF_COUNT_HW_BRANCH_MISSES] = "bpfail", /* NP-T, PT-NT, PNT-T */#endif	[PERF_COUNT_ARC_LDC] = "imemrdc",	/* Instr: mem read cached */	[PERF_COUNT_ARC_STC] = "imemwrc",	/* Instr: mem write cached */ 	[PERF_COUNT_ARC_DCLM] = "dclm",		/* D-cache Load Miss */	[PERF_COUNT_ARC_DCSM] = "dcsm",		/* D-cache Store Miss */	[PERF_COUNT_ARC_ICM] = "icm",		/* I-cache Miss */	[PERF_COUNT_ARC_EDTLB] = "edtlb",	/* D-TLB Miss */	[PERF_COUNT_ARC_EITLB] = "eitlb",	/* I-TLB Miss */ 	[PERF_COUNT_HW_CACHE_REFERENCES] = "imemrdc",	/* Instr: mem read cached */	[PERF_COUNT_HW_CACHE_MISSES] = "dclm",		/* D-cache Load Miss */}; #define C(_x)			PERF_COUNT_HW_CACHE_##_x#define CACHE_OP_UNSUPPORTED	0xffff static const unsigned int arc_pmu_cache_map[C(MAX)][C(OP_MAX)][C(RESULT_MAX)] = {	[C(L1D)] = {		[C(OP_READ)] = {			[C(RESULT_ACCESS)]	= PERF_COUNT_ARC_LDC,			[C(RESULT_MISS)]	= PERF_COUNT_ARC_DCLM,		},		[C(OP_WRITE)] = {			[C(RESULT_ACCESS)]	= PERF_COUNT_ARC_STC,			[C(RESULT_MISS)]	= PERF_COUNT_ARC_DCSM,		},		[C(OP_PREFETCH)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},	},	[C(L1I)] = {		[C(OP_READ)] = {			[C(RESULT_ACCESS)]	= PERF_COUNT_HW_INSTRUCTIONS,			[C(RESULT_MISS)]	= PERF_COUNT_ARC_ICM,		},		[C(OP_WRITE)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},		[C(OP_PREFETCH)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},	},	[C(LL)] = {		[C(OP_READ)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},		[C(OP_WRITE)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},		[C(OP_PREFETCH)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},	},	[C(DTLB)] = {		[C(OP_READ)] = {			[C(RESULT_ACCESS)]	= PERF_COUNT_ARC_LDC,			[C(RESULT_MISS)]	= PERF_COUNT_ARC_EDTLB,		},			/* DTLB LD/ST Miss not segregated by h/w*/		[C(OP_WRITE)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},		[C(OP_PREFETCH)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},	},	[C(ITLB)] = {		[C(OP_READ)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= PERF_COUNT_ARC_EITLB,		},		[C(OP_WRITE)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},		[C(OP_PREFETCH)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},	},	[C(BPU)] = {		[C(OP_READ)] = {			[C(RESULT_ACCESS)] = PERF_COUNT_HW_BRANCH_INSTRUCTIONS,			[C(RESULT_MISS)]	= PERF_COUNT_HW_BRANCH_MISSES,		},		[C(OP_WRITE)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},		[C(OP_PREFETCH)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},	},	[C(NODE)] = {		[C(OP_READ)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},		[C(OP_WRITE)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},		[C(OP_PREFETCH)] = {			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,		},	},}; enum arc_pmu_attr_groups {	ARCPMU_ATTR_GR_EVENTS,	ARCPMU_ATTR_GR_FORMATS,	ARCPMU_NR_ATTR_GR}; struct arc_pmu_raw_event_entry {	char name[ARCPMU_EVENT_NAME_LEN];}; struct arc_pmu {	struct pmu	pmu;	unsigned int	irq;	int		n_counters;	int		n_events;	u64		max_period;	int		ev_hw_idx[PERF_COUNT_ARC_HW_MAX]; 	struct arc_pmu_raw_event_entry	*raw_entry;	struct attribute		**attrs;	struct perf_pmu_events_attr	*attr;	const struct attribute_group	*attr_groups[ARCPMU_NR_ATTR_GR + 1];}; struct arc_pmu_cpu {	/*	 * A 1 bit for an index indicates that the counter is being used for	 * an event. A 0 means that the counter can be used.	 */	unsigned long	used_mask[BITS_TO_LONGS(ARC_PERF_MAX_COUNTERS)]; 	/*	 * The events that are active on the PMU for the given index.	 */	struct perf_event *act_counter[ARC_PERF_MAX_COUNTERS];}; struct arc_callchain_trace {	int depth;	void *perf_stuff;}; static int callchain_trace(unsigned int addr, void *data){	struct arc_callchain_trace *ctrl = data;	struct perf_callchain_entry_ctx *entry = ctrl->perf_stuff; 	perf_callchain_store(entry, addr); 	if (ctrl->depth++ < 3)		return 0; 	return -1;} void perf_callchain_kernel(struct perf_callchain_entry_ctx *entry,			   struct pt_regs *regs){	struct arc_callchain_trace ctrl = {		.depth = 0,		.perf_stuff = entry,	}; 	arc_unwind_core(NULL, regs, callchain_trace, &ctrl);} void perf_callchain_user(struct perf_callchain_entry_ctx *entry,			 struct pt_regs *regs){	/*	 * User stack can't be unwound trivially with kernel dwarf unwinder	 * So for now just record the user PC	 */	perf_callchain_store(entry, instruction_pointer(regs));} static struct arc_pmu *arc_pmu;static DEFINE_PER_CPU(struct arc_pmu_cpu, arc_pmu_cpu); /* read counter #idx; note that counter# != event# on ARC! */static u64 arc_pmu_read_counter(int idx){	u32 tmp;	u64 result; 	/*	 * ARC supports making 'snapshots' of the counters, so we don't	 * need to care about counters wrapping to 0 underneath our feet	 */	write_aux_reg(ARC_REG_PCT_INDEX, idx);	tmp = read_aux_reg(ARC_REG_PCT_CONTROL);	write_aux_reg(ARC_REG_PCT_CONTROL, tmp | ARC_REG_PCT_CONTROL_SN);	result = (u64) (read_aux_reg(ARC_REG_PCT_SNAPH)) << 32;	result |= read_aux_reg(ARC_REG_PCT_SNAPL); 	return result;} static void arc_perf_event_update(struct perf_event *event,				  struct hw_perf_event *hwc, int idx){	u64 prev_raw_count = local64_read(&hwc->prev_count);	u64 new_raw_count = arc_pmu_read_counter(idx);	s64 delta = new_raw_count - prev_raw_count; 	/*	 * We aren't afraid of hwc->prev_count changing beneath our feet	 * because there's no way for us to re-enter this function anytime.	 */	local64_set(&hwc->prev_count, new_raw_count);	local64_add(delta, &event->count);	local64_sub(delta, &hwc->period_left);} static void arc_pmu_read(struct perf_event *event){	arc_perf_event_update(event, &event->hw, event->hw.idx);} static int arc_pmu_cache_event(u64 config){	unsigned int cache_type, cache_op, cache_result;	int ret; 	cache_type	= (config >>  0) & 0xff;	cache_op	= (config >>  8) & 0xff;	cache_result	= (config >> 16) & 0xff;	if (cache_type >= PERF_COUNT_HW_CACHE_MAX)		return -EINVAL;	if (cache_op >= PERF_COUNT_HW_CACHE_OP_MAX)		return -EINVAL;	if (cache_result >= PERF_COUNT_HW_CACHE_RESULT_MAX)		return -EINVAL; 	ret = arc_pmu_cache_map[cache_type][cache_op][cache_result]; 	if (ret == CACHE_OP_UNSUPPORTED)		return -ENOENT; 	pr_debug("init cache event: type/op/result %d/%d/%d with h/w %d \'%s\'\n",		 cache_type, cache_op, cache_result, ret,		 arc_pmu_ev_hw_map[ret]); 	return ret;} /* initializes hw_perf_event structure if event is supported */static int arc_pmu_event_init(struct perf_event *event){	struct hw_perf_event *hwc = &event->hw;	int ret; 	if (!is_sampling_event(event)) {		hwc->sample_period = arc_pmu->max_period;		hwc->last_period = hwc->sample_period;		local64_set(&hwc->period_left, hwc->sample_period);	} 	hwc->config = 0; 	if (is_isa_arcv2()) {		/* "exclude user" means "count only kernel" */		if (event->attr.exclude_user)			hwc->config |= ARC_REG_PCT_CONFIG_KERN; 		/* "exclude kernel" means "count only user" */		if (event->attr.exclude_kernel)			hwc->config |= ARC_REG_PCT_CONFIG_USER;	} 	switch (event->attr.type) {	case PERF_TYPE_HARDWARE:		if (event->attr.config >= PERF_COUNT_HW_MAX)			return -ENOENT;		if (arc_pmu->ev_hw_idx[event->attr.config] < 0)			return -ENOENT;		hwc->config |= arc_pmu->ev_hw_idx[event->attr.config];		pr_debug("init event %d with h/w %08x \'%s\'\n",			 (int)event->attr.config, (int)hwc->config,			 arc_pmu_ev_hw_map[event->attr.config]);		return 0; 	case PERF_TYPE_HW_CACHE:		ret = arc_pmu_cache_event(event->attr.config);		if (ret < 0)			return ret;		hwc->config |= arc_pmu->ev_hw_idx[ret];		pr_debug("init cache event with h/w %08x \'%s\'\n",			 (int)hwc->config, arc_pmu_ev_hw_map[ret]);		return 0; 	case PERF_TYPE_RAW:		if (event->attr.config >= arc_pmu->n_events)			return -ENOENT; 		hwc->config |= event->attr.config;		pr_debug("init raw event with idx %lld \'%s\'\n",			 event->attr.config,			 arc_pmu->raw_entry[event->attr.config].name); 		return 0; 	default:		return -ENOENT;	}} /* starts all counters */static void arc_pmu_enable(struct pmu *pmu){	u32 tmp;	tmp = read_aux_reg(ARC_REG_PCT_CONTROL);	write_aux_reg(ARC_REG_PCT_CONTROL, (tmp & 0xffff0000) | 0x1);} /* stops all counters */static void arc_pmu_disable(struct pmu *pmu){	u32 tmp;	tmp = read_aux_reg(ARC_REG_PCT_CONTROL);	write_aux_reg(ARC_REG_PCT_CONTROL, (tmp & 0xffff0000) | 0x0);} static int arc_pmu_event_set_period(struct perf_event *event){	struct hw_perf_event *hwc = &event->hw;	s64 left = local64_read(&hwc->period_left);	s64 period = hwc->sample_period;	int idx = hwc->idx;	int overflow = 0;	u64 value; 	if (unlikely(left <= -period)) {		/* left underflowed by more than period. */		left = period;		local64_set(&hwc->period_left, left);		hwc->last_period = period;		overflow = 1;	} else if (unlikely(left <= 0)) {		/* left underflowed by less than period. */		left += period;		local64_set(&hwc->period_left, left);		hwc->last_period = period;		overflow = 1;	} 	if (left > arc_pmu->max_period)		left = arc_pmu->max_period; 	value = arc_pmu->max_period - left;	local64_set(&hwc->prev_count, value); 	/* Select counter */	write_aux_reg(ARC_REG_PCT_INDEX, idx); 	/* Write value */	write_aux_reg(ARC_REG_PCT_COUNTL, lower_32_bits(value));	write_aux_reg(ARC_REG_PCT_COUNTH, upper_32_bits(value)); 	perf_event_update_userpage(event); 	return overflow;} /* * Assigns hardware counter to hardware condition. * Note that there is no separate start/stop mechanism; * stopping is achieved by assigning the 'never' condition */static void arc_pmu_start(struct perf_event *event, int flags){	struct hw_perf_event *hwc = &event->hw;	int idx = hwc->idx; 	if (WARN_ON_ONCE(idx == -1))		return; 	if (flags & PERF_EF_RELOAD)		WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE)); 	hwc->state = 0; 	arc_pmu_event_set_period(event); 	/* Enable interrupt for this counter */	if (is_sampling_event(event))		write_aux_reg(ARC_REG_PCT_INT_CTRL,			      read_aux_reg(ARC_REG_PCT_INT_CTRL) | BIT(idx)); 	/* enable ARC pmu here */	write_aux_reg(ARC_REG_PCT_INDEX, idx);		/* counter # */	write_aux_reg(ARC_REG_PCT_CONFIG, hwc->config);	/* condition */} static void arc_pmu_stop(struct perf_event *event, int flags){	struct hw_perf_event *hwc = &event->hw;	int idx = hwc->idx; 	/* Disable interrupt for this counter */	if (is_sampling_event(event)) {		/*		 * Reset interrupt flag by writing of 1. This is required		 * to make sure pending interrupt was not left.		 */		write_aux_reg(ARC_REG_PCT_INT_ACT, BIT(idx));		write_aux_reg(ARC_REG_PCT_INT_CTRL,			      read_aux_reg(ARC_REG_PCT_INT_CTRL) & ~BIT(idx));	} 	if (!(event->hw.state & PERF_HES_STOPPED)) {		/* stop hw counter here */		write_aux_reg(ARC_REG_PCT_INDEX, idx); 		/* condition code #0 is always "never" */		write_aux_reg(ARC_REG_PCT_CONFIG, 0); 		event->hw.state |= PERF_HES_STOPPED;	}

Lines 1–500 of 848.