acpi_cppc.c revision 1.3 1 /* $NetBSD: acpi_cppc.c,v 1.3 2025/01/31 12:29:19 jmcneill Exp $ */
2
3 /*-
4 * Copyright (c) 2020 Jared McNeill <jmcneill (at) invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 /*
30 * ACPI Collaborative Processor Performance Control support.
31 */
32
33 #include <sys/cdefs.h>
34 __KERNEL_RCSID(0, "$NetBSD: acpi_cppc.c,v 1.3 2025/01/31 12:29:19 jmcneill Exp $");
35
36 #include <sys/param.h>
37 #include <sys/bus.h>
38 #include <sys/cpu.h>
39 #include <sys/device.h>
40 #include <sys/kmem.h>
41 #include <sys/sysctl.h>
42
43 #include <dev/acpi/acpireg.h>
44 #include <dev/acpi/acpivar.h>
45 #include <dev/acpi/acpi_pcc.h>
46
47 #include <external/bsd/acpica/dist/include/amlresrc.h>
48
49 /* _CPC package elements */
50 typedef enum CPCPackageElement {
51 CPCNumEntries,
52 CPCRevision,
53 CPCHighestPerformance,
54 CPCNominalPerformance,
55 CPCLowestNonlinearPerformance,
56 CPCLowestPerformance,
57 CPCGuaranteedPerformanceReg,
58 CPCDesiredPerformanceReg,
59 CPCMinimumPerformanceReg,
60 CPCMaximumPerformanceReg,
61 CPCPerformanceReductionToleranceReg,
62 CPCTimeWindowReg,
63 CPCCounterWraparoundTime,
64 CPCReferencePerformanceCounterReg,
65 CPCDeliveredPerformanceCounterReg,
66 CPCPerformanceLimitedReg,
67 CPCCPPCEnableReg,
68 CPCAutonomousSelectionEnable,
69 CPCAutonomousActivityWindowReg,
70 CPCEnergyPerformancePreferenceReg,
71 CPCReferencePerformance,
72 CPCLowestFrequency,
73 CPCNominalFrequency,
74 } CPCPackageElement;
75
76 /* PCC command numbers */
77 #define CPPC_PCC_READ 0x00
78 #define CPPC_PCC_WRITE 0x01
79
80 struct cppc_softc {
81 device_t sc_dev;
82 struct cpu_info * sc_cpuinfo;
83 ACPI_HANDLE sc_handle;
84 ACPI_OBJECT * sc_cpc;
85 u_int sc_ncpc;
86
87 char * sc_available;
88 int sc_node_target;
89 int sc_node_current;
90 ACPI_INTEGER sc_max_target;
91 ACPI_INTEGER sc_min_target;
92 u_int sc_freq_range;
93 u_int sc_perf_range;
94 };
95
96 static int cppc_match(device_t, cfdata_t, void *);
97 static void cppc_attach(device_t, device_t, void *);
98
99 static ACPI_STATUS cppc_parse_cpc(struct cppc_softc *);
100 static ACPI_STATUS cppc_cpufreq_init(struct cppc_softc *);
101 static ACPI_STATUS cppc_read(struct cppc_softc *, CPCPackageElement,
102 ACPI_INTEGER *);
103 static ACPI_STATUS cppc_write(struct cppc_softc *, CPCPackageElement,
104 ACPI_INTEGER);
105
106 CFATTACH_DECL_NEW(acpicppc, sizeof(struct cppc_softc),
107 cppc_match, cppc_attach, NULL, NULL);
108
109 static const struct device_compatible_entry compat_data[] = {
110 { .compat = "ACPI0007" }, /* ACPI Processor Device */
111 DEVICE_COMPAT_EOL
112 };
113
114 static int
115 cppc_match(device_t parent, cfdata_t cf, void *aux)
116 {
117 struct acpi_attach_args * const aa = aux;
118 ACPI_HANDLE handle;
119 ACPI_STATUS rv;
120
121 if (acpi_compatible_match(aa, compat_data) == 0)
122 return 0;
123
124 rv = AcpiGetHandle(aa->aa_node->ad_handle, "_CPC", &handle);
125 if (ACPI_FAILURE(rv)) {
126 return 0;
127 }
128
129 if (acpi_match_cpu_handle(aa->aa_node->ad_handle) == NULL) {
130 return 0;
131 }
132
133 /* When CPPC and P-states/T-states are both available, prefer CPPC */
134 return ACPI_MATCHSCORE_CID_MAX + 1;
135 }
136
137 static void
138 cppc_attach(device_t parent, device_t self, void *aux)
139 {
140 struct cppc_softc * const sc = device_private(self);
141 struct acpi_attach_args * const aa = aux;
142 ACPI_HANDLE handle = aa->aa_node->ad_handle;
143 struct cpu_info *ci;
144 ACPI_STATUS rv;
145
146 ci = acpi_match_cpu_handle(handle);
147 KASSERT(ci != NULL);
148
149 aprint_naive("\n");
150 aprint_normal(": Processor Performance Control (%s)\n", cpu_name(ci));
151
152 sc->sc_dev = self;
153 sc->sc_cpuinfo = ci;
154 sc->sc_handle = handle;
155
156 rv = cppc_parse_cpc(sc);
157 if (ACPI_FAILURE(rv)) {
158 aprint_error_dev(self, "failed to parse CPC package: %s\n",
159 AcpiFormatException(rv));
160 return;
161 }
162
163 cppc_cpufreq_init(sc);
164 }
165
166 /*
167 * cppc_parse_cpc --
168 *
169 * Read and verify the contents of the _CPC package.
170 */
171 static ACPI_STATUS
172 cppc_parse_cpc(struct cppc_softc *sc)
173 {
174 ACPI_BUFFER buf;
175 ACPI_STATUS rv;
176
177 buf.Pointer = NULL;
178 buf.Length = ACPI_ALLOCATE_BUFFER;
179 rv = AcpiEvaluateObjectTyped(sc->sc_handle, "_CPC", NULL, &buf,
180 ACPI_TYPE_PACKAGE);
181 if (ACPI_FAILURE(rv)) {
182 return rv;
183 }
184
185 sc->sc_cpc = (ACPI_OBJECT *)buf.Pointer;
186 if (sc->sc_cpc->Package.Count == 0) {
187 return AE_NOT_EXIST;
188 }
189 if (sc->sc_cpc->Package.Elements[CPCNumEntries].Type !=
190 ACPI_TYPE_INTEGER) {
191 return AE_TYPE;
192 }
193 sc->sc_ncpc =
194 sc->sc_cpc->Package.Elements[CPCNumEntries].Integer.Value;
195
196 return AE_OK;
197 }
198
199 /*
200 * cppc_perf_to_freq, cppc_freq_to_perf --
201 *
202 * Convert between abstract performance values and CPU frequencies,
203 * when possible.
204 */
205 static ACPI_INTEGER
206 cppc_perf_to_freq(struct cppc_softc *sc, ACPI_INTEGER perf)
207 {
208 return howmany(perf * sc->sc_freq_range, sc->sc_perf_range);
209 }
210 static ACPI_INTEGER
211 cppc_freq_to_perf(struct cppc_softc *sc, ACPI_INTEGER freq)
212 {
213 return howmany(freq * sc->sc_perf_range, sc->sc_freq_range);
214 }
215
216 /*
217 * cppc_cpufreq_sysctl --
218 *
219 * sysctl helper function for machdep.cpu.cpuN.{target,current}
220 * nodes.
221 */
222 static int
223 cppc_cpufreq_sysctl(SYSCTLFN_ARGS)
224 {
225 struct cppc_softc * const sc = rnode->sysctl_data;
226 struct sysctlnode node;
227 u_int fq, oldfq = 0;
228 ACPI_INTEGER val;
229 ACPI_STATUS rv;
230 int error;
231
232 node = *rnode;
233 node.sysctl_data = &fq;
234
235 if (rnode->sysctl_num == sc->sc_node_target) {
236 rv = cppc_read(sc, CPCDesiredPerformanceReg, &val);
237 } else {
238 /*
239 * XXX We should measure the delivered performance and
240 * report it here. For now, just report the desired
241 * performance level.
242 */
243 rv = cppc_read(sc, CPCDesiredPerformanceReg, &val);
244 }
245 if (ACPI_FAILURE(rv)) {
246 return EIO;
247 }
248 if (val > UINT32_MAX) {
249 return ERANGE;
250 }
251 fq = (u_int)cppc_perf_to_freq(sc, val);
252
253 if (rnode->sysctl_num == sc->sc_node_target) {
254 oldfq = fq;
255 }
256
257 error = sysctl_lookup(SYSCTLFN_CALL(&node));
258 if (error != 0 || newp == NULL) {
259 return error;
260 }
261
262 if (fq == oldfq || rnode->sysctl_num != sc->sc_node_target) {
263 return 0;
264 }
265
266 if (fq < sc->sc_min_target || fq > sc->sc_max_target) {
267 return EINVAL;
268 }
269
270 rv = cppc_write(sc, CPCDesiredPerformanceReg,
271 cppc_freq_to_perf(sc, fq));
272 if (ACPI_FAILURE(rv)) {
273 return EIO;
274 }
275
276 return 0;
277 }
278
279 /*
280 * cppc_cpufreq_init --
281 *
282 * Create sysctl machdep.cpu.cpuN.* sysctl tree.
283 */
284 static ACPI_STATUS
285 cppc_cpufreq_init(struct cppc_softc *sc)
286 {
287 static CPCPackageElement perf_regs[4] = {
288 CPCHighestPerformance,
289 CPCNominalPerformance,
290 CPCLowestNonlinearPerformance,
291 CPCLowestPerformance
292 };
293 ACPI_INTEGER perf[4], min_freq = 0, nom_freq = 0, last;
294 const struct sysctlnode *node, *cpunode;
295 struct sysctllog *log = NULL;
296 struct cpu_info *ci = sc->sc_cpuinfo;
297 ACPI_STATUS rv;
298 int error, i, n;
299
300 /*
301 * Read optional nominal and lowest frequencies. These are used,
302 * when present, to scale units for display in the sysctl interface.
303 */
304 cppc_read(sc, CPCLowestFrequency, &min_freq);
305 cppc_read(sc, CPCNominalFrequency, &nom_freq);
306 /*
307 * Read highest, nominal, lowest nonlinear, and lowest performance
308 * levels.
309 */
310 for (i = 0, n = 0; i < __arraycount(perf_regs); i++) {
311 rv = cppc_read(sc, perf_regs[i], &perf[i]);
312 if (ACPI_FAILURE(rv)) {
313 return rv;
314 }
315 }
316 if (min_freq && nom_freq) {
317 sc->sc_freq_range = nom_freq - min_freq;
318 sc->sc_perf_range = perf[1] - perf[3];
319 } else {
320 sc->sc_freq_range = 1;
321 sc->sc_perf_range = 1;
322 }
323
324 /*
325 * Build a list of performance levels for the
326 * machdep.cpufreq.cpuN.available sysctl.
327 */
328 sc->sc_available = kmem_zalloc(
329 strlen("########## ") * __arraycount(perf_regs), KM_SLEEP);
330 last = 0;
331 for (i = 0, n = 0; i < __arraycount(perf_regs); i++) {
332 rv = cppc_read(sc, perf_regs[i], &perf[i]);
333 if (ACPI_FAILURE(rv)) {
334 return rv;
335 }
336 if (perf[i] != last) {
337 char buf[12];
338 snprintf(buf, sizeof(buf), n ? " %u" : "%u",
339 (u_int)cppc_perf_to_freq(sc, perf[i]));
340 strcat(sc->sc_available, buf);
341 last = perf[i];
342 n++;
343 }
344 }
345 sc->sc_max_target = cppc_perf_to_freq(sc, perf[0]);
346 sc->sc_min_target = cppc_perf_to_freq(sc, perf[3]);
347
348 error = sysctl_createv(&log, 0, NULL, &node,
349 CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL,
350 NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL);
351 if (error != 0) {
352 goto sysctl_failed;
353 }
354
355 error = sysctl_createv(&log, 0, &node, &node,
356 0, CTLTYPE_NODE, "cpufreq", NULL,
357 NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
358 if (error != 0) {
359 goto sysctl_failed;
360 }
361
362 error = sysctl_createv(&log, 0, &node, &cpunode,
363 0, CTLTYPE_NODE, cpu_name(ci), NULL,
364 NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
365 if (error != 0) {
366 goto sysctl_failed;
367 }
368
369 error = sysctl_createv(&log, 0, &cpunode, &node,
370 CTLFLAG_READWRITE, CTLTYPE_INT, "target", NULL,
371 cppc_cpufreq_sysctl, 0, (void *)sc, 0,
372 CTL_CREATE, CTL_EOL);
373 if (error != 0) {
374 goto sysctl_failed;
375 }
376 sc->sc_node_target = node->sysctl_num;
377
378 error = sysctl_createv(&log, 0, &cpunode, &node,
379 CTLFLAG_READONLY, CTLTYPE_INT, "current", NULL,
380 cppc_cpufreq_sysctl, 0, (void *)sc, 0,
381 CTL_CREATE, CTL_EOL);
382 if (error != 0) {
383 goto sysctl_failed;
384 }
385 sc->sc_node_current = node->sysctl_num;
386
387 error = sysctl_createv(&log, 0, &cpunode, &node,
388 CTLFLAG_READONLY, CTLTYPE_STRING, "available", NULL,
389 NULL, 0, sc->sc_available, 0,
390 CTL_CREATE, CTL_EOL);
391 if (error != 0) {
392 goto sysctl_failed;
393 }
394
395 return AE_OK;
396
397 sysctl_failed:
398 aprint_error_dev(sc->sc_dev, "couldn't create sysctl nodes: %d\n",
399 error);
400 sysctl_teardown(&log);
401
402 return AE_ERROR;
403 }
404
405 /*
406 * cppc_read --
407 *
408 * Read a value from the CPC package that contains either an integer
409 * or indirect register reference.
410 */
411 static ACPI_STATUS
412 cppc_read(struct cppc_softc *sc, CPCPackageElement index, ACPI_INTEGER *val)
413 {
414 ACPI_OBJECT *obj;
415 ACPI_GENERIC_ADDRESS addr;
416 ACPI_STATUS rv;
417
418 if (index >= sc->sc_ncpc) {
419 return AE_NOT_EXIST;
420 }
421
422 obj = &sc->sc_cpc->Package.Elements[index];
423 switch (obj->Type) {
424 case ACPI_TYPE_INTEGER:
425 *val = obj->Integer.Value;
426 return AE_OK;
427
428 case ACPI_TYPE_BUFFER:
429 if (obj->Buffer.Length <
430 sizeof(AML_RESOURCE_GENERIC_REGISTER)) {
431 return AE_TYPE;
432 }
433 memcpy(&addr, obj->Buffer.Pointer +
434 sizeof(AML_RESOURCE_LARGE_HEADER), sizeof(addr));
435 if (addr.SpaceId == ACPI_ADR_SPACE_PLATFORM_COMM) {
436 rv = pcc_message(&addr, CPPC_PCC_READ, PCC_READ, val);
437 } else {
438 rv = AcpiRead(val, &addr);
439 }
440 return rv;
441
442 default:
443 return AE_SUPPORT;
444 }
445 }
446
447 /*
448 * cppc_write --
449 *
450 * Write a value based on the CPC package to the specified register.
451 */
452 static ACPI_STATUS
453 cppc_write(struct cppc_softc *sc, CPCPackageElement index, ACPI_INTEGER val)
454 {
455 ACPI_OBJECT *obj;
456 ACPI_GENERIC_ADDRESS addr;
457 ACPI_STATUS rv;
458
459 if (index >= sc->sc_ncpc) {
460 return AE_NOT_EXIST;
461 }
462
463 obj = &sc->sc_cpc->Package.Elements[index];
464 if (obj->Type != ACPI_TYPE_BUFFER ||
465 obj->Buffer.Length < sizeof(AML_RESOURCE_GENERIC_REGISTER)) {
466 return AE_TYPE;
467 }
468
469 memcpy(&addr, obj->Buffer.Pointer +
470 sizeof(AML_RESOURCE_LARGE_HEADER), sizeof(addr));
471 if (addr.SpaceId == ACPI_ADR_SPACE_PLATFORM_COMM) {
472 rv = pcc_message(&addr, CPPC_PCC_WRITE, PCC_WRITE, &val);
473 } else {
474 rv = AcpiWrite(val, &addr);
475 }
476
477 return rv;
478 }
479