/* $NetBSD: tprof_pmi.c,v 1.14 2017/05/23 08:54:39 nonaka Exp $ */ /*- * Copyright (c)2008,2009 YAMAMOTO Takashi, * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __KERNEL_RCSID(0, "$NetBSD: tprof_pmi.c,v 1.14 2017/05/23 08:54:39 nonaka Exp $"); #include #include #include #include #include #include #include #include #include /* VM_MIN_KERNEL_ADDRESS */ #include #include #include #include /* CPUVENDER_* */ #include /* cpu_vendor */ #include #include #define ESCR_T1_USR __BIT(0) #define ESCR_T1_OS __BIT(1) #define ESCR_T0_USR __BIT(2) #define ESCR_T0_OS __BIT(3) #define ESCR_TAG_ENABLE __BIT(4) #define ESCR_TAG_VALUE __BITS(5, 8) #define ESCR_EVENT_MASK __BITS(9, 24) #define ESCR_EVENT_SELECT __BITS(25, 30) #define CCCR_ENABLE __BIT(12) #define CCCR_ESCR_SELECT __BITS(13, 15) #define CCCR_MUST_BE_SET __BITS(16, 17) #define CCCR_COMPARE __BIT(18) #define CCCR_COMPLEMENT __BIT(19) #define CCCR_THRESHOLD __BITS(20, 23) #define CCCR_EDGE __BIT(24) #define CCCR_FORCE_OVF __BIT(25) #define CCCR_OVF_PMI_T0 __BIT(26) #define CCCR_OVF_PMI_T1 __BIT(27) #define CCCR_CASCADE __BIT(30) #define CCCR_OVF __BIT(31) struct msrs { u_int msr_cccr; u_int msr_escr; u_int msr_counter; }; /* * parameters (see 253669.pdf Table A-6) * * XXX should not hardcode */ static const struct msrs msrs[] = { { .msr_cccr = 0x360, /* MSR_BPU_CCCR0 */ .msr_escr = 0x3a2, /* MSR_FSB_ESCR0 */ .msr_counter = 0x300, /* MSR_BPU_COUNTER0 */ }, { .msr_cccr = 0x362, /* MSR_BPU_CCCR2 */ .msr_escr = 0x3a3, /* MSR_FSB_ESCR1 */ .msr_counter = 0x302, /* MSR_BPU_COUNTER2 */ }, }; static const u_int cccr_escr_select = 0x6; /* MSR_FSB_ESCR? */ static const u_int escr_event_select = 0x13; /* global_power_events */ static const u_int escr_event_mask = 0x1; /* running */ static uint64_t counter_val = 5000000; static uint64_t counter_reset_val; static uint32_t tprof_pmi_lapic_saved[MAXCPUS]; static nmi_handler_t *tprof_pmi_nmi_handle; static tprof_backend_cookie_t *tprof_cookie; static void tprof_pmi_start_cpu(void *arg1, void *arg2) { struct cpu_info * const ci = curcpu(); const struct msrs *msr; uint64_t cccr; uint64_t escr; if (ci->ci_smt_id >= 2) { printf("%s: ignoring %s smt id=%u", __func__, device_xname(ci->ci_dev), (u_int)ci->ci_smt_id); return; } msr = &msrs[ci->ci_smt_id]; escr = __SHIFTIN(escr_event_mask, ESCR_EVENT_MASK) | __SHIFTIN(escr_event_select, ESCR_EVENT_SELECT); cccr = CCCR_ENABLE | __SHIFTIN(cccr_escr_select, CCCR_ESCR_SELECT) | CCCR_MUST_BE_SET; if (ci->ci_smt_id == 0) { escr |= ESCR_T0_OS | ESCR_T0_USR; cccr |= CCCR_OVF_PMI_T0; } else { escr |= ESCR_T1_OS | ESCR_T0_USR; cccr |= CCCR_OVF_PMI_T1; } wrmsr(msr->msr_counter, counter_reset_val); wrmsr(msr->msr_escr, escr); wrmsr(msr->msr_cccr, cccr); tprof_pmi_lapic_saved[cpu_index(ci)] = lapic_readreg(LAPIC_PCINT); lapic_writereg(LAPIC_PCINT, LAPIC_DLMODE_NMI); } static void tprof_pmi_stop_cpu(void *arg1, void *arg2) { struct cpu_info * const ci = curcpu(); const struct msrs *msr; if (ci->ci_smt_id >= 2) { printf("%s: ignoring %s smt id=%u", __func__, device_xname(ci->ci_dev), (u_int)ci->ci_smt_id); return; } msr = &msrs[ci->ci_smt_id]; wrmsr(msr->msr_escr, 0); wrmsr(msr->msr_cccr, 0); lapic_writereg(LAPIC_PCINT, tprof_pmi_lapic_saved[cpu_index(ci)]); } static int tprof_pmi_nmi(const struct trapframe *tf, void *dummy) { struct cpu_info * const ci = curcpu(); const struct msrs *msr; uint32_t pcint; uint64_t cccr; tprof_frame_info_t tfi; KASSERT(dummy == NULL); if (ci->ci_smt_id >= 2) { /* not ours */ return 0; } msr = &msrs[ci->ci_smt_id]; /* check if it's for us */ cccr = rdmsr(msr->msr_cccr); if ((cccr & CCCR_OVF) == 0) { /* not ours */ return 0; } /* record a sample */ #if defined(__x86_64__) tfi.tfi_pc = tf->tf_rip; #else /* defined(__x86_64__) */ tfi.tfi_pc = tf->tf_eip; #endif /* defined(__x86_64__) */ tfi.tfi_inkernel = tfi.tfi_pc >= VM_MIN_KERNEL_ADDRESS; tprof_sample(tprof_cookie, &tfi); /* reset counter */ wrmsr(msr->msr_counter, counter_reset_val); wrmsr(msr->msr_cccr, cccr & ~CCCR_OVF); /* unmask PMI */ pcint = lapic_readreg(LAPIC_PCINT); KASSERT((pcint & LAPIC_LVT_MASKED) != 0); lapic_writereg(LAPIC_PCINT, pcint & ~LAPIC_LVT_MASKED); return 1; } static uint64_t tprof_pmi_estimate_freq(void) { uint64_t cpufreq = curcpu()->ci_data.cpu_cc_freq; uint64_t freq = 10000; counter_val = cpufreq / freq; if (counter_val == 0) { counter_val = UINT64_C(4000000000) / freq; return freq; } return freq; } static int tprof_pmi_start(tprof_backend_cookie_t *cookie) { struct cpu_info * const ci = curcpu(); uint64_t xc; if (!(cpu_vendor == CPUVENDOR_INTEL && CPUID_TO_BASEFAMILY(ci->ci_signature) == 15)) { return ENOTSUP; } KASSERT(tprof_pmi_nmi_handle == NULL); tprof_pmi_nmi_handle = nmi_establish(tprof_pmi_nmi, NULL); counter_reset_val = - counter_val + 1; xc = xc_broadcast(0, tprof_pmi_start_cpu, NULL, NULL); xc_wait(xc); KASSERT(tprof_cookie == NULL); tprof_cookie = cookie; return 0; } static void tprof_pmi_stop(tprof_backend_cookie_t *cookie) { uint64_t xc; xc = xc_broadcast(0, tprof_pmi_stop_cpu, NULL, NULL); xc_wait(xc); KASSERT(tprof_pmi_nmi_handle != NULL); KASSERT(tprof_cookie == cookie); nmi_disestablish(tprof_pmi_nmi_handle); tprof_pmi_nmi_handle = NULL; tprof_cookie = NULL; } static const tprof_backend_ops_t tprof_pmi_ops = { .tbo_estimate_freq = tprof_pmi_estimate_freq, .tbo_start = tprof_pmi_start, .tbo_stop = tprof_pmi_stop, }; MODULE(MODULE_CLASS_DRIVER, tprof_pmi, "tprof"); static int tprof_pmi_modcmd(modcmd_t cmd, void *arg) { switch (cmd) { case MODULE_CMD_INIT: return tprof_backend_register("tprof_pmi", &tprof_pmi_ops, TPROF_BACKEND_VERSION); case MODULE_CMD_FINI: return tprof_backend_unregister("tprof_pmi"); default: return ENOTTY; } }