xref: /illumos-gate/usr/src/uts/sun4v/io/fault_iso.c (revision ba2be53024c0b999e74ba9adcd7d80fec5df8c57)
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 2007 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  * sun4v Fault Isolation Services Module
31  */
32 
33 #include <sys/modctl.h>
34 #include <sys/cmn_err.h>
35 #include <sys/machsystm.h>
36 #include <sys/processor.h>
37 #include <sys/mem.h>
38 #include <vm/page.h>
39 #include <sys/note.h>
40 #include <sys/ds.h>
41 #include <sys/fault_iso.h>
42 
43 /*
44  * Debugging routines
45  */
46 #ifdef DEBUG
47 uint_t fi_debug = 0x0;
48 #define	FI_DBG	if (fi_debug) cmn_err
49 #else /* DEBUG */
50 #define	FI_DBG	_NOTE(CONSTCOND) if (0) cmn_err
51 #endif /* DEBUG */
52 
53 /*
54  * Domains Services interaction
55  */
56 static ds_svc_hdl_t	cpu_handle;
57 static ds_svc_hdl_t	mem_handle;
58 
59 static ds_ver_t		fi_vers[] = { { 1, 0 } };
60 #define	FI_NVERS	(sizeof (fi_vers) / sizeof (fi_vers[0]))
61 
62 static ds_capability_t cpu_cap = {
63 	"fma-cpu-service",	/* svc_id */
64 	fi_vers,		/* vers */
65 	FI_NVERS		/* nvers */
66 };
67 
68 static ds_capability_t mem_cap = {
69 	"fma-mem-service",	/* svc_id */
70 	fi_vers,		/* vers */
71 	FI_NVERS		/* nvers */
72 };
73 
74 static void fi_reg_handler(ds_cb_arg_t arg, ds_ver_t *ver, ds_svc_hdl_t hdl);
75 static void fi_unreg_handler(ds_cb_arg_t arg);
76 
77 static void cpu_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen);
78 static void mem_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen);
79 
80 static ds_clnt_ops_t cpu_ops = {
81 	fi_reg_handler,		/* ds_reg_cb */
82 	fi_unreg_handler,	/* ds_unreg_cb */
83 	cpu_data_handler,	/* ds_data_cb */
84 	&cpu_handle		/* cb_arg */
85 };
86 
87 static ds_clnt_ops_t mem_ops = {
88 	fi_reg_handler,		/* ds_reg_cb */
89 	fi_unreg_handler,	/* ds_unreg_cb */
90 	mem_data_handler,	/* ds_data_cb */
91 	&mem_handle		/* cb_arg */
92 };
93 
94 static int fi_init(void);
95 static void fi_fini(void);
96 
97 static struct modlmisc modlmisc = {
98 	&mod_miscops,
99 	"sun4v Fault Isolation Services %I%"
100 };
101 
102 static struct modlinkage modlinkage = {
103 	MODREV_1,
104 	(void *)&modlmisc,
105 	NULL
106 };
107 
108 int
109 _init(void)
110 {
111 	int	rv;
112 
113 	if ((rv = fi_init()) != 0)
114 		return (rv);
115 
116 	if ((rv = mod_install(&modlinkage)) != 0)
117 		fi_fini();
118 
119 	return (rv);
120 }
121 
122 int
123 _info(struct modinfo *modinfop)
124 {
125 	return (mod_info(&modlinkage, modinfop));
126 }
127 
128 int fi_allow_unload;
129 
130 int
131 _fini(void)
132 {
133 	int	status;
134 
135 	if (fi_allow_unload == 0)
136 		return (EBUSY);
137 
138 	if ((status = mod_remove(&modlinkage)) == 0)
139 		fi_fini();
140 
141 	return (status);
142 }
143 
144 static int
145 fi_init(void)
146 {
147 	int	rv;
148 
149 	/* register CPU service with domain services framework */
150 	rv = ds_cap_init(&cpu_cap, &cpu_ops);
151 	if (rv != 0) {
152 		FI_DBG(CE_CONT, "ds_cap_init failed: %d", rv);
153 		return (rv);
154 	}
155 
156 	/* register MEM servicewith domain services framework */
157 	rv = ds_cap_init(&mem_cap, &mem_ops);
158 	if (rv != 0) {
159 		FI_DBG(CE_CONT, "ds_cap_init failed: %d", rv);
160 		(void) ds_cap_fini(&cpu_cap);
161 		return (rv);
162 	}
163 
164 	return (rv);
165 }
166 
167 static void
168 fi_fini(void)
169 {
170 	/*
171 	 * Stop incoming requests from Zeus
172 	 */
173 	(void) ds_cap_fini(&cpu_cap);
174 	(void) ds_cap_fini(&mem_cap);
175 }
176 
177 static void
178 cpu_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen)
179 {
180 	_NOTE(ARGUNUSED(arg))
181 
182 	fma_cpu_service_req_t	*msg = buf;
183 	fma_cpu_resp_t		resp_msg;
184 	int			rv = 0;
185 	int			cpu_status;
186 	int			resp_back = 0;
187 
188 	/*
189 	 * If the buffer is the wrong size for CPU calls or is NULL then
190 	 * do not return any message. The call from the ldom mgr. will time out
191 	 * and the response will be NULL.
192 	 */
193 	if (msg == NULL || buflen != sizeof (fma_cpu_service_req_t)) {
194 		return;
195 	}
196 
197 	FI_DBG(CE_CONT, "req_num = %ld, msg_type = %d, cpu_id = %d\n",
198 	    msg->req_num, msg->msg_type, msg->cpu_id);
199 
200 	resp_msg.req_num = msg->req_num;
201 
202 	switch (msg->msg_type) {
203 	case FMA_CPU_REQ_STATUS:
204 		rv = p_online_internal(msg->cpu_id, P_STATUS,
205 		    &cpu_status);
206 		if (rv == EINVAL) {
207 			FI_DBG(CE_CONT, "Failed p_online call failed."
208 			    "Invalid CPU\n");
209 			resp_msg.result = FMA_CPU_RESP_FAILURE;
210 			resp_msg.status = FMA_CPU_STAT_ILLEGAL;
211 			resp_back = 1;
212 		}
213 		break;
214 	case FMA_CPU_REQ_OFFLINE:
215 		rv = p_online_internal(msg->cpu_id, P_FAULTED,
216 		    &cpu_status);
217 		if (rv == EINVAL) {
218 			FI_DBG(CE_CONT, "Failed p_online call failed."
219 			    "Invalid CPU\n");
220 			resp_msg.result = FMA_CPU_RESP_FAILURE;
221 			resp_msg.status = FMA_CPU_STAT_ILLEGAL;
222 			resp_back = 1;
223 		} else if (rv == EBUSY) {
224 			FI_DBG(CE_CONT, "Failed p_online call failed."
225 			    "Tried to offline while busy\n");
226 			resp_msg.result = FMA_CPU_RESP_FAILURE;
227 			resp_msg.status = FMA_CPU_STAT_ONLINE;
228 			resp_back = 1;
229 		}
230 		break;
231 	case FMA_CPU_REQ_ONLINE:
232 		rv = p_online_internal(msg->cpu_id, P_ONLINE,
233 		    &cpu_status);
234 		if (rv == EINVAL) {
235 			FI_DBG(CE_CONT, "Failed p_online call failed."
236 			    "Invalid CPU\n");
237 			resp_msg.result = FMA_CPU_RESP_FAILURE;
238 			resp_msg.status = FMA_CPU_STAT_ILLEGAL;
239 			resp_back = 1;
240 		} else if (rv == ENOTSUP) {
241 			FI_DBG(CE_CONT, "Failed p_online call failed."
242 			    "Online not supported for single CPU\n");
243 			resp_msg.result = FMA_CPU_RESP_FAILURE;
244 			resp_msg.status = FMA_CPU_STAT_OFFLINE;
245 			resp_back = 1;
246 		}
247 		break;
248 	default:
249 		/*
250 		 * If the msg_type was of unknown type simply return and
251 		 * have the ldom mgr. time out with a NULL response.
252 		 */
253 		return;
254 	}
255 
256 	if (rv != 0) {
257 		if (resp_back) {
258 			if ((rv = ds_cap_send(cpu_handle, &resp_msg,
259 			    sizeof (resp_msg))) != 0) {
260 				FI_DBG(CE_CONT, "ds_cap_send failed (%d)\n",
261 				    rv);
262 			}
263 			return;
264 		}
265 		ASSERT((rv == EINVAL) || ((rv == EBUSY) &&
266 		    (msg->msg_type == FMA_CPU_REQ_OFFLINE)) ||
267 		    ((rv == ENOTSUP) && (msg->msg_type == FMA_CPU_REQ_ONLINE)));
268 
269 		cmn_err(CE_WARN, "p_online_internal error not handled "
270 		    "rv = %d\n", rv);
271 	}
272 
273 	resp_msg.req_num = msg->req_num;
274 	resp_msg.result = FMA_CPU_RESP_OK;
275 
276 	switch (cpu_status) {
277 	case P_OFFLINE:
278 	case P_FAULTED:
279 	case P_POWEROFF:
280 	case P_SPARE:
281 		resp_msg.status = FMA_CPU_STAT_OFFLINE;
282 		break;
283 	case P_ONLINE:
284 	case P_NOINTR:
285 		resp_msg.status = FMA_CPU_STAT_ONLINE;
286 		break;
287 	default:
288 		resp_msg.status = FMA_CPU_STAT_ILLEGAL;
289 	}
290 
291 	if ((rv = ds_cap_send(cpu_handle, &resp_msg,
292 	    sizeof (resp_msg))) != 0) {
293 		FI_DBG(CE_CONT, "ds_cap_send failed (%d)\n", rv);
294 	}
295 }
296 
297 static void
298 mem_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen)
299 {
300 	_NOTE(ARGUNUSED(arg))
301 
302 	fma_mem_service_req_t	*msg = buf;
303 	fma_mem_resp_t		resp_msg;
304 	int			rv = 0;
305 
306 	/*
307 	 * If the buffer is the wrong size for Mem calls or is NULL then
308 	 * do not return any message. The call from the ldom mgr. will time out
309 	 * and the response will be NULL.
310 	 */
311 	if (msg == NULL || buflen != sizeof (fma_mem_service_req_t)) {
312 		return;
313 	}
314 
315 	FI_DBG(CE_CONT, "req_num = %ld, msg_type = %d, memory addr = 0x%lx"
316 	"memory length = 0x%lx\n", msg->req_num, msg->msg_type,
317 	    msg->real_addr, msg->length);
318 
319 	resp_msg.req_num = msg->req_num;
320 	resp_msg.res_addr = msg->real_addr;
321 	resp_msg.res_length = msg->length;
322 
323 	/*
324 	 * Information about return values for page calls can be referenced
325 	 * in usr/src/uts/common/vm/page_retire.c
326 	 */
327 	switch (msg->msg_type) {
328 	case FMA_MEM_REQ_STATUS:
329 		rv = page_retire_check(msg->real_addr, NULL);
330 		switch (rv) {
331 		/* Page is retired */
332 		case 0:
333 			resp_msg.result = FMA_MEM_RESP_OK;
334 			resp_msg.status = FMA_MEM_STAT_RETIRED;
335 			break;
336 		/* Page is pending. Send back failure and not retired */
337 		case EAGAIN:
338 			resp_msg.result = FMA_MEM_RESP_FAILURE;
339 			resp_msg.status = FMA_MEM_STAT_NOTRETIRED;
340 			break;
341 		/* Page is not retired. */
342 		case EIO:
343 			resp_msg.result = FMA_MEM_RESP_OK;
344 			resp_msg.status = FMA_MEM_STAT_NOTRETIRED;
345 			break;
346 		/* PA is not valid */
347 		case EINVAL:
348 			resp_msg.result = FMA_MEM_RESP_FAILURE;
349 			resp_msg.status = FMA_MEM_STAT_ILLEGAL;
350 			break;
351 		default:
352 			ASSERT((rv == 0) || (rv == EAGAIN) || (rv == EIO) ||
353 			    (rv ==  EINVAL));
354 			cmn_err(CE_WARN, "fault_iso: return value from "
355 			    "page_retire_check invalid: %d\n", rv);
356 		}
357 		break;
358 	case FMA_MEM_REQ_RETIRE:
359 		rv = page_retire(msg->real_addr, PR_FMA);
360 		switch (rv) {
361 		/* Page retired successfully */
362 		case 0:
363 			resp_msg.result = FMA_MEM_RESP_OK;
364 			resp_msg.status = FMA_MEM_STAT_RETIRED;
365 			break;
366 		/* Tried to retire and now Pending retirement */
367 		case EAGAIN:
368 			resp_msg.result = FMA_MEM_RESP_FAILURE;
369 			resp_msg.status = FMA_MEM_STAT_NOTRETIRED;
370 			break;
371 		/* Did not try to retire. Page already retired */
372 		case EIO:
373 			resp_msg.result = FMA_MEM_RESP_FAILURE;
374 			resp_msg.status = FMA_MEM_STAT_RETIRED;
375 			break;
376 		/* PA is not valid */
377 		case EINVAL:
378 			resp_msg.result = FMA_MEM_RESP_FAILURE;
379 			resp_msg.status = FMA_MEM_STAT_ILLEGAL;
380 			break;
381 		default:
382 			ASSERT((rv == 0) || (rv == EAGAIN) || (rv == EIO) ||
383 			    (rv ==  EINVAL));
384 			cmn_err(CE_WARN, "fault_iso: return value from "
385 			    "page_retire invalid: %d\n", rv);
386 		}
387 		break;
388 	case FMA_MEM_REQ_RESURRECT:
389 		rv = page_unretire(msg->real_addr);
390 		switch (rv) {
391 		/* Page succesfullly unretired */
392 		case 0:
393 			resp_msg.result = FMA_MEM_RESP_OK;
394 			resp_msg.status = FMA_MEM_STAT_NOTRETIRED;
395 			break;
396 		/* Page could not be locked. Still retired */
397 		case EAGAIN:
398 			resp_msg.result = FMA_MEM_RESP_FAILURE;
399 			resp_msg.status = FMA_MEM_STAT_RETIRED;
400 			break;
401 		/* Page was not retired already */
402 		case EIO:
403 			resp_msg.result = FMA_MEM_RESP_FAILURE;
404 			resp_msg.status = FMA_MEM_STAT_NOTRETIRED;
405 			break;
406 		/* PA is not valid */
407 		case EINVAL:
408 			resp_msg.result = FMA_MEM_RESP_FAILURE;
409 			resp_msg.status = FMA_MEM_STAT_ILLEGAL;
410 			break;
411 		default:
412 			ASSERT((rv == 0) || (rv == EAGAIN) || (rv == EIO) ||
413 			    (rv ==  EINVAL));
414 			cmn_err(CE_WARN, "fault_iso: return value from "
415 			    "page_unretire invalid: %d\n", rv);
416 		}
417 		break;
418 	default:
419 		/*
420 		 * If the msg_type was of unknown type simply return and
421 		 * have the ldom mgr. time out with a NULL response.
422 		 */
423 		return;
424 	}
425 
426 	if ((rv = ds_cap_send(mem_handle, &resp_msg, sizeof (resp_msg))) != 0) {
427 		FI_DBG(CE_CONT, "ds_cap_send failed (%d)\n", rv);
428 	}
429 }
430 
431 static void
432 fi_reg_handler(ds_cb_arg_t arg, ds_ver_t *ver, ds_svc_hdl_t hdl)
433 {
434 	FI_DBG(CE_CONT, "fi_reg_handler: arg=0x%p, ver=%d.%d, hdl=0x%lx\n",
435 	    arg, ver->major, ver->minor, hdl);
436 
437 	if ((ds_svc_hdl_t *)arg == &cpu_handle)
438 		cpu_handle = hdl;
439 	if ((ds_svc_hdl_t *)arg == &mem_handle)
440 		mem_handle = hdl;
441 }
442 
443 static void
444 fi_unreg_handler(ds_cb_arg_t arg)
445 {
446 	FI_DBG(CE_CONT, "fi_unreg_handler: arg=0x%p\n", arg);
447 
448 	if ((ds_svc_hdl_t *)arg == &cpu_handle)
449 		cpu_handle = DS_INVALID_HDL;
450 	if ((ds_svc_hdl_t *)arg == &mem_handle)
451 		mem_handle = DS_INVALID_HDL;
452 }
453