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 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