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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 #include <libdevinfo.h>
27 #include <sys/modctl.h>
28 #include <sys/stat.h>
29 #include <string.h>
30 #include <librcm.h>
31 #include <dlfcn.h>
32 
33 #undef	NDEBUG
34 #include <assert.h>
35 
36 typedef struct rio_path {
37 	char		rpt_path[PATH_MAX];
38 	struct rio_path	*rpt_next;
39 } rio_path_t;
40 
41 typedef struct rcm_arg {
42 	char		*rcm_root;
43 	di_node_t	rcm_node;
44 	int		rcm_supp;
45 	rcm_handle_t	*rcm_handle;
46 	int		rcm_retcode;
47 	di_retire_t	*rcm_dp;
48 	rio_path_t	*rcm_cons_nodes;
49 	rio_path_t	*rcm_rsrc_minors;
50 	int		(*rcm_offline)();
51 	int		(*rcm_online)();
52 	int		(*rcm_remove)();
53 } rcm_arg_t;
54 
55 typedef struct selector {
56 	char	*sel_name;
57 	int	(*sel_selector)(di_node_t node, rcm_arg_t *rp);
58 } di_selector_t;
59 
60 static void rio_assert(di_retire_t *dp, const char *EXstr, int line,
61     const char *file);
62 
63 #define	LIBRCM_PATH	"/usr/lib/librcm.so"
64 #define	RIO_ASSERT(d, x)	\
65 		{if (!(x)) rio_assert(d, #x, __LINE__, __FILE__); }
66 
67 static int disk_select(di_node_t node, rcm_arg_t *rp);
68 static int nexus_select(di_node_t node, rcm_arg_t *rp);
69 static int enclosure_select(di_node_t node, rcm_arg_t *rp);
70 static int smp_select(di_node_t node, rcm_arg_t *rp);
71 
72 di_selector_t supported_devices[] = {
73 	{"disk",	disk_select},
74 	{"nexus",	nexus_select},
75 	{"enclosure",	enclosure_select},
76 	{"smp",		smp_select},
77 	{NULL, 		NULL}
78 };
79 
80 void *
81 s_calloc(size_t nelem, size_t elsize, int fail)
82 {
83 	if (fail) {
84 		errno = ENOMEM;
85 		return (NULL);
86 	} else {
87 		return (calloc(nelem, elsize));
88 	}
89 }
90 
91 static void
92 rio_assert(di_retire_t *dp, const char *EXstr, int line, const char *file)
93 {
94 	char	buf[PATH_MAX];
95 
96 	if (dp->rt_abort == NULL)
97 		assert(0);
98 
99 	(void) snprintf(buf, sizeof (buf),
100 	    "Assertion failed: %s, file %s, line %d\n",
101 	    EXstr, file, line);
102 	dp->rt_abort(dp->rt_hdl, buf);
103 }
104 
105 /*ARGSUSED*/
106 static int
107 enclosure_minor(di_node_t node, di_minor_t minor, void *arg)
108 {
109 	rcm_arg_t *rp = (rcm_arg_t *)arg;
110 	di_retire_t *dp = rp->rcm_dp;
111 
112 	rp->rcm_supp = 1;
113 	dp->rt_debug(dp->rt_hdl, "[INFO]: enclosure_minor: "
114 	    "IDed this node as enclosure\n");
115 	return (DI_WALK_TERMINATE);
116 }
117 
118 static int
119 enclosure_select(di_node_t node, rcm_arg_t *rp)
120 {
121 	rcm_arg_t rarg;
122 	di_retire_t	*dp = rp->rcm_dp;
123 
124 	rarg.rcm_dp = dp;
125 
126 	/*
127 	 * Check if this is an enclosure minor. If any one minor is DDI_NT_SGEN
128 	 * or DDI_NT_SCSI_ENCLOSURE we assume it is an enclosure.
129 	 */
130 	rarg.rcm_supp = 0;
131 	if (di_walk_minor(node, DDI_NT_SCSI_ENCLOSURE, 0, &rarg,
132 	    enclosure_minor) != 0) {
133 		dp->rt_debug(dp->rt_hdl, "[INFO]: enclosure_select:"
134 		    "di_walk_minor failed. Returning NOTSUP\n");
135 		return (0);
136 	}
137 	if (di_walk_minor(node, "ddi_generic:scsi", 0, &rarg,
138 	    enclosure_minor) != 0) {
139 		dp->rt_debug(dp->rt_hdl, "[INFO]: enclosure_select:"
140 		    "di_walk_minor failed. Returning NOTSUP\n");
141 		return (0);
142 	}
143 
144 	return (rarg.rcm_supp);
145 }
146 
147 /*ARGSUSED*/
148 static int
149 smp_minor(di_node_t node, di_minor_t minor, void *arg)
150 {
151 	rcm_arg_t *rp = (rcm_arg_t *)arg;
152 	di_retire_t *dp = rp->rcm_dp;
153 
154 	rp->rcm_supp = 1;
155 	dp->rt_debug(dp->rt_hdl, "[INFO]: smp_minor: "
156 	    "IDed this node as smp\n");
157 	return (DI_WALK_TERMINATE);
158 }
159 
160 static int
161 smp_select(di_node_t node, rcm_arg_t *rp)
162 {
163 	rcm_arg_t rarg;
164 	di_retire_t	*dp = rp->rcm_dp;
165 
166 	rarg.rcm_dp = dp;
167 
168 	/*
169 	 * Check if this is an smp minor. If any one minor is DDI_NT_SMP
170 	 * we assume it is an smp.
171 	 */
172 	rarg.rcm_supp = 0;
173 	if (di_walk_minor(node, DDI_NT_SMP, 0, &rarg, smp_minor) != 0) {
174 		dp->rt_debug(dp->rt_hdl, "[INFO]: smp_select:"
175 		    "di_walk_minor failed. Returning NOTSUP\n");
176 		return (0);
177 	}
178 
179 	return (rarg.rcm_supp);
180 }
181 
182 /*ARGSUSED*/
183 static int
184 disk_minor(di_node_t node, di_minor_t minor, void *arg)
185 {
186 	rcm_arg_t *rp = (rcm_arg_t *)arg;
187 	di_retire_t *dp = rp->rcm_dp;
188 
189 	if (di_minor_spectype(minor) == S_IFBLK) {
190 		rp->rcm_supp = 1;
191 		dp->rt_debug(dp->rt_hdl, "[INFO]: disk_minor: is disk minor. "
192 		    "IDed this node as disk\n");
193 		return (DI_WALK_TERMINATE);
194 	}
195 
196 	dp->rt_debug(dp->rt_hdl, "[INFO]: disk_minor: Not a disk minor. "
197 	    "Continuing minor walk\n");
198 	return (DI_WALK_CONTINUE);
199 }
200 
201 static int
202 disk_select(di_node_t node, rcm_arg_t *rp)
203 {
204 	rcm_arg_t rarg;
205 	di_retire_t	*dp = rp->rcm_dp;
206 
207 	rarg.rcm_dp = dp;
208 
209 	/*
210 	 * Check if this is a disk minor. If any one minor is DDI_NT_BLOCK
211 	 * we assume it is a disk
212 	 */
213 	rarg.rcm_supp = 0;
214 	if (di_walk_minor(node, DDI_NT_BLOCK, 0, &rarg, disk_minor) != 0) {
215 		dp->rt_debug(dp->rt_hdl, "[INFO]: disk_select: di_walk_minor "
216 		    "failed. Returning NOTSUP\n");
217 		return (0);
218 	}
219 
220 	return (rarg.rcm_supp);
221 }
222 
223 static int
224 nexus_select(di_node_t node, rcm_arg_t *rp)
225 {
226 	int select;
227 	char *path;
228 
229 	di_retire_t *dp = rp->rcm_dp;
230 
231 	path = di_devfs_path(node);
232 	if (path == NULL) {
233 		dp->rt_debug(dp->rt_hdl, "[INFO]: nexus_select: "
234 		    "di_devfs_path() is NULL. Returning NOTSUP\n");
235 		return (0);
236 	}
237 
238 	/*
239 	 * Check if it is a nexus
240 	 */
241 	if (di_driver_ops(node) & DI_BUS_OPS) {
242 		dp->rt_debug(dp->rt_hdl, "[INFO]: nexus_select: is nexus %s\n",
243 		    path);
244 		select = 1;
245 	} else {
246 		dp->rt_debug(dp->rt_hdl, "[INFO]: nexus_select: not nexus %s\n",
247 		    path);
248 		select = 0;
249 	}
250 
251 	di_devfs_path_free(path);
252 
253 	return (select);
254 }
255 
256 static int
257 node_select(di_node_t node, void *arg)
258 {
259 	rcm_arg_t *rp = (rcm_arg_t *)arg;
260 	di_retire_t *dp;
261 	int	sel;
262 	int	i;
263 	char	*path;
264 	uint_t	state;
265 
266 	dp = rp->rcm_dp;
267 
268 	/* skip pseudo nodes - we only retire real hardware */
269 	path = di_devfs_path(node);
270 	if (strncmp(path, "/pseudo/", strlen("/pseudo/")) == 0 ||
271 	    strcmp(path, "/pseudo") == 0) {
272 		dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: "
273 		    "pseudo device in subtree - returning NOTSUP: %s\n",
274 		    path);
275 		rp->rcm_supp = 0;
276 		di_devfs_path_free(path);
277 		return (DI_WALK_TERMINATE);
278 	}
279 	di_devfs_path_free(path);
280 
281 	/*
282 	 * If a device is offline/detached/down it is
283 	 * retireable irrespective of the type of device,
284 	 * presumably the system is able to function without
285 	 * it.
286 	 */
287 	state = di_state(node);
288 	if ((state & DI_DRIVER_DETACHED) || (state & DI_DEVICE_OFFLINE) ||
289 	    (state & DI_BUS_DOWN)) {
290 		dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: device "
291 		    "is offline/detached. Assuming retire supported\n");
292 		return (DI_WALK_CONTINUE);
293 	}
294 
295 	sel = 0;
296 	for (i = 0; supported_devices[i].sel_name != NULL; i++) {
297 		sel = supported_devices[i].sel_selector(node, rp);
298 		if (sel == 1) {
299 			dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: "
300 			    "found supported device: %s\n",
301 			    supported_devices[i].sel_name);
302 			break;
303 		}
304 	}
305 
306 	if (sel != 1) {
307 		/*
308 		 * This node is not a supported device. Retire cannot proceed
309 		 */
310 		dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: found "
311 		    "unsupported device. Returning NOTSUP\n");
312 		rp->rcm_supp = 0;
313 		return (DI_WALK_TERMINATE);
314 	}
315 
316 	/*
317 	 * This node is supported. Check other nodes in this subtree.
318 	 */
319 	dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: This node supported. "
320 	    "Checking other nodes in subtree: %s\n", rp->rcm_root);
321 	return (DI_WALK_CONTINUE);
322 }
323 
324 
325 
326 /*
327  * when in doubt assume that retire is not supported for this device.
328  */
329 static int
330 retire_supported(rcm_arg_t *rp)
331 {
332 	di_retire_t	*dp;
333 	di_node_t rnode = rp->rcm_node;
334 
335 	dp = rp->rcm_dp;
336 
337 	/*
338 	 * We should not be here if devinfo snapshot is NULL.
339 	 */
340 	RIO_ASSERT(dp, rnode != DI_NODE_NIL);
341 
342 	/*
343 	 * Note: We initally set supported to 1, then walk the
344 	 * subtree rooted at devpath, allowing each node the
345 	 * opportunity to veto the support. We cannot do things
346 	 * the other way around i.e. assume "not supported" and
347 	 * let individual nodes indicate that they are supported.
348 	 * In the latter case, the supported flag would be set
349 	 * if any one node in the subtree was supported which is
350 	 * not what we want.
351 	 */
352 	rp->rcm_supp = 1;
353 	if (di_walk_node(rnode, DI_WALK_CLDFIRST, rp, node_select) != 0) {
354 		dp->rt_debug(dp->rt_hdl, "[ERROR]: retire_supported: "
355 		    "di_walk_node: failed. Returning NOTSUP\n");
356 		rp->rcm_supp = 0;
357 	}
358 
359 	if (rp->rcm_supp) {
360 		dp->rt_debug(dp->rt_hdl, "[INFO]: retire IS supported\n");
361 	}
362 
363 	return (rp->rcm_supp);
364 }
365 
366 static void
367 rcm_finalize(rcm_arg_t *rp, int retcode)
368 {
369 	rio_path_t 	*p;
370 	rio_path_t 	*tmp;
371 	int		flags = RCM_RETIRE_NOTIFY;
372 	int		retval;
373 	int		error;
374 	di_retire_t	*dp;
375 
376 	dp = rp->rcm_dp;
377 
378 	RIO_ASSERT(dp, retcode == 0 || retcode == -1);
379 
380 	dp->rt_debug(dp->rt_hdl, "[INFO]: rcm_finalize: retcode=%d: dev=%s\n",
381 	    retcode, rp->rcm_root);
382 
383 	for (p = rp->rcm_cons_nodes; p; ) {
384 		tmp = p;
385 		p = tmp->rpt_next;
386 		free(tmp);
387 	}
388 	rp->rcm_cons_nodes = NULL;
389 
390 	dp->rt_debug(dp->rt_hdl, "[INFO]: rcm_finalize: cons_nodes NULL\n");
391 
392 	for (p = rp->rcm_rsrc_minors; p; ) {
393 		tmp = p;
394 		p = tmp->rpt_next;
395 		if (retcode == 0) {
396 			retval = rp->rcm_remove(rp->rcm_handle,
397 			    tmp->rpt_path, flags, NULL);
398 			error = errno;
399 		} else {
400 			RIO_ASSERT(dp, retcode == -1);
401 			retval = rp->rcm_online(rp->rcm_handle,
402 			    tmp->rpt_path, flags, NULL);
403 			error = errno;
404 		}
405 		if (retval != RCM_SUCCESS) {
406 			dp->rt_debug(dp->rt_hdl, "[ERROR]: rcm_finalize: "
407 			    "rcm_%s: retval=%d: error=%s: path=%s\n",
408 			    retcode == 0 ? "remove" : "online", retval,
409 			    strerror(error), tmp->rpt_path);
410 		} else {
411 			dp->rt_debug(dp->rt_hdl, "[INFO]: rcm_finalize: "
412 			    "rcm_%s: SUCCESS: path=%s\n",
413 			    retcode == 0 ? "remove" : "online", tmp->rpt_path);
414 		}
415 		free(tmp);
416 	}
417 	rp->rcm_rsrc_minors = NULL;
418 }
419 /*ARGSUSED*/
420 static int
421 call_offline(di_node_t node, di_minor_t minor, void *arg)
422 {
423 	rcm_arg_t	*rp = (rcm_arg_t *)arg;
424 	di_retire_t	*dp = rp->rcm_dp;
425 	char		*mnp;
426 	rio_path_t	*rpt;
427 	int		retval;
428 
429 	mnp = di_devfs_minor_path(minor);
430 	if (mnp == NULL) {
431 		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_devfs_minor_path "
432 		    "failed. Returning RCM FAILURE: %s\n", rp->rcm_root);
433 		rp->rcm_retcode = RCM_FAILURE;
434 		return (DI_WALK_TERMINATE);
435 	}
436 
437 	rpt = s_calloc(1, sizeof (rio_path_t), 0);
438 	if (rpt == NULL) {
439 		dp->rt_debug(dp->rt_hdl, "[ERROR]: calloc failed. "
440 		    "Returning RCM FAILURE: %s\n", rp->rcm_root);
441 		di_devfs_path_free(mnp);
442 		rp->rcm_retcode = RCM_FAILURE;
443 		return (DI_WALK_TERMINATE);
444 	}
445 
446 	(void) snprintf(rpt->rpt_path, sizeof (rpt->rpt_path),
447 	    "/devices%s", mnp);
448 
449 	di_devfs_path_free(mnp);
450 
451 	retval = rp->rcm_offline(rp->rcm_handle, rpt->rpt_path,
452 	    RCM_RETIRE_REQUEST, NULL);
453 
454 	rpt->rpt_next = rp->rcm_rsrc_minors;
455 	rp->rcm_rsrc_minors = rpt;
456 
457 	if (retval == RCM_FAILURE) {
458 		dp->rt_debug(dp->rt_hdl, "[ERROR]: RCM OFFLINE failed "
459 		    "for: %s\n", rpt->rpt_path);
460 		rp->rcm_retcode = RCM_FAILURE;
461 		return (DI_WALK_TERMINATE);
462 	} else if (retval == RCM_SUCCESS) {
463 		rp->rcm_retcode = RCM_SUCCESS;
464 		dp->rt_debug(dp->rt_hdl, "[INFO]: RCM OFFLINE returned "
465 		    "RCM_SUCCESS: %s\n", rpt->rpt_path);
466 	} else if (retval != RCM_NO_CONSTRAINT) {
467 		dp->rt_debug(dp->rt_hdl, "[ERROR]: RCM OFFLINE returned "
468 		    "invalid value for: %s\n", rpt->rpt_path);
469 		rp->rcm_retcode = RCM_FAILURE;
470 		return (DI_WALK_TERMINATE);
471 	} else {
472 		dp->rt_debug(dp->rt_hdl, "[INFO]: RCM OFFLINE returned "
473 		    "RCM_NO_CONSTRAINT: %s\n", rpt->rpt_path);
474 	}
475 
476 	return (DI_WALK_CONTINUE);
477 }
478 
479 static int
480 offline_one(di_node_t node, void *arg)
481 {
482 	rcm_arg_t 	*rp = (rcm_arg_t *)arg;
483 	rio_path_t	*rpt;
484 	di_retire_t	*dp = rp->rcm_dp;
485 	char		*path;
486 
487 	/*
488 	 * We should already have terminated the walk
489 	 * in case of failure
490 	 */
491 	RIO_ASSERT(dp, rp->rcm_retcode == RCM_SUCCESS ||
492 	    rp->rcm_retcode == RCM_NO_CONSTRAINT);
493 
494 	dp->rt_debug(dp->rt_hdl, "[INFO]: offline_one: entered\n");
495 
496 	rp->rcm_retcode = RCM_NO_CONSTRAINT;
497 
498 	rpt = s_calloc(1, sizeof (rio_path_t), 0);
499 	if (rpt == NULL) {
500 		dp->rt_debug(dp->rt_hdl, "[ERROR]: rio_path_t calloc "
501 		    "failed: error: %s\n", strerror(errno));
502 		goto fail;
503 	}
504 
505 	path = di_devfs_path(node);
506 	if (path == NULL) {
507 		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_devfs_path "
508 		    "failed: error: %s\n", strerror(errno));
509 		free(rpt);
510 		goto fail;
511 	}
512 
513 	(void) strlcpy(rpt->rpt_path, path, sizeof (rpt->rpt_path));
514 
515 	di_devfs_path_free(path);
516 
517 	if (di_walk_minor(node, NULL, 0, rp, call_offline) != 0) {
518 		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_minor "
519 		    "failed: error: %s: %s\n", strerror(errno), path);
520 		free(rpt);
521 		goto fail;
522 	}
523 
524 	if (rp->rcm_retcode == RCM_FAILURE) {
525 		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_minor "
526 		    "returned: RCM_FAILURE: %s\n", rpt->rpt_path);
527 		free(rpt);
528 		goto fail;
529 	} else if (rp->rcm_retcode == RCM_SUCCESS) {
530 		dp->rt_debug(dp->rt_hdl, "[INFO]: di_walk_minor "
531 		    "returned: RCM_SUCCESS: %s\n", rpt->rpt_path);
532 		rpt->rpt_next = rp->rcm_cons_nodes;
533 		rp->rcm_cons_nodes = rpt;
534 	} else if (rp->rcm_retcode != RCM_NO_CONSTRAINT) {
535 		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_minor "
536 		    "returned: unknown RCM error code: %d, %s\n",
537 		    rp->rcm_retcode, rpt->rpt_path);
538 		free(rpt);
539 		goto fail;
540 	} else {
541 		dp->rt_debug(dp->rt_hdl, "[INFO]: di_walk_minor "
542 		    "returned: RCM_NO_CONSTRAINT: %s\n", rpt->rpt_path);
543 		free(rpt);
544 	}
545 
546 	/*
547 	 * RCM_SUCCESS or RCM_NO_CONSTRAINT.
548 	 * RCM_SUCCESS implies we overcame a constraint, so keep walking.
549 	 * RCM_NO_CONSTRAINT implies no constraints applied via RCM.
550 	 *	Continue walking in the hope that contracts or LDI will
551 	 * 	apply constraints
552 	 * set retcode to RCM_SUCCESS to show that at least 1 node
553 	 * completely walked
554 	 */
555 	rp->rcm_retcode = RCM_SUCCESS;
556 	return (DI_WALK_CONTINUE);
557 
558 fail:
559 	rp->rcm_retcode = RCM_FAILURE;
560 	return (DI_WALK_TERMINATE);
561 }
562 
563 /*
564  * Returns:
565  *	RCM_SUCCESS:  RCM constraints (if any) were applied. The
566  *	device paths for which constraints were applied is passed
567  *	back via the pp argument
568  *
569  *	RCM_FAILURE: Either RCM constraints prevent a retire or
570  *	an error occurred
571  */
572 static int
573 rcm_notify(rcm_arg_t *rp, char **pp, size_t *clen)
574 {
575 	size_t	len;
576 	rio_path_t *p;
577 	rio_path_t *tmp;
578 	char *plistp;
579 	char *s;
580 	di_retire_t *dp;
581 	di_node_t rnode;
582 
583 	dp = rp->rcm_dp;
584 
585 	dp->rt_debug(dp->rt_hdl, "[INFO]: rcm_notify() entered\n");
586 
587 	RIO_ASSERT(dp, rp->rcm_root);
588 
589 	*pp = NULL;
590 
591 	rnode = rp->rcm_node;
592 	if (rnode == DI_NODE_NIL) {
593 		dp->rt_debug(dp->rt_hdl, "[ERROR]: devinfo snapshot "
594 		    "NULL. Returning no RCM constraint: %s\n", rp->rcm_root);
595 		return (RCM_NO_CONSTRAINT);
596 	}
597 
598 	rp->rcm_retcode = RCM_NO_CONSTRAINT;
599 	rp->rcm_cons_nodes = NULL;
600 	rp->rcm_rsrc_minors = NULL;
601 	if (di_walk_node(rnode, DI_WALK_CLDFIRST, rp, offline_one) != 0) {
602 		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_node "
603 		    "failed: error: %s: %s\n", strerror(errno), rp->rcm_root);
604 		/* online is idempotent - safe to online non-offlined nodes */
605 		rcm_finalize(rp, -1);
606 		rp->rcm_retcode = RCM_FAILURE;
607 		goto out;
608 	}
609 
610 	if (rp->rcm_retcode == RCM_FAILURE) {
611 		dp->rt_debug(dp->rt_hdl, "[ERROR]: walk_node "
612 		    "returned retcode of RCM_FAILURE: %s\n", rp->rcm_root);
613 		rcm_finalize(rp, -1);
614 		goto out;
615 	}
616 
617 	if (rp->rcm_retcode == RCM_NO_CONSTRAINT) {
618 		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_node "
619 		    " - no nodes walked: RCM_NO_CONSTRAINT: %s\n",
620 		    rp->rcm_root);
621 	} else {
622 		dp->rt_debug(dp->rt_hdl, "[INFO]: walk_node: RCM_SUCCESS\n");
623 	}
624 
625 	/*
626 	 * Convert to a sequence of NUL separated strings terminated by '\0'\0'
627 	 */
628 	for (len = 0, p = rp->rcm_cons_nodes; p; p = p->rpt_next) {
629 		RIO_ASSERT(dp, p->rpt_path);
630 		RIO_ASSERT(dp, strlen(p->rpt_path) > 0);
631 		len += (strlen(p->rpt_path) + 1);
632 	}
633 	len++;	/* list terminating '\0' */
634 
635 	dp->rt_debug(dp->rt_hdl, "[INFO]: len of constraint str = %lu\n", len);
636 
637 	plistp = s_calloc(1, len, 0);
638 	if (plistp == NULL) {
639 		dp->rt_debug(dp->rt_hdl, "[ERROR]: fail to alloc "
640 		    "constraint list: error: %s: %s\n", strerror(errno),
641 		    rp->rcm_root);
642 		rcm_finalize(rp, -1);
643 		rp->rcm_retcode = RCM_FAILURE;
644 		goto out;
645 	}
646 
647 	for (s = plistp, p = rp->rcm_cons_nodes; p; ) {
648 		tmp = p;
649 		p = tmp->rpt_next;
650 		(void) strcpy(s, tmp->rpt_path);
651 		s += strlen(s) + 1;
652 		RIO_ASSERT(dp, s - plistp < len);
653 		free(tmp);
654 	}
655 	rp->rcm_cons_nodes = NULL;
656 	RIO_ASSERT(dp, s - plistp == len - 1);
657 	*s = '\0';
658 
659 	dp->rt_debug(dp->rt_hdl, "[INFO]: constraint str = %p\n", plistp);
660 
661 	*pp = plistp;
662 	*clen = len;
663 
664 	rp->rcm_retcode = RCM_SUCCESS;
665 out:
666 	return (rp->rcm_retcode);
667 }
668 
669 
670 /*ARGSUSED*/
671 int
672 di_retire_device(char *devpath, di_retire_t *dp, int flags)
673 {
674 	char path[PATH_MAX];
675 	struct stat sb;
676 	int retval = EINVAL;
677 	char *constraint = NULL;
678 	size_t clen;
679 	void *librcm_hdl;
680 	rcm_arg_t rarg = {0};
681 	int (*librcm_alloc_handle)();
682 	int (*librcm_free_handle)();
683 
684 	if (dp == NULL || dp->rt_debug == NULL || dp->rt_hdl == NULL)
685 		return (EINVAL);
686 
687 	if (devpath == NULL || devpath[0] == '\0') {
688 		dp->rt_debug(dp->rt_hdl, "[ERROR]: NULL argument(s)\n");
689 		return (EINVAL);
690 	}
691 
692 	if (devpath[0] != '/' || strlen(devpath) >= PATH_MAX ||
693 	    strncmp(devpath, "/devices/", strlen("/devices/")) == 0 ||
694 	    strstr(devpath, "../devices/") || strrchr(devpath, ':')) {
695 		dp->rt_debug(dp->rt_hdl, "[ERROR]: invalid devpath: %s\n",
696 		    devpath);
697 		return (EINVAL);
698 	}
699 
700 	if (flags != 0) {
701 		dp->rt_debug(dp->rt_hdl, "[ERROR]: flags should be 0: %d\n",
702 		    flags);
703 		return (EINVAL);
704 	}
705 
706 	/*
707 	 * dlopen rather than link against librcm since libdevinfo
708 	 * resides in / and librcm resides in /usr. The dlopen is
709 	 * safe to do since fmd which invokes the retire code
710 	 * resides on /usr and will not come here until /usr is
711 	 * mounted.
712 	 */
713 	librcm_hdl = dlopen(LIBRCM_PATH, RTLD_LAZY);
714 	if (librcm_hdl == NULL) {
715 		char *errstr = dlerror();
716 		dp->rt_debug(dp->rt_hdl, "[ERROR]: Cannot dlopen librcm: %s\n",
717 		    errstr ? errstr : "Unknown error");
718 		return (ENOSYS);
719 	}
720 
721 	librcm_alloc_handle = (int (*)())dlsym(librcm_hdl, "rcm_alloc_handle");
722 	rarg.rcm_offline = (int (*)())dlsym(librcm_hdl, "rcm_request_offline");
723 	rarg.rcm_online = (int (*)())dlsym(librcm_hdl, "rcm_notify_online");
724 	rarg.rcm_remove = (int (*)())dlsym(librcm_hdl, "rcm_notify_remove");
725 	librcm_free_handle = (int (*)())dlsym(librcm_hdl, "rcm_free_handle");
726 
727 	if (librcm_alloc_handle == NULL ||
728 	    rarg.rcm_offline == NULL ||
729 	    rarg.rcm_online == NULL ||
730 	    rarg.rcm_remove == NULL ||
731 	    librcm_free_handle == NULL) {
732 		dp->rt_debug(dp->rt_hdl, "[ERROR]: dlsym failed\n");
733 		retval = ENOSYS;
734 		goto out;
735 	}
736 
737 	/*
738 	 * Take a libdevinfo snapshot here because we cannot do so
739 	 * after device is retired. If device doesn't attach, we retire
740 	 * anyway i.e. it is not fatal.
741 	 */
742 	rarg.rcm_node = di_init(devpath, DINFOCPYALL);
743 	if (rarg.rcm_node == DI_NODE_NIL) {
744 		dp->rt_debug(dp->rt_hdl, "[ERROR]: device doesn't attach, "
745 		    "retiring anyway: %s\n", devpath);
746 	}
747 
748 	rarg.rcm_handle = NULL;
749 	if (librcm_alloc_handle(NULL, 0,  NULL, &rarg.rcm_handle)
750 	    != RCM_SUCCESS) {
751 		retval = errno;
752 		dp->rt_debug(dp->rt_hdl, "[ERROR]: failed to alloc "
753 		    "RCM handle. Returning RCM failure: %s\n", devpath);
754 		rarg.rcm_handle = NULL;
755 		goto out;
756 	}
757 
758 	rarg.rcm_root = devpath;
759 	rarg.rcm_dp = dp;
760 
761 	/*
762 	 * If device is already detached/nonexistent and cannot be
763 	 * attached, allow retire without checking device type.
764 	 * XXX
765 	 * Else, check if retire is supported for this device type.
766 	 */
767 	(void) snprintf(path, sizeof (path), "/devices%s", devpath);
768 	if (stat(path, &sb) == -1 || !S_ISDIR(sb.st_mode)) {
769 		dp->rt_debug(dp->rt_hdl, "[ERROR]: detached or nonexistent "
770 		    "device. Bypassing retire_supported: %s\n", devpath);
771 	} else if (!retire_supported(&rarg)) {
772 		dp->rt_debug(dp->rt_hdl, "[ERROR]: retire not supported for "
773 		    "device type: %s\n", devpath);
774 		retval = ENOTSUP;
775 		goto out;
776 	}
777 
778 	clen = 0;
779 	constraint = NULL;
780 	retval = rcm_notify(&rarg, &constraint, &clen);
781 	if (retval == RCM_FAILURE) {
782 		/* retire not permitted */
783 		dp->rt_debug(dp->rt_hdl, "[ERROR]: RCM constraints block "
784 		    "retire: %s\n", devpath);
785 		retval = EBUSY;
786 		goto out;
787 	} else if (retval == RCM_SUCCESS) {
788 		dp->rt_debug(dp->rt_hdl, "[INFO]: RCM constraints applied"
789 		    ": %s\n", devpath);
790 	} else if (retval == RCM_NO_CONSTRAINT) {
791 		dp->rt_debug(dp->rt_hdl, "[INFO]: No RCM constraints applied"
792 		    ": %s\n", devpath);
793 	} else {
794 		dp->rt_debug(dp->rt_hdl, "[ERROR]: notify returned unknown "
795 		    "return code: %d: %s\n", retval, devpath);
796 		retval = ESRCH;
797 		goto out;
798 	}
799 
800 	if (modctl(MODRETIRE, devpath, constraint, clen) != 0) {
801 		retval = errno;
802 		dp->rt_debug(dp->rt_hdl, "[ERROR]: retire modctl() failed: "
803 		    "%s: %s\n", devpath, strerror(retval));
804 		rcm_finalize(&rarg, -1);
805 		goto out;
806 	}
807 
808 	dp->rt_debug(dp->rt_hdl, "[INFO]: retire modctl() succeeded: %s\n",
809 	    devpath);
810 
811 	rcm_finalize(&rarg, 0);
812 
813 	retval = 0;
814 
815 out:
816 	if (rarg.rcm_handle)
817 		(void) librcm_free_handle(rarg.rcm_handle);
818 
819 	RIO_ASSERT(dp, rarg.rcm_cons_nodes == NULL);
820 	RIO_ASSERT(dp, rarg.rcm_rsrc_minors == NULL);
821 
822 	(void) dlclose(librcm_hdl);
823 
824 	free(constraint);
825 
826 	if (rarg.rcm_node != DI_NODE_NIL)
827 		di_fini(rarg.rcm_node);
828 
829 	return (retval);
830 }
831 
832 /*ARGSUSED*/
833 int
834 di_unretire_device(char *devpath, di_retire_t *dp)
835 {
836 	if (dp == NULL || dp->rt_debug == NULL || dp->rt_hdl == NULL)
837 		return (EINVAL);
838 
839 	if (devpath == NULL || devpath[0] == '\0') {
840 		dp->rt_debug(dp->rt_hdl, "[ERROR]: NULL devpath\n");
841 		return (EINVAL);
842 	}
843 
844 	if (devpath[0] != '/' || strlen(devpath) >= PATH_MAX ||
845 	    strncmp(devpath, "/devices/", strlen("/devices/")) == 0 ||
846 	    strstr(devpath, "../devices/") || strrchr(devpath, ':')) {
847 		dp->rt_debug(dp->rt_hdl, "[ERROR]: invalid devpath: %s\n",
848 		    devpath);
849 		return (EINVAL);
850 	}
851 
852 	if (modctl(MODUNRETIRE, devpath) != 0) {
853 		int err = errno;
854 		dp->rt_debug(dp->rt_hdl, "[ERROR]: unretire modctl() failed: "
855 		    "%s: %s\n", devpath, strerror(err));
856 		return (err);
857 	}
858 
859 	dp->rt_debug(dp->rt_hdl, "[INFO]: unretire modctl() done: %s\n",
860 	    devpath);
861 
862 	return (0);
863 }
864