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 "system/block-backend.h"
19 #include "hw/scsi/scsi.h"
20 #include "scsi/constants.h"
21 #include "hw/virtio/iothread-vq-mapping.h"
22 #include "hw/virtio/virtio-bus.h"
23
24 /* Context: BQL held */
virtio_scsi_dataplane_setup(VirtIOSCSI * s,Error ** errp)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 && vs->conf.iothread_vq_mapping_list) {
33 error_setg(errp,
34 "iothread and iothread-vq-mapping properties cannot be set "
35 "at the same time");
36 return;
37 }
38
39 if (vs->conf.iothread || vs->conf.iothread_vq_mapping_list) {
40 if (!k->set_guest_notifiers || !k->ioeventfd_assign) {
41 error_setg(errp,
42 "device is incompatible with iothread "
43 "(transport does not support notifiers)");
44 return;
45 }
46 if (!virtio_device_ioeventfd_enabled(vdev)) {
47 error_setg(errp, "ioeventfd is required for iothread");
48 return;
49 }
50 }
51
52 s->vq_aio_context = g_new(AioContext *, vs->conf.num_queues +
53 VIRTIO_SCSI_VQ_NUM_FIXED);
54
55 /*
56 * Handle the ctrl virtqueue in the main loop thread where device resets
57 * can be performed.
58 */
59 s->vq_aio_context[0] = qemu_get_aio_context();
60
61 /*
62 * Handle the event virtqueue in the main loop thread where its no_poll
63 * behavior won't stop IOThread polling.
64 */
65 s->vq_aio_context[1] = qemu_get_aio_context();
66
67 if (vs->conf.iothread_vq_mapping_list) {
68 if (!iothread_vq_mapping_apply(vs->conf.iothread_vq_mapping_list,
69 &s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED],
70 vs->conf.num_queues, errp)) {
71 g_free(s->vq_aio_context);
72 s->vq_aio_context = NULL;
73 return;
74 }
75 } else if (vs->conf.iothread) {
76 AioContext *ctx = iothread_get_aio_context(vs->conf.iothread);
77 for (uint16_t i = 0; i < vs->conf.num_queues; i++) {
78 s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i] = ctx;
79 }
80
81 /* Released in virtio_scsi_dataplane_cleanup() */
82 object_ref(OBJECT(vs->conf.iothread));
83 } else {
84 AioContext *ctx = qemu_get_aio_context();
85 for (unsigned i = 0; i < vs->conf.num_queues; i++) {
86 s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i] = ctx;
87 }
88 }
89 }
90
91 /* Context: BQL held */
virtio_scsi_dataplane_cleanup(VirtIOSCSI * s)92 void virtio_scsi_dataplane_cleanup(VirtIOSCSI *s)
93 {
94 VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
95
96 if (vs->conf.iothread_vq_mapping_list) {
97 iothread_vq_mapping_cleanup(vs->conf.iothread_vq_mapping_list);
98 }
99
100 if (vs->conf.iothread) {
101 object_unref(OBJECT(vs->conf.iothread));
102 }
103
104 g_free(s->vq_aio_context);
105 s->vq_aio_context = NULL;
106 }
107
virtio_scsi_set_host_notifier(VirtIOSCSI * s,VirtQueue * vq,int n)108 static int virtio_scsi_set_host_notifier(VirtIOSCSI *s, VirtQueue *vq, int n)
109 {
110 BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s)));
111 int rc;
112
113 /* Set up virtqueue notify */
114 rc = virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), n, true);
115 if (rc != 0) {
116 fprintf(stderr, "virtio-scsi: Failed to set host notifier (%d)\n",
117 rc);
118 s->dataplane_fenced = true;
119 return rc;
120 }
121
122 return 0;
123 }
124
125 /* Context: BH in IOThread */
virtio_scsi_dataplane_stop_vq_bh(void * opaque)126 static void virtio_scsi_dataplane_stop_vq_bh(void *opaque)
127 {
128 AioContext *ctx = qemu_get_current_aio_context();
129 VirtQueue *vq = opaque;
130 EventNotifier *host_notifier;
131
132 virtio_queue_aio_detach_host_notifier(vq, ctx);
133 host_notifier = virtio_queue_get_host_notifier(vq);
134
135 /*
136 * Test and clear notifier after disabling event, in case poll callback
137 * didn't have time to run.
138 */
139 virtio_queue_host_notifier_read(host_notifier);
140 }
141
142 /* Context: BQL held */
virtio_scsi_dataplane_start(VirtIODevice * vdev)143 int virtio_scsi_dataplane_start(VirtIODevice *vdev)
144 {
145 int i;
146 int rc;
147 int vq_init_count = 0;
148 BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
149 VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
150 VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
151 VirtIOSCSI *s = VIRTIO_SCSI(vdev);
152
153 if (s->dataplane_started ||
154 s->dataplane_starting ||
155 s->dataplane_fenced) {
156 return 0;
157 }
158
159 s->dataplane_starting = true;
160
161 /* Set up guest notifier (irq) */
162 rc = k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, true);
163 if (rc != 0) {
164 error_report("virtio-scsi: Failed to set guest notifiers (%d), "
165 "ensure -accel kvm is set.", rc);
166 goto fail_guest_notifiers;
167 }
168
169 /*
170 * Batch all the host notifiers in a single transaction to avoid
171 * quadratic time complexity in address_space_update_ioeventfds().
172 */
173 memory_region_transaction_begin();
174
175 rc = virtio_scsi_set_host_notifier(s, vs->ctrl_vq, 0);
176 if (rc != 0) {
177 goto fail_host_notifiers;
178 }
179
180 vq_init_count++;
181 rc = virtio_scsi_set_host_notifier(s, vs->event_vq, 1);
182 if (rc != 0) {
183 goto fail_host_notifiers;
184 }
185
186 vq_init_count++;
187
188 for (i = 0; i < vs->conf.num_queues; i++) {
189 rc = virtio_scsi_set_host_notifier(s, vs->cmd_vqs[i], i + 2);
190 if (rc) {
191 goto fail_host_notifiers;
192 }
193 vq_init_count++;
194 }
195
196 memory_region_transaction_commit();
197
198 s->dataplane_starting = false;
199 s->dataplane_started = true;
200 smp_wmb(); /* paired with aio_notify_accept() */
201
202 if (s->bus.drain_count == 0) {
203 virtio_queue_aio_attach_host_notifier(vs->ctrl_vq,
204 s->vq_aio_context[0]);
205 virtio_queue_aio_attach_host_notifier_no_poll(vs->event_vq,
206 s->vq_aio_context[1]);
207
208 for (i = 0; i < vs->conf.num_queues; i++) {
209 AioContext *ctx = s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i];
210 virtio_queue_aio_attach_host_notifier(vs->cmd_vqs[i], ctx);
211 }
212 }
213 return 0;
214
215 fail_host_notifiers:
216 for (i = 0; i < vq_init_count; i++) {
217 virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
218 }
219
220 /*
221 * The transaction expects the ioeventfds to be open when it
222 * commits. Do it now, before the cleanup loop.
223 */
224 memory_region_transaction_commit();
225
226 for (i = 0; i < vq_init_count; i++) {
227 virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i);
228 }
229 k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
230 fail_guest_notifiers:
231 s->dataplane_fenced = true;
232 s->dataplane_starting = false;
233 s->dataplane_started = true;
234 return -ENOSYS;
235 }
236
237 /* Context: BQL held */
virtio_scsi_dataplane_stop(VirtIODevice * vdev)238 void virtio_scsi_dataplane_stop(VirtIODevice *vdev)
239 {
240 BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
241 VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
242 VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
243 VirtIOSCSI *s = VIRTIO_SCSI(vdev);
244 int i;
245
246 if (!s->dataplane_started || s->dataplane_stopping) {
247 return;
248 }
249
250 /* Better luck next time. */
251 if (s->dataplane_fenced) {
252 s->dataplane_fenced = false;
253 s->dataplane_started = false;
254 return;
255 }
256 s->dataplane_stopping = true;
257
258 if (s->bus.drain_count == 0) {
259 for (i = 0; i < vs->conf.num_queues + VIRTIO_SCSI_VQ_NUM_FIXED; i++) {
260 VirtQueue *vq = virtio_get_queue(&vs->parent_obj, i);
261 AioContext *ctx = s->vq_aio_context[i];
262 aio_wait_bh_oneshot(ctx, virtio_scsi_dataplane_stop_vq_bh, vq);
263 }
264 }
265
266 blk_drain_all(); /* ensure there are no in-flight requests */
267
268 /*
269 * Batch all the host notifiers in a single transaction to avoid
270 * quadratic time complexity in address_space_update_ioeventfds().
271 */
272 memory_region_transaction_begin();
273
274 for (i = 0; i < vs->conf.num_queues + 2; i++) {
275 virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
276 }
277
278 /*
279 * The transaction expects the ioeventfds to be open when it
280 * commits. Do it now, before the cleanup loop.
281 */
282 memory_region_transaction_commit();
283
284 for (i = 0; i < vs->conf.num_queues + 2; i++) {
285 virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i);
286 }
287
288 /* Clean up guest notifier (irq) */
289 k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
290 s->dataplane_stopping = false;
291 s->dataplane_started = false;
292 }
293