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
36typedef struct rio_path {
37	char		rpt_path[PATH_MAX];
38	struct rio_path	*rpt_next;
39} rio_path_t;
40
41typedef 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
55typedef struct selector {
56	char	*sel_name;
57	int	(*sel_selector)(di_node_t node, rcm_arg_t *rp);
58} di_selector_t;
59
60static 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
67static int disk_select(di_node_t node, rcm_arg_t *rp);
68static int nexus_select(di_node_t node, rcm_arg_t *rp);
69static int enclosure_select(di_node_t node, rcm_arg_t *rp);
70static int smp_select(di_node_t node, rcm_arg_t *rp);
71
72di_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
80void *
81s_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
91static void
92rio_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*/
106static int
107enclosure_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
118static int
119enclosure_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*/
148static int
149smp_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
160static int
161smp_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*/
183static int
184disk_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
201static int
202disk_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
223static int
224nexus_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
256static int
257node_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 */
329static int
330retire_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
366static void
367rcm_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*/
420static int
421call_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
479static int
480offline_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
558fail:
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 */
572static int
573rcm_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;
665out:
666	return (rp->rcm_retcode);
667}
668
669
670/*ARGSUSED*/
671int
672di_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
815out:
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*/
833int
834di_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