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