xref: /qemu/hw/scsi/virtio-scsi-dataplane.c (revision de6cd759)
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     EventNotifier *host_notifier;
75     int i;
76 
77     virtio_queue_aio_detach_host_notifier(vs->ctrl_vq, s->ctx);
78     host_notifier = virtio_queue_get_host_notifier(vs->ctrl_vq);
79 
80     /*
81      * Test and clear notifier after disabling event, in case poll callback
82      * didn't have time to run.
83      */
84     virtio_queue_host_notifier_read(host_notifier);
85 
86     virtio_queue_aio_detach_host_notifier(vs->event_vq, s->ctx);
87     host_notifier = virtio_queue_get_host_notifier(vs->event_vq);
88     virtio_queue_host_notifier_read(host_notifier);
89 
90     for (i = 0; i < vs->conf.num_queues; i++) {
91         virtio_queue_aio_detach_host_notifier(vs->cmd_vqs[i], s->ctx);
92         host_notifier = virtio_queue_get_host_notifier(vs->cmd_vqs[i]);
93         virtio_queue_host_notifier_read(host_notifier);
94     }
95 }
96 
97 /* Context: QEMU global mutex held */
98 int virtio_scsi_dataplane_start(VirtIODevice *vdev)
99 {
100     int i;
101     int rc;
102     int vq_init_count = 0;
103     BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
104     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
105     VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
106     VirtIOSCSI *s = VIRTIO_SCSI(vdev);
107 
108     if (s->dataplane_started ||
109         s->dataplane_starting ||
110         s->dataplane_fenced) {
111         return 0;
112     }
113 
114     s->dataplane_starting = true;
115 
116     /* Set up guest notifier (irq) */
117     rc = k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, true);
118     if (rc != 0) {
119         error_report("virtio-scsi: Failed to set guest notifiers (%d), "
120                      "ensure -accel kvm is set.", rc);
121         goto fail_guest_notifiers;
122     }
123 
124     /*
125      * Batch all the host notifiers in a single transaction to avoid
126      * quadratic time complexity in address_space_update_ioeventfds().
127      */
128     memory_region_transaction_begin();
129 
130     rc = virtio_scsi_set_host_notifier(s, vs->ctrl_vq, 0);
131     if (rc != 0) {
132         goto fail_host_notifiers;
133     }
134 
135     vq_init_count++;
136     rc = virtio_scsi_set_host_notifier(s, vs->event_vq, 1);
137     if (rc != 0) {
138         goto fail_host_notifiers;
139     }
140 
141     vq_init_count++;
142 
143     for (i = 0; i < vs->conf.num_queues; i++) {
144         rc = virtio_scsi_set_host_notifier(s, vs->cmd_vqs[i], i + 2);
145         if (rc) {
146             goto fail_host_notifiers;
147         }
148         vq_init_count++;
149     }
150 
151     memory_region_transaction_commit();
152 
153     /*
154      * These fields are visible to the IOThread so we rely on implicit barriers
155      * in aio_context_acquire() on the write side and aio_notify_accept() on
156      * the read side.
157      */
158     s->dataplane_starting = false;
159     s->dataplane_started = true;
160 
161     if (s->bus.drain_count == 0) {
162         aio_context_acquire(s->ctx);
163         virtio_queue_aio_attach_host_notifier(vs->ctrl_vq, s->ctx);
164         virtio_queue_aio_attach_host_notifier_no_poll(vs->event_vq, s->ctx);
165 
166         for (i = 0; i < vs->conf.num_queues; i++) {
167             virtio_queue_aio_attach_host_notifier(vs->cmd_vqs[i], s->ctx);
168         }
169         aio_context_release(s->ctx);
170     }
171     return 0;
172 
173 fail_host_notifiers:
174     for (i = 0; i < vq_init_count; i++) {
175         virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
176     }
177 
178     /*
179      * The transaction expects the ioeventfds to be open when it
180      * commits. Do it now, before the cleanup loop.
181      */
182     memory_region_transaction_commit();
183 
184     for (i = 0; i < vq_init_count; i++) {
185         virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i);
186     }
187     k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
188 fail_guest_notifiers:
189     s->dataplane_fenced = true;
190     s->dataplane_starting = false;
191     s->dataplane_started = true;
192     return -ENOSYS;
193 }
194 
195 /* Context: QEMU global mutex held */
196 void virtio_scsi_dataplane_stop(VirtIODevice *vdev)
197 {
198     BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
199     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
200     VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
201     VirtIOSCSI *s = VIRTIO_SCSI(vdev);
202     int i;
203 
204     if (!s->dataplane_started || s->dataplane_stopping) {
205         return;
206     }
207 
208     /* Better luck next time. */
209     if (s->dataplane_fenced) {
210         s->dataplane_fenced = false;
211         s->dataplane_started = false;
212         return;
213     }
214     s->dataplane_stopping = true;
215 
216     if (s->bus.drain_count == 0) {
217         aio_wait_bh_oneshot(s->ctx, virtio_scsi_dataplane_stop_bh, s);
218     }
219 
220     blk_drain_all(); /* ensure there are no in-flight requests */
221 
222     /*
223      * Batch all the host notifiers in a single transaction to avoid
224      * quadratic time complexity in address_space_update_ioeventfds().
225      */
226     memory_region_transaction_begin();
227 
228     for (i = 0; i < vs->conf.num_queues + 2; i++) {
229         virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
230     }
231 
232     /*
233      * The transaction expects the ioeventfds to be open when it
234      * commits. Do it now, before the cleanup loop.
235      */
236     memory_region_transaction_commit();
237 
238     for (i = 0; i < vs->conf.num_queues + 2; i++) {
239         virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i);
240     }
241 
242     /* Clean up guest notifier (irq) */
243     k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
244     s->dataplane_stopping = false;
245     s->dataplane_started = false;
246 }
247