master
c 427 lines 12.5 KB
Raw
1 /*
2 * Export QEMU block device via VDUSE
3 *
4 * Copyright (C) 2022 Bytedance Inc. and/or its affiliates. All rights reserved.
5 *
6 * Author:
7 * Xie Yongji <xieyongji@bytedance.com>
8 *
9 * This work is licensed under the terms of the GNU GPL, version 2 or
10 * later. See the COPYING file in the top-level directory.
11 */
12
13 #include "qemu/osdep.h"
14 #include <sys/eventfd.h>
15
16 #include "qemu/bswap.h"
17 #include "qapi/error.h"
18 #include "block/export.h"
19 #include "qemu/error-report.h"
20 #include "util/block-helpers.h"
21 #include "subprojects/libvduse/libvduse.h"
22 #include "virtio-blk-handler.h"
23
24 #include "standard-headers/linux/virtio_blk.h"
25
26 #define VDUSE_DEFAULT_NUM_QUEUE 1
27 #define VDUSE_DEFAULT_QUEUE_SIZE 256
28
29 typedef struct VduseBlkExport {
30 BlockExport export;
31 VirtioBlkHandler handler;
32 VduseDev *dev;
33 uint16_t num_queues;
34 char *recon_file;
35 unsigned int inflight; /* atomic */
36 bool vqs_started;
37 } VduseBlkExport;
38
39 typedef struct VduseBlkReq {
40 VduseVirtqElement elem;
41 VduseVirtq *vq;
42 } VduseBlkReq;
43
44 static void vduse_blk_inflight_inc(VduseBlkExport *vblk_exp)
45 {
46 if (qatomic_fetch_inc(&vblk_exp->inflight) == 0) {
47 /* Prevent export from being deleted */
48 blk_exp_ref(&vblk_exp->export);
49 }
50 }
51
52 static void vduse_blk_inflight_dec(VduseBlkExport *vblk_exp)
53 {
54 if (qatomic_fetch_dec(&vblk_exp->inflight) == 1) {
55 /* Wake AIO_WAIT_WHILE() */
56 aio_wait_kick();
57
58 /* Now the export can be deleted */
59 blk_exp_unref(&vblk_exp->export);
60 }
61 }
62
63 static void vduse_blk_req_complete(VduseBlkReq *req, size_t in_len)
64 {
65 vduse_queue_push(req->vq, &req->elem, in_len);
66 vduse_queue_notify(req->vq);
67
68 free(req);
69 }
70
71 static void coroutine_fn vduse_blk_virtio_process_req(void *opaque)
72 {
73 VduseBlkReq *req = opaque;
74 VduseVirtq *vq = req->vq;
75 VduseDev *dev = vduse_queue_get_dev(vq);
76 VduseBlkExport *vblk_exp = vduse_dev_get_priv(dev);
77 VirtioBlkHandler *handler = &vblk_exp->handler;
78 VduseVirtqElement *elem = &req->elem;
79 struct iovec *in_iov = elem->in_sg;
80 struct iovec *out_iov = elem->out_sg;
81 unsigned in_num = elem->in_num;
82 unsigned out_num = elem->out_num;
83 int in_len;
84
85 in_len = virtio_blk_process_req(handler, in_iov,
86 out_iov, in_num, out_num);
87 if (in_len < 0) {
88 free(req);
89 return;
90 }
91
92 vduse_blk_req_complete(req, in_len);
93 vduse_blk_inflight_dec(vblk_exp);
94 }
95
96 static void vduse_blk_vq_handler(VduseDev *dev, VduseVirtq *vq)
97 {
98 VduseBlkExport *vblk_exp = vduse_dev_get_priv(dev);
99
100 while (1) {
101 VduseBlkReq *req;
102
103 req = vduse_queue_pop(vq, sizeof(VduseBlkReq));
104 if (!req) {
105 break;
106 }
107 req->vq = vq;
108
109 Coroutine *co =
110 qemu_coroutine_create(vduse_blk_virtio_process_req, req);
111
112 vduse_blk_inflight_inc(vblk_exp);
113 qemu_coroutine_enter(co);
114 }
115 }
116
117 static void on_vduse_vq_kick(void *opaque)
118 {
119 VduseVirtq *vq = opaque;
120 VduseDev *dev = vduse_queue_get_dev(vq);
121 int fd = vduse_queue_get_fd(vq);
122 eventfd_t kick_data;
123
124 if (eventfd_read(fd, &kick_data) == -1) {
125 error_report("failed to read data from eventfd");
126 return;
127 }
128
129 vduse_blk_vq_handler(dev, vq);
130 }
131
132 static void vduse_blk_enable_queue(VduseDev *dev, VduseVirtq *vq)
133 {
134 VduseBlkExport *vblk_exp = vduse_dev_get_priv(dev);
135
136 if (!vblk_exp->vqs_started) {
137 return; /* vduse_blk_drained_end() will start vqs later */
138 }
139
140 aio_set_fd_handler(vblk_exp->export.ctx, vduse_queue_get_fd(vq),
141 on_vduse_vq_kick, NULL, NULL, NULL, vq);
142 /* Make sure we don't miss any kick after reconnecting */
143 eventfd_write(vduse_queue_get_fd(vq), 1);
144 }
145
146 static void vduse_blk_disable_queue(VduseDev *dev, VduseVirtq *vq)
147 {
148 VduseBlkExport *vblk_exp = vduse_dev_get_priv(dev);
149 int fd = vduse_queue_get_fd(vq);
150
151 if (fd < 0) {
152 return;
153 }
154
155 aio_set_fd_handler(vblk_exp->export.ctx, fd,
156 NULL, NULL, NULL, NULL, NULL);
157 }
158
159 static const VduseOps vduse_blk_ops = {
160 .enable_queue = vduse_blk_enable_queue,
161 .disable_queue = vduse_blk_disable_queue,
162 };
163
164 static void on_vduse_dev_kick(void *opaque)
165 {
166 VduseDev *dev = opaque;
167
168 vduse_dev_handler(dev);
169 }
170
171 static void vduse_blk_attach_ctx(VduseBlkExport *vblk_exp, AioContext *ctx)
172 {
173 aio_set_fd_handler(vblk_exp->export.ctx, vduse_dev_get_fd(vblk_exp->dev),
174 on_vduse_dev_kick, NULL, NULL, NULL,
175 vblk_exp->dev);
176
177 /* Virtqueues are handled by vduse_blk_drained_end() */
178 }
179
180 static void vduse_blk_detach_ctx(VduseBlkExport *vblk_exp)
181 {
182 aio_set_fd_handler(vblk_exp->export.ctx, vduse_dev_get_fd(vblk_exp->dev),
183 NULL, NULL, NULL, NULL, NULL);
184
185 /* Virtqueues are handled by vduse_blk_drained_begin() */
186 }
187
188
189 static void blk_aio_attached(AioContext *ctx, void *opaque)
190 {
191 VduseBlkExport *vblk_exp = opaque;
192
193 vblk_exp->export.ctx = ctx;
194 vduse_blk_attach_ctx(vblk_exp, ctx);
195 }
196
197 static void blk_aio_detach(void *opaque)
198 {
199 VduseBlkExport *vblk_exp = opaque;
200
201 vduse_blk_detach_ctx(vblk_exp);
202 vblk_exp->export.ctx = NULL;
203 }
204
205 static void vduse_blk_resize(void *opaque)
206 {
207 BlockExport *exp = opaque;
208 VduseBlkExport *vblk_exp = container_of(exp, VduseBlkExport, export);
209 struct virtio_blk_config config;
210
211 config.capacity =
212 cpu_to_le64(blk_getlength(exp->blk) >> VIRTIO_BLK_SECTOR_BITS);
213 vduse_dev_update_config(vblk_exp->dev, sizeof(config.capacity),
214 offsetof(struct virtio_blk_config, capacity),
215 (char *)&config.capacity);
216 }
217
218 static void vduse_blk_stop_virtqueues(VduseBlkExport *vblk_exp)
219 {
220 for (uint16_t i = 0; i < vblk_exp->num_queues; i++) {
221 VduseVirtq *vq = vduse_dev_get_queue(vblk_exp->dev, i);
222 vduse_blk_disable_queue(vblk_exp->dev, vq);
223 }
224
225 vblk_exp->vqs_started = false;
226 }
227
228 static void vduse_blk_start_virtqueues(VduseBlkExport *vblk_exp)
229 {
230 vblk_exp->vqs_started = true;
231
232 for (uint16_t i = 0; i < vblk_exp->num_queues; i++) {
233 VduseVirtq *vq = vduse_dev_get_queue(vblk_exp->dev, i);
234 vduse_blk_enable_queue(vblk_exp->dev, vq);
235 }
236 }
237
238 static void vduse_blk_drained_begin(void *opaque)
239 {
240 BlockExport *exp = opaque;
241 VduseBlkExport *vblk_exp = container_of(exp, VduseBlkExport, export);
242
243 vduse_blk_stop_virtqueues(vblk_exp);
244 }
245
246 static void vduse_blk_drained_end(void *opaque)
247 {
248 BlockExport *exp = opaque;
249 VduseBlkExport *vblk_exp = container_of(exp, VduseBlkExport, export);
250
251 vduse_blk_start_virtqueues(vblk_exp);
252 }
253
254 static bool vduse_blk_drained_poll(void *opaque)
255 {
256 BlockExport *exp = opaque;
257 VduseBlkExport *vblk_exp = container_of(exp, VduseBlkExport, export);
258
259 return qatomic_read(&vblk_exp->inflight) > 0;
260 }
261
262 static const BlockDevOps vduse_block_ops = {
263 .resize_cb = vduse_blk_resize,
264 .drained_begin = vduse_blk_drained_begin,
265 .drained_end = vduse_blk_drained_end,
266 .drained_poll = vduse_blk_drained_poll,
267 };
268
269 static int vduse_blk_exp_create(BlockExport *exp, BlockExportOptions *opts,
270 AioContext *const *multithread, size_t mt_count,
271 Error **errp)
272 {
273 VduseBlkExport *vblk_exp = container_of(exp, VduseBlkExport, export);
274 BlockExportOptionsVduseBlk *vblk_opts = &opts->u.vduse_blk;
275 uint64_t logical_block_size = VIRTIO_BLK_SECTOR_SIZE;
276 uint16_t num_queues = VDUSE_DEFAULT_NUM_QUEUE;
277 uint16_t queue_size = VDUSE_DEFAULT_QUEUE_SIZE;
278 struct virtio_blk_config config = { 0 };
279 uint64_t features;
280 int i, ret;
281
282 if (vblk_opts->has_num_queues) {
283 num_queues = vblk_opts->num_queues;
284 if (num_queues == 0) {
285 error_setg(errp, "num-queues must be greater than 0");
286 return -EINVAL;
287 }
288 }
289
290 if (vblk_opts->has_queue_size) {
291 queue_size = vblk_opts->queue_size;
292 if (queue_size <= 2 || !is_power_of_2(queue_size) ||
293 queue_size > VIRTQUEUE_MAX_SIZE) {
294 error_setg(errp, "queue-size is invalid");
295 return -EINVAL;
296 }
297 }
298
299 if (vblk_opts->has_logical_block_size) {
300 logical_block_size = vblk_opts->logical_block_size;
301 if (!check_block_size("logical-block-size", logical_block_size,
302 errp)) {
303 return -EINVAL;
304 }
305 }
306
307 if (multithread) {
308 error_setg(errp, "vduse-blk export does not support multi-threading");
309 return -EINVAL;
310 }
311
312 vblk_exp->num_queues = num_queues;
313 vblk_exp->handler.blk = exp->blk;
314 vblk_exp->handler.serial = g_strdup(vblk_opts->serial ?: "");
315 vblk_exp->handler.logical_block_size = logical_block_size;
316 vblk_exp->handler.writable = opts->writable;
317 vblk_exp->vqs_started = true;
318
319 config.capacity =
320 cpu_to_le64(blk_getlength(exp->blk) >> VIRTIO_BLK_SECTOR_BITS);
321 config.seg_max = cpu_to_le32(queue_size - 2);
322 config.min_io_size = cpu_to_le16(1);
323 config.opt_io_size = cpu_to_le32(1);
324 config.num_queues = cpu_to_le16(num_queues);
325 config.blk_size = cpu_to_le32(logical_block_size);
326 config.max_discard_sectors = cpu_to_le32(VIRTIO_BLK_MAX_DISCARD_SECTORS);
327 config.max_discard_seg = cpu_to_le32(1);
328 config.discard_sector_alignment =
329 cpu_to_le32(logical_block_size >> VIRTIO_BLK_SECTOR_BITS);
330 config.max_write_zeroes_sectors =
331 cpu_to_le32(VIRTIO_BLK_MAX_WRITE_ZEROES_SECTORS);
332 config.max_write_zeroes_seg = cpu_to_le32(1);
333
334 features = vduse_get_virtio_features() |
335 (1ULL << VIRTIO_BLK_F_SEG_MAX) |
336 (1ULL << VIRTIO_BLK_F_TOPOLOGY) |
337 (1ULL << VIRTIO_BLK_F_BLK_SIZE) |
338 (1ULL << VIRTIO_BLK_F_FLUSH) |
339 (1ULL << VIRTIO_BLK_F_DISCARD) |
340 (1ULL << VIRTIO_BLK_F_WRITE_ZEROES);
341
342 if (num_queues > 1) {
343 features |= 1ULL << VIRTIO_BLK_F_MQ;
344 }
345 if (!opts->writable) {
346 features |= 1ULL << VIRTIO_BLK_F_RO;
347 }
348
349 vblk_exp->dev = vduse_dev_create(vblk_opts->name, VIRTIO_ID_BLOCK, 0,
350 features, num_queues,
351 sizeof(struct virtio_blk_config),
352 (char *)&config, &vduse_blk_ops,
353 vblk_exp);
354 if (!vblk_exp->dev) {
355 error_setg(errp, "failed to create vduse device");
356 ret = -ENOMEM;
357 goto err_dev;
358 }
359
360 vblk_exp->recon_file = g_strdup_printf("%s/vduse-blk-%s",
361 g_get_tmp_dir(), vblk_opts->name);
362 if (vduse_set_reconnect_log_file(vblk_exp->dev, vblk_exp->recon_file)) {
363 error_setg(errp, "failed to set reconnect log file");
364 ret = -EINVAL;
365 goto err;
366 }
367
368 for (i = 0; i < num_queues; i++) {
369 vduse_dev_setup_queue(vblk_exp->dev, i, queue_size);
370 }
371
372 aio_set_fd_handler(exp->ctx, vduse_dev_get_fd(vblk_exp->dev),
373 on_vduse_dev_kick, NULL, NULL, NULL, vblk_exp->dev);
374
375 blk_add_aio_context_notifier(exp->blk, blk_aio_attached, blk_aio_detach,
376 vblk_exp);
377 blk_set_dev_ops(exp->blk, &vduse_block_ops, exp);
378
379 /*
380 * We handle draining ourselves using an in-flight counter and by disabling
381 * virtqueue fd handlers. Do not queue BlockBackend requests, they need to
382 * complete so the in-flight counter reaches zero.
383 */
384 blk_set_disable_request_queuing(exp->blk, true);
385
386 return 0;
387 err:
388 vduse_dev_destroy(vblk_exp->dev);
389 g_free(vblk_exp->recon_file);
390 err_dev:
391 g_free(vblk_exp->handler.serial);
392 return ret;
393 }
394
395 static void vduse_blk_exp_delete(BlockExport *exp)
396 {
397 VduseBlkExport *vblk_exp = container_of(exp, VduseBlkExport, export);
398 int ret;
399
400 assert(qatomic_read(&vblk_exp->inflight) == 0);
401
402 vduse_blk_detach_ctx(vblk_exp);
403 blk_remove_aio_context_notifier(exp->blk, blk_aio_attached, blk_aio_detach,
404 vblk_exp);
405 ret = vduse_dev_destroy(vblk_exp->dev);
406 if (ret != -EBUSY) {
407 unlink(vblk_exp->recon_file);
408 }
409 g_free(vblk_exp->recon_file);
410 g_free(vblk_exp->handler.serial);
411 }
412
413 /* Called with exp->ctx acquired */
414 static void vduse_blk_exp_request_shutdown(BlockExport *exp)
415 {
416 VduseBlkExport *vblk_exp = container_of(exp, VduseBlkExport, export);
417
418 vduse_blk_stop_virtqueues(vblk_exp);
419 }
420
421 const BlockExportDriver blk_exp_vduse_blk = {
422 .type = BLOCK_EXPORT_TYPE_VDUSE_BLK,
423 .instance_size = sizeof(VduseBlkExport),
424 .create = vduse_blk_exp_create,
425 .delete = vduse_blk_exp_delete,
426 .request_shutdown = vduse_blk_exp_request_shutdown,
427 };