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