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 /* Portions Copyright 2008 Hitachi Ltd. */
  23 
  24 /*
  25  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  26  * Use is subject to license terms.
  27  */
  28 
  29 /*
  30  * Implementation of "scsi_vhci_f_sym_hds" asymmetric-active-active
  31  * failover_ops. The device has a preferred(owner)/non-preferred
  32  * with no action needed to use the non-preferred path. This is really
  33  * more inline with symmetric device so am using that prefix.
  34  *
  35  * This file imports the standard "scsi_vhci_f_sym", but with HDS specific
  36  * knowledge related to preferred/non-preferred path.
  37  */
  38 
  39 #include <sys/conf.h>
  40 #include <sys/file.h>
  41 #include <sys/ddi.h>
  42 #include <sys/sunddi.h>
  43 #include <sys/scsi/scsi.h>
  44 #include <sys/scsi/adapters/scsi_vhci.h>
  45 
  46 /* Supported device table entries.  */
  47 char *hds_sym_dev_table[] = {
  48 /*      "                  111111" */
  49 /*      "012345670123456789012345" */
  50 /*      "|-VID--||-----PID------|" */
  51 
  52         "HITACHI DF",
  53         NULL
  54 };
  55 
  56 static int      hds_sym_device_probe(struct scsi_device *,
  57                         struct scsi_inquiry *, void **);
  58 static void     hds_sym_device_unprobe(struct scsi_device *, void *);
  59 static void     hds_sym_init();
  60 static int      hds_sym_get_opinfo(struct scsi_device *sd,
  61                         struct scsi_path_opinfo *opinfo, void *ctpriv);
  62 
  63 #ifdef  lint
  64 #define scsi_vhci_failover_ops  scsi_vhci_failover_ops_f_sym_hds
  65 #endif  /* lint */
  66 /*
  67  * Use the following for the Asymmetric-Active-Active fops.
  68  * A different fops may get used for the Symmetric-Active-Active.
  69  */
  70 struct scsi_failover_ops scsi_vhci_failover_ops = {
  71         SFO_REV,
  72         SFO_NAME_SYM "_hds",
  73         hds_sym_dev_table,
  74         hds_sym_init,
  75         hds_sym_device_probe,
  76         hds_sym_device_unprobe,
  77         NULL,
  78         NULL,
  79         hds_sym_get_opinfo,
  80         /* The rest of the implementation comes from SFO_NAME_SYM import  */
  81 };
  82 
  83 static struct modlmisc modlmisc = {
  84         &mod_miscops, "f_sym_hds"
  85 };
  86 
  87 static struct modlinkage modlinkage = {
  88         MODREV_1, { (void *)&modlmisc, NULL }
  89 };
  90 
  91 #define HDS_MAX_INQ_BUF_SIZE            0xff
  92 #define HDS_INQ_PAGE_E0                 0xe0
  93 #define HDS_SAA_TYPE                    "DF00"
  94 #define ASYM_ACTIVE_ACTIVE              0
  95 #define SYM_ACTIVE_ACTIVE               1
  96 
  97 extern struct scsi_failover_ops *vhci_failover_ops_by_name(char *);
  98 
  99 int
 100 _init()
 101 {
 102         return (mod_install(&modlinkage));
 103 }
 104 
 105 int
 106 _fini()
 107 {
 108         return (mod_remove(&modlinkage));
 109 }
 110 
 111 int
 112 _info(struct modinfo *modinfop)
 113 {
 114         return (mod_info(&modlinkage, modinfop));
 115 }
 116 
 117 static void
 118 hds_sym_init()
 119 {
 120         struct scsi_failover_ops        *sfo, *ssfo, clone;
 121 
 122         /* clone SFO_NAME_SYM implementation for most things */
 123         ssfo = vhci_failover_ops_by_name(SFO_NAME_SYM);
 124         if (ssfo == NULL) {
 125                 VHCI_DEBUG(4, (CE_NOTE, NULL, "!hds_sym_init: "
 126                     "can't import " SFO_NAME_SYM "\n"));
 127                 return;
 128         }
 129         sfo                             = &scsi_vhci_failover_ops;
 130         clone                           = *ssfo;
 131         clone.sfo_rev                   = sfo->sfo_rev;
 132         clone.sfo_name                  = sfo->sfo_name;
 133         clone.sfo_devices               = sfo->sfo_devices;
 134         clone.sfo_init                  = sfo->sfo_init;
 135         clone.sfo_device_probe          = sfo->sfo_device_probe;
 136         clone.sfo_device_unprobe        = sfo->sfo_device_unprobe;
 137         clone.sfo_path_get_opinfo       = sfo->sfo_path_get_opinfo;
 138         *sfo                            = clone;
 139 }
 140 
 141 /* ARGSUSED */
 142 static int
 143 hds_sym_device_probe(struct scsi_device *sd, struct scsi_inquiry *stdinq,
 144     void **ctprivp)
 145 {
 146         char            **dt;
 147         char            *dftype;
 148         unsigned char   len;
 149         unsigned char   *inq_data = (unsigned char *)stdinq;
 150         unsigned char   pv;
 151         int             ret;
 152 
 153         VHCI_DEBUG(6, (CE_NOTE, NULL, "hds_sym_device_probe: vidpid %s\n",
 154             stdinq->inq_vid));
 155         for (dt = hds_sym_dev_table; *dt; dt++) {
 156                 if (strncmp(stdinq->inq_vid, *dt, strlen(*dt)))
 157                         continue;
 158                 len = inq_data[4];
 159                 if (len < 128) {
 160                         vhci_log(CE_NOTE, NULL,
 161                             "hds_sym_device_probe: vidpid %s len error: %d\n",
 162                             stdinq->inq_vid, len);
 163                         return (SFO_DEVICE_PROBE_PHCI);
 164                 }
 165 
 166                 dftype = (char *)&inq_data[128];
 167                 if (*dftype == 0) {
 168                         VHCI_DEBUG(4, (CE_NOTE, NULL,
 169                             "hds_sym_device_probe: vidpid %s"
 170                             " ASYM_ACTIVE_ACTIVE\n", stdinq->inq_vid));
 171                         pv = ASYM_ACTIVE_ACTIVE;
 172                         ret = SFO_DEVICE_PROBE_VHCI;
 173                 } else if (strncmp(dftype, HDS_SAA_TYPE,
 174                     strlen(HDS_SAA_TYPE)) == 0) {
 175                         VHCI_DEBUG(4, (CE_NOTE, NULL,
 176                             "hds_sym_device_probe: vidpid %s"
 177                             " SYM_ACTIVE_ACTIVE\n", stdinq->inq_vid));
 178                         pv = SYM_ACTIVE_ACTIVE;
 179                         ret = SFO_DEVICE_PROBE_VHCI;
 180                 } else
 181                         ret = SFO_DEVICE_PROBE_PHCI;
 182 
 183                 if (ret == SFO_DEVICE_PROBE_VHCI) {
 184                         /* ctprivp is NULL for vhci_is_dev_supported() probe */
 185                         if (ctprivp) {
 186                                 /*
 187                                  * Allocate failover module's 'client' private
 188                                  * data on the first successfull path probe.
 189                                  * NOTE: 'client' private means per lun guid,
 190                                  * not per-path.
 191                                  */
 192                                 if (*ctprivp == NULL)
 193                                         *ctprivp = kmem_alloc(sizeof (pv),
 194                                             KM_SLEEP);
 195 
 196                                 /* update private data */
 197                                 *((unsigned char *)*ctprivp) = pv;
 198                         }
 199                 } else {
 200                         VHCI_DEBUG(4, (CE_NOTE, NULL,
 201                             "hds_sym_device_probe: vidpid %s"
 202                             " - unknown dftype: %d\n",
 203                             stdinq->inq_vid, *dftype));
 204                 }
 205                 return (SFO_DEVICE_PROBE_PHCI);
 206 
 207         }
 208         return (SFO_DEVICE_PROBE_PHCI);
 209 }
 210 
 211 /* ARGSUSED */
 212 static void
 213 hds_sym_device_unprobe(struct scsi_device *sd, void *ctpriv)
 214 {
 215         if (ctpriv != NULL) {
 216                 kmem_free(ctpriv, sizeof (unsigned char));
 217         }
 218 }
 219 
 220 
 221 /*
 222  * Local routine to get inquiry VPD page from the device.
 223  *
 224  * return 1 for failure
 225  * return 0 for success
 226  */
 227 static int
 228 hds_get_inquiry_vpd_page(struct scsi_device *sd, unsigned char page,
 229     unsigned char *buf, int size)
 230 {
 231         int             retval = 0;
 232         struct buf      *bp;
 233         struct scsi_pkt *pkt;
 234         struct scsi_address     *ap;
 235 
 236         if ((buf == NULL) || (size == 0)) {
 237                 return (1);
 238         }
 239         bp = getrbuf(KM_NOSLEEP);
 240         if (bp == NULL) {
 241                 return (1);
 242         }
 243         bp->b_un.b_addr = (char *)buf;
 244         bp->b_flags = B_READ;
 245         bp->b_bcount = size;
 246         bp->b_resid = 0;
 247 
 248         ap = &sd->sd_address;
 249         pkt = scsi_init_pkt(ap, NULL, bp, CDB_GROUP0,
 250             sizeof (struct scsi_arq_status), 0, 0, NULL, NULL);
 251         if (pkt == NULL) {
 252                 VHCI_DEBUG(4, (CE_WARN, NULL,
 253                     "hds_get_inquiry_vpd_page:"
 254                     "Failed to initialize packet"));
 255                 freerbuf(bp);
 256                 return (1);
 257         }
 258 
 259         /*
 260          * Send the inquiry command for page xx to the target.
 261          * Data is returned in the buf pointed to by buf.
 262          */
 263 
 264         pkt->pkt_cdbp[0] = SCMD_INQUIRY;
 265         pkt->pkt_cdbp[1] = 0x1;
 266         pkt->pkt_cdbp[2] = page;
 267         pkt->pkt_cdbp[4] = (unsigned char)size;
 268         pkt->pkt_time = 90;
 269         retval = vhci_do_scsi_cmd(pkt);
 270         scsi_destroy_pkt(pkt);
 271         freerbuf(bp);
 272         return (!retval);
 273 
 274 }
 275 
 276 /* ARGSUSED */
 277 static int
 278 hds_sym_get_opinfo(struct scsi_device *sd, struct scsi_path_opinfo *opinfo,
 279     void *ctpriv)
 280 {
 281         unsigned char   inq_vpd_buf[HDS_MAX_INQ_BUF_SIZE];
 282 
 283         opinfo->opinfo_rev = OPINFO_REV;
 284         (void) strcpy(opinfo->opinfo_path_attr, "primary");
 285         opinfo->opinfo_path_state  = SCSI_PATH_ACTIVE;
 286         opinfo->opinfo_pswtch_best = 0;              /* N/A */
 287         opinfo->opinfo_pswtch_worst = 0;     /* N/A */
 288         opinfo->opinfo_xlf_capable = 0;
 289         opinfo->opinfo_mode = SCSI_NO_FAILOVER;
 290         ASSERT(ctpriv != NULL);
 291         if (*((unsigned char *)ctpriv) == SYM_ACTIVE_ACTIVE) {
 292                 VHCI_DEBUG(4, (CE_NOTE, NULL,
 293                     "hds_get_opinfo: sd(%p): sym_active_active "
 294                     "preferred bit set ", (void*)sd));
 295                 opinfo->opinfo_preferred = PCLASS_PREFERRED;
 296                 return (0);
 297         }
 298         /* check if this is the preferred path */
 299         if (hds_get_inquiry_vpd_page(sd, HDS_INQ_PAGE_E0, inq_vpd_buf,
 300             sizeof (inq_vpd_buf)) != 0) {
 301                 VHCI_DEBUG(4, (CE_WARN, NULL,
 302                     "hds_get_opinfo: sd(%p):Unable to "
 303                     "get inquiry Page %x", (void*)sd, HDS_INQ_PAGE_E0));
 304                 return (1);
 305         }
 306         if (inq_vpd_buf[4] & 0x80) {
 307                 if (inq_vpd_buf[4] & 0x40) {
 308                         VHCI_DEBUG(4, (CE_NOTE, NULL,
 309                             "hds_get_opinfo: sd(%p): preferred bit set ",
 310                             (void*)sd));
 311                         opinfo->opinfo_preferred = PCLASS_PREFERRED;
 312                 } else {
 313                         VHCI_DEBUG(4, (CE_NOTE, NULL,
 314                             "hds_get_opinfo: sd(%p): non-preferred bit set ",
 315                             (void*)sd));
 316                         opinfo->opinfo_preferred = PCLASS_NONPREFERRED;
 317                 }
 318         } else {
 319                 vhci_log(CE_NOTE, NULL,
 320                     "hds_get_opinfo: sd(%p): "
 321                     "get inquiry Page %x has invalid P/SVid bit set",
 322                     (void*)sd, HDS_INQ_PAGE_E0);
 323                 return (1);
 324         }
 325 
 326         return (0);
 327 }