summaryrefslogtreecommitdiff
path: root/arch/x86/machine/pmu_amd.c
blob: 4d2a4f8a1af690aa208e8a052e20c13b2b20bc28 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
/*
 * 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 <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 <kern/perfmon.h>
#include <machine/cpu.h>
#include <machine/pmu.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_INT  0x00100000
#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 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);
}

#ifdef CONFIG_PERFMON_TEST

static void
pmu_amd_write(unsigned int pmc_id, uint64_t value)
{
    cpu_set_msr64(PMU_AMD_MSR_PERCTR0 + pmc_id, value);
}

#endif /* CONFIG_PERFMON_TEST */

static void
pmu_amd_handle_of_intr_v1(struct trap_frame *frame)
{
    struct pmu_amd *pmu;
    unsigned int mask;
    uint64_t value;
    uint64_t prev;

    (void)frame;

    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) {
            /* counter not enabled: can't overflow. */
            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.get_pmc_width = pmu_amd_get_pmc_width;
    pmu_driver.handle_of_intr = pmu_amd_handle_of_intr_v1;
#ifdef CONFIG_PERFMON_TEST
    pmu_driver.write = pmu_amd_write;
#endif

    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));