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 2007 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#include <cmd_mem.h>
29#include <cmd_bank.h>
30#include <cmd_dimm.h>
31#include <cmd.h>
32
33#include <errno.h>
34#include <string.h>
35#include <strings.h>
36#include <fcntl.h>
37#include <unistd.h>
38#include <fm/fmd_api.h>
39#include <sys/fm/protocol.h>
40#include <sys/mem.h>
41#include <sys/nvpair.h>
42
43void
44cmd_bank_add_dimm(fmd_hdl_t *hdl, cmd_bank_t *bank, cmd_dimm_t *dimm)
45{
46	cmd_bank_memb_t *bm;
47
48	fmd_hdl_debug(hdl, "attaching dimm %s to bank %s\n", dimm->dimm_unum,
49	    bank->bank_unum);
50
51	dimm->dimm_bank = bank;
52
53	bm = fmd_hdl_zalloc(hdl, sizeof (cmd_bank_memb_t), FMD_SLEEP);
54	bm->bm_dimm = dimm;
55	cmd_list_append(&bank->bank_dimms, bm);
56}
57
58void
59cmd_bank_remove_dimm(fmd_hdl_t *hdl, cmd_bank_t *bank, cmd_dimm_t *dimm)
60{
61	cmd_bank_memb_t *bm;
62
63	fmd_hdl_debug(hdl, "detaching dimm %s from bank %s\n", dimm->dimm_unum,
64	    bank->bank_unum);
65
66	for (bm = cmd_list_next(&bank->bank_dimms); bm != NULL;
67	    bm = cmd_list_next(bm)) {
68		if (bm->bm_dimm != dimm)
69			continue;
70
71		cmd_list_delete(&bank->bank_dimms, bm);
72		dimm->dimm_bank = NULL;
73		fmd_hdl_free(hdl, bm, sizeof (cmd_bank_memb_t));
74		return;
75	}
76
77	fmd_hdl_abort(hdl, "attempt to disconnect dimm from non-parent bank\n");
78}
79
80static void
81bank_dimmlist_create(fmd_hdl_t *hdl, cmd_bank_t *bank)
82{
83	cmd_dimm_t *dimm;
84
85	for (dimm = cmd_list_next(&cmd.cmd_dimms); dimm != NULL;
86	    dimm = cmd_list_next(dimm)) {
87		if (fmd_nvl_fmri_contains(hdl, bank->bank_asru_nvl,
88		    dimm->dimm_asru_nvl))
89			cmd_bank_add_dimm(hdl, bank, dimm);
90	}
91}
92
93static void
94bank_dimmlist_free(fmd_hdl_t *hdl, cmd_bank_t *bank)
95{
96	cmd_bank_memb_t *bm;
97
98	while ((bm = cmd_list_next(&bank->bank_dimms)) != NULL) {
99		cmd_list_delete(&bank->bank_dimms, bm);
100		bm->bm_dimm->dimm_bank = NULL;
101		fmd_hdl_free(hdl, bm, sizeof (cmd_bank_memb_t));
102	}
103}
104
105nvlist_t *
106cmd_bank_fru(cmd_bank_t *bank)
107{
108	return (bank->bank_asru_nvl);
109}
110
111nvlist_t *
112cmd_bank_create_fault(fmd_hdl_t *hdl, cmd_bank_t *bank, const char *fltnm,
113    uint_t cert)
114{
115	return (cmd_nvl_create_fault(hdl, fltnm, cert, bank->bank_asru_nvl,
116	    bank->bank_asru_nvl, NULL));
117}
118
119static void
120bank_free(fmd_hdl_t *hdl, cmd_bank_t *bank, int destroy)
121{
122	if (bank->bank_case.cc_cp != NULL)
123		cmd_case_fini(hdl, bank->bank_case.cc_cp, destroy);
124
125	bank_dimmlist_free(hdl, bank);
126	cmd_fmri_fini(hdl, &bank->bank_asru, destroy);
127
128	if (destroy)
129		fmd_buf_destroy(hdl, NULL, bank->bank_bufname);
130	cmd_list_delete(&cmd.cmd_banks, bank);
131	fmd_hdl_free(hdl, bank, sizeof (cmd_bank_t));
132}
133
134void
135cmd_bank_destroy(fmd_hdl_t *hdl, cmd_bank_t *bank)
136{
137	fmd_stat_destroy(hdl, 1, &(bank->bank_retstat));
138	bank_free(hdl, bank, FMD_B_TRUE);
139}
140
141static cmd_bank_t *
142bank_lookup_by_unum(const char *unum)
143{
144	cmd_bank_t *bank;
145
146	for (bank = cmd_list_next(&cmd.cmd_banks); bank != NULL;
147	    bank = cmd_list_next(bank)) {
148		if (strcmp(bank->bank_unum, unum) == 0)
149			return (bank);
150	}
151
152	return (NULL);
153}
154
155cmd_bank_t *
156cmd_bank_create(fmd_hdl_t *hdl, nvlist_t *asru)
157{
158	cmd_bank_t *bank;
159	const char *unum;
160
161	if (!fmd_nvl_fmri_present(hdl, asru)) {
162		fmd_hdl_debug(hdl, "dimm_lookup: discarding old ereport\n");
163		return (NULL);
164	}
165
166	if ((unum = cmd_fmri_get_unum(asru)) == NULL) {
167		CMD_STAT_BUMP(bad_mem_asru);
168		return (NULL);
169	}
170
171	fmd_hdl_debug(hdl, "bank_create: creating new bank %s\n", unum);
172	CMD_STAT_BUMP(bank_creat);
173
174	bank = fmd_hdl_zalloc(hdl, sizeof (cmd_bank_t), FMD_SLEEP);
175	bank->bank_nodetype = CMD_NT_BANK;
176	bank->bank_version = CMD_BANK_VERSION;
177
178	cmd_bufname(bank->bank_bufname, sizeof (bank->bank_bufname), "bank_%s",
179	    unum);
180	cmd_fmri_init(hdl, &bank->bank_asru, asru, "bank_asru_%s", unum);
181
182	(void) nvlist_lookup_string(bank->bank_asru_nvl, FM_FMRI_MEM_UNUM,
183	    (char **)&bank->bank_unum);
184
185	bank_dimmlist_create(hdl, bank);
186
187	cmd_mem_retirestat_create(hdl, &bank->bank_retstat, bank->bank_unum, 0,
188	    CMD_BANK_STAT_PREFIX);
189
190	cmd_list_append(&cmd.cmd_banks, bank);
191	cmd_bank_dirty(hdl, bank);
192
193	return (bank);
194}
195
196cmd_bank_t *
197cmd_bank_lookup(fmd_hdl_t *hdl, nvlist_t *asru)
198{
199	cmd_bank_t *bank;
200	const char *unum;
201
202	if ((unum = cmd_fmri_get_unum(asru)) == NULL) {
203		CMD_STAT_BUMP(bad_mem_asru);
204		return (NULL);
205	}
206
207	bank = bank_lookup_by_unum(unum);
208
209	if (bank != NULL && !fmd_nvl_fmri_present(hdl, bank->bank_asru_nvl)) {
210		fmd_hdl_debug(hdl, "bank_lookup: discarding old bank\n");
211		cmd_bank_destroy(hdl, bank);
212		return (NULL);
213	}
214
215	return (bank);
216}
217
218static cmd_bank_t *
219bank_v0tov1(fmd_hdl_t *hdl, cmd_bank_0_t *old, size_t oldsz)
220{
221	cmd_bank_t *new;
222
223	if (oldsz != sizeof (cmd_bank_0_t)) {
224		fmd_hdl_abort(hdl, "size of state doesn't match size of "
225		    "version 0 state (%u bytes).\n", sizeof (cmd_bank_0_t));
226	}
227
228	new = fmd_hdl_zalloc(hdl, sizeof (cmd_bank_t), FMD_SLEEP);
229	new->bank_header = old->bank0_header;
230	new->bank_version = CMD_BANK_VERSION;
231	new->bank_asru = old->bank0_asru;
232	new->bank_nretired = old->bank0_nretired;
233
234	fmd_hdl_free(hdl, old, oldsz);
235	return (new);
236}
237
238static cmd_bank_t *
239bank_wrapv1(fmd_hdl_t *hdl, cmd_bank_pers_t *pers, size_t psz)
240{
241	cmd_bank_t *bank;
242
243	if (psz != sizeof (cmd_bank_pers_t)) {
244		fmd_hdl_abort(hdl, "size of state doesn't match size of "
245		    "version 1 state (%u bytes).\n", sizeof (cmd_bank_pers_t));
246	}
247
248	bank = fmd_hdl_zalloc(hdl, sizeof (cmd_bank_t), FMD_SLEEP);
249	bcopy(pers, bank, sizeof (cmd_bank_pers_t));
250	fmd_hdl_free(hdl, pers, psz);
251	return (bank);
252}
253
254void *
255cmd_bank_restore(fmd_hdl_t *hdl, fmd_case_t *cp, cmd_case_ptr_t *ptr)
256{
257	cmd_bank_t *bank;
258
259	for (bank = cmd_list_next(&cmd.cmd_banks); bank != NULL;
260	    bank = cmd_list_next(bank)) {
261		if (strcmp(bank->bank_bufname, ptr->ptr_name) == 0)
262			break;
263	}
264
265	if (bank == NULL) {
266		int migrated = 0;
267		size_t banksz;
268
269		fmd_hdl_debug(hdl, "restoring bank from %s\n", ptr->ptr_name);
270
271		if ((banksz = fmd_buf_size(hdl, NULL, ptr->ptr_name)) == 0) {
272			fmd_hdl_abort(hdl, "bank referenced by case %s does "
273			    "not exist in saved state\n",
274			    fmd_case_uuid(hdl, cp));
275		} else if (banksz > CMD_BANK_MAXSIZE ||
276		    banksz < CMD_BANK_MINSIZE) {
277			fmd_hdl_abort(hdl, "bank buffer referenced by case %s "
278			    "is out of bounds (is %u bytes, max %u, min %u)\n",
279			    fmd_case_uuid(hdl, cp), banksz,
280			    CMD_BANK_MAXSIZE, CMD_BANK_MAXSIZE);
281		}
282
283		if ((bank = cmd_buf_read(hdl, NULL, ptr->ptr_name,
284		    banksz)) == NULL) {
285			fmd_hdl_abort(hdl, "failed to read bank buf %s",
286			    ptr->ptr_name);
287		}
288
289		fmd_hdl_debug(hdl, "found %d in version field\n",
290		    bank->bank_version);
291
292		if (CMD_BANK_VERSIONED(bank)) {
293			switch (bank->bank_version) {
294			case CMD_BANK_VERSION_1:
295				bank = bank_wrapv1(hdl, (cmd_bank_pers_t *)bank,
296				    banksz);
297				break;
298			default:
299				fmd_hdl_abort(hdl, "unknown version (found %d) "
300				    "for bank state referenced by case %s.\n",
301				    bank->bank_version, fmd_case_uuid(hdl, cp));
302				break;
303			}
304		} else {
305			bank = bank_v0tov1(hdl, (cmd_bank_0_t *)bank, banksz);
306			migrated = 1;
307		}
308
309		if (migrated) {
310			CMD_STAT_BUMP(bank_migrat);
311			cmd_bank_dirty(hdl, bank);
312		}
313
314		cmd_fmri_restore(hdl, &bank->bank_asru);
315
316		if ((errno = nvlist_lookup_string(bank->bank_asru_nvl,
317		    FM_FMRI_MEM_UNUM, (char **)&bank->bank_unum)) != 0)
318			fmd_hdl_abort(hdl, "failed to retrieve nuum from asru");
319
320		bank_dimmlist_create(hdl, bank);
321
322		cmd_mem_retirestat_create(hdl, &bank->bank_retstat,
323		    bank->bank_unum, bank->bank_nretired, CMD_BANK_STAT_PREFIX);
324
325		cmd_list_append(&cmd.cmd_banks, bank);
326	}
327
328	switch (ptr->ptr_subtype) {
329	case BUG_PTR_BANK_CASE:
330		fmd_hdl_debug(hdl, "recovering from out of order page ptr\n");
331		cmd_case_redirect(hdl, cp, CMD_PTR_BANK_CASE);
332		/*FALLTHROUGH*/
333	case CMD_PTR_BANK_CASE:
334		cmd_mem_case_restore(hdl, &bank->bank_case, cp, "bank",
335		    bank->bank_unum);
336		break;
337	default:
338		fmd_hdl_abort(hdl, "invalid %s subtype %d\n",
339		    ptr->ptr_name, ptr->ptr_subtype);
340	}
341
342	return (bank);
343}
344
345void
346cmd_bank_validate(fmd_hdl_t *hdl)
347{
348	cmd_bank_t *bank, *next;
349
350	for (bank = cmd_list_next(&cmd.cmd_banks); bank != NULL; bank = next) {
351		next = cmd_list_next(bank);
352
353		if (!fmd_nvl_fmri_present(hdl, bank->bank_asru_nvl))
354			cmd_bank_destroy(hdl, bank);
355	}
356}
357
358void
359cmd_bank_dirty(fmd_hdl_t *hdl, cmd_bank_t *bank)
360{
361	if (fmd_buf_size(hdl, NULL, bank->bank_bufname) !=
362	    sizeof (cmd_bank_pers_t))
363		fmd_buf_destroy(hdl, NULL, bank->bank_bufname);
364
365	/* No need to rewrite the FMRIs in the bank - they don't change */
366	fmd_buf_write(hdl, NULL, bank->bank_bufname, &bank->bank_pers,
367	    sizeof (cmd_bank_pers_t));
368}
369
370void
371cmd_bank_gc(fmd_hdl_t *hdl)
372{
373	cmd_bank_validate(hdl);
374}
375
376void
377cmd_bank_fini(fmd_hdl_t *hdl)
378{
379	cmd_bank_t *bank;
380
381	while ((bank = cmd_list_next(&cmd.cmd_banks)) != NULL)
382		bank_free(hdl, bank, FMD_B_FALSE);
383}
384