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 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 #include <mem.h> 30 #include <fm/fmd_fmri.h> 31 32 #include <string.h> 33 #include <strings.h> 34 #include <sys/mem.h> 35 36 #ifdef sparc 37 #include <sys/fm/ldom.h> 38 ldom_hdl_t *mem_scheme_lhp; 39 #else 40 #include <fm/libtopo.h> 41 #endif /* sparc */ 42 43 mem_t mem; 44 45 static int 46 mem_fmri_get_unum(nvlist_t *nvl, char **unump) 47 { 48 uint8_t version; 49 char *unum; 50 51 if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 || 52 version > FM_MEM_SCHEME_VERSION || 53 nvlist_lookup_string(nvl, FM_FMRI_MEM_UNUM, &unum) != 0) 54 return (fmd_fmri_set_errno(EINVAL)); 55 56 *unump = unum; 57 58 return (0); 59 } 60 61 ssize_t 62 fmd_fmri_nvl2str(nvlist_t *nvl, char *buf, size_t buflen) 63 { 64 char format[64]; 65 ssize_t size, presz; 66 char *rawunum, *preunum, *escunum, *prefix; 67 uint64_t val; 68 int i; 69 70 if (mem_fmri_get_unum(nvl, &rawunum) < 0) 71 return (-1); /* errno is set for us */ 72 73 /* 74 * If we have a well-formed unum (hc-FMRI), use the string verbatim 75 * to form the initial mem:/// components. Otherwise use unum=%s. 76 */ 77 if (strncmp(rawunum, "hc://", 5) != 0) 78 prefix = FM_FMRI_MEM_UNUM "="; 79 else 80 prefix = ""; 81 82 /* 83 * If we have a DIMM offset, include it in the string. If we have a PA 84 * then use that. Otherwise just format the unum element. 85 */ 86 if (nvlist_lookup_uint64(nvl, FM_FMRI_MEM_OFFSET, &val) == 0) { 87 (void) snprintf(format, sizeof (format), 88 "%s:///%s%%1$s/%s=%%2$llx", 89 FM_FMRI_SCHEME_MEM, prefix, FM_FMRI_MEM_OFFSET); 90 } else if (nvlist_lookup_uint64(nvl, FM_FMRI_MEM_PHYSADDR, &val) == 0) { 91 (void) snprintf(format, sizeof (format), 92 "%s:///%s%%1$s/%s=%%2$llx", 93 FM_FMRI_SCHEME_MEM, prefix, FM_FMRI_MEM_PHYSADDR); 94 } else { 95 (void) snprintf(format, sizeof (format), 96 "%s:///%s%%1$s", FM_FMRI_SCHEME_MEM, prefix); 97 } 98 99 /* 100 * If we have a well-formed unum (hc-FMRI), we skip over the 101 * the scheme and authority prefix. 102 * Otherwise, the spaces and colons will be escaped, 103 * rendering the resulting FMRI pretty much unreadable. 104 * We're therefore going to do some escaping of our own first. 105 */ 106 if (strncmp(rawunum, "hc://", 5) == 0) { 107 rawunum += 5; 108 rawunum = strchr(rawunum, '/'); 109 ++rawunum; 110 /* LINTED: variable format specifier */ 111 size = snprintf(buf, buflen, format, rawunum, val); 112 } else { 113 preunum = fmd_fmri_strdup(rawunum); 114 presz = strlen(preunum) + 1; 115 116 for (i = 0; i < presz - 1; i++) { 117 if (preunum[i] == ':' && preunum[i + 1] == ' ') { 118 bcopy(preunum + i + 2, preunum + i + 1, 119 presz - (i + 2)); 120 } else if (preunum[i] == ' ') { 121 preunum[i] = ','; 122 } 123 } 124 125 escunum = fmd_fmri_strescape(preunum); 126 fmd_fmri_free(preunum, presz); 127 128 /* LINTED: variable format specifier */ 129 size = snprintf(buf, buflen, format, escunum, val); 130 fmd_fmri_strfree(escunum); 131 } 132 133 return (size); 134 } 135 136 int 137 fmd_fmri_expand(nvlist_t *nvl) 138 { 139 char *unum, **serids; 140 uint_t nnvlserids; 141 size_t nserids; 142 int rc; 143 144 if ((mem_fmri_get_unum(nvl, &unum) < 0) || (*unum == '\0')) 145 return (fmd_fmri_set_errno(EINVAL)); 146 147 if ((rc = nvlist_lookup_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, 148 &serids, &nnvlserids)) == 0) { /* already have serial #s */ 149 mem_expand_opt(nvl, unum, serids); 150 return (0); 151 } else if (rc != ENOENT) 152 return (fmd_fmri_set_errno(EINVAL)); 153 154 if (mem_get_serids_by_unum(unum, &serids, &nserids) < 0) { 155 /* errno is set for us */ 156 if (errno == ENOTSUP) 157 return (0); /* nothing to add - no s/n support */ 158 else 159 return (-1); 160 } 161 162 rc = nvlist_add_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, serids, 163 nserids); 164 mem_expand_opt(nvl, unum, serids); 165 166 mem_strarray_free(serids, nserids); 167 168 if (rc != 0) 169 return (fmd_fmri_set_errno(EINVAL)); 170 else 171 return (0); 172 } 173 174 #ifdef sparc 175 static int 176 serids_eq(char **serids1, uint_t nserids1, char **serids2, uint_t nserids2) 177 { 178 int i; 179 180 if (nserids1 != nserids2) 181 return (0); 182 183 for (i = 0; i < nserids1; i++) { 184 if (strcmp(serids1[i], serids2[i]) != 0) 185 return (0); 186 } 187 188 return (1); 189 } 190 #endif /* sparc */ 191 192 int 193 fmd_fmri_present(nvlist_t *nvl) 194 { 195 char *unum = NULL; 196 int rc; 197 #ifdef sparc 198 char **nvlserids, **serids; 199 uint_t nnvlserids; 200 size_t nserids; 201 uint64_t memconfig; 202 #else 203 struct topo_hdl *thp; 204 nvlist_t *unum_nvl; 205 int err; 206 #endif /* sparc */ 207 208 if (mem_fmri_get_unum(nvl, &unum) < 0) 209 return (-1); /* errno is set for us */ 210 211 #ifdef sparc 212 if (nvlist_lookup_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, &nvlserids, 213 &nnvlserids) != 0) { 214 /* 215 * Some mem scheme FMRIs don't have serial ids because 216 * either the platform does not support them, or because 217 * the FMRI was created before support for serial ids was 218 * introduced. If this is the case, assume it is there. 219 */ 220 if (mem.mem_dm == NULL) 221 return (1); 222 else 223 return (fmd_fmri_set_errno(EINVAL)); 224 } 225 226 /* 227 * Hypervisor will change the memconfig value when the mapping of 228 * pages to DIMMs changes, e.g. for change in DIMM size or interleave. 229 * If we detect such a change, we discard ereports associated with a 230 * previous memconfig value as invalid. 231 * 232 * The test (mem.mem_memconfig != 0) means we run on a system that 233 * actually suplies a memconfig value. 234 */ 235 236 if ((nvlist_lookup_uint64(nvl, FM_FMRI_MEM_MEMCONFIG, 237 &memconfig) == 0) && (mem.mem_memconfig != 0) && 238 (memconfig != mem.mem_memconfig)) 239 return (0); 240 241 if (mem_get_serids_by_unum(unum, &serids, &nserids) < 0) { 242 if (errno == ENOTSUP) 243 return (1); /* assume it's there, no s/n support here */ 244 if (errno != ENOENT) { 245 /* 246 * Errors are only signalled to the caller if they're 247 * the caller's fault. This isn't - it's a failure on 248 * our part to burst or read the serial numbers. We'll 249 * whine about it, and tell the caller the named 250 * module(s) isn't/aren't there. 251 */ 252 fmd_fmri_warn("failed to retrieve serial number for " 253 "unum %s", unum); 254 } 255 return (0); 256 } 257 258 rc = serids_eq(serids, nserids, nvlserids, nnvlserids); 259 260 mem_strarray_free(serids, nserids); 261 #else 262 /* 263 * On X86 we will invoke the topo is_present method passing in the 264 * unum, which is in hc scheme. The libtopo hc-scheme is_present method 265 * will invoke the node-specific is_present method, which is implemented 266 * by the chip enumerator for rank nodes. The rank node's is_present 267 * method will compare the serial number in the unum with the current 268 * serial to determine if the same DIMM is present. 269 */ 270 if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL) { 271 fmd_fmri_warn("failed to get handle to topology"); 272 return (-1); 273 } 274 if (topo_fmri_str2nvl(thp, unum, &unum_nvl, &err) == 0) { 275 rc = topo_fmri_present(thp, unum_nvl, &err); 276 nvlist_free(unum_nvl); 277 } else 278 rc = fmd_fmri_set_errno(EINVAL); 279 fmd_fmri_topo_rele(thp); 280 281 #endif /* sparc */ 282 return (rc); 283 } 284 285 int 286 fmd_fmri_contains(nvlist_t *er, nvlist_t *ee) 287 { 288 char *erunum, *eeunum; 289 uint64_t erval = 0, eeval = 0; 290 291 if (mem_fmri_get_unum(er, &erunum) < 0 || 292 mem_fmri_get_unum(ee, &eeunum) < 0) 293 return (-1); /* errno is set for us */ 294 295 if (mem_unum_contains(erunum, eeunum) <= 0) 296 return (0); /* can't parse/match, so assume no containment */ 297 298 if (nvlist_lookup_uint64(er, FM_FMRI_MEM_OFFSET, &erval) == 0) { 299 return (nvlist_lookup_uint64(ee, 300 FM_FMRI_MEM_OFFSET, &eeval) == 0 && erval == eeval); 301 } 302 303 if (nvlist_lookup_uint64(er, FM_FMRI_MEM_PHYSADDR, &erval) == 0) { 304 return (nvlist_lookup_uint64(ee, 305 FM_FMRI_MEM_PHYSADDR, &eeval) == 0 && erval == eeval); 306 } 307 308 return (1); 309 } 310 311 /* 312 * We can only make a usable/unusable determination for pages. Mem FMRIs 313 * without page addresses will be reported as usable since Solaris has no 314 * way at present to dynamically disable an entire DIMM or DIMM pair. 315 */ 316 int 317 fmd_fmri_unusable(nvlist_t *nvl) 318 { 319 uint64_t val; 320 uint8_t version; 321 int rc, err1, err2; 322 nvlist_t *nvlcp = NULL; 323 int retval; 324 325 if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 || 326 version > FM_MEM_SCHEME_VERSION) 327 return (fmd_fmri_set_errno(EINVAL)); 328 329 err1 = nvlist_lookup_uint64(nvl, FM_FMRI_MEM_OFFSET, &val); 330 err2 = nvlist_lookup_uint64(nvl, FM_FMRI_MEM_PHYSADDR, &val); 331 332 if (err1 == ENOENT && err2 == ENOENT) 333 return (0); /* no page, so assume it's still usable */ 334 335 if ((err1 != 0 && err1 != ENOENT) || (err2 != 0 && err2 != ENOENT)) 336 return (fmd_fmri_set_errno(EINVAL)); 337 338 if ((err1 = mem_unum_rewrite(nvl, &nvlcp)) != 0) 339 return (fmd_fmri_set_errno(err1)); 340 341 /* 342 * Ask the kernel if the page is retired, using either the rewritten 343 * hc FMRI or the original mem FMRI with the specified offset or PA. 344 * Refer to the kernel's page_retire_check() for the error codes. 345 */ 346 rc = mem_page_cmd(MEM_PAGE_FMRI_ISRETIRED, nvlcp ? nvlcp : nvl); 347 348 if (rc == -1 && errno == EIO) { 349 /* 350 * The page is not retired and is not scheduled for retirement 351 * (i.e. no request pending and has not seen any errors) 352 */ 353 retval = 0; 354 } else if (rc == 0 || errno == EAGAIN || errno == EINVAL) { 355 /* 356 * The page has been retired, is in the process of being 357 * retired, or doesn't exist. The latter is valid if the page 358 * existed in the past but has been DR'd out. 359 */ 360 retval = 1; 361 } else { 362 /* 363 * Errors are only signalled to the caller if they're the 364 * caller's fault. This isn't - it's a failure of the 365 * retirement-check code. We'll whine about it and tell 366 * the caller the page is unusable. 367 */ 368 fmd_fmri_warn("failed to determine page %s=%llx usability: " 369 "rc=%d errno=%d\n", err1 == 0 ? FM_FMRI_MEM_OFFSET : 370 FM_FMRI_MEM_PHYSADDR, (u_longlong_t)val, rc, errno); 371 retval = 1; 372 } 373 374 if (nvlcp) 375 nvlist_free(nvlcp); 376 377 return (retval); 378 } 379 380 int 381 fmd_fmri_init(void) 382 { 383 #ifdef sparc 384 mem_scheme_lhp = ldom_init(fmd_fmri_alloc, fmd_fmri_free); 385 #endif /* sparc */ 386 return (mem_discover()); 387 } 388 389 void 390 fmd_fmri_fini(void) 391 { 392 mem_dimm_map_t *dm, *em; 393 mem_bank_map_t *bm, *cm; 394 mem_grp_t *gm, *hm; 395 mem_seg_map_t *sm, *tm; 396 397 for (dm = mem.mem_dm; dm != NULL; dm = em) { 398 em = dm->dm_next; 399 fmd_fmri_strfree(dm->dm_label); 400 fmd_fmri_strfree(dm->dm_part); 401 fmd_fmri_strfree(dm->dm_device); 402 fmd_fmri_free(dm, sizeof (mem_dimm_map_t)); 403 } 404 for (bm = mem.mem_bank; bm != NULL; bm = cm) { 405 cm = bm->bm_next; 406 fmd_fmri_free(bm, sizeof (mem_bank_map_t)); 407 } 408 for (gm = mem.mem_group; gm != NULL; gm = hm) { 409 hm = gm->mg_next; 410 fmd_fmri_free(gm, sizeof (mem_grp_t)); 411 } 412 for (sm = mem.mem_seg; sm != NULL; sm = tm) { 413 tm = sm->sm_next; 414 fmd_fmri_free(sm, sizeof (mem_seg_map_t)); 415 } 416 #ifdef sparc 417 ldom_fini(mem_scheme_lhp); 418 #endif /* sparc */ 419 } 420