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 }