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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 * 22 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * Given a physical address and an optional syndrome, determine the 30 * name of the memory module that contains it. 31 */ 32 33 #include <sys/errno.h> 34 #include <sys/types.h> 35 #include <sys/mc.h> 36 37 #include <mcamd_api.h> 38 #include <mcamd_err.h> 39 40 extern int mc_pa_to_offset(struct mcamd_hdl *, mcamd_node_t *, mcamd_node_t *, 41 mcamd_node_t *, uint64_t, uint64_t *); 42 43 #define LO_DIMM 0x1 44 #define UP_DIMM 0x2 45 46 #define BITS(val, high, low) \ 47 ((val) & (((2ULL << (high)) - 1) & ~((1ULL << (low)) - 1))) 48 49 static int 50 iaddr_gen(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa, 51 uint64_t *iaddrp) 52 { 53 uint64_t orig = pa; 54 uint64_t mcnum, base, lim, dramaddr, ilen, ilsel, top, dramhole; 55 56 if (!mcamd_get_numprop(hdl, mc, MCAMD_PROP_NUM, &mcnum) || 57 !mcamd_get_numprop(hdl, mc, MCAMD_PROP_BASE_ADDR, &base) || 58 !mcamd_get_numprop(hdl, mc, MCAMD_PROP_LIM_ADDR, &lim) || 59 !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_ILEN, &ilen) || 60 !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_ILSEL, &ilsel) || 61 !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_HOLE, &dramhole)) { 62 mcamd_dprintf(hdl, MCAMD_DBG_ERR, "iaddr_gen: failed to " 63 "lookup required properties"); 64 return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 65 } 66 67 if (pa < base || pa > lim) { 68 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: PA 0x%llx not " 69 "in range [0x%llx, 0x%llx] of MC %d\n", pa, base, lim, 70 (int)mcnum); 71 return (mcamd_set_errno(hdl, EMCAMD_NOADDR)); 72 } 73 74 /* 75 * Rev E and later added the DRAM Hole Address Register for 76 * memory hoisting. In earlier revisions memory hoisting is 77 * achieved by following some algorithm to modify the CS bases etc, 78 * and this pa to unum algorithm will simply see those modified 79 * values. But if the Hole Address Register is being used then 80 * we need to reduce any address at or above 4GB by the size of 81 * the hole. 82 */ 83 if (dramhole & MC_DC_HOLE_VALID && pa >= 0x100000000) { 84 uint64_t holesize = (dramhole & MC_DC_HOLE_OFFSET_MASK) << 85 MC_DC_HOLE_OFFSET_LSHIFT; 86 pa -= holesize; 87 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: dram hole " 88 "valid; pa decremented from 0x%llx to 0x%llx for " 89 "a dramhole size of 0x%llx\n", orig, pa, holesize); 90 } 91 92 dramaddr = BITS(pa, 39, 0) - BITS(base, 39, 24); 93 94 if (ilen != 0) { 95 int pailsel; 96 97 if (ilen != 1 && ilen != 3 && ilen != 7) { 98 mcamd_dprintf(hdl, MCAMD_DBG_ERR, "Invalid intlven " 99 "of %d for MC %d\n", (int)ilen, (int)mcnum); 100 return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 101 } 102 103 if ((pailsel = BITS(pa, 14, 12) >> 12 & ilen) != ilsel) { 104 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: " 105 "PA 0x%llx in a %d-way node interleave indicates " 106 "selection %d, MC %d has ilsel of %d\n", 107 pa, (int)ilen + 1, pailsel, (int)mcnum, (int)ilsel); 108 return (mcamd_set_errno(hdl, EMCAMD_NOADDR)); 109 } 110 111 if (ilen == 1) 112 top = BITS(dramaddr, 36, 13) >> 1; 113 else if (ilen == 3) 114 top = BITS(dramaddr, 37, 14) >> 2; 115 else if (ilen == 7) 116 top = BITS(dramaddr, 38, 15) >> 3; 117 } else { 118 top = BITS(dramaddr, 35, 12); 119 } 120 121 *iaddrp = top | BITS(dramaddr, 11, 0); 122 123 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: PA 0x%llx in range " 124 "[0x%llx, 0x%llx] of MC %d; normalized address for cs compare " 125 "is 0x%llx\n", pa, base, lim, (int)mcnum, *iaddrp); 126 127 return (0); 128 } 129 130 static int 131 cs_match(struct mcamd_hdl *hdl, uint64_t iaddr, mcamd_node_t *cs) 132 { 133 uint64_t csnum, csbase, csmask; 134 int match; 135 136 if (!mcamd_get_numprop(hdl, cs, MCAMD_PROP_NUM, &csnum) || 137 !mcamd_get_numprop(hdl, cs, MCAMD_PROP_BASE_ADDR, &csbase) || 138 !mcamd_get_numprop(hdl, cs, MCAMD_PROP_MASK, &csmask)) { 139 mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_match: failed to lookup " 140 "required properties\n"); 141 return (0); 142 } 143 144 match = ((iaddr & ~csmask) == (csbase & ~csmask)); 145 146 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "cs_match: iaddr 0x%llx does " 147 "%smatch CS %d (base 0x%llx, mask 0x%llx)\n", iaddr, 148 match ? "" : "not ", (int)csnum, csbase, csmask); 149 150 return (match); 151 } 152 153 static int 154 unum_fill(struct mcamd_hdl *hdl, mcamd_node_t *cs, int which, 155 uint64_t iaddr, struct mc_unum *unump, int incloff) 156 { 157 mcamd_node_t *mc, *dimm; 158 uint64_t chipnum, csnum, lonum, upnum; 159 int i; 160 int offsetdimm; 161 162 if ((mc = mcamd_cs_mc(hdl, cs)) == NULL || 163 !mcamd_get_numprop(hdl, mc, MCAMD_PROP_NUM, &chipnum) || 164 !mcamd_get_numprop(hdl, cs, MCAMD_PROP_NUM, &csnum)) { 165 mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to " 166 "lookup required properties\n"); 167 return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 168 } 169 170 if ((which & LO_DIMM) && 171 !mcamd_get_numprop(hdl, cs, MCAMD_PROP_LODIMM, &lonum) || 172 (which & UP_DIMM) && 173 !mcamd_get_numprop(hdl, cs, MCAMD_PROP_UPDIMM, &upnum)) { 174 mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to " 175 "lookup lodimm/hidimm properties\n"); 176 return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 177 } 178 179 unump->unum_board = 0; 180 unump->unum_chip = chipnum; 181 unump->unum_mc = 0; 182 unump->unum_cs = csnum; 183 184 for (i = 0; i < MC_UNUM_NDIMM; i++) { 185 unump->unum_dimms[i] = -1; 186 } 187 switch (which) { 188 case LO_DIMM: 189 unump->unum_dimms[0] = lonum; 190 offsetdimm = lonum; 191 break; 192 case UP_DIMM: 193 unump->unum_dimms[0] = upnum; 194 offsetdimm = upnum; 195 break; 196 case LO_DIMM | UP_DIMM: 197 unump->unum_dimms[0] = lonum; 198 unump->unum_dimms[1] = upnum; 199 offsetdimm = lonum; 200 break; 201 } 202 203 if (!incloff) { 204 unump->unum_offset = MCAMD_RC_INVALID_OFFSET; 205 return (0); 206 } 207 208 /* 209 * We wish to calculate a dimm offset. In the paired case we will 210 * lookup the lodimm (see offsetdimm above). 211 */ 212 for (dimm = mcamd_dimm_next(hdl, mc, NULL); dimm != NULL; 213 dimm = mcamd_dimm_next(hdl, mc, dimm)) { 214 uint64_t dnum; 215 if (!mcamd_get_numprop(hdl, dimm, MCAMD_PROP_NUM, &dnum)) { 216 mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed " 217 "to lookup dimm number property\n"); 218 continue; 219 } 220 if (dnum == offsetdimm) 221 break; 222 } 223 224 if (dimm == NULL) { 225 mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to " 226 "find dimm with number %d for offset calculation\n", 227 offsetdimm); 228 unump->unum_offset = MCAMD_RC_INVALID_OFFSET; 229 return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 230 } 231 232 /* 233 * mc_pa_to_offset sets the offset to an invalid value if 234 * it hits an error. 235 */ 236 (void) mc_pa_to_offset(hdl, mc, cs, dimm, iaddr, &unump->unum_offset); 237 238 return (0); 239 } 240 241 /* 242 * We have translated a system address to a (node, chip-select). That 243 * identifies one (in 64-bit MC mode) or two (in 128-bit MC mode DIMMs, 244 * either a lodimm or a lodimm/updimm pair. For all cases except an 245 * uncorrectable ChipKill error we can interpret the address alignment and 246 * syndrome to deduce whether we are on the lodimm or updimm. 247 */ 248 static int 249 mc_whichdimm(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa, 250 uint32_t synd, int syndtype) 251 { 252 uint64_t accwidth; 253 uint_t sym, pat; 254 int lobit, hibit, data, check; 255 256 if (!mcamd_get_numprop(hdl, mc, MCAMD_PROP_ACCESS_WIDTH, &accwidth) || 257 (accwidth != 64 && accwidth != 128)) { 258 mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_whichdimm: failed " 259 "to lookup required properties\n"); 260 return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 261 } 262 263 /* 264 * In 64 bit mode only LO dimms are occupied. 265 */ 266 if (accwidth == 64) { 267 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: 64-bit mode " 268 "therefore LO_DIMM\n"); 269 return (LO_DIMM); 270 } 271 272 if (syndtype == AMD_SYNDTYPE_ECC) { 273 /* 274 * 64/8 ECC is checked separately for the upper and lower 275 * halves, so even an uncorrectable error is contained within 276 * one of the two halves. The error address is accurate to 277 * 8 bytes, so bit 4 distinguises upper from lower. 278 */ 279 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: 64/8 ECC " 280 "and PA 0x%llx is in %s half\n", pa, 281 pa & 8 ? "lower" : "upper"); 282 return (pa & 8 ? UP_DIMM : LO_DIMM); 283 } 284 285 /* 286 * ChipKill ECC (necessarily in 128-bit mode. 287 */ 288 if (mcamd_cksynd_decode(hdl, synd, &sym, &pat)) { 289 /* 290 * A correctable ChipKill syndrome and we can tell 291 * which half the error was in from the symbol number. 292 */ 293 if (mcamd_cksym_decode(hdl, sym, &lobit, &hibit, &data, 294 &check) == 0) 295 return (mcamd_set_errno(hdl, EMCAMD_SYNDINVALID)); 296 297 if (data && hibit <= 63 || check && hibit <= 7) { 298 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: " 299 "ChipKill symbol %d (%s %d..%d), so LODIMM\n", sym, 300 data ? "data" : "check", lobit, hibit); 301 return (LO_DIMM); 302 } else { 303 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: " 304 "ChipKill symbol %d (%s %d..%d), so UPDIMM\n", sym, 305 data ? "data" : "check", lobit, hibit); 306 return (UP_DIMM); 307 } 308 } else { 309 /* 310 * An uncorrectable error while in ChipKill ECC mode - can't 311 * tell which dimm or dimms the errors lie within. 312 */ 313 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichhdimm: " 314 "uncorrectable ChipKill, could be either LODIMM " 315 "or UPDIMM\n"); 316 return (LO_DIMM | UP_DIMM); 317 } 318 } 319 320 /* 321 * Brute-force BKDG pa to cs translation. The following is from BKDG 3.29 322 * so is for revisions prior to F. It is coded to look as much like the 323 * BKDG code as possible. 324 */ 325 static int 326 mc_bkdg_patounum(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa, 327 uint32_t synd, int syndtype, struct mc_unum *unump) 328 { 329 int which; 330 uint64_t mcnum; 331 mcamd_node_t *cs; 332 /* 333 * Variables as per BKDG 334 */ 335 int Ilog; 336 uint32_t SystemAddr = (uint32_t)(pa >> 8); 337 uint64_t IntlvEn, IntlvSel; 338 uint32_t DramBase, DramLimit; /* assume DramEn */ 339 uint32_t HoleOffset, HoleEn; 340 uint32_t CSBase, CSMask; /* assuume CSBE */ 341 uint32_t InputAddr, Temp; 342 343 /* 344 * Additional variables which we need since we will reading 345 * MC properties instead of PCI config space, and the MC properties 346 * are stored in a cooked state. 347 */ 348 uint64_t prop_drambase, prop_dramlimit, prop_dramhole; 349 uint64_t prop_intlven, prop_intlvsel; 350 uint64_t prop_csbase, prop_csmask; 351 352 if (!mcamd_get_numprop(hdl, mc, MCAMD_PROP_NUM, &mcnum) || 353 !mcamd_get_numprop(hdl, mc, MCAMD_PROP_BASE_ADDR, &prop_drambase) || 354 !mcamd_get_numprop(hdl, mc, MCAMD_PROP_LIM_ADDR, &prop_dramlimit) || 355 !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_HOLE, &prop_dramhole) || 356 !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_ILEN, &prop_intlven) || 357 !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_ILSEL, 358 &prop_intlvsel)) { 359 mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounm: failed " 360 "to lookup required properties\n"); 361 return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 362 } 363 364 /* 365 * Brute force deconstruction of the MC properties. If we decide to 366 * keep this then we need some of the mcamd.g defines available to us. 367 */ 368 DramBase = ((prop_drambase >> 8) & 0xffff0000) | (prop_intlven << 8); 369 IntlvEn = (DramBase & 0x00000700) >> 8; 370 DramBase &= 0xffff0000; 371 DramLimit = ((prop_dramlimit >> 8) & 0xffff0000) | (prop_intlvsel << 8); 372 IntlvSel = (DramLimit & 0x00000700) >> 8; 373 DramLimit |= 0x0000ffff; 374 HoleEn = prop_dramhole; /* uncooked */ 375 HoleOffset = (HoleEn & 0x0000ff00) << 8; 376 HoleEn &= 0x00000001; 377 378 if (!(DramBase <= SystemAddr && SystemAddr <= DramLimit)) { 379 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: " 380 "SystemAddr 0x%x derived from PA 0x%llx is not in the " 381 "address range [0x%x, 0x%x] of MC %d\n", 382 SystemAddr, pa, DramBase, DramLimit, (int)mcnum); 383 return (mcamd_set_errno(hdl, EMCAMD_NOADDR)); 384 } 385 386 if (IntlvEn) { 387 if (IntlvSel == ((SystemAddr >> 4) & IntlvEn)) { 388 switch (IntlvEn) { 389 case 1: 390 Ilog = 1; 391 break; 392 case 3: 393 Ilog = 2; 394 break; 395 case 7: 396 Ilog = 3; 397 break; 398 default: 399 return (mcamd_set_errno(hdl, 400 EMCAMD_TREEINVALID)); 401 } 402 Temp = (SystemAddr >> (4 + Ilog)) << 4; 403 InputAddr = (Temp | (SystemAddr & 0x0000000f)) << 4; 404 } else { 405 /* not this node */ 406 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: " 407 "Node interleaving, MC node %d not selected\n", 408 (int)mcnum); 409 return (mcamd_set_errno(hdl, EMCAMD_NOADDR)); 410 } 411 } else { 412 /* No interleave */ 413 InputAddr = (SystemAddr - DramBase) << 4; 414 } 415 416 if (HoleEn && SystemAddr > 0x00ffffff) 417 InputAddr -= HoleOffset; 418 419 for (cs = mcamd_cs_next(hdl, mc, NULL); cs != NULL; 420 cs = mcamd_cs_next(hdl, mc, cs)) { 421 uint64_t csnum; 422 423 if (!mcamd_get_numprop(hdl, cs, MCAMD_PROP_BASE_ADDR, 424 &prop_csbase) || 425 !mcamd_get_numprop(hdl, cs, MCAMD_PROP_MASK, 426 &prop_csmask) || 427 !mcamd_get_numprop(hdl, cs, MCAMD_PROP_NUM, &csnum)) { 428 mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounm: " 429 "failed to read cs properties\n"); 430 return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 431 } 432 433 CSBase = ((prop_csbase >> 4) & 0xffe00000) | 434 ((prop_csbase >> 4) & 0x0000fe00); 435 CSBase &= 0xffe0fe00; 436 CSMask = ((prop_csmask >> 4) & 0x3fe00000) | 437 ((prop_csmask >> 4) & 0x0000fe00); 438 CSMask = (CSMask | 0x001f01ff) & 0x3fffffff; 439 440 if (((InputAddr & ~CSMask) == (CSBase & ~CSMask))) { 441 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: " 442 "match for chip select %d of MC %d\n", (int)csnum, 443 (int)mcnum); 444 445 if ((which = mc_whichdimm(hdl, mc, pa, synd, 446 syndtype)) < 0) 447 return (-1); /* errno is set for us */ 448 449 /* 450 * The BKDG algorithm drops low-order bits that 451 * are unimportant in deriving chip-select but are 452 * included in row/col/bank mapping, so do not 453 * perform offset calculation in this case. 454 */ 455 if (unum_fill(hdl, cs, which, InputAddr, unump, 0) < 0) 456 return (-1); /* errno is set for us */ 457 458 return (0); 459 } 460 } 461 462 mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounum: in range " 463 "for MC %d but no cs responds\n", (int)mcnum); 464 465 return (mcamd_set_errno(hdl, EMCAMD_NOADDR)); 466 } 467 468 /*ARGSUSED*/ 469 static int 470 mc_patounum(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa, 471 uint32_t synd, int syndtype, struct mc_unum *unump) 472 { 473 uint64_t iaddr; 474 mcamd_node_t *cs; 475 int which; 476 #ifdef DEBUG 477 struct mc_unum bkdg_unum; 478 int bkdgres; 479 480 /* 481 * We perform the translation twice, once using the brute-force 482 * approach of the BKDG and again using a more elegant but more 483 * difficult to review against the BKDG approach. Note that both 484 * approaches need to change for rev F since it increases max CS 485 * size and so iaddr calculation etc changes. 486 */ 487 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "BKDG brute-force method begins\n"); 488 bkdgres = mc_bkdg_patounum(hdl, mc, pa, synd, syndtype, &bkdg_unum); 489 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "BKDG brute-force method ends\n"); 490 #endif 491 492 if (iaddr_gen(hdl, mc, pa, &iaddr) < 0) 493 return (-1); /* errno is set for us */ 494 495 for (cs = mcamd_cs_next(hdl, mc, NULL); cs != NULL; 496 cs = mcamd_cs_next(hdl, mc, cs)) { 497 if (cs_match(hdl, iaddr, cs)) 498 break; 499 } 500 501 if (cs == NULL) 502 return (mcamd_set_errno(hdl, EMCAMD_NOADDR)); 503 504 if ((which = mc_whichdimm(hdl, mc, pa, synd, syndtype)) < 0) 505 return (-1); /* errno is set for us */ 506 507 if (unum_fill(hdl, cs, which, iaddr, unump, 1) < 0) 508 return (-1); /* errno is set for us */ 509 510 #ifdef DEBUG 511 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "bkdgres=%d res=0\n", bkdgres); 512 #ifndef _KERNEL 513 /* offset is not checked - see note in BKDG algorithm */ 514 assert(bkdgres == 0 && unump->unum_board == bkdg_unum.unum_board && 515 unump->unum_chip == bkdg_unum.unum_chip && 516 unump->unum_mc == bkdg_unum.unum_mc && 517 unump->unum_cs == bkdg_unum.unum_cs && 518 unump->unum_dimms[0] == bkdg_unum.unum_dimms[0] && 519 unump->unum_dimms[1] == bkdg_unum.unum_dimms[1]); 520 #endif /* !_KERNEL */ 521 #endif /* DEBUG */ 522 523 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "Result: chip %d mc %d cs %d " 524 "offset 0x%llx\n", unump->unum_chip, unump->unum_mc, 525 unump->unum_cs, unump->unum_offset); 526 527 return (0); 528 } 529 530 int 531 mcamd_patounum(struct mcamd_hdl *hdl, mcamd_node_t *root, uint64_t pa, 532 uint32_t synd, int syndtype, struct mc_unum *unump) 533 { 534 mcamd_node_t *mc; 535 536 mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mcamd_patounum: pa=0x%llx, " 537 "synd=0x%x, syndtype=%d\n", pa, synd, syndtype); 538 539 if (!mcamd_synd_validate(hdl, synd, syndtype)) 540 return (mcamd_set_errno(hdl, EMCAMD_SYNDINVALID)); 541 542 for (mc = mcamd_mc_next(hdl, root, NULL); mc != NULL; 543 mc = mcamd_mc_next(hdl, root, mc)) { 544 if (mc_patounum(hdl, mc, pa, synd, syndtype, unump) == 0) 545 return (0); 546 547 if (mcamd_errno(hdl) != EMCAMD_NOADDR) 548 break; 549 } 550 551 return (-1); /* errno is set for us */ 552 } 553