acpi_cppc.c revision 1.1.2.2 1 /* $NetBSD: acpi_cppc.c,v 1.1.2.2 2020/12/14 14:38:05 thorpej 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.1.2.2 2020/12/14 14:38:05 thorpej 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 };
93
94 static int cppc_match(device_t, cfdata_t, void *);
95 static void cppc_attach(device_t, device_t, void *);
96
97 static ACPI_STATUS cppc_parse_cpc(struct cppc_softc *);
98 static ACPI_STATUS cppc_cpufreq_init(struct cppc_softc *);
99 static ACPI_STATUS cppc_read(struct cppc_softc *, CPCPackageElement,
100 ACPI_INTEGER *);
101 static ACPI_STATUS cppc_write(struct cppc_softc *, CPCPackageElement,
102 ACPI_INTEGER);
103
104 CFATTACH_DECL_NEW(acpicppc, sizeof(struct cppc_softc),
105 cppc_match, cppc_attach, NULL, NULL);
106
107 static const char * const compatible[] = {
108 "ACPI0007", /* ACPI Processor Device */
109 NULL
110 };
111
112 static int
113 cppc_match(device_t parent, cfdata_t cf, void *aux)
114 {
115 struct acpi_attach_args * const aa = aux;
116 ACPI_HANDLE handle;
117 ACPI_STATUS rv;
118
119 if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE) {
120 return 0;
121 }
122
123 if (acpi_match_hid(aa->aa_node->ad_devinfo, compatible) == 0) {
124 return 0;
125 }
126
127 rv = AcpiGetHandle(aa->aa_node->ad_handle, "_CPC", &handle);
128 if (ACPI_FAILURE(rv)) {
129 return 0;
130 }
131
132 if (acpi_match_cpu_handle(aa->aa_node->ad_handle) == NULL) {
133 return 0;
134 }
135
136 /* When CPPC and P-states/T-states are both available, prefer CPPC */
137 return 20;
138 }
139
140 static void
141 cppc_attach(device_t parent, device_t self, void *aux)
142 {
143 struct cppc_softc * const sc = device_private(self);
144 struct acpi_attach_args * const aa = aux;
145 ACPI_HANDLE handle = aa->aa_node->ad_handle;
146 struct cpu_info *ci;
147 ACPI_STATUS rv;
148
149 ci = acpi_match_cpu_handle(handle);
150 KASSERT(ci != NULL);
151
152 aprint_naive("\n");
153 aprint_normal(": Processor Performance Control (%s)\n", cpu_name(ci));
154
155 sc->sc_dev = self;
156 sc->sc_cpuinfo = ci;
157 sc->sc_handle = handle;
158
159 rv = cppc_parse_cpc(sc);
160 if (ACPI_FAILURE(rv)) {
161 aprint_error_dev(self, "failed to parse CPC package: %s\n",
162 AcpiFormatException(rv));
163 return;
164 }
165
166 cppc_cpufreq_init(sc);
167 }
168
169 /*
170 * cppc_parse_cpc --
171 *
172 * Read and verify the contents of the _CPC package.
173 */
174 static ACPI_STATUS
175 cppc_parse_cpc(struct cppc_softc *sc)
176 {
177 ACPI_BUFFER buf;
178 ACPI_STATUS rv;
179
180 buf.Pointer = NULL;
181 buf.Length = ACPI_ALLOCATE_BUFFER;
182 rv = AcpiEvaluateObjectTyped(sc->sc_handle, "_CPC", NULL, &buf,
183 ACPI_TYPE_PACKAGE);
184 if (ACPI_FAILURE(rv)) {
185 return rv;
186 }
187
188 sc->sc_cpc = (ACPI_OBJECT *)buf.Pointer;
189 if (sc->sc_cpc->Package.Count == 0) {
190 return AE_NOT_EXIST;
191 }
192 if (sc->sc_cpc->Package.Elements[CPCNumEntries].Type !=
193 ACPI_TYPE_INTEGER) {
194 return AE_TYPE;
195 }
196 sc->sc_ncpc =
197 sc->sc_cpc->Package.Elements[CPCNumEntries].Integer.Value;
198
199 return AE_OK;
200 }
201
202 /*
203 * cppc_cpufreq_sysctl --
204 *
205 * sysctl helper function for machdep.cpu.cpuN.{target,current}
206 * nodes.
207 */
208 static int
209 cppc_cpufreq_sysctl(SYSCTLFN_ARGS)
210 {
211 struct cppc_softc * const sc = rnode->sysctl_data;
212 struct sysctlnode node;
213 u_int fq, oldfq = 0;
214 ACPI_INTEGER val;
215 ACPI_STATUS rv;
216 int error;
217
218 node = *rnode;
219 node.sysctl_data = &fq;
220
221 if (rnode->sysctl_num == sc->sc_node_target) {
222 rv = cppc_read(sc, CPCDesiredPerformanceReg, &val);
223 } else {
224 /*
225 * XXX We should measure the delivered performance and
226 * report it here. For now, just report the desired
227 * performance level.
228 */
229 rv = cppc_read(sc, CPCDesiredPerformanceReg, &val);
230 }
231 if (ACPI_FAILURE(rv)) {
232 return EIO;
233 }
234 if (val > UINT32_MAX) {
235 return ERANGE;
236 }
237 fq = (u_int)val;
238
239 if (rnode->sysctl_num == sc->sc_node_target) {
240 oldfq = fq;
241 }
242
243 error = sysctl_lookup(SYSCTLFN_CALL(&node));
244 if (error != 0 || newp == NULL) {
245 return error;
246 }
247
248 if (fq == oldfq || rnode->sysctl_num != sc->sc_node_target) {
249 return 0;
250 }
251
252 if (fq < sc->sc_min_target || fq > sc->sc_max_target) {
253 return EINVAL;
254 }
255
256 rv = cppc_write(sc, CPCDesiredPerformanceReg, fq);
257 if (ACPI_FAILURE(rv)) {
258 return EIO;
259 }
260
261 return 0;
262 }
263
264 /*
265 * cppc_cpufreq_init --
266 *
267 * Create sysctl machdep.cpu.cpuN.* sysctl tree.
268 */
269 static ACPI_STATUS
270 cppc_cpufreq_init(struct cppc_softc *sc)
271 {
272 static CPCPackageElement perf_regs[4] = {
273 CPCHighestPerformance,
274 CPCNominalPerformance,
275 CPCLowestNonlinearPerformance,
276 CPCLowestPerformance
277 };
278 ACPI_INTEGER perf[4], last;
279 const struct sysctlnode *node, *cpunode;
280 struct sysctllog *log = NULL;
281 struct cpu_info *ci = sc->sc_cpuinfo;
282 ACPI_STATUS rv;
283 int error, i, n;
284
285 /*
286 * Read highest, nominal, lowest nonlinear, and lowest performance
287 * levels and advertise this list of performance levels in the
288 * machdep.cpufreq.cpuN.available sysctl.
289 */
290 sc->sc_available = kmem_zalloc(
291 strlen("########## ") * __arraycount(perf_regs), KM_SLEEP);
292 last = 0;
293 for (i = 0, n = 0; i < __arraycount(perf_regs); i++) {
294 rv = cppc_read(sc, perf_regs[i], &perf[i]);
295 if (ACPI_FAILURE(rv)) {
296 return rv;
297 }
298 if (perf[i] != last) {
299 char buf[12];
300 snprintf(buf, sizeof(buf), n ? " %u" : "%u",
301 (u_int)perf[i]);
302 strcat(sc->sc_available, buf);
303 last = perf[i];
304 n++;
305 }
306 }
307 sc->sc_max_target = perf[0];
308 sc->sc_min_target = perf[3];
309
310 error = sysctl_createv(&log, 0, NULL, &node,
311 CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL,
312 NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL);
313 if (error != 0) {
314 goto sysctl_failed;
315 }
316
317 error = sysctl_createv(&log, 0, &node, &node,
318 0, CTLTYPE_NODE, "cpufreq", NULL,
319 NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
320 if (error != 0) {
321 goto sysctl_failed;
322 }
323
324 error = sysctl_createv(&log, 0, &node, &cpunode,
325 0, CTLTYPE_NODE, cpu_name(ci), NULL,
326 NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
327 if (error != 0) {
328 goto sysctl_failed;
329 }
330
331 error = sysctl_createv(&log, 0, &cpunode, &node,
332 CTLFLAG_READWRITE, CTLTYPE_INT, "target", NULL,
333 cppc_cpufreq_sysctl, 0, (void *)sc, 0,
334 CTL_CREATE, CTL_EOL);
335 if (error != 0) {
336 goto sysctl_failed;
337 }
338 sc->sc_node_target = node->sysctl_num;
339
340 error = sysctl_createv(&log, 0, &cpunode, &node,
341 CTLFLAG_READONLY, CTLTYPE_INT, "current", NULL,
342 cppc_cpufreq_sysctl, 0, (void *)sc, 0,
343 CTL_CREATE, CTL_EOL);
344 if (error != 0) {
345 goto sysctl_failed;
346 }
347 sc->sc_node_current = node->sysctl_num;
348
349 error = sysctl_createv(&log, 0, &cpunode, &node,
350 CTLFLAG_READONLY, CTLTYPE_STRING, "available", NULL,
351 NULL, 0, sc->sc_available, 0,
352 CTL_CREATE, CTL_EOL);
353 if (error != 0) {
354 goto sysctl_failed;
355 }
356
357 return AE_OK;
358
359 sysctl_failed:
360 aprint_error_dev(sc->sc_dev, "couldn't create sysctl nodes: %d\n",
361 error);
362 sysctl_teardown(&log);
363
364 return AE_ERROR;
365 }
366
367 /*
368 * cppc_read --
369 *
370 * Read a value from the CPC package that contains either an integer
371 * or indirect register reference.
372 */
373 static ACPI_STATUS
374 cppc_read(struct cppc_softc *sc, CPCPackageElement index, ACPI_INTEGER *val)
375 {
376 ACPI_OBJECT *obj;
377 ACPI_GENERIC_ADDRESS addr;
378 ACPI_STATUS rv;
379
380 if (index >= sc->sc_ncpc) {
381 return AE_NOT_EXIST;
382 }
383
384 obj = &sc->sc_cpc->Package.Elements[index];
385 switch (obj->Type) {
386 case ACPI_TYPE_INTEGER:
387 *val = obj->Integer.Value;
388 return AE_OK;
389
390 case ACPI_TYPE_BUFFER:
391 if (obj->Buffer.Length <
392 sizeof(AML_RESOURCE_GENERIC_REGISTER)) {
393 return AE_TYPE;
394 }
395 memcpy(&addr, obj->Buffer.Pointer +
396 sizeof(AML_RESOURCE_LARGE_HEADER), sizeof(addr));
397 if (addr.SpaceId == ACPI_ADR_SPACE_PLATFORM_COMM) {
398 rv = pcc_message(&addr, CPPC_PCC_READ, PCC_READ, val);
399 } else {
400 rv = AcpiRead(val, &addr);
401 }
402 return rv;
403
404 default:
405 return AE_SUPPORT;
406 }
407 }
408
409 /*
410 * cppc_write --
411 *
412 * Write a value based on the CPC package to the specified register.
413 */
414 static ACPI_STATUS
415 cppc_write(struct cppc_softc *sc, CPCPackageElement index, ACPI_INTEGER val)
416 {
417 ACPI_OBJECT *obj;
418 ACPI_GENERIC_ADDRESS addr;
419 ACPI_STATUS rv;
420
421 if (index >= sc->sc_ncpc) {
422 return AE_NOT_EXIST;
423 }
424
425 obj = &sc->sc_cpc->Package.Elements[index];
426 if (obj->Type != ACPI_TYPE_BUFFER ||
427 obj->Buffer.Length < sizeof(AML_RESOURCE_GENERIC_REGISTER)) {
428 return AE_TYPE;
429 }
430
431 memcpy(&addr, obj->Buffer.Pointer +
432 sizeof(AML_RESOURCE_LARGE_HEADER), sizeof(addr));
433 if (addr.SpaceId == ACPI_ADR_SPACE_PLATFORM_COMM) {
434 rv = pcc_message(&addr, CPPC_PCC_WRITE, PCC_WRITE, &val);
435 } else {
436 rv = AcpiWrite(val, &addr);
437 }
438
439 return rv;
440 }
441