xref: /qemu/hw/scsi/virtio-scsi-dataplane.c (revision d93d16c0450b3d0fe8e25568663531eb250d6aae)
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 void virtio_scsi_data_plane_handle_cmd(VirtIODevice *vdev,
53                                               VirtQueue *vq)
54 {
55     VirtIOSCSI *s = VIRTIO_SCSI(vdev);
56 
57     virtio_scsi_acquire(s);
58     if (!s->dataplane_fenced) {
59         assert(s->ctx && s->dataplane_started);
60         virtio_scsi_handle_cmd_vq(s, vq);
61     }
62     virtio_scsi_release(s);
63 }
64 
65 static void virtio_scsi_data_plane_handle_ctrl(VirtIODevice *vdev,
66                                                VirtQueue *vq)
67 {
68     VirtIOSCSI *s = VIRTIO_SCSI(vdev);
69 
70     virtio_scsi_acquire(s);
71     if (!s->dataplane_fenced) {
72         assert(s->ctx && s->dataplane_started);
73         virtio_scsi_handle_ctrl_vq(s, vq);
74     }
75     virtio_scsi_release(s);
76 }
77 
78 static void virtio_scsi_data_plane_handle_event(VirtIODevice *vdev,
79                                                 VirtQueue *vq)
80 {
81     VirtIOSCSI *s = VIRTIO_SCSI(vdev);
82 
83     virtio_scsi_acquire(s);
84     if (!s->dataplane_fenced) {
85         assert(s->ctx && s->dataplane_started);
86         virtio_scsi_handle_event_vq(s, vq);
87     }
88     virtio_scsi_release(s);
89 }
90 
91 static int virtio_scsi_set_host_notifier(VirtIOSCSI *s, VirtQueue *vq, int n)
92 {
93     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s)));
94     int rc;
95 
96     /* Set up virtqueue notify */
97     rc = virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), n, true);
98     if (rc != 0) {
99         fprintf(stderr, "virtio-scsi: Failed to set host notifier (%d)\n",
100                 rc);
101         s->dataplane_fenced = true;
102         return rc;
103     }
104 
105     return 0;
106 }
107 
108 /* Context: BH in IOThread */
109 static void virtio_scsi_dataplane_stop_bh(void *opaque)
110 {
111     VirtIOSCSI *s = opaque;
112     VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
113     int i;
114 
115     virtio_queue_aio_set_host_notifier_handler(vs->ctrl_vq, s->ctx, NULL);
116     virtio_queue_aio_set_host_notifier_handler(vs->event_vq, s->ctx, NULL);
117     for (i = 0; i < vs->conf.num_queues; i++) {
118         virtio_queue_aio_set_host_notifier_handler(vs->cmd_vqs[i], s->ctx, NULL);
119     }
120 }
121 
122 /* Context: QEMU global mutex held */
123 int virtio_scsi_dataplane_start(VirtIODevice *vdev)
124 {
125     int i;
126     int rc;
127     int vq_init_count = 0;
128     BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
129     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
130     VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
131     VirtIOSCSI *s = VIRTIO_SCSI(vdev);
132 
133     if (s->dataplane_started ||
134         s->dataplane_starting ||
135         s->dataplane_fenced) {
136         return 0;
137     }
138 
139     s->dataplane_starting = true;
140 
141     /* Set up guest notifier (irq) */
142     rc = k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, true);
143     if (rc != 0) {
144         error_report("virtio-scsi: Failed to set guest notifiers (%d), "
145                      "ensure -accel kvm is set.", rc);
146         goto fail_guest_notifiers;
147     }
148 
149     /*
150      * Batch all the host notifiers in a single transaction to avoid
151      * quadratic time complexity in address_space_update_ioeventfds().
152      */
153     memory_region_transaction_begin();
154 
155     rc = virtio_scsi_set_host_notifier(s, vs->ctrl_vq, 0);
156     if (rc != 0) {
157         goto fail_host_notifiers;
158     }
159 
160     vq_init_count++;
161     rc = virtio_scsi_set_host_notifier(s, vs->event_vq, 1);
162     if (rc != 0) {
163         goto fail_host_notifiers;
164     }
165 
166     vq_init_count++;
167 
168     for (i = 0; i < vs->conf.num_queues; i++) {
169         rc = virtio_scsi_set_host_notifier(s, vs->cmd_vqs[i], i + 2);
170         if (rc) {
171             goto fail_host_notifiers;
172         }
173         vq_init_count++;
174     }
175 
176     memory_region_transaction_commit();
177 
178     aio_context_acquire(s->ctx);
179     virtio_queue_aio_set_host_notifier_handler(vs->ctrl_vq, s->ctx,
180                                             virtio_scsi_data_plane_handle_ctrl);
181     virtio_queue_aio_set_host_notifier_handler(vs->event_vq, s->ctx,
182                                            virtio_scsi_data_plane_handle_event);
183 
184     for (i = 0; i < vs->conf.num_queues; i++) {
185         virtio_queue_aio_set_host_notifier_handler(vs->cmd_vqs[i], s->ctx,
186                                              virtio_scsi_data_plane_handle_cmd);
187     }
188 
189     s->dataplane_starting = false;
190     s->dataplane_started = true;
191     aio_context_release(s->ctx);
192     return 0;
193 
194 fail_host_notifiers:
195     for (i = 0; i < vq_init_count; i++) {
196         virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
197     }
198 
199     /*
200      * The transaction expects the ioeventfds to be open when it
201      * commits. Do it now, before the cleanup loop.
202      */
203     memory_region_transaction_commit();
204 
205     for (i = 0; i < vq_init_count; i++) {
206         virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i);
207     }
208     k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
209 fail_guest_notifiers:
210     s->dataplane_fenced = true;
211     s->dataplane_starting = false;
212     s->dataplane_started = true;
213     return -ENOSYS;
214 }
215 
216 /* Context: QEMU global mutex held */
217 void virtio_scsi_dataplane_stop(VirtIODevice *vdev)
218 {
219     BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
220     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
221     VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
222     VirtIOSCSI *s = VIRTIO_SCSI(vdev);
223     int i;
224 
225     if (!s->dataplane_started || s->dataplane_stopping) {
226         return;
227     }
228 
229     /* Better luck next time. */
230     if (s->dataplane_fenced) {
231         s->dataplane_fenced = false;
232         s->dataplane_started = false;
233         return;
234     }
235     s->dataplane_stopping = true;
236 
237     aio_context_acquire(s->ctx);
238     aio_wait_bh_oneshot(s->ctx, virtio_scsi_dataplane_stop_bh, s);
239     aio_context_release(s->ctx);
240 
241     blk_drain_all(); /* ensure there are no in-flight requests */
242 
243     /*
244      * Batch all the host notifiers in a single transaction to avoid
245      * quadratic time complexity in address_space_update_ioeventfds().
246      */
247     memory_region_transaction_begin();
248 
249     for (i = 0; i < vs->conf.num_queues + 2; i++) {
250         virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
251     }
252 
253     /*
254      * The transaction expects the ioeventfds to be open when it
255      * commits. Do it now, before the cleanup loop.
256      */
257     memory_region_transaction_commit();
258 
259     for (i = 0; i < vs->conf.num_queues + 2; i++) {
260         virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i);
261     }
262 
263     /* Clean up guest notifier (irq) */
264     k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
265     s->dataplane_stopping = false;
266     s->dataplane_started = false;
267 }
268