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