1 /*
2  * Virtio SCSI dataplane
3  *
4  * Copyright Red Hat, Inc. 2014
5  *
6  * Authors:
7  *   Fam Zheng <famz@redhat.com>
8  *
9  * This work is licensed under the terms of the GNU GPL, version 2 or later.
10  * See the COPYING file in the top-level directory.
11  *
12  */
13 
14 #include "qemu/osdep.h"
15 #include "qapi/error.h"
16 #include "hw/virtio/virtio-scsi.h"
17 #include "qemu/error-report.h"
18 #include "sysemu/block-backend.h"
19 #include "hw/scsi/scsi.h"
20 #include "scsi/constants.h"
21 #include "hw/virtio/virtio-bus.h"
22 #include "hw/virtio/virtio-access.h"
23 
24 /* Context: QEMU global mutex held */
25 void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
26 {
27     VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
28     VirtIODevice *vdev = VIRTIO_DEVICE(s);
29     BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
30     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
31 
32     if (vs->conf.iothread) {
33         if (!k->set_guest_notifiers || !k->ioeventfd_assign) {
34             error_setg(errp,
35                        "device is incompatible with iothread "
36                        "(transport does not support notifiers)");
37             return;
38         }
39         if (!virtio_device_ioeventfd_enabled(vdev)) {
40             error_setg(errp, "ioeventfd is required for iothread");
41             return;
42         }
43         s->ctx = iothread_get_aio_context(vs->conf.iothread);
44     } else {
45         if (!virtio_device_ioeventfd_enabled(vdev)) {
46             return;
47         }
48         s->ctx = qemu_get_aio_context();
49     }
50 }
51 
52 static int virtio_scsi_set_host_notifier(VirtIOSCSI *s, VirtQueue *vq, int n)
53 {
54     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s)));
55     int rc;
56 
57     /* Set up virtqueue notify */
58     rc = virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), n, true);
59     if (rc != 0) {
60         fprintf(stderr, "virtio-scsi: Failed to set host notifier (%d)\n",
61                 rc);
62         s->dataplane_fenced = true;
63         return rc;
64     }
65 
66     return 0;
67 }
68 
69 /* Context: BH in IOThread */
70 static void virtio_scsi_dataplane_stop_bh(void *opaque)
71 {
72     VirtIOSCSI *s = opaque;
73     VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
74     int i;
75 
76     virtio_queue_aio_detach_host_notifier(vs->ctrl_vq, s->ctx);
77     virtio_queue_aio_detach_host_notifier(vs->event_vq, s->ctx);
78     for (i = 0; i < vs->conf.num_queues; i++) {
79         virtio_queue_aio_detach_host_notifier(vs->cmd_vqs[i], s->ctx);
80     }
81 }
82 
83 /* Context: QEMU global mutex held */
84 int virtio_scsi_dataplane_start(VirtIODevice *vdev)
85 {
86     int i;
87     int rc;
88     int vq_init_count = 0;
89     BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
90     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
91     VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
92     VirtIOSCSI *s = VIRTIO_SCSI(vdev);
93 
94     if (s->dataplane_started ||
95         s->dataplane_starting ||
96         s->dataplane_fenced) {
97         return 0;
98     }
99 
100     s->dataplane_starting = true;
101 
102     /* Set up guest notifier (irq) */
103     rc = k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, true);
104     if (rc != 0) {
105         error_report("virtio-scsi: Failed to set guest notifiers (%d), "
106                      "ensure -accel kvm is set.", rc);
107         goto fail_guest_notifiers;
108     }
109 
110     /*
111      * Batch all the host notifiers in a single transaction to avoid
112      * quadratic time complexity in address_space_update_ioeventfds().
113      */
114     memory_region_transaction_begin();
115 
116     rc = virtio_scsi_set_host_notifier(s, vs->ctrl_vq, 0);
117     if (rc != 0) {
118         goto fail_host_notifiers;
119     }
120 
121     vq_init_count++;
122     rc = virtio_scsi_set_host_notifier(s, vs->event_vq, 1);
123     if (rc != 0) {
124         goto fail_host_notifiers;
125     }
126 
127     vq_init_count++;
128 
129     for (i = 0; i < vs->conf.num_queues; i++) {
130         rc = virtio_scsi_set_host_notifier(s, vs->cmd_vqs[i], i + 2);
131         if (rc) {
132             goto fail_host_notifiers;
133         }
134         vq_init_count++;
135     }
136 
137     memory_region_transaction_commit();
138 
139     aio_context_acquire(s->ctx);
140     virtio_queue_aio_attach_host_notifier(vs->ctrl_vq, s->ctx);
141     virtio_queue_aio_attach_host_notifier(vs->event_vq, s->ctx);
142 
143     for (i = 0; i < vs->conf.num_queues; i++) {
144         virtio_queue_aio_attach_host_notifier(vs->cmd_vqs[i], s->ctx);
145     }
146 
147     s->dataplane_starting = false;
148     s->dataplane_started = true;
149     aio_context_release(s->ctx);
150     return 0;
151 
152 fail_host_notifiers:
153     for (i = 0; i < vq_init_count; i++) {
154         virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
155     }
156 
157     /*
158      * The transaction expects the ioeventfds to be open when it
159      * commits. Do it now, before the cleanup loop.
160      */
161     memory_region_transaction_commit();
162 
163     for (i = 0; i < vq_init_count; i++) {
164         virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i);
165     }
166     k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
167 fail_guest_notifiers:
168     s->dataplane_fenced = true;
169     s->dataplane_starting = false;
170     s->dataplane_started = true;
171     return -ENOSYS;
172 }
173 
174 /* Context: QEMU global mutex held */
175 void virtio_scsi_dataplane_stop(VirtIODevice *vdev)
176 {
177     BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
178     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
179     VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
180     VirtIOSCSI *s = VIRTIO_SCSI(vdev);
181     int i;
182 
183     if (!s->dataplane_started || s->dataplane_stopping) {
184         return;
185     }
186 
187     /* Better luck next time. */
188     if (s->dataplane_fenced) {
189         s->dataplane_fenced = false;
190         s->dataplane_started = false;
191         return;
192     }
193     s->dataplane_stopping = true;
194 
195     aio_context_acquire(s->ctx);
196     aio_wait_bh_oneshot(s->ctx, virtio_scsi_dataplane_stop_bh, s);
197     aio_context_release(s->ctx);
198 
199     blk_drain_all(); /* ensure there are no in-flight requests */
200 
201     /*
202      * Batch all the host notifiers in a single transaction to avoid
203      * quadratic time complexity in address_space_update_ioeventfds().
204      */
205     memory_region_transaction_begin();
206 
207     for (i = 0; i < vs->conf.num_queues + 2; i++) {
208         virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
209     }
210 
211     /*
212      * The transaction expects the ioeventfds to be open when it
213      * commits. Do it now, before the cleanup loop.
214      */
215     memory_region_transaction_commit();
216 
217     for (i = 0; i < vs->conf.num_queues + 2; i++) {
218         virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i);
219     }
220 
221     /* Clean up guest notifier (irq) */
222     k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
223     s->dataplane_stopping = false;
224     s->dataplane_started = false;
225 }
226