xref: /openbsd-src/usr.sbin/iscsid/vscsi.c (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
1 /*	$OpenBSD: vscsi.c,v 1.8 2011/05/04 21:00:04 claudio Exp $ */
2 
3 /*
4  * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/ioctl.h>
21 #include <sys/queue.h>
22 #include <sys/socket.h>
23 #include <sys/uio.h>
24 
25 #include <scsi/iscsi.h>
26 #include <scsi/scsi_all.h>
27 #include <dev/vscsivar.h>
28 
29 #include <event.h>
30 #include <fcntl.h>
31 #include <stdlib.h>
32 #include <string.h>
33 
34 #include "iscsid.h"
35 #include "log.h"
36 
37 struct vscsi {
38 	struct event	ev;
39 	int		fd;
40 } v;
41 
42 struct scsi_task {
43 	struct task	task;
44 	int		tag;
45 	u_int		target;
46 	u_int		lun;
47 	size_t		datalen;
48 };
49 
50 void vscsi_callback(struct connection *, void *, struct pdu *);
51 void vscsi_fail(void *arg);
52 void vscsi_dataout(struct connection *, struct scsi_task *, u_int32_t, size_t);
53 
54 void
55 vscsi_open(char *dev)
56 {
57 	if ((v.fd = open(dev, O_RDWR)) == -1)
58 		fatal("vscsi_open");
59 
60 	event_set(&v.ev, v.fd, EV_READ|EV_PERSIST, vscsi_dispatch, NULL);
61 	event_add(&v.ev, NULL);
62 }
63 
64 void
65 vscsi_dispatch(int fd, short event, void *arg)
66 {
67 	struct vscsi_ioc_i2t i2t;
68 	struct iscsi_pdu_scsi_request *sreq;
69 	struct session *s;
70 	struct scsi_task *t;
71 	struct pdu *p;
72 #if 0
73 	char *buf;
74 	u_int32_t t32;
75 #endif
76 
77 	if (!(event & EV_READ)) {
78 		log_debug("spurious read call");
79 		return;
80 	}
81 
82 	if (ioctl(v.fd, VSCSI_I2T, &i2t) == -1)
83 		fatal("vscsi_dispatch");
84 
85 	s = initiator_t2s(i2t.target);
86 	if (s == NULL)
87 		fatalx("vscsi_dispatch: unknown target");
88 
89 	if (!(t = calloc(1, sizeof(*t))))
90 		fatal("vscsi_dispatch");
91 
92 	t->tag = i2t.tag;
93 	t->target = i2t.target;
94 	t->lun = i2t.lun;
95 
96 	if (!(p = pdu_new()))
97 		fatal("vscsi_dispatch");
98 	if (!(sreq = pdu_gethdr(p)))
99 		fatal("vscsi_dispatch");
100 
101 	sreq->opcode = ISCSI_OP_SCSI_REQUEST;
102 	/* XXX use untagged commands, dlg sais so */
103 	sreq->flags = ISCSI_SCSI_F_F | ISCSI_SCSI_ATTR_UNTAGGED;
104 	if (i2t.direction == VSCSI_DIR_WRITE)
105 		sreq->flags |= ISCSI_SCSI_F_W;
106 	if (i2t.direction == VSCSI_DIR_READ)
107 		sreq->flags |= ISCSI_SCSI_F_R;
108 	sreq->bytes = htonl(i2t.datalen);
109 
110 	/* LUN handling: currently we only do single level LUNs < 256 */
111 	if (t->lun >= 256)
112 		fatal("vscsi_dispatch: I'm sorry, Dave. "
113 		    "I'm afraid I can't do that.");
114 	sreq->lun[1] = t->lun;
115 
116 	bcopy(&i2t.cmd, sreq->cdb, i2t.cmdlen);
117 
118 #if 0
119 	if (i2t.direction == VSCSI_DIR_WRITE) {
120 		if (!(buf = pdu_alloc(i2t.datalen)))
121 			fatal("vscsi_dispatch");
122 		t32 = htonl(i2t.datalen);
123 		bcopy(&t32, &sreq->ahslen, sizeof(t32));
124 		vscsi_data(VSCSI_DATA_WRITE, i2t.tag, buf, i2t.datalen);
125 		pdu_addbuf(p, buf, i2t.datalen, PDU_DATA);
126 	}
127 #endif
128 
129 	task_init(&t->task, s, 0, t, vscsi_callback, vscsi_fail);
130 	task_pdu_add(&t->task, p);
131 	session_task_issue(s, &t->task);
132 }
133 
134 void
135 vscsi_data(unsigned long req, int tag, void *buf, size_t len)
136 {
137 	struct vscsi_ioc_data data;
138 
139 	data.tag = tag;
140 	data.data = buf;
141 	data.datalen = len;
142 
143 	if (ioctl(v.fd, req, &data) == -1)
144 		fatal("vscsi_data");
145 }
146 
147 void
148 vscsi_status(int tag, int status, void *buf, size_t len)
149 {
150 	struct vscsi_ioc_t2i t2i;
151 
152 	bzero(&t2i, sizeof(t2i));
153 	t2i.tag = tag;
154 	t2i.status = status;
155 	if (buf) {
156 		if (len > sizeof(t2i.sense))
157 			len = sizeof(t2i.sense);
158 		bcopy(buf, &t2i.sense, len);
159 	}
160 
161 	if (ioctl(v.fd, VSCSI_T2I, &t2i) == -1)
162 		fatal("vscsi_status");
163 }
164 
165 void
166 vscsi_event(unsigned long req, u_int target, u_int lun)
167 {
168 	struct vscsi_ioc_devevent devev;
169 
170 	devev.target = target;
171 	devev.lun = lun;
172 
173 	if (ioctl(v.fd, req, &devev) == -1)
174 		fatal("vscsi_event");
175 }
176 
177 void
178 vscsi_callback(struct connection *c, void *arg, struct pdu *p)
179 {
180 	struct scsi_task *t = arg;
181 	struct iscsi_pdu_scsi_response *sresp;
182 	struct iscsi_pdu_rt2 *r2t;
183 	int status = VSCSI_STAT_DONE;
184 	u_char *buf = NULL;
185 	size_t size = 0, n;
186 	int tag;
187 
188 	sresp = pdu_getbuf(p, NULL, PDU_HEADER);
189 	switch (ISCSI_PDU_OPCODE(sresp->opcode)) {
190 	case ISCSI_OP_SCSI_RESPONSE:
191 		conn_task_cleanup(c, &t->task);
192 		tag = t->tag;
193 		free(t);
194 
195 		if (!(sresp->flags & 0x80) || (sresp->flags & 0x06) == 0x06 ||
196 		    (sresp->flags & 0x18) == 0x18) {
197 			log_debug("vscsi_callback: bad scsi response");
198 			conn_fail(c);
199 			break;
200 		}
201 		/* XXX handle the various serial numbers */
202 		if (sresp->response) {
203 			status = VSCSI_STAT_ERR;
204 			goto send_status;
205 		}
206 		switch (sresp->status) {
207 		case ISCSI_SCSI_STAT_GOOD:
208 			break;
209 		case ISCSI_SCSI_STAT_CHCK_COND:
210 			status = VSCSI_STAT_SENSE;
211 			/* stupid encoding of sense data in the data segment */
212 			buf = pdu_getbuf(p, &n, PDU_DATA);
213 			if (buf) {
214 				size = buf[0] << 8 | buf[1];
215 				buf += 2;
216 			}
217 			break;
218 		default:
219 			status = VSCSI_STAT_ERR;
220 			break;
221 		}
222 send_status:
223 		vscsi_status(tag, status, buf, size);
224 		break;
225 	case ISCSI_OP_DATA_IN:
226 		buf = pdu_getbuf(p, &n, PDU_DATA);
227 		size = sresp->datalen[0] << 16 | sresp->datalen[1] << 8 |
228 		    sresp->datalen[2];
229 		if (size > n)
230 			fatal("This does not work as it should");
231 		vscsi_data(VSCSI_DATA_READ, t->tag, buf, size);
232 		if (sresp->flags & 1) {			/* XXX magic */
233 			conn_task_cleanup(c, &t->task);
234 			vscsi_status(t->tag, status, NULL, 0);
235 			free(t);
236 		}
237 		break;
238 	case ISCSI_OP_R2T:
239 		conn_task_cleanup(c, &t->task);
240 		r2t = (struct iscsi_pdu_rt2 *)sresp;
241 		if (ntohl(r2t->buffer_offs))
242 			fatalx("vscsi: r2t bummer failure");
243 		size = ntohl(r2t->desired_datalen);
244 
245 		vscsi_dataout(c, t, r2t->ttt, size);
246 		break;
247 	default:
248 		log_debug("scsi task: tag %d, target %d lun %d", t->tag,
249 		    t->target, t->lun);
250 		log_pdu(p, 1);
251 	}
252 	pdu_free(p);
253 }
254 
255 void
256 vscsi_fail(void *arg)
257 {
258 	struct scsi_task *t = arg;
259 	int tag;
260 
261 	conn_task_cleanup(NULL, &t->task);
262 	tag = t->tag;
263 	free(t);
264 	vscsi_status(tag, VSCSI_STAT_RESET, NULL, 0);
265 }
266 
267 void
268 vscsi_dataout(struct connection *c, struct scsi_task *t, u_int32_t ttt,
269     size_t len)
270 {
271 	struct pdu *p;
272 	struct iscsi_pdu_data_out *dout;
273 	u_char *buf = NULL;
274 	size_t size, off;
275 	u_int32_t t32, dsn = 0;
276 
277 	for (off = 0; off < len; off += size) {
278 		size = len - off > c->active.MaxRecvDataSegmentLength ?
279 		    c->active.MaxRecvDataSegmentLength : len - off;
280 
281 		if (!(p = pdu_new()))
282 			fatal("vscsi_r2t");
283 		if (!(dout = pdu_gethdr(p)))
284 			fatal("vscsi_r2t");
285 
286 		dout->opcode = ISCSI_OP_DATA_OUT;
287 		if (off + size == len)
288 			dout->flags = 0x80; /* XXX magic value: F flag*/
289 		/* LUN handling: currently we only do single level LUNs < 256 */
290 		dout->lun[1] = t->lun;
291 		dout->ttt = ttt;
292 		dout->datasn = htonl(dsn++);
293 		t32 = htonl(size);
294 		bcopy(&t32, &dout->ahslen, sizeof(t32));
295 
296 		dout->buffer_offs = htonl(off);
297 		if (!(buf = pdu_alloc(size)))
298 			fatal("vscsi_r2t");
299 
300 		vscsi_data(VSCSI_DATA_WRITE, t->tag, buf, size);
301 		pdu_addbuf(p, buf, size, PDU_DATA);
302 		task_pdu_add(&t->task, p);
303 	}
304 	conn_task_issue(c, &t->task);
305 }
306