1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. 24 * Copyright 2022 Oxide Computer Co. 25 */ 26 27 /* 28 * "Generic AMD" model-specific support. If no more-specific support can 29 * be found, or such modules declines to initialize, then for AuthenticAMD 30 * cpus this module can have a crack at providing some AMD model-specific 31 * support that at least goes beyond common MCA architectural features 32 * if not down to the nitty-gritty level for a particular model. We 33 * are layered on top of a cpu module, likely cpu.generic, so there is no 34 * need for us to perform common architecturally-accessible functions. 35 */ 36 37 #include <sys/types.h> 38 #include <sys/cmn_err.h> 39 #include <sys/modctl.h> 40 #include <sys/cpu_module.h> 41 #include <sys/mca_x86.h> 42 #include <sys/pci_cfgspace.h> 43 #include <sys/x86_archext.h> 44 #include <sys/mc_amd.h> 45 #include <sys/fm/protocol.h> 46 #include <sys/fm/cpu/GENAMD.h> 47 #include <sys/fm/smb/fmsmb.h> 48 #include <sys/fm/util.h> 49 #include <sys/nvpair.h> 50 #include <sys/controlregs.h> 51 #include <sys/pghw.h> 52 #include <sys/sunddi.h> 53 #include <sys/sysmacros.h> 54 #include <sys/cpu_module_ms_impl.h> 55 56 #include "authamd.h" 57 58 extern int x86gentopo_legacy; /* x86 generic topo support */ 59 60 int authamd_ms_support_disable = 0; 61 62 #define AUTHAMD_F_REVS_BCDE \ 63 (X86_CHIPREV_AMD_LEGACY_F_REV_B | X86_CHIPREV_AMD_LEGACY_F_REV_C0 | \ 64 X86_CHIPREV_AMD_LEGACY_F_REV_CG | X86_CHIPREV_AMD_LEGACY_F_REV_D | \ 65 X86_CHIPREV_AMD_LEGACY_F_REV_E) 66 67 #define AUTHAMD_F_REVS_FG \ 68 (X86_CHIPREV_AMD_LEGACY_F_REV_F | X86_CHIPREV_AMD_LEGACY_F_REV_G) 69 70 #define AUTHAMD_10_REVS_AB \ 71 (X86_CHIPREV_AMD_LEGACY_10_REV_A | X86_CHIPREV_AMD_LEGACY_10_REV_B) 72 73 /* 74 * Bitmasks of support for various features. Try to enable features 75 * via inclusion in one of these bitmasks and check that at the 76 * feature imlementation - that way new family support may often simply 77 * simply need to update these bitmasks. 78 */ 79 80 /* 81 * Models that include an on-chip NorthBridge. 82 */ 83 #define AUTHAMD_NBONCHIP(rev) \ 84 (chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_B) || \ 85 chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A)) 86 87 /* 88 * Families/revisions for which we can recognise main memory ECC errors. 89 */ 90 #define AUTHAMD_MEMECC_RECOGNISED(rev) \ 91 (chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_B) || \ 92 chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A)) 93 94 /* 95 * Families/revisions that have an Online Spare Control Register 96 */ 97 #define AUTHAMD_HAS_ONLINESPARECTL(rev) \ 98 (chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_F) || \ 99 chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A)) 100 101 /* 102 * Families/revisions for which we will perform NB MCA Config changes 103 */ 104 #define AUTHAMD_DO_NBMCACFG(rev) \ 105 (chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_B) || \ 106 chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A)) 107 108 /* 109 * Families/revisions that have chip cache scrubbers. 110 */ 111 #define AUTHAMD_HAS_CHIPSCRUB(rev) \ 112 (chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_B) || \ 113 chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A)) 114 115 /* 116 * Families/revisions that have a NB misc register or registers - 117 * evaluates to 0 if no support, otherwise the number of MC4_MISCj. 118 */ 119 #define AUTHAMD_NBMISC_NUM(rev) \ 120 (chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_F)? 1 : \ 121 (chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A) ? 3 : 0)) 122 123 /* 124 * Families/revision for which we wish not to machine check for GART 125 * table walk errors - bit 10 of NB CTL. 126 */ 127 #define AUTHAMD_NOGARTTBLWLK_MC(rev) \ 128 (chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_F_REV_B) || \ 129 chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A)) 130 131 /* 132 * Families/revisions that are potentially L3 capable 133 */ 134 #define AUTHAMD_L3CAPABLE(rev) \ 135 (chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A)) 136 137 /* 138 * Families/revisions that support x8 ChipKill ECC 139 */ 140 #define AUTHAMD_SUPPORTS_X8ECC(rev) \ 141 (chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_D0)) 142 143 /* 144 * We recognise main memory ECC errors for AUTHAMD_MEMECC_RECOGNISED 145 * revisions as: 146 * 147 * - being reported by the NB 148 * - being a compound bus/interconnect error (external to chip) 149 * - having LL of LG 150 * - having II of MEM (but could still be a master/target abort) 151 * - having CECC or UECC set 152 * 153 * We do not check the extended error code (first nibble of the 154 * model-specific error code on AMD) since this has changed from 155 * family 0xf to family 0x10 (ext code 0 now reserved on family 0x10). 156 * Instead we use CECC/UECC to separate off the master/target 157 * abort cases. 158 * 159 * We insist that the detector be the NorthBridge bank; although 160 * IC/DC can report some main memory errors, they do not capture 161 * an address at sufficient resolution to be useful and the NB will 162 * report most errors. 163 */ 164 #define AUTHAMD_IS_MEMECCERR(bank, status) \ 165 ((bank) == AMD_MCA_BANK_NB && \ 166 MCAX86_ERRCODE_ISBUS_INTERCONNECT(MCAX86_ERRCODE(status)) && \ 167 MCAX86_ERRCODE_LL(MCAX86_ERRCODE(status)) == MCAX86_ERRCODE_LL_LG && \ 168 MCAX86_ERRCODE_II(MCAX86_ERRCODE(status)) == MCAX86_ERRCODE_II_MEM && \ 169 ((status) & (AMD_BANK_STAT_CECC | AMD_BANK_STAT_UECC))) 170 171 static authamd_error_disp_t authamd_memce_disp = { 172 FM_EREPORT_CPU_GENAMD, 173 FM_EREPORT_CPU_GENAMD_MEM_CE, 174 FM_EREPORT_GENAMD_PAYLOAD_FLAGS_MEM_CE 175 }; 176 177 static authamd_error_disp_t authamd_memue_disp = { 178 FM_EREPORT_CPU_GENAMD, 179 FM_EREPORT_CPU_GENAMD_MEM_UE, 180 FM_EREPORT_GENAMD_PAYLOAD_FLAGS_MEM_UE 181 }; 182 183 static authamd_error_disp_t authamd_ckmemce_disp = { 184 FM_EREPORT_CPU_GENAMD, 185 FM_EREPORT_CPU_GENAMD_CKMEM_CE, 186 FM_EREPORT_GENAMD_PAYLOAD_FLAGS_CKMEM_CE 187 }; 188 189 static authamd_error_disp_t authamd_ckmemue_disp = { 190 FM_EREPORT_CPU_GENAMD, 191 FM_EREPORT_CPU_GENAMD_CKMEM_UE, 192 FM_EREPORT_GENAMD_PAYLOAD_FLAGS_CKMEM_UE 193 }; 194 195 /* 196 * We recognise GART walk errors as: 197 * 198 * - being reported by the NB 199 * - being a compound TLB error 200 * - having LL of LG and TT of GEN 201 * - having UC set 202 * - possibly having PCC set (if source CPU) 203 */ 204 #define AUTHAMD_IS_GARTERR(bank, status) \ 205 ((bank) == AMD_MCA_BANK_NB && \ 206 MCAX86_ERRCODE_ISTLB(MCAX86_ERRCODE(status)) && \ 207 MCAX86_ERRCODE_LL(MCAX86_ERRCODE(status)) == MCAX86_ERRCODE_LL_LG && \ 208 MCAX86_ERRCODE_TT(MCAX86_ERRCODE(status)) == MCAX86_ERRCODE_TT_GEN && \ 209 (status) & MSR_MC_STATUS_UC) 210 211 static authamd_error_disp_t authamd_gart_disp = { 212 FM_EREPORT_CPU_GENAMD, /* use generic subclass */ 213 FM_EREPORT_CPU_GENADM_GARTTBLWLK, /* use generic leafclass */ 214 0 /* no additional payload */ 215 }; 216 217 218 static struct authamd_nodeshared *authamd_shared[AUTHAMD_MAX_NODES]; 219 220 static int 221 authamd_chip_once(authamd_data_t *authamd, enum authamd_cfgonce_bitnum what) 222 { 223 return (atomic_set_long_excl(&authamd->amd_shared->ans_cfgonce, 224 what) == 0 ? B_TRUE : B_FALSE); 225 } 226 227 static void 228 authamd_pcicfg_write(uint_t procnodeid, uint_t func, uint_t reg, uint32_t val) 229 { 230 ASSERT(procnodeid + 24 <= 31); 231 ASSERT((func & 7) == func); 232 ASSERT((reg & 3) == 0 && reg < 4096); 233 234 cmi_pci_putl(0, procnodeid + 24, func, reg, 0, val); 235 } 236 237 static uint32_t 238 authamd_pcicfg_read(uint_t procnodeid, uint_t func, uint_t reg) 239 { 240 ASSERT(procnodeid + 24 <= 31); 241 ASSERT((func & 7) == func); 242 ASSERT((reg & 3) == 0 && reg < 4096); 243 244 return (cmi_pci_getl(0, procnodeid + 24, func, reg, 0, 0)); 245 } 246 247 void 248 authamd_bankstatus_prewrite(cmi_hdl_t hdl, authamd_data_t *authamd) 249 { 250 uint64_t hwcr; 251 252 if (cmi_hdl_rdmsr(hdl, MSR_AMD_HWCR, &hwcr) != CMI_SUCCESS) 253 return; 254 255 authamd->amd_hwcr = hwcr; 256 257 if (!(hwcr & AMD_HWCR_MCI_STATUS_WREN)) { 258 hwcr |= AMD_HWCR_MCI_STATUS_WREN; 259 (void) cmi_hdl_wrmsr(hdl, MSR_AMD_HWCR, hwcr); 260 } 261 } 262 263 void 264 authamd_bankstatus_postwrite(cmi_hdl_t hdl, authamd_data_t *authamd) 265 { 266 uint64_t hwcr = authamd->amd_hwcr; 267 268 if (!(hwcr & AMD_HWCR_MCI_STATUS_WREN)) { 269 hwcr &= ~AMD_HWCR_MCI_STATUS_WREN; 270 (void) cmi_hdl_wrmsr(hdl, MSR_AMD_HWCR, hwcr); 271 } 272 } 273 274 /* 275 * Read EccCnt repeatedly for all possible channel/chip-select combos: 276 * 277 * - read sparectl register 278 * - if EccErrCntWrEn is set, clear that bit in the just-read value 279 * and write it back to sparectl; this *may* clobber the EccCnt 280 * for the channel/chip-select combination currently selected, so 281 * we leave this bit clear if we had to clear it 282 * - cycle through all channel/chip-select combinations writing each 283 * combination to sparectl before reading the register back for 284 * EccCnt for that combination; since EccErrCntWrEn is clear 285 * the writes to select what count to read will not themselves 286 * zero any counts 287 */ 288 static int 289 authamd_read_ecccnt(authamd_data_t *authamd, struct authamd_logout *msl) 290 { 291 union mcreg_sparectl sparectl; 292 uint_t procnodeid = authamd->amd_shared->ans_procnodeid; 293 uint_t family = authamd->amd_shared->ans_family; 294 x86_chiprev_t rev = authamd->amd_shared->ans_rev; 295 int chan, cs; 296 297 /* 298 * Check for feature support; this macro will test down to the 299 * family revision number, whereafter we'll switch on family 300 * assuming that future revisions will use the same register 301 * format. 302 */ 303 if (!AUTHAMD_HAS_ONLINESPARECTL(rev)) { 304 bzero(&msl->aal_eccerrcnt, sizeof (msl->aal_eccerrcnt)); 305 return (0); 306 } 307 308 MCREG_VAL32(&sparectl) = 309 authamd_pcicfg_read(procnodeid, MC_FUNC_MISCCTL, 310 MC_CTL_REG_SPARECTL); 311 312 switch (family) { 313 case AUTHAMD_FAMILY_F: 314 MCREG_FIELD_F_revFG(&sparectl, EccErrCntWrEn) = 0; 315 break; 316 317 case AUTHAMD_FAMILY_10: 318 MCREG_FIELD_10_revAB(&sparectl, EccErrCntWrEn) = 0; 319 break; 320 } 321 322 for (chan = 0; chan < AUTHAMD_DRAM_NCHANNEL; chan++) { 323 switch (family) { 324 case AUTHAMD_FAMILY_F: 325 MCREG_FIELD_F_revFG(&sparectl, EccErrCntDramChan) = 326 chan; 327 break; 328 329 case AUTHAMD_FAMILY_10: 330 MCREG_FIELD_10_revAB(&sparectl, EccErrCntDramChan) = 331 chan; 332 break; 333 } 334 335 for (cs = 0; cs < AUTHAMD_DRAM_NCS; cs++) { 336 switch (family) { 337 case AUTHAMD_FAMILY_F: 338 MCREG_FIELD_F_revFG(&sparectl, 339 EccErrCntDramCs) = cs; 340 break; 341 342 case AUTHAMD_FAMILY_10: 343 MCREG_FIELD_10_revAB(&sparectl, 344 EccErrCntDramCs) = cs; 345 break; 346 } 347 348 authamd_pcicfg_write(procnodeid, MC_FUNC_MISCCTL, 349 MC_CTL_REG_SPARECTL, MCREG_VAL32(&sparectl)); 350 351 MCREG_VAL32(&sparectl) = authamd_pcicfg_read(procnodeid, 352 MC_FUNC_MISCCTL, MC_CTL_REG_SPARECTL); 353 354 switch (family) { 355 case AUTHAMD_FAMILY_F: 356 msl->aal_eccerrcnt[chan][cs] = 357 MCREG_FIELD_F_revFG(&sparectl, EccErrCnt); 358 break; 359 case AUTHAMD_FAMILY_10: 360 msl->aal_eccerrcnt[chan][cs] = 361 MCREG_FIELD_10_revAB(&sparectl, EccErrCnt); 362 break; 363 } 364 } 365 } 366 367 return (1); 368 } 369 370 /* 371 * Clear EccCnt for all possible channel/chip-select combos: 372 * 373 * - set EccErrCntWrEn in sparectl, if necessary 374 * - write 0 to EccCnt for all channel/chip-select combinations 375 * - clear EccErrCntWrEn 376 * 377 * If requested also disable the interrupts taken on counter overflow 378 * and on swap done. 379 */ 380 static void 381 authamd_clear_ecccnt(authamd_data_t *authamd, boolean_t clrint) 382 { 383 union mcreg_sparectl sparectl; 384 uint_t procnodeid = authamd->amd_shared->ans_procnodeid; 385 uint_t family = authamd->amd_shared->ans_family; 386 x86_chiprev_t rev = authamd->amd_shared->ans_rev; 387 int chan, cs; 388 389 if (!AUTHAMD_HAS_ONLINESPARECTL(rev)) 390 return; 391 392 MCREG_VAL32(&sparectl) = 393 authamd_pcicfg_read(procnodeid, MC_FUNC_MISCCTL, 394 MC_CTL_REG_SPARECTL); 395 396 switch (family) { 397 case AUTHAMD_FAMILY_F: 398 MCREG_FIELD_F_revFG(&sparectl, EccErrCntWrEn) = 1; 399 if (clrint) { 400 MCREG_FIELD_F_revFG(&sparectl, EccErrInt) = 0; 401 MCREG_FIELD_F_revFG(&sparectl, SwapDoneInt) = 0; 402 } 403 break; 404 405 case AUTHAMD_FAMILY_10: 406 MCREG_FIELD_10_revAB(&sparectl, EccErrCntWrEn) = 1; 407 if (clrint) { 408 MCREG_FIELD_10_revAB(&sparectl, EccErrInt) = 0; 409 MCREG_FIELD_10_revAB(&sparectl, SwapDoneInt) = 0; 410 } 411 break; 412 } 413 414 authamd_pcicfg_write(procnodeid, MC_FUNC_MISCCTL, 415 MC_CTL_REG_SPARECTL, MCREG_VAL32(&sparectl)); 416 417 for (chan = 0; chan < AUTHAMD_DRAM_NCHANNEL; chan++) { 418 switch (family) { 419 case AUTHAMD_FAMILY_F: 420 MCREG_FIELD_F_revFG(&sparectl, EccErrCntDramChan) = 421 chan; 422 break; 423 424 case AUTHAMD_FAMILY_10: 425 MCREG_FIELD_10_revAB(&sparectl, EccErrCntDramChan) = 426 chan; 427 break; 428 } 429 430 for (cs = 0; cs < AUTHAMD_DRAM_NCS; cs++) { 431 switch (family) { 432 case AUTHAMD_FAMILY_F: 433 MCREG_FIELD_F_revFG(&sparectl, 434 EccErrCntDramCs) = cs; 435 MCREG_FIELD_F_revFG(&sparectl, 436 EccErrCnt) = 0; 437 break; 438 439 case AUTHAMD_FAMILY_10: 440 MCREG_FIELD_10_revAB(&sparectl, 441 EccErrCntDramCs) = cs; 442 MCREG_FIELD_10_revAB(&sparectl, 443 EccErrCnt) = 0; 444 break; 445 } 446 447 authamd_pcicfg_write(procnodeid, MC_FUNC_MISCCTL, 448 MC_CTL_REG_SPARECTL, MCREG_VAL32(&sparectl)); 449 } 450 } 451 } 452 453 454 /* 455 * Return 456 * 1: supported 457 * 0: unsupported 458 */ 459 static int 460 authamd_supported(cmi_hdl_t hdl) 461 { 462 uint_t family = cmi_hdl_family(hdl); 463 464 switch (family) { 465 case AUTHAMD_FAMILY_6: 466 case AUTHAMD_FAMILY_F: 467 case AUTHAMD_FAMILY_10: 468 return (1); 469 default: 470 return (0); 471 } 472 } 473 474 /* 475 * cms_init entry point. 476 * 477 * This module provides broad model-specific support for AMD families 478 * 0x6, 0xf and 0x10. Future families will have to be evaluated once their 479 * documentation is available. 480 */ 481 int 482 authamd_init(cmi_hdl_t hdl, void **datap) 483 { 484 uint_t chipid = cmi_hdl_chipid(hdl); 485 uint_t procnodeid = cmi_hdl_procnodeid(hdl); 486 struct authamd_nodeshared *sp, *osp; 487 uint_t family = cmi_hdl_family(hdl); 488 x86_chiprev_t rev = cmi_hdl_chiprev(hdl); 489 authamd_data_t *authamd; 490 uint64_t cap; 491 492 if (authamd_ms_support_disable || 493 !authamd_supported(hdl)) 494 return (ENOTSUP); 495 496 if (!is_x86_feature(x86_featureset, X86FSET_MCA)) 497 return (ENOTSUP); 498 499 if (cmi_hdl_rdmsr(hdl, IA32_MSR_MCG_CAP, &cap) != CMI_SUCCESS) 500 return (ENOTSUP); 501 502 if (!(cap & MCG_CAP_CTL_P)) 503 return (ENOTSUP); 504 505 authamd = *datap = kmem_zalloc(sizeof (authamd_data_t), KM_SLEEP); 506 cmi_hdl_hold(hdl); /* release in fini */ 507 authamd->amd_hdl = hdl; 508 509 if ((sp = authamd_shared[procnodeid]) == NULL) { 510 sp = kmem_zalloc(sizeof (struct authamd_nodeshared), KM_SLEEP); 511 sp->ans_chipid = chipid; 512 sp->ans_procnodeid = procnodeid; 513 sp->ans_family = family; 514 sp->ans_rev = rev; 515 membar_producer(); 516 517 osp = atomic_cas_ptr(&authamd_shared[procnodeid], NULL, sp); 518 if (osp != NULL) { 519 kmem_free(sp, sizeof (struct authamd_nodeshared)); 520 sp = osp; 521 } 522 } 523 authamd->amd_shared = sp; 524 525 return (0); 526 } 527 528 /* 529 * cms_logout_size entry point. 530 */ 531 /*ARGSUSED*/ 532 size_t 533 authamd_logout_size(cmi_hdl_t hdl) 534 { 535 return (sizeof (struct authamd_logout)); 536 } 537 538 /* 539 * cms_mcgctl_val entry point 540 * 541 * Instead of setting all bits to 1 we can set just those for the 542 * error detector banks known to exist. 543 */ 544 /*ARGSUSED*/ 545 uint64_t 546 authamd_mcgctl_val(cmi_hdl_t hdl, int nbanks, uint64_t proposed) 547 { 548 return (nbanks < 64 ? (1ULL << nbanks) - 1 : proposed); 549 } 550 551 /* 552 * cms_bankctl_skipinit entry point 553 * 554 * On K6 we do not initialize MC0_CTL since, reportedly, this bank (for DC) 555 * may produce spurious machine checks. 556 * 557 * Only allow a single core to setup the NorthBridge MCi_CTL register. 558 */ 559 /*ARGSUSED*/ 560 boolean_t 561 authamd_bankctl_skipinit(cmi_hdl_t hdl, int bank) 562 { 563 authamd_data_t *authamd = cms_hdl_getcmsdata(hdl); 564 x86_chiprev_t rev = authamd->amd_shared->ans_rev; 565 566 if (authamd->amd_shared->ans_family == AUTHAMD_FAMILY_6) 567 return (bank == 0 ? B_TRUE : B_FALSE); 568 569 if (AUTHAMD_NBONCHIP(rev) && bank == AMD_MCA_BANK_NB) { 570 return (authamd_chip_once(authamd, AUTHAMD_CFGONCE_NBMCA) == 571 B_TRUE ? B_FALSE : B_TRUE); 572 } 573 574 return (B_FALSE); 575 } 576 577 /* 578 * cms_bankctl_val entry point 579 */ 580 uint64_t 581 authamd_bankctl_val(cmi_hdl_t hdl, int bank, uint64_t proposed) 582 { 583 authamd_data_t *authamd = cms_hdl_getcmsdata(hdl); 584 x86_chiprev_t rev = authamd->amd_shared->ans_rev; 585 uint64_t val = proposed; 586 587 /* 588 * The Intel MCA says we can write all 1's to enable #MC for 589 * all errors, and AMD docs say much the same. But, depending 590 * perhaps on other config registers, taking machine checks 591 * for some errors such as GART TLB errors and master/target 592 * aborts may be bad - they set UC and sometime also PCC, but 593 * we should not always panic for these error types. 594 * 595 * Our cms_error_action entry point can suppress such panics, 596 * however we can also use the cms_bankctl_val entry point to 597 * veto enabling of some of the known villains in the first place. 598 */ 599 if (bank == AMD_MCA_BANK_NB && AUTHAMD_NOGARTTBLWLK_MC(rev)) 600 val &= ~AMD_NB_EN_GARTTBLWK; 601 602 return (val); 603 } 604 605 /* 606 * Bits to add to NB MCA config (after watchdog config). 607 */ 608 uint32_t authamd_nb_mcacfg_add = AMD_NB_CFG_ADD_CMN; 609 610 /* 611 * Bits to remove from NB MCA config (after watchdog config) 612 */ 613 uint32_t authamd_nb_mcacfg_remove = AMD_NB_CFG_REMOVE_CMN; 614 615 /* 616 * NB Watchdog policy, and rate we use if enabling. 617 */ 618 enum { 619 AUTHAMD_NB_WDOG_LEAVEALONE, 620 AUTHAMD_NB_WDOG_DISABLE, 621 AUTHAMD_NB_WDOG_ENABLE_IF_DISABLED, 622 AUTHAMD_NB_WDOG_ENABLE_FORCE_RATE 623 } authamd_nb_watchdog_policy = AUTHAMD_NB_WDOG_ENABLE_IF_DISABLED; 624 625 uint32_t authamd_nb_mcacfg_wdog = AMD_NB_CFG_WDOGTMRCNTSEL_4095 | 626 AMD_NB_CFG_WDOGTMRBASESEL_1MS; 627 628 /* 629 * Per-core cache scrubbing policy and rates. 630 */ 631 enum { 632 AUTHAMD_SCRUB_BIOSDEFAULT, /* leave as BIOS configured */ 633 AUTHAMD_SCRUB_FIXED, /* assign our chosen rate */ 634 AUTHAMD_SCRUB_MAX /* use higher of ours and BIOS rate */ 635 } authamd_scrub_policy = AUTHAMD_SCRUB_MAX; 636 637 uint32_t authamd_scrub_rate_dcache = 0xf; /* 64K per 0.67 seconds */ 638 uint32_t authamd_scrub_rate_l2cache = 0xe; /* 1MB per 5.3 seconds */ 639 uint32_t authamd_scrub_rate_l3cache = 0xd; /* 1MB per 2.7 seconds */ 640 641 static uint32_t 642 authamd_scrubrate(uint32_t osrate, uint32_t biosrate, const char *varnm) 643 { 644 uint32_t rate; 645 646 if (osrate > AMD_NB_SCRUBCTL_RATE_MAX) { 647 cmn_err(CE_WARN, "%s is too large, resetting to 0x%x\n", 648 varnm, AMD_NB_SCRUBCTL_RATE_MAX); 649 osrate = AMD_NB_SCRUBCTL_RATE_MAX; 650 } 651 652 switch (authamd_scrub_policy) { 653 case AUTHAMD_SCRUB_FIXED: 654 rate = osrate; 655 break; 656 657 default: 658 cmn_err(CE_WARN, "Unknown authamd_scrub_policy %d - " 659 "using default policy of AUTHAMD_SCRUB_MAX", 660 authamd_scrub_policy); 661 /*FALLTHRU*/ 662 663 case AUTHAMD_SCRUB_MAX: 664 if (osrate != 0 && biosrate != 0) 665 rate = MIN(osrate, biosrate); /* small is fast */ 666 else 667 rate = osrate ? osrate : biosrate; 668 } 669 670 return (rate); 671 } 672 673 /* 674 * cms_mca_init entry point. 675 */ 676 /*ARGSUSED*/ 677 void 678 authamd_mca_init(cmi_hdl_t hdl, int nbanks) 679 { 680 authamd_data_t *authamd = cms_hdl_getcmsdata(hdl); 681 x86_chiprev_t rev = authamd->amd_shared->ans_rev; 682 uint_t procnodeid = authamd->amd_shared->ans_procnodeid; 683 684 /* 685 * On chips with a NB online spare control register take control 686 * and clear ECC counts. 687 */ 688 if (AUTHAMD_HAS_ONLINESPARECTL(rev) && 689 authamd_chip_once(authamd, AUTHAMD_CFGONCE_ONLNSPRCFG)) { 690 authamd_clear_ecccnt(authamd, B_TRUE); 691 } 692 693 /* 694 * And since we are claiming the telemetry stop the BIOS receiving 695 * an SMI on NB threshold overflow. 696 */ 697 if (AUTHAMD_NBMISC_NUM(rev) && 698 authamd_chip_once(authamd, AUTHAMD_CFGONCE_NBTHRESH)) { 699 union mcmsr_nbmisc nbm; 700 int i; 701 702 authamd_bankstatus_prewrite(hdl, authamd); 703 704 for (i = 0; i < AUTHAMD_NBMISC_NUM(rev); i++) { 705 if (cmi_hdl_rdmsr(hdl, MC_MSR_NB_MISC(i), 706 (uint64_t *)&nbm) != CMI_SUCCESS) 707 continue; 708 709 if (chiprev_at_least(rev, 710 X86_CHIPREV_AMD_LEGACY_F_REV_F) && 711 MCMSR_FIELD_F_revFG(&nbm, mcmisc_Valid) && 712 MCMSR_FIELD_F_revFG(&nbm, mcmisc_CntP)) { 713 MCMSR_FIELD_F_revFG(&nbm, mcmisc_IntType) = 0; 714 } else if (chiprev_at_least(rev, 715 X86_CHIPREV_AMD_LEGACY_10_REV_A) && 716 MCMSR_FIELD_10_revAB(&nbm, mcmisc_Valid) && 717 MCMSR_FIELD_10_revAB(&nbm, mcmisc_CntP)) { 718 MCMSR_FIELD_10_revAB(&nbm, mcmisc_IntType) = 0; 719 } 720 721 (void) cmi_hdl_wrmsr(hdl, MC_MSR_NB_MISC(i), 722 MCMSR_VAL(&nbm)); 723 } 724 725 authamd_bankstatus_postwrite(hdl, authamd); 726 } 727 728 /* 729 * NB MCA Configuration Register. 730 */ 731 if (AUTHAMD_DO_NBMCACFG(rev) && 732 authamd_chip_once(authamd, AUTHAMD_CFGONCE_NBMCACFG)) { 733 uint32_t val = authamd_pcicfg_read(procnodeid, MC_FUNC_MISCCTL, 734 MC_CTL_REG_NBCFG); 735 736 switch (authamd_nb_watchdog_policy) { 737 case AUTHAMD_NB_WDOG_LEAVEALONE: 738 break; 739 740 case AUTHAMD_NB_WDOG_DISABLE: 741 val &= ~(AMD_NB_CFG_WDOGTMRBASESEL_MASK | 742 AMD_NB_CFG_WDOGTMRCNTSEL_MASK); 743 val |= AMD_NB_CFG_WDOGTMRDIS; 744 break; 745 746 default: 747 cmn_err(CE_NOTE, "authamd_nb_watchdog_policy=%d " 748 "unrecognised, using default policy", 749 authamd_nb_watchdog_policy); 750 /*FALLTHRU*/ 751 752 case AUTHAMD_NB_WDOG_ENABLE_IF_DISABLED: 753 if (!(val & AMD_NB_CFG_WDOGTMRDIS)) 754 break; /* if enabled leave rate intact */ 755 /*FALLTHRU*/ 756 757 case AUTHAMD_NB_WDOG_ENABLE_FORCE_RATE: 758 val &= ~(AMD_NB_CFG_WDOGTMRBASESEL_MASK | 759 AMD_NB_CFG_WDOGTMRCNTSEL_MASK | 760 AMD_NB_CFG_WDOGTMRDIS); 761 val |= authamd_nb_mcacfg_wdog; 762 break; 763 } 764 765 /* 766 * Bit 0 of the NB MCA Config register is reserved on family 767 * 0x10. 768 */ 769 if (chiprev_at_least(rev, X86_CHIPREV_AMD_LEGACY_10_REV_A)) 770 authamd_nb_mcacfg_add &= ~AMD_NB_CFG_CPUECCERREN; 771 772 val &= ~authamd_nb_mcacfg_remove; 773 val |= authamd_nb_mcacfg_add; 774 775 authamd_pcicfg_write(procnodeid, MC_FUNC_MISCCTL, 776 MC_CTL_REG_NBCFG, val); 777 } 778 779 /* 780 * Cache scrubbing. We can't enable DRAM scrubbing since 781 * we don't know the DRAM base for this node. 782 */ 783 if (AUTHAMD_HAS_CHIPSCRUB(rev) && 784 authamd_scrub_policy != AUTHAMD_SCRUB_BIOSDEFAULT && 785 authamd_chip_once(authamd, AUTHAMD_CFGONCE_CACHESCRUB)) { 786 uint32_t val = authamd_pcicfg_read(procnodeid, MC_FUNC_MISCCTL, 787 MC_CTL_REG_SCRUBCTL); 788 int l3cap = 0; 789 790 if (AUTHAMD_L3CAPABLE(rev)) { 791 l3cap = (authamd_pcicfg_read(procnodeid, 792 MC_FUNC_MISCCTL, MC_CTL_REG_NBCAP) & 793 MC_NBCAP_L3CAPABLE) != 0; 794 } 795 796 authamd_scrub_rate_dcache = 797 authamd_scrubrate(authamd_scrub_rate_dcache, 798 (val & AMD_NB_SCRUBCTL_DC_MASK) >> AMD_NB_SCRUBCTL_DC_SHIFT, 799 "authamd_scrub_rate_dcache"); 800 801 authamd_scrub_rate_l2cache = 802 authamd_scrubrate(authamd_scrub_rate_l2cache, 803 (val & AMD_NB_SCRUBCTL_L2_MASK) >> AMD_NB_SCRUBCTL_L2_SHIFT, 804 "authamd_scrub_rate_l2cache"); 805 806 authamd_scrub_rate_l3cache = l3cap ? 807 authamd_scrubrate(authamd_scrub_rate_l3cache, 808 (val & AMD_NB_SCRUBCTL_L3_MASK) >> AMD_NB_SCRUBCTL_L3_SHIFT, 809 "authamd_scrub_rate_l3cache") : 0; 810 811 val = AMD_NB_MKSCRUBCTL(authamd_scrub_rate_l3cache, 812 authamd_scrub_rate_dcache, authamd_scrub_rate_l2cache, 813 val & AMD_NB_SCRUBCTL_DRAM_MASK); 814 815 authamd_pcicfg_write(procnodeid, MC_FUNC_MISCCTL, 816 MC_CTL_REG_SCRUBCTL, val); 817 } 818 819 /* 820 * ECC symbol size. Defaults to 4. 821 * Set to 8 on systems that support x8 ECC and have it enabled. 822 */ 823 if (authamd_chip_once(authamd, AUTHAMD_CFGONCE_ECCSYMSZ)) { 824 authamd->amd_shared->ans_eccsymsz = "C4"; 825 if (AUTHAMD_SUPPORTS_X8ECC(rev) && 826 (authamd_pcicfg_read(procnodeid, MC_FUNC_MISCCTL, 827 MC_CTL_REG_EXTNBCFG) & MC_EXTNBCFG_ECCSYMSZ)) 828 authamd->amd_shared->ans_eccsymsz = "C8"; 829 } 830 } 831 832 /* 833 * cms_poll_ownermask entry point. 834 */ 835 uint64_t 836 authamd_poll_ownermask(cmi_hdl_t hdl, hrtime_t pintvl) 837 { 838 authamd_data_t *authamd = cms_hdl_getcmsdata(hdl); 839 struct authamd_nodeshared *ansp = authamd->amd_shared; 840 hrtime_t now = gethrtime_waitfree(); 841 hrtime_t last = ansp->ans_poll_timestamp; 842 int dopoll = 0; 843 844 if (now - last > 2 * pintvl || last == 0) { 845 ansp->ans_pollowner = hdl; 846 dopoll = 1; 847 } else if (ansp->ans_pollowner == hdl) { 848 dopoll = 1; 849 } 850 851 if (dopoll) 852 ansp->ans_poll_timestamp = now; 853 854 return (dopoll ? -1ULL : ~(1 << AMD_MCA_BANK_NB)); 855 856 } 857 858 /* 859 * cms_bank_logout entry point. 860 */ 861 /*ARGSUSED*/ 862 void 863 authamd_bank_logout(cmi_hdl_t hdl, int bank, uint64_t status, 864 uint64_t addr, uint64_t misc, void *mslogout) 865 { 866 authamd_data_t *authamd = cms_hdl_getcmsdata(hdl); 867 struct authamd_logout *msl = mslogout; 868 x86_chiprev_t rev = authamd->amd_shared->ans_rev; 869 870 if (msl == NULL) 871 return; 872 873 /* 874 * For main memory ECC errors on revisions with an Online Spare 875 * Control Register grab the ECC counts by channel and chip-select 876 * and reset them to 0. 877 */ 878 if (AUTHAMD_MEMECC_RECOGNISED(rev) && 879 AUTHAMD_IS_MEMECCERR(bank, status) && 880 AUTHAMD_HAS_ONLINESPARECTL(rev)) { 881 if (authamd_read_ecccnt(authamd, msl)) 882 authamd_clear_ecccnt(authamd, B_FALSE); 883 } 884 } 885 886 /* 887 * cms_error_action entry point 888 */ 889 890 int authamd_forgive_uc = 0; /* For test/debug only */ 891 int authamd_forgive_pcc = 0; /* For test/debug only */ 892 int authamd_fake_poison = 0; /* For test/debug only */ 893 894 /*ARGSUSED*/ 895 uint32_t 896 authamd_error_action(cmi_hdl_t hdl, int ismc, int bank, 897 uint64_t status, uint64_t addr, uint64_t misc, void *mslogout) 898 { 899 authamd_error_disp_t *disp; 900 uint32_t rv = 0; 901 902 if (authamd_forgive_uc) 903 rv |= CMS_ERRSCOPE_CLEARED_UC; 904 905 if (authamd_forgive_pcc) 906 rv |= CMS_ERRSCOPE_CURCONTEXT_OK; 907 908 if (authamd_fake_poison && status & MSR_MC_STATUS_UC) 909 rv |= CMS_ERRSCOPE_POISONED; 910 911 if (rv) 912 return (rv); 913 914 disp = authamd_disp_match(hdl, ismc, bank, status, addr, misc, 915 mslogout); 916 917 if (disp == &authamd_gart_disp) { 918 /* 919 * GART walk errors set UC and possibly PCC (if source CPU) 920 * but should not be regarded as terminal. 921 */ 922 return (CMS_ERRSCOPE_IGNORE_ERR); 923 } 924 925 /* 926 * May also want to consider master abort and target abort. These 927 * also set UC and PCC (if src CPU) but the requester gets -1 928 * and I believe the IO stuff in Solaris will handle that. 929 */ 930 931 return (rv); 932 } 933 934 /* 935 * cms_disp_match entry point 936 */ 937 /*ARGSUSED*/ 938 cms_cookie_t 939 authamd_disp_match(cmi_hdl_t hdl, int ismc, int bank, uint64_t status, 940 uint64_t addr, uint64_t misc, void *mslogout) 941 { 942 authamd_data_t *authamd = cms_hdl_getcmsdata(hdl); 943 /* uint16_t errcode = MCAX86_ERRCODE(status); */ 944 uint16_t exterrcode = AMD_EXT_ERRCODE(status); 945 x86_chiprev_t rev = authamd->amd_shared->ans_rev; 946 947 /* 948 * Recognise main memory ECC errors 949 */ 950 if (AUTHAMD_MEMECC_RECOGNISED(rev) && 951 AUTHAMD_IS_MEMECCERR(bank, status)) { 952 if (status & AMD_BANK_STAT_CECC) { 953 return (exterrcode == 0 ? &authamd_memce_disp : 954 &authamd_ckmemce_disp); 955 } else if (status & AMD_BANK_STAT_UECC) { 956 return (exterrcode == 0 ? &authamd_memue_disp : 957 &authamd_ckmemue_disp); 958 } 959 } 960 961 /* 962 * Recognise GART walk errors 963 */ 964 if (AUTHAMD_NOGARTTBLWLK_MC(rev) && AUTHAMD_IS_GARTERR(bank, status)) 965 return (&authamd_gart_disp); 966 967 return (NULL); 968 } 969 970 /* 971 * cms_ereport_class entry point 972 */ 973 /*ARGSUSED*/ 974 void 975 authamd_ereport_class(cmi_hdl_t hdl, cms_cookie_t mscookie, 976 const char **cpuclsp, const char **leafclsp) 977 { 978 const authamd_error_disp_t *aed = mscookie; 979 980 if (aed == NULL) 981 return; 982 983 if (aed->aad_subclass != NULL) 984 *cpuclsp = aed->aad_subclass; 985 if (aed->aad_leafclass != NULL) 986 *leafclsp = aed->aad_leafclass; 987 } 988 989 /*ARGSUSED*/ 990 static void 991 authamd_ereport_add_resource(cmi_hdl_t hdl, authamd_data_t *authamd, 992 nvlist_t *ereport, nv_alloc_t *nva, void *mslogout) 993 { 994 nvlist_t *elems[AUTHAMD_DRAM_NCHANNEL * AUTHAMD_DRAM_NCS]; 995 uint8_t counts[AUTHAMD_DRAM_NCHANNEL * AUTHAMD_DRAM_NCS]; 996 authamd_logout_t *msl; 997 nvlist_t *nvl; 998 int nelems = 0; 999 int i, chan, cs, mc; 1000 nvlist_t *board_list = NULL; 1001 1002 if ((msl = mslogout) == NULL) 1003 return; 1004 1005 /* Assume all processors have the same number of nodes */ 1006 mc = authamd->amd_shared->ans_procnodeid % 1007 cpuid_get_procnodes_per_pkg(CPU); 1008 1009 for (chan = 0; chan < AUTHAMD_DRAM_NCHANNEL; chan++) { 1010 for (cs = 0; cs < AUTHAMD_DRAM_NCS; cs++) { 1011 if (msl->aal_eccerrcnt[chan][cs] == 0) 1012 continue; 1013 1014 if ((nvl = fm_nvlist_create(nva)) == NULL) 1015 continue; 1016 1017 elems[nelems] = nvl; 1018 counts[nelems++] = msl->aal_eccerrcnt[chan][cs]; 1019 1020 if (!x86gentopo_legacy) { 1021 board_list = cmi_hdl_smb_bboard(hdl); 1022 if (board_list == NULL) 1023 continue; 1024 fm_fmri_hc_create(nvl, FM_HC_SCHEME_VERSION, 1025 NULL, NULL, board_list, 4, 1026 "chip", cmi_hdl_smb_chipid(hdl), 1027 "memory-controller", 0, 1028 "dram-channel", chan, 1029 "chip-select", cs); 1030 } else { 1031 fm_fmri_hc_set(nvl, FM_HC_SCHEME_VERSION, 1032 NULL, NULL, 5, 1033 "motherboard", 0, 1034 "chip", authamd->amd_shared->ans_chipid, 1035 "memory-controller", mc, 1036 "dram-channel", chan, 1037 "chip-select", cs); 1038 } 1039 } 1040 } 1041 1042 if (nelems == 0) 1043 return; 1044 1045 fm_payload_set(ereport, FM_EREPORT_GENAMD_PAYLOAD_NAME_RESOURCE, 1046 DATA_TYPE_NVLIST_ARRAY, nelems, elems, 1047 NULL); 1048 1049 fm_payload_set(ereport, FM_EREPORT_GENAMD_PAYLOAD_NAME_RESOURCECNT, 1050 DATA_TYPE_UINT8_ARRAY, nelems, &counts[0], 1051 NULL); 1052 1053 for (i = 0; i < nelems; i++) 1054 fm_nvlist_destroy(elems[i], nva ? FM_NVA_RETAIN : FM_NVA_FREE); 1055 } 1056 1057 /* 1058 * cms_ereport_add_logout entry point 1059 */ 1060 /*ARGSUSED*/ 1061 void 1062 authamd_ereport_add_logout(cmi_hdl_t hdl, nvlist_t *ereport, nv_alloc_t *nva, 1063 int bank, uint64_t status, uint64_t addr, uint64_t misc, 1064 void *mslogout, cms_cookie_t mscookie) 1065 { 1066 authamd_data_t *authamd = cms_hdl_getcmsdata(hdl); 1067 const authamd_error_disp_t *aed = mscookie; 1068 uint64_t members; 1069 1070 if (aed == NULL) 1071 return; 1072 1073 members = aed->aad_ereport_members; 1074 1075 if (members & FM_EREPORT_GENAMD_PAYLOAD_FLAG_SYND) { 1076 fm_payload_set(ereport, FM_EREPORT_GENAMD_PAYLOAD_NAME_SYND, 1077 DATA_TYPE_UINT16, (uint16_t)AMD_BANK_SYND(status), 1078 NULL); 1079 1080 if (members & FM_EREPORT_GENAMD_PAYLOAD_FLAG_SYNDTYPE) { 1081 fm_payload_set(ereport, 1082 FM_EREPORT_GENAMD_PAYLOAD_NAME_SYNDTYPE, 1083 DATA_TYPE_STRING, "E", 1084 NULL); 1085 } 1086 } 1087 1088 if (members & FM_EREPORT_GENAMD_PAYLOAD_FLAG_CKSYND) { 1089 fm_payload_set(ereport, FM_EREPORT_GENAMD_PAYLOAD_NAME_CKSYND, 1090 DATA_TYPE_UINT16, (uint16_t)AMD_NB_STAT_CKSYND(status), 1091 NULL); 1092 1093 if (members & FM_EREPORT_GENAMD_PAYLOAD_FLAG_SYNDTYPE) { 1094 fm_payload_set(ereport, 1095 FM_EREPORT_GENAMD_PAYLOAD_NAME_SYNDTYPE, 1096 DATA_TYPE_STRING, authamd->amd_shared->ans_eccsymsz, 1097 NULL); 1098 } 1099 } 1100 1101 if (members & FM_EREPORT_GENAMD_PAYLOAD_FLAG_RESOURCE && 1102 status & MSR_MC_STATUS_ADDRV) { 1103 authamd_ereport_add_resource(hdl, authamd, ereport, nva, 1104 mslogout); 1105 } 1106 } 1107 1108 /* 1109 * cms_msrinject entry point 1110 */ 1111 cms_errno_t 1112 authamd_msrinject(cmi_hdl_t hdl, uint_t msr, uint64_t val) 1113 { 1114 authamd_data_t *authamd = cms_hdl_getcmsdata(hdl); 1115 cms_errno_t rv = CMSERR_BADMSRWRITE; 1116 1117 authamd_bankstatus_prewrite(hdl, authamd); 1118 if (cmi_hdl_wrmsr(hdl, msr, val) == CMI_SUCCESS) 1119 rv = CMS_SUCCESS; 1120 authamd_bankstatus_postwrite(hdl, authamd); 1121 1122 return (rv); 1123 } 1124 1125 cms_api_ver_t _cms_api_version = CMS_API_VERSION_2; 1126 1127 const cms_ops_t _cms_ops = { 1128 authamd_init, /* cms_init */ 1129 NULL, /* cms_post_startup */ 1130 NULL, /* cms_post_mpstartup */ 1131 authamd_logout_size, /* cms_logout_size */ 1132 authamd_mcgctl_val, /* cms_mcgctl_val */ 1133 authamd_bankctl_skipinit, /* cms_bankctl_skipinit */ 1134 authamd_bankctl_val, /* cms_bankctl_val */ 1135 NULL, /* cms_bankstatus_skipinit */ 1136 NULL, /* cms_bankstatus_val */ 1137 authamd_mca_init, /* cms_mca_init */ 1138 authamd_poll_ownermask, /* cms_poll_ownermask */ 1139 authamd_bank_logout, /* cms_bank_logout */ 1140 authamd_error_action, /* cms_error_action */ 1141 authamd_disp_match, /* cms_disp_match */ 1142 authamd_ereport_class, /* cms_ereport_class */ 1143 NULL, /* cms_ereport_detector */ 1144 NULL, /* cms_ereport_includestack */ 1145 authamd_ereport_add_logout, /* cms_ereport_add_logout */ 1146 authamd_msrinject, /* cms_msrinject */ 1147 NULL, /* cms_fini */ 1148 }; 1149 1150 static struct modlcpu modlcpu = { 1151 &mod_cpuops, 1152 "Generic AMD model-specific MCA" 1153 }; 1154 1155 static struct modlinkage modlinkage = { 1156 MODREV_1, 1157 (void *)&modlcpu, 1158 NULL 1159 }; 1160 1161 int 1162 _init(void) 1163 { 1164 return (mod_install(&modlinkage)); 1165 } 1166 1167 int 1168 _info(struct modinfo *modinfop) 1169 { 1170 return (mod_info(&modlinkage, modinfop)); 1171 } 1172 1173 int 1174 _fini(void) 1175 { 1176 return (mod_remove(&modlinkage)); 1177 } 1178