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