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