]>
Commit | Line | Data |
---|---|---|
e72f66a0 SH |
1 | /* |
2 | * Dedicated thread for virtio-blk I/O processing | |
3 | * | |
4 | * Copyright 2012 IBM, Corp. | |
5 | * Copyright 2012 Red Hat, Inc. and/or its affiliates | |
6 | * | |
7 | * Authors: | |
8 | * Stefan Hajnoczi <[email protected]> | |
9 | * | |
10 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
11 | * See the COPYING file in the top-level directory. | |
12 | * | |
13 | */ | |
14 | ||
15 | #include "trace.h" | |
16 | #include "qemu/iov.h" | |
e72f66a0 | 17 | #include "qemu/thread.h" |
b4a42f81 | 18 | #include "qemu/error-report.h" |
b0e5d90e | 19 | #include "hw/virtio/virtio-access.h" |
0d09e41a | 20 | #include "hw/virtio/dataplane/vring.h" |
b0e5d90e | 21 | #include "hw/virtio/dataplane/vring-accessors.h" |
4be74634 | 22 | #include "sysemu/block-backend.h" |
0d09e41a PB |
23 | #include "hw/virtio/virtio-blk.h" |
24 | #include "virtio-blk.h" | |
2c20e711 | 25 | #include "block/aio.h" |
1c819449 | 26 | #include "hw/virtio/virtio-bus.h" |
54bee5c2 | 27 | #include "qom/object_interfaces.h" |
e72f66a0 | 28 | |
e72f66a0 SH |
29 | struct VirtIOBlockDataPlane { |
30 | bool started; | |
8caf907f | 31 | bool starting; |
cd7fdfe5 | 32 | bool stopping; |
2f5f70fa | 33 | bool disabled; |
e72f66a0 | 34 | |
2a30307f | 35 | VirtIOBlkConf *conf; |
e72f66a0 SH |
36 | |
37 | VirtIODevice *vdev; | |
38 | Vring vring; /* virtqueue vring */ | |
39 | EventNotifier *guest_notifier; /* irq */ | |
5b2ffbe4 | 40 | QEMUBH *bh; /* bh for guest notification */ |
e72f66a0 | 41 | |
2c20e711 PB |
42 | /* Note that these EventNotifiers are assigned by value. This is |
43 | * fine as long as you do not call event_notifier_cleanup on them | |
44 | * (because you don't own the file descriptor or handle; you just | |
45 | * use it). | |
46 | */ | |
48ff2692 | 47 | IOThread *iothread; |
54bee5c2 | 48 | IOThread internal_iothread_obj; |
2c20e711 | 49 | AioContext *ctx; |
2c20e711 | 50 | EventNotifier host_notifier; /* doorbell */ |
e72f66a0 | 51 | |
3718d8ab FZ |
52 | /* Operation blocker on BDS */ |
53 | Error *blocker; | |
b002254d FZ |
54 | void (*saved_complete_request)(struct VirtIOBlockReq *req, |
55 | unsigned char status); | |
e72f66a0 SH |
56 | }; |
57 | ||
58 | /* Raise an interrupt to signal guest, if necessary */ | |
59 | static void notify_guest(VirtIOBlockDataPlane *s) | |
60 | { | |
61 | if (!vring_should_notify(s->vdev, &s->vring)) { | |
62 | return; | |
63 | } | |
64 | ||
65 | event_notifier_set(s->guest_notifier); | |
66 | } | |
67 | ||
5b2ffbe4 ML |
68 | static void notify_guest_bh(void *opaque) |
69 | { | |
70 | VirtIOBlockDataPlane *s = opaque; | |
71 | ||
72 | notify_guest(s); | |
73 | } | |
74 | ||
d64c60a7 | 75 | static void complete_request_vring(VirtIOBlockReq *req, unsigned char status) |
e72f66a0 | 76 | { |
5b2ffbe4 | 77 | VirtIOBlockDataPlane *s = req->dev->dataplane; |
ab2e3cd2 | 78 | stb_p(&req->in->status, status); |
e72f66a0 | 79 | |
2a6cdd6d | 80 | vring_push(s->vdev, &req->dev->dataplane->vring, &req->elem, req->in_len); |
5b2ffbe4 ML |
81 | |
82 | /* Suppress notification to guest by BH and its scheduled | |
83 | * flag because requests are completed as a batch after io | |
84 | * plug & unplug is introduced, and the BH can still be | |
85 | * executed in dataplane aio context even after it is | |
86 | * stopped, so needn't worry about notification loss with BH. | |
87 | */ | |
88 | qemu_bh_schedule(s->bh); | |
e72f66a0 SH |
89 | } |
90 | ||
2c20e711 | 91 | static void handle_notify(EventNotifier *e) |
e72f66a0 | 92 | { |
2c20e711 PB |
93 | VirtIOBlockDataPlane *s = container_of(e, VirtIOBlockDataPlane, |
94 | host_notifier); | |
f897bf75 | 95 | VirtIOBlock *vblk = VIRTIO_BLK(s->vdev); |
e72f66a0 | 96 | |
2c20e711 | 97 | event_notifier_test_and_clear(&s->host_notifier); |
4be74634 | 98 | blk_io_plug(s->conf->conf.blk); |
e72f66a0 | 99 | for (;;) { |
95f7142a | 100 | MultiReqBuffer mrb = {}; |
f897bf75 SH |
101 | int ret; |
102 | ||
e72f66a0 SH |
103 | /* Disable guest->host notifies to avoid unnecessary vmexits */ |
104 | vring_disable_notification(s->vdev, &s->vring); | |
105 | ||
106 | for (;;) { | |
f897bf75 SH |
107 | VirtIOBlockReq *req = virtio_blk_alloc_request(vblk); |
108 | ||
109 | ret = vring_pop(s->vdev, &s->vring, &req->elem); | |
8c1b566f | 110 | if (ret < 0) { |
f897bf75 | 111 | virtio_blk_free_request(req); |
e72f66a0 SH |
112 | break; /* no more requests */ |
113 | } | |
114 | ||
f897bf75 SH |
115 | trace_virtio_blk_data_plane_process_request(s, req->elem.out_num, |
116 | req->elem.in_num, | |
117 | req->elem.index); | |
e72f66a0 | 118 | |
b002254d | 119 | virtio_blk_handle_request(req, &mrb); |
e72f66a0 SH |
120 | } |
121 | ||
95f7142a PL |
122 | if (mrb.num_reqs) { |
123 | virtio_blk_submit_multireq(s->conf->conf.blk, &mrb); | |
124 | } | |
b002254d | 125 | |
8c1b566f | 126 | if (likely(ret == -EAGAIN)) { /* vring emptied */ |
e72f66a0 SH |
127 | /* Re-enable guest->host notifies and stop processing the vring. |
128 | * But if the guest has snuck in more descriptors, keep processing. | |
129 | */ | |
130 | if (vring_enable_notification(s->vdev, &s->vring)) { | |
131 | break; | |
132 | } | |
580b6b2a | 133 | } else { /* fatal error */ |
e72f66a0 SH |
134 | break; |
135 | } | |
136 | } | |
4be74634 | 137 | blk_io_unplug(s->conf->conf.blk); |
e72f66a0 SH |
138 | } |
139 | ||
48ff2692 | 140 | /* Context: QEMU global mutex held */ |
2a30307f | 141 | void virtio_blk_data_plane_create(VirtIODevice *vdev, VirtIOBlkConf *conf, |
3ffeeef7 AF |
142 | VirtIOBlockDataPlane **dataplane, |
143 | Error **errp) | |
e72f66a0 SH |
144 | { |
145 | VirtIOBlockDataPlane *s; | |
3718d8ab | 146 | Error *local_err = NULL; |
a9968c77 CH |
147 | BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); |
148 | VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); | |
e72f66a0 SH |
149 | |
150 | *dataplane = NULL; | |
151 | ||
2a30307f | 152 | if (!conf->data_plane && !conf->iothread) { |
3ffeeef7 | 153 | return; |
e72f66a0 SH |
154 | } |
155 | ||
a9968c77 CH |
156 | /* Don't try if transport does not support notifiers. */ |
157 | if (!k->set_guest_notifiers || !k->set_host_notifier) { | |
158 | error_setg(errp, | |
159 | "device is incompatible with x-data-plane " | |
160 | "(transport does not support notifiers)"); | |
161 | return; | |
162 | } | |
163 | ||
b0f2027c SH |
164 | /* If dataplane is (re-)enabled while the guest is running there could be |
165 | * block jobs that can conflict. | |
166 | */ | |
4be74634 MA |
167 | if (blk_op_is_blocked(conf->conf.blk, BLOCK_OP_TYPE_DATAPLANE, |
168 | &local_err)) { | |
745a9bb9 SH |
169 | error_setg(errp, "cannot start dataplane thread: %s", |
170 | error_get_pretty(local_err)); | |
3718d8ab | 171 | error_free(local_err); |
3ffeeef7 | 172 | return; |
b0f2027c SH |
173 | } |
174 | ||
e72f66a0 SH |
175 | s = g_new0(VirtIOBlockDataPlane, 1); |
176 | s->vdev = vdev; | |
2a30307f | 177 | s->conf = conf; |
e72f66a0 | 178 | |
2a30307f MA |
179 | if (conf->iothread) { |
180 | s->iothread = conf->iothread; | |
54bee5c2 | 181 | object_ref(OBJECT(s->iothread)); |
48ff2692 | 182 | } else { |
54bee5c2 SH |
183 | /* Create per-device IOThread if none specified. This is for |
184 | * x-data-plane option compatibility. If x-data-plane is removed we | |
185 | * can drop this. | |
186 | */ | |
187 | object_initialize(&s->internal_iothread_obj, | |
188 | sizeof(s->internal_iothread_obj), | |
189 | TYPE_IOTHREAD); | |
190 | user_creatable_complete(OBJECT(&s->internal_iothread_obj), &error_abort); | |
191 | s->iothread = &s->internal_iothread_obj; | |
48ff2692 SH |
192 | } |
193 | s->ctx = iothread_get_aio_context(s->iothread); | |
5b2ffbe4 | 194 | s->bh = aio_bh_new(s->ctx, notify_guest_bh, s); |
48ff2692 | 195 | |
3718d8ab | 196 | error_setg(&s->blocker, "block device is in use by data plane"); |
4be74634 MA |
197 | blk_op_block_all(conf->conf.blk, s->blocker); |
198 | blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_RESIZE, s->blocker); | |
199 | blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_DRIVE_DEL, s->blocker); | |
b112a65c | 200 | blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_BACKUP_SOURCE, s->blocker); |
729962f6 | 201 | blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_CHANGE, s->blocker); |
bb00021d FZ |
202 | blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_COMMIT_SOURCE, s->blocker); |
203 | blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_COMMIT_TARGET, s->blocker); | |
e3442099 | 204 | blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_EJECT, s->blocker); |
5d6e96ef SH |
205 | blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_EXTERNAL_SNAPSHOT, s->blocker); |
206 | blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_INTERNAL_SNAPSHOT, s->blocker); | |
4ef3982a SH |
207 | blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_INTERNAL_SNAPSHOT_DELETE, |
208 | s->blocker); | |
b112a65c SH |
209 | blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_MIRROR, s->blocker); |
210 | blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_STREAM, s->blocker); | |
211 | blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_REPLACE, s->blocker); | |
e72f66a0 | 212 | |
e72f66a0 | 213 | *dataplane = s; |
e72f66a0 SH |
214 | } |
215 | ||
48ff2692 | 216 | /* Context: QEMU global mutex held */ |
e72f66a0 SH |
217 | void virtio_blk_data_plane_destroy(VirtIOBlockDataPlane *s) |
218 | { | |
219 | if (!s) { | |
220 | return; | |
221 | } | |
222 | ||
223 | virtio_blk_data_plane_stop(s); | |
4be74634 | 224 | blk_op_unblock_all(s->conf->conf.blk, s->blocker); |
3718d8ab | 225 | error_free(s->blocker); |
5b2ffbe4 | 226 | qemu_bh_delete(s->bh); |
fed105e2 | 227 | object_unref(OBJECT(s->iothread)); |
e72f66a0 SH |
228 | g_free(s); |
229 | } | |
230 | ||
48ff2692 | 231 | /* Context: QEMU global mutex held */ |
e72f66a0 SH |
232 | void virtio_blk_data_plane_start(VirtIOBlockDataPlane *s) |
233 | { | |
1c819449 FK |
234 | BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s->vdev))); |
235 | VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); | |
e926d9b8 | 236 | VirtIOBlock *vblk = VIRTIO_BLK(s->vdev); |
e72f66a0 | 237 | VirtQueue *vq; |
267e1a20 | 238 | int r; |
e72f66a0 | 239 | |
2f5f70fa | 240 | if (s->started || s->disabled) { |
e72f66a0 SH |
241 | return; |
242 | } | |
243 | ||
8caf907f CH |
244 | if (s->starting) { |
245 | return; | |
246 | } | |
247 | ||
248 | s->starting = true; | |
249 | ||
e72f66a0 SH |
250 | vq = virtio_get_queue(s->vdev, 0); |
251 | if (!vring_setup(&s->vring, s->vdev, 0)) { | |
f9907ebc | 252 | goto fail_vring; |
e72f66a0 SH |
253 | } |
254 | ||
e72f66a0 | 255 | /* Set up guest notifier (irq) */ |
267e1a20 CH |
256 | r = k->set_guest_notifiers(qbus->parent, 1, true); |
257 | if (r != 0) { | |
258 | fprintf(stderr, "virtio-blk failed to set guest notifier (%d), " | |
259 | "ensure -enable-kvm is set\n", r); | |
f9907ebc | 260 | goto fail_guest_notifiers; |
e72f66a0 SH |
261 | } |
262 | s->guest_notifier = virtio_queue_get_guest_notifier(vq); | |
263 | ||
264 | /* Set up virtqueue notify */ | |
267e1a20 CH |
265 | r = k->set_host_notifier(qbus->parent, 0, true); |
266 | if (r != 0) { | |
267 | fprintf(stderr, "virtio-blk failed to set host notifier (%d)\n", r); | |
f9907ebc | 268 | goto fail_host_notifier; |
e72f66a0 | 269 | } |
2c20e711 | 270 | s->host_notifier = *virtio_queue_get_host_notifier(vq); |
e72f66a0 | 271 | |
e926d9b8 ML |
272 | s->saved_complete_request = vblk->complete_request; |
273 | vblk->complete_request = complete_request_vring; | |
274 | ||
8caf907f | 275 | s->starting = false; |
e72f66a0 SH |
276 | s->started = true; |
277 | trace_virtio_blk_data_plane_start(s); | |
278 | ||
4be74634 | 279 | blk_set_aio_context(s->conf->conf.blk, s->ctx); |
580b6b2a | 280 | |
e72f66a0 SH |
281 | /* Kick right away to begin processing requests already in vring */ |
282 | event_notifier_set(virtio_queue_get_host_notifier(vq)); | |
283 | ||
48ff2692 SH |
284 | /* Get this show started by hooking up our callbacks */ |
285 | aio_context_acquire(s->ctx); | |
3a1e8074 | 286 | aio_set_event_notifier(s->ctx, &s->host_notifier, true, |
dca21ef2 | 287 | handle_notify); |
48ff2692 | 288 | aio_context_release(s->ctx); |
f9907ebc CH |
289 | return; |
290 | ||
291 | fail_host_notifier: | |
292 | k->set_guest_notifiers(qbus->parent, 1, false); | |
293 | fail_guest_notifiers: | |
294 | vring_teardown(&s->vring, s->vdev, 0); | |
2f5f70fa | 295 | s->disabled = true; |
f9907ebc CH |
296 | fail_vring: |
297 | s->starting = false; | |
e72f66a0 SH |
298 | } |
299 | ||
48ff2692 | 300 | /* Context: QEMU global mutex held */ |
e72f66a0 SH |
301 | void virtio_blk_data_plane_stop(VirtIOBlockDataPlane *s) |
302 | { | |
1c819449 FK |
303 | BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s->vdev))); |
304 | VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); | |
b002254d | 305 | VirtIOBlock *vblk = VIRTIO_BLK(s->vdev); |
2f5f70fa CH |
306 | |
307 | ||
308 | /* Better luck next time. */ | |
309 | if (s->disabled) { | |
310 | s->disabled = false; | |
311 | return; | |
312 | } | |
cd7fdfe5 | 313 | if (!s->started || s->stopping) { |
e72f66a0 SH |
314 | return; |
315 | } | |
cd7fdfe5 | 316 | s->stopping = true; |
b002254d | 317 | vblk->complete_request = s->saved_complete_request; |
e72f66a0 SH |
318 | trace_virtio_blk_data_plane_stop(s); |
319 | ||
48ff2692 SH |
320 | aio_context_acquire(s->ctx); |
321 | ||
322 | /* Stop notifications for new requests from guest */ | |
3a1e8074 | 323 | aio_set_event_notifier(s->ctx, &s->host_notifier, true, NULL); |
48ff2692 | 324 | |
580b6b2a | 325 | /* Drain and switch bs back to the QEMU main loop */ |
4be74634 | 326 | blk_set_aio_context(s->conf->conf.blk, qemu_get_aio_context()); |
e72f66a0 | 327 | |
48ff2692 | 328 | aio_context_release(s->ctx); |
e72f66a0 | 329 | |
48ff2692 SH |
330 | /* Sync vring state back to virtqueue so that non-dataplane request |
331 | * processing can continue when we disable the host notifier below. | |
332 | */ | |
333 | vring_teardown(&s->vring, s->vdev, 0); | |
334 | ||
48ff2692 | 335 | k->set_host_notifier(qbus->parent, 0, false); |
e72f66a0 SH |
336 | |
337 | /* Clean up guest notifier (irq) */ | |
1c819449 | 338 | k->set_guest_notifiers(qbus->parent, 1, false); |
e72f66a0 | 339 | |
cd7fdfe5 SH |
340 | s->started = false; |
341 | s->stopping = false; | |
e72f66a0 | 342 | } |