1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019
|
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
/*
* XSCOM driver
*
* Copyright 2013-2019 IBM Corp.
*/
#include <skiboot.h>
#include <xscom.h>
#include <io.h>
#include <processor.h>
#include <device.h>
#include <chip.h>
#include <centaur.h>
#include <errorlog.h>
#include <opal-api.h>
#include <timebase.h>
#include <nvram.h>
/* Mask of bits to clear in HMER before an access */
#define HMER_CLR_MASK (~(SPR_HMER_XSCOM_FAIL | \
SPR_HMER_XSCOM_DONE | \
SPR_HMER_XSCOM_STATUS))
DEFINE_LOG_ENTRY(OPAL_RC_XSCOM_RW, OPAL_PLATFORM_ERR_EVT, OPAL_XSCOM,
OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL,
OPAL_NA);
DEFINE_LOG_ENTRY(OPAL_RC_XSCOM_INDIRECT_RW, OPAL_PLATFORM_ERR_EVT, OPAL_XSCOM,
OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL,
OPAL_NA);
DEFINE_LOG_ENTRY(OPAL_RC_XSCOM_RESET, OPAL_PLATFORM_ERR_EVT, OPAL_XSCOM,
OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL,
OPAL_NA);
DEFINE_LOG_ENTRY(OPAL_RC_XSCOM_BUSY, OPAL_PLATFORM_ERR_EVT, OPAL_XSCOM,
OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL,
OPAL_NA);
/* xscom details to trigger xstop */
static struct {
uint64_t addr;
uint64_t fir_bit;
} xstop_xscom;
/*
* Locking notes:
*
* We used to have a per-target lock. However due to errata HW822317
* we can have issues on the issuer side if multiple threads try to
* send XSCOMs simultaneously (HMER responses get mixed up), so just
* use a global lock instead
*/
static struct lock xscom_lock = LOCK_UNLOCKED;
static inline void *xscom_addr(uint32_t gcid, uint32_t pcb_addr)
{
struct proc_chip *chip = get_chip(gcid);
uint64_t addr;
assert(chip);
addr = chip->xscom_base;
if (proc_gen == proc_gen_p8) {
addr |= ((uint64_t)pcb_addr << 4) & ~0xfful;
addr |= (pcb_addr << 3) & 0x78;
} else
addr |= ((uint64_t)pcb_addr << 3);
return (void *)addr;
}
static uint64_t xscom_wait_done(void)
{
uint64_t hmer;
do
hmer = mfspr(SPR_HMER);
while(!(hmer & SPR_HMER_XSCOM_DONE));
/*
* HW822317: We need to read a second time as the actual
* status can be delayed by 1 cycle after DONE
*/
return mfspr(SPR_HMER);
}
static void xscom_reset(uint32_t gcid, bool need_delay)
{
u64 hmer;
uint32_t recv_status_reg, log_reg, err_reg;
struct timespec ts;
/* Clear errors in HMER */
mtspr(SPR_HMER, HMER_CLR_MASK);
/* Setup local and target scom addresses */
if (proc_gen == proc_gen_p10) {
recv_status_reg = 0x00090018;
log_reg = 0x0090012;
err_reg = 0x0090013;
} else if (proc_gen == proc_gen_p9) {
recv_status_reg = 0x00090018;
log_reg = 0x0090012;
err_reg = 0x0090013;
} else {
recv_status_reg = 0x202000f;
log_reg = 0x2020007;
err_reg = 0x2020009;
}
/* First we need to write 0 to a register on our chip */
out_be64(xscom_addr(this_cpu()->chip_id, recv_status_reg), 0);
hmer = xscom_wait_done();
if (hmer & SPR_HMER_XSCOM_FAIL)
goto fail;
/* Then we need to clear those two other registers on the target */
out_be64(xscom_addr(gcid, log_reg), 0);
hmer = xscom_wait_done();
if (hmer & SPR_HMER_XSCOM_FAIL)
goto fail;
out_be64(xscom_addr(gcid, err_reg), 0);
hmer = xscom_wait_done();
if (hmer & SPR_HMER_XSCOM_FAIL)
goto fail;
if (need_delay) {
/*
* Its observed that sometimes immediate retry of
* XSCOM operation returns wrong data. Adding a
* delay for XSCOM reset to be effective. Delay of
* 10 ms is found to be working fine experimentally.
* FIXME: Replace 10ms delay by exact delay needed
* or other alternate method to confirm XSCOM reset
* completion, after checking from HW folks.
*/
ts.tv_sec = 0;
ts.tv_nsec = 10 * 1000;
nanosleep_nopoll(&ts, NULL);
}
return;
fail:
/* Fatal error resetting XSCOM */
log_simple_error(&e_info(OPAL_RC_XSCOM_RESET),
"XSCOM: Fatal error resetting engine after failed access !\n");
/* XXX Generate error log ? attn ? panic ?
* If we decide to panic, change the above severity to PANIC
*/
}
static int xscom_clear_error(uint32_t gcid, uint32_t pcb_addr)
{
u64 hmer;
uint32_t base_xscom_addr;
uint32_t xscom_clear_reg = 0x20010800;
/* only in case of p9 */
if (proc_gen != proc_gen_p9)
return 0;
/* xscom clear address range/mask */
#define XSCOM_CLEAR_RANGE_START 0x20010A00
#define XSCOM_CLEAR_RANGE_END 0x20010ABF
#define XSCOM_CLEAR_RANGE_MASK 0x200FFBFF
/*
* Due to a hardware issue where core responding to scom was delayed
* due to thread reconfiguration, leaves the scom logic in a state
* where the subsequent scom to that core can get errors. This is
* affected for Core PC scom registers in the range of
* 20010A80-20010ABF.
*
* The solution is if a xscom timeout occurs to one of Core PC scom
* registers in the range of 20010A80-20010ABF, a clearing scom
* write is done to 0x20010800 with data of '0x00000000' which will
* also get a timeout but clears the scom logic errors. After the
* clearing write is done the original scom operation can be retried.
*
* The scom timeout is reported as status 0x4 (Invalid address)
* in HMER[21-23].
*/
base_xscom_addr = pcb_addr & XSCOM_CLEAR_RANGE_MASK;
if (!((base_xscom_addr >= XSCOM_CLEAR_RANGE_START) &&
(base_xscom_addr <= XSCOM_CLEAR_RANGE_END)))
return 0;
/*
* Reset the XSCOM or next scom operation will fail.
* We also need a small delay before we go ahead with clearing write.
* We have observed that without a delay the clearing write has reported
* a wrong status.
*/
xscom_reset(gcid, true);
/* Clear errors in HMER */
mtspr(SPR_HMER, HMER_CLR_MASK);
/* Write 0 to clear the xscom logic errors on target chip */
out_be64(xscom_addr(gcid, xscom_clear_reg), 0);
hmer = xscom_wait_done();
/*
* Above clearing xscom write will timeout and error out with
* invalid access as there is no register at that address. This
* xscom operation just helps to clear the xscom logic error.
*
* On failure, reset the XSCOM or we'll hang on the next access
*/
if (hmer & SPR_HMER_XSCOM_FAIL)
xscom_reset(gcid, true);
return 1;
}
static int64_t xscom_handle_error(uint64_t hmer, uint32_t gcid, uint32_t pcb_addr,
bool is_write, int64_t retries,
int64_t *xscom_clear_retries)
{
unsigned int stat = GETFIELD(SPR_HMER_XSCOM_STATUS, hmer);
int64_t rc = OPAL_HARDWARE;
/* XXX Figure out error codes from doc and error
* recovery procedures
*/
switch(stat) {
case 1:
/*
* XSCOM engine is blocked, need to retry. Reset XSCOM
* engine after crossing retry threshold before
* retrying again.
*/
if (retries && !(retries % XSCOM_BUSY_RESET_THRESHOLD)) {
prlog(PR_NOTICE, "XSCOM: Busy even after %d retries, "
"resetting XSCOM now. Total retries = %lld\n",
XSCOM_BUSY_RESET_THRESHOLD, retries);
xscom_reset(gcid, true);
}
/* Log error if we have retried enough and its still busy */
if (retries == XSCOM_BUSY_MAX_RETRIES)
log_simple_error(&e_info(OPAL_RC_XSCOM_BUSY),
"XSCOM: %s-busy error gcid=0x%x pcb_addr=0x%x "
"stat=0x%x\n", is_write ? "write" : "read",
gcid, pcb_addr, stat);
return OPAL_XSCOM_BUSY;
case 2: /* CPU is asleep, reset XSCOM engine and return */
xscom_reset(gcid, false);
return OPAL_XSCOM_CHIPLET_OFF;
case 3: /* Partial good */
rc = OPAL_XSCOM_PARTIAL_GOOD;
break;
case 4: /* Invalid address / address error */
rc = OPAL_XSCOM_ADDR_ERROR;
if (xscom_clear_error(gcid, pcb_addr)) {
/* return busy if retries still pending. */
if ((*xscom_clear_retries)--)
return OPAL_XSCOM_BUSY;
prlog(PR_DEBUG, "XSCOM: error recovery failed for "
"gcid=0x%x pcb_addr=0x%x\n", gcid, pcb_addr);
}
break;
case 5: /* Clock error */
rc = OPAL_XSCOM_CLOCK_ERROR;
break;
case 6: /* Parity error */
rc = OPAL_XSCOM_PARITY_ERROR;
break;
case 7: /* Time out */
rc = OPAL_XSCOM_TIMEOUT;
break;
}
/*
* If we're in an XSCOM opal call then squash the error
* we assume that the caller (probably opal-prd) will
* handle logging it
*/
if (this_cpu()->current_token != OPAL_XSCOM_READ &&
this_cpu()->current_token != OPAL_XSCOM_WRITE) {
log_simple_error(&e_info(OPAL_RC_XSCOM_RW),
"XSCOM: %s error gcid=0x%x pcb_addr=0x%x stat=0x%x\n",
is_write ? "write" : "read", gcid, pcb_addr, stat);
}
/* We need to reset the XSCOM or we'll hang on the next access */
xscom_reset(gcid, false);
/* Non recovered ... just fail */
return rc;
}
static void xscom_handle_ind_error(uint64_t data, uint32_t gcid,
uint64_t pcb_addr, bool is_write)
{
unsigned int stat = GETFIELD(XSCOM_DATA_IND_ERR, data);
bool timeout = !(data & XSCOM_DATA_IND_COMPLETE);
/* XXX: Create error log entry ? */
if (timeout)
log_simple_error(&e_info(OPAL_RC_XSCOM_INDIRECT_RW),
"XSCOM: indirect %s timeout, gcid=0x%x pcb_addr=0x%llx"
" stat=0x%x\n",
is_write ? "write" : "read", gcid, pcb_addr, stat);
else
log_simple_error(&e_info(OPAL_RC_XSCOM_INDIRECT_RW),
"XSCOM: indirect %s error, gcid=0x%x pcb_addr=0x%llx"
" stat=0x%x\n",
is_write ? "write" : "read", gcid, pcb_addr, stat);
}
static bool xscom_gcid_ok(uint32_t gcid)
{
return get_chip(gcid) != NULL;
}
/* Determine if SCOM address is multicast */
static inline bool xscom_is_multicast_addr(uint32_t addr)
{
return (((addr >> 30) & 0x1) == 0x1);
}
/*
* Low level XSCOM access functions, perform a single direct xscom
* access via MMIO
*/
static int __xscom_read(uint32_t gcid, uint32_t pcb_addr, uint64_t *val)
{
uint64_t hmer;
int64_t ret, retries;
int64_t xscom_clear_retries = XSCOM_CLEAR_MAX_RETRIES;
if (!xscom_gcid_ok(gcid)) {
prerror("%s: invalid XSCOM gcid 0x%x\n", __func__, gcid);
return OPAL_PARAMETER;
}
for (retries = 0; retries <= XSCOM_BUSY_MAX_RETRIES; retries++) {
/* Clear status bits in HMER (HMER is special
* writing to it *ands* bits
*/
mtspr(SPR_HMER, HMER_CLR_MASK);
/* Read value from SCOM */
*val = in_be64(xscom_addr(gcid, pcb_addr));
/* Wait for done bit */
hmer = xscom_wait_done();
/* Check for error */
if (!(hmer & SPR_HMER_XSCOM_FAIL))
return OPAL_SUCCESS;
/* Handle error and possibly eventually retry */
ret = xscom_handle_error(hmer, gcid, pcb_addr, false, retries,
&xscom_clear_retries);
if (ret != OPAL_BUSY)
break;
}
/* Do not print error message for multicast SCOMS */
if (xscom_is_multicast_addr(pcb_addr) && ret == OPAL_XSCOM_CHIPLET_OFF)
return ret;
/*
* Workaround on P9: PRD does operations it *knows* will fail with this
* error to work around a hardware issue where accesses via the PIB
* (FSI or OCC) work as expected, accesses via the ADU (what xscom goes
* through) do not. The chip logic will always return all FFs if there
* is any error on the scom.
*/
if (proc_gen == proc_gen_p9 && ret == OPAL_XSCOM_CHIPLET_OFF)
return ret;
/*
* If an OPAL call XSCOM read fails, then the OPAL-PRD will
* handle logging the error. Hence just print an
* informational message here.
*/
if (this_cpu()->current_token == OPAL_XSCOM_READ)
prlog(PR_INFO, "XSCOM: Read failed, ret = %lld\n", ret);
else
prerror("XSCOM: Read failed, ret = %lld\n", ret);
return ret;
}
static int __xscom_write(uint32_t gcid, uint32_t pcb_addr, uint64_t val)
{
uint64_t hmer;
int64_t ret, retries = 0;
int64_t xscom_clear_retries = XSCOM_CLEAR_MAX_RETRIES;
if (!xscom_gcid_ok(gcid)) {
prerror("%s: invalid XSCOM gcid 0x%x\n", __func__, gcid);
return OPAL_PARAMETER;
}
for (retries = 0; retries <= XSCOM_BUSY_MAX_RETRIES; retries++) {
/* Clear status bits in HMER (HMER is special
* writing to it *ands* bits
*/
mtspr(SPR_HMER, HMER_CLR_MASK);
/* Write value to SCOM */
out_be64(xscom_addr(gcid, pcb_addr), val);
/* Wait for done bit */
hmer = xscom_wait_done();
/* Check for error */
if (!(hmer & SPR_HMER_XSCOM_FAIL))
return OPAL_SUCCESS;
/* Handle error and possibly eventually retry */
ret = xscom_handle_error(hmer, gcid, pcb_addr, true, retries,
&xscom_clear_retries);
if (ret != OPAL_BUSY)
break;
}
/* Do not print error message for multicast SCOMS */
if (xscom_is_multicast_addr(pcb_addr) && ret == OPAL_XSCOM_CHIPLET_OFF)
return ret;
/*
* Workaround on P9: PRD does operations it *knows* will fail with this
* error to work around a hardware issue where accesses via the PIB
* (FSI or OCC) work as expected, accesses via the ADU (what xscom goes
* through) do not. The chip logic will always return all FFs if there
* is any error on the scom.
*/
if (proc_gen == proc_gen_p9 && ret == OPAL_XSCOM_CHIPLET_OFF)
return ret;
/*
* If an OPAL call XSCOM write fails, then the OPAL-PRD will
* handle logging the error. Hence just print an
* informational message here.
*/
if (this_cpu()->current_token == OPAL_XSCOM_WRITE)
prlog(PR_INFO, "XSCOM: Write failed, ret = %lld\n", ret);
else
prerror("XSCOM: Write failed, ret = %lld\n", ret);
return ret;
}
/*
* Indirect XSCOM access functions
*/
static int xscom_indirect_read_form0(uint32_t gcid, uint64_t pcb_addr,
uint64_t *val)
{
uint32_t addr;
uint64_t data;
int rc, retries;
/* Write indirect address */
addr = pcb_addr & 0x7fffffff;
data = XSCOM_DATA_IND_READ |
(pcb_addr & XSCOM_ADDR_IND_ADDR);
rc = __xscom_write(gcid, addr, data);
if (rc)
goto bail;
/* Wait for completion */
for (retries = 0; retries < XSCOM_IND_MAX_RETRIES; retries++) {
rc = __xscom_read(gcid, addr, &data);
if (rc)
goto bail;
if ((data & XSCOM_DATA_IND_COMPLETE) &&
((data & XSCOM_DATA_IND_ERR) == 0)) {
*val = data & XSCOM_DATA_IND_DATA;
break;
}
if ((data & XSCOM_DATA_IND_COMPLETE) ||
(retries >= XSCOM_IND_MAX_RETRIES)) {
xscom_handle_ind_error(data, gcid, pcb_addr,
false);
rc = OPAL_HARDWARE;
goto bail;
}
}
bail:
if (rc)
*val = (uint64_t)-1;
return rc;
}
static int xscom_indirect_form(uint64_t pcb_addr)
{
return (pcb_addr >> 60) & 1;
}
static int xscom_indirect_read(uint32_t gcid, uint64_t pcb_addr, uint64_t *val)
{
uint64_t form = xscom_indirect_form(pcb_addr);
if ((proc_gen >= proc_gen_p9) && (form == 1))
return OPAL_UNSUPPORTED;
return xscom_indirect_read_form0(gcid, pcb_addr, val);
}
static int xscom_indirect_write_form0(uint32_t gcid, uint64_t pcb_addr,
uint64_t val)
{
uint32_t addr;
uint64_t data;
int rc, retries;
/* Only 16 bit data with indirect */
if (val & ~(XSCOM_ADDR_IND_DATA))
return OPAL_PARAMETER;
/* Write indirect address & data */
addr = pcb_addr & 0x7fffffff;
data = pcb_addr & XSCOM_ADDR_IND_ADDR;
data |= val & XSCOM_ADDR_IND_DATA;
rc = __xscom_write(gcid, addr, data);
if (rc)
goto bail;
/* Wait for completion */
for (retries = 0; retries < XSCOM_IND_MAX_RETRIES; retries++) {
rc = __xscom_read(gcid, addr, &data);
if (rc)
goto bail;
if ((data & XSCOM_DATA_IND_COMPLETE) &&
((data & XSCOM_DATA_IND_ERR) == 0))
break;
if ((data & XSCOM_DATA_IND_COMPLETE) ||
(retries >= XSCOM_IND_MAX_RETRIES)) {
xscom_handle_ind_error(data, gcid, pcb_addr,
true);
rc = OPAL_HARDWARE;
goto bail;
}
}
bail:
return rc;
}
static int xscom_indirect_write_form1(uint32_t gcid, uint64_t pcb_addr,
uint64_t val)
{
uint32_t addr;
uint64_t data;
if (proc_gen < proc_gen_p9)
return OPAL_UNSUPPORTED;
if (val & ~(XSCOM_DATA_IND_FORM1_DATA))
return OPAL_PARAMETER;
/* Mangle address and data for form1 */
addr = (pcb_addr & 0x000ffffffffUL);
data = (pcb_addr & 0xfff00000000UL) << 20;
data |= val;
return __xscom_write(gcid, addr, data);
}
static int xscom_indirect_write(uint32_t gcid, uint64_t pcb_addr, uint64_t val)
{
uint64_t form = xscom_indirect_form(pcb_addr);
if ((proc_gen >= proc_gen_p9) && (form == 1))
return xscom_indirect_write_form1(gcid, pcb_addr, val);
return xscom_indirect_write_form0(gcid, pcb_addr, val);
}
static uint32_t xscom_decode_chiplet(uint32_t partid, uint64_t *pcb_addr)
{
uint32_t gcid = (partid & 0x0fffffff) >> 4;
uint32_t core = partid & 0xf;
if (proc_gen >= proc_gen_p9) {
/* XXX Not supported */
*pcb_addr = 0;
} else {
*pcb_addr |= P8_EX_PCB_SLAVE_BASE;
*pcb_addr |= core << 24;
}
return gcid;
}
void _xscom_lock(void)
{
lock(&xscom_lock);
}
void _xscom_unlock(void)
{
unlock(&xscom_lock);
}
/* sorted by the scom controller's partid */
static LIST_HEAD(scom_list);
int64_t scom_register(struct scom_controller *new)
{
struct scom_controller *cur;
list_for_each(&scom_list, cur, link) {
if (cur->part_id == new->part_id) {
prerror("Attempted to add duplicate scom, partid %x\n",
new->part_id);
return OPAL_BUSY;
}
if (cur->part_id > new->part_id) {
list_add_before(&scom_list, &new->link, &cur->link);
return 0;
}
}
/* if we never find a larger partid then this is the largest */
list_add_tail(&scom_list, &new->link);
return 0;
}
static struct scom_controller *scom_find(uint32_t partid)
{
struct scom_controller *cur;
list_for_each(&scom_list, cur, link)
if (partid == cur->part_id)
return cur;
return NULL;
}
static int64_t scom_read(struct scom_controller *scom, uint32_t partid,
uint64_t pcbaddr, uint64_t *val)
{
int64_t rc = scom->read(scom, partid, pcbaddr, val);
if (rc) {
prerror("%s: to %x off: %llx rc = %lld\n",
__func__, partid, pcbaddr, rc);
}
return rc;
}
static int64_t scom_write(struct scom_controller *scom, uint32_t partid,
uint64_t pcbaddr, uint64_t val)
{
int64_t rc = scom->write(scom, partid, pcbaddr, val);
if (rc) {
prerror("%s: to %x off: %llx rc = %lld\n",
__func__, partid, pcbaddr, rc);
}
return rc;
}
/*
* External API
*/
int _xscom_read(uint32_t partid, uint64_t pcb_addr, uint64_t *val, bool take_lock)
{
struct scom_controller *scom;
uint32_t gcid;
int rc;
if (!opal_addr_valid(val))
return OPAL_PARAMETER;
/* Due to a bug in some versions of the PRD wrapper app, errors
* might not be properly forwarded to PRD, in which case the data
* set here will be used. Rather than a random value let's thus
* initialize the data to a known clean state.
*/
*val = 0xdeadbeefdeadbeefull;
/* Handle part ID decoding */
switch(partid >> 28) {
case 0: /* Normal processor chip */
gcid = partid;
break;
case 4: /* EX chiplet */
gcid = xscom_decode_chiplet(partid, &pcb_addr);
if (pcb_addr == 0)
return OPAL_UNSUPPORTED;
break;
default:
/* is it one of our hacks? */
scom = scom_find(partid);
if (scom)
return scom_read(scom, partid, pcb_addr, val);
/**
* @fwts-label XSCOMReadInvalidPartID
* @fwts-advice xscom_read was called with an invalid partid.
* There's likely a bug somewhere in the stack that's causing
* someone to try an xscom_read on something that isn't a
* processor, Centaur or EX chiplet.
*/
prerror("%s: invalid XSCOM partid 0x%x\n", __func__, partid);
return OPAL_PARAMETER;
}
/* HW822317 requires us to do global locking */
if (take_lock)
lock(&xscom_lock);
/* Direct vs indirect access */
if (pcb_addr & XSCOM_ADDR_IND_FLAG)
rc = xscom_indirect_read(gcid, pcb_addr, val);
else
rc = __xscom_read(gcid, pcb_addr & 0x7fffffff, val);
/* Unlock it */
if (take_lock)
unlock(&xscom_lock);
return rc;
}
static int64_t opal_xscom_read(uint32_t partid, uint64_t pcb_addr, __be64 *__val)
{
uint64_t val;
int64_t rc;
rc = xscom_read(partid, pcb_addr, &val);
*__val = cpu_to_be64(val);
return rc;
}
opal_call(OPAL_XSCOM_READ, opal_xscom_read, 3);
int _xscom_write(uint32_t partid, uint64_t pcb_addr, uint64_t val, bool take_lock)
{
struct scom_controller *scom;
uint32_t gcid;
int rc;
/* Handle part ID decoding */
switch(partid >> 28) {
case 0: /* Normal processor chip */
gcid = partid;
break;
case 4: /* EX chiplet */
gcid = xscom_decode_chiplet(partid, &pcb_addr);
break;
default:
/* is it one of our hacks? */
scom = scom_find(partid);
if (scom)
return scom_write(scom, partid, pcb_addr, val);
/**
* @fwts-label XSCOMWriteInvalidPartID
* @fwts-advice xscom_write was called with an invalid partid.
* There's likely a bug somewhere in the stack that's causing
* someone to try an xscom_write on something that isn't a
* processor, Centaur or EX chiplet.
*/
prerror("%s: invalid XSCOM partid 0x%x\n", __func__, partid);
return OPAL_PARAMETER;
}
/* HW822317 requires us to do global locking */
if (take_lock)
lock(&xscom_lock);
/* Direct vs indirect access */
if (pcb_addr & XSCOM_ADDR_IND_FLAG)
rc = xscom_indirect_write(gcid, pcb_addr, val);
else
rc = __xscom_write(gcid, pcb_addr & 0x7fffffff, val);
/* Unlock it */
if (take_lock)
unlock(&xscom_lock);
return rc;
}
static int64_t opal_xscom_write(uint32_t partid, uint64_t pcb_addr, uint64_t val)
{
return xscom_write(partid, pcb_addr, val);
}
opal_call(OPAL_XSCOM_WRITE, opal_xscom_write, 3);
/*
* Perform a xscom read-modify-write.
*/
int xscom_write_mask(uint32_t partid, uint64_t pcb_addr, uint64_t val, uint64_t mask)
{
int rc;
uint64_t old_val;
rc = xscom_read(partid, pcb_addr, &old_val);
if (rc)
return rc;
val = (old_val & ~mask) | (val & mask);
return xscom_write(partid, pcb_addr, val);
}
int xscom_readme(uint64_t pcb_addr, uint64_t *val)
{
return xscom_read(this_cpu()->chip_id, pcb_addr, val);
}
int xscom_writeme(uint64_t pcb_addr, uint64_t val)
{
return xscom_write(this_cpu()->chip_id, pcb_addr, val);
}
int64_t xscom_read_cfam_chipid(uint32_t partid, uint32_t *chip_id)
{
uint64_t val;
int64_t rc = OPAL_SUCCESS;
/* Mambo chip model lacks the f000f register, just make
* something up
*/
if (chip_quirk(QUIRK_NO_F000F)) {
if (proc_gen == proc_gen_p10)
val = 0x220DA04980000000UL; /* P10 DD2.0 */
else if (proc_gen == proc_gen_p9)
val = 0x203D104980000000UL; /* P9 Nimbus DD2.3 */
else
val = 0x221EF04980000000UL; /* P8 Murano DD2.1 */
} else
rc = xscom_read(partid, 0xf000f, &val);
/* Extract CFAM id */
if (rc == OPAL_SUCCESS)
*chip_id = (uint32_t)(val >> 44);
return rc;
}
static void xscom_init_chip_info(struct proc_chip *chip)
{
uint32_t val;
int64_t rc;
rc = xscom_read_cfam_chipid(chip->id, &val);
if (rc) {
prerror("XSCOM: Error %lld reading 0xf000f register\n", rc);
/* We leave chip type to UNKNOWN */
return;
}
/* Identify chip */
switch(val & 0xff) {
case 0xef:
chip->type = PROC_CHIP_P8_MURANO;
assert(proc_gen == proc_gen_p8);
break;
case 0xea:
chip->type = PROC_CHIP_P8_VENICE;
assert(proc_gen == proc_gen_p8);
break;
case 0xd3:
chip->type = PROC_CHIP_P8_NAPLES;
assert(proc_gen == proc_gen_p8);
break;
case 0xd1:
chip->type = PROC_CHIP_P9_NIMBUS;
assert(proc_gen == proc_gen_p9);
break;
case 0xd4:
chip->type = PROC_CHIP_P9_CUMULUS;
assert(proc_gen == proc_gen_p9);
break;
case 0xd9:
chip->type = PROC_CHIP_P9P;
assert(proc_gen == proc_gen_p9);
break;
case 0xda:
chip->type = PROC_CHIP_P10;
assert(proc_gen == proc_gen_p10);
break;
default:
printf("CHIP: Unknown chip type 0x%02x !!!\n",
(unsigned char)(val & 0xff));
}
/* Get EC level from CFAM ID */
chip->ec_level = ((val >> 16) & 0xf) << 4;
chip->ec_level |= (val >> 8) & 0xf;
/*
* On P9, grab the ECID bits to differenciate
* DD1.01, 1.02, 2.00, etc...
*/
if (chip_quirk(QUIRK_MAMBO_CALLOUTS)) {
chip->ec_rev = 0;
} else if (proc_gen == proc_gen_p9) {
uint64_t ecid2 = 0;
uint8_t rev;
xscom_read(chip->id, 0x18002, &ecid2);
switch((ecid2 >> 45) & 7) {
case 0:
rev = 0;
break;
case 1:
rev = 1;
break;
case 3:
rev = 2;
break;
case 7:
rev = 3;
break;
default:
rev = 0;
}
prlog(PR_INFO,"P9 DD%i.%i%d detected\n", 0xf & (chip->ec_level >> 4),
chip->ec_level & 0xf, rev);
chip->ec_rev = rev;
} /* XXX P10 */
}
/*
* This function triggers xstop by writing to XSCOM.
* Machine would enter xstop state post completion of this.
*/
int64_t xscom_trigger_xstop(void)
{
int rc = OPAL_UNSUPPORTED;
bool xstop_disabled = false;
if (nvram_query_eq_dangerous("opal-sw-xstop", "disable"))
xstop_disabled = true;
if (xstop_disabled) {
prlog(PR_NOTICE, "Software initiated checkstop disabled.\n");
return rc;
}
if (xstop_xscom.addr)
rc = xscom_writeme(xstop_xscom.addr,
PPC_BIT(xstop_xscom.fir_bit));
return rc;
}
void xscom_init(void)
{
struct dt_node *xn;
const struct dt_property *p;
dt_for_each_compatible(dt_root, xn, "ibm,xscom") {
uint32_t gcid = dt_get_chip_id(xn);
const struct dt_property *reg;
struct proc_chip *chip;
const char *chip_name;
static const char *chip_names[] = {
"UNKNOWN", "P8E", "P8", "P8NVL", "P9N", "P9C", "P9P",
"P10",
};
chip = get_chip(gcid);
assert(chip);
/* XXX We need a proper address parsing. For now, we just
* "know" that we are looking at a u64
*/
reg = dt_find_property(xn, "reg");
assert(reg);
chip->xscom_base = dt_translate_address(xn, 0, NULL);
/* Grab processor type and EC level */
xscom_init_chip_info(chip);
if (chip->type >= ARRAY_SIZE(chip_names))
chip_name = "INVALID";
else
chip_name = chip_names[chip->type];
/* We keep a "CHIP" prefix to make the log more user-friendly */
prlog(PR_NOTICE, "CHIP: Chip ID %04x type: %s DD%x.%x%d\n",
gcid, chip_name, chip->ec_level >> 4,
chip->ec_level & 0xf, chip->ec_rev);
prlog(PR_DEBUG, "XSCOM: Base address: 0x%llx\n", chip->xscom_base);
}
/* Collect details to trigger xstop via XSCOM write */
p = dt_find_property(dt_root, "ibm,sw-checkstop-fir");
if (p) {
xstop_xscom.addr = dt_property_get_cell(p, 0);
xstop_xscom.fir_bit = dt_property_get_cell(p, 1);
prlog(PR_DEBUG, "XSTOP: XSCOM addr = 0x%llx, FIR bit = %lld\n",
xstop_xscom.addr, xstop_xscom.fir_bit);
} else
prlog(PR_DEBUG, "XSTOP: ibm,sw-checkstop-fir prop not found\n");
}
void xscom_used_by_console(void)
{
xscom_lock.in_con_path = true;
/*
* Some other processor might hold it without having
* disabled the console locally so let's make sure that
* is over by taking/releasing the lock ourselves
*/
lock(&xscom_lock);
unlock(&xscom_lock);
}
bool xscom_ok(void)
{
return !lock_held_by_me(&xscom_lock);
}
|