Print this page
OS-66 Retired devices may still get attached leading to ndi_devi_online errors

@@ -19,18 +19,22 @@
  * CDDL HEADER END
  */
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, Nexenta Systemc, Inc.  All rights reserved.
  */
 
 #include <libdevinfo.h>
 #include <sys/modctl.h>
 #include <sys/stat.h>
 #include <string.h>
 #include <librcm.h>
 #include <dlfcn.h>
+#include <sys/scsi/scsi_address.h>
+#include <limits.h>
+#include <errno.h>
 
 #undef  NDEBUG
 #include <assert.h>
 
 typedef struct rio_path {

@@ -664,14 +668,13 @@
         rp->rcm_retcode = RCM_SUCCESS;
 out:
         return (rp->rcm_retcode);
 }
 
-
 /*ARGSUSED*/
-int
-di_retire_device(char *devpath, di_retire_t *dp, int flags)
+static int
+do_di_retire_device(char *devpath, di_retire_t *dp, int flags)
 {
         char path[PATH_MAX];
         struct stat sb;
         int retval = EINVAL;
         char *constraint = NULL;

@@ -828,12 +831,12 @@
 
         return (retval);
 }
 
 /*ARGSUSED*/
-int
-di_unretire_device(char *devpath, di_retire_t *dp)
+static int
+do_di_unretire_device(char *devpath, di_retire_t *dp)
 {
         if (dp == NULL || dp->rt_debug == NULL || dp->rt_hdl == NULL)
                 return (EINVAL);
 
         if (devpath == NULL || devpath[0] == '\0') {

@@ -858,6 +861,154 @@
 
         dp->rt_debug(dp->rt_hdl, "[INFO]: unretire modctl() done: %s\n",
             devpath);
 
         return (0);
+}
+
+/* Structure that holds physical path instance. */
+struct retire_mpath_info {
+        char *pathname;
+        struct retire_mpath_info *next;
+        char nodename[PATH_MAX];
+};
+
+static int
+retire_walk_nodes(di_node_t node, void *arg)
+{
+        char *dn = NULL;
+        struct retire_mpath_info **mpinfo = (struct retire_mpath_info **)arg;
+        di_node_t pnode;
+        char *baddr;
+        di_path_t path;
+
+        if (node == NULL || ((baddr = di_bus_addr(node)) == NULL) ||
+            baddr[0] == '\0') {
+                return (DI_WALK_CONTINUE);
+        }
+
+        if (((dn = strstr((*mpinfo)->pathname, baddr)) == NULL) ||
+            /* Make sure bus address matches completely. */
+            (strlen(dn) != strlen(baddr))) {
+                return (DI_WALK_CONTINUE);
+        }
+
+        if ((path = di_path_client_next_path(node, DI_PATH_NIL)) == NULL) {
+                return (DI_WALK_CONTINUE);
+        }
+
+        for (; path != NULL; path = di_path_client_next_path(node, path)) {
+                struct retire_mpath_info *ri, *prev;
+                char *port_id = NULL;
+                char *p;
+
+                if ((pnode = di_path_phci_node(path)) == DI_NODE_NIL) {
+                        continue;
+                }
+
+                if ((p = di_devfs_path(pnode)) == NULL) {
+                        continue;
+                }
+
+                if (di_path_prop_lookup_strings(path,
+                    SCSI_ADDR_PROP_TARGET_PORT, &port_id) == 1) {
+
+                        ri = malloc(sizeof (*ri) + PATH_MAX);
+                        if (ri != NULL) {
+                                prev = *mpinfo;
+
+                                ri->next = prev;
+
+                                /* Preserve nodename */
+                                ri->pathname = prev->pathname;
+                                (void) snprintf(&ri->nodename[0], PATH_MAX,
+                                    "%s/disk@%s,0", p, port_id);
+
+                                *mpinfo = ri;
+                        }
+                }
+
+                di_devfs_path_free(p);
+        }
+
+        return (DI_WALK_CONTINUE);
+}
+
+int
+do_di_retire_device_mp(char *devpath, di_retire_t *dp, int flags,
+    boolean_t retire)
+{
+        int err = 0;
+        struct retire_mpath_info mpinfo, *pmpinfo, *pcurr;
+        char *path;
+        di_node_t root_node;
+
+        /* First, retire the device itself. */
+        err = retire ?
+            do_di_retire_device(devpath, dp, flags) :
+            do_di_unretire_device(devpath, dp);
+
+        if (err != 0) {
+                dp->rt_debug(dp->rt_hdl, "di_%sretire_device failed to"
+                    " %sretire device: %d %s", retire ? "" : "un",
+                    retire ? "" : "un", err, devpath);
+                return (err);
+        }
+
+        /* Next, try to retire all physical paths, if possible. */
+        root_node = di_init("/", DINFOCPYALL | DINFOPATH | DINFOLYR);
+        if (root_node == DI_NODE_NIL) {
+                dp->rt_debug(dp->rt_hdl, "di_%sretire_device can't access"
+                    " device tree, MPxIO checks ignored for %s",
+                    retire ? "" : "un", devpath);
+                return (0);
+        }
+
+        /* Obtain multipath information. */
+        (void) memset(&mpinfo, 0, sizeof (mpinfo));
+        mpinfo.pathname = devpath;
+
+        pmpinfo = &mpinfo;
+
+        (void) di_walk_node(root_node, DI_WALK_CLDFIRST, &pmpinfo,
+            retire_walk_nodes);
+
+        /* Next, retire all possible physical paths. */
+        for (; err == 0 && pmpinfo != &mpinfo; ) {
+                pcurr = pmpinfo;
+                pmpinfo = pmpinfo->next;
+
+                path = &pcurr->nodename[0];
+
+                dp->rt_debug(dp->rt_hdl,
+                    "di_%sretire_device %sretiring physical path %s\n",
+                    retire ? "" : "un", retire ? "" : "un", path);
+
+                err = retire ?
+                    do_di_retire_device(path, dp, flags) :
+                    do_di_unretire_device(path, dp);
+
+                if (err != 0)
+                        dp->rt_debug(dp->rt_hdl,
+                            "di_%sretire_device failed to %sretire physical"
+                            " path %s, %d\n", retire ? "" : "un",
+                            retire ? "" : "un", path, err);
+
+                free(pcurr);
+        }
+
+        return (0);
+}
+
+/*ARGSUSED*/
+int
+di_retire_device(char *devpath, di_retire_t *dp, int flags)
+{
+        return (do_di_retire_device_mp(devpath, dp, flags, B_TRUE));
+}
+
+/*ARGSUSED*/
+int
+di_unretire_device(char *devpath, di_retire_t *dp)
+{
+        return (do_di_retire_device_mp(devpath, dp, 0, B_FALSE));
 }