Home | History | Annotate | Line # | Download | only in tprof
tprof_armv7.c revision 1.8
      1 /* $NetBSD: tprof_armv7.c,v 1.8 2022/12/01 00:29:10 ryo Exp $ */
      2 
      3 /*-
      4  * Copyright (c) 2018 Jared McNeill <jmcneill (at) invisible.ca>
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
     21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
     23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26  * SUCH DAMAGE.
     27  */
     28 
     29 #include <sys/cdefs.h>
     30 __KERNEL_RCSID(0, "$NetBSD: tprof_armv7.c,v 1.8 2022/12/01 00:29:10 ryo Exp $");
     31 
     32 #include <sys/param.h>
     33 #include <sys/bus.h>
     34 #include <sys/cpu.h>
     35 #include <sys/xcall.h>
     36 
     37 #include <dev/tprof/tprof.h>
     38 
     39 #include <arm/armreg.h>
     40 #include <arm/locore.h>
     41 
     42 #include <dev/tprof/tprof_armv7.h>
     43 
     44 #define	PMCR_N			__BITS(15,11)
     45 #define	PMCR_D			__BIT(3)
     46 #define	PMCR_E			__BIT(0)
     47 
     48 #define	PMINTEN_C		__BIT(31)
     49 #define	PMINTEN_P		__BITS(30,0)
     50 #define	PMCNTEN_C		__BIT(31)
     51 #define	PMCNTEN_P		__BITS(30,0)
     52 
     53 #define	PMEVTYPER_P		__BIT(31)
     54 #define	PMEVTYPER_U		__BIT(30)
     55 #define	PMEVTYPER_EVTCOUNT	__BITS(7,0)
     56 
     57 static tprof_param_t armv7_pmu_param;
     58 static const u_int armv7_pmu_counter = 1;
     59 static uint32_t counter_val;
     60 static uint32_t counter_reset_val;
     61 
     62 static uint16_t cortexa9_events[] = {
     63 	0x40, 0x41, 0x42,
     64 	0x50, 0x51,
     65 	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
     66 	0x6e,
     67 	0x70, 0x71, 0x72, 0x73, 0x74,
     68 	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86,
     69 	0x8a, 0x8b,
     70 	0x90, 0x91, 0x92, 0x93,
     71 	0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5
     72 };
     73 
     74 static bool
     75 armv7_pmu_event_implemented(uint16_t event)
     76 {
     77 	if (CPU_ID_CORTEX_A9_P(curcpu()->ci_midr)) {
     78 		/* Cortex-A9 with PMUv1 lacks PMCEID0/1 */
     79 		u_int n;
     80 
     81 		/* Events specific to the Cortex-A9 */
     82 		for (n = 0; n < __arraycount(cortexa9_events); n++) {
     83 			if (cortexa9_events[n] == event) {
     84 				return true;
     85 			}
     86 		}
     87 		/* Supported architectural events */
     88 		if (event != 0x08 && event != 0x0e && event < 0x1e) {
     89 			return true;
     90 		}
     91 	} else {
     92 		/* PMUv2 */
     93 		uint32_t eid[2];
     94 
     95 		if (event >= 64) {
     96 			return false;
     97 		}
     98 
     99 		eid[0] = armreg_pmceid0_read();
    100 		eid[1] = armreg_pmceid1_read();
    101 
    102 		const u_int idx = event / 32;
    103 		const u_int bit = event % 32;
    104 
    105 		if (eid[idx] & __BIT(bit)) {
    106 			return true;
    107 		}
    108 	}
    109 
    110 	return false;
    111 }
    112 
    113 static void
    114 armv7_pmu_set_pmevtyper(u_int counter, uint64_t val)
    115 {
    116 	armreg_pmselr_write(counter);
    117 	isb();
    118 	armreg_pmxevtyper_write(val);
    119 }
    120 
    121 static void
    122 armv7_pmu_set_pmevcntr(u_int counter, uint32_t val)
    123 {
    124 	armreg_pmselr_write(counter);
    125 	isb();
    126 	armreg_pmxevcntr_write(val);
    127 }
    128 
    129 static void
    130 armv7_pmu_start_cpu(void *arg1, void *arg2)
    131 {
    132 	const uint32_t counter_mask = __BIT(armv7_pmu_counter);
    133 	uint64_t pmcr, pmevtyper;
    134 
    135 	/* Enable performance monitor */
    136 	pmcr = armreg_pmcr_read();
    137 	pmcr |= PMCR_E;
    138 	armreg_pmcr_write(pmcr);
    139 
    140 	/* Disable event counter */
    141 	armreg_pmcntenclr_write(counter_mask);
    142 
    143 	/* Configure event counter */
    144 	pmevtyper = __SHIFTIN(armv7_pmu_param.p_event, PMEVTYPER_EVTCOUNT);
    145 	if (!ISSET(armv7_pmu_param.p_flags, TPROF_PARAM_USER))
    146 		pmevtyper |= PMEVTYPER_U;
    147 	if (!ISSET(armv7_pmu_param.p_flags, TPROF_PARAM_KERN))
    148 		pmevtyper |= PMEVTYPER_P;
    149 
    150 	armv7_pmu_set_pmevtyper(armv7_pmu_counter, pmevtyper);
    151 
    152 	/* Enable overflow interrupts */
    153 	armreg_pmintenset_write(counter_mask);
    154 
    155 	/* Clear overflow flag */
    156 	armreg_pmovsr_write(counter_mask);
    157 
    158 	/* Initialize event counter value */
    159 	armv7_pmu_set_pmevcntr(armv7_pmu_counter, counter_reset_val);
    160 
    161 	/* Enable event counter */
    162 	armreg_pmcntenset_write(counter_mask);
    163 }
    164 
    165 static void
    166 armv7_pmu_stop_cpu(void *arg1, void *arg2)
    167 {
    168 	const uint32_t counter_mask = __BIT(armv7_pmu_counter);
    169 
    170 	/* Disable overflow interrupts */
    171 	armreg_pmintenclr_write(counter_mask);
    172 
    173 	/* Disable event counter */
    174 	armreg_pmcntenclr_write(counter_mask);
    175 }
    176 
    177 static uint64_t
    178 armv7_pmu_estimate_freq(void)
    179 {
    180 	uint64_t cpufreq = curcpu()->ci_data.cpu_cc_freq;
    181 	uint64_t freq = 10000;
    182 	uint32_t pmcr;
    183 
    184 	counter_val = cpufreq / freq;
    185 	if (counter_val == 0)
    186 		counter_val = 4000000000ULL / freq;
    187 
    188 	pmcr = armreg_pmcr_read();
    189 	if (pmcr & PMCR_D)
    190 		counter_val /= 64;
    191 
    192 	return freq;
    193 }
    194 
    195 static uint32_t
    196 armv7_pmu_ident(void)
    197 {
    198 	return TPROF_IDENT_ARMV7_GENERIC;
    199 }
    200 
    201 static int
    202 armv7_pmu_start(const tprof_param_t *param)
    203 {
    204 	/* PMCR.N of 0 means that no event counters are available */
    205 	if (__SHIFTOUT(armreg_pmcr_read(), PMCR_N) == 0) {
    206 		return EINVAL;
    207 	}
    208 
    209 	if (!armv7_pmu_event_implemented(param->p_event)) {
    210 		printf("%s: event %#llx not implemented on this CPU\n",
    211 		    __func__, param->p_event);
    212 		return EINVAL;
    213 	}
    214 
    215 	counter_reset_val = -counter_val + 1;
    216 
    217 	armv7_pmu_param = *param;
    218 	uint64_t xc = xc_broadcast(0, armv7_pmu_start_cpu, NULL, NULL);
    219 	xc_wait(xc);
    220 
    221 	return 0;
    222 }
    223 
    224 static void
    225 armv7_pmu_stop(const tprof_param_t *param)
    226 {
    227 	uint64_t xc;
    228 
    229 	xc = xc_broadcast(0, armv7_pmu_stop_cpu, NULL, NULL);
    230 	xc_wait(xc);
    231 }
    232 
    233 static const tprof_backend_ops_t tprof_armv7_pmu_ops = {
    234 	.tbo_estimate_freq = armv7_pmu_estimate_freq,
    235 	.tbo_ident = armv7_pmu_ident,
    236 	.tbo_start = armv7_pmu_start,
    237 	.tbo_stop = armv7_pmu_stop,
    238 };
    239 
    240 int
    241 armv7_pmu_intr(void *priv)
    242 {
    243 	const struct trapframe * const tf = priv;
    244 	const uint32_t counter_mask = __BIT(armv7_pmu_counter);
    245 	tprof_frame_info_t tfi;
    246 
    247 	const uint32_t pmovsr = armreg_pmovsr_read();
    248 	if ((pmovsr & counter_mask) != 0) {
    249 		tfi.tfi_pc = tf->tf_pc;
    250 		tfi.tfi_inkernel = tfi.tfi_pc >= VM_MIN_KERNEL_ADDRESS &&
    251 		    tfi.tfi_pc < VM_MAX_KERNEL_ADDRESS;
    252 		tprof_sample(NULL, &tfi);
    253 
    254 		armv7_pmu_set_pmevcntr(armv7_pmu_counter, counter_reset_val);
    255 	}
    256 	armreg_pmovsr_write(pmovsr);
    257 
    258 	return 1;
    259 }
    260 
    261 int
    262 armv7_pmu_init(void)
    263 {
    264 	/* Disable user mode access to performance monitors */
    265 	armreg_pmuserenr_write(0);
    266 
    267 	/* Disable interrupts */
    268 	armreg_pmintenclr_write(PMINTEN_P);
    269 
    270 	/* Disable counters */
    271 	armreg_pmcntenclr_write(PMCNTEN_P);
    272 
    273 	return tprof_backend_register("tprof_armv7", &tprof_armv7_pmu_ops,
    274 	    TPROF_BACKEND_VERSION);
    275 }
    276