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  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 
25 
26 /*
27  * Support routines for DIMMs.
28  */
29 
30 #include <gmem_mem.h>
31 #include <gmem_dimm.h>
32 #include <gmem.h>
33 #include <errno.h>
34 #include <limits.h>
35 #include <string.h>
36 #include <strings.h>
37 #include <fcntl.h>
38 #include <unistd.h>
39 #include <fm/fmd_api.h>
40 #include <fm/libtopo.h>
41 #include <sys/fm/protocol.h>
42 #include <sys/mem.h>
43 #include <sys/nvpair.h>
44 
45 nvlist_t *dimm_nvl;
46 
47 typedef struct dimmid {
48 	char serial[100];
49 	int type;
50 } dimmid_t;
51 
52 static int gmem_find_dimm_chip(nvlist_t *, uint32_t *);
53 
54 nvlist_t *
55 gmem_dimm_fru(gmem_dimm_t *dimm)
56 {
57 	return (dimm->dimm_asru_nvl);
58 }
59 
60 static void
61 gmem_dimm_free(fmd_hdl_t *hdl, gmem_dimm_t *dimm, int destroy)
62 {
63 	gmem_case_t *cc = &dimm->dimm_case;
64 	int i;
65 	gmem_mq_t *q;
66 	tstamp_t *tsp, *next;
67 
68 	if (cc->cc_cp != NULL) {
69 		gmem_case_fini(hdl, cc->cc_cp, destroy);
70 		if (cc->cc_serdnm != NULL) {
71 			if (fmd_serd_exists(hdl, cc->cc_serdnm) &&
72 			    destroy)
73 				fmd_serd_destroy(hdl, cc->cc_serdnm);
74 			fmd_hdl_strfree(hdl, cc->cc_serdnm);
75 		}
76 	}
77 
78 	gmem_fmri_fini(hdl, &dimm->dimm_asru, destroy);
79 
80 	for (i = 0; i < GMEM_MAX_CKWDS; i++) {
81 		while ((q = gmem_list_next(&dimm->mq_root[i])) != NULL) {
82 			if (q->mq_serdnm != NULL) {
83 				if (fmd_serd_exists(hdl, q->mq_serdnm))
84 					fmd_serd_destroy(hdl, q->mq_serdnm);
85 				fmd_hdl_strfree(hdl, q->mq_serdnm);
86 				q->mq_serdnm = NULL;
87 			}
88 
89 			for (tsp = gmem_list_next(&q->mq_dupce_tstamp);
90 			    tsp != NULL; tsp = next) {
91 				next = gmem_list_next(tsp);
92 				gmem_list_delete(&q->mq_dupce_tstamp,
93 				    &tsp->ts_l);
94 				fmd_hdl_free(hdl, tsp, sizeof (tstamp_t));
95 			}
96 
97 			gmem_list_delete(&dimm->mq_root[i], q);
98 			fmd_hdl_free(hdl, q, sizeof (gmem_mq_t));
99 		}
100 	}
101 
102 	if (destroy)
103 		fmd_buf_destroy(hdl, NULL, dimm->dimm_bufname);
104 
105 	gmem_list_delete(&gmem.gm_dimms, dimm);
106 	fmd_hdl_free(hdl, dimm, sizeof (gmem_dimm_t));
107 }
108 
109 void
110 gmem_dimm_destroy(fmd_hdl_t *hdl, gmem_dimm_t *dimm)
111 {
112 	fmd_stat_destroy(hdl, 1, &(dimm->dimm_retstat));
113 	gmem_dimm_free(hdl, dimm, FMD_B_TRUE);
114 }
115 
116 static gmem_dimm_t *
117 dimm_lookup_by_serial(const char *serial)
118 {
119 	gmem_dimm_t *dimm;
120 
121 	for (dimm = gmem_list_next(&gmem.gm_dimms); dimm != NULL;
122 	    dimm = gmem_list_next(dimm)) {
123 		if (strcmp(dimm->dimm_serial, serial) == 0)
124 			return (dimm);
125 	}
126 
127 	return (NULL);
128 }
129 
130 gmem_dimm_t *
131 gmem_dimm_create(fmd_hdl_t *hdl, nvlist_t *asru, nvlist_t *det)
132 {
133 	gmem_dimm_t *dimm;
134 	nvlist_t *fmri;
135 	char *serial;
136 	uint32_t chip_id;
137 
138 	if (nvlist_lookup_string(asru, FM_FMRI_HC_SERIAL_ID, &serial) != 0) {
139 		fmd_hdl_debug(hdl, "Unable to get dimm serial\n");
140 		return (NULL);
141 	}
142 
143 	if (nvlist_dup(asru, &fmri, 0) != 0) {
144 		fmd_hdl_debug(hdl, "dimm create nvlist dup failed");
145 		return (NULL);
146 	}
147 
148 	(void) gmem_find_dimm_chip(det, &chip_id);
149 
150 	fmd_hdl_debug(hdl, "dimm_create: creating new DIMM serial=%s\n",
151 	    serial);
152 	GMEM_STAT_BUMP(dimm_creat);
153 
154 	dimm = fmd_hdl_zalloc(hdl, sizeof (gmem_dimm_t), FMD_SLEEP);
155 	dimm->dimm_nodetype = GMEM_NT_DIMM;
156 	dimm->dimm_version = GMEM_DIMM_VERSION;
157 	dimm->dimm_phys_addr_low = ULLONG_MAX;
158 	dimm->dimm_phys_addr_hi = 0;
159 	dimm->dimm_syl_error = USHRT_MAX;
160 	dimm->dimm_chipid = chip_id;
161 
162 	gmem_bufname(dimm->dimm_bufname, sizeof (dimm->dimm_bufname), "dimm_%s",
163 	    serial);
164 	gmem_fmri_init(hdl, &dimm->dimm_asru, fmri, "dimm_asru_%s", serial);
165 
166 	nvlist_free(fmri);
167 
168 	(void) nvlist_lookup_string(dimm->dimm_asru_nvl, FM_FMRI_HC_SERIAL_ID,
169 	    (char **)&dimm->dimm_serial);
170 
171 	gmem_mem_retirestat_create(hdl, &dimm->dimm_retstat, dimm->dimm_serial,
172 	    0, GMEM_DIMM_STAT_PREFIX);
173 
174 	gmem_list_append(&gmem.gm_dimms, dimm);
175 	gmem_dimm_dirty(hdl, dimm);
176 
177 	return (dimm);
178 }
179 
180 gmem_dimm_t *
181 gmem_dimm_lookup(fmd_hdl_t *hdl, nvlist_t *asru)
182 {
183 	gmem_dimm_t *dimm;
184 	char *serial;
185 	int err;
186 
187 	err = nvlist_lookup_string(asru, FM_FMRI_HC_SERIAL_ID, &serial);
188 
189 	if (err != 0) {
190 		fmd_hdl_debug(hdl, "Can't get dimm serial number\n");
191 		GMEM_STAT_BUMP(bad_mem_resource);
192 		return (NULL);
193 	}
194 
195 	dimm = dimm_lookup_by_serial(serial);
196 	return (dimm);
197 }
198 
199 
200 static gmem_dimm_t *
201 gmem_dimm_v0tov1(fmd_hdl_t *hdl, gmem_dimm_0_t *old, size_t oldsz)
202 {
203 	gmem_dimm_t *new;
204 	if (oldsz != sizeof (gmem_dimm_0_t)) {
205 		fmd_hdl_abort(hdl, "size of state doesn't match size of "
206 		    "version 0 state (%u bytes).\n", sizeof (gmem_dimm_0_t));
207 	}
208 
209 	new = fmd_hdl_zalloc(hdl, sizeof (gmem_dimm_t), FMD_SLEEP);
210 	new->dimm_header = old->dimm0_header;
211 	new->dimm_version = GMEM_DIMM_VERSION;
212 	new->dimm_asru = old->dimm0_asru;
213 	new->dimm_nretired = old->dimm0_nretired;
214 	new->dimm_phys_addr_hi = 0;
215 	new->dimm_phys_addr_low = ULLONG_MAX;
216 
217 	fmd_hdl_free(hdl, old, oldsz);
218 	return (new);
219 }
220 
221 static gmem_dimm_t *
222 gmem_dimm_wrapv1(fmd_hdl_t *hdl, gmem_dimm_pers_t *pers, size_t psz)
223 {
224 	gmem_dimm_t *dimm;
225 
226 	if (psz != sizeof (gmem_dimm_pers_t)) {
227 		fmd_hdl_abort(hdl, "size of state doesn't match size of "
228 		    "version 0 state (%u bytes).\n", sizeof (gmem_dimm_pers_t));
229 	}
230 
231 	dimm = fmd_hdl_zalloc(hdl, sizeof (gmem_dimm_t), FMD_SLEEP);
232 	bcopy(pers, dimm, sizeof (gmem_dimm_pers_t));
233 	fmd_hdl_free(hdl, pers, psz);
234 	return (dimm);
235 }
236 
237 void *
238 gmem_dimm_restore(fmd_hdl_t *hdl, fmd_case_t *cp, gmem_case_ptr_t *ptr)
239 {
240 	gmem_dimm_t *dimm;
241 
242 	for (dimm = gmem_list_next(&gmem.gm_dimms); dimm != NULL;
243 	    dimm = gmem_list_next(dimm)) {
244 		if (strcmp(dimm->dimm_bufname, ptr->ptr_name) == 0)
245 			break;
246 	}
247 
248 	if (dimm == NULL) {
249 		int migrated = 0;
250 		size_t dimmsz;
251 
252 		fmd_hdl_debug(hdl, "restoring dimm from %s\n", ptr->ptr_name);
253 
254 		if ((dimmsz = fmd_buf_size(hdl, NULL, ptr->ptr_name)) == 0) {
255 			fmd_hdl_abort(hdl, "dimm referenced by case %s does "
256 			    "not exist in saved state\n",
257 			    fmd_case_uuid(hdl, cp));
258 		} else if (dimmsz > GMEM_DIMM_MAXSIZE ||
259 		    dimmsz < GMEM_DIMM_MINSIZE) {
260 			fmd_hdl_abort(hdl, "dimm buffer referenced by case %s "
261 			    "is out of bounds (is %u bytes, max %u, min %u)\n",
262 			    fmd_case_uuid(hdl, cp), dimmsz,
263 			    GMEM_DIMM_MAXSIZE, GMEM_DIMM_MINSIZE);
264 		}
265 
266 		if ((dimm = gmem_buf_read(hdl, NULL, ptr->ptr_name,
267 		    dimmsz)) == NULL) {
268 			fmd_hdl_abort(hdl, "failed to read dimm buf %s",
269 			    ptr->ptr_name);
270 		}
271 
272 		fmd_hdl_debug(hdl, "found %d in version field\n",
273 		    dimm->dimm_version);
274 
275 		if (GMEM_DIMM_VERSIONED(dimm)) {
276 
277 			switch (dimm->dimm_version) {
278 			case GMEM_DIMM_VERSION_1:
279 				dimm = gmem_dimm_wrapv1(hdl,
280 				    (gmem_dimm_pers_t *)dimm, dimmsz);
281 				break;
282 			default:
283 				fmd_hdl_abort(hdl, "unknown version (found %d) "
284 				    "for dimm state referenced by case %s.\n",
285 				    dimm->dimm_version, fmd_case_uuid(hdl, cp));
286 				break;
287 			}
288 		} else {
289 			dimm = gmem_dimm_v0tov1(hdl, (gmem_dimm_0_t *)dimm,
290 			    dimmsz);
291 			migrated = 1;
292 		}
293 
294 		if (migrated) {
295 			GMEM_STAT_BUMP(dimm_migrat);
296 			gmem_dimm_dirty(hdl, dimm);
297 		}
298 
299 		gmem_fmri_restore(hdl, &dimm->dimm_asru);
300 
301 		if ((errno = nvlist_lookup_string(dimm->dimm_asru_nvl,
302 		    FM_FMRI_HC_SERIAL_ID, (char **)&dimm->dimm_serial)) != 0)
303 			fmd_hdl_abort(hdl,
304 			    "failed to retrieve serial from asru");
305 
306 
307 		gmem_mem_retirestat_create(hdl, &dimm->dimm_retstat,
308 		    dimm->dimm_serial, dimm->dimm_nretired,
309 		    GMEM_DIMM_STAT_PREFIX);
310 
311 		gmem_list_append(&gmem.gm_dimms, dimm);
312 	}
313 
314 	switch (ptr->ptr_subtype) {
315 	case GMEM_PTR_DIMM_CASE:
316 		gmem_mem_case_restore(hdl, &dimm->dimm_case, cp, "dimm",
317 		    dimm->dimm_serial);
318 		break;
319 	default:
320 		fmd_hdl_abort(hdl, "invalid %s subtype %d\n",
321 		    ptr->ptr_name, ptr->ptr_subtype);
322 	}
323 
324 	return (dimm);
325 }
326 
327 void
328 gmem_dimm_validate(fmd_hdl_t *hdl)
329 {
330 	gmem_dimm_t *dimm, *next;
331 
332 	for (dimm = gmem_list_next(&gmem.gm_dimms); dimm != NULL; dimm = next) {
333 		next = gmem_list_next(dimm);
334 
335 		if (!gmem_dimm_present(hdl, dimm->dimm_asru_nvl))
336 			gmem_dimm_destroy(hdl, dimm);
337 	}
338 }
339 
340 void
341 gmem_dimm_dirty(fmd_hdl_t *hdl, gmem_dimm_t *dimm)
342 {
343 	if (fmd_buf_size(hdl, NULL, dimm->dimm_bufname) !=
344 	    sizeof (gmem_dimm_pers_t))
345 		fmd_buf_destroy(hdl, NULL, dimm->dimm_bufname);
346 
347 	/* No need to rewrite the FMRIs in the dimm - they don't change */
348 	fmd_buf_write(hdl, NULL, dimm->dimm_bufname, &dimm->dimm_pers,
349 	    sizeof (gmem_dimm_pers_t));
350 }
351 
352 void
353 gmem_dimm_gc(fmd_hdl_t *hdl)
354 {
355 	gmem_dimm_validate(hdl);
356 }
357 
358 void
359 gmem_dimm_fini(fmd_hdl_t *hdl)
360 {
361 	gmem_dimm_t *dimm;
362 
363 	while ((dimm = gmem_list_next(&gmem.gm_dimms)) != NULL)
364 		gmem_dimm_free(hdl, dimm, FMD_B_FALSE);
365 }
366 
367 
368 /*ARGSUSED*/
369 static int
370 find_dimm_hc_fmri(topo_hdl_t *thp, tnode_t *node, void *arg)
371 {
372 
373 	char *topo_sn;
374 	dimmid_t *dimmid = (dimmid_t *)arg;
375 	nvlist_t *fru = NULL;
376 	nvlist_t *rsc = NULL;
377 	nvlist_t *asru = NULL;
378 	int err;
379 
380 	if (topo_node_fru(node, &fru, NULL, &err) < 0)
381 		return (TOPO_WALK_NEXT);
382 
383 	err = nvlist_lookup_string(fru, FM_FMRI_HC_SERIAL_ID, &topo_sn);
384 	if (err != 0) {
385 		nvlist_free(fru);
386 		return (TOPO_WALK_NEXT);
387 	}
388 
389 	if (strcmp(dimmid->serial, topo_sn) != 0) {
390 		nvlist_free(fru);
391 		return (TOPO_WALK_NEXT);
392 	}
393 
394 	switch (dimmid->type) {
395 		case FINDFRU:
396 			(void) nvlist_dup(fru, &dimm_nvl, NV_UNIQUE_NAME);
397 			break;
398 		case FINDRSC:
399 			(void) topo_node_resource(node, &rsc, &err);
400 			if (rsc != NULL) {
401 				(void) nvlist_dup(rsc, &dimm_nvl,
402 				    NV_UNIQUE_NAME);
403 				nvlist_free(rsc);
404 			}
405 			break;
406 		case FINDASRU:
407 			(void) topo_node_asru(node, &asru, NULL, &err);
408 			if (asru != NULL) {
409 				(void) nvlist_dup(asru, &dimm_nvl,
410 				    NV_UNIQUE_NAME);
411 				nvlist_free(asru);
412 			}
413 			break;
414 		default:
415 			break;
416 	}
417 	nvlist_free(fru);
418 	return (TOPO_WALK_TERMINATE);
419 }
420 
421 nvlist_t *
422 gmem_find_dimm_by_sn(fmd_hdl_t *hdl, dimmid_t *dimmid) {
423 	topo_hdl_t *thp;
424 	topo_walk_t *twp;
425 	int err;
426 	dimm_nvl = NULL;
427 
428 	if ((thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION)) == NULL)
429 		return (NULL);
430 
431 	if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC,
432 	    find_dimm_hc_fmri, dimmid, &err)) == NULL) {
433 		fmd_hdl_topo_rele(hdl, thp);
434 		return (NULL);
435 	}
436 
437 	(void) topo_walk_step(twp, TOPO_WALK_CHILD);
438 	topo_walk_fini(twp);
439 	fmd_hdl_topo_rele(hdl, thp);
440 	return (dimm_nvl);
441 }
442 
443 nvlist_t *
444 gmem_find_dimm_fru(fmd_hdl_t *hdl, char *sn)
445 {
446 	dimmid_t fru;
447 	(void) strcpy(fru.serial, sn);
448 	fru.type = FINDFRU;
449 	return (gmem_find_dimm_by_sn(hdl, &fru));
450 }
451 
452 nvlist_t *
453 gmem_find_dimm_rsc(fmd_hdl_t *hdl, char *sn)
454 {
455 	dimmid_t rsc;
456 	(void) strcpy(rsc.serial, sn);
457 	rsc.type = FINDRSC;
458 	return (gmem_find_dimm_by_sn(hdl, &rsc));
459 }
460 
461 nvlist_t *
462 gmem_find_dimm_asru(fmd_hdl_t *hdl, char *sn)
463 {
464 	dimmid_t asru;
465 	(void) strcpy(asru.serial, sn);
466 	asru.type = FINDASRU;
467 	return (gmem_find_dimm_by_sn(hdl, &asru));
468 }
469 
470 int
471 gmem_dimm_present(fmd_hdl_t *hdl, nvlist_t *asru)
472 {
473 	char *sn;
474 	nvlist_t *dimm = NULL;
475 
476 	if (nvlist_lookup_string(asru, FM_FMRI_HC_SERIAL_ID, &sn) != 0) {
477 		fmd_hdl_debug(hdl, "Unable to get dimm serial\n");
478 		return (0);
479 	}
480 	dimm = gmem_find_dimm_fru(hdl, sn);
481 	if (dimm == NULL) {
482 		fmd_hdl_debug(hdl, "Dimm sn=%s is not present\n", sn);
483 		return (0);
484 	}
485 	if (dimm != NULL)
486 		nvlist_free(dimm);
487 	return (1);
488 }
489 
490 static int
491 gmem_find_dimm_chip(nvlist_t *nvl, uint32_t *chip)
492 {
493 
494 	char *name, *id, *end;
495 	nvlist_t **hcl;
496 	uint_t n;
497 	int i;
498 	int rc = 0;
499 	*chip = ULONG_MAX;
500 
501 	if (nvlist_lookup_nvlist_array(nvl, FM_FMRI_HC_LIST, &hcl, &n) < 0)
502 		return (0);
503 	for (i = 0; i < n; i++) {
504 		(void) nvlist_lookup_string(hcl[i], FM_FMRI_HC_NAME, &name);
505 		(void) nvlist_lookup_string(hcl[i], FM_FMRI_HC_ID, &id);
506 
507 		if (strcmp(name, "chip") == 0) {
508 			*chip = (uint32_t)strtoul(id, &end, 10);
509 			rc = 1;
510 			break;
511 		}
512 	}
513 	return (rc);
514 }
515 
516 /*ARGSUSED*/
517 int
518 gmem_same_datapath_dimms(fmd_hdl_t *hdl, gmem_dimm_t *d1, gmem_dimm_t *d2)
519 {
520 
521 	if (d1->dimm_chipid == ULONG_MAX || d2->dimm_chipid == ULONG_MAX)
522 		return (0);
523 
524 	if (d1->dimm_chipid == d2->dimm_chipid)
525 		return (1);
526 
527 	return (0);
528 }
529 
530 int
531 gmem_check_symbol_error(fmd_hdl_t *hdl, gmem_dimm_t *d, uint16_t upos)
532 {
533 	gmem_dimm_t *dimm = NULL, *next = NULL;
534 
535 	for (dimm = gmem_list_next(&gmem.gm_dimms); dimm != NULL;
536 	    dimm = next) {
537 		next = gmem_list_next(dimm);
538 		if (gmem_same_datapath_dimms(hdl, dimm, d) &&
539 		    dimm->dimm_syl_error == upos)
540 			return (1);
541 	}
542 	return (0);
543 }
544 
545 void
546 gmem_save_symbol_error(fmd_hdl_t *hdl, gmem_dimm_t *d, uint16_t upos)
547 {
548 	gmem_dimm_t *dimm = NULL, *next = NULL;
549 
550 	for (dimm = gmem_list_next(&gmem.gm_dimms); dimm != NULL;
551 	    dimm = next) {
552 		next = gmem_list_next(dimm);
553 		if (gmem_same_datapath_dimms(hdl, dimm, d))
554 			dimm->dimm_syl_error = upos;
555 	}
556 }
557