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@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
cppc_match(device_t parent,cfdata_t cf,void * aux)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
cppc_attach(device_t parent,device_t self,void * aux)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
cppc_parse_cpc(struct cppc_softc * sc)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
cppc_perf_to_freq(struct cppc_softc * sc,ACPI_INTEGER perf)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
cppc_freq_to_perf(struct cppc_softc * sc,ACPI_INTEGER freq)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
cppc_cpufreq_sysctl(SYSCTLFN_ARGS)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
cppc_cpufreq_init(struct cppc_softc * sc)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
cppc_read(struct cppc_softc * sc,CPCPackageElement index,ACPI_INTEGER * val)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
cppc_write(struct cppc_softc * sc,CPCPackageElement index,ACPI_INTEGER val)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