xref: /freebsd/sys/dev/hwpmc/hwpmc_perf.c (revision a259b98fa211ed87bfee58c575de4e2de94ee0fa)
1 /*
2  * Copyright (c) 2026 Advanced Micro Devices, Inc.
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  *
6  * AMD/Intel MPERF and APERF MSRs counters exposed as an hwpmc(4) PMC class.
7  *
8  * Read-only, system-scope (PMC_MODE_SC), 64-bit counters reporting
9  * MPERF and APERF MSR values.
10  */
11 
12 #include <sys/param.h>
13 #include <sys/pmc.h>
14 #include <sys/pmckern.h>
15 #include <sys/priv.h>
16 
17 #include <machine/specialreg.h>
18 
19 #define PERF_CAPS	PMC_CAP_READ
20 
21 struct perf_descr {
22 	struct pmc_descr pm_descr;  /* "base class" */
23 };
24 
25 static const struct perf_descr perf_pmcdesc[PERF_NPMCS] = {
26 	{
27 		.pm_descr = {
28 			.pd_name  = "MPERF",
29 			.pd_class = PMC_CLASS_PERF,
30 			.pd_caps  = PERF_CAPS,
31 			.pd_width = 64
32 		},
33 	},
34 	{
35 		.pm_descr = {
36 			.pd_name  = "APERF",
37 			.pd_class = PMC_CLASS_PERF,
38 			.pd_caps  = PERF_CAPS,
39 			.pd_width = 64
40 		}
41 	}
42 };
43 
44 struct perf_cpu {
45 	struct pmc_hw tc_hw[PERF_NPMCS];
46 };
47 
48 static struct perf_cpu **perf_pcpu;
49 static int perf_classindex;
50 
51 static int
52 perf_allocate_pmc(int cpu __diagused, int ri __diagused,
53 	    struct pmc *pm __unused, const struct pmc_op_pmcallocate *a)
54 {
55 	KASSERT(cpu >= 0 && cpu < pmc_cpu_max(),
56 	    ("[perf,%d] illegal CPU value %d", __LINE__, cpu));
57 	KASSERT(ri >= 0 && ri < PERF_NPMCS,
58 	    ("[perf,%d] illegal row index %d", __LINE__, ri));
59 
60 	if (a->pm_class != PMC_CLASS_PERF)
61 		return (EINVAL);
62 
63 	if ((a->pm_ev < PMC_EV_PERF_FIRST || a->pm_ev > PMC_EV_PERF_LAST) ||
64 	    a->pm_mode != PMC_MODE_SC)
65 		return (EINVAL);
66 
67 	/*
68 	 * Allows the PERF class to be accessed only by privileged users.
69 	 * Frequency is an indirect power proxy and could be abused by
70 	 * attacks like Hertzbleed.
71 	 */
72 
73 	if (priv_check(curthread, PRIV_PMC_SYSTEM) != 0)
74 		return (EPERM);
75 
76 	if ((a->pm_caps & PERF_CAPS) == 0)
77 		return (EINVAL);
78 	if ((a->pm_caps & ~PERF_CAPS) != 0)
79 		return (EPERM);
80 
81 	switch (ri) {
82 	case PERF_MPERF:
83 		if (a->pm_ev != PMC_EV_PERF_MPERF)
84 			return (EINVAL);
85 		break;
86 	case PERF_APERF:
87 		if (a->pm_ev != PMC_EV_PERF_APERF)
88 			return (EINVAL);
89 		break;
90 	default:
91 		return (EINVAL);
92 	}
93 
94 	return (0);
95 }
96 
97 static int
98 perf_config_pmc(int cpu, int ri, struct pmc *pm)
99 {
100 	struct pmc_hw *phw;
101 
102 	PMCDBG3(MDP, CFG, 1, "cpu=%d ri=%d pm=%p", cpu, ri, pm);
103 
104 	KASSERT(cpu >= 0 && cpu < pmc_cpu_max(),
105 	    ("[perf,%d] illegal CPU value %d", __LINE__, cpu));
106 	KASSERT(ri >= 0 && ri < PERF_NPMCS, ("[perf,%d] illegal row-index %d",
107 	    __LINE__, ri));
108 
109 	phw = &perf_pcpu[cpu]->tc_hw[ri];
110 
111 	KASSERT(pm == NULL || phw->phw_pmc == NULL,
112 	    ("[perf,%d] pm=%p phw->pm=%p hwpmc not unconfigured", __LINE__,
113 	    pm, phw->phw_pmc));
114 
115 	phw->phw_pmc = pm;
116 
117 	return (0);
118 }
119 
120 static int
121 perf_describe(int cpu, int ri, struct pmc_info *pi, struct pmc **ppmc)
122 {
123 	const struct perf_descr *pd;
124 	struct pmc_hw *phw;
125 
126 	KASSERT(cpu >= 0 && cpu < pmc_cpu_max(),
127 	    ("[perf,%d] illegal CPU %d", __LINE__, cpu));
128 	KASSERT(ri >= 0 && ri < PERF_NPMCS, ("[perf,%d] illegal row-index %d",
129 	    __LINE__, ri));
130 
131 	phw = &perf_pcpu[cpu]->tc_hw[ri];
132 	pd  = &perf_pmcdesc[ri];
133 
134 	strlcpy(pi->pm_name, pd->pm_descr.pd_name, sizeof(pi->pm_name));
135 	pi->pm_class = pd->pm_descr.pd_class;
136 
137 	if (phw->phw_state & PMC_PHW_FLAG_IS_ENABLED) {
138 		pi->pm_enabled = TRUE;
139 		*ppmc = phw->phw_pmc;
140 	} else {
141 		pi->pm_enabled = FALSE;
142 		*ppmc = NULL;
143 	}
144 
145 	return (0);
146 }
147 
148 static int
149 perf_get_config(int cpu, int ri, struct pmc **ppm)
150 {
151 	KASSERT(cpu >= 0 && cpu < pmc_cpu_max(),
152 	    ("[perf,%d] illegal CPU %d", __LINE__, cpu));
153 	KASSERT(ri >= 0 && ri < PERF_NPMCS, ("[perf,%d] illegal row-index %d",
154 	    __LINE__, ri));
155 
156 	*ppm = perf_pcpu[cpu]->tc_hw[ri].phw_pmc;
157 
158 	return (0);
159 }
160 
161 static int
162 perf_get_msr(int ri __diagused, uint32_t *msr __unused)
163 {
164 	KASSERT(ri >= 0 && ri < PERF_NPMCS,
165 	    ("[perf,%d] ri %d out of range", __LINE__, ri));
166 
167 	return (EINVAL);
168 }
169 
170 static int
171 perf_pcpu_fini(struct pmc_mdep *md, int cpu)
172 {
173 	int i, ri;
174 	struct pmc_cpu *pc;
175 
176 	KASSERT(cpu >= 0 && cpu < pmc_cpu_max(),
177 	    ("[perf,%d] illegal cpu %d", __LINE__, cpu));
178 	KASSERT(perf_pcpu[cpu] != NULL, ("[perf,%d] null pcpu", __LINE__));
179 
180 	free(perf_pcpu[cpu], M_PMC);
181 	perf_pcpu[cpu] = NULL;
182 
183 	ri = md->pmd_classdep[perf_classindex].pcd_ri;
184 	pc = pmc_pcpu[cpu];
185 
186 	for (i = 0; i < PERF_NPMCS; i++) {
187 		pc->pc_hwpmcs[i + ri] = NULL;
188 	}
189 
190 	return (0);
191 }
192 
193 static int
194 perf_pcpu_init(struct pmc_mdep *md, int cpu)
195 {
196 	int i, ri;
197 	struct pmc_cpu *pc;
198 	struct perf_cpu *perf_pc;
199 
200 	KASSERT(cpu >= 0 && cpu < pmc_cpu_max(),
201 	    ("[perf,%d] illegal cpu %d", __LINE__, cpu));
202 	KASSERT(perf_pcpu, ("[perf,%d] null pcpu", __LINE__));
203 	KASSERT(perf_pcpu[cpu] == NULL, ("[perf,%d] non-null per-cpu",
204 	    __LINE__));
205 
206 	perf_pc = malloc(sizeof(struct perf_cpu), M_PMC, M_WAITOK | M_ZERO);
207 
208 	perf_pcpu[cpu] = perf_pc;
209 
210 	ri = md->pmd_classdep[perf_classindex].pcd_ri;
211 
212 	KASSERT(pmc_pcpu, ("[perf,%d] null generic pcpu", __LINE__));
213 
214 	pc = pmc_pcpu[cpu];
215 
216 	KASSERT(pc, ("[perf,%d] null generic per-cpu", __LINE__));
217 
218 	for (i = 0; i < PERF_NPMCS; i++) {
219 		perf_pc->tc_hw[i].phw_state = PMC_PHW_FLAG_IS_ENABLED |
220 			PMC_PHW_CPU_TO_STATE(cpu) | PMC_PHW_INDEX_TO_STATE(i) |
221 			PMC_PHW_FLAG_IS_SHAREABLE;
222 		pc->pc_hwpmcs[i + ri] = &perf_pc->tc_hw[i];
223 	}
224 
225 	return (0);
226 }
227 
228 static int
229 perf_read_pmc(int cpu __diagused, int ri, struct pmc *pm, pmc_value_t *v)
230 {
231 	enum pmc_mode mode __diagused;
232 
233 	KASSERT(cpu >= 0 && cpu < pmc_cpu_max(),
234 	    ("[perf,%d] illegal CPU value %d", __LINE__, cpu));
235 	KASSERT(ri >= 0 && ri < PERF_NPMCS, ("[perf,%d] illegal ri %d",
236 	    __LINE__, ri));
237 
238 	mode = PMC_TO_MODE(pm);
239 
240 	KASSERT(mode == PMC_MODE_SC,
241 		("[perf,%d] illegal pmc mode %d", __LINE__, mode));
242 
243 	PMCDBG1(MDP, REA, 1, "perf-read id=%d", ri);
244 
245 	switch (ri) {
246 	case PERF_MPERF:
247 		*v = rdmsr(MSR_MPERF);
248 		break;
249 	case PERF_APERF:
250 		*v = rdmsr(MSR_APERF);
251 		break;
252 	default:
253 		return (EINVAL);
254 	}
255 
256 	return (0);
257 }
258 
259 static int
260 perf_write_pmc(int cpu __diagused, int ri __diagused, struct pmc *pm __unused,
261 	    pmc_value_t v __unused)
262 {
263 	KASSERT(cpu >= 0 && cpu < pmc_cpu_max(),
264 	    ("[perf,%d] illegal CPU value %d", __LINE__, cpu));
265 	KASSERT(ri >= 0 && ri < PERF_NPMCS, ("[perf,%d] illegal row-index %d",
266 	    __LINE__, ri));
267 
268 	return (0);
269 }
270 
271 static int
272 perf_release_pmc(int cpu __diagused, int ri __diagused, struct pmc *pmc __unused)
273 {
274 	KASSERT(cpu >= 0 && cpu < pmc_cpu_max(),
275 	    ("[perf,%d] illegal CPU value %d", __LINE__, cpu));
276 	KASSERT(ri >= 0 && ri < PERF_NPMCS,
277 	    ("[perf,%d] illegal row-index %d", __LINE__, ri));
278 	KASSERT(perf_pcpu[cpu]->tc_hw[ri].phw_pmc == NULL,
279 	   ("[perf,%d] PHW pmc non-NULL", __LINE__));
280 
281 	return (0);
282 }
283 
284 static int
285 perf_start_pmc(int cpu __diagused, int ri __diagused, struct pmc *pm __unused)
286 {
287 	KASSERT(cpu >= 0 && cpu < pmc_cpu_max(),
288 	    ("[perf,%d] illegal CPU value %d", __LINE__, cpu));
289 	KASSERT(ri >= 0 && ri < PERF_NPMCS, ("[perf,%d] illegal row-index %d",
290 	    __LINE__, ri));
291 
292 	return (0);
293 }
294 
295 static int
296 perf_stop_pmc(int cpu __diagused, int ri __diagused, struct pmc *pm __unused)
297 {
298 	KASSERT(cpu >= 0 && cpu < pmc_cpu_max(),
299 	    ("[perf,%d] illegal CPU value %d", __LINE__, cpu));
300 	KASSERT(ri >= 0 && ri < PERF_NPMCS, ("[perf,%d] illegal row-index %d",
301 	    __LINE__, ri));
302 
303 	return (0);
304 }
305 
306 int
307 pmc_perf_initialize(struct pmc_mdep *md, int maxcpu, int classindex)
308 {
309 	struct pmc_classdep *pcd;
310 
311 	KASSERT(md != NULL, ("[perf,%d] md is NULL", __LINE__));
312 	KASSERT(md->pmd_nclass >= 1, ("[perf,%d] dubious md->nclass %d",
313 	    __LINE__, md->pmd_nclass));
314 
315 	if ((cpu_power_ecx & CPUID_PERF_STAT) && (tsc_perf_stat == 1)) {
316 
317 		perf_pcpu = malloc(sizeof(struct perf_cpu *) * maxcpu, M_PMC,
318 			M_ZERO | M_WAITOK);
319 
320 		perf_classindex = classindex;
321 		pcd = &md->pmd_classdep[classindex];
322 
323 		pcd->pcd_caps   = PMC_CAP_READ;
324 		pcd->pcd_class  = PMC_CLASS_PERF;
325 		pcd->pcd_num    = PERF_NPMCS;
326 		pcd->pcd_ri	= md->pmd_npmc;
327 		pcd->pcd_width  = 64;
328 
329 		pcd->pcd_allocate_pmc	= perf_allocate_pmc;
330 		pcd->pcd_config_pmc	= perf_config_pmc;
331 		pcd->pcd_describe	= perf_describe;
332 		pcd->pcd_get_config	= perf_get_config;
333 		pcd->pcd_get_msr	= perf_get_msr;
334 		pcd->pcd_pcpu_init	= perf_pcpu_init;
335 		pcd->pcd_pcpu_fini	= perf_pcpu_fini;
336 		pcd->pcd_read_pmc	= perf_read_pmc;
337 		pcd->pcd_write_pmc	= perf_write_pmc;
338 		pcd->pcd_release_pmc	= perf_release_pmc;
339 		pcd->pcd_start_pmc	= perf_start_pmc;
340 		pcd->pcd_stop_pmc	= perf_stop_pmc;
341 
342 		md->pmd_npmc += PERF_NPMCS;
343 	}
344 
345 	return (0);
346 }
347 
348 void pmc_perf_finalize(struct pmc_mdep *md)
349 {
350 	PMCDBG0(MDP, INI, 1, "perf-finalize");
351 
352 	if (perf_pcpu != NULL) {
353 		for (int i = 0; i < pmc_cpu_max(); i++)
354 			KASSERT(perf_pcpu[i] == NULL,
355 			    ("[perf,%d] non-null pcpu cpu %d", __LINE__, i));
356 
357 		free(perf_pcpu, M_PMC);
358 		perf_pcpu = NULL;
359 	}
360 }
361