diff --git a/kvm-blkdebug-Add-delay-ns-option.patch b/kvm-blkdebug-Add-delay-ns-option.patch new file mode 100644 index 0000000..b71e62f --- /dev/null +++ b/kvm-blkdebug-Add-delay-ns-option.patch @@ -0,0 +1,121 @@ +From b37120279fbb5615558dce392365527aae8169ea Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Tue, 21 Apr 2026 18:11:26 +0200 +Subject: [PATCH 1/8] blkdebug: Add 'delay-ns' option + +RH-Author: Kevin Wolf +RH-MergeRequest: 562: ide: Fix deadlock between TRIM and drain [10.2.z] +RH-Jira: RHEL-190702 +RH-Acked-by: German Maglione +RH-Acked-by: Miroslav Rezanina +RH-Commit: [1/8] c07ed8a9897c2f8cd8efa5049ed100a1e42b9123 (kmwolf/rhel-qemu-kvm) + +Sometimes reproducing a problem for debugging involves slow I/O, so +let's add something to blkdebug to make I/O slow when we need it. This +can be used either together with an error so that the request fails +after the delay, or with errno=0, which allows the request to succeed +after the delay. + +Signed-off-by: Kevin Wolf +Message-ID: <20260421161132.99878-2-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit d5e4090177ad382e01084a1594a1a60a69f4c1cd) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + block/blkdebug.c | 15 ++++++++++++++- + qapi/block-core.json | 4 ++++ + 2 files changed, 18 insertions(+), 1 deletion(-) + +diff --git a/block/blkdebug.c b/block/blkdebug.c +index c54aee0c84..8954fc2977 100644 +--- a/block/blkdebug.c ++++ b/block/blkdebug.c +@@ -95,6 +95,7 @@ typedef struct BlkdebugRule { + int immediately; + int once; + int64_t offset; ++ int64_t delay_ns; + } inject; + struct { + int new_state; +@@ -144,6 +145,10 @@ static QemuOptsList inject_error_opts = { + .name = "immediately", + .type = QEMU_OPT_BOOL, + }, ++ { ++ .name = "delay-ns", ++ .type = QEMU_OPT_NUMBER, ++ }, + { /* end of list */ } + }, + }; +@@ -216,6 +221,8 @@ static int add_rule(void *opaque, QemuOpts *opts, Error **errp) + rule->options.inject.once = qemu_opt_get_bool(opts, "once", 0); + rule->options.inject.immediately = + qemu_opt_get_bool(opts, "immediately", 0); ++ rule->options.inject.delay_ns = ++ qemu_opt_get_number(opts, "delay-ns", 0); + sector = qemu_opt_get_number(opts, "sector", -1); + rule->options.inject.offset = + sector == -1 ? -1 : sector * BDRV_SECTOR_SIZE; +@@ -594,6 +601,7 @@ static int coroutine_fn rule_check(BlockDriverState *bs, uint64_t offset, + BlkdebugRule *rule = NULL; + int error; + bool immediately; ++ int64_t delay_ns; + + qemu_mutex_lock(&s->lock); + QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) { +@@ -608,13 +616,14 @@ static int coroutine_fn rule_check(BlockDriverState *bs, uint64_t offset, + } + } + +- if (!rule || !rule->options.inject.error) { ++ if (!rule) { + qemu_mutex_unlock(&s->lock); + return 0; + } + + immediately = rule->options.inject.immediately; + error = rule->options.inject.error; ++ delay_ns = rule->options.inject.delay_ns; + + if (rule->options.inject.once) { + QSIMPLEQ_REMOVE(&s->active_rules, rule, BlkdebugRule, active_next); +@@ -622,6 +631,10 @@ static int coroutine_fn rule_check(BlockDriverState *bs, uint64_t offset, + } + + qemu_mutex_unlock(&s->lock); ++ ++ if (delay_ns) { ++ qemu_co_sleep_ns(QEMU_CLOCK_REALTIME, delay_ns); ++ } + if (!immediately) { + aio_co_schedule(qemu_get_current_aio_context(), qemu_coroutine_self()); + qemu_coroutine_yield(); +diff --git a/qapi/block-core.json b/qapi/block-core.json +index 0236936139..6cf8c1b9c8 100644 +--- a/qapi/block-core.json ++++ b/qapi/block-core.json +@@ -3909,6 +3909,9 @@ + # + # @errno: error identifier (errno) to be returned; defaults to EIO + # ++# @delay-ns: request delay before completion in nanoseconds ++# (default: 0, since: 11.1) ++# + # @sector: specifies the sector index which has to be affected in + # order to actually trigger the event; defaults to "any sector" + # +@@ -3924,6 +3927,7 @@ + '*state': 'int', + '*iotype': 'BlkdebugIOType', + '*errno': 'int', ++ '*delay-ns': 'int', + '*sector': 'int', + '*once': 'bool', + '*immediately': 'bool' } } +-- +2.52.0 + diff --git a/kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch b/kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch new file mode 100644 index 0000000..3461d36 --- /dev/null +++ b/kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch @@ -0,0 +1,203 @@ +From bd929a4dbcd20cb9f6314c867726b59e18254994 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Tue, 21 Apr 2026 18:11:27 +0200 +Subject: [PATCH 2/8] block: Add blk_co_start/end_request() and + BDRV_REQ_NO_QUEUE + +RH-Author: Kevin Wolf +RH-MergeRequest: 562: ide: Fix deadlock between TRIM and drain [10.2.z] +RH-Jira: RHEL-190702 +RH-Acked-by: German Maglione +RH-Acked-by: Miroslav Rezanina +RH-Commit: [2/8] 12df3f715235d07f3b45fa3ee56e5e8aa45e866c (kmwolf/rhel-qemu-kvm) + +If a device uses blk_inc/dec_in_flight() in order to build macro +operations that involve multiple requests for the block layer and that +need to be completed as a unit before the BlockBackend can be considered +drained, it sets the stage for a deadlock: When a drain is requested, +the inner request at the BlockBackend level will be queued in +blk_wait_while_drained() and wait until the drained section ends, but at +the same time, drain_begin can only return if the whole macro operation +at the device level has completed. + +Introduce a new interface to allow implementing the logic correctly: +Instead of queueing individual requests, blk_co_start_request() calls +blk_wait_while_drained() once at the beginning. The individual requests +must then set BDRV_REQ_NO_QUEUE to avoid being queued and running into +the deadlock; being wrapped in blk_co_start/end_request() makes sure +that drain_begin waits for them and they don't sneak in when the +BlockBackend is supposed to already be quiescent. + +Signed-off-by: Kevin Wolf +Message-ID: <20260421161132.99878-3-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit 34a67637767d3ed1ac813c44effe827bbfba5996) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + block/block-backend.c | 38 +++++++++++++++++++++++-------- + include/block/block-common.h | 11 ++++++++- + include/system/block-backend-io.h | 2 ++ + 3 files changed, 41 insertions(+), 10 deletions(-) + +diff --git a/block/block-backend.c b/block/block-backend.c +index d6df369188..a0f4c841da 100644 +--- a/block/block-backend.c ++++ b/block/block-backend.c +@@ -82,6 +82,7 @@ struct BlockBackend { + QemuMutex queued_requests_lock; /* protects queued_requests */ + CoQueue queued_requests; + bool disable_request_queuing; /* atomic */ ++ int start_request_count; /* atomic */ + + VMChangeStateEntry *vmsh; + bool force_allow_inactivate; +@@ -1306,10 +1307,16 @@ bool blk_in_drain(BlockBackend *blk) + } + + /* To be called between exactly one pair of blk_inc/dec_in_flight() */ +-static void coroutine_fn blk_wait_while_drained(BlockBackend *blk) ++static void coroutine_fn blk_wait_while_drained(BlockBackend *blk, ++ BdrvRequestFlags flags) + { + assert(blk->in_flight > 0); + ++ if (flags & BDRV_REQ_NO_QUEUE) { ++ assert(qatomic_read(&blk->start_request_count)); ++ return; ++ } ++ + if (qatomic_read(&blk->quiesce_counter) && + !qatomic_read(&blk->disable_request_queuing)) { + /* +@@ -1335,7 +1342,7 @@ blk_co_do_preadv_part(BlockBackend *blk, int64_t offset, int64_t bytes, + BlockDriverState *bs; + IO_CODE(); + +- blk_wait_while_drained(blk); ++ blk_wait_while_drained(blk, flags); + GRAPH_RDLOCK_GUARD(); + + /* Call blk_bs() only after waiting, the graph may have changed */ +@@ -1410,7 +1417,7 @@ blk_co_do_pwritev_part(BlockBackend *blk, int64_t offset, int64_t bytes, + BlockDriverState *bs; + IO_CODE(); + +- blk_wait_while_drained(blk); ++ blk_wait_while_drained(blk, flags); + GRAPH_RDLOCK_GUARD(); + + /* Call blk_bs() only after waiting, the graph may have changed */ +@@ -1523,6 +1530,19 @@ void blk_dec_in_flight(BlockBackend *blk) + aio_wait_kick(); + } + ++void coroutine_fn blk_co_start_request(BlockBackend *blk) ++{ ++ blk_inc_in_flight(blk); ++ blk_wait_while_drained(blk, 0); ++ qatomic_inc(&blk->start_request_count); ++} ++ ++void blk_end_request(BlockBackend *blk) ++{ ++ qatomic_dec(&blk->start_request_count); ++ blk_dec_in_flight(blk); ++} ++ + static void error_callback_bh(void *opaque) + { + struct BlockBackendAIOCB *acb = opaque; +@@ -1741,7 +1761,7 @@ blk_co_do_ioctl(BlockBackend *blk, unsigned long int req, void *buf) + { + IO_CODE(); + +- blk_wait_while_drained(blk); ++ blk_wait_while_drained(blk, 0); + GRAPH_RDLOCK_GUARD(); + + if (!blk_co_is_available(blk)) { +@@ -1788,7 +1808,7 @@ blk_co_do_pdiscard(BlockBackend *blk, int64_t offset, int64_t bytes) + int ret; + IO_CODE(); + +- blk_wait_while_drained(blk); ++ blk_wait_while_drained(blk, 0); + GRAPH_RDLOCK_GUARD(); + + ret = blk_check_byte_request(blk, offset, bytes); +@@ -1834,7 +1854,7 @@ int coroutine_fn blk_co_pdiscard(BlockBackend *blk, int64_t offset, + static int coroutine_fn blk_co_do_flush(BlockBackend *blk) + { + IO_CODE(); +- blk_wait_while_drained(blk); ++ blk_wait_while_drained(blk, 0); + GRAPH_RDLOCK_GUARD(); + + if (!blk_co_is_available(blk)) { +@@ -2009,7 +2029,7 @@ int coroutine_fn blk_co_zone_report(BlockBackend *blk, int64_t offset, + IO_CODE(); + + blk_inc_in_flight(blk); /* increase before waiting */ +- blk_wait_while_drained(blk); ++ blk_wait_while_drained(blk, 0); + GRAPH_RDLOCK_GUARD(); + if (!blk_is_available(blk)) { + blk_dec_in_flight(blk); +@@ -2034,7 +2054,7 @@ int coroutine_fn blk_co_zone_mgmt(BlockBackend *blk, BlockZoneOp op, + IO_CODE(); + + blk_inc_in_flight(blk); +- blk_wait_while_drained(blk); ++ blk_wait_while_drained(blk, 0); + GRAPH_RDLOCK_GUARD(); + + ret = blk_check_byte_request(blk, offset, len); +@@ -2058,7 +2078,7 @@ int coroutine_fn blk_co_zone_append(BlockBackend *blk, int64_t *offset, + IO_CODE(); + + blk_inc_in_flight(blk); +- blk_wait_while_drained(blk); ++ blk_wait_while_drained(blk, flags); + GRAPH_RDLOCK_GUARD(); + if (!blk_is_available(blk)) { + blk_dec_in_flight(blk); +diff --git a/include/block/block-common.h b/include/block/block-common.h +index c8c626daea..895ea17541 100644 +--- a/include/block/block-common.h ++++ b/include/block/block-common.h +@@ -215,8 +215,17 @@ typedef enum { + */ + BDRV_REQ_NO_WAIT = 0x400, + ++ /* ++ * Used between blk_co_start_request() and blk_end_request() to avoid ++ * that the request waits in a drained BlockBackend until the drained ++ * section ends. Waiting would cause a deadlock because drain waits for ++ * blk_end_request() to be called, but the request never completes ++ * because it waits for the drain to end. ++ */ ++ BDRV_REQ_NO_QUEUE = 0x800, ++ + /* Mask of valid flags */ +- BDRV_REQ_MASK = 0x7ff, ++ BDRV_REQ_MASK = 0xfff, + } BdrvRequestFlags; + + #define BDRV_O_NO_SHARE 0x0001 /* don't share permissions */ +diff --git a/include/system/block-backend-io.h b/include/system/block-backend-io.h +index ba8dfcc7d0..59841e04a8 100644 +--- a/include/system/block-backend-io.h ++++ b/include/system/block-backend-io.h +@@ -71,6 +71,8 @@ BlockAIOCB *blk_aio_ioctl(BlockBackend *blk, unsigned long int req, void *buf, + + void blk_inc_in_flight(BlockBackend *blk); + void blk_dec_in_flight(BlockBackend *blk); ++void coroutine_fn blk_co_start_request(BlockBackend *blk); ++void blk_end_request(BlockBackend *blk); + + bool coroutine_fn GRAPH_RDLOCK blk_co_is_inserted(BlockBackend *blk); + bool co_wrapper_mixed_bdrv_rdlock blk_is_inserted(BlockBackend *blk); +-- +2.52.0 + diff --git a/kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch b/kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch new file mode 100644 index 0000000..264ab7e --- /dev/null +++ b/kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch @@ -0,0 +1,174 @@ +From f834c37186b266b515c3f7e6b15a5e87e153f699 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Tue, 21 Apr 2026 18:11:28 +0200 +Subject: [PATCH 3/8] block: Add flags parameter to blk_*_pdiscard() + +RH-Author: Kevin Wolf +RH-MergeRequest: 562: ide: Fix deadlock between TRIM and drain [10.2.z] +RH-Jira: RHEL-190702 +RH-Acked-by: German Maglione +RH-Acked-by: Miroslav Rezanina +RH-Commit: [3/8] 6ce7ccfc577d41eda1283271dd5ee7913a1266c5 (kmwolf/rhel-qemu-kvm) + +All existing callers pass 0, but we need a way to pass BDRV_REQ_NO_QUEUE +for discard requests. + +Signed-off-by: Kevin Wolf +Message-ID: <20260421161132.99878-4-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit 53074ba0330ae8831abbae2521c012e1d9072ed3) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + block/block-backend.c | 11 ++++++----- + block/export/virtio-blk-handler.c | 2 +- + block/mirror.c | 4 ++-- + include/system/block-backend-io.h | 4 ++-- + nbd/server.c | 2 +- + qemu-io-cmds.c | 2 +- + tests/unit/test-block-iothread.c | 4 ++-- + 7 files changed, 15 insertions(+), 14 deletions(-) + +diff --git a/block/block-backend.c b/block/block-backend.c +index a0f4c841da..d97b26b743 100644 +--- a/block/block-backend.c ++++ b/block/block-backend.c +@@ -1803,12 +1803,13 @@ BlockAIOCB *blk_aio_ioctl(BlockBackend *blk, unsigned long int req, void *buf, + + /* To be called between exactly one pair of blk_inc/dec_in_flight() */ + static int coroutine_fn +-blk_co_do_pdiscard(BlockBackend *blk, int64_t offset, int64_t bytes) ++blk_co_do_pdiscard(BlockBackend *blk, int64_t offset, int64_t bytes, ++ BdrvRequestFlags flags) + { + int ret; + IO_CODE(); + +- blk_wait_while_drained(blk, 0); ++ blk_wait_while_drained(blk, flags); + GRAPH_RDLOCK_GUARD(); + + ret = blk_check_byte_request(blk, offset, bytes); +@@ -1824,7 +1825,7 @@ static void coroutine_fn blk_aio_pdiscard_entry(void *opaque) + BlkAioEmAIOCB *acb = opaque; + BlkRwCo *rwco = &acb->rwco; + +- rwco->ret = blk_co_do_pdiscard(rwco->blk, rwco->offset, acb->bytes); ++ rwco->ret = blk_co_do_pdiscard(rwco->blk, rwco->offset, acb->bytes, 0); + blk_aio_complete(acb); + } + +@@ -1838,13 +1839,13 @@ BlockAIOCB *blk_aio_pdiscard(BlockBackend *blk, + } + + int coroutine_fn blk_co_pdiscard(BlockBackend *blk, int64_t offset, +- int64_t bytes) ++ int64_t bytes, BdrvRequestFlags flags) + { + int ret; + IO_OR_GS_CODE(); + + blk_inc_in_flight(blk); +- ret = blk_co_do_pdiscard(blk, offset, bytes); ++ ret = blk_co_do_pdiscard(blk, offset, bytes, flags); + blk_dec_in_flight(blk); + + return ret; +diff --git a/block/export/virtio-blk-handler.c b/block/export/virtio-blk-handler.c +index bc1cec6757..b82baae553 100644 +--- a/block/export/virtio-blk-handler.c ++++ b/block/export/virtio-blk-handler.c +@@ -121,7 +121,7 @@ virtio_blk_discard_write_zeroes(VirtioBlkHandler *handler, struct iovec *iov, + } + + if (blk_co_pdiscard(blk, sector << VIRTIO_BLK_SECTOR_BITS, +- bytes) == 0) { ++ bytes, 0) == 0) { + return VIRTIO_BLK_S_OK; + } + } +diff --git a/block/mirror.c b/block/mirror.c +index f01be99b55..c87f1e205b 100644 +--- a/block/mirror.c ++++ b/block/mirror.c +@@ -454,7 +454,7 @@ static void coroutine_fn mirror_co_discard(void *opaque) + *op->bytes_handled = op->bytes; + op->is_in_flight = true; + +- ret = blk_co_pdiscard(op->s->target, op->offset, op->bytes); ++ ret = blk_co_pdiscard(op->s->target, op->offset, op->bytes, 0); + mirror_write_complete(op, ret); + } + +@@ -1527,7 +1527,7 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method, + zero_bitmap_end - zero_bitmap_offset); + } + assert(!qiov); +- ret = blk_co_pdiscard(job->target, offset, bytes); ++ ret = blk_co_pdiscard(job->target, offset, bytes, 0); + break; + + default: +diff --git a/include/system/block-backend-io.h b/include/system/block-backend-io.h +index 59841e04a8..91c48299b7 100644 +--- a/include/system/block-backend-io.h ++++ b/include/system/block-backend-io.h +@@ -217,9 +217,9 @@ int co_wrapper_mixed blk_zone_append(BlockBackend *blk, int64_t *offset, + BdrvRequestFlags flags); + + int co_wrapper_mixed blk_pdiscard(BlockBackend *blk, int64_t offset, +- int64_t bytes); ++ int64_t bytes, BdrvRequestFlags flags); + int coroutine_fn blk_co_pdiscard(BlockBackend *blk, int64_t offset, +- int64_t bytes); ++ int64_t bytes, BdrvRequestFlags flags); + + int co_wrapper_mixed blk_flush(BlockBackend *blk); + int coroutine_fn blk_co_flush(BlockBackend *blk); +diff --git a/nbd/server.c b/nbd/server.c +index d242be9811..c72dee7a3b 100644 +--- a/nbd/server.c ++++ b/nbd/server.c +@@ -2982,7 +2982,7 @@ static coroutine_fn int nbd_handle_request(NBDClient *client, + "flush failed", errp); + + case NBD_CMD_TRIM: +- ret = blk_co_pdiscard(exp->common.blk, request->from, request->len); ++ ret = blk_co_pdiscard(exp->common.blk, request->from, request->len, 0); + if (ret >= 0 && request->flags & NBD_CMD_FLAG_FUA) { + ret = blk_co_flush(exp->common.blk); + } +diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c +index 13e0330162..f6d077908f 100644 +--- a/qemu-io-cmds.c ++++ b/qemu-io-cmds.c +@@ -2201,7 +2201,7 @@ static int discard_f(BlockBackend *blk, int argc, char **argv) + } + + clock_gettime(CLOCK_MONOTONIC, &t1); +- ret = blk_pdiscard(blk, offset, bytes); ++ ret = blk_pdiscard(blk, offset, bytes, 0); + clock_gettime(CLOCK_MONOTONIC, &t2); + + if (ret < 0) { +diff --git a/tests/unit/test-block-iothread.c b/tests/unit/test-block-iothread.c +index e26b3be593..5273ff235a 100644 +--- a/tests/unit/test-block-iothread.c ++++ b/tests/unit/test-block-iothread.c +@@ -270,11 +270,11 @@ static void test_sync_op_blk_pdiscard(BlockBackend *blk) + int ret; + + /* Early success: UNMAP not supported */ +- ret = blk_pdiscard(blk, 0, 512); ++ ret = blk_pdiscard(blk, 0, 512, 0); + g_assert_cmpint(ret, ==, 0); + + /* Early error: Negative offset */ +- ret = blk_pdiscard(blk, -2, 512); ++ ret = blk_pdiscard(blk, -2, 512, 0); + g_assert_cmpint(ret, ==, -EIO); + } + +-- +2.52.0 + diff --git a/kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch b/kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch new file mode 100644 index 0000000..01db191 --- /dev/null +++ b/kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch @@ -0,0 +1,103 @@ +From c92790705ea64e29191ad00bd61e1ad13011c6b4 Mon Sep 17 00:00:00 2001 +From: Stefan Hajnoczi +Date: Tue, 7 Oct 2025 10:16:59 -0400 +Subject: [PATCH 2/3] block: use pwrite_zeroes_alignment when writing first + sector + +RH-Author: Kevin Wolf +RH-MergeRequest: 565: block: Fix image creation on storage with 4k sector size +RH-Jira: RHEL-207389 +RH-Acked-by: Hanna Czenczek +RH-Acked-by: German Maglione +RH-Acked-by: Stefan Hajnoczi +RH-Commit: [2/3] 1854afab367accbe5a4ffdcbb7cea180595b6bdc (kmwolf/rhel-qemu-kvm) + +Since commit 5634622bcb33 ("file-posix: allow BLKZEROOUT with -t +writeback"), qemu-img create errors out on a Linux loop block device +with a 4 KB sector size: + + # dd if=/dev/zero of=blockfile bs=1M count=1024 + # losetup --sector-size 4096 /dev/loop0 blockfile + # qemu-img create -f raw /dev/loop0 1G + Formatting '/dev/loop0', fmt=raw size=1073741824 + qemu-img: /dev/loop0: Failed to clear the new image's first sector: Invalid argument + +Use the pwrite_zeroes_alignment block limit to avoid misaligned +fallocate(2) or ioctl(BLKZEROOUT) in the block/file-posix.c block +driver. + +Cc: qemu-stable@nongnu.org +Fixes: 5634622bcb33 ("file-posix: allow BLKZEROOUT with -t writeback") +Reported-by: Jean-Louis Dupond +Buglink: https://gitlab.com/qemu-project/qemu/-/issues/3127 +Reviewed-by: Vladimir Sementsov-Ogievskiy +Signed-off-by: Stefan Hajnoczi +Message-ID: <20251007141700.71891-3-stefanha@redhat.com> +Tested-by: Fiona Ebner +Reviewed-by: Fiona Ebner +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit d704a13d2c025779bc91d04e127427347ddcf3b3) +Signed-off-by: Kevin Wolf +--- + block.c | 3 ++- + block/block-backend.c | 11 +++++++++++ + include/system/block-backend-io.h | 1 + + 3 files changed, 14 insertions(+), 1 deletion(-) + +diff --git a/block.c b/block.c +index 8848e9a7ed..be77e03904 100644 +--- a/block.c ++++ b/block.c +@@ -606,12 +606,13 @@ create_file_fallback_zero_first_sector(BlockBackend *blk, + int64_t current_size, + Error **errp) + { ++ uint32_t alignment = blk_get_pwrite_zeroes_alignment(blk); + int64_t bytes_to_clear; + int ret; + + GLOBAL_STATE_CODE(); + +- bytes_to_clear = MIN(current_size, BDRV_SECTOR_SIZE); ++ bytes_to_clear = MIN(current_size, MAX(BDRV_SECTOR_SIZE, alignment)); + if (bytes_to_clear) { + ret = blk_co_pwrite_zeroes(blk, 0, bytes_to_clear, BDRV_REQ_MAY_UNMAP); + if (ret < 0) { +diff --git a/block/block-backend.c b/block/block-backend.c +index d97b26b743..850f2ecec2 100644 +--- a/block/block-backend.c ++++ b/block/block-backend.c +@@ -2326,6 +2326,17 @@ uint32_t blk_get_request_alignment(BlockBackend *blk) + return bs ? bs->bl.request_alignment : BDRV_SECTOR_SIZE; + } + ++/* Returns the optimal write zeroes alignment, in bytes; guaranteed nonzero */ ++uint32_t blk_get_pwrite_zeroes_alignment(BlockBackend *blk) ++{ ++ BlockDriverState *bs = blk_bs(blk); ++ IO_CODE(); ++ if (!bs) { ++ return BDRV_SECTOR_SIZE; ++ } ++ return bs->bl.pwrite_zeroes_alignment ?: bs->bl.request_alignment; ++} ++ + /* Returns the maximum hardware transfer length, in bytes; guaranteed nonzero */ + uint64_t blk_get_max_hw_transfer(BlockBackend *blk) + { +diff --git a/include/system/block-backend-io.h b/include/system/block-backend-io.h +index 91c48299b7..fd84723d9d 100644 +--- a/include/system/block-backend-io.h ++++ b/include/system/block-backend-io.h +@@ -118,6 +118,7 @@ BlockAIOCB *blk_abort_aio_request(BlockBackend *blk, + void *opaque, int ret); + + uint32_t blk_get_request_alignment(BlockBackend *blk); ++uint32_t blk_get_pwrite_zeroes_alignment(BlockBackend *blk); + uint32_t blk_get_max_transfer(BlockBackend *blk); + uint64_t blk_get_max_hw_transfer(BlockBackend *blk); + +-- +2.52.0 + diff --git a/kvm-file-posix-populate-pwrite_zeroes_alignment.patch b/kvm-file-posix-populate-pwrite_zeroes_alignment.patch new file mode 100644 index 0000000..03dd6d0 --- /dev/null +++ b/kvm-file-posix-populate-pwrite_zeroes_alignment.patch @@ -0,0 +1,65 @@ +From 9aa4988b7506ff56f7c02ac0794f29b0766adb6f Mon Sep 17 00:00:00 2001 +From: Stefan Hajnoczi +Date: Tue, 7 Oct 2025 10:16:58 -0400 +Subject: [PATCH 1/3] file-posix: populate pwrite_zeroes_alignment + +RH-Author: Kevin Wolf +RH-MergeRequest: 565: block: Fix image creation on storage with 4k sector size +RH-Jira: RHEL-207389 +RH-Acked-by: Hanna Czenczek +RH-Acked-by: German Maglione +RH-Acked-by: Stefan Hajnoczi +RH-Commit: [1/3] dd57fecd1f1d62be600451c47efb061925e89154 (kmwolf/rhel-qemu-kvm) + +Linux block devices require write zeroes alignment whereas files do not. + +It may come as a surprise that block devices opened in buffered I/O mode +require the alignment for write zeroes requests although normal +read/write requests do not. + +Therefore it is necessary to populate the pwrite_zeroes_alignment field. + +Cc: qemu-stable@nongnu.org +Signed-off-by: Stefan Hajnoczi +Message-ID: <20251007141700.71891-2-stefanha@redhat.com> +Reviewed-by: Vladimir Sementsov-Ogievskiy +Tested-by: Fiona Ebner +Reviewed-by: Fiona Ebner +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit 98e788b91ad037193b1fb375561ef7e0fef3c2fd) +Signed-off-by: Kevin Wolf +--- + block/file-posix.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/block/file-posix.c b/block/file-posix.c +index ffca37130b..0129413273 100644 +--- a/block/file-posix.c ++++ b/block/file-posix.c +@@ -1607,6 +1607,22 @@ static void raw_refresh_limits(BlockDriverState *bs, Error **errp) + + bs->bl.pdiscard_alignment = dalign; + } ++ ++#ifdef __linux__ ++ /* ++ * Linux requires logical block size alignment for write zeroes even ++ * when normal reads/writes do not require alignment. ++ */ ++ if (!s->needs_alignment) { ++ ret = probe_logical_blocksize(s->fd, ++ &bs->bl.pwrite_zeroes_alignment); ++ if (ret < 0) { ++ error_setg_errno(errp, -ret, ++ "Failed to probe logical block size"); ++ return; ++ } ++ } ++#endif /* __linux__ */ + } + + raw_refresh_zoned_limits(bs, &st, errp); +-- +2.52.0 + diff --git a/kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch b/kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch new file mode 100644 index 0000000..150afd1 --- /dev/null +++ b/kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch @@ -0,0 +1,163 @@ +From e08abed8efc20906c2aeabeadd36f4f3e3dd3142 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Tue, 21 Apr 2026 18:11:30 +0200 +Subject: [PATCH 5/8] ide: Clean up ide_trim_co_entry() to be idiomatic + coroutine code + +RH-Author: Kevin Wolf +RH-MergeRequest: 562: ide: Fix deadlock between TRIM and drain [10.2.z] +RH-Jira: RHEL-190702 +RH-Acked-by: German Maglione +RH-Acked-by: Miroslav Rezanina +RH-Commit: [5/8] 8ecbfb819ce278daf17904c328464a474ba9c4bc (kmwolf/rhel-qemu-kvm) + +The previous commit did a minimal conversion of the callback based state +machine for TRIM to a coroutine in order to fix a bug. Refactor it to +actually look like normal coroutine based code, which improves its +readability. + +Signed-off-by: Kevin Wolf +Message-ID: <20260421161132.99878-6-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit c1c71a7e167fdabaa9827d00c0be3aeafebdd921) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + hw/ide/core.c | 87 +++++++++++++++++++++++---------------------------- + 1 file changed, 39 insertions(+), 48 deletions(-) + +diff --git a/hw/ide/core.c b/hw/ide/core.c +index 82c3ada14a..8b9f06547b 100644 +--- a/hw/ide/core.c ++++ b/hw/ide/core.c +@@ -420,18 +420,15 @@ typedef struct TrimAIOCB { + QEMUBH *bh; + int ret; + QEMUIOVector *qiov; +- int i, j; ++ bool canceled; + } TrimAIOCB; + + static void trim_aio_cancel(BlockAIOCB *acb) + { + TrimAIOCB *iocb = container_of(acb, TrimAIOCB, common); + +- /* Exit the loop so ide_issue_trim_cb will not continue */ +- iocb->j = iocb->qiov->niov - 1; +- iocb->i = (iocb->qiov->iov[iocb->j].iov_len / 8) - 1; +- +- iocb->ret = -ECANCELED; ++ /* Exit the loop so ide_trim_co_entry will not continue */ ++ iocb->canceled = true; + } + + static const AIOCBInfo trim_aiocb_info = { +@@ -458,60 +455,55 @@ static void coroutine_fn ide_trim_co_entry(void *opaque) + { + TrimAIOCB *iocb = opaque; + IDEState *s = iocb->s; +- int ret = 0; ++ int i, j; ++ int ret; + + /* Paired with blk_end_request in ide_trim_bh_cb() */ + blk_co_start_request(s->blk); + +-loop: +- if (iocb->i >= 0) { +- if (ret >= 0) { +- block_acct_done(blk_get_stats(s->blk), &s->acct); +- } else { +- block_acct_failed(blk_get_stats(s->blk), &s->acct); +- } +- } ++ for (j = 0; j < iocb->qiov->niov; j++) { ++ for (i = 0; i < iocb->qiov->iov[j].iov_len / 8; i++) { ++ uint64_t *buffer = iocb->qiov->iov[j].iov_base; + +- if (ret >= 0) { +- while (iocb->j < iocb->qiov->niov) { +- int j = iocb->j; +- while (++iocb->i < iocb->qiov->iov[j].iov_len / 8) { +- int i = iocb->i; +- uint64_t *buffer = iocb->qiov->iov[j].iov_base; ++ /* 6-byte LBA + 2-byte range per entry */ ++ uint64_t entry = le64_to_cpu(buffer[i]); ++ uint64_t sector = entry & 0x0000ffffffffffffULL; ++ uint16_t count = entry >> 48; + +- /* 6-byte LBA + 2-byte range per entry */ +- uint64_t entry = le64_to_cpu(buffer[i]); +- uint64_t sector = entry & 0x0000ffffffffffffULL; +- uint16_t count = entry >> 48; ++ if (count == 0) { ++ continue; ++ } + +- if (count == 0) { +- continue; +- } ++ if (iocb->canceled) { ++ iocb->ret = -ECANCELED; ++ goto done; ++ } + +- if (!ide_sect_range_ok(s, sector, count)) { +- block_acct_invalid(blk_get_stats(s->blk), BLOCK_ACCT_UNMAP); +- iocb->ret = -EINVAL; +- goto done; +- } ++ if (!ide_sect_range_ok(s, sector, count)) { ++ block_acct_invalid(blk_get_stats(s->blk), BLOCK_ACCT_UNMAP); ++ iocb->ret = -EINVAL; ++ goto done; ++ } + +- block_acct_start(blk_get_stats(s->blk), &s->acct, +- count << BDRV_SECTOR_BITS, BLOCK_ACCT_UNMAP); ++ block_acct_start(blk_get_stats(s->blk), &s->acct, ++ count << BDRV_SECTOR_BITS, BLOCK_ACCT_UNMAP); + +- /* Got an entry! Submit and exit. */ +- ret = blk_co_pdiscard(s->blk, +- sector << BDRV_SECTOR_BITS, +- count << BDRV_SECTOR_BITS, +- BDRV_REQ_NO_QUEUE); +- goto loop; ++ /* Got an entry! Submit and exit. */ ++ ret = blk_co_pdiscard(s->blk, ++ sector << BDRV_SECTOR_BITS, ++ count << BDRV_SECTOR_BITS, ++ BDRV_REQ_NO_QUEUE); ++ if (ret >= 0) { ++ block_acct_done(blk_get_stats(s->blk), &s->acct); ++ } else { ++ iocb->ret = ret; ++ block_acct_failed(blk_get_stats(s->blk), &s->acct); ++ goto done; + } +- +- iocb->j++; +- iocb->i = -1; + } +- } else { +- iocb->ret = ret; + } + ++ iocb->ret = 0; + done: + if (iocb->bh) { + replay_bh_schedule_event(iocb->bh); +@@ -533,8 +525,7 @@ BlockAIOCB *ide_issue_trim( + &DEVICE(dev)->mem_reentrancy_guard); + iocb->ret = 0; + iocb->qiov = qiov; +- iocb->i = -1; +- iocb->j = 0; ++ iocb->canceled = false; + + co = qemu_coroutine_create(ide_trim_co_entry, iocb); + aio_co_enter(qemu_get_current_aio_context(), co); +-- +2.52.0 + diff --git a/kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch b/kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch new file mode 100644 index 0000000..5a909f7 --- /dev/null +++ b/kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch @@ -0,0 +1,141 @@ +From 1af428a8371e7c95be02569a551095a93f17a4ac Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Tue, 21 Apr 2026 18:11:29 +0200 +Subject: [PATCH 4/8] ide: Minimal fix for deadlock between TRIM and drain + +RH-Author: Kevin Wolf +RH-MergeRequest: 562: ide: Fix deadlock between TRIM and drain [10.2.z] +RH-Jira: RHEL-190702 +RH-Acked-by: German Maglione +RH-Acked-by: Miroslav Rezanina +RH-Commit: [4/8] 339ddf749cae0dace22058df1998a80fa570d4b4 (kmwolf/rhel-qemu-kvm) + +The implementation of TRIM in IDE can chain multiple discard requests +and uses blk_inc/dec_in_flight() to make sure that the whole TRIM +operation has completed when the device needs to be quiescent (e.g. for +the drain when performing an IDE reset, it would be bad if an IDE +request like TRIM were still in flight). + +The problem is that each drain request calls blk_wait_while_drained() +and when draining, it waits until the drained section ends. At the same +time, drain_begin can only return if the whole TRIM operation has +completed. This is a classic deadlock. + +Use blk_co_start/end_request() and BDRV_REQ_NO_QUEUE to avoid the +problem. This requires moving the TRIM state machine to a coroutine. +This commit does the minimal conversion so that we do have a coroutine +that works for the fix, but it still looks much like a callback-based +implementation. This will be cleaned up in the next patch. + +Cc: qemu-stable@nongnu.org +Fixes: 7e5cdb345f77 ('ide: Increment BB in-flight counter for TRIM BH') +Buglink: https://redhat.atlassian.net/browse/RHEL-121686 +Signed-off-by: Kevin Wolf +Message-ID: <20260421161132.99878-5-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit 095c08a7ba68cabaa6e0ce7a8a0804a949542c4c) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + hw/ide/core.c | 37 ++++++++++++++++++------------------- + 1 file changed, 18 insertions(+), 19 deletions(-) + +diff --git a/hw/ide/core.c b/hw/ide/core.c +index b14983ec54..82c3ada14a 100644 +--- a/hw/ide/core.c ++++ b/hw/ide/core.c +@@ -420,7 +420,6 @@ typedef struct TrimAIOCB { + QEMUBH *bh; + int ret; + QEMUIOVector *qiov; +- BlockAIOCB *aiocb; + int i, j; + } TrimAIOCB; + +@@ -433,11 +432,6 @@ static void trim_aio_cancel(BlockAIOCB *acb) + iocb->i = (iocb->qiov->iov[iocb->j].iov_len / 8) - 1; + + iocb->ret = -ECANCELED; +- +- if (iocb->aiocb) { +- blk_aio_cancel_async(iocb->aiocb); +- iocb->aiocb = NULL; +- } + } + + static const AIOCBInfo trim_aiocb_info = { +@@ -456,15 +450,20 @@ static void ide_trim_bh_cb(void *opaque) + iocb->bh = NULL; + qemu_aio_unref(iocb); + +- /* Paired with an increment in ide_issue_trim() */ +- blk_dec_in_flight(blk); ++ /* Paired with blk_co_start_request in ide_trim_co_entry() */ ++ blk_end_request(blk); + } + +-static void ide_issue_trim_cb(void *opaque, int ret) ++static void coroutine_fn ide_trim_co_entry(void *opaque) + { + TrimAIOCB *iocb = opaque; + IDEState *s = iocb->s; ++ int ret = 0; ++ ++ /* Paired with blk_end_request in ide_trim_bh_cb() */ ++ blk_co_start_request(s->blk); + ++loop: + if (iocb->i >= 0) { + if (ret >= 0) { + block_acct_done(blk_get_stats(s->blk), &s->acct); +@@ -499,11 +498,11 @@ static void ide_issue_trim_cb(void *opaque, int ret) + count << BDRV_SECTOR_BITS, BLOCK_ACCT_UNMAP); + + /* Got an entry! Submit and exit. */ +- iocb->aiocb = blk_aio_pdiscard(s->blk, +- sector << BDRV_SECTOR_BITS, +- count << BDRV_SECTOR_BITS, +- ide_issue_trim_cb, opaque); +- return; ++ ret = blk_co_pdiscard(s->blk, ++ sector << BDRV_SECTOR_BITS, ++ count << BDRV_SECTOR_BITS, ++ BDRV_REQ_NO_QUEUE); ++ goto loop; + } + + iocb->j++; +@@ -514,7 +513,6 @@ static void ide_issue_trim_cb(void *opaque, int ret) + } + + done: +- iocb->aiocb = NULL; + if (iocb->bh) { + replay_bh_schedule_event(iocb->bh); + } +@@ -527,9 +525,7 @@ BlockAIOCB *ide_issue_trim( + IDEState *s = opaque; + IDEDevice *dev = s->unit ? s->bus->slave : s->bus->master; + TrimAIOCB *iocb; +- +- /* Paired with a decrement in ide_trim_bh_cb() */ +- blk_inc_in_flight(s->blk); ++ Coroutine *co; + + iocb = blk_aio_get(&trim_aiocb_info, s->blk, cb, cb_opaque); + iocb->s = s; +@@ -539,7 +535,10 @@ BlockAIOCB *ide_issue_trim( + iocb->qiov = qiov; + iocb->i = -1; + iocb->j = 0; +- ide_issue_trim_cb(iocb, 0); ++ ++ co = qemu_coroutine_create(ide_trim_co_entry, iocb); ++ aio_co_enter(qemu_get_current_aio_context(), co); ++ + return &iocb->common; + } + +-- +2.52.0 + diff --git a/kvm-ide-test-Factor-out-wait_dma_completion.patch b/kvm-ide-test-Factor-out-wait_dma_completion.patch new file mode 100644 index 0000000..512c497 --- /dev/null +++ b/kvm-ide-test-Factor-out-wait_dma_completion.patch @@ -0,0 +1,91 @@ +From 16cf591d59298ccc7cc1fe55427f2e2066d852d1 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Tue, 21 Apr 2026 18:11:31 +0200 +Subject: [PATCH 6/8] ide-test: Factor out wait_dma_completion() + +RH-Author: Kevin Wolf +RH-MergeRequest: 562: ide: Fix deadlock between TRIM and drain [10.2.z] +RH-Jira: RHEL-190702 +RH-Acked-by: German Maglione +RH-Acked-by: Miroslav Rezanina +RH-Commit: [6/8] 52ad17b4233e8589241928f2cbc8def83a0863de (kmwolf/rhel-qemu-kvm) + +Signed-off-by: Kevin Wolf +Message-ID: <20260421161132.99878-7-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit 92854c9c7539bdbf4f9c1abb33dd3ba59ff91e58) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + tests/qtest/ide-test.c | 48 +++++++++++++++++++++++++----------------- + 1 file changed, 29 insertions(+), 19 deletions(-) + +diff --git a/tests/qtest/ide-test.c b/tests/qtest/ide-test.c +index ceee444a9e..c6dcb2c074 100644 +--- a/tests/qtest/ide-test.c ++++ b/tests/qtest/ide-test.c +@@ -200,6 +200,34 @@ static uint64_t trim_range_le(uint64_t sector, uint16_t count) + return cpu_to_le64(((uint64_t)count << 48) + sector); + } + ++static uint8_t wait_dma_completion(QTestState *qts, QPCIDevice *dev, ++ QPCIBar bmdma_bar, QPCIBar ide_bar) ++{ ++ uint8_t status; ++ ++ /* Wait for the DMA transfer to complete */ ++ do { ++ status = qpci_io_readb(dev, bmdma_bar, bmreg_status); ++ } while ((status & (BM_STS_ACTIVE | BM_STS_INTR)) == BM_STS_ACTIVE); ++ ++ g_assert_cmpint(qtest_get_irq(qts, IDE_PRIMARY_IRQ), ==, ++ !!(status & BM_STS_INTR)); ++ ++ /* Check IDE status code */ ++ assert_bit_set(qpci_io_readb(dev, ide_bar, reg_status), DRDY); ++ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), BSY | DRQ); ++ ++ /* Reading the status register clears the IRQ */ ++ g_assert(!qtest_get_irq(qts, IDE_PRIMARY_IRQ)); ++ ++ /* Stop DMA transfer if still active */ ++ if (status & BM_STS_ACTIVE) { ++ qpci_io_writeb(dev, bmdma_bar, bmreg_cmd, 0); ++ } ++ ++ return status; ++} ++ + static int send_dma_request(QTestState *qts, int cmd, uint64_t sector, + int nb_sectors, PrdtEntry *prdt, int prdt_entries, + void(*post_exec)(QPCIDevice *dev, QPCIBar ide_bar, +@@ -280,25 +308,7 @@ static int send_dma_request(QTestState *qts, int cmd, uint64_t sector, + qpci_io_writeb(dev, bmdma_bar, bmreg_cmd, 0); + } + +- /* Wait for the DMA transfer to complete */ +- do { +- status = qpci_io_readb(dev, bmdma_bar, bmreg_status); +- } while ((status & (BM_STS_ACTIVE | BM_STS_INTR)) == BM_STS_ACTIVE); +- +- g_assert_cmpint(qtest_get_irq(qts, IDE_PRIMARY_IRQ), ==, +- !!(status & BM_STS_INTR)); +- +- /* Check IDE status code */ +- assert_bit_set(qpci_io_readb(dev, ide_bar, reg_status), DRDY); +- assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), BSY | DRQ); +- +- /* Reading the status register clears the IRQ */ +- g_assert(!qtest_get_irq(qts, IDE_PRIMARY_IRQ)); +- +- /* Stop DMA transfer if still active */ +- if (status & BM_STS_ACTIVE) { +- qpci_io_writeb(dev, bmdma_bar, bmreg_cmd, 0); +- } ++ status = wait_dma_completion(qts, dev, bmdma_bar, ide_bar); + + free_pci_device(dev); + +-- +2.52.0 + diff --git a/kvm-ide-test-Test-reset-during-TRIM.patch b/kvm-ide-test-Test-reset-during-TRIM.patch new file mode 100644 index 0000000..a67584e --- /dev/null +++ b/kvm-ide-test-Test-reset-during-TRIM.patch @@ -0,0 +1,181 @@ +From ffd0b4cfc9ad6b40eecc982c971bab08d8a3df4d Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Tue, 21 Apr 2026 18:11:32 +0200 +Subject: [PATCH 7/8] ide-test: Test reset during TRIM + +RH-Author: Kevin Wolf +RH-MergeRequest: 562: ide: Fix deadlock between TRIM and drain [10.2.z] +RH-Jira: RHEL-190702 +RH-Acked-by: German Maglione +RH-Acked-by: Miroslav Rezanina +RH-Commit: [7/8] e74433de82754973428769f19fb387acc34b0455 (kmwolf/rhel-qemu-kvm) + +This is a regression test for the bug fixed in the previous commits, a +deadlock between the drain issued by an IDE reset and the TRIM state +machine. + +Signed-off-by: Kevin Wolf +Message-ID: <20260421161132.99878-8-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit 2fa24e9755994f76f08ea2452215eb50f26f4c21) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + tests/qtest/ide-test.c | 95 ++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 87 insertions(+), 8 deletions(-) + +diff --git a/tests/qtest/ide-test.c b/tests/qtest/ide-test.c +index c6dcb2c074..721e78170b 100644 +--- a/tests/qtest/ide-test.c ++++ b/tests/qtest/ide-test.c +@@ -41,8 +41,11 @@ + #define IDE_PCI_FUNC 1 + + #define IDE_BASE 0x1f0 ++#define IDE_BASE2 0x3f6 + #define IDE_PRIMARY_IRQ 14 + ++#define IDE_CTRL_RESET 0x04 ++ + #define ATAPI_BLOCK_SIZE 2048 + + /* How many bytes to receive via ATAPI PIO at one time. +@@ -99,6 +102,7 @@ enum { + + CMDF_ABORT = 0x100, + CMDF_NO_BM = 0x200, ++ CMDF_NO_WAIT = 0x400, + }; + + enum { +@@ -228,21 +232,21 @@ static uint8_t wait_dma_completion(QTestState *qts, QPCIDevice *dev, + return status; + } + +-static int send_dma_request(QTestState *qts, int cmd, uint64_t sector, +- int nb_sectors, PrdtEntry *prdt, int prdt_entries, +- void(*post_exec)(QPCIDevice *dev, QPCIBar ide_bar, +- uint64_t sector, int nb_sectors)) ++static int send_dma_request_dev(QTestState *qts, QPCIDevice *dev, ++ QPCIBar bmdma_bar, QPCIBar ide_bar, int cmd, ++ uint64_t sector, int nb_sectors, ++ PrdtEntry *prdt, int prdt_entries, ++ void(*post_exec)(QPCIDevice *dev, ++ QPCIBar ide_bar, ++ uint64_t sector, ++ int nb_sectors)) + { +- QPCIDevice *dev; +- QPCIBar bmdma_bar, ide_bar; + uintptr_t guest_prdt; + size_t len; + bool from_dev; + uint8_t status; + int flags; + +- dev = get_pci_device(qts, &bmdma_bar, &ide_bar); +- + flags = cmd & ~0xff; + cmd &= 0xff; + +@@ -308,8 +312,28 @@ static int send_dma_request(QTestState *qts, int cmd, uint64_t sector, + qpci_io_writeb(dev, bmdma_bar, bmreg_cmd, 0); + } + ++ if (flags & CMDF_NO_WAIT) { ++ return 0; ++ } ++ + status = wait_dma_completion(qts, dev, bmdma_bar, ide_bar); + ++ return status; ++} ++ ++static int send_dma_request(QTestState *qts, int cmd, uint64_t sector, ++ int nb_sectors, PrdtEntry *prdt, int prdt_entries, ++ void(*post_exec)(QPCIDevice *dev, QPCIBar ide_bar, ++ uint64_t sector, int nb_sectors)) ++{ ++ QPCIDevice *dev; ++ QPCIBar bmdma_bar, ide_bar; ++ uint8_t status; ++ ++ dev = get_pci_device(qts, &bmdma_bar, &ide_bar); ++ status = send_dma_request_dev(qts, dev, bmdma_bar, ide_bar, ++ cmd, sector, nb_sectors, prdt, prdt_entries, ++ post_exec); + free_pci_device(dev); + + return status; +@@ -457,6 +481,60 @@ static void test_bmdma_trim(void) + test_bmdma_teardown(qts); + } + ++static void test_bmdma_trim_reset(void) ++{ ++ QTestState *qts; ++ QPCIDevice *dev; ++ QPCIBar bmdma_bar, ide_bar, ide_bar2; ++ uint8_t status; ++ const uint64_t trim_range[] = { ++ trim_range_le(0, 2), ++ trim_range_le(6, 8), ++ }; ++ size_t len = 512; ++ uint8_t *buf; ++ uintptr_t guest_buf; ++ PrdtEntry prdt[1]; ++ ++ qts = ide_test_start( ++ "-blockdev file,filename=%s,node-name=img " ++ "-blockdev blkdebug,image=img,node-name=dbg,discard=unmap," ++ "inject-error.0.event=none,inject-error.0.iotype=discard," ++ "inject-error.0.errno=0,inject-error.0.delay-ns=1000000 " ++ "-device ide-hd,drive=dbg,bus=ide.0", ++ tmp_path[0]); ++ qtest_irq_intercept_in(qts, "ioapic"); ++ ++ guest_buf = guest_alloc(&guest_malloc, len); ++ prdt[0].addr = cpu_to_le32(guest_buf), ++ prdt[0].size = cpu_to_le32(len | PRDT_EOT), ++ ++ dev = get_pci_device(qts, &bmdma_bar, &ide_bar); ++ ide_bar2 = qpci_legacy_iomap(dev, IDE_BASE2); ++ ++ buf = g_malloc(len); ++ ++ /* TRIM request with two segments */ ++ *((uint64_t *)buf) = trim_range[0]; ++ *((uint64_t *)buf + 1) = trim_range[1]; ++ ++ qtest_memwrite(qts, guest_buf, buf, 2 * sizeof(uint64_t)); ++ ++ send_dma_request_dev(qts, dev, bmdma_bar, ide_bar, CMD_DSM | CMDF_NO_WAIT, 0, 1, prdt, ++ ARRAY_SIZE(prdt), NULL); ++ ++ /* Reset the device while the first segment is in flight */ ++ qpci_io_writeb(dev, ide_bar2, 0, IDE_CTRL_RESET); ++ ++ status = wait_dma_completion(qts, dev, bmdma_bar, ide_bar); ++ g_assert_cmphex(status, ==, BM_STS_INTR); ++ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR); ++ ++ free_pci_device(dev); ++ g_free(buf); ++ test_bmdma_teardown(qts); ++} ++ + /* + * This test is developed according to the Programming Interface for + * Bus Master IDE Controller (Revision 1.0 5/16/94) +@@ -1138,6 +1216,7 @@ int main(int argc, char **argv) + + qtest_add_func("/ide/bmdma/simple_rw", test_bmdma_simple_rw); + qtest_add_func("/ide/bmdma/trim", test_bmdma_trim); ++ qtest_add_func("/ide/bmdma/trim_reset", test_bmdma_trim_reset); + qtest_add_func("/ide/bmdma/various_prdts", test_bmdma_various_prdts); + qtest_add_func("/ide/bmdma/no_busmaster", test_bmdma_no_busmaster); + +-- +2.52.0 + diff --git a/kvm-iotests-add-Linux-loop-device-image-creation-test.patch b/kvm-iotests-add-Linux-loop-device-image-creation-test.patch new file mode 100644 index 0000000..e36b28b --- /dev/null +++ b/kvm-iotests-add-Linux-loop-device-image-creation-test.patch @@ -0,0 +1,116 @@ +From f55e897a36ddca7116d08445a9237b6d34ceacd6 Mon Sep 17 00:00:00 2001 +From: Stefan Hajnoczi +Date: Tue, 7 Oct 2025 10:17:00 -0400 +Subject: [PATCH 3/3] iotests: add Linux loop device image creation test + +RH-Author: Kevin Wolf +RH-MergeRequest: 565: block: Fix image creation on storage with 4k sector size +RH-Jira: RHEL-207389 +RH-Acked-by: Hanna Czenczek +RH-Acked-by: German Maglione +RH-Acked-by: Stefan Hajnoczi +RH-Commit: [3/3] b878b225ca4cbe5d1ef012122472d58bda27ffff (kmwolf/rhel-qemu-kvm) + +This qemu-iotests test case is based on the reproducer that Jean-Louis +Dupond shared in +https://gitlab.com/qemu-project/qemu/-/issues/3127. + +Signed-off-by: Stefan Hajnoczi +Message-ID: <20251007141700.71891-4-stefanha@redhat.com> +Reviewed-by: Vladimir Sementsov-Ogievskiy +Tested-by: Vladimir Sementsov-Ogievskiy +Tested-by: Fiona Ebner +Reviewed-by: Fiona Ebner +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit 59a1cf0cd31597d2f6e2c18dc400a1de8427d47d) +Signed-off-by: Kevin Wolf +--- + tests/qemu-iotests/tests/loop-create-file | 59 +++++++++++++++++++ + tests/qemu-iotests/tests/loop-create-file.out | 8 +++ + 2 files changed, 67 insertions(+) + create mode 100755 tests/qemu-iotests/tests/loop-create-file + create mode 100644 tests/qemu-iotests/tests/loop-create-file.out + +diff --git a/tests/qemu-iotests/tests/loop-create-file b/tests/qemu-iotests/tests/loop-create-file +new file mode 100755 +index 0000000000..5ec75b046b +--- /dev/null ++++ b/tests/qemu-iotests/tests/loop-create-file +@@ -0,0 +1,59 @@ ++#!/usr/bin/env bash ++# group: quick ++# ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Copyright Red Hat, Inc. ++# ++# Test Linux loop device image creation ++# ++# This test verifies #3127 "qemu-img create fails on loop device with sector size 4096" ++# https://gitlab.com/qemu-project/qemu/-/issues/3127 ++ ++seq="$(basename $0)" ++echo "QA output created by $seq" ++ ++status=1 # failure is the default! ++ ++_cleanup() { ++ if [ -n "$loopdev" ]; then ++ sudo losetup --detach "$loopdev" ++ fi ++ ++ _cleanup_test_img ++} ++ ++trap "_cleanup; exit \$status" 0 1 2 3 15 ++ ++# get standard environment, filters and checks ++cd .. ++. ./common.rc ++. ./common.filter ++ ++_supported_fmt raw ++_supported_proto file ++_supported_os Linux ++ ++if ! sudo -n losetup &>/dev/null; then ++ _notrun "sudo losetup not available" ++fi ++ ++echo ++echo "=== Create image on a 4 KB sector size loop device ===" ++echo ++ ++_make_test_img -f $IMGFMT 1M ++ ++loopdev=$(sudo losetup --sector-size 4096 --find --show "$TEST_IMG") ++if [ -z "$loopdev" ]; then ++ _fail ++fi ++ ++sudo $QEMU_IMG_PROG create -f raw "$loopdev" 1M | \ ++ sed -e "s#/dev/loop[0-9]\\+#LOOPDEV#g" ++ ++# success, all done ++echo ++echo '*** done' ++rm -f $seq.full ++status=0 +diff --git a/tests/qemu-iotests/tests/loop-create-file.out b/tests/qemu-iotests/tests/loop-create-file.out +new file mode 100644 +index 0000000000..32d4155695 +--- /dev/null ++++ b/tests/qemu-iotests/tests/loop-create-file.out +@@ -0,0 +1,8 @@ ++QA output created by loop-create-file ++ ++=== Create image on a 4 KB sector size loop device === ++ ++Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 ++Formatting 'LOOPDEV', fmt=raw size=1048576 ++ ++*** done +-- +2.52.0 + diff --git a/kvm-virtio-blk-add-missing-VIRTIO_BLK_T_SCSI_CMD-size-ch.patch b/kvm-virtio-blk-add-missing-VIRTIO_BLK_T_SCSI_CMD-size-ch.patch new file mode 100644 index 0000000..f9d4bcc --- /dev/null +++ b/kvm-virtio-blk-add-missing-VIRTIO_BLK_T_SCSI_CMD-size-ch.patch @@ -0,0 +1,61 @@ +From fb2752faded838bfea82450db6a9a56510ea998f Mon Sep 17 00:00:00 2001 +From: Stefan Hajnoczi +Date: Tue, 26 May 2026 11:49:57 -0400 +Subject: [PATCH] virtio-blk: add missing VIRTIO_BLK_T_SCSI_CMD size check + (CVE-2026-48914) + +RH-Author: Stefan Hajnoczi +RH-MergeRequest: 560: virtio-blk: add missing VIRTIO_BLK_T_SCSI_CMD size check (CVE-2026-48914) +RH-Jira: RHEL-184529 +RH-Acked-by: Kevin Wolf +RH-Acked-by: Miroslav Rezanina +RH-Commit: [1/1] 2c669222671cda70b6fbf30b813e2e927cb8399d + +Check that the iovec containing struct virtio_scsi_inhdr is large enough +before storing an error value there. + +Feifan Qian pointed out that this can be used to +corrupt heap memory when the descriptor uses an MMIO address and a +length of 1, forcing QEMU to allocate a 1-byte heap bounce buffer. +virtio_stl_p() stores 4 bytes and therefore corrupts whatever is beyond +the bounce buffer. + +Fixes: CVE-2026-48914 +Fixes: f34e73cd69bd ("virtio-blk: report non-zero status when failing SG_IO requests") +Reported-by: Feifan Qian +Cc: Paolo Bonzini +Signed-off-by: Stefan Hajnoczi +Message-ID: <20260526154957.1741622-1-stefanha@redhat.com> +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit aeea0c2804c42f24915467a1e4c70e649e39b8e0) +Signed-off-by: Stefan Hajnoczi +--- + hw/block/virtio-blk.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c +index 9bab2716c1..a0678300b4 100644 +--- a/hw/block/virtio-blk.c ++++ b/hw/block/virtio-blk.c +@@ -200,10 +200,16 @@ static void virtio_blk_handle_scsi(VirtIOBlockReq *req) + + /* + * The scsi inhdr is placed in the second-to-last input segment, just +- * before the regular inhdr. ++ * before the regular inhdr. VIRTIO implementations normally do not rely on ++ * the precise message framing, but legacy implementations did and so we do ++ * too for the legacy virtio-blk SCSI request type. + * + * Just put anything nonzero so that the ioctl fails in the guest. + */ ++ if (elem->in_sg[elem->in_num - 2].iov_len != sizeof(*scsi)) { ++ status = VIRTIO_BLK_S_IOERR; ++ goto fail; ++ } + scsi = (void *)elem->in_sg[elem->in_num - 2].iov_base; + virtio_stl_p(vdev, &scsi->errors, 255); + status = VIRTIO_BLK_S_UNSUPP; +-- +2.52.0 + diff --git a/qemu-kvm.spec b/qemu-kvm.spec index 527134b..e2d9789 100644 --- a/qemu-kvm.spec +++ b/qemu-kvm.spec @@ -158,7 +158,7 @@ Obsoletes: %{name}-block-ssh <= %{epoch}:%{version} \ Summary: QEMU is a machine emulator and virtualizer Name: qemu-kvm Version: 10.1.0 -Release: 16%{?rcrel}%{?dist}%{?cc_suffix}.2.alma.1 +Release: 16%{?rcrel}%{?dist}%{?cc_suffix}.5.alma.1 # Epoch because we pushed a qemu-1.0 package. AIUI this can't ever be dropped # Epoch 15 used for RHEL 8 # Epoch 17 used for RHEL 9 (due to release versioning offset in RHEL 8.5) @@ -449,6 +449,28 @@ Patch138: kvm-scsi-register-again-after-PREEMPT-without-reservatio.patch Patch139: kvm-scsi-change-buf_size-to-unsigned-int-in-scsi_SG_IO.patch # For RHEL-166033 - live migration failed or get failed WSFC test result during WSFC testing [rhel-10.2.z] Patch140: kvm-scsi-handle-reservation-changes-across-migration.patch +# For RHEL-184529 - CVE-2026-48914 qemu-kvm: Heap buffer overflow in virtio-blk SCSI request handling [rhel-10.2.z] +Patch141: kvm-virtio-blk-add-missing-VIRTIO_BLK_T_SCSI_CMD-size-ch.patch +# For RHEL-190702 - qemu-kvm hung during drain after double pause [rhel-10.2.z] +Patch142: kvm-blkdebug-Add-delay-ns-option.patch +# For RHEL-190702 - qemu-kvm hung during drain after double pause [rhel-10.2.z] +Patch143: kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch +# For RHEL-190702 - qemu-kvm hung during drain after double pause [rhel-10.2.z] +Patch144: kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch +# For RHEL-190702 - qemu-kvm hung during drain after double pause [rhel-10.2.z] +Patch145: kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch +# For RHEL-190702 - qemu-kvm hung during drain after double pause [rhel-10.2.z] +Patch146: kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch +# For RHEL-190702 - qemu-kvm hung during drain after double pause [rhel-10.2.z] +Patch147: kvm-ide-test-Factor-out-wait_dma_completion.patch +# For RHEL-190702 - qemu-kvm hung during drain after double pause [rhel-10.2.z] +Patch148: kvm-ide-test-Test-reset-during-TRIM.patch +# For RHEL-207389 - qemu-img create/convert fails on target block device with 4k sector size [rhel-10.2.z] +Patch149: kvm-file-posix-populate-pwrite_zeroes_alignment.patch +# For RHEL-207389 - qemu-img create/convert fails on target block device with 4k sector size [rhel-10.2.z] +Patch150: kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch +# For RHEL-207389 - qemu-img create/convert fails on target block device with 4k sector size [rhel-10.2.z] +Patch151: kvm-iotests-add-Linux-loop-device-image-creation-test.patch # AlmaLinux Patch Patch2001: 2001-Add-ppc64-support.patch @@ -1177,6 +1199,7 @@ install -D -p -m 0644 %{modprobe_kvm_conf} $RPM_BUILD_ROOT%{_sysconfdir}/modprob mkdir -p %{buildroot}%{testsdir}/python mkdir -p %{buildroot}%{testsdir}/tests mkdir -p %{buildroot}%{testsdir}/tests/qemu-iotests +mkdir -p %{buildroot}%{testsdir}/tests/qtest mkdir -p %{buildroot}%{testsdir}/scripts/qmp @@ -1195,6 +1218,9 @@ cp -ur %{qemu_kvm_build}/tests/qemu-iotests/* %{buildroot}%{testsdir}/tests/qemu install -p -m 0644 %{_sourcedir}/README.tests %{buildroot}%{testsdir}/README +# Install qtests +find %{qemu_kvm_build}/tests/qtest/ -type f -executable -exec install -p -m 0755 {} %{buildroot}%{testsdir}/tests/qtest/ \; + # Do the actual qemu tree install pushd %{qemu_kvm_build} %make_install @@ -1577,12 +1603,36 @@ useradd -r -u 107 -g qemu -G kvm -d / -s /sbin/nologin \ %endif %changelog -* Tue Jul 21 2026 Eduard Abdullin - 18:10.1.0-16.2.alma.1 +* Mon Aug 24 2026 Eduard Abdullin - 18:10.1.0-16.5.alma.1 - Enable QXL device build - Enable building for ppc64le - Re-added Spice support - Don't remove slof.bin for ppc64le +* Tue Aug 04 2026 Miroslav Rezanina - 10.1.0-16.el10_2.5 +- kvm-file-posix-populate-pwrite_zeroes_alignment.patch [RHEL-207389] +- kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch [RHEL-207389] +- kvm-iotests-add-Linux-loop-device-image-creation-test.patch [RHEL-207389] +- Resolves: RHEL-207389 + (qemu-img create/convert fails on target block device with 4k sector size [rhel-10.2.z]) + +* Thu Jul 09 2026 Miroslav Rezanina - 10.1.0-16.el10_2.4 +- kvm-blkdebug-Add-delay-ns-option.patch [RHEL-190702] +- kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch [RHEL-190702] +- kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch [RHEL-190702] +- kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch [RHEL-190702] +- kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch [RHEL-190702] +- kvm-ide-test-Factor-out-wait_dma_completion.patch [RHEL-190702] +- kvm-ide-test-Test-reset-during-TRIM.patch [RHEL-190702] +- kvm-spec-Install-qtests-into-qemu-kvm-tests-package.patch [RHEL-190702] +- Resolves: RHEL-190702 + (qemu-kvm hung during drain after double pause [rhel-10.2.z]) + +* Fri Jun 19 2026 Miroslav Rezanina - 10.1.0-16.el10_2.3 +- kvm-virtio-blk-add-missing-VIRTIO_BLK_T_SCSI_CMD-size-ch.patch [RHEL-184529] +- Resolves: RHEL-184529 + (CVE-2026-48914 qemu-kvm: Heap buffer overflow in virtio-blk SCSI request handling [rhel-10.2.z]) + * Mon Jun 15 2026 Miroslav Rezanina - 10.1.0-16.el10_2.2 - kvm-scsi-adjust-error_prepend-formatting.patch [RHEL-166007] - kvm-scsi-always-send-valid-PREEMPT-TYPE-field.patch [RHEL-166007]