Home | History | Annotate | Line # | Download | only in acpi
      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