Home | History | Annotate | Line # | Download | only in acpi
acpi_cppc.c revision 1.1.2.3
      1 /* $NetBSD: acpi_cppc.c,v 1.1.2.3 2021/04/03 22:28:43 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.3 2021/04/03 22:28:43 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
    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
    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
    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
    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
    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
    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
    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