summaryrefslogtreecommitdiff
path: root/arch/x86/machine/pmu_amd.c
diff options
context:
space:
mode:
authorRemy Noel <mocramis@gmail.com>2018-01-20 11:41:39 +0100
committerRemy Noel <mocramis@gmail.com>2018-02-22 23:33:24 +0100
commit652168fe3d867eec17ac7fa318c8743d524ef40f (patch)
tree0d16e8f3f07c99f0f5b78c74794686651cde4aed /arch/x86/machine/pmu_amd.c
parente363294da50ad602791ceccae7182a7d799bf032 (diff)
perfmon: split pmu into amd and intel architectures.
pmu_driver handling is now perfmon's responsibility.
Diffstat (limited to 'arch/x86/machine/pmu_amd.c')
-rw-r--r--arch/x86/machine/pmu_amd.c231
1 files changed, 231 insertions, 0 deletions
diff --git a/arch/x86/machine/pmu_amd.c b/arch/x86/machine/pmu_amd.c
new file mode 100644
index 0000000..815f403
--- /dev/null
+++ b/arch/x86/machine/pmu_amd.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2014 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 <http://www.gnu.org/licenses/>.
+ *
+ * AMD PMU driver module.
+ */
+
+#include <stdint.h>
+
+#include <include/assert.h>
+#include <kern/error.h>
+#include <kern/init.h>
+#include <kern/log.h>
+#include <machine/cpu.h>
+#include <kern/perfmon.h>
+
+/*
+ * 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
+
+
+/*
+ * 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_EN 0x00400000
+
+/*
+ * AMD PMU properties seem to be identical across all processors despite
+ * many of them being implementation-specific.
+ */
+#define PMU_AMD_NR_PMCS 4
+#define PMU_AMD_PMC_WIDTH 48
+
+struct pmu_amd {
+ unsigned int pmc_bm;
+};
+
+struct pmu_amd_event_code {
+ unsigned short event_select;
+ unsigned short umask;
+};
+
+static struct pmu_amd pmu_amd;
+
+/*
+ * 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 },
+};
+
+#define PMU_AMD_RE_INVALID ((unsigned int)-1)
+
+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 Check raw event availability */
+ (void)raw_event_id;
+
+ pmu = pmu_amd_get();
+
+ if (pmu->pmc_bm == 0)
+ return ERROR_AGAIN;
+
+ 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_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)
+{
+ uint32_t high, low;
+
+ assert(pmc_id < PMU_AMD_NR_PMCS);
+
+ cpu_get_msr(PMU_AMD_MSR_PERCTR0 + pmc_id, &high, &low);
+ return (((uint64_t)high << 32) | low);
+}
+
+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 ERROR_NODEV;
+
+ 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;
+
+ 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));
+