Home | History | Annotate | Line # | Download | only in tprof
tprof_armv8.c revision 1.16
      1 /* $NetBSD: tprof_armv8.c,v 1.16 2022/11/10 07:54:20 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_armv8.c,v 1.16 2022/11/10 07:54:20 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/cpufunc.h>
     41 
     42 #include <dev/tprof/tprof_armv8.h>
     43 
     44 static tprof_param_t armv8_pmu_param;
     45 static const u_int armv8_pmu_counter = 0;
     46 static uint32_t counter_val;
     47 static uint32_t counter_reset_val;
     48 
     49 static bool
     50 armv8_pmu_event_implemented(uint16_t event)
     51 {
     52 	uint64_t eid[2];
     53 
     54 	if (event >= 64)
     55 		return false;
     56 
     57 	eid[0] = reg_pmceid0_el0_read();
     58 	eid[1] = reg_pmceid1_el0_read();
     59 
     60 	/* The low 32bits of PMCEID[01]_EL0 contain the common events 0 to n */
     61 	const u_int idx = event / 32;
     62 	const u_int bit = event % 32;
     63 
     64 	if (eid[idx] & __BIT(bit))
     65 		return true;
     66 
     67 	return false;
     68 }
     69 
     70 static void
     71 armv8_pmu_set_pmevtyper(u_int counter, uint64_t val)
     72 {
     73 	reg_pmselr_el0_write(counter);
     74 	isb();
     75 	reg_pmxevtyper_el0_write(val);
     76 }
     77 
     78 static void
     79 armv8_pmu_set_pmevcntr(u_int counter, uint32_t val)
     80 {
     81 	reg_pmselr_el0_write(counter);
     82 	isb();
     83 	reg_pmxevcntr_el0_write(val);
     84 }
     85 
     86 static void
     87 armv8_pmu_start_cpu(void *arg1, void *arg2)
     88 {
     89 	const uint32_t counter_mask = __BIT(armv8_pmu_counter);
     90 	uint64_t pmevtyper;
     91 
     92 	/* Disable event counter */
     93 	reg_pmcntenclr_el0_write(counter_mask);
     94 
     95 	/* Configure event counter */
     96 	pmevtyper = __SHIFTIN(armv8_pmu_param.p_event, PMEVTYPER_EVTCOUNT);
     97 	if (!ISSET(armv8_pmu_param.p_flags, TPROF_PARAM_USER))
     98 		pmevtyper |= PMEVTYPER_U;
     99 	if (!ISSET(armv8_pmu_param.p_flags, TPROF_PARAM_KERN))
    100 		pmevtyper |= PMEVTYPER_P;
    101 
    102 	armv8_pmu_set_pmevtyper(armv8_pmu_counter, pmevtyper);
    103 
    104 	/* Enable overflow interrupts */
    105 	reg_pmintenset_el1_write(counter_mask);
    106 
    107 	/* Clear overflow flag */
    108 	reg_pmovsclr_el0_write(counter_mask);
    109 
    110 	/* Initialize event counter value */
    111 	armv8_pmu_set_pmevcntr(armv8_pmu_counter, counter_reset_val);
    112 
    113 	/* Enable event counter */
    114 	reg_pmcntenset_el0_write(counter_mask);
    115 	reg_pmcr_el0_write(PMCR_E);
    116 }
    117 
    118 static void
    119 armv8_pmu_stop_cpu(void *arg1, void *arg2)
    120 {
    121 	const uint32_t counter_mask = __BIT(armv8_pmu_counter);
    122 
    123 	/* Disable overflow interrupts */
    124 	reg_pmintenclr_el1_write(counter_mask);
    125 
    126 	/* Disable event counter */
    127 	reg_pmcntenclr_el0_write(counter_mask);
    128 	reg_pmcr_el0_write(0);
    129 }
    130 
    131 static uint64_t
    132 armv8_pmu_estimate_freq(void)
    133 {
    134 	uint64_t cpufreq = curcpu()->ci_data.cpu_cc_freq;
    135 	uint64_t freq = 10000;
    136 
    137 	counter_val = cpufreq / freq;
    138 	if (counter_val == 0)
    139 		counter_val = 4000000000ULL / freq;
    140 
    141 	return freq;
    142 }
    143 
    144 static uint32_t
    145 armv8_pmu_ident(void)
    146 {
    147 	return TPROF_IDENT_ARMV8_GENERIC;
    148 }
    149 
    150 static int
    151 armv8_pmu_start(const tprof_param_t *param)
    152 {
    153 	/* PMCR.N of 0 means that no event counters are available */
    154 	if (__SHIFTOUT(reg_pmcr_el0_read(), PMCR_N) == 0) {
    155 		return EINVAL;
    156 	}
    157 
    158 	if (!armv8_pmu_event_implemented(param->p_event)) {
    159 		printf("%s: event %#" PRIx64 " not implemented on this CPU\n",
    160 		    __func__, param->p_event);
    161 		return EINVAL;
    162 	}
    163 
    164 	counter_reset_val = -counter_val + 1;
    165 
    166 	armv8_pmu_param = *param;
    167 	uint64_t xc = xc_broadcast(0, armv8_pmu_start_cpu, NULL, NULL);
    168 	xc_wait(xc);
    169 
    170 	return 0;
    171 }
    172 
    173 static void
    174 armv8_pmu_stop(const tprof_param_t *param)
    175 {
    176 	uint64_t xc;
    177 
    178 	xc = xc_broadcast(0, armv8_pmu_stop_cpu, NULL, NULL);
    179 	xc_wait(xc);
    180 }
    181 
    182 static const tprof_backend_ops_t tprof_armv8_pmu_ops = {
    183 	.tbo_estimate_freq = armv8_pmu_estimate_freq,
    184 	.tbo_ident = armv8_pmu_ident,
    185 	.tbo_start = armv8_pmu_start,
    186 	.tbo_stop = armv8_pmu_stop,
    187 };
    188 
    189 int
    190 armv8_pmu_intr(void *priv)
    191 {
    192 	const struct trapframe * const tf = priv;
    193 	const uint32_t counter_mask = __BIT(armv8_pmu_counter);
    194 	tprof_frame_info_t tfi;
    195 
    196 	const uint32_t pmovs = reg_pmovsset_el0_read();
    197 	if ((pmovs & counter_mask) != 0) {
    198 		tfi.tfi_pc = tf->tf_pc;
    199 		tfi.tfi_inkernel = tfi.tfi_pc >= VM_MIN_KERNEL_ADDRESS &&
    200 		    tfi.tfi_pc < VM_MAX_KERNEL_ADDRESS;
    201 		tprof_sample(NULL, &tfi);
    202 
    203 		armv8_pmu_set_pmevcntr(armv8_pmu_counter, counter_reset_val);
    204 	}
    205 	reg_pmovsclr_el0_write(pmovs);
    206 
    207 	return 1;
    208 }
    209 
    210 static void
    211 armv8_pmu_init_cpu(void *arg1, void *arg2)
    212 {
    213 	/* Disable EL0 access to performance monitors */
    214 	reg_pmuserenr_el0_write(0);
    215 
    216 	/* Disable interrupts */
    217 	reg_pmintenclr_el1_write(~0U);
    218 
    219 	/* Disable event counters */
    220 	reg_pmcntenclr_el0_write(PMCNTEN_P);
    221 }
    222 
    223 bool
    224 armv8_pmu_detect(void)
    225 {
    226 	const uint64_t dfr0 = reg_id_aa64dfr0_el1_read();
    227 	const u_int pmuver = __SHIFTOUT(dfr0, ID_AA64DFR0_EL1_PMUVER);
    228 
    229 	return pmuver != ID_AA64DFR0_EL1_PMUVER_NONE &&
    230 	       pmuver != ID_AA64DFR0_EL1_PMUVER_IMPL;
    231 }
    232 
    233 int
    234 armv8_pmu_init(void)
    235 {
    236 	KASSERT(armv8_pmu_detect());
    237 
    238 	uint64_t xc = xc_broadcast(0, armv8_pmu_init_cpu, NULL, NULL);
    239 	xc_wait(xc);
    240 
    241 	return tprof_backend_register("tprof_armv8", &tprof_armv8_pmu_ops,
    242 	    TPROF_BACKEND_VERSION);
    243 }
    244