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