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 managing per-page state.
28  */
29 
30 #include <gmem_page.h>
31 #include <gmem_mem.h>
32 #include <gmem_dimm.h>
33 #include <gmem.h>
34 
35 #include <errno.h>
36 #include <strings.h>
37 #include <fm/fmd_api.h>
38 #include <sys/fm/protocol.h>
39 
40 static void
41 page_write(fmd_hdl_t *hdl, gmem_page_t *page)
42 {
43 	fmd_buf_write(hdl, NULL, page->page_bufname, page,
44 	    sizeof (gmem_page_pers_t));
45 }
46 
47 static void
48 gmem_page_free(fmd_hdl_t *hdl, gmem_page_t *page, int destroy)
49 {
50 	gmem_case_t *cc = &page->page_case;
51 
52 	if (cc->cc_cp != NULL)
53 		gmem_case_fini(hdl, cc->cc_cp, destroy);
54 
55 	if (cc->cc_serdnm != NULL) {
56 		if (fmd_serd_exists(hdl, cc->cc_serdnm) && destroy)
57 			fmd_serd_destroy(hdl, cc->cc_serdnm);
58 		fmd_hdl_strfree(hdl, cc->cc_serdnm);
59 	}
60 
61 	if (destroy)
62 		fmd_buf_destroy(hdl, NULL, page->page_bufname);
63 
64 	gmem_fmri_fini(hdl, &page->page_asru, destroy);
65 
66 	gmem_list_delete(&gmem.gm_pages, page);
67 	fmd_hdl_free(hdl, page, sizeof (gmem_page_t));
68 }
69 
70 void
71 gmem_page_destroy(fmd_hdl_t *hdl, gmem_page_t *page)
72 {
73 	fmd_hdl_debug(hdl, "destroying the page\n");
74 	gmem_page_free(hdl, page, FMD_B_TRUE);
75 }
76 
77 static gmem_page_t *
78 page_lookup_by_physaddr(uint64_t pa)
79 {
80 	gmem_page_t *page;
81 
82 	for (page = gmem_list_next(&gmem.gm_pages); page != NULL;
83 	    page = gmem_list_next(page)) {
84 		if (page->page_physbase == pa)
85 			return (page);
86 	}
87 
88 	return (NULL);
89 }
90 
91 gmem_page_t *
92 gmem_page_create(fmd_hdl_t *hdl, nvlist_t *modasru, uint64_t pa,
93     uint64_t offset)
94 {
95 	gmem_page_t *page;
96 	nvlist_t *asru, *hsp;
97 
98 	pa = pa & gmem.gm_pagemask;
99 
100 	fmd_hdl_debug(hdl, "page_lookup: creating new page for %llx\n",
101 	    (u_longlong_t)pa);
102 	GMEM_STAT_BUMP(page_creat);
103 
104 	page = fmd_hdl_zalloc(hdl, sizeof (gmem_page_t), FMD_SLEEP);
105 	page->page_nodetype = GMEM_NT_PAGE;
106 	page->page_version = CMD_PAGE_VERSION;
107 	page->page_physbase = pa;
108 	page->page_offset = offset;
109 
110 	gmem_bufname(page->page_bufname, sizeof (page->page_bufname),
111 	    "page_%llx", (u_longlong_t)pa);
112 
113 	if (nvlist_dup(modasru, &asru, 0) != 0) {
114 		fmd_hdl_debug(hdl, "Page create nvlist dup failed");
115 		return (NULL);
116 	}
117 
118 	if (nvlist_alloc(&hsp, NV_UNIQUE_NAME, 0) != 0) {
119 		fmd_hdl_debug(hdl, "Page create nvlist alloc failed");
120 		nvlist_free(asru);
121 		return (NULL);
122 	}
123 
124 	if (nvlist_add_uint64(hsp, FM_FMRI_MEM_PHYSADDR,
125 	    page->page_physbase) != 0 ||
126 	    nvlist_add_uint64(hsp, FM_FMRI_HC_SPECIFIC_OFFSET,
127 	    page->page_offset) != 0 ||
128 	    nvlist_add_nvlist(asru, FM_FMRI_HC_SPECIFIC, hsp) != 0) {
129 		fmd_hdl_debug(hdl, "Page create failed to build page fmri");
130 		nvlist_free(asru);
131 		nvlist_free(hsp);
132 		return (NULL);
133 	}
134 
135 	gmem_fmri_init(hdl, &page->page_asru, asru, "page_asru_%llx",
136 	    (u_longlong_t)pa);
137 
138 	nvlist_free(asru);
139 	nvlist_free(hsp);
140 
141 	gmem_list_append(&gmem.gm_pages, page);
142 	page_write(hdl, page);
143 
144 	return (page);
145 }
146 
147 gmem_page_t *
148 gmem_page_lookup(uint64_t pa)
149 {
150 	pa = pa & gmem.gm_pagemask;
151 
152 	return (page_lookup_by_physaddr(pa));
153 }
154 
155 static gmem_page_t *
156 page_wrapv0(fmd_hdl_t *hdl, gmem_page_pers_t *pers, size_t psz)
157 {
158 	gmem_page_t *page;
159 
160 	if (psz != sizeof (gmem_page_pers_t)) {
161 		fmd_hdl_abort(hdl, "size of state doesn't match size of "
162 		    "version 0 state (%u bytes).\n", sizeof (gmem_page_pers_t));
163 	}
164 
165 	page = fmd_hdl_zalloc(hdl, sizeof (gmem_page_t), FMD_SLEEP);
166 	bcopy(pers, page, sizeof (gmem_page_pers_t));
167 	fmd_hdl_free(hdl, pers, psz);
168 	return (page);
169 }
170 
171 void *
172 gmem_page_restore(fmd_hdl_t *hdl, fmd_case_t *cp, gmem_case_ptr_t *ptr)
173 {
174 	gmem_page_t *page;
175 
176 	for (page = gmem_list_next(&gmem.gm_pages); page != NULL;
177 	    page = gmem_list_next(page)) {
178 		if (strcmp(page->page_bufname, ptr->ptr_name) == 0)
179 			break;
180 	}
181 
182 	if (page == NULL) {
183 		size_t pagesz;
184 
185 		fmd_hdl_debug(hdl, "restoring page from %s\n", ptr->ptr_name);
186 
187 		if ((pagesz = fmd_buf_size(hdl, NULL, ptr->ptr_name)) == 0) {
188 			if (fmd_case_solved(hdl, cp) ||
189 			    fmd_case_closed(hdl, cp)) {
190 				fmd_hdl_debug(hdl, "page %s from case %s not "
191 				    "found. Case is already solved or closed\n",
192 				    ptr->ptr_name, fmd_case_uuid(hdl, cp));
193 				return (NULL);
194 			} else {
195 				fmd_hdl_abort(hdl, "page referenced by case %s "
196 				    "does not exist in saved state\n",
197 				    fmd_case_uuid(hdl, cp));
198 			}
199 		} else if (pagesz > CMD_PAGE_MAXSIZE ||
200 		    pagesz < CMD_PAGE_MINSIZE) {
201 			fmd_hdl_abort(hdl, "page buffer referenced by case %s "
202 			    "is out of bounds (is %u bytes, max %u, min %u)\n",
203 			    fmd_case_uuid(hdl, cp), pagesz,
204 			    CMD_PAGE_MAXSIZE, CMD_PAGE_MINSIZE);
205 		}
206 
207 		if ((page = gmem_buf_read(hdl, NULL, ptr->ptr_name,
208 		    pagesz)) == NULL) {
209 			fmd_hdl_abort(hdl, "failed to read page buf %s",
210 			    ptr->ptr_name);
211 		}
212 
213 		fmd_hdl_debug(hdl, "found %d in version field\n",
214 		    page->page_version);
215 
216 		switch (page->page_version) {
217 		case CMD_PAGE_VERSION_0:
218 			page = page_wrapv0(hdl, (gmem_page_pers_t *)page,
219 			    pagesz);
220 			break;
221 		default:
222 			fmd_hdl_abort(hdl, "unknown version (found %d) "
223 			    "for page state referenced by case %s.\n",
224 			    page->page_version, fmd_case_uuid(hdl, cp));
225 			break;
226 		}
227 
228 		gmem_fmri_restore(hdl, &page->page_asru);
229 
230 		gmem_list_append(&gmem.gm_pages, page);
231 	}
232 
233 	switch (ptr->ptr_subtype) {
234 	case GMEM_PTR_PAGE_CASE:
235 		gmem_case_restore(hdl, &page->page_case, cp,
236 		    gmem_page_serdnm_create(hdl, "page", page->page_physbase));
237 		break;
238 	default:
239 		fmd_hdl_abort(hdl, "invalid %s subtype %d\n",
240 		    ptr->ptr_name, ptr->ptr_subtype);
241 	}
242 
243 	return (page);
244 }
245 
246 /*ARGSUSED*/
247 int
248 gmem_page_unusable(fmd_hdl_t *hdl, gmem_page_t *page)
249 {
250 	nvlist_t *asru = NULL;
251 	char *sn;
252 
253 	if (nvlist_lookup_string(page->page_asru_nvl,
254 	    FM_FMRI_HC_SERIAL_ID, &sn) != 0)
255 		return (1);
256 
257 	/*
258 	 * get asru in mem scheme from topology
259 	 */
260 	asru = gmem_find_dimm_asru(hdl, sn);
261 	if (asru == NULL)
262 		return (1);
263 
264 	(void) nvlist_add_string_array(asru, FM_FMRI_MEM_SERIAL_ID, &sn, 1);
265 	(void) nvlist_add_uint64(asru, FM_FMRI_MEM_PHYSADDR,
266 	    page->page_physbase);
267 	(void) nvlist_add_uint64(asru, FM_FMRI_MEM_OFFSET, page->page_offset);
268 
269 	if (fmd_nvl_fmri_unusable(hdl, asru)) {
270 		nvlist_free(asru);
271 		return (1);
272 	}
273 
274 	if (asru != NULL)
275 		nvlist_free(asru);
276 
277 	return (0);
278 }
279 
280 
281 /*ARGSUSED*/
282 void
283 gmem_page_validate(fmd_hdl_t *hdl)
284 {
285 	gmem_page_t *page, *next;
286 
287 	for (page = gmem_list_next(&gmem.gm_pages); page != NULL; page = next) {
288 		next = gmem_list_next(page);
289 
290 		if (gmem_page_unusable(hdl, page))
291 			gmem_page_destroy(hdl, page);
292 	}
293 }
294 
295 void
296 gmem_page_dirty(fmd_hdl_t *hdl, gmem_page_t *page)
297 {
298 	if (fmd_buf_size(hdl, NULL, page->page_bufname) !=
299 	    sizeof (gmem_page_pers_t))
300 		fmd_buf_destroy(hdl, NULL, page->page_bufname);
301 
302 	/* No need to rewrite the FMRIs in the page - they don't change */
303 	fmd_buf_write(hdl, NULL, page->page_bufname, &page->page_pers,
304 	    sizeof (gmem_page_pers_t));
305 }
306 
307 void
308 gmem_page_fini(fmd_hdl_t *hdl)
309 {
310 	gmem_page_t *page;
311 
312 	while ((page = gmem_list_next(&gmem.gm_pages)) != NULL)
313 		gmem_page_free(hdl, page, FMD_B_FALSE);
314 }
315 
316 
317 int
318 gmem_page_fault(fmd_hdl_t *hdl, nvlist_t *fru, nvlist_t *rsc,
319     fmd_event_t *ep, uint64_t afar, uint64_t offset)
320 {
321 	gmem_page_t *page = NULL;
322 	const char *uuid;
323 	nvlist_t *flt, *hsp;
324 
325 	page = gmem_page_lookup(afar);
326 	if (page != NULL) {
327 		if (page->page_flags & GMEM_F_FAULTING ||
328 		    gmem_page_unusable(hdl, page)) {
329 			if (rsc != NULL)
330 				nvlist_free(rsc);
331 			page->page_flags |= GMEM_F_FAULTING;
332 			return (0);
333 		}
334 	} else {
335 		page = gmem_page_create(hdl, fru, afar, offset);
336 	}
337 
338 	page->page_flags |= GMEM_F_FAULTING;
339 	if (page->page_case.cc_cp == NULL)
340 		page->page_case.cc_cp = gmem_case_create(hdl,
341 		    &page->page_header, GMEM_PTR_PAGE_CASE, &uuid);
342 
343 	if (nvlist_lookup_nvlist(page->page_asru_nvl, FM_FMRI_HC_SPECIFIC,
344 	    &hsp) == 0)
345 		(void) nvlist_add_nvlist(rsc, FM_FMRI_HC_SPECIFIC, hsp);
346 
347 	flt = fmd_nvl_create_fault(hdl, GMEM_FAULT_PAGE, 100, NULL, fru, rsc);
348 	if (rsc != NULL)
349 		nvlist_free(rsc);
350 
351 	if (nvlist_add_boolean_value(flt, FM_SUSPECT_MESSAGE, B_FALSE) != 0)
352 		fmd_hdl_abort(hdl, "failed to add no-message member to fault");
353 
354 	fmd_case_add_ereport(hdl, page->page_case.cc_cp, ep);
355 	fmd_case_add_suspect(hdl, page->page_case.cc_cp, flt);
356 	fmd_case_solve(hdl, page->page_case.cc_cp);
357 	return (1);
358 }
359 
360 void
361 gmem_page_close(fmd_hdl_t *hdl, void *arg)
362 {
363 	gmem_page_destroy(hdl, arg);
364 }
365