master
c 232 lines 7.01 KB
Raw
1 /*
2 * Linux io_uring support.
3 *
4 * Copyright (C) 2009 IBM, Corp.
5 * Copyright (C) 2009 Red Hat, Inc.
6 * Copyright (C) 2019 Aarushi Mehta
7 *
8 * This work is licensed under the terms of the GNU GPL, version 2 or later.
9 * See the COPYING file in the top-level directory.
10 */
11 #include "qemu/osdep.h"
12 #include <liburing.h>
13 #include "qemu/aio.h"
14 #include "block/block.h"
15 #include "block/raw-aio.h"
16 #include "qemu/coroutine.h"
17 #include "system/block-backend.h"
18 #include "trace.h"
19
20 typedef struct {
21 Coroutine *co;
22 QEMUIOVector *qiov;
23 uint64_t offset;
24 ssize_t ret;
25 int type;
26 int fd;
27 BdrvRequestFlags flags;
28
29 /*
30 * Short reads/writes require resubmission, see
31 * luring_resubmit_short_io().
32 */
33 int total_done;
34 QEMUIOVector resubmit_qiov;
35
36 CqeHandler cqe_handler;
37 } LuringRequest;
38
39 static void luring_prep_sqe(struct io_uring_sqe *sqe, void *opaque)
40 {
41 LuringRequest *req = opaque;
42 QEMUIOVector *qiov = req->qiov;
43 uint64_t offset = req->offset + req->total_done;
44 int fd = req->fd;
45 BdrvRequestFlags flags = req->flags;
46
47 if (req->resubmit_qiov.iov) {
48 qiov = &req->resubmit_qiov;
49 }
50
51 switch (req->type) {
52 case QEMU_AIO_WRITE:
53 {
54 int luring_flags = (flags & BDRV_REQ_FUA) ? RWF_DSYNC : 0;
55 if (luring_flags != 0 || qiov->niov > 1) {
56 #ifdef HAVE_IO_URING_PREP_WRITEV2
57 io_uring_prep_writev2(sqe, fd, qiov->iov,
58 qiov->niov, offset, luring_flags);
59 #else
60 /*
61 * FUA should only be enabled with HAVE_IO_URING_PREP_WRITEV2, see
62 * luring_has_fua().
63 */
64 assert(luring_flags == 0);
65
66 io_uring_prep_writev(sqe, fd, qiov->iov, qiov->niov, offset);
67 #endif
68 } else {
69 /* The man page says non-vectored is faster than vectored */
70 struct iovec *iov = qiov->iov;
71 io_uring_prep_write(sqe, fd, iov->iov_base, iov->iov_len, offset);
72 }
73 break;
74 }
75 case QEMU_AIO_ZONE_APPEND:
76 io_uring_prep_writev(sqe, fd, qiov->iov, qiov->niov, offset);
77 break;
78 case QEMU_AIO_READ:
79 {
80 if (qiov->niov > 1) {
81 io_uring_prep_readv(sqe, fd, qiov->iov, qiov->niov, offset);
82 } else {
83 /* The man page says non-vectored is faster than vectored */
84 struct iovec *iov = qiov->iov;
85 io_uring_prep_read(sqe, fd, iov->iov_base, iov->iov_len, offset);
86 }
87 break;
88 }
89 case QEMU_AIO_FLUSH:
90 io_uring_prep_fsync(sqe, fd, IORING_FSYNC_DATASYNC);
91 break;
92 default:
93 fprintf(stderr, "%s: invalid AIO request type, aborting 0x%x.\n",
94 __func__, req->type);
95 abort();
96 }
97 }
98
99 /**
100 * luring_resubmit_short_io:
101 *
102 * Short reads and writes are rare but may occur. The remaining request needs
103 * to be resubmitted.
104 *
105 * For example, short reads can be reproduced by a FUSE export deliberately
106 * executing short reads. The tail of short writes is generally resubmitted by
107 * io-uring in the kernel, but if that resubmission encounters an I/O error, the
108 * already submitted portion will be returned as a short write.
109 */
110 static void luring_resubmit_short_io(LuringRequest *req, int ndone)
111 {
112 QEMUIOVector *resubmit_qiov;
113 size_t remaining;
114
115 trace_luring_resubmit_short_io(req, ndone);
116
117 /* Update I/O position */
118 req->total_done += ndone;
119 remaining = req->qiov->size - req->total_done;
120
121 /* Shorten qiov */
122 resubmit_qiov = &req->resubmit_qiov;
123 if (resubmit_qiov->iov == NULL) {
124 qemu_iovec_init(resubmit_qiov, req->qiov->niov);
125 } else {
126 qemu_iovec_reset(resubmit_qiov);
127 }
128 qemu_iovec_concat(resubmit_qiov, req->qiov, req->total_done, remaining);
129
130 aio_add_sqe(luring_prep_sqe, req, &req->cqe_handler);
131 }
132
133 static void luring_cqe_handler(CqeHandler *cqe_handler)
134 {
135 LuringRequest *req = container_of(cqe_handler, LuringRequest, cqe_handler);
136 int ret = cqe_handler->cqe.res;
137
138 trace_luring_cqe_handler(req, ret);
139
140 if (ret < 0) {
141 /*
142 * Only writev/readv/fsync requests on regular files or host block
143 * devices are submitted. Therefore -EAGAIN is not expected but it's
144 * known to happen sometimes with Linux SCSI. Submit again and hope
145 * the request completes successfully.
146 *
147 * For more information, see:
148 * https://lore.kernel.org/io-uring/20210727165811.284510-3-axboe@kernel.dk/T/#u
149 *
150 * If the code is changed to submit other types of requests in the
151 * future, then this workaround may need to be extended to deal with
152 * genuine -EAGAIN results that should not be resubmitted
153 * immediately.
154 */
155 if (ret == -EINTR || ret == -EAGAIN) {
156 aio_add_sqe(luring_prep_sqe, req, &req->cqe_handler);
157 return;
158 }
159 } else if (req->qiov) {
160 /* total_done is non-zero only for resubmitted requests */
161 int total_bytes = ret + req->total_done;
162
163 if (total_bytes == req->qiov->size) {
164 ret = 0;
165 } else if (ret > 0 && (req->type == QEMU_AIO_READ ||
166 req->type == QEMU_AIO_WRITE)) {
167 /* Short Read/Write */
168 luring_resubmit_short_io(req, ret);
169 return;
170 } else if (req->type == QEMU_AIO_READ) {
171 /* Read ret == 0: EOF, pad with zeroes */
172 qemu_iovec_memset(req->qiov, total_bytes, 0,
173 req->qiov->size - total_bytes);
174 ret = 0;
175 } else {
176 /*
177 * Normal write ret == 0 means ENOSPC.
178 * For zone-append, we treat any 0 <= ret < qiov->size as ENOSPC,
179 * too, because resubmitting the tail seems a little unsafe.
180 */
181 ret = -ENOSPC;
182 }
183 }
184
185 req->ret = ret;
186 if (req->resubmit_qiov.iov) {
187 qemu_iovec_destroy(&req->resubmit_qiov);
188 }
189
190 /*
191 * If the coroutine is already entered it must be in luring_co_submit() and
192 * will notice req->ret has been filled in when it eventually runs later.
193 * Coroutines cannot be entered recursively so avoid doing that!
194 */
195 if (!qemu_coroutine_entered(req->co)) {
196 aio_co_wake(req->co);
197 }
198 }
199
200 int coroutine_fn luring_co_submit(BlockDriverState *bs, int fd,
201 uint64_t offset, QEMUIOVector *qiov,
202 int type, BdrvRequestFlags flags)
203 {
204 LuringRequest req = {
205 .co = qemu_coroutine_self(),
206 .qiov = qiov,
207 .ret = -EINPROGRESS,
208 .type = type,
209 .fd = fd,
210 .offset = offset,
211 .flags = flags,
212 };
213
214 req.cqe_handler.cb = luring_cqe_handler;
215
216 trace_luring_co_submit(bs, &req, fd, offset, qiov ? qiov->size : 0, type);
217 aio_add_sqe(luring_prep_sqe, &req, &req.cqe_handler);
218
219 if (req.ret == -EINPROGRESS) {
220 qemu_coroutine_yield();
221 }
222 return req.ret;
223 }
224
225 bool luring_has_fua(void)
226 {
227 #ifdef HAVE_IO_URING_PREP_WRITEV2
228 return true;
229 #else
230 return false;
231 #endif
232 }