xref: /qemu/hw/scsi/virtio-scsi-dataplane.c (revision 0462a32b4f63b2448b4a196381138afd50719dc4)
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