xref: /netbsd-src/sys/dev/acpi/acpi_cppc.c (revision 3772555307d774424243e6d1cdc66aa160602c26)
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