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 2008 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 **ctpriv)
145 {
146 	char	**dt;
147 	char	*dftype;
148 	unsigned char	len;
149 	unsigned char	*inq_data = (unsigned char *)stdinq;
150 
151 	VHCI_DEBUG(6, (CE_NOTE, NULL, "hds_sym_device_probe: vidpid %s\n",
152 	    stdinq->inq_vid));
153 	for (dt = hds_sym_dev_table; *dt; dt++) {
154 		if (strncmp(stdinq->inq_vid, *dt, strlen(*dt)))
155 			continue;
156 		len = inq_data[4];
157 		if (len < 128) {
158 			vhci_log(CE_NOTE, NULL,
159 			    "hds_sym_device_probe: vidpid %s len error: %d\n",
160 			    stdinq->inq_vid, len);
161 			return (SFO_DEVICE_PROBE_PHCI);
162 		}
163 		*ctpriv = kmem_alloc(sizeof (unsigned char), KM_SLEEP);
164 		dftype = (char *)&inq_data[128];
165 		if (*dftype == 0) {
166 			VHCI_DEBUG(4, (CE_NOTE, NULL,
167 			    "hds_sym_device_probe: vidpid %s"
168 			    " ASYM_ACTIVE_ACTIVE\n", stdinq->inq_vid));
169 			*((unsigned char *)*ctpriv) = ASYM_ACTIVE_ACTIVE;
170 			return (SFO_DEVICE_PROBE_VHCI);
171 		}
172 		if (strncmp(dftype, HDS_SAA_TYPE, strlen(HDS_SAA_TYPE)) == 0) {
173 			*((unsigned char *)*ctpriv) = SYM_ACTIVE_ACTIVE;
174 			VHCI_DEBUG(4, (CE_NOTE, NULL,
175 			    "hds_sym_device_probe: vidpid %s"
176 			    " SYM_ACTIVE_ACTIVE\n", stdinq->inq_vid));
177 			return (SFO_DEVICE_PROBE_VHCI);
178 		}
179 		VHCI_DEBUG(4, (CE_NOTE, NULL,
180 		    "hds_sym_device_probe: vidpid %s"
181 		    " - unknown dftype: %d\n", stdinq->inq_vid, *dftype));
182 		kmem_free(*ctpriv, sizeof (unsigned char));
183 		*ctpriv = NULL;
184 		return (SFO_DEVICE_PROBE_PHCI);
185 
186 	}
187 	return (SFO_DEVICE_PROBE_PHCI);
188 }
189 
190 /* ARGSUSED */
191 static void
192 hds_sym_device_unprobe(struct scsi_device *sd, void *ctpriv)
193 {
194 	if (ctpriv != NULL) {
195 		kmem_free(ctpriv, sizeof (unsigned char));
196 	}
197 }
198 
199 
200 /*
201  * Local routine to get inquiry VPD page from the device.
202  *
203  * return 1 for failure
204  * return 0 for success
205  */
206 static int
207 hds_get_inquiry_vpd_page(struct scsi_device *sd, unsigned char page,
208     unsigned char *buf, int size)
209 {
210 	int		retval = 0;
211 	struct buf	*bp;
212 	struct scsi_pkt	*pkt;
213 	struct scsi_address	*ap;
214 
215 	if ((buf == NULL) || (size == 0)) {
216 		return (1);
217 	}
218 	bp = getrbuf(KM_NOSLEEP);
219 	if (bp == NULL) {
220 		return (1);
221 	}
222 	bp->b_un.b_addr = (char *)buf;
223 	bp->b_flags = B_READ;
224 	bp->b_bcount = size;
225 	bp->b_resid = 0;
226 
227 	ap = &sd->sd_address;
228 	pkt = scsi_init_pkt(ap, NULL, bp, CDB_GROUP0,
229 	    sizeof (struct scsi_arq_status), 0, 0, NULL, NULL);
230 	if (pkt == NULL) {
231 		VHCI_DEBUG(4, (CE_WARN, NULL,
232 		    "hds_get_inquiry_vpd_page:"
233 		    "Failed to initialize packet"));
234 		freerbuf(bp);
235 		return (1);
236 	}
237 
238 	/*
239 	 * Send the inquiry command for page xx to the target.
240 	 * Data is returned in the buf pointed to by buf.
241 	 */
242 
243 	pkt->pkt_cdbp[0] = SCMD_INQUIRY;
244 	pkt->pkt_cdbp[1] = 0x1;
245 	pkt->pkt_cdbp[2] = page;
246 	pkt->pkt_cdbp[4] = (unsigned char)size;
247 	pkt->pkt_time = 90;
248 	retval = vhci_do_scsi_cmd(pkt);
249 	scsi_destroy_pkt(pkt);
250 	freerbuf(bp);
251 	return (!retval);
252 
253 }
254 
255 /* ARGSUSED */
256 static int
257 hds_sym_get_opinfo(struct scsi_device *sd, struct scsi_path_opinfo *opinfo,
258     void *ctpriv)
259 {
260 	unsigned char	inq_vpd_buf[HDS_MAX_INQ_BUF_SIZE];
261 
262 	opinfo->opinfo_rev = OPINFO_REV;
263 	(void) strcpy(opinfo->opinfo_path_attr, "primary");
264 	opinfo->opinfo_path_state  = SCSI_PATH_ACTIVE;
265 	opinfo->opinfo_pswtch_best = 0;		/* N/A */
266 	opinfo->opinfo_pswtch_worst = 0;	/* N/A */
267 	opinfo->opinfo_xlf_capable = 0;
268 	opinfo->opinfo_mode = SCSI_NO_FAILOVER;
269 	ASSERT(ctpriv != NULL);
270 	if (*((unsigned char *)ctpriv) == SYM_ACTIVE_ACTIVE) {
271 		VHCI_DEBUG(4, (CE_NOTE, NULL,
272 		    "hds_get_opinfo: sd(%p): sym_active_active "
273 		    "preferred bit set ", (void*)sd));
274 		opinfo->opinfo_preferred = PCLASS_PREFERRED;
275 		return (0);
276 	}
277 	/* check if this is the preferred path */
278 	if (hds_get_inquiry_vpd_page(sd, HDS_INQ_PAGE_E0, inq_vpd_buf,
279 	    sizeof (inq_vpd_buf)) != 0) {
280 		VHCI_DEBUG(4, (CE_WARN, NULL,
281 		    "hds_get_opinfo: sd(%p):Unable to "
282 		    "get inquiry Page %x", (void*)sd, HDS_INQ_PAGE_E0));
283 		return (1);
284 	}
285 	if (inq_vpd_buf[4] & 0x80) {
286 		if (inq_vpd_buf[4] & 0x40) {
287 			VHCI_DEBUG(4, (CE_NOTE, NULL,
288 			    "hds_get_opinfo: sd(%p): preferred bit set ",
289 			    (void*)sd));
290 			opinfo->opinfo_preferred = PCLASS_PREFERRED;
291 		} else {
292 			VHCI_DEBUG(4, (CE_NOTE, NULL,
293 			    "hds_get_opinfo: sd(%p): non-preferred bit set ",
294 			    (void*)sd));
295 			opinfo->opinfo_preferred = PCLASS_NONPREFERRED;
296 		}
297 	} else {
298 		vhci_log(CE_NOTE, NULL,
299 		    "hds_get_opinfo: sd(%p): "
300 		    "get inquiry Page %x has invalid P/SVid bit set",
301 		    (void*)sd, HDS_INQ_PAGE_E0);
302 		return (1);
303 	}
304 
305 	return (0);
306 }
307