xref: /illumos-gate/usr/src/uts/sun4v/io/fault_iso.c (revision f808c858fa61e7769218966759510a8b1190dfcf)
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 2006 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) &&
268 			(msg->msg_type == FMA_CPU_REQ_ONLINE)));
269 
270 		cmn_err(CE_WARN, "p_online_internal error not handled "
271 		    "rv = %d\n", rv);
272 	}
273 
274 	resp_msg.req_num = msg->req_num;
275 	resp_msg.result = FMA_CPU_RESP_OK;
276 
277 	switch (cpu_status) {
278 	case P_OFFLINE:
279 	case P_FAULTED:
280 	case P_POWEROFF:
281 	case P_SPARE:
282 		resp_msg.status = FMA_CPU_STAT_OFFLINE;
283 		break;
284 	case P_ONLINE:
285 	case P_NOINTR:
286 		resp_msg.status = FMA_CPU_STAT_ONLINE;
287 		break;
288 	default:
289 		resp_msg.status = FMA_CPU_STAT_ILLEGAL;
290 	}
291 
292 	if ((rv = ds_cap_send(cpu_handle, &resp_msg,
293 	    sizeof (resp_msg))) != 0) {
294 		FI_DBG(CE_CONT, "ds_cap_send failed (%d)\n", rv);
295 	}
296 }
297 
298 static void
299 mem_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen)
300 {
301 	_NOTE(ARGUNUSED(arg))
302 
303 	fma_mem_service_req_t	*msg = buf;
304 	fma_mem_resp_t		resp_msg;
305 	int			rv = 0;
306 
307 	/*
308 	 * If the buffer is the wrong size for Mem calls or is NULL then
309 	 * do not return any message. The call from the ldom mgr. will time out
310 	 * and the response will be NULL.
311 	 */
312 	if (msg == NULL || buflen != sizeof (fma_mem_service_req_t)) {
313 		return;
314 	}
315 
316 	FI_DBG(CE_CONT, "req_num = %ld, msg_type = %d, memory addr = 0x%lx"
317 	"memory length = 0x%lx\n", msg->req_num, msg->msg_type,
318 	    msg->real_addr, msg->length);
319 
320 	resp_msg.req_num = msg->req_num;
321 	resp_msg.res_addr = msg->real_addr;
322 	resp_msg.res_length = msg->length;
323 
324 	/*
325 	 * Information about return values for page calls can be referenced
326 	 * in usr/src/uts/common/vm/page_retire.c
327 	 */
328 	switch (msg->msg_type) {
329 	case FMA_MEM_REQ_STATUS:
330 		rv = page_retire_check(msg->real_addr, NULL);
331 		switch (rv) {
332 		/* Page is retired */
333 		case 0:
334 			resp_msg.result = FMA_MEM_RESP_OK;
335 			resp_msg.status = FMA_MEM_STAT_RETIRED;
336 			break;
337 		/* Page is pending. Send back failure and not retired */
338 		case EAGAIN:
339 			resp_msg.result = FMA_MEM_RESP_FAILURE;
340 			resp_msg.status = FMA_MEM_STAT_NOTRETIRED;
341 			break;
342 		/* Page is not retired. */
343 		case EIO:
344 			resp_msg.result = FMA_MEM_RESP_FAILURE;
345 			resp_msg.status = FMA_MEM_STAT_NOTRETIRED;
346 			break;
347 		/* PA is not valid */
348 		case EINVAL:
349 			resp_msg.result = FMA_MEM_RESP_FAILURE;
350 			resp_msg.status = FMA_MEM_STAT_ILLEGAL;
351 			break;
352 		default:
353 			ASSERT((rv == 0) || (rv == EAGAIN) || (rv == EIO) ||
354 			    (rv ==  EINVAL));
355 			cmn_err(CE_WARN, "fault_iso: return value from "
356 			    "page_retire_check invalid: %d\n", rv);
357 		}
358 		break;
359 	case FMA_MEM_REQ_RETIRE:
360 		rv = page_retire(msg->real_addr, PR_FMA);
361 		switch (rv) {
362 		/* Page retired successfully */
363 		case 0:
364 			resp_msg.result = FMA_MEM_RESP_OK;
365 			resp_msg.status = FMA_MEM_STAT_RETIRED;
366 			break;
367 		/* Tried to retire and now Pending retirement */
368 		case EAGAIN:
369 			resp_msg.result = FMA_MEM_RESP_FAILURE;
370 			resp_msg.status = FMA_MEM_STAT_NOTRETIRED;
371 			break;
372 		/* Did not try to retire. Page already retired */
373 		case EIO:
374 			resp_msg.result = FMA_MEM_RESP_FAILURE;
375 			resp_msg.status = FMA_MEM_STAT_RETIRED;
376 			break;
377 		/* PA is not valid */
378 		case EINVAL:
379 			resp_msg.result = FMA_MEM_RESP_FAILURE;
380 			resp_msg.status = FMA_MEM_STAT_ILLEGAL;
381 			break;
382 		default:
383 			ASSERT((rv == 0) || (rv == EAGAIN) || (rv == EIO) ||
384 			    (rv ==  EINVAL));
385 			cmn_err(CE_WARN, "fault_iso: return value from "
386 			    "page_retire invalid: %d\n", rv);
387 		}
388 		break;
389 	case FMA_MEM_REQ_RESURRECT:
390 		rv = page_unretire(msg->real_addr);
391 		switch (rv) {
392 		/* Page succesfullly unretired */
393 		case 0:
394 			resp_msg.result = FMA_MEM_RESP_OK;
395 			resp_msg.status = FMA_MEM_STAT_NOTRETIRED;
396 			break;
397 		/* Page could not be locked. Still retired */
398 		case EAGAIN:
399 			resp_msg.result = FMA_MEM_RESP_FAILURE;
400 			resp_msg.status = FMA_MEM_STAT_RETIRED;
401 			break;
402 		/* Page was not retired already */
403 		case EIO:
404 			resp_msg.result = FMA_MEM_RESP_FAILURE;
405 			resp_msg.status = FMA_MEM_STAT_NOTRETIRED;
406 			break;
407 		/* PA is not valid */
408 		case EINVAL:
409 			resp_msg.result = FMA_MEM_RESP_FAILURE;
410 			resp_msg.status = FMA_MEM_STAT_ILLEGAL;
411 			break;
412 		default:
413 			ASSERT((rv == 0) || (rv == EAGAIN) || (rv == EIO) ||
414 			    (rv ==  EINVAL));
415 			cmn_err(CE_WARN, "fault_iso: return value from "
416 			    "page_unretire invalid: %d\n", rv);
417 		}
418 		break;
419 	default:
420 		/*
421 		 * If the msg_type was of unknown type simply return and
422 		 * have the ldom mgr. time out with a NULL response.
423 		 */
424 		return;
425 	}
426 
427 	if ((rv = ds_cap_send(mem_handle, &resp_msg, sizeof (resp_msg))) != 0) {
428 		FI_DBG(CE_CONT, "ds_cap_send failed (%d)\n", rv);
429 	}
430 }
431 
432 static void
433 fi_reg_handler(ds_cb_arg_t arg, ds_ver_t *ver, ds_svc_hdl_t hdl)
434 {
435 	FI_DBG(CE_CONT, "fi_reg_handler: arg=0x%p, ver=%d.%d, hdl=0x%lx\n",
436 	    arg, ver->major, ver->minor, hdl);
437 
438 	if ((ds_svc_hdl_t *)arg == &cpu_handle)
439 		cpu_handle = hdl;
440 	if ((ds_svc_hdl_t *)arg == &mem_handle)
441 		mem_handle = hdl;
442 }
443 
444 static void
445 fi_unreg_handler(ds_cb_arg_t arg)
446 {
447 	FI_DBG(CE_CONT, "fi_unreg_handler: arg=0x%p\n", arg);
448 
449 	if ((ds_svc_hdl_t *)arg == &cpu_handle)
450 		cpu_handle = DS_INVALID_HDL;
451 	if ((ds_svc_hdl_t *)arg == &mem_handle)
452 		mem_handle = DS_INVALID_HDL;
453 }
454