2 * vhost-user-scsi sample application
4 * Copyright (c) 2016 Nutanix Inc. All rights reserved.
7 * Felipe Franciosi <felipe@nutanix.com>
9 * This work is licensed under the terms of the GNU GPL, version 2 only.
10 * See the COPYING file in the top-level directory.
13 #include "qemu/osdep.h"
14 #include "contrib/libvhost-user/libvhost-user.h"
15 #include "standard-headers/linux/virtio_scsi.h"
16 #include "iscsi/iscsi.h"
17 #include "iscsi/scsi-lowlevel.h"
21 /* #define VUS_DEBUG 1 */
29 (void)clock_gettime(CLOCK_REALTIME, &ts); \
30 (void)strftime(timebuf, 64, "%Y%m%d %T", gmtime_r(&ts.tv_sec, &tm))
32 #define PEXT(lvl, msg, ...) do { \
34 fprintf(stderr, "%s.%06ld " lvl ": %s:%s():%d: " msg "\n", \
35 timebuf, ts.tv_nsec / 1000, \
36 __FILE__, __func__, __LINE__, ## __VA_ARGS__); \
39 #define PNOR(lvl, msg, ...) do { \
41 fprintf(stderr, "%s.%06ld " lvl ": " msg "\n", \
42 timebuf, ts.tv_nsec / 1000, ## __VA_ARGS__); \
46 #define PDBG(msg, ...) PEXT("DBG", msg, ## __VA_ARGS__)
47 #define PERR(msg, ...) PEXT("ERR", msg, ## __VA_ARGS__)
48 #define PLOG(msg, ...) PEXT("LOG", msg, ## __VA_ARGS__)
50 #define PDBG(msg, ...) { }
51 #define PERR(msg, ...) PNOR("ERR", msg, ## __VA_ARGS__)
52 #define PLOG(msg, ...) PNOR("LOG", msg, ## __VA_ARGS__)
55 /** vhost-user-scsi specific definitions **/
57 #define VUS_ISCSI_INITIATOR "iqn.2016-11.com.nutanix:vhost-user-scsi"
59 typedef struct VusIscsiLun
{
60 struct iscsi_context
*iscsi_ctx
;
64 typedef struct VusDev
{
68 GTree
*fdmap
; /* fd -> gsource context id */
72 /** glib event loop integration for libvhost-user and misc callbacks **/
74 QEMU_BUILD_BUG_ON((int)G_IO_IN
!= (int)VU_WATCH_IN
);
75 QEMU_BUILD_BUG_ON((int)G_IO_OUT
!= (int)VU_WATCH_OUT
);
76 QEMU_BUILD_BUG_ON((int)G_IO_PRI
!= (int)VU_WATCH_PRI
);
77 QEMU_BUILD_BUG_ON((int)G_IO_ERR
!= (int)VU_WATCH_ERR
);
78 QEMU_BUILD_BUG_ON((int)G_IO_HUP
!= (int)VU_WATCH_HUP
);
80 typedef struct vus_gsrc
{
87 static gint
vus_fdmap_compare(gconstpointer a
, gconstpointer b
)
89 return (b
> a
) - (b
< a
);
92 static gboolean
vus_gsrc_prepare(GSource
*src
, gint
*timeout
)
100 static gboolean
vus_gsrc_check(GSource
*src
)
102 vus_gsrc_t
*vus_src
= (vus_gsrc_t
*)src
;
106 return vus_src
->gfd
.revents
& vus_src
->gfd
.events
;
109 static gboolean
vus_gsrc_dispatch(GSource
*src
, GSourceFunc cb
, gpointer data
)
112 vus_gsrc_t
*vus_src
= (vus_gsrc_t
*)src
;
115 assert(!(vus_src
->vu_cb
&& cb
));
117 vdev_scsi
= vus_src
->vdev_scsi
;
124 if (vus_src
->vu_cb
) {
125 vus_src
->vu_cb(&vdev_scsi
->vu_dev
, vus_src
->gfd
.revents
, data
);
127 return G_SOURCE_CONTINUE
;
130 static GSourceFuncs vus_gsrc_funcs
= {
137 static void vus_gsrc_new(VusDev
*vdev_scsi
, int fd
, GIOCondition cond
,
138 vu_watch_cb vu_cb
, GSourceFunc gsrc_cb
, gpointer data
)
146 assert(vu_cb
|| gsrc_cb
);
147 assert(!(vu_cb
&& gsrc_cb
));
149 vus_gsrc
= g_source_new(&vus_gsrc_funcs
, sizeof(vus_gsrc_t
));
150 vus_src
= (vus_gsrc_t
*)vus_gsrc
;
152 vus_src
->vdev_scsi
= vdev_scsi
;
153 vus_src
->gfd
.fd
= fd
;
154 vus_src
->gfd
.events
= cond
;
155 vus_src
->vu_cb
= vu_cb
;
157 g_source_add_poll(vus_gsrc
, &vus_src
->gfd
);
158 g_source_set_callback(vus_gsrc
, gsrc_cb
, data
, NULL
);
159 id
= g_source_attach(vus_gsrc
, NULL
);
161 g_source_unref(vus_gsrc
);
163 g_tree_insert(vdev_scsi
->fdmap
, (gpointer
)(uintptr_t)fd
,
164 (gpointer
)(uintptr_t)id
);
167 /** libiscsi integration **/
169 typedef struct virtio_scsi_cmd_req VirtIOSCSICmdReq
;
170 typedef struct virtio_scsi_cmd_resp VirtIOSCSICmdResp
;
172 static int vus_iscsi_add_lun(VusIscsiLun
*lun
, char *iscsi_uri
)
174 struct iscsi_url
*iscsi_url
;
175 struct iscsi_context
*iscsi_ctx
;
180 assert(!lun
->iscsi_ctx
);
182 iscsi_ctx
= iscsi_create_context(VUS_ISCSI_INITIATOR
);
184 PERR("Unable to create iSCSI context");
188 iscsi_url
= iscsi_parse_full_url(iscsi_ctx
, iscsi_uri
);
190 PERR("Unable to parse iSCSI URL: %s", iscsi_get_error(iscsi_ctx
));
194 iscsi_set_session_type(iscsi_ctx
, ISCSI_SESSION_NORMAL
);
195 iscsi_set_header_digest(iscsi_ctx
, ISCSI_HEADER_DIGEST_NONE_CRC32C
);
196 if (iscsi_full_connect_sync(iscsi_ctx
, iscsi_url
->portal
, iscsi_url
->lun
)) {
197 PERR("Unable to login to iSCSI portal: %s", iscsi_get_error(iscsi_ctx
));
201 lun
->iscsi_ctx
= iscsi_ctx
;
202 lun
->iscsi_lun
= iscsi_url
->lun
;
204 PDBG("Context %p created for lun 0: %s", iscsi_ctx
, iscsi_uri
);
208 iscsi_destroy_url(iscsi_url
);
213 (void)iscsi_destroy_context(iscsi_ctx
);
218 static struct scsi_task
*scsi_task_new(int cdb_len
, uint8_t *cdb
, int dir
,
221 struct scsi_task
*task
;
226 task
= g_new0(struct scsi_task
, 1);
227 memcpy(task
->cdb
, cdb
, cdb_len
);
228 task
->cdb_size
= cdb_len
;
229 task
->xfer_dir
= dir
;
230 task
->expxferlen
= xfer_len
;
235 static int get_cdb_len(uint8_t *cdb
)
239 switch (cdb
[0] >> 5) {
241 case 1: /* fall through */
246 PERR("Unable to determine cdb len (0x%02hhX)", cdb
[0] >> 5);
250 static int handle_cmd_sync(struct iscsi_context
*ctx
,
251 VirtIOSCSICmdReq
*req
,
252 struct iovec
*out
, unsigned int out_len
,
253 VirtIOSCSICmdResp
*rsp
,
254 struct iovec
*in
, unsigned int in_len
)
256 struct scsi_task
*task
;
266 if (!(!req
->lun
[1] && req
->lun
[2] == 0x40 && !req
->lun
[3])) {
267 /* Ignore anything different than target=0, lun=0 */
268 PDBG("Ignoring unconnected lun (0x%hhX, 0x%hhX)",
269 req
->lun
[1], req
->lun
[3]);
270 rsp
->status
= SCSI_STATUS_CHECK_CONDITION
;
271 memset(rsp
->sense
, 0, sizeof(rsp
->sense
));
273 rsp
->sense
[0] = 0x70;
274 rsp
->sense
[2] = SCSI_SENSE_ILLEGAL_REQUEST
;
276 rsp
->sense
[12] = 0x24;
281 cdb_len
= get_cdb_len(req
->cdb
);
287 if (!out_len
&& !in_len
) {
288 dir
= SCSI_XFER_NONE
;
289 } else if (out_len
) {
290 dir
= SCSI_XFER_WRITE
;
291 for (i
= 0; i
< out_len
; i
++) {
292 len
+= out
[i
].iov_len
;
295 dir
= SCSI_XFER_READ
;
296 for (i
= 0; i
< in_len
; i
++) {
297 len
+= in
[i
].iov_len
;
301 task
= scsi_task_new(cdb_len
, req
->cdb
, dir
, len
);
303 if (dir
== SCSI_XFER_WRITE
) {
304 task
->iovector_out
.iov
= (struct scsi_iovec
*)out
;
305 task
->iovector_out
.niov
= out_len
;
306 } else if (dir
== SCSI_XFER_READ
) {
307 task
->iovector_in
.iov
= (struct scsi_iovec
*)in
;
308 task
->iovector_in
.niov
= in_len
;
311 PDBG("Sending iscsi cmd (cdb_len=%d, dir=%d, task=%p)",
313 if (!iscsi_scsi_command_sync(ctx
, 0, task
, NULL
)) {
314 PERR("Error serving SCSI command");
319 memset(rsp
, 0, sizeof(*rsp
));
321 rsp
->status
= task
->status
;
322 rsp
->resid
= task
->residual
;
324 if (task
->status
== SCSI_STATUS_CHECK_CONDITION
) {
325 rsp
->response
= VIRTIO_SCSI_S_FAILURE
;
326 rsp
->sense_len
= task
->datain
.size
- 2;
327 memcpy(rsp
->sense
, &task
->datain
.data
[2], rsp
->sense_len
);
332 PDBG("Filled in rsp: status=%hhX, resid=%u, response=%hhX, sense_len=%u",
333 rsp
->status
, rsp
->resid
, rsp
->response
, rsp
->sense_len
);
338 /** libvhost-user callbacks **/
340 static void vus_panic_cb(VuDev
*vu_dev
, const char *buf
)
346 vdev_scsi
= container_of(vu_dev
, VusDev
, vu_dev
);
348 PERR("vu_panic: %s", buf
);
351 g_main_loop_quit(vdev_scsi
->loop
);
354 static void vus_add_watch_cb(VuDev
*vu_dev
, int fd
, int vu_evt
, vu_watch_cb cb
,
364 vdev_scsi
= container_of(vu_dev
, VusDev
, vu_dev
);
365 id
= (guint
)(uintptr_t)g_tree_lookup(vdev_scsi
->fdmap
,
366 (gpointer
)(uintptr_t)fd
);
368 GSource
*vus_src
= g_main_context_find_source_by_id(NULL
, id
);
370 g_source_destroy(vus_src
);
371 (void)g_tree_remove(vdev_scsi
->fdmap
, (gpointer
)(uintptr_t)fd
);
374 vus_gsrc_new(vdev_scsi
, fd
, vu_evt
, cb
, NULL
, pvt
);
377 static void vus_del_watch_cb(VuDev
*vu_dev
, int fd
)
385 vdev_scsi
= container_of(vu_dev
, VusDev
, vu_dev
);
386 id
= (guint
)(uintptr_t)g_tree_lookup(vdev_scsi
->fdmap
,
387 (gpointer
)(uintptr_t)fd
);
389 GSource
*vus_src
= g_main_context_find_source_by_id(NULL
, id
);
391 g_source_destroy(vus_src
);
392 (void)g_tree_remove(vdev_scsi
->fdmap
, (gpointer
)(uintptr_t)fd
);
396 static void vus_proc_req(VuDev
*vu_dev
, int idx
)
403 vdev_scsi
= container_of(vu_dev
, VusDev
, vu_dev
);
404 if (idx
< 0 || idx
>= VHOST_MAX_NR_VIRTQUEUE
) {
405 PERR("VQ Index out of range: %d", idx
);
406 vus_panic_cb(vu_dev
, NULL
);
410 vq
= vu_get_queue(vu_dev
, idx
);
412 PERR("Error fetching VQ (dev=%p, idx=%d)", vu_dev
, idx
);
413 vus_panic_cb(vu_dev
, NULL
);
417 PDBG("Got kicked on vq[%d]@%p", idx
, vq
);
420 VuVirtqElement
*elem
;
421 VirtIOSCSICmdReq
*req
;
422 VirtIOSCSICmdResp
*rsp
;
424 elem
= vu_queue_pop(vu_dev
, vq
, sizeof(VuVirtqElement
));
426 PDBG("No more elements pending on vq[%d]@%p", idx
, vq
);
429 PDBG("Popped elem@%p", elem
);
431 assert(!(elem
->out_num
> 1 && elem
->in_num
> 1));
432 assert(elem
->out_num
> 0 && elem
->in_num
> 0);
434 if (elem
->out_sg
[0].iov_len
< sizeof(VirtIOSCSICmdReq
)) {
435 PERR("Invalid virtio-scsi req header");
436 vus_panic_cb(vu_dev
, NULL
);
439 req
= (VirtIOSCSICmdReq
*)elem
->out_sg
[0].iov_base
;
441 if (elem
->in_sg
[0].iov_len
< sizeof(VirtIOSCSICmdResp
)) {
442 PERR("Invalid virtio-scsi rsp header");
443 vus_panic_cb(vu_dev
, NULL
);
446 rsp
= (VirtIOSCSICmdResp
*)elem
->in_sg
[0].iov_base
;
448 if (handle_cmd_sync(vdev_scsi
->lun
.iscsi_ctx
,
449 req
, &elem
->out_sg
[1], elem
->out_num
- 1,
450 rsp
, &elem
->in_sg
[1], elem
->in_num
- 1) != 0) {
451 vus_panic_cb(vu_dev
, NULL
);
455 vu_queue_push(vu_dev
, vq
, elem
, 0);
456 vu_queue_notify(vu_dev
, vq
);
462 static void vus_queue_set_started(VuDev
*vu_dev
, int idx
, bool started
)
468 if (idx
< 0 || idx
>= VHOST_MAX_NR_VIRTQUEUE
) {
469 PERR("VQ Index out of range: %d", idx
);
470 vus_panic_cb(vu_dev
, NULL
);
474 vq
= vu_get_queue(vu_dev
, idx
);
476 if (idx
== 0 || idx
== 1) {
477 PDBG("queue %d unimplemented", idx
);
479 vu_set_queue_handler(vu_dev
, vq
, started
? vus_proc_req
: NULL
);
483 static const VuDevIface vus_iface
= {
484 .queue_set_started
= vus_queue_set_started
,
487 static gboolean
vus_vhost_cb(gpointer data
)
489 VuDev
*vu_dev
= (VuDev
*)data
;
493 if (!vu_dispatch(vu_dev
) != 0) {
494 PERR("Error processing vhost message");
495 vus_panic_cb(vu_dev
, NULL
);
496 return G_SOURCE_REMOVE
;
499 return G_SOURCE_CONTINUE
;
504 static int unix_sock_new(char *unix_fn
)
507 struct sockaddr_un un
;
512 sock
= socket(AF_UNIX
, SOCK_STREAM
, 0);
518 un
.sun_family
= AF_UNIX
;
519 (void)snprintf(un
.sun_path
, sizeof(un
.sun_path
), "%s", unix_fn
);
520 len
= sizeof(un
.sun_family
) + strlen(un
.sun_path
);
522 (void)unlink(unix_fn
);
523 if (bind(sock
, (struct sockaddr
*)&un
, len
) < 0) {
528 if (listen(sock
, 1) < 0) {
541 /** vhost-user-scsi **/
543 static void vdev_scsi_free(VusDev
*vdev_scsi
)
545 if (vdev_scsi
->server_sock
>= 0) {
546 close(vdev_scsi
->server_sock
);
548 g_main_loop_unref(vdev_scsi
->loop
);
549 g_tree_destroy(vdev_scsi
->fdmap
);
553 static VusDev
*vdev_scsi_new(int server_sock
)
557 vdev_scsi
= g_new0(VusDev
, 1);
558 vdev_scsi
->server_sock
= server_sock
;
559 vdev_scsi
->loop
= g_main_loop_new(NULL
, FALSE
);
560 vdev_scsi
->fdmap
= g_tree_new(vus_fdmap_compare
);
565 static int vdev_scsi_run(VusDev
*vdev_scsi
)
571 assert(vdev_scsi
->server_sock
>= 0);
572 assert(vdev_scsi
->loop
);
574 cli_sock
= accept(vdev_scsi
->server_sock
, NULL
, NULL
);
580 vu_init(&vdev_scsi
->vu_dev
,
587 vus_gsrc_new(vdev_scsi
, cli_sock
, G_IO_IN
, NULL
, vus_vhost_cb
,
590 g_main_loop_run(vdev_scsi
->loop
);
592 vu_deinit(&vdev_scsi
->vu_dev
);
597 int main(int argc
, char **argv
)
599 VusDev
*vdev_scsi
= NULL
;
600 char *unix_fn
= NULL
;
601 char *iscsi_uri
= NULL
;
602 int sock
, opt
, err
= EXIT_SUCCESS
;
604 while ((opt
= getopt(argc
, argv
, "u:i:")) != -1) {
609 unix_fn
= g_strdup(optarg
);
612 iscsi_uri
= g_strdup(optarg
);
618 if (!unix_fn
|| !iscsi_uri
) {
622 sock
= unix_sock_new(unix_fn
);
626 vdev_scsi
= vdev_scsi_new(sock
);
628 if (vus_iscsi_add_lun(&vdev_scsi
->lun
, iscsi_uri
) != 0) {
632 if (vdev_scsi_run(vdev_scsi
) != 0) {
638 vdev_scsi_free(vdev_scsi
);
651 fprintf(stderr
, "Usage: %s [ -u unix_sock_path -i iscsi_uri ] | [ -h ]\n",
653 fprintf(stderr
, " -u path to unix socket\n");
654 fprintf(stderr
, " -i iscsi uri for lun 0\n");
655 fprintf(stderr
, " -h print help and quit\n");