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 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  *
26  * Copyright (c) 2017, Joyent, Inc.
27  */
28 
29 #include <sys/types.h>
30 #include <sys/scsi/impl/uscsi.h>
31 #include <sys/scsi/generic/commands.h>
32 
33 #include <unistd.h>
34 #include <fcntl.h>
35 #include <errno.h>
36 #include <string.h>
37 #include <strings.h>
38 #include <stdio.h>
39 #include <limits.h>
40 
41 #include <scsi/libscsi.h>
42 #include "libscsi_impl.h"
43 
44 struct uscsi_dev {
45 	int fd;
46 	char *dev;
47 };
48 
49 static void *
uscsi_open(libscsi_hdl_t * hp,const void * target)50 uscsi_open(libscsi_hdl_t *hp, const void *target)
51 {
52 	struct uscsi_dev *dp;
53 	const char *target_name = (const char *)target;
54 
55 	if ((dp = libscsi_zalloc(hp, sizeof (struct uscsi_dev))) == NULL)
56 		return (NULL);
57 
58 	if ((dp->dev = libscsi_strdup(hp, target_name)) == NULL) {
59 		libscsi_free(hp, dp);
60 		return (NULL);
61 	}
62 
63 	if ((dp->fd = open(target_name, O_RDONLY)) < 0) {
64 		(void) libscsi_error(hp, ESCSI_BADTARGET, "failed to open %s "
65 		    "for reading: %s", target_name, strerror(errno));
66 		libscsi_free(hp, dp->dev);
67 		libscsi_free(hp, dp);
68 		return (NULL);
69 	}
70 
71 	return (dp);
72 }
73 
74 static void
uscsi_close(libscsi_hdl_t * hp,void * private)75 uscsi_close(libscsi_hdl_t *hp, void *private)
76 {
77 	struct uscsi_dev *dp = (struct uscsi_dev *)private;
78 
79 	if (dp == NULL)
80 		return;
81 
82 	if (dp->fd > 0)
83 		(void) close(dp->fd);
84 
85 	libscsi_free(hp, dp->dev);
86 	libscsi_free(hp, dp);
87 }
88 
89 static int
xlate_flags(libscsi_hdl_t * hp,uint_t flags,int * uf)90 xlate_flags(libscsi_hdl_t *hp, uint_t flags, int *uf)
91 {
92 	uint_t f;
93 	int i;
94 
95 	f = 0;
96 
97 	for (i = 0; i < sizeof (flags) * 8; i++) {
98 		switch (flags & (1 << i)) {
99 		case 0:
100 			continue;
101 		case LIBSCSI_AF_READ:
102 			f |= USCSI_READ;
103 			break;
104 		case LIBSCSI_AF_WRITE:
105 			f |= USCSI_WRITE;
106 			break;
107 		case LIBSCSI_AF_SILENT:
108 			f |= USCSI_SILENT;
109 			break;
110 		case LIBSCSI_AF_DIAGNOSE:
111 			f |= USCSI_DIAGNOSE;
112 			break;
113 		case LIBSCSI_AF_ISOLATE:
114 			f |= USCSI_ISOLATE;
115 			break;
116 		case LIBSCSI_AF_RQSENSE:
117 			f |= USCSI_RQENABLE;
118 			break;
119 		default:
120 			return (libscsi_error(hp, ESCSI_BOGUSFLAGS,
121 			    "flag 0x%x is unknown", 1 << i));
122 		}
123 	}
124 
125 	*uf = f;
126 
127 	return (0);
128 }
129 
130 static int
uscsi_exec(libscsi_hdl_t * hp,void * private,libscsi_action_t * ap)131 uscsi_exec(libscsi_hdl_t *hp, void *private, libscsi_action_t *ap)
132 {
133 	struct uscsi_dev *dp = (struct uscsi_dev *)private;
134 	struct uscsi_cmd cmd;
135 	size_t data_a, data_v;
136 	uint8_t *cp;
137 	uint_t flags;
138 
139 	bzero(&cmd, sizeof (cmd));
140 
141 	cp = libscsi_action_get_cdb(ap);
142 	if (cp == NULL)
143 		return (-1);
144 
145 	flags = libscsi_action_get_flags(ap);
146 	if (xlate_flags(hp, flags, &cmd.uscsi_flags) != 0)
147 		return (-1);
148 
149 	cmd.uscsi_status = (short)-1;
150 	cmd.uscsi_timeout = (short)libscsi_action_get_timeout(ap);
151 
152 	cmd.uscsi_cdb = (caddr_t)cp;
153 	cmd.uscsi_cdblen = libscsi_action_get_cdblen(ap);
154 	if (cmd.uscsi_cdblen == 0)
155 		return (-1);
156 
157 	if (flags & (LIBSCSI_AF_READ | LIBSCSI_AF_WRITE)) {
158 		if (libscsi_action_get_buffer(ap,
159 		    (uint8_t **)&cmd.uscsi_bufaddr, &data_a, &data_v) != 0)
160 			return (-1);
161 		if (flags & LIBSCSI_AF_READ)
162 			cmd.uscsi_buflen = data_a;
163 		else
164 			cmd.uscsi_buflen = data_v;
165 	}
166 	if (flags & LIBSCSI_AF_RQSENSE) {
167 		if (libscsi_action_get_sense(ap, (uint8_t **)&cmd.uscsi_rqbuf,
168 		    &data_a, NULL) != 0)
169 			return (-1);
170 		if (data_a > UCHAR_MAX)
171 			data_a = UCHAR_MAX;
172 		cmd.uscsi_rqlen = (uchar_t)data_a;
173 		cmd.uscsi_rqstatus = (uchar_t)-1;
174 	}
175 
176 	if (ioctl(dp->fd, USCSICMD, &cmd) < 0) {
177 		ASSERT(errno != EFAULT);
178 		switch (errno) {
179 		case EINVAL:
180 			return (libscsi_error(hp, ESCSI_BADCMD, "internal "
181 			    "uscsi error"));
182 		case EPERM:
183 			return (libscsi_error(hp, ESCSI_PERM, "insufficient "
184 			    "privileges "));
185 		case EIO:
186 			/* Command never executed at all */
187 			if (cmd.uscsi_status == (short)-1)
188 				return (libscsi_error(hp, ESCSI_IO, "I/O "
189 				    "error", strerror(errno)));
190 			break;
191 		default:
192 			return (libscsi_error(hp, ESCSI_SYS, "uscsi ioctl "
193 			    "failed: %s", strerror(errno)));
194 		}
195 	}
196 
197 	libscsi_action_set_status(ap, cmd.uscsi_status);
198 	if ((flags & LIBSCSI_AF_READ) && libscsi_action_set_datalen(ap,
199 	    cmd.uscsi_buflen - cmd.uscsi_resid) != 0)
200 		return (-1);
201 	if ((flags & LIBSCSI_AF_RQSENSE) && libscsi_action_set_senselen(ap,
202 	    cmd.uscsi_rqlen - cmd.uscsi_rqresid) != 0)
203 		return (-1);
204 
205 	return (0);
206 }
207 
208 /*ARGSUSED*/
209 static void
uscsi_target_name(libscsi_hdl_t * hp,void * private,char * buf,size_t len)210 uscsi_target_name(libscsi_hdl_t *hp, void *private, char *buf, size_t len)
211 {
212 	struct uscsi_dev *dp = (struct uscsi_dev *)private;
213 
214 	(void) snprintf(buf, len, "%s", dp->dev);
215 }
216 
217 static int
uscsi_max_transfer(libscsi_hdl_t * hp,void * private,size_t * sizep)218 uscsi_max_transfer(libscsi_hdl_t *hp, void *private, size_t *sizep)
219 {
220 	uscsi_xfer_t xfer;
221 	struct uscsi_dev *dp = (struct uscsi_dev *)private;
222 
223 	if (ioctl(dp->fd, USCSIMAXXFER, &xfer) < 0) {
224 		ASSERT(errno != EFAULT);
225 		switch (errno) {
226 		case EINVAL:
227 			return (libscsi_error(hp, ESCSI_BADCMD, "internal "
228 			    "uscsi error"));
229 		case EPERM:
230 			return (libscsi_error(hp, ESCSI_PERM, "insufficient "
231 			    "privileges "));
232 		case ENOTTY:
233 			return (libscsi_error(hp, ESCSI_NOTSUP, "max transfer "
234 			    "request not supported on device"));
235 		default:
236 			return (libscsi_error(hp, ESCSI_SYS, "uscsi ioctl "
237 			    "failed: %s", strerror(errno)));
238 		}
239 	}
240 
241 	if (xfer > SIZE_MAX)
242 		xfer = SIZE_MAX;
243 
244 	*sizep = (size_t)xfer;
245 	return (0);
246 }
247 
248 static const libscsi_engine_ops_t uscsi_ops = {
249 	.lseo_open = uscsi_open,
250 	.lseo_close = uscsi_close,
251 	.lseo_exec = uscsi_exec,
252 	.lseo_target_name = uscsi_target_name,
253 	.lseo_max_transfer = uscsi_max_transfer
254 };
255 
256 static const libscsi_engine_t uscsi_engine = {
257 	.lse_name = "uscsi",
258 	.lse_libversion = LIBSCSI_VERSION,
259 	.lse_ops = &uscsi_ops
260 };
261 
262 /*ARGSUSED*/
263 const libscsi_engine_t *
libscsi_uscsi_init(libscsi_hdl_t * hp)264 libscsi_uscsi_init(libscsi_hdl_t *hp)
265 {
266 	return (&uscsi_engine);
267 }
268