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 /*
30 * DIMM unum/device map construction
31 *
32 * The map is constructed from PICL configuration files, which contain a map
33 * between a form of the unum and the device to be used for serial number
34 * retrieval. We massage the PICL unum into a form that matches the one used
35 * by mem FMRIs, creating a map entry from the munged version. As described
36 * below, two configuration files must be correlated to determine the correct
37 * device path, and thus to build the mem_dimm_map_t list. While platforms
38 * without PICL configuration files are acceptable (some platforms, like
39 * Serengeti and Starcat, don't have configuration files as of this writing),
40 * platforms with only one or the other aren't.
41 *
42 * On Sun4v platforms, we read the 'mdesc' machine description file in order
43 * to obtain the mapping between dimm unum+jnum strings (which denote slot
44 * names) and the serial numbers of the dimms occupying those slots.
45 */
46
47 #include <sys/param.h>
48 #include <sys/mdesc.h>
49
50 #include <mem.h>
51 #include <fm/fmd_fmri.h>
52
53 #include <fcntl.h>
54 #include <unistd.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <strings.h>
59 #include <errno.h>
60 #include <time.h>
61 #include <sys/mem.h>
62 #include <sys/fm/ldom.h>
63
64 extern ldom_hdl_t *mem_scheme_lhp;
65
66 #define PICL_FRUTREE_PATH \
67 "%s/usr/platform/%s/lib/picl/plugins/piclfrutree.conf"
68
69 #define PICL_FRUDATA_PATH \
70 "%s/usr/platform/%s/lib/picl/plugins/libpiclfrudata.conf"
71
72 typedef struct mem_path_map {
73 struct mem_path_map *pm_next;
74 char *pm_path;
75 char *pm_fullpath;
76 } mem_path_map_t;
77
78 typedef struct label_xlators {
79 const char *lx_infmt;
80 uint_t lx_matches;
81 const char *lx_outfmt;
82 } label_xlators_t;
83
84 /*
85 * PICL configuration files use a different format for the DIMM name (unum)
86 * than that used in mem FMRIs. The following patterns and routine are used
87 * to convert between the PICL and unum formats.
88 */
89 static const label_xlators_t label_xlators[] = {
90 { "/system-board/mem-slot?Label=J%4d%5$n", 1,
91 "J%04d" },
92 { "/system-board/mem-slot?Label=DIMM%1d%5$n", 1,
93 "DIMM%d" },
94 { "/system-board/cpu-mem-slot?Label=%4$c/mem-slot?Label=J%1$4d%5$n", 2,
95 "Slot %4$c: J%1$4d" },
96 { "/MB/system-board/mem-slot?Label=DIMM%1d%5$n", 1,
97 "DIMM%d" },
98 { "/MB/system-board/P%1d/cpu/B%1d/bank/D%1d%5$n", 3,
99 "MB/P%d/B%d/D%d" },
100 { "/MB/system-board/C%1d/cpu-module/P0/cpu/B%1d/bank/D%1d%5$n", 3,
101 "MB/C%d/P0/B%d/D%d" },
102 { "/MB/system-board/DIMM%1d%5$n", 1,
103 "MB/DIMM%d" },
104 { "/C%1d/system-board/P0/cpu/B%1d/bank/D%1d%5$n", 3,
105 "C%d/P0/B%d/D%d" },
106 { NULL }
107 };
108
109 static int
label_xlate(char * buf)110 label_xlate(char *buf)
111 {
112 const label_xlators_t *xlator;
113
114 if (strncmp(buf, "/frutree/chassis", 16) != 0)
115 return (0);
116
117 for (xlator = label_xlators; xlator->lx_infmt != NULL; xlator++) {
118 uint_t len, a1, a2, a3;
119 char a4;
120
121 if (sscanf(buf + 16, xlator->lx_infmt, &a1, &a2, &a3, &a4,
122 &len) == xlator->lx_matches && len == strlen(buf + 16)) {
123 (void) sprintf(buf, xlator->lx_outfmt, a1, a2, a3, a4);
124 return (0);
125 }
126 }
127
128 return (fmd_fmri_set_errno(EINVAL));
129 }
130
131 /*
132 * Match two paths taken from picl files. This is a normal component-based path
133 * comparison, but for the fact that components `foo' and `foo@1,2' are assumed
134 * to be equal. `foo@1,2' and `foo@3,4', however, are not assumed to be equal.
135 */
136 static int
picl_path_eq(const char * p1,const char * p2)137 picl_path_eq(const char *p1, const char *p2)
138 {
139 for (;;) {
140 if (*p1 == *p2) {
141 if (*p1 == '\0')
142 return (1);
143 else {
144 p1++;
145 p2++;
146 continue;
147 }
148 }
149
150 if (*p1 == '@' && (*p2 == '/' || *p2 == '\0')) {
151 while (*p1 != '/' && *p1 != '\0')
152 p1++;
153 continue;
154 }
155
156 if ((*p1 == '/' || *p1 == '\0') && *p2 == '@') {
157 while (*p2 != '/' && *p2 != '\0')
158 p2++;
159 continue;
160 }
161
162 return (0);
163 }
164 }
165
166 /*
167 * PICL paths begin with `/platform' instead of `/devices', as they are
168 * intended to reference points in the PICL tree, rather than places in the
169 * device tree. Furthermore, some paths use the construct `?UnitAddress=a,b'
170 * instead of `@a,b' to indicate unit number and address. This routine
171 * replaces both constructs with forms more appropriate for /devices path
172 * lookup.
173 */
174 static void
path_depicl(char * path)175 path_depicl(char *path)
176 {
177 char *c;
178
179 if (strncmp(path, "name:", 4) == 0)
180 bcopy(path + 5, path, strlen(path + 5) + 1);
181
182 for (c = path; (c = strstr(c, "?UnitAddress=")) != NULL; c++) {
183 uint_t len = 0;
184
185 (void) sscanf(c + 13, "%*x,%*x%n", &len);
186 if (len == 0)
187 continue;
188
189 *c = '@';
190 bcopy(c + 13, c + 1, strlen(c + 13) + 1);
191 }
192 }
193
194 /*
195 * The libpiclfrudata configuration file contains a map between the generic
196 * (minor-less) device and the specific device to be used for SPD/SEEPROM
197 * data access.
198 *
199 * Entries are of the form:
200 *
201 * name:/platform/generic-path
202 * PROP FRUDevicePath string r 0 "full-path"
203 *
204 * Where `generic-path' is the path, sans minor name, to be used for DIMM
205 * data access, and `full-path' is the path with the minor name.
206 */
207 static int
picl_frudata_parse(char * buf,char * path,void * arg)208 picl_frudata_parse(char *buf, char *path, void *arg)
209 {
210 mem_path_map_t **mapp = arg;
211 mem_path_map_t *pm = NULL;
212 char fullpath[BUFSIZ];
213 uint_t len;
214
215 if (sscanf(buf, " PROP FRUDevicePath string r 0 \"%[^\"]\" \n%n",
216 fullpath, &len) != 1 || fullpath[0] == '\0' || len != strlen(buf))
217 return (0);
218
219 path_depicl(path);
220
221 pm = fmd_fmri_alloc(sizeof (mem_path_map_t));
222 pm->pm_path = fmd_fmri_strdup(path);
223 pm->pm_fullpath = fmd_fmri_strdup(fullpath);
224
225 pm->pm_next = *mapp;
226 *mapp = pm;
227
228 return (1);
229 }
230
231 /*
232 * The piclfrutree configuration file contains a map between a form of the
233 * DIMM's unum and the generic (minor-less) device used for SPD/SEEPROM data
234 * access.
235 *
236 * Entries are of the form:
237 *
238 * name:/frutree/chassis/picl-unum
239 * REFNODE mem-module fru WITH /platform/generic-path
240 *
241 * Where `picl-unum' is the PICL form of the unum, which we'll massage into
242 * the form compatible with FMRIs (see label_xlate), and `generic-path' is
243 * the minor-less path into the PICL tree for the device used to access the
244 * DIMM. It is this path that will be used as the key in the frudata
245 * configuration file to determine the proper /devices path.
246 */
247 typedef struct dimm_map_arg {
248 mem_path_map_t *dma_pm;
249 mem_dimm_map_t *dma_dm;
250 } dimm_map_arg_t;
251
252 static int
picl_frutree_parse(char * buf,char * label,void * arg)253 picl_frutree_parse(char *buf, char *label, void *arg)
254 {
255 dimm_map_arg_t *dma = arg;
256 mem_dimm_map_t *dm = NULL;
257 mem_path_map_t *pm;
258 char path[BUFSIZ];
259 uint_t len;
260
261 /* LINTED - sscanf cannot exceed sizeof (path) */
262 if (sscanf(buf, " REFNODE mem-module fru WITH %s \n%n",
263 path, &len) != 1 || path[0] == '\0' || len != strlen(buf))
264 return (0);
265
266 if (label_xlate(label) < 0)
267 return (-1); /* errno is set for us */
268
269 path_depicl(path);
270
271 for (pm = dma->dma_pm; pm != NULL; pm = pm->pm_next) {
272 if (picl_path_eq(pm->pm_path, path)) {
273 (void) strcpy(path, pm->pm_fullpath);
274 break;
275 }
276 }
277
278 dm = fmd_fmri_zalloc(sizeof (mem_dimm_map_t));
279 dm->dm_label = fmd_fmri_strdup(label);
280 dm->dm_device = fmd_fmri_strdup(path);
281
282 dm->dm_next = dma->dma_dm;
283 dma->dma_dm = dm;
284
285 return (1);
286 }
287
288 /*
289 * Both configuration files use the same format, thus allowing us to use the
290 * same parser to process them.
291 */
292 static int
picl_conf_parse(const char * pathpat,int (* func)(char *,char *,void *),void * arg)293 picl_conf_parse(const char *pathpat, int (*func)(char *, char *, void *),
294 void *arg)
295 {
296 char confpath[MAXPATHLEN];
297 char buf[BUFSIZ], label[BUFSIZ];
298 int line, len, rc;
299 FILE *fp;
300
301 (void) snprintf(confpath, sizeof (confpath), pathpat,
302 fmd_fmri_get_rootdir(), fmd_fmri_get_platform());
303
304 if ((fp = fopen(confpath, "r")) == NULL)
305 return (-1); /* errno is set for us */
306
307 label[0] = '\0';
308 for (line = 1; fgets(buf, sizeof (buf), fp) != NULL; line++) {
309 if (buf[0] == '#')
310 continue;
311
312 if (buf[0] == '\n') {
313 label[0] = '\0';
314 continue;
315 }
316
317 /* LINTED - label length cannot exceed length of buf */
318 if (sscanf(buf, " name:%s \n%n", label, &len) == 1 &&
319 label[0] != '\0' && len == strlen(buf))
320 continue;
321
322 if (label[0] != '\0') {
323 if ((rc = func(buf, label, arg)) < 0) {
324 int err = errno;
325 (void) fclose(fp);
326 return (fmd_fmri_set_errno(err));
327 } else if (rc != 0) {
328 label[0] = '\0';
329 }
330 }
331 }
332
333 (void) fclose(fp);
334 return (0);
335 }
336
337 static void
path_map_destroy(mem_path_map_t * pm)338 path_map_destroy(mem_path_map_t *pm)
339 {
340 mem_path_map_t *next;
341
342 for (/* */; pm != NULL; pm = next) {
343 next = pm->pm_next;
344
345 fmd_fmri_strfree(pm->pm_path);
346 fmd_fmri_strfree(pm->pm_fullpath);
347 fmd_fmri_free(pm, sizeof (mem_path_map_t));
348 }
349 }
350
351 int
mem_discover(void)352 mem_discover(void)
353 {
354 mem_path_map_t *path_map = NULL;
355 dimm_map_arg_t dma;
356 int rc;
357
358 if (picl_conf_parse(PICL_FRUDATA_PATH, picl_frudata_parse,
359 &path_map) < 0 && errno != ENOENT)
360 return (-1); /* errno is set for us */
361
362 dma.dma_pm = path_map;
363 dma.dma_dm = NULL;
364
365 if ((rc = picl_conf_parse(PICL_FRUTREE_PATH, picl_frutree_parse,
366 &dma)) < 0 && errno == ENOENT && path_map == NULL) {
367 /*
368 * This platform doesn't support serial number retrieval via
369 * PICL mapping files. Unfortunate, but not an error.
370 */
371 return (0);
372 }
373
374 path_map_destroy(path_map);
375
376 if (rc < 0)
377 return (-1); /* errno is set for us */
378
379 if (dma.dma_dm == NULL) {
380 /*
381 * This platform should support DIMM serial numbers, but we
382 * weren't able to derive the paths. Return an error.
383 */
384 return (fmd_fmri_set_errno(EIO));
385 }
386
387 mem.mem_dm = dma.dma_dm;
388 return (0);
389 }
390
391 /*
392 * Retry values for handling the case where the kernel is not yet ready
393 * to provide DIMM serial ids. Some platforms acquire DIMM serial id
394 * information from their System Controller via a mailbox interface.
395 * The values chosen are for 10 retries 3 seconds apart to approximate the
396 * possible 30 second timeout length of a mailbox message request.
397 */
398 #define MAX_MEM_SID_RETRIES 10
399 #define MEM_SID_RETRY_WAIT 3
400
401 /*
402 * The comparison is asymmetric. It compares up to the length of the
403 * argument unum.
404 */
405 static mem_dimm_map_t *
dm_lookup(const char * name)406 dm_lookup(const char *name)
407 {
408 mem_dimm_map_t *dm;
409
410 for (dm = mem.mem_dm; dm != NULL; dm = dm->dm_next) {
411 if (strncmp(name, dm->dm_label, strlen(name)) == 0)
412 return (dm);
413 }
414
415 return (NULL);
416 }
417
418 /*
419 * Returns 0 with serial numbers if found, -1 (with errno set) for errors. If
420 * the unum (or a component of same) wasn't found, -1 is returned with errno
421 * set to ENOENT. If the kernel doesn't have support for serial numbers,
422 * -1 is returned with errno set to ENOTSUP.
423 */
424 static int
mem_get_serids_from_kernel(const char * unum,char *** seridsp,size_t * nseridsp)425 mem_get_serids_from_kernel(const char *unum, char ***seridsp, size_t *nseridsp)
426 {
427 char **dimms, **serids;
428 size_t ndimms, nserids;
429 int i, rc = 0;
430 int fd;
431 int retries = MAX_MEM_SID_RETRIES;
432 mem_name_t mn;
433 struct timespec rqt;
434
435 if ((fd = open("/dev/mem", O_RDONLY)) < 0)
436 return (-1);
437
438 if (mem_unum_burst(unum, &dimms, &ndimms) < 0) {
439 (void) close(fd);
440 return (-1); /* errno is set for us */
441 }
442
443 serids = fmd_fmri_zalloc(sizeof (char *) * ndimms);
444 nserids = ndimms;
445
446 bzero(&mn, sizeof (mn));
447
448 for (i = 0; i < ndimms; i++) {
449 mn.m_namelen = strlen(dimms[i]) + 1;
450 mn.m_sidlen = MEM_SERID_MAXLEN;
451
452 mn.m_name = fmd_fmri_alloc(mn.m_namelen);
453 mn.m_sid = fmd_fmri_alloc(mn.m_sidlen);
454
455 (void) strcpy(mn.m_name, dimms[i]);
456
457 do {
458 rc = ioctl(fd, MEM_SID, &mn);
459
460 if (rc >= 0 || errno != EAGAIN)
461 break;
462
463 if (retries == 0) {
464 errno = ETIMEDOUT;
465 break;
466 }
467
468 /*
469 * EAGAIN indicates the kernel is
470 * not ready to provide DIMM serial
471 * ids. Sleep MEM_SID_RETRY_WAIT seconds
472 * and try again.
473 * nanosleep() is used instead of sleep()
474 * to avoid interfering with fmd timers.
475 */
476 rqt.tv_sec = MEM_SID_RETRY_WAIT;
477 rqt.tv_nsec = 0;
478 (void) nanosleep(&rqt, NULL);
479
480 } while (retries--);
481
482 if (rc < 0) {
483 /*
484 * ENXIO can happen if the kernel memory driver
485 * doesn't have the MEM_SID ioctl (e.g. if the
486 * kernel hasn't been patched to provide the
487 * support).
488 *
489 * If the MEM_SID ioctl is available but the
490 * particular platform doesn't support providing
491 * serial ids, ENOTSUP will be returned by the ioctl.
492 */
493 if (errno == ENXIO)
494 errno = ENOTSUP;
495 fmd_fmri_free(mn.m_name, mn.m_namelen);
496 fmd_fmri_free(mn.m_sid, mn.m_sidlen);
497 mem_strarray_free(serids, nserids);
498 mem_strarray_free(dimms, ndimms);
499 (void) close(fd);
500 return (-1);
501 }
502
503 serids[i] = fmd_fmri_strdup(mn.m_sid);
504
505 fmd_fmri_free(mn.m_name, mn.m_namelen);
506 fmd_fmri_free(mn.m_sid, mn.m_sidlen);
507 }
508
509 mem_strarray_free(dimms, ndimms);
510
511 (void) close(fd);
512
513 *seridsp = serids;
514 *nseridsp = nserids;
515
516 return (0);
517 }
518
519 /*
520 * Returns 0 with serial numbers if found, -1 (with errno set) for errors. If
521 * the unum (or a component of same) wasn't found, -1 is returned with errno
522 * set to ENOENT.
523 */
524 static int
mem_get_serids_from_cache(const char * unum,char *** seridsp,size_t * nseridsp)525 mem_get_serids_from_cache(const char *unum, char ***seridsp, size_t *nseridsp)
526 {
527 uint64_t drgen = fmd_fmri_get_drgen();
528 char **dimms, **serids;
529 size_t ndimms, nserids;
530 mem_dimm_map_t *dm;
531 int i, rc = 0;
532
533 if (mem_unum_burst(unum, &dimms, &ndimms) < 0)
534 return (-1); /* errno is set for us */
535
536 serids = fmd_fmri_zalloc(sizeof (char *) * ndimms);
537 nserids = ndimms;
538
539 for (i = 0; i < ndimms; i++) {
540 if ((dm = dm_lookup(dimms[i])) == NULL) {
541 rc = fmd_fmri_set_errno(EINVAL);
542 break;
543 }
544
545 if (*dm->dm_serid == '\0' || dm->dm_drgen != drgen) {
546 /*
547 * We don't have a cached copy, or the copy we've got is
548 * out of date. Look it up again.
549 */
550 if (mem_get_serid(dm->dm_device, dm->dm_serid,
551 sizeof (dm->dm_serid)) < 0) {
552 rc = -1; /* errno is set for us */
553 break;
554 }
555
556 dm->dm_drgen = drgen;
557 }
558
559 serids[i] = fmd_fmri_strdup(dm->dm_serid);
560 }
561
562 mem_strarray_free(dimms, ndimms);
563
564 if (rc == 0) {
565 *seridsp = serids;
566 *nseridsp = nserids;
567 } else {
568 mem_strarray_free(serids, nserids);
569 }
570
571 return (rc);
572 }
573
574 int
mem_get_serids_by_unum(const char * unum,char *** seridsp,size_t * nseridsp)575 mem_get_serids_by_unum(const char *unum, char ***seridsp, size_t *nseridsp)
576 {
577 /*
578 * Some platforms do not support the caching of serial ids by the
579 * mem scheme plugin but instead support making serial ids available
580 * via the kernel.
581 */
582 if (mem.mem_dm == NULL)
583 return (mem_get_serids_from_kernel(unum, seridsp, nseridsp));
584 else
585 return (mem_get_serids_from_cache(unum, seridsp, nseridsp));
586 }
587