1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright(c) 2010-2021 Intel Corporation 3 * Copyright(c) 2021 Arm Limited 4 */ 5 6 #include <rte_memcpy.h> 7 8 #include "power_cppc_cpufreq.h" 9 #include "power_common.h" 10 11 /* macros used for rounding frequency to nearest 100000 */ 12 #define FREQ_ROUNDING_DELTA 50000 13 #define ROUND_FREQ_TO_N_100000 100000 14 15 /* the unit of highest_perf and nominal_perf differs on different arm platforms. 16 * For highest_perf, it maybe 300 or 3000000, both means 3.0GHz. 17 */ 18 #define UNIT_DIFF 10000 19 20 #define POWER_CONVERT_TO_DECIMAL 10 21 22 #define POWER_GOVERNOR_USERSPACE "userspace" 23 #define POWER_SYSFILE_SETSPEED \ 24 "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_setspeed" 25 #define POWER_SYSFILE_SCALING_MAX_FREQ \ 26 "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_max_freq" 27 #define POWER_SYSFILE_SCALING_MIN_FREQ \ 28 "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_min_freq" 29 #define POWER_SYSFILE_HIGHEST_PERF \ 30 "/sys/devices/system/cpu/cpu%u/acpi_cppc/highest_perf" 31 #define POWER_SYSFILE_NOMINAL_PERF \ 32 "/sys/devices/system/cpu/cpu%u/acpi_cppc/nominal_perf" 33 #define POWER_SYSFILE_SYS_MAX \ 34 "/sys/devices/system/cpu/cpu%u/cpufreq/cpuinfo_max_freq" 35 36 #define POWER_CPPC_DRIVER "cppc-cpufreq" 37 #define BUS_FREQ 100000 38 39 enum power_state { 40 POWER_IDLE = 0, 41 POWER_ONGOING, 42 POWER_USED, 43 POWER_UNKNOWN 44 }; 45 46 /** 47 * Power info per lcore. 48 */ 49 struct cppc_power_info { 50 unsigned int lcore_id; /**< Logical core id */ 51 uint32_t state; /**< Power in use state */ 52 FILE *f; /**< FD of scaling_setspeed */ 53 char governor_ori[32]; /**< Original governor name */ 54 uint32_t curr_idx; /**< Freq index in freqs array */ 55 uint32_t highest_perf; /**< system wide max freq */ 56 uint32_t nominal_perf; /**< system wide nominal freq */ 57 uint16_t turbo_available; /**< Turbo Boost available */ 58 uint16_t turbo_enable; /**< Turbo Boost enable/disable */ 59 uint32_t nb_freqs; /**< number of available freqs */ 60 uint32_t freqs[RTE_MAX_LCORE_FREQS]; /**< Frequency array */ 61 } __rte_cache_aligned; 62 63 static struct cppc_power_info lcore_power_info[RTE_MAX_LCORE]; 64 65 /** 66 * It is to set specific freq for specific logical core, according to the index 67 * of supported frequencies. 68 */ 69 static int 70 set_freq_internal(struct cppc_power_info *pi, uint32_t idx) 71 { 72 if (idx >= RTE_MAX_LCORE_FREQS || idx >= pi->nb_freqs) { 73 RTE_LOG(ERR, POWER, "Invalid frequency index %u, which " 74 "should be less than %u\n", idx, pi->nb_freqs); 75 return -1; 76 } 77 78 /* Check if it is the same as current */ 79 if (idx == pi->curr_idx) 80 return 0; 81 82 POWER_DEBUG_TRACE("Frequency[%u] %u to be set for lcore %u\n", 83 idx, pi->freqs[idx], pi->lcore_id); 84 if (fseek(pi->f, 0, SEEK_SET) < 0) { 85 RTE_LOG(ERR, POWER, "Fail to set file position indicator to 0 " 86 "for setting frequency for lcore %u\n", pi->lcore_id); 87 return -1; 88 } 89 if (fprintf(pi->f, "%u", pi->freqs[idx]) < 0) { 90 RTE_LOG(ERR, POWER, "Fail to write new frequency for " 91 "lcore %u\n", pi->lcore_id); 92 return -1; 93 } 94 fflush(pi->f); 95 pi->curr_idx = idx; 96 97 return 1; 98 } 99 100 /** 101 * It is to check the current scaling governor by reading sys file, and then 102 * set it into 'userspace' if it is not by writing the sys file. The original 103 * governor will be saved for rolling back. 104 */ 105 static int 106 power_set_governor_userspace(struct cppc_power_info *pi) 107 { 108 return power_set_governor(pi->lcore_id, POWER_GOVERNOR_USERSPACE, 109 pi->governor_ori, sizeof(pi->governor_ori)); 110 } 111 112 static int 113 power_check_turbo(struct cppc_power_info *pi) 114 { 115 FILE *f_nom = NULL, *f_max = NULL, *f_cmax = NULL; 116 int ret = -1; 117 uint32_t nominal_perf = 0, highest_perf = 0, cpuinfo_max_freq = 0; 118 119 open_core_sysfs_file(&f_max, "r", POWER_SYSFILE_HIGHEST_PERF, 120 pi->lcore_id); 121 if (f_max == NULL) { 122 RTE_LOG(ERR, POWER, "failed to open %s\n", 123 POWER_SYSFILE_HIGHEST_PERF); 124 goto err; 125 } 126 127 open_core_sysfs_file(&f_nom, "r", POWER_SYSFILE_NOMINAL_PERF, 128 pi->lcore_id); 129 if (f_nom == NULL) { 130 RTE_LOG(ERR, POWER, "failed to open %s\n", 131 POWER_SYSFILE_NOMINAL_PERF); 132 goto err; 133 } 134 135 open_core_sysfs_file(&f_cmax, "r", POWER_SYSFILE_SYS_MAX, 136 pi->lcore_id); 137 if (f_cmax == NULL) { 138 RTE_LOG(ERR, POWER, "failed to open %s\n", 139 POWER_SYSFILE_SYS_MAX); 140 goto err; 141 } 142 143 ret = read_core_sysfs_u32(f_max, &highest_perf); 144 if (ret < 0) { 145 RTE_LOG(ERR, POWER, "Failed to read %s\n", 146 POWER_SYSFILE_HIGHEST_PERF); 147 goto err; 148 } 149 150 ret = read_core_sysfs_u32(f_nom, &nominal_perf); 151 if (ret < 0) { 152 RTE_LOG(ERR, POWER, "Failed to read %s\n", 153 POWER_SYSFILE_NOMINAL_PERF); 154 goto err; 155 } 156 157 ret = read_core_sysfs_u32(f_cmax, &cpuinfo_max_freq); 158 if (ret < 0) { 159 RTE_LOG(ERR, POWER, "Failed to read %s\n", 160 POWER_SYSFILE_SYS_MAX); 161 goto err; 162 } 163 164 pi->highest_perf = highest_perf; 165 pi->nominal_perf = nominal_perf; 166 167 if ((highest_perf > nominal_perf) && ((cpuinfo_max_freq == highest_perf) 168 || cpuinfo_max_freq == highest_perf * UNIT_DIFF)) { 169 pi->turbo_available = 1; 170 pi->turbo_enable = 1; 171 ret = 0; 172 POWER_DEBUG_TRACE("Lcore %u can do Turbo Boost! highest perf %u, " 173 "nominal perf %u\n", 174 pi->lcore_id, highest_perf, nominal_perf); 175 } else { 176 pi->turbo_available = 0; 177 pi->turbo_enable = 0; 178 POWER_DEBUG_TRACE("Lcore %u Turbo not available! highest perf %u, " 179 "nominal perf %u\n", 180 pi->lcore_id, highest_perf, nominal_perf); 181 } 182 183 err: 184 if (f_max != NULL) 185 fclose(f_max); 186 if (f_nom != NULL) 187 fclose(f_nom); 188 if (f_cmax != NULL) 189 fclose(f_cmax); 190 191 return ret; 192 } 193 194 /** 195 * It is to get the available frequencies of the specific lcore by reading the 196 * sys file. 197 */ 198 static int 199 power_get_available_freqs(struct cppc_power_info *pi) 200 { 201 FILE *f_min = NULL, *f_max = NULL; 202 int ret = -1; 203 uint32_t scaling_min_freq = 0, scaling_max_freq = 0, nominal_perf = 0; 204 uint32_t i, num_freqs = 0; 205 206 open_core_sysfs_file(&f_max, "r", POWER_SYSFILE_SCALING_MAX_FREQ, 207 pi->lcore_id); 208 if (f_max == NULL) { 209 RTE_LOG(ERR, POWER, "failed to open %s\n", 210 POWER_SYSFILE_SCALING_MAX_FREQ); 211 goto out; 212 } 213 214 open_core_sysfs_file(&f_min, "r", POWER_SYSFILE_SCALING_MIN_FREQ, 215 pi->lcore_id); 216 if (f_min == NULL) { 217 RTE_LOG(ERR, POWER, "failed to open %s\n", 218 POWER_SYSFILE_SCALING_MIN_FREQ); 219 goto out; 220 } 221 222 ret = read_core_sysfs_u32(f_max, &scaling_max_freq); 223 if (ret < 0) { 224 RTE_LOG(ERR, POWER, "Failed to read %s\n", 225 POWER_SYSFILE_SCALING_MAX_FREQ); 226 goto out; 227 } 228 229 ret = read_core_sysfs_u32(f_min, &scaling_min_freq); 230 if (ret < 0) { 231 RTE_LOG(ERR, POWER, "Failed to read %s\n", 232 POWER_SYSFILE_SCALING_MIN_FREQ); 233 goto out; 234 } 235 236 power_check_turbo(pi); 237 238 if (scaling_max_freq < scaling_min_freq) 239 goto out; 240 241 /* If turbo is available then there is one extra freq bucket 242 * to store the sys max freq which value is scaling_max_freq 243 */ 244 nominal_perf = (pi->nominal_perf < UNIT_DIFF) ? 245 pi->nominal_perf * UNIT_DIFF : pi->nominal_perf; 246 num_freqs = (nominal_perf - scaling_min_freq) / BUS_FREQ + 1 + 247 pi->turbo_available; 248 if (num_freqs >= RTE_MAX_LCORE_FREQS) { 249 RTE_LOG(ERR, POWER, "Too many available frequencies: %d\n", 250 num_freqs); 251 goto out; 252 } 253 254 /* Generate the freq bucket array. */ 255 for (i = 0, pi->nb_freqs = 0; i < num_freqs; i++) { 256 if ((i == 0) && pi->turbo_available) 257 pi->freqs[pi->nb_freqs++] = scaling_max_freq; 258 else 259 pi->freqs[pi->nb_freqs++] = 260 nominal_perf - (i - pi->turbo_available) * BUS_FREQ; 261 } 262 263 ret = 0; 264 265 POWER_DEBUG_TRACE("%d frequency(s) of lcore %u are available\n", 266 num_freqs, pi->lcore_id); 267 268 out: 269 if (f_min != NULL) 270 fclose(f_min); 271 if (f_max != NULL) 272 fclose(f_max); 273 274 return ret; 275 } 276 277 /** 278 * It is to fopen the sys file for the future setting the lcore frequency. 279 */ 280 static int 281 power_init_for_setting_freq(struct cppc_power_info *pi) 282 { 283 FILE *f = NULL; 284 char buf[BUFSIZ]; 285 uint32_t i, freq; 286 int ret; 287 288 open_core_sysfs_file(&f, "rw+", POWER_SYSFILE_SETSPEED, pi->lcore_id); 289 if (f == NULL) { 290 RTE_LOG(ERR, POWER, "failed to open %s\n", 291 POWER_SYSFILE_SETSPEED); 292 goto err; 293 } 294 295 ret = read_core_sysfs_s(f, buf, sizeof(buf)); 296 if (ret < 0) { 297 RTE_LOG(ERR, POWER, "Failed to read %s\n", 298 POWER_SYSFILE_SETSPEED); 299 goto err; 300 } 301 302 freq = strtoul(buf, NULL, POWER_CONVERT_TO_DECIMAL); 303 304 /* convert the frequency to nearest 100000 value 305 * Ex: if freq=1396789 then freq_conv=1400000 306 * Ex: if freq=800030 then freq_conv=800000 307 */ 308 unsigned int freq_conv = 0; 309 freq_conv = (freq + FREQ_ROUNDING_DELTA) 310 / ROUND_FREQ_TO_N_100000; 311 freq_conv = freq_conv * ROUND_FREQ_TO_N_100000; 312 313 for (i = 0; i < pi->nb_freqs; i++) { 314 if (freq_conv == pi->freqs[i]) { 315 pi->curr_idx = i; 316 pi->f = f; 317 return 0; 318 } 319 } 320 321 err: 322 if (f != NULL) 323 fclose(f); 324 325 return -1; 326 } 327 328 int 329 power_cppc_cpufreq_check_supported(void) 330 { 331 return cpufreq_check_scaling_driver(POWER_CPPC_DRIVER); 332 } 333 334 int 335 power_cppc_cpufreq_init(unsigned int lcore_id) 336 { 337 struct cppc_power_info *pi; 338 uint32_t exp_state; 339 340 if (lcore_id >= RTE_MAX_LCORE) { 341 RTE_LOG(ERR, POWER, "Lcore id %u can not exceeds %u\n", 342 lcore_id, RTE_MAX_LCORE - 1U); 343 return -1; 344 } 345 346 pi = &lcore_power_info[lcore_id]; 347 exp_state = POWER_IDLE; 348 /* The power in use state works as a guard variable between 349 * the CPU frequency control initialization and exit process. 350 * The ACQUIRE memory ordering here pairs with the RELEASE 351 * ordering below as lock to make sure the frequency operations 352 * in the critical section are done under the correct state. 353 */ 354 if (!__atomic_compare_exchange_n(&(pi->state), &exp_state, 355 POWER_ONGOING, 0, 356 __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) { 357 RTE_LOG(INFO, POWER, "Power management of lcore %u is " 358 "in use\n", lcore_id); 359 return -1; 360 } 361 362 pi->lcore_id = lcore_id; 363 /* Check and set the governor */ 364 if (power_set_governor_userspace(pi) < 0) { 365 RTE_LOG(ERR, POWER, "Cannot set governor of lcore %u to " 366 "userspace\n", lcore_id); 367 goto fail; 368 } 369 370 /* Get the available frequencies */ 371 if (power_get_available_freqs(pi) < 0) { 372 RTE_LOG(ERR, POWER, "Cannot get available frequencies of " 373 "lcore %u\n", lcore_id); 374 goto fail; 375 } 376 377 /* Init for setting lcore frequency */ 378 if (power_init_for_setting_freq(pi) < 0) { 379 RTE_LOG(ERR, POWER, "Cannot init for setting frequency for " 380 "lcore %u\n", lcore_id); 381 goto fail; 382 } 383 384 /* Set freq to max by default */ 385 if (power_cppc_cpufreq_freq_max(lcore_id) < 0) { 386 RTE_LOG(ERR, POWER, "Cannot set frequency of lcore %u " 387 "to max\n", lcore_id); 388 goto fail; 389 } 390 391 RTE_LOG(INFO, POWER, "Initialized successfully for lcore %u " 392 "power management\n", lcore_id); 393 394 __atomic_store_n(&(pi->state), POWER_USED, __ATOMIC_RELEASE); 395 396 return 0; 397 398 fail: 399 __atomic_store_n(&(pi->state), POWER_UNKNOWN, __ATOMIC_RELEASE); 400 return -1; 401 } 402 403 /** 404 * It is to check the governor and then set the original governor back if 405 * needed by writing the sys file. 406 */ 407 static int 408 power_set_governor_original(struct cppc_power_info *pi) 409 { 410 return power_set_governor(pi->lcore_id, pi->governor_ori, NULL, 0); 411 } 412 413 int 414 power_cppc_cpufreq_exit(unsigned int lcore_id) 415 { 416 struct cppc_power_info *pi; 417 uint32_t exp_state; 418 419 if (lcore_id >= RTE_MAX_LCORE) { 420 RTE_LOG(ERR, POWER, "Lcore id %u can not exceeds %u\n", 421 lcore_id, RTE_MAX_LCORE - 1U); 422 return -1; 423 } 424 pi = &lcore_power_info[lcore_id]; 425 exp_state = POWER_USED; 426 /* The power in use state works as a guard variable between 427 * the CPU frequency control initialization and exit process. 428 * The ACQUIRE memory ordering here pairs with the RELEASE 429 * ordering below as lock to make sure the frequency operations 430 * in the critical section are done under the correct state. 431 */ 432 if (!__atomic_compare_exchange_n(&(pi->state), &exp_state, 433 POWER_ONGOING, 0, 434 __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) { 435 RTE_LOG(INFO, POWER, "Power management of lcore %u is " 436 "not used\n", lcore_id); 437 return -1; 438 } 439 440 /* Close FD of setting freq */ 441 fclose(pi->f); 442 pi->f = NULL; 443 444 /* Set the governor back to the original */ 445 if (power_set_governor_original(pi) < 0) { 446 RTE_LOG(ERR, POWER, "Cannot set the governor of %u back " 447 "to the original\n", lcore_id); 448 goto fail; 449 } 450 451 RTE_LOG(INFO, POWER, "Power management of lcore %u has exited from " 452 "'userspace' mode and been set back to the " 453 "original\n", lcore_id); 454 __atomic_store_n(&(pi->state), POWER_IDLE, __ATOMIC_RELEASE); 455 456 return 0; 457 458 fail: 459 __atomic_store_n(&(pi->state), POWER_UNKNOWN, __ATOMIC_RELEASE); 460 461 return -1; 462 } 463 464 uint32_t 465 power_cppc_cpufreq_freqs(unsigned int lcore_id, uint32_t *freqs, uint32_t num) 466 { 467 struct cppc_power_info *pi; 468 469 if (lcore_id >= RTE_MAX_LCORE) { 470 RTE_LOG(ERR, POWER, "Invalid lcore ID\n"); 471 return 0; 472 } 473 474 if (freqs == NULL) { 475 RTE_LOG(ERR, POWER, "NULL buffer supplied\n"); 476 return 0; 477 } 478 479 pi = &lcore_power_info[lcore_id]; 480 if (num < pi->nb_freqs) { 481 RTE_LOG(ERR, POWER, "Buffer size is not enough\n"); 482 return 0; 483 } 484 rte_memcpy(freqs, pi->freqs, pi->nb_freqs * sizeof(uint32_t)); 485 486 return pi->nb_freqs; 487 } 488 489 uint32_t 490 power_cppc_cpufreq_get_freq(unsigned int lcore_id) 491 { 492 if (lcore_id >= RTE_MAX_LCORE) { 493 RTE_LOG(ERR, POWER, "Invalid lcore ID\n"); 494 return RTE_POWER_INVALID_FREQ_INDEX; 495 } 496 497 return lcore_power_info[lcore_id].curr_idx; 498 } 499 500 int 501 power_cppc_cpufreq_set_freq(unsigned int lcore_id, uint32_t index) 502 { 503 if (lcore_id >= RTE_MAX_LCORE) { 504 RTE_LOG(ERR, POWER, "Invalid lcore ID\n"); 505 return -1; 506 } 507 508 return set_freq_internal(&(lcore_power_info[lcore_id]), index); 509 } 510 511 int 512 power_cppc_cpufreq_freq_down(unsigned int lcore_id) 513 { 514 struct cppc_power_info *pi; 515 516 if (lcore_id >= RTE_MAX_LCORE) { 517 RTE_LOG(ERR, POWER, "Invalid lcore ID\n"); 518 return -1; 519 } 520 521 pi = &lcore_power_info[lcore_id]; 522 if (pi->curr_idx + 1 == pi->nb_freqs) 523 return 0; 524 525 /* Frequencies in the array are from high to low. */ 526 return set_freq_internal(pi, pi->curr_idx + 1); 527 } 528 529 int 530 power_cppc_cpufreq_freq_up(unsigned int lcore_id) 531 { 532 struct cppc_power_info *pi; 533 534 if (lcore_id >= RTE_MAX_LCORE) { 535 RTE_LOG(ERR, POWER, "Invalid lcore ID\n"); 536 return -1; 537 } 538 539 pi = &lcore_power_info[lcore_id]; 540 if (pi->curr_idx == 0 || (pi->curr_idx == 1 && 541 pi->turbo_available && !pi->turbo_enable)) 542 return 0; 543 544 /* Frequencies in the array are from high to low. */ 545 return set_freq_internal(pi, pi->curr_idx - 1); 546 } 547 548 int 549 power_cppc_cpufreq_freq_max(unsigned int lcore_id) 550 { 551 if (lcore_id >= RTE_MAX_LCORE) { 552 RTE_LOG(ERR, POWER, "Invalid lcore ID\n"); 553 return -1; 554 } 555 556 /* Frequencies in the array are from high to low. */ 557 if (lcore_power_info[lcore_id].turbo_available) { 558 if (lcore_power_info[lcore_id].turbo_enable) 559 /* Set to Turbo */ 560 return set_freq_internal( 561 &lcore_power_info[lcore_id], 0); 562 else 563 /* Set to max non-turbo */ 564 return set_freq_internal( 565 &lcore_power_info[lcore_id], 1); 566 } else 567 return set_freq_internal(&lcore_power_info[lcore_id], 0); 568 } 569 570 int 571 power_cppc_cpufreq_freq_min(unsigned int lcore_id) 572 { 573 struct cppc_power_info *pi; 574 575 if (lcore_id >= RTE_MAX_LCORE) { 576 RTE_LOG(ERR, POWER, "Invalid lcore ID\n"); 577 return -1; 578 } 579 580 pi = &lcore_power_info[lcore_id]; 581 582 /* Frequencies in the array are from high to low. */ 583 return set_freq_internal(pi, pi->nb_freqs - 1); 584 } 585 586 int 587 power_cppc_turbo_status(unsigned int lcore_id) 588 { 589 struct cppc_power_info *pi; 590 591 if (lcore_id >= RTE_MAX_LCORE) { 592 RTE_LOG(ERR, POWER, "Invalid lcore ID\n"); 593 return -1; 594 } 595 596 pi = &lcore_power_info[lcore_id]; 597 598 return pi->turbo_enable; 599 } 600 601 int 602 power_cppc_enable_turbo(unsigned int lcore_id) 603 { 604 struct cppc_power_info *pi; 605 606 if (lcore_id >= RTE_MAX_LCORE) { 607 RTE_LOG(ERR, POWER, "Invalid lcore ID\n"); 608 return -1; 609 } 610 611 pi = &lcore_power_info[lcore_id]; 612 613 if (pi->turbo_available) 614 pi->turbo_enable = 1; 615 else { 616 pi->turbo_enable = 0; 617 RTE_LOG(ERR, POWER, 618 "Failed to enable turbo on lcore %u\n", 619 lcore_id); 620 return -1; 621 } 622 623 /* TODO: must set to max once enabling Turbo? Considering add condition: 624 * if ((pi->turbo_available) && (pi->curr_idx <= 1)) 625 */ 626 /* Max may have changed, so call to max function */ 627 if (power_cppc_cpufreq_freq_max(lcore_id) < 0) { 628 RTE_LOG(ERR, POWER, 629 "Failed to set frequency of lcore %u to max\n", 630 lcore_id); 631 return -1; 632 } 633 634 return 0; 635 } 636 637 int 638 power_cppc_disable_turbo(unsigned int lcore_id) 639 { 640 struct cppc_power_info *pi; 641 642 if (lcore_id >= RTE_MAX_LCORE) { 643 RTE_LOG(ERR, POWER, "Invalid lcore ID\n"); 644 return -1; 645 } 646 647 pi = &lcore_power_info[lcore_id]; 648 649 pi->turbo_enable = 0; 650 651 if ((pi->turbo_available) && (pi->curr_idx <= 1)) { 652 /* Try to set freq to max by default coming out of turbo */ 653 if (power_cppc_cpufreq_freq_max(lcore_id) < 0) { 654 RTE_LOG(ERR, POWER, 655 "Failed to set frequency of lcore %u to max\n", 656 lcore_id); 657 return -1; 658 } 659 } 660 661 return 0; 662 } 663 664 int 665 power_cppc_get_capabilities(unsigned int lcore_id, 666 struct rte_power_core_capabilities *caps) 667 { 668 struct cppc_power_info *pi; 669 670 if (lcore_id >= RTE_MAX_LCORE) { 671 RTE_LOG(ERR, POWER, "Invalid lcore ID\n"); 672 return -1; 673 } 674 if (caps == NULL) { 675 RTE_LOG(ERR, POWER, "Invalid argument\n"); 676 return -1; 677 } 678 679 pi = &lcore_power_info[lcore_id]; 680 caps->capabilities = 0; 681 caps->turbo = !!(pi->turbo_available); 682 683 return 0; 684 } 685