Merge tag 'for-upstream' of https://repo.or.cz/qemu/kevin into staging

Block layer patches

- virtio-scsi: add iothread-vq-mapping parameter
- Improve writethrough performance
- Fix missing zero init in bdrv_snapshot_goto()
- Added scripts/qcow2-to-stdout.py
- Code cleanup and iotests fixes

# -----BEGIN PGP SIGNATURE-----
#
# iQJFBAABCAAvFiEE3D3rFZqa+V09dFb+fwmycsiPL9YFAmfTDysRHGt3b2xmQHJl
# ZGhhdC5jb20ACgkQfwmycsiPL9Yz6A//asOl37zjbtf9pYjY/gliH859TQOppPGD
# LB9IIr+nTDME0wfUkCOlag+CeEYZwkeo2PF+XeopsyzlJeBOk4tL7AkY57XYe3lZ
# M5hlnNrn6l3gb6iioMg60pEKSMrpKprB16vT3nAtyN6aEXsm9TvtPkWPFTCFGVeK
# W74VCr7wuXbfdEJcOGd8WhB9ZHIgwoWYnoL41tvCoefW2yNaMA6X0TLn98toXzOi
# il50ZnnchTQngns5R+n+1R1Ma995t393D+CArQcYVRzxKGOs5p0y4otz4gCkMhdp
# GVL09R7Ge4TteSJ2myxlN/EjYOxmdoMrVDajr4xPdHBw12MKzgk8i82h4/Es/Q5o
# 3Npgx74+jDyqlICb/czTVM5KJINpyO80vO3N3WpYUOQGyTCcYgv7pIpy8pB2o6Te
# RPlv0W9bHVSSgThFFLQ0Ud8WRGJe1K/ar8bdmiWN08Wez1avENWaYmsv5zGnFL24
# vD6cNXMR4mF7mzyeWda/5hGKv75djVgX+ZfzvWNT3qgizD56JBOA3RdCRwBZJOJb
# TvJkfi5RGyaji9BfKVCYBL3/iDELJEVDW8jxvIIUrS0aPcTHpAQ5gTO7VAokreqZ
# 5Smll11eeoEgPPvNLw8ikmOGTWOMkJGrmExP2K1ApANq3kSbBSU4jroEr0BG9PZT
# 6Y0hUdtFSdU=
# =w2Ri
# -----END PGP SIGNATURE-----
# gpg: Signature made Fri 14 Mar 2025 01:00:27 HKT
# gpg:                using RSA key DC3DEB159A9AF95D3D7456FE7F09B272C88F2FD6
# gpg:                issuer "kwolf@redhat.com"
# gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" [full]
# Primary key fingerprint: DC3D EB15 9A9A F95D 3D74  56FE 7F09 B272 C88F 2FD6

* tag 'for-upstream' of https://repo.or.cz/qemu/kevin: (23 commits)
  scripts/qcow2-to-stdout.py: Add script to write qcow2 images to stdout
  virtio-scsi: only expose cmd vqs via iothread-vq-mapping
  virtio-scsi: handle ctrl virtqueue in main loop
  virtio-scsi: add iothread-vq-mapping parameter
  virtio: extract iothread-vq-mapping.h API
  virtio-blk: tidy up iothread_vq_mapping functions
  virtio-blk: extract cleanup_iothread_vq_mapping() function
  virtio-scsi: perform TMFs in appropriate AioContexts
  virtio-scsi: protect events_dropped field
  virtio-scsi: introduce event and ctrl virtqueue locks
  scsi: introduce requests_lock
  scsi: track per-SCSIRequest AioContext
  dma: use current AioContext for dma_blk_io()
  scsi-disk: drop unused SCSIDiskState->bh field
  iotests: Limit qsd-migrate to working formats
  aio-posix: Adjust polling time also for new handlers
  aio-posix: Separate AioPolledEvent per AioHandler
  aio-posix: Factor out adjust_polling_time()
  aio: Create AioPolledEvent
  block/io: Ignore FUA with cache.no-flush=on
  ...

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi
2025-03-14 09:31:13 +08:00
30 changed files with 1306 additions and 531 deletions
-12
View File
@@ -2357,18 +2357,6 @@ void *blk_blockalign(BlockBackend *blk, size_t size)
return qemu_blockalign(blk ? blk_bs(blk) : NULL, size);
}
bool blk_op_is_blocked(BlockBackend *blk, BlockOpType op, Error **errp)
{
BlockDriverState *bs = blk_bs(blk);
GLOBAL_STATE_CODE();
GRAPH_RDLOCK_GUARD_MAINLOOP();
if (!bs) {
return false;
}
return bdrv_op_is_blocked(bs, op, errp);
}
/**
* Return BB's current AioContext. Note that this context may change
+21 -8
View File
@@ -194,6 +194,7 @@ static int fd_open(BlockDriverState *bs)
}
static int64_t raw_getlength(BlockDriverState *bs);
static int coroutine_fn raw_co_flush_to_disk(BlockDriverState *bs);
typedef struct RawPosixAIOData {
BlockDriverState *bs;
@@ -804,6 +805,13 @@ static int raw_open_common(BlockDriverState *bs, QDict *options,
#endif
s->needs_alignment = raw_needs_alignment(bs);
bs->supported_write_flags = BDRV_REQ_FUA;
if (s->use_linux_aio && !laio_has_fua()) {
bs->supported_write_flags &= ~BDRV_REQ_FUA;
} else if (s->use_linux_io_uring && !luring_has_fua()) {
bs->supported_write_flags &= ~BDRV_REQ_FUA;
}
bs->supported_zero_flags = BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK;
if (S_ISREG(st.st_mode)) {
/* When extending regular files, we get zeros from the OS */
@@ -2477,7 +2485,8 @@ static inline bool raw_check_linux_aio(BDRVRawState *s)
#endif
static int coroutine_fn raw_co_prw(BlockDriverState *bs, int64_t *offset_ptr,
uint64_t bytes, QEMUIOVector *qiov, int type)
uint64_t bytes, QEMUIOVector *qiov, int type,
int flags)
{
BDRVRawState *s = bs->opaque;
RawPosixAIOData acb;
@@ -2508,13 +2517,13 @@ static int coroutine_fn raw_co_prw(BlockDriverState *bs, int64_t *offset_ptr,
#ifdef CONFIG_LINUX_IO_URING
} else if (raw_check_linux_io_uring(s)) {
assert(qiov->size == bytes);
ret = luring_co_submit(bs, s->fd, offset, qiov, type);
ret = luring_co_submit(bs, s->fd, offset, qiov, type, flags);
goto out;
#endif
#ifdef CONFIG_LINUX_AIO
} else if (raw_check_linux_aio(s)) {
assert(qiov->size == bytes);
ret = laio_co_submit(s->fd, offset, qiov, type,
ret = laio_co_submit(s->fd, offset, qiov, type, flags,
s->aio_max_batch);
goto out;
#endif
@@ -2534,6 +2543,10 @@ static int coroutine_fn raw_co_prw(BlockDriverState *bs, int64_t *offset_ptr,
assert(qiov->size == bytes);
ret = raw_thread_pool_submit(handle_aiocb_rw, &acb);
if (ret == 0 && (flags & BDRV_REQ_FUA)) {
/* TODO Use pwritev2() instead if it's available */
ret = raw_co_flush_to_disk(bs);
}
goto out; /* Avoid the compiler err of unused label */
out:
@@ -2571,14 +2584,14 @@ static int coroutine_fn raw_co_preadv(BlockDriverState *bs, int64_t offset,
int64_t bytes, QEMUIOVector *qiov,
BdrvRequestFlags flags)
{
return raw_co_prw(bs, &offset, bytes, qiov, QEMU_AIO_READ);
return raw_co_prw(bs, &offset, bytes, qiov, QEMU_AIO_READ, flags);
}
static int coroutine_fn raw_co_pwritev(BlockDriverState *bs, int64_t offset,
int64_t bytes, QEMUIOVector *qiov,
BdrvRequestFlags flags)
{
return raw_co_prw(bs, &offset, bytes, qiov, QEMU_AIO_WRITE);
return raw_co_prw(bs, &offset, bytes, qiov, QEMU_AIO_WRITE, flags);
}
static int coroutine_fn raw_co_flush_to_disk(BlockDriverState *bs)
@@ -2600,12 +2613,12 @@ static int coroutine_fn raw_co_flush_to_disk(BlockDriverState *bs)
#ifdef CONFIG_LINUX_IO_URING
if (raw_check_linux_io_uring(s)) {
return luring_co_submit(bs, s->fd, 0, NULL, QEMU_AIO_FLUSH);
return luring_co_submit(bs, s->fd, 0, NULL, QEMU_AIO_FLUSH, 0);
}
#endif
#ifdef CONFIG_LINUX_AIO
if (s->has_laio_fdsync && raw_check_linux_aio(s)) {
return laio_co_submit(s->fd, 0, NULL, QEMU_AIO_FLUSH, 0);
return laio_co_submit(s->fd, 0, NULL, QEMU_AIO_FLUSH, 0, 0);
}
#endif
return raw_thread_pool_submit(handle_aiocb_flush, &acb);
@@ -3540,7 +3553,7 @@ static int coroutine_fn raw_co_zone_append(BlockDriverState *bs,
}
trace_zbd_zone_append(bs, *offset >> BDRV_SECTOR_BITS);
return raw_co_prw(bs, offset, len, qiov, QEMU_AIO_ZONE_APPEND);
return raw_co_prw(bs, offset, len, qiov, QEMU_AIO_ZONE_APPEND, 0);
}
#endif
+4
View File
@@ -1058,6 +1058,10 @@ bdrv_driver_pwritev(BlockDriverState *bs, int64_t offset, int64_t bytes,
return -ENOMEDIUM;
}
if (bs->open_flags & BDRV_O_NO_FLUSH) {
flags &= ~BDRV_REQ_FUA;
}
if ((flags & BDRV_REQ_FUA) &&
(~bs->supported_write_flags & BDRV_REQ_FUA)) {
flags &= ~BDRV_REQ_FUA;
+22 -3
View File
@@ -335,15 +335,24 @@ static void luring_deferred_fn(void *opaque)
*
*/
static int luring_do_submit(int fd, LuringAIOCB *luringcb, LuringState *s,
uint64_t offset, int type)
uint64_t offset, int type, BdrvRequestFlags flags)
{
int ret;
struct io_uring_sqe *sqes = &luringcb->sqeq;
switch (type) {
case QEMU_AIO_WRITE:
#ifdef HAVE_IO_URING_PREP_WRITEV2
{
int luring_flags = (flags & BDRV_REQ_FUA) ? RWF_DSYNC : 0;
io_uring_prep_writev2(sqes, fd, luringcb->qiov->iov,
luringcb->qiov->niov, offset, luring_flags);
}
#else
assert(flags == 0);
io_uring_prep_writev(sqes, fd, luringcb->qiov->iov,
luringcb->qiov->niov, offset);
#endif
break;
case QEMU_AIO_ZONE_APPEND:
io_uring_prep_writev(sqes, fd, luringcb->qiov->iov,
@@ -380,7 +389,8 @@ static int luring_do_submit(int fd, LuringAIOCB *luringcb, LuringState *s,
}
int coroutine_fn luring_co_submit(BlockDriverState *bs, int fd, uint64_t offset,
QEMUIOVector *qiov, int type)
QEMUIOVector *qiov, int type,
BdrvRequestFlags flags)
{
int ret;
AioContext *ctx = qemu_get_current_aio_context();
@@ -393,7 +403,7 @@ int coroutine_fn luring_co_submit(BlockDriverState *bs, int fd, uint64_t offset,
};
trace_luring_co_submit(bs, s, &luringcb, fd, offset, qiov ? qiov->size : 0,
type);
ret = luring_do_submit(fd, &luringcb, s, offset, type);
ret = luring_do_submit(fd, &luringcb, s, offset, type, flags);
if (ret < 0) {
return ret;
@@ -448,3 +458,12 @@ void luring_cleanup(LuringState *s)
trace_luring_cleanup_state(s);
g_free(s);
}
bool luring_has_fua(void)
{
#ifdef HAVE_IO_URING_PREP_WRITEV2
return true;
#else
return false;
#endif
}
+22 -3
View File
@@ -368,7 +368,8 @@ static void laio_deferred_fn(void *opaque)
}
static int laio_do_submit(int fd, struct qemu_laiocb *laiocb, off_t offset,
int type, uint64_t dev_max_batch)
int type, BdrvRequestFlags flags,
uint64_t dev_max_batch)
{
LinuxAioState *s = laiocb->ctx;
struct iocb *iocbs = &laiocb->iocb;
@@ -376,7 +377,15 @@ static int laio_do_submit(int fd, struct qemu_laiocb *laiocb, off_t offset,
switch (type) {
case QEMU_AIO_WRITE:
#ifdef HAVE_IO_PREP_PWRITEV2
{
int laio_flags = (flags & BDRV_REQ_FUA) ? RWF_DSYNC : 0;
io_prep_pwritev2(iocbs, fd, qiov->iov, qiov->niov, offset, laio_flags);
}
#else
assert(flags == 0);
io_prep_pwritev(iocbs, fd, qiov->iov, qiov->niov, offset);
#endif
break;
case QEMU_AIO_ZONE_APPEND:
io_prep_pwritev(iocbs, fd, qiov->iov, qiov->niov, offset);
@@ -409,7 +418,8 @@ static int laio_do_submit(int fd, struct qemu_laiocb *laiocb, off_t offset,
}
int coroutine_fn laio_co_submit(int fd, uint64_t offset, QEMUIOVector *qiov,
int type, uint64_t dev_max_batch)
int type, BdrvRequestFlags flags,
uint64_t dev_max_batch)
{
int ret;
AioContext *ctx = qemu_get_current_aio_context();
@@ -422,7 +432,7 @@ int coroutine_fn laio_co_submit(int fd, uint64_t offset, QEMUIOVector *qiov,
.qiov = qiov,
};
ret = laio_do_submit(fd, &laiocb, offset, type, dev_max_batch);
ret = laio_do_submit(fd, &laiocb, offset, type, flags, dev_max_batch);
if (ret < 0) {
return ret;
}
@@ -505,3 +515,12 @@ bool laio_has_fdsync(int fd)
io_destroy(ctx);
return (ret == -EINVAL) ? false : true;
}
bool laio_has_fua(void)
{
#ifdef HAVE_IO_PREP_PWRITEV2
return true;
#else
return false;
#endif
}
+1
View File
@@ -296,6 +296,7 @@ int bdrv_snapshot_goto(BlockDriverState *bs,
bdrv_graph_wrunlock();
ret = bdrv_snapshot_goto(fallback_bs, snapshot_id, errp);
memset(bs->opaque, 0, drv->instance_size);
open_ret = drv->bdrv_open(bs, options, bs->open_flags, &local_err);
qobject_unref(options);
if (open_ret < 0) {
+3 -129
View File
@@ -33,6 +33,7 @@
#endif
#include "hw/virtio/virtio-bus.h"
#include "migration/qemu-file-types.h"
#include "hw/virtio/iothread-vq-mapping.h"
#include "hw/virtio/virtio-access.h"
#include "hw/virtio/virtio-blk-common.h"
#include "qemu/coroutine.h"
@@ -1423,128 +1424,6 @@ static const BlockDevOps virtio_block_ops = {
.drained_end = virtio_blk_drained_end,
};
static bool
validate_iothread_vq_mapping_list(IOThreadVirtQueueMappingList *list,
uint16_t num_queues, Error **errp)
{
g_autofree unsigned long *vqs = bitmap_new(num_queues);
g_autoptr(GHashTable) iothreads =
g_hash_table_new(g_str_hash, g_str_equal);
for (IOThreadVirtQueueMappingList *node = list; node; node = node->next) {
const char *name = node->value->iothread;
uint16List *vq;
if (!iothread_by_id(name)) {
error_setg(errp, "IOThread \"%s\" object does not exist", name);
return false;
}
if (!g_hash_table_add(iothreads, (gpointer)name)) {
error_setg(errp,
"duplicate IOThread name \"%s\" in iothread-vq-mapping",
name);
return false;
}
if (node != list) {
if (!!node->value->vqs != !!list->value->vqs) {
error_setg(errp, "either all items in iothread-vq-mapping "
"must have vqs or none of them must have it");
return false;
}
}
for (vq = node->value->vqs; vq; vq = vq->next) {
if (vq->value >= num_queues) {
error_setg(errp, "vq index %u for IOThread \"%s\" must be "
"less than num_queues %u in iothread-vq-mapping",
vq->value, name, num_queues);
return false;
}
if (test_and_set_bit(vq->value, vqs)) {
error_setg(errp, "cannot assign vq %u to IOThread \"%s\" "
"because it is already assigned", vq->value, name);
return false;
}
}
}
if (list->value->vqs) {
for (uint16_t i = 0; i < num_queues; i++) {
if (!test_bit(i, vqs)) {
error_setg(errp,
"missing vq %u IOThread assignment in iothread-vq-mapping",
i);
return false;
}
}
}
return true;
}
/**
* apply_iothread_vq_mapping:
* @iothread_vq_mapping_list: The mapping of virtqueues to IOThreads.
* @vq_aio_context: The array of AioContext pointers to fill in.
* @num_queues: The length of @vq_aio_context.
* @errp: If an error occurs, a pointer to the area to store the error.
*
* Fill in the AioContext for each virtqueue in the @vq_aio_context array given
* the iothread-vq-mapping parameter in @iothread_vq_mapping_list.
*
* Returns: %true on success, %false on failure.
**/
static bool apply_iothread_vq_mapping(
IOThreadVirtQueueMappingList *iothread_vq_mapping_list,
AioContext **vq_aio_context,
uint16_t num_queues,
Error **errp)
{
IOThreadVirtQueueMappingList *node;
size_t num_iothreads = 0;
size_t cur_iothread = 0;
if (!validate_iothread_vq_mapping_list(iothread_vq_mapping_list,
num_queues, errp)) {
return false;
}
for (node = iothread_vq_mapping_list; node; node = node->next) {
num_iothreads++;
}
for (node = iothread_vq_mapping_list; node; node = node->next) {
IOThread *iothread = iothread_by_id(node->value->iothread);
AioContext *ctx = iothread_get_aio_context(iothread);
/* Released in virtio_blk_vq_aio_context_cleanup() */
object_ref(OBJECT(iothread));
if (node->value->vqs) {
uint16List *vq;
/* Explicit vq:IOThread assignment */
for (vq = node->value->vqs; vq; vq = vq->next) {
assert(vq->value < num_queues);
vq_aio_context[vq->value] = ctx;
}
} else {
/* Round-robin vq:IOThread assignment */
for (unsigned i = cur_iothread; i < num_queues;
i += num_iothreads) {
vq_aio_context[i] = ctx;
}
}
cur_iothread++;
}
return true;
}
/* Context: BQL held */
static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp)
{
@@ -1577,7 +1456,7 @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp)
s->vq_aio_context = g_new(AioContext *, conf->num_queues);
if (conf->iothread_vq_mapping_list) {
if (!apply_iothread_vq_mapping(conf->iothread_vq_mapping_list,
if (!iothread_vq_mapping_apply(conf->iothread_vq_mapping_list,
s->vq_aio_context,
conf->num_queues,
errp)) {
@@ -1611,12 +1490,7 @@ static void virtio_blk_vq_aio_context_cleanup(VirtIOBlock *s)
assert(!s->ioeventfd_started);
if (conf->iothread_vq_mapping_list) {
IOThreadVirtQueueMappingList *node;
for (node = conf->iothread_vq_mapping_list; node; node = node->next) {
IOThread *iothread = iothread_by_id(node->value->iothread);
object_unref(OBJECT(iothread));
}
iothread_vq_mapping_cleanup(conf->iothread_vq_mapping_list);
}
if (conf->iothread) {
+1 -2
View File
@@ -968,8 +968,7 @@ static void ide_dma_cb(void *opaque, int ret)
BDRV_SECTOR_SIZE, ide_dma_cb, s);
break;
case IDE_DMA_TRIM:
s->bus->dma->aiocb = dma_blk_io(blk_get_aio_context(s->blk),
&s->sg, offset, BDRV_SECTOR_SIZE,
s->bus->dma->aiocb = dma_blk_io(&s->sg, offset, BDRV_SECTOR_SIZE,
ide_issue_trim, s, ide_dma_cb, s,
DMA_DIRECTION_TO_DEVICE);
break;
+1 -2
View File
@@ -187,8 +187,7 @@ static void pmac_ide_transfer_cb(void *opaque, int ret)
pmac_ide_transfer_cb, io);
break;
case IDE_DMA_TRIM:
s->bus->dma->aiocb = dma_blk_io(blk_get_aio_context(s->blk), &s->sg,
offset, 0x1, ide_issue_trim, s,
s->bus->dma->aiocb = dma_blk_io(&s->sg, offset, 0x1, ide_issue_trim, s,
pmac_ide_transfer_cb, io,
DMA_DIRECTION_TO_DEVICE);
break;
+86 -35
View File
@@ -100,8 +100,15 @@ static void scsi_device_for_each_req_sync(SCSIDevice *s,
assert(!runstate_is_running());
assert(qemu_in_main_thread());
QTAILQ_FOREACH_SAFE(req, &s->requests, next, next_req) {
fn(req, opaque);
/*
* Locking is not necessary because the guest is stopped and no other
* threads can be accessing the requests list, but take the lock for
* consistency.
*/
WITH_QEMU_LOCK_GUARD(&s->requests_lock) {
QTAILQ_FOREACH_SAFE(req, &s->requests, next, next_req) {
fn(req, opaque);
}
}
}
@@ -115,21 +122,29 @@ static void scsi_device_for_each_req_async_bh(void *opaque)
{
g_autofree SCSIDeviceForEachReqAsyncData *data = opaque;
SCSIDevice *s = data->s;
AioContext *ctx;
SCSIRequest *req;
SCSIRequest *next;
g_autoptr(GList) reqs = NULL;
/*
* The BB cannot have changed contexts between this BH being scheduled and
* now: BBs' AioContexts, when they have a node attached, can only be
* changed via bdrv_try_change_aio_context(), in a drained section. While
* we have the in-flight counter incremented, that drain must block.
* Build a list of requests in this AioContext so fn() can be invoked later
* outside requests_lock.
*/
ctx = blk_get_aio_context(s->conf.blk);
assert(ctx == qemu_get_current_aio_context());
WITH_QEMU_LOCK_GUARD(&s->requests_lock) {
AioContext *ctx = qemu_get_current_aio_context();
SCSIRequest *req;
SCSIRequest *next;
QTAILQ_FOREACH_SAFE(req, &s->requests, next, next) {
data->fn(req, data->fn_opaque);
QTAILQ_FOREACH_SAFE(req, &s->requests, next, next) {
if (req->ctx == ctx) {
scsi_req_ref(req); /* dropped after calling fn() */
reqs = g_list_prepend(reqs, req);
}
}
}
/* Call fn() on each request */
for (GList *elem = g_list_first(reqs); elem; elem = g_list_next(elem)) {
data->fn(elem->data, data->fn_opaque);
scsi_req_unref(elem->data);
}
/* Drop the reference taken by scsi_device_for_each_req_async() */
@@ -139,9 +154,35 @@ static void scsi_device_for_each_req_async_bh(void *opaque)
blk_dec_in_flight(s->conf.blk);
}
static void scsi_device_for_each_req_async_do_ctx(gpointer key, gpointer value,
gpointer user_data)
{
AioContext *ctx = key;
SCSIDeviceForEachReqAsyncData *params = user_data;
SCSIDeviceForEachReqAsyncData *data;
data = g_new(SCSIDeviceForEachReqAsyncData, 1);
data->s = params->s;
data->fn = params->fn;
data->fn_opaque = params->fn_opaque;
/*
* Hold a reference to the SCSIDevice until
* scsi_device_for_each_req_async_bh() finishes.
*/
object_ref(OBJECT(data->s));
/* Paired with scsi_device_for_each_req_async_bh() */
blk_inc_in_flight(data->s->conf.blk);
aio_bh_schedule_oneshot(ctx, scsi_device_for_each_req_async_bh, data);
}
/*
* Schedule @fn() to be invoked for each enqueued request in device @s. @fn()
* runs in the AioContext that is executing the request.
* must be thread-safe because it runs concurrently in each AioContext that is
* executing a request.
*
* Keeps the BlockBackend's in-flight counter incremented until everything is
* done, so draining it will settle all scheduled @fn() calls.
*/
@@ -151,24 +192,26 @@ static void scsi_device_for_each_req_async(SCSIDevice *s,
{
assert(qemu_in_main_thread());
SCSIDeviceForEachReqAsyncData *data =
g_new(SCSIDeviceForEachReqAsyncData, 1);
/* The set of AioContexts where the requests are being processed */
g_autoptr(GHashTable) aio_contexts = g_hash_table_new(NULL, NULL);
WITH_QEMU_LOCK_GUARD(&s->requests_lock) {
SCSIRequest *req;
QTAILQ_FOREACH(req, &s->requests, next) {
g_hash_table_add(aio_contexts, req->ctx);
}
}
data->s = s;
data->fn = fn;
data->fn_opaque = opaque;
/*
* Hold a reference to the SCSIDevice until
* scsi_device_for_each_req_async_bh() finishes.
*/
object_ref(OBJECT(s));
/* Paired with blk_dec_in_flight() in scsi_device_for_each_req_async_bh() */
blk_inc_in_flight(s->conf.blk);
aio_bh_schedule_oneshot(blk_get_aio_context(s->conf.blk),
scsi_device_for_each_req_async_bh,
data);
/* Schedule a BH for each AioContext */
SCSIDeviceForEachReqAsyncData params = {
.s = s,
.fn = fn,
.fn_opaque = opaque,
};
g_hash_table_foreach(
aio_contexts,
scsi_device_for_each_req_async_do_ctx,
&params
);
}
static void scsi_device_realize(SCSIDevice *s, Error **errp)
@@ -349,6 +392,7 @@ static void scsi_qdev_realize(DeviceState *qdev, Error **errp)
dev->lun = lun;
}
qemu_mutex_init(&dev->requests_lock);
QTAILQ_INIT(&dev->requests);
scsi_device_realize(dev, &local_err);
if (local_err) {
@@ -369,6 +413,8 @@ static void scsi_qdev_unrealize(DeviceState *qdev)
scsi_device_purge_requests(dev, SENSE_CODE(NO_SENSE));
qemu_mutex_destroy(&dev->requests_lock);
scsi_device_unrealize(dev);
blockdev_mark_auto_del(dev->conf.blk);
@@ -868,6 +914,7 @@ invalid_opcode:
}
}
req->ctx = qemu_get_current_aio_context();
req->cmd = cmd;
req->residual = req->cmd.xfer;
@@ -964,7 +1011,10 @@ static void scsi_req_enqueue_internal(SCSIRequest *req)
req->sg = NULL;
}
req->enqueued = true;
QTAILQ_INSERT_TAIL(&req->dev->requests, req, next);
WITH_QEMU_LOCK_GUARD(&req->dev->requests_lock) {
QTAILQ_INSERT_TAIL(&req->dev->requests, req, next);
}
}
int32_t scsi_req_enqueue(SCSIRequest *req)
@@ -984,7 +1034,9 @@ static void scsi_req_dequeue(SCSIRequest *req)
trace_scsi_req_dequeue(req->dev->id, req->lun, req->tag);
req->retry = false;
if (req->enqueued) {
QTAILQ_REMOVE(&req->dev->requests, req, next);
WITH_QEMU_LOCK_GUARD(&req->dev->requests_lock) {
QTAILQ_REMOVE(&req->dev->requests, req, next);
}
req->enqueued = false;
scsi_req_unref(req);
}
@@ -1961,8 +2013,7 @@ static void scsi_device_class_init(ObjectClass *klass, void *data)
static void scsi_dev_instance_init(Object *obj)
{
DeviceState *dev = DEVICE(obj);
SCSIDevice *s = SCSI_DEVICE(dev);
SCSIDevice *s = SCSI_DEVICE(obj);
device_add_bootindex_property(obj, &s->conf.bootindex,
"bootindex", NULL,
+8 -16
View File
@@ -106,7 +106,6 @@ struct SCSIDiskState {
uint64_t max_unmap_size;
uint64_t max_io_size;
uint32_t quirks;
QEMUBH *bh;
char *version;
char *serial;
char *vendor;
@@ -329,9 +328,8 @@ static void scsi_aio_complete(void *opaque, int ret)
SCSIDiskReq *r = (SCSIDiskReq *)opaque;
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
/* The request must only run in the BlockBackend's AioContext */
assert(blk_get_aio_context(s->qdev.conf.blk) ==
qemu_get_current_aio_context());
/* The request must run in its AioContext */
assert(r->req.ctx == qemu_get_current_aio_context());
assert(r->req.aiocb != NULL);
r->req.aiocb = NULL;
@@ -431,12 +429,10 @@ static void scsi_dma_complete(void *opaque, int ret)
static void scsi_read_complete_noio(SCSIDiskReq *r, int ret)
{
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
uint32_t n;
/* The request must only run in the BlockBackend's AioContext */
assert(blk_get_aio_context(s->qdev.conf.blk) ==
qemu_get_current_aio_context());
/* The request must run in its AioContext */
assert(r->req.ctx == qemu_get_current_aio_context());
assert(r->req.aiocb == NULL);
if (scsi_disk_req_check_error(r, ret, ret > 0)) {
@@ -488,8 +484,7 @@ static void scsi_do_read(SCSIDiskReq *r, int ret)
if (r->req.sg) {
dma_acct_start(s->qdev.conf.blk, &r->acct, r->req.sg, BLOCK_ACCT_READ);
r->req.residual -= r->req.sg->size;
r->req.aiocb = dma_blk_io(blk_get_aio_context(s->qdev.conf.blk),
r->req.sg, r->sector << BDRV_SECTOR_BITS,
r->req.aiocb = dma_blk_io(r->req.sg, r->sector << BDRV_SECTOR_BITS,
BDRV_SECTOR_SIZE,
sdc->dma_readv, r, scsi_dma_complete, r,
DMA_DIRECTION_FROM_DEVICE);
@@ -564,12 +559,10 @@ static void scsi_read_data(SCSIRequest *req)
static void scsi_write_complete_noio(SCSIDiskReq *r, int ret)
{
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
uint32_t n;
/* The request must only run in the BlockBackend's AioContext */
assert(blk_get_aio_context(s->qdev.conf.blk) ==
qemu_get_current_aio_context());
/* The request must run in its AioContext */
assert(r->req.ctx == qemu_get_current_aio_context());
assert (r->req.aiocb == NULL);
if (scsi_disk_req_check_error(r, ret, ret > 0)) {
@@ -651,8 +644,7 @@ static void scsi_write_data(SCSIRequest *req)
if (r->req.sg) {
dma_acct_start(s->qdev.conf.blk, &r->acct, r->req.sg, BLOCK_ACCT_WRITE);
r->req.residual -= r->req.sg->size;
r->req.aiocb = dma_blk_io(blk_get_aio_context(s->qdev.conf.blk),
r->req.sg, r->sector << BDRV_SECTOR_BITS,
r->req.aiocb = dma_blk_io(r->req.sg, r->sector << BDRV_SECTOR_BITS,
BDRV_SECTOR_SIZE,
sdc->dma_writev, r, scsi_dma_complete, r,
DMA_DIRECTION_TO_DEVICE);
+78 -25
View File
@@ -18,6 +18,7 @@
#include "system/block-backend.h"
#include "hw/scsi/scsi.h"
#include "scsi/constants.h"
#include "hw/virtio/iothread-vq-mapping.h"
#include "hw/virtio/virtio-bus.h"
/* Context: BQL held */
@@ -28,7 +29,14 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
if (vs->conf.iothread) {
if (vs->conf.iothread && vs->conf.iothread_vq_mapping_list) {
error_setg(errp,
"iothread and iothread-vq-mapping properties cannot be set "
"at the same time");
return;
}
if (vs->conf.iothread || vs->conf.iothread_vq_mapping_list) {
if (!k->set_guest_notifiers || !k->ioeventfd_assign) {
error_setg(errp,
"device is incompatible with iothread "
@@ -39,15 +47,64 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
error_setg(errp, "ioeventfd is required for iothread");
return;
}
s->ctx = iothread_get_aio_context(vs->conf.iothread);
} else {
if (!virtio_device_ioeventfd_enabled(vdev)) {
}
s->vq_aio_context = g_new(AioContext *, vs->conf.num_queues +
VIRTIO_SCSI_VQ_NUM_FIXED);
/*
* Handle the ctrl virtqueue in the main loop thread where device resets
* can be performed.
*/
s->vq_aio_context[0] = qemu_get_aio_context();
/*
* Handle the event virtqueue in the main loop thread where its no_poll
* behavior won't stop IOThread polling.
*/
s->vq_aio_context[1] = qemu_get_aio_context();
if (vs->conf.iothread_vq_mapping_list) {
if (!iothread_vq_mapping_apply(vs->conf.iothread_vq_mapping_list,
&s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED],
vs->conf.num_queues, errp)) {
g_free(s->vq_aio_context);
s->vq_aio_context = NULL;
return;
}
s->ctx = qemu_get_aio_context();
} else if (vs->conf.iothread) {
AioContext *ctx = iothread_get_aio_context(vs->conf.iothread);
for (uint16_t i = 0; i < vs->conf.num_queues; i++) {
s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i] = ctx;
}
/* Released in virtio_scsi_dataplane_cleanup() */
object_ref(OBJECT(vs->conf.iothread));
} else {
AioContext *ctx = qemu_get_aio_context();
for (unsigned i = 0; i < vs->conf.num_queues; i++) {
s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i] = ctx;
}
}
}
/* Context: BQL held */
void virtio_scsi_dataplane_cleanup(VirtIOSCSI *s)
{
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
if (vs->conf.iothread_vq_mapping_list) {
iothread_vq_mapping_cleanup(vs->conf.iothread_vq_mapping_list);
}
if (vs->conf.iothread) {
object_unref(OBJECT(vs->conf.iothread));
}
g_free(s->vq_aio_context);
s->vq_aio_context = NULL;
}
static int virtio_scsi_set_host_notifier(VirtIOSCSI *s, VirtQueue *vq, int n)
{
BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s)));
@@ -66,31 +123,20 @@ static int virtio_scsi_set_host_notifier(VirtIOSCSI *s, VirtQueue *vq, int n)
}
/* Context: BH in IOThread */
static void virtio_scsi_dataplane_stop_bh(void *opaque)
static void virtio_scsi_dataplane_stop_vq_bh(void *opaque)
{
VirtIOSCSI *s = opaque;
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
AioContext *ctx = qemu_get_current_aio_context();
VirtQueue *vq = opaque;
EventNotifier *host_notifier;
int i;
virtio_queue_aio_detach_host_notifier(vs->ctrl_vq, s->ctx);
host_notifier = virtio_queue_get_host_notifier(vs->ctrl_vq);
virtio_queue_aio_detach_host_notifier(vq, ctx);
host_notifier = virtio_queue_get_host_notifier(vq);
/*
* Test and clear notifier after disabling event, in case poll callback
* didn't have time to run.
*/
virtio_queue_host_notifier_read(host_notifier);
virtio_queue_aio_detach_host_notifier(vs->event_vq, s->ctx);
host_notifier = virtio_queue_get_host_notifier(vs->event_vq);
virtio_queue_host_notifier_read(host_notifier);
for (i = 0; i < vs->conf.num_queues; i++) {
virtio_queue_aio_detach_host_notifier(vs->cmd_vqs[i], s->ctx);
host_notifier = virtio_queue_get_host_notifier(vs->cmd_vqs[i]);
virtio_queue_host_notifier_read(host_notifier);
}
}
/* Context: BQL held */
@@ -154,11 +200,14 @@ int virtio_scsi_dataplane_start(VirtIODevice *vdev)
smp_wmb(); /* paired with aio_notify_accept() */
if (s->bus.drain_count == 0) {
virtio_queue_aio_attach_host_notifier(vs->ctrl_vq, s->ctx);
virtio_queue_aio_attach_host_notifier_no_poll(vs->event_vq, s->ctx);
virtio_queue_aio_attach_host_notifier(vs->ctrl_vq,
s->vq_aio_context[0]);
virtio_queue_aio_attach_host_notifier_no_poll(vs->event_vq,
s->vq_aio_context[1]);
for (i = 0; i < vs->conf.num_queues; i++) {
virtio_queue_aio_attach_host_notifier(vs->cmd_vqs[i], s->ctx);
AioContext *ctx = s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i];
virtio_queue_aio_attach_host_notifier(vs->cmd_vqs[i], ctx);
}
}
return 0;
@@ -207,7 +256,11 @@ void virtio_scsi_dataplane_stop(VirtIODevice *vdev)
s->dataplane_stopping = true;
if (s->bus.drain_count == 0) {
aio_wait_bh_oneshot(s->ctx, virtio_scsi_dataplane_stop_bh, s);
for (i = 0; i < vs->conf.num_queues + VIRTIO_SCSI_VQ_NUM_FIXED; i++) {
VirtQueue *vq = virtio_get_queue(&vs->parent_obj, i);
AioContext *ctx = s->vq_aio_context[i];
aio_wait_bh_oneshot(ctx, virtio_scsi_dataplane_stop_vq_bh, vq);
}
}
blk_drain_all(); /* ensure there are no in-flight requests */
+313 -229
View File
File diff suppressed because it is too large Load Diff
+131
View File
@@ -0,0 +1,131 @@
/*
* IOThread Virtqueue Mapping
*
* Copyright Red Hat, Inc
*
* SPDX-License-Identifier: GPL-2.0-only
*/
#include "qemu/osdep.h"
#include "system/iothread.h"
#include "hw/virtio/iothread-vq-mapping.h"
static bool
iothread_vq_mapping_validate(IOThreadVirtQueueMappingList *list, uint16_t
num_queues, Error **errp)
{
g_autofree unsigned long *vqs = bitmap_new(num_queues);
g_autoptr(GHashTable) iothreads =
g_hash_table_new(g_str_hash, g_str_equal);
for (IOThreadVirtQueueMappingList *node = list; node; node = node->next) {
const char *name = node->value->iothread;
uint16List *vq;
if (!iothread_by_id(name)) {
error_setg(errp, "IOThread \"%s\" object does not exist", name);
return false;
}
if (!g_hash_table_add(iothreads, (gpointer)name)) {
error_setg(errp,
"duplicate IOThread name \"%s\" in iothread-vq-mapping",
name);
return false;
}
if (node != list) {
if (!!node->value->vqs != !!list->value->vqs) {
error_setg(errp, "either all items in iothread-vq-mapping "
"must have vqs or none of them must have it");
return false;
}
}
for (vq = node->value->vqs; vq; vq = vq->next) {
if (vq->value >= num_queues) {
error_setg(errp, "vq index %u for IOThread \"%s\" must be "
"less than num_queues %u in iothread-vq-mapping",
vq->value, name, num_queues);
return false;
}
if (test_and_set_bit(vq->value, vqs)) {
error_setg(errp, "cannot assign vq %u to IOThread \"%s\" "
"because it is already assigned", vq->value, name);
return false;
}
}
}
if (list->value->vqs) {
for (uint16_t i = 0; i < num_queues; i++) {
if (!test_bit(i, vqs)) {
error_setg(errp,
"missing vq %u IOThread assignment in iothread-vq-mapping",
i);
return false;
}
}
}
return true;
}
bool iothread_vq_mapping_apply(
IOThreadVirtQueueMappingList *list,
AioContext **vq_aio_context,
uint16_t num_queues,
Error **errp)
{
IOThreadVirtQueueMappingList *node;
size_t num_iothreads = 0;
size_t cur_iothread = 0;
if (!iothread_vq_mapping_validate(list, num_queues, errp)) {
return false;
}
for (node = list; node; node = node->next) {
num_iothreads++;
}
for (node = list; node; node = node->next) {
IOThread *iothread = iothread_by_id(node->value->iothread);
AioContext *ctx = iothread_get_aio_context(iothread);
/* Released in virtio_blk_vq_aio_context_cleanup() */
object_ref(OBJECT(iothread));
if (node->value->vqs) {
uint16List *vq;
/* Explicit vq:IOThread assignment */
for (vq = node->value->vqs; vq; vq = vq->next) {
assert(vq->value < num_queues);
vq_aio_context[vq->value] = ctx;
}
} else {
/* Round-robin vq:IOThread assignment */
for (unsigned i = cur_iothread; i < num_queues;
i += num_iothreads) {
vq_aio_context[i] = ctx;
}
}
cur_iothread++;
}
return true;
}
void iothread_vq_mapping_cleanup(IOThreadVirtQueueMappingList *list)
{
IOThreadVirtQueueMappingList *node;
for (node = list; node; node = node->next) {
IOThread *iothread = iothread_by_id(node->value->iothread);
object_unref(OBJECT(iothread));
}
}
+1
View File
@@ -1,5 +1,6 @@
system_virtio_ss = ss.source_set()
system_virtio_ss.add(files('virtio-bus.c'))
system_virtio_ss.add(files('iothread-vq-mapping.c'))
system_virtio_ss.add(when: 'CONFIG_VIRTIO_PCI', if_true: files('virtio-pci.c'))
system_virtio_ss.add(when: 'CONFIG_VIRTIO_MMIO', if_true: files('virtio-mmio.c'))
system_virtio_ss.add(when: 'CONFIG_VIRTIO_CRYPTO', if_true: files('virtio-crypto.c'))
+4 -1
View File
@@ -123,6 +123,10 @@ struct BHListSlice {
typedef QSLIST_HEAD(, AioHandler) AioHandlerSList;
typedef struct AioPolledEvent {
int64_t ns; /* current polling time in nanoseconds */
} AioPolledEvent;
struct AioContext {
GSource source;
@@ -229,7 +233,6 @@ struct AioContext {
int poll_disable_cnt;
/* Polling mode parameters */
int64_t poll_ns; /* current polling time in nanoseconds */
int64_t poll_max_ns; /* maximum polling time in nanoseconds */
int64_t poll_grow; /* polling time growth factor */
int64_t poll_shrink; /* polling time shrink factor */
+17 -2
View File
@@ -17,6 +17,7 @@
#define QEMU_RAW_AIO_H
#include "block/aio.h"
#include "block/block-common.h"
#include "qemu/iov.h"
/* AIO request types */
@@ -58,11 +59,18 @@ void laio_cleanup(LinuxAioState *s);
/* laio_co_submit: submit I/O requests in the thread's current AioContext. */
int coroutine_fn laio_co_submit(int fd, uint64_t offset, QEMUIOVector *qiov,
int type, uint64_t dev_max_batch);
int type, BdrvRequestFlags flags,
uint64_t dev_max_batch);
bool laio_has_fdsync(int);
bool laio_has_fua(void);
void laio_detach_aio_context(LinuxAioState *s, AioContext *old_context);
void laio_attach_aio_context(LinuxAioState *s, AioContext *new_context);
#else
static inline bool laio_has_fua(void)
{
return false;
}
#endif
/* io_uring.c - Linux io_uring implementation */
#ifdef CONFIG_LINUX_IO_URING
@@ -71,9 +79,16 @@ void luring_cleanup(LuringState *s);
/* luring_co_submit: submit I/O requests in the thread's current AioContext. */
int coroutine_fn luring_co_submit(BlockDriverState *bs, int fd, uint64_t offset,
QEMUIOVector *qiov, int type);
QEMUIOVector *qiov, int type,
BdrvRequestFlags flags);
void luring_detach_aio_context(LuringState *s, AioContext *old_context);
void luring_attach_aio_context(LuringState *s, AioContext *new_context);
bool luring_has_fua(void);
#else
static inline bool luring_has_fua(void)
{
return false;
}
#endif
#ifdef _WIN32
+4 -4
View File
@@ -24,6 +24,7 @@ struct SCSIRequest {
SCSIBus *bus;
SCSIDevice *dev;
const SCSIReqOps *ops;
AioContext *ctx;
uint32_t refcount;
uint32_t tag;
uint32_t lun;
@@ -48,6 +49,8 @@ struct SCSIRequest {
bool dma_started;
BlockAIOCB *aiocb;
QEMUSGList *sg;
/* Protected by SCSIDevice->requests_lock */
QTAILQ_ENTRY(SCSIRequest) next;
};
@@ -76,10 +79,7 @@ struct SCSIDevice
uint8_t sense[SCSI_SENSE_BUF_SIZE];
uint32_t sense_len;
/*
* The requests list is only accessed from the AioContext that executes
* requests or from the main loop when IOThread processing is stopped.
*/
QemuMutex requests_lock; /* protects the requests list */
QTAILQ_HEAD(, SCSIRequest) requests;
uint32_t channel;
+45
View File
@@ -0,0 +1,45 @@
/*
* IOThread Virtqueue Mapping
*
* Copyright Red Hat, Inc
*
* SPDX-License-Identifier: GPL-2.0-only
*/
#ifndef HW_VIRTIO_IOTHREAD_VQ_MAPPING_H
#define HW_VIRTIO_IOTHREAD_VQ_MAPPING_H
#include "qapi/error.h"
#include "qapi/qapi-types-virtio.h"
/**
* iothread_vq_mapping_apply:
* @list: The mapping of virtqueues to IOThreads.
* @vq_aio_context: The array of AioContext pointers to fill in.
* @num_queues: The length of @vq_aio_context.
* @errp: If an error occurs, a pointer to the area to store the error.
*
* Fill in the AioContext for each virtqueue in the @vq_aio_context array given
* the iothread-vq-mapping parameter in @list.
*
* iothread_vq_mapping_cleanup() must be called to free IOThread object
* references after this function returns success.
*
* Returns: %true on success, %false on failure.
**/
bool iothread_vq_mapping_apply(
IOThreadVirtQueueMappingList *list,
AioContext **vq_aio_context,
uint16_t num_queues,
Error **errp);
/**
* iothread_vq_mapping_cleanup:
* @list: The mapping of virtqueues to IOThreads.
*
* Release IOThread object references that were acquired by
* iothread_vq_mapping_apply().
*/
void iothread_vq_mapping_cleanup(IOThreadVirtQueueMappingList *list);
#endif /* HW_VIRTIO_IOTHREAD_VQ_MAPPING_H */
+7 -8
View File
@@ -22,6 +22,7 @@
#include "hw/virtio/virtio.h"
#include "hw/scsi/scsi.h"
#include "chardev/char-fe.h"
#include "qapi/qapi-types-virtio.h"
#include "system/iothread.h"
#define TYPE_VIRTIO_SCSI_COMMON "virtio-scsi-common"
@@ -60,6 +61,7 @@ struct VirtIOSCSIConf {
CharBackend chardev;
uint32_t boot_tpgt;
IOThread *iothread;
IOThreadVirtQueueMappingList *iothread_vq_mapping_list;
};
struct VirtIOSCSI;
@@ -82,18 +84,14 @@ struct VirtIOSCSI {
SCSIBus bus;
int resetting; /* written from main loop thread, read from any thread */
QemuMutex event_lock; /* protects event_vq and events_dropped */
bool events_dropped;
/*
* TMFs deferred to main loop BH. These fields are protected by
* tmf_bh_lock.
*/
QemuMutex tmf_bh_lock;
QEMUBH *tmf_bh;
QTAILQ_HEAD(, VirtIOSCSIReq) tmf_bh_list;
QemuMutex ctrl_lock; /* protects ctrl_vq */
/* Fields for dataplane below */
AioContext *ctx; /* one iothread per virtio-scsi-pci for now */
AioContext **vq_aio_context; /* per-virtqueue AioContext pointer */
bool dataplane_started;
bool dataplane_starting;
@@ -111,6 +109,7 @@ void virtio_scsi_common_realize(DeviceState *dev,
void virtio_scsi_common_unrealize(DeviceState *dev);
void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp);
void virtio_scsi_dataplane_cleanup(VirtIOSCSI *s);
int virtio_scsi_dataplane_start(VirtIODevice *s);
void virtio_scsi_dataplane_stop(VirtIODevice *s);

Some files were not shown because too many files have changed in this diff Show More