/* * Copyright (c) 2014-2018 Remy Noel. * Copyright (c) 2014 Richard Braun. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include /* * AMD raw event IDs. */ #define PMU_AMD_RE_CYCLE 0 #define PMU_AMD_RE_INSTRUCTION 1 #define PMU_AMD_RE_CACHE_REF 2 #define PMU_AMD_RE_CACHE_MISS 3 #define PMU_AMD_RE_BRANCH 4 #define PMU_AMD_RE_BRANCH_MISS 5 #define PMU_AMD_RE_DCACHE_REF 6 #define PMU_AMD_RE_DCACHE_MISS 7 #define PMU_AMD_RE_IFETCH_STALL 8 #define PMU_AMD_RE_INVALID ((unsigned int)-1) /* * PMU MSR addresses */ #define PMU_AMD_MSR_PERFEVTSEL0 0xc0010000 #define PMU_AMD_MSR_PERCTR0 0xc0010004 /* * Event Select Register addresses */ #define PMU_AMD_EVTSEL_USR 0x00010000 #define PMU_AMD_EVTSEL_OS 0x00020000 #define PMU_AMD_EVTSEL_INT 0x00100000 #define PMU_AMD_EVTSEL_EN 0x00400000 /* * XXX These properties have the minimum values required by the architecture. * TODO Per-family/model event availability database. */ #define PMU_AMD_NR_PMCS 4 #define PMU_AMD_PMC_WIDTH 48 /* * Global PMU properties. * * The bitmap is used to implement counter allocation, where each bit denotes * whether a counter is available or not. */ struct pmu_amd { unsigned int pmc_bm; }; static struct pmu_amd pmu_amd; struct pmu_amd_event_code { unsigned short event_select; unsigned short umask; }; /* * TODO Per-family/model event availability database. */ static const struct pmu_amd_event_code pmu_amd_event_codes[] = { [PMU_AMD_RE_CYCLE] = { 0x76, 0x00 }, [PMU_AMD_RE_INSTRUCTION] = { 0xc0, 0x00 }, [PMU_AMD_RE_CACHE_REF] = { 0x80, 0x00 }, [PMU_AMD_RE_CACHE_MISS] = { 0x81, 0x00 }, [PMU_AMD_RE_BRANCH] = { 0xc2, 0x00 }, [PMU_AMD_RE_BRANCH_MISS] = { 0xc3, 0x00 }, [PMU_AMD_RE_DCACHE_REF] = { 0x40, 0x00 }, [PMU_AMD_RE_DCACHE_MISS] = { 0x41, 0x00 }, [PMU_AMD_RE_IFETCH_STALL] = { 0x87, 0x00 }, }; static const unsigned int pmu_amd_generic_events[] = { [PERFMON_EV_CYCLE] = PMU_AMD_RE_CYCLE, [PERFMON_EV_REF_CYCLE] = PMU_AMD_RE_INVALID, [PERFMON_EV_INSTRUCTION] = PMU_AMD_RE_INSTRUCTION, [PERFMON_EV_CACHE_REF] = PMU_AMD_RE_CACHE_REF, [PERFMON_EV_CACHE_MISS] = PMU_AMD_RE_CACHE_MISS, [PERFMON_EV_BRANCH] = PMU_AMD_RE_BRANCH, [PERFMON_EV_BRANCH_MISS] = PMU_AMD_RE_BRANCH_MISS, }; static struct pmu_amd * pmu_amd_get(void) { return &pmu_amd; } static void pmu_amd_info(void) { log_info("pmu: driver: amd, nr_pmcs: %u, pmc_width: %u\n", PMU_AMD_NR_PMCS, PMU_AMD_PMC_WIDTH); } static int pmu_amd_translate(unsigned int *raw_event_idp, unsigned int event_id) { assert(event_id < ARRAY_SIZE(pmu_amd_generic_events)); *raw_event_idp = pmu_amd_generic_events[event_id]; return 0; } static int pmu_amd_alloc(unsigned int *pmc_idp, unsigned int raw_event_id) { struct pmu_amd *pmu; unsigned int pmc_id; /* TODO Per-family/model event availability database */ (void)raw_event_id; pmu = pmu_amd_get(); if (pmu->pmc_bm == 0) { return EAGAIN; } pmc_id = __builtin_ffs(pmu->pmc_bm) - 1; pmu->pmc_bm &= ~(1U << pmc_id); *pmc_idp = pmc_id; return 0; } static void pmu_amd_free(unsigned int pmc_id) { struct pmu_amd *pmu; unsigned int mask; assert(pmc_id < PMU_AMD_NR_PMCS); pmu = pmu_amd_get(); mask = (1U << pmc_id); assert(!(pmu->pmc_bm & mask)); pmu->pmc_bm |= mask; } static void pmu_amd_start(unsigned int pmc_id, unsigned int raw_event_id) { const struct pmu_amd_event_code *code; uint32_t high, low; assert(pmc_id < PMU_AMD_NR_PMCS); assert(raw_event_id < ARRAY_SIZE(pmu_amd_event_codes)); code = &pmu_amd_event_codes[raw_event_id]; /* TODO Handle PERFMON_EF_KERN/PERFMON_EF_USER */ high = code->event_select >> 8; low = PMU_AMD_EVTSEL_EN | PMU_AMD_EVTSEL_INT | PMU_AMD_EVTSEL_OS | PMU_AMD_EVTSEL_USR | (code->umask << 8) | (code->event_select & 0xff); cpu_set_msr(PMU_AMD_MSR_PERFEVTSEL0 + pmc_id, high, low); } static void pmu_amd_stop(unsigned int pmc_id) { assert(pmc_id < PMU_AMD_NR_PMCS); cpu_set_msr(PMU_AMD_MSR_PERFEVTSEL0 + pmc_id, 0, 0); } static uint64_t pmu_amd_read(unsigned int pmc_id) { assert(pmc_id < PMU_AMD_NR_PMCS); return cpu_get_msr64(PMU_AMD_MSR_PERCTR0 + pmc_id); } static void pmu_amd_write(unsigned int pmc_id, uint64_t value) { cpu_set_msr64(PMU_AMD_MSR_PERCTR0 + pmc_id, value); } /* * TODO Make the perfmon module handle basic overflow handling by polling * counters. */ static void pmu_amd_handle_of_intr_v1(void) { struct pmu_amd *pmu; uint64_t value, prev; unsigned int mask; pmu = pmu_amd_get(); for (unsigned int pmc_id = 0; pmc_id != PMU_AMD_NR_PMCS; pmc_id++) { mask = (1U << pmc_id); if (pmu->pmc_bm & mask) { continue; } value = pmu_amd_read(pmc_id); prev = perfmon_cpu_pmc_get_prev(pmc_id); if (prev > value) { /* Overflow */ perfmon_cpu_pmc_inc_of(pmc_id); /* Prevents us from overflowing twice */ perfmon_cpu_pmc_set_prev(pmc_id, value); } } } static uint8_t pmu_amd_get_pmc_width(void) { return PMU_AMD_PMC_WIDTH; } static int __init pmu_amd_setup(void) { const struct cpu *cpu; struct pmu_amd *pmu; struct perfmon_pmu_ops pmu_driver; cpu = cpu_current(); if (cpu->vendor_id != CPU_VENDOR_AMD) { return 0; } /* Support AMD Family 10h processors and later */ if (cpu->family < 16) { return ENODEV; } pmu = pmu_amd_get(); pmu->pmc_bm = (1U << PMU_AMD_NR_PMCS) - 1; pmu_driver.info = pmu_amd_info; pmu_driver.translate = pmu_amd_translate; pmu_driver.alloc = pmu_amd_alloc; pmu_driver.free = pmu_amd_free; pmu_driver.start = pmu_amd_start; pmu_driver.stop = pmu_amd_stop; pmu_driver.read = pmu_amd_read; pmu_driver.write = pmu_amd_write; pmu_driver.get_pmc_width = pmu_amd_get_pmc_width; pmu_driver.handle_of_intr = pmu_amd_handle_of_intr_v1; return perfmon_pmu_register(&pmu_driver); } INIT_OP_DEFINE(pmu_amd_setup, INIT_OP_DEP(perfmon_bootstrap, true), INIT_OP_DEP(cpu_setup, true), INIT_OP_DEP(log_setup, true));