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