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